@eifi1/ui-kit 0.48.1 → 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 +40 -1
  26. package/dist/components/data-table.js +213 -218
  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 +291 -314
  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";
@@ -109,6 +91,7 @@ function DataTable({
109
91
  leadingRowSticky = false,
110
92
  groupBy,
111
93
  loadMore,
94
+ virtualize = false,
112
95
  density = "comfortable",
113
96
  sortCycle = "tri",
114
97
  chrome = "full",
@@ -319,12 +302,15 @@ function DataTable({
319
302
  else setPage(p);
320
303
  announce(labels.pageChanged(p + 1, totalPages));
321
304
  };
305
+ const large = useLargeText();
322
306
  const { autoHidden, keep: keepColumn } = useColumnFit({
323
307
  columns,
324
308
  hiddenCols,
325
309
  headRefs,
326
310
  scroller: () => desktopScroller.current,
327
- 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]
328
314
  });
329
315
  const visibleColumns = useMemo(
330
316
  () => columns.filter((c) => !hiddenCols.has(c.key) && !autoHidden.has(c.key)),
@@ -364,11 +350,13 @@ function DataTable({
364
350
  return () => ro.disconnect();
365
351
  }, [measuresVisibleWidth]);
366
352
  const edgeFadeProps = useEdgeFade(desktopScroller, edgeFade && isMdUp);
367
- const mobileColumns = visibleColumns.filter((c) => !c.mobileHidden);
353
+ const mobileColumns = useMemo(() => visibleColumns.filter((c) => !c.mobileHidden), [visibleColumns]);
368
354
  const mobilePrimaryCol = mobileColumns.find((c) => c.mobilePrimary) ?? mobileColumns[0] ?? null;
369
- const mobileSecondaryColumns = mobileColumns.filter((c) => c !== mobilePrimaryCol);
355
+ const mobileSecondaryColumns = useMemo(
356
+ () => mobileColumns.filter((c) => c !== mobilePrimaryCol),
357
+ [mobileColumns, mobilePrimaryCol]
358
+ );
370
359
  const mobileDetailColumns = mobileDetailsInRow ? visibleColumns.filter((c) => c.mobileHidden) : [];
371
- const large = useLargeText();
372
360
  const opensRow = !!expandedRow || mobileDetailColumns.length > 0;
373
361
  const openedRowContent = (row) => {
374
362
  const detail = expandedRow?.(row) ?? null;
@@ -417,7 +405,7 @@ function DataTable({
417
405
  if (!anySelected) setPhoneSelectMode(false);
418
406
  }
419
407
  const phoneSelecting = !!selection && !isMdUp && (phoneSelectMode || anySelected);
420
- const { selectionAnchor, rangeClick, canSelect, selectRange } = useRangeSelection({
408
+ const { canSelect, selectRange, setAnchor, markRangeClick, takeRangeClick } = useRangeSelection({
421
409
  selection,
422
410
  slice,
423
411
  rowKey
@@ -425,49 +413,73 @@ function DataTable({
425
413
  const rowsFocusable = !!onRowClick;
426
414
  const [focusRowKey, setFocusRowKey] = useState(null);
427
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
+ });
428
444
  const mobileDialogRow = mobileExpandAsDialog && !isMdUp ? mobileSlice.find((r) => isExpanded?.(r)) : void 0;
429
445
  const mobileDialogContent = mobileDialogRow ? openedRowContent(mobileDialogRow) : null;
430
446
  const dialogOpen = !!(mobileDialogRow && mobileDialogContent);
431
447
  const closeDialog = () => {
432
448
  if (mobileDialogRow) onRowClick?.(mobileDialogRow);
433
449
  };
434
- const renderMobileRow = (row) => /* @__PURE__ */ jsx(
435
- MobileRow,
436
- {
437
- row,
438
- isExpanded,
439
- openedRowContent,
440
- opensRow,
441
- rowClassName,
442
- rowAttributes,
443
- onRowClick,
444
- rowHref,
445
- swipeActionsFor,
446
- mobileDir,
447
- mobileCardLinkable,
448
- mobileCard,
449
- mobilePrimaryCol,
450
- mobileSecondaryColumns,
451
- mobileExpandAsDialog,
452
- compact,
453
- large,
454
- select: selection ? {
455
- 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,
456
477
  selectable: canSelect(row),
457
- selected: selection.isSelected(row),
458
- onToggle: (checked) => {
459
- selectionAnchor.current = rowKey(row);
460
- selection.onToggle(row, checked);
461
- },
462
- onLongPress: () => {
463
- setPhoneSelectMode(true);
464
- selectionAnchor.current = rowKey(row);
465
- if (!selection.isSelected(row)) selection.onToggle(row, true);
466
- }
467
- } : null
468
- },
469
- rowKey(row)
470
- );
478
+ selected: !!selection && selection.isSelected(row)
479
+ },
480
+ key
481
+ );
482
+ };
471
483
  const tableRef = useRef(null);
472
484
  const desktopGroups = isMdUp && levels.length > 0 ? groupRows(slice, levels) : null;
473
485
  const bandTops = useGroupBandTops(
@@ -475,6 +487,51 @@ function DataTable({
475
487
  !!desktopGroups,
476
488
  `${levels.length}:${leadingRowSticky && !!leadingRow}:${desktopGroups?.[0]?.key ?? ""}`
477
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]);
478
535
  const mobileLevel = typeof mobileGroupBy === "function" ? null : mobileGroupBy ?? null;
479
536
  const mobileKey = typeof mobileGroupBy === "function" ? mobileGroupBy : mobileGroupBy?.key;
480
537
  const mobileGroups = mobileKey ? mobileSlice.reduce((acc, row) => {
@@ -704,7 +761,13 @@ function DataTable({
704
761
  "data-fit-grow": growDrawn ? "" : void 0,
705
762
  "aria-label": labels.table,
706
763
  "aria-busy": isLoading || void 0,
764
+ "aria-rowcount": rowWindow?.rowCount,
765
+ "data-virtualized": rowWindow ? "" : void 0,
707
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
+ }) }),
708
771
  /* @__PURE__ */ jsx(
709
772
  DataTableHead,
710
773
  {
@@ -729,154 +792,58 @@ function DataTable({
729
792
  clearFilter,
730
793
  startResize,
731
794
  moveResize,
732
- finishResize
795
+ finishResize,
796
+ ariaRowIndex: rowWindow ? 1 : void 0,
797
+ lockedWidths: rowWindow?.colWidths,
798
+ selectionLockKey: VIRTUAL_SELECT_COL
733
799
  }
734
800
  ),
735
801
  (() => {
736
802
  const bodyClass = cn(isLoading && slice.length > 0 && "opacity-60 transition-opacity");
737
- 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;
738
812
  const renderRow = (row, rowIndex) => {
739
813
  const expanded = isExpanded?.(row) ?? false;
740
- const expansion = expanded ? expandedRow?.(row) : null;
741
- const rowInteractive = !!onRowClick || !!selection;
742
- const href = rowHref?.(row);
743
814
  const key = rowKey(row);
744
- const detailId = `${detailIdBase}-${rowIndex}`;
745
- const disclosure = rowsFocusable && expandedRow ? /* @__PURE__ */ jsx(
746
- "button",
815
+ return /* @__PURE__ */ jsx(
816
+ DataTableRow,
747
817
  {
748
- type: "button",
749
- tabIndex: -1,
750
- className: "sr-only-fixed",
751
- "aria-label": labels.rowDetails,
752
- "aria-expanded": expanded,
753
- "aria-controls": expansion ? detailId : void 0,
754
- onClick: () => onRowClick(row)
755
- }
756
- ) : null;
757
- return /* @__PURE__ */ jsxs(Fragment2, { children: [
758
- /* @__PURE__ */ jsxs(
759
- "tr",
760
- {
761
- ...cleanAttrs(rowAttributes?.(row)),
762
- "data-row-nav": rowsFocusable ? "" : void 0,
763
- tabIndex: rowsFocusable ? key === tabRowKey ? 0 : -1 : void 0,
764
- onFocus: rowsFocusable ? (e) => {
765
- if (e.target === e.currentTarget) setFocusRowKey(key);
766
- } : void 0,
767
- onKeyDown: rowsFocusable ? (e) => {
768
- if (e.target !== e.currentTarget) return;
769
- if (e.altKey || e.ctrlKey || e.metaKey) return;
770
- if (e.key === "Enter" || e.key === " ") {
771
- e.preventDefault();
772
- onRowClick(row);
773
- return;
774
- }
775
- if (e.shiftKey) return;
776
- if (moveRowFocus(e.currentTarget, e.key)) e.preventDefault();
777
- } : void 0,
778
- className: cn(
779
- "border-t border-subtle",
780
- rowInteractive && "cursor-pointer hover:bg-hover",
781
- // The kit's focus frame width (§5, `--focus-ring-width`), drawn as an
782
- // OUTLINE rather than FOCUS_RING's box-shadow: a shadow on a `<tr>`
783
- // is not painted by every engine, an outline is.
784
- rowsFocusable && "focus-visible:bg-hover focus-visible:outline-focus focus-visible:-outline-offset-2 focus-visible:outline-brand",
785
- rowClassName?.(row)
786
- ),
787
- onClick: rowInteractive ? (e) => {
788
- if (fromOwnControl(e.target, e.currentTarget)) return;
789
- if (fromOutside(e.target, e.currentTarget)) return;
790
- if (selection && canSelect(row)) {
791
- if (e.shiftKey) {
792
- window.getSelection?.()?.removeAllRanges();
793
- if (selectRange(rowIndex)) return;
794
- selectionAnchor.current = rowKey(row);
795
- selection.onToggle(row, !selection.isSelected(row));
796
- return;
797
- }
798
- if (e.metaKey || e.ctrlKey) {
799
- selectionAnchor.current = rowKey(row);
800
- selection.onToggle(row, !selection.isSelected(row));
801
- return;
802
- }
803
- }
804
- onRowClick?.(row);
805
- } : void 0,
806
- children: [
807
- selection && /* @__PURE__ */ jsx(
808
- "td",
809
- {
810
- className: cn("align-top", compact ? "w-8 px-2 py-1" : "w-10 px-3 py-2"),
811
- onClick: (e) => e.stopPropagation(),
812
- children: canSelect(row) && /* @__PURE__ */ jsx(
813
- "input",
814
- {
815
- type: "checkbox",
816
- className: cn("accent-indigo-600", compact ? "size-3.5" : "size-4"),
817
- checked: selection.isSelected(row),
818
- onClick: (e) => {
819
- if (e.shiftKey && selectRange(rowIndex)) {
820
- e.preventDefault();
821
- rangeClick.current = e.nativeEvent;
822
- }
823
- },
824
- onChange: (e) => {
825
- if (rangeClick.current && e.nativeEvent === rangeClick.current) {
826
- rangeClick.current = null;
827
- return;
828
- }
829
- selectionAnchor.current = rowKey(row);
830
- selection.onToggle(row, e.target.checked);
831
- },
832
- "aria-label": labels.selectRow
833
- }
834
- )
835
- }
836
- ),
837
- visibleColumns.map((col, colIndex) => {
838
- const width = widths[col.key];
839
- const own = splitCellProps(col.cellProps?.(row));
840
- const pinned = width ? { width, minWidth: width, maxWidth: width } : void 0;
841
- return /* @__PURE__ */ createElement(
842
- "td",
843
- {
844
- ...own.attrs,
845
- key: col.key,
846
- "data-col": col.key,
847
- style: own.style || pinned ? { ...own.style, ...pinned } : void 0,
848
- className: cn(
849
- "align-top",
850
- compact ? "px-2 py-1" : "px-3 py-2",
851
- width && "overflow-hidden text-ellipsis",
852
- col.grow ? GROW_CELL_CLASS : growDrawn && GROW_PEER_CELL_CLASS,
853
- logicalAlign(own.className),
854
- logicalAlign(col.className)
855
- )
856
- },
857
- colIndex === 0 && disclosure,
858
- col === linkColumn && href ? (
859
- // No `onActivate`: the click is left to bubble to the
860
- // <tr> handler above, which already owns expand-vs-select.
861
- // Calling it here as well would toggle the row twice.
862
- /* @__PURE__ */ jsx(RowLink, { href, className: "block", "data-row-link": "", children: col.cell(row) })
863
- ) : col.cell(row)
864
- );
865
- })
866
- ]
867
- }
868
- ),
869
- 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(
870
- "div",
871
- {
872
- className: cn(
873
- "sticky start-0 w-[var(--dt-visible-w,100%)] max-w-full",
874
- compact ? "px-2 py-2" : "px-3 py-3"
875
- ),
876
- children: expansion
877
- }
878
- ) }) }) })
879
- ] }, 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
+ );
880
847
  };
881
848
  const emptyRow = slice.length === 0 ? /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx(
882
849
  "td",
@@ -886,14 +853,7 @@ function DataTable({
886
853
  children: isLoading ? /* @__PURE__ */ jsx(LoadingText, { label: labels.loading }) : empty ?? labels.empty
887
854
  }
888
855
  ) }) : null;
889
- if (!desktopGroups) {
890
- return /* @__PURE__ */ jsxs("tbody", { className: bodyClass, children: [
891
- leading,
892
- slice.map(renderRow),
893
- emptyRow
894
- ] });
895
- }
896
- const band = (level, def, g) => /* @__PURE__ */ jsx(
856
+ const band = (level, def, g, ariaRowIndex) => /* @__PURE__ */ jsx(
897
857
  DataTableGroupBand,
898
858
  {
899
859
  level,
@@ -901,9 +861,44 @@ function DataTable({
901
861
  top: level === 1 ? bandTops[0] : bandTops[1],
902
862
  compact,
903
863
  label: def.label ? def.label(g.key, g.rows) : g.key,
904
- end: def.end?.(g.key, g.rows)
864
+ end: def.end?.(g.key, g.rows),
865
+ ariaRowIndex
905
866
  }
906
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
+ }
907
902
  const rowsOf = (g) => g.rows.map((r, i) => renderRow(r, g.start + i));
908
903
  return /* @__PURE__ */ jsxs(Fragment, { children: [
909
904
  (leading || emptyRow) && /* @__PURE__ */ jsxs("tbody", { className: bodyClass, children: [
@@ -935,7 +930,7 @@ function DataTable({
935
930
  "font-semibold text-primary",
936
931
  isLoading && "opacity-60 transition-opacity"
937
932
  ),
938
- children: /* @__PURE__ */ jsxs("tr", { children: [
933
+ children: /* @__PURE__ */ jsxs("tr", { "aria-rowindex": rowWindow?.rowCount, children: [
939
934
  selection && /* @__PURE__ */ jsx(
940
935
  "td",
941
936
  {