@eifi1/ui-kit 0.48.0 → 0.48.2

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 (47) hide show
  1. package/README.md +4 -3
  2. package/dist/components/data-table-fit.d.ts +8 -1
  3. package/dist/components/data-table-fit.js +7 -1
  4. package/dist/components/data-table-fit.js.map +1 -1
  5. package/dist/components/data-table-groups.d.ts +3 -1
  6. package/dist/components/data-table-groups.js +11 -5
  7. package/dist/components/data-table-groups.js.map +1 -1
  8. package/dist/components/data-table-head.d.ts +8 -1
  9. package/dist/components/data-table-head.js +11 -3
  10. package/dist/components/data-table-head.js.map +1 -1
  11. package/dist/components/data-table-leading-row.d.ts +3 -1
  12. package/dist/components/data-table-leading-row.js +2 -1
  13. package/dist/components/data-table-leading-row.js.map +1 -1
  14. package/dist/components/data-table-row-actions.js +2 -2
  15. package/dist/components/data-table-row-actions.js.map +1 -1
  16. package/dist/components/data-table-row.d.ts +84 -0
  17. package/dist/components/data-table-row.js +261 -0
  18. package/dist/components/data-table-row.js.map +1 -0
  19. package/dist/components/data-table-selection.d.ts +3 -0
  20. package/dist/components/data-table-selection.js +12 -1
  21. package/dist/components/data-table-selection.js.map +1 -1
  22. package/dist/components/data-table-virtual.d.ts +98 -0
  23. package/dist/components/data-table-virtual.js +369 -0
  24. package/dist/components/data-table-virtual.js.map +1 -0
  25. package/dist/components/data-table.d.ts +58 -2
  26. package/dist/components/data-table.js +227 -230
  27. package/dist/components/data-table.js.map +1 -1
  28. package/dist/data-table.d.ts +1 -0
  29. package/dist/data-table.js.map +1 -1
  30. package/dist/eslint/index.js +1 -1
  31. package/dist/hooks/use-anchored-panel.js +2 -1
  32. package/dist/hooks/use-anchored-panel.js.map +1 -1
  33. package/dist/shell/app-shell.js +7 -1
  34. package/dist/shell/app-shell.js.map +1 -1
  35. package/package.json +1 -1
  36. package/src/components/data-table-fit.ts +17 -2
  37. package/src/components/data-table-groups.tsx +15 -4
  38. package/src/components/data-table-head.tsx +25 -2
  39. package/src/components/data-table-leading-row.tsx +4 -1
  40. package/src/components/data-table-row-actions.tsx +3 -2
  41. package/src/components/data-table-row.tsx +483 -0
  42. package/src/components/data-table-selection.ts +17 -1
  43. package/src/components/data-table-virtual.ts +547 -0
  44. package/src/components/data-table.tsx +326 -327
  45. package/src/data-table.ts +2 -0
  46. package/src/hooks/use-anchored-panel.ts +9 -1
  47. package/src/shell/app-shell.tsx +7 -1
@@ -1,23 +1,14 @@
1
1
  "use client";
