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
@@ -4,8 +4,8 @@ import { clsx } from 'clsx';
4
4
  import { extendTailwindMerge } from 'tailwind-merge';
5
5
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
6
6
  import * as RPopover2 from '@radix-ui/react-popover';
7
- import * as Ariakit from '@ariakit/react';
8
7
  import * as RCheckbox from '@radix-ui/react-checkbox';
8
+ import * as Ariakit from '@ariakit/react';
9
9
 
10
10
  var twMerge = extendTailwindMerge({
11
11
  extend: {
@@ -2873,10 +2873,15 @@ var FLOATING_SLIDE_ANIM = [
2873
2873
  "data-[state=closed]:data-[side=right]:animate-floating-out-right"
2874
2874
  ].join(" ");
2875
2875
  var PortalContainerContext = createContext(null);
2876
+ var InsideModalContext = createContext(false);
2876
2877
  PortalContainerContext.Provider;
2878
+ InsideModalContext.Provider;
2877
2879
  function usePortalContainer() {
2878
2880
  return useContext(PortalContainerContext) ?? void 0;
2879
2881
  }
2882
+ function useInsideModal() {
2883
+ return useContext(InsideModalContext);
2884
+ }
2880
2885
 
2881
2886
  // src/components/STag/tag.config.ts
2882
2887
  var TAG_SIZE_CONFIG = {
@@ -3400,6 +3405,7 @@ var SField = forwardRef(function SField2({
3400
3405
  name,
3401
3406
  label = "",
3402
3407
  icon,
3408
+ iconColor,
3403
3409
  labelTooltip = "",
3404
3410
  labelTooltipProps,
3405
3411
  labelWidth,
@@ -3477,7 +3483,15 @@ var SField = forwardRef(function SField2({
3477
3483
  lineHeight: `${s.lineHeight}px`
3478
3484
  },
3479
3485
  children: [
3480
- icon && /* @__PURE__ */ jsx(SIcon, { name: icon, size: s.icon, style: { marginRight: s.labelGap } }),
3486
+ icon && /* @__PURE__ */ jsx(
3487
+ SIcon,
3488
+ {
3489
+ name: icon,
3490
+ size: s.icon,
3491
+ color: iconColor,
3492
+ style: { marginRight: s.labelGap }
3493
+ }
3494
+ ),
3481
3495
  /* @__PURE__ */ jsx("span", { className: "min-w-0 overflow-hidden text-ellipsis whitespace-nowrap", children: label }),
3482
3496
  (labelTooltip !== "" || labelTooltipProps) && /* @__PURE__ */ jsx("span", { className: "shrink-0", style: { marginLeft: s.labelGap }, children: /* @__PURE__ */ jsx(
3483
3497
  STooltip,
@@ -3527,7 +3541,7 @@ var SField = forwardRef(function SField2({
3527
3541
  borderBottomLeftRadius: s.radius - 1
3528
3542
  },
3529
3543
  children: [
3530
- icon && !label && /* @__PURE__ */ jsx(SIcon, { name: icon, size: s.icon }),
3544
+ icon && !label && /* @__PURE__ */ jsx(SIcon, { name: icon, size: s.icon, color: iconColor }),
3531
3545
  effectiveAddonLabel
3532
3546
  ]
3533
3547
  }
@@ -3553,6 +3567,7 @@ var SInput = forwardRef(function SInput2({
3553
3567
  type = "text",
3554
3568
  size = "sm",
3555
3569
  icon,
3570
+ iconColor,
3556
3571
  prefix,
3557
3572
  suffix,
3558
3573
  clearable = false,
@@ -3606,6 +3621,7 @@ var SInput = forwardRef(function SInput2({
3606
3621
  labelTooltip,
3607
3622
  labelTooltipProps,
3608
3623
  icon,
3624
+ iconColor,
3609
3625
  size,
3610
3626
  hint,
3611
3627
  error: resolvedError,
@@ -3707,6 +3723,7 @@ var STextarea = forwardRef(function STextarea2({
3707
3723
  label,
3708
3724
  labelWidth,
3709
3725
  icon,
3726
+ iconColor,
3710
3727
  labelTooltip,
3711
3728
  labelTooltipProps,
3712
3729
  addonLabel,
@@ -3743,6 +3760,7 @@ var STextarea = forwardRef(function STextarea2({
3743
3760
  label,
3744
3761
  labelWidth,
3745
3762
  icon,
3763
+ iconColor,
3746
3764
  labelTooltip,
3747
3765
  labelTooltipProps,
3748
3766
  addonLabel,
@@ -3875,6 +3893,7 @@ var SNumberInput = forwardRef(function SNumberInput2({
3875
3893
  label,
3876
3894
  labelWidth,
3877
3895
  icon,
3896
+ iconColor,
3878
3897
  labelTooltip,
3879
3898
  labelTooltipProps,
3880
3899
  addonLabel,
@@ -4033,6 +4052,7 @@ var SNumberInput = forwardRef(function SNumberInput2({
4033
4052
  label,
4034
4053
  labelWidth,
4035
4054
  icon,
4055
+ iconColor,
4036
4056
  labelTooltip,
4037
4057
  labelTooltipProps,
4038
4058
  addonLabel,
@@ -4118,40 +4138,6 @@ var SNumberInput = forwardRef(function SNumberInput2({
4118
4138
  );
4119
4139
  });
4120
4140
 
4121
- // src/components/SSelect/select.config.ts
4122
- function flattenOptions(options, depth = 1) {
4123
- const out = [];
4124
- for (const o of options) {
4125
- const hasChildren = Array.isArray(o.children) && o.children.length > 0;
4126
- if (hasChildren) {
4127
- const childFlat = flattenOptions(o.children, depth + 1);
4128
- const allChildrenDisabled = childFlat.every((c) => c.disabled);
4129
- out.push({
4130
- value: String(o.value),
4131
- label: o.label,
4132
- disabled: !!o.disabled || allChildrenDisabled,
4133
- depth,
4134
- isGroup: true,
4135
- raw: o.value
4136
- });
4137
- out.push(...childFlat);
4138
- } else {
4139
- out.push({
4140
- value: String(o.value),
4141
- label: o.label,
4142
- disabled: !!o.disabled,
4143
- depth,
4144
- isGroup: false,
4145
- raw: o.value
4146
- });
4147
- }
4148
- }
4149
- return out;
4150
- }
4151
- var selectableOptions = (flat) => flat.filter((o) => !o.isGroup && !o.disabled);
4152
- var isHtmlLabel = (label) => /<[a-z][\s\S]*>/i.test(label);
4153
- var extractText = (html) => html.replace(/<[^>]*>/g, "");
4154
-
4155
4141
  // src/lib/sanitize-inline-html.ts
4156
4142
  var ALLOWED_INLINE_TAGS = /* @__PURE__ */ new Set(["A", "B", "STRONG", "I", "EM", "BR", "SPAN"]);
4157
4143
  var ALLOWED_ATTRS_BY_TAG = {
@@ -4220,6 +4206,111 @@ var sanitizeInlineHtml = (value) => {
4220
4206
  Array.from(template.content.childNodes).forEach((child) => sanitizeNode(child));
4221
4207
  return template.innerHTML;
4222
4208
  };
4209
+ function SPortal({
4210
+ open,
4211
+ onClose,
4212
+ onOpenChange,
4213
+ anchorRef,
4214
+ placement = "bottom",
4215
+ align = "start",
4216
+ offset = 4,
4217
+ alignOffset = 0,
4218
+ fit = false,
4219
+ maxHeight,
4220
+ autoClose = false,
4221
+ persistent = false,
4222
+ viewportPadding = 0,
4223
+ onOpenAutoFocus,
4224
+ onCloseAutoFocus,
4225
+ children,
4226
+ className,
4227
+ style,
4228
+ ariaLabel
4229
+ }) {
4230
+ const portalContainer = usePortalContainer();
4231
+ return /* @__PURE__ */ jsxs(
4232
+ RPopover2.Root,
4233
+ {
4234
+ open,
4235
+ onOpenChange: (next) => {
4236
+ onOpenChange?.(next);
4237
+ if (!next) onClose?.();
4238
+ },
4239
+ children: [
4240
+ /* @__PURE__ */ jsx(RPopover2.Anchor, { virtualRef: anchorRef }),
4241
+ /* @__PURE__ */ jsx(RPopover2.Portal, { container: portalContainer, children: /* @__PURE__ */ jsx(
4242
+ RPopover2.Content,
4243
+ {
4244
+ side: placement,
4245
+ align,
4246
+ sideOffset: offset,
4247
+ alignOffset,
4248
+ collisionPadding: viewportPadding,
4249
+ "aria-label": ariaLabel,
4250
+ onPointerDownOutside: (e) => {
4251
+ if (persistent || anchorRef.current?.contains(e.target)) {
4252
+ e.preventDefault();
4253
+ }
4254
+ },
4255
+ onFocusOutside: (e) => {
4256
+ if (persistent || anchorRef.current?.contains(e.target)) {
4257
+ e.preventDefault();
4258
+ }
4259
+ },
4260
+ onEscapeKeyDown: persistent ? (e) => e.preventDefault() : void 0,
4261
+ onOpenAutoFocus,
4262
+ onCloseAutoFocus,
4263
+ onClick: autoClose ? () => onClose?.() : void 0,
4264
+ className: cn(
4265
+ "z-50 overflow-hidden rounded-(--cmp-listBox-radius) bg-(--cmp-listBox-bg) py-4 shadow-normal-sm",
4266
+ maxHeight != null && "overflow-y-auto",
4267
+ fit && "min-w-(--radix-popover-trigger-width)",
4268
+ FLOATING_SLIDE_ANIM,
4269
+ className
4270
+ ),
4271
+ style: { maxHeight, ...style },
4272
+ children
4273
+ }
4274
+ ) })
4275
+ ]
4276
+ }
4277
+ );
4278
+ }
4279
+ SPortal.displayName = "SPortal";
4280
+
4281
+ // src/components/SSelect/select.config.ts
4282
+ function flattenOptions(options, depth = 1) {
4283
+ const out = [];
4284
+ for (const o of options) {
4285
+ const hasChildren = Array.isArray(o.children) && o.children.length > 0;
4286
+ if (hasChildren) {
4287
+ const childFlat = flattenOptions(o.children, depth + 1);
4288
+ const allChildrenDisabled = childFlat.every((c) => c.disabled);
4289
+ out.push({
4290
+ value: String(o.value),
4291
+ label: o.label,
4292
+ disabled: !!o.disabled || allChildrenDisabled,
4293
+ depth,
4294
+ isGroup: true,
4295
+ raw: o.value
4296
+ });
4297
+ out.push(...childFlat);
4298
+ } else {
4299
+ out.push({
4300
+ value: String(o.value),
4301
+ label: o.label,
4302
+ disabled: !!o.disabled,
4303
+ depth,
4304
+ isGroup: false,
4305
+ raw: o.value
4306
+ });
4307
+ }
4308
+ }
4309
+ return out;
4310
+ }
4311
+ var selectableOptions = (flat) => flat.filter((o) => !o.isGroup && !o.disabled);
4312
+ var isHtmlLabel = (label) => /<[a-z][\s\S]*>/i.test(label);
4313
+ var extractText = (html) => html.replace(/<[^>]*>/g, "");
4223
4314
  var SEARCH_THRESHOLD = 11;
4224
4315
  var SSelect = forwardRef(function SSelect2({
4225
4316
  value,
@@ -4243,6 +4334,7 @@ var SSelect = forwardRef(function SSelect2({
4243
4334
  label,
4244
4335
  labelWidth,
4245
4336
  icon,
4337
+ iconColor,
4246
4338
  labelTooltip,
4247
4339
  addonLabel,
4248
4340
  addonAlign,
@@ -4254,18 +4346,15 @@ var SSelect = forwardRef(function SSelect2({
4254
4346
  }, ref) {
4255
4347
  const multi = type === "multi" || type === "multi_depth";
4256
4348
  const portalContainer = usePortalContainer();
4349
+ const insideModal = useInsideModal();
4257
4350
  const [search, setSearch] = useState("");
4258
4351
  const [ruleError, setRuleError] = useState("");
4259
4352
  const [open, setOpen] = useState(false);
4353
+ const [pendingOpen, setPendingOpen] = useState(false);
4354
+ const [focusedIndex, setFocusedIndex] = useState(-1);
4260
4355
  const triggerRef = useRef(null);
4261
- useImperativeHandle(
4262
- ref,
4263
- () => ({
4264
- focus: () => triggerRef.current?.focus(),
4265
- open: () => setOpen(true)
4266
- }),
4267
- []
4268
- );
4356
+ const searchInputRef = useRef(null);
4357
+ const listRef = useRef(null);
4269
4358
  const flat = useMemo(() => flattenOptions(options), [options]);
4270
4359
  const selectable = useMemo(() => selectableOptions(flat), [flat]);
4271
4360
  const hasLeaf = useMemo(() => flat.some((o) => !o.isGroup), [flat]);
@@ -4283,6 +4372,46 @@ var SSelect = forwardRef(function SSelect2({
4283
4372
  const arr = Array.isArray(value) ? value : [value];
4284
4373
  return arr.map((v) => v != null && typeof v === "object" ? v.value : v).filter((v) => v != null && v !== "").map(String);
4285
4374
  }, [value]);
4375
+ const selectedRef = useRef(selected);
4376
+ selectedRef.current = selected;
4377
+ const emit = (next) => {
4378
+ if (!onValueChange) return;
4379
+ const mapped = next.map(
4380
+ (v) => emitValue ? rawByValue.get(v) ?? v : optionByValue.get(v) ?? { value: v, label: v }
4381
+ );
4382
+ onValueChange(multi ? mapped : mapped[0] ?? (emitValue ? "" : null));
4383
+ };
4384
+ const commitOpen = (next) => {
4385
+ setOpen(next);
4386
+ onOpenChange?.(next);
4387
+ if (!next) {
4388
+ setPendingOpen(false);
4389
+ setSearch("");
4390
+ if (rules && rules.length > 0) {
4391
+ setRuleError(runRules(multi ? selectedRef.current : selectedRef.current[0] ?? "", rules));
4392
+ }
4393
+ }
4394
+ };
4395
+ const requestOpen = (next) => {
4396
+ if (next && insideModal && portalContainer == null) {
4397
+ setPendingOpen(true);
4398
+ return;
4399
+ }
4400
+ commitOpen(next);
4401
+ };
4402
+ useEffect(() => {
4403
+ if (!insideModal || portalContainer == null || !pendingOpen) return;
4404
+ setPendingOpen(false);
4405
+ commitOpen(true);
4406
+ }, [insideModal, pendingOpen, portalContainer]);
4407
+ useImperativeHandle(
4408
+ ref,
4409
+ () => ({
4410
+ focus: () => triggerRef.current?.focus(),
4411
+ open: () => requestOpen(true)
4412
+ }),
4413
+ [insideModal, portalContainer]
4414
+ );
4286
4415
  useFormField({
4287
4416
  name,
4288
4417
  validate: () => {
@@ -4292,13 +4421,6 @@ var SSelect = forwardRef(function SSelect2({
4292
4421
  },
4293
4422
  resetValidation: () => setRuleError("")
4294
4423
  });
4295
- const emit = (next) => {
4296
- if (!onValueChange) return;
4297
- const mapped = next.map(
4298
- (v) => emitValue ? rawByValue.get(v) ?? v : optionByValue.get(v) ?? { value: v, label: v }
4299
- );
4300
- onValueChange(multi ? mapped : mapped[0] ?? (emitValue ? "" : null));
4301
- };
4302
4424
  const filtered = useMemo(() => {
4303
4425
  if (!showSearch || search.trim() === "") return flat;
4304
4426
  const q = search.trim().toLowerCase();
@@ -4309,6 +4431,16 @@ var SSelect = forwardRef(function SSelect2({
4309
4431
  const allValues = selectable.map((o) => o.value);
4310
4432
  const allSelected = allValues.length > 0 && allValues.every((v) => selected.includes(v));
4311
4433
  const hasItems = filtered.some((o) => !o.isGroup);
4434
+ const navigableItems = useMemo(() => {
4435
+ const items = [];
4436
+ if (multi && useSelectAll && allValues.length > 0) {
4437
+ items.push({ value: "__select_all__", isSelectAll: true });
4438
+ }
4439
+ filtered.forEach((o) => {
4440
+ if (!o.isGroup && !o.disabled) items.push({ value: o.value });
4441
+ });
4442
+ return items;
4443
+ }, [allValues.length, filtered, multi, useSelectAll]);
4312
4444
  const triggerText = (() => {
4313
4445
  if (selected.length === 0) return placeholder;
4314
4446
  if (multi) {
@@ -4318,26 +4450,119 @@ var SSelect = forwardRef(function SSelect2({
4318
4450
  return labelByValue.get(selected[0]) ?? selected[0];
4319
4451
  })();
4320
4452
  const isPlaceholder = selected.length === 0;
4321
- const selectStore = /* @__PURE__ */ jsxs(
4322
- Ariakit.SelectProvider,
4453
+ const handleSelect = (nextValue) => {
4454
+ if (multi) {
4455
+ const next = selected.includes(nextValue) ? selected.filter((v) => v !== nextValue) : [...selected, nextValue];
4456
+ emit(next);
4457
+ return;
4458
+ }
4459
+ emit([nextValue]);
4460
+ commitOpen(false);
4461
+ };
4462
+ const handleSelectAll = () => {
4463
+ emit(allSelected ? [] : allValues);
4464
+ };
4465
+ useEffect(() => {
4466
+ if (!open) {
4467
+ setFocusedIndex(-1);
4468
+ return;
4469
+ }
4470
+ setFocusedIndex(navigableItems.length > 0 ? 0 : -1);
4471
+ }, [open, navigableItems.length]);
4472
+ useEffect(() => {
4473
+ if (!open) return;
4474
+ setFocusedIndex(navigableItems.length > 0 ? 0 : -1);
4475
+ }, [search, open, navigableItems.length]);
4476
+ useEffect(() => {
4477
+ if (!open || focusedIndex < 0) return;
4478
+ const list = listRef.current;
4479
+ const focusedEl = list?.querySelector('[data-select-focused="true"]');
4480
+ if (!list || !focusedEl) return;
4481
+ const listRect = list.getBoundingClientRect();
4482
+ const itemRect = focusedEl.getBoundingClientRect();
4483
+ if (itemRect.top < listRect.top) {
4484
+ list.scrollTop += itemRect.top - listRect.top;
4485
+ } else if (itemRect.bottom > listRect.bottom) {
4486
+ list.scrollTop += itemRect.bottom - listRect.bottom;
4487
+ }
4488
+ }, [focusedIndex, open]);
4489
+ const moveFocusedIndex = (direction) => {
4490
+ if (!open || navigableItems.length === 0) return;
4491
+ setFocusedIndex((current) => {
4492
+ if (direction === "next") {
4493
+ return current < 0 ? 0 : (current + 1) % navigableItems.length;
4494
+ }
4495
+ return current <= 0 ? navigableItems.length - 1 : current - 1;
4496
+ });
4497
+ };
4498
+ const selectFocusedItem = () => {
4499
+ if (focusedIndex < 0 || focusedIndex >= navigableItems.length) return;
4500
+ const focused = navigableItems[focusedIndex];
4501
+ if (focused.isSelectAll) {
4502
+ handleSelectAll();
4503
+ return;
4504
+ }
4505
+ handleSelect(focused.value);
4506
+ };
4507
+ const handleKeyDown = (event) => {
4508
+ if (!open) {
4509
+ if ((event.key === "ArrowDown" || event.key === "ArrowUp") && !effectiveDisabled) {
4510
+ event.preventDefault();
4511
+ requestOpen(true);
4512
+ }
4513
+ return;
4514
+ }
4515
+ if (event.key === "ArrowDown") {
4516
+ event.preventDefault();
4517
+ moveFocusedIndex("next");
4518
+ return;
4519
+ }
4520
+ if (event.key === "ArrowUp") {
4521
+ event.preventDefault();
4522
+ moveFocusedIndex("prev");
4523
+ return;
4524
+ }
4525
+ if (event.key === "Enter") {
4526
+ event.preventDefault();
4527
+ selectFocusedItem();
4528
+ return;
4529
+ }
4530
+ if (event.key === "Escape") {
4531
+ event.preventDefault();
4532
+ commitOpen(false);
4533
+ }
4534
+ };
4535
+ return /* @__PURE__ */ jsxs(
4536
+ SField,
4323
4537
  {
4324
- animated: portalContainer == null ? 150 : false,
4325
- value: multi ? selected : selected[0] ?? "",
4326
- setValue: (v) => emit(Array.isArray(v) ? v : v === "" ? [] : [v]),
4327
- open,
4328
- setOpen: (o) => {
4329
- setOpen(o);
4330
- onOpenChange?.(o);
4331
- if (!o && rules && rules.length > 0) {
4332
- setRuleError(runRules(multi ? selected : selected[0] ?? "", rules));
4333
- }
4334
- },
4538
+ label,
4539
+ labelWidth,
4540
+ icon,
4541
+ iconColor,
4542
+ labelTooltip,
4543
+ labelTooltipProps,
4544
+ addonLabel,
4545
+ addonAlign,
4546
+ hint,
4547
+ error: error || ruleError !== "",
4548
+ status: rules && rules.length > 0 ? ruleError !== "" ? "error" : void 0 : void 0,
4549
+ errorMessage: ruleError !== "" ? ruleError : errorMessage,
4550
+ disabled: effectiveDisabled,
4551
+ width,
4552
+ className,
4553
+ style,
4335
4554
  children: [
4555
+ name && /* @__PURE__ */ jsx("input", { type: "hidden", name, value: selected.join(",") }),
4336
4556
  /* @__PURE__ */ jsxs(
4337
- Ariakit.Select,
4557
+ "button",
4338
4558
  {
4339
4559
  ref: triggerRef,
4560
+ type: "button",
4340
4561
  disabled: effectiveDisabled,
4562
+ "aria-haspopup": "listbox",
4563
+ "aria-expanded": open,
4564
+ onClick: () => requestOpen(!open),
4565
+ onKeyDown: handleKeyDown,
4341
4566
  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",
4342
4567
  style: {
4343
4568
  color: effectiveDisabled ? "var(--cmp-select-text-disabled)" : isPlaceholder ? "var(--cmp-select-icon-default)" : "var(--cmp-select-text-default)"
@@ -4362,178 +4587,229 @@ var SSelect = forwardRef(function SSelect2({
4362
4587
  ]
4363
4588
  }
4364
4589
  ),
4365
- /* @__PURE__ */ jsxs(
4366
- Ariakit.SelectPopover,
4590
+ /* @__PURE__ */ jsx(
4591
+ SPortal,
4367
4592
  {
4368
- portal: true,
4369
- portalElement: portalContainer,
4370
- gutter: 4,
4371
- sameWidth: !dropdownWidth,
4372
- 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",
4593
+ open,
4594
+ onClose: () => commitOpen(false),
4595
+ onOpenChange: (next) => {
4596
+ if (next) requestOpen(true);
4597
+ },
4598
+ anchorRef: triggerRef,
4599
+ placement: "bottom",
4600
+ align: "start",
4601
+ offset: 4,
4602
+ fit: !dropdownWidth,
4603
+ onOpenAutoFocus: (event) => {
4604
+ if (!showSearch) {
4605
+ event.preventDefault();
4606
+ return;
4607
+ }
4608
+ event.preventDefault();
4609
+ requestAnimationFrame(() => searchInputRef.current?.focus());
4610
+ },
4611
+ onCloseAutoFocus: (event) => {
4612
+ event.preventDefault();
4613
+ triggerRef.current?.focus();
4614
+ },
4615
+ className: "rounded-[var(--cmp-listBox-radius)] bg-[var(--cmp-listBox-bg)] py-[4px] shadow-[2px_2px_12px_2px_rgba(0,0,0,0.1)]",
4373
4616
  style: {
4374
- maxHeight: dropdownHeight,
4375
4617
  ...dropdownWidth ? { width: dropdownWidth } : {},
4376
4618
  maxWidth: maxDropdownWidth
4377
4619
  },
4378
- children: [
4379
- showSearch && // 원본 sd-select-list-item-search: sticky + 검색 아이콘 + 테두리 input + focus-within + clear
4380
- /* @__PURE__ */ jsx("div", { className: "sticky top-0 z-[1] bg-[var(--cmp-listBox-bg)] px-[8px] py-[4px]", children: /* @__PURE__ */ 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: [
4381
- /* @__PURE__ */ jsx(SIcon, { name: "search", size: 16, color: "var(--color-grey-70)", className: "shrink-0" }),
4382
- /* @__PURE__ */ jsx(
4383
- Ariakit.Combobox,
4384
- {
4385
- autoSelect: true,
4386
- placeholder: "\uAC80\uC0C9",
4387
- 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)]"
4388
- }
4389
- ),
4390
- search !== "" && /* @__PURE__ */ jsx(
4391
- SGhostButton,
4392
- {
4393
- icon: "close",
4394
- size: "xxs",
4395
- intent: "default",
4396
- ariaLabel: "\uAC80\uC0C9\uC5B4 \uC9C0\uC6B0\uAE30",
4397
- onClick: () => setSearch(""),
4398
- className: "shrink-0"
4399
- }
4400
- )
4401
- ] }) }),
4402
- multi && useSelectAll && /* @__PURE__ */ jsx(
4403
- "button",
4404
- {
4405
- type: "button",
4406
- 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)]",
4407
- onClick: () => emit(allSelected ? [] : allValues),
4408
- children: allSelectedLabel
4409
- }
4410
- ),
4411
- hasItems ? /* @__PURE__ */ jsx(ItemList, { items: filtered, multi, selected, useSearch: showSearch }) : (
4412
- // 원본 sd-select-listbox__empty (표시할 항목이 없을 때)
4413
- /* @__PURE__ */ 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." })
4414
- )
4415
- ]
4620
+ children: /* @__PURE__ */ jsxs(
4621
+ "div",
4622
+ {
4623
+ ref: listRef,
4624
+ 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)]",
4625
+ style: { maxHeight: dropdownHeight },
4626
+ onMouseLeave: () => setFocusedIndex(-1),
4627
+ children: [
4628
+ showSearch && /* @__PURE__ */ jsx("div", { className: "sticky top-0 z-[1] bg-[var(--cmp-listBox-bg)] px-[8px] py-[4px]", children: /* @__PURE__ */ 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: [
4629
+ /* @__PURE__ */ jsx(SIcon, { name: "search", size: 16, color: "var(--color-grey-70)", className: "shrink-0" }),
4630
+ /* @__PURE__ */ jsx(
4631
+ "input",
4632
+ {
4633
+ ref: searchInputRef,
4634
+ value: search,
4635
+ onChange: (e) => setSearch(e.target.value),
4636
+ onKeyDown: handleKeyDown,
4637
+ placeholder: "\uAC80\uC0C9",
4638
+ 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)]"
4639
+ }
4640
+ ),
4641
+ search !== "" && /* @__PURE__ */ jsx(
4642
+ SGhostButton,
4643
+ {
4644
+ icon: "close",
4645
+ size: "xxs",
4646
+ intent: "default",
4647
+ ariaLabel: "\uAC80\uC0C9\uC5B4 \uC9C0\uC6B0\uAE30",
4648
+ onClick: () => setSearch(""),
4649
+ className: "shrink-0"
4650
+ }
4651
+ )
4652
+ ] }) }),
4653
+ hasItems ? /* @__PURE__ */ jsx(
4654
+ ItemList,
4655
+ {
4656
+ items: filtered,
4657
+ multi,
4658
+ selected,
4659
+ focusedValue: navigableItems[focusedIndex]?.isSelectAll ? "__select_all__" : navigableItems[focusedIndex]?.value,
4660
+ showSelectAll: multi && useSelectAll && allValues.length > 0,
4661
+ selectAllLabel: allSelectedLabel,
4662
+ selectAllSelected: allSelected,
4663
+ onSelectAll: handleSelectAll,
4664
+ onSelect: handleSelect,
4665
+ onHoverValue: (value2) => {
4666
+ const index = navigableItems.findIndex(
4667
+ (item) => item.isSelectAll ? value2 === "__select_all__" : item.value === value2
4668
+ );
4669
+ if (index >= 0) setFocusedIndex(index);
4670
+ }
4671
+ }
4672
+ ) : /* @__PURE__ */ 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." })
4673
+ ]
4674
+ }
4675
+ )
4416
4676
  }
4417
4677
  )
4418
4678
  ]
4419
4679
  }
4420
4680
  );
4421
- const control = showSearch ? /* @__PURE__ */ jsx(Ariakit.ComboboxProvider, { value: search, setValue: setSearch, resetValueOnHide: true, children: selectStore }) : selectStore;
4422
- return /* @__PURE__ */ jsxs(
4423
- SField,
4424
- {
4425
- label,
4426
- labelWidth,
4427
- icon,
4428
- labelTooltip,
4429
- labelTooltipProps,
4430
- addonLabel,
4431
- addonAlign,
4432
- hint,
4433
- error: error || ruleError !== "",
4434
- status: rules && rules.length > 0 ? ruleError !== "" ? "error" : void 0 : void 0,
4435
- errorMessage: ruleError !== "" ? ruleError : errorMessage,
4436
- disabled: effectiveDisabled,
4437
- width,
4438
- className,
4439
- style,
4440
- children: [
4441
- name && /* @__PURE__ */ jsx("input", { type: "hidden", name, value: selected.join(",") }),
4442
- control
4443
- ]
4444
- }
4445
- );
4446
4681
  });
4447
4682
  SSelect.displayName = "SSelect";
4448
4683
  function ItemList({
4449
4684
  items,
4450
4685
  multi,
4451
4686
  selected,
4452
- useSearch
4687
+ focusedValue,
4688
+ showSelectAll,
4689
+ selectAllLabel,
4690
+ selectAllSelected,
4691
+ onSelectAll,
4692
+ onSelect,
4693
+ onHoverValue
4453
4694
  }) {
4454
4695
  const itemBase = cn(
4455
- // group: 체크박스 inverse(group-data-[active-item])를 위해 필요
4456
- "group flex cursor-pointer select-none items-center gap-[8px] text-[12px] leading-[20px] outline-none",
4696
+ "group flex w-full cursor-pointer select-none items-center gap-[8px] border-none bg-transparent text-left text-[12px] leading-[20px] outline-none",
4457
4697
  "text-[color:var(--cmp-listItem-content-default)]",
4458
- // Ariakit은 disabled 항목에 aria-disabled를 붙이므로 aria-disabled 변형으로 스타일 (data-disabled 아님)
4459
- "aria-disabled:cursor-not-allowed aria-disabled:text-[color:var(--cmp-listItem-content-disabled)]"
4698
+ "disabled:cursor-not-allowed disabled:text-[color:var(--cmp-listItem-content-disabled)]"
4460
4699
  );
4461
- const activeClass = "data-[active-item]:bg-[var(--cmp-listItem-bg-hover)] data-[active-item]:text-[color:var(--cmp-listItem-content-hover)]";
4462
- return /* @__PURE__ */ jsx(Fragment, { children: items.map((o) => {
4463
- const padLeft = o.depth === 1 ? 12 : o.depth === 2 ? 20 : 28;
4464
- if (o.isGroup) {
4465
- const isDepth1 = o.depth === 1;
4466
- return /* @__PURE__ */ jsx(
4467
- "div",
4468
- {
4469
- className: cn(
4470
- "select-none py-[4px] pr-[12px] text-[12px] leading-[20px] text-[color:var(--cmp-listItem-content-default)]",
4471
- 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",
4472
- // 하위가 전부 비활성이면(select.config 에서 bubble-up) 어미 그룹도 비활성 처리:
4473
- // 텍스트는 회색(#888), depth1 그룹은 파란 배경 대신 회색(#EEE)으로 (depth2 는 #F6F6F6 유지)
4474
- o.disabled && "cursor-not-allowed text-[color:var(--cmp-listItem-content-disabled)]",
4475
- o.disabled && isDepth1 && "bg-[var(--cmp-listItem-depth1-bg-disabled)]"
4476
- ),
4477
- style: { paddingLeft: padLeft },
4478
- children: o.label
4479
- },
4480
- o.value
4481
- );
4482
- }
4483
- const isSelected = selected.includes(o.value);
4484
- return /* @__PURE__ */ jsxs(
4485
- Ariakit.SelectItem,
4700
+ 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)]";
4701
+ return /* @__PURE__ */ jsxs("div", { role: "listbox", "aria-multiselectable": multi || void 0, children: [
4702
+ showSelectAll && /* @__PURE__ */ jsxs(
4703
+ "button",
4486
4704
  {
4487
- value: o.value,
4488
- disabled: o.disabled,
4489
- accessibleWhenDisabled: true,
4705
+ type: "button",
4706
+ "data-select-focused": focusedValue === "__select_all__" ? "true" : void 0,
4707
+ "data-select-active": focusedValue === "__select_all__" ? "true" : void 0,
4490
4708
  className: cn(
4491
4709
  itemBase,
4492
- isSelected && !multi && "font-bold text-[color:var(--cmp-listItem-content-selected)]",
4493
- // Ariakit은 disabled 항목도 hover 시 active(data-active-item) 처리하므로,
4494
- // 비활성 항목에는 hover 하이라이트를 적용하지 않아 회색 비활성 스타일을 유지한다.
4495
- // (원본 Stencil의 `--focused...:not(--disabled)` 규칙과 동일한 의도)
4496
- !o.disabled && activeClass
4710
+ focusedValue === "__select_all__" && "bg-[var(--cmp-listItem-bg-hover)] text-[color:var(--cmp-listItem-content-hover)]"
4497
4711
  ),
4498
- style: { paddingTop: 4, paddingBottom: 4, paddingRight: 12, paddingLeft: padLeft },
4499
- render: useSearch ? /* @__PURE__ */ jsx(Ariakit.ComboboxItem, {}) : void 0,
4712
+ style: { paddingTop: 4, paddingBottom: 4, paddingRight: 12, paddingLeft: 12 },
4713
+ onMouseEnter: () => onHoverValue("__select_all__"),
4714
+ onClick: onSelectAll,
4500
4715
  children: [
4501
- multi && // hover(active) 시 checkbox inverse — 파란 hover 배경 위에서 checked/unchecked 모두 흰 테두리 + 투명 배경
4502
4716
  /* @__PURE__ */ jsx(
4503
4717
  "span",
4504
4718
  {
4505
4719
  className: cn(
4506
4720
  "flex h-[16px] w-[16px] flex-shrink-0 items-center justify-center rounded-[2px] border border-solid",
4507
- o.disabled ? (
4508
- // 비활성: 회색 채움(#E1E1E1) + 회색 테두리(#CCCCCC)
4509
- "cursor-not-allowed border-[color:var(--cmp-checkbox-border-disabled)] bg-[var(--cmp-checkbox-bg-disabled)]"
4510
- ) : cn(
4511
- "group-data-[active-item]:border-[color:var(--cmp-checkbox-checked-border-inverse)]",
4512
- 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"
4513
- )
4721
+ 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)]"
4514
4722
  ),
4515
- children: isSelected && /* @__PURE__ */ jsx(
4723
+ children: selectAllSelected && /* @__PURE__ */ jsx(
4516
4724
  SIcon,
4517
4725
  {
4518
4726
  name: "checkboxCheck",
4519
4727
  size: 12,
4520
- color: o.disabled ? "var(--cmp-checkbox-checked-icon-disabled)" : "var(--cmp-checkbox-checked-icon-default)"
4728
+ color: "var(--cmp-checkbox-checked-icon-default)"
4521
4729
  }
4522
4730
  )
4523
4731
  }
4524
4732
  ),
4525
- isHtmlLabel(o.label) ? /* @__PURE__ */ jsx(
4526
- "span",
4527
- {
4528
- className: "flex-1 truncate",
4529
- dangerouslySetInnerHTML: { __html: sanitizeInlineHtml(o.label) }
4530
- }
4531
- ) : /* @__PURE__ */ jsx("span", { className: "flex-1 truncate", children: o.label })
4733
+ /* @__PURE__ */ jsx("span", { className: "flex-1 truncate", children: selectAllLabel })
4532
4734
  ]
4533
- },
4534
- o.value
4535
- );
4536
- }) });
4735
+ }
4736
+ ),
4737
+ items.map((o) => {
4738
+ const padLeft = o.depth === 1 ? 12 : o.depth === 2 ? 20 : 28;
4739
+ if (o.isGroup) {
4740
+ const isDepth1 = o.depth === 1;
4741
+ return /* @__PURE__ */ jsx(
4742
+ "div",
4743
+ {
4744
+ className: cn(
4745
+ "select-none py-[4px] pr-[12px] text-[12px] leading-[20px] text-[color:var(--cmp-listItem-content-default)]",
4746
+ 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",
4747
+ // 하위가 전부 비활성이면(select.config 에서 bubble-up) 어미 그룹도 비활성 처리:
4748
+ // 텍스트는 회색(#888), depth1 그룹은 파란 배경 대신 회색(#EEE)으로 (depth2 는 #F6F6F6 유지)
4749
+ o.disabled && "cursor-not-allowed text-[color:var(--cmp-listItem-content-disabled)]",
4750
+ o.disabled && isDepth1 && "bg-[var(--cmp-listItem-depth1-bg-disabled)]"
4751
+ ),
4752
+ style: { paddingLeft: padLeft },
4753
+ children: o.label
4754
+ },
4755
+ o.value
4756
+ );
4757
+ }
4758
+ const isSelected = selected.includes(o.value);
4759
+ return /* @__PURE__ */ jsxs(
4760
+ "button",
4761
+ {
4762
+ type: "button",
4763
+ disabled: o.disabled,
4764
+ role: "option",
4765
+ "aria-selected": isSelected,
4766
+ "data-select-focused": focusedValue === o.value ? "true" : void 0,
4767
+ "data-select-active": focusedValue === o.value ? "true" : void 0,
4768
+ className: cn(
4769
+ itemBase,
4770
+ isSelected && !multi && "font-bold text-[color:var(--cmp-listItem-content-selected)]",
4771
+ focusedValue === o.value && "bg-[var(--cmp-listItem-bg-hover)] text-[color:var(--cmp-listItem-content-hover)]",
4772
+ activeClass
4773
+ ),
4774
+ style: { paddingTop: 4, paddingBottom: 4, paddingRight: 12, paddingLeft: padLeft },
4775
+ onMouseEnter: () => onHoverValue(o.value),
4776
+ onClick: () => onSelect(o.value),
4777
+ children: [
4778
+ multi && /* @__PURE__ */ jsx(
4779
+ "span",
4780
+ {
4781
+ className: cn(
4782
+ "flex h-[16px] w-[16px] flex-shrink-0 items-center justify-center rounded-[2px] border border-solid",
4783
+ o.disabled ? (
4784
+ // 비활성: 회색 채움(#E1E1E1) + 회색 테두리(#CCCCCC)
4785
+ "cursor-not-allowed border-[color:var(--cmp-checkbox-border-disabled)] bg-[var(--cmp-checkbox-bg-disabled)]"
4786
+ ) : cn(
4787
+ 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)]"
4788
+ )
4789
+ ),
4790
+ children: isSelected && /* @__PURE__ */ jsx(
4791
+ SIcon,
4792
+ {
4793
+ name: "checkboxCheck",
4794
+ size: 12,
4795
+ color: o.disabled ? "var(--cmp-checkbox-checked-icon-disabled)" : "var(--cmp-checkbox-checked-icon-default)"
4796
+ }
4797
+ )
4798
+ }
4799
+ ),
4800
+ isHtmlLabel(o.label) ? /* @__PURE__ */ jsx(
4801
+ "span",
4802
+ {
4803
+ className: "flex-1 truncate",
4804
+ dangerouslySetInnerHTML: { __html: sanitizeInlineHtml(o.label) }
4805
+ }
4806
+ ) : /* @__PURE__ */ jsx("span", { className: "flex-1 truncate", children: o.label })
4807
+ ]
4808
+ },
4809
+ o.value
4810
+ );
4811
+ })
4812
+ ] });
4537
4813
  }
4538
4814
  function SRadio({
4539
4815
  val,
@@ -4996,6 +5272,7 @@ function SDatePicker({
4996
5272
  label,
4997
5273
  labelWidth,
4998
5274
  icon,
5275
+ iconColor,
4999
5276
  labelTooltip,
5000
5277
  labelTooltipProps,
5001
5278
  addonLabel,
@@ -5034,6 +5311,7 @@ function SDatePicker({
5034
5311
  label,
5035
5312
  labelWidth,
5036
5313
  icon,
5314
+ iconColor,
5037
5315
  labelTooltip,
5038
5316
  labelTooltipProps,
5039
5317
  addonLabel,
@@ -5302,6 +5580,7 @@ function SDateRangePicker({
5302
5580
  label,
5303
5581
  labelWidth,
5304
5582
  icon,
5583
+ iconColor,
5305
5584
  labelTooltip,
5306
5585
  labelTooltipProps,
5307
5586
  addonLabel,
@@ -5344,6 +5623,7 @@ function SDateRangePicker({
5344
5623
  label,
5345
5624
  labelWidth,
5346
5625
  icon,
5626
+ iconColor,
5347
5627
  labelTooltip,
5348
5628
  labelTooltipProps,
5349
5629
  addonLabel,
@@ -5605,6 +5885,7 @@ function SFilePicker({
5605
5885
  label,
5606
5886
  labelWidth,
5607
5887
  icon,
5888
+ iconColor,
5608
5889
  labelTooltip,
5609
5890
  labelTooltipProps,
5610
5891
  addonLabel,
@@ -5774,7 +6055,7 @@ function SFilePicker({
5774
6055
  unmountOnHide: true,
5775
6056
  getAnchorRect: () => contentBoxRef.current?.getBoundingClientRect() ?? null,
5776
6057
  style: { minWidth: contentBoxRef.current?.offsetWidth },
5777
- 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)]",
6058
+ 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)]",
5778
6059
  children: filesArr.map((f, i) => /* @__PURE__ */ jsx(SChip, { value: f.name, editable: false, onRemove: () => removeChip(i) }, i))
5779
6060
  }
5780
6061
  )
@@ -5788,6 +6069,7 @@ function SFilePicker({
5788
6069
  label,
5789
6070
  labelWidth,
5790
6071
  icon,
6072
+ iconColor,
5791
6073
  labelTooltip,
5792
6074
  labelTooltipProps,
5793
6075
  addonLabel,
@@ -5810,21 +6092,26 @@ function SFilePicker({
5810
6092
  var cssLen2 = (v) => v == null ? void 0 : typeof v === "number" ? `${v}px` : v;
5811
6093
  function SKeyValueTable({
5812
6094
  fields = [],
6095
+ values = {},
5813
6096
  search = false,
5814
6097
  radius = "default",
5815
- dense = false,
5816
6098
  onChange,
5817
6099
  onSearch,
5818
6100
  className,
5819
6101
  style
5820
6102
  }) {
5821
6103
  if (fields.length === 0) return null;
5822
- const emit = (name, value) => onChange?.({ name, value });
5823
- const cellHeight = dense ? "var(--cmp-table-body-dense-height)" : "var(--cmp-table-keyValue-height)";
5824
- const cellPadY = dense ? "var(--cmp-table-body-paddingY-dense)" : "var(--cmp-table-keyValue-body-paddingY)";
6104
+ const emit = (name, value) => onChange?.({
6105
+ name,
6106
+ value,
6107
+ values: { ...values, [name]: value }
6108
+ });
6109
+ const cellHeight = "var(--cmp-table-keyValue-height)";
6110
+ const cellPadY = "var(--cmp-table-keyValue-body-paddingY)";
5825
6111
  const renderField = (f) => {
5826
6112
  if (f.render) return f.render;
5827
- const opt = f.options ?? {};
6113
+ const opt = { ...f.options ?? {} };
6114
+ if (Object.prototype.hasOwnProperty.call(values, f.name)) opt.value = values[f.name];
5828
6115
  const up = (v) => emit(f.name, v);
5829
6116
  switch (f.type) {
5830
6117
  case "input":
@@ -5936,7 +6223,10 @@ function SKeyValueTable({
5936
6223
  rowSpan: f.tdRowSpan && f.tdRowSpan > 1 ? f.tdRowSpan : void 0,
5937
6224
  colSpan: f.tdColSpan && f.tdColSpan > 1 ? f.tdColSpan : void 0,
5938
6225
  className: cn(
5939
- "box-border bg-[var(--sys-color-bg-frame)] px-[var(--cmp-table-keyValue-body-paddingX-default)] align-middle",
6226
+ "box-border bg-[var(--sys-color-bg-frame)] align-middle",
6227
+ // text 타입(및 미지정 기본값)은 읽기 전용 콘텐츠라 넓은 paddingX(wide),
6228
+ // 폼 필드 셀은 default paddingX 유지
6229
+ f.type == null || f.type === "text" ? "px-[var(--cmp-table-keyValue-body-paddingX-wide)]" : "px-[var(--cmp-table-keyValue-body-paddingX-default)]",
5940
6230
  cellBorder,
5941
6231
  f.tdClass
5942
6232
  ),