@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.
Files changed (64) hide show
  1. package/dist/components/analytics-chart/analytics-chart-pie.js +3 -3
  2. package/dist/components/analytics-chart/analytics-chart-pie.js.map +1 -1
  3. package/dist/components/data-grid/data-grid-sizing.d.ts +2 -1
  4. package/dist/components/data-grid/data-grid-sizing.d.ts.map +1 -1
  5. package/dist/components/data-grid/data-grid-sizing.js +33 -4
  6. package/dist/components/data-grid/data-grid-sizing.js.map +1 -1
  7. package/dist/components/data-grid/data-grid-toolbar.js +18 -15
  8. package/dist/components/data-grid/data-grid-toolbar.js.map +1 -1
  9. package/dist/components/data-grid/data-grid.d.ts +35 -1
  10. package/dist/components/data-grid/data-grid.d.ts.map +1 -1
  11. package/dist/components/data-grid/data-grid.js +329 -127
  12. package/dist/components/data-grid/data-grid.js.map +1 -1
  13. package/dist/components/data-grid/data-grid.test.d.ts +1 -0
  14. package/dist/components/data-grid/data-grid.test.js +215 -0
  15. package/dist/components/data-grid/data-grid.test.js.map +1 -0
  16. package/dist/components/data-grid/index.d.ts +3 -2
  17. package/dist/components/data-grid/index.js +13 -0
  18. package/dist/components/data-grid/state.d.ts.map +1 -1
  19. package/dist/components/data-grid/state.js +24 -7
  20. package/dist/components/data-grid/state.js.map +1 -1
  21. package/dist/components/data-grid/types.d.ts +34 -3
  22. package/dist/components/data-grid/types.d.ts.map +1 -1
  23. package/dist/components/data-grid/use-data-source.d.ts +6 -0
  24. package/dist/components/data-grid/use-data-source.d.ts.map +1 -1
  25. package/dist/components/data-grid/use-data-source.js +10 -2
  26. package/dist/components/data-grid/use-data-source.js.map +1 -1
  27. package/dist/components/tabs.d.ts +5 -1
  28. package/dist/components/tabs.d.ts.map +1 -1
  29. package/dist/components/tabs.js +40 -27
  30. package/dist/components/tabs.js.map +1 -1
  31. package/dist/dashboard-ui-components.global.js +672 -368
  32. package/dist/dashboard-ui-components.global.js.map +4 -4
  33. package/dist/esm/components/analytics-chart/analytics-chart-pie.js +3 -3
  34. package/dist/esm/components/analytics-chart/analytics-chart-pie.js.map +1 -1
  35. package/dist/esm/components/data-grid/data-grid-sizing.d.ts +2 -1
  36. package/dist/esm/components/data-grid/data-grid-sizing.d.ts.map +1 -1
  37. package/dist/esm/components/data-grid/data-grid-sizing.js +33 -5
  38. package/dist/esm/components/data-grid/data-grid-sizing.js.map +1 -1
  39. package/dist/esm/components/data-grid/data-grid-toolbar.js +18 -15
  40. package/dist/esm/components/data-grid/data-grid-toolbar.js.map +1 -1
  41. package/dist/esm/components/data-grid/data-grid.d.ts +35 -1
  42. package/dist/esm/components/data-grid/data-grid.d.ts.map +1 -1
  43. package/dist/esm/components/data-grid/data-grid.js +329 -128
  44. package/dist/esm/components/data-grid/data-grid.js.map +1 -1
  45. package/dist/esm/components/data-grid/data-grid.test.d.ts +1 -0
  46. package/dist/esm/components/data-grid/data-grid.test.js +215 -0
  47. package/dist/esm/components/data-grid/data-grid.test.js.map +1 -0
  48. package/dist/esm/components/data-grid/index.d.ts +3 -2
  49. package/dist/esm/components/data-grid/index.js +3 -2
  50. package/dist/esm/components/data-grid/state.d.ts.map +1 -1
  51. package/dist/esm/components/data-grid/state.js +24 -7
  52. package/dist/esm/components/data-grid/state.js.map +1 -1
  53. package/dist/esm/components/data-grid/types.d.ts +34 -3
  54. package/dist/esm/components/data-grid/types.d.ts.map +1 -1
  55. package/dist/esm/components/data-grid/use-data-source.d.ts +6 -0
  56. package/dist/esm/components/data-grid/use-data-source.d.ts.map +1 -1
  57. package/dist/esm/components/data-grid/use-data-source.js +10 -2
  58. package/dist/esm/components/data-grid/use-data-source.js.map +1 -1
  59. package/dist/esm/components/tabs.d.ts +5 -1
  60. package/dist/esm/components/tabs.d.ts.map +1 -1
  61. package/dist/esm/components/tabs.js +40 -27
  62. package/dist/esm/components/tabs.js.map +1 -1
  63. package/dist/index.d.ts +3 -2
  64. package/package.json +4 -4
