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
@@ -1,6 +1,5 @@
1
1
  "use client";
2
- import { createContext, forwardRef, useState, useRef, useImperativeHandle, useMemo, useContext, useEffect } from 'react';
3
- import * as Ariakit from '@ariakit/react';
2
+ import { createContext, forwardRef, useState, useRef, useImperativeHandle, useMemo, useEffect, useContext } from 'react';
4
3
  import { clsx } from 'clsx';
5
4
  import { extendTailwindMerge } from 'tailwind-merge';
6
5
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
@@ -17,10 +16,110 @@ function cn(...inputs) {
17
16
  return twMerge(clsx(inputs));
18
17
  }
19
18
  var PortalContainerContext = createContext(null);
19
+ var InsideModalContext = createContext(false);
20
20
  PortalContainerContext.Provider;
21
+ InsideModalContext.Provider;
21
22
  function usePortalContainer() {
22
23
  return useContext(PortalContainerContext) ?? void 0;
23
24
  }
25
+ function useInsideModal() {
26
+ return useContext(InsideModalContext);
27
+ }
28
+ function runRules(value, rules) {
29
+ if (!rules || rules.length === 0) return "";
30
+ for (const rule of rules) {
31
+ const result = rule(value ?? "");
32
+ if (result !== true) return typeof result === "string" ? result : "\uC720\uD6A8\uD558\uC9C0 \uC54A\uC740 \uAC12\uC785\uB2C8\uB2E4.";
33
+ }
34
+ return "";
35
+ }
36
+ var FormContext = createContext(null);
37
+ function useFormContext() {
38
+ return useContext(FormContext);
39
+ }
40
+ function useFormField(field) {
41
+ const ctx = useFormContext();
42
+ const cbRef = useRef(field);
43
+ cbRef.current = field;
44
+ const name = field.name ?? "";
45
+ useEffect(() => {
46
+ if (!ctx) return;
47
+ return ctx.register({
48
+ name,
49
+ validate: () => cbRef.current.validate(),
50
+ resetValidation: () => cbRef.current.resetValidation()
51
+ });
52
+ }, [ctx, name]);
53
+ }
54
+
55
+ // src/lib/sanitize-inline-html.ts
56
+ var ALLOWED_INLINE_TAGS = /* @__PURE__ */ new Set(["A", "B", "STRONG", "I", "EM", "BR", "SPAN"]);
57
+ var ALLOWED_ATTRS_BY_TAG = {
58
+ A: /* @__PURE__ */ new Set(["href", "target", "rel", "class"])
59
+ };
60
+ var COMMON_ALLOWED_ATTRS = /* @__PURE__ */ new Set(["class"]);
61
+ var SAFE_HREF_RE = /^(https?:|mailto:|tel:|\/|#|\.)/i;
62
+ var DROP_CONTENT_TAGS = /* @__PURE__ */ new Set([
63
+ "SCRIPT",
64
+ "STYLE",
65
+ "IFRAME",
66
+ "OBJECT",
67
+ "EMBED",
68
+ "META",
69
+ "LINK",
70
+ "BASE",
71
+ "NOSCRIPT",
72
+ // SVG/MATH 는 foreign-content 파싱 모드로 전환되어 재직렬화 시 다른 트리로
73
+ // 재파싱될 수 있다(mXSS 벡터). 인라인 라벨에 필요 없으므로 통째로 제거한다.
74
+ "SVG",
75
+ "MATH",
76
+ "TEMPLATE"
77
+ ]);
78
+ var escapeHtml = (value) => value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
79
+ var sanitizeNode = (node) => {
80
+ if (node.nodeType === Node.COMMENT_NODE) {
81
+ node.remove();
82
+ return;
83
+ }
84
+ if (node.nodeType !== Node.ELEMENT_NODE) return;
85
+ const element = node;
86
+ const tagName = element.tagName;
87
+ if (DROP_CONTENT_TAGS.has(tagName)) {
88
+ element.remove();
89
+ return;
90
+ }
91
+ Array.from(element.childNodes).forEach((child) => sanitizeNode(child));
92
+ if (ALLOWED_INLINE_TAGS.has(tagName)) {
93
+ if (element.hasAttribute("className")) {
94
+ const val = element.getAttribute("className");
95
+ element.removeAttribute("className");
96
+ if (!element.hasAttribute("class")) element.setAttribute("class", val);
97
+ }
98
+ const allowedAttrs = ALLOWED_ATTRS_BY_TAG[tagName] ?? COMMON_ALLOWED_ATTRS;
99
+ Array.from(element.attributes).forEach((attr) => {
100
+ if (!allowedAttrs.has(attr.name)) element.removeAttribute(attr.name);
101
+ });
102
+ if (tagName === "A") {
103
+ const href = element.getAttribute("href");
104
+ if (href !== null && href !== "" && !SAFE_HREF_RE.test(href)) element.removeAttribute("href");
105
+ if (element.hasAttribute("target")) {
106
+ element.setAttribute("rel", "noopener noreferrer");
107
+ }
108
+ }
109
+ return;
110
+ }
111
+ const parent = element.parentNode;
112
+ if (parent == null) return;
113
+ while (element.firstChild) parent.insertBefore(element.firstChild, element);
114
+ parent.removeChild(element);
115
+ };
116
+ var sanitizeInlineHtml = (value) => {
117
+ if (typeof document === "undefined") return escapeHtml(value);
118
+ const template = document.createElement("template");
119
+ template.innerHTML = value;
120
+ Array.from(template.content.childNodes).forEach((child) => sanitizeNode(child));
121
+ return template.innerHTML;
122
+ };
24
123
 
25
124
  // src/lib/color.ts
26
125
  var COLOR_KEYS = [
@@ -3193,32 +3292,6 @@ var STooltip = forwardRef(function STooltip2({
3193
3292
  ] });
3194
3293
  });
