@1771technologies/lytenyte-pro 1.0.16 → 1.0.17

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.
@@ -0,0 +1,7 @@
1
+ import { type SlotComponent } from "@1771technologies/lytenyte-core/yinternal";
2
+ export interface ClearProps {
3
+ readonly as?: SlotComponent<{
4
+ onClear: () => void;
5
+ }>;
6
+ }
7
+ export declare const Clear: import("react").ForwardRefExoticComponent<Omit<import("react").ClassAttributes<HTMLButtonElement> & import("react").ButtonHTMLAttributes<HTMLButtonElement> & ClearProps, "ref"> & import("react").RefAttributes<HTMLButtonElement>>;
@@ -0,0 +1,22 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useSlot } from "@1771technologies/lytenyte-core/yinternal";
3
+ import { forwardRef } from "react";
4
+ import { useFilterSelectRoot } from "./context.js";
5
+ function ClearImpl({ as, ...props }, ref) {
6
+ const ctx = useFilterSelectRoot();
7
+ const slot = useSlot({
8
+ props: [
9
+ {
10
+ onClick: ctx.clear,
11
+ },
12
+ props,
13
+ ],
14
+ ref: ref,
15
+ slot: as ?? _jsx("button", { children: "Clear" }),
16
+ state: {
17
+ onReset: ctx.clear,
18
+ },
19
+ });
20
+ return slot;
21
+ }
22
+ export const Clear = forwardRef(ClearImpl);
@@ -7,6 +7,7 @@ export interface FilterSelectRoot {
7
7
  readonly maxCount: number;
8
8
  readonly apply: () => void;
9
9
  readonly reset: () => void;
10
+ readonly clear: () => void;
10
11
  }
11
12
  export declare const context: import("react").Context<FilterSelectRoot>;
12
13
  export declare const useFilterSelectRoot: () => FilterSelectRoot;
@@ -3,6 +3,7 @@ export declare const FilterSelect: {
3
3
  useFilterSelect: <T>({ grid, column, maxCount }: import("./use-filter-select.js").UseFilterSelectParams<T>) => {
4
4
  reset: () => void;
5
5
  apply: () => void;
6
+ clear: () => void;
6
7
  defaultFilter: import("./use-filter-select.js").FilterSelectFlat;
7
8
  filters: import("./use-filter-select.js").FilterSelectFlat[];
8
9
  setFilters: (v: import("./use-filter-select.js").FilterSelectFlat[] | ((v: import("./use-filter-select.js").FilterSelectFlat[]) => import("./use-filter-select.js").FilterSelectFlat[])) => void;
@@ -17,4 +18,5 @@ export declare const FilterSelect: {
17
18
  FilterCombinator: import("react").ForwardRefExoticComponent<Omit<import("react").ClassAttributes<HTMLDivElement> & import("react").HTMLAttributes<HTMLDivElement> & import("./filter-combinator.js").FilterCombinatorProps, "ref"> & import("react").RefAttributes<HTMLDivElement>>;
18
19
  Apply: import("react").ForwardRefExoticComponent<Omit<import("react").ClassAttributes<HTMLButtonElement> & import("react").ButtonHTMLAttributes<HTMLButtonElement> & import("./apply.js").ApplyProps, "ref"> & import("react").RefAttributes<HTMLButtonElement>>;
19
20
  Reset: import("react").ForwardRefExoticComponent<Omit<import("react").ClassAttributes<HTMLButtonElement> & import("react").ButtonHTMLAttributes<HTMLButtonElement> & import("./reset.js").ResetProps, "ref"> & import("react").RefAttributes<HTMLButtonElement>>;
21
+ Clear: import("react").ForwardRefExoticComponent<Omit<import("react").ClassAttributes<HTMLButtonElement> & import("react").ButtonHTMLAttributes<HTMLButtonElement> & import("./clear.js").ClearProps, "ref"> & import("react").RefAttributes<HTMLButtonElement>>;
20
22
  };
@@ -1,4 +1,5 @@
1
1
  import { Apply } from "./apply.js";
2
+ import { Clear } from "./clear.js";
2
3
  import { FilterCombinator } from "./filter-combinator.js";
3
4
  import { FilterRow } from "./filter-row.js";
4
5
  import { OperatorSelect } from "./operator-select.js";
@@ -15,4 +16,5 @@ export const FilterSelect = {
15
16
  FilterCombinator,
16
17
  Apply,
17
18
  Reset,
19
+ Clear,
18
20
  };
@@ -7,6 +7,7 @@ export interface RootProps {
7
7
  readonly setFilters: Dispatch<SetStateAction<FilterSelectFlat[]>>;
8
8
  readonly apply: () => void;
9
9
  readonly reset: () => void;
10
+ readonly clear: () => void;
10
11
  readonly maxCount: number;
11
12
  };
12
13
  }
@@ -30,6 +30,7 @@ export type FilterSelectFlat = FilterSelectFlatFunc | FilterSelectFlatNumber | F
30
30
  export declare const useFilterSelect: <T>({ grid, column, maxCount }: UseFilterSelectParams<T>) => {
31
31
  reset: () => void;
32
32
  apply: () => void;
33
+ clear: () => void;
33
34
  defaultFilter: FilterSelectFlat;
34
35
  filters: FilterSelectFlat[];
35
36
  setFilters: (v: FilterSelectFlat[] | ((v: FilterSelectFlat[]) => FilterSelectFlat[])) => void;
@@ -40,7 +40,6 @@ export const useFilterSelect = ({ grid, column, maxCount = 2 }) => {
40
40
  filters.push({ kind: "date", operator: f.operator, value: f.value, nextExtender });
41
41
  }
42
42
  }
43
- console.log(filters);
44
43
  return filters;
45
44
  }