@@ -9,10 +9,11 @@ let react = require("react");
9
9
  react = require_chunk.__toESM(react);
10
10
  let __state_js = require("./state.js");
11
11
  let __strings_js = require("./strings.js");
12
+ let _stackframe_stack_shared_dist_utils_errors = require("@stackframe/stack-shared/dist/utils/errors");
12
13
  let _tanstack_react_virtual = require("@tanstack/react-virtual");
13
14
  let ___skeleton_js = require("../skeleton.js");
14
- let __data_grid_toolbar_js = require("./data-grid-toolbar.js");
15
15
  let __data_grid_sizing_js = require("./data-grid-sizing.js");
16
+ let __data_grid_toolbar_js = require("./data-grid-toolbar.js");
16
17
 
17
18
  //#region src/components/data-grid/data-grid.tsx
18
19
  function ResizeHandle({ onResize, onResizeEnd }) {
@@ -72,6 +73,37 @@ function ResizeHandle({ onResize, onResizeEnd }) {
72
73
  onPointerDown
73
74
  });
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
+ function getEventTargetElement(target) {
87
+ if (target instanceof Element) return target;
88
+ if (target instanceof Node) return target.parentElement;
89
+ return null;
90
+ }
91
+ function isDataGridInteractiveRowClickTarget(target) {
92
+ return getEventTargetElement(target)?.closest([
93
+ "a",
94
+ "button",
95
+ "input",
96
+ "select",
97
+ "textarea",
98
+ "[role=\"button\"]",
99
+ "[role=\"menuitem\"]",
100
+ "[contenteditable]:not([contenteditable=\"false\"])",
101
+ "[data-no-row-click]"
102
+ ].join(",")) != null;
103
+ }
104
+ function shouldIgnoreRowClick(event) {
105
+ return event.defaultPrevented || isDataGridInteractiveRowClickTarget(event.target);
106
+ }
75
107
  function HeaderCell({ col, isSorted, sortIndex, resizable, onSort, onResize, onResizeEnd }) {
76
108
  const ctx = {
77
109
  columnId: col.id,
@@ -82,7 +114,7 @@ function HeaderCell({ col, isSorted, sortIndex, resizable, onSort, onResize, onR
82
114
  const label = typeof col.header === "function" ? col.header(ctx) : col.header;
83
115
  const sortable = col.sortable !== false;
84
116
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
85
- className: (0, _stackframe_stack_ui.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"),
117
+ 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"),
86
118
  style: (0, __data_grid_sizing_js.getColumnSizingStyle)(col),
87
119
  "data-col-id": col.id,
88
120
  onClick: (e) => sortable && onSort(col.id, e.metaKey || e.ctrlKey),
@@ -90,7 +122,7 @@ function HeaderCell({ col, isSorted, sortIndex, resizable, onSort, onResize, onR
90
122
  "aria-sort": isSorted === "asc" ? "ascending" : isSorted === "desc" ? "descending" : "none",
91
123
  children: [
92
124
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
93
- className: (0, _stackframe_stack_ui.cn)("flex-1 truncate text-xs font-semibold uppercase tracking-wider text-muted-foreground", col.align === "center" && "text-center", col.align === "right" && "text-right"),
125
+ 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"),
94
126
  children: label
95
127
  }),
96
128
  isSorted && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
@@ -140,8 +172,9 @@ function DataCell({ col, row, rowId, rowIndex, isSelected, dateDisplay }) {
140
172
  else if (isDateCol) content = renderDateCell(value, dateDisplay, col);
141
173
  else content = formatCellValue(value);
142
174
  const hasCellClick = col.onCellClick || col.onCellDoubleClick;
175
+ const isWrap = col.cellOverflow === "wrap";
143
176
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
144
- className: (0, _stackframe_stack_ui.cn)("flex items-center px-3 truncate bg-transparent", "border-r border-black/[0.04] dark:border-white/[0.04] last:border-r-0", "text-sm text-foreground", col.align === "center" && "justify-center", col.align === "right" && "justify-end", hasCellClick && "cursor-pointer"),
177
+ 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"),
145
178
  style: (0, __data_grid_sizing_js.getColumnSizingStyle)(col),
146
179
  "data-col-id": col.id,
147
180
  role: "gridcell",
@@ -153,7 +186,10 @@ function DataCell({ col, row, rowId, rowIndex, isSelected, dateDisplay }) {
153
186
  e.stopPropagation();
154
187
  col.onCellDoubleClick(ctx, e);
155
188
  } : void 0,
156
- children: content
189
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
190
+ className: (0, _stackframe_stack_ui.cn)("min-w-0", isWrap ? "flex-1" : "truncate"),
191
+ children: content
192
+ })
157
193
  });
158
194
  }
159
195
  function formatCellValue(value) {
@@ -193,6 +229,11 @@ function renderDateCell(value, dateDisplay, col) {
193
229
  children: display
194
230
  });
195
231
  }
232
+ function hashStringToInt(value) {
233
+ let hash = 0;
234
+ for (let i = 0; i < value.length; i++) hash = (hash << 5) - hash + value.charCodeAt(i) | 0;
235
+ return Math.abs(hash);
236
+ }
196
237
  function SkeletonRow({ columns, height, showCheckbox }) {
197
238
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
198
239
  className: "flex",
@@ -207,7 +248,7 @@ function SkeletonRow({ columns, height, showCheckbox }) {
207
248
  style: (0, __data_grid_sizing_js.getColumnSizingStyle)(col),
208
249
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(___skeleton_js.DesignSkeleton, {
209
250
  className: "h-3.5 rounded-md",
210
- style: { width: `${40 + Math.random() * 40}%` }
251
+ style: { width: `${40 + hashStringToInt(col.id) % 40}%` }
211
252
  })
212
253
  }, col.id))]
