cursedbelt 5.23.0 โ†’ 5.26.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.
Files changed (131) hide show
  1. package/dist/react/components/Badge.d.ts +6 -6
  2. package/dist/react/components/Badge.d.ts.map +1 -1
  3. package/dist/react/components/Badge.js +13 -5
  4. package/dist/react/components/Badge.js.map +1 -1
  5. package/dist/react/components/DataTable/CardView.d.ts +6 -2
  6. package/dist/react/components/DataTable/CardView.d.ts.map +1 -1
  7. package/dist/react/components/DataTable/CardView.js +6 -5
  8. package/dist/react/components/DataTable/CardView.js.map +1 -1
  9. package/dist/react/components/DataTable/DataTable.d.ts +165 -5
  10. package/dist/react/components/DataTable/DataTable.d.ts.map +1 -1
  11. package/dist/react/components/DataTable/DataTable.js +445 -114
  12. package/dist/react/components/DataTable/DataTable.js.map +1 -1
  13. package/dist/react/components/DataTable/RowActions.d.ts +3 -1
  14. package/dist/react/components/DataTable/RowActions.d.ts.map +1 -1
  15. package/dist/react/components/DataTable/RowActions.js +9 -2
  16. package/dist/react/components/DataTable/RowActions.js.map +1 -1
  17. package/dist/react/components/DataTable/SortHeader.d.ts +4 -1
  18. package/dist/react/components/DataTable/SortHeader.d.ts.map +1 -1
  19. package/dist/react/components/DataTable/SortHeader.js +7 -1
  20. package/dist/react/components/DataTable/SortHeader.js.map +1 -1
  21. package/dist/react/components/DataTable/cells/CellRenderer.d.ts.map +1 -1
  22. package/dist/react/components/DataTable/cells/CellRenderer.js +4 -1
  23. package/dist/react/components/DataTable/cells/CellRenderer.js.map +1 -1
  24. package/dist/react/components/DataTable/index.d.ts +2 -2
  25. package/dist/react/components/DataTable/index.d.ts.map +1 -1
  26. package/dist/react/components/DataTable/index.js +1 -1
  27. package/dist/react/components/DataTable/index.js.map +1 -1
  28. package/dist/react/components/DataTable/lazyPart.d.ts +27 -0
  29. package/dist/react/components/DataTable/lazyPart.d.ts.map +1 -0
  30. package/dist/react/components/DataTable/lazyPart.js +46 -0
  31. package/dist/react/components/DataTable/lazyPart.js.map +1 -0
  32. package/dist/react/components/DataTable/types.d.ts +106 -0
  33. package/dist/react/components/DataTable/types.d.ts.map +1 -1
  34. package/dist/react/components/DataTable/types.js.map +1 -1
  35. package/dist/react/components/auth/SignInCard.d.ts +49 -0
  36. package/dist/react/components/auth/SignInCard.d.ts.map +1 -0
  37. package/dist/react/components/auth/SignInCard.js +21 -0
  38. package/dist/react/components/auth/SignInCard.js.map +1 -0
  39. package/dist/react/components/auth/index.d.ts +1 -0
  40. package/dist/react/components/auth/index.d.ts.map +1 -1
  41. package/dist/react/components/auth/index.js +1 -0
  42. package/dist/react/components/auth/index.js.map +1 -1
  43. package/dist/react/deep-link/index.d.ts +1 -1
  44. package/dist/react/deep-link/index.d.ts.map +1 -1
  45. package/dist/react/deep-link/index.js.map +1 -1
  46. package/dist/react/deep-link/useDeepLink.d.ts +21 -15
  47. package/dist/react/deep-link/useDeepLink.d.ts.map +1 -1
  48. package/dist/react/deep-link/useDeepLink.js +8 -5
  49. package/dist/react/deep-link/useDeepLink.js.map +1 -1
  50. package/dist/react/file-tree/FileTree.d.ts +14 -1
  51. package/dist/react/file-tree/FileTree.d.ts.map +1 -1
  52. package/dist/react/file-tree/FileTree.js +2 -2
  53. package/dist/react/file-tree/FileTree.js.map +1 -1
  54. package/dist/react/file-tree/fileTree.css +8 -0
  55. package/dist/react/filter-rail/FilterRail.d.ts +31 -1
  56. package/dist/react/filter-rail/FilterRail.d.ts.map +1 -1
  57. package/dist/react/filter-rail/FilterRail.js +20 -5
  58. package/dist/react/filter-rail/FilterRail.js.map +1 -1
  59. package/dist/react/filter-rail/RailSection.d.ts +56 -0
  60. package/dist/react/filter-rail/RailSection.d.ts.map +1 -0
  61. package/dist/react/filter-rail/RailSection.js +108 -0
  62. package/dist/react/filter-rail/RailSection.js.map +1 -0
  63. package/dist/react/filter-rail/index.d.ts +2 -1
  64. package/dist/react/filter-rail/index.d.ts.map +1 -1
  65. package/dist/react/filter-rail/index.js +2 -1
  66. package/dist/react/filter-rail/index.js.map +1 -1
  67. package/dist/react/hooks/useScrollMemory.d.ts +6 -0
  68. package/dist/react/hooks/useScrollMemory.d.ts.map +1 -1
  69. package/dist/react/hooks/useScrollMemory.js +11 -0
  70. package/dist/react/hooks/useScrollMemory.js.map +1 -1
  71. package/dist/react/index.d.ts +3 -1
  72. package/dist/react/index.d.ts.map +1 -1
  73. package/dist/react/index.js +6 -1
  74. package/dist/react/index.js.map +1 -1
  75. package/dist/react/media-gallery/preferenceBus.d.ts +10 -0
  76. package/dist/react/media-gallery/preferenceBus.d.ts.map +1 -1
  77. package/dist/react/media-gallery/preferenceBus.js +24 -0
  78. package/dist/react/media-gallery/preferenceBus.js.map +1 -1
  79. package/dist/react/media-gallery/preferenceSync.d.ts.map +1 -1
  80. package/dist/react/media-gallery/preferenceSync.js +3 -0
  81. package/dist/react/media-gallery/preferenceSync.js.map +1 -1
  82. package/dist/react/nav/TopBar.d.ts +8 -1
  83. package/dist/react/nav/TopBar.d.ts.map +1 -1
  84. package/dist/react/nav/TopBar.js +2 -2
  85. package/dist/react/nav/TopBar.js.map +1 -1
  86. package/dist/styles-areas/analytics.css +18 -15
  87. package/dist/styles-areas/core.css +8 -7
  88. package/dist/styles-areas/data-table.css +13 -10
  89. package/dist/styles-areas/filter-rail.css +6 -5
  90. package/dist/styles-utilities.css +90 -0
  91. package/package.json +3 -3
  92. package/src/react/components/Badge.capsule.spec.ts +178 -0
  93. package/src/react/components/Badge.tsx +13 -5
  94. package/src/react/components/DataTable/CardView.tsx +8 -1
  95. package/src/react/components/DataTable/DataTable.spec.tsx +6 -2
  96. package/src/react/components/DataTable/DataTable.tsx +770 -86
  97. package/src/react/components/DataTable/RowActions.tsx +18 -10
  98. package/src/react/components/DataTable/SortHeader.tsx +19 -0
  99. package/src/react/components/DataTable/cells/CellRenderer.tsx +2 -0
  100. package/src/react/components/DataTable/index.ts +10 -1
  101. package/src/react/components/DataTable/lazyPart.spec.tsx +99 -0
  102. package/src/react/components/DataTable/lazyPart.tsx +58 -0
  103. package/src/react/components/DataTable/rowHooks.spec.tsx +289 -0
  104. package/src/react/components/DataTable/selection.spec.tsx +193 -0
  105. package/src/react/components/DataTable/tableSystem.spec.tsx +6 -2
  106. package/src/react/components/DataTable/types.ts +110 -0
  107. package/src/react/components/DataTable/virtualize.spec.tsx +38 -2
  108. package/src/react/components/auth/SignInCard.spec.tsx +48 -0
  109. package/src/react/components/auth/SignInCard.tsx +90 -0
  110. package/src/react/components/auth/index.ts +1 -0
  111. package/src/react/deep-link/index.ts +1 -0
  112. package/src/react/deep-link/useDeepLink.spec.tsx +31 -0
  113. package/src/react/deep-link/useDeepLink.tsx +31 -11
  114. package/src/react/file-tree/FileTree.tsx +15 -1
  115. package/src/react/file-tree/fileTree.css +8 -0
  116. package/src/react/filter-rail/FilterRail.tsx +93 -26
  117. package/src/react/filter-rail/RailSection.spec.tsx +156 -0
  118. package/src/react/filter-rail/RailSection.tsx +216 -0
  119. package/src/react/filter-rail/index.ts +2 -0
  120. package/src/react/hooks/useScrollMemory.ts +11 -0
  121. package/src/react/index.ts +10 -0
  122. package/src/react/media-gallery/MediaGallery.spec.tsx +9 -3
  123. package/src/react/media-gallery/preferenceBus.ts +28 -0
  124. package/src/react/media-gallery/preferenceSync.ts +3 -0
  125. package/src/react/nav/TopBar.spec.tsx +11 -0
  126. package/src/react/nav/TopBar.tsx +9 -1
  127. package/src/styles-areas/analytics.css +18 -15
  128. package/src/styles-areas/core.css +8 -7
  129. package/src/styles-areas/data-table.css +13 -10
  130. package/src/styles-areas/filter-rail.css +6 -5
  131. package/src/styles-utilities.css +90 -0
