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.js CHANGED
@@ -1175,6 +1175,45 @@ var Icon_close = (p) => {
1175
1175
  }
1176
1176
  );
1177
1177
  };
1178
+ var Icon_company = (p) => {
1179
+ const { size = 24, ...rest } = p;
1180
+ return /* @__PURE__ */ jsxs(
1181
+ "svg",
1182
+ {
1183
+ width: size,
1184
+ height: size,
1185
+ viewBox: "0 0 24 24",
1186
+ fill: "none",
1187
+ xmlns: "http://www.w3.org/2000/svg",
1188
+ ...rest,
1189
+ children: [
1190
+ /* @__PURE__ */ jsx(
1191
+ "path",
1192
+ {
1193
+ 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",
1194
+ fill: "currentColor"
1195
+ }
1196
+ ),
1197
+ /* @__PURE__ */ jsx(
1198
+ "path",
1199
+ {
1200
+ 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",
1201
+ fill: "currentColor"
1202
+ }
1203
+ ),
1204
+ /* @__PURE__ */ jsx(
1205
+ "path",
1206
+ {
1207
+ fillRule: "evenodd",
1208
+ clipRule: "evenodd",
1209
+ 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",
1210
+ fill: "currentColor"
1211
+ }
1212
+ )
1213
+ ]
1214
+ }
1215
+ );
1216
+ };
1178
1217
  var Icon_copy = (p) => {
1179
1218
  const { size = 24, ...rest } = p;
1180
1219
  return /* @__PURE__ */ jsxs(
@@ -2398,6 +2437,27 @@ var Icon_row = (p) => {
2398
2437
  }
2399
2438
  );
2400
2439
  };