213
254
  });
@@ -229,17 +270,21 @@ function SelectionCheckbox({ checked, indeterminate, onChange, ariaLabel }) {
229
270
  })
230
271
  });
231
272
  }
232
- function InfiniteScrollSentinel({ onIntersect, isLoading, strings }) {
273
+ const NOOP = () => {};
274
+ function InfiniteScrollSentinel({ onIntersect, isLoading, rootRef, strings }) {
233
275
  const ref = (0, react.useRef)(null);
234
276
  (0, react.useEffect)(() => {
235
277
  const el = ref.current;
236
278
  if (!el) return;
237
279
  const observer = new IntersectionObserver((entries) => {
238
280
  if (entries[0]?.isIntersecting) onIntersect();
239
- }, { rootMargin: "200px" });
281
+ }, {
282
+ root: rootRef?.current ?? null,
283
+ rootMargin: "200px"
284
+ });
240
285
  observer.observe(el);
241
286
  return () => observer.disconnect();
242
- }, [onIntersect]);
287
+ }, [onIntersect, rootRef]);
243
288
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
244
289
  ref,
245
290
  className: "flex items-center justify-center py-4",
@@ -516,6 +561,39 @@ function DefaultFooter({ ctx, pagination, onChange }) {
516
561
  * and footer all call it for you. You do not need to wire any of this
517
562
  * manually.
518
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
+ *
519
597
  * ## Height and scrolling
520
598
  *
521
599
  * DataGrid is NOT a card. It has no border, rounded corners, or shadow of
@@ -556,7 +634,10 @@ function DefaultFooter({ ctx, pagination, onChange }) {
556
634
  * toggle for `date` / `dateTime` columns.
557
635
  */
558
636
  function DataGrid(props) {
559
- 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;
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;
638
+ const isDynamicRowHeight = rowHeightProp === "auto";
639
+ const fixedRowHeight = isDynamicRowHeight ? void 0 : rowHeightProp;
640
+ const estimatedRowHeight = estimatedRowHeightProp ?? fixedRowHeight ?? 44;
560
641
  const strings = (0, react.useMemo)(() => (0, __strings_js.resolveDataGridStrings)(stringsOverride), [stringsOverride]);
561
642
  const visibleColumns = (0, react.useMemo)(() => (state.columnOrder.length > 0 ? state.columnOrder.map((id) => allColumns.find((c) => c.id === id)).filter(Boolean) : allColumns).filter((col) => (0, __state_js.isColumnVisible)(col.id, state.columnVisibility)), [
562
643
  allColumns,
@@ -585,15 +666,17 @@ function DataGrid(props) {
585
666
  const resizeRef = (0, react.useRef)(null);
586
667
  const gridRef = (0, react.useRef)(null);
587
668
  const handleSort = (0, react.useCallback)((columnId, multi) => {
588
- onChange((s) => {
589
- const next = (0, __state_js.toggleSort)(s.sorting, columnId, multi);
590
- onSortChange?.(next);
591
- return {
592
- ...s,
593
- sorting: next
594
- };
595
- });
596
- }, [onChange, onSortChange]);
669
+ const next = (0, __state_js.toggleSort)(state.sorting, columnId, multi);
670
+ onChange((s) => ({
671
+ ...s,
672
+ sorting: next
673
+ }));
674
+ onSortChange?.(next);
675
+ }, [
676
+ onChange,
677
+ onSortChange,
678
+ state.sorting
679
+ ]);
597
680
  const handleResize = (0, react.useCallback)((columnId, delta) => {
598
681
  const col = allColumns.find((c) => c.id === columnId);
599
682
  if (!col) return;
@@ -631,17 +714,17 @@ function DataGrid(props) {
631
714
  }));
632
715
  }, [onChange]);
633
716
  const handleRowClick = (0, react.useCallback)((row, rowId, event) => {
634
- if (selectionMode !== "none") onChange((s) => {
635
- const next = (0, __state_js.toggleRowSelection)(s.selection, rowId, selectionMode, event.shiftKey, event.metaKey || event.ctrlKey, rowIds);
717
+ if (selectionMode !== "none") {
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
+ }));
636
723
  if (onSelectionChange) {
637
724
  const selectedRows = rows.filter((r) => next.selectedIds.has(getRowId(r)));
638
- setTimeout(() => onSelectionChange(next.selectedIds, selectedRows), 0);
725
+ onSelectionChange(next.selectedIds, selectedRows);
639
726
  }
640
- return {
641
- ...s,
642
- selection: next
643
- };
644
- });
727
+ }
645
728
  onRowClick?.(row, rowId, event);
646
729
  }, [
647
730
  selectionMode,
@@ -650,29 +733,27 @@ function DataGrid(props) {
650
733
  onSelectionChange,
651
734
  rowIds,
652
735
  rows,
653
- getRowId
736
+ getRowId,
737
+ state.selection
654
738
  ]);