2
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
- import { createElement } from "react";
4
3
  import { Fragment as Fragment2, useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
5
4
  import { Card } from "./card.js";
6
5
  import { Spinner } from "./spinner.js";
7
6
  import { isTableOwnColumn, useRowActionColumn } from "./data-table-row-actions.js";
8
7
  import { useColumnResize } from "./data-table-resize.js";
9
8
  import { useRangeSelection } from "./data-table-selection.js";
10
- import {
11
- RowLink,
12
- cleanAttrs,
13
- columnLabel,
14
- fromOutside,
15
- fromOwnControl,
16
- logicalAlign,
17
- resolveDataTableLabels,
18
- splitCellProps
19
- } from "./data-table-shared.js";
20
- import { MobileFilters, MobileRow, MobileSelectBar, MobileSort, StackedFields } from "./data-table-mobile.js";
9
+ import { columnLabel, logicalAlign, resolveDataTableLabels } from "./data-table-shared.js";
10
+ import { MobileFilters, MobileSelectBar, MobileSort, StackedFields } from "./data-table-mobile.js";
11
+ import { DataTableMobileRow, DataTableRow } from "./data-table-row.js";
21
12
  import { DataTableHead } from "./data-table-head.js";
22
13
  import { DataTableLeadingRow } from "./data-table-leading-row.js";
23
14
  import {
@@ -27,6 +18,7 @@ import {
27
18
  groupRows,
28
19
  useGroupBandTops
29
20
  } from "./data-table-groups.js";
21
+ import { VIRTUAL_SELECT_COL, rowNavTarget, useRowWindow } from "./data-table-virtual.js";
30
22
  import { ColumnSettingsPanel } from "./data-table-column-settings.js";
31
23
  import { useLargeText } from "../hooks/use-large-text.js";
32
24
  import { cn } from "../lib/cn.js";
@@ -48,21 +40,11 @@ const DEFAULT_DATA_TABLE_SORT_LABELS = {
48
40
  sortDescending: DEFAULT_DATA_TABLE_LABELS.sortDescending
49
41
  };
50
42
  const flipDir = (d) => d === "asc" ? "desc" : "asc";
51
- function moveRowFocus(from, key) {
52
- if (!(key in ROW_NAV_KEYS)) return false;
53
- const table = from.closest("table");
54
- if (!table) return false;
55
- const rows = Array.from(table.tBodies).flatMap(
56
- (body) => Array.from(body.children).filter(
57
- (el) => el instanceof HTMLElement && el.hasAttribute("data-row-nav")
58
- )
59
- );
60
- const i = rows.indexOf(from);
61
- const target = key === "ArrowDown" ? rows[i + 1] : key === "ArrowUp" ? rows[i - 1] : key === "Home" ? rows[0] : key === "End" ? rows[rows.length - 1] : void 0;
62
- target?.focus();
63
- return true;
64
- }
65
- const ROW_NAV_KEYS = { ArrowDown: 1, ArrowUp: 1, Home: 1, End: 1 };
43
+ const NO_BANDS = [];
44
+ const BAND_1 = [1];
45
+ const BAND_2 = [2];
46
+ const BAND_1_2 = [1, 2];
47
+ const NO_VIRTUAL_OPTIONS = {};
66
48
  import { DEFAULT_PERSIST_PREFIX, useTableState } from "./use-table-state.js";
67
49
  import { useMobileReveal } from "./use-mobile-reveal.js";
68
50
  import { GROW_CELL_CLASS, GROW_PEER_CELL_CLASS, useColumnFit } from "./data-table-fit.js";
@@ -84,6 +66,7 @@ function DataTable({
84
66
  fillHeight = false,
85
67
  maxBodyHeight = "calc(100dvh - 12rem)",
86
68
  serverPagination,
69
+ serverRows: serverRowsProp,
87
70
  filters: filtersProp,
88
71
  onFiltersChange,
89
72
  sorts: sortsProp,
@@ -108,6 +91,7 @@ function DataTable({
108
91
  leadingRowSticky = false,
109
92
  groupBy,
110
93
  loadMore,
94
+ virtualize = false,
111
95
  density = "comfortable",
112
96
  sortCycle = "tri",
113
97
  chrome = "full",
@@ -129,6 +113,7 @@ function DataTable({
129
113
  const resizable = resizableProp ?? !minimal;
130
114
  const multiSort = multiSortProp ?? !minimal;
131
115
  const isServer = !!serverPagination;
116
+ const serverRows = isServer || (serverRowsProp ?? !!loadMore);
132
117
  const isLoading = !!serverPagination?.isLoading;
133
118
  const labelOverrides = useKitLabelOverrides("dataTable");
134
119
  const providerStorageKeyPrefix = useKitTableStorageKeyPrefix();
@@ -210,7 +195,7 @@ function DataTable({
210
195
  return map;
211
196
  }, [columnSig, selectOptionsSig, rows]);
212
197
  const filtered = useMemo(() => {
213
- if (isServer) return rows;
198
+ if (serverRows) return rows;
214
199
  let result = rows;
215
200
  for (const col of columns) {
216
201
  const state = filters[col.key];
@@ -218,9 +203,9 @@ function DataTable({
218
203
  result = result.filter((row) => rowMatches(col, row, state, locale));
219
204
  }
220
205
  return result;
221
- }, [rows, columnSig, filters, isServer, locale]);
206
+ }, [rows, columnSig, filters, serverRows, locale]);
222
207
  const sorted = useMemo(() => {
223
- if (isServer) return filtered;
208
+ if (serverRows) return filtered;
224
209
  const chain = sorts.map((s) => {
225
210
  const col = columns.find((c) => c.key === s.key);
226
211
  return col?.sortBy ? { sortBy: col.sortBy, dir: s.dir } : null;
@@ -240,8 +225,8 @@ function DataTable({
240
225
  return 0;
241
226
  });
242
227
  return copy;
243
- }, [filtered, sorts, columnSig, isServer]);
244
- const unpaged = !isServer && (!paginated || !!loadMore);
228
+ }, [filtered, sorts, columnSig, serverRows]);
229
+ const unpaged = !isServer && (!paginated || !!loadMore || serverRows);
245
230
  const effectivePageSize = isServer ? serverPagination.pageSize : unpaged || pageSize === Infinity ? sorted.length || 1 : pageSize;
246
231
  const paginationTotal = isServer ? serverPagination.total : sorted.length;
247
232
  const paginationPageSize = isServer ? serverPagination.pageSize : pageSize;
@@ -289,7 +274,7 @@ function DataTable({
289
274
  setPage(0);
290
275
  };
291
276
  const clearAllFilters = () => {
292
- const keys = columns.filter((col) => !isServer || onFiltersChange ? resolveFilter(col) : null).map((col) => col.key);
277
+ const keys = columns.filter((col) => !serverRows || onFiltersChange ? resolveFilter(col) : null).map((col) => col.key);
293
278
  if (!keys.length) return;
294
279
  if (onFiltersChange) {
295
280
  const rest = { ...filters };
@@ -309,20 +294,23 @@ function DataTable({
309
294
  useEffect(() => {
310
295
  if (announcedFilterKey.current === filterKey) return;
311
296
  announcedFilterKey.current = filterKey;
312
- if (isServer) return;
297
+ if (serverRows) return;
313
298
  announce(labels.filterResults(filtered.length, rows.length));
314
- }, [filterKey, filtered.length, rows.length, isServer, announce, labels]);
299
+ }, [filterKey, filtered.length, rows.length, serverRows, announce, labels]);
315
300
  const goToPage = (p) => {
316
301
  if (isServer) serverPagination.onPageChange(p);
317
302
  else setPage(p);
318
303
  announce(labels.pageChanged(p + 1, totalPages));
319
304
  };
305
+ const large = useLargeText();
320
306
  const { autoHidden, keep: keepColumn } = useColumnFit({
321
307
  columns,
322
308
  hiddenCols,
323
309
  headRefs,
324
310
  scroller: () => desktopScroller.current,
325
- enabled: !usePhoneLayout()
311
+ enabled: !usePhoneLayout(),
312
+ // Measured only when one of these changed (0.48.2): not on a ticked box.
313
+ content: [slice, columns, hiddenCols, widths, density, large]
326
314
  });
327
315
  const visibleColumns = useMemo(
328
316
  () => columns.filter((c) => !hiddenCols.has(c.key) && !autoHidden.has(c.key)),
@@ -362,11 +350,13 @@ function DataTable({
362
350
  return () => ro.disconnect();
363
351
  }, [measuresVisibleWidth]);
364
352
  const edgeFadeProps = useEdgeFade(desktopScroller, edgeFade && isMdUp);
365
- const mobileColumns = visibleColumns.filter((c) => !c.mobileHidden);
353
+ const mobileColumns = useMemo(() => visibleColumns.filter((c) => !c.mobileHidden), [visibleColumns]);
366
354
  const mobilePrimaryCol = mobileColumns.find((c) => c.mobilePrimary) ?? mobileColumns[0] ?? null;
367
- const mobileSecondaryColumns = mobileColumns.filter((c) => c !== mobilePrimaryCol);
355
+ const mobileSecondaryColumns = useMemo(
356
+ () => mobileColumns.filter((c) => c !== mobilePrimaryCol),
357
+ [mobileColumns, mobilePrimaryCol]
358
+ );
368
359
  const mobileDetailColumns = mobileDetailsInRow ? visibleColumns.filter((c) => c.mobileHidden) : [];
369
- const large = useLargeText();
370
360
  const opensRow = !!expandedRow || mobileDetailColumns.length > 0;
371
361
  const openedRowContent = (row) => {
372
362
  const detail = expandedRow?.(row) ?? null;
@@ -415,7 +405,7 @@ function DataTable({
415
405
  if (!anySelected) setPhoneSelectMode(false);
416
406
  }
417
407
  const phoneSelecting = !!selection && !isMdUp && (phoneSelectMode || anySelected);
418
- const { selectionAnchor, rangeClick, canSelect, selectRange } = useRangeSelection({
408
+ const { canSelect, selectRange, setAnchor, markRangeClick, takeRangeClick } = useRangeSelection({
419
409
  selection,
420
410
  slice,
421
411
  rowKey
@@ -423,49 +413,73 @@ function DataTable({
423
413
  const rowsFocusable = !!onRowClick;
424
414
  const [focusRowKey, setFocusRowKey] = useState(null);
425
415
  const tabRowKey = focusRowKey !== null && slice.some((r) => rowKey(r) === focusRowKey) ? focusRowKey : slice.length ? rowKey(slice[0]) : null;
416
+ const virtualOn = !!virtualize && isMdUp && slice.length > 0;
417
+ const focusRowRef = useRef(void 0);
418
+ const latestRowCtx = {
419
+ moveFocus: virtualOn ? (from, key) => {
420
+ const target = rowNavTarget(from, key, slice.length);
421
+ if (target === null) return false;
422
+ focusRowRef.current?.(target);
423
+ return true;
424
+ } : void 0,
425
+ onRowClick,
426
+ onToggle: selection?.onToggle,
427
+ isSelected: (row) => !!selection?.isSelected(row),
428
+ canSelect,
429
+ selectRange,
430
+ setAnchor,
431
+ markRangeClick,
432
+ takeRangeClick,
433
+ setFocusRowKey,
434
+ onLongPress: (row) => {
435
+ setPhoneSelectMode(true);
436
+ setAnchor(rowKey(row));
437
+ if (selection && !selection.isSelected(row)) selection.onToggle(row, true);
438
+ }
439
+ };
440
+ const rowCtx = useRef(latestRowCtx);
441
+ useLayoutEffect(() => {
442
+ rowCtx.current = latestRowCtx;
443
+ });
426
444
  const mobileDialogRow = mobileExpandAsDialog && !isMdUp ? mobileSlice.find((r) => isExpanded?.(r)) : void 0;
427
445
  const mobileDialogContent = mobileDialogRow ? openedRowContent(mobileDialogRow) : null;
428
446
  const dialogOpen = !!(mobileDialogRow && mobileDialogContent);
429
447
  const closeDialog = () => {
430
448
  if (mobileDialogRow) onRowClick?.(mobileDialogRow);
431
449
  };
432
- const renderMobileRow = (row) => /* @__PURE__ */ jsx(
433
- MobileRow,
434
- {
435
- row,
436
- isExpanded,
437
- openedRowContent,
438
- opensRow,
439
- rowClassName,
440
- rowAttributes,
441
- onRowClick,
442
- rowHref,
443
- swipeActionsFor,
444
- mobileDir,
445
- mobileCardLinkable,
446
- mobileCard,
447
- mobilePrimaryCol,
448
- mobileSecondaryColumns,
449
- mobileExpandAsDialog,
450
- compact,
451
- large,
452
- select: selection ? {
453
- active: phoneSelecting,
450
+ const renderMobileRow = (row) => {
451
+ const key = rowKey(row);
452
+ const expanded = isExpanded?.(row) ?? false;
453
+ return /* @__PURE__ */ jsx(
454
+ DataTableMobileRow,
455
+ {
456
+ ctx: rowCtx,
457
+ rowKey: key,
458
+ row,
459
+ expanded,
460
+ expansion: expanded ? openedRowContent(row) : null,
461
+ opensRow,
462
+ className: rowClassName?.(row),
463
+ attrs: rowAttributes?.(row),
464
+ interactive: !!onRowClick,
465
+ href: mobileCardLinkable ? rowHref?.(row) : void 0,
466
+ swipeActionsFor,
467
+ mobileDir,
468
+ mobileCardLinkable,
469
+ mobileCard,
470
+ mobilePrimaryCol,
471
+ mobileSecondaryColumns,
472
+ mobileExpandAsDialog,
473
+ compact,
474
+ large,
475
+ selectionOn: !!selection,
476
+ selectActive: phoneSelecting,
454
477
  selectable: canSelect(row),
455
- selected: selection.isSelected(row),
456
- onToggle: (checked) => {
457
- selectionAnchor.current = rowKey(row);
458
- selection.onToggle(row, checked);
459
- },
460
- onLongPress: () => {
461
- setPhoneSelectMode(true);
462
- selectionAnchor.current = rowKey(row);
463
- if (!selection.isSelected(row)) selection.onToggle(row, true);
464
- }
465
- } : null
466
- },
467
- rowKey(row)
468
- );
478
+ selected: !!selection && selection.isSelected(row)
479
+ },
480
+ key
481
+ );
482
+ };
469
483
  const tableRef = useRef(null);
470
484
  const desktopGroups = isMdUp && levels.length > 0 ? groupRows(slice, levels) : null;
471
485
  const bandTops = useGroupBandTops(
@@ -473,6 +487,51 @@ function DataTable({
473
487
  !!desktopGroups,
474
488
  `${levels.length}:${leadingRowSticky && !!leadingRow}:${desktopGroups?.[0]?.key ?? ""}`
475
489
  );
490
+ const virtualGroups = [];
491
+ const virtualBands = [];
492
+ if (virtualOn) {
493
+ if (!desktopGroups) virtualGroups.push({ start: 0, end: slice.length, bands: NO_BANDS, first: 0 });
494
+ else
495
+ for (const g1 of desktopGroups) {
496
+ const first = virtualGroups.length;
497
+ if (!g1.children) {
498
+ virtualGroups.push({ start: g1.start, end: g1.start + g1.rows.length, bands: BAND_1, first });
499
+ virtualBands.push({ g1, g2: null });
500
+ continue;
501
+ }
502
+ g1.children.forEach((g2, j) => {
503
+ virtualGroups.push({ start: g2.start, end: g2.start + g2.rows.length, bands: j === 0 ? BAND_1_2 : BAND_2, first });
504
+ virtualBands.push({ g1, g2 });
505
+ });
506
+ }
507
+ }
508
+ const expandedIndices = [];
509
+ if (virtualOn && isExpanded && expandedRow) slice.forEach((r, i) => isExpanded(r) && expandedIndices.push(i));
510
+ const virtualColKeys = [...selection ? [VIRTUAL_SELECT_COL] : [], ...visibleColumns.map((c) => c.key)];
511
+ const virtualLockable = new Set(
512
+ virtualColKeys.filter((k) => !widths[k] && !visibleColumns.some((c) => c.key === k && c.grow))
513
+ );
514
+ const rowWindow = useRowWindow({
515
+ enabled: virtualOn,
516
+ rows: slice,
517
+ rowKey,
518
+ groups: virtualGroups,
519
+ expanded: expandedIndices,
520
+ pinnedKeys: [tabRowKey],
521
+ scrollerRef: desktopScroller,
522
+ tableRef,
523
+ options: typeof virtualize === "object" ? virtualize : NO_VIRTUAL_OPTIONS,
524
+ compact,
525
+ rowsBefore: 1 + (leadingRow ? 1 : 0),
526
+ rowsAfter: showFooter ? 1 : 0,
527
+ colKeys: virtualColKeys,
528
+ lockable: virtualLockable,
529
+ resetKey: `${Object.entries(widths).join(";")}|${density}|${large}|${slice.length}:${slice.length ? `${rowKey(slice[0])}:${rowKey(slice[slice.length - 1])}` : ""}`
530
+ });
531
+ const focusRowAt = rowWindow?.focusRow;
532
+ useLayoutEffect(() => {
533
+ focusRowRef.current = focusRowAt;
534
+ }, [focusRowAt]);
476
535
  const mobileLevel = typeof mobileGroupBy === "function" ? null : mobileGroupBy ?? null;
477
536
  const mobileKey = typeof mobileGroupBy === "function" ? mobileGroupBy : mobileGroupBy?.key;
478
537
  const mobileGroups = mobileKey ? mobileSlice.reduce((acc, row) => {
@@ -515,13 +574,13 @@ function DataTable({
515
574
  }
516
575
  ),
517
576
  !isMdUp && /* @__PURE__ */ jsxs("div", { ref: mobileRootRef, className: cn(fillHeight && "flex min-h-0 flex-1 flex-col"), children: [
518
- (!isServer || onFiltersChange) && /* @__PURE__ */ jsx(
577
+ (!serverRows || onFiltersChange) && /* @__PURE__ */ jsx(
519
578
  MobileFilters,
520
579
  {
521
580
  columns,
522
581
  filters,
523
582
  selectOptionsByKey,
524
- isServer,
583
+ isServer: serverRows,
525
584
  hasFilterCallback: !!onFiltersChange,
526
585
  onSetFilter: setFilterValue,
527
586
  onClearFilter: clearFilter,
@@ -533,7 +592,7 @@ function DataTable({
533
592
  mobileSort && /* @__PURE__ */ jsx(
534
593
  MobileSort,
535
594
  {
536
- columns: visibleColumns.filter((c) => (!isServer || !!onSortsChange) && !!c.sortBy),
595
+ columns: visibleColumns.filter((c) => (!serverRows || !!onSortsChange) && !!c.sortBy),
537
596
  sorts,
538
597
  sortCycle,
539
598
  compact,
@@ -702,7 +761,13 @@ function DataTable({
702
761
  "data-fit-grow": growDrawn ? "" : void 0,
703
762
  "aria-label": labels.table,
704
763
  "aria-busy": isLoading || void 0,
764
+ "aria-rowcount": rowWindow?.rowCount,
765
+ "data-virtualized": rowWindow ? "" : void 0,
705
766
  children: [
767
+ rowWindow && /* @__PURE__ */ jsx("colgroup", { children: virtualColKeys.map((key) => {
768
+ const w = rowWindow.colWidths?.[key];
769
+ return /* @__PURE__ */ jsx("col", { style: w !== void 0 ? { width: w } : void 0 }, key);
770
+ }) }),
706
771
  /* @__PURE__ */ jsx(
707
772
  DataTableHead,
708
773
  {
@@ -711,7 +776,7 @@ function DataTable({
711
776
  compact,
712
777
  labels,
713
778
  locale,
714
- isServer,
779
+ isServer: serverRows,
715
780
  hasSortCallback: !!onSortsChange,
716
781
  hasFilterCallback: !!onFiltersChange,
717
782
  sorts,
@@ -727,154 +792,58 @@ function DataTable({
727
792
  clearFilter,
728
793
  startResize,
729
794
  moveResize,
730
- finishResize
795
+ finishResize,
796
+ ariaRowIndex: rowWindow ? 1 : void 0,
797
+ lockedWidths: rowWindow?.colWidths,
798
+ selectionLockKey: VIRTUAL_SELECT_COL
731
799
  }
732
800
  ),
733
801
  (() => {
734
802
  const bodyClass = cn(isLoading && slice.length > 0 && "opacity-60 transition-opacity");
735
- const leading = leadingRow ? /* @__PURE__ */ jsx(DataTableLeadingRow, { colSpan: totalColSpan, sticky: leadingRowSticky, children: leadingRow }) : null;
803
+ const leading = leadingRow ? /* @__PURE__ */ jsx(
804
+ DataTableLeadingRow,
805
+ {
806
+ colSpan: totalColSpan,
807
+ sticky: leadingRowSticky,
808
+ ariaRowIndex: rowWindow ? 2 : void 0,
809
+ children: leadingRow
810
+ }
811
+ ) : null;
736
812
  const renderRow = (row, rowIndex) => {
737
813
  const expanded = isExpanded?.(row) ?? false;
738
- const expansion = expanded ? expandedRow?.(row) : null;
739
- const rowInteractive = !!onRowClick || !!selection;
740
- const href = rowHref?.(row);
741
814
  const key = rowKey(row);
742
- const detailId = `${detailIdBase}-${rowIndex}`;
743
- const disclosure = rowsFocusable && expandedRow ? /* @__PURE__ */ jsx(
744
- "button",
815
+ return /* @__PURE__ */ jsx(
816
+ DataTableRow,
745
817
  {
746
- type: "button",
747
- tabIndex: -1,
748
- className: "sr-only-fixed",
749
- "aria-label": labels.rowDetails,
750
- "aria-expanded": expanded,
751
- "aria-controls": expansion ? detailId : void 0,
752
- onClick: () => onRowClick(row)
753
- }
754
- ) : null;
755
- return /* @__PURE__ */ jsxs(Fragment2, { children: [
756
- /* @__PURE__ */ jsxs(
757
- "tr",
758
- {
759
- ...cleanAttrs(rowAttributes?.(row)),
760
- "data-row-nav": rowsFocusable ? "" : void 0,
761
- tabIndex: rowsFocusable ? key === tabRowKey ? 0 : -1 : void 0,
762
- onFocus: rowsFocusable ? (e) => {
763
- if (e.target === e.currentTarget) setFocusRowKey(key);
764
- } : void 0,
765
- onKeyDown: rowsFocusable ? (e) => {
766
- if (e.target !== e.currentTarget) return;
767
- if (e.altKey || e.ctrlKey || e.metaKey) return;
768
- if (e.key === "Enter" || e.key === " ") {
769
- e.preventDefault();
770
- onRowClick(row);
771
- return;
772
- }
773
- if (e.shiftKey) return;
774
- if (moveRowFocus(e.currentTarget, e.key)) e.preventDefault();
775
- } : void 0,
776
- className: cn(
777
- "border-t border-subtle",
778
- rowInteractive && "cursor-pointer hover:bg-hover",
779
- // The kit's focus frame width (§5, `--focus-ring-width`), drawn as an
780
- // OUTLINE rather than FOCUS_RING's box-shadow: a shadow on a `<tr>`
781
- // is not painted by every engine, an outline is.
782
- rowsFocusable && "focus-visible:bg-hover focus-visible:outline-focus focus-visible:-outline-offset-2 focus-visible:outline-brand",
783
- rowClassName?.(row)
784
- ),
785
- onClick: rowInteractive ? (e) => {
786
- if (fromOwnControl(e.target, e.currentTarget)) return;
787
- if (fromOutside(e.target, e.currentTarget)) return;
788
- if (selection && canSelect(row)) {
789
- if (e.shiftKey) {
790
- window.getSelection?.()?.removeAllRanges();
791
- if (selectRange(rowIndex)) return;
792
- selectionAnchor.current = rowKey(row);
793
- selection.onToggle(row, !selection.isSelected(row));
794
- return;
795
- }
796
- if (e.metaKey || e.ctrlKey) {
797
- selectionAnchor.current = rowKey(row);
798
- selection.onToggle(row, !selection.isSelected(row));
799
- return;
800
- }
801
- }
802
- onRowClick?.(row);
803
- } : void 0,
804
- children: [
805
- selection && /* @__PURE__ */ jsx(
806
- "td",
807
- {
808
- className: cn("align-top", compact ? "w-8 px-2 py-1" : "w-10 px-3 py-2"),
809
- onClick: (e) => e.stopPropagation(),
810
- children: canSelect(row) && /* @__PURE__ */ jsx(
811
- "input",
812
- {
813
- type: "checkbox",
814
- className: cn("accent-indigo-600", compact ? "size-3.5" : "size-4"),
815
- checked: selection.isSelected(row),
816
- onClick: (e) => {
817
- if (e.shiftKey && selectRange(rowIndex)) {
818
- e.preventDefault();
819
- rangeClick.current = e.nativeEvent;
820
- }
821
- },
822
- onChange: (e) => {
823
- if (rangeClick.current && e.nativeEvent === rangeClick.current) {
824
- rangeClick.current = null;
825
- return;
826
- }
827
- selectionAnchor.current = rowKey(row);
828
- selection.onToggle(row, e.target.checked);
829
- },
830
- "aria-label": labels.selectRow
831
- }
832
- )
833
- }
834
- ),
835
- visibleColumns.map((col, colIndex) => {
836
- const width = widths[col.key];
837
- const own = splitCellProps(col.cellProps?.(row));
838
- const pinned = width ? { width, minWidth: width, maxWidth: width } : void 0;
839
- return /* @__PURE__ */ createElement(
840
- "td",
841
- {
842
- ...own.attrs,
843
- key: col.key,
844
- "data-col": col.key,
845
- style: own.style || pinned ? { ...own.style, ...pinned } : void 0,
846
- className: cn(
847
- "align-top",
848
- compact ? "px-2 py-1" : "px-3 py-2",
849
- width && "overflow-hidden text-ellipsis",
850
- col.grow ? GROW_CELL_CLASS : growDrawn && GROW_PEER_CELL_CLASS,
851
- logicalAlign(own.className),
852
- logicalAlign(col.className)
853
- )
854
- },
855
- colIndex === 0 && disclosure,
856
- col === linkColumn && href ? (
857
- // No `onActivate`: the click is left to bubble to the
858
- // <tr> handler above, which already owns expand-vs-select.
859
- // Calling it here as well would toggle the row twice.
860
- /* @__PURE__ */ jsx(RowLink, { href, className: "block", "data-row-link": "", children: col.cell(row) })
861
- ) : col.cell(row)
862
- );
863
- })
864
- ]
865
- }
866
- ),
867
- expansion && /* @__PURE__ */ jsx("tr", { id: disclosure ? detailId : void 0, className: "bg-surface-2 border-t border-subtle", children: /* @__PURE__ */ jsx("td", { colSpan: totalColSpan, className: "p-0", children: /* @__PURE__ */ jsx("div", { className: "w-0 min-w-full", children: /* @__PURE__ */ jsx(
868
- "div",
869
- {
870
- className: cn(
871
- "sticky start-0 w-[var(--dt-visible-w,100%)] max-w-full",
872
- compact ? "px-2 py-2" : "px-3 py-3"
873
- ),
874
- children: expansion
875
- }
876
- ) }) }) })
877
- ] }, key);
818
+ row,
819
+ rowIndex,
820
+ rowKey: key,
821
+ ctx: rowCtx,
822
+ columns: visibleColumns,
823
+ widths,
824
+ growDrawn,
825
+ linkColumn,
826
+ href: rowHref?.(row),
827
+ selectionColumn: !!selection,
828
+ selectable: canSelect(row),
829
+ selected: !!selection && selection.isSelected(row),
830
+ expanded,
831
+ expansion: expanded ? expandedRow?.(row) : null,
832
+ disclosure: rowsFocusable && !!expandedRow,
833
+ detailId: `${detailIdBase}-${rowIndex}`,
834
+ rowsFocusable,
835
+ rowInteractive: !!onRowClick || !!selection,
836
+ tabbable: key === tabRowKey,
837
+ className: rowClassName?.(row),
838
+ attrs: rowAttributes?.(row),
839
+ compact,
840
+ totalColSpan,
841
+ rowDetailsLabel: labels.rowDetails,
842
+ selectRowLabel: labels.selectRow,
843
+ ariaRowIndex: rowWindow?.ariaRow(rowIndex)
844
+ },
845
+ key
846
+ );
878
847
  };
879
848
  const emptyRow = slice.length === 0 ? /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx(
880
849
  "td",
@@ -884,14 +853,7 @@ function DataTable({
884
853
  children: isLoading ? /* @__PURE__ */ jsx(LoadingText, { label: labels.loading }) : empty ?? labels.empty
885
854
  }
886
855
  ) }) : null;
887
- if (!desktopGroups) {
888
- return /* @__PURE__ */ jsxs("tbody", { className: bodyClass, children: [
889
- leading,
890
- slice.map(renderRow),
891
- emptyRow
892
- ] });
893
- }
894
- const band = (level, def, g) => /* @__PURE__ */ jsx(
856
+ const band = (level, def, g, ariaRowIndex) => /* @__PURE__ */ jsx(
895
857
  DataTableGroupBand,
896
858
  {
897
859
  level,
@@ -899,9 +861,44 @@ function DataTable({
899
861
  top: level === 1 ? bandTops[0] : bandTops[1],
900
862
  compact,
901
863
  label: def.label ? def.label(g.key, g.rows) : g.key,
902
- end: def.end?.(g.key, g.rows)
864
+ end: def.end?.(g.key, g.rows),
865
+ ariaRowIndex
903
866
  }
904
867
  );
868
+ if (rowWindow) {
869
+ const spacer = (key, height) => /* @__PURE__ */ jsx("tr", { "aria-hidden": "true", "data-vt-spacer": "", children: /* @__PURE__ */ jsx("td", { colSpan: totalColSpan, style: { height, padding: 0, border: 0 } }) }, key);
870
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
871
+ leading && /* @__PURE__ */ jsx("tbody", { className: bodyClass, children: leading }),
872
+ rowWindow.bodies.map((b) => {
873
+ if (b.kind === "spacer")
874
+ return /* @__PURE__ */ jsx("tbody", { "data-vt-body": "", "aria-hidden": "true", children: spacer("s", b.height) }, b.key);
875
+ const g = virtualGroups[b.group];
876
+ const vb = virtualBands[b.group];
877
+ return /* @__PURE__ */ jsxs(
878
+ "tbody",
879
+ {
880
+ "data-vt-body": "",
881
+ className: bodyClass,
882
+ children: [
883
+ vb && g.bands.includes(1) && band(1, levels[0], vb.g1, rowWindow.ariaBand(b.group, 1)),
884
+ vb?.g2 && band(2, levels[1], vb.g2, rowWindow.ariaBand(b.group, 2)),
885
+ b.items.map(
886
+ (it) => it.kind === "gap" ? spacer(it.key, it.height) : renderRow(slice[it.index], it.index)
887
+ )
888
+ ]
889
+ },
890
+ vb ? `vg:${vb.g1.key}:${vb.g2?.key ?? ""}:${g.start}` : "vg"
891
+ );
892
+ })
893
+ ] });
894
+ }
895
+ if (!desktopGroups) {
896
+ return /* @__PURE__ */ jsxs("tbody", { className: bodyClass, children: [
897
+ leading,
898
+ slice.map(renderRow),
899
+ emptyRow
900
+ ] });
901
+ }
905
902
  const rowsOf = (g) => g.rows.map((r, i) => renderRow(r, g.start + i));
906
903
  return /* @__PURE__ */ jsxs(Fragment, { children: [
907
904
  (leading || emptyRow) && /* @__PURE__ */ jsxs("tbody", { className: bodyClass, children: [
@@ -933,7 +930,7 @@ function DataTable({
933
930
  "font-semibold text-primary",
934
931
  isLoading && "opacity-60 transition-opacity"
935
932
  ),
936
- children: /* @__PURE__ */ jsxs("tr", { children: [
933
+ children: /* @__PURE__ */ jsxs("tr", { "aria-rowindex": rowWindow?.rowCount, children: [
937
934
  selection && /* @__PURE__ */ jsx(
938
935
  "td",
939
936
  {