cursedbelt 5.32.0 → 5.34.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/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 +168 -44
- 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/Menu.d.ts +58 -0
- package/dist/react/components/Menu.d.ts.map +1 -0
- package/dist/react/components/Menu.js +86 -0
- package/dist/react/components/Menu.js.map +1 -0
- 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/SaveIndicator.d.ts +36 -0
- package/dist/react/components/SaveIndicator.d.ts.map +1 -0
- package/dist/react/components/SaveIndicator.js +48 -0
- package/dist/react/components/SaveIndicator.js.map +1 -0
- package/dist/react/components/SelectionBar.d.ts +26 -0
- package/dist/react/components/SelectionBar.d.ts.map +1 -0
- package/dist/react/components/SelectionBar.js +21 -0
- package/dist/react/components/SelectionBar.js.map +1 -0
- 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/UnstyledButton.d.ts +37 -0
- package/dist/react/components/UnstyledButton.d.ts.map +1 -0
- package/dist/react/components/UnstyledButton.js +46 -0
- package/dist/react/components/UnstyledButton.js.map +1 -0
- package/dist/react/components/auth/SignInCard.d.ts +15 -1
- package/dist/react/components/auth/SignInCard.d.ts.map +1 -1
- package/dist/react/components/auth/SignInCard.js +6 -2
- package/dist/react/components/auth/SignInCard.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/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 +8 -4
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +7 -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/styles-areas/analytics.css +16 -16
- package/dist/styles-areas/calendar.css +7 -7
- package/dist/styles-areas/chat.css +9 -10
- package/dist/styles-areas/core.css +69 -62
- package/dist/styles-areas/data-table.css +16 -16
- package/dist/styles-areas/disk-usage.css +7 -7
- package/dist/styles-areas/emoji.css +3 -3
- package/dist/styles-areas/fields.css +10 -10
- package/dist/styles-areas/filter-rail.css +7 -8
- package/dist/styles-areas/layout-engine.css +5 -6
- package/dist/styles-areas/master-detail.css +6 -7
- package/dist/styles-areas/media-gallery.css +2 -2
- package/dist/styles-areas/media.css +2 -2
- package/dist/styles-areas/nav.css +17 -18
- package/dist/styles-areas/notifications.css +5 -5
- package/dist/styles-areas/overlays.css +5 -5
- package/dist/styles-areas/sharing.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 +18 -19
- package/dist/styles-utilities.css +72 -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 +81 -3
- package/scripts/guardrails-baseline.json +1 -1
- package/scripts/styleAreas.ts +4 -0
- 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/CopyButton.spec.tsx +35 -0
- package/src/react/components/CopyButton.tsx +11 -2
- package/src/react/components/DataTable/DataTable.spec.tsx +16 -0
- package/src/react/components/DataTable/DataTable.tsx +266 -37
- 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/Menu.spec.tsx +90 -0
- package/src/react/components/Menu.tsx +204 -0
- package/src/react/components/Modal.spec.tsx +84 -0
- package/src/react/components/Modal.tsx +4 -1
- package/src/react/components/SaveIndicator.spec.tsx +35 -0
- package/src/react/components/SaveIndicator.tsx +85 -0
- package/src/react/components/SelectionBar.spec.tsx +32 -0
- package/src/react/components/SelectionBar.tsx +49 -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/UnstyledButton.spec.tsx +61 -0
- package/src/react/components/UnstyledButton.tsx +83 -0
- package/src/react/components/auth/SignInCard.spec.tsx +34 -0
- package/src/react/components/auth/SignInCard.tsx +36 -3
- package/src/react/components/dialog.tsx +49 -9
- 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 +23 -2
- package/src/react/lib/hoverIntent.ts +28 -0
- package/src/react/lib/openDialogs.ts +48 -0
- package/src/styles-areas/analytics.css +16 -16
- package/src/styles-areas/calendar.css +7 -7
- package/src/styles-areas/chat.css +9 -10
- package/src/styles-areas/core.css +69 -62
- package/src/styles-areas/data-table.css +16 -16
- package/src/styles-areas/disk-usage.css +7 -7
- package/src/styles-areas/emoji.css +3 -3
- package/src/styles-areas/fields.css +10 -10
- package/src/styles-areas/filter-rail.css +7 -8
- package/src/styles-areas/layout-engine.css +5 -6
- package/src/styles-areas/master-detail.css +6 -7
- package/src/styles-areas/media-gallery.css +2 -2
- package/src/styles-areas/media.css +2 -2
- package/src/styles-areas/nav.css +17 -18
- package/src/styles-areas/notifications.css +5 -5
- package/src/styles-areas/overlays.css +5 -5
- package/src/styles-areas/sharing.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 +18 -19
- package/src/styles-utilities.css +72 -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
|
|
@@ -584,6 +584,24 @@ export function DataTable({ columns, data, getRowId, onRowClick, onRowEdit, onRo
|
|
|
584
584
|
: null;
|
|
585
585
|
const estimateRowHeight = fixedRowHeight ?? virtualOptions?.estimateRowHeight ?? (density === 'compact' ? 36 : 44);
|
|
586
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;
|
|
587
605
|
/** Rows before the loaded window, and the size of the whole result set — both 0/`rows.length`
|
|
588
606
|
* without a {@link serverWindow}, which makes every index below the same number as before. */
|
|
589
607
|
const windowStart = serverWindow ? Math.max(0, serverWindow.start) : 0;
|
|
@@ -603,10 +621,11 @@ export function DataTable({ columns, data, getRowId, onRowClick, onRowEdit, onRo
|
|
|
603
621
|
if (settleRef.current)
|
|
604
622
|
clearTimeout(settleRef.current);
|
|
605
623
|
}, []);
|
|
606
|
-
const
|
|
607
|
-
count: virtualEnabled ? windowTotal : 0,
|
|
624
|
+
const selfVirtualizer = useVirtualizer({
|
|
625
|
+
count: virtualEnabled && !windowScroll ? windowTotal : 0,
|
|
626
|
+
enabled: virtualEnabled && !windowScroll,
|
|
608
627
|
getScrollElement: () => scrollRef.current,
|
|
609
|
-
estimateSize: () =>
|
|
628
|
+
estimateSize: () => rowEstimate,
|
|
610
629
|
overscan: virtualOptions?.overscan ?? 8,
|
|
611
630
|
onChange: serverWindow
|
|
612
631
|
? (instance) => {
|
|
@@ -658,8 +677,47 @@ export function DataTable({ columns, data, getRowId, onRowClick, onRowEdit, onRo
|
|
|
658
677
|
// 🔴 MEASURED, not assumed. A wrapping cell, an expanded detail panel and the
|
|
659
678
|
// `compact` density all change a row's height, and a caller cannot be asked to
|
|
660
679
|
// compute any of them. `estimateSize` only seeds the spacer before first paint.
|
|
661
|
-
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();
|
|
662
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);
|
|
663
721
|
/**
|
|
664
722
|
* 🔴 Invalidate the measurement cache when the row height changes.
|
|
665
723
|
*
|
|
@@ -676,11 +734,22 @@ export function DataTable({ columns, data, getRowId, onRowClick, onRowEdit, onRo
|
|
|
676
734
|
* exists so the third virtualizer does not pay for it a third time — and it is
|
|
677
735
|
* what caught this one.
|
|
678
736
|
*/
|
|
679
|
-
//
|
|
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.
|
|
680
739
|
useEffect(() => {
|
|
681
740
|
rowVirtualizer.measure();
|
|
682
|
-
}, [rowVirtualizer,
|
|
741
|
+
}, [rowVirtualizer, rowEstimate, activeTier]);
|
|
683
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]);
|
|
684
753
|
/** The rows to render, and the two spacers that keep the scrollbar honest about the
|
|
685
754
|
* ones that are not. Off, this is every row and no spacers — byte-identical output.
|
|
686
755
|
* `null` is a row a {@link serverWindow} has not loaded: a placeholder of the estimated
|
|
@@ -688,25 +757,36 @@ export function DataTable({ columns, data, getRowId, onRowClick, onRowEdit, onRo
|
|
|
688
757
|
const bodyRows = virtualEnabled
|
|
689
758
|
? virtualRows.map((v) => rows[v.index - windowStart] ?? null)
|
|
690
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
|
+
: [];
|
|
691
767
|
useImperativeHandle(handleRef, () => ({
|
|
692
768
|
get scrollElement() {
|
|
693
769
|
return scrollRef.current;
|
|
694
770
|
},
|
|
695
771
|
scrollToIndex(index, options = {}) {
|
|
696
|
-
const element = scrollRef.current;
|
|
772
|
+
const element = windowScroll ? window : scrollRef.current;
|
|
697
773
|
if (!element || !virtualEnabled)
|
|
698
774
|
return null;
|
|
699
775
|
const clamped = Math.max(0, Math.min(index, loadedRef.current.total - 1));
|
|
700
|
-
const [offset] = rowVirtualizer.getOffsetForIndex(clamped, options.align ?? 'center') ?? [clamped *
|
|
776
|
+
const [offset] = rowVirtualizer.getOffsetForIndex(clamped, options.align ?? 'center') ?? [clamped * rowEstimate];
|
|
701
777
|
const top = Math.max(0, Math.round(offset));
|
|
702
778
|
element.scrollTo({ top, behavior: options.behavior ?? 'auto' });
|
|
703
779
|
return top;
|
|
704
780
|
},
|
|
705
|
-
}), [
|
|
706
|
-
|
|
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;
|
|
707
785
|
const padBottom = virtualEnabled
|
|
708
|
-
? rowVirtualizer.getTotalSize() - (virtualRows.at(-1)?.end ??
|
|
786
|
+
? Math.max(0, rowVirtualizer.getTotalSize() - ((virtualRows.at(-1)?.end ?? margin) - margin))
|
|
709
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;
|
|
710
790
|
const hasActions = !!(onRowEdit || onRowDelete || rowMenu);
|
|
711
791
|
// ── Row selection (see the `selectedIds` prop doc) ──────────────────────────
|
|
712
792
|
const canSelect = (row) => isRowSelectable?.(row) ?? true;
|
|
@@ -817,19 +897,46 @@ export function DataTable({ columns, data, getRowId, onRowClick, onRowEdit, onRo
|
|
|
817
897
|
// say so at the cut edge, or a narrow pane reads as a table with its right-hand
|
|
818
898
|
// columns missing rather than one that scrolls (`useScrollEdgeFade`).
|
|
819
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;
|
|
820
918
|
const tableMinWidth = fixedLayout
|
|
821
|
-
? 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))
|
|
822
920
|
: minTableWidth;
|
|
823
|
-
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,
|
|
824
922
|
// guardrails-ignore no-raw-overflow: dual-axis table scroll container — sticky header pins to it; ScrollArea cannot own horizontal table scroll.
|
|
825
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
|
|
826
924
|
? (_row, index) => {
|
|
827
925
|
const tsRow = rows[index];
|
|
828
926
|
return tsRow ? rowCheckbox(tsRow) : null;
|
|
829
927
|
}
|
|
830
|
-
: undefined, cardMinWidth: cardMinWidth, className: 'p-2' })) : (_jsxs(_Fragment, { children: [mobileCards && (_jsx("div", {
|
|
831
|
-
|
|
832
|
-
|
|
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: (() => {
|
|
833
940
|
const [title, ...rest] = cardColumns;
|
|
834
941
|
// ONE shared grid template for every row — parent and child rows
|
|
835
942
|
// lock into identical column slots, so the status/ports/actions
|
|
@@ -855,18 +962,24 @@ export function DataTable({ columns, data, getRowId, onRowClick, onRowEdit, onRo
|
|
|
855
962
|
...(hasActions ? ['32px'] : []),
|
|
856
963
|
].join(' ');
|
|
857
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] }));
|
|
858
|
-
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
|
+
}
|
|
859
969
|
const isExpanded = renderExpanded != null && expandedRowId != null && row.id === expandedRowId;
|
|
860
|
-
|
|
970
|
+
const item = (_jsxs(_Fragment, { children: [_jsxs("div", { ...mergeRowProps({
|
|
861
971
|
...dragPropsFor(row.original),
|
|
862
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)),
|
|
863
973
|
style: { gridTemplateColumns: template },
|
|
864
|
-
}, 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));
|
|
865
978
|
});
|
|
866
|
-
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 })] }));
|
|
867
980
|
})() })), _jsx("div", { ref: bodyScrolls ? undefined : gridFade.ref, onScroll: bodyScrolls ? undefined : gridFade.onScroll, style: bodyScrolls ? undefined : gridFade.maskStyle,
|
|
868
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).
|
|
869
|
-
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: [(() => {
|
|
870
983
|
// `ColDef.leading` columns sit BEFORE the checkbox column; see its doc.
|
|
871
984
|
const renderHeader = (h, headerIndex) => {
|
|
872
985
|
const sorted = h.column.getIsSorted();
|
|
@@ -897,13 +1010,13 @@ export function DataTable({ columns, data, getRowId, onRowClick, onRowEdit, onRo
|
|
|
897
1010
|
frozen &&
|
|
898
1011
|
(selectionShown
|
|
899
1012
|
? 'sticky left-11 bg-card shadow-[1px_0_0_0_var(--color-border)]'
|
|
900
|
-
: '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
|
|
901
1014
|
? null
|
|
902
1015
|
: renderTemplate(h.column.columnDef.header, h.getContext()) }, h.id));
|
|
903
1016
|
};
|
|
904
1017
|
const isLeading = (h) => h.column.columnDef.meta?.leading === true;
|
|
905
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)] }));
|
|
906
|
-
})(), 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) => {
|
|
907
1020
|
if (row === null) {
|
|
908
1021
|
// A row the server window has not loaded yet: the estimated height, so
|
|
909
1022
|
// the spacer arithmetic holds, and a skeleton line rather than a gap.
|
|
@@ -923,7 +1036,7 @@ export function DataTable({ columns, data, getRowId, onRowClick, onRowEdit, onRo
|
|
|
923
1036
|
// every measurement lands on index 0 and the spacer never grows.
|
|
924
1037
|
// (The ref stays OUT of the merge: `getRowProps` cannot take it.)
|
|
925
1038
|
ref: virtualEnabled && fixedRowHeight === null ? rowVirtualizer.measureElement : undefined, ...mergeRowProps({
|
|
926
|
-
...(virtualEnabled ? { 'data-index': rowIndex } : {}),
|
|
1039
|
+
...(virtualEnabled ? { 'data-index': rowIndex, 'aria-rowindex': rowIndex + 2 } : {}),
|
|
927
1040
|
style: fixedRowHeight === null ? undefined : { height: fixedRowHeight },
|
|
928
1041
|
'data-expanded': isExpanded || undefined,
|
|
929
1042
|
...dragPropsFor(row.original),
|
|
@@ -972,7 +1085,7 @@ export function DataTable({ columns, data, getRowId, onRowClick, onRowEdit, onRo
|
|
|
972
1085
|
// `overflow: hidden`: a cell may host a menu/tooltip that
|
|
973
1086
|
// has to escape its bounds.
|
|
974
1087
|
fixedLayout && '[overflow-wrap:anywhere]', cell.column.columnDef.meta?.align === 'right' && 'text-right', cell.column.columnDef.meta?.hideBelow &&
|
|
975
|
-
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));
|
|
976
1089
|
};
|
|
977
1090
|
return (_jsxs(_Fragment, { children: [cells.filter(isLeading).map((cell) => renderCell(cell, -1)), selectionShown ? (_jsx("td", { ...selectCellProps(row), style: stickyFirstColumn
|
|
978
1091
|
? {
|
|
@@ -981,22 +1094,33 @@ export function DataTable({ columns, data, getRowId, onRowClick, onRowEdit, onRo
|
|
|
981
1094
|
: 'var(--color-card)',
|
|
982
1095
|
}
|
|
983
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)] }));
|
|
984
|
-
})(), 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) => {
|
|
985
1098
|
if (el && scrolledExpansionRef.current !== row.id) {
|
|
986
1099
|
scrolledExpansionRef.current = row.id;
|
|
987
1100
|
// A row expanded near the fold must reveal its panel.
|
|
988
1101
|
el.scrollIntoView({ block: 'nearest' });
|
|
989
1102
|
}
|
|
990
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));
|
|
991
|
-
}), 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] }));
|
|
992
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;
|
|
993
1109
|
/**
|
|
994
|
-
*
|
|
995
|
-
*
|
|
996
|
-
*
|
|
997
|
-
* anchored bottom-right inside the card bounds. Values render through the SAME
|
|
998
|
-
* {@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.
|
|
999
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
|
+
}
|
|
1000
1124
|
function MobileRowCard({ row, columns, className, onRowClick, dragProps, select, actions, expanded, }) {
|
|
1001
1125
|
const visible = columns.filter((c) => !c.hidden);
|
|
1002
1126
|
const [title, ...rest] = visible;
|