@stackframe/dashboard-ui-components 2.8.89 → 2.8.92
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/chart-legend.d.ts +1 -1
- package/dist/components/data-grid/data-grid-sizing.d.ts +6 -5
- package/dist/components/data-grid/data-grid-sizing.d.ts.map +1 -1
- package/dist/components/data-grid/data-grid-sizing.js +9 -28
- package/dist/components/data-grid/data-grid-sizing.js.map +1 -1
- package/dist/components/data-grid/data-grid.d.ts +17 -237
- package/dist/components/data-grid/data-grid.d.ts.map +1 -1
- package/dist/components/data-grid/data-grid.js +377 -523
- package/dist/components/data-grid/data-grid.js.map +1 -1
- package/dist/components/data-grid/data-grid.test.js +82 -0
- package/dist/components/data-grid/data-grid.test.js.map +1 -1
- package/dist/components/data-grid/index.d.ts +4 -3
- package/dist/components/data-grid/index.js +17 -58
- package/dist/components/data-grid/state.d.ts +4 -61
- package/dist/components/data-grid/state.d.ts.map +1 -1
- package/dist/components/data-grid/state.js +13 -160
- package/dist/components/data-grid/state.js.map +1 -1
- package/dist/components/data-grid/types.d.ts +9 -4
- package/dist/components/data-grid/types.d.ts.map +1 -1
- package/dist/components/data-grid/use-url-state.d.ts +38 -0
- package/dist/components/data-grid/use-url-state.d.ts.map +1 -0
- package/dist/components/data-grid/use-url-state.js +214 -0
- package/dist/components/data-grid/use-url-state.js.map +1 -0
- package/dist/components/data-grid/use-url-state.test.d.ts +1 -0
- package/dist/components/data-grid/use-url-state.test.js +91 -0
- package/dist/components/data-grid/use-url-state.test.js.map +1 -0
- package/dist/components/dialog.d.ts +67 -0
- package/dist/components/dialog.d.ts.map +1 -0
- package/dist/components/dialog.js +100 -0
- package/dist/components/dialog.js.map +1 -0
- package/dist/dashboard-ui-components.global.js +10651 -6388
- package/dist/dashboard-ui-components.global.js.map +4 -4
- package/dist/esm/components/chart-legend.d.ts +1 -1
- package/dist/esm/components/data-grid/data-grid-sizing.d.ts +6 -5
- package/dist/esm/components/data-grid/data-grid-sizing.d.ts.map +1 -1
- package/dist/esm/components/data-grid/data-grid-sizing.js +7 -26
- package/dist/esm/components/data-grid/data-grid-sizing.js.map +1 -1
- package/dist/esm/components/data-grid/data-grid.d.ts +17 -237
- package/dist/esm/components/data-grid/data-grid.d.ts.map +1 -1
- package/dist/esm/components/data-grid/data-grid.js +380 -526
- package/dist/esm/components/data-grid/data-grid.js.map +1 -1
- package/dist/esm/components/data-grid/data-grid.test.js +82 -0
- package/dist/esm/components/data-grid/data-grid.test.js.map +1 -1
- package/dist/esm/components/data-grid/index.d.ts +4 -3
- package/dist/esm/components/data-grid/index.js +4 -3
- package/dist/esm/components/data-grid/state.d.ts +4 -61
- package/dist/esm/components/data-grid/state.d.ts.map +1 -1
- package/dist/esm/components/data-grid/state.js +15 -150
- package/dist/esm/components/data-grid/state.js.map +1 -1
- package/dist/esm/components/data-grid/types.d.ts +9 -4
- package/dist/esm/components/data-grid/types.d.ts.map +1 -1
- package/dist/esm/components/data-grid/use-url-state.d.ts +38 -0
- package/dist/esm/components/data-grid/use-url-state.d.ts.map +1 -0
- package/dist/esm/components/data-grid/use-url-state.js +212 -0
- package/dist/esm/components/data-grid/use-url-state.js.map +1 -0
- package/dist/esm/components/data-grid/use-url-state.test.d.ts +1 -0
- package/dist/esm/components/data-grid/use-url-state.test.js +91 -0
- package/dist/esm/components/data-grid/use-url-state.test.js.map +1 -0
- package/dist/esm/components/dialog.d.ts +67 -0
- package/dist/esm/components/dialog.d.ts.map +1 -0
- package/dist/esm/components/dialog.js +92 -0
- package/dist/esm/components/dialog.js.map +1 -0
- package/dist/esm/index.d.ts +2 -1
- package/dist/esm/index.js +2 -1
- package/dist/index.d.ts +5 -3
- package/dist/index.js +37 -0
- package/package.json +4 -3
|
@@ -10,79 +10,13 @@ react = require_chunk.__toESM(react);
|
|
|
10
10
|
let __state_js = require("./state.js");
|
|
11
11
|
let __strings_js = require("./strings.js");
|
|
12
12
|
let _stackframe_stack_shared_dist_utils_errors = require("@stackframe/stack-shared/dist/utils/errors");
|
|
13
|
+
let _tanstack_react_table = require("@tanstack/react-table");
|
|
13
14
|
let _tanstack_react_virtual = require("@tanstack/react-virtual");
|
|
14
15
|
let ___skeleton_js = require("../skeleton.js");
|
|
15
16
|
let __data_grid_sizing_js = require("./data-grid-sizing.js");
|
|
16
17
|
let __data_grid_toolbar_js = require("./data-grid-toolbar.js");
|
|
17
18
|
|
|
18
19
|
//#region src/components/data-grid/data-grid.tsx
|
|
19
|
-
function ResizeHandle({ onResize, onResizeEnd }) {
|
|
20
|
-
const startXRef = (0, react.useRef)(0);
|
|
21
|
-
const rafRef = (0, react.useRef)(0);
|
|
22
|
-
const latestDeltaRef = (0, react.useRef)(0);
|
|
23
|
-
const callbacksRef = (0, react.useRef)({
|
|
24
|
-
onResize,
|
|
25
|
-
onResizeEnd
|
|
26
|
-
});
|
|
27
|
-
callbacksRef.current = {
|
|
28
|
-
onResize,
|
|
29
|
-
onResizeEnd
|
|
30
|
-
};
|
|
31
|
-
const onPointerDown = (0, react.useCallback)((e) => {
|
|
32
|
-
e.preventDefault();
|
|
33
|
-
e.stopPropagation();
|
|
34
|
-
startXRef.current = e.clientX;
|
|
35
|
-
latestDeltaRef.current = 0;
|
|
36
|
-
const el = e.currentTarget;
|
|
37
|
-
el.setPointerCapture(e.pointerId);
|
|
38
|
-
let finished = false;
|
|
39
|
-
const onMove = (ev) => {
|
|
40
|
-
latestDeltaRef.current = ev.clientX - startXRef.current;
|
|
41
|
-
if (rafRef.current !== 0) return;
|
|
42
|
-
rafRef.current = requestAnimationFrame(() => {
|
|
43
|
-
rafRef.current = 0;
|
|
44
|
-
callbacksRef.current.onResize(latestDeltaRef.current);
|
|
45
|
-
});
|
|
46
|
-
};
|
|
47
|
-
const finish = () => {
|
|
48
|
-
if (finished) return;
|
|
49
|
-
finished = true;
|
|
50
|
-
if (rafRef.current !== 0) {
|
|
51
|
-
cancelAnimationFrame(rafRef.current);
|
|
52
|
-
rafRef.current = 0;
|
|
53
|
-
callbacksRef.current.onResize(latestDeltaRef.current);
|
|
54
|
-
}
|
|
55
|
-
el.removeEventListener("pointermove", onMove);
|
|
56
|
-
el.removeEventListener("pointerup", finish);
|
|
57
|
-
el.removeEventListener("pointercancel", finish);
|
|
58
|
-
el.removeEventListener("lostpointercapture", finish);
|
|
59
|
-
if (el.hasPointerCapture(e.pointerId)) el.releasePointerCapture(e.pointerId);
|
|
60
|
-
callbacksRef.current.onResizeEnd();
|
|
61
|
-
};
|
|
62
|
-
el.addEventListener("pointermove", onMove);
|
|
63
|
-
el.addEventListener("pointerup", finish);
|
|
64
|
-
el.addEventListener("pointercancel", finish);
|
|
65
|
-
el.addEventListener("lostpointercapture", finish);
|
|
66
|
-
}, []);
|
|
67
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
68
|
-
className: (0, _stackframe_stack_ui.cn)("absolute right-0 top-0 bottom-0 z-10 w-[5px] cursor-col-resize touch-none", "group-hover/header:bg-foreground/[0.06] hover:!bg-blue-500/30", "transition-colors duration-100"),
|
|
69
|
-
onClick: (e) => {
|
|
70
|
-
e.preventDefault();
|
|
71
|
-
e.stopPropagation();
|
|
72
|
-
},
|
|
73
|
-
onPointerDown
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
function getNearestVerticalScrollElement(element) {
|
|
77
|
-
let current = element?.parentElement ?? null;
|
|
78
|
-
while (current) {
|
|
79
|
-
const style = window.getComputedStyle(current);
|
|
80
|
-
const overflowY = style.overflowY === "visible" ? style.overflow : style.overflowY;
|
|
81
|
-
if ((overflowY === "auto" || overflowY === "scroll" || overflowY === "overlay") && current.scrollHeight > current.clientHeight + 1) return current;
|
|
82
|
-
current = current.parentElement;
|
|
83
|
-
}
|
|
84
|
-
return window;
|
|
85
|
-
}
|
|
86
20
|
function getEventTargetElement(target) {
|
|
87
21
|
if (target instanceof Element) return target;
|
|
88
22
|
if (target instanceof Node) return target.parentElement;
|
|
@@ -104,41 +38,122 @@ function isDataGridInteractiveRowClickTarget(target) {
|
|
|
104
38
|
function shouldIgnoreRowClick(event) {
|
|
105
39
|
return event.defaultPrevented || isDataGridInteractiveRowClickTarget(event.target);
|
|
106
40
|
}
|
|
107
|
-
function
|
|
41
|
+
function toTanstackSorting(sorting) {
|
|
42
|
+
return sorting.map((s) => ({
|
|
43
|
+
id: s.columnId,
|
|
44
|
+
desc: s.direction === "desc"
|
|
45
|
+
}));
|
|
46
|
+
}
|
|
47
|
+
function fromTanstackSorting(sorting) {
|
|
48
|
+
return sorting.map((s) => ({
|
|
49
|
+
columnId: s.id,
|
|
50
|
+
direction: s.desc ? "desc" : "asc"
|
|
51
|
+
}));
|
|
52
|
+
}
|
|
53
|
+
function toTanstackRowSelection(ids) {
|
|
54
|
+
const out = {};
|
|
55
|
+
for (const id of ids) out[id] = true;
|
|
56
|
+
return out;
|
|
57
|
+
}
|
|
58
|
+
function resolveUpdater(updater, current) {
|
|
59
|
+
return typeof updater === "function" ? updater(current) : updater;
|
|
60
|
+
}
|
|
61
|
+
function distributeFlexWidths(sizes, visibleColumns, available) {
|
|
62
|
+
const flexCols = visibleColumns.filter((c) => c.flex != null && c.flex > 0);
|
|
63
|
+
if (flexCols.length === 0 || available <= 0) return;
|
|
64
|
+
const totalFlex = flexCols.reduce((acc, c) => acc + (c.flex ?? 0), 0);
|
|
65
|
+
let remaining = available;
|
|
66
|
+
flexCols.forEach((col, i) => {
|
|
67
|
+
const share = i === flexCols.length - 1 ? remaining : Math.floor(available * ((col.flex ?? 0) / totalFlex));
|
|
68
|
+
const max = col.maxWidth ?? Infinity;
|
|
69
|
+
const add = Math.max(0, Math.min(share, max - sizes[col.id]));
|
|
70
|
+
sizes[col.id] += add;
|
|
71
|
+
remaining -= add;
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
function selectSingle(current, rowId) {
|
|
75
|
+
const isSelected = current.selectedIds.has(rowId);
|
|
76
|
+
return {
|
|
77
|
+
selectedIds: isSelected ? /* @__PURE__ */ new Set() : new Set([rowId]),
|
|
78
|
+
anchorId: isSelected ? null : rowId
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
function selectRange(current, rowId, allRowIds, additive) {
|
|
82
|
+
if (current.anchorId == null) return null;
|
|
83
|
+
const anchorIdx = allRowIds.indexOf(current.anchorId);
|
|
84
|
+
const currentIdx = allRowIds.indexOf(rowId);
|
|
85
|
+
if (anchorIdx < 0 || currentIdx < 0) return null;
|
|
86
|
+
const start = Math.min(anchorIdx, currentIdx);
|
|
87
|
+
const end = Math.max(anchorIdx, currentIdx);
|
|
88
|
+
const next = additive ? new Set(current.selectedIds) : /* @__PURE__ */ new Set();
|
|
89
|
+
for (let i = start; i <= end; i++) next.add(allRowIds[i]);
|
|
90
|
+
return {
|
|
91
|
+
selectedIds: next,
|
|
92
|
+
anchorId: current.anchorId
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
function selectToggle(current, rowId) {
|
|
96
|
+
const next = new Set(current.selectedIds);
|
|
97
|
+
if (next.has(rowId)) next.delete(rowId);
|
|
98
|
+
else next.add(rowId);
|
|
99
|
+
return {
|
|
100
|
+
selectedIds: next,
|
|
101
|
+
anchorId: rowId
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
function nextSelection(input) {
|
|
105
|
+
const { current, rowId, mode, modifiers, allRowIds } = input;
|
|
106
|
+
if (mode === "single") return selectSingle(current, rowId);
|
|
107
|
+
if (modifiers.shift) {
|
|
108
|
+
const range = selectRange(current, rowId, allRowIds, modifiers.ctrl);
|
|
109
|
+
if (range != null) return range;
|
|
110
|
+
}
|
|
111
|
+
if (modifiers.ctrl) return selectToggle(current, rowId);
|
|
112
|
+
return {
|
|
113
|
+
selectedIds: new Set([rowId]),
|
|
114
|
+
anchorId: rowId
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
function HeaderCell({ header, col, resizable }) {
|
|
118
|
+
const sorted = header.column.getIsSorted();
|
|
119
|
+
const sortIndex = header.column.getSortIndex();
|
|
120
|
+
const totalSorts = header.column.getCanMultiSort() ? header.getContext().table.getState().sorting.length : 0;
|
|
108
121
|
const ctx = {
|
|
109
122
|
columnId: col.id,
|
|
110
123
|
columnDef: col,
|
|
111
|
-
isSorted,
|
|
112
|
-
sortIndex
|
|
124
|
+
isSorted: sorted === false ? false : sorted,
|
|
125
|
+
sortIndex: totalSorts > 1 && sortIndex >= 0 ? sortIndex + 1 : null
|
|
113
126
|
};
|
|
114
127
|
const label = typeof col.header === "function" ? col.header(ctx) : col.header;
|
|
115
|
-
const sortable =
|
|
128
|
+
const sortable = header.column.getCanSort();
|
|
129
|
+
const canResize = resizable && header.column.getCanResize();
|
|
130
|
+
const isResizing = header.column.getIsResizing();
|
|
116
131
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
117
132
|
className: (0, _stackframe_stack_ui.cn)("group/header relative flex items-center gap-1.5 px-3 select-none bg-transparent overflow-hidden", "border-r border-black/[0.04] dark:border-white/[0.04] last:border-r-0", sortable && "cursor-pointer"),
|
|
118
|
-
style:
|
|
133
|
+
style: { width: `calc(var(--col-${col.id}-size) * 1px)` },
|
|
119
134
|
"data-col-id": col.id,
|
|
120
|
-
onClick:
|
|
135
|
+
onClick: sortable ? header.column.getToggleSortingHandler() : void 0,
|
|
121
136
|
role: "columnheader",
|
|
122
|
-
"aria-sort":
|
|
137
|
+
"aria-sort": sorted === "asc" ? "ascending" : sorted === "desc" ? "descending" : "none",
|
|
123
138
|
children: [
|
|
124
139
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
125
140
|
className: (0, _stackframe_stack_ui.cn)("flex-1 min-w-0 truncate text-xs font-semibold uppercase tracking-wider text-muted-foreground", col.align === "center" && "text-center", col.align === "right" && "text-right"),
|
|
126
141
|
children: label
|
|
127
142
|
}),
|
|
128
|
-
|
|
143
|
+
sorted && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
129
144
|
className: "flex items-center gap-0.5 text-foreground/60",
|
|
130
|
-
children: [
|
|
145
|
+
children: [sorted === "asc" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_phosphor_icons_react.ArrowUp, {
|
|
131
146
|
className: "h-3 w-3",
|
|
132
147
|
weight: "bold"
|
|
133
148
|
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_phosphor_icons_react.ArrowDown, {
|
|
134
149
|
className: "h-3 w-3",
|
|
135
150
|
weight: "bold"
|
|
136
|
-
}), sortIndex != null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
151
|
+
}), ctx.sortIndex != null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
137
152
|
className: "text-[10px] font-medium tabular-nums",
|
|
138
|
-
children: sortIndex
|
|
153
|
+
children: ctx.sortIndex
|
|
139
154
|
})]
|
|
140
155
|
}),
|
|
141
|
-
!
|
|
156
|
+
!sorted && sortable && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
142
157
|
className: "hidden group-hover/header:flex items-center text-foreground/20",
|
|
143
158
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_phosphor_icons_react.CaretUp, {
|
|
144
159
|
className: "h-2.5 w-2.5 -mb-[1px]",
|
|
@@ -148,9 +163,14 @@ function HeaderCell({ col, isSorted, sortIndex, resizable, onSort, onResize, onR
|
|
|
148
163
|
weight: "bold"
|
|
149
164
|
})]
|
|
150
165
|
}),
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
166
|
+
canResize && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
167
|
+
onMouseDown: header.getResizeHandler(),
|
|
168
|
+
onTouchStart: header.getResizeHandler(),
|
|
169
|
+
onClick: (e) => {
|
|
170
|
+
e.preventDefault();
|
|
171
|
+
e.stopPropagation();
|
|
172
|
+
},
|
|
173
|
+
className: (0, _stackframe_stack_ui.cn)("absolute right-0 top-0 bottom-0 z-10 w-[5px] cursor-col-resize touch-none", "group-hover/header:bg-foreground/[0.06] hover:!bg-blue-500/30", "transition-colors duration-100", isResizing && "bg-blue-500/40")
|
|
154
174
|
})
|
|
155
175
|
]
|
|
156
176
|
});
|
|
@@ -175,7 +195,7 @@ function DataCell({ col, row, rowId, rowIndex, isSelected, dateDisplay }) {
|
|
|
175
195
|
const isWrap = col.cellOverflow === "wrap";
|
|
176
196
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
177
197
|
className: (0, _stackframe_stack_ui.cn)("flex px-3 bg-transparent overflow-hidden", "border-r border-black/[0.04] dark:border-white/[0.04] last:border-r-0", "text-sm text-foreground", isWrap ? "items-start py-2" : "items-center", col.align === "center" && "justify-center", col.align === "right" && "justify-end", hasCellClick && "cursor-pointer"),
|
|
178
|
-
style:
|
|
198
|
+
style: { width: `calc(var(--col-${col.id}-size) * 1px)` },
|
|
179
199
|
"data-col-id": col.id,
|
|
180
200
|
role: "gridcell",
|
|
181
201
|
onClick: col.onCellClick ? (e) => {
|
|
@@ -210,10 +230,6 @@ function formatCellValue(value) {
|
|
|
210
230
|
children: String(value)
|
|
211
231
|
});
|
|
212
232
|
}
|
|
213
|
-
/** Built-in date cell — mirrors what `formatGridDate` returns but wraps
|
|
214
|
-
* the display in a `<span>` with a `title` tooltip showing the absolute
|
|
215
|
-
* datetime. Only used when the column has `type: "date" | "dateTime"`
|
|
216
|
-
* and no custom `renderCell`. */
|
|
217
233
|
function renderDateCell(value, dateDisplay, col) {
|
|
218
234
|
const { display, tooltip } = (0, __state_js.formatGridDate)(value, dateDisplay, {
|
|
219
235
|
parseValue: col.parseValue,
|
|
@@ -245,7 +261,7 @@ function SkeletonRow({ columns, height, showCheckbox }) {
|
|
|
245
261
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(___skeleton_js.DesignSkeleton, { className: "h-4 w-4 rounded" })
|
|
246
262
|
}), columns.map((col) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
247
263
|
className: "flex items-center px-3 border-r border-black/[0.04] dark:border-white/[0.04] last:border-r-0",
|
|
248
|
-
style:
|
|
264
|
+
style: { width: `calc(var(--col-${col.id}-size) * 1px)` },
|
|
249
265
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(___skeleton_js.DesignSkeleton, {
|
|
250
266
|
className: "h-3.5 rounded-md",
|
|
251
267
|
style: { width: `${40 + hashStringToInt(col.id) % 40}%` }
|
|
@@ -253,7 +269,7 @@ function SkeletonRow({ columns, height, showCheckbox }) {
|
|
|
253
269
|
}, col.id))]
|
|
254
270
|
});
|
|
255
271
|
}
|
|
256
|
-
function SelectionCheckbox({ checked, indeterminate, onChange, ariaLabel }) {
|
|
272
|
+
function SelectionCheckbox({ checked, indeterminate, onChange, ariaLabel, title }) {
|
|
257
273
|
const Icon = indeterminate ? _phosphor_icons_react.MinusSquare : checked ? _phosphor_icons_react.CheckSquare : _phosphor_icons_react.Square;
|
|
258
274
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
259
275
|
className: (0, _stackframe_stack_ui.cn)("flex items-center justify-center w-full h-full", "hover:bg-foreground/[0.04] transition-colors duration-75", checked || indeterminate ? "text-blue-600 dark:text-blue-400" : "text-muted-foreground/40 hover:text-muted-foreground/60"),
|
|
@@ -262,6 +278,7 @@ function SelectionCheckbox({ checked, indeterminate, onChange, ariaLabel }) {
|
|
|
262
278
|
onChange(e);
|
|
263
279
|
},
|
|
264
280
|
"aria-label": ariaLabel,
|
|
281
|
+
title: title ?? ariaLabel,
|
|
265
282
|
role: "checkbox",
|
|
266
283
|
"aria-checked": indeterminate ? "mixed" : checked,
|
|
267
284
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
|
|
@@ -376,396 +393,292 @@ function DefaultFooter({ ctx, pagination, onChange }) {
|
|
|
376
393
|
});
|
|
377
394
|
}
|
|
378
395
|
/**
|
|
379
|
-
* Interactive table
|
|
380
|
-
* and
|
|
381
|
-
*
|
|
382
|
-
*
|
|
396
|
+
* Interactive table built on TanStack Table v8. Sorting, column sizing,
|
|
397
|
+
* visibility, ordering, and pinning are owned by the table instance; we
|
|
398
|
+
* layer virtualization, sticky toolbar/header/footer, infinite scroll,
|
|
399
|
+
* quick search, CSV export, and date-format toggling on top.
|
|
383
400
|
*
|
|
384
|
-
*
|
|
385
|
-
*
|
|
386
|
-
*
|
|
387
|
-
* paginate your data directly — you own that, but `useDataSource` does
|
|
388
|
-
* it for you. The `rows` prop is always the already-processed slice to
|
|
389
|
-
* show. The grid tracks user intent in `state` (sort model, quick
|
|
390
|
-
* search text, page index). You feed that state into `useDataSource`,
|
|
391
|
-
* and its output goes back in as `rows`.
|
|
392
|
-
*
|
|
393
|
-
* `useDataSource` IS the processor. Given your full dataset and the
|
|
394
|
-
* grid's state, it returns the searched + sorted + paginated rows
|
|
395
|
-
* ready to pass to DataGrid. This is the ONLY correct pattern for
|
|
396
|
-
* client-side data — do NOT pass a raw array to `rows`.
|
|
397
|
-
*
|
|
398
|
-
* ## Search (client vs async)
|
|
399
|
-
*
|
|
400
|
-
* - **Client mode** (`useDataSource` with `data`): a case-insensitive
|
|
401
|
-
* substring match across every column is applied automatically.
|
|
402
|
-
* Override the matcher with `matchRow` for fuzzy / weighted search,
|
|
403
|
-
* or disable by passing `matchRow: () => true`.
|
|
404
|
-
* - **Async mode** (`useDataSource` with `dataSource`): `state.quickSearch`
|
|
405
|
-
* is forwarded to the generator as `params.quickSearch`. Same
|
|
406
|
-
* mechanism as `params.sorting` — a change triggers a refetch, and
|
|
407
|
-
* the generator is the "matching logic" (typically a WHERE / ILIKE
|
|
408
|
-
* clause in the backend query). The grid does NO client-side
|
|
409
|
-
* filtering in async mode.
|
|
410
|
-
*
|
|
411
|
-
* ## The canonical pattern
|
|
401
|
+
* The grid is display-only — it does not fetch or page data itself. Pair
|
|
402
|
+
* with `useDataSource` for client- or server-side data and pass the
|
|
403
|
+
* already-processed slice through `rows`.
|
|
412
404
|
*
|
|
413
405
|
* ```tsx
|
|
414
|
-
*
|
|
415
|
-
* const
|
|
416
|
-
* { id: "name", header: "Name", accessor: "name", width: 180, type: "string" },
|
|
417
|
-
* { id: "email", header: "Email", accessor: "email", width: 240, type: "string" },
|
|
418
|
-
* { id: "role", header: "Role", accessor: "role", width: 120, type: "singleSelect",
|
|
419
|
-
* valueOptions: [{ value: "admin", label: "Admin" }, { value: "member", label: "Member" }] },
|
|
420
|
-
* { id: "signUps", header: "Sign-ups", accessor: "signUps", width: 120, type: "number", align: "right",
|
|
421
|
-
* renderCell: ({ value }) => <span className="tabular-nums">{Number(value).toLocaleString()}</span> },
|
|
422
|
-
* ], []);
|
|
423
|
-
*
|
|
424
|
-
* // 2. Grid state — one hook, initialized from the columns. NEVER build the state object by hand.
|
|
425
|
-
* const [gridState, setGridState] = React.useState(() => createDefaultDataGridState(columns));
|
|
426
|
-
*
|
|
427
|
-
* // 3. Data source — wires your raw array through the grid state. ALWAYS call this
|
|
428
|
-
* // hook unconditionally at the top level (no if/return before it).
|
|
406
|
+
* const columns = useMemo(() => [...], []);
|
|
407
|
+
* const [gridState, setGridState] = useState(() => createDefaultDataGridState(columns));
|
|
429
408
|
* const gridData = useDataSource({
|
|
430
|
-
* data: users,
|
|
431
|
-
* columns,
|
|
432
|
-
* getRowId: (row) => row.id,
|
|
409
|
+
* data: users, columns, getRowId: (r) => r.id,
|
|
433
410
|
* sorting: gridState.sorting,
|
|
434
411
|
* quickSearch: gridState.quickSearch,
|
|
435
412
|
* pagination: gridState.pagination,
|
|
436
|
-
* paginationMode: "client",
|
|
413
|
+
* paginationMode: "client",
|
|
437
414
|
* });
|
|
438
415
|
*
|
|
439
|
-
* // 4. Render — `rows` comes from gridData.rows, NOT from your raw array.
|
|
440
416
|
* <DataGrid
|
|
441
417
|
* columns={columns}
|
|
442
418
|
* rows={gridData.rows}
|
|
443
|
-
* getRowId={(
|
|
419
|
+
* getRowId={(r) => r.id}
|
|
444
420
|
* totalRowCount={gridData.totalRowCount}
|
|
445
421
|
* isLoading={gridData.isLoading}
|
|
446
422
|
* state={gridState}
|
|
447
423
|
* onChange={setGridState}
|
|
448
|
-
* selectionMode="none" // "none" | "single" | "multiple"
|
|
449
|
-
* maxHeight={480}
|
|
450
424
|
* />
|
|
451
425
|
* ```
|
|
452
426
|
*
|
|
453
|
-
*
|
|
454
|
-
*
|
|
455
|
-
*
|
|
456
|
-
*
|
|
457
|
-
*
|
|
458
|
-
* `rows` — the grid won't search, sort, or paginate it.
|
|
459
|
-
* 3. Columns must be stable across renders. Define them outside the
|
|
460
|
-
* component or wrap in `React.useMemo`. A fresh columns array every
|
|
461
|
-
* render will reset sorting state.
|
|
462
|
-
* 4. Initialize grid state with `createDefaultDataGridState(columns)`.
|
|
463
|
-
* Do NOT spell out the state object manually — you will miss fields
|
|
464
|
-
* and crash.
|
|
465
|
-
* 5. `onChange` takes a `SetStateAction` (the setter you got from
|
|
466
|
-
* `useState`). Pass `setGridState` directly. Do NOT wrap it unless
|
|
467
|
-
* you know exactly what you're doing.
|
|
468
|
-
* 6. Call `useDataSource` ONCE per grid, at the top level, before any
|
|
469
|
-
* early return. It contains hooks.
|
|
470
|
-
* 7. `renderCell` is a PURE function of its context. NEVER call React
|
|
471
|
-
* hooks inside it (no `useState`, `useMemo`, `useEffect`, nothing).
|
|
472
|
-
* If you need derived data per row, compute it BEFORE the render —
|
|
473
|
-
* e.g. build a `Map<rowId, sparklineData>` in a `useMemo` and look
|
|
474
|
-
* it up in `renderCell`.
|
|
475
|
-
* 8. `toolbar` accepts `false` (hide it) or a render function
|
|
476
|
-
* `(ctx) => ReactNode`. Anything else — `true`, `undefined`, a state
|
|
477
|
-
* variable — will either show the default toolbar or crash. If you
|
|
478
|
-
* just want the default toolbar, omit the prop entirely.
|
|
479
|
-
* 9. The toolbar's search input writes to `state.quickSearch`. That
|
|
480
|
-
* value is consumed by `useDataSource` — client mode filters
|
|
481
|
-
* client-side, async mode forwards to the generator. Do NOT wire
|
|
482
|
-
* a separate "controlled" search prop, everything flows through
|
|
483
|
-
* grid state.
|
|
484
|
-
*
|
|
485
|
-
* ## renderCell — what you can and cannot do inside it
|
|
486
|
-
*
|
|
487
|
-
* ```tsx
|
|
488
|
-
* // OK — pure rendering from ctx:
|
|
489
|
-
* renderCell: ({ value }) => <span className="tabular-nums">{Number(value).toLocaleString()}</span>
|
|
490
|
-
* renderCell: ({ row }) => <Badge variant={row.active ? "default" : "outline"}>{row.status}</Badge>
|
|
491
|
-
*
|
|
492
|
-
* // OK — looking up pre-computed data by row id:
|
|
493
|
-
* // BEFORE the return, in the component body:
|
|
494
|
-
* const sparklinesById = React.useMemo(() => {
|
|
495
|
-
* const m = new Map();
|
|
496
|
-
* for (const u of users) {
|
|
497
|
-
* m.set(u.id, u.recentActivity.map((n, i) => ({ ts: i, values: { primary: n } })));
|
|
498
|
-
* }
|
|
499
|
-
* return m;
|
|
500
|
-
* }, [users]);
|
|
501
|
-
* // Then inside the column def:
|
|
502
|
-
* renderCell: ({ rowId }) => <MiniSparkline data={sparklinesById.get(rowId) ?? []} />
|
|
503
|
-
*
|
|
504
|
-
* // NOT OK — hooks inside renderCell:
|
|
505
|
-
* renderCell: ({ row }) => {
|
|
506
|
-
* const [hovered, setHovered] = React.useState(false); // ← crashes the grid
|
|
507
|
-
* const data = React.useMemo(() => ..., []); // ← crashes the grid
|
|
508
|
-
* return ...;
|
|
509
|
-
* }
|
|
510
|
-
*
|
|
511
|
-
* // NOT OK — embedding AnalyticsChart (or any other controlled, stateful chart) per row:
|
|
512
|
-
* // AnalyticsChart owns its own state, tooltips, zoom, and virtualized data
|
|
513
|
-
* // pipeline. Instantiating one per row is expensive and fights the grid's
|
|
514
|
-
* // virtualizer. Don't do it.
|
|
515
|
-
* ```
|
|
516
|
-
*
|
|
517
|
-
* ## Sparklines and mini-charts in cells — use raw Recharts
|
|
518
|
-
*
|
|
519
|
-
* If you want a tiny chart (sparkline, micro bar chart, trend line) inside
|
|
520
|
-
* a cell, drop down to raw `Recharts.*` components — they are lightweight
|
|
521
|
-
* and stateless, so they render cleanly per row without owning any state.
|
|
522
|
-
* Read pre-computed points off the row (or off a `Map<rowId, points>` you
|
|
523
|
-
* built in a `useMemo` above) and pass them directly to the Recharts
|
|
524
|
-
* primitive. Do NOT wrap them in `DesignChartContainer` or
|
|
525
|
-
* `DesignChartCard` inside a cell — those add chrome meant for full-size
|
|
526
|
-
* charts.
|
|
527
|
-
*
|
|
528
|
-
* ```tsx
|
|
529
|
-
* // OK — raw Recharts sparkline per row:
|
|
530
|
-
* renderCell: ({ rowId }) => {
|
|
531
|
-
* const points = sparklinesById.get(rowId) ?? [];
|
|
532
|
-
* return (
|
|
533
|
-
* <Recharts.ResponsiveContainer width="100%" height={28}>
|
|
534
|
-
* <Recharts.LineChart data={points} margin={{ top: 2, right: 2, bottom: 2, left: 2 }}>
|
|
535
|
-
* <Recharts.Line type="monotone" dataKey="v" stroke="currentColor" strokeWidth={1.5} dot={false} isAnimationActive={false} />
|
|
536
|
-
* </Recharts.LineChart>
|
|
537
|
-
* </Recharts.ResponsiveContainer>
|
|
538
|
-
* );
|
|
539
|
-
* }
|
|
540
|
-
* ```
|
|
541
|
-
*
|
|
542
|
-
* Keep in-cell Recharts configs minimal: no axes, no tooltips, no animation
|
|
543
|
-
* (`isAnimationActive={false}`), tight margins, fixed height. The goal is a
|
|
544
|
-
* visual summary, not an interactive chart.
|
|
545
|
-
*
|
|
546
|
-
* ## State shape (from `createDefaultDataGridState`)
|
|
547
|
-
*
|
|
548
|
-
* ```ts
|
|
549
|
-
* {
|
|
550
|
-
* sorting: [], // { columnId, direction: "asc" | "desc" }[]
|
|
551
|
-
* quickSearch: "", // search input text
|
|
552
|
-
* dateDisplay: "relative", // "relative" | "absolute"
|
|
553
|
-
* columnVisibility: {}, columnWidths: {...},
|
|
554
|
-
* columnPinning: { left: [], right: [] }, columnOrder: [...],
|
|
555
|
-
* pagination: { pageIndex: 0, pageSize: 50 },
|
|
556
|
-
* selection: { selectedIds: new Set(), anchorId: null },
|
|
557
|
-
* }
|
|
558
|
-
* ```
|
|
559
|
-
*
|
|
560
|
-
* Everything is updated through `setGridState` — the toolbar, header,
|
|
561
|
-
* and footer all call it for you. You do not need to wire any of this
|
|
562
|
-
* manually.
|
|
563
|
-
*
|
|
564
|
-
* ## Cell overflow and dynamic row heights
|
|
565
|
-
*
|
|
566
|
-
* By default every cell truncates its content with an ellipsis
|
|
567
|
-
* (`cellOverflow: "truncate"`). For columns whose content should wrap
|
|
568
|
-
* — badge lists, multi-line text, permission chips — set
|
|
569
|
-
* `cellOverflow: "wrap"` on the column definition.
|
|
570
|
-
*
|
|
571
|
-
* To let rows grow to fit their tallest cell, set `rowHeight="auto"`
|
|
572
|
-
* on the grid. The virtualizer will measure each row after render and
|
|
573
|
-
* adjust scroll positions accordingly. Pair with `estimatedRowHeight`
|
|
574
|
-
* (default 44) for better scroll-position estimates before measurement.
|
|
575
|
-
*
|
|
576
|
-
* ```tsx
|
|
577
|
-
* // Columns: UUIDs truncate, auth-method badges wrap
|
|
578
|
-
* const columns = [
|
|
579
|
-
* { id: "userId", header: "User ID", width: 130 }, // default truncate
|
|
580
|
-
* { id: "auth", header: "Auth methods", width: 150, cellOverflow: "wrap",
|
|
581
|
-
* renderCell: ({ row }) => (
|
|
582
|
-
* <div className="flex flex-wrap gap-1">
|
|
583
|
-
* {row.authTypes.map((t) => <Badge key={t}>{t}</Badge>)}
|
|
584
|
-
* </div>
|
|
585
|
-
* ),
|
|
586
|
-
* },
|
|
587
|
-
* ];
|
|
588
|
-
*
|
|
589
|
-
* <DataGrid columns={columns} rowHeight="auto" estimatedRowHeight={48} ... />
|
|
590
|
-
* ```
|
|
591
|
-
*
|
|
592
|
-
* With a fixed numeric `rowHeight` (the default), `cellOverflow: "wrap"`
|
|
593
|
-
* still lets content wrap within the row, but anything exceeding the
|
|
594
|
-
* fixed height is clipped. This is useful when you want controlled
|
|
595
|
-
* wrapping without variable row heights.
|
|
596
|
-
*
|
|
597
|
-
* ## Height and scrolling
|
|
598
|
-
*
|
|
599
|
-
* DataGrid is NOT a card. It has no border, rounded corners, or shadow of
|
|
600
|
-
* its own. Wrap it in whatever chrome you want — a `DesignCard`, a section,
|
|
601
|
-
* or just raw layout. The grid itself fills its parent's height via
|
|
602
|
-
* `h-full`.
|
|
603
|
-
*
|
|
604
|
-
* How the grid gets its height (pick ONE):
|
|
605
|
-
* 1. Bounded parent — put the grid inside a flex/grid container with a
|
|
606
|
-
* definite height (e.g. `flex-1 min-h-0` inside a page-filling flex
|
|
607
|
-
* column). The grid stretches to that height and scrolls its body.
|
|
608
|
-
* 2. `maxHeight` prop — pass a number (pixels) or CSS string
|
|
609
|
-
* (`"480px"`, `"60vh"`, `"100%"`). The grid caps at that size and
|
|
610
|
-
* scrolls its body.
|
|
611
|
-
* 3. Unbounded — omit `maxHeight` and let the parent grow freely. The
|
|
612
|
-
* grid renders at its full content height and the page scrolls. Fine
|
|
613
|
-
* for small lists; bad UX for thousands of rows.
|
|
614
|
-
*
|
|
615
|
-
* The toolbar, header, and footer are always `shrink-0`; only the body
|
|
616
|
-
* scrolls. You do NOT need to subtract toolbar/footer heights from
|
|
617
|
-
* `maxHeight` — the grid's internal flex layout handles that.
|
|
618
|
-
*
|
|
619
|
-
* ## When to use what
|
|
620
|
-
*
|
|
621
|
-
* - Simple static list, < 20 rows, no interaction → use a plain table component instead.
|
|
622
|
-
* - Interactive table, sortable + searchable, any size → `DataGrid` +
|
|
623
|
-
* `useDataSource` with `paginationMode: "client"`.
|
|
624
|
-
* - Infinite scroll over a huge dataset you fetch in pages → `dataSource` async
|
|
625
|
-
* generator + `paginationMode: "infinite"`. Only reach for this if you actually
|
|
626
|
-
* need pagination over a remote source. For anything that fits in memory,
|
|
627
|
-
* `"client"` is simpler and faster.
|
|
628
|
-
*
|
|
629
|
-
* ## Features you get for free
|
|
630
|
-
*
|
|
631
|
-
* Quick search, sortable columns (shift-click for multi-sort), column
|
|
632
|
-
* visibility toggle, column resize, CSV export, virtualized rendering
|
|
633
|
-
* for 10k+ rows, keyboard navigation, and a relative/absolute date
|
|
634
|
-
* toggle for `date` / `dateTime` columns.
|
|
427
|
+
* Iron rules:
|
|
428
|
+
* - `rows` is always `gridData.rows`, never your raw array.
|
|
429
|
+
* - Columns must be stable (define outside the component or wrap in `useMemo`).
|
|
430
|
+
* - Initialize state with `createDefaultDataGridState(columns)`.
|
|
431
|
+
* - `renderCell` must be a pure function — no React hooks inside.
|
|
635
432
|
*/
|
|
636
433
|
function DataGrid(props) {
|
|
637
|
-
const { columns: allColumns, rows, getRowId, totalRowCount, isLoading = false, isRefetching = false, hasMore = false, isLoadingMore = false, onLoadMore, state, onChange, paginationMode = "paginated", selectionMode = "none", resizable = true, rowHeight: rowHeightProp = 44, estimatedRowHeight: estimatedRowHeightProp, headerHeight = 44, overscan = 5, maxHeight, fillHeight = true, stickyTop, toolbar, toolbarExtra, emptyState, loadingState, footer, footerExtra, exportFilename = "export", strings: stringsOverride, className, onRowClick, onRowDoubleClick, onSelectionChange, onSortChange } = props;
|
|
434
|
+
const { columns: allColumns, rows, getRowId, totalRowCount, isLoading = false, isRefetching = false, hasMore = false, isLoadingMore = false, onLoadMore, state, onChange, paginationMode = "paginated", selectionMode = "none", resizable = true, rowHeight: rowHeightProp = 44, estimatedRowHeight: estimatedRowHeightProp, headerHeight = 44, overscan = 5, maxHeight, fillHeight = true, stickyTop, toolbar, toolbarExtra, emptyState, loadingState, footer, footerExtra, exportFilename = "export", strings: stringsOverride, className, onRowClick, onRowDoubleClick, onSelectionChange, onSortChange, onColumnResize, onColumnVisibilityChange } = props;
|
|
638
435
|
const isDynamicRowHeight = rowHeightProp === "auto";
|
|
639
436
|
const fixedRowHeight = isDynamicRowHeight ? void 0 : rowHeightProp;
|
|
640
437
|
const estimatedRowHeight = estimatedRowHeightProp ?? fixedRowHeight ?? 44;
|
|
641
438
|
const strings = (0, react.useMemo)(() => (0, __strings_js.resolveDataGridStrings)(stringsOverride), [stringsOverride]);
|
|
642
|
-
const
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
439
|
+
const tableColumns = (0, react.useMemo)(() => allColumns.map((col) => ({
|
|
440
|
+
id: col.id,
|
|
441
|
+
accessorFn: (row) => (0, __state_js.resolveColumnValue)(col, row),
|
|
442
|
+
header: typeof col.header === "string" ? col.header : col.id,
|
|
443
|
+
size: col.width ?? __data_grid_sizing_js.DEFAULT_COL_WIDTH,
|
|
444
|
+
minSize: (0, __data_grid_sizing_js.getEffectiveMinWidth)(col),
|
|
445
|
+
maxSize: (0, __data_grid_sizing_js.getEffectiveMaxWidth)(col),
|
|
446
|
+
enableSorting: col.sortable !== false,
|
|
447
|
+
enableHiding: col.hideable !== false,
|
|
448
|
+
enableResizing: col.resizable !== false,
|
|
449
|
+
enableMultiSort: true
|
|
450
|
+
})), [allColumns]);
|
|
451
|
+
const tanstackSorting = (0, react.useMemo)(() => toTanstackSorting(state.sorting), [state.sorting]);
|
|
452
|
+
const tanstackRowSelection = (0, react.useMemo)(() => toTanstackRowSelection(state.selection.selectedIds), [state.selection.selectedIds]);
|
|
453
|
+
const tanstackColumnPinning = (0, react.useMemo)(() => ({
|
|
454
|
+
left: [...state.columnPinning.left],
|
|
455
|
+
right: [...state.columnPinning.right]
|
|
456
|
+
}), [state.columnPinning]);
|
|
457
|
+
const tanstackColumnOrder = (0, react.useMemo)(() => [...state.columnOrder], [state.columnOrder]);
|
|
458
|
+
const allColumnsRef = (0, react.useRef)(allColumns);
|
|
459
|
+
allColumnsRef.current = allColumns;
|
|
460
|
+
const handleSortingChange = (0, react.useCallback)((updater) => {
|
|
461
|
+
const ours = fromTanstackSorting(resolveUpdater(updater, toTanstackSorting(state.sorting))).map((s) => ({ ...s }));
|
|
462
|
+
onChange((s) => ({
|
|
463
|
+
...s,
|
|
464
|
+
sorting: ours,
|
|
465
|
+
pagination: {
|
|
466
|
+
...s.pagination,
|
|
467
|
+
pageIndex: 0
|
|
468
|
+
}
|
|
469
|
+
}));
|
|
470
|
+
onSortChange?.(ours);
|
|
471
|
+
}, [
|
|
472
|
+
onChange,
|
|
473
|
+
onSortChange,
|
|
474
|
+
state.sorting
|
|
646
475
|
]);
|
|
647
|
-
const
|
|
648
|
-
|
|
649
|
-
const
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
476
|
+
const handleColumnSizingChange = (0, react.useCallback)((updater) => {
|
|
477
|
+
const next = resolveUpdater(updater, state.columnWidths);
|
|
478
|
+
const clamped = {};
|
|
479
|
+
for (const [id, w] of Object.entries(next)) {
|
|
480
|
+
const col = allColumnsRef.current.find((c) => c.id === id);
|
|
481
|
+
clamped[id] = col ? (0, __data_grid_sizing_js.clampColumnWidth)(col, w) : w;
|
|
482
|
+
}
|
|
483
|
+
onChange((s) => ({
|
|
484
|
+
...s,
|
|
485
|
+
columnWidths: clamped
|
|
486
|
+
}));
|
|
487
|
+
if (onColumnResize) {
|
|
488
|
+
for (const [id, w] of Object.entries(clamped)) if (state.columnWidths[id] !== w) onColumnResize(id, w);
|
|
655
489
|
}
|
|
656
|
-
return {
|
|
657
|
-
widths,
|
|
658
|
-
totalWidth
|
|
659
|
-
};
|
|
660
490
|
}, [
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
491
|
+
onChange,
|
|
492
|
+
onColumnResize,
|
|
493
|
+
state.columnWidths
|
|
664
494
|
]);
|
|
665
|
-
const
|
|
666
|
-
|
|
667
|
-
const gridRef = (0, react.useRef)(null);
|
|
668
|
-
const handleSort = (0, react.useCallback)((columnId, multi) => {
|
|
669
|
-
const next = (0, __state_js.toggleSort)(state.sorting, columnId, multi);
|
|
495
|
+
const handleVisibilityChange = (0, react.useCallback)((updater) => {
|
|
496
|
+
const next = resolveUpdater(updater, state.columnVisibility);
|
|
670
497
|
onChange((s) => ({
|
|
671
498
|
...s,
|
|
672
|
-
|
|
499
|
+
columnVisibility: next
|
|
673
500
|
}));
|
|
674
|
-
|
|
501
|
+
onColumnVisibilityChange?.(next);
|
|
675
502
|
}, [
|
|
676
503
|
onChange,
|
|
677
|
-
|
|
678
|
-
state.
|
|
504
|
+
onColumnVisibilityChange,
|
|
505
|
+
state.columnVisibility
|
|
679
506
|
]);
|
|
680
|
-
const
|
|
681
|
-
const
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
}
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
507
|
+
const handleColumnOrderChange = (0, react.useCallback)((updater) => {
|
|
508
|
+
const next = resolveUpdater(updater, [...state.columnOrder]);
|
|
509
|
+
onChange((s) => ({
|
|
510
|
+
...s,
|
|
511
|
+
columnOrder: next
|
|
512
|
+
}));
|
|
513
|
+
}, [onChange, state.columnOrder]);
|
|
514
|
+
const handleColumnPinningChange = (0, react.useCallback)((updater) => {
|
|
515
|
+
const next = resolveUpdater(updater, {
|
|
516
|
+
left: [...state.columnPinning.left],
|
|
517
|
+
right: [...state.columnPinning.right]
|
|
518
|
+
});
|
|
519
|
+
onChange((s) => ({
|
|
520
|
+
...s,
|
|
521
|
+
columnPinning: {
|
|
522
|
+
left: next.left ?? [],
|
|
523
|
+
right: next.right ?? []
|
|
524
|
+
}
|
|
525
|
+
}));
|
|
526
|
+
}, [onChange, state.columnPinning]);
|
|
527
|
+
const table = (0, _tanstack_react_table.useReactTable)({
|
|
528
|
+
data: rows,
|
|
529
|
+
columns: tableColumns,
|
|
530
|
+
getRowId: (row) => getRowId(row),
|
|
531
|
+
getCoreRowModel: (0, _tanstack_react_table.getCoreRowModel)(),
|
|
532
|
+
state: {
|
|
533
|
+
sorting: tanstackSorting,
|
|
534
|
+
columnVisibility: state.columnVisibility,
|
|
535
|
+
columnSizing: state.columnWidths,
|
|
536
|
+
columnOrder: tanstackColumnOrder,
|
|
537
|
+
columnPinning: tanstackColumnPinning,
|
|
538
|
+
rowSelection: tanstackRowSelection
|
|
539
|
+
},
|
|
540
|
+
onSortingChange: handleSortingChange,
|
|
541
|
+
onColumnSizingChange: handleColumnSizingChange,
|
|
542
|
+
onColumnVisibilityChange: handleVisibilityChange,
|
|
543
|
+
onColumnOrderChange: handleColumnOrderChange,
|
|
544
|
+
onColumnPinningChange: handleColumnPinningChange,
|
|
545
|
+
columnResizeMode: "onEnd",
|
|
546
|
+
enableRowSelection: selectionMode !== "none",
|
|
547
|
+
enableMultiRowSelection: selectionMode === "multiple",
|
|
548
|
+
enableColumnResizing: resizable,
|
|
549
|
+
manualSorting: true,
|
|
550
|
+
manualPagination: true,
|
|
551
|
+
manualFiltering: true
|
|
552
|
+
});
|
|
553
|
+
const visibleColumns = (0, react.useMemo)(() => {
|
|
554
|
+
const colMap = new Map(allColumns.map((c) => [c.id, c]));
|
|
555
|
+
return table.getVisibleLeafColumns().map((c) => colMap.get(c.id)).filter(Boolean);
|
|
695
556
|
}, [
|
|
696
557
|
allColumns,
|
|
697
|
-
|
|
698
|
-
|
|
558
|
+
table,
|
|
559
|
+
state.columnOrder,
|
|
560
|
+
state.columnVisibility
|
|
699
561
|
]);
|
|
562
|
+
const rowIds = (0, react.useMemo)(() => rows.map(getRowId), [rows, getRowId]);
|
|
563
|
+
const [containerWidth, setContainerWidth] = (0, react.useState)(0);
|
|
700
564
|
(0, react.useLayoutEffect)(() => {
|
|
701
|
-
const
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
565
|
+
const grid = gridRef.current;
|
|
566
|
+
const scroller = scrollContainerRef.current;
|
|
567
|
+
if (!grid) return;
|
|
568
|
+
const update = () => {
|
|
569
|
+
const w = scroller?.clientWidth ?? grid.clientWidth;
|
|
570
|
+
if (w > 0) setContainerWidth(w);
|
|
571
|
+
};
|
|
572
|
+
update();
|
|
573
|
+
const observer = new ResizeObserver(update);
|
|
574
|
+
observer.observe(grid);
|
|
575
|
+
if (scroller) observer.observe(scroller);
|
|
576
|
+
return () => observer.disconnect();
|
|
577
|
+
}, []);
|
|
578
|
+
const columnSizingInfo = table.getState().columnSizingInfo;
|
|
579
|
+
const columnSizes = (0, react.useMemo)(() => {
|
|
580
|
+
const sizes = {};
|
|
581
|
+
let baseTotal = selectionMode !== "none" ? 44 : 0;
|
|
582
|
+
const resizingId = columnSizingInfo.isResizingColumn || null;
|
|
583
|
+
const deltaOffset = columnSizingInfo.deltaOffset ?? 0;
|
|
584
|
+
for (const col of visibleColumns) {
|
|
585
|
+
const baseSize = table.getColumn(col.id)?.getSize() ?? col.width ?? __data_grid_sizing_js.DEFAULT_COL_WIDTH;
|
|
586
|
+
const liveSize = resizingId === col.id ? (0, __data_grid_sizing_js.clampColumnWidth)(col, baseSize + deltaOffset) : baseSize;
|
|
587
|
+
sizes[col.id] = liveSize;
|
|
588
|
+
baseTotal += liveSize;
|
|
589
|
+
}
|
|
590
|
+
distributeFlexWidths(sizes, visibleColumns, containerWidth - baseTotal);
|
|
591
|
+
return sizes;
|
|
592
|
+
}, [
|
|
593
|
+
visibleColumns,
|
|
594
|
+
table,
|
|
595
|
+
columnSizingInfo,
|
|
596
|
+
state.columnWidths,
|
|
597
|
+
containerWidth,
|
|
598
|
+
selectionMode
|
|
599
|
+
]);
|
|
600
|
+
const totalContentWidth = (0, react.useMemo)(() => {
|
|
601
|
+
let total = selectionMode !== "none" ? 44 : 0;
|
|
602
|
+
for (const col of visibleColumns) total += columnSizes[col.id] ?? 0;
|
|
603
|
+
return total;
|
|
604
|
+
}, [
|
|
605
|
+
visibleColumns,
|
|
606
|
+
columnSizes,
|
|
607
|
+
selectionMode
|
|
608
|
+
]);
|
|
609
|
+
const cssVars = (0, react.useMemo)(() => {
|
|
610
|
+
const vars = { "--grid-total-w": `${totalContentWidth}px` };
|
|
611
|
+
for (const col of visibleColumns) vars[`--col-${col.id}-size`] = columnSizes[col.id];
|
|
612
|
+
return vars;
|
|
613
|
+
}, [
|
|
614
|
+
visibleColumns,
|
|
615
|
+
columnSizes,
|
|
616
|
+
totalContentWidth
|
|
617
|
+
]);
|
|
618
|
+
const fireSelection = (0, react.useCallback)((next) => {
|
|
708
619
|
onChange((s) => ({
|
|
709
620
|
...s,
|
|
710
|
-
|
|
711
|
-
...s.columnWidths,
|
|
712
|
-
[r.columnId]: r.latestWidth
|
|
713
|
-
}
|
|
621
|
+
selection: next
|
|
714
622
|
}));
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
const next = (0, __state_js.toggleRowSelection)(state.selection, rowId, selectionMode, event.shiftKey, event.metaKey || event.ctrlKey, rowIds);
|
|
719
|
-
onChange((s) => ({
|
|
720
|
-
...s,
|
|
721
|
-
selection: next
|
|
722
|
-
}));
|
|
723
|
-
if (onSelectionChange) {
|
|
724
|
-
const selectedRows = rows.filter((r) => next.selectedIds.has(getRowId(r)));
|
|
725
|
-
onSelectionChange(next.selectedIds, selectedRows);
|
|
726
|
-
}
|
|
623
|
+
if (onSelectionChange) {
|
|
624
|
+
const idSet = next.selectedIds;
|
|
625
|
+
onSelectionChange(idSet, rows.filter((r) => idSet.has(getRowId(r))));
|
|
727
626
|
}
|
|
728
|
-
onRowClick?.(row, rowId, event);
|
|
729
627
|
}, [
|
|
730
|
-
selectionMode,
|
|
731
628
|
onChange,
|
|
732
|
-
onRowClick,
|
|
733
629
|
onSelectionChange,
|
|
734
|
-
rowIds,
|
|
735
630
|
rows,
|
|
736
|
-
getRowId
|
|
737
|
-
state.selection
|
|
631
|
+
getRowId
|
|
738
632
|
]);
|
|
739
|
-
const
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
633
|
+
const handleRowClick = (0, react.useCallback)((row, rowId, event) => {
|
|
634
|
+
if (selectionMode !== "none") fireSelection(nextSelection({
|
|
635
|
+
current: state.selection,
|
|
636
|
+
rowId,
|
|
637
|
+
mode: selectionMode,
|
|
638
|
+
modifiers: {
|
|
639
|
+
shift: event.shiftKey,
|
|
640
|
+
ctrl: event.metaKey || event.ctrlKey
|
|
641
|
+
},
|
|
642
|
+
allRowIds: rowIds
|
|
749
643
|
}));
|
|
750
|
-
|
|
644
|
+
onRowClick?.(row, rowId, event);
|
|
751
645
|
}, [
|
|
752
|
-
|
|
646
|
+
selectionMode,
|
|
647
|
+
state.selection,
|
|
753
648
|
rowIds,
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
649
|
+
fireSelection,
|
|
650
|
+
onRowClick
|
|
651
|
+
]);
|
|
652
|
+
const handleSelectAll = (0, react.useCallback)(() => {
|
|
653
|
+
fireSelection(rowIds.length > 0 && rowIds.every((id) => state.selection.selectedIds.has(id)) ? {
|
|
654
|
+
selectedIds: /* @__PURE__ */ new Set(),
|
|
655
|
+
anchorId: null
|
|
656
|
+
} : {
|
|
657
|
+
selectedIds: new Set(rowIds),
|
|
658
|
+
anchorId: null
|
|
659
|
+
});
|
|
660
|
+
}, [
|
|
661
|
+
rowIds,
|
|
662
|
+
state.selection.selectedIds,
|
|
663
|
+
fireSelection
|
|
757
664
|
]);
|
|
758
665
|
const handleExportCsv = (0, react.useCallback)(() => {
|
|
666
|
+
if (typeof window !== "undefined" && rows.length > 0) {
|
|
667
|
+
const totalSuffix = totalRowCount != null && totalRowCount > rows.length ? ` of ${totalRowCount} total — load more rows first to include them` : "";
|
|
668
|
+
if (!window.confirm(`Export ${rows.length.toLocaleString()} loaded row${rows.length === 1 ? "" : "s"}${totalSuffix}?`)) return;
|
|
669
|
+
}
|
|
759
670
|
(0, __state_js.exportToCsv)(rows, visibleColumns, exportFilename);
|
|
760
671
|
}, [
|
|
761
672
|
rows,
|
|
762
673
|
visibleColumns,
|
|
763
|
-
exportFilename
|
|
674
|
+
exportFilename,
|
|
675
|
+
totalRowCount
|
|
764
676
|
]);
|
|
765
677
|
const scrollContainerRef = (0, react.useRef)(null);
|
|
766
678
|
const headerScrollRef = (0, react.useRef)(null);
|
|
767
679
|
const stickyChromeRef = (0, react.useRef)(null);
|
|
768
680
|
const rowsClipRef = (0, react.useRef)(null);
|
|
681
|
+
const gridRef = (0, react.useRef)(null);
|
|
769
682
|
const measureElementFn = (0, react.useCallback)((el) => el.getBoundingClientRect().height, []);
|
|
770
683
|
const rowVirtualizer = (0, _tanstack_react_virtual.useVirtualizer)({
|
|
771
684
|
count: rows.length,
|
|
@@ -778,104 +691,33 @@ function DataGrid(props) {
|
|
|
778
691
|
},
|
|
779
692
|
...isDynamicRowHeight ? { measureElement: measureElementFn } : {}
|
|
780
693
|
});
|
|
781
|
-
(0, react.useLayoutEffect)(() => {
|
|
782
|
-
const grid = gridRef.current;
|
|
783
|
-
const stickyEl = stickyChromeRef.current;
|
|
784
|
-
if (!grid || !stickyEl) return;
|
|
785
|
-
const parseRgba = (raw) => {
|
|
786
|
-
const rgbaMatch = raw.match(/rgba?\(\s*([\d.]+),\s*([\d.]+),\s*([\d.]+)(?:,\s*([\d.]+))?\s*\)/);
|
|
787
|
-
if (!rgbaMatch) return null;
|
|
788
|
-
const alphaRaw = rgbaMatch[4];
|
|
789
|
-
return [
|
|
790
|
-
Number(rgbaMatch[1]),
|
|
791
|
-
Number(rgbaMatch[2]),
|
|
792
|
-
Number(rgbaMatch[3]),
|
|
793
|
-
alphaRaw === void 0 ? 1 : Number(alphaRaw)
|
|
794
|
-
];
|
|
795
|
-
};
|
|
796
|
-
const blendOver = (base, top) => {
|
|
797
|
-
const [tr, tg, tb, ta] = top;
|
|
798
|
-
const [br, bg, bb, ba] = base;
|
|
799
|
-
const outA = ta + ba * (1 - ta);
|
|
800
|
-
if (outA === 0) return [
|
|
801
|
-
0,
|
|
802
|
-
0,
|
|
803
|
-
0,
|
|
804
|
-
0
|
|
805
|
-
];
|
|
806
|
-
return [
|
|
807
|
-
(tr * ta + br * ba * (1 - ta)) / outA,
|
|
808
|
-
(tg * ta + bg * ba * (1 - ta)) / outA,
|
|
809
|
-
(tb * ta + bb * ba * (1 - ta)) / outA,
|
|
810
|
-
outA
|
|
811
|
-
];
|
|
812
|
-
};
|
|
813
|
-
const detect = () => {
|
|
814
|
-
const layers = [];
|
|
815
|
-
let ancestor = grid.parentElement;
|
|
816
|
-
while (ancestor) {
|
|
817
|
-
const parsed = parseRgba(getComputedStyle(ancestor).backgroundColor);
|
|
818
|
-
if (parsed && parsed[3] > 0) {
|
|
819
|
-
layers.push(parsed);
|
|
820
|
-
if (parsed[3] >= 1) break;
|
|
821
|
-
}
|
|
822
|
-
ancestor = ancestor.parentElement;
|
|
823
|
-
}
|
|
824
|
-
if (layers.length === 0) {
|
|
825
|
-
stickyEl.style.backgroundColor = "";
|
|
826
|
-
return;
|
|
827
|
-
}
|
|
828
|
-
let result = layers[layers.length - 1];
|
|
829
|
-
for (let i = layers.length - 2; i >= 0; i--) result = blendOver(result, layers[i]);
|
|
830
|
-
const [r, g, b] = result;
|
|
831
|
-
stickyEl.style.backgroundColor = `rgb(${Math.round(r)}, ${Math.round(g)}, ${Math.round(b)})`;
|
|
832
|
-
};
|
|
833
|
-
detect();
|
|
834
|
-
const observer = new MutationObserver(detect);
|
|
835
|
-
observer.observe(document.documentElement, {
|
|
836
|
-
attributes: true,
|
|
837
|
-
attributeFilter: ["class"]
|
|
838
|
-
});
|
|
839
|
-
return () => observer.disconnect();
|
|
840
|
-
}, []);
|
|
841
694
|
(0, react.useLayoutEffect)(() => {
|
|
842
695
|
const gridEl = gridRef.current;
|
|
843
696
|
const stickyEl = stickyChromeRef.current;
|
|
844
697
|
const bodyEl = scrollContainerRef.current;
|
|
845
698
|
const clipEl = rowsClipRef.current;
|
|
846
699
|
if (!gridEl || !stickyEl || !bodyEl || !clipEl) return;
|
|
847
|
-
const verticalScrollEl = fillHeight ? bodyEl : getNearestVerticalScrollElement(gridEl);
|
|
848
|
-
let extraObservedScrollEl = null;
|
|
849
|
-
if (verticalScrollEl instanceof HTMLElement && verticalScrollEl !== bodyEl) extraObservedScrollEl = verticalScrollEl;
|
|
850
700
|
const updateClip = () => {
|
|
851
701
|
const stickyRect = stickyEl.getBoundingClientRect();
|
|
852
702
|
const clipRect = clipEl.getBoundingClientRect();
|
|
853
703
|
const overlap = Math.max(0, stickyRect.bottom - clipRect.top);
|
|
854
|
-
|
|
855
|
-
const maskValue = overlap > 0 ? `linear-gradient(to bottom, transparent 0px, transparent ${overlap}px, black ${overlap}px, black 100%)` : "";
|
|
856
|
-
clipEl.style.clipPath = clipValue;
|
|
857
|
-
clipEl.style.setProperty("-webkit-clip-path", clipValue);
|
|
858
|
-
clipEl.style.maskImage = maskValue;
|
|
859
|
-
clipEl.style.setProperty("-webkit-mask-image", maskValue);
|
|
704
|
+
clipEl.style.setProperty("--data-grid-sticky-overlap", `${overlap}px`);
|
|
860
705
|
};
|
|
861
706
|
updateClip();
|
|
862
707
|
bodyEl.addEventListener("scroll", updateClip);
|
|
863
|
-
|
|
864
|
-
else if (extraObservedScrollEl) extraObservedScrollEl.addEventListener("scroll", updateClip);
|
|
708
|
+
window.addEventListener("scroll", updateClip, true);
|
|
865
709
|
window.addEventListener("resize", updateClip);
|
|
866
|
-
const
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
if (extraObservedScrollEl) ro.observe(extraObservedScrollEl);
|
|
710
|
+
const observer = new ResizeObserver(updateClip);
|
|
711
|
+
observer.observe(gridEl);
|
|
712
|
+
observer.observe(stickyEl);
|
|
713
|
+
observer.observe(bodyEl);
|
|
871
714
|
return () => {
|
|
872
715
|
bodyEl.removeEventListener("scroll", updateClip);
|
|
873
|
-
|
|
874
|
-
else if (extraObservedScrollEl) extraObservedScrollEl.removeEventListener("scroll", updateClip);
|
|
716
|
+
window.removeEventListener("scroll", updateClip, true);
|
|
875
717
|
window.removeEventListener("resize", updateClip);
|
|
876
|
-
|
|
718
|
+
observer.disconnect();
|
|
877
719
|
};
|
|
878
|
-
}, [
|
|
720
|
+
}, []);
|
|
879
721
|
const handleBodyScroll = (0, react.useCallback)(() => {
|
|
880
722
|
const body = scrollContainerRef.current;
|
|
881
723
|
const header = headerScrollRef.current;
|
|
@@ -916,21 +758,28 @@ function DataGrid(props) {
|
|
|
916
758
|
const allSelected = rowIds.length > 0 && rowIds.every((id) => state.selection.selectedIds.has(id));
|
|
917
759
|
const someSelected = !allSelected && rowIds.some((id) => state.selection.selectedIds.has(id));
|
|
918
760
|
const infiniteScrollRootRef = paginationMode === "infinite" && (fillHeight || maxHeight != null) ? scrollContainerRef : void 0;
|
|
761
|
+
const headers = (0, react.useMemo)(() => table.getHeaderGroups()[0]?.headers.filter((h) => visibleColumns.some((c) => c.id === h.column.id)) ?? [], [table, visibleColumns]);
|
|
762
|
+
const headerByColId = (0, react.useMemo)(() => {
|
|
763
|
+
const m = /* @__PURE__ */ new Map();
|
|
764
|
+
for (const h of headers) m.set(h.column.id, h);
|
|
765
|
+
return m;
|
|
766
|
+
}, [headers]);
|
|
767
|
+
const isBounded = fillHeight || maxHeight != null;
|
|
919
768
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
920
769
|
ref: gridRef,
|
|
921
|
-
className: (0, _stackframe_stack_ui.cn)("flex w-full min-w-0 max-w-full flex-col bg-transparent rounded-[calc(var(--radius)*2)]", fillHeight ? "min-h-0 h-full" : "min-h-0 h-auto", className),
|
|
770
|
+
className: (0, _stackframe_stack_ui.cn)("isolate flex w-full min-w-0 max-w-full flex-col bg-transparent rounded-[calc(var(--radius)*2)]", fillHeight ? "min-h-0 h-full" : "min-h-0 h-auto", isBounded && "overflow-hidden", className),
|
|
922
771
|
style: maxHeight != null ? {
|
|
923
|
-
...
|
|
772
|
+
...cssVars,
|
|
924
773
|
maxHeight
|
|
925
|
-
} :
|
|
774
|
+
} : cssVars,
|
|
926
775
|
role: "grid",
|
|
927
776
|
"aria-rowcount": totalRowCount ?? rows.length,
|
|
928
777
|
"aria-colcount": visibleColumns.length,
|
|
929
778
|
children: [
|
|
930
779
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
931
780
|
ref: stickyChromeRef,
|
|
932
|
-
className: "sticky z-
|
|
933
|
-
style: { top: stickyTop ?? "var(--data-grid-sticky-top, 0px)" },
|
|
781
|
+
className: "sticky z-30 w-full min-w-0 shrink-0 overflow-visible rounded-t-[calc(var(--radius)*2)] bg-white/90 dark:bg-background/60 backdrop-blur-xl",
|
|
782
|
+
style: { top: stickyTop ?? (maxHeight != null ? 0 : "var(--data-grid-sticky-top, 0px)") },
|
|
934
783
|
children: [toolbar !== false && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
935
784
|
className: "relative bg-transparent",
|
|
936
785
|
children: toolbar ? toolbar(toolbarCtx) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__data_grid_toolbar_js.DataGridToolbar, {
|
|
@@ -949,7 +798,7 @@ function DataGrid(props) {
|
|
|
949
798
|
className: "flex",
|
|
950
799
|
style: {
|
|
951
800
|
height: headerHeight,
|
|
952
|
-
minWidth:
|
|
801
|
+
minWidth: totalContentWidth
|
|
953
802
|
},
|
|
954
803
|
role: "row",
|
|
955
804
|
children: [selectionMode !== "none" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
@@ -959,30 +808,37 @@ function DataGrid(props) {
|
|
|
959
808
|
checked: allSelected,
|
|
960
809
|
indeterminate: someSelected,
|
|
961
810
|
onChange: handleSelectAll,
|
|
962
|
-
ariaLabel: "Select all rows"
|
|
811
|
+
ariaLabel: "Select all rows on this page",
|
|
812
|
+
title: "Select all rows on this page"
|
|
963
813
|
})
|
|
964
|
-
}), visibleColumns.map((col) =>
|
|
965
|
-
col
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
}
|
|
814
|
+
}), visibleColumns.map((col) => {
|
|
815
|
+
const header = headerByColId.get(col.id);
|
|
816
|
+
if (!header) return null;
|
|
817
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(HeaderCell, {
|
|
818
|
+
header,
|
|
819
|
+
col,
|
|
820
|
+
resizable
|
|
821
|
+
}, col.id);
|
|
822
|
+
})]
|
|
973
823
|
})
|
|
974
824
|
})]
|
|
975
825
|
})]
|
|
976
826
|
}),
|
|
977
827
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
978
828
|
ref: scrollContainerRef,
|
|
979
|
-
className: (0, _stackframe_stack_ui.cn)("w-full min-w-0 overflow-auto bg-transparent",
|
|
829
|
+
className: (0, _stackframe_stack_ui.cn)("relative z-0 w-full min-w-0 overflow-auto bg-transparent", isBounded ? "min-h-0 flex-1" : "flex-none", "[&::-webkit-scrollbar]:w-1.5 [&::-webkit-scrollbar]:h-1.5", "[&::-webkit-scrollbar-track]:bg-transparent", "[&::-webkit-scrollbar-thumb]:bg-foreground/[0.08] [&::-webkit-scrollbar-thumb]:rounded-full", "[&::-webkit-scrollbar-thumb]:hover:bg-foreground/[0.15]"),
|
|
980
830
|
onScroll: handleBodyScroll,
|
|
981
831
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
982
832
|
ref: rowsClipRef,
|
|
833
|
+
"data-data-grid-rows-clip": "",
|
|
834
|
+
className: "relative z-0",
|
|
835
|
+
style: {
|
|
836
|
+
minWidth: totalContentWidth,
|
|
837
|
+
clipPath: "inset(var(--data-grid-sticky-overlap, 0px) 0 0 0)"
|
|
838
|
+
},
|
|
983
839
|
children: [
|
|
984
840
|
isLoading && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
985
|
-
style: { minWidth:
|
|
841
|
+
style: { minWidth: totalContentWidth },
|
|
986
842
|
children: loadingState ?? Array.from({ length: 8 }).map((_, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SkeletonRow, {
|
|
987
843
|
columns: visibleColumns,
|
|
988
844
|
height: estimatedRowHeight,
|
|
@@ -991,14 +847,14 @@ function DataGrid(props) {
|
|
|
991
847
|
}),
|
|
992
848
|
!isLoading && rows.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
993
849
|
className: "flex items-center justify-center py-16 text-sm text-muted-foreground",
|
|
994
|
-
style: { minWidth:
|
|
850
|
+
style: { minWidth: totalContentWidth },
|
|
995
851
|
children: emptyState ?? strings.noData
|
|
996
852
|
}),
|
|
997
853
|
!isLoading && rows.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
998
854
|
style: {
|
|
999
855
|
height: rowVirtualizer.getTotalSize(),
|
|
1000
856
|
width: "100%",
|
|
1001
|
-
minWidth:
|
|
857
|
+
minWidth: totalContentWidth,
|
|
1002
858
|
position: "relative"
|
|
1003
859
|
},
|
|
1004
860
|
children: rowVirtualizer.getVirtualItems().map((virtualRow) => {
|
|
@@ -1015,12 +871,10 @@ function DataGrid(props) {
|
|
|
1015
871
|
transform: `translateY(${virtualRow.start}px)`
|
|
1016
872
|
},
|
|
1017
873
|
onClick: (e) => {
|
|
1018
|
-
if (shouldIgnoreRowClick(e))
|
|
1019
|
-
handleRowClick(row, rowId, e);
|
|
874
|
+
if (!shouldIgnoreRowClick(e)) handleRowClick(row, rowId, e);
|
|
1020
875
|
},
|
|
1021
876
|
onDoubleClick: (e) => {
|
|
1022
|
-
if (shouldIgnoreRowClick(e))
|
|
1023
|
-
onRowDoubleClick?.(row, rowId, e);
|
|
877
|
+
if (!shouldIgnoreRowClick(e)) onRowDoubleClick?.(row, rowId, e);
|
|
1024
878
|
},
|
|
1025
879
|
role: "row",
|
|
1026
880
|
"aria-rowindex": virtualRow.index + 2,
|
|
@@ -1032,7 +886,7 @@ function DataGrid(props) {
|
|
|
1032
886
|
style: { width: 44 },
|
|
1033
887
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SelectionCheckbox, {
|
|
1034
888
|
checked: isSelected,
|
|
1035
|
-
onChange: (event) =>
|
|
889
|
+
onChange: (event) => handleRowClick(row, rowId, event),
|
|
1036
890
|
ariaLabel: `Select row ${rowId}`
|
|
1037
891
|
})
|
|
1038
892
|
}), visibleColumns.map((col) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DataCell, {
|
|
@@ -1056,7 +910,7 @@ function DataGrid(props) {
|
|
|
1056
910
|
})
|
|
1057
911
|
}),
|
|
1058
912
|
footer !== false && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1059
|
-
className: "
|
|
913
|
+
className: "sticky bottom-0 z-30 shrink-0 overflow-hidden rounded-b-[calc(var(--radius)*2)] bg-white/90 dark:bg-background/60 backdrop-blur-xl",
|
|
1060
914
|
children: [footer ? footer(footerCtx) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DefaultFooter, {
|
|
1061
915
|
ctx: footerCtx,
|
|
1062
916
|
pagination: paginationMode,
|