@rowsncolumns/y-spreadsheet 15.0.2 → 15.1.0
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/cjs/helpers.d.ts +5 -5
- package/dist/cjs/helpers.d.ts.map +1 -1
- package/dist/cjs/helpers.js +21 -17
- package/dist/cjs/types.d.ts +36 -0
- package/dist/cjs/types.d.ts.map +1 -1
- package/dist/cjs/use-yspreadsheet-v2.d.ts +21 -3
- package/dist/cjs/use-yspreadsheet-v2.d.ts.map +1 -1
- package/dist/cjs/use-yspreadsheet-v2.js +125 -63
- package/dist/cjs/use-yspreadsheet.d.ts +6 -4
- package/dist/cjs/use-yspreadsheet.d.ts.map +1 -1
- package/dist/cjs/use-yspreadsheet.js +17 -15
- package/dist/esm/helpers.d.ts +5 -5
- package/dist/esm/helpers.d.ts.map +1 -1
- package/dist/esm/helpers.js +17 -17
- package/dist/esm/types.d.ts +36 -0
- package/dist/esm/types.d.ts.map +1 -1
- package/dist/esm/use-yspreadsheet-v2.d.ts +21 -3
- package/dist/esm/use-yspreadsheet-v2.d.ts.map +1 -1
- package/dist/esm/use-yspreadsheet-v2.js +84 -38
- package/dist/esm/use-yspreadsheet.d.ts +6 -4
- package/dist/esm/use-yspreadsheet.d.ts.map +1 -1
- package/dist/esm/use-yspreadsheet.js +13 -13
- package/package.json +1 -2
package/dist/esm/helpers.js
CHANGED
|
@@ -13,7 +13,7 @@ import unset from "lodash/unset";
|
|
|
13
13
|
* @param source
|
|
14
14
|
* @param sheetDataVersion - Version of sheet data format ("v2" or "v3"). Falls back to docMeta if undefined.
|
|
15
15
|
*/
|
|
16
|
-
export const applyPatchesToYDoc = (doc, userId, patches, source, sheetDataVersion) => {
|
|
16
|
+
export const applyPatchesToYDoc = (doc, userId, patches, source, sheetDataVersion, yjs = Y) => {
|
|
17
17
|
const ySheetDataV2 = doc.getMap("sheetDataV2");
|
|
18
18
|
const ySheetDataV3 = doc.getMap("sheetDataV3");
|
|
19
19
|
const yDocMeta = doc.getMap("docMeta");
|
|
@@ -42,7 +42,7 @@ export const applyPatchesToYDoc = (doc, userId, patches, source, sheetDataVersio
|
|
|
42
42
|
addSheetDataPatchesV3(ySheetDataV3, patches);
|
|
43
43
|
}
|
|
44
44
|
else {
|
|
45
|
-
addSheetDataPatches(ySheetDataV2, patches);
|
|
45
|
+
addSheetDataPatches(ySheetDataV2, patches, yjs);
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
48
|
if (patch.sheets) {
|
|
@@ -114,7 +114,7 @@ export const applyPatchesToYDoc = (doc, userId, patches, source, sheetDataVersio
|
|
|
114
114
|
* 1. Deduplicating patches targeting the same cell (later patches win)
|
|
115
115
|
* 2. Grouping updates by sheet/row for efficient batch application
|
|
116
116
|
*/
|
|
117
|
-
export const addSheetDataPatches = (ySheetData, patches) => {
|
|
117
|
+
export const addSheetDataPatches = (ySheetData, patches, yjs = Y) => {
|
|
118
118
|
// Optimization: consecutive cell-level patches are consolidated (later
|
|
119
119
|
// patches override earlier ones for the same cell) and applied together,
|
|
120
120
|
// grouped by sheet and row so bulk operations like paste (10k+ rows) cost
|
|
@@ -138,9 +138,9 @@ export const addSheetDataPatches = (ySheetData, patches) => {
|
|
|
138
138
|
rowUpdates.get(rowIdx).set(colIdx, value);
|
|
139
139
|
}
|
|
140
140
|
for (const [sheetIdStr, rowUpdates] of sheetRowUpdates) {
|
|
141
|
-
const ysheet = getSheetFromYSheetData(ySheetData, sheetIdStr);
|
|
141
|
+
const ysheet = getSheetFromYSheetData(ySheetData, sheetIdStr, yjs);
|
|
142
142
|
for (const [rowIdx, colUpdates] of rowUpdates) {
|
|
143
|
-
const rowData = getRowDataFromSheet(ysheet, rowIdx);
|
|
143
|
+
const rowData = getRowDataFromSheet(ysheet, rowIdx, yjs);
|
|
144
144
|
for (const [colIdx, value] of colUpdates) {
|
|
145
145
|
setCellDataInRowData(rowData, colIdx, value);
|
|
146
146
|
}
|
|
@@ -169,12 +169,12 @@ export const addSheetDataPatches = (ySheetData, patches) => {
|
|
|
169
169
|
return;
|
|
170
170
|
}
|
|
171
171
|
if (op === "replace" || op === "add") {
|
|
172
|
-
const ysheet = getSheetFromYSheetData(ySheetData, sheetId);
|
|
172
|
+
const ysheet = getSheetFromYSheetData(ySheetData, sheetId, yjs);
|
|
173
173
|
// Cell-level patches (path.length === 4) are already handled above via consolidation
|
|
174
174
|
// [sheetId, rowIndex, 'values'] - Replace values array directly
|
|
175
175
|
if (path.length === 3 && path[2] === "values") {
|
|
176
176
|
const [, rowIndex] = path;
|
|
177
|
-
const rowData = getRowDataFromSheet(ysheet, rowIndex);
|
|
177
|
+
const rowData = getRowDataFromSheet(ysheet, rowIndex, yjs);
|
|
178
178
|
// Clear existing row data
|
|
179
179
|
rowData.delete(0, rowData.length);
|
|
180
180
|
if (Array.isArray(value)) {
|
|
@@ -192,7 +192,7 @@ export const addSheetDataPatches = (ySheetData, patches) => {
|
|
|
192
192
|
const [, rowIndex] = path;
|
|
193
193
|
if (value?.values) {
|
|
194
194
|
// Replace entire row with new values
|
|
195
|
-
const rowData = getRowDataFromSheet(ysheet, rowIndex);
|
|
195
|
+
const rowData = getRowDataFromSheet(ysheet, rowIndex, yjs);
|
|
196
196
|
// Clear existing row data
|
|
197
197
|
rowData.delete(0, rowData.length);
|
|
198
198
|
// Add new cell values
|
|
@@ -218,7 +218,7 @@ export const addSheetDataPatches = (ySheetData, patches) => {
|
|
|
218
218
|
.sort(([a], [b]) => Number(a) - Number(b))
|
|
219
219
|
.forEach(([rowIndex, rowData]) => {
|
|
220
220
|
if (rowData?.values) {
|
|
221
|
-
const rowDataArray = getRowDataFromSheet(ysheet, Number(rowIndex));
|
|
221
|
+
const rowDataArray = getRowDataFromSheet(ysheet, Number(rowIndex), yjs);
|
|
222
222
|
Object.entries(rowData.values)
|
|
223
223
|
.sort(([a], [b]) => Number(a) - Number(b))
|
|
224
224
|
.forEach(([columnIndex, cellData]) => {
|
|
@@ -234,7 +234,7 @@ export const addSheetDataPatches = (ySheetData, patches) => {
|
|
|
234
234
|
// [sheetId, rowIndex] - Remove entire row
|
|
235
235
|
if (path.length === 2) {
|
|
236
236
|
const [, rowIndex] = path;
|
|
237
|
-
const ysheet = getSheetFromYSheetData(ySheetData, sheetId);
|
|
237
|
+
const ysheet = getSheetFromYSheetData(ySheetData, sheetId, yjs);
|
|
238
238
|
removeRowData(ysheet, rowIndex);
|
|
239
239
|
}
|
|
240
240
|
// [sheetId] - Remove entire sheet
|
|
@@ -268,11 +268,11 @@ export const addSheetDataPatches = (ySheetData, patches) => {
|
|
|
268
268
|
}
|
|
269
269
|
flushPendingCellUpdates();
|
|
270
270
|
};
|
|
271
|
-
export const getSheetFromYSheetData = (ySheetData, sheetId) => {
|
|
271
|
+
export const getSheetFromYSheetData = (ySheetData, sheetId, yjs = Y) => {
|
|
272
272
|
const id = String(sheetId);
|
|
273
273
|
let sheet = ySheetData.get(id);
|
|
274
274
|
if (!sheet) {
|
|
275
|
-
sheet = new
|
|
275
|
+
sheet = new yjs.Array();
|
|
276
276
|
ySheetData.set(id, sheet);
|
|
277
277
|
}
|
|
278
278
|
return sheet;
|
|
@@ -281,7 +281,7 @@ export const deleteSheetFromYSheetData = (ySheetData, sheetId) => {
|
|
|
281
281
|
const id = String(sheetId);
|
|
282
282
|
ySheetData.delete(id);
|
|
283
283
|
};
|
|
284
|
-
export const getRowDataFromSheet = (ysheet, rowIndex) => {
|
|
284
|
+
export const getRowDataFromSheet = (ysheet, rowIndex, yjs = Y) => {
|
|
285
285
|
const index = Number(rowIndex);
|
|
286
286
|
if (index < 0) {
|
|
287
287
|
throw new Error("Row index cannot be negative");
|
|
@@ -292,8 +292,8 @@ export const getRowDataFromSheet = (ysheet, rowIndex) => {
|
|
|
292
292
|
ysheet.insert(ysheet.length, padding);
|
|
293
293
|
ysheet.insert(index, [null]); // Insert null first
|
|
294
294
|
// Create new row data
|
|
295
|
-
const newRowData = new
|
|
296
|
-
newRowData.set("values", new
|
|
295
|
+
const newRowData = new yjs.Map();
|
|
296
|
+
newRowData.set("values", new yjs.Array());
|
|
297
297
|
// Replace the null
|
|
298
298
|
ysheet.insert(index, [newRowData]);
|
|
299
299
|
ysheet.delete(index + 1, 1);
|
|
@@ -311,8 +311,8 @@ export const getRowDataFromSheet = (ysheet, rowIndex) => {
|
|
|
311
311
|
let rowData = ysheet.get(index);
|
|
312
312
|
if (!rowData || !rowData.get("values")) {
|
|
313
313
|
// Create new row data if it doesn't exist or is incomplete
|
|
314
|
-
const newRowData = new
|
|
315
|
-
newRowData.set("values", new
|
|
314
|
+
const newRowData = new yjs.Map();
|
|
315
|
+
newRowData.set("values", new yjs.Array());
|
|
316
316
|
// Replace the existing item
|
|
317
317
|
ysheet.insert(index, [newRowData]);
|
|
318
318
|
ysheet.delete(index + 1, 1);
|
package/dist/esm/types.d.ts
CHANGED
|
@@ -1,6 +1,42 @@
|
|
|
1
1
|
import type { Patch } from "immer";
|
|
2
2
|
import type { RecalcCellCoordinate } from "@rowsncolumns/spreadsheet-state";
|
|
3
3
|
import type { CellData } from "@rowsncolumns/spreadsheet";
|
|
4
|
+
import type * as Y from "yjs";
|
|
5
|
+
/**
|
|
6
|
+
* The Yjs members y-spreadsheet needs as runtime values: the shared-type
|
|
7
|
+
* constructors it instantiates and `instanceof`-checks, and the update
|
|
8
|
+
* functions `useYSpreadsheet` calls on the document. Defaults to the `yjs`
|
|
9
|
+
* (13) module; pass the namespace of the Yjs implementation that created
|
|
10
|
+
* `doc` (for example a yjs-13-compatible layer over `@y/y` 14) so the types
|
|
11
|
+
* this hook creates belong to the same implementation as the document and
|
|
12
|
+
* its `instanceof` checks see that implementation's classes. The members are
|
|
13
|
+
* typed against yjs 13, so another implementation is passed with a cast.
|
|
14
|
+
*/
|
|
15
|
+
export type YjsRuntime = {
|
|
16
|
+
Map: typeof Y.Map;
|
|
17
|
+
Array: typeof Y.Array;
|
|
18
|
+
YArrayEvent: typeof Y.YArrayEvent;
|
|
19
|
+
Doc: typeof Y.Doc;
|
|
20
|
+
applyUpdate: typeof Y.applyUpdate;
|
|
21
|
+
diffUpdate: typeof Y.diffUpdate;
|
|
22
|
+
encodeStateAsUpdate: typeof Y.encodeStateAsUpdate;
|
|
23
|
+
encodeStateVector: typeof Y.encodeStateVector;
|
|
24
|
+
};
|
|
25
|
+
/** The awareness surface the hooks use (y-protocols and @y/protocols both satisfy it). */
|
|
26
|
+
export type AwarenessLike = {
|
|
27
|
+
getStates(): Map<number, Record<string, any>>;
|
|
28
|
+
on(event: "change", callback: (...args: any[]) => void): void;
|
|
29
|
+
off(event: "change", callback: (...args: any[]) => void): void;
|
|
30
|
+
setLocalState(state: Record<string, any> | null): void;
|
|
31
|
+
setLocalStateField(field: string, value: any): void;
|
|
32
|
+
};
|
|
33
|
+
/** A collaboration provider as the hooks see it: an optional awareness plus sync/status events. */
|
|
34
|
+
export type CollabProviderLike = {
|
|
35
|
+
awareness?: AwarenessLike | null;
|
|
36
|
+
synced?: boolean;
|
|
37
|
+
on?(event: string, callback: (...args: any[]) => void): void;
|
|
38
|
+
off?(event: string, callback: (...args: any[]) => void): void;
|
|
39
|
+
};
|
|
4
40
|
export type PatchDocument<T = Patch> = {
|
|
5
41
|
userId: string | number;
|
|
6
42
|
patches: T[];
|
package/dist/esm/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,OAAO,CAAC;AACnC,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,iCAAiC,CAAC;AAC5E,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,OAAO,CAAC;AACnC,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,iCAAiC,CAAC;AAC5E,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AAC1D,OAAO,KAAK,KAAK,CAAC,MAAM,KAAK,CAAC;AAE9B;;;;;;;;;GASG;AACH,MAAM,MAAM,UAAU,GAAG;IACvB,GAAG,EAAE,OAAO,CAAC,CAAC,GAAG,CAAC;IAClB,KAAK,EAAE,OAAO,CAAC,CAAC,KAAK,CAAC;IACtB,WAAW,EAAE,OAAO,CAAC,CAAC,WAAW,CAAC;IAClC,GAAG,EAAE,OAAO,CAAC,CAAC,GAAG,CAAC;IAClB,WAAW,EAAE,OAAO,CAAC,CAAC,WAAW,CAAC;IAClC,UAAU,EAAE,OAAO,CAAC,CAAC,UAAU,CAAC;IAChC,mBAAmB,EAAE,OAAO,CAAC,CAAC,mBAAmB,CAAC;IAClD,iBAAiB,EAAE,OAAO,CAAC,CAAC,iBAAiB,CAAC;CAC/C,CAAC;AAEF,0FAA0F;AAC1F,MAAM,MAAM,aAAa,GAAG;IAC1B,SAAS,IAAI,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC;IAC9C,EAAE,CAAC,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,GAAG,IAAI,CAAC;IAC9D,GAAG,CAAC,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,GAAG,IAAI,CAAC;IAC/D,aAAa,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,GAAG,IAAI,CAAC;IACvD,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,GAAG,IAAI,CAAC;CACrD,CAAC;AAEF,mGAAmG;AACnG,MAAM,MAAM,kBAAkB,GAAG;IAC/B,SAAS,CAAC,EAAE,aAAa,GAAG,IAAI,CAAC;IACjC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,EAAE,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,GAAG,IAAI,CAAC;IAC7D,GAAG,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,GAAG,IAAI,CAAC;CAC/D,CAAC;AAEF,MAAM,MAAM,aAAa,CAAC,CAAC,GAAG,KAAK,IAAI;IACrC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,OAAO,EAAE,CAAC,EAAE,CAAC;CACd,CAAC;AACF,MAAM,MAAM,gBAAgB,GAAG,aAAa,CAAC,eAAe,CAAC,CAAC;AAC9D,MAAM,MAAM,eAAe,GAAG;IAC5B,GAAG,EAAE,MAAM;IACX,QAAQ,EAAE,oBAAoB;IAC9B;;;;;OAKG;IACH,MAAM,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS;CACtC,CAAC;AAEF;;;;;;;;;;;GAWG;AAEH;;;;;;GAMG;AACH,MAAM,MAAM,UAAU,CAAC,CAAC,SAAS,QAAQ,GAAG,QAAQ,IAAI;IACtD,KAAK,EAAE,CAAC,CAAC;IACT,eAAe;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,0BAA0B;IAC1B,CAAC,EAAE,MAAM,CAAC;IACV,6BAA6B;IAC7B,CAAC,EAAE,MAAM,CAAC;CACX,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC;AAE/B;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC"}
|
|
@@ -4,8 +4,8 @@ import type { CellInterface, SelectionArea } from "@rowsncolumns/grid";
|
|
|
4
4
|
import { PivotTable, type CellData, type Collaborator, type ConditionalFormatRule, type DataValidationRuleRecord, type EmbeddedChart, type EmbeddedObject, type NamedRange, type ProtectedRange, type Sheet, type TableView } from "@rowsncolumns/spreadsheet";
|
|
5
5
|
import { type CalculateNowOptions, type CalculationOperation, type CellXfs, type HistoryType, type SheetData, type SharedStrings, type SpreadsheetPatch } from "@rowsncolumns/spreadsheet-state";
|
|
6
6
|
import type { Patch } from "immer";
|
|
7
|
-
import type { WebsocketProvider } from "y-websocket";
|
|
8
7
|
import * as Y from "yjs";
|
|
8
|
+
import type { CollabProviderLike, YjsRuntime } from "./types";
|
|
9
9
|
export type UseYSpreadsheetPropsV2<T extends CellData, S extends Sheet, C extends EmbeddedChart, O extends EmbeddedObject> = {
|
|
10
10
|
/**
|
|
11
11
|
* Scopes awareness updates to a single document.
|
|
@@ -23,6 +23,18 @@ export type UseYSpreadsheetPropsV2<T extends CellData, S extends Sheet, C extend
|
|
|
23
23
|
* convention to remain wire-compatible.
|
|
24
24
|
*/
|
|
25
25
|
awarenessScopeId?: string;
|
|
26
|
+
/**
|
|
27
|
+
* Whether this client may persist to the shared document (the host's
|
|
28
|
+
* edit permission; `false` for view-only sessions). Published on awareness
|
|
29
|
+
* as `canWrite` and consulted by the leader election: the leader is the
|
|
30
|
+
* peer that evaluates backend/agent recalc backlogs and writes the computed
|
|
31
|
+
* values back, so a client whose writes the server refuses must never win —
|
|
32
|
+
* every value it computed would be dropped. Peers whose awareness state has
|
|
33
|
+
* no `canWrite` field (older clients, REST writers) count as writers. When
|
|
34
|
+
* no writer is present there is no leader: nothing could persist the values
|
|
35
|
+
* anyway. Default: `true`.
|
|
36
|
+
*/
|
|
37
|
+
canWrite?: boolean;
|
|
26
38
|
userId: string | number;
|
|
27
39
|
title: string;
|
|
28
40
|
sheetId: number;
|
|
@@ -44,8 +56,14 @@ export type UseYSpreadsheetPropsV2<T extends CellData, S extends Sheet, C extend
|
|
|
44
56
|
* needed so agents touching A1:E10 don't appear to be sitting on A1.
|
|
45
57
|
*/
|
|
46
58
|
selections?: SelectionArea[];
|
|
47
|
-
provider?: HocuspocusProvider |
|
|
59
|
+
provider?: HocuspocusProvider | CollabProviderLike | any;
|
|
48
60
|
doc: Y.Doc;
|
|
61
|
+
/**
|
|
62
|
+
* The Yjs implementation `doc` belongs to (its shared-type constructors and
|
|
63
|
+
* event classes). Defaults to `yjs`; pass the namespace that created `doc`
|
|
64
|
+
* when it is not the `yjs` module (see `YjsRuntime`).
|
|
65
|
+
*/
|
|
66
|
+
yjs?: YjsRuntime;
|
|
49
67
|
calculateNow(options?: CalculateNowOptions): Promise<void>;
|
|
50
68
|
initialSheets: S[];
|
|
51
69
|
/**
|
|
@@ -142,7 +160,7 @@ export type UseYSpreadsheetPropsV2<T extends CellData, S extends Sheet, C extend
|
|
|
142
160
|
};
|
|
143
161
|
export type YRowData = Y.Map<Y.Array<CellData | null>>;
|
|
144
162
|
export type YSheetData = Y.Array<YRowData | null>;
|
|
145
|
-
export declare const useYSpreadsheetV2: <T extends CellData, S extends Sheet, C extends EmbeddedChart, O extends EmbeddedObject>({ onChangeSheetData, onChangeSheets, onChangeTables, onChangeCharts, onChangeEmbeds, onChangeConditionalFormats, onChangeDataValidations, onChangeCitations, onAfterBroadcastPatch, recalcCellsMaxEntries, onChangeNamedRanges, onChangeProtectedRanges, onChangePivotTables, enqueueCalculation, enqueueGraphOperation, reevaluateDerivationsForCells, onChangeCellXfs, onChangeSharedStrings, calculateNow, onChangeActiveSheet, clientOnlyFunctionPattern, initialSheets, awarenessScopeId, userId, title, sheetId, activeCell, kind, agentInitiator, selections, doc, provider, useV3, sheetDataVersion, onMigrationComplete, deleteV2AfterMigration, skipInitialSync, supportLegacySharedStringsArray, }: UseYSpreadsheetPropsV2<T, S, C, O>) => {
|
|
163
|
+
export declare const useYSpreadsheetV2: <T extends CellData, S extends Sheet, C extends EmbeddedChart, O extends EmbeddedObject>({ onChangeSheetData, onChangeSheets, onChangeTables, onChangeCharts, onChangeEmbeds, onChangeConditionalFormats, onChangeDataValidations, onChangeCitations, onAfterBroadcastPatch, recalcCellsMaxEntries, onChangeNamedRanges, onChangeProtectedRanges, onChangePivotTables, enqueueCalculation, enqueueGraphOperation, reevaluateDerivationsForCells, onChangeCellXfs, onChangeSharedStrings, calculateNow, onChangeActiveSheet, clientOnlyFunctionPattern, initialSheets, awarenessScopeId, canWrite, userId, title, sheetId, activeCell, kind, agentInitiator, selections, doc, provider, yjs, useV3, sheetDataVersion, onMigrationComplete, deleteV2AfterMigration, skipInitialSync, supportLegacySharedStringsArray, }: UseYSpreadsheetPropsV2<T, S, C, O>) => {
|
|
146
164
|
synced: boolean;
|
|
147
165
|
users: Collaborator[];
|
|
148
166
|
onBroadcastPatch: (patches: [patch: SpreadsheetPatch<Patch>, type?: HistoryType | undefined | null][]) => void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-yspreadsheet-v2.d.ts","sourceRoot":"","sources":["../../use-yspreadsheet-v2.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC/D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,4BAA4B,CAAC;AAC3D,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACvE,OAAO,EAEL,UAAU,EACV,KAAK,QAAQ,EAEb,KAAK,YAAY,EACjB,KAAK,qBAAqB,EAC1B,KAAK,wBAAwB,EAC7B,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,UAAU,EACf,KAAK,cAAc,EACnB,KAAK,KAAK,EACV,KAAK,SAAS,EACf,MAAM,2BAA2B,CAAC;AACnC,OAAO,EACL,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,KAAK,OAAO,EACZ,KAAK,WAAW,EAChB,KAAK,SAAS,EACd,KAAK,aAAa,EAClB,KAAK,gBAAgB,EACtB,MAAM,iCAAiC,CAAC;AASzC,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,OAAO,CAAC;AAInC,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"use-yspreadsheet-v2.d.ts","sourceRoot":"","sources":["../../use-yspreadsheet-v2.ts"],"names":[],"mappings":"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"}
|
|
@@ -8,9 +8,9 @@ import { addSheetDataPatches, addSheetDataPatchesV3, applyMapPatches, createCell
|
|
|
8
8
|
/**
|
|
9
9
|
* Convert Y.js events to SheetDataEvent format for incremental processing
|
|
10
10
|
*/
|
|
11
|
-
const convertToSheetDataEvents = (events) => {
|
|
11
|
+
const convertToSheetDataEvents = (events, yjs) => {
|
|
12
12
|
return events.map((event) => {
|
|
13
|
-
const isYArrayEvent = event instanceof
|
|
13
|
+
const isYArrayEvent = event instanceof yjs.YArrayEvent;
|
|
14
14
|
return {
|
|
15
15
|
path: event.path,
|
|
16
16
|
delta: isYArrayEvent
|
|
@@ -52,7 +52,7 @@ const getUserEnteredValueFromYDocV3 = (ySheetDataV3, sheetId, rowIndex, columnIn
|
|
|
52
52
|
import { createDeepObservable, createObservable, createSheetDataObservable, getPatchDeltasFromEvent, } from "./observables";
|
|
53
53
|
// Add a single null entry to the YSheetData
|
|
54
54
|
const origin = "local";
|
|
55
|
-
export const useYSpreadsheetV2 = ({ onChangeSheetData, onChangeSheets, onChangeTables, onChangeCharts, onChangeEmbeds, onChangeConditionalFormats, onChangeDataValidations, onChangeCitations, onAfterBroadcastPatch, recalcCellsMaxEntries, onChangeNamedRanges, onChangeProtectedRanges, onChangePivotTables, enqueueCalculation, enqueueGraphOperation, reevaluateDerivationsForCells, onChangeCellXfs, onChangeSharedStrings, calculateNow, onChangeActiveSheet, clientOnlyFunctionPattern, initialSheets, awarenessScopeId, userId, title, sheetId, activeCell, kind, agentInitiator, selections, doc, provider, useV3 = false, sheetDataVersion = "v2", onMigrationComplete, deleteV2AfterMigration = false, skipInitialSync = false, supportLegacySharedStringsArray = false, }) => {
|
|
55
|
+
export const useYSpreadsheetV2 = ({ onChangeSheetData, onChangeSheets, onChangeTables, onChangeCharts, onChangeEmbeds, onChangeConditionalFormats, onChangeDataValidations, onChangeCitations, onAfterBroadcastPatch, recalcCellsMaxEntries, onChangeNamedRanges, onChangeProtectedRanges, onChangePivotTables, enqueueCalculation, enqueueGraphOperation, reevaluateDerivationsForCells, onChangeCellXfs, onChangeSharedStrings, calculateNow, onChangeActiveSheet, clientOnlyFunctionPattern, initialSheets, awarenessScopeId, canWrite = true, userId, title, sheetId, activeCell, kind, agentInitiator, selections, doc, provider, yjs = Y, useV3 = false, sheetDataVersion = "v2", onMigrationComplete, deleteV2AfterMigration = false, skipInitialSync = false, supportLegacySharedStringsArray = false, }) => {
|
|
56
56
|
const [synced, setSynced] = useState(false);
|
|
57
57
|
const [users, setUsers] = useState([]);
|
|
58
58
|
const [isLeader, setIsLeader] = useState(false);
|
|
@@ -68,19 +68,24 @@ export const useYSpreadsheetV2 = ({ onChangeSheetData, onChangeSheets, onChangeT
|
|
|
68
68
|
const docStates = resolvedAwarenessScopeId
|
|
69
69
|
? entries.filter(([_, state]) => state?.docGuid === resolvedAwarenessScopeId)
|
|
70
70
|
: entries;
|
|
71
|
-
|
|
71
|
+
// Only peers that can persist may lead (see `canWrite`). A view-only
|
|
72
|
+
// client's recalc write-backs are refused by the server, so electing it
|
|
73
|
+
// silently drops every computed value. A missing field is a writer.
|
|
74
|
+
const writers = docStates
|
|
75
|
+
.filter(([_, state]) => state?.canWrite !== false)
|
|
76
|
+
.map(([clientId]) => clientId);
|
|
77
|
+
if (!canWrite || !writers.length) {
|
|
72
78
|
setIsLeader(false);
|
|
73
79
|
return;
|
|
74
80
|
}
|
|
75
|
-
const
|
|
76
|
-
const minId = Math.min(...states);
|
|
81
|
+
const minId = Math.min(...writers);
|
|
77
82
|
// Always include self in the elec
|
|
78
83
|
setIsLeader(doc.clientID === minId);
|
|
79
84
|
};
|
|
80
85
|
awareness.on("change", updateLeader);
|
|
81
86
|
updateLeader(); // Initial check
|
|
82
87
|
return () => awareness.off("change", updateLeader);
|
|
83
|
-
}, [awareness, doc, resolvedAwarenessScopeId]);
|
|
88
|
+
}, [awareness, doc, resolvedAwarenessScopeId, canWrite]);
|
|
84
89
|
useIsomorphicLayoutEffect(() => {
|
|
85
90
|
isLeaderRef.current = isLeader;
|
|
86
91
|
}, [isLeader]);
|
|
@@ -219,6 +224,12 @@ export const useYSpreadsheetV2 = ({ onChangeSheetData, onChangeSheets, onChangeT
|
|
|
219
224
|
.flatMap((entry) => entry.patches)
|
|
220
225
|
.filter(([, , source]) => source === "backend" || source === "agent");
|
|
221
226
|
}, [yRecalcCells]);
|
|
227
|
+
/**
|
|
228
|
+
* Set once the initial sync has built the dependency graph and replayed the
|
|
229
|
+
* backlog, so a later leadership hand-over can replay it again.
|
|
230
|
+
*/
|
|
231
|
+
const graphReadyRef = useRef(false);
|
|
232
|
+
const initialSyncTimerRef = useRef(null);
|
|
222
233
|
// Trigger cell recalcs
|
|
223
234
|
const syncRecalcCells = useCallback((recalcCells, origin) => {
|
|
224
235
|
for (const [_, position, source] of recalcCells) {
|
|
@@ -389,6 +400,24 @@ export const useYSpreadsheetV2 = ({ onChangeSheetData, onChangeSheets, onChangeT
|
|
|
389
400
|
onMigrationComplete,
|
|
390
401
|
deleteV2AfterMigration,
|
|
391
402
|
]);
|
|
403
|
+
/**
|
|
404
|
+
* Replay the backend/agent recalc backlog into the local graph — and, on
|
|
405
|
+
* the leader, evaluate it. Only the leader clears the entries, and only once
|
|
406
|
+
* they sit in its calculation queue: a non-leader merely graph-enqueues
|
|
407
|
+
* (no evaluation), so if it deleted them the leader would never recompute
|
|
408
|
+
* the backlog at all.
|
|
409
|
+
*/
|
|
410
|
+
const drainBacklog = useCallback(() => {
|
|
411
|
+
const backlog = getBacklog();
|
|
412
|
+
if (!backlog.length)
|
|
413
|
+
return;
|
|
414
|
+
syncRecalcCells(backlog, "backend");
|
|
415
|
+
if (isLeaderRef.current) {
|
|
416
|
+
doc.transact(() => {
|
|
417
|
+
yRecalcCells.delete(0, yRecalcCells.length);
|
|
418
|
+
}, origin);
|
|
419
|
+
}
|
|
420
|
+
}, [getBacklog, syncRecalcCells, doc, yRecalcCells]);
|
|
392
421
|
// Synced
|
|
393
422
|
const onSynced = useCallback(() => {
|
|
394
423
|
if (!synced) {
|
|
@@ -398,35 +427,40 @@ export const useYSpreadsheetV2 = ({ onChangeSheetData, onChangeSheets, onChangeT
|
|
|
398
427
|
flushSync(() => {
|
|
399
428
|
setupInitialSync();
|
|
400
429
|
});
|
|
401
|
-
// Build dependency graph, then sync backlog cells
|
|
402
|
-
requestAnimationFrame
|
|
430
|
+
// Build dependency graph, then sync backlog cells. Deferred through a
|
|
431
|
+
// timer, not `requestAnimationFrame`: rAF is paused in background tabs,
|
|
432
|
+
// which left a backgrounded leader's backlog unevaluated indefinitely.
|
|
433
|
+
initialSyncTimerRef.current = setTimeout(async () => {
|
|
434
|
+
initialSyncTimerRef.current = null;
|
|
403
435
|
await calculateNow({
|
|
404
436
|
disableEvaluation: true,
|
|
405
437
|
shouldResetCellDependencyGraph: true,
|
|
406
438
|
});
|
|
407
439
|
// Sync backlog after graph is initialized
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
// Clear processed backlog entries from yRecalcCells
|
|
412
|
-
doc.transact(() => {
|
|
413
|
-
yRecalcCells.delete(0, yRecalcCells.length);
|
|
414
|
-
}, origin);
|
|
415
|
-
}
|
|
416
|
-
});
|
|
440
|
+
drainBacklog();
|
|
441
|
+
graphReadyRef.current = true;
|
|
442
|
+
}, 0);
|
|
417
443
|
}
|
|
418
444
|
setSynced(true);
|
|
419
445
|
}
|
|
420
|
-
}, [
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
]);
|
|
446
|
+
}, [calculateNow, setupInitialSync, synced, drainBacklog, skipInitialSync]);
|
|
447
|
+
// A client that becomes leader after its initial sync (the previous leader
|
|
448
|
+
// left, or the election settled late) takes over whatever backlog is still
|
|
449
|
+
// in the document: as a non-leader it only mirrored those cells into its
|
|
450
|
+
// graph, so it evaluates them now — and only now clears them.
|
|
451
|
+
useIsomorphicLayoutEffect(() => {
|
|
452
|
+
if (!isLeader || !synced || !graphReadyRef.current)
|
|
453
|
+
return;
|
|
454
|
+
drainBacklog();
|
|
455
|
+
}, [isLeader, synced, drainBacklog]);
|
|
456
|
+
useIsomorphicLayoutEffect(() => {
|
|
457
|
+
return () => {
|
|
458
|
+
if (initialSyncTimerRef.current !== null) {
|
|
459
|
+
clearTimeout(initialSyncTimerRef.current);
|
|
460
|
+
initialSyncTimerRef.current = null;
|
|
461
|
+
}
|
|
462
|
+
};
|
|
463
|
+
}, []);
|
|
430
464
|
// Sync state
|
|
431
465
|
if (provider?.synced && !synced) {
|
|
432
466
|
onSynced();
|
|
@@ -454,7 +488,7 @@ export const useYSpreadsheetV2 = ({ onChangeSheetData, onChangeSheets, onChangeT
|
|
|
454
488
|
const origin = events?.[0]?.transaction?.origin;
|
|
455
489
|
if (origin !== "local") {
|
|
456
490
|
// Convert Y.js events to SheetDataEvent format and apply incremental updates
|
|
457
|
-
const sheetDataEvents = convertToSheetDataEvents(events);
|
|
491
|
+
const sheetDataEvents = convertToSheetDataEvents(events, yjs);
|
|
458
492
|
onChangeSheetData((prevSheetData) => {
|
|
459
493
|
const result = processSheetDataEvents(sheetDataEvents, prevSheetData, ySheetDataV2);
|
|
460
494
|
// Fall back to full serialization only if incremental update fails
|
|
@@ -517,7 +551,7 @@ export const useYSpreadsheetV2 = ({ onChangeSheetData, onChangeSheets, onChangeT
|
|
|
517
551
|
const subs = [];
|
|
518
552
|
for (const [mappingKey, [ydoc, callback]] of documentMap.entries()) {
|
|
519
553
|
// Only observe Arrays (shared strings Map is handled separately if needed)
|
|
520
|
-
if (ydoc instanceof
|
|
554
|
+
if (ydoc instanceof yjs.Array) {
|
|
521
555
|
const subscription = createDeepObservable(ydoc).subscribe((events) => {
|
|
522
556
|
for (const event of events) {
|
|
523
557
|
if (event.transaction.origin !== "local") {
|
|
@@ -533,7 +567,7 @@ export const useYSpreadsheetV2 = ({ onChangeSheetData, onChangeSheets, onChangeT
|
|
|
533
567
|
});
|
|
534
568
|
subs.push(subscription);
|
|
535
569
|
}
|
|
536
|
-
else if (ydoc instanceof
|
|
570
|
+
else if (ydoc instanceof yjs.Map && mappingKey === "sharedStrings") {
|
|
537
571
|
// Handle shared strings Map observation
|
|
538
572
|
const callback_map = (event) => {
|
|
539
573
|
if (event.transaction.origin !== "local") {
|
|
@@ -557,20 +591,29 @@ export const useYSpreadsheetV2 = ({ onChangeSheetData, onChangeSheets, onChangeT
|
|
|
557
591
|
if (!synced) {
|
|
558
592
|
return;
|
|
559
593
|
}
|
|
594
|
+
const pending = new Set();
|
|
560
595
|
// Sync all cells that are updated in the backend
|
|
561
596
|
// // Observe recalc cells separat
|
|
562
597
|
const onRecalcCellsChange = (event) => {
|
|
563
598
|
const patches = getPatchDeltasFromEvent(event);
|
|
564
599
|
const origin = event.transaction.origin;
|
|
565
600
|
if (origin !== "local") {
|
|
566
|
-
requestAnimationFrame
|
|
601
|
+
// A timer, not `requestAnimationFrame`: rAF is paused in background
|
|
602
|
+
// tabs, so a backgrounded leader would stop evaluating remote patches.
|
|
603
|
+
const timer = setTimeout(() => {
|
|
604
|
+
pending.delete(timer);
|
|
567
605
|
syncRecalcCells(patches, origin);
|
|
568
|
-
});
|
|
606
|
+
}, 0);
|
|
607
|
+
pending.add(timer);
|
|
569
608
|
}
|
|
570
609
|
};
|
|
571
610
|
// Skip the first recalc cells change
|
|
572
611
|
const sub = createObservable(yRecalcCells).subscribe(onRecalcCellsChange);
|
|
573
|
-
return () =>
|
|
612
|
+
return () => {
|
|
613
|
+
sub.unsubscribe();
|
|
614
|
+
for (const timer of pending)
|
|
615
|
+
clearTimeout(timer);
|
|
616
|
+
};
|
|
574
617
|
}, [syncRecalcCells, recalcCellsMaxEntries, synced, yRecalcCells]);
|
|
575
618
|
// Broadcast patch
|
|
576
619
|
const onBroadcastPatch = useCallback((patches) => {
|
|
@@ -628,7 +671,7 @@ export const useYSpreadsheetV2 = ({ onChangeSheetData, onChangeSheets, onChangeT
|
|
|
628
671
|
addSheetDataPatchesV3(ySheetDataV3, persistableErrorCellPatches);
|
|
629
672
|
}
|
|
630
673
|
else {
|
|
631
|
-
addSheetDataPatches(ySheetDataV2, persistableErrorCellPatches);
|
|
674
|
+
addSheetDataPatches(ySheetDataV2, persistableErrorCellPatches, yjs);
|
|
632
675
|
}
|
|
633
676
|
}, origin);
|
|
634
677
|
}
|
|
@@ -650,7 +693,7 @@ export const useYSpreadsheetV2 = ({ onChangeSheetData, onChangeSheets, onChangeT
|
|
|
650
693
|
addSheetDataPatchesV3(ySheetDataV3, patches);
|
|
651
694
|
}
|
|
652
695
|
else {
|
|
653
|
-
addSheetDataPatches(ySheetDataV2, patches);
|
|
696
|
+
addSheetDataPatches(ySheetDataV2, patches, yjs);
|
|
654
697
|
}
|
|
655
698
|
}
|
|
656
699
|
if (patch.cellXfs) {
|
|
@@ -661,10 +704,10 @@ export const useYSpreadsheetV2 = ({ onChangeSheetData, onChangeSheets, onChangeT
|
|
|
661
704
|
if (patch?.[mappingKey]) {
|
|
662
705
|
const patches = patch[mappingKey]?.[patchKey];
|
|
663
706
|
// Handle shared strings Map (always use Map now)
|
|
664
|
-
if (mappingKey === "sharedStrings" && ydoc instanceof
|
|
707
|
+
if (mappingKey === "sharedStrings" && ydoc instanceof yjs.Map) {
|
|
665
708
|
applyMapPatches(ydoc, patches);
|
|
666
709
|
}
|
|
667
|
-
else if (ydoc instanceof
|
|
710
|
+
else if (ydoc instanceof yjs.Array) {
|
|
668
711
|
// Check if we are updating sheets
|
|
669
712
|
if (mappingKey === "sheets" && ydoc.length === 0) {
|
|
670
713
|
// Add initial sheets
|
|
@@ -694,6 +737,7 @@ export const useYSpreadsheetV2 = ({ onChangeSheetData, onChangeSheets, onChangeT
|
|
|
694
737
|
onAfterBroadcastPatch,
|
|
695
738
|
documentMap,
|
|
696
739
|
yRecalcCells,
|
|
740
|
+
yjs,
|
|
697
741
|
doc,
|
|
698
742
|
userId,
|
|
699
743
|
yDisableRecalc,
|
|
@@ -777,6 +821,7 @@ export const useYSpreadsheetV2 = ({ onChangeSheetData, onChangeSheets, onChangeT
|
|
|
777
821
|
awareness.setLocalStateField("userId", userId);
|
|
778
822
|
awareness.setLocalStateField("title", title);
|
|
779
823
|
awareness.setLocalStateField("sheetId", sheetId);
|
|
824
|
+
awareness.setLocalStateField("canWrite", canWrite);
|
|
780
825
|
if (resolvedAwarenessScopeId) {
|
|
781
826
|
awareness.setLocalStateField("docGuid", resolvedAwarenessScopeId);
|
|
782
827
|
}
|
|
@@ -798,6 +843,7 @@ export const useYSpreadsheetV2 = ({ onChangeSheetData, onChangeSheets, onChangeT
|
|
|
798
843
|
}, [
|
|
799
844
|
awareness,
|
|
800
845
|
resolvedAwarenessScopeId,
|
|
846
|
+
canWrite,
|
|
801
847
|
userId,
|
|
802
848
|
title,
|
|
803
849
|
sheetId,
|
|
@@ -3,15 +3,17 @@ import type { CellInterface } from "@rowsncolumns/grid";
|
|
|
3
3
|
import type { CellData, Collaborator, ConditionalFormatRule, DataValidationRuleRecord, EmbeddedChart, EmbeddedObject, NamedRange, ProtectedRange, Sheet, TableView } from "@rowsncolumns/spreadsheet";
|
|
4
4
|
import type { CalculateNowOptions, CalculationOperation, HistoryType, SheetData, SpreadsheetPatch } from "@rowsncolumns/spreadsheet-state";
|
|
5
5
|
import type { Patch } from "immer";
|
|
6
|
-
import
|
|
7
|
-
import type
|
|
6
|
+
import * as Y from "yjs";
|
|
7
|
+
import type { CollabProviderLike, YjsRuntime } from "./types";
|
|
8
8
|
export type useYSpreadsheetProps<T extends CellData, S extends Sheet, C extends EmbeddedChart, O extends EmbeddedObject> = {
|
|
9
9
|
userId: string | number;
|
|
10
10
|
title: string;
|
|
11
11
|
sheetId: number;
|
|
12
12
|
activeCell?: CellInterface;
|
|
13
|
-
provider?: HocuspocusProvider |
|
|
13
|
+
provider?: HocuspocusProvider | CollabProviderLike | any;
|
|
14
14
|
doc: Y.Doc;
|
|
15
|
+
/** The Yjs implementation `doc` belongs to; defaults to `yjs` (see `YjsRuntime`). */
|
|
16
|
+
yjs?: YjsRuntime;
|
|
15
17
|
calculateNow(options?: CalculateNowOptions): void;
|
|
16
18
|
onChangeSheetData: React.Dispatch<React.SetStateAction<SheetData<T>>>;
|
|
17
19
|
onChangeTables: React.Dispatch<React.SetStateAction<TableView[]>>;
|
|
@@ -31,7 +33,7 @@ export type useYSpreadsheetProps<T extends CellData, S extends Sheet, C extends
|
|
|
31
33
|
* Does not load initial data
|
|
32
34
|
* @returns
|
|
33
35
|
*/
|
|
34
|
-
export declare const useYSpreadsheet: <T extends CellData, S extends Sheet, C extends EmbeddedChart, O extends EmbeddedObject>({ onChangeSheetData, onChangeSheets, onChangeTables, onChangeCharts, onChangeEmbeds, onChangeConditionalFormats, onChangeDataValidations, onChangeNamedRanges, onChangeProtectedRanges, onError, enqueueCalculation, calculateNow, userId, title, sheetId, activeCell, doc, provider, }: useYSpreadsheetProps<T, S, C, O>) => {
|
|
36
|
+
export declare const useYSpreadsheet: <T extends CellData, S extends Sheet, C extends EmbeddedChart, O extends EmbeddedObject>({ onChangeSheetData, onChangeSheets, onChangeTables, onChangeCharts, onChangeEmbeds, onChangeConditionalFormats, onChangeDataValidations, onChangeNamedRanges, onChangeProtectedRanges, onError, enqueueCalculation, calculateNow, userId, title, sheetId, activeCell, doc, provider, yjs, }: useYSpreadsheetProps<T, S, C, O>) => {
|
|
35
37
|
onBroadcastPatch: (patches: [patch: SpreadsheetPatch<Patch>, type?: HistoryType | undefined][]) => void;
|
|
36
38
|
users: Collaborator[];
|
|
37
39
|
synced: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-yspreadsheet.d.ts","sourceRoot":"","sources":["../../use-yspreadsheet.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC/D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,KAAK,EACV,QAAQ,EACR,YAAY,EACZ,qBAAqB,EACrB,wBAAwB,EACxB,aAAa,EACb,cAAc,EACd,UAAU,EACV,cAAc,EACd,KAAK,EACL,SAAS,EACV,MAAM,2BAA2B,CAAC;AAKnC,OAAO,KAAK,EACV,mBAAmB,EACnB,oBAAoB,EACpB,WAAW,EAEX,SAAS,EACT,gBAAgB,EACjB,MAAM,iCAAiC,CAAC;AACzC,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,OAAO,CAAC;AAInC,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"use-yspreadsheet.d.ts","sourceRoot":"","sources":["../../use-yspreadsheet.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,20 +2,20 @@ import { scheduleNormalPriorityCallback, useIsomorphicLayoutEffect, } from "@row
|
|
|
2
2
|
import { applyPatches } from "immer";
|
|
3
3
|
import uniqBy from "lodash/uniqBy";
|
|
4
4
|
import { useCallback, useMemo, useRef, useState } from "react";
|
|
5
|
-
import
|
|
6
|
-
const getStateVector = (doc) => encodeStateVector(doc);
|
|
5
|
+
import * as Y from "yjs";
|
|
6
|
+
const getStateVector = (doc, yjs) => yjs.encodeStateVector(doc);
|
|
7
7
|
// Function to get updates between two state vectors
|
|
8
|
-
const getUpdatesBetween = (doc, stateVector1, stateVector2) => {
|
|
9
|
-
const diff = encodeStateAsUpdate(doc, stateVector1);
|
|
10
|
-
const newDiff = encodeStateAsUpdate(doc, stateVector2);
|
|
11
|
-
return diffUpdate(diff, newDiff);
|
|
8
|
+
const getUpdatesBetween = (doc, stateVector1, stateVector2, yjs) => {
|
|
9
|
+
const diff = yjs.encodeStateAsUpdate(doc, stateVector1);
|
|
10
|
+
const newDiff = yjs.encodeStateAsUpdate(doc, stateVector2);
|
|
11
|
+
return yjs.diffUpdate(diff, newDiff);
|
|
12
12
|
};
|
|
13
13
|
/**
|
|
14
14
|
* Connects to a Y server and picks up the most recent change
|
|
15
15
|
* Does not load initial data
|
|
16
16
|
* @returns
|
|
17
17
|
*/
|
|
18
|
-
export const useYSpreadsheet = ({ onChangeSheetData, onChangeSheets, onChangeTables, onChangeCharts, onChangeEmbeds, onChangeConditionalFormats, onChangeDataValidations, onChangeNamedRanges, onChangeProtectedRanges, onError, enqueueCalculation, calculateNow, userId, title, sheetId, activeCell, doc, provider, }) => {
|
|
18
|
+
export const useYSpreadsheet = ({ onChangeSheetData, onChangeSheets, onChangeTables, onChangeCharts, onChangeEmbeds, onChangeConditionalFormats, onChangeDataValidations, onChangeNamedRanges, onChangeProtectedRanges, onError, enqueueCalculation, calculateNow, userId, title, sheetId, activeCell, doc, provider, yjs = Y, }) => {
|
|
19
19
|
const [users, setUsers] = useState([]);
|
|
20
20
|
const [synced, setSynced] = useState(false);
|
|
21
21
|
const previousStateVectorRef = useRef(undefined);
|
|
@@ -89,10 +89,10 @@ export const useYSpreadsheet = ({ onChangeSheetData, onChangeSheets, onChangeTab
|
|
|
89
89
|
let syncedDoc = doc;
|
|
90
90
|
if (previousStateVectorRef.current) {
|
|
91
91
|
// Capture the current state vector on subsequent syncs
|
|
92
|
-
currentStateVectorRef.current = getStateVector(doc);
|
|
93
|
-
const updates = getUpdatesBetween(doc, previousStateVectorRef.current, currentStateVectorRef.current);
|
|
94
|
-
const newDoc = new Doc({ gc: true });
|
|
95
|
-
applyUpdate(newDoc, updates);
|
|
92
|
+
currentStateVectorRef.current = getStateVector(doc, yjs);
|
|
93
|
+
const updates = getUpdatesBetween(doc, previousStateVectorRef.current, currentStateVectorRef.current, yjs);
|
|
94
|
+
const newDoc = new yjs.Doc({ gc: true });
|
|
95
|
+
yjs.applyUpdate(newDoc, updates);
|
|
96
96
|
syncedDoc = newDoc;
|
|
97
97
|
previousStateVectorRef.current = currentStateVectorRef.current;
|
|
98
98
|
}
|
|
@@ -137,7 +137,7 @@ export const useYSpreadsheet = ({ onChangeSheetData, onChangeSheets, onChangeTab
|
|
|
137
137
|
});
|
|
138
138
|
// Set synced
|
|
139
139
|
setSynced(true);
|
|
140
|
-
}, [calculateNow, docMap, onError, doc]);
|
|
140
|
+
}, [calculateNow, docMap, onError, doc, yjs]);
|
|
141
141
|
// Sync state
|
|
142
142
|
if (provider?.synced && !synced) {
|
|
143
143
|
onSynced();
|
|
@@ -147,7 +147,7 @@ export const useYSpreadsheet = ({ onChangeSheetData, onChangeSheets, onChangeTab
|
|
|
147
147
|
provider?.on("synced", onSynced);
|
|
148
148
|
const onConnectionStatusChange = ({ status }) => {
|
|
149
149
|
if (status === "disconnected") {
|
|
150
|
-
previousStateVectorRef.current = getStateVector(doc);
|
|
150
|
+
previousStateVectorRef.current = getStateVector(doc, yjs);
|
|
151
151
|
}
|
|
152
152
|
};
|
|
153
153
|
provider?.on("status", onConnectionStatusChange);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rowsncolumns/y-spreadsheet",
|
|
3
|
-
"version": "15.0
|
|
3
|
+
"version": "15.1.0",
|
|
4
4
|
"main": "./dist/cjs/index.js",
|
|
5
5
|
"module": "./dist/esm/index.js",
|
|
6
6
|
"exports": {
|
|
@@ -56,7 +56,6 @@
|
|
|
56
56
|
"@rowsncolumns/utils": ">=9.3.0",
|
|
57
57
|
"react": ">=18.0.0",
|
|
58
58
|
"react-dom": ">=18.0.0",
|
|
59
|
-
"y-websocket": ">=2.0.0",
|
|
60
59
|
"yjs": ">=13.0.0"
|
|
61
60
|
},
|
|
62
61
|
"dependencies": {
|