sellmate-design-system-react 9.0.0-beta.92 → 9.0.0-beta.93
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 +20 -2
- package/dist/components/SBarChart/README.md +1 -0
- package/dist/components/SBarChart/SBarChart.d.ts +7 -0
- package/dist/components/SBarcodeInput/README.md +2 -0
- package/dist/components/SCheckbox/README.md +2 -0
- package/dist/components/SGhostButton/README.md +2 -0
- package/dist/components/SIcon/README.md +2 -0
- package/dist/components/SInput/README.md +2 -0
- package/dist/components/SLineChart/README.md +1 -0
- package/dist/components/SLineChart/SLineChart.d.ts +5 -0
- package/dist/components/SLineChart/lineChart.config.d.ts +1 -2
- package/dist/components/SMeatballButton/README.md +2 -0
- package/dist/components/SNumberInput/README.md +2 -0
- package/dist/components/SSelect/README.md +8 -3
- package/dist/components/SSelect/SSelect.d.ts +17 -3
- package/dist/components/SSelect/select.config.d.ts +34 -4
- package/dist/components/STable/README.md +1 -1
- package/dist/components/STable/STable.d.ts +3 -0
- package/dist/components/STag/README.md +8 -0
- package/dist/components/STreeTable/STreeTable.examples.d.ts +7 -0
- package/dist/index.cjs +1126 -671
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1126 -671
- package/dist/index.js.map +1 -1
- package/dist/lib/chart/cartesian.d.ts +1 -3
- package/dist/lib/chart/category-labels.d.ts +37 -0
- package/dist/lib/chart/use-element-size.d.ts +4 -1
- package/dist/lib/tree-row.d.ts +74 -0
- package/dist/llms-full.txt +40 -6
- package/dist/llms.txt +20 -2
- package/dist/styles.css +9 -8
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -7711,32 +7711,47 @@ var TREE_LABEL_TYPOGRAPHY = {
|
|
|
7711
7711
|
rowHeight: "h-[32px]"
|
|
7712
7712
|
}
|
|
7713
7713
|
};
|
|
7714
|
-
|
|
7715
|
-
|
|
7716
|
-
|
|
7717
|
-
|
|
7718
|
-
|
|
7719
|
-
|
|
7714
|
+
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)";
|
|
7715
|
+
var TREE_ROW_BORDER_CLASS = "border-b border-(--cmp-tree-item-border-default)";
|
|
7716
|
+
var TREE_ROW_ACTIVE_CLASS = "bg-(--cmp-tree-item-bg-hover)";
|
|
7717
|
+
var TREE_CONTENT_CLASS = "box-border flex min-w-0 flex-1 items-center gap-(--cmp-tree-item-contentGap)";
|
|
7718
|
+
function treeLabelClass(size, {
|
|
7719
|
+
emphasized,
|
|
7720
|
+
disabled,
|
|
7721
|
+
selectable
|
|
7722
|
+
}) {
|
|
7723
|
+
const typography = TREE_LABEL_TYPOGRAPHY[size];
|
|
7724
|
+
return cn(
|
|
7725
|
+
"block min-w-0 flex-1 truncate py-(--cmp-tree-item-paddingY) text-(--cmp-tree-item-text-default)",
|
|
7726
|
+
emphasized ? typography.selected : typography.default,
|
|
7727
|
+
disabled && "opacity-60",
|
|
7728
|
+
disabled ? "cursor-not-allowed" : selectable ? "cursor-pointer" : void 0
|
|
7729
|
+
);
|
|
7720
7730
|
}
|
|
7721
|
-
function
|
|
7722
|
-
|
|
7731
|
+
function isRowControlClick(event) {
|
|
7732
|
+
const control = event.target.closest("[data-tree-control]");
|
|
7733
|
+
return control != null && event.currentTarget.contains(control);
|
|
7723
7734
|
}
|
|
7724
|
-
|
|
7725
|
-
|
|
7726
|
-
|
|
7727
|
-
|
|
7728
|
-
|
|
7729
|
-
|
|
7730
|
-
|
|
7731
|
-
|
|
7732
|
-
|
|
7733
|
-
|
|
7735
|
+
function TreeCaret({
|
|
7736
|
+
expanded,
|
|
7737
|
+
onToggle
|
|
7738
|
+
}) {
|
|
7739
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
7740
|
+
SGhostButton,
|
|
7741
|
+
{
|
|
7742
|
+
icon: expanded ? "caretDown" : "caretRight",
|
|
7743
|
+
size: "xxs",
|
|
7744
|
+
ariaLabel: expanded ? "collapse tree item" : "expand tree item",
|
|
7745
|
+
ariaExpanded: expanded,
|
|
7746
|
+
onClick: (event) => {
|
|
7747
|
+
event.stopPropagation();
|
|
7748
|
+
onToggle(event);
|
|
7749
|
+
}
|
|
7750
|
+
}
|
|
7734
7751
|
);
|
|
7735
|
-
|
|
7736
|
-
|
|
7737
|
-
|
|
7738
|
-
indeterminate: childStates.some((state2) => state2.indeterminate) || selectedCount > 0 && selectedCount < childStates.length
|
|
7739
|
-
};
|
|
7752
|
+
}
|
|
7753
|
+
function TreeCaretSpacer() {
|
|
7754
|
+
return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "size-(--cmp-ghostButton-xxs-size) shrink-0", "aria-hidden": "true" });
|
|
7740
7755
|
}
|
|
7741
7756
|
function TreeConnector({
|
|
7742
7757
|
guideline,
|
|
@@ -7787,6 +7802,83 @@ function TreeConnector({
|
|
|
7787
7802
|
] })
|
|
7788
7803
|
] });
|
|
7789
7804
|
}
|
|
7805
|
+
function TreeAllRow({
|
|
7806
|
+
label = "\uC804\uCCB4",
|
|
7807
|
+
selected,
|
|
7808
|
+
indeterminate = false,
|
|
7809
|
+
size = "sm",
|
|
7810
|
+
border = true,
|
|
7811
|
+
active = false,
|
|
7812
|
+
tabIndex,
|
|
7813
|
+
onKeyDown,
|
|
7814
|
+
onToggle,
|
|
7815
|
+
className,
|
|
7816
|
+
...rest
|
|
7817
|
+
}) {
|
|
7818
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
7819
|
+
"div",
|
|
7820
|
+
{
|
|
7821
|
+
role: "treeitem",
|
|
7822
|
+
"aria-level": 1,
|
|
7823
|
+
"aria-selected": selected,
|
|
7824
|
+
tabIndex,
|
|
7825
|
+
onKeyDown,
|
|
7826
|
+
onClick: (event) => {
|
|
7827
|
+
if (!isRowControlClick(event)) onToggle(event);
|
|
7828
|
+
},
|
|
7829
|
+
className: cn(
|
|
7830
|
+
TREE_ROW_CLASS,
|
|
7831
|
+
"cursor-pointer",
|
|
7832
|
+
TREE_LABEL_TYPOGRAPHY[size].rowHeight,
|
|
7833
|
+
border && TREE_ROW_BORDER_CLASS,
|
|
7834
|
+
active && TREE_ROW_ACTIVE_CLASS,
|
|
7835
|
+
className
|
|
7836
|
+
),
|
|
7837
|
+
...rest,
|
|
7838
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: TREE_CONTENT_CLASS, children: [
|
|
7839
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { "data-tree-control": true, className: "flex shrink-0", children: /* @__PURE__ */ jsxRuntime.jsx(SCheckbox, { value: indeterminate ? null : selected, onValueChange: () => onToggle() }) }),
|
|
7840
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
7841
|
+
"span",
|
|
7842
|
+
{
|
|
7843
|
+
className: treeLabelClass(size, {
|
|
7844
|
+
emphasized: selected || indeterminate,
|
|
7845
|
+
disabled: false,
|
|
7846
|
+
selectable: false
|
|
7847
|
+
}),
|
|
7848
|
+
children: label
|
|
7849
|
+
}
|
|
7850
|
+
)
|
|
7851
|
+
] })
|
|
7852
|
+
}
|
|
7853
|
+
);
|
|
7854
|
+
}
|
|
7855
|
+
function collectIds(nodes) {
|
|
7856
|
+
return nodes.flatMap((node) => [node.id, ...collectIds(node.children ?? [])]);
|
|
7857
|
+
}
|
|
7858
|
+
function collectSelectableIds(node) {
|
|
7859
|
+
if (node.disabled) return [];
|
|
7860
|
+
return [node.id, ...(node.children ?? []).flatMap(collectSelectableIds)];
|
|
7861
|
+
}
|
|
7862
|
+
function toSelectedSet(value) {
|
|
7863
|
+
return new Set(value ?? []);
|
|
7864
|
+
}
|
|
7865
|
+
var TREE_ALL_NODE = { id: "__all__", label: "\uC804\uCCB4" };
|
|
7866
|
+
function getSelectionState(node, selectedSet) {
|
|
7867
|
+
const selectableChildren = (node.children ?? []).filter(
|
|
7868
|
+
(child) => collectSelectableIds(child).length > 0
|
|
7869
|
+
);
|
|
7870
|
+
if (selectableChildren.length === 0) {
|
|
7871
|
+
return { selected: selectedSet.has(node.id), indeterminate: false };
|
|
7872
|
+
}
|
|
7873
|
+
const childStates = selectableChildren.map(
|
|
7874
|
+
(child) => getSelectionState(child, selectedSet)
|
|
7875
|
+
);
|
|
7876
|
+
const selectedCount = childStates.filter((state2) => state2.selected).length;
|
|
7877
|
+
return {
|
|
7878
|
+
selected: selectedCount === childStates.length,
|
|
7879
|
+
indeterminate: childStates.some((state2) => state2.indeterminate) || selectedCount > 0 && selectedCount < childStates.length
|
|
7880
|
+
};
|
|
7881
|
+
}
|
|
7790
7882
|
function TreeBranch({ expanded, children }) {
|
|
7791
7883
|
const branchRef = useInertRef(!expanded);
|
|
7792
7884
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -7802,10 +7894,6 @@ function TreeBranch({ expanded, children }) {
|
|
|
7802
7894
|
}
|
|
7803
7895
|
);
|
|
7804
7896
|
}
|
|
7805
|
-
function isRowControlClick(event) {
|
|
7806
|
-
const control = event.target.closest("[data-tree-control]");
|
|
7807
|
-
return control != null && event.currentTarget.contains(control);
|
|
7808
|
-
}
|
|
7809
7897
|
function STreeAllItem({
|
|
7810
7898
|
selected,
|
|
7811
7899
|
indeterminate,
|
|
@@ -7816,39 +7904,17 @@ function STreeAllItem({
|
|
|
7816
7904
|
onKeyDown,
|
|
7817
7905
|
onSelect
|
|
7818
7906
|
}) {
|
|
7819
|
-
const checkboxValue = indeterminate ? null : selected;
|
|
7820
|
-
const typography = TREE_LABEL_TYPOGRAPHY[size];
|
|
7821
|
-
const labelTypo = selected || indeterminate ? typography.selected : typography.default;
|
|
7822
7907
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
7823
|
-
|
|
7908
|
+
TreeAllRow,
|
|
7824
7909
|
{
|
|
7825
|
-
role: "treeitem",
|
|
7826
|
-
"aria-level": 1,
|
|
7827
|
-
"aria-selected": selected,
|
|
7828
7910
|
"data-tree-row": rowId,
|
|
7829
7911
|
tabIndex,
|
|
7830
7912
|
onKeyDown,
|
|
7831
|
-
|
|
7832
|
-
|
|
7833
|
-
|
|
7834
|
-
|
|
7835
|
-
|
|
7836
|
-
typography.rowHeight,
|
|
7837
|
-
border && "border-b border-(--cmp-tree-item-border-default)"
|
|
7838
|
-
),
|
|
7839
|
-
children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "box-border flex min-w-0 flex-1 items-center gap-(--cmp-tree-item-contentGap)", children: [
|
|
7840
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { "data-tree-control": true, className: "flex shrink-0", children: /* @__PURE__ */ jsxRuntime.jsx(SCheckbox, { value: checkboxValue, onValueChange: () => onSelect() }) }),
|
|
7841
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
7842
|
-
"span",
|
|
7843
|
-
{
|
|
7844
|
-
className: cn(
|
|
7845
|
-
"block min-w-0 flex-1 truncate py-(--cmp-tree-item-paddingY) text-(--cmp-tree-item-text-default)",
|
|
7846
|
-
labelTypo
|
|
7847
|
-
),
|
|
7848
|
-
children: "\uC804\uCCB4"
|
|
7849
|
-
}
|
|
7850
|
-
)
|
|
7851
|
-
] })
|
|
7913
|
+
selected,
|
|
7914
|
+
indeterminate,
|
|
7915
|
+
border,
|
|
7916
|
+
size,
|
|
7917
|
+
onToggle: onSelect
|
|
7852
7918
|
}
|
|
7853
7919
|
);
|
|
7854
7920
|
}
|
|
@@ -7889,8 +7955,6 @@ var STreeItemImpl = /* @__PURE__ */ react.forwardRef(function STreeItem({
|
|
|
7889
7955
|
const leadingNode = typeof leading === "function" ? leading(renderState) : leading;
|
|
7890
7956
|
const trailingNode = typeof trailing === "function" ? trailing(renderState) : trailing;
|
|
7891
7957
|
const checkboxValue = indeterminate ? null : selected;
|
|
7892
|
-
const typography = TREE_LABEL_TYPOGRAPHY[size];
|
|
7893
|
-
const labelTypo = selected || indeterminate ? typography.selected : typography.default;
|
|
7894
7958
|
const handleSelect = (event) => {
|
|
7895
7959
|
if (node.disabled) return;
|
|
7896
7960
|
onTreeSelect?.(node, event);
|
|
@@ -7916,10 +7980,9 @@ var STreeItemImpl = /* @__PURE__ */ react.forwardRef(function STreeItem({
|
|
|
7916
7980
|
onKeyDown,
|
|
7917
7981
|
onClick: handleRowClick,
|
|
7918
7982
|
className: cn(
|
|
7919
|
-
|
|
7920
|
-
|
|
7921
|
-
border &&
|
|
7922
|
-
"hover:bg-(--cmp-tree-item-bg-hover)",
|
|
7983
|
+
TREE_ROW_CLASS,
|
|
7984
|
+
TREE_LABEL_TYPOGRAPHY[size].rowHeight,
|
|
7985
|
+
border && TREE_ROW_BORDER_CLASS,
|
|
7923
7986
|
hasChildren && "cursor-pointer",
|
|
7924
7987
|
className
|
|
7925
7988
|
),
|
|
@@ -7936,25 +7999,8 @@ var STreeItemImpl = /* @__PURE__ */ react.forwardRef(function STreeItem({
|
|
|
7936
7999
|
ancestorLast
|
|
7937
8000
|
}
|
|
7938
8001
|
),
|
|
7939
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className:
|
|
7940
|
-
hasChildren ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
7941
|
-
SGhostButton,
|
|
7942
|
-
{
|
|
7943
|
-
icon: expanded ? "caretDown" : "caretRight",
|
|
7944
|
-
size: "xxs",
|
|
7945
|
-
ariaLabel: expanded ? "collapse tree item" : "expand tree item",
|
|
7946
|
-
ariaExpanded: expanded,
|
|
7947
|
-
onClick: (event) => {
|
|
7948
|
-
event.stopPropagation();
|
|
7949
|
-
onToggle?.(node, event);
|
|
7950
|
-
}
|
|
7951
|
-
}
|
|
7952
|
-
) : (
|
|
7953
|
-
// 자식이 없는 줄은 펼침 버튼 자리를 빈 칸으로 남겨 라벨의 시작점을 맞춘다.
|
|
7954
|
-
// 크기는 자리를 대신하는 그 버튼(`SGhostButton size="xxs"`)의 토큰을 그대로 쓴다 —
|
|
7955
|
-
// 버튼 크기가 바뀌면 빈 칸도 같이 따라가야 줄이 어긋나지 않는다.
|
|
7956
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "size-(--cmp-ghostButton-xxs-size) shrink-0", "aria-hidden": "true" })
|
|
7957
|
-
),
|
|
8002
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: TREE_CONTENT_CLASS, children: [
|
|
8003
|
+
hasChildren ? /* @__PURE__ */ jsxRuntime.jsx(TreeCaret, { expanded, onToggle: (event) => onToggle?.(node, event) }) : /* @__PURE__ */ jsxRuntime.jsx(TreeCaretSpacer, {}),
|
|
7958
8004
|
leadingNode,
|
|
7959
8005
|
selectable && /* @__PURE__ */ jsxRuntime.jsx(
|
|
7960
8006
|
"span",
|
|
@@ -7976,12 +8022,11 @@ var STreeItemImpl = /* @__PURE__ */ react.forwardRef(function STreeItem({
|
|
|
7976
8022
|
"span",
|
|
7977
8023
|
{
|
|
7978
8024
|
"data-tree-label": true,
|
|
7979
|
-
className:
|
|
7980
|
-
|
|
7981
|
-
|
|
7982
|
-
|
|
7983
|
-
|
|
7984
|
-
),
|
|
8025
|
+
className: treeLabelClass(size, {
|
|
8026
|
+
emphasized: selected || indeterminate,
|
|
8027
|
+
disabled: Boolean(node.disabled),
|
|
8028
|
+
selectable
|
|
8029
|
+
}),
|
|
7985
8030
|
children: node.label
|
|
7986
8031
|
}
|
|
7987
8032
|
),
|
|
@@ -15773,13 +15818,13 @@ var SSearchInput = /* @__PURE__ */ react.forwardRef(function SSearchInput2({
|
|
|
15773
15818
|
});
|
|
15774
15819
|
|
|
15775
15820
|
// src/components/SSelect/select.config.ts
|
|
15776
|
-
function flattenOptions(options, depth = 1) {
|
|
15821
|
+
function flattenOptions(options, depth = 1, inheritDisabled = true) {
|
|
15777
15822
|
const out = [];
|
|
15778
15823
|
for (const o of options) {
|
|
15779
15824
|
const hasChildren = Array.isArray(o.children) && o.children.length > 0;
|
|
15780
15825
|
if (hasChildren) {
|
|
15781
|
-
const childFlat = flattenOptions(o.children, depth + 1);
|
|
15782
|
-
const allChildrenDisabled = childFlat.every((c) => c.disabled);
|
|
15826
|
+
const childFlat = flattenOptions(o.children, depth + 1, inheritDisabled);
|
|
15827
|
+
const allChildrenDisabled = inheritDisabled && childFlat.every((c) => c.disabled);
|
|
15783
15828
|
out.push({
|
|
15784
15829
|
value: String(o.value),
|
|
15785
15830
|
label: o.label,
|
|
@@ -15813,12 +15858,71 @@ function collectGroupLeafValues(flat, groupIndex) {
|
|
|
15813
15858
|
}
|
|
15814
15859
|
return values;
|
|
15815
15860
|
}
|
|
15861
|
+
function visibleTreeRows(rows, isExpanded) {
|
|
15862
|
+
const out = [];
|
|
15863
|
+
const sourceIndex = [];
|
|
15864
|
+
let hiddenBelow = null;
|
|
15865
|
+
rows.forEach((row, i) => {
|
|
15866
|
+
if (hiddenBelow != null && row.depth > hiddenBelow) return;
|
|
15867
|
+
hiddenBelow = null;
|
|
15868
|
+
out.push(row);
|
|
15869
|
+
sourceIndex.push(i);
|
|
15870
|
+
if (row.isGroup && !isExpanded(row)) hiddenBelow = row.depth;
|
|
15871
|
+
});
|
|
15872
|
+
return { rows: out, sourceIndex };
|
|
15873
|
+
}
|
|
15874
|
+
function treeLineInfo(rows) {
|
|
15875
|
+
const isLast = new Array(rows.length);
|
|
15876
|
+
const seen = [];
|
|
15877
|
+
for (let i = rows.length - 1; i >= 0; i--) {
|
|
15878
|
+
const depth = rows[i].depth;
|
|
15879
|
+
isLast[i] = !seen[depth];
|
|
15880
|
+
seen[depth] = true;
|
|
15881
|
+
seen.length = depth + 1;
|
|
15882
|
+
}
|
|
15883
|
+
const ancestors = [];
|
|
15884
|
+
const parents = [];
|
|
15885
|
+
const parentOf = new Array(rows.length);
|
|
15886
|
+
const posInSet = new Array(rows.length);
|
|
15887
|
+
const siblingCount = /* @__PURE__ */ new Map();
|
|
15888
|
+
rows.forEach((row, i) => {
|
|
15889
|
+
const parent = row.depth > 1 ? parents[row.depth - 2] ?? -1 : -1;
|
|
15890
|
+
parentOf[i] = parent;
|
|
15891
|
+
const count = (siblingCount.get(parent) ?? 0) + 1;
|
|
15892
|
+
siblingCount.set(parent, count);
|
|
15893
|
+
posInSet[i] = count;
|
|
15894
|
+
parents.length = row.depth - 1;
|
|
15895
|
+
parents[row.depth - 1] = i;
|
|
15896
|
+
});
|
|
15897
|
+
return rows.map((row, i) => {
|
|
15898
|
+
ancestors.length = row.depth - 1;
|
|
15899
|
+
const ancestorLast = ancestors.slice();
|
|
15900
|
+
ancestors[row.depth - 1] = isLast[i];
|
|
15901
|
+
return {
|
|
15902
|
+
isLast: isLast[i],
|
|
15903
|
+
ancestorLast,
|
|
15904
|
+
posInSet: posInSet[i],
|
|
15905
|
+
setSize: siblingCount.get(parentOf[i]) ?? 1
|
|
15906
|
+
};
|
|
15907
|
+
});
|
|
15908
|
+
}
|
|
15909
|
+
function ancestorGroupValues(flat, selected) {
|
|
15910
|
+
const out = /* @__PURE__ */ new Set();
|
|
15911
|
+
const stack = [];
|
|
15912
|
+
for (const row of flat) {
|
|
15913
|
+
while (stack.length > 0 && stack[stack.length - 1].depth >= row.depth) stack.pop();
|
|
15914
|
+
if (selected.has(row.value)) for (const group of stack) out.add(group.value);
|
|
15915
|
+
if (row.isGroup) stack.push(row);
|
|
15916
|
+
}
|
|
15917
|
+
return out;
|
|
15918
|
+
}
|
|
15816
15919
|
var isHtmlLabel = (label) => /<[a-z][\s\S]*>/i.test(label);
|
|
15817
15920
|
var extractText = (html) => html.replace(/<[^>]*>/g, "");
|
|
15818
15921
|
var OPTION_ROW_HEIGHT = 28;
|
|
15819
15922
|
var DEFAULT_VIRTUAL_BUFFER = 10;
|
|
15820
15923
|
var DEFAULT_REACH_END_THRESHOLD = 10;
|
|
15821
15924
|
var DEFAULT_SEARCH_DEBOUNCE = 300;
|
|
15925
|
+
var NO_SEARCH_COLLAPSED = /* @__PURE__ */ new Set();
|
|
15822
15926
|
var SEARCH_THRESHOLD = 11;
|
|
15823
15927
|
function isListSized(el) {
|
|
15824
15928
|
return el.clientHeight > 0 && getComputedStyle(el).maxHeight !== "none";
|
|
@@ -15873,8 +15977,9 @@ var SSelectImpl = /* @__PURE__ */ react.forwardRef(function SSelect({
|
|
|
15873
15977
|
className,
|
|
15874
15978
|
style
|
|
15875
15979
|
}, ref) {
|
|
15876
|
-
const multi = type === "multi" || type === "multi_depth";
|
|
15980
|
+
const multi = type === "multi" || type === "multi_depth" || type === "multi_tree";
|
|
15877
15981
|
const isDepth = type === "default_depth" || type === "multi_depth";
|
|
15982
|
+
const isTree = type === "tree" || type === "multi_tree";
|
|
15878
15983
|
const pagingEnabled = onReachEnd != null;
|
|
15879
15984
|
const partialOptions = pagingEnabled || serverSearch;
|
|
15880
15985
|
const portalContainer = usePortalContainer();
|
|
@@ -15884,6 +15989,8 @@ var SSelectImpl = /* @__PURE__ */ react.forwardRef(function SSelect({
|
|
|
15884
15989
|
const [open, setOpen] = react.useState(false);
|
|
15885
15990
|
const [pendingOpen, setPendingOpen] = react.useState(false);
|
|
15886
15991
|
const [focusedIndex, setFocusedIndex] = react.useState(-1);
|
|
15992
|
+
const [expanded, setExpanded] = react.useState(() => /* @__PURE__ */ new Set());
|
|
15993
|
+
const [searchCollapsed, setSearchCollapsed] = react.useState(NO_SEARCH_COLLAPSED);
|
|
15887
15994
|
const baseId = react.useId();
|
|
15888
15995
|
const listboxId = `${baseId}-listbox`;
|
|
15889
15996
|
const activeOptionId = `${baseId}-active-option`;
|
|
@@ -15907,10 +16014,11 @@ var SSelectImpl = /* @__PURE__ */ react.forwardRef(function SSelect({
|
|
|
15907
16014
|
const alignSelectedTopRef = react.useRef(false);
|
|
15908
16015
|
const selectedAnchorRef = react.useRef(-1);
|
|
15909
16016
|
const rangeAnchorRef = react.useRef(null);
|
|
15910
|
-
const
|
|
16017
|
+
const groupIsValue = type === "tree";
|
|
16018
|
+
const flat = react.useMemo(() => flattenOptions(options, 1, !groupIsValue), [groupIsValue, options]);
|
|
15911
16019
|
const selectable = react.useMemo(() => selectableOptions(flat), [flat]);
|
|
15912
16020
|
const hasLeaf = react.useMemo(() => flat.some((o) => !o.isGroup), [flat]);
|
|
15913
|
-
const allOptionsDisabled = hasLeaf && selectable.length === 0;
|
|
16021
|
+
const allOptionsDisabled = groupIsValue ? flat.length > 0 && flat.every((o) => o.disabled) : hasLeaf && selectable.length === 0;
|
|
15914
16022
|
const effectiveDisabled = disabled || allOptionsDisabled;
|
|
15915
16023
|
const searchVisible = showSearch && (partialOptions || selectable.length >= SEARCH_THRESHOLD);
|
|
15916
16024
|
const labelByValue = react.useMemo(() => new Map(flat.map((o) => [o.value, o.label])), [flat]);
|
|
@@ -15953,6 +16061,10 @@ var SSelectImpl = /* @__PURE__ */ react.forwardRef(function SSelect({
|
|
|
15953
16061
|
if (next) {
|
|
15954
16062
|
dismissedByPointerRef.current = false;
|
|
15955
16063
|
alignSelectedTopRef.current = true;
|
|
16064
|
+
if (isTree) {
|
|
16065
|
+
setExpanded(ancestorGroupValues(flat, new Set(selectedRef.current)));
|
|
16066
|
+
setSearchCollapsed(NO_SEARCH_COLLAPSED);
|
|
16067
|
+
}
|
|
15956
16068
|
} else {
|
|
15957
16069
|
setPendingOpen(false);
|
|
15958
16070
|
setSearch("");
|
|
@@ -15995,7 +16107,7 @@ var SSelectImpl = /* @__PURE__ */ react.forwardRef(function SSelect({
|
|
|
15995
16107
|
},
|
|
15996
16108
|
resetValidation: () => setRuleError("")
|
|
15997
16109
|
});
|
|
15998
|
-
const
|
|
16110
|
+
const searched = react.useMemo(() => {
|
|
15999
16111
|
if (serverSearch || !searchVisible || search.trim() === "") return flat;
|
|
16000
16112
|
const q = search.trim().toLowerCase();
|
|
16001
16113
|
return filterDepthRows(
|
|
@@ -16003,23 +16115,53 @@ var SSelectImpl = /* @__PURE__ */ react.forwardRef(function SSelect({
|
|
|
16003
16115
|
(o) => (isHtmlLabel(o.label) ? extractText(o.label) : o.label).toLowerCase().includes(q)
|
|
16004
16116
|
);
|
|
16005
16117
|
}, [flat, search, searchVisible, serverSearch]);
|
|
16118
|
+
const treeSearching = isTree && search.trim() !== "";
|
|
16119
|
+
const isGroupExpanded = react.useCallback(
|
|
16120
|
+
(groupValue) => treeSearching ? !searchCollapsed.has(groupValue) : expanded.has(groupValue),
|
|
16121
|
+
[expanded, searchCollapsed, treeSearching]
|
|
16122
|
+
);
|
|
16123
|
+
const treeRows = react.useMemo(
|
|
16124
|
+
() => isTree ? visibleTreeRows(searched, (row) => isGroupExpanded(row.value)) : null,
|
|
16125
|
+
[isGroupExpanded, isTree, searched]
|
|
16126
|
+
);
|
|
16127
|
+
const filtered = treeRows?.rows ?? searched;
|
|
16128
|
+
const treeLines = react.useMemo(() => treeRows ? treeLineInfo(treeRows.rows) : [], [treeRows]);
|
|
16006
16129
|
const allValues = selectable.map((o) => o.value);
|
|
16007
16130
|
const allSelected = !partialOptions && allValues.length > 0 && allValues.every((v) => selectedSet.has(v));
|
|
16008
16131
|
const selectAllVisible = multi && showSelectAll && !partialOptions && allValues.length > 0;
|
|
16009
|
-
const hasItems = filtered.some((o) => !o.isGroup);
|
|
16132
|
+
const hasItems = isTree ? filtered.length > 0 : filtered.some((o) => !o.isGroup);
|
|
16010
16133
|
const groupLeafValues = react.useMemo(() => {
|
|
16011
16134
|
const map = /* @__PURE__ */ new Map();
|
|
16012
16135
|
filtered.forEach((o, i) => {
|
|
16013
|
-
if (o.isGroup)
|
|
16136
|
+
if (!o.isGroup) return;
|
|
16137
|
+
map.set(
|
|
16138
|
+
i,
|
|
16139
|
+
treeRows ? collectGroupLeafValues(searched, treeRows.sourceIndex[i]) : collectGroupLeafValues(filtered, i)
|
|
16140
|
+
);
|
|
16014
16141
|
});
|
|
16015
16142
|
return map;
|
|
16016
|
-
}, [filtered]);
|
|
16143
|
+
}, [filtered, searched, treeRows]);
|
|
16017
16144
|
const navigableItems = react.useMemo(() => {
|
|
16018
16145
|
const items = [];
|
|
16019
16146
|
if (selectAllVisible) {
|
|
16020
16147
|
items.push({ key: "__select_all__", isSelectAll: true });
|
|
16021
16148
|
}
|
|
16022
16149
|
filtered.forEach((o, i) => {
|
|
16150
|
+
if (o.isGroup && isTree) {
|
|
16151
|
+
if (multi) {
|
|
16152
|
+
const leafValues = groupLeafValues.get(i) ?? [];
|
|
16153
|
+
items.push({
|
|
16154
|
+
key: `__group_${i}__`,
|
|
16155
|
+
isGroup: true,
|
|
16156
|
+
index: i,
|
|
16157
|
+
leafValues,
|
|
16158
|
+
disabled: o.disabled || leafValues.length === 0
|
|
16159
|
+
});
|
|
16160
|
+
} else {
|
|
16161
|
+
items.push({ key: o.value, value: o.value, index: i, disabled: o.disabled });
|
|
16162
|
+
}
|
|
16163
|
+
return;
|
|
16164
|
+
}
|
|
16023
16165
|
if (o.disabled) return;
|
|
16024
16166
|
if (o.isGroup) {
|
|
16025
16167
|
if (!multi) return;
|
|
@@ -16031,7 +16173,7 @@ var SSelectImpl = /* @__PURE__ */ react.forwardRef(function SSelect({
|
|
|
16031
16173
|
}
|
|
16032
16174
|
});
|
|
16033
16175
|
return items;
|
|
16034
|
-
}, [filtered, groupLeafValues, multi, selectAllVisible]);
|
|
16176
|
+
}, [filtered, groupLeafValues, isTree, multi, selectAllVisible]);
|
|
16035
16177
|
const selectedAnchorIndex = react.useMemo(() => {
|
|
16036
16178
|
if (selectedSet.size === 0) return -1;
|
|
16037
16179
|
return navigableItems.findIndex((item4) => item4.value != null && selectedSet.has(item4.value));
|
|
@@ -16042,7 +16184,10 @@ var SSelectImpl = /* @__PURE__ */ react.forwardRef(function SSelect({
|
|
|
16042
16184
|
const rowCount = filtered.length;
|
|
16043
16185
|
const metrics = metricsRef.current;
|
|
16044
16186
|
const lastSearchRef = react.useRef(search);
|
|
16045
|
-
|
|
16187
|
+
const lastExpansionRef = react.useRef({ expanded, searchCollapsed });
|
|
16188
|
+
const expansionChanged = lastExpansionRef.current.expanded !== expanded || lastExpansionRef.current.searchCollapsed !== searchCollapsed;
|
|
16189
|
+
lastExpansionRef.current = { expanded, searchCollapsed };
|
|
16190
|
+
if (lastSearchRef.current !== search || isTree && expansionChanged) {
|
|
16046
16191
|
lastSearchRef.current = search;
|
|
16047
16192
|
resetRowMetrics(metrics, rowCount);
|
|
16048
16193
|
measuredKeyRef.current = "";
|
|
@@ -16182,7 +16327,7 @@ var SSelectImpl = /* @__PURE__ */ react.forwardRef(function SSelect({
|
|
|
16182
16327
|
);
|
|
16183
16328
|
const fullGroupByLeaf = react.useMemo(() => {
|
|
16184
16329
|
const map = /* @__PURE__ */ new Map();
|
|
16185
|
-
if (!showChips || type !== "multi_depth") return map;
|
|
16330
|
+
if (!showChips || type !== "multi_depth" && type !== "multi_tree") return map;
|
|
16186
16331
|
flat.forEach((o, i) => {
|
|
16187
16332
|
if (!o.isGroup) return;
|
|
16188
16333
|
const leafValues = collectGroupLeafValues(flat, i);
|
|
@@ -16260,6 +16405,21 @@ var SSelectImpl = /* @__PURE__ */ react.forwardRef(function SSelect({
|
|
|
16260
16405
|
rangeAnchorRef.current = null;
|
|
16261
16406
|
emit3(allSelected ? [] : allValues);
|
|
16262
16407
|
};
|
|
16408
|
+
const handleSearchChange = (next) => {
|
|
16409
|
+
setSearch(next);
|
|
16410
|
+
setSearchCollapsed(NO_SEARCH_COLLAPSED);
|
|
16411
|
+
};
|
|
16412
|
+
const handleToggleExpand = (groupValue) => {
|
|
16413
|
+
rangeAnchorRef.current = null;
|
|
16414
|
+
const toggle = (prev) => {
|
|
16415
|
+
const next = new Set(prev);
|
|
16416
|
+
if (next.has(groupValue)) next.delete(groupValue);
|
|
16417
|
+
else next.add(groupValue);
|
|
16418
|
+
return next;
|
|
16419
|
+
};
|
|
16420
|
+
if (treeSearching) setSearchCollapsed(toggle);
|
|
16421
|
+
else setExpanded(toggle);
|
|
16422
|
+
};
|
|
16263
16423
|
const handleToggleGroup = (leafValues) => {
|
|
16264
16424
|
if (leafValues.length === 0) return;
|
|
16265
16425
|
rangeAnchorRef.current = null;
|
|
@@ -16332,6 +16492,7 @@ var SSelectImpl = /* @__PURE__ */ react.forwardRef(function SSelect({
|
|
|
16332
16492
|
const selectFocusedItem = (extend = false) => {
|
|
16333
16493
|
if (focusedIndex < 0 || focusedIndex >= navigableItems.length) return;
|
|
16334
16494
|
const focused = navigableItems[focusedIndex];
|
|
16495
|
+
if (focused.disabled) return;
|
|
16335
16496
|
if (focused.isSelectAll) {
|
|
16336
16497
|
handleSelectAll();
|
|
16337
16498
|
return;
|
|
@@ -16360,6 +16521,41 @@ var SSelectImpl = /* @__PURE__ */ react.forwardRef(function SSelect({
|
|
|
16360
16521
|
moveFocusedIndex("prev");
|
|
16361
16522
|
return;
|
|
16362
16523
|
}
|
|
16524
|
+
const input = event.target instanceof HTMLInputElement ? event.target : null;
|
|
16525
|
+
const caretAtEdge = input == null || input.selectionStart === input.selectionEnd && input.selectionStart === (event.key === "ArrowRight" ? input.value.length : 0);
|
|
16526
|
+
if (isTree && (event.key === "ArrowRight" || event.key === "ArrowLeft") && caretAtEdge) {
|
|
16527
|
+
const rowIndex = navigableItems[focusedIndex]?.index;
|
|
16528
|
+
const row = rowIndex != null ? filtered[rowIndex] : void 0;
|
|
16529
|
+
if (rowIndex == null || row == null) return;
|
|
16530
|
+
event.preventDefault();
|
|
16531
|
+
const rowExpanded = row.isGroup && isGroupExpanded(row.value);
|
|
16532
|
+
if (event.key === "ArrowRight") {
|
|
16533
|
+
if (!row.isGroup) return;
|
|
16534
|
+
if (!rowExpanded) {
|
|
16535
|
+
handleToggleExpand(row.value);
|
|
16536
|
+
return;
|
|
16537
|
+
}
|
|
16538
|
+
for (let i = rowIndex + 1; i < filtered.length && filtered[i].depth > row.depth; i++) {
|
|
16539
|
+
const child = navigableItems.findIndex((item4) => item4.index === i);
|
|
16540
|
+
if (child >= 0) {
|
|
16541
|
+
setFocusedIndex(child);
|
|
16542
|
+
break;
|
|
16543
|
+
}
|
|
16544
|
+
}
|
|
16545
|
+
return;
|
|
16546
|
+
}
|
|
16547
|
+
if (rowExpanded) {
|
|
16548
|
+
handleToggleExpand(row.value);
|
|
16549
|
+
return;
|
|
16550
|
+
}
|
|
16551
|
+
for (let i = rowIndex - 1; i >= 0; i--) {
|
|
16552
|
+
if (filtered[i].depth >= row.depth) continue;
|
|
16553
|
+
const parent = navigableItems.findIndex((item4) => item4.index === i);
|
|
16554
|
+
if (parent >= 0) setFocusedIndex(parent);
|
|
16555
|
+
return;
|
|
16556
|
+
}
|
|
16557
|
+
return;
|
|
16558
|
+
}
|
|
16363
16559
|
if (event.key === "Enter") {
|
|
16364
16560
|
event.preventDefault();
|
|
16365
16561
|
selectFocusedItem(event.shiftKey);
|
|
@@ -16404,7 +16600,7 @@ var SSelectImpl = /* @__PURE__ */ react.forwardRef(function SSelect({
|
|
|
16404
16600
|
type: "button",
|
|
16405
16601
|
disabled: effectiveDisabled,
|
|
16406
16602
|
"aria-labelledby": label ? `${fieldLabelId(triggerId)} ${triggerId}` : void 0,
|
|
16407
|
-
"aria-haspopup": "listbox",
|
|
16603
|
+
"aria-haspopup": isTree ? "tree" : "listbox",
|
|
16408
16604
|
"aria-expanded": open,
|
|
16409
16605
|
"aria-controls": open ? listboxId : void 0,
|
|
16410
16606
|
"aria-activedescendant": open && focusedIndex >= 0 ? activeOptionId : void 0,
|
|
@@ -16541,7 +16737,7 @@ var SSelectImpl = /* @__PURE__ */ react.forwardRef(function SSelect({
|
|
|
16541
16737
|
{
|
|
16542
16738
|
ref: searchInputRef,
|
|
16543
16739
|
value: search,
|
|
16544
|
-
onValueChange:
|
|
16740
|
+
onValueChange: handleSearchChange,
|
|
16545
16741
|
onKeyDown: handleKeyDown,
|
|
16546
16742
|
placeholder: "\uAC80\uC0C9",
|
|
16547
16743
|
clearable: true,
|
|
@@ -16560,8 +16756,12 @@ var SSelectImpl = /* @__PURE__ */ react.forwardRef(function SSelect({
|
|
|
16560
16756
|
indexOffset: windowStart,
|
|
16561
16757
|
topSpacer: win.top,
|
|
16562
16758
|
bottomSpacer: win.bottom,
|
|
16563
|
-
setSize: isDepth ? void 0 : rowCount,
|
|
16759
|
+
setSize: isDepth || isTree ? void 0 : rowCount,
|
|
16564
16760
|
multi,
|
|
16761
|
+
tree: isTree,
|
|
16762
|
+
isExpanded: isGroupExpanded,
|
|
16763
|
+
onToggleExpand: handleToggleExpand,
|
|
16764
|
+
treeLines,
|
|
16565
16765
|
selected,
|
|
16566
16766
|
groupLeafValues,
|
|
16567
16767
|
focusedValue: navigableItems[focusedIndex]?.key,
|
|
@@ -16615,6 +16815,10 @@ function ItemList({
|
|
|
16615
16815
|
bottomSpacer,
|
|
16616
16816
|
setSize,
|
|
16617
16817
|
multi,
|
|
16818
|
+
tree,
|
|
16819
|
+
isExpanded,
|
|
16820
|
+
onToggleExpand,
|
|
16821
|
+
treeLines,
|
|
16618
16822
|
selected,
|
|
16619
16823
|
groupLeafValues,
|
|
16620
16824
|
focusedValue,
|
|
@@ -16628,14 +16832,34 @@ function ItemList({
|
|
|
16628
16832
|
onHoverValue
|
|
16629
16833
|
}) {
|
|
16630
16834
|
const selectedSet = react.useMemo(() => new Set(selected), [selected]);
|
|
16835
|
+
const checkboxShiftRef = react.useRef(false);
|
|
16631
16836
|
const itemBase = cn(
|
|
16632
16837
|
"group/select-item flex w-full cursor-pointer select-none items-center gap-[8px] border-none bg-transparent text-left text-[12px] leading-[20px] outline-none",
|
|
16633
16838
|
"text-[color:var(--cmp-listBoxItem-content-default)]",
|
|
16634
16839
|
"disabled:cursor-not-allowed disabled:text-[color:var(--cmp-listBoxItem-content-disabled)]"
|
|
16635
16840
|
);
|
|
16636
16841
|
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)]";
|
|
16637
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", { id: listboxId, role: "listbox", "aria-multiselectable": multi || void 0, children: [
|
|
16638
|
-
showSelectAll &&
|
|
16842
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { id: listboxId, role: tree ? "tree" : "listbox", "aria-multiselectable": multi || void 0, children: [
|
|
16843
|
+
showSelectAll && (tree ? (
|
|
16844
|
+
// 트리의 전체선택 줄은 STree 의 "전체" 줄 그대로다 (lib/tree-row).
|
|
16845
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
16846
|
+
TreeAllRow,
|
|
16847
|
+
{
|
|
16848
|
+
"data-select-all-row": true,
|
|
16849
|
+
id: focusedValue === "__select_all__" ? activeOptionId : void 0,
|
|
16850
|
+
"data-select-focused": focusedValue === "__select_all__" ? "true" : void 0,
|
|
16851
|
+
"data-select-active": focusedValue === "__select_all__" ? "true" : void 0,
|
|
16852
|
+
tabIndex: -1,
|
|
16853
|
+
label: selectAllLabel,
|
|
16854
|
+
selected: selectAllSelected,
|
|
16855
|
+
active: focusedValue === "__select_all__",
|
|
16856
|
+
className: "select-none",
|
|
16857
|
+
onMouseEnter: () => onHoverValue("__select_all__"),
|
|
16858
|
+
onMouseLeave: () => onHoverValue(null),
|
|
16859
|
+
onToggle: onSelectAll
|
|
16860
|
+
}
|
|
16861
|
+
)
|
|
16862
|
+
) : /* @__PURE__ */ jsxRuntime.jsxs(
|
|
16639
16863
|
"button",
|
|
16640
16864
|
{
|
|
16641
16865
|
type: "button",
|
|
@@ -16656,12 +16880,109 @@ function ItemList({
|
|
|
16656
16880
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1 truncate", children: selectAllLabel })
|
|
16657
16881
|
]
|
|
16658
16882
|
}
|
|
16659
|
-
),
|
|
16883
|
+
)),
|
|
16660
16884
|
topSpacer > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { "aria-hidden": true, style: { height: topSpacer } }),
|
|
16661
16885
|
items.map((o, windowIndex) => {
|
|
16662
16886
|
const idx = windowIndex + indexOffset;
|
|
16663
16887
|
const padLeft = o.depth === 1 ? 12 : o.depth === 2 ? 20 : 28;
|
|
16664
16888
|
const rowKey = `${o.value}-${idx}`;
|
|
16889
|
+
if (tree) {
|
|
16890
|
+
const togglesLeaves = multi && o.isGroup;
|
|
16891
|
+
const leafValues = togglesLeaves ? groupLeafValues.get(idx) ?? [] : [];
|
|
16892
|
+
const total = leafValues.length;
|
|
16893
|
+
const selectedCount = leafValues.reduce((n, v) => selectedSet.has(v) ? n + 1 : n, 0);
|
|
16894
|
+
const isSelected2 = selectedSet.has(o.value);
|
|
16895
|
+
const checkState = togglesLeaves ? total === 0 || selectedCount === 0 ? false : selectedCount === total ? true : null : isSelected2;
|
|
16896
|
+
const rowDisabled = o.disabled || togglesLeaves && total === 0;
|
|
16897
|
+
const focusKey = togglesLeaves ? `__group_${idx}__` : o.value;
|
|
16898
|
+
const focused = focusedValue === focusKey;
|
|
16899
|
+
const expanded = o.isGroup && isExpanded(o.value);
|
|
16900
|
+
const line = treeLines[idx];
|
|
16901
|
+
const toggle = () => {
|
|
16902
|
+
if (rowDisabled) return;
|
|
16903
|
+
if (togglesLeaves) onToggleGroup(leafValues);
|
|
16904
|
+
else onSelect(o.value, idx, checkboxShiftRef.current);
|
|
16905
|
+
};
|
|
16906
|
+
return (
|
|
16907
|
+
// eslint-disable-next-line jsx-a11y/click-events-have-key-events -- 키보드는 트리거(또는 검색 입력)가 aria-activedescendant 로 맡는다. 줄로 포커스를 옮기지 않는다
|
|
16908
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
16909
|
+
"div",
|
|
16910
|
+
{
|
|
16911
|
+
tabIndex: -1,
|
|
16912
|
+
"data-select-row": true,
|
|
16913
|
+
"data-index": idx,
|
|
16914
|
+
id: focused ? activeOptionId : void 0,
|
|
16915
|
+
role: "treeitem",
|
|
16916
|
+
"aria-level": o.depth,
|
|
16917
|
+
"aria-posinset": line?.posInSet,
|
|
16918
|
+
"aria-setsize": line?.setSize,
|
|
16919
|
+
"aria-expanded": o.isGroup ? expanded : void 0,
|
|
16920
|
+
"aria-selected": checkState === true,
|
|
16921
|
+
"aria-disabled": rowDisabled || void 0,
|
|
16922
|
+
"data-select-focused": focused ? "true" : void 0,
|
|
16923
|
+
"data-select-active": focused ? "true" : void 0,
|
|
16924
|
+
className: cn(
|
|
16925
|
+
TREE_ROW_CLASS,
|
|
16926
|
+
TREE_LABEL_TYPOGRAPHY.sm.rowHeight,
|
|
16927
|
+
TREE_ROW_BORDER_CLASS,
|
|
16928
|
+
"select-none",
|
|
16929
|
+
rowDisabled ? "cursor-not-allowed" : "cursor-pointer",
|
|
16930
|
+
focused && TREE_ROW_ACTIVE_CLASS
|
|
16931
|
+
),
|
|
16932
|
+
onMouseEnter: () => onHoverValue(focusKey),
|
|
16933
|
+
onMouseLeave: () => onHoverValue(null),
|
|
16934
|
+
onClick: (event) => {
|
|
16935
|
+
if (isRowControlClick(event) || rowDisabled) return;
|
|
16936
|
+
if (togglesLeaves) onToggleGroup(leafValues);
|
|
16937
|
+
else onSelect(o.value, idx, event.shiftKey);
|
|
16938
|
+
},
|
|
16939
|
+
children: [
|
|
16940
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
16941
|
+
TreeConnector,
|
|
16942
|
+
{
|
|
16943
|
+
guideline: true,
|
|
16944
|
+
size: "sm",
|
|
16945
|
+
depth: o.depth,
|
|
16946
|
+
hasChildren: o.isGroup,
|
|
16947
|
+
isLast: line?.isLast ?? true,
|
|
16948
|
+
ancestorLast: line?.ancestorLast ?? []
|
|
16949
|
+
}
|
|
16950
|
+
),
|
|
16951
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: TREE_CONTENT_CLASS, children: [
|
|
16952
|
+
o.isGroup ? (
|
|
16953
|
+
// 비활성 그룹도 접고 편다 — 그룹이 비활성이어도 아래에 고를 수 있는 자식이 있을 수 있다.
|
|
16954
|
+
/* @__PURE__ */ jsxRuntime.jsx(TreeCaret, { expanded, onToggle: () => onToggleExpand(o.value) })
|
|
16955
|
+
) : /* @__PURE__ */ jsxRuntime.jsx(TreeCaretSpacer, {}),
|
|
16956
|
+
multi && /* @__PURE__ */ jsxRuntime.jsx(
|
|
16957
|
+
"span",
|
|
16958
|
+
{
|
|
16959
|
+
"data-tree-control": true,
|
|
16960
|
+
className: "flex shrink-0",
|
|
16961
|
+
onPointerDownCapture: (event) => {
|
|
16962
|
+
checkboxShiftRef.current = event.shiftKey;
|
|
16963
|
+
},
|
|
16964
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(SCheckbox, { value: checkState, disabled: rowDisabled, onValueChange: toggle })
|
|
16965
|
+
}
|
|
16966
|
+
),
|
|
16967
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
16968
|
+
OptionLabel,
|
|
16969
|
+
{
|
|
16970
|
+
label: o.label,
|
|
16971
|
+
className: treeLabelClass("sm", {
|
|
16972
|
+
// 고른 줄은 굵게 — multi_tree 는 일부만 고른 그룹도 굵다 (STree 와 같다)
|
|
16973
|
+
emphasized: checkState !== false,
|
|
16974
|
+
disabled: rowDisabled,
|
|
16975
|
+
selectable: true
|
|
16976
|
+
})
|
|
16977
|
+
}
|
|
16978
|
+
)
|
|
16979
|
+
] })
|
|
16980
|
+
]
|
|
16981
|
+
},
|
|
16982
|
+
rowKey
|
|
16983
|
+
)
|
|
16984
|
+
);
|
|
16985
|
+
}
|
|
16665
16986
|
if (o.isGroup) {
|
|
16666
16987
|
const isDepth1 = o.depth === 1;
|
|
16667
16988
|
const isFirstRow = idx === 0 && !showSelectAll && !searchVisible;
|
|
@@ -16702,13 +17023,7 @@ function ItemList({
|
|
|
16702
17023
|
onClick: () => onToggleGroup(leafValues),
|
|
16703
17024
|
children: [
|
|
16704
17025
|
/* @__PURE__ */ jsxRuntime.jsx(CheckboxBox, { state: groupState, disabled: groupDisabled, focused: groupFocused }),
|
|
16705
|
-
|
|
16706
|
-
"span",
|
|
16707
|
-
{
|
|
16708
|
-
className: "min-w-0 truncate",
|
|
16709
|
-
dangerouslySetInnerHTML: { __html: sanitizeInlineHtml(o.label) }
|
|
16710
|
-
}
|
|
16711
|
-
) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 truncate", children: o.label }),
|
|
17026
|
+
/* @__PURE__ */ jsxRuntime.jsx(OptionLabel, { label: o.label, className: "min-w-0 truncate" }),
|
|
16712
17027
|
total > 0 && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
16713
17028
|
"span",
|
|
16714
17029
|
{
|
|
@@ -16785,13 +17100,7 @@ function ItemList({
|
|
|
16785
17100
|
focused: focusedValue === o.value
|
|
16786
17101
|
}
|
|
16787
17102
|
),
|
|
16788
|
-
|
|
16789
|
-
"span",
|
|
16790
|
-
{
|
|
16791
|
-
className: "flex-1 truncate",
|
|
16792
|
-
dangerouslySetInnerHTML: { __html: sanitizeInlineHtml(o.label) }
|
|
16793
|
-
}
|
|
16794
|
-
) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1 truncate", children: o.label })
|
|
17103
|
+
/* @__PURE__ */ jsxRuntime.jsx(OptionLabel, { label: o.label, className: "flex-1 truncate" })
|
|
16795
17104
|
]
|
|
16796
17105
|
},
|
|
16797
17106
|
rowKey
|
|
@@ -16800,8 +17109,11 @@ function ItemList({
|
|
|
16800
17109
|
bottomSpacer > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { "aria-hidden": true, style: { height: bottomSpacer } })
|
|
16801
17110
|
] });
|
|
16802
17111
|
}
|
|
17112
|
+
function OptionLabel({ label, className }) {
|
|
17113
|
+
return isHtmlLabel(label) ? /* @__PURE__ */ jsxRuntime.jsx("span", { className, dangerouslySetInnerHTML: { __html: sanitizeInlineHtml(label) } }) : /* @__PURE__ */ jsxRuntime.jsx("span", { className, children: label });
|
|
17114
|
+
}
|
|
16803
17115
|
function TriggerLabel({ label }) {
|
|
16804
|
-
return
|
|
17116
|
+
return /* @__PURE__ */ jsxRuntime.jsx(OptionLabel, { label, className: "truncate" });
|
|
16805
17117
|
}
|
|
16806
17118
|
function TriggerRemoveButton({ ariaLabel, onRemove }) {
|
|
16807
17119
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
@@ -17056,8 +17368,8 @@ var STableImpl = /* @__PURE__ */ react.forwardRef(function STable({
|
|
|
17056
17368
|
const [rowCountOverride, setRowCountOverride] = react.useState(null);
|
|
17057
17369
|
const rowCount = rowCountOverride ?? rows.length;
|
|
17058
17370
|
const scrollRef = react.useRef(null);
|
|
17059
|
-
const
|
|
17060
|
-
const [
|
|
17371
|
+
const headerRef = react.useRef(null);
|
|
17372
|
+
const [headerHeight, setHeaderHeight] = react.useState(0);
|
|
17061
17373
|
const [vsRange, setVsRange] = react.useState({ start: 0, end: 0 });
|
|
17062
17374
|
const lastReachEndRef = react.useRef(-1);
|
|
17063
17375
|
const cbRef = react.useRef({ onVirtualUpdate, onVirtualReachEnd, onScroll });
|
|
@@ -17099,17 +17411,13 @@ var STableImpl = /* @__PURE__ */ react.forwardRef(function STable({
|
|
|
17099
17411
|
);
|
|
17100
17412
|
const handleScroll = react.useCallback(() => {
|
|
17101
17413
|
const el = scrollRef.current;
|
|
17102
|
-
if (!el)
|
|
17103
|
-
|
|
17104
|
-
|
|
17105
|
-
}
|
|
17106
|
-
const header = headerScrollRef.current;
|
|
17107
|
-
if (header) header.scrollLeft = el.scrollLeft;
|
|
17108
|
-
setHeaderGutter(el.offsetWidth - el.clientWidth);
|
|
17414
|
+
if (!el) return;
|
|
17415
|
+
const headerH = headerRef.current?.offsetHeight ?? 0;
|
|
17416
|
+
setHeaderHeight(headerH);
|
|
17109
17417
|
setScrolledLeft(el.scrollLeft > 0);
|
|
17110
17418
|
setScrolledRight(el.scrollLeft + el.clientWidth < el.scrollWidth - 1);
|
|
17111
17419
|
if (virtualScroll) {
|
|
17112
|
-
const { start, end } = calcRange(el.scrollTop, el.clientHeight);
|
|
17420
|
+
const { start, end } = calcRange(el.scrollTop, el.clientHeight - headerH);
|
|
17113
17421
|
setVsRange((prev) => prev.start === start && prev.end === end ? prev : { start, end });
|
|
17114
17422
|
}
|
|
17115
17423
|
}, [virtualScroll, calcRange]);
|
|
@@ -17506,7 +17814,7 @@ var STableImpl = /* @__PURE__ */ react.forwardRef(function STable({
|
|
|
17506
17814
|
);
|
|
17507
17815
|
};
|
|
17508
17816
|
const bodyScrollClass = cn(
|
|
17509
|
-
"min-h-0 flex-1 overflow-auto
|
|
17817
|
+
"min-h-0 flex-1 overflow-auto",
|
|
17510
17818
|
SCROLLBAR_CLASS,
|
|
17511
17819
|
SCROLLBAR_TRACK_BG_CLASS,
|
|
17512
17820
|
SCROLLBAR_TRACK_BORDER_CLASS
|
|
@@ -17523,28 +17831,226 @@ var STableImpl = /* @__PURE__ */ react.forwardRef(function STable({
|
|
|
17523
17831
|
selectable && /* @__PURE__ */ jsxRuntime.jsx("col", { style: { width: SELECTABLE_COLUMN_WIDTH } }),
|
|
17524
17832
|
visibleCols.map((col, i) => /* @__PURE__ */ jsxRuntime.jsx("col", { style: col.autoWidth ? void 0 : { width: columnWidths[i] } }, col.name))
|
|
17525
17833
|
] });
|
|
17526
|
-
|
|
17527
|
-
"
|
|
17834
|
+
const headerTable = /* @__PURE__ */ jsxRuntime.jsxs(
|
|
17835
|
+
"table",
|
|
17528
17836
|
{
|
|
17529
|
-
className:
|
|
17530
|
-
style: {
|
|
17531
|
-
"data-table-id": resolvedTableId,
|
|
17837
|
+
className: "w-full table-fixed border-separate border-spacing-0 border-b border-solid border-[color:var(--cmp-table-border-color)] text-left",
|
|
17838
|
+
style: { fontSize: 12 },
|
|
17532
17839
|
children: [
|
|
17533
|
-
|
|
17534
|
-
|
|
17535
|
-
{
|
|
17536
|
-
|
|
17537
|
-
|
|
17538
|
-
|
|
17539
|
-
|
|
17540
|
-
|
|
17541
|
-
|
|
17542
|
-
|
|
17543
|
-
|
|
17544
|
-
|
|
17545
|
-
|
|
17546
|
-
|
|
17547
|
-
|
|
17840
|
+
colgroup,
|
|
17841
|
+
/* @__PURE__ */ jsxRuntime.jsxs("thead", { className: cn(isNoData && "opacity-40"), children: [
|
|
17842
|
+
headerGroupInfo && /* @__PURE__ */ jsxRuntime.jsxs("tr", { className: "h-[var(--cmp-table-header-height)] bg-[var(--cmp-table-header-bg)]", children: [
|
|
17843
|
+
selectable && /* @__PURE__ */ jsxRuntime.jsx(
|
|
17844
|
+
"th",
|
|
17845
|
+
{
|
|
17846
|
+
rowSpan: 2,
|
|
17847
|
+
className: cn(
|
|
17848
|
+
"sticky left-0 z-[7] bg-[var(--cmp-table-header-bg)] text-center align-middle",
|
|
17849
|
+
stickyLeft === 0 && scrolledLeft && "shadow-[5px_0_8px_0_rgba(34,34,34,0.1)]"
|
|
17850
|
+
),
|
|
17851
|
+
style: {
|
|
17852
|
+
width: SELECTABLE_COLUMN_WIDTH,
|
|
17853
|
+
minWidth: SELECTABLE_COLUMN_WIDTH,
|
|
17854
|
+
maxWidth: SELECTABLE_COLUMN_WIDTH
|
|
17855
|
+
},
|
|
17856
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
17857
|
+
SCheckbox,
|
|
17858
|
+
{
|
|
17859
|
+
value: headerCheck,
|
|
17860
|
+
disabled: !hasSelectableRow,
|
|
17861
|
+
onValueChange: toggleAll
|
|
17862
|
+
}
|
|
17863
|
+
) })
|
|
17864
|
+
}
|
|
17865
|
+
),
|
|
17866
|
+
headerGroupInfo.cells.map((group) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
17867
|
+
"th",
|
|
17868
|
+
{
|
|
17869
|
+
colSpan: group.colSpan,
|
|
17870
|
+
rowSpan: group.rowSpan,
|
|
17871
|
+
className: cn(
|
|
17872
|
+
"relative border-b border-r border-solid border-[color:var(--cmp-table-border-color)] bg-[var(--cmp-table-header-bg)] px-[16px] text-[12px] font-medium leading-[20px] text-[color:var(--sys-color-fg-primary)]",
|
|
17873
|
+
group.firstIndex + group.colSpan === visibleCols.length && "border-r-0",
|
|
17874
|
+
group.rowSpan === 2 && "border-b-0",
|
|
17875
|
+
group.className
|
|
17876
|
+
),
|
|
17877
|
+
style: { textAlign: group.align ?? "left", ...group.style },
|
|
17878
|
+
children: [
|
|
17879
|
+
group.render ? group.render() : group.label,
|
|
17880
|
+
group.rowSpan === 2 && group.colSpan === 1 ? headerResizeHitArea(group.firstIndex, `group-resizer-${group.columnNames[0]}`) : null
|
|
17881
|
+
]
|
|
17882
|
+
},
|
|
17883
|
+
group.columnNames.join("|")
|
|
17884
|
+
))
|
|
17885
|
+
] }),
|
|
17886
|
+
/* @__PURE__ */ jsxRuntime.jsxs("tr", { className: "h-[var(--cmp-table-header-height)] bg-[var(--cmp-table-header-bg)]", children: [
|
|
17887
|
+
selectable && !headerGroupInfo && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
17888
|
+
"th",
|
|
17889
|
+
{
|
|
17890
|
+
className: cn(
|
|
17891
|
+
// 원본 .th--selected: 48px 고정폭, 항상 left sticky, 체크박스 중앙정렬
|
|
17892
|
+
// 하단 border는 헤더 표가 연속으로 그리므로 셀에는 두지 않는다.
|
|
17893
|
+
"sticky left-0 z-[7] bg-[var(--cmp-table-header-bg)] text-center align-middle",
|
|
17894
|
+
stickyLeft === 0 && scrolledLeft && "shadow-[5px_0_8px_0_rgba(34,34,34,0.1)]"
|
|
17895
|
+
),
|
|
17896
|
+
style: {
|
|
17897
|
+
width: SELECTABLE_COLUMN_WIDTH,
|
|
17898
|
+
minWidth: SELECTABLE_COLUMN_WIDTH,
|
|
17899
|
+
maxWidth: SELECTABLE_COLUMN_WIDTH
|
|
17900
|
+
},
|
|
17901
|
+
children: [
|
|
17902
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
17903
|
+
SCheckbox,
|
|
17904
|
+
{
|
|
17905
|
+
value: headerCheck,
|
|
17906
|
+
disabled: !hasSelectableRow,
|
|
17907
|
+
onValueChange: toggleAll
|
|
17908
|
+
}
|
|
17909
|
+
) }),
|
|
17910
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
17911
|
+
"span",
|
|
17912
|
+
{
|
|
17913
|
+
"aria-hidden": true,
|
|
17914
|
+
className: "pointer-events-none absolute right-0 top-1/2 h-[16px] w-px -translate-y-1/2 bg-[var(--cmp-table-header-resizingBar-color)]"
|
|
17915
|
+
}
|
|
17916
|
+
)
|
|
17917
|
+
]
|
|
17918
|
+
}
|
|
17919
|
+
),
|
|
17920
|
+
visibleCols.map((col, i) => {
|
|
17921
|
+
if (headerGroupInfo?.rowSpanNames.has(col.name)) return null;
|
|
17922
|
+
const isSortable = !!col.sortable && !col.renderHeader;
|
|
17923
|
+
const sortDir = isSortable && sort?.name === col.name ? sort.dir : void 0;
|
|
17924
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
17925
|
+
"th",
|
|
17926
|
+
{
|
|
17927
|
+
className: cn(
|
|
17928
|
+
// 하단 border는 헤더 표가 연속으로 그린다.
|
|
17929
|
+
// leading-[0]: th 의 스트럿(line-height 만큼의 빈 인라인 박스)을 없앤다.
|
|
17930
|
+
// 내용이 inline-flex 라 스트럿이 남아 있으면 라벨보다 큰 컨트롤(정렬 버튼 등)이
|
|
17931
|
+
// 들어올 때 줄 상자가 베이스라인 기준으로 늘어나 헤더 높이를 넘기고
|
|
17932
|
+
// 세로 가운데도 어긋난다. 글자 줄높이는 아래 th__content 가 다시 준다.
|
|
17933
|
+
headerGroupInfo && "border-r border-solid border-[color:var(--cmp-table-border-color)]",
|
|
17934
|
+
headerGroupInfo && i === visibleCols.length - 1 && "border-r-0",
|
|
17935
|
+
"relative bg-[var(--cmp-table-header-bg)] px-[16px] text-[12px] font-medium leading-[0] text-[color:var(--sys-color-fg-primary)]",
|
|
17936
|
+
stickyInfo(i).isSticky && "z-[6]",
|
|
17937
|
+
col.thClass
|
|
17938
|
+
),
|
|
17939
|
+
style: {
|
|
17940
|
+
textAlign: col.align ?? "left",
|
|
17941
|
+
boxShadow: stickyShadowValue(i) ?? void 0,
|
|
17942
|
+
...cellStyle(i),
|
|
17943
|
+
...col.thStyle
|
|
17944
|
+
},
|
|
17945
|
+
"aria-sort": isSortable ? sortDir === "asc" ? "ascending" : sortDir === "desc" ? "descending" : "none" : void 0,
|
|
17946
|
+
children: [
|
|
17947
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
17948
|
+
"span",
|
|
17949
|
+
{
|
|
17950
|
+
className: cn(
|
|
17951
|
+
// 원본 th__content: align에 따라 justify-content 적용 (th도 정렬 반영)
|
|
17952
|
+
// th 가 leading-[0] 이므로 글자 줄높이는 여기서 준다 (truncate 가
|
|
17953
|
+
// overflow-hidden 이라 줄높이가 0이면 라벨이 잘려 사라진다).
|
|
17954
|
+
"inline-flex w-full min-w-0 items-center gap-[4px] leading-[20px]",
|
|
17955
|
+
col.align === "center" && "justify-center",
|
|
17956
|
+
col.align === "right" && "justify-end"
|
|
17957
|
+
),
|
|
17958
|
+
children: col.renderHeader ? col.renderHeader(col, i) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
17959
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: col.label }),
|
|
17960
|
+
col.helpText && col.helpText.length > 0 && // 라벨이 좁아져 truncate 되더라도 아이콘까지 함께 줄어들면 안 되므로
|
|
17961
|
+
// shrink-0 래퍼로 감싼다 (STooltip 의 className 은 본문 쪽으로 간다).
|
|
17962
|
+
// 아이콘·크기·색은 SKeyValueTable 헤더 도움말과 동일하게 맞춘다.
|
|
17963
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "inline-flex shrink-0", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
17964
|
+
STooltip2,
|
|
17965
|
+
{
|
|
17966
|
+
trigger: "hover",
|
|
17967
|
+
icon: "helpOutline",
|
|
17968
|
+
iconSize: 12,
|
|
17969
|
+
color: "var(--sys-color-fg-success)",
|
|
17970
|
+
placement: "top",
|
|
17971
|
+
message: col.helpText
|
|
17972
|
+
}
|
|
17973
|
+
) }),
|
|
17974
|
+
col.editable && /* @__PURE__ */ jsxRuntime.jsx(
|
|
17975
|
+
SIcon,
|
|
17976
|
+
{
|
|
17977
|
+
name: "pageEdit",
|
|
17978
|
+
size: 12,
|
|
17979
|
+
color: "var(--sys-color-link-accent)",
|
|
17980
|
+
label: "\uC218\uC815 \uAC00\uB2A5",
|
|
17981
|
+
className: "shrink-0"
|
|
17982
|
+
}
|
|
17983
|
+
),
|
|
17984
|
+
col.navigable && /* @__PURE__ */ jsxRuntime.jsx(
|
|
17985
|
+
SIcon,
|
|
17986
|
+
{
|
|
17987
|
+
name: "pageMove",
|
|
17988
|
+
size: 12,
|
|
17989
|
+
color: "var(--sys-color-link-accent)",
|
|
17990
|
+
label: "\uC0C8 \uD654\uBA74\uC5D0\uC11C \uC5F4\uAE30",
|
|
17991
|
+
className: "shrink-0"
|
|
17992
|
+
}
|
|
17993
|
+
),
|
|
17994
|
+
col.required && // 라벨이 truncate 돼도 필수 표시는 남아야 하므로 shrink-0.
|
|
17995
|
+
// 아이콘·크기·색은 SKeyValueTable 헤더 필수 표시와 동일하게 맞춘다.
|
|
17996
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
17997
|
+
SIcon,
|
|
17998
|
+
{
|
|
17999
|
+
name: "asterisk",
|
|
18000
|
+
size: 12,
|
|
18001
|
+
color: "var(--sys-color-fg-accent)",
|
|
18002
|
+
className: "shrink-0"
|
|
18003
|
+
}
|
|
18004
|
+
),
|
|
18005
|
+
isSortable && // 미정렬은 updown 을 default(회색)로 — 정렬할 수 있다는 것 자체가
|
|
18006
|
+
// 헤더에 보여야 한다. 정렬 중이면 action 색으로 올려 활성임을 알린다.
|
|
18007
|
+
// 라벨이 truncate 돼도 버튼은 남도록 shrink-0.
|
|
18008
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "inline-flex shrink-0", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
18009
|
+
SGhostButton,
|
|
18010
|
+
{
|
|
18011
|
+
size: "xxs",
|
|
18012
|
+
intent: sortDir ? "action" : "default",
|
|
18013
|
+
icon: sortDir === "asc" ? "arrowUp" : sortDir === "desc" ? "arrowDown" : "updown",
|
|
18014
|
+
ariaLabel: `${col.label} \uC815\uB82C`,
|
|
18015
|
+
onClick: () => toggleSort(col)
|
|
18016
|
+
}
|
|
18017
|
+
) })
|
|
18018
|
+
] })
|
|
18019
|
+
}
|
|
18020
|
+
),
|
|
18021
|
+
headerGroupInfo ? headerResizeHitArea(i, `resizer-${col.name}`) : headerResizer(i, `resizer-${col.name}`)
|
|
18022
|
+
]
|
|
18023
|
+
},
|
|
18024
|
+
col.name
|
|
18025
|
+
);
|
|
18026
|
+
})
|
|
18027
|
+
] })
|
|
18028
|
+
] })
|
|
18029
|
+
]
|
|
18030
|
+
}
|
|
18031
|
+
);
|
|
18032
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
18033
|
+
"div",
|
|
18034
|
+
{
|
|
18035
|
+
className: cn("flex flex-col", className),
|
|
18036
|
+
style: { width, ...style },
|
|
18037
|
+
"data-table-id": resolvedTableId,
|
|
18038
|
+
children: [
|
|
18039
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
18040
|
+
"div",
|
|
18041
|
+
{
|
|
18042
|
+
className: cn(
|
|
18043
|
+
// isolate: no-data/loading 오버레이(z-30·31)를 테이블 안에서 끝낸다. relative + z-auto
|
|
18044
|
+
// 만으로는 스태킹 컨텍스트가 생기지 않아, 모달 안에 들어가면 30·31이 모달 Content
|
|
18045
|
+
// 레벨까지 올라가 같은 컨텍스트의 floating portal 밴드(z-2)를 덮는다.
|
|
18046
|
+
// 자손은 이미 overflow-hidden으로 클리핑되므로 테이블 내부 순서는 달라지지 않는다.
|
|
18047
|
+
"bg-white relative isolate flex flex-col overflow-hidden",
|
|
18048
|
+
bordered && "border border-solid border-[color:var(--cmp-table-border-color)]",
|
|
18049
|
+
// height가 없으면 루트가 받은 높이를 끝까지 채운다. 호스트가 className으로
|
|
18050
|
+
// `flex-1 min-h-0` 등을 줘 루트 높이가 확정된 경우, 행이 적어도 테두리 박스가
|
|
18051
|
+
// 남은 영역을 그대로 차지한다. basis는 auto로 둬 높이가 확정되지 않은
|
|
18052
|
+
// 기본 사용(내용만큼 커지는 케이스)에서는 동작이 바뀌지 않는다.
|
|
18053
|
+
!height && "min-h-0 grow",
|
|
17548
18054
|
radiusClass,
|
|
17549
18055
|
// 원본 sd-table__scroll-container--loading/--no-data: 로딩·데이터 없음 시 인터랙션 차단
|
|
17550
18056
|
(loading2 || rowCount === 0) && "pointer-events-none"
|
|
@@ -17554,220 +18060,20 @@ var STableImpl = /* @__PURE__ */ react.forwardRef(function STable({
|
|
|
17554
18060
|
minHeight: isNoData ? "calc(var(--cmp-table-header-height) + 60px)" : void 0
|
|
17555
18061
|
},
|
|
17556
18062
|
children: [
|
|
17557
|
-
/* @__PURE__ */ jsxRuntime.
|
|
17558
|
-
"div",
|
|
17559
|
-
{
|
|
17560
|
-
|
|
17561
|
-
|
|
17562
|
-
|
|
17563
|
-
|
|
17564
|
-
|
|
17565
|
-
|
|
17566
|
-
|
|
17567
|
-
|
|
17568
|
-
|
|
17569
|
-
|
|
17570
|
-
|
|
17571
|
-
colgroup,
|
|
17572
|
-
/* @__PURE__ */ jsxRuntime.jsxs("thead", { className: cn(isNoData && "opacity-40"), children: [
|
|
17573
|
-
headerGroupInfo && /* @__PURE__ */ jsxRuntime.jsxs("tr", { className: "h-[var(--cmp-table-header-height)] bg-[var(--cmp-table-header-bg)]", children: [
|
|
17574
|
-
selectable && /* @__PURE__ */ jsxRuntime.jsx(
|
|
17575
|
-
"th",
|
|
17576
|
-
{
|
|
17577
|
-
rowSpan: 2,
|
|
17578
|
-
className: cn(
|
|
17579
|
-
"sticky left-0 z-[7] bg-[var(--cmp-table-header-bg)] text-center align-middle",
|
|
17580
|
-
stickyLeft === 0 && scrolledLeft && "shadow-[5px_0_8px_0_rgba(34,34,34,0.1)]"
|
|
17581
|
-
),
|
|
17582
|
-
style: {
|
|
17583
|
-
width: SELECTABLE_COLUMN_WIDTH,
|
|
17584
|
-
minWidth: SELECTABLE_COLUMN_WIDTH,
|
|
17585
|
-
maxWidth: SELECTABLE_COLUMN_WIDTH
|
|
17586
|
-
},
|
|
17587
|
-
children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
17588
|
-
SCheckbox,
|
|
17589
|
-
{
|
|
17590
|
-
value: headerCheck,
|
|
17591
|
-
disabled: !hasSelectableRow,
|
|
17592
|
-
onValueChange: toggleAll
|
|
17593
|
-
}
|
|
17594
|
-
) })
|
|
17595
|
-
}
|
|
17596
|
-
),
|
|
17597
|
-
headerGroupInfo.cells.map((group) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
17598
|
-
"th",
|
|
17599
|
-
{
|
|
17600
|
-
colSpan: group.colSpan,
|
|
17601
|
-
rowSpan: group.rowSpan,
|
|
17602
|
-
className: cn(
|
|
17603
|
-
"relative border-b border-r border-solid border-[color:var(--cmp-table-border-color)] bg-[var(--cmp-table-header-bg)] px-[16px] text-[12px] font-medium leading-[20px] text-[color:var(--sys-color-fg-primary)]",
|
|
17604
|
-
group.firstIndex + group.colSpan === visibleCols.length && "border-r-0",
|
|
17605
|
-
group.rowSpan === 2 && "border-b-0",
|
|
17606
|
-
group.className
|
|
17607
|
-
),
|
|
17608
|
-
style: { textAlign: group.align ?? "left", ...group.style },
|
|
17609
|
-
children: [
|
|
17610
|
-
group.render ? group.render() : group.label,
|
|
17611
|
-
group.rowSpan === 2 && group.colSpan === 1 ? headerResizeHitArea(
|
|
17612
|
-
group.firstIndex,
|
|
17613
|
-
`group-resizer-${group.columnNames[0]}`
|
|
17614
|
-
) : null
|
|
17615
|
-
]
|
|
17616
|
-
},
|
|
17617
|
-
group.columnNames.join("|")
|
|
17618
|
-
))
|
|
17619
|
-
] }),
|
|
17620
|
-
/* @__PURE__ */ jsxRuntime.jsxs("tr", { className: "h-[var(--cmp-table-header-height)] bg-[var(--cmp-table-header-bg)]", children: [
|
|
17621
|
-
selectable && !headerGroupInfo && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
17622
|
-
"th",
|
|
17623
|
-
{
|
|
17624
|
-
className: cn(
|
|
17625
|
-
// 원본 .th--selected: 48px 고정폭, 항상 left sticky, 체크박스 중앙정렬
|
|
17626
|
-
// 하단 border는 헤더 래퍼가 연속으로 그리므로 셀에는 두지 않는다.
|
|
17627
|
-
"sticky left-0 z-[7] bg-[var(--cmp-table-header-bg)] text-center align-middle",
|
|
17628
|
-
stickyLeft === 0 && scrolledLeft && "shadow-[5px_0_8px_0_rgba(34,34,34,0.1)]"
|
|
17629
|
-
),
|
|
17630
|
-
style: {
|
|
17631
|
-
width: SELECTABLE_COLUMN_WIDTH,
|
|
17632
|
-
minWidth: SELECTABLE_COLUMN_WIDTH,
|
|
17633
|
-
maxWidth: SELECTABLE_COLUMN_WIDTH
|
|
17634
|
-
},
|
|
17635
|
-
children: [
|
|
17636
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
17637
|
-
SCheckbox,
|
|
17638
|
-
{
|
|
17639
|
-
value: headerCheck,
|
|
17640
|
-
disabled: !hasSelectableRow,
|
|
17641
|
-
onValueChange: toggleAll
|
|
17642
|
-
}
|
|
17643
|
-
) }),
|
|
17644
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
17645
|
-
"span",
|
|
17646
|
-
{
|
|
17647
|
-
"aria-hidden": true,
|
|
17648
|
-
className: "pointer-events-none absolute right-0 top-1/2 h-[16px] w-px -translate-y-1/2 bg-[var(--cmp-table-header-resizingBar-color)]"
|
|
17649
|
-
}
|
|
17650
|
-
)
|
|
17651
|
-
]
|
|
17652
|
-
}
|
|
17653
|
-
),
|
|
17654
|
-
visibleCols.map((col, i) => {
|
|
17655
|
-
if (headerGroupInfo?.rowSpanNames.has(col.name)) return null;
|
|
17656
|
-
const isSortable = !!col.sortable && !col.renderHeader;
|
|
17657
|
-
const sortDir = isSortable && sort?.name === col.name ? sort.dir : void 0;
|
|
17658
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
17659
|
-
"th",
|
|
17660
|
-
{
|
|
17661
|
-
className: cn(
|
|
17662
|
-
// 하단 border는 헤더 래퍼가 연속으로 그린다 (거터 영역까지 이어짐).
|
|
17663
|
-
// leading-[0]: th 의 스트럿(line-height 만큼의 빈 인라인 박스)을 없앤다.
|
|
17664
|
-
// 내용이 inline-flex 라 스트럿이 남아 있으면 라벨보다 큰 컨트롤(정렬 버튼 등)이
|
|
17665
|
-
// 들어올 때 줄 상자가 베이스라인 기준으로 늘어나 헤더 높이를 넘기고
|
|
17666
|
-
// 세로 가운데도 어긋난다. 글자 줄높이는 아래 th__content 가 다시 준다.
|
|
17667
|
-
headerGroupInfo && "border-r border-solid border-[color:var(--cmp-table-border-color)]",
|
|
17668
|
-
headerGroupInfo && i === visibleCols.length - 1 && "border-r-0",
|
|
17669
|
-
"relative bg-[var(--cmp-table-header-bg)] px-[16px] text-[12px] font-medium leading-[0] text-[color:var(--sys-color-fg-primary)]",
|
|
17670
|
-
stickyInfo(i).isSticky && "z-[6]",
|
|
17671
|
-
col.thClass
|
|
17672
|
-
),
|
|
17673
|
-
style: {
|
|
17674
|
-
textAlign: col.align ?? "left",
|
|
17675
|
-
boxShadow: stickyShadowValue(i) ?? void 0,
|
|
17676
|
-
...cellStyle(i),
|
|
17677
|
-
...col.thStyle
|
|
17678
|
-
},
|
|
17679
|
-
"aria-sort": isSortable ? sortDir === "asc" ? "ascending" : sortDir === "desc" ? "descending" : "none" : void 0,
|
|
17680
|
-
children: [
|
|
17681
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
17682
|
-
"span",
|
|
17683
|
-
{
|
|
17684
|
-
className: cn(
|
|
17685
|
-
// 원본 th__content: align에 따라 justify-content 적용 (th도 정렬 반영)
|
|
17686
|
-
// th 가 leading-[0] 이므로 글자 줄높이는 여기서 준다 (truncate 가
|
|
17687
|
-
// overflow-hidden 이라 줄높이가 0이면 라벨이 잘려 사라진다).
|
|
17688
|
-
"inline-flex w-full min-w-0 items-center gap-[4px] leading-[20px]",
|
|
17689
|
-
col.align === "center" && "justify-center",
|
|
17690
|
-
col.align === "right" && "justify-end"
|
|
17691
|
-
),
|
|
17692
|
-
children: col.renderHeader ? col.renderHeader(col, i) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
17693
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: col.label }),
|
|
17694
|
-
col.helpText && col.helpText.length > 0 && // 라벨이 좁아져 truncate 되더라도 아이콘까지 함께 줄어들면 안 되므로
|
|
17695
|
-
// shrink-0 래퍼로 감싼다 (STooltip 의 className 은 본문 쪽으로 간다).
|
|
17696
|
-
// 아이콘·크기·색은 SKeyValueTable 헤더 도움말과 동일하게 맞춘다.
|
|
17697
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "inline-flex shrink-0", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
17698
|
-
STooltip2,
|
|
17699
|
-
{
|
|
17700
|
-
trigger: "hover",
|
|
17701
|
-
icon: "helpOutline",
|
|
17702
|
-
iconSize: 12,
|
|
17703
|
-
color: "var(--sys-color-fg-success)",
|
|
17704
|
-
placement: "top",
|
|
17705
|
-
message: col.helpText
|
|
17706
|
-
}
|
|
17707
|
-
) }),
|
|
17708
|
-
col.editable && /* @__PURE__ */ jsxRuntime.jsx(
|
|
17709
|
-
SIcon,
|
|
17710
|
-
{
|
|
17711
|
-
name: "pageEdit",
|
|
17712
|
-
size: 12,
|
|
17713
|
-
color: "var(--sys-color-link-accent)",
|
|
17714
|
-
label: "\uC218\uC815 \uAC00\uB2A5",
|
|
17715
|
-
className: "shrink-0"
|
|
17716
|
-
}
|
|
17717
|
-
),
|
|
17718
|
-
col.navigable && /* @__PURE__ */ jsxRuntime.jsx(
|
|
17719
|
-
SIcon,
|
|
17720
|
-
{
|
|
17721
|
-
name: "pageMove",
|
|
17722
|
-
size: 12,
|
|
17723
|
-
color: "var(--sys-color-link-accent)",
|
|
17724
|
-
label: "\uC0C8 \uD654\uBA74\uC5D0\uC11C \uC5F4\uAE30",
|
|
17725
|
-
className: "shrink-0"
|
|
17726
|
-
}
|
|
17727
|
-
),
|
|
17728
|
-
col.required && // 라벨이 truncate 돼도 필수 표시는 남아야 하므로 shrink-0.
|
|
17729
|
-
// 아이콘·크기·색은 SKeyValueTable 헤더 필수 표시와 동일하게 맞춘다.
|
|
17730
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
17731
|
-
SIcon,
|
|
17732
|
-
{
|
|
17733
|
-
name: "asterisk",
|
|
17734
|
-
size: 12,
|
|
17735
|
-
color: "var(--sys-color-fg-accent)",
|
|
17736
|
-
className: "shrink-0"
|
|
17737
|
-
}
|
|
17738
|
-
),
|
|
17739
|
-
isSortable && // 미정렬은 updown 을 default(회색)로 — 정렬할 수 있다는 것 자체가
|
|
17740
|
-
// 헤더에 보여야 한다. 정렬 중이면 action 색으로 올려 활성임을 알린다.
|
|
17741
|
-
// 라벨이 truncate 돼도 버튼은 남도록 shrink-0.
|
|
17742
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "inline-flex shrink-0", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
17743
|
-
SGhostButton,
|
|
17744
|
-
{
|
|
17745
|
-
size: "xxs",
|
|
17746
|
-
intent: sortDir ? "action" : "default",
|
|
17747
|
-
icon: sortDir === "asc" ? "arrowUp" : sortDir === "desc" ? "arrowDown" : "updown",
|
|
17748
|
-
ariaLabel: `${col.label} \uC815\uB82C`,
|
|
17749
|
-
onClick: () => toggleSort(col)
|
|
17750
|
-
}
|
|
17751
|
-
) })
|
|
17752
|
-
] })
|
|
17753
|
-
}
|
|
17754
|
-
),
|
|
17755
|
-
headerGroupInfo ? headerResizeHitArea(i, `resizer-${col.name}`) : headerResizer(i, `resizer-${col.name}`)
|
|
17756
|
-
]
|
|
17757
|
-
},
|
|
17758
|
-
col.name
|
|
17759
|
-
);
|
|
17760
|
-
})
|
|
17761
|
-
] })
|
|
17762
|
-
] })
|
|
17763
|
-
]
|
|
17764
|
-
}
|
|
17765
|
-
)
|
|
17766
|
-
}
|
|
17767
|
-
),
|
|
17768
|
-
showNoDataSlot ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn(bodyScrollClass, "pointer-events-auto bg-white"), children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full min-h-[60px] w-full items-center justify-center", children: emptySlot }) }) : (
|
|
17769
|
-
/* 바디: 세로 스크롤은 이 컨테이너에만 생긴다 (헤더 제외) */
|
|
17770
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
18063
|
+
showNoDataSlot ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
18064
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-none overflow-hidden", children: headerTable }),
|
|
18065
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: cn(bodyScrollClass, "pointer-events-auto bg-white"), children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full min-h-[60px] w-full items-center justify-center", children: emptySlot }) })
|
|
18066
|
+
] }) : (
|
|
18067
|
+
/**
|
|
18068
|
+
* 헤더와 body 가 한 스크롤 컨테이너에 있다 — 헤더는 위에 붙어(sticky) 세로로는 서 있고
|
|
18069
|
+
* 가로로는 body 와 같은 스크롤로 움직인다. 헤더를 따로 두고 scroll 이벤트에서
|
|
18070
|
+
* scrollLeft 를 맞추면, 브라우저가 body 스크롤을 먼저 그리고 이벤트를 나중에 돌려
|
|
18071
|
+
* 헤더가 한 프레임 이상 늦게 따라온다.
|
|
18072
|
+
*
|
|
18073
|
+
* 행 좌표는 body 표 기준 그대로다 — `scrollTop` 이 y 이면 y 에 있는 행이 헤더 바로
|
|
18074
|
+
* 아래에 선다. 보이는 높이만 헤더 높이만큼 줄어든다(`headerHeight`).
|
|
18075
|
+
*/
|
|
18076
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
17771
18077
|
"div",
|
|
17772
18078
|
{
|
|
17773
18079
|
ref: scrollRef,
|
|
@@ -17777,136 +18083,140 @@ var STableImpl = /* @__PURE__ */ react.forwardRef(function STable({
|
|
|
17777
18083
|
// 원본 sd-table__scroll-container--loading/--no-data: 로딩·데이터 없음 시 스크롤 차단
|
|
17778
18084
|
(loading2 || rowCount === 0) && "overflow-hidden"
|
|
17779
18085
|
),
|
|
17780
|
-
|
|
17781
|
-
|
|
17782
|
-
{
|
|
17783
|
-
|
|
17784
|
-
|
|
17785
|
-
|
|
17786
|
-
|
|
17787
|
-
|
|
17788
|
-
|
|
17789
|
-
|
|
17790
|
-
|
|
17791
|
-
|
|
17792
|
-
|
|
17793
|
-
|
|
17794
|
-
|
|
17795
|
-
|
|
17796
|
-
|
|
17797
|
-
|
|
17798
|
-
|
|
17799
|
-
"
|
|
17800
|
-
|
|
17801
|
-
|
|
17802
|
-
|
|
17803
|
-
|
|
17804
|
-
|
|
17805
|
-
|
|
17806
|
-
onRowDoubleClick(
|
|
17807
|
-
|
|
17808
|
-
|
|
17809
|
-
|
|
17810
|
-
|
|
17811
|
-
|
|
17812
|
-
|
|
17813
|
-
|
|
17814
|
-
(
|
|
17815
|
-
|
|
17816
|
-
|
|
17817
|
-
|
|
17818
|
-
|
|
17819
|
-
|
|
17820
|
-
|
|
17821
|
-
|
|
17822
|
-
|
|
17823
|
-
|
|
17824
|
-
|
|
17825
|
-
|
|
18086
|
+
style: { scrollPaddingTop: headerHeight },
|
|
18087
|
+
children: [
|
|
18088
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { ref: headerRef, className: "sticky top-0 z-[8]", children: headerTable }),
|
|
18089
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
18090
|
+
"table",
|
|
18091
|
+
{
|
|
18092
|
+
className: "w-full table-fixed border-separate border-spacing-0 text-left",
|
|
18093
|
+
style: { fontSize: 12 },
|
|
18094
|
+
children: [
|
|
18095
|
+
colgroup,
|
|
18096
|
+
/* @__PURE__ */ jsxRuntime.jsxs("tbody", { children: [
|
|
18097
|
+
virtualScroll && topSpacer > 0 && /* @__PURE__ */ jsxRuntime.jsx("tr", { "aria-hidden": true, style: { height: topSpacer }, children: /* @__PURE__ */ jsxRuntime.jsx("td", { colSpan: totalColSpan, className: "p-0" }) }),
|
|
18098
|
+
rowCount === 0 ? null : bodyRows.map((row, ri) => {
|
|
18099
|
+
const rowIndex = rowOffset + ri;
|
|
18100
|
+
const isSel = isViewSelected(row);
|
|
18101
|
+
const dragSel = dragSelectOn && isSel;
|
|
18102
|
+
const dragTop = dragSel && !isViewSelected(rows[rowIndex - 1]);
|
|
18103
|
+
const dragBottom = dragSel && !isViewSelected(rows[rowIndex + 1]);
|
|
18104
|
+
const rowLocked = (selectable || dragSelectable) && !canSelectRow(row);
|
|
18105
|
+
const rowBg = "bg-white";
|
|
18106
|
+
const rowHover = hoverable && !rowLocked;
|
|
18107
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
18108
|
+
"tr",
|
|
18109
|
+
{
|
|
18110
|
+
"data-row-index": rowIndex,
|
|
18111
|
+
onClick: () => onRowClick?.(row),
|
|
18112
|
+
onDoubleClick: onRowDoubleClick ? (event) => {
|
|
18113
|
+
const target = event.target;
|
|
18114
|
+
if (target.closest(ROW_INTERACTIVE_SELECTOR)) return;
|
|
18115
|
+
onRowDoubleClick(row);
|
|
18116
|
+
} : void 0,
|
|
18117
|
+
onMouseDown: dragSelectOn ? (event) => startDragSelect(rowIndex, event) : void 0,
|
|
18118
|
+
onMouseEnter: dragSelectOn ? () => extendDragSelect(rowIndex) : void 0,
|
|
18119
|
+
onContextMenu: contextMenuOn ? (event) => openContextMenu(row, event) : void 0,
|
|
18120
|
+
className: cn(
|
|
18121
|
+
"group/table-row",
|
|
18122
|
+
rowHover && "hover:bg-[var(--color-grey-05)]",
|
|
18123
|
+
(onRowClick || onRowDoubleClick) && "cursor-pointer",
|
|
18124
|
+
dragSelectOn && "select-none",
|
|
18125
|
+
rowClassName?.(row)
|
|
18126
|
+
),
|
|
18127
|
+
style: { height: virtualScroll ? effectiveRowHeight : rowH },
|
|
18128
|
+
children: [
|
|
18129
|
+
selectable && /* @__PURE__ */ jsxRuntime.jsx(
|
|
18130
|
+
"td",
|
|
18131
|
+
{
|
|
18132
|
+
className: cn(
|
|
18133
|
+
// 원본 .td--selected: 48px 고정폭, 항상 left sticky, 체크박스 중앙정렬
|
|
18134
|
+
"sticky left-0 z-[2] border-b border-solid border-[color:var(--cmp-table-border-color)] align-middle",
|
|
18135
|
+
rowBg,
|
|
18136
|
+
rowHover && "group-hover/table-row:bg-[var(--color-grey-05)]",
|
|
18137
|
+
stickyLeft === 0 && scrolledLeft && "shadow-[5px_0_8px_0_rgba(34,34,34,0.1)]"
|
|
18138
|
+
),
|
|
18139
|
+
style: {
|
|
18140
|
+
width: SELECTABLE_COLUMN_WIDTH,
|
|
18141
|
+
minWidth: SELECTABLE_COLUMN_WIDTH,
|
|
18142
|
+
maxWidth: SELECTABLE_COLUMN_WIDTH,
|
|
18143
|
+
// 선택 열도 행의 일부다 — 여기만 희지 않으면 회색 줄이 끊긴다
|
|
18144
|
+
background: rowLocked ? LOCKED_BG : void 0
|
|
18145
|
+
},
|
|
18146
|
+
onClick: (e) => e.stopPropagation(),
|
|
18147
|
+
onDoubleClick: (e) => e.stopPropagation(),
|
|
18148
|
+
onClickCapture: (e) => {
|
|
18149
|
+
shiftPressedRef.current = e.shiftKey;
|
|
18150
|
+
if (e.shiftKey) window.getSelection()?.removeAllRanges();
|
|
18151
|
+
},
|
|
18152
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex select-none items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
18153
|
+
SCheckbox,
|
|
18154
|
+
{
|
|
18155
|
+
value: isSel || !isRowIndeterminate?.(row) ? isSel : null,
|
|
18156
|
+
disabled: !canSelectRow(row),
|
|
18157
|
+
onValueChange: () => toggleRow(row, rowOffset + ri)
|
|
18158
|
+
}
|
|
18159
|
+
) })
|
|
18160
|
+
}
|
|
18161
|
+
),
|
|
18162
|
+
visibleCols.map((col, i) => {
|
|
18163
|
+
const tdClassName = cn(
|
|
18164
|
+
// 원본 .td: 컬럼 폭에 맞춰 내용 말줄임(overflow hidden + ellipsis) + 행 구분선(border-b)
|
|
18165
|
+
// 셀 배경은 흰색으로 채워 뒤 배경이 비치지 않게 하고, hover 시 행 배경(grey_05)을 따른다.
|
|
18166
|
+
"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)]",
|
|
17826
18167
|
rowBg,
|
|
17827
18168
|
rowHover && "group-hover/table-row:bg-[var(--color-grey-05)]",
|
|
17828
|
-
|
|
17829
|
-
)
|
|
17830
|
-
|
|
17831
|
-
|
|
17832
|
-
|
|
17833
|
-
|
|
17834
|
-
|
|
17835
|
-
|
|
17836
|
-
|
|
17837
|
-
|
|
17838
|
-
|
|
17839
|
-
|
|
17840
|
-
|
|
17841
|
-
|
|
17842
|
-
|
|
17843
|
-
|
|
17844
|
-
|
|
17845
|
-
|
|
17846
|
-
|
|
17847
|
-
|
|
17848
|
-
|
|
17849
|
-
|
|
17850
|
-
|
|
17851
|
-
|
|
17852
|
-
|
|
17853
|
-
|
|
17854
|
-
|
|
17855
|
-
|
|
17856
|
-
|
|
17857
|
-
|
|
17858
|
-
|
|
17859
|
-
|
|
17860
|
-
|
|
17861
|
-
|
|
17862
|
-
|
|
17863
|
-
|
|
17864
|
-
|
|
17865
|
-
|
|
17866
|
-
|
|
17867
|
-
|
|
17868
|
-
|
|
17869
|
-
|
|
17870
|
-
|
|
17871
|
-
|
|
17872
|
-
|
|
17873
|
-
|
|
17874
|
-
|
|
17875
|
-
|
|
17876
|
-
|
|
17877
|
-
|
|
17878
|
-
|
|
17879
|
-
// color, STag 등)까지는 닿지 않는다 — 그건 소비 앱 몫이다.
|
|
17880
|
-
color: rowLocked ? LOCKED_CONTENT : void 0,
|
|
17881
|
-
boxShadow: shadows.length > 0 ? shadows.join(", ") : void 0,
|
|
17882
|
-
...cellStyle(i)
|
|
17883
|
-
};
|
|
17884
|
-
if (col.renderCell) {
|
|
17885
|
-
const cell = col.renderCell({
|
|
17886
|
-
row,
|
|
17887
|
-
value: cellValue(col, row),
|
|
17888
|
-
column: col,
|
|
17889
|
-
colIndex: i,
|
|
17890
|
-
rowIndex: ri,
|
|
17891
|
-
isSelected: isSel,
|
|
17892
|
-
className: tdClassName,
|
|
17893
|
-
style: tdStyle
|
|
17894
|
-
});
|
|
17895
|
-
if (cell == null || cell === false) return null;
|
|
17896
|
-
return /* @__PURE__ */ jsxRuntime.jsx(react.Fragment, { children: cell }, col.name);
|
|
17897
|
-
}
|
|
17898
|
-
return /* @__PURE__ */ jsxRuntime.jsx("td", { className: tdClassName, style: tdStyle, children: col.render ? col.render(row, cellValue(col, row)) : cellValue(col, row) }, col.name);
|
|
17899
|
-
})
|
|
17900
|
-
]
|
|
17901
|
-
},
|
|
17902
|
-
row[rowKey] ?? ri
|
|
17903
|
-
);
|
|
17904
|
-
}),
|
|
17905
|
-
virtualScroll && bottomSpacer > 0 && /* @__PURE__ */ jsxRuntime.jsx("tr", { "aria-hidden": true, style: { height: bottomSpacer }, children: /* @__PURE__ */ jsxRuntime.jsx("td", { colSpan: totalColSpan, className: "p-0" }) })
|
|
17906
|
-
] })
|
|
17907
|
-
]
|
|
17908
|
-
}
|
|
17909
|
-
)
|
|
18169
|
+
typeof col.tdClass === "function" ? col.tdClass(row) : col.tdClass
|
|
18170
|
+
);
|
|
18171
|
+
const tdPadX = cellPadXOf(col);
|
|
18172
|
+
const dragEdges = dragSel ? [
|
|
18173
|
+
dragTop && `inset 0 ${DRAG_EDGE_W} 0 0 ${DRAG_EDGE_C}`,
|
|
18174
|
+
dragBottom && `inset 0 ${DRAG_EDGE_NEG_W} 0 0 ${DRAG_EDGE_C}`,
|
|
18175
|
+
i === 0 && `inset ${DRAG_EDGE_W} 0 0 0 ${DRAG_EDGE_C}`,
|
|
18176
|
+
i === visibleCols.length - 1 && `inset ${DRAG_EDGE_NEG_W} 0 0 0 ${DRAG_EDGE_C}`
|
|
18177
|
+
].filter(Boolean) : [];
|
|
18178
|
+
const shadows = [...dragEdges, stickyShadowValue(i)].filter(Boolean);
|
|
18179
|
+
const tdStyle = {
|
|
18180
|
+
paddingLeft: tdPadX,
|
|
18181
|
+
paddingRight: tdPadX,
|
|
18182
|
+
paddingTop: cellPadY,
|
|
18183
|
+
paddingBottom: cellPadY,
|
|
18184
|
+
textAlign: col.align ?? "left",
|
|
18185
|
+
// 인라인이라 hover 배경보다 세다 — 고른 행은 hover 로 흐려지지 않는다.
|
|
18186
|
+
background: dragSel ? DRAG_BG : rowLocked ? LOCKED_BG : void 0,
|
|
18187
|
+
// 클래스의 기본 글자색을 덮는다. 셀이 자기 색을 정한 내용(SIcon 의
|
|
18188
|
+
// color, STag 등)까지는 닿지 않는다 — 그건 소비 앱 몫이다.
|
|
18189
|
+
color: rowLocked ? LOCKED_CONTENT : void 0,
|
|
18190
|
+
boxShadow: shadows.length > 0 ? shadows.join(", ") : void 0,
|
|
18191
|
+
...cellStyle(i)
|
|
18192
|
+
};
|
|
18193
|
+
if (col.renderCell) {
|
|
18194
|
+
const cell = col.renderCell({
|
|
18195
|
+
row,
|
|
18196
|
+
value: cellValue(col, row),
|
|
18197
|
+
column: col,
|
|
18198
|
+
colIndex: i,
|
|
18199
|
+
rowIndex: ri,
|
|
18200
|
+
isSelected: isSel,
|
|
18201
|
+
className: tdClassName,
|
|
18202
|
+
style: tdStyle
|
|
18203
|
+
});
|
|
18204
|
+
if (cell == null || cell === false) return null;
|
|
18205
|
+
return /* @__PURE__ */ jsxRuntime.jsx(react.Fragment, { children: cell }, col.name);
|
|
18206
|
+
}
|
|
18207
|
+
return /* @__PURE__ */ jsxRuntime.jsx("td", { className: tdClassName, style: tdStyle, children: col.render ? col.render(row, cellValue(col, row)) : cellValue(col, row) }, col.name);
|
|
18208
|
+
})
|
|
18209
|
+
]
|
|
18210
|
+
},
|
|
18211
|
+
row[rowKey] ?? ri
|
|
18212
|
+
);
|
|
18213
|
+
}),
|
|
18214
|
+
virtualScroll && bottomSpacer > 0 && /* @__PURE__ */ jsxRuntime.jsx("tr", { "aria-hidden": true, style: { height: bottomSpacer }, children: /* @__PURE__ */ jsxRuntime.jsx("td", { colSpan: totalColSpan, className: "p-0" }) })
|
|
18215
|
+
] })
|
|
18216
|
+
]
|
|
18217
|
+
}
|
|
18218
|
+
)
|
|
18219
|
+
]
|
|
17910
18220
|
}
|
|
17911
18221
|
)
|
|
17912
18222
|
),
|
|
@@ -21183,6 +21493,169 @@ function ChartAxisLabels({
|
|
|
21183
21493
|
}
|
|
21184
21494
|
);
|
|
21185
21495
|
}
|
|
21496
|
+
function useElementSize(target) {
|
|
21497
|
+
const ref = react.useRef(null);
|
|
21498
|
+
const [size, setSize] = react.useState({ width: 0, height: 0 });
|
|
21499
|
+
react.useEffect(() => {
|
|
21500
|
+
const element = ref.current;
|
|
21501
|
+
if (!element) return;
|
|
21502
|
+
const update = () => {
|
|
21503
|
+
const { width, height } = element.getBoundingClientRect();
|
|
21504
|
+
setSize(
|
|
21505
|
+
(prev) => Math.abs(prev.width - width) < 0.5 && Math.abs(prev.height - height) < 0.5 ? prev : { width, height }
|
|
21506
|
+
);
|
|
21507
|
+
};
|
|
21508
|
+
update();
|
|
21509
|
+
if (typeof ResizeObserver === "undefined") return;
|
|
21510
|
+
const observer = new ResizeObserver(update);
|
|
21511
|
+
observer.observe(element);
|
|
21512
|
+
return () => observer.disconnect();
|
|
21513
|
+
}, [target]);
|
|
21514
|
+
return { ref, size };
|
|
21515
|
+
}
|
|
21516
|
+
function useSvgTextMetrics() {
|
|
21517
|
+
const ref = react.useRef(null);
|
|
21518
|
+
const [metrics, setMetrics] = react.useState({ top: 0, width: 0 });
|
|
21519
|
+
react.useEffect(() => {
|
|
21520
|
+
const element = ref.current;
|
|
21521
|
+
if (!element) return;
|
|
21522
|
+
let box3;
|
|
21523
|
+
try {
|
|
21524
|
+
box3 = element.getBBox();
|
|
21525
|
+
} catch {
|
|
21526
|
+
return;
|
|
21527
|
+
}
|
|
21528
|
+
setMetrics(
|
|
21529
|
+
(prev) => Math.abs(prev.top - box3.y) < 0.5 && Math.abs(prev.width - box3.width) < 0.5 ? prev : { top: box3.y, width: box3.width }
|
|
21530
|
+
);
|
|
21531
|
+
});
|
|
21532
|
+
return { ref, metrics };
|
|
21533
|
+
}
|
|
21534
|
+
var CATEGORY_LABEL_ROTATED_MAX = 120;
|
|
21535
|
+
var DIAGONAL = Math.SQRT1_2;
|
|
21536
|
+
function ChartRotatedAxisLayout({
|
|
21537
|
+
axis,
|
|
21538
|
+
axisStyle,
|
|
21539
|
+
plot,
|
|
21540
|
+
minPlotWidth,
|
|
21541
|
+
labels,
|
|
21542
|
+
xAt,
|
|
21543
|
+
labelsRef
|
|
21544
|
+
}) {
|
|
21545
|
+
const scrollRef = react.useRef(null);
|
|
21546
|
+
const { ref: axisRef, size: axisBox } = useElementSize();
|
|
21547
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { ref: scrollRef, className: "min-w-0 overflow-x-auto", style: { gridColumn: "1 / -1" }, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { width: `max(100%, ${axisBox.width + minPlotWidth}px)` }, children: [
|
|
21548
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex", children: [
|
|
21549
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
21550
|
+
"div",
|
|
21551
|
+
{
|
|
21552
|
+
ref: axisRef,
|
|
21553
|
+
className: "sticky left-0 z-1 shrink-0 bg-(--cmp-chart-bg)",
|
|
21554
|
+
style: { ...axisStyle, paddingRight: AXIS_LABEL_GAP },
|
|
21555
|
+
children: axis
|
|
21556
|
+
}
|
|
21557
|
+
),
|
|
21558
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-w-0 flex-1", children: plot })
|
|
21559
|
+
] }),
|
|
21560
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { ref: labelsRef, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
21561
|
+
ChartRotatedCategoryLabels,
|
|
21562
|
+
{
|
|
21563
|
+
labels,
|
|
21564
|
+
xAt,
|
|
21565
|
+
gutter: axisBox.width,
|
|
21566
|
+
scrollRef
|
|
21567
|
+
}
|
|
21568
|
+
) })
|
|
21569
|
+
] }) });
|
|
21570
|
+
}
|
|
21571
|
+
function ChartRotatedCategoryLabels({
|
|
21572
|
+
labels,
|
|
21573
|
+
xAt,
|
|
21574
|
+
gutter,
|
|
21575
|
+
scrollRef
|
|
21576
|
+
}) {
|
|
21577
|
+
const sizerRef = react.useRef(null);
|
|
21578
|
+
const maskRef = react.useRef(null);
|
|
21579
|
+
const [box3, setBox] = react.useState({ line: 0, widest: 0 });
|
|
21580
|
+
react.useEffect(() => {
|
|
21581
|
+
const sizer = sizerRef.current;
|
|
21582
|
+
if (!sizer) return;
|
|
21583
|
+
const measure = () => {
|
|
21584
|
+
const { width, height: height2 } = sizer.getBoundingClientRect();
|
|
21585
|
+
const line = labels.length > 0 ? height2 / labels.length : 0;
|
|
21586
|
+
setBox(
|
|
21587
|
+
(prev) => Math.abs(prev.line - line) < 0.5 && Math.abs(prev.widest - width) < 0.5 ? prev : { line, widest: width }
|
|
21588
|
+
);
|
|
21589
|
+
};
|
|
21590
|
+
measure();
|
|
21591
|
+
if (typeof ResizeObserver === "undefined") return;
|
|
21592
|
+
const observer = new ResizeObserver(measure);
|
|
21593
|
+
observer.observe(sizer);
|
|
21594
|
+
return () => observer.disconnect();
|
|
21595
|
+
});
|
|
21596
|
+
react.useEffect(() => {
|
|
21597
|
+
const scroller = scrollRef.current;
|
|
21598
|
+
const mask = maskRef.current;
|
|
21599
|
+
if (!scroller || !mask) return;
|
|
21600
|
+
const sync2 = () => {
|
|
21601
|
+
mask.style.visibility = scroller.scrollLeft > 0 ? "visible" : "hidden";
|
|
21602
|
+
};
|
|
21603
|
+
sync2();
|
|
21604
|
+
scroller.addEventListener("scroll", sync2, { passive: true });
|
|
21605
|
+
return () => scroller.removeEventListener("scroll", sync2);
|
|
21606
|
+
}, [scrollRef]);
|
|
21607
|
+
const maxWidthAt = (index) => Math.max(
|
|
21608
|
+
0,
|
|
21609
|
+
Math.min(CATEGORY_LABEL_ROTATED_MAX, (xAt(index) + gutter) / DIAGONAL - box3.line / 2)
|
|
21610
|
+
);
|
|
21611
|
+
const height = Math.ceil(box3.line / 2 + (box3.widest + box3.line / 2) * DIAGONAL);
|
|
21612
|
+
return (
|
|
21613
|
+
// overflow 는 clip 이어야 한다 — hidden 이면 이 행이 스크롤 상자로 잡혀 가림막의
|
|
21614
|
+
// sticky 가 스크롤을 따라가지 않는다.
|
|
21615
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
21616
|
+
"div",
|
|
21617
|
+
{
|
|
21618
|
+
"aria-hidden": true,
|
|
21619
|
+
className: "relative overflow-clip typo-body-sm-medium text-(--cmp-chart-axis-color)",
|
|
21620
|
+
style: { height },
|
|
21621
|
+
children: [
|
|
21622
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
21623
|
+
"div",
|
|
21624
|
+
{
|
|
21625
|
+
ref: maskRef,
|
|
21626
|
+
className: "invisible sticky left-0 z-1 h-full bg-(--cmp-chart-bg)",
|
|
21627
|
+
style: { width: gutter }
|
|
21628
|
+
}
|
|
21629
|
+
),
|
|
21630
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-y-0 right-0", style: { left: gutter }, children: labels.map((label, index) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
21631
|
+
"span",
|
|
21632
|
+
{
|
|
21633
|
+
className: "absolute top-0 truncate",
|
|
21634
|
+
style: {
|
|
21635
|
+
left: xAt(index),
|
|
21636
|
+
maxWidth: maxWidthAt(index),
|
|
21637
|
+
// 글자 끝(오른쪽 가운데)을 눈금에 맞춘 뒤 그 점을 축으로 기울인다.
|
|
21638
|
+
transform: "translateX(-100%) rotate(-45deg)",
|
|
21639
|
+
transformOrigin: "right center"
|
|
21640
|
+
},
|
|
21641
|
+
children: label
|
|
21642
|
+
},
|
|
21643
|
+
`${label}-${index}`
|
|
21644
|
+
)) }),
|
|
21645
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { ref: sizerRef, className: "invisible absolute top-0 left-0 flex flex-col items-start", children: labels.map((label, index) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
21646
|
+
"span",
|
|
21647
|
+
{
|
|
21648
|
+
className: "block truncate",
|
|
21649
|
+
style: { maxWidth: maxWidthAt(index) },
|
|
21650
|
+
children: label
|
|
21651
|
+
},
|
|
21652
|
+
`${label}-${index}`
|
|
21653
|
+
)) })
|
|
21654
|
+
]
|
|
21655
|
+
}
|
|
21656
|
+
)
|
|
21657
|
+
);
|
|
21658
|
+
}
|
|
21186
21659
|
var ChartFrame = /* @__PURE__ */ react.forwardRef(function ChartFrame2({ title, unit, legend, legendPosition = "top", children, className, style, ...props }, ref) {
|
|
21187
21660
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
21188
21661
|
"div",
|
|
@@ -21374,44 +21847,6 @@ function linePointDimmedColor(palette, index, colors) {
|
|
|
21374
21847
|
function dataValueColor(palette, index, colors) {
|
|
21375
21848
|
return pick(DATA_VALUE_COLORS, DATA_VALUE_PALETTES, palette, index, colors);
|
|
21376
21849
|
}
|
|
21377
|
-
function useElementSize() {
|
|
21378
|
-
const ref = react.useRef(null);
|
|
21379
|
-
const [size, setSize] = react.useState({ width: 0, height: 0 });
|
|
21380
|
-
react.useEffect(() => {
|
|
21381
|
-
const element = ref.current;
|
|
21382
|
-
if (!element) return;
|
|
21383
|
-
const update = () => {
|
|
21384
|
-
const { width, height } = element.getBoundingClientRect();
|
|
21385
|
-
setSize(
|
|
21386
|
-
(prev) => Math.abs(prev.width - width) < 0.5 && Math.abs(prev.height - height) < 0.5 ? prev : { width, height }
|
|
21387
|
-
);
|
|
21388
|
-
};
|
|
21389
|
-
update();
|
|
21390
|
-
if (typeof ResizeObserver === "undefined") return;
|
|
21391
|
-
const observer = new ResizeObserver(update);
|
|
21392
|
-
observer.observe(element);
|
|
21393
|
-
return () => observer.disconnect();
|
|
21394
|
-
}, []);
|
|
21395
|
-
return { ref, size };
|
|
21396
|
-
}
|
|
21397
|
-
function useSvgTextMetrics() {
|
|
21398
|
-
const ref = react.useRef(null);
|
|
21399
|
-
const [metrics, setMetrics] = react.useState({ top: 0, width: 0 });
|
|
21400
|
-
react.useEffect(() => {
|
|
21401
|
-
const element = ref.current;
|
|
21402
|
-
if (!element) return;
|
|
21403
|
-
let box3;
|
|
21404
|
-
try {
|
|
21405
|
-
box3 = element.getBBox();
|
|
21406
|
-
} catch {
|
|
21407
|
-
return;
|
|
21408
|
-
}
|
|
21409
|
-
setMetrics(
|
|
21410
|
-
(prev) => Math.abs(prev.top - box3.y) < 0.5 && Math.abs(prev.width - box3.width) < 0.5 ? prev : { top: box3.y, width: box3.width }
|
|
21411
|
-
);
|
|
21412
|
-
});
|
|
21413
|
-
return { ref, metrics };
|
|
21414
|
-
}
|
|
21415
21850
|
var DOT_SIZE2 = 6;
|
|
21416
21851
|
var DEFAULT_GAP_X = { after: 4, before: 4 };
|
|
21417
21852
|
var ANCHOR_GAP_Y = 0;
|
|
@@ -21573,6 +22008,7 @@ var SBarChart = /* @__PURE__ */ react.forwardRef(function SBarChart2({
|
|
|
21573
22008
|
tickCount = 8,
|
|
21574
22009
|
barSize = BAR_SIZE_MAX,
|
|
21575
22010
|
showValueLabel = true,
|
|
22011
|
+
rotateCategoryLabels = false,
|
|
21576
22012
|
showLegend = true,
|
|
21577
22013
|
legendPosition = "top",
|
|
21578
22014
|
showTooltip = true,
|
|
@@ -21584,9 +22020,10 @@ var SBarChart = /* @__PURE__ */ react.forwardRef(function SBarChart2({
|
|
|
21584
22020
|
style
|
|
21585
22021
|
}, ref) {
|
|
21586
22022
|
const vertical = direction === "vertical";
|
|
22023
|
+
const rotateLabels = rotateCategoryLabels && vertical;
|
|
21587
22024
|
const { ref: plotRef, size } = useElementSize();
|
|
21588
|
-
const { ref: axisTextRef, size: axisText } = useElementSize();
|
|
21589
|
-
const { ref: axisSampleRef, size: axisSample } = useElementSize();
|
|
22025
|
+
const { ref: axisTextRef, size: axisText } = useElementSize(rotateLabels);
|
|
22026
|
+
const { ref: axisSampleRef, size: axisSample } = useElementSize(rotateLabels);
|
|
21590
22027
|
const clipId = react.useId();
|
|
21591
22028
|
const [hoveredCategory, setHoveredCategory] = react.useState(null);
|
|
21592
22029
|
const perCategory = series.length === 1;
|
|
@@ -22045,7 +22482,29 @@ var SBarChart = /* @__PURE__ */ react.forwardRef(function SBarChart2({
|
|
|
22045
22482
|
// 눈금은 왼쪽 한 줄뿐이다 — 오른쪽에도 적으면 서로 다른 단위처럼 읽힌다.
|
|
22046
22483
|
gridTemplateColumns: "max-content 1fr"
|
|
22047
22484
|
},
|
|
22048
|
-
children:
|
|
22485
|
+
children: rotateLabels ? (
|
|
22486
|
+
// 기울인 이름은 눈금 열 아래까지 뻗으므로 스크롤 상자가 눈금 열까지 감싼다.
|
|
22487
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
22488
|
+
ChartRotatedAxisLayout,
|
|
22489
|
+
{
|
|
22490
|
+
axis: /* @__PURE__ */ jsxRuntime.jsx(
|
|
22491
|
+
ChartAxisLabels,
|
|
22492
|
+
{
|
|
22493
|
+
scale,
|
|
22494
|
+
direction,
|
|
22495
|
+
format: formatValue,
|
|
22496
|
+
sampleRef: axisSampleRef
|
|
22497
|
+
}
|
|
22498
|
+
),
|
|
22499
|
+
axisStyle: { height: plotHeight, ...inset },
|
|
22500
|
+
plot,
|
|
22501
|
+
minPlotWidth,
|
|
22502
|
+
labels: categories,
|
|
22503
|
+
xAt: (index) => layout.offset + (index + 0.5) * layout.band,
|
|
22504
|
+
labelsRef: axisTextRef
|
|
22505
|
+
}
|
|
22506
|
+
)
|
|
22507
|
+
) : vertical ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
22049
22508
|
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { height: plotHeight, ...inset }, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
22050
22509
|
ChartAxisLabels,
|
|
22051
22510
|
{
|
|
@@ -22176,6 +22635,7 @@ var SLineChart = /* @__PURE__ */ react.forwardRef(function SLineChart2({
|
|
|
22176
22635
|
tickCount = 8,
|
|
22177
22636
|
showPoint = true,
|
|
22178
22637
|
showValueLabel = false,
|
|
22638
|
+
rotateCategoryLabels = false,
|
|
22179
22639
|
showLegend = true,
|
|
22180
22640
|
legendPosition = "top",
|
|
22181
22641
|
showTooltip = true,
|
|
@@ -22188,8 +22648,8 @@ var SLineChart = /* @__PURE__ */ react.forwardRef(function SLineChart2({
|
|
|
22188
22648
|
}, ref) {
|
|
22189
22649
|
const smooth = curve === "smooth";
|
|
22190
22650
|
const { ref: plotRef, size } = useElementSize();
|
|
22191
|
-
const { ref: axisTextRef, size: axisText } = useElementSize();
|
|
22192
|
-
const { ref: axisSampleRef, size: axisSample } = useElementSize();
|
|
22651
|
+
const { ref: axisTextRef, size: axisText } = useElementSize(rotateCategoryLabels);
|
|
22652
|
+
const { ref: axisSampleRef, size: axisSample } = useElementSize(rotateCategoryLabels);
|
|
22193
22653
|
const gradientId = react.useId();
|
|
22194
22654
|
const [hoveredCategory, setHoveredCategory] = react.useState(null);
|
|
22195
22655
|
const colorAt = react.useCallback(
|
|
@@ -22223,23 +22683,16 @@ var SLineChart = /* @__PURE__ */ react.forwardRef(function SLineChart2({
|
|
|
22223
22683
|
[rows, tickCount, max]
|
|
22224
22684
|
);
|
|
22225
22685
|
const count = categories.length;
|
|
22226
|
-
const
|
|
22227
|
-
const xAt = react.useCallback(
|
|
22228
|
-
(index) => count > 1 ? index * step : size.width / 2,
|
|
22229
|
-
[count, step, size.width]
|
|
22230
|
-
);
|
|
22686
|
+
const band = Math.max(0, size.width - AXIS_EDGE_PADDING * 2) / Math.max(1, count);
|
|
22687
|
+
const xAt = react.useCallback((index) => AXIS_EDGE_PADDING + (index + 0.5) * band, [band]);
|
|
22231
22688
|
const cellOf = react.useCallback(
|
|
22232
|
-
(index) =>
|
|
22233
|
-
left:
|
|
22234
|
-
right:
|
|
22235
|
-
}
|
|
22236
|
-
[
|
|
22237
|
-
);
|
|
22238
|
-
const edgeOf = react.useCallback(
|
|
22239
|
-
(index) => count > 1 && index === 0 ? "start" : count > 1 && index === count - 1 ? "end" : null,
|
|
22240
|
-
[count]
|
|
22689
|
+
(index) => ({
|
|
22690
|
+
left: AXIS_EDGE_PADDING + index * band,
|
|
22691
|
+
right: AXIS_EDGE_PADDING + (index + 1) * band
|
|
22692
|
+
}),
|
|
22693
|
+
[band]
|
|
22241
22694
|
);
|
|
22242
|
-
const minPlotWidth =
|
|
22695
|
+
const minPlotWidth = count * BAND_MIN + AXIS_EDGE_PADDING * 2;
|
|
22243
22696
|
const totalHeight = height ?? 320;
|
|
22244
22697
|
const plotHeight = typeof totalHeight === "number" ? Math.max(0, totalHeight - axisText.height) : `calc(${totalHeight} - ${axisText.height}px)`;
|
|
22245
22698
|
const plotted = react.useMemo(() => {
|
|
@@ -22253,10 +22706,10 @@ var SLineChart = /* @__PURE__ */ react.forwardRef(function SLineChart2({
|
|
|
22253
22706
|
}, [rows, size.width, size.height, xAt, scale]);
|
|
22254
22707
|
const legend = showLegend ? /* @__PURE__ */ jsxRuntime.jsx(ChartLegend, { items: series.map((s, i) => ({ name: s.name, color: colorAt(i) })) }) : null;
|
|
22255
22708
|
const handleMove = (event) => {
|
|
22256
|
-
if (!showTooltip || count === 0 ||
|
|
22709
|
+
if (!showTooltip || count === 0 || band <= 0 || series.length === 0) return;
|
|
22257
22710
|
const rect = event.currentTarget.getBoundingClientRect();
|
|
22258
|
-
const along = event.clientX - rect.left;
|
|
22259
|
-
const category =
|
|
22711
|
+
const along = event.clientX - rect.left - AXIS_EDGE_PADDING;
|
|
22712
|
+
const category = Math.floor(along / band);
|
|
22260
22713
|
setHoveredCategory(category >= 0 && category < categories.length ? category : null);
|
|
22261
22714
|
};
|
|
22262
22715
|
const tooltipItems = hoveredCategory == null ? [] : rows.flatMap((row, seriesIndex) => {
|
|
@@ -22295,10 +22748,7 @@ var SLineChart = /* @__PURE__ */ react.forwardRef(function SLineChart2({
|
|
|
22295
22748
|
categories.map((label, index) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
22296
22749
|
"span",
|
|
22297
22750
|
{
|
|
22298
|
-
className:
|
|
22299
|
-
"absolute top-0 whitespace-nowrap",
|
|
22300
|
-
edgeOf(index) === "start" ? "translate-x-0" : edgeOf(index) === "end" ? "-translate-x-full" : "-translate-x-1/2"
|
|
22301
|
-
),
|
|
22751
|
+
className: "absolute top-0 -translate-x-1/2 whitespace-nowrap",
|
|
22302
22752
|
style: { left: xAt(index) },
|
|
22303
22753
|
children: label
|
|
22304
22754
|
},
|
|
@@ -22479,7 +22929,7 @@ var SLineChart = /* @__PURE__ */ react.forwardRef(function SLineChart2({
|
|
|
22479
22929
|
y: point.y - POINT_RADIUS - VALUE_LABEL_GAP2,
|
|
22480
22930
|
className: hoveredCategory === categoryIndex ? LINE_DATA_LABEL_TYPO_HOVER : LINE_DATA_LABEL_TYPO,
|
|
22481
22931
|
style: { fill: LINE_DATA_LABEL_COLOR },
|
|
22482
|
-
textAnchor:
|
|
22932
|
+
textAnchor: "middle",
|
|
22483
22933
|
dominantBaseline: "auto",
|
|
22484
22934
|
children: formatValue(point.own)
|
|
22485
22935
|
},
|
|
@@ -22487,9 +22937,106 @@ var SLineChart = /* @__PURE__ */ react.forwardRef(function SLineChart2({
|
|
|
22487
22937
|
)
|
|
22488
22938
|
)
|
|
22489
22939
|
) : [],
|
|
22490
|
-
[showValueLabel, plotted, hoveredCategory, formatValue
|
|
22940
|
+
[showValueLabel, plotted, hoveredCategory, formatValue]
|
|
22491
22941
|
);
|
|
22492
22942
|
const inset = { paddingTop: axisSample.height / 2, paddingBottom: axisSample.height / 2 };
|
|
22943
|
+
const axisLabels = /* @__PURE__ */ jsxRuntime.jsx(
|
|
22944
|
+
ChartAxisLabels,
|
|
22945
|
+
{
|
|
22946
|
+
scale,
|
|
22947
|
+
direction: "vertical",
|
|
22948
|
+
format: formatValue,
|
|
22949
|
+
sampleRef: axisSampleRef
|
|
22950
|
+
}
|
|
22951
|
+
);
|
|
22952
|
+
const plot = (
|
|
22953
|
+
// 바깥은 자리(높이·물러남)를, 안쪽은 그림을 맡는다. minWidth 는 바깥에 둬야
|
|
22954
|
+
// 스크롤 컨테이너가 넘치는 폭을 알아본다.
|
|
22955
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
22956
|
+
"div",
|
|
22957
|
+
{
|
|
22958
|
+
className: "relative min-w-0",
|
|
22959
|
+
style: { height: plotHeight, minWidth: minPlotWidth || void 0, ...inset },
|
|
22960
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
22961
|
+
"div",
|
|
22962
|
+
{
|
|
22963
|
+
ref: plotRef,
|
|
22964
|
+
className: "relative h-full w-full",
|
|
22965
|
+
onMouseMove: handleMove,
|
|
22966
|
+
onMouseLeave: () => setHoveredCategory(null),
|
|
22967
|
+
children: [
|
|
22968
|
+
size.width > 0 && size.height > 0 && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
22969
|
+
"svg",
|
|
22970
|
+
{
|
|
22971
|
+
width: size.width,
|
|
22972
|
+
height: size.height,
|
|
22973
|
+
overflow: "visible",
|
|
22974
|
+
role: "img",
|
|
22975
|
+
"aria-label": `${categories.length}\uAC1C \uD56D\uBAA9, ${series.length}\uAC1C \uACC4\uC5F4 ${stacked ? "\uB204\uC801 " : ""}\uAEBE\uC740\uC120 \uADF8\uB798\uD504`,
|
|
22976
|
+
children: [
|
|
22977
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
22978
|
+
"text",
|
|
22979
|
+
{
|
|
22980
|
+
ref: valueLabelRef,
|
|
22981
|
+
"aria-hidden": true,
|
|
22982
|
+
x: 0,
|
|
22983
|
+
y: 0,
|
|
22984
|
+
className: LINE_DATA_LABEL_TYPO_HOVER,
|
|
22985
|
+
dominantBaseline: "auto",
|
|
22986
|
+
style: { visibility: "hidden" },
|
|
22987
|
+
children: "0"
|
|
22988
|
+
}
|
|
22989
|
+
),
|
|
22990
|
+
/* @__PURE__ */ jsxRuntime.jsx(ChartGrid, { scale, direction: "vertical", width: size.width, height: size.height }),
|
|
22991
|
+
hoveredCategory != null && /* @__PURE__ */ jsxRuntime.jsx(
|
|
22992
|
+
"rect",
|
|
22993
|
+
{
|
|
22994
|
+
"aria-hidden": true,
|
|
22995
|
+
x: cellOf(hoveredCategory).left,
|
|
22996
|
+
y: 0,
|
|
22997
|
+
width: cellOf(hoveredCategory).right - cellOf(hoveredCategory).left,
|
|
22998
|
+
height: size.height,
|
|
22999
|
+
style: {
|
|
23000
|
+
fill: "var(--cmp-chart-barContainer-hover-bg)",
|
|
23001
|
+
fillOpacity: "var(--cmp-chart-barContainer-hover-opacity)"
|
|
23002
|
+
}
|
|
23003
|
+
}
|
|
23004
|
+
),
|
|
23005
|
+
areaGradients,
|
|
23006
|
+
areaMask,
|
|
23007
|
+
area && /* @__PURE__ */ jsxRuntime.jsx("g", { mask: `url(#${areaMaskId})`, children: areas }),
|
|
23008
|
+
lines,
|
|
23009
|
+
pointHalos,
|
|
23010
|
+
pointDots,
|
|
23011
|
+
valueLabels
|
|
23012
|
+
]
|
|
23013
|
+
}
|
|
23014
|
+
),
|
|
23015
|
+
showTooltip && hoveredCategory != null && tooltipAnchor != null && tooltipItems.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
|
|
23016
|
+
ChartTooltip,
|
|
23017
|
+
{
|
|
23018
|
+
title: categories[hoveredCategory] ?? "",
|
|
23019
|
+
items: tooltipItems,
|
|
23020
|
+
anchor: tooltipAnchor,
|
|
23021
|
+
bounds: size,
|
|
23022
|
+
gapX: { after: -4, before: -4 },
|
|
23023
|
+
footer: tooltipFooter?.({
|
|
23024
|
+
categoryIndex: hoveredCategory,
|
|
23025
|
+
name: categories[hoveredCategory] ?? "",
|
|
23026
|
+
points: rows.flatMap((row, seriesIndex) => {
|
|
23027
|
+
const point = row[hoveredCategory];
|
|
23028
|
+
if (point == null) return [];
|
|
23029
|
+
return [{ seriesIndex, categoryIndex: hoveredCategory, value: point.own }];
|
|
23030
|
+
})
|
|
23031
|
+
})
|
|
23032
|
+
}
|
|
23033
|
+
)
|
|
23034
|
+
]
|
|
23035
|
+
}
|
|
23036
|
+
)
|
|
23037
|
+
}
|
|
23038
|
+
)
|
|
23039
|
+
);
|
|
22493
23040
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
22494
23041
|
ChartFrame,
|
|
22495
23042
|
{
|
|
@@ -22500,7 +23047,7 @@ var SLineChart = /* @__PURE__ */ react.forwardRef(function SLineChart2({
|
|
|
22500
23047
|
legendPosition,
|
|
22501
23048
|
className,
|
|
22502
23049
|
style,
|
|
22503
|
-
children: /* @__PURE__ */ jsxRuntime.
|
|
23050
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
22504
23051
|
"div",
|
|
22505
23052
|
{
|
|
22506
23053
|
className: "grid",
|
|
@@ -22509,119 +23056,27 @@ var SLineChart = /* @__PURE__ */ react.forwardRef(function SLineChart2({
|
|
|
22509
23056
|
// 눈금은 왼쪽 한 줄뿐이다 — 오른쪽에도 적으면 서로 다른 단위처럼 읽힌다.
|
|
22510
23057
|
gridTemplateColumns: "max-content 1fr"
|
|
22511
23058
|
},
|
|
22512
|
-
children:
|
|
22513
|
-
|
|
22514
|
-
|
|
22515
|
-
|
|
22516
|
-
scale,
|
|
22517
|
-
direction: "vertical",
|
|
22518
|
-
format: formatValue,
|
|
22519
|
-
sampleRef: axisSampleRef
|
|
22520
|
-
}
|
|
22521
|
-
) }),
|
|
22522
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
22523
|
-
"div",
|
|
23059
|
+
children: rotateCategoryLabels ? (
|
|
23060
|
+
// 기울인 이름은 눈금 열 아래까지 뻗으므로 스크롤 상자가 눈금 열까지 감싼다.
|
|
23061
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
23062
|
+
ChartRotatedAxisLayout,
|
|
22524
23063
|
{
|
|
22525
|
-
|
|
22526
|
-
|
|
22527
|
-
|
|
22528
|
-
|
|
22529
|
-
|
|
22530
|
-
|
|
22531
|
-
|
|
22532
|
-
style: { height: plotHeight, minWidth: minPlotWidth || void 0, ...inset },
|
|
22533
|
-
children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
22534
|
-
"div",
|
|
22535
|
-
{
|
|
22536
|
-
ref: plotRef,
|
|
22537
|
-
className: "relative h-full w-full",
|
|
22538
|
-
onMouseMove: handleMove,
|
|
22539
|
-
onMouseLeave: () => setHoveredCategory(null),
|
|
22540
|
-
children: [
|
|
22541
|
-
size.width > 0 && size.height > 0 && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
22542
|
-
"svg",
|
|
22543
|
-
{
|
|
22544
|
-
width: size.width,
|
|
22545
|
-
height: size.height,
|
|
22546
|
-
overflow: "visible",
|
|
22547
|
-
role: "img",
|
|
22548
|
-
"aria-label": `${categories.length}\uAC1C \uD56D\uBAA9, ${series.length}\uAC1C \uACC4\uC5F4 ${stacked ? "\uB204\uC801 " : ""}\uAEBE\uC740\uC120 \uADF8\uB798\uD504`,
|
|
22549
|
-
children: [
|
|
22550
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
22551
|
-
"text",
|
|
22552
|
-
{
|
|
22553
|
-
ref: valueLabelRef,
|
|
22554
|
-
"aria-hidden": true,
|
|
22555
|
-
x: 0,
|
|
22556
|
-
y: 0,
|
|
22557
|
-
className: LINE_DATA_LABEL_TYPO_HOVER,
|
|
22558
|
-
dominantBaseline: "auto",
|
|
22559
|
-
style: { visibility: "hidden" },
|
|
22560
|
-
children: "0"
|
|
22561
|
-
}
|
|
22562
|
-
),
|
|
22563
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
22564
|
-
ChartGrid,
|
|
22565
|
-
{
|
|
22566
|
-
scale,
|
|
22567
|
-
direction: "vertical",
|
|
22568
|
-
width: size.width,
|
|
22569
|
-
height: size.height
|
|
22570
|
-
}
|
|
22571
|
-
),
|
|
22572
|
-
hoveredCategory != null && /* @__PURE__ */ jsxRuntime.jsx(
|
|
22573
|
-
"rect",
|
|
22574
|
-
{
|
|
22575
|
-
"aria-hidden": true,
|
|
22576
|
-
x: cellOf(hoveredCategory).left,
|
|
22577
|
-
y: 0,
|
|
22578
|
-
width: cellOf(hoveredCategory).right - cellOf(hoveredCategory).left,
|
|
22579
|
-
height: size.height,
|
|
22580
|
-
style: {
|
|
22581
|
-
fill: "var(--cmp-chart-barContainer-hover-bg)",
|
|
22582
|
-
fillOpacity: "var(--cmp-chart-barContainer-hover-opacity)"
|
|
22583
|
-
}
|
|
22584
|
-
}
|
|
22585
|
-
),
|
|
22586
|
-
areaGradients,
|
|
22587
|
-
areaMask,
|
|
22588
|
-
area && /* @__PURE__ */ jsxRuntime.jsx("g", { mask: `url(#${areaMaskId})`, children: areas }),
|
|
22589
|
-
lines,
|
|
22590
|
-
pointHalos,
|
|
22591
|
-
pointDots,
|
|
22592
|
-
valueLabels
|
|
22593
|
-
]
|
|
22594
|
-
}
|
|
22595
|
-
),
|
|
22596
|
-
showTooltip && hoveredCategory != null && tooltipAnchor != null && tooltipItems.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
|
|
22597
|
-
ChartTooltip,
|
|
22598
|
-
{
|
|
22599
|
-
title: categories[hoveredCategory] ?? "",
|
|
22600
|
-
items: tooltipItems,
|
|
22601
|
-
anchor: tooltipAnchor,
|
|
22602
|
-
bounds: size,
|
|
22603
|
-
gapX: { after: -4, before: -4 },
|
|
22604
|
-
footer: tooltipFooter?.({
|
|
22605
|
-
categoryIndex: hoveredCategory,
|
|
22606
|
-
name: categories[hoveredCategory] ?? "",
|
|
22607
|
-
points: rows.flatMap((row, seriesIndex) => {
|
|
22608
|
-
const point = row[hoveredCategory];
|
|
22609
|
-
if (point == null) return [];
|
|
22610
|
-
return [{ seriesIndex, categoryIndex: hoveredCategory, value: point.own }];
|
|
22611
|
-
})
|
|
22612
|
-
})
|
|
22613
|
-
}
|
|
22614
|
-
)
|
|
22615
|
-
]
|
|
22616
|
-
}
|
|
22617
|
-
)
|
|
22618
|
-
}
|
|
22619
|
-
),
|
|
22620
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { ref: axisTextRef, children: categoryLabels })
|
|
22621
|
-
]
|
|
23064
|
+
axis: axisLabels,
|
|
23065
|
+
axisStyle: { height: plotHeight, ...inset },
|
|
23066
|
+
plot,
|
|
23067
|
+
minPlotWidth,
|
|
23068
|
+
labels: categories,
|
|
23069
|
+
xAt,
|
|
23070
|
+
labelsRef: axisTextRef
|
|
22622
23071
|
}
|
|
22623
23072
|
)
|
|
22624
|
-
|
|
23073
|
+
) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
23074
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { height: plotHeight, ...inset }, children: axisLabels }),
|
|
23075
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 overflow-x-auto", children: [
|
|
23076
|
+
plot,
|
|
23077
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { ref: axisTextRef, children: categoryLabels })
|
|
23078
|
+
] })
|
|
23079
|
+
] })
|
|
22625
23080
|
}
|
|
22626
23081
|
)
|
|
22627
23082
|
}
|