sellmate-design-system-react 9.0.0-beta.16 → 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
  {
@@ -10438,6 +10501,7 @@ var SSelectImpl = /* @__PURE__ */ react.forwardRef(function SSelect({
10438
10501
  allSelectedLabel = "\uC804\uCCB4",
10439
10502
  placeholder = "\uC120\uD0DD",
10440
10503
  disabled = false,
10504
+ clearable = false,
10441
10505
  error = false,
10442
10506
  rules,
10443
10507
  labelTooltipProps,
@@ -10593,6 +10657,11 @@ var SSelectImpl = /* @__PURE__ */ react.forwardRef(function SSelect({
10593
10657
  return labelByValue.get(selected[0]) ?? selected[0];
10594
10658
  })();
10595
10659
  const isPlaceholder = selected.length === 0;
10660
+ const showClear = clearable && !effectiveDisabled && !isPlaceholder;
10661
+ const handleClear = () => {
10662
+ emit3([]);
10663
+ if (rules && rules.length > 0) setRuleError(runRules(multi ? [] : "", rules));
10664
+ };
10596
10665
  const handleSelect = (nextValue) => {
10597
10666
  if (multi) {
10598
10667
  const next = selected.includes(nextValue) ? selected.filter((v) => v !== nextValue) : [...selected, nextValue];
@@ -10735,15 +10804,53 @@ var SSelectImpl = /* @__PURE__ */ react.forwardRef(function SSelect({
10735
10804
  dangerouslySetInnerHTML: { __html: sanitizeInlineHtml(triggerText) }
10736
10805
  }
10737
10806
  ) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: triggerText }),
10738
- /* @__PURE__ */ jsxRuntime.jsx(
10739
- SIcon,
10740
- {
10741
- name: "chevronDown",
10742
- size: 12,
10743
- color: effectiveDisabled ? "var(--cmp-select-icon-disabled)" : "var(--cmp-select-icon-default)",
10744
- className: cn("shrink-0 transition-transform duration-200", open && "rotate-180")
10745
- }
10746
- )
10807
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex shrink-0 items-center gap-[8px]", children: [
10808
+ showClear && /* @__PURE__ */ jsxRuntime.jsxs(
10809
+ "span",
10810
+ {
10811
+ role: "button",
10812
+ tabIndex: 0,
10813
+ "aria-label": "\uC120\uD0DD \uC9C0\uC6B0\uAE30",
10814
+ className: "group/select-clear relative inline-flex shrink-0 cursor-pointer items-center justify-center",
10815
+ style: {
10816
+ width: "var(--cmp-ghostButton-xxs-size)",
10817
+ height: "var(--cmp-ghostButton-xxs-size)",
10818
+ borderRadius: "var(--cmp-ghostButton-radius-sm)"
10819
+ },
10820
+ onClick: (event) => {
10821
+ event.preventDefault();
10822
+ event.stopPropagation();
10823
+ handleClear();
10824
+ },
10825
+ onKeyDown: (event) => {
10826
+ if (event.key !== "Enter" && event.key !== " ") return;
10827
+ event.preventDefault();
10828
+ event.stopPropagation();
10829
+ handleClear();
10830
+ },
10831
+ children: [
10832
+ /* @__PURE__ */ jsxRuntime.jsx(
10833
+ "span",
10834
+ {
10835
+ "aria-hidden": true,
10836
+ className: "pointer-events-none absolute inset-0 rounded-[inherit] opacity-0 transition-opacity duration-200 group-hover/select-clear:opacity-5",
10837
+ style: { background: "var(--cmp-ghostButton-default-hover-bg)" }
10838
+ }
10839
+ ),
10840
+ /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: "close", size: 12, color: "var(--cmp-ghostButton-default-content)" })
10841
+ ]
10842
+ }
10843
+ ),
10844
+ /* @__PURE__ */ jsxRuntime.jsx(
10845
+ SIcon,
10846
+ {
10847
+ name: "chevronDown",
10848
+ size: 12,
10849
+ color: effectiveDisabled ? "var(--cmp-select-icon-disabled)" : "var(--cmp-select-icon-default)",
10850
+ className: cn("shrink-0 transition-transform duration-200", open && "rotate-180")
10851
+ }
10852
+ )
10853
+ ] })
10747
10854
  ]
