zubin-grid 0.4.13 → 0.42.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/README.md +171 -36
  2. package/dist/adaptors/react/index.d.ts +71 -0
  3. package/dist/adaptors/react/index.d.ts.map +1 -0
  4. package/dist/adaptors/react/index.js +249 -0
  5. package/dist/adaptors/react/index.js.map +1 -0
  6. package/dist/core/cell.d.ts +17 -8
  7. package/dist/core/cell.d.ts.map +1 -1
  8. package/dist/core/cell.js +57 -39
  9. package/dist/core/cell.js.map +1 -1
  10. package/dist/core/grid-id.d.ts +7 -0
  11. package/dist/core/grid-id.d.ts.map +1 -0
  12. package/dist/core/grid-id.js +51 -0
  13. package/dist/core/grid-id.js.map +1 -0
  14. package/dist/core/grid.d.ts +9 -9
  15. package/dist/core/grid.d.ts.map +1 -1
  16. package/dist/core/grid.js +608 -199
  17. package/dist/core/grid.js.map +1 -1
  18. package/dist/core/head.d.ts +6 -12
  19. package/dist/core/head.d.ts.map +1 -1
  20. package/dist/core/head.js +20 -69
  21. package/dist/core/head.js.map +1 -1
  22. package/dist/core/helpers.d.ts +3 -3
  23. package/dist/core/helpers.d.ts.map +1 -1
  24. package/dist/core/helpers.js +5 -2
  25. package/dist/core/helpers.js.map +1 -1
  26. package/dist/core/persist.d.ts.map +1 -1
  27. package/dist/core/persist.js +53 -42
  28. package/dist/core/persist.js.map +1 -1
  29. package/dist/core/tail.d.ts +5 -10
  30. package/dist/core/tail.d.ts.map +1 -1
  31. package/dist/core/tail.js +7 -32
  32. package/dist/core/tail.js.map +1 -1
  33. package/dist/core/types/grid.types.d.ts +79 -32
  34. package/dist/core/types/grid.types.d.ts.map +1 -1
  35. package/dist/core/types/head.types.d.ts +7 -6
  36. package/dist/core/types/head.types.d.ts.map +1 -1
  37. package/dist/core/types/persist.types.d.ts +2 -0
  38. package/dist/core/types/persist.types.d.ts.map +1 -1
  39. package/dist/core/types/tail.types.d.ts +3 -2
  40. package/dist/core/types/tail.types.d.ts.map +1 -1
  41. package/dist/index.d.ts +7 -7
  42. package/dist/index.d.ts.map +1 -1
  43. package/dist/index.js +5 -5
  44. package/dist/index.js.map +1 -1
  45. package/package.json +5 -1
  46. package/dist/core/gridPersist.d.ts +0 -3
  47. package/dist/core/gridPersist.d.ts.map +0 -1
  48. package/dist/core/gridPersist.js +0 -2
  49. package/dist/core/gridPersist.js.map +0 -1
  50. package/dist/core/types/gridPersist.types.d.ts +0 -2
  51. package/dist/core/types/gridPersist.types.d.ts.map +0 -1
  52. package/dist/core/types/gridPersist.types.js +0 -2
  53. package/dist/core/types/gridPersist.types.js.map +0 -1
package/dist/core/grid.js CHANGED
@@ -1,6 +1,6 @@
1
- import { useCallback, useEffect, useRef, useSyncExternalStore } from "react";
2
1
  import { cell } from "./cell.js";
3
- import { createGridPersistController, defaultGridPersistAdapter, } from "./gridPersist.js";
2
+ import { assertGridId, createGridIdKey, formatGridId } from "./grid-id.js";
3
+ import { createGridPersistController, defaultGridPersistAdapter } from "./persist.js";
4
4
  import { assertHeadId, createHeadCellMap, createHeadOrderIndex, getHeadCell, getOrderedHeads, } from "./head.js";
5
5
  import { createTailCellMap, getTailCell, setTailCellResult } from "./tail.js";
