@gridkitjs/react 0.8.0 → 0.10.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,29 +1,36 @@
1
+ "use client";
2
+
1
3
  // src/DataGrid/DataGrid.tsx
2
4
  import {
3
- useEffect as useEffect2,
5
+ useEffect as useEffect3,
4
6
  useImperativeHandle,
5
- useMemo as useMemo4,
7
+ useMemo as useMemo3,
6
8
  useRef as useRef4,
7
- useState as useState5
9
+ useState as useState8
8
10
  } from "react";
9
11
  import {
10
- applyColumnOrder,
11
- computeAggregates,
12
- defineColumnsFromRows,
13
- fitColumnsToWidth,
14
- groupRows,
15
12
  moveColumnBefore,
16
- paginateRows as paginateRows2,
17
- resolveColumnWidths,
18
- resolveRowId,
19
- resolveShownRows,
20
- totalColumnWidth,
21
- withGroupAggregates
13
+ resolveColumnHeaderText,
14
+ resolveMessages,
15
+ resolveRows,
16
+ totalColumnWidth
22
17
  } from "@gridkitjs/core";
23
18
 
19
+ // src/DataGrid/components/GridColgroup.tsx
20
+ import { jsx } from "react/jsx-runtime";
21
+ function GridColgroup({ columns }) {
22
+ return /* @__PURE__ */ jsx("colgroup", { children: columns.map((entry) => /* @__PURE__ */ jsx("col", { style: { width: entry.width } }, entry.id)) });
23
+ }
24
+
24
25
  // src/DataGrid/components/GridHeader.tsx
25
26
  import { createPortal } from "react-dom";
26
- import { buildKeyShortcuts, intentOf, KEYBOARD_STEP } from "@gridkitjs/core";
27
+ import {
28
+ buildKeyShortcuts,
29
+ intentOf,
30
+ KEYBOARD_STEP,
31
+ sortDirectionFor,
32
+ sortPriorityFor
33
+ } from "@gridkitjs/core";
27
34
 
28
35
  // src/DataGrid/ariaAttr.ts
29
36
  function ariaAttr(condition, key, value) {
@@ -38,133 +45,67 @@ function classNames(...values) {
38
45
  // src/DataGrid/useGridNavigation.ts
39
46
  import {
40
47
  useEffect,
41
- useMemo,
42
- useRef,
43
- useState
48
+ useLayoutEffect,
49
+ useState,
50
+ useSyncExternalStore
44
51
  } from "react";
52
+ import { HEADER_ROW } from "@gridkitjs/core";
45
53
  import {
46
- clampFocus,
47
- nextFocusForKey,
48
- HEADER_ROW
49
- } from "@gridkitjs/core";
50
- var FALLBACK_PAGE = 10;
54
+ createGridFocus,
55
+ findBodyRow,
56
+ measurePageSize
57
+ } from "@gridkitjs/dom";
51
58
  function tabIndexFor(columnIndex, focusedColumnIndex) {
52
59
  return columnIndex === focusedColumnIndex ? 0 : -1;
53
60
  }
54
- function cellAt(table, focus) {
55
- const row = focus.rowIndex === HEADER_ROW ? table.tHead?.rows[0] ?? null : table.tBodies[0]?.rows[focus.rowIndex] ?? null;
56
- if (row === null) {
57
- return null;
58
- }
59
- const columnIndex = Math.min(focus.columnIndex, row.cells.length - 1);
60
- return row.cells[columnIndex] ?? null;
61
- }
62
61
  function useGridNavigation({
63
- tableRef,
62
+ headerTableRef,
63
+ bodyTableRef,
64
64
  rowCount,
65
65
  columnCount,
66
- isSkippableRow
66
+ isSkippableRow,
67
+ virtualRange
67
68
  }) {
68
- const [stored, setStored] = useState({
69
- rowIndex: HEADER_ROW,
70
- columnIndex: 0
71
- });
72
- const focus = useMemo(
73
- () => clampFocus(stored, rowCount, columnCount),
74
- [stored, rowCount, columnCount]
69
+ const options = {
70
+ rowCount,
71
+ columnCount,
72
+ isSkippableRow,
73
+ virtualRange
74
+ };
75
+ const [gridFocus] = useState(() => createGridFocus(options));
76
+ gridFocus.setOptions(options);
77
+ const focus = useSyncExternalStore(gridFocus.subscribe, gridFocus.getFocus);
78
+ useLayoutEffect(
79
+ () => gridFocus.attach({
80
+ getHeaderRow: () => headerTableRef.current?.tHead?.rows[0] ?? null,
81
+ getBodyRow: (rowIndex) => findBodyRow(bodyTableRef.current, rowIndex)
82
+ }),
83
+ [gridFocus, headerTableRef, bodyTableRef]
75
84
  );
76
- const pending = useRef(false);
77
85
  useEffect(() => {
78
- if (!pending.current) {
79
- return;
80
- }
81
- pending.current = false;
82
- const table = tableRef.current;
83
- if (table === null) {
84
- return;
85
- }
86
- cellAt(table, { rowIndex: focus.rowIndex, columnIndex: focus.columnIndex })?.focus();
87
- }, [tableRef, focus.rowIndex, focus.columnIndex]);
88
- function pageSize() {
89
- const table = tableRef.current;
90
- const firstRow = table?.tBodies[0]?.rows[0];
91
- const viewport = table?.parentElement;
92
- if (firstRow === void 0 || viewport === null || viewport === void 0 || firstRow.offsetHeight === 0) {
93
- return FALLBACK_PAGE;
94
- }
95
- return Math.max(
96
- 1,
97
- Math.floor(viewport.clientHeight / firstRow.offsetHeight)
98
- );
99
- }
100
- function focusCell(rowIndex, columnIndex) {
101
- const next = clampFocus({ rowIndex, columnIndex }, rowCount, columnCount);
102
- if (next.rowIndex === focus.rowIndex && next.columnIndex === focus.columnIndex) {
103
- return;
104
- }
105
- pending.current = true;
106
- setStored(next);
107
- }
108
- function onKeyDown(event) {
109
- const next = nextFocusForKey(
110
- event.key,
111
- {
112
- ctrlKey: event.ctrlKey,
113
- metaKey: event.metaKey,
114
- altKey: event.altKey,
115
- shiftKey: event.shiftKey
116
- },
117
- focus,
118
- rowCount,
119
- columnCount,
120
- pageSize(),
121
- isSkippableRow
122
- );
123
- if (next === null) {
124
- return;
125
- }
126
- event.preventDefault();
127
- focusCell(next.rowIndex, next.columnIndex);
128
- }
86
+ gridFocus.focusDrawnCell(focus);
87
+ });
129
88
  return {
130
89
  focus,
131
90
  tabIndexFor: (rowIndex, columnIndex) => tabIndexFor(
132
91
  columnIndex,
133
92
  rowIndex === focus.rowIndex ? focus.columnIndex : null
134
93
  ),
135
- focusCell,
136
- onKeyDown
94
+ focusCell: gridFocus.focusCell,
95
+ onKeyDown: (event) => {
96
+ if (gridFocus.handleKey(
97
+ event.key,
98
+ event,
99
+ measurePageSize(bodyTableRef.current)
100
+ )) {
101
+ event.preventDefault();
102
+ }
103
+ }
137
104
  };
138
105
  }
139
106
 
140
107
  // src/DataGrid/useGridSelection.ts
141
- import {
142
- useMemo as useMemo2,
143
- useRef as useRef2
144
- } from "react";
145
- import {
146
- applySelectionIntent,
147
- clearSelection,
148
- diffSelection,
149
- resolveCell,
150
- resolveColumns,
151
- resolveRows,
152
- selectAll,
153
- selectCell,
154
- toggleCellSelection
155
- } from "@gridkitjs/core";
156
-
157
- // src/DataGrid/commitIfChanged.ts
158
- function commitIfChanged(current, next, setState, onCommit) {
159
- if (next === current) {
160
- return false;
161
- }
162
- setState(next);
163
- onCommit?.(next);
164
- return true;
165
- }
166
-
167
- // src/DataGrid/useGridSelection.ts
108
+ import { useMemo } from "react";
168
109
  function keyboardSelectIntent(event) {
169
110
  return {
170
111
  ctrlKey: event.key === " " || event.ctrlKey,
@@ -172,193 +113,39 @@ function keyboardSelectIntent(event) {
172
113
  shiftKey: event.shiftKey
173
114
  };
174
115
  }
175
- function countMessage(count, noun) {
176
- if (count === 0) {
177
- return `No ${noun}s selected`;
178
- }
179
- return count === 1 ? `1 ${noun} selected` : `${String(count)} ${noun}s selected`;
180
- }
181
- function useAsSet(values) {
182
- return useMemo2(() => new Set(values), [values]);
183
- }
184
- function useIndexedBy(values, key) {
185
- return useMemo2(
186
- () => new Map(values.map((value) => [value[key], value])),
187
- [values, key]
188
- );
189
- }
190
- function useMappedArray(values, key) {
191
- return useMemo2(() => values.map((value) => value[key]), [values, key]);
192
- }
193
116
  function useGridSelection({
194
- rows,
195
- columns,
196
- selectable,
197
- rowSelection,
198
- setRowSelection,
199
- columnSelection,
200
- setColumnSelection,
201
- cellSelection,
202
- setCellSelection,
203
- callbacks,
204
- announce,
205
- columnName
117
+ controller,
118
+ state,
119
+ selectable
206
120
  }) {
207
- const rowMode = selectable?.rows ?? false;
208
- const columnMode = selectable?.columns ?? false;
209
- const cellMode = selectable?.cells ?? false;
210
- const rowsById = useIndexedBy(rows, "rowId");
211
- const rowIds = useMappedArray(rows, "rowId");
212
- const columnIds = useMappedArray(columns, "id");
213
- const selectedRowIds = useAsSet(rowSelection);
214
- const selectedColumnIds = useAsSet(columnSelection);
215
- const rowAnchor = useRef2(null);
216
- const columnAnchor = useRef2(null);
217
- function announceCount(changed, total, noun) {
218
- if (changed > 1) {
219
- announce(countMessage(total, noun));
220
- }
221
- }
222
- function commitRows(next) {
223
- commitIfChanged(rowSelection, next, setRowSelection, (committed) => {
224
- const { added, removed } = diffSelection(rowSelection, committed);
225
- const addedRows = resolveRows(rowsById, added);
226
- const removedRows = resolveRows(rowsById, removed);
227
- for (const row of addedRows) {
228
- callbacks.onRowSelect?.({ row, selection: committed });
229
- }
230
- if (addedRows.length > 0) {
231
- callbacks.onRowsSelect?.({ rows: addedRows, selection: committed });
232
- }
233
- for (const row of removedRows) {
234
- callbacks.onRowDeselect?.({ row, selection: committed });
235
- }
236
- if (removedRows.length > 0) {
237
- callbacks.onRowsDeselect?.({ rows: removedRows, selection: committed });
238
- }
239
- callbacks.onRowSelectionChange?.({
240
- added: addedRows,
241
- removed: removedRows,
242
- selected: resolveRows(rowsById, committed),
243
- selection: committed
244
- });
245
- announceCount(added.length + removed.length, committed.length, "row");
246
- });
247
- }
248
- function commitColumns(next) {
249
- commitIfChanged(columnSelection, next, setColumnSelection, (committed) => {
250
- const { added, removed } = diffSelection(columnSelection, committed);
251
- const addedColumns = resolveColumns(columns, added);
252
- const removedColumns = resolveColumns(columns, removed);
253
- for (const column of addedColumns) {
254
- callbacks.onColumnSelect?.({ column, selection: committed });
255
- }
256
- if (addedColumns.length > 0) {
257
- callbacks.onColumnsSelect?.({
258
- columns: addedColumns,
259
- selection: committed
260
- });
261
- }
262
- for (const column of removedColumns) {
263
- callbacks.onColumnDeselect?.({ column, selection: committed });
264
- }
265
- if (removedColumns.length > 0) {
266
- callbacks.onColumnsDeselect?.({
267
- columns: removedColumns,
268
- selection: committed
269
- });
270
- }
271
- callbacks.onColumnSelectionChange?.({
272
- added: addedColumns,
273
- removed: removedColumns,
274
- selected: resolveColumns(columns, committed),
275
- selection: committed
276
- });
277
- announceCount(added.length + removed.length, committed.length, "column");
278
- });
279
- }
280
- function commitCell(next) {
281
- commitIfChanged(cellSelection, next, setCellSelection, (committed) => {
282
- const selected = resolveCell(rowsById, columns, committed);
283
- const deselected = resolveCell(rowsById, columns, cellSelection);
284
- if (deselected !== null) {
285
- callbacks.onCellDeselect?.({ cell: deselected, selection: committed });
286
- }
287
- if (selected !== null) {
288
- callbacks.onCellSelect?.({ cell: selected, selection: committed });
289
- }
290
- callbacks.onCellSelectionChange?.({
291
- selected,
292
- deselected,
293
- selection: committed
294
- });
295
- if (selected !== null) {
296
- announce(
297
- `${columnName(selected.columnId)}, row ${String(selected.rowIndex + 1)}, selected`
298
- );
299
- }
300
- });
301
- }
302
- function selectRow(rowId, intent) {
303
- const next = applySelectionIntent(
304
- rowSelection,
305
- rowIds,
306
- rowAnchor.current,
307
- rowId,
308
- intent,
309
- rowMode
310
- );
311
- if (intent !== "range") {
312
- rowAnchor.current = rowId;
313
- }
314
- commitRows(next);
315
- }
316
- function selectColumn(columnId, intent) {
317
- const next = applySelectionIntent(
318
- columnSelection,
319
- columnIds,
320
- columnAnchor.current,
321
- columnId,
322
- intent,
323
- columnMode
324
- );
325
- if (intent !== "range") {
326
- columnAnchor.current = columnId;
327
- }
328
- commitColumns(next);
329
- }
330
- function selectCellAt(cell, intent) {
331
- commitCell(
332
- intent === "toggle" ? toggleCellSelection(cellSelection, cell, cellMode) : selectCell(cellSelection, cell, cellMode)
333
- );
334
- }
335
- function selectAllRows() {
336
- commitRows(selectAll(rowSelection, rowIds, rowMode));
337
- }
338
- function clear() {
339
- commitRows(clearSelection(rowSelection));
340
- commitColumns(clearSelection(columnSelection));
341
- commitCell(null);
342
- }
121
+ const selectedRowIds = useMemo(
122
+ () => new Set(state.rowSelection),
123
+ [state.rowSelection]
124
+ );
125
+ const selectedColumnIds = useMemo(
126
+ () => new Set(state.columnSelection),
127
+ [state.columnSelection]
128
+ );
343
129
  return {
344
- rowMode,
345
- columnMode,
346
- cellMode,
130
+ rowMode: selectable?.rows ?? false,
131
+ columnMode: selectable?.columns ?? false,
132
+ cellMode: selectable?.cells ?? false,
133
+ toggleOnClick: selectable?.toggleOnClick ?? false,
347
134
  selectedRowIds,
348
135
  selectedColumnIds,
349
- selectedCell: cellSelection,
350
- selectRow,
351
- selectColumn,
352
- selectCell: selectCellAt,
353
- selectAllRows,
354
- clear
136
+ selectedCell: state.cellSelection,
137
+ selectRow: controller.selectRow,
138
+ selectColumn: controller.selectColumn,
139
+ selectCell: controller.selectCell,
140
+ selectAllRows: controller.selectAllRows,
141
+ clear: controller.clearSelection
355
142
  };
356
143
  }
357
144
 
358
145
  // src/DataGrid/components/GridHeader.tsx
359
- import { jsx, jsxs } from "react/jsx-runtime";
146
+ import { jsx as jsx2, jsxs } from "react/jsx-runtime";
360
147
  function Icon({ children }) {
361
- return /* @__PURE__ */ jsx(
148
+ return /* @__PURE__ */ jsx2(
362
149
  "svg",
363
150
  {
364
151
  viewBox: "0 0 24 24",
@@ -374,27 +161,28 @@ function Icon({ children }) {
374
161
  }
375
162
  function ChevronsUpDownIcon() {
376
163
  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" })
164
+ /* @__PURE__ */ jsx2("path", { d: "m7 15 5 5 5-5" }),
165
+ /* @__PURE__ */ jsx2("path", { d: "m7 9 5-5 5 5" })
379
166
  ] });
380
167
  }
381
168
  function ChevronUpIcon() {
382
- return /* @__PURE__ */ jsx(Icon, { children: /* @__PURE__ */ jsx("path", { d: "m18 15-6-6-6 6" }) });
169
+ return /* @__PURE__ */ jsx2(Icon, { children: /* @__PURE__ */ jsx2("path", { d: "m18 15-6-6-6 6" }) });
383
170
  }
384
171
  function GroupIcon() {
385
172
  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" })
173
+ /* @__PURE__ */ jsx2("rect", { x: "3", y: "3", width: "7", height: "7", rx: "1" }),
174
+ /* @__PURE__ */ jsx2("rect", { x: "14", y: "3", width: "7", height: "7", rx: "1" }),
175
+ /* @__PURE__ */ jsx2("rect", { x: "8.5", y: "14", width: "7", height: "7", rx: "1" })
389
176
  ] });