655
739
  const handleRowSelectionCheckboxClick = (0, react.useCallback)((row, rowId, event) => {
656
740
  handleRowClick(row, rowId, event);
657
741
  }, [handleRowClick]);
658
742
  const handleSelectAll = (0, react.useCallback)(() => {
659
- onChange((s) => {
660
- const allSelected = rowIds.every((id) => s.selection.selectedIds.has(id));
661
- const next = allSelected ? (0, __state_js.clearSelection)() : (0, __state_js.selectAll)(rowIds);
662
- if (onSelectionChange) {
663
- const selectedRows = allSelected ? [] : rows;
664
- setTimeout(() => onSelectionChange(next.selectedIds, [...selectedRows]), 0);
665
- }
666
- return {
667
- ...s,
668
- selection: next
669
- };
670
- });
743
+ const allSelectedNow = rowIds.every((id) => state.selection.selectedIds.has(id));
744
+ const next = allSelectedNow ? (0, __state_js.clearSelection)() : (0, __state_js.selectAll)(rowIds);
745
+ const selectedRows = allSelectedNow ? [] : rows;
746
+ onChange((s) => ({
747
+ ...s,
748
+ selection: next
749
+ }));
750
+ if (onSelectionChange) onSelectionChange(next.selectedIds, [...selectedRows]);
671
751
  }, [
672
752
  onChange,
673
753
  rowIds,
674
754
  rows,
675
- onSelectionChange
755
+ onSelectionChange,
756
+ state.selection
676
757
  ]);
