@gridkitjs/react 0.9.0 → 0.11.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,20 @@
1
+ "use client";
2
+
1
3
  // src/DataGrid/DataGrid.tsx
2
4
  import {
3
- useEffect as useEffect4,
5
+ useEffect as useEffect5,
6
+ useId,
4
7
  useImperativeHandle,
5
8
  useMemo as useMemo4,
6
- useRef as useRef6,
7
- useState as useState6
9
+ useRef as useRef13,
10
+ useState as useState15
8
11
  } from "react";
9
12
  import {
10
- applyColumnOrder,
11
- computeAggregates,
12
- defineColumnsFromRows,
13
- fitColumnsToWidth,
14
- groupRows,
15
13
  moveColumnBefore,
16
- paginateRows as paginateRows2,
17
- resolveColumnWidths,
18
- resolveRowId,
19
- resolveShownRows,
20
- totalColumnWidth,
21
- withGroupAggregates
14
+ resolveColumnHeaderText,
15
+ resolveMessages,
16
+ resolveRows,
17
+ totalColumnWidth
22
18
  } from "@gridkitjs/core";
23
19
 
24
20
  // src/DataGrid/components/GridColgroup.tsx
@@ -29,7 +25,13 @@ function GridColgroup({ columns }) {
29
25
 
30
26
  // src/DataGrid/components/GridHeader.tsx
31
27
  import { createPortal } from "react-dom";
32
- import { buildKeyShortcuts, intentOf, KEYBOARD_STEP } from "@gridkitjs/core";
28
+ import {
29
+ buildKeyShortcuts,
30
+ intentOf,
31
+ KEYBOARD_STEP,
32
+ sortDirectionFor,
33
+ sortPriorityFor
34
+ } from "@gridkitjs/core";
33
35
 
34
36
  // src/DataGrid/ariaAttr.ts
35
37
  function ariaAttr(condition, key, value) {
@@ -41,30 +43,61 @@ function classNames(...values) {
41
43
  return values.filter(Boolean).join(" ");
42
44
  }
43
45
 
46
+ // src/DataGrid/icons.tsx
47
+ import { jsx as jsx2, jsxs } from "react/jsx-runtime";
48
+ function Icon({ children }) {
49
+ return /* @__PURE__ */ jsx2(
50
+ "svg",
51
+ {
52
+ viewBox: "0 0 24 24",
53
+ fill: "none",
54
+ stroke: "currentColor",
55
+ strokeWidth: 2,
56
+ strokeLinecap: "round",
57
+ strokeLinejoin: "round",
58
+ "aria-hidden": "true",
59
+ children
60
+ }
61
+ );
62
+ }
63
+ function ChevronsUpDownIcon() {
64
+ return /* @__PURE__ */ jsxs(Icon, { children: [
65
+ /* @__PURE__ */ jsx2("path", { d: "m7 15 5 5 5-5" }),
66
+ /* @__PURE__ */ jsx2("path", { d: "m7 9 5-5 5 5" })
67
+ ] });
68
+ }
69
+ function ChevronUpIcon() {
70
+ return /* @__PURE__ */ jsx2(Icon, { children: /* @__PURE__ */ jsx2("path", { d: "m18 15-6-6-6 6" }) });
71
+ }
72
+ function GroupIcon() {
73
+ return /* @__PURE__ */ jsxs(Icon, { children: [
74
+ /* @__PURE__ */ jsx2("rect", { x: "3", y: "3", width: "7", height: "7", rx: "1" }),
75
+ /* @__PURE__ */ jsx2("rect", { x: "14", y: "3", width: "7", height: "7", rx: "1" }),
76
+ /* @__PURE__ */ jsx2("rect", { x: "8.5", y: "14", width: "7", height: "7", rx: "1" })
77
+ ] });
78
+ }
79
+
80
+ // src/DataGrid/isOwnEvent.ts
81
+ function isOwnEvent(event) {
82
+ return event.target instanceof Node && event.currentTarget.contains(event.target);
83
+ }
84
+
44
85
  // src/DataGrid/useGridNavigation.ts
45
86
  import {
46
87
  useEffect,
47
- useMemo,
48
- useRef,
49
- useState
88
+ useLayoutEffect,
89
+ useState,
90
+ useSyncExternalStore
50
91
  } from "react";
92
+ import { HEADER_ROW } from "@gridkitjs/core";
51
93
  import {
52
- clampFocus,
53
- nextFocusForKey,
54
- HEADER_ROW
55
- } from "@gridkitjs/core";
56
- var FALLBACK_PAGE = 10;
94
+ createGridFocus,
95
+ findBodyRow,
96
+ measurePageSize
97
+ } from "@gridkitjs/dom";
57
98
  function tabIndexFor(columnIndex, focusedColumnIndex) {
58
99
  return columnIndex === focusedColumnIndex ? 0 : -1;
59
100
  }
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
101
  function useGridNavigation({
69
102
  headerTableRef,
70
103
  bodyTableRef,
@@ -73,120 +106,52 @@ function useGridNavigation({
73
106
  isSkippableRow,
74
107
  virtualRange
75
108
  }) {
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]
83
- );
84
- const pending = useRef(false);
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,
109
+ const options = {
110
+ rowCount,
111
+ columnCount,
112
+ isSkippableRow,
108
113
  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
- }
114
+ };
115
+ const [gridFocus] = useState(() => createGridFocus(options));
116
+ gridFocus.setOptions(options);
117
+ const focus = useSyncExternalStore(gridFocus.subscribe, gridFocus.getFocus);
118
+ const [rows] = useState(() => ({
119
+ getHeaderRow: () => headerTableRef.current?.tHead?.rows[0] ?? null,
120
+ getBodyRow: (rowIndex) => findBodyRow(bodyTableRef.current, rowIndex)
121
+ }));
122
+ useLayoutEffect(() => gridFocus.attach(rows), [gridFocus, rows]);
123
+ useEffect(() => {
124
+ gridFocus.focusDrawnCell(focus);
125
+ });
151
126
  return {
152
127
  focus,
153
128
  tabIndexFor: (rowIndex, columnIndex) => tabIndexFor(
154
129
  columnIndex,
155
130
  rowIndex === focus.rowIndex ? focus.columnIndex : null
156
131
  ),
157
- focusCell,
158
- onKeyDown
132
+ focusCell: gridFocus.focusCell,
133
+ focusCellNow: (rowIndex, columnIndex) => {
134
+ gridFocus.focusCell(rowIndex, columnIndex);
135
+ gridFocus.focusDrawnCell(gridFocus.getFocus());
136
+ },
137
+ focusTabStop: () => {
138
+ const cells = (focus.rowIndex === HEADER_ROW ? rows.getHeaderRow() : rows.getBodyRow(focus.rowIndex))?.cells;
139
+ cells?.[Math.min(focus.columnIndex, cells.length - 1)]?.focus();
140
+ },
141
+ onKeyDown: (event) => {
142
+ if (gridFocus.handleKey(
143
+ event.key,
144
+ event,
145
+ measurePageSize(bodyTableRef.current)
146
+ )) {
147
+ event.preventDefault();
148
+ }
149
+ }
159
150
  };
160
151
  }
161
152
 
162
153
  // 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
154
+ import { useMemo } from "react";
190
155
  function keyboardSelectIntent(event) {
191
156
  return {
192
157
  ctrlKey: event.key === " " || event.ctrlKey,
@@ -194,238 +159,55 @@ function keyboardSelectIntent(event) {
194
159
  shiftKey: event.shiftKey
195
160
  };
196
161
  }
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
162
  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
163
+ controller,
164
+ state,
165
+ selectable
228
166
  }) {
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
- }
167
+ const selectedRowIds = useMemo(
168
+ () => new Set(state.rowSelection),
169
+ [state.rowSelection]
170
+ );
171
+ const selectedColumnIds = useMemo(
172
+ () => new Set(state.columnSelection),
173
+ [state.columnSelection]
174
+ );
365
175
  return {
366
- rowMode,
367
- columnMode,
368
- cellMode,
176
+ rowMode: selectable?.rows ?? false,
177
+ columnMode: selectable?.columns ?? false,
178
+ cellMode: selectable?.cells ?? false,
179
+ toggleOnClick: selectable?.toggleOnClick ?? false,
369
180
  selectedRowIds,
370
181
  selectedColumnIds,
371
- selectedCell: cellSelection,
372
- selectRow,
373
- selectColumn,
374
- selectCell: selectCellAt,
375
- selectAllRows,
376
- clear
182
+ selectedCell: state.cellSelection,
183
+ selectRow: controller.selectRow,
184
+ selectColumn: controller.selectColumn,
185
+ selectCell: controller.selectCell,
186
+ selectAllRows: controller.selectAllRows,
187
+ clear: controller.clearSelection
377
188
  };
378
189
  }
379
190
 
380
191
  // src/DataGrid/components/GridHeader.tsx
