@stackframe/dashboard-ui-components 2.8.86 → 2.8.89
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/analytics-chart/analytics-chart-pie.js +3 -3
- package/dist/components/analytics-chart/analytics-chart-pie.js.map +1 -1
- package/dist/components/data-grid/data-grid-sizing.d.ts +2 -1
- package/dist/components/data-grid/data-grid-sizing.d.ts.map +1 -1
- package/dist/components/data-grid/data-grid-sizing.js +33 -4
- package/dist/components/data-grid/data-grid-sizing.js.map +1 -1
- package/dist/components/data-grid/data-grid-toolbar.js +18 -15
- package/dist/components/data-grid/data-grid-toolbar.js.map +1 -1
- package/dist/components/data-grid/data-grid.d.ts +35 -1
- package/dist/components/data-grid/data-grid.d.ts.map +1 -1
- package/dist/components/data-grid/data-grid.js +329 -127
- package/dist/components/data-grid/data-grid.js.map +1 -1
- package/dist/components/data-grid/data-grid.test.d.ts +1 -0
- package/dist/components/data-grid/data-grid.test.js +215 -0
- package/dist/components/data-grid/data-grid.test.js.map +1 -0
- package/dist/components/data-grid/index.d.ts +3 -2
- package/dist/components/data-grid/index.js +13 -0
- package/dist/components/data-grid/state.d.ts.map +1 -1
- package/dist/components/data-grid/state.js +24 -7
- package/dist/components/data-grid/state.js.map +1 -1
- package/dist/components/data-grid/types.d.ts +34 -3
- package/dist/components/data-grid/types.d.ts.map +1 -1
- package/dist/components/data-grid/use-data-source.d.ts +6 -0
- package/dist/components/data-grid/use-data-source.d.ts.map +1 -1
- package/dist/components/data-grid/use-data-source.js +10 -2
- package/dist/components/data-grid/use-data-source.js.map +1 -1
- package/dist/components/tabs.d.ts +5 -1
- package/dist/components/tabs.d.ts.map +1 -1
- package/dist/components/tabs.js +40 -27
- package/dist/components/tabs.js.map +1 -1
- package/dist/dashboard-ui-components.global.js +672 -368
- package/dist/dashboard-ui-components.global.js.map +4 -4
- package/dist/esm/components/analytics-chart/analytics-chart-pie.js +3 -3
- package/dist/esm/components/analytics-chart/analytics-chart-pie.js.map +1 -1
- package/dist/esm/components/data-grid/data-grid-sizing.d.ts +2 -1
- package/dist/esm/components/data-grid/data-grid-sizing.d.ts.map +1 -1
- package/dist/esm/components/data-grid/data-grid-sizing.js +33 -5
- package/dist/esm/components/data-grid/data-grid-sizing.js.map +1 -1
- package/dist/esm/components/data-grid/data-grid-toolbar.js +18 -15
- package/dist/esm/components/data-grid/data-grid-toolbar.js.map +1 -1
- package/dist/esm/components/data-grid/data-grid.d.ts +35 -1
- package/dist/esm/components/data-grid/data-grid.d.ts.map +1 -1
- package/dist/esm/components/data-grid/data-grid.js +329 -128
- package/dist/esm/components/data-grid/data-grid.js.map +1 -1
- package/dist/esm/components/data-grid/data-grid.test.d.ts +1 -0
- package/dist/esm/components/data-grid/data-grid.test.js +215 -0
- package/dist/esm/components/data-grid/data-grid.test.js.map +1 -0
- package/dist/esm/components/data-grid/index.d.ts +3 -2
- package/dist/esm/components/data-grid/index.js +3 -2
- package/dist/esm/components/data-grid/state.d.ts.map +1 -1
- package/dist/esm/components/data-grid/state.js +24 -7
- package/dist/esm/components/data-grid/state.js.map +1 -1
- package/dist/esm/components/data-grid/types.d.ts +34 -3
- package/dist/esm/components/data-grid/types.d.ts.map +1 -1
- package/dist/esm/components/data-grid/use-data-source.d.ts +6 -0
- package/dist/esm/components/data-grid/use-data-source.d.ts.map +1 -1
- package/dist/esm/components/data-grid/use-data-source.js +10 -2
- package/dist/esm/components/data-grid/use-data-source.js.map +1 -1
- package/dist/esm/components/tabs.d.ts +5 -1
- package/dist/esm/components/tabs.d.ts.map +1 -1
- package/dist/esm/components/tabs.js +40 -27
- package/dist/esm/components/tabs.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/package.json +4 -4
|
@@ -6,10 +6,11 @@ import { jsx, jsxs } from "react/jsx-runtime";
|
|
|
6
6
|
import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef } from "react";
|
|
7
7
|
import { clearSelection, exportToCsv, formatGridDate, getSortDirection, getSortIndex, isColumnVisible, resolveColumnValue, resolveColumnWidth, selectAll, toggleRowSelection, toggleSort } from "./state.js";
|
|
8
8
|
import { resolveDataGridStrings } from "./strings.js";
|
|
9
|
+
import { throwErr } from "@stackframe/stack-shared/dist/utils/errors";
|
|
9
10
|
import { useVirtualizer } from "@tanstack/react-virtual";
|
|
10
11
|
import { DesignSkeleton } from "../skeleton.js";
|
|
11
|
-
import { DataGridToolbar } from "./data-grid-toolbar.js";
|
|
12
12
|
import { applyDraggedColumnWidth, clampColumnWidth, createGridSizingStyle, getColumnSizingStyle } from "./data-grid-sizing.js";
|
|
13
|
+
import { DataGridToolbar } from "./data-grid-toolbar.js";
|
|
13
14
|
|
|
14
15
|
//#region src/components/data-grid/data-grid.tsx
|
|
15
16
|
function ResizeHandle({ onResize, onResizeEnd }) {
|
|
@@ -69,6 +70,37 @@ function ResizeHandle({ onResize, onResizeEnd }) {
|
|
|
69
70
|
onPointerDown
|
|
70
71
|
});
|
|
71
72
|
}
|
|
73
|
+
function getNearestVerticalScrollElement(element) {
|
|
74
|
+
let current = element?.parentElement ?? null;
|
|
75
|
+
while (current) {
|
|
76
|
+
const style = window.getComputedStyle(current);
|
|
77
|
+
const overflowY = style.overflowY === "visible" ? style.overflow : style.overflowY;
|
|
78
|
+
if ((overflowY === "auto" || overflowY === "scroll" || overflowY === "overlay") && current.scrollHeight > current.clientHeight + 1) return current;
|
|
79
|
+
current = current.parentElement;
|
|
80
|
+
}
|
|
81
|
+
return window;
|
|
82
|
+
}
|
|
83
|
+
function getEventTargetElement(target) {
|
|
84
|
+
if (target instanceof Element) return target;
|
|
85
|
+
if (target instanceof Node) return target.parentElement;
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
function isDataGridInteractiveRowClickTarget(target) {
|
|
89
|
+
return getEventTargetElement(target)?.closest([
|
|
90
|
+
"a",
|
|
91
|
+
"button",
|
|
92
|
+
"input",
|
|
93
|
+
"select",
|
|
94
|
+
"textarea",
|
|
95
|
+
"[role=\"button\"]",
|
|
96
|
+
"[role=\"menuitem\"]",
|
|
97
|
+
"[contenteditable]:not([contenteditable=\"false\"])",
|
|
98
|
+
"[data-no-row-click]"
|
|
99
|
+
].join(",")) != null;
|
|
100
|
+
}
|
|
101
|
+
function shouldIgnoreRowClick(event) {
|
|
102
|
+
return event.defaultPrevented || isDataGridInteractiveRowClickTarget(event.target);
|
|
103
|
+
}
|
|
72
104
|
function HeaderCell({ col, isSorted, sortIndex, resizable, onSort, onResize, onResizeEnd }) {
|
|
73
105
|
const ctx = {
|
|
74
106
|
columnId: col.id,
|
|
@@ -79,7 +111,7 @@ function HeaderCell({ col, isSorted, sortIndex, resizable, onSort, onResize, onR
|
|
|
79
111
|
const label = typeof col.header === "function" ? col.header(ctx) : col.header;
|
|
80
112
|
const sortable = col.sortable !== false;
|
|
81
113
|
return /* @__PURE__ */ jsxs("div", {
|
|
82
|
-
className: cn("group/header relative flex items-center gap-1.5 px-3 select-none bg-transparent", "border-r border-black/[0.04] dark:border-white/[0.04] last:border-r-0", sortable && "cursor-pointer"),
|
|
114
|
+
className: 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"),
|
|
83
115
|
style: getColumnSizingStyle(col),
|
|
84
116
|
"data-col-id": col.id,
|
|
85
117
|
onClick: (e) => sortable && onSort(col.id, e.metaKey || e.ctrlKey),
|
|
@@ -87,7 +119,7 @@ function HeaderCell({ col, isSorted, sortIndex, resizable, onSort, onResize, onR
|
|
|
87
119
|
"aria-sort": isSorted === "asc" ? "ascending" : isSorted === "desc" ? "descending" : "none",
|
|
88
120
|
children: [
|
|
89
121
|
/* @__PURE__ */ jsx("span", {
|
|
90
|
-
className: cn("flex-1 truncate text-xs font-semibold uppercase tracking-wider text-muted-foreground", col.align === "center" && "text-center", col.align === "right" && "text-right"),
|
|
122
|
+
className: 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"),
|
|
91
123
|
children: label
|
|
92
124
|
}),
|
|
93
125
|
isSorted && /* @__PURE__ */ jsxs("span", {
|
|
@@ -137,8 +169,9 @@ function DataCell({ col, row, rowId, rowIndex, isSelected, dateDisplay }) {
|
|
|
137
169
|
else if (isDateCol) content = renderDateCell(value, dateDisplay, col);
|
|
138
170
|
else content = formatCellValue(value);
|
|
139
171
|
const hasCellClick = col.onCellClick || col.onCellDoubleClick;
|
|
172
|
+
const isWrap = col.cellOverflow === "wrap";
|
|
140
173
|
return /* @__PURE__ */ jsx("div", {
|
|
141
|
-
className: cn("flex
|
|
174
|
+
className: 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"),
|
|
142
175
|
style: getColumnSizingStyle(col),
|
|
143
176
|
"data-col-id": col.id,
|
|
144
177
|
role: "gridcell",
|
|
@@ -150,7 +183,10 @@ function DataCell({ col, row, rowId, rowIndex, isSelected, dateDisplay }) {
|
|
|
150
183
|
e.stopPropagation();
|
|
151
184
|
col.onCellDoubleClick(ctx, e);
|
|
152
185
|
} : void 0,
|
|
153
|
-
children:
|
|
186
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
187
|
+
className: cn("min-w-0", isWrap ? "flex-1" : "truncate"),
|
|
188
|
+
children: content
|
|
189
|
+
})
|
|
154
190
|
});
|
|
155
191
|
}
|
|
156
192
|
function formatCellValue(value) {
|
|
@@ -190,6 +226,11 @@ function renderDateCell(value, dateDisplay, col) {
|
|
|
190
226
|
children: display
|
|
191
227
|
});
|
|
192
228
|
}
|
|
229
|
+
function hashStringToInt(value) {
|
|
230
|
+
let hash = 0;
|
|
231
|
+
for (let i = 0; i < value.length; i++) hash = (hash << 5) - hash + value.charCodeAt(i) | 0;
|
|
232
|
+
return Math.abs(hash);
|
|
233
|
+
}
|
|
193
234
|
function SkeletonRow({ columns, height, showCheckbox }) {
|
|
194
235
|
return /* @__PURE__ */ jsxs("div", {
|
|
195
236
|
className: "flex",
|
|
@@ -204,7 +245,7 @@ function SkeletonRow({ columns, height, showCheckbox }) {
|
|
|
204
245
|
style: getColumnSizingStyle(col),
|
|
205
246
|
children: /* @__PURE__ */ jsx(DesignSkeleton, {
|
|
206
247
|
className: "h-3.5 rounded-md",
|
|
207
|
-
style: { width: `${40 +
|
|
248
|
+
style: { width: `${40 + hashStringToInt(col.id) % 40}%` }
|
|
208
249
|
})
|
|
209
250
|
}, col.id))]
|
|
210
251
|
});
|
|
@@ -226,17 +267,21 @@ function SelectionCheckbox({ checked, indeterminate, onChange, ariaLabel }) {
|
|
|
226
267
|
})
|
|
227
268
|
});
|
|
228
269
|
}
|
|
229
|
-
|
|
270
|
+
const NOOP = () => {};
|
|
271
|
+
function InfiniteScrollSentinel({ onIntersect, isLoading, rootRef, strings }) {
|
|
230
272
|
const ref = useRef(null);
|
|
231
273
|
useEffect(() => {
|
|
232
274
|
const el = ref.current;
|
|
233
275
|
if (!el) return;
|
|
234
276
|
const observer = new IntersectionObserver((entries) => {
|
|
235
277
|
if (entries[0]?.isIntersecting) onIntersect();
|
|
236
|
-
}, {
|
|
278
|
+
}, {
|
|
279
|
+
root: rootRef?.current ?? null,
|
|
280
|
+
rootMargin: "200px"
|
|
281
|
+
});
|
|
237
282
|
observer.observe(el);
|
|
238
283
|
return () => observer.disconnect();
|
|
239
|
-
}, [onIntersect]);
|
|
284
|
+
}, [onIntersect, rootRef]);
|
|
240
285
|
return /* @__PURE__ */ jsx("div", {
|
|
241
286
|
ref,
|
|
242
287
|
className: "flex items-center justify-center py-4",
|
|
@@ -513,6 +558,39 @@ function DefaultFooter({ ctx, pagination, onChange }) {
|
|
|
513
558
|
* and footer all call it for you. You do not need to wire any of this
|
|
514
559
|
* manually.
|
|
515
560
|
*
|
|
561
|
+
* ## Cell overflow and dynamic row heights
|
|
562
|
+
*
|
|
563
|
+
* By default every cell truncates its content with an ellipsis
|
|
564
|
+
* (`cellOverflow: "truncate"`). For columns whose content should wrap
|
|
565
|
+
* — badge lists, multi-line text, permission chips — set
|
|
566
|
+
* `cellOverflow: "wrap"` on the column definition.
|
|
567
|
+
*
|
|
568
|
+
* To let rows grow to fit their tallest cell, set `rowHeight="auto"`
|
|
569
|
+
* on the grid. The virtualizer will measure each row after render and
|
|
570
|
+
* adjust scroll positions accordingly. Pair with `estimatedRowHeight`
|
|
571
|
+
* (default 44) for better scroll-position estimates before measurement.
|
|
572
|
+
*
|
|
573
|
+
* ```tsx
|
|
574
|
+
* // Columns: UUIDs truncate, auth-method badges wrap
|
|
575
|
+
* const columns = [
|
|
576
|
+
* { id: "userId", header: "User ID", width: 130 }, // default truncate
|
|
577
|
+
* { id: "auth", header: "Auth methods", width: 150, cellOverflow: "wrap",
|
|
578
|
+
* renderCell: ({ row }) => (
|
|
579
|
+
* <div className="flex flex-wrap gap-1">
|
|
580
|
+
* {row.authTypes.map((t) => <Badge key={t}>{t}</Badge>)}
|
|
581
|
+
* </div>
|
|
582
|
+
* ),
|
|
583
|
+
* },
|
|
584
|
+
* ];
|
|
585
|
+
*
|
|
586
|
+
* <DataGrid columns={columns} rowHeight="auto" estimatedRowHeight={48} ... />
|
|
587
|
+
* ```
|
|
588
|
+
*
|
|
589
|
+
* With a fixed numeric `rowHeight` (the default), `cellOverflow: "wrap"`
|
|
590
|
+
* still lets content wrap within the row, but anything exceeding the
|
|
591
|
+
* fixed height is clipped. This is useful when you want controlled
|
|
592
|
+
* wrapping without variable row heights.
|
|
593
|
+
*
|
|
516
594
|
* ## Height and scrolling
|
|
517
595
|
*
|
|
518
596
|
* DataGrid is NOT a card. It has no border, rounded corners, or shadow of
|
|
@@ -553,7 +631,10 @@ function DefaultFooter({ ctx, pagination, onChange }) {
|
|
|
553
631
|
* toggle for `date` / `dateTime` columns.
|
|
554
632
|
*/
|
|
555
633
|
function DataGrid(props) {
|
|
556
|
-
const { columns: allColumns, rows, getRowId, totalRowCount, isLoading = false, isRefetching = false, hasMore = false, isLoadingMore = false, onLoadMore, state, onChange, paginationMode = "paginated", selectionMode = "none", resizable = true, rowHeight = 44, headerHeight = 44, overscan = 5, maxHeight, toolbar, toolbarExtra, emptyState, loadingState, footer, footerExtra, exportFilename = "export", strings: stringsOverride, className, onRowClick, onRowDoubleClick, onSelectionChange, onSortChange } = props;
|
|
634
|
+
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;
|
|
635
|
+
const isDynamicRowHeight = rowHeightProp === "auto";
|
|
636
|
+
const fixedRowHeight = isDynamicRowHeight ? void 0 : rowHeightProp;
|
|
637
|
+
const estimatedRowHeight = estimatedRowHeightProp ?? fixedRowHeight ?? 44;
|
|
557
638
|
const strings = useMemo(() => resolveDataGridStrings(stringsOverride), [stringsOverride]);
|
|
558
639
|
const visibleColumns = useMemo(() => (state.columnOrder.length > 0 ? state.columnOrder.map((id) => allColumns.find((c) => c.id === id)).filter(Boolean) : allColumns).filter((col) => isColumnVisible(col.id, state.columnVisibility)), [
|
|
559
640
|
allColumns,
|
|
@@ -582,15 +663,17 @@ function DataGrid(props) {
|
|
|
582
663
|
const resizeRef = useRef(null);
|
|
583
664
|
const gridRef = useRef(null);
|
|
584
665
|
const handleSort = useCallback((columnId, multi) => {
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
666
|
+
const next = toggleSort(state.sorting, columnId, multi);
|
|
667
|
+
onChange((s) => ({
|
|
668
|
+
...s,
|
|
669
|
+
sorting: next
|
|
670
|
+
}));
|
|
671
|
+
onSortChange?.(next);
|
|
672
|
+
}, [
|
|
673
|
+
onChange,
|
|
674
|
+
onSortChange,
|
|
675
|
+
state.sorting
|
|
676
|
+
]);
|
|
594
677
|
const handleResize = useCallback((columnId, delta) => {
|
|
595
678
|
const col = allColumns.find((c) => c.id === columnId);
|
|
596
679
|
if (!col) return;
|
|
@@ -628,17 +711,17 @@ function DataGrid(props) {
|
|
|
628
711
|
}));
|
|
629
712
|
}, [onChange]);
|
|
630
713
|
const handleRowClick = useCallback((row, rowId, event) => {
|
|
631
|
-
if (selectionMode !== "none")
|
|
632
|
-
const next = toggleRowSelection(
|
|
714
|
+
if (selectionMode !== "none") {
|
|
715
|
+
const next = toggleRowSelection(state.selection, rowId, selectionMode, event.shiftKey, event.metaKey || event.ctrlKey, rowIds);
|
|
716
|
+
onChange((s) => ({
|
|
717
|
+
...s,
|
|
718
|
+
selection: next
|
|
719
|
+
}));
|
|
633
720
|
if (onSelectionChange) {
|
|
634
721
|
const selectedRows = rows.filter((r) => next.selectedIds.has(getRowId(r)));
|
|
635
|
-
|
|
722
|
+
onSelectionChange(next.selectedIds, selectedRows);
|
|
636
723
|
}
|
|
637
|
-
|
|
638
|
-
...s,
|
|
639
|
-
selection: next
|
|
640
|
-
};
|
|
641
|
-
});
|
|
724
|
+
}
|
|
642
725
|
onRowClick?.(row, rowId, event);
|
|
643
726
|
}, [
|
|
644
727
|
selectionMode,
|
|
@@ -647,29 +730,27 @@ function DataGrid(props) {
|
|
|
647
730
|
onSelectionChange,
|
|
648
731
|
rowIds,
|
|
649
732
|
rows,
|
|
650
|
-
getRowId
|
|
733
|
+
getRowId,
|
|
734
|
+
state.selection
|
|
651
735
|
]);
|
|
652
736
|
const handleRowSelectionCheckboxClick = useCallback((row, rowId, event) => {
|
|
653
737
|
handleRowClick(row, rowId, event);
|
|
654
738
|
}, [handleRowClick]);
|
|
655
739
|
const handleSelectAll = useCallback(() => {
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
...s,
|
|
665
|
-
selection: next
|
|
666
|
-
};
|
|
667
|
-
});
|
|
740
|
+
const allSelectedNow = rowIds.every((id) => state.selection.selectedIds.has(id));
|
|
741
|
+
const next = allSelectedNow ? clearSelection() : selectAll(rowIds);
|
|
742
|
+
const selectedRows = allSelectedNow ? [] : rows;
|
|
743
|
+
onChange((s) => ({
|
|
744
|
+
...s,
|
|
745
|
+
selection: next
|
|
746
|
+
}));
|
|
747
|
+
if (onSelectionChange) onSelectionChange(next.selectedIds, [...selectedRows]);
|
|
668
748
|
}, [
|
|
669
749
|
onChange,
|
|
670
750
|
rowIds,
|
|
671
751
|
rows,
|
|
672
|
-
onSelectionChange
|
|
752
|
+
onSelectionChange,
|
|
753
|
+
state.selection
|
|
673
754
|
]);
|
|
674
755
|
const handleExportCsv = useCallback(() => {
|
|
675
756
|
exportToCsv(rows, visibleColumns, exportFilename);
|
|
@@ -680,12 +761,118 @@ function DataGrid(props) {
|
|
|
680
761
|
]);
|
|
681
762
|
const scrollContainerRef = useRef(null);
|
|
682
763
|
const headerScrollRef = useRef(null);
|
|
764
|
+
const stickyChromeRef = useRef(null);
|
|
765
|
+
const rowsClipRef = useRef(null);
|
|
766
|
+
const measureElementFn = useCallback((el) => el.getBoundingClientRect().height, []);
|
|
683
767
|
const rowVirtualizer = useVirtualizer({
|
|
684
768
|
count: rows.length,
|
|
685
769
|
getScrollElement: () => scrollContainerRef.current,
|
|
686
|
-
estimateSize: () =>
|
|
687
|
-
overscan
|
|
770
|
+
estimateSize: () => estimatedRowHeight,
|
|
771
|
+
overscan,
|
|
772
|
+
getItemKey: (index) => {
|
|
773
|
+
const row = rows[index];
|
|
774
|
+
return row != null ? String(getRowId(row)) : index;
|
|
775
|
+
},
|
|
776
|
+
...isDynamicRowHeight ? { measureElement: measureElementFn } : {}
|
|
688
777
|
});
|
|
778
|
+
useLayoutEffect(() => {
|
|
779
|
+
const grid = gridRef.current;
|
|
780
|
+
const stickyEl = stickyChromeRef.current;
|
|
781
|
+
if (!grid || !stickyEl) return;
|
|
782
|
+
const parseRgba = (raw) => {
|
|
783
|
+
const rgbaMatch = raw.match(/rgba?\(\s*([\d.]+),\s*([\d.]+),\s*([\d.]+)(?:,\s*([\d.]+))?\s*\)/);
|
|
784
|
+
if (!rgbaMatch) return null;
|
|
785
|
+
const alphaRaw = rgbaMatch[4];
|
|
786
|
+
return [
|
|
787
|
+
Number(rgbaMatch[1]),
|
|
788
|
+
Number(rgbaMatch[2]),
|
|
789
|
+
Number(rgbaMatch[3]),
|
|
790
|
+
alphaRaw === void 0 ? 1 : Number(alphaRaw)
|
|
791
|
+
];
|
|
792
|
+
};
|
|
793
|
+
const blendOver = (base, top) => {
|
|
794
|
+
const [tr, tg, tb, ta] = top;
|
|
795
|
+
const [br, bg, bb, ba] = base;
|
|
796
|
+
const outA = ta + ba * (1 - ta);
|
|
797
|
+
if (outA === 0) return [
|
|
798
|
+
0,
|
|
799
|
+
0,
|
|
800
|
+
0,
|
|
801
|
+
0
|
|
802
|
+
];
|
|
803
|
+
return [
|
|
804
|
+
(tr * ta + br * ba * (1 - ta)) / outA,
|
|
805
|
+
(tg * ta + bg * ba * (1 - ta)) / outA,
|
|
806
|
+
(tb * ta + bb * ba * (1 - ta)) / outA,
|
|
807
|
+
outA
|
|
808
|
+
];
|
|
809
|
+
};
|
|
810
|
+
const detect = () => {
|
|
811
|
+
const layers = [];
|
|
812
|
+
let ancestor = grid.parentElement;
|
|
813
|
+
while (ancestor) {
|
|
814
|
+
const parsed = parseRgba(getComputedStyle(ancestor).backgroundColor);
|
|
815
|
+
if (parsed && parsed[3] > 0) {
|
|
816
|
+
layers.push(parsed);
|
|
817
|
+
if (parsed[3] >= 1) break;
|
|
818
|
+
}
|
|
819
|
+
ancestor = ancestor.parentElement;
|
|
820
|
+
}
|
|
821
|
+
if (layers.length === 0) {
|
|
822
|
+
stickyEl.style.backgroundColor = "";
|
|
823
|
+
return;
|
|
824
|
+
}
|
|
825
|
+
let result = layers[layers.length - 1];
|
|
826
|
+
for (let i = layers.length - 2; i >= 0; i--) result = blendOver(result, layers[i]);
|
|
827
|
+
const [r, g, b] = result;
|
|
828
|
+
stickyEl.style.backgroundColor = `rgb(${Math.round(r)}, ${Math.round(g)}, ${Math.round(b)})`;
|
|
829
|
+
};
|
|
830
|
+
detect();
|
|
831
|
+
const observer = new MutationObserver(detect);
|
|
832
|
+
observer.observe(document.documentElement, {
|
|
833
|
+
attributes: true,
|
|
834
|
+
attributeFilter: ["class"]
|
|
835
|
+
});
|
|
836
|
+
return () => observer.disconnect();
|
|
837
|
+
}, []);
|
|
838
|
+
useLayoutEffect(() => {
|
|
839
|
+
const gridEl = gridRef.current;
|
|
840
|
+
const stickyEl = stickyChromeRef.current;
|
|
841
|
+
const bodyEl = scrollContainerRef.current;
|
|
842
|
+
const clipEl = rowsClipRef.current;
|
|
843
|
+
if (!gridEl || !stickyEl || !bodyEl || !clipEl) return;
|
|
844
|
+
const verticalScrollEl = fillHeight ? bodyEl : getNearestVerticalScrollElement(gridEl);
|
|
845
|
+
let extraObservedScrollEl = null;
|
|
846
|
+
if (verticalScrollEl instanceof HTMLElement && verticalScrollEl !== bodyEl) extraObservedScrollEl = verticalScrollEl;
|
|
847
|
+
const updateClip = () => {
|
|
848
|
+
const stickyRect = stickyEl.getBoundingClientRect();
|
|
849
|
+
const clipRect = clipEl.getBoundingClientRect();
|
|
850
|
+
const overlap = Math.max(0, stickyRect.bottom - clipRect.top);
|
|
851
|
+
const clipValue = overlap > 0 ? `inset(${overlap}px 0 0 0)` : "";
|
|
852
|
+
const maskValue = overlap > 0 ? `linear-gradient(to bottom, transparent 0px, transparent ${overlap}px, black ${overlap}px, black 100%)` : "";
|
|
853
|
+
clipEl.style.clipPath = clipValue;
|
|
854
|
+
clipEl.style.setProperty("-webkit-clip-path", clipValue);
|
|
855
|
+
clipEl.style.maskImage = maskValue;
|
|
856
|
+
clipEl.style.setProperty("-webkit-mask-image", maskValue);
|
|
857
|
+
};
|
|
858
|
+
updateClip();
|
|
859
|
+
bodyEl.addEventListener("scroll", updateClip);
|
|
860
|
+
if (verticalScrollEl === window) window.addEventListener("scroll", updateClip, true);
|
|
861
|
+
else if (extraObservedScrollEl) extraObservedScrollEl.addEventListener("scroll", updateClip);
|
|
862
|
+
window.addEventListener("resize", updateClip);
|
|
863
|
+
const ro = new ResizeObserver(updateClip);
|
|
864
|
+
ro.observe(gridEl);
|
|
865
|
+
ro.observe(stickyEl);
|
|
866
|
+
ro.observe(bodyEl);
|
|
867
|
+
if (extraObservedScrollEl) ro.observe(extraObservedScrollEl);
|
|
868
|
+
return () => {
|
|
869
|
+
bodyEl.removeEventListener("scroll", updateClip);
|
|
870
|
+
if (verticalScrollEl === window) window.removeEventListener("scroll", updateClip, true);
|
|
871
|
+
else if (extraObservedScrollEl) extraObservedScrollEl.removeEventListener("scroll", updateClip);
|
|
872
|
+
window.removeEventListener("resize", updateClip);
|
|
873
|
+
ro.disconnect();
|
|
874
|
+
};
|
|
875
|
+
}, [fillHeight]);
|
|
689
876
|
const handleBodyScroll = useCallback(() => {
|
|
690
877
|
const body = scrollContainerRef.current;
|
|
691
878
|
const header = headerScrollRef.current;
|
|
@@ -725,9 +912,10 @@ function DataGrid(props) {
|
|
|
725
912
|
]);
|
|
726
913
|
const allSelected = rowIds.length > 0 && rowIds.every((id) => state.selection.selectedIds.has(id));
|
|
727
914
|
const someSelected = !allSelected && rowIds.some((id) => state.selection.selectedIds.has(id));
|
|
915
|
+
const infiniteScrollRootRef = paginationMode === "infinite" && (fillHeight || maxHeight != null) ? scrollContainerRef : void 0;
|
|
728
916
|
return /* @__PURE__ */ jsxs("div", {
|
|
729
917
|
ref: gridRef,
|
|
730
|
-
className: cn("flex flex-col h-full min-h-0
|
|
918
|
+
className: 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),
|
|
731
919
|
style: maxHeight != null ? {
|
|
732
920
|
...gridSizingStyle,
|
|
733
921
|
maxHeight
|
|
@@ -736,23 +924,24 @@ function DataGrid(props) {
|
|
|
736
924
|
"aria-rowcount": totalRowCount ?? rows.length,
|
|
737
925
|
"aria-colcount": visibleColumns.length,
|
|
738
926
|
children: [
|
|
739
|
-
toolbar !== false && /* @__PURE__ */ jsx("div", {
|
|
740
|
-
className: "relative shrink-0 bg-transparent",
|
|
741
|
-
children: toolbar ? toolbar(toolbarCtx) : /* @__PURE__ */ jsx(DataGridToolbar, {
|
|
742
|
-
ctx: toolbarCtx,
|
|
743
|
-
extra: typeof toolbarExtra === "function" ? toolbarExtra(toolbarCtx) : toolbarExtra
|
|
744
|
-
})
|
|
745
|
-
}),
|
|
746
927
|
/* @__PURE__ */ jsxs("div", {
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
928
|
+
ref: stickyChromeRef,
|
|
929
|
+
className: "sticky z-20 w-full min-w-0 shrink-0 rounded-t-[calc(var(--radius)*2)] bg-background",
|
|
930
|
+
style: { top: stickyTop ?? "var(--data-grid-sticky-top, 0px)" },
|
|
931
|
+
children: [toolbar !== false && /* @__PURE__ */ jsx("div", {
|
|
932
|
+
className: "relative bg-transparent",
|
|
933
|
+
children: toolbar ? toolbar(toolbarCtx) : /* @__PURE__ */ jsx(DataGridToolbar, {
|
|
934
|
+
ctx: toolbarCtx,
|
|
935
|
+
extra: typeof toolbarExtra === "function" ? toolbarExtra(toolbarCtx) : toolbarExtra
|
|
936
|
+
})
|
|
937
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
938
|
+
className: "relative",
|
|
939
|
+
children: [isRefetching && /* @__PURE__ */ jsx("div", {
|
|
750
940
|
className: "absolute top-0 left-0 right-0 h-0.5 z-30 bg-foreground/[0.04] overflow-hidden",
|
|
751
941
|
children: /* @__PURE__ */ jsx("div", { className: "h-full w-1/3 bg-blue-500/60 rounded-full animate-pulse" })
|
|
752
|
-
}),
|
|
753
|
-
/* @__PURE__ */ jsx("div", {
|
|
942
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
754
943
|
ref: headerScrollRef,
|
|
755
|
-
className: "
|
|
944
|
+
className: "w-full min-w-0 shrink-0 overflow-hidden border-b border-foreground/[0.06]",
|
|
756
945
|
children: /* @__PURE__ */ jsxs("div", {
|
|
757
946
|
className: "flex",
|
|
758
947
|
style: {
|
|
@@ -779,77 +968,89 @@ function DataGrid(props) {
|
|
|
779
968
|
onResizeEnd: handleResizeEnd
|
|
780
969
|
}, col.id))]
|
|
781
970
|
})
|
|
782
|
-
})
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
971
|
+
})]
|
|
972
|
+
})]
|
|
973
|
+
}),
|
|
974
|
+
/* @__PURE__ */ jsx("div", {
|
|
975
|
+
ref: scrollContainerRef,
|
|
976
|
+
className: cn("w-full min-w-0 overflow-auto bg-transparent", fillHeight ? "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]"),
|
|
977
|
+
onScroll: handleBodyScroll,
|
|
978
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
979
|
+
ref: rowsClipRef,
|
|
980
|
+
children: [
|
|
981
|
+
isLoading && /* @__PURE__ */ jsx("div", {
|
|
982
|
+
style: { minWidth: visibleColumnMetrics.totalWidth },
|
|
983
|
+
children: loadingState ?? Array.from({ length: 8 }).map((_, i) => /* @__PURE__ */ jsx(SkeletonRow, {
|
|
984
|
+
columns: visibleColumns,
|
|
985
|
+
height: estimatedRowHeight,
|
|
986
|
+
showCheckbox: selectionMode !== "none"
|
|
987
|
+
}, i))
|
|
988
|
+
}),
|
|
989
|
+
!isLoading && rows.length === 0 && /* @__PURE__ */ jsx("div", {
|
|
990
|
+
className: "flex items-center justify-center py-16 text-sm text-muted-foreground",
|
|
991
|
+
style: { minWidth: visibleColumnMetrics.totalWidth },
|
|
992
|
+
children: emptyState ?? strings.noData
|
|
993
|
+
}),
|
|
994
|
+
!isLoading && rows.length > 0 && /* @__PURE__ */ jsx("div", {
|
|
995
|
+
style: {
|
|
996
|
+
height: rowVirtualizer.getTotalSize(),
|
|
997
|
+
width: "100%",
|
|
998
|
+
minWidth: visibleColumnMetrics.totalWidth,
|
|
999
|
+
position: "relative"
|
|
1000
|
+
},
|
|
1001
|
+
children: rowVirtualizer.getVirtualItems().map((virtualRow) => {
|
|
1002
|
+
const row = rows[virtualRow.index] ?? throwErr(`DataGrid: virtualized row index ${virtualRow.index} out of range (rows.length=${rows.length})`);
|
|
1003
|
+
const rowId = getRowId(row);
|
|
1004
|
+
const isSelected = state.selection.selectedIds.has(rowId);
|
|
1005
|
+
const isOddRow = virtualRow.index % 2 === 1;
|
|
1006
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
1007
|
+
ref: isDynamicRowHeight ? rowVirtualizer.measureElement : void 0,
|
|
1008
|
+
"data-index": virtualRow.index,
|
|
1009
|
+
className: cn("absolute left-0 w-full flex", "border-b border-black/[0.03] dark:border-white/[0.03]", "transition-colors duration-75", isSelected ? "bg-blue-500/[0.06] dark:bg-blue-400/[0.08] hover:bg-blue-500/[0.08] dark:hover:bg-blue-400/[0.1]" : isOddRow ? "bg-foreground/[0.02] dark:bg-foreground/[0.03] hover:bg-foreground/[0.04] dark:hover:bg-foreground/[0.06]" : "hover:bg-foreground/[0.025] dark:hover:bg-foreground/[0.04]", (selectionMode !== "none" || onRowClick) && "cursor-pointer"),
|
|
1010
|
+
style: {
|
|
1011
|
+
...isDynamicRowHeight ? { minHeight: estimatedRowHeight } : { height: fixedRowHeight },
|
|
1012
|
+
transform: `translateY(${virtualRow.start}px)`
|
|
1013
|
+
},
|
|
1014
|
+
onClick: (e) => {
|
|
1015
|
+
if (shouldIgnoreRowClick(e)) return;
|
|
1016
|
+
handleRowClick(row, rowId, e);
|
|
1017
|
+
},
|
|
1018
|
+
onDoubleClick: (e) => {
|
|
1019
|
+
if (shouldIgnoreRowClick(e)) return;
|
|
1020
|
+
onRowDoubleClick?.(row, rowId, e);
|
|
1021
|
+
},
|
|
1022
|
+
role: "row",
|
|
1023
|
+
"aria-rowindex": virtualRow.index + 2,
|
|
1024
|
+
"aria-selected": isSelected,
|
|
1025
|
+
"data-row-id": rowId,
|
|
1026
|
+
"data-state": isSelected ? "selected" : void 0,
|
|
1027
|
+
children: [selectionMode !== "none" && /* @__PURE__ */ jsx("div", {
|
|
1028
|
+
className: "flex items-center justify-center border-r border-black/[0.04] dark:border-white/[0.04]",
|
|
1029
|
+
style: { width: 44 },
|
|
1030
|
+
children: /* @__PURE__ */ jsx(SelectionCheckbox, {
|
|
1031
|
+
checked: isSelected,
|
|
1032
|
+
onChange: (event) => handleRowSelectionCheckboxClick(row, rowId, event),
|
|
1033
|
+
ariaLabel: `Select row ${rowId}`
|
|
1034
|
+
})
|
|
1035
|
+
}), visibleColumns.map((col) => /* @__PURE__ */ jsx(DataCell, {
|
|
1036
|
+
col,
|
|
1037
|
+
row,
|
|
1038
|
+
rowId,
|
|
1039
|
+
rowIndex: virtualRow.index,
|
|
1040
|
+
isSelected,
|
|
1041
|
+
dateDisplay: state.dateDisplay
|
|
1042
|
+
}, col.id))]
|
|
1043
|
+
}, rowId);
|
|
849
1044
|
})
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
1045
|
+
}),
|
|
1046
|
+
paginationMode === "infinite" && hasMore && !isLoading && /* @__PURE__ */ jsx(InfiniteScrollSentinel, {
|
|
1047
|
+
onIntersect: onLoadMore ?? NOOP,
|
|
1048
|
+
isLoading: isLoadingMore,
|
|
1049
|
+
rootRef: infiniteScrollRootRef,
|
|
1050
|
+
strings
|
|
1051
|
+
})
|
|
1052
|
+
]
|
|
1053
|
+
})
|
|
853
1054
|
}),
|
|
854
1055
|
footer !== false && /* @__PURE__ */ jsxs("div", {
|
|
855
1056
|
className: "relative z-10 shrink-0 bg-transparent",
|
|
@@ -864,5 +1065,5 @@ function DataGrid(props) {
|
|
|
864
1065
|
}
|
|
865
1066
|
|
|
866
1067
|
//#endregion
|
|
867
|
-
export { DataGrid };
|
|
1068
|
+
export { DataGrid, isDataGridInteractiveRowClickTarget };
|
|
868
1069
|
//# sourceMappingURL=data-grid.js.map
|