677
758
  const handleExportCsv = (0, react.useCallback)(() => {
678
759
  (0, __state_js.exportToCsv)(rows, visibleColumns, exportFilename);
@@ -683,12 +764,118 @@ function DataGrid(props) {
683
764
  ]);
684
765
  const scrollContainerRef = (0, react.useRef)(null);
685
766
  const headerScrollRef = (0, react.useRef)(null);
767
+ const stickyChromeRef = (0, react.useRef)(null);
768
+ const rowsClipRef = (0, react.useRef)(null);
769
+ const measureElementFn = (0, react.useCallback)((el) => el.getBoundingClientRect().height, []);
686
770
  const rowVirtualizer = (0, _tanstack_react_virtual.useVirtualizer)({
687
771
  count: rows.length,
688
772
  getScrollElement: () => scrollContainerRef.current,
689
- estimateSize: () => rowHeight,
690
- overscan
773
+ estimateSize: () => estimatedRowHeight,
774
+ overscan,
775
+ getItemKey: (index) => {
776
+ const row = rows[index];
777
+ return row != null ? String(getRowId(row)) : index;
778
+ },
779
+ ...isDynamicRowHeight ? { measureElement: measureElementFn } : {}
691
780
  });
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
+ (0, react.useLayoutEffect)(() => {
842
+ const gridEl = gridRef.current;
843
+ const stickyEl = stickyChromeRef.current;
844
+ const bodyEl = scrollContainerRef.current;
845
+ const clipEl = rowsClipRef.current;
846
+ 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
+ const updateClip = () => {
851
+ const stickyRect = stickyEl.getBoundingClientRect();
852
+ const clipRect = clipEl.getBoundingClientRect();
853
+ const overlap = Math.max(0, stickyRect.bottom - clipRect.top);
854
+ const clipValue = overlap > 0 ? `inset(${overlap}px 0 0 0)` : "";
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);
860
+ };
861
+ updateClip();
862
+ bodyEl.addEventListener("scroll", updateClip);
863
+ if (verticalScrollEl === window) window.addEventListener("scroll", updateClip, true);
864
+ else if (extraObservedScrollEl) extraObservedScrollEl.addEventListener("scroll", updateClip);
865
+ window.addEventListener("resize", updateClip);
866
+ const ro = new ResizeObserver(updateClip);
867
+ ro.observe(gridEl);
868
+ ro.observe(stickyEl);
869
+ ro.observe(bodyEl);
870
+ if (extraObservedScrollEl) ro.observe(extraObservedScrollEl);
871
+ return () => {
872
+ bodyEl.removeEventListener("scroll", updateClip);
873
+ if (verticalScrollEl === window) window.removeEventListener("scroll", updateClip, true);
874
+ else if (extraObservedScrollEl) extraObservedScrollEl.removeEventListener("scroll", updateClip);
875
+ window.removeEventListener("resize", updateClip);
876
+ ro.disconnect();
877
+ };
878
+ }, [fillHeight]);
692
879
  const handleBodyScroll = (0, react.useCallback)(() => {
693
880
  const body = scrollContainerRef.current;
694
881
  const header = headerScrollRef.current;
@@ -728,9 +915,10 @@ function DataGrid(props) {
728
915
  ]);
729
916
  const allSelected = rowIds.length > 0 && rowIds.every((id) => state.selection.selectedIds.has(id));
730
917
  const someSelected = !allSelected && rowIds.some((id) => state.selection.selectedIds.has(id));
918
+ const infiniteScrollRootRef = paginationMode === "infinite" && (fillHeight || maxHeight != null) ? scrollContainerRef : void 0;
731
919
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
732
920
  ref: gridRef,
733
- className: (0, _stackframe_stack_ui.cn)("flex flex-col h-full min-h-0 bg-transparent", className),
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),
734
922
  style: maxHeight != null ? {
735
923
  ...gridSizingStyle,
736
924
  maxHeight
@@ -739,23 +927,24 @@ function DataGrid(props) {
739
927
  "aria-rowcount": totalRowCount ?? rows.length,
740
928
  "aria-colcount": visibleColumns.length,
741
929
  children: [
742
- toolbar !== false && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
743
- className: "relative shrink-0 bg-transparent",
744
- children: toolbar ? toolbar(toolbarCtx) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__data_grid_toolbar_js.DataGridToolbar, {
745
- ctx: toolbarCtx,
746
- extra: typeof toolbarExtra === "function" ? toolbarExtra(toolbarCtx) : toolbarExtra
747
- })
748
- }),
749
930
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
750
- className: "relative flex min-h-0 flex-1 flex-col",
751
- children: [
752
- isRefetching && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
931
+ ref: stickyChromeRef,
932
+ className: "sticky z-20 w-full min-w-0 shrink-0 rounded-t-[calc(var(--radius)*2)] bg-background",
933
+ style: { top: stickyTop ?? "var(--data-grid-sticky-top, 0px)" },
934
+ children: [toolbar !== false && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
935
+ className: "relative bg-transparent",
936
+ children: toolbar ? toolbar(toolbarCtx) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__data_grid_toolbar_js.DataGridToolbar, {
937
+ ctx: toolbarCtx,
938
+ extra: typeof toolbarExtra === "function" ? toolbarExtra(toolbarCtx) : toolbarExtra
939
+ })
940
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
941
+ className: "relative",
942
+ children: [isRefetching && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
753
943
  className: "absolute top-0 left-0 right-0 h-0.5 z-30 bg-foreground/[0.04] overflow-hidden",
754
944
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "h-full w-1/3 bg-blue-500/60 rounded-full animate-pulse" })
755
- }),
756
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
945
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
757
946
  ref: headerScrollRef,
