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,8 +6,8 @@ var clsx = require('clsx');
6
6
  var tailwindMerge = require('tailwind-merge');
7
7
  var jsxRuntime = require('react/jsx-runtime');
8
8
  var RPopover2 = require('@radix-ui/react-popover');
9
- var Ariakit = require('@ariakit/react');
10
9
  var RCheckbox = require('@radix-ui/react-checkbox');
10
+ var Ariakit = require('@ariakit/react');
11
11
 
12
12
  function _interopNamespace(e) {
13
13
  if (e && e.__esModule) return e;
@@ -28,8 +28,8 @@ function _interopNamespace(e) {
28
28
  }
29
29
 
30
30
  var RPopover2__namespace = /*#__PURE__*/_interopNamespace(RPopover2);
31
- var Ariakit__namespace = /*#__PURE__*/_interopNamespace(Ariakit);
32
31
  var RCheckbox__namespace = /*#__PURE__*/_interopNamespace(RCheckbox);
32
+ var Ariakit__namespace = /*#__PURE__*/_interopNamespace(Ariakit);
33
33
 
34
34
  var twMerge = tailwindMerge.extendTailwindMerge({
35
35
  extend: {
@@ -2897,10 +2897,15 @@ var FLOATING_SLIDE_ANIM = [
2897
2897
  "data-[state=closed]:data-[side=right]:animate-floating-out-right"
2898
2898
  ].join(" ");
2899
2899
  var PortalContainerContext = react.createContext(null);
2900
+ var InsideModalContext = react.createContext(false);
2900
2901
  PortalContainerContext.Provider;
2902
+ InsideModalContext.Provider;
2901
2903
  function usePortalContainer() {
2902
2904
  return react.useContext(PortalContainerContext) ?? void 0;
2903
2905
  }
2906
+ function useInsideModal() {
2907
+ return react.useContext(InsideModalContext);
2908
+ }
2904
2909
 
2905
2910
  // src/components/STag/tag.config.ts
2906
2911
  var TAG_SIZE_CONFIG = {
@@ -3424,6 +3429,7 @@ var SField = react.forwardRef(function SField2({
3424
3429
  name,
3425
3430
  label = "",
3426
3431
  icon,
3432
+ iconColor,
3427
3433
  labelTooltip = "",
3428
3434
  labelTooltipProps,
3429
3435
  labelWidth,
@@ -3501,7 +3507,15 @@ var SField = react.forwardRef(function SField2({
3501
3507
  lineHeight: `${s.lineHeight}px`
3502
3508
  },
3503
3509
  children: [
3504
- icon && /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: icon, size: s.icon, style: { marginRight: s.labelGap } }),
3510
+ icon && /* @__PURE__ */ jsxRuntime.jsx(
3511
+ SIcon,
3512
+ {
3513
+ name: icon,
3514
+ size: s.icon,
3515
+ color: iconColor,
3516
+ style: { marginRight: s.labelGap }
3517
+ }
3518
+ ),
3505
3519
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 overflow-hidden text-ellipsis whitespace-nowrap", children: label }),
3506
3520
  (labelTooltip !== "" || labelTooltipProps) && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "shrink-0", style: { marginLeft: s.labelGap }, children: /* @__PURE__ */ jsxRuntime.jsx(
3507
3521
  STooltip,
@@ -3551,7 +3565,7 @@ var SField = react.forwardRef(function SField2({
3551
3565
  borderBottomLeftRadius: s.radius - 1
3552
3566
  },
3553
3567
  children: [
3554
- icon && !label && /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: icon, size: s.icon }),
3568
+ icon && !label && /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: icon, size: s.icon, color: iconColor }),
3555
3569
  effectiveAddonLabel
3556
3570
  ]
3557
3571
  }
@@ -3577,6 +3591,7 @@ var SInput = react.forwardRef(function SInput2({
3577
3591
  type = "text",
3578
3592
  size = "sm",
3579
3593
  icon,
3594
+ iconColor,
3580
3595
  prefix,
3581
3596
  suffix,
3582
3597
  clearable = false,
@@ -3630,6 +3645,7 @@ var SInput = react.forwardRef(function SInput2({
3630
3645
  labelTooltip,
3631
3646
  labelTooltipProps,
3632
3647
  icon,
3648
+ iconColor,
3633
3649
  size,
3634
3650
  hint,
3635
3651
  error: resolvedError,
@@ -3731,6 +3747,7 @@ var STextarea = react.forwardRef(function STextarea2({
3731
3747
  label,
3732
3748
  labelWidth,
3733
3749
  icon,
3750
+ iconColor,
3734
3751
  labelTooltip,
3735
3752
  labelTooltipProps,
3736
3753
  addonLabel,
@@ -3767,6 +3784,7 @@ var STextarea = react.forwardRef(function STextarea2({
3767
3784
  label,
3768
3785
  labelWidth,
3769
3786
  icon,
3787
+ iconColor,
3770
3788
  labelTooltip,
3771
3789
  labelTooltipProps,
3772
3790
  addonLabel,
@@ -3899,6 +3917,7 @@ var SNumberInput = react.forwardRef(function SNumberInput2({
3899
3917
  label,
3900
3918
  labelWidth,
3901
3919
  icon,
3920
+ iconColor,
3902
3921
  labelTooltip,
3903
3922
  labelTooltipProps,
3904
3923
  addonLabel,
@@ -4057,6 +4076,7 @@ var SNumberInput = react.forwardRef(function SNumberInput2({
4057
4076
  label,
4058
4077
  labelWidth,
4059
4078
  icon,
4079
+ iconColor,
4060
4080
  labelTooltip,
4061
4081
  labelTooltipProps,
4062
4082
  addonLabel,
@@ -4142,40 +4162,6 @@ var SNumberInput = react.forwardRef(function SNumberInput2({
4142
4162
  );
4143
4163
  });
4144
4164
 
4145
- // src/components/SSelect/select.config.ts
4146
- function flattenOptions(options, depth = 1) {
4147
- const out = [];
4148
- for (const o of options) {
4149
- const hasChildren = Array.isArray(o.children) && o.children.length > 0;
4150
- if (hasChildren) {
4151
- const childFlat = flattenOptions(o.children, depth + 1);
4152
- const allChildrenDisabled = childFlat.every((c) => c.disabled);
4153
- out.push({
4154
- value: String(o.value),
4155
- label: o.label,
4156
- disabled: !!o.disabled || allChildrenDisabled,
4157
- depth,
4158
- isGroup: true,
4159
- raw: o.value
4160
- });
4161
- out.push(...childFlat);
4162
- } else {
4163
- out.push({
4164
- value: String(o.value),
4165
- label: o.label,
4166
- disabled: !!o.disabled,
4167
- depth,
4168
- isGroup: false,
4169
- raw: o.value
4170
- });
4171
- }
4172
- }
4173
- return out;
4174
- }
4175
- var selectableOptions = (flat) => flat.filter((o) => !o.isGroup && !o.disabled);
4176
- var isHtmlLabel = (label) => /<[a-z][\s\S]*>/i.test(label);
4177
- var extractText = (html) => html.replace(/<[^>]*>/g, "");
4178
-
4179
4165
  // src/lib/sanitize-inline-html.ts
4180
4166
  var ALLOWED_INLINE_TAGS = /* @__PURE__ */ new Set(["A", "B", "STRONG", "I", "EM", "BR", "SPAN"]);
4181
4167
  var ALLOWED_ATTRS_BY_TAG = {
@@ -4244,6 +4230,111 @@ var sanitizeInlineHtml = (value) => {
4244
4230
  Array.from(template.content.childNodes).forEach((child) => sanitizeNode(child));
4245
4231
  return template.innerHTML;
4246
4232
  };
4233
+ function SPortal({
4234
+ open,
4235
+ onClose,
4236
+ onOpenChange,
4237
+ anchorRef,
4238
+ placement = "bottom",
4239
+ align = "start",
4240
+ offset = 4,
4241
+ alignOffset = 0,
4242
+ fit = false,
4243
+ maxHeight,
4244
+ autoClose = false,
4245
+ persistent = false,
4246
+ viewportPadding = 0,
4247
+ onOpenAutoFocus,
4248
+ onCloseAutoFocus,
4249
+ children,
4250
+ className,
4251
+ style,
4252
+ ariaLabel
4253
+ }) {
4254
+ const portalContainer = usePortalContainer();
4255
+ return /* @__PURE__ */ jsxRuntime.jsxs(
4256
+ RPopover2__namespace.Root,
4257
+ {
4258
+ open,
4259
+ onOpenChange: (next) => {
4260
+ onOpenChange?.(next);
4261
+ if (!next) onClose?.();
4262
+ },
4263
+ children: [
4264
+ /* @__PURE__ */ jsxRuntime.jsx(RPopover2__namespace.Anchor, { virtualRef: anchorRef }),
4265
+ /* @__PURE__ */ jsxRuntime.jsx(RPopover2__namespace.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxRuntime.jsx(
4266
+ RPopover2__namespace.Content,
4267
+ {
4268
+ side: placement,
4269
+ align,
4270
+ sideOffset: offset,
4271
+ alignOffset,
4272
+ collisionPadding: viewportPadding,
4273
+ "aria-label": ariaLabel,
4274
+ onPointerDownOutside: (e) => {
4275
+ if (persistent || anchorRef.current?.contains(e.target)) {
4276
+ e.preventDefault();
4277
+ }
4278
+ },
4279
+ onFocusOutside: (e) => {
4280
+ if (persistent || anchorRef.current?.contains(e.target)) {
4281
+ e.preventDefault();
4282
+ }
4283
+ },
4284
+ onEscapeKeyDown: persistent ? (e) => e.preventDefault() : void 0,
4285
+ onOpenAutoFocus,
4286
+ onCloseAutoFocus,
4287
+ onClick: autoClose ? () => onClose?.() : void 0,
4288
+ className: cn(
4289
+ "z-50 overflow-hidden rounded-(--cmp-listBox-radius) bg-(--cmp-listBox-bg) py-4 shadow-normal-sm",
4290
+ maxHeight != null && "overflow-y-auto",
4291
+ fit && "min-w-(--radix-popover-trigger-width)",
4292
+ FLOATING_SLIDE_ANIM,
4293
+ className
4294
+ ),
4295
+ style: { maxHeight, ...style },
4296
+ children
4297
+ }
4298
+ ) })
4299
+ ]
4300
+ }
4301
+ );
4302
+ }
4303
+ SPortal.displayName = "SPortal";
4304
+
4305
+ // src/components/SSelect/select.config.ts
4306
+ function flattenOptions(options, depth = 1) {
4307
+ const out = [];
4308
+ for (const o of options) {
4309
+ const hasChildren = Array.isArray(o.children) && o.children.length > 0;
4310
+ if (hasChildren) {
4311
+ const childFlat = flattenOptions(o.children, depth + 1);
4312
+ const allChildrenDisabled = childFlat.every((c) => c.disabled);
4313
+ out.push({
4314
+ value: String(o.value),
4315
+ label: o.label,
4316
+ disabled: !!o.disabled || allChildrenDisabled,
4317
+ depth,
4318
+ isGroup: true,
4319
+ raw: o.value
4320
+ });
4321
+ out.push(...childFlat);
4322
+ } else {
4323
+ out.push({
4324
+ value: String(o.value),
4325
+ label: o.label,
4326
+ disabled: !!o.disabled,
4327
+ depth,
4328
+ isGroup: false,
4329
+ raw: o.value
4330
+ });
4331
+ }
4332
+ }
4333
+ return out;
4334
+ }
4335
+ var selectableOptions = (flat) => flat.filter((o) => !o.isGroup && !o.disabled);
4336
+ var isHtmlLabel = (label) => /<[a-z][\s\S]*>/i.test(label);
4337
+ var extractText = (html) => html.replace(/<[^>]*>/g, "");
4247
4338
  var SEARCH_THRESHOLD = 11;
4248
4339
  var SSelect = react.forwardRef(function SSelect2({
4249
4340
  value,
@@ -4267,6 +4358,7 @@ var SSelect = react.forwardRef(function SSelect2({
4267
4358
  label,
4268
4359
  labelWidth,
4269
4360
  icon,
4361
+ iconColor,
4270
4362
  labelTooltip,
4271
4363
  addonLabel,
4272
4364
  addonAlign,
@@ -4278,18 +4370,15 @@ var SSelect = react.forwardRef(function SSelect2({
4278
4370
  }, ref) {
4279
4371
  const multi = type === "multi" || type === "multi_depth";
4280
4372
  const portalContainer = usePortalContainer();
4373
+ const insideModal = useInsideModal();
4281
4374
  const [search, setSearch] = react.useState("");
4282
4375
  const [ruleError, setRuleError] = react.useState("");
4283
4376
  const [open, setOpen] = react.useState(false);
4377
+ const [pendingOpen, setPendingOpen] = react.useState(false);
4378
+ const [focusedIndex, setFocusedIndex] = react.useState(-1);
4284
4379
  const triggerRef = react.useRef(null);
4285
- react.useImperativeHandle(
4286
- ref,
4287
- () => ({
4288
- focus: () => triggerRef.current?.focus(),
4289
- open: () => setOpen(true)
4290
- }),
4291
- []
4292
- );
4380
+ const searchInputRef = react.useRef(null);
4381
+ const listRef = react.useRef(null);
4293
4382
  const flat = react.useMemo(() => flattenOptions(options), [options]);
4294
4383
  const selectable = react.useMemo(() => selectableOptions(flat), [flat]);
4295
4384
  const hasLeaf = react.useMemo(() => flat.some((o) => !o.isGroup), [flat]);
@@ -4307,6 +4396,46 @@ var SSelect = react.forwardRef(function SSelect2({
4307
4396
  const arr = Array.isArray(value) ? value : [value];
4308
4397
  return arr.map((v) => v != null && typeof v === "object" ? v.value : v).filter((v) => v != null && v !== "").map(String);
4309
4398
  }, [value]);
4399
+ const selectedRef = react.useRef(selected);
4400
+ selectedRef.current = selected;
4401
+ const emit = (next) => {
4402
+ if (!onValueChange) return;
4403
+ const mapped = next.map(
4404
+ (v) => emitValue ? rawByValue.get(v) ?? v : optionByValue.get(v) ?? { value: v, label: v }
4405
+ );
4406
+ onValueChange(multi ? mapped : mapped[0] ?? (emitValue ? "" : null));
4407
+ };
4408
+ const commitOpen = (next) => {
4409
+ setOpen(next);
4410
+ onOpenChange?.(next);
4411
+ if (!next) {
4412
+ setPendingOpen(false);
4413
+ setSearch("");
4414
+ if (rules && rules.length > 0) {
4415
+ setRuleError(runRules(multi ? selectedRef.current : selectedRef.current[0] ?? "", rules));
4416
+ }
4417
+ }
4418
+ };
4419
+ const requestOpen = (next) => {
4420
+ if (next && insideModal && portalContainer == null) {
4421
+ setPendingOpen(true);
4422
+ return;
4423
+ }
4424
+ commitOpen(next);
4425
+ };
4426
+ react.useEffect(() => {
4427
+ if (!insideModal || portalContainer == null || !pendingOpen) return;
4428
+ setPendingOpen(false);
4429
+ commitOpen(true);
4430
+ }, [insideModal, pendingOpen, portalContainer]);
4431
+ react.useImperativeHandle(
4432
+ ref,
4433
+ () => ({
4434
+ focus: () => triggerRef.current?.focus(),
4435
+ open: () => requestOpen(true)
4436
+ }),
4437
+ [insideModal, portalContainer]
4438
+ );
4310
4439
  useFormField({
4311
4440
  name,
4312
4441
  validate: () => {
@@ -4316,13 +4445,6 @@ var SSelect = react.forwardRef(function SSelect2({
4316
4445
  },
4317
4446
  resetValidation: () => setRuleError("")
4318
4447
  });
4319
- const emit = (next) => {
4320
- if (!onValueChange) return;
4321
- const mapped = next.map(
4322
- (v) => emitValue ? rawByValue.get(v) ?? v : optionByValue.get(v) ?? { value: v, label: v }
4323
- );
4324
- onValueChange(multi ? mapped : mapped[0] ?? (emitValue ? "" : null));
4325
- };
4326
4448
  const filtered = react.useMemo(() => {
4327
4449
  if (!showSearch || search.trim() === "") return flat;
4328
4450
  const q = search.trim().toLowerCase();
@@ -4333,6 +4455,16 @@ var SSelect = react.forwardRef(function SSelect2({
4333
4455
  const allValues = selectable.map((o) => o.value);
4334
4456
  const allSelected = allValues.length > 0 && allValues.every((v) => selected.includes(v));
4335
4457
  const hasItems = filtered.some((o) => !o.isGroup);
4458
+ const navigableItems = react.useMemo(() => {
4459
+ const items = [];
4460
+ if (multi && useSelectAll && allValues.length > 0) {
4461
+ items.push({ value: "__select_all__", isSelectAll: true });
4462
+ }
4463
+ filtered.forEach((o) => {
4464
+ if (!o.isGroup && !o.disabled) items.push({ value: o.value });
4465
+ });
4466
+ return items;
4467
+ }, [allValues.length, filtered, multi, useSelectAll]);
4336
4468
  const triggerText = (() => {
4337
4469
  if (selected.length === 0) return placeholder;
4338
4470
  if (multi) {
@@ -4342,26 +4474,119 @@ var SSelect = react.forwardRef(function SSelect2({
4342
4474
  return labelByValue.get(selected[0]) ?? selected[0];
4343
4475
  })();
4344
4476
  const isPlaceholder = selected.length === 0;
4345
- const selectStore = /* @__PURE__ */ jsxRuntime.jsxs(
4346
- Ariakit__namespace.SelectProvider,
4477
+ const handleSelect = (nextValue) => {
4478
+ if (multi) {
4479
+ const next = selected.includes(nextValue) ? selected.filter((v) => v !== nextValue) : [...selected, nextValue];
4480
+ emit(next);
4481
+ return;
4482
+ }
4483
+ emit([nextValue]);
4484
+ commitOpen(false);
4485
+ };
4486
+ const handleSelectAll = () => {
4487
+ emit(allSelected ? [] : allValues);
4488
+ };
4489
+ react.useEffect(() => {
4490
+ if (!open) {
4491
+ setFocusedIndex(-1);
4492
+ return;
4493
+ }
4494
+ setFocusedIndex(navigableItems.length > 0 ? 0 : -1);
4495
+ }, [open, navigableItems.length]);
4496
+ react.useEffect(() => {
4497
+ if (!open) return;
4498
+ setFocusedIndex(navigableItems.length > 0 ? 0 : -1);
4499
+ }, [search, open, navigableItems.length]);
4500
+ react.useEffect(() => {
4501
+ if (!open || focusedIndex < 0) return;
4502
+ const list = listRef.current;
4503
+ const focusedEl = list?.querySelector('[data-select-focused="true"]');
4504
+ if (!list || !focusedEl) return;
4505
+ const listRect = list.getBoundingClientRect();
4506
+ const itemRect = focusedEl.getBoundingClientRect();
4507
+ if (itemRect.top < listRect.top) {
4508
+ list.scrollTop += itemRect.top - listRect.top;
4509
+ } else if (itemRect.bottom > listRect.bottom) {
4510
+ list.scrollTop += itemRect.bottom - listRect.bottom;
4511
+ }
4512
+ }, [focusedIndex, open]);
4513
+ const moveFocusedIndex = (direction) => {
4514
+ if (!open || navigableItems.length === 0) return;
4515
+ setFocusedIndex((current) => {
4516
+ if (direction === "next") {
4517
+ return current < 0 ? 0 : (current + 1) % navigableItems.length;
4518
+ }
4519
+ return current <= 0 ? navigableItems.length - 1 : current - 1;
4520
+ });
4521
+ };
4522
+ const selectFocusedItem = () => {
4523
+ if (focusedIndex < 0 || focusedIndex >= navigableItems.length) return;
4524
+ const focused = navigableItems[focusedIndex];
4525
+ if (focused.isSelectAll) {
4526
+ handleSelectAll();
4527
+ return;
4528
+ }
4529
+ handleSelect(focused.value);
4530
+ };
4531
+ const handleKeyDown = (event) => {
4532
+ if (!open) {
4533
+ if ((event.key === "ArrowDown" || event.key === "ArrowUp") && !effectiveDisabled) {
4534
+ event.preventDefault();
4535
+ requestOpen(true);
4536
+ }
4537
+ return;
4538
+ }
4539
+ if (event.key === "ArrowDown") {
4540
+ event.preventDefault();
4541
+ moveFocusedIndex("next");
4542
+ return;
4543
+ }
4544
+ if (event.key === "ArrowUp") {
4545
+ event.preventDefault();
4546
+ moveFocusedIndex("prev");
4547
+ return;
4548
+ }
4549
+ if (event.key === "Enter") {
4550
+ event.preventDefault();
4551
+ selectFocusedItem();
4552
+ return;
4553
+ }
4554
+ if (event.key === "Escape") {
4555
+ event.preventDefault();
4556
+ commitOpen(false);
4557
+ }
4558
+ };
4559
+ return /* @__PURE__ */ jsxRuntime.jsxs(
4560
+ SField,
4347
4561
  {
4348
- animated: portalContainer == null ? 150 : false,
4349
- value: multi ? selected : selected[0] ?? "",
4350
- setValue: (v) => emit(Array.isArray(v) ? v : v === "" ? [] : [v]),
4351
- open,
4352
- setOpen: (o) => {
4353
- setOpen(o);
4354
- onOpenChange?.(o);
4355
- if (!o && rules && rules.length > 0) {
4356
- setRuleError(runRules(multi ? selected : selected[0] ?? "", rules));
4357
- }
4358
- },
4562
+ label,
4563
+ labelWidth,
4564
+ icon,
4565
+ iconColor,
4566
+ labelTooltip,
4567
+ labelTooltipProps,
4568
+ addonLabel,
4569
+ addonAlign,
4570
+ hint,
4571
+ error: error || ruleError !== "",
4572
+ status: rules && rules.length > 0 ? ruleError !== "" ? "error" : void 0 : void 0,
4573
+ errorMessage: ruleError !== "" ? ruleError : errorMessage,
4574
+ disabled: effectiveDisabled,
4575
+ width,
4576
+ className,
4577
+ style,
4359
4578
  children: [
4579
+ name && /* @__PURE__ */ jsxRuntime.jsx("input", { type: "hidden", name, value: selected.join(",") }),
4360
4580
  /* @__PURE__ */ jsxRuntime.jsxs(
4361
- Ariakit__namespace.Select,
4581
+ "button",
4362
4582
  {
4363
4583
  ref: triggerRef,
4584
+ type: "button",
4364
4585
  disabled: effectiveDisabled,
4586
+ "aria-haspopup": "listbox",
4587
+ "aria-expanded": open,
4588
+ onClick: () => requestOpen(!open),
4589
+ onKeyDown: handleKeyDown,
4365
4590
  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",
4366
4591
  style: {
4367
4592
  color: effectiveDisabled ? "var(--cmp-select-text-disabled)" : isPlaceholder ? "var(--cmp-select-icon-default)" : "var(--cmp-select-text-default)"
@@ -4386,178 +4611,229 @@ var SSelect = react.forwardRef(function SSelect2({
4386
4611
  ]
4387
4612
  }
4388
4613
  ),
4389
- /* @__PURE__ */ jsxRuntime.jsxs(
4390
- Ariakit__namespace.SelectPopover,
4614
+ /* @__PURE__ */ jsxRuntime.jsx(
4615
+ SPortal,
4391
4616
  {
4392
- portal: true,
4393
- portalElement: portalContainer,
4394
- gutter: 4,
4395
- sameWidth: !dropdownWidth,
4396
- 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",
4617
+ open,
4618
+ onClose: () => commitOpen(false),
4619
+ onOpenChange: (next) => {
4620
+ if (next) requestOpen(true);
4621
+ },
4622
+ anchorRef: triggerRef,
4623
+ placement: "bottom",
4624
+ align: "start",
4625
+ offset: 4,
4626
+ fit: !dropdownWidth,
4627
+ onOpenAutoFocus: (event) => {
4628
+ if (!showSearch) {
4629
+ event.preventDefault();
4630
+ return;
4631
+ }
4632
+ event.preventDefault();
4633
+ requestAnimationFrame(() => searchInputRef.current?.focus());
4634
+ },
4635
+ onCloseAutoFocus: (event) => {
4636
+ event.preventDefault();
4637
+ triggerRef.current?.focus();
4638
+ },
4639
+ className: "rounded-[var(--cmp-listBox-radius)] bg-[var(--cmp-listBox-bg)] py-[4px] shadow-[2px_2px_12px_2px_rgba(0,0,0,0.1)]",
4397
4640
  style: {
4398
- maxHeight: dropdownHeight,
4399
4641
  ...dropdownWidth ? { width: dropdownWidth } : {},
4400
4642
  maxWidth: maxDropdownWidth
4401
4643
  },
4402
- children: [
4403
- showSearch && // 원본 sd-select-list-item-search: sticky + 검색 아이콘 + 테두리 input + focus-within + clear
4404
- /* @__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: [
4405
- /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: "search", size: 16, color: "var(--color-grey-70)", className: "shrink-0" }),
4406
- /* @__PURE__ */ jsxRuntime.jsx(
4407
- Ariakit__namespace.Combobox,
4408
- {
4409
- autoSelect: true,
4410
- placeholder: "\uAC80\uC0C9",
4411
- 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)]"
4412
- }
4413
- ),
4414
- search !== "" && /* @__PURE__ */ jsxRuntime.jsx(
4415
- SGhostButton,
4416
- {
4417
- icon: "close",
4418
- size: "xxs",
4419
- intent: "default",
4420
- ariaLabel: "\uAC80\uC0C9\uC5B4 \uC9C0\uC6B0\uAE30",
4421
- onClick: () => setSearch(""),
4422
- className: "shrink-0"
4423
- }
4424
- )
4425
- ] }) }),
4426
- multi && useSelectAll && /* @__PURE__ */ jsxRuntime.jsx(
4427
- "button",
4428
- {
4429
- type: "button",
4430
- 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)]",
4431
- onClick: () => emit(allSelected ? [] : allValues),
4432
- children: allSelectedLabel
4433
- }
4434
- ),
4435
- hasItems ? /* @__PURE__ */ jsxRuntime.jsx(ItemList, { items: filtered, multi, selected, useSearch: showSearch }) : (
4436
- // 원본 sd-select-listbox__empty (표시할 항목이 없을 때)
4437
- /* @__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." })
4438
- )
4439
- ]
4644
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
4645
+ "div",
4646
+ {
4647
+ ref: listRef,
4648
+ 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)]",
4649
+ style: { maxHeight: dropdownHeight },
4650
+ onMouseLeave: () => setFocusedIndex(-1),
4651
+ children: [
4652
+ 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: [
4653
+ /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: "search", size: 16, color: "var(--color-grey-70)", className: "shrink-0" }),
4654
+ /* @__PURE__ */ jsxRuntime.jsx(
4655
+ "input",
4656
+ {
4657
+ ref: searchInputRef,
4658
+ value: search,
4659
+ onChange: (e) => setSearch(e.target.value),
4660
+ onKeyDown: handleKeyDown,
4661
+ placeholder: "\uAC80\uC0C9",
4662
+ 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)]"
4663
+ }
4664
+ ),
4665
+ search !== "" && /* @__PURE__ */ jsxRuntime.jsx(
4666
+ SGhostButton,
4667
+ {
4668
+ icon: "close",
4669
+ size: "xxs",
4670
+ intent: "default",
4671
+ ariaLabel: "\uAC80\uC0C9\uC5B4 \uC9C0\uC6B0\uAE30",
4672
+ onClick: () => setSearch(""),
4673
+ className: "shrink-0"
4674
+ }
4675
+ )
4676
+ ] }) }),
4677
+ hasItems ? /* @__PURE__ */ jsxRuntime.jsx(
4678
+ ItemList,
4679
+ {
4680
+ items: filtered,
4681
+ multi,
4682
+ selected,
4683
+ focusedValue: navigableItems[focusedIndex]?.isSelectAll ? "__select_all__" : navigableItems[focusedIndex]?.value,
4684
+ showSelectAll: multi && useSelectAll && allValues.length > 0,
4685
+ selectAllLabel: allSelectedLabel,
4686
+ selectAllSelected: allSelected,
4687
+ onSelectAll: handleSelectAll,
4688
+ onSelect: handleSelect,
4689
+ onHoverValue: (value2) => {
4690
+ const index = navigableItems.findIndex(
4691
+ (item) => item.isSelectAll ? value2 === "__select_all__" : item.value === value2
4692
+ );
4693
+ if (index >= 0) setFocusedIndex(index);
4694
+ }
4695
+ }
4696
+ ) : /* @__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." })
4697
+ ]
4698
+ }
4699
+ )
4440
4700
  }
4441
4701
  )
4442
4702
  ]
4443
4703
  }
4444
4704
  );
4445
- const control = showSearch ? /* @__PURE__ */ jsxRuntime.jsx(Ariakit__namespace.ComboboxProvider, { value: search, setValue: setSearch, resetValueOnHide: true, children: selectStore }) : selectStore;
4446
- return /* @__PURE__ */ jsxRuntime.jsxs(
4447
- SField,
4448
- {
4449
- label,
4450
- labelWidth,
4451
- icon,
4452
- labelTooltip,
4453
- labelTooltipProps,
4454
- addonLabel,
4455
- addonAlign,
4456
- hint,
4457
- error: error || ruleError !== "",
4458
- status: rules && rules.length > 0 ? ruleError !== "" ? "error" : void 0 : void 0,
4459
- errorMessage: ruleError !== "" ? ruleError : errorMessage,
4460
- disabled: effectiveDisabled,
4461
- width,
4462
- className,
4463
- style,
4464
- children: [
4465
- name && /* @__PURE__ */ jsxRuntime.jsx("input", { type: "hidden", name, value: selected.join(",") }),
4466
- control
4467
- ]
4468
- }
4469
- );
4470
4705
  });
4471
4706
  SSelect.displayName = "SSelect";
4472
4707
  function ItemList({
4473
4708
  items,
4474
4709
  multi,
4475
4710
  selected,
4476
- useSearch
4711
+ focusedValue,
4712
+ showSelectAll,
4713
+ selectAllLabel,
4714
+ selectAllSelected,
4715
+ onSelectAll,
4716
+ onSelect,
4717
+ onHoverValue
4477
4718
  }) {
4478
4719
  const itemBase = cn(
4479
- // group: 체크박스 inverse(group-data-[active-item])를 위해 필요
4480
- "group flex cursor-pointer select-none items-center gap-[8px] text-[12px] leading-[20px] outline-none",
4720
+ "group flex w-full cursor-pointer select-none items-center gap-[8px] border-none bg-transparent text-left text-[12px] leading-[20px] outline-none",
4481
4721
  "text-[color:var(--cmp-listItem-content-default)]",
4482
- // Ariakit은 disabled 항목에 aria-disabled를 붙이므로 aria-disabled 변형으로 스타일 (data-disabled 아님)
4483
- "aria-disabled:cursor-not-allowed aria-disabled:text-[color:var(--cmp-listItem-content-disabled)]"
4722
+ "disabled:cursor-not-allowed disabled:text-[color:var(--cmp-listItem-content-disabled)]"
4484
4723
  );
4485
- const activeClass = "data-[active-item]:bg-[var(--cmp-listItem-bg-hover)] data-[active-item]:text-[color:var(--cmp-listItem-content-hover)]";
4486
- return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: items.map((o) => {
4487
- const padLeft = o.depth === 1 ? 12 : o.depth === 2 ? 20 : 28;
4488
- if (o.isGroup) {
4489
- const isDepth1 = o.depth === 1;
4490
- return /* @__PURE__ */ jsxRuntime.jsx(
4491
- "div",
4492
- {
4493
- className: cn(
4494
- "select-none py-[4px] pr-[12px] text-[12px] leading-[20px] text-[color:var(--cmp-listItem-content-default)]",
4495
- 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",
4496
- // 하위가 전부 비활성이면(select.config 에서 bubble-up) 어미 그룹도 비활성 처리:
4497
- // 텍스트는 회색(#888), depth1 그룹은 파란 배경 대신 회색(#EEE)으로 (depth2 는 #F6F6F6 유지)
4498
- o.disabled && "cursor-not-allowed text-[color:var(--cmp-listItem-content-disabled)]",
4499
- o.disabled && isDepth1 && "bg-[var(--cmp-listItem-depth1-bg-disabled)]"
4500
- ),
4501
- style: { paddingLeft: padLeft },
4502
- children: o.label
4503
- },
4504
- o.value
4505
- );
4506
- }
4507
- const isSelected = selected.includes(o.value);
4508
- return /* @__PURE__ */ jsxRuntime.jsxs(
4509
- Ariakit__namespace.SelectItem,
4724
+ 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)]";
4725
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { role: "listbox", "aria-multiselectable": multi || void 0, children: [
4726
+ showSelectAll && /* @__PURE__ */ jsxRuntime.jsxs(
4727
+ "button",
4510
4728
  {
4511
- value: o.value,
4512
- disabled: o.disabled,
4513
- accessibleWhenDisabled: true,
4729
+ type: "button",
4730
+ "data-select-focused": focusedValue === "__select_all__" ? "true" : void 0,
4731
+ "data-select-active": focusedValue === "__select_all__" ? "true" : void 0,
4514
4732
  className: cn(
4515
4733
  itemBase,
4516
- isSelected && !multi && "font-bold text-[color:var(--cmp-listItem-content-selected)]",
4517
- // Ariakit은 disabled 항목도 hover 시 active(data-active-item) 처리하므로,
4518
- // 비활성 항목에는 hover 하이라이트를 적용하지 않아 회색 비활성 스타일을 유지한다.
4519
- // (원본 Stencil의 `--focused...:not(--disabled)` 규칙과 동일한 의도)
4520
- !o.disabled && activeClass
4734
+ focusedValue === "__select_all__" && "bg-[var(--cmp-listItem-bg-hover)] text-[color:var(--cmp-listItem-content-hover)]"
4521
4735
  ),
4522
- style: { paddingTop: 4, paddingBottom: 4, paddingRight: 12, paddingLeft: padLeft },
4523
- render: useSearch ? /* @__PURE__ */ jsxRuntime.jsx(Ariakit__namespace.ComboboxItem, {}) : void 0,
4736
+ style: { paddingTop: 4, paddingBottom: 4, paddingRight: 12, paddingLeft: 12 },
4737
+ onMouseEnter: () => onHoverValue("__select_all__"),
4738
+ onClick: onSelectAll,
4524
4739
  children: [
4525
- multi && // hover(active) 시 checkbox inverse — 파란 hover 배경 위에서 checked/unchecked 모두 흰 테두리 + 투명 배경
4526
4740
  /* @__PURE__ */ jsxRuntime.jsx(
4527
4741
  "span",
4528
4742
  {
4529
4743
  className: cn(
4530
4744
  "flex h-[16px] w-[16px] flex-shrink-0 items-center justify-center rounded-[2px] border border-solid",
4531
- o.disabled ? (
4532
- // 비활성: 회색 채움(#E1E1E1) + 회색 테두리(#CCCCCC)
4533
- "cursor-not-allowed border-[color:var(--cmp-checkbox-border-disabled)] bg-[var(--cmp-checkbox-bg-disabled)]"
4534
- ) : cn(
4535
- "group-data-[active-item]:border-[color:var(--cmp-checkbox-checked-border-inverse)]",
4536
- 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"
4537
- )
4745
+ 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)]"
4538
4746
  ),
4539
- children: isSelected && /* @__PURE__ */ jsxRuntime.jsx(
4747
+ children: selectAllSelected && /* @__PURE__ */ jsxRuntime.jsx(
4540
4748
  SIcon,
4541
4749
  {
4542
4750
  name: "checkboxCheck",
4543
4751
  size: 12,
4544
- color: o.disabled ? "var(--cmp-checkbox-checked-icon-disabled)" : "var(--cmp-checkbox-checked-icon-default)"
4752
+ color: "var(--cmp-checkbox-checked-icon-default)"
4545
4753
  }
4546
4754
  )
4547
4755
  }
4548
4756
  ),
4549
- isHtmlLabel(o.label) ? /* @__PURE__ */ jsxRuntime.jsx(
4550
- "span",
4551
- {
4552
- className: "flex-1 truncate",
4553
- dangerouslySetInnerHTML: { __html: sanitizeInlineHtml(o.label) }
4554
- }
4555
- ) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1 truncate", children: o.label })
4757
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1 truncate", children: selectAllLabel })
4556
4758
  ]
4557
- },
4558
- o.value
4559
- );
4560
- }) });
4759
+ }
4760
+ ),
4761
+ items.map((o) => {
4762
+ const padLeft = o.depth === 1 ? 12 : o.depth === 2 ? 20 : 28;
4763
+ if (o.isGroup) {
4764
+ const isDepth1 = o.depth === 1;
4765
+ return /* @__PURE__ */ jsxRuntime.jsx(
4766
+ "div",
4767
+ {
4768
+ className: cn(
4769
+ "select-none py-[4px] pr-[12px] text-[12px] leading-[20px] text-[color:var(--cmp-listItem-content-default)]",
4770
+ 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",
4771
+ // 하위가 전부 비활성이면(select.config 에서 bubble-up) 어미 그룹도 비활성 처리:
4772
+ // 텍스트는 회색(#888), depth1 그룹은 파란 배경 대신 회색(#EEE)으로 (depth2 는 #F6F6F6 유지)
4773
+ o.disabled && "cursor-not-allowed text-[color:var(--cmp-listItem-content-disabled)]",
4774
+ o.disabled && isDepth1 && "bg-[var(--cmp-listItem-depth1-bg-disabled)]"
4775
+ ),
4776
+ style: { paddingLeft: padLeft },
4777
+ children: o.label
4778
+ },
4779
+ o.value
4780
+ );
4781
+ }
4782
+ const isSelected = selected.includes(o.value);
4783
+ return /* @__PURE__ */ jsxRuntime.jsxs(
4784
+ "button",
4785
+ {
4786
+ type: "button",
4787
+ disabled: o.disabled,
4788
+ role: "option",
4789
+ "aria-selected": isSelected,
4790
+ "data-select-focused": focusedValue === o.value ? "true" : void 0,
4791
+ "data-select-active": focusedValue === o.value ? "true" : void 0,
4792
+ className: cn(
4793
+ itemBase,
4794
+ isSelected && !multi && "font-bold text-[color:var(--cmp-listItem-content-selected)]",
4795
+ focusedValue === o.value && "bg-[var(--cmp-listItem-bg-hover)] text-[color:var(--cmp-listItem-content-hover)]",
4796
+ activeClass
4797
+ ),
4798
+ style: { paddingTop: 4, paddingBottom: 4, paddingRight: 12, paddingLeft: padLeft },
4799
+ onMouseEnter: () => onHoverValue(o.value),
4800
+ onClick: () => onSelect(o.value),
4801
+ children: [
4802
+ multi && /* @__PURE__ */ jsxRuntime.jsx(
4803
+ "span",
4804
+ {
4805
+ className: cn(
4806
+ "flex h-[16px] w-[16px] flex-shrink-0 items-center justify-center rounded-[2px] border border-solid",
4807
+ o.disabled ? (
4808
+ // 비활성: 회색 채움(#E1E1E1) + 회색 테두리(#CCCCCC)
4809
+ "cursor-not-allowed border-[color:var(--cmp-checkbox-border-disabled)] bg-[var(--cmp-checkbox-bg-disabled)]"
4810
+ ) : cn(
4811
+ 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)]"
4812
+ )
4813
+ ),
4814
+ children: isSelected && /* @__PURE__ */ jsxRuntime.jsx(
4815
+ SIcon,
4816
+ {
4817
+ name: "checkboxCheck",
4818
+ size: 12,
4819
+ color: o.disabled ? "var(--cmp-checkbox-checked-icon-disabled)" : "var(--cmp-checkbox-checked-icon-default)"
4820
+ }
4821
+ )
4822
+ }
4823
+ ),
4824
+ isHtmlLabel(o.label) ? /* @__PURE__ */ jsxRuntime.jsx(
4825
+ "span",
4826
+ {
4827
+ className: "flex-1 truncate",
4828
+ dangerouslySetInnerHTML: { __html: sanitizeInlineHtml(o.label) }
4829
+ }
4830
+ ) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1 truncate", children: o.label })
4831
+ ]
4832
+ },
4833
+ o.value
4834
+ );
4835
+ })
4836
+ ] });
4561
4837
  }
4562
4838
  function SRadio({
4563
4839
  val,
@@ -5020,6 +5296,7 @@ function SDatePicker({
5020
5296
  label,
5021
5297
  labelWidth,
5022
5298
  icon,
5299
+ iconColor,
5023
5300
  labelTooltip,
5024
5301
  labelTooltipProps,
5025
5302
  addonLabel,
@@ -5058,6 +5335,7 @@ function SDatePicker({
5058
5335
  label,
5059
5336
  labelWidth,
5060
5337
  icon,
5338
+ iconColor,
5061
5339
  labelTooltip,
5062
5340
  labelTooltipProps,
5063
5341
  addonLabel,
@@ -5326,6 +5604,7 @@ function SDateRangePicker({
5326
5604
  label,
5327
5605
  labelWidth,
5328
5606
  icon,
5607
+ iconColor,
5329
5608
  labelTooltip,
5330
5609
  labelTooltipProps,
5331
5610
  addonLabel,
@@ -5368,6 +5647,7 @@ function SDateRangePicker({
5368
5647
  label,
5369
5648
  labelWidth,
5370
5649
  icon,
5650
+ iconColor,
5371
5651
  labelTooltip,
5372
5652
  labelTooltipProps,
5373
5653
  addonLabel,
@@ -5629,6 +5909,7 @@ function SFilePicker({
5629
5909
  label,
5630
5910
  labelWidth,
5631
5911
  icon,
5912
+ iconColor,
5632
5913
  labelTooltip,
5633
5914
  labelTooltipProps,
5634
5915
  addonLabel,
@@ -5798,7 +6079,7 @@ function SFilePicker({
5798
6079
  unmountOnHide: true,
5799
6080
  getAnchorRect: () => contentBoxRef.current?.getBoundingClientRect() ?? null,
5800
6081
  style: { minWidth: contentBoxRef.current?.offsetWidth },
5801
- className: "z-50 box-border flex flex-col items-stretch gap-[8px] rounded-[var(--cmp-listBox-radius)] bg-[var(--cmp-listBox-bg)] p-[8px] shadow-[2px_2px_12px_2px_rgba(0,0,0,0.1)]",
6082
+ className: "z-50 box-border flex flex-col items-start gap-[8px] rounded-[var(--cmp-listBox-radius)] bg-[var(--cmp-listBox-bg)] p-[8px] shadow-[2px_2px_12px_2px_rgba(0,0,0,0.1)]",
5802
6083
  children: filesArr.map((f, i) => /* @__PURE__ */ jsxRuntime.jsx(SChip, { value: f.name, editable: false, onRemove: () => removeChip(i) }, i))
5803
6084
  }
5804
6085
  )
@@ -5812,6 +6093,7 @@ function SFilePicker({
5812
6093
  label,
5813
6094
  labelWidth,
5814
6095
  icon,
6096
+ iconColor,
5815
6097
  labelTooltip,
5816
6098
  labelTooltipProps,
5817
6099
  addonLabel,
@@ -5834,21 +6116,26 @@ function SFilePicker({
5834
6116
  var cssLen2 = (v) => v == null ? void 0 : typeof v === "number" ? `${v}px` : v;
5835
6117
  function SKeyValueTable({
5836
6118
  fields = [],
6119
+ values = {},
5837
6120
  search = false,
5838
6121
  radius = "default",
5839
- dense = false,
5840
6122
  onChange,
5841
6123
  onSearch,
5842
6124
  className,
5843
6125
  style
5844
6126
  }) {
5845
6127
  if (fields.length === 0) return null;
5846
- const emit = (name, value) => onChange?.({ name, value });
5847
- const cellHeight = dense ? "var(--cmp-table-body-dense-height)" : "var(--cmp-table-keyValue-height)";
5848
- const cellPadY = dense ? "var(--cmp-table-body-paddingY-dense)" : "var(--cmp-table-keyValue-body-paddingY)";
6128
+ const emit = (name, value) => onChange?.({
6129
+ name,
6130
+ value,
6131
+ values: { ...values, [name]: value }
6132
+ });
6133
+ const cellHeight = "var(--cmp-table-keyValue-height)";
6134
+ const cellPadY = "var(--cmp-table-keyValue-body-paddingY)";
5849
6135
  const renderField = (f) => {
5850
6136
  if (f.render) return f.render;
5851
- const opt = f.options ?? {};
6137
+ const opt = { ...f.options ?? {} };
6138
+ if (Object.prototype.hasOwnProperty.call(values, f.name)) opt.value = values[f.name];
5852
6139
  const up = (v) => emit(f.name, v);
5853
6140
  switch (f.type) {
5854
6141
  case "input":
@@ -5960,7 +6247,10 @@ function SKeyValueTable({
5960
6247
  rowSpan: f.tdRowSpan && f.tdRowSpan > 1 ? f.tdRowSpan : void 0,
5961
6248
  colSpan: f.tdColSpan && f.tdColSpan > 1 ? f.tdColSpan : void 0,
5962
6249
  className: cn(
5963
- "box-border bg-[var(--sys-color-bg-frame)] px-[var(--cmp-table-keyValue-body-paddingX-default)] align-middle",
6250
+ "box-border bg-[var(--sys-color-bg-frame)] align-middle",
6251
+ // text 타입(및 미지정 기본값)은 읽기 전용 콘텐츠라 넓은 paddingX(wide),
6252
+ // 폼 필드 셀은 default paddingX 유지
6253
+ f.type == null || f.type === "text" ? "px-[var(--cmp-table-keyValue-body-paddingX-wide)]" : "px-[var(--cmp-table-keyValue-body-paddingX-default)]",
5964
6254
  cellBorder,
5965
6255
  f.tdClass
5966
6256
  ),