3195
3294
  STooltip.displayName = "STooltip";
3196
- function runRules(value, rules) {
3197
- if (!rules || rules.length === 0) return "";
3198
- for (const rule of rules) {
3199
- const result = rule(value ?? "");
3200
- if (result !== true) return typeof result === "string" ? result : "\uC720\uD6A8\uD558\uC9C0 \uC54A\uC740 \uAC12\uC785\uB2C8\uB2E4.";
3201
- }
3202
- return "";
3203
- }
3204
- var FormContext = createContext(null);
3205
- function useFormContext() {
3206
- return useContext(FormContext);
3207
- }
3208
- function useFormField(field) {
3209
- const ctx = useFormContext();
3210
- const cbRef = useRef(field);
3211
- cbRef.current = field;
3212
- const name = field.name ?? "";
3213
- useEffect(() => {
3214
- if (!ctx) return;
3215
- return ctx.register({
3216
- name,
3217
- validate: () => cbRef.current.validate(),
3218
- resetValidation: () => cbRef.current.resetValidation()
3219
- });
3220
- }, [ctx, name]);
3221
- }
3222
3295
  var FIELD_SIZE = {
3223
3296
  sm: { height: 28, radius: 4, fieldGap: 12, labelGap: 6, fontSize: 12, lineHeight: 20, icon: 16 },
3224
3297
  md: { height: 36, radius: 6, fieldGap: 16, labelGap: 8, fontSize: 14, lineHeight: 24, icon: 16 }
@@ -3233,6 +3306,7 @@ var SField = forwardRef(function SField2({
3233
3306
  name,
3234
3307
  label = "",
3235
3308
  icon,
3309
+ iconColor,
3236
3310
  labelTooltip = "",
3237
3311
  labelTooltipProps,
3238
3312
  labelWidth,
@@ -3310,7 +3384,15 @@ var SField = forwardRef(function SField2({
3310
3384
  lineHeight: `${s.lineHeight}px`
3311
3385
  },
3312
3386
  children: [
3313
- icon && /* @__PURE__ */ jsx(SIcon, { name: icon, size: s.icon, style: { marginRight: s.labelGap } }),
3387
+ icon && /* @__PURE__ */ jsx(
3388
+ SIcon,
3389
+ {
3390
+ name: icon,
3391
+ size: s.icon,
3392
+ color: iconColor,
3393
+ style: { marginRight: s.labelGap }
3394
+ }
3395
+ ),
3314
3396
  /* @__PURE__ */ jsx("span", { className: "min-w-0 overflow-hidden text-ellipsis whitespace-nowrap", children: label }),
3315
3397
  (labelTooltip !== "" || labelTooltipProps) && /* @__PURE__ */ jsx("span", { className: "shrink-0", style: { marginLeft: s.labelGap }, children: /* @__PURE__ */ jsx(
3316
3398
  STooltip,
@@ -3360,7 +3442,7 @@ var SField = forwardRef(function SField2({
3360
3442
  borderBottomLeftRadius: s.radius - 1
3361
3443
  },
3362
3444
  children: [
3363
- icon && !label && /* @__PURE__ */ jsx(SIcon, { name: icon, size: s.icon }),
3445
+ icon && !label && /* @__PURE__ */ jsx(SIcon, { name: icon, size: s.icon, color: iconColor }),
3364
3446
  effectiveAddonLabel
3365
3447
  ]
3366
3448
  }
@@ -3375,6 +3457,77 @@ var SField = forwardRef(function SField2({
3375
3457
  }
3376
3458
  );
3377
3459
  });
3460
+ function SPortal({
3461
+ open,
3462
+ onClose,
3463
+ onOpenChange,
3464
+ anchorRef,
3465
+ placement = "bottom",
3466
+ align = "start",
3467
+ offset = 4,
3468
+ alignOffset = 0,
3469
+ fit = false,
3470
+ maxHeight,
3471
+ autoClose = false,
3472
+ persistent = false,
3473
+ viewportPadding = 0,
3474
+ onOpenAutoFocus,
3475
+ onCloseAutoFocus,
3476
+ children,
3477
+ className,
3478
+ style,
3479
+ ariaLabel
3480
+ }) {
3481
+ const portalContainer = usePortalContainer();
3482
+ return /* @__PURE__ */ jsxs(
3483
+ RPopover2.Root,
3484
+ {
3485
+ open,
3486
+ onOpenChange: (next) => {
3487
+ onOpenChange?.(next);
3488
+ if (!next) onClose?.();
3489
+ },
3490
+ children: [
3491
+ /* @__PURE__ */ jsx(RPopover2.Anchor, { virtualRef: anchorRef }),
3492
+ /* @__PURE__ */ jsx(RPopover2.Portal, { container: portalContainer, children: /* @__PURE__ */ jsx(
3493
+ RPopover2.Content,
3494
+ {
3495
+ side: placement,
3496
+ align,
3497
+ sideOffset: offset,
3498
+ alignOffset,
3499
+ collisionPadding: viewportPadding,
3500
+ "aria-label": ariaLabel,
3501
+ onPointerDownOutside: (e) => {
3502
+ if (persistent || anchorRef.current?.contains(e.target)) {
3503
+ e.preventDefault();
3504
+ }
3505
+ },
3506
+ onFocusOutside: (e) => {
3507
+ if (persistent || anchorRef.current?.contains(e.target)) {
3508
+ e.preventDefault();
3509
+ }
3510
+ },
3511
+ onEscapeKeyDown: persistent ? (e) => e.preventDefault() : void 0,
3512
+ onOpenAutoFocus,
3513
+ onCloseAutoFocus,
3514
+ onClick: autoClose ? () => onClose?.() : void 0,
3515
+ className: cn(
3516
+ "z-50 overflow-hidden rounded-(--cmp-listBox-radius) bg-(--cmp-listBox-bg) py-4 shadow-normal-sm",
3517
+ maxHeight != null && "overflow-y-auto",
3518
+ fit && "min-w-(--radix-popover-trigger-width)",
3519
+ FLOATING_SLIDE_ANIM,
3520
+ className
3521
+ ),
3522
+ style: { maxHeight, ...style },
3523
+ children
3524
+ }
3525
+ ) })
3526
+ ]
3527
+ }
3528
+ );
3529
+ }
3530
+ SPortal.displayName = "SPortal";
3378
3531
 
3379
3532
  // src/components/SSelect/select.config.ts
3380
3533
  function flattenOptions(options, depth = 1) {
@@ -3409,75 +3562,6 @@ function flattenOptions(options, depth = 1) {
3409
3562
  var selectableOptions = (flat) => flat.filter((o) => !o.isGroup && !o.disabled);
3410
3563
  var isHtmlLabel = (label) => /<[a-z][\s\S]*>/i.test(label);
3411
3564
  var extractText = (html) => html.replace(/<[^>]*>/g, "");
3412
-
3413
- // src/lib/sanitize-inline-html.ts
3414
- var ALLOWED_INLINE_TAGS = /* @__PURE__ */ new Set(["A", "B", "STRONG", "I", "EM", "BR", "SPAN"]);
3415
- var ALLOWED_ATTRS_BY_TAG = {
3416
- A: /* @__PURE__ */ new Set(["href", "target", "rel", "class"])
3417
- };
3418
- var COMMON_ALLOWED_ATTRS = /* @__PURE__ */ new Set(["class"]);
3419
- var SAFE_HREF_RE = /^(https?:|mailto:|tel:|\/|#|\.)/i;
3420
- var DROP_CONTENT_TAGS = /* @__PURE__ */ new Set([
3421
- "SCRIPT",
3422
- "STYLE",
3423
- "IFRAME",
3424
- "OBJECT",
3425
- "EMBED",
3426
- "META",
3427
- "LINK",
3428
- "BASE",
3429
- "NOSCRIPT",
3430
- // SVG/MATH 는 foreign-content 파싱 모드로 전환되어 재직렬화 시 다른 트리로
3431
- // 재파싱될 수 있다(mXSS 벡터). 인라인 라벨에 필요 없으므로 통째로 제거한다.
3432
- "SVG",
3433
- "MATH",
3434
- "TEMPLATE"
3435
- ]);
3436
- var escapeHtml = (value) => value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
3437
- var sanitizeNode = (node) => {
3438
- if (node.nodeType === Node.COMMENT_NODE) {
3439
- node.remove();
3440
- return;
3441
- }
3442
- if (node.nodeType !== Node.ELEMENT_NODE) return;
3443
- const element = node;
3444
- const tagName = element.tagName;
3445
- if (DROP_CONTENT_TAGS.has(tagName)) {
3446
- element.remove();
3447
- return;
3448
- }
3449
- Array.from(element.childNodes).forEach((child) => sanitizeNode(child));
3450
- if (ALLOWED_INLINE_TAGS.has(tagName)) {
3451
- if (element.hasAttribute("className")) {
3452
- const val = element.getAttribute("className");
3453
- element.removeAttribute("className");
3454
- if (!element.hasAttribute("class")) element.setAttribute("class", val);
3455
- }
3456
- const allowedAttrs = ALLOWED_ATTRS_BY_TAG[tagName] ?? COMMON_ALLOWED_ATTRS;
3457
- Array.from(element.attributes).forEach((attr) => {
3458
- if (!allowedAttrs.has(attr.name)) element.removeAttribute(attr.name);
3459
- });
3460
- if (tagName === "A") {
3461
- const href = element.getAttribute("href");
3462
- if (href !== null && href !== "" && !SAFE_HREF_RE.test(href)) element.removeAttribute("href");
3463
- if (element.hasAttribute("target")) {
3464
- element.setAttribute("rel", "noopener noreferrer");
3465
- }
3466
- }
3467
- return;
3468
- }
3469
- const parent = element.parentNode;
3470
- if (parent == null) return;
3471
- while (element.firstChild) parent.insertBefore(element.firstChild, element);
3472
- parent.removeChild(element);
3473
- };
3474
- var sanitizeInlineHtml = (value) => {
3475
- if (typeof document === "undefined") return escapeHtml(value);
3476
- const template = document.createElement("template");
3477
- template.innerHTML = value;
3478
- Array.from(template.content.childNodes).forEach((child) => sanitizeNode(child));
3479
- return template.innerHTML;
3480
- };
3481
3565
  var SEARCH_THRESHOLD = 11;
3482
3566
  var SSelect = forwardRef(function SSelect2({
3483
3567
  value,
@@ -3501,6 +3585,7 @@ var SSelect = forwardRef(function SSelect2({
3501
3585
  label,
3502
3586
  labelWidth,
3503
3587
  icon,
3588
+ iconColor,
3504
3589
  labelTooltip,
3505
3590
  addonLabel,
3506
3591
  addonAlign,
@@ -3512,18 +3597,15 @@ var SSelect = forwardRef(function SSelect2({
3512
3597
  }, ref) {
3513
3598
  const multi = type === "multi" || type === "multi_depth";
3514
3599
  const portalContainer = usePortalContainer();
3600
+ const insideModal = useInsideModal();
3515
3601
  const [search, setSearch] = useState("");
3516
3602
  const [ruleError, setRuleError] = useState("");
3517
3603
  const [open, setOpen] = useState(false);
3604
+ const [pendingOpen, setPendingOpen] = useState(false);
3605
+ const [focusedIndex, setFocusedIndex] = useState(-1);
3518
3606
  const triggerRef = useRef(null);
3519
- useImperativeHandle(
3520
- ref,
3521
- () => ({
3522
- focus: () => triggerRef.current?.focus(),
3523
- open: () => setOpen(true)
3524
- }),
3525
- []
3526
- );
3607
+ const searchInputRef = useRef(null);
3608
+ const listRef = useRef(null);
3527
3609
  const flat = useMemo(() => flattenOptions(options), [options]);
3528
3610
  const selectable = useMemo(() => selectableOptions(flat), [flat]);
3529
3611
  const hasLeaf = useMemo(() => flat.some((o) => !o.isGroup), [flat]);
@@ -3541,6 +3623,46 @@ var SSelect = forwardRef(function SSelect2({
3541
3623
  const arr = Array.isArray(value) ? value : [value];
3542
3624
  return arr.map((v) => v != null && typeof v === "object" ? v.value : v).filter((v) => v != null && v !== "").map(String);
3543
3625
  }, [value]);
3626
+ const selectedRef = useRef(selected);
3627
+ selectedRef.current = selected;
3628
+ const emit = (next) => {
3629
+ if (!onValueChange) return;
3630
+ const mapped = next.map(
3631
+ (v) => emitValue ? rawByValue.get(v) ?? v : optionByValue.get(v) ?? { value: v, label: v }
3632
+ );
3633
+ onValueChange(multi ? mapped : mapped[0] ?? (emitValue ? "" : null));
3634
+ };
3635
+ const commitOpen = (next) => {
3636
+ setOpen(next);
3637
+ onOpenChange?.(next);
3638
+ if (!next) {
3639
+ setPendingOpen(false);
3640
+ setSearch("");
3641
+ if (rules && rules.length > 0) {
3642
+ setRuleError(runRules(multi ? selectedRef.current : selectedRef.current[0] ?? "", rules));
3643
+ }
3644
+ }
3645
+ };
3646
+ const requestOpen = (next) => {
3647
+ if (next && insideModal && portalContainer == null) {
3648
+ setPendingOpen(true);
3649
+ return;
3650
+ }
3651
+ commitOpen(next);
3652
+ };
3653
+ useEffect(() => {
3654
+ if (!insideModal || portalContainer == null || !pendingOpen) return;
3655
+ setPendingOpen(false);
3656
+ commitOpen(true);
3657
+ }, [insideModal, pendingOpen, portalContainer]);
3658
+ useImperativeHandle(
3659
+ ref,
3660
+ () => ({
3661
+ focus: () => triggerRef.current?.focus(),
3662
+ open: () => requestOpen(true)
3663
+ }),
3664
+ [insideModal, portalContainer]
3665
+ );
3544
3666
  useFormField({
3545
3667
  name,
3546
3668
  validate: () => {
@@ -3550,13 +3672,6 @@ var SSelect = forwardRef(function SSelect2({
3550
3672
  },
3551
3673
  resetValidation: () => setRuleError("")
3552
3674
  });
3553
- const emit = (next) => {
3554
- if (!onValueChange) return;
3555
- const mapped = next.map(
3556
- (v) => emitValue ? rawByValue.get(v) ?? v : optionByValue.get(v) ?? { value: v, label: v }
3557
- );
3558
- onValueChange(multi ? mapped : mapped[0] ?? (emitValue ? "" : null));
3559
- };
3560
3675
  const filtered = useMemo(() => {
3561
3676
  if (!showSearch || search.trim() === "") return flat;
3562
3677
  const q = search.trim().toLowerCase();
@@ -3567,6 +3682,16 @@ var SSelect = forwardRef(function SSelect2({
3567
3682
  const allValues = selectable.map((o) => o.value);
3568
3683
  const allSelected = allValues.length > 0 && allValues.every((v) => selected.includes(v));
3569
3684
  const hasItems = filtered.some((o) => !o.isGroup);
3685
+ const navigableItems = useMemo(() => {
3686
+ const items = [];
3687
+ if (multi && useSelectAll && allValues.length > 0) {
3688
+ items.push({ value: "__select_all__", isSelectAll: true });
3689
+ }
3690
+ filtered.forEach((o) => {
3691
+ if (!o.isGroup && !o.disabled) items.push({ value: o.value });
3692
+ });
3693
+ return items;
3694
+ }, [allValues.length, filtered, multi, useSelectAll]);
3570
3695
  const triggerText = (() => {
3571
3696
  if (selected.length === 0) return placeholder;
3572
3697
  if (multi) {
@@ -3576,26 +3701,119 @@ var SSelect = forwardRef(function SSelect2({
3576
3701
  return labelByValue.get(selected[0]) ?? selected[0];
3577
3702
  })();
3578
3703
  const isPlaceholder = selected.length === 0;
3579
- const selectStore = /* @__PURE__ */ jsxs(
3580
- Ariakit.SelectProvider,
3704
+ const handleSelect = (nextValue) => {
3705
+ if (multi) {
3706
+ const next = selected.includes(nextValue) ? selected.filter((v) => v !== nextValue) : [...selected, nextValue];
3707
+ emit(next);
3708
+ return;
3709
+ }
3710
+ emit([nextValue]);
3711
+ commitOpen(false);
3712
+ };
3713
+ const handleSelectAll = () => {
3714
+ emit(allSelected ? [] : allValues);
3715
+ };
3716
+ useEffect(() => {
3717
+ if (!open) {
3718
+ setFocusedIndex(-1);
3719
+ return;
3720
+ }
3721
+ setFocusedIndex(navigableItems.length > 0 ? 0 : -1);
3722
+ }, [open, navigableItems.length]);
3723
+ useEffect(() => {
3724
+ if (!open) return;
3725
+ setFocusedIndex(navigableItems.length > 0 ? 0 : -1);
3726
+ }, [search, open, navigableItems.length]);
3727
+ useEffect(() => {
3728
+ if (!open || focusedIndex < 0) return;
3729
+ const list = listRef.current;
3730
+ const focusedEl = list?.querySelector('[data-select-focused="true"]');
3731
+ if (!list || !focusedEl) return;
3732
+ const listRect = list.getBoundingClientRect();
3733
+ const itemRect = focusedEl.getBoundingClientRect();
3734
+ if (itemRect.top < listRect.top) {
3735
+ list.scrollTop += itemRect.top - listRect.top;
3736
+ } else if (itemRect.bottom > listRect.bottom) {
3737
+ list.scrollTop += itemRect.bottom - listRect.bottom;
3738
+ }
3739
+ }, [focusedIndex, open]);
3740
+ const moveFocusedIndex = (direction) => {
3741
+ if (!open || navigableItems.length === 0) return;
3742
+ setFocusedIndex((current) => {
3743
+ if (direction === "next") {
3744
+ return current < 0 ? 0 : (current + 1) % navigableItems.length;
3745
+ }
3746
+ return current <= 0 ? navigableItems.length - 1 : current - 1;
3747
+ });
3748
+ };
3749
+ const selectFocusedItem = () => {
3750
+ if (focusedIndex < 0 || focusedIndex >= navigableItems.length) return;
3751
+ const focused = navigableItems[focusedIndex];
3752
+ if (focused.isSelectAll) {
3753
+ handleSelectAll();
3754
+ return;
3755
+ }
3756
+ handleSelect(focused.value);
3757
+ };
3758
+ const handleKeyDown = (event) => {
3759
+ if (!open) {
3760
+ if ((event.key === "ArrowDown" || event.key === "ArrowUp") && !effectiveDisabled) {
3761
+ event.preventDefault();
3762
+ requestOpen(true);
3763
+ }
3764
+ return;
3765
+ }
3766
+ if (event.key === "ArrowDown") {
3767
+ event.preventDefault();
3768
+ moveFocusedIndex("next");
3769
+ return;
3770
+ }
3771
+ if (event.key === "ArrowUp") {
3772
+ event.preventDefault();
3773
+ moveFocusedIndex("prev");
3774
+ return;
3775
+ }
3776
+ if (event.key === "Enter") {
3777
+ event.preventDefault();
3778
+ selectFocusedItem();
3779
+ return;
3780
+ }
3781
+ if (event.key === "Escape") {
3782
+ event.preventDefault();
3783
+ commitOpen(false);
3784
+ }
3785
+ };
3786
+ return /* @__PURE__ */ jsxs(
3787
+ SField,
3581
3788
  {
3582
- animated: portalContainer == null ? 150 : false,
3583
- value: multi ? selected : selected[0] ?? "",
3584
- setValue: (v) => emit(Array.isArray(v) ? v : v === "" ? [] : [v]),
3585
- open,
3586
- setOpen: (o) => {
3587
- setOpen(o);
3588
- onOpenChange?.(o);
3589
- if (!o && rules && rules.length > 0) {
3590
- setRuleError(runRules(multi ? selected : selected[0] ?? "", rules));
3591
- }
3592
- },
3789
+ label,
3790
+ labelWidth,
3791
+ icon,
3792
+ iconColor,
3793
+ labelTooltip,
3794
+ labelTooltipProps,
3795
+ addonLabel,
3796
+ addonAlign,
3797
+ hint,
3798
+ error: error || ruleError !== "",
3799
+ status: rules && rules.length > 0 ? ruleError !== "" ? "error" : void 0 : void 0,
3800
+ errorMessage: ruleError !== "" ? ruleError : errorMessage,
3801
+ disabled: effectiveDisabled,
3802
+ width,
3803
+ className,
3804
+ style,
3593
3805
  children: [
3806
+ name && /* @__PURE__ */ jsx("input", { type: "hidden", name, value: selected.join(",") }),
3594
3807
  /* @__PURE__ */ jsxs(
3595
- Ariakit.Select,
3808
+ "button",
3596
3809
  {
3597
3810
  ref: triggerRef,
3811
+ type: "button",
3598
3812
  disabled: effectiveDisabled,
3813
+ "aria-haspopup": "listbox",
3814
+ "aria-expanded": open,
3815
+ onClick: () => requestOpen(!open),
3816
+ onKeyDown: handleKeyDown,
3599
3817
  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",
3600
3818
  style: {
3601
3819
  color: effectiveDisabled ? "var(--cmp-select-text-disabled)" : isPlaceholder ? "var(--cmp-select-icon-default)" : "var(--cmp-select-text-default)"
@@ -3620,178 +3838,229 @@ var SSelect = forwardRef(function SSelect2({
3620
3838
  ]
3621
3839
  }
3622
3840
  ),
3623
- /* @__PURE__ */ jsxs(
3624
- Ariakit.SelectPopover,
3841
+ /* @__PURE__ */ jsx(
3842
+ SPortal,
3625
3843
  {
3626
- portal: true,
3627
- portalElement: portalContainer,
3628
- gutter: 4,
3629
- sameWidth: !dropdownWidth,
3630
- 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",
3844
+ open,
3845
+ onClose: () => commitOpen(false),
3846
+ onOpenChange: (next) => {
3847
+ if (next) requestOpen(true);
3848
+ },
3849
+ anchorRef: triggerRef,
3850
+ placement: "bottom",
3851
+ align: "start",
3852
+ offset: 4,
3853
+ fit: !dropdownWidth,
3854
+ onOpenAutoFocus: (event) => {
3855
+ if (!showSearch) {
3856
+ event.preventDefault();
3857
+ return;
3858
+ }
3859
+ event.preventDefault();
3860
+ requestAnimationFrame(() => searchInputRef.current?.focus());
3861
+ },
3862
+ onCloseAutoFocus: (event) => {
3863
+ event.preventDefault();
3864
+ triggerRef.current?.focus();
3865
+ },
3866
+ className: "rounded-[var(--cmp-listBox-radius)] bg-[var(--cmp-listBox-bg)] py-[4px] shadow-[2px_2px_12px_2px_rgba(0,0,0,0.1)]",
3631
3867
  style: {
3632
- maxHeight: dropdownHeight,
3633
3868
  ...dropdownWidth ? { width: dropdownWidth } : {},
3634
3869
  maxWidth: maxDropdownWidth
3635
3870
  },
3636
- children: [
3637
- showSearch && // 원본 sd-select-list-item-search: sticky + 검색 아이콘 + 테두리 input + focus-within + clear
3638
- /* @__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: [
3639
- /* @__PURE__ */ jsx(SIcon, { name: "search", size: 16, color: "var(--color-grey-70)", className: "shrink-0" }),
3640
- /* @__PURE__ */ jsx(
3641
- Ariakit.Combobox,
3642
- {
3643
- autoSelect: true,
3644
- placeholder: "\uAC80\uC0C9",
3645
- 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)]"
3646
- }
3647
- ),
3648
- search !== "" && /* @__PURE__ */ jsx(
3649
- SGhostButton,
3650
- {
3651
- icon: "close",
3652
- size: "xxs",
3653
- intent: "default",
3654
- ariaLabel: "\uAC80\uC0C9\uC5B4 \uC9C0\uC6B0\uAE30",
3655
- onClick: () => setSearch(""),
3656
- className: "shrink-0"
3657
- }
3658
- )
3659
- ] }) }),
3660
- multi && useSelectAll && /* @__PURE__ */ jsx(
3661
- "button",
3662
- {
3663
- type: "button",
3664
- 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)]",
3665
- onClick: () => emit(allSelected ? [] : allValues),
3666
- children: allSelectedLabel
3667
- }
3668
- ),
3669
- hasItems ? /* @__PURE__ */ jsx(ItemList, { items: filtered, multi, selected, useSearch: showSearch }) : (
3670
- // 원본 sd-select-listbox__empty (표시할 항목이 없을 때)
3671
- /* @__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." })
3672
- )
3673
- ]
3871
+ children: /* @__PURE__ */ jsxs(
3872
+ "div",
3873
+ {
3874
+ ref: listRef,
3875
+ 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)]",
3876
+ style: { maxHeight: dropdownHeight },
3877
+ onMouseLeave: () => setFocusedIndex(-1),
3878
+ children: [
3879
+ 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: [
3880
+ /* @__PURE__ */ jsx(SIcon, { name: "search", size: 16, color: "var(--color-grey-70)", className: "shrink-0" }),
3881
+ /* @__PURE__ */ jsx(
3882
+ "input",
3883
+ {
3884
+ ref: searchInputRef,
3885
+ value: search,
3886
+ onChange: (e) => setSearch(e.target.value),
3887
+ onKeyDown: handleKeyDown,
3888
+ placeholder: "\uAC80\uC0C9",
3889
+ 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)]"
3890
+ }
3891
+ ),
3892
+ search !== "" && /* @__PURE__ */ jsx(
3893
+ SGhostButton,
3894
+ {
3895
+ icon: "close",
3896
+ size: "xxs",
3897
+ intent: "default",
3898
+ ariaLabel: "\uAC80\uC0C9\uC5B4 \uC9C0\uC6B0\uAE30",
3899
+ onClick: () => setSearch(""),
3900
+ className: "shrink-0"
3901
+ }
3902
+ )
3903
+ ] }) }),
3904
+ hasItems ? /* @__PURE__ */ jsx(
3905
+ ItemList,
3906
+ {
3907
+ items: filtered,
3908
+ multi,
3909
+ selected,
3910
+ focusedValue: navigableItems[focusedIndex]?.isSelectAll ? "__select_all__" : navigableItems[focusedIndex]?.value,
3911
+ showSelectAll: multi && useSelectAll && allValues.length > 0,
3912
+ selectAllLabel: allSelectedLabel,
3913
+ selectAllSelected: allSelected,
3914
+ onSelectAll: handleSelectAll,
3915
+ onSelect: handleSelect,
3916
+ onHoverValue: (value2) => {
3917
+ const index = navigableItems.findIndex(
3918
+ (item) => item.isSelectAll ? value2 === "__select_all__" : item.value === value2
3919
+ );
3920
+ if (index >= 0) setFocusedIndex(index);
3921
+ }
3922
+ }
3923
+ ) : /* @__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." })
3924
+ ]
3925
+ }
3926
+ )
3674
3927
  }
3675
3928
  )
3676
3929
  ]
3677
3930
  }
3678
3931
  );
3679
- const control = showSearch ? /* @__PURE__ */ jsx(Ariakit.ComboboxProvider, { value: search, setValue: setSearch, resetValueOnHide: true, children: selectStore }) : selectStore;
3680
- return /* @__PURE__ */ jsxs(
3681
- SField,
3682
- {
3683
- label,
3684
- labelWidth,
3685
- icon,
3686
- labelTooltip,
3687
- labelTooltipProps,
3688
- addonLabel,
3689
- addonAlign,
3690
- hint,
3691
- error: error || ruleError !== "",
3692
- status: rules && rules.length > 0 ? ruleError !== "" ? "error" : void 0 : void 0,
3693
- errorMessage: ruleError !== "" ? ruleError : errorMessage,
3694
- disabled: effectiveDisabled,
3695
- width,
3696
- className,
3697
- style,
3698
- children: [
3699
- name && /* @__PURE__ */ jsx("input", { type: "hidden", name, value: selected.join(",") }),
3700
- control
3701
- ]
3702
- }
3703
- );
3704
3932
  });
3705
3933
  SSelect.displayName = "SSelect";
3706
3934
  function ItemList({
3707
3935
  items,
3708
3936
  multi,
3709
3937
  selected,
3710
- useSearch
3938
+ focusedValue,
3939
+ showSelectAll,
3940
+ selectAllLabel,
3941
+ selectAllSelected,
3942
+ onSelectAll,
3943
+ onSelect,
3944
+ onHoverValue
3711
3945
  }) {
3712
3946
  const itemBase = cn(
3713
- // group: 체크박스 inverse(group-data-[active-item])를 위해 필요
3714
- "group flex cursor-pointer select-none items-center gap-[8px] text-[12px] leading-[20px] outline-none",
3947
+ "group flex w-full cursor-pointer select-none items-center gap-[8px] border-none bg-transparent text-left text-[12px] leading-[20px] outline-none",
3715
3948
  "text-[color:var(--cmp-listItem-content-default)]",
3716
- // Ariakit은 disabled 항목에 aria-disabled를 붙이므로 aria-disabled 변형으로 스타일 (data-disabled 아님)
3717
- "aria-disabled:cursor-not-allowed aria-disabled:text-[color:var(--cmp-listItem-content-disabled)]"
3718
- );
3719
- const activeClass = "data-[active-item]:bg-[var(--cmp-listItem-bg-hover)] data-[active-item]:text-[color:var(--cmp-listItem-content-hover)]";
3720
- return /* @__PURE__ */ jsx(Fragment, { children: items.map((o) => {
3721
- const padLeft = o.depth === 1 ? 12 : o.depth === 2 ? 20 : 28;
3722
- if (o.isGroup) {
3723
- const isDepth1 = o.depth === 1;
3724
- return /* @__PURE__ */ jsx(
3725
- "div",
3726
- {
3727
- className: cn(
3728
- "select-none py-[4px] pr-[12px] text-[12px] leading-[20px] text-[color:var(--cmp-listItem-content-default)]",
3729
- 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",
3730
- // 하위가 전부 비활성이면(select.config 에서 bubble-up) 어미 그룹도 비활성 처리:
3731
- // 텍스트는 회색(#888), depth1 그룹은 파란 배경 대신 회색(#EEE)으로 (depth2 는 #F6F6F6 유지)
3732
- o.disabled && "cursor-not-allowed text-[color:var(--cmp-listItem-content-disabled)]",
3733
- o.disabled && isDepth1 && "bg-[var(--cmp-listItem-depth1-bg-disabled)]"
3734
- ),
3735
- style: { paddingLeft: padLeft },
3736
- children: o.label
3737
- },
3738
- o.value
3739
- );
3740
- }
3741
- const isSelected = selected.includes(o.value);
3742
- return /* @__PURE__ */ jsxs(
3743
- Ariakit.SelectItem,
3949
+ "disabled:cursor-not-allowed disabled:text-[color:var(--cmp-listItem-content-disabled)]"
3950
+ );
3951
+ 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)]";
3952
+ return /* @__PURE__ */ jsxs("div", { role: "listbox", "aria-multiselectable": multi || void 0, children: [
3953
+ showSelectAll && /* @__PURE__ */ jsxs(
3954
+ "button",
3744
3955
  {
3745
- value: o.value,
3746
- disabled: o.disabled,
3747
- accessibleWhenDisabled: true,
3956
+ type: "button",
3957
+ "data-select-focused": focusedValue === "__select_all__" ? "true" : void 0,
3958
+ "data-select-active": focusedValue === "__select_all__" ? "true" : void 0,
3748
3959
  className: cn(
3749
3960
  itemBase,
3750
- isSelected && !multi && "font-bold text-[color:var(--cmp-listItem-content-selected)]",
3751
- // Ariakit은 disabled 항목도 hover 시 active(data-active-item) 처리하므로,
3752
- // 비활성 항목에는 hover 하이라이트를 적용하지 않아 회색 비활성 스타일을 유지한다.
3753
- // (원본 Stencil의 `--focused...:not(--disabled)` 규칙과 동일한 의도)
3754
- !o.disabled && activeClass
3755
- ),
3756
- style: { paddingTop: 4, paddingBottom: 4, paddingRight: 12, paddingLeft: padLeft },
3757
- render: useSearch ? /* @__PURE__ */ jsx(Ariakit.ComboboxItem, {}) : void 0,
3961
+ focusedValue === "__select_all__" && "bg-[var(--cmp-listItem-bg-hover)] text-[color:var(--cmp-listItem-content-hover)]"
3962
+ ),
3963
+ style: { paddingTop: 4, paddingBottom: 4, paddingRight: 12, paddingLeft: 12 },
3964
+ onMouseEnter: () => onHoverValue("__select_all__"),
3965
+ onClick: onSelectAll,
3758
3966
  children: [
3759
- multi && // hover(active) 시 checkbox inverse — 파란 hover 배경 위에서 checked/unchecked 모두 흰 테두리 + 투명 배경
3760
3967
  /* @__PURE__ */ jsx(
3761
3968
  "span",
3762
3969
  {
3763
3970
  className: cn(
3764
3971
  "flex h-[16px] w-[16px] flex-shrink-0 items-center justify-center rounded-[2px] border border-solid",
3765
- o.disabled ? (
3766
- // 비활성: 회색 채움(#E1E1E1) + 회색 테두리(#CCCCCC)
3767
- "cursor-not-allowed border-[color:var(--cmp-checkbox-border-disabled)] bg-[var(--cmp-checkbox-bg-disabled)]"
3768
- ) : cn(
3769
- "group-data-[active-item]:border-[color:var(--cmp-checkbox-checked-border-inverse)]",
3770
- 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"
3771
- )
3972
+ 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)]"
3772
3973
  ),
3773
- children: isSelected && /* @__PURE__ */ jsx(
3974
+ children: selectAllSelected && /* @__PURE__ */ jsx(
3774
3975
  SIcon,
3775
3976
  {
3776
3977
  name: "checkboxCheck",
3777
3978
  size: 12,
3778
- color: o.disabled ? "var(--cmp-checkbox-checked-icon-disabled)" : "var(--cmp-checkbox-checked-icon-default)"
3979
+ color: "var(--cmp-checkbox-checked-icon-default)"
3779
3980
  }
3780
3981
  )
3781
3982
  }
3782
3983
  ),
3783
- isHtmlLabel(o.label) ? /* @__PURE__ */ jsx(
3784
- "span",
3785
- {
3786
- className: "flex-1 truncate",
3787
- dangerouslySetInnerHTML: { __html: sanitizeInlineHtml(o.label) }
3788
- }
3789
- ) : /* @__PURE__ */ jsx("span", { className: "flex-1 truncate", children: o.label })
3984
+ /* @__PURE__ */ jsx("span", { className: "flex-1 truncate", children: selectAllLabel })
3790
3985
  ]
3791
- },
3792
- o.value
3793
- );
3794
- }) });
3986
+ }
3987
+ ),
3988
+ items.map((o) => {
3989
+ const padLeft = o.depth === 1 ? 12 : o.depth === 2 ? 20 : 28;
3990
+ if (o.isGroup) {
3991
+ const isDepth1 = o.depth === 1;
3992
+ return /* @__PURE__ */ jsx(
3993
+ "div",
3994
+ {
3995
+ className: cn(
3996
+ "select-none py-[4px] pr-[12px] text-[12px] leading-[20px] text-[color:var(--cmp-listItem-content-default)]",
3997
+ 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",
3998
+ // 하위가 전부 비활성이면(select.config 에서 bubble-up) 어미 그룹도 비활성 처리:
3999
+ // 텍스트는 회색(#888), depth1 그룹은 파란 배경 대신 회색(#EEE)으로 (depth2 는 #F6F6F6 유지)
4000
+ o.disabled && "cursor-not-allowed text-[color:var(--cmp-listItem-content-disabled)]",
4001
+ o.disabled && isDepth1 && "bg-[var(--cmp-listItem-depth1-bg-disabled)]"
4002
+ ),
4003
+ style: { paddingLeft: padLeft },
4004
+ children: o.label
4005
+ },
4006
+ o.value
4007
+ );
4008
+ }
4009
+ const isSelected = selected.includes(o.value);
4010
+ return /* @__PURE__ */ jsxs(
4011
+ "button",
4012
+ {
4013
+ type: "button",
4014
+ disabled: o.disabled,
4015
+ role: "option",
4016
+ "aria-selected": isSelected,
4017
+ "data-select-focused": focusedValue === o.value ? "true" : void 0,
4018
+ "data-select-active": focusedValue === o.value ? "true" : void 0,
4019
+ className: cn(
4020
+ itemBase,
4021
+ isSelected && !multi && "font-bold text-[color:var(--cmp-listItem-content-selected)]",
4022
+ focusedValue === o.value && "bg-[var(--cmp-listItem-bg-hover)] text-[color:var(--cmp-listItem-content-hover)]",
4023
+ activeClass
4024
+ ),
4025
+ style: { paddingTop: 4, paddingBottom: 4, paddingRight: 12, paddingLeft: padLeft },
4026
+ onMouseEnter: () => onHoverValue(o.value),
4027
+ onClick: () => onSelect(o.value),
4028
+ children: [
4029
+ multi && /* @__PURE__ */ jsx(
4030
+ "span",
4031
+ {
4032
+ className: cn(
4033
+ "flex h-[16px] w-[16px] flex-shrink-0 items-center justify-center rounded-[2px] border border-solid",
4034
+ o.disabled ? (
4035
+ // 비활성: 회색 채움(#E1E1E1) + 회색 테두리(#CCCCCC)
4036
+ "cursor-not-allowed border-[color:var(--cmp-checkbox-border-disabled)] bg-[var(--cmp-checkbox-bg-disabled)]"
4037
+ ) : cn(
4038
+ 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)]"
4039
+ )
4040
+ ),
4041
+ children: isSelected && /* @__PURE__ */ jsx(
4042
+ SIcon,
4043
+ {
4044
+ name: "checkboxCheck",
4045
+ size: 12,
4046
+ color: o.disabled ? "var(--cmp-checkbox-checked-icon-disabled)" : "var(--cmp-checkbox-checked-icon-default)"
4047
+ }
4048
+ )
4049
+ }
4050
+ ),
4051
+ isHtmlLabel(o.label) ? /* @__PURE__ */ jsx(
4052
+ "span",
4053
+ {
4054
+ className: "flex-1 truncate",
4055
+ dangerouslySetInnerHTML: { __html: sanitizeInlineHtml(o.label) }
4056
+ }
4057
+ ) : /* @__PURE__ */ jsx("span", { className: "flex-1 truncate", children: o.label })
4058
+ ]
4059
+ },
4060
+ o.value
4061
+ );
4062
+ })
4063
+ ] });
3795
4064
  }
3796
4065
 
3797
4066
  export { SSelect };