46
45
  return [];
@@ -49,6 +48,8 @@ export const useFilterSelect = ({ grid, column, maxCount = 2 }) => {
49
48
  }, [filterOnModel, defaultFilter]);
50
49
  const [filters, setFilters] = useTwoFlowState(filterValue);
51
50
  const finalFilters = useMemo(() => {
51
+ if (!filters.length)
52
+ return [defaultFilter];
52
53
  const last = filters.at(-1);
53
54
  if (last.kind === "function" || filters.length >= maxCount)
54
55
  return filters;
@@ -88,7 +89,6 @@ export const useFilterSelect = ({ grid, column, maxCount = 2 }) => {
88
89
  else if (filter.kind === "string")
89
90
  next = { kind: "string", value: filter.value, operator: filter.operator };
90
91
  if (next) {
91
- console.log(next);
92
92
  grid.state.filterModel.set((prev) => {
93
93
  const filters = { ...prev };
94
94
  filters[column.id] = next;
@@ -147,12 +147,21 @@ export const useFilterSelect = ({ grid, column, maxCount = 2 }) => {
147
147
  const next = typeof v === "function" ? v(finalFilters) : v;
148
148
  setFilters(next);
149
149
  }, [finalFilters, setFilters]);
150
+ const clear = useCallback(() => {
151
+ setF([]);
152
+ grid.state.filterModel.set((prev) => {
153
+ const filters = { ...prev };
154
+ delete filters[column.id];
155
+ return filters;
156
+ });
157
+ }, [column.id, grid.state.filterModel, setF]);
150
158
  return useMemo(() => ({
151
159
  reset,
152
160
  apply,
161
+ clear,
153
162
  defaultFilter,
154
163
  filters: finalFilters,
155
164
  setFilters: setF,
156
165
  maxCount,
157
- }), [apply, defaultFilter, finalFilters, maxCount, reset, setF]);
166
+ }), [apply, clear, defaultFilter, finalFilters, maxCount, reset, setF]);
158
167
  };
@@ -13,7 +13,16 @@ export interface FlatView {
13
13
  readonly rowIdToRowIndex: Map<string, number>;
14
14
  readonly rowIdToTreeNode: Map<string, LeafOrParent<RowGroup, RowLeaf>>;
15
15
  readonly loading: Set<number>;
16
- readonly errored: Map<number, unknown>;
16
+ readonly loadingGroup: Set<number>;
17
+ readonly errored: Map<number, {
18
+ error: unknown;
19
+ request?: DataRequest;
20
+ }>;
21
+ readonly erroredGroup: Map<number, {
22
+ error: unknown;
23
+ request: DataRequest;
24
+ }>;
25
+ readonly seenRequests: Set<string>;
17
26
  }
18
27
  export interface ServerDataConstructorParams {
19
28
  readonly blocksize: number;
@@ -24,11 +33,12 @@ export interface ServerDataConstructorParams {
24
33
  readonly onResetLoadError: (error: unknown) => void;
25
34
  readonly onResetLoadEnd: () => void;
26
35
  readonly onFlatten: (r: FlatView) => void;
36
+ readonly onInvalidate: () => void;
27
37
  readonly defaultExpansion: boolean | number;
28
38
  }
29
39
  export declare class ServerData {
30
40
  #private;
31
- constructor({ blocksize, pivotMode, pivotExpansions, expansions, onResetLoadBegin, onResetLoadEnd, onResetLoadError, onFlatten, defaultExpansion, }: ServerDataConstructorParams);
41
+ constructor({ blocksize, pivotMode, pivotExpansions, expansions, onResetLoadBegin, onResetLoadEnd, onResetLoadError, onFlatten, onInvalidate, defaultExpansion, }: ServerDataConstructorParams);
32
42
  set dataFetcher(d: DataFetcher);
33
43
  set pivotMode(b: boolean);
34
44
  set expansions(d: Record<string, boolean | undefined>);
@@ -36,13 +46,25 @@ export declare class ServerData {
36
46
  set defaultExpansion(d: boolean | number);
37
47
  set rowViewBounds(viewBounds: [start: number, end: number]);
38
48
  reset: () => Promise<void>;
49
+ requestForGroup(i: number): DataRequest | null;
39
50
  handleRequests: (requests: DataRequest[], opts?: {
40
51
  onError?: (e: unknown) => void;
41
52
  onSuccess?: () => void;
42
53
  skipState?: boolean;
43
54
  }) => Promise<void>;
44
55
  handleResponses: (data: (DataResponse | DataResponsePinned)[], beforeOnFlat?: () => void) => void;
56
+ requestForNextSlice(req: DataRequest): {
57
+ id: string;
58
+ path: (string | null)[];
59
+ start: number;
60
+ end: number;
61
+ rowStartIndex: number;
62
+ rowEndIndex: number;
63
+ } | null;
64
+ requestsForView(start?: number, end?: number): DataRequest[];
45
65
  handleViewBoundsChange(): Promise<void>;
66
+ retry(): void;
46
67
  updateRow(id: string, data: any): void;
68
+ flatten: () => void;
47
69
  }
48
70
  export {};
@@ -19,13 +19,16 @@ export class ServerData {
19
19
  #onResetLoadError;
20
20
  #onResetLoadEnd;
21
21
  #onFlatten;
22
+ #onInvalidate;
22
23
  #rowViewBounds = [0, 0];
23
- #prevRequests = [];
24
+ #seenRequests = new Set();
24
25
  #loadingRows = new Set();
26
+ #loadingGroup = new Set();
25
27
  #rowsWithError = new Map();
28
+ #rowsWithGroupError = new Map();
26
29
  #controllers = new Set();
27
30
  #defaultExpansion;
28
- constructor({ blocksize, pivotMode, pivotExpansions, expansions, onResetLoadBegin, onResetLoadEnd, onResetLoadError, onFlatten, defaultExpansion, }) {
31
+ constructor({ blocksize, pivotMode, pivotExpansions, expansions, onResetLoadBegin, onResetLoadEnd, onResetLoadError, onFlatten, onInvalidate, defaultExpansion, }) {
29
32
  this.#tree = makeAsyncTree();
30
33
  this.#blocksize = blocksize;
31
34
  this.#pivotMode = pivotMode;
@@ -36,6 +39,7 @@ export class ServerData {
36
39
  this.#onResetLoadEnd = onResetLoadEnd;
37
40
  this.#onResetLoadError = onResetLoadError;
38
41
  this.#onFlatten = onFlatten;
42
+ this.#onInvalidate = onInvalidate;
39
43
  }
40
44
  // Properties
41
45
  set dataFetcher(d) {
@@ -77,29 +81,54 @@ export class ServerData {
77
81
  this.#controllers.forEach((c) => c.abort());
78
82
  this.#tree = makeAsyncTree();
79
83
  this.#flatten();
84
+ this.#rowsWithError.clear();
85
+ this.#rowsWithGroupError.clear();
86
+ this.#loadingRows.clear();
87
+ this.#loadingGroup.clear();
80
88
  try {
81
89
  this.#onResetLoadBegin();
82
- this.#prevRequests = [
83
- {
84
- rowStartIndex: 0,
85
- rowEndIndex: this.#blocksize,
86
- id: getRequestId([], 0, this.#blocksize),
87
- path: [],
88
- start: 0,
89
- end: this.#blocksize,
90
- },
91
- ];
92
- const res = await this.#dataFetcher(this.#prevRequests, this.#expansions, this.#pivotExpansions);
90
+ this.#seenRequests.clear();
91
+ const req = {
92
+ rowStartIndex: 0,
93
+ rowEndIndex: this.#blocksize,
94
+ id: getRequestId([], 0, this.#blocksize),
95
+ path: [],
96
+ start: 0,
97
+ end: this.#blocksize,
98
+ };
99
+ this.#seenRequests.add(req.id);
100
+ const res = await this.#dataFetcher([req], this.#expansions, this.#pivotExpansions);
93
101
  this.handleResponses(res);
94
102
  }
95
103
  catch (e) {
96
- console.log(e);
97
104
  this.#onResetLoadError(e);
98
105
  }
99
106
  finally {
100
107
  this.#onResetLoadEnd();
101
108
  }
102
109
  };
110
+ requestForGroup(i) {
111
+ const ranges = this.#flat.rangeTree.findRangesForRowIndex(i);
112
+ const path = ranges.slice(1).map((c) => (c.parent.kind === "parent" ? c.parent.path : null));
113
+ const row = this.#flat.rowIndexToRow.get(i);
114
+ if (row?.kind !== "branch")
115
+ return null;
116
+ path.push(row.key);
117
+ const r = (ranges.at(-1)?.parent).byPath.get(row.key);
118
+ const blocksize = this.#blocksize;
119
+ const start = 0;
120
+ const end = Math.min(start + blocksize, r.size);
121
+ const reqSize = end - start;
122
+ const req = {
123
+ path,
124
+ start: start,
125
+ end: end,
126
+ id: getRequestId(path, 0, blocksize),
127
+ rowStartIndex: i + 1,
128
+ rowEndIndex: i + 1 + reqSize,
129
+ };
130
+ return req;
131
+ }
103
132
  handleRequests = async (requests, opts = {}) => {
104
133
  const controller = new AbortController();
105
134
  this.#controllers.add(controller);
@@ -133,10 +162,11 @@ export class ServerData {
133
162
  if (controller.signal.aborted)
134
163
  return;
135
164
  opts?.onError?.(e);
165
+ this.#onInvalidate();
136
166
  if (!skip)
137
167
  requests.forEach((req) => {
138
168
  for (let i = req.rowStartIndex; i < req.rowEndIndex; i++)
139
- this.#rowsWithError.set(i, e);
169
+ this.#rowsWithError.set(i, { error: e, request: req });
140
170
  for (let i = req.rowStartIndex; i < req.rowEndIndex; i++)
141
171
  this.#loadingRows.delete(i);
142
172
  });
@@ -204,8 +234,38 @@ export class ServerData {
204
234
  // Re-flatten our tree once everything has been re-updated.
205
235
  this.#flatten(beforeOnFlat);
206
236
  };
207
- async handleViewBoundsChange() {
208
- const [start, end] = this.#rowViewBounds;
237
+ requestForNextSlice(req) {
238
+ let current = this.#tree;
239
+ for (const c of req.path) {
240
+ if (current.kind === "leaf")
241
+ return null;
242
+ const next = current.byPath.get(c);
243
+ if (!next)
244
+ return null;
245
+ current = next;
246
+ }
247
+ if (current.kind === "leaf")
248
+ return null;
249
+ const maxSize = current.size;
250
+ if (req.end >= maxSize)
251
+ return null;
252
+ const prevSize = req.end - req.start;
253
+ const start = req.end;
254
+ const end = Math.min(req.end + this.#blocksize, maxSize);
255
+ const size = end - start;
256
+ return {
257
+ id: getRequestId(req.path, start, start + this.#blocksize),
258
+ path: req.path,
259
+ start,
260
+ end,
261
+ rowStartIndex: req.rowStartIndex + prevSize,
262
+ rowEndIndex: req.rowStartIndex + prevSize + size,
263
+ };
264
+ }
265
+ requestsForView(start, end) {
266
+ const bounds = this.#rowViewBounds;
267
+ start = start ?? bounds[0];
268
+ end = end ?? bounds[1];
209
269
  const seen = new Set();
210
270
  const requests = [];
211
271
  for (let i = start; i < end; i++) {
@@ -237,13 +297,74 @@ export class ServerData {
237
297
  }
238
298
  });
239
299
  }
240
- const newRequests = requests.filter((c) => !this.#prevRequests.find((prev) => prev.id === c.id));
300
+ return requests;
301
+ }
302
+ async handleViewBoundsChange() {
303
+ const requests = this.requestsForView();
304
+ const newRequests = requests.filter((c) => !this.#seenRequests.has(c.id));
241
305
  // We don't have any new requests to make in our view, so we can return
242
306
  if (!newRequests.length)
243
307
  return;
244
- this.#prevRequests = requests;
308
+ for (const n of newRequests)
309
+ this.#seenRequests.add(n.id);
245
310
  await this.handleRequests(newRequests);
246
311
  }
312
+ retry() {
313
+ const inViewSet = new Set(this.requestsForView().map((c) => c.id));
314
+ const erroredRequests = [...this.#rowsWithError.values()]
315
+ .map((c) => c.request)
316
+ .filter(Boolean);
317
+ const erroredGroups = [...this.#rowsWithGroupError.entries()];
318
+ const errors = erroredRequests.filter((x) => inViewSet.has(x.id));
319
+ const [start, end] = this.#rowViewBounds;
320
+ const groupErrors = erroredGroups
321
+ .filter(([index]) => {
322
+ return index >= start && index < end;
323
+ })
324
+ .map(([index, c]) => [index, c.request]);
325
+ const seenRequests = this.#seenRequests;
326
+ console.log(structuredClone(seenRequests));
327
+ erroredRequests.map((x) => seenRequests.delete(x.id));
328
+ erroredGroups.map((x) => seenRequests.delete(x[1].request.id));
329
+ console.log(structuredClone(seenRequests));
330
+ this.#rowsWithError.clear();
331
+ this.#rowsWithGroupError.clear();
332
+ const requests = [];
333
+ const seen = new Set();
334
+ groupErrors.forEach((x) => {
335
+ if (seen.has(x[1].id))
336
+ return;
337
+ requests.push(x[1]);
338
+ seen.add(x[1].id);
339
+ });
340
+ errors.forEach((x) => {
341
+ if (seen.has(x.id))
342
+ return;
343
+ seen.add(x.id);
344
+ requests.push(x);
345
+ });
346
+ for (const x of groupErrors) {
347
+ this.#loadingGroup.add(x[0]);
348
+ }
349
+ requests.forEach((x) => seenRequests.add(x.id));
350
+ const invalidate = this.#onInvalidate;
351
+ const withGroupError = this.#rowsWithGroupError;
352
+ const loadingGroup = this.#loadingGroup;
353
+ this.handleRequests(requests, {
354
+ onError: (e) => {
355
+ invalidate();
356
+ groupErrors.forEach((c) => {
357
+ withGroupError.set(c[0], { error: e, request: c[1] });
358
+ loadingGroup.delete(c[0]);
359
+ });
360
+ },
361
+ onSuccess: () => {
362
+ groupErrors.forEach((c) => {
363
+ loadingGroup.delete(c[0]);
364
+ });
365
+ },
366
+ });
367
+ }
247
368
  updateRow(id, data) {
248
369
  const centerRow = this.#flat.rowIdToTreeNode.get(id);
249
370
  if (centerRow) {
@@ -260,19 +381,28 @@ export class ServerData {
260
381
  this.#top.rows[botIndex] = { ...this.#top.rows[botIndex], data };
261
382
  }
262
383
  }
384
+ flatten = () => {
385
+ this.#flatten();
386
+ };
263
387
  #flatten = (beforeOnFlat) => {
388
+ // The mode we are in determines the expansions we will use for the server data.
264
389
  const mode = this.#pivotMode;
265
390
  const expansions = mode ? this.#pivotExpansions : this.#expansions;
266
391
  const t = this.#tree;
392
+ // We use these maps to keep track of the current view. These are helpful for
393
+ // quick lookup. They are also used to implement many of the data source APIs.
267
394
  const rowIdToRow = new Map();
268
395
  const rowIndexToRow = new Map();
269
396
  const rowIdToRowIndex = new Map();
270
397
  const rowIdToTreeNode = new Map();
398
+ // When flattening the tree we need to keep track of the ranges. The tree itself
399
+ // will only have some rows loaded, but the ranges will be fully defined.
271
400
  const ranges = [];
272
401
  const blocksize = this.#blocksize;
273
- const previousRequests = this.#prevRequests;
274
- const withError = this.#rowsWithError;
275
- const withLoading = this.#loadingRows;
402
+ const seen = this.#seenRequests;
403
+ // Tracks the error and loading state of the rows.
404
+ const withGroupError = this.#rowsWithGroupError;
405
+ const withLoadingGroup = this.#loadingGroup;
276
406
  const handleRequests = this.handleRequests;
277
407
  const defaultExpansion = this.#defaultExpansion;
278
408
  const postFlatRequests = [];
@@ -286,11 +416,17 @@ export class ServerData {
286
416
  rowIdToRowIndex.set(row.data.id, rowIndex);
287
417
  rowIdToRow.set(row.data.id, row.data);
288
418
  rowIdToTreeNode.set(row.data.id, row);
419
+ // If this rows is a parent row, we need to check if it is expanded. There are a couple of
420
+ // situations this to consider.
421
+ // - the row is not expanded, in which case we only add the row itself to the flat view
422
+ // - the row is expanded but it has no data loaded. We should then request data, but not add the rows
423
+ // - the row is expanded and there is add. This is the easy case, we simply add the child rows as we flatten
289
424
  if (row.kind === "parent") {
290
425
  const expanded = expansions[row.data.id] ??
291
426
  (typeof defaultExpansion === "number"
292
427
  ? getNodeDepth(row) <= defaultExpansion
293
428
  : defaultExpansion);
429
+ // Expanded but no data. Fetch the child data.
294
430
  if (expanded && !row.byIndex.size) {
295
431
  const path = getNodePath(row);
296
432
  const start = 0;
@@ -304,8 +440,8 @@ export class ServerData {
304
440
  rowStartIndex: rowIndex + 1,
305
441
  rowEndIndex: rowIndex + 1 + reqSize,
306
442
  };
307
- // If we haven't already requested this node
308
- if (!previousRequests.find((c) => c.id === req.id)) {
443
+ // If we haven't already requested the children data for this node, let's request it.
444
+ if (!seen.has(req.id)) {
309
445
  postFlatRequests.push([rowIndex, req]);
310
446
  }
311
447
  }
@@ -340,21 +476,25 @@ export class ServerData {
340
476
  const rangeTree = new RangeTree(ranges);
341
477
  if (postFlatRequests.length > 0) {
342
478
  postFlatRequests.forEach((c) => {
343
- withLoading.add(c[0]);
344
- previousRequests.push(c[1]);
479
+ withLoadingGroup.add(c[0]);
480
+ seen.add(c[1].id);
345
481
  });
482
+ const invalidate = this.#onInvalidate;
346
483
  const reqs = postFlatRequests.map((c) => c[1]);
347
484
  handleRequests(reqs, {
348
485
  skipState: true,
349
486
  onError: (e) => {
487
+ invalidate();
350
488
  postFlatRequests.forEach((c) => {
351
- withLoading.delete(c[0]);
352
- withError.set(c[0], e);
489
+ const rowIndex = c[0];
490
+ const req = c[1];
491
+ withLoadingGroup.delete(rowIndex);
492
+ withGroupError.set(rowIndex, { error: e, request: req });
353
493
  });
354
494
  },
355
495
  onSuccess: () => {
356
496
  postFlatRequests.forEach((c) => {
357
- withLoading.delete(c[0]);
497
+ withLoadingGroup.delete(c[0]);
358
498
  });
359
499
  },
360
500
  });
@@ -370,7 +510,10 @@ export class ServerData {
370
510
  rowIdToRowIndex,
371
511
  rowIdToTreeNode,
372
512
  errored: this.#rowsWithError,
513
+ erroredGroup: this.#rowsWithGroupError,
373
514
  loading: this.#loadingRows,
515
+ loadingGroup: this.#loadingGroup,
516
+ seenRequests: seen,
374
517
  };
375
518
  beforeOnFlat?.();
376
519
  this.#onFlatten(this.#flat);
@@ -1,3 +1,3 @@
1
1
  import type { RowDataSourceServer, RowDataSourceServerParams } from "../+types.js";
2
- export declare function makeServerDataSource<T>({ dataFetcher, dataInFilterItemFetcher, dataColumnPivotFetcher, cellUpdateHandler, cellUpdateOptimistically, blockSize, }: RowDataSourceServerParams<T>): RowDataSourceServer<T>;
2
+ export declare function makeServerDataSource<T>({ dataFetcher, dataInFilterItemFetcher, dataColumnPivotFetcher, cellUpdateHandler, cellUpdateOptimistically, blockSize, }: Omit<RowDataSourceServerParams<T>, "dataFetchExternals">): RowDataSourceServer<T>;
3
3
  export declare function useServerDataSource<T>(p: RowDataSourceServerParams<T>): RowDataSourceServer<T>;
@@ -9,6 +9,7 @@ export function makeServerDataSource({ dataFetcher, dataInFilterItemFetcher, dat
9
9
  void grid;
10
10
  const isLoading = makeAtom(signal(false));
11
11
  const loadError = makeAtom(signal(null));
12
+ const requestsForView = makeAtom(signal([]));
12
13
  const cleanup = [];
13
14
  const init = (g) => {
14
15
  grid = g;
@@ -22,6 +23,7 @@ export function makeServerDataSource({ dataFetcher, dataInFilterItemFetcher, dat
22
23
  isLoading.set(true);
23
24
  loadError.set(null);
24
25
  },
26
+ onInvalidate: () => g.state.rowDataStore.rowClearCache(),
25
27
  onResetLoadEnd: () => isLoading.set(false),
26
28
  onResetLoadError: (e) => loadError.set(e),
27
29
  onFlatten: (f) => {
@@ -119,6 +121,11 @@ export function makeServerDataSource({ dataFetcher, dataInFilterItemFetcher, dat
119
121
  cleanup.push(g.state.viewBounds.watch(() => {
120
122
  const bounds = g.state.viewBounds.get();
121
123
  source.rowViewBounds = [bounds.rowCenterStart, bounds.rowCenterEnd];
124
+ const requests = source.requestsForView();
125
+ const current = requestsForView.get();
126
+ if (equal(requests, current))
127
+ return;
128
+ requestsForView.set(requests);
122
129
  }));
123
130
  source.dataFetcher = (req, expansions, pivotExpansions) => {
124
131
  return dataFetcher({
@@ -161,8 +168,9 @@ export function makeServerDataSource({ dataFetcher, dataInFilterItemFetcher, dat
161
168
  const rowByIndex = (i) => {
162
169
  const row = flat.rowIndexToRow.get(i);
163
170
  const isLoading = flat.loading.has(i);
171
+ const isGroupLoading = flat.loadingGroup.has(i);
172
+ const errorGroup = flat.erroredGroup.get(i);
164
173
  const error = flat.errored.get(i);
165
- // If we haven't loaded a row yet.
166
174
  if (!row)
167
175
  return {
168
176
  id: `__loading__placeholder__${i}`,
@@ -171,13 +179,23 @@ export function makeServerDataSource({ dataFetcher, dataInFilterItemFetcher, dat
171
179
  loading: isLoading,
172
180
  error: error,
173
181
  };
174
- if (error) {
175
- return { ...row, error };
182
+ if (row.kind === "leaf") {
183
+ if (error || isLoading) {
184
+ return { ...row, loading: isLoading, error: error?.error };
185
+ }
176
186
  }
177
- if (isLoading) {
178
- return { ...row, loading: isLoading };
187
+ else if (row.kind === "branch") {
188
+ if (error || isLoading || isGroupLoading || errorGroup) {
189
+ return {
190
+ ...row,
191
+ loading: isLoading,
192
+ error: error?.error,
193
+ errorGroup: errorGroup?.error,
194
+ loadingGroup: isGroupLoading,
195
+ };
196
+ }
179
197
  }
180
- return flat.rowIndexToRow.get(i) ?? null;
198
+ return row ?? null;
181
199
  };
182
200
  const rowExpand = (expansions) => {
183
201
  if (!grid)
@@ -290,7 +308,7 @@ export function makeServerDataSource({ dataFetcher, dataInFilterItemFetcher, dat
290
308
  idMap.forEach((data, id) => {
291
309
  source.updateRow(id, data);
292
310
  });
293
- grid?.state.rowDataStore.rowClearCache();
311
+ source.flatten();
294
312
  };
295
313
  const inFilterItems = (c) => {
296
314
  if (!dataInFilterItemFetcher || !grid)
@@ -309,7 +327,23 @@ export function makeServerDataSource({ dataFetcher, dataInFilterItemFetcher, dat
309
327
  const pushRequests = (requests) => {
310
328
  source.handleRequests(requests);
311
329
  };
312
- const retry = () => { };
330
+ const retry = () => {
331
+ source.retry();
332
+ grid?.state.rowDataStore.rowClearCache();
333
+ };
334
+ const refresh = (onSuccess, onError) => {
335
+ const requests = source.requestsForView();
336
+ pushRequests(requests, onSuccess, onError);
337
+ };
338
+ const requestForGroup = (row) => {
339
+ const index = typeof row === "number" ? row : flat.rowIdToRowIndex.get(row.id);
340
+ if (index == null)
341
+ return null;
342
+ return source.requestForGroup(index);
343
+ };
344
+ const requestForNextSlice = (req) => {
345
+ return source.requestForNextSlice(req);
346
+ };
313
347
  return {
314
348
  init,
315
349
  rowAdd,
@@ -333,12 +367,47 @@ export function makeServerDataSource({ dataFetcher, dataInFilterItemFetcher, dat
333
367
  pushRequests,
334
368
  reset,
335
369
  retry,
370
+ refresh,
371
+ requestsForView: requestsForView,
372
+ requestForGroup,
373
+ requestForNextSlice,
374
+ get seenRequests() {
375
+ return flat.seenRequests;
376
+ },
336
377
  };
337
378
  }
338
379
  export function useServerDataSource(p) {
339
380
  const ds = useRef(null);
381
+ const fetcherRef = useRef(p.dataFetcher);
382
+ const prevExternal = useRef(p.dataFetchExternals ?? []);
383
+ const animRef = useRef(false);
384
+ if (!arrayShallow(prevExternal.current, p.dataFetchExternals ?? [])) {
385
+ prevExternal.current = p.dataFetchExternals ?? [];
386
+ fetcherRef.current = p.dataFetcher;
387
+ if (ds.current && !animRef.current) {
388
+ animRef.current = true;
389
+ queueMicrotask(() => {
390
+ ds.current.reset();
391
+ animRef.current = false;
392
+ });
393
+ }
394
+ }
340
395
  if (!ds.current) {
341
- ds.current = makeServerDataSource(p);
396
+ ds.current = makeServerDataSource({
397
+ ...p,
398
+ dataFetcher: (params) => {
399
+ return fetcherRef.current(params);
400
+ },
401
+ });
342
402
  }
343
403
  return ds.current;
344
404
  }
405
+ function arrayShallow(left, right) {
406
+ if (left.length !== right.length)
407
+ return false;
408
+ for (let i = 0; i < left.length; i++) {
409
+ if (left[i] !== right[i])
410
+ return false;
411
+ }
412
+ return true;
413
+ }