cursedbelt 5.30.0 → 5.33.0
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/react/Toast.d.ts +1 -1
- package/dist/react/Toast.d.ts.map +1 -1
- package/dist/react/Toast.js +19 -4
- package/dist/react/Toast.js.map +1 -1
- package/dist/react/components/Button.d.ts +16 -1
- package/dist/react/components/Button.d.ts.map +1 -1
- package/dist/react/components/Button.js +34 -5
- package/dist/react/components/Button.js.map +1 -1
- package/dist/react/components/ChoiceGroup.d.ts +20 -1
- package/dist/react/components/ChoiceGroup.d.ts.map +1 -1
- package/dist/react/components/ChoiceGroup.js +15 -2
- package/dist/react/components/ChoiceGroup.js.map +1 -1
- package/dist/react/components/ConfirmDialog.d.ts +4 -1
- package/dist/react/components/ConfirmDialog.d.ts.map +1 -1
- package/dist/react/components/ConfirmDialog.js +2 -2
- package/dist/react/components/ConfirmDialog.js.map +1 -1
- package/dist/react/components/CopyButton.d.ts +10 -1
- package/dist/react/components/CopyButton.d.ts.map +1 -1
- package/dist/react/components/CopyButton.js +2 -2
- package/dist/react/components/CopyButton.js.map +1 -1
- package/dist/react/components/DataTable/DataTable.d.ts +27 -2
- package/dist/react/components/DataTable/DataTable.d.ts.map +1 -1
- package/dist/react/components/DataTable/DataTable.js +173 -45
- package/dist/react/components/DataTable/DataTable.js.map +1 -1
- package/dist/react/components/DataTable/RowActions.d.ts +4 -1
- package/dist/react/components/DataTable/RowActions.d.ts.map +1 -1
- package/dist/react/components/DataTable/RowActions.js +2 -2
- package/dist/react/components/DataTable/RowActions.js.map +1 -1
- package/dist/react/components/DataTable/columnPriority.d.ts +30 -0
- package/dist/react/components/DataTable/columnPriority.d.ts.map +1 -0
- package/dist/react/components/DataTable/columnPriority.js +45 -0
- package/dist/react/components/DataTable/columnPriority.js.map +1 -0
- package/dist/react/components/DataTable/types.d.ts +14 -0
- package/dist/react/components/DataTable/types.d.ts.map +1 -1
- package/dist/react/components/DataTable/types.js.map +1 -1
- package/dist/react/components/EmptyState.d.ts.map +1 -1
- package/dist/react/components/EmptyState.js +2 -2
- package/dist/react/components/EmptyState.js.map +1 -1
- package/dist/react/components/ErrorCard.d.ts +13 -1
- package/dist/react/components/ErrorCard.d.ts.map +1 -1
- package/dist/react/components/ErrorCard.js +3 -2
- package/dist/react/components/ErrorCard.js.map +1 -1
- package/dist/react/components/Field.d.ts +37 -12
- package/dist/react/components/Field.d.ts.map +1 -1
- package/dist/react/components/Field.js +19 -8
- package/dist/react/components/Field.js.map +1 -1
- package/dist/react/components/Modal.d.ts +1 -0
- package/dist/react/components/Modal.d.ts.map +1 -1
- package/dist/react/components/Modal.js +3 -2
- package/dist/react/components/Modal.js.map +1 -1
- package/dist/react/components/PromptDialog.d.ts +4 -1
- package/dist/react/components/PromptDialog.d.ts.map +1 -1
- package/dist/react/components/PromptDialog.js +2 -2
- package/dist/react/components/PromptDialog.js.map +1 -1
- package/dist/react/components/Switch.d.ts +16 -2
- package/dist/react/components/Switch.d.ts.map +1 -1
- package/dist/react/components/Switch.js +10 -3
- package/dist/react/components/Switch.js.map +1 -1
- package/dist/react/components/Tooltip.d.ts +1 -1
- package/dist/react/components/Tooltip.d.ts.map +1 -1
- package/dist/react/components/Tooltip.js +14 -1
- package/dist/react/components/Tooltip.js.map +1 -1
- package/dist/react/components/TooltipImpl.d.ts.map +1 -1
- package/dist/react/components/TooltipImpl.js +37 -7
- package/dist/react/components/TooltipImpl.js.map +1 -1
- package/dist/react/components/dialog.d.ts.map +1 -1
- package/dist/react/components/dialog.js +35 -3
- package/dist/react/components/dialog.js.map +1 -1
- package/dist/react/file-tree/FileTree.d.ts +26 -1
- package/dist/react/file-tree/FileTree.d.ts.map +1 -1
- package/dist/react/file-tree/FileTree.js +182 -40
- package/dist/react/file-tree/FileTree.js.map +1 -1
- package/dist/react/hooks/useCopyToClipboard.d.ts +10 -2
- package/dist/react/hooks/useCopyToClipboard.d.ts.map +1 -1
- package/dist/react/hooks/useCopyToClipboard.js +13 -4
- package/dist/react/hooks/useCopyToClipboard.js.map +1 -1
- package/dist/react/icons.d.ts +12 -1
- package/dist/react/icons.d.ts.map +1 -1
- package/dist/react/icons.js +12 -22
- package/dist/react/icons.js.map +1 -1
- package/dist/react/index.d.ts +4 -4
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +3 -3
- package/dist/react/index.js.map +1 -1
- package/dist/react/lib/hoverIntent.d.ts +21 -0
- package/dist/react/lib/hoverIntent.d.ts.map +1 -1
- package/dist/react/lib/hoverIntent.js +24 -0
- package/dist/react/lib/hoverIntent.js.map +1 -1
- package/dist/react/lib/openDialogs.d.ts +5 -0
- package/dist/react/lib/openDialogs.d.ts.map +1 -0
- package/dist/react/lib/openDialogs.js +45 -0
- package/dist/react/lib/openDialogs.js.map +1 -0
- package/dist/scripts/guardrailsEnforce.d.ts +11 -0
- package/dist/scripts/guardrailsEnforce.d.ts.map +1 -1
- package/dist/styles-areas/analytics.css +10 -9
- package/dist/styles-areas/calendar.css +6 -6
- package/dist/styles-areas/chat.css +6 -6
- package/dist/styles-areas/core.css +41 -36
- package/dist/styles-areas/data-table.css +10 -10
- package/dist/styles-areas/disk-usage.css +7 -7
- package/dist/styles-areas/fields.css +4 -4
- package/dist/styles-areas/filter-rail.css +7 -8
- package/dist/styles-areas/media-gallery.css +2 -2
- package/dist/styles-areas/nav.css +17 -18
- package/dist/styles-areas/overlays.css +2 -2
- package/dist/styles-areas/stats.css +4 -4
- package/dist/styles-areas/wizard.css +3 -3
- package/dist/styles-areas/workbench.css +2 -2
- package/dist/styles-utilities.css +46 -0
- package/dist/testing/index.d.ts +3 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +4 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/testing/objectLeak.d.ts +23 -0
- package/dist/testing/objectLeak.d.ts.map +1 -0
- package/dist/testing/objectLeak.js +37 -0
- package/dist/testing/objectLeak.js.map +1 -0
- package/dist/testing/tooltip.d.ts.map +1 -1
- package/dist/testing/tooltip.js +8 -18
- package/dist/testing/tooltip.js.map +1 -1
- package/package.json +61 -7
- package/scripts/guardrails-baseline.json +4 -1
- package/scripts/guardrailsEnforce.spec.ts +163 -6
- package/scripts/guardrailsEnforce.ts +365 -40
- package/scripts/paths.ts +1 -1
- package/src/react/Toast.spec.tsx +17 -1
- package/src/react/Toast.tsx +20 -3
- package/src/react/components/Button.spec.tsx +49 -1
- package/src/react/components/Button.tsx +63 -6
- package/src/react/components/ChoiceGroup.spec.tsx +32 -0
- package/src/react/components/ChoiceGroup.tsx +78 -23
- package/src/react/components/ConfirmDialog.tsx +5 -0
- package/src/react/components/CopyButton.spec.tsx +35 -0
- package/src/react/components/CopyButton.tsx +11 -2
- package/src/react/components/DataTable/DataTable.spec.tsx +32 -0
- package/src/react/components/DataTable/DataTable.tsx +271 -38
- package/src/react/components/DataTable/RowActions.tsx +5 -2
- package/src/react/components/DataTable/columnPriority.spec.ts +97 -0
- package/src/react/components/DataTable/columnPriority.ts +54 -0
- package/src/react/components/DataTable/types.ts +14 -0
- package/src/react/components/DataTable/virtualize.spec.tsx +47 -2
- package/src/react/components/EmptyState.spec.tsx +7 -0
- package/src/react/components/EmptyState.tsx +3 -0
- package/src/react/components/ErrorCard.spec.tsx +32 -0
- package/src/react/components/ErrorCard.tsx +17 -1
- package/src/react/components/Field.spec.tsx +61 -0
- package/src/react/components/Field.tsx +84 -27
- package/src/react/components/Modal.spec.tsx +84 -0
- package/src/react/components/Modal.tsx +4 -1
- package/src/react/components/PromptDialog.spec.tsx +29 -1
- package/src/react/components/PromptDialog.tsx +5 -0
- package/src/react/components/Switch.spec.tsx +25 -0
- package/src/react/components/Switch.tsx +49 -4
- package/src/react/components/Tooltip.spec.tsx +183 -2
- package/src/react/components/Tooltip.tsx +14 -2
- package/src/react/components/TooltipImpl.tsx +42 -8
- package/src/react/components/dialog.tsx +49 -9
- package/src/react/file-tree/FileTree.spec.tsx +205 -0
- package/src/react/file-tree/FileTree.tsx +220 -38
- package/src/react/hooks/useCopyToClipboard.ts +17 -4
- package/src/react/icons.spec.ts +32 -1
- package/src/react/icons.tsx +59 -0
- package/src/react/index.ts +7 -2
- package/src/react/lib/hoverIntent.ts +28 -0
- package/src/react/lib/openDialogs.ts +48 -0
- package/src/styles-areas/analytics.css +10 -9
- package/src/styles-areas/calendar.css +6 -6
- package/src/styles-areas/chat.css +6 -6
- package/src/styles-areas/core.css +41 -36
- package/src/styles-areas/data-table.css +10 -10
- package/src/styles-areas/disk-usage.css +7 -7
- package/src/styles-areas/fields.css +4 -4
- package/src/styles-areas/filter-rail.css +7 -8
- package/src/styles-areas/media-gallery.css +2 -2
- package/src/styles-areas/nav.css +17 -18
- package/src/styles-areas/overlays.css +2 -2
- package/src/styles-areas/stats.css +4 -4
- package/src/styles-areas/wizard.css +3 -3
- package/src/styles-areas/workbench.css +2 -2
- package/src/styles-utilities.css +46 -0
- package/src/testing/index.ts +3 -0
- package/src/testing/objectLeak.spec.ts +21 -0
- package/src/testing/objectLeak.ts +39 -0
- package/src/testing/tooltip.spec.ts +19 -0
- package/src/testing/tooltip.ts +17 -1
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { getCoreRowModel, getFilteredRowModel, getPaginationRowModel, getSortedRowModel, useReactTable, } from '@tanstack/react-table';
|
|
3
|
-
import { useVirtualizer } from '@tanstack/react-virtual';
|
|
4
|
-
import { Fragment, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react';
|
|
3
|
+
import { useVirtualizer, useWindowVirtualizer } from '@tanstack/react-virtual';
|
|
4
|
+
import { Fragment, useCallback, useEffect, useImperativeHandle, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
|
5
|
+
import { useResizeObserver } from '../../hooks/useResizeObserver.js';
|
|
5
6
|
import { useScrollEdgeFade } from '../../hooks/useScrollEdgeFade.js';
|
|
6
7
|
import { useRememberedPreference } from '../../media-gallery/rememberedPreference.js';
|
|
7
8
|
import { cn } from '../../lib/cn.js';
|
|
@@ -19,6 +20,7 @@ import { preloadRowMenu, RowActions } from './RowActions.js';
|
|
|
19
20
|
import { writeRowDrag } from './rowDrag.js';
|
|
20
21
|
import { SortHeader } from './SortHeader.js';
|
|
21
22
|
import { resolveColumnAlign, SELECT_COLUMN_ID } from './types.js';
|
|
23
|
+
import { AUTO_TRACK_FLOOR_PX, columnFloor, columnsDroppedForWidth } from './columnPriority.js';
|
|
22
24
|
import { lazyPart } from './lazyPart.js';
|
|
23
25
|
// 🔴 The toolbar and the pager load only when a table DRAWS them (forge task 2309).
|
|
24
26
|
// Between them they reach Radix's Select, Menu and Popover — ~20 kB gzip that a
|
|
@@ -105,7 +107,7 @@ function chainUpdaterResettingPage(set, setPagination, notify) {
|
|
|
105
107
|
* zero — this is the share below which the division stops and the table
|
|
106
108
|
* overflows its wrapper instead.
|
|
107
109
|
*/
|
|
108
|
-
export
|
|
110
|
+
export { AUTO_TRACK_FLOOR_PX };
|
|
109
111
|
/** The trailing row-actions column is a fixed `w-20`. */
|
|
110
112
|
const ACTIONS_TRACK_PX = 80;
|
|
111
113
|
/** The leading row-selection column is a fixed `w-11` (44px — a full tap target). */
|
|
@@ -150,13 +152,7 @@ const SELECT_TRACK_PX = 44;
|
|
|
150
152
|
* spec here can see it.
|
|
151
153
|
*/
|
|
152
154
|
export function declaredTrackFloor(columns, hasActions = false) {
|
|
153
|
-
const columnsTotal = columns.reduce((sum, col) =>
|
|
154
|
-
if (col.minWidth != null)
|
|
155
|
-
return sum + col.minWidth;
|
|
156
|
-
if (typeof col.width === 'number')
|
|
157
|
-
return sum + col.width;
|
|
158
|
-
return sum + AUTO_TRACK_FLOOR_PX;
|
|
159
|
-
}, 0);
|
|
155
|
+
const columnsTotal = columns.reduce((sum, col) => sum + columnFloor(col), 0);
|
|
160
156
|
return columnsTotal + (hasActions ? ACTIONS_TRACK_PX : 0);
|
|
161
157
|
}
|
|
162
158
|
/**
|
|
@@ -227,6 +223,9 @@ const HIDE_BELOW_CLASS = {
|
|
|
227
223
|
md: '@max-4xl:w-0! @max-4xl:min-w-0! @max-4xl:p-0! @max-4xl:border-0 @max-4xl:[&>*]:hidden',
|
|
228
224
|
lg: '@max-6xl:w-0! @max-6xl:min-w-0! @max-6xl:p-0! @max-6xl:border-0 @max-6xl:[&>*]:hidden',
|
|
229
225
|
};
|
|
226
|
+
/** `ColDef.priority`'s collapse — {@link HIDE_BELOW_CLASS}'s, unconditional, for the same reason. */
|
|
227
|
+
const COLLAPSED_CLASS = 'w-0! min-w-0! p-0! border-0 [&>*]:hidden';
|
|
228
|
+
const NOTHING_DROPPED = new Set();
|
|
230
229
|
/**
|
|
231
230
|
* The table's own props for a row, with the caller's `getRowProps` merged on top: classes join,
|
|
232
231
|
* styles spread, and a handler both set runs the table's first, then the caller's.
|
|
@@ -273,7 +272,7 @@ function isEmptyCellValue(v) {
|
|
|
273
272
|
* full-width accordion detail row that never breaks column alignment, and an automatic
|
|
274
273
|
* stacked-card layout under `sm` so phones are never handed a clipped grid.
|
|
275
274
|
*/
|
|
276
|
-
export function DataTable({ columns, data, getRowId, onRowClick, onRowEdit, onRowDelete, rowMenu, loading, emptyState, toolbar, pageSize = 50, pageSizeOptions, hidePagination, caption, className, stickyHeader, stickyFirstColumn, renderExpanded, expandedRowId, rowClassName, hideEmptyColumns, fixedLayout, minTableWidth, scrollBody, virtualize, serverWindow, handleRef, scrollClassName, mobileCards = true, serverSide, pageCount, rowCount, onSortingChange, onColumnFiltersChange, onGlobalFilterChange, onPaginationChange, defaultSort, sorting: sortingProp, enableSortingRemoval = true, sortDisabledReason, density = 'comfortable', viewMode, onViewModeChange, cardMinWidth, searchable = true, searchPlaceholder, toolbarOptions, multiSort = true, filterQuery: filterQueryProp, onFilterQueryChange, getRowDrag, dragSourceId, selectedIds, onSelectionChange, isRowSelectable, unselectableReason, selectionLabel, selectionSummary, selectAll, getRowProps, columnVisibility: columnVisibilityProp, onColumnVisibilityChange, columnsPersistKey, }) {
|
|
275
|
+
export function DataTable({ columns, data, getRowId, onRowClick, onRowEdit, onRowDelete, rowMenu, rowMenuLabel, loading, emptyState, toolbar, pageSize = 50, pageSizeOptions, hidePagination, caption, className, stickyHeader, stickyFirstColumn, renderExpanded, expandedRowId, rowClassName, hideEmptyColumns, fixedLayout, minTableWidth, scrollBody, virtualize, serverWindow, handleRef, scrollClassName, mobileCards = true, serverSide, pageCount, rowCount, onSortingChange, onColumnFiltersChange, onGlobalFilterChange, onPaginationChange, defaultSort, sorting: sortingProp, enableSortingRemoval = true, sortDisabledReason, density = 'comfortable', viewMode, onViewModeChange, cardMinWidth, searchable = true, searchPlaceholder, toolbarOptions, multiSort = true, filterQuery: filterQueryProp, onFilterQueryChange, getRowDrag, dragSourceId, selectedIds, onSelectionChange, isRowSelectable, unselectableReason, selectionLabel, selectionSummary, selectAll, getRowProps, columnVisibility: columnVisibilityProp, onColumnVisibilityChange, columnsPersistKey, }) {
|
|
277
276
|
// Vertical cell padding for the wide grid tier — the one lever the density knob
|
|
278
277
|
// pulls (headers + body cells stay aligned by sharing it).
|
|
279
278
|
const cellPadY = density === 'compact' ? 'py-1.5' : 'py-2.5';
|
|
@@ -469,7 +468,8 @@ export function DataTable({ columns, data, getRowId, onRowClick, onRowEdit, onRo
|
|
|
469
468
|
*/
|
|
470
469
|
const virtualOptions = virtualize === true || (serverWindow && !virtualize) ? {} : virtualize || null;
|
|
471
470
|
const virtualEnabled = virtualOptions !== null;
|
|
472
|
-
const
|
|
471
|
+
const windowScroll = virtualEnabled && virtualOptions?.scroll === 'window' && !serverWindow;
|
|
472
|
+
const bodyScrolls = scrollBody || (virtualEnabled && !windowScroll);
|
|
473
473
|
const paginationHidden = hidePagination || virtualEnabled;
|
|
474
474
|
const selectionEnabled = selectedIds !== undefined;
|
|
475
475
|
/** The checkbox column is ON and not hidden through the Columns menu. Hiding it hides the
|
|
@@ -532,7 +532,11 @@ export function DataTable({ columns, data, getRowId, onRowClick, onRowEdit, onRo
|
|
|
532
532
|
getFilteredRowModel: serverSide || serverWindow ? undefined : getFilteredRowModel(),
|
|
533
533
|
// 🔴 `virtualEnabled` too, not only `serverSide`. See the note where the flag is
|
|
534
534
|
// computed: a paginated virtualizer sizes its spacer from one PAGE.
|
|
535
|
-
|
|
535
|
+
// 🔴 And `hidePagination` (forge task 2396): hiding the pager used to leave the paging ON,
|
|
536
|
+
// so a table past `pageSize` rows (50) silently showed its first page with no control to
|
|
537
|
+
// reach the rest — binary-server's run history and station's metric tables among them.
|
|
538
|
+
// A table with no pager shows every row; that is what "no pager" means.
|
|
539
|
+
getPaginationRowModel: serverSide || paginationHidden ? undefined : getPaginationRowModel(),
|
|
536
540
|
// TanStack's built-in auto-reset fires `resetPageIndex()` on EVERY `data`
|
|
537
541
|
// identity change (see `getCoreRowModel`'s memo deps), not just on filter/sort
|
|
538
542
|
// changes — any live-polling table (this one included: rows are re-derived
|
|
@@ -580,6 +584,24 @@ export function DataTable({ columns, data, getRowId, onRowClick, onRowEdit, onRo
|
|
|
580
584
|
: null;
|
|
581
585
|
const estimateRowHeight = fixedRowHeight ?? virtualOptions?.estimateRowHeight ?? (density === 'compact' ? 36 : 44);
|
|
582
586
|
const scrollRef = useRef(null);
|
|
587
|
+
const outerRef = useRef(null);
|
|
588
|
+
/**
|
|
589
|
+
* 🔴 The width tier ON SCREEN, read in JS so a virtualized table windows the ONE tier the
|
|
590
|
+
* reader sees and mounts no rows at all in the two that `display: none` hides. Until forge
|
|
591
|
+
* task 2404 `virtualize` windowed only the grid, and the phone cards and the tablet list each
|
|
592
|
+
* mounted EVERY row at every width — 5,000 rows put ~10,000 extra nodes on a phone, with every
|
|
593
|
+
* virtualization gate green because each one passed `mobileCards={false}`. A table that does
|
|
594
|
+
* not virtualize still renders all three tiers and lets the container query pick, as before.
|
|
595
|
+
*/
|
|
596
|
+
const narrowTiers = virtualEnabled && mobileCards && viewMode !== 'card';
|
|
597
|
+
const [tier, setTier] = useState('grid');
|
|
598
|
+
const activeTier = narrowTiers ? tier : 'grid';
|
|
599
|
+
/** A narrow tier's row is a card or a two-line list row, nothing like the grid's height.
|
|
600
|
+
* Only a seed: every rendered row is measured. */
|
|
601
|
+
const rowEstimate = activeTier === 'cards' ? CARD_ESTIMATE_PX : activeTier === 'list' ? LIST_ESTIMATE_PX : estimateRowHeight;
|
|
602
|
+
/** A {@link serverWindow}'s fixed height is the GRID row's. A card cannot be held to it, so
|
|
603
|
+
* the narrow tiers measure — see {@link fixedRowHeight}. */
|
|
604
|
+
const fixedHeight = activeTier === 'grid' ? fixedRowHeight : null;
|
|
583
605
|
/** Rows before the loaded window, and the size of the whole result set — both 0/`rows.length`
|
|
584
606
|
* without a {@link serverWindow}, which makes every index below the same number as before. */
|
|
585
607
|
const windowStart = serverWindow ? Math.max(0, serverWindow.start) : 0;
|
|
@@ -599,10 +621,11 @@ export function DataTable({ columns, data, getRowId, onRowClick, onRowEdit, onRo
|
|
|
599
621
|
if (settleRef.current)
|
|
600
622
|
clearTimeout(settleRef.current);
|
|
601
623
|
}, []);
|
|
602
|
-
const
|
|
603
|
-
count: virtualEnabled ? windowTotal : 0,
|
|
624
|
+
const selfVirtualizer = useVirtualizer({
|
|
625
|
+
count: virtualEnabled && !windowScroll ? windowTotal : 0,
|
|
626
|
+
enabled: virtualEnabled && !windowScroll,
|
|
604
627
|
getScrollElement: () => scrollRef.current,
|
|
605
|
-
estimateSize: () =>
|
|
628
|
+
estimateSize: () => rowEstimate,
|
|
606
629
|
overscan: virtualOptions?.overscan ?? 8,
|
|
607
630
|
onChange: serverWindow
|
|
608
631
|
? (instance) => {
|
|
@@ -654,8 +677,47 @@ export function DataTable({ columns, data, getRowId, onRowClick, onRowEdit, onRo
|
|
|
654
677
|
// 🔴 MEASURED, not assumed. A wrapping cell, an expanded detail panel and the
|
|
655
678
|
// `compact` density all change a row's height, and a caller cannot be asked to
|
|
656
679
|
// compute any of them. `estimateSize` only seeds the spacer before first paint.
|
|
657
|
-
measureElement: (el) =>
|
|
680
|
+
measureElement: (el) => fixedHeight ?? el.getBoundingClientRect().height,
|
|
681
|
+
});
|
|
682
|
+
/**
|
|
683
|
+
* `virtualize.scroll: 'window'` — see the prop doc. Always called (hooks cannot be
|
|
684
|
+
* conditional) and `enabled: false` otherwise, so no other table on the page subscribes to
|
|
685
|
+
* the window's scroll. `scrollMargin` is the BODY's top in document coordinates: the
|
|
686
|
+
* virtualizer's offsets are measured from there, and the header, the toolbar and whatever the
|
|
687
|
+
* page draws above the table all sit inside that margin.
|
|
688
|
+
*/
|
|
689
|
+
/** The active tier's row container — the grid's `<tbody>`, or a narrow tier's list. */
|
|
690
|
+
const bodyRef = useRef(null);
|
|
691
|
+
const [scrollMargin, setScrollMargin] = useState(0);
|
|
692
|
+
const readScrollMargin = useCallback(() => {
|
|
693
|
+
const body = bodyRef.current;
|
|
694
|
+
if (!body)
|
|
695
|
+
return;
|
|
696
|
+
const top = Math.round(body.getBoundingClientRect().top + window.scrollY);
|
|
697
|
+
setScrollMargin((prev) => (prev === top ? prev : top));
|
|
698
|
+
}, []);
|
|
699
|
+
// Every render: anything above the table (a banner, a toolbar wrapping to two lines) moves
|
|
700
|
+
// the body without resizing it. A no-op when the number is unchanged.
|
|
701
|
+
useLayoutEffect(() => {
|
|
702
|
+
if (windowScroll)
|
|
703
|
+
readScrollMargin();
|
|
658
704
|
});
|
|
705
|
+
useEffect(() => {
|
|
706
|
+
if (!windowScroll)
|
|
707
|
+
return;
|
|
708
|
+
window.addEventListener('resize', readScrollMargin);
|
|
709
|
+
return () => window.removeEventListener('resize', readScrollMargin);
|
|
710
|
+
}, [readScrollMargin, windowScroll]);
|
|
711
|
+
const pageVirtualizer = useWindowVirtualizer({
|
|
712
|
+
count: windowScroll ? windowTotal : 0,
|
|
713
|
+
enabled: windowScroll,
|
|
714
|
+
estimateSize: () => rowEstimate,
|
|
715
|
+
overscan: virtualOptions?.overscan ?? 8,
|
|
716
|
+
scrollMargin,
|
|
717
|
+
measureElement: (el) => el.getBoundingClientRect().height,
|
|
718
|
+
});
|
|
719
|
+
// One interface from here on: the two differ only in what they measure against.
|
|
720
|
+
const rowVirtualizer = (windowScroll ? pageVirtualizer : selfVirtualizer);
|
|
659
721
|
/**
|
|
660
722
|
* 🔴 Invalidate the measurement cache when the row height changes.
|
|
661
723
|
*
|
|
@@ -672,11 +734,22 @@ export function DataTable({ columns, data, getRowId, onRowClick, onRowEdit, onRo
|
|
|
672
734
|
* exists so the third virtualizer does not pay for it a third time — and it is
|
|
673
735
|
* what caught this one.
|
|
674
736
|
*/
|
|
675
|
-
//
|
|
737
|
+
// The same holds for a TIER change: a card measured at 180px is a 56px list row a resize later.
|
|
738
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: `rowEstimate` and `activeTier` are the POINT of this effect, not values it reads — re-running when they change is what invalidates the cached offsets. Dropping them makes the measure fire once and never again, which is the bug.
|
|
676
739
|
useEffect(() => {
|
|
677
740
|
rowVirtualizer.measure();
|
|
678
|
-
}, [rowVirtualizer,
|
|
741
|
+
}, [rowVirtualizer, rowEstimate, activeTier]);
|
|
679
742
|
const virtualRows = virtualEnabled ? rowVirtualizer.getVirtualItems() : [];
|
|
743
|
+
// `virtualize.onNearEnd`: the last rendered row is within `nearEndCount` of the end of `data`.
|
|
744
|
+
const lastRendered = virtualRows.at(-1)?.index ?? -1;
|
|
745
|
+
const onNearEnd = virtualOptions?.onNearEnd;
|
|
746
|
+
const nearEndCount = virtualOptions?.nearEndCount ?? 8;
|
|
747
|
+
useEffect(() => {
|
|
748
|
+
if (!onNearEnd || lastRendered < 0)
|
|
749
|
+
return;
|
|
750
|
+
if (lastRendered >= windowStart + rows.length - nearEndCount)
|
|
751
|
+
onNearEnd();
|
|
752
|
+
}, [lastRendered, nearEndCount, onNearEnd, rows.length, windowStart]);
|
|
680
753
|
/** The rows to render, and the two spacers that keep the scrollbar honest about the
|
|
681
754
|
* ones that are not. Off, this is every row and no spacers — byte-identical output.
|
|
682
755
|
* `null` is a row a {@link serverWindow} has not loaded: a placeholder of the estimated
|
|
@@ -684,25 +757,36 @@ export function DataTable({ columns, data, getRowId, onRowClick, onRowEdit, onRo
|
|
|
684
757
|
const bodyRows = virtualEnabled
|
|
685
758
|
? virtualRows.map((v) => rows[v.index - windowStart] ?? null)
|
|
686
759
|
: rows;
|
|
760
|
+
/** What a tier renders: every row when nothing is virtualized; the window in the ONE tier on
|
|
761
|
+
* screen, and nothing in the others. `index` is the row's position over the whole result set. */
|
|
762
|
+
const tierRows = (t) => !virtualEnabled
|
|
763
|
+
? rows.map((row, index) => ({ row, index }))
|
|
764
|
+
: activeTier === t
|
|
765
|
+
? bodyRows.map((row, i) => ({ row, index: virtualRows[i].index }))
|
|
766
|
+
: [];
|
|
687
767
|
useImperativeHandle(handleRef, () => ({
|
|
688
768
|
get scrollElement() {
|
|
689
769
|
return scrollRef.current;
|
|
690
770
|
},
|
|
691
771
|
scrollToIndex(index, options = {}) {
|
|
692
|
-
const element = scrollRef.current;
|
|
772
|
+
const element = windowScroll ? window : scrollRef.current;
|
|
693
773
|
if (!element || !virtualEnabled)
|
|
694
774
|
return null;
|
|
695
775
|
const clamped = Math.max(0, Math.min(index, loadedRef.current.total - 1));
|
|
696
|
-
const [offset] = rowVirtualizer.getOffsetForIndex(clamped, options.align ?? 'center') ?? [clamped *
|
|
776
|
+
const [offset] = rowVirtualizer.getOffsetForIndex(clamped, options.align ?? 'center') ?? [clamped * rowEstimate];
|
|
697
777
|
const top = Math.max(0, Math.round(offset));
|
|
698
778
|
element.scrollTo({ top, behavior: options.behavior ?? 'auto' });
|
|
699
779
|
return top;
|
|
700
780
|
},
|
|
701
|
-
}), [
|
|
702
|
-
|
|
781
|
+
}), [rowEstimate, rowVirtualizer, virtualEnabled, windowScroll]);
|
|
782
|
+
// Item offsets include the `scrollMargin` (0 unless the window scrolls); the spacers do not.
|
|
783
|
+
const margin = windowScroll ? rowVirtualizer.options.scrollMargin : 0;
|
|
784
|
+
const padTop = virtualEnabled ? Math.max(0, (virtualRows[0]?.start ?? margin) - margin) : 0;
|
|
703
785
|
const padBottom = virtualEnabled
|
|
704
|
-
? rowVirtualizer.getTotalSize() - (virtualRows.at(-1)?.end ??
|
|
786
|
+
? Math.max(0, rowVirtualizer.getTotalSize() - ((virtualRows.at(-1)?.end ?? margin) - margin))
|
|
705
787
|
: 0;
|
|
788
|
+
/** The spacers belong to the tier on screen; the hidden ones hold no rows and no padding. */
|
|
789
|
+
const tierPad = (t) => virtualEnabled && activeTier === t ? { paddingTop: padTop, paddingBottom: padBottom } : undefined;
|
|
706
790
|
const hasActions = !!(onRowEdit || onRowDelete || rowMenu);
|
|
707
791
|
// ── Row selection (see the `selectedIds` prop doc) ──────────────────────────
|
|
708
792
|
const canSelect = (row) => isRowSelectable?.(row) ?? true;
|
|
@@ -813,19 +897,46 @@ export function DataTable({ columns, data, getRowId, onRowClick, onRowEdit, onRo
|
|
|
813
897
|
// say so at the cut edge, or a narrow pane reads as a table with its right-hand
|
|
814
898
|
// columns missing rather than one that scrolls (`useScrollEdgeFade`).
|
|
815
899
|
const gridFade = useScrollEdgeFade();
|
|
900
|
+
// `ColDef.priority`: collapse the columns that do not fit the table's MEASURED width. Only a
|
|
901
|
+
// table that declares a priority re-renders on resize for it.
|
|
902
|
+
const hasPriority = liveColumns.some((c) => c.priority != null);
|
|
903
|
+
const [measuredWidth, setMeasuredWidth] = useState(0);
|
|
904
|
+
useResizeObserver(outerRef, (el) => {
|
|
905
|
+
if (hasPriority)
|
|
906
|
+
setMeasuredWidth(el.clientWidth);
|
|
907
|
+
if (narrowTiers)
|
|
908
|
+
setTier(tierForWidth(el));
|
|
909
|
+
}, { layout: true });
|
|
910
|
+
// The observer only fires on a RESIZE: a table that starts virtualizing reads its tier now.
|
|
911
|
+
useLayoutEffect(() => {
|
|
912
|
+
if (narrowTiers && outerRef.current)
|
|
913
|
+
setTier(tierForWidth(outerRef.current));
|
|
914
|
+
}, [narrowTiers]);
|
|
915
|
+
const droppedByWidth = hasPriority
|
|
916
|
+
? columnsDroppedForWidth(cardColumns, measuredWidth, (hasActions ? ACTIONS_TRACK_PX : 0) + (selectionShown ? SELECT_TRACK_PX : 0))
|
|
917
|
+
: NOTHING_DROPPED;
|
|
816
918
|
const tableMinWidth = fixedLayout
|
|
817
|
-
? Math.max(minTableWidth ?? 0, declaredTrackFloor(cardColumns, hasActions) + (selectionShown ? SELECT_TRACK_PX : 0))
|
|
919
|
+
? Math.max(minTableWidth ?? 0, declaredTrackFloor(cardColumns.filter((c) => !droppedByWidth.has(c.key)), hasActions) + (selectionShown ? SELECT_TRACK_PX : 0))
|
|
818
920
|
: minTableWidth;
|
|
819
|
-
return (_jsxs("div", { className: cn('@container flex min-h-0 flex-col', className), children: [toolbarNode, summaryNode != null ? (_jsx("div", { "data-cb-table-selection-summary": '', "aria-live": 'polite', className: 'flex min-h-11 flex-wrap items-center gap-3 border-b border-border px-4 py-2 text-sm', children: summaryNode })) : null, loading ? (_jsx("div", { className: 'flex flex-col gap-2 p-4', children: Array.from({ length: 5 }).map((_, i) => (_jsx(Skeleton, { className: 'h-10 w-full' }, `dt-skeleton-${i}`))) })) : rows.length === 0 && windowTotal === 0 ? (_jsx("div", { className: 'p-2', children: emptyState ?? _jsx(EmptyState, { title: 'No results' }) })) : (_jsxs("div", { ref: scrollRef, "data-cb-table-scroll": bodyScrolls ? '' : undefined,
|
|
921
|
+
return (_jsxs("div", { ref: outerRef, className: cn('@container flex min-h-0 flex-col', className), children: [toolbarNode, summaryNode != null ? (_jsx("div", { "data-cb-table-selection-summary": '', "aria-live": 'polite', className: 'flex min-h-11 flex-wrap items-center gap-3 border-b border-border px-4 py-2 text-sm', children: summaryNode })) : null, loading ? (_jsx("div", { className: 'flex flex-col gap-2 p-4', children: Array.from({ length: 5 }).map((_, i) => (_jsx(Skeleton, { className: 'h-10 w-full' }, `dt-skeleton-${i}`))) })) : rows.length === 0 && windowTotal === 0 ? (_jsx("div", { className: 'p-2', children: emptyState ?? _jsx(EmptyState, { title: 'No results' }) })) : (_jsxs("div", { ref: scrollRef, "data-cb-table-scroll": bodyScrolls ? '' : undefined,
|
|
820
922
|
// guardrails-ignore no-raw-overflow: dual-axis table scroll container — sticky header pins to it; ScrollArea cannot own horizontal table scroll.
|
|
821
923
|
className: cn(bodyScrolls && 'min-h-0 flex-1 overflow-auto', scrollClassName), children: [selectionShown && (viewMode === 'card' || mobileCards) ? (_jsx("div", { "data-cb-table-select-all": '', className: cn('flex items-center px-2 py-1', viewMode !== 'card' && '@xl:hidden'), children: headerCheckbox('Select all') })) : null, viewMode === 'card' ? (_jsx(CardView, { columns: cardColumns, data: rows.map((row) => row.original), getRowId: getRowId, onRowClick: onRowClick, dragPropsFor: getRowDrag ? dragPropsFor : undefined, selectFor: selectionShown
|
|
822
924
|
? (_row, index) => {
|
|
823
925
|
const tsRow = rows[index];
|
|
824
926
|
return tsRow ? rowCheckbox(tsRow) : null;
|
|
825
927
|
}
|
|
826
|
-
: undefined, cardMinWidth: cardMinWidth, className: 'p-2' })) : (_jsxs(_Fragment, { children: [mobileCards && (_jsx("div", {
|
|
827
|
-
|
|
828
|
-
|
|
928
|
+
: undefined, cardMinWidth: cardMinWidth, className: 'p-2' })) : (_jsxs(_Fragment, { children: [mobileCards && (_jsx("div", { ref: activeTier === 'cards' && narrowTiers ? bodyRef : undefined, "data-cb-table-cards": '',
|
|
929
|
+
// Virtualized, each card sits in a measured wrapper that carries the gap as padding —
|
|
930
|
+
// a flex `gap` is outside every row, so the virtualizer would never count it.
|
|
931
|
+
className: cn('flex flex-col @xl:hidden', !virtualEnabled && 'gap-3'), style: tierPad('cards'), children: tierRows('cards').map(({ row, index }) => {
|
|
932
|
+
if (row === null) {
|
|
933
|
+
return (_jsx("div", { ref: rowVirtualizer.measureElement, "data-index": index, "data-cb-placeholder": '', "aria-hidden": true, className: 'pb-3', children: _jsx(Skeleton, { className: 'w-full rounded-lg', style: { height: rowEstimate - 12 } }) }, `cb-placeholder-${index}`));
|
|
934
|
+
}
|
|
935
|
+
const card = (_jsx(MobileRowCard, { row: row, columns: cardColumns, className: rowClassName?.(row.original), onRowClick: onRowClick, dragProps: mergeRowProps(dragPropsFor(row.original), getRowProps?.(row.original, windowStart + row.index)), select: selectionShown ? rowCheckbox(row) : undefined, actions: hasActions ? (_jsx(RowActions, { row: row.original, onEdit: onRowEdit, onDelete: onRowDelete, menu: rowMenu?.(row.original), menuLabel: rowMenuLabel?.(row.original) })) : null, expanded: renderExpanded && expandedRowId != null && row.id === expandedRowId
|
|
936
|
+
? renderExpanded(row.original)
|
|
937
|
+
: null }, row.id));
|
|
938
|
+
return virtualEnabled ? (_jsx("div", { ref: rowVirtualizer.measureElement, "data-index": index, className: 'pb-3', children: card }, row.id)) : (card);
|
|
939
|
+
}) })), mobileCards && (_jsx("div", { "data-cb-table-list": '', className: 'hidden @xl:block @3xl:hidden', children: (() => {
|
|
829
940
|
const [title, ...rest] = cardColumns;
|
|
830
941
|
// ONE shared grid template for every row — parent and child rows
|
|
831
942
|
// lock into identical column slots, so the status/ports/actions
|
|
@@ -851,18 +962,24 @@ export function DataTable({ columns, data, getRowId, onRowClick, onRowEdit, onRo
|
|
|
851
962
|
...(hasActions ? ['32px'] : []),
|
|
852
963
|
].join(' ');
|
|
853
964
|
const header = (_jsxs("div", { "data-cb-table-list-header": '', className: 'grid items-center gap-3 border-b border-border px-4 py-2 text-caption font-bold uppercase tracking-wider text-muted-foreground', style: { gridTemplateColumns: template }, children: [selectionShown ? _jsx("span", { className: 'flex justify-center', children: headerCheckbox() }) : null, _jsx("span", { children: title?.label ?? '' }), rest.map((col) => (_jsx("span", { className: cn(resolveColumnAlign(col) === 'right' && 'text-right'), children: col.label }, col.key))), hasActions ? _jsx("span", { "aria-hidden": true }) : null] }));
|
|
854
|
-
const body =
|
|
965
|
+
const body = tierRows('list').map(({ row, index }) => {
|
|
966
|
+
if (row === null) {
|
|
967
|
+
return (_jsx("div", { ref: rowVirtualizer.measureElement, "data-index": index, "data-cb-placeholder": '', "aria-hidden": true, className: 'flex items-center px-4', style: { height: rowEstimate }, children: _jsx(Skeleton, { className: 'h-3 w-40' }) }, `cb-placeholder-${index}`));
|
|
968
|
+
}
|
|
855
969
|
const isExpanded = renderExpanded != null && expandedRowId != null && row.id === expandedRowId;
|
|
856
|
-
|
|
970
|
+
const item = (_jsxs(_Fragment, { children: [_jsxs("div", { ...mergeRowProps({
|
|
857
971
|
...dragPropsFor(row.original),
|
|
858
972
|
className: cn('grid items-center gap-3 px-4 py-3 transition-colors hover:bg-muted/40', getRowDrag && 'cursor-grab active:cursor-grabbing', rowClassName?.(row.original)),
|
|
859
973
|
style: { gridTemplateColumns: template },
|
|
860
|
-
}, getRowProps?.(row.original, windowStart + row.index)), children: [selectionShown ? (_jsx("div", { ...selectCellProps(row), className: '-my-3 flex self-stretch items-center justify-center', children: rowCheckbox(row) })) : null, _jsx("div", { className: 'min-w-0', children: title ? (_jsx(CellRenderer, { col: title, value: row.original[title.key], row: row.original })) : null }), rest.map((col) => (_jsx("div", { className: cn(resolveColumnAlign(col) === 'right' && 'justify-self-end text-right'), children: _jsx(CellRenderer, { col: col, value: row.original[col.key], row: row.original }) }, col.key))), hasActions ? (_jsx(RowActions, { row: row.original, onEdit: onRowEdit, onDelete: onRowDelete, menu: rowMenu?.(row.original) })) : null] }), isExpanded ? (_jsx("div", { className: 'bg-muted/20 px-4 pt-1 pb-3', children: _jsx(Surface, { level: "raised", pad: "sm", radius: "lg", children: renderExpanded(row.original) }) })) : null] }
|
|
974
|
+
}, getRowProps?.(row.original, windowStart + row.index)), children: [selectionShown ? (_jsx("div", { ...selectCellProps(row), className: '-my-3 flex self-stretch items-center justify-center', children: rowCheckbox(row) })) : null, _jsx("div", { className: 'min-w-0', children: title ? (_jsx(CellRenderer, { col: title, value: row.original[title.key], row: row.original })) : null }), rest.map((col) => (_jsx("div", { className: cn(resolveColumnAlign(col) === 'right' && 'justify-self-end text-right'), children: _jsx(CellRenderer, { col: col, value: row.original[col.key], row: row.original }) }, col.key))), hasActions ? (_jsx(RowActions, { row: row.original, onEdit: onRowEdit, onDelete: onRowDelete, menu: rowMenu?.(row.original), menuLabel: rowMenuLabel?.(row.original) })) : null] }), isExpanded ? (_jsx("div", { className: 'bg-muted/20 px-4 pt-1 pb-3', children: _jsx(Surface, { level: "raised", pad: "sm", radius: "lg", children: renderExpanded(row.original) }) })) : null] }));
|
|
975
|
+
// Virtualized, the row AND its expansion are one measured node — the
|
|
976
|
+
// virtualizer's `data-index` names a row, not the first half of one.
|
|
977
|
+
return virtualEnabled ? (_jsx("div", { ref: rowVirtualizer.measureElement, "data-index": index, children: item }, row.id)) : (_jsx(Fragment, { children: item }, row.id));
|
|
861
978
|
});
|
|
862
|
-
return (_jsxs(_Fragment, { children: [header, _jsx("div", { className: 'divide-y divide-border', children: body })] }));
|
|
979
|
+
return (_jsxs(_Fragment, { children: [header, _jsx("div", { ref: activeTier === 'list' && narrowTiers ? bodyRef : undefined, className: 'divide-y divide-border', style: tierPad('list'), children: body })] }));
|
|
863
980
|
})() })), _jsx("div", { ref: bodyScrolls ? undefined : gridFade.ref, onScroll: bodyScrolls ? undefined : gridFade.onScroll, style: bodyScrolls ? undefined : gridFade.maskStyle,
|
|
864
981
|
// guardrails-ignore no-raw-overflow: content-sized horizontal table scroll, not a page-level vertical scroll region — see reasoning above. Under scrollBody the OUTER wrapper owns both axes (a nested overflow ancestor would un-stick the sticky header).
|
|
865
|
-
className: cn(!bodyScrolls && 'overflow-x-auto', mobileCards && 'hidden @3xl:block'), children: _jsxs("table", { className: cn('w-full border-collapse text-sm', fixedLayout && 'table-fixed'), style: tableMinWidth != null ? { minWidth: tableMinWidth } : undefined, children: [caption ? _jsx("caption", { className: 'sr-only', children: caption }) : null, _jsx("thead", { children: table.getHeaderGroups().map((hg) => (_jsxs("tr", { children: [(() => {
|
|
982
|
+
className: cn(!bodyScrolls && 'overflow-x-auto', mobileCards && 'hidden @3xl:block'), children: _jsxs("table", { "aria-rowcount": virtualEnabled ? (serverWindow ? windowTotal + 1 : onNearEnd ? -1 : rows.length + 1) : undefined, className: cn('w-full border-collapse text-sm', fixedLayout && 'table-fixed'), style: tableMinWidth != null ? { minWidth: tableMinWidth } : undefined, children: [caption ? _jsx("caption", { className: 'sr-only', children: caption }) : null, _jsx("thead", { children: table.getHeaderGroups().map((hg) => (_jsxs("tr", { "aria-rowindex": virtualEnabled ? 1 : undefined, children: [(() => {
|
|
866
983
|
// `ColDef.leading` columns sit BEFORE the checkbox column; see its doc.
|
|
867
984
|
const renderHeader = (h, headerIndex) => {
|
|
868
985
|
const sorted = h.column.getIsSorted();
|
|
@@ -893,13 +1010,13 @@ export function DataTable({ columns, data, getRowId, onRowClick, onRowEdit, onRo
|
|
|
893
1010
|
frozen &&
|
|
894
1011
|
(selectionShown
|
|
895
1012
|
? 'sticky left-11 bg-card shadow-[1px_0_0_0_var(--color-border)]'
|
|
896
|
-
: 'sticky left-0 bg-card shadow-[1px_0_0_0_var(--color-border)]'), frozen && (stickyHeader ? 'z-30' : 'z-20'), meta?.hideBelow && HIDE_BELOW_CLASS[meta.hideBelow]), children: h.isPlaceholder
|
|
1013
|
+
: 'sticky left-0 bg-card shadow-[1px_0_0_0_var(--color-border)]'), frozen && (stickyHeader ? 'z-30' : 'z-20'), meta?.hideBelow && HIDE_BELOW_CLASS[meta.hideBelow], droppedByWidth.has(h.column.id) && COLLAPSED_CLASS), children: h.isPlaceholder
|
|
897
1014
|
? null
|
|
898
1015
|
: renderTemplate(h.column.columnDef.header, h.getContext()) }, h.id));
|
|
899
1016
|
};
|
|
900
1017
|
const isLeading = (h) => h.column.columnDef.meta?.leading === true;
|
|
901
1018
|
return (_jsxs(_Fragment, { children: [hg.headers.filter(isLeading).map((h) => renderHeader(h, -1)), selectionShown ? (_jsx("th", { scope: 'col', className: cn('w-11 border-b border-border px-0 text-center', stickyHeader && 'sticky top-0 z-10 bg-card/95 backdrop-blur-sm', stickyFirstColumn && 'sticky left-0 bg-card', stickyFirstColumn && (stickyHeader ? 'z-30' : 'z-20')), children: headerCheckbox() })) : null, hg.headers.filter((h) => !isLeading(h)).map(renderHeader)] }));
|
|
902
|
-
})(), hasActions ? (_jsx("th", { className: cn('w-20 border-b border-border px-2', stickyHeader && 'sticky top-0 z-10 bg-card/95 backdrop-blur-sm'), "aria-label": 'Row actions' })) : null] }, hg.id))) }), _jsxs("tbody", { children: [padTop > 0 ? (_jsx("tr", { "aria-hidden": true, style: { height: padTop }, children: _jsx("td", { colSpan: visibleColumnCount }) })) : null, bodyRows.map((row, sliceIndex) => {
|
|
1019
|
+
})(), hasActions ? (_jsx("th", { className: cn('w-20 border-b border-border px-2', stickyHeader && 'sticky top-0 z-10 bg-card/95 backdrop-blur-sm'), "aria-label": 'Row actions' })) : null] }, hg.id))) }), _jsxs("tbody", { ref: activeTier === 'grid' ? bodyRef : undefined, children: [padTop > 0 && activeTier === 'grid' ? (_jsx("tr", { "aria-hidden": true, style: { height: padTop }, children: _jsx("td", { colSpan: visibleColumnCount }) })) : null, (activeTier === 'grid' ? bodyRows : []).map((row, sliceIndex) => {
|
|
903
1020
|
if (row === null) {
|
|
904
1021
|
// A row the server window has not loaded yet: the estimated height, so
|
|
905
1022
|
// the spacer arithmetic holds, and a skeleton line rather than a gap.
|
|
@@ -919,7 +1036,7 @@ export function DataTable({ columns, data, getRowId, onRowClick, onRowEdit, onRo
|
|
|
919
1036
|
// every measurement lands on index 0 and the spacer never grows.
|
|
920
1037
|
// (The ref stays OUT of the merge: `getRowProps` cannot take it.)
|
|
921
1038
|
ref: virtualEnabled && fixedRowHeight === null ? rowVirtualizer.measureElement : undefined, ...mergeRowProps({
|
|
922
|
-
...(virtualEnabled ? { 'data-index': rowIndex } : {}),
|
|
1039
|
+
...(virtualEnabled ? { 'data-index': rowIndex, 'aria-rowindex': rowIndex + 2 } : {}),
|
|
923
1040
|
style: fixedRowHeight === null ? undefined : { height: fixedRowHeight },
|
|
924
1041
|
'data-expanded': isExpanded || undefined,
|
|
925
1042
|
...dragPropsFor(row.original),
|
|
@@ -968,7 +1085,7 @@ export function DataTable({ columns, data, getRowId, onRowClick, onRowEdit, onRo
|
|
|
968
1085
|
// `overflow: hidden`: a cell may host a menu/tooltip that
|
|
969
1086
|
// has to escape its bounds.
|
|
970
1087
|
fixedLayout && '[overflow-wrap:anywhere]', cell.column.columnDef.meta?.align === 'right' && 'text-right', cell.column.columnDef.meta?.hideBelow &&
|
|
971
|
-
HIDE_BELOW_CLASS[cell.column.columnDef.meta.hideBelow]), children: renderTemplate(cell.column.columnDef.cell, cell.getContext()) }, cell.id));
|
|
1088
|
+
HIDE_BELOW_CLASS[cell.column.columnDef.meta.hideBelow], droppedByWidth.has(cell.column.id) && COLLAPSED_CLASS), children: renderTemplate(cell.column.columnDef.cell, cell.getContext()) }, cell.id));
|
|
972
1089
|
};
|
|
973
1090
|
return (_jsxs(_Fragment, { children: [cells.filter(isLeading).map((cell) => renderCell(cell, -1)), selectionShown ? (_jsx("td", { ...selectCellProps(row), style: stickyFirstColumn
|
|
974
1091
|
? {
|
|
@@ -977,22 +1094,33 @@ export function DataTable({ columns, data, getRowId, onRowClick, onRowEdit, onRo
|
|
|
977
1094
|
: 'var(--color-card)',
|
|
978
1095
|
}
|
|
979
1096
|
: undefined, className: cn('w-11 cursor-pointer px-0 text-center align-middle', stickyFirstColumn && 'sticky left-0 z-10 bg-[var(--cb-frozen-bg)]'), children: rowCheckbox(row) })) : null, cells.filter((cell) => !isLeading(cell)).map(renderCell)] }));
|
|
980
|
-
})(), hasActions ? (_jsx("td", { className: 'px-2 py-2', children: _jsx(RowActions, { row: row.original, onEdit: onRowEdit, onDelete: onRowDelete, menu: rowMenu?.(row.original) }) })) : null] }), isExpanded ? (_jsx("tr", { className: 'border-b border-border bg-muted/20', ref: (el) => {
|
|
1097
|
+
})(), hasActions ? (_jsx("td", { className: 'px-2 py-2', children: _jsx(RowActions, { row: row.original, onEdit: onRowEdit, onDelete: onRowDelete, menu: rowMenu?.(row.original), menuLabel: rowMenuLabel?.(row.original) }) })) : null] }), isExpanded ? (_jsx("tr", { className: 'border-b border-border bg-muted/20', ref: (el) => {
|
|
981
1098
|
if (el && scrolledExpansionRef.current !== row.id) {
|
|
982
1099
|
scrolledExpansionRef.current = row.id;
|
|
983
1100
|
// A row expanded near the fold must reveal its panel.
|
|
984
1101
|
el.scrollIntoView({ block: 'nearest' });
|
|
985
1102
|
}
|
|
986
1103
|
}, children: _jsx("td", { colSpan: visibleColumnCount, className: 'p-0', children: _jsx("div", { className: 'animate-in fade-in slide-in-from-top-1 px-4 pt-1 pb-3 duration-200', children: _jsx(Surface, { level: "raised", pad: "sm", radius: "lg", children: renderExpanded(row.original) }) }) }) })) : null] }, row.id));
|
|
987
|
-
}), padBottom > 0 ? (_jsx("tr", { "aria-hidden": true, style: { height: padBottom }, children: _jsx("td", { colSpan: visibleColumnCount }) })) : null] })] }) })] })), bodyScrolls && (_jsx("div", { "aria-hidden": true, className: 'pointer-events-none sticky bottom-0 -mt-6 h-6 w-full bg-gradient-to-t from-card to-transparent' }))] })), !loading && rows.length > 0 && !paginationHidden ? (_jsx(PaginationPart, { table: table, pageSizeOptions: pageSizeOptions, fallback: _jsx(PaginationPlaceholder, {}) })) : null] }));
|
|
1104
|
+
}), padBottom > 0 && activeTier === 'grid' ? (_jsx("tr", { "aria-hidden": true, style: { height: padBottom }, children: _jsx("td", { colSpan: visibleColumnCount }) })) : null] })] }) })] })), bodyScrolls && (_jsx("div", { "aria-hidden": true, className: 'pointer-events-none sticky bottom-0 -mt-6 h-6 w-full bg-gradient-to-t from-card to-transparent' }))] })), !loading && rows.length > 0 && !paginationHidden ? (_jsx(PaginationPart, { table: table, pageSizeOptions: pageSizeOptions, fallback: _jsx(PaginationPlaceholder, {}) })) : null] }));
|
|
988
1105
|
}
|
|
1106
|
+
/** Seeds for the narrow tiers' row heights; every rendered row is measured over them. */
|
|
1107
|
+
const CARD_ESTIMATE_PX = 160;
|
|
1108
|
+
const LIST_ESTIMATE_PX = 56;
|
|
989
1109
|
/**
|
|
990
|
-
*
|
|
991
|
-
*
|
|
992
|
-
*
|
|
993
|
-
* anchored bottom-right inside the card bounds. Values render through the SAME
|
|
994
|
-
* {@link CellRenderer} registry as the grid, so a custom cell shows identically here.
|
|
1110
|
+
* The tier `mobileCards`' container query shows at `el`'s width: its two steps are 36rem and
|
|
1111
|
+
* 48rem, both `min-width` against the container's CONTENT box — so padding comes off, and the
|
|
1112
|
+
* rem is the root's, read live, because an app that scales its root size scales the ladder too.
|
|
995
1113
|
*/
|
|
1114
|
+
function tierForWidth(el) {
|
|
1115
|
+
// Nothing laid out (a hidden ancestor, happy-dom): no tier is on screen, and the grid is the
|
|
1116
|
+
// one whose output a caller without layout can still inspect.
|
|
1117
|
+
if (el.clientWidth === 0)
|
|
1118
|
+
return 'grid';
|
|
1119
|
+
const style = getComputedStyle(el);
|
|
1120
|
+
const width = el.clientWidth - (Number.parseFloat(style.paddingLeft) || 0) - (Number.parseFloat(style.paddingRight) || 0);
|
|
1121
|
+
const rem = Number.parseFloat(getComputedStyle(document.documentElement).fontSize) || 16;
|
|
1122
|
+
return width < 36 * rem ? 'cards' : width < 48 * rem ? 'list' : 'grid';
|
|
1123
|
+
}
|
|
996
1124
|
function MobileRowCard({ row, columns, className, onRowClick, dragProps, select, actions, expanded, }) {
|
|
997
1125
|
const visible = columns.filter((c) => !c.hidden);
|
|
998
1126
|
const [title, ...rest] = visible;
|