@@ -1,22 +1,52 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { flexRender, getCoreRowModel, getFilteredRowModel, getPaginationRowModel, getSortedRowModel, useReactTable, } from '@tanstack/react-table';
3
3
  import { useVirtualizer } from '@tanstack/react-virtual';
4
- import { Fragment, useCallback, useEffect, useMemo, useRef, useState } from 'react';
4
+ import { Fragment, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react';
5
5
  import { useScrollEdgeFade } from '../../hooks/useScrollEdgeFade.js';
6
6
  import { cn } from '../../lib/cn.js';
7
+ import { TAP_TARGET_HEIGHT } from '../../lib/tapTarget.js';
7
8
  import { CardTapTarget } from '../CardTapTarget.js';
9
+ import { Checkbox } from '../Checkbox.js';
8
10
  import { EmptyState } from '../EmptyState.js';
9
11
  import { Skeleton } from '../Skeleton.js';
10
12
  import { Surface } from '../Surface.js';
13
+ import { Tooltip } from '../Tooltip.js';
11
14
  import { CardView } from './CardView.js';
12
15
  import { CellRenderer } from './cells/CellRenderer.js';
13
16
  import { EMPTY_FILTER_QUERY, evaluateFilterQuery, filterableText, } from './filterQuery.js';
14
- import { TablePagination } from './Pagination.js';
15
- import { RowActions } from './RowActions.js';
17
+ import { preloadRowMenu, RowActions } from './RowActions.js';
16
18
  import { writeRowDrag } from './rowDrag.js';
17
19
  import { SortHeader } from './SortHeader.js';
18
- import { TableToolbar } from './Toolbar.js';
19
20
  import { resolveColumnAlign } from './types.js';
21
+ import { lazyPart } from './lazyPart.js';
22
+ // ๐Ÿ”ด The toolbar and the pager load only when a table DRAWS them (forge task 2309).
23
+ // Between them they reach Radix's Select, Menu and Popover โ€” ~20 kB gzip that a
24
+ // read-only list (`searchable={false}`, `serverWindow`, no row menu) never opens.
25
+ // Measured on apps/music's track list: 67 kB gzip over its entry ceiling, most of
26
+ // it these two parts' dependencies. A static import made every consumer pay for
27
+ // them. The row kebab's menu is lazy the same way, inside `RowActions`; all three
28
+ // are loaded ahead by `preloadDataTableParts`. `lazyPart` erases the component's
29
+ // type parameter, so each is given back its own signature.
30
+ const ToolbarPart = lazyPart(() => import('./Toolbar.js').then((m) => m.TableToolbar));
31
+ const PaginationPart = lazyPart(() => import('./Pagination.js').then((m) => m.TablePagination));
32
+ /**
33
+ * Load the table's lazy parts โ€” the toolbar, the pager, the row kebab's menu โ€” now,
34
+ * so every table mounted after this resolves draws them on its first render. For a
35
+ * spec setup that queries them synchronously after `render(โ€ฆ)`, and for an app about
36
+ * to show a table with a toolbar. Never required: without it they arrive a tick late,
37
+ * into a placeholder of their own height.
38
+ */
39
+ export async function preloadDataTableParts() {
40
+ await Promise.all([ToolbarPart.preload(), PaginationPart.preload(), preloadRowMenu()]);
41
+ }
42
+ /** Where the toolbar will be, the height it will be โ€” so its arrival shifts nothing. */
43
+ function ToolbarPlaceholder() {
44
+ return (_jsx("div", { "data-cb-table-toolbar-pending": '', "aria-hidden": true, className: 'border-b border-border px-2 py-2', children: _jsx("div", { className: cn('h-9', TAP_TARGET_HEIGHT) }) }));
45
+ }
46
+ /** Where the pager will be, the height it will be. */
47
+ function PaginationPlaceholder() {
48
+ return (_jsx("div", { "data-cb-table-pagination-pending": '', "aria-hidden": true, className: 'border-t border-border px-4 py-2.5', children: _jsx("div", { className: cn('h-8', TAP_TARGET_HEIGHT) }) }));
49
+ }
20
50
  /** Build an OnChangeFn that updates local state AND notifies an optional listener. */
21
51
  function chainUpdater(set, notify) {
22
52
  return (updater) => set((prev) => {
@@ -45,6 +75,8 @@ function chainUpdaterResettingPage(set, setPagination, notify) {
45
75
  export const AUTO_TRACK_FLOOR_PX = 120;
46
76
  /** The trailing row-actions column is a fixed `w-20`. */
47
77
  const ACTIONS_TRACK_PX = 80;
78
+ /** The leading row-selection column is a fixed `w-11` (44px โ€” a full tap target). */
79
+ const SELECT_TRACK_PX = 44;
48
80
  /**
49
81
  * The narrowest width at which a `fixedLayout` table still honors the tracks its
50
82
  * columns declared: the sum of every visible column's floor (`minWidth`, else a
@@ -115,15 +147,16 @@ export function declaredTrackFloor(columns, hasActions = false) {
115
147
  * been required to be stable since ES2019 and TanStack's sorted row model uses
116
148
  * it, so rows that tie on every key in the stack keep the order they arrived in.
117
149
  */
118
- export function nextSortStack(current, id, multi) {
150
+ export function nextSortStack(current, id, multi, options = {}) {
151
+ const first = options.descFirst ?? false;
119
152
  const existing = current.find((sort) => sort.id === id);
120
153
  if (!existing) {
121
- const ascending = { id, desc: false };
122
- return multi ? [ascending, ...current] : [ascending];
154
+ const started = { id, desc: first };
155
+ return multi ? [started, ...current] : [started];
123
156
  }
124
- if (!existing.desc) {
125
- const flipped = current.map((sort) => (sort.id === id ? { id, desc: true } : sort));
126
- return multi ? flipped : [{ id, desc: true }];
157
+ if (existing.desc === first || options.removable === false) {
158
+ const flipped = { id, desc: !existing.desc };
159
+ return multi ? current.map((sort) => (sort.id === id ? flipped : sort)) : [flipped];
127
160
  }
128
161
  return multi ? current.filter((sort) => sort.id !== id) : [];
129
162
  }
@@ -142,6 +175,48 @@ function readPath(row, key) {
142
175
  return value[part];
143
176
  }, row);
144
177
  }
178
+ /**
179
+ * `ColDef.hideBelow` as the container-query classes that do it. Literal strings, so the style
180
+ * generators (`scripts/generate*Styles.ts`) find them in the source.
181
+ *
182
+ * ๐Ÿ”ด The column COLLAPSES โ€” zero width, no padding, its content removed โ€” rather than
183
+ * `display: none`. A hidden cell is not a column, but the rows that SPAN the table (the
184
+ * virtualizer's spacers, a server window's placeholder rows, an expanded panel) still say
185
+ * `colSpan={every column}`, and a span wider than the table's real columns makes the missing
186
+ * ones up: they took the width the hidden columns gave back, so the table's rows stopped short
187
+ * of its right edge and the title โ€” the one width-less column โ€” was crushed to "Sโ€ฆ". Measured
188
+ * on apps/music at 390px and 1100px, forge task 2302. Collapsed, the column count never moves,
189
+ * and every span stays true. (`[&>*]:hidden` takes the content out of the accessibility tree
190
+ * too, so a collapsed cell reads as nothing, not as a value nobody can see.)
191
+ */
192
+ const HIDE_BELOW_CLASS = {
193
+ sm: '@max-2xl:w-0! @max-2xl:min-w-0! @max-2xl:p-0! @max-2xl:border-0 @max-2xl:[&>*]:hidden',
194
+ md: '@max-4xl:w-0! @max-4xl:min-w-0! @max-4xl:p-0! @max-4xl:border-0 @max-4xl:[&>*]:hidden',
195
+ lg: '@max-6xl:w-0! @max-6xl:min-w-0! @max-6xl:p-0! @max-6xl:border-0 @max-6xl:[&>*]:hidden',
196
+ };
197
+ /**
198
+ * The table's own props for a row, with the caller's `getRowProps` merged on top: classes join,
199
+ * styles spread, and a handler both set runs the table's first, then the caller's.
200
+ */
201
+ export function mergeRowProps(own, extra) {
202
+ if (!extra)
203
+ return own;
204
+ const merged = { ...own, ...extra };
205
+ if (own.className || extra.className)
206
+ merged.className = cn(own.className, extra.className);
207
+ if (own.style || extra.style)
208
+ merged.style = { ...own.style, ...extra.style };
209
+ for (const [key, handler] of Object.entries(extra)) {
210
+ const mine = own[key];
211
+ if (!key.startsWith('on') || typeof handler !== 'function' || typeof mine !== 'function')
212
+ continue;
213
+ merged[key] = (event) => {
214
+ mine(event);
215
+ handler(event);
216
+ };
217
+ }
218
+ return merged;
219
+ }
145
220
  /** True when a cell value carries nothing worth a column: null/undefined/''/[]. */
146
221
  function isEmptyCellValue(v) {
147
222
  if (v == null)
@@ -165,11 +240,15 @@ function isEmptyCellValue(v) {
165
240
  * full-width accordion detail row that never breaks column alignment, and an automatic
166
241
  * stacked-card layout under `sm` so phones are never handed a clipped grid.
167
242
  */
168
- 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, mobileCards = true, serverSide, pageCount, rowCount, onSortingChange, onColumnFiltersChange, onGlobalFilterChange, onPaginationChange, defaultSort, density = 'comfortable', viewMode, onViewModeChange, cardMinWidth, searchable = true, searchPlaceholder, multiSort = true, filterQuery: filterQueryProp, onFilterQueryChange, getRowDrag, dragSourceId, }) {
243
+ 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, multiSort = true, filterQuery: filterQueryProp, onFilterQueryChange, getRowDrag, dragSourceId, selectedIds, onSelectionChange, isRowSelectable, unselectableReason, selectionLabel, selectionSummary, selectAll, getRowProps, }) {
169
244
  // Vertical cell padding for the wide grid tier โ€” the one lever the density knob
170
245
  // pulls (headers + body cells stay aligned by sharing it).
171
246
  const cellPadY = density === 'compact' ? 'py-1.5' : 'py-2.5';
172
- const [sorting, setSorting] = useState(defaultSort ?? []);
247
+ const [ownSorting, setOwnSorting] = useState(defaultSort ?? []);
248
+ const sorting = sortingProp ?? ownSorting;
249
+ /** The sort as of THIS render, for a write that has to start from it (see `applySorting`). */
250
+ const sortingRef = useRef(sorting);
251
+ sortingRef.current = sorting;
173
252
  const [columnFilters, setColumnFilters] = useState([]);
174
253
  const [globalFilter, setGlobalFilter] = useState('');
175
254
  const [columnVisibility, setColumnVisibility] = useState(() => Object.fromEntries(columns.filter((c) => c.hidden).map((c) => [c.key, false])));
@@ -223,14 +302,17 @@ export function DataTable({ columns, data, getRowId, onRowClick, onRowEdit, onRo
223
302
  * server-side table ends up sorted differently from what its chips say.
224
303
  */
225
304
  const applySorting = useCallback((next) => {
226
- setSorting((prev) => {
227
- const value = next(prev);
228
- setPagination((p) => (p.pageIndex === 0 ? p : { ...p, pageIndex: 0 }));
229
- onSortingChange?.(value);
230
- return value;
231
- });
232
- }, [onSortingChange]);
233
- const toggleSortKey = useCallback((id) => applySorting((prev) => nextSortStack(prev, id, multiSort)), [applySorting, multiSort]);
305
+ // Computed OUTSIDE a state updater: under a controlled `sorting` there is no state to
306
+ // update, and a notify inside an updater fires twice in StrictMode.
307
+ const value = next(sortingRef.current);
308
+ sortingRef.current = value;
309
+ if (sortingProp === undefined)
310
+ setOwnSorting(value);
311
+ setPagination((p) => (p.pageIndex === 0 ? p : { ...p, pageIndex: 0 }));
312
+ onSortingChange?.(value);
313
+ }, [onSortingChange, sortingProp]);
314
+ const sortDescFirstOf = useCallback((id) => columns.find((col) => col.key === id)?.sortDescFirst ?? false, [columns]);
315
+ const toggleSortKey = useCallback((id) => applySorting((prev) => nextSortStack(prev, id, multiSort, { descFirst: sortDescFirstOf(id), removable: enableSortingRemoval })), [applySorting, enableSortingRemoval, multiSort, sortDescFirstOf]);
234
316
  const tsColumns = useMemo(() => liveColumns.map((col) => ({
235
317
  id: col.key,
236
318
  accessorKey: col.key,
@@ -241,6 +323,8 @@ export function DataTable({ columns, data, getRowId, onRowClick, onRowEdit, onRo
241
323
  align: resolveColumnAlign(col),
242
324
  width: col.width,
243
325
  minWidth: col.minWidth,
326
+ hideBelow: col.hideBelow,
327
+ leading: col.leading,
244
328
  },
245
329
  size: typeof col.width === 'number' ? col.width : undefined,
246
330
  enableSorting: col.sortable ?? false,
@@ -262,10 +346,10 @@ export function DataTable({ columns, data, getRowId, onRowClick, onRowEdit, onRo
262
346
  // TanStack's column instances mid-interaction.
263
347
  header: ({ column, table: t }) => {
264
348
  const stack = t.getState().sorting;
265
- return (_jsx(SortHeader, { column: column, label: col.label, sortable: col.sortable, sortIndex: stack.length > 1 ? stack.findIndex((s) => s.id === col.key) : -1, onSort: () => toggleSortKey(col.key) }));
349
+ return (_jsx(SortHeader, { column: column, label: col.label, sortable: col.sortable, sortIndex: stack.length > 1 ? stack.findIndex((s) => s.id === col.key) : -1, onSort: () => toggleSortKey(col.key), disabledReason: sortDisabledReason }));
266
350
  },
267
351
  cell: ({ getValue, row }) => (_jsx(CellRenderer, { col: col, value: getValue(), row: row.original, onEdit: onRowEdit ? () => onRowEdit(row.original) : undefined })),
268
- })), [liveColumns, onRowEdit, toggleSortKey]);
352
+ })), [liveColumns, onRowEdit, sortDisabledReason, toggleSortKey]);
269
353
  const labelOf = useCallback((id) => liveColumns.find((col) => col.key === id)?.label ?? id, [liveColumns]);
270
354
  const controls = useMemo(() => ({
271
355
  searchText: globalFilter,
@@ -317,16 +401,28 @@ export function DataTable({ columns, data, getRowId, onRowClick, onRowEdit, onRo
317
401
  * failure the pager's absence hides rather than reveals, which is why it is tested by
318
402
  * measuring `scrollHeight` in a real browser.
319
403
  */
320
- const virtualOptions = virtualize === true ? {} : virtualize || null;
404
+ const virtualOptions = virtualize === true || (serverWindow && !virtualize) ? {} : virtualize || null;
321
405
  const virtualEnabled = virtualOptions !== null;
322
406
  const bodyScrolls = scrollBody || virtualEnabled;
323
407
  const paginationHidden = hidePagination || virtualEnabled;
408
+ const selectionEnabled = selectedIds !== undefined;
409
+ /**
410
+ * Without `getRowId`, TanStack ids a row by its index in the array it was
411
+ * handed โ€” which is `filteredData`, so the builder narrowing the rows would
412
+ * re-number them and a selection would silently move to other rows. When
413
+ * selection is on, the fallback id is the row's index in `data` instead.
414
+ * Off, TanStack's own default stands, untouched.
415
+ */
416
+ const dataIndex = useMemo(() => (selectionEnabled && !getRowId ? new Map(data.map((row, i) => [row, i])) : null), [data, getRowId, selectionEnabled]);
417
+ const resolvedGetRowId = useMemo(() => getRowId ?? (dataIndex ? (row) => String(dataIndex.get(row)) : undefined), [dataIndex, getRowId]);
324
418
  const table = useReactTable({
325
419
  data: filteredData,
326
420
  columns: tsColumns,
327
- getRowId,
421
+ getRowId: resolvedGetRowId,
328
422
  state: { sorting, columnFilters, globalFilter: search, columnVisibility, pagination },
329
- onSortingChange: chainUpdaterResettingPage(setSorting, setPagination, onSortingChange),
423
+ // Through `applySorting`, like every other write โ€” so a controlled `sorting` is honoured
424
+ // whether TanStack or a header asked for the change.
425
+ onSortingChange: (updater) => applySorting((prev) => (typeof updater === 'function' ? updater(prev) : updater)),
330
426
  onColumnFiltersChange: chainUpdaterResettingPage(setColumnFilters, setPagination, onColumnFiltersChange),
331
427
  // TanStack hands the updater whatever `state.globalFilter` is, which is now
332
428
  // a TableSearchState; the toolbar sets TEXT, so normalise both shapes.
@@ -354,14 +450,16 @@ export function DataTable({ columns, data, getRowId, onRowClick, onRowEdit, onRo
354
450
  return false;
355
451
  return filterableText(row.getValue(columnId)).toLowerCase().includes(text.toLowerCase());
356
452
  },
357
- manualSorting: serverSide,
358
- manualFiltering: serverSide,
453
+ // A server window is server-ordered and server-filtered by construction: a WINDOW
454
+ // re-sorted locally would put row 4,000's neighbours next to row 12's.
455
+ manualSorting: serverSide || !!serverWindow,
456
+ manualFiltering: serverSide || !!serverWindow,
359
457
  manualPagination: serverSide,
360
458
  pageCount: serverSide ? pageCount : undefined,
361
459
  rowCount: serverSide ? rowCount : undefined,
362
460
  getCoreRowModel: getCoreRowModel(),
363
- getSortedRowModel: serverSide ? undefined : getSortedRowModel(),
364
- getFilteredRowModel: serverSide ? undefined : getFilteredRowModel(),
461
+ getSortedRowModel: serverSide || serverWindow ? undefined : getSortedRowModel(),
462
+ getFilteredRowModel: serverSide || serverWindow ? undefined : getFilteredRowModel(),
365
463
  // ๐Ÿ”ด `virtualEnabled` too, not only `serverSide`. See the note where the flag is
366
464
  // computed: a paginated virtualizer sizes its spacer from one PAGE.
367
465
  getPaginationRowModel: serverSide || virtualEnabled ? undefined : getPaginationRowModel(),
@@ -399,17 +497,94 @@ export function DataTable({ columns, data, getRowId, onRowClick, onRowEdit, onRo
399
497
  };
400
498
  };
401
499
  const rows = table.getRowModel().rows;
402
- const estimateRowHeight = virtualOptions?.estimateRowHeight ?? (density === 'compact' ? 36 : 44);
500
+ /**
501
+ * ๐Ÿ”ด A {@link serverWindow} row is FIXED-height, never measured. A measured list places
502
+ * every unloaded row at the estimate and every loaded one at its real height, so the page
503
+ * that arrives above the viewport re-sizes a hundred rows and moves everything under the
504
+ * reader โ€” measured in the gate: a smooth reveal of row 4,000 that paged in the rows above
505
+ * it on arrival came to rest thirteen rows short. apps/music's own list held every row at
506
+ * one height for the same reason ("Fixed row height is what makes the arithmetic jitter-free").
507
+ */
508
+ const fixedRowHeight = serverWindow
509
+ ? (serverWindow.rowHeight ?? virtualOptions?.estimateRowHeight ?? (density === 'compact' ? 36 : 44))
510
+ : null;
511
+ const estimateRowHeight = fixedRowHeight ?? virtualOptions?.estimateRowHeight ?? (density === 'compact' ? 36 : 44);
403
512
  const scrollRef = useRef(null);
513
+ /** Rows before the loaded window, and the size of the whole result set โ€” both 0/`rows.length`
514
+ * without a {@link serverWindow}, which makes every index below the same number as before. */
515
+ const windowStart = serverWindow ? Math.max(0, serverWindow.start) : 0;
516
+ const windowTotal = serverWindow ? Math.max(serverWindow.total, windowStart + rows.length) : rows.length;
517
+ /** The caller's window callbacks, read at FIRE time: by the time a settle timer fires the
518
+ * window may have moved, and the fresh handler is the one that knows. */
519
+ const windowRef = useRef(serverWindow);
520
+ windowRef.current = serverWindow;
521
+ const loadedRef = useRef({ start: windowStart, end: windowStart + rows.length, total: windowTotal });
522
+ loadedRef.current = { start: windowStart, end: windowStart + rows.length, total: windowTotal };
523
+ const settleRef = useRef(null);
524
+ /** The `scrollTop` the last `onChange` saw. The virtualizer also calls `onChange` when its
525
+ * is-scrolling flag resets and on every re-render; only a MOVE may arm the settle timer, or
526
+ * the flag's own reset re-asks for the window that was just requested (measured: twice). */
527
+ const lastTopRef = useRef(null);
528
+ useEffect(() => () => {
529
+ if (settleRef.current)
530
+ clearTimeout(settleRef.current);
531
+ }, []);
404
532
  const rowVirtualizer = useVirtualizer({
405
- count: virtualEnabled ? rows.length : 0,
533
+ count: virtualEnabled ? windowTotal : 0,
406
534
  getScrollElement: () => scrollRef.current,
407
535
  estimateSize: () => estimateRowHeight,
408
536
  overscan: virtualOptions?.overscan ?? 8,
537
+ onChange: serverWindow
538
+ ? (instance) => {
539
+ const win = windowRef.current;
540
+ const element = instance.scrollElement;
541
+ if (!win || !element)
542
+ return;
543
+ const { start, end, total } = loadedRef.current;
544
+ // Rows over the whole result set: the first one on screen, and the one after the last.
545
+ const items = instance.getVirtualItems();
546
+ if (items.length === 0)
547
+ return;
548
+ const top = element.scrollTop;
549
+ const bottom = top + element.clientHeight;
550
+ const firstShown = items.find((item) => item.end > top)?.index ?? items[0].index;
551
+ const lastShown = (items.findLast((item) => item.start < bottom)?.index ?? items.at(-1).index) + 1;
552
+ const moved = lastTopRef.current !== top;
553
+ lastTopRef.current = top;
554
+ // Nothing on screen is loaded. Hand the destination over once the scroll RESTS,
555
+ // and do not also ask for a page โ€” it would load rows nowhere near the reader.
556
+ if (firstShown >= end || lastShown <= start) {
557
+ if (!moved)
558
+ return;
559
+ if (settleRef.current)
560
+ clearTimeout(settleRef.current);
561
+ settleRef.current = setTimeout(() => {
562
+ settleRef.current = null;
563
+ // Read at FIRE time: the window may have arrived while the scroll rested.
564
+ const loaded = loadedRef.current;
565
+ if (firstShown < loaded.end && lastShown > loaded.start)
566
+ return;
567
+ windowRef.current?.onNeedWindow?.(firstShown);
568
+ }, win.settleMs ?? 120);
569
+ return;
570
+ }
571
+ if (settleRef.current) {
572
+ clearTimeout(settleRef.current);
573
+ settleRef.current = null;
574
+ }
575
+ // Against the end of the LOADED window, never the spacer's โ€” see the prop doc.
576
+ if (win.onReachEnd && end < total && end - lastShown < (win.reachRows ?? 20))
577
+ win.onReachEnd();
578
+ // Tighter, and only off row 0: a reveal lands a few rows into a fresh window, and a
579
+ // generous trigger here would fire on arrival and cascade back to the top.
580
+ if (win.onReachStart && start > 0 && firstShown - start < (win.reachRowsUp ?? 8))
581
+ win.onReachStart();
582
+ }
583
+ : undefined,
409
584
  // ๐Ÿ”ด MEASURED, not assumed. A wrapping cell, an expanded detail panel and the
410
585
  // `compact` density all change a row's height, and a caller cannot be asked to
411
586
  // compute any of them. `estimateSize` only seeds the spacer before first paint.
412
- measureElement: (el) => el.getBoundingClientRect().height,
587
+ measureElement: (el) => fixedRowHeight ?? el.getBoundingClientRect().height,
413
588
  });
414
589
  /**
415
590
  * ๐Ÿ”ด Invalidate the measurement cache when the row height changes.
@@ -433,13 +608,118 @@ export function DataTable({ columns, data, getRowId, onRowClick, onRowEdit, onRo
433
608
  }, [rowVirtualizer, estimateRowHeight]);
434
609
  const virtualRows = virtualEnabled ? rowVirtualizer.getVirtualItems() : [];
435
610
  /** The rows to render, and the two spacers that keep the scrollbar honest about the
436
- * ones that are not. Off, this is every row and no spacers โ€” byte-identical output. */
437
- const bodyRows = virtualEnabled ? virtualRows.map((v) => rows[v.index]) : rows;
611
+ * ones that are not. Off, this is every row and no spacers โ€” byte-identical output.
612
+ * `null` is a row a {@link serverWindow} has not loaded: a placeholder of the estimated
613
+ * height, so the window can land anywhere in the spacer before its rows arrive. */
614
+ const bodyRows = virtualEnabled
615
+ ? virtualRows.map((v) => rows[v.index - windowStart] ?? null)
616
+ : rows;
617
+ useImperativeHandle(handleRef, () => ({
618
+ get scrollElement() {
619
+ return scrollRef.current;
620
+ },
621
+ scrollToIndex(index, options = {}) {
622
+ const element = scrollRef.current;
623
+ if (!element || !virtualEnabled)
624
+ return null;
625
+ const clamped = Math.max(0, Math.min(index, loadedRef.current.total - 1));
626
+ const [offset] = rowVirtualizer.getOffsetForIndex(clamped, options.align ?? 'center') ?? [clamped * estimateRowHeight];
627
+ const top = Math.max(0, Math.round(offset));
628
+ element.scrollTo({ top, behavior: options.behavior ?? 'auto' });
629
+ return top;
630
+ },
631
+ }), [estimateRowHeight, rowVirtualizer, virtualEnabled]);
438
632
  const padTop = virtualEnabled ? (virtualRows[0]?.start ?? 0) : 0;
439
633
  const padBottom = virtualEnabled
440
634
  ? rowVirtualizer.getTotalSize() - (virtualRows.at(-1)?.end ?? 0)
441
635
  : 0;
442
636
  const hasActions = !!(onRowEdit || onRowDelete || rowMenu);
637
+ // โ”€โ”€ Row selection (see the `selectedIds` prop doc) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
638
+ const canSelect = (row) => isRowSelectable?.(row) ?? true;
639
+ // Select-all's universe: the FILTERED rows across every page, minus the ones
640
+ // that cannot be selected. `getFilteredRowModel` is pre-pagination by design.
641
+ const selectableFilteredIds = selectionEnabled
642
+ ? table
643
+ .getFilteredRowModel()
644
+ .rows.filter((row) => canSelect(row.original))
645
+ .map((row) => row.id)
646
+ : [];
647
+ const selectedFilteredCount = selectionEnabled
648
+ ? selectableFilteredIds.filter((id) => selectedIds.has(id)).length
649
+ : 0;
650
+ const headerChecked = selectedFilteredCount === 0
651
+ ? false
652
+ : selectedFilteredCount === selectableFilteredIds.length
653
+ ? true
654
+ : 'indeterminate';
655
+ const toggleRowSelected = (id) => {
656
+ if (!selectedIds)
657
+ return;
658
+ const next = new Set(selectedIds);
659
+ if (next.has(id))
660
+ next.delete(id);
661
+ else
662
+ next.add(id);
663
+ onSelectionChange?.(next);
664
+ };
665
+ const toggleAllSelected = () => {
666
+ if (!selectedIds)
667
+ return;
668
+ const next = new Set(selectedIds);
669
+ if (headerChecked === true)
670
+ for (const id of selectableFilteredIds)
671
+ next.delete(id);
672
+ else
673
+ for (const id of selectableFilteredIds)
674
+ next.add(id);
675
+ onSelectionChange?.(next);
676
+ };
677
+ const labelForSelection = (row) => {
678
+ if (selectionLabel)
679
+ return selectionLabel(row);
680
+ const first = liveColumns[0];
681
+ const value = first ? readPath(row, first.key) : undefined;
682
+ const text = first?.displayValue ? first.displayValue(value, row) : filterableText(value);
683
+ return `Select ${text || 'row'}`;
684
+ };
685
+ const selectAllLabel = `Select all ${selectableFilteredIds.length} rows`;
686
+ /** One row's checkbox โ€” the same control in every tier. */
687
+ const rowCheckbox = (row) => {
688
+ if (!selectedIds)
689
+ return null;
690
+ const selectable = canSelect(row.original);
691
+ const box = (_jsx(Checkbox, { checked: selectedIds.has(row.id), disabled: !selectable, onCheckedChange: () => toggleRowSelected(row.id), "aria-label": labelForSelection(row.original) }));
692
+ if (selectable)
693
+ return box;
694
+ // A disabled button fires no pointer events, so the reason hangs off a
695
+ // wrapper โ€” visible and explained, never hidden (the owner UI rule).
696
+ return (_jsx(Tooltip, { content: unselectableReason?.(row.original) ?? "This row can't be selected", mobile: 'tap', children: _jsx("span", { "data-cb-unselectable": '', className: 'inline-flex', children: box }) }));
697
+ };
698
+ /**
699
+ * The whole cell toggles, not just the 16px box โ€” generous hit target. A click
700
+ * that landed on the checkbox's own `<label>` has already toggled it, so only
701
+ * the padding around it is handled here. Propagation stops either way so a
702
+ * selection click is never also the row click.
703
+ */
704
+ const selectCellProps = (row) => ({
705
+ 'data-cb-select-cell': '',
706
+ onClick: (event) => {
707
+ event.stopPropagation();
708
+ if (event.target.closest('label'))
709
+ return;
710
+ if (canSelect(row.original))
711
+ toggleRowSelected(row.id);
712
+ },
713
+ // Space/Enter on the focused checkbox must not bubble into the row's
714
+ // Enter-to-open handler.
715
+ onKeyDown: (event) => event.stopPropagation(),
716
+ });
717
+ const headerCheckbox = (label) => selectAll ? (_jsx(Tooltip, { content: selectAll.label, children: _jsx("span", { className: 'inline-flex', children: _jsx(Checkbox, { checked: selectAll.checked, disabled: selectAll.disabled, onCheckedChange: (checked) => selectAll.onChange(checked === true), "aria-label": selectAll.label, label: label, labelClassName: label == null ? undefined : 'text-xs text-muted-foreground' }) }) })) : (_jsx(Checkbox, { checked: headerChecked, disabled: selectableFilteredIds.length === 0, onCheckedChange: toggleAllSelected, "aria-label": selectAllLabel, label: label, labelClassName: label == null ? undefined : 'text-xs text-muted-foreground' }));
718
+ const summaryNode = selectedIds && selectionSummary
719
+ ? selectionSummary(resolvedGetRowId
720
+ ? data.filter((row) => selectedIds.has(resolvedGetRowId(row)))
721
+ : [])
722
+ : null;
443
723
  const toolbarNode = typeof toolbar === 'function'
444
724
  ? toolbar(table)
445
725
  : (toolbar ??
@@ -450,8 +730,8 @@ export function DataTable({ columns, data, getRowId, onRowClick, onRowEdit, onRo
450
730
  // passes its own gets exactly that, and `TableToolbar` reads the same
451
731
  // controls off `table.options.meta`, so composing a toolbar is never
452
732
  // the thing that costs you the search box.
453
- (searchable ? _jsx(TableToolbar, { table: table, searchPlaceholder: searchPlaceholder }) : null));
454
- const visibleColumnCount = table.getVisibleLeafColumns().length + (hasActions ? 1 : 0);
733
+ (searchable ? (_jsx(ToolbarPart, { table: table, searchPlaceholder: searchPlaceholder, fallback: _jsx(ToolbarPlaceholder, {}) })) : null));
734
+ const visibleColumnCount = table.getVisibleLeafColumns().length + (hasActions ? 1 : 0) + (selectionEnabled ? 1 : 0);
455
735
  // Cards track the SAME live visibility state as the grid (ViewOptionsMenu toggles
456
736
  // apply to both) โ€” not just the ColDefs' initial `hidden` flags.
457
737
  const cardColumns = liveColumns.filter((c) => table.getColumn(c.key)?.getIsVisible() ?? true);
@@ -464,9 +744,16 @@ export function DataTable({ columns, data, getRowId, onRowClick, onRowEdit, onRo
464
744
  // columns missing rather than one that scrolls (`useScrollEdgeFade`).
465
745
  const gridFade = useScrollEdgeFade();
466
746
  const tableMinWidth = fixedLayout
467
- ? Math.max(minTableWidth ?? 0, declaredTrackFloor(cardColumns, hasActions))
747
+ ? Math.max(minTableWidth ?? 0, declaredTrackFloor(cardColumns, hasActions) + (selectionEnabled ? SELECT_TRACK_PX : 0))
468
748
  : minTableWidth;
469
- return (_jsxs("div", { className: cn('@container flex min-h-0 flex-col', className), children: [toolbarNode, 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 ? (_jsx("div", { className: 'p-2', children: emptyState ?? _jsx(EmptyState, { title: 'No results' }) })) : (_jsxs("div", { ref: scrollRef, className: cn(bodyScrolls && 'min-h-0 flex-1 overflow-auto'), children: [viewMode === 'card' ? (_jsx(CardView, { columns: cardColumns, data: rows.map((row) => row.original), getRowId: getRowId, onRowClick: onRowClick, dragPropsFor: getRowDrag ? dragPropsFor : undefined, cardMinWidth: cardMinWidth, className: 'p-2' })) : (_jsxs(_Fragment, { children: [mobileCards && (_jsx("div", { "data-cb-table-cards": '', className: 'flex flex-col gap-3 @xl:hidden', children: rows.map((row) => (_jsx(MobileRowCard, { row: row, columns: cardColumns, className: rowClassName?.(row.original), onRowClick: onRowClick, dragProps: dragPropsFor(row.original), actions: hasActions ? (_jsx(RowActions, { row: row.original, onEdit: onRowEdit, onDelete: onRowDelete, menu: rowMenu?.(row.original) })) : null, expanded: renderExpanded && expandedRowId != null && row.id === expandedRowId
749
+ 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,
750
+ // guardrails-ignore no-raw-overflow: dual-axis table scroll container โ€” sticky header pins to it; ScrollArea cannot own horizontal table scroll.
751
+ className: cn(bodyScrolls && 'min-h-0 flex-1 overflow-auto', scrollClassName), children: [selectionEnabled && (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: selectionEnabled
752
+ ? (_row, index) => {
753
+ const tsRow = rows[index];
754
+ return tsRow ? rowCheckbox(tsRow) : null;
755
+ }
756
+ : undefined, cardMinWidth: cardMinWidth, className: 'p-2' })) : (_jsxs(_Fragment, { children: [mobileCards && (_jsx("div", { "data-cb-table-cards": '', className: 'flex flex-col gap-3 @xl:hidden', children: rows.map((row) => (_jsx(MobileRowCard, { row: row, columns: cardColumns, className: rowClassName?.(row.original), onRowClick: onRowClick, dragProps: mergeRowProps(dragPropsFor(row.original), getRowProps?.(row.original, windowStart + row.index)), select: selectionEnabled ? rowCheckbox(row) : undefined, actions: hasActions ? (_jsx(RowActions, { row: row.original, onEdit: onRowEdit, onDelete: onRowDelete, menu: rowMenu?.(row.original) })) : null, expanded: renderExpanded && expandedRowId != null && row.id === expandedRowId
470
757
  ? renderExpanded(row.original)
471
758
  : null }, row.id))) })), mobileCards && (_jsx("div", { "data-cb-table-list": '', className: 'hidden @xl:block @3xl:hidden', children: (() => {
472
759
  const [title, ...rest] = cardColumns;
@@ -488,49 +775,67 @@ export function DataTable({ columns, data, getRowId, onRowClick, onRowEdit, onRo
488
775
  // absent (the header) and every preceding lane drifts ~32px off
489
776
  // its label โ€” the v4 judge's tablet misalignment finding.
490
777
  const template = [
778
+ ...(selectionEnabled ? ['44px'] : []),
491
779
  'minmax(0,1fr)',
492
780
  ...rest.map(trackOf),
493
781
  ...(hasActions ? ['32px'] : []),
494
782
  ].join(' ');
495
- 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: [_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] }));
783
+ 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: [selectionEnabled ? _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] }));
496
784
  const body = rows.map((row) => {
497
785
  const isExpanded = renderExpanded != null && expandedRowId != null && row.id === expandedRowId;
498
- return (_jsxs(Fragment, { children: [_jsxs("div", { ...dragPropsFor(row.original), 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)), style: { gridTemplateColumns: template }, children: [_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("div", { className: 'rounded-lg border border-border bg-card p-3 shadow-raised', children: renderExpanded(row.original) }) })) : null] }, row.id));
786
+ return (_jsxs(Fragment, { children: [_jsxs("div", { ...mergeRowProps({
787
+ ...dragPropsFor(row.original),
788
+ 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)),
789
+ style: { gridTemplateColumns: template },
790
+ }, getRowProps?.(row.original, windowStart + row.index)), children: [selectionEnabled ? (_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("div", { className: 'rounded-lg border border-border bg-card p-3 shadow-raised', children: renderExpanded(row.original) }) })) : null] }, row.id));
499
791
  });
500
792
  return (_jsxs(_Fragment, { children: [header, _jsx("div", { className: 'divide-y divide-border', children: body })] }));
501
793
  })() })), _jsx("div", { ref: bodyScrolls ? undefined : gridFade.ref, onScroll: bodyScrolls ? undefined : gridFade.onScroll, style: bodyScrolls ? undefined : gridFade.maskStyle,
502
794
  // 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).
503
- 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: [hg.headers.map((h, headerIndex) => {
504
- const sorted = h.column.getIsSorted();
505
- const meta = h.column.columnDef.meta;
506
- const width = meta?.width ?? h.column.columnDef.size;
507
- const align = meta?.align;
508
- const frozen = stickyFirstColumn === true && headerIndex === 0;
509
- return (_jsx("th", { scope: 'col', "aria-sort": sorted === 'asc'
510
- ? 'ascending'
511
- : sorted === 'desc'
512
- ? 'descending'
513
- : undefined, style: width != null || meta?.minWidth != null
514
- ? { width, minWidth: meta?.minWidth }
515
- : undefined, className: cn(
516
- // Metadata treatment: headers are chrome, not data โ€” small,
517
- // bold, uppercase, tracked, muted (STANDARDS ยง5c) โ€” via the
518
- // explicit secondary token, never an opacity fade (the
519
- // design manual bans opacity-blended text). The border
520
- // rides the <th> (not the <tr>) so it pins with stickyHeader.
521
- 'border-b border-border px-4 text-caption font-bold uppercase tracking-wider text-muted-foreground', cellPadY, align === 'right' ? 'text-right' : 'text-left',
522
- // Sticky pins per-<th> (a <tr> can't stick); needs its own
523
- // near-opaque bg so rows scrolling beneath never bleed through.
524
- stickyHeader &&
525
- 'sticky top-0 z-10 bg-card/95 backdrop-blur-sm',
526
- // The corner cell must outrank BOTH pinned axes, or the
527
- // next header slides over the frozen one. It is opaque
528
- // for the same reason the body cells are.
529
- frozen &&
530
- 'sticky left-0 bg-card shadow-[1px_0_0_0_var(--color-border)]', frozen && (stickyHeader ? 'z-30' : 'z-20')), children: h.isPlaceholder
531
- ? null
532
- : flexRender(h.column.columnDef.header, h.getContext()) }, h.id));
533
- }), 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) => {
795
+ 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: [(() => {
796
+ // `ColDef.leading` columns sit BEFORE the checkbox column; see its doc.
797
+ const renderHeader = (h, headerIndex) => {
798
+ const sorted = h.column.getIsSorted();
799
+ const meta = h.column.columnDef.meta;
800
+ const width = meta?.width ?? h.column.columnDef.size;
801
+ const align = meta?.align;
802
+ const frozen = stickyFirstColumn === true && headerIndex === 0;
803
+ return (_jsx("th", { scope: 'col', "aria-sort": sorted === 'asc'
804
+ ? 'ascending'
805
+ : sorted === 'desc'
806
+ ? 'descending'
807
+ : undefined, style: width != null || meta?.minWidth != null
808
+ ? { width, minWidth: meta?.minWidth }
809
+ : undefined, className: cn(
810
+ // Metadata treatment: headers are chrome, not data โ€” small,
811
+ // bold, uppercase, tracked, muted (STANDARDS ยง5c) โ€” via the
812
+ // explicit secondary token, never an opacity fade (the
813
+ // design manual bans opacity-blended text). The border
814
+ // rides the <th> (not the <tr>) so it pins with stickyHeader.
815
+ 'border-b border-border px-4 text-caption font-bold uppercase tracking-wider text-muted-foreground', cellPadY, align === 'right' ? 'text-right' : 'text-left',
816
+ // Sticky pins per-<th> (a <tr> can't stick); needs its own
817
+ // near-opaque bg so rows scrolling beneath never bleed through.
818
+ stickyHeader &&
819
+ 'sticky top-0 z-10 bg-card/95 backdrop-blur-sm',
820
+ // The corner cell must outrank BOTH pinned axes, or the
821
+ // next header slides over the frozen one. It is opaque
822
+ // for the same reason the body cells are.
823
+ frozen &&
824
+ (selectionEnabled
825
+ ? 'sticky left-11 bg-card shadow-[1px_0_0_0_var(--color-border)]'
826
+ : '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
827
+ ? null
828
+ : flexRender(h.column.columnDef.header, h.getContext()) }, h.id));
829
+ };
830
+ const isLeading = (h) => h.column.columnDef.meta?.leading === true;
831
+ return (_jsxs(_Fragment, { children: [hg.headers.filter(isLeading).map((h) => renderHeader(h, -1)), selectionEnabled ? (_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)] }));
832
+ })(), 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) => {
833
+ if (row === null) {
834
+ // A row the server window has not loaded yet: the estimated height, so
835
+ // the spacer arithmetic holds, and a skeleton line rather than a gap.
836
+ const index = virtualRows[sliceIndex].index;
837
+ return (_jsx("tr", { "data-index": index, "data-cb-placeholder": '', "aria-hidden": true, className: cn('border-b border-border', index % 2 === 1 && 'bg-muted/50'), style: { height: estimateRowHeight }, children: _jsx("td", { colSpan: visibleColumnCount, className: 'px-3', children: _jsx(Skeleton, { className: 'h-3', style: { width: '40%' } }) }) }, `cb-placeholder-${index}`));
838
+ }
534
839
  /* ๐Ÿ”ด The ZEBRA index is the row's ABSOLUTE position, not its
535
840
  position in the rendered slice. Off by that, the stripes
536
841
  would repaint themselves every time the window moved โ€” the
@@ -538,53 +843,78 @@ export function DataTable({ columns, data, getRowId, onRowClick, onRowEdit, onRo
538
843
  fault rather than as a guide. */
539
844
  const rowIndex = virtualEnabled ? virtualRows[sliceIndex].index : sliceIndex;
540
845
  const isExpanded = renderExpanded != null && expandedRowId != null && row.id === expandedRowId;
541
- return (_jsxs(Fragment, { children: [_jsxs("tr", { ...(virtualEnabled
542
- ? { ref: rowVirtualizer.measureElement, 'data-index': rowIndex }
543
- : {}), "data-expanded": isExpanded || undefined, ...dragPropsFor(row.original), className: cn(getRowDrag && 'cursor-grab active:cursor-grabbing',
544
- // Always-on hover wash: wide rows need a trace line for the
545
- // eye even when the row itself isn't clickable. Zebra fill on
546
- // every second row gives the eye a guide path across wide
547
- // columns (design manual ยง4 zebra-vs-outline rule) โ€” indexed
548
- // off the DATA row, not DOM position, so an expansion row
549
- // never flips the rhythm.
550
- 'border-b border-border transition-colors last:border-0 hover:bg-muted', rowIndex % 2 === 1 && 'bg-muted/50', rowClassName?.(row.original), isExpanded && 'border-0 bg-muted/30', onRowClick &&
551
- 'cursor-pointer hover:bg-muted focus-visible:bg-muted focus-visible:outline-none'), tabIndex: onRowClick ? 0 : undefined, onClick: onRowClick ? () => onRowClick(row.original) : undefined, onKeyDown: onRowClick
552
- ? (e) => {
553
- if (e.key === 'Enter')
554
- onRowClick(row.original);
555
- }
556
- : undefined, children: [row.getVisibleCells().map((cell, cellIndex) => {
557
- const frozen = stickyFirstColumn === true && cellIndex === 0;
558
- return (_jsx("td", { style: frozen
559
- ? {
560
- // The zebra step, made OPAQUE โ€” see the prop doc.
561
- // A custom property rather than `background:`
562
- // directly, so the value survives any CSS parser
563
- // that has not learned `color-mix` (happy-dom) and
564
- // the gate can read it back.
565
- '--cb-frozen-bg': rowIndex % 2 === 1
566
- ? 'color-mix(in srgb, var(--color-muted) 50%, var(--color-card))'
567
- : 'var(--color-card)',
568
- }
569
- : undefined, className: cn('px-4 align-middle', cellPadY, frozen &&
570
- 'sticky left-0 z-10 bg-[var(--cb-frozen-bg)] shadow-[1px_0_0_0_var(--color-border)]',
571
- // A rigid track cannot widen for its content, so an
572
- // unbreakable token (a task id, a path, a hash) would
573
- // otherwise run straight across the next column's value.
574
- // Break inside the word instead โ€” the row grows taller,
575
- // which is recoverable; overlapping text is not. Not
576
- // `overflow: hidden`: a cell may host a menu/tooltip that
577
- // has to escape its bounds.
578
- fixedLayout && '[overflow-wrap:anywhere]', cell.column.columnDef.meta
579
- ?.align === 'right' && 'text-right'), children: flexRender(cell.column.columnDef.cell, cell.getContext()) }, cell.id));
580
- }), 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) => {
846
+ return (_jsxs(Fragment, { children: [_jsxs("tr", {
847
+ // ๐Ÿ”ด The virtualizer reads its real height back off this node, and
848
+ // `data-index` is how it knows WHICH row it measured โ€” without it
849
+ // every measurement lands on index 0 and the spacer never grows.
850
+ // (The ref stays OUT of the merge: `getRowProps` cannot take it.)
851
+ ref: virtualEnabled && fixedRowHeight === null ? rowVirtualizer.measureElement : undefined, ...mergeRowProps({
852
+ ...(virtualEnabled ? { 'data-index': rowIndex } : {}),
853
+ style: fixedRowHeight === null ? undefined : { height: fixedRowHeight },
854
+ 'data-expanded': isExpanded || undefined,
855
+ ...dragPropsFor(row.original),
856
+ className: cn(getRowDrag && 'cursor-grab active:cursor-grabbing',
857
+ // Always-on hover wash: wide rows need a trace line for the
858
+ // eye even when the row itself isn't clickable. Zebra fill on
859
+ // every second row gives the eye a guide path across wide
860
+ // columns (design manual ยง4 zebra-vs-outline rule) โ€” indexed
861
+ // off the DATA row, not DOM position, so an expansion row
862
+ // never flips the rhythm.
863
+ 'border-b border-border transition-colors last:border-0 hover:bg-muted', rowIndex % 2 === 1 && 'bg-muted/50', rowClassName?.(row.original), isExpanded && 'border-0 bg-muted/30', onRowClick &&
864
+ 'cursor-pointer hover:bg-muted focus-visible:bg-muted focus-visible:outline-none'),
865
+ tabIndex: onRowClick ? 0 : undefined,
866
+ onClick: onRowClick ? (event) => onRowClick(row.original, event) : undefined,
867
+ onKeyDown: onRowClick
868
+ ? (e) => {
869
+ if (e.key === 'Enter')
870
+ onRowClick(row.original);
871
+ }
872
+ : undefined,
873
+ }, getRowProps?.(row.original, rowIndex)), children: [(() => {
874
+ const cells = row.getVisibleCells();
875
+ const isLeading = (cell) => cell.column.columnDef.meta?.leading === true;
876
+ const renderCell = (cell, cellIndex) => {
877
+ const frozen = stickyFirstColumn === true && cellIndex === 0;
878
+ return (_jsx("td", { style: frozen
879
+ ? {
880
+ // The zebra step, made OPAQUE โ€” see the prop doc.
881
+ // A custom property rather than `background:`
882
+ // directly, so the value survives any CSS parser
883
+ // that has not learned `color-mix` (happy-dom) and
884
+ // the gate can read it back.
885
+ '--cb-frozen-bg': rowIndex % 2 === 1
886
+ ? 'color-mix(in srgb, var(--color-muted) 50%, var(--color-card))'
887
+ : 'var(--color-card)',
888
+ }
889
+ : undefined, className: cn('px-4 align-middle', cellPadY, frozen &&
890
+ (selectionEnabled
891
+ ? 'sticky left-11 z-10 bg-[var(--cb-frozen-bg)] shadow-[1px_0_0_0_var(--color-border)]'
892
+ : 'sticky left-0 z-10 bg-[var(--cb-frozen-bg)] shadow-[1px_0_0_0_var(--color-border)]'),
893
+ // A rigid track cannot widen for its content, so an
894
+ // unbreakable token (a task id, a path, a hash) would
895
+ // otherwise run straight across the next column's value.
896
+ // Break inside the word instead โ€” the row grows taller,
897
+ // which is recoverable; overlapping text is not. Not
898
+ // `overflow: hidden`: a cell may host a menu/tooltip that
899
+ // has to escape its bounds.
900
+ fixedLayout && '[overflow-wrap:anywhere]', cell.column.columnDef.meta?.align === 'right' && 'text-right', cell.column.columnDef.meta?.hideBelow &&
901
+ HIDE_BELOW_CLASS[cell.column.columnDef.meta.hideBelow]), children: flexRender(cell.column.columnDef.cell, cell.getContext()) }, cell.id));
902
+ };
903
+ return (_jsxs(_Fragment, { children: [cells.filter(isLeading).map((cell) => renderCell(cell, -1)), selectionEnabled ? (_jsx("td", { ...selectCellProps(row), style: stickyFirstColumn
904
+ ? {
905
+ '--cb-frozen-bg': rowIndex % 2 === 1
906
+ ? 'color-mix(in srgb, var(--color-muted) 50%, var(--color-card))'
907
+ : 'var(--color-card)',
908
+ }
909
+ : 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)] }));
910
+ })(), 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) => {
581
911
  if (el && scrolledExpansionRef.current !== row.id) {
582
912
  scrolledExpansionRef.current = row.id;
583
913
  // A row expanded near the fold must reveal its panel.
584
914
  el.scrollIntoView({ block: 'nearest' });
585
915
  }
586
916
  }, 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("div", { className: 'rounded-lg border border-border bg-card p-3 shadow-raised', children: renderExpanded(row.original) }) }) }) })) : null] }, row.id));
587
- }), 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(TablePagination, { table: table, pageSizeOptions: pageSizeOptions })) : null] }));
917
+ }), 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] }));
588
918
  }
589
919
  /**
590
920
  * One row rendered as a phone card: first visible column = the title zone (unlabeled,
@@ -593,7 +923,7 @@ export function DataTable({ columns, data, getRowId, onRowClick, onRowEdit, onRo
593
923
  * anchored bottom-right inside the card bounds. Values render through the SAME
594
924
  * {@link CellRenderer} registry as the grid, so a custom cell shows identically here.
595
925
  */
596
- function MobileRowCard({ row, columns, className, onRowClick, dragProps, actions, expanded, }) {
926
+ function MobileRowCard({ row, columns, className, onRowClick, dragProps, select, actions, expanded, }) {
597
927
  const visible = columns.filter((c) => !c.hidden);
598
928
  const [title, ...rest] = visible;
599
929
  const footer = rest.filter((c) => c.cardFooter || c.label === '');
@@ -606,6 +936,7 @@ function MobileRowCard({ row, columns, className, onRowClick, dragProps, actions
606
936
  // glance pairs identity with state; the rest of the inline strip flows below.
607
937
  const [titleSideCol, ...restInline] = inline;
608
938
  const body = (_jsxs("div", { className: cn('flex flex-col gap-2', actions && 'pr-10'), children: [title ? (_jsxs("div", { className: 'flex items-start justify-between gap-2 text-sm', children: [_jsx("div", { className: 'min-w-0 flex-1', children: _jsx(CellRenderer, { col: title, value: row.original[title.key], row: row.original }) }), titleSideCol ? (_jsx("span", { className: 'shrink-0', children: _jsx(CellRenderer, { col: titleSideCol, value: row.original[titleSideCol.key], row: row.original }) })) : null] })) : null, restInline.length > 0 && (_jsx("div", { className: 'flex flex-wrap items-center gap-2', children: restInline.map((col) => (_jsx(CellRenderer, { col: col, value: row.original[col.key], row: row.original }, col.key))) })), detail.length > 0 && (_jsx("dl", { className: 'flex flex-col gap-1.5', children: detail.map((col) => (_jsxs("div", { className: 'flex items-baseline justify-between gap-3', children: [_jsx("dt", { className: 'cb-meta-label shrink-0', children: col.label }), _jsx("dd", { className: 'min-w-0 text-right text-sm', children: _jsx(CellRenderer, { col: col, value: row.original[col.key], row: row.original }) })] }, col.key))) }))] }));
609
- return (_jsxs(Surface, { level: 'raised', pad: 'sm', radius: 'lg', ...dragProps, className: cn('relative isolate', dragProps?.draggable && 'cursor-grab', className), children: [actions ? _jsx("div", { className: 'absolute top-3 right-3 z-10', children: actions }) : null, onRowClick ? (_jsx(CardTapTarget, { onClick: () => onRowClick(row.original), className: '-m-1 rounded-md p-1 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring', children: body })) : (body), footer.length > 0 && (_jsx("div", { className: 'mt-3 flex items-center justify-end gap-2 border-t border-border/60 pt-3', children: footer.map((col) => (_jsx(CellRenderer, { col: col, value: row.original[col.key], row: row.original }, col.key))) })), expanded ? _jsx("div", { className: 'mt-4', children: expanded }) : null] }));
939
+ const main = onRowClick ? (_jsx(CardTapTarget, { onClick: () => onRowClick(row.original), className: '-m-1 rounded-md p-1 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring', children: body })) : (body);
940
+ return (_jsxs(Surface, { level: 'raised', pad: 'sm', radius: 'lg', ...dragProps, className: cn('relative isolate', dragProps?.draggable && 'cursor-grab', className, dragProps?.className), children: [actions ? _jsx("div", { className: 'absolute top-3 right-3 z-10', children: actions }) : null, select == null ? main : (_jsxs("div", { className: 'flex items-start gap-1', children: [_jsx("div", { className: '-mt-2 -ml-2 shrink-0', children: select }), _jsx("div", { className: 'min-w-0 flex-1', children: main })] })), footer.length > 0 && (_jsx("div", { className: 'mt-3 flex items-center justify-end gap-2 border-t border-border/60 pt-3', children: footer.map((col) => (_jsx(CellRenderer, { col: col, value: row.original[col.key], row: row.original }, col.key))) })), expanded ? _jsx("div", { className: 'mt-4', children: expanded }) : null] }));
610
941
  }
611
942
  //# sourceMappingURL=DataTable.js.map