381
- import { jsx as jsx2, jsxs } from "react/jsx-runtime";
382
- function Icon({ children }) {
383
- return /* @__PURE__ */ jsx2(
384
- "svg",
385
- {
386
- viewBox: "0 0 24 24",
387
- fill: "none",
388
- stroke: "currentColor",
389
- strokeWidth: 2,
390
- strokeLinecap: "round",
391
- strokeLinejoin: "round",
392
- "aria-hidden": "true",
393
- children
394
- }
395
- );
396
- }
397
- function ChevronsUpDownIcon() {
398
- return /* @__PURE__ */ jsxs(Icon, { children: [
399
- /* @__PURE__ */ jsx2("path", { d: "m7 15 5 5 5-5" }),
400
- /* @__PURE__ */ jsx2("path", { d: "m7 9 5-5 5 5" })
401
- ] });
402
- }
403
- function ChevronUpIcon() {
404
- return /* @__PURE__ */ jsx2(Icon, { children: /* @__PURE__ */ jsx2("path", { d: "m18 15-6-6-6 6" }) });
405
- }
406
- function GroupIcon() {
407
- return /* @__PURE__ */ jsxs(Icon, { children: [
408
- /* @__PURE__ */ jsx2("rect", { x: "3", y: "3", width: "7", height: "7", rx: "1" }),
409
- /* @__PURE__ */ jsx2("rect", { x: "14", y: "3", width: "7", height: "7", rx: "1" }),
410
- /* @__PURE__ */ jsx2("rect", { x: "8.5", y: "14", width: "7", height: "7", rx: "1" })
411
- ] });
412
- }
192
+ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
413
193
  function GridHeader({
414
194
  columns,
415
195
  resize,
416
196
  drag,
417
- sort,
197
+ controller,
198
+ columnSort,
418
199
  sortableColumns,
419
- grouping,
200
+ groupBy,
420
201
  groupableColumns,
421
202
  groupToggleIconColumns,
203
+ headerMenu,
422
204
  nav,
423
205
  selection
424
206
  }) {
425
207
  const beforeId = drag.dropTarget?.kind === "column-order" ? drag.dropTarget.beforeId : void 0;
426
208
  const draggedEntry = columns.find((entry) => entry.id === drag.draggedColumnId) ?? null;
427
- return /* @__PURE__ */ jsxs("thead", { children: [
428
- /* @__PURE__ */ jsx2("tr", { className: "grid-header", role: "row", "aria-rowindex": 1, children: columns.map((entry, index) => {
209
+ return /* @__PURE__ */ jsxs2("thead", { children: [
210
+ /* @__PURE__ */ jsx3("tr", { className: "grid-header", role: "row", "aria-rowindex": 1, children: columns.map((entry, index) => {
429
211
  const { column } = entry;
430
212
  const resizing = resize.activeColumnId === entry.id;
431
213
  const dragging = drag.draggedColumnId === entry.id;
@@ -433,20 +215,21 @@ function GridHeader({
433
215
  const dropAfter = beforeId === null && index === columns.length - 1;
434
216
  const selected = selection.selectedColumnIds.has(entry.id);
435
217
  const sortable = column.sortable ?? sortableColumns;
436
- const sortDirection = sort.directionFor(entry.id);
437
- const sortPriority = sort.priorityFor(entry.id);
218
+ const sortDirection = sortDirectionFor(columnSort, entry.id);
219
+ const sortPriority = sortPriorityFor(columnSort, entry.id);
438
220
  const groupable = column.groupable ?? groupableColumns;
439
221
  const showGroupToggleIcon = column.groupToggleIcon ?? groupToggleIconColumns;
440
- const isGrouped = grouping.groupBy.some(
222
+ const isGrouped = groupBy.some(
441
223
  (level) => level.columnId === entry.id
442
224
  );
443
225
  const shortcuts = buildKeyShortcuts({
444
226
  reorderable: entry.reorderable,
445
227
  resizable: entry.resizable,
446
228
  sortable,
447
- groupable
229
+ groupable,
230
+ contextMenu: headerMenu
448
231
  });
449
- return /* @__PURE__ */ jsxs(
232
+ return /* @__PURE__ */ jsxs2(
450
233
  "th",
451
234
  {
452
235
  scope: "col",
@@ -472,6 +255,7 @@ function GridHeader({
472
255
  selection.selectColumn(entry.id, intentOf(event));
473
256
  },
474
257
  onKeyDown: (event) => {
258
+ if (!isOwnEvent(event)) return;
475
259
  const horizontal = event.key === "ArrowLeft" || event.key === "ArrowRight";
476
260
  const direction = event.key === "ArrowLeft" ? -1 : 1;
477
261
  if (horizontal && event.ctrlKey && entry.reorderable) {
@@ -491,12 +275,14 @@ function GridHeader({
491
275
  }
492
276
  if (event.key === "ArrowUp" && event.altKey && sortable) {
493
277
  event.preventDefault();
494
- sort.toggle(entry, { shiftKey: event.shiftKey });
278
+ controller.toggleColumnSort(entry.id, {
279
+ stack: event.shiftKey
280
+ });
495
281
  return;
496
282
  }
497
283
  if (event.key === "ArrowDown" && event.altKey && groupable) {
498
284
  event.preventDefault();
499
- grouping.toggleGroupBy(entry.id);
285
+ controller.toggleGroupBy(entry.id);
500
286
  return;
501
287
  }
502
288
  if (event.key === " " || event.key === "Enter") {
@@ -545,7 +331,7 @@ function GridHeader({
545
331
  * A pointer affordance only, same as the other two — the
546
332
  * keyboard equivalent is Alt+ArrowDown on the header itself.
547
333
  */
548
- /* @__PURE__ */ jsx2(
334
+ /* @__PURE__ */ jsx3(
549
335
  "span",
550
336
  {
551
337
  className: classNames(
@@ -559,9 +345,9 @@ function GridHeader({
559
345
  },
560
346
  onClick: (event) => {
561
347
  event.stopPropagation();
562
- grouping.toggleGroupBy(entry.id);
348
+ controller.toggleGroupBy(entry.id);
563
349
  },
564
- children: /* @__PURE__ */ jsx2(GroupIcon, {})
350
+ children: /* @__PURE__ */ jsx3(GroupIcon, {})
565
351
  }
566
352
  ),
567
353
  entry.label,
@@ -571,7 +357,7 @@ function GridHeader({
571
357
  * lives on the header itself, under Alt+ArrowUp (plain) /
572
358
  * Alt+Shift+ArrowUp (stacking).
573
359
  */
574
- /* @__PURE__ */ jsxs(
360
+ /* @__PURE__ */ jsxs2(
575
361
  "span",
576
362
  {
577
363
  className: classNames(
@@ -584,11 +370,13 @@ function GridHeader({
584
370
  event.stopPropagation();
585
371
  },
586
372
  onClick: (event) => {
587
- sort.toggle(entry, { shiftKey: event.shiftKey });
373
+ controller.toggleColumnSort(entry.id, {
374
+ stack: event.shiftKey
375
+ });
588
376
  },
589
377
  children: [
590
- sortDirection === null ? /* @__PURE__ */ jsx2(ChevronsUpDownIcon, {}) : /* @__PURE__ */ jsx2(ChevronUpIcon, {}),
591
- sortPriority !== null && sortPriority > 1 && /* @__PURE__ */ jsx2("span", { className: "header-sort-priority", children: sortPriority })
378
+ sortDirection === null ? /* @__PURE__ */ jsx3(ChevronsUpDownIcon, {}) : /* @__PURE__ */ jsx3(ChevronUpIcon, {}),
379
+ sortPriority !== null && sortPriority > 1 && /* @__PURE__ */ jsx3("span", { className: "header-sort-priority", children: sortPriority })
592
380
  ]
593
381
  }
594
382
  ),
@@ -598,7 +386,7 @@ function GridHeader({
598
386
  * put N tab stops inside a widget that is meant to have one.
599
387
  * The keyboard resizes from the header itself, under Alt.
600
388
  */
601
- /* @__PURE__ */ jsx2(
389
+ /* @__PURE__ */ jsx3(
602
390
  "span",
603
391
  {
604
392
  className: "header-resize-handle",
@@ -618,7 +406,7 @@ function GridHeader({
618
406
  );
619
407
  }) }),
620
408
  draggedEntry !== null && drag.ghostTransform !== null && createPortal(
621
- /* @__PURE__ */ jsx2(
409
+ /* @__PURE__ */ jsx3(
622
410
  "div",
623
411
  {
624
412
  className: "gridkit-data-grid header-cell drag-ghost",
@@ -636,65 +424,82 @@ function GridHeader({
636
424
  }
637
425
 
638
426
  // src/DataGrid/components/GridBody.tsx
639
- import { useMemo as useMemo3 } from "react";
427
+ import {
428
+ useMemo as useMemo2,
429
+ useState as useState2
430
+ } from "react";
640
431
  import {
641
432
  groupRowId,
642
433
  intentOf as intentOf2
643
434
  } from "@gridkitjs/core";
435
+ import {
436
+ bodyCellFrom,
437
+ bodyRowFrom,
438
+ groupRowFrom
439
+ } from "@gridkitjs/dom";
440
+
441
+ // src/DataGrid/rowProps.ts
442
+ var NO_ATTRIBUTES = {};
443
+ var NO_ROW_PROPS = {
444
+ className: "",
445
+ attributes: NO_ATTRIBUTES
446
+ };
447
+ function sameAttributes(a, b) {
448
+ const keys = Object.keys(a);
449
+ return keys.length === Object.keys(b).length && keys.every((key) => a[key] === b[key]);
450
+ }
451
+ function createRowPropsResolver() {
452
+ let previous = /* @__PURE__ */ new Map();
453
+ return (rows, getRowProps) => {
454
+ if (getRowProps === void 0) {
455
+ previous = /* @__PURE__ */ new Map();
456
+ return () => NO_ROW_PROPS;
457
+ }
458
+ const resolved = /* @__PURE__ */ new Map();
459
+ const attributesById = /* @__PURE__ */ new Map();
460
+ for (const entry of rows) {
461
+ const { className = "", attributes = NO_ATTRIBUTES } = getRowProps(entry);
462
+ const last = previous.get(entry.rowId);
463
+ const stable = last !== void 0 && sameAttributes(last, attributes) ? last : attributes;
464
+ attributesById.set(entry.rowId, stable);
465
+ resolved.set(entry.rowId, { className, attributes: stable });
466
+ }
467
+ previous = attributesById;
468
+ return (rowId) => resolved.get(rowId) ?? NO_ROW_PROPS;
469
+ };
470
+ }
644
471
 
645
472
  // src/DataGrid/components/GridGroupRow.tsx
646
473
  import { memo } from "react";
474
+ import {
475
+ formatCellValue,
476
+ resolveColumnLabel
477
+ } from "@gridkitjs/core";
647
478
 
648
- // src/DataGrid/components/formatAggregateValue.ts
649
- function formatAggregateValue(value) {
479
+ // src/DataGrid/components/renderAggregate.ts
480
+ import {
481
+ formatAggregateValue
482
+ } from "@gridkitjs/core";
483
+ function renderAggregate(value, spec, column, rows, locale) {
484
+ if (column?.footerTemplate) {
485
+ return column.footerTemplate({ value, rows });
486
+ }
650
487
  if (value === null || value === void 0) {
651
488
  return "\u2014";
652
489
  }
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
- }
490
+ return formatAggregateValue(value, spec, column, locale);
669
491
  }
670
492
 
671
493
  // src/DataGrid/components/GridGroupRow.tsx
672
- 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
- }
494
+ import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
495
+ function formatGroupValue(value, column, locale, messages) {
496
+ return value === null || value === void 0 ? messages.blankGroupValue : formatCellValue(value, column, locale, void 0, messages);
692
497
  }
693
498
  function GridGroupRowComponent({
694
499
  columnCount,
695
500
  groupId,
696
501
  level,
697
- columnLabel,
502
+ groupColumn,
698
503
  value,
699
504
  expanded,
700
505
  count,
@@ -703,13 +508,15 @@ function GridGroupRowComponent({
703
508
  posinset,
704
509
  setsize,
705
510
  focused,
511
+ contextMenu,
706
512
  aggregates,
707
513
  results,
708
- columns,
514
+ definitions,
515
+ locale,
516
+ cellMessages,
709
517
  ref
710
518
  }) {
711
- const byId = new Map(columns.map((entry) => [entry.id, entry]));
712
- return /* @__PURE__ */ jsx3(
519
+ return /* @__PURE__ */ jsx4(
713
520
  "tr",
714
521
  {
715
522
  ref,
@@ -725,21 +532,21 @@ function GridGroupRowComponent({
725
532
  "grid-group-row",
726
533
  `is-group-level-${String(level)}`
727
534
  ),
728
- children: /* @__PURE__ */ jsx3(
535
+ children: /* @__PURE__ */ jsx4(
729
536
  "td",
730
537
  {
731
538
  role: "gridcell",
732
539
  colSpan: columnCount,
733
540
  tabIndex: focused ? 0 : -1,
734
- "aria-keyshortcuts": "Space Enter",
541
+ "aria-keyshortcuts": contextMenu ? "Space Enter Shift+F10" : "Space Enter",
735
542
  className: "grid-group-cell",
736
- children: /* @__PURE__ */ jsxs2(
543
+ children: /* @__PURE__ */ jsxs3(
737
544
  "div",
738
545
  {
739
546
  className: "grid-group-cell-content",
740
547
  style: { paddingInlineStart: `${String(level * 1.25 + 0.5)}rem` },
741
548
  children: [
742
- /* @__PURE__ */ jsx3(
549
+ /* @__PURE__ */ jsx4(
743
550
  "span",
744
551
  {
745
552
  className: classNames(
@@ -749,26 +556,30 @@ function GridGroupRowComponent({
749
556
  "aria-hidden": "true"
750
557
  }
751
558
  ),
752
- /* @__PURE__ */ jsxs2("span", { className: "group-label", children: [
753
- columnLabel,
754
- ": ",
755
- formatGroupValue(value)
559
+ /* @__PURE__ */ jsxs3("span", { className: "group-label", children: [
560
+ resolveColumnLabel(groupColumn),
561
+ ":",
562
+ " ",
563
+ formatGroupValue(value, groupColumn, locale, cellMessages)
756
564
  ] }),
757
- /* @__PURE__ */ jsxs2("span", { className: "group-count", children: [
565
+ /* @__PURE__ */ jsxs3("span", { className: "group-count", children: [
758
566
  "(",
759
567
  count,
760
568
  ")"
761
569
  ] }),
762
- aggregates.length > 0 && /* @__PURE__ */ jsx3("span", { className: "group-aggregates", children: aggregates.map((spec) => {
570
+ aggregates.length > 0 && /* @__PURE__ */ jsx4("span", { className: "group-aggregates", children: aggregates.map((spec) => {
763
571
  const key = spec.id ?? spec.columnId;
764
572
  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;
771
- return /* @__PURE__ */ jsxs2("span", { className: "group-aggregate", children: [
573
+ const column = definitions.get(spec.columnId);
574
+ const rendered = renderAggregate(
575
+ aggregateValue,
576
+ spec,
577
+ column,
578
+ [],
579
+ locale
580
+ );
581
+ const aggregateLabel = spec.id ?? (column === void 0 ? spec.columnId : resolveColumnLabel(column));
582
+ return /* @__PURE__ */ jsxs3("span", { className: "group-aggregate", children: [
772
583
  aggregateLabel,
773
584
  ": ",
774
585
  rendered
@@ -789,7 +600,7 @@ var GridGroupRow_default = GridGroupRow;
789
600
 
790
601
  // src/DataGrid/components/GridGroupSummaryRow.tsx
791
602
  import { memo as memo2 } from "react";
792
- import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
603
+ import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
793
604
  function GridGroupSummaryRowComponent({
794
605
  groupId,
795
606
  level,
@@ -798,9 +609,10 @@ function GridGroupSummaryRowComponent({
798
609
  results,
799
610
  aggregates,
800
611
  columns,
612
+ locale,
801
613
  ref
802
614
  }) {
803
- return /* @__PURE__ */ jsx4(
615
+ return /* @__PURE__ */ jsx5(
804
616
  "tr",
805
617
  {
806
618
  ref,
@@ -816,8 +628,7 @@ function GridGroupSummaryRowComponent({
816
628
  const specs = aggregates.filter(
817
629
  (candidate) => candidate.columnId === entry.id
818
630
  );
819
- const { column } = entry;
820
- return /* @__PURE__ */ jsxs3(
631
+ return /* @__PURE__ */ jsxs4(
821
632
  "td",
822
633
  {
823
634
  role: "gridcell",
@@ -825,7 +636,7 @@ function GridGroupSummaryRowComponent({
825
636
  className: "grid-group-summary-cell",
826
637
  style: { textAlign: specs[0]?.alignment ?? entry.alignment },
827
638
  children: [
828
- entry.id === columns[0]?.id && /* @__PURE__ */ jsx4(
639
+ entry.id === columns[0]?.id && /* @__PURE__ */ jsx5(
829
640
  "span",
830
641
  {
831
642
  className: "grid-group-summary-indent",
@@ -833,10 +644,15 @@ function GridGroupSummaryRowComponent({
833
644
  "aria-hidden": "true"
834
645
  }
835
646
  ),
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(
647
+ specs.map(
648
+ (spec) => renderAggregate(
649
+ results.get(spec.id ?? spec.columnId),
650
+ spec,
651
+ entry.column,
652
+ [],
653
+ locale
654
+ )
655
+ ).flatMap(
840
656
  (rendered, index) => index === 0 ? [rendered] : [", ", rendered]
841
657
  )
842
658
  ]
@@ -853,9 +669,12 @@ var GridGroupSummaryRow = memo2(
853
669
  var GridGroupSummaryRow_default = GridGroupSummaryRow;
854
670
 
855
671
  // src/DataGrid/components/GridRow.tsx
856
- import { accessDotted } from "@gridkitjs/core";
672
+ import {
673
+ accessDotted,
674
+ formatCellValue as formatCellValue2
675
+ } from "@gridkitjs/core";
857
676
  import { memo as memo3 } from "react";
858
- import { jsx as jsx5 } from "react/jsx-runtime";
677
+ import { jsx as jsx6 } from "react/jsx-runtime";
859
678
  function GridRowComponent({
860
679
  columns,
861
680
  rowId,
@@ -869,21 +688,36 @@ function GridRowComponent({
869
688
  focusedColumnIndex,
870
689
  rowsSelectable,
871
690
  cellsSelectable,
691
+ contextMenu,
692
+ locale,
693
+ cellMessages,
694
+ rowClassName,
695
+ rowAttributes,
872
696
  ref
873
697
  }) {
874
- return /* @__PURE__ */ jsx5(
698
+ const shortcuts = [
699
+ cellsSelectable ? "Space Enter" : "",
700
+ contextMenu ? "Shift+F10" : ""
701
+ ].filter(Boolean).join(" ");
702
+ return /* @__PURE__ */ jsx6(
875
703
  "tr",
876
704
  {
705
+ ...rowAttributes,
877
706
  ref,
878
707
  role: "row",
879
708
  "aria-rowindex": datasetIndex + 2,
880
709
  "data-gridkit-row-index": rowIndex,
710
+ "data-gridkit-row-id": rowId,
881
711
  ...ariaAttr(rowsSelectable, "aria-selected", selected),
882
- className: classNames("grid-row", selected ? "is-selected" : ""),
712
+ className: classNames(
713
+ "grid-row",
714
+ selected ? "is-selected" : "",
715
+ rowClassName
716
+ ),
883
717
  children: columns.map(({ id, column, alignment }, columnIndex) => {
884
718
  const value = accessDotted(row, column.field);
885
719
  const cellSelected = id === selectedColumnId;
886
- return /* @__PURE__ */ jsx5(
720
+ return /* @__PURE__ */ jsx6(
887
721
  "td",
888
722
  {
889
723
  role: "gridcell",
@@ -891,7 +725,7 @@ function GridRowComponent({
891
725
  "aria-colindex": columnIndex + 1,
892
726
  tabIndex: tabIndexFor(columnIndex, focusedColumnIndex),
893
727
  ...ariaAttr(cellsSelectable, "aria-selected", cellSelected),
894
- ...ariaAttr(cellsSelectable, "aria-keyshortcuts", "Space Enter"),
728
+ ...ariaAttr(shortcuts !== "", "aria-keyshortcuts", shortcuts),
895
729
  className: classNames(
896
730
  "grid-cell",
897
731
  id === activeColumnId ? "is-resizing" : "",
@@ -907,7 +741,7 @@ function GridRowComponent({
907
741
  datasetIndex,
908
742
  rowId,
909
743
  selected
910
- }) : value
744
+ }) : formatCellValue2(value, column, locale, row, cellMessages)
911
745
  },
912
746
  id
913
747
  );
@@ -919,40 +753,7 @@ var GridRow = memo3(GridRowComponent);
919
753
  var GridRow_default = GridRow;
920
754
 
921
755
  // src/DataGrid/components/GridBody.tsx
922
- import { Fragment, jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
923
- function rowIndexOf(row) {
924
- const attribute = row.getAttribute("data-gridkit-row-index");
925
- return attribute === null ? null : Number(attribute);
926
- }
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
- }
756
+ import { Fragment, jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
956
757
  function groupAriaMeta(rows) {
957
758
  const totals = /* @__PURE__ */ new Map();
958
759
  for (const entry of rows) {
@@ -976,22 +777,42 @@ function groupAriaMeta(rows) {
976
777
  }
977
778
  function GridBody({
978
779
  columns,
780
+ definitions,
979
781
  rows,
980
782
  activeColumnId,
981
783
  nav,
982
784
  selection,
983
- grouping,
785
+ controller,
984
786
  aggregates,
985
787
  groupAggregateDisplay,
986
788
  virtualRange,
987
789
  infiniteScroll,
988
- sentinelRef
790
+ sentinelRef,
791
+ locale,
792
+ getRowProps,
793
+ onRowClick,
794
+ onRowDoubleClick,
795
+ messages,
796
+ cellMenu,
797
+ groupMenu
989
798
  }) {
990
- const { selectedCell, rowMode, cellMode } = selection;
991
- const ariaMeta = useMemo3(() => groupAriaMeta(rows), [rows]);
799
+ const { selectedCell, rowMode, cellMode, toggleOnClick } = selection;
800
+ const ariaMeta = useMemo2(() => groupAriaMeta(rows), [rows]);
992
801
  const visibleRows = virtualRange === null ? rows : rows.slice(virtualRange.startIndex, virtualRange.endIndex + 1);
802
+ const { booleanTrue, booleanFalse, blankGroupValue } = messages;
803
+ const cellMessages = useMemo2(
804
+ () => ({ booleanTrue, booleanFalse, blankGroupValue }),
805
+ [booleanTrue, booleanFalse, blankGroupValue]
806
+ );
807
+ const [resolveRowProps] = useState2(() => createRowPropsResolver());
808
+ const rowPropsOf = resolveRowProps(
809
+ visibleRows.filter(
810
+ (entry) => !("kind" in entry)
811
+ ),
812
+ getRowProps
813
+ );
993
814
  function targetOf(target) {
994
- const position = cellFrom(target);
815
+ const position = bodyCellFrom(target);
995
816
  const entry = position === null ? void 0 : rows[position.rowIndex];
996
817
  if (position === null || entry === void 0 || "kind" in entry) {
997
818
  return null;
@@ -999,15 +820,39 @@ function GridBody({
999
820
  return { position, rowId: entry.rowId };
1000
821
  }
1001
822
  function select(rowId, columnId, modifiers) {
1002
- const intent = intentOf2(modifiers);
1003
823
  if (rowMode !== false) {
1004
- selection.selectRow(rowId, intent);
824
+ selection.selectRow(rowId, intentOf2(modifiers, { toggleOnClick }));
1005
825
  }
1006
826
  if (cellMode !== false) {
1007
- selection.selectCell({ rowId, columnId }, intent);
827
+ selection.selectCell({ rowId, columnId }, intentOf2(modifiers));
828
+ }
829
+ }
830
+ function rowPointerEventOf(event) {
831
+ const target = bodyRowFrom(event.target);
832
+ const entry = target === null ? void 0 : rows[target.rowIndex];
833
+ if (target === null || entry === void 0 || "kind" in entry) {
834
+ return null;
835
+ }
836
+ return {
837
+ row: entry,
838
+ columnId: target.columnId,
839
+ native: {
840
+ ctrlKey: event.ctrlKey,
841
+ metaKey: event.metaKey,
842
+ shiftKey: event.shiftKey,
843
+ altKey: event.altKey,
844
+ button: event.button
845
+ }
846
+ };
847
+ }
848
+ function reportRowPointer(event, callback) {
849
+ if (callback === void 0) return;
850
+ const pointer = rowPointerEventOf(event);
851
+ if (pointer !== null) {
852
+ callback(pointer);
1008
853
  }
1009
854
  }
1010
- return /* @__PURE__ */ jsxs4(
855
+ return /* @__PURE__ */ jsxs5(
1011
856
  "tbody",
1012
857
  {
1013
858
  className: "grid-body",
@@ -1024,14 +869,20 @@ function GridBody({
1024
869
  onClick: (event) => {
1025
870
  const group = groupRowFrom(event.target);
1026
871
  if (group !== null) {
1027
- grouping.toggleExpansion(group.groupId);
872
+ controller.toggleGroupExpansion(group.groupId);
1028
873
  return;
1029
874
  }
1030
875
  const found = targetOf(event.target);
1031
- if (found === null) return;
1032
- select(found.rowId, found.position.columnId, event);
876
+ if (found !== null) {
877
+ select(found.rowId, found.position.columnId, event);
878
+ }
879
+ reportRowPointer(event, onRowClick);
880
+ },
881
+ onDoubleClick: (event) => {
882
+ reportRowPointer(event, onRowDoubleClick);
1033
883
  },
1034
884
  onKeyDown: (event) => {
885
+ if (!isOwnEvent(event)) return;
1035
886
  const group = groupRowFrom(event.target);
1036
887
  if (group !== null) {
1037
888
  if (event.key === "ArrowLeft" || event.key === "ArrowRight") {
@@ -1040,7 +891,7 @@ function GridBody({
1040
891
  }
1041
892
  if (event.key === " " || event.key === "Enter") {
1042
893
  event.preventDefault();
1043
- grouping.toggleExpansion(group.groupId);
894
+ controller.toggleGroupExpansion(group.groupId);
1044
895
  return;
1045
896
  }
1046
897
  nav.onKeyDown(event);
@@ -1061,12 +912,12 @@ function GridBody({
1061
912
  },
1062
913
  children: [
1063
914
  virtualRange !== null && virtualRange.offsetBefore > 0 && // `aria-hidden` and carrying neither `data-gridkit-column` nor
1064
- // `data-gridkit-group`/`data-gridkit-row-index`, so `cellFrom`/
915
+ // `data-gridkit-group`/`data-gridkit-row-index`, so `bodyCellFrom`/
1065
916
  // `groupRowFrom` never mistake it for a real row — it exists purely
1066
917
  // to give the browser's scrollbar the correct total height and thumb
1067
918
  // position, the same way `GridFooter`'s `<tfoot>` isn't a "row" to
1068
919
  // any of this grid's own row-counting logic.
1069
- /* @__PURE__ */ jsx6("tr", { "aria-hidden": "true", children: /* @__PURE__ */ jsx6(
920
+ /* @__PURE__ */ jsx7("tr", { "aria-hidden": "true", children: /* @__PURE__ */ jsx7(
1070
921
  "td",
1071
922
  {
1072
923
  style: {
@@ -1078,7 +929,8 @@ function GridBody({
1078
929
  ) }),
1079
930
  visibleRows.map((entry) => {
1080
931
  if (!("kind" in entry)) {
1081
- return /* @__PURE__ */ jsx6(
932
+ const rowProps = rowPropsOf(entry.rowId);
933
+ return /* @__PURE__ */ jsx7(
1082
934
  GridRow_default,
1083
935
  {
1084
936
  ref: virtualRange?.measureRow(entry.rowIndex),
@@ -1093,13 +945,18 @@ function GridBody({
1093
945
  selectedColumnId: selectedCell?.rowId === entry.rowId ? selectedCell.columnId : null,
1094
946
  focusedColumnIndex: nav.focus.rowIndex === entry.rowIndex ? nav.focus.columnIndex : null,
1095
947
  rowsSelectable: rowMode !== false,
1096
- cellsSelectable: cellMode !== false
948
+ cellsSelectable: cellMode !== false,
949
+ contextMenu: cellMenu,
950
+ locale,
951
+ cellMessages,
952
+ rowClassName: rowProps.className,
953
+ rowAttributes: rowProps.attributes
1097
954
  },
1098
955
  entry.rowId
1099
956
  );
1100
957
  }
1101
958
  if (entry.kind === "group-summary") {
1102
- return /* @__PURE__ */ jsx6(
959
+ return /* @__PURE__ */ jsx7(
1103
960
  GridGroupSummaryRow_default,
1104
961
  {
1105
962
  ref: virtualRange?.measureRow(entry.rowIndex),
@@ -1109,19 +966,20 @@ function GridBody({
1109
966
  datasetIndex: entry.datasetIndex,
1110
967
  results: entry.aggregates,
1111
968
  aggregates,
1112
- columns
969
+ columns,
970
+ locale
1113
971
  },
1114
972
  `${entry.groupId}-summary`
1115
973
  );
1116
974
  }
1117
- return /* @__PURE__ */ jsx6(
975
+ return /* @__PURE__ */ jsx7(
1118
976
  GridGroupRow_default,
1119
977
  {
1120
978
  ref: virtualRange?.measureRow(entry.rowIndex),
1121
979
  columnCount: columns.length,
1122
980
  groupId: entry.groupId,
1123
981
  level: entry.level,
1124
- columnLabel: columns.find((column) => column.id === entry.columnId)?.label ?? entry.columnId,
982
+ groupColumn: definitions.get(entry.columnId) ?? { field: entry.columnId },
1125
983
  value: entry.value,
1126
984
  expanded: entry.expanded,
1127
985
  count: entry.count,
@@ -1131,13 +989,16 @@ function GridBody({
1131
989
  setsize: ariaMeta.get(entry.groupId)?.setsize ?? 1,
1132
990
  aggregates: groupAggregateDisplay === "inline" ? aggregates : [],
1133
991
  results: entry.aggregates,
1134
- columns,
1135
- focused: nav.focus.rowIndex === entry.rowIndex
992
+ definitions,
993
+ locale,
994
+ cellMessages,
995
+ focused: nav.focus.rowIndex === entry.rowIndex,
996
+ contextMenu: groupMenu
1136
997
  },
1137
998
  entry.groupId
1138
999
  );
1139
1000
  }),
1140
- virtualRange !== null && virtualRange.offsetAfter > 0 && /* @__PURE__ */ jsx6("tr", { "aria-hidden": "true", children: /* @__PURE__ */ jsx6(
1001
+ virtualRange !== null && virtualRange.offsetAfter > 0 && /* @__PURE__ */ jsx7("tr", { "aria-hidden": "true", children: /* @__PURE__ */ jsx7(
1141
1002
  "td",
1142
1003
  {
1143
1004
  style: {
@@ -1147,15 +1008,15 @@ function GridBody({
1147
1008
  }
1148
1009
  }
1149
1010
  ) }),
1150
- infiniteScroll?.hasMore && /* @__PURE__ */ jsxs4(Fragment, { children: [
1151
- infiniteScroll.isLoadingMore === true && /* @__PURE__ */ jsx6("tr", { "aria-hidden": "true", className: "grid-loading-row", children: /* @__PURE__ */ jsx6("td", { colSpan: columns.length, className: "grid-loading-cell", children: infiniteScroll.loadingTemplate ? infiniteScroll.loadingTemplate() : "Loading more\u2026" }) }),
1152
- /* @__PURE__ */ jsx6(
1011
+ infiniteScroll?.hasMore && /* @__PURE__ */ jsxs5(Fragment, { children: [
1012
+ infiniteScroll.isLoadingMore === true && /* @__PURE__ */ jsx7("tr", { "aria-hidden": "true", className: "grid-loading-row", children: /* @__PURE__ */ jsx7("td", { colSpan: columns.length, className: "grid-loading-cell", children: infiniteScroll.loadingTemplate ? infiniteScroll.loadingTemplate() : messages.loadingMore }) }),
1013
+ /* @__PURE__ */ jsx7(
1153
1014
  "tr",
1154
1015
  {
1155
1016
  "aria-hidden": "true",
1156
1017
  ref: sentinelRef,
1157
1018
  className: "grid-sentinel-row",
1158
- children: /* @__PURE__ */ jsx6(
1019
+ children: /* @__PURE__ */ jsx7(
1159
1020
  "td",
1160
1021
  {
1161
1022
  colSpan: columns.length,
@@ -1171,29 +1032,34 @@ function GridBody({
1171
1032
  }
1172
1033
 
1173
1034
  // src/DataGrid/components/GridFooter.tsx
1174
- import { jsx as jsx7 } from "react/jsx-runtime";
1035
+ import { jsx as jsx8 } from "react/jsx-runtime";
1175
1036
  function GridFooter({
1176
1037
  columns,
1177
1038
  aggregates,
1178
1039
  results,
1179
- rows
1040
+ rows,
1041
+ locale
1180
1042
  }) {
1181
- return /* @__PURE__ */ jsx7("tfoot", { children: /* @__PURE__ */ jsx7("tr", { className: "grid-footer", role: "row", children: columns.map((entry) => {
1043
+ return /* @__PURE__ */ jsx8("tfoot", { children: /* @__PURE__ */ jsx8("tr", { className: "grid-footer", role: "row", children: columns.map((entry) => {
1182
1044
  const specs = aggregates.filter(
1183
1045
  (candidate) => candidate.columnId === entry.id
1184
1046
  );
1185
- const { column } = entry;
1186
- return /* @__PURE__ */ jsx7(
1047
+ return /* @__PURE__ */ jsx8(
1187
1048
  "td",
1188
1049
  {
1189
1050
  role: "gridcell",
1190
1051
  "data-gridkit-column": entry.id,
1191
1052
  className: "grid-footer-cell",
1192
1053
  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(
1054
+ children: specs.map(
1055
+ (spec) => renderAggregate(
1056
+ results.get(spec.id ?? spec.columnId),
1057
+ spec,
1058
+ entry.column,
1059
+ rows,
1060
+ locale
1061
+ )
1062
+ ).flatMap(
1197
1063
  (rendered, index) => index === 0 ? [rendered] : [", ", rendered]
1198
1064
  )
1199
1065
  },
@@ -1204,223 +1070,140 @@ function GridFooter({
1204
1070
 
1205
1071
  // src/DataGrid/components/GridPager.tsx
1206
1072
  import { paginationWindow } from "@gridkitjs/core";
1207
- import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
1208
- function GridPager({ pager, config }) {
1073
+ import { jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
1074
+ function GridPager({ pager, config, messages }) {
1209
1075
  const { pagination, pageCount, currentPage } = pager;
1210
1076
  const variant = config?.variant ?? "compact";
1211
1077
  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
- ] });
1078
+ return /* @__PURE__ */ jsxs6(
1079
+ "div",
1080
+ {
1081
+ className: "gridkit-grid-pager",
1082
+ role: "group",
1083
+ "aria-label": messages.pagerLabel,
1084
+ children: [
1085
+ /* @__PURE__ */ jsx9(
1086
+ "button",
1087
+ {
1088
+ type: "button",
1089
+ className: "grid-pager-button",
1090
+ disabled: pagination.pageIndex <= 0,
1091
+ onClick: () => {
1092
+ pager.previousPage();
1093
+ },
1094
+ children: messages.previousPage
1095
+ }
1096
+ ),
1097
+ variant === "numbered" ? paginationWindow(currentPage, pageCount, {
1098
+ boundaryCount: config?.boundaryCount,
1099
+ siblingCount: config?.siblingCount
1100
+ }).map(
1101
+ (entry, index) => entry === "ellipsis" ? /* @__PURE__ */ jsx9(
1102
+ "span",
1103
+ {
1104
+ className: "grid-pager-ellipsis",
1105
+ "aria-hidden": "true",
1106
+ children: "\u2026"
1107
+ },
1108
+ `ellipsis-${String(index)}`
1109
+ ) : /* @__PURE__ */ jsx9(
1110
+ "button",
1111
+ {
1112
+ type: "button",
1113
+ className: "grid-pager-button",
1114
+ "aria-label": messages.pageButtonLabel(entry),
1115
+ "aria-current": entry === currentPage ? "page" : void 0,
1116
+ onClick: () => {
1117
+ pager.goToPage(entry - 1);
1118
+ },
1119
+ children: entry
1120
+ },
1121
+ entry
1122
+ )
1123
+ ) : /* @__PURE__ */ jsx9("span", { className: "grid-pager-status", children: messages.pageStatus(pageCount === 0 ? 0 : currentPage, pageCount) }),
1124
+ /* @__PURE__ */ jsx9(
1125
+ "button",
1126
+ {
1127
+ type: "button",
1128
+ className: "grid-pager-button",
1129
+ disabled: pagination.pageIndex >= pageCount - 1,
1130
+ onClick: () => {
1131
+ pager.nextPage();
1132
+ },
1133
+ children: messages.nextPage
1134
+ }
1135
+ ),
1136
+ sizeOptions !== void 0 && sizeOptions.length > 0 && /* @__PURE__ */ jsxs6("label", { className: "grid-pager-page-size", children: [
1137
+ messages.pageSizeLabel,
1138
+ /* @__PURE__ */ jsx9(
1139
+ "select",
1140
+ {
1141
+ value: pagination.pageSize,
1142
+ onChange: (event) => {
1143
+ pager.setPageSize(Number(event.target.value));
1144
+ },
1145
+ children: sizeOptions.map((size) => /* @__PURE__ */ jsx9("option", { value: size, children: size }, size))
1146
+ }
1147
+ )
1148
+ ] })
1149
+ ]
1150
+ }
1151
+ );
1283
1152
  }
1284
1153
 
1285
1154
  // src/DataGrid/components/GroupByBar.tsx
1286
1155
  import { createPortal as createPortal2 } from "react-dom";
1287
- import { resolveKeyboardDropTarget } from "@gridkitjs/core";
1288
-
1289
- // src/DataGrid/groupByDropTarget.ts
1290
1156
  import {
1291
- movesGroupBy,
1292
- resolveDropBefore
1157
+ resolveColumnLabel as resolveColumnLabel2,
1158
+ resolveKeyboardDropTarget
1293
1159
  } from "@gridkitjs/core";
1294
1160
 
1295
- // src/DataGrid/useDragReorder.ts
1161
+ // src/DataGrid/useGroupByDrag.ts
1296
1162
  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
- }
1163
+ resolveGroupByDropTarget
1164
+ } from "@gridkitjs/dom";
1334
1165
 
1335
1166
  // 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
- }
1167
+ import {
1168
+ useRef,
1169
+ useState as useState3
1170
+ } from "react";
1171
+ import {
1172
+ NO_DRAG,
1173
+ startDragReorder
1174
+ } from "@gridkitjs/dom";
1344
1175
  function useDragReorder({
1345
1176
  resolveTarget,
1346
1177
  onDrop
1347
1178
  }) {
1348
- const [draggedId, setDraggedId] = useState2(null);
1349
- const [dropTarget, setDropTarget] = useState2(null);
1350
- const [ghostTransform, setGhostTransform] = useState2(null);
1351
- const dragged = useRef3(false);
1179
+ const [drag, setDrag] = useState3(NO_DRAG);
1180
+ const dragged = useRef(false);
1352
1181
  function justDragged() {
1353
1182
  const answer = dragged.current;
1354
1183
  dragged.current = false;
1355
1184
  return answer;
1356
1185
  }
1357
1186
  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;
1187
+ startDragReorder(id, grabElement, event, {
1188
+ resolveTarget,
1189
+ onState(state) {
1190
+ if (state.draggedId !== null) {
1378
1191
  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
1192
  }
1193
+ setDrag(state);
1391
1194
  },
1392
- // Escape and a cancelled pointer both leave the order as it was.
1393
- onCancel() {
1394
- target = null;
1395
- reset();
1396
- }
1195
+ onDrop
1397
1196
  });
1398
1197
  }
1399
1198
  return {
1400
- draggedId,
1401
- dropTarget,
1402
- ghostTransform,
1199
+ draggedId: drag.draggedId,
1200
+ dropTarget: drag.dropTarget,
1201
+ ghostTransform: drag.ghostTransform,
1403
1202
  startDrag,
1404
1203
  justDragged
1405
1204
  };
1406
1205
  }
1407
1206
 
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
1207
  // src/DataGrid/useGroupByDrag.ts
1425
1208
  function useGroupByDrag({
1426
1209
  groupBy,
@@ -1444,28 +1227,31 @@ function useGroupByDrag({
1444
1227
  }
1445
1228
 
1446
1229
  // src/DataGrid/components/GroupByBar.tsx
1447
- import { jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
1230
+ import { jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
1448
1231
  function GroupByBar({
1449
1232
  columns,
1450
- grouping,
1233
+ definitions,
1234
+ groupBy,
1235
+ controller,
1451
1236
  columnName,
1452
1237
  headerDragEligible,
1453
1238
  headerDragBlocked,
1454
- headerDropTarget
1239
+ headerDropTarget,
1240
+ messages
1455
1241
  }) {
1456
- const { groupBy } = grouping;
1457
1242
  const groupByIds = groupBy.map((entry) => entry.columnId);
1458
1243
  const chipDrag = useGroupByDrag({
1459
1244
  groupBy,
1460
1245
  onDrop: (movedId, beforeColumnId) => {
1461
- grouping.moveGroupBy(movedId, beforeColumnId);
1246
+ controller.moveGroupBy(movedId, beforeColumnId);
1462
1247
  }
1463
1248
  });
1464
1249
  const activeDropTarget = chipDrag.dropTarget ?? headerDropTarget;
1465
1250
  const isDragTarget = chipDrag.draggedColumnId !== null || headerDragEligible;
1466
1251
  const showPlaceholder = groupBy.length === 0 && columns.some((entry) => entry.groupByDraggable);
1467
1252
  function labelFor(columnId) {
1468
- return columns.find((entry) => entry.id === columnId)?.label ?? columnId;
1253
+ const definition = definitions.get(columnId);
1254
+ return definition === void 0 ? columnId : resolveColumnLabel2(definition);
1469
1255
  }
1470
1256
  function moveByKeyboard(columnId, direction) {
1471
1257
  const beforeColumnId = resolveKeyboardDropTarget(
@@ -1476,9 +1262,9 @@ function GroupByBar({
1476
1262
  if (beforeColumnId === void 0) {
1477
1263
  return;
1478
1264
  }
1479
- grouping.moveGroupBy(columnId, beforeColumnId);
1265
+ controller.moveGroupBy(columnId, beforeColumnId);
1480
1266
  }
1481
- return /* @__PURE__ */ jsxs6(
1267
+ return /* @__PURE__ */ jsxs7(
1482
1268
  "div",
1483
1269
  {
1484
1270
  className: classNames(
@@ -1488,14 +1274,14 @@ function GroupByBar({
1488
1274
  ),
1489
1275
  "data-gridkit-group-by-bar": "",
1490
1276
  role: "group",
1491
- "aria-label": "Grouped by",
1277
+ "aria-label": messages.groupByBarLabel,
1492
1278
  children: [
1493
- showPlaceholder && /* @__PURE__ */ jsx9("span", { className: "group-by-bar-placeholder", children: "Drag a column here to group by it" }),
1279
+ showPlaceholder && /* @__PURE__ */ jsx10("span", { className: "group-by-bar-placeholder", children: messages.groupByBarPlaceholder }),
1494
1280
  groupBy.map((entry, index) => {
1495
1281
  const dropBefore = activeDropTarget?.beforeColumnId === entry.columnId;
1496
1282
  const dropAfter = activeDropTarget?.beforeColumnId === null && index === groupBy.length - 1;
1497
1283
  const dragging = chipDrag.draggedColumnId === entry.columnId;
1498
- return /* @__PURE__ */ jsxs6(
1284
+ return /* @__PURE__ */ jsxs7(
1499
1285
  "span",
1500
1286
  {
1501
1287
  className: classNames(
@@ -1506,7 +1292,11 @@ function GroupByBar({
1506
1292
  ),
1507
1293
  "data-gridkit-group-chip": entry.columnId,
1508
1294
  tabIndex: 0,
1509
- "aria-label": groupBy.length > 1 ? `${columnName(entry.columnId)}, level ${String(index + 1)} of ${String(groupBy.length)}` : columnName(entry.columnId),
1295
+ "aria-label": messages.groupByChipLabel(
1296
+ columnName(entry.columnId),
1297
+ index + 1,
1298
+ groupBy.length
1299
+ ),
1510
1300
  "aria-keyshortcuts": "Control+ArrowLeft Control+ArrowRight",
1511
1301
  onPointerDown: (event) => {
1512
1302
  chipDrag.startDrag(entry.columnId, event);
@@ -1522,18 +1312,18 @@ function GroupByBar({
1522
1312
  }
1523
1313
  },
1524
1314
  children: [
1525
- /* @__PURE__ */ jsx9("span", { className: "group-by-chip-label", children: labelFor(entry.columnId) }),
1526
- /* @__PURE__ */ jsx9(
1315
+ /* @__PURE__ */ jsx10("span", { className: "group-by-chip-label", children: labelFor(entry.columnId) }),
1316
+ /* @__PURE__ */ jsx10(
1527
1317
  "button",
1528
1318
  {
1529
1319
  type: "button",
1530
1320
  className: "group-by-chip-remove",
1531
- "aria-label": `Stop grouping by ${columnName(entry.columnId)}`,
1321
+ "aria-label": messages.removeGroupBy(columnName(entry.columnId)),
1532
1322
  onPointerDown: (event) => {
1533
1323
  event.stopPropagation();
1534
1324
  },
1535
1325
  onClick: () => {
1536
- grouping.toggleGroupBy(entry.columnId);
1326
+ controller.toggleGroupBy(entry.columnId);
1537
1327
  },
1538
1328
  children: "\xD7"
1539
1329
  }
@@ -1544,7 +1334,7 @@ function GroupByBar({
1544
1334
  );
1545
1335
  }),
1546
1336
  chipDrag.draggedColumnId !== null && chipDrag.ghostTransform !== null && createPortal2(
1547
- /* @__PURE__ */ jsx9(
1337
+ /* @__PURE__ */ jsx10(
1548
1338
  "span",
1549
1339
  {
1550
1340
  className: "group-by-chip drag-ghost",
@@ -1560,167 +1350,1553 @@ function GroupByBar({
1560
1350
  );
1561
1351
  }
1562
1352
 
1563
- // src/DataGrid/useColumnDrag.ts
1353
+ // src/DataGrid/announcedCallbacks.ts
1564
1354
  import {
1565
- movesColumn,
1566
- resolveDropBefore as resolveDropBefore2,
1567
- resolveKeyboardDropTarget as resolveKeyboardDropTarget2
1355
+ isColumnVisible
1568
1356
  } from "@gridkitjs/core";
1569
- function useColumnDrag({
1570
- order,
1571
- groupBy,
1572
- groupByDraggableIds,
1573
- onDrop
1357
+ function announcedCallbacks({
1358
+ callbacks,
1359
+ announce,
1360
+ messages,
1361
+ columnName,
1362
+ columnOf
1574
1363
  }) {
1575
- 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;
1364
+ return {
1365
+ /**
1366
+ * Announced only once the drag ends: the controller reports the
1367
+ * continuous `"move"` phase as well, and a live region given every frame
1368
+ * of a drag says nothing an assistive technology can keep up with.
1369
+ */
1370
+ onColumnResize: (event) => {
1371
+ callbacks.onColumnResize?.(event);
1372
+ if (event.phase === "end") {
1373
+ announce(
1374
+ messages.columnResized(
1375
+ columnName(event.columnId),
1376
+ Math.round(event.width)
1377
+ )
1378
+ );
1379
+ }
1380
+ },
1381
+ /**
1382
+ * Naming a column shown or hidden alone, and counting several — "Show
1383
+ * all" in the column chooser, or `hideColumns` given a list. A change
1384
+ * that shows some and hides others, as restoring a layout can, has no
1385
+ * one thing to say, and says nothing.
1386
+ */
1387
+ onColumnVisibilityChange: (event) => {
1388
+ callbacks.onColumnVisibilityChange?.(event);
1389
+ const { columnIds, visibility } = event;
1390
+ const [columnId] = columnIds;
1391
+ const shownCount = columnIds.filter((id) => {
1392
+ const column = columnOf(id);
1393
+ return column !== void 0 && isColumnVisible(column, visibility);
1394
+ }).length;
1395
+ if (columnId === void 0 || shownCount !== 0 && shownCount !== columnIds.length) {
1396
+ return;
1397
+ }
1398
+ const shown = shownCount > 0;
1399
+ if (columnIds.length > 1) {
1400
+ announce(
1401
+ shown ? messages.columnsShown(columnIds.length) : messages.columnsHidden(columnIds.length)
1402
+ );
1403
+ return;
1582
1404
  }
1583
- const alreadyGrouped = groupBy.some(
1584
- (entry) => entry.columnId === movedId
1405
+ announce(
1406
+ shown ? messages.columnShown(columnName(columnId)) : messages.columnHidden(columnName(columnId))
1585
1407
  );
1586
- return groupByDraggableIds.has(movedId) && !alreadyGrouped ? resolveGroupByDropTarget(clientX, clientY, movedId, groupBy) : null;
1587
1408
  },
1588
- onDrop
1589
- });
1590
- function startDrag(entry, event) {
1591
- drag.startDrag(entry.id, event.currentTarget, event);
1592
- }
1593
- function moveByKeyboard(entry, direction) {
1594
- const beforeId = resolveKeyboardDropTarget2(order, entry.id, direction);
1595
- if (beforeId === void 0) {
1596
- return;
1409
+ /** A toggle, a stack, or a clear back to "none" each get their own. */
1410
+ onColumnSortChange: (event) => {
1411
+ callbacks.onColumnSortChange?.(event);
1412
+ const entry = event.sort.find(
1413
+ (candidate) => candidate.columnId === event.columnId
1414
+ );
1415
+ if (entry === void 0) {
1416
+ announce(messages.sortCleared(columnName(event.columnId)));
1417
+ return;
1418
+ }
1419
+ announce(
1420
+ messages.columnSorted(
1421
+ columnName(event.columnId),
1422
+ entry.direction,
1423
+ event.sort.indexOf(entry) + 1,
1424
+ event.sort.length
1425
+ )
1426
+ );
1427
+ },
1428
+ onGroupByChange: (event) => {
1429
+ callbacks.onGroupByChange?.(event);
1430
+ const entry = event.groupBy.find(
1431
+ (candidate) => candidate.columnId === event.columnId
1432
+ );
1433
+ if (entry === void 0) {
1434
+ announce(messages.groupingRemoved(columnName(event.columnId)));
1435
+ return;
1436
+ }
1437
+ announce(
1438
+ messages.groupedBy(
1439
+ columnName(event.columnId),
1440
+ event.groupBy.indexOf(entry) + 1,
1441
+ event.groupBy.length
1442
+ )
1443
+ );
1444
+ },
1445
+ /**
1446
+ * `groupId: null` marks `expandAll`/`collapseAll`, which touch every
1447
+ * group in one call rather than one at a time — announced as a single
1448
+ * summary instead of naming a group that doesn't apply.
1449
+ */
1450
+ onGroupExpansionChange: (event) => {
1451
+ callbacks.onGroupExpansionChange?.(event);
1452
+ if (event.groupId === null) {
1453
+ announce(
1454
+ event.expansion.length === 0 ? messages.allGroupsExpanded : messages.allGroupsCollapsed
1455
+ );
1456
+ return;
1457
+ }
1458
+ announce(
1459
+ event.expansion.includes(event.groupId) ? messages.groupCollapsed : messages.groupExpanded
1460
+ );
1461
+ },
1462
+ onPaginationChange: (event) => {
1463
+ callbacks.onPaginationChange?.(event);
1464
+ announce(
1465
+ messages.pageStatus(event.pagination.pageIndex + 1, event.pageCount)
1466
+ );
1467
+ },
1468
+ /**
1469
+ * Unlike rows and columns, a cell selection is always exactly one cell —
1470
+ * there is no multi-cell range — so it never crosses the count's >1
1471
+ * threshold and the focused cell's own `aria-selected` is the only
1472
+ * feedback a screen-reader user would otherwise get. Announced on every
1473
+ * change that lands on a cell instead.
1474
+ */
1475
+ onCellSelectionChange: (event) => {
1476
+ callbacks.onCellSelectionChange?.(event);
1477
+ if (event.selected !== null) {
1478
+ announce(
1479
+ messages.cellSelected(
1480
+ columnName(event.selected.columnId),
1481
+ event.selected.rowIndex + 1
1482
+ )
1483
+ );
1484
+ }
1485
+ },
1486
+ /**
1487
+ * Announced only for a change touching more than one row or column. A
1488
+ * single selection is already carried by the focused member's own
1489
+ * `aria-selected`, and repeating it here would talk over that. Counted
1490
+ * by id, so rows the change touched off screen — on another page, in a
1491
+ * collapsed group — count too.
1492
+ */
1493
+ onSelectionDiff: (event) => {
1494
+ if (event.added.length + event.removed.length > 1) {
1495
+ announce(messages.selectedCount(event.selection.length, event.target));
1496
+ }
1597
1497
  }
1598
- onDrop({ kind: "column-order", beforeId }, entry.id);
1498
+ };
1499
+ }
1500
+
1501
+ // src/DataGrid/commands/useGridCommands.ts
1502
+ import { useMemo as useMemo3 } from "react";
1503
+ import {
1504
+ createCommandContext,
1505
+ resolveCommands,
1506
+ runCommand
1507
+ } from "@gridkitjs/core";
1508
+ function useGridCommands({
1509
+ controller,
1510
+ commands: appCommands,
1511
+ onCommand,
1512
+ messages,
1513
+ announce,
1514
+ resize,
1515
+ openPanel
1516
+ }) {
1517
+ const commands = useMemo3(() => resolveCommands(appCommands), [appCommands]);
1518
+ const host = {
1519
+ autoFitColumns: (columnIds) => {
1520
+ const { columns } = controller.getView();
1521
+ let fitted = 0;
1522
+ for (const columnId of columnIds) {
1523
+ const entry = columns.find((candidate) => candidate.id === columnId);
1524
+ if (entry !== void 0 && resize.sizeToContent(entry)) {
1525
+ fitted += 1;
1526
+ }
1527
+ }
1528
+ if (fitted > 1) {
1529
+ announce(messages.columnsAutoFitted(fitted));
1530
+ }
1531
+ },
1532
+ openPanel,
1533
+ announce
1534
+ };
1535
+ function contextFor(target) {
1536
+ return createCommandContext({ controller, target, messages, host });
1537
+ }
1538
+ function report(commandId, target, source) {
1539
+ onCommand?.({ commandId, target, source });
1599
1540
  }
1600
1541
  return {
1601
- draggedColumnId: drag.draggedId,
1602
- dropTarget: drag.dropTarget,
1603
- ghostTransform: drag.ghostTransform,
1604
- startDrag,
1605
- moveByKeyboard,
1606
- justDragged: drag.justDragged
1542
+ commands,
1543
+ host,
1544
+ contextFor,
1545
+ report,
1546
+ run: (commandId, target, source) => {
1547
+ const command = commands.get(commandId);
1548
+ if (command === void 0) {
1549
+ console.error(`DataGrid: there is no command \`${commandId}\` to run.`);
1550
+ return false;
1551
+ }
1552
+ if (!runCommand(command, contextFor(target))) {
1553
+ return false;
1554
+ }
1555
+ report(commandId, target, source);
1556
+ return true;
1557
+ }
1607
1558
  };
1608
1559
  }
1609
1560
 
1610
- // src/DataGrid/useColumnResize.ts
1561
+ // src/DataGrid/menu/GridMenu.tsx
1562
+ import { useLayoutEffect as useLayoutEffect3, useRef as useRef3, useState as useState4 } from "react";
1611
1563
  import {
1612
- useState as useState3
1613
- } from "react";
1614
- import {
1615
- applyColumnResize,
1616
- beginColumnResize,
1617
- clampColumnWidth,
1618
- resolveColumnConstraints,
1619
- revertColumnSize,
1620
- sizeColumnToContent
1621
- } from "@gridkitjs/core";
1564
+ attachMenu
1565
+ } from "@gridkitjs/dom";
1622
1566
 
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;
1567
+ // src/DataGrid/overlay/Overlay.tsx
1568
+ import { useLayoutEffect as useLayoutEffect2, useRef as useRef2 } from "react";
1569
+ import { createPortal as createPortal3 } from "react-dom";
1570
+ import {
1571
+ attachOverlay
1572
+ } from "@gridkitjs/dom";
1573
+ import { jsx as jsx11 } from "react/jsx-runtime";
1574
+ var ANCHOR_GAP = 4;
1575
+ function overlayThemeClassName(element) {
1576
+ const scope = element?.closest(".gridkit-light, .gridkit-dark");
1577
+ if (scope === null || scope === void 0) {
1578
+ return "";
1579
+ }
1580
+ return scope.classList.contains("gridkit-dark") ? "gridkit-dark" : "gridkit-light";
1581
+ }
1582
+ function Overlay({
1583
+ anchor,
1584
+ placement,
1585
+ gap,
1586
+ offset,
1587
+ initialFocus,
1588
+ returnFocusTo,
1589
+ ignoreScrollAfterInput,
1590
+ themeClassName,
1591
+ onDismiss,
1592
+ children
1593
+ }) {
1594
+ const panelRef = useRef2(null);
1595
+ const onDismissRef = useRef2(onDismiss);
1596
+ useLayoutEffect2(() => {
1597
+ onDismissRef.current = onDismiss;
1598
+ });
1599
+ useLayoutEffect2(() => {
1600
+ const panel = panelRef.current;
1601
+ if (panel === null) {
1602
+ return void 0;
1636
1603
  }
1637
- }
1638
- return widest;
1604
+ return attachOverlay(anchor, panel, {
1605
+ placement,
1606
+ gap,
1607
+ offset,
1608
+ initialFocus,
1609
+ returnFocusTo,
1610
+ ignoreScrollAfterInput,
1611
+ onDismiss: (reason) => {
1612
+ onDismissRef.current(reason);
1613
+ }
1614
+ });
1615
+ }, [
1616
+ anchor,
1617
+ placement,
1618
+ gap,
1619
+ offset,
1620
+ initialFocus,
1621
+ returnFocusTo,
1622
+ ignoreScrollAfterInput
1623
+ ]);
1624
+ return createPortal3(
1625
+ /* @__PURE__ */ jsx11(
1626
+ "div",
1627
+ {
1628
+ ref: panelRef,
1629
+ className: classNames("gridkit-overlay", themeClassName),
1630
+ children
1631
+ }
1632
+ ),
1633
+ document.body
1634
+ );
1639
1635
  }
1640
1636
 
1641
- // src/DataGrid/useColumnResize.ts
1642
- function useColumnResize({
1643
- tableRef,
1644
- sizing,
1645
- setSizing,
1646
- columnSizeDefaults,
1647
- onColumnResize
1637
+ // src/DataGrid/menu/menuIcons.tsx
1638
+ import { jsx as jsx12, jsxs as jsxs8 } from "react/jsx-runtime";
1639
+ var BUILT_IN_ICONS = {
1640
+ sortAscending: () => /* @__PURE__ */ jsxs8(Icon, { children: [
1641
+ /* @__PURE__ */ jsx12("path", { d: "m3 8 4-4 4 4" }),
1642
+ /* @__PURE__ */ jsx12("path", { d: "M7 4v16" }),
1643
+ /* @__PURE__ */ jsx12("path", { d: "M11 12h4" }),
1644
+ /* @__PURE__ */ jsx12("path", { d: "M11 16h7" }),
1645
+ /* @__PURE__ */ jsx12("path", { d: "M11 20h10" })
1646
+ ] }),
1647
+ sortDescending: () => /* @__PURE__ */ jsxs8(Icon, { children: [
1648
+ /* @__PURE__ */ jsx12("path", { d: "m3 16 4 4 4-4" }),
1649
+ /* @__PURE__ */ jsx12("path", { d: "M7 20V4" }),
1650
+ /* @__PURE__ */ jsx12("path", { d: "M11 4h10" }),
1651
+ /* @__PURE__ */ jsx12("path", { d: "M11 8h7" }),
1652
+ /* @__PURE__ */ jsx12("path", { d: "M11 12h4" })
1653
+ ] }),
1654
+ group: () => /* @__PURE__ */ jsx12(GroupIcon, {}),
1655
+ hide: () => /* @__PURE__ */ jsxs8(Icon, { children: [
1656
+ /* @__PURE__ */ jsx12("path", { d: "M9.88 9.88a3 3 0 1 0 4.24 4.24" }),
1657
+ /* @__PURE__ */ jsx12("path", { d: "M10.73 5.08A10.43 10.43 0 0 1 12 5c7 0 10 7 10 7a13.16 13.16 0 0 1-1.67 2.68" }),
1658
+ /* @__PURE__ */ jsx12("path", { d: "M6.61 6.61A13.53 13.53 0 0 0 2 12s3 7 10 7a9.74 9.74 0 0 0 5.39-1.61" }),
1659
+ /* @__PURE__ */ jsx12("path", { d: "m2 2 20 20" })
1660
+ ] }),
1661
+ autoFit: () => /* @__PURE__ */ jsxs8(Icon, { children: [
1662
+ /* @__PURE__ */ jsx12("path", { d: "m18 8 4 4-4 4" }),
1663
+ /* @__PURE__ */ jsx12("path", { d: "M2 12h20" }),
1664
+ /* @__PURE__ */ jsx12("path", { d: "m6 8-4 4 4 4" })
1665
+ ] })
1666
+ };
1667
+ var ICONS = new Map(Object.entries(BUILT_IN_ICONS));
1668
+ function MenuIcon({ name }) {
1669
+ const draw = name === void 0 ? void 0 : ICONS.get(name);
1670
+ return draw === void 0 ? null : draw();
1671
+ }
1672
+ function CheckIcon() {
1673
+ return /* @__PURE__ */ jsx12(Icon, { children: /* @__PURE__ */ jsx12("path", { d: "M20 6 9 17l-5-5" }) });
1674
+ }
1675
+ function ChevronDownIcon() {
1676
+ return /* @__PURE__ */ jsx12(Icon, { children: /* @__PURE__ */ jsx12("path", { d: "m6 9 6 6 6-6" }) });
1677
+ }
1678
+ function ChevronRightIcon() {
1679
+ return /* @__PURE__ */ jsx12(Icon, { children: /* @__PURE__ */ jsx12("path", { d: "m9 18 6-6-6-6" }) });
1680
+ }
1681
+
1682
+ // src/DataGrid/menu/GridMenu.tsx
1683
+ import { Fragment as Fragment2, jsx as jsx13, jsxs as jsxs9 } from "react/jsx-runtime";
1684
+ function itemElement(menu, id) {
1685
+ return Array.from(
1686
+ menu.querySelectorAll("[data-gridkit-menu-item]")
1687
+ ).find((item) => item.getAttribute("data-gridkit-menu-item") === id) ?? null;
1688
+ }
1689
+ function topInset(menu) {
1690
+ const style = getComputedStyle(menu);
1691
+ return parseFloat(style.borderTopWidth) + parseFloat(style.paddingTop);
1692
+ }
1693
+ function MenuItem({
1694
+ entry,
1695
+ expanded
1648
1696
  }) {
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
- }
1655
- function startResize(entry, event) {
1656
- const handle = event.currentTarget;
1657
- const { pointerId } = event;
1658
- const session = beginColumnResize(
1659
- entry.column,
1660
- entry.width,
1661
- event.clientX,
1662
- columnSizeDefaults
1663
- );
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");
1697
+ const isItem = entry.type === "item";
1698
+ const checked = isItem ? entry.checked : void 0;
1699
+ const shortcut = isItem ? entry.shortcut : void 0;
1700
+ return /* @__PURE__ */ jsxs9(
1701
+ "div",
1702
+ {
1703
+ role: checked === void 0 ? "menuitem" : "menuitemcheckbox",
1704
+ "data-gridkit-menu-item": entry.id,
1705
+ className: "menu-item",
1706
+ ...ariaAttr(isItem && entry.disabled, "aria-disabled", true),
1707
+ ...ariaAttr(checked !== void 0, "aria-checked", checked),
1708
+ ...ariaAttr(shortcut !== void 0, "aria-keyshortcuts", shortcut),
1709
+ ...ariaAttr(!isItem, "aria-haspopup", "menu"),
1710
+ ...ariaAttr(!isItem, "aria-expanded", expanded),
1711
+ children: [
1712
+ /* @__PURE__ */ jsx13("span", { className: "menu-item-icon", "aria-hidden": "true", children: checked === true ? /* @__PURE__ */ jsx13(CheckIcon, {}) : /* @__PURE__ */ jsx13(MenuIcon, { name: entry.icon }) }),
1713
+ /* @__PURE__ */ jsx13("span", { className: "menu-item-label", children: entry.label }),
1714
+ shortcut !== void 0 && /* @__PURE__ */ jsx13("span", { className: "menu-item-shortcut", "aria-hidden": "true", children: shortcut }),
1715
+ !isItem && /* @__PURE__ */ jsx13("span", { className: "menu-submenu-indicator", "aria-hidden": "true", children: /* @__PURE__ */ jsx13(ChevronRightIcon, {}) })
1716
+ ]
1717
+ }
1718
+ );
1719
+ }
1720
+ function GridMenu({
1721
+ entries,
1722
+ label,
1723
+ themeClassName,
1724
+ onRun,
1725
+ onClose,
1726
+ onCloseSubmenu,
1727
+ behaviourRef
1728
+ }) {
1729
+ const menuRef = useRef3(null);
1730
+ const submenuBehaviourRef = useRef3(null);
1731
+ const [submenu, setSubmenu] = useState4(null);
1732
+ const isSubmenu = onCloseSubmenu !== void 0;
1733
+ const latest = useRef3({ entries, submenu, onRun, onClose, onCloseSubmenu });
1734
+ useLayoutEffect3(() => {
1735
+ latest.current = { entries, submenu, onRun, onClose, onCloseSubmenu };
1736
+ });
1737
+ useLayoutEffect3(() => {
1738
+ const menu = menuRef.current;
1739
+ if (menu === null) {
1740
+ return void 0;
1741
+ }
1742
+ const behaviour = attachMenu(menu, {
1743
+ onActivate: (id) => {
1744
+ const item = latest.current.entries.find(
1745
+ (entry) => entry.type === "item" && entry.id === id
1746
+ );
1747
+ if (item !== void 0) {
1748
+ latest.current.onRun(item);
1749
+ }
1673
1750
  },
1674
- onEnd() {
1675
- setActiveColumnId(null);
1676
- if (width !== session.startWidth) {
1677
- commit(base, session.columnId, width, "end");
1751
+ onOpenSubmenu: (id, { focus }) => {
1752
+ if (latest.current.submenu?.id === id) {
1753
+ if (focus === "first") {
1754
+ submenuBehaviourRef.current?.focusItem(0);
1755
+ }
1756
+ return;
1678
1757
  }
1758
+ const anchor = itemElement(menu, id);
1759
+ if (anchor !== null) {
1760
+ setSubmenu({ id, anchor, focus, offset: -topInset(menu) });
1761
+ }
1762
+ },
1763
+ onCollapseSubmenu: () => {
1764
+ setSubmenu(null);
1679
1765
  },
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
- });
1766
+ onCloseSubmenu: isSubmenu ? () => {
1767
+ latest.current.onCloseSubmenu?.();
1768
+ } : void 0,
1769
+ onClose: () => {
1770
+ latest.current.onClose();
1699
1771
  }
1700
1772
  });
1701
- }
1702
- function sizeToContent(entry) {
1703
- const table = tableRef.current;
1704
- if (table === null) {
1705
- return;
1706
- }
1707
- const measured = measureColumnContent(table, entry.id);
1708
- if (measured === null) {
1709
- return;
1773
+ if (behaviourRef !== void 0) {
1774
+ behaviourRef.current = behaviour;
1710
1775
  }
1711
- commit(
1712
- sizing,
1713
- entry.id,
1714
- sizeColumnToContent(
1715
- measured,
1716
- resolveColumnConstraints(entry.column, columnSizeDefaults)
1717
- ),
1718
- "end"
1719
- );
1776
+ return () => {
1777
+ if (behaviourRef !== void 0) {
1778
+ behaviourRef.current = null;
1779
+ }
1780
+ behaviour.detach();
1781
+ };
1782
+ }, [isSubmenu, behaviourRef]);
1783
+ function closeSubmenu() {
1784
+ setSubmenu(null);
1785
+ }
1786
+ const openEntry = submenu === null ? void 0 : entries.find(
1787
+ (entry) => entry.type === "submenu" && entry.id === submenu.id
1788
+ );
1789
+ return /* @__PURE__ */ jsxs9(Fragment2, { children: [
1790
+ /* @__PURE__ */ jsx13(
1791
+ "div",
1792
+ {
1793
+ ref: menuRef,
1794
+ role: "menu",
1795
+ "aria-label": label,
1796
+ className: "gridkit-menu",
1797
+ children: entries.map(
1798
+ (entry) => entry.type === "separator" ? /* @__PURE__ */ jsx13("div", { role: "separator", className: "menu-separator" }, entry.key) : /* @__PURE__ */ jsx13(
1799
+ MenuItem,
1800
+ {
1801
+ entry,
1802
+ expanded: entry.id === submenu?.id
1803
+ },
1804
+ entry.id
1805
+ )
1806
+ )
1807
+ }
1808
+ ),
1809
+ submenu !== null && openEntry !== void 0 && /* @__PURE__ */ jsx13(
1810
+ Overlay,
1811
+ {
1812
+ anchor: submenu.anchor,
1813
+ placement: "right-start",
1814
+ offset: submenu.offset,
1815
+ initialFocus: submenu.focus,
1816
+ themeClassName,
1817
+ onDismiss: closeSubmenu,
1818
+ children: /* @__PURE__ */ jsx13(
1819
+ GridMenu,
1820
+ {
1821
+ entries: openEntry.items,
1822
+ label: openEntry.label,
1823
+ themeClassName,
1824
+ onRun,
1825
+ onClose,
1826
+ onCloseSubmenu: closeSubmenu,
1827
+ behaviourRef: submenuBehaviourRef
1828
+ }
1829
+ )
1830
+ },
1831
+ submenu.id
1832
+ )
1833
+ ] });
1834
+ }
1835
+
1836
+ // src/DataGrid/contextMenu/GridContextMenu.tsx
1837
+ import { jsx as jsx14 } from "react/jsx-runtime";
1838
+ function GridContextMenu({
1839
+ contextMenu
1840
+ }) {
1841
+ const { menu, run, close } = contextMenu;
1842
+ if (menu === null) {
1843
+ return null;
1844
+ }
1845
+ return /* @__PURE__ */ jsx14(
1846
+ Overlay,
1847
+ {
1848
+ anchor: menu.anchor,
1849
+ gap: menu.anchor instanceof Element ? ANCHOR_GAP : 0,
1850
+ returnFocusTo: menu.cell ?? void 0,
1851
+ themeClassName: menu.themeClassName,
1852
+ onDismiss: close,
1853
+ children: /* @__PURE__ */ jsx14(
1854
+ GridMenu,
1855
+ {
1856
+ entries: menu.entries,
1857
+ label: menu.label,
1858
+ themeClassName: menu.themeClassName,
1859
+ onRun: run,
1860
+ onClose: close
1861
+ }
1862
+ )
1863
+ },
1864
+ menu.key
1865
+ );
1866
+ }
1867
+
1868
+ // src/DataGrid/contextMenu/useContextMenu.ts
1869
+ import {
1870
+ useEffect as useEffect2,
1871
+ useLayoutEffect as useLayoutEffect4,
1872
+ useRef as useRef4,
1873
+ useState as useState5
1874
+ } from "react";
1875
+ import {
1876
+ contextMenuEntries,
1877
+ hasContextMenu,
1878
+ isSameCell,
1879
+ resolveMenu
1880
+ } from "@gridkitjs/core";
1881
+ import {
1882
+ attachContextMenuTrigger
1883
+ } from "@gridkitjs/dom";
1884
+
1885
+ // src/DataGrid/contextMenu/contextMenuPlaces.ts
1886
+ import {
1887
+ HEADER_ROW as HEADER_ROW2
1888
+ } from "@gridkitjs/core";
1889
+ import {
1890
+ bodyCellFrom as bodyCellFrom2,
1891
+ bodyRowFrom as bodyRowFrom2,
1892
+ findBodyRow as findBodyRow2,
1893
+ groupRowFrom as groupRowFrom2
1894
+ } from "@gridkitjs/dom";
1895
+ function headerCell(parts, columnIndex) {
1896
+ const element = parts.headerTable?.tHead?.rows[0]?.cells[columnIndex];
1897
+ return element === void 0 ? null : { element, rowIndex: HEADER_ROW2, columnIndex };
1898
+ }
1899
+ function bodyCell(parts, rowIndex, columnIndex) {
1900
+ const element = findBodyRow2(parts.bodyTable, rowIndex)?.cells[columnIndex];
1901
+ return element === void 0 ? null : { element, rowIndex, columnIndex };
1902
+ }
1903
+ function viewOf(element, parts) {
1904
+ const root = parts.root?.getBoundingClientRect();
1905
+ if (root === void 0) {
1906
+ return null;
1907
+ }
1908
+ const body = parts.body?.contains(element) === true ? parts.body.getBoundingClientRect() : root;
1909
+ const left = Math.max(root.left, body.left);
1910
+ const top = Math.max(root.top, body.top);
1911
+ return new DOMRect(
1912
+ left,
1913
+ top,
1914
+ Math.max(0, Math.min(root.right, body.right) - left),
1915
+ Math.max(0, Math.min(root.bottom, body.bottom) - top)
1916
+ );
1917
+ }
1918
+ function clamp(value, min, max) {
1919
+ return Math.min(Math.max(value, min), max);
1920
+ }
1921
+ function onCell(target, cell, parts) {
1922
+ if (cell === null) {
1923
+ return null;
1924
+ }
1925
+ const box = cell.element.getBoundingClientRect();
1926
+ const view = viewOf(cell.element, parts);
1927
+ if (view === null || box.right > view.left && box.left < view.right && box.bottom > view.top && box.top < view.bottom) {
1928
+ return { target, cell, anchor: cell.element };
1929
+ }
1930
+ return {
1931
+ target,
1932
+ cell,
1933
+ anchor: {
1934
+ x: clamp(box.left, view.left, view.right),
1935
+ y: clamp(box.bottom, view.top, view.bottom)
1936
+ }
1937
+ };
1938
+ }
1939
+ function onBody(body) {
1940
+ const { left, top } = body.getBoundingClientRect();
1941
+ return { target: { area: "body" }, cell: null, anchor: { x: left, y: top } };
1942
+ }
1943
+ function headerPlaceAt(element, parts) {
1944
+ const cell = element.closest("th[data-gridkit-column]");
1945
+ const columnId = cell?.getAttribute("data-gridkit-column");
1946
+ if (cell === null || cell.parentElement !== parts.headerTable?.tHead?.rows[0] || columnId === null || columnId === void 0) {
1947
+ return null;
1948
+ }
1949
+ return onCell(
1950
+ { area: "header", columnId },
1951
+ headerCell(parts, cell.cellIndex),
1952
+ parts
1953
+ );
1954
+ }
1955
+ function bodyPlaceAt(element, parts) {
1956
+ const { body, pageRows } = parts;
1957
+ if (body === null || !body.contains(element)) {
1958
+ return null;
1959
+ }
1960
+ const group = groupRowFrom2(element);
1961
+ if (group !== null) {
1962
+ const entry = pageRows[group.rowIndex];
1963
+ const cell = bodyCell(parts, group.rowIndex, 0);
1964
+ return entry !== void 0 && "kind" in entry && entry.kind === "group" && cell?.element.contains(element) === true ? onCell(
1965
+ { area: "group", groupId: entry.groupId, columnId: entry.columnId },
1966
+ cell,
1967
+ parts
1968
+ ) : null;
1969
+ }
1970
+ const position = bodyCellFrom2(element);
1971
+ if (position !== null) {
1972
+ const entry = pageRows[position.rowIndex];
1973
+ const cell = bodyCell(parts, position.rowIndex, position.columnIndex);
1974
+ return entry !== void 0 && !("kind" in entry) && cell?.element.contains(element) === true ? onCell(
1975
+ { area: "cell", rowId: entry.rowId, columnId: position.columnId },
1976
+ cell,
1977
+ parts
1978
+ ) : null;
1979
+ }
1980
+ return bodyRowFrom2(element) === null ? onBody(body) : null;
1981
+ }
1982
+ function placeAt(element, parts) {
1983
+ return headerPlaceAt(element, parts) ?? bodyPlaceAt(element, parts);
1984
+ }
1985
+ function placeOf(target, parts) {
1986
+ const columnIndexOf = (columnId) => parts.columns.findIndex((column) => column.id === columnId);
1987
+ switch (target.area) {
1988
+ case "header":
1989
+ return onCell(
1990
+ target,
1991
+ headerCell(parts, columnIndexOf(target.columnId)),
1992
+ parts
1993
+ );
1994
+ case "cell": {
1995
+ const rowIndex = parts.pageRows.findIndex(
1996
+ (entry) => !("kind" in entry) && entry.rowId === target.rowId
1997
+ );
1998
+ return onCell(
1999
+ target,
2000
+ bodyCell(parts, rowIndex, columnIndexOf(target.columnId)),
2001
+ parts
2002
+ );
2003
+ }
2004
+ case "group": {
2005
+ const rowIndex = parts.pageRows.findIndex(
2006
+ (entry) => "kind" in entry && entry.kind === "group" && entry.groupId === target.groupId
2007
+ );
2008
+ return onCell(target, bodyCell(parts, rowIndex, 0), parts);
2009
+ }
2010
+ case "body":
2011
+ return parts.body === null ? null : onBody(parts.body);
2012
+ }
2013
+ }
2014
+
2015
+ // src/DataGrid/contextMenu/useContextMenu.ts
2016
+ function useContextMenu({
2017
+ rootRef,
2018
+ headerTableRef,
2019
+ bodyTableRef,
2020
+ bodyScrollRef,
2021
+ contextMenu,
2022
+ onContextMenuOpenChange,
2023
+ controller,
2024
+ commands,
2025
+ nav,
2026
+ selection,
2027
+ messages,
2028
+ columnName
2029
+ }) {
2030
+ const [menu, setMenu] = useState5(null);
2031
+ const current = useRef4(null);
2032
+ const openings = useRef4(0);
2033
+ const enabled = contextMenu !== void 0 && contextMenu !== false;
2034
+ function parts() {
2035
+ const { columns, pageRows } = controller.getView();
2036
+ return {
2037
+ root: rootRef.current,
2038
+ headerTable: headerTableRef.current,
2039
+ bodyTable: bodyTableRef.current,
2040
+ body: bodyScrollRef.current,
2041
+ columns,
2042
+ pageRows
2043
+ };
2044
+ }
2045
+ function labelOf(target) {
2046
+ switch (target.area) {
2047
+ case "header":
2048
+ return messages.headerMenuLabel(columnName(target.columnId));
2049
+ case "cell":
2050
+ return messages.cellMenuLabel;
2051
+ case "group":
2052
+ return messages.groupMenuLabel;
2053
+ case "body":
2054
+ return messages.bodyMenuLabel;
2055
+ }
2056
+ }
2057
+ function selectTarget(target) {
2058
+ const state = controller.getState();
2059
+ if (target.area === "header") {
2060
+ if (selection.columnMode === false || state.columnSelection.includes(target.columnId)) {
2061
+ return false;
2062
+ }
2063
+ controller.selectColumn(target.columnId, "replace");
2064
+ return true;
2065
+ }
2066
+ if (target.area !== "cell") {
2067
+ return false;
2068
+ }
2069
+ const cell = { rowId: target.rowId, columnId: target.columnId };
2070
+ const selectRow = selection.rowMode !== false && !state.rowSelection.includes(target.rowId);
2071
+ const selectCell = selection.cellMode !== false && !isSameCell(state.cellSelection, cell);
2072
+ if (selectRow) {
2073
+ controller.selectRow(target.rowId, "replace");
2074
+ }
2075
+ if (selectCell) {
2076
+ controller.selectCell(cell, "replace");
2077
+ }
2078
+ return selectRow || selectCell;
2079
+ }
2080
+ function resolveFor(target) {
2081
+ const context = commands.contextFor(target);
2082
+ return resolveMenu(contextMenuEntries(contextMenu, context), {
2083
+ commands: commands.commands,
2084
+ context
2085
+ });
2086
+ }
2087
+ function show(next) {
2088
+ const previous = current.current;
2089
+ if (previous === next) {
2090
+ return;
2091
+ }
2092
+ current.current = next;
2093
+ setMenu(next);
2094
+ if (previous !== null) {
2095
+ onContextMenuOpenChange?.({ open: false, target: previous.target });
2096
+ }
2097
+ if (next !== null) {
2098
+ onContextMenuOpenChange?.({ open: true, target: next.target });
2099
+ }
2100
+ }
2101
+ function openAt({ target, cell, anchor }, point) {
2102
+ let resolved = resolveFor(target);
2103
+ if (resolved.length === 0) {
2104
+ return false;
2105
+ }
2106
+ if (selectTarget(target)) {
2107
+ resolved = resolveFor(target);
2108
+ }
2109
+ if (cell !== null && document.activeElement !== cell.element) {
2110
+ cell.element.focus({ preventScroll: true });
2111
+ nav.focusCellNow(cell.rowIndex, cell.columnIndex);
2112
+ }
2113
+ openings.current += 1;
2114
+ show({
2115
+ key: openings.current,
2116
+ target,
2117
+ entries: resolved,
2118
+ label: labelOf(target),
2119
+ anchor: point ?? anchor,
2120
+ cell: cell?.element ?? null,
2121
+ themeClassName: overlayThemeClassName(rootRef.current)
2122
+ });
2123
+ return true;
2124
+ }
2125
+ function handleRequest({ target, point }) {
2126
+ const place = placeAt(target, parts());
2127
+ return place !== null && openAt(place, point);
2128
+ }
2129
+ const latestRequest = useRef4(handleRequest);
2130
+ useLayoutEffect4(() => {
2131
+ latestRequest.current = handleRequest;
2132
+ });
2133
+ useEffect2(() => {
2134
+ const root = rootRef.current;
2135
+ if (!enabled || root === null) {
2136
+ return void 0;
2137
+ }
2138
+ return attachContextMenuTrigger(root, {
2139
+ onRequest: (request) => latestRequest.current(request)
2140
+ });
2141
+ }, [rootRef, enabled]);
2142
+ const shown = useRef4(null);
2143
+ useLayoutEffect4(() => {
2144
+ const closed = shown.current;
2145
+ shown.current = menu;
2146
+ if (menu === null && closed !== null && closed.cell !== null && !closed.cell.isConnected && document.activeElement === document.body) {
2147
+ nav.focusTabStop();
2148
+ }
2149
+ });
2150
+ function close() {
2151
+ show(null);
2152
+ }
2153
+ return {
2154
+ menu,
2155
+ opens: (area) => hasContextMenu(contextMenu, area),
2156
+ open: (target, anchor) => {
2157
+ const place = placeOf(target, parts());
2158
+ return place !== null && openAt(place, anchor ?? null);
2159
+ },
2160
+ run: (item) => {
2161
+ const open = current.current;
2162
+ if (item.run() && open !== null) {
2163
+ commands.report(item.id, open.target, "contextMenu");
2164
+ }
2165
+ close();
2166
+ },
2167
+ close
2168
+ };
2169
+ }
2170
+
2171
+ // src/DataGrid/columnChooser/ColumnChooser.tsx
2172
+ import { useLayoutEffect as useLayoutEffect5, useRef as useRef5, useState as useState6 } from "react";
2173
+ import { resolveColumnChooser } from "@gridkitjs/core";
2174
+ import { Fragment as Fragment3, jsx as jsx15, jsxs as jsxs10 } from "react/jsx-runtime";
2175
+ function ColumnChooser({
2176
+ controller,
2177
+ messages
2178
+ }) {
2179
+ const [search, setSearch] = useState6("");
2180
+ const showAllRef = useRef5(null);
2181
+ const { entries, selectAll, toggleAll } = resolveColumnChooser({
2182
+ columns: controller.getView().dataColumns,
2183
+ visibility: controller.getState().columnVisibility,
2184
+ search
2185
+ });
2186
+ useLayoutEffect5(() => {
2187
+ if (showAllRef.current !== null) {
2188
+ showAllRef.current.indeterminate = selectAll === "mixed";
2189
+ }
2190
+ });
2191
+ function setVisible(columnIds, visible) {
2192
+ if (visible) {
2193
+ controller.showColumns(columnIds);
2194
+ } else {
2195
+ controller.hideColumns(columnIds);
2196
+ }
2197
+ }
2198
+ return /* @__PURE__ */ jsxs10(
2199
+ "div",
2200
+ {
2201
+ role: "dialog",
2202
+ "aria-label": messages.columnChooserLabel,
2203
+ className: "gridkit-column-chooser",
2204
+ children: [
2205
+ /* @__PURE__ */ jsx15(
2206
+ "input",
2207
+ {
2208
+ type: "search",
2209
+ className: "column-chooser-search",
2210
+ "aria-label": messages.columnChooserSearchLabel,
2211
+ placeholder: messages.searchPlaceholder,
2212
+ value: search,
2213
+ onChange: (event) => {
2214
+ setSearch(event.currentTarget.value);
2215
+ }
2216
+ }
2217
+ ),
2218
+ entries.length === 0 ? /* @__PURE__ */ jsx15("p", { role: "status", className: "column-chooser-empty", children: messages.noMatchingColumns }) : /* @__PURE__ */ jsxs10(Fragment3, { children: [
2219
+ /* @__PURE__ */ jsxs10("label", { className: "column-chooser-option column-chooser-all", children: [
2220
+ /* @__PURE__ */ jsx15(
2221
+ "input",
2222
+ {
2223
+ ref: showAllRef,
2224
+ type: "checkbox",
2225
+ checked: selectAll === "checked",
2226
+ disabled: toggleAll === null,
2227
+ onChange: () => {
2228
+ if (toggleAll !== null) {
2229
+ setVisible(toggleAll.columnIds, toggleAll.visible);
2230
+ }
2231
+ }
2232
+ }
2233
+ ),
2234
+ messages.showAllColumns
2235
+ ] }),
2236
+ /* @__PURE__ */ jsx15("ul", { className: "column-chooser-list", children: entries.map((entry) => /* @__PURE__ */ jsx15("li", { children: /* @__PURE__ */ jsxs10("label", { className: "column-chooser-option", children: [
2237
+ /* @__PURE__ */ jsx15(
2238
+ "input",
2239
+ {
2240
+ type: "checkbox",
2241
+ checked: entry.visible,
2242
+ disabled: entry.disabled,
2243
+ onChange: () => {
2244
+ setVisible([entry.columnId], !entry.visible);
2245
+ }
2246
+ }
2247
+ ),
2248
+ entry.label
2249
+ ] }) }, entry.columnId)) })
2250
+ ] })
2251
+ ]
2252
+ }
2253
+ );
2254
+ }
2255
+
2256
+ // src/DataGrid/panels/GridPanels.tsx
2257
+ import { jsx as jsx16 } from "react/jsx-runtime";
2258
+ var panelComponents = {
2259
+ columnChooser: ColumnChooser
2260
+ };
2261
+ function GridPanels({
2262
+ panels,
2263
+ controller,
2264
+ messages
2265
+ }) {
2266
+ const { panel, close } = panels;
2267
+ if (panel === null) {
2268
+ return null;
2269
+ }
2270
+ const Panel = panelComponents[panel.kind];
2271
+ return /* @__PURE__ */ jsx16(
2272
+ Overlay,
2273
+ {
2274
+ anchor: panel.anchor,
2275
+ placement: panel.placement,
2276
+ gap: panel.anchor instanceof Element ? ANCHOR_GAP : 0,
2277
+ returnFocusTo: panel.returnFocusTo,
2278
+ ignoreScrollAfterInput: true,
2279
+ themeClassName: panel.themeClassName,
2280
+ onDismiss: close,
2281
+ children: /* @__PURE__ */ jsx16(
2282
+ Panel,
2283
+ {
2284
+ target: panel.target,
2285
+ controller,
2286
+ messages
2287
+ }
2288
+ )
2289
+ },
2290
+ panel.key
2291
+ );
2292
+ }
2293
+
2294
+ // src/DataGrid/panels/useGridPanels.ts
2295
+ import { useLayoutEffect as useLayoutEffect6, useRef as useRef6, useState as useState7 } from "react";
2296
+ function toolbarItem(root, itemId) {
2297
+ const items = root.querySelectorAll(
2298
+ ":scope > .gridkit-toolbar > [data-gridkit-toolbar-item]"
2299
+ );
2300
+ return Array.from(items).find(
2301
+ (item) => item.getAttribute("data-gridkit-toolbar-item") === itemId
2302
+ ) ?? // In "More" while the toolbar is narrow: the button it is chosen from.
2303
+ Array.from(items).find(
2304
+ (item) => item.hasAttribute("data-gridkit-toolbar-more")
2305
+ ) ?? null;
2306
+ }
2307
+ function headerCell2(headerTable, columnId) {
2308
+ const cells = headerTable?.tHead?.rows[0]?.cells;
2309
+ return cells === void 0 ? null : Array.from(cells).find(
2310
+ (cell) => cell.getAttribute("data-gridkit-column") === columnId
2311
+ ) ?? null;
2312
+ }
2313
+ function useGridPanels({
2314
+ rootRef,
2315
+ headerTableRef,
2316
+ nav
2317
+ }) {
2318
+ const [panel, setPanel] = useState7(null);
2319
+ const openings = useRef6(0);
2320
+ function placeFor(root, target) {
2321
+ const element = target.area === "toolbar" ? toolbarItem(root, target.itemId) : target.area === "header" ? headerCell2(headerTableRef.current, target.columnId) : null;
2322
+ if (element !== null) {
2323
+ return {
2324
+ anchor: element,
2325
+ // The toolbar's own button sits at its far end.
2326
+ placement: target.area === "toolbar" ? "bottom-end" : "bottom-start",
2327
+ returnFocusTo: element
2328
+ };
2329
+ }
2330
+ const { right, top } = root.getBoundingClientRect();
2331
+ return {
2332
+ anchor: { x: right, y: top },
2333
+ placement: "bottom-end",
2334
+ returnFocusTo: void 0
2335
+ };
2336
+ }
2337
+ const shown = useRef6(null);
2338
+ useLayoutEffect6(() => {
2339
+ const closed = shown.current;
2340
+ shown.current = panel;
2341
+ if (panel === null && closed?.returnFocusTo?.isConnected === false && document.activeElement === document.body) {
2342
+ nav.focusTabStop();
2343
+ }
2344
+ });
2345
+ return {
2346
+ panel,
2347
+ open: (kind, target) => {
2348
+ const root = rootRef.current;
2349
+ if (root === null) {
2350
+ return;
2351
+ }
2352
+ openings.current += 1;
2353
+ setPanel({
2354
+ key: openings.current,
2355
+ kind,
2356
+ target,
2357
+ ...placeFor(root, target),
2358
+ themeClassName: overlayThemeClassName(root)
2359
+ });
2360
+ },
2361
+ close: () => {
2362
+ setPanel(null);
2363
+ }
2364
+ };
2365
+ }
2366
+
2367
+ // src/DataGrid/toolbar/GridToolbar.tsx
2368
+ import { useLayoutEffect as useLayoutEffect9, useRef as useRef10 } from "react";
2369
+ import {
2370
+ resolveToolbar,
2371
+ splitToolbarItems,
2372
+ toolbarEntries
2373
+ } from "@gridkitjs/core";
2374
+ import { attachToolbar } from "@gridkitjs/dom";
2375
+
2376
+ // src/DataGrid/toolbar/ToolbarMenuButton.tsx
2377
+ import { useLayoutEffect as useLayoutEffect7, useRef as useRef7, useState as useState8 } from "react";
2378
+ import { Fragment as Fragment4, jsx as jsx17, jsxs as jsxs11 } from "react/jsx-runtime";
2379
+ function ToolbarMenuButton({
2380
+ itemKey,
2381
+ label,
2382
+ icon,
2383
+ entries,
2384
+ more = false,
2385
+ onMenuClose
2386
+ }) {
2387
+ const [menu, setMenu] = useState8(null);
2388
+ const menuBehaviourRef = useRef7(null);
2389
+ useLayoutEffect7(() => {
2390
+ if (menu !== null && menu.focusItem > 0) {
2391
+ menuBehaviourRef.current?.focusItem(menu.focusItem);
2392
+ }
2393
+ }, [menu]);
2394
+ function open(button, onLast = false) {
2395
+ setMenu({
2396
+ anchor: button,
2397
+ themeClassName: overlayThemeClassName(button),
2398
+ focusItem: onLast ? entries.filter((entry) => entry.type !== "separator").length - 1 : 0
2399
+ });
2400
+ }
2401
+ function close() {
2402
+ setMenu(null);
2403
+ onMenuClose();
2404
+ }
2405
+ return /* @__PURE__ */ jsxs11(Fragment4, { children: [
2406
+ /* @__PURE__ */ jsxs11(
2407
+ "button",
2408
+ {
2409
+ type: "button",
2410
+ className: classNames("toolbar-button", more && "toolbar-more"),
2411
+ "data-gridkit-toolbar-item": itemKey,
2412
+ ...ariaAttr(more, "data-gridkit-toolbar-more", ""),
2413
+ "aria-haspopup": "menu",
2414
+ "aria-expanded": menu !== null,
2415
+ onClick: (event) => {
2416
+ if (menu === null) {
2417
+ open(event.currentTarget);
2418
+ } else {
2419
+ close();
2420
+ }
2421
+ },
2422
+ onKeyDown: (event) => {
2423
+ if ((event.key === "ArrowDown" || event.key === "ArrowUp") && menu === null) {
2424
+ event.preventDefault();
2425
+ open(event.currentTarget, event.key === "ArrowUp");
2426
+ }
2427
+ },
2428
+ children: [
2429
+ icon !== void 0 && /* @__PURE__ */ jsx17("span", { className: "toolbar-button-icon", "aria-hidden": "true", children: /* @__PURE__ */ jsx17(MenuIcon, { name: icon }) }),
2430
+ label,
2431
+ /* @__PURE__ */ jsx17("span", { className: "toolbar-button-indicator", "aria-hidden": "true", children: /* @__PURE__ */ jsx17(ChevronDownIcon, {}) })
2432
+ ]
2433
+ }
2434
+ ),
2435
+ menu !== null && /* @__PURE__ */ jsx17(
2436
+ Overlay,
2437
+ {
2438
+ anchor: menu.anchor,
2439
+ placement: "bottom-start",
2440
+ gap: ANCHOR_GAP,
2441
+ returnFocusTo: menu.anchor,
2442
+ themeClassName: menu.themeClassName,
2443
+ onDismiss: close,
2444
+ children: /* @__PURE__ */ jsx17(
2445
+ GridMenu,
2446
+ {
2447
+ entries,
2448
+ label,
2449
+ themeClassName: menu.themeClassName,
2450
+ behaviourRef: menuBehaviourRef,
2451
+ onRun: (item) => {
2452
+ item.run();
2453
+ close();
2454
+ },
2455
+ onClose: close
2456
+ }
2457
+ )
2458
+ }
2459
+ )
2460
+ ] });
2461
+ }
2462
+
2463
+ // src/DataGrid/toolbar/ToolbarSearch.tsx
2464
+ import { useEffect as useEffect3, useRef as useRef8, useState as useState9 } from "react";
2465
+ import {
2466
+ searchTextOf,
2467
+ setSearchText
2468
+ } from "@gridkitjs/core";
2469
+ import { jsx as jsx18 } from "react/jsx-runtime";
2470
+ var SEARCH_DELAY_MS = 250;
2471
+ function ToolbarSearch({
2472
+ itemKey,
2473
+ controller,
2474
+ messages,
2475
+ announce
2476
+ }) {
2477
+ const applied = searchTextOf(controller.getState().filter);
2478
+ const [draft, setDraft] = useState9(null);
2479
+ const timer = useRef8(void 0);
2480
+ const unannounced = useRef8(null);
2481
+ const text = draft !== null && draft.over === applied ? draft.text : applied;
2482
+ function apply(next, over) {
2483
+ clearTimeout(timer.current);
2484
+ setDraft(null);
2485
+ const filter = controller.getState().filter;
2486
+ if (searchTextOf(filter) !== over) {
2487
+ return;
2488
+ }
2489
+ const searched = setSearchText(filter, next);
2490
+ if (searched !== filter) {
2491
+ unannounced.current = next;
2492
+ controller.setFilter(searched);
2493
+ }
2494
+ }
2495
+ useEffect3(() => {
2496
+ if (unannounced.current === applied) {
2497
+ unannounced.current = null;
2498
+ announce(messages.searchResults(controller.getView().rows.length));
2499
+ }
2500
+ });
2501
+ useEffect3(
2502
+ () => () => {
2503
+ clearTimeout(timer.current);
2504
+ },
2505
+ []
2506
+ );
2507
+ return /* @__PURE__ */ jsx18(
2508
+ "input",
2509
+ {
2510
+ type: "search",
2511
+ className: "toolbar-search",
2512
+ "data-gridkit-toolbar-item": itemKey,
2513
+ "aria-label": messages.searchLabel,
2514
+ placeholder: messages.searchPlaceholder,
2515
+ value: text,
2516
+ onChange: (event) => {
2517
+ const typed = event.target.value;
2518
+ setDraft({ text: typed, over: applied });
2519
+ clearTimeout(timer.current);
2520
+ timer.current = setTimeout(() => {
2521
+ apply(typed, applied);
2522
+ }, SEARCH_DELAY_MS);
2523
+ },
2524
+ onKeyDown: (event) => {
2525
+ if (event.key === "Enter") {
2526
+ apply(text, applied);
2527
+ } else if (event.key === "Escape" && text !== "") {
2528
+ event.preventDefault();
2529
+ apply("", applied);
2530
+ }
2531
+ }
2532
+ }
2533
+ );
2534
+ }
2535
+
2536
+ // src/DataGrid/toolbar/useToolbarOverflow.ts
2537
+ import { useLayoutEffect as useLayoutEffect8, useRef as useRef9, useState as useState10 } from "react";
2538
+ import { observeToolbarOverflow } from "@gridkitjs/dom";
2539
+ function useToolbarOverflow(toolbarRef, items) {
2540
+ const [fit, setFit] = useState10(items.length);
2541
+ const overflow = useRef9(null);
2542
+ const measuredLayout = useRef9("");
2543
+ const layout = JSON.stringify(
2544
+ items.map((item) => [
2545
+ item.type,
2546
+ item.key,
2547
+ "label" in item ? item.label : null
2548
+ ])
2549
+ );
2550
+ useLayoutEffect8(() => {
2551
+ const toolbar = toolbarRef.current;
2552
+ if (toolbar === null || layout === measuredLayout.current) {
2553
+ return;
2554
+ }
2555
+ measuredLayout.current = layout;
2556
+ const options = { items, onChange: setFit };
2557
+ if (overflow.current === null) {
2558
+ overflow.current = observeToolbarOverflow(toolbar, options);
2559
+ } else {
2560
+ overflow.current.update(options);
2561
+ }
2562
+ }, [toolbarRef, layout, items]);
2563
+ useLayoutEffect8(
2564
+ () => () => {
2565
+ overflow.current?.detach();
2566
+ overflow.current = null;
2567
+ measuredLayout.current = "";
2568
+ },
2569
+ []
2570
+ );
2571
+ return fit;
2572
+ }
2573
+
2574
+ // src/DataGrid/toolbar/GridToolbar.tsx
2575
+ import { jsx as jsx19, jsxs as jsxs12 } from "react/jsx-runtime";
2576
+ function reporting(run, report) {
2577
+ return () => {
2578
+ const ran = run();
2579
+ if (ran) {
2580
+ report();
2581
+ }
2582
+ return ran;
2583
+ };
2584
+ }
2585
+ function reportingEntries(entries, report) {
2586
+ return entries.map((entry) => {
2587
+ switch (entry.type) {
2588
+ case "item":
2589
+ return {
2590
+ ...entry,
2591
+ run: reporting(entry.run, () => {
2592
+ report(entry.id);
2593
+ })
2594
+ };
2595
+ case "submenu":
2596
+ return { ...entry, items: reportingEntries(entry.items, report) };
2597
+ default:
2598
+ return entry;
2599
+ }
2600
+ });
2601
+ }
2602
+ function reportingItem(item, report) {
2603
+ switch (item.type) {
2604
+ case "button":
2605
+ return {
2606
+ ...item,
2607
+ run: reporting(item.run, () => {
2608
+ report(item.id, item.id);
2609
+ })
2610
+ };
2611
+ case "menu":
2612
+ return {
2613
+ ...item,
2614
+ items: reportingEntries(item.items, (commandId) => {
2615
+ report(commandId, item.id);
2616
+ })
2617
+ };
2618
+ default:
2619
+ return item;
2620
+ }
2621
+ }
2622
+ function GridToolbar(props) {
2623
+ const entries = toolbarEntries(props.toolbar);
2624
+ if (entries.length === 0) {
2625
+ return null;
2626
+ }
2627
+ const label = (typeof props.toolbar === "object" ? props.toolbar.label : void 0) ?? props.messages.toolbarLabel;
2628
+ return /* @__PURE__ */ jsx19(Toolbar, { ...props, entries, label });
2629
+ }
2630
+ function Toolbar({
2631
+ entries,
2632
+ label,
2633
+ renderItem,
2634
+ gridId,
2635
+ controller,
2636
+ commands,
2637
+ messages,
2638
+ announce
2639
+ }) {
2640
+ const toolbarRef = useRef10(null);
2641
+ const menuClosed = useRef10(false);
2642
+ const items = resolveToolbar(entries, {
2643
+ commands: commands.commands,
2644
+ contextFor: commands.contextFor
2645
+ }).map(
2646
+ (item) => reportingItem(item, (commandId, itemId) => {
2647
+ commands.report(commandId, { area: "toolbar", itemId }, "toolbar");
2648
+ })
2649
+ );
2650
+ const fit = useToolbarOverflow(toolbarRef, items);
2651
+ const { shown, overflow } = splitToolbarItems(items, fit);
2652
+ useLayoutEffect9(() => {
2653
+ const toolbar = toolbarRef.current;
2654
+ return toolbar === null ? void 0 : attachToolbar(toolbar);
2655
+ }, []);
2656
+ useLayoutEffect9(() => {
2657
+ if (!menuClosed.current) {
2658
+ return;
2659
+ }
2660
+ menuClosed.current = false;
2661
+ const toolbar = toolbarRef.current;
2662
+ if (toolbar !== null && document.activeElement === document.body) {
2663
+ (toolbar.querySelector('[tabindex="0"]') ?? toolbar.querySelector("button, input"))?.focus();
2664
+ }
2665
+ });
2666
+ function onMenuClose() {
2667
+ menuClosed.current = true;
2668
+ }
2669
+ function draw(item) {
2670
+ switch (item.type) {
2671
+ case "button":
2672
+ return /* @__PURE__ */ jsxs12(
2673
+ "button",
2674
+ {
2675
+ type: "button",
2676
+ className: "toolbar-button",
2677
+ "data-gridkit-toolbar-item": item.key,
2678
+ ...ariaAttr(item.disabled, "aria-disabled", true),
2679
+ ...ariaAttr(
2680
+ item.pressed !== void 0,
2681
+ "aria-pressed",
2682
+ item.pressed
2683
+ ),
2684
+ ...ariaAttr(
2685
+ item.shortcut !== void 0,
2686
+ "aria-keyshortcuts",
2687
+ item.shortcut
2688
+ ),
2689
+ onClick: () => {
2690
+ item.run();
2691
+ },
2692
+ children: [
2693
+ item.icon !== void 0 && /* @__PURE__ */ jsx19("span", { className: "toolbar-button-icon", "aria-hidden": "true", children: /* @__PURE__ */ jsx19(MenuIcon, { name: item.icon }) }),
2694
+ item.label
2695
+ ]
2696
+ },
2697
+ item.key
2698
+ );
2699
+ case "menu":
2700
+ return /* @__PURE__ */ jsx19(
2701
+ ToolbarMenuButton,
2702
+ {
2703
+ itemKey: item.key,
2704
+ label: item.label,
2705
+ icon: item.icon,
2706
+ entries: item.items,
2707
+ onMenuClose
2708
+ },
2709
+ item.key
2710
+ );
2711
+ case "search":
2712
+ return /* @__PURE__ */ jsx19(
2713
+ ToolbarSearch,
2714
+ {
2715
+ itemKey: item.key,
2716
+ controller,
2717
+ messages,
2718
+ announce
2719
+ },
2720
+ item.key
2721
+ );
2722
+ case "separator":
2723
+ return /* @__PURE__ */ jsx19(
2724
+ "div",
2725
+ {
2726
+ role: "separator",
2727
+ "aria-orientation": "vertical",
2728
+ className: "toolbar-separator",
2729
+ "data-gridkit-toolbar-item": item.key
2730
+ },
2731
+ item.key
2732
+ );
2733
+ case "spacer":
2734
+ return /* @__PURE__ */ jsx19(
2735
+ "div",
2736
+ {
2737
+ className: "toolbar-spacer",
2738
+ "data-gridkit-toolbar-item": item.key
2739
+ },
2740
+ item.key
2741
+ );
2742
+ case "custom":
2743
+ return /* @__PURE__ */ jsx19(
2744
+ "div",
2745
+ {
2746
+ className: "toolbar-custom",
2747
+ "data-gridkit-toolbar-item": item.key,
2748
+ children: renderItem?.(item.id)
2749
+ },
2750
+ item.key
2751
+ );
2752
+ }
2753
+ }
2754
+ return /* @__PURE__ */ jsxs12(
2755
+ "div",
2756
+ {
2757
+ ref: toolbarRef,
2758
+ role: "toolbar",
2759
+ "aria-label": label,
2760
+ "aria-controls": gridId,
2761
+ className: "gridkit-toolbar",
2762
+ children: [
2763
+ shown.map(draw),
2764
+ overflow.length > 0 && /* @__PURE__ */ jsx19(
2765
+ ToolbarMenuButton,
2766
+ {
2767
+ itemKey: "more",
2768
+ label: messages.moreToolbarItems,
2769
+ entries: overflow,
2770
+ more: true,
2771
+ onMenuClose
2772
+ }
2773
+ )
2774
+ ]
2775
+ }
2776
+ );
2777
+ }
2778
+
2779
+ // src/DataGrid/useColumnDrag.ts
2780
+ import {
2781
+ resolveKeyboardDropTarget as resolveKeyboardDropTarget2
2782
+ } from "@gridkitjs/core";
2783
+ import { resolveColumnDropTarget } from "@gridkitjs/dom";
2784
+ function useColumnDrag({
2785
+ order,
2786
+ groupBy,
2787
+ groupByDraggableIds,
2788
+ onDrop
2789
+ }) {
2790
+ const drag = useDragReorder({
2791
+ resolveTarget: (clientX, clientY, movedId) => resolveColumnDropTarget(clientX, clientY, movedId, {
2792
+ order,
2793
+ groupBy,
2794
+ groupByDraggableIds
2795
+ }),
2796
+ onDrop
2797
+ });
2798
+ function startDrag(entry, event) {
2799
+ drag.startDrag(entry.id, event.currentTarget, event);
2800
+ }
2801
+ function moveByKeyboard(entry, direction) {
2802
+ const beforeId = resolveKeyboardDropTarget2(order, entry.id, direction);
2803
+ if (beforeId === void 0) {
2804
+ return;
2805
+ }
2806
+ onDrop({ kind: "column-order", beforeId }, entry.id);
2807
+ }
2808
+ return {
2809
+ draggedColumnId: drag.draggedId,
2810
+ dropTarget: drag.dropTarget,
2811
+ ghostTransform: drag.ghostTransform,
2812
+ startDrag,
2813
+ moveByKeyboard,
2814
+ justDragged: drag.justDragged
2815
+ };
2816
+ }
2817
+
2818
+ // src/DataGrid/useColumnResize.ts
2819
+ import {
2820
+ useState as useState11
2821
+ } from "react";
2822
+ import {
2823
+ beginColumnResize,
2824
+ clampColumnWidth,
2825
+ resolveColumnConstraints,
2826
+ sizeColumnToContent
2827
+ } from "@gridkitjs/core";
2828
+ import { measureColumnContent, startColumnResizeGesture } from "@gridkitjs/dom";
2829
+ function useColumnResize({
2830
+ tableRef,
2831
+ controller,
2832
+ columnSizeDefaults
2833
+ }) {
2834
+ const [activeColumnId, setActiveColumnId] = useState11(null);
2835
+ function startResize(entry, event) {
2836
+ event.stopPropagation();
2837
+ const session = beginColumnResize(
2838
+ entry.column,
2839
+ entry.width,
2840
+ event.clientX,
2841
+ columnSizeDefaults
2842
+ );
2843
+ const base = controller.getState().columnSizing;
2844
+ const started = startColumnResizeGesture(
2845
+ event.currentTarget,
2846
+ event,
2847
+ session,
2848
+ {
2849
+ onMove(width) {
2850
+ controller.resizeColumn(session.columnId, width, "move");
2851
+ },
2852
+ onEnd(width) {
2853
+ setActiveColumnId(null);
2854
+ if (width !== null) {
2855
+ controller.resizeColumn(session.columnId, width, "end");
2856
+ }
2857
+ },
2858
+ /**
2859
+ * Escape puts back only the dragged column's pre-drag width, merged
2860
+ * into whatever the sizing state is *now* — not the `base` snapshot
2861
+ * taken at drag-start — so a different column resized via keyboard
2862
+ * while this drag was still open keeps its change instead of being
2863
+ * clobbered by a stale restore.
2864
+ */
2865
+ onCancel() {
2866
+ setActiveColumnId(null);
2867
+ controller.cancelColumnResize(
2868
+ session.columnId,
2869
+ base,
2870
+ session.startWidth
2871
+ );
2872
+ }
2873
+ }
2874
+ );
2875
+ if (started) {
2876
+ setActiveColumnId(session.columnId);
2877
+ }
2878
+ }
2879
+ function sizeToContent(entry) {
2880
+ const table = tableRef.current;
2881
+ if (table === null) {
2882
+ return false;
2883
+ }
2884
+ const measured = measureColumnContent(table, entry.id);
2885
+ if (measured === null) {
2886
+ return false;
2887
+ }
2888
+ controller.resizeColumn(
2889
+ entry.id,
2890
+ sizeColumnToContent(
2891
+ measured,
2892
+ resolveColumnConstraints(entry.column, columnSizeDefaults)
2893
+ ),
2894
+ "end"
2895
+ );
2896
+ return true;
1720
2897
  }
1721
2898
  function nudge(entry, delta) {
1722
- commit(
1723
- sizing,
2899
+ controller.resizeColumn(
1724
2900
  entry.id,
1725
2901
  clampColumnWidth(
1726
2902
  entry.width + delta,
@@ -1729,238 +2905,107 @@ function useColumnResize({
1729
2905
  "end"
1730
2906
  );
1731
2907
  }
1732
- return { activeColumnId, startResize, sizeToContent, nudge };
1733
- }
1734
-
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
- };
2908
+ return { activeColumnId, startResize, sizeToContent, nudge };
1758
2909
  }
1759
2910
 
1760
2911
  // src/DataGrid/useElementWidth.ts
1761
- import { useLayoutEffect, useState as useState4 } from "react";
2912
+ import { useLayoutEffect as useLayoutEffect10, useState as useState12 } from "react";
2913
+ import { observeElementWidth } from "@gridkitjs/dom";
1762
2914
  function useElementWidth(ref, enabled) {
1763
- const [width, setWidth] = useState4(null);
1764
- useLayoutEffect(() => {
2915
+ const [width, setWidth] = useState12(null);
2916
+ useLayoutEffect10(() => {
1765
2917
  const element = ref.current;
1766
2918
  if (element === null || !enabled) {
1767
2919
  return void 0;
1768
2920
  }
1769
- setWidth(element.clientWidth);
1770
- const observer = new ResizeObserver(() => {
1771
- setWidth(element.clientWidth);
1772
- });
1773
- observer.observe(element);
1774
- return () => {
1775
- observer.disconnect();
1776
- };
2921
+ return observeElementWidth(element, setWidth);
1777
2922
  }, [ref, enabled]);
1778
2923
  return width;
1779
2924
  }
1780
2925
 
2926
+ // src/DataGrid/useGridController.ts
2927
+ import { useState as useState13, useSyncExternalStore as useSyncExternalStore2 } from "react";
2928
+ import {
2929
+ createGridController
2930
+ } from "@gridkitjs/core";
2931
+ function useGridController(options) {
2932
+ const [controller] = useState13(
2933
+ () => createGridController(options)
2934
+ );
2935
+ controller.setOptions(options);
2936
+ const state = useSyncExternalStore2(controller.subscribe, controller.getState);
2937
+ return { controller, state, view: controller.getView() };
2938
+ }
2939
+
1781
2940
  // src/DataGrid/useInfiniteScroll.ts
1782
- import { useCallback, useEffect as useEffect2, useRef as useRef4 } from "react";
2941
+ import { useEffect as useEffect4, useRef as useRef11 } from "react";
2942
+ import { attachInfiniteScroll } from "@gridkitjs/dom";
1783
2943
  function useInfiniteScroll({
1784
2944
  rootRef,
1785
2945
  sentinelRef,
1786
2946
  config
1787
2947
  }) {
1788
- const hasMore = config?.hasMore ?? false;
1789
- const isLoadingMore = config?.isLoadingMore ?? false;
1790
- const rootMargin = config?.rootMargin ?? "200px";
1791
- const latestRef = useRef4({
1792
- hasMore,
1793
- isLoadingMore,
1794
- onLoadMore: config?.onLoadMore
1795
- });
1796
- useEffect2(() => {
1797
- latestRef.current = {
1798
- hasMore,
1799
- isLoadingMore,
1800
- onLoadMore: config?.onLoadMore
1801
- };
1802
- });
1803
- const firedRef = useRef4(false);
1804
- const intersectingRef = useRef4(false);
1805
- const maybeLoadMore = useCallback(() => {
1806
- const current = latestRef.current;
1807
- if (!intersectingRef.current || !current.hasMore || current.isLoadingMore || firedRef.current) {
1808
- return;
1809
- }
1810
- firedRef.current = true;
1811
- current.onLoadMore?.();
1812
- }, []);
1813
- const wasLoadingRef = useRef4(isLoadingMore);
1814
- useEffect2(() => {
1815
- if (wasLoadingRef.current && !isLoadingMore) {
1816
- firedRef.current = false;
1817
- maybeLoadMore();
1818
- }
1819
- wasLoadingRef.current = isLoadingMore;
1820
- }, [isLoadingMore, maybeLoadMore]);
1821
- useEffect2(() => {
1822
- if (!hasMore) {
1823
- return void 0;
1824
- }
2948
+ const scroll = useRef11(null);
2949
+ useEffect4(() => {
1825
2950
  const root = rootRef.current;
1826
- const sentinel = sentinelRef.current;
1827
- if (root === null || sentinel === null) {
2951
+ if (root === null) {
1828
2952
  return void 0;
1829
2953
  }
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);
2954
+ const attached = attachInfiniteScroll(root, {
2955
+ sentinel: null,
2956
+ hasMore: false
2957
+ });
2958
+ scroll.current = attached;
1842
2959
  return () => {
1843
- observer.disconnect();
1844
- intersectingRef.current = false;
2960
+ attached.detach();
2961
+ scroll.current = null;
1845
2962
  };
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
2963
+ }, [rootRef]);
2964
+ useEffect4(() => {
2965
+ scroll.current?.update({
2966
+ sentinel: sentinelRef.current,
2967
+ hasMore: config?.hasMore ?? false,
2968
+ onLoadMore: config?.onLoadMore,
2969
+ isLoadingMore: config?.isLoadingMore,
2970
+ rootMargin: config?.rootMargin
1873
2971
  });
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
- };
2972
+ });
1893
2973
  }
1894
2974
 
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
- };
2975
+ // src/DataGrid/useLiveAnnouncer.ts
2976
+ import { useCallback, useLayoutEffect as useLayoutEffect11, useRef as useRef12 } from "react";
2977
+ import { createLiveAnnouncer } from "@gridkitjs/dom";
2978
+ function useLiveAnnouncer(containerRef) {
2979
+ const announcer = useRef12(null);
2980
+ useLayoutEffect11(() => {
2981
+ const container = containerRef.current;
2982
+ if (container === null) {
2983
+ return void 0;
2984
+ }
2985
+ const created = createLiveAnnouncer(container);
2986
+ announcer.current = created;
2987
+ return () => {
2988
+ created.destroy();
2989
+ announcer.current = null;
2990
+ };
2991
+ }, [containerRef]);
2992
+ return useCallback((text) => {
2993
+ announcer.current?.announce(text);
2994
+ }, []);
1947
2995
  }
1948
2996
 
1949
2997
  // 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";
2998
+ import { useLayoutEffect as useLayoutEffect12, useState as useState14 } from "react";
2999
+ import { createRowVirtualizer } from "@gridkitjs/dom";
1958
3000
  var EMPTY_RANGE = {
1959
3001
  startIndex: 0,
1960
3002
  endIndex: -1,
1961
3003
  offsetBefore: 0,
1962
3004
  offsetAfter: 0
1963
3005
  };
3006
+ function ignoreRow() {
3007
+ return () => void 0;
3008
+ }
1964
3009
  function useRowVirtualizer({
1965
3010
  enabled,
1966
3011
  rows,
@@ -1968,339 +3013,142 @@ function useRowVirtualizer({
1968
3013
  estimatedRowHeight,
1969
3014
  overscan
1970
3015
  }) {
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
- }
3016
+ const [virtualizer, setVirtualizer] = useState14(null);
3017
+ const [range, setRange] = useState14(EMPTY_RANGE);
3018
+ useLayoutEffect12(() => {
2009
3019
  const element = bodyScrollRef.current;
2010
- if (element === null) {
2011
- return void 0;
2012
- }
2013
- scrollTopRef.current = element.scrollTop;
2014
- viewportHeightRef.current = element.clientHeight;
2015
- recompute();
2016
- const handleScroll = () => {
2017
- scrollTopRef.current = element.scrollTop;
2018
- recompute();
2019
- };
2020
- element.addEventListener("scroll", handleScroll, { passive: true });
2021
- const resizeObserver = new ResizeObserver(() => {
2022
- viewportHeightRef.current = element.clientHeight;
2023
- recompute();
2024
- });
2025
- resizeObserver.observe(element);
2026
- return () => {
2027
- element.removeEventListener("scroll", handleScroll);
2028
- resizeObserver.disconnect();
2029
- };
2030
- }, [enabled, bodyScrollRef, recompute]);
2031
- useEffect3(() => {
2032
- if (!enabled) {
3020
+ if (!enabled || element === null) {
2033
3021
  return void 0;
2034
3022
  }
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
- }
3023
+ const created = createRowVirtualizer(element, {
3024
+ estimatedRowHeight,
3025
+ overscan,
3026
+ onRange: setRange
2049
3027
  });
2050
- observerRef.current = observer;
2051
- for (const element of elementsRef.current.values()) {
2052
- observer.observe(element);
2053
- }
3028
+ setVirtualizer(created);
2054
3029
  return () => {
2055
- observer.disconnect();
2056
- observerRef.current = null;
3030
+ created.destroy();
2057
3031
  };
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
- }
3032
+ }, [enabled, bodyScrollRef, estimatedRowHeight, overscan]);
3033
+ useLayoutEffect12(() => {
3034
+ virtualizer?.setRowCount(rows.length);
3035
+ virtualizer?.resetHeights();
3036
+ }, [virtualizer, rows]);
2114
3037
  return {
2115
3038
  startIndex: range.startIndex,
2116
3039
  endIndex: range.endIndex,
2117
3040
  offsetBefore: range.offsetBefore,
2118
3041
  offsetAfter: range.offsetAfter,
2119
- measureRow,
2120
- scrollToIndex
3042
+ measureRow: virtualizer?.measureRow ?? ignoreRow,
3043
+ scrollToIndex: (index, options) => {
3044
+ virtualizer?.scrollToIndex(index, options);
3045
+ }
2121
3046
  };
2122
3047
  }
2123
3048
 
2124
3049
  // src/DataGrid/DataGrid.tsx
2125
- import { jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
2126
- var DEFAULT_PAGE_SIZE = 25;
3050
+ import { jsx as jsx20, jsxs as jsxs13 } from "react/jsx-runtime";
2127
3051
  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
- }) {
3052
+ function paginationReader(controller) {
3053
+ let last;
3054
+ return () => {
3055
+ const { pageIndex } = controller.getView();
3056
+ const { pageSize } = controller.getState().pagination;
3057
+ if (last?.pageIndex !== pageIndex || last.pageSize !== pageSize) {
3058
+ last = { pageIndex, pageSize };
3059
+ }
3060
+ return last;
3061
+ };
3062
+ }
3063
+ function selectedRowsReader(controller) {
3064
+ let last;
3065
+ return () => {
3066
+ const { rows } = controller.getView();
3067
+ const selection = controller.getState().rowSelection;
3068
+ if (last?.rows !== rows || last.selection !== selection) {
3069
+ const rowsById = new Map(rows.map((entry) => [entry.rowId, entry]));
3070
+ last = { rows, selection, selected: resolveRows(rowsById, selection) };
3071
+ }
3072
+ return last.selected;
3073
+ };
3074
+ }
3075
+ function DataGrid(props) {
3076
+ const {
3077
+ borders,
3078
+ hoverable,
3079
+ selectable,
3080
+ resizeMode = "fit",
3081
+ columnSizeDefaults,
3082
+ sortableColumns = false,
3083
+ groupableColumns = false,
3084
+ groupToggleIconColumns = true,
3085
+ groupByBarVisibility = "auto",
3086
+ paginated = false,
3087
+ pager,
3088
+ infiniteScroll,
3089
+ aggregates = NO_AGGREGATES,
3090
+ groupAggregateDisplay = "inline",
3091
+ height,
3092
+ virtualized = false,
3093
+ overscan = 4,
3094
+ estimatedRowHeight = 40,
3095
+ label,
3096
+ labelledBy,
3097
+ locale,
3098
+ getRowProps,
3099
+ onRowClick,
3100
+ onRowDoubleClick,
3101
+ messages: messageOverrides,
3102
+ ref
3103
+ } = props;
2174
3104
  const hoverRows = hoverable?.rows ?? true;
2175
3105
  const hoverColumns = hoverable?.columns ?? true;
2176
3106
  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);
3107
+ const viewportRef = useRef13(null);
3108
+ const headerTableRef = useRef13(null);
3109
+ const bodyTableRef = useRef13(null);
3110
+ const bodyScrollRef = useRef13(null);
3111
+ const sentinelRef = useRef13(null);
2182
3112
  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
3113
+ const subscribersRef = useRef13(/* @__PURE__ */ new Set());
3114
+ const gridId = useId();
3115
+ const announce = useLiveAnnouncer(viewportRef);
3116
+ const messages = useMemo4(
3117
+ () => resolveMessages(messageOverrides),
3118
+ [messageOverrides]
2208
3119
  );
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]
2227
- );
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,
3120
+ const { controller, state, view } = useGridController({
3121
+ ...props,
2240
3122
  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
- );
3123
+ ...announcedCallbacks({
3124
+ callbacks: props,
3125
+ announce,
3126
+ messages,
3127
+ columnName,
3128
+ columnOf
3129
+ })
3130
+ });
3131
+ const [readPagination] = useState15(() => paginationReader(controller));
3132
+ const [readSelectedRows] = useState15(() => selectedRowsReader(controller));
2285
3133
  const virtualizer = useRowVirtualizer({
2286
3134
  enabled: virtualized,
2287
- rows: paginatedRows.rows,
3135
+ rows: view.pageRows,
2288
3136
  bodyScrollRef,
2289
3137
  estimatedRowHeight,
2290
3138
  overscan
2291
3139
  });
2292
- useEffect4(() => {
3140
+ useEffect5(() => {
2293
3141
  if (virtualized && height === void 0) {
2294
3142
  console.error(
2295
- "DataGridComponent: `virtualized` has no effect without `height` \u2014 a body with no bounded height has no viewport to window rows against."
3143
+ "DataGrid: `virtualized` has no effect without `height` \u2014 a body with no bounded height has no viewport to window rows against."
2296
3144
  );
2297
3145
  }
2298
3146
  }, [virtualized, height]);
2299
3147
  const hasInfiniteScroll = infiniteScroll !== void 0;
2300
- useEffect4(() => {
3148
+ useEffect5(() => {
2301
3149
  if (paginated && hasInfiniteScroll) {
2302
3150
  console.error(
2303
- "DataGridComponent: `paginated` and `infiniteScroll` are mutually exclusive \u2014 only `paginated` takes effect while both are set."
3151
+ "DataGrid: `paginated` and `infiniteScroll` are mutually exclusive \u2014 only `paginated` takes effect while both are set."
2304
3152
  );
2305
3153
  }
2306
3154
  }, [paginated, hasInfiniteScroll]);
@@ -2309,79 +3157,21 @@ function DataGridComponent({
2309
3157
  sentinelRef,
2310
3158
  config: infiniteScroll
2311
3159
  });
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
- 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);
3160
+ function columnOf(columnId) {
3161
+ return view.dataColumns.find((candidate) => candidate.id === columnId)?.column;
2334
3162
  }
2335
- function handleColumnResize(event) {
2336
- onColumnResize?.(event);
2337
- if (event.phase === "end") {
2338
- announce(
2339
- `${columnName(event.columnId)}, ${String(Math.round(event.width))} pixels wide`
2340
- );
2341
- }
3163
+ function columnName(columnId) {
3164
+ const column = columnOf(columnId);
3165
+ return column === void 0 ? columnId : resolveColumnHeaderText(column);
2342
3166
  }
2343
3167
  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.
3168
+ // `@gridkitjs/dom`'s `measureColumnContent` only ever matches
3169
+ // `td[data-gridkit-column]` (never `th`), so the header table has nothing
3170
+ // for it to find — the body table alone was already the effective scope
3171
+ // before this split.
2347
3172
  tableRef: bodyTableRef,
2348
- sizing,
2349
- setSizing,
2350
- columnSizeDefaults,
2351
- onColumnResize: handleColumnResize
2352
- });
2353
- function handleGroupByChange(event) {
2354
- onGroupByChange?.(event);
2355
- const entry = event.groupBy.find(
2356
- (candidate) => candidate.columnId === event.columnId
2357
- );
2358
- if (entry === void 0) {
2359
- announce(`${columnName(event.columnId)}, grouping removed`);
2360
- return;
2361
- }
2362
- 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)}`
2364
- );
2365
- }
2366
- function handleGroupExpansionChange(event) {
2367
- onGroupExpansionChange?.(event);
2368
- if (event.groupId === null) {
2369
- announce(
2370
- event.expansion.length === 0 ? "All groups expanded" : "All groups collapsed"
2371
- );
2372
- return;
2373
- }
2374
- announce(
2375
- event.expansion.includes(event.groupId) ? "Group collapsed" : "Group expanded"
2376
- );
2377
- }
2378
- const grouping = useRowGrouping({
2379
- groupBy,
2380
- setGroupBy,
2381
- expansion,
2382
- setExpansion,
2383
- onGroupByChange: handleGroupByChange,
2384
- onGroupExpansionChange: handleGroupExpansionChange
3173
+ controller,
3174
+ columnSizeDefaults
2385
3175
  });
2386
3176
  const nav = useGridNavigation({
2387
3177
  headerTableRef,
@@ -2392,131 +3182,106 @@ function DataGridComponent({
2392
3182
  // Group headers are addressable rows too — the whole point of the flat
2393
3183
  // `DisplayRow[]` shape is that they share one position space with data
2394
3184
  // rows, so navigation counts them the same way. Page-relative
2395
- // (`paginatedRows`, not `displayRows`): arrow keys operate on what's
3185
+ // (`view.pageRows`, not `view.displayRows`): arrow keys operate on what's
2396
3186
  // actually in the DOM, which is the current page — a different "page"
2397
3187
  // concept than `useGridNavigation`'s own Page Up/Down viewport scrolling.
2398
- rowCount: paginatedRows.rows.length,
2399
- columnCount: resolved.length,
3188
+ rowCount: view.pageRows.length,
3189
+ columnCount: view.columns.length,
2400
3190
  // A group's own summary row (`groupAggregateDisplay: "row"`) occupies a
2401
3191
  // real slot in `rowCount` above — its DOM position has to line up with
2402
3192
  // everything else — but is presentational, never a tab stop: arrow-key
2403
3193
  // vertical movement steps over it rather than landing there.
2404
3194
  isSkippableRow: (rowIndex) => {
2405
- const entry = paginatedRows.rows[rowIndex];
3195
+ const entry = view.pageRows[rowIndex];
2406
3196
  return entry !== void 0 && "kind" in entry && entry.kind === "group-summary";
2407
3197
  }
2408
3198
  });
3199
+ const panels = useGridPanels({ rootRef: viewportRef, headerTableRef, nav });
3200
+ const commands = useGridCommands({
3201
+ controller,
3202
+ commands: props.commands,
3203
+ onCommand: props.onCommand,
3204
+ messages,
3205
+ announce,
3206
+ resize,
3207
+ openPanel: panels.open
3208
+ });
3209
+ const orderedIds = useMemo4(
3210
+ () => view.dataColumns.map((entry) => entry.id),
3211
+ [view.dataColumns]
3212
+ );
2409
3213
  const displayedIds = useMemo4(
2410
- () => orderedColumns.map((column) => column.id ?? column.field),
2411
- [orderedColumns]
3214
+ () => view.columns.map((entry) => entry.id),
3215
+ [view.columns]
3216
+ );
3217
+ const definitions = useMemo4(
3218
+ () => new Map(view.dataColumns.map((entry) => [entry.id, entry.column])),
3219
+ [view.dataColumns]
2412
3220
  );
2413
3221
  const groupByDraggableIds = useMemo4(
2414
3222
  () => new Set(
2415
- resolved.filter((entry) => entry.groupByDraggable).map((entry) => entry.id)
3223
+ view.columns.filter((entry) => entry.groupByDraggable).map((entry) => entry.id)
2416
3224
  ),
2417
- [resolved]
3225
+ [view.columns]
2418
3226
  );
2419
3227
  function handleDrop(target, movedId) {
2420
3228
  if (target.kind === "group-by") {
2421
- grouping.moveGroupBy(movedId, target.beforeColumnId);
3229
+ controller.moveGroupBy(movedId, target.beforeColumnId);
2422
3230
  return;
2423
3231
  }
2424
- const next = moveColumnBefore(displayedIds, movedId, target.beforeId);
2425
- if (next === displayedIds) {
3232
+ const next = moveColumnBefore(orderedIds, movedId, target.beforeId);
3233
+ if (next === orderedIds) {
2426
3234
  return;
2427
3235
  }
2428
- const position = next.indexOf(movedId);
2429
- setOrder(next);
2430
- onColumnOrderChange?.({ columnId: movedId, order: next });
3236
+ controller.moveColumn(movedId, target.beforeId);
3237
+ const shown = next.filter((id) => displayedIds.includes(id));
3238
+ const position = shown.indexOf(movedId);
2431
3239
  announce(
2432
- `${columnName(movedId)}, column ${String(position + 1)} of ${String(next.length)}`
3240
+ messages.columnMoved(columnName(movedId), position + 1, shown.length)
2433
3241
  );
2434
3242
  nav.focusCell(HEADER_ROW, position);
2435
3243
  }
2436
3244
  const drag = useColumnDrag({
2437
3245
  order: displayedIds,
2438
- groupBy,
3246
+ groupBy: state.groupBy,
2439
3247
  groupByDraggableIds,
2440
3248
  onDrop: handleDrop
2441
3249
  });
2442
- const draggedColumnAlreadyGrouped = drag.draggedColumnId !== null && groupBy.some((entry) => entry.columnId === drag.draggedColumnId);
3250
+ const draggedColumnAlreadyGrouped = drag.draggedColumnId !== null && state.groupBy.some((entry) => entry.columnId === drag.draggedColumnId);
2443
3251
  const dragEligibleForGroupBy = drag.draggedColumnId !== null && groupByDraggableIds.has(drag.draggedColumnId) && !draggedColumnAlreadyGrouped;
2444
- const showGroupByBar = groupByBarVisibility === "always" || groupByBarVisibility === "auto" && (groupBy.length > 0 || dragEligibleForGroupBy);
3252
+ const showGroupByBar = groupByBarVisibility === "always" || groupByBarVisibility === "auto" && (state.groupBy.length > 0 || dragEligibleForGroupBy);
2445
3253
  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,
3254
+ const pagerContext = {
3255
+ pagination: state.pagination,
3256
+ currentPage: Math.min(state.pagination.pageIndex + 1, view.pageCount),
3257
+ pageCount: view.pageCount,
3258
+ pageSizeOptions: pager?.sizeOptions,
3259
+ goToPage: controller.goToPage,
3260
+ nextPage: controller.nextPage,
3261
+ previousPage: controller.previousPage,
3262
+ setPageSize: controller.setPageSize
3263
+ };
3264
+ const selection = useGridSelection({ controller, state, selectable });
3265
+ const multiselectable = selection.rowMode === "multiple" || selection.columnMode === "multiple";
3266
+ const contextMenu = useContextMenu({
3267
+ rootRef: viewportRef,
3268
+ headerTableRef,
3269
+ bodyTableRef,
3270
+ bodyScrollRef,
3271
+ contextMenu: props.contextMenu,
3272
+ onContextMenuOpenChange: props.onContextMenuOpenChange,
3273
+ controller,
3274
+ commands,
3275
+ nav,
3276
+ selection,
3277
+ messages,
2496
3278
  columnName
2497
3279
  });
2498
- const multiselectable = selection.rowMode === "multiple" || selection.columnMode === "multiple";
2499
- useEffect4(() => {
3280
+ useEffect5(() => {
2500
3281
  for (const listener of subscribersRef.current) {
2501
3282
  listener();
2502
3283
  }
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
- ]);
3284
+ }, [view, state, nav.focus]);
2520
3285
  useImperativeHandle(ref, () => ({
2521
3286
  get element() {
2522
3287
  return viewportRef.current;
@@ -2524,38 +3289,70 @@ function DataGridComponent({
2524
3289
  get table() {
2525
3290
  return bodyTableRef.current;
2526
3291
  },
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,
3292
+ getRows: () => controller.getView().rows,
3293
+ // Every group header's own `aggregates` field is populated here — the
3294
+ // whole reason `getDisplayRows()` is where a consumer reads a specific
3295
+ // group's computed results from.
3296
+ getDisplayRows: () => controller.getView().displayRows,
3297
+ getPageRows: () => controller.getView().pageRows,
3298
+ getColumns: () => controller.getView().columns,
3299
+ getDataColumns: () => controller.getView().dataColumns,
3300
+ getColumnSizing: () => controller.getState().columnSizing,
3301
+ getColumnOrder: () => controller.getState().columnOrder,
3302
+ getColumnVisibility: () => controller.getState().columnVisibility,
3303
+ getColumnSort: () => controller.getState().columnSort,
3304
+ getFilter: () => controller.getState().filter,
3305
+ getGroupBy: () => controller.getState().groupBy,
3306
+ getGroupExpansion: () => controller.getState().groupExpansion,
3307
+ getPagination: readPagination,
3308
+ getPageCount: () => controller.getView().pageCount,
3309
+ getAggregates: () => controller.getView().grandTotal,
3310
+ getRowSelection: () => controller.getState().rowSelection,
3311
+ getSelectedRows: readSelectedRows,
3312
+ getColumnSelection: () => controller.getState().columnSelection,
3313
+ getCellSelection: () => controller.getState().cellSelection,
2545
3314
  getFocusedCell: () => nav.focus,
3315
+ getState: controller.getState,
3316
+ restoreState: controller.restoreState,
2546
3317
  focusCell: nav.focusCell,
2547
- clearSelection: selection.clear,
2548
- selectAllRows: selection.selectAllRows,
2549
- expandAllGroups: grouping.expandAll,
2550
- collapseAllGroups: () => {
2551
- grouping.collapseAll(displayRows);
3318
+ clearSelection: controller.clearSelection,
3319
+ selectAllRows: controller.selectAllRows,
3320
+ selectRows: controller.selectRows,
3321
+ expandAllGroups: controller.expandAllGroups,
3322
+ collapseAllGroups: controller.collapseAllGroups,
3323
+ goToPage: controller.goToPage,
3324
+ nextPage: controller.nextPage,
3325
+ previousPage: controller.previousPage,
3326
+ setPageSize: controller.setPageSize,
3327
+ showColumns: controller.showColumns,
3328
+ hideColumns: controller.hideColumns,
3329
+ setFilter: controller.setFilter,
3330
+ setColumnSort: controller.setColumnSort,
3331
+ setGroupBy: controller.setGroupBy,
3332
+ setGroupExpansion: controller.setGroupExpansion,
3333
+ setPagination: controller.setPagination,
3334
+ setColumnSizing: controller.setColumnSizing,
3335
+ setColumnOrder: controller.setColumnOrder,
3336
+ setColumnVisibility: controller.setColumnVisibility,
3337
+ setRowSelection: controller.setRowSelection,
3338
+ setColumnSelection: controller.setColumnSelection,
3339
+ setCellSelection: controller.setCellSelection,
3340
+ autoFitColumn: (columnId) => {
3341
+ commands.host.autoFitColumns([columnId]);
2552
3342
  },
2553
- goToPage: paginationApi.goToPage,
2554
- nextPage: paginationApi.nextPage,
2555
- previousPage: paginationApi.previousPage,
2556
- setPageSize: paginationApi.setPageSize,
3343
+ autoFitColumns: (columnIds) => {
3344
+ commands.host.autoFitColumns(
3345
+ columnIds ?? controller.getView().columns.map((entry) => entry.id)
3346
+ );
3347
+ },
3348
+ openContextMenu: contextMenu.open,
3349
+ closeContextMenu: contextMenu.close,
3350
+ openColumnChooser: () => {
3351
+ panels.open("columnChooser", { area: "grid" });
3352
+ },
3353
+ runCommand: (commandId, target = { area: "grid" }) => commands.run(commandId, target, "api"),
2557
3354
  scrollToRow: (rowId, options) => {
2558
- const index = paginatedRows.rows.findIndex(
3355
+ const index = controller.getView().pageRows.findIndex(
2559
3356
  (entry) => !("kind" in entry) && entry.rowId === rowId
2560
3357
  );
2561
3358
  if (index === -1) return;
@@ -2594,25 +3391,41 @@ function DataGridComponent({
2594
3391
  selection.columnMode === false ? "" : "selectable-columns",
2595
3392
  selection.cellMode === false ? "" : "selectable-cells"
2596
3393
  );
2597
- return /* @__PURE__ */ jsxs7("div", { className: "gridkit-data-grid-viewport", ref: viewportRef, children: [
2598
- showGroupByBar && /* @__PURE__ */ jsx10(
3394
+ return /* @__PURE__ */ jsxs13("div", { className: "gridkit-data-grid-viewport", ref: viewportRef, children: [
3395
+ /* @__PURE__ */ jsx20(
3396
+ GridToolbar,
3397
+ {
3398
+ toolbar: props.toolbar,
3399
+ renderItem: props.renderToolbarItem,
3400
+ gridId,
3401
+ controller,
3402
+ commands,
3403
+ messages,
3404
+ announce
3405
+ }
3406
+ ),
3407
+ showGroupByBar && /* @__PURE__ */ jsx20(
2599
3408
  GroupByBar,
2600
3409
  {
2601
- columns: resolved,
2602
- grouping,
3410
+ columns: view.columns,
3411
+ definitions,
3412
+ groupBy: state.groupBy,
3413
+ controller,
2603
3414
  columnName,
2604
3415
  headerDragEligible: dragEligibleForGroupBy,
2605
3416
  headerDragBlocked: draggedColumnAlreadyGrouped,
2606
- headerDropTarget: headerGroupByDropTarget
3417
+ headerDropTarget: headerGroupByDropTarget,
3418
+ messages
2607
3419
  }
2608
3420
  ),
2609
- /* @__PURE__ */ jsxs7(
3421
+ /* @__PURE__ */ jsxs13(
2610
3422
  "div",
2611
3423
  {
3424
+ id: gridId,
2612
3425
  className: "gridkit-data-grid-tables",
2613
- role: groupBy.length > 0 ? "treegrid" : "grid",
2614
- "aria-rowcount": aggregatedRows.length + 1,
2615
- "aria-colcount": resolved.length,
3426
+ role: state.groupBy.length > 0 ? "treegrid" : "grid",
3427
+ "aria-rowcount": view.displayRows.length + 1,
3428
+ "aria-colcount": view.columns.length,
2616
3429
  ...ariaAttr(multiselectable, "aria-multiselectable", true),
2617
3430
  ...ariaAttr(labelledBy !== void 0, "aria-labelledby", labelledBy),
2618
3431
  ...ariaAttr(
@@ -2621,6 +3434,7 @@ function DataGridComponent({
2621
3434
  label
2622
3435
  ),
2623
3436
  onKeyDown: (event) => {
3437
+ if (!isOwnEvent(event)) return;
2624
3438
  if (event.key === "Escape") {
2625
3439
  if (drag.draggedColumnId !== null || resize.activeColumnId !== null)
2626
3440
  return;
@@ -2634,25 +3448,27 @@ function DataGridComponent({
2634
3448
  }
2635
3449
  },
2636
3450
  children: [
2637
- /* @__PURE__ */ jsxs7(
3451
+ /* @__PURE__ */ jsxs13(
2638
3452
  "table",
2639
3453
  {
2640
3454
  ref: headerTableRef,
2641
- style: { width: totalColumnWidth(resolved) },
3455
+ style: { width: totalColumnWidth(view.columns) },
2642
3456
  className: classNames(gridTableClassName, "gridkit-data-grid-header"),
2643
3457
  children: [
2644
- /* @__PURE__ */ jsx10(GridColgroup, { columns: resolved }),
2645
- /* @__PURE__ */ jsx10(
3458
+ /* @__PURE__ */ jsx20(GridColgroup, { columns: view.columns }),
3459
+ /* @__PURE__ */ jsx20(
2646
3460
  GridHeader,
2647
3461
  {
2648
- columns: resolved,
3462
+ columns: view.columns,
2649
3463
  resize,
2650
3464
  drag,
2651
- sort: columnSort,
3465
+ controller,
3466
+ columnSort: state.columnSort,
2652
3467
  sortableColumns,
2653
- grouping,
3468
+ groupBy: state.groupBy,
2654
3469
  groupableColumns,
2655
3470
  groupToggleIconColumns,
3471
+ headerMenu: contextMenu.opens("header"),
2656
3472
  nav,
2657
3473
  selection
2658
3474
  }
@@ -2660,7 +3476,7 @@ function DataGridComponent({
2660
3476
  ]
2661
3477
  }
2662
3478
  ),
2663
- /* @__PURE__ */ jsx10(
3479
+ /* @__PURE__ */ jsx20(
2664
3480
  "div",
2665
3481
  {
2666
3482
  className: "gridkit-data-grid-body",
@@ -2668,28 +3484,36 @@ function DataGridComponent({
2668
3484
  style: height === void 0 ? void 0 : {
2669
3485
  "--gridkit-body-height": typeof height === "number" ? `${String(height)}px` : height
2670
3486
  },
2671
- children: /* @__PURE__ */ jsxs7(
3487
+ children: /* @__PURE__ */ jsxs13(
2672
3488
  "table",
2673
3489
  {
2674
3490
  ref: bodyTableRef,
2675
- style: { width: totalColumnWidth(resolved) },
3491
+ style: { width: totalColumnWidth(view.columns) },
2676
3492
  className: gridTableClassName,
2677
3493
  children: [
2678
- /* @__PURE__ */ jsx10(GridColgroup, { columns: resolved }),
2679
- /* @__PURE__ */ jsx10(
3494
+ /* @__PURE__ */ jsx20(GridColgroup, { columns: view.columns }),
3495
+ /* @__PURE__ */ jsx20(
2680
3496
  GridBody,
2681
3497
  {
2682
- columns: resolved,
2683
- rows: paginatedRows.rows,
3498
+ columns: view.columns,
3499
+ definitions,
3500
+ rows: view.pageRows,
2684
3501
  activeColumnId: resize.activeColumnId,
2685
3502
  nav,
2686
3503
  selection,
2687
- grouping,
2688
- aggregates: activeAggregates,
3504
+ controller,
3505
+ aggregates,
2689
3506
  groupAggregateDisplay,
2690
3507
  virtualRange: virtualized ? virtualizer : null,
2691
3508
  infiniteScroll,
2692
- sentinelRef
3509
+ sentinelRef,
3510
+ locale,
3511
+ getRowProps,
3512
+ onRowClick,
3513
+ onRowDoubleClick,
3514
+ messages,
3515
+ cellMenu: contextMenu.opens("cell"),
3516
+ groupMenu: contextMenu.opens("group")
2693
3517
  }
2694
3518
  )
2695
3519
  ]
@@ -2697,23 +3521,24 @@ function DataGridComponent({
2697
3521
  )
2698
3522
  }
2699
3523
  ),
2700
- activeAggregates.length > 0 && /* @__PURE__ */ jsxs7(
3524
+ aggregates.length > 0 && /* @__PURE__ */ jsxs13(
2701
3525
  "table",
2702
3526
  {
2703
- style: { width: totalColumnWidth(resolved) },
3527
+ style: { width: totalColumnWidth(view.columns) },
2704
3528
  className: classNames(
2705
3529
  gridTableClassName,
2706
3530
  "gridkit-data-grid-footer"
2707
3531
  ),
2708
3532
  children: [
2709
- /* @__PURE__ */ jsx10(GridColgroup, { columns: resolved }),
2710
- /* @__PURE__ */ jsx10(
3533
+ /* @__PURE__ */ jsx20(GridColgroup, { columns: view.columns }),
3534
+ /* @__PURE__ */ jsx20(
2711
3535
  GridFooter,
2712
3536
  {
2713
- columns: resolved,
2714
- aggregates: activeAggregates,
2715
- results: grandTotal,
2716
- rows: shownRows.map((entry) => entry.row)
3537
+ columns: view.columns,
3538
+ aggregates,
3539
+ results: view.grandTotal,
3540
+ rows: view.rows.map((entry) => entry.row),
3541
+ locale
2717
3542
  }
2718
3543
  )
2719
3544
  ]
@@ -2722,27 +3547,22 @@ function DataGridComponent({
2722
3547
  ]
2723
3548
  }
2724
3549
  ),
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 })
3550
+ /* @__PURE__ */ jsx20(GridContextMenu, { contextMenu }),
3551
+ /* @__PURE__ */ jsx20(GridPanels, { panels, controller, messages }),
3552
+ paginated && (pager?.template ? pager.template(pagerContext) : /* @__PURE__ */ jsx20(GridPager, { pager: pagerContext, config: pager, messages }))
2736
3553
  ] });
2737
3554
  }
2738
3555
 
3556
+ // src/index.ts
3557
+ import { serializeGridState } from "@gridkitjs/core";
3558
+
2739
3559
  // src/DataGrid/hooks/usePaginationState.ts
2740
- import { useCallback as useCallback4 } from "react";
3560
+ import { useCallback as useCallback3 } from "react";
2741
3561
 
2742
3562
  // src/DataGrid/hooks/useGridSnapshot.ts
2743
- import { useCallback as useCallback3, useSyncExternalStore } from "react";
3563
+ import { useCallback as useCallback2, useSyncExternalStore as useSyncExternalStore3 } from "react";
2744
3564
  function useGridSnapshot(gridRef, getSnapshot, fallback) {
2745
- const subscribe = useCallback3(
3565
+ const subscribe = useCallback2(
2746
3566
  (listener) => {
2747
3567
  const api = gridRef.current;
2748
3568
  if (api === null) {
@@ -2753,7 +3573,7 @@ function useGridSnapshot(gridRef, getSnapshot, fallback) {
2753
3573
  },
2754
3574
  [gridRef]
2755
3575
  );
2756
- return useSyncExternalStore(subscribe, () => {
3576
+ return useSyncExternalStore3(subscribe, () => {
2757
3577
  const api = gridRef.current;
2758
3578
  return api === null ? fallback : getSnapshot(api);
2759
3579
  });
@@ -2764,12 +3584,12 @@ var FALLBACK_PAGINATION = { pageIndex: 0, pageSize: 0 };
2764
3584
  function usePaginationState(gridRef) {
2765
3585
  const pagination = useGridSnapshot(
2766
3586
  gridRef,
2767
- useCallback4((api) => api.getPagination(), []),
3587
+ useCallback3((api) => api.getPagination(), []),
2768
3588
  FALLBACK_PAGINATION
2769
3589
  );
2770
3590
  const pageCount = useGridSnapshot(
2771
3591
  gridRef,
2772
- useCallback4((api) => api.getPageCount(), []),
3592
+ useCallback3((api) => api.getPageCount(), []),
2773
3593
  0
2774
3594
  );
2775
3595
  return {
@@ -2791,18 +3611,18 @@ function usePaginationState(gridRef) {
2791
3611
  }
2792
3612
 
2793
3613
  // src/DataGrid/hooks/useGroupByState.ts
2794
- import { useCallback as useCallback5 } from "react";
3614
+ import { useCallback as useCallback4 } from "react";
2795
3615
  var FALLBACK_GROUP_BY = [];
2796
3616
  var FALLBACK_EXPANSION = [];
2797
3617
  function useGroupByState(gridRef) {
2798
3618
  const groupBy = useGridSnapshot(
2799
3619
  gridRef,
2800
- useCallback5((api) => api.getGroupBy(), []),
3620
+ useCallback4((api) => api.getGroupBy(), []),
2801
3621
  FALLBACK_GROUP_BY
2802
3622
  );
2803
3623
  const groupExpansion = useGridSnapshot(
2804
3624
  gridRef,
2805
- useCallback5((api) => api.getGroupExpansion(), []),
3625
+ useCallback4((api) => api.getGroupExpansion(), []),
2806
3626
  FALLBACK_EXPANSION
2807
3627
  );
2808
3628
  return {
@@ -2818,34 +3638,34 @@ function useGroupByState(gridRef) {
2818
3638
  }
2819
3639
 
2820
3640
  // src/DataGrid/hooks/useColumnSortState.ts
2821
- import { useCallback as useCallback6 } from "react";
3641
+ import { useCallback as useCallback5 } from "react";
2822
3642
  var FALLBACK_SORT = [];
2823
3643
  function useColumnSortState(gridRef) {
2824
3644
  return useGridSnapshot(
2825
3645
  gridRef,
2826
- useCallback6((api) => api.getColumnSort(), []),
3646
+ useCallback5((api) => api.getColumnSort(), []),
2827
3647
  FALLBACK_SORT
2828
3648
  );
2829
3649
  }
2830
3650
 
2831
3651
  // src/DataGrid/hooks/useSelectionState.ts
2832
- import { useCallback as useCallback7 } from "react";
3652
+ import { useCallback as useCallback6 } from "react";
2833
3653
  var FALLBACK_SELECTION = [];
2834
3654
  var FALLBACK_CELL_SELECTION = null;
2835
3655
  function useSelectionState(gridRef) {
2836
3656
  const rowSelection = useGridSnapshot(
2837
3657
  gridRef,
2838
- useCallback7((api) => api.getRowSelection(), []),
3658
+ useCallback6((api) => api.getRowSelection(), []),
2839
3659
  FALLBACK_SELECTION
2840
3660
  );
2841
3661
  const columnSelection = useGridSnapshot(
2842
3662
  gridRef,
2843
- useCallback7((api) => api.getColumnSelection(), []),
3663
+ useCallback6((api) => api.getColumnSelection(), []),
2844
3664
  FALLBACK_SELECTION
2845
3665
  );
2846
3666
  const cellSelection = useGridSnapshot(
2847
3667
  gridRef,
2848
- useCallback7((api) => api.getCellSelection(), []),
3668
+ useCallback6((api) => api.getCellSelection(), []),
2849
3669
  FALLBACK_CELL_SELECTION
2850
3670
  );
2851
3671
  return {
@@ -2862,43 +3682,74 @@ function useSelectionState(gridRef) {
2862
3682
  }
2863
3683
 
2864
3684
  // src/DataGrid/hooks/useAggregateState.ts
2865
- import { useCallback as useCallback8 } from "react";
3685
+ import { useCallback as useCallback7 } from "react";
2866
3686
  var FALLBACK_AGGREGATES = /* @__PURE__ */ new Map();
2867
3687
  function useAggregateState(gridRef) {
2868
3688
  return useGridSnapshot(
2869
3689
  gridRef,
2870
- useCallback8((api) => api.getAggregates(), []),
3690
+ useCallback7((api) => api.getAggregates(), []),
2871
3691
  FALLBACK_AGGREGATES
2872
3692
  );
2873
3693
  }
2874
3694
 
2875
3695
  // src/DataGrid/hooks/useColumnSizingState.ts
2876
- import { useCallback as useCallback9 } from "react";
3696
+ import { useCallback as useCallback8 } from "react";
2877
3697
  var FALLBACK_SIZING = {};
2878
3698
  function useColumnSizingState(gridRef) {
2879
3699
  return useGridSnapshot(
2880
3700
  gridRef,
2881
- useCallback9((api) => api.getColumnSizing(), []),
3701
+ useCallback8((api) => api.getColumnSizing(), []),
2882
3702
  FALLBACK_SIZING
2883
3703
  );
2884
3704
  }
2885
3705
 
2886
3706
  // src/DataGrid/hooks/useColumnOrderState.ts
2887
- import { useCallback as useCallback10 } from "react";
3707
+ import { useCallback as useCallback9 } from "react";
2888
3708
  var FALLBACK_ORDER = [];
2889
3709
  function useColumnOrderState(gridRef) {
2890
3710
  return useGridSnapshot(
2891
3711
  gridRef,
2892
- useCallback10((api) => api.getColumnOrder(), []),
3712
+ useCallback9((api) => api.getColumnOrder(), []),
2893
3713
  FALLBACK_ORDER
2894
3714
  );
2895
3715
  }
3716
+
3717
+ // src/DataGrid/hooks/useColumnVisibilityState.ts
3718
+ import { useCallback as useCallback10 } from "react";
3719
+ var FALLBACK_VISIBILITY = {};
3720
+ function useColumnVisibilityState(gridRef) {
3721
+ return useGridSnapshot(
3722
+ gridRef,
3723
+ useCallback10((api) => api.getColumnVisibility(), []),
3724
+ FALLBACK_VISIBILITY
3725
+ );
3726
+ }
3727
+
3728
+ // src/DataGrid/hooks/useFilterState.ts
3729
+ import { useCallback as useCallback11, useState as useState16 } from "react";
3730
+ function useFilterState(gridRef) {
3731
+ const [fallback] = useState16(() => []);
3732
+ const filter = useGridSnapshot(
3733
+ gridRef,
3734
+ useCallback11((api) => api.getFilter(), []),
3735
+ fallback
3736
+ );
3737
+ return {
3738
+ filter,
3739
+ setFilter: (next) => {
3740
+ gridRef.current?.setFilter(next);
3741
+ }
3742
+ };
3743
+ }
2896
3744
  export {
2897
- DataGridComponent,
3745
+ DataGrid,
3746
+ serializeGridState,
2898
3747
  useAggregateState,
2899
3748
  useColumnOrderState,
2900
3749
  useColumnSizingState,
2901
3750
  useColumnSortState,
3751
+ useColumnVisibilityState,
3752
+ useFilterState,
2902
3753
  useGroupByState,
2903
3754
  usePaginationState,
2904
3755
  useSelectionState