sellmate-design-system-react 9.0.0-beta.91 → 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 +27 -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/SGnbSystem/README.md +6 -2
- package/dist/components/SGnbSystem/SGnbSystem.d.ts +3 -3
- package/dist/components/SGnbSystem/gnbSystem.config.d.ts +5 -3
- 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/STimePicker/README.md +1 -1
- package/dist/components/STimePicker/STimePicker.d.ts +3 -7
- package/dist/components/STimePicker/TimeListbox.d.ts +21 -0
- package/dist/components/STimeRangePicker/README.md +2 -2
- package/dist/components/STimeRangePicker/STimeRangePicker.d.ts +5 -1
- package/dist/components/STreeTable/STreeTable.examples.d.ts +7 -0
- package/dist/index.cjs +1339 -1128
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1339 -1128
- 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/mode-listbox.d.ts +10 -0
- package/dist/lib/tree-row.d.ts +74 -0
- package/dist/llms-full.txt +55 -11
- package/dist/llms.txt +27 -2
- package/dist/styles.css +12 -37
- 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
|
),
|
|
@@ -13423,9 +13468,20 @@ var SystemButton = /* @__PURE__ */ react.forwardRef(function SystemButton2({
|
|
|
13423
13468
|
}
|
|
13424
13469
|
),
|
|
13425
13470
|
label && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "typo-body-sm-semibold min-w-0 truncate", children: label }),
|
|
13426
|
-
count != null && count > 0 && //
|
|
13471
|
+
count != null && count > 0 && // 개수는 채운 배지로 단다 — 배경·글자색·치수는 xs deep 태그 토큰이 쥔다(시안).
|
|
13472
|
+
// 배지 색은 고정이다: 흰 면은 파랑, 어두운 면(dark·darker)은 노랑 바탕에 검은 글자.
|
|
13473
|
+
// 배지 오른쪽은 버튼 여백(alertButton.paddingX)만으로는 좁다 — 시안이 4px 을 더 준다.
|
|
13427
13474
|
// 대응 토큰이 없어 디자인 값을 그대로 쓴다.
|
|
13428
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
13475
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
13476
|
+
STag,
|
|
13477
|
+
{
|
|
13478
|
+
size: "xs",
|
|
13479
|
+
deep: true,
|
|
13480
|
+
color: color === "light" ? "blue" : "yellow",
|
|
13481
|
+
label: count.toLocaleString(),
|
|
13482
|
+
className: "me-[4px] shrink-0"
|
|
13483
|
+
}
|
|
13484
|
+
)
|
|
13429
13485
|
]
|
|
13430
13486
|
}
|
|
13431
13487
|
);
|
|
@@ -14304,7 +14360,7 @@ var SStepper = /* @__PURE__ */ react.forwardRef(function SStepper2({
|
|
|
14304
14360
|
items.findIndex((item4, index) => getItemValue(item4, index) === value)
|
|
14305
14361
|
);
|
|
14306
14362
|
const lastCompletedIndex = items.reduce(
|
|
14307
|
-
(
|
|
14363
|
+
(lastIndex2, item4, index) => item4.completed === true ? index : lastIndex2,
|
|
14308
14364
|
-1
|
|
14309
14365
|
);
|
|
14310
14366
|
const resolvedClickableIndex = lastCompletedIndex + 1;
|
|
@@ -15762,13 +15818,13 @@ var SSearchInput = /* @__PURE__ */ react.forwardRef(function SSearchInput2({
|
|
|
15762
15818
|
});
|
|
15763
15819
|
|
|
15764
15820
|
// src/components/SSelect/select.config.ts
|
|
15765
|
-
function flattenOptions(options, depth = 1) {
|
|
15821
|
+
function flattenOptions(options, depth = 1, inheritDisabled = true) {
|
|
15766
15822
|
const out = [];
|
|
15767
15823
|
for (const o of options) {
|
|
15768
15824
|
const hasChildren = Array.isArray(o.children) && o.children.length > 0;
|
|
15769
15825
|
if (hasChildren) {
|
|
15770
|
-
const childFlat = flattenOptions(o.children, depth + 1);
|
|
15771
|
-
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);
|
|
15772
15828
|
out.push({
|
|
15773
15829
|
value: String(o.value),
|
|
15774
15830
|
label: o.label,
|
|
@@ -15802,12 +15858,71 @@ function collectGroupLeafValues(flat, groupIndex) {
|
|
|
15802
15858
|
}
|
|
15803
15859
|
return values;
|
|
15804
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
|
+
}
|
|
15805
15919
|
var isHtmlLabel = (label) => /<[a-z][\s\S]*>/i.test(label);
|
|
15806
15920
|
var extractText = (html) => html.replace(/<[^>]*>/g, "");
|
|
15807
15921
|
var OPTION_ROW_HEIGHT = 28;
|
|
15808
15922
|
var DEFAULT_VIRTUAL_BUFFER = 10;
|
|
15809
15923
|
var DEFAULT_REACH_END_THRESHOLD = 10;
|
|
15810
15924
|
var DEFAULT_SEARCH_DEBOUNCE = 300;
|
|
15925
|
+
var NO_SEARCH_COLLAPSED = /* @__PURE__ */ new Set();
|
|
15811
15926
|
var SEARCH_THRESHOLD = 11;
|
|
15812
15927
|
function isListSized(el) {
|
|
15813
15928
|
return el.clientHeight > 0 && getComputedStyle(el).maxHeight !== "none";
|
|
@@ -15862,8 +15977,9 @@ var SSelectImpl = /* @__PURE__ */ react.forwardRef(function SSelect({
|
|
|
15862
15977
|
className,
|
|
15863
15978
|
style
|
|
15864
15979
|
}, ref) {
|
|
15865
|
-
const multi = type === "multi" || type === "multi_depth";
|
|
15980
|
+
const multi = type === "multi" || type === "multi_depth" || type === "multi_tree";
|
|
15866
15981
|
const isDepth = type === "default_depth" || type === "multi_depth";
|
|
15982
|
+
const isTree = type === "tree" || type === "multi_tree";
|
|
15867
15983
|
const pagingEnabled = onReachEnd != null;
|
|
15868
15984
|
const partialOptions = pagingEnabled || serverSearch;
|
|
15869
15985
|
const portalContainer = usePortalContainer();
|
|
@@ -15873,6 +15989,8 @@ var SSelectImpl = /* @__PURE__ */ react.forwardRef(function SSelect({
|
|
|
15873
15989
|
const [open, setOpen] = react.useState(false);
|
|
15874
15990
|
const [pendingOpen, setPendingOpen] = react.useState(false);
|
|
15875
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);
|
|
15876
15994
|
const baseId = react.useId();
|
|
15877
15995
|
const listboxId = `${baseId}-listbox`;
|
|
15878
15996
|
const activeOptionId = `${baseId}-active-option`;
|
|
@@ -15896,10 +16014,11 @@ var SSelectImpl = /* @__PURE__ */ react.forwardRef(function SSelect({
|
|
|
15896
16014
|
const alignSelectedTopRef = react.useRef(false);
|
|
15897
16015
|
const selectedAnchorRef = react.useRef(-1);
|
|
15898
16016
|
const rangeAnchorRef = react.useRef(null);
|
|
15899
|
-
const
|
|
16017
|
+
const groupIsValue = type === "tree";
|
|
16018
|
+
const flat = react.useMemo(() => flattenOptions(options, 1, !groupIsValue), [groupIsValue, options]);
|
|
15900
16019
|
const selectable = react.useMemo(() => selectableOptions(flat), [flat]);
|
|
15901
16020
|
const hasLeaf = react.useMemo(() => flat.some((o) => !o.isGroup), [flat]);
|
|
15902
|
-
const allOptionsDisabled = hasLeaf && selectable.length === 0;
|
|
16021
|
+
const allOptionsDisabled = groupIsValue ? flat.length > 0 && flat.every((o) => o.disabled) : hasLeaf && selectable.length === 0;
|
|
15903
16022
|
const effectiveDisabled = disabled || allOptionsDisabled;
|
|
15904
16023
|
const searchVisible = showSearch && (partialOptions || selectable.length >= SEARCH_THRESHOLD);
|
|
15905
16024
|
const labelByValue = react.useMemo(() => new Map(flat.map((o) => [o.value, o.label])), [flat]);
|
|
@@ -15942,6 +16061,10 @@ var SSelectImpl = /* @__PURE__ */ react.forwardRef(function SSelect({
|
|
|
15942
16061
|
if (next) {
|
|
15943
16062
|
dismissedByPointerRef.current = false;
|
|
15944
16063
|
alignSelectedTopRef.current = true;
|
|
16064
|
+
if (isTree) {
|
|
16065
|
+
setExpanded(ancestorGroupValues(flat, new Set(selectedRef.current)));
|
|
16066
|
+
setSearchCollapsed(NO_SEARCH_COLLAPSED);
|
|
16067
|
+
}
|
|
15945
16068
|
} else {
|
|
15946
16069
|
setPendingOpen(false);
|
|
15947
16070
|
setSearch("");
|
|
@@ -15984,7 +16107,7 @@ var SSelectImpl = /* @__PURE__ */ react.forwardRef(function SSelect({
|
|
|
15984
16107
|
},
|
|
15985
16108
|
resetValidation: () => setRuleError("")
|
|
15986
16109
|
});
|
|
15987
|
-
const
|
|
16110
|
+
const searched = react.useMemo(() => {
|
|
15988
16111
|
if (serverSearch || !searchVisible || search.trim() === "") return flat;
|
|
15989
16112
|
const q = search.trim().toLowerCase();
|
|
15990
16113
|
return filterDepthRows(
|
|
@@ -15992,23 +16115,53 @@ var SSelectImpl = /* @__PURE__ */ react.forwardRef(function SSelect({
|
|
|
15992
16115
|
(o) => (isHtmlLabel(o.label) ? extractText(o.label) : o.label).toLowerCase().includes(q)
|
|
15993
16116
|
);
|
|
15994
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]);
|
|
15995
16129
|
const allValues = selectable.map((o) => o.value);
|
|
15996
16130
|
const allSelected = !partialOptions && allValues.length > 0 && allValues.every((v) => selectedSet.has(v));
|
|
15997
16131
|
const selectAllVisible = multi && showSelectAll && !partialOptions && allValues.length > 0;
|
|
15998
|
-
const hasItems = filtered.some((o) => !o.isGroup);
|
|
16132
|
+
const hasItems = isTree ? filtered.length > 0 : filtered.some((o) => !o.isGroup);
|
|
15999
16133
|
const groupLeafValues = react.useMemo(() => {
|
|
16000
16134
|
const map = /* @__PURE__ */ new Map();
|
|
16001
16135
|
filtered.forEach((o, i) => {
|
|
16002
|
-
if (o.isGroup)
|
|
16136
|
+
if (!o.isGroup) return;
|
|
16137
|
+
map.set(
|
|
16138
|
+
i,
|
|
16139
|
+
treeRows ? collectGroupLeafValues(searched, treeRows.sourceIndex[i]) : collectGroupLeafValues(filtered, i)
|
|
16140
|
+
);
|
|
16003
16141
|
});
|
|
16004
16142
|
return map;
|
|
16005
|
-
}, [filtered]);
|
|
16143
|
+
}, [filtered, searched, treeRows]);
|
|
16006
16144
|
const navigableItems = react.useMemo(() => {
|
|
16007
16145
|
const items = [];
|
|
16008
16146
|
if (selectAllVisible) {
|
|
16009
16147
|
items.push({ key: "__select_all__", isSelectAll: true });
|
|
16010
16148
|
}
|
|
16011
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
|
+
}
|
|
16012
16165
|
if (o.disabled) return;
|
|
16013
16166
|
if (o.isGroup) {
|
|
16014
16167
|
if (!multi) return;
|
|
@@ -16020,7 +16173,7 @@ var SSelectImpl = /* @__PURE__ */ react.forwardRef(function SSelect({
|
|
|
16020
16173
|
}
|
|
16021
16174
|
});
|
|
16022
16175
|
return items;
|
|
16023
|
-
}, [filtered, groupLeafValues, multi, selectAllVisible]);
|
|
16176
|
+
}, [filtered, groupLeafValues, isTree, multi, selectAllVisible]);
|
|
16024
16177
|
const selectedAnchorIndex = react.useMemo(() => {
|
|
16025
16178
|
if (selectedSet.size === 0) return -1;
|
|
16026
16179
|
return navigableItems.findIndex((item4) => item4.value != null && selectedSet.has(item4.value));
|
|
@@ -16031,7 +16184,10 @@ var SSelectImpl = /* @__PURE__ */ react.forwardRef(function SSelect({
|
|
|
16031
16184
|
const rowCount = filtered.length;
|
|
16032
16185
|
const metrics = metricsRef.current;
|
|
16033
16186
|
const lastSearchRef = react.useRef(search);
|
|
16034
|
-
|
|
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) {
|
|
16035
16191
|
lastSearchRef.current = search;
|
|
16036
16192
|
resetRowMetrics(metrics, rowCount);
|
|
16037
16193
|
measuredKeyRef.current = "";
|
|
@@ -16171,7 +16327,7 @@ var SSelectImpl = /* @__PURE__ */ react.forwardRef(function SSelect({
|
|
|
16171
16327
|
);
|
|
16172
16328
|
const fullGroupByLeaf = react.useMemo(() => {
|
|
16173
16329
|
const map = /* @__PURE__ */ new Map();
|
|
16174
|
-
if (!showChips || type !== "multi_depth") return map;
|
|
16330
|
+
if (!showChips || type !== "multi_depth" && type !== "multi_tree") return map;
|
|
16175
16331
|
flat.forEach((o, i) => {
|
|
16176
16332
|
if (!o.isGroup) return;
|
|
16177
16333
|
const leafValues = collectGroupLeafValues(flat, i);
|
|
@@ -16249,6 +16405,21 @@ var SSelectImpl = /* @__PURE__ */ react.forwardRef(function SSelect({
|
|
|
16249
16405
|
rangeAnchorRef.current = null;
|
|
16250
16406
|
emit3(allSelected ? [] : allValues);
|
|
16251
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
|
+
};
|
|
16252
16423
|
const handleToggleGroup = (leafValues) => {
|
|
16253
16424
|
if (leafValues.length === 0) return;
|
|
16254
16425
|
rangeAnchorRef.current = null;
|
|
@@ -16321,6 +16492,7 @@ var SSelectImpl = /* @__PURE__ */ react.forwardRef(function SSelect({
|
|
|
16321
16492
|
const selectFocusedItem = (extend = false) => {
|
|
16322
16493
|
if (focusedIndex < 0 || focusedIndex >= navigableItems.length) return;
|
|
16323
16494
|
const focused = navigableItems[focusedIndex];
|
|
16495
|
+
if (focused.disabled) return;
|
|
16324
16496
|
if (focused.isSelectAll) {
|
|
16325
16497
|
handleSelectAll();
|
|
16326
16498
|
return;
|
|
@@ -16349,6 +16521,41 @@ var SSelectImpl = /* @__PURE__ */ react.forwardRef(function SSelect({
|
|
|
16349
16521
|
moveFocusedIndex("prev");
|
|
16350
16522
|
return;
|
|
16351
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
|
+
}
|
|
16352
16559
|
if (event.key === "Enter") {
|
|
16353
16560
|
event.preventDefault();
|
|
16354
16561
|
selectFocusedItem(event.shiftKey);
|
|
@@ -16393,7 +16600,7 @@ var SSelectImpl = /* @__PURE__ */ react.forwardRef(function SSelect({
|
|
|
16393
16600
|
type: "button",
|
|
16394
16601
|
disabled: effectiveDisabled,
|
|
16395
16602
|
"aria-labelledby": label ? `${fieldLabelId(triggerId)} ${triggerId}` : void 0,
|
|
16396
|
-
"aria-haspopup": "listbox",
|
|
16603
|
+
"aria-haspopup": isTree ? "tree" : "listbox",
|
|
16397
16604
|
"aria-expanded": open,
|
|
16398
16605
|
"aria-controls": open ? listboxId : void 0,
|
|
16399
16606
|
"aria-activedescendant": open && focusedIndex >= 0 ? activeOptionId : void 0,
|
|
@@ -16530,7 +16737,7 @@ var SSelectImpl = /* @__PURE__ */ react.forwardRef(function SSelect({
|
|
|
16530
16737
|
{
|
|
16531
16738
|
ref: searchInputRef,
|
|
16532
16739
|
value: search,
|
|
16533
|
-
onValueChange:
|
|
16740
|
+
onValueChange: handleSearchChange,
|
|
16534
16741
|
onKeyDown: handleKeyDown,
|
|
16535
16742
|
placeholder: "\uAC80\uC0C9",
|
|
16536
16743
|
clearable: true,
|
|
@@ -16549,8 +16756,12 @@ var SSelectImpl = /* @__PURE__ */ react.forwardRef(function SSelect({
|
|
|
16549
16756
|
indexOffset: windowStart,
|
|
16550
16757
|
topSpacer: win.top,
|
|
16551
16758
|
bottomSpacer: win.bottom,
|
|
16552
|
-
setSize: isDepth ? void 0 : rowCount,
|
|
16759
|
+
setSize: isDepth || isTree ? void 0 : rowCount,
|
|
16553
16760
|
multi,
|
|
16761
|
+
tree: isTree,
|
|
16762
|
+
isExpanded: isGroupExpanded,
|
|
16763
|
+
onToggleExpand: handleToggleExpand,
|
|
16764
|
+
treeLines,
|
|
16554
16765
|
selected,
|
|
16555
16766
|
groupLeafValues,
|
|
16556
16767
|
focusedValue: navigableItems[focusedIndex]?.key,
|
|
@@ -16604,6 +16815,10 @@ function ItemList({
|
|
|
16604
16815
|
bottomSpacer,
|
|
16605
16816
|
setSize,
|
|
16606
16817
|
multi,
|
|
16818
|
+
tree,
|
|
16819
|
+
isExpanded,
|
|
16820
|
+
onToggleExpand,
|
|
16821
|
+
treeLines,
|
|
16607
16822
|
selected,
|
|
16608
16823
|
groupLeafValues,
|
|
16609
16824
|
focusedValue,
|
|
@@ -16617,14 +16832,34 @@ function ItemList({
|
|
|
16617
16832
|
onHoverValue
|
|
16618
16833
|
}) {
|
|
16619
16834
|
const selectedSet = react.useMemo(() => new Set(selected), [selected]);
|
|
16835
|
+
const checkboxShiftRef = react.useRef(false);
|
|
16620
16836
|
const itemBase = cn(
|
|
16621
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",
|
|
16622
16838
|
"text-[color:var(--cmp-listBoxItem-content-default)]",
|
|
16623
16839
|
"disabled:cursor-not-allowed disabled:text-[color:var(--cmp-listBoxItem-content-disabled)]"
|
|
16624
16840
|
);
|
|
16625
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)]";
|
|
16626
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", { id: listboxId, role: "listbox", "aria-multiselectable": multi || void 0, children: [
|
|
16627
|
-
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(
|
|
16628
16863
|
"button",
|
|
16629
16864
|
{
|
|
16630
16865
|
type: "button",
|
|
@@ -16645,12 +16880,109 @@ function ItemList({
|
|
|
16645
16880
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1 truncate", children: selectAllLabel })
|
|
16646
16881
|
]
|
|
16647
16882
|
}
|
|
16648
|
-
),
|
|
16883
|
+
)),
|
|
16649
16884
|
topSpacer > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { "aria-hidden": true, style: { height: topSpacer } }),
|
|
16650
16885
|
items.map((o, windowIndex) => {
|
|
16651
16886
|
const idx = windowIndex + indexOffset;
|
|
16652
16887
|
const padLeft = o.depth === 1 ? 12 : o.depth === 2 ? 20 : 28;
|
|
16653
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
|
+
}
|
|
16654
16986
|
if (o.isGroup) {
|
|
16655
16987
|
const isDepth1 = o.depth === 1;
|
|
16656
16988
|
const isFirstRow = idx === 0 && !showSelectAll && !searchVisible;
|
|
@@ -16691,13 +17023,7 @@ function ItemList({
|
|
|
16691
17023
|
onClick: () => onToggleGroup(leafValues),
|
|
16692
17024
|
children: [
|
|
16693
17025
|
/* @__PURE__ */ jsxRuntime.jsx(CheckboxBox, { state: groupState, disabled: groupDisabled, focused: groupFocused }),
|
|
16694
|
-
|
|
16695
|
-
"span",
|
|
16696
|
-
{
|
|
16697
|
-
className: "min-w-0 truncate",
|
|
16698
|
-
dangerouslySetInnerHTML: { __html: sanitizeInlineHtml(o.label) }
|
|
16699
|
-
}
|
|
16700
|
-
) : /* @__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" }),
|
|
16701
17027
|
total > 0 && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
16702
17028
|
"span",
|
|
16703
17029
|
{
|
|
@@ -16774,13 +17100,7 @@ function ItemList({
|
|
|
16774
17100
|
focused: focusedValue === o.value
|
|
16775
17101
|
}
|
|
16776
17102
|
),
|
|
16777
|
-
|
|
16778
|
-
"span",
|
|
16779
|
-
{
|
|
16780
|
-
className: "flex-1 truncate",
|
|
16781
|
-
dangerouslySetInnerHTML: { __html: sanitizeInlineHtml(o.label) }
|
|
16782
|
-
}
|
|
16783
|
-
) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1 truncate", children: o.label })
|
|
17103
|
+
/* @__PURE__ */ jsxRuntime.jsx(OptionLabel, { label: o.label, className: "flex-1 truncate" })
|
|
16784
17104
|
]
|
|
16785
17105
|
},
|
|
16786
17106
|
rowKey
|
|
@@ -16789,8 +17109,11 @@ function ItemList({
|
|
|
16789
17109
|
bottomSpacer > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { "aria-hidden": true, style: { height: bottomSpacer } })
|
|
16790
17110
|
] });
|
|
16791
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
|
+
}
|
|
16792
17115
|
function TriggerLabel({ label }) {
|
|
16793
|
-
return
|
|
17116
|
+
return /* @__PURE__ */ jsxRuntime.jsx(OptionLabel, { label, className: "truncate" });
|
|
16794
17117
|
}
|
|
16795
17118
|
function TriggerRemoveButton({ ariaLabel, onRemove }) {
|
|
16796
17119
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
@@ -17045,8 +17368,8 @@ var STableImpl = /* @__PURE__ */ react.forwardRef(function STable({
|
|
|
17045
17368
|
const [rowCountOverride, setRowCountOverride] = react.useState(null);
|
|
17046
17369
|
const rowCount = rowCountOverride ?? rows.length;
|
|
17047
17370
|
const scrollRef = react.useRef(null);
|
|
17048
|
-
const
|
|
17049
|
-
const [
|
|
17371
|
+
const headerRef = react.useRef(null);
|
|
17372
|
+
const [headerHeight, setHeaderHeight] = react.useState(0);
|
|
17050
17373
|
const [vsRange, setVsRange] = react.useState({ start: 0, end: 0 });
|
|
17051
17374
|
const lastReachEndRef = react.useRef(-1);
|
|
17052
17375
|
const cbRef = react.useRef({ onVirtualUpdate, onVirtualReachEnd, onScroll });
|
|
@@ -17088,17 +17411,13 @@ var STableImpl = /* @__PURE__ */ react.forwardRef(function STable({
|
|
|
17088
17411
|
);
|
|
17089
17412
|
const handleScroll = react.useCallback(() => {
|
|
17090
17413
|
const el = scrollRef.current;
|
|
17091
|
-
if (!el)
|
|
17092
|
-
|
|
17093
|
-
|
|
17094
|
-
}
|
|
17095
|
-
const header = headerScrollRef.current;
|
|
17096
|
-
if (header) header.scrollLeft = el.scrollLeft;
|
|
17097
|
-
setHeaderGutter(el.offsetWidth - el.clientWidth);
|
|
17414
|
+
if (!el) return;
|
|
17415
|
+
const headerH = headerRef.current?.offsetHeight ?? 0;
|
|
17416
|
+
setHeaderHeight(headerH);
|
|
17098
17417
|
setScrolledLeft(el.scrollLeft > 0);
|
|
17099
17418
|
setScrolledRight(el.scrollLeft + el.clientWidth < el.scrollWidth - 1);
|
|
17100
17419
|
if (virtualScroll) {
|
|
17101
|
-
const { start, end } = calcRange(el.scrollTop, el.clientHeight);
|
|
17420
|
+
const { start, end } = calcRange(el.scrollTop, el.clientHeight - headerH);
|
|
17102
17421
|
setVsRange((prev) => prev.start === start && prev.end === end ? prev : { start, end });
|
|
17103
17422
|
}
|
|
17104
17423
|
}, [virtualScroll, calcRange]);
|
|
@@ -17495,7 +17814,7 @@ var STableImpl = /* @__PURE__ */ react.forwardRef(function STable({
|
|
|
17495
17814
|
);
|
|
17496
17815
|
};
|
|
17497
17816
|
const bodyScrollClass = cn(
|
|
17498
|
-
"min-h-0 flex-1 overflow-auto
|
|
17817
|
+
"min-h-0 flex-1 overflow-auto",
|
|
17499
17818
|
SCROLLBAR_CLASS,
|
|
17500
17819
|
SCROLLBAR_TRACK_BG_CLASS,
|
|
17501
17820
|
SCROLLBAR_TRACK_BORDER_CLASS
|
|
@@ -17512,23 +17831,221 @@ var STableImpl = /* @__PURE__ */ react.forwardRef(function STable({
|
|
|
17512
17831
|
selectable && /* @__PURE__ */ jsxRuntime.jsx("col", { style: { width: SELECTABLE_COLUMN_WIDTH } }),
|
|
17513
17832
|
visibleCols.map((col, i) => /* @__PURE__ */ jsxRuntime.jsx("col", { style: col.autoWidth ? void 0 : { width: columnWidths[i] } }, col.name))
|
|
17514
17833
|
] });
|
|
17515
|
-
|
|
17516
|
-
"
|
|
17834
|
+
const headerTable = /* @__PURE__ */ jsxRuntime.jsxs(
|
|
17835
|
+
"table",
|
|
17517
17836
|
{
|
|
17518
|
-
className:
|
|
17519
|
-
style: {
|
|
17520
|
-
"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 },
|
|
17521
17839
|
children: [
|
|
17522
|
-
|
|
17523
|
-
|
|
17524
|
-
{
|
|
17525
|
-
|
|
17526
|
-
|
|
17527
|
-
|
|
17528
|
-
|
|
17529
|
-
|
|
17530
|
-
|
|
17531
|
-
|
|
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)]",
|
|
17532
18049
|
// height가 없으면 루트가 받은 높이를 끝까지 채운다. 호스트가 className으로
|
|
17533
18050
|
// `flex-1 min-h-0` 등을 줘 루트 높이가 확정된 경우, 행이 적어도 테두리 박스가
|
|
17534
18051
|
// 남은 영역을 그대로 차지한다. basis는 auto로 둬 높이가 확정되지 않은
|
|
@@ -17543,220 +18060,20 @@ var STableImpl = /* @__PURE__ */ react.forwardRef(function STable({
|
|
|
17543
18060
|
minHeight: isNoData ? "calc(var(--cmp-table-header-height) + 60px)" : void 0
|
|
17544
18061
|
},
|
|
17545
18062
|
children: [
|
|
17546
|
-
/* @__PURE__ */ jsxRuntime.
|
|
17547
|
-
"div",
|
|
17548
|
-
{
|
|
17549
|
-
|
|
17550
|
-
|
|
17551
|
-
|
|
17552
|
-
|
|
17553
|
-
|
|
17554
|
-
|
|
17555
|
-
|
|
17556
|
-
|
|
17557
|
-
|
|
17558
|
-
|
|
17559
|
-
|
|
17560
|
-
colgroup,
|
|
17561
|
-
/* @__PURE__ */ jsxRuntime.jsxs("thead", { className: cn(isNoData && "opacity-40"), children: [
|
|
17562
|
-
headerGroupInfo && /* @__PURE__ */ jsxRuntime.jsxs("tr", { className: "h-[var(--cmp-table-header-height)] bg-[var(--cmp-table-header-bg)]", children: [
|
|
17563
|
-
selectable && /* @__PURE__ */ jsxRuntime.jsx(
|
|
17564
|
-
"th",
|
|
17565
|
-
{
|
|
17566
|
-
rowSpan: 2,
|
|
17567
|
-
className: cn(
|
|
17568
|
-
"sticky left-0 z-[7] bg-[var(--cmp-table-header-bg)] text-center align-middle",
|
|
17569
|
-
stickyLeft === 0 && scrolledLeft && "shadow-[5px_0_8px_0_rgba(34,34,34,0.1)]"
|
|
17570
|
-
),
|
|
17571
|
-
style: {
|
|
17572
|
-
width: SELECTABLE_COLUMN_WIDTH,
|
|
17573
|
-
minWidth: SELECTABLE_COLUMN_WIDTH,
|
|
17574
|
-
maxWidth: SELECTABLE_COLUMN_WIDTH
|
|
17575
|
-
},
|
|
17576
|
-
children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
17577
|
-
SCheckbox,
|
|
17578
|
-
{
|
|
17579
|
-
value: headerCheck,
|
|
17580
|
-
disabled: !hasSelectableRow,
|
|
17581
|
-
onValueChange: toggleAll
|
|
17582
|
-
}
|
|
17583
|
-
) })
|
|
17584
|
-
}
|
|
17585
|
-
),
|
|
17586
|
-
headerGroupInfo.cells.map((group) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
17587
|
-
"th",
|
|
17588
|
-
{
|
|
17589
|
-
colSpan: group.colSpan,
|
|
17590
|
-
rowSpan: group.rowSpan,
|
|
17591
|
-
className: cn(
|
|
17592
|
-
"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)]",
|
|
17593
|
-
group.firstIndex + group.colSpan === visibleCols.length && "border-r-0",
|
|
17594
|
-
group.rowSpan === 2 && "border-b-0",
|
|
17595
|
-
group.className
|
|
17596
|
-
),
|
|
17597
|
-
style: { textAlign: group.align ?? "left", ...group.style },
|
|
17598
|
-
children: [
|
|
17599
|
-
group.render ? group.render() : group.label,
|
|
17600
|
-
group.rowSpan === 2 && group.colSpan === 1 ? headerResizeHitArea(
|
|
17601
|
-
group.firstIndex,
|
|
17602
|
-
`group-resizer-${group.columnNames[0]}`
|
|
17603
|
-
) : null
|
|
17604
|
-
]
|
|
17605
|
-
},
|
|
17606
|
-
group.columnNames.join("|")
|
|
17607
|
-
))
|
|
17608
|
-
] }),
|
|
17609
|
-
/* @__PURE__ */ jsxRuntime.jsxs("tr", { className: "h-[var(--cmp-table-header-height)] bg-[var(--cmp-table-header-bg)]", children: [
|
|
17610
|
-
selectable && !headerGroupInfo && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
17611
|
-
"th",
|
|
17612
|
-
{
|
|
17613
|
-
className: cn(
|
|
17614
|
-
// 원본 .th--selected: 48px 고정폭, 항상 left sticky, 체크박스 중앙정렬
|
|
17615
|
-
// 하단 border는 헤더 래퍼가 연속으로 그리므로 셀에는 두지 않는다.
|
|
17616
|
-
"sticky left-0 z-[7] bg-[var(--cmp-table-header-bg)] text-center align-middle",
|
|
17617
|
-
stickyLeft === 0 && scrolledLeft && "shadow-[5px_0_8px_0_rgba(34,34,34,0.1)]"
|
|
17618
|
-
),
|
|
17619
|
-
style: {
|
|
17620
|
-
width: SELECTABLE_COLUMN_WIDTH,
|
|
17621
|
-
minWidth: SELECTABLE_COLUMN_WIDTH,
|
|
17622
|
-
maxWidth: SELECTABLE_COLUMN_WIDTH
|
|
17623
|
-
},
|
|
17624
|
-
children: [
|
|
17625
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
17626
|
-
SCheckbox,
|
|
17627
|
-
{
|
|
17628
|
-
value: headerCheck,
|
|
17629
|
-
disabled: !hasSelectableRow,
|
|
17630
|
-
onValueChange: toggleAll
|
|
17631
|
-
}
|
|
17632
|
-
) }),
|
|
17633
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
17634
|
-
"span",
|
|
17635
|
-
{
|
|
17636
|
-
"aria-hidden": true,
|
|
17637
|
-
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)]"
|
|
17638
|
-
}
|
|
17639
|
-
)
|
|
17640
|
-
]
|
|
17641
|
-
}
|
|
17642
|
-
),
|
|
17643
|
-
visibleCols.map((col, i) => {
|
|
17644
|
-
if (headerGroupInfo?.rowSpanNames.has(col.name)) return null;
|
|
17645
|
-
const isSortable = !!col.sortable && !col.renderHeader;
|
|
17646
|
-
const sortDir = isSortable && sort?.name === col.name ? sort.dir : void 0;
|
|
17647
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
17648
|
-
"th",
|
|
17649
|
-
{
|
|
17650
|
-
className: cn(
|
|
17651
|
-
// 하단 border는 헤더 래퍼가 연속으로 그린다 (거터 영역까지 이어짐).
|
|
17652
|
-
// leading-[0]: th 의 스트럿(line-height 만큼의 빈 인라인 박스)을 없앤다.
|
|
17653
|
-
// 내용이 inline-flex 라 스트럿이 남아 있으면 라벨보다 큰 컨트롤(정렬 버튼 등)이
|
|
17654
|
-
// 들어올 때 줄 상자가 베이스라인 기준으로 늘어나 헤더 높이를 넘기고
|
|
17655
|
-
// 세로 가운데도 어긋난다. 글자 줄높이는 아래 th__content 가 다시 준다.
|
|
17656
|
-
headerGroupInfo && "border-r border-solid border-[color:var(--cmp-table-border-color)]",
|
|
17657
|
-
headerGroupInfo && i === visibleCols.length - 1 && "border-r-0",
|
|
17658
|
-
"relative bg-[var(--cmp-table-header-bg)] px-[16px] text-[12px] font-medium leading-[0] text-[color:var(--sys-color-fg-primary)]",
|
|
17659
|
-
stickyInfo(i).isSticky && "z-[6]",
|
|
17660
|
-
col.thClass
|
|
17661
|
-
),
|
|
17662
|
-
style: {
|
|
17663
|
-
textAlign: col.align ?? "left",
|
|
17664
|
-
boxShadow: stickyShadowValue(i) ?? void 0,
|
|
17665
|
-
...cellStyle(i),
|
|
17666
|
-
...col.thStyle
|
|
17667
|
-
},
|
|
17668
|
-
"aria-sort": isSortable ? sortDir === "asc" ? "ascending" : sortDir === "desc" ? "descending" : "none" : void 0,
|
|
17669
|
-
children: [
|
|
17670
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
17671
|
-
"span",
|
|
17672
|
-
{
|
|
17673
|
-
className: cn(
|
|
17674
|
-
// 원본 th__content: align에 따라 justify-content 적용 (th도 정렬 반영)
|
|
17675
|
-
// th 가 leading-[0] 이므로 글자 줄높이는 여기서 준다 (truncate 가
|
|
17676
|
-
// overflow-hidden 이라 줄높이가 0이면 라벨이 잘려 사라진다).
|
|
17677
|
-
"inline-flex w-full min-w-0 items-center gap-[4px] leading-[20px]",
|
|
17678
|
-
col.align === "center" && "justify-center",
|
|
17679
|
-
col.align === "right" && "justify-end"
|
|
17680
|
-
),
|
|
17681
|
-
children: col.renderHeader ? col.renderHeader(col, i) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
17682
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: col.label }),
|
|
17683
|
-
col.helpText && col.helpText.length > 0 && // 라벨이 좁아져 truncate 되더라도 아이콘까지 함께 줄어들면 안 되므로
|
|
17684
|
-
// shrink-0 래퍼로 감싼다 (STooltip 의 className 은 본문 쪽으로 간다).
|
|
17685
|
-
// 아이콘·크기·색은 SKeyValueTable 헤더 도움말과 동일하게 맞춘다.
|
|
17686
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "inline-flex shrink-0", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
17687
|
-
STooltip2,
|
|
17688
|
-
{
|
|
17689
|
-
trigger: "hover",
|
|
17690
|
-
icon: "helpOutline",
|
|
17691
|
-
iconSize: 12,
|
|
17692
|
-
color: "var(--sys-color-fg-success)",
|
|
17693
|
-
placement: "top",
|
|
17694
|
-
message: col.helpText
|
|
17695
|
-
}
|
|
17696
|
-
) }),
|
|
17697
|
-
col.editable && /* @__PURE__ */ jsxRuntime.jsx(
|
|
17698
|
-
SIcon,
|
|
17699
|
-
{
|
|
17700
|
-
name: "pageEdit",
|
|
17701
|
-
size: 12,
|
|
17702
|
-
color: "var(--sys-color-link-accent)",
|
|
17703
|
-
label: "\uC218\uC815 \uAC00\uB2A5",
|
|
17704
|
-
className: "shrink-0"
|
|
17705
|
-
}
|
|
17706
|
-
),
|
|
17707
|
-
col.navigable && /* @__PURE__ */ jsxRuntime.jsx(
|
|
17708
|
-
SIcon,
|
|
17709
|
-
{
|
|
17710
|
-
name: "pageMove",
|
|
17711
|
-
size: 12,
|
|
17712
|
-
color: "var(--sys-color-link-accent)",
|
|
17713
|
-
label: "\uC0C8 \uD654\uBA74\uC5D0\uC11C \uC5F4\uAE30",
|
|
17714
|
-
className: "shrink-0"
|
|
17715
|
-
}
|
|
17716
|
-
),
|
|
17717
|
-
col.required && // 라벨이 truncate 돼도 필수 표시는 남아야 하므로 shrink-0.
|
|
17718
|
-
// 아이콘·크기·색은 SKeyValueTable 헤더 필수 표시와 동일하게 맞춘다.
|
|
17719
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
17720
|
-
SIcon,
|
|
17721
|
-
{
|
|
17722
|
-
name: "asterisk",
|
|
17723
|
-
size: 12,
|
|
17724
|
-
color: "var(--sys-color-fg-accent)",
|
|
17725
|
-
className: "shrink-0"
|
|
17726
|
-
}
|
|
17727
|
-
),
|
|
17728
|
-
isSortable && // 미정렬은 updown 을 default(회색)로 — 정렬할 수 있다는 것 자체가
|
|
17729
|
-
// 헤더에 보여야 한다. 정렬 중이면 action 색으로 올려 활성임을 알린다.
|
|
17730
|
-
// 라벨이 truncate 돼도 버튼은 남도록 shrink-0.
|
|
17731
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "inline-flex shrink-0", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
17732
|
-
SGhostButton,
|
|
17733
|
-
{
|
|
17734
|
-
size: "xxs",
|
|
17735
|
-
intent: sortDir ? "action" : "default",
|
|
17736
|
-
icon: sortDir === "asc" ? "arrowUp" : sortDir === "desc" ? "arrowDown" : "updown",
|
|
17737
|
-
ariaLabel: `${col.label} \uC815\uB82C`,
|
|
17738
|
-
onClick: () => toggleSort(col)
|
|
17739
|
-
}
|
|
17740
|
-
) })
|
|
17741
|
-
] })
|
|
17742
|
-
}
|
|
17743
|
-
),
|
|
17744
|
-
headerGroupInfo ? headerResizeHitArea(i, `resizer-${col.name}`) : headerResizer(i, `resizer-${col.name}`)
|
|
17745
|
-
]
|
|
17746
|
-
},
|
|
17747
|
-
col.name
|
|
17748
|
-
);
|
|
17749
|
-
})
|
|
17750
|
-
] })
|
|
17751
|
-
] })
|
|
17752
|
-
]
|
|
17753
|
-
}
|
|
17754
|
-
)
|
|
17755
|
-
}
|
|
17756
|
-
),
|
|
17757
|
-
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 }) }) : (
|
|
17758
|
-
/* 바디: 세로 스크롤은 이 컨테이너에만 생긴다 (헤더 제외) */
|
|
17759
|
-
/* @__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(
|
|
17760
18077
|
"div",
|
|
17761
18078
|
{
|
|
17762
18079
|
ref: scrollRef,
|
|
@@ -17766,136 +18083,140 @@ var STableImpl = /* @__PURE__ */ react.forwardRef(function STable({
|
|
|
17766
18083
|
// 원본 sd-table__scroll-container--loading/--no-data: 로딩·데이터 없음 시 스크롤 차단
|
|
17767
18084
|
(loading2 || rowCount === 0) && "overflow-hidden"
|
|
17768
18085
|
),
|
|
17769
|
-
|
|
17770
|
-
|
|
17771
|
-
{
|
|
17772
|
-
|
|
17773
|
-
|
|
17774
|
-
|
|
17775
|
-
|
|
17776
|
-
|
|
17777
|
-
|
|
17778
|
-
|
|
17779
|
-
|
|
17780
|
-
|
|
17781
|
-
|
|
17782
|
-
|
|
17783
|
-
|
|
17784
|
-
|
|
17785
|
-
|
|
17786
|
-
|
|
17787
|
-
|
|
17788
|
-
"
|
|
17789
|
-
|
|
17790
|
-
|
|
17791
|
-
|
|
17792
|
-
|
|
17793
|
-
|
|
17794
|
-
|
|
17795
|
-
onRowDoubleClick(
|
|
17796
|
-
|
|
17797
|
-
|
|
17798
|
-
|
|
17799
|
-
|
|
17800
|
-
|
|
17801
|
-
|
|
17802
|
-
|
|
17803
|
-
(
|
|
17804
|
-
|
|
17805
|
-
|
|
17806
|
-
|
|
17807
|
-
|
|
17808
|
-
|
|
17809
|
-
|
|
17810
|
-
|
|
17811
|
-
|
|
17812
|
-
|
|
17813
|
-
|
|
17814
|
-
|
|
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)]",
|
|
17815
18167
|
rowBg,
|
|
17816
18168
|
rowHover && "group-hover/table-row:bg-[var(--color-grey-05)]",
|
|
17817
|
-
|
|
17818
|
-
)
|
|
17819
|
-
|
|
17820
|
-
|
|
17821
|
-
|
|
17822
|
-
|
|
17823
|
-
|
|
17824
|
-
|
|
17825
|
-
|
|
17826
|
-
|
|
17827
|
-
|
|
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
|
-
// color, STag 등)까지는 닿지 않는다 — 그건 소비 앱 몫이다.
|
|
17869
|
-
color: rowLocked ? LOCKED_CONTENT : void 0,
|
|
17870
|
-
boxShadow: shadows.length > 0 ? shadows.join(", ") : void 0,
|
|
17871
|
-
...cellStyle(i)
|
|
17872
|
-
};
|
|
17873
|
-
if (col.renderCell) {
|
|
17874
|
-
const cell = col.renderCell({
|
|
17875
|
-
row,
|
|
17876
|
-
value: cellValue(col, row),
|
|
17877
|
-
column: col,
|
|
17878
|
-
colIndex: i,
|
|
17879
|
-
rowIndex: ri,
|
|
17880
|
-
isSelected: isSel,
|
|
17881
|
-
className: tdClassName,
|
|
17882
|
-
style: tdStyle
|
|
17883
|
-
});
|
|
17884
|
-
if (cell == null || cell === false) return null;
|
|
17885
|
-
return /* @__PURE__ */ jsxRuntime.jsx(react.Fragment, { children: cell }, col.name);
|
|
17886
|
-
}
|
|
17887
|
-
return /* @__PURE__ */ jsxRuntime.jsx("td", { className: tdClassName, style: tdStyle, children: col.render ? col.render(row, cellValue(col, row)) : cellValue(col, row) }, col.name);
|
|
17888
|
-
})
|
|
17889
|
-
]
|
|
17890
|
-
},
|
|
17891
|
-
row[rowKey] ?? ri
|
|
17892
|
-
);
|
|
17893
|
-
}),
|
|
17894
|
-
virtualScroll && bottomSpacer > 0 && /* @__PURE__ */ jsxRuntime.jsx("tr", { "aria-hidden": true, style: { height: bottomSpacer }, children: /* @__PURE__ */ jsxRuntime.jsx("td", { colSpan: totalColSpan, className: "p-0" }) })
|
|
17895
|
-
] })
|
|
17896
|
-
]
|
|
17897
|
-
}
|
|
17898
|
-
)
|
|
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
|
+
]
|
|
17899
18220
|
}
|
|
17900
18221
|
)
|
|
17901
18222
|
),
|
|
@@ -19403,13 +19724,21 @@ function SCalendar({
|
|
|
19403
19724
|
}
|
|
19404
19725
|
);
|
|
19405
19726
|
}
|
|
19727
|
+
|
|
19728
|
+
// src/lib/mode-listbox.ts
|
|
19729
|
+
var MODE_LIST_HEIGHT = 240;
|
|
19730
|
+
var MODE_ITEM_HEIGHT = 28;
|
|
19731
|
+
var modeListItemClass = (selected, disabled) => cn(
|
|
19732
|
+
"flex w-full items-center justify-center gap-[var(--cmp-datepicker-mode-item-gap)] border-0 px-[var(--cmp-datepicker-mode-item-paddingX)] py-[var(--cmp-datepicker-mode-item-paddingY)] text-center font-base text-12 leading-[20px]",
|
|
19733
|
+
disabled && "cursor-not-allowed bg-[var(--cmp-datepicker-mode-item-bg-default)] font-medium text-[color:var(--cmp-datepicker-mode-item-content-disabled)]",
|
|
19734
|
+
!disabled && !selected && "cursor-pointer bg-[var(--cmp-datepicker-mode-item-bg-default)] font-medium text-[color:var(--cmp-datepicker-mode-item-content-default)] hover:bg-[var(--cmp-datepicker-mode-item-bg-hover)] hover:text-[color:var(--cmp-datepicker-mode-item-content-hover)]",
|
|
19735
|
+
!disabled && selected && "cursor-pointer bg-[var(--cmp-datepicker-mode-item-bg-default)] font-bold text-[color:var(--cmp-datepicker-mode-item-content-selected)]"
|
|
19736
|
+
);
|
|
19406
19737
|
var MODE_NOUN = {
|
|
19407
19738
|
date: "\uB0A0\uC9DC",
|
|
19408
19739
|
month: "\uC6D4",
|
|
19409
19740
|
year: "\uC5F0\uB3C4"
|
|
19410
19741
|
};
|
|
19411
|
-
var DATEPICKER_MODE_LIST_HEIGHT = 240;
|
|
19412
|
-
var DATEPICKER_MODE_ITEM_HEIGHT = 28;
|
|
19413
19742
|
var YEAR_LIST_EDGE_BUFFER = 24;
|
|
19414
19743
|
var YEAR_LIST_EXPAND_SIZE = 80;
|
|
19415
19744
|
var MONTHS = Array.from({ length: 12 }, (_, i) => i + 1);
|
|
@@ -19460,7 +19789,7 @@ function YearWheel({ value, anchorYear, selectable, onCommit }) {
|
|
|
19460
19789
|
if (!list) return;
|
|
19461
19790
|
const index = anchorYearRef.current - rangeStartRef.current;
|
|
19462
19791
|
syncingScrollRef.current = true;
|
|
19463
|
-
list.scrollTop = index *
|
|
19792
|
+
list.scrollTop = index * MODE_ITEM_HEIGHT;
|
|
19464
19793
|
requestAnimationFrame(() => {
|
|
19465
19794
|
syncingScrollRef.current = false;
|
|
19466
19795
|
});
|
|
@@ -19469,7 +19798,7 @@ function YearWheel({ value, anchorYear, selectable, onCommit }) {
|
|
|
19469
19798
|
const list = listRef.current;
|
|
19470
19799
|
if (!list) return;
|
|
19471
19800
|
if (syncingScrollRef.current) return;
|
|
19472
|
-
if (list.scrollTop <
|
|
19801
|
+
if (list.scrollTop < MODE_ITEM_HEIGHT * YEAR_LIST_EDGE_BUFFER) {
|
|
19473
19802
|
const previousHeight = list.scrollHeight;
|
|
19474
19803
|
setRange((current) => ({
|
|
19475
19804
|
start: current.start - YEAR_LIST_EXPAND_SIZE,
|
|
@@ -19485,7 +19814,7 @@ function YearWheel({ value, anchorYear, selectable, onCommit }) {
|
|
|
19485
19814
|
});
|
|
19486
19815
|
return;
|
|
19487
19816
|
}
|
|
19488
|
-
if (list.scrollHeight - list.scrollTop - list.clientHeight <
|
|
19817
|
+
if (list.scrollHeight - list.scrollTop - list.clientHeight < MODE_ITEM_HEIGHT * YEAR_LIST_EDGE_BUFFER) {
|
|
19489
19818
|
setRange((current) => ({
|
|
19490
19819
|
start: current.start,
|
|
19491
19820
|
end: current.end + YEAR_LIST_EXPAND_SIZE
|
|
@@ -19500,8 +19829,8 @@ function YearWheel({ value, anchorYear, selectable, onCommit }) {
|
|
|
19500
19829
|
"aria-label": "year",
|
|
19501
19830
|
className: "h-full overflow-y-auto overscroll-contain [scrollbar-width:none] [&::-webkit-scrollbar]:hidden",
|
|
19502
19831
|
style: {
|
|
19503
|
-
paddingTop: `calc((${
|
|
19504
|
-
paddingBottom: `calc((${
|
|
19832
|
+
paddingTop: `calc((${MODE_LIST_HEIGHT}px - ${MODE_ITEM_HEIGHT}px) / 2)`,
|
|
19833
|
+
paddingBottom: `calc((${MODE_LIST_HEIGHT}px - ${MODE_ITEM_HEIGHT}px) / 2)`
|
|
19505
19834
|
},
|
|
19506
19835
|
onScroll: handleScroll,
|
|
19507
19836
|
children: years.map((year) => {
|
|
@@ -19514,12 +19843,7 @@ function YearWheel({ value, anchorYear, selectable, onCommit }) {
|
|
|
19514
19843
|
role: "option",
|
|
19515
19844
|
"aria-selected": selected,
|
|
19516
19845
|
disabled,
|
|
19517
|
-
className:
|
|
19518
|
-
"flex w-full items-center justify-center gap-[var(--cmp-datepicker-mode-item-gap)] border-0 px-[var(--cmp-datepicker-mode-item-paddingX)] py-[var(--cmp-datepicker-mode-item-paddingY)] text-center font-base text-12 leading-[20px]",
|
|
19519
|
-
disabled && "cursor-not-allowed bg-[var(--cmp-datepicker-mode-item-bg-default)] font-medium text-[color:var(--cmp-datepicker-mode-item-content-disabled)]",
|
|
19520
|
-
!disabled && !selected && "cursor-pointer bg-[var(--cmp-datepicker-mode-item-bg-default)] font-medium text-[color:var(--cmp-datepicker-mode-item-content-default)] hover:bg-[var(--cmp-datepicker-mode-item-bg-hover)] hover:text-[color:var(--cmp-datepicker-mode-item-content-hover)]",
|
|
19521
|
-
!disabled && selected && "cursor-pointer bg-[var(--cmp-datepicker-mode-item-bg-default)] font-bold text-[color:var(--cmp-datepicker-mode-item-content-selected)]"
|
|
19522
|
-
),
|
|
19846
|
+
className: modeListItemClass(selected, disabled),
|
|
19523
19847
|
onClick: () => !disabled && onCommit(year),
|
|
19524
19848
|
children: year
|
|
19525
19849
|
},
|
|
@@ -19575,7 +19899,7 @@ function MonthWheel({ year, value, selectable, onCommit }) {
|
|
|
19575
19899
|
if (!list) return;
|
|
19576
19900
|
list.scrollTop = Math.max(
|
|
19577
19901
|
0,
|
|
19578
|
-
(initialValueRef.current - 1) *
|
|
19902
|
+
(initialValueRef.current - 1) * MODE_ITEM_HEIGHT - (list.clientHeight - MODE_ITEM_HEIGHT) / 2
|
|
19579
19903
|
);
|
|
19580
19904
|
}, []);
|
|
19581
19905
|
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-full w-full overflow-hidden bg-[var(--cmp-datepicker-mode-bg)]", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -19595,12 +19919,7 @@ function MonthWheel({ year, value, selectable, onCommit }) {
|
|
|
19595
19919
|
role: "option",
|
|
19596
19920
|
"aria-selected": selected,
|
|
19597
19921
|
disabled,
|
|
19598
|
-
className:
|
|
19599
|
-
"flex w-full items-center justify-center gap-[var(--cmp-datepicker-mode-item-gap)] border-0 px-[var(--cmp-datepicker-mode-item-paddingX)] py-[var(--cmp-datepicker-mode-item-paddingY)] text-center font-base text-12 leading-[20px]",
|
|
19600
|
-
disabled && "cursor-not-allowed bg-[var(--cmp-datepicker-mode-item-bg-default)] font-medium text-[color:var(--cmp-datepicker-mode-item-content-disabled)]",
|
|
19601
|
-
!disabled && !selected && "cursor-pointer bg-[var(--cmp-datepicker-mode-item-bg-default)] font-medium text-[color:var(--cmp-datepicker-mode-item-content-default)] hover:bg-[var(--cmp-datepicker-mode-item-bg-hover)] hover:text-[color:var(--cmp-datepicker-mode-item-content-hover)]",
|
|
19602
|
-
!disabled && selected && "cursor-pointer bg-[var(--cmp-datepicker-mode-item-bg-default)] font-bold text-[color:var(--cmp-datepicker-mode-item-content-selected)]"
|
|
19603
|
-
),
|
|
19922
|
+
className: modeListItemClass(selected, disabled),
|
|
19604
19923
|
onClick: () => !disabled && onCommit(month),
|
|
19605
19924
|
children: [
|
|
19606
19925
|
month,
|
|
@@ -21174,6 +21493,169 @@ function ChartAxisLabels({
|
|
|
21174
21493
|
}
|
|
21175
21494
|
);
|
|
21176
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
|
+
}
|
|
21177
21659
|
var ChartFrame = /* @__PURE__ */ react.forwardRef(function ChartFrame2({ title, unit, legend, legendPosition = "top", children, className, style, ...props }, ref) {
|
|
21178
21660
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
21179
21661
|
"div",
|
|
@@ -21365,44 +21847,6 @@ function linePointDimmedColor(palette, index, colors) {
|
|
|
21365
21847
|
function dataValueColor(palette, index, colors) {
|
|
21366
21848
|
return pick(DATA_VALUE_COLORS, DATA_VALUE_PALETTES, palette, index, colors);
|
|
21367
21849
|
}
|
|
21368
|
-
function useElementSize() {
|
|
21369
|
-
const ref = react.useRef(null);
|
|
21370
|
-
const [size, setSize] = react.useState({ width: 0, height: 0 });
|
|
21371
|
-
react.useEffect(() => {
|
|
21372
|
-
const element = ref.current;
|
|
21373
|
-
if (!element) return;
|
|
21374
|
-
const update = () => {
|
|
21375
|
-
const { width, height } = element.getBoundingClientRect();
|
|
21376
|
-
setSize(
|
|
21377
|
-
(prev) => Math.abs(prev.width - width) < 0.5 && Math.abs(prev.height - height) < 0.5 ? prev : { width, height }
|
|
21378
|
-
);
|
|
21379
|
-
};
|
|
21380
|
-
update();
|
|
21381
|
-
if (typeof ResizeObserver === "undefined") return;
|
|
21382
|
-
const observer = new ResizeObserver(update);
|
|
21383
|
-
observer.observe(element);
|
|
21384
|
-
return () => observer.disconnect();
|
|
21385
|
-
}, []);
|
|
21386
|
-
return { ref, size };
|
|
21387
|
-
}
|
|
21388
|
-
function useSvgTextMetrics() {
|
|
21389
|
-
const ref = react.useRef(null);
|
|
21390
|
-
const [metrics, setMetrics] = react.useState({ top: 0, width: 0 });
|
|
21391
|
-
react.useEffect(() => {
|
|
21392
|
-
const element = ref.current;
|
|
21393
|
-
if (!element) return;
|
|
21394
|
-
let box3;
|
|
21395
|
-
try {
|
|
21396
|
-
box3 = element.getBBox();
|
|
21397
|
-
} catch {
|
|
21398
|
-
return;
|
|
21399
|
-
}
|
|
21400
|
-
setMetrics(
|
|
21401
|
-
(prev) => Math.abs(prev.top - box3.y) < 0.5 && Math.abs(prev.width - box3.width) < 0.5 ? prev : { top: box3.y, width: box3.width }
|
|
21402
|
-
);
|
|
21403
|
-
});
|
|
21404
|
-
return { ref, metrics };
|
|
21405
|
-
}
|
|
21406
21850
|
var DOT_SIZE2 = 6;
|
|
21407
21851
|
var DEFAULT_GAP_X = { after: 4, before: 4 };
|
|
21408
21852
|
var ANCHOR_GAP_Y = 0;
|
|
@@ -21564,6 +22008,7 @@ var SBarChart = /* @__PURE__ */ react.forwardRef(function SBarChart2({
|
|
|
21564
22008
|
tickCount = 8,
|
|
21565
22009
|
barSize = BAR_SIZE_MAX,
|
|
21566
22010
|
showValueLabel = true,
|
|
22011
|
+
rotateCategoryLabels = false,
|
|
21567
22012
|
showLegend = true,
|
|
21568
22013
|
legendPosition = "top",
|
|
21569
22014
|
showTooltip = true,
|
|
@@ -21575,9 +22020,10 @@ var SBarChart = /* @__PURE__ */ react.forwardRef(function SBarChart2({
|
|
|
21575
22020
|
style
|
|
21576
22021
|
}, ref) {
|
|
21577
22022
|
const vertical = direction === "vertical";
|
|
22023
|
+
const rotateLabels = rotateCategoryLabels && vertical;
|
|
21578
22024
|
const { ref: plotRef, size } = useElementSize();
|
|
21579
|
-
const { ref: axisTextRef, size: axisText } = useElementSize();
|
|
21580
|
-
const { ref: axisSampleRef, size: axisSample } = useElementSize();
|
|
22025
|
+
const { ref: axisTextRef, size: axisText } = useElementSize(rotateLabels);
|
|
22026
|
+
const { ref: axisSampleRef, size: axisSample } = useElementSize(rotateLabels);
|
|
21581
22027
|
const clipId = react.useId();
|
|
21582
22028
|
const [hoveredCategory, setHoveredCategory] = react.useState(null);
|
|
21583
22029
|
const perCategory = series.length === 1;
|
|
@@ -22036,7 +22482,29 @@ var SBarChart = /* @__PURE__ */ react.forwardRef(function SBarChart2({
|
|
|
22036
22482
|
// 눈금은 왼쪽 한 줄뿐이다 — 오른쪽에도 적으면 서로 다른 단위처럼 읽힌다.
|
|
22037
22483
|
gridTemplateColumns: "max-content 1fr"
|
|
22038
22484
|
},
|
|
22039
|
-
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: [
|
|
22040
22508
|
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { height: plotHeight, ...inset }, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
22041
22509
|
ChartAxisLabels,
|
|
22042
22510
|
{
|
|
@@ -22167,6 +22635,7 @@ var SLineChart = /* @__PURE__ */ react.forwardRef(function SLineChart2({
|
|
|
22167
22635
|
tickCount = 8,
|
|
22168
22636
|
showPoint = true,
|
|
22169
22637
|
showValueLabel = false,
|
|
22638
|
+
rotateCategoryLabels = false,
|
|
22170
22639
|
showLegend = true,
|
|
22171
22640
|
legendPosition = "top",
|
|
22172
22641
|
showTooltip = true,
|
|
@@ -22179,8 +22648,8 @@ var SLineChart = /* @__PURE__ */ react.forwardRef(function SLineChart2({
|
|
|
22179
22648
|
}, ref) {
|
|
22180
22649
|
const smooth = curve === "smooth";
|
|
22181
22650
|
const { ref: plotRef, size } = useElementSize();
|
|
22182
|
-
const { ref: axisTextRef, size: axisText } = useElementSize();
|
|
22183
|
-
const { ref: axisSampleRef, size: axisSample } = useElementSize();
|
|
22651
|
+
const { ref: axisTextRef, size: axisText } = useElementSize(rotateCategoryLabels);
|
|
22652
|
+
const { ref: axisSampleRef, size: axisSample } = useElementSize(rotateCategoryLabels);
|
|
22184
22653
|
const gradientId = react.useId();
|
|
22185
22654
|
const [hoveredCategory, setHoveredCategory] = react.useState(null);
|
|
22186
22655
|
const colorAt = react.useCallback(
|
|
@@ -22214,23 +22683,16 @@ var SLineChart = /* @__PURE__ */ react.forwardRef(function SLineChart2({
|
|
|
22214
22683
|
[rows, tickCount, max]
|
|
22215
22684
|
);
|
|
22216
22685
|
const count = categories.length;
|
|
22217
|
-
const
|
|
22218
|
-
const xAt = react.useCallback(
|
|
22219
|
-
(index) => count > 1 ? index * step : size.width / 2,
|
|
22220
|
-
[count, step, size.width]
|
|
22221
|
-
);
|
|
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]);
|
|
22222
22688
|
const cellOf = react.useCallback(
|
|
22223
|
-
(index) =>
|
|
22224
|
-
left:
|
|
22225
|
-
right:
|
|
22226
|
-
}
|
|
22227
|
-
[
|
|
22228
|
-
);
|
|
22229
|
-
const edgeOf = react.useCallback(
|
|
22230
|
-
(index) => count > 1 && index === 0 ? "start" : count > 1 && index === count - 1 ? "end" : null,
|
|
22231
|
-
[count]
|
|
22689
|
+
(index) => ({
|
|
22690
|
+
left: AXIS_EDGE_PADDING + index * band,
|
|
22691
|
+
right: AXIS_EDGE_PADDING + (index + 1) * band
|
|
22692
|
+
}),
|
|
22693
|
+
[band]
|
|
22232
22694
|
);
|
|
22233
|
-
const minPlotWidth =
|
|
22695
|
+
const minPlotWidth = count * BAND_MIN + AXIS_EDGE_PADDING * 2;
|
|
22234
22696
|
const totalHeight = height ?? 320;
|
|
22235
22697
|
const plotHeight = typeof totalHeight === "number" ? Math.max(0, totalHeight - axisText.height) : `calc(${totalHeight} - ${axisText.height}px)`;
|
|
22236
22698
|
const plotted = react.useMemo(() => {
|
|
@@ -22244,10 +22706,10 @@ var SLineChart = /* @__PURE__ */ react.forwardRef(function SLineChart2({
|
|
|
22244
22706
|
}, [rows, size.width, size.height, xAt, scale]);
|
|
22245
22707
|
const legend = showLegend ? /* @__PURE__ */ jsxRuntime.jsx(ChartLegend, { items: series.map((s, i) => ({ name: s.name, color: colorAt(i) })) }) : null;
|
|
22246
22708
|
const handleMove = (event) => {
|
|
22247
|
-
if (!showTooltip || count === 0 ||
|
|
22709
|
+
if (!showTooltip || count === 0 || band <= 0 || series.length === 0) return;
|
|
22248
22710
|
const rect = event.currentTarget.getBoundingClientRect();
|
|
22249
|
-
const along = event.clientX - rect.left;
|
|
22250
|
-
const category =
|
|
22711
|
+
const along = event.clientX - rect.left - AXIS_EDGE_PADDING;
|
|
22712
|
+
const category = Math.floor(along / band);
|
|
22251
22713
|
setHoveredCategory(category >= 0 && category < categories.length ? category : null);
|
|
22252
22714
|
};
|
|
22253
22715
|
const tooltipItems = hoveredCategory == null ? [] : rows.flatMap((row, seriesIndex) => {
|
|
@@ -22286,10 +22748,7 @@ var SLineChart = /* @__PURE__ */ react.forwardRef(function SLineChart2({
|
|
|
22286
22748
|
categories.map((label, index) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
22287
22749
|
"span",
|
|
22288
22750
|
{
|
|
22289
|
-
className:
|
|
22290
|
-
"absolute top-0 whitespace-nowrap",
|
|
22291
|
-
edgeOf(index) === "start" ? "translate-x-0" : edgeOf(index) === "end" ? "-translate-x-full" : "-translate-x-1/2"
|
|
22292
|
-
),
|
|
22751
|
+
className: "absolute top-0 -translate-x-1/2 whitespace-nowrap",
|
|
22293
22752
|
style: { left: xAt(index) },
|
|
22294
22753
|
children: label
|
|
22295
22754
|
},
|
|
@@ -22470,17 +22929,114 @@ var SLineChart = /* @__PURE__ */ react.forwardRef(function SLineChart2({
|
|
|
22470
22929
|
y: point.y - POINT_RADIUS - VALUE_LABEL_GAP2,
|
|
22471
22930
|
className: hoveredCategory === categoryIndex ? LINE_DATA_LABEL_TYPO_HOVER : LINE_DATA_LABEL_TYPO,
|
|
22472
22931
|
style: { fill: LINE_DATA_LABEL_COLOR },
|
|
22473
|
-
textAnchor:
|
|
22932
|
+
textAnchor: "middle",
|
|
22474
22933
|
dominantBaseline: "auto",
|
|
22475
22934
|
children: formatValue(point.own)
|
|
22476
22935
|
},
|
|
22477
22936
|
`label-${seriesIndex}-${categoryIndex}`
|
|
22478
22937
|
)
|
|
22479
|
-
)
|
|
22480
|
-
) : [],
|
|
22481
|
-
[showValueLabel, plotted, hoveredCategory, formatValue
|
|
22938
|
+
)
|
|
22939
|
+
) : [],
|
|
22940
|
+
[showValueLabel, plotted, hoveredCategory, formatValue]
|
|
22941
|
+
);
|
|
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
|
+
)
|
|
22482
23039
|
);
|
|
22483
|
-
const inset = { paddingTop: axisSample.height / 2, paddingBottom: axisSample.height / 2 };
|
|
22484
23040
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
22485
23041
|
ChartFrame,
|
|
22486
23042
|
{
|
|
@@ -22491,7 +23047,7 @@ var SLineChart = /* @__PURE__ */ react.forwardRef(function SLineChart2({
|
|
|
22491
23047
|
legendPosition,
|
|
22492
23048
|
className,
|
|
22493
23049
|
style,
|
|
22494
|
-
children: /* @__PURE__ */ jsxRuntime.
|
|
23050
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
22495
23051
|
"div",
|
|
22496
23052
|
{
|
|
22497
23053
|
className: "grid",
|
|
@@ -22500,119 +23056,27 @@ var SLineChart = /* @__PURE__ */ react.forwardRef(function SLineChart2({
|
|
|
22500
23056
|
// 눈금은 왼쪽 한 줄뿐이다 — 오른쪽에도 적으면 서로 다른 단위처럼 읽힌다.
|
|
22501
23057
|
gridTemplateColumns: "max-content 1fr"
|
|
22502
23058
|
},
|
|
22503
|
-
children:
|
|
22504
|
-
|
|
22505
|
-
|
|
22506
|
-
|
|
22507
|
-
scale,
|
|
22508
|
-
direction: "vertical",
|
|
22509
|
-
format: formatValue,
|
|
22510
|
-
sampleRef: axisSampleRef
|
|
22511
|
-
}
|
|
22512
|
-
) }),
|
|
22513
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
22514
|
-
"div",
|
|
23059
|
+
children: rotateCategoryLabels ? (
|
|
23060
|
+
// 기울인 이름은 눈금 열 아래까지 뻗으므로 스크롤 상자가 눈금 열까지 감싼다.
|
|
23061
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
23062
|
+
ChartRotatedAxisLayout,
|
|
22515
23063
|
{
|
|
22516
|
-
|
|
22517
|
-
|
|
22518
|
-
|
|
22519
|
-
|
|
22520
|
-
|
|
22521
|
-
|
|
22522
|
-
|
|
22523
|
-
style: { height: plotHeight, minWidth: minPlotWidth || void 0, ...inset },
|
|
22524
|
-
children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
22525
|
-
"div",
|
|
22526
|
-
{
|
|
22527
|
-
ref: plotRef,
|
|
22528
|
-
className: "relative h-full w-full",
|
|
22529
|
-
onMouseMove: handleMove,
|
|
22530
|
-
onMouseLeave: () => setHoveredCategory(null),
|
|
22531
|
-
children: [
|
|
22532
|
-
size.width > 0 && size.height > 0 && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
22533
|
-
"svg",
|
|
22534
|
-
{
|
|
22535
|
-
width: size.width,
|
|
22536
|
-
height: size.height,
|
|
22537
|
-
overflow: "visible",
|
|
22538
|
-
role: "img",
|
|
22539
|
-
"aria-label": `${categories.length}\uAC1C \uD56D\uBAA9, ${series.length}\uAC1C \uACC4\uC5F4 ${stacked ? "\uB204\uC801 " : ""}\uAEBE\uC740\uC120 \uADF8\uB798\uD504`,
|
|
22540
|
-
children: [
|
|
22541
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
22542
|
-
"text",
|
|
22543
|
-
{
|
|
22544
|
-
ref: valueLabelRef,
|
|
22545
|
-
"aria-hidden": true,
|
|
22546
|
-
x: 0,
|
|
22547
|
-
y: 0,
|
|
22548
|
-
className: LINE_DATA_LABEL_TYPO_HOVER,
|
|
22549
|
-
dominantBaseline: "auto",
|
|
22550
|
-
style: { visibility: "hidden" },
|
|
22551
|
-
children: "0"
|
|
22552
|
-
}
|
|
22553
|
-
),
|
|
22554
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
22555
|
-
ChartGrid,
|
|
22556
|
-
{
|
|
22557
|
-
scale,
|
|
22558
|
-
direction: "vertical",
|
|
22559
|
-
width: size.width,
|
|
22560
|
-
height: size.height
|
|
22561
|
-
}
|
|
22562
|
-
),
|
|
22563
|
-
hoveredCategory != null && /* @__PURE__ */ jsxRuntime.jsx(
|
|
22564
|
-
"rect",
|
|
22565
|
-
{
|
|
22566
|
-
"aria-hidden": true,
|
|
22567
|
-
x: cellOf(hoveredCategory).left,
|
|
22568
|
-
y: 0,
|
|
22569
|
-
width: cellOf(hoveredCategory).right - cellOf(hoveredCategory).left,
|
|
22570
|
-
height: size.height,
|
|
22571
|
-
style: {
|
|
22572
|
-
fill: "var(--cmp-chart-barContainer-hover-bg)",
|
|
22573
|
-
fillOpacity: "var(--cmp-chart-barContainer-hover-opacity)"
|
|
22574
|
-
}
|
|
22575
|
-
}
|
|
22576
|
-
),
|
|
22577
|
-
areaGradients,
|
|
22578
|
-
areaMask,
|
|
22579
|
-
area && /* @__PURE__ */ jsxRuntime.jsx("g", { mask: `url(#${areaMaskId})`, children: areas }),
|
|
22580
|
-
lines,
|
|
22581
|
-
pointHalos,
|
|
22582
|
-
pointDots,
|
|
22583
|
-
valueLabels
|
|
22584
|
-
]
|
|
22585
|
-
}
|
|
22586
|
-
),
|
|
22587
|
-
showTooltip && hoveredCategory != null && tooltipAnchor != null && tooltipItems.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
|
|
22588
|
-
ChartTooltip,
|
|
22589
|
-
{
|
|
22590
|
-
title: categories[hoveredCategory] ?? "",
|
|
22591
|
-
items: tooltipItems,
|
|
22592
|
-
anchor: tooltipAnchor,
|
|
22593
|
-
bounds: size,
|
|
22594
|
-
gapX: { after: -4, before: -4 },
|
|
22595
|
-
footer: tooltipFooter?.({
|
|
22596
|
-
categoryIndex: hoveredCategory,
|
|
22597
|
-
name: categories[hoveredCategory] ?? "",
|
|
22598
|
-
points: rows.flatMap((row, seriesIndex) => {
|
|
22599
|
-
const point = row[hoveredCategory];
|
|
22600
|
-
if (point == null) return [];
|
|
22601
|
-
return [{ seriesIndex, categoryIndex: hoveredCategory, value: point.own }];
|
|
22602
|
-
})
|
|
22603
|
-
})
|
|
22604
|
-
}
|
|
22605
|
-
)
|
|
22606
|
-
]
|
|
22607
|
-
}
|
|
22608
|
-
)
|
|
22609
|
-
}
|
|
22610
|
-
),
|
|
22611
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { ref: axisTextRef, children: categoryLabels })
|
|
22612
|
-
]
|
|
23064
|
+
axis: axisLabels,
|
|
23065
|
+
axisStyle: { height: plotHeight, ...inset },
|
|
23066
|
+
plot,
|
|
23067
|
+
minPlotWidth,
|
|
23068
|
+
labels: categories,
|
|
23069
|
+
xAt,
|
|
23070
|
+
labelsRef: axisTextRef
|
|
22613
23071
|
}
|
|
22614
23072
|
)
|
|
22615
|
-
|
|
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
|
+
] })
|
|
22616
23080
|
}
|
|
22617
23081
|
)
|
|
22618
23082
|
}
|
|
@@ -28687,212 +29151,171 @@ function formatTimeDisplay(value, type) {
|
|
|
28687
29151
|
const displayHour = hour % 12 === 0 ? 12 : hour % 12;
|
|
28688
29152
|
return `${meridiem} ${padTimePart(displayHour)}:${padTimePart(minute)}`;
|
|
28689
29153
|
}
|
|
28690
|
-
|
|
28691
|
-
|
|
28692
|
-
|
|
29154
|
+
function scrollSelectedIntoView(list, index, center) {
|
|
29155
|
+
const top = index * MODE_ITEM_HEIGHT;
|
|
29156
|
+
if (center) {
|
|
29157
|
+
list.scrollTop = Math.max(0, top - (list.clientHeight - MODE_ITEM_HEIGHT) / 2);
|
|
29158
|
+
return;
|
|
29159
|
+
}
|
|
29160
|
+
if (top < list.scrollTop) list.scrollTop = top;
|
|
29161
|
+
else if (top + MODE_ITEM_HEIGHT > list.scrollTop + list.clientHeight)
|
|
29162
|
+
list.scrollTop = top + MODE_ITEM_HEIGHT - list.clientHeight;
|
|
29163
|
+
}
|
|
29164
|
+
function lastIndex(arr, pred) {
|
|
29165
|
+
for (let i = arr.length - 1; i >= 0; i--) if (pred(arr[i], i)) return i;
|
|
29166
|
+
return -1;
|
|
29167
|
+
}
|
|
29168
|
+
function ListColumn({ ariaLabel, items }) {
|
|
29169
|
+
const listRef = react.useRef(null);
|
|
29170
|
+
const mountedRef = react.useRef(false);
|
|
29171
|
+
const selectedIndex = items.findIndex((item4) => item4.selected);
|
|
29172
|
+
const tabIndexTarget = selectedIndex >= 0 ? selectedIndex : items.findIndex((item4) => !item4.disabled);
|
|
29173
|
+
react.useLayoutEffect(() => {
|
|
29174
|
+
const list = listRef.current;
|
|
29175
|
+
if (!list || selectedIndex < 0) return;
|
|
29176
|
+
scrollSelectedIntoView(list, selectedIndex, !mountedRef.current);
|
|
29177
|
+
mountedRef.current = true;
|
|
29178
|
+
}, [selectedIndex]);
|
|
29179
|
+
const onKeyDown = (event) => {
|
|
29180
|
+
const buttons = [...listRef.current?.querySelectorAll("button") ?? []];
|
|
29181
|
+
const current = buttons.indexOf(document.activeElement);
|
|
29182
|
+
if (current < 0) return;
|
|
29183
|
+
const enabled = (item4, i) => !item4.disabled && i !== current;
|
|
29184
|
+
let next;
|
|
29185
|
+
if (event.key === "ArrowDown") next = items.findIndex((it, i) => i > current && enabled(it, i));
|
|
29186
|
+
else if (event.key === "ArrowUp")
|
|
29187
|
+
next = lastIndex(items, (it, i) => i < current && enabled(it, i));
|
|
29188
|
+
else if (event.key === "Home") next = items.findIndex((it) => !it.disabled);
|
|
29189
|
+
else if (event.key === "End") next = lastIndex(items, (it) => !it.disabled);
|
|
29190
|
+
else return;
|
|
29191
|
+
event.preventDefault();
|
|
29192
|
+
if (next < 0) return;
|
|
29193
|
+
buttons[next]?.focus({ preventScroll: true });
|
|
29194
|
+
if (listRef.current) scrollSelectedIntoView(listRef.current, next, false);
|
|
29195
|
+
};
|
|
29196
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-full w-[var(--cmp-datepicker-mode-yearMonth-column-width)] min-w-0 overflow-hidden bg-[var(--cmp-datepicker-mode-bg)]", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
29197
|
+
"div",
|
|
29198
|
+
{
|
|
29199
|
+
ref: listRef,
|
|
29200
|
+
role: "listbox",
|
|
29201
|
+
"aria-label": ariaLabel,
|
|
29202
|
+
tabIndex: -1,
|
|
29203
|
+
className: "h-full overflow-y-auto overscroll-contain [scrollbar-width:none] [&::-webkit-scrollbar]:hidden",
|
|
29204
|
+
onKeyDown,
|
|
29205
|
+
children: items.map((item4, index) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
29206
|
+
"button",
|
|
29207
|
+
{
|
|
29208
|
+
type: "button",
|
|
29209
|
+
role: "option",
|
|
29210
|
+
"aria-selected": item4.selected,
|
|
29211
|
+
disabled: item4.disabled,
|
|
29212
|
+
tabIndex: index === tabIndexTarget ? 0 : -1,
|
|
29213
|
+
className: cn(modeListItemClass(item4.selected, item4.disabled), "outline-offset-[-2px]"),
|
|
29214
|
+
onClick: () => !item4.disabled && item4.onSelect(),
|
|
29215
|
+
children: item4.label
|
|
29216
|
+
},
|
|
29217
|
+
item4.key
|
|
29218
|
+
))
|
|
29219
|
+
}
|
|
29220
|
+
) });
|
|
29221
|
+
}
|
|
29222
|
+
var HOURS_24 = Array.from({ length: 24 }, (_, i) => i);
|
|
29223
|
+
var HOURS_12 = [12, ...Array.from({ length: 11 }, (_, i) => i + 1)];
|
|
29224
|
+
function TimeListbox({
|
|
28693
29225
|
value,
|
|
28694
29226
|
showMeridiem,
|
|
28695
29227
|
minuteStep,
|
|
28696
|
-
|
|
29228
|
+
isDisabled,
|
|
29229
|
+
onChange,
|
|
29230
|
+
labelPrefix,
|
|
29231
|
+
elevated = true
|
|
28697
29232
|
}) {
|
|
28698
|
-
const
|
|
28699
|
-
const [
|
|
28700
|
-
const [minute, setMinute] = react.useState(parsed.minute);
|
|
28701
|
-
const [editingPart, setEditingPart] = react.useState(null);
|
|
28702
|
-
const [inputDraft, setInputDraft] = react.useState({});
|
|
29233
|
+
const toState = (v) => v ? parseTimeValue(v) : null;
|
|
29234
|
+
const [time, setTime] = react.useState(() => toState(value));
|
|
28703
29235
|
react.useEffect(() => {
|
|
28704
|
-
|
|
28705
|
-
|
|
28706
|
-
|
|
28707
|
-
const
|
|
28708
|
-
const
|
|
28709
|
-
const
|
|
28710
|
-
|
|
28711
|
-
|
|
28712
|
-
|
|
28713
|
-
|
|
28714
|
-
|
|
28715
|
-
|
|
28716
|
-
|
|
28717
|
-
if (next
|
|
28718
|
-
|
|
28719
|
-
|
|
28720
|
-
const changeMinute = (delta) => {
|
|
28721
|
-
const next = minute + delta;
|
|
28722
|
-
if (next > 59) {
|
|
28723
|
-
commit((hour + 1) % 24, next - 60);
|
|
28724
|
-
return;
|
|
28725
|
-
}
|
|
28726
|
-
if (next < 0) {
|
|
28727
|
-
commit((hour + 23) % 24, next + 60);
|
|
28728
|
-
return;
|
|
28729
|
-
}
|
|
28730
|
-
commit(hour, next);
|
|
28731
|
-
};
|
|
28732
|
-
const changeHour = (delta) => commit((hour + delta + 24) % 24, minute);
|
|
28733
|
-
const sanitizeInput = (next) => next.replace(/\D/g, "").slice(0, 2);
|
|
28734
|
-
const beginEdit = (part, currentValue) => {
|
|
28735
|
-
setEditingPart(part);
|
|
28736
|
-
setInputDraft((prev) => ({ ...prev, [part]: String(currentValue).padStart(2, "0") }));
|
|
28737
|
-
};
|
|
28738
|
-
const commitHourDraft = (draft) => {
|
|
28739
|
-
const clamped = clampTimePart(Number(draft), showMeridiem ? 1 : 0, showMeridiem ? 12 : 23);
|
|
28740
|
-
const nextHour = showMeridiem ? meridiem === "PM" ? clamped === 12 ? 12 : clamped + 12 : clamped === 12 ? 0 : clamped : clamped;
|
|
28741
|
-
commit(nextHour, minute);
|
|
28742
|
-
return clamped;
|
|
28743
|
-
};
|
|
28744
|
-
const commitMinuteDraft = (draft) => {
|
|
28745
|
-
const clamped = clampTimePart(Number(draft), 0, 59);
|
|
28746
|
-
commit(hour, clamped);
|
|
28747
|
-
return clamped;
|
|
28748
|
-
};
|
|
28749
|
-
const endEdit = (part) => {
|
|
28750
|
-
const draft = inputDraft[part];
|
|
28751
|
-
if (part === "hour" && draft != null && draft !== "") {
|
|
28752
|
-
commitHourDraft(draft);
|
|
28753
|
-
}
|
|
28754
|
-
if (part === "minute" && draft != null && draft !== "") {
|
|
28755
|
-
commitMinuteDraft(draft);
|
|
28756
|
-
}
|
|
28757
|
-
setEditingPart(null);
|
|
28758
|
-
setInputDraft({});
|
|
28759
|
-
};
|
|
28760
|
-
const inputStyle = {
|
|
28761
|
-
width: "var(--cmp-timepicker-selector-time-input-width)",
|
|
28762
|
-
height: "var(--cmp-textinput-sm-height)",
|
|
28763
|
-
borderRadius: "var(--cmp-textinput-sm-radius)",
|
|
28764
|
-
border: "var(--cmp-textinput-borderWidth) solid var(--cmp-textinput-border-default)",
|
|
28765
|
-
color: "var(--cmp-textinput-text-default)",
|
|
28766
|
-
fontSize: 12,
|
|
28767
|
-
lineHeight: "20px"
|
|
29236
|
+
setTime(toState(value));
|
|
29237
|
+
}, [value]);
|
|
29238
|
+
const step = Math.max(1, Math.floor(minuteStep));
|
|
29239
|
+
const minutes = Array.from({ length: Math.ceil(60 / step) }, (_, i) => i * step);
|
|
29240
|
+
const blocked = (h, m) => isDisabled?.(h * 60 + m) ?? false;
|
|
29241
|
+
const hourBlocked = (h) => minutes.every((m) => blocked(h, m));
|
|
29242
|
+
const commit = (hours, preferred, part) => {
|
|
29243
|
+
let next = blocked(preferred.hour, preferred.minute) ? null : preferred;
|
|
29244
|
+
for (const h of hours) {
|
|
29245
|
+
if (next) break;
|
|
29246
|
+
const m = minutes.find((mm) => !blocked(h, mm));
|
|
29247
|
+
if (m !== void 0) next = { hour: h, minute: m };
|
|
29248
|
+
}
|
|
29249
|
+
if (!next) return;
|
|
29250
|
+
setTime(next);
|
|
29251
|
+
onChange(formatTimeValue(next.hour, next.minute), part);
|
|
28768
29252
|
};
|
|
28769
|
-
const
|
|
28770
|
-
|
|
28771
|
-
|
|
28772
|
-
|
|
28773
|
-
|
|
28774
|
-
|
|
28775
|
-
|
|
28776
|
-
|
|
28777
|
-
|
|
28778
|
-
|
|
28779
|
-
|
|
28780
|
-
|
|
28781
|
-
|
|
28782
|
-
|
|
28783
|
-
|
|
28784
|
-
|
|
28785
|
-
onClick: () => isHour ? changeHour(1) : changeMinute(minuteStep)
|
|
28786
|
-
}
|
|
28787
|
-
),
|
|
28788
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
28789
|
-
"input",
|
|
28790
|
-
{
|
|
28791
|
-
"aria-label": isHour ? "\uC2DC" : "\uBD84",
|
|
28792
|
-
inputMode: "numeric",
|
|
28793
|
-
value: displayValue2,
|
|
28794
|
-
placeholder: "-",
|
|
28795
|
-
onFocus: () => beginEdit(type, val),
|
|
28796
|
-
onBlur: () => endEdit(type),
|
|
28797
|
-
onChange: (event) => {
|
|
28798
|
-
const draft = sanitizeInput(event.target.value);
|
|
28799
|
-
setInputDraft((prev) => ({ ...prev, [type]: draft }));
|
|
28800
|
-
if (draft === "") return;
|
|
28801
|
-
const raw = Number(draft);
|
|
28802
|
-
if (isHour) {
|
|
28803
|
-
if (draft.length < 2) return;
|
|
28804
|
-
const clamped2 = commitHourDraft(draft);
|
|
28805
|
-
const displayDraft2 = raw === clamped2 ? draft : String(clamped2);
|
|
28806
|
-
setInputDraft((prev) => ({ ...prev, [type]: displayDraft2 }));
|
|
28807
|
-
return;
|
|
28808
|
-
}
|
|
28809
|
-
if (draft.length < 2) return;
|
|
28810
|
-
const clamped = commitMinuteDraft(draft);
|
|
28811
|
-
const displayDraft = raw === clamped ? draft : String(clamped);
|
|
28812
|
-
setInputDraft((prev) => ({ ...prev, [type]: displayDraft }));
|
|
28813
|
-
},
|
|
28814
|
-
className: "box-border bg-white text-center outline-none",
|
|
28815
|
-
style: inputStyle
|
|
28816
|
-
}
|
|
28817
|
-
),
|
|
28818
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
28819
|
-
SGhostButton,
|
|
28820
|
-
{
|
|
28821
|
-
icon: "chevronDown",
|
|
28822
|
-
size: "xs",
|
|
28823
|
-
ariaLabel: `${isHour ? "\uC2DC" : "\uBD84"} \uAC10\uC18C`,
|
|
28824
|
-
onClick: () => isHour ? changeHour(-1) : changeMinute(-minuteStep)
|
|
28825
|
-
}
|
|
28826
|
-
)
|
|
28827
|
-
]
|
|
29253
|
+
const hour = time?.hour;
|
|
29254
|
+
const minute = time?.minute ?? 0;
|
|
29255
|
+
const meridiem = hour === void 0 ? void 0 : hour >= 12 ? "PM" : "AM";
|
|
29256
|
+
const prefix = labelPrefix ? `${labelPrefix} ` : "";
|
|
29257
|
+
const meridiemItems = ["AM", "PM"].map((item4) => {
|
|
29258
|
+
const base = item4 === "AM" ? 0 : 12;
|
|
29259
|
+
const range = HOURS_24.slice(base, base + 12);
|
|
29260
|
+
return {
|
|
29261
|
+
key: item4,
|
|
29262
|
+
label: item4 === "AM" ? "\uC624\uC804" : "\uC624\uD6C4",
|
|
29263
|
+
selected: meridiem === item4,
|
|
29264
|
+
disabled: range.every(hourBlocked),
|
|
29265
|
+
onSelect: () => {
|
|
29266
|
+
if (meridiem === item4) return;
|
|
29267
|
+
const h = hour === void 0 ? base : hour % 12 + base;
|
|
29268
|
+
commit(range, { hour: h, minute }, "meridiem");
|
|
28828
29269
|
}
|
|
28829
|
-
|
|
28830
|
-
};
|
|
29270
|
+
};
|
|
29271
|
+
});
|
|
29272
|
+
const hourItems = showMeridiem ? HOURS_12.map((display) => {
|
|
29273
|
+
const h = display % 12 + (meridiem === "PM" ? 12 : 0);
|
|
29274
|
+
return {
|
|
29275
|
+
key: String(display),
|
|
29276
|
+
label: `${padTimePart(display)}\uC2DC`,
|
|
29277
|
+
selected: hour === h,
|
|
29278
|
+
disabled: hourBlocked(h),
|
|
29279
|
+
onSelect: () => commit([h], { hour: h, minute }, "hour")
|
|
29280
|
+
};
|
|
29281
|
+
}) : HOURS_24.map((h) => ({
|
|
29282
|
+
key: String(h),
|
|
29283
|
+
label: `${padTimePart(h)}\uC2DC`,
|
|
29284
|
+
selected: hour === h,
|
|
29285
|
+
disabled: hourBlocked(h),
|
|
29286
|
+
onSelect: () => commit([h], { hour: h, minute }, "hour")
|
|
29287
|
+
}));
|
|
29288
|
+
const minuteHour = hour ?? (meridiem === "PM" ? 12 : 0);
|
|
29289
|
+
const minuteItems = minutes.map((m) => ({
|
|
29290
|
+
key: String(m),
|
|
29291
|
+
label: `${padTimePart(m)}\uBD84`,
|
|
29292
|
+
selected: time != null && minute === m,
|
|
29293
|
+
disabled: blocked(minuteHour, m),
|
|
29294
|
+
onSelect: () => commit([minuteHour], { hour: minuteHour, minute: m }, "minute")
|
|
29295
|
+
}));
|
|
29296
|
+
const divider = /* @__PURE__ */ jsxRuntime.jsx(
|
|
29297
|
+
"div",
|
|
29298
|
+
{
|
|
29299
|
+
"aria-hidden": true,
|
|
29300
|
+
className: "w-[var(--border-width-1)] self-stretch bg-[var(--sys-color-divider-default)]"
|
|
29301
|
+
}
|
|
29302
|
+
);
|
|
28831
29303
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
28832
29304
|
"div",
|
|
28833
29305
|
{
|
|
28834
|
-
className:
|
|
28835
|
-
|
|
28836
|
-
|
|
28837
|
-
|
|
28838
|
-
|
|
28839
|
-
},
|
|
29306
|
+
className: cn(
|
|
29307
|
+
"inline-flex gap-[var(--cmp-datepicker-mode-yearMonth-gap)] overflow-hidden bg-[var(--cmp-datepicker-mode-bg)]",
|
|
29308
|
+
elevated && "rounded-[var(--cmp-datepicker-mode-radius)] shadow-normal-sm"
|
|
29309
|
+
),
|
|
29310
|
+
style: { height: MODE_LIST_HEIGHT },
|
|
28840
29311
|
children: [
|
|
28841
|
-
showMeridiem && /* @__PURE__ */ jsxRuntime.
|
|
28842
|
-
|
|
28843
|
-
|
|
28844
|
-
|
|
28845
|
-
|
|
28846
|
-
|
|
28847
|
-
|
|
28848
|
-
children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "isolate flex", children: ["AM", "PM"].map((item4, index) => {
|
|
28849
|
-
const selected = item4 === meridiem;
|
|
28850
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
28851
|
-
"button",
|
|
28852
|
-
{
|
|
28853
|
-
type: "button",
|
|
28854
|
-
onClick: () => setMeridiem(item4),
|
|
28855
|
-
className: cn(
|
|
28856
|
-
"box-border h-[var(--cmp-textinput-sm-height)] w-[61px] cursor-pointer border border-solid bg-white text-center text-[12px] font-medium leading-[20px]",
|
|
28857
|
-
index === 0 ? "rounded-l-[var(--cmp-select-sm-radius)]" : "rounded-r-[var(--cmp-select-sm-radius)]",
|
|
28858
|
-
index === 0 ? "-mr-px" : "",
|
|
28859
|
-
selected ? "z-[1] border-[var(--cmp-timepicker-border-focus)] text-[var(--cmp-timepicker-border-focus)]" : "border-[var(--cmp-timepicker-border-default)] text-[var(--cmp-field-hint-color)]"
|
|
28860
|
-
),
|
|
28861
|
-
children: item4 === "AM" ? "\uC624\uC804" : "\uC624\uD6C4"
|
|
28862
|
-
},
|
|
28863
|
-
item4
|
|
28864
|
-
);
|
|
28865
|
-
}) })
|
|
28866
|
-
}
|
|
28867
|
-
),
|
|
28868
|
-
showMeridiem && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-px w-full bg-[var(--sys-color-divider-default)]" }),
|
|
28869
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
28870
|
-
"div",
|
|
28871
|
-
{
|
|
28872
|
-
className: "flex items-center justify-center",
|
|
28873
|
-
style: {
|
|
28874
|
-
gap: "var(--cmp-timepicker-selector-time-gap)",
|
|
28875
|
-
padding: "var(--cmp-timepicker-selector-section-paddingY) var(--cmp-timepicker-selector-section-paddingX)"
|
|
28876
|
-
},
|
|
28877
|
-
children: [
|
|
28878
|
-
renderColumn("hour"),
|
|
28879
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
28880
|
-
"span",
|
|
28881
|
-
{
|
|
28882
|
-
className: "text-center",
|
|
28883
|
-
style: {
|
|
28884
|
-
width: "var(--cmp-timepicker-selector-colon-size)",
|
|
28885
|
-
color: "var(--cmp-field-hint-color)",
|
|
28886
|
-
fontSize: "var(--cmp-timepicker-selector-colon-size)",
|
|
28887
|
-
lineHeight: "var(--cmp-timepicker-selector-colon-size)"
|
|
28888
|
-
},
|
|
28889
|
-
children: ":"
|
|
28890
|
-
}
|
|
28891
|
-
),
|
|
28892
|
-
renderColumn("minute")
|
|
28893
|
-
]
|
|
28894
|
-
}
|
|
28895
|
-
)
|
|
29312
|
+
showMeridiem && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
29313
|
+
/* @__PURE__ */ jsxRuntime.jsx(ListColumn, { ariaLabel: `${prefix}\uC624\uC804/\uC624\uD6C4`, items: meridiemItems }),
|
|
29314
|
+
divider
|
|
29315
|
+
] }),
|
|
29316
|
+
/* @__PURE__ */ jsxRuntime.jsx(ListColumn, { ariaLabel: `${prefix}\uC2DC`, items: hourItems }),
|
|
29317
|
+
divider,
|
|
29318
|
+
/* @__PURE__ */ jsxRuntime.jsx(ListColumn, { ariaLabel: `${prefix}\uBD84`, items: minuteItems })
|
|
28896
29319
|
]
|
|
28897
29320
|
}
|
|
28898
29321
|
);
|
|
@@ -28930,11 +29353,9 @@ function STimePicker({
|
|
|
28930
29353
|
style
|
|
28931
29354
|
}) {
|
|
28932
29355
|
const portalContainer = usePortalContainer();
|
|
28933
|
-
const triggerRef = react.useRef(null);
|
|
28934
29356
|
const [open, setOpen] = react.useState(false);
|
|
28935
29357
|
const triggerId = useFieldControlId();
|
|
28936
29358
|
const triggerTextId = `${triggerId}-text`;
|
|
28937
|
-
const [popoverAlignOffset, setPopoverAlignOffset] = react.useState(0);
|
|
28938
29359
|
const [focused, setFocused] = react.useState(false);
|
|
28939
29360
|
const [ruleError, setRuleError] = react.useState("");
|
|
28940
29361
|
const dims = TIMEPICKER_SIZE_CONFIG[size] ?? TIMEPICKER_SIZE_CONFIG.sm;
|
|
@@ -28955,11 +29376,6 @@ function STimePicker({
|
|
|
28955
29376
|
const resolvedStatus = status ?? (rules && rules.length > 0 ? ruleError !== "" ? "error" : void 0 : void 0);
|
|
28956
29377
|
const commitOpen = (next) => {
|
|
28957
29378
|
if (disabled) return;
|
|
28958
|
-
if (next) {
|
|
28959
|
-
const triggerWidth = triggerRef.current?.getBoundingClientRect().width ?? 0;
|
|
28960
|
-
const selectorWidth = effectiveShowMeridiem ? MERIDIEM_TIME_SELECTOR_WIDTH : TIME_SELECTOR_WIDTH;
|
|
28961
|
-
setPopoverAlignOffset(Math.max(0, (triggerWidth - selectorWidth) / 2));
|
|
28962
|
-
}
|
|
28963
29379
|
setOpen(next);
|
|
28964
29380
|
onOpenChange?.(next);
|
|
28965
29381
|
if (!next && rules && rules.length > 0) setRuleError(runRules(value, rules));
|
|
@@ -28998,7 +29414,6 @@ function STimePicker({
|
|
|
28998
29414
|
/* @__PURE__ */ jsxRuntime.jsx(RPopover13__namespace.Trigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
28999
29415
|
"div",
|
|
29000
29416
|
{
|
|
29001
|
-
ref: triggerRef,
|
|
29002
29417
|
id: triggerId,
|
|
29003
29418
|
role: "button",
|
|
29004
29419
|
"aria-disabled": disabled,
|
|
@@ -29062,20 +29477,22 @@ function STimePicker({
|
|
|
29062
29477
|
RPopover13__namespace.Content,
|
|
29063
29478
|
{
|
|
29064
29479
|
side: "bottom",
|
|
29065
|
-
align: "
|
|
29480
|
+
align: "center",
|
|
29066
29481
|
sideOffset: 4,
|
|
29067
|
-
alignOffset: popoverAlignOffset,
|
|
29068
29482
|
className: cn(
|
|
29069
29483
|
"z-50 max-w-[var(--radix-popover-content-available-width)]",
|
|
29070
29484
|
FLOATING_SLIDE_ANIM
|
|
29071
29485
|
),
|
|
29072
29486
|
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
29073
|
-
|
|
29487
|
+
TimeListbox,
|
|
29074
29488
|
{
|
|
29075
29489
|
value: normalized,
|
|
29076
29490
|
showMeridiem: effectiveShowMeridiem,
|
|
29077
29491
|
minuteStep,
|
|
29078
|
-
onChange:
|
|
29492
|
+
onChange: (time, part) => {
|
|
29493
|
+
commitValue(time);
|
|
29494
|
+
if (part === "minute") commitOpen(false);
|
|
29495
|
+
}
|
|
29079
29496
|
}
|
|
29080
29497
|
)
|
|
29081
29498
|
}
|
|
@@ -29105,7 +29522,6 @@ function formatTimeRangeDisplay(value, type) {
|
|
|
29105
29522
|
if (value == null || value[0] === "" || value[1] === "") return "";
|
|
29106
29523
|
return `${formatTimeDisplay(value[0], type)} ~ ${formatTimeDisplay(value[1], type)}`;
|
|
29107
29524
|
}
|
|
29108
|
-
var TIME_RANGE_SELECTOR_WIDTH = 345;
|
|
29109
29525
|
var MIDDAY_FIELD_MIN_WIDTH = {
|
|
29110
29526
|
sm: 178,
|
|
29111
29527
|
md: 216
|
|
@@ -29114,6 +29530,10 @@ var MIDDAY_FIELD_MAX_WIDTH = {
|
|
|
29114
29530
|
sm: "lg",
|
|
29115
29531
|
md: "lg"
|
|
29116
29532
|
};
|
|
29533
|
+
var toMinutes = (time) => {
|
|
29534
|
+
const { hour, minute } = parseTimeValue(time);
|
|
29535
|
+
return hour * 60 + minute;
|
|
29536
|
+
};
|
|
29117
29537
|
function RangeTimeSelector({
|
|
29118
29538
|
value,
|
|
29119
29539
|
showMeridiem,
|
|
@@ -29121,233 +29541,32 @@ function RangeTimeSelector({
|
|
|
29121
29541
|
rangeOrder,
|
|
29122
29542
|
onChange
|
|
29123
29543
|
}) {
|
|
29124
|
-
const range =
|
|
29125
|
-
const
|
|
29126
|
-
const
|
|
29127
|
-
|
|
29128
|
-
|
|
29129
|
-
const getMeridiem = (side) => getParts(side).hour >= 12 ? "PM" : "AM";
|
|
29130
|
-
const getDisplayHour = (side) => {
|
|
29131
|
-
const { hour } = getParts(side);
|
|
29132
|
-
if (!showMeridiem) return hour;
|
|
29133
|
-
const displayHour = hour % 12;
|
|
29134
|
-
return displayHour === 0 ? 12 : displayHour;
|
|
29135
|
-
};
|
|
29136
|
-
const commit = (side, hour, minute, direction = "unknown") => {
|
|
29137
|
-
const next = formatTimeValue(hour, minute);
|
|
29138
|
-
onChange(
|
|
29139
|
-
normalizeNextRange(side === "start" ? [next, range[1]] : [range[0], next], side, direction)
|
|
29140
|
-
);
|
|
29141
|
-
};
|
|
29142
|
-
const getTimeMinutes2 = (time) => {
|
|
29143
|
-
const { hour, minute } = parseTimeValue(time);
|
|
29144
|
-
return hour * 60 + minute;
|
|
29145
|
-
};
|
|
29146
|
-
const normalizeNextRange = (nextRange, changedSide, direction) => {
|
|
29147
|
-
if (rangeOrder === "allow-cross-day") return nextRange;
|
|
29148
|
-
const [start, end] = nextRange;
|
|
29149
|
-
const [currentStart, currentEnd] = range;
|
|
29150
|
-
if (getTimeMinutes2(start) <= getTimeMinutes2(end)) {
|
|
29151
|
-
const shouldKeepSynced = autoAdjustedRef.current?.side === changedSide && currentStart === currentEnd && start !== end && (direction === "unknown" || autoAdjustedRef.current.direction === direction);
|
|
29152
|
-
if (shouldKeepSynced) {
|
|
29153
|
-
return changedSide === "start" ? [start, start] : [end, end];
|
|
29154
|
-
}
|
|
29155
|
-
autoAdjustedRef.current = null;
|
|
29156
|
-
return nextRange;
|
|
29157
|
-
}
|
|
29158
|
-
autoAdjustedRef.current = {
|
|
29159
|
-
side: changedSide,
|
|
29160
|
-
direction: changedSide === "start" ? "increase" : "decrease"
|
|
29161
|
-
};
|
|
29162
|
-
return changedSide === "start" ? [start, start] : [end, end];
|
|
29163
|
-
};
|
|
29164
|
-
const changeHour = (side, delta) => {
|
|
29165
|
-
const { hour, minute } = getParts(side);
|
|
29166
|
-
commit(side, (hour + delta + 24) % 24, minute, delta > 0 ? "increase" : "decrease");
|
|
29167
|
-
};
|
|
29168
|
-
const changeMinute = (side, delta) => {
|
|
29169
|
-
const { hour, minute } = getParts(side);
|
|
29170
|
-
const next = minute + delta;
|
|
29171
|
-
if (next > 59) {
|
|
29172
|
-
commit(side, (hour + 1) % 24, next - 60, delta > 0 ? "increase" : "decrease");
|
|
29173
|
-
return;
|
|
29174
|
-
}
|
|
29175
|
-
if (next < 0) {
|
|
29176
|
-
commit(side, (hour + 23) % 24, next + 60, delta > 0 ? "increase" : "decrease");
|
|
29544
|
+
const range = value ?? ["00:00", "23:59"];
|
|
29545
|
+
const strict = rangeOrder !== "allow-cross-day";
|
|
29546
|
+
const change = (side, time) => {
|
|
29547
|
+
if (side === "end") {
|
|
29548
|
+
onChange([range[0], time]);
|
|
29177
29549
|
return;
|
|
29178
29550
|
}
|
|
29179
|
-
|
|
29180
|
-
};
|
|
29181
|
-
const setMeridiem = (side, next) => {
|
|
29182
|
-
const current = getMeridiem(side);
|
|
29183
|
-
if (current === next) return;
|
|
29184
|
-
const { hour, minute } = getParts(side);
|
|
29185
|
-
commit(side, next === "AM" ? hour - 12 : hour + 12, minute);
|
|
29186
|
-
};
|
|
29187
|
-
const getEditingKey = (side, part) => `${side}-${part}`;
|
|
29188
|
-
const sanitizeInput = (next) => next.replace(/\D/g, "").slice(0, 2);
|
|
29189
|
-
const beginEdit = (side, part, currentValue) => {
|
|
29190
|
-
const key = getEditingKey(side, part);
|
|
29191
|
-
setEditingKey(key);
|
|
29192
|
-
setInputDraft((prev) => ({ ...prev, [key]: String(currentValue).padStart(2, "0") }));
|
|
29193
|
-
};
|
|
29194
|
-
const commitHourDraft = (side, draft) => {
|
|
29195
|
-
const clamped = clampTimePart(Number(draft), showMeridiem ? 1 : 0, showMeridiem ? 12 : 23);
|
|
29196
|
-
const parts = getParts(side);
|
|
29197
|
-
const meridiem = getMeridiem(side);
|
|
29198
|
-
const nextHour = showMeridiem ? meridiem === "PM" ? clamped === 12 ? 12 : clamped + 12 : clamped === 12 ? 0 : clamped : clamped;
|
|
29199
|
-
commit(side, nextHour, parts.minute);
|
|
29200
|
-
return clamped;
|
|
29201
|
-
};
|
|
29202
|
-
const commitMinuteDraft = (side, draft) => {
|
|
29203
|
-
const { hour } = getParts(side);
|
|
29204
|
-
const clamped = clampTimePart(Number(draft), 0, 59);
|
|
29205
|
-
commit(side, hour, clamped);
|
|
29206
|
-
return clamped;
|
|
29207
|
-
};
|
|
29208
|
-
const endEdit = (side, part) => {
|
|
29209
|
-
const key = getEditingKey(side, part);
|
|
29210
|
-
const draft = inputDraft[key];
|
|
29211
|
-
if (part === "hour" && draft != null && draft !== "") {
|
|
29212
|
-
commitHourDraft(side, draft);
|
|
29213
|
-
}
|
|
29214
|
-
if (part === "minute" && draft != null && draft !== "") {
|
|
29215
|
-
commitMinuteDraft(side, draft);
|
|
29216
|
-
}
|
|
29217
|
-
setEditingKey(null);
|
|
29218
|
-
setInputDraft({});
|
|
29219
|
-
};
|
|
29220
|
-
const inputStyle = {
|
|
29221
|
-
width: "var(--cmp-timepicker-selector-time-input-width)",
|
|
29222
|
-
height: "var(--cmp-textinput-sm-height)",
|
|
29223
|
-
borderRadius: "var(--cmp-textinput-sm-radius)",
|
|
29224
|
-
border: "var(--cmp-textinput-borderWidth) solid var(--cmp-textinput-border-default)",
|
|
29225
|
-
color: "var(--cmp-textinput-text-default)",
|
|
29226
|
-
fontSize: 12,
|
|
29227
|
-
lineHeight: "20px"
|
|
29228
|
-
};
|
|
29229
|
-
const renderColumn = (side, kind) => {
|
|
29230
|
-
const isHour = kind === "hour";
|
|
29231
|
-
const { minute } = getParts(side);
|
|
29232
|
-
const val = isHour ? getDisplayHour(side) : minute;
|
|
29233
|
-
const currentEditingKey = getEditingKey(side, kind);
|
|
29234
|
-
const displayValue2 = editingKey === currentEditingKey ? inputDraft[currentEditingKey] ?? "" : String(val).padStart(2, "0");
|
|
29235
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
29236
|
-
"div",
|
|
29237
|
-
{
|
|
29238
|
-
className: "flex flex-col items-center justify-center",
|
|
29239
|
-
style: { gap: "var(--cmp-timepicker-selector-time-column-gap)" },
|
|
29240
|
-
children: [
|
|
29241
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
29242
|
-
SGhostButton,
|
|
29243
|
-
{
|
|
29244
|
-
icon: "chevronUp",
|
|
29245
|
-
size: "xs",
|
|
29246
|
-
ariaLabel: `${side === "start" ? "\uC2DC\uC791" : "\uC885\uB8CC"} ${isHour ? "\uC2DC" : "\uBD84"} \uC99D\uAC00`,
|
|
29247
|
-
onClick: () => isHour ? changeHour(side, 1) : changeMinute(side, minuteStep)
|
|
29248
|
-
}
|
|
29249
|
-
),
|
|
29250
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
29251
|
-
"input",
|
|
29252
|
-
{
|
|
29253
|
-
"aria-label": `${side === "start" ? "\uC2DC\uC791" : "\uC885\uB8CC"} ${isHour ? "\uC2DC" : "\uBD84"}`,
|
|
29254
|
-
inputMode: "numeric",
|
|
29255
|
-
value: displayValue2,
|
|
29256
|
-
placeholder: "-",
|
|
29257
|
-
onFocus: () => beginEdit(side, kind, val),
|
|
29258
|
-
onBlur: () => endEdit(side, kind),
|
|
29259
|
-
onChange: (event) => {
|
|
29260
|
-
const draft = sanitizeInput(event.target.value);
|
|
29261
|
-
setInputDraft((prev) => ({ ...prev, [currentEditingKey]: draft }));
|
|
29262
|
-
if (draft === "") return;
|
|
29263
|
-
const raw = Number(draft);
|
|
29264
|
-
if (isHour) {
|
|
29265
|
-
if (draft.length < 2) return;
|
|
29266
|
-
const clamped2 = commitHourDraft(side, draft);
|
|
29267
|
-
const displayDraft2 = raw === clamped2 ? draft : String(clamped2);
|
|
29268
|
-
setInputDraft((prev) => ({ ...prev, [currentEditingKey]: displayDraft2 }));
|
|
29269
|
-
return;
|
|
29270
|
-
}
|
|
29271
|
-
if (draft.length < 2) return;
|
|
29272
|
-
const clamped = commitMinuteDraft(side, draft);
|
|
29273
|
-
const displayDraft = raw === clamped ? draft : String(clamped);
|
|
29274
|
-
setInputDraft((prev) => ({ ...prev, [currentEditingKey]: displayDraft }));
|
|
29275
|
-
},
|
|
29276
|
-
className: "box-border bg-white text-center outline-none",
|
|
29277
|
-
style: inputStyle
|
|
29278
|
-
}
|
|
29279
|
-
),
|
|
29280
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
29281
|
-
SGhostButton,
|
|
29282
|
-
{
|
|
29283
|
-
icon: "chevronDown",
|
|
29284
|
-
size: "xs",
|
|
29285
|
-
ariaLabel: `${side === "start" ? "\uC2DC\uC791" : "\uC885\uB8CC"} ${isHour ? "\uC2DC" : "\uBD84"} \uAC10\uC18C`,
|
|
29286
|
-
onClick: () => isHour ? changeHour(side, -1) : changeMinute(side, -minuteStep)
|
|
29287
|
-
}
|
|
29288
|
-
)
|
|
29289
|
-
]
|
|
29290
|
-
}
|
|
29291
|
-
);
|
|
29551
|
+
onChange([time, strict && toMinutes(time) > toMinutes(range[1]) ? time : range[1]]);
|
|
29292
29552
|
};
|
|
29293
29553
|
const renderPanel = (side, title) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
29294
29554
|
"div",
|
|
29295
29555
|
{
|
|
29296
|
-
className: "flex flex-col items-center
|
|
29297
|
-
style: { gap: "var(--cmp-timepicker-selector-range-
|
|
29556
|
+
className: "flex flex-col items-center",
|
|
29557
|
+
style: { gap: "var(--cmp-timepicker-selector-range-heading-gap)" },
|
|
29298
29558
|
children: [
|
|
29299
|
-
/* @__PURE__ */ jsxRuntime.
|
|
29300
|
-
|
|
29301
|
-
|
|
29302
|
-
className: "flex flex-col items-center",
|
|
29303
|
-
style: { gap: "var(--cmp-timepicker-selector-range-heading-gap)" },
|
|
29304
|
-
children: [
|
|
29305
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "whitespace-nowrap text-center text-[12px] font-bold leading-[20px] text-[var(--cmp-timepicker-selector-range-heading-color)]", children: title }),
|
|
29306
|
-
showMeridiem && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "isolate flex", children: ["AM", "PM"].map((item4, index) => {
|
|
29307
|
-
const selected = item4 === getMeridiem(side);
|
|
29308
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
29309
|
-
"button",
|
|
29310
|
-
{
|
|
29311
|
-
type: "button",
|
|
29312
|
-
onClick: () => setMeridiem(side, item4),
|
|
29313
|
-
className: cn(
|
|
29314
|
-
"box-border h-[var(--cmp-textinput-sm-height)] w-[61px] cursor-pointer border border-solid bg-white text-center text-[12px] font-medium leading-[20px]",
|
|
29315
|
-
index === 0 ? "rounded-l-[var(--cmp-select-sm-radius)] -mr-px" : "rounded-r-[var(--cmp-select-sm-radius)]",
|
|
29316
|
-
selected ? "z-[1] border-[var(--cmp-timepicker-border-focus)] text-[var(--cmp-timepicker-border-focus)]" : "border-[var(--cmp-timepicker-border-default)] text-[var(--cmp-field-hint-color)]"
|
|
29317
|
-
),
|
|
29318
|
-
children: item4 === "AM" ? "\uC624\uC804" : "\uC624\uD6C4"
|
|
29319
|
-
},
|
|
29320
|
-
item4
|
|
29321
|
-
);
|
|
29322
|
-
}) })
|
|
29323
|
-
]
|
|
29324
|
-
}
|
|
29325
|
-
),
|
|
29326
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
29327
|
-
"div",
|
|
29559
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "whitespace-nowrap text-center text-[12px] font-bold leading-[20px] text-[var(--cmp-timepicker-selector-range-heading-color)]", children: title }),
|
|
29560
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
29561
|
+
TimeListbox,
|
|
29328
29562
|
{
|
|
29329
|
-
|
|
29330
|
-
|
|
29331
|
-
|
|
29332
|
-
|
|
29333
|
-
|
|
29334
|
-
|
|
29335
|
-
|
|
29336
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
29337
|
-
"span",
|
|
29338
|
-
{
|
|
29339
|
-
className: "text-center",
|
|
29340
|
-
style: {
|
|
29341
|
-
width: "var(--cmp-timepicker-selector-colon-size)",
|
|
29342
|
-
color: "var(--cmp-field-hint-color)",
|
|
29343
|
-
fontSize: "var(--cmp-timepicker-selector-colon-size)",
|
|
29344
|
-
lineHeight: "var(--cmp-timepicker-selector-colon-size)"
|
|
29345
|
-
},
|
|
29346
|
-
children: ":"
|
|
29347
|
-
}
|
|
29348
|
-
),
|
|
29349
|
-
renderColumn(side, "minute")
|
|
29350
|
-
]
|
|
29563
|
+
value: side === "start" ? range[0] : range[1],
|
|
29564
|
+
showMeridiem,
|
|
29565
|
+
minuteStep,
|
|
29566
|
+
labelPrefix: side === "start" ? "\uC2DC\uC791" : "\uC885\uB8CC",
|
|
29567
|
+
elevated: false,
|
|
29568
|
+
isDisabled: strict && side === "end" ? (minutes) => minutes < toMinutes(range[0]) : void 0,
|
|
29569
|
+
onChange: (time) => change(side, time)
|
|
29351
29570
|
}
|
|
29352
29571
|
)
|
|
29353
29572
|
]
|
|
@@ -29406,11 +29625,9 @@ function STimeRangePicker({
|
|
|
29406
29625
|
style
|
|
29407
29626
|
}) {
|
|
29408
29627
|
const portalContainer = usePortalContainer();
|
|
29409
|
-
const triggerRef = react.useRef(null);
|
|
29410
29628
|
const [open, setOpen] = react.useState(false);
|
|
29411
29629
|
const triggerId = useFieldControlId();
|
|
29412
29630
|
const triggerTextId = `${triggerId}-text`;
|
|
29413
|
-
const [popoverAlignOffset, setPopoverAlignOffset] = react.useState(0);
|
|
29414
29631
|
const [focused, setFocused] = react.useState(false);
|
|
29415
29632
|
const [ruleError, setRuleError] = react.useState("");
|
|
29416
29633
|
const dims = TIMEPICKER_SIZE_CONFIG[size] ?? TIMEPICKER_SIZE_CONFIG.sm;
|
|
@@ -29434,10 +29651,6 @@ function STimeRangePicker({
|
|
|
29434
29651
|
const resolvedStatus = status ?? (rules && rules.length > 0 ? ruleError !== "" ? "error" : void 0 : void 0);
|
|
29435
29652
|
const commitOpen = (next) => {
|
|
29436
29653
|
if (disabled) return;
|
|
29437
|
-
if (next) {
|
|
29438
|
-
const triggerWidth = triggerRef.current?.getBoundingClientRect().width ?? 0;
|
|
29439
|
-
setPopoverAlignOffset(Math.max(0, (triggerWidth - TIME_RANGE_SELECTOR_WIDTH) / 2));
|
|
29440
|
-
}
|
|
29441
29654
|
setOpen(next);
|
|
29442
29655
|
onOpenChange?.(next);
|
|
29443
29656
|
if (!next && rules && rules.length > 0) setRuleError(runRules(value, rules));
|
|
@@ -29476,7 +29689,6 @@ function STimeRangePicker({
|
|
|
29476
29689
|
/* @__PURE__ */ jsxRuntime.jsx(RPopover13__namespace.Trigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
29477
29690
|
"div",
|
|
29478
29691
|
{
|
|
29479
|
-
ref: triggerRef,
|
|
29480
29692
|
id: triggerId,
|
|
29481
29693
|
role: "button",
|
|
29482
29694
|
"aria-disabled": disabled,
|
|
@@ -29540,9 +29752,8 @@ function STimeRangePicker({
|
|
|
29540
29752
|
RPopover13__namespace.Content,
|
|
29541
29753
|
{
|
|
29542
29754
|
side: "bottom",
|
|
29543
|
-
align: "
|
|
29755
|
+
align: "center",
|
|
29544
29756
|
sideOffset: 4,
|
|
29545
|
-
alignOffset: popoverAlignOffset,
|
|
29546
29757
|
className: cn(
|
|
29547
29758
|
"z-50 max-w-[var(--radix-popover-content-available-width)]",
|
|
29548
29759
|
FLOATING_SLIDE_ANIM
|