sellmate-design-system-react 9.0.0-beta.17 → 9.0.0-beta.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1203,6 +1203,45 @@ var Icon_close = (p) => {
1203
1203
  }
1204
1204
  );
1205
1205
  };
1206
+ var Icon_company = (p) => {
1207
+ const { size = 24, ...rest } = p;
1208
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1209
+ "svg",
1210
+ {
1211
+ width: size,
1212
+ height: size,
1213
+ viewBox: "0 0 24 24",
1214
+ fill: "none",
1215
+ xmlns: "http://www.w3.org/2000/svg",
1216
+ ...rest,
1217
+ children: [
1218
+ /* @__PURE__ */ jsxRuntime.jsx(
1219
+ "path",
1220
+ {
1221
+ d: "M14.25 8.5C14.6642 8.5 15 8.83579 15 9.25C15 9.66421 14.6642 10 14.25 10H9.75C9.33579 10 9 9.66421 9 9.25C9 8.83579 9.33579 8.5 9.75 8.5H14.25Z",
1222
+ fill: "currentColor"
1223
+ }
1224
+ ),
1225
+ /* @__PURE__ */ jsxRuntime.jsx(
1226
+ "path",
1227
+ {
1228
+ d: "M14.25 5.5C14.6642 5.5 15 5.83579 15 6.25C15 6.66421 14.6642 7 14.25 7H9.75C9.33579 7 9 6.66421 9 6.25C9 5.83579 9.33579 5.5 9.75 5.5H14.25Z",
1229
+ fill: "currentColor"
1230
+ }
1231
+ ),
1232
+ /* @__PURE__ */ jsxRuntime.jsx(
1233
+ "path",
1234
+ {
1235
+ fillRule: "evenodd",
1236
+ clipRule: "evenodd",
1237
+ d: "M16 2.25C16.9665 2.25 17.75 3.0335 17.75 4V6.25H20.25C21.2165 6.25 22 7.0335 22 8V20C22 20.9665 21.2165 21.75 20.25 21.75H3.75C2.7835 21.75 2 20.9665 2 20V10C2 9.0335 2.7835 8.25 3.75 8.25H6.25V4C6.25 3.0335 7.0335 2.25 8 2.25H16ZM3.75 9.75C3.61193 9.75 3.5 9.86193 3.5 10V20C3.5 20.1381 3.61193 20.25 3.75 20.25H6.25V9.75H3.75ZM8 3.75C7.86193 3.75 7.75 3.86193 7.75 4V20.25H9.25V16C9.25 15.0335 10.0335 14.25 11 14.25H13C13.9665 14.25 14.75 15.0335 14.75 16V20.25H16.25V4C16.25 3.86193 16.1381 3.75 16 3.75H8ZM11 15.75C10.8619 15.75 10.75 15.8619 10.75 16V20.25H13.25V16C13.25 15.8619 13.1381 15.75 13 15.75H11ZM17.75 20.25H20.25C20.3881 20.25 20.5 20.1381 20.5 20V8C20.5 7.86193 20.3881 7.75 20.25 7.75H17.75V20.25Z",
1238
+ fill: "currentColor"
1239
+ }
1240
+ )
1241
+ ]
1242
+ }
1243
+ );
1244
+ };
1206
1245
  var Icon_copy = (p) => {
1207
1246
  const { size = 24, ...rest } = p;
1208
1247
  return /* @__PURE__ */ jsxRuntime.jsxs(
@@ -2426,6 +2465,27 @@ var Icon_row = (p) => {
2426
2465
  }
2427
2466
  );
2428
2467
  };
