@fileverse-dev/dsheet 4.1.23 → 4.1.24-stabilise-package-1

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.
Files changed (131) hide show
  1. package/README.md +10 -0
  2. package/dist/editor/contexts/editor-context.d.ts +10 -0
  3. package/dist/editor/hooks/use-celldata-compaction.d.ts +44 -2
  4. package/dist/editor/hooks/use-celldata-compaction.test.d.ts +1 -0
  5. package/dist/editor/hooks/use-editor-data.d.ts +1 -0
  6. package/dist/editor/hooks/use-editor-sync.d.ts +5 -1
  7. package/dist/editor/hooks/use-editor-sync.test.d.ts +1 -0
  8. package/dist/editor/types.d.ts +44 -1
  9. package/dist/editor/utils/active-sheet-keeper.test.d.ts +1 -0
  10. package/dist/editor/utils/after-update-cell.d.ts +6 -0
  11. package/dist/editor/utils/after-update-cell.test.d.ts +1 -0
  12. package/dist/editor/utils/apply-comment-markers.d.ts +3 -1
  13. package/dist/editor/utils/apply-comment-markers.test.d.ts +1 -0
  14. package/dist/editor/utils/calc-chain-ydoc-update.test.d.ts +1 -0
  15. package/dist/editor/utils/content-change.d.ts +48 -0
  16. package/dist/editor/utils/content-change.test.d.ts +1 -0
  17. package/dist/editor/utils/content-fingerprint.d.ts +37 -0
  18. package/dist/editor/utils/content-fingerprint.test.d.ts +1 -0
  19. package/dist/editor/utils/editor-handle.d.ts +1 -1
  20. package/dist/editor/utils/format-gate.d.ts +27 -0
  21. package/dist/editor/utils/format-gate.test.d.ts +1 -0
  22. package/dist/editor/utils/legacy-on-change.d.ts +20 -0
  23. package/dist/editor/utils/legacy-on-change.test.d.ts +1 -0
  24. package/dist/editor/utils/patch-plain-celldata.d.ts +19 -0
  25. package/dist/editor/utils/patch-plain-celldata.test.d.ts +1 -0
  26. package/dist/editor/utils/rebuild-indexeddb-store.d.ts +19 -0
  27. package/dist/editor/utils/rebuild-indexeddb-store.failure.test.d.ts +1 -0
  28. package/dist/editor/utils/rebuild-indexeddb-store.test.d.ts +1 -0
  29. package/dist/editor/utils/remote-apply-guard.d.ts +45 -0
  30. package/dist/editor/utils/remote-apply-guard.test.d.ts +1 -0
  31. package/dist/editor/utils/sheet-ydoc-sync-utils.d.ts +13 -4
  32. package/dist/editor/utils/sheet-ydoc-sync-utils.test.d.ts +1 -0
  33. package/dist/editor/utils/structural-edit-formula-sync.test.d.ts +1 -0
  34. package/dist/editor/utils/structural-edit-other-tabs.test.d.ts +1 -0
  35. package/dist/editor/utils/update-ydoc.d.ts +9 -1
  36. package/dist/editor/utils/wait-for-sync.d.ts +35 -0
  37. package/dist/editor/utils/wait-for-sync.test.d.ts +1 -0
  38. package/dist/editor/utils/xlsx-export-impl.d.ts +1 -0
  39. package/dist/editor/utils/xlsx-export.d.ts +1 -0
  40. package/dist/{executeStringFunction-ZHYCWIpK.js → executeStringFunction-CT-4of9q.js} +13725 -12781
  41. package/dist/formula.js +1 -1
  42. package/dist/{index-8_qnUyMl.js → index-CfRtF9ry.js} +23233 -21903
  43. package/dist/index.d.ts +2 -2
  44. package/dist/index.es.js +44 -43
  45. package/dist/persistence-utils-CSKBobX8.js +151 -0
  46. package/dist/persistence-utils.d.ts +2 -0
  47. package/dist/persistence.append.test.d.ts +1 -0
  48. package/dist/persistence.d.ts +13 -0
  49. package/dist/persistence.fingerprint.test.d.ts +1 -0
  50. package/dist/persistence.js +122 -74
  51. package/dist/sheet-engine/core/api/cell.d.ts +25 -1
  52. package/dist/sheet-engine/core/api/remote-apply.test.d.ts +1 -0
  53. package/dist/sheet-engine/core/api/sheet-flowdata-lifecycle.d.ts +1 -0
  54. package/dist/sheet-engine/core/api/sheet-flowdata-lifecycle.test.d.ts +1 -0
  55. package/dist/sheet-engine/core/index.d.ts +1 -1
  56. package/dist/sheet-engine/core/modules/b6-ydoc-writes.test.d.ts +1 -0
  57. package/dist/sheet-engine/core/modules/border.d.ts +9 -0
  58. package/dist/sheet-engine/core/modules/cell-shift.d.ts +11 -0
  59. package/dist/sheet-engine/core/modules/cell.commit-ydoc.test.d.ts +1 -0
  60. package/dist/sheet-engine/core/modules/dataVerification.d.ts +15 -0
  61. package/dist/sheet-engine/core/modules/engine-hot-spots.test.d.ts +1 -0
  62. package/dist/sheet-engine/core/modules/formula-arrow-mode.d.ts +81 -0
  63. package/dist/sheet-engine/core/modules/formula-arrow-mode.test.d.ts +1 -0
  64. package/dist/sheet-engine/core/modules/formula-async-eval.d.ts +5 -0
  65. package/dist/sheet-engine/core/modules/formula-dep-text.d.ts +23 -0
  66. package/dist/sheet-engine/core/modules/formula-dep-text.test.d.ts +1 -0
  67. package/dist/sheet-engine/core/modules/formula-edge-update.d.ts +13 -0
  68. package/dist/sheet-engine/core/modules/formula-narrowing.test.d.ts +1 -0
  69. package/dist/sheet-engine/core/modules/formula-snapshot-eval.d.ts +2 -0
  70. package/dist/sheet-engine/core/modules/formula-tab-load.test.d.ts +1 -0
  71. package/dist/sheet-engine/core/modules/formula-transitive-deps.d.ts +26 -1
  72. package/dist/sheet-engine/core/modules/formula-warmup.d.ts +33 -0
  73. package/dist/sheet-engine/core/modules/formula-warmup.test.d.ts +1 -0
  74. package/dist/sheet-engine/core/modules/formula.d.ts +119 -2
  75. package/dist/sheet-engine/core/modules/formula.dep-graph.test.d.ts +1 -0
  76. package/dist/sheet-engine/core/modules/formula.str-change.test.d.ts +1 -0
  77. package/dist/sheet-engine/core/modules/formula.wide-seed.test.d.ts +1 -0
  78. package/dist/sheet-engine/core/modules/history-calc-chain.d.ts +39 -0
  79. package/dist/sheet-engine/core/modules/history-calc-chain.test.d.ts +1 -0
  80. package/dist/sheet-engine/core/modules/history-celldata.d.ts +25 -0
  81. package/dist/sheet-engine/core/modules/history-grid-size.d.ts +16 -0
  82. package/dist/sheet-engine/core/modules/history-grid-size.test.d.ts +1 -0
  83. package/dist/sheet-engine/core/modules/index.d.ts +4 -3
  84. package/dist/sheet-engine/core/modules/rowcol-refused-edit.test.d.ts +1 -0
  85. package/dist/sheet-engine/core/modules/rowcol-unloaded-history.test.d.ts +1 -0
  86. package/dist/sheet-engine/core/modules/rowcol.d.ts +8 -0
  87. package/dist/sheet-engine/core/modules/structural-edit-prep.d.ts +36 -0
  88. package/dist/sheet-engine/core/modules/structural-edit-prep.test.d.ts +1 -0
  89. package/dist/sheet-engine/core/paste/paste-dep-graph.test.d.ts +1 -0
  90. package/dist/sheet-engine/core/paste-helpers/calculate-range-cell-size.d.ts +14 -0
  91. package/dist/sheet-engine/core/paste-helpers/calculate-range-cell-size.test.d.ts +1 -0
  92. package/dist/sheet-engine/core/settings.d.ts +11 -1
  93. package/dist/sheet-engine/core/utils/ydoc-celldata-changes.d.ts +29 -2
  94. package/dist/sheet-engine/react/components/ContextMenu/FilterMenu.d.ts +2 -2
  95. package/dist/sheet-engine/react/components/ContextMenu/SheetTab.d.ts +2 -2
  96. package/dist/sheet-engine/react/components/ContextMenu/index.d.ts +2 -2
  97. package/dist/sheet-engine/react/components/NotationBoxes/shown-comments.d.ts +20 -0
  98. package/dist/sheet-engine/react/components/NotationBoxes/shown-comments.test.d.ts +1 -0
  99. package/dist/sheet-engine/react/components/SVGDefines.d.ts +2 -2
  100. package/dist/sheet-engine/react/components/SheetOverlay/editing-as-formula.d.ts +7 -0
  101. package/dist/sheet-engine/react/components/SheetOverlay/editing-as-formula.test.d.ts +1 -0
  102. package/dist/sheet-engine/react/components/SheetOverlay/hover-tracker.d.ts +44 -0
  103. package/dist/sheet-engine/react/components/SheetOverlay/hover-tracker.test.d.ts +1 -0
  104. package/dist/sheet-engine/react/components/SheetTab/index.d.ts +2 -2
  105. package/dist/sheet-engine/react/components/Toolbar/FunctionList/group-functions.d.ts +9 -0
  106. package/dist/sheet-engine/react/components/Toolbar/FunctionList/group-functions.test.d.ts +1 -0
  107. package/dist/sheet-engine/react/components/Toolbar/index.d.ts +2 -2
  108. package/dist/sheet-engine/react/components/Workbook/api.d.ts +4 -0
  109. package/dist/sheet-engine/react/components/Workbook/denomination-scan.d.ts +16 -0
  110. package/dist/sheet-engine/react/components/Workbook/denomination-scan.test.d.ts +1 -0
  111. package/dist/sheet-engine/react/components/Workbook/index.d.ts +2 -0
  112. package/dist/sheet-engine/react/components/Workbook/stable-handle.d.ts +8 -0
  113. package/dist/sheet-engine/react/components/Workbook/stable-handle.test.d.ts +1 -0
  114. package/dist/sheet-engine/react/context/index.d.ts +15 -0
  115. package/dist/sheet-engine/react/context/use-workbook-store.d.ts +30 -0
  116. package/dist/sheet-engine/react/context/workbook-store.d.ts +30 -0
  117. package/dist/sheet-engine/react/context/workbook-store.test.d.ts +1 -0
  118. package/dist/style.css +1 -1
  119. package/dist/sync-local/SyncManager.d.ts +2 -2
  120. package/dist/sync-local/SyncManager.remote-apply.test.d.ts +1 -0
  121. package/dist/sync-local/SyncManager.test.d.ts +1 -0
  122. package/dist/sync-local/socketClient.d.ts +2 -0
  123. package/dist/sync-local/socketClient.test.d.ts +1 -0
  124. package/dist/sync-local/types/index.d.ts +5 -1
  125. package/dist/{use-xlsx-import-impl-cgDH14_y.js → use-xlsx-import-impl-Yyav1K_X.js} +120 -119
  126. package/dist/{xlsx-defined-names-DCK7aKIO.js → xlsx-defined-names-D6d7zKHF.js} +2 -2
  127. package/dist/xlsx-export-impl-BHJqLH7w.js +903 -0
  128. package/dist/{xlsx-hyperlink-inline-T4GsXFm7.js → xlsx-hyperlink-inline-DGb-w2IM.js} +2 -2
  129. package/package.json +1 -1
  130. package/dist/persistence-utils-BXFajzvq.js +0 -98
  131. package/dist/xlsx-export-impl-BHDWz0aW.js +0 -880