390
177
  }
391
178
  function GridHeader({
392
179
  columns,
393
180
  resize,
394
181
  drag,
395
- sort,
182
+ controller,
183
+ columnSort,
396
184
  sortableColumns,
397
- grouping,
185
+ groupBy,
398
186
  groupableColumns,
399
187
  groupToggleIconColumns,
400
188
  nav,
@@ -403,7 +191,7 @@ function GridHeader({
403
191
  const beforeId = drag.dropTarget?.kind === "column-order" ? drag.dropTarget.beforeId : void 0;
404
192
  const draggedEntry = columns.find((entry) => entry.id === drag.draggedColumnId) ?? null;
405
193
  return /* @__PURE__ */ jsxs("thead", { children: [
406
- /* @__PURE__ */ jsx("tr", { className: "grid-header", role: "row", "aria-rowindex": 1, children: columns.map((entry, index) => {
194
+ /* @__PURE__ */ jsx2("tr", { className: "grid-header", role: "row", "aria-rowindex": 1, children: columns.map((entry, index) => {
407
195
  const { column } = entry;
408
196
  const resizing = resize.activeColumnId === entry.id;
409
197
  const dragging = drag.draggedColumnId === entry.id;
@@ -411,11 +199,11 @@ function GridHeader({
411
199
  const dropAfter = beforeId === null && index === columns.length - 1;
412
200
  const selected = selection.selectedColumnIds.has(entry.id);
413
201
  const sortable = column.sortable ?? sortableColumns;
414
- const sortDirection = sort.directionFor(entry.id);
415
- const sortPriority = sort.priorityFor(entry.id);
202
+ const sortDirection = sortDirectionFor(columnSort, entry.id);
203
+ const sortPriority = sortPriorityFor(columnSort, entry.id);
416
204
  const groupable = column.groupable ?? groupableColumns;
417
205
  const showGroupToggleIcon = column.groupToggleIcon ?? groupToggleIconColumns;
418
- const isGrouped = grouping.groupBy.some(
206
+ const isGrouped = groupBy.some(
419
207
  (level) => level.columnId === entry.id
420
208
  );
421
209
  const shortcuts = buildKeyShortcuts({
@@ -469,12 +257,14 @@ function GridHeader({
469
257
  }
470
258
  if (event.key === "ArrowUp" && event.altKey && sortable) {
471
259
  event.preventDefault();
472
- sort.toggle(entry, { shiftKey: event.shiftKey });
260
+ controller.toggleColumnSort(entry.id, {
261
+ stack: event.shiftKey
262
+ });
473
263
  return;
474
264
  }
475
265
  if (event.key === "ArrowDown" && event.altKey && groupable) {
476
266
  event.preventDefault();
477
- grouping.toggleGroupBy(entry.id);
267
+ controller.toggleGroupBy(entry.id);
478
268
  return;
479
269
  }
480
270
  if (event.key === " " || event.key === "Enter") {
@@ -523,7 +313,7 @@ function GridHeader({
523
313
  * A pointer affordance only, same as the other two — the
524
314
  * keyboard equivalent is Alt+ArrowDown on the header itself.
525
315
  */
526
- /* @__PURE__ */ jsx(
316
+ /* @__PURE__ */ jsx2(
527
317
  "span",
528
318
  {
529
319
  className: classNames(
@@ -537,9 +327,9 @@ function GridHeader({
537
327
  },
538
328
  onClick: (event) => {
539
329
  event.stopPropagation();
540
- grouping.toggleGroupBy(entry.id);
330
+ controller.toggleGroupBy(entry.id);
541
331
  },
542
- children: /* @__PURE__ */ jsx(GroupIcon, {})
332
+ children: /* @__PURE__ */ jsx2(GroupIcon, {})
543
333
  }
544
334
  ),
545
335
  entry.label,
@@ -562,11 +352,13 @@ function GridHeader({
562
352
  event.stopPropagation();
563
353
  },
564
354
  onClick: (event) => {
565
- sort.toggle(entry, { shiftKey: event.shiftKey });
355
+ controller.toggleColumnSort(entry.id, {
356
+ stack: event.shiftKey
357
+ });
566
358
  },
567
359
  children: [
568
- sortDirection === null ? /* @__PURE__ */ jsx(ChevronsUpDownIcon, {}) : /* @__PURE__ */ jsx(ChevronUpIcon, {}),
569
- sortPriority !== null && sortPriority > 1 && /* @__PURE__ */ jsx("span", { className: "header-sort-priority", children: sortPriority })
360
+ sortDirection === null ? /* @__PURE__ */ jsx2(ChevronsUpDownIcon, {}) : /* @__PURE__ */ jsx2(ChevronUpIcon, {}),
361
+ sortPriority !== null && sortPriority > 1 && /* @__PURE__ */ jsx2("span", { className: "header-sort-priority", children: sortPriority })
570
362
  ]
571
363
  }
572
364
  ),
@@ -576,7 +368,7 @@ function GridHeader({
576
368
  * put N tab stops inside a widget that is meant to have one.
577
369
  * The keyboard resizes from the header itself, under Alt.
578
370
  */
579
- /* @__PURE__ */ jsx(
371
+ /* @__PURE__ */ jsx2(
580
372
  "span",
581
373
  {
582
374
  className: "header-resize-handle",
@@ -596,7 +388,7 @@ function GridHeader({
596
388
  );
597
389
  }) }),
598
390
  draggedEntry !== null && drag.ghostTransform !== null && createPortal(
599
- /* @__PURE__ */ jsx(
391
+ /* @__PURE__ */ jsx2(
600
392
  "div",
601
393
  {
602
394
  className: "gridkit-data-grid header-cell drag-ghost",
@@ -614,80 +406,101 @@ function GridHeader({
614
406
  }
615
407
 
616
408
  // src/DataGrid/components/GridBody.tsx
617
- import { useMemo as useMemo3 } from "react";
409
+ import {
410
+ useMemo as useMemo2,
411
+ useState as useState2
412
+ } from "react";
618
413
  import {
619
414
  groupRowId,
620
415
  intentOf as intentOf2
621
416
  } from "@gridkitjs/core";
417
+ import {
418
+ bodyCellFrom,
419
+ bodyRowFrom,
420
+ groupRowFrom
421
+ } from "@gridkitjs/dom";
422
+
423
+ // src/DataGrid/rowProps.ts
424
+ var NO_ATTRIBUTES = {};
425
+ var NO_ROW_PROPS = {
426
+ className: "",
427
+ attributes: NO_ATTRIBUTES
428
+ };
429
+ function sameAttributes(a, b) {
430
+ const keys = Object.keys(a);
431
+ return keys.length === Object.keys(b).length && keys.every((key) => a[key] === b[key]);
432
+ }
433
+ function createRowPropsResolver() {
434
+ let previous = /* @__PURE__ */ new Map();
435
+ return (rows, getRowProps) => {
436
+ if (getRowProps === void 0) {
437
+ previous = /* @__PURE__ */ new Map();
438
+ return () => NO_ROW_PROPS;
439
+ }
440
+ const resolved = /* @__PURE__ */ new Map();
441
+ const attributesById = /* @__PURE__ */ new Map();
442
+ for (const entry of rows) {
443
+ const { className = "", attributes = NO_ATTRIBUTES } = getRowProps(entry);
444
+ const last = previous.get(entry.rowId);
445
+ const stable = last !== void 0 && sameAttributes(last, attributes) ? last : attributes;
446
+ attributesById.set(entry.rowId, stable);
447
+ resolved.set(entry.rowId, { className, attributes: stable });
448
+ }
449
+ previous = attributesById;
450
+ return (rowId) => resolved.get(rowId) ?? NO_ROW_PROPS;
451
+ };
452
+ }
622
453
 
623
454
  // src/DataGrid/components/GridGroupRow.tsx
624
455
  import { memo } from "react";
456
+ import {
457
+ formatCellValue,
458
+ resolveColumnLabel
459
+ } from "@gridkitjs/core";
625
460
 
626
- // src/DataGrid/components/formatAggregateValue.ts
627
- function formatAggregateValue(value) {
461
+ // src/DataGrid/components/renderAggregate.ts
462
+ import {
463
+ formatAggregateValue
464
+ } from "@gridkitjs/core";
465
+ function renderAggregate(value, spec, column, rows, locale) {
466
+ if (column?.footerTemplate) {
467
+ return column.footerTemplate({ value, rows });
468
+ }
628
469
  if (value === null || value === void 0) {
629
470
  return "\u2014";
630
471
  }
631
- if (value instanceof Date) {
632
- return value.toLocaleString();
633
- }
634
- switch (typeof value) {
635
- case "string":
636
- case "boolean":
637
- case "bigint":
638
- case "symbol":
639
- return String(value);
640
- case "number":
641
- return value.toLocaleString();
642
- case "function":
643
- return "(function)";
644
- default:
645
- return JSON.stringify(value);
646
- }
472
+ return formatAggregateValue(value, spec, column, locale);
647
473
  }
648
474
 
649
475
  // src/DataGrid/components/GridGroupRow.tsx
650
- import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
651
- function formatGroupValue(value) {
652
- if (value === null || value === void 0) {
653
- return "(blank)";
654
- }
655
- if (value instanceof Date) {
656
- return value.toLocaleString();
657
- }
658
- switch (typeof value) {
659
- case "string":
660
- case "number":
661
- case "boolean":
662
- case "bigint":
663
- case "symbol":
664
- return String(value);
665
- case "function":
666
- return "(function)";
667
- default:
668
- return JSON.stringify(value);
669
- }
476
+ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
477
+ function formatGroupValue(value, column, locale, messages) {
478
+ return value === null || value === void 0 ? messages.blankGroupValue : formatCellValue(value, column, locale, void 0, messages);
670
479
  }
671
480
  function GridGroupRowComponent({
672
481
  columnCount,
673
482
  groupId,
674
483
  level,
675
- columnLabel,
484
+ groupColumn,
676
485
  value,
677
486
  expanded,
678
487
  count,
488
+ rowIndex,
679
489
  datasetIndex,
680
490
  posinset,
681
491
  setsize,
682
492
  focused,
683
493
  aggregates,
684
494
  results,
685
- columns
495
+ definitions,
496
+ locale,
497
+ cellMessages,
498
+ ref
686
499
  }) {
687
- const byId = new Map(columns.map((entry) => [entry.id, entry]));
688
- return /* @__PURE__ */ jsx2(
500
+ return /* @__PURE__ */ jsx3(
689
501
  "tr",
690
502
  {
503
+ ref,
691
504
  role: "row",
692
505
  "aria-rowindex": datasetIndex + 2,
693
506
  "aria-level": level + 1,
@@ -695,11 +508,12 @@ function GridGroupRowComponent({
695
508
  "aria-setsize": setsize,
696
509
  "aria-posinset": posinset,
697
510
  "data-gridkit-group": groupId,
511
+ "data-gridkit-row-index": rowIndex,
698
512
  className: classNames(
699
513
  "grid-group-row",
700
514
  `is-group-level-${String(level)}`
701
515
  ),
702
- children: /* @__PURE__ */ jsx2(
516
+ children: /* @__PURE__ */ jsx3(
703
517
  "td",
704
518
  {
705
519
  role: "gridcell",
@@ -713,7 +527,7 @@ function GridGroupRowComponent({
713
527
  className: "grid-group-cell-content",
714
528
  style: { paddingInlineStart: `${String(level * 1.25 + 0.5)}rem` },
715
529
  children: [
716
- /* @__PURE__ */ jsx2(
530
+ /* @__PURE__ */ jsx3(
717
531
  "span",
718
532
  {
719
533
  className: classNames(
@@ -724,24 +538,28 @@ function GridGroupRowComponent({
724
538
  }
725
539
  ),
726
540
  /* @__PURE__ */ jsxs2("span", { className: "group-label", children: [
727
- columnLabel,
728
- ": ",
729
- formatGroupValue(value)
541
+ resolveColumnLabel(groupColumn),
542
+ ":",
543
+ " ",
544
+ formatGroupValue(value, groupColumn, locale, cellMessages)
730
545
  ] }),
731
546
  /* @__PURE__ */ jsxs2("span", { className: "group-count", children: [
732
547
  "(",
733
548
  count,
734
549
  ")"
735
550
  ] }),
736
- aggregates.length > 0 && /* @__PURE__ */ jsx2("span", { className: "group-aggregates", children: aggregates.map((spec) => {
551
+ aggregates.length > 0 && /* @__PURE__ */ jsx3("span", { className: "group-aggregates", children: aggregates.map((spec) => {
737
552
  const key = spec.id ?? spec.columnId;
738
553
  const aggregateValue = results.get(key);
739
- const column = byId.get(spec.columnId);
740
- const rendered = column?.column.footerTemplate ? column.column.footerTemplate({
741
- value: aggregateValue,
742
- rows: []
743
- }) : formatAggregateValue(aggregateValue);
744
- const aggregateLabel = spec.id ?? column?.label ?? spec.columnId;
554
+ const column = definitions.get(spec.columnId);
555
+ const rendered = renderAggregate(
556
+ aggregateValue,
557
+ spec,
558
+ column,
559
+ [],
560
+ locale
561
+ );
562
+ const aggregateLabel = spec.id ?? (column === void 0 ? spec.columnId : resolveColumnLabel(column));
745
563
  return /* @__PURE__ */ jsxs2("span", { className: "group-aggregate", children: [
746
564
  aggregateLabel,
747
565
  ": ",
@@ -763,21 +581,26 @@ var GridGroupRow_default = GridGroupRow;
763
581
 
764
582
  // src/DataGrid/components/GridGroupSummaryRow.tsx
765
583
  import { memo as memo2 } from "react";
766
- import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
584
+ import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
767
585
  function GridGroupSummaryRowComponent({
768
586
  groupId,
769
587
  level,
588
+ rowIndex,
770
589
  datasetIndex,
771
590
  results,
772
591
  aggregates,
773
- columns
592
+ columns,
593
+ locale,
594
+ ref
774
595
  }) {
775
- return /* @__PURE__ */ jsx3(
596
+ return /* @__PURE__ */ jsx4(
776
597
  "tr",
777
598
  {
599
+ ref,
778
600
  role: "row",
779
601
  "aria-rowindex": datasetIndex + 2,
780
602
  "data-gridkit-group-summary": groupId,
603
+ "data-gridkit-row-index": rowIndex,
781
604
  className: classNames(
782
605
  "grid-group-summary-row",
783
606
  `is-group-level-${String(level)}`
@@ -786,7 +609,6 @@ function GridGroupSummaryRowComponent({
786
609
  const specs = aggregates.filter(
787
610
  (candidate) => candidate.columnId === entry.id
788
611
  );
789
- const { column } = entry;
790
612
  return /* @__PURE__ */ jsxs3(
791
613
  "td",
792
614
  {
@@ -795,7 +617,7 @@ function GridGroupSummaryRowComponent({
795
617
  className: "grid-group-summary-cell",
796
618
  style: { textAlign: specs[0]?.alignment ?? entry.alignment },
797
619
  children: [
798
- entry.id === columns[0]?.id && /* @__PURE__ */ jsx3(
620
+ entry.id === columns[0]?.id && /* @__PURE__ */ jsx4(
799
621
  "span",
800
622
  {
801
623
  className: "grid-group-summary-indent",
@@ -803,10 +625,15 @@ function GridGroupSummaryRowComponent({
803
625
  "aria-hidden": "true"
804
626
  }
805
627
  ),
806
- specs.map((spec) => {
807
- const value = results.get(spec.id ?? spec.columnId);
808
- return column.footerTemplate ? column.footerTemplate({ value, rows: [] }) : formatAggregateValue(value);
809
- }).flatMap(
628
+ specs.map(
629
+ (spec) => renderAggregate(
630
+ results.get(spec.id ?? spec.columnId),
631
+ spec,
632
+ entry.column,
633
+ [],
634
+ locale
635
+ )
636
+ ).flatMap(
810
637
  (rendered, index) => index === 0 ? [rendered] : [", ", rendered]
811
638
  )
812
639
  ]
@@ -823,9 +650,12 @@ var GridGroupSummaryRow = memo2(
823
650
  var GridGroupSummaryRow_default = GridGroupSummaryRow;
824
651
 
825
652
  // src/DataGrid/components/GridRow.tsx
826
- import { accessDotted } from "@gridkitjs/core";
653
+ import {
654
+ accessDotted,
655
+ formatCellValue as formatCellValue2
656
+ } from "@gridkitjs/core";
827
657
  import { memo as memo3 } from "react";
828
- import { jsx as jsx4 } from "react/jsx-runtime";
658
+ import { jsx as jsx5 } from "react/jsx-runtime";
829
659
  function GridRowComponent({
830
660
  columns,
831
661
  rowId,
@@ -838,19 +668,32 @@ function GridRowComponent({
838
668
  selectedColumnId,
839
669
  focusedColumnIndex,
840
670
  rowsSelectable,
841
- cellsSelectable
671
+ cellsSelectable,
672
+ locale,
673
+ cellMessages,
674
+ rowClassName,
675
+ rowAttributes,
676
+ ref
842
677
  }) {
843
- return /* @__PURE__ */ jsx4(
678
+ return /* @__PURE__ */ jsx5(
844
679
  "tr",
845
680
  {
681
+ ...rowAttributes,
682
+ ref,
846
683
  role: "row",
847
684
  "aria-rowindex": datasetIndex + 2,
685
+ "data-gridkit-row-index": rowIndex,
686
+ "data-gridkit-row-id": rowId,
848
687
  ...ariaAttr(rowsSelectable, "aria-selected", selected),
849
- className: classNames("grid-row", selected ? "is-selected" : ""),
688
+ className: classNames(
689
+ "grid-row",
690
+ selected ? "is-selected" : "",
691
+ rowClassName
692
+ ),
850
693
  children: columns.map(({ id, column, alignment }, columnIndex) => {
851
694
  const value = accessDotted(row, column.field);
852
695
  const cellSelected = id === selectedColumnId;
853
- return /* @__PURE__ */ jsx4(
696
+ return /* @__PURE__ */ jsx5(
854
697
  "td",
855
698
  {
856
699
  role: "gridcell",
@@ -874,7 +717,7 @@ function GridRowComponent({
874
717
  datasetIndex,
875
718
  rowId,
876
719
  selected
877
- }) : value
720
+ }) : formatCellValue2(value, column, locale, row, cellMessages)
878
721
  },
879
722
  id
880
723
  );
@@ -886,34 +729,7 @@ var GridRow = memo3(GridRowComponent);
886
729
  var GridRow_default = GridRow;
887
730
 
888
731
  // src/DataGrid/components/GridBody.tsx
889
- import { jsx as jsx5 } from "react/jsx-runtime";
890
- function cellFrom(target) {
891
- if (!(target instanceof Element)) {
892
- return null;
893
- }
894
- const cell = target.closest("td[data-gridkit-column]");
895
- const row = cell?.parentElement;
896
- const columnId = cell?.getAttribute("data-gridkit-column");
897
- if (!(cell instanceof HTMLTableCellElement) || !(row instanceof HTMLTableRowElement) || columnId === null || columnId === void 0) {
898
- return null;
899
- }
900
- return {
901
- rowIndex: row.sectionRowIndex,
902
- columnIndex: cell.cellIndex,
903
- columnId
904
- };
905
- }
906
- function groupRowFrom(target) {
907
- if (!(target instanceof Element)) {
908
- return null;
909
- }
910
- const row = target.closest("tr[data-gridkit-group]");
911
- const groupId = row?.getAttribute("data-gridkit-group");
912
- if (!(row instanceof HTMLTableRowElement) || groupId === null || groupId === void 0) {
913
- return null;
914
- }
915
- return { rowIndex: row.sectionRowIndex, groupId };
916
- }
732
+ import { Fragment, jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
917
733
  function groupAriaMeta(rows) {
918
734
  const totals = /* @__PURE__ */ new Map();
919
735
  for (const entry of rows) {
@@ -937,18 +753,40 @@ function groupAriaMeta(rows) {
937
753
  }
938
754
  function GridBody({
939
755
  columns,
756
+ definitions,
940
757
  rows,
941
758
  activeColumnId,
942
759
  nav,
943
760
  selection,
944
- grouping,
761
+ controller,
945
762
  aggregates,
946
- groupAggregateDisplay
763
+ groupAggregateDisplay,
764
+ virtualRange,
765
+ infiniteScroll,
766
+ sentinelRef,
767
+ locale,
768
+ getRowProps,
769
+ onRowClick,
770
+ onRowDoubleClick,
771
+ messages
947
772
  }) {
948
- const { selectedCell, rowMode, cellMode } = selection;
949
- const ariaMeta = useMemo3(() => groupAriaMeta(rows), [rows]);
773
+ const { selectedCell, rowMode, cellMode, toggleOnClick } = selection;
774
+ const ariaMeta = useMemo2(() => groupAriaMeta(rows), [rows]);
775
+ const visibleRows = virtualRange === null ? rows : rows.slice(virtualRange.startIndex, virtualRange.endIndex + 1);
776
+ const { booleanTrue, booleanFalse, blankGroupValue } = messages;
777
+ const cellMessages = useMemo2(
778
+ () => ({ booleanTrue, booleanFalse, blankGroupValue }),
779
+ [booleanTrue, booleanFalse, blankGroupValue]
780
+ );
781
+ const [resolveRowProps] = useState2(() => createRowPropsResolver());
782
+ const rowPropsOf = resolveRowProps(
783
+ visibleRows.filter(
784
+ (entry) => !("kind" in entry)
785
+ ),
786
+ getRowProps
787
+ );
950
788
  function targetOf(target) {
951
- const position = cellFrom(target);
789
+ const position = bodyCellFrom(target);
952
790
  const entry = position === null ? void 0 : rows[position.rowIndex];
953
791
  if (position === null || entry === void 0 || "kind" in entry) {
954
792
  return null;
@@ -956,15 +794,39 @@ function GridBody({
956
794
  return { position, rowId: entry.rowId };
957
795
  }
958
796
  function select(rowId, columnId, modifiers) {
959
- const intent = intentOf2(modifiers);
960
797
  if (rowMode !== false) {
961
- selection.selectRow(rowId, intent);
798
+ selection.selectRow(rowId, intentOf2(modifiers, { toggleOnClick }));
962
799
  }
963
800
  if (cellMode !== false) {
964
- selection.selectCell({ rowId, columnId }, intent);
801
+ selection.selectCell({ rowId, columnId }, intentOf2(modifiers));
965
802
  }
966
803
  }
967
- return /* @__PURE__ */ jsx5(
804
+ function rowPointerEventOf(event) {
805
+ const target = bodyRowFrom(event.target);
806
+ const entry = target === null ? void 0 : rows[target.rowIndex];
807
+ if (target === null || entry === void 0 || "kind" in entry) {
808
+ return null;
809
+ }
810
+ return {
811
+ row: entry,
812
+ columnId: target.columnId,
813
+ native: {
814
+ ctrlKey: event.ctrlKey,
815
+ metaKey: event.metaKey,
816
+ shiftKey: event.shiftKey,
817
+ altKey: event.altKey,
818
+ button: event.button
819
+ }
820
+ };
821
+ }
822
+ function reportRowPointer(event, callback) {
823
+ if (callback === void 0) return;
824
+ const pointer = rowPointerEventOf(event);
825
+ if (pointer !== null) {
826
+ callback(pointer);
827
+ }
828
+ }
829
+ return /* @__PURE__ */ jsxs4(
968
830
  "tbody",
969
831
  {
970
832
  className: "grid-body",
@@ -981,12 +843,17 @@ function GridBody({
981
843
  onClick: (event) => {
982
844
  const group = groupRowFrom(event.target);
983
845
  if (group !== null) {
984
- grouping.toggleExpansion(group.groupId);
846
+ controller.toggleGroupExpansion(group.groupId);
985
847
  return;
986
848
  }
987
849
  const found = targetOf(event.target);
988
- if (found === null) return;
989
- select(found.rowId, found.position.columnId, event);
850
+ if (found !== null) {
851
+ select(found.rowId, found.position.columnId, event);
852
+ }
853
+ reportRowPointer(event, onRowClick);
854
+ },
855
+ onDoubleClick: (event) => {
856
+ reportRowPointer(event, onRowDoubleClick);
990
857
  },
991
858
  onKeyDown: (event) => {
992
859
  const group = groupRowFrom(event.target);
@@ -997,7 +864,7 @@ function GridBody({
997
864
  }
998
865
  if (event.key === " " || event.key === "Enter") {
999
866
  event.preventDefault();
1000
- grouping.toggleExpansion(group.groupId);
867
+ controller.toggleGroupExpansion(group.groupId);
1001
868
  return;
1002
869
  }
1003
870
  nav.onKeyDown(event);
@@ -1016,90 +883,154 @@ function GridBody({
1016
883
  }
1017
884
  nav.onKeyDown(event);
1018
885
  },
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,
886
+ children: [
887
+ virtualRange !== null && virtualRange.offsetBefore > 0 && // `aria-hidden` and carrying neither `data-gridkit-column` nor
888
+ // `data-gridkit-group`/`data-gridkit-row-index`, so `bodyCellFrom`/
889
+ // `groupRowFrom` never mistake it for a real row — it exists purely
890
+ // to give the browser's scrollbar the correct total height and thumb
891
+ // position, the same way `GridFooter`'s `<tfoot>` isn't a "row" to
892
+ // any of this grid's own row-counting logic.
893
+ /* @__PURE__ */ jsx6("tr", { "aria-hidden": "true", children: /* @__PURE__ */ jsx6(
894
+ "td",
895
+ {
896
+ style: {
897
+ height: virtualRange.offsetBefore,
898
+ padding: 0,
899
+ border: "none"
900
+ }
901
+ }
902
+ ) }),
903
+ visibleRows.map((entry) => {
904
+ if (!("kind" in entry)) {
905
+ const rowProps = rowPropsOf(entry.rowId);
906
+ return /* @__PURE__ */ jsx6(
907
+ GridRow_default,
908
+ {
909
+ ref: virtualRange?.measureRow(entry.rowIndex),
910
+ columns,
911
+ rowId: entry.rowId,
912
+ row: entry.row,
913
+ rowIndex: entry.rowIndex,
914
+ datasetIndex: entry.datasetIndex,
915
+ activeColumnId,
916
+ selectedColumnIds: selection.selectedColumnIds,
917
+ selected: selection.selectedRowIds.has(entry.rowId),
918
+ selectedColumnId: selectedCell?.rowId === entry.rowId ? selectedCell.columnId : null,
919
+ focusedColumnIndex: nav.focus.rowIndex === entry.rowIndex ? nav.focus.columnIndex : null,
920
+ rowsSelectable: rowMode !== false,
921
+ cellsSelectable: cellMode !== false,
922
+ locale,
923
+ cellMessages,
924
+ rowClassName: rowProps.className,
925
+ rowAttributes: rowProps.attributes
926
+ },
927
+ entry.rowId
928
+ );
929
+ }
930
+ if (entry.kind === "group-summary") {
931
+ return /* @__PURE__ */ jsx6(
932
+ GridGroupSummaryRow_default,
933
+ {
934
+ ref: virtualRange?.measureRow(entry.rowIndex),
935
+ groupId: entry.groupId,
936
+ level: entry.level,
937
+ rowIndex: entry.rowIndex,
938
+ datasetIndex: entry.datasetIndex,
939
+ results: entry.aggregates,
940
+ aggregates,
941
+ columns,
942
+ locale
943
+ },
944
+ `${entry.groupId}-summary`
945
+ );
946
+ }
947
+ return /* @__PURE__ */ jsx6(
948
+ GridGroupRow_default,
1043
949
  {
950
+ ref: virtualRange?.measureRow(entry.rowIndex),
951
+ columnCount: columns.length,
1044
952
  groupId: entry.groupId,
1045
953
  level: entry.level,
954
+ groupColumn: definitions.get(entry.columnId) ?? { field: entry.columnId },
955
+ value: entry.value,
956
+ expanded: entry.expanded,
957
+ count: entry.count,
958
+ rowIndex: entry.rowIndex,
1046
959
  datasetIndex: entry.datasetIndex,
960
+ posinset: ariaMeta.get(entry.groupId)?.posinset ?? 1,
961
+ setsize: ariaMeta.get(entry.groupId)?.setsize ?? 1,
962
+ aggregates: groupAggregateDisplay === "inline" ? aggregates : [],
1047
963
  results: entry.aggregates,
1048
- aggregates,
1049
- columns
964
+ definitions,
965
+ locale,
966
+ cellMessages,
967
+ focused: nav.focus.rowIndex === entry.rowIndex
1050
968
  },
1051
- `${entry.groupId}-summary`
969
+ entry.groupId
1052
970
  );
1053
- }
1054
- return /* @__PURE__ */ jsx5(
1055
- GridGroupRow_default,
971
+ }),
972
+ virtualRange !== null && virtualRange.offsetAfter > 0 && /* @__PURE__ */ jsx6("tr", { "aria-hidden": "true", children: /* @__PURE__ */ jsx6(
973
+ "td",
1056
974
  {
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
- })
975
+ style: {
976
+ height: virtualRange.offsetAfter,
977
+ padding: 0,
978
+ border: "none"
979
+ }
980
+ }
981
+ ) }),
982
+ infiniteScroll?.hasMore && /* @__PURE__ */ jsxs4(Fragment, { children: [
983
+ 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() : messages.loadingMore }) }),
984
+ /* @__PURE__ */ jsx6(
985
+ "tr",
986
+ {
987
+ "aria-hidden": "true",
988
+ ref: sentinelRef,
989
+ className: "grid-sentinel-row",
990
+ children: /* @__PURE__ */ jsx6(
991
+ "td",
992
+ {
993
+ colSpan: columns.length,
994
+ style: { padding: 0, border: "none" }
995
+ }
996
+ )
997
+ }
998
+ )
999
+ ] })
1000
+ ]
1075
1001
  }
1076
1002
  );
1077
1003
  }
1078
1004
 
1079
1005
  // src/DataGrid/components/GridFooter.tsx
1080
- import { jsx as jsx6 } from "react/jsx-runtime";
1006
+ import { jsx as jsx7 } from "react/jsx-runtime";
1081
1007
  function GridFooter({
1082
1008
  columns,
1083
1009
  aggregates,
1084
1010
  results,
1085
- rows
1011
+ rows,
1012
+ locale
1086
1013
  }) {
1087
- return /* @__PURE__ */ jsx6("tfoot", { children: /* @__PURE__ */ jsx6("tr", { className: "grid-footer", role: "row", children: columns.map((entry) => {
1014
+ return /* @__PURE__ */ jsx7("tfoot", { children: /* @__PURE__ */ jsx7("tr", { className: "grid-footer", role: "row", children: columns.map((entry) => {
1088
1015
  const specs = aggregates.filter(
1089
1016
  (candidate) => candidate.columnId === entry.id
1090
1017
  );
1091
- const { column } = entry;
1092
- return /* @__PURE__ */ jsx6(
1018
+ return /* @__PURE__ */ jsx7(
1093
1019
  "td",
1094
1020
  {
1095
1021
  role: "gridcell",
1096
1022
  "data-gridkit-column": entry.id,
1097
1023
  className: "grid-footer-cell",
1098
1024
  style: { textAlign: specs[0]?.alignment ?? entry.alignment },
1099
- children: specs.map((spec) => {
1100
- const value = results.get(spec.id ?? spec.columnId);
1101
- return column.footerTemplate ? column.footerTemplate({ value, rows }) : formatAggregateValue(value);
1102
- }).flatMap(
1025
+ children: specs.map(
1026
+ (spec) => renderAggregate(
1027
+ results.get(spec.id ?? spec.columnId),
1028
+ spec,
1029
+ entry.column,
1030
+ rows,
1031
+ locale
1032
+ )
1033
+ ).flatMap(
1103
1034
  (rendered, index) => index === 0 ? [rendered] : [", ", rendered]
1104
1035
  )
1105
1036
  },
@@ -1110,223 +1041,140 @@ function GridFooter({
1110
1041
 
1111
1042
  // src/DataGrid/components/GridPager.tsx
1112
1043
  import { paginationWindow } from "@gridkitjs/core";
1113
- import { jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
1114
- function GridPager({ pager, config }) {
1044
+ import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
1045
+ function GridPager({ pager, config, messages }) {
1115
1046
  const { pagination, pageCount, currentPage } = pager;
1116
1047
  const variant = config?.variant ?? "compact";
1117
1048
  const sizeOptions = config?.sizeOptions;
1118
- return /* @__PURE__ */ jsxs4("div", { className: "gridkit-grid-pager", role: "group", "aria-label": "Pagination", children: [
1119
- /* @__PURE__ */ jsx7(
1120
- "button",
1121
- {
1122
- type: "button",
1123
- className: "grid-pager-button",
1124
- disabled: pagination.pageIndex <= 0,
1125
- onClick: () => {
1126
- pager.previousPage();
1127
- },
1128
- children: "Previous"
1129
- }
1130
- ),
1131
- variant === "numbered" ? paginationWindow(currentPage, pageCount, {
1132
- boundaryCount: config?.boundaryCount,
1133
- siblingCount: config?.siblingCount
1134
- }).map(
1135
- (entry, index) => entry === "ellipsis" ? /* @__PURE__ */ jsx7(
1136
- "span",
1137
- {
1138
- className: "grid-pager-ellipsis",
1139
- "aria-hidden": "true",
1140
- children: "\u2026"
1141
- },
1142
- `ellipsis-${String(index)}`
1143
- ) : /* @__PURE__ */ jsx7(
1144
- "button",
1145
- {
1146
- type: "button",
1147
- className: "grid-pager-button",
1148
- "aria-label": `Page ${String(entry)}`,
1149
- "aria-current": entry === currentPage ? "page" : void 0,
1150
- onClick: () => {
1151
- pager.goToPage(entry - 1);
1152
- },
1153
- children: entry
1154
- },
1155
- entry
1156
- )
1157
- ) : /* @__PURE__ */ jsxs4("span", { className: "grid-pager-status", children: [
1158
- "Page ",
1159
- pageCount === 0 ? 0 : currentPage,
1160
- " of ",
1161
- pageCount
1162
- ] }),
1163
- /* @__PURE__ */ jsx7(
1164
- "button",
1165
- {
1166
- type: "button",
1167
- className: "grid-pager-button",
1168
- disabled: pagination.pageIndex >= pageCount - 1,
1169
- onClick: () => {
1170
- pager.nextPage();
1171
- },
1172
- children: "Next"
1173
- }
1174
- ),
1175
- sizeOptions !== void 0 && sizeOptions.length > 0 && /* @__PURE__ */ jsxs4("label", { className: "grid-pager-page-size", children: [
1176
- "Rows per page",
1177
- /* @__PURE__ */ jsx7(
1178
- "select",
1179
- {
1180
- value: pagination.pageSize,
1181
- onChange: (event) => {
1182
- pager.setPageSize(Number(event.target.value));
1183
- },
1184
- children: sizeOptions.map((size) => /* @__PURE__ */ jsx7("option", { value: size, children: size }, size))
1185
- }
1186
- )
1187
- ] })
1188
- ] });
1049
+ return /* @__PURE__ */ jsxs5(
1050
+ "div",
1051
+ {
1052
+ className: "gridkit-grid-pager",
1053
+ role: "group",
1054
+ "aria-label": messages.pagerLabel,
1055
+ children: [
1056
+ /* @__PURE__ */ jsx8(
1057
+ "button",
1058
+ {
1059
+ type: "button",
1060
+ className: "grid-pager-button",
1061
+ disabled: pagination.pageIndex <= 0,
1062
+ onClick: () => {
1063
+ pager.previousPage();
1064
+ },
1065
+ children: messages.previousPage
1066
+ }
1067
+ ),
1068
+ variant === "numbered" ? paginationWindow(currentPage, pageCount, {
1069
+ boundaryCount: config?.boundaryCount,
1070
+ siblingCount: config?.siblingCount
1071
+ }).map(
1072
+ (entry, index) => entry === "ellipsis" ? /* @__PURE__ */ jsx8(
1073
+ "span",
1074
+ {
1075
+ className: "grid-pager-ellipsis",
1076
+ "aria-hidden": "true",
1077
+ children: "\u2026"
1078
+ },
1079
+ `ellipsis-${String(index)}`
1080
+ ) : /* @__PURE__ */ jsx8(
1081
+ "button",
1082
+ {
1083
+ type: "button",
1084
+ className: "grid-pager-button",
1085
+ "aria-label": messages.pageButtonLabel(entry),
1086
+ "aria-current": entry === currentPage ? "page" : void 0,
1087
+ onClick: () => {
1088
+ pager.goToPage(entry - 1);
1089
+ },
1090
+ children: entry
1091
+ },
1092
+ entry
1093
+ )
1094
+ ) : /* @__PURE__ */ jsx8("span", { className: "grid-pager-status", children: messages.pageStatus(pageCount === 0 ? 0 : currentPage, pageCount) }),
1095
+ /* @__PURE__ */ jsx8(
1096
+ "button",
1097
+ {
1098
+ type: "button",
1099
+ className: "grid-pager-button",
1100
+ disabled: pagination.pageIndex >= pageCount - 1,
1101
+ onClick: () => {
1102
+ pager.nextPage();
1103
+ },
1104
+ children: messages.nextPage
1105
+ }
1106
+ ),
1107
+ sizeOptions !== void 0 && sizeOptions.length > 0 && /* @__PURE__ */ jsxs5("label", { className: "grid-pager-page-size", children: [
1108
+ messages.pageSizeLabel,
1109
+ /* @__PURE__ */ jsx8(
1110
+ "select",
1111
+ {
1112
+ value: pagination.pageSize,
1113
+ onChange: (event) => {
1114
+ pager.setPageSize(Number(event.target.value));
1115
+ },
1116
+ children: sizeOptions.map((size) => /* @__PURE__ */ jsx8("option", { value: size, children: size }, size))
1117
+ }
1118
+ )
1119
+ ] })
1120
+ ]
1121
+ }
1122
+ );
1189
1123
  }
1190
1124
 
1191
1125
  // src/DataGrid/components/GroupByBar.tsx
1192
1126
  import { createPortal as createPortal2 } from "react-dom";
1193
- import { resolveKeyboardDropTarget } from "@gridkitjs/core";
1194
-
1195
- // src/DataGrid/groupByDropTarget.ts
1196
1127
  import {
1197
- movesGroupBy,
1198
- resolveDropBefore
1128
+ resolveColumnLabel as resolveColumnLabel2,
1129
+ resolveKeyboardDropTarget
1199
1130
  } from "@gridkitjs/core";
1200
1131
 
1201
- // src/DataGrid/useDragReorder.ts
1132
+ // src/DataGrid/useGroupByDrag.ts
1202
1133
  import {
1203
- useRef as useRef3,
1204
- useState as useState2
1205
- } from "react";
1206
-
1207
- // src/DataGrid/pointerGesture.ts
1208
- function startPointerGesture(element, pointerId, handlers) {
1209
- element.setPointerCapture(pointerId);
1210
- function stop() {
1211
- element.removeEventListener("pointermove", move);
1212
- element.removeEventListener("pointerup", end);
1213
- element.removeEventListener("pointercancel", cancel);
1214
- window.removeEventListener("keydown", onKeyDown);
1215
- if (element.hasPointerCapture(pointerId)) {
1216
- element.releasePointerCapture(pointerId);
1217
- }
1218
- }
1219
- function move(event) {
1220
- handlers.onMove(event);
1221
- }
1222
- function end() {
1223
- stop();
1224
- handlers.onEnd();
1225
- }
1226
- function cancel() {
1227
- stop();
1228
- handlers.onCancel();
1229
- }
1230
- function onKeyDown(event) {
1231
- if (event.key === "Escape") {
1232
- cancel();
1233
- }
1234
- }
1235
- element.addEventListener("pointermove", move);
1236
- element.addEventListener("pointerup", end);
1237
- element.addEventListener("pointercancel", cancel);
1238
- window.addEventListener("keydown", onKeyDown);
1239
- }
1134
+ resolveGroupByDropTarget
1135
+ } from "@gridkitjs/dom";
1240
1136
 
1241
1137
  // src/DataGrid/useDragReorder.ts
1242
- var DRAG_THRESHOLD = 4;
1243
- function elementAt(x, y, selector) {
1244
- return document.elementFromPoint(x, y)?.closest(selector) ?? null;
1245
- }
1246
- function sideOf(element, x) {
1247
- const rect = element.getBoundingClientRect();
1248
- return x < rect.left + rect.width / 2 ? "before" : "after";
1249
- }
1138
+ import {
1139
+ useRef,
1140
+ useState as useState3
1141
+ } from "react";
1142
+ import {
1143
+ NO_DRAG,
1144
+ startDragReorder
1145
+ } from "@gridkitjs/dom";
1250
1146
  function useDragReorder({
1251
1147
  resolveTarget,
1252
1148
  onDrop
1253
1149
  }) {
1254
- const [draggedId, setDraggedId] = useState2(null);
1255
- const [dropTarget, setDropTarget] = useState2(null);
1256
- const [ghostTransform, setGhostTransform] = useState2(null);
1257
- const dragged = useRef3(false);
1150
+ const [drag, setDrag] = useState3(NO_DRAG);
1151
+ const dragged = useRef(false);
1258
1152
  function justDragged() {
1259
1153
  const answer = dragged.current;
1260
1154
  dragged.current = false;
1261
1155
  return answer;
1262
1156
  }
1263
1157
  function startDrag(id, grabElement, event) {
1264
- const { pointerId, clientX: startX } = event;
1265
- const grabX = startX - grabElement.getBoundingClientRect().left;
1266
- let dragging = false;
1267
- let target = null;
1268
- function placeGhost(x, y) {
1269
- setGhostTransform(`translate(${String(x - grabX)}px, ${String(y)}px)`);
1270
- }
1271
- function reset() {
1272
- setGhostTransform(null);
1273
- setDraggedId(null);
1274
- setDropTarget(null);
1275
- }
1276
- startPointerGesture(grabElement, pointerId, {
1277
- onMove(moveEvent) {
1278
- const { clientX, clientY } = moveEvent;
1279
- if (!dragging) {
1280
- if (Math.abs(clientX - startX) < DRAG_THRESHOLD) {
1281
- return;
1282
- }
1283
- dragging = true;
1158
+ startDragReorder(id, grabElement, event, {
1159
+ resolveTarget,
1160
+ onState(state) {
1161
+ if (state.draggedId !== null) {
1284
1162
  dragged.current = true;
1285
- setDraggedId(id);
1286
1163
  }
1287
- placeGhost(clientX, clientY);
1288
- target = resolveTarget(clientX, clientY, id);
1289
- setDropTarget(target);
1164
+ setDrag(state);
1290
1165
  },
1291
- onEnd() {
1292
- const dropped = target;
1293
- reset();
1294
- if (dragging && dropped !== null) {
1295
- onDrop(dropped, id);
1296
- }
1297
- },
1298
- // Escape and a cancelled pointer both leave the order as it was.
1299
- onCancel() {
1300
- target = null;
1301
- reset();
1302
- }
1166
+ onDrop
1303
1167
  });
1304
1168
  }
1305
1169
  return {
1306
- draggedId,
1307
- dropTarget,
1308
- ghostTransform,
1170
+ draggedId: drag.draggedId,
1171
+ dropTarget: drag.dropTarget,
1172
+ ghostTransform: drag.ghostTransform,
1309
1173
  startDrag,
1310
1174
  justDragged
1311
1175
  };
1312
1176
  }
1313
1177
 
1314
- // src/DataGrid/groupByDropTarget.ts
1315
- function resolveGroupByDropTarget(clientX, clientY, movedId, groupBy) {
1316
- const overBar = elementAt(clientX, clientY, "[data-gridkit-group-by-bar]");
1317
- if (overBar === null) {
1318
- return null;
1319
- }
1320
- const overChip = elementAt(clientX, clientY, "[data-gridkit-group-chip]");
1321
- const chipId = overChip?.getAttribute("data-gridkit-group-chip");
1322
- const beforeColumnId = overChip && chipId ? resolveDropBefore(
1323
- groupBy.map((entry) => entry.columnId),
1324
- chipId,
1325
- sideOf(overChip, clientX)
1326
- ) : null;
1327
- return movesGroupBy(groupBy, movedId, beforeColumnId) ? { kind: "group-by", beforeColumnId } : null;
1328
- }
1329
-
1330
1178
  // src/DataGrid/useGroupByDrag.ts
1331
1179
  function useGroupByDrag({
1332
1180
  groupBy,
@@ -1350,28 +1198,31 @@ function useGroupByDrag({
1350
1198
  }
1351
1199
 
1352
1200
  // src/DataGrid/components/GroupByBar.tsx
1353
- import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
1201
+ import { jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
1354
1202
  function GroupByBar({
1355
1203
  columns,
1356
- grouping,
1204
+ definitions,
1205
+ groupBy,
1206
+ controller,
1357
1207
  columnName,
1358
1208
  headerDragEligible,
1359
1209
  headerDragBlocked,
1360
- headerDropTarget
1210
+ headerDropTarget,
1211
+ messages
1361
1212
  }) {
1362
- const { groupBy } = grouping;
1363
1213
  const groupByIds = groupBy.map((entry) => entry.columnId);
1364
1214
  const chipDrag = useGroupByDrag({
1365
1215
  groupBy,
1366
1216
  onDrop: (movedId, beforeColumnId) => {
1367
- grouping.moveGroupBy(movedId, beforeColumnId);
1217
+ controller.moveGroupBy(movedId, beforeColumnId);
1368
1218
  }
1369
1219
  });
1370
1220
  const activeDropTarget = chipDrag.dropTarget ?? headerDropTarget;
1371
1221
  const isDragTarget = chipDrag.draggedColumnId !== null || headerDragEligible;
1372
1222
  const showPlaceholder = groupBy.length === 0 && columns.some((entry) => entry.groupByDraggable);
1373
1223
  function labelFor(columnId) {
1374
- return columns.find((entry) => entry.id === columnId)?.label ?? columnId;
1224
+ const definition = definitions.get(columnId);
1225
+ return definition === void 0 ? columnId : resolveColumnLabel2(definition);
1375
1226
  }
1376
1227
  function moveByKeyboard(columnId, direction) {
1377
1228
  const beforeColumnId = resolveKeyboardDropTarget(
@@ -1382,9 +1233,9 @@ function GroupByBar({
1382
1233
  if (beforeColumnId === void 0) {
1383
1234
  return;
1384
1235
  }
1385
- grouping.moveGroupBy(columnId, beforeColumnId);
1236
+ controller.moveGroupBy(columnId, beforeColumnId);
1386
1237
  }
1387
- return /* @__PURE__ */ jsxs5(
1238
+ return /* @__PURE__ */ jsxs6(
1388
1239
  "div",
1389
1240
  {
1390
1241
  className: classNames(
@@ -1394,14 +1245,14 @@ function GroupByBar({
1394
1245
  ),
1395
1246
  "data-gridkit-group-by-bar": "",
1396
1247
  role: "group",
1397
- "aria-label": "Grouped by",
1248
+ "aria-label": messages.groupByBarLabel,
1398
1249
  children: [
1399
- showPlaceholder && /* @__PURE__ */ jsx8("span", { className: "group-by-bar-placeholder", children: "Drag a column here to group by it" }),
1250
+ showPlaceholder && /* @__PURE__ */ jsx9("span", { className: "group-by-bar-placeholder", children: messages.groupByBarPlaceholder }),
1400
1251
  groupBy.map((entry, index) => {
1401
1252
  const dropBefore = activeDropTarget?.beforeColumnId === entry.columnId;
1402
1253
  const dropAfter = activeDropTarget?.beforeColumnId === null && index === groupBy.length - 1;
1403
1254
  const dragging = chipDrag.draggedColumnId === entry.columnId;
1404
- return /* @__PURE__ */ jsxs5(
1255
+ return /* @__PURE__ */ jsxs6(
1405
1256
  "span",
1406
1257
  {
1407
1258
  className: classNames(
@@ -1412,7 +1263,11 @@ function GroupByBar({
1412
1263
  ),
1413
1264
  "data-gridkit-group-chip": entry.columnId,
1414
1265
  tabIndex: 0,
1415
- "aria-label": groupBy.length > 1 ? `${columnName(entry.columnId)}, level ${String(index + 1)} of ${String(groupBy.length)}` : columnName(entry.columnId),
1266
+ "aria-label": messages.groupByChipLabel(
1267
+ columnName(entry.columnId),
1268
+ index + 1,
1269
+ groupBy.length
1270
+ ),
1416
1271
  "aria-keyshortcuts": "Control+ArrowLeft Control+ArrowRight",
1417
1272
  onPointerDown: (event) => {
1418
1273
  chipDrag.startDrag(entry.columnId, event);
@@ -1428,18 +1283,18 @@ function GroupByBar({
1428
1283
  }
1429
1284
  },
1430
1285
  children: [
1431
- /* @__PURE__ */ jsx8("span", { className: "group-by-chip-label", children: labelFor(entry.columnId) }),
1432
- /* @__PURE__ */ jsx8(
1286
+ /* @__PURE__ */ jsx9("span", { className: "group-by-chip-label", children: labelFor(entry.columnId) }),
1287
+ /* @__PURE__ */ jsx9(
1433
1288
  "button",
1434
1289
  {
1435
1290
  type: "button",
1436
1291
  className: "group-by-chip-remove",
1437
- "aria-label": `Stop grouping by ${columnName(entry.columnId)}`,
1292
+ "aria-label": messages.removeGroupBy(columnName(entry.columnId)),
1438
1293
  onPointerDown: (event) => {
1439
1294
  event.stopPropagation();
1440
1295
  },
1441
1296
  onClick: () => {
1442
- grouping.toggleGroupBy(entry.columnId);
1297
+ controller.toggleGroupBy(entry.columnId);
1443
1298
  },
1444
1299
  children: "\xD7"
1445
1300
  }
@@ -1450,7 +1305,7 @@ function GroupByBar({
1450
1305
  );
1451
1306
  }),
1452
1307
  chipDrag.draggedColumnId !== null && chipDrag.ghostTransform !== null && createPortal2(
1453
- /* @__PURE__ */ jsx8(
1308
+ /* @__PURE__ */ jsx9(
1454
1309
  "span",
1455
1310
  {
1456
1311
  className: "group-by-chip drag-ghost",
@@ -1468,10 +1323,9 @@ function GroupByBar({
1468
1323
 
1469
1324
  // src/DataGrid/useColumnDrag.ts
1470
1325
  import {
1471
- movesColumn,
1472
- resolveDropBefore as resolveDropBefore2,
1473
1326
  resolveKeyboardDropTarget as resolveKeyboardDropTarget2
1474
1327
  } from "@gridkitjs/core";
1328
+ import { resolveColumnDropTarget } from "@gridkitjs/dom";
1475
1329
  function useColumnDrag({
1476
1330
  order,
1477
1331
  groupBy,
@@ -1479,18 +1333,11 @@ function useColumnDrag({
1479
1333
  onDrop
1480
1334
  }) {
1481
1335
  const drag = useDragReorder({
1482
- resolveTarget(clientX, clientY, movedId) {
1483
- const over = elementAt(clientX, clientY, "th[data-gridkit-column]");
1484
- const overId = over?.getAttribute("data-gridkit-column");
1485
- const beforeId = over && overId ? resolveDropBefore2(order, overId, sideOf(over, clientX)) : void 0;
1486
- if (beforeId !== void 0) {
1487
- return movesColumn(order, movedId, beforeId) ? { kind: "column-order", beforeId } : null;
1488
- }
1489
- const alreadyGrouped = groupBy.some(
1490
- (entry) => entry.columnId === movedId
1491
- );
1492
- return groupByDraggableIds.has(movedId) && !alreadyGrouped ? resolveGroupByDropTarget(clientX, clientY, movedId, groupBy) : null;
1493
- },
1336
+ resolveTarget: (clientX, clientY, movedId) => resolveColumnDropTarget(clientX, clientY, movedId, {
1337
+ order,
1338
+ groupBy,
1339
+ groupByDraggableIds
1340
+ }),
1494
1341
  onDrop
1495
1342
  });
1496
1343
  function startDrag(entry, event) {
@@ -1515,95 +1362,64 @@ function useColumnDrag({
1515
1362
 
1516
1363
  // src/DataGrid/useColumnResize.ts
1517
1364
  import {
1518
- useState as useState3
1365
+ useState as useState4
1519
1366
  } from "react";
1520
1367
  import {
1521
- applyColumnResize,
1522
1368
  beginColumnResize,
1523
1369
  clampColumnWidth,
1524
1370
  resolveColumnConstraints,
1525
- revertColumnSize,
1526
1371
  sizeColumnToContent
1527
1372
  } from "@gridkitjs/core";
1528
-
1529
- // src/DataGrid/measureColumnContent.ts
1530
- function measureColumnContent(table, columnId) {
1531
- const cells = table.querySelectorAll(
1532
- `td[data-gridkit-column="${CSS.escape(columnId)}"]`
1533
- );
1534
- const range = document.createRange();
1535
- let widest = null;
1536
- for (const cell of cells) {
1537
- range.selectNodeContents(cell);
1538
- const style = getComputedStyle(cell);
1539
- const width = range.getBoundingClientRect().width + parseFloat(style.paddingLeft) + parseFloat(style.paddingRight) + parseFloat(style.borderLeftWidth) + parseFloat(style.borderRightWidth);
1540
- if (widest === null || width > widest) {
1541
- widest = width;
1542
- }
1543
- }
1544
- return widest;
1545
- }
1546
-
1547
- // src/DataGrid/useColumnResize.ts
1373
+ import { measureColumnContent, startColumnResizeGesture } from "@gridkitjs/dom";
1548
1374
  function useColumnResize({
1549
1375
  tableRef,
1550
- sizing,
1551
- setSizing,
1552
- columnSizeDefaults,
1553
- onColumnResize
1376
+ controller,
1377
+ columnSizeDefaults
1554
1378
  }) {
1555
- const [activeColumnId, setActiveColumnId] = useState3(null);
1556
- function commit(base, columnId, width, phase) {
1557
- const next = { ...base, [columnId]: width };
1558
- setSizing(next);
1559
- onColumnResize?.({ columnId, width, sizing: next, phase });
1560
- }
1379
+ const [activeColumnId, setActiveColumnId] = useState4(null);
1561
1380
  function startResize(entry, event) {
1562
- const handle = event.currentTarget;
1563
- const { pointerId } = event;
1381
+ event.stopPropagation();
1564
1382
  const session = beginColumnResize(
1565
1383
  entry.column,
1566
1384
  entry.width,
1567
1385
  event.clientX,
1568
1386
  columnSizeDefaults
1569
1387
  );
1570
- const base = sizing;
1571
- let width = session.startWidth;
1572
- event.preventDefault();
1573
- event.stopPropagation();
1574
- setActiveColumnId(session.columnId);
1575
- startPointerGesture(handle, pointerId, {
1576
- onMove(moveEvent) {
1577
- width = applyColumnResize(session, moveEvent.clientX);
1578
- commit(base, session.columnId, width, "move");
1579
- },
1580
- onEnd() {
1581
- setActiveColumnId(null);
1582
- if (width !== session.startWidth) {
1583
- commit(base, session.columnId, width, "end");
1388
+ const base = controller.getState().columnSizing;
1389
+ const started = startColumnResizeGesture(
1390
+ event.currentTarget,
1391
+ event,
1392
+ session,
1393
+ {
1394
+ onMove(width) {
1395
+ controller.resizeColumn(session.columnId, width, "move");
1396
+ },
1397
+ onEnd(width) {
1398
+ setActiveColumnId(null);
1399
+ if (width !== null) {
1400
+ controller.resizeColumn(session.columnId, width, "end");
1401
+ }
1402
+ },
1403
+ /**
1404
+ * Escape puts back only the dragged column's pre-drag width, merged
1405
+ * into whatever the sizing state is *now* — not the `base` snapshot
1406
+ * taken at drag-start — so a different column resized via keyboard
1407
+ * while this drag was still open keeps its change instead of being
1408
+ * clobbered by a stale restore.
1409
+ */
1410
+ onCancel() {
1411
+ setActiveColumnId(null);
1412
+ controller.cancelColumnResize(
1413
+ session.columnId,
1414
+ base,
1415
+ session.startWidth
1416
+ );
1584
1417
  }
1585
- },
1586
- /**
1587
- * Escape puts back only the dragged column's pre-drag width, merged
1588
- * into whatever the sizing state is *now* — not the `base` snapshot
1589
- * taken at drag-start — so a different column resized via keyboard
1590
- * while this drag was still open keeps its change instead of being
1591
- * clobbered by a stale restore.
1592
- */
1593
- onCancel() {
1594
- setActiveColumnId(null);
1595
- setSizing((current) => {
1596
- const next = revertColumnSize(current, base, session.columnId);
1597
- onColumnResize?.({
1598
- columnId: session.columnId,
1599
- width: session.startWidth,
1600
- sizing: next,
1601
- phase: "end"
1602
- });
1603
- return next;
1604
- });
1605
1418
  }
1606
- });
1419
+ );
1420
+ if (started) {
1421
+ setActiveColumnId(session.columnId);
1422
+ }
1607
1423
  }
1608
1424
  function sizeToContent(entry) {
1609
1425
  const table = tableRef.current;
@@ -1614,8 +1430,7 @@ function useColumnResize({
1614
1430
  if (measured === null) {
1615
1431
  return;
1616
1432
  }
1617
- commit(
1618
- sizing,
1433
+ controller.resizeColumn(
1619
1434
  entry.id,
1620
1435
  sizeColumnToContent(
1621
1436
  measured,
@@ -1625,8 +1440,7 @@ function useColumnResize({
1625
1440
  );
1626
1441
  }
1627
1442
  function nudge(entry, delta) {
1628
- commit(
1629
- sizing,
1443
+ controller.resizeColumn(
1630
1444
  entry.id,
1631
1445
  clampColumnWidth(
1632
1446
  entry.width + delta,
@@ -1638,342 +1452,285 @@ function useColumnResize({
1638
1452
  return { activeColumnId, startResize, sizeToContent, nudge };
1639
1453
  }
1640
1454
 
1641
- // src/DataGrid/useColumnSort.ts
1642
- import {
1643
- sortDirectionFor,
1644
- sortPriorityFor,
1645
- toggleColumnSort
1646
- } from "@gridkitjs/core";
1647
- function useColumnSort({
1648
- sort,
1649
- setSort,
1650
- onColumnSortChange
1651
- }) {
1652
- function toggle(entry, event) {
1653
- const next = toggleColumnSort(sort, entry.id, { stack: event.shiftKey });
1654
- commitIfChanged(sort, next, setSort, (committed) => {
1655
- onColumnSortChange?.({ columnId: entry.id, sort: committed });
1656
- });
1657
- }
1658
- return {
1659
- sort,
1660
- directionFor: (columnId) => sortDirectionFor(sort, columnId),
1661
- priorityFor: (columnId) => sortPriorityFor(sort, columnId),
1662
- toggle
1663
- };
1664
- }
1665
-
1666
1455
  // src/DataGrid/useElementWidth.ts
1667
- import { useLayoutEffect, useState as useState4 } from "react";
1456
+ import { useLayoutEffect as useLayoutEffect2, useState as useState5 } from "react";
1457
+ import { observeElementWidth } from "@gridkitjs/dom";
1668
1458
  function useElementWidth(ref, enabled) {
1669
- const [width, setWidth] = useState4(null);
1670
- useLayoutEffect(() => {
1459
+ const [width, setWidth] = useState5(null);
1460
+ useLayoutEffect2(() => {
1671
1461
  const element = ref.current;
1672
1462
  if (element === null || !enabled) {
1673
1463
  return void 0;
1674
1464
  }
1675
- setWidth(element.clientWidth);
1676
- const observer = new ResizeObserver(() => {
1677
- setWidth(element.clientWidth);
1678
- });
1679
- observer.observe(element);
1680
- return () => {
1681
- observer.disconnect();
1682
- };
1465
+ return observeElementWidth(element, setWidth);
1683
1466
  }, [ref, enabled]);
1684
1467
  return width;
1685
1468
  }
1686
1469
 
1687
- // src/DataGrid/usePagination.ts
1470
+ // src/DataGrid/useGridController.ts
1471
+ import { useState as useState6, useSyncExternalStore as useSyncExternalStore2 } from "react";
1688
1472
  import {
1689
- paginateRows
1473
+ createGridController
1690
1474
  } from "@gridkitjs/core";
1691
- function usePagination({
1692
- pagination,
1693
- setPagination,
1694
- rows,
1695
- onPaginationChange
1475
+ function useGridController(options) {
1476
+ const [controller] = useState6(
1477
+ () => createGridController(options)
1478
+ );
1479
+ controller.setOptions(options);
1480
+ const state = useSyncExternalStore2(controller.subscribe, controller.getState);
1481
+ return { controller, state, view: controller.getView() };
1482
+ }
1483
+
1484
+ // src/DataGrid/useInfiniteScroll.ts
1485
+ import { useEffect as useEffect2, useRef as useRef2 } from "react";
1486
+ import { attachInfiniteScroll } from "@gridkitjs/dom";
1487
+ function useInfiniteScroll({
1488
+ rootRef,
1489
+ sentinelRef,
1490
+ config
1696
1491
  }) {
1697
- const { pageCount, pageIndex } = paginateRows(rows, pagination);
1698
- function commit(next) {
1699
- commitIfChanged(pagination, next, setPagination, (committed) => {
1700
- const { pageCount: committedPageCount } = paginateRows(rows, committed);
1701
- onPaginationChange?.({
1702
- pagination: committed,
1703
- pageCount: committedPageCount
1704
- });
1492
+ const scroll = useRef2(null);
1493
+ useEffect2(() => {
1494
+ const root = rootRef.current;
1495
+ if (root === null) {
1496
+ return void 0;
1497
+ }
1498
+ const attached = attachInfiniteScroll(root, {
1499
+ sentinel: null,
1500
+ hasMore: false
1705
1501
  });
1706
- }
1707
- function goToPage(nextPageIndex) {
1708
- const { pageIndex: clamped } = paginateRows(rows, {
1709
- ...pagination,
1710
- pageIndex: nextPageIndex
1502
+ scroll.current = attached;
1503
+ return () => {
1504
+ attached.detach();
1505
+ scroll.current = null;
1506
+ };
1507
+ }, [rootRef]);
1508
+ useEffect2(() => {
1509
+ scroll.current?.update({
1510
+ sentinel: sentinelRef.current,
1511
+ hasMore: config?.hasMore ?? false,
1512
+ onLoadMore: config?.onLoadMore,
1513
+ isLoadingMore: config?.isLoadingMore,
1514
+ rootMargin: config?.rootMargin
1711
1515
  });
1712
- commit({ ...pagination, pageIndex: clamped });
1713
- }
1714
- return {
1715
- pagination,
1716
- pageCount,
1717
- currentPage: Math.min(pagination.pageIndex + 1, pageCount),
1718
- goToPage,
1719
- nextPage: () => {
1720
- goToPage(pageIndex + 1);
1721
- },
1722
- previousPage: () => {
1723
- goToPage(pageIndex - 1);
1724
- },
1725
- setPageSize: (pageSize) => {
1726
- const next = { pageIndex: 0, pageSize };
1727
- const { pageIndex: clamped } = paginateRows(rows, next);
1728
- commit({ ...next, pageIndex: clamped });
1516
+ });
1517
+ }
1518
+
1519
+ // src/DataGrid/useLiveAnnouncer.ts
1520
+ import { useCallback, useLayoutEffect as useLayoutEffect3, useRef as useRef3 } from "react";
1521
+ import { createLiveAnnouncer } from "@gridkitjs/dom";
1522
+ function useLiveAnnouncer(containerRef) {
1523
+ const announcer = useRef3(null);
1524
+ useLayoutEffect3(() => {
1525
+ const container = containerRef.current;
1526
+ if (container === null) {
1527
+ return void 0;
1729
1528
  }
1730
- };
1529
+ const created = createLiveAnnouncer(container);
1530
+ announcer.current = created;
1531
+ return () => {
1532
+ created.destroy();
1533
+ announcer.current = null;
1534
+ };
1535
+ }, [containerRef]);
1536
+ return useCallback((text) => {
1537
+ announcer.current?.announce(text);
1538
+ }, []);
1731
1539
  }
1732
1540
 
1733
- // src/DataGrid/useRowGrouping.ts
1734
- import {
1735
- collapseAllGroups,
1736
- expandAllGroups,
1737
- moveGroupByBefore,
1738
- toggleGroupExpansion
1739
- } from "@gridkitjs/core";
1740
- function useRowGrouping({
1741
- groupBy,
1742
- setGroupBy,
1743
- expansion,
1744
- setExpansion,
1745
- onGroupByChange,
1746
- onGroupExpansionChange
1541
+ // src/DataGrid/useRowVirtualizer.ts
1542
+ import { useLayoutEffect as useLayoutEffect4, useState as useState7 } from "react";
1543
+ import { createRowVirtualizer } from "@gridkitjs/dom";
1544
+ var EMPTY_RANGE = {
1545
+ startIndex: 0,
1546
+ endIndex: -1,
1547
+ offsetBefore: 0,
1548
+ offsetAfter: 0
1549
+ };
1550
+ function ignoreRow() {
1551
+ return () => void 0;
1552
+ }
1553
+ function useRowVirtualizer({
1554
+ enabled,
1555
+ rows,
1556
+ bodyScrollRef,
1557
+ estimatedRowHeight,
1558
+ overscan
1747
1559
  }) {
1748
- function commitExpansion(next, groupId) {
1749
- commitIfChanged(expansion, next, setExpansion, (committed) => {
1750
- onGroupExpansionChange?.({ groupId, expansion: committed });
1751
- });
1752
- }
1753
- function commitGroupBy(next, columnId) {
1754
- commitIfChanged(groupBy, next, setGroupBy, (committed) => {
1755
- onGroupByChange?.({ columnId, groupBy: committed });
1560
+ const [virtualizer, setVirtualizer] = useState7(null);
1561
+ const [range, setRange] = useState7(EMPTY_RANGE);
1562
+ useLayoutEffect4(() => {
1563
+ const element = bodyScrollRef.current;
1564
+ if (!enabled || element === null) {
1565
+ return void 0;
1566
+ }
1567
+ const created = createRowVirtualizer(element, {
1568
+ estimatedRowHeight,
1569
+ overscan,
1570
+ onRange: setRange
1756
1571
  });
1757
- }
1758
- function toggleGroupBy(columnId) {
1759
- const isGrouped = groupBy.some((entry) => entry.columnId === columnId);
1760
- const next = isGrouped ? groupBy.filter((entry) => entry.columnId !== columnId) : [...groupBy, { columnId }];
1761
- commitGroupBy(next, columnId);
1762
- }
1763
- function moveGroupBy(columnId, beforeColumnId) {
1764
- commitGroupBy(
1765
- moveGroupByBefore(groupBy, columnId, beforeColumnId),
1766
- columnId
1767
- );
1768
- }
1572
+ setVirtualizer(created);
1573
+ return () => {
1574
+ created.destroy();
1575
+ };
1576
+ }, [enabled, bodyScrollRef, estimatedRowHeight, overscan]);
1577
+ useLayoutEffect4(() => {
1578
+ virtualizer?.setRowCount(rows.length);
1579
+ virtualizer?.resetHeights();
1580
+ }, [virtualizer, rows]);
1769
1581
  return {
1770
- groupBy,
1771
- expansion,
1772
- isExpanded: (groupId) => !expansion.includes(groupId),
1773
- toggleExpansion: (groupId) => {
1774
- commitExpansion(toggleGroupExpansion(expansion, groupId), groupId);
1775
- },
1776
- expandAll: () => {
1777
- commitExpansion(expandAllGroups(expansion), null);
1778
- },
1779
- collapseAll: (rows) => {
1780
- commitExpansion(collapseAllGroups(rows), null);
1781
- },
1782
- toggleGroupBy,
1783
- moveGroupBy
1582
+ startIndex: range.startIndex,
1583
+ endIndex: range.endIndex,
1584
+ offsetBefore: range.offsetBefore,
1585
+ offsetAfter: range.offsetAfter,
1586
+ measureRow: virtualizer?.measureRow ?? ignoreRow,
1587
+ scrollToIndex: (index, options) => {
1588
+ virtualizer?.scrollToIndex(index, options);
1589
+ }
1784
1590
  };
1785
1591
  }
1786
1592
 
1787
1593
  // src/DataGrid/DataGrid.tsx
1788
- import { jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
1789
- var DEFAULT_PAGE_SIZE = 25;
1594
+ import { jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
1790
1595
  var NO_AGGREGATES = [];
1791
- function DataGridComponent({
1792
- dataSource,
1793
- columns,
1794
- getRowId,
1795
- borders,
1796
- hoverable,
1797
- selectable,
1798
- defaultRowSelection,
1799
- defaultColumnSelection,
1800
- defaultCellSelection,
1801
- resizableColumns = false,
1802
- reorderableColumns = false,
1803
- resizeMode = "fit",
1804
- defaultColumnSizing,
1805
- defaultColumnOrder,
1806
- columnSizeDefaults,
1807
- onColumnResize,
1808
- onColumnOrderChange,
1809
- sortableColumns = false,
1810
- defaultColumnSort,
1811
- onColumnSortChange,
1812
- groupableColumns = false,
1813
- groupToggleIconColumns = true,
1814
- groupByDraggableColumns = false,
1815
- groupByBarVisibility = "auto",
1816
- defaultGroupBy,
1817
- onGroupByChange,
1818
- defaultGroupExpansion,
1819
- onGroupExpansionChange,
1820
- defaultFilter,
1821
- paginated = false,
1822
- defaultPagination,
1823
- pager,
1824
- onPaginationChange,
1825
- aggregates,
1826
- groupAggregateDisplay = "inline",
1827
- label,
1828
- labelledBy,
1829
- ref,
1830
- ...callbacks
1831
- }) {
1596
+ function paginationReader(controller) {
1597
+ let last;
1598
+ return () => {
1599
+ const { pageIndex } = controller.getView();
1600
+ const { pageSize } = controller.getState().pagination;
1601
+ if (last?.pageIndex !== pageIndex || last.pageSize !== pageSize) {
1602
+ last = { pageIndex, pageSize };
1603
+ }
1604
+ return last;
1605
+ };
1606
+ }
1607
+ function selectedRowsReader(controller) {
1608
+ let last;
1609
+ return () => {
1610
+ const { rows } = controller.getView();
1611
+ const selection = controller.getState().rowSelection;
1612
+ if (last?.rows !== rows || last.selection !== selection) {
1613
+ const rowsById = new Map(rows.map((entry) => [entry.rowId, entry]));
1614
+ last = { rows, selection, selected: resolveRows(rowsById, selection) };
1615
+ }
1616
+ return last.selected;
1617
+ };
1618
+ }
1619
+ function DataGrid(props) {
1620
+ const {
1621
+ borders,
1622
+ hoverable,
1623
+ selectable,
1624
+ resizeMode = "fit",
1625
+ columnSizeDefaults,
1626
+ onColumnResize,
1627
+ sortableColumns = false,
1628
+ onColumnSortChange,
1629
+ groupableColumns = false,
1630
+ groupToggleIconColumns = true,
1631
+ groupByBarVisibility = "auto",
1632
+ onGroupByChange,
1633
+ onGroupExpansionChange,
1634
+ paginated = false,
1635
+ pager,
1636
+ onPaginationChange,
1637
+ onCellSelectionChange,
1638
+ infiniteScroll,
1639
+ aggregates = NO_AGGREGATES,
1640
+ groupAggregateDisplay = "inline",
1641
+ height,
1642
+ virtualized = false,
1643
+ overscan = 4,
1644
+ estimatedRowHeight = 40,
1645
+ label,
1646
+ labelledBy,
1647
+ locale,
1648
+ getRowProps,
1649
+ onRowClick,
1650
+ onRowDoubleClick,
1651
+ messages: messageOverrides,
1652
+ ref
1653
+ } = props;
1832
1654
  const hoverRows = hoverable?.rows ?? true;
1833
1655
  const hoverColumns = hoverable?.columns ?? true;
1834
1656
  const hoverCells = hoverable?.cells ?? true;
1835
1657
  const viewportRef = useRef4(null);
1836
- const tableRef = useRef4(null);
1658
+ const headerTableRef = useRef4(null);
1659
+ const bodyTableRef = useRef4(null);
1660
+ const bodyScrollRef = useRef4(null);
1661
+ const sentinelRef = useRef4(null);
1837
1662
  const viewportWidth = useElementWidth(viewportRef, resizeMode === "fit");
1838
1663
  const subscribersRef = useRef4(/* @__PURE__ */ new Set());
1839
- const [sizing, setSizing] = useState5(
1840
- defaultColumnSizing ?? {}
1841
- );
1842
- const [order, setOrder] = useState5(
1843
- defaultColumnOrder ?? []
1844
- );
1845
- const [sort, setSort] = useState5(defaultColumnSort ?? []);
1846
- const [groupBy, setGroupBy] = useState5(defaultGroupBy ?? []);
1847
- const [expansion, setExpansion] = useState5(
1848
- defaultGroupExpansion ?? []
1849
- );
1850
- const [filter] = useState5(defaultFilter ?? []);
1851
- const [pagination, setPagination] = useState5(
1852
- defaultPagination ?? { pageIndex: 0, pageSize: DEFAULT_PAGE_SIZE }
1853
- );
1854
- const [announcement, setAnnouncement] = useState5("");
1855
- const [rowSelection, setRowSelection] = useState5(
1856
- defaultRowSelection ?? []
1857
- );
1858
- const [columnSelection, setColumnSelection] = useState5(
1859
- defaultColumnSelection ?? []
1860
- );
1861
- const [cellSelection, setCellSelection] = useState5(
1862
- defaultCellSelection ?? null
1863
- );
1864
- const rows = useMemo4(
1865
- () => dataSource?.map((row, rowIndex) => ({
1866
- rowId: resolveRowId(row, rowIndex, getRowId),
1867
- row,
1868
- rowIndex,
1869
- datasetIndex: rowIndex
1870
- })) ?? [],
1871
- [dataSource, getRowId]
1872
- );
1873
- const definedColumns = useMemo4(() => {
1874
- if (columns && columns.length !== 0) return columns;
1875
- if (dataSource && dataSource.length > 0)
1876
- return defineColumnsFromRows(dataSource);
1877
- return [];
1878
- }, [columns, dataSource]);
1879
- const orderedColumns = useMemo4(
1880
- () => applyColumnOrder(definedColumns, order),
1881
- [definedColumns, order]
1664
+ const announce = useLiveAnnouncer(viewportRef);
1665
+ const messages = useMemo3(
1666
+ () => resolveMessages(messageOverrides),
1667
+ [messageOverrides]
1882
1668
  );
1883
- const resolved = useMemo4(() => {
1884
- const widths = resolveColumnWidths(orderedColumns, sizing, {
1885
- sizes: columnSizeDefaults,
1886
- resizable: resizableColumns,
1887
- reorderable: reorderableColumns,
1888
- groupByDraggable: groupByDraggableColumns
1889
- });
1890
- return resizeMode === "fit" && viewportWidth !== null ? fitColumnsToWidth(widths, viewportWidth, columnSizeDefaults) : widths;
1891
- }, [
1892
- orderedColumns,
1893
- sizing,
1894
- resizeMode,
1669
+ const { controller, state, view } = useGridController({
1670
+ ...props,
1895
1671
  viewportWidth,
1896
- columnSizeDefaults,
1897
- resizableColumns,
1898
- reorderableColumns,
1899
- groupByDraggableColumns
1900
- ]);
1901
- const shownRows = useMemo4(
1902
- () => resolveShownRows(rows, filter, sort, resolved),
1903
- [rows, filter, sort, resolved]
1904
- );
1905
- const displayRows = useMemo4(
1906
- () => groupRows(shownRows, groupBy, expansion, resolved),
1907
- [shownRows, groupBy, expansion, resolved]
1908
- );
1909
- const activeAggregates = aggregates ?? NO_AGGREGATES;
1910
- const aggregatedRows = useMemo4(
1911
- () => withGroupAggregates(
1912
- displayRows,
1913
- shownRows,
1914
- groupBy,
1915
- activeAggregates,
1916
- resolved,
1917
- groupAggregateDisplay
1918
- ),
1919
- [
1920
- displayRows,
1921
- shownRows,
1922
- groupBy,
1923
- activeAggregates,
1924
- resolved,
1925
- groupAggregateDisplay
1926
- ]
1927
- );
1928
- const grandTotal = useMemo4(
1929
- () => computeAggregates(
1930
- shownRows.map((entry) => entry.row),
1931
- activeAggregates,
1932
- resolved
1933
- ),
1934
- [shownRows, activeAggregates, resolved]
1935
- );
1936
- const paginatedRows = useMemo4(
1937
- () => paginated ? paginateRows2(aggregatedRows, pagination) : { rows: aggregatedRows, pageCount: 1, pageIndex: 0 },
1938
- [paginated, aggregatedRows, pagination]
1939
- );
1940
- const paginationSnapshot = useMemo4(
1941
- () => ({
1942
- pageIndex: paginatedRows.pageIndex,
1943
- pageSize: pagination.pageSize
1944
- }),
1945
- [paginatedRows.pageIndex, pagination.pageSize]
1946
- );
1947
- const displayDataRows = useMemo4(
1948
- () => groupBy.length === 0 && !paginated ? shownRows : paginatedRows.rows.filter(
1949
- (entry) => !("kind" in entry)
1950
- ),
1951
- [paginatedRows, groupBy.length, paginated, shownRows]
1952
- );
1953
- function columnName(columnId) {
1954
- const entry = resolved.find((candidate) => candidate.id === columnId);
1955
- if (entry === void 0) {
1956
- return columnId;
1672
+ onColumnResize: handleColumnResize,
1673
+ onColumnSortChange: handleColumnSortChange,
1674
+ onGroupByChange: handleGroupByChange,
1675
+ onGroupExpansionChange: handleGroupExpansionChange,
1676
+ onPaginationChange: handlePaginationChange,
1677
+ onCellSelectionChange: handleCellSelectionChange,
1678
+ onSelectionDiff: handleSelectionDiff
1679
+ });
1680
+ const [readPagination] = useState8(() => paginationReader(controller));
1681
+ const [readSelectedRows] = useState8(() => selectedRowsReader(controller));
1682
+ const virtualizer = useRowVirtualizer({
1683
+ enabled: virtualized,
1684
+ rows: view.pageRows,
1685
+ bodyScrollRef,
1686
+ estimatedRowHeight,
1687
+ overscan
1688
+ });
1689
+ useEffect3(() => {
1690
+ if (virtualized && height === void 0) {
1691
+ console.error(
1692
+ "DataGrid: `virtualized` has no effect without `height` \u2014 a body with no bounded height has no viewport to window rows against."
1693
+ );
1957
1694
  }
1958
- return typeof entry.label === "string" ? entry.label : entry.column.field;
1959
- }
1960
- function announce(message) {
1961
- setAnnouncement(message);
1695
+ }, [virtualized, height]);
1696
+ const hasInfiniteScroll = infiniteScroll !== void 0;
1697
+ useEffect3(() => {
1698
+ if (paginated && hasInfiniteScroll) {
1699
+ console.error(
1700
+ "DataGrid: `paginated` and `infiniteScroll` are mutually exclusive \u2014 only `paginated` takes effect while both are set."
1701
+ );
1702
+ }
1703
+ }, [paginated, hasInfiniteScroll]);
1704
+ useInfiniteScroll({
1705
+ rootRef: bodyScrollRef,
1706
+ sentinelRef,
1707
+ config: infiniteScroll
1708
+ });
1709
+ function columnName(columnId) {
1710
+ const entry = view.dataColumns.find(
1711
+ (candidate) => candidate.id === columnId
1712
+ );
1713
+ return entry === void 0 ? columnId : resolveColumnHeaderText(entry.column);
1962
1714
  }
1963
1715
  function handleColumnResize(event) {
1964
1716
  onColumnResize?.(event);
1965
1717
  if (event.phase === "end") {
1966
1718
  announce(
1967
- `${columnName(event.columnId)}, ${String(Math.round(event.width))} pixels wide`
1719
+ messages.columnResized(
1720
+ columnName(event.columnId),
1721
+ Math.round(event.width)
1722
+ )
1968
1723
  );
1969
1724
  }
1970
1725
  }
1971
1726
  const resize = useColumnResize({
1972
- tableRef,
1973
- sizing,
1974
- setSizing,
1975
- columnSizeDefaults,
1976
- onColumnResize: handleColumnResize
1727
+ // `@gridkitjs/dom`'s `measureColumnContent` only ever matches
1728
+ // `td[data-gridkit-column]` (never `th`), so the header table has nothing
1729
+ // for it to find — the body table alone was already the effective scope
1730
+ // before this split.
1731
+ tableRef: bodyTableRef,
1732
+ controller,
1733
+ columnSizeDefaults
1977
1734
  });
1978
1735
  function handleGroupByChange(event) {
1979
1736
  onGroupByChange?.(event);
@@ -1981,209 +1738,230 @@ function DataGridComponent({
1981
1738
  (candidate) => candidate.columnId === event.columnId
1982
1739
  );
1983
1740
  if (entry === void 0) {
1984
- announce(`${columnName(event.columnId)}, grouping removed`);
1741
+ announce(messages.groupingRemoved(columnName(event.columnId)));
1985
1742
  return;
1986
1743
  }
1987
1744
  announce(
1988
- event.groupBy.length > 1 ? `Grouped by ${columnName(event.columnId)}, level ${String(event.groupBy.indexOf(entry) + 1)} of ${String(event.groupBy.length)}` : `Grouped by ${columnName(event.columnId)}`
1745
+ messages.groupedBy(
1746
+ columnName(event.columnId),
1747
+ event.groupBy.indexOf(entry) + 1,
1748
+ event.groupBy.length
1749
+ )
1989
1750
  );
1990
1751
  }
1991
1752
  function handleGroupExpansionChange(event) {
1992
1753
  onGroupExpansionChange?.(event);
1993
1754
  if (event.groupId === null) {
1994
1755
  announce(
1995
- event.expansion.length === 0 ? "All groups expanded" : "All groups collapsed"
1756
+ event.expansion.length === 0 ? messages.allGroupsExpanded : messages.allGroupsCollapsed
1996
1757
  );
1997
1758
  return;
1998
1759
  }
1999
1760
  announce(
2000
- event.expansion.includes(event.groupId) ? "Group collapsed" : "Group expanded"
1761
+ event.expansion.includes(event.groupId) ? messages.groupCollapsed : messages.groupExpanded
2001
1762
  );
2002
1763
  }
2003
- const grouping = useRowGrouping({
2004
- groupBy,
2005
- setGroupBy,
2006
- expansion,
2007
- setExpansion,
2008
- onGroupByChange: handleGroupByChange,
2009
- onGroupExpansionChange: handleGroupExpansionChange
2010
- });
1764
+ function handleColumnSortChange(event) {
1765
+ onColumnSortChange?.(event);
1766
+ const entry = event.sort.find(
1767
+ (candidate) => candidate.columnId === event.columnId
1768
+ );
1769
+ if (entry === void 0) {
1770
+ announce(messages.sortCleared(columnName(event.columnId)));
1771
+ return;
1772
+ }
1773
+ announce(
1774
+ messages.columnSorted(
1775
+ columnName(event.columnId),
1776
+ entry.direction,
1777
+ event.sort.indexOf(entry) + 1,
1778
+ event.sort.length
1779
+ )
1780
+ );
1781
+ }
1782
+ function handlePaginationChange(event) {
1783
+ onPaginationChange?.(event);
1784
+ announce(
1785
+ messages.pageStatus(event.pagination.pageIndex + 1, event.pageCount)
1786
+ );
1787
+ }
1788
+ function handleSelectionDiff(event) {
1789
+ if (event.added.length + event.removed.length > 1) {
1790
+ announce(messages.selectedCount(event.selection.length, event.target));
1791
+ }
1792
+ }
1793
+ function handleCellSelectionChange(event) {
1794
+ onCellSelectionChange?.(event);
1795
+ if (event.selected !== null) {
1796
+ announce(
1797
+ messages.cellSelected(
1798
+ columnName(event.selected.columnId),
1799
+ event.selected.rowIndex + 1
1800
+ )
1801
+ );
1802
+ }
1803
+ }
2011
1804
  const nav = useGridNavigation({
2012
- tableRef,
1805
+ headerTableRef,
1806
+ bodyTableRef,
1807
+ // `null` when virtualization is off, in which case behavior is
1808
+ // byte-for-byte unchanged from before this feature existed.
1809
+ virtualRange: virtualized ? virtualizer : null,
2013
1810
  // Group headers are addressable rows too — the whole point of the flat
2014
1811
  // `DisplayRow[]` shape is that they share one position space with data
2015
1812
  // rows, so navigation counts them the same way. Page-relative
2016
- // (`paginatedRows`, not `displayRows`): arrow keys operate on what's
1813
+ // (`view.pageRows`, not `view.displayRows`): arrow keys operate on what's
2017
1814
  // actually in the DOM, which is the current page — a different "page"
2018
1815
  // concept than `useGridNavigation`'s own Page Up/Down viewport scrolling.
2019
- rowCount: paginatedRows.rows.length,
2020
- columnCount: resolved.length,
1816
+ rowCount: view.pageRows.length,
1817
+ columnCount: view.columns.length,
2021
1818
  // A group's own summary row (`groupAggregateDisplay: "row"`) occupies a
2022
1819
  // real slot in `rowCount` above — its DOM position has to line up with
2023
1820
  // everything else — but is presentational, never a tab stop: arrow-key
2024
1821
  // vertical movement steps over it rather than landing there.
2025
1822
  isSkippableRow: (rowIndex) => {
2026
- const entry = paginatedRows.rows[rowIndex];
1823
+ const entry = view.pageRows[rowIndex];
2027
1824
  return entry !== void 0 && "kind" in entry && entry.kind === "group-summary";
2028
1825
  }
2029
1826
  });
2030
- const displayedIds = useMemo4(
2031
- () => orderedColumns.map((column) => column.id ?? column.field),
2032
- [orderedColumns]
1827
+ const orderedIds = useMemo3(
1828
+ () => view.dataColumns.map((entry) => entry.id),
1829
+ [view.dataColumns]
1830
+ );
1831
+ const displayedIds = useMemo3(
1832
+ () => view.columns.map((entry) => entry.id),
1833
+ [view.columns]
2033
1834
  );
2034
- const groupByDraggableIds = useMemo4(
1835
+ const definitions = useMemo3(
1836
+ () => new Map(view.dataColumns.map((entry) => [entry.id, entry.column])),
1837
+ [view.dataColumns]
1838
+ );
1839
+ const groupByDraggableIds = useMemo3(
2035
1840
  () => new Set(
2036
- resolved.filter((entry) => entry.groupByDraggable).map((entry) => entry.id)
1841
+ view.columns.filter((entry) => entry.groupByDraggable).map((entry) => entry.id)
2037
1842
  ),
2038
- [resolved]
1843
+ [view.columns]
2039
1844
  );
2040
1845
  function handleDrop(target, movedId) {
2041
1846
  if (target.kind === "group-by") {
2042
- grouping.moveGroupBy(movedId, target.beforeColumnId);
1847
+ controller.moveGroupBy(movedId, target.beforeColumnId);
2043
1848
  return;
2044
1849
  }
2045
- const next = moveColumnBefore(displayedIds, movedId, target.beforeId);
2046
- if (next === displayedIds) {
1850
+ const next = moveColumnBefore(orderedIds, movedId, target.beforeId);
1851
+ if (next === orderedIds) {
2047
1852
  return;
2048
1853
  }
2049
- const position = next.indexOf(movedId);
2050
- setOrder(next);
2051
- onColumnOrderChange?.({ columnId: movedId, order: next });
1854
+ controller.moveColumn(movedId, target.beforeId);
1855
+ const shown = next.filter((id) => displayedIds.includes(id));
1856
+ const position = shown.indexOf(movedId);
2052
1857
  announce(
2053
- `${columnName(movedId)}, column ${String(position + 1)} of ${String(next.length)}`
1858
+ messages.columnMoved(columnName(movedId), position + 1, shown.length)
2054
1859
  );
2055
1860
  nav.focusCell(HEADER_ROW, position);
2056
1861
  }
2057
1862
  const drag = useColumnDrag({
2058
1863
  order: displayedIds,
2059
- groupBy,
1864
+ groupBy: state.groupBy,
2060
1865
  groupByDraggableIds,
2061
1866
  onDrop: handleDrop
2062
1867
  });
2063
- const draggedColumnAlreadyGrouped = drag.draggedColumnId !== null && groupBy.some((entry) => entry.columnId === drag.draggedColumnId);
1868
+ const draggedColumnAlreadyGrouped = drag.draggedColumnId !== null && state.groupBy.some((entry) => entry.columnId === drag.draggedColumnId);
2064
1869
  const dragEligibleForGroupBy = drag.draggedColumnId !== null && groupByDraggableIds.has(drag.draggedColumnId) && !draggedColumnAlreadyGrouped;
2065
- const showGroupByBar = groupByBarVisibility === "always" || groupByBarVisibility === "auto" && (groupBy.length > 0 || dragEligibleForGroupBy);
1870
+ const showGroupByBar = groupByBarVisibility === "always" || groupByBarVisibility === "auto" && (state.groupBy.length > 0 || dragEligibleForGroupBy);
2066
1871
  const headerGroupByDropTarget = drag.dropTarget?.kind === "group-by" ? drag.dropTarget : null;
2067
- function handleColumnSortChange(event) {
2068
- onColumnSortChange?.(event);
2069
- const entry = event.sort.find(
2070
- (candidate) => candidate.columnId === event.columnId
2071
- );
2072
- if (entry === void 0) {
2073
- announce(`${columnName(event.columnId)}, sort cleared`);
2074
- return;
2075
- }
2076
- const direction = entry.direction === "asc" ? "ascending" : "descending";
2077
- announce(
2078
- event.sort.length > 1 ? `${columnName(event.columnId)} sorted ${direction}, key ${String(event.sort.indexOf(entry) + 1)} of ${String(event.sort.length)}` : `${columnName(event.columnId)} sorted ${direction}`
2079
- );
2080
- }
2081
- const columnSort = useColumnSort({
2082
- sort,
2083
- setSort,
2084
- onColumnSortChange: handleColumnSortChange
2085
- });
2086
- function handlePaginationChange(event) {
2087
- onPaginationChange?.(event);
2088
- announce(
2089
- `Page ${String(event.pagination.pageIndex + 1)} of ${String(event.pageCount)}`
2090
- );
2091
- }
2092
- const paginationApi = usePagination({
2093
- pagination,
2094
- setPagination,
2095
- rows: displayRows,
2096
- onPaginationChange: handlePaginationChange
2097
- });
2098
- const [previous, setPrevious] = useState5({ filter, sort, groupBy });
2099
- if (previous.filter !== filter || previous.sort !== sort || previous.groupBy !== groupBy) {
2100
- setPrevious({ filter, sort, groupBy });
2101
- if (pagination.pageIndex !== 0) {
2102
- setPagination({ ...pagination, pageIndex: 0 });
2103
- }
2104
- }
2105
- const selection = useGridSelection({
2106
- rows: displayDataRows,
2107
- columns: resolved,
2108
- selectable,
2109
- rowSelection,
2110
- setRowSelection,
2111
- columnSelection,
2112
- setColumnSelection,
2113
- cellSelection,
2114
- setCellSelection,
2115
- callbacks,
2116
- announce,
2117
- columnName
2118
- });
1872
+ const pagerContext = {
1873
+ pagination: state.pagination,
1874
+ currentPage: Math.min(state.pagination.pageIndex + 1, view.pageCount),
1875
+ pageCount: view.pageCount,
1876
+ pageSizeOptions: pager?.sizeOptions,
1877
+ goToPage: controller.goToPage,
1878
+ nextPage: controller.nextPage,
1879
+ previousPage: controller.previousPage,
1880
+ setPageSize: controller.setPageSize
1881
+ };
1882
+ const selection = useGridSelection({ controller, state, selectable });
2119
1883
  const multiselectable = selection.rowMode === "multiple" || selection.columnMode === "multiple";
2120
- useEffect2(() => {
1884
+ useEffect3(() => {
2121
1885
  for (const listener of subscribersRef.current) {
2122
1886
  listener();
2123
1887
  }
2124
- }, [
2125
- shownRows,
2126
- aggregatedRows,
2127
- resolved,
2128
- sizing,
2129
- order,
2130
- sort,
2131
- groupBy,
2132
- expansion,
2133
- paginationSnapshot,
2134
- paginatedRows.pageCount,
2135
- grandTotal,
2136
- rowSelection,
2137
- columnSelection,
2138
- cellSelection,
2139
- nav.focus
2140
- ]);
1888
+ }, [view, state, nav.focus]);
2141
1889
  useImperativeHandle(ref, () => ({
2142
1890
  get element() {
2143
1891
  return viewportRef.current;
2144
1892
  },
2145
1893
  get table() {
2146
- return tableRef.current;
1894
+ return bodyTableRef.current;
2147
1895
  },
2148
- getRows: () => shownRows,
2149
- // `aggregatedRows`, not the plain `displayRows` it's derived from: the
2150
- // same array shape and content, except every group header's own
2151
- // `aggregates` field is populated — the whole reason `getDisplayRows()`
2152
- // is where a consumer reads a specific group's computed results from.
2153
- getDisplayRows: () => aggregatedRows,
2154
- getColumns: () => resolved,
2155
- getColumnSizing: () => sizing,
2156
- getColumnOrder: () => order,
2157
- getColumnSort: () => sort,
2158
- getGroupBy: () => groupBy,
2159
- getGroupExpansion: () => expansion,
2160
- getPagination: () => paginationSnapshot,
2161
- getPageCount: () => paginatedRows.pageCount,
2162
- getAggregates: () => grandTotal,
2163
- getRowSelection: () => rowSelection,
2164
- getColumnSelection: () => columnSelection,
2165
- getCellSelection: () => cellSelection,
1896
+ getRows: () => controller.getView().rows,
1897
+ // Every group header's own `aggregates` field is populated here — the
1898
+ // whole reason `getDisplayRows()` is where a consumer reads a specific
1899
+ // group's computed results from.
1900
+ getDisplayRows: () => controller.getView().displayRows,
1901
+ getPageRows: () => controller.getView().pageRows,
1902
+ getColumns: () => controller.getView().columns,
1903
+ getDataColumns: () => controller.getView().dataColumns,
1904
+ getColumnSizing: () => controller.getState().columnSizing,
1905
+ getColumnOrder: () => controller.getState().columnOrder,
1906
+ getColumnVisibility: () => controller.getState().columnVisibility,
1907
+ getColumnSort: () => controller.getState().columnSort,
1908
+ getFilter: () => controller.getState().filter,
1909
+ getGroupBy: () => controller.getState().groupBy,
1910
+ getGroupExpansion: () => controller.getState().groupExpansion,
1911
+ getPagination: readPagination,
1912
+ getPageCount: () => controller.getView().pageCount,
1913
+ getAggregates: () => controller.getView().grandTotal,
1914
+ getRowSelection: () => controller.getState().rowSelection,
1915
+ getSelectedRows: readSelectedRows,
1916
+ getColumnSelection: () => controller.getState().columnSelection,
1917
+ getCellSelection: () => controller.getState().cellSelection,
2166
1918
  getFocusedCell: () => nav.focus,
1919
+ getState: controller.getState,
1920
+ restoreState: controller.restoreState,
2167
1921
  focusCell: nav.focusCell,
2168
- clearSelection: selection.clear,
2169
- selectAllRows: selection.selectAllRows,
2170
- expandAllGroups: grouping.expandAll,
2171
- collapseAllGroups: () => {
2172
- grouping.collapseAll(displayRows);
1922
+ clearSelection: controller.clearSelection,
1923
+ selectAllRows: controller.selectAllRows,
1924
+ selectRows: controller.selectRows,
1925
+ expandAllGroups: controller.expandAllGroups,
1926
+ collapseAllGroups: controller.collapseAllGroups,
1927
+ goToPage: controller.goToPage,
1928
+ nextPage: controller.nextPage,
1929
+ previousPage: controller.previousPage,
1930
+ setPageSize: controller.setPageSize,
1931
+ showColumns: controller.showColumns,
1932
+ hideColumns: controller.hideColumns,
1933
+ setFilter: controller.setFilter,
1934
+ setColumnSort: controller.setColumnSort,
1935
+ setGroupBy: controller.setGroupBy,
1936
+ setGroupExpansion: controller.setGroupExpansion,
1937
+ setPagination: controller.setPagination,
1938
+ setColumnSizing: controller.setColumnSizing,
1939
+ setColumnOrder: controller.setColumnOrder,
1940
+ setColumnVisibility: controller.setColumnVisibility,
1941
+ setRowSelection: controller.setRowSelection,
1942
+ setColumnSelection: controller.setColumnSelection,
1943
+ setCellSelection: controller.setCellSelection,
1944
+ autoFitColumn: (columnId) => {
1945
+ const entry = controller.getView().columns.find((candidate) => candidate.id === columnId);
1946
+ if (entry !== void 0) {
1947
+ resize.sizeToContent(entry);
1948
+ }
2173
1949
  },
2174
- goToPage: paginationApi.goToPage,
2175
- nextPage: paginationApi.nextPage,
2176
- previousPage: paginationApi.previousPage,
2177
- setPageSize: paginationApi.setPageSize,
2178
1950
  scrollToRow: (rowId, options) => {
2179
- const index = paginatedRows.rows.findIndex(
1951
+ const index = controller.getView().pageRows.findIndex(
2180
1952
  (entry) => !("kind" in entry) && entry.rowId === rowId
2181
1953
  );
2182
1954
  if (index === -1) return;
2183
- tableRef.current?.tBodies[0]?.rows[index]?.scrollIntoView(options);
1955
+ if (virtualized) {
1956
+ virtualizer.scrollToIndex(index, options);
1957
+ return;
1958
+ }
1959
+ bodyTableRef.current?.tBodies[0]?.rows[index]?.scrollIntoView(options);
2184
1960
  },
2185
1961
  scrollToColumn: (columnId, options) => {
2186
- const cells = tableRef.current?.querySelectorAll("[data-gridkit-column]");
1962
+ const cells = viewportRef.current?.querySelectorAll(
1963
+ "[data-gridkit-column]"
1964
+ );
2187
1965
  const cell = cells ? Array.from(cells).find(
2188
1966
  (entry) => entry.getAttribute("data-gridkit-column") === columnId
2189
1967
  ) : void 0;
@@ -2196,25 +1974,41 @@ function DataGridComponent({
2196
1974
  };
2197
1975
  }
2198
1976
  }));
2199
- return /* @__PURE__ */ jsxs6("div", { className: "gridkit-data-grid-viewport", ref: viewportRef, children: [
2200
- showGroupByBar && /* @__PURE__ */ jsx9(
1977
+ const gridTableClassName = classNames(
1978
+ "gridkit-data-grid",
1979
+ borders ? `borders-${borders}` : "",
1980
+ // Hover is on by default and selection off, so one set of classes
1981
+ // turns styling off and the other turns it on. The polarity differs
1982
+ // because the defaults do.
1983
+ hoverRows ? "" : "no-hover-rows",
1984
+ hoverColumns ? "" : "no-hover-columns",
1985
+ hoverCells ? "" : "no-hover-cells",
1986
+ selection.rowMode === false ? "" : "selectable-rows",
1987
+ selection.columnMode === false ? "" : "selectable-columns",
1988
+ selection.cellMode === false ? "" : "selectable-cells"
1989
+ );
1990
+ return /* @__PURE__ */ jsxs7("div", { className: "gridkit-data-grid-viewport", ref: viewportRef, children: [
1991
+ showGroupByBar && /* @__PURE__ */ jsx10(
2201
1992
  GroupByBar,
2202
1993
  {
2203
- columns: resolved,
2204
- grouping,
1994
+ columns: view.columns,
1995
+ definitions,
1996
+ groupBy: state.groupBy,
1997
+ controller,
2205
1998
  columnName,
2206
1999
  headerDragEligible: dragEligibleForGroupBy,
2207
2000
  headerDragBlocked: draggedColumnAlreadyGrouped,
2208
- headerDropTarget: headerGroupByDropTarget
2001
+ headerDropTarget: headerGroupByDropTarget,
2002
+ messages
2209
2003
  }
2210
2004
  ),
2211
- /* @__PURE__ */ jsxs6(
2212
- "table",
2005
+ /* @__PURE__ */ jsxs7(
2006
+ "div",
2213
2007
  {
2214
- ref: tableRef,
2215
- role: groupBy.length > 0 ? "treegrid" : "grid",
2216
- "aria-rowcount": aggregatedRows.length + 1,
2217
- "aria-colcount": resolved.length,
2008
+ className: "gridkit-data-grid-tables",
2009
+ role: state.groupBy.length > 0 ? "treegrid" : "grid",
2010
+ "aria-rowcount": view.displayRows.length + 1,
2011
+ "aria-colcount": view.columns.length,
2218
2012
  ...ariaAttr(multiselectable, "aria-multiselectable", true),
2219
2013
  ...ariaAttr(labelledBy !== void 0, "aria-labelledby", labelledBy),
2220
2014
  ...ariaAttr(
@@ -2235,83 +2029,117 @@ function DataGridComponent({
2235
2029
  selection.selectAllRows();
2236
2030
  }
2237
2031
  },
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
2032
  children: [
2253
- /* @__PURE__ */ jsx9("colgroup", { children: resolved.map((entry) => /* @__PURE__ */ jsx9("col", { style: { width: entry.width } }, entry.id)) }),
2254
- /* @__PURE__ */ jsx9(
2255
- GridHeader,
2033
+ /* @__PURE__ */ jsxs7(
2034
+ "table",
2256
2035
  {
2257
- columns: resolved,
2258
- resize,
2259
- drag,
2260
- sort: columnSort,
2261
- sortableColumns,
2262
- grouping,
2263
- groupableColumns,
2264
- groupToggleIconColumns,
2265
- nav,
2266
- selection
2036
+ ref: headerTableRef,
2037
+ style: { width: totalColumnWidth(view.columns) },
2038
+ className: classNames(gridTableClassName, "gridkit-data-grid-header"),
2039
+ children: [
2040
+ /* @__PURE__ */ jsx10(GridColgroup, { columns: view.columns }),
2041
+ /* @__PURE__ */ jsx10(
2042
+ GridHeader,
2043
+ {
2044
+ columns: view.columns,
2045
+ resize,
2046
+ drag,
2047
+ controller,
2048
+ columnSort: state.columnSort,
2049
+ sortableColumns,
2050
+ groupBy: state.groupBy,
2051
+ groupableColumns,
2052
+ groupToggleIconColumns,
2053
+ nav,
2054
+ selection
2055
+ }
2056
+ )
2057
+ ]
2267
2058
  }
2268
2059
  ),
2269
- /* @__PURE__ */ jsx9(
2270
- GridBody,
2060
+ /* @__PURE__ */ jsx10(
2061
+ "div",
2271
2062
  {
2272
- columns: resolved,
2273
- rows: paginatedRows.rows,
2274
- activeColumnId: resize.activeColumnId,
2275
- nav,
2276
- selection,
2277
- grouping,
2278
- aggregates: activeAggregates,
2279
- groupAggregateDisplay
2063
+ className: "gridkit-data-grid-body",
2064
+ ref: bodyScrollRef,
2065
+ style: height === void 0 ? void 0 : {
2066
+ "--gridkit-body-height": typeof height === "number" ? `${String(height)}px` : height
2067
+ },
2068
+ children: /* @__PURE__ */ jsxs7(
2069
+ "table",
2070
+ {
2071
+ ref: bodyTableRef,
2072
+ style: { width: totalColumnWidth(view.columns) },
2073
+ className: gridTableClassName,
2074
+ children: [
2075
+ /* @__PURE__ */ jsx10(GridColgroup, { columns: view.columns }),
2076
+ /* @__PURE__ */ jsx10(
2077
+ GridBody,
2078
+ {
2079
+ columns: view.columns,
2080
+ definitions,
2081
+ rows: view.pageRows,
2082
+ activeColumnId: resize.activeColumnId,
2083
+ nav,
2084
+ selection,
2085
+ controller,
2086
+ aggregates,
2087
+ groupAggregateDisplay,
2088
+ virtualRange: virtualized ? virtualizer : null,
2089
+ infiniteScroll,
2090
+ sentinelRef,
2091
+ locale,
2092
+ getRowProps,
2093
+ onRowClick,
2094
+ onRowDoubleClick,
2095
+ messages
2096
+ }
2097
+ )
2098
+ ]
2099
+ }
2100
+ )
2280
2101
  }
2281
2102
  ),
2282
- activeAggregates.length > 0 && /* @__PURE__ */ jsx9(
2283
- GridFooter,
2103
+ aggregates.length > 0 && /* @__PURE__ */ jsxs7(
2104
+ "table",
2284
2105
  {
2285
- columns: resolved,
2286
- aggregates: activeAggregates,
2287
- results: grandTotal,
2288
- rows: shownRows.map((entry) => entry.row)
2106
+ style: { width: totalColumnWidth(view.columns) },
2107
+ className: classNames(
2108
+ gridTableClassName,
2109
+ "gridkit-data-grid-footer"
2110
+ ),
2111
+ children: [
2112
+ /* @__PURE__ */ jsx10(GridColgroup, { columns: view.columns }),
2113
+ /* @__PURE__ */ jsx10(
2114
+ GridFooter,
2115
+ {
2116
+ columns: view.columns,
2117
+ aggregates,
2118
+ results: view.grandTotal,
2119
+ rows: view.rows.map((entry) => entry.row),
2120
+ locale
2121
+ }
2122
+ )
2123
+ ]
2289
2124
  }
2290
2125
  )
2291
2126
  ]
2292
2127
  }
2293
2128
  ),
2294
- paginated && (pager?.template ? pager.template({
2295
- pagination: paginationApi.pagination,
2296
- currentPage: paginationApi.currentPage,
2297
- pageCount: paginationApi.pageCount,
2298
- pageSizeOptions: pager.sizeOptions,
2299
- goToPage: paginationApi.goToPage,
2300
- nextPage: paginationApi.nextPage,
2301
- previousPage: paginationApi.previousPage,
2302
- 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 })
2129
+ paginated && (pager?.template ? pager.template(pagerContext) : /* @__PURE__ */ jsx10(GridPager, { pager: pagerContext, config: pager, messages }))
2305
2130
  ] });
2306
2131
  }
2307
2132
 
2133
+ // src/index.ts
2134
+ import { serializeGridState } from "@gridkitjs/core";
2135
+
2308
2136
  // src/DataGrid/hooks/usePaginationState.ts
2309
- import { useCallback as useCallback2 } from "react";
2137
+ import { useCallback as useCallback3 } from "react";
2310
2138
 
2311
2139
  // src/DataGrid/hooks/useGridSnapshot.ts
2312
- import { useCallback, useSyncExternalStore } from "react";
2140
+ import { useCallback as useCallback2, useSyncExternalStore as useSyncExternalStore3 } from "react";
2313
2141
  function useGridSnapshot(gridRef, getSnapshot, fallback) {
2314
- const subscribe = useCallback(
2142
+ const subscribe = useCallback2(
2315
2143
  (listener) => {
2316
2144
  const api = gridRef.current;
2317
2145
  if (api === null) {
@@ -2322,7 +2150,7 @@ function useGridSnapshot(gridRef, getSnapshot, fallback) {
2322
2150
  },
2323
2151
  [gridRef]
2324
2152
  );
2325
- return useSyncExternalStore(subscribe, () => {
2153
+ return useSyncExternalStore3(subscribe, () => {
2326
2154
  const api = gridRef.current;
2327
2155
  return api === null ? fallback : getSnapshot(api);
2328
2156
  });
@@ -2333,12 +2161,12 @@ var FALLBACK_PAGINATION = { pageIndex: 0, pageSize: 0 };
2333
2161
  function usePaginationState(gridRef) {
2334
2162
  const pagination = useGridSnapshot(
2335
2163
  gridRef,
2336
- useCallback2((api) => api.getPagination(), []),
2164
+ useCallback3((api) => api.getPagination(), []),
2337
2165
  FALLBACK_PAGINATION
2338
2166
  );
2339
2167
  const pageCount = useGridSnapshot(
2340
2168
  gridRef,
2341
- useCallback2((api) => api.getPageCount(), []),
2169
+ useCallback3((api) => api.getPageCount(), []),
2342
2170
  0
2343
2171
  );
2344
2172
  return {
@@ -2360,18 +2188,18 @@ function usePaginationState(gridRef) {
2360
2188
  }
2361
2189
 
2362
2190
  // src/DataGrid/hooks/useGroupByState.ts
2363
- import { useCallback as useCallback3 } from "react";
2191
+ import { useCallback as useCallback4 } from "react";
2364
2192
  var FALLBACK_GROUP_BY = [];
2365
2193
  var FALLBACK_EXPANSION = [];
2366
2194
  function useGroupByState(gridRef) {
2367
2195
  const groupBy = useGridSnapshot(
2368
2196
  gridRef,
2369
- useCallback3((api) => api.getGroupBy(), []),
2197
+ useCallback4((api) => api.getGroupBy(), []),
2370
2198
  FALLBACK_GROUP_BY
2371
2199
  );
2372
2200
  const groupExpansion = useGridSnapshot(
2373
2201
  gridRef,
2374
- useCallback3((api) => api.getGroupExpansion(), []),
2202
+ useCallback4((api) => api.getGroupExpansion(), []),
2375
2203
  FALLBACK_EXPANSION
2376
2204
  );
2377
2205
  return {
@@ -2387,34 +2215,34 @@ function useGroupByState(gridRef) {
2387
2215
  }
2388
2216
 
2389
2217
  // src/DataGrid/hooks/useColumnSortState.ts
2390
- import { useCallback as useCallback4 } from "react";
2218
+ import { useCallback as useCallback5 } from "react";
2391
2219
  var FALLBACK_SORT = [];
2392
2220
  function useColumnSortState(gridRef) {
2393
2221
  return useGridSnapshot(
2394
2222
  gridRef,
2395
- useCallback4((api) => api.getColumnSort(), []),
2223
+ useCallback5((api) => api.getColumnSort(), []),
2396
2224
  FALLBACK_SORT
2397
2225
  );
2398
2226
  }
2399
2227
 
2400
2228
  // src/DataGrid/hooks/useSelectionState.ts
2401
- import { useCallback as useCallback5 } from "react";
2229
+ import { useCallback as useCallback6 } from "react";
2402
2230
  var FALLBACK_SELECTION = [];
2403
2231
  var FALLBACK_CELL_SELECTION = null;
2404
2232
  function useSelectionState(gridRef) {
2405
2233
  const rowSelection = useGridSnapshot(
2406
2234
  gridRef,
2407
- useCallback5((api) => api.getRowSelection(), []),
2235
+ useCallback6((api) => api.getRowSelection(), []),
2408
2236
  FALLBACK_SELECTION
2409
2237
  );
2410
2238
  const columnSelection = useGridSnapshot(
2411
2239
  gridRef,
2412
- useCallback5((api) => api.getColumnSelection(), []),
2240
+ useCallback6((api) => api.getColumnSelection(), []),
2413
2241
  FALLBACK_SELECTION
2414
2242
  );
2415
2243
  const cellSelection = useGridSnapshot(
2416
2244
  gridRef,
2417
- useCallback5((api) => api.getCellSelection(), []),
2245
+ useCallback6((api) => api.getCellSelection(), []),
2418
2246
  FALLBACK_CELL_SELECTION
2419
2247
  );
2420
2248
  return {
@@ -2431,43 +2259,74 @@ function useSelectionState(gridRef) {
2431
2259
  }
2432
2260
 
2433
2261
  // src/DataGrid/hooks/useAggregateState.ts
2434
- import { useCallback as useCallback6 } from "react";
2262
+ import { useCallback as useCallback7 } from "react";
2435
2263
  var FALLBACK_AGGREGATES = /* @__PURE__ */ new Map();
2436
2264
  function useAggregateState(gridRef) {
2437
2265
  return useGridSnapshot(
2438
2266
  gridRef,
2439
- useCallback6((api) => api.getAggregates(), []),
2267
+ useCallback7((api) => api.getAggregates(), []),
2440
2268
  FALLBACK_AGGREGATES
2441
2269
  );
2442
2270
  }
2443
2271
 
2444
2272
  // src/DataGrid/hooks/useColumnSizingState.ts
2445
- import { useCallback as useCallback7 } from "react";
2273
+ import { useCallback as useCallback8 } from "react";
2446
2274
  var FALLBACK_SIZING = {};
2447
2275
  function useColumnSizingState(gridRef) {
2448
2276
  return useGridSnapshot(
2449
2277
  gridRef,
2450
- useCallback7((api) => api.getColumnSizing(), []),
2278
+ useCallback8((api) => api.getColumnSizing(), []),
2451
2279
  FALLBACK_SIZING
2452
2280
  );
2453
2281
  }
2454
2282
 
2455
2283
  // src/DataGrid/hooks/useColumnOrderState.ts
2456
- import { useCallback as useCallback8 } from "react";
2284
+ import { useCallback as useCallback9 } from "react";
2457
2285
  var FALLBACK_ORDER = [];
2458
2286
  function useColumnOrderState(gridRef) {
2459
2287
  return useGridSnapshot(
2460
2288
  gridRef,
2461
- useCallback8((api) => api.getColumnOrder(), []),
2289
+ useCallback9((api) => api.getColumnOrder(), []),
2462
2290
  FALLBACK_ORDER
2463
2291
  );
2464
2292
  }
2293
+
2294
+ // src/DataGrid/hooks/useColumnVisibilityState.ts
2295
+ import { useCallback as useCallback10 } from "react";
2296
+ var FALLBACK_VISIBILITY = {};
2297
+ function useColumnVisibilityState(gridRef) {
2298
+ return useGridSnapshot(
2299
+ gridRef,
2300
+ useCallback10((api) => api.getColumnVisibility(), []),
2301
+ FALLBACK_VISIBILITY
2302
+ );
2303
+ }
2304
+
2305
+ // src/DataGrid/hooks/useFilterState.ts
2306
+ import { useCallback as useCallback11, useState as useState9 } from "react";
2307
+ function useFilterState(gridRef) {
2308
+ const [fallback] = useState9(() => []);
2309
+ const filter = useGridSnapshot(
2310
+ gridRef,
2311
+ useCallback11((api) => api.getFilter(), []),
2312
+ fallback
2313
+ );
2314
+ return {
2315
+ filter,
2316
+ setFilter: (next) => {
2317
+ gridRef.current?.setFilter(next);
2318
+ }
2319
+ };
2320
+ }
2465
2321
  export {
2466
- DataGridComponent,
2322
+ DataGrid,
2323
+ serializeGridState,
2467
2324
  useAggregateState,
2468
2325
  useColumnOrderState,
2469
2326
  useColumnSizingState,
2470
2327
  useColumnSortState,
2328
+ useColumnVisibilityState,
2329
+ useFilterState,
2471
2330
  useGroupByState,
2472
2331
  usePaginationState,
2473
2332
  useSelectionState