6
6
  export function grid(input, options) {
@@ -13,12 +13,277 @@ export function grid(input, options) {
13
13
  const stateAdapter = createSchemaStateAdapter(rowCellKey, columnCellKey);
14
14
  return createGridStore(normalizedRowHeads, normalizedColumnHeads, createGridInitialCellsFromState(initialState.cells, stateAdapter), stateAdapter, schemaOptions.persist);
15
15
  }
16
- export function createSubGrid(parentGrid, cellsOrOptionsOrPersist, persist) {
17
- const { initialCells, persistOption } = resolveCreateSubGridArgs(cellsOrOptionsOrPersist, persist);
16
+ export function createDimensionGrid(parentGrid, initialCells, dimension, options) {
17
+ const readBoundIds = () => (dimension === "rows"
18
+ ? parentGrid.rowHeaders
19
+ : parentGrid.colHeaders);
20
+ const emptyRowHeaders = [];
21
+ const emptyColumnHeaders = [];
22
+ const dimensionSubscribers = new Set();
23
+ const resolvedPersistOption = createDimensionGridPersistOption(parentGrid, dimension, options?.persist);
24
+ let isApplyingState = false;
25
+ let boundIds = [...readBoundIds()];
26
+ const cellsById = new Map(boundIds.map((boundId, index) => [
27
+ createGridIdKey(boundId),
28
+ cell(initialCells[index]),
29
+ ]));
30
+ const dirtyBoundIdKeys = new Set();
31
+ const persistedCellValuesById = new Map(boundIds.map((boundId, index) => [createGridIdKey(boundId), initialCells[index]]));
32
+ let dimensionGridApi;
33
+ const resetPersistedDimensionCells = () => {
34
+ persistedCellValuesById.clear();
35
+ boundIds.forEach((boundId) => {
36
+ const idKey = createGridIdKey(boundId);
37
+ persistedCellValuesById.set(idKey, cellsById.get(idKey)?.get());
38
+ });
39
+ dirtyBoundIdKeys.clear();
40
+ };
41
+ const syncDimensionCellDirty = (boundId) => {
42
+ const idKey = createGridIdKey(boundId);
43
+ const currentValue = cellsById.get(idKey)?.get();
44
+ if (!persistedCellValuesById.has(idKey)) {
45
+ if (currentValue === undefined)
46
+ dirtyBoundIdKeys.delete(idKey);
47
+ else
48
+ dirtyBoundIdKeys.add(idKey);
49
+ return;
50
+ }
51
+ if (haveSameRuntimeValue(currentValue, persistedCellValuesById.get(idKey))) {
52
+ dirtyBoundIdKeys.delete(idKey);
53
+ return;
54
+ }
55
+ dirtyBoundIdKeys.add(idKey);
56
+ };
57
+ const getCellAtIndex = (index) => {
58
+ if (!Number.isInteger(index) || index < 0 || index >= boundIds.length) {
59
+ throw new Error(`Dimension grid ${dimension} index ${index} is out of range. ${describeIndexRange(boundIds.length, index)}`);
60
+ }
61
+ const boundId = boundIds[index];
62
+ const currentCell = cellsById.get(createGridIdKey(boundId));
63
+ if (!currentCell) {
64
+ throw new Error(`Missing dimension cell for ${dimension} index ${index}.`);
65
+ }
66
+ return currentCell;
67
+ };
68
+ const readCellAtIndex = (index) => {
69
+ const currentCell = getCellAtIndex(index);
70
+ const boundId = boundIds[index];
71
+ return {
72
+ value: currentCell.get(),
73
+ meta: {
74
+ existsInDb: currentCell.get() !== undefined,
75
+ isDirty: dirtyBoundIdKeys.has(createGridIdKey(boundId)),
76
+ },
77
+ };
78
+ };
79
+ const getState = () => ({
80
+ dimension,
81
+ cells: boundIds.map((boundId) => {
82
+ const currentCell = cellsById.get(createGridIdKey(boundId));
83
+ if (!currentCell) {
84
+ throw new Error(`Missing dimension cell for ${dimension} id "${formatGridId(boundId)}".`);
85
+ }
86
+ return currentCell.get();
87
+ }),
88
+ });
89
+ const applyDimensionCells = (nextCells, mode) => {
90
+ const changedIndices = [];
91
+ const limit = mode === "update" ? Math.min(nextCells.length, boundIds.length) : boundIds.length;
92
+ isApplyingState = true;
93
+ try {
94
+ for (let index = 0; index < limit; index += 1) {
95
+ const currentCell = getCellAtIndex(index);
96
+ const nextValue = nextCells[index];
97
+ if (Object.is(currentCell.get(), nextValue)) {
98
+ continue;
99
+ }
100
+ currentCell.set(nextValue);
101
+ changedIndices.push(index);
102
+ }
103
+ if (mode === "replace") {
104
+ for (let index = nextCells.length; index < boundIds.length; index += 1) {
105
+ const currentCell = getCellAtIndex(index);
106
+ if (Object.is(currentCell.get(), undefined)) {
107
+ continue;
108
+ }
109
+ currentCell.set(undefined);
110
+ changedIndices.push(index);
111
+ }
112
+ }
113
+ }
114
+ finally {
115
+ isApplyingState = false;
116
+ }
117
+ changedIndices.forEach((index) => {
118
+ const boundId = boundIds[index];
119
+ if (boundId !== undefined) {
120
+ syncDimensionCellDirty(boundId);
121
+ }
122
+ });
123
+ return changedIndices;
124
+ };
125
+ const { hydrate, markStateChanged } = createGridPersistController(resolvedPersistOption, {
126
+ getState,
127
+ replaceState: (nextState) => {
128
+ applyDimensionCells(normalizeDimensionGridStateInput(nextState, dimension).cells, "replace");
129
+ },
130
+ isApplyingState: () => isApplyingState,
131
+ onHydratedStateApplied: resetPersistedDimensionCells,
132
+ onPersistedStateApplied: resetPersistedDimensionCells,
133
+ });
134
+ const emitDimensionGridUpdate = (diff, persistStateChanged = false) => {
135
+ if (persistStateChanged) {
136
+ markStateChanged();
137
+ }
138
+ [...dimensionSubscribers].forEach((callback) => {
139
+ callback(dimensionGridApi, diff);
140
+ });
141
+ };
142
+ const syncDimensionFromParent = () => {
143
+ const nextBoundIds = [...readBoundIds()];
144
+ if (haveSameItems(boundIds, nextBoundIds)) {
145
+ return;
146
+ }
147
+ const nextCellsById = new Map(nextBoundIds.map((boundId) => [
148
+ createGridIdKey(boundId),
149
+ cellsById.get(createGridIdKey(boundId)) ?? cell(undefined),
150
+ ]));
151
+ const previousSize = boundIds.length;
152
+ boundIds = nextBoundIds;
153
+ cellsById.clear();
154
+ nextCellsById.forEach((currentCell, boundIdKey) => {
155
+ cellsById.set(boundIdKey, currentCell);
156
+ });
157
+ const nextIdKeys = new Set(nextBoundIds.map((boundId) => createGridIdKey(boundId)));
158
+ [...persistedCellValuesById.keys()].forEach((idKey) => {
159
+ if (nextIdKeys.has(idKey))
160
+ return;
161
+ persistedCellValuesById.delete(idKey);
162
+ dirtyBoundIdKeys.delete(idKey);
163
+ });
164
+ nextBoundIds.forEach((boundId) => {
165
+ const idKey = createGridIdKey(boundId);
166
+ if (!persistedCellValuesById.has(idKey)) {
167
+ persistedCellValuesById.set(idKey, undefined);
168
+ }
169
+ syncDimensionCellDirty(boundId);
170
+ });
171
+ emitDimensionGridUpdate({
172
+ type: "dimension",
173
+ action: previousSize === nextBoundIds.length ? "update" : "resize",
174
+ source: "parent",
175
+ dimension,
176
+ size: nextBoundIds.length,
177
+ previousSize,
178
+ }, true);
179
+ };
180
+ const setGrid = (nextState, mode = "replace") => {
181
+ const changedIndices = applyDimensionCells(normalizeDimensionGridStateInput(nextState, dimension).cells, mode);
182
+ if (changedIndices.length === 0) {
183
+ return;
184
+ }
185
+ emitDimensionGridUpdate({
186
+ type: "cells",
187
+ action: mode === "update" ? "update" : "replace",
188
+ source: "setGrid",
189
+ dimension,
190
+ size: boundIds.length,
191
+ indices: changedIndices,
192
+ }, true);
193
+ };
194
+ const clearGrid = () => {
195
+ const changedIndices = applyDimensionCells([], "replace");
196
+ if (changedIndices.length === 0) {
197
+ return;
198
+ }
199
+ emitDimensionGridUpdate({
200
+ type: "cells",
201
+ action: "clear",
202
+ source: "clearGrid",
203
+ dimension,
204
+ size: boundIds.length,
205
+ indices: changedIndices,
206
+ }, true);
207
+ };
208
+ dimensionGridApi = {
209
+ dimension,
210
+ get size() {
211
+ return boundIds.length;
212
+ },
213
+ get rowHeaders() {
214
+ return dimension === "rows" ? parentGrid.rowHeaders : emptyRowHeaders;
215
+ },
216
+ get colHeaders() {
217
+ return dimension === "columns" ? parentGrid.colHeaders : emptyColumnHeaders;
218
+ },
219
+ getState,
220
+ setGrid,
221
+ readCell: readCellAtIndex,
222
+ setCell: (index, nextValue) => {
223
+ const boundId = boundIds[index];
224
+ const currentCell = getCellAtIndex(index);
225
+ const resolvedNextValue = resolveUpdater(nextValue, currentCell.get());
226
+ if (Object.is(currentCell.get(), resolvedNextValue)) {
227
+ return;
228
+ }
229
+ isApplyingState = true;
230
+ try {
231
+ currentCell.set(resolvedNextValue);
232
+ }
233
+ finally {
234
+ isApplyingState = false;
235
+ }
236
+ if (boundId !== undefined) {
237
+ syncDimensionCellDirty(boundId);
238
+ }
239
+ emitDimensionGridUpdate({
240
+ type: "cells",
241
+ action: resolvedNextValue === undefined ? "clear" : "update",
242
+ source: "setCell",
243
+ dimension,
244
+ size: boundIds.length,
245
+ indices: [index],
246
+ }, true);
247
+ },
248
+ getCell: getCellAtIndex,
249
+ getValue: (index) => getCellAtIndex(index).get(),
250
+ hasCell: (index) => Number.isInteger(index) && index >= 0 && index < boundIds.length,
251
+ clearGrid,
252
+ subscribeGrid: (callback) => {
253
+ dimensionSubscribers.add(callback);
254
+ return () => {
255
+ dimensionSubscribers.delete(callback);
256
+ };
257
+ },
258
+ };
259
+ Object.defineProperty(dimensionGridApi, "__persistOption", {
260
+ value: resolvedPersistOption,
261
+ enumerable: false,
262
+ configurable: true,
263
+ });
264
+ hydrate();
265
+ void parentGrid.subscribeGrid((_currentParentGrid, diff) => {
266
+ if (dimension === "rows") {
267
+ if (diff.type === "rows" ||
268
+ diff.type === "row-head" ||
269
+ (diff.type === "grid" && didGridDiffAffectAxes(diff))) {
270
+ syncDimensionFromParent();
271
+ }
272
+ return;
273
+ }
274
+ if (diff.type === "columns" ||
275
+ diff.type === "column-head" ||
276
+ (diff.type === "grid" && didGridDiffAffectAxes(diff))) {
277
+ syncDimensionFromParent();
278
+ }
279
+ });
280
+ return dimensionGridApi;
281
+ }
282
+ export function createSubGrid(parentGrid, initialCells = [], options) {
18
283
  const parentState = parentGrid.getState();
19
284
  const stateAdapter = createGridStateCellAdapter();
20
285
  const resolvedInitialCells = assertGridStateCellsWithinAxes(initialCells, parentState.rows, parentState.columns, "Sub grid cells");
21
- const subGridStore = createGridStore(parentState.rows, parentState.columns, createGridInitialCellsFromState(resolvedInitialCells, stateAdapter), stateAdapter, createSubGridPersistOption(parentGrid, persistOption));
286
+ const subGridStore = createGridStore(parentState.rows, parentState.columns, createGridInitialCellsFromState(resolvedInitialCells, stateAdapter), stateAdapter, createSubGridPersistOption(parentGrid, options?.persist));
22
287
  const getSubGridState = () => {
23
288
  const nextParentState = parentGrid.getState();
24
289
  return createSubGridState(nextParentState.rows, nextParentState.columns, subGridStore.getState().cells);
@@ -146,7 +411,12 @@ function createGridStore(initialRows, initialColumns, initialCells, stateAdapter
146
411
  const columnTailUpdaters = new Map();
147
412
  const cellsMap = new Map();
148
413
  const cellSubscriptions = new Map();
414
+ const dirtyCellKeys = new Set();
149
415
  const gridSubscribers = new Set();
416
+ let persistedCellValues = new Map(initialCells.map(({ rowId, columnId, cell: currentCell }) => [
417
+ createGridKey(rowId, columnId),
418
+ currentCell.get(),
419
+ ]));
150
420
  let nextRowFallbackOrder = initialRows.length;
151
421
  let nextColumnFallbackOrder = initialColumns.length;
152
422
  let isApplyingState = false;
@@ -173,14 +443,52 @@ function createGridStore(initialRows, initialColumns, initialCells, stateAdapter
173
443
  const normalizeCellValue = (rowId, columnId, value) => {
174
444
  return stateAdapter.deserializeCell(stateAdapter.serializeCell(rowId, columnId, value)).value;
175
445
  };
446
+ const assertCellAxesExist = (rowId, columnId) => {
447
+ if (!rowHeadCells.has(createGridIdKey(rowId))) {
448
+ throw new Error(`Missing row header "${formatGridId(rowId)}".`);
449
+ }
450
+ if (!columnHeadCells.has(createGridIdKey(columnId))) {
451
+ throw new Error(`Missing column header "${formatGridId(columnId)}".`);
452
+ }
453
+ };
454
+ const syncDirtyCellKey = (gridKey) => {
455
+ const currentCell = cellsMap.get(gridKey);
456
+ if (!persistedCellValues.has(gridKey)) {
457
+ if (!currentCell)
458
+ dirtyCellKeys.delete(gridKey);
459
+ else
460
+ dirtyCellKeys.add(gridKey);
461
+ return;
462
+ }
463
+ if (currentCell &&
464
+ haveSameRuntimeValue(currentCell.get(), persistedCellValues.get(gridKey))) {
465
+ dirtyCellKeys.delete(gridKey);
466
+ return;
467
+ }
468
+ dirtyCellKeys.add(gridKey);
469
+ };
470
+ const refreshDirtyCellKeys = () => {
471
+ const nextGridKeys = new Set([...persistedCellValues.keys(), ...cellsMap.keys()]);
472
+ dirtyCellKeys.clear();
473
+ nextGridKeys.forEach((gridKey) => {
474
+ syncDirtyCellKey(gridKey);
475
+ });
476
+ };
477
+ const resetPersistedCellState = () => {
478
+ persistedCellValues = new Map([...cellsMap.entries()].map(([gridKey, currentCell]) => [
479
+ gridKey,
480
+ currentCell.get(),
481
+ ]));
482
+ dirtyCellKeys.clear();
483
+ };
176
484
  const recomputeRowTailInternal = (rowId) => {
177
- const onRowUpdate = rowTailUpdaters.get(rowId);
485
+ const onRowUpdate = rowTailUpdaters.get(createGridIdKey(rowId));
178
486
  if (!onRowUpdate)
179
487
  return false;
180
488
  return setTailCellResult(getTailCell(rowTailCells, rowId, "row"), onRowUpdate(getRowCells(rowId)));
181
489
  };
182
490
  const recomputeColumnTailInternal = (columnId) => {
183
- const onColumnUpdate = columnTailUpdaters.get(columnId);
491
+ const onColumnUpdate = columnTailUpdaters.get(createGridIdKey(columnId));
184
492
  if (!onColumnUpdate)
185
493
  return false;
186
494
  return setTailCellResult(getTailCell(columnTailCells, columnId, "column"), onColumnUpdate(getColumnCells(columnId)));
@@ -209,13 +517,11 @@ function createGridStore(initialRows, initialColumns, initialCells, stateAdapter
209
517
  cellsMap.delete(gridKey);
210
518
  };
211
519
  const attachCell = (rowId, columnId, currentCell) => {
212
- if (!rowHeadCells.has(rowId))
213
- throw new Error(`Missing row header "${rowId}".`);
214
- if (!columnHeadCells.has(columnId))
215
- throw new Error(`Missing column header "${columnId}".`);
520
+ assertCellAxesExist(rowId, columnId);
216
521
  const gridKey = createGridKey(rowId, columnId);
217
- if (cellsMap.has(gridKey))
218
- throw new Error(`Duplicate cell for row "${rowId}" and column "${columnId}".`);
522
+ if (cellsMap.has(gridKey)) {
523
+ throw new Error(`Duplicate cell for row "${formatGridId(rowId)}" and column "${formatGridId(columnId)}".`);
524
+ }
219
525
  cellsMap.set(gridKey, currentCell);
220
526
  let previousValue = currentCell.get();
221
527
  const unsubscribe = currentCell.subscribe(() => {
@@ -224,6 +530,7 @@ function createGridStore(initialRows, initialColumns, initialCells, stateAdapter
224
530
  previousValue = nextValue;
225
531
  return;
226
532
  }
533
+ syncDirtyCellKey(gridKey);
227
534
  const rowTailIds = recomputeRowTailInternal(rowId) ? [rowId] : [];
228
535
  const columnTailIds = recomputeColumnTailInternal(columnId) ? [columnId] : [];
229
536
  emitGridUpdate({
@@ -243,11 +550,25 @@ function createGridStore(initialRows, initialColumns, initialCells, stateAdapter
243
550
  cellSubscriptions.set(gridKey, unsubscribe);
244
551
  };
245
552
  const getCell = (rowId, columnId) => {
553
+ assertCellAxesExist(rowId, columnId);
246
554
  const currentCell = cellsMap.get(createGridKey(rowId, columnId));
247
- if (!currentCell)
248
- throw new Error(`Missing cell for row "${rowId}" and column "${columnId}".`);
555
+ if (!currentCell) {
556
+ throw new Error(`Missing cell for row "${formatGridId(rowId)}" and column "${formatGridId(columnId)}".`);
557
+ }
249
558
  return currentCell;
250
559
  };
560
+ const readCell = (rowId, columnId) => {
561
+ assertCellAxesExist(rowId, columnId);
562
+ const gridKey = createGridKey(rowId, columnId);
563
+ const currentCell = cellsMap.get(gridKey);
564
+ return {
565
+ value: currentCell?.get(),
566
+ meta: {
567
+ existsInDb: currentCell !== undefined,
568
+ isDirty: dirtyCellKeys.has(gridKey),
569
+ },
570
+ };
571
+ };
251
572
  const createAxisCellSnapshot = (rowId, columnId) => {
252
573
  const currentCell = getCell(rowId, columnId);
253
574
  return {
@@ -272,15 +593,16 @@ function createGridStore(initialRows, initialColumns, initialCells, stateAdapter
272
593
  assertUniqueHeadIds(nextHeads, axis);
273
594
  const nextIds = new Set();
274
595
  nextHeads.forEach((nextHead, index) => {
275
- nextIds.add(nextHead.id);
276
- const currentHeadCell = headCells.get(nextHead.id);
596
+ const idKey = createGridIdKey(nextHead.id);
597
+ nextIds.add(idKey);
598
+ const currentHeadCell = headCells.get(idKey);
277
599
  if (currentHeadCell)
278
600
  currentHeadCell.set(nextHead);
279
601
  else
280
- headCells.set(nextHead.id, cell(nextHead));
281
- if (!tailCells.has(nextHead.id))
282
- tailCells.set(nextHead.id, cell(createEmptyTailState()));
283
- headOrder.set(nextHead.id, index);
602
+ headCells.set(idKey, cell(nextHead));
603
+ if (!tailCells.has(idKey))
604
+ tailCells.set(idKey, cell(createEmptyTailState()));
605
+ headOrder.set(idKey, index);
284
606
  });
285
607
  [...headCells.keys()].forEach((id) => {
286
608
  if (nextIds.has(id))
@@ -294,24 +616,24 @@ function createGridStore(initialRows, initialColumns, initialCells, stateAdapter
294
616
  const replaceState = (nextState) => {
295
617
  isApplyingState = true;
296
618
  try {
297
- syncAxisHeads(nextState.rows, "row", rowHeadCells, rowTailCells, rowHeadOrder, (id) => {
298
- rowTailUpdaters.delete(id);
619
+ syncAxisHeads(nextState.rows, "row", rowHeadCells, rowTailCells, rowHeadOrder, (idKey) => {
620
+ rowTailUpdaters.delete(idKey);
299
621
  });
300
- syncAxisHeads(nextState.columns, "column", columnHeadCells, columnTailCells, columnHeadOrder, (id) => {
301
- columnTailUpdaters.delete(id);
622
+ syncAxisHeads(nextState.columns, "column", columnHeadCells, columnTailCells, columnHeadOrder, (idKey) => {
623
+ columnTailUpdaters.delete(idKey);
302
624
  });
303
625
  const nextCells = new Map();
304
626
  nextState.cells.forEach((nextStateCell) => {
305
627
  const { rowId, columnId, value } = stateAdapter.deserializeCell(nextStateCell);
306
- if (!rowHeadCells.has(rowId)) {
307
- throw new Error(`Missing row header "${rowId}".`);
628
+ if (!rowHeadCells.has(createGridIdKey(rowId))) {
629
+ throw new Error(`Missing row header "${formatGridId(rowId)}".`);
308
630
  }
309
- if (!columnHeadCells.has(columnId)) {
310
- throw new Error(`Missing column header "${columnId}".`);
631
+ if (!columnHeadCells.has(createGridIdKey(columnId))) {
632
+ throw new Error(`Missing column header "${formatGridId(columnId)}".`);
311
633
  }
312
634
  const gridKey = createGridKey(rowId, columnId);
313
635
  if (nextCells.has(gridKey)) {
314
- throw new Error(`Duplicate cell for row "${rowId}" and column "${columnId}".`);
636
+ throw new Error(`Duplicate cell for row "${formatGridId(rowId)}" and column "${formatGridId(columnId)}".`);
315
637
  }
316
638
  nextCells.set(gridKey, {
317
639
  rowId,
@@ -342,6 +664,7 @@ function createGridStore(initialRows, initialColumns, initialCells, stateAdapter
342
664
  const rowTailIds = recomputeAllRowTails();
343
665
  const columnTailIds = recomputeAllColumnTails();
344
666
  refreshAxisIdsSnapshot();
667
+ refreshDirtyCellKeys();
345
668
  return {
346
669
  rowTailIds,
347
670
  columnTailIds,
@@ -351,6 +674,8 @@ function createGridStore(initialRows, initialColumns, initialCells, stateAdapter
351
674
  getState,
352
675
  replaceState,
353
676
  isApplyingState: () => isApplyingState,
677
+ onHydratedStateApplied: resetPersistedCellState,
678
+ onPersistedStateApplied: resetPersistedCellState,
354
679
  });
355
680
  const emitGridUpdate = (diff, persistStateChanged = false) => {
356
681
  refreshAxisIdsSnapshot();
@@ -365,8 +690,8 @@ function createGridStore(initialRows, initialColumns, initialCells, stateAdapter
365
690
  if (nextCells.length === 0) {
366
691
  return null;
367
692
  }
368
- const touchedRowIds = new Set();
369
- const touchedColumnIds = new Set();
693
+ const touchedRowIds = new Map();
694
+ const touchedColumnIds = new Map();
370
695
  const normalizedCells = [];
371
696
  const previousCells = [];
372
697
  const cellKeys = [];
@@ -374,10 +699,12 @@ function createGridStore(initialRows, initialColumns, initialCells, stateAdapter
374
699
  try {
375
700
  nextCells.forEach((nextStateCell) => {
376
701
  const { rowId, columnId, value } = stateAdapter.deserializeCell(nextStateCell);
377
- if (!rowHeadCells.has(rowId))
378
- throw new Error(`Missing row header "${rowId}".`);
379
- if (!columnHeadCells.has(columnId))
380
- throw new Error(`Missing column header "${columnId}".`);
702
+ if (!rowHeadCells.has(createGridIdKey(rowId))) {
703
+ throw new Error(`Missing row header "${formatGridId(rowId)}".`);
704
+ }
705
+ if (!columnHeadCells.has(createGridIdKey(columnId))) {
706
+ throw new Error(`Missing column header "${formatGridId(columnId)}".`);
707
+ }
381
708
  const normalizedValue = normalizeCellValue(rowId, columnId, value);
382
709
  const gridKey = createGridKey(rowId, columnId);
383
710
  const currentCell = cellsMap.get(gridKey);
@@ -389,16 +716,19 @@ function createGridStore(initialRows, initialColumns, initialCells, stateAdapter
389
716
  attachCell(rowId, columnId, cell(normalizedValue));
390
717
  }
391
718
  normalizedCells.push(stateAdapter.serializeCell(rowId, columnId, normalizedValue));
392
- touchedRowIds.add(rowId);
393
- touchedColumnIds.add(columnId);
719
+ touchedRowIds.set(createGridIdKey(rowId), rowId);
720
+ touchedColumnIds.set(createGridIdKey(columnId), columnId);
394
721
  cellKeys.push(gridKey);
395
722
  });
396
723
  }
397
724
  finally {
398
725
  isApplyingState = false;
399
726
  }
400
- const rowIds = [...touchedRowIds];
401
- const columnIds = [...touchedColumnIds];
727
+ cellKeys.forEach((gridKey) => {
728
+ syncDirtyCellKey(gridKey);
729
+ });
730
+ const rowIds = [...touchedRowIds.values()];
731
+ const columnIds = [...touchedColumnIds.values()];
402
732
  return {
403
733
  rowIds,
404
734
  columnIds,
@@ -431,35 +761,34 @@ function createGridStore(initialRows, initialColumns, initialCells, stateAdapter
431
761
  if (nextRowHeads.length === 0) {
432
762
  return null;
433
763
  }
434
- const rowIds = new Set();
764
+ const rowIds = new Map();
435
765
  const rows = [];
436
766
  const previousRows = [];
437
767
  let shouldRecomputeColumns = false;
438
768
  nextRowHeads.forEach((nextRowHead) => {
439
769
  const rowHeadLike = nextRowHead;
440
- if (typeof rowHeadLike.id !== "string") {
441
- throw new Error("Row header upserts must include a string id.");
442
- }
443
- const currentHeadCell = rowHeadCells.get(rowHeadLike.id);
770
+ const rowIdKey = createGridIdKey(rowHeadLike.id);
771
+ assertGridId(rowHeadLike.id, "Row header upserts");
772
+ const currentHeadCell = rowHeadCells.get(rowIdKey);
444
773
  const currentHead = currentHeadCell?.get();
445
774
  const resolvedHead = normalizeUpsertHead(rowHeadLike, currentHead, nextRowFallbackOrder);
446
775
  if (currentHeadCell) {
447
776
  currentHeadCell.set(resolvedHead);
448
777
  }
449
778
  else {
450
- rowHeadCells.set(resolvedHead.id, cell(resolvedHead));
451
- rowTailCells.set(resolvedHead.id, cell(createEmptyTailState()));
452
- rowHeadOrder.set(resolvedHead.id, nextRowFallbackOrder);
779
+ rowHeadCells.set(rowIdKey, cell(resolvedHead));
780
+ rowTailCells.set(rowIdKey, cell(createEmptyTailState()));
781
+ rowHeadOrder.set(rowIdKey, nextRowFallbackOrder);
453
782
  nextRowFallbackOrder += 1;
454
783
  }
455
784
  rows.push(resolvedHead);
456
785
  if (currentHead) {
457
786
  previousRows.push(currentHead);
458
787
  }
459
- rowIds.add(resolvedHead.id);
788
+ rowIds.set(rowIdKey, resolvedHead.id);
460
789
  shouldRecomputeColumns ||= !currentHead || currentHead.order !== resolvedHead.order;
461
790
  });
462
- const resolvedRowIds = [...rowIds];
791
+ const resolvedRowIds = [...rowIds.values()];
463
792
  return {
464
793
  rows,
465
794
  previousRows,
@@ -488,35 +817,34 @@ function createGridStore(initialRows, initialColumns, initialCells, stateAdapter
488
817
  if (nextColumnHeads.length === 0) {
489
818
  return null;
490
819
  }
491
- const columnIds = new Set();
820
+ const columnIds = new Map();
492
821
  const columns = [];
493
822
  const previousColumns = [];
494
823
  let shouldRecomputeRows = false;
495
824
  nextColumnHeads.forEach((nextColumnHead) => {
496
825
  const columnHeadLike = nextColumnHead;
497
- if (typeof columnHeadLike.id !== "string") {
498
- throw new Error("Column header upserts must include a string id.");
499
- }
500
- const currentHeadCell = columnHeadCells.get(columnHeadLike.id);
826
+ const columnIdKey = createGridIdKey(columnHeadLike.id);
827
+ assertGridId(columnHeadLike.id, "Column header upserts");
828
+ const currentHeadCell = columnHeadCells.get(columnIdKey);
501
829
  const currentHead = currentHeadCell?.get();
502
830
  const resolvedHead = normalizeUpsertHead(columnHeadLike, currentHead, nextColumnFallbackOrder);
503
831
  if (currentHeadCell) {
504
832
  currentHeadCell.set(resolvedHead);
505
833
  }
506
834
  else {
507
- columnHeadCells.set(resolvedHead.id, cell(resolvedHead));
508
- columnTailCells.set(resolvedHead.id, cell(createEmptyTailState()));
509
- columnHeadOrder.set(resolvedHead.id, nextColumnFallbackOrder);
835
+ columnHeadCells.set(columnIdKey, cell(resolvedHead));
836
+ columnTailCells.set(columnIdKey, cell(createEmptyTailState()));
837
+ columnHeadOrder.set(columnIdKey, nextColumnFallbackOrder);
510
838
  nextColumnFallbackOrder += 1;
511
839
  }
512
840
  columns.push(resolvedHead);
513
841
  if (currentHead) {
514
842
  previousColumns.push(currentHead);
515
843
  }
516
- columnIds.add(resolvedHead.id);
844
+ columnIds.set(columnIdKey, resolvedHead.id);
517
845
  shouldRecomputeRows ||= !currentHead || currentHead.order !== resolvedHead.order;
518
846
  });
519
- const resolvedColumnIds = [...columnIds];
847
+ const resolvedColumnIds = [...columnIds.values()];
520
848
  return {
521
849
  columns,
522
850
  previousColumns,
@@ -597,6 +925,66 @@ function createGridStore(initialRows, initialColumns, initialCells, stateAdapter
597
925
  const upsertCell = (nextCell) => {
598
926
  emitCellUpsert("upsertCell", [nextCell]);
599
927
  };
928
+ const setCell = (rowId, columnId, nextValue) => {
929
+ assertCellAxesExist(rowId, columnId);
930
+ const gridKey = createGridKey(rowId, columnId);
931
+ const currentCell = cellsMap.get(gridKey);
932
+ const currentValue = currentCell?.get();
933
+ const resolvedNextValue = resolveUpdater(nextValue, currentValue);
934
+ if (resolvedNextValue === undefined) {
935
+ if (!currentCell) {
936
+ return;
937
+ }
938
+ detachCell(gridKey);
939
+ syncDirtyCellKey(gridKey);
940
+ const rowTailIds = recomputeRowTailInternal(rowId) ? [rowId] : [];
941
+ const columnTailIds = recomputeColumnTailInternal(columnId) ? [columnId] : [];
942
+ emitGridUpdate({
943
+ type: "cells",
944
+ action: "clear",
945
+ source: "setCell",
946
+ rowIds: [rowId],
947
+ columnIds: [columnId],
948
+ rowTailIds: toOptionalArray(rowTailIds),
949
+ columnTailIds: toOptionalArray(columnTailIds),
950
+ cellKeys: [gridKey],
951
+ previousCells: [
952
+ stateAdapter.serializeCell(rowId, columnId, currentValue),
953
+ ],
954
+ }, true);
955
+ return;
956
+ }
957
+ const normalizedValue = normalizeCellValue(rowId, columnId, resolvedNextValue);
958
+ isApplyingState = true;
959
+ try {
960
+ if (currentCell) {
961
+ currentCell.set(normalizedValue);
962
+ }
963
+ else {
964
+ attachCell(rowId, columnId, cell(normalizedValue));
965
+ }
966
+ }
967
+ finally {
968
+ isApplyingState = false;
969
+ }
970
+ syncDirtyCellKey(gridKey);
971
+ const rowTailIds = recomputeRowTailInternal(rowId) ? [rowId] : [];
972
+ const columnTailIds = recomputeColumnTailInternal(columnId) ? [columnId] : [];
973
+ emitGridUpdate({
974
+ type: "cells",
975
+ action: currentCell ? "update" : "upsert",
976
+ source: "setCell",
977
+ rowIds: [rowId],
978
+ columnIds: [columnId],
979
+ rowTailIds: toOptionalArray(rowTailIds),
980
+ columnTailIds: toOptionalArray(columnTailIds),
981
+ cellKeys: [gridKey],
982
+ cells: [stateAdapter.serializeCell(rowId, columnId, normalizedValue)],
983
+ previousCells: currentCell
984
+ ? [stateAdapter.serializeCell(rowId, columnId, currentValue)]
985
+ : undefined,
986
+ }, true);
987
+ };
600
988
  function getState() {
601
989
  const rows = getOrderedRowHeads();
602
990
  const columns = getOrderedColumnHeads();
@@ -644,23 +1032,25 @@ function createGridStore(initialRows, initialColumns, initialCells, stateAdapter
644
1032
  });
645
1033
  };
646
1034
  function registerRowTail(rowId, onRowUpdate) {
647
- rowTailUpdaters.set(rowId, onRowUpdate);
1035
+ const rowIdKey = createGridIdKey(rowId);
1036
+ rowTailUpdaters.set(rowIdKey, onRowUpdate);
648
1037
  recomputeRowTailInternal(rowId);
649
1038
  return () => {
650
- if (rowTailUpdaters.get(rowId) !== onRowUpdate) {
1039
+ if (rowTailUpdaters.get(rowIdKey) !== onRowUpdate) {
651
1040
  return;
652
1041
  }
653
- rowTailUpdaters.delete(rowId);
1042
+ rowTailUpdaters.delete(rowIdKey);
654
1043
  };
655
1044
  }
656
1045
  function registerColumnTail(columnId, onColumnUpdate) {
657
- columnTailUpdaters.set(columnId, onColumnUpdate);
1046
+ const columnIdKey = createGridIdKey(columnId);
1047
+ columnTailUpdaters.set(columnIdKey, onColumnUpdate);
658
1048
  recomputeColumnTailInternal(columnId);
659
1049
  return () => {
660
- if (columnTailUpdaters.get(columnId) !== onColumnUpdate) {
1050
+ if (columnTailUpdaters.get(columnIdKey) !== onColumnUpdate) {
661
1051
  return;
662
1052
  }
663
- columnTailUpdaters.delete(columnId);
1053
+ columnTailUpdaters.delete(columnIdKey);
664
1054
  };
665
1055
  }
666
1056
  const setGrid = (nextState, mode = "replace") => {
@@ -804,31 +1194,10 @@ function createGridStore(initialRows, initialColumns, initialCells, stateAdapter
804
1194
  },
805
1195
  getState,
806
1196
  setGrid,
1197
+ readCell,
1198
+ setCell,
807
1199
  getCell,
808
1200
  getValue: (rowId, columnId) => getCell(rowId, columnId).get(),
809
- __setCellValue: (rowId, columnId, newValue) => {
810
- const currentCell = getCell(rowId, columnId);
811
- isApplyingState = true;
812
- try {
813
- currentCell.set(normalizeCellValue(rowId, columnId, newValue));
814
- }
815
- finally {
816
- isApplyingState = false;
817
- }
818
- const rowTailIds = recomputeRowTailInternal(rowId) ? [rowId] : [];
819
- const columnTailIds = recomputeColumnTailInternal(columnId) ? [columnId] : [];
820
- emitGridUpdate({
821
- type: "cells",
822
- action: "update",
823
- source: "cell.set",
824
- rowIds: [rowId],
825
- columnIds: [columnId],
826
- rowTailIds: toOptionalArray(rowTailIds),
827
- columnTailIds: toOptionalArray(columnTailIds),
828
- cellKeys: [createGridKey(rowId, columnId)],
829
- cells: [stateAdapter.serializeCell(rowId, columnId, currentCell.get())],
830
- }, true);
831
- },
832
1201
  hasCell: (rowId, columnId) => cellsMap.has(createGridKey(rowId, columnId)),
833
1202
  getRowHead: (rowId) => getHeadCell(rowHeadCells, rowId, "row").get(),
834
1203
  getColumnHead: (columnId) => getHeadCell(columnHeadCells, columnId, "column").get(),
@@ -871,6 +1240,11 @@ function createGridStore(initialRows, initialColumns, initialCells, stateAdapter
871
1240
  recomputeRowTail,
872
1241
  recomputeColumnTail,
873
1242
  };
1243
+ Object.defineProperty(gridApi, "__persistOption", {
1244
+ value: persist,
1245
+ enumerable: false,
1246
+ configurable: true,
1247
+ });
874
1248
  return gridApi;
875
1249
  }
876
1250
  function resolveUpdater(nextValue, currentValue) {
@@ -879,45 +1253,49 @@ function resolveUpdater(nextValue, currentValue) {
879
1253
  : nextValue;
880
1254
  }
881
1255
  export function createGridKey(rowId, columnId) {
882
- return `${rowId}:${columnId}`;
883
- }
884
- export function useGrid(currentGrid, options) {
885
- const snapshotRef = useRef({
886
- rows: currentGrid.rowHeaders,
887
- cols: currentGrid.colHeaders,
888
- });
889
- const onGridUpdateRef = useRef(options?.onGridUpdate);
890
- useEffect(() => {
891
- onGridUpdateRef.current = options?.onGridUpdate;
892
- }, [options?.onGridUpdate]);
893
- useEffect(() => {
894
- return currentGrid.subscribeGrid((updatedGrid, diff) => {
895
- onGridUpdateRef.current?.(updatedGrid, diff);
896
- });
897
- }, [currentGrid]);
898
- const subscribe = useCallback((callback) => currentGrid.subscribeGrid(() => callback()), [currentGrid]);
899
- const getSnapshot = useCallback(() => {
900
- const nextRows = currentGrid.rowHeaders;
901
- const nextCols = currentGrid.colHeaders;
902
- const currentSnapshot = snapshotRef.current;
903
- if (currentSnapshot.rows === nextRows && currentSnapshot.cols === nextCols) {
904
- return currentSnapshot;
905
- }
906
- const nextSnapshot = {
907
- rows: nextRows,
908
- cols: nextCols,
909
- };
910
- snapshotRef.current = nextSnapshot;
911
- return nextSnapshot;
912
- }, [currentGrid]);
913
- return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
1256
+ return `row=${createGridIdKey(rowId)}|col=${createGridIdKey(columnId)}`;
914
1257
  }
915
1258
  function toOptionalArray(values) {
916
1259
  return values.length === 0 ? undefined : values;
917
1260
  }
1261
+ function describeIndexRange(size, index) {
1262
+ if (size === 0) {
1263
+ return `The grid currently has no items, so index ${index} cannot be selected.`;
1264
+ }
1265
+ return `Expected an index between 0 and ${size - 1} (${size} total items).`;
1266
+ }
1267
+ function haveSameRuntimeValue(leftValue, rightValue) {
1268
+ if (Object.is(leftValue, rightValue)) {
1269
+ return true;
1270
+ }
1271
+ if (leftValue instanceof Date && rightValue instanceof Date) {
1272
+ return leftValue.getTime() === rightValue.getTime();
1273
+ }
1274
+ if (Array.isArray(leftValue) && Array.isArray(rightValue)) {
1275
+ return (leftValue.length === rightValue.length &&
1276
+ leftValue.every((currentValue, index) => haveSameRuntimeValue(currentValue, rightValue[index])));
1277
+ }
1278
+ if (!isPlainObject(leftValue) || !isPlainObject(rightValue)) {
1279
+ return false;
1280
+ }
1281
+ const leftKeys = Object.keys(leftValue);
1282
+ const rightKeys = Object.keys(rightValue);
1283
+ if (leftKeys.length !== rightKeys.length) {
1284
+ return false;
1285
+ }
1286
+ return leftKeys.every((key) => {
1287
+ if (!(key in rightValue)) {
1288
+ return false;
1289
+ }
1290
+ return haveSameRuntimeValue(leftValue[key], rightValue[key]);
1291
+ });
1292
+ }
1293
+ function isPlainObject(value) {
1294
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1295
+ }
918
1296
  function haveSameItems(left, right) {
919
1297
  return (left.length === right.length &&
920
- left.every((currentValue, index) => Object.is(currentValue, right[index])));
1298
+ left.every((currentValue, index) => createGridIdKey(currentValue) === createGridIdKey(right[index])));
921
1299
  }
922
1300
  function normalizeGridStateInput(value) {
923
1301
  if (!value || typeof value !== "object") {
@@ -969,16 +1347,16 @@ function mergeHeadStates(currentHeads, nextHeads) {
969
1347
  const currentIds = new Set();
970
1348
  const nextHeadsById = new Map();
971
1349
  currentHeads.forEach((currentHead) => {
972
- currentIds.add(currentHead.id);
1350
+ currentIds.add(createGridIdKey(currentHead.id));
973
1351
  });
974
1352
  nextHeads.forEach((nextHead) => {
975
- nextHeadsById.set(nextHead.id, nextHead);
1353
+ nextHeadsById.set(createGridIdKey(nextHead.id), nextHead);
976
1354
  });
977
1355
  const resolvedHeads = currentHeads.map((currentHead) => {
978
- return nextHeadsById.get(currentHead.id) ?? currentHead;
1356
+ return nextHeadsById.get(createGridIdKey(currentHead.id)) ?? currentHead;
979
1357
  });
980
1358
  nextHeads.forEach((nextHead) => {
981
- if (currentIds.has(nextHead.id)) {
1359
+ if (currentIds.has(createGridIdKey(nextHead.id))) {
982
1360
  return;
983
1361
  }
984
1362
  resolvedHeads.push(nextHead);
@@ -1013,7 +1391,7 @@ function normalizeGridState(value) {
1013
1391
  throw new Error("Grid state initializer must return an object.");
1014
1392
  }
1015
1393
  if (Array.isArray(value)) {
1016
- throw new Error("Legacy matrix grid input has been removed. Pass a schema object with rows, columns, and cells instead.");
1394
+ throw new Error("Grid state initializer must be a schema object with rows, columns, and cells.");
1017
1395
  }
1018
1396
  const partialState = value;
1019
1397
  const { cells = [], rows = [], columns = [] } = partialState;
@@ -1029,9 +1407,7 @@ function normalizeGridState(value) {
1029
1407
  function normalizeRecordHeads(heads, idKey) {
1030
1408
  return heads.map((head, index) => {
1031
1409
  const id = head[idKey];
1032
- if (typeof id !== "string") {
1033
- throw new Error(`Grid header key "${String(idKey)}" must resolve to a string id.`);
1034
- }
1410
+ assertGridId(id, `Grid header key "${String(idKey)}"`);
1035
1411
  return {
1036
1412
  ...head,
1037
1413
  id,
@@ -1047,7 +1423,7 @@ function createGridInitialCellsFromState(stateCells, stateAdapter) {
1047
1423
  const { rowId, columnId, value } = stateAdapter.deserializeCell(stateCell);
1048
1424
  const gridKey = createGridKey(rowId, columnId);
1049
1425
  if (seenKeys.has(gridKey)) {
1050
- throw new Error(`Duplicate cell for row "${rowId}" and column "${columnId}".`);
1426
+ throw new Error(`Duplicate cell for row "${formatGridId(rowId)}" and column "${formatGridId(columnId)}".`);
1051
1427
  }
1052
1428
  seenKeys.add(gridKey);
1053
1429
  initialCells.push({
@@ -1063,9 +1439,8 @@ function createSchemaStateAdapter(rowCellKey, columnCellKey) {
1063
1439
  deserializeCell: (stateCell) => {
1064
1440
  const rowId = stateCell[rowCellKey];
1065
1441
  const columnId = stateCell[columnCellKey];
1066
- if (typeof rowId !== "string" || typeof columnId !== "string") {
1067
- throw new Error(`Grid cell keys "${String(rowCellKey)}" and "${String(columnCellKey)}" must resolve to string ids.`);
1068
- }
1442
+ assertGridId(rowId, `Grid cell key "${String(rowCellKey)}"`);
1443
+ assertGridId(columnId, `Grid cell key "${String(columnCellKey)}"`);
1069
1444
  return {
1070
1445
  rowId: rowId,
1071
1446
  columnId: columnId,
@@ -1104,10 +1479,11 @@ function normalizeUpsertHead(nextHead, currentHead, fallbackOrder) {
1104
1479
  function assertUniqueHeadIds(heads, axis) {
1105
1480
  const seenIds = new Set();
1106
1481
  heads.forEach((head) => {
1107
- if (seenIds.has(head.id)) {
1108
- throw new Error(`Duplicate ${axis} header "${head.id}".`);
1482
+ const idKey = createGridIdKey(head.id);
1483
+ if (seenIds.has(idKey)) {
1484
+ throw new Error(`Duplicate ${axis} header "${formatGridId(head.id)}".`);
1109
1485
  }
1110
- seenIds.add(head.id);
1486
+ seenIds.add(idKey);
1111
1487
  });
1112
1488
  }
1113
1489
  function createEmptyTailState() {
@@ -1125,54 +1501,7 @@ function readGridHeadLabel(head, id) {
1125
1501
  if (typeof name === "string") {
1126
1502
  return name;
1127
1503
  }
1128
- return id;
1129
- }
1130
- function resolveCreateSubGridArgs(cellsOrOptionsOrPersist, persist) {
1131
- if (persist) {
1132
- return {
1133
- initialCells: readCreateSubGridCells(cellsOrOptionsOrPersist),
1134
- persistOption: persist,
1135
- };
1136
- }
1137
- if (!cellsOrOptionsOrPersist) {
1138
- return {
1139
- initialCells: [],
1140
- persistOption: undefined,
1141
- };
1142
- }
1143
- if (isCreateSubGridOptions(cellsOrOptionsOrPersist)) {
1144
- const subGridOptions = cellsOrOptionsOrPersist;
1145
- return {
1146
- initialCells: subGridOptions.cells ?? [],
1147
- persistOption: subGridOptions.persist,
1148
- };
1149
- }
1150
- if (isGridPersistOption(cellsOrOptionsOrPersist)) {
1151
- return {
1152
- initialCells: [],
1153
- persistOption: cellsOrOptionsOrPersist,
1154
- };
1155
- }
1156
- return {
1157
- initialCells: cellsOrOptionsOrPersist,
1158
- persistOption: undefined,
1159
- };
1160
- }
1161
- function readCreateSubGridCells(value) {
1162
- if (!value || isGridPersistOption(value)) {
1163
- return [];
1164
- }
1165
- if (isCreateSubGridOptions(value)) {
1166
- return (value
1167
- .cells ?? []);
1168
- }
1169
- return value;
1170
- }
1171
- function isCreateSubGridOptions(value) {
1172
- return typeof value === "object" && value !== null && !Array.isArray(value);
1173
- }
1174
- function isGridPersistOption(value) {
1175
- return Array.isArray(value) && value.length > 0 && typeof value[0] === "string";
1504
+ return formatGridId(id);
1176
1505
  }
1177
1506
  function createGridStateCellAdapter() {
1178
1507
  return {
@@ -1189,12 +1518,13 @@ function createGridStateCellAdapter() {
1189
1518
  };
1190
1519
  }
1191
1520
  function createSubGridPersistOption(parentGrid, persist) {
1192
- if (!persist) {
1521
+ const resolvedPersist = resolveDependentGridPersistOption(parentGrid, persist, "sub-grid");
1522
+ if (!resolvedPersist) {
1193
1523
  return undefined;
1194
1524
  }
1195
- const [storageKey, adapter] = persist;
1196
- const resolvedAdapter = adapter ??
1197
- defaultGridPersistAdapter;
1525
+ const [storageKey, adapter] = resolvedPersist;
1526
+ const resolvedAdapter = (adapter ??
1527
+ defaultGridPersistAdapter);
1198
1528
  return [
1199
1529
  storageKey,
1200
1530
  {
@@ -1220,6 +1550,73 @@ function createSubGridPersistOption(parentGrid, persist) {
1220
1550
  },
1221
1551
  ];
1222
1552
  }
1553
+ function createDimensionGridPersistOption(parentGrid, dimension, persist) {
1554
+ const resolvedPersist = resolveDependentGridPersistOption(parentGrid, persist, `dimension-grid:${dimension}`);
1555
+ if (!resolvedPersist) {
1556
+ return undefined;
1557
+ }
1558
+ const [storageKey, adapter] = resolvedPersist;
1559
+ const resolvedAdapter = (adapter ?? defaultGridPersistAdapter);
1560
+ return [
1561
+ storageKey,
1562
+ {
1563
+ get: async (nextStorageKey) => {
1564
+ const persistedState = await Promise.resolve(resolvedAdapter.get(nextStorageKey));
1565
+ if (persistedState === null) {
1566
+ return null;
1567
+ }
1568
+ try {
1569
+ return normalizeDimensionGridStateInput(persistedState, dimension);
1570
+ }
1571
+ catch {
1572
+ return null;
1573
+ }
1574
+ },
1575
+ set: (nextStorageKey, nextState) => {
1576
+ return resolvedAdapter.set(nextStorageKey, normalizeDimensionGridStateInput(nextState, dimension));
1577
+ },
1578
+ remove: (nextStorageKey) => resolvedAdapter.remove(nextStorageKey),
1579
+ },
1580
+ ];
1581
+ }
1582
+ function normalizeDimensionGridStateInput(value, dimension) {
1583
+ if (Array.isArray(value)) {
1584
+ return {
1585
+ dimension,
1586
+ cells: value,
1587
+ };
1588
+ }
1589
+ if (!value || typeof value !== "object") {
1590
+ throw new Error("Dimension grid state updates must be arrays or objects.");
1591
+ }
1592
+ const nextState = value;
1593
+ if (nextState.dimension !== undefined && nextState.dimension !== dimension) {
1594
+ throw new Error(`Dimension grid state must use the "${dimension}" dimension when updating this grid.`);
1595
+ }
1596
+ if (!Array.isArray(nextState.cells)) {
1597
+ throw new Error("Dimension grid state updates must provide a cells array.");
1598
+ }
1599
+ return {
1600
+ dimension,
1601
+ cells: nextState.cells,
1602
+ };
1603
+ }
1604
+ function resolveDependentGridPersistOption(parentGrid, persist, suffix) {
1605
+ if (persist === false) {
1606
+ return undefined;
1607
+ }
1608
+ if (persist) {
1609
+ return persist;
1610
+ }
1611
+ const inheritedPersist = parentGrid.__persistOption;
1612
+ if (!inheritedPersist) {
1613
+ return undefined;
1614
+ }
1615
+ return [
1616
+ `${inheritedPersist[0]}:${suffix}`,
1617
+ inheritedPersist[1],
1618
+ ];
1619
+ }
1223
1620
  function createSubGridState(rows, columns, cells) {
1224
1621
  return {
1225
1622
  rows,
@@ -1228,22 +1625,23 @@ function createSubGridState(rows, columns, cells) {
1228
1625
  };
1229
1626
  }
1230
1627
  function assertGridStateCellsWithinAxes(cells, rows, columns, contextLabel) {
1231
- const rowIds = new Set(rows.map((rowHead) => rowHead.id));
1232
- const columnIds = new Set(columns.map((columnHead) => columnHead.id));
1628
+ const rowIds = new Set(rows.map((rowHead) => createGridIdKey(rowHead.id)));
1629
+ const columnIds = new Set(columns.map((columnHead) => createGridIdKey(columnHead.id)));
1233
1630
  cells.forEach((currentCell) => {
1234
- if (!rowIds.has(currentCell.rowId)) {
1235
- throw new Error(`${contextLabel} must reference an existing parent row header. Missing row "${currentCell.rowId}".`);
1631
+ if (!rowIds.has(createGridIdKey(currentCell.rowId))) {
1632
+ throw new Error(`${contextLabel} must reference an existing parent row header. Missing row "${formatGridId(currentCell.rowId)}".`);
1236
1633
  }
1237
- if (!columnIds.has(currentCell.columnId)) {
1238
- throw new Error(`${contextLabel} must reference an existing parent column header. Missing column "${currentCell.columnId}".`);
1634
+ if (!columnIds.has(createGridIdKey(currentCell.columnId))) {
1635
+ throw new Error(`${contextLabel} must reference an existing parent column header. Missing column "${formatGridId(currentCell.columnId)}".`);
1239
1636
  }
1240
1637
  });
1241
1638
  return cells;
1242
1639
  }
1243
1640
  function filterGridStateCellsToAxes(cells, rows, columns) {
1244
- const rowIds = new Set(rows.map((rowHead) => rowHead.id));
1245
- const columnIds = new Set(columns.map((columnHead) => columnHead.id));
1246
- return cells.filter((currentCell) => rowIds.has(currentCell.rowId) && columnIds.has(currentCell.columnId));
1641
+ const rowIds = new Set(rows.map((rowHead) => createGridIdKey(rowHead.id)));
1642
+ const columnIds = new Set(columns.map((columnHead) => createGridIdKey(columnHead.id)));
1643
+ return cells.filter((currentCell) => rowIds.has(createGridIdKey(currentCell.rowId)) &&
1644
+ columnIds.has(createGridIdKey(currentCell.columnId)));
1247
1645
  }
1248
1646
  function didGridDiffAffectAxes(diff) {
1249
1647
  return (!haveSameHeadSnapshots(diff.rows, diff.previousRows) ||
@@ -1277,7 +1675,18 @@ function haveSameShallowRecord(leftRecord, rightRecord) {
1277
1675
  if (!(key in rightRecord)) {
1278
1676
  return false;
1279
1677
  }
1280
- return Object.is(leftRecord[key], rightRecord[key]);
1678
+ const leftValue = leftRecord[key];
1679
+ const rightValue = rightRecord[key];
1680
+ if (isGridIdValue(leftValue) && isGridIdValue(rightValue)) {
1681
+ return createGridIdKey(leftValue) === createGridIdKey(rightValue);
1682
+ }
1683
+ return Object.is(leftValue, rightValue);
1281
1684
  });
1282
1685
  }
1686
+ function isGridIdValue(value) {
1687
+ return (typeof value === "string" ||
1688
+ typeof value === "number" ||
1689
+ typeof value === "boolean" ||
1690
+ value instanceof Date);
1691
+ }
1283
1692
  //# sourceMappingURL=grid.js.map