sellmate-design-system-react 0.8.3 → 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 (68) 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/SChipInput/SChipInput.cjs +78 -2
  6. package/dist/components/SChipInput/SChipInput.cjs.map +1 -1
  7. package/dist/components/SChipInput/SChipInput.js +78 -2
  8. package/dist/components/SChipInput/SChipInput.js.map +1 -1
  9. package/dist/components/SDatePicker/SDatePicker.cjs +78 -2
  10. package/dist/components/SDatePicker/SDatePicker.cjs.map +1 -1
  11. package/dist/components/SDatePicker/SDatePicker.js +78 -2
  12. package/dist/components/SDatePicker/SDatePicker.js.map +1 -1
  13. package/dist/components/SDateRangePicker/SDateRangePicker.cjs +78 -2
  14. package/dist/components/SDateRangePicker/SDateRangePicker.cjs.map +1 -1
  15. package/dist/components/SDateRangePicker/SDateRangePicker.js +78 -2
  16. package/dist/components/SDateRangePicker/SDateRangePicker.js.map +1 -1
  17. package/dist/components/SField/SField.cjs +78 -2
  18. package/dist/components/SField/SField.cjs.map +1 -1
  19. package/dist/components/SField/SField.js +78 -2
  20. package/dist/components/SField/SField.js.map +1 -1
  21. package/dist/components/SFilePicker/SFilePicker.cjs +78 -2
  22. package/dist/components/SFilePicker/SFilePicker.cjs.map +1 -1
  23. package/dist/components/SFilePicker/SFilePicker.js +78 -2
  24. package/dist/components/SFilePicker/SFilePicker.js.map +1 -1
  25. package/dist/components/SInput/SInput.cjs +78 -2
  26. package/dist/components/SInput/SInput.cjs.map +1 -1
  27. package/dist/components/SInput/SInput.js +78 -2
  28. package/dist/components/SInput/SInput.js.map +1 -1
  29. package/dist/components/SKeyValueTable/SKeyValueTable.cjs +224 -118
  30. package/dist/components/SKeyValueTable/SKeyValueTable.cjs.map +1 -1
  31. package/dist/components/SKeyValueTable/SKeyValueTable.js +224 -118
  32. package/dist/components/SKeyValueTable/SKeyValueTable.js.map +1 -1
  33. package/dist/components/SNumberInput/SNumberInput.cjs +78 -2
  34. package/dist/components/SNumberInput/SNumberInput.cjs.map +1 -1
  35. package/dist/components/SNumberInput/SNumberInput.js +78 -2
  36. package/dist/components/SNumberInput/SNumberInput.js.map +1 -1
  37. package/dist/components/SSelect/SSelect.cjs +155 -49
  38. package/dist/components/SSelect/SSelect.cjs.map +1 -1
  39. package/dist/components/SSelect/SSelect.js +155 -49
  40. package/dist/components/SSelect/SSelect.js.map +1 -1
  41. package/dist/components/SSelect/select.config.d.ts +7 -0
  42. package/dist/components/STable/STable.cjs +155 -49
  43. package/dist/components/STable/STable.cjs.map +1 -1
  44. package/dist/components/STable/STable.js +155 -49
  45. package/dist/components/STable/STable.js.map +1 -1
  46. package/dist/components/STextarea/STextarea.cjs +78 -2
  47. package/dist/components/STextarea/STextarea.cjs.map +1 -1
  48. package/dist/components/STextarea/STextarea.js +78 -2
  49. package/dist/components/STextarea/STextarea.js.map +1 -1
  50. package/dist/components/STimePicker/STimePicker.cjs +78 -2
  51. package/dist/components/STimePicker/STimePicker.cjs.map +1 -1
  52. package/dist/components/STimePicker/STimePicker.js +78 -2
  53. package/dist/components/STimePicker/STimePicker.js.map +1 -1
  54. package/dist/components/STimeRangePicker/STimeRangePicker.cjs +78 -2
  55. package/dist/components/STimeRangePicker/STimeRangePicker.cjs.map +1 -1
  56. package/dist/components/STimeRangePicker/STimeRangePicker.js +78 -2
  57. package/dist/components/STimeRangePicker/STimeRangePicker.js.map +1 -1
  58. package/dist/components/STooltip/STooltip.cjs +78 -2
  59. package/dist/components/STooltip/STooltip.cjs.map +1 -1
  60. package/dist/components/STooltip/STooltip.d.ts +1 -1
  61. package/dist/components/STooltip/STooltip.js +78 -2
  62. package/dist/components/STooltip/STooltip.js.map +1 -1
  63. package/dist/index.cjs +155 -49
  64. package/dist/index.cjs.map +1 -1
  65. package/dist/index.js +155 -49
  66. package/dist/index.js.map +1 -1
  67. package/dist/styles.css +4 -8
  68. package/package.json +1 -1
