sellmate-design-system-react 9.0.0-beta.94 → 9.0.0-beta.95
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/index.cjs +347 -257
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +347 -257
- package/dist/index.js.map +1 -1
- package/dist/lib/tree-row.d.ts +11 -0
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -7731,6 +7731,13 @@ var TREE_LABEL_TYPOGRAPHY = {
|
|
|
7731
7731
|
rowHeight: "h-[32px]"
|
|
7732
7732
|
}
|
|
7733
7733
|
};
|
|
7734
|
+
var TREE_BRANCH_MOTION = { duration: 200, easing: "cubic-bezier(0, 0, 0.2, 1)" };
|
|
7735
|
+
function canAnimateTreeBranch() {
|
|
7736
|
+
if (typeof window === "undefined" || typeof HTMLElement.prototype.animate !== "function") {
|
|
7737
|
+
return false;
|
|
7738
|
+
}
|
|
7739
|
+
return !window.matchMedia?.("(prefers-reduced-motion: reduce)").matches;
|
|
7740
|
+
}
|
|
7734
7741
|
var TREE_ROW_CLASS = "group/tree-item box-border flex w-full items-center gap-(--cmp-tree-item-rowGap) overflow-hidden bg-(--cmp-tree-item-bg-default) px-(--cmp-tree-item-paddingX) text-left hover:bg-(--cmp-tree-item-bg-hover)";
|
|
7735
7742
|
var TREE_ROW_BORDER_CLASS = "border-b-(length:--cmp-tree-item-border-width-default) border-(--cmp-tree-item-border-default)";
|
|
7736
7743
|
var TREE_ROW_ACTIVE_CLASS = "bg-(--cmp-tree-item-bg-hover)";
|
|
@@ -16127,6 +16134,21 @@ var SSelectImpl = /* @__PURE__ */ react.forwardRef(function SSelect({
|
|
|
16127
16134
|
const [focusedIndex, setFocusedIndex] = react.useState(-1);
|
|
16128
16135
|
const [expanded, setExpanded] = react.useState(() => /* @__PURE__ */ new Set());
|
|
16129
16136
|
const [searchCollapsed, setSearchCollapsed] = react.useState(NO_SEARCH_COLLAPSED);
|
|
16137
|
+
const [treeMotion, setTreeMotion] = react.useState(null);
|
|
16138
|
+
const treeMotionTimerRef = react.useRef(null);
|
|
16139
|
+
const pendingCollapseRef = react.useRef(null);
|
|
16140
|
+
const cancelTreeMotion = () => {
|
|
16141
|
+
if (treeMotionTimerRef.current != null) clearTimeout(treeMotionTimerRef.current);
|
|
16142
|
+
treeMotionTimerRef.current = null;
|
|
16143
|
+
pendingCollapseRef.current = null;
|
|
16144
|
+
setTreeMotion(null);
|
|
16145
|
+
};
|
|
16146
|
+
react.useEffect(
|
|
16147
|
+
() => () => {
|
|
16148
|
+
if (treeMotionTimerRef.current != null) clearTimeout(treeMotionTimerRef.current);
|
|
16149
|
+
},
|
|
16150
|
+
[]
|
|
16151
|
+
);
|
|
16130
16152
|
const baseId = react.useId();
|
|
16131
16153
|
const listboxId = `${baseId}-listbox`;
|
|
16132
16154
|
const activeOptionId = `${baseId}-active-option`;
|
|
@@ -16194,6 +16216,7 @@ var SSelectImpl = /* @__PURE__ */ react.forwardRef(function SSelect({
|
|
|
16194
16216
|
const commitOpen = (next, valueForRules) => {
|
|
16195
16217
|
setOpen(next);
|
|
16196
16218
|
onOpenChange?.(next);
|
|
16219
|
+
cancelTreeMotion();
|
|
16197
16220
|
if (next) {
|
|
16198
16221
|
dismissedByPointerRef.current = false;
|
|
16199
16222
|
alignSelectedTopRef.current = true;
|
|
@@ -16264,6 +16287,10 @@ var SSelectImpl = /* @__PURE__ */ react.forwardRef(function SSelect({
|
|
|
16264
16287
|
(groupValue) => treeSearching ? !searchCollapsed.has(groupValue) : expanded.has(groupValue),
|
|
16265
16288
|
[expanded, searchCollapsed, treeSearching]
|
|
16266
16289
|
);
|
|
16290
|
+
const isGroupShownExpanded = react.useCallback(
|
|
16291
|
+
(groupValue) => isGroupExpanded(groupValue) && !(treeMotion != null && !treeMotion.opening && treeMotion.group === groupValue),
|
|
16292
|
+
[isGroupExpanded, treeMotion]
|
|
16293
|
+
);
|
|
16267
16294
|
const treeRows = react.useMemo(
|
|
16268
16295
|
() => isTree ? visibleTreeRows(searched, (row) => isGroupExpanded(row.value)) : null,
|
|
16269
16296
|
[isGroupExpanded, isTree, searched]
|
|
@@ -16553,6 +16580,7 @@ var SSelectImpl = /* @__PURE__ */ react.forwardRef(function SSelect({
|
|
|
16553
16580
|
const handleSearchChange = (next) => {
|
|
16554
16581
|
setSearch(next);
|
|
16555
16582
|
setSearchCollapsed(NO_SEARCH_COLLAPSED);
|
|
16583
|
+
cancelTreeMotion();
|
|
16556
16584
|
};
|
|
16557
16585
|
const handleToggleExpand = (groupValue) => {
|
|
16558
16586
|
rangeAnchorRef.current = null;
|
|
@@ -16562,8 +16590,41 @@ var SSelectImpl = /* @__PURE__ */ react.forwardRef(function SSelect({
|
|
|
16562
16590
|
else next.add(groupValue);
|
|
16563
16591
|
return next;
|
|
16564
16592
|
};
|
|
16565
|
-
|
|
16566
|
-
|
|
16593
|
+
const applyToggle = treeSearching ? () => setSearchCollapsed(toggle) : () => setExpanded(toggle);
|
|
16594
|
+
if (treeMotionTimerRef.current != null) clearTimeout(treeMotionTimerRef.current);
|
|
16595
|
+
treeMotionTimerRef.current = null;
|
|
16596
|
+
const pendingCollapse = pendingCollapseRef.current;
|
|
16597
|
+
pendingCollapseRef.current = null;
|
|
16598
|
+
if (treeMotion?.group === groupValue && !treeMotion.opening) {
|
|
16599
|
+
setTreeMotion({ group: groupValue, opening: true });
|
|
16600
|
+
treeMotionTimerRef.current = setTimeout(
|
|
16601
|
+
() => setTreeMotion(null),
|
|
16602
|
+
TREE_BRANCH_MOTION.duration
|
|
16603
|
+
);
|
|
16604
|
+
return;
|
|
16605
|
+
}
|
|
16606
|
+
pendingCollapse?.();
|
|
16607
|
+
if (!canAnimateTreeBranch()) {
|
|
16608
|
+
setTreeMotion(null);
|
|
16609
|
+
applyToggle();
|
|
16610
|
+
return;
|
|
16611
|
+
}
|
|
16612
|
+
if (isGroupExpanded(groupValue)) {
|
|
16613
|
+
setTreeMotion({ group: groupValue, opening: false });
|
|
16614
|
+
pendingCollapseRef.current = applyToggle;
|
|
16615
|
+
treeMotionTimerRef.current = setTimeout(() => {
|
|
16616
|
+
pendingCollapseRef.current = null;
|
|
16617
|
+
setTreeMotion(null);
|
|
16618
|
+
applyToggle();
|
|
16619
|
+
}, TREE_BRANCH_MOTION.duration);
|
|
16620
|
+
} else {
|
|
16621
|
+
applyToggle();
|
|
16622
|
+
setTreeMotion({ group: groupValue, opening: true });
|
|
16623
|
+
treeMotionTimerRef.current = setTimeout(
|
|
16624
|
+
() => setTreeMotion(null),
|
|
16625
|
+
TREE_BRANCH_MOTION.duration
|
|
16626
|
+
);
|
|
16627
|
+
}
|
|
16567
16628
|
};
|
|
16568
16629
|
const handleToggleGroup = (leafValues) => {
|
|
16569
16630
|
if (leafValues.length === 0) return;
|
|
@@ -16673,7 +16734,7 @@ var SSelectImpl = /* @__PURE__ */ react.forwardRef(function SSelect({
|
|
|
16673
16734
|
const row = rowIndex != null ? filtered[rowIndex] : void 0;
|
|
16674
16735
|
if (rowIndex == null || row == null) return;
|
|
16675
16736
|
event.preventDefault();
|
|
16676
|
-
const rowExpanded = row.isGroup &&
|
|
16737
|
+
const rowExpanded = row.isGroup && isGroupShownExpanded(row.value);
|
|
16677
16738
|
if (event.key === "ArrowRight") {
|
|
16678
16739
|
if (!row.isGroup) return;
|
|
16679
16740
|
if (!rowExpanded) {
|
|
@@ -16918,8 +16979,9 @@ var SSelectImpl = /* @__PURE__ */ react.forwardRef(function SSelect({
|
|
|
16918
16979
|
setSize: isDepth || isTree ? void 0 : rowCount,
|
|
16919
16980
|
multi,
|
|
16920
16981
|
tree: isTree,
|
|
16921
|
-
isExpanded:
|
|
16982
|
+
isExpanded: isGroupShownExpanded,
|
|
16922
16983
|
onToggleExpand: handleToggleExpand,
|
|
16984
|
+
treeMotion,
|
|
16923
16985
|
treeLines,
|
|
16924
16986
|
selected,
|
|
16925
16987
|
groupLeafValues,
|
|
@@ -16976,6 +17038,7 @@ function ItemList({
|
|
|
16976
17038
|
tree,
|
|
16977
17039
|
isExpanded,
|
|
16978
17040
|
onToggleExpand,
|
|
17041
|
+
treeMotion,
|
|
16979
17042
|
treeLines,
|
|
16980
17043
|
selected,
|
|
16981
17044
|
groupLeafValues,
|
|
@@ -16996,295 +17059,322 @@ function ItemList({
|
|
|
16996
17059
|
"disabled:cursor-not-allowed disabled:text-[color:var(--cmp-listBoxItem-content-disabled)]"
|
|
16997
17060
|
);
|
|
16998
17061
|
const activeClass = "hover:bg-[var(--cmp-listBoxItem-bg-hover)] hover:text-[color:var(--cmp-listBoxItem-content-hover)] focus-visible:bg-[var(--cmp-listBoxItem-bg-hover)] focus-visible:text-[color:var(--cmp-listBoxItem-content-hover)]";
|
|
16999
|
-
|
|
17000
|
-
|
|
17001
|
-
|
|
17002
|
-
|
|
17003
|
-
|
|
17004
|
-
|
|
17005
|
-
|
|
17006
|
-
|
|
17007
|
-
|
|
17008
|
-
|
|
17009
|
-
|
|
17010
|
-
|
|
17011
|
-
|
|
17012
|
-
|
|
17013
|
-
|
|
17014
|
-
|
|
17015
|
-
|
|
17016
|
-
onToggle: onSelectAll
|
|
17017
|
-
}
|
|
17018
|
-
)
|
|
17019
|
-
) : /* @__PURE__ */ jsxRuntime.jsxs(
|
|
17020
|
-
"button",
|
|
17062
|
+
const wrapTreeMotion = (nodes) => {
|
|
17063
|
+
if (!tree || treeMotion == null) return nodes;
|
|
17064
|
+
const start = items.findIndex((o) => o.isGroup && o.value === treeMotion.group);
|
|
17065
|
+
if (start < 0) return nodes;
|
|
17066
|
+
let end = start + 1;
|
|
17067
|
+
while (end < items.length && items[end].depth > items[start].depth) end++;
|
|
17068
|
+
if (end === start + 1) return nodes;
|
|
17069
|
+
return [
|
|
17070
|
+
...nodes.slice(0, start + 1),
|
|
17071
|
+
/* @__PURE__ */ jsxRuntime.jsx(TreeRowsMotion, { opening: treeMotion.opening, children: nodes.slice(start + 1, end) }, `__motion_${treeMotion.group}__`),
|
|
17072
|
+
...nodes.slice(end)
|
|
17073
|
+
];
|
|
17074
|
+
};
|
|
17075
|
+
const selectAllRow = showSelectAll && (tree ? (
|
|
17076
|
+
// 트리의 전체선택 줄은 STree 의 "전체" 줄 그대로다 (lib/tree-row).
|
|
17077
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
17078
|
+
TreeAllRow,
|
|
17021
17079
|
{
|
|
17022
|
-
type: "button",
|
|
17023
17080
|
"data-select-all-row": true,
|
|
17024
17081
|
id: focusedValue === "__select_all__" ? activeOptionId : void 0,
|
|
17025
17082
|
"data-select-focused": focusedValue === "__select_all__" ? "true" : void 0,
|
|
17026
17083
|
"data-select-active": focusedValue === "__select_all__" ? "true" : void 0,
|
|
17027
|
-
|
|
17028
|
-
|
|
17029
|
-
|
|
17030
|
-
|
|
17031
|
-
|
|
17084
|
+
tabIndex: -1,
|
|
17085
|
+
label: selectAllLabel,
|
|
17086
|
+
selected: selectAllSelected,
|
|
17087
|
+
active: focusedValue === "__select_all__",
|
|
17088
|
+
className: "select-none",
|
|
17032
17089
|
onMouseEnter: () => onHoverValue("__select_all__"),
|
|
17033
17090
|
onMouseLeave: () => onHoverValue(null),
|
|
17034
|
-
|
|
17035
|
-
children: [
|
|
17036
|
-
/* @__PURE__ */ jsxRuntime.jsx(CheckboxBox, { state: selectAllSelected, focused: focusedValue === "__select_all__" }),
|
|
17037
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1 truncate", children: selectAllLabel })
|
|
17038
|
-
]
|
|
17091
|
+
onToggle: onSelectAll
|
|
17039
17092
|
}
|
|
17040
|
-
)
|
|
17041
|
-
|
|
17042
|
-
|
|
17043
|
-
|
|
17044
|
-
|
|
17045
|
-
|
|
17046
|
-
|
|
17047
|
-
|
|
17048
|
-
|
|
17049
|
-
|
|
17050
|
-
|
|
17051
|
-
|
|
17052
|
-
|
|
17053
|
-
|
|
17054
|
-
|
|
17055
|
-
|
|
17056
|
-
|
|
17057
|
-
|
|
17058
|
-
|
|
17059
|
-
|
|
17060
|
-
|
|
17061
|
-
|
|
17062
|
-
|
|
17063
|
-
|
|
17064
|
-
|
|
17065
|
-
|
|
17066
|
-
|
|
17067
|
-
|
|
17068
|
-
|
|
17069
|
-
|
|
17070
|
-
|
|
17071
|
-
|
|
17072
|
-
|
|
17073
|
-
|
|
17074
|
-
|
|
17075
|
-
|
|
17076
|
-
|
|
17077
|
-
|
|
17078
|
-
|
|
17079
|
-
|
|
17080
|
-
|
|
17081
|
-
|
|
17082
|
-
|
|
17083
|
-
|
|
17084
|
-
|
|
17085
|
-
|
|
17086
|
-
|
|
17087
|
-
|
|
17088
|
-
|
|
17089
|
-
|
|
17090
|
-
|
|
17091
|
-
|
|
17092
|
-
|
|
17093
|
-
|
|
17094
|
-
|
|
17095
|
-
|
|
17096
|
-
|
|
17097
|
-
|
|
17098
|
-
|
|
17099
|
-
|
|
17100
|
-
|
|
17101
|
-
|
|
17102
|
-
|
|
17103
|
-
|
|
17104
|
-
|
|
17105
|
-
|
|
17106
|
-
|
|
17107
|
-
|
|
17108
|
-
|
|
17109
|
-
|
|
17110
|
-
|
|
17111
|
-
|
|
17112
|
-
|
|
17113
|
-
|
|
17114
|
-
|
|
17115
|
-
|
|
17116
|
-
|
|
17117
|
-
|
|
17118
|
-
|
|
17119
|
-
|
|
17120
|
-
|
|
17121
|
-
selectable: true,
|
|
17122
|
-
disabled: rowDisabled,
|
|
17123
|
-
control: multi && /* @__PURE__ */ jsxRuntime.jsx(
|
|
17124
|
-
"span",
|
|
17125
|
-
{
|
|
17126
|
-
"data-tree-control": true,
|
|
17127
|
-
className: "flex shrink-0",
|
|
17128
|
-
onPointerDownCapture: (event) => {
|
|
17129
|
-
checkboxShiftRef.current = event.shiftKey;
|
|
17130
|
-
},
|
|
17131
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
17132
|
-
SCheckbox,
|
|
17133
|
-
{
|
|
17134
|
-
value: checkState,
|
|
17135
|
-
disabled: rowDisabled,
|
|
17136
|
-
onValueChange: toggle
|
|
17137
|
-
}
|
|
17138
|
-
)
|
|
17139
|
-
}
|
|
17140
|
-
),
|
|
17141
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
17142
|
-
OptionLabel,
|
|
17143
|
-
{
|
|
17144
|
-
label: o.label,
|
|
17145
|
-
className: treeLabelClass("sm", {
|
|
17146
|
-
// 고른 줄은 굵게 — multi_tree 는 일부만 고른 그룹도 굵다 (STree 와 같다)
|
|
17147
|
-
emphasized: checkState !== false,
|
|
17148
|
-
disabled: rowDisabled,
|
|
17149
|
-
selectable: true
|
|
17150
|
-
})
|
|
17151
|
-
}
|
|
17152
|
-
)
|
|
17153
|
-
}
|
|
17154
|
-
)
|
|
17155
|
-
] })
|
|
17156
|
-
]
|
|
17093
|
+
)
|
|
17094
|
+
) : /* @__PURE__ */ jsxRuntime.jsxs(
|
|
17095
|
+
"button",
|
|
17096
|
+
{
|
|
17097
|
+
type: "button",
|
|
17098
|
+
"data-select-all-row": true,
|
|
17099
|
+
id: focusedValue === "__select_all__" ? activeOptionId : void 0,
|
|
17100
|
+
"data-select-focused": focusedValue === "__select_all__" ? "true" : void 0,
|
|
17101
|
+
"data-select-active": focusedValue === "__select_all__" ? "true" : void 0,
|
|
17102
|
+
className: cn(
|
|
17103
|
+
itemBase,
|
|
17104
|
+
focusedValue === "__select_all__" && "bg-[var(--cmp-listBoxItem-bg-hover)] text-[color:var(--cmp-listBoxItem-content-hover)]"
|
|
17105
|
+
),
|
|
17106
|
+
style: { ...LIST_BOX_ITEM_PADDING_Y_RIGHT, paddingLeft: LIST_BOX_ITEM_PADDING_LEFT[1] },
|
|
17107
|
+
onMouseEnter: () => onHoverValue("__select_all__"),
|
|
17108
|
+
onMouseLeave: () => onHoverValue(null),
|
|
17109
|
+
onClick: onSelectAll,
|
|
17110
|
+
children: [
|
|
17111
|
+
/* @__PURE__ */ jsxRuntime.jsx(CheckboxBox, { state: selectAllSelected, focused: focusedValue === "__select_all__" }),
|
|
17112
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1 truncate", children: selectAllLabel })
|
|
17113
|
+
]
|
|
17114
|
+
}
|
|
17115
|
+
));
|
|
17116
|
+
const rowNodes = items.map((o, windowIndex) => {
|
|
17117
|
+
const idx = windowIndex + indexOffset;
|
|
17118
|
+
const padLeft = LIST_BOX_ITEM_PADDING_LEFT[o.depth === 1 ? 1 : o.depth === 2 ? 2 : 3];
|
|
17119
|
+
const rowKey = `${o.value}-${idx}`;
|
|
17120
|
+
if (tree) {
|
|
17121
|
+
const togglesLeaves = multi && o.isGroup;
|
|
17122
|
+
const leafValues = togglesLeaves ? groupLeafValues.get(idx) ?? [] : [];
|
|
17123
|
+
const total = leafValues.length;
|
|
17124
|
+
const selectedCount = leafValues.reduce((n, v) => selectedSet.has(v) ? n + 1 : n, 0);
|
|
17125
|
+
const isSelected2 = selectedSet.has(o.value);
|
|
17126
|
+
const checkState = togglesLeaves ? total === 0 || selectedCount === 0 ? false : selectedCount === total ? true : null : isSelected2;
|
|
17127
|
+
const rowDisabled = o.disabled || togglesLeaves && total === 0;
|
|
17128
|
+
const focusKey = togglesLeaves ? `__group_${idx}__` : o.value;
|
|
17129
|
+
const focused = focusedValue === focusKey;
|
|
17130
|
+
const expanded = o.isGroup && isExpanded(o.value);
|
|
17131
|
+
const line = treeLines[idx];
|
|
17132
|
+
const toggle = () => {
|
|
17133
|
+
if (rowDisabled) return;
|
|
17134
|
+
if (togglesLeaves) onToggleGroup(leafValues);
|
|
17135
|
+
else onSelect(o.value, idx, checkboxShiftRef.current);
|
|
17136
|
+
};
|
|
17137
|
+
return (
|
|
17138
|
+
// eslint-disable-next-line jsx-a11y/click-events-have-key-events -- 키보드는 트리거(또는 검색 입력)가 aria-activedescendant 로 맡는다. 줄로 포커스를 옮기지 않는다
|
|
17139
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
17140
|
+
"div",
|
|
17141
|
+
{
|
|
17142
|
+
tabIndex: -1,
|
|
17143
|
+
"data-select-row": true,
|
|
17144
|
+
"data-index": idx,
|
|
17145
|
+
id: focused ? activeOptionId : void 0,
|
|
17146
|
+
role: "treeitem",
|
|
17147
|
+
"aria-level": o.depth,
|
|
17148
|
+
"aria-posinset": line?.posInSet,
|
|
17149
|
+
"aria-setsize": line?.setSize,
|
|
17150
|
+
"aria-expanded": o.isGroup ? expanded : void 0,
|
|
17151
|
+
"aria-selected": checkState === true,
|
|
17152
|
+
"aria-disabled": rowDisabled || void 0,
|
|
17153
|
+
"data-select-focused": focused ? "true" : void 0,
|
|
17154
|
+
"data-select-active": focused ? "true" : void 0,
|
|
17155
|
+
className: cn(
|
|
17156
|
+
TREE_ROW_CLASS,
|
|
17157
|
+
TREE_LABEL_TYPOGRAPHY.sm.rowHeight,
|
|
17158
|
+
TREE_ROW_BORDER_CLASS,
|
|
17159
|
+
"select-none",
|
|
17160
|
+
o.isGroup && "cursor-pointer",
|
|
17161
|
+
focused && TREE_ROW_ACTIVE_CLASS
|
|
17162
|
+
),
|
|
17163
|
+
onMouseEnter: () => onHoverValue(focusKey),
|
|
17164
|
+
onMouseLeave: () => onHoverValue(null),
|
|
17165
|
+
onClick: (event) => {
|
|
17166
|
+
if (isRowControlClick(event)) return;
|
|
17167
|
+
if (isRowSelectClick(event)) {
|
|
17168
|
+
if (rowDisabled) return;
|
|
17169
|
+
if (togglesLeaves) onToggleGroup(leafValues);
|
|
17170
|
+
else onSelect(o.value, idx, event.shiftKey);
|
|
17171
|
+
return;
|
|
17172
|
+
}
|
|
17173
|
+
if (o.isGroup) onToggleExpand(o.value);
|
|
17157
17174
|
},
|
|
17158
|
-
|
|
17159
|
-
|
|
17160
|
-
|
|
17161
|
-
|
|
17162
|
-
|
|
17163
|
-
|
|
17164
|
-
|
|
17165
|
-
|
|
17166
|
-
|
|
17167
|
-
|
|
17168
|
-
|
|
17169
|
-
if (multi) {
|
|
17170
|
-
const leafValues = groupLeafValues.get(idx) ?? [];
|
|
17171
|
-
const total = leafValues.length;
|
|
17172
|
-
const selectedCount = leafValues.reduce((n, v) => selectedSet.has(v) ? n + 1 : n, 0);
|
|
17173
|
-
const groupState = total === 0 || selectedCount === 0 ? false : selectedCount === total ? true : null;
|
|
17174
|
-
const groupDisabled = o.disabled || total === 0;
|
|
17175
|
-
const groupFocused = focusedValue === `__group_${idx}__`;
|
|
17176
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
17177
|
-
"button",
|
|
17178
|
-
{
|
|
17179
|
-
type: "button",
|
|
17180
|
-
"data-select-row": true,
|
|
17181
|
-
"data-index": idx,
|
|
17182
|
-
id: groupFocused ? activeOptionId : void 0,
|
|
17183
|
-
role: "option",
|
|
17184
|
-
"aria-selected": groupState === true,
|
|
17185
|
-
disabled: groupDisabled,
|
|
17186
|
-
"data-select-focused": groupFocused ? "true" : void 0,
|
|
17187
|
-
"data-select-active": groupFocused ? "true" : void 0,
|
|
17188
|
-
className: cn(
|
|
17189
|
-
"flex w-full select-none items-center gap-(--cmp-listBoxItem-gap) border-none py-(--cmp-listBoxItem-paddingY) pr-(--cmp-listBoxItem-paddingRight) text-left typo-field-sm-default text-[color:var(--cmp-listBoxItem-content-default)]",
|
|
17190
|
-
groupTone,
|
|
17191
|
-
groupDisabled ? "cursor-not-allowed text-[color:var(--cmp-listBoxItem-content-disabled)]" : "cursor-pointer",
|
|
17192
|
-
groupDisabled && isDepth1 && "bg-[var(--cmp-listBoxItem-depth1-bg-disabled)]",
|
|
17193
|
-
// 포커스 하이라이트는 depth 배경보다 뒤에 둬 tailwind-merge 로 이기게 한다.
|
|
17194
|
-
groupFocused && !groupDisabled && "bg-[var(--cmp-listBoxItem-bg-hover)] text-[color:var(--cmp-listBoxItem-content-hover)]"
|
|
17175
|
+
children: [
|
|
17176
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
17177
|
+
TreeConnector,
|
|
17178
|
+
{
|
|
17179
|
+
guideline: true,
|
|
17180
|
+
size: "sm",
|
|
17181
|
+
depth: o.depth,
|
|
17182
|
+
hasChildren: o.isGroup,
|
|
17183
|
+
isLast: line?.isLast ?? true,
|
|
17184
|
+
ancestorLast: line?.ancestorLast ?? []
|
|
17185
|
+
}
|
|
17195
17186
|
),
|
|
17196
|
-
|
|
17197
|
-
|
|
17198
|
-
|
|
17199
|
-
|
|
17200
|
-
|
|
17201
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
17202
|
-
|
|
17203
|
-
total > 0 && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
17204
|
-
"span",
|
|
17187
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: TREE_CONTENT_CLASS, children: [
|
|
17188
|
+
o.isGroup ? (
|
|
17189
|
+
// 비활성 그룹도 접고 편다 — 그룹이 비활성이어도 아래에 고를 수 있는 자식이 있을 수 있다.
|
|
17190
|
+
/* @__PURE__ */ jsxRuntime.jsx(TreeCaret, { expanded, onToggle: () => onToggleExpand(o.value) })
|
|
17191
|
+
) : /* @__PURE__ */ jsxRuntime.jsx(TreeCaretSpacer, {}),
|
|
17192
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
17193
|
+
TreeSelectZone,
|
|
17205
17194
|
{
|
|
17206
|
-
|
|
17207
|
-
|
|
17208
|
-
|
|
17195
|
+
selectable: true,
|
|
17196
|
+
disabled: rowDisabled,
|
|
17197
|
+
control: multi && /* @__PURE__ */ jsxRuntime.jsx(
|
|
17198
|
+
"span",
|
|
17199
|
+
{
|
|
17200
|
+
"data-tree-control": true,
|
|
17201
|
+
className: "flex shrink-0",
|
|
17202
|
+
onPointerDownCapture: (event) => {
|
|
17203
|
+
checkboxShiftRef.current = event.shiftKey;
|
|
17204
|
+
},
|
|
17205
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(SCheckbox, { value: checkState, disabled: rowDisabled, onValueChange: toggle })
|
|
17206
|
+
}
|
|
17209
17207
|
),
|
|
17210
|
-
children:
|
|
17211
|
-
|
|
17212
|
-
|
|
17213
|
-
|
|
17214
|
-
|
|
17215
|
-
|
|
17216
|
-
|
|
17208
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
17209
|
+
OptionLabel,
|
|
17210
|
+
{
|
|
17211
|
+
label: o.label,
|
|
17212
|
+
className: treeLabelClass("sm", {
|
|
17213
|
+
// 고른 줄은 굵게 — multi_tree 는 일부만 고른 그룹도 굵다 (STree 와 같다)
|
|
17214
|
+
emphasized: checkState !== false,
|
|
17215
|
+
disabled: rowDisabled,
|
|
17216
|
+
selectable: true
|
|
17217
|
+
})
|
|
17218
|
+
}
|
|
17219
|
+
)
|
|
17217
17220
|
}
|
|
17218
17221
|
)
|
|
17219
|
-
]
|
|
17220
|
-
|
|
17221
|
-
|
|
17222
|
-
|
|
17223
|
-
|
|
17224
|
-
|
|
17225
|
-
|
|
17222
|
+
] })
|
|
17223
|
+
]
|
|
17224
|
+
},
|
|
17225
|
+
rowKey
|
|
17226
|
+
)
|
|
17227
|
+
);
|
|
17228
|
+
}
|
|
17229
|
+
if (o.isGroup) {
|
|
17230
|
+
const isDepth1 = o.depth === 1;
|
|
17231
|
+
const isFirstRow = idx === 0 && !showSelectAll;
|
|
17232
|
+
const groupTone = cn(
|
|
17233
|
+
isDepth1 ? "bg-[var(--cmp-listBoxItem-depth1-bg-default)] font-bold" : "bg-[var(--cmp-listBoxItem-depth2-middle-bg)] font-medium",
|
|
17234
|
+
isDepth1 && !isFirstRow && "border-t border-solid border-[color:var(--cmp-listBoxItem-depth1-border-color)]"
|
|
17235
|
+
);
|
|
17236
|
+
if (multi) {
|
|
17237
|
+
const leafValues = groupLeafValues.get(idx) ?? [];
|
|
17238
|
+
const total = leafValues.length;
|
|
17239
|
+
const selectedCount = leafValues.reduce((n, v) => selectedSet.has(v) ? n + 1 : n, 0);
|
|
17240
|
+
const groupState = total === 0 || selectedCount === 0 ? false : selectedCount === total ? true : null;
|
|
17241
|
+
const groupDisabled = o.disabled || total === 0;
|
|
17242
|
+
const groupFocused = focusedValue === `__group_${idx}__`;
|
|
17243
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
17244
|
+
"button",
|
|
17226
17245
|
{
|
|
17246
|
+
type: "button",
|
|
17227
17247
|
"data-select-row": true,
|
|
17228
17248
|
"data-index": idx,
|
|
17249
|
+
id: groupFocused ? activeOptionId : void 0,
|
|
17250
|
+
role: "option",
|
|
17251
|
+
"aria-selected": groupState === true,
|
|
17252
|
+
disabled: groupDisabled,
|
|
17253
|
+
"data-select-focused": groupFocused ? "true" : void 0,
|
|
17254
|
+
"data-select-active": groupFocused ? "true" : void 0,
|
|
17229
17255
|
className: cn(
|
|
17230
|
-
"select-none py-(--cmp-listBoxItem-paddingY) pr-(--cmp-listBoxItem-paddingRight) typo-field-sm-default text-[color:var(--cmp-listBoxItem-content-default)]",
|
|
17256
|
+
"flex w-full select-none items-center gap-(--cmp-listBoxItem-gap) border-none py-(--cmp-listBoxItem-paddingY) pr-(--cmp-listBoxItem-paddingRight) text-left typo-field-sm-default text-[color:var(--cmp-listBoxItem-content-default)]",
|
|
17231
17257
|
groupTone,
|
|
17232
|
-
|
|
17233
|
-
|
|
17234
|
-
|
|
17235
|
-
|
|
17258
|
+
groupDisabled ? "cursor-not-allowed text-[color:var(--cmp-listBoxItem-content-disabled)]" : "cursor-pointer",
|
|
17259
|
+
groupDisabled && isDepth1 && "bg-[var(--cmp-listBoxItem-depth1-bg-disabled)]",
|
|
17260
|
+
// 포커스 하이라이트는 depth 배경보다 뒤에 둬 tailwind-merge 로 이기게 한다.
|
|
17261
|
+
groupFocused && !groupDisabled && "bg-[var(--cmp-listBoxItem-bg-hover)] text-[color:var(--cmp-listBoxItem-content-hover)]"
|
|
17236
17262
|
),
|
|
17237
17263
|
style: { paddingLeft: padLeft },
|
|
17238
|
-
|
|
17264
|
+
onMouseEnter: () => onHoverValue(`__group_${idx}__`),
|
|
17265
|
+
onMouseLeave: () => onHoverValue(null),
|
|
17266
|
+
onClick: () => onToggleGroup(leafValues),
|
|
17267
|
+
children: [
|
|
17268
|
+
/* @__PURE__ */ jsxRuntime.jsx(CheckboxBox, { state: groupState, disabled: groupDisabled, focused: groupFocused }),
|
|
17269
|
+
/* @__PURE__ */ jsxRuntime.jsx(OptionLabel, { label: o.label, className: "min-w-0 truncate" }),
|
|
17270
|
+
total > 0 && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
17271
|
+
"span",
|
|
17272
|
+
{
|
|
17273
|
+
className: cn(
|
|
17274
|
+
"shrink-0 font-medium",
|
|
17275
|
+
groupFocused ? "text-white" : "text-[color:var(--cmp-listBoxItem-content-disabled)]"
|
|
17276
|
+
),
|
|
17277
|
+
children: [
|
|
17278
|
+
"(",
|
|
17279
|
+
selectedCount,
|
|
17280
|
+
"/",
|
|
17281
|
+
total,
|
|
17282
|
+
")"
|
|
17283
|
+
]
|
|
17284
|
+
}
|
|
17285
|
+
)
|
|
17286
|
+
]
|
|
17239
17287
|
},
|
|
17240
17288
|
rowKey
|
|
17241
17289
|
);
|
|
17242
17290
|
}
|
|
17243
|
-
|
|
17244
|
-
|
|
17245
|
-
"button",
|
|
17291
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
17292
|
+
"div",
|
|
17246
17293
|
{
|
|
17247
|
-
type: "button",
|
|
17248
17294
|
"data-select-row": true,
|
|
17249
17295
|
"data-index": idx,
|
|
17250
|
-
id: focusedValue === o.value ? activeOptionId : void 0,
|
|
17251
|
-
disabled: o.disabled,
|
|
17252
|
-
role: "option",
|
|
17253
|
-
"aria-selected": isSelected,
|
|
17254
|
-
"aria-setsize": setSize,
|
|
17255
|
-
"aria-posinset": setSize != null ? idx + 1 : void 0,
|
|
17256
|
-
"data-select-focused": focusedValue === o.value ? "true" : void 0,
|
|
17257
|
-
"data-select-active": focusedValue === o.value ? "true" : void 0,
|
|
17258
17296
|
className: cn(
|
|
17259
|
-
|
|
17260
|
-
|
|
17261
|
-
|
|
17262
|
-
//
|
|
17263
|
-
|
|
17264
|
-
|
|
17297
|
+
"select-none py-(--cmp-listBoxItem-paddingY) pr-(--cmp-listBoxItem-paddingRight) typo-field-sm-default text-[color:var(--cmp-listBoxItem-content-default)]",
|
|
17298
|
+
groupTone,
|
|
17299
|
+
// 하위가 전부 비활성이면(select.config 에서 bubble-up) 어미 그룹도 비활성 처리:
|
|
17300
|
+
// 텍스트는 회색(#888), depth1 그룹은 파란 배경 대신 회색(#EEE)으로 (depth2 는 #F6F6F6 유지)
|
|
17301
|
+
o.disabled && "cursor-not-allowed text-[color:var(--cmp-listBoxItem-content-disabled)]",
|
|
17302
|
+
o.disabled && isDepth1 && "bg-[var(--cmp-listBoxItem-depth1-bg-disabled)]"
|
|
17265
17303
|
),
|
|
17266
|
-
style: {
|
|
17267
|
-
|
|
17268
|
-
onMouseLeave: () => onHoverValue(null),
|
|
17269
|
-
onClick: (event) => onSelect(o.value, idx, event.shiftKey),
|
|
17270
|
-
children: [
|
|
17271
|
-
multi && /* @__PURE__ */ jsxRuntime.jsx(
|
|
17272
|
-
CheckboxBox,
|
|
17273
|
-
{
|
|
17274
|
-
state: isSelected,
|
|
17275
|
-
disabled: o.disabled,
|
|
17276
|
-
focused: focusedValue === o.value
|
|
17277
|
-
}
|
|
17278
|
-
),
|
|
17279
|
-
/* @__PURE__ */ jsxRuntime.jsx(OptionLabel, { label: o.label, className: "flex-1 truncate" })
|
|
17280
|
-
]
|
|
17304
|
+
style: { paddingLeft: padLeft },
|
|
17305
|
+
children: o.label
|
|
17281
17306
|
},
|
|
17282
17307
|
rowKey
|
|
17283
17308
|
);
|
|
17284
|
-
}
|
|
17309
|
+
}
|
|
17310
|
+
const isSelected = selected.includes(o.value);
|
|
17311
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
17312
|
+
"button",
|
|
17313
|
+
{
|
|
17314
|
+
type: "button",
|
|
17315
|
+
"data-select-row": true,
|
|
17316
|
+
"data-index": idx,
|
|
17317
|
+
id: focusedValue === o.value ? activeOptionId : void 0,
|
|
17318
|
+
disabled: o.disabled,
|
|
17319
|
+
role: "option",
|
|
17320
|
+
"aria-selected": isSelected,
|
|
17321
|
+
"aria-setsize": setSize,
|
|
17322
|
+
"aria-posinset": setSize != null ? idx + 1 : void 0,
|
|
17323
|
+
"data-select-focused": focusedValue === o.value ? "true" : void 0,
|
|
17324
|
+
"data-select-active": focusedValue === o.value ? "true" : void 0,
|
|
17325
|
+
className: cn(
|
|
17326
|
+
itemBase,
|
|
17327
|
+
isSelected && !multi && "font-bold text-[color:var(--cmp-listBoxItem-content-selected)]",
|
|
17328
|
+
focusedValue === o.value && "bg-[var(--cmp-listBoxItem-bg-hover)] text-[color:var(--cmp-listBoxItem-content-hover)]",
|
|
17329
|
+
// 비활성 항목은 hover/focus 스타일 대상에서 제외한다.
|
|
17330
|
+
// (:hover 는 disabled 버튼에도 매칭되므로 클래스 자체를 붙이지 않아야 한다)
|
|
17331
|
+
!o.disabled && activeClass
|
|
17332
|
+
),
|
|
17333
|
+
style: { ...LIST_BOX_ITEM_PADDING_Y_RIGHT, paddingLeft: padLeft },
|
|
17334
|
+
onMouseEnter: () => onHoverValue(o.value),
|
|
17335
|
+
onMouseLeave: () => onHoverValue(null),
|
|
17336
|
+
onClick: (event) => onSelect(o.value, idx, event.shiftKey),
|
|
17337
|
+
children: [
|
|
17338
|
+
multi && /* @__PURE__ */ jsxRuntime.jsx(
|
|
17339
|
+
CheckboxBox,
|
|
17340
|
+
{
|
|
17341
|
+
state: isSelected,
|
|
17342
|
+
disabled: o.disabled,
|
|
17343
|
+
focused: focusedValue === o.value
|
|
17344
|
+
}
|
|
17345
|
+
),
|
|
17346
|
+
/* @__PURE__ */ jsxRuntime.jsx(OptionLabel, { label: o.label, className: "flex-1 truncate" })
|
|
17347
|
+
]
|
|
17348
|
+
},
|
|
17349
|
+
rowKey
|
|
17350
|
+
);
|
|
17351
|
+
});
|
|
17352
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { id: listboxId, role: tree ? "tree" : "listbox", "aria-multiselectable": multi || void 0, children: [
|
|
17353
|
+
selectAllRow,
|
|
17354
|
+
topSpacer > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { "aria-hidden": true, style: { height: topSpacer } }),
|
|
17355
|
+
wrapTreeMotion(rowNodes),
|
|
17285
17356
|
bottomSpacer > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { "aria-hidden": true, style: { height: bottomSpacer } })
|
|
17286
17357
|
] });
|
|
17287
17358
|
}
|
|
17359
|
+
function TreeRowsMotion({ opening, children }) {
|
|
17360
|
+
const ref = react.useRef(null);
|
|
17361
|
+
const animationRef = react.useRef(null);
|
|
17362
|
+
const mountedRef = react.useRef(false);
|
|
17363
|
+
react.useLayoutEffect(() => {
|
|
17364
|
+
const el = ref.current;
|
|
17365
|
+
if (el == null || typeof el.animate !== "function") return;
|
|
17366
|
+
const from = !mountedRef.current && opening ? 0 : el.getBoundingClientRect().height;
|
|
17367
|
+
mountedRef.current = true;
|
|
17368
|
+
animationRef.current?.cancel();
|
|
17369
|
+
animationRef.current = el.animate(
|
|
17370
|
+
[{ height: `${from}px` }, { height: opening ? `${el.scrollHeight}px` : "0px" }],
|
|
17371
|
+
// 접힌 뒤 줄이 목록에서 빠질 때까지 0 으로 붙잡아 둔다. 편 뒤에는 제 높이(auto)로 돌아간다
|
|
17372
|
+
{ ...TREE_BRANCH_MOTION, fill: opening ? "none" : "forwards" }
|
|
17373
|
+
);
|
|
17374
|
+
}, [opening]);
|
|
17375
|
+
react.useEffect(() => () => animationRef.current?.cancel(), []);
|
|
17376
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { ref, className: "overflow-hidden", "aria-hidden": opening ? void 0 : true, children });
|
|
17377
|
+
}
|
|
17288
17378
|
function OptionLabel({ label, className }) {
|
|
17289
17379
|
return isHtmlLabel(label) ? /* @__PURE__ */ jsxRuntime.jsx("span", { className, dangerouslySetInnerHTML: { __html: sanitizeInlineHtml(label) } }) : /* @__PURE__ */ jsxRuntime.jsx("span", { className, children: label });
|
|
17290
17380
|
}
|