sellmate-design-system-react 0.1.10 → 0.1.11

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 (129) hide show
  1. package/dist/components/SActionModal/SActionModal.cjs +3 -1
  2. package/dist/components/SActionModal/SActionModal.cjs.map +1 -1
  3. package/dist/components/SActionModal/SActionModal.js +3 -1
  4. package/dist/components/SActionModal/SActionModal.js.map +1 -1
  5. package/dist/components/SBarcodeInput/SBarcodeInput.cjs +15 -2
  6. package/dist/components/SBarcodeInput/SBarcodeInput.cjs.map +1 -1
  7. package/dist/components/SBarcodeInput/SBarcodeInput.d.ts +3 -1
  8. package/dist/components/SBarcodeInput/SBarcodeInput.js +15 -2
  9. package/dist/components/SBarcodeInput/SBarcodeInput.js.map +1 -1
  10. package/dist/components/SCalendar/SCalendar.cjs +2 -0
  11. package/dist/components/SCalendar/SCalendar.cjs.map +1 -1
  12. package/dist/components/SCalendar/SCalendar.js +2 -0
  13. package/dist/components/SCalendar/SCalendar.js.map +1 -1
  14. package/dist/components/SChip/SChip.cjs +2 -0
  15. package/dist/components/SChip/SChip.cjs.map +1 -1
  16. package/dist/components/SChip/SChip.js +2 -0
  17. package/dist/components/SChip/SChip.js.map +1 -1
  18. package/dist/components/SChipInput/SChipInput.cjs +21 -3
  19. package/dist/components/SChipInput/SChipInput.cjs.map +1 -1
  20. package/dist/components/SChipInput/SChipInput.d.ts +5 -0
  21. package/dist/components/SChipInput/SChipInput.js +21 -3
  22. package/dist/components/SChipInput/SChipInput.js.map +1 -1
  23. package/dist/components/SChipInput/index.d.ts +1 -1
  24. package/dist/components/SConfirmModal/SConfirmModal.cjs +13 -10
  25. package/dist/components/SConfirmModal/SConfirmModal.cjs.map +1 -1
  26. package/dist/components/SConfirmModal/SConfirmModal.d.ts +7 -4
  27. package/dist/components/SConfirmModal/SConfirmModal.js +13 -10
  28. package/dist/components/SConfirmModal/SConfirmModal.js.map +1 -1
  29. package/dist/components/SDatePicker/SDatePicker.cjs +15 -2
  30. package/dist/components/SDatePicker/SDatePicker.cjs.map +1 -1
  31. package/dist/components/SDatePicker/SDatePicker.d.ts +3 -1
  32. package/dist/components/SDatePicker/SDatePicker.js +15 -2
  33. package/dist/components/SDatePicker/SDatePicker.js.map +1 -1
  34. package/dist/components/SDateRangePicker/SDateRangePicker.cjs +15 -2
  35. package/dist/components/SDateRangePicker/SDateRangePicker.cjs.map +1 -1
  36. package/dist/components/SDateRangePicker/SDateRangePicker.d.ts +3 -1
  37. package/dist/components/SDateRangePicker/SDateRangePicker.js +15 -2
  38. package/dist/components/SDateRangePicker/SDateRangePicker.js.map +1 -1
  39. package/dist/components/SDropdownButton/SDropdownButton.cjs +2 -0
  40. package/dist/components/SDropdownButton/SDropdownButton.cjs.map +1 -1
  41. package/dist/components/SDropdownButton/SDropdownButton.js +2 -0
  42. package/dist/components/SDropdownButton/SDropdownButton.js.map +1 -1
  43. package/dist/components/SField/SField.cjs +13 -2
  44. package/dist/components/SField/SField.cjs.map +1 -1
  45. package/dist/components/SField/SField.d.ts +4 -1
  46. package/dist/components/SField/SField.js +13 -2
  47. package/dist/components/SField/SField.js.map +1 -1
  48. package/dist/components/SFilePicker/SFilePicker.cjs +16 -3
  49. package/dist/components/SFilePicker/SFilePicker.cjs.map +1 -1
  50. package/dist/components/SFilePicker/SFilePicker.d.ts +3 -1
  51. package/dist/components/SFilePicker/SFilePicker.js +16 -3
  52. package/dist/components/SFilePicker/SFilePicker.js.map +1 -1
  53. package/dist/components/SGhostButton/SGhostButton.cjs +2 -0
  54. package/dist/components/SGhostButton/SGhostButton.cjs.map +1 -1
  55. package/dist/components/SGhostButton/SGhostButton.js +2 -0
  56. package/dist/components/SGhostButton/SGhostButton.js.map +1 -1
  57. package/dist/components/SGuide/SGuide.cjs +2 -0
  58. package/dist/components/SGuide/SGuide.cjs.map +1 -1
  59. package/dist/components/SGuide/SGuide.js +2 -0
  60. package/dist/components/SGuide/SGuide.js.map +1 -1
  61. package/dist/components/SInput/SInput.cjs +15 -2
  62. package/dist/components/SInput/SInput.cjs.map +1 -1
  63. package/dist/components/SInput/SInput.d.ts +4 -1
  64. package/dist/components/SInput/SInput.js +15 -2
  65. package/dist/components/SInput/SInput.js.map +1 -1
  66. package/dist/components/SKeyValueTable/SKeyValueTable.cjs +496 -206
  67. package/dist/components/SKeyValueTable/SKeyValueTable.cjs.map +1 -1
  68. package/dist/components/SKeyValueTable/SKeyValueTable.d.ts +27 -6
  69. package/dist/components/SKeyValueTable/SKeyValueTable.js +495 -205
  70. package/dist/components/SKeyValueTable/SKeyValueTable.js.map +1 -1
  71. package/dist/components/SLoadingModal/SLoadingModal.cjs +3 -1
  72. package/dist/components/SLoadingModal/SLoadingModal.cjs.map +1 -1
  73. package/dist/components/SLoadingModal/SLoadingModal.js +3 -1
  74. package/dist/components/SLoadingModal/SLoadingModal.js.map +1 -1
  75. package/dist/components/SModalContainer/SModalContainer.cjs +3 -1
  76. package/dist/components/SModalContainer/SModalContainer.cjs.map +1 -1
  77. package/dist/components/SModalContainer/SModalContainer.js +3 -1
  78. package/dist/components/SModalContainer/SModalContainer.js.map +1 -1
  79. package/dist/components/SNumberInput/SNumberInput.cjs +15 -2
  80. package/dist/components/SNumberInput/SNumberInput.cjs.map +1 -1
  81. package/dist/components/SNumberInput/SNumberInput.d.ts +2 -0
  82. package/dist/components/SNumberInput/SNumberInput.js +15 -2
  83. package/dist/components/SNumberInput/SNumberInput.js.map +1 -1
  84. package/dist/components/SPopover/SPopover.cjs +2 -0
  85. package/dist/components/SPopover/SPopover.cjs.map +1 -1
  86. package/dist/components/SPopover/SPopover.js +2 -0
  87. package/dist/components/SPopover/SPopover.js.map +1 -1
  88. package/dist/components/SPortal/SPortal.cjs +2 -0
  89. package/dist/components/SPortal/SPortal.cjs.map +1 -1
  90. package/dist/components/SPortal/SPortal.js +2 -0
  91. package/dist/components/SPortal/SPortal.js.map +1 -1
  92. package/dist/components/SSelect/SSelect.cjs +530 -262
  93. package/dist/components/SSelect/SSelect.cjs.map +1 -1
  94. package/dist/components/SSelect/SSelect.d.ts +4 -2
  95. package/dist/components/SSelect/SSelect.js +531 -262
  96. package/dist/components/SSelect/SSelect.js.map +1 -1
  97. package/dist/components/STable/STable.cjs +529 -261
  98. package/dist/components/STable/STable.cjs.map +1 -1
  99. package/dist/components/STable/STable.js +530 -261
  100. package/dist/components/STable/STable.js.map +1 -1
  101. package/dist/components/STextLink/STextLink.cjs +2 -0
  102. package/dist/components/STextLink/STextLink.cjs.map +1 -1
  103. package/dist/components/STextLink/STextLink.js +2 -0
  104. package/dist/components/STextLink/STextLink.js.map +1 -1
  105. package/dist/components/STextarea/STextarea.cjs +15 -2
  106. package/dist/components/STextarea/STextarea.cjs.map +1 -1
  107. package/dist/components/STextarea/STextarea.d.ts +2 -0
  108. package/dist/components/STextarea/STextarea.js +15 -2
  109. package/dist/components/STextarea/STextarea.js.map +1 -1
  110. package/dist/components/SToast/SToast.cjs +2 -0
  111. package/dist/components/SToast/SToast.cjs.map +1 -1
  112. package/dist/components/SToast/SToast.js +2 -0
  113. package/dist/components/SToast/SToast.js.map +1 -1
  114. package/dist/components/SToast/SToastContainer.cjs +2 -0
  115. package/dist/components/SToast/SToastContainer.cjs.map +1 -1
  116. package/dist/components/SToast/SToastContainer.js +2 -0
  117. package/dist/components/SToast/SToastContainer.js.map +1 -1
  118. package/dist/components/STooltip/STooltip.cjs +2 -0
  119. package/dist/components/STooltip/STooltip.cjs.map +1 -1
  120. package/dist/components/STooltip/STooltip.js +2 -0
  121. package/dist/components/STooltip/STooltip.js.map +1 -1
  122. package/dist/index.cjs +412 -184
  123. package/dist/index.cjs.map +1 -1
  124. package/dist/index.d.ts +0 -1
  125. package/dist/index.js +413 -184
  126. package/dist/index.js.map +1 -1
  127. package/dist/lib/portal-container.d.ts +7 -0
  128. package/dist/styles.css +61 -37
  129. package/package.json +1 -1