@@ -2907,6 +2907,75 @@ function useInsideModal() {
2907
2907
  return react.useContext(InsideModalContext);
2908
2908
  }
2909
2909
 
2910
+ // src/lib/sanitize-inline-html.ts
2911
+ var ALLOWED_INLINE_TAGS = /* @__PURE__ */ new Set(["A", "B", "STRONG", "I", "EM", "BR", "SPAN"]);
2912
+ var ALLOWED_ATTRS_BY_TAG = {
2913
+ A: /* @__PURE__ */ new Set(["href", "target", "rel", "class"])
2914
+ };
2915
+ var COMMON_ALLOWED_ATTRS = /* @__PURE__ */ new Set(["class"]);
2916
+ var SAFE_HREF_RE = /^(https?:|mailto:|tel:|\/|#|\.)/i;
2917
+ var DROP_CONTENT_TAGS = /* @__PURE__ */ new Set([
2918
+ "SCRIPT",
2919
+ "STYLE",
2920
+ "IFRAME",
2921
+ "OBJECT",
2922
+ "EMBED",
2923
+ "META",
2924
+ "LINK",
2925
+ "BASE",
2926
+ "NOSCRIPT",
2927
+ // SVG/MATH 는 foreign-content 파싱 모드로 전환되어 재직렬화 시 다른 트리로
2928
+ // 재파싱될 수 있다(mXSS 벡터). 인라인 라벨에 필요 없으므로 통째로 제거한다.
2929
+ "SVG",
2930
+ "MATH",
2931
+ "TEMPLATE"
2932
+ ]);
2933
+ var escapeHtml = (value) => value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
2934
+ var sanitizeNode = (node) => {
2935
+ if (node.nodeType === Node.COMMENT_NODE) {
2936
+ node.remove();
2937
+ return;
2938
+ }
2939
+ if (node.nodeType !== Node.ELEMENT_NODE) return;
2940
+ const element = node;
2941
+ const tagName = element.tagName;
2942
+ if (DROP_CONTENT_TAGS.has(tagName)) {
2943
+ element.remove();
2944
+ return;
2945
+ }
2946
+ Array.from(element.childNodes).forEach((child) => sanitizeNode(child));
2947
+ if (ALLOWED_INLINE_TAGS.has(tagName)) {
2948
+ if (element.hasAttribute("className")) {
2949
+ const val = element.getAttribute("className");
2950
+ element.removeAttribute("className");
2951
+ if (!element.hasAttribute("class")) element.setAttribute("class", val);
2952
+ }
2953
+ const allowedAttrs = ALLOWED_ATTRS_BY_TAG[tagName] ?? COMMON_ALLOWED_ATTRS;
2954
+ Array.from(element.attributes).forEach((attr) => {
2955
+ if (!allowedAttrs.has(attr.name)) element.removeAttribute(attr.name);
2956
+ });
2957
+ if (tagName === "A") {
2958
+ const href = element.getAttribute("href");
2959
+ if (href !== null && href !== "" && !SAFE_HREF_RE.test(href)) element.removeAttribute("href");
2960
+ if (element.hasAttribute("target")) {
2961
+ element.setAttribute("rel", "noopener noreferrer");
2962
+ }
2963
+ }
2964
+ return;
2965
+ }
2966
+ const parent = element.parentNode;
2967
+ if (parent == null) return;
2968
+ while (element.firstChild) parent.insertBefore(element.firstChild, element);
2969
+ parent.removeChild(element);
2970
+ };
2971
+ var sanitizeInlineHtml = (value) => {
2972
+ if (typeof document === "undefined") return escapeHtml(value);
2973
+ const template = document.createElement("template");
2974
+ template.innerHTML = value;
2975
+ Array.from(template.content.childNodes).forEach((child) => sanitizeNode(child));
2976
+ return template.innerHTML;
2977
+ };
2978
+
2910
2979
  // src/components/STag/tag.config.ts
2911
2980
  var TAG_SIZE_CONFIG = {
2912
2981
  xs: {
@@ -3105,7 +3174,14 @@ function useBody(content, message = []) {
3105
3174
  if (content != null) return content;
3106
3175
  if (message.length === 0) return null;
3107
3176
  const singleLine = message.length === 1;
3108
- return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: message.map((line, i) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: singleLine ? "leading-[24px]" : void 0, children: line }, i)) });
3177
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: message.map((line, i) => /* @__PURE__ */ jsxRuntime.jsx(
3178
+ "div",
3179
+ {
3180
+ className: singleLine ? "leading-[24px]" : void 0,
3181
+ dangerouslySetInnerHTML: { __html: sanitizeInlineHtml(line) }
3182
+ },
3183
+ i
3184
+ )) });
3109
3185
  }
