@1771technologies/lytenyte-pro 1.0.9 → 1.0.10

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.
@@ -1,404 +1,154 @@
1
+ import { effect, makeAtom, signal } from "@1771technologies/lytenyte-shared";
1
2
  import { useRef } from "react";
2
- import { makeAsyncTree } from "./async-tree/make-async-tree.js";
3
- import { getNodeDepth } from "./utils/get-node-depth.js";
4
- import { getRequestId } from "./utils/get-request-id.js";
5
- import { RangeTree } from "./range-tree/range-tree.js";
6
- import { getNodePath } from "./utils/get-node-path.js";
3
+ import { ServerData } from "./server-data.js";
7
4
  import { equal } from "@1771technologies/lytenyte-js-utils";
8
- import { computed, effect, makeAtom, peek, signal } from "@1771technologies/lytenyte-shared";
9
- export function makeServerDataSource({ dataFetcher, dataColumnPivotFetcher, dataInFilterItemFetcher, cellUpdateHandler, cellUpdateOptimistically = true, blockSize = 200, }) {
5
+ export function makeServerDataSource({ dataFetcher, dataInFilterItemFetcher, dataColumnPivotFetcher, cellUpdateHandler, cellUpdateOptimistically, blockSize = 200, }) {
10
6
  let grid = null;
11
- const snapshot$ = signal(Date.now());
12
- const tree$ = signal(makeAsyncTree());
13
- const tree = makeAtom(tree$);
14
- const isLoading$ = signal(false);
15
- const isLoading = makeAtom(isLoading$);
16
- const error$ = signal(null);
17
- const loadError = makeAtom(error$);
18
- const nodesWithError = signal(new Map());
19
- const initialized = signal(false);
20
- const model$ = signal({
21
- sorts: [],
22
- filters: {},
23
- filtersIn: {},
24
- quickSearch: null,
25
- group: [],
26
- groupExpansions: {},
27
- aggregations: {},
28
- pivotGroupExpansions: {},
29
- pivotMode: false,
30
- pivotModel: {
31
- columns: [],
32
- filters: {},
33
- filtersIn: {},
34
- rows: [],
35
- sorts: [],
36
- values: [],
37
- },
38
- });
39
- const model = makeAtom(model$);
40
- const pivotGroupExpansions = computed(() => model$().pivotGroupExpansions);
41
- const rowGroupExpansions = computed(() => model$().groupExpansions);
42
- const pivotMode = computed(() => model$().pivotMode);
43
- const topData = signal([]);
44
- const botData = signal([]);
45
- const seenRequests = new Set();
46
- const dataResponseHandler = (res) => {
47
- const t = tree.get();
48
- const dataResponses = res.filter((r) => {
49
- return !("kind" in r && (r.kind === "top" || r.kind === "bottom"));
50
- });
51
- dataResponses.sort((l, r) => l.path.length - r.path.length);
52
- const pinResponses = res.filter((r) => {
53
- return "kind" in r && (r.kind === "top" || r.kind === "bottom");
54
- });
55
- pinResponses.forEach((p) => {
56
- if (p.kind === "top") {
57
- topData.set(p.data);
58
- }
59
- else {
60
- botData.set(p.data);
61
- }
62
- });
63
- dataResponses.forEach((r) => {
64
- t.set({
65
- path: r.path,
66
- items: r.data.map((c, i) => {
67
- if (c.kind === "leaf") {
68
- return {
69
- kind: "leaf",
70
- data: c,
71
- relIndex: r.start + i,
72
- };
73
- }
74
- else {
75
- return {
76
- kind: "parent",
77
- data: c,
78
- path: c.key,
79
- relIndex: r.start + i,
80
- size: c.childCount,
81
- };
82
- }
83
- }),
84
- size: r.size,
85
- });
86
- });
87
- };
88
- const dataRequestHandler = async (p, force = false, onSuccess, onFailure) => {
89
- if (!grid)
90
- return;
91
- const unseen = p.filter((x) => force || !seenRequests.has(x.id));
92
- unseen.forEach((x) => seenRequests.add(x.id));
93
- if (unseen.length === 0) {
94
- onSuccess?.();
95
- return;
96
- }
97
- nodesWithError.set((prev) => {
98
- const next = new Map(prev);
99
- p.forEach((r) => {
100
- for (let i = r.rowStartIndex; i < r.rowEndIndex; i++) {
101
- next.delete(i);
102
- }
103
- });
104
- return prev;
105
- });
106
- try {
107
- const res = await dataFetcher({
108
- grid: grid,
109
- model: model.get(),
110
- reqTime: Date.now(),
111
- requests: p,
112
- });
113
- dataResponseHandler(res);
114
- onSuccess?.();
115
- }
116
- catch (e) {
117
- // Mark them as unseen since the request failed.
118
- unseen.forEach((x) => seenRequests.delete(x.id));
119
- onFailure?.(e);
120
- console.error(e);
121
- }
122
- finally {
123
- snapshot$.set(Date.now());
124
- grid.state.rowDataStore.rowClearCache();
125
- }
126
- };
127
- const viewChange = () => {
128
- if (!grid)
129
- return;
130
- const bounds = grid.state.viewBounds.get();
131
- const f = flat.get();
132
- const te = bounds.rowTopEnd;
133
- const seen = new Set();
134
- const requests = [];
135
- for (let i = bounds.rowCenterStart - te; i < bounds.rowCenterEnd - te; i++) {
136
- const ranges = f.rangeTree.findRangesForRowIndex(i);
137
- ranges.forEach((c) => {
138
- if (c.parent.kind === "root") {
139
- const blockIndex = Math.floor(i / blockSize);
140
- const start = blockIndex * blockSize;
141
- const end = Math.min(start + blockSize, c.parent.size);
142
- const reqId = getRequestId([], start, end);
143
- if (seen.has(reqId))
144
- return;
145
- seen.add(reqId);
146
- const size = start + blockSize > c.parent.size ? c.parent.size - start : blockSize;
147
- requests.push({
148
- id: reqId,
149
- path: [],
150
- start,
151
- end,
152
- rowStartIndex: i,
153
- rowEndIndex: i + size,
154
- });
155
- }
156
- else {
157
- const blockIndex = Math.floor(c.parent.relIndex / blockSize);
158
- const start = blockIndex * blockSize;
159
- const end = Math.min(start + blockSize, c.parent.size);
160
- const path = getNodePath(c.parent);
161
- const reqId = getRequestId(path, start, end);
162
- if (seen.has(reqId))
163
- return;
164
- seen.add(reqId);
165
- const size = start + blockSize > c.parent.size ? c.parent.size - start : blockSize;
166
- requests.push({
167
- id: reqId,
168
- path,
169
- start,
170
- end,
171
- rowStartIndex: i,
172
- rowEndIndex: i + size,
173
- });
174
- }
175
- });
176
- }
177
- dataRequestHandler(requests, false, () => { }, (e) => {
178
- nodesWithError.set((prev) => {
179
- const next = new Map(prev);
180
- requests.forEach((r) => {
181
- for (let i = r.rowStartIndex; i < r.rowEndIndex; i++) {
182
- next.set(i, e);
183
- }
184
- });
185
- return prev;
186
- });
187
- });
188
- };
189
- const resetRequest = async () => {
190
- seenRequests.clear();
191
- tree.set(makeAsyncTree());
192
- nodesWithError.set(new Map());
193
- // Initialize the grid.
194
- const initialDataRequest = {
195
- path: [],
196
- id: getRequestId([], 0, blockSize),
197
- rowStartIndex: 0,
198
- rowEndIndex: blockSize,
199
- start: 0,
200
- end: blockSize,
201
- };
202
- loadError.set(null);
203
- isLoading.set(true);
204
- await dataRequestHandler([initialDataRequest], false, () => { }, (e) => {
205
- loadError.set(e);
206
- });
207
- isLoading.set(false);
208
- };
209
- const flat$ = computed(() => {
210
- snapshot$();
211
- const mode = pivotMode();
212
- const expansions = mode ? pivotGroupExpansions() : rowGroupExpansions();
213
- const t = tree$();
214
- const rowIdToRow = new Map();
215
- const rowIndexToRow = new Map();
216
- const rowIdToRowIndex = new Map();
217
- const ranges = [];
218
- function processParent(node, start) {
219
- const rows = [...node.byIndex.values()].sort((l, r) => l.relIndex - r.relIndex);
220
- // Track the additional rows added by expanded groups
221
- let offset = 0;
222
- for (let i = 0; i < rows.length; i++) {
223
- const row = rows[i];
224
- const rowIndex = row.relIndex + start + offset;
225
- rowIndexToRow.set(rowIndex, row);
226
- rowIdToRowIndex.set(row.data.id, rowIndex);
227
- rowIdToRow.set(row.data.id, row);
228
- if (row.kind === "parent" && expansions[row.data.id]) {
229
- offset += processParent(row, rowIndex + 1);
230
- }
231
- }
232
- ranges.push({ rowStart: start, rowEnd: offset + node.size + start, parent: node });
233
- return offset + node.size;
234
- }
235
- const size = processParent(t, 0);
236
- const rangeTree = new RangeTree(ranges);
237
- return {
238
- size,
239
- rowIndexToRow,
240
- rowIdToRow,
241
- rowIdToRowIndex,
242
- rangeTree,
243
- };
244
- });
245
- const flat = makeAtom(flat$);
246
- const topLength = computed(() => topData().length);
247
- const botLength = computed(() => botData().length);
248
- const flatLength = computed(() => flat.$().size);
7
+ let flat;
8
+ let source;
9
+ void grid;
10
+ const isLoading = makeAtom(signal(false));
11
+ const loadError = makeAtom(signal(null));
249
12
  const cleanup = [];
250
13
  const init = (g) => {
251
14
  grid = g;
252
- const sorts = g.state.sortModel.get();
253
- const filters = g.state.filterModel.get();
254
- const filtersIn = g.state.filterInModel.get();
255
- const quickSearch = g.state.quickSearch.get();
256
- const group = g.state.rowGroupModel.get();
257
- const groupExpansions = g.state.rowGroupExpansions.get();
258
- const aggregations = g.state.aggModel.get();
259
- const pivotMode = g.state.columnPivotMode.get();
260
- const pivotModel = g.state.columnPivotModel.get();
261
- const pivotGroupExpansions = g.state.columnPivotRowGroupExpansions.get();
262
- model$.set({
263
- sorts,
264
- filters,
265
- filtersIn,
266
- quickSearch,
267
- aggregations,
268
- group,
269
- groupExpansions,
270
- pivotMode,
271
- pivotModel,
272
- pivotGroupExpansions,
15
+ source = new ServerData({
16
+ defaultExpansion: g.state.rowGroupDefaultExpansion.get(),
17
+ blocksize: blockSize,
18
+ expansions: g.state.rowGroupExpansions.get(),
19
+ pivotExpansions: g.state.columnPivotRowGroupExpansions.get(),
20
+ pivotMode: g.state.columnPivotMode.get(),
21
+ onResetLoadBegin: () => {
22
+ isLoading.set(true);
23
+ loadError.set(null);
24
+ },
25
+ onResetLoadEnd: () => isLoading.set(false),
26
+ onResetLoadError: (e) => loadError.set(e),
27
+ onFlatten: (f) => {
28
+ const store = g.state.rowDataStore;
29
+ flat = f;
30
+ store.rowTopCount.set(f.top);
31
+ store.rowCenterCount.set(f.center);
32
+ store.rowBottomCount.set(f.bottom);
33
+ store.rowClearCache();
34
+ },
273
35
  });
274
- initialized.set(true);
275
- // Handle row count changes
276
- const f = flat.get();
277
- g.state.rowDataStore.rowCenterCount.set(f.size);
278
- g.state.rowDataStore.rowBottomCount.set(peek(botData).length);
279
- g.state.rowDataStore.rowTopCount.set(peek(topData).length);
280
- cleanup.push(effect(() => {
281
- g.state.rowDataStore.rowCenterCount.set(flatLength());
282
- viewChange();
283
- }));
284
- cleanup.push(effect(() => {
285
- g.state.rowDataStore.rowTopCount.set(topLength());
286
- g.state.rowDataStore.rowClearCache();
287
- }));
36
+ let prevModel = {
37
+ sorts: g.state.sortModel.get(),
38
+ groups: g.state.rowGroupModel.get(),
39
+ filters: g.state.filterModel.get(),
40
+ filtersIn: g.state.filterInModel.get(),
41
+ quickSearch: g.state.quickSearch.get(),
42
+ aggregations: g.state.aggModel.get(),
43
+ pivotMode: g.state.columnPivotMode.get(),
44
+ pivotModel: g.state.columnPivotModel.get(),
45
+ };
288
46
  cleanup.push(effect(() => {
289
- g.state.rowDataStore.rowBottomCount.set(botLength);
290
- g.state.rowDataStore.rowClearCache();
47
+ const newModel = {
48
+ // @ts-expect-error this is fine - just a hidden type
49
+ sorts: g.state.sortModel.$(),
50
+ // @ts-expect-error this is fine - just a hidden type
51
+ groups: g.state.rowGroupModel.$(),
52
+ // @ts-expect-error this is fine - just a hidden type
53
+ filters: g.state.filterModel.$(),
54
+ // @ts-expect-error this is fine - just a hidden type
55
+ filtersIn: g.state.filterInModel.$(),
56
+ // @ts-expect-error this is fine - just a hidden type
57
+ quickSearch: g.state.quickSearch.$(),
58
+ // @ts-expect-error this is fine - just a hidden type
59
+ aggregations: g.state.aggModel.$(),
60
+ // @ts-expect-error this is fine - just a hidden type
61
+ pivotMode: g.state.columnPivotMode.$(),
62
+ // @ts-expect-error this is fine - just a hidden type
63
+ pivotModel: g.state.columnPivotModel.$(),
64
+ };
65
+ if (equal(newModel, prevModel))
66
+ return;
67
+ source.dataFetcher = (req, expansions, pivotExpansions) => {
68
+ return dataFetcher({
69
+ grid: g,
70
+ model: {
71
+ ...newModel,
72
+ groupExpansions: expansions,
73
+ pivotGroupExpansions: pivotExpansions,
74
+ },
75
+ reqTime: Date.now(),
76
+ requests: req,
77
+ });
78
+ };
79
+ prevModel = newModel;
291
80
  }));
292
- resetRequest();
293
- // Watch the view bound changes.
294
- g.state.viewBounds.watch(() => {
295
- viewChange();
296
- });
81
+ const pivotModel = g.state.columnPivotModel.get();
297
82
  let prevPivotColumnModel = pivotModel.columns;
298
83
  let prevPivotColumnValues = pivotModel.values;
299
- const updatePivotColumns = async (mdl, ignoreEqualCheck = false) => {
84
+ const updatePivotColumns = async (model, ignoreEqualCheck = false) => {
300
85
  if (!ignoreEqualCheck &&
301
- equal(prevPivotColumnModel, mdl.columns) &&
302
- equal(prevPivotColumnValues, mdl.values))
86
+ equal(prevPivotColumnModel, model.columns) &&
87
+ equal(prevPivotColumnValues, model.values))
303
88
  return;
304
- const cols = await dataColumnPivotFetcher?.({
305
- grid: grid,
306
- model: model.get(),
89
+ const pivotColumns = await dataColumnPivotFetcher?.({
90
+ grid: g,
91
+ model: {
92
+ ...prevModel,
93
+ pivotMode: g.state.columnPivotMode.get(),
94
+ pivotModel: model,
95
+ groupExpansions: g.state.rowGroupExpansions.get(),
96
+ pivotGroupExpansions: g.state.columnPivotColumnGroupExpansions.get(),
97
+ },
307
98
  reqTime: Date.now(),
308
99
  });
309
- if (cols)
310
- grid?.state.columnPivotColumns.set(cols);
311
- resetRequest();
312
- prevPivotColumnModel = mdl.columns;
313
- prevPivotColumnValues = mdl.values;
100
+ g.state.columnPivotColumns.set(pivotColumns ?? []);
101
+ g.state.rowDataStore.rowClearCache();
102
+ prevPivotColumnModel = model.columns;
103
+ prevPivotColumnValues = model.values;
314
104
  };
315
- if (pivotMode)
105
+ if (g.state.columnPivotMode.get())
316
106
  updatePivotColumns(pivotModel);
317
- let prevModel = peek(model$);
318
- effect(() => {
319
- // @ts-expect-error this is fine
320
- const rowGroupModel = g.state.rowGroupModel.$();
321
- // @ts-expect-error this is fine
322
- const rowGroupExpansions = g.state.rowGroupExpansions.$();
323
- // @ts-expect-error this is fine
324
- const aggModel = g.state.aggModel.$();
325
- // @ts-expect-error this is fine
326
- const filterModelIn = g.state.filterInModel.$();
327
- // @ts-expect-error this is fine
328
- const quickSearch = g.state.quickSearch.$();
329
- // @ts-expect-error this is fine
330
- const filterModel = g.state.filterModel.$();
331
- // @ts-expect-error this is fine
332
- const sortModel = g.state.sortModel.$();
333
- // @ts-expect-error this is fine
334
- const columnPivotExpansions = g.state.columnPivotColumnGroupExpansions.$();
335
- // @ts-expect-error this is fine
336
- const model = g.state.columnPivotModel.$();
337
- // @ts-expect-error this is fine
338
- const mode = g.state.columnPivotMode.$();
107
+ // Pivot model monitoring
108
+ cleanup.push(grid.state.columnPivotMode.watch(() => {
109
+ const model = g.state.columnPivotModel.get();
339
110
  updatePivotColumns(model);
340
- const newModel = {
341
- aggregations: aggModel,
342
- filters: filterModel,
343
- filtersIn: filterModelIn,
344
- group: rowGroupModel,
345
- groupExpansions: rowGroupExpansions,
346
- quickSearch,
347
- pivotGroupExpansions: columnPivotExpansions,
348
- pivotMode: mode,
349
- pivotModel: model,
350
- sorts: sortModel,
351
- };
352
- if (equal(newModel, prevModel))
353
- return;
354
- prevModel = newModel;
355
- model$.set(newModel);
356
- resetRequest();
357
- });
111
+ }));
112
+ cleanup.push(grid.state.columnPivotModel.watch(() => {
113
+ const model = g.state.columnPivotModel.get();
114
+ updatePivotColumns(model);
115
+ }));
116
+ cleanup.push(g.state.rowGroupDefaultExpansion.watch(() => {
117
+ source.defaultExpansion = g.state.rowGroupDefaultExpansion.get();
118
+ }));
119
+ cleanup.push(g.state.viewBounds.watch(() => {
120
+ const bounds = g.state.viewBounds.get();
121
+ source.rowViewBounds = [bounds.rowCenterStart, bounds.rowCenterEnd];
122
+ }));
123
+ source.dataFetcher = (req, expansions, pivotExpansions) => {
124
+ return dataFetcher({
125
+ grid: g,
126
+ model: {
127
+ ...prevModel,
128
+ groupExpansions: expansions,
129
+ pivotGroupExpansions: pivotExpansions,
130
+ },
131
+ reqTime: Date.now(),
132
+ requests: req,
133
+ });
134
+ };
358
135
  };
359
136
  const rowById = (id) => {
360
- const f = flat.get();
361
- const node = f.rowIdToRow.get(id);
362
- if (!node) {
363
- {
364
- const top = peek(topData);
365
- const node = top.find((c) => c.id === id);
366
- if (node)
367
- return { kind: "leaf", data: node.data, id: node.id };
368
- }
369
- {
370
- const bot = peek(topData);
371
- const node = bot.find((c) => c.id === id);
372
- if (node)
373
- return { kind: "leaf", data: node.data, id: node.id };
374
- }
375
- return null;
376
- }
377
- if (node.kind === "parent") {
378
- return {
379
- kind: "branch",
380
- data: node.data.data,
381
- id: node.data.id,
382
- key: node.path,
383
- depth: getNodeDepth(node),
384
- };
385
- }
386
- return {
387
- kind: "leaf",
388
- data: node.data.data,
389
- id: node.data.id,
390
- };
137
+ return flat.rowIdToRow.get(id) ?? null;
391
138
  };
392
139
  const rowAllChildIds = (rowId) => {
393
- const f = flat.get();
140
+ const f = flat;
394
141
  const row = f.rowIdToRow.get(rowId);
395
142
  if (!row || row.kind === "leaf")
396
143
  return [];
397
144
  const ids = new Set();
398
- const stack = [...row.byPath.values()];
145
+ const node = f.rowIdToTreeNode.get(row.id);
146
+ if (!node || node.kind === "leaf")
147
+ return [];
148
+ const stack = [...node.byPath.values()];
399
149
  while (stack.length) {
400
150
  const item = stack.pop();
401
- if (item.kind === "leaf") {
151
+ if (item?.kind === "leaf") {
402
152
  ids.add(item.data.id);
403
153
  }
404
154
  else {
@@ -408,124 +158,46 @@ export function makeServerDataSource({ dataFetcher, dataColumnPivotFetcher, data
408
158
  }
409
159
  return [...ids];
410
160
  };
411
- const rowByIndex = (ri) => {
412
- const f = flat.get();
413
- const top = peek(topData);
414
- const bot = peek(botData);
415
- const errors = peek(nodesWithError);
416
- const error = errors.get(ri);
417
- if (ri == null || ri < 0 || ri >= f.size + top.length + bot.length)
418
- return null;
419
- // Top node
420
- if (ri < top.length) {
421
- const row = top[ri];
422
- if (!row)
423
- return null;
424
- return { kind: "leaf", data: row.data, id: row.id, error };
425
- }
426
- if (ri >= f.size + top.length) {
427
- const i = ri - f.size - top.length;
428
- const row = bot[i];
429
- if (!row)
430
- return null;
431
- return { kind: "leaf", data: row.data, id: row.id, error };
432
- }
433
- const node = f.rowIndexToRow.get(ri - top.length);
434
- if (!node && error)
435
- return { kind: "leaf", data: null, id: `error-${ri}`, loading: false, error };
436
- if (!node)
437
- return { kind: "leaf", data: null, id: `loading-${ri}`, loading: true, error: null };
438
- const isLoading = peek(loadingGroups$);
439
- if (node.kind === "parent") {
161
+ const rowByIndex = (i) => {
162
+ const row = flat.rowIndexToRow.get(i);
163
+ const isLoading = flat.loading.has(i);
164
+ const error = flat.errored.get(i);
165
+ // If we haven't loaded a row yet.
166
+ if (!row)
440
167
  return {
441
- kind: "branch",
442
- data: node.data.data,
443
- id: node.data.id,
444
- key: node.path,
445
- depth: getNodeDepth(node),
446
- error,
447
- loading: isLoading.has(node.data.id),
168
+ id: `__loading__placeholder__${i}`,
169
+ data: null,
170
+ kind: "leaf",
171
+ loading: isLoading,
172
+ error: error,
448
173
  };
174
+ if (error) {
175
+ return { ...row, error };
449
176
  }
450
- return {
451
- kind: "leaf",
452
- data: node.data.data,
453
- id: node.data.id,
454
- error,
455
- };
177
+ if (isLoading) {
178
+ return { ...row, loading: isLoading };
179
+ }
180
+ return flat.rowIndexToRow.get(i) ?? null;
456
181
  };
457
- const loadingGroups$ = signal(new Set());
458
- const rowExpand = (p) => {
459
- const f = flat.get();
460
- const loadingRows = new Set(peek(loadingGroups$));
461
- const rowsForThis = new Set();
462
- const rowIndices = [];
463
- const requests = Object.entries(p)
464
- .map(([rowId, state]) => {
465
- if (!state)
466
- return null;
467
- // Mark as loading
468
- loadingRows.add(rowId);
469
- rowsForThis.add(rowId);
470
- const rowIndex = rowToIndex(rowId);
471
- if (rowIndex == null)
472
- return null;
473
- const row = f.rowIndexToRow.get(rowIndex);
474
- if (!row || row.kind === "leaf")
475
- return null;
476
- const path = getNodePath(row);
477
- rowIndices.push(rowIndex);
478
- return {
479
- id: getRequestId(path, 0, Math.min(blockSize, row.size)),
480
- start: 0,
481
- end: Math.min(blockSize, row.size),
482
- path,
483
- rowStartIndex: rowIndex + 1,
484
- rowEndIndex: Math.min(row.size, rowIndex + blockSize + 1),
485
- };
486
- })
487
- .filter((c) => !!c);
488
- const mode = peek(pivotMode);
489
- // Remove the errors before requesting.
490
- const errorRows = peek(nodesWithError);
491
- if (rowIndices.some((c) => errorRows.has(c))) {
492
- nodesWithError.set((prev) => {
493
- const n = new Map(prev);
494
- rowIndices.forEach((c) => n.delete(c));
495
- return prev;
496
- });
182
+ const rowExpand = (expansions) => {
183
+ if (!grid)
184
+ return;
185
+ const mode = grid.state.columnPivotMode.get();
186
+ if (mode) {
187
+ const current = grid.state.columnPivotColumnGroupExpansions.get();
188
+ const next = { ...current, ...expansions };
189
+ source.pivotExpansions = next;
190
+ grid.state.columnPivotRowGroupExpansions.set(next);
191
+ }
192
+ else {
193
+ const current = grid.state.rowGroupExpansions.get();
194
+ const next = { ...current, ...expansions };
195
+ source.expansions = next;
196
+ grid.state.rowGroupExpansions.set(next);
497
197
  }
498
- // Mark these groups as loading
499
- loadingGroups$.set(loadingRows);
500
- grid?.state.rowDataStore.rowClearCache();
501
- dataRequestHandler(requests, false, () => {
502
- if (mode)
503
- grid?.state.columnPivotColumnGroupExpansions.set((prev) => ({ ...prev, ...p }));
504
- else
505
- grid?.state.rowGroupExpansions.set((prev) => ({ ...prev, ...p }));
506
- const next = new Set(peek(loadingGroups$));
507
- rowsForThis.forEach((c) => next.delete(c));
508
- loadingGroups$.set(next);
509
- setTimeout(() => {
510
- grid?.state.rowDataStore.rowClearCache();
511
- });
512
- }, (error) => {
513
- nodesWithError.set((prev) => {
514
- const n = new Map(prev);
515
- rowIndices.forEach((c) => n.set(c, error));
516
- return prev;
517
- });
518
- const next = new Set(peek(loadingGroups$));
519
- rowsForThis.forEach((c) => next.delete(c));
520
- loadingGroups$.set(next);
521
- setTimeout(() => {
522
- grid?.state.rowDataStore.rowClearCache();
523
- });
524
- });
525
198
  };
526
199
  const rowToIndex = (rowId) => {
527
- const f = flat.get();
528
- return f.rowIdToRowIndex.get(rowId) ?? null;
200
+ return flat.rowIdToRowIndex.get(rowId) ?? null;
529
201
  };
530
202
  const rowSelect = (params) => {
531
203
  if (!grid || params.mode === "none")
@@ -582,14 +254,17 @@ export function makeServerDataSource({ dataFetcher, dataColumnPivotFetcher, data
582
254
  grid.state.rowSelectedIds.set(new Set());
583
255
  return;
584
256
  }
585
- const t = flat.get();
257
+ const t = flat;
586
258
  grid.state.rowSelectedIds.set(new Set(t.rowIdToRow.keys()));
587
259
  };
588
260
  const rowSetBotData = () => {
589
- throw new Error(`Server data source does not support directly setting pinned rows. Instead send a DataResponsePinned object via the pushData method`);
261
+ console.error("Directly setting bottom data in the server data model is not supported.");
590
262
  };
591
263
  const rowSetTopData = () => {
592
- throw new Error(`Server data source does not support directly setting pinned rows. Instead send a DataResponsePinned object via the pushData method`);
264
+ console.error("Directly setting top data in the server data model is not supported.");
265
+ };
266
+ const rowSetCenterData = () => {
267
+ console.error("Directly setting center data in the server data model is not supported.");
593
268
  };
594
269
  // CRUD ops
595
270
  const rowAdd = () => {
@@ -599,10 +274,6 @@ export function makeServerDataSource({ dataFetcher, dataColumnPivotFetcher, data
599
274
  throw new Error(`Server data source does not support deleting rows directly. Instead push updates via the pushResponses or pushRequests method`);
600
275
  };
601
276
  const rowUpdate = (updates) => {
602
- const f = flat.get();
603
- const top = peek(topData);
604
- const bot = peek(botData);
605
- const firstBot = f.size + top.length;
606
277
  const idMap = new Map([...updates.entries()]
607
278
  .map(([key, data]) => {
608
279
  if (typeof key === "string")
@@ -616,26 +287,10 @@ export function makeServerDataSource({ dataFetcher, dataColumnPivotFetcher, data
616
287
  cellUpdateHandler?.(idMap);
617
288
  if (!cellUpdateOptimistically)
618
289
  return;
619
- for (const [key, data] of updates.entries()) {
620
- const rowIndex = typeof key === "number" ? key : rowToIndex(key);
621
- const row = rowByIndex(rowIndex);
622
- if (!row || !grid) {
623
- console.error(`Failed to find the row at index ${rowIndex} which is being updated.`);
624
- continue;
625
- }
626
- if (rowIndex < top.length) {
627
- top[rowIndex].data = data;
628
- }
629
- else if (rowIndex >= firstBot) {
630
- bot[rowIndex].data = data;
631
- }
632
- else {
633
- const node = f.rowIndexToRow.get(rowIndex - top.length);
634
- if (node)
635
- node.data.data = data;
636
- }
637
- grid.state.rowDataStore.rowInvalidateIndex(rowIndex);
638
- }
290
+ idMap.forEach((data, id) => {
291
+ source.updateRow(id, data);
292
+ });
293
+ grid?.state.rowDataStore.rowClearCache();
639
294
  };
640
295
  const inFilterItems = (c) => {
641
296
  if (!dataInFilterItemFetcher || !grid)
@@ -645,6 +300,16 @@ export function makeServerDataSource({ dataFetcher, dataColumnPivotFetcher, data
645
300
  const rowAreAllSelected = () => {
646
301
  return false;
647
302
  };
303
+ const reset = () => {
304
+ source.reset();
305
+ };
306
+ const pushResponses = (responses) => {
307
+ source.handleResponses(responses);
308
+ };
309
+ const pushRequests = (requests) => {
310
+ source.handleRequests(requests);
311
+ };
312
+ const retry = () => { };
648
313
  return {
649
314
  init,
650
315
  rowAdd,
@@ -656,9 +321,7 @@ export function makeServerDataSource({ dataFetcher, dataColumnPivotFetcher, data
656
321
  rowSelect,
657
322
  rowSelectAll,
658
323
  rowSetBotData,
659
- rowSetCenterData: () => {
660
- throw new Error("Server side data source does not support full row updates");
661
- },
324
+ rowSetCenterData,
662
325
  rowSetTopData,
663
326
  rowToIndex,
664
327
  rowUpdate,
@@ -666,16 +329,10 @@ export function makeServerDataSource({ dataFetcher, dataColumnPivotFetcher, data
666
329
  rowAreAllSelected,
667
330
  isLoading,
668
331
  loadError,
669
- pushResponses: (res) => {
670
- dataResponseHandler(res);
671
- },
672
- pushRequests: (req, onSuccess, onFailure) => {
673
- dataRequestHandler(req, true, onSuccess, onFailure);
674
- },
675
- reset: resetRequest,
676
- retry: () => {
677
- viewChange();
678
- },
332
+ pushResponses,
333
+ pushRequests,
334
+ reset,
335
+ retry,
679
336
  };
680
337
  }
681
338
  export function useServerDataSource(p) {