@gridkitjs/react 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,10 +1,10 @@
1
1
  // src/DataGrid/DataGrid.tsx
2
2
  import {
3
- useEffect as useEffect2,
3
+ useEffect as useEffect4,
4
4
  useImperativeHandle,
5
5
  useMemo as useMemo4,
6
- useRef as useRef4,
7
- useState as useState5
6
+ useRef as useRef6,
7
+ useState as useState6
8
8
  } from "react";
9
9
  import {
10
10
  applyColumnOrder,
@@ -21,6 +21,12 @@ import {
21
21
  withGroupAggregates
22
22
  } from "@gridkitjs/core";
23
23
 
24
+ // src/DataGrid/components/GridColgroup.tsx
25
+ import { jsx } from "react/jsx-runtime";
26
+ function GridColgroup({ columns }) {
27
+ return /* @__PURE__ */ jsx("colgroup", { children: columns.map((entry) => /* @__PURE__ */ jsx("col", { style: { width: entry.width } }, entry.id)) });
28
+ }
29
+
24
30
  // src/DataGrid/components/GridHeader.tsx
25
31
  import { createPortal } from "react-dom";
26
32
  import { buildKeyShortcuts, intentOf, KEYBOARD_STEP } from "@gridkitjs/core";
@@ -51,8 +57,8 @@ var FALLBACK_PAGE = 10;
51
57
  function tabIndexFor(columnIndex, focusedColumnIndex) {
52
58
  return columnIndex === focusedColumnIndex ? 0 : -1;
53
59
  }
54
- function cellAt(table, focus) {
55
- const row = focus.rowIndex === HEADER_ROW ? table.tHead?.rows[0] ?? null : table.tBodies[0]?.rows[focus.rowIndex] ?? null;
60
+ function cellAt(headerTable, bodyTable, focus) {
61
+ const row = focus.rowIndex === HEADER_ROW ? headerTable?.tHead?.rows[0] ?? null : bodyTable?.tBodies[0]?.rows[focus.rowIndex] ?? null;
56
62
  if (row === null) {
57
63
  return null;
58
64
  }
@@ -60,10 +66,12 @@ function cellAt(table, focus) {
60
66
  return row.cells[columnIndex] ?? null;
61
67
  }
62
68
  function useGridNavigation({
63
- tableRef,
69
+ headerTableRef,
70
+ bodyTableRef,
64
71
  rowCount,
65
72
  columnCount,
66
- isSkippableRow
73
+ isSkippableRow,
74
+ virtualRange
67
75
  }) {
68
76
  const [stored, setStored] = useState({
69
77
  rowIndex: HEADER_ROW,
@@ -78,15 +86,29 @@ function useGridNavigation({
78
86
  if (!pending.current) {
79
87
  return;
80
88
  }
81
- pending.current = false;
82
- const table = tableRef.current;
83
- if (table === null) {
89
+ const cell = cellAt(headerTableRef.current, bodyTableRef.current, {
90
+ rowIndex: focus.rowIndex,
91
+ columnIndex: focus.columnIndex
92
+ });
93
+ if (cell !== null) {
94
+ pending.current = false;
95
+ cell.focus();
96
+ return;
97
+ }
98
+ if (virtualRange != null && focus.rowIndex !== HEADER_ROW && (focus.rowIndex < virtualRange.startIndex || focus.rowIndex > virtualRange.endIndex)) {
99
+ virtualRange.scrollToIndex(focus.rowIndex);
84
100
  return;
85
101
  }
86
- cellAt(table, { rowIndex: focus.rowIndex, columnIndex: focus.columnIndex })?.focus();
87
- }, [tableRef, focus.rowIndex, focus.columnIndex]);
102
+ pending.current = false;
103
+ }, [
104
+ headerTableRef,
105
+ bodyTableRef,
106
+ focus.rowIndex,
107
+ focus.columnIndex,
108
+ virtualRange
109
+ ]);
88
110
  function pageSize() {
89
- const table = tableRef.current;
111
+ const table = bodyTableRef.current;
90
112
  const firstRow = table?.tBodies[0]?.rows[0];
91
113
  const viewport = table?.parentElement;
92
114
  if (firstRow === void 0 || viewport === null || viewport === void 0 || firstRow.offsetHeight === 0) {
@@ -356,9 +378,9 @@ function useGridSelection({
356
378
  }
357
379
 
358
380
  // src/DataGrid/components/GridHeader.tsx
359
- import { jsx, jsxs } from "react/jsx-runtime";
381
+ import { jsx as jsx2, jsxs } from "react/jsx-runtime";
360
382
  function Icon({ children }) {
361
- return /* @__PURE__ */ jsx(
383
+ return /* @__PURE__ */ jsx2(
362
384
  "svg",
363
385
  {
364
386
  viewBox: "0 0 24 24",
@@ -374,18 +396,18 @@ function Icon({ children }) {
374
396
  }
375
397
  function ChevronsUpDownIcon() {
376
398
  return /* @__PURE__ */ jsxs(Icon, { children: [
377
- /* @__PURE__ */ jsx("path", { d: "m7 15 5 5 5-5" }),
378
- /* @__PURE__ */ jsx("path", { d: "m7 9 5-5 5 5" })
399
+ /* @__PURE__ */ jsx2("path", { d: "m7 15 5 5 5-5" }),
400
+ /* @__PURE__ */ jsx2("path", { d: "m7 9 5-5 5 5" })
379
401
  ] });
380
402
  }
381
403
  function ChevronUpIcon() {
382
- return /* @__PURE__ */ jsx(Icon, { children: /* @__PURE__ */ jsx("path", { d: "m18 15-6-6-6 6" }) });
404
+ return /* @__PURE__ */ jsx2(Icon, { children: /* @__PURE__ */ jsx2("path", { d: "m18 15-6-6-6 6" }) });
383
405
  }
384
406
  function GroupIcon() {
385
407
  return /* @__PURE__ */ jsxs(Icon, { children: [
386
- /* @__PURE__ */ jsx("rect", { x: "3", y: "3", width: "7", height: "7", rx: "1" }),
387
- /* @__PURE__ */ jsx("rect", { x: "14", y: "3", width: "7", height: "7", rx: "1" }),
388
- /* @__PURE__ */ jsx("rect", { x: "8.5", y: "14", width: "7", height: "7", rx: "1" })
408
+ /* @__PURE__ */ jsx2("rect", { x: "3", y: "3", width: "7", height: "7", rx: "1" }),
409
+ /* @__PURE__ */ jsx2("rect", { x: "14", y: "3", width: "7", height: "7", rx: "1" }),
410
+ /* @__PURE__ */ jsx2("rect", { x: "8.5", y: "14", width: "7", height: "7", rx: "1" })
389
411
  ] });
390
412
  }
391
413
  function GridHeader({
@@ -403,7 +425,7 @@ function GridHeader({
403
425
  const beforeId = drag.dropTarget?.kind === "column-order" ? drag.dropTarget.beforeId : void 0;
404
426
  const draggedEntry = columns.find((entry) => entry.id === drag.draggedColumnId) ?? null;
405
427
  return /* @__PURE__ */ jsxs("thead", { children: [
406
- /* @__PURE__ */ jsx("tr", { className: "grid-header", role: "row", "aria-rowindex": 1, children: columns.map((entry, index) => {
428
+ /* @__PURE__ */ jsx2("tr", { className: "grid-header", role: "row", "aria-rowindex": 1, children: columns.map((entry, index) => {
407
429
  const { column } = entry;
408
430
  const resizing = resize.activeColumnId === entry.id;
409
431
  const dragging = drag.draggedColumnId === entry.id;
@@ -523,7 +545,7 @@ function GridHeader({
523
545
  * A pointer affordance only, same as the other two — the
524
546
  * keyboard equivalent is Alt+ArrowDown on the header itself.
525
547
  */
526
- /* @__PURE__ */ jsx(
548
+ /* @__PURE__ */ jsx2(
527
549
  "span",
528
550
  {
529
551
  className: classNames(
@@ -539,7 +561,7 @@ function GridHeader({
539
561
  event.stopPropagation();
540
562
  grouping.toggleGroupBy(entry.id);
541
563
  },
542
- children: /* @__PURE__ */ jsx(GroupIcon, {})
564
+ children: /* @__PURE__ */ jsx2(GroupIcon, {})
543
565
  }
544
566
  ),
545
567
  entry.label,
@@ -565,8 +587,8 @@ function GridHeader({
565
587
  sort.toggle(entry, { shiftKey: event.shiftKey });
566
588
  },
567
589
  children: [
568
- sortDirection === null ? /* @__PURE__ */ jsx(ChevronsUpDownIcon, {}) : /* @__PURE__ */ jsx(ChevronUpIcon, {}),
569
- sortPriority !== null && sortPriority > 1 && /* @__PURE__ */ jsx("span", { className: "header-sort-priority", children: sortPriority })
590
+ sortDirection === null ? /* @__PURE__ */ jsx2(ChevronsUpDownIcon, {}) : /* @__PURE__ */ jsx2(ChevronUpIcon, {}),
591
+ sortPriority !== null && sortPriority > 1 && /* @__PURE__ */ jsx2("span", { className: "header-sort-priority", children: sortPriority })
570
592
  ]
571
593
  }
572
594
  ),
@@ -576,7 +598,7 @@ function GridHeader({
576
598
  * put N tab stops inside a widget that is meant to have one.
577
599
  * The keyboard resizes from the header itself, under Alt.
578
600
  */
579
- /* @__PURE__ */ jsx(
601
+ /* @__PURE__ */ jsx2(
580
602
  "span",
581
603
  {
582
604
  className: "header-resize-handle",
@@ -596,7 +618,7 @@ function GridHeader({
596
618
  );
597
619
  }) }),
598
620
  draggedEntry !== null && drag.ghostTransform !== null && createPortal(
599
- /* @__PURE__ */ jsx(
621
+ /* @__PURE__ */ jsx2(
600
622
  "div",
601
623
  {
602
624
  className: "gridkit-data-grid header-cell drag-ghost",
@@ -647,7 +669,7 @@ function formatAggregateValue(value) {
647
669
  }
648
670
 
649
671
  // src/DataGrid/components/GridGroupRow.tsx
650
- import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
672
+ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
651
673
  function formatGroupValue(value) {
652
674
  if (value === null || value === void 0) {
653
675
  return "(blank)";
@@ -676,18 +698,21 @@ function GridGroupRowComponent({
676
698
  value,
677
699
  expanded,
678
700
  count,
701
+ rowIndex,
679
702
  datasetIndex,
680
703
  posinset,
681
704
  setsize,
682
705
  focused,
683
706
  aggregates,
684
707
  results,
685
- columns
708
+ columns,
709
+ ref
686
710
  }) {
687
711
  const byId = new Map(columns.map((entry) => [entry.id, entry]));
688
- return /* @__PURE__ */ jsx2(
712
+ return /* @__PURE__ */ jsx3(
689
713
  "tr",
690
714
  {
715
+ ref,
691
716
  role: "row",
692
717
  "aria-rowindex": datasetIndex + 2,
693
718
  "aria-level": level + 1,
@@ -695,11 +720,12 @@ function GridGroupRowComponent({
695
720
  "aria-setsize": setsize,
696
721
  "aria-posinset": posinset,
697
722
  "data-gridkit-group": groupId,
723
+ "data-gridkit-row-index": rowIndex,
698
724
  className: classNames(
699
725
  "grid-group-row",
700
726
  `is-group-level-${String(level)}`
701
727
  ),
702
- children: /* @__PURE__ */ jsx2(
728
+ children: /* @__PURE__ */ jsx3(
703
729
  "td",
704
730
  {
705
731
  role: "gridcell",
@@ -713,7 +739,7 @@ function GridGroupRowComponent({
713
739
  className: "grid-group-cell-content",
714
740
  style: { paddingInlineStart: `${String(level * 1.25 + 0.5)}rem` },
715
741
  children: [
716
- /* @__PURE__ */ jsx2(
742
+ /* @__PURE__ */ jsx3(
717
743
  "span",
718
744
  {
719
745
  className: classNames(
@@ -733,7 +759,7 @@ function GridGroupRowComponent({
733
759
  count,
734
760
  ")"
735
761
  ] }),
736
- aggregates.length > 0 && /* @__PURE__ */ jsx2("span", { className: "group-aggregates", children: aggregates.map((spec) => {
762
+ aggregates.length > 0 && /* @__PURE__ */ jsx3("span", { className: "group-aggregates", children: aggregates.map((spec) => {
737
763
  const key = spec.id ?? spec.columnId;
738
764
  const aggregateValue = results.get(key);
739
765
  const column = byId.get(spec.columnId);
@@ -763,21 +789,25 @@ var GridGroupRow_default = GridGroupRow;
763
789
 
764
790
  // src/DataGrid/components/GridGroupSummaryRow.tsx
765
791
  import { memo as memo2 } from "react";
766
- import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
792
+ import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
767
793
  function GridGroupSummaryRowComponent({
768
794
  groupId,
769
795
  level,
796
+ rowIndex,
770
797
  datasetIndex,
771
798
  results,
772
799
  aggregates,
773
- columns
800
+ columns,
801
+ ref
774
802
  }) {
775
- return /* @__PURE__ */ jsx3(
803
+ return /* @__PURE__ */ jsx4(
776
804
  "tr",
777
805
  {
806
+ ref,
778
807
  role: "row",
779
808
  "aria-rowindex": datasetIndex + 2,
780
809
  "data-gridkit-group-summary": groupId,
810
+ "data-gridkit-row-index": rowIndex,
781
811
  className: classNames(
782
812
  "grid-group-summary-row",
783
813
  `is-group-level-${String(level)}`
@@ -795,7 +825,7 @@ function GridGroupSummaryRowComponent({
795
825
  className: "grid-group-summary-cell",
796
826
  style: { textAlign: specs[0]?.alignment ?? entry.alignment },
797
827
  children: [
798
- entry.id === columns[0]?.id && /* @__PURE__ */ jsx3(
828
+ entry.id === columns[0]?.id && /* @__PURE__ */ jsx4(
799
829
  "span",
800
830
  {
801
831
  className: "grid-group-summary-indent",
@@ -825,7 +855,7 @@ var GridGroupSummaryRow_default = GridGroupSummaryRow;
825
855
  // src/DataGrid/components/GridRow.tsx
826
856
  import { accessDotted } from "@gridkitjs/core";
827
857
  import { memo as memo3 } from "react";
828
- import { jsx as jsx4 } from "react/jsx-runtime";
858
+ import { jsx as jsx5 } from "react/jsx-runtime";
829
859
  function GridRowComponent({
830
860
  columns,
831
861
  rowId,
@@ -838,19 +868,22 @@ function GridRowComponent({
838
868
  selectedColumnId,
839
869
  focusedColumnIndex,
840
870
  rowsSelectable,
841
- cellsSelectable
871
+ cellsSelectable,
872
+ ref
842
873
  }) {
843
- return /* @__PURE__ */ jsx4(
874
+ return /* @__PURE__ */ jsx5(
844
875
  "tr",
845
876
  {
877
+ ref,
846
878
  role: "row",
847
879
  "aria-rowindex": datasetIndex + 2,
880
+ "data-gridkit-row-index": rowIndex,
848
881
  ...ariaAttr(rowsSelectable, "aria-selected", selected),
849
882
  className: classNames("grid-row", selected ? "is-selected" : ""),
850
883
  children: columns.map(({ id, column, alignment }, columnIndex) => {
851
884
  const value = accessDotted(row, column.field);
852
885
  const cellSelected = id === selectedColumnId;
853
- return /* @__PURE__ */ jsx4(
886
+ return /* @__PURE__ */ jsx5(
854
887
  "td",
855
888
  {
856
889
  role: "gridcell",
@@ -886,7 +919,11 @@ var GridRow = memo3(GridRowComponent);
886
919
  var GridRow_default = GridRow;
887
920
 
888
921
  // src/DataGrid/components/GridBody.tsx
889
- import { jsx as jsx5 } from "react/jsx-runtime";
922
+ import { Fragment, jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
923
+ function rowIndexOf(row) {
924
+ const attribute = row.getAttribute("data-gridkit-row-index");
925
+ return attribute === null ? null : Number(attribute);
926
+ }
890
927
  function cellFrom(target) {
891
928
  if (!(target instanceof Element)) {
892
929
  return null;
@@ -894,11 +931,12 @@ function cellFrom(target) {
894
931
  const cell = target.closest("td[data-gridkit-column]");
895
932
  const row = cell?.parentElement;
896
933
  const columnId = cell?.getAttribute("data-gridkit-column");
897
- if (!(cell instanceof HTMLTableCellElement) || !(row instanceof HTMLTableRowElement) || columnId === null || columnId === void 0) {
934
+ const rowIndex = row === null || row === void 0 ? null : rowIndexOf(row);
935
+ if (!(cell instanceof HTMLTableCellElement) || !(row instanceof HTMLTableRowElement) || columnId === null || columnId === void 0 || rowIndex === null) {
898
936
  return null;
899
937
  }
900
938
  return {
901
- rowIndex: row.sectionRowIndex,
939
+ rowIndex,
902
940
  columnIndex: cell.cellIndex,
903
941
  columnId
904
942
  };
@@ -909,10 +947,11 @@ function groupRowFrom(target) {
909
947
  }
910
948
  const row = target.closest("tr[data-gridkit-group]");
911
949
  const groupId = row?.getAttribute("data-gridkit-group");
912
- if (!(row instanceof HTMLTableRowElement) || groupId === null || groupId === void 0) {
950
+ const rowIndex = row === null ? null : rowIndexOf(row);
951
+ if (!(row instanceof HTMLTableRowElement) || groupId === null || groupId === void 0 || rowIndex === null) {
913
952
  return null;
914
953
  }
915
- return { rowIndex: row.sectionRowIndex, groupId };
954
+ return { rowIndex, groupId };
916
955
  }
917
956
  function groupAriaMeta(rows) {
918
957
  const totals = /* @__PURE__ */ new Map();
@@ -943,10 +982,14 @@ function GridBody({
943
982
  selection,
944
983
  grouping,
945
984
  aggregates,
946
- groupAggregateDisplay
985
+ groupAggregateDisplay,
986
+ virtualRange,
987
+ infiniteScroll,
988
+ sentinelRef
947
989
  }) {
948
990
  const { selectedCell, rowMode, cellMode } = selection;
949
991
  const ariaMeta = useMemo3(() => groupAriaMeta(rows), [rows]);
992
+ const visibleRows = virtualRange === null ? rows : rows.slice(virtualRange.startIndex, virtualRange.endIndex + 1);
950
993
  function targetOf(target) {
951
994
  const position = cellFrom(target);
952
995
  const entry = position === null ? void 0 : rows[position.rowIndex];
@@ -964,7 +1007,7 @@ function GridBody({
964
1007
  selection.selectCell({ rowId, columnId }, intent);
965
1008
  }
966
1009
  }
967
- return /* @__PURE__ */ jsx5(
1010
+ return /* @__PURE__ */ jsxs4(
968
1011
  "tbody",
969
1012
  {
970
1013
  className: "grid-body",
@@ -1016,80 +1059,131 @@ function GridBody({
1016
1059
  }
1017
1060
  nav.onKeyDown(event);
1018
1061
  },
1019
- children: rows.map((entry) => {
1020
- if (!("kind" in entry)) {
1021
- return /* @__PURE__ */ jsx5(
1022
- GridRow_default,
1023
- {
1024
- columns,
1025
- rowId: entry.rowId,
1026
- row: entry.row,
1027
- rowIndex: entry.rowIndex,
1028
- datasetIndex: entry.datasetIndex,
1029
- activeColumnId,
1030
- selectedColumnIds: selection.selectedColumnIds,
1031
- selected: selection.selectedRowIds.has(entry.rowId),
1032
- selectedColumnId: selectedCell?.rowId === entry.rowId ? selectedCell.columnId : null,
1033
- focusedColumnIndex: nav.focus.rowIndex === entry.rowIndex ? nav.focus.columnIndex : null,
1034
- rowsSelectable: rowMode !== false,
1035
- cellsSelectable: cellMode !== false
1036
- },
1037
- entry.rowId
1038
- );
1039
- }
1040
- if (entry.kind === "group-summary") {
1041
- return /* @__PURE__ */ jsx5(
1042
- GridGroupSummaryRow_default,
1062
+ children: [
1063
+ virtualRange !== null && virtualRange.offsetBefore > 0 && // `aria-hidden` and carrying neither `data-gridkit-column` nor
1064
+ // `data-gridkit-group`/`data-gridkit-row-index`, so `cellFrom`/
1065
+ // `groupRowFrom` never mistake it for a real row — it exists purely
1066
+ // to give the browser's scrollbar the correct total height and thumb
1067
+ // position, the same way `GridFooter`'s `<tfoot>` isn't a "row" to
1068
+ // any of this grid's own row-counting logic.
1069
+ /* @__PURE__ */ jsx6("tr", { "aria-hidden": "true", children: /* @__PURE__ */ jsx6(
1070
+ "td",
1071
+ {
1072
+ style: {
1073
+ height: virtualRange.offsetBefore,
1074
+ padding: 0,
1075
+ border: "none"
1076
+ }
1077
+ }
1078
+ ) }),
1079
+ visibleRows.map((entry) => {
1080
+ if (!("kind" in entry)) {
1081
+ return /* @__PURE__ */ jsx6(
1082
+ GridRow_default,
1083
+ {
1084
+ ref: virtualRange?.measureRow(entry.rowIndex),
1085
+ columns,
1086
+ rowId: entry.rowId,
1087
+ row: entry.row,
1088
+ rowIndex: entry.rowIndex,
1089
+ datasetIndex: entry.datasetIndex,
1090
+ activeColumnId,
1091
+ selectedColumnIds: selection.selectedColumnIds,
1092
+ selected: selection.selectedRowIds.has(entry.rowId),
1093
+ selectedColumnId: selectedCell?.rowId === entry.rowId ? selectedCell.columnId : null,
1094
+ focusedColumnIndex: nav.focus.rowIndex === entry.rowIndex ? nav.focus.columnIndex : null,
1095
+ rowsSelectable: rowMode !== false,
1096
+ cellsSelectable: cellMode !== false
1097
+ },
1098
+ entry.rowId
1099
+ );
1100
+ }
1101
+ if (entry.kind === "group-summary") {
1102
+ return /* @__PURE__ */ jsx6(
1103
+ GridGroupSummaryRow_default,
1104
+ {
1105
+ ref: virtualRange?.measureRow(entry.rowIndex),
1106
+ groupId: entry.groupId,
1107
+ level: entry.level,
1108
+ rowIndex: entry.rowIndex,
1109
+ datasetIndex: entry.datasetIndex,
1110
+ results: entry.aggregates,
1111
+ aggregates,
1112
+ columns
1113
+ },
1114
+ `${entry.groupId}-summary`
1115
+ );
1116
+ }
1117
+ return /* @__PURE__ */ jsx6(
1118
+ GridGroupRow_default,
1043
1119
  {
1120
+ ref: virtualRange?.measureRow(entry.rowIndex),
1121
+ columnCount: columns.length,
1044
1122
  groupId: entry.groupId,
1045
1123
  level: entry.level,
1124
+ columnLabel: columns.find((column) => column.id === entry.columnId)?.label ?? entry.columnId,
1125
+ value: entry.value,
1126
+ expanded: entry.expanded,
1127
+ count: entry.count,
1128
+ rowIndex: entry.rowIndex,
1046
1129
  datasetIndex: entry.datasetIndex,
1130
+ posinset: ariaMeta.get(entry.groupId)?.posinset ?? 1,
1131
+ setsize: ariaMeta.get(entry.groupId)?.setsize ?? 1,
1132
+ aggregates: groupAggregateDisplay === "inline" ? aggregates : [],
1047
1133
  results: entry.aggregates,
1048
- aggregates,
1049
- columns
1134
+ columns,
1135
+ focused: nav.focus.rowIndex === entry.rowIndex
1050
1136
  },
1051
- `${entry.groupId}-summary`
1137
+ entry.groupId
1052
1138
  );
1053
- }
1054
- return /* @__PURE__ */ jsx5(
1055
- GridGroupRow_default,
1139
+ }),
1140
+ virtualRange !== null && virtualRange.offsetAfter > 0 && /* @__PURE__ */ jsx6("tr", { "aria-hidden": "true", children: /* @__PURE__ */ jsx6(
1141
+ "td",
1056
1142
  {
1057
- columnCount: columns.length,
1058
- groupId: entry.groupId,
1059
- level: entry.level,
1060
- columnLabel: columns.find((column) => column.id === entry.columnId)?.label ?? entry.columnId,
1061
- value: entry.value,
1062
- expanded: entry.expanded,
1063
- count: entry.count,
1064
- datasetIndex: entry.datasetIndex,
1065
- posinset: ariaMeta.get(entry.groupId)?.posinset ?? 1,
1066
- setsize: ariaMeta.get(entry.groupId)?.setsize ?? 1,
1067
- aggregates: groupAggregateDisplay === "inline" ? aggregates : [],
1068
- results: entry.aggregates,
1069
- columns,
1070
- focused: nav.focus.rowIndex === entry.rowIndex
1071
- },
1072
- entry.groupId
1073
- );
1074
- })
1143
+ style: {
1144
+ height: virtualRange.offsetAfter,
1145
+ padding: 0,
1146
+ border: "none"
1147
+ }
1148
+ }
1149
+ ) }),
1150
+ infiniteScroll?.hasMore && /* @__PURE__ */ jsxs4(Fragment, { children: [
1151
+ infiniteScroll.isLoadingMore === true && /* @__PURE__ */ jsx6("tr", { "aria-hidden": "true", className: "grid-loading-row", children: /* @__PURE__ */ jsx6("td", { colSpan: columns.length, className: "grid-loading-cell", children: infiniteScroll.loadingTemplate ? infiniteScroll.loadingTemplate() : "Loading more\u2026" }) }),
1152
+ /* @__PURE__ */ jsx6(
1153
+ "tr",
1154
+ {
1155
+ "aria-hidden": "true",
1156
+ ref: sentinelRef,
1157
+ className: "grid-sentinel-row",
1158
+ children: /* @__PURE__ */ jsx6(
1159
+ "td",
1160
+ {
1161
+ colSpan: columns.length,
1162
+ style: { padding: 0, border: "none" }
1163
+ }
1164
+ )
1165
+ }
1166
+ )
1167
+ ] })
1168
+ ]
1075
1169
  }
1076
1170
  );
1077
1171
  }
1078
1172
 
1079
1173
  // src/DataGrid/components/GridFooter.tsx
1080
- import { jsx as jsx6 } from "react/jsx-runtime";
1174
+ import { jsx as jsx7 } from "react/jsx-runtime";
1081
1175
  function GridFooter({
1082
1176
  columns,
1083
1177
  aggregates,
1084
1178
  results,
1085
1179
  rows
1086
1180
  }) {
1087
- return /* @__PURE__ */ jsx6("tfoot", { children: /* @__PURE__ */ jsx6("tr", { className: "grid-footer", role: "row", children: columns.map((entry) => {
1181
+ return /* @__PURE__ */ jsx7("tfoot", { children: /* @__PURE__ */ jsx7("tr", { className: "grid-footer", role: "row", children: columns.map((entry) => {
1088
1182
  const specs = aggregates.filter(
1089
1183
  (candidate) => candidate.columnId === entry.id
1090
1184
  );
1091
1185
  const { column } = entry;
1092
- return /* @__PURE__ */ jsx6(
1186
+ return /* @__PURE__ */ jsx7(
1093
1187
  "td",
1094
1188
  {
1095
1189
  role: "gridcell",
@@ -1110,13 +1204,13 @@ function GridFooter({
1110
1204
 
1111
1205
  // src/DataGrid/components/GridPager.tsx
1112
1206
  import { paginationWindow } from "@gridkitjs/core";
1113
- import { jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
1207
+ import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
1114
1208
  function GridPager({ pager, config }) {
1115
1209
  const { pagination, pageCount, currentPage } = pager;
1116
1210
  const variant = config?.variant ?? "compact";
1117
1211
  const sizeOptions = config?.sizeOptions;
1118
- return /* @__PURE__ */ jsxs4("div", { className: "gridkit-grid-pager", role: "group", "aria-label": "Pagination", children: [
1119
- /* @__PURE__ */ jsx7(
1212
+ return /* @__PURE__ */ jsxs5("div", { className: "gridkit-grid-pager", role: "group", "aria-label": "Pagination", children: [
1213
+ /* @__PURE__ */ jsx8(
1120
1214
  "button",
1121
1215
  {
1122
1216
  type: "button",
@@ -1132,7 +1226,7 @@ function GridPager({ pager, config }) {
1132
1226
  boundaryCount: config?.boundaryCount,
1133
1227
  siblingCount: config?.siblingCount
1134
1228
  }).map(
1135
- (entry, index) => entry === "ellipsis" ? /* @__PURE__ */ jsx7(
1229
+ (entry, index) => entry === "ellipsis" ? /* @__PURE__ */ jsx8(
1136
1230
  "span",
1137
1231
  {
1138
1232
  className: "grid-pager-ellipsis",
@@ -1140,7 +1234,7 @@ function GridPager({ pager, config }) {
1140
1234
  children: "\u2026"
1141
1235
  },
1142
1236
  `ellipsis-${String(index)}`
1143
- ) : /* @__PURE__ */ jsx7(
1237
+ ) : /* @__PURE__ */ jsx8(
1144
1238
  "button",
1145
1239
  {
1146
1240
  type: "button",
@@ -1154,13 +1248,13 @@ function GridPager({ pager, config }) {
1154
1248
  },
1155
1249
  entry
1156
1250
  )
1157
- ) : /* @__PURE__ */ jsxs4("span", { className: "grid-pager-status", children: [
1251
+ ) : /* @__PURE__ */ jsxs5("span", { className: "grid-pager-status", children: [
1158
1252
  "Page ",
1159
1253
  pageCount === 0 ? 0 : currentPage,
1160
1254
  " of ",
1161
1255
  pageCount
1162
1256
  ] }),
1163
- /* @__PURE__ */ jsx7(
1257
+ /* @__PURE__ */ jsx8(
1164
1258
  "button",
1165
1259
  {
1166
1260
  type: "button",
@@ -1172,16 +1266,16 @@ function GridPager({ pager, config }) {
1172
1266
  children: "Next"
1173
1267
  }
1174
1268
  ),
1175
- sizeOptions !== void 0 && sizeOptions.length > 0 && /* @__PURE__ */ jsxs4("label", { className: "grid-pager-page-size", children: [
1269
+ sizeOptions !== void 0 && sizeOptions.length > 0 && /* @__PURE__ */ jsxs5("label", { className: "grid-pager-page-size", children: [
1176
1270
  "Rows per page",
1177
- /* @__PURE__ */ jsx7(
1271
+ /* @__PURE__ */ jsx8(
1178
1272
  "select",
1179
1273
  {
1180
1274
  value: pagination.pageSize,
1181
1275
  onChange: (event) => {
1182
1276
  pager.setPageSize(Number(event.target.value));
1183
1277
  },
1184
- children: sizeOptions.map((size) => /* @__PURE__ */ jsx7("option", { value: size, children: size }, size))
1278
+ children: sizeOptions.map((size) => /* @__PURE__ */ jsx8("option", { value: size, children: size }, size))
1185
1279
  }
1186
1280
  )
1187
1281
  ] })
@@ -1350,7 +1444,7 @@ function useGroupByDrag({
1350
1444
  }
1351
1445
 
1352
1446
  // src/DataGrid/components/GroupByBar.tsx
1353
- import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
1447
+ import { jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
1354
1448
  function GroupByBar({
1355
1449
  columns,
1356
1450
  grouping,
@@ -1384,7 +1478,7 @@ function GroupByBar({
1384
1478
  }
1385
1479
  grouping.moveGroupBy(columnId, beforeColumnId);
1386
1480
  }
1387
- return /* @__PURE__ */ jsxs5(
1481
+ return /* @__PURE__ */ jsxs6(
1388
1482
  "div",
1389
1483
  {
1390
1484
  className: classNames(
@@ -1396,12 +1490,12 @@ function GroupByBar({
1396
1490
  role: "group",
1397
1491
  "aria-label": "Grouped by",
1398
1492
  children: [
1399
- showPlaceholder && /* @__PURE__ */ jsx8("span", { className: "group-by-bar-placeholder", children: "Drag a column here to group by it" }),
1493
+ showPlaceholder && /* @__PURE__ */ jsx9("span", { className: "group-by-bar-placeholder", children: "Drag a column here to group by it" }),
1400
1494
  groupBy.map((entry, index) => {
1401
1495
  const dropBefore = activeDropTarget?.beforeColumnId === entry.columnId;
1402
1496
  const dropAfter = activeDropTarget?.beforeColumnId === null && index === groupBy.length - 1;
1403
1497
  const dragging = chipDrag.draggedColumnId === entry.columnId;
1404
- return /* @__PURE__ */ jsxs5(
1498
+ return /* @__PURE__ */ jsxs6(
1405
1499
  "span",
1406
1500
  {
1407
1501
  className: classNames(
@@ -1428,8 +1522,8 @@ function GroupByBar({
1428
1522
  }
1429
1523
  },
1430
1524
  children: [
1431
- /* @__PURE__ */ jsx8("span", { className: "group-by-chip-label", children: labelFor(entry.columnId) }),
1432
- /* @__PURE__ */ jsx8(
1525
+ /* @__PURE__ */ jsx9("span", { className: "group-by-chip-label", children: labelFor(entry.columnId) }),
1526
+ /* @__PURE__ */ jsx9(
1433
1527
  "button",
1434
1528
  {
1435
1529
  type: "button",
@@ -1450,7 +1544,7 @@ function GroupByBar({
1450
1544
  );
1451
1545
  }),
1452
1546
  chipDrag.draggedColumnId !== null && chipDrag.ghostTransform !== null && createPortal2(
1453
- /* @__PURE__ */ jsx8(
1547
+ /* @__PURE__ */ jsx9(
1454
1548
  "span",
1455
1549
  {
1456
1550
  className: "group-by-chip drag-ghost",
@@ -1684,6 +1778,74 @@ function useElementWidth(ref, enabled) {
1684
1778
  return width;
1685
1779
  }
1686
1780
 
1781
+ // src/DataGrid/useInfiniteScroll.ts
1782
+ import { useCallback, useEffect as useEffect2, useRef as useRef4 } from "react";
1783
+ function useInfiniteScroll({
1784
+ rootRef,
1785
+ sentinelRef,
1786
+ config
1787
+ }) {
1788
+ const hasMore = config?.hasMore ?? false;
1789
+ const isLoadingMore = config?.isLoadingMore ?? false;
1790
+ const rootMargin = config?.rootMargin ?? "200px";
1791
+ const latestRef = useRef4({
1792
+ hasMore,
1793
+ isLoadingMore,
1794
+ onLoadMore: config?.onLoadMore
1795
+ });
1796
+ useEffect2(() => {
1797
+ latestRef.current = {
1798
+ hasMore,
1799
+ isLoadingMore,
1800
+ onLoadMore: config?.onLoadMore
1801
+ };
1802
+ });
1803
+ const firedRef = useRef4(false);
1804
+ const intersectingRef = useRef4(false);
1805
+ const maybeLoadMore = useCallback(() => {
1806
+ const current = latestRef.current;
1807
+ if (!intersectingRef.current || !current.hasMore || current.isLoadingMore || firedRef.current) {
1808
+ return;
1809
+ }
1810
+ firedRef.current = true;
1811
+ current.onLoadMore?.();
1812
+ }, []);
1813
+ const wasLoadingRef = useRef4(isLoadingMore);
1814
+ useEffect2(() => {
1815
+ if (wasLoadingRef.current && !isLoadingMore) {
1816
+ firedRef.current = false;
1817
+ maybeLoadMore();
1818
+ }
1819
+ wasLoadingRef.current = isLoadingMore;
1820
+ }, [isLoadingMore, maybeLoadMore]);
1821
+ useEffect2(() => {
1822
+ if (!hasMore) {
1823
+ return void 0;
1824
+ }
1825
+ const root = rootRef.current;
1826
+ const sentinel = sentinelRef.current;
1827
+ if (root === null || sentinel === null) {
1828
+ return void 0;
1829
+ }
1830
+ const observer = new IntersectionObserver(
1831
+ (entries) => {
1832
+ const entry = entries[entries.length - 1];
1833
+ if (entry === void 0) {
1834
+ return;
1835
+ }
1836
+ intersectingRef.current = entry.isIntersecting;
1837
+ maybeLoadMore();
1838
+ },
1839
+ { root, rootMargin }
1840
+ );
1841
+ observer.observe(sentinel);
1842
+ return () => {
1843
+ observer.disconnect();
1844
+ intersectingRef.current = false;
1845
+ };
1846
+ }, [hasMore, rootMargin, rootRef, sentinelRef, maybeLoadMore]);
1847
+ }
1848
+
1687
1849
  // src/DataGrid/usePagination.ts
1688
1850
  import {
1689
1851
  paginateRows
@@ -1784,8 +1946,183 @@ function useRowGrouping({
1784
1946
  };
1785
1947
  }
1786
1948
 
1949
+ // src/DataGrid/useRowVirtualizer.ts
1950
+ import {
1951
+ useCallback as useCallback2,
1952
+ useEffect as useEffect3,
1953
+ useLayoutEffect as useLayoutEffect2,
1954
+ useRef as useRef5,
1955
+ useState as useState5
1956
+ } from "react";
1957
+ import { visibleRange } from "@gridkitjs/core";
1958
+ var EMPTY_RANGE = {
1959
+ startIndex: 0,
1960
+ endIndex: -1,
1961
+ offsetBefore: 0,
1962
+ offsetAfter: 0
1963
+ };
1964
+ function useRowVirtualizer({
1965
+ enabled,
1966
+ rows,
1967
+ bodyScrollRef,
1968
+ estimatedRowHeight,
1969
+ overscan
1970
+ }) {
1971
+ const rowCount = rows.length;
1972
+ const heightsRef = useRef5([]);
1973
+ const scrollTopRef = useRef5(0);
1974
+ const viewportHeightRef = useRef5(0);
1975
+ const elementsRef = useRef5(/* @__PURE__ */ new Map());
1976
+ const indexByElementRef = useRef5(/* @__PURE__ */ new Map());
1977
+ const refCallbacksRef = useRef5(
1978
+ /* @__PURE__ */ new Map()
1979
+ );
1980
+ const observerRef = useRef5(null);
1981
+ const pendingScrollRef = useRef5(void 0);
1982
+ const [range, setRange] = useState5(EMPTY_RANGE);
1983
+ const recompute = useCallback2(() => {
1984
+ setRange(
1985
+ enabled ? visibleRange(
1986
+ heightsRef.current,
1987
+ estimatedRowHeight,
1988
+ scrollTopRef.current,
1989
+ viewportHeightRef.current,
1990
+ overscan
1991
+ ) : rowCount === 0 ? EMPTY_RANGE : {
1992
+ startIndex: 0,
1993
+ endIndex: rowCount - 1,
1994
+ offsetBefore: 0,
1995
+ offsetAfter: 0
1996
+ }
1997
+ );
1998
+ }, [enabled, rowCount, estimatedRowHeight, overscan]);
1999
+ useLayoutEffect2(() => {
2000
+ heightsRef.current = new Array(rowCount).fill(
2001
+ void 0
2002
+ );
2003
+ recompute();
2004
+ }, [rows, rowCount, recompute]);
2005
+ useLayoutEffect2(() => {
2006
+ if (!enabled) {
2007
+ return void 0;
2008
+ }
2009
+ const element = bodyScrollRef.current;
2010
+ if (element === null) {
2011
+ return void 0;
2012
+ }
2013
+ scrollTopRef.current = element.scrollTop;
2014
+ viewportHeightRef.current = element.clientHeight;
2015
+ recompute();
2016
+ const handleScroll = () => {
2017
+ scrollTopRef.current = element.scrollTop;
2018
+ recompute();
2019
+ };
2020
+ element.addEventListener("scroll", handleScroll, { passive: true });
2021
+ const resizeObserver = new ResizeObserver(() => {
2022
+ viewportHeightRef.current = element.clientHeight;
2023
+ recompute();
2024
+ });
2025
+ resizeObserver.observe(element);
2026
+ return () => {
2027
+ element.removeEventListener("scroll", handleScroll);
2028
+ resizeObserver.disconnect();
2029
+ };
2030
+ }, [enabled, bodyScrollRef, recompute]);
2031
+ useEffect3(() => {
2032
+ if (!enabled) {
2033
+ return void 0;
2034
+ }
2035
+ const observer = new ResizeObserver((entries) => {
2036
+ let changed = false;
2037
+ for (const entry of entries) {
2038
+ const index = indexByElementRef.current.get(entry.target);
2039
+ if (index === void 0) continue;
2040
+ const height = entry.target.getBoundingClientRect().height;
2041
+ if (heightsRef.current[index] !== height) {
2042
+ heightsRef.current[index] = height;
2043
+ changed = true;
2044
+ }
2045
+ }
2046
+ if (changed) {
2047
+ recompute();
2048
+ }
2049
+ });
2050
+ observerRef.current = observer;
2051
+ for (const element of elementsRef.current.values()) {
2052
+ observer.observe(element);
2053
+ }
2054
+ return () => {
2055
+ observer.disconnect();
2056
+ observerRef.current = null;
2057
+ };
2058
+ }, [enabled, recompute]);
2059
+ function measureRow(index) {
2060
+ let callback = refCallbacksRef.current.get(index);
2061
+ if (callback === void 0) {
2062
+ callback = (element) => {
2063
+ const observer = observerRef.current;
2064
+ const previous = elementsRef.current.get(index);
2065
+ if (previous !== void 0) {
2066
+ observer?.unobserve(previous);
2067
+ indexByElementRef.current.delete(previous);
2068
+ elementsRef.current.delete(index);
2069
+ }
2070
+ if (element === null) {
2071
+ return;
2072
+ }
2073
+ elementsRef.current.set(index, element);
2074
+ indexByElementRef.current.set(element, index);
2075
+ observer?.observe(element);
2076
+ };
2077
+ refCallbacksRef.current.set(index, callback);
2078
+ }
2079
+ return callback;
2080
+ }
2081
+ useEffect3(() => {
2082
+ const pending = pendingScrollRef.current;
2083
+ if (pending === void 0) {
2084
+ return;
2085
+ }
2086
+ const element = elementsRef.current.get(pending.index);
2087
+ if (element === void 0) {
2088
+ return;
2089
+ }
2090
+ element.scrollIntoView(pending.options);
2091
+ pendingScrollRef.current = void 0;
2092
+ }, [range]);
2093
+ function estimateOffsetBefore(index) {
2094
+ let total = 0;
2095
+ for (let current = 0; current < index; current++) {
2096
+ total += heightsRef.current[current] ?? estimatedRowHeight;
2097
+ }
2098
+ return total;
2099
+ }
2100
+ function scrollToIndex(index, options) {
2101
+ const element = bodyScrollRef.current;
2102
+ if (element === null || rowCount === 0) {
2103
+ return;
2104
+ }
2105
+ const clamped = Math.max(0, Math.min(index, rowCount - 1));
2106
+ const mounted = elementsRef.current.get(clamped);
2107
+ if (mounted !== void 0) {
2108
+ mounted.scrollIntoView(options);
2109
+ return;
2110
+ }
2111
+ pendingScrollRef.current = { index: clamped, options };
2112
+ element.scrollTop = estimateOffsetBefore(clamped);
2113
+ }
2114
+ return {
2115
+ startIndex: range.startIndex,
2116
+ endIndex: range.endIndex,
2117
+ offsetBefore: range.offsetBefore,
2118
+ offsetAfter: range.offsetAfter,
2119
+ measureRow,
2120
+ scrollToIndex
2121
+ };
2122
+ }
2123
+
1787
2124
  // src/DataGrid/DataGrid.tsx
1788
- import { jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
2125
+ import { jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
1789
2126
  var DEFAULT_PAGE_SIZE = 25;
1790
2127
  var NO_AGGREGATES = [];
1791
2128
  function DataGridComponent({
@@ -1822,8 +2159,13 @@ function DataGridComponent({
1822
2159
  defaultPagination,
1823
2160
  pager,
1824
2161
  onPaginationChange,
2162
+ infiniteScroll,
1825
2163
  aggregates,
1826
2164
  groupAggregateDisplay = "inline",
2165
+ height,
2166
+ virtualized = false,
2167
+ overscan = 4,
2168
+ estimatedRowHeight = 40,
1827
2169
  label,
1828
2170
  labelledBy,
1829
2171
  ref,
@@ -1832,33 +2174,36 @@ function DataGridComponent({
1832
2174
  const hoverRows = hoverable?.rows ?? true;
1833
2175
  const hoverColumns = hoverable?.columns ?? true;
1834
2176
  const hoverCells = hoverable?.cells ?? true;
1835
- const viewportRef = useRef4(null);
1836
- const tableRef = useRef4(null);
2177
+ const viewportRef = useRef6(null);
2178
+ const headerTableRef = useRef6(null);
2179
+ const bodyTableRef = useRef6(null);
2180
+ const bodyScrollRef = useRef6(null);
2181
+ const sentinelRef = useRef6(null);
1837
2182
  const viewportWidth = useElementWidth(viewportRef, resizeMode === "fit");
1838
- const subscribersRef = useRef4(/* @__PURE__ */ new Set());
1839
- const [sizing, setSizing] = useState5(
2183
+ const subscribersRef = useRef6(/* @__PURE__ */ new Set());
2184
+ const [sizing, setSizing] = useState6(
1840
2185
  defaultColumnSizing ?? {}
1841
2186
  );
1842
- const [order, setOrder] = useState5(
2187
+ const [order, setOrder] = useState6(
1843
2188
  defaultColumnOrder ?? []
1844
2189
  );
1845
- const [sort, setSort] = useState5(defaultColumnSort ?? []);
1846
- const [groupBy, setGroupBy] = useState5(defaultGroupBy ?? []);
1847
- const [expansion, setExpansion] = useState5(
2190
+ const [sort, setSort] = useState6(defaultColumnSort ?? []);
2191
+ const [groupBy, setGroupBy] = useState6(defaultGroupBy ?? []);
2192
+ const [expansion, setExpansion] = useState6(
1848
2193
  defaultGroupExpansion ?? []
1849
2194
  );
1850
- const [filter] = useState5(defaultFilter ?? []);
1851
- const [pagination, setPagination] = useState5(
2195
+ const [filter] = useState6(defaultFilter ?? []);
2196
+ const [pagination, setPagination] = useState6(
1852
2197
  defaultPagination ?? { pageIndex: 0, pageSize: DEFAULT_PAGE_SIZE }
1853
2198
  );
1854
- const [announcement, setAnnouncement] = useState5("");
1855
- const [rowSelection, setRowSelection] = useState5(
2199
+ const [announcement, setAnnouncement] = useState6("");
2200
+ const [rowSelection, setRowSelection] = useState6(
1856
2201
  defaultRowSelection ?? []
1857
2202
  );
1858
- const [columnSelection, setColumnSelection] = useState5(
2203
+ const [columnSelection, setColumnSelection] = useState6(
1859
2204
  defaultColumnSelection ?? []
1860
2205
  );
1861
- const [cellSelection, setCellSelection] = useState5(
2206
+ const [cellSelection, setCellSelection] = useState6(
1862
2207
  defaultCellSelection ?? null
1863
2208
  );
1864
2209
  const rows = useMemo4(
@@ -1937,6 +2282,33 @@ function DataGridComponent({
1937
2282
  () => paginated ? paginateRows2(aggregatedRows, pagination) : { rows: aggregatedRows, pageCount: 1, pageIndex: 0 },
1938
2283
  [paginated, aggregatedRows, pagination]
1939
2284
  );
2285
+ const virtualizer = useRowVirtualizer({
2286
+ enabled: virtualized,
2287
+ rows: paginatedRows.rows,
2288
+ bodyScrollRef,
2289
+ estimatedRowHeight,
2290
+ overscan
2291
+ });
2292
+ useEffect4(() => {
2293
+ if (virtualized && height === void 0) {
2294
+ console.error(
2295
+ "DataGridComponent: `virtualized` has no effect without `height` \u2014 a body with no bounded height has no viewport to window rows against."
2296
+ );
2297
+ }
2298
+ }, [virtualized, height]);
2299
+ const hasInfiniteScroll = infiniteScroll !== void 0;
2300
+ useEffect4(() => {
2301
+ if (paginated && hasInfiniteScroll) {
2302
+ console.error(
2303
+ "DataGridComponent: `paginated` and `infiniteScroll` are mutually exclusive \u2014 only `paginated` takes effect while both are set."
2304
+ );
2305
+ }
2306
+ }, [paginated, hasInfiniteScroll]);
2307
+ useInfiniteScroll({
2308
+ rootRef: bodyScrollRef,
2309
+ sentinelRef,
2310
+ config: infiniteScroll
2311
+ });
1940
2312
  const paginationSnapshot = useMemo4(
1941
2313
  () => ({
1942
2314
  pageIndex: paginatedRows.pageIndex,
@@ -1969,7 +2341,10 @@ function DataGridComponent({
1969
2341
  }
1970
2342
  }
1971
2343
  const resize = useColumnResize({
1972
- tableRef,
2344
+ // `measureColumnContent` only ever matches `td[data-gridkit-column]`
2345
+ // (never `th`), so the header table has nothing for it to find — the
2346
+ // body table alone was already the effective scope before this split.
2347
+ tableRef: bodyTableRef,
1973
2348
  sizing,
1974
2349
  setSizing,
1975
2350
  columnSizeDefaults,
@@ -2009,7 +2384,11 @@ function DataGridComponent({
2009
2384
  onGroupExpansionChange: handleGroupExpansionChange
2010
2385
  });
2011
2386
  const nav = useGridNavigation({
2012
- tableRef,
2387
+ headerTableRef,
2388
+ bodyTableRef,
2389
+ // `null` when virtualization is off, in which case behavior is
2390
+ // byte-for-byte unchanged from before this feature existed.
2391
+ virtualRange: virtualized ? virtualizer : null,
2013
2392
  // Group headers are addressable rows too — the whole point of the flat
2014
2393
  // `DisplayRow[]` shape is that they share one position space with data
2015
2394
  // rows, so navigation counts them the same way. Page-relative
@@ -2095,7 +2474,7 @@ function DataGridComponent({
2095
2474
  rows: displayRows,
2096
2475
  onPaginationChange: handlePaginationChange
2097
2476
  });
2098
- const [previous, setPrevious] = useState5({ filter, sort, groupBy });
2477
+ const [previous, setPrevious] = useState6({ filter, sort, groupBy });
2099
2478
  if (previous.filter !== filter || previous.sort !== sort || previous.groupBy !== groupBy) {
2100
2479
  setPrevious({ filter, sort, groupBy });
2101
2480
  if (pagination.pageIndex !== 0) {
@@ -2117,7 +2496,7 @@ function DataGridComponent({
2117
2496
  columnName
2118
2497
  });
2119
2498
  const multiselectable = selection.rowMode === "multiple" || selection.columnMode === "multiple";
2120
- useEffect2(() => {
2499
+ useEffect4(() => {
2121
2500
  for (const listener of subscribersRef.current) {
2122
2501
  listener();
2123
2502
  }
@@ -2143,7 +2522,7 @@ function DataGridComponent({
2143
2522
  return viewportRef.current;
2144
2523
  },
2145
2524
  get table() {
2146
- return tableRef.current;
2525
+ return bodyTableRef.current;
2147
2526
  },
2148
2527
  getRows: () => shownRows,
2149
2528
  // `aggregatedRows`, not the plain `displayRows` it's derived from: the
@@ -2180,10 +2559,16 @@ function DataGridComponent({
2180
2559
  (entry) => !("kind" in entry) && entry.rowId === rowId
2181
2560
  );
2182
2561
  if (index === -1) return;
2183
- tableRef.current?.tBodies[0]?.rows[index]?.scrollIntoView(options);
2562
+ if (virtualized) {
2563
+ virtualizer.scrollToIndex(index, options);
2564
+ return;
2565
+ }
2566
+ bodyTableRef.current?.tBodies[0]?.rows[index]?.scrollIntoView(options);
2184
2567
  },
2185
2568
  scrollToColumn: (columnId, options) => {
2186
- const cells = tableRef.current?.querySelectorAll("[data-gridkit-column]");
2569
+ const cells = viewportRef.current?.querySelectorAll(
2570
+ "[data-gridkit-column]"
2571
+ );
2187
2572
  const cell = cells ? Array.from(cells).find(
2188
2573
  (entry) => entry.getAttribute("data-gridkit-column") === columnId
2189
2574
  ) : void 0;
@@ -2196,8 +2581,21 @@ function DataGridComponent({
2196
2581
  };
2197
2582
  }
2198
2583
  }));
2199
- return /* @__PURE__ */ jsxs6("div", { className: "gridkit-data-grid-viewport", ref: viewportRef, children: [
2200
- showGroupByBar && /* @__PURE__ */ jsx9(
2584
+ const gridTableClassName = classNames(
2585
+ "gridkit-data-grid",
2586
+ borders ? `borders-${borders}` : "",
2587
+ // Hover is on by default and selection off, so one set of classes
2588
+ // turns styling off and the other turns it on. The polarity differs
2589
+ // because the defaults do.
2590
+ hoverRows ? "" : "no-hover-rows",
2591
+ hoverColumns ? "" : "no-hover-columns",
2592
+ hoverCells ? "" : "no-hover-cells",
2593
+ selection.rowMode === false ? "" : "selectable-rows",
2594
+ selection.columnMode === false ? "" : "selectable-columns",
2595
+ selection.cellMode === false ? "" : "selectable-cells"
2596
+ );
2597
+ return /* @__PURE__ */ jsxs7("div", { className: "gridkit-data-grid-viewport", ref: viewportRef, children: [
2598
+ showGroupByBar && /* @__PURE__ */ jsx10(
2201
2599
  GroupByBar,
2202
2600
  {
2203
2601
  columns: resolved,
@@ -2208,10 +2606,10 @@ function DataGridComponent({
2208
2606
  headerDropTarget: headerGroupByDropTarget
2209
2607
  }
2210
2608
  ),
2211
- /* @__PURE__ */ jsxs6(
2212
- "table",
2609
+ /* @__PURE__ */ jsxs7(
2610
+ "div",
2213
2611
  {
2214
- ref: tableRef,
2612
+ className: "gridkit-data-grid-tables",
2215
2613
  role: groupBy.length > 0 ? "treegrid" : "grid",
2216
2614
  "aria-rowcount": aggregatedRows.length + 1,
2217
2615
  "aria-colcount": resolved.length,
@@ -2235,57 +2633,90 @@ function DataGridComponent({
2235
2633
  selection.selectAllRows();
2236
2634
  }
2237
2635
  },
2238
- style: { width: totalColumnWidth(resolved) },
2239
- className: classNames(
2240
- "gridkit-data-grid",
2241
- borders ? `borders-${borders}` : "",
2242
- // Hover is on by default and selection off, so one set of classes
2243
- // turns styling off and the other turns it on. The polarity differs
2244
- // because the defaults do.
2245
- hoverRows ? "" : "no-hover-rows",
2246
- hoverColumns ? "" : "no-hover-columns",
2247
- hoverCells ? "" : "no-hover-cells",
2248
- selection.rowMode === false ? "" : "selectable-rows",
2249
- selection.columnMode === false ? "" : "selectable-columns",
2250
- selection.cellMode === false ? "" : "selectable-cells"
2251
- ),
2252
2636
  children: [
2253
- /* @__PURE__ */ jsx9("colgroup", { children: resolved.map((entry) => /* @__PURE__ */ jsx9("col", { style: { width: entry.width } }, entry.id)) }),
2254
- /* @__PURE__ */ jsx9(
2255
- GridHeader,
2637
+ /* @__PURE__ */ jsxs7(
2638
+ "table",
2256
2639
  {
2257
- columns: resolved,
2258
- resize,
2259
- drag,
2260
- sort: columnSort,
2261
- sortableColumns,
2262
- grouping,
2263
- groupableColumns,
2264
- groupToggleIconColumns,
2265
- nav,
2266
- selection
2640
+ ref: headerTableRef,
2641
+ style: { width: totalColumnWidth(resolved) },
2642
+ className: classNames(gridTableClassName, "gridkit-data-grid-header"),
2643
+ children: [
2644
+ /* @__PURE__ */ jsx10(GridColgroup, { columns: resolved }),
2645
+ /* @__PURE__ */ jsx10(
2646
+ GridHeader,
2647
+ {
2648
+ columns: resolved,
2649
+ resize,
2650
+ drag,
2651
+ sort: columnSort,
2652
+ sortableColumns,
2653
+ grouping,
2654
+ groupableColumns,
2655
+ groupToggleIconColumns,
2656
+ nav,
2657
+ selection
2658
+ }
2659
+ )
2660
+ ]
2267
2661
  }
2268
2662
  ),
2269
- /* @__PURE__ */ jsx9(
2270
- GridBody,
2663
+ /* @__PURE__ */ jsx10(
2664
+ "div",
2271
2665
  {
2272
- columns: resolved,
2273
- rows: paginatedRows.rows,
2274
- activeColumnId: resize.activeColumnId,
2275
- nav,
2276
- selection,
2277
- grouping,
2278
- aggregates: activeAggregates,
2279
- groupAggregateDisplay
2666
+ className: "gridkit-data-grid-body",
2667
+ ref: bodyScrollRef,
2668
+ style: height === void 0 ? void 0 : {
2669
+ "--gridkit-body-height": typeof height === "number" ? `${String(height)}px` : height
2670
+ },
2671
+ children: /* @__PURE__ */ jsxs7(
2672
+ "table",
2673
+ {
2674
+ ref: bodyTableRef,
2675
+ style: { width: totalColumnWidth(resolved) },
2676
+ className: gridTableClassName,
2677
+ children: [
2678
+ /* @__PURE__ */ jsx10(GridColgroup, { columns: resolved }),
2679
+ /* @__PURE__ */ jsx10(
2680
+ GridBody,
2681
+ {
2682
+ columns: resolved,
2683
+ rows: paginatedRows.rows,
2684
+ activeColumnId: resize.activeColumnId,
2685
+ nav,
2686
+ selection,
2687
+ grouping,
2688
+ aggregates: activeAggregates,
2689
+ groupAggregateDisplay,
2690
+ virtualRange: virtualized ? virtualizer : null,
2691
+ infiniteScroll,
2692
+ sentinelRef
2693
+ }
2694
+ )
2695
+ ]
2696
+ }
2697
+ )
2280
2698
  }
2281
2699
  ),
2282
- activeAggregates.length > 0 && /* @__PURE__ */ jsx9(
2283
- GridFooter,
2700
+ activeAggregates.length > 0 && /* @__PURE__ */ jsxs7(
2701
+ "table",
2284
2702
  {
2285
- columns: resolved,
2286
- aggregates: activeAggregates,
2287
- results: grandTotal,
2288
- rows: shownRows.map((entry) => entry.row)
2703
+ style: { width: totalColumnWidth(resolved) },
2704
+ className: classNames(
2705
+ gridTableClassName,
2706
+ "gridkit-data-grid-footer"
2707
+ ),
2708
+ children: [
2709
+ /* @__PURE__ */ jsx10(GridColgroup, { columns: resolved }),
2710
+ /* @__PURE__ */ jsx10(
2711
+ GridFooter,
2712
+ {
2713
+ columns: resolved,
2714
+ aggregates: activeAggregates,
2715
+ results: grandTotal,
2716
+ rows: shownRows.map((entry) => entry.row)
2717
+ }
2718
+ )
2719
+ ]
2289
2720
  }
2290
2721
  )
2291
2722
  ]
@@ -2300,18 +2731,18 @@ function DataGridComponent({
2300
2731
  nextPage: paginationApi.nextPage,
2301
2732
  previousPage: paginationApi.previousPage,
2302
2733
  setPageSize: paginationApi.setPageSize
2303
- }) : /* @__PURE__ */ jsx9(GridPager, { pager: paginationApi, config: pager })),
2304
- /* @__PURE__ */ jsx9("div", { className: "gridkit-sr-only", role: "status", "aria-live": "polite", children: announcement })
2734
+ }) : /* @__PURE__ */ jsx10(GridPager, { pager: paginationApi, config: pager })),
2735
+ /* @__PURE__ */ jsx10("div", { className: "gridkit-sr-only", role: "status", "aria-live": "polite", children: announcement })
2305
2736
  ] });
2306
2737
  }
2307
2738
 
2308
2739
  // src/DataGrid/hooks/usePaginationState.ts
2309
- import { useCallback as useCallback2 } from "react";
2740
+ import { useCallback as useCallback4 } from "react";
2310
2741
 
2311
2742
  // src/DataGrid/hooks/useGridSnapshot.ts
2312
- import { useCallback, useSyncExternalStore } from "react";
2743
+ import { useCallback as useCallback3, useSyncExternalStore } from "react";
2313
2744
  function useGridSnapshot(gridRef, getSnapshot, fallback) {
2314
- const subscribe = useCallback(
2745
+ const subscribe = useCallback3(
2315
2746
  (listener) => {
2316
2747
  const api = gridRef.current;
2317
2748
  if (api === null) {
@@ -2333,12 +2764,12 @@ var FALLBACK_PAGINATION = { pageIndex: 0, pageSize: 0 };
2333
2764
  function usePaginationState(gridRef) {
2334
2765
  const pagination = useGridSnapshot(
2335
2766
  gridRef,
2336
- useCallback2((api) => api.getPagination(), []),
2767
+ useCallback4((api) => api.getPagination(), []),
2337
2768
  FALLBACK_PAGINATION
2338
2769
  );
2339
2770
  const pageCount = useGridSnapshot(
2340
2771
  gridRef,
2341
- useCallback2((api) => api.getPageCount(), []),
2772
+ useCallback4((api) => api.getPageCount(), []),
2342
2773
  0
2343
2774
  );
2344
2775
  return {
@@ -2360,18 +2791,18 @@ function usePaginationState(gridRef) {
2360
2791
  }
2361
2792
 
2362
2793
  // src/DataGrid/hooks/useGroupByState.ts
2363
- import { useCallback as useCallback3 } from "react";
2794
+ import { useCallback as useCallback5 } from "react";
2364
2795
  var FALLBACK_GROUP_BY = [];
2365
2796
  var FALLBACK_EXPANSION = [];
2366
2797
  function useGroupByState(gridRef) {
2367
2798
  const groupBy = useGridSnapshot(
2368
2799
  gridRef,
2369
- useCallback3((api) => api.getGroupBy(), []),
2800
+ useCallback5((api) => api.getGroupBy(), []),
2370
2801
  FALLBACK_GROUP_BY
2371
2802
  );
2372
2803
  const groupExpansion = useGridSnapshot(
2373
2804
  gridRef,
2374
- useCallback3((api) => api.getGroupExpansion(), []),
2805
+ useCallback5((api) => api.getGroupExpansion(), []),
2375
2806
  FALLBACK_EXPANSION
2376
2807
  );
2377
2808
  return {
@@ -2387,34 +2818,34 @@ function useGroupByState(gridRef) {
2387
2818
  }
2388
2819
 
2389
2820
  // src/DataGrid/hooks/useColumnSortState.ts
2390
- import { useCallback as useCallback4 } from "react";
2821
+ import { useCallback as useCallback6 } from "react";
2391
2822
  var FALLBACK_SORT = [];
2392
2823
  function useColumnSortState(gridRef) {
2393
2824
  return useGridSnapshot(
2394
2825
  gridRef,
2395
- useCallback4((api) => api.getColumnSort(), []),
2826
+ useCallback6((api) => api.getColumnSort(), []),
2396
2827
  FALLBACK_SORT
2397
2828
  );
2398
2829
  }
2399
2830
 
2400
2831
  // src/DataGrid/hooks/useSelectionState.ts
2401
- import { useCallback as useCallback5 } from "react";
2832
+ import { useCallback as useCallback7 } from "react";
2402
2833
  var FALLBACK_SELECTION = [];
2403
2834
  var FALLBACK_CELL_SELECTION = null;
2404
2835
  function useSelectionState(gridRef) {
2405
2836
  const rowSelection = useGridSnapshot(
2406
2837
  gridRef,
2407
- useCallback5((api) => api.getRowSelection(), []),
2838
+ useCallback7((api) => api.getRowSelection(), []),
2408
2839
  FALLBACK_SELECTION
2409
2840
  );
2410
2841
  const columnSelection = useGridSnapshot(
2411
2842
  gridRef,
2412
- useCallback5((api) => api.getColumnSelection(), []),
2843
+ useCallback7((api) => api.getColumnSelection(), []),
2413
2844
  FALLBACK_SELECTION
2414
2845
  );
2415
2846
  const cellSelection = useGridSnapshot(
2416
2847
  gridRef,
2417
- useCallback5((api) => api.getCellSelection(), []),
2848
+ useCallback7((api) => api.getCellSelection(), []),
2418
2849
  FALLBACK_CELL_SELECTION
2419
2850
  );
2420
2851
  return {
@@ -2431,34 +2862,34 @@ function useSelectionState(gridRef) {
2431
2862
  }
2432
2863
 
2433
2864
  // src/DataGrid/hooks/useAggregateState.ts
2434
- import { useCallback as useCallback6 } from "react";
2865
+ import { useCallback as useCallback8 } from "react";
2435
2866
  var FALLBACK_AGGREGATES = /* @__PURE__ */ new Map();
2436
2867
  function useAggregateState(gridRef) {
2437
2868
  return useGridSnapshot(
2438
2869
  gridRef,
2439
- useCallback6((api) => api.getAggregates(), []),
2870
+ useCallback8((api) => api.getAggregates(), []),
2440
2871
  FALLBACK_AGGREGATES
2441
2872
  );
2442
2873
  }
2443
2874
 
2444
2875
  // src/DataGrid/hooks/useColumnSizingState.ts
2445
- import { useCallback as useCallback7 } from "react";
2876
+ import { useCallback as useCallback9 } from "react";
2446
2877
  var FALLBACK_SIZING = {};
2447
2878
  function useColumnSizingState(gridRef) {
2448
2879
  return useGridSnapshot(
2449
2880
  gridRef,
2450
- useCallback7((api) => api.getColumnSizing(), []),
2881
+ useCallback9((api) => api.getColumnSizing(), []),
2451
2882
  FALLBACK_SIZING
2452
2883
  );
2453
2884
  }
2454
2885
 
2455
2886
  // src/DataGrid/hooks/useColumnOrderState.ts
2456
- import { useCallback as useCallback8 } from "react";
2887
+ import { useCallback as useCallback10 } from "react";
2457
2888
  var FALLBACK_ORDER = [];
2458
2889
  function useColumnOrderState(gridRef) {
2459
2890
  return useGridSnapshot(
2460
2891
  gridRef,
2461
- useCallback8((api) => api.getColumnOrder(), []),
2892
+ useCallback10((api) => api.getColumnOrder(), []),
2462
2893
  FALLBACK_ORDER
2463
2894
  );
2464
2895
  }