sellmate-design-system-react 0.1.9 → 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 (148) 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 +16 -3
  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 +16 -3
  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 +22 -4
  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 +22 -4
  22. package/dist/components/SChipInput/SChipInput.js.map +1 -1
  23. package/dist/components/SChipInput/index.d.ts +1 -1
  24. package/dist/components/SCircleProgress/SCircleProgress.cjs +69 -50
  25. package/dist/components/SCircleProgress/SCircleProgress.cjs.map +1 -1
  26. package/dist/components/SCircleProgress/SCircleProgress.d.ts +2 -0
  27. package/dist/components/SCircleProgress/SCircleProgress.js +69 -50
  28. package/dist/components/SCircleProgress/SCircleProgress.js.map +1 -1
  29. package/dist/components/SConfirmModal/SConfirmModal.cjs +13 -10
  30. package/dist/components/SConfirmModal/SConfirmModal.cjs.map +1 -1
  31. package/dist/components/SConfirmModal/SConfirmModal.d.ts +7 -4
  32. package/dist/components/SConfirmModal/SConfirmModal.js +13 -10
  33. package/dist/components/SConfirmModal/SConfirmModal.js.map +1 -1
  34. package/dist/components/SDatePicker/SDatePicker.cjs +16 -3
  35. package/dist/components/SDatePicker/SDatePicker.cjs.map +1 -1
  36. package/dist/components/SDatePicker/SDatePicker.d.ts +3 -1
  37. package/dist/components/SDatePicker/SDatePicker.js +16 -3
  38. package/dist/components/SDatePicker/SDatePicker.js.map +1 -1
  39. package/dist/components/SDateRangePicker/SDateRangePicker.cjs +16 -3
  40. package/dist/components/SDateRangePicker/SDateRangePicker.cjs.map +1 -1
  41. package/dist/components/SDateRangePicker/SDateRangePicker.d.ts +3 -1
  42. package/dist/components/SDateRangePicker/SDateRangePicker.js +16 -3
  43. package/dist/components/SDateRangePicker/SDateRangePicker.js.map +1 -1
  44. package/dist/components/SDropdownButton/SDropdownButton.cjs +2 -0
  45. package/dist/components/SDropdownButton/SDropdownButton.cjs.map +1 -1
  46. package/dist/components/SDropdownButton/SDropdownButton.js +2 -0
  47. package/dist/components/SDropdownButton/SDropdownButton.js.map +1 -1
  48. package/dist/components/SField/SField.cjs +14 -3
  49. package/dist/components/SField/SField.cjs.map +1 -1
  50. package/dist/components/SField/SField.d.ts +4 -1
  51. package/dist/components/SField/SField.js +14 -3
  52. package/dist/components/SField/SField.js.map +1 -1
  53. package/dist/components/SFilePicker/SFilePicker.cjs +51 -19
  54. package/dist/components/SFilePicker/SFilePicker.cjs.map +1 -1
  55. package/dist/components/SFilePicker/SFilePicker.d.ts +4 -2
  56. package/dist/components/SFilePicker/SFilePicker.js +50 -19
  57. package/dist/components/SFilePicker/SFilePicker.js.map +1 -1
  58. package/dist/components/SGhostButton/SGhostButton.cjs +2 -0
  59. package/dist/components/SGhostButton/SGhostButton.cjs.map +1 -1
  60. package/dist/components/SGhostButton/SGhostButton.js +2 -0
  61. package/dist/components/SGhostButton/SGhostButton.js.map +1 -1
  62. package/dist/components/SGuide/SGuide.cjs +2 -0
  63. package/dist/components/SGuide/SGuide.cjs.map +1 -1
  64. package/dist/components/SGuide/SGuide.js +2 -0
  65. package/dist/components/SGuide/SGuide.js.map +1 -1
  66. package/dist/components/SInput/SInput.cjs +16 -3
  67. package/dist/components/SInput/SInput.cjs.map +1 -1
  68. package/dist/components/SInput/SInput.d.ts +4 -1
  69. package/dist/components/SInput/SInput.js +16 -3
  70. package/dist/components/SInput/SInput.js.map +1 -1
  71. package/dist/components/SKeyValueTable/SKeyValueTable.cjs +530 -219
  72. package/dist/components/SKeyValueTable/SKeyValueTable.cjs.map +1 -1
  73. package/dist/components/SKeyValueTable/SKeyValueTable.d.ts +27 -6
  74. package/dist/components/SKeyValueTable/SKeyValueTable.js +529 -218
  75. package/dist/components/SKeyValueTable/SKeyValueTable.js.map +1 -1
  76. package/dist/components/SLoadingContainer/SLoadingContainer.cjs +69 -50
  77. package/dist/components/SLoadingContainer/SLoadingContainer.cjs.map +1 -1
  78. package/dist/components/SLoadingContainer/SLoadingContainer.js +69 -50
  79. package/dist/components/SLoadingContainer/SLoadingContainer.js.map +1 -1
  80. package/dist/components/SLoadingContainer/loading.cjs +69 -50
  81. package/dist/components/SLoadingContainer/loading.cjs.map +1 -1
  82. package/dist/components/SLoadingContainer/loading.js +69 -50
  83. package/dist/components/SLoadingContainer/loading.js.map +1 -1
  84. package/dist/components/SLoadingModal/SLoadingModal.cjs +75 -51
  85. package/dist/components/SLoadingModal/SLoadingModal.cjs.map +1 -1
  86. package/dist/components/SLoadingModal/SLoadingModal.d.ts +3 -1
  87. package/dist/components/SLoadingModal/SLoadingModal.js +75 -51
  88. package/dist/components/SLoadingModal/SLoadingModal.js.map +1 -1
  89. package/dist/components/SModalContainer/SModalContainer.cjs +3 -1
  90. package/dist/components/SModalContainer/SModalContainer.cjs.map +1 -1
  91. package/dist/components/SModalContainer/SModalContainer.js +3 -1
  92. package/dist/components/SModalContainer/SModalContainer.js.map +1 -1
  93. package/dist/components/SNumberInput/SNumberInput.cjs +16 -3
  94. package/dist/components/SNumberInput/SNumberInput.cjs.map +1 -1
  95. package/dist/components/SNumberInput/SNumberInput.d.ts +2 -0
  96. package/dist/components/SNumberInput/SNumberInput.js +16 -3
  97. package/dist/components/SNumberInput/SNumberInput.js.map +1 -1
  98. package/dist/components/SPopover/SPopover.cjs +2 -0
  99. package/dist/components/SPopover/SPopover.cjs.map +1 -1
  100. package/dist/components/SPopover/SPopover.js +2 -0
  101. package/dist/components/SPopover/SPopover.js.map +1 -1
  102. package/dist/components/SPortal/SPortal.cjs +2 -0
  103. package/dist/components/SPortal/SPortal.cjs.map +1 -1
  104. package/dist/components/SPortal/SPortal.js +2 -0
  105. package/dist/components/SPortal/SPortal.js.map +1 -1
  106. package/dist/components/SRadio/SRadioGroup.cjs +1 -1
  107. package/dist/components/SRadio/SRadioGroup.cjs.map +1 -1
  108. package/dist/components/SRadio/SRadioGroup.js +1 -1
  109. package/dist/components/SRadio/SRadioGroup.js.map +1 -1
  110. package/dist/components/SSelect/SSelect.cjs +530 -258
  111. package/dist/components/SSelect/SSelect.cjs.map +1 -1
  112. package/dist/components/SSelect/SSelect.d.ts +4 -2
  113. package/dist/components/SSelect/SSelect.js +531 -258
  114. package/dist/components/SSelect/SSelect.js.map +1 -1
  115. package/dist/components/STable/STable.cjs +603 -309
  116. package/dist/components/STable/STable.cjs.map +1 -1
  117. package/dist/components/STable/STable.js +604 -309
  118. package/dist/components/STable/STable.js.map +1 -1
  119. package/dist/components/STextLink/STextLink.cjs +2 -0
  120. package/dist/components/STextLink/STextLink.cjs.map +1 -1
  121. package/dist/components/STextLink/STextLink.js +2 -0
  122. package/dist/components/STextLink/STextLink.js.map +1 -1
  123. package/dist/components/STextarea/STextarea.cjs +16 -3
  124. package/dist/components/STextarea/STextarea.cjs.map +1 -1
  125. package/dist/components/STextarea/STextarea.d.ts +2 -0
  126. package/dist/components/STextarea/STextarea.js +16 -3
  127. package/dist/components/STextarea/STextarea.js.map +1 -1
  128. package/dist/components/SToast/SToast.cjs +2 -0
  129. package/dist/components/SToast/SToast.cjs.map +1 -1
  130. package/dist/components/SToast/SToast.js +2 -0
  131. package/dist/components/SToast/SToast.js.map +1 -1
  132. package/dist/components/SToast/SToastContainer.cjs +2 -0
  133. package/dist/components/SToast/SToastContainer.cjs.map +1 -1
  134. package/dist/components/SToast/SToastContainer.js +2 -0
  135. package/dist/components/SToast/SToastContainer.js.map +1 -1
  136. package/dist/components/STooltip/STooltip.cjs +3 -1
  137. package/dist/components/STooltip/STooltip.cjs.map +1 -1
  138. package/dist/components/STooltip/STooltip.js +3 -1
  139. package/dist/components/STooltip/STooltip.js.map +1 -1
  140. package/dist/index.cjs +522 -248
  141. package/dist/index.cjs.map +1 -1
  142. package/dist/index.d.ts +0 -1
  143. package/dist/index.js +523 -248
  144. package/dist/index.js.map +1 -1
  145. package/dist/lib/portal-container.d.ts +7 -0
  146. package/dist/styles.css +123 -58
  147. package/dist/theme.css +45 -21
  148. package/package.json +1 -1