10748
10855
  }
10749
10856
  ),
@@ -11080,11 +11187,13 @@ var STable = /* @__PURE__ */ react.forwardRef(function STable2({
11080
11187
  noDataSlot,
11081
11188
  isLoading = false,
11082
11189
  dense = false,
11190
+ onDenseChange,
11083
11191
  noHover = false,
11084
11192
  pagination,
11085
11193
  onPageChange,
11086
11194
  useInternalPagination = false,
11087
11195
  useRowsPerPageSelect = false,
11196
+ useDensityToggle = false,
11088
11197
  rowsPerPageOption = DEFAULT_ROWS_PER_PAGE_OPTION,
11089
11198
  onRowsPerPageChange,
11090
11199
  useVirtualScroll = false,
@@ -11480,21 +11589,7 @@ var STable = /* @__PURE__ */ react.forwardRef(function STable2({
11480
11589
  col.align === "right" && "justify-end"
11481
11590
  ),
11482
11591
  children: col.renderHeader ? col.renderHeader(col, i) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
11483
- col.icon && /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: col.icon, size: 16, color: col.iconColor }),
11484
11592
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: col.label }),
11485
- isSortable && // 미정렬은 updown 을 default(회색)로 — 정렬할 수 있다는 것 자체가
11486
- // 헤더에 보여야 한다. 정렬 중이면 action 색으로 올려 활성임을 알린다.
11487
- // 라벨이 truncate 돼도 버튼은 남도록 shrink-0.
11488
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "inline-flex shrink-0", children: /* @__PURE__ */ jsxRuntime.jsx(
11489
- SGhostButton,
11490
- {
11491
- size: "xxs",
11492
- intent: sortDir ? "action" : "default",
11493
- icon: sortDir === "asc" ? "arrowUp" : sortDir === "desc" ? "arrowDown" : "updown",
11494
- ariaLabel: `${col.label} \uC815\uB82C`,
11495
- onClick: () => toggleSort(col)
11496
- }
11497
- ) }),
11498
11593
  col.helpText && col.helpText.length > 0 && // 라벨이 좁아져 truncate 되더라도 아이콘까지 함께 줄어들면 안 되므로
11499
11594
  // shrink-0 래퍼로 감싼다 (STooltip 의 className 은 본문 쪽으로 간다).
11500
11595
  // 아이콘·크기·색은 SKeyValueTable 헤더 도움말과 동일하게 맞춘다.
@@ -11508,6 +11603,50 @@ var STable = /* @__PURE__ */ react.forwardRef(function STable2({
11508
11603
  placement: "top",
11509
11604
  message: col.helpText
11510
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
+ }
11511
11650
  ) })
11512
11651
  ] })
11513
11652
  }
@@ -11665,16 +11804,32 @@ var STable = /* @__PURE__ */ react.forwardRef(function STable2({
11665
11804
  ),
11666
11805
  children: [
11667
11806
  /* @__PURE__ */ jsxRuntime.jsx(SPagination, { currentPage: displayPage, lastPage, onPageChange: changePage }),
11668
- useRowsPerPageSelect && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute right-[10px] top-1/2 -translate-y-1/2", children: /* @__PURE__ */ jsxRuntime.jsx(
11669
- SSelect2,
11670
- {
11671
- options: rowsPerPageOption,
11672
- value: useInternalPagination ? innerRowsPerPage : pagination?.rowsPerPage,
11673
- emitValue: true,
11674
- onValueChange: (v) => changeRowsPerPage(Number(v)),
11675
- width: 128
11676
- }
11677
- ) })
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
+ ] })
11678
11833
  ]
11679
11834
  }
11680
11835
  )
@@ -12770,6 +12925,11 @@ function SCalendar({
12770
12925
  }
12771
12926
  );
12772
12927
  }
12928
+ var MODE_NOUN = {
12929
+ date: "\uB0A0\uC9DC",
12930
+ month: "\uC6D4",
12931
+ year: "\uC5F0\uB3C4"
12932
+ };
12773
12933
  var DATEPICKER_MODE_LIST_HEIGHT = 240;
12774
12934
  var DATEPICKER_MODE_ITEM_HEIGHT = 28;
12775
12935
  var YEAR_LIST_EDGE_BUFFER = 24;
