sellmate-design-system-react 0.8.2 → 0.8.4

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.
Files changed (72) hide show
  1. package/dist/components/SBarcodeInput/SBarcodeInput.cjs +78 -2
  2. package/dist/components/SBarcodeInput/SBarcodeInput.cjs.map +1 -1
  3. package/dist/components/SBarcodeInput/SBarcodeInput.js +78 -2
  4. package/dist/components/SBarcodeInput/SBarcodeInput.js.map +1 -1
  5. package/dist/components/SCheckbox/SCheckbox.cjs +1 -1
  6. package/dist/components/SCheckbox/SCheckbox.cjs.map +1 -1
  7. package/dist/components/SCheckbox/SCheckbox.js +1 -1
  8. package/dist/components/SCheckbox/SCheckbox.js.map +1 -1
  9. package/dist/components/SChipInput/SChipInput.cjs +78 -2
  10. package/dist/components/SChipInput/SChipInput.cjs.map +1 -1
  11. package/dist/components/SChipInput/SChipInput.js +78 -2
  12. package/dist/components/SChipInput/SChipInput.js.map +1 -1
  13. package/dist/components/SDatePicker/SDatePicker.cjs +78 -2
  14. package/dist/components/SDatePicker/SDatePicker.cjs.map +1 -1
  15. package/dist/components/SDatePicker/SDatePicker.js +78 -2
  16. package/dist/components/SDatePicker/SDatePicker.js.map +1 -1
  17. package/dist/components/SDateRangePicker/SDateRangePicker.cjs +78 -2
  18. package/dist/components/SDateRangePicker/SDateRangePicker.cjs.map +1 -1
  19. package/dist/components/SDateRangePicker/SDateRangePicker.js +78 -2
  20. package/dist/components/SDateRangePicker/SDateRangePicker.js.map +1 -1
  21. package/dist/components/SField/SField.cjs +78 -2
  22. package/dist/components/SField/SField.cjs.map +1 -1
  23. package/dist/components/SField/SField.js +78 -2
  24. package/dist/components/SField/SField.js.map +1 -1
  25. package/dist/components/SFilePicker/SFilePicker.cjs +78 -2
  26. package/dist/components/SFilePicker/SFilePicker.cjs.map +1 -1
  27. package/dist/components/SFilePicker/SFilePicker.js +78 -2
  28. package/dist/components/SFilePicker/SFilePicker.js.map +1 -1
  29. package/dist/components/SInput/SInput.cjs +78 -2
  30. package/dist/components/SInput/SInput.cjs.map +1 -1
  31. package/dist/components/SInput/SInput.js +78 -2
  32. package/dist/components/SInput/SInput.js.map +1 -1
  33. package/dist/components/SKeyValueTable/SKeyValueTable.cjs +229 -122
  34. package/dist/components/SKeyValueTable/SKeyValueTable.cjs.map +1 -1
  35. package/dist/components/SKeyValueTable/SKeyValueTable.js +229 -122
  36. package/dist/components/SKeyValueTable/SKeyValueTable.js.map +1 -1
  37. package/dist/components/SNumberInput/SNumberInput.cjs +78 -2
  38. package/dist/components/SNumberInput/SNumberInput.cjs.map +1 -1
  39. package/dist/components/SNumberInput/SNumberInput.js +78 -2
  40. package/dist/components/SNumberInput/SNumberInput.js.map +1 -1
  41. package/dist/components/SSelect/SSelect.cjs +159 -52
  42. package/dist/components/SSelect/SSelect.cjs.map +1 -1
  43. package/dist/components/SSelect/SSelect.js +159 -52
  44. package/dist/components/SSelect/SSelect.js.map +1 -1
  45. package/dist/components/SSelect/select.config.d.ts +7 -0
  46. package/dist/components/STable/STable.cjs +160 -53
  47. package/dist/components/STable/STable.cjs.map +1 -1
  48. package/dist/components/STable/STable.js +160 -53
  49. package/dist/components/STable/STable.js.map +1 -1
  50. package/dist/components/STextarea/STextarea.cjs +78 -2
  51. package/dist/components/STextarea/STextarea.cjs.map +1 -1
  52. package/dist/components/STextarea/STextarea.js +78 -2
  53. package/dist/components/STextarea/STextarea.js.map +1 -1
  54. package/dist/components/STimePicker/STimePicker.cjs +78 -2
  55. package/dist/components/STimePicker/STimePicker.cjs.map +1 -1
  56. package/dist/components/STimePicker/STimePicker.js +78 -2
  57. package/dist/components/STimePicker/STimePicker.js.map +1 -1
  58. package/dist/components/STimeRangePicker/STimeRangePicker.cjs +78 -2
  59. package/dist/components/STimeRangePicker/STimeRangePicker.cjs.map +1 -1
  60. package/dist/components/STimeRangePicker/STimeRangePicker.js +78 -2
  61. package/dist/components/STimeRangePicker/STimeRangePicker.js.map +1 -1
  62. package/dist/components/STooltip/STooltip.cjs +78 -2
  63. package/dist/components/STooltip/STooltip.cjs.map +1 -1
  64. package/dist/components/STooltip/STooltip.d.ts +1 -1
  65. package/dist/components/STooltip/STooltip.js +78 -2
  66. package/dist/components/STooltip/STooltip.js.map +1 -1
  67. package/dist/index.cjs +160 -53
  68. package/dist/index.cjs.map +1 -1
  69. package/dist/index.js +160 -53
  70. package/dist/index.js.map +1 -1
  71. package/dist/styles.css +4 -8
  72. package/package.json +1 -1