2440
+ var Icon_rowDense = (p) => {
2441
+ const { size = 24, ...rest } = p;
2442
+ return /* @__PURE__ */ jsx(
2443
+ "svg",
2444
+ {
2445
+ width: size,
2446
+ height: size,
2447
+ viewBox: "0 0 24 24",
2448
+ fill: "none",
2449
+ xmlns: "http://www.w3.org/2000/svg",
2450
+ ...rest,
2451
+ children: /* @__PURE__ */ jsx(
2452
+ "path",
2453
+ {
2454
+ 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",
2455
+ fill: "currentColor"
2456
+ }
2457
+ )
2458
+ }
2459
+ );
2460
+ };
2401
2461
  var Icon_save = (p) => {
2402
2462
  const { size = 24, ...rest } = p;
2403
2463
  return /* @__PURE__ */ jsx(
@@ -3046,6 +3106,7 @@ var ICONS = {
3046
3106
  clockFill: Icon_clockFill,
3047
3107
  clockOutline: Icon_clockOutline,
3048
3108
  close: Icon_close,
3109
+ company: Icon_company,
3049
3110
  copy: Icon_copy,
3050
3111
  coupon: Icon_coupon,
3051
3112
  dashboard: Icon_dashboard,
@@ -3083,6 +3144,7 @@ var ICONS = {
3083
3144
  reset: Icon_reset,
3084
3145
  robot: Icon_robot,
3085
3146
  row: Icon_row,
3147
+ rowDense: Icon_rowDense,
3086
3148
  save: Icon_save,
3087
3149
  search: Icon_search,
3088
3150
  send: Icon_send,
@@ -6076,6 +6138,7 @@ var STextLink = /* @__PURE__ */ forwardRef(function STextLink2({
6076
6138
  label = "",
6077
6139
  icon,
6078
6140
  iconColor,
6141
+ iconRotate,
6079
6142
  labelClass,
6080
6143
  rightArrow = "none",
6081
6144
  underline = false,
@@ -6118,7 +6181,7 @@ var STextLink = /* @__PURE__ */ forwardRef(function STextLink2({
6118
6181
  style: { gap: s.gap, color: contentColor, ...style },
6119
6182
  ...rest,
6120
6183
  children: [
6121
- icon && /* @__PURE__ */ jsx(SIcon, { name: icon, size: s.iconSize, color: resolvedIconColor }),
6184
+ icon && /* @__PURE__ */ jsx(SIcon, { name: icon, size: s.iconSize, color: resolvedIconColor, rotate: iconRotate }),
6122
6185
  /* @__PURE__ */ jsx(
6123
6186
  "span",
6124
6187
  {
@@ -10410,6 +10473,7 @@ var SSelectImpl = /* @__PURE__ */ forwardRef(function SSelect({
10410
10473
  allSelectedLabel = "\uC804\uCCB4",
10411
10474
  placeholder = "\uC120\uD0DD",
10412
10475
  disabled = false,
10476
+ clearable = false,
10413
10477
  error = false,
10414
10478
  rules,
10415
10479
  labelTooltipProps,
@@ -10565,6 +10629,11 @@ var SSelectImpl = /* @__PURE__ */ forwardRef(function SSelect({
10565
10629
  return labelByValue.get(selected[0]) ?? selected[0];
10566
10630
  })();
10567
10631
  const isPlaceholder = selected.length === 0;
10632
+ const showClear = clearable && !effectiveDisabled && !isPlaceholder;
10633
+ const handleClear = () => {
10634
+ emit3([]);
10635
+ if (rules && rules.length > 0) setRuleError(runRules(multi ? [] : "", rules));
10636
+ };
10568
10637
  const handleSelect = (nextValue) => {
10569
10638
  if (multi) {
10570
10639
  const next = selected.includes(nextValue) ? selected.filter((v) => v !== nextValue) : [...selected, nextValue];
@@ -10707,15 +10776,53 @@ var SSelectImpl = /* @__PURE__ */ forwardRef(function SSelect({
10707
10776
  dangerouslySetInnerHTML: { __html: sanitizeInlineHtml(triggerText) }
10708
10777
  }
10709
10778
  ) : /* @__PURE__ */ jsx("span", { className: "truncate", children: triggerText }),
10710
- /* @__PURE__ */ jsx(
10711
- SIcon,
10712
- {
10713
- name: "chevronDown",
10714
- size: 12,
10715
- color: effectiveDisabled ? "var(--cmp-select-icon-disabled)" : "var(--cmp-select-icon-default)",
10716
- className: cn("shrink-0 transition-transform duration-200", open && "rotate-180")
10717
- }
10718
- )
10779
+ /* @__PURE__ */ jsxs("span", { className: "flex shrink-0 items-center gap-[8px]", children: [
10780
+ showClear && /* @__PURE__ */ jsxs(
10781
+ "span",
10782
+ {
10783
+ role: "button",
10784
+ tabIndex: 0,
10785
+ "aria-label": "\uC120\uD0DD \uC9C0\uC6B0\uAE30",
10786
+ className: "group/select-clear relative inline-flex shrink-0 cursor-pointer items-center justify-center",
10787
+ style: {
10788
+ width: "var(--cmp-ghostButton-xxs-size)",
10789
+ height: "var(--cmp-ghostButton-xxs-size)",
10790
+ borderRadius: "var(--cmp-ghostButton-radius-sm)"
10791
+ },
10792
+ onClick: (event) => {
10793
+ event.preventDefault();
10794
+ event.stopPropagation();
10795
+ handleClear();
10796
+ },
10797
+ onKeyDown: (event) => {
10798
+ if (event.key !== "Enter" && event.key !== " ") return;
10799
+ event.preventDefault();
10800
+ event.stopPropagation();
10801
+ handleClear();
10802
+ },
10803
+ children: [
10804
+ /* @__PURE__ */ jsx(
10805
+ "span",
10806
+ {
10807
+ "aria-hidden": true,
10808
+ className: "pointer-events-none absolute inset-0 rounded-[inherit] opacity-0 transition-opacity duration-200 group-hover/select-clear:opacity-5",
10809
+ style: { background: "var(--cmp-ghostButton-default-hover-bg)" }
10810
+ }
10811
+ ),
10812
+ /* @__PURE__ */ jsx(SIcon, { name: "close", size: 12, color: "var(--cmp-ghostButton-default-content)" })
10813
+ ]
10814
+ }
10815
+ ),
10816
+ /* @__PURE__ */ jsx(
10817
+ SIcon,
10818
+ {
10819
+ name: "chevronDown",
10820
+ size: 12,
10821
+ color: effectiveDisabled ? "var(--cmp-select-icon-disabled)" : "var(--cmp-select-icon-default)",
10822
+ className: cn("shrink-0 transition-transform duration-200", open && "rotate-180")
10823
+ }
10824
+ )
10825
+ ] })
10719
10826
  ]
10720
10827
  }
10721
10828
  ),
@@ -11052,11 +11159,13 @@ var STable = /* @__PURE__ */ forwardRef(function STable2({
11052
11159
  noDataSlot,
11053
11160
  isLoading = false,
11054
11161
  dense = false,
11162
+ onDenseChange,
11055
11163
  noHover = false,
11056
11164
  pagination,
11057
11165
  onPageChange,
11058
11166
  useInternalPagination = false,
11059
11167
  useRowsPerPageSelect = false,
11168
+ useDensityToggle = false,
11060
11169
  rowsPerPageOption = DEFAULT_ROWS_PER_PAGE_OPTION,
11061
11170
  onRowsPerPageChange,
11062
11171
  useVirtualScroll = false,
@@ -11452,21 +11561,7 @@ var STable = /* @__PURE__ */ forwardRef(function STable2({
11452
11561
  col.align === "right" && "justify-end"
11453
11562
  ),
11454
11563
  children: col.renderHeader ? col.renderHeader(col, i) : /* @__PURE__ */ jsxs(Fragment, { children: [
11455
- col.icon && /* @__PURE__ */ jsx(SIcon, { name: col.icon, size: 16, color: col.iconColor }),
11456
11564
  /* @__PURE__ */ jsx("span", { className: "truncate", children: col.label }),
11457
- isSortable && // 미정렬은 updown 을 default(회색)로 — 정렬할 수 있다는 것 자체가
11458
- // 헤더에 보여야 한다. 정렬 중이면 action 색으로 올려 활성임을 알린다.
11459
- // 라벨이 truncate 돼도 버튼은 남도록 shrink-0.
11460
- /* @__PURE__ */ jsx("span", { className: "inline-flex shrink-0", children: /* @__PURE__ */ jsx(
11461
- SGhostButton,
11462
- {
11463
- size: "xxs",
11464
- intent: sortDir ? "action" : "default",
11465
- icon: sortDir === "asc" ? "arrowUp" : sortDir === "desc" ? "arrowDown" : "updown",
11466
- ariaLabel: `${col.label} \uC815\uB82C`,
11467
- onClick: () => toggleSort(col)
11468
- }
11469
- ) }),
11470
11565
  col.helpText && col.helpText.length > 0 && // 라벨이 좁아져 truncate 되더라도 아이콘까지 함께 줄어들면 안 되므로
11471
11566
  // shrink-0 래퍼로 감싼다 (STooltip 의 className 은 본문 쪽으로 간다).
11472
11567
  // 아이콘·크기·색은 SKeyValueTable 헤더 도움말과 동일하게 맞춘다.
@@ -11480,6 +11575,50 @@ var STable = /* @__PURE__ */ forwardRef(function STable2({
11480
11575
  placement: "top",
11481
11576
  message: col.helpText
11482
11577
  }
11578
+ ) }),
11579
+ col.editable && /* @__PURE__ */ jsx(
11580
+ SIcon,
11581
+ {
11582
+ name: "pageEdit",
11583
+ size: 12,
11584
+ color: "var(--sys-color-link-accent)",
11585
+ label: "\uC218\uC815 \uAC00\uB2A5",
11586
+ className: "shrink-0"
11587
+ }
11588
+ ),
11589
+ col.navigable && /* @__PURE__ */ jsx(
11590
+ SIcon,
11591
+ {
11592
+ name: "pageMove",
11593
+ size: 12,
11594
+ color: "var(--sys-color-link-accent)",
11595
+ label: "\uB2E4\uB978 \uD654\uBA74\uC73C\uB85C \uC774\uB3D9",
11596
+ className: "shrink-0"
11597
+ }
11598
+ ),
11599
+ col.required && // 라벨이 truncate 돼도 필수 표시는 남아야 하므로 shrink-0.
11600
+ // 아이콘·크기·색은 SKeyValueTable 헤더 필수 표시와 동일하게 맞춘다.
11601
+ /* @__PURE__ */ jsx(
11602
+ SIcon,
11603
+ {
11604
+ name: "asterisk",
11605
+ size: 12,
11606
+ color: "var(--sys-color-fg-accent)",
11607
+ className: "shrink-0"
11608
+ }
11609
+ ),
11610
+ isSortable && // 미정렬은 updown 을 default(회색)로 — 정렬할 수 있다는 것 자체가
11611
+ // 헤더에 보여야 한다. 정렬 중이면 action 색으로 올려 활성임을 알린다.
11612
+ // 라벨이 truncate 돼도 버튼은 남도록 shrink-0.
11613
+ /* @__PURE__ */ jsx("span", { className: "inline-flex shrink-0", children: /* @__PURE__ */ jsx(
11614
+ SGhostButton,
11615
+ {
11616
+ size: "xxs",
11617
+ intent: sortDir ? "action" : "default",
11618
+ icon: sortDir === "asc" ? "arrowUp" : sortDir === "desc" ? "arrowDown" : "updown",
11619
+ ariaLabel: `${col.label} \uC815\uB82C`,
11620
+ onClick: () => toggleSort(col)
11621
+ }
11483
11622
  ) })
11484
11623
  ] })
11485
11624
  }
@@ -11637,16 +11776,32 @@ var STable = /* @__PURE__ */ forwardRef(function STable2({
11637
11776
  ),
11638
11777
  children: [
11639
11778
  /* @__PURE__ */ jsx(SPagination, { currentPage: displayPage, lastPage, onPageChange: changePage }),
11640
- useRowsPerPageSelect && /* @__PURE__ */ jsx("div", { className: "absolute right-[10px] top-1/2 -translate-y-1/2", children: /* @__PURE__ */ jsx(
11641
- SSelect2,
11642
- {
11643
- options: rowsPerPageOption,
11644
- value: useInternalPagination ? innerRowsPerPage : pagination?.rowsPerPage,
11645
- emitValue: true,
11646
- onValueChange: (v) => changeRowsPerPage(Number(v)),
11647
- width: 128
11648
- }
11649
- ) })
11779
+ (useDensityToggle || useRowsPerPageSelect) && /* @__PURE__ */ jsxs("div", { className: "absolute right-[10px] top-1/2 flex -translate-y-1/2 items-center gap-[12px]", children: [
11780
+ useDensityToggle && // 라벨·아이콘은 현재 밀도가 아니라 **누르면 되는 상태**를 가리킨다 (dense 면 "넓게 보기").
11781
+ // row·rowDense 는 세로 막대라 90도 돌려 가로 행으로 쓴다.
11782
+ /* @__PURE__ */ jsx(
11783
+ STextLink,
11784
+ {
11785
+ size: "sm",
11786
+ icon: dense ? "row" : "rowDense",
11787
+ iconRotate: 90,
11788
+ iconColor: "var(--sys-color-fg-quaternary)",
11789
+ label: dense ? "\uB113\uAC8C \uBCF4\uAE30" : "\uC881\uAC8C \uBCF4\uAE30",
11790
+ onClick: () => onDenseChange?.(!dense)
11791
+ }
11792
+ ),
11793
+ useDensityToggle && useRowsPerPageSelect && /* @__PURE__ */ jsx(SDivider, { vertical: true, className: "my-[6px]" }),
11794
+ useRowsPerPageSelect && /* @__PURE__ */ jsx(
11795
+ SSelect2,
11796
+ {
11797
+ options: rowsPerPageOption,
11798
+ value: useInternalPagination ? innerRowsPerPage : pagination?.rowsPerPage,
11799
+ emitValue: true,
11800
+ onValueChange: (v) => changeRowsPerPage(Number(v)),
11801
+ width: 128
11802
+ }
11803
+ )
11804
+ ] })
11650
11805
  ]
11651
11806
  }
11652
11807
  )
@@ -12742,6 +12897,11 @@ function SCalendar({
12742
12897
  }
12743
12898
  );
12744
12899
  }
12900
+ var MODE_NOUN = {
12901
+ date: "\uB0A0\uC9DC",
12902
+ month: "\uC6D4",
12903
+ year: "\uC5F0\uB3C4"
12904
+ };
12745
12905
  var DATEPICKER_MODE_LIST_HEIGHT = 240;
12746
12906
  var DATEPICKER_MODE_ITEM_HEIGHT = 28;
12747
12907
  var YEAR_LIST_EDGE_BUFFER = 24;
@@ -13013,7 +13173,9 @@ function SDatePicker({
13013
13173
  placeholder,
13014
13174
  selectable,
13015
13175
  disabled = false,
13176
+ clearable = false,
13016
13177
  width,
13178
+ maxWidth,
13017
13179
  name,
13018
13180
  rules,
13019
13181
  status,
@@ -13056,7 +13218,8 @@ function SDatePicker({
13056
13218
  fieldMinW: 168,
13057
13219
  // 값 길이가 YYYY-MM-DD 로 정해져 있어, width="100%" 를 받아도 행 전체로
13058
13220
  // 늘어나지 않게 상한을 건다 (SKeyValueTable 이 모든 컨트롤에 100% 를 넘긴다).
13059
- // px 가 아니라 등급이라 --cmp-field-width-* 가 바뀌면 함께 따라간다
13221
+ // px 가 아니라 등급이라 --cmp-field-width-* 가 바뀌면 함께 따라간다.
13222
+ // 이 기본 상한이 걸리적거리는 자리(폭이 이미 좁게 정해진 팝오버 등)는 maxWidth prop 으로 덮는다
13060
13223
  fieldMaxW: "lg",
13061
13224
  fontSize: 14,
13062
13225
  lineHeight: 24,
@@ -13077,6 +13240,10 @@ function SDatePicker({
13077
13240
  if (rules && rules.length > 0) setRuleError(runRules(date, rules));
13078
13241
  setOpen(false);
13079
13242
  };
13243
+ const clear = () => {
13244
+ onValueChange?.(null);
13245
+ if (rules && rules.length > 0) setRuleError(runRules(null, rules));
13246
+ };
13080
13247
  const selectListboxValue = (nextValue) => {
13081
13248
  onValueChange?.(nextValue);
13082
13249
  if (rules && rules.length > 0) setRuleError(runRules(nextValue, rules));
@@ -13100,20 +13267,31 @@ function SDatePicker({
13100
13267
  errorMessage: ruleError !== "" ? ruleError : errorMessage,
13101
13268
  width,
13102
13269
  minWidth: dims.fieldMinW,
13103
- maxWidth: dims.fieldMaxW,
13270
+ maxWidth: maxWidth ?? dims.fieldMaxW,
13104
13271
  disabled,
13105
13272
  className,
13106
13273
  style,
13107
13274
  children: /* @__PURE__ */ jsxs(RPopover10.Root, { open, onOpenChange: (o) => !disabled && setOpen(o), children: [
13108
13275
  /* @__PURE__ */ jsx(RPopover10.Trigger, { asChild: true, children: /* @__PURE__ */ jsxs(
13109
- "button",
13276
+ "div",
13110
13277
  {
13111
- type: "button",
13112
- name,
13113
- disabled,
13278
+ role: "button",
13279
+ "aria-disabled": disabled,
13280
+ tabIndex: disabled ? -1 : 0,
13281
+ "data-name": name,
13114
13282
  onFocus: () => setFocused(true),
13115
13283
  onBlur: () => setFocused(false),
13116
- className: "flex h-full w-full items-center overflow-hidden bg-transparent outline-none disabled:cursor-not-allowed",
13284
+ onKeyDown: (event) => {
13285
+ if (disabled) return;
13286
+ if (event.key === "Enter" || event.key === " ") {
13287
+ event.preventDefault();
13288
+ setOpen(!open);
13289
+ }
13290
+ },
13291
+ className: cn(
13292
+ "flex h-full w-full items-center overflow-hidden bg-transparent outline-none",
13293
+ disabled ? "cursor-not-allowed" : "cursor-pointer"
13294
+ ),
13117
13295
  style: { padding: `0 ${dims.padX}px`, gap: dims.gap, borderRadius: dims.radius },
13118
13296
  children: [
13119
13297
  /* @__PURE__ */ jsx(
@@ -13137,6 +13315,18 @@ function SDatePicker({
13137
13315
  },
13138
13316
  children: hasValue ? value : resolvedPlaceholder
13139
13317
  }
13318
+ ),
13319
+ clearable && hasValue && !disabled && /* @__PURE__ */ jsx(
13320
+ SGhostButton,
13321
+ {
13322
+ icon: "close",
13323
+ size: "xxs",
13324
+ ariaLabel: `${MODE_NOUN[mode]} \uC9C0\uC6B0\uAE30`,
13325
+ onClick: (event) => {
13326
+ event.stopPropagation();
13327
+ clear();
13328
+ }
13329
+ }
13140
13330
  )
13141
13331
  ]
13142
13332
  }
@@ -13516,6 +13706,7 @@ function SDateRangePicker({
13516
13706
  maxRange,
13517
13707
  useTimePicker = false,
13518
13708
  disabled = false,
13709
+ clearable = false,
13519
13710
  width,
13520
13711
  name,
13521
13712
  rules,
@@ -13583,6 +13774,11 @@ function SDateRangePicker({
13583
13774
  if (rules && rules.length > 0) setRuleError(runRules(r, rules));
13584
13775
  if (!useTimePicker) setOpen(false);
13585
13776
  };
13777
+ const clear = () => {
13778
+ setPendingStart(null);
13779
+ onValueChange?.(null);
13780
+ if (rules && rules.length > 0) setRuleError(runRules(null, rules));
13781
+ };
13586
13782
  const resolvedPlaceholder = useTimePicker && placeholder === "YYYY-MM-DD ~ YYYY-MM-DD" ? "YYYY-MM-DD HH:mm ~ YYYY-MM-DD HH:mm" : placeholder;
13587
13783
  return /* @__PURE__ */ jsx(
13588
13784
  SField,
@@ -13618,12 +13814,23 @@ function SDateRangePicker({
13618
13814
  },
13619
13815
  children: [
13620
13816
  /* @__PURE__ */ jsx(RPopover10.Trigger, { asChild: true, children: /* @__PURE__ */ jsxs(
13621
- "button",
13817
+ "div",
13622
13818
  {
13623
- type: "button",
13624
- name,
13625
- disabled,
13626
- className: "flex h-full w-full items-center overflow-hidden bg-transparent outline-none disabled:cursor-not-allowed",
13819
+ role: "button",
13820
+ "aria-disabled": disabled,
13821
+ tabIndex: disabled ? -1 : 0,
13822
+ "data-name": name,
13823
+ onKeyDown: (event) => {
13824
+ if (disabled) return;
13825
+ if (event.key === "Enter" || event.key === " ") {
13826
+ event.preventDefault();
13827
+ setOpen(!open);
13828
+ }
13829
+ },
13830
+ className: cn(
13831
+ "flex h-full w-full items-center overflow-hidden bg-transparent outline-none",
13832
+ disabled ? "cursor-not-allowed" : "cursor-pointer"
13833
+ ),
13627
13834
  style: { padding: `0 ${dims.padX}px`, gap: dims.gap, borderRadius: dims.radius },
13628
13835
  children: [
13629
13836
  /* @__PURE__ */ jsx(
@@ -13654,6 +13861,18 @@ function SDateRangePicker({
13654
13861
  /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate text-left", children: endText })
13655
13862
  ] }) : resolvedPlaceholder
13656
13863
  }
13864
+ ),
13865
+ clearable && hasDisplay && !disabled && /* @__PURE__ */ jsx(
13866
+ SGhostButton,
13867
+ {
13868
+ icon: "close",
13869
+ size: "xxs",
13870
+ ariaLabel: "\uAE30\uAC04 \uC9C0\uC6B0\uAE30",
13871
+ onClick: (event) => {
13872
+ event.stopPropagation();
13873
+ clear();
13874
+ }
13875
+ }
13657
13876
  )
13658
13877
  ]
13659
13878
  }
@@ -14918,6 +15137,7 @@ var SChipFilter = /* @__PURE__ */ forwardRef(function SChipFilter2({
14918
15137
  );
14919
15138
  const [openKey, setOpenKey] = useState(null);
14920
15139
  const [addOpen, setAddOpen] = useState(false);
15140
+ const pendingOpenKeyRef = useRef(null);
14921
15141
  const triggerRefs = useRef(/* @__PURE__ */ new Map());
14922
15142
  const initializedDefaultsRef = useRef(/* @__PURE__ */ new Set());
14923
15143
  const prevOpenKeyRef = useRef(null);
@@ -14993,6 +15213,7 @@ var SChipFilter = /* @__PURE__ */ forwardRef(function SChipFilter2({
14993
15213
  const hasAddButton = fields.some(isAddableField);
14994
15214
  const hasRightActions = hasAddButton || showReset;
14995
15215
  const currentOpenField = fields.find((field) => field.key === openKey);
15216
+ const menuOpen = openKey != null || addOpen;
14996
15217
  const editorPopoverStyle = useMemo(() => {
14997
15218
  if (currentOpenField?.type === "keyword") {
14998
15219
  return { width: CHIPFILTER_LISTBOX_WIDTH };
@@ -15051,7 +15272,13 @@ var SChipFilter = /* @__PURE__ */ forwardRef(function SChipFilter2({
15051
15272
  onAddFilter?.(key);
15052
15273
  setAddOpen(false);
15053
15274
  const field = fields.find((f) => f.key === key);
15054
- if (field && !isInlineField(field)) requestAnimationFrame(() => setOpenKey(key));
15275
+ if (field && !isInlineField(field)) {
15276
+ pendingOpenKeyRef.current = key;
15277
+ requestAnimationFrame(() => {
15278
+ triggerRefs.current.get(key)?.focus();
15279
+ setOpenKey(key);
15280
+ });
15281
+ }
15055
15282
  };
15056
15283
  const rootStyle = useMemo(
15057
15284
  () => ({
@@ -15094,6 +15321,7 @@ var SChipFilter = /* @__PURE__ */ forwardRef(function SChipFilter2({
15094
15321
  {
15095
15322
  message,
15096
15323
  open: invalidGroups.has(runGroupIndex),
15324
+ placement: menuOpen ? "top" : "bottom",
15097
15325
  children: runBuffer
15098
15326
  },
15099
15327
  `group-${runGroupIndex}`
@@ -15107,14 +15335,16 @@ var SChipFilter = /* @__PURE__ */ forwardRef(function SChipFilter2({
15107
15335
  activeFields.forEach((field, index) => {
15108
15336
  const inline = isInlineField(field);
15109
15337
  const groupIndex = groupIndexByKey.get(field.key) ?? 0;
15110
- const warning = field.warning || invalidGroups.has(groupIndex);
15338
+ const warning = invalidGroups.has(groupIndex);
15111
15339
  const prevField = index > 0 ? activeFields[index - 1] : null;
15112
15340
  const prevGroupIndex = prevField ? groupIndexByKey.get(prevField.key) ?? 0 : null;
15113
15341
  const needsLeadingDivider = index === 0 ? false : inline || prevField != null && isInlineField(prevField) || groupIndex !== prevGroupIndex && groups[groupIndex]?.divider === true;
15342
+ const hoistDivider = needsLeadingDivider && groupIndex !== runGroupIndex;
15114
15343
  if (groupIndex !== runGroupIndex) {
15115
15344
  flushRun();
15116
15345
  runGroupIndex = groupIndex;
15117
15346
  }
15347
+ if (hoistDivider) nodes.push(/* @__PURE__ */ jsx(Divider, { inset: true }, `${field.key}-divider`));
15118
15348
  const addable = isAddableField(field);
15119
15349
  const showClear = addable || !isAtDefaultValue(field, values[field.key]);
15120
15350
  const handleClear = () => {
@@ -15178,7 +15408,7 @@ var SChipFilter = /* @__PURE__ */ forwardRef(function SChipFilter2({
15178
15408
  field.key
15179
15409
  );
15180
15410
  runBuffer.push(
15181
- needsLeadingDivider ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
15411
+ needsLeadingDivider && !hoistDivider ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
15182
15412
  /* @__PURE__ */ jsx(Divider, { inset: true }),
15183
15413
  node
15184
15414
  ] }, `${field.key}-wrap`) : node
@@ -15226,6 +15456,11 @@ var SChipFilter = /* @__PURE__ */ forwardRef(function SChipFilter2({
15226
15456
  side: "bottom",
15227
15457
  align: "start",
15228
15458
  sideOffset: 4,
15459
+ onCloseAutoFocus: (event) => {
15460
+ if (pendingOpenKeyRef.current == null) return;
15461
+ pendingOpenKeyRef.current = null;
15462
+ event.preventDefault();
15463
+ },
15229
15464
  className: cn("z-50 min-w-[180px]", FLOATING_SLIDE_ANIM),
15230
15465
  children: /* @__PURE__ */ jsx(ListBox, { children: inactiveFields.length > 0 ? inactiveFields.map((field) => /* @__PURE__ */ jsx(MenuItem, { onClick: () => addField(field.key), children: field.label }, field.key)) : /* @__PURE__ */ jsx(EmptyItem, { children: "\uCD94\uAC00\uD560 \uD544\uD130\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4." }) })
15231
15466
  }
@@ -15276,6 +15511,7 @@ var SChipFilter = /* @__PURE__ */ forwardRef(function SChipFilter2({
15276
15511
  value: values[currentOpenField.key],
15277
15512
  disabled: disabled || currentOpenField.disabled,
15278
15513
  onValueChange: (next) => emitValue(currentOpenField.key, next),
15514
+ onValueCommit: (next) => emitValueAndSearch(currentOpenField.key, next),
15279
15515
  onClose: () => setOpenKey(null)
15280
15516
  }
15281
15517
  )
@@ -15303,9 +15539,20 @@ function Divider({ inset, top } = {}) {
15303
15539
  function GroupTooltip({
15304
15540
  message,
15305
15541
  open,
15542
+ placement,
15306
15543
  children
15307
15544
  }) {
15308
- return /* @__PURE__ */ jsx(STooltip2, { trigger: "none", open, placement: "top", tooltipType: "warning", message: [message], children: /* @__PURE__ */ jsx("div", { className: "flex flex-wrap items-center gap-[var(--cmp-chipFilter-chip-group)]", children }) });
15545
+ return /* @__PURE__ */ jsx(
15546
+ STooltip2,
15547
+ {
15548
+ trigger: "none",
15549
+ open,
15550
+ placement,
15551
+ tooltipType: "warning",
15552
+ message: [message],
15553
+ children: /* @__PURE__ */ jsx("div", { className: "flex flex-wrap items-center gap-[var(--cmp-chipFilter-chip-group)]", children })
15554
+ }
15555
+ );
15309
15556
  }
15310
15557
  function FilterChip({
15311
15558
  field,
@@ -15415,6 +15662,7 @@ function EditorPanel({
15415
15662
  value,
15416
15663
  disabled,
15417
15664
  onValueChange,
15665
+ onValueCommit,
15418
15666
  onClose
15419
15667
  }) {
15420
15668
  const wrapsInPaddedTop = field.type === "date" || field.type === "period";
@@ -15434,7 +15682,8 @@ function EditorPanel({
15434
15682
  field,
15435
15683
  value,
15436
15684
  disabled,
15437
- onValueChange
15685
+ onValueChange,
15686
+ onValueCommit
15438
15687
  }
15439
15688
  ) }) : /* @__PURE__ */ jsx(
15440
15689
  FilterEditor,
@@ -15442,7 +15691,8 @@ function EditorPanel({
15442
15691
  field,
15443
15692
  value,
15444
15693
  disabled,
15445
- onValueChange
15694
+ onValueChange,
15695
+ onValueCommit
15446
15696
  }
15447
15697
  ),
15448
15698
  /* @__PURE__ */ jsxs(
@@ -15494,7 +15744,8 @@ function FilterEditor({
15494
15744
  field,
15495
15745
  value,
15496
15746
  disabled,
15497
- onValueChange
15747
+ onValueChange,
15748
+ onValueCommit
15498
15749
  }) {
15499
15750
  if (field.type === "period") {
15500
15751
  return /* @__PURE__ */ jsx(
@@ -15525,7 +15776,8 @@ function FilterEditor({
15525
15776
  field,
15526
15777
  value: isKeywordValue(value) ? value : keywordEntries(value),
15527
15778
  disabled,
15528
- onValueChange
15779
+ onValueChange,
15780
+ onValueCommit
15529
15781
  }
15530
15782
  );
15531
15783
  }
@@ -15567,7 +15819,9 @@ function DatePresetEditor({
15567
15819
  onValueChange,
15568
15820
  selectable: field.selectable,
15569
15821
  disabled,
15570
- placeholder: field.placeholder
15822
+ placeholder: field.placeholder,
15823
+ width: "100%",
15824
+ maxWidth: "100%"
15571
15825
  }
15572
15826
  );
15573
15827
  }
@@ -15627,7 +15881,8 @@ function PeriodPresetEditor({
15627
15881
  onValueChange: updateInputValue,
15628
15882
  selectable: field.selectable,
15629
15883
  disabled,
15630
- width: "100%"
15884
+ width: "100%",
15885
+ maxWidth: "100%"
15631
15886
  }
15632
15887
  );
15633
15888
  }
@@ -15640,7 +15895,8 @@ function PeriodPresetEditor({
15640
15895
  onValueChange: updateInputValue,
15641
15896
  selectable: field.selectable,
15642
15897
  disabled,
15643
- width: "100%"
15898
+ width: "100%",
15899
+ maxWidth: "100%"
15644
15900
  }
15645
15901
  );
15646
15902
  }
@@ -15687,7 +15943,8 @@ function PeriodPresetEditor({
15687
15943
  onValueChange: updateInputValue,
15688
15944
  selectable: field.selectable,
15689
15945
  disabled,
15690
- width: "100%"
15946
+ width: "100%",
15947
+ maxWidth: "100%"
15691
15948
  }
15692
15949
  );
15693
15950
  }
@@ -15837,6 +16094,7 @@ function InlineDatePresetFilter({
15837
16094
  ) : badge)
15838
16095
  ] });
15839
16096
  }
16097
+ var CSV_PASTE_SEPARATOR = /[,\r\n\t]+/;
15840
16098
  var MATCH_MODE_OPTIONS = [
15841
16099
  { value: "contains", label: "\uD3EC\uD568" },
15842
16100
  { value: "exact", label: "\uC77C\uCE58" }
@@ -15849,20 +16107,61 @@ function KeywordEditor({
15849
16107
  field,
15850
16108
  value,
15851
16109
  disabled,
15852
- onValueChange
16110
+ onValueChange,
16111
+ onValueCommit
15853
16112
  }) {
15854
16113
  const entries3 = keywordEntries(value);
15855
16114
  const [draft, setDraft] = useState("");
15856
16115
  const [mode, setMode] = useState(
15857
16116
  isKeywordValue(value) ? value.mode : "contains"
15858
16117
  );
15859
- const emit3 = (nextEntries) => {
15860
- onValueChange(field.matchModes ? { keywords: nextEntries, mode } : nextEntries);
16118
+ const inputRef = useRef(null);
16119
+ useEffect(() => {
16120
+ if (disabled) return;
16121
+ inputRef.current?.focus();
16122
+ }, [disabled]);
16123
+ const emit3 = (nextEntries, nextMode = mode) => {
16124
+ onValueChange(field.matchModes ? { keywords: nextEntries, mode: nextMode } : nextEntries);
15861
16125
  };
15862
- const addEntry = (raw) => {
16126
+ const commit = (nextEntries) => {
16127
+ onValueCommit(field.matchModes ? { keywords: nextEntries, mode } : nextEntries);
16128
+ };
16129
+ const changeMode = (next) => {
16130
+ setMode(next);
16131
+ emit3(entries3, next);
16132
+ };
16133
+ const newEntries = (candidates) => candidates.reduce((acc, raw) => {
15863
16134
  const next = raw.trim();
15864
- if (next === "" || entries3.includes(next)) return;
15865
- emit3([...entries3, next]);
16135
+ if (next === "" || entries3.includes(next) || acc.includes(next)) return acc;
16136
+ return [...acc, next];
16137
+ }, []);
16138
+ const addEntry = (raw) => {
16139
+ const added = newEntries([raw]);
16140
+ if (added.length === 0) {
16141
+ setDraft("");
16142
+ return;
16143
+ }
16144
+ commit([...entries3, ...added]);
16145
+ setDraft("");
16146
+ };
16147
+ const changeDraft = (next) => {
16148
+ if (field.input !== "csv" || !next.includes(",")) {
16149
+ setDraft(next);
16150
+ return;
16151
+ }
16152
+ const parts = next.split(",");
16153
+ const tail = parts.pop() ?? "";
16154
+ const added = newEntries(parts);
16155
+ if (added.length > 0) commit([...entries3, ...added]);
16156
+ setDraft(tail);
16157
+ };
16158
+ const onPaste = (event) => {
16159
+ if (field.input !== "csv") return;
16160
+ const text = event.clipboardData.getData("text");
16161
+ if (!CSV_PASTE_SEPARATOR.test(text)) return;
16162
+ event.preventDefault();
16163
+ const added = newEntries((draft + text).split(CSV_PASTE_SEPARATOR));
16164
+ if (added.length > 0) commit([...entries3, ...added]);
15866
16165
  setDraft("");
15867
16166
  };
15868
16167
  const removeEntry = (entry) => {
@@ -15894,7 +16193,7 @@ function KeywordEditor({
15894
16193
  options: field.excludeMode ? [...MATCH_MODE_OPTIONS, EXCLUDE_MATCH_MODE_OPTION] : MATCH_MODE_OPTIONS,
15895
16194
  value: mode,
15896
16195
  disabled,
15897
- onValueChange: (next) => setMode(next)
16196
+ onValueChange: (next) => changeMode(next)
15898
16197
  }
15899
16198
  )
15900
16199
  ] }),
@@ -15903,10 +16202,12 @@ function KeywordEditor({
15903
16202
  /* @__PURE__ */ jsx(
15904
16203
  "input",
15905
16204
  {
16205
+ ref: inputRef,
15906
16206
  value: draft,
15907
16207
  disabled,
15908
- placeholder: field.placeholder ?? "\uC785\uB825 \uD6C4 Enter\uB85C \uCD94\uAC00",
15909
- onChange: (event) => setDraft(event.target.value),
16208
+ placeholder: field.placeholder ?? (field.input === "csv" ? "\uB2E4\uC911 \uD0A4\uC6CC\uB4DC\uB294 \uCF64\uB9C8(,)\uB85C \uAD6C\uBD84" : "\uC785\uB825 \uD6C4 Enter\uB85C \uCD94\uAC00"),
16209
+ onChange: (event) => changeDraft(event.target.value),
16210
+ onPaste,
15910
16211
  onKeyDown,
15911
16212
  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"
15912
16213
  }