@gridkitjs/react 0.9.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,24 +1,19 @@
1
+ "use client";
2
+
1
3
  // src/DataGrid/DataGrid.tsx
2
4
  import {
3
- useEffect as useEffect4,
5
+ useEffect as useEffect3,
4
6
  useImperativeHandle,
5
- useMemo as useMemo4,
6
- useRef as useRef6,
7
- useState as useState6
7
+ useMemo as useMemo3,
8
+ useRef as useRef4,
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
 
24
19
  // src/DataGrid/components/GridColgroup.tsx
@@ -29,7 +24,13 @@ function GridColgroup({ columns }) {
29
24
 
30
25
  // src/DataGrid/components/GridHeader.tsx
31
26
  import { createPortal } from "react-dom";
32
- 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";
33
34
 
34
35
  // src/DataGrid/ariaAttr.ts
35
36
  function ariaAttr(condition, key, value) {
@@ -44,27 +45,19 @@ function classNames(...values) {
44
45
  // src/DataGrid/useGridNavigation.ts
45
46
  import {
46
47
  useEffect,
47
- useMemo,
48
- useRef,
49
- useState
48
+ useLayoutEffect,
49
+ useState,
50
+ useSyncExternalStore
50
51
  } from "react";
52
+ import { HEADER_ROW } from "@gridkitjs/core";
51
53
  import {
52
- clampFocus,
53
- nextFocusForKey,
54
- HEADER_ROW
55
- } from "@gridkitjs/core";
56
- var FALLBACK_PAGE = 10;
54
+ createGridFocus,
55
+ findBodyRow,
56
+ measurePageSize
57
+ } from "@gridkitjs/dom";
57
58
  function tabIndexFor(columnIndex, focusedColumnIndex) {
58
59
  return columnIndex === focusedColumnIndex ? 0 : -1;
59
60
  }
60
- function cellAt(headerTable, bodyTable, focus) {
61
- const row = focus.rowIndex === HEADER_ROW ? headerTable?.tHead?.rows[0] ?? null : bodyTable?.tBodies[0]?.rows[focus.rowIndex] ?? null;
62
- if (row === null) {
63
- return null;
64
- }
65
- const columnIndex = Math.min(focus.columnIndex, row.cells.length - 1);
66
- return row.cells[columnIndex] ?? null;
67
- }
68
61
  function useGridNavigation({
69
62
  headerTableRef,
70
63
  bodyTableRef,
@@ -73,120 +66,46 @@ function useGridNavigation({
73
66
  isSkippableRow,
74
67
  virtualRange
75
68
  }) {
76
- const [stored, setStored] = useState({
77
- rowIndex: HEADER_ROW,
78
- columnIndex: 0
79
- });
80
- const focus = useMemo(
81
- () => clampFocus(stored, rowCount, columnCount),
82
- [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]
83
84
  );
84
- const pending = useRef(false);
85
85
  useEffect(() => {
86
- if (!pending.current) {
87
- return;
88
- }
89
- const cell = cellAt(headerTableRef.current, bodyTableRef.current, {
90
- rowIndex: focus.rowIndex,
91
- columnIndex: focus.columnIndex
92
- });
93
- if (cell !== null) {
94
- pending.current = false;
95
- cell.focus();
96
- return;
97
- }
98
- if (virtualRange != null && focus.rowIndex !== HEADER_ROW && (focus.rowIndex < virtualRange.startIndex || focus.rowIndex > virtualRange.endIndex)) {
99
- virtualRange.scrollToIndex(focus.rowIndex);
100
- return;
101
- }
102
- pending.current = false;
103
- }, [
104
- headerTableRef,
105
- bodyTableRef,
106
- focus.rowIndex,
107
- focus.columnIndex,
108
- virtualRange
109
- ]);
110
- function pageSize() {
111
- const table = bodyTableRef.current;
112
- const firstRow = table?.tBodies[0]?.rows[0];
113
- const viewport = table?.parentElement;
114
- if (firstRow === void 0 || viewport === null || viewport === void 0 || firstRow.offsetHeight === 0) {
115
- return FALLBACK_PAGE;
116
- }
117
- return Math.max(
118
- 1,
119
- Math.floor(viewport.clientHeight / firstRow.offsetHeight)
120
- );
121
- }
122
- function focusCell(rowIndex, columnIndex) {
123
- const next = clampFocus({ rowIndex, columnIndex }, rowCount, columnCount);
124
- if (next.rowIndex === focus.rowIndex && next.columnIndex === focus.columnIndex) {
125
- return;
126
- }
127
- pending.current = true;
128
- setStored(next);
129
- }
130
- function onKeyDown(event) {
131
- const next = nextFocusForKey(
132
- event.key,
133
- {
134
- ctrlKey: event.ctrlKey,
135
- metaKey: event.metaKey,
136
- altKey: event.altKey,
137
- shiftKey: event.shiftKey
138
- },
139
- focus,
140
- rowCount,
141
- columnCount,
142
- pageSize(),
143
- isSkippableRow
144
- );
145
- if (next === null) {
146
- return;
147
- }
148
- event.preventDefault();
149
- focusCell(next.rowIndex, next.columnIndex);
150
- }
86
+ gridFocus.focusDrawnCell(focus);
87
+ });
151
88
  return {
152
89
  focus,
153
90
  tabIndexFor: (rowIndex, columnIndex) => tabIndexFor(
154
91
  columnIndex,
155
92
  rowIndex === focus.rowIndex ? focus.columnIndex : null
156
93
  ),
157
- focusCell,
158
- 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
+ }
159
104
  };
160
105
  }
161
106
 
162
107
  // src/DataGrid/useGridSelection.ts
163
- import {
164
- useMemo as useMemo2,
165
- useRef as useRef2
166
- } from "react";
167
- import {
168
- applySelectionIntent,
169
- clearSelection,
170
- diffSelection,
171
- resolveCell,
172
- resolveColumns,
173
- resolveRows,
174
- selectAll,
175
- selectCell,
176
- toggleCellSelection
177
- } from "@gridkitjs/core";
178
-
179
- // src/DataGrid/commitIfChanged.ts
180
- function commitIfChanged(current, next, setState, onCommit) {
181
- if (next === current) {
182
- return false;
183
- }
184
- setState(next);
185
- onCommit?.(next);
186
- return true;
187
- }
188
-
189
- // src/DataGrid/useGridSelection.ts
108
+ import { useMemo } from "react";
190
109
  function keyboardSelectIntent(event) {
191
110
  return {
192
111
  ctrlKey: event.key === " " || event.ctrlKey,
@@ -194,186 +113,32 @@ function keyboardSelectIntent(event) {
194
113
  shiftKey: event.shiftKey
195
114
  };
196
115
  }
197
- function countMessage(count, noun) {
198
- if (count === 0) {
199
- return `No ${noun}s selected`;
200
- }
201
- return count === 1 ? `1 ${noun} selected` : `${String(count)} ${noun}s selected`;
202
- }
203
- function useAsSet(values) {
204
- return useMemo2(() => new Set(values), [values]);
205
- }
206
- function useIndexedBy(values, key) {
207
- return useMemo2(
208
- () => new Map(values.map((value) => [value[key], value])),
209
- [values, key]
210
- );
211
- }
212
- function useMappedArray(values, key) {
213
- return useMemo2(() => values.map((value) => value[key]), [values, key]);
214
- }
215
116
  function useGridSelection({
216
- rows,
217
- columns,
218
- selectable,
219
- rowSelection,
220
- setRowSelection,
221
- columnSelection,
222
- setColumnSelection,
223
- cellSelection,
224
- setCellSelection,
225
- callbacks,
226
- announce,
227
- columnName
117
+ controller,
118
+ state,
119
+ selectable
228
120
  }) {
229
- const rowMode = selectable?.rows ?? false;
230
- const columnMode = selectable?.columns ?? false;
231
- const cellMode = selectable?.cells ?? false;
232
- const rowsById = useIndexedBy(rows, "rowId");
233
- const rowIds = useMappedArray(rows, "rowId");
234
- const columnIds = useMappedArray(columns, "id");
235
- const selectedRowIds = useAsSet(rowSelection);
236
- const selectedColumnIds = useAsSet(columnSelection);
237
- const rowAnchor = useRef2(null);
238
- const columnAnchor = useRef2(null);
239
- function announceCount(changed, total, noun) {
240
- if (changed > 1) {
241
- announce(countMessage(total, noun));
242
- }
243
- }
244
- function commitRows(next) {
245
- commitIfChanged(rowSelection, next, setRowSelection, (committed) => {
246
- const { added, removed } = diffSelection(rowSelection, committed);
247
- const addedRows = resolveRows(rowsById, added);
248
- const removedRows = resolveRows(rowsById, removed);
249
- for (const row of addedRows) {
250
- callbacks.onRowSelect?.({ row, selection: committed });
251
- }
252
- if (addedRows.length > 0) {
253
- callbacks.onRowsSelect?.({ rows: addedRows, selection: committed });
254
- }
255
- for (const row of removedRows) {
256
- callbacks.onRowDeselect?.({ row, selection: committed });
257
- }
258
- if (removedRows.length > 0) {
259
- callbacks.onRowsDeselect?.({ rows: removedRows, selection: committed });
260
- }
261
- callbacks.onRowSelectionChange?.({
262
- added: addedRows,
263
- removed: removedRows,
264
- selected: resolveRows(rowsById, committed),
265
- selection: committed
266
- });
267
- announceCount(added.length + removed.length, committed.length, "row");
268
- });
269
- }
270
- function commitColumns(next) {
271
- commitIfChanged(columnSelection, next, setColumnSelection, (committed) => {
272
- const { added, removed } = diffSelection(columnSelection, committed);
273
- const addedColumns = resolveColumns(columns, added);
274
- const removedColumns = resolveColumns(columns, removed);
275
- for (const column of addedColumns) {
276
- callbacks.onColumnSelect?.({ column, selection: committed });
277
- }
278
- if (addedColumns.length > 0) {
279
- callbacks.onColumnsSelect?.({
280
- columns: addedColumns,
281
- selection: committed
282
- });
283
- }
284
- for (const column of removedColumns) {
285
- callbacks.onColumnDeselect?.({ column, selection: committed });
286
- }
287
- if (removedColumns.length > 0) {
288
- callbacks.onColumnsDeselect?.({
289
- columns: removedColumns,
290
- selection: committed
291
- });
292
- }
293
- callbacks.onColumnSelectionChange?.({
294
- added: addedColumns,
295
- removed: removedColumns,
296
- selected: resolveColumns(columns, committed),
297
- selection: committed
298
- });
299
- announceCount(added.length + removed.length, committed.length, "column");
300
- });
301
- }
302
- function commitCell(next) {
303
- commitIfChanged(cellSelection, next, setCellSelection, (committed) => {
304
- const selected = resolveCell(rowsById, columns, committed);
305
- const deselected = resolveCell(rowsById, columns, cellSelection);
306
- if (deselected !== null) {
307
- callbacks.onCellDeselect?.({ cell: deselected, selection: committed });
308
- }
309
- if (selected !== null) {
310
- callbacks.onCellSelect?.({ cell: selected, selection: committed });
311
- }
312
- callbacks.onCellSelectionChange?.({
313
- selected,
314
- deselected,
315
- selection: committed
316
- });
317
- if (selected !== null) {
318
- announce(
319
- `${columnName(selected.columnId)}, row ${String(selected.rowIndex + 1)}, selected`
320
- );
321
- }
322
- });
323
- }
324
- function selectRow(rowId, intent) {
325
- const next = applySelectionIntent(
326
- rowSelection,
327
- rowIds,
328
- rowAnchor.current,
329
- rowId,
330
- intent,
331
- rowMode
332
- );
333
- if (intent !== "range") {
334
- rowAnchor.current = rowId;
335
- }
336
- commitRows(next);
337
- }
338
- function selectColumn(columnId, intent) {
339
- const next = applySelectionIntent(
340
- columnSelection,
341
- columnIds,
342
- columnAnchor.current,
343
- columnId,
344
- intent,
345
- columnMode
346
- );
347
- if (intent !== "range") {
348
- columnAnchor.current = columnId;
349
- }
350
- commitColumns(next);
351
- }
352
- function selectCellAt(cell, intent) {
353
- commitCell(
354
- intent === "toggle" ? toggleCellSelection(cellSelection, cell, cellMode) : selectCell(cellSelection, cell, cellMode)
355
- );
356
- }
357
- function selectAllRows() {
358
- commitRows(selectAll(rowSelection, rowIds, rowMode));
359
- }
360
- function clear() {
361
- commitRows(clearSelection(rowSelection));
362
- commitColumns(clearSelection(columnSelection));
363
- commitCell(null);
364
- }
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
+ );
365
129
  return {
366
- rowMode,
367
- columnMode,
368
- cellMode,
130
+ rowMode: selectable?.rows ?? false,
131
+ columnMode: selectable?.columns ?? false,
132
+ cellMode: selectable?.cells ?? false,
133
+ toggleOnClick: selectable?.toggleOnClick ?? false,
369
134
  selectedRowIds,
370
135
  selectedColumnIds,
371
- selectedCell: cellSelection,
372
- selectRow,
373
- selectColumn,
374
- selectCell: selectCellAt,
375
- selectAllRows,
376
- 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
377
142
  };
378
143
  }
379
144
 
@@ -414,9 +179,10 @@ function GridHeader({
414
179
  columns,
415
180
  resize,
416
181
  drag,
417
- sort,
182
+ controller,
183
+ columnSort,
418
184
  sortableColumns,
419
- grouping,
185
+ groupBy,
420
186
  groupableColumns,
421
187
  groupToggleIconColumns,
422
188
  nav,
@@ -433,11 +199,11 @@ function GridHeader({
433
199
  const dropAfter = beforeId === null && index === columns.length - 1;
434
200
  const selected = selection.selectedColumnIds.has(entry.id);
435
201
  const sortable = column.sortable ?? sortableColumns;
436
- const sortDirection = sort.directionFor(entry.id);
437
- const sortPriority = sort.priorityFor(entry.id);
202
+ const sortDirection = sortDirectionFor(columnSort, entry.id);
203
+ const sortPriority = sortPriorityFor(columnSort, entry.id);
438
204
  const groupable = column.groupable ?? groupableColumns;
439
205
  const showGroupToggleIcon = column.groupToggleIcon ?? groupToggleIconColumns;
440
- const isGrouped = grouping.groupBy.some(
206
+ const isGrouped = groupBy.some(
441
207
  (level) => level.columnId === entry.id
442
208
  );
443
209
  const shortcuts = buildKeyShortcuts({
@@ -491,12 +257,14 @@ function GridHeader({
491
257
  }
492
258
  if (event.key === "ArrowUp" && event.altKey && sortable) {
493
259
  event.preventDefault();
494
- sort.toggle(entry, { shiftKey: event.shiftKey });
260
+ controller.toggleColumnSort(entry.id, {
261
+ stack: event.shiftKey
262
+ });
495
263
  return;
496
264
  }
497
265
  if (event.key === "ArrowDown" && event.altKey && groupable) {
498
266
  event.preventDefault();
499
- grouping.toggleGroupBy(entry.id);
267
+ controller.toggleGroupBy(entry.id);
500
268
  return;
501
269
  }
502
270
  if (event.key === " " || event.key === "Enter") {
@@ -559,7 +327,7 @@ function GridHeader({
559
327
  },
560
328
  onClick: (event) => {
561
329
  event.stopPropagation();
562
- grouping.toggleGroupBy(entry.id);
330
+ controller.toggleGroupBy(entry.id);
563
331
  },
564
332
  children: /* @__PURE__ */ jsx2(GroupIcon, {})
565
333
  }
@@ -584,7 +352,9 @@ function GridHeader({
584
352
  event.stopPropagation();
585
353
  },
586
354
  onClick: (event) => {
587
- sort.toggle(entry, { shiftKey: event.shiftKey });
355
+ controller.toggleColumnSort(entry.id, {
356
+ stack: event.shiftKey
357
+ });
588
358
  },
589
359
  children: [
590
360
  sortDirection === null ? /* @__PURE__ */ jsx2(ChevronsUpDownIcon, {}) : /* @__PURE__ */ jsx2(ChevronUpIcon, {}),
@@ -636,65 +406,82 @@ function GridHeader({
636
406
  }
637
407
 
638
408
  // src/DataGrid/components/GridBody.tsx
639
- import { useMemo as useMemo3 } from "react";
409
+ import {
410
+ useMemo as useMemo2,
411
+ useState as useState2
412
+ } from "react";
640
413
  import {
641
414
  groupRowId,
642
415
  intentOf as intentOf2
643
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
+ }
644
453
 
645
454
  // src/DataGrid/components/GridGroupRow.tsx
646
455
  import { memo } from "react";
456
+ import {
457
+ formatCellValue,
458
+ resolveColumnLabel
459
+ } from "@gridkitjs/core";
647
460
 
648
- // src/DataGrid/components/formatAggregateValue.ts
649
- 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
+ }
650
469
  if (value === null || value === void 0) {
651
470
  return "\u2014";
652
471
  }
653
- if (value instanceof Date) {
654
- return value.toLocaleString();
655
- }
656
- switch (typeof value) {
657
- case "string":
658
- case "boolean":
659
- case "bigint":
660
- case "symbol":
661
- return String(value);
662
- case "number":
663
- return value.toLocaleString();
664
- case "function":
665
- return "(function)";
666
- default:
667
- return JSON.stringify(value);
668
- }
472
+ return formatAggregateValue(value, spec, column, locale);
669
473
  }
670
474
 
671
475
  // src/DataGrid/components/GridGroupRow.tsx
672
476
  import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
673
- function formatGroupValue(value) {
674
- if (value === null || value === void 0) {
675
- return "(blank)";
676
- }
677
- if (value instanceof Date) {
678
- return value.toLocaleString();
679
- }
680
- switch (typeof value) {
681
- case "string":
682
- case "number":
683
- case "boolean":
684
- case "bigint":
685
- case "symbol":
686
- return String(value);
687
- case "function":
688
- return "(function)";
689
- default:
690
- return JSON.stringify(value);
691
- }
477
+ function formatGroupValue(value, column, locale, messages) {
478
+ return value === null || value === void 0 ? messages.blankGroupValue : formatCellValue(value, column, locale, void 0, messages);
692
479
  }
693
480
  function GridGroupRowComponent({
694
481
  columnCount,
695
482
  groupId,
696
483
  level,
697
- columnLabel,
484
+ groupColumn,
698
485
  value,
699
486
  expanded,
700
487
  count,
@@ -705,10 +492,11 @@ function GridGroupRowComponent({
705
492
  focused,
706
493
  aggregates,
707
494
  results,
708
- columns,
495
+ definitions,
496
+ locale,
497
+ cellMessages,
709
498
  ref
710
499
  }) {
711
- const byId = new Map(columns.map((entry) => [entry.id, entry]));
712
500
  return /* @__PURE__ */ jsx3(
713
501
  "tr",
714
502
  {
@@ -750,9 +538,10 @@ function GridGroupRowComponent({
750
538
  }
751
539
  ),
752
540
  /* @__PURE__ */ jsxs2("span", { className: "group-label", children: [
753
- columnLabel,
754
- ": ",
755
- formatGroupValue(value)
541
+ resolveColumnLabel(groupColumn),
542
+ ":",
543
+ " ",
544
+ formatGroupValue(value, groupColumn, locale, cellMessages)
756
545
  ] }),
757
546
  /* @__PURE__ */ jsxs2("span", { className: "group-count", children: [
758
547
  "(",
@@ -762,12 +551,15 @@ function GridGroupRowComponent({
762
551
  aggregates.length > 0 && /* @__PURE__ */ jsx3("span", { className: "group-aggregates", children: aggregates.map((spec) => {
763
552
  const key = spec.id ?? spec.columnId;
764
553
  const aggregateValue = results.get(key);
765
- const column = byId.get(spec.columnId);
766
- const rendered = column?.column.footerTemplate ? column.column.footerTemplate({
767
- value: aggregateValue,
768
- rows: []
769
- }) : formatAggregateValue(aggregateValue);
770
- 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));
771
563
  return /* @__PURE__ */ jsxs2("span", { className: "group-aggregate", children: [
772
564
  aggregateLabel,
773
565
  ": ",
@@ -798,6 +590,7 @@ function GridGroupSummaryRowComponent({
798
590
  results,
799
591
  aggregates,
800
592
  columns,
593
+ locale,
801
594
  ref
802
595
  }) {
803
596
  return /* @__PURE__ */ jsx4(
@@ -816,7 +609,6 @@ function GridGroupSummaryRowComponent({
816
609
  const specs = aggregates.filter(
817
610
  (candidate) => candidate.columnId === entry.id
818
611
  );
819
- const { column } = entry;
820
612
  return /* @__PURE__ */ jsxs3(
821
613
  "td",
822
614
  {
@@ -833,10 +625,15 @@ function GridGroupSummaryRowComponent({
833
625
  "aria-hidden": "true"
834
626
  }
835
627
  ),
836
- specs.map((spec) => {
837
- const value = results.get(spec.id ?? spec.columnId);
838
- return column.footerTemplate ? column.footerTemplate({ value, rows: [] }) : formatAggregateValue(value);
839
- }).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(
840
637
  (rendered, index) => index === 0 ? [rendered] : [", ", rendered]
841
638
  )
842
639
  ]
@@ -853,7 +650,10 @@ var GridGroupSummaryRow = memo2(
853
650
  var GridGroupSummaryRow_default = GridGroupSummaryRow;
854
651
 
855
652
  // src/DataGrid/components/GridRow.tsx
856
- import { accessDotted } from "@gridkitjs/core";
653
+ import {
654
+ accessDotted,
655
+ formatCellValue as formatCellValue2
656
+ } from "@gridkitjs/core";
857
657
  import { memo as memo3 } from "react";
858
658
  import { jsx as jsx5 } from "react/jsx-runtime";
859
659
  function GridRowComponent({
@@ -869,17 +669,27 @@ function GridRowComponent({
869
669
  focusedColumnIndex,
870
670
  rowsSelectable,
871
671
  cellsSelectable,
672
+ locale,
673
+ cellMessages,
674
+ rowClassName,
675
+ rowAttributes,
872
676
  ref
873
677
  }) {
874
678
  return /* @__PURE__ */ jsx5(
875
679
  "tr",
876
680
  {
681
+ ...rowAttributes,
877
682
  ref,
878
683
  role: "row",
879
684
  "aria-rowindex": datasetIndex + 2,
880
685
  "data-gridkit-row-index": rowIndex,
686
+ "data-gridkit-row-id": rowId,
881
687
  ...ariaAttr(rowsSelectable, "aria-selected", selected),
882
- className: classNames("grid-row", selected ? "is-selected" : ""),
688
+ className: classNames(
689
+ "grid-row",
690
+ selected ? "is-selected" : "",
691
+ rowClassName
692
+ ),
883
693
  children: columns.map(({ id, column, alignment }, columnIndex) => {
884
694
  const value = accessDotted(row, column.field);
885
695
  const cellSelected = id === selectedColumnId;
@@ -907,7 +717,7 @@ function GridRowComponent({
907
717
  datasetIndex,
908
718
  rowId,
909
719
  selected
910
- }) : value
720
+ }) : formatCellValue2(value, column, locale, row, cellMessages)
911
721
  },
912
722
  id
913
723
  );
@@ -920,39 +730,6 @@ var GridRow_default = GridRow;
920
730
 
921
731
  // src/DataGrid/components/GridBody.tsx
922
732
  import { Fragment, jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
923
- function rowIndexOf(row) {
924
- const attribute = row.getAttribute("data-gridkit-row-index");
925
- return attribute === null ? null : Number(attribute);
926
- }
927
- function cellFrom(target) {
928
- if (!(target instanceof Element)) {
929
- return null;
930
- }
931
- const cell = target.closest("td[data-gridkit-column]");
932
- const row = cell?.parentElement;
933
- const columnId = cell?.getAttribute("data-gridkit-column");
934
- const rowIndex = row === null || row === void 0 ? null : rowIndexOf(row);
935
- if (!(cell instanceof HTMLTableCellElement) || !(row instanceof HTMLTableRowElement) || columnId === null || columnId === void 0 || rowIndex === null) {
936
- return null;
937
- }
938
- return {
939
- rowIndex,
940
- columnIndex: cell.cellIndex,
941
- columnId
942
- };
943
- }
944
- function groupRowFrom(target) {
945
- if (!(target instanceof Element)) {
946
- return null;
947
- }
948
- const row = target.closest("tr[data-gridkit-group]");
949
- const groupId = row?.getAttribute("data-gridkit-group");
950
- const rowIndex = row === null ? null : rowIndexOf(row);
951
- if (!(row instanceof HTMLTableRowElement) || groupId === null || groupId === void 0 || rowIndex === null) {
952
- return null;
953
- }
954
- return { rowIndex, groupId };
955
- }
956
733
  function groupAriaMeta(rows) {
957
734
  const totals = /* @__PURE__ */ new Map();
958
735
  for (const entry of rows) {
@@ -976,22 +753,40 @@ function groupAriaMeta(rows) {
976
753
  }
977
754
  function GridBody({
978
755
  columns,
756
+ definitions,
979
757
  rows,
980
758
  activeColumnId,
981
759
  nav,
982
760
  selection,
983
- grouping,
761
+ controller,
984
762
  aggregates,
985
763
  groupAggregateDisplay,
986
764
  virtualRange,
987
765
  infiniteScroll,
988
- sentinelRef
766
+ sentinelRef,
767
+ locale,
768
+ getRowProps,
769
+ onRowClick,
770
+ onRowDoubleClick,
771
+ messages
989
772
  }) {
990
- const { selectedCell, rowMode, cellMode } = selection;
991
- const ariaMeta = useMemo3(() => groupAriaMeta(rows), [rows]);
773
+ const { selectedCell, rowMode, cellMode, toggleOnClick } = selection;
774
+ const ariaMeta = useMemo2(() => groupAriaMeta(rows), [rows]);
992
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
+ );
993
788
  function targetOf(target) {
994
- const position = cellFrom(target);
789
+ const position = bodyCellFrom(target);
995
790
  const entry = position === null ? void 0 : rows[position.rowIndex];
996
791
  if (position === null || entry === void 0 || "kind" in entry) {
997
792
  return null;
@@ -999,12 +794,36 @@ function GridBody({
999
794
  return { position, rowId: entry.rowId };
1000
795
  }
1001
796
  function select(rowId, columnId, modifiers) {
1002
- const intent = intentOf2(modifiers);
1003
797
  if (rowMode !== false) {
1004
- selection.selectRow(rowId, intent);
798
+ selection.selectRow(rowId, intentOf2(modifiers, { toggleOnClick }));
1005
799
  }
1006
800
  if (cellMode !== false) {
1007
- selection.selectCell({ rowId, columnId }, intent);
801
+ selection.selectCell({ rowId, columnId }, intentOf2(modifiers));
802
+ }
803
+ }
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);
1008
827
  }
1009
828
  }
1010
829
  return /* @__PURE__ */ jsxs4(
@@ -1024,12 +843,17 @@ function GridBody({
1024
843
  onClick: (event) => {
1025
844
  const group = groupRowFrom(event.target);
1026
845
  if (group !== null) {
1027
- grouping.toggleExpansion(group.groupId);
846
+ controller.toggleGroupExpansion(group.groupId);
1028
847
  return;
1029
848
  }
1030
849
  const found = targetOf(event.target);
1031
- if (found === null) return;
1032
- 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);
1033
857
  },
1034
858
  onKeyDown: (event) => {
1035
859
  const group = groupRowFrom(event.target);
@@ -1040,7 +864,7 @@ function GridBody({
1040
864
  }
1041
865
  if (event.key === " " || event.key === "Enter") {
1042
866
  event.preventDefault();
1043
- grouping.toggleExpansion(group.groupId);
867
+ controller.toggleGroupExpansion(group.groupId);
1044
868
  return;
1045
869
  }
1046
870
  nav.onKeyDown(event);
@@ -1061,7 +885,7 @@ function GridBody({
1061
885
  },
1062
886
  children: [
1063
887
  virtualRange !== null && virtualRange.offsetBefore > 0 && // `aria-hidden` and carrying neither `data-gridkit-column` nor
1064
- // `data-gridkit-group`/`data-gridkit-row-index`, so `cellFrom`/
888
+ // `data-gridkit-group`/`data-gridkit-row-index`, so `bodyCellFrom`/
1065
889
  // `groupRowFrom` never mistake it for a real row — it exists purely
1066
890
  // to give the browser's scrollbar the correct total height and thumb
1067
891
  // position, the same way `GridFooter`'s `<tfoot>` isn't a "row" to
@@ -1078,6 +902,7 @@ function GridBody({
1078
902
  ) }),
1079
903
  visibleRows.map((entry) => {
1080
904
  if (!("kind" in entry)) {
905
+ const rowProps = rowPropsOf(entry.rowId);
1081
906
  return /* @__PURE__ */ jsx6(
1082
907
  GridRow_default,
1083
908
  {
@@ -1093,7 +918,11 @@ function GridBody({
1093
918
  selectedColumnId: selectedCell?.rowId === entry.rowId ? selectedCell.columnId : null,
1094
919
  focusedColumnIndex: nav.focus.rowIndex === entry.rowIndex ? nav.focus.columnIndex : null,
1095
920
  rowsSelectable: rowMode !== false,
1096
- cellsSelectable: cellMode !== false
921
+ cellsSelectable: cellMode !== false,
922
+ locale,
923
+ cellMessages,
924
+ rowClassName: rowProps.className,
925
+ rowAttributes: rowProps.attributes
1097
926
  },
1098
927
  entry.rowId
1099
928
  );
@@ -1109,7 +938,8 @@ function GridBody({
1109
938
  datasetIndex: entry.datasetIndex,
1110
939
  results: entry.aggregates,
1111
940
  aggregates,
1112
- columns
941
+ columns,
942
+ locale
1113
943
  },
1114
944
  `${entry.groupId}-summary`
1115
945
  );
@@ -1121,7 +951,7 @@ function GridBody({
1121
951
  columnCount: columns.length,
1122
952
  groupId: entry.groupId,
1123
953
  level: entry.level,
1124
- columnLabel: columns.find((column) => column.id === entry.columnId)?.label ?? entry.columnId,
954
+ groupColumn: definitions.get(entry.columnId) ?? { field: entry.columnId },
1125
955
  value: entry.value,
1126
956
  expanded: entry.expanded,
1127
957
  count: entry.count,
@@ -1131,7 +961,9 @@ function GridBody({
1131
961
  setsize: ariaMeta.get(entry.groupId)?.setsize ?? 1,
1132
962
  aggregates: groupAggregateDisplay === "inline" ? aggregates : [],
1133
963
  results: entry.aggregates,
1134
- columns,
964
+ definitions,
965
+ locale,
966
+ cellMessages,
1135
967
  focused: nav.focus.rowIndex === entry.rowIndex
1136
968
  },
1137
969
  entry.groupId
@@ -1148,7 +980,7 @@ function GridBody({
1148
980
  }
1149
981
  ) }),
1150
982
  infiniteScroll?.hasMore && /* @__PURE__ */ jsxs4(Fragment, { children: [
1151
- infiniteScroll.isLoadingMore === true && /* @__PURE__ */ jsx6("tr", { "aria-hidden": "true", className: "grid-loading-row", children: /* @__PURE__ */ jsx6("td", { colSpan: columns.length, className: "grid-loading-cell", children: infiniteScroll.loadingTemplate ? infiniteScroll.loadingTemplate() : "Loading more\u2026" }) }),
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 }) }),
1152
984
  /* @__PURE__ */ jsx6(
1153
985
  "tr",
1154
986
  {
@@ -1176,13 +1008,13 @@ function GridFooter({
1176
1008
  columns,
1177
1009
  aggregates,
1178
1010
  results,
1179
- rows
1011
+ rows,
1012
+ locale
1180
1013
  }) {
1181
1014
  return /* @__PURE__ */ jsx7("tfoot", { children: /* @__PURE__ */ jsx7("tr", { className: "grid-footer", role: "row", children: columns.map((entry) => {
1182
1015
  const specs = aggregates.filter(
1183
1016
  (candidate) => candidate.columnId === entry.id
1184
1017
  );
1185
- const { column } = entry;
1186
1018
  return /* @__PURE__ */ jsx7(
1187
1019
  "td",
1188
1020
  {
@@ -1190,10 +1022,15 @@ function GridFooter({
1190
1022
  "data-gridkit-column": entry.id,
1191
1023
  className: "grid-footer-cell",
1192
1024
  style: { textAlign: specs[0]?.alignment ?? entry.alignment },
1193
- children: specs.map((spec) => {
1194
- const value = results.get(spec.id ?? spec.columnId);
1195
- return column.footerTemplate ? column.footerTemplate({ value, rows }) : formatAggregateValue(value);
1196
- }).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(
1197
1034
  (rendered, index) => index === 0 ? [rendered] : [", ", rendered]
1198
1035
  )
1199
1036
  },
@@ -1205,222 +1042,139 @@ function GridFooter({
1205
1042
  // src/DataGrid/components/GridPager.tsx
1206
1043
  import { paginationWindow } from "@gridkitjs/core";
1207
1044
  import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
1208
- function GridPager({ pager, config }) {
1045
+ function GridPager({ pager, config, messages }) {
1209
1046
  const { pagination, pageCount, currentPage } = pager;
1210
1047
  const variant = config?.variant ?? "compact";
1211
1048
  const sizeOptions = config?.sizeOptions;
1212
- return /* @__PURE__ */ jsxs5("div", { className: "gridkit-grid-pager", role: "group", "aria-label": "Pagination", children: [
1213
- /* @__PURE__ */ jsx8(
1214
- "button",
1215
- {
1216
- type: "button",
1217
- className: "grid-pager-button",
1218
- disabled: pagination.pageIndex <= 0,
1219
- onClick: () => {
1220
- pager.previousPage();
1221
- },
1222
- children: "Previous"
1223
- }
1224
- ),
1225
- variant === "numbered" ? paginationWindow(currentPage, pageCount, {
1226
- boundaryCount: config?.boundaryCount,
1227
- siblingCount: config?.siblingCount
1228
- }).map(
1229
- (entry, index) => entry === "ellipsis" ? /* @__PURE__ */ jsx8(
1230
- "span",
1231
- {
1232
- className: "grid-pager-ellipsis",
1233
- "aria-hidden": "true",
1234
- children: "\u2026"
1235
- },
1236
- `ellipsis-${String(index)}`
1237
- ) : /* @__PURE__ */ jsx8(
1238
- "button",
1239
- {
1240
- type: "button",
1241
- className: "grid-pager-button",
1242
- "aria-label": `Page ${String(entry)}`,
1243
- "aria-current": entry === currentPage ? "page" : void 0,
1244
- onClick: () => {
1245
- pager.goToPage(entry - 1);
1246
- },
1247
- children: entry
1248
- },
1249
- entry
1250
- )
1251
- ) : /* @__PURE__ */ jsxs5("span", { className: "grid-pager-status", children: [
1252
- "Page ",
1253
- pageCount === 0 ? 0 : currentPage,
1254
- " of ",
1255
- pageCount
1256
- ] }),
1257
- /* @__PURE__ */ jsx8(
1258
- "button",
1259
- {
1260
- type: "button",
1261
- className: "grid-pager-button",
1262
- disabled: pagination.pageIndex >= pageCount - 1,
1263
- onClick: () => {
1264
- pager.nextPage();
1265
- },
1266
- children: "Next"
1267
- }
1268
- ),
1269
- sizeOptions !== void 0 && sizeOptions.length > 0 && /* @__PURE__ */ jsxs5("label", { className: "grid-pager-page-size", children: [
1270
- "Rows per page",
1271
- /* @__PURE__ */ jsx8(
1272
- "select",
1273
- {
1274
- value: pagination.pageSize,
1275
- onChange: (event) => {
1276
- pager.setPageSize(Number(event.target.value));
1277
- },
1278
- children: sizeOptions.map((size) => /* @__PURE__ */ jsx8("option", { value: size, children: size }, size))
1279
- }
1280
- )
1281
- ] })
1282
- ] });
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
+ );
1283
1123
  }
1284
1124
 
1285
1125
  // src/DataGrid/components/GroupByBar.tsx
1286
1126
  import { createPortal as createPortal2 } from "react-dom";
1287
- import { resolveKeyboardDropTarget } from "@gridkitjs/core";
1288
-
1289
- // src/DataGrid/groupByDropTarget.ts
1290
1127
  import {
1291
- movesGroupBy,
1292
- resolveDropBefore
1128
+ resolveColumnLabel as resolveColumnLabel2,
1129
+ resolveKeyboardDropTarget
1293
1130
  } from "@gridkitjs/core";
1294
1131
 
1295
- // src/DataGrid/useDragReorder.ts
1132
+ // src/DataGrid/useGroupByDrag.ts
1296
1133
  import {
1297
- useRef as useRef3,
1298
- useState as useState2
1299
- } from "react";
1300
-
1301
- // src/DataGrid/pointerGesture.ts
1302
- function startPointerGesture(element, pointerId, handlers) {
1303
- element.setPointerCapture(pointerId);
1304
- function stop() {
1305
- element.removeEventListener("pointermove", move);
1306
- element.removeEventListener("pointerup", end);
1307
- element.removeEventListener("pointercancel", cancel);
1308
- window.removeEventListener("keydown", onKeyDown);
1309
- if (element.hasPointerCapture(pointerId)) {
1310
- element.releasePointerCapture(pointerId);
1311
- }
1312
- }
1313
- function move(event) {
1314
- handlers.onMove(event);
1315
- }
1316
- function end() {
1317
- stop();
1318
- handlers.onEnd();
1319
- }
1320
- function cancel() {
1321
- stop();
1322
- handlers.onCancel();
1323
- }
1324
- function onKeyDown(event) {
1325
- if (event.key === "Escape") {
1326
- cancel();
1327
- }
1328
- }
1329
- element.addEventListener("pointermove", move);
1330
- element.addEventListener("pointerup", end);
1331
- element.addEventListener("pointercancel", cancel);
1332
- window.addEventListener("keydown", onKeyDown);
1333
- }
1134
+ resolveGroupByDropTarget
1135
+ } from "@gridkitjs/dom";
1334
1136
 
1335
1137
  // src/DataGrid/useDragReorder.ts
1336
- var DRAG_THRESHOLD = 4;
1337
- function elementAt(x, y, selector) {
1338
- return document.elementFromPoint(x, y)?.closest(selector) ?? null;
1339
- }
1340
- function sideOf(element, x) {
1341
- const rect = element.getBoundingClientRect();
1342
- return x < rect.left + rect.width / 2 ? "before" : "after";
1343
- }
1138
+ import {
1139
+ useRef,
1140
+ useState as useState3
1141
+ } from "react";
1142
+ import {
1143
+ NO_DRAG,
1144
+ startDragReorder
1145
+ } from "@gridkitjs/dom";
1344
1146
  function useDragReorder({
1345
1147
  resolveTarget,
1346
1148
  onDrop
1347
1149
  }) {
1348
- const [draggedId, setDraggedId] = useState2(null);
1349
- const [dropTarget, setDropTarget] = useState2(null);
1350
- const [ghostTransform, setGhostTransform] = useState2(null);
1351
- const dragged = useRef3(false);
1150
+ const [drag, setDrag] = useState3(NO_DRAG);
1151
+ const dragged = useRef(false);
1352
1152
  function justDragged() {
1353
1153
  const answer = dragged.current;
1354
1154
  dragged.current = false;
1355
1155
  return answer;
1356
1156
  }
1357
1157
  function startDrag(id, grabElement, event) {
1358
- const { pointerId, clientX: startX } = event;
1359
- const grabX = startX - grabElement.getBoundingClientRect().left;
1360
- let dragging = false;
1361
- let target = null;
1362
- function placeGhost(x, y) {
1363
- setGhostTransform(`translate(${String(x - grabX)}px, ${String(y)}px)`);
1364
- }
1365
- function reset() {
1366
- setGhostTransform(null);
1367
- setDraggedId(null);
1368
- setDropTarget(null);
1369
- }
1370
- startPointerGesture(grabElement, pointerId, {
1371
- onMove(moveEvent) {
1372
- const { clientX, clientY } = moveEvent;
1373
- if (!dragging) {
1374
- if (Math.abs(clientX - startX) < DRAG_THRESHOLD) {
1375
- return;
1376
- }
1377
- dragging = true;
1158
+ startDragReorder(id, grabElement, event, {
1159
+ resolveTarget,
1160
+ onState(state) {
1161
+ if (state.draggedId !== null) {
1378
1162
  dragged.current = true;
1379
- setDraggedId(id);
1380
- }
1381
- placeGhost(clientX, clientY);
1382
- target = resolveTarget(clientX, clientY, id);
1383
- setDropTarget(target);
1384
- },
1385
- onEnd() {
1386
- const dropped = target;
1387
- reset();
1388
- if (dragging && dropped !== null) {
1389
- onDrop(dropped, id);
1390
1163
  }
1164
+ setDrag(state);
1391
1165
  },
1392
- // Escape and a cancelled pointer both leave the order as it was.
1393
- onCancel() {
1394
- target = null;
1395
- reset();
1396
- }
1166
+ onDrop
1397
1167
  });
1398
1168
  }
1399
1169
  return {
1400
- draggedId,
1401
- dropTarget,
1402
- ghostTransform,
1170
+ draggedId: drag.draggedId,
1171
+ dropTarget: drag.dropTarget,
1172
+ ghostTransform: drag.ghostTransform,
1403
1173
  startDrag,
1404
1174
  justDragged
1405
1175
  };
1406
1176
  }
1407
1177
 
1408
- // src/DataGrid/groupByDropTarget.ts
1409
- function resolveGroupByDropTarget(clientX, clientY, movedId, groupBy) {
1410
- const overBar = elementAt(clientX, clientY, "[data-gridkit-group-by-bar]");
1411
- if (overBar === null) {
1412
- return null;
1413
- }
1414
- const overChip = elementAt(clientX, clientY, "[data-gridkit-group-chip]");
1415
- const chipId = overChip?.getAttribute("data-gridkit-group-chip");
1416
- const beforeColumnId = overChip && chipId ? resolveDropBefore(
1417
- groupBy.map((entry) => entry.columnId),
1418
- chipId,
1419
- sideOf(overChip, clientX)
1420
- ) : null;
1421
- return movesGroupBy(groupBy, movedId, beforeColumnId) ? { kind: "group-by", beforeColumnId } : null;
1422
- }
1423
-
1424
1178
  // src/DataGrid/useGroupByDrag.ts
1425
1179
  function useGroupByDrag({
1426
1180
  groupBy,
@@ -1447,25 +1201,28 @@ function useGroupByDrag({
1447
1201
  import { jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
1448
1202
  function GroupByBar({
1449
1203
  columns,
1450
- grouping,
1204
+ definitions,
1205
+ groupBy,
1206
+ controller,
1451
1207
  columnName,
1452
1208
  headerDragEligible,
1453
1209
  headerDragBlocked,
1454
- headerDropTarget
1210
+ headerDropTarget,
1211
+ messages
1455
1212
  }) {
1456
- const { groupBy } = grouping;
1457
1213
  const groupByIds = groupBy.map((entry) => entry.columnId);
1458
1214
  const chipDrag = useGroupByDrag({
1459
1215
  groupBy,
1460
1216
  onDrop: (movedId, beforeColumnId) => {
1461
- grouping.moveGroupBy(movedId, beforeColumnId);
1217
+ controller.moveGroupBy(movedId, beforeColumnId);
1462
1218
  }
1463
1219
  });
1464
1220
  const activeDropTarget = chipDrag.dropTarget ?? headerDropTarget;
1465
1221
  const isDragTarget = chipDrag.draggedColumnId !== null || headerDragEligible;
1466
1222
  const showPlaceholder = groupBy.length === 0 && columns.some((entry) => entry.groupByDraggable);
1467
1223
  function labelFor(columnId) {
1468
- return columns.find((entry) => entry.id === columnId)?.label ?? columnId;
1224
+ const definition = definitions.get(columnId);
1225
+ return definition === void 0 ? columnId : resolveColumnLabel2(definition);
1469
1226
  }
1470
1227
  function moveByKeyboard(columnId, direction) {
1471
1228
  const beforeColumnId = resolveKeyboardDropTarget(
@@ -1476,7 +1233,7 @@ function GroupByBar({
1476
1233
  if (beforeColumnId === void 0) {
1477
1234
  return;
1478
1235
  }
1479
- grouping.moveGroupBy(columnId, beforeColumnId);
1236
+ controller.moveGroupBy(columnId, beforeColumnId);
1480
1237
  }
1481
1238
  return /* @__PURE__ */ jsxs6(
1482
1239
  "div",
@@ -1488,9 +1245,9 @@ function GroupByBar({
1488
1245
  ),
1489
1246
  "data-gridkit-group-by-bar": "",
1490
1247
  role: "group",
1491
- "aria-label": "Grouped by",
1248
+ "aria-label": messages.groupByBarLabel,
1492
1249
  children: [
1493
- showPlaceholder && /* @__PURE__ */ jsx9("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 }),
1494
1251
  groupBy.map((entry, index) => {
1495
1252
  const dropBefore = activeDropTarget?.beforeColumnId === entry.columnId;
1496
1253
  const dropAfter = activeDropTarget?.beforeColumnId === null && index === groupBy.length - 1;
@@ -1506,7 +1263,11 @@ function GroupByBar({
1506
1263
  ),
1507
1264
  "data-gridkit-group-chip": entry.columnId,
1508
1265
  tabIndex: 0,
1509
- "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
+ ),
1510
1271
  "aria-keyshortcuts": "Control+ArrowLeft Control+ArrowRight",
1511
1272
  onPointerDown: (event) => {
1512
1273
  chipDrag.startDrag(entry.columnId, event);
@@ -1528,12 +1289,12 @@ function GroupByBar({
1528
1289
  {
1529
1290
  type: "button",
1530
1291
  className: "group-by-chip-remove",
1531
- "aria-label": `Stop grouping by ${columnName(entry.columnId)}`,
1292
+ "aria-label": messages.removeGroupBy(columnName(entry.columnId)),
1532
1293
  onPointerDown: (event) => {
1533
1294
  event.stopPropagation();
1534
1295
  },
1535
1296
  onClick: () => {
1536
- grouping.toggleGroupBy(entry.columnId);
1297
+ controller.toggleGroupBy(entry.columnId);
1537
1298
  },
1538
1299
  children: "\xD7"
1539
1300
  }
@@ -1562,10 +1323,9 @@ function GroupByBar({
1562
1323
 
1563
1324
  // src/DataGrid/useColumnDrag.ts
1564
1325
  import {
1565
- movesColumn,
1566
- resolveDropBefore as resolveDropBefore2,
1567
1326
  resolveKeyboardDropTarget as resolveKeyboardDropTarget2
1568
1327
  } from "@gridkitjs/core";
1328
+ import { resolveColumnDropTarget } from "@gridkitjs/dom";
1569
1329
  function useColumnDrag({
1570
1330
  order,
1571
1331
  groupBy,
@@ -1573,18 +1333,11 @@ function useColumnDrag({
1573
1333
  onDrop
1574
1334
  }) {
1575
1335
  const drag = useDragReorder({
1576
- resolveTarget(clientX, clientY, movedId) {
1577
- const over = elementAt(clientX, clientY, "th[data-gridkit-column]");
1578
- const overId = over?.getAttribute("data-gridkit-column");
1579
- const beforeId = over && overId ? resolveDropBefore2(order, overId, sideOf(over, clientX)) : void 0;
1580
- if (beforeId !== void 0) {
1581
- return movesColumn(order, movedId, beforeId) ? { kind: "column-order", beforeId } : null;
1582
- }
1583
- const alreadyGrouped = groupBy.some(
1584
- (entry) => entry.columnId === movedId
1585
- );
1586
- return groupByDraggableIds.has(movedId) && !alreadyGrouped ? resolveGroupByDropTarget(clientX, clientY, movedId, groupBy) : null;
1587
- },
1336
+ resolveTarget: (clientX, clientY, movedId) => resolveColumnDropTarget(clientX, clientY, movedId, {
1337
+ order,
1338
+ groupBy,
1339
+ groupByDraggableIds
1340
+ }),
1588
1341
  onDrop
1589
1342
  });
1590
1343
  function startDrag(entry, event) {
@@ -1609,95 +1362,64 @@ function useColumnDrag({
1609
1362
 
1610
1363
  // src/DataGrid/useColumnResize.ts
1611
1364
  import {
1612
- useState as useState3
1365
+ useState as useState4
1613
1366
  } from "react";
1614
1367
  import {
1615
- applyColumnResize,
1616
1368
  beginColumnResize,
1617
1369
  clampColumnWidth,
1618
1370
  resolveColumnConstraints,
1619
- revertColumnSize,
1620
1371
  sizeColumnToContent
1621
1372
  } from "@gridkitjs/core";
1622
-
1623
- // src/DataGrid/measureColumnContent.ts
1624
- function measureColumnContent(table, columnId) {
1625
- const cells = table.querySelectorAll(
1626
- `td[data-gridkit-column="${CSS.escape(columnId)}"]`
1627
- );
1628
- const range = document.createRange();
1629
- let widest = null;
1630
- for (const cell of cells) {
1631
- range.selectNodeContents(cell);
1632
- const style = getComputedStyle(cell);
1633
- const width = range.getBoundingClientRect().width + parseFloat(style.paddingLeft) + parseFloat(style.paddingRight) + parseFloat(style.borderLeftWidth) + parseFloat(style.borderRightWidth);
1634
- if (widest === null || width > widest) {
1635
- widest = width;
1636
- }
1637
- }
1638
- return widest;
1639
- }
1640
-
1641
- // src/DataGrid/useColumnResize.ts
1373
+ import { measureColumnContent, startColumnResizeGesture } from "@gridkitjs/dom";
1642
1374
  function useColumnResize({
1643
1375
  tableRef,
1644
- sizing,
1645
- setSizing,
1646
- columnSizeDefaults,
1647
- onColumnResize
1376
+ controller,
1377
+ columnSizeDefaults
1648
1378
  }) {
1649
- const [activeColumnId, setActiveColumnId] = useState3(null);
1650
- function commit(base, columnId, width, phase) {
1651
- const next = { ...base, [columnId]: width };
1652
- setSizing(next);
1653
- onColumnResize?.({ columnId, width, sizing: next, phase });
1654
- }
1379
+ const [activeColumnId, setActiveColumnId] = useState4(null);
1655
1380
  function startResize(entry, event) {
1656
- const handle = event.currentTarget;
1657
- const { pointerId } = event;
1381
+ event.stopPropagation();
1658
1382
  const session = beginColumnResize(
1659
1383
  entry.column,
1660
1384
  entry.width,
1661
1385
  event.clientX,
1662
1386
  columnSizeDefaults
1663
1387
  );
1664
- const base = sizing;
1665
- let width = session.startWidth;
1666
- event.preventDefault();
1667
- event.stopPropagation();
1668
- setActiveColumnId(session.columnId);
1669
- startPointerGesture(handle, pointerId, {
1670
- onMove(moveEvent) {
1671
- width = applyColumnResize(session, moveEvent.clientX);
1672
- commit(base, session.columnId, width, "move");
1673
- },
1674
- onEnd() {
1675
- setActiveColumnId(null);
1676
- if (width !== session.startWidth) {
1677
- 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
+ );
1678
1417
  }
1679
- },
1680
- /**
1681
- * Escape puts back only the dragged column's pre-drag width, merged
1682
- * into whatever the sizing state is *now* — not the `base` snapshot
1683
- * taken at drag-start — so a different column resized via keyboard
1684
- * while this drag was still open keeps its change instead of being
1685
- * clobbered by a stale restore.
1686
- */
1687
- onCancel() {
1688
- setActiveColumnId(null);
1689
- setSizing((current) => {
1690
- const next = revertColumnSize(current, base, session.columnId);
1691
- onColumnResize?.({
1692
- columnId: session.columnId,
1693
- width: session.startWidth,
1694
- sizing: next,
1695
- phase: "end"
1696
- });
1697
- return next;
1698
- });
1699
1418
  }
1700
- });
1419
+ );
1420
+ if (started) {
1421
+ setActiveColumnId(session.columnId);
1422
+ }
1701
1423
  }
1702
1424
  function sizeToContent(entry) {
1703
1425
  const table = tableRef.current;
@@ -1708,8 +1430,7 @@ function useColumnResize({
1708
1430
  if (measured === null) {
1709
1431
  return;
1710
1432
  }
1711
- commit(
1712
- sizing,
1433
+ controller.resizeColumn(
1713
1434
  entry.id,
1714
1435
  sizeColumnToContent(
1715
1436
  measured,
@@ -1719,8 +1440,7 @@ function useColumnResize({
1719
1440
  );
1720
1441
  }
1721
1442
  function nudge(entry, delta) {
1722
- commit(
1723
- sizing,
1443
+ controller.resizeColumn(
1724
1444
  entry.id,
1725
1445
  clampColumnWidth(
1726
1446
  entry.width + delta,
@@ -1732,235 +1452,104 @@ function useColumnResize({
1732
1452
  return { activeColumnId, startResize, sizeToContent, nudge };
1733
1453
  }
1734
1454
 
1735
- // src/DataGrid/useColumnSort.ts
1736
- import {
1737
- sortDirectionFor,
1738
- sortPriorityFor,
1739
- toggleColumnSort
1740
- } from "@gridkitjs/core";
1741
- function useColumnSort({
1742
- sort,
1743
- setSort,
1744
- onColumnSortChange
1745
- }) {
1746
- function toggle(entry, event) {
1747
- const next = toggleColumnSort(sort, entry.id, { stack: event.shiftKey });
1748
- commitIfChanged(sort, next, setSort, (committed) => {
1749
- onColumnSortChange?.({ columnId: entry.id, sort: committed });
1750
- });
1751
- }
1752
- return {
1753
- sort,
1754
- directionFor: (columnId) => sortDirectionFor(sort, columnId),
1755
- priorityFor: (columnId) => sortPriorityFor(sort, columnId),
1756
- toggle
1757
- };
1758
- }
1759
-
1760
1455
  // src/DataGrid/useElementWidth.ts
1761
- import { useLayoutEffect, useState as useState4 } from "react";
1456
+ import { useLayoutEffect as useLayoutEffect2, useState as useState5 } from "react";
1457
+ import { observeElementWidth } from "@gridkitjs/dom";
1762
1458
  function useElementWidth(ref, enabled) {
1763
- const [width, setWidth] = useState4(null);
1764
- useLayoutEffect(() => {
1459
+ const [width, setWidth] = useState5(null);
1460
+ useLayoutEffect2(() => {
1765
1461
  const element = ref.current;
1766
1462
  if (element === null || !enabled) {
1767
1463
  return void 0;
1768
1464
  }
1769
- setWidth(element.clientWidth);
1770
- const observer = new ResizeObserver(() => {
1771
- setWidth(element.clientWidth);
1772
- });
1773
- observer.observe(element);
1774
- return () => {
1775
- observer.disconnect();
1776
- };
1465
+ return observeElementWidth(element, setWidth);
1777
1466
  }, [ref, enabled]);
1778
1467
  return width;
1779
1468
  }
1780
1469
 
1470
+ // src/DataGrid/useGridController.ts
1471
+ import { useState as useState6, useSyncExternalStore as useSyncExternalStore2 } from "react";
1472
+ import {
1473
+ createGridController
1474
+ } from "@gridkitjs/core";
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
+
1781
1484
  // src/DataGrid/useInfiniteScroll.ts
1782
- import { useCallback, useEffect as useEffect2, useRef as useRef4 } from "react";
1485
+ import { useEffect as useEffect2, useRef as useRef2 } from "react";
1486
+ import { attachInfiniteScroll } from "@gridkitjs/dom";
1783
1487
  function useInfiniteScroll({
1784
1488
  rootRef,
1785
1489
  sentinelRef,
1786
1490
  config
1787
1491
  }) {
1788
- const hasMore = config?.hasMore ?? false;
1789
- const isLoadingMore = config?.isLoadingMore ?? false;
1790
- const rootMargin = config?.rootMargin ?? "200px";
1791
- const latestRef = useRef4({
1792
- hasMore,
1793
- isLoadingMore,
1794
- onLoadMore: config?.onLoadMore
1795
- });
1796
- useEffect2(() => {
1797
- latestRef.current = {
1798
- hasMore,
1799
- isLoadingMore,
1800
- onLoadMore: config?.onLoadMore
1801
- };
1802
- });
1803
- const firedRef = useRef4(false);
1804
- const intersectingRef = useRef4(false);
1805
- const maybeLoadMore = useCallback(() => {
1806
- const current = latestRef.current;
1807
- if (!intersectingRef.current || !current.hasMore || current.isLoadingMore || firedRef.current) {
1808
- return;
1809
- }
1810
- firedRef.current = true;
1811
- current.onLoadMore?.();
1812
- }, []);
1813
- const wasLoadingRef = useRef4(isLoadingMore);
1814
- useEffect2(() => {
1815
- if (wasLoadingRef.current && !isLoadingMore) {
1816
- firedRef.current = false;
1817
- maybeLoadMore();
1818
- }
1819
- wasLoadingRef.current = isLoadingMore;
1820
- }, [isLoadingMore, maybeLoadMore]);
1492
+ const scroll = useRef2(null);
1821
1493
  useEffect2(() => {
1822
- if (!hasMore) {
1823
- return void 0;
1824
- }
1825
1494
  const root = rootRef.current;
1826
- const sentinel = sentinelRef.current;
1827
- if (root === null || sentinel === null) {
1495
+ if (root === null) {
1828
1496
  return void 0;
1829
1497
  }
1830
- const observer = new IntersectionObserver(
1831
- (entries) => {
1832
- const entry = entries[entries.length - 1];
1833
- if (entry === void 0) {
1834
- return;
1835
- }
1836
- intersectingRef.current = entry.isIntersecting;
1837
- maybeLoadMore();
1838
- },
1839
- { root, rootMargin }
1840
- );
1841
- observer.observe(sentinel);
1498
+ const attached = attachInfiniteScroll(root, {
1499
+ sentinel: null,
1500
+ hasMore: false
1501
+ });
1502
+ scroll.current = attached;
1842
1503
  return () => {
1843
- observer.disconnect();
1844
- intersectingRef.current = false;
1504
+ attached.detach();
1505
+ scroll.current = null;
1845
1506
  };
1846
- }, [hasMore, rootMargin, rootRef, sentinelRef, maybeLoadMore]);
1847
- }
1848
-
1849
- // src/DataGrid/usePagination.ts
1850
- import {
1851
- paginateRows
1852
- } from "@gridkitjs/core";
1853
- function usePagination({
1854
- pagination,
1855
- setPagination,
1856
- rows,
1857
- onPaginationChange
1858
- }) {
1859
- const { pageCount, pageIndex } = paginateRows(rows, pagination);
1860
- function commit(next) {
1861
- commitIfChanged(pagination, next, setPagination, (committed) => {
1862
- const { pageCount: committedPageCount } = paginateRows(rows, committed);
1863
- onPaginationChange?.({
1864
- pagination: committed,
1865
- pageCount: committedPageCount
1866
- });
1867
- });
1868
- }
1869
- function goToPage(nextPageIndex) {
1870
- const { pageIndex: clamped } = paginateRows(rows, {
1871
- ...pagination,
1872
- pageIndex: nextPageIndex
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
1873
1515
  });
1874
- commit({ ...pagination, pageIndex: clamped });
1875
- }
1876
- return {
1877
- pagination,
1878
- pageCount,
1879
- currentPage: Math.min(pagination.pageIndex + 1, pageCount),
1880
- goToPage,
1881
- nextPage: () => {
1882
- goToPage(pageIndex + 1);
1883
- },
1884
- previousPage: () => {
1885
- goToPage(pageIndex - 1);
1886
- },
1887
- setPageSize: (pageSize) => {
1888
- const next = { pageIndex: 0, pageSize };
1889
- const { pageIndex: clamped } = paginateRows(rows, next);
1890
- commit({ ...next, pageIndex: clamped });
1891
- }
1892
- };
1516
+ });
1893
1517
  }
1894
1518
 
1895
- // src/DataGrid/useRowGrouping.ts
1896
- import {
1897
- collapseAllGroups,
1898
- expandAllGroups,
1899
- moveGroupByBefore,
1900
- toggleGroupExpansion
1901
- } from "@gridkitjs/core";
1902
- function useRowGrouping({
1903
- groupBy,
1904
- setGroupBy,
1905
- expansion,
1906
- setExpansion,
1907
- onGroupByChange,
1908
- onGroupExpansionChange
1909
- }) {
1910
- function commitExpansion(next, groupId) {
1911
- commitIfChanged(expansion, next, setExpansion, (committed) => {
1912
- onGroupExpansionChange?.({ groupId, expansion: committed });
1913
- });
1914
- }
1915
- function commitGroupBy(next, columnId) {
1916
- commitIfChanged(groupBy, next, setGroupBy, (committed) => {
1917
- onGroupByChange?.({ columnId, groupBy: committed });
1918
- });
1919
- }
1920
- function toggleGroupBy(columnId) {
1921
- const isGrouped = groupBy.some((entry) => entry.columnId === columnId);
1922
- const next = isGrouped ? groupBy.filter((entry) => entry.columnId !== columnId) : [...groupBy, { columnId }];
1923
- commitGroupBy(next, columnId);
1924
- }
1925
- function moveGroupBy(columnId, beforeColumnId) {
1926
- commitGroupBy(
1927
- moveGroupByBefore(groupBy, columnId, beforeColumnId),
1928
- columnId
1929
- );
1930
- }
1931
- return {
1932
- groupBy,
1933
- expansion,
1934
- isExpanded: (groupId) => !expansion.includes(groupId),
1935
- toggleExpansion: (groupId) => {
1936
- commitExpansion(toggleGroupExpansion(expansion, groupId), groupId);
1937
- },
1938
- expandAll: () => {
1939
- commitExpansion(expandAllGroups(expansion), null);
1940
- },
1941
- collapseAll: (rows) => {
1942
- commitExpansion(collapseAllGroups(rows), null);
1943
- },
1944
- toggleGroupBy,
1945
- moveGroupBy
1946
- };
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;
1528
+ }
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
+ }, []);
1947
1539
  }
1948
1540
 
1949
1541
  // src/DataGrid/useRowVirtualizer.ts
1950
- import {
1951
- useCallback as useCallback2,
1952
- useEffect as useEffect3,
1953
- useLayoutEffect as useLayoutEffect2,
1954
- useRef as useRef5,
1955
- useState as useState5
1956
- } from "react";
1957
- import { visibleRange } from "@gridkitjs/core";
1542
+ import { useLayoutEffect as useLayoutEffect4, useState as useState7 } from "react";
1543
+ import { createRowVirtualizer } from "@gridkitjs/dom";
1958
1544
  var EMPTY_RANGE = {
1959
1545
  startIndex: 0,
1960
1546
  endIndex: -1,
1961
1547
  offsetBefore: 0,
1962
1548
  offsetAfter: 0
1963
1549
  };
1550
+ function ignoreRow() {
1551
+ return () => void 0;
1552
+ }
1964
1553
  function useRowVirtualizer({
1965
1554
  enabled,
1966
1555
  rows,
@@ -1968,339 +1557,147 @@ function useRowVirtualizer({
1968
1557
  estimatedRowHeight,
1969
1558
  overscan
1970
1559
  }) {
1971
- const rowCount = rows.length;
1972
- const heightsRef = useRef5([]);
1973
- const scrollTopRef = useRef5(0);
1974
- const viewportHeightRef = useRef5(0);
1975
- const elementsRef = useRef5(/* @__PURE__ */ new Map());
1976
- const indexByElementRef = useRef5(/* @__PURE__ */ new Map());
1977
- const refCallbacksRef = useRef5(
1978
- /* @__PURE__ */ new Map()
1979
- );
1980
- const observerRef = useRef5(null);
1981
- const pendingScrollRef = useRef5(void 0);
1982
- const [range, setRange] = useState5(EMPTY_RANGE);
1983
- const recompute = useCallback2(() => {
1984
- setRange(
1985
- enabled ? visibleRange(
1986
- heightsRef.current,
1987
- estimatedRowHeight,
1988
- scrollTopRef.current,
1989
- viewportHeightRef.current,
1990
- overscan
1991
- ) : rowCount === 0 ? EMPTY_RANGE : {
1992
- startIndex: 0,
1993
- endIndex: rowCount - 1,
1994
- offsetBefore: 0,
1995
- offsetAfter: 0
1996
- }
1997
- );
1998
- }, [enabled, rowCount, estimatedRowHeight, overscan]);
1999
- useLayoutEffect2(() => {
2000
- heightsRef.current = new Array(rowCount).fill(
2001
- void 0
2002
- );
2003
- recompute();
2004
- }, [rows, rowCount, recompute]);
2005
- useLayoutEffect2(() => {
2006
- if (!enabled) {
2007
- return void 0;
2008
- }
1560
+ const [virtualizer, setVirtualizer] = useState7(null);
1561
+ const [range, setRange] = useState7(EMPTY_RANGE);
1562
+ useLayoutEffect4(() => {
2009
1563
  const element = bodyScrollRef.current;
2010
- if (element === null) {
1564
+ if (!enabled || element === null) {
2011
1565
  return void 0;
2012
1566
  }
2013
- scrollTopRef.current = element.scrollTop;
2014
- viewportHeightRef.current = element.clientHeight;
2015
- recompute();
2016
- const handleScroll = () => {
2017
- scrollTopRef.current = element.scrollTop;
2018
- recompute();
2019
- };
2020
- element.addEventListener("scroll", handleScroll, { passive: true });
2021
- const resizeObserver = new ResizeObserver(() => {
2022
- viewportHeightRef.current = element.clientHeight;
2023
- recompute();
1567
+ const created = createRowVirtualizer(element, {
1568
+ estimatedRowHeight,
1569
+ overscan,
1570
+ onRange: setRange
2024
1571
  });
2025
- resizeObserver.observe(element);
1572
+ setVirtualizer(created);
2026
1573
  return () => {
2027
- element.removeEventListener("scroll", handleScroll);
2028
- resizeObserver.disconnect();
1574
+ created.destroy();
2029
1575
  };
2030
- }, [enabled, bodyScrollRef, recompute]);
2031
- useEffect3(() => {
2032
- if (!enabled) {
2033
- return void 0;
2034
- }
2035
- const observer = new ResizeObserver((entries) => {
2036
- let changed = false;
2037
- for (const entry of entries) {
2038
- const index = indexByElementRef.current.get(entry.target);
2039
- if (index === void 0) continue;
2040
- const height = entry.target.getBoundingClientRect().height;
2041
- if (heightsRef.current[index] !== height) {
2042
- heightsRef.current[index] = height;
2043
- changed = true;
2044
- }
2045
- }
2046
- if (changed) {
2047
- recompute();
2048
- }
2049
- });
2050
- observerRef.current = observer;
2051
- for (const element of elementsRef.current.values()) {
2052
- observer.observe(element);
2053
- }
2054
- return () => {
2055
- observer.disconnect();
2056
- observerRef.current = null;
2057
- };
2058
- }, [enabled, recompute]);
2059
- function measureRow(index) {
2060
- let callback = refCallbacksRef.current.get(index);
2061
- if (callback === void 0) {
2062
- callback = (element) => {
2063
- const observer = observerRef.current;
2064
- const previous = elementsRef.current.get(index);
2065
- if (previous !== void 0) {
2066
- observer?.unobserve(previous);
2067
- indexByElementRef.current.delete(previous);
2068
- elementsRef.current.delete(index);
2069
- }
2070
- if (element === null) {
2071
- return;
2072
- }
2073
- elementsRef.current.set(index, element);
2074
- indexByElementRef.current.set(element, index);
2075
- observer?.observe(element);
2076
- };
2077
- refCallbacksRef.current.set(index, callback);
2078
- }
2079
- return callback;
2080
- }
2081
- useEffect3(() => {
2082
- const pending = pendingScrollRef.current;
2083
- if (pending === void 0) {
2084
- return;
2085
- }
2086
- const element = elementsRef.current.get(pending.index);
2087
- if (element === void 0) {
2088
- return;
2089
- }
2090
- element.scrollIntoView(pending.options);
2091
- pendingScrollRef.current = void 0;
2092
- }, [range]);
2093
- function estimateOffsetBefore(index) {
2094
- let total = 0;
2095
- for (let current = 0; current < index; current++) {
2096
- total += heightsRef.current[current] ?? estimatedRowHeight;
2097
- }
2098
- return total;
2099
- }
2100
- function scrollToIndex(index, options) {
2101
- const element = bodyScrollRef.current;
2102
- if (element === null || rowCount === 0) {
2103
- return;
2104
- }
2105
- const clamped = Math.max(0, Math.min(index, rowCount - 1));
2106
- const mounted = elementsRef.current.get(clamped);
2107
- if (mounted !== void 0) {
2108
- mounted.scrollIntoView(options);
2109
- return;
2110
- }
2111
- pendingScrollRef.current = { index: clamped, options };
2112
- element.scrollTop = estimateOffsetBefore(clamped);
2113
- }
1576
+ }, [enabled, bodyScrollRef, estimatedRowHeight, overscan]);
1577
+ useLayoutEffect4(() => {
1578
+ virtualizer?.setRowCount(rows.length);
1579
+ virtualizer?.resetHeights();
1580
+ }, [virtualizer, rows]);
2114
1581
  return {
2115
1582
  startIndex: range.startIndex,
2116
1583
  endIndex: range.endIndex,
2117
1584
  offsetBefore: range.offsetBefore,
2118
1585
  offsetAfter: range.offsetAfter,
2119
- measureRow,
2120
- scrollToIndex
1586
+ measureRow: virtualizer?.measureRow ?? ignoreRow,
1587
+ scrollToIndex: (index, options) => {
1588
+ virtualizer?.scrollToIndex(index, options);
1589
+ }
2121
1590
  };
2122
1591
  }
2123
1592
 
2124
1593
  // src/DataGrid/DataGrid.tsx
2125
1594
  import { jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
2126
- var DEFAULT_PAGE_SIZE = 25;
2127
1595
  var NO_AGGREGATES = [];
2128
- function DataGridComponent({
2129
- dataSource,
2130
- columns,
2131
- getRowId,
2132
- borders,
2133
- hoverable,
2134
- selectable,
2135
- defaultRowSelection,
2136
- defaultColumnSelection,
2137
- defaultCellSelection,
2138
- resizableColumns = false,
2139
- reorderableColumns = false,
2140
- resizeMode = "fit",
2141
- defaultColumnSizing,
2142
- defaultColumnOrder,
2143
- columnSizeDefaults,
2144
- onColumnResize,
2145
- onColumnOrderChange,
2146
- sortableColumns = false,
2147
- defaultColumnSort,
2148
- onColumnSortChange,
2149
- groupableColumns = false,
2150
- groupToggleIconColumns = true,
2151
- groupByDraggableColumns = false,
2152
- groupByBarVisibility = "auto",
2153
- defaultGroupBy,
2154
- onGroupByChange,
2155
- defaultGroupExpansion,
2156
- onGroupExpansionChange,
2157
- defaultFilter,
2158
- paginated = false,
2159
- defaultPagination,
2160
- pager,
2161
- onPaginationChange,
2162
- infiniteScroll,
2163
- aggregates,
2164
- groupAggregateDisplay = "inline",
2165
- height,
2166
- virtualized = false,
2167
- overscan = 4,
2168
- estimatedRowHeight = 40,
2169
- label,
2170
- labelledBy,
2171
- ref,
2172
- ...callbacks
2173
- }) {
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;
2174
1654
  const hoverRows = hoverable?.rows ?? true;
2175
1655
  const hoverColumns = hoverable?.columns ?? true;
2176
1656
  const hoverCells = hoverable?.cells ?? true;
2177
- const viewportRef = useRef6(null);
2178
- const headerTableRef = useRef6(null);
2179
- const bodyTableRef = useRef6(null);
2180
- const bodyScrollRef = useRef6(null);
2181
- const sentinelRef = useRef6(null);
1657
+ const viewportRef = useRef4(null);
1658
+ const headerTableRef = useRef4(null);
1659
+ const bodyTableRef = useRef4(null);
1660
+ const bodyScrollRef = useRef4(null);
1661
+ const sentinelRef = useRef4(null);
2182
1662
  const viewportWidth = useElementWidth(viewportRef, resizeMode === "fit");
2183
- const subscribersRef = useRef6(/* @__PURE__ */ new Set());
2184
- const [sizing, setSizing] = useState6(
2185
- defaultColumnSizing ?? {}
2186
- );
2187
- const [order, setOrder] = useState6(
2188
- defaultColumnOrder ?? []
2189
- );
2190
- const [sort, setSort] = useState6(defaultColumnSort ?? []);
2191
- const [groupBy, setGroupBy] = useState6(defaultGroupBy ?? []);
2192
- const [expansion, setExpansion] = useState6(
2193
- defaultGroupExpansion ?? []
2194
- );
2195
- const [filter] = useState6(defaultFilter ?? []);
2196
- const [pagination, setPagination] = useState6(
2197
- defaultPagination ?? { pageIndex: 0, pageSize: DEFAULT_PAGE_SIZE }
2198
- );
2199
- const [announcement, setAnnouncement] = useState6("");
2200
- const [rowSelection, setRowSelection] = useState6(
2201
- defaultRowSelection ?? []
2202
- );
2203
- const [columnSelection, setColumnSelection] = useState6(
2204
- defaultColumnSelection ?? []
2205
- );
2206
- const [cellSelection, setCellSelection] = useState6(
2207
- defaultCellSelection ?? null
2208
- );
2209
- const rows = useMemo4(
2210
- () => dataSource?.map((row, rowIndex) => ({
2211
- rowId: resolveRowId(row, rowIndex, getRowId),
2212
- row,
2213
- rowIndex,
2214
- datasetIndex: rowIndex
2215
- })) ?? [],
2216
- [dataSource, getRowId]
2217
- );
2218
- const definedColumns = useMemo4(() => {
2219
- if (columns && columns.length !== 0) return columns;
2220
- if (dataSource && dataSource.length > 0)
2221
- return defineColumnsFromRows(dataSource);
2222
- return [];
2223
- }, [columns, dataSource]);
2224
- const orderedColumns = useMemo4(
2225
- () => applyColumnOrder(definedColumns, order),
2226
- [definedColumns, order]
1663
+ const subscribersRef = useRef4(/* @__PURE__ */ new Set());
1664
+ const announce = useLiveAnnouncer(viewportRef);
1665
+ const messages = useMemo3(
1666
+ () => resolveMessages(messageOverrides),
1667
+ [messageOverrides]
2227
1668
  );
2228
- const resolved = useMemo4(() => {
2229
- const widths = resolveColumnWidths(orderedColumns, sizing, {
2230
- sizes: columnSizeDefaults,
2231
- resizable: resizableColumns,
2232
- reorderable: reorderableColumns,
2233
- groupByDraggable: groupByDraggableColumns
2234
- });
2235
- return resizeMode === "fit" && viewportWidth !== null ? fitColumnsToWidth(widths, viewportWidth, columnSizeDefaults) : widths;
2236
- }, [
2237
- orderedColumns,
2238
- sizing,
2239
- resizeMode,
1669
+ const { controller, state, view } = useGridController({
1670
+ ...props,
2240
1671
  viewportWidth,
2241
- columnSizeDefaults,
2242
- resizableColumns,
2243
- reorderableColumns,
2244
- groupByDraggableColumns
2245
- ]);
2246
- const shownRows = useMemo4(
2247
- () => resolveShownRows(rows, filter, sort, resolved),
2248
- [rows, filter, sort, resolved]
2249
- );
2250
- const displayRows = useMemo4(
2251
- () => groupRows(shownRows, groupBy, expansion, resolved),
2252
- [shownRows, groupBy, expansion, resolved]
2253
- );
2254
- const activeAggregates = aggregates ?? NO_AGGREGATES;
2255
- const aggregatedRows = useMemo4(
2256
- () => withGroupAggregates(
2257
- displayRows,
2258
- shownRows,
2259
- groupBy,
2260
- activeAggregates,
2261
- resolved,
2262
- groupAggregateDisplay
2263
- ),
2264
- [
2265
- displayRows,
2266
- shownRows,
2267
- groupBy,
2268
- activeAggregates,
2269
- resolved,
2270
- groupAggregateDisplay
2271
- ]
2272
- );
2273
- const grandTotal = useMemo4(
2274
- () => computeAggregates(
2275
- shownRows.map((entry) => entry.row),
2276
- activeAggregates,
2277
- resolved
2278
- ),
2279
- [shownRows, activeAggregates, resolved]
2280
- );
2281
- const paginatedRows = useMemo4(
2282
- () => paginated ? paginateRows2(aggregatedRows, pagination) : { rows: aggregatedRows, pageCount: 1, pageIndex: 0 },
2283
- [paginated, aggregatedRows, pagination]
2284
- );
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));
2285
1682
  const virtualizer = useRowVirtualizer({
2286
1683
  enabled: virtualized,
2287
- rows: paginatedRows.rows,
1684
+ rows: view.pageRows,
2288
1685
  bodyScrollRef,
2289
1686
  estimatedRowHeight,
2290
1687
  overscan
2291
1688
  });
2292
- useEffect4(() => {
1689
+ useEffect3(() => {
2293
1690
  if (virtualized && height === void 0) {
2294
1691
  console.error(
2295
- "DataGridComponent: `virtualized` has no effect without `height` \u2014 a body with no bounded height has no viewport to window rows against."
1692
+ "DataGrid: `virtualized` has no effect without `height` \u2014 a body with no bounded height has no viewport to window rows against."
2296
1693
  );
2297
1694
  }
2298
1695
  }, [virtualized, height]);
2299
1696
  const hasInfiniteScroll = infiniteScroll !== void 0;
2300
- useEffect4(() => {
1697
+ useEffect3(() => {
2301
1698
  if (paginated && hasInfiniteScroll) {
2302
1699
  console.error(
2303
- "DataGridComponent: `paginated` and `infiniteScroll` are mutually exclusive \u2014 only `paginated` takes effect while both are set."
1700
+ "DataGrid: `paginated` and `infiniteScroll` are mutually exclusive \u2014 only `paginated` takes effect while both are set."
2304
1701
  );
2305
1702
  }
2306
1703
  }, [paginated, hasInfiniteScroll]);
@@ -2309,46 +1706,31 @@ function DataGridComponent({
2309
1706
  sentinelRef,
2310
1707
  config: infiniteScroll
2311
1708
  });
2312
- const paginationSnapshot = useMemo4(
2313
- () => ({
2314
- pageIndex: paginatedRows.pageIndex,
2315
- pageSize: pagination.pageSize
2316
- }),
2317
- [paginatedRows.pageIndex, pagination.pageSize]
2318
- );
2319
- const displayDataRows = useMemo4(
2320
- () => groupBy.length === 0 && !paginated ? shownRows : paginatedRows.rows.filter(
2321
- (entry) => !("kind" in entry)
2322
- ),
2323
- [paginatedRows, groupBy.length, paginated, shownRows]
2324
- );
2325
1709
  function columnName(columnId) {
2326
- const entry = resolved.find((candidate) => candidate.id === columnId);
2327
- if (entry === void 0) {
2328
- return columnId;
2329
- }
2330
- return typeof entry.label === "string" ? entry.label : entry.column.field;
2331
- }
2332
- function announce(message) {
2333
- setAnnouncement(message);
1710
+ const entry = view.dataColumns.find(
1711
+ (candidate) => candidate.id === columnId
1712
+ );
1713
+ return entry === void 0 ? columnId : resolveColumnHeaderText(entry.column);
2334
1714
  }
2335
1715
  function handleColumnResize(event) {
2336
1716
  onColumnResize?.(event);
2337
1717
  if (event.phase === "end") {
2338
1718
  announce(
2339
- `${columnName(event.columnId)}, ${String(Math.round(event.width))} pixels wide`
1719
+ messages.columnResized(
1720
+ columnName(event.columnId),
1721
+ Math.round(event.width)
1722
+ )
2340
1723
  );
2341
1724
  }
2342
1725
  }
2343
1726
  const resize = useColumnResize({
2344
- // `measureColumnContent` only ever matches `td[data-gridkit-column]`
2345
- // (never `th`), so the header table has nothing for it to find — the
2346
- // body table alone was already the effective scope before this split.
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.
2347
1731
  tableRef: bodyTableRef,
2348
- sizing,
2349
- setSizing,
2350
- columnSizeDefaults,
2351
- onColumnResize: handleColumnResize
1732
+ controller,
1733
+ columnSizeDefaults
2352
1734
  });
2353
1735
  function handleGroupByChange(event) {
2354
1736
  onGroupByChange?.(event);
@@ -2356,33 +1738,69 @@ function DataGridComponent({
2356
1738
  (candidate) => candidate.columnId === event.columnId
2357
1739
  );
2358
1740
  if (entry === void 0) {
2359
- announce(`${columnName(event.columnId)}, grouping removed`);
1741
+ announce(messages.groupingRemoved(columnName(event.columnId)));
2360
1742
  return;
2361
1743
  }
2362
1744
  announce(
2363
- 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
+ )
2364
1750
  );
2365
1751
  }
2366
1752
  function handleGroupExpansionChange(event) {
2367
1753
  onGroupExpansionChange?.(event);
2368
1754
  if (event.groupId === null) {
2369
1755
  announce(
2370
- event.expansion.length === 0 ? "All groups expanded" : "All groups collapsed"
1756
+ event.expansion.length === 0 ? messages.allGroupsExpanded : messages.allGroupsCollapsed
2371
1757
  );
2372
1758
  return;
2373
1759
  }
2374
1760
  announce(
2375
- event.expansion.includes(event.groupId) ? "Group collapsed" : "Group expanded"
1761
+ event.expansion.includes(event.groupId) ? messages.groupCollapsed : messages.groupExpanded
2376
1762
  );
2377
1763
  }
2378
- const grouping = useRowGrouping({
2379
- groupBy,
2380
- setGroupBy,
2381
- expansion,
2382
- setExpansion,
2383
- onGroupByChange: handleGroupByChange,
2384
- onGroupExpansionChange: handleGroupExpansionChange
2385
- });
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
+ }
2386
1804
  const nav = useGridNavigation({
2387
1805
  headerTableRef,
2388
1806
  bodyTableRef,
@@ -2392,131 +1810,82 @@ function DataGridComponent({
2392
1810
  // Group headers are addressable rows too — the whole point of the flat
2393
1811
  // `DisplayRow[]` shape is that they share one position space with data
2394
1812
  // rows, so navigation counts them the same way. Page-relative
2395
- // (`paginatedRows`, not `displayRows`): arrow keys operate on what's
1813
+ // (`view.pageRows`, not `view.displayRows`): arrow keys operate on what's
2396
1814
  // actually in the DOM, which is the current page — a different "page"
2397
1815
  // concept than `useGridNavigation`'s own Page Up/Down viewport scrolling.
2398
- rowCount: paginatedRows.rows.length,
2399
- columnCount: resolved.length,
1816
+ rowCount: view.pageRows.length,
1817
+ columnCount: view.columns.length,
2400
1818
  // A group's own summary row (`groupAggregateDisplay: "row"`) occupies a
2401
1819
  // real slot in `rowCount` above — its DOM position has to line up with
2402
1820
  // everything else — but is presentational, never a tab stop: arrow-key
2403
1821
  // vertical movement steps over it rather than landing there.
2404
1822
  isSkippableRow: (rowIndex) => {
2405
- const entry = paginatedRows.rows[rowIndex];
1823
+ const entry = view.pageRows[rowIndex];
2406
1824
  return entry !== void 0 && "kind" in entry && entry.kind === "group-summary";
2407
1825
  }
2408
1826
  });
2409
- const displayedIds = useMemo4(
2410
- () => orderedColumns.map((column) => column.id ?? column.field),
2411
- [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]
1834
+ );
1835
+ const definitions = useMemo3(
1836
+ () => new Map(view.dataColumns.map((entry) => [entry.id, entry.column])),
1837
+ [view.dataColumns]
2412
1838
  );
2413
- const groupByDraggableIds = useMemo4(
1839
+ const groupByDraggableIds = useMemo3(
2414
1840
  () => new Set(
2415
- resolved.filter((entry) => entry.groupByDraggable).map((entry) => entry.id)
1841
+ view.columns.filter((entry) => entry.groupByDraggable).map((entry) => entry.id)
2416
1842
  ),
2417
- [resolved]
1843
+ [view.columns]
2418
1844
  );
2419
1845
  function handleDrop(target, movedId) {
2420
1846
  if (target.kind === "group-by") {
2421
- grouping.moveGroupBy(movedId, target.beforeColumnId);
1847
+ controller.moveGroupBy(movedId, target.beforeColumnId);
2422
1848
  return;
2423
1849
  }
2424
- const next = moveColumnBefore(displayedIds, movedId, target.beforeId);
2425
- if (next === displayedIds) {
1850
+ const next = moveColumnBefore(orderedIds, movedId, target.beforeId);
1851
+ if (next === orderedIds) {
2426
1852
  return;
2427
1853
  }
2428
- const position = next.indexOf(movedId);
2429
- setOrder(next);
2430
- 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);
2431
1857
  announce(
2432
- `${columnName(movedId)}, column ${String(position + 1)} of ${String(next.length)}`
1858
+ messages.columnMoved(columnName(movedId), position + 1, shown.length)
2433
1859
  );
2434
1860
  nav.focusCell(HEADER_ROW, position);
2435
1861
  }
2436
1862
  const drag = useColumnDrag({
2437
1863
  order: displayedIds,
2438
- groupBy,
1864
+ groupBy: state.groupBy,
2439
1865
  groupByDraggableIds,
2440
1866
  onDrop: handleDrop
2441
1867
  });
2442
- 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);
2443
1869
  const dragEligibleForGroupBy = drag.draggedColumnId !== null && groupByDraggableIds.has(drag.draggedColumnId) && !draggedColumnAlreadyGrouped;
2444
- const showGroupByBar = groupByBarVisibility === "always" || groupByBarVisibility === "auto" && (groupBy.length > 0 || dragEligibleForGroupBy);
1870
+ const showGroupByBar = groupByBarVisibility === "always" || groupByBarVisibility === "auto" && (state.groupBy.length > 0 || dragEligibleForGroupBy);
2445
1871
  const headerGroupByDropTarget = drag.dropTarget?.kind === "group-by" ? drag.dropTarget : null;
2446
- function handleColumnSortChange(event) {
2447
- onColumnSortChange?.(event);
2448
- const entry = event.sort.find(
2449
- (candidate) => candidate.columnId === event.columnId
2450
- );
2451
- if (entry === void 0) {
2452
- announce(`${columnName(event.columnId)}, sort cleared`);
2453
- return;
2454
- }
2455
- const direction = entry.direction === "asc" ? "ascending" : "descending";
2456
- announce(
2457
- 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}`
2458
- );
2459
- }
2460
- const columnSort = useColumnSort({
2461
- sort,
2462
- setSort,
2463
- onColumnSortChange: handleColumnSortChange
2464
- });
2465
- function handlePaginationChange(event) {
2466
- onPaginationChange?.(event);
2467
- announce(
2468
- `Page ${String(event.pagination.pageIndex + 1)} of ${String(event.pageCount)}`
2469
- );
2470
- }
2471
- const paginationApi = usePagination({
2472
- pagination,
2473
- setPagination,
2474
- rows: displayRows,
2475
- onPaginationChange: handlePaginationChange
2476
- });
2477
- const [previous, setPrevious] = useState6({ filter, sort, groupBy });
2478
- if (previous.filter !== filter || previous.sort !== sort || previous.groupBy !== groupBy) {
2479
- setPrevious({ filter, sort, groupBy });
2480
- if (pagination.pageIndex !== 0) {
2481
- setPagination({ ...pagination, pageIndex: 0 });
2482
- }
2483
- }
2484
- const selection = useGridSelection({
2485
- rows: displayDataRows,
2486
- columns: resolved,
2487
- selectable,
2488
- rowSelection,
2489
- setRowSelection,
2490
- columnSelection,
2491
- setColumnSelection,
2492
- cellSelection,
2493
- setCellSelection,
2494
- callbacks,
2495
- announce,
2496
- columnName
2497
- });
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 });
2498
1883
  const multiselectable = selection.rowMode === "multiple" || selection.columnMode === "multiple";
2499
- useEffect4(() => {
1884
+ useEffect3(() => {
2500
1885
  for (const listener of subscribersRef.current) {
2501
1886
  listener();
2502
1887
  }
2503
- }, [
2504
- shownRows,
2505
- aggregatedRows,
2506
- resolved,
2507
- sizing,
2508
- order,
2509
- sort,
2510
- groupBy,
2511
- expansion,
2512
- paginationSnapshot,
2513
- paginatedRows.pageCount,
2514
- grandTotal,
2515
- rowSelection,
2516
- columnSelection,
2517
- cellSelection,
2518
- nav.focus
2519
- ]);
1888
+ }, [view, state, nav.focus]);
2520
1889
  useImperativeHandle(ref, () => ({
2521
1890
  get element() {
2522
1891
  return viewportRef.current;
@@ -2524,38 +1893,62 @@ function DataGridComponent({
2524
1893
  get table() {
2525
1894
  return bodyTableRef.current;
2526
1895
  },
2527
- getRows: () => shownRows,
2528
- // `aggregatedRows`, not the plain `displayRows` it's derived from: the
2529
- // same array shape and content, except every group header's own
2530
- // `aggregates` field is populated — the whole reason `getDisplayRows()`
2531
- // is where a consumer reads a specific group's computed results from.
2532
- getDisplayRows: () => aggregatedRows,
2533
- getColumns: () => resolved,
2534
- getColumnSizing: () => sizing,
2535
- getColumnOrder: () => order,
2536
- getColumnSort: () => sort,
2537
- getGroupBy: () => groupBy,
2538
- getGroupExpansion: () => expansion,
2539
- getPagination: () => paginationSnapshot,
2540
- getPageCount: () => paginatedRows.pageCount,
2541
- getAggregates: () => grandTotal,
2542
- getRowSelection: () => rowSelection,
2543
- getColumnSelection: () => columnSelection,
2544
- 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,
2545
1918
  getFocusedCell: () => nav.focus,
1919
+ getState: controller.getState,
1920
+ restoreState: controller.restoreState,
2546
1921
  focusCell: nav.focusCell,
2547
- clearSelection: selection.clear,
2548
- selectAllRows: selection.selectAllRows,
2549
- expandAllGroups: grouping.expandAll,
2550
- collapseAllGroups: () => {
2551
- 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
+ }
2552
1949
  },
2553
- goToPage: paginationApi.goToPage,
2554
- nextPage: paginationApi.nextPage,
2555
- previousPage: paginationApi.previousPage,
2556
- setPageSize: paginationApi.setPageSize,
2557
1950
  scrollToRow: (rowId, options) => {
2558
- const index = paginatedRows.rows.findIndex(
1951
+ const index = controller.getView().pageRows.findIndex(
2559
1952
  (entry) => !("kind" in entry) && entry.rowId === rowId
2560
1953
  );
2561
1954
  if (index === -1) return;
@@ -2598,21 +1991,24 @@ function DataGridComponent({
2598
1991
  showGroupByBar && /* @__PURE__ */ jsx10(
2599
1992
  GroupByBar,
2600
1993
  {
2601
- columns: resolved,
2602
- grouping,
1994
+ columns: view.columns,
1995
+ definitions,
1996
+ groupBy: state.groupBy,
1997
+ controller,
2603
1998
  columnName,
2604
1999
  headerDragEligible: dragEligibleForGroupBy,
2605
2000
  headerDragBlocked: draggedColumnAlreadyGrouped,
2606
- headerDropTarget: headerGroupByDropTarget
2001
+ headerDropTarget: headerGroupByDropTarget,
2002
+ messages
2607
2003
  }
2608
2004
  ),
2609
2005
  /* @__PURE__ */ jsxs7(
2610
2006
  "div",
2611
2007
  {
2612
2008
  className: "gridkit-data-grid-tables",
2613
- role: groupBy.length > 0 ? "treegrid" : "grid",
2614
- "aria-rowcount": aggregatedRows.length + 1,
2615
- "aria-colcount": resolved.length,
2009
+ role: state.groupBy.length > 0 ? "treegrid" : "grid",
2010
+ "aria-rowcount": view.displayRows.length + 1,
2011
+ "aria-colcount": view.columns.length,
2616
2012
  ...ariaAttr(multiselectable, "aria-multiselectable", true),
2617
2013
  ...ariaAttr(labelledBy !== void 0, "aria-labelledby", labelledBy),
2618
2014
  ...ariaAttr(
@@ -2638,19 +2034,20 @@ function DataGridComponent({
2638
2034
  "table",
2639
2035
  {
2640
2036
  ref: headerTableRef,
2641
- style: { width: totalColumnWidth(resolved) },
2037
+ style: { width: totalColumnWidth(view.columns) },
2642
2038
  className: classNames(gridTableClassName, "gridkit-data-grid-header"),
2643
2039
  children: [
2644
- /* @__PURE__ */ jsx10(GridColgroup, { columns: resolved }),
2040
+ /* @__PURE__ */ jsx10(GridColgroup, { columns: view.columns }),
2645
2041
  /* @__PURE__ */ jsx10(
2646
2042
  GridHeader,
2647
2043
  {
2648
- columns: resolved,
2044
+ columns: view.columns,
2649
2045
  resize,
2650
2046
  drag,
2651
- sort: columnSort,
2047
+ controller,
2048
+ columnSort: state.columnSort,
2652
2049
  sortableColumns,
2653
- grouping,
2050
+ groupBy: state.groupBy,
2654
2051
  groupableColumns,
2655
2052
  groupToggleIconColumns,
2656
2053
  nav,
@@ -2672,24 +2069,30 @@ function DataGridComponent({
2672
2069
  "table",
2673
2070
  {
2674
2071
  ref: bodyTableRef,
2675
- style: { width: totalColumnWidth(resolved) },
2072
+ style: { width: totalColumnWidth(view.columns) },
2676
2073
  className: gridTableClassName,
2677
2074
  children: [
2678
- /* @__PURE__ */ jsx10(GridColgroup, { columns: resolved }),
2075
+ /* @__PURE__ */ jsx10(GridColgroup, { columns: view.columns }),
2679
2076
  /* @__PURE__ */ jsx10(
2680
2077
  GridBody,
2681
2078
  {
2682
- columns: resolved,
2683
- rows: paginatedRows.rows,
2079
+ columns: view.columns,
2080
+ definitions,
2081
+ rows: view.pageRows,
2684
2082
  activeColumnId: resize.activeColumnId,
2685
2083
  nav,
2686
2084
  selection,
2687
- grouping,
2688
- aggregates: activeAggregates,
2085
+ controller,
2086
+ aggregates,
2689
2087
  groupAggregateDisplay,
2690
2088
  virtualRange: virtualized ? virtualizer : null,
2691
2089
  infiniteScroll,
2692
- sentinelRef
2090
+ sentinelRef,
2091
+ locale,
2092
+ getRowProps,
2093
+ onRowClick,
2094
+ onRowDoubleClick,
2095
+ messages
2693
2096
  }
2694
2097
  )
2695
2098
  ]
@@ -2697,23 +2100,24 @@ function DataGridComponent({
2697
2100
  )
2698
2101
  }
2699
2102
  ),
2700
- activeAggregates.length > 0 && /* @__PURE__ */ jsxs7(
2103
+ aggregates.length > 0 && /* @__PURE__ */ jsxs7(
2701
2104
  "table",
2702
2105
  {
2703
- style: { width: totalColumnWidth(resolved) },
2106
+ style: { width: totalColumnWidth(view.columns) },
2704
2107
  className: classNames(
2705
2108
  gridTableClassName,
2706
2109
  "gridkit-data-grid-footer"
2707
2110
  ),
2708
2111
  children: [
2709
- /* @__PURE__ */ jsx10(GridColgroup, { columns: resolved }),
2112
+ /* @__PURE__ */ jsx10(GridColgroup, { columns: view.columns }),
2710
2113
  /* @__PURE__ */ jsx10(
2711
2114
  GridFooter,
2712
2115
  {
2713
- columns: resolved,
2714
- aggregates: activeAggregates,
2715
- results: grandTotal,
2716
- rows: shownRows.map((entry) => entry.row)
2116
+ columns: view.columns,
2117
+ aggregates,
2118
+ results: view.grandTotal,
2119
+ rows: view.rows.map((entry) => entry.row),
2120
+ locale
2717
2121
  }
2718
2122
  )
2719
2123
  ]
@@ -2722,27 +2126,20 @@ function DataGridComponent({
2722
2126
  ]
2723
2127
  }
2724
2128
  ),
2725
- paginated && (pager?.template ? pager.template({
2726
- pagination: paginationApi.pagination,
2727
- currentPage: paginationApi.currentPage,
2728
- pageCount: paginationApi.pageCount,
2729
- pageSizeOptions: pager.sizeOptions,
2730
- goToPage: paginationApi.goToPage,
2731
- nextPage: paginationApi.nextPage,
2732
- previousPage: paginationApi.previousPage,
2733
- setPageSize: paginationApi.setPageSize
2734
- }) : /* @__PURE__ */ jsx10(GridPager, { pager: paginationApi, config: pager })),
2735
- /* @__PURE__ */ jsx10("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 }))
2736
2130
  ] });
2737
2131
  }
2738
2132
 
2133
+ // src/index.ts
2134
+ import { serializeGridState } from "@gridkitjs/core";
2135
+
2739
2136
  // src/DataGrid/hooks/usePaginationState.ts
2740
- import { useCallback as useCallback4 } from "react";
2137
+ import { useCallback as useCallback3 } from "react";
2741
2138
 
2742
2139
  // src/DataGrid/hooks/useGridSnapshot.ts
2743
- import { useCallback as useCallback3, useSyncExternalStore } from "react";
2140
+ import { useCallback as useCallback2, useSyncExternalStore as useSyncExternalStore3 } from "react";
2744
2141
  function useGridSnapshot(gridRef, getSnapshot, fallback) {
2745
- const subscribe = useCallback3(
2142
+ const subscribe = useCallback2(
2746
2143
  (listener) => {
2747
2144
  const api = gridRef.current;
2748
2145
  if (api === null) {
@@ -2753,7 +2150,7 @@ function useGridSnapshot(gridRef, getSnapshot, fallback) {
2753
2150
  },
2754
2151
  [gridRef]
2755
2152
  );
2756
- return useSyncExternalStore(subscribe, () => {
2153
+ return useSyncExternalStore3(subscribe, () => {
2757
2154
  const api = gridRef.current;
2758
2155
  return api === null ? fallback : getSnapshot(api);
2759
2156
  });
@@ -2764,12 +2161,12 @@ var FALLBACK_PAGINATION = { pageIndex: 0, pageSize: 0 };
2764
2161
  function usePaginationState(gridRef) {
2765
2162
  const pagination = useGridSnapshot(
2766
2163
  gridRef,
2767
- useCallback4((api) => api.getPagination(), []),
2164
+ useCallback3((api) => api.getPagination(), []),
2768
2165
  FALLBACK_PAGINATION
2769
2166
  );
2770
2167
  const pageCount = useGridSnapshot(
2771
2168
  gridRef,
2772
- useCallback4((api) => api.getPageCount(), []),
2169
+ useCallback3((api) => api.getPageCount(), []),
2773
2170
  0
2774
2171
  );
2775
2172
  return {
@@ -2791,18 +2188,18 @@ function usePaginationState(gridRef) {
2791
2188
  }
2792
2189
 
2793
2190
  // src/DataGrid/hooks/useGroupByState.ts
2794
- import { useCallback as useCallback5 } from "react";
2191
+ import { useCallback as useCallback4 } from "react";
2795
2192
  var FALLBACK_GROUP_BY = [];
2796
2193
  var FALLBACK_EXPANSION = [];
2797
2194
  function useGroupByState(gridRef) {
2798
2195
  const groupBy = useGridSnapshot(
2799
2196
  gridRef,
2800
- useCallback5((api) => api.getGroupBy(), []),
2197
+ useCallback4((api) => api.getGroupBy(), []),
2801
2198
  FALLBACK_GROUP_BY
2802
2199
  );
2803
2200
  const groupExpansion = useGridSnapshot(
2804
2201
  gridRef,
2805
- useCallback5((api) => api.getGroupExpansion(), []),
2202
+ useCallback4((api) => api.getGroupExpansion(), []),
2806
2203
  FALLBACK_EXPANSION
2807
2204
  );
2808
2205
  return {
@@ -2818,34 +2215,34 @@ function useGroupByState(gridRef) {
2818
2215
  }
2819
2216
 
2820
2217
  // src/DataGrid/hooks/useColumnSortState.ts
2821
- import { useCallback as useCallback6 } from "react";
2218
+ import { useCallback as useCallback5 } from "react";
2822
2219
  var FALLBACK_SORT = [];
2823
2220
  function useColumnSortState(gridRef) {
2824
2221
  return useGridSnapshot(
2825
2222
  gridRef,
2826
- useCallback6((api) => api.getColumnSort(), []),
2223
+ useCallback5((api) => api.getColumnSort(), []),
2827
2224
  FALLBACK_SORT
2828
2225
  );
2829
2226
  }
2830
2227
 
2831
2228
  // src/DataGrid/hooks/useSelectionState.ts
2832
- import { useCallback as useCallback7 } from "react";
2229
+ import { useCallback as useCallback6 } from "react";
2833
2230
  var FALLBACK_SELECTION = [];
2834
2231
  var FALLBACK_CELL_SELECTION = null;
2835
2232
  function useSelectionState(gridRef) {
2836
2233
  const rowSelection = useGridSnapshot(
2837
2234
  gridRef,
2838
- useCallback7((api) => api.getRowSelection(), []),
2235
+ useCallback6((api) => api.getRowSelection(), []),
2839
2236
  FALLBACK_SELECTION
2840
2237
  );
2841
2238
  const columnSelection = useGridSnapshot(
2842
2239
  gridRef,
2843
- useCallback7((api) => api.getColumnSelection(), []),
2240
+ useCallback6((api) => api.getColumnSelection(), []),
2844
2241
  FALLBACK_SELECTION
2845
2242
  );
2846
2243
  const cellSelection = useGridSnapshot(
2847
2244
  gridRef,
2848
- useCallback7((api) => api.getCellSelection(), []),
2245
+ useCallback6((api) => api.getCellSelection(), []),
2849
2246
  FALLBACK_CELL_SELECTION
2850
2247
  );
2851
2248
  return {
@@ -2862,43 +2259,74 @@ function useSelectionState(gridRef) {
2862
2259
  }
2863
2260
 
2864
2261
  // src/DataGrid/hooks/useAggregateState.ts
2865
- import { useCallback as useCallback8 } from "react";
2262
+ import { useCallback as useCallback7 } from "react";
2866
2263
  var FALLBACK_AGGREGATES = /* @__PURE__ */ new Map();
2867
2264
  function useAggregateState(gridRef) {
2868
2265
  return useGridSnapshot(
2869
2266
  gridRef,
2870
- useCallback8((api) => api.getAggregates(), []),
2267
+ useCallback7((api) => api.getAggregates(), []),
2871
2268
  FALLBACK_AGGREGATES
2872
2269
  );
2873
2270
  }
2874
2271
 
2875
2272
  // src/DataGrid/hooks/useColumnSizingState.ts
2876
- import { useCallback as useCallback9 } from "react";
2273
+ import { useCallback as useCallback8 } from "react";
2877
2274
  var FALLBACK_SIZING = {};
2878
2275
  function useColumnSizingState(gridRef) {
2879
2276
  return useGridSnapshot(
2880
2277
  gridRef,
2881
- useCallback9((api) => api.getColumnSizing(), []),
2278
+ useCallback8((api) => api.getColumnSizing(), []),
2882
2279
  FALLBACK_SIZING
2883
2280
  );
2884
2281
  }
2885
2282
 
2886
2283
  // src/DataGrid/hooks/useColumnOrderState.ts
2887
- import { useCallback as useCallback10 } from "react";
2284
+ import { useCallback as useCallback9 } from "react";
2888
2285
  var FALLBACK_ORDER = [];
2889
2286
  function useColumnOrderState(gridRef) {
2890
2287
  return useGridSnapshot(
2891
2288
  gridRef,
2892
- useCallback10((api) => api.getColumnOrder(), []),
2289
+ useCallback9((api) => api.getColumnOrder(), []),
2893
2290
  FALLBACK_ORDER
2894
2291
  );
2895
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
+ }
2896
2321
  export {
2897
- DataGridComponent,
2322
+ DataGrid,
2323
+ serializeGridState,
2898
2324
  useAggregateState,
2899
2325
  useColumnOrderState,
2900
2326
  useColumnSizingState,
2901
2327
  useColumnSortState,
2328
+ useColumnVisibilityState,
2329
+ useFilterState,
2902
2330
  useGroupByState,
2903
2331
  usePaginationState,
2904
2332
  useSelectionState