@@ -2883,6 +2883,75 @@ function useInsideModal() {
2883
2883
  return useContext(InsideModalContext);
2884
2884
  }
2885
2885
 
2886
+ // src/lib/sanitize-inline-html.ts
2887
+ var ALLOWED_INLINE_TAGS = /* @__PURE__ */ new Set(["A", "B", "STRONG", "I", "EM", "BR", "SPAN"]);
2888
+ var ALLOWED_ATTRS_BY_TAG = {
2889
+ A: /* @__PURE__ */ new Set(["href", "target", "rel", "class"])
2890
+ };
2891
+ var COMMON_ALLOWED_ATTRS = /* @__PURE__ */ new Set(["class"]);
2892
+ var SAFE_HREF_RE = /^(https?:|mailto:|tel:|\/|#|\.)/i;
2893
+ var DROP_CONTENT_TAGS = /* @__PURE__ */ new Set([
2894
+ "SCRIPT",
2895
+ "STYLE",
2896
+ "IFRAME",
2897
+ "OBJECT",
2898
+ "EMBED",
2899
+ "META",
2900
+ "LINK",
2901
+ "BASE",
2902
+ "NOSCRIPT",
2903
+ // SVG/MATH 는 foreign-content 파싱 모드로 전환되어 재직렬화 시 다른 트리로
2904
+ // 재파싱될 수 있다(mXSS 벡터). 인라인 라벨에 필요 없으므로 통째로 제거한다.
2905
+ "SVG",
2906
+ "MATH",
2907
+ "TEMPLATE"
2908
+ ]);
2909
+ var escapeHtml = (value) => value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
2910
+ var sanitizeNode = (node) => {
2911
+ if (node.nodeType === Node.COMMENT_NODE) {
2912
+ node.remove();
2913
+ return;
2914
+ }
2915
+ if (node.nodeType !== Node.ELEMENT_NODE) return;
2916
+ const element = node;
2917
+ const tagName = element.tagName;
2918
+ if (DROP_CONTENT_TAGS.has(tagName)) {
2919
+ element.remove();
2920
+ return;
2921
+ }
2922
+ Array.from(element.childNodes).forEach((child) => sanitizeNode(child));
2923
+ if (ALLOWED_INLINE_TAGS.has(tagName)) {
2924
+ if (element.hasAttribute("className")) {
2925
+ const val = element.getAttribute("className");
2926
+ element.removeAttribute("className");
2927
+ if (!element.hasAttribute("class")) element.setAttribute("class", val);
2928
+ }
2929
+ const allowedAttrs = ALLOWED_ATTRS_BY_TAG[tagName] ?? COMMON_ALLOWED_ATTRS;
2930
+ Array.from(element.attributes).forEach((attr) => {
2931
+ if (!allowedAttrs.has(attr.name)) element.removeAttribute(attr.name);
2932
+ });
2933
+ if (tagName === "A") {
2934
+ const href = element.getAttribute("href");
2935
+ if (href !== null && href !== "" && !SAFE_HREF_RE.test(href)) element.removeAttribute("href");
2936
+ if (element.hasAttribute("target")) {
2937
+ element.setAttribute("rel", "noopener noreferrer");
2938
+ }
2939
+ }
2940
+ return;
2941
+ }
2942
+ const parent = element.parentNode;
2943
+ if (parent == null) return;
2944
+ while (element.firstChild) parent.insertBefore(element.firstChild, element);
2945
+ parent.removeChild(element);
2946
+ };
2947
+ var sanitizeInlineHtml = (value) => {
2948
+ if (typeof document === "undefined") return escapeHtml(value);
2949
+ const template = document.createElement("template");
2950
+ template.innerHTML = value;
2951
+ Array.from(template.content.childNodes).forEach((child) => sanitizeNode(child));
2952
+ return template.innerHTML;
2953
+ };
2954
+
2886
2955
  // src/components/STag/tag.config.ts
2887
2956
  var TAG_SIZE_CONFIG = {
2888
2957
  xs: {
@@ -3081,7 +3150,14 @@ function useBody(content, message = []) {
3081
3150
  if (content != null) return content;
3082
3151
  if (message.length === 0) return null;
3083
3152
  const singleLine = message.length === 1;
3084
- return /* @__PURE__ */ jsx(Fragment, { children: message.map((line, i) => /* @__PURE__ */ jsx("div", { className: singleLine ? "leading-[24px]" : void 0, children: line }, i)) });
3153
+ return /* @__PURE__ */ jsx(Fragment, { children: message.map((line, i) => /* @__PURE__ */ jsx(
3154
+ "div",
3155
+ {
3156
+ className: singleLine ? "leading-[24px]" : void 0,
3157
+ dangerouslySetInnerHTML: { __html: sanitizeInlineHtml(line) }
3158
+ },
3159
+ i
3160
+ )) });
3085
3161
  }
3086
3162
  var STooltip = forwardRef(function STooltip2({
3087
3163
  content,
@@ -3139,7 +3215,7 @@ var STooltip = forwardRef(function STooltip2({
3139
3215
  if (disabled || body == null) return /* @__PURE__ */ jsx(Fragment, { children: triggerNode });
3140
3216
  const isHover = trigger === "hover";
3141
3217
  const contentClass = cn(
3142
- "z-50 max-w-xs rounded-[6px] px-[16px] py-[12px] text-[12px] leading-[20px]",
3218
+ "z-50 w-fit whitespace-nowrap rounded-[6px] px-[16px] py-[12px] text-[12px] leading-[20px]",
3143
3219
  FLOATING_SLIDE_ANIM,
3144
3220
  className
3145
3221
  );
@@ -4163,75 +4239,6 @@ var FIREFOX_SCROLLBAR_CLASS = "supports-[not_selector(::-webkit-scrollbar)]:[scr
4163
4239
  var SCROLLBAR_CLASS = `${FIREFOX_SCROLLBAR_CLASS} [&::-webkit-scrollbar]:h-[12px] [&::-webkit-scrollbar]:w-[12px] [&::-webkit-scrollbar]:bg-transparent [&::-webkit-scrollbar-track]:bg-transparent [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:border-2 [&::-webkit-scrollbar-thumb]:border-solid [&::-webkit-scrollbar-thumb]:border-transparent [&::-webkit-scrollbar-thumb]:bg-[var(--color-grey-45)] [&::-webkit-scrollbar-thumb]:bg-clip-padding`;
4164
4240
  var SCROLLBAR_TRACK_BG_CLASS = "supports-[not_selector(::-webkit-scrollbar)]:[scrollbar-color:var(--color-grey-45)_var(--color-grey-10)] [&::-webkit-scrollbar]:bg-[var(--color-grey-10)] [&::-webkit-scrollbar-track]:bg-[var(--color-grey-10)]";
4165
4241
  var SCROLLBAR_TRACK_BORDER_CLASS = "[&::-webkit-scrollbar-track:vertical]:[border-left:1px_solid_var(--sys-color-border-default)] [&::-webkit-scrollbar-track:horizontal]:[border-top:1px_solid_var(--sys-color-border-default)] [&::-webkit-scrollbar-thumb:vertical]:[border-left-width:3px] [&::-webkit-scrollbar-thumb:horizontal]:[border-top-width:3px]";
4166
-
4167
- // src/lib/sanitize-inline-html.ts
4168
- var ALLOWED_INLINE_TAGS = /* @__PURE__ */ new Set(["A", "B", "STRONG", "I", "EM", "BR", "SPAN"]);
4169
- var ALLOWED_ATTRS_BY_TAG = {
4170
- A: /* @__PURE__ */ new Set(["href", "target", "rel", "class"])
4171
- };
4172
- var COMMON_ALLOWED_ATTRS = /* @__PURE__ */ new Set(["class"]);
4173
- var SAFE_HREF_RE = /^(https?:|mailto:|tel:|\/|#|\.)/i;
4174
- var DROP_CONTENT_TAGS = /* @__PURE__ */ new Set([
4175
- "SCRIPT",
4176
- "STYLE",
4177
- "IFRAME",
4178
- "OBJECT",
4179
- "EMBED",
4180
- "META",
4181
- "LINK",
4182
- "BASE",
4183
- "NOSCRIPT",
4184
- // SVG/MATH 는 foreign-content 파싱 모드로 전환되어 재직렬화 시 다른 트리로
4185
- // 재파싱될 수 있다(mXSS 벡터). 인라인 라벨에 필요 없으므로 통째로 제거한다.
4186
- "SVG",
4187
- "MATH",
4188
- "TEMPLATE"
4189
- ]);
4190
- var escapeHtml = (value) => value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
4191
- var sanitizeNode = (node) => {
4192
- if (node.nodeType === Node.COMMENT_NODE) {
4193
- node.remove();
4194
- return;
4195
- }
4196
- if (node.nodeType !== Node.ELEMENT_NODE) return;
4197
- const element = node;
4198
- const tagName = element.tagName;
4199
- if (DROP_CONTENT_TAGS.has(tagName)) {
4200
- element.remove();
4201
- return;
4202
- }
4203
- Array.from(element.childNodes).forEach((child) => sanitizeNode(child));
4204
- if (ALLOWED_INLINE_TAGS.has(tagName)) {
4205
- if (element.hasAttribute("className")) {
4206
- const val = element.getAttribute("className");
4207
- element.removeAttribute("className");
4208
- if (!element.hasAttribute("class")) element.setAttribute("class", val);
4209
- }
4210
- const allowedAttrs = ALLOWED_ATTRS_BY_TAG[tagName] ?? COMMON_ALLOWED_ATTRS;
4211
- Array.from(element.attributes).forEach((attr) => {
4212
- if (!allowedAttrs.has(attr.name)) element.removeAttribute(attr.name);
4213
- });
4214
- if (tagName === "A") {
4215
- const href = element.getAttribute("href");
4216
- if (href !== null && href !== "" && !SAFE_HREF_RE.test(href)) element.removeAttribute("href");
4217
- if (element.hasAttribute("target")) {
4218
- element.setAttribute("rel", "noopener noreferrer");
4219
- }
4220
- }
4221
- return;
4222
- }
4223
- const parent = element.parentNode;
4224
- if (parent == null) return;
4225
- while (element.firstChild) parent.insertBefore(element.firstChild, element);
4226
- parent.removeChild(element);
4227
- };
4228
- var sanitizeInlineHtml = (value) => {
4229
- if (typeof document === "undefined") return escapeHtml(value);
4230
- const template = document.createElement("template");
4231
- template.innerHTML = value;
4232
- Array.from(template.content.childNodes).forEach((child) => sanitizeNode(child));
4233
- return template.innerHTML;
4234
- };
4235
4242
  function SPortal({
4236
4243
  open,
4237
4244
  onClose,
@@ -4338,6 +4345,16 @@ function flattenOptions(options, depth = 1) {
4338
4345
  return out;
4339
4346
  }
4340
4347
  var selectableOptions = (flat) => flat.filter((o) => !o.isGroup && !o.disabled);
4348
+ function collectGroupLeafValues(flat, groupIndex) {
4349
+ const group = flat[groupIndex];
4350
+ if (group == null || !group.isGroup) return [];
4351
+ const values = [];
4352
+ for (let i = groupIndex + 1; i < flat.length; i++) {
4353
+ if (flat[i].depth <= group.depth) break;
4354
+ if (!flat[i].isGroup && !flat[i].disabled) values.push(flat[i].value);
4355
+ }
4356
+ return values;
4357
+ }
4341
4358
  var isHtmlLabel = (label) => /<[a-z][\s\S]*>/i.test(label);
4342
4359
  var extractText = (html) => html.replace(/<[^>]*>/g, "");
4343
4360
  var SEARCH_THRESHOLD = 11;
@@ -4463,16 +4480,31 @@ var SSelect = forwardRef(function SSelect2({
4463
4480
  const allValues = selectable.map((o) => o.value);
4464
4481
  const allSelected = allValues.length > 0 && allValues.every((v) => selected.includes(v));
4465
4482
  const hasItems = filtered.some((o) => !o.isGroup);
4483
+ const groupLeafValues = useMemo(() => {
4484
+ const map = /* @__PURE__ */ new Map();
4485
+ filtered.forEach((o, i) => {
4486
+ if (o.isGroup) map.set(i, collectGroupLeafValues(filtered, i));
4487
+ });
4488
+ return map;
4489
+ }, [filtered]);
4466
4490
  const navigableItems = useMemo(() => {
4467
4491
  const items = [];
4468
4492
  if (multi && useSelectAll && allValues.length > 0) {
4469
- items.push({ value: "__select_all__", isSelectAll: true });
4493
+ items.push({ key: "__select_all__", isSelectAll: true });
4470
4494
  }
4471
- filtered.forEach((o) => {
4472
- if (!o.isGroup && !o.disabled) items.push({ value: o.value });
4495
+ filtered.forEach((o, i) => {
4496
+ if (o.disabled) return;
4497
+ if (o.isGroup) {
4498
+ if (!multi) return;
4499
+ const leafValues = groupLeafValues.get(i) ?? [];
4500
+ if (leafValues.length === 0) return;
4501
+ items.push({ key: `__group_${i}__`, isGroup: true, leafValues });
4502
+ } else {
4503
+ items.push({ key: o.value, value: o.value });
4504
+ }
4473
4505
  });
4474
4506
  return items;
4475
- }, [allValues.length, filtered, multi, useSelectAll]);
4507
+ }, [allValues.length, filtered, groupLeafValues, multi, useSelectAll]);
4476
4508
  const triggerText = (() => {
4477
4509
  if (selected.length === 0) return placeholder;
4478
4510
  if (multi) {
@@ -4494,6 +4526,17 @@ var SSelect = forwardRef(function SSelect2({
4494
4526
  const handleSelectAll = () => {
4495
4527
  emit(allSelected ? [] : allValues);
4496
4528
  };
4529
+ const handleToggleGroup = (leafValues) => {
4530
+ if (leafValues.length === 0) return;
4531
+ const selectedSet = new Set(selected);
4532
+ const allSelectedInGroup = leafValues.every((v) => selectedSet.has(v));
4533
+ if (allSelectedInGroup) {
4534
+ const remove = new Set(leafValues);
4535
+ emit(selected.filter((v) => !remove.has(v)));
4536
+ } else {
4537
+ emit([...selected, ...leafValues.filter((v) => !selectedSet.has(v))]);
4538
+ }
4539
+ };
4497
4540
  useEffect(() => {
4498
4541
  if (!open) {
4499
4542
  setFocusedIndex(-1);
@@ -4534,6 +4577,10 @@ var SSelect = forwardRef(function SSelect2({
4534
4577
  handleSelectAll();
4535
4578
  return;
4536
4579
  }
4580
+ if (focused.isGroup) {
4581
+ handleToggleGroup(focused.leafValues ?? []);
4582
+ return;
4583
+ }
4537
4584
  handleSelect(focused.value);
4538
4585
  };
4539
4586
  const handleKeyDown = (event) => {
@@ -4697,20 +4744,20 @@ var SSelect = forwardRef(function SSelect2({
4697
4744
  items: filtered,
4698
4745
  multi,
4699
4746
  selected,
4700
- focusedValue: navigableItems[focusedIndex]?.isSelectAll ? "__select_all__" : navigableItems[focusedIndex]?.value,
4747
+ groupLeafValues,
4748
+ focusedValue: navigableItems[focusedIndex]?.key,
4701
4749
  showSelectAll: multi && useSelectAll && allValues.length > 0,
4702
4750
  selectAllLabel: allSelectedLabel,
4703
4751
  selectAllSelected: allSelected,
4704
4752
  onSelectAll: handleSelectAll,
4705
4753
  onSelect: handleSelect,
4706
- onHoverValue: (value2) => {
4707
- if (value2 == null) {
4754
+ onToggleGroup: handleToggleGroup,
4755
+ onHoverValue: (key) => {
4756
+ if (key == null) {
4708
4757
  setFocusedIndex(-1);
4709
4758
  return;
4710
4759
  }
4711
- const index = navigableItems.findIndex(
4712
- (item) => item.isSelectAll ? value2 === "__select_all__" : item.value === value2
4713
- );
4760
+ const index = navigableItems.findIndex((item) => item.key === key);
4714
4761
  if (index >= 0) setFocusedIndex(index);
4715
4762
  }
4716
4763
  }
@@ -4729,20 +4776,23 @@ function ItemList({
4729
4776
  items,
4730
4777
  multi,
4731
4778
  selected,
4779
+ groupLeafValues,
4732
4780
  focusedValue,
4733
4781
  showSelectAll,
4734
4782
  selectAllLabel,
4735
4783
  selectAllSelected,
4736
4784
  onSelectAll,
4737
4785
  onSelect,
4786
+ onToggleGroup,
4738
4787
  onHoverValue
4739
4788
  }) {
4789
+ const selectedSet = useMemo(() => new Set(selected), [selected]);
4740
4790
  const itemBase = cn(
4741
4791
  "group flex w-full cursor-pointer select-none items-center gap-[8px] border-none bg-transparent text-left text-[12px] leading-[20px] outline-none",
4742
4792
  "text-[color:var(--cmp-listItem-content-default)]",
4743
4793
  "disabled:cursor-not-allowed disabled:text-[color:var(--cmp-listItem-content-disabled)]"
4744
4794
  );
4745
- const activeClass = "hover:bg-[var(--cmp-listItem-bg-hover)] hover:text-[color:var(--cmp-listItem-content-hover)] focus:bg-[var(--cmp-listItem-bg-hover)] focus:text-[color:var(--cmp-listItem-content-hover)]";
4795
+ const activeClass = "hover:bg-[var(--cmp-listItem-bg-hover)] hover:text-[color:var(--cmp-listItem-content-hover)] focus-visible:bg-[var(--cmp-listItem-bg-hover)] focus-visible:text-[color:var(--cmp-listItem-content-hover)]";
4746
4796
  return /* @__PURE__ */ jsxs("div", { role: "listbox", "aria-multiselectable": multi || void 0, children: [
4747
4797
  showSelectAll && /* @__PURE__ */ jsxs(
4748
4798
  "button",
@@ -4759,37 +4809,81 @@ function ItemList({
4759
4809
  onMouseLeave: () => onHoverValue(null),
4760
4810
  onClick: onSelectAll,
4761
4811
  children: [
4762
- /* @__PURE__ */ jsx(
4763
- "span",
4764
- {
4765
- className: cn(
4766
- "flex h-[16px] w-[16px] flex-shrink-0 items-center justify-center rounded-[2px] border border-solid",
4767
- selectAllSelected ? focusedValue === "__select_all__" ? "border-[color:var(--cmp-checkbox-checked-border-inverse)] bg-[var(--cmp-checkbox-checked-bg-default)]" : "border-[color:var(--cmp-checkbox-checked-bg-default)] bg-[var(--cmp-checkbox-checked-bg-default)]" : "border-[color:var(--cmp-checkbox-unchecked-border-default)] bg-[var(--cmp-checkbox-unchecked-bg-default)]"
4768
- ),
4769
- children: selectAllSelected && /* @__PURE__ */ jsx(
4770
- SIcon,
4771
- {
4772
- name: "checkboxCheck",
4773
- size: 12,
4774
- color: "var(--cmp-checkbox-checked-icon-default)"
4775
- }
4776
- )
4777
- }
4778
- ),
4812
+ /* @__PURE__ */ jsx(CheckboxBox, { state: selectAllSelected, focused: focusedValue === "__select_all__" }),
4779
4813
  /* @__PURE__ */ jsx("span", { className: "flex-1 truncate", children: selectAllLabel })
4780
4814
  ]
4781
4815
  }
4782
4816
  ),
4783
- items.map((o) => {
4817
+ items.map((o, idx) => {
4784
4818
  const padLeft = o.depth === 1 ? 12 : o.depth === 2 ? 20 : 28;
4819
+ const rowKey = `${o.value}-${idx}`;
4785
4820
  if (o.isGroup) {
4786
4821
  const isDepth1 = o.depth === 1;
4822
+ const groupTone = isDepth1 ? "border-t border-solid border-[color:var(--cmp-listItem-depth1-border-color)] bg-[var(--cmp-listItem-depth1-bg-default)] font-bold" : "bg-[var(--cmp-listItem-depth2-middle-bg)] font-medium";
4823
+ if (multi) {
4824
+ const leafValues = groupLeafValues.get(idx) ?? [];
4825
+ const total = leafValues.length;
4826
+ const selectedCount = leafValues.reduce((n, v) => selectedSet.has(v) ? n + 1 : n, 0);
4827
+ const groupState = total === 0 || selectedCount === 0 ? false : selectedCount === total ? true : null;
4828
+ const groupDisabled = o.disabled || total === 0;
4829
+ const groupFocused = focusedValue === `__group_${idx}__`;
4830
+ return /* @__PURE__ */ jsxs(
4831
+ "button",
4832
+ {
4833
+ type: "button",
4834
+ role: "option",
4835
+ "aria-selected": groupState === true,
4836
+ disabled: groupDisabled,
4837
+ "data-select-focused": groupFocused ? "true" : void 0,
4838
+ "data-select-active": groupFocused ? "true" : void 0,
4839
+ className: cn(
4840
+ "flex w-full select-none items-center gap-[8px] border-none py-[4px] pr-[12px] text-left text-[12px] leading-[20px] text-[color:var(--cmp-listItem-content-default)]",
4841
+ groupTone,
4842
+ groupDisabled ? "cursor-not-allowed text-[color:var(--cmp-listItem-content-disabled)]" : "cursor-pointer",
4843
+ groupDisabled && isDepth1 && "bg-[var(--cmp-listItem-depth1-bg-disabled)]",
4844
+ // 포커스 하이라이트는 depth 배경보다 뒤에 둬 tailwind-merge 로 이기게 한다.
4845
+ groupFocused && !groupDisabled && "bg-[var(--cmp-listItem-bg-hover)] text-[color:var(--cmp-listItem-content-hover)]"
4846
+ ),
4847
+ style: { paddingLeft: padLeft },
4848
+ onMouseEnter: () => onHoverValue(`__group_${idx}__`),
4849
+ onMouseLeave: () => onHoverValue(null),
4850
+ onClick: () => onToggleGroup(leafValues),
4851
+ children: [
4852
+ /* @__PURE__ */ jsx(CheckboxBox, { state: groupState, disabled: groupDisabled, focused: groupFocused }),
4853
+ isHtmlLabel(o.label) ? /* @__PURE__ */ jsx(
4854
+ "span",
4855
+ {
4856
+ className: "min-w-0 truncate",
4857
+ dangerouslySetInnerHTML: { __html: sanitizeInlineHtml(o.label) }
4858
+ }
4859
+ ) : /* @__PURE__ */ jsx("span", { className: "min-w-0 truncate", children: o.label }),
4860
+ total > 0 && /* @__PURE__ */ jsxs(
4861
+ "span",
4862
+ {
4863
+ className: cn(
4864
+ "shrink-0 font-medium",
4865
+ groupFocused ? "text-white" : "text-[color:var(--cmp-listItem-content-disabled)]"
4866
+ ),
4867
+ children: [
4868
+ "(",
4869
+ selectedCount,
4870
+ "/",
4871
+ total,
4872
+ ")"
4873
+ ]
4874
+ }
4875
+ )
4876
+ ]
4877
+ },
4878
+ rowKey
4879
+ );
4880
+ }
4787
4881
  return /* @__PURE__ */ jsx(
4788
4882
  "div",
4789
4883
  {
4790
4884
  className: cn(
4791
4885
  "select-none py-[4px] pr-[12px] text-[12px] leading-[20px] text-[color:var(--cmp-listItem-content-default)]",
4792
- isDepth1 ? "border-t border-solid border-[color:var(--cmp-listItem-depth1-border-color)] bg-[var(--cmp-listItem-depth1-bg-default)] font-bold" : "bg-[var(--cmp-listItem-depth2-middle-bg)] font-medium",
4886
+ groupTone,
4793
4887
  // 하위가 전부 비활성이면(select.config 에서 bubble-up) 어미 그룹도 비활성 처리:
4794
4888
  // 텍스트는 회색(#888), depth1 그룹은 파란 배경 대신 회색(#EEE)으로 (depth2 는 #F6F6F6 유지)
4795
4889
  o.disabled && "cursor-not-allowed text-[color:var(--cmp-listItem-content-disabled)]",
@@ -4798,7 +4892,7 @@ function ItemList({
4798
4892
  style: { paddingLeft: padLeft },
4799
4893
  children: o.label
4800
4894
  },
4801
- o.value
4895
+ rowKey
4802
4896
  );
4803
4897
  }
4804
4898
  const isSelected = selected.includes(o.value);
@@ -4825,25 +4919,11 @@ function ItemList({
4825
4919
  onClick: () => onSelect(o.value),
4826
4920
  children: [
4827
4921
  multi && /* @__PURE__ */ jsx(
4828
- "span",
4922
+ CheckboxBox,
4829
4923
  {
4830
- className: cn(
4831
- "flex h-[16px] w-[16px] flex-shrink-0 items-center justify-center rounded-[2px] border border-solid",
4832
- o.disabled ? (
4833
- // 비활성: 회색 채움(#E1E1E1) + 회색 테두리(#CCCCCC)
4834
- "cursor-not-allowed border-[color:var(--cmp-checkbox-border-disabled)] bg-[var(--cmp-checkbox-bg-disabled)]"
4835
- ) : cn(
4836
- isSelected ? focusedValue === o.value ? "border-[color:var(--cmp-checkbox-checked-border-inverse)] bg-[var(--cmp-checkbox-checked-bg-default)]" : "border-[color:var(--cmp-checkbox-checked-bg-default)] bg-[var(--cmp-checkbox-checked-bg-default)]" : "border-[color:var(--cmp-checkbox-unchecked-border-default)] bg-[var(--cmp-checkbox-unchecked-bg-default)]"
4837
- )
4838
- ),
4839
- children: isSelected && /* @__PURE__ */ jsx(
4840
- SIcon,
4841
- {
4842
- name: "checkboxCheck",
4843
- size: 12,
4844
- color: o.disabled ? "var(--cmp-checkbox-checked-icon-disabled)" : "var(--cmp-checkbox-checked-icon-default)"
4845
- }
4846
- )
4924
+ state: isSelected,
4925
+ disabled: o.disabled,
4926
+ focused: focusedValue === o.value
4847
4927
  }
4848
4928
  ),
4849
4929
  isHtmlLabel(o.label) ? /* @__PURE__ */ jsx(
@@ -4855,11 +4935,38 @@ function ItemList({
4855
4935
  ) : /* @__PURE__ */ jsx("span", { className: "flex-1 truncate", children: o.label })
4856
4936
  ]
4857
4937
  },
4858
- o.value
4938
+ rowKey
4859
4939
  );
4860
4940
  })
4861
4941
  ] });
4862
4942
  }
4943
+ function CheckboxBox({
4944
+ state,
4945
+ disabled = false,
4946
+ focused = false
4947
+ }) {
4948
+ const filled = state === true || state === null;
4949
+ return /* @__PURE__ */ jsx(
4950
+ "span",
4951
+ {
4952
+ className: cn(
4953
+ "flex h-[16px] w-[16px] flex-shrink-0 items-center justify-center rounded-[2px] border border-solid",
4954
+ disabled ? (
4955
+ // 비활성: 회색 채움(#E1E1E1) + 회색 테두리(#CCCCCC)
4956
+ "cursor-not-allowed border-[color:var(--cmp-checkbox-border-disabled)] bg-[var(--cmp-checkbox-bg-disabled)]"
4957
+ ) : filled ? focused ? "border-[color:var(--cmp-checkbox-checked-border-inverse)] bg-[var(--cmp-checkbox-checked-bg-default)]" : "border-[color:var(--cmp-checkbox-checked-bg-default)] bg-[var(--cmp-checkbox-checked-bg-default)]" : "border-[color:var(--cmp-checkbox-unchecked-border-default)] bg-[var(--cmp-checkbox-unchecked-bg-default)]"
4958
+ ),
4959
+ children: filled && /* @__PURE__ */ jsx(
4960
+ SIcon,
4961
+ {
4962
+ name: state === null ? "checkboxIndeterminate" : "checkboxCheck",
4963
+ size: 12,
4964
+ color: disabled ? "var(--cmp-checkbox-checked-icon-disabled)" : "var(--cmp-checkbox-checked-icon-default)"
4965
+ }
4966
+ )
4967
+ }
4968
+ );
4969
+ }
4863
4970
  function SRadio({
4864
4971
  val,
4865
4972
  value,
@@ -5047,7 +5154,7 @@ function SCheckbox({
5047
5154
  }
5048
5155
  ),
5049
5156
  label && // 고스트(500) 텍스트로 너비를 고정 → 두께 400↔500 전환 시 레이아웃이 밀리지 않음
5050
- /* @__PURE__ */ jsxs("span", { className: "grid text-[12px] leading-[20px]", style: { color: labelColor }, children: [
5157
+ /* @__PURE__ */ jsxs("span", { className: "grid text-nowrap text-[12px] leading-[20px]", style: { color: labelColor }, children: [
5051
5158
  /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: "invisible font-medium", style: { gridArea: "1 / 1" }, children: label }),
5052
5159
  /* @__PURE__ */ jsx("span", { style: { gridArea: "1 / 1", fontWeight: isChecked ? 500 : 400 }, children: label })
5053
5160
  ] })