@bendyline/squisq-grid-react 2.11.12 → 2.11.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +23 -9
- package/dist/index.js +145 -33
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -202,10 +202,12 @@ declare function buildKernelSource(): string;
|
|
|
202
202
|
interface KernelHost {
|
|
203
203
|
post(message: KernelRequest, transfer?: Transferable[]): void;
|
|
204
204
|
onResponse: (message: KernelResponse) => void;
|
|
205
|
+
onFailure: (error: Error) => void;
|
|
205
206
|
terminate(): void;
|
|
206
207
|
}
|
|
207
208
|
/** In-process host: drives `tableKernel` directly (SSR/tests/parity). */
|
|
208
209
|
declare class LocalKernelHost implements KernelHost {
|
|
210
|
+
onFailure: (error: Error) => void;
|
|
209
211
|
onResponse: (message: KernelResponse) => void;
|
|
210
212
|
private readonly scope;
|
|
211
213
|
constructor();
|
|
@@ -215,6 +217,8 @@ declare class LocalKernelHost implements KernelHost {
|
|
|
215
217
|
interface TableStoreClientOptions {
|
|
216
218
|
/** Force the in-process host (tests, SSR probes). */
|
|
217
219
|
forceLocal?: boolean;
|
|
220
|
+
/** Maximum wait for a worker response; default 30 seconds. */
|
|
221
|
+
requestTimeoutMs?: number;
|
|
218
222
|
}
|
|
219
223
|
declare class TableStoreClient implements TableQueryProvider {
|
|
220
224
|
private readonly host;
|
|
@@ -222,8 +226,11 @@ declare class TableStoreClient implements TableQueryProvider {
|
|
|
222
226
|
private seq;
|
|
223
227
|
private viewRowCount;
|
|
224
228
|
private readonly pending;
|
|
229
|
+
private failure;
|
|
230
|
+
private readonly requestTimeoutMs;
|
|
225
231
|
private ready;
|
|
226
232
|
constructor(table: IngestTable, options?: TableStoreClientOptions);
|
|
233
|
+
private fail;
|
|
227
234
|
private request;
|
|
228
235
|
describe(): Promise<TableSchema>;
|
|
229
236
|
setView(view: TableViewState): Promise<TableViewResult>;
|
|
@@ -244,10 +251,9 @@ declare class TableStoreClient implements TableQueryProvider {
|
|
|
244
251
|
* identity was never derived from values.
|
|
245
252
|
*
|
|
246
253
|
* Batches are the undo unit (one commit = one batch). The journal is
|
|
247
|
-
* additionally cached
|
|
248
|
-
*
|
|
249
|
-
*
|
|
250
|
-
* re-upload) naturally misses the cache and starts clean.
|
|
254
|
+
* additionally cached by storage owner, path, and region so a widget
|
|
255
|
+
* unmount/remount does not lose unsaved edits. Scoped journals survive
|
|
256
|
+
* revision bumps; saving acknowledges only the persisted snapshot.
|
|
251
257
|
*/
|
|
252
258
|
|
|
253
259
|
interface JournalEntry {
|
|
@@ -273,17 +279,25 @@ declare class EditJournal {
|
|
|
273
279
|
redo(): TableCellEdit[];
|
|
274
280
|
/** Net outstanding edits (what a Save must persist). */
|
|
275
281
|
entries(): JournalEntry[];
|
|
282
|
+
/** Advance the save baseline without losing edits (or undo) made during I/O. */
|
|
283
|
+
acknowledge(saved: readonly JournalEntry[]): void;
|
|
276
284
|
clear(): void;
|
|
277
285
|
private applyLatest;
|
|
278
286
|
private rebuildLatest;
|
|
279
287
|
}
|
|
288
|
+
interface JournalScope {
|
|
289
|
+
/** Container or provider that owns the bytes; never a relative file path. */
|
|
290
|
+
owner: object;
|
|
291
|
+
/** Worksheet, region anchor, and header interpretation. */
|
|
292
|
+
region: string;
|
|
293
|
+
}
|
|
280
294
|
/**
|
|
281
|
-
* Get
|
|
282
|
-
*
|
|
283
|
-
*
|
|
295
|
+
* Get the journal for an owner/path/region. Scoped edits survive a revision
|
|
296
|
+
* change elsewhere in the container. Legacy unscoped callers start clean
|
|
297
|
+
* at each revision, preserving the original two-argument API.
|
|
284
298
|
*/
|
|
285
|
-
declare function journalFor(path: string, revision: number): EditJournal;
|
|
286
|
-
declare function discardJournal(path: string): void;
|
|
299
|
+
declare function journalFor(path: string, revision: number, scope?: JournalScope): EditJournal;
|
|
300
|
+
declare function discardJournal(path: string, scope?: JournalScope): void;
|
|
287
301
|
|
|
288
302
|
/**
|
|
289
303
|
* DataGrid — the virtualized grid over a `TableQueryProvider`.
|
package/dist/index.js
CHANGED
|
@@ -399,6 +399,8 @@ function buildKernelSource() {
|
|
|
399
399
|
import { parseTableViewState, serializeTableViewState } from "@bendyline/squisq/table";
|
|
400
400
|
var LocalKernelHost = class {
|
|
401
401
|
constructor() {
|
|
402
|
+
this.onFailure = () => {
|
|
403
|
+
};
|
|
402
404
|
this.onResponse = () => {
|
|
403
405
|
};
|
|
404
406
|
const scope = {
|
|
@@ -417,6 +419,8 @@ var LocalKernelHost = class {
|
|
|
417
419
|
};
|
|
418
420
|
var BlobWorkerHost = class {
|
|
419
421
|
constructor() {
|
|
422
|
+
this.onFailure = () => {
|
|
423
|
+
};
|
|
420
424
|
this.onResponse = () => {
|
|
421
425
|
};
|
|
422
426
|
const url = URL.createObjectURL(new Blob([buildKernelSource()], { type: "text/javascript" }));
|
|
@@ -428,6 +432,11 @@ var BlobWorkerHost = class {
|
|
|
428
432
|
this.worker.onmessage = (event) => {
|
|
429
433
|
this.onResponse(event.data);
|
|
430
434
|
};
|
|
435
|
+
this.worker.onerror = (event) => {
|
|
436
|
+
event.preventDefault();
|
|
437
|
+
this.onFailure(new Error(event.message || "Table worker failed"));
|
|
438
|
+
};
|
|
439
|
+
this.worker.onmessageerror = () => this.onFailure(new Error("Table worker response could not be decoded"));
|
|
431
440
|
}
|
|
432
441
|
post(message, transfer) {
|
|
433
442
|
this.worker.postMessage(message, transfer ?? []);
|
|
@@ -452,6 +461,8 @@ var TableStoreClient = class {
|
|
|
452
461
|
constructor(table, options = {}) {
|
|
453
462
|
this.seq = 0;
|
|
454
463
|
this.pending = /* @__PURE__ */ new Map();
|
|
464
|
+
this.failure = null;
|
|
465
|
+
this.requestTimeoutMs = options.requestTimeoutMs ?? 3e4;
|
|
455
466
|
const columnar = buildColumnarTable(table);
|
|
456
467
|
this.schema = {
|
|
457
468
|
columns: columnar.columns.map((column) => ({ name: column.name, kind: column.kind })),
|
|
@@ -460,12 +471,14 @@ var TableStoreClient = class {
|
|
|
460
471
|
this.viewRowCount = columnar.rowCount;
|
|
461
472
|
this.host = options.forceLocal || typeof Worker === "undefined" ? new LocalKernelHost() : new BlobWorkerHost();
|
|
462
473
|
this.host.onResponse = (message) => {
|
|
463
|
-
const
|
|
464
|
-
if (
|
|
474
|
+
const waiter = this.pending.get(message.seq);
|
|
475
|
+
if (waiter) {
|
|
465
476
|
this.pending.delete(message.seq);
|
|
466
|
-
|
|
477
|
+
clearTimeout(waiter.timer);
|
|
478
|
+
waiter.respond(message);
|
|
467
479
|
}
|
|
468
480
|
};
|
|
481
|
+
this.host.onFailure = (error) => this.fail(error);
|
|
469
482
|
const payloads = columnar.columns.map(toPayload);
|
|
470
483
|
this.ready = this.request(
|
|
471
484
|
(seq) => ({
|
|
@@ -476,19 +489,43 @@ var TableStoreClient = class {
|
|
|
476
489
|
}),
|
|
477
490
|
payloads.flatMap((entry) => entry.transfer)
|
|
478
491
|
).then(() => void 0);
|
|
492
|
+
void this.ready.catch(() => void 0);
|
|
493
|
+
}
|
|
494
|
+
fail(error) {
|
|
495
|
+
if (this.failure) return;
|
|
496
|
+
this.failure = error;
|
|
497
|
+
for (const waiter of this.pending.values()) {
|
|
498
|
+
clearTimeout(waiter.timer);
|
|
499
|
+
waiter.reject(error);
|
|
500
|
+
}
|
|
501
|
+
this.pending.clear();
|
|
502
|
+
this.host.terminate();
|
|
479
503
|
}
|
|
480
504
|
request(build, transfer) {
|
|
505
|
+
if (this.failure) return Promise.reject(this.failure);
|
|
481
506
|
const seq = ++this.seq;
|
|
482
507
|
return new Promise((resolve, reject) => {
|
|
483
|
-
this.pending.set(seq,
|
|
484
|
-
|
|
485
|
-
|
|
508
|
+
this.pending.set(seq, {
|
|
509
|
+
respond: (message) => {
|
|
510
|
+
if (message.type === "error") reject(new Error(message.message));
|
|
511
|
+
else resolve(message);
|
|
512
|
+
},
|
|
513
|
+
reject,
|
|
514
|
+
timer: setTimeout(
|
|
515
|
+
() => this.fail(new Error("Table worker request timed out")),
|
|
516
|
+
this.requestTimeoutMs
|
|
517
|
+
)
|
|
486
518
|
});
|
|
487
|
-
|
|
519
|
+
try {
|
|
520
|
+
this.host.post(build(seq), transfer);
|
|
521
|
+
} catch (error) {
|
|
522
|
+
this.fail(error instanceof Error ? error : new Error(String(error)));
|
|
523
|
+
}
|
|
488
524
|
});
|
|
489
525
|
}
|
|
490
526
|
async describe() {
|
|
491
527
|
await this.ready;
|
|
528
|
+
if (this.failure) throw this.failure;
|
|
492
529
|
return this.schema;
|
|
493
530
|
}
|
|
494
531
|
async setView(view) {
|
|
@@ -546,9 +583,12 @@ var TableStoreClient = class {
|
|
|
546
583
|
return this.viewRowCount;
|
|
547
584
|
}
|
|
548
585
|
dispose() {
|
|
549
|
-
this.
|
|
550
|
-
|
|
551
|
-
|
|
586
|
+
if (this.failure) return;
|
|
587
|
+
try {
|
|
588
|
+
this.host.post({ type: "dispose" });
|
|
589
|
+
} finally {
|
|
590
|
+
this.fail(new Error("Table store disposed"));
|
|
591
|
+
}
|
|
552
592
|
}
|
|
553
593
|
};
|
|
554
594
|
|
|
@@ -602,7 +642,22 @@ var EditJournal = class {
|
|
|
602
642
|
}
|
|
603
643
|
/** Net outstanding edits (what a Save must persist). */
|
|
604
644
|
entries() {
|
|
605
|
-
return [...this.latest.values()];
|
|
645
|
+
return [...this.latest.values()].map((entry) => ({ ...entry }));
|
|
646
|
+
}
|
|
647
|
+
/** Advance the save baseline without losing edits (or undo) made during I/O. */
|
|
648
|
+
acknowledge(saved) {
|
|
649
|
+
const remaining = new Map(
|
|
650
|
+
this.entries().map((entry) => [`${entry.rowId}:${entry.col}`, entry])
|
|
651
|
+
);
|
|
652
|
+
for (const entry of saved) {
|
|
653
|
+
const key = `${entry.rowId}:${entry.col}`;
|
|
654
|
+
const current = remaining.get(key);
|
|
655
|
+
const next = current ? current.next : entry.prev;
|
|
656
|
+
if (next === entry.next) remaining.delete(key);
|
|
657
|
+
else remaining.set(key, { ...entry, prev: entry.next, next });
|
|
658
|
+
}
|
|
659
|
+
this.clear();
|
|
660
|
+
this.commit([...remaining.values()]);
|
|
606
661
|
}
|
|
607
662
|
clear() {
|
|
608
663
|
this.latest.clear();
|
|
@@ -624,21 +679,38 @@ var EditJournal = class {
|
|
|
624
679
|
}
|
|
625
680
|
};
|
|
626
681
|
var journalCache = /* @__PURE__ */ new Map();
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
682
|
+
var scopedCaches = /* @__PURE__ */ new WeakMap();
|
|
683
|
+
function cacheFor(scope) {
|
|
684
|
+
if (!scope) return journalCache;
|
|
685
|
+
let cache = scopedCaches.get(scope.owner);
|
|
686
|
+
if (!cache) {
|
|
687
|
+
cache = /* @__PURE__ */ new Map();
|
|
688
|
+
scopedCaches.set(scope.owner, cache);
|
|
689
|
+
}
|
|
690
|
+
return cache;
|
|
691
|
+
}
|
|
692
|
+
function journalFor(path, revision, scope) {
|
|
693
|
+
const cache = cacheFor(scope);
|
|
694
|
+
const identity = JSON.stringify([path, scope?.region ?? ""]);
|
|
695
|
+
const key = `${revision} ${identity}`;
|
|
696
|
+
let journal = cache.get(key);
|
|
630
697
|
if (!journal) {
|
|
631
|
-
for (const existing of
|
|
632
|
-
if (existing.endsWith(` ${
|
|
698
|
+
for (const [existing, previous] of cache) {
|
|
699
|
+
if (existing.endsWith(` ${identity}`) && existing !== key) {
|
|
700
|
+
if (scope) journal = previous;
|
|
701
|
+
cache.delete(existing);
|
|
702
|
+
}
|
|
633
703
|
}
|
|
634
|
-
journal = new EditJournal();
|
|
635
|
-
|
|
704
|
+
journal ?? (journal = new EditJournal());
|
|
705
|
+
cache.set(key, journal);
|
|
636
706
|
}
|
|
637
707
|
return journal;
|
|
638
708
|
}
|
|
639
|
-
function discardJournal(path) {
|
|
640
|
-
|
|
641
|
-
|
|
709
|
+
function discardJournal(path, scope) {
|
|
710
|
+
const cache = cacheFor(scope);
|
|
711
|
+
const identity = JSON.stringify([path, scope?.region ?? ""]);
|
|
712
|
+
for (const existing of [...cache.keys()]) {
|
|
713
|
+
if (existing.endsWith(` ${identity}`)) cache.delete(existing);
|
|
642
714
|
}
|
|
643
715
|
}
|
|
644
716
|
|
|
@@ -783,13 +855,32 @@ function DataGrid({
|
|
|
783
855
|
const selectionCells = useRef(/* @__PURE__ */ new Map());
|
|
784
856
|
const viewRef = useRef(view);
|
|
785
857
|
viewRef.current = view;
|
|
786
|
-
const
|
|
858
|
+
const editCallbacksRef = useRef({ formulaSupport, onCellEdited });
|
|
859
|
+
editCallbacksRef.current = { formulaSupport, onCellEdited };
|
|
860
|
+
const restoreRef = useRef(Promise.resolve());
|
|
861
|
+
const writable = Boolean(journal && provider.applyEdits && !readOnlyReason);
|
|
862
|
+
const editable = writable && !saving;
|
|
787
863
|
const dirtyCount = journal?.dirtyCount ?? 0;
|
|
788
864
|
void dirtyTick;
|
|
789
865
|
void fetchTick;
|
|
790
866
|
useEffect(() => {
|
|
791
867
|
let cancelled = false;
|
|
792
|
-
|
|
868
|
+
const restore = async () => {
|
|
869
|
+
const edits = journal?.entries().filter(
|
|
870
|
+
({ rowId, col }) => !editCallbacksRef.current.formulaSupport?.getFormula(rowId, col)
|
|
871
|
+
).map(({ rowId, col, next }) => ({ rowId, col, value: next })) ?? [];
|
|
872
|
+
if (edits.length && provider.applyEdits) {
|
|
873
|
+
await provider.applyEdits(edits);
|
|
874
|
+
for (const edit of edits) {
|
|
875
|
+
if (cancelled) break;
|
|
876
|
+
const updates = await editCallbacksRef.current.onCellEdited?.(edit);
|
|
877
|
+
if (updates?.length) await provider.applyEdits(updates);
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
return provider.describe();
|
|
881
|
+
};
|
|
882
|
+
restoreRef.current = restore();
|
|
883
|
+
restoreRef.current.then(
|
|
793
884
|
(described) => {
|
|
794
885
|
if (!cancelled) setSchema(described);
|
|
795
886
|
},
|
|
@@ -798,9 +889,10 @@ function DataGrid({
|
|
|
798
889
|
return () => {
|
|
799
890
|
cancelled = true;
|
|
800
891
|
};
|
|
801
|
-
}, [provider]);
|
|
892
|
+
}, [provider, journal]);
|
|
802
893
|
const applyView = useCallback(
|
|
803
894
|
async (next) => {
|
|
895
|
+
await restoreRef.current;
|
|
804
896
|
const result = await provider.setView(next);
|
|
805
897
|
cache.current = { version: cache.current.version + 1, pages: /* @__PURE__ */ new Map() };
|
|
806
898
|
inFlight.current.clear();
|
|
@@ -814,7 +906,7 @@ function DataGrid({
|
|
|
814
906
|
[provider]
|
|
815
907
|
);
|
|
816
908
|
useEffect(() => {
|
|
817
|
-
void applyView(view);
|
|
909
|
+
void applyView(view).catch(() => void 0);
|
|
818
910
|
}, [applyView, JSON.stringify(serializeTableViewState2(view))]);
|
|
819
911
|
const virtualizer = useVirtualizer({
|
|
820
912
|
count: viewRowCount,
|
|
@@ -909,14 +1001,15 @@ function DataGrid({
|
|
|
909
1001
|
(pos, seed) => {
|
|
910
1002
|
if (!editable || !schema) return;
|
|
911
1003
|
const current = cellAt(pos.row);
|
|
912
|
-
if (current
|
|
1004
|
+
if (!current) return;
|
|
1005
|
+
if (isCellLocked?.(current.rowId, pos.col)) {
|
|
913
1006
|
setAnnounce(lockedReason ?? "This cell is locked");
|
|
914
1007
|
return;
|
|
915
1008
|
}
|
|
916
|
-
const formula =
|
|
1009
|
+
const formula = formulaSupport?.getFormula(current.rowId, pos.col);
|
|
917
1010
|
setEditing({
|
|
918
1011
|
...pos,
|
|
919
|
-
draft: seed ?? (formula !== void 0 ? `=${formula}` : cellDisplay(current
|
|
1012
|
+
draft: seed ?? (formula !== void 0 ? `=${formula}` : cellDisplay(current.cells[pos.col] ?? null))
|
|
920
1013
|
});
|
|
921
1014
|
},
|
|
922
1015
|
[cellAt, editable, schema, isCellLocked, lockedReason, formulaSupport]
|
|
@@ -941,6 +1034,7 @@ function DataGrid({
|
|
|
941
1034
|
setFetchTick((t) => t + 1);
|
|
942
1035
|
}, []);
|
|
943
1036
|
const commitEdit = useCallback(async () => {
|
|
1037
|
+
if (!editable) return false;
|
|
944
1038
|
if (!editing || !schema || !journal || !provider.applyEdits) return true;
|
|
945
1039
|
if (formulaSupport && editing.draft.trim().startsWith("=")) {
|
|
946
1040
|
const located2 = cellAt(editing.row);
|
|
@@ -999,10 +1093,20 @@ function DataGrid({
|
|
|
999
1093
|
}
|
|
1000
1094
|
setDirtyTick((t) => t + 1);
|
|
1001
1095
|
return true;
|
|
1002
|
-
}, [
|
|
1096
|
+
}, [
|
|
1097
|
+
applyCacheUpdates,
|
|
1098
|
+
cellAt,
|
|
1099
|
+
editable,
|
|
1100
|
+
editing,
|
|
1101
|
+
formulaSupport,
|
|
1102
|
+
journal,
|
|
1103
|
+
onCellEdited,
|
|
1104
|
+
provider,
|
|
1105
|
+
schema
|
|
1106
|
+
]);
|
|
1003
1107
|
const handlePaste = useCallback(
|
|
1004
1108
|
(event) => {
|
|
1005
|
-
if (!selection || !schema || !journal || !provider.applyEdits || editing) return;
|
|
1109
|
+
if (!editable || !selection || !schema || !journal || !provider.applyEdits || editing) return;
|
|
1006
1110
|
const target = event.target;
|
|
1007
1111
|
if (target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement) return;
|
|
1008
1112
|
const text = event.clipboardData.getData("text/plain");
|
|
@@ -1073,6 +1177,7 @@ function DataGrid({
|
|
|
1073
1177
|
[
|
|
1074
1178
|
applyCacheUpdates,
|
|
1075
1179
|
cellAt,
|
|
1180
|
+
editable,
|
|
1076
1181
|
editing,
|
|
1077
1182
|
isCellLocked,
|
|
1078
1183
|
journal,
|
|
@@ -1085,16 +1190,23 @@ function DataGrid({
|
|
|
1085
1190
|
);
|
|
1086
1191
|
const runJournal = useCallback(
|
|
1087
1192
|
async (direction) => {
|
|
1088
|
-
if (!journal || !provider.applyEdits) return;
|
|
1193
|
+
if (!editable || !journal || !provider.applyEdits) return;
|
|
1089
1194
|
const edits = direction === "undo" ? journal.undo() : journal.redo();
|
|
1090
1195
|
if (edits.length === 0) return;
|
|
1091
1196
|
const result = await provider.applyEdits(edits);
|
|
1092
1197
|
if (result.staleView) setStaleView(true);
|
|
1198
|
+
for (const edit of edits) {
|
|
1199
|
+
const dependents = await onCellEdited?.(edit);
|
|
1200
|
+
if (dependents?.length) {
|
|
1201
|
+
const applied = await provider.applyEdits(dependents);
|
|
1202
|
+
if (applied.staleView) setStaleView(true);
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1093
1205
|
cache.current.pages.clear();
|
|
1094
1206
|
setDirtyTick((t) => t + 1);
|
|
1095
1207
|
setFetchTick((t) => t + 1);
|
|
1096
1208
|
},
|
|
1097
|
-
[journal, provider]
|
|
1209
|
+
[editable, journal, provider, onCellEdited]
|
|
1098
1210
|
);
|
|
1099
1211
|
const cycleSort = useCallback(
|
|
1100
1212
|
(columnName, additive) => {
|
|
@@ -1728,7 +1840,7 @@ function DataGrid({
|
|
|
1728
1840
|
}
|
|
1729
1841
|
),
|
|
1730
1842
|
readOnlyReason && /* @__PURE__ */ jsx("span", { className: "squisq-grid-readonly", children: readOnlyReason }),
|
|
1731
|
-
|
|
1843
|
+
writable && dirtyCount + extraDirtyCount > 0 && /* @__PURE__ */ jsxs("span", { className: "squisq-grid-dirtybar", children: [
|
|
1732
1844
|
(dirtyCount + extraDirtyCount).toLocaleString(),
|
|
1733
1845
|
" unsaved edit",
|
|
1734
1846
|
dirtyCount + extraDirtyCount === 1 ? "" : "s",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bendyline/squisq-grid-react",
|
|
3
|
-
"version": "2.11.
|
|
3
|
+
"version": "2.11.14",
|
|
4
4
|
"description": "Virtualized data grid for squisq data sidecars: columnar worker store, TableQueryProvider, and a TanStack-Virtual React renderer",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Bendyline",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"typecheck": "tsc --noEmit"
|
|
54
54
|
},
|
|
55
55
|
"dependencies": {
|
|
56
|
-
"@bendyline/squisq": "2.11.
|
|
56
|
+
"@bendyline/squisq": "2.11.13",
|
|
57
57
|
"@tanstack/react-virtual": "3.14.10"
|
|
58
58
|
},
|
|
59
59
|
"peerDependencies": {
|