@@ -13041,7 +13201,9 @@ function SDatePicker({
13041
13201
  placeholder,
13042
13202
  selectable,
13043
13203
  disabled = false,
13204
+ clearable = false,
13044
13205
  width,
13206
+ maxWidth,
13045
13207
  name,
13046
13208
  rules,
13047
13209
  status,
@@ -13084,7 +13246,8 @@ function SDatePicker({
13084
13246
  fieldMinW: 168,
13085
13247
  // 값 길이가 YYYY-MM-DD 로 정해져 있어, width="100%" 를 받아도 행 전체로
13086
13248
  // 늘어나지 않게 상한을 건다 (SKeyValueTable 이 모든 컨트롤에 100% 를 넘긴다).
13087
- // px 가 아니라 등급이라 --cmp-field-width-* 가 바뀌면 함께 따라간다
13249
+ // px 가 아니라 등급이라 --cmp-field-width-* 가 바뀌면 함께 따라간다.
13250
+ // 이 기본 상한이 걸리적거리는 자리(폭이 이미 좁게 정해진 팝오버 등)는 maxWidth prop 으로 덮는다
13088
13251
  fieldMaxW: "lg",
13089
13252
  fontSize: 14,
13090
13253
  lineHeight: 24,
@@ -13105,6 +13268,10 @@ function SDatePicker({
13105
13268
  if (rules && rules.length > 0) setRuleError(runRules(date, rules));
13106
13269
  setOpen(false);
13107
13270
  };
13271
+ const clear = () => {
13272
+ onValueChange?.(null);
13273
+ if (rules && rules.length > 0) setRuleError(runRules(null, rules));
13274
+ };
13108
13275
  const selectListboxValue = (nextValue) => {
13109
13276
  onValueChange?.(nextValue);
13110
13277
  if (rules && rules.length > 0) setRuleError(runRules(nextValue, rules));
@@ -13128,20 +13295,31 @@ function SDatePicker({
13128
13295
  errorMessage: ruleError !== "" ? ruleError : errorMessage,
13129
13296
  width,
13130
13297
  minWidth: dims.fieldMinW,
13131
- maxWidth: dims.fieldMaxW,
13298
+ maxWidth: maxWidth ?? dims.fieldMaxW,
13132
13299
  disabled,
13133
13300
  className,
13134
13301
  style,
13135
13302
  children: /* @__PURE__ */ jsxRuntime.jsxs(RPopover10__namespace.Root, { open, onOpenChange: (o) => !disabled && setOpen(o), children: [
13136
13303
  /* @__PURE__ */ jsxRuntime.jsx(RPopover10__namespace.Trigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs(
13137
- "button",
13304
+ "div",
13138
13305
  {
13139
- type: "button",
13140
- name,
13141
- disabled,
13306
+ role: "button",
13307
+ "aria-disabled": disabled,
13308
+ tabIndex: disabled ? -1 : 0,
13309
+ "data-name": name,
13142
13310
  onFocus: () => setFocused(true),
13143
13311
  onBlur: () => setFocused(false),
13144
- 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
+ ),
13145
13323
  style: { padding: `0 ${dims.padX}px`, gap: dims.gap, borderRadius: dims.radius },
13146
13324
  children: [
13147
13325
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -13165,6 +13343,18 @@ function SDatePicker({
13165
13343
  },
13166
13344
  children: hasValue ? value : resolvedPlaceholder
13167
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
+ }
13168
13358
  )
13169
13359
  ]
13170
13360
  }
@@ -13544,6 +13734,7 @@ function SDateRangePicker({
13544
13734
  maxRange,
13545
13735
  useTimePicker = false,
13546
13736
  disabled = false,
13737
+ clearable = false,
13547
13738
  width,
13548
13739
  name,
13549
13740
  rules,
@@ -13611,6 +13802,11 @@ function SDateRangePicker({
13611
13802
  if (rules && rules.length > 0) setRuleError(runRules(r, rules));
13612
13803
  if (!useTimePicker) setOpen(false);
13613
13804
  };
13805
+ const clear = () => {
13806
+ setPendingStart(null);
13807
+ onValueChange?.(null);
13808
+ if (rules && rules.length > 0) setRuleError(runRules(null, rules));
13809
+ };
13614
13810
  const resolvedPlaceholder = useTimePicker && placeholder === "YYYY-MM-DD ~ YYYY-MM-DD" ? "YYYY-MM-DD HH:mm ~ YYYY-MM-DD HH:mm" : placeholder;
13615
13811
  return /* @__PURE__ */ jsxRuntime.jsx(
13616
13812
  SField,
@@ -13646,12 +13842,23 @@ function SDateRangePicker({
13646
13842
  },
13647
13843
  children: [
13648
13844
  /* @__PURE__ */ jsxRuntime.jsx(RPopover10__namespace.Trigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs(
13649
- "button",
13845
+ "div",
13650
13846
  {
13651
- type: "button",
13652
- name,
13653
- disabled,
13654
- 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
+ ),
13655
13862
  style: { padding: `0 ${dims.padX}px`, gap: dims.gap, borderRadius: dims.radius },
13656
13863
  children: [
13657
13864
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -13682,6 +13889,18 @@ function SDateRangePicker({
13682
13889
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 flex-1 truncate text-left", children: endText })
13683
13890
  ] }) : resolvedPlaceholder
13684
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
+ }
13685
13904
  )
13686
13905
  ]
13687
13906
  }
@@ -14946,6 +15165,7 @@ var SChipFilter = /* @__PURE__ */ react.forwardRef(function SChipFilter2({
14946
15165
  );
14947
15166
  const [openKey, setOpenKey] = react.useState(null);
14948
15167
  const [addOpen, setAddOpen] = react.useState(false);
15168
+ const pendingOpenKeyRef = react.useRef(null);
14949
15169
  const triggerRefs = react.useRef(/* @__PURE__ */ new Map());
14950
15170
  const initializedDefaultsRef = react.useRef(/* @__PURE__ */ new Set());
14951
15171
  const prevOpenKeyRef = react.useRef(null);
@@ -15021,6 +15241,7 @@ var SChipFilter = /* @__PURE__ */ react.forwardRef(function SChipFilter2({
15021
15241
  const hasAddButton = fields.some(isAddableField);
15022
15242
  const hasRightActions = hasAddButton || showReset;
15023
15243
  const currentOpenField = fields.find((field) => field.key === openKey);
15244
+ const menuOpen = openKey != null || addOpen;
15024
15245
  const editorPopoverStyle = react.useMemo(() => {
15025
15246
  if (currentOpenField?.type === "keyword") {
15026
15247
  return { width: CHIPFILTER_LISTBOX_WIDTH };
@@ -15079,7 +15300,13 @@ var SChipFilter = /* @__PURE__ */ react.forwardRef(function SChipFilter2({
15079
15300
  onAddFilter?.(key);
15080
15301
  setAddOpen(false);
15081
15302
  const field = fields.find((f) => f.key === key);
15082
- 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
+ }
15083
15310
  };
15084
15311
  const rootStyle = react.useMemo(
15085
15312
  () => ({
@@ -15122,6 +15349,7 @@ var SChipFilter = /* @__PURE__ */ react.forwardRef(function SChipFilter2({
15122
15349
  {
15123
15350
  message,
15124
15351
  open: invalidGroups.has(runGroupIndex),
15352
+ placement: menuOpen ? "top" : "bottom",
15125
15353
  children: runBuffer
15126
15354
  },
15127
15355
  `group-${runGroupIndex}`
@@ -15135,14 +15363,16 @@ var SChipFilter = /* @__PURE__ */ react.forwardRef(function SChipFilter2({
15135
15363
  activeFields.forEach((field, index) => {
15136
15364
  const inline = isInlineField(field);
15137
15365
  const groupIndex = groupIndexByKey.get(field.key) ?? 0;
15138
- const warning = field.warning || invalidGroups.has(groupIndex);
15366
+ const warning = invalidGroups.has(groupIndex);
15139
15367
  const prevField = index > 0 ? activeFields[index - 1] : null;
15140
15368
  const prevGroupIndex = prevField ? groupIndexByKey.get(prevField.key) ?? 0 : null;
15141
15369
  const needsLeadingDivider = index === 0 ? false : inline || prevField != null && isInlineField(prevField) || groupIndex !== prevGroupIndex && groups[groupIndex]?.divider === true;
15370
+ const hoistDivider = needsLeadingDivider && groupIndex !== runGroupIndex;
15142
15371
  if (groupIndex !== runGroupIndex) {
15143
15372
  flushRun();
15144
15373
  runGroupIndex = groupIndex;
15145
15374
  }
15375
+ if (hoistDivider) nodes.push(/* @__PURE__ */ jsxRuntime.jsx(Divider, { inset: true }, `${field.key}-divider`));
15146
15376
  const addable = isAddableField(field);
15147
15377
  const showClear = addable || !isAtDefaultValue(field, values[field.key]);
15148
15378
  const handleClear = () => {
@@ -15206,7 +15436,7 @@ var SChipFilter = /* @__PURE__ */ react.forwardRef(function SChipFilter2({
15206
15436
  field.key
15207
15437
  );
15208
15438
  runBuffer.push(
15209
- needsLeadingDivider ? /* @__PURE__ */ jsxRuntime.jsxs(react.Fragment, { children: [
15439
+ needsLeadingDivider && !hoistDivider ? /* @__PURE__ */ jsxRuntime.jsxs(react.Fragment, { children: [
15210
15440
  /* @__PURE__ */ jsxRuntime.jsx(Divider, { inset: true }),
15211
15441
  node
15212
15442
  ] }, `${field.key}-wrap`) : node
@@ -15254,6 +15484,11 @@ var SChipFilter = /* @__PURE__ */ react.forwardRef(function SChipFilter2({
15254
15484
  side: "bottom",
15255
15485
  align: "start",
15256
15486
  sideOffset: 4,
15487
+ onCloseAutoFocus: (event) => {
15488
+ if (pendingOpenKeyRef.current == null) return;
15489
+ pendingOpenKeyRef.current = null;
15490
+ event.preventDefault();
15491
+ },
15257
15492
  className: cn("z-50 min-w-[180px]", FLOATING_SLIDE_ANIM),
15258
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." }) })
15259
15494
  }
@@ -15304,6 +15539,7 @@ var SChipFilter = /* @__PURE__ */ react.forwardRef(function SChipFilter2({
15304
15539
  value: values[currentOpenField.key],
15305
15540
  disabled: disabled || currentOpenField.disabled,
15306
15541
  onValueChange: (next) => emitValue(currentOpenField.key, next),
15542
+ onValueCommit: (next) => emitValueAndSearch(currentOpenField.key, next),
15307
15543
  onClose: () => setOpenKey(null)
15308
15544
  }
15309
15545
  )
@@ -15331,9 +15567,20 @@ function Divider({ inset, top } = {}) {
15331
15567
  function GroupTooltip({
15332
15568
  message,
15333
15569
  open,
15570
+ placement,
15334
15571
  children
15335
15572
  }) {
15336
- 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
+ );
15337
15584
  }
15338
15585
  function FilterChip({
15339
15586
  field,
@@ -15443,6 +15690,7 @@ function EditorPanel({
15443
15690
  value,
15444
15691
  disabled,
15445
15692
  onValueChange,
15693
+ onValueCommit,
15446
15694
  onClose
15447
15695
  }) {
15448
15696
  const wrapsInPaddedTop = field.type === "date" || field.type === "period";
@@ -15462,7 +15710,8 @@ function EditorPanel({
15462
15710
  field,
15463
15711
  value,
15464
15712
  disabled,
15465
- onValueChange
15713
+ onValueChange,
15714
+ onValueCommit
15466
15715
  }
15467
15716
  ) }) : /* @__PURE__ */ jsxRuntime.jsx(
15468
15717
  FilterEditor,
@@ -15470,7 +15719,8 @@ function EditorPanel({
15470
15719
  field,
15471
15720
  value,
15472
15721
  disabled,
15473
- onValueChange
15722
+ onValueChange,
15723
+ onValueCommit
15474
15724
  }
15475
15725
  ),
15476
15726
  /* @__PURE__ */ jsxRuntime.jsxs(
@@ -15522,7 +15772,8 @@ function FilterEditor({
15522
15772
  field,
15523
15773
  value,
15524
15774
  disabled,
15525
- onValueChange
15775
+ onValueChange,
15776
+ onValueCommit
15526
15777
  }) {
15527
15778
  if (field.type === "period") {
15528
15779
  return /* @__PURE__ */ jsxRuntime.jsx(
@@ -15553,7 +15804,8 @@ function FilterEditor({
15553
15804
  field,
15554
15805
  value: isKeywordValue(value) ? value : keywordEntries(value),
15555
15806
  disabled,
15556
- onValueChange
15807
+ onValueChange,
15808
+ onValueCommit
15557
15809
  }
15558
15810
  );
15559
15811
  }
@@ -15595,7 +15847,9 @@ function DatePresetEditor({
15595
15847
  onValueChange,
15596
15848
  selectable: field.selectable,
15597
15849
  disabled,
15598
- placeholder: field.placeholder
15850
+ placeholder: field.placeholder,
15851
+ width: "100%",
15852
+ maxWidth: "100%"
15599
15853
  }
15600
15854
  );
15601
15855
  }
@@ -15655,7 +15909,8 @@ function PeriodPresetEditor({
15655
15909
  onValueChange: updateInputValue,
15656
15910
  selectable: field.selectable,
15657
15911
  disabled,
15658
- width: "100%"
15912
+ width: "100%",
15913
+ maxWidth: "100%"
15659
15914
  }
15660
15915
  );
15661
15916
  }
@@ -15668,7 +15923,8 @@ function PeriodPresetEditor({
15668
15923
  onValueChange: updateInputValue,
15669
15924
  selectable: field.selectable,
15670
15925
  disabled,
15671
- width: "100%"
15926
+ width: "100%",
15927
+ maxWidth: "100%"
15672
15928
  }
15673
15929
  );
15674
15930
  }
@@ -15715,7 +15971,8 @@ function PeriodPresetEditor({
15715
15971
  onValueChange: updateInputValue,
15716
15972
  selectable: field.selectable,
15717
15973
  disabled,
15718
- width: "100%"
15974
+ width: "100%",
15975
+ maxWidth: "100%"
15719
15976
  }
15720
15977
  );
15721
15978
  }
@@ -15865,6 +16122,7 @@ function InlineDatePresetFilter({
15865
16122
  ) : badge)
15866
16123
  ] });
15867
16124
  }
16125
+ var CSV_PASTE_SEPARATOR = /[,\r\n\t]+/;
15868
16126
  var MATCH_MODE_OPTIONS = [
15869
16127
  { value: "contains", label: "\uD3EC\uD568" },
15870
16128
  { value: "exact", label: "\uC77C\uCE58" }
@@ -15877,20 +16135,61 @@ function KeywordEditor({
15877
16135
  field,
15878
16136
  value,
15879
16137
  disabled,
15880
- onValueChange
16138
+ onValueChange,
16139
+ onValueCommit
15881
16140
  }) {
15882
16141
  const entries3 = keywordEntries(value);
15883
16142
  const [draft, setDraft] = react.useState("");
15884
16143
  const [mode, setMode] = react.useState(
15885
16144
  isKeywordValue(value) ? value.mode : "contains"
15886
16145
  );
15887
- const emit3 = (nextEntries) => {
15888
- 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);
15889
16153
  };
15890
- 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) => {
15891
16162
  const next = raw.trim();
15892
- if (next === "" || entries3.includes(next)) return;
15893
- 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]);
15894
16193
  setDraft("");
15895
16194
  };
15896
16195
  const removeEntry = (entry) => {
@@ -15922,7 +16221,7 @@ function KeywordEditor({
15922
16221
  options: field.excludeMode ? [...MATCH_MODE_OPTIONS, EXCLUDE_MATCH_MODE_OPTION] : MATCH_MODE_OPTIONS,
15923
16222
  value: mode,
15924
16223
  disabled,
15925
- onValueChange: (next) => setMode(next)
16224
+ onValueChange: (next) => changeMode(next)
15926
16225
  }
15927
16226
  )
15928
16227
  ] }),
@@ -15931,10 +16230,12 @@ function KeywordEditor({
15931
16230
  /* @__PURE__ */ jsxRuntime.jsx(
15932
16231
  "input",
15933
16232
  {
16233
+ ref: inputRef,
15934
16234
  value: draft,
15935
16235
  disabled,
15936
- placeholder: field.placeholder ?? "\uC785\uB825 \uD6C4 Enter\uB85C \uCD94\uAC00",
15937
- 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,
15938
16239
  onKeyDown,
15939
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"
15940
16241
  }