package/README.md CHANGED
@@ -86,6 +86,7 @@ function App() {
86
86
  | `isCollaborative` | `boolean` | `false` | Enable collaborative features |
87
87
  | `enableWebrtc` | `boolean` | `true` | Enable WebRTC for P2P collaboration |
88
88
  | `enableIndexeddbSync` | `boolean` | `true` | Enable local IndexedDB persistence |
89
+ | `onIndexedDbError` | `(error: Error) => void` | - | Called when local persistence fails, or when the first read of the local store does not finish within 15 s. The editor continues with the in-memory document; call `rebuildLocalStore()` to recreate the store |
89
90
  | `isTemplateOpen` | `boolean` | - | Template sidebar state |
90
91
  | `selectedTemplate` | `string` | - | Selected template identifier |
91
92
  | `onboardingComplete` | `boolean` | - | Onboarding completion status |
@@ -115,6 +116,15 @@ function App() {
115
116
  | `setInputFetchURLDataBlock` | `Dispatch<SetStateAction<string>>` | URL input state setter |
116
117
  | `setForceSheetRender` | `Dispatch<SetStateAction<number>>` | Force re-render trigger |
117
118
 
119
+ ### Editor handle methods
120
+
121
+ `sheetEditorRef` is a `DSheetEditorHandle`: the workbook instance plus these methods.
122
+
123
+ | Method | Description |
124
+ |--------|-------------|
125
+ | `rebuildLocalStore()` | `Promise<void>`. Deletes this sheet's IndexedDB database and rewrites it from the live document, so local edits are kept. A no-op when read-only or when `enableIndexeddbSync` is off. Rejects if the rebuild fails, the new store does not sync within 15 s, or a refresh or unmount supersedes it. Concurrent calls share one rebuild. |
126
+ | `getContentFingerprint()` | `string \| null`. Changes on every document change, deletes included; compare with `===` only. `null` until local persistence has synced. |
127
+
118
128
  ## Examples
119
129
 
120
130
  ### Read-Only Viewer
@@ -10,6 +10,7 @@ import { UseSmartContractReturn } from '../hooks/use-smart-contract';
10
10
  import { CollaborationProps, CollabState, CollabUser } from '../../sync-local/types';
11
11
  import { Awareness } from 'y-protocols/awareness';
12
12
  import { DSheetContentSnapshot } from '../../persistence';
13
+ import { DSheetContentChange } from '../utils/content-change';
13
14
 
14
15
  import * as Y from 'yjs';
15
16
  export interface EditorContextType {
@@ -20,6 +21,8 @@ export interface EditorContextType {
20
21
  getDocumentTitle?: (dsheetId: string) => Promise<string>;
21
22
  updateDocumentTitle?: (title: string) => void;
22
23
  isAuthorized: boolean;
24
+ /** True once the sheet's meta.format is above what this client supports. */
25
+ formatLocked?: boolean;
23
26
  dataBlockCalcFunction: {
24
27
  [key: string]: {
25
28
  [key: string]: any;
@@ -31,14 +34,19 @@ export interface EditorContextType {
31
34
  };
32
35
  }>>;
33
36
  refreshIndexedDB: () => Promise<void>;
37
+ rebuildLocalStore: () => Promise<void>;
34
38
  sheetEditorRef: React.MutableRefObject<WorkbookInstance | null>;
35
39
  setSheetEditorRef: React.RefCallback<WorkbookInstance>;
40
+ /** Call when the active sheet changes or sheets appear; lets the keeper remember or restore it. */
41
+ notifyActiveSheetChange: () => void;
36
42
  ydocRef: React.MutableRefObject<Y.Doc | null>;
37
43
  persistenceRef: React.MutableRefObject<IndexeddbPersistence | null>;
38
44
  sheetData: Sheet[];
39
45
  setSheetData: React.Dispatch<React.SetStateAction<Sheet[]>>;
40
46
  currentDataRef: React.MutableRefObject<Sheet[]>;
41
47
  remoteUpdateRef: React.MutableRefObject<boolean>;
48
+ /** True while a peer's structural edit waits to remount the grid. */
49
+ structuralRemountPendingRef: React.MutableRefObject<boolean>;
42
50
  handleChange: (data: Sheet[]) => void;
43
51
  initialiseLiveQueryData: (data: Sheet[]) => void;
44
52
  loading: boolean;
@@ -75,11 +83,13 @@ interface EditorProviderProps {
75
83
  isAuthorized: boolean;
76
84
  children: React.ReactNode;
77
85
  dsheetId: string;
86
+ localStoreId?: string;
78
87
  username?: string;
79
88
  portalContent?: string;
80
89
  enableIndexeddbSync?: boolean;
81
90
  isReadOnly?: boolean;
82
91
  onContentUpdate?: () => void;
92
+ onContentChange?: (change: DSheetContentChange) => void;
83
93
  onChange?: (data: SheetUpdateData, encodedUpdate?: string) => void;
84
94
  collaboration?: CollaborationProps;
85
95
  externalEditorRef?: React.Ref<DSheetEditorHandle>;
@@ -1,7 +1,12 @@
1
1
  import { MutableRefObject, RefObject } from 'react';
2
2
  import { WorkbookInstance, Sheet } from '../../sheet-engine/react';
3
+ import { planYdocCompaction } from '../utils/compact-committed-celldata';
3
4
 
4
- import type * as Y from 'yjs';
5
+ import * as Y from 'yjs';
6
+ /** Wait before a refused or stopped run is tried again. */
7
+ export declare const COMPACTION_RETRY_MS = 30000;
8
+ /** Tries after the first run; the compaction is also retried on a later open. */
9
+ export declare const COMPACTION_MAX_RETRIES = 10;
5
10
  type UseCelldataCompactionArgs = {
6
11
  dsheetId: string;
7
12
  ydocRef: RefObject<Y.Doc | null>;
@@ -12,11 +17,48 @@ type UseCelldataCompactionArgs = {
12
17
  isDataLoaded: boolean;
13
18
  isReadOnly: boolean;
14
19
  remoteUpdateRef: MutableRefObject<boolean>;
20
+ /** True while a structural remote change waits to remount the Workbook. */
21
+ structuralRemountPendingRef?: MutableRefObject<boolean>;
15
22
  collabSyncing: boolean;
16
23
  };
24
+ /**
25
+ * False while a remote apply is running, while a structural remount is pending
26
+ * (the engine still has the old grid and the Yjs document has the new one) or
27
+ * while collaboration is still syncing.
28
+ */
29
+ export declare function canRunCompaction(refs: {
30
+ remoteUpdateRef: MutableRefObject<boolean>;
31
+ structuralRemountPendingRef?: MutableRefObject<boolean>;
32
+ }, collabSyncing: boolean): boolean;
33
+ /**
34
+ * Runs `attempt` and, when it asks for a retry (the gate was closed, or a run
35
+ * stopped because the document changed), calls `retry` after `delayMs`, up to
36
+ * `maxRetries` times. Without it a refused run waited for the next open, so
37
+ * compaction made one chunk of progress per open (PR 466 N10).
38
+ */
39
+ export declare function createCompactionRetrier({ attempt, retry, delayMs, maxRetries, setTimer, clearTimer, }: {
40
+ attempt: () => Promise<'done' | 'retry'>;
41
+ retry: () => void;
42
+ delayMs?: number;
43
+ maxRetries?: number;
44
+ setTimer?: (cb: () => void, ms: number) => ReturnType<typeof setTimeout>;
45
+ clearTimer?: (handle: ReturnType<typeof setTimeout>) => void;
46
+ }): {
47
+ run(): Promise<void>;
48
+ cancel(): void;
49
+ };
50
+ /**
51
+ * Applies the plan chunk by chunk, yielding between chunks. The plan was made
52
+ * from the document as it was, and one change in it, the whole-value
53
+ * borderInfo write, would overwrite a newer value. So the run stops, leaving
54
+ * the compaction unfinished (it is retried on a later open), as soon as the
55
+ * document differs from what the previous chunk left. Returns true when every
56
+ * chunk was applied.
57
+ */
58
+ export declare function applyCompactionChangesInChunks(ydoc: Y.Doc, dsheetId: string, changes: ReturnType<typeof planYdocCompaction>['changes'], handleOnChangePortalUpdate: () => void, isCancelled: () => boolean, sheetEditorRef: RefObject<WorkbookInstance | null>, chunkSize?: number): Promise<boolean>;
17
59
  /**
18
60
  * One-shot background compaction (celldata + borderInfo). Completion is stored
19
61
  * on the anchor sheet's config in Yjs so it syncs across devices.
20
62
  */
21
- export declare function useCelldataCompaction({ dsheetId, ydocRef, sheetEditorRef, currentDataRef, handleOnChangePortalUpdate, syncStatus, isDataLoaded, isReadOnly, remoteUpdateRef, collabSyncing, }: UseCelldataCompactionArgs): void;
63
+ export declare function useCelldataCompaction({ dsheetId, ydocRef, sheetEditorRef, currentDataRef, handleOnChangePortalUpdate, syncStatus, isDataLoaded, isReadOnly, remoteUpdateRef, structuralRemountPendingRef, collabSyncing, }: UseCelldataCompactionArgs): void;
22
64
  export {};
@@ -21,6 +21,7 @@ export declare const useEditorData: (ydocRef: React.MutableRefObject<Y.Doc | nul
21
21
  setSheetData: import('react').Dispatch<import('react').SetStateAction<Sheet[]>>;
22
22
  currentDataRef: import('react').MutableRefObject<Sheet[]>;
23
23
  remoteUpdateRef: import('react').MutableRefObject<boolean>;
24
+ structuralRemountPendingRef: import('react').MutableRefObject<boolean>;
24
25
  isDataLoaded: boolean;
25
26
  setIsDataLoaded: import('react').Dispatch<import('react').SetStateAction<boolean>>;
26
27
  handleChange: (_data: Sheet[]) => void;
@@ -1,13 +1,17 @@
1
1
  import { IndexeddbPersistence } from 'y-indexeddb';
2
2
  import { CollaborationProps } from '../../sync-local/types';
3
+ import { DSheetContentChange } from '../utils/content-change';
3
4
  import * as Y from 'yjs';
4
- export declare const useEditorSync: (dsheetId: string, enableIndexeddbSync?: boolean, isReadOnly?: boolean, portalContent?: string, collaboration?: CollaborationProps, onCollabUpdate?: (fullState: string, updateChunk: string) => void, onIndexedDbError?: (error: Error) => void) => {
5
+ export declare const useEditorSync: (dsheetId: string, enableIndexeddbSync?: boolean, isReadOnly?: boolean, portalContent?: string, collaboration?: CollaborationProps, onRemoteApplied?: (update: Uint8Array) => void, onIndexedDbError?: (error: Error) => void, localStoreId?: string) => {
5
6
  ydocRef: import('react').MutableRefObject<Y.Doc | null>;
6
7
  persistenceRef: import('react').MutableRefObject<IndexeddbPersistence | null>;
7
8
  syncStatus: "error" | "syncing" | "initializing" | "synced";
8
9
  isContentBootstrapReady: boolean;
9
10
  refreshIndexedDB: () => Promise<void>;
11
+ rebuildLocalStore: () => Promise<void>;
10
12
  getContentSnapshot: () => import('../../persistence-utils').DSheetContentSnapshot;
13
+ getContentFingerprint: () => string | null;
14
+ getContentChange: () => DSheetContentChange;
11
15
  mergeContent: (encodedState: string) => import('../../persistence-utils').DSheetContentSnapshot;
12
16
  collabState: import('../..').CollabState;
13
17
  isCollabReady: boolean;
@@ -0,0 +1 @@
1
+ export {};
@@ -5,11 +5,13 @@ import { CollaborationProps } from '../sync-local/types';
5
5
  import { CommentsConfig } from './types/comments';
6
6
  import { SmartContractConfig } from './types/smart-contract';
7
7
  import { ApiKeyStorage } from './utils/api-key-storage';
8
+ import { DSheetContentChange } from './utils/content-change';
8
9
  import { ThemeKey } from '../sheet-engine/core/theme';
9
10
  import { DSheetContentSnapshot } from '../persistence';
10
11
  import { NavigateToSheetOptions } from './utils/navigate-to-sheet';
11
12
 
12
13
  import * as Y from 'yjs';
14
+ export type { DSheetContentChange } from './utils/content-change';
13
15
  export type { ThemeKey } from '../sheet-engine/core/theme';
14
16
  export type DSheetPermissionMode = 'view' | 'comment' | 'edit';
15
17
  export type { CommentThread, CommentReply, CommentActionParams, CommentsConfig, CommentAnchorMove, } from './types/comments';
@@ -26,6 +28,20 @@ export interface EditorValues {
26
28
  }
27
29
  export type DSheetEditorHandle = WorkbookInstance & {
28
30
  refreshIndexedDB: () => Promise<void>;
31
+ /**
32
+ * Delete this sheet's IndexedDB database and rewrite it from the live
33
+ * document (same Y.Doc, never replaced), so local edits are kept. Resolves
34
+ * once the new store has synced. Resolves as a no-op when the editor is
35
+ * read-only, local persistence is disabled or there is no sheet id. Rejects
36
+ * if the rebuild fails, if the new store does not sync within 15 s, or if a
37
+ * refresh/unmount supersedes it. Concurrent calls share one rebuild.
38
+ *
39
+ * A bootstrap that is still waiting on an unreadable store (the case this
40
+ * heals) is abandoned after 3 s: the rebuild then finishes the bootstrap
41
+ * itself, merging the published content into the document, so the editor
42
+ * leaves "syncing" once the rebuild resolves.
43
+ */
44
+ rebuildLocalStore: () => Promise<void>;
29
45
  terminateSession: () => void;
30
46
  updateCollaboratorName: (name: string) => void;
31
47
  updateSessionTitle: (args: {
@@ -33,6 +49,13 @@ export type DSheetEditorHandle = WorkbookInstance & {
33
49
  documentTitle: string;
34
50
  }) => void;
35
51
  navigateToSheet: (sheetId: string, opts?: NavigateToSheetOptions) => Promise<boolean>;
52
+ /**
53
+ * Content fingerprint: base64 state vector + ":" + deleted count. Changes
54
+ * on every change, deletes included; compare with === only. null until
55
+ * y-indexeddb is attached and synced; use fingerprintFromUpdate of the
56
+ * IndexedDB body then.
57
+ */
58
+ getContentFingerprint: () => string | null;
36
59
  };
37
60
  export interface PanelConfig {
38
61
  id: string;
@@ -79,15 +102,35 @@ export interface DsheetProps {
79
102
  showFormulaBar?: boolean;
80
103
  enableIndexeddbSync?: boolean;
81
104
  dsheetId: string;
105
+ /** Names the local IndexedDB database and labels content snapshots
106
+ * (default `dsheetId`). `dsheetId` stays the Yjs root key. */
107
+ localStoreId?: string;
82
108
  /**
83
109
  * Cheap change signal for hosts that persist sheet bodies through Yjs.
84
110
  * Unlike `onChange`, this does not build a plain sheet or encode the Y.Doc.
85
111
  */
86
112
  onContentUpdate?: () => void;
113
+ /**
114
+ * Cheap change signal (Stabilize A1): `hasContent`, `userEdited` and the
115
+ * content `fingerprint`, computed without encoding the Y.Doc. At most one
116
+ * call per second (trailing), flushed on pagehide, beforeunload and unmount.
117
+ * Read full state on demand with `editorStateRef.getContentSnapshot()`.
118
+ */
119
+ onContentChange?: (change: DSheetContentChange) => void;
120
+ /**
121
+ * @deprecated Encodes the whole Y.Doc and rebuilds a plain workbook per
122
+ * call. Use `onContentChange` and read full state on demand. Called at most
123
+ * once per second (trailing), flushed only on unload and unmount. The latest
124
+ * `onChange` you pass is the one called, so an inline function is safe: a new
125
+ * identity does not cancel a pending call.
126
+ */
87
127
  onChange?: (updateData: SheetUpdateData, encodedUpdate?: string) => void;
88
128
  collaboration?: CollaborationProps;
89
129
  /** Called when local IndexedDB persistence fails. The editor and durable
90
- * collaboration continue using the in-memory Y.Doc, matching dDoc. */
130
+ * collaboration continue using the in-memory Y.Doc, matching dDoc. Also
131
+ * called when the first read of the local store does not finish within 15 s
132
+ * (the editor then continues without local persistence, and
133
+ * `rebuildLocalStore()` can recreate the store). */
91
134
  onIndexedDbError?: (error: Error) => void;
92
135
  username?: string;
93
136
  portalContent?: string;
@@ -0,0 +1 @@
1
+ export {};
@@ -50,7 +50,13 @@ interface AfterUpdateCellParams {
50
50
  collabEnabled?: boolean;
51
51
  collabIsOwner?: boolean;
52
52
  remoteUpdateRef?: React.MutableRefObject<boolean>;
53
+ /** True while a peer's structural edit waits to remount the grid. */
54
+ structuralRemountPendingRef?: React.MutableRefObject<boolean>;
53
55
  localUserEditRef?: React.MutableRefObject<boolean>;
56
+ /** Sheet that held the cell at commit time; falls back to the current sheet. */
57
+ sheetId?: string;
58
+ /** The caller writes the cell to Yjs itself (paste and fill batch writes). */
59
+ skipPersist?: boolean;
54
60
  }
55
61
  /** True when data-block formulas (API / smart contract) should run for this edit. */
56
62
  export declare const shouldExecuteDataBlocks: (params: {
@@ -0,0 +1 @@
1
+ export {};
@@ -8,7 +8,9 @@ type CommentData = object | null | undefined;
8
8
  * `null`, so a tiny `{ ps }` object is created in the live view only so the
9
9
  * triangle can render. Row/column drag still owns real cell data.
10
10
  *
11
- * Mutates in place and returns the same array.
11
+ * Updates the given arrays in place and returns them. Sparse celldata entries
12
+ * are replaced with copies, never written into, because they may be Yjs
13
+ * content (see ySheetArrayToPlain).
12
14
  */
13
15
  export declare const applyCommentMarkers: <T>(sheets: T | null | undefined, commentData: CommentData, allowComments: boolean | undefined) => T | null | undefined;
14
16
  export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,48 @@
1
+ import * as Y from 'yjs';
2
+ /**
3
+ * Cheap change signal for hosts (Stabilize A1). Built from the live Y.Doc
4
+ * without encoding it; full state stays available through
5
+ * editorStateRef.getContentSnapshot().
6
+ */
7
+ export type DSheetContentChange = {
8
+ /** Same meaning as dsheets.new `isEmptySheet(...) === false`. */
9
+ hasContent: boolean;
10
+ /** A local user edit has happened since this editor mounted. */
11
+ userEdited: boolean;
12
+ /**
13
+ * base64 state vector + ":" + deleted count. Changes on every change,
14
+ * deletes included; compare with === only. null until y-indexeddb is
15
+ * attached and synced.
16
+ */
17
+ fingerprint: string | null;
18
+ };
19
+ /** Default-workbook seed and legacy migration: content, but not a user edit. */
20
+ export declare const INIT_ORIGIN = "init";
21
+ /** Idle celldata compaction: rewrites storage, not a user edit. */
22
+ export declare const COMPACTION_ORIGIN = "compaction";
23
+ /**
24
+ * Mirrors dsheets.new `isEmptySheet` (components/dsheet-editor/editor-utils.ts):
25
+ * no sheets → empty; more than one sheet → content; otherwise content when
26
+ * sheet 0 has any config key or any celldata entry. Stops at the first live
27
+ * entry, so it does not walk the sheet.
28
+ */
29
+ export declare function computeHasContent(doc: Y.Doc, dsheetId: string): boolean;
30
+ export type UserEditTracker = {
31
+ userEdited(): boolean;
32
+ dispose(): void;
33
+ };
34
+ /**
35
+ * Turns on at the first transaction that changes the document and whose
36
+ * origin is not a known non-user origin or the IndexedDB provider.
37
+ */
38
+ export declare function attachUserEditTracker(doc: Y.Doc, getIndexeddbOrigin: () => unknown): UserEditTracker;
39
+ export declare const CONTENT_CHANGE_WAIT_MS = 1000;
40
+ /**
41
+ * At most one host call per second (trailing only); local and remote changes
42
+ * share the throttle. The change is built when the call fires, never per edit.
43
+ */
44
+ export declare function createContentChangeNotifier(getChange: () => DSheetContentChange, getHandler: () => ((change: DSheetContentChange) => void) | undefined, waitMs?: number): {
45
+ notify: () => void;
46
+ flush: () => void;
47
+ cancel: () => any;
48
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,37 @@
1
+ import * as Y from 'yjs';
2
+ /**
3
+ * Cheap "did the document change" signal (Stabilize A1 / D6).
4
+ *
5
+ * fingerprint = base64(state vector) + ":" + deletedCount
6
+ *
7
+ * A state vector alone misses delete-only transactions (clearing a filter,
8
+ * dropdown, conditional format, sub-sheet or named range; undo of a typed
9
+ * value), because a Yjs delete adds no item. deletedCount is the total length
10
+ * of all deleted ranges in the document, kept as a running count so no full
11
+ * scan is needed per check. Compare fingerprints with === only.
12
+ */
13
+ export type ContentFingerprintTracker = {
14
+ getFingerprint(): string;
15
+ getDeletedCount(): number;
16
+ dispose(): void;
17
+ };
18
+ /**
19
+ * Attach when the Y.Doc is created, before any provider writes to it, so the
20
+ * count is built from transactions. Attaching later costs one full scan.
21
+ */
22
+ export declare function attachContentFingerprint(doc: Y.Doc): ContentFingerprintTracker;
23
+ /**
24
+ * Same value as a live tracker, for a stored body or a merge of raw rows.
25
+ * Builds a temporary document and scans its delete set, as getDsheetStateVector
26
+ * builds one. Reading the delete ranges from the encoded update instead
27
+ * (Y.decodeUpdate) misses GC structs that arrive without a delete range, e.g.
28
+ * a row written when the document received a compacted body.
29
+ */
30
+ export declare function fingerprintFromUpdate(update: Uint8Array): string;
31
+ /**
32
+ * Same value as a live tracker, computed from a document by one scan of its
33
+ * delete set (under 1.5 ms at 100k cells). Used by the body reads, which
34
+ * already build a document from every IndexedDB row, so GC structs that
35
+ * arrived without a delete range are counted.
36
+ */
37
+ export declare function fingerprintFromDocument(doc: Y.Doc): string;
@@ -2,7 +2,7 @@ import { Ref } from 'react';
2
2
  import { WorkbookInstance } from '../../sheet-engine/react';
3
3
  import { DSheetEditorHandle } from '../types';
4
4
 
5
- export type DSheetEditorMethods = Pick<DSheetEditorHandle, "refreshIndexedDB" | "terminateSession" | "updateCollaboratorName" | "updateSessionTitle" | "navigateToSheet">;
5
+ export type DSheetEditorMethods = Pick<DSheetEditorHandle, "refreshIndexedDB" | "rebuildLocalStore" | "terminateSession" | "updateCollaboratorName" | "updateSessionTitle" | "navigateToSheet" | "getContentFingerprint">;
6
6
  /**
7
7
  * Compose Fortune's regenerated imperative handle with dSheet lifecycle methods.
8
8
  * React callback refs invoke this again after every Workbook remount, so both
@@ -0,0 +1,27 @@
1
+ import * as Y from 'yjs';
2
+ /** Highest `meta.format` this client can write. */
3
+ export declare const SUPPORTED_FORMAT = 1;
4
+ /** Shown to the user once the sheet is locked. */
5
+ export declare const FORMAT_LOCKED_MESSAGE = "This sheet was updated. Reload to continue.";
6
+ /**
7
+ * Format of the sheet in `doc`. A missing map or key means 1, and so does a
8
+ * value that is not a number. Does not create the map when it is absent.
9
+ */
10
+ export declare function readFormatVersion(doc: Y.Doc, dsheetId: string): number;
11
+ /**
12
+ * True once a watched sheet of `doc` has shown a format above SUPPORTED_FORMAT.
13
+ * With `dsheetId` it answers for that sheet only, and also reads the doc, so
14
+ * it holds before the watch starts. Without it, true if any sheet is locked.
15
+ */
16
+ export declare function isFormatLocked(doc: Y.Doc | null | undefined, dsheetId?: string): boolean;
17
+ /**
18
+ * Make the grid read-only in the same tick the lock is seen. The Yjs writers
19
+ * stop at once (isFormatLocked), but the grid became read-only only after the
20
+ * workbook data changed identity and the Workbook init effect ran: a commit in
21
+ * between showed in the grid, never reached Yjs, and was gone on reload.
22
+ * The Workbook init effect sets the same flag from the prop afterwards.
23
+ */
24
+ export declare function lockWorkbookReadOnly(workbook: {
25
+ getWorkbookSetContext?: () => unknown;
26
+ } | null | undefined): boolean;
27
+ export declare function watchFormatGate(doc: Y.Doc, dsheetId: string, onLocked: (format: number) => void): () => void;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,20 @@
1
+ /** The legacy `onChange` argument pair: plain sheet data and the encoded doc. */
2
+ export type LegacyOnChangeArgs = {
3
+ data: unknown;
4
+ update: string;
5
+ };
6
+ export declare const LEGACY_ON_CHANGE_WAIT_MS = 1000;
7
+ /**
8
+ * Trailing-only throttle for the host's legacy `onChange`.
9
+ *
10
+ * The host's handler is read when the throttle fires, not when it is created.
11
+ * A host that passes a new inline `onChange` on every render must not rebuild
12
+ * this throttle: rebuilding cancels the pending call, so an edit followed
13
+ * within the wait by a re-render never reached the host's save.
14
+ * `buildArgs` runs only when there is a handler, because it encodes the whole
15
+ * document.
16
+ */
17
+ export declare function createLegacyOnChangeThrottle<TData>(getOnChange: () => ((data: TData, update: string) => void) | undefined, buildArgs: () => {
18
+ data: TData;
19
+ update: string;
20
+ } | null, waitMs?: number): any;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,19 @@
1
+ export type PlainCelldataEntry = {
2
+ r: number;
3
+ c: number;
4
+ v: unknown;
5
+ };
6
+ export type CelldataPatch = {
7
+ key: string;
8
+ action: 'delete' | 'update';
9
+ v?: unknown;
10
+ };
11
+ /**
12
+ * Apply a remote batch of cell changes to a sheet's plain celldata array in
13
+ * place. Keys are `r_c` and unique within a batch.
14
+ *
15
+ * One pass over celldata finds every patched cell (A5): O(entries + changes)
16
+ * instead of a findIndex per changed key. The lookup is by row then column
17
+ * number, so the pass allocates nothing per entry.
18
+ */
19
+ export declare function patchPlainCelldata(celldata: PlainCelldataEntry[], patches: Iterable<CelldataPatch>): void;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,19 @@
1
+ import { IndexeddbPersistence } from 'y-indexeddb';
2
+ import type * as Y from 'yjs';
3
+ /**
4
+ * D3: rebuild the local IndexedDB store from the live document.
5
+ *
6
+ * Destroys `current`, deletes the database, then attaches a new provider to
7
+ * the SAME live Y.Doc, so y-indexeddb writes the full current state as one
8
+ * fresh row. `onCreated` runs right after the new provider is constructed,
9
+ * before it has synced, so the caller can track it and attach listeners. The Y.Doc is never replaced or cleared. Unlike delete-and-reopen
10
+ * (heal), edits that exist only in the live doc are kept.
11
+ *
12
+ * The wait for the new provider is bounded: `whenSynced` never rejects, and a
13
+ * provider destroyed before its first read (a bootstrap restarting meanwhile)
14
+ * never syncs. It rejects on `timeoutMs` or once `isCurrent()` is false.
15
+ */
16
+ export declare function rebuildIndexedDbStore(dsheetId: string, doc: Y.Doc, current: IndexeddbPersistence | null, onCreated?: (provider: IndexeddbPersistence) => void, syncOptions?: {
17
+ timeoutMs?: number;
18
+ isCurrent?: () => boolean;
19
+ }): Promise<IndexeddbPersistence>;
@@ -11,9 +11,54 @@ export type RemoteApplyGuardRefs = {
11
11
  remoteApplyDepthRef: MutableRefObject<number>;
12
12
  remoteUpdateRef: MutableRefObject<boolean>;
13
13
  };
14
+ /**
15
+ * True when an engine → Yjs echo hook (calcChain, conditional formats, ...)
16
+ * must stay quiet: a remote apply is running, or a structural remount is
17
+ * pending. While one is pending the engine still has the grid from before the
18
+ * peer's insert or delete, so a diff of its metadata against Yjs would write
19
+ * the old coordinates over the peer's (and delete the peer's calcChain
20
+ * entries). Cell commits are not echo hooks and are not held back by this.
21
+ */
22
+ export declare function shouldSkipEchoWrite(refs: {
23
+ remoteUpdateRef: MutableRefObject<boolean>;
24
+ structuralRemountPendingRef?: MutableRefObject<boolean>;
25
+ }): boolean;
14
26
  export declare function syncRemoteUpdateFlag(refs: RemoteApplyGuardRefs): void;
15
27
  export declare function beginRemoteApply(refs: RemoteApplyGuardRefs): void;
16
28
  export declare function endRemoteApply(refs: RemoteApplyGuardRefs): void;
17
29
  export declare function runUnderRemoteApply(refs: RemoteApplyGuardRefs, fn: () => void): void;
30
+ export type StructuralRemountRefs = RemoteApplyGuardRefs & {
31
+ pendingRef: MutableRefObject<boolean>;
32
+ timerRef: MutableRefObject<number | null>;
33
+ };
34
+ /**
35
+ * Debounced structural remount under the remote-apply guard.
36
+ *
37
+ * Each pending remount owns exactly one begin and one end (B1): the guard is
38
+ * begun when the remount becomes pending (no remount was pending before),
39
+ * held while the timer is pending or deferred by a cell edit (B3), and ended
40
+ * after paint once `run` has finished. Events that land while one is pending
41
+ * only re-arm its timer.
42
+ *
43
+ * While `isEditing()` is true at fire time the timer re-arms (250 ms) and the
44
+ * pending flag and guard depth are kept; the remount runs once the edit ends.
45
+ * The typed cell itself is still written: the commit-time write and the cell
46
+ * write in afterUpdateCell do not check the guard.
47
+ */
48
+ export declare function scheduleStructuralRemount(refs: StructuralRemountRefs, run: () => void, delayMs?: number, isEditing?: () => boolean): void;
49
+ /** Re-arm interval while a cell edit blocks the remount. */
50
+ export declare const STRUCTURAL_REMOUNT_EDIT_RETRY_MS = 250;
51
+ /**
52
+ * Drop a pending structural remount (observer teardown) and release the
53
+ * guard begun for it.
54
+ */
55
+ export declare function cancelStructuralRemount(refs: StructuralRemountRefs): void;
56
+ /**
57
+ * Run `fn` under the guard and keep it until paint (a remount follows). If
58
+ * `fn` throws, the guard is ended at once and the error is rethrown: a begin
59
+ * with no matching end leaves the depth above zero, and `handleChange` then
60
+ * drops every local write until reload.
61
+ */
62
+ export declare function runRemoteApplyUntilPaint<T>(refs: RemoteApplyGuardRefs, fn: () => T): T;
18
63
  /** End after Workbook remount hooks have flushed (2 rAF ≈ one paint). */
19
64
  export declare function endRemoteApplyAfterPaint(refs: RemoteApplyGuardRefs): void;
@@ -0,0 +1 @@
1
+ export {};
@@ -2,19 +2,29 @@ import { WorkbookInstance } from '../../sheet-engine/react';
2
2
  import { SheetChangePath } from './update-ydoc';
3
3
 
4
4
  import * as Y from 'yjs';
5
+ export { readYdocSheetField } from './update-ydoc';
6
+ /** Sheet fields the per-feature Yjs hooks sync. */
7
+ export declare const SYNCED_SHEET_FIELDS: readonly ["calcChain", "config", "dataVerification", "luckysheet_conditionformat_save", "hyperlink", "filter", "filter_select", "liveQueryList", "dataBlockCalcFunction"];
5
8
  type SyncContext = {
6
9
  currentSheetId: string;
10
+ /** Only `field` is populated: the value stored in Yjs. */
7
11
  oldSheet: Record<string, any>;
12
+ /** The engine's current value of `field` for the active sheet. */
13
+ newValue: any;
14
+ /** The engine's active sheet, for checks against its cells. */
15
+ engineSheet: Record<string, any> | undefined;
8
16
  ydoc: Y.Doc;
9
17
  };
10
18
  /**
11
- * Build a stable sync context from current editor sheet and Yjs snapshot.
12
- * This keeps all field-level sync handlers aligned on the same baseline.
19
+ * Old (Yjs) and new (engine) value of one field of the active sheet (A3).
20
+ * Reads only that field: no whole-workbook conversion, and no getSheet(),
21
+ * which rebuilds celldata from the dense grid.
13
22
  */
14
- export declare const getSheetYdocSyncContext: ({ sheetEditorRef, ydocRef, dsheetId, }: {
23
+ export declare const getSheetYdocSyncContext: ({ sheetEditorRef, ydocRef, dsheetId, field, }: {
15
24
  sheetEditorRef: React.RefObject<WorkbookInstance | null>;
16
25
  ydocRef: React.RefObject<Y.Doc | null>;
17
26
  dsheetId: string;
27
+ field: string;
18
28
  }) => SyncContext | null;
19
29
  export declare const buildMapFieldChanges: ({ sheetId, fieldPath, oldData, newData, }: {
20
30
  sheetId: string;
@@ -28,4 +38,3 @@ export declare const applyYdocSheetChanges: ({ ydoc, dsheetId, changes, handleCo
28
38
  changes: SheetChangePath[];
29
39
  handleContentPortal?: any;
30
40
  }) => void;
31
- export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -17,5 +17,13 @@ export type SheetChangePath = {
17
17
  export declare const updateYdocSheetData: (ydoc: Y.Doc | null, dsheetId: string, changes: SheetChangePath[], handleContentPortal: any, onCellFormatRangesCommitted?: (commits: Array<{
18
18
  sheetId: string;
19
19
  ranges: CellFormatRange[];
20
- }>) => void) => void;
20
+ }>) => void,
21
+ /** Transaction origin; omit for user edits. */
22
+ origin?: unknown) => void;
21
23
  export declare function ySheetArrayToPlain(sheetArray: Y.Array<Y.Map>): Sheet[];
24
+ /**
25
+ * One field of one sheet, with the same value ySheetArrayToPlain gives for
26
+ * it, without converting the rest of the workbook (A3). Shared, read-only
27
+ * semantics as ySheetArrayToPlain.
28
+ */
29
+ export declare function readYdocSheetField(ydoc: Y.Doc, dsheetId: string, sheetId: string, field: string): any;