@@ -2,7 +2,6 @@
2
2
  'use strict';
3
3
 
4
4
  var react = require('react');
5
- var Ariakit = require('@ariakit/react');
6
5
  var clsx = require('clsx');
7
6
  var tailwindMerge = require('tailwind-merge');
8
7
  var jsxRuntime = require('react/jsx-runtime');
@@ -26,7 +25,6 @@ function _interopNamespace(e) {
26
25
  return Object.freeze(n);
27
26
  }
28
27
 
29
- var Ariakit__namespace = /*#__PURE__*/_interopNamespace(Ariakit);
30
28
  var RPopover2__namespace = /*#__PURE__*/_interopNamespace(RPopover2);
31
29
 
32
30
  var twMerge = tailwindMerge.extendTailwindMerge({
@@ -40,10 +38,110 @@ function cn(...inputs) {
40
38
  return twMerge(clsx.clsx(inputs));
41
39
  }
42
40
  var PortalContainerContext = react.createContext(null);
41
+ var InsideModalContext = react.createContext(false);
43
42
  PortalContainerContext.Provider;
43
+ InsideModalContext.Provider;
44
44
  function usePortalContainer() {
45
45
  return react.useContext(PortalContainerContext) ?? void 0;
46
46
  }
47
+ function useInsideModal() {
48
+ return react.useContext(InsideModalContext);
49
+ }
50
+ function runRules(value, rules) {
51
+ if (!rules || rules.length === 0) return "";
52
+ for (const rule of rules) {
53
+ const result = rule(value ?? "");
54
+ if (result !== true) return typeof result === "string" ? result : "\uC720\uD6A8\uD558\uC9C0 \uC54A\uC740 \uAC12\uC785\uB2C8\uB2E4.";
55
+ }
56
+ return "";
57
+ }
58
+ var FormContext = react.createContext(null);
59
+ function useFormContext() {
60
+ return react.useContext(FormContext);
61
+ }
62
+ function useFormField(field) {
63
+ const ctx = useFormContext();
64
+ const cbRef = react.useRef(field);
65
+ cbRef.current = field;
66
+ const name = field.name ?? "";
67
+ react.useEffect(() => {
68
+ if (!ctx) return;
69
+ return ctx.register({
70
+ name,
71
+ validate: () => cbRef.current.validate(),
72
+ resetValidation: () => cbRef.current.resetValidation()
73
+ });
74
+ }, [ctx, name]);
75
+ }
76
+
77
+ // src/lib/sanitize-inline-html.ts
78
+ var ALLOWED_INLINE_TAGS = /* @__PURE__ */ new Set(["A", "B", "STRONG", "I", "EM", "BR", "SPAN"]);
79
+ var ALLOWED_ATTRS_BY_TAG = {
80
+ A: /* @__PURE__ */ new Set(["href", "target", "rel", "class"])
81
+ };
82
+ var COMMON_ALLOWED_ATTRS = /* @__PURE__ */ new Set(["class"]);
83
+ var SAFE_HREF_RE = /^(https?:|mailto:|tel:|\/|#|\.)/i;
84
+ var DROP_CONTENT_TAGS = /* @__PURE__ */ new Set([
85
+ "SCRIPT",
86
+ "STYLE",
87
+ "IFRAME",
88
+ "OBJECT",
89
+ "EMBED",
90
+ "META",
91
+ "LINK",
92
+ "BASE",
93
+ "NOSCRIPT",
94
+ // SVG/MATH 는 foreign-content 파싱 모드로 전환되어 재직렬화 시 다른 트리로
95
+ // 재파싱될 수 있다(mXSS 벡터). 인라인 라벨에 필요 없으므로 통째로 제거한다.
96
+ "SVG",
97
+ "MATH",
98
+ "TEMPLATE"
99
+ ]);
100
+ var escapeHtml = (value) => value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
101
+ var sanitizeNode = (node) => {
102
+ if (node.nodeType === Node.COMMENT_NODE) {
103
+ node.remove();
104
+ return;
105
+ }
106
+ if (node.nodeType !== Node.ELEMENT_NODE) return;
107
+ const element = node;
108
+ const tagName = element.tagName;
109
+ if (DROP_CONTENT_TAGS.has(tagName)) {
110
+ element.remove();
111
+ return;
112
+ }
113
+ Array.from(element.childNodes).forEach((child) => sanitizeNode(child));
114
+ if (ALLOWED_INLINE_TAGS.has(tagName)) {
115
+ if (element.hasAttribute("className")) {
116
+ const val = element.getAttribute("className");
117
+ element.removeAttribute("className");
118
+ if (!element.hasAttribute("class")) element.setAttribute("class", val);
119
+ }
120
+ const allowedAttrs = ALLOWED_ATTRS_BY_TAG[tagName] ?? COMMON_ALLOWED_ATTRS;
121
+ Array.from(element.attributes).forEach((attr) => {
122
+ if (!allowedAttrs.has(attr.name)) element.removeAttribute(attr.name);
123
+ });
124
+ if (tagName === "A") {
125
+ const href = element.getAttribute("href");
126
+ if (href !== null && href !== "" && !SAFE_HREF_RE.test(href)) element.removeAttribute("href");
127
+ if (element.hasAttribute("target")) {
128
+ element.setAttribute("rel", "noopener noreferrer");
129
+ }
130
+ }
131
+ return;
132
+ }
133
+ const parent = element.parentNode;
134
+ if (parent == null) return;
135
+ while (element.firstChild) parent.insertBefore(element.firstChild, element);
136
+ parent.removeChild(element);
137
+ };
138
+ var sanitizeInlineHtml = (value) => {
139
+ if (typeof document === "undefined") return escapeHtml(value);
140
+ const template = document.createElement("template");
141
+ template.innerHTML = value;
142
+ Array.from(template.content.childNodes).forEach((child) => sanitizeNode(child));
143
+ return template.innerHTML;
144
+ };
47
145
 
48
146
  // src/lib/color.ts
49
147
  var COLOR_KEYS = [
@@ -3150,7 +3248,7 @@ var STooltip = react.forwardRef(function STooltip2({
3150
3248
  if (disabled || body == null) return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: triggerNode });
3151
3249
  const isHover = trigger === "hover";
3152
3250
  const contentClass = cn(
3153
- "z-50 max-w-xs rounded-[6px] px-[16px] py-[12px] text-[12px] leading-[20px] shadow-[2px_2px_12px_2px_rgba(0,0,0,0.1)]",
3251
+ "z-50 max-w-xs rounded-[6px] px-[16px] py-[12px] text-[12px] leading-[20px]",
3154
3252
  FLOATING_SLIDE_ANIM,
3155
3253
  className
3156
3254
  );
@@ -3216,32 +3314,6 @@ var STooltip = react.forwardRef(function STooltip2({
3216
3314
  ] });
3217
3315
  });
3218
3316
  STooltip.displayName = "STooltip";
3219
- function runRules(value, rules) {
3220
- if (!rules || rules.length === 0) return "";
3221
- for (const rule of rules) {
3222
- const result = rule(value ?? "");
3223
- if (result !== true) return typeof result === "string" ? result : "\uC720\uD6A8\uD558\uC9C0 \uC54A\uC740 \uAC12\uC785\uB2C8\uB2E4.";
3224
- }
3225
- return "";
3226
- }
3227
- var FormContext = react.createContext(null);
3228
- function useFormContext() {
3229
- return react.useContext(FormContext);
3230
- }
3231
- function useFormField(field) {
3232
- const ctx = useFormContext();
3233
- const cbRef = react.useRef(field);
3234
- cbRef.current = field;
3235
- const name = field.name ?? "";
3236
- react.useEffect(() => {
3237
- if (!ctx) return;
3238
- return ctx.register({
3239
- name,
3240
- validate: () => cbRef.current.validate(),
3241
- resetValidation: () => cbRef.current.resetValidation()
3242
- });
3243
- }, [ctx, name]);
3244
- }
3245
3317
  var FIELD_SIZE = {
3246
3318
  sm: { height: 28, radius: 4, fieldGap: 12, labelGap: 6, fontSize: 12, lineHeight: 20, icon: 16 },
3247
3319
  md: { height: 36, radius: 6, fieldGap: 16, labelGap: 8, fontSize: 14, lineHeight: 24, icon: 16 }
@@ -3256,6 +3328,7 @@ var SField = react.forwardRef(function SField2({
3256
3328
  name,
3257
3329
  label = "",
3258
3330
  icon,
3331
+ iconColor,
3259
3332
  labelTooltip = "",
3260
3333
  labelTooltipProps,
3261
3334
  labelWidth,
@@ -3333,7 +3406,15 @@ var SField = react.forwardRef(function SField2({
3333
3406
  lineHeight: `${s.lineHeight}px`
3334
3407
  },
3335
3408
  children: [
3336
- icon && /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: icon, size: s.icon, style: { marginRight: s.labelGap } }),
3409
+ icon && /* @__PURE__ */ jsxRuntime.jsx(
3410
+ SIcon,
3411
+ {
3412
+ name: icon,
3413
+ size: s.icon,
3414
+ color: iconColor,
3415
+ style: { marginRight: s.labelGap }
3416
+ }
3417
+ ),
3337
3418
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 overflow-hidden text-ellipsis whitespace-nowrap", children: label }),
3338
3419
  (labelTooltip !== "" || labelTooltipProps) && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "shrink-0", style: { marginLeft: s.labelGap }, children: /* @__PURE__ */ jsxRuntime.jsx(
3339
3420
  STooltip,
@@ -3383,7 +3464,7 @@ var SField = react.forwardRef(function SField2({
3383
3464
  borderBottomLeftRadius: s.radius - 1
3384
3465
  },
3385
3466
  children: [
3386
- icon && !label && /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: icon, size: s.icon }),
3467
+ icon && !label && /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: icon, size: s.icon, color: iconColor }),
3387
3468
  effectiveAddonLabel
3388
3469
  ]
3389
3470
  }
@@ -3398,6 +3479,77 @@ var SField = react.forwardRef(function SField2({
3398
3479
  }
3399
3480
  );
3400
3481
  });
3482
+ function SPortal({
3483
+ open,
3484
+ onClose,
3485
+ onOpenChange,
3486
+ anchorRef,
3487
+ placement = "bottom",
3488
+ align = "start",
3489
+ offset = 4,
3490
+ alignOffset = 0,
3491
+ fit = false,
3492
+ maxHeight,
3493
+ autoClose = false,
3494
+ persistent = false,
3495
+ viewportPadding = 0,
3496
+ onOpenAutoFocus,
3497
+ onCloseAutoFocus,
3498
+ children,
3499
+ className,
3500
+ style,
3501
+ ariaLabel
3502
+ }) {
3503
+ const portalContainer = usePortalContainer();
3504
+ return /* @__PURE__ */ jsxRuntime.jsxs(
3505
+ RPopover2__namespace.Root,
3506
+ {
3507
+ open,
3508
+ onOpenChange: (next) => {
3509
+ onOpenChange?.(next);
3510
+ if (!next) onClose?.();
3511
+ },
3512
+ children: [
3513
+ /* @__PURE__ */ jsxRuntime.jsx(RPopover2__namespace.Anchor, { virtualRef: anchorRef }),
3514
+ /* @__PURE__ */ jsxRuntime.jsx(RPopover2__namespace.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxRuntime.jsx(
3515
+ RPopover2__namespace.Content,
3516
+ {
3517
+ side: placement,
3518
+ align,
3519
+ sideOffset: offset,
3520
+ alignOffset,
3521
+ collisionPadding: viewportPadding,
3522
+ "aria-label": ariaLabel,
3523
+ onPointerDownOutside: (e) => {
3524
+ if (persistent || anchorRef.current?.contains(e.target)) {
3525
+ e.preventDefault();
3526
+ }
3527
+ },
3528
+ onFocusOutside: (e) => {
3529
+ if (persistent || anchorRef.current?.contains(e.target)) {
3530
+ e.preventDefault();
3531
+ }
3532
+ },
3533
+ onEscapeKeyDown: persistent ? (e) => e.preventDefault() : void 0,
3534
+ onOpenAutoFocus,
3535
+ onCloseAutoFocus,
3536
+ onClick: autoClose ? () => onClose?.() : void 0,
3537
+ className: cn(
3538
+ "z-50 overflow-hidden rounded-(--cmp-listBox-radius) bg-(--cmp-listBox-bg) py-4 shadow-normal-sm",
3539
+ maxHeight != null && "overflow-y-auto",
3540
+ fit && "min-w-(--radix-popover-trigger-width)",
3541
+ FLOATING_SLIDE_ANIM,
3542
+ className
3543
+ ),
3544
+ style: { maxHeight, ...style },
3545
+ children
3546
+ }
3547
+ ) })
3548
+ ]
3549
+ }
3550
+ );
3551
+ }
3552
+ SPortal.displayName = "SPortal";
3401
3553
 
3402
3554
  // src/components/SSelect/select.config.ts
3403
3555
  function flattenOptions(options, depth = 1) {
@@ -3432,75 +3584,6 @@ function flattenOptions(options, depth = 1) {
3432
3584
  var selectableOptions = (flat) => flat.filter((o) => !o.isGroup && !o.disabled);
3433
3585
  var isHtmlLabel = (label) => /<[a-z][\s\S]*>/i.test(label);
3434
3586
  var extractText = (html) => html.replace(/<[^>]*>/g, "");
3435
-
3436
- // src/lib/sanitize-inline-html.ts
3437
- var ALLOWED_INLINE_TAGS = /* @__PURE__ */ new Set(["A", "B", "STRONG", "I", "EM", "BR", "SPAN"]);
3438
- var ALLOWED_ATTRS_BY_TAG = {
3439
- A: /* @__PURE__ */ new Set(["href", "target", "rel", "class"])
3440
- };
3441
- var COMMON_ALLOWED_ATTRS = /* @__PURE__ */ new Set(["class"]);
3442
- var SAFE_HREF_RE = /^(https?:|mailto:|tel:|\/|#|\.)/i;
3443
- var DROP_CONTENT_TAGS = /* @__PURE__ */ new Set([
3444
- "SCRIPT",
3445
- "STYLE",
3446
- "IFRAME",
3447
- "OBJECT",
3448
- "EMBED",
3449
- "META",
3450
- "LINK",
3451
- "BASE",
3452
- "NOSCRIPT",
3453
- // SVG/MATH 는 foreign-content 파싱 모드로 전환되어 재직렬화 시 다른 트리로
3454
- // 재파싱될 수 있다(mXSS 벡터). 인라인 라벨에 필요 없으므로 통째로 제거한다.
3455
- "SVG",
3456
- "MATH",
3457
- "TEMPLATE"
3458
- ]);
3459
- var escapeHtml = (value) => value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
3460
- var sanitizeNode = (node) => {
3461
- if (node.nodeType === Node.COMMENT_NODE) {
3462
- node.remove();
3463
- return;
3464
- }
3465
- if (node.nodeType !== Node.ELEMENT_NODE) return;
3466
- const element = node;
3467
- const tagName = element.tagName;
3468
- if (DROP_CONTENT_TAGS.has(tagName)) {
3469
- element.remove();
3470
- return;
3471
- }
3472
- Array.from(element.childNodes).forEach((child) => sanitizeNode(child));
3473
- if (ALLOWED_INLINE_TAGS.has(tagName)) {
3474
- if (element.hasAttribute("className")) {
3475
- const val = element.getAttribute("className");
3476
- element.removeAttribute("className");
3477
- if (!element.hasAttribute("class")) element.setAttribute("class", val);
3478
- }
3479
- const allowedAttrs = ALLOWED_ATTRS_BY_TAG[tagName] ?? COMMON_ALLOWED_ATTRS;
3480
- Array.from(element.attributes).forEach((attr) => {
3481
- if (!allowedAttrs.has(attr.name)) element.removeAttribute(attr.name);
3482
- });
3483
- if (tagName === "A") {
3484
- const href = element.getAttribute("href");
3485
- if (href !== null && href !== "" && !SAFE_HREF_RE.test(href)) element.removeAttribute("href");
3486
- if (element.hasAttribute("target")) {
3487
- element.setAttribute("rel", "noopener noreferrer");
3488
- }
3489
- }
3490
- return;
3491
- }
3492
- const parent = element.parentNode;
3493
- if (parent == null) return;
3494
- while (element.firstChild) parent.insertBefore(element.firstChild, element);
3495
- parent.removeChild(element);
3496
- };
3497
- var sanitizeInlineHtml = (value) => {
3498
- if (typeof document === "undefined") return escapeHtml(value);
3499
- const template = document.createElement("template");
3500
- template.innerHTML = value;
3501
- Array.from(template.content.childNodes).forEach((child) => sanitizeNode(child));
3502
- return template.innerHTML;
3503
- };
3504
3587
  var SEARCH_THRESHOLD = 11;
3505
3588
  var SSelect = react.forwardRef(function SSelect2({
3506
3589
  value,
@@ -3524,6 +3607,7 @@ var SSelect = react.forwardRef(function SSelect2({
3524
3607
  label,
3525
3608
  labelWidth,
3526
3609
  icon,
3610
+ iconColor,
3527
3611
  labelTooltip,
3528
3612
  addonLabel,
3529
3613
  addonAlign,
@@ -3535,18 +3619,15 @@ var SSelect = react.forwardRef(function SSelect2({
3535
3619
  }, ref) {
3536
3620
  const multi = type === "multi" || type === "multi_depth";
3537
3621
  const portalContainer = usePortalContainer();
3622
+ const insideModal = useInsideModal();
3538
3623
  const [search, setSearch] = react.useState("");
3539
3624
  const [ruleError, setRuleError] = react.useState("");
3540
3625
  const [open, setOpen] = react.useState(false);
3626
+ const [pendingOpen, setPendingOpen] = react.useState(false);
3627
+ const [focusedIndex, setFocusedIndex] = react.useState(-1);
3541
3628
  const triggerRef = react.useRef(null);
3542
- react.useImperativeHandle(
3543
- ref,
3544
- () => ({
3545
- focus: () => triggerRef.current?.focus(),
3546
- open: () => setOpen(true)
3547
- }),
3548
- []
3549
- );
3629
+ const searchInputRef = react.useRef(null);
3630
+ const listRef = react.useRef(null);
3550
3631
  const flat = react.useMemo(() => flattenOptions(options), [options]);
3551
3632
  const selectable = react.useMemo(() => selectableOptions(flat), [flat]);
3552
3633
  const hasLeaf = react.useMemo(() => flat.some((o) => !o.isGroup), [flat]);
@@ -3564,6 +3645,46 @@ var SSelect = react.forwardRef(function SSelect2({
3564
3645
  const arr = Array.isArray(value) ? value : [value];
3565
3646
  return arr.map((v) => v != null && typeof v === "object" ? v.value : v).filter((v) => v != null && v !== "").map(String);
3566
3647
  }, [value]);
3648
+ const selectedRef = react.useRef(selected);
3649
+ selectedRef.current = selected;
3650
+ const emit = (next) => {
3651
+ if (!onValueChange) return;
3652
+ const mapped = next.map(
3653
+ (v) => emitValue ? rawByValue.get(v) ?? v : optionByValue.get(v) ?? { value: v, label: v }
3654
+ );
3655
+ onValueChange(multi ? mapped : mapped[0] ?? (emitValue ? "" : null));
3656
+ };
3657
+ const commitOpen = (next) => {
3658
+ setOpen(next);
3659
+ onOpenChange?.(next);
3660
+ if (!next) {
3661
+ setPendingOpen(false);
3662
+ setSearch("");
3663
+ if (rules && rules.length > 0) {
3664
+ setRuleError(runRules(multi ? selectedRef.current : selectedRef.current[0] ?? "", rules));
3665
+ }
3666
+ }
3667
+ };
3668
+ const requestOpen = (next) => {
3669
+ if (next && insideModal && portalContainer == null) {
3670
+ setPendingOpen(true);
3671
+ return;
3672
+ }
3673
+ commitOpen(next);
3674
+ };
3675
+ react.useEffect(() => {
3676
+ if (!insideModal || portalContainer == null || !pendingOpen) return;
3677
+ setPendingOpen(false);
3678
+ commitOpen(true);
3679
+ }, [insideModal, pendingOpen, portalContainer]);
3680
+ react.useImperativeHandle(
3681
+ ref,
3682
+ () => ({
3683
+ focus: () => triggerRef.current?.focus(),
3684
+ open: () => requestOpen(true)
3685
+ }),
3686
+ [insideModal, portalContainer]
3687
+ );
3567
3688
  useFormField({
3568
3689
  name,
3569
3690
  validate: () => {
@@ -3573,13 +3694,6 @@ var SSelect = react.forwardRef(function SSelect2({
3573
3694
  },
3574
3695
  resetValidation: () => setRuleError("")
3575
3696
  });
3576
- const emit = (next) => {
3577
- if (!onValueChange) return;
3578
- const mapped = next.map(
3579
- (v) => emitValue ? rawByValue.get(v) ?? v : optionByValue.get(v) ?? { value: v, label: v }
3580
- );
3581
- onValueChange(multi ? mapped : mapped[0] ?? (emitValue ? "" : null));
3582
- };
3583
3697
  const filtered = react.useMemo(() => {
3584
3698
  if (!showSearch || search.trim() === "") return flat;
3585
3699
  const q = search.trim().toLowerCase();
@@ -3590,6 +3704,16 @@ var SSelect = react.forwardRef(function SSelect2({
3590
3704
  const allValues = selectable.map((o) => o.value);
3591
3705
  const allSelected = allValues.length > 0 && allValues.every((v) => selected.includes(v));
3592
3706
  const hasItems = filtered.some((o) => !o.isGroup);
3707
+ const navigableItems = react.useMemo(() => {
3708
+ const items = [];
3709
+ if (multi && useSelectAll && allValues.length > 0) {
3710
+ items.push({ value: "__select_all__", isSelectAll: true });
3711
+ }
3712
+ filtered.forEach((o) => {
3713
+ if (!o.isGroup && !o.disabled) items.push({ value: o.value });
3714
+ });
3715
+ return items;
3716
+ }, [allValues.length, filtered, multi, useSelectAll]);
3593
3717
  const triggerText = (() => {
3594
3718
  if (selected.length === 0) return placeholder;
3595
3719
  if (multi) {
@@ -3599,26 +3723,119 @@ var SSelect = react.forwardRef(function SSelect2({
3599
3723
  return labelByValue.get(selected[0]) ?? selected[0];
3600
3724
  })();
3601
3725
  const isPlaceholder = selected.length === 0;
3602
- const selectStore = /* @__PURE__ */ jsxRuntime.jsxs(
3603
- Ariakit__namespace.SelectProvider,
3726
+ const handleSelect = (nextValue) => {
3727
+ if (multi) {
3728
+ const next = selected.includes(nextValue) ? selected.filter((v) => v !== nextValue) : [...selected, nextValue];
3729
+ emit(next);
3730
+ return;
3731
+ }
3732
+ emit([nextValue]);
3733
+ commitOpen(false);
3734
+ };
3735
+ const handleSelectAll = () => {
3736
+ emit(allSelected ? [] : allValues);
3737
+ };
3738
+ react.useEffect(() => {
3739
+ if (!open) {
3740
+ setFocusedIndex(-1);
3741
+ return;
3742
+ }
3743
+ setFocusedIndex(navigableItems.length > 0 ? 0 : -1);
3744
+ }, [open, navigableItems.length]);
3745
+ react.useEffect(() => {
3746
+ if (!open) return;
3747
+ setFocusedIndex(navigableItems.length > 0 ? 0 : -1);
3748
+ }, [search, open, navigableItems.length]);
3749
+ react.useEffect(() => {
3750
+ if (!open || focusedIndex < 0) return;
3751
+ const list = listRef.current;
3752
+ const focusedEl = list?.querySelector('[data-select-focused="true"]');
3753
+ if (!list || !focusedEl) return;
3754
+ const listRect = list.getBoundingClientRect();
3755
+ const itemRect = focusedEl.getBoundingClientRect();
3756
+ if (itemRect.top < listRect.top) {
3757
+ list.scrollTop += itemRect.top - listRect.top;
3758
+ } else if (itemRect.bottom > listRect.bottom) {
3759
+ list.scrollTop += itemRect.bottom - listRect.bottom;
3760
+ }
3761
+ }, [focusedIndex, open]);
3762
+ const moveFocusedIndex = (direction) => {
3763
+ if (!open || navigableItems.length === 0) return;
3764
+ setFocusedIndex((current) => {
3765
+ if (direction === "next") {
3766
+ return current < 0 ? 0 : (current + 1) % navigableItems.length;
3767
+ }
3768
+ return current <= 0 ? navigableItems.length - 1 : current - 1;
3769
+ });
3770
+ };
3771
+ const selectFocusedItem = () => {
3772
+ if (focusedIndex < 0 || focusedIndex >= navigableItems.length) return;
3773
+ const focused = navigableItems[focusedIndex];
3774
+ if (focused.isSelectAll) {
3775
+ handleSelectAll();
3776
+ return;
3777
+ }
3778
+ handleSelect(focused.value);
3779
+ };
3780
+ const handleKeyDown = (event) => {
3781
+ if (!open) {
3782
+ if ((event.key === "ArrowDown" || event.key === "ArrowUp") && !effectiveDisabled) {
3783
+ event.preventDefault();
3784
+ requestOpen(true);
3785
+ }
3786
+ return;
3787
+ }
3788
+ if (event.key === "ArrowDown") {
3789
+ event.preventDefault();
3790
+ moveFocusedIndex("next");
3791
+ return;
3792
+ }
3793
+ if (event.key === "ArrowUp") {
3794
+ event.preventDefault();
3795
+ moveFocusedIndex("prev");
3796
+ return;
3797
+ }
3798
+ if (event.key === "Enter") {
3799
+ event.preventDefault();
3800
+ selectFocusedItem();
3801
+ return;
3802
+ }
3803
+ if (event.key === "Escape") {
3804
+ event.preventDefault();
3805
+ commitOpen(false);
3806
+ }
3807
+ };
3808
+ return /* @__PURE__ */ jsxRuntime.jsxs(
3809
+ SField,
3604
3810
  {
3605
- animated: portalContainer == null ? 150 : false,
3606
- value: multi ? selected : selected[0] ?? "",
3607
- setValue: (v) => emit(Array.isArray(v) ? v : v === "" ? [] : [v]),
3608
- open,
3609
- setOpen: (o) => {
3610
- setOpen(o);
3611
- onOpenChange?.(o);
3612
- if (!o && rules && rules.length > 0) {
3613
- setRuleError(runRules(multi ? selected : selected[0] ?? "", rules));
3614
- }
3615
- },
3811
+ label,
3812
+ labelWidth,
3813
+ icon,
3814
+ iconColor,
3815
+ labelTooltip,
3816
+ labelTooltipProps,
3817
+ addonLabel,
3818
+ addonAlign,
3819
+ hint,
3820
+ error: error || ruleError !== "",
3821
+ status: rules && rules.length > 0 ? ruleError !== "" ? "error" : void 0 : void 0,
3822
+ errorMessage: ruleError !== "" ? ruleError : errorMessage,
3823
+ disabled: effectiveDisabled,
3824
+ width,
3825
+ className,
3826
+ style,
3616
3827
  children: [
3828
+ name && /* @__PURE__ */ jsxRuntime.jsx("input", { type: "hidden", name, value: selected.join(",") }),
3617
3829
  /* @__PURE__ */ jsxRuntime.jsxs(
3618
- Ariakit__namespace.Select,
3830
+ "button",
3619
3831
  {
3620
3832
  ref: triggerRef,
3833
+ type: "button",
3621
3834
  disabled: effectiveDisabled,
3835
+ "aria-haspopup": "listbox",
3836
+ "aria-expanded": open,
3837
+ onClick: () => requestOpen(!open),
3838
+ onKeyDown: handleKeyDown,
3622
3839
  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",
3623
3840
  style: {
3624
3841
  color: effectiveDisabled ? "var(--cmp-select-text-disabled)" : isPlaceholder ? "var(--cmp-select-icon-default)" : "var(--cmp-select-text-default)"
@@ -3643,174 +3860,229 @@ var SSelect = react.forwardRef(function SSelect2({
3643
3860
  ]
3644
3861
  }
3645
3862
  ),
3646
- /* @__PURE__ */ jsxRuntime.jsxs(
3647
- Ariakit__namespace.SelectPopover,
3863
+ /* @__PURE__ */ jsxRuntime.jsx(
3864
+ SPortal,
3648
3865
  {
3649
- portal: true,
3650
- portalElement: portalContainer,
3651
- gutter: 4,
3652
- sameWidth: !dropdownWidth,
3653
- 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",
3866
+ open,
3867
+ onClose: () => commitOpen(false),
3868
+ onOpenChange: (next) => {
3869
+ if (next) requestOpen(true);
3870
+ },
3871
+ anchorRef: triggerRef,
3872
+ placement: "bottom",
3873
+ align: "start",
3874
+ offset: 4,
3875
+ fit: !dropdownWidth,
3876
+ onOpenAutoFocus: (event) => {
3877
+ if (!showSearch) {
3878
+ event.preventDefault();
3879
+ return;
3880
+ }
3881
+ event.preventDefault();
3882
+ requestAnimationFrame(() => searchInputRef.current?.focus());
3883
+ },
3884
+ onCloseAutoFocus: (event) => {
3885
+ event.preventDefault();
3886
+ triggerRef.current?.focus();
3887
+ },
3888
+ className: "rounded-[var(--cmp-listBox-radius)] bg-[var(--cmp-listBox-bg)] py-[4px] shadow-[2px_2px_12px_2px_rgba(0,0,0,0.1)]",
3654
3889
  style: {
3655
- maxHeight: dropdownHeight,
3656
3890
  ...dropdownWidth ? { width: dropdownWidth } : {},
3657
3891
  maxWidth: maxDropdownWidth
3658
3892
  },
3659
- children: [
3660
- showSearch && // 원본 sd-select-list-item-search: sticky + 검색 아이콘 + 테두리 input + focus-within + clear
3661
- /* @__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: [
3662
- /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: "search", size: 16, color: "var(--color-grey-70)", className: "shrink-0" }),
3663
- /* @__PURE__ */ jsxRuntime.jsx(
3664
- Ariakit__namespace.Combobox,
3665
- {
3666
- autoSelect: true,
3667
- placeholder: "\uAC80\uC0C9",
3668
- 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)]"
3669
- }
3670
- ),
3671
- search !== "" && /* @__PURE__ */ jsxRuntime.jsx(
3672
- SGhostButton,
3673
- {
3674
- icon: "close",
3675
- size: "xxs",
3676
- intent: "default",
3677
- ariaLabel: "\uAC80\uC0C9\uC5B4 \uC9C0\uC6B0\uAE30",
3678
- onClick: () => setSearch(""),
3679
- className: "shrink-0"
3680
- }
3681
- )
3682
- ] }) }),
3683
- multi && useSelectAll && /* @__PURE__ */ jsxRuntime.jsx(
3684
- "button",
3685
- {
3686
- type: "button",
3687
- 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)]",
3688
- onClick: () => emit(allSelected ? [] : allValues),
3689
- children: allSelectedLabel
3690
- }
3691
- ),
3692
- hasItems ? /* @__PURE__ */ jsxRuntime.jsx(ItemList, { items: filtered, multi, selected, useSearch: showSearch }) : (
3693
- // 원본 sd-select-listbox__empty (표시할 항목이 없을 때)
3694
- /* @__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." })
3695
- )
3696
- ]
3893
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
3894
+ "div",
3895
+ {
3896
+ ref: listRef,
3897
+ 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)]",
3898
+ style: { maxHeight: dropdownHeight },
3899
+ onMouseLeave: () => setFocusedIndex(-1),
3900
+ children: [
3901
+ 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: [
3902
+ /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: "search", size: 16, color: "var(--color-grey-70)", className: "shrink-0" }),
3903
+ /* @__PURE__ */ jsxRuntime.jsx(
3904
+ "input",
3905
+ {
3906
+ ref: searchInputRef,
3907
+ value: search,
3908
+ onChange: (e) => setSearch(e.target.value),
3909
+ onKeyDown: handleKeyDown,
3910
+ placeholder: "\uAC80\uC0C9",
3911
+ 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)]"
3912
+ }
3913
+ ),
3914
+ search !== "" && /* @__PURE__ */ jsxRuntime.jsx(
3915
+ SGhostButton,
3916
+ {
3917
+ icon: "close",
3918
+ size: "xxs",
3919
+ intent: "default",
3920
+ ariaLabel: "\uAC80\uC0C9\uC5B4 \uC9C0\uC6B0\uAE30",
3921
+ onClick: () => setSearch(""),
3922
+ className: "shrink-0"
3923
+ }
3924
+ )
3925
+ ] }) }),
3926
+ hasItems ? /* @__PURE__ */ jsxRuntime.jsx(
3927
+ ItemList,
3928
+ {
3929
+ items: filtered,
3930
+ multi,
3931
+ selected,
3932
+ focusedValue: navigableItems[focusedIndex]?.isSelectAll ? "__select_all__" : navigableItems[focusedIndex]?.value,
3933
+ showSelectAll: multi && useSelectAll && allValues.length > 0,
3934
+ selectAllLabel: allSelectedLabel,
3935
+ selectAllSelected: allSelected,
3936
+ onSelectAll: handleSelectAll,
3937
+ onSelect: handleSelect,
3938
+ onHoverValue: (value2) => {
3939
+ const index = navigableItems.findIndex(
3940
+ (item) => item.isSelectAll ? value2 === "__select_all__" : item.value === value2
3941
+ );
3942
+ if (index >= 0) setFocusedIndex(index);
3943
+ }
3944
+ }
3945
+ ) : /* @__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." })
3946
+ ]
3947
+ }
3948
+ )
3697
3949
  }
3698
3950
  )
3699
3951
  ]
3700
3952
  }
3701
3953
  );
3702
- const control = showSearch ? /* @__PURE__ */ jsxRuntime.jsx(Ariakit__namespace.ComboboxProvider, { value: search, setValue: setSearch, resetValueOnHide: true, children: selectStore }) : selectStore;
3703
- return /* @__PURE__ */ jsxRuntime.jsxs(
3704
- SField,
3705
- {
3706
- label,
3707
- labelWidth,
3708
- icon,
3709
- labelTooltip,
3710
- labelTooltipProps,
3711
- addonLabel,
3712
- addonAlign,
3713
- hint,
3714
- error: error || ruleError !== "",
3715
- status: rules && rules.length > 0 ? ruleError !== "" ? "error" : void 0 : void 0,
3716
- errorMessage: ruleError !== "" ? ruleError : errorMessage,
3717
- disabled: effectiveDisabled,
3718
- width,
3719
- className,
3720
- style,
3721
- children: [
3722
- name && /* @__PURE__ */ jsxRuntime.jsx("input", { type: "hidden", name, value: selected.join(",") }),
3723
- control
3724
- ]
3725
- }
3726
- );
3727
3954
  });
3728
3955
  SSelect.displayName = "SSelect";
3729
3956
  function ItemList({
3730
3957
  items,
3731
3958
  multi,
3732
3959
  selected,
3733
- useSearch
3960
+ focusedValue,
3961
+ showSelectAll,
3962
+ selectAllLabel,
3963
+ selectAllSelected,
3964
+ onSelectAll,
3965
+ onSelect,
3966
+ onHoverValue
3734
3967
  }) {
3735
3968
  const itemBase = cn(
3736
- // group: 체크박스 inverse(group-data-[active-item])를 위해 필요
3737
- "group flex cursor-pointer select-none items-center gap-[8px] text-[12px] leading-[20px] outline-none",
3969
+ "group flex w-full cursor-pointer select-none items-center gap-[8px] border-none bg-transparent text-left text-[12px] leading-[20px] outline-none",
3738
3970
  "text-[color:var(--cmp-listItem-content-default)]",
3739
- // Ariakit은 disabled 항목에 aria-disabled를 붙이므로 aria-disabled 변형으로 스타일 (data-disabled 아님)
3740
- "aria-disabled:cursor-not-allowed aria-disabled:text-[color:var(--cmp-listItem-content-disabled)]"
3741
- );
3742
- const activeClass = "data-[active-item]:bg-[var(--cmp-listItem-bg-hover)] data-[active-item]:text-[color:var(--cmp-listItem-content-hover)]";
3743
- return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: items.map((o) => {
3744
- const padLeft = o.depth === 1 ? 12 : o.depth === 2 ? 20 : 28;
3745
- if (o.isGroup) {
3746
- const isDepth1 = o.depth === 1;
3747
- return /* @__PURE__ */ jsxRuntime.jsx(
3748
- "div",
3749
- {
3750
- className: cn(
3751
- "select-none py-[4px] pr-[12px] text-[12px] leading-[20px] text-[color:var(--cmp-listItem-content-default)]",
3752
- 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",
3753
- // 하위가 전부 비활성이면(select.config 에서 bubble-up) 어미 그룹도 비활성 처리:
3754
- // 텍스트는 회색(#888), depth1 그룹은 파란 배경 대신 회색(#EEE)으로 (depth2 는 #F6F6F6 유지)
3755
- o.disabled && "text-[color:var(--cmp-listItem-content-disabled)]",
3756
- o.disabled && isDepth1 && "bg-[var(--cmp-listItem-depth1-bg-disabled)]"
3757
- ),
3758
- style: { paddingLeft: padLeft },
3759
- children: o.label
3760
- },
3761
- o.value
3762
- );
3763
- }
3764
- const isSelected = selected.includes(o.value);
3765
- return /* @__PURE__ */ jsxRuntime.jsxs(
3766
- Ariakit__namespace.SelectItem,
3971
+ "disabled:cursor-not-allowed disabled:text-[color:var(--cmp-listItem-content-disabled)]"
3972
+ );
3973
+ 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)]";
3974
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { role: "listbox", "aria-multiselectable": multi || void 0, children: [
3975
+ showSelectAll && /* @__PURE__ */ jsxRuntime.jsxs(
3976
+ "button",
3767
3977
  {
3768
- value: o.value,
3769
- disabled: o.disabled,
3978
+ type: "button",
3979
+ "data-select-focused": focusedValue === "__select_all__" ? "true" : void 0,
3980
+ "data-select-active": focusedValue === "__select_all__" ? "true" : void 0,
3770
3981
  className: cn(
3771
3982
  itemBase,
3772
- isSelected && !multi && "font-bold text-[color:var(--cmp-listItem-content-selected)]",
3773
- activeClass
3983
+ focusedValue === "__select_all__" && "bg-[var(--cmp-listItem-bg-hover)] text-[color:var(--cmp-listItem-content-hover)]"
3774
3984
  ),
3775
- style: { paddingTop: 4, paddingBottom: 4, paddingRight: 12, paddingLeft: padLeft },
3776
- render: useSearch ? /* @__PURE__ */ jsxRuntime.jsx(Ariakit__namespace.ComboboxItem, {}) : void 0,
3985
+ style: { paddingTop: 4, paddingBottom: 4, paddingRight: 12, paddingLeft: 12 },
3986
+ onMouseEnter: () => onHoverValue("__select_all__"),
3987
+ onClick: onSelectAll,
3777
3988
  children: [
3778
- multi && // hover(active) 시 checkbox inverse — 파란 hover 배경 위에서 checked/unchecked 모두 흰 테두리 + 투명 배경
3779
3989
  /* @__PURE__ */ jsxRuntime.jsx(
3780
3990
  "span",
3781
3991
  {
3782
3992
  className: cn(
3783
3993
  "flex h-[16px] w-[16px] flex-shrink-0 items-center justify-center rounded-[2px] border border-solid",
3784
- o.disabled ? (
3785
- // 비활성: 회색 채움(#E1E1E1) + 회색 테두리(#CCCCCC)
3786
- "border-[color:var(--cmp-checkbox-border-disabled)] bg-[var(--cmp-checkbox-bg-disabled)]"
3787
- ) : cn(
3788
- "group-data-[active-item]:border-[color:var(--cmp-checkbox-checked-border-inverse)]",
3789
- 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"
3790
- )
3994
+ 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)]"
3791
3995
  ),
3792
- children: isSelected && /* @__PURE__ */ jsxRuntime.jsx(
3996
+ children: selectAllSelected && /* @__PURE__ */ jsxRuntime.jsx(
3793
3997
  SIcon,
3794
3998
  {
3795
3999
  name: "checkboxCheck",
3796
4000
  size: 12,
3797
- color: o.disabled ? "var(--cmp-checkbox-checked-icon-disabled)" : "var(--cmp-checkbox-checked-icon-default)"
4001
+ color: "var(--cmp-checkbox-checked-icon-default)"
3798
4002
  }
3799
4003
  )
3800
4004
  }
3801
4005
  ),
3802
- isHtmlLabel(o.label) ? /* @__PURE__ */ jsxRuntime.jsx(
3803
- "span",
3804
- {
3805
- className: "flex-1 truncate",
3806
- dangerouslySetInnerHTML: { __html: sanitizeInlineHtml(o.label) }
3807
- }
3808
- ) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1 truncate", children: o.label })
4006
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1 truncate", children: selectAllLabel })
3809
4007
  ]
3810
- },
3811
- o.value
3812
- );
3813
- }) });
4008
+ }
4009
+ ),
4010
+ items.map((o) => {
4011
+ const padLeft = o.depth === 1 ? 12 : o.depth === 2 ? 20 : 28;
4012
+ if (o.isGroup) {
4013
+ const isDepth1 = o.depth === 1;
4014
+ return /* @__PURE__ */ jsxRuntime.jsx(
4015
+ "div",
4016
+ {
4017
+ className: cn(
4018
+ "select-none py-[4px] pr-[12px] text-[12px] leading-[20px] text-[color:var(--cmp-listItem-content-default)]",
4019
+ 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",
4020
+ // 하위가 전부 비활성이면(select.config 에서 bubble-up) 어미 그룹도 비활성 처리:
4021
+ // 텍스트는 회색(#888), depth1 그룹은 파란 배경 대신 회색(#EEE)으로 (depth2 는 #F6F6F6 유지)
4022
+ o.disabled && "cursor-not-allowed text-[color:var(--cmp-listItem-content-disabled)]",
4023
+ o.disabled && isDepth1 && "bg-[var(--cmp-listItem-depth1-bg-disabled)]"
4024
+ ),
4025
+ style: { paddingLeft: padLeft },
4026
+ children: o.label
4027
+ },
4028
+ o.value
4029
+ );
4030
+ }
4031
+ const isSelected = selected.includes(o.value);
4032
+ return /* @__PURE__ */ jsxRuntime.jsxs(
4033
+ "button",
4034
+ {
4035
+ type: "button",
4036
+ disabled: o.disabled,
4037
+ role: "option",
4038
+ "aria-selected": isSelected,
4039
+ "data-select-focused": focusedValue === o.value ? "true" : void 0,
4040
+ "data-select-active": focusedValue === o.value ? "true" : void 0,
4041
+ className: cn(
4042
+ itemBase,
4043
+ isSelected && !multi && "font-bold text-[color:var(--cmp-listItem-content-selected)]",
4044
+ focusedValue === o.value && "bg-[var(--cmp-listItem-bg-hover)] text-[color:var(--cmp-listItem-content-hover)]",
4045
+ activeClass
4046
+ ),
4047
+ style: { paddingTop: 4, paddingBottom: 4, paddingRight: 12, paddingLeft: padLeft },
4048
+ onMouseEnter: () => onHoverValue(o.value),
4049
+ onClick: () => onSelect(o.value),
4050
+ children: [
4051
+ multi && /* @__PURE__ */ jsxRuntime.jsx(
4052
+ "span",
4053
+ {
4054
+ className: cn(
4055
+ "flex h-[16px] w-[16px] flex-shrink-0 items-center justify-center rounded-[2px] border border-solid",
4056
+ o.disabled ? (
4057
+ // 비활성: 회색 채움(#E1E1E1) + 회색 테두리(#CCCCCC)
4058
+ "cursor-not-allowed border-[color:var(--cmp-checkbox-border-disabled)] bg-[var(--cmp-checkbox-bg-disabled)]"
4059
+ ) : cn(
4060
+ 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)]"
4061
+ )
4062
+ ),
4063
+ children: isSelected && /* @__PURE__ */ jsxRuntime.jsx(
4064
+ SIcon,
4065
+ {
4066
+ name: "checkboxCheck",
4067
+ size: 12,
4068
+ color: o.disabled ? "var(--cmp-checkbox-checked-icon-disabled)" : "var(--cmp-checkbox-checked-icon-default)"
4069
+ }
4070
+ )
4071
+ }
4072
+ ),
4073
+ isHtmlLabel(o.label) ? /* @__PURE__ */ jsxRuntime.jsx(
4074
+ "span",
4075
+ {
4076
+ className: "flex-1 truncate",
4077
+ dangerouslySetInnerHTML: { __html: sanitizeInlineHtml(o.label) }
4078
+ }
4079
+ ) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1 truncate", children: o.label })
4080
+ ]
4081
+ },
4082
+ o.value
4083
+ );
4084
+ })
4085
+ ] });
3814
4086
  }
3815
4087
 
3816
4088
  exports.SSelect = SSelect;