sellmate-design-system-react 9.0.0-beta.94 → 9.0.0-beta.97
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/AGENTS.md +21 -2
- package/dist/components/SSelect/README.md +1 -0
- package/dist/components/SSelect/SSelect.d.ts +8 -0
- package/dist/components/STable/README.md +15 -1
- package/dist/components/STable/STable.d.ts +45 -1
- package/dist/components/STable/index.d.ts +1 -1
- package/dist/components/STreeTable/STreeTable.d.ts +1 -1
- package/dist/index.cjs +743 -462
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +743 -462
- package/dist/index.js.map +1 -1
- package/dist/lib/row-motion.d.ts +56 -0
- package/dist/lib/tree-row.d.ts +11 -0
- package/dist/llms-full.txt +37 -3
- package/dist/llms.txt +21 -2
- package/dist/styles.css +9 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -7702,6 +7702,13 @@ var TREE_LABEL_TYPOGRAPHY = {
|
|
|
7702
7702
|
rowHeight: "h-[32px]"
|
|
7703
7703
|
}
|
|
7704
7704
|
};
|
|
7705
|
+
var TREE_BRANCH_MOTION = { duration: 200, easing: "cubic-bezier(0, 0, 0.2, 1)" };
|
|
7706
|
+
function canAnimateTreeBranch() {
|
|
7707
|
+
if (typeof window === "undefined" || typeof HTMLElement.prototype.animate !== "function") {
|
|
7708
|
+
return false;
|
|
7709
|
+
}
|
|
7710
|
+
return !window.matchMedia?.("(prefers-reduced-motion: reduce)").matches;
|
|
7711
|
+
}
|
|
7705
7712
|
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)";
|
|
7706
7713
|
var TREE_ROW_BORDER_CLASS = "border-b-(length:--cmp-tree-item-border-width-default) border-(--cmp-tree-item-border-default)";
|
|
7707
7714
|
var TREE_ROW_ACTIVE_CLASS = "bg-(--cmp-tree-item-bg-hover)";
|
|
@@ -15650,6 +15657,93 @@ function SCalendarBoard({
|
|
|
15650
15657
|
);
|
|
15651
15658
|
}
|
|
15652
15659
|
|
|
15660
|
+
// src/lib/row-motion.ts
|
|
15661
|
+
var EXPAND_MOTION = { duration: 220, easing: "ease-out" };
|
|
15662
|
+
var COLLAPSE_MOTION = { duration: 140, easing: "ease-in" };
|
|
15663
|
+
function sameKeys(a, b) {
|
|
15664
|
+
return a.size === b.size && [...a].every((key) => b.has(key));
|
|
15665
|
+
}
|
|
15666
|
+
function motionAllowed() {
|
|
15667
|
+
if (typeof window === "undefined" || typeof Element.prototype.animate !== "function")
|
|
15668
|
+
return false;
|
|
15669
|
+
return !window.matchMedia?.("(prefers-reduced-motion: reduce)").matches;
|
|
15670
|
+
}
|
|
15671
|
+
function startRowMotion({
|
|
15672
|
+
scrollEl,
|
|
15673
|
+
isInBlock,
|
|
15674
|
+
blockKeys,
|
|
15675
|
+
from,
|
|
15676
|
+
to
|
|
15677
|
+
}) {
|
|
15678
|
+
const spec = to === 1 ? EXPAND_MOTION : COLLAPSE_MOTION;
|
|
15679
|
+
const duration = spec.duration * Math.abs(to - from);
|
|
15680
|
+
if (duration < 1) return null;
|
|
15681
|
+
const trs = [
|
|
15682
|
+
...scrollEl.querySelectorAll("tr[data-row-index], tr[data-expansion-row]")
|
|
15683
|
+
].map((tr) => ({ tr, inBlock: isInBlock(tr), height: tr.getBoundingClientRect().height }));
|
|
15684
|
+
const viewBottom = scrollEl.scrollTop + scrollEl.clientHeight;
|
|
15685
|
+
const openAt = (offset) => from + (to - from) * offset;
|
|
15686
|
+
const translate = (shift2, s) => `translateY(${-shift2 * (1 - s)}px)`;
|
|
15687
|
+
const timing = {
|
|
15688
|
+
duration,
|
|
15689
|
+
easing: spec.easing,
|
|
15690
|
+
// 접힘은 남겨 둔 행이 빠질 때까지 끝 위치를 붙잡는다 — 놓으면 그 사이 한 프레임 제자리로 튄다
|
|
15691
|
+
fill: to === 0 ? "forwards" : "none"
|
|
15692
|
+
};
|
|
15693
|
+
const animations = [];
|
|
15694
|
+
let shift = 0;
|
|
15695
|
+
let blockHeight = 0;
|
|
15696
|
+
let y = 0;
|
|
15697
|
+
trs.forEach(({ tr, inBlock, height }, i) => {
|
|
15698
|
+
if (inBlock && !trs[i - 1]?.inBlock) {
|
|
15699
|
+
blockHeight = 0;
|
|
15700
|
+
for (let j = i; j < trs.length && trs[j].inBlock; j++) blockHeight += trs[j].height;
|
|
15701
|
+
shift += blockHeight;
|
|
15702
|
+
y = 0;
|
|
15703
|
+
}
|
|
15704
|
+
if (shift === 0 || tr.offsetTop - shift > viewBottom) return;
|
|
15705
|
+
if (!inBlock) {
|
|
15706
|
+
animations.push(
|
|
15707
|
+
tr.animate(
|
|
15708
|
+
[{ transform: translate(shift, from) }, { transform: translate(shift, to) }],
|
|
15709
|
+
timing
|
|
15710
|
+
)
|
|
15711
|
+
);
|
|
15712
|
+
return;
|
|
15713
|
+
}
|
|
15714
|
+
const rowY = y;
|
|
15715
|
+
const clipAt = (s) => Math.min(height, Math.max(0, blockHeight * (1 - s) - rowY));
|
|
15716
|
+
const bends = [1 - rowY / blockHeight, 1 - (rowY + height) / blockHeight].map((s) => (s - from) / (to - from)).filter((offset) => offset > 0 && offset < 1);
|
|
15717
|
+
const offsets = [0, ...bends, 1].sort((a, b) => a - b);
|
|
15718
|
+
animations.push(
|
|
15719
|
+
tr.animate(
|
|
15720
|
+
offsets.map((offset) => ({
|
|
15721
|
+
offset,
|
|
15722
|
+
transform: translate(shift, openAt(offset)),
|
|
15723
|
+
clipPath: `inset(${clipAt(openAt(offset))}px 0 0 0)`
|
|
15724
|
+
})),
|
|
15725
|
+
timing
|
|
15726
|
+
)
|
|
15727
|
+
);
|
|
15728
|
+
y += height;
|
|
15729
|
+
});
|
|
15730
|
+
if (animations.length === 0) return null;
|
|
15731
|
+
return {
|
|
15732
|
+
keys: blockKeys,
|
|
15733
|
+
to,
|
|
15734
|
+
done: false,
|
|
15735
|
+
finished: Promise.all(animations.map((animation) => animation.finished)).then(
|
|
15736
|
+
() => true,
|
|
15737
|
+
() => false
|
|
15738
|
+
),
|
|
15739
|
+
openness: () => {
|
|
15740
|
+
const progress = animations[0].effect?.getComputedTiming().progress;
|
|
15741
|
+
return progress == null ? to : openAt(progress);
|
|
15742
|
+
},
|
|
15743
|
+
cancel: () => animations.forEach((animation) => animation.cancel())
|
|
15744
|
+
};
|
|
15745
|
+
}
|
|
15746
|
+
|
|
15653
15747
|
// src/lib/row-metrics.ts
|
|
15654
15748
|
function createRowMetrics(count, estimate) {
|
|
15655
15749
|
const safeCount = Math.max(0, Math.floor(count));
|
|
@@ -16069,6 +16163,7 @@ var SSelectImpl = /* @__PURE__ */ forwardRef(function SSelect({
|
|
|
16069
16163
|
onSearchChange,
|
|
16070
16164
|
searchDebounce = DEFAULT_SEARCH_DEBOUNCE,
|
|
16071
16165
|
label,
|
|
16166
|
+
ariaLabel,
|
|
16072
16167
|
labelPosition,
|
|
16073
16168
|
subLabel,
|
|
16074
16169
|
labelWidth,
|
|
@@ -16098,6 +16193,21 @@ var SSelectImpl = /* @__PURE__ */ forwardRef(function SSelect({
|
|
|
16098
16193
|
const [focusedIndex, setFocusedIndex] = useState(-1);
|
|
16099
16194
|
const [expanded, setExpanded] = useState(() => /* @__PURE__ */ new Set());
|
|
16100
16195
|
const [searchCollapsed, setSearchCollapsed] = useState(NO_SEARCH_COLLAPSED);
|
|
16196
|
+
const [treeMotion, setTreeMotion] = useState(null);
|
|
16197
|
+
const treeMotionTimerRef = useRef(null);
|
|
16198
|
+
const pendingCollapseRef = useRef(null);
|
|
16199
|
+
const cancelTreeMotion = () => {
|
|
16200
|
+
if (treeMotionTimerRef.current != null) clearTimeout(treeMotionTimerRef.current);
|
|
16201
|
+
treeMotionTimerRef.current = null;
|
|
16202
|
+
pendingCollapseRef.current = null;
|
|
16203
|
+
setTreeMotion(null);
|
|
16204
|
+
};
|
|
16205
|
+
useEffect(
|
|
16206
|
+
() => () => {
|
|
16207
|
+
if (treeMotionTimerRef.current != null) clearTimeout(treeMotionTimerRef.current);
|
|
16208
|
+
},
|
|
16209
|
+
[]
|
|
16210
|
+
);
|
|
16101
16211
|
const baseId = useId();
|
|
16102
16212
|
const listboxId = `${baseId}-listbox`;
|
|
16103
16213
|
const activeOptionId = `${baseId}-active-option`;
|
|
@@ -16165,6 +16275,7 @@ var SSelectImpl = /* @__PURE__ */ forwardRef(function SSelect({
|
|
|
16165
16275
|
const commitOpen = (next, valueForRules) => {
|
|
16166
16276
|
setOpen(next);
|
|
16167
16277
|
onOpenChange?.(next);
|
|
16278
|
+
cancelTreeMotion();
|
|
16168
16279
|
if (next) {
|
|
16169
16280
|
dismissedByPointerRef.current = false;
|
|
16170
16281
|
alignSelectedTopRef.current = true;
|
|
@@ -16235,6 +16346,10 @@ var SSelectImpl = /* @__PURE__ */ forwardRef(function SSelect({
|
|
|
16235
16346
|
(groupValue) => treeSearching ? !searchCollapsed.has(groupValue) : expanded.has(groupValue),
|
|
16236
16347
|
[expanded, searchCollapsed, treeSearching]
|
|
16237
16348
|
);
|
|
16349
|
+
const isGroupShownExpanded = useCallback(
|
|
16350
|
+
(groupValue) => isGroupExpanded(groupValue) && !(treeMotion != null && !treeMotion.opening && treeMotion.group === groupValue),
|
|
16351
|
+
[isGroupExpanded, treeMotion]
|
|
16352
|
+
);
|
|
16238
16353
|
const treeRows = useMemo(
|
|
16239
16354
|
() => isTree ? visibleTreeRows(searched, (row) => isGroupExpanded(row.value)) : null,
|
|
16240
16355
|
[isGroupExpanded, isTree, searched]
|
|
@@ -16524,6 +16639,7 @@ var SSelectImpl = /* @__PURE__ */ forwardRef(function SSelect({
|
|
|
16524
16639
|
const handleSearchChange = (next) => {
|
|
16525
16640
|
setSearch(next);
|
|
16526
16641
|
setSearchCollapsed(NO_SEARCH_COLLAPSED);
|
|
16642
|
+
cancelTreeMotion();
|
|
16527
16643
|
};
|
|
16528
16644
|
const handleToggleExpand = (groupValue) => {
|
|
16529
16645
|
rangeAnchorRef.current = null;
|
|
@@ -16533,8 +16649,41 @@ var SSelectImpl = /* @__PURE__ */ forwardRef(function SSelect({
|
|
|
16533
16649
|
else next.add(groupValue);
|
|
16534
16650
|
return next;
|
|
16535
16651
|
};
|
|
16536
|
-
|
|
16537
|
-
|
|
16652
|
+
const applyToggle = treeSearching ? () => setSearchCollapsed(toggle) : () => setExpanded(toggle);
|
|
16653
|
+
if (treeMotionTimerRef.current != null) clearTimeout(treeMotionTimerRef.current);
|
|
16654
|
+
treeMotionTimerRef.current = null;
|
|
16655
|
+
const pendingCollapse = pendingCollapseRef.current;
|
|
16656
|
+
pendingCollapseRef.current = null;
|
|
16657
|
+
if (treeMotion?.group === groupValue && !treeMotion.opening) {
|
|
16658
|
+
setTreeMotion({ group: groupValue, opening: true });
|
|
16659
|
+
treeMotionTimerRef.current = setTimeout(
|
|
16660
|
+
() => setTreeMotion(null),
|
|
16661
|
+
TREE_BRANCH_MOTION.duration
|
|
16662
|
+
);
|
|
16663
|
+
return;
|
|
16664
|
+
}
|
|
16665
|
+
pendingCollapse?.();
|
|
16666
|
+
if (!canAnimateTreeBranch()) {
|
|
16667
|
+
setTreeMotion(null);
|
|
16668
|
+
applyToggle();
|
|
16669
|
+
return;
|
|
16670
|
+
}
|
|
16671
|
+
if (isGroupExpanded(groupValue)) {
|
|
16672
|
+
setTreeMotion({ group: groupValue, opening: false });
|
|
16673
|
+
pendingCollapseRef.current = applyToggle;
|
|
16674
|
+
treeMotionTimerRef.current = setTimeout(() => {
|
|
16675
|
+
pendingCollapseRef.current = null;
|
|
16676
|
+
setTreeMotion(null);
|
|
16677
|
+
applyToggle();
|
|
16678
|
+
}, TREE_BRANCH_MOTION.duration);
|
|
16679
|
+
} else {
|
|
16680
|
+
applyToggle();
|
|
16681
|
+
setTreeMotion({ group: groupValue, opening: true });
|
|
16682
|
+
treeMotionTimerRef.current = setTimeout(
|
|
16683
|
+
() => setTreeMotion(null),
|
|
16684
|
+
TREE_BRANCH_MOTION.duration
|
|
16685
|
+
);
|
|
16686
|
+
}
|
|
16538
16687
|
};
|
|
16539
16688
|
const handleToggleGroup = (leafValues) => {
|
|
16540
16689
|
if (leafValues.length === 0) return;
|
|
@@ -16644,7 +16793,7 @@ var SSelectImpl = /* @__PURE__ */ forwardRef(function SSelect({
|
|
|
16644
16793
|
const row = rowIndex != null ? filtered[rowIndex] : void 0;
|
|
16645
16794
|
if (rowIndex == null || row == null) return;
|
|
16646
16795
|
event.preventDefault();
|
|
16647
|
-
const rowExpanded = row.isGroup &&
|
|
16796
|
+
const rowExpanded = row.isGroup && isGroupShownExpanded(row.value);
|
|
16648
16797
|
if (event.key === "ArrowRight") {
|
|
16649
16798
|
if (!row.isGroup) return;
|
|
16650
16799
|
if (!rowExpanded) {
|
|
@@ -16717,6 +16866,7 @@ var SSelectImpl = /* @__PURE__ */ forwardRef(function SSelect({
|
|
|
16717
16866
|
style,
|
|
16718
16867
|
children: [
|
|
16719
16868
|
name && /* @__PURE__ */ jsx("input", { type: "hidden", name, value: selected.join(",") }),
|
|
16869
|
+
!label && ariaLabel && /* @__PURE__ */ jsx("span", { id: `${triggerId}-aria-label`, hidden: true, children: ariaLabel }),
|
|
16720
16870
|
/* @__PURE__ */ jsxs(
|
|
16721
16871
|
"div",
|
|
16722
16872
|
{
|
|
@@ -16725,7 +16875,7 @@ var SSelectImpl = /* @__PURE__ */ forwardRef(function SSelect({
|
|
|
16725
16875
|
role: "combobox",
|
|
16726
16876
|
tabIndex: effectiveDisabled ? -1 : 0,
|
|
16727
16877
|
"aria-disabled": effectiveDisabled || void 0,
|
|
16728
|
-
"aria-labelledby": label ? `${fieldLabelId(triggerId)} ${triggerId}` : triggerId,
|
|
16878
|
+
"aria-labelledby": label ? `${fieldLabelId(triggerId)} ${triggerId}` : ariaLabel ? `${triggerId}-aria-label ${triggerId}` : triggerId,
|
|
16729
16879
|
"aria-haspopup": isTree ? "tree" : "listbox",
|
|
16730
16880
|
"aria-expanded": open,
|
|
16731
16881
|
"aria-controls": open ? listboxId : void 0,
|
|
@@ -16889,8 +17039,9 @@ var SSelectImpl = /* @__PURE__ */ forwardRef(function SSelect({
|
|
|
16889
17039
|
setSize: isDepth || isTree ? void 0 : rowCount,
|
|
16890
17040
|
multi,
|
|
16891
17041
|
tree: isTree,
|
|
16892
|
-
isExpanded:
|
|
17042
|
+
isExpanded: isGroupShownExpanded,
|
|
16893
17043
|
onToggleExpand: handleToggleExpand,
|
|
17044
|
+
treeMotion,
|
|
16894
17045
|
treeLines,
|
|
16895
17046
|
selected,
|
|
16896
17047
|
groupLeafValues,
|
|
@@ -16947,6 +17098,7 @@ function ItemList({
|
|
|
16947
17098
|
tree,
|
|
16948
17099
|
isExpanded,
|
|
16949
17100
|
onToggleExpand,
|
|
17101
|
+
treeMotion,
|
|
16950
17102
|
treeLines,
|
|
16951
17103
|
selected,
|
|
16952
17104
|
groupLeafValues,
|
|
@@ -16967,295 +17119,322 @@ function ItemList({
|
|
|
16967
17119
|
"disabled:cursor-not-allowed disabled:text-[color:var(--cmp-listBoxItem-content-disabled)]"
|
|
16968
17120
|
);
|
|
16969
17121
|
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)]";
|
|
16970
|
-
|
|
16971
|
-
|
|
16972
|
-
|
|
16973
|
-
|
|
16974
|
-
|
|
16975
|
-
|
|
16976
|
-
|
|
16977
|
-
|
|
16978
|
-
|
|
16979
|
-
|
|
16980
|
-
|
|
16981
|
-
|
|
16982
|
-
|
|
16983
|
-
|
|
16984
|
-
|
|
16985
|
-
|
|
16986
|
-
|
|
16987
|
-
onToggle: onSelectAll
|
|
16988
|
-
}
|
|
16989
|
-
)
|
|
16990
|
-
) : /* @__PURE__ */ jsxs(
|
|
16991
|
-
"button",
|
|
17122
|
+
const wrapTreeMotion = (nodes) => {
|
|
17123
|
+
if (!tree || treeMotion == null) return nodes;
|
|
17124
|
+
const start = items.findIndex((o) => o.isGroup && o.value === treeMotion.group);
|
|
17125
|
+
if (start < 0) return nodes;
|
|
17126
|
+
let end = start + 1;
|
|
17127
|
+
while (end < items.length && items[end].depth > items[start].depth) end++;
|
|
17128
|
+
if (end === start + 1) return nodes;
|
|
17129
|
+
return [
|
|
17130
|
+
...nodes.slice(0, start + 1),
|
|
17131
|
+
/* @__PURE__ */ jsx(TreeRowsMotion, { opening: treeMotion.opening, children: nodes.slice(start + 1, end) }, `__motion_${treeMotion.group}__`),
|
|
17132
|
+
...nodes.slice(end)
|
|
17133
|
+
];
|
|
17134
|
+
};
|
|
17135
|
+
const selectAllRow = showSelectAll && (tree ? (
|
|
17136
|
+
// 트리의 전체선택 줄은 STree 의 "전체" 줄 그대로다 (lib/tree-row).
|
|
17137
|
+
/* @__PURE__ */ jsx(
|
|
17138
|
+
TreeAllRow,
|
|
16992
17139
|
{
|
|
16993
|
-
type: "button",
|
|
16994
17140
|
"data-select-all-row": true,
|
|
16995
17141
|
id: focusedValue === "__select_all__" ? activeOptionId : void 0,
|
|
16996
17142
|
"data-select-focused": focusedValue === "__select_all__" ? "true" : void 0,
|
|
16997
17143
|
"data-select-active": focusedValue === "__select_all__" ? "true" : void 0,
|
|
16998
|
-
|
|
16999
|
-
|
|
17000
|
-
|
|
17001
|
-
|
|
17002
|
-
|
|
17144
|
+
tabIndex: -1,
|
|
17145
|
+
label: selectAllLabel,
|
|
17146
|
+
selected: selectAllSelected,
|
|
17147
|
+
active: focusedValue === "__select_all__",
|
|
17148
|
+
className: "select-none",
|
|
17003
17149
|
onMouseEnter: () => onHoverValue("__select_all__"),
|
|
17004
17150
|
onMouseLeave: () => onHoverValue(null),
|
|
17005
|
-
|
|
17006
|
-
children: [
|
|
17007
|
-
/* @__PURE__ */ jsx(CheckboxBox, { state: selectAllSelected, focused: focusedValue === "__select_all__" }),
|
|
17008
|
-
/* @__PURE__ */ jsx("span", { className: "flex-1 truncate", children: selectAllLabel })
|
|
17009
|
-
]
|
|
17151
|
+
onToggle: onSelectAll
|
|
17010
17152
|
}
|
|
17011
|
-
)
|
|
17012
|
-
|
|
17013
|
-
|
|
17014
|
-
|
|
17015
|
-
|
|
17016
|
-
|
|
17017
|
-
|
|
17018
|
-
|
|
17019
|
-
|
|
17020
|
-
|
|
17021
|
-
|
|
17022
|
-
|
|
17023
|
-
|
|
17024
|
-
|
|
17025
|
-
|
|
17026
|
-
|
|
17027
|
-
|
|
17028
|
-
|
|
17029
|
-
|
|
17030
|
-
|
|
17031
|
-
|
|
17032
|
-
|
|
17033
|
-
|
|
17034
|
-
|
|
17035
|
-
|
|
17036
|
-
|
|
17037
|
-
|
|
17038
|
-
|
|
17039
|
-
|
|
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
|
-
selectable: true,
|
|
17093
|
-
disabled: rowDisabled,
|
|
17094
|
-
control: multi && /* @__PURE__ */ jsx(
|
|
17095
|
-
"span",
|
|
17096
|
-
{
|
|
17097
|
-
"data-tree-control": true,
|
|
17098
|
-
className: "flex shrink-0",
|
|
17099
|
-
onPointerDownCapture: (event) => {
|
|
17100
|
-
checkboxShiftRef.current = event.shiftKey;
|
|
17101
|
-
},
|
|
17102
|
-
children: /* @__PURE__ */ jsx(
|
|
17103
|
-
SCheckbox,
|
|
17104
|
-
{
|
|
17105
|
-
value: checkState,
|
|
17106
|
-
disabled: rowDisabled,
|
|
17107
|
-
onValueChange: toggle
|
|
17108
|
-
}
|
|
17109
|
-
)
|
|
17110
|
-
}
|
|
17111
|
-
),
|
|
17112
|
-
children: /* @__PURE__ */ jsx(
|
|
17113
|
-
OptionLabel,
|
|
17114
|
-
{
|
|
17115
|
-
label: o.label,
|
|
17116
|
-
className: treeLabelClass("sm", {
|
|
17117
|
-
// 고른 줄은 굵게 — multi_tree 는 일부만 고른 그룹도 굵다 (STree 와 같다)
|
|
17118
|
-
emphasized: checkState !== false,
|
|
17119
|
-
disabled: rowDisabled,
|
|
17120
|
-
selectable: true
|
|
17121
|
-
})
|
|
17122
|
-
}
|
|
17123
|
-
)
|
|
17124
|
-
}
|
|
17125
|
-
)
|
|
17126
|
-
] })
|
|
17127
|
-
]
|
|
17153
|
+
)
|
|
17154
|
+
) : /* @__PURE__ */ jsxs(
|
|
17155
|
+
"button",
|
|
17156
|
+
{
|
|
17157
|
+
type: "button",
|
|
17158
|
+
"data-select-all-row": true,
|
|
17159
|
+
id: focusedValue === "__select_all__" ? activeOptionId : void 0,
|
|
17160
|
+
"data-select-focused": focusedValue === "__select_all__" ? "true" : void 0,
|
|
17161
|
+
"data-select-active": focusedValue === "__select_all__" ? "true" : void 0,
|
|
17162
|
+
className: cn(
|
|
17163
|
+
itemBase,
|
|
17164
|
+
focusedValue === "__select_all__" && "bg-[var(--cmp-listBoxItem-bg-hover)] text-[color:var(--cmp-listBoxItem-content-hover)]"
|
|
17165
|
+
),
|
|
17166
|
+
style: { ...LIST_BOX_ITEM_PADDING_Y_RIGHT, paddingLeft: LIST_BOX_ITEM_PADDING_LEFT[1] },
|
|
17167
|
+
onMouseEnter: () => onHoverValue("__select_all__"),
|
|
17168
|
+
onMouseLeave: () => onHoverValue(null),
|
|
17169
|
+
onClick: onSelectAll,
|
|
17170
|
+
children: [
|
|
17171
|
+
/* @__PURE__ */ jsx(CheckboxBox, { state: selectAllSelected, focused: focusedValue === "__select_all__" }),
|
|
17172
|
+
/* @__PURE__ */ jsx("span", { className: "flex-1 truncate", children: selectAllLabel })
|
|
17173
|
+
]
|
|
17174
|
+
}
|
|
17175
|
+
));
|
|
17176
|
+
const rowNodes = items.map((o, windowIndex) => {
|
|
17177
|
+
const idx = windowIndex + indexOffset;
|
|
17178
|
+
const padLeft = LIST_BOX_ITEM_PADDING_LEFT[o.depth === 1 ? 1 : o.depth === 2 ? 2 : 3];
|
|
17179
|
+
const rowKey = `${o.value}-${idx}`;
|
|
17180
|
+
if (tree) {
|
|
17181
|
+
const togglesLeaves = multi && o.isGroup;
|
|
17182
|
+
const leafValues = togglesLeaves ? groupLeafValues.get(idx) ?? [] : [];
|
|
17183
|
+
const total = leafValues.length;
|
|
17184
|
+
const selectedCount = leafValues.reduce((n, v) => selectedSet.has(v) ? n + 1 : n, 0);
|
|
17185
|
+
const isSelected2 = selectedSet.has(o.value);
|
|
17186
|
+
const checkState = togglesLeaves ? total === 0 || selectedCount === 0 ? false : selectedCount === total ? true : null : isSelected2;
|
|
17187
|
+
const rowDisabled = o.disabled || togglesLeaves && total === 0;
|
|
17188
|
+
const focusKey = togglesLeaves ? `__group_${idx}__` : o.value;
|
|
17189
|
+
const focused = focusedValue === focusKey;
|
|
17190
|
+
const expanded = o.isGroup && isExpanded(o.value);
|
|
17191
|
+
const line = treeLines[idx];
|
|
17192
|
+
const toggle = () => {
|
|
17193
|
+
if (rowDisabled) return;
|
|
17194
|
+
if (togglesLeaves) onToggleGroup(leafValues);
|
|
17195
|
+
else onSelect(o.value, idx, checkboxShiftRef.current);
|
|
17196
|
+
};
|
|
17197
|
+
return (
|
|
17198
|
+
// eslint-disable-next-line jsx-a11y/click-events-have-key-events -- 키보드는 트리거(또는 검색 입력)가 aria-activedescendant 로 맡는다. 줄로 포커스를 옮기지 않는다
|
|
17199
|
+
/* @__PURE__ */ jsxs(
|
|
17200
|
+
"div",
|
|
17201
|
+
{
|
|
17202
|
+
tabIndex: -1,
|
|
17203
|
+
"data-select-row": true,
|
|
17204
|
+
"data-index": idx,
|
|
17205
|
+
id: focused ? activeOptionId : void 0,
|
|
17206
|
+
role: "treeitem",
|
|
17207
|
+
"aria-level": o.depth,
|
|
17208
|
+
"aria-posinset": line?.posInSet,
|
|
17209
|
+
"aria-setsize": line?.setSize,
|
|
17210
|
+
"aria-expanded": o.isGroup ? expanded : void 0,
|
|
17211
|
+
"aria-selected": checkState === true,
|
|
17212
|
+
"aria-disabled": rowDisabled || void 0,
|
|
17213
|
+
"data-select-focused": focused ? "true" : void 0,
|
|
17214
|
+
"data-select-active": focused ? "true" : void 0,
|
|
17215
|
+
className: cn(
|
|
17216
|
+
TREE_ROW_CLASS,
|
|
17217
|
+
TREE_LABEL_TYPOGRAPHY.sm.rowHeight,
|
|
17218
|
+
TREE_ROW_BORDER_CLASS,
|
|
17219
|
+
"select-none",
|
|
17220
|
+
o.isGroup && "cursor-pointer",
|
|
17221
|
+
focused && TREE_ROW_ACTIVE_CLASS
|
|
17222
|
+
),
|
|
17223
|
+
onMouseEnter: () => onHoverValue(focusKey),
|
|
17224
|
+
onMouseLeave: () => onHoverValue(null),
|
|
17225
|
+
onClick: (event) => {
|
|
17226
|
+
if (isRowControlClick(event)) return;
|
|
17227
|
+
if (isRowSelectClick(event)) {
|
|
17228
|
+
if (rowDisabled) return;
|
|
17229
|
+
if (togglesLeaves) onToggleGroup(leafValues);
|
|
17230
|
+
else onSelect(o.value, idx, event.shiftKey);
|
|
17231
|
+
return;
|
|
17232
|
+
}
|
|
17233
|
+
if (o.isGroup) onToggleExpand(o.value);
|
|
17128
17234
|
},
|
|
17129
|
-
|
|
17130
|
-
|
|
17131
|
-
|
|
17132
|
-
|
|
17133
|
-
|
|
17134
|
-
|
|
17135
|
-
|
|
17136
|
-
|
|
17137
|
-
|
|
17138
|
-
|
|
17139
|
-
|
|
17140
|
-
if (multi) {
|
|
17141
|
-
const leafValues = groupLeafValues.get(idx) ?? [];
|
|
17142
|
-
const total = leafValues.length;
|
|
17143
|
-
const selectedCount = leafValues.reduce((n, v) => selectedSet.has(v) ? n + 1 : n, 0);
|
|
17144
|
-
const groupState = total === 0 || selectedCount === 0 ? false : selectedCount === total ? true : null;
|
|
17145
|
-
const groupDisabled = o.disabled || total === 0;
|
|
17146
|
-
const groupFocused = focusedValue === `__group_${idx}__`;
|
|
17147
|
-
return /* @__PURE__ */ jsxs(
|
|
17148
|
-
"button",
|
|
17149
|
-
{
|
|
17150
|
-
type: "button",
|
|
17151
|
-
"data-select-row": true,
|
|
17152
|
-
"data-index": idx,
|
|
17153
|
-
id: groupFocused ? activeOptionId : void 0,
|
|
17154
|
-
role: "option",
|
|
17155
|
-
"aria-selected": groupState === true,
|
|
17156
|
-
disabled: groupDisabled,
|
|
17157
|
-
"data-select-focused": groupFocused ? "true" : void 0,
|
|
17158
|
-
"data-select-active": groupFocused ? "true" : void 0,
|
|
17159
|
-
className: cn(
|
|
17160
|
-
"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)]",
|
|
17161
|
-
groupTone,
|
|
17162
|
-
groupDisabled ? "cursor-not-allowed text-[color:var(--cmp-listBoxItem-content-disabled)]" : "cursor-pointer",
|
|
17163
|
-
groupDisabled && isDepth1 && "bg-[var(--cmp-listBoxItem-depth1-bg-disabled)]",
|
|
17164
|
-
// 포커스 하이라이트는 depth 배경보다 뒤에 둬 tailwind-merge 로 이기게 한다.
|
|
17165
|
-
groupFocused && !groupDisabled && "bg-[var(--cmp-listBoxItem-bg-hover)] text-[color:var(--cmp-listBoxItem-content-hover)]"
|
|
17235
|
+
children: [
|
|
17236
|
+
/* @__PURE__ */ jsx(
|
|
17237
|
+
TreeConnector,
|
|
17238
|
+
{
|
|
17239
|
+
guideline: true,
|
|
17240
|
+
size: "sm",
|
|
17241
|
+
depth: o.depth,
|
|
17242
|
+
hasChildren: o.isGroup,
|
|
17243
|
+
isLast: line?.isLast ?? true,
|
|
17244
|
+
ancestorLast: line?.ancestorLast ?? []
|
|
17245
|
+
}
|
|
17166
17246
|
),
|
|
17167
|
-
|
|
17168
|
-
|
|
17169
|
-
|
|
17170
|
-
|
|
17171
|
-
|
|
17172
|
-
/* @__PURE__ */ jsx(
|
|
17173
|
-
|
|
17174
|
-
total > 0 && /* @__PURE__ */ jsxs(
|
|
17175
|
-
"span",
|
|
17247
|
+
/* @__PURE__ */ jsxs("div", { className: TREE_CONTENT_CLASS, children: [
|
|
17248
|
+
o.isGroup ? (
|
|
17249
|
+
// 비활성 그룹도 접고 편다 — 그룹이 비활성이어도 아래에 고를 수 있는 자식이 있을 수 있다.
|
|
17250
|
+
/* @__PURE__ */ jsx(TreeCaret, { expanded, onToggle: () => onToggleExpand(o.value) })
|
|
17251
|
+
) : /* @__PURE__ */ jsx(TreeCaretSpacer, {}),
|
|
17252
|
+
/* @__PURE__ */ jsx(
|
|
17253
|
+
TreeSelectZone,
|
|
17176
17254
|
{
|
|
17177
|
-
|
|
17178
|
-
|
|
17179
|
-
|
|
17255
|
+
selectable: true,
|
|
17256
|
+
disabled: rowDisabled,
|
|
17257
|
+
control: multi && /* @__PURE__ */ jsx(
|
|
17258
|
+
"span",
|
|
17259
|
+
{
|
|
17260
|
+
"data-tree-control": true,
|
|
17261
|
+
className: "flex shrink-0",
|
|
17262
|
+
onPointerDownCapture: (event) => {
|
|
17263
|
+
checkboxShiftRef.current = event.shiftKey;
|
|
17264
|
+
},
|
|
17265
|
+
children: /* @__PURE__ */ jsx(SCheckbox, { value: checkState, disabled: rowDisabled, onValueChange: toggle })
|
|
17266
|
+
}
|
|
17180
17267
|
),
|
|
17181
|
-
children:
|
|
17182
|
-
|
|
17183
|
-
|
|
17184
|
-
|
|
17185
|
-
|
|
17186
|
-
|
|
17187
|
-
|
|
17268
|
+
children: /* @__PURE__ */ jsx(
|
|
17269
|
+
OptionLabel,
|
|
17270
|
+
{
|
|
17271
|
+
label: o.label,
|
|
17272
|
+
className: treeLabelClass("sm", {
|
|
17273
|
+
// 고른 줄은 굵게 — multi_tree 는 일부만 고른 그룹도 굵다 (STree 와 같다)
|
|
17274
|
+
emphasized: checkState !== false,
|
|
17275
|
+
disabled: rowDisabled,
|
|
17276
|
+
selectable: true
|
|
17277
|
+
})
|
|
17278
|
+
}
|
|
17279
|
+
)
|
|
17188
17280
|
}
|
|
17189
17281
|
)
|
|
17190
|
-
]
|
|
17191
|
-
|
|
17192
|
-
|
|
17193
|
-
|
|
17194
|
-
|
|
17195
|
-
|
|
17196
|
-
|
|
17282
|
+
] })
|
|
17283
|
+
]
|
|
17284
|
+
},
|
|
17285
|
+
rowKey
|
|
17286
|
+
)
|
|
17287
|
+
);
|
|
17288
|
+
}
|
|
17289
|
+
if (o.isGroup) {
|
|
17290
|
+
const isDepth1 = o.depth === 1;
|
|
17291
|
+
const isFirstRow = idx === 0 && !showSelectAll;
|
|
17292
|
+
const groupTone = cn(
|
|
17293
|
+
isDepth1 ? "bg-[var(--cmp-listBoxItem-depth1-bg-default)] font-bold" : "bg-[var(--cmp-listBoxItem-depth2-middle-bg)] font-medium",
|
|
17294
|
+
isDepth1 && !isFirstRow && "border-t border-solid border-[color:var(--cmp-listBoxItem-depth1-border-color)]"
|
|
17295
|
+
);
|
|
17296
|
+
if (multi) {
|
|
17297
|
+
const leafValues = groupLeafValues.get(idx) ?? [];
|
|
17298
|
+
const total = leafValues.length;
|
|
17299
|
+
const selectedCount = leafValues.reduce((n, v) => selectedSet.has(v) ? n + 1 : n, 0);
|
|
17300
|
+
const groupState = total === 0 || selectedCount === 0 ? false : selectedCount === total ? true : null;
|
|
17301
|
+
const groupDisabled = o.disabled || total === 0;
|
|
17302
|
+
const groupFocused = focusedValue === `__group_${idx}__`;
|
|
17303
|
+
return /* @__PURE__ */ jsxs(
|
|
17304
|
+
"button",
|
|
17197
17305
|
{
|
|
17306
|
+
type: "button",
|
|
17198
17307
|
"data-select-row": true,
|
|
17199
17308
|
"data-index": idx,
|
|
17309
|
+
id: groupFocused ? activeOptionId : void 0,
|
|
17310
|
+
role: "option",
|
|
17311
|
+
"aria-selected": groupState === true,
|
|
17312
|
+
disabled: groupDisabled,
|
|
17313
|
+
"data-select-focused": groupFocused ? "true" : void 0,
|
|
17314
|
+
"data-select-active": groupFocused ? "true" : void 0,
|
|
17200
17315
|
className: cn(
|
|
17201
|
-
"select-none py-(--cmp-listBoxItem-paddingY) pr-(--cmp-listBoxItem-paddingRight) typo-field-sm-default text-[color:var(--cmp-listBoxItem-content-default)]",
|
|
17316
|
+
"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)]",
|
|
17202
17317
|
groupTone,
|
|
17203
|
-
|
|
17204
|
-
|
|
17205
|
-
|
|
17206
|
-
|
|
17318
|
+
groupDisabled ? "cursor-not-allowed text-[color:var(--cmp-listBoxItem-content-disabled)]" : "cursor-pointer",
|
|
17319
|
+
groupDisabled && isDepth1 && "bg-[var(--cmp-listBoxItem-depth1-bg-disabled)]",
|
|
17320
|
+
// 포커스 하이라이트는 depth 배경보다 뒤에 둬 tailwind-merge 로 이기게 한다.
|
|
17321
|
+
groupFocused && !groupDisabled && "bg-[var(--cmp-listBoxItem-bg-hover)] text-[color:var(--cmp-listBoxItem-content-hover)]"
|
|
17207
17322
|
),
|
|
17208
17323
|
style: { paddingLeft: padLeft },
|
|
17209
|
-
|
|
17324
|
+
onMouseEnter: () => onHoverValue(`__group_${idx}__`),
|
|
17325
|
+
onMouseLeave: () => onHoverValue(null),
|
|
17326
|
+
onClick: () => onToggleGroup(leafValues),
|
|
17327
|
+
children: [
|
|
17328
|
+
/* @__PURE__ */ jsx(CheckboxBox, { state: groupState, disabled: groupDisabled, focused: groupFocused }),
|
|
17329
|
+
/* @__PURE__ */ jsx(OptionLabel, { label: o.label, className: "min-w-0 truncate" }),
|
|
17330
|
+
total > 0 && /* @__PURE__ */ jsxs(
|
|
17331
|
+
"span",
|
|
17332
|
+
{
|
|
17333
|
+
className: cn(
|
|
17334
|
+
"shrink-0 font-medium",
|
|
17335
|
+
groupFocused ? "text-white" : "text-[color:var(--cmp-listBoxItem-content-disabled)]"
|
|
17336
|
+
),
|
|
17337
|
+
children: [
|
|
17338
|
+
"(",
|
|
17339
|
+
selectedCount,
|
|
17340
|
+
"/",
|
|
17341
|
+
total,
|
|
17342
|
+
")"
|
|
17343
|
+
]
|
|
17344
|
+
}
|
|
17345
|
+
)
|
|
17346
|
+
]
|
|
17210
17347
|
},
|
|
17211
17348
|
rowKey
|
|
17212
17349
|
);
|
|
17213
17350
|
}
|
|
17214
|
-
|
|
17215
|
-
|
|
17216
|
-
"button",
|
|
17351
|
+
return /* @__PURE__ */ jsx(
|
|
17352
|
+
"div",
|
|
17217
17353
|
{
|
|
17218
|
-
type: "button",
|
|
17219
17354
|
"data-select-row": true,
|
|
17220
17355
|
"data-index": idx,
|
|
17221
|
-
id: focusedValue === o.value ? activeOptionId : void 0,
|
|
17222
|
-
disabled: o.disabled,
|
|
17223
|
-
role: "option",
|
|
17224
|
-
"aria-selected": isSelected,
|
|
17225
|
-
"aria-setsize": setSize,
|
|
17226
|
-
"aria-posinset": setSize != null ? idx + 1 : void 0,
|
|
17227
|
-
"data-select-focused": focusedValue === o.value ? "true" : void 0,
|
|
17228
|
-
"data-select-active": focusedValue === o.value ? "true" : void 0,
|
|
17229
17356
|
className: cn(
|
|
17230
|
-
|
|
17231
|
-
|
|
17232
|
-
|
|
17233
|
-
//
|
|
17234
|
-
|
|
17235
|
-
|
|
17357
|
+
"select-none py-(--cmp-listBoxItem-paddingY) pr-(--cmp-listBoxItem-paddingRight) typo-field-sm-default text-[color:var(--cmp-listBoxItem-content-default)]",
|
|
17358
|
+
groupTone,
|
|
17359
|
+
// 하위가 전부 비활성이면(select.config 에서 bubble-up) 어미 그룹도 비활성 처리:
|
|
17360
|
+
// 텍스트는 회색(#888), depth1 그룹은 파란 배경 대신 회색(#EEE)으로 (depth2 는 #F6F6F6 유지)
|
|
17361
|
+
o.disabled && "cursor-not-allowed text-[color:var(--cmp-listBoxItem-content-disabled)]",
|
|
17362
|
+
o.disabled && isDepth1 && "bg-[var(--cmp-listBoxItem-depth1-bg-disabled)]"
|
|
17236
17363
|
),
|
|
17237
|
-
style: {
|
|
17238
|
-
|
|
17239
|
-
onMouseLeave: () => onHoverValue(null),
|
|
17240
|
-
onClick: (event) => onSelect(o.value, idx, event.shiftKey),
|
|
17241
|
-
children: [
|
|
17242
|
-
multi && /* @__PURE__ */ jsx(
|
|
17243
|
-
CheckboxBox,
|
|
17244
|
-
{
|
|
17245
|
-
state: isSelected,
|
|
17246
|
-
disabled: o.disabled,
|
|
17247
|
-
focused: focusedValue === o.value
|
|
17248
|
-
}
|
|
17249
|
-
),
|
|
17250
|
-
/* @__PURE__ */ jsx(OptionLabel, { label: o.label, className: "flex-1 truncate" })
|
|
17251
|
-
]
|
|
17364
|
+
style: { paddingLeft: padLeft },
|
|
17365
|
+
children: o.label
|
|
17252
17366
|
},
|
|
17253
17367
|
rowKey
|
|
17254
17368
|
);
|
|
17255
|
-
}
|
|
17369
|
+
}
|
|
17370
|
+
const isSelected = selected.includes(o.value);
|
|
17371
|
+
return /* @__PURE__ */ jsxs(
|
|
17372
|
+
"button",
|
|
17373
|
+
{
|
|
17374
|
+
type: "button",
|
|
17375
|
+
"data-select-row": true,
|
|
17376
|
+
"data-index": idx,
|
|
17377
|
+
id: focusedValue === o.value ? activeOptionId : void 0,
|
|
17378
|
+
disabled: o.disabled,
|
|
17379
|
+
role: "option",
|
|
17380
|
+
"aria-selected": isSelected,
|
|
17381
|
+
"aria-setsize": setSize,
|
|
17382
|
+
"aria-posinset": setSize != null ? idx + 1 : void 0,
|
|
17383
|
+
"data-select-focused": focusedValue === o.value ? "true" : void 0,
|
|
17384
|
+
"data-select-active": focusedValue === o.value ? "true" : void 0,
|
|
17385
|
+
className: cn(
|
|
17386
|
+
itemBase,
|
|
17387
|
+
isSelected && !multi && "font-bold text-[color:var(--cmp-listBoxItem-content-selected)]",
|
|
17388
|
+
focusedValue === o.value && "bg-[var(--cmp-listBoxItem-bg-hover)] text-[color:var(--cmp-listBoxItem-content-hover)]",
|
|
17389
|
+
// 비활성 항목은 hover/focus 스타일 대상에서 제외한다.
|
|
17390
|
+
// (:hover 는 disabled 버튼에도 매칭되므로 클래스 자체를 붙이지 않아야 한다)
|
|
17391
|
+
!o.disabled && activeClass
|
|
17392
|
+
),
|
|
17393
|
+
style: { ...LIST_BOX_ITEM_PADDING_Y_RIGHT, paddingLeft: padLeft },
|
|
17394
|
+
onMouseEnter: () => onHoverValue(o.value),
|
|
17395
|
+
onMouseLeave: () => onHoverValue(null),
|
|
17396
|
+
onClick: (event) => onSelect(o.value, idx, event.shiftKey),
|
|
17397
|
+
children: [
|
|
17398
|
+
multi && /* @__PURE__ */ jsx(
|
|
17399
|
+
CheckboxBox,
|
|
17400
|
+
{
|
|
17401
|
+
state: isSelected,
|
|
17402
|
+
disabled: o.disabled,
|
|
17403
|
+
focused: focusedValue === o.value
|
|
17404
|
+
}
|
|
17405
|
+
),
|
|
17406
|
+
/* @__PURE__ */ jsx(OptionLabel, { label: o.label, className: "flex-1 truncate" })
|
|
17407
|
+
]
|
|
17408
|
+
},
|
|
17409
|
+
rowKey
|
|
17410
|
+
);
|
|
17411
|
+
});
|
|
17412
|
+
return /* @__PURE__ */ jsxs("div", { id: listboxId, role: tree ? "tree" : "listbox", "aria-multiselectable": multi || void 0, children: [
|
|
17413
|
+
selectAllRow,
|
|
17414
|
+
topSpacer > 0 && /* @__PURE__ */ jsx("div", { "aria-hidden": true, style: { height: topSpacer } }),
|
|
17415
|
+
wrapTreeMotion(rowNodes),
|
|
17256
17416
|
bottomSpacer > 0 && /* @__PURE__ */ jsx("div", { "aria-hidden": true, style: { height: bottomSpacer } })
|
|
17257
17417
|
] });
|
|
17258
17418
|
}
|
|
17419
|
+
function TreeRowsMotion({ opening, children }) {
|
|
17420
|
+
const ref = useRef(null);
|
|
17421
|
+
const animationRef = useRef(null);
|
|
17422
|
+
const mountedRef = useRef(false);
|
|
17423
|
+
useLayoutEffect(() => {
|
|
17424
|
+
const el = ref.current;
|
|
17425
|
+
if (el == null || typeof el.animate !== "function") return;
|
|
17426
|
+
const from = !mountedRef.current && opening ? 0 : el.getBoundingClientRect().height;
|
|
17427
|
+
mountedRef.current = true;
|
|
17428
|
+
animationRef.current?.cancel();
|
|
17429
|
+
animationRef.current = el.animate(
|
|
17430
|
+
[{ height: `${from}px` }, { height: opening ? `${el.scrollHeight}px` : "0px" }],
|
|
17431
|
+
// 접힌 뒤 줄이 목록에서 빠질 때까지 0 으로 붙잡아 둔다. 편 뒤에는 제 높이(auto)로 돌아간다
|
|
17432
|
+
{ ...TREE_BRANCH_MOTION, fill: opening ? "none" : "forwards" }
|
|
17433
|
+
);
|
|
17434
|
+
}, [opening]);
|
|
17435
|
+
useEffect(() => () => animationRef.current?.cancel(), []);
|
|
17436
|
+
return /* @__PURE__ */ jsx("div", { ref, className: "overflow-hidden", "aria-hidden": opening ? void 0 : true, children });
|
|
17437
|
+
}
|
|
17259
17438
|
function OptionLabel({ label, className }) {
|
|
17260
17439
|
return isHtmlLabel(label) ? /* @__PURE__ */ jsx("span", { className, dangerouslySetInnerHTML: { __html: sanitizeInlineHtml(label) } }) : /* @__PURE__ */ jsx("span", { className, children: label });
|
|
17261
17440
|
}
|
|
@@ -17304,6 +17483,7 @@ function CheckboxBox({
|
|
|
17304
17483
|
}
|
|
17305
17484
|
);
|
|
17306
17485
|
}
|
|
17486
|
+
var NO_KEYS = /* @__PURE__ */ new Set();
|
|
17307
17487
|
var DEFAULT_ROW_HEIGHT = 44;
|
|
17308
17488
|
var DENSE_ROW_HEIGHT = 32;
|
|
17309
17489
|
var SELECTABLE_COLUMN_WIDTH = 48;
|
|
@@ -17334,6 +17514,11 @@ var CONTEXT_MENU_ITEM_CLASS = cn(
|
|
|
17334
17514
|
"data-[highlighted]:bg-[var(--sys-color-bg-accent)] data-[highlighted]:text-[color:var(--sys-color-fg-inverse)]"
|
|
17335
17515
|
);
|
|
17336
17516
|
var ROW_INTERACTIVE_SELECTOR = 'button, a, input, select, textarea, label, [role="button"], [role="checkbox"], [contenteditable="true"]';
|
|
17517
|
+
function rowBlockHeight(tr) {
|
|
17518
|
+
const next = tr.nextElementSibling;
|
|
17519
|
+
const expansion = next instanceof HTMLElement && next.dataset.expansionRow != null ? next.offsetHeight : 0;
|
|
17520
|
+
return tr.offsetHeight + expansion;
|
|
17521
|
+
}
|
|
17337
17522
|
var colWidthFromDef = (c) => {
|
|
17338
17523
|
if (c.autoWidth) return 0;
|
|
17339
17524
|
if (c.width == null) return DEFAULT_COLUMN_WIDTH;
|
|
@@ -17346,6 +17531,8 @@ var STableImpl = /* @__PURE__ */ forwardRef(function STable({
|
|
|
17346
17531
|
rows = [],
|
|
17347
17532
|
rowKey = "id",
|
|
17348
17533
|
selectable = false,
|
|
17534
|
+
rowSelectLabel,
|
|
17535
|
+
selectAllLabel = "\uC804\uCCB4 \uC120\uD0DD",
|
|
17349
17536
|
dragSelectable = false,
|
|
17350
17537
|
contextMenuItems,
|
|
17351
17538
|
onContextMenuItemClick,
|
|
@@ -17384,6 +17571,11 @@ var STableImpl = /* @__PURE__ */ forwardRef(function STable({
|
|
|
17384
17571
|
tableId,
|
|
17385
17572
|
onRowClick,
|
|
17386
17573
|
onRowDoubleClick,
|
|
17574
|
+
renderExpansion,
|
|
17575
|
+
expansionColumn,
|
|
17576
|
+
expandedKeys,
|
|
17577
|
+
defaultExpandedKeys,
|
|
17578
|
+
onExpandedChange,
|
|
17387
17579
|
className,
|
|
17388
17580
|
style
|
|
17389
17581
|
}, ref) {
|
|
@@ -17398,6 +17590,44 @@ var STableImpl = /* @__PURE__ */ forwardRef(function STable({
|
|
|
17398
17590
|
onDenseChange?.(next);
|
|
17399
17591
|
};
|
|
17400
17592
|
const visibleCols = useMemo(() => columns.filter((c) => c.visible !== false), [columns]);
|
|
17593
|
+
const expansionOn = renderExpansion != null;
|
|
17594
|
+
const [innerExpandedKeys, setInnerExpandedKeys] = useState(
|
|
17595
|
+
() => defaultExpandedKeys ?? []
|
|
17596
|
+
);
|
|
17597
|
+
const currentExpandedKeys = expandedKeys ?? innerExpandedKeys;
|
|
17598
|
+
const expandedSet = useMemo(() => new Set(currentExpandedKeys), [currentExpandedKeys]);
|
|
17599
|
+
const isRowExpanded = (row) => expansionOn && expandedSet.has(row[rowKey]);
|
|
17600
|
+
const [closingKeys, setClosingKeys] = useState(NO_KEYS);
|
|
17601
|
+
const [prevExpandedSet, setPrevExpandedSet] = useState(expandedSet);
|
|
17602
|
+
if (prevExpandedSet !== expandedSet) {
|
|
17603
|
+
setPrevExpandedSet(expandedSet);
|
|
17604
|
+
let nextClosing = NO_KEYS;
|
|
17605
|
+
if (expansionOn && motionAllowed()) {
|
|
17606
|
+
const collapsed = [...prevExpandedSet].filter((key) => !expandedSet.has(key));
|
|
17607
|
+
const opened = [...expandedSet].filter((key) => !prevExpandedSet.has(key));
|
|
17608
|
+
if (collapsed.length > 0 && opened.length === 0) nextClosing = new Set(collapsed);
|
|
17609
|
+
}
|
|
17610
|
+
if (nextClosing !== closingKeys) setClosingKeys(nextClosing);
|
|
17611
|
+
}
|
|
17612
|
+
const shownExpansionSet = useMemo(
|
|
17613
|
+
() => closingKeys.size === 0 ? expandedSet : /* @__PURE__ */ new Set([...expandedSet, ...closingKeys]),
|
|
17614
|
+
[expandedSet, closingKeys]
|
|
17615
|
+
);
|
|
17616
|
+
const toggleExpanded = (row) => {
|
|
17617
|
+
const key = row[rowKey];
|
|
17618
|
+
const next = expandedSet.has(key) ? currentExpandedKeys.filter((k) => k !== key) : [...currentExpandedKeys, key];
|
|
17619
|
+
if (expandedKeys === void 0) setInnerExpandedKeys(next);
|
|
17620
|
+
onExpandedChange?.(next, row);
|
|
17621
|
+
};
|
|
17622
|
+
const expansionColIndex = !expansionOn ? -1 : expansionColumn == null ? 0 : visibleCols.findIndex((c) => c.name === expansionColumn);
|
|
17623
|
+
if (expansionColIndex >= 0) {
|
|
17624
|
+
const col = visibleCols[expansionColIndex];
|
|
17625
|
+
if (col?.renderCell) {
|
|
17626
|
+
throw new Error(
|
|
17627
|
+
`[STable] \uD3BC\uCE68 \uCEEC\uB7FC(${col.name})\uC5D0\uB294 renderCell \uC744 \uC4F8 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. render \uB97C \uC4F0\uC138\uC694.`
|
|
17628
|
+
);
|
|
17629
|
+
}
|
|
17630
|
+
}
|
|
17401
17631
|
const headerGroupInfo = useMemo(() => {
|
|
17402
17632
|
if (!headerGroups || headerGroups.length === 0) return null;
|
|
17403
17633
|
const indexByName = new Map(visibleCols.map((col, index) => [col.name, index]));
|
|
@@ -17502,6 +17732,7 @@ var STableImpl = /* @__PURE__ */ forwardRef(function STable({
|
|
|
17502
17732
|
metricsRef.current ?? (metricsRef.current = createRowMetrics(0, effectiveRowHeight));
|
|
17503
17733
|
const metrics = metricsRef.current;
|
|
17504
17734
|
const measuredKeyRef = useRef("");
|
|
17735
|
+
const measuredExpansionRef = useRef(shownExpansionSet);
|
|
17505
17736
|
const pendingRowRef = useRef(null);
|
|
17506
17737
|
const pinnedTopRef = useRef(-1);
|
|
17507
17738
|
const lastEstimateRef = useRef(effectiveRowHeight);
|
|
@@ -17515,6 +17746,7 @@ var STableImpl = /* @__PURE__ */ forwardRef(function STable({
|
|
|
17515
17746
|
}
|
|
17516
17747
|
const [scrolledLeft, setScrolledLeft] = useState(false);
|
|
17517
17748
|
const [scrolledRight, setScrolledRight] = useState(false);
|
|
17749
|
+
const [viewportWidth, setViewportWidth] = useState(0);
|
|
17518
17750
|
const setRowCount = useCallback((count) => {
|
|
17519
17751
|
const safe = Math.max(0, Math.floor(Number.isFinite(count) ? count : 0));
|
|
17520
17752
|
lastReachEndRef.current = -1;
|
|
@@ -17540,6 +17772,7 @@ var STableImpl = /* @__PURE__ */ forwardRef(function STable({
|
|
|
17540
17772
|
setHeaderHeight(headerH);
|
|
17541
17773
|
setScrolledLeft(el.scrollLeft > 0);
|
|
17542
17774
|
setScrolledRight(el.scrollLeft + el.clientWidth < el.scrollWidth - 1);
|
|
17775
|
+
setViewportWidth(el.clientWidth);
|
|
17543
17776
|
if (virtualScroll) {
|
|
17544
17777
|
const { start, end } = calcRange(el.scrollTop, el.clientHeight - headerH);
|
|
17545
17778
|
setVsRange((prev) => prev.start === start && prev.end === end ? prev : { start, end });
|
|
@@ -17557,6 +17790,13 @@ var STableImpl = /* @__PURE__ */ forwardRef(function STable({
|
|
|
17557
17790
|
useEffect(() => {
|
|
17558
17791
|
handleScroll();
|
|
17559
17792
|
}, [handleScroll, rowCount, columnWidths, loading2, height]);
|
|
17793
|
+
useEffect(() => {
|
|
17794
|
+
const el = scrollRef.current;
|
|
17795
|
+
if (!expansionOn || !el || typeof ResizeObserver === "undefined") return;
|
|
17796
|
+
const observer = new ResizeObserver(() => handleScroll());
|
|
17797
|
+
observer.observe(el);
|
|
17798
|
+
return () => observer.disconnect();
|
|
17799
|
+
}, [expansionOn, handleScroll]);
|
|
17560
17800
|
const handleScrollRef = useRef(handleScroll);
|
|
17561
17801
|
handleScrollRef.current = handleScroll;
|
|
17562
17802
|
useIsomorphicLayoutEffect(() => {
|
|
@@ -17566,9 +17806,10 @@ var STableImpl = /* @__PURE__ */ forwardRef(function STable({
|
|
|
17566
17806
|
if (!el || m == null) return;
|
|
17567
17807
|
const pending2 = pendingRowRef.current;
|
|
17568
17808
|
const key = `${vsRange.start}:${vsRange.end}:${rowCount}`;
|
|
17569
|
-
const alreadyMeasured = measuredKeyRef.current === key;
|
|
17809
|
+
const alreadyMeasured = measuredKeyRef.current === key && measuredExpansionRef.current === shownExpansionSet;
|
|
17570
17810
|
if (alreadyMeasured && pending2 == null) return;
|
|
17571
17811
|
measuredKeyRef.current = key;
|
|
17812
|
+
measuredExpansionRef.current = shownExpansionSet;
|
|
17572
17813
|
const anchor = vsRange.start;
|
|
17573
17814
|
const anchorBefore = offsetOf(m, anchor);
|
|
17574
17815
|
let changed = false;
|
|
@@ -17576,7 +17817,7 @@ var STableImpl = /* @__PURE__ */ forwardRef(function STable({
|
|
|
17576
17817
|
for (const tr of el.querySelectorAll("tr[data-row-index]")) {
|
|
17577
17818
|
const index = Number(tr.dataset.rowIndex);
|
|
17578
17819
|
if (!Number.isInteger(index)) continue;
|
|
17579
|
-
const h = tr
|
|
17820
|
+
const h = rowBlockHeight(tr);
|
|
17580
17821
|
if (h <= 0) continue;
|
|
17581
17822
|
if (setRowSize(m, index, h)) changed = true;
|
|
17582
17823
|
}
|
|
@@ -17598,6 +17839,54 @@ var STableImpl = /* @__PURE__ */ forwardRef(function STable({
|
|
|
17598
17839
|
if (delta !== 0 && el.scrollTop > 0) el.scrollTop += delta;
|
|
17599
17840
|
handleScrollRef.current();
|
|
17600
17841
|
});
|
|
17842
|
+
const expansionMotionRef = useRef(null);
|
|
17843
|
+
const prevExpansionRef = useRef(null);
|
|
17844
|
+
useIsomorphicLayoutEffect(() => {
|
|
17845
|
+
if (!expansionOn) return;
|
|
17846
|
+
const prev = prevExpansionRef.current;
|
|
17847
|
+
prevExpansionRef.current = { rows, keys: expandedSet };
|
|
17848
|
+
const running = expansionMotionRef.current;
|
|
17849
|
+
if (running?.done) {
|
|
17850
|
+
running.cancel();
|
|
17851
|
+
expansionMotionRef.current = null;
|
|
17852
|
+
}
|
|
17853
|
+
if (!prev || prev.keys === expandedSet) return;
|
|
17854
|
+
const added = new Set([...expandedSet].filter((key) => !prev.keys.has(key)));
|
|
17855
|
+
const removed = new Set([...prev.keys].filter((key) => !expandedSet.has(key)));
|
|
17856
|
+
if (added.size === 0 && removed.size === 0) return;
|
|
17857
|
+
let from = added.size > 0 ? 0 : 1;
|
|
17858
|
+
if (expansionMotionRef.current) {
|
|
17859
|
+
const current = expansionMotionRef.current;
|
|
17860
|
+
const reversing = current.to === 0 && sameKeys(added, current.keys) || current.to === 1 && sameKeys(removed, current.keys);
|
|
17861
|
+
if (reversing) from = current.openness();
|
|
17862
|
+
current.cancel();
|
|
17863
|
+
expansionMotionRef.current = null;
|
|
17864
|
+
}
|
|
17865
|
+
const scrollEl = scrollRef.current;
|
|
17866
|
+
const keys = added.size > 0 ? added : removed;
|
|
17867
|
+
const keyNames = new Set([...keys].map(String));
|
|
17868
|
+
const canAnimate = scrollEl != null && prev.rows === rows && motionAllowed() && (added.size === 0 || removed.size === 0);
|
|
17869
|
+
const motion = canAnimate ? startRowMotion({
|
|
17870
|
+
scrollEl,
|
|
17871
|
+
isInBlock: (tr) => tr.dataset.expansionRow != null && keyNames.has(tr.dataset.expansionKey ?? ""),
|
|
17872
|
+
blockKeys: keys,
|
|
17873
|
+
from,
|
|
17874
|
+
to: added.size > 0 ? 1 : 0
|
|
17875
|
+
}) : null;
|
|
17876
|
+
if (motion) {
|
|
17877
|
+
expansionMotionRef.current = motion;
|
|
17878
|
+
if (motion.to === 0) {
|
|
17879
|
+
void motion.finished.then((completed) => {
|
|
17880
|
+
if (!completed || expansionMotionRef.current !== motion) return;
|
|
17881
|
+
motion.done = true;
|
|
17882
|
+
setClosingKeys(NO_KEYS);
|
|
17883
|
+
});
|
|
17884
|
+
}
|
|
17885
|
+
} else if (closingKeys.size > 0) {
|
|
17886
|
+
setClosingKeys(NO_KEYS);
|
|
17887
|
+
}
|
|
17888
|
+
}, [expandedSet, closingKeys]);
|
|
17889
|
+
useEffect(() => () => expansionMotionRef.current?.cancel(), []);
|
|
17601
17890
|
useEffect(() => {
|
|
17602
17891
|
if (!virtualScroll) return;
|
|
17603
17892
|
cbRef.current.onVirtualUpdate?.({ from: vsRange.start, to: vsRange.end });
|
|
@@ -17654,7 +17943,7 @@ var STableImpl = /* @__PURE__ */ forwardRef(function STable({
|
|
|
17654
17943
|
if (virtualScroll && m != null) return indexAtOffset(m, el.scrollTop);
|
|
17655
17944
|
const top = el.scrollTop;
|
|
17656
17945
|
for (const tr of el.querySelectorAll("tr[data-row-index]")) {
|
|
17657
|
-
if (tr.offsetTop + tr
|
|
17946
|
+
if (tr.offsetTop + rowBlockHeight(tr) > top) return Number(tr.dataset.rowIndex) || 0;
|
|
17658
17947
|
}
|
|
17659
17948
|
return 0;
|
|
17660
17949
|
},
|
|
@@ -17982,7 +18271,8 @@ var STableImpl = /* @__PURE__ */ forwardRef(function STable({
|
|
|
17982
18271
|
{
|
|
17983
18272
|
value: headerCheck,
|
|
17984
18273
|
disabled: !hasSelectableRow,
|
|
17985
|
-
onValueChange: toggleAll
|
|
18274
|
+
onValueChange: toggleAll,
|
|
18275
|
+
ariaLabel: selectAllLabel
|
|
17986
18276
|
}
|
|
17987
18277
|
) })
|
|
17988
18278
|
}
|
|
@@ -18028,7 +18318,8 @@ var STableImpl = /* @__PURE__ */ forwardRef(function STable({
|
|
|
18028
18318
|
{
|
|
18029
18319
|
value: headerCheck,
|
|
18030
18320
|
disabled: !hasSelectableRow,
|
|
18031
|
-
onValueChange: toggleAll
|
|
18321
|
+
onValueChange: toggleAll,
|
|
18322
|
+
ariaLabel: selectAllLabel
|
|
18032
18323
|
}
|
|
18033
18324
|
) }),
|
|
18034
18325
|
/* @__PURE__ */ jsx(
|
|
@@ -18228,112 +18519,168 @@ var STableImpl = /* @__PURE__ */ forwardRef(function STable({
|
|
|
18228
18519
|
const rowLocked = (selectable || dragSelectable) && !canSelectRow(row);
|
|
18229
18520
|
const rowBg = "bg-white";
|
|
18230
18521
|
const rowHover = hoverable && !rowLocked;
|
|
18231
|
-
|
|
18232
|
-
|
|
18233
|
-
|
|
18234
|
-
"
|
|
18235
|
-
|
|
18236
|
-
|
|
18237
|
-
|
|
18238
|
-
|
|
18239
|
-
|
|
18240
|
-
|
|
18241
|
-
|
|
18242
|
-
|
|
18243
|
-
|
|
18244
|
-
|
|
18245
|
-
|
|
18246
|
-
|
|
18247
|
-
(
|
|
18248
|
-
dragSelectOn
|
|
18249
|
-
|
|
18250
|
-
|
|
18251
|
-
|
|
18252
|
-
|
|
18253
|
-
|
|
18254
|
-
"
|
|
18255
|
-
|
|
18256
|
-
|
|
18257
|
-
|
|
18258
|
-
|
|
18522
|
+
const expansionShown = expansionOn && shownExpansionSet.has(row[rowKey]);
|
|
18523
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
18524
|
+
/* @__PURE__ */ jsxs(
|
|
18525
|
+
"tr",
|
|
18526
|
+
{
|
|
18527
|
+
"data-row-index": rowIndex,
|
|
18528
|
+
onClick: onRowClick ? (event) => {
|
|
18529
|
+
const target = event.target;
|
|
18530
|
+
if (target.closest(ROW_INTERACTIVE_SELECTOR)) return;
|
|
18531
|
+
onRowClick(row);
|
|
18532
|
+
} : void 0,
|
|
18533
|
+
onDoubleClick: onRowDoubleClick ? (event) => {
|
|
18534
|
+
const target = event.target;
|
|
18535
|
+
if (target.closest(ROW_INTERACTIVE_SELECTOR)) return;
|
|
18536
|
+
onRowDoubleClick(row);
|
|
18537
|
+
} : void 0,
|
|
18538
|
+
onMouseDown: dragSelectOn ? (event) => startDragSelect(rowIndex, event) : void 0,
|
|
18539
|
+
onMouseEnter: dragSelectOn ? () => extendDragSelect(rowIndex) : void 0,
|
|
18540
|
+
onContextMenu: contextMenuOn ? (event) => openContextMenu(row, event) : void 0,
|
|
18541
|
+
className: cn(
|
|
18542
|
+
"group/table-row",
|
|
18543
|
+
rowHover && "hover:bg-[var(--color-grey-05)]",
|
|
18544
|
+
(onRowClick || onRowDoubleClick) && "cursor-pointer",
|
|
18545
|
+
dragSelectOn && "select-none",
|
|
18546
|
+
rowClassName?.(row)
|
|
18547
|
+
),
|
|
18548
|
+
style: { height: virtualScroll ? effectiveRowHeight : rowH },
|
|
18549
|
+
children: [
|
|
18550
|
+
selectable && /* @__PURE__ */ jsx(
|
|
18551
|
+
"td",
|
|
18552
|
+
{
|
|
18553
|
+
className: cn(
|
|
18554
|
+
// 원본 .td--selected: 48px 고정폭, 항상 left sticky, 체크박스 중앙정렬
|
|
18555
|
+
"sticky left-0 z-[2] border-b border-solid border-[color:var(--cmp-table-border-color)] align-middle",
|
|
18556
|
+
rowBg,
|
|
18557
|
+
rowHover && "group-hover/table-row:bg-[var(--color-grey-05)]",
|
|
18558
|
+
stickyLeft === 0 && scrolledLeft && "shadow-[5px_0_8px_0_rgba(34,34,34,0.1)]"
|
|
18559
|
+
),
|
|
18560
|
+
style: {
|
|
18561
|
+
width: SELECTABLE_COLUMN_WIDTH,
|
|
18562
|
+
minWidth: SELECTABLE_COLUMN_WIDTH,
|
|
18563
|
+
maxWidth: SELECTABLE_COLUMN_WIDTH,
|
|
18564
|
+
// 선택 열도 행의 일부다 — 여기만 희지 않으면 회색 줄이 끊긴다
|
|
18565
|
+
background: rowLocked ? LOCKED_BG : void 0
|
|
18566
|
+
},
|
|
18567
|
+
onClick: (e) => e.stopPropagation(),
|
|
18568
|
+
onDoubleClick: (e) => e.stopPropagation(),
|
|
18569
|
+
onClickCapture: (e) => {
|
|
18570
|
+
shiftPressedRef.current = e.shiftKey;
|
|
18571
|
+
if (e.shiftKey) window.getSelection()?.removeAllRanges();
|
|
18572
|
+
},
|
|
18573
|
+
children: /* @__PURE__ */ jsx("div", { className: "flex select-none items-center justify-center", children: /* @__PURE__ */ jsx(
|
|
18574
|
+
SCheckbox,
|
|
18575
|
+
{
|
|
18576
|
+
value: isSel || !isRowIndeterminate?.(row) ? isSel : null,
|
|
18577
|
+
disabled: !canSelectRow(row),
|
|
18578
|
+
onValueChange: () => toggleRow(row, rowOffset + ri),
|
|
18579
|
+
ariaLabel: rowSelectLabel?.(row) ?? "\uD589 \uC120\uD0DD"
|
|
18580
|
+
}
|
|
18581
|
+
) })
|
|
18582
|
+
}
|
|
18583
|
+
),
|
|
18584
|
+
visibleCols.map((col, i) => {
|
|
18585
|
+
const tdClassName = cn(
|
|
18586
|
+
// 원본 .td: 컬럼 폭에 맞춰 내용 말줄임(overflow hidden + ellipsis) + 행 구분선(border-b)
|
|
18587
|
+
// 셀 배경은 흰색으로 채워 뒤 배경이 비치지 않게 하고, hover 시 행 배경(grey_05)을 따른다.
|
|
18588
|
+
"overflow-hidden text-ellipsis whitespace-nowrap border-b border-solid border-[color:var(--cmp-table-border-color)] text-[12px] leading-[20px] text-[color:var(--sys-color-fg-primary)]",
|
|
18259
18589
|
rowBg,
|
|
18260
18590
|
rowHover && "group-hover/table-row:bg-[var(--color-grey-05)]",
|
|
18261
|
-
|
|
18262
|
-
)
|
|
18263
|
-
|
|
18264
|
-
|
|
18265
|
-
|
|
18266
|
-
|
|
18267
|
-
|
|
18268
|
-
|
|
18269
|
-
|
|
18270
|
-
|
|
18271
|
-
|
|
18272
|
-
|
|
18273
|
-
|
|
18274
|
-
|
|
18275
|
-
|
|
18276
|
-
|
|
18277
|
-
|
|
18591
|
+
typeof col.tdClass === "function" ? col.tdClass(row) : col.tdClass
|
|
18592
|
+
);
|
|
18593
|
+
const tdPadX = cellPadXOf(col);
|
|
18594
|
+
const dragEdges = dragSel ? [
|
|
18595
|
+
dragTop && `inset 0 ${DRAG_EDGE_W} 0 0 ${DRAG_EDGE_C}`,
|
|
18596
|
+
dragBottom && `inset 0 ${DRAG_EDGE_NEG_W} 0 0 ${DRAG_EDGE_C}`,
|
|
18597
|
+
i === 0 && `inset ${DRAG_EDGE_W} 0 0 0 ${DRAG_EDGE_C}`,
|
|
18598
|
+
i === visibleCols.length - 1 && `inset ${DRAG_EDGE_NEG_W} 0 0 0 ${DRAG_EDGE_C}`
|
|
18599
|
+
].filter(Boolean) : [];
|
|
18600
|
+
const shadows = [...dragEdges, stickyShadowValue(i)].filter(Boolean);
|
|
18601
|
+
const tdStyle = {
|
|
18602
|
+
paddingLeft: tdPadX,
|
|
18603
|
+
paddingRight: tdPadX,
|
|
18604
|
+
paddingTop: cellPadY,
|
|
18605
|
+
paddingBottom: cellPadY,
|
|
18606
|
+
textAlign: col.align ?? "left",
|
|
18607
|
+
// 인라인이라 hover 배경보다 세다 — 고른 행은 hover 로 흐려지지 않는다.
|
|
18608
|
+
background: dragSel ? DRAG_BG : rowLocked ? LOCKED_BG : void 0,
|
|
18609
|
+
// 클래스의 기본 글자색을 덮는다. 셀이 자기 색을 정한 내용(SIcon 의
|
|
18610
|
+
// color, STag 등)까지는 닿지 않는다 — 그건 소비 앱 몫이다.
|
|
18611
|
+
color: rowLocked ? LOCKED_CONTENT : void 0,
|
|
18612
|
+
boxShadow: shadows.length > 0 ? shadows.join(", ") : void 0,
|
|
18613
|
+
...cellStyle(i)
|
|
18614
|
+
};
|
|
18615
|
+
if (col.renderCell) {
|
|
18616
|
+
const cell = col.renderCell({
|
|
18617
|
+
row,
|
|
18618
|
+
value: cellValue(col, row),
|
|
18619
|
+
column: col,
|
|
18620
|
+
colIndex: i,
|
|
18621
|
+
rowIndex: ri,
|
|
18622
|
+
isSelected: isSel,
|
|
18623
|
+
className: tdClassName,
|
|
18624
|
+
style: tdStyle
|
|
18625
|
+
});
|
|
18626
|
+
if (cell == null || cell === false) return null;
|
|
18627
|
+
return /* @__PURE__ */ jsx(Fragment$1, { children: cell }, col.name);
|
|
18628
|
+
}
|
|
18629
|
+
const content = col.render ? col.render(row, cellValue(col, row)) : cellValue(col, row);
|
|
18630
|
+
if (i === expansionColIndex) {
|
|
18631
|
+
return /* @__PURE__ */ jsx(
|
|
18632
|
+
"td",
|
|
18633
|
+
{
|
|
18634
|
+
className: cn(tdClassName, "cursor-pointer"),
|
|
18635
|
+
style: tdStyle,
|
|
18636
|
+
onClick: (event) => {
|
|
18637
|
+
if (isOwnControl(event.target, event.currentTarget)) return;
|
|
18638
|
+
event.stopPropagation();
|
|
18639
|
+
toggleExpanded(row);
|
|
18640
|
+
},
|
|
18641
|
+
onDoubleClick: (event) => event.stopPropagation(),
|
|
18642
|
+
children: /* @__PURE__ */ jsx(ExpansionToggleCell, { expanded: isRowExpanded(row), children: content })
|
|
18643
|
+
},
|
|
18644
|
+
col.name
|
|
18645
|
+
);
|
|
18646
|
+
}
|
|
18647
|
+
return /* @__PURE__ */ jsx("td", { className: tdClassName, style: tdStyle, children: content }, col.name);
|
|
18648
|
+
})
|
|
18649
|
+
]
|
|
18650
|
+
}
|
|
18651
|
+
),
|
|
18652
|
+
expansionShown && // 상세 영역 — 행이 아니므로 data-row-index 를 달지 않는다 (행 수·선택·실측 대상 밖)
|
|
18653
|
+
/* @__PURE__ */ jsx(
|
|
18654
|
+
"tr",
|
|
18655
|
+
{
|
|
18656
|
+
"data-expansion-row": "",
|
|
18657
|
+
"data-expansion-key": String(row[rowKey]),
|
|
18658
|
+
className: expandedSet.has(row[rowKey]) ? void 0 : "pointer-events-none",
|
|
18659
|
+
children: /* @__PURE__ */ jsx(
|
|
18660
|
+
"td",
|
|
18661
|
+
{
|
|
18662
|
+
colSpan: totalColSpan,
|
|
18663
|
+
className: "whitespace-normal border-b border-solid border-[color:var(--cmp-table-border-color)] bg-white p-0 text-[12px] leading-[20px] text-[color:var(--sys-color-fg-primary)]",
|
|
18664
|
+
children: /* @__PURE__ */ jsx(
|
|
18665
|
+
"div",
|
|
18278
18666
|
{
|
|
18279
|
-
|
|
18280
|
-
|
|
18281
|
-
|
|
18667
|
+
className: "sticky left-0 box-border min-w-0",
|
|
18668
|
+
style: {
|
|
18669
|
+
// 가로 스크롤이 있어도 보이는 폭에 머문다. 재기 전(0)에는 표 폭을 따른다
|
|
18670
|
+
width: viewportWidth > 0 ? viewportWidth : void 0,
|
|
18671
|
+
paddingLeft: "var(--cmp-table-body-paddingX-wide)",
|
|
18672
|
+
paddingRight: "var(--cmp-table-body-paddingX-wide)",
|
|
18673
|
+
paddingTop: cellPadY,
|
|
18674
|
+
paddingBottom: cellPadY
|
|
18675
|
+
},
|
|
18676
|
+
children: renderExpansion?.(row)
|
|
18282
18677
|
}
|
|
18283
|
-
)
|
|
18284
|
-
}
|
|
18285
|
-
),
|
|
18286
|
-
visibleCols.map((col, i) => {
|
|
18287
|
-
const tdClassName = cn(
|
|
18288
|
-
// 원본 .td: 컬럼 폭에 맞춰 내용 말줄임(overflow hidden + ellipsis) + 행 구분선(border-b)
|
|
18289
|
-
// 셀 배경은 흰색으로 채워 뒤 배경이 비치지 않게 하고, hover 시 행 배경(grey_05)을 따른다.
|
|
18290
|
-
"overflow-hidden text-ellipsis whitespace-nowrap border-b border-solid border-[color:var(--cmp-table-border-color)] text-[12px] leading-[20px] text-[color:var(--sys-color-fg-primary)]",
|
|
18291
|
-
rowBg,
|
|
18292
|
-
rowHover && "group-hover/table-row:bg-[var(--color-grey-05)]",
|
|
18293
|
-
typeof col.tdClass === "function" ? col.tdClass(row) : col.tdClass
|
|
18294
|
-
);
|
|
18295
|
-
const tdPadX = cellPadXOf(col);
|
|
18296
|
-
const dragEdges = dragSel ? [
|
|
18297
|
-
dragTop && `inset 0 ${DRAG_EDGE_W} 0 0 ${DRAG_EDGE_C}`,
|
|
18298
|
-
dragBottom && `inset 0 ${DRAG_EDGE_NEG_W} 0 0 ${DRAG_EDGE_C}`,
|
|
18299
|
-
i === 0 && `inset ${DRAG_EDGE_W} 0 0 0 ${DRAG_EDGE_C}`,
|
|
18300
|
-
i === visibleCols.length - 1 && `inset ${DRAG_EDGE_NEG_W} 0 0 0 ${DRAG_EDGE_C}`
|
|
18301
|
-
].filter(Boolean) : [];
|
|
18302
|
-
const shadows = [...dragEdges, stickyShadowValue(i)].filter(Boolean);
|
|
18303
|
-
const tdStyle = {
|
|
18304
|
-
paddingLeft: tdPadX,
|
|
18305
|
-
paddingRight: tdPadX,
|
|
18306
|
-
paddingTop: cellPadY,
|
|
18307
|
-
paddingBottom: cellPadY,
|
|
18308
|
-
textAlign: col.align ?? "left",
|
|
18309
|
-
// 인라인이라 hover 배경보다 세다 — 고른 행은 hover 로 흐려지지 않는다.
|
|
18310
|
-
background: dragSel ? DRAG_BG : rowLocked ? LOCKED_BG : void 0,
|
|
18311
|
-
// 클래스의 기본 글자색을 덮는다. 셀이 자기 색을 정한 내용(SIcon 의
|
|
18312
|
-
// color, STag 등)까지는 닿지 않는다 — 그건 소비 앱 몫이다.
|
|
18313
|
-
color: rowLocked ? LOCKED_CONTENT : void 0,
|
|
18314
|
-
boxShadow: shadows.length > 0 ? shadows.join(", ") : void 0,
|
|
18315
|
-
...cellStyle(i)
|
|
18316
|
-
};
|
|
18317
|
-
if (col.renderCell) {
|
|
18318
|
-
const cell = col.renderCell({
|
|
18319
|
-
row,
|
|
18320
|
-
value: cellValue(col, row),
|
|
18321
|
-
column: col,
|
|
18322
|
-
colIndex: i,
|
|
18323
|
-
rowIndex: ri,
|
|
18324
|
-
isSelected: isSel,
|
|
18325
|
-
className: tdClassName,
|
|
18326
|
-
style: tdStyle
|
|
18327
|
-
});
|
|
18328
|
-
if (cell == null || cell === false) return null;
|
|
18329
|
-
return /* @__PURE__ */ jsx(Fragment$1, { children: cell }, col.name);
|
|
18678
|
+
)
|
|
18330
18679
|
}
|
|
18331
|
-
|
|
18332
|
-
|
|
18333
|
-
|
|
18334
|
-
|
|
18335
|
-
row[rowKey] ?? ri
|
|
18336
|
-
);
|
|
18680
|
+
)
|
|
18681
|
+
}
|
|
18682
|
+
)
|
|
18683
|
+
] }, row[rowKey] ?? ri);
|
|
18337
18684
|
}),
|
|
18338
18685
|
virtualScroll && bottomSpacer > 0 && /* @__PURE__ */ jsx("tr", { "aria-hidden": true, style: { height: bottomSpacer }, children: /* @__PURE__ */ jsx("td", { colSpan: totalColSpan, className: "p-0" }) })
|
|
18339
18686
|
] })
|
|
@@ -18459,6 +18806,26 @@ var STableImpl = /* @__PURE__ */ forwardRef(function STable({
|
|
|
18459
18806
|
);
|
|
18460
18807
|
});
|
|
18461
18808
|
var STable2 = STableImpl;
|
|
18809
|
+
function isOwnControl(target, cell) {
|
|
18810
|
+
if (!(target instanceof Element)) return false;
|
|
18811
|
+
if (target.closest("[data-expansion-toggle]")) return false;
|
|
18812
|
+
const control = target.closest(ROW_INTERACTIVE_SELECTOR);
|
|
18813
|
+
return control != null && cell.contains(control);
|
|
18814
|
+
}
|
|
18815
|
+
function ExpansionToggleCell({ expanded, children }) {
|
|
18816
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 items-center gap-(--cmp-tree-item-contentGap)", children: [
|
|
18817
|
+
/* @__PURE__ */ jsx("span", { "data-expansion-toggle": true, className: "flex shrink-0", children: /* @__PURE__ */ jsx(
|
|
18818
|
+
SGhostButton,
|
|
18819
|
+
{
|
|
18820
|
+
icon: expanded ? "caretDown" : "caretRight",
|
|
18821
|
+
size: "xxs",
|
|
18822
|
+
ariaLabel: expanded ? "collapse row" : "expand row",
|
|
18823
|
+
ariaExpanded: expanded
|
|
18824
|
+
}
|
|
18825
|
+
) }),
|
|
18826
|
+
/* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate", children })
|
|
18827
|
+
] });
|
|
18828
|
+
}
|
|
18462
18829
|
var COUNT_NULL = "var(--cmp-table-bar-count-null)";
|
|
18463
18830
|
var totalColor = (n) => n > 0 ? "var(--cmp-table-bar-count-total)" : COUNT_NULL;
|
|
18464
18831
|
var selectedColor = (n) => n > 0 ? "var(--color-brilliantblue-75)" : COUNT_NULL;
|
|
@@ -18527,9 +18894,7 @@ var STableBar = /* @__PURE__ */ forwardRef(function STableBar2({ title, total, s
|
|
|
18527
18894
|
}
|
|
18528
18895
|
);
|
|
18529
18896
|
});
|
|
18530
|
-
var
|
|
18531
|
-
var EXPAND_MOTION = { duration: 220, easing: "ease-out" };
|
|
18532
|
-
var COLLAPSE_MOTION = { duration: 140, easing: "ease-in" };
|
|
18897
|
+
var NO_KEYS2 = /* @__PURE__ */ new Set();
|
|
18533
18898
|
function STreeTable({
|
|
18534
18899
|
rows = [],
|
|
18535
18900
|
columns = [],
|
|
@@ -18582,11 +18947,11 @@ function STreeTable({
|
|
|
18582
18947
|
[rows, expandedSet, rowKey, childrenKey]
|
|
18583
18948
|
);
|
|
18584
18949
|
const tableRef = useRef(null);
|
|
18585
|
-
const [closingKeys, setClosingKeys] = useState(
|
|
18950
|
+
const [closingKeys, setClosingKeys] = useState(NO_KEYS2);
|
|
18586
18951
|
const [prevExpand, setPrevExpand] = useState({ rows, expandedSet });
|
|
18587
18952
|
if (prevExpand.rows !== rows || prevExpand.expandedSet !== expandedSet) {
|
|
18588
18953
|
setPrevExpand({ rows, expandedSet });
|
|
18589
|
-
let nextClosing =
|
|
18954
|
+
let nextClosing = NO_KEYS2;
|
|
18590
18955
|
if (prevExpand.rows === rows && motionAllowed()) {
|
|
18591
18956
|
const collapsed = [...prevExpand.expandedSet].filter((key) => !expandedSet.has(key));
|
|
18592
18957
|
const expanded = [...expandedSet].filter((key) => !prevExpand.expandedSet.has(key));
|
|
@@ -18635,11 +19000,15 @@ function STreeTable({
|
|
|
18635
19000
|
motionRef.current = null;
|
|
18636
19001
|
}
|
|
18637
19002
|
const scrollEl = tableRef.current?.getScrollElement();
|
|
19003
|
+
const rowKeys = renderedRows.map(keyOf);
|
|
18638
19004
|
const keys = added.size > 0 ? added : removed;
|
|
18639
19005
|
const canAnimate = scrollEl != null && motionAllowed() && (added.size === 0 || removed.size === 0);
|
|
18640
19006
|
const motion = canAnimate ? startRowMotion({
|
|
18641
19007
|
scrollEl,
|
|
18642
|
-
|
|
19008
|
+
isInBlock: (tr) => {
|
|
19009
|
+
const key = rowKeys[Number(tr.dataset.rowIndex)];
|
|
19010
|
+
return key !== void 0 && keys.has(key);
|
|
19011
|
+
},
|
|
18643
19012
|
blockKeys: keys,
|
|
18644
19013
|
from,
|
|
18645
19014
|
to: added.size > 0 ? 1 : 0
|
|
@@ -18650,11 +19019,11 @@ function STreeTable({
|
|
|
18650
19019
|
void motion.finished.then((completed) => {
|
|
18651
19020
|
if (!completed || motionRef.current !== motion) return;
|
|
18652
19021
|
motion.done = true;
|
|
18653
|
-
setClosingKeys(
|
|
19022
|
+
setClosingKeys(NO_KEYS2);
|
|
18654
19023
|
});
|
|
18655
19024
|
}
|
|
18656
19025
|
} else if (closingKeys.size > 0) {
|
|
18657
|
-
setClosingKeys(
|
|
19026
|
+
setClosingKeys(NO_KEYS2);
|
|
18658
19027
|
}
|
|
18659
19028
|
}, [renderedRows]);
|
|
18660
19029
|
useEffect(() => () => motionRef.current?.cancel(), []);
|
|
@@ -18727,7 +19096,7 @@ function STreeTable({
|
|
|
18727
19096
|
className: cn(className, hasChildren && "cursor-pointer"),
|
|
18728
19097
|
style,
|
|
18729
19098
|
onClick: hasChildren ? (event) => {
|
|
18730
|
-
if (
|
|
19099
|
+
if (isOwnControl2(event.target, event.currentTarget)) return;
|
|
18731
19100
|
event.stopPropagation();
|
|
18732
19101
|
toggleExpanded(row);
|
|
18733
19102
|
} : void 0,
|
|
@@ -18782,96 +19151,8 @@ function flattenRows(nodes, isOpen, childrenOf) {
|
|
|
18782
19151
|
walk(nodes);
|
|
18783
19152
|
return out;
|
|
18784
19153
|
}
|
|
18785
|
-
function sameKeys(a, b) {
|
|
18786
|
-
return a.size === b.size && [...a].every((key) => b.has(key));
|
|
18787
|
-
}
|
|
18788
|
-
function motionAllowed() {
|
|
18789
|
-
if (typeof window === "undefined" || typeof Element.prototype.animate !== "function")
|
|
18790
|
-
return false;
|
|
18791
|
-
return !window.matchMedia?.("(prefers-reduced-motion: reduce)").matches;
|
|
18792
|
-
}
|
|
18793
|
-
function startRowMotion({
|
|
18794
|
-
scrollEl,
|
|
18795
|
-
rowKeys,
|
|
18796
|
-
blockKeys,
|
|
18797
|
-
from,
|
|
18798
|
-
to
|
|
18799
|
-
}) {
|
|
18800
|
-
const spec = to === 1 ? EXPAND_MOTION : COLLAPSE_MOTION;
|
|
18801
|
-
const duration = spec.duration * Math.abs(to - from);
|
|
18802
|
-
if (duration < 1) return null;
|
|
18803
|
-
const trs = [...scrollEl.querySelectorAll("tr[data-row-index]")].map((tr) => {
|
|
18804
|
-
const key = rowKeys[Number(tr.dataset.rowIndex)];
|
|
18805
|
-
return {
|
|
18806
|
-
tr,
|
|
18807
|
-
inBlock: key !== void 0 && blockKeys.has(key),
|
|
18808
|
-
height: tr.getBoundingClientRect().height
|
|
18809
|
-
};
|
|
18810
|
-
});
|
|
18811
|
-
const viewBottom = scrollEl.scrollTop + scrollEl.clientHeight;
|
|
18812
|
-
const openAt = (offset) => from + (to - from) * offset;
|
|
18813
|
-
const translate = (shift2, s) => `translateY(${-shift2 * (1 - s)}px)`;
|
|
18814
|
-
const timing = {
|
|
18815
|
-
duration,
|
|
18816
|
-
easing: spec.easing,
|
|
18817
|
-
// 접힘은 남겨 둔 행이 빠질 때까지 끝 위치를 붙잡는다 — 놓으면 그 사이 한 프레임 제자리로 튄다
|
|
18818
|
-
fill: to === 0 ? "forwards" : "none"
|
|
18819
|
-
};
|
|
18820
|
-
const animations = [];
|
|
18821
|
-
let shift = 0;
|
|
18822
|
-
let blockHeight = 0;
|
|
18823
|
-
let y = 0;
|
|
18824
|
-
trs.forEach(({ tr, inBlock, height }, i) => {
|
|
18825
|
-
if (inBlock && !trs[i - 1]?.inBlock) {
|
|
18826
|
-
blockHeight = 0;
|
|
18827
|
-
for (let j = i; j < trs.length && trs[j].inBlock; j++) blockHeight += trs[j].height;
|
|
18828
|
-
shift += blockHeight;
|
|
18829
|
-
y = 0;
|
|
18830
|
-
}
|
|
18831
|
-
if (shift === 0 || tr.offsetTop - shift > viewBottom) return;
|
|
18832
|
-
if (!inBlock) {
|
|
18833
|
-
animations.push(
|
|
18834
|
-
tr.animate(
|
|
18835
|
-
[{ transform: translate(shift, from) }, { transform: translate(shift, to) }],
|
|
18836
|
-
timing
|
|
18837
|
-
)
|
|
18838
|
-
);
|
|
18839
|
-
return;
|
|
18840
|
-
}
|
|
18841
|
-
const rowY = y;
|
|
18842
|
-
const clipAt = (s) => Math.min(height, Math.max(0, blockHeight * (1 - s) - rowY));
|
|
18843
|
-
const bends = [1 - rowY / blockHeight, 1 - (rowY + height) / blockHeight].map((s) => (s - from) / (to - from)).filter((offset) => offset > 0 && offset < 1);
|
|
18844
|
-
const offsets = [0, ...bends, 1].sort((a, b) => a - b);
|
|
18845
|
-
animations.push(
|
|
18846
|
-
tr.animate(
|
|
18847
|
-
offsets.map((offset) => ({
|
|
18848
|
-
offset,
|
|
18849
|
-
transform: translate(shift, openAt(offset)),
|
|
18850
|
-
clipPath: `inset(${clipAt(openAt(offset))}px 0 0 0)`
|
|
18851
|
-
})),
|
|
18852
|
-
timing
|
|
18853
|
-
)
|
|
18854
|
-
);
|
|
18855
|
-
y += height;
|
|
18856
|
-
});
|
|
18857
|
-
if (animations.length === 0) return null;
|
|
18858
|
-
return {
|
|
18859
|
-
keys: blockKeys,
|
|
18860
|
-
to,
|
|
18861
|
-
done: false,
|
|
18862
|
-
finished: Promise.all(animations.map((animation) => animation.finished)).then(
|
|
18863
|
-
() => true,
|
|
18864
|
-
() => false
|
|
18865
|
-
),
|
|
18866
|
-
openness: () => {
|
|
18867
|
-
const progress = animations[0].effect?.getComputedTiming().progress;
|
|
18868
|
-
return progress == null ? to : openAt(progress);
|
|
18869
|
-
},
|
|
18870
|
-
cancel: () => animations.forEach((animation) => animation.cancel())
|
|
18871
|
-
};
|
|
18872
|
-
}
|
|
18873
19154
|
var OWN_CONTROL_SELECTOR = 'a, button, input, select, textarea, label, [role="button"], [role="checkbox"], [contenteditable="true"]';
|
|
18874
|
-
function
|
|
19155
|
+
function isOwnControl2(target, cell) {
|
|
18875
19156
|
if (!(target instanceof Element)) return false;
|
|
18876
19157
|
if (target.closest("[data-tree-toggle]")) return false;
|
|
18877
19158
|
const control = target.closest(OWN_CONTROL_SELECTOR);
|