sellmate-design-system-react 0.8.3 → 0.8.4
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.
- package/dist/components/SBarcodeInput/SBarcodeInput.cjs +78 -2
- package/dist/components/SBarcodeInput/SBarcodeInput.cjs.map +1 -1
- package/dist/components/SBarcodeInput/SBarcodeInput.js +78 -2
- package/dist/components/SBarcodeInput/SBarcodeInput.js.map +1 -1
- package/dist/components/SChipInput/SChipInput.cjs +78 -2
- package/dist/components/SChipInput/SChipInput.cjs.map +1 -1
- package/dist/components/SChipInput/SChipInput.js +78 -2
- package/dist/components/SChipInput/SChipInput.js.map +1 -1
- package/dist/components/SDatePicker/SDatePicker.cjs +78 -2
- package/dist/components/SDatePicker/SDatePicker.cjs.map +1 -1
- package/dist/components/SDatePicker/SDatePicker.js +78 -2
- package/dist/components/SDatePicker/SDatePicker.js.map +1 -1
- package/dist/components/SDateRangePicker/SDateRangePicker.cjs +78 -2
- package/dist/components/SDateRangePicker/SDateRangePicker.cjs.map +1 -1
- package/dist/components/SDateRangePicker/SDateRangePicker.js +78 -2
- package/dist/components/SDateRangePicker/SDateRangePicker.js.map +1 -1
- package/dist/components/SField/SField.cjs +78 -2
- package/dist/components/SField/SField.cjs.map +1 -1
- package/dist/components/SField/SField.js +78 -2
- package/dist/components/SField/SField.js.map +1 -1
- package/dist/components/SFilePicker/SFilePicker.cjs +78 -2
- package/dist/components/SFilePicker/SFilePicker.cjs.map +1 -1
- package/dist/components/SFilePicker/SFilePicker.js +78 -2
- package/dist/components/SFilePicker/SFilePicker.js.map +1 -1
- package/dist/components/SInput/SInput.cjs +78 -2
- package/dist/components/SInput/SInput.cjs.map +1 -1
- package/dist/components/SInput/SInput.js +78 -2
- package/dist/components/SInput/SInput.js.map +1 -1
- package/dist/components/SKeyValueTable/SKeyValueTable.cjs +224 -118
- package/dist/components/SKeyValueTable/SKeyValueTable.cjs.map +1 -1
- package/dist/components/SKeyValueTable/SKeyValueTable.js +224 -118
- package/dist/components/SKeyValueTable/SKeyValueTable.js.map +1 -1
- package/dist/components/SNumberInput/SNumberInput.cjs +78 -2
- package/dist/components/SNumberInput/SNumberInput.cjs.map +1 -1
- package/dist/components/SNumberInput/SNumberInput.js +78 -2
- package/dist/components/SNumberInput/SNumberInput.js.map +1 -1
- package/dist/components/SSelect/SSelect.cjs +155 -49
- package/dist/components/SSelect/SSelect.cjs.map +1 -1
- package/dist/components/SSelect/SSelect.js +155 -49
- package/dist/components/SSelect/SSelect.js.map +1 -1
- package/dist/components/SSelect/select.config.d.ts +7 -0
- package/dist/components/STable/STable.cjs +155 -49
- package/dist/components/STable/STable.cjs.map +1 -1
- package/dist/components/STable/STable.js +155 -49
- package/dist/components/STable/STable.js.map +1 -1
- package/dist/components/STextarea/STextarea.cjs +78 -2
- package/dist/components/STextarea/STextarea.cjs.map +1 -1
- package/dist/components/STextarea/STextarea.js +78 -2
- package/dist/components/STextarea/STextarea.js.map +1 -1
- package/dist/components/STimePicker/STimePicker.cjs +78 -2
- package/dist/components/STimePicker/STimePicker.cjs.map +1 -1
- package/dist/components/STimePicker/STimePicker.js +78 -2
- package/dist/components/STimePicker/STimePicker.js.map +1 -1
- package/dist/components/STimeRangePicker/STimeRangePicker.cjs +78 -2
- package/dist/components/STimeRangePicker/STimeRangePicker.cjs.map +1 -1
- package/dist/components/STimeRangePicker/STimeRangePicker.js +78 -2
- package/dist/components/STimeRangePicker/STimeRangePicker.js.map +1 -1
- package/dist/components/STooltip/STooltip.cjs +78 -2
- package/dist/components/STooltip/STooltip.cjs.map +1 -1
- package/dist/components/STooltip/STooltip.d.ts +1 -1
- package/dist/components/STooltip/STooltip.js +78 -2
- package/dist/components/STooltip/STooltip.js.map +1 -1
- package/dist/index.cjs +155 -49
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +155 -49
- package/dist/index.js.map +1 -1
- package/dist/styles.css +4 -8
- package/package.json +1 -1
|
@@ -2883,6 +2883,75 @@ function useInsideModal() {
|
|
|
2883
2883
|
return useContext(InsideModalContext);
|
|
2884
2884
|
}
|
|
2885
2885
|
|
|
2886
|
+
// src/lib/sanitize-inline-html.ts
|
|
2887
|
+
var ALLOWED_INLINE_TAGS = /* @__PURE__ */ new Set(["A", "B", "STRONG", "I", "EM", "BR", "SPAN"]);
|
|
2888
|
+
var ALLOWED_ATTRS_BY_TAG = {
|
|
2889
|
+
A: /* @__PURE__ */ new Set(["href", "target", "rel", "class"])
|
|
2890
|
+
};
|
|
2891
|
+
var COMMON_ALLOWED_ATTRS = /* @__PURE__ */ new Set(["class"]);
|
|
2892
|
+
var SAFE_HREF_RE = /^(https?:|mailto:|tel:|\/|#|\.)/i;
|
|
2893
|
+
var DROP_CONTENT_TAGS = /* @__PURE__ */ new Set([
|
|
2894
|
+
"SCRIPT",
|
|
2895
|
+
"STYLE",
|
|
2896
|
+
"IFRAME",
|
|
2897
|
+
"OBJECT",
|
|
2898
|
+
"EMBED",
|
|
2899
|
+
"META",
|
|
2900
|
+
"LINK",
|
|
2901
|
+
"BASE",
|
|
2902
|
+
"NOSCRIPT",
|
|
2903
|
+
// SVG/MATH 는 foreign-content 파싱 모드로 전환되어 재직렬화 시 다른 트리로
|
|
2904
|
+
// 재파싱될 수 있다(mXSS 벡터). 인라인 라벨에 필요 없으므로 통째로 제거한다.
|
|
2905
|
+
"SVG",
|
|
2906
|
+
"MATH",
|
|
2907
|
+
"TEMPLATE"
|
|
2908
|
+
]);
|
|
2909
|
+
var escapeHtml = (value) => value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
2910
|
+
var sanitizeNode = (node) => {
|
|
2911
|
+
if (node.nodeType === Node.COMMENT_NODE) {
|
|
2912
|
+
node.remove();
|
|
2913
|
+
return;
|
|
2914
|
+
}
|
|
2915
|
+
if (node.nodeType !== Node.ELEMENT_NODE) return;
|
|
2916
|
+
const element = node;
|
|
2917
|
+
const tagName = element.tagName;
|
|
2918
|
+
if (DROP_CONTENT_TAGS.has(tagName)) {
|
|
2919
|
+
element.remove();
|
|
2920
|
+
return;
|
|
2921
|
+
}
|
|
2922
|
+
Array.from(element.childNodes).forEach((child) => sanitizeNode(child));
|
|
2923
|
+
if (ALLOWED_INLINE_TAGS.has(tagName)) {
|
|
2924
|
+
if (element.hasAttribute("className")) {
|
|
2925
|
+
const val = element.getAttribute("className");
|
|
2926
|
+
element.removeAttribute("className");
|
|
2927
|
+
if (!element.hasAttribute("class")) element.setAttribute("class", val);
|
|
2928
|
+
}
|
|
2929
|
+
const allowedAttrs = ALLOWED_ATTRS_BY_TAG[tagName] ?? COMMON_ALLOWED_ATTRS;
|
|
2930
|
+
Array.from(element.attributes).forEach((attr) => {
|
|
2931
|
+
if (!allowedAttrs.has(attr.name)) element.removeAttribute(attr.name);
|
|
2932
|
+
});
|
|
2933
|
+
if (tagName === "A") {
|
|
2934
|
+
const href = element.getAttribute("href");
|
|
2935
|
+
if (href !== null && href !== "" && !SAFE_HREF_RE.test(href)) element.removeAttribute("href");
|
|
2936
|
+
if (element.hasAttribute("target")) {
|
|
2937
|
+
element.setAttribute("rel", "noopener noreferrer");
|
|
2938
|
+
}
|
|
2939
|
+
}
|
|
2940
|
+
return;
|
|
2941
|
+
}
|
|
2942
|
+
const parent = element.parentNode;
|
|
2943
|
+
if (parent == null) return;
|
|
2944
|
+
while (element.firstChild) parent.insertBefore(element.firstChild, element);
|
|
2945
|
+
parent.removeChild(element);
|
|
2946
|
+
};
|
|
2947
|
+
var sanitizeInlineHtml = (value) => {
|
|
2948
|
+
if (typeof document === "undefined") return escapeHtml(value);
|
|
2949
|
+
const template = document.createElement("template");
|
|
2950
|
+
template.innerHTML = value;
|
|
2951
|
+
Array.from(template.content.childNodes).forEach((child) => sanitizeNode(child));
|
|
2952
|
+
return template.innerHTML;
|
|
2953
|
+
};
|
|
2954
|
+
|
|
2886
2955
|
// src/components/STag/tag.config.ts
|
|
2887
2956
|
var TAG_SIZE_CONFIG = {
|
|
2888
2957
|
xs: {
|
|
@@ -3081,7 +3150,14 @@ function useBody(content, message = []) {
|
|
|
3081
3150
|
if (content != null) return content;
|
|
3082
3151
|
if (message.length === 0) return null;
|
|
3083
3152
|
const singleLine = message.length === 1;
|
|
3084
|
-
return /* @__PURE__ */ jsx(Fragment, { children: message.map((line, i) => /* @__PURE__ */ jsx(
|
|
3153
|
+
return /* @__PURE__ */ jsx(Fragment, { children: message.map((line, i) => /* @__PURE__ */ jsx(
|
|
3154
|
+
"div",
|
|
3155
|
+
{
|
|
3156
|
+
className: singleLine ? "leading-[24px]" : void 0,
|
|
3157
|
+
dangerouslySetInnerHTML: { __html: sanitizeInlineHtml(line) }
|
|
3158
|
+
},
|
|
3159
|
+
i
|
|
3160
|
+
)) });
|
|
3085
3161
|
}
|
|
3086
3162
|
var STooltip = forwardRef(function STooltip2({
|
|
3087
3163
|
content,
|
|
@@ -3139,7 +3215,7 @@ var STooltip = forwardRef(function STooltip2({
|
|
|
3139
3215
|
if (disabled || body == null) return /* @__PURE__ */ jsx(Fragment, { children: triggerNode });
|
|
3140
3216
|
const isHover = trigger === "hover";
|
|
3141
3217
|
const contentClass = cn(
|
|
3142
|
-
"z-50
|
|
3218
|
+
"z-50 w-fit whitespace-nowrap rounded-[6px] px-[16px] py-[12px] text-[12px] leading-[20px]",
|
|
3143
3219
|
FLOATING_SLIDE_ANIM,
|
|
3144
3220
|
className
|
|
3145
3221
|
);
|
|
@@ -4163,75 +4239,6 @@ var FIREFOX_SCROLLBAR_CLASS = "supports-[not_selector(::-webkit-scrollbar)]:[scr
|
|
|
4163
4239
|
var SCROLLBAR_CLASS = `${FIREFOX_SCROLLBAR_CLASS} [&::-webkit-scrollbar]:h-[12px] [&::-webkit-scrollbar]:w-[12px] [&::-webkit-scrollbar]:bg-transparent [&::-webkit-scrollbar-track]:bg-transparent [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:border-2 [&::-webkit-scrollbar-thumb]:border-solid [&::-webkit-scrollbar-thumb]:border-transparent [&::-webkit-scrollbar-thumb]:bg-[var(--color-grey-45)] [&::-webkit-scrollbar-thumb]:bg-clip-padding`;
|
|
4164
4240
|
var SCROLLBAR_TRACK_BG_CLASS = "supports-[not_selector(::-webkit-scrollbar)]:[scrollbar-color:var(--color-grey-45)_var(--color-grey-10)] [&::-webkit-scrollbar]:bg-[var(--color-grey-10)] [&::-webkit-scrollbar-track]:bg-[var(--color-grey-10)]";
|
|
4165
4241
|
var SCROLLBAR_TRACK_BORDER_CLASS = "[&::-webkit-scrollbar-track:vertical]:[border-left:1px_solid_var(--sys-color-border-default)] [&::-webkit-scrollbar-track:horizontal]:[border-top:1px_solid_var(--sys-color-border-default)] [&::-webkit-scrollbar-thumb:vertical]:[border-left-width:3px] [&::-webkit-scrollbar-thumb:horizontal]:[border-top-width:3px]";
|
|
4166
|
-
|
|
4167
|
-
// src/lib/sanitize-inline-html.ts
|
|
4168
|
-
var ALLOWED_INLINE_TAGS = /* @__PURE__ */ new Set(["A", "B", "STRONG", "I", "EM", "BR", "SPAN"]);
|
|
4169
|
-
var ALLOWED_ATTRS_BY_TAG = {
|
|
4170
|
-
A: /* @__PURE__ */ new Set(["href", "target", "rel", "class"])
|
|
4171
|
-
};
|
|
4172
|
-
var COMMON_ALLOWED_ATTRS = /* @__PURE__ */ new Set(["class"]);
|
|
4173
|
-
var SAFE_HREF_RE = /^(https?:|mailto:|tel:|\/|#|\.)/i;
|
|
4174
|
-
var DROP_CONTENT_TAGS = /* @__PURE__ */ new Set([
|
|
4175
|
-
"SCRIPT",
|
|
4176
|
-
"STYLE",
|
|
4177
|
-
"IFRAME",
|
|
4178
|
-
"OBJECT",
|
|
4179
|
-
"EMBED",
|
|
4180
|
-
"META",
|
|
4181
|
-
"LINK",
|
|
4182
|
-
"BASE",
|
|
4183
|
-
"NOSCRIPT",
|
|
4184
|
-
// SVG/MATH 는 foreign-content 파싱 모드로 전환되어 재직렬화 시 다른 트리로
|
|
4185
|
-
// 재파싱될 수 있다(mXSS 벡터). 인라인 라벨에 필요 없으므로 통째로 제거한다.
|
|
4186
|
-
"SVG",
|
|
4187
|
-
"MATH",
|
|
4188
|
-
"TEMPLATE"
|
|
4189
|
-
]);
|
|
4190
|
-
var escapeHtml = (value) => value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
4191
|
-
var sanitizeNode = (node) => {
|
|
4192
|
-
if (node.nodeType === Node.COMMENT_NODE) {
|
|
4193
|
-
node.remove();
|
|
4194
|
-
return;
|
|
4195
|
-
}
|
|
4196
|
-
if (node.nodeType !== Node.ELEMENT_NODE) return;
|
|
4197
|
-
const element = node;
|
|
4198
|
-
const tagName = element.tagName;
|
|
4199
|
-
if (DROP_CONTENT_TAGS.has(tagName)) {
|
|
4200
|
-
element.remove();
|
|
4201
|
-
return;
|
|
4202
|
-
}
|
|
4203
|
-
Array.from(element.childNodes).forEach((child) => sanitizeNode(child));
|
|
4204
|
-
if (ALLOWED_INLINE_TAGS.has(tagName)) {
|
|
4205
|
-
if (element.hasAttribute("className")) {
|
|
4206
|
-
const val = element.getAttribute("className");
|
|
4207
|
-
element.removeAttribute("className");
|
|
4208
|
-
if (!element.hasAttribute("class")) element.setAttribute("class", val);
|
|
4209
|
-
}
|
|
4210
|
-
const allowedAttrs = ALLOWED_ATTRS_BY_TAG[tagName] ?? COMMON_ALLOWED_ATTRS;
|
|
4211
|
-
Array.from(element.attributes).forEach((attr) => {
|
|
4212
|
-
if (!allowedAttrs.has(attr.name)) element.removeAttribute(attr.name);
|
|
4213
|
-
});
|
|
4214
|
-
if (tagName === "A") {
|
|
4215
|
-
const href = element.getAttribute("href");
|
|
4216
|
-
if (href !== null && href !== "" && !SAFE_HREF_RE.test(href)) element.removeAttribute("href");
|
|
4217
|
-
if (element.hasAttribute("target")) {
|
|
4218
|
-
element.setAttribute("rel", "noopener noreferrer");
|
|
4219
|
-
}
|
|
4220
|
-
}
|
|
4221
|
-
return;
|
|
4222
|
-
}
|
|
4223
|
-
const parent = element.parentNode;
|
|
4224
|
-
if (parent == null) return;
|
|
4225
|
-
while (element.firstChild) parent.insertBefore(element.firstChild, element);
|
|
4226
|
-
parent.removeChild(element);
|
|
4227
|
-
};
|
|
4228
|
-
var sanitizeInlineHtml = (value) => {
|
|
4229
|
-
if (typeof document === "undefined") return escapeHtml(value);
|
|
4230
|
-
const template = document.createElement("template");
|
|
4231
|
-
template.innerHTML = value;
|
|
4232
|
-
Array.from(template.content.childNodes).forEach((child) => sanitizeNode(child));
|
|
4233
|
-
return template.innerHTML;
|
|
4234
|
-
};
|
|
4235
4242
|
function SPortal({
|
|
4236
4243
|
open,
|
|
4237
4244
|
onClose,
|
|
@@ -4338,6 +4345,16 @@ function flattenOptions(options, depth = 1) {
|
|
|
4338
4345
|
return out;
|
|
4339
4346
|
}
|
|
4340
4347
|
var selectableOptions = (flat) => flat.filter((o) => !o.isGroup && !o.disabled);
|
|
4348
|
+
function collectGroupLeafValues(flat, groupIndex) {
|
|
4349
|
+
const group = flat[groupIndex];
|
|
4350
|
+
if (group == null || !group.isGroup) return [];
|
|
4351
|
+
const values = [];
|
|
4352
|
+
for (let i = groupIndex + 1; i < flat.length; i++) {
|
|
4353
|
+
if (flat[i].depth <= group.depth) break;
|
|
4354
|
+
if (!flat[i].isGroup && !flat[i].disabled) values.push(flat[i].value);
|
|
4355
|
+
}
|
|
4356
|
+
return values;
|
|
4357
|
+
}
|
|
4341
4358
|
var isHtmlLabel = (label) => /<[a-z][\s\S]*>/i.test(label);
|
|
4342
4359
|
var extractText = (html) => html.replace(/<[^>]*>/g, "");
|
|
4343
4360
|
var SEARCH_THRESHOLD = 11;
|
|
@@ -4463,16 +4480,31 @@ var SSelect = forwardRef(function SSelect2({
|
|
|
4463
4480
|
const allValues = selectable.map((o) => o.value);
|
|
4464
4481
|
const allSelected = allValues.length > 0 && allValues.every((v) => selected.includes(v));
|
|
4465
4482
|
const hasItems = filtered.some((o) => !o.isGroup);
|
|
4483
|
+
const groupLeafValues = useMemo(() => {
|
|
4484
|
+
const map = /* @__PURE__ */ new Map();
|
|
4485
|
+
filtered.forEach((o, i) => {
|
|
4486
|
+
if (o.isGroup) map.set(i, collectGroupLeafValues(filtered, i));
|
|
4487
|
+
});
|
|
4488
|
+
return map;
|
|
4489
|
+
}, [filtered]);
|
|
4466
4490
|
const navigableItems = useMemo(() => {
|
|
4467
4491
|
const items = [];
|
|
4468
4492
|
if (multi && useSelectAll && allValues.length > 0) {
|
|
4469
|
-
items.push({
|
|
4493
|
+
items.push({ key: "__select_all__", isSelectAll: true });
|
|
4470
4494
|
}
|
|
4471
|
-
filtered.forEach((o) => {
|
|
4472
|
-
if (
|
|
4495
|
+
filtered.forEach((o, i) => {
|
|
4496
|
+
if (o.disabled) return;
|
|
4497
|
+
if (o.isGroup) {
|
|
4498
|
+
if (!multi) return;
|
|
4499
|
+
const leafValues = groupLeafValues.get(i) ?? [];
|
|
4500
|
+
if (leafValues.length === 0) return;
|
|
4501
|
+
items.push({ key: `__group_${i}__`, isGroup: true, leafValues });
|
|
4502
|
+
} else {
|
|
4503
|
+
items.push({ key: o.value, value: o.value });
|
|
4504
|
+
}
|
|
4473
4505
|
});
|
|
4474
4506
|
return items;
|
|
4475
|
-
}, [allValues.length, filtered, multi, useSelectAll]);
|
|
4507
|
+
}, [allValues.length, filtered, groupLeafValues, multi, useSelectAll]);
|
|
4476
4508
|
const triggerText = (() => {
|
|
4477
4509
|
if (selected.length === 0) return placeholder;
|
|
4478
4510
|
if (multi) {
|
|
@@ -4494,6 +4526,17 @@ var SSelect = forwardRef(function SSelect2({
|
|
|
4494
4526
|
const handleSelectAll = () => {
|
|
4495
4527
|
emit(allSelected ? [] : allValues);
|
|
4496
4528
|
};
|
|
4529
|
+
const handleToggleGroup = (leafValues) => {
|
|
4530
|
+
if (leafValues.length === 0) return;
|
|
4531
|
+
const selectedSet = new Set(selected);
|
|
4532
|
+
const allSelectedInGroup = leafValues.every((v) => selectedSet.has(v));
|
|
4533
|
+
if (allSelectedInGroup) {
|
|
4534
|
+
const remove = new Set(leafValues);
|
|
4535
|
+
emit(selected.filter((v) => !remove.has(v)));
|
|
4536
|
+
} else {
|
|
4537
|
+
emit([...selected, ...leafValues.filter((v) => !selectedSet.has(v))]);
|
|
4538
|
+
}
|
|
4539
|
+
};
|
|
4497
4540
|
useEffect(() => {
|
|
4498
4541
|
if (!open) {
|
|
4499
4542
|
setFocusedIndex(-1);
|
|
@@ -4534,6 +4577,10 @@ var SSelect = forwardRef(function SSelect2({
|
|
|
4534
4577
|
handleSelectAll();
|
|
4535
4578
|
return;
|
|
4536
4579
|
}
|
|
4580
|
+
if (focused.isGroup) {
|
|
4581
|
+
handleToggleGroup(focused.leafValues ?? []);
|
|
4582
|
+
return;
|
|
4583
|
+
}
|
|
4537
4584
|
handleSelect(focused.value);
|
|
4538
4585
|
};
|
|
4539
4586
|
const handleKeyDown = (event) => {
|
|
@@ -4697,20 +4744,20 @@ var SSelect = forwardRef(function SSelect2({
|
|
|
4697
4744
|
items: filtered,
|
|
4698
4745
|
multi,
|
|
4699
4746
|
selected,
|
|
4700
|
-
|
|
4747
|
+
groupLeafValues,
|
|
4748
|
+
focusedValue: navigableItems[focusedIndex]?.key,
|
|
4701
4749
|
showSelectAll: multi && useSelectAll && allValues.length > 0,
|
|
4702
4750
|
selectAllLabel: allSelectedLabel,
|
|
4703
4751
|
selectAllSelected: allSelected,
|
|
4704
4752
|
onSelectAll: handleSelectAll,
|
|
4705
4753
|
onSelect: handleSelect,
|
|
4706
|
-
|
|
4707
|
-
|
|
4754
|
+
onToggleGroup: handleToggleGroup,
|
|
4755
|
+
onHoverValue: (key) => {
|
|
4756
|
+
if (key == null) {
|
|
4708
4757
|
setFocusedIndex(-1);
|
|
4709
4758
|
return;
|
|
4710
4759
|
}
|
|
4711
|
-
const index = navigableItems.findIndex(
|
|
4712
|
-
(item) => item.isSelectAll ? value2 === "__select_all__" : item.value === value2
|
|
4713
|
-
);
|
|
4760
|
+
const index = navigableItems.findIndex((item) => item.key === key);
|
|
4714
4761
|
if (index >= 0) setFocusedIndex(index);
|
|
4715
4762
|
}
|
|
4716
4763
|
}
|
|
@@ -4729,20 +4776,23 @@ function ItemList({
|
|
|
4729
4776
|
items,
|
|
4730
4777
|
multi,
|
|
4731
4778
|
selected,
|
|
4779
|
+
groupLeafValues,
|
|
4732
4780
|
focusedValue,
|
|
4733
4781
|
showSelectAll,
|
|
4734
4782
|
selectAllLabel,
|
|
4735
4783
|
selectAllSelected,
|
|
4736
4784
|
onSelectAll,
|
|
4737
4785
|
onSelect,
|
|
4786
|
+
onToggleGroup,
|
|
4738
4787
|
onHoverValue
|
|
4739
4788
|
}) {
|
|
4789
|
+
const selectedSet = useMemo(() => new Set(selected), [selected]);
|
|
4740
4790
|
const itemBase = cn(
|
|
4741
4791
|
"group flex w-full cursor-pointer select-none items-center gap-[8px] border-none bg-transparent text-left text-[12px] leading-[20px] outline-none",
|
|
4742
4792
|
"text-[color:var(--cmp-listItem-content-default)]",
|
|
4743
4793
|
"disabled:cursor-not-allowed disabled:text-[color:var(--cmp-listItem-content-disabled)]"
|
|
4744
4794
|
);
|
|
4745
|
-
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)]";
|
|
4795
|
+
const activeClass = "hover:bg-[var(--cmp-listItem-bg-hover)] hover:text-[color:var(--cmp-listItem-content-hover)] focus-visible:bg-[var(--cmp-listItem-bg-hover)] focus-visible:text-[color:var(--cmp-listItem-content-hover)]";
|
|
4746
4796
|
return /* @__PURE__ */ jsxs("div", { role: "listbox", "aria-multiselectable": multi || void 0, children: [
|
|
4747
4797
|
showSelectAll && /* @__PURE__ */ jsxs(
|
|
4748
4798
|
"button",
|
|
@@ -4759,23 +4809,7 @@ function ItemList({
|
|
|
4759
4809
|
onMouseLeave: () => onHoverValue(null),
|
|
4760
4810
|
onClick: onSelectAll,
|
|
4761
4811
|
children: [
|
|
4762
|
-
/* @__PURE__ */ jsx(
|
|
4763
|
-
"span",
|
|
4764
|
-
{
|
|
4765
|
-
className: cn(
|
|
4766
|
-
"flex h-[16px] w-[16px] flex-shrink-0 items-center justify-center rounded-[2px] border border-solid",
|
|
4767
|
-
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)]"
|
|
4768
|
-
),
|
|
4769
|
-
children: selectAllSelected && /* @__PURE__ */ jsx(
|
|
4770
|
-
SIcon,
|
|
4771
|
-
{
|
|
4772
|
-
name: "checkboxCheck",
|
|
4773
|
-
size: 12,
|
|
4774
|
-
color: "var(--cmp-checkbox-checked-icon-default)"
|
|
4775
|
-
}
|
|
4776
|
-
)
|
|
4777
|
-
}
|
|
4778
|
-
),
|
|
4812
|
+
/* @__PURE__ */ jsx(CheckboxBox, { state: selectAllSelected, focused: focusedValue === "__select_all__" }),
|
|
4779
4813
|
/* @__PURE__ */ jsx("span", { className: "flex-1 truncate", children: selectAllLabel })
|
|
4780
4814
|
]
|
|
4781
4815
|
}
|
|
@@ -4785,12 +4819,71 @@ function ItemList({
|
|
|
4785
4819
|
const rowKey = `${o.value}-${idx}`;
|
|
4786
4820
|
if (o.isGroup) {
|
|
4787
4821
|
const isDepth1 = o.depth === 1;
|
|
4822
|
+
const groupTone = 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";
|
|
4823
|
+
if (multi) {
|
|
4824
|
+
const leafValues = groupLeafValues.get(idx) ?? [];
|
|
4825
|
+
const total = leafValues.length;
|
|
4826
|
+
const selectedCount = leafValues.reduce((n, v) => selectedSet.has(v) ? n + 1 : n, 0);
|
|
4827
|
+
const groupState = total === 0 || selectedCount === 0 ? false : selectedCount === total ? true : null;
|
|
4828
|
+
const groupDisabled = o.disabled || total === 0;
|
|
4829
|
+
const groupFocused = focusedValue === `__group_${idx}__`;
|
|
4830
|
+
return /* @__PURE__ */ jsxs(
|
|
4831
|
+
"button",
|
|
4832
|
+
{
|
|
4833
|
+
type: "button",
|
|
4834
|
+
role: "option",
|
|
4835
|
+
"aria-selected": groupState === true,
|
|
4836
|
+
disabled: groupDisabled,
|
|
4837
|
+
"data-select-focused": groupFocused ? "true" : void 0,
|
|
4838
|
+
"data-select-active": groupFocused ? "true" : void 0,
|
|
4839
|
+
className: cn(
|
|
4840
|
+
"flex w-full select-none items-center gap-[8px] border-none py-[4px] pr-[12px] text-left text-[12px] leading-[20px] text-[color:var(--cmp-listItem-content-default)]",
|
|
4841
|
+
groupTone,
|
|
4842
|
+
groupDisabled ? "cursor-not-allowed text-[color:var(--cmp-listItem-content-disabled)]" : "cursor-pointer",
|
|
4843
|
+
groupDisabled && isDepth1 && "bg-[var(--cmp-listItem-depth1-bg-disabled)]",
|
|
4844
|
+
// 포커스 하이라이트는 depth 배경보다 뒤에 둬 tailwind-merge 로 이기게 한다.
|
|
4845
|
+
groupFocused && !groupDisabled && "bg-[var(--cmp-listItem-bg-hover)] text-[color:var(--cmp-listItem-content-hover)]"
|
|
4846
|
+
),
|
|
4847
|
+
style: { paddingLeft: padLeft },
|
|
4848
|
+
onMouseEnter: () => onHoverValue(`__group_${idx}__`),
|
|
4849
|
+
onMouseLeave: () => onHoverValue(null),
|
|
4850
|
+
onClick: () => onToggleGroup(leafValues),
|
|
4851
|
+
children: [
|
|
4852
|
+
/* @__PURE__ */ jsx(CheckboxBox, { state: groupState, disabled: groupDisabled, focused: groupFocused }),
|
|
4853
|
+
isHtmlLabel(o.label) ? /* @__PURE__ */ jsx(
|
|
4854
|
+
"span",
|
|
4855
|
+
{
|
|
4856
|
+
className: "min-w-0 truncate",
|
|
4857
|
+
dangerouslySetInnerHTML: { __html: sanitizeInlineHtml(o.label) }
|
|
4858
|
+
}
|
|
4859
|
+
) : /* @__PURE__ */ jsx("span", { className: "min-w-0 truncate", children: o.label }),
|
|
4860
|
+
total > 0 && /* @__PURE__ */ jsxs(
|
|
4861
|
+
"span",
|
|
4862
|
+
{
|
|
4863
|
+
className: cn(
|
|
4864
|
+
"shrink-0 font-medium",
|
|
4865
|
+
groupFocused ? "text-white" : "text-[color:var(--cmp-listItem-content-disabled)]"
|
|
4866
|
+
),
|
|
4867
|
+
children: [
|
|
4868
|
+
"(",
|
|
4869
|
+
selectedCount,
|
|
4870
|
+
"/",
|
|
4871
|
+
total,
|
|
4872
|
+
")"
|
|
4873
|
+
]
|
|
4874
|
+
}
|
|
4875
|
+
)
|
|
4876
|
+
]
|
|
4877
|
+
},
|
|
4878
|
+
rowKey
|
|
4879
|
+
);
|
|
4880
|
+
}
|
|
4788
4881
|
return /* @__PURE__ */ jsx(
|
|
4789
4882
|
"div",
|
|
4790
4883
|
{
|
|
4791
4884
|
className: cn(
|
|
4792
4885
|
"select-none py-[4px] pr-[12px] text-[12px] leading-[20px] text-[color:var(--cmp-listItem-content-default)]",
|
|
4793
|
-
|
|
4886
|
+
groupTone,
|
|
4794
4887
|
// 하위가 전부 비활성이면(select.config 에서 bubble-up) 어미 그룹도 비활성 처리:
|
|
4795
4888
|
// 텍스트는 회색(#888), depth1 그룹은 파란 배경 대신 회색(#EEE)으로 (depth2 는 #F6F6F6 유지)
|
|
4796
4889
|
o.disabled && "cursor-not-allowed text-[color:var(--cmp-listItem-content-disabled)]",
|
|
@@ -4826,25 +4919,11 @@ function ItemList({
|
|
|
4826
4919
|
onClick: () => onSelect(o.value),
|
|
4827
4920
|
children: [
|
|
4828
4921
|
multi && /* @__PURE__ */ jsx(
|
|
4829
|
-
|
|
4922
|
+
CheckboxBox,
|
|
4830
4923
|
{
|
|
4831
|
-
|
|
4832
|
-
|
|
4833
|
-
|
|
4834
|
-
// 비활성: 회색 채움(#E1E1E1) + 회색 테두리(#CCCCCC)
|
|
4835
|
-
"cursor-not-allowed border-[color:var(--cmp-checkbox-border-disabled)] bg-[var(--cmp-checkbox-bg-disabled)]"
|
|
4836
|
-
) : cn(
|
|
4837
|
-
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)]"
|
|
4838
|
-
)
|
|
4839
|
-
),
|
|
4840
|
-
children: isSelected && /* @__PURE__ */ jsx(
|
|
4841
|
-
SIcon,
|
|
4842
|
-
{
|
|
4843
|
-
name: "checkboxCheck",
|
|
4844
|
-
size: 12,
|
|
4845
|
-
color: o.disabled ? "var(--cmp-checkbox-checked-icon-disabled)" : "var(--cmp-checkbox-checked-icon-default)"
|
|
4846
|
-
}
|
|
4847
|
-
)
|
|
4924
|
+
state: isSelected,
|
|
4925
|
+
disabled: o.disabled,
|
|
4926
|
+
focused: focusedValue === o.value
|
|
4848
4927
|
}
|
|
4849
4928
|
),
|
|
4850
4929
|
isHtmlLabel(o.label) ? /* @__PURE__ */ jsx(
|
|
@@ -4861,6 +4940,33 @@ function ItemList({
|
|
|
4861
4940
|
})
|
|
4862
4941
|
] });
|
|
4863
4942
|
}
|
|
4943
|
+
function CheckboxBox({
|
|
4944
|
+
state,
|
|
4945
|
+
disabled = false,
|
|
4946
|
+
focused = false
|
|
4947
|
+
}) {
|
|
4948
|
+
const filled = state === true || state === null;
|
|
4949
|
+
return /* @__PURE__ */ jsx(
|
|
4950
|
+
"span",
|
|
4951
|
+
{
|
|
4952
|
+
className: cn(
|
|
4953
|
+
"flex h-[16px] w-[16px] flex-shrink-0 items-center justify-center rounded-[2px] border border-solid",
|
|
4954
|
+
disabled ? (
|
|
4955
|
+
// 비활성: 회색 채움(#E1E1E1) + 회색 테두리(#CCCCCC)
|
|
4956
|
+
"cursor-not-allowed border-[color:var(--cmp-checkbox-border-disabled)] bg-[var(--cmp-checkbox-bg-disabled)]"
|
|
4957
|
+
) : filled ? focused ? "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)]"
|
|
4958
|
+
),
|
|
4959
|
+
children: filled && /* @__PURE__ */ jsx(
|
|
4960
|
+
SIcon,
|
|
4961
|
+
{
|
|
4962
|
+
name: state === null ? "checkboxIndeterminate" : "checkboxCheck",
|
|
4963
|
+
size: 12,
|
|
4964
|
+
color: disabled ? "var(--cmp-checkbox-checked-icon-disabled)" : "var(--cmp-checkbox-checked-icon-default)"
|
|
4965
|
+
}
|
|
4966
|
+
)
|
|
4967
|
+
}
|
|
4968
|
+
);
|
|
4969
|
+
}
|
|
4864
4970
|
function SRadio({
|
|
4865
4971
|
val,
|
|
4866
4972
|
value,
|