@@ -6,7 +6,6 @@ var clsx = require('clsx');
6
6
  var tailwindMerge = require('tailwind-merge');
7
7
  var jsxRuntime = require('react/jsx-runtime');
8
8
  var RCheckbox = require('@radix-ui/react-checkbox');
9
- var Ariakit = require('@ariakit/react');
10
9
  var RPopover2 = require('@radix-ui/react-popover');
11
10
 
12
11
  function _interopNamespace(e) {
@@ -28,7 +27,6 @@ function _interopNamespace(e) {
28
27
  }
29
28
 
30
29
  var RCheckbox__namespace = /*#__PURE__*/_interopNamespace(RCheckbox);
31
- var Ariakit__namespace = /*#__PURE__*/_interopNamespace(Ariakit);
32
30
  var RPopover2__namespace = /*#__PURE__*/_interopNamespace(RPopover2);
33
31
 
34
32
  var twMerge = tailwindMerge.extendTailwindMerge({
@@ -3135,10 +3133,110 @@ function SPagination({
3135
3133
  );
3136
3134
  }
3137
3135
  var PortalContainerContext = react.createContext(null);
3136
+ var InsideModalContext = react.createContext(false);
3138
3137
  PortalContainerContext.Provider;
3138
+ InsideModalContext.Provider;
3139
3139
  function usePortalContainer() {
3140
3140
  return react.useContext(PortalContainerContext) ?? void 0;
3141
3141
  }
3142
+ function useInsideModal() {
3143
+ return react.useContext(InsideModalContext);
3144
+ }
3145
+ function runRules(value, rules) {
3146
+ if (!rules || rules.length === 0) return "";
3147
+ for (const rule of rules) {
3148
+ const result = rule(value ?? "");
3149
+ if (result !== true) return typeof result === "string" ? result : "\uC720\uD6A8\uD558\uC9C0 \uC54A\uC740 \uAC12\uC785\uB2C8\uB2E4.";
3150
+ }
3151
+ return "";
3152
+ }
3153
+ var FormContext = react.createContext(null);
3154
+ function useFormContext() {
3155
+ return react.useContext(FormContext);
3156
+ }
3157
+ function useFormField(field) {
3158
+ const ctx = useFormContext();
3159
+ const cbRef = react.useRef(field);
3160
+ cbRef.current = field;
3161
+ const name = field.name ?? "";
3162
+ react.useEffect(() => {
3163
+ if (!ctx) return;
3164
+ return ctx.register({
3165
+ name,
3166
+ validate: () => cbRef.current.validate(),
3167
+ resetValidation: () => cbRef.current.resetValidation()
3168
+ });
3169
+ }, [ctx, name]);
3170
+ }
3171
+
3172
+ // src/lib/sanitize-inline-html.ts
3173
+ var ALLOWED_INLINE_TAGS = /* @__PURE__ */ new Set(["A", "B", "STRONG", "I", "EM", "BR", "SPAN"]);
3174
+ var ALLOWED_ATTRS_BY_TAG = {
3175
+ A: /* @__PURE__ */ new Set(["href", "target", "rel", "class"])
3176
+ };
3177
+ var COMMON_ALLOWED_ATTRS = /* @__PURE__ */ new Set(["class"]);
3178
+ var SAFE_HREF_RE = /^(https?:|mailto:|tel:|\/|#|\.)/i;
3179
+ var DROP_CONTENT_TAGS = /* @__PURE__ */ new Set([
3180
+ "SCRIPT",
3181
+ "STYLE",
3182
+ "IFRAME",
3183
+ "OBJECT",
3184
+ "EMBED",
3185
+ "META",
3186
+ "LINK",
3187
+ "BASE",
3188
+ "NOSCRIPT",
3189
+ // SVG/MATH 는 foreign-content 파싱 모드로 전환되어 재직렬화 시 다른 트리로
3190
+ // 재파싱될 수 있다(mXSS 벡터). 인라인 라벨에 필요 없으므로 통째로 제거한다.
3191
+ "SVG",
3192
+ "MATH",
3193
+ "TEMPLATE"
3194
+ ]);
3195
+ var escapeHtml = (value) => value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
3196
+ var sanitizeNode = (node) => {
3197
+ if (node.nodeType === Node.COMMENT_NODE) {
3198
+ node.remove();
3199
+ return;
3200
+ }
3201
+ if (node.nodeType !== Node.ELEMENT_NODE) return;
3202
+ const element = node;
3203
+ const tagName = element.tagName;
3204
+ if (DROP_CONTENT_TAGS.has(tagName)) {
3205
+ element.remove();
3206
+ return;
3207
+ }
3208
+ Array.from(element.childNodes).forEach((child) => sanitizeNode(child));
3209
+ if (ALLOWED_INLINE_TAGS.has(tagName)) {
3210
+ if (element.hasAttribute("className")) {
3211
+ const val = element.getAttribute("className");
3212
+ element.removeAttribute("className");
3213
+ if (!element.hasAttribute("class")) element.setAttribute("class", val);
3214
+ }
3215
+ const allowedAttrs = ALLOWED_ATTRS_BY_TAG[tagName] ?? COMMON_ALLOWED_ATTRS;
3216
+ Array.from(element.attributes).forEach((attr) => {
3217
+ if (!allowedAttrs.has(attr.name)) element.removeAttribute(attr.name);
3218
+ });
3219
+ if (tagName === "A") {
3220
+ const href = element.getAttribute("href");
3221
+ if (href !== null && href !== "" && !SAFE_HREF_RE.test(href)) element.removeAttribute("href");
3222
+ if (element.hasAttribute("target")) {
3223
+ element.setAttribute("rel", "noopener noreferrer");
3224
+ }
3225
+ }
3226
+ return;
3227
+ }
3228
+ const parent = element.parentNode;
3229
+ if (parent == null) return;
3230
+ while (element.firstChild) parent.insertBefore(element.firstChild, element);
3231
+ parent.removeChild(element);
3232
+ };
3233
+ var sanitizeInlineHtml = (value) => {
3234
+ if (typeof document === "undefined") return escapeHtml(value);
3235
+ const template = document.createElement("template");
3236
+ template.innerHTML = value;
3237
+ Array.from(template.content.childNodes).forEach((child) => sanitizeNode(child));
3238
+ return template.innerHTML;
3239
+ };
3142
3240
 
3143
3241
  // src/lib/floating-anim.ts
3144
3242
  var FLOATING_SLIDE_ANIM = [
@@ -3476,32 +3574,6 @@ var STooltip = react.forwardRef(function STooltip2({
3476
3574
  ] });
3477
3575
  });
3478
3576
  STooltip.displayName = "STooltip";
3479
- function runRules(value, rules) {
3480
- if (!rules || rules.length === 0) return "";
3481
- for (const rule of rules) {
3482
- const result = rule(value ?? "");
3483
- if (result !== true) return typeof result === "string" ? result : "\uC720\uD6A8\uD558\uC9C0 \uC54A\uC740 \uAC12\uC785\uB2C8\uB2E4.";
3484
- }
3485
- return "";
3486
- }
3487
- var FormContext = react.createContext(null);
3488
- function useFormContext() {
3489
- return react.useContext(FormContext);
3490
- }
3491
- function useFormField(field) {
3492
- const ctx = useFormContext();
3493
- const cbRef = react.useRef(field);
3494
- cbRef.current = field;
3495
- const name = field.name ?? "";
3496
- react.useEffect(() => {
3497
- if (!ctx) return;
3498
- return ctx.register({
3499
- name,
3500
- validate: () => cbRef.current.validate(),
3501
- resetValidation: () => cbRef.current.resetValidation()
3502
- });
3503
- }, [ctx, name]);
3504
- }
3505
3577
  var FIELD_SIZE = {
3506
3578
  sm: { height: 28, radius: 4, fieldGap: 12, labelGap: 6, fontSize: 12, lineHeight: 20, icon: 16 },
3507
3579
  md: { height: 36, radius: 6, fieldGap: 16, labelGap: 8, fontSize: 14, lineHeight: 24, icon: 16 }
@@ -3516,6 +3588,7 @@ var SField = react.forwardRef(function SField2({
3516
3588
  name,
3517
3589
  label = "",
3518
3590
  icon,
3591
+ iconColor,
3519
3592
  labelTooltip = "",
3520
3593
  labelTooltipProps,
3521
3594
  labelWidth,
@@ -3593,7 +3666,15 @@ var SField = react.forwardRef(function SField2({
3593
3666
  lineHeight: `${s.lineHeight}px`
3594
3667
  },
3595
3668
  children: [
3596
- icon && /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: icon, size: s.icon, style: { marginRight: s.labelGap } }),
3669
+ icon && /* @__PURE__ */ jsxRuntime.jsx(
3670
+ SIcon,
3671
+ {
3672
+ name: icon,
3673
+ size: s.icon,
3674
+ color: iconColor,
3675
+ style: { marginRight: s.labelGap }
3676
+ }
3677
+ ),
3597
3678
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 overflow-hidden text-ellipsis whitespace-nowrap", children: label }),
3598
3679
  (labelTooltip !== "" || labelTooltipProps) && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "shrink-0", style: { marginLeft: s.labelGap }, children: /* @__PURE__ */ jsxRuntime.jsx(
3599
3680
  STooltip,
@@ -3643,7 +3724,7 @@ var SField = react.forwardRef(function SField2({
3643
3724
  borderBottomLeftRadius: s.radius - 1
3644
3725
  },
3645
3726
  children: [
3646
- icon && !label && /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: icon, size: s.icon }),
3727
+ icon && !label && /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: icon, size: s.icon, color: iconColor }),
3647
3728
  effectiveAddonLabel
3648
3729
  ]
3649
3730
  }
@@ -3658,6 +3739,77 @@ var SField = react.forwardRef(function SField2({
3658
3739
  }
3659
3740
  );
3660
3741
  });
3742
+ function SPortal({
3743
+ open,
3744
+ onClose,
3745
+ onOpenChange,
3746
+ anchorRef,
3747
+ placement = "bottom",
3748
+ align = "start",
3749
+ offset = 4,
3750
+ alignOffset = 0,
3751
+ fit = false,
3752
+ maxHeight,
3753
+ autoClose = false,
3754
+ persistent = false,
3755
+ viewportPadding = 0,
3756
+ onOpenAutoFocus,
3757
+ onCloseAutoFocus,
3758
+ children,
3759
+ className,
3760
+ style,
3761
+ ariaLabel
3762
+ }) {
3763
+ const portalContainer = usePortalContainer();
3764
+ return /* @__PURE__ */ jsxRuntime.jsxs(
3765
+ RPopover2__namespace.Root,
3766
+ {
3767
+ open,
3768
+ onOpenChange: (next) => {
3769
+ onOpenChange?.(next);
3770
+ if (!next) onClose?.();
3771
+ },
3772
+ children: [
3773
+ /* @__PURE__ */ jsxRuntime.jsx(RPopover2__namespace.Anchor, { virtualRef: anchorRef }),
3774
+ /* @__PURE__ */ jsxRuntime.jsx(RPopover2__namespace.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxRuntime.jsx(
3775
+ RPopover2__namespace.Content,
3776
+ {
3777
+ side: placement,
3778
+ align,
3779
+ sideOffset: offset,
3780
+ alignOffset,
3781
+ collisionPadding: viewportPadding,
3782
+ "aria-label": ariaLabel,
3783
+ onPointerDownOutside: (e) => {
3784
+ if (persistent || anchorRef.current?.contains(e.target)) {
3785
+ e.preventDefault();
3786
+ }
3787
+ },
3788
+ onFocusOutside: (e) => {
3789
+ if (persistent || anchorRef.current?.contains(e.target)) {
3790
+ e.preventDefault();
3791
+ }
3792
+ },
3793
+ onEscapeKeyDown: persistent ? (e) => e.preventDefault() : void 0,
3794
+ onOpenAutoFocus,
3795
+ onCloseAutoFocus,
3796
+ onClick: autoClose ? () => onClose?.() : void 0,
3797
+ className: cn(
3798
+ "z-50 overflow-hidden rounded-(--cmp-listBox-radius) bg-(--cmp-listBox-bg) py-4 shadow-normal-sm",
3799
+ maxHeight != null && "overflow-y-auto",
3800
+ fit && "min-w-(--radix-popover-trigger-width)",
3801
+ FLOATING_SLIDE_ANIM,
3802
+ className
3803
+ ),
3804
+ style: { maxHeight, ...style },
3805
+ children
3806
+ }
3807
+ ) })
3808
+ ]
3809
+ }
3810
+ );
3811
+ }
3812
+ SPortal.displayName = "SPortal";
3661
3813
 
3662
3814
  // src/components/SSelect/select.config.ts
3663
3815
  function flattenOptions(options, depth = 1) {
@@ -3692,75 +3844,6 @@ function flattenOptions(options, depth = 1) {
3692
3844
  var selectableOptions = (flat) => flat.filter((o) => !o.isGroup && !o.disabled);
3693
3845
  var isHtmlLabel = (label) => /<[a-z][\s\S]*>/i.test(label);
3694
3846
  var extractText = (html) => html.replace(/<[^>]*>/g, "");
3695
-
3696
- // src/lib/sanitize-inline-html.ts
3697
- var ALLOWED_INLINE_TAGS = /* @__PURE__ */ new Set(["A", "B", "STRONG", "I", "EM", "BR", "SPAN"]);
3698
- var ALLOWED_ATTRS_BY_TAG = {
3699
- A: /* @__PURE__ */ new Set(["href", "target", "rel", "class"])
3700
- };
3701
- var COMMON_ALLOWED_ATTRS = /* @__PURE__ */ new Set(["class"]);
3702
- var SAFE_HREF_RE = /^(https?:|mailto:|tel:|\/|#|\.)/i;
3703
- var DROP_CONTENT_TAGS = /* @__PURE__ */ new Set([
3704
- "SCRIPT",
3705
- "STYLE",
3706
- "IFRAME",
3707
- "OBJECT",
3708
- "EMBED",
3709
- "META",
3710
- "LINK",
3711
- "BASE",
3712
- "NOSCRIPT",
3713
- // SVG/MATH 는 foreign-content 파싱 모드로 전환되어 재직렬화 시 다른 트리로
3714
- // 재파싱될 수 있다(mXSS 벡터). 인라인 라벨에 필요 없으므로 통째로 제거한다.
3715
- "SVG",
3716
- "MATH",
3717
- "TEMPLATE"
3718
- ]);
3719
- var escapeHtml = (value) => value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
3720
- var sanitizeNode = (node) => {
3721
- if (node.nodeType === Node.COMMENT_NODE) {
3722
- node.remove();
3723
- return;
3724
- }
3725
- if (node.nodeType !== Node.ELEMENT_NODE) return;
3726
- const element = node;
3727
- const tagName = element.tagName;
3728
- if (DROP_CONTENT_TAGS.has(tagName)) {
3729
- element.remove();
3730
- return;
3731
- }
3732
- Array.from(element.childNodes).forEach((child) => sanitizeNode(child));
3733
- if (ALLOWED_INLINE_TAGS.has(tagName)) {
3734
- if (element.hasAttribute("className")) {
3735
- const val = element.getAttribute("className");
3736
- element.removeAttribute("className");
3737
- if (!element.hasAttribute("class")) element.setAttribute("class", val);
3738
- }
3739
- const allowedAttrs = ALLOWED_ATTRS_BY_TAG[tagName] ?? COMMON_ALLOWED_ATTRS;
3740
- Array.from(element.attributes).forEach((attr) => {
3741
- if (!allowedAttrs.has(attr.name)) element.removeAttribute(attr.name);
3742
- });
3743
- if (tagName === "A") {
3744
- const href = element.getAttribute("href");
3745
- if (href !== null && href !== "" && !SAFE_HREF_RE.test(href)) element.removeAttribute("href");
3746
- if (element.hasAttribute("target")) {
3747
- element.setAttribute("rel", "noopener noreferrer");
3748
- }
3749
- }
3750
- return;
3751
- }
3752
- const parent = element.parentNode;
3753
- if (parent == null) return;
3754
- while (element.firstChild) parent.insertBefore(element.firstChild, element);
3755
- parent.removeChild(element);
3756
- };
3757
- var sanitizeInlineHtml = (value) => {
3758
- if (typeof document === "undefined") return escapeHtml(value);
3759
- const template = document.createElement("template");
3760
- template.innerHTML = value;
3761
- Array.from(template.content.childNodes).forEach((child) => sanitizeNode(child));
3762
- return template.innerHTML;
3763
- };
3764
3847
  var SEARCH_THRESHOLD = 11;
3765
3848
  var SSelect = react.forwardRef(function SSelect2({
3766
3849
  value,
@@ -3784,6 +3867,7 @@ var SSelect = react.forwardRef(function SSelect2({
3784
3867
  label,
3785
3868
  labelWidth,
3786
3869
  icon,
3870
+ iconColor,
3787
3871
  labelTooltip,
3788
3872
  addonLabel,
3789
3873
  addonAlign,
@@ -3795,18 +3879,15 @@ var SSelect = react.forwardRef(function SSelect2({
3795
3879
  }, ref) {
3796
3880
  const multi = type === "multi" || type === "multi_depth";
3797
3881
  const portalContainer = usePortalContainer();
3882
+ const insideModal = useInsideModal();
3798
3883
  const [search, setSearch] = react.useState("");
3799
3884
  const [ruleError, setRuleError] = react.useState("");
3800
3885
  const [open, setOpen] = react.useState(false);
3886
+ const [pendingOpen, setPendingOpen] = react.useState(false);
3887
+ const [focusedIndex, setFocusedIndex] = react.useState(-1);
3801
3888
  const triggerRef = react.useRef(null);
3802
- react.useImperativeHandle(
3803
- ref,
3804
- () => ({
3805
- focus: () => triggerRef.current?.focus(),
3806
- open: () => setOpen(true)
3807
- }),
3808
- []
3809
- );
3889
+ const searchInputRef = react.useRef(null);
3890
+ const listRef = react.useRef(null);
3810
3891
  const flat = react.useMemo(() => flattenOptions(options), [options]);
3811
3892
  const selectable = react.useMemo(() => selectableOptions(flat), [flat]);
3812
3893
  const hasLeaf = react.useMemo(() => flat.some((o) => !o.isGroup), [flat]);
@@ -3824,6 +3905,46 @@ var SSelect = react.forwardRef(function SSelect2({
3824
3905
  const arr = Array.isArray(value) ? value : [value];
3825
3906
  return arr.map((v) => v != null && typeof v === "object" ? v.value : v).filter((v) => v != null && v !== "").map(String);
3826
3907
  }, [value]);
3908
+ const selectedRef = react.useRef(selected);
3909
+ selectedRef.current = selected;
3910
+ const emit = (next) => {
3911
+ if (!onValueChange) return;
3912
+ const mapped = next.map(
3913
+ (v) => emitValue ? rawByValue.get(v) ?? v : optionByValue.get(v) ?? { value: v, label: v }
3914
+ );
3915
+ onValueChange(multi ? mapped : mapped[0] ?? (emitValue ? "" : null));
3916
+ };
3917
+ const commitOpen = (next) => {
3918
+ setOpen(next);
3919
+ onOpenChange?.(next);
3920
+ if (!next) {
3921
+ setPendingOpen(false);
3922
+ setSearch("");
3923
+ if (rules && rules.length > 0) {
3924
+ setRuleError(runRules(multi ? selectedRef.current : selectedRef.current[0] ?? "", rules));
3925
+ }
3926
+ }
3927
+ };
3928
+ const requestOpen = (next) => {
3929
+ if (next && insideModal && portalContainer == null) {
3930
+ setPendingOpen(true);
3931
+ return;
3932
+ }
3933
+ commitOpen(next);
3934
+ };
3935
+ react.useEffect(() => {
3936
+ if (!insideModal || portalContainer == null || !pendingOpen) return;
3937
+ setPendingOpen(false);
3938
+ commitOpen(true);
3939
+ }, [insideModal, pendingOpen, portalContainer]);
3940
+ react.useImperativeHandle(
3941
+ ref,
3942
+ () => ({
3943
+ focus: () => triggerRef.current?.focus(),
3944
+ open: () => requestOpen(true)
3945
+ }),
3946
+ [insideModal, portalContainer]
3947
+ );
3827
3948
  useFormField({
3828
3949
  name,
3829
3950
  validate: () => {
@@ -3833,13 +3954,6 @@ var SSelect = react.forwardRef(function SSelect2({
3833
3954
  },
3834
3955
  resetValidation: () => setRuleError("")
3835
3956
  });
3836
- const emit = (next) => {
3837
- if (!onValueChange) return;
3838
- const mapped = next.map(
3839
- (v) => emitValue ? rawByValue.get(v) ?? v : optionByValue.get(v) ?? { value: v, label: v }
3840
- );
3841
- onValueChange(multi ? mapped : mapped[0] ?? (emitValue ? "" : null));
3842
- };
3843
3957
  const filtered = react.useMemo(() => {
3844
3958
  if (!showSearch || search.trim() === "") return flat;
3845
3959
  const q = search.trim().toLowerCase();
@@ -3850,6 +3964,16 @@ var SSelect = react.forwardRef(function SSelect2({
3850
3964
  const allValues = selectable.map((o) => o.value);
3851
3965
  const allSelected = allValues.length > 0 && allValues.every((v) => selected.includes(v));
3852
3966
  const hasItems = filtered.some((o) => !o.isGroup);
3967
+ const navigableItems = react.useMemo(() => {
3968
+ const items = [];
3969
+ if (multi && useSelectAll && allValues.length > 0) {
3970
+ items.push({ value: "__select_all__", isSelectAll: true });
3971
+ }
3972
+ filtered.forEach((o) => {
3973
+ if (!o.isGroup && !o.disabled) items.push({ value: o.value });
3974
+ });
3975
+ return items;
3976
+ }, [allValues.length, filtered, multi, useSelectAll]);
3853
3977
  const triggerText = (() => {
3854
3978
  if (selected.length === 0) return placeholder;
3855
3979
  if (multi) {
@@ -3859,26 +3983,119 @@ var SSelect = react.forwardRef(function SSelect2({
3859
3983
  return labelByValue.get(selected[0]) ?? selected[0];
3860
3984
  })();
3861
3985
  const isPlaceholder = selected.length === 0;
3862
- const selectStore = /* @__PURE__ */ jsxRuntime.jsxs(
3863
- Ariakit__namespace.SelectProvider,
3986
+ const handleSelect = (nextValue) => {
3987
+ if (multi) {
3988
+ const next = selected.includes(nextValue) ? selected.filter((v) => v !== nextValue) : [...selected, nextValue];
3989
+ emit(next);
3990
+ return;
3991
+ }
3992
+ emit([nextValue]);
3993
+ commitOpen(false);
3994
+ };
3995
+ const handleSelectAll = () => {
3996
+ emit(allSelected ? [] : allValues);
3997
+ };
3998
+ react.useEffect(() => {
3999
+ if (!open) {
4000
+ setFocusedIndex(-1);
4001
+ return;
4002
+ }
4003
+ setFocusedIndex(navigableItems.length > 0 ? 0 : -1);
4004
+ }, [open, navigableItems.length]);
4005
+ react.useEffect(() => {
4006
+ if (!open) return;
4007
+ setFocusedIndex(navigableItems.length > 0 ? 0 : -1);
4008
+ }, [search, open, navigableItems.length]);
4009
+ react.useEffect(() => {
4010
+ if (!open || focusedIndex < 0) return;
4011
+ const list = listRef.current;
4012
+ const focusedEl = list?.querySelector('[data-select-focused="true"]');
4013
+ if (!list || !focusedEl) return;
4014
+ const listRect = list.getBoundingClientRect();
4015
+ const itemRect = focusedEl.getBoundingClientRect();
4016
+ if (itemRect.top < listRect.top) {
4017
+ list.scrollTop += itemRect.top - listRect.top;
4018
+ } else if (itemRect.bottom > listRect.bottom) {
4019
+ list.scrollTop += itemRect.bottom - listRect.bottom;
4020
+ }
4021
+ }, [focusedIndex, open]);
4022
+ const moveFocusedIndex = (direction) => {
4023
+ if (!open || navigableItems.length === 0) return;
4024
+ setFocusedIndex((current) => {
4025
+ if (direction === "next") {
4026
+ return current < 0 ? 0 : (current + 1) % navigableItems.length;
4027
+ }
4028
+ return current <= 0 ? navigableItems.length - 1 : current - 1;
4029
+ });
4030
+ };
4031
+ const selectFocusedItem = () => {
4032
+ if (focusedIndex < 0 || focusedIndex >= navigableItems.length) return;
4033
+ const focused = navigableItems[focusedIndex];
4034
+ if (focused.isSelectAll) {
4035
+ handleSelectAll();
4036
+ return;
4037
+ }
4038
+ handleSelect(focused.value);
4039
+ };
4040
+ const handleKeyDown = (event) => {
4041
+ if (!open) {
4042
+ if ((event.key === "ArrowDown" || event.key === "ArrowUp") && !effectiveDisabled) {
4043
+ event.preventDefault();
4044
+ requestOpen(true);
4045
+ }
4046
+ return;
4047
+ }
4048
+ if (event.key === "ArrowDown") {
4049
+ event.preventDefault();
4050
+ moveFocusedIndex("next");
4051
+ return;
4052
+ }
4053
+ if (event.key === "ArrowUp") {
4054
+ event.preventDefault();
4055
+ moveFocusedIndex("prev");
4056
+ return;
4057
+ }
4058
+ if (event.key === "Enter") {
4059
+ event.preventDefault();
4060
+ selectFocusedItem();
4061
+ return;
4062
+ }
4063
+ if (event.key === "Escape") {
4064
+ event.preventDefault();
4065
+ commitOpen(false);
4066
+ }
4067
+ };
4068
+ return /* @__PURE__ */ jsxRuntime.jsxs(
4069
+ SField,
3864
4070
  {
3865
- animated: portalContainer == null ? 150 : false,
3866
- value: multi ? selected : selected[0] ?? "",
3867
- setValue: (v) => emit(Array.isArray(v) ? v : v === "" ? [] : [v]),
3868
- open,
3869
- setOpen: (o) => {
3870
- setOpen(o);
3871
- onOpenChange?.(o);
3872
- if (!o && rules && rules.length > 0) {
3873
- setRuleError(runRules(multi ? selected : selected[0] ?? "", rules));
3874
- }
3875
- },
4071
+ label,
4072
+ labelWidth,
4073
+ icon,
4074
+ iconColor,
4075
+ labelTooltip,
4076
+ labelTooltipProps,
4077
+ addonLabel,
4078
+ addonAlign,
4079
+ hint,
4080
+ error: error || ruleError !== "",
4081
+ status: rules && rules.length > 0 ? ruleError !== "" ? "error" : void 0 : void 0,
4082
+ errorMessage: ruleError !== "" ? ruleError : errorMessage,
4083
+ disabled: effectiveDisabled,
4084
+ width,
4085
+ className,
4086
+ style,
3876
4087
  children: [
4088
+ name && /* @__PURE__ */ jsxRuntime.jsx("input", { type: "hidden", name, value: selected.join(",") }),
3877
4089
  /* @__PURE__ */ jsxRuntime.jsxs(
3878
- Ariakit__namespace.Select,
4090
+ "button",
3879
4091
  {
3880
4092
  ref: triggerRef,
4093
+ type: "button",
3881
4094
  disabled: effectiveDisabled,
4095
+ "aria-haspopup": "listbox",
4096
+ "aria-expanded": open,
4097
+ onClick: () => requestOpen(!open),
4098
+ onKeyDown: handleKeyDown,
3882
4099
  className: "flex h-full w-full cursor-pointer items-center justify-between gap-[8px] bg-transparent px-[12px] text-left text-[12px] leading-[20px] outline-none disabled:cursor-not-allowed",
3883
4100
  style: {
3884
4101
  color: effectiveDisabled ? "var(--cmp-select-text-disabled)" : isPlaceholder ? "var(--cmp-select-icon-default)" : "var(--cmp-select-text-default)"
@@ -3903,178 +4120,229 @@ var SSelect = react.forwardRef(function SSelect2({
3903
4120
  ]
3904
4121
  }
3905
4122
  ),
3906
- /* @__PURE__ */ jsxRuntime.jsxs(
3907
- Ariakit__namespace.SelectPopover,
4123
+ /* @__PURE__ */ jsxRuntime.jsx(
4124
+ SPortal,
3908
4125
  {
3909
- portal: true,
3910
- portalElement: portalContainer,
3911
- gutter: 4,
3912
- sameWidth: !dropdownWidth,
3913
- className: "z-50 overflow-auto rounded-[var(--cmp-listBox-radius)] bg-[var(--cmp-listBox-bg)] shadow-[2px_2px_12px_2px_rgba(0,0,0,0.1)] data-[enter]:animate-floating-in-bottom data-[leave]:animate-floating-out-bottom",
4126
+ open,
4127
+ onClose: () => commitOpen(false),
4128
+ onOpenChange: (next) => {
4129
+ if (next) requestOpen(true);
4130
+ },
4131
+ anchorRef: triggerRef,
4132
+ placement: "bottom",
4133
+ align: "start",
4134
+ offset: 4,
4135
+ fit: !dropdownWidth,
4136
+ onOpenAutoFocus: (event) => {
4137
+ if (!showSearch) {
4138
+ event.preventDefault();
4139
+ return;
4140
+ }
4141
+ event.preventDefault();
4142
+ requestAnimationFrame(() => searchInputRef.current?.focus());
4143
+ },
4144
+ onCloseAutoFocus: (event) => {
4145
+ event.preventDefault();
4146
+ triggerRef.current?.focus();
4147
+ },
4148
+ className: "rounded-[var(--cmp-listBox-radius)] bg-[var(--cmp-listBox-bg)] py-[4px] shadow-[2px_2px_12px_2px_rgba(0,0,0,0.1)]",
3914
4149
  style: {
3915
- maxHeight: dropdownHeight,
3916
4150
  ...dropdownWidth ? { width: dropdownWidth } : {},
3917
4151
  maxWidth: maxDropdownWidth
3918
4152
  },
3919
- children: [
3920
- showSearch && // 원본 sd-select-list-item-search: sticky + 검색 아이콘 + 테두리 input + focus-within + clear
3921
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sticky top-0 z-[1] bg-[var(--cmp-listBox-bg)] px-[8px] py-[4px]", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-[6px] rounded-[4px] border border-solid border-[color:var(--color-grey-45)] bg-white px-[8px] py-[4px] focus-within:border-[color:var(--sys-color-field-border-focus)]", children: [
3922
- /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: "search", size: 16, color: "var(--color-grey-70)", className: "shrink-0" }),
3923
- /* @__PURE__ */ jsxRuntime.jsx(
3924
- Ariakit__namespace.Combobox,
3925
- {
3926
- autoSelect: true,
3927
- placeholder: "\uAC80\uC0C9",
3928
- className: "min-w-0 flex-1 border-none bg-transparent text-[12px] leading-[20px] text-[color:var(--cmp-select-text-default)] outline-none placeholder:text-[color:var(--color-grey-55)]"
3929
- }
3930
- ),
3931
- search !== "" && /* @__PURE__ */ jsxRuntime.jsx(
3932
- SGhostButton,
3933
- {
3934
- icon: "close",
3935
- size: "xxs",
3936
- intent: "default",
3937
- ariaLabel: "\uAC80\uC0C9\uC5B4 \uC9C0\uC6B0\uAE30",
3938
- onClick: () => setSearch(""),
3939
- className: "shrink-0"
3940
- }
3941
- )
3942
- ] }) }),
3943
- multi && useSelectAll && /* @__PURE__ */ jsxRuntime.jsx(
3944
- "button",
3945
- {
3946
- type: "button",
3947
- className: "flex w-full items-center px-[12px] py-[4px] text-left text-[12px] leading-[20px] text-[color:var(--cmp-listItem-content-default)] hover:bg-[var(--cmp-listItem-bg-hover)] hover:text-[color:var(--cmp-listItem-content-hover)]",
3948
- onClick: () => emit(allSelected ? [] : allValues),
3949
- children: allSelectedLabel
3950
- }
3951
- ),
3952
- hasItems ? /* @__PURE__ */ jsxRuntime.jsx(ItemList, { items: filtered, multi, selected, useSearch: showSearch }) : (
3953
- // 원본 sd-select-listbox__empty (표시할 항목이 없을 때)
3954
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-[12px] py-[12px] text-left text-[12px] leading-[20px] text-[color:var(--cmp-listItem-content-disabled)]", children: "\uD45C\uC2DC\uD560 \uD56D\uBAA9\uC774 \uC5C6\uC2B5\uB2C8\uB2E4." })
3955
- )
3956
- ]
4153
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
4154
+ "div",
4155
+ {
4156
+ ref: listRef,
4157
+ className: "overflow-auto [scrollbar-width:auto] [&::-webkit-scrollbar]:w-[8px] [&::-webkit-scrollbar]:bg-[var(--color-grey-25)] [&::-webkit-scrollbar:horizontal]:h-[8px] [&::-webkit-scrollbar-thumb]:rounded-[4px] [&::-webkit-scrollbar-thumb]:bg-[var(--color-grey-45)] [&::-webkit-scrollbar-track]:bg-[var(--color-grey-25)]",
4158
+ style: { maxHeight: dropdownHeight },
4159
+ onMouseLeave: () => setFocusedIndex(-1),
4160
+ children: [
4161
+ showSearch && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sticky top-0 z-[1] bg-[var(--cmp-listBox-bg)] px-[8px] py-[4px]", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-[6px] rounded-[4px] border border-solid border-[color:var(--color-grey-45)] bg-white px-[8px] py-[4px] focus-within:border-[color:var(--sys-color-field-border-focus)]", children: [
4162
+ /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: "search", size: 16, color: "var(--color-grey-70)", className: "shrink-0" }),
4163
+ /* @__PURE__ */ jsxRuntime.jsx(
4164
+ "input",
4165
+ {
4166
+ ref: searchInputRef,
4167
+ value: search,
4168
+ onChange: (e) => setSearch(e.target.value),
4169
+ onKeyDown: handleKeyDown,
4170
+ placeholder: "\uAC80\uC0C9",
4171
+ className: "min-w-0 flex-1 border-none bg-transparent text-[12px] leading-[20px] text-[color:var(--cmp-select-text-default)] outline-none placeholder:text-[color:var(--color-grey-55)]"
4172
+ }
4173
+ ),
4174
+ search !== "" && /* @__PURE__ */ jsxRuntime.jsx(
4175
+ SGhostButton,
4176
+ {
4177
+ icon: "close",
4178
+ size: "xxs",
4179
+ intent: "default",
4180
+ ariaLabel: "\uAC80\uC0C9\uC5B4 \uC9C0\uC6B0\uAE30",
4181
+ onClick: () => setSearch(""),
4182
+ className: "shrink-0"
4183
+ }
4184
+ )
4185
+ ] }) }),
4186
+ hasItems ? /* @__PURE__ */ jsxRuntime.jsx(
4187
+ ItemList,
4188
+ {
4189
+ items: filtered,
4190
+ multi,
4191
+ selected,
4192
+ focusedValue: navigableItems[focusedIndex]?.isSelectAll ? "__select_all__" : navigableItems[focusedIndex]?.value,
4193
+ showSelectAll: multi && useSelectAll && allValues.length > 0,
4194
+ selectAllLabel: allSelectedLabel,
4195
+ selectAllSelected: allSelected,
4196
+ onSelectAll: handleSelectAll,
4197
+ onSelect: handleSelect,
4198
+ onHoverValue: (value2) => {
4199
+ const index = navigableItems.findIndex(
4200
+ (item) => item.isSelectAll ? value2 === "__select_all__" : item.value === value2
4201
+ );
4202
+ if (index >= 0) setFocusedIndex(index);
4203
+ }
4204
+ }
4205
+ ) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-[12px] py-[12px] text-left text-[12px] leading-[20px] text-[color:var(--cmp-listItem-content-disabled)]", children: "\uD45C\uC2DC\uD560 \uD56D\uBAA9\uC774 \uC5C6\uC2B5\uB2C8\uB2E4." })
4206
+ ]
4207
+ }
4208
+ )
3957
4209
  }
3958
4210
  )
3959
4211
  ]
3960
4212
  }
3961
4213
  );
3962
- const control = showSearch ? /* @__PURE__ */ jsxRuntime.jsx(Ariakit__namespace.ComboboxProvider, { value: search, setValue: setSearch, resetValueOnHide: true, children: selectStore }) : selectStore;
3963
- return /* @__PURE__ */ jsxRuntime.jsxs(
3964
- SField,
3965
- {
3966
- label,
3967
- labelWidth,
3968
- icon,
3969
- labelTooltip,
3970
- labelTooltipProps,
3971
- addonLabel,
3972
- addonAlign,
3973
- hint,
3974
- error: error || ruleError !== "",
3975
- status: rules && rules.length > 0 ? ruleError !== "" ? "error" : void 0 : void 0,
3976
- errorMessage: ruleError !== "" ? ruleError : errorMessage,
3977
- disabled: effectiveDisabled,
3978
- width,
3979
- className,
3980
- style,
3981
- children: [
3982
- name && /* @__PURE__ */ jsxRuntime.jsx("input", { type: "hidden", name, value: selected.join(",") }),
3983
- control
3984
- ]
3985
- }
3986
- );
3987
4214
  });
3988
4215
  SSelect.displayName = "SSelect";
3989
4216
  function ItemList({
3990
4217
  items,
3991
4218
  multi,
3992
4219
  selected,
3993
- useSearch
4220
+ focusedValue,
4221
+ showSelectAll,
4222
+ selectAllLabel,
4223
+ selectAllSelected,
4224
+ onSelectAll,
4225
+ onSelect,
4226
+ onHoverValue
3994
4227
  }) {
3995
4228
  const itemBase = cn(
3996
- // group: 체크박스 inverse(group-data-[active-item])를 위해 필요
3997
- "group flex cursor-pointer select-none items-center gap-[8px] text-[12px] leading-[20px] outline-none",
4229
+ "group flex w-full cursor-pointer select-none items-center gap-[8px] border-none bg-transparent text-left text-[12px] leading-[20px] outline-none",
3998
4230
  "text-[color:var(--cmp-listItem-content-default)]",
3999
- // Ariakit은 disabled 항목에 aria-disabled를 붙이므로 aria-disabled 변형으로 스타일 (data-disabled 아님)
4000
- "aria-disabled:cursor-not-allowed aria-disabled:text-[color:var(--cmp-listItem-content-disabled)]"
4231
+ "disabled:cursor-not-allowed disabled:text-[color:var(--cmp-listItem-content-disabled)]"
4001
4232
  );
4002
- const activeClass = "data-[active-item]:bg-[var(--cmp-listItem-bg-hover)] data-[active-item]:text-[color:var(--cmp-listItem-content-hover)]";
4003
- return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: items.map((o) => {
4004
- const padLeft = o.depth === 1 ? 12 : o.depth === 2 ? 20 : 28;
4005
- if (o.isGroup) {
4006
- const isDepth1 = o.depth === 1;
4007
- return /* @__PURE__ */ jsxRuntime.jsx(
4008
- "div",
4009
- {
4010
- className: cn(
4011
- "select-none py-[4px] pr-[12px] text-[12px] leading-[20px] text-[color:var(--cmp-listItem-content-default)]",
4012
- 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",
4013
- // 하위가 전부 비활성이면(select.config 에서 bubble-up) 어미 그룹도 비활성 처리:
4014
- // 텍스트는 회색(#888), depth1 그룹은 파란 배경 대신 회색(#EEE)으로 (depth2 는 #F6F6F6 유지)
4015
- o.disabled && "cursor-not-allowed text-[color:var(--cmp-listItem-content-disabled)]",
4016
- o.disabled && isDepth1 && "bg-[var(--cmp-listItem-depth1-bg-disabled)]"
4017
- ),
4018
- style: { paddingLeft: padLeft },
4019
- children: o.label
4020
- },
4021
- o.value
4022
- );
4023
- }
4024
- const isSelected = selected.includes(o.value);
4025
- return /* @__PURE__ */ jsxRuntime.jsxs(
4026
- Ariakit__namespace.SelectItem,
4233
+ 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)]";
4234
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { role: "listbox", "aria-multiselectable": multi || void 0, children: [
4235
+ showSelectAll && /* @__PURE__ */ jsxRuntime.jsxs(
4236
+ "button",
4027
4237
  {
4028
- value: o.value,
4029
- disabled: o.disabled,
4030
- accessibleWhenDisabled: true,
4238
+ type: "button",
4239
+ "data-select-focused": focusedValue === "__select_all__" ? "true" : void 0,
4240
+ "data-select-active": focusedValue === "__select_all__" ? "true" : void 0,
4031
4241
  className: cn(
4032
4242
  itemBase,
4033
- isSelected && !multi && "font-bold text-[color:var(--cmp-listItem-content-selected)]",
4034
- // Ariakit은 disabled 항목도 hover 시 active(data-active-item) 처리하므로,
4035
- // 비활성 항목에는 hover 하이라이트를 적용하지 않아 회색 비활성 스타일을 유지한다.
4036
- // (원본 Stencil의 `--focused...:not(--disabled)` 규칙과 동일한 의도)
4037
- !o.disabled && activeClass
4243
+ focusedValue === "__select_all__" && "bg-[var(--cmp-listItem-bg-hover)] text-[color:var(--cmp-listItem-content-hover)]"
4038
4244
  ),
4039
- style: { paddingTop: 4, paddingBottom: 4, paddingRight: 12, paddingLeft: padLeft },
4040
- render: useSearch ? /* @__PURE__ */ jsxRuntime.jsx(Ariakit__namespace.ComboboxItem, {}) : void 0,
4245
+ style: { paddingTop: 4, paddingBottom: 4, paddingRight: 12, paddingLeft: 12 },
4246
+ onMouseEnter: () => onHoverValue("__select_all__"),
4247
+ onClick: onSelectAll,
4041
4248
  children: [
4042
- multi && // hover(active) 시 checkbox inverse — 파란 hover 배경 위에서 checked/unchecked 모두 흰 테두리 + 투명 배경
4043
4249
  /* @__PURE__ */ jsxRuntime.jsx(
4044
4250
  "span",
4045
4251
  {
4046
4252
  className: cn(
4047
4253
  "flex h-[16px] w-[16px] flex-shrink-0 items-center justify-center rounded-[2px] border border-solid",
4048
- o.disabled ? (
4049
- // 비활성: 회색 채움(#E1E1E1) + 회색 테두리(#CCCCCC)
4050
- "cursor-not-allowed border-[color:var(--cmp-checkbox-border-disabled)] bg-[var(--cmp-checkbox-bg-disabled)]"
4051
- ) : cn(
4052
- "group-data-[active-item]:border-[color:var(--cmp-checkbox-checked-border-inverse)]",
4053
- isSelected ? "border-[color:var(--cmp-checkbox-checked-bg-default)] bg-[var(--cmp-checkbox-checked-bg-default)] group-data-[active-item]:bg-transparent" : "border-[color:var(--cmp-checkbox-unchecked-border-default)] bg-transparent"
4054
- )
4254
+ 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)]"
4055
4255
  ),
4056
- children: isSelected && /* @__PURE__ */ jsxRuntime.jsx(
4256
+ children: selectAllSelected && /* @__PURE__ */ jsxRuntime.jsx(
4057
4257
  SIcon,
4058
4258
  {
4059
4259
  name: "checkboxCheck",
4060
4260
  size: 12,
4061
- color: o.disabled ? "var(--cmp-checkbox-checked-icon-disabled)" : "var(--cmp-checkbox-checked-icon-default)"
4261
+ color: "var(--cmp-checkbox-checked-icon-default)"
4062
4262
  }
4063
4263
  )
4064
4264
  }
4065
4265
  ),
4066
- isHtmlLabel(o.label) ? /* @__PURE__ */ jsxRuntime.jsx(
4067
- "span",
4068
- {
4069
- className: "flex-1 truncate",
4070
- dangerouslySetInnerHTML: { __html: sanitizeInlineHtml(o.label) }
4071
- }
4072
- ) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1 truncate", children: o.label })
4266
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1 truncate", children: selectAllLabel })
4073
4267
  ]
4074
- },
4075
- o.value
4076
- );
4077
- }) });
4268
+ }
4269
+ ),
4270
+ items.map((o) => {
4271
+ const padLeft = o.depth === 1 ? 12 : o.depth === 2 ? 20 : 28;
4272
+ if (o.isGroup) {
4273
+ const isDepth1 = o.depth === 1;
4274
+ return /* @__PURE__ */ jsxRuntime.jsx(
4275
+ "div",
4276
+ {
4277
+ className: cn(
4278
+ "select-none py-[4px] pr-[12px] text-[12px] leading-[20px] text-[color:var(--cmp-listItem-content-default)]",
4279
+ 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",
4280
+ // 하위가 전부 비활성이면(select.config 에서 bubble-up) 어미 그룹도 비활성 처리:
4281
+ // 텍스트는 회색(#888), depth1 그룹은 파란 배경 대신 회색(#EEE)으로 (depth2 는 #F6F6F6 유지)
4282
+ o.disabled && "cursor-not-allowed text-[color:var(--cmp-listItem-content-disabled)]",
4283
+ o.disabled && isDepth1 && "bg-[var(--cmp-listItem-depth1-bg-disabled)]"
4284
+ ),
4285
+ style: { paddingLeft: padLeft },
4286
+ children: o.label
4287
+ },
4288
+ o.value
4289
+ );
4290
+ }
4291
+ const isSelected = selected.includes(o.value);
4292
+ return /* @__PURE__ */ jsxRuntime.jsxs(
4293
+ "button",
4294
+ {
4295
+ type: "button",
4296
+ disabled: o.disabled,
4297
+ role: "option",
4298
+ "aria-selected": isSelected,
4299
+ "data-select-focused": focusedValue === o.value ? "true" : void 0,
4300
+ "data-select-active": focusedValue === o.value ? "true" : void 0,
4301
+ className: cn(
4302
+ itemBase,
4303
+ isSelected && !multi && "font-bold text-[color:var(--cmp-listItem-content-selected)]",
4304
+ focusedValue === o.value && "bg-[var(--cmp-listItem-bg-hover)] text-[color:var(--cmp-listItem-content-hover)]",
4305
+ activeClass
4306
+ ),
4307
+ style: { paddingTop: 4, paddingBottom: 4, paddingRight: 12, paddingLeft: padLeft },
4308
+ onMouseEnter: () => onHoverValue(o.value),
4309
+ onClick: () => onSelect(o.value),
4310
+ children: [
4311
+ multi && /* @__PURE__ */ jsxRuntime.jsx(
4312
+ "span",
4313
+ {
4314
+ className: cn(
4315
+ "flex h-[16px] w-[16px] flex-shrink-0 items-center justify-center rounded-[2px] border border-solid",
4316
+ o.disabled ? (
4317
+ // 비활성: 회색 채움(#E1E1E1) + 회색 테두리(#CCCCCC)
4318
+ "cursor-not-allowed border-[color:var(--cmp-checkbox-border-disabled)] bg-[var(--cmp-checkbox-bg-disabled)]"
4319
+ ) : cn(
4320
+ 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)]"
4321
+ )
4322
+ ),
4323
+ children: isSelected && /* @__PURE__ */ jsxRuntime.jsx(
4324
+ SIcon,
4325
+ {
4326
+ name: "checkboxCheck",
4327
+ size: 12,
4328
+ color: o.disabled ? "var(--cmp-checkbox-checked-icon-disabled)" : "var(--cmp-checkbox-checked-icon-default)"
4329
+ }
4330
+ )
4331
+ }
4332
+ ),
4333
+ isHtmlLabel(o.label) ? /* @__PURE__ */ jsxRuntime.jsx(
4334
+ "span",
4335
+ {
4336
+ className: "flex-1 truncate",
4337
+ dangerouslySetInnerHTML: { __html: sanitizeInlineHtml(o.label) }
4338
+ }
4339
+ ) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1 truncate", children: o.label })
4340
+ ]
4341
+ },
4342
+ o.value
4343
+ );
4344
+ })
4345
+ ] });
4078
4346
  }
4079
4347
  var DEFAULT_ROW_HEIGHT = 44;
4080
4348
  var DENSE_ROW_HEIGHT = 32;
@@ -4350,7 +4618,7 @@ var STable = react.forwardRef(function STable2({
4350
4618
  };
4351
4619
  const radiusClass = radius === "full" ? "rounded-none" : radius === "useTop" ? "rounded-b-[var(--cmp-table-radius)] rounded-t-none" : "rounded-[var(--cmp-table-radius)]";
4352
4620
  const cellPadY = dense ? "var(--cmp-table-body-paddingY-dense)" : "var(--cmp-table-body-paddingY-default)";
4353
- const cellPadX = dense ? "var(--cmp-table-body-paddingX-default)" : "var(--cmp-table-body-paddingX-wide)";
4621
+ const cellPadX = "var(--cmp-table-body-paddingX-wide)";
4354
4622
  const rowH = dense ? "var(--cmp-table-body-dense-height)" : "var(--cmp-table-body-default-height)";
4355
4623
  const totalColSpan = visibleCols.length + (selectable ? 1 : 0);
4356
4624
  const stickyInfo = (i) => {