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.
- package/README.md +171 -36
- package/dist/adaptors/react/index.d.ts +71 -0
- package/dist/adaptors/react/index.d.ts.map +1 -0
- package/dist/adaptors/react/index.js +249 -0
- package/dist/adaptors/react/index.js.map +1 -0
- package/dist/core/cell.d.ts +17 -8
- package/dist/core/cell.d.ts.map +1 -1
- package/dist/core/cell.js +57 -39
- package/dist/core/cell.js.map +1 -1
- package/dist/core/grid-id.d.ts +7 -0
- package/dist/core/grid-id.d.ts.map +1 -0
- package/dist/core/grid-id.js +51 -0
- package/dist/core/grid-id.js.map +1 -0
- package/dist/core/grid.d.ts +9 -9
- package/dist/core/grid.d.ts.map +1 -1
- package/dist/core/grid.js +608 -199
- package/dist/core/grid.js.map +1 -1
- package/dist/core/head.d.ts +6 -12
- package/dist/core/head.d.ts.map +1 -1
- package/dist/core/head.js +20 -69
- package/dist/core/head.js.map +1 -1
- package/dist/core/helpers.d.ts +3 -3
- package/dist/core/helpers.d.ts.map +1 -1
- package/dist/core/helpers.js +5 -2
- package/dist/core/helpers.js.map +1 -1
- package/dist/core/persist.d.ts.map +1 -1
- package/dist/core/persist.js +53 -42
- package/dist/core/persist.js.map +1 -1
- package/dist/core/tail.d.ts +5 -10
- package/dist/core/tail.d.ts.map +1 -1
- package/dist/core/tail.js +7 -32
- package/dist/core/tail.js.map +1 -1
- package/dist/core/types/grid.types.d.ts +79 -32
- package/dist/core/types/grid.types.d.ts.map +1 -1
- package/dist/core/types/head.types.d.ts +7 -6
- package/dist/core/types/head.types.d.ts.map +1 -1
- package/dist/core/types/persist.types.d.ts +2 -0
- package/dist/core/types/persist.types.d.ts.map +1 -1
- package/dist/core/types/tail.types.d.ts +3 -2
- package/dist/core/types/tail.types.d.ts.map +1 -1
- package/dist/index.d.ts +7 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -5
- package/dist/index.js.map +1 -1
- package/package.json +5 -1
- package/dist/core/gridPersist.d.ts +0 -3
- package/dist/core/gridPersist.d.ts.map +0 -1
- package/dist/core/gridPersist.js +0 -2
- package/dist/core/gridPersist.js.map +0 -1
- package/dist/core/types/gridPersist.types.d.ts +0 -2
- package/dist/core/types/gridPersist.types.d.ts.map +0 -1
- package/dist/core/types/gridPersist.types.js +0 -2
- 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 {
|
|
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
|
|
17
|
-
const
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
276
|
-
|
|
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(
|
|
281
|
-
if (!tailCells.has(
|
|
282
|
-
tailCells.set(
|
|
283
|
-
headOrder.set(
|
|
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, (
|
|
298
|
-
rowTailUpdaters.delete(
|
|
619
|
+
syncAxisHeads(nextState.rows, "row", rowHeadCells, rowTailCells, rowHeadOrder, (idKey) => {
|
|
620
|
+
rowTailUpdaters.delete(idKey);
|
|
299
621
|
});
|
|
300
|
-
syncAxisHeads(nextState.columns, "column", columnHeadCells, columnTailCells, columnHeadOrder, (
|
|
301
|
-
columnTailUpdaters.delete(
|
|
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
|
|
369
|
-
const touchedColumnIds = new
|
|
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
|
-
|
|
380
|
-
|
|
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.
|
|
393
|
-
touchedColumnIds.
|
|
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
|
-
|
|
401
|
-
|
|
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
|
|
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
|
-
|
|
441
|
-
|
|
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(
|
|
451
|
-
rowTailCells.set(
|
|
452
|
-
rowHeadOrder.set(
|
|
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.
|
|
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
|
|
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
|
-
|
|
498
|
-
|
|
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(
|
|
508
|
-
columnTailCells.set(
|
|
509
|
-
columnHeadOrder.set(
|
|
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.
|
|
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
|
-
|
|
1035
|
+
const rowIdKey = createGridIdKey(rowId);
|
|
1036
|
+
rowTailUpdaters.set(rowIdKey, onRowUpdate);
|
|
648
1037
|
recomputeRowTailInternal(rowId);
|
|
649
1038
|
return () => {
|
|
650
|
-
if (rowTailUpdaters.get(
|
|
1039
|
+
if (rowTailUpdaters.get(rowIdKey) !== onRowUpdate) {
|
|
651
1040
|
return;
|
|
652
1041
|
}
|
|
653
|
-
rowTailUpdaters.delete(
|
|
1042
|
+
rowTailUpdaters.delete(rowIdKey);
|
|
654
1043
|
};
|
|
655
1044
|
}
|
|
656
1045
|
function registerColumnTail(columnId, onColumnUpdate) {
|
|
657
|
-
|
|
1046
|
+
const columnIdKey = createGridIdKey(columnId);
|
|
1047
|
+
columnTailUpdaters.set(columnIdKey, onColumnUpdate);
|
|
658
1048
|
recomputeColumnTailInternal(columnId);
|
|
659
1049
|
return () => {
|
|
660
|
-
if (columnTailUpdaters.get(
|
|
1050
|
+
if (columnTailUpdaters.get(columnIdKey) !== onColumnUpdate) {
|
|
661
1051
|
return;
|
|
662
1052
|
}
|
|
663
|
-
columnTailUpdaters.delete(
|
|
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
|
|
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) =>
|
|
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("
|
|
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
|
-
|
|
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
|
-
|
|
1067
|
-
|
|
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
|
-
|
|
1108
|
-
|
|
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(
|
|
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
|
-
|
|
1521
|
+
const resolvedPersist = resolveDependentGridPersistOption(parentGrid, persist, "sub-grid");
|
|
1522
|
+
if (!resolvedPersist) {
|
|
1193
1523
|
return undefined;
|
|
1194
1524
|
}
|
|
1195
|
-
const [storageKey, adapter] =
|
|
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) &&
|
|
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
|
-
|
|
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
|