2468
+ var Icon_rowDense = (p) => {
2469
+ const { size = 24, ...rest } = p;
2470
+ return /* @__PURE__ */ jsxRuntime.jsx(
2471
+ "svg",
2472
+ {
2473
+ width: size,
2474
+ height: size,
2475
+ viewBox: "0 0 24 24",
2476
+ fill: "none",
2477
+ xmlns: "http://www.w3.org/2000/svg",
2478
+ ...rest,
2479
+ children: /* @__PURE__ */ jsxRuntime.jsx(
2480
+ "path",
2481
+ {
2482
+ d: "M7.25 2C7.66421 2 8 2.33579 8 2.75V21.25C8 21.6642 7.66421 22 7.25 22H5.75C5.33579 22 5 21.6642 5 21.25V2.75C5 2.33579 5.33579 2 5.75 2H7.25ZM12.75 2C13.1642 2 13.5 2.33579 13.5 2.75V21.25C13.5 21.6642 13.1642 22 12.75 22H11.25C10.8358 22 10.5 21.6642 10.5 21.25V2.75C10.5 2.33579 10.8358 2 11.25 2H12.75ZM18.25 2C18.6642 2 19 2.33579 19 2.75V21.25C19 21.6642 18.6642 22 18.25 22H16.75C16.3358 22 16 21.6642 16 21.25V2.75C16 2.33579 16.3358 2 16.75 2H18.25Z",
2483
+ fill: "currentColor"
2484
+ }
2485
+ )
2486
+ }
2487
+ );
2488
+ };
2429
2489
  var Icon_save = (p) => {
2430
2490
  const { size = 24, ...rest } = p;
2431
2491
  return /* @__PURE__ */ jsxRuntime.jsx(
@@ -3074,6 +3134,7 @@ var ICONS = {
3074
3134
  clockFill: Icon_clockFill,
3075
3135
  clockOutline: Icon_clockOutline,
3076
3136
  close: Icon_close,
3137
+ company: Icon_company,
3077
3138
  copy: Icon_copy,
3078
3139
  coupon: Icon_coupon,
3079
3140
  dashboard: Icon_dashboard,
@@ -3111,6 +3172,7 @@ var ICONS = {
3111
3172
  reset: Icon_reset,
3112
3173
  robot: Icon_robot,
3113
3174
  row: Icon_row,
3175
+ rowDense: Icon_rowDense,
3114
3176
  save: Icon_save,
3115
3177
  search: Icon_search,
3116
3178
  send: Icon_send,
@@ -6104,6 +6166,7 @@ var STextLink = /* @__PURE__ */ react.forwardRef(function STextLink2({
6104
6166
  label = "",
6105
6167
  icon,
6106
6168
  iconColor,
6169
+ iconRotate,
6107
6170
  labelClass,
6108
6171
  rightArrow = "none",
6109
6172
  underline = false,
@@ -6146,7 +6209,7 @@ var STextLink = /* @__PURE__ */ react.forwardRef(function STextLink2({
6146
6209
  style: { gap: s.gap, color: contentColor, ...style },
6147
6210
  ...rest,
6148
6211
  children: [
6149
- icon && /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: icon, size: s.iconSize, color: resolvedIconColor }),
6212
+ icon && /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: icon, size: s.iconSize, color: resolvedIconColor, rotate: iconRotate }),
6150
6213
  /* @__PURE__ */ jsxRuntime.jsx(
6151
6214
  "span",
6152
6215
  {
@@ -11124,11 +11187,13 @@ var STable = /* @__PURE__ */ react.forwardRef(function STable2({
11124
11187
  noDataSlot,
11125
11188
  isLoading = false,
11126
11189
  dense = false,
11190
+ onDenseChange,
11127
11191
  noHover = false,
11128
11192
  pagination,
11129
11193
  onPageChange,
11130
11194
  useInternalPagination = false,
11131
11195
  useRowsPerPageSelect = false,
11196
+ useDensityToggle = false,
11132
11197
  rowsPerPageOption = DEFAULT_ROWS_PER_PAGE_OPTION,
11133
11198
  onRowsPerPageChange,
11134
11199
  useVirtualScroll = false,
@@ -11524,21 +11589,7 @@ var STable = /* @__PURE__ */ react.forwardRef(function STable2({
11524
11589
  col.align === "right" && "justify-end"
11525
11590
  ),
11526
11591
  children: col.renderHeader ? col.renderHeader(col, i) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
11527
- col.icon && /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: col.icon, size: 16, color: col.iconColor }),
11528
11592
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: col.label }),
11529
- isSortable && // 미정렬은 updown 을 default(회색)로 — 정렬할 수 있다는 것 자체가
11530
- // 헤더에 보여야 한다. 정렬 중이면 action 색으로 올려 활성임을 알린다.
11531
- // 라벨이 truncate 돼도 버튼은 남도록 shrink-0.
11532
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "inline-flex shrink-0", children: /* @__PURE__ */ jsxRuntime.jsx(
11533
- SGhostButton,
11534
- {
11535
- size: "xxs",
11536
- intent: sortDir ? "action" : "default",
11537
- icon: sortDir === "asc" ? "arrowUp" : sortDir === "desc" ? "arrowDown" : "updown",
11538
- ariaLabel: `${col.label} \uC815\uB82C`,
11539
- onClick: () => toggleSort(col)
11540
- }
11541
- ) }),
11542
11593
  col.helpText && col.helpText.length > 0 && // 라벨이 좁아져 truncate 되더라도 아이콘까지 함께 줄어들면 안 되므로
11543
11594
  // shrink-0 래퍼로 감싼다 (STooltip 의 className 은 본문 쪽으로 간다).
11544
11595
  // 아이콘·크기·색은 SKeyValueTable 헤더 도움말과 동일하게 맞춘다.
@@ -11552,6 +11603,50 @@ var STable = /* @__PURE__ */ react.forwardRef(function STable2({
11552
11603
  placement: "top",
11553
11604
  message: col.helpText
11554
11605
  }
11606
+ ) }),
11607
+ col.editable && /* @__PURE__ */ jsxRuntime.jsx(
11608
+ SIcon,
11609
+ {
11610
+ name: "pageEdit",
11611
+ size: 12,
11612
+ color: "var(--sys-color-link-accent)",
11613
+ label: "\uC218\uC815 \uAC00\uB2A5",
11614
+ className: "shrink-0"
11615
+ }
11616
+ ),
11617
+ col.navigable && /* @__PURE__ */ jsxRuntime.jsx(
11618
+ SIcon,
11619
+ {
11620
+ name: "pageMove",
11621
+ size: 12,
11622
+ color: "var(--sys-color-link-accent)",
11623
+ label: "\uB2E4\uB978 \uD654\uBA74\uC73C\uB85C \uC774\uB3D9",
11624
+ className: "shrink-0"
11625
+ }
11626
+ ),
11627
+ col.required && // 라벨이 truncate 돼도 필수 표시는 남아야 하므로 shrink-0.
11628
+ // 아이콘·크기·색은 SKeyValueTable 헤더 필수 표시와 동일하게 맞춘다.
11629
+ /* @__PURE__ */ jsxRuntime.jsx(
11630
+ SIcon,
11631
+ {
11632
+ name: "asterisk",
11633
+ size: 12,
11634
+ color: "var(--sys-color-fg-accent)",
11635
+ className: "shrink-0"
11636
+ }
11637
+ ),
11638
+ isSortable && // 미정렬은 updown 을 default(회색)로 — 정렬할 수 있다는 것 자체가
11639
+ // 헤더에 보여야 한다. 정렬 중이면 action 색으로 올려 활성임을 알린다.
11640
+ // 라벨이 truncate 돼도 버튼은 남도록 shrink-0.
11641
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "inline-flex shrink-0", children: /* @__PURE__ */ jsxRuntime.jsx(
11642
+ SGhostButton,
11643
+ {
11644
+ size: "xxs",
11645
+ intent: sortDir ? "action" : "default",
11646
+ icon: sortDir === "asc" ? "arrowUp" : sortDir === "desc" ? "arrowDown" : "updown",
11647
+ ariaLabel: `${col.label} \uC815\uB82C`,
11648
+ onClick: () => toggleSort(col)
11649
+ }
11555
11650
  ) })
11556
11651
  ] })
11557
11652
  }
@@ -11709,16 +11804,32 @@ var STable = /* @__PURE__ */ react.forwardRef(function STable2({
11709
11804
  ),
11710
11805
  children: [
11711
11806
  /* @__PURE__ */ jsxRuntime.jsx(SPagination, { currentPage: displayPage, lastPage, onPageChange: changePage }),
11712
- useRowsPerPageSelect && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute right-[10px] top-1/2 -translate-y-1/2", children: /* @__PURE__ */ jsxRuntime.jsx(
11713
- SSelect2,
11714
- {
11715
- options: rowsPerPageOption,
11716
- value: useInternalPagination ? innerRowsPerPage : pagination?.rowsPerPage,
11717
- emitValue: true,
11718
- onValueChange: (v) => changeRowsPerPage(Number(v)),
11719
- width: 128
11720
- }
11721
- ) })
11807
+ (useDensityToggle || useRowsPerPageSelect) && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute right-[10px] top-1/2 flex -translate-y-1/2 items-center gap-[12px]", children: [
11808
+ useDensityToggle && // 라벨·아이콘은 현재 밀도가 아니라 **누르면 되는 상태**를 가리킨다 (dense 면 "넓게 보기").
11809
+ // row·rowDense 는 세로 막대라 90도 돌려 가로 행으로 쓴다.
11810
+ /* @__PURE__ */ jsxRuntime.jsx(
11811
+ STextLink,
11812
+ {
11813
+ size: "sm",
11814
+ icon: dense ? "row" : "rowDense",
11815
+ iconRotate: 90,
11816
+ iconColor: "var(--sys-color-fg-quaternary)",
11817
+ label: dense ? "\uB113\uAC8C \uBCF4\uAE30" : "\uC881\uAC8C \uBCF4\uAE30",
11818
+ onClick: () => onDenseChange?.(!dense)
11819
+ }
11820
+ ),
11821
+ useDensityToggle && useRowsPerPageSelect && /* @__PURE__ */ jsxRuntime.jsx(SDivider, { vertical: true, className: "my-[6px]" }),
11822
+ useRowsPerPageSelect && /* @__PURE__ */ jsxRuntime.jsx(
11823
+ SSelect2,
11824
+ {
11825
+ options: rowsPerPageOption,
11826
+ value: useInternalPagination ? innerRowsPerPage : pagination?.rowsPerPage,
11827
+ emitValue: true,
11828
+ onValueChange: (v) => changeRowsPerPage(Number(v)),
11829
+ width: 128
11830
+ }
11831
+ )
11832
+ ] })
11722
11833
  ]