758
- className: "overflow-hidden shrink-0 border-b border-foreground/[0.06]",
947
+ className: "w-full min-w-0 shrink-0 overflow-hidden border-b border-foreground/[0.06]",
759
948
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
760
949
  className: "flex",
761
950
  style: {
@@ -782,77 +971,89 @@ function DataGrid(props) {
782
971
  onResizeEnd: handleResizeEnd
783
972
  }, col.id))]
784
973
  })
785
- }),
786
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
787
- ref: scrollContainerRef,
788
- className: (0, _stackframe_stack_ui.cn)("min-h-0 overflow-auto flex-1 bg-transparent", "[&::-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]"),
789
- onScroll: handleBodyScroll,
790
- children: [
791
- isLoading && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
792
- style: { minWidth: visibleColumnMetrics.totalWidth },
793
- children: loadingState ?? Array.from({ length: 8 }).map((_, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SkeletonRow, {
794
- columns: visibleColumns,
795
- height: rowHeight,
796
- showCheckbox: selectionMode !== "none"
797
- }, i))
798
- }),
799
- !isLoading && rows.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
800
- className: "flex items-center justify-center py-16 text-sm text-muted-foreground",
801
- style: { minWidth: visibleColumnMetrics.totalWidth },
802
- children: emptyState ?? strings.noData
803
- }),
804
- !isLoading && rows.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
805
- style: {
806
- height: rowVirtualizer.getTotalSize(),
807
- width: "100%",
808
- minWidth: visibleColumnMetrics.totalWidth,
809
- position: "relative"
810
- },
811
- children: rowVirtualizer.getVirtualItems().map((virtualRow) => {
812
- const row = rows[virtualRow.index];
813
- const rowId = getRowId(row);
814
- const isSelected = state.selection.selectedIds.has(rowId);
815
- const isOddRow = virtualRow.index % 2 === 1;
816
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
817
- className: (0, _stackframe_stack_ui.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" && "cursor-pointer"),
818
- style: {
819
- height: rowHeight,
820
- transform: `translateY(${virtualRow.start}px)`
821
- },
822
- onClick: (e) => handleRowClick(row, rowId, e),
823
- onDoubleClick: (e) => onRowDoubleClick?.(row, rowId, e),
824
- role: "row",
825
- "aria-rowindex": virtualRow.index + 2,
826
- "aria-selected": isSelected,
827
- "data-row-id": rowId,
828
- "data-state": isSelected ? "selected" : void 0,
829
- children: [selectionMode !== "none" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
830
- className: "flex items-center justify-center border-r border-black/[0.04] dark:border-white/[0.04]",
831
- style: { width: 44 },
832
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SelectionCheckbox, {
833
- checked: isSelected,
834
- onChange: (event) => handleRowSelectionCheckboxClick(row, rowId, event),
835
- ariaLabel: `Select row ${rowId}`
836
- })
837
- }), visibleColumns.map((col) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DataCell, {
838
- col,
839
- row,
840
- rowId,
841
- rowIndex: virtualRow.index,
842
- isSelected,
843
- dateDisplay: state.dateDisplay
844
- }, col.id))]
845
- }, rowId);
846
- })
847
- }),
848
- paginationMode === "infinite" && hasMore && !isLoading && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InfiniteScrollSentinel, {
849
- onIntersect: onLoadMore ?? (() => {}),
850
- isLoading: isLoadingMore,
851
- strings
974
+ })]
975
+ })]
976
+ }),
977
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
978
+ ref: scrollContainerRef,
979
+ className: (0, _stackframe_stack_ui.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]"),
980
+ onScroll: handleBodyScroll,
981
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
982
+ ref: rowsClipRef,
983
+ children: [
984
+ isLoading && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
985
+ style: { minWidth: visibleColumnMetrics.totalWidth },
986
+ children: loadingState ?? Array.from({ length: 8 }).map((_, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SkeletonRow, {
987
+ columns: visibleColumns,
988
+ height: estimatedRowHeight,
989
+ showCheckbox: selectionMode !== "none"
990
+ }, i))
991
+ }),
992
+ !isLoading && rows.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
993
+ className: "flex items-center justify-center py-16 text-sm text-muted-foreground",
994
+ style: { minWidth: visibleColumnMetrics.totalWidth },
995
+ children: emptyState ?? strings.noData
996
+ }),
997
+ !isLoading && rows.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
998
+ style: {
999
+ height: rowVirtualizer.getTotalSize(),
1000
+ width: "100%",
1001
+ minWidth: visibleColumnMetrics.totalWidth,
1002
+ position: "relative"
1003
+ },
1004
+ children: rowVirtualizer.getVirtualItems().map((virtualRow) => {
1005
+ const row = rows[virtualRow.index] ?? (0, _stackframe_stack_shared_dist_utils_errors.throwErr)(`DataGrid: virtualized row index ${virtualRow.index} out of range (rows.length=${rows.length})`);
1006
+ const rowId = getRowId(row);
1007
+ const isSelected = state.selection.selectedIds.has(rowId);
1008
+ const isOddRow = virtualRow.index % 2 === 1;
1009
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1010
+ ref: isDynamicRowHeight ? rowVirtualizer.measureElement : void 0,
1011
+ "data-index": virtualRow.index,
1012
+ className: (0, _stackframe_stack_ui.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"),
1013
+ style: {
1014
+ ...isDynamicRowHeight ? { minHeight: estimatedRowHeight } : { height: fixedRowHeight },
1015
+ transform: `translateY(${virtualRow.start}px)`
1016
+ },
1017
+ onClick: (e) => {
1018
+ if (shouldIgnoreRowClick(e)) return;
1019
+ handleRowClick(row, rowId, e);
1020
+ },
1021
+ onDoubleClick: (e) => {
1022
+ if (shouldIgnoreRowClick(e)) return;
1023
+ onRowDoubleClick?.(row, rowId, e);
1024
+ },
1025
+ role: "row",
1026
+ "aria-rowindex": virtualRow.index + 2,
1027
+ "aria-selected": isSelected,
1028
+ "data-row-id": rowId,
1029
+ "data-state": isSelected ? "selected" : void 0,
1030
+ children: [selectionMode !== "none" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1031
+ className: "flex items-center justify-center border-r border-black/[0.04] dark:border-white/[0.04]",
1032
+ style: { width: 44 },
1033
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SelectionCheckbox, {
1034
+ checked: isSelected,
1035
+ onChange: (event) => handleRowSelectionCheckboxClick(row, rowId, event),
1036
+ ariaLabel: `Select row ${rowId}`
1037
+ })
1038
+ }), visibleColumns.map((col) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DataCell, {
1039
+ col,
1040
+ row,
1041
+ rowId,
1042
+ rowIndex: virtualRow.index,
1043
+ isSelected,
1044
+ dateDisplay: state.dateDisplay
1045
+ }, col.id))]
1046
+ }, rowId);
852
1047
  })
853
- ]
854
- })
855
- ]
1048
+ }),
1049
+ paginationMode === "infinite" && hasMore && !isLoading && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InfiniteScrollSentinel, {
1050
+ onIntersect: onLoadMore ?? NOOP,
1051
+ isLoading: isLoadingMore,
1052
+ rootRef: infiniteScrollRootRef,
1053
+ strings
1054
+ })
1055
+ ]
1056
+ })
856
1057
  }),
857
1058
  footer !== false && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
858
1059
  className: "relative z-10 shrink-0 bg-transparent",
@@ -868,4 +1069,5 @@ function DataGrid(props) {
868
1069
 
869
1070
  //#endregion
870
1071
  exports.DataGrid = DataGrid;
1072
+ exports.isDataGridInteractiveRowClickTarget = isDataGridInteractiveRowClickTarget;
871
1073
  //# sourceMappingURL=data-grid.js.map