sellmate-design-system-react 0.8.3 → 0.9.0
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/SGnb/SGnb.cjs +58 -16
- package/dist/components/SGnb/SGnb.cjs.map +1 -1
- package/dist/components/SGnb/SGnb.d.ts +15 -0
- package/dist/components/SGnb/SGnb.js +58 -16
- package/dist/components/SGnb/SGnb.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 +209 -65
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +209 -65
- package/dist/index.js.map +1 -1
- package/dist/styles.css +35 -8
- package/package.json +1 -1
|
@@ -2907,6 +2907,75 @@ function useInsideModal() {
|
|
|
2907
2907
|
return react.useContext(InsideModalContext);
|
|
2908
2908
|
}
|
|
2909
2909
|
|
|
2910
|
+
// src/lib/sanitize-inline-html.ts
|
|
2911
|
+
var ALLOWED_INLINE_TAGS = /* @__PURE__ */ new Set(["A", "B", "STRONG", "I", "EM", "BR", "SPAN"]);
|
|
2912
|
+
var ALLOWED_ATTRS_BY_TAG = {
|
|
2913
|
+
A: /* @__PURE__ */ new Set(["href", "target", "rel", "class"])
|
|
2914
|
+
};
|
|
2915
|
+
var COMMON_ALLOWED_ATTRS = /* @__PURE__ */ new Set(["class"]);
|
|
2916
|
+
var SAFE_HREF_RE = /^(https?:|mailto:|tel:|\/|#|\.)/i;
|
|
2917
|
+
var DROP_CONTENT_TAGS = /* @__PURE__ */ new Set([
|
|
2918
|
+
"SCRIPT",
|
|
2919
|
+
"STYLE",
|
|
2920
|
+
"IFRAME",
|
|
2921
|
+
"OBJECT",
|
|
2922
|
+
"EMBED",
|
|
2923
|
+
"META",
|
|
2924
|
+
"LINK",
|
|
2925
|
+
"BASE",
|
|
2926
|
+
"NOSCRIPT",
|
|
2927
|
+
// SVG/MATH 는 foreign-content 파싱 모드로 전환되어 재직렬화 시 다른 트리로
|
|
2928
|
+
// 재파싱될 수 있다(mXSS 벡터). 인라인 라벨에 필요 없으므로 통째로 제거한다.
|
|
2929
|
+
"SVG",
|
|
2930
|
+
"MATH",
|
|
2931
|
+
"TEMPLATE"
|
|
2932
|
+
]);
|
|
2933
|
+
var escapeHtml = (value) => value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
2934
|
+
var sanitizeNode = (node) => {
|
|
2935
|
+
if (node.nodeType === Node.COMMENT_NODE) {
|
|
2936
|
+
node.remove();
|
|
2937
|
+
return;
|
|
2938
|
+
}
|
|
2939
|
+
if (node.nodeType !== Node.ELEMENT_NODE) return;
|
|
2940
|
+
const element = node;
|
|
2941
|
+
const tagName = element.tagName;
|
|
2942
|
+
if (DROP_CONTENT_TAGS.has(tagName)) {
|
|
2943
|
+
element.remove();
|
|
2944
|
+
return;
|
|
2945
|
+
}
|
|
2946
|
+
Array.from(element.childNodes).forEach((child) => sanitizeNode(child));
|
|
2947
|
+
if (ALLOWED_INLINE_TAGS.has(tagName)) {
|
|
2948
|
+
if (element.hasAttribute("className")) {
|
|
2949
|
+
const val = element.getAttribute("className");
|
|
2950
|
+
element.removeAttribute("className");
|
|
2951
|
+
if (!element.hasAttribute("class")) element.setAttribute("class", val);
|
|
2952
|
+
}
|
|
2953
|
+
const allowedAttrs = ALLOWED_ATTRS_BY_TAG[tagName] ?? COMMON_ALLOWED_ATTRS;
|
|
2954
|
+
Array.from(element.attributes).forEach((attr) => {
|
|
2955
|
+
if (!allowedAttrs.has(attr.name)) element.removeAttribute(attr.name);
|
|
2956
|
+
});
|
|
2957
|
+
if (tagName === "A") {
|
|
2958
|
+
const href = element.getAttribute("href");
|
|
2959
|
+
if (href !== null && href !== "" && !SAFE_HREF_RE.test(href)) element.removeAttribute("href");
|
|
2960
|
+
if (element.hasAttribute("target")) {
|
|
2961
|
+
element.setAttribute("rel", "noopener noreferrer");
|
|
2962
|
+
}
|
|
2963
|
+
}
|
|
2964
|
+
return;
|
|
2965
|
+
}
|
|
2966
|
+
const parent = element.parentNode;
|
|
2967
|
+
if (parent == null) return;
|
|
2968
|
+
while (element.firstChild) parent.insertBefore(element.firstChild, element);
|
|
2969
|
+
parent.removeChild(element);
|
|
2970
|
+
};
|
|
2971
|
+
var sanitizeInlineHtml = (value) => {
|
|
2972
|
+
if (typeof document === "undefined") return escapeHtml(value);
|
|
2973
|
+
const template = document.createElement("template");
|
|
2974
|
+
template.innerHTML = value;
|
|
2975
|
+
Array.from(template.content.childNodes).forEach((child) => sanitizeNode(child));
|
|
2976
|
+
return template.innerHTML;
|
|
2977
|
+
};
|
|
2978
|
+
|
|
2910
2979
|
// src/components/STag/tag.config.ts
|
|
2911
2980
|
var TAG_SIZE_CONFIG = {
|
|
2912
2981
|
xs: {
|
|
@@ -3105,7 +3174,14 @@ function useBody(content, message = []) {
|
|
|
3105
3174
|
if (content != null) return content;
|
|
3106
3175
|
if (message.length === 0) return null;
|
|
3107
3176
|
const singleLine = message.length === 1;
|
|
3108
|
-
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: message.map((line, i) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
3177
|
+
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: message.map((line, i) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
3178
|
+
"div",
|
|
3179
|
+
{
|
|
3180
|
+
className: singleLine ? "leading-[24px]" : void 0,
|
|
3181
|
+
dangerouslySetInnerHTML: { __html: sanitizeInlineHtml(line) }
|
|
3182
|
+
},
|
|
3183
|
+
i
|
|
3184
|
+
)) });
|
|
3109
3185
|
}
|
|
3110
3186
|
var STooltip = react.forwardRef(function STooltip2({
|
|
3111
3187
|
content,
|
|
@@ -3163,7 +3239,7 @@ var STooltip = react.forwardRef(function STooltip2({
|
|
|
3163
3239
|
if (disabled || body == null) return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: triggerNode });
|
|
3164
3240
|
const isHover = trigger === "hover";
|
|
3165
3241
|
const contentClass = cn(
|
|
3166
|
-
"z-50
|
|
3242
|
+
"z-50 w-fit whitespace-nowrap rounded-[6px] px-[16px] py-[12px] text-[12px] leading-[20px]",
|
|
3167
3243
|
FLOATING_SLIDE_ANIM,
|
|
3168
3244
|
className
|
|
3169
3245
|
);
|
|
@@ -4187,75 +4263,6 @@ var FIREFOX_SCROLLBAR_CLASS = "supports-[not_selector(::-webkit-scrollbar)]:[scr
|
|
|
4187
4263
|
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`;
|
|
4188
4264
|
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)]";
|
|
4189
4265
|
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]";
|
|
4190
|
-
|
|
4191
|
-
// src/lib/sanitize-inline-html.ts
|
|
4192
|
-
var ALLOWED_INLINE_TAGS = /* @__PURE__ */ new Set(["A", "B", "STRONG", "I", "EM", "BR", "SPAN"]);
|
|
4193
|
-
var ALLOWED_ATTRS_BY_TAG = {
|
|
4194
|
-
A: /* @__PURE__ */ new Set(["href", "target", "rel", "class"])
|
|
4195
|
-
};
|
|
4196
|
-
var COMMON_ALLOWED_ATTRS = /* @__PURE__ */ new Set(["class"]);
|
|
4197
|
-
var SAFE_HREF_RE = /^(https?:|mailto:|tel:|\/|#|\.)/i;
|
|
4198
|
-
var DROP_CONTENT_TAGS = /* @__PURE__ */ new Set([
|
|
4199
|
-
"SCRIPT",
|
|
4200
|
-
"STYLE",
|
|
4201
|
-
"IFRAME",
|
|
4202
|
-
"OBJECT",
|
|
4203
|
-
"EMBED",
|
|
4204
|
-
"META",
|
|
4205
|
-
"LINK",
|
|
4206
|
-
"BASE",
|
|
4207
|
-
"NOSCRIPT",
|
|
4208
|
-
// SVG/MATH 는 foreign-content 파싱 모드로 전환되어 재직렬화 시 다른 트리로
|
|
4209
|
-
// 재파싱될 수 있다(mXSS 벡터). 인라인 라벨에 필요 없으므로 통째로 제거한다.
|
|
4210
|
-
"SVG",
|
|
4211
|
-
"MATH",
|
|
4212
|
-
"TEMPLATE"
|
|
4213
|
-
]);
|
|
4214
|
-
var escapeHtml = (value) => value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
4215
|
-
var sanitizeNode = (node) => {
|
|
4216
|
-
if (node.nodeType === Node.COMMENT_NODE) {
|
|
4217
|
-
node.remove();
|
|
4218
|
-
return;
|
|
4219
|
-
}
|
|
4220
|
-
if (node.nodeType !== Node.ELEMENT_NODE) return;
|
|
4221
|
-
const element = node;
|
|
4222
|
-
const tagName = element.tagName;
|
|
4223
|
-
if (DROP_CONTENT_TAGS.has(tagName)) {
|
|
4224
|
-
element.remove();
|
|
4225
|
-
return;
|
|
4226
|
-
}
|
|
4227
|
-
Array.from(element.childNodes).forEach((child) => sanitizeNode(child));
|
|
4228
|
-
if (ALLOWED_INLINE_TAGS.has(tagName)) {
|
|
4229
|
-
if (element.hasAttribute("className")) {
|
|
4230
|
-
const val = element.getAttribute("className");
|
|
4231
|
-
element.removeAttribute("className");
|
|
4232
|
-
if (!element.hasAttribute("class")) element.setAttribute("class", val);
|
|
4233
|
-
}
|
|
4234
|
-
const allowedAttrs = ALLOWED_ATTRS_BY_TAG[tagName] ?? COMMON_ALLOWED_ATTRS;
|
|
4235
|
-
Array.from(element.attributes).forEach((attr) => {
|
|
4236
|
-
if (!allowedAttrs.has(attr.name)) element.removeAttribute(attr.name);
|
|
4237
|
-
});
|
|
4238
|
-
if (tagName === "A") {
|
|
4239
|
-
const href = element.getAttribute("href");
|
|
4240
|
-
if (href !== null && href !== "" && !SAFE_HREF_RE.test(href)) element.removeAttribute("href");
|
|
4241
|
-
if (element.hasAttribute("target")) {
|
|
4242
|
-
element.setAttribute("rel", "noopener noreferrer");
|
|
4243
|
-
}
|
|
4244
|
-
}
|
|
4245
|
-
return;
|
|
4246
|
-
}
|
|
4247
|
-
const parent = element.parentNode;
|
|
4248
|
-
if (parent == null) return;
|
|
4249
|
-
while (element.firstChild) parent.insertBefore(element.firstChild, element);
|
|
4250
|
-
parent.removeChild(element);
|
|
4251
|
-
};
|
|
4252
|
-
var sanitizeInlineHtml = (value) => {
|
|
4253
|
-
if (typeof document === "undefined") return escapeHtml(value);
|
|
4254
|
-
const template = document.createElement("template");
|
|
4255
|
-
template.innerHTML = value;
|
|
4256
|
-
Array.from(template.content.childNodes).forEach((child) => sanitizeNode(child));
|
|
4257
|
-
return template.innerHTML;
|
|
4258
|
-
};
|
|
4259
4266
|
function SPortal({
|
|
4260
4267
|
open,
|
|
4261
4268
|
onClose,
|
|
@@ -4362,6 +4369,16 @@ function flattenOptions(options, depth = 1) {
|
|
|
4362
4369
|
return out;
|
|
4363
4370
|
}
|
|
4364
4371
|
var selectableOptions = (flat) => flat.filter((o) => !o.isGroup && !o.disabled);
|
|
4372
|
+
function collectGroupLeafValues(flat, groupIndex) {
|
|
4373
|
+
const group = flat[groupIndex];
|
|
4374
|
+
if (group == null || !group.isGroup) return [];
|
|
4375
|
+
const values = [];
|
|
4376
|
+
for (let i = groupIndex + 1; i < flat.length; i++) {
|
|
4377
|
+
if (flat[i].depth <= group.depth) break;
|
|
4378
|
+
if (!flat[i].isGroup && !flat[i].disabled) values.push(flat[i].value);
|
|
4379
|
+
}
|
|
4380
|
+
return values;
|
|
4381
|
+
}
|
|
4365
4382
|
var isHtmlLabel = (label) => /<[a-z][\s\S]*>/i.test(label);
|
|
4366
4383
|
var extractText = (html) => html.replace(/<[^>]*>/g, "");
|
|
4367
4384
|
var SEARCH_THRESHOLD = 11;
|
|
@@ -4487,16 +4504,31 @@ var SSelect = react.forwardRef(function SSelect2({
|
|
|
4487
4504
|
const allValues = selectable.map((o) => o.value);
|
|
4488
4505
|
const allSelected = allValues.length > 0 && allValues.every((v) => selected.includes(v));
|
|
4489
4506
|
const hasItems = filtered.some((o) => !o.isGroup);
|
|
4507
|
+
const groupLeafValues = react.useMemo(() => {
|
|
4508
|
+
const map = /* @__PURE__ */ new Map();
|
|
4509
|
+
filtered.forEach((o, i) => {
|
|
4510
|
+
if (o.isGroup) map.set(i, collectGroupLeafValues(filtered, i));
|
|
4511
|
+
});
|
|
4512
|
+
return map;
|
|
4513
|
+
}, [filtered]);
|
|
4490
4514
|
const navigableItems = react.useMemo(() => {
|
|
4491
4515
|
const items = [];
|
|
4492
4516
|
if (multi && useSelectAll && allValues.length > 0) {
|
|
4493
|
-
items.push({
|
|
4517
|
+
items.push({ key: "__select_all__", isSelectAll: true });
|
|
4494
4518
|
}
|
|
4495
|
-
filtered.forEach((o) => {
|
|
4496
|
-
if (
|
|
4519
|
+
filtered.forEach((o, i) => {
|
|
4520
|
+
if (o.disabled) return;
|
|
4521
|
+
if (o.isGroup) {
|
|
4522
|
+
if (!multi) return;
|
|
4523
|
+
const leafValues = groupLeafValues.get(i) ?? [];
|
|
4524
|
+
if (leafValues.length === 0) return;
|
|
4525
|
+
items.push({ key: `__group_${i}__`, isGroup: true, leafValues });
|
|
4526
|
+
} else {
|
|
4527
|
+
items.push({ key: o.value, value: o.value });
|
|
4528
|
+
}
|
|
4497
4529
|
});
|
|
4498
4530
|
return items;
|
|
4499
|
-
}, [allValues.length, filtered, multi, useSelectAll]);
|
|
4531
|
+
}, [allValues.length, filtered, groupLeafValues, multi, useSelectAll]);
|
|
4500
4532
|
const triggerText = (() => {
|
|
4501
4533
|
if (selected.length === 0) return placeholder;
|
|
4502
4534
|
if (multi) {
|
|
@@ -4518,6 +4550,17 @@ var SSelect = react.forwardRef(function SSelect2({
|
|
|
4518
4550
|
const handleSelectAll = () => {
|
|
4519
4551
|
emit(allSelected ? [] : allValues);
|
|
4520
4552
|
};
|
|
4553
|
+
const handleToggleGroup = (leafValues) => {
|
|
4554
|
+
if (leafValues.length === 0) return;
|
|
4555
|
+
const selectedSet = new Set(selected);
|
|
4556
|
+
const allSelectedInGroup = leafValues.every((v) => selectedSet.has(v));
|
|
4557
|
+
if (allSelectedInGroup) {
|
|
4558
|
+
const remove = new Set(leafValues);
|
|
4559
|
+
emit(selected.filter((v) => !remove.has(v)));
|
|
4560
|
+
} else {
|
|
4561
|
+
emit([...selected, ...leafValues.filter((v) => !selectedSet.has(v))]);
|
|
4562
|
+
}
|
|
4563
|
+
};
|
|
4521
4564
|
react.useEffect(() => {
|
|
4522
4565
|
if (!open) {
|
|
4523
4566
|
setFocusedIndex(-1);
|
|
@@ -4558,6 +4601,10 @@ var SSelect = react.forwardRef(function SSelect2({
|
|
|
4558
4601
|
handleSelectAll();
|
|
4559
4602
|
return;
|
|
4560
4603
|
}
|
|
4604
|
+
if (focused.isGroup) {
|
|
4605
|
+
handleToggleGroup(focused.leafValues ?? []);
|
|
4606
|
+
return;
|
|
4607
|
+
}
|
|
4561
4608
|
handleSelect(focused.value);
|
|
4562
4609
|
};
|
|
4563
4610
|
const handleKeyDown = (event) => {
|
|
@@ -4721,20 +4768,20 @@ var SSelect = react.forwardRef(function SSelect2({
|
|
|
4721
4768
|
items: filtered,
|
|
4722
4769
|
multi,
|
|
4723
4770
|
selected,
|
|
4724
|
-
|
|
4771
|
+
groupLeafValues,
|
|
4772
|
+
focusedValue: navigableItems[focusedIndex]?.key,
|
|
4725
4773
|
showSelectAll: multi && useSelectAll && allValues.length > 0,
|
|
4726
4774
|
selectAllLabel: allSelectedLabel,
|
|
4727
4775
|
selectAllSelected: allSelected,
|
|
4728
4776
|
onSelectAll: handleSelectAll,
|
|
4729
4777
|
onSelect: handleSelect,
|
|
4730
|
-
|
|
4731
|
-
|
|
4778
|
+
onToggleGroup: handleToggleGroup,
|
|
4779
|
+
onHoverValue: (key) => {
|
|
4780
|
+
if (key == null) {
|
|
4732
4781
|
setFocusedIndex(-1);
|
|
4733
4782
|
return;
|
|
4734
4783
|
}
|
|
4735
|
-
const index = navigableItems.findIndex(
|
|
4736
|
-
(item) => item.isSelectAll ? value2 === "__select_all__" : item.value === value2
|
|
4737
|
-
);
|
|
4784
|
+
const index = navigableItems.findIndex((item) => item.key === key);
|
|
4738
4785
|
if (index >= 0) setFocusedIndex(index);
|
|
4739
4786
|
}
|
|
4740
4787
|
}
|
|
@@ -4753,20 +4800,23 @@ function ItemList({
|
|
|
4753
4800
|
items,
|
|
4754
4801
|
multi,
|
|
4755
4802
|
selected,
|
|
4803
|
+
groupLeafValues,
|
|
4756
4804
|
focusedValue,
|
|
4757
4805
|
showSelectAll,
|
|
4758
4806
|
selectAllLabel,
|
|
4759
4807
|
selectAllSelected,
|
|
4760
4808
|
onSelectAll,
|
|
4761
4809
|
onSelect,
|
|
4810
|
+
onToggleGroup,
|
|
4762
4811
|
onHoverValue
|
|
4763
4812
|
}) {
|
|
4813
|
+
const selectedSet = react.useMemo(() => new Set(selected), [selected]);
|
|
4764
4814
|
const itemBase = cn(
|
|
4765
4815
|
"group flex w-full cursor-pointer select-none items-center gap-[8px] border-none bg-transparent text-left text-[12px] leading-[20px] outline-none",
|
|
4766
4816
|
"text-[color:var(--cmp-listItem-content-default)]",
|
|
4767
4817
|
"disabled:cursor-not-allowed disabled:text-[color:var(--cmp-listItem-content-disabled)]"
|
|
4768
4818
|
);
|
|
4769
|
-
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)]";
|
|
4819
|
+
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)]";
|
|
4770
4820
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { role: "listbox", "aria-multiselectable": multi || void 0, children: [
|
|
4771
4821
|
showSelectAll && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
4772
4822
|
"button",
|
|
@@ -4783,23 +4833,7 @@ function ItemList({
|
|
|
4783
4833
|
onMouseLeave: () => onHoverValue(null),
|
|
4784
4834
|
onClick: onSelectAll,
|
|
4785
4835
|
children: [
|
|
4786
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4787
|
-
"span",
|
|
4788
|
-
{
|
|
4789
|
-
className: cn(
|
|
4790
|
-
"flex h-[16px] w-[16px] flex-shrink-0 items-center justify-center rounded-[2px] border border-solid",
|
|
4791
|
-
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)]"
|
|
4792
|
-
),
|
|
4793
|
-
children: selectAllSelected && /* @__PURE__ */ jsxRuntime.jsx(
|
|
4794
|
-
SIcon,
|
|
4795
|
-
{
|
|
4796
|
-
name: "checkboxCheck",
|
|
4797
|
-
size: 12,
|
|
4798
|
-
color: "var(--cmp-checkbox-checked-icon-default)"
|
|
4799
|
-
}
|
|
4800
|
-
)
|
|
4801
|
-
}
|
|
4802
|
-
),
|
|
4836
|
+
/* @__PURE__ */ jsxRuntime.jsx(CheckboxBox, { state: selectAllSelected, focused: focusedValue === "__select_all__" }),
|
|
4803
4837
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1 truncate", children: selectAllLabel })
|
|
4804
4838
|
]
|
|
4805
4839
|
}
|
|
@@ -4809,12 +4843,71 @@ function ItemList({
|
|
|
4809
4843
|
const rowKey = `${o.value}-${idx}`;
|
|
4810
4844
|
if (o.isGroup) {
|
|
4811
4845
|
const isDepth1 = o.depth === 1;
|
|
4846
|
+
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";
|
|
4847
|
+
if (multi) {
|
|
4848
|
+
const leafValues = groupLeafValues.get(idx) ?? [];
|
|
4849
|
+
const total = leafValues.length;
|
|
4850
|
+
const selectedCount = leafValues.reduce((n, v) => selectedSet.has(v) ? n + 1 : n, 0);
|
|
4851
|
+
const groupState = total === 0 || selectedCount === 0 ? false : selectedCount === total ? true : null;
|
|
4852
|
+
const groupDisabled = o.disabled || total === 0;
|
|
4853
|
+
const groupFocused = focusedValue === `__group_${idx}__`;
|
|
4854
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
4855
|
+
"button",
|
|
4856
|
+
{
|
|
4857
|
+
type: "button",
|
|
4858
|
+
role: "option",
|
|
4859
|
+
"aria-selected": groupState === true,
|
|
4860
|
+
disabled: groupDisabled,
|
|
4861
|
+
"data-select-focused": groupFocused ? "true" : void 0,
|
|
4862
|
+
"data-select-active": groupFocused ? "true" : void 0,
|
|
4863
|
+
className: cn(
|
|
4864
|
+
"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)]",
|
|
4865
|
+
groupTone,
|
|
4866
|
+
groupDisabled ? "cursor-not-allowed text-[color:var(--cmp-listItem-content-disabled)]" : "cursor-pointer",
|
|
4867
|
+
groupDisabled && isDepth1 && "bg-[var(--cmp-listItem-depth1-bg-disabled)]",
|
|
4868
|
+
// 포커스 하이라이트는 depth 배경보다 뒤에 둬 tailwind-merge 로 이기게 한다.
|
|
4869
|
+
groupFocused && !groupDisabled && "bg-[var(--cmp-listItem-bg-hover)] text-[color:var(--cmp-listItem-content-hover)]"
|
|
4870
|
+
),
|
|
4871
|
+
style: { paddingLeft: padLeft },
|
|
4872
|
+
onMouseEnter: () => onHoverValue(`__group_${idx}__`),
|
|
4873
|
+
onMouseLeave: () => onHoverValue(null),
|
|
4874
|
+
onClick: () => onToggleGroup(leafValues),
|
|
4875
|
+
children: [
|
|
4876
|
+
/* @__PURE__ */ jsxRuntime.jsx(CheckboxBox, { state: groupState, disabled: groupDisabled, focused: groupFocused }),
|
|
4877
|
+
isHtmlLabel(o.label) ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
4878
|
+
"span",
|
|
4879
|
+
{
|
|
4880
|
+
className: "min-w-0 truncate",
|
|
4881
|
+
dangerouslySetInnerHTML: { __html: sanitizeInlineHtml(o.label) }
|
|
4882
|
+
}
|
|
4883
|
+
) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 truncate", children: o.label }),
|
|
4884
|
+
total > 0 && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
4885
|
+
"span",
|
|
4886
|
+
{
|
|
4887
|
+
className: cn(
|
|
4888
|
+
"shrink-0 font-medium",
|
|
4889
|
+
groupFocused ? "text-white" : "text-[color:var(--cmp-listItem-content-disabled)]"
|
|
4890
|
+
),
|
|
4891
|
+
children: [
|
|
4892
|
+
"(",
|
|
4893
|
+
selectedCount,
|
|
4894
|
+
"/",
|
|
4895
|
+
total,
|
|
4896
|
+
")"
|
|
4897
|
+
]
|
|
4898
|
+
}
|
|
4899
|
+
)
|
|
4900
|
+
]
|
|
4901
|
+
},
|
|
4902
|
+
rowKey
|
|
4903
|
+
);
|
|
4904
|
+
}
|
|
4812
4905
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
4813
4906
|
"div",
|
|
4814
4907
|
{
|
|
4815
4908
|
className: cn(
|
|
4816
4909
|
"select-none py-[4px] pr-[12px] text-[12px] leading-[20px] text-[color:var(--cmp-listItem-content-default)]",
|
|
4817
|
-
|
|
4910
|
+
groupTone,
|
|
4818
4911
|
// 하위가 전부 비활성이면(select.config 에서 bubble-up) 어미 그룹도 비활성 처리:
|
|
4819
4912
|
// 텍스트는 회색(#888), depth1 그룹은 파란 배경 대신 회색(#EEE)으로 (depth2 는 #F6F6F6 유지)
|
|
4820
4913
|
o.disabled && "cursor-not-allowed text-[color:var(--cmp-listItem-content-disabled)]",
|
|
@@ -4850,25 +4943,11 @@ function ItemList({
|
|
|
4850
4943
|
onClick: () => onSelect(o.value),
|
|
4851
4944
|
children: [
|
|
4852
4945
|
multi && /* @__PURE__ */ jsxRuntime.jsx(
|
|
4853
|
-
|
|
4946
|
+
CheckboxBox,
|
|
4854
4947
|
{
|
|
4855
|
-
|
|
4856
|
-
|
|
4857
|
-
|
|
4858
|
-
// 비활성: 회색 채움(#E1E1E1) + 회색 테두리(#CCCCCC)
|
|
4859
|
-
"cursor-not-allowed border-[color:var(--cmp-checkbox-border-disabled)] bg-[var(--cmp-checkbox-bg-disabled)]"
|
|
4860
|
-
) : cn(
|
|
4861
|
-
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)]"
|
|
4862
|
-
)
|
|
4863
|
-
),
|
|
4864
|
-
children: isSelected && /* @__PURE__ */ jsxRuntime.jsx(
|
|
4865
|
-
SIcon,
|
|
4866
|
-
{
|
|
4867
|
-
name: "checkboxCheck",
|
|
4868
|
-
size: 12,
|
|
4869
|
-
color: o.disabled ? "var(--cmp-checkbox-checked-icon-disabled)" : "var(--cmp-checkbox-checked-icon-default)"
|
|
4870
|
-
}
|
|
4871
|
-
)
|
|
4948
|
+
state: isSelected,
|
|
4949
|
+
disabled: o.disabled,
|
|
4950
|
+
focused: focusedValue === o.value
|
|
4872
4951
|
}
|
|
4873
4952
|
),
|
|
4874
4953
|
isHtmlLabel(o.label) ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -4885,6 +4964,33 @@ function ItemList({
|
|
|
4885
4964
|
})
|
|
4886
4965
|
] });
|
|
4887
4966
|
}
|
|
4967
|
+
function CheckboxBox({
|
|
4968
|
+
state,
|
|
4969
|
+
disabled = false,
|
|
4970
|
+
focused = false
|
|
4971
|
+
}) {
|
|
4972
|
+
const filled = state === true || state === null;
|
|
4973
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
4974
|
+
"span",
|
|
4975
|
+
{
|
|
4976
|
+
className: cn(
|
|
4977
|
+
"flex h-[16px] w-[16px] flex-shrink-0 items-center justify-center rounded-[2px] border border-solid",
|
|
4978
|
+
disabled ? (
|
|
4979
|
+
// 비활성: 회색 채움(#E1E1E1) + 회색 테두리(#CCCCCC)
|
|
4980
|
+
"cursor-not-allowed border-[color:var(--cmp-checkbox-border-disabled)] bg-[var(--cmp-checkbox-bg-disabled)]"
|
|
4981
|
+
) : 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)]"
|
|
4982
|
+
),
|
|
4983
|
+
children: filled && /* @__PURE__ */ jsxRuntime.jsx(
|
|
4984
|
+
SIcon,
|
|
4985
|
+
{
|
|
4986
|
+
name: state === null ? "checkboxIndeterminate" : "checkboxCheck",
|
|
4987
|
+
size: 12,
|
|
4988
|
+
color: disabled ? "var(--cmp-checkbox-checked-icon-disabled)" : "var(--cmp-checkbox-checked-icon-default)"
|
|
4989
|
+
}
|
|
4990
|
+
)
|
|
4991
|
+
}
|
|
4992
|
+
);
|
|
4993
|
+
}
|
|
4888
4994
|
function SRadio({
|
|
4889
4995
|
val,
|
|
4890
4996
|
value,
|