11723
11834
  }
11724
11835
  )
@@ -12814,6 +12925,11 @@ function SCalendar({
12814
12925
  }
12815
12926
  );
12816
12927
  }
12928
+ var MODE_NOUN = {
12929
+ date: "\uB0A0\uC9DC",
12930
+ month: "\uC6D4",
12931
+ year: "\uC5F0\uB3C4"
12932
+ };
12817
12933
  var DATEPICKER_MODE_LIST_HEIGHT = 240;
12818
12934
  var DATEPICKER_MODE_ITEM_HEIGHT = 28;
12819
12935
  var YEAR_LIST_EDGE_BUFFER = 24;
@@ -13085,7 +13201,9 @@ function SDatePicker({
13085
13201
  placeholder,
13086
13202
  selectable,
13087
13203
  disabled = false,
13204
+ clearable = false,
13088
13205
  width,
13206
+ maxWidth,
13089
13207
  name,
13090
13208
  rules,
13091
13209
  status,
@@ -13128,7 +13246,8 @@ function SDatePicker({
13128
13246
  fieldMinW: 168,
13129
13247
  // 값 길이가 YYYY-MM-DD 로 정해져 있어, width="100%" 를 받아도 행 전체로
13130
13248
  // 늘어나지 않게 상한을 건다 (SKeyValueTable 이 모든 컨트롤에 100% 를 넘긴다).
13131
- // px 가 아니라 등급이라 --cmp-field-width-* 가 바뀌면 함께 따라간다
13249
+ // px 가 아니라 등급이라 --cmp-field-width-* 가 바뀌면 함께 따라간다.
13250
+ // 이 기본 상한이 걸리적거리는 자리(폭이 이미 좁게 정해진 팝오버 등)는 maxWidth prop 으로 덮는다
13132
13251
  fieldMaxW: "lg",
13133
13252
  fontSize: 14,
13134
13253
  lineHeight: 24,
@@ -13149,6 +13268,10 @@ function SDatePicker({
13149
13268
  if (rules && rules.length > 0) setRuleError(runRules(date, rules));
13150
13269
  setOpen(false);
13151
13270
  };
13271
+ const clear = () => {
13272
+ onValueChange?.(null);
13273
+ if (rules && rules.length > 0) setRuleError(runRules(null, rules));
13274
+ };
13152
13275
  const selectListboxValue = (nextValue) => {
13153
13276
  onValueChange?.(nextValue);
13154
13277
  if (rules && rules.length > 0) setRuleError(runRules(nextValue, rules));
@@ -13172,20 +13295,31 @@ function SDatePicker({
13172
13295
  errorMessage: ruleError !== "" ? ruleError : errorMessage,
13173
13296
  width,
13174
13297
  minWidth: dims.fieldMinW,
13175
- maxWidth: dims.fieldMaxW,
13298
+ maxWidth: maxWidth ?? dims.fieldMaxW,
13176
13299
  disabled,
13177
13300
  className,
13178
13301
  style,
13179
13302
  children: /* @__PURE__ */ jsxRuntime.jsxs(RPopover10__namespace.Root, { open, onOpenChange: (o) => !disabled && setOpen(o), children: [
13180
13303
  /* @__PURE__ */ jsxRuntime.jsx(RPopover10__namespace.Trigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs(
13181
- "button",
13304
+ "div",
13182
13305
  {
13183
- type: "button",
13184
- name,
13185
- disabled,
13306
+ role: "button",
13307
+ "aria-disabled": disabled,
13308
+ tabIndex: disabled ? -1 : 0,
13309
+ "data-name": name,
13186
13310
  onFocus: () => setFocused(true),
13187
13311
  onBlur: () => setFocused(false),
13188
- className: "flex h-full w-full items-center overflow-hidden bg-transparent outline-none disabled:cursor-not-allowed",
13312
+ onKeyDown: (event) => {
13313
+ if (disabled) return;
13314
+ if (event.key === "Enter" || event.key === " ") {
13315
+ event.preventDefault();
13316
+ setOpen(!open);
13317
+ }
13318
+ },
13319
+ className: cn(
13320
+ "flex h-full w-full items-center overflow-hidden bg-transparent outline-none",
13321
+ disabled ? "cursor-not-allowed" : "cursor-pointer"
13322
+ ),
13189
13323
  style: { padding: `0 ${dims.padX}px`, gap: dims.gap, borderRadius: dims.radius },
13190
13324
  children: [
13191
13325
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -13209,6 +13343,18 @@ function SDatePicker({
13209
13343
  },
13210
13344
  children: hasValue ? value : resolvedPlaceholder
13211
13345
  }
13346
+ ),
13347
+ clearable && hasValue && !disabled && /* @__PURE__ */ jsxRuntime.jsx(
13348
+ SGhostButton,
13349
+ {
13350
+ icon: "close",
13351
+ size: "xxs",
13352
+ ariaLabel: `${MODE_NOUN[mode]} \uC9C0\uC6B0\uAE30`,
13353
+ onClick: (event) => {
13354
+ event.stopPropagation();
13355
+ clear();
13356
+ }
13357
+ }
13212
13358
  )
13213
13359
  ]
13214
13360
  }
@@ -13588,6 +13734,7 @@ function SDateRangePicker({
13588
13734
  maxRange,
13589
13735
  useTimePicker = false,
13590
13736
  disabled = false,
13737
+ clearable = false,
13591
13738
  width,
13592
13739
  name,
13593
13740
  rules,
@@ -13655,6 +13802,11 @@ function SDateRangePicker({
13655
13802
  if (rules && rules.length > 0) setRuleError(runRules(r, rules));
13656
13803
  if (!useTimePicker) setOpen(false);
13657
13804
  };
13805
+ const clear = () => {
13806
+ setPendingStart(null);
13807
+ onValueChange?.(null);
13808
+ if (rules && rules.length > 0) setRuleError(runRules(null, rules));
13809
+ };
13658
13810
  const resolvedPlaceholder = useTimePicker && placeholder === "YYYY-MM-DD ~ YYYY-MM-DD" ? "YYYY-MM-DD HH:mm ~ YYYY-MM-DD HH:mm" : placeholder;
13659
13811
  return /* @__PURE__ */ jsxRuntime.jsx(
13660
13812
  SField,
@@ -13690,12 +13842,23 @@ function SDateRangePicker({
13690
13842
  },
13691
13843
  children: [
13692
13844
  /* @__PURE__ */ jsxRuntime.jsx(RPopover10__namespace.Trigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs(
13693
- "button",
13845
+ "div",
13694
13846
  {
13695
- type: "button",
13696
- name,
13697
- disabled,
13698
- className: "flex h-full w-full items-center overflow-hidden bg-transparent outline-none disabled:cursor-not-allowed",
13847
+ role: "button",
13848
+ "aria-disabled": disabled,
13849
+ tabIndex: disabled ? -1 : 0,
13850
+ "data-name": name,
13851
+ onKeyDown: (event) => {
13852
+ if (disabled) return;
13853
+ if (event.key === "Enter" || event.key === " ") {
13854
+ event.preventDefault();
13855
+ setOpen(!open);
13856
+ }
13857
+ },
13858
+ className: cn(
13859
+ "flex h-full w-full items-center overflow-hidden bg-transparent outline-none",
13860
+ disabled ? "cursor-not-allowed" : "cursor-pointer"
13861
+ ),
13699
13862
  style: { padding: `0 ${dims.padX}px`, gap: dims.gap, borderRadius: dims.radius },
13700
13863
  children: [
13701
13864
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -13726,6 +13889,18 @@ function SDateRangePicker({
13726
13889
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 flex-1 truncate text-left", children: endText })
13727
13890
  ] }) : resolvedPlaceholder
13728
13891
  }
13892
+ ),
13893
+ clearable && hasDisplay && !disabled && /* @__PURE__ */ jsxRuntime.jsx(
13894
+ SGhostButton,
13895
+ {
13896
+ icon: "close",
13897
+ size: "xxs",
13898
+ ariaLabel: "\uAE30\uAC04 \uC9C0\uC6B0\uAE30",
13899
+ onClick: (event) => {
13900
+ event.stopPropagation();
13901
+ clear();
13902
+ }
13903
+ }
13729
13904
  )
13730
13905
  ]
13731
13906
  }
@@ -14990,6 +15165,7 @@ var SChipFilter = /* @__PURE__ */ react.forwardRef(function SChipFilter2({
14990
15165
  );
14991
15166
  const [openKey, setOpenKey] = react.useState(null);
14992
15167
  const [addOpen, setAddOpen] = react.useState(false);
15168
+ const pendingOpenKeyRef = react.useRef(null);
14993
15169
  const triggerRefs = react.useRef(/* @__PURE__ */ new Map());
14994
15170
  const initializedDefaultsRef = react.useRef(/* @__PURE__ */ new Set());
14995
15171
  const prevOpenKeyRef = react.useRef(null);
@@ -15065,6 +15241,7 @@ var SChipFilter = /* @__PURE__ */ react.forwardRef(function SChipFilter2({
15065
15241
  const hasAddButton = fields.some(isAddableField);
15066
15242
  const hasRightActions = hasAddButton || showReset;
15067
15243
  const currentOpenField = fields.find((field) => field.key === openKey);
15244
+ const menuOpen = openKey != null || addOpen;
15068
15245
  const editorPopoverStyle = react.useMemo(() => {
15069
15246
  if (currentOpenField?.type === "keyword") {
15070
15247
  return { width: CHIPFILTER_LISTBOX_WIDTH };
@@ -15123,7 +15300,13 @@ var SChipFilter = /* @__PURE__ */ react.forwardRef(function SChipFilter2({
15123
15300
  onAddFilter?.(key);
15124
15301
  setAddOpen(false);
15125
15302
  const field = fields.find((f) => f.key === key);
15126
- if (field && !isInlineField(field)) requestAnimationFrame(() => setOpenKey(key));
15303
+ if (field && !isInlineField(field)) {
15304
+ pendingOpenKeyRef.current = key;
15305
+ requestAnimationFrame(() => {
15306
+ triggerRefs.current.get(key)?.focus();
15307
+ setOpenKey(key);
15308
+ });
15309
+ }
15127
15310
  };
15128
15311
  const rootStyle = react.useMemo(
15129
15312
  () => ({
@@ -15166,6 +15349,7 @@ var SChipFilter = /* @__PURE__ */ react.forwardRef(function SChipFilter2({
15166
15349
  {
15167
15350
  message,
15168
15351
  open: invalidGroups.has(runGroupIndex),
15352
+ placement: menuOpen ? "top" : "bottom",
15169
15353
  children: runBuffer
15170
15354
  },
15171
15355
  `group-${runGroupIndex}`
@@ -15179,14 +15363,16 @@ var SChipFilter = /* @__PURE__ */ react.forwardRef(function SChipFilter2({
15179
15363
  activeFields.forEach((field, index) => {
15180
15364
  const inline = isInlineField(field);
15181
15365
  const groupIndex = groupIndexByKey.get(field.key) ?? 0;
15182
- const warning = field.warning || invalidGroups.has(groupIndex);
15366
+ const warning = invalidGroups.has(groupIndex);
15183
15367
  const prevField = index > 0 ? activeFields[index - 1] : null;
15184
15368
  const prevGroupIndex = prevField ? groupIndexByKey.get(prevField.key) ?? 0 : null;
15185
15369
  const needsLeadingDivider = index === 0 ? false : inline || prevField != null && isInlineField(prevField) || groupIndex !== prevGroupIndex && groups[groupIndex]?.divider === true;
15370
+ const hoistDivider = needsLeadingDivider && groupIndex !== runGroupIndex;
15186
15371
  if (groupIndex !== runGroupIndex) {
15187
15372
  flushRun();
15188
15373
  runGroupIndex = groupIndex;
15189
15374
  }
15375
+ if (hoistDivider) nodes.push(/* @__PURE__ */ jsxRuntime.jsx(Divider, { inset: true }, `${field.key}-divider`));
15190
15376
  const addable = isAddableField(field);
15191
15377
  const showClear = addable || !isAtDefaultValue(field, values[field.key]);
15192
15378
  const handleClear = () => {
@@ -15250,7 +15436,7 @@ var SChipFilter = /* @__PURE__ */ react.forwardRef(function SChipFilter2({
15250
15436
  field.key
15251
15437
  );
15252
15438
  runBuffer.push(
15253
- needsLeadingDivider ? /* @__PURE__ */ jsxRuntime.jsxs(react.Fragment, { children: [
15439
+ needsLeadingDivider && !hoistDivider ? /* @__PURE__ */ jsxRuntime.jsxs(react.Fragment, { children: [
15254
15440
  /* @__PURE__ */ jsxRuntime.jsx(Divider, { inset: true }),
15255
15441
  node
15256
15442
  ] }, `${field.key}-wrap`) : node
@@ -15298,6 +15484,11 @@ var SChipFilter = /* @__PURE__ */ react.forwardRef(function SChipFilter2({
15298
15484
  side: "bottom",
15299
15485
  align: "start",
15300
15486
  sideOffset: 4,
15487
+ onCloseAutoFocus: (event) => {
15488
+ if (pendingOpenKeyRef.current == null) return;
15489
+ pendingOpenKeyRef.current = null;
15490
+ event.preventDefault();
15491
+ },
15301
15492
  className: cn("z-50 min-w-[180px]", FLOATING_SLIDE_ANIM),
15302
15493
  children: /* @__PURE__ */ jsxRuntime.jsx(ListBox, { children: inactiveFields.length > 0 ? inactiveFields.map((field) => /* @__PURE__ */ jsxRuntime.jsx(MenuItem, { onClick: () => addField(field.key), children: field.label }, field.key)) : /* @__PURE__ */ jsxRuntime.jsx(EmptyItem, { children: "\uCD94\uAC00\uD560 \uD544\uD130\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4." }) })
15303
15494
  }
@@ -15348,6 +15539,7 @@ var SChipFilter = /* @__PURE__ */ react.forwardRef(function SChipFilter2({
15348
15539
  value: values[currentOpenField.key],
15349
15540
  disabled: disabled || currentOpenField.disabled,
15350
15541
  onValueChange: (next) => emitValue(currentOpenField.key, next),
15542
+ onValueCommit: (next) => emitValueAndSearch(currentOpenField.key, next),
15351
15543
  onClose: () => setOpenKey(null)
15352
15544
  }
15353
15545
  )
@@ -15375,9 +15567,20 @@ function Divider({ inset, top } = {}) {
15375
15567
  function GroupTooltip({
15376
15568
  message,
15377
15569
  open,
15570
+ placement,
15378
15571
  children
15379
15572
  }) {
15380
- return /* @__PURE__ */ jsxRuntime.jsx(STooltip2, { trigger: "none", open, placement: "top", tooltipType: "warning", message: [message], children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap items-center gap-[var(--cmp-chipFilter-chip-group)]", children }) });
15573
+ return /* @__PURE__ */ jsxRuntime.jsx(
15574
+ STooltip2,
15575
+ {
15576
+ trigger: "none",
15577
+ open,
15578
+ placement,
15579
+ tooltipType: "warning",
15580
+ message: [message],
15581
+ children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap items-center gap-[var(--cmp-chipFilter-chip-group)]", children })
15582
+ }
15583
+ );
15381
15584
  }
15382
15585
  function FilterChip({
15383
15586
  field,
@@ -15487,6 +15690,7 @@ function EditorPanel({
15487
15690
  value,
15488
15691
  disabled,
15489
15692
  onValueChange,
15693
+ onValueCommit,
15490
15694
  onClose
15491
15695
  }) {
15492
15696
  const wrapsInPaddedTop = field.type === "date" || field.type === "period";
@@ -15506,7 +15710,8 @@ function EditorPanel({
15506
15710
  field,
15507
15711
  value,
15508
15712
  disabled,
15509
- onValueChange
15713
+ onValueChange,
15714
+ onValueCommit
15510
15715
  }
15511
15716
  ) }) : /* @__PURE__ */ jsxRuntime.jsx(
15512
15717
  FilterEditor,
@@ -15514,7 +15719,8 @@ function EditorPanel({
15514
15719
  field,
15515
15720
  value,
15516
15721
  disabled,
15517
- onValueChange
15722
+ onValueChange,
15723
+ onValueCommit
15518
15724
  }
15519
15725
  ),
15520
15726
  /* @__PURE__ */ jsxRuntime.jsxs(
@@ -15566,7 +15772,8 @@ function FilterEditor({
15566
15772
  field,
15567
15773
  value,
15568
15774
  disabled,
15569
- onValueChange
15775
+ onValueChange,
15776
+ onValueCommit
15570
15777
  }) {
15571
15778
  if (field.type === "period") {
15572
15779
  return /* @__PURE__ */ jsxRuntime.jsx(
@@ -15597,7 +15804,8 @@ function FilterEditor({
15597
15804
  field,
15598
15805
  value: isKeywordValue(value) ? value : keywordEntries(value),
15599
15806
  disabled,
15600
- onValueChange
15807
+ onValueChange,
15808
+ onValueCommit
15601
15809
  }
15602
15810
  );
15603
15811
  }
@@ -15639,7 +15847,9 @@ function DatePresetEditor({
15639
15847
  onValueChange,
15640
15848
  selectable: field.selectable,
15641
15849
  disabled,
15642
- placeholder: field.placeholder
15850
+ placeholder: field.placeholder,
15851
+ width: "100%",
15852
+ maxWidth: "100%"
15643
15853
  }
15644
15854
  );
15645
15855
  }
@@ -15699,7 +15909,8 @@ function PeriodPresetEditor({
15699
15909
  onValueChange: updateInputValue,
15700
15910
  selectable: field.selectable,
15701
15911
  disabled,
15702
- width: "100%"
15912
+ width: "100%",
15913
+ maxWidth: "100%"
15703
15914
  }
15704
15915
  );
15705
15916
  }
@@ -15712,7 +15923,8 @@ function PeriodPresetEditor({
15712
15923
  onValueChange: updateInputValue,
15713
15924
  selectable: field.selectable,
15714
15925
  disabled,
15715
- width: "100%"
15926
+ width: "100%",
15927
+ maxWidth: "100%"
15716
15928
  }
15717
15929
  );
15718
15930
  }
@@ -15759,7 +15971,8 @@ function PeriodPresetEditor({
15759
15971
  onValueChange: updateInputValue,
15760
15972
  selectable: field.selectable,
15761
15973
  disabled,
15762
- width: "100%"
15974
+ width: "100%",
15975
+ maxWidth: "100%"
15763
15976
  }
15764
15977
  );
15765
15978
  }
@@ -15909,6 +16122,7 @@ function InlineDatePresetFilter({
15909
16122
  ) : badge)
15910
16123
  ] });
15911
16124
  }
16125
+ var CSV_PASTE_SEPARATOR = /[,\r\n\t]+/;
15912
16126
  var MATCH_MODE_OPTIONS = [
15913
16127
  { value: "contains", label: "\uD3EC\uD568" },
15914
16128
  { value: "exact", label: "\uC77C\uCE58" }
@@ -15921,20 +16135,61 @@ function KeywordEditor({
15921
16135
  field,
15922
16136
  value,
15923
16137
  disabled,
15924
- onValueChange
16138
+ onValueChange,
16139
+ onValueCommit
15925
16140
  }) {
15926
16141
  const entries3 = keywordEntries(value);
15927
16142
  const [draft, setDraft] = react.useState("");
15928
16143
  const [mode, setMode] = react.useState(
15929
16144
  isKeywordValue(value) ? value.mode : "contains"
15930
16145
  );
15931
- const emit3 = (nextEntries) => {
15932
- onValueChange(field.matchModes ? { keywords: nextEntries, mode } : nextEntries);
16146
+ const inputRef = react.useRef(null);
16147
+ react.useEffect(() => {
16148
+ if (disabled) return;
16149
+ inputRef.current?.focus();
16150
+ }, [disabled]);
16151
+ const emit3 = (nextEntries, nextMode = mode) => {
16152
+ onValueChange(field.matchModes ? { keywords: nextEntries, mode: nextMode } : nextEntries);
15933
16153
  };
15934
- const addEntry = (raw) => {
16154
+ const commit = (nextEntries) => {
16155
+ onValueCommit(field.matchModes ? { keywords: nextEntries, mode } : nextEntries);
16156
+ };
16157
+ const changeMode = (next) => {
16158
+ setMode(next);
16159
+ emit3(entries3, next);
16160
+ };
16161
+ const newEntries = (candidates) => candidates.reduce((acc, raw) => {
15935
16162
  const next = raw.trim();
15936
- if (next === "" || entries3.includes(next)) return;
15937
- emit3([...entries3, next]);
16163
+ if (next === "" || entries3.includes(next) || acc.includes(next)) return acc;
16164
+ return [...acc, next];
16165
+ }, []);
16166
+ const addEntry = (raw) => {
16167
+ const added = newEntries([raw]);
16168
+ if (added.length === 0) {
16169
+ setDraft("");
16170
+ return;
16171
+ }
16172
+ commit([...entries3, ...added]);
16173
+ setDraft("");
16174
+ };
16175
+ const changeDraft = (next) => {
16176
+ if (field.input !== "csv" || !next.includes(",")) {
16177
+ setDraft(next);
16178
+ return;
16179
+ }
16180
+ const parts = next.split(",");
16181
+ const tail = parts.pop() ?? "";
16182
+ const added = newEntries(parts);
16183
+ if (added.length > 0) commit([...entries3, ...added]);
16184
+ setDraft(tail);
16185
+ };
16186
+ const onPaste = (event) => {
16187
+ if (field.input !== "csv") return;
16188
+ const text = event.clipboardData.getData("text");
16189
+ if (!CSV_PASTE_SEPARATOR.test(text)) return;
16190
+ event.preventDefault();
16191
+ const added = newEntries((draft + text).split(CSV_PASTE_SEPARATOR));
16192
+ if (added.length > 0) commit([...entries3, ...added]);
15938
16193
  setDraft("");
15939
16194
  };
15940
16195
  const removeEntry = (entry) => {
@@ -15966,7 +16221,7 @@ function KeywordEditor({
15966
16221
  options: field.excludeMode ? [...MATCH_MODE_OPTIONS, EXCLUDE_MATCH_MODE_OPTION] : MATCH_MODE_OPTIONS,
15967
16222
  value: mode,
15968
16223
  disabled,
15969
- onValueChange: (next) => setMode(next)
16224
+ onValueChange: (next) => changeMode(next)
15970
16225
  }
15971
16226
  )
15972
16227
  ] }),
@@ -15975,10 +16230,12 @@ function KeywordEditor({
15975
16230
  /* @__PURE__ */ jsxRuntime.jsx(
15976
16231
  "input",
15977
16232
  {
16233
+ ref: inputRef,
15978
16234
  value: draft,
15979
16235
  disabled,
15980
- placeholder: field.placeholder ?? "\uC785\uB825 \uD6C4 Enter\uB85C \uCD94\uAC00",
15981
- onChange: (event) => setDraft(event.target.value),
16236
+ placeholder: field.placeholder ?? (field.input === "csv" ? "\uB2E4\uC911 \uD0A4\uC6CC\uB4DC\uB294 \uCF64\uB9C8(,)\uB85C \uAD6C\uBD84" : "\uC785\uB825 \uD6C4 Enter\uB85C \uCD94\uAC00"),
16237
+ onChange: (event) => changeDraft(event.target.value),
16238
+ onPaste,
15982
16239
  onKeyDown,
15983
16240
  className: "min-w-0 flex-1 border-none bg-transparent text-[12px] leading-[20px] text-[color:var(--sys-color-field-text-default)] outline-none placeholder:text-[color:var(--sys-color-field-text-placeholder)] disabled:cursor-not-allowed"
15984
16241
  }