3110
3186
  var STooltip = react.forwardRef(function STooltip2({
3111
3187
  content,
@@ -3163,7 +3239,7 @@ var STooltip = react.forwardRef(function STooltip2({
3163
3239
  if (disabled || body == null) return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: triggerNode });
3164
3240
  const isHover = trigger === "hover";
3165
3241
  const contentClass = cn(
3166
- "z-50 max-w-xs rounded-[6px] px-[16px] py-[12px] text-[12px] leading-[20px]",
3242
+ "z-50 w-fit whitespace-nowrap rounded-[6px] px-[16px] py-[12px] text-[12px] leading-[20px]",
3167
3243
  FLOATING_SLIDE_ANIM,
3168
3244
  className
3169
3245
  );
@@ -4187,75 +4263,6 @@ var FIREFOX_SCROLLBAR_CLASS = "supports-[not_selector(::-webkit-scrollbar)]:[scr
4187
4263
  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`;
4188
4264
  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)]";
4189
4265
  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]";
4190
-
4191
- // src/lib/sanitize-inline-html.ts
4192
- var ALLOWED_INLINE_TAGS = /* @__PURE__ */ new Set(["A", "B", "STRONG", "I", "EM", "BR", "SPAN"]);
4193
- var ALLOWED_ATTRS_BY_TAG = {
4194
- A: /* @__PURE__ */ new Set(["href", "target", "rel", "class"])
4195
- };
4196
- var COMMON_ALLOWED_ATTRS = /* @__PURE__ */ new Set(["class"]);
4197
- var SAFE_HREF_RE = /^(https?:|mailto:|tel:|\/|#|\.)/i;
4198
- var DROP_CONTENT_TAGS = /* @__PURE__ */ new Set([
4199
- "SCRIPT",
4200
- "STYLE",
4201
- "IFRAME",
4202
- "OBJECT",
4203
- "EMBED",
4204
- "META",
4205
- "LINK",
4206
- "BASE",
4207
- "NOSCRIPT",
4208
- // SVG/MATH 는 foreign-content 파싱 모드로 전환되어 재직렬화 시 다른 트리로
4209
- // 재파싱될 수 있다(mXSS 벡터). 인라인 라벨에 필요 없으므로 통째로 제거한다.
4210
- "SVG",
4211
- "MATH",
4212
- "TEMPLATE"
4213
- ]);
4214
- var escapeHtml = (value) => value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
4215
- var sanitizeNode = (node) => {
4216
- if (node.nodeType === Node.COMMENT_NODE) {
4217
- node.remove();
4218
- return;
4219
- }
4220
- if (node.nodeType !== Node.ELEMENT_NODE) return;
4221
- const element = node;
4222
- const tagName = element.tagName;
4223
- if (DROP_CONTENT_TAGS.has(tagName)) {
4224
- element.remove();
4225
- return;
4226
- }
4227
- Array.from(element.childNodes).forEach((child) => sanitizeNode(child));
4228
- if (ALLOWED_INLINE_TAGS.has(tagName)) {
4229
- if (element.hasAttribute("className")) {
4230
- const val = element.getAttribute("className");
4231
- element.removeAttribute("className");
4232
- if (!element.hasAttribute("class")) element.setAttribute("class", val);
4233
- }
4234
- const allowedAttrs = ALLOWED_ATTRS_BY_TAG[tagName] ?? COMMON_ALLOWED_ATTRS;
4235
- Array.from(element.attributes).forEach((attr) => {
4236
- if (!allowedAttrs.has(attr.name)) element.removeAttribute(attr.name);
4237
- });
4238
- if (tagName === "A") {
4239
- const href = element.getAttribute("href");
4240
- if (href !== null && href !== "" && !SAFE_HREF_RE.test(href)) element.removeAttribute("href");
4241
- if (element.hasAttribute("target")) {
4242
- element.setAttribute("rel", "noopener noreferrer");
4243
- }
4244
- }
4245
- return;
4246
- }
4247
- const parent = element.parentNode;
4248
- if (parent == null) return;
4249
- while (element.firstChild) parent.insertBefore(element.firstChild, element);
4250
- parent.removeChild(element);
4251
- };
4252
- var sanitizeInlineHtml = (value) => {
4253
- if (typeof document === "undefined") return escapeHtml(value);
4254
- const template = document.createElement("template");
4255
- template.innerHTML = value;
4256
- Array.from(template.content.childNodes).forEach((child) => sanitizeNode(child));
4257
- return template.innerHTML;
4258
- };
4259
4266
  function SPortal({
4260
4267
  open,
4261
4268
  onClose,
@@ -4362,6 +4369,16 @@ function flattenOptions(options, depth = 1) {
4362
4369
  return out;
4363
4370
  }
4364
4371
  var selectableOptions = (flat) => flat.filter((o) => !o.isGroup && !o.disabled);
4372
+ function collectGroupLeafValues(flat, groupIndex) {
4373
+ const group = flat[groupIndex];
4374
+ if (group == null || !group.isGroup) return [];
4375
+ const values = [];
4376
+ for (let i = groupIndex + 1; i < flat.length; i++) {
4377
+ if (flat[i].depth <= group.depth) break;
4378
+ if (!flat[i].isGroup && !flat[i].disabled) values.push(flat[i].value);
4379
+ }
4380
+ return values;
4381
+ }
4365
4382
  var isHtmlLabel = (label) => /<[a-z][\s\S]*>/i.test(label);
4366
4383
  var extractText = (html) => html.replace(/<[^>]*>/g, "");
4367
4384
  var SEARCH_THRESHOLD = 11;
@@ -4487,16 +4504,31 @@ var SSelect = react.forwardRef(function SSelect2({
4487
4504
  const allValues = selectable.map((o) => o.value);
4488
4505
  const allSelected = allValues.length > 0 && allValues.every((v) => selected.includes(v));
4489
4506
  const hasItems = filtered.some((o) => !o.isGroup);
4507
+ const groupLeafValues = react.useMemo(() => {
4508
+ const map = /* @__PURE__ */ new Map();
4509
+ filtered.forEach((o, i) => {
4510
+ if (o.isGroup) map.set(i, collectGroupLeafValues(filtered, i));
4511
+ });
4512
+ return map;
4513
+ }, [filtered]);
4490
4514
  const navigableItems = react.useMemo(() => {
4491
4515
  const items = [];
4492
4516
  if (multi && useSelectAll && allValues.length > 0) {
4493
- items.push({ value: "__select_all__", isSelectAll: true });
4517
+ items.push({ key: "__select_all__", isSelectAll: true });
4494
4518
  }
4495
- filtered.forEach((o) => {
4496
- if (!o.isGroup && !o.disabled) items.push({ value: o.value });
4519
+ filtered.forEach((o, i) => {
4520
+ if (o.disabled) return;
4521
+ if (o.isGroup) {
4522
+ if (!multi) return;
4523
+ const leafValues = groupLeafValues.get(i) ?? [];
4524
+ if (leafValues.length === 0) return;
4525
+ items.push({ key: `__group_${i}__`, isGroup: true, leafValues });
4526
+ } else {
4527
+ items.push({ key: o.value, value: o.value });
4528
+ }
4497
4529
  });
4498
4530
  return items;
4499
- }, [allValues.length, filtered, multi, useSelectAll]);
4531
+ }, [allValues.length, filtered, groupLeafValues, multi, useSelectAll]);
4500
4532
  const triggerText = (() => {
4501
4533
  if (selected.length === 0) return placeholder;
4502
4534
  if (multi) {
@@ -4518,6 +4550,17 @@ var SSelect = react.forwardRef(function SSelect2({
4518
4550
  const handleSelectAll = () => {
4519
4551
  emit(allSelected ? [] : allValues);
4520
4552
  };
4553
+ const handleToggleGroup = (leafValues) => {
4554
+ if (leafValues.length === 0) return;
4555
+ const selectedSet = new Set(selected);
4556
+ const allSelectedInGroup = leafValues.every((v) => selectedSet.has(v));
4557
+ if (allSelectedInGroup) {
4558
+ const remove = new Set(leafValues);
4559
+ emit(selected.filter((v) => !remove.has(v)));
4560
+ } else {
4561
+ emit([...selected, ...leafValues.filter((v) => !selectedSet.has(v))]);
4562
+ }
4563
+ };
4521
4564
  react.useEffect(() => {
4522
4565
  if (!open) {
4523
4566
  setFocusedIndex(-1);
@@ -4558,6 +4601,10 @@ var SSelect = react.forwardRef(function SSelect2({
4558
4601
  handleSelectAll();
4559
4602
  return;
4560
4603
  }
4604
+ if (focused.isGroup) {
4605
+ handleToggleGroup(focused.leafValues ?? []);
4606
+ return;
4607
+ }
4561
4608
  handleSelect(focused.value);
4562
4609
  };
4563
4610
  const handleKeyDown = (event) => {
@@ -4721,20 +4768,20 @@ var SSelect = react.forwardRef(function SSelect2({
4721
4768
  items: filtered,
4722
4769
  multi,
4723
4770
  selected,
4724
- focusedValue: navigableItems[focusedIndex]?.isSelectAll ? "__select_all__" : navigableItems[focusedIndex]?.value,
4771
+ groupLeafValues,
4772
+ focusedValue: navigableItems[focusedIndex]?.key,
4725
4773
  showSelectAll: multi && useSelectAll && allValues.length > 0,
4726
4774
  selectAllLabel: allSelectedLabel,
4727
4775
  selectAllSelected: allSelected,
4728
4776
  onSelectAll: handleSelectAll,
4729
4777
  onSelect: handleSelect,
4730
- onHoverValue: (value2) => {
4731
- if (value2 == null) {
4778
+ onToggleGroup: handleToggleGroup,
4779
+ onHoverValue: (key) => {
4780
+ if (key == null) {
4732
4781
  setFocusedIndex(-1);
4733
4782
  return;
4734
4783
  }
4735
- const index = navigableItems.findIndex(
4736
- (item) => item.isSelectAll ? value2 === "__select_all__" : item.value === value2
4737
- );
4784
+ const index = navigableItems.findIndex((item) => item.key === key);
4738
4785
  if (index >= 0) setFocusedIndex(index);
4739
4786
  }
4740
4787
  }
@@ -4753,20 +4800,23 @@ function ItemList({
4753
4800
  items,
4754
4801
  multi,
4755
4802
  selected,
4803
+ groupLeafValues,
4756
4804
  focusedValue,
4757
4805
  showSelectAll,
4758
4806
  selectAllLabel,
4759
4807
  selectAllSelected,
4760
4808
  onSelectAll,
4761
4809
  onSelect,
4810
+ onToggleGroup,
4762
4811
  onHoverValue
4763
4812
  }) {
4813
+ const selectedSet = react.useMemo(() => new Set(selected), [selected]);
4764
4814
  const itemBase = cn(
4765
4815
  "group flex w-full cursor-pointer select-none items-center gap-[8px] border-none bg-transparent text-left text-[12px] leading-[20px] outline-none",
4766
4816
  "text-[color:var(--cmp-listItem-content-default)]",
4767
4817
  "disabled:cursor-not-allowed disabled:text-[color:var(--cmp-listItem-content-disabled)]"
4768
4818
  );
4769
- 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)]";
4819
+ 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)]";
4770
4820
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { role: "listbox", "aria-multiselectable": multi || void 0, children: [
4771
4821
  showSelectAll && /* @__PURE__ */ jsxRuntime.jsxs(
4772
4822
  "button",
@@ -4783,23 +4833,7 @@ function ItemList({
4783
4833
  onMouseLeave: () => onHoverValue(null),
4784
4834
  onClick: onSelectAll,
4785
4835
  children: [
4786
- /* @__PURE__ */ jsxRuntime.jsx(
4787
- "span",
4788
- {
4789
- className: cn(
4790
- "flex h-[16px] w-[16px] flex-shrink-0 items-center justify-center rounded-[2px] border border-solid",
4791
- 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)]"
4792
- ),
4793
- children: selectAllSelected && /* @__PURE__ */ jsxRuntime.jsx(
4794
- SIcon,
4795
- {
4796
- name: "checkboxCheck",
4797
- size: 12,
4798
- color: "var(--cmp-checkbox-checked-icon-default)"
4799
- }
4800
- )
4801
- }
4802
- ),
4836
+ /* @__PURE__ */ jsxRuntime.jsx(CheckboxBox, { state: selectAllSelected, focused: focusedValue === "__select_all__" }),
4803
4837
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1 truncate", children: selectAllLabel })
4804
4838
  ]
4805
4839
  }
@@ -4809,12 +4843,71 @@ function ItemList({
4809
4843
  const rowKey = `${o.value}-${idx}`;
4810
4844
  if (o.isGroup) {
4811
4845
  const isDepth1 = o.depth === 1;
4846
+ 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";
4847
+ if (multi) {
4848
+ const leafValues = groupLeafValues.get(idx) ?? [];
4849
+ const total = leafValues.length;
4850
+ const selectedCount = leafValues.reduce((n, v) => selectedSet.has(v) ? n + 1 : n, 0);
4851
+ const groupState = total === 0 || selectedCount === 0 ? false : selectedCount === total ? true : null;
4852
+ const groupDisabled = o.disabled || total === 0;
4853
+ const groupFocused = focusedValue === `__group_${idx}__`;
4854
+ return /* @__PURE__ */ jsxRuntime.jsxs(
4855
+ "button",
4856
+ {
4857
+ type: "button",
4858
+ role: "option",
4859
+ "aria-selected": groupState === true,
4860
+ disabled: groupDisabled,
4861
+ "data-select-focused": groupFocused ? "true" : void 0,
4862
+ "data-select-active": groupFocused ? "true" : void 0,
4863
+ className: cn(
4864
+ "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)]",
4865
+ groupTone,
4866
+ groupDisabled ? "cursor-not-allowed text-[color:var(--cmp-listItem-content-disabled)]" : "cursor-pointer",
4867
+ groupDisabled && isDepth1 && "bg-[var(--cmp-listItem-depth1-bg-disabled)]",
4868
+ // 포커스 하이라이트는 depth 배경보다 뒤에 둬 tailwind-merge 로 이기게 한다.
4869
+ groupFocused && !groupDisabled && "bg-[var(--cmp-listItem-bg-hover)] text-[color:var(--cmp-listItem-content-hover)]"
4870
+ ),
4871
+ style: { paddingLeft: padLeft },
4872
+ onMouseEnter: () => onHoverValue(`__group_${idx}__`),
4873
+ onMouseLeave: () => onHoverValue(null),
4874
+ onClick: () => onToggleGroup(leafValues),
4875
+ children: [
4876
+ /* @__PURE__ */ jsxRuntime.jsx(CheckboxBox, { state: groupState, disabled: groupDisabled, focused: groupFocused }),
4877
+ isHtmlLabel(o.label) ? /* @__PURE__ */ jsxRuntime.jsx(
4878
+ "span",
4879
+ {
4880
+ className: "min-w-0 truncate",
4881
+ dangerouslySetInnerHTML: { __html: sanitizeInlineHtml(o.label) }
4882
+ }
4883
+ ) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 truncate", children: o.label }),
4884
+ total > 0 && /* @__PURE__ */ jsxRuntime.jsxs(
4885
+ "span",
4886
+ {
4887
+ className: cn(
4888
+ "shrink-0 font-medium",
4889
+ groupFocused ? "text-white" : "text-[color:var(--cmp-listItem-content-disabled)]"
4890
+ ),
4891
+ children: [
4892
+ "(",
4893
+ selectedCount,
4894
+ "/",
4895
+ total,
4896
+ ")"
4897
+ ]
4898
+ }
4899
+ )
4900
+ ]
4901
+ },
4902
+ rowKey
4903
+ );
4904
+ }
4812
4905
  return /* @__PURE__ */ jsxRuntime.jsx(
4813
4906
  "div",
4814
4907
  {
4815
4908
  className: cn(
4816
4909
  "select-none py-[4px] pr-[12px] text-[12px] leading-[20px] text-[color:var(--cmp-listItem-content-default)]",
4817
- 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",
4910
+ groupTone,
4818
4911
  // 하위가 전부 비활성이면(select.config 에서 bubble-up) 어미 그룹도 비활성 처리:
4819
4912
  // 텍스트는 회색(#888), depth1 그룹은 파란 배경 대신 회색(#EEE)으로 (depth2 는 #F6F6F6 유지)
4820
4913
  o.disabled && "cursor-not-allowed text-[color:var(--cmp-listItem-content-disabled)]",
@@ -4850,25 +4943,11 @@ function ItemList({
4850
4943
  onClick: () => onSelect(o.value),
4851
4944
  children: [
4852
4945
  multi && /* @__PURE__ */ jsxRuntime.jsx(
4853
- "span",
4946
+ CheckboxBox,
4854
4947
  {
4855
- className: cn(
4856
- "flex h-[16px] w-[16px] flex-shrink-0 items-center justify-center rounded-[2px] border border-solid",
4857
- o.disabled ? (
4858
- // 비활성: 회색 채움(#E1E1E1) + 회색 테두리(#CCCCCC)
4859
- "cursor-not-allowed border-[color:var(--cmp-checkbox-border-disabled)] bg-[var(--cmp-checkbox-bg-disabled)]"
4860
- ) : cn(
4861
- 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)]"
4862
- )
4863
- ),
4864
- children: isSelected && /* @__PURE__ */ jsxRuntime.jsx(
4865
- SIcon,
4866
- {
4867
- name: "checkboxCheck",
4868
- size: 12,
4869
- color: o.disabled ? "var(--cmp-checkbox-checked-icon-disabled)" : "var(--cmp-checkbox-checked-icon-default)"
4870
- }
4871
- )
4948
+ state: isSelected,
4949
+ disabled: o.disabled,
4950
+ focused: focusedValue === o.value
4872
4951
  }
4873
4952
  ),
4874
4953
  isHtmlLabel(o.label) ? /* @__PURE__ */ jsxRuntime.jsx(
@@ -4885,6 +4964,33 @@ function ItemList({
4885
4964
  })
4886
4965
  ] });
4887
4966
  }
4967
+ function CheckboxBox({
4968
+ state,
4969
+ disabled = false,
4970
+ focused = false
4971
+ }) {
4972
+ const filled = state === true || state === null;
4973
+ return /* @__PURE__ */ jsxRuntime.jsx(
4974
+ "span",
4975
+ {
4976
+ className: cn(
4977
+ "flex h-[16px] w-[16px] flex-shrink-0 items-center justify-center rounded-[2px] border border-solid",
4978
+ disabled ? (
4979
+ // 비활성: 회색 채움(#E1E1E1) + 회색 테두리(#CCCCCC)
4980
+ "cursor-not-allowed border-[color:var(--cmp-checkbox-border-disabled)] bg-[var(--cmp-checkbox-bg-disabled)]"
4981
+ ) : 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)]"
4982
+ ),
4983
+ children: filled && /* @__PURE__ */ jsxRuntime.jsx(
4984
+ SIcon,
4985
+ {
4986
+ name: state === null ? "checkboxIndeterminate" : "checkboxCheck",
4987
+ size: 12,
4988
+ color: disabled ? "var(--cmp-checkbox-checked-icon-disabled)" : "var(--cmp-checkbox-checked-icon-default)"
4989
+ }
4990
+ )
4991
+ }
4992
+ );
4993
+ }
4888
4994
  function SRadio({
4889
4995
  val,
4890
4996
  value,