@casualoffice/sheets 0.9.0 → 0.10.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/api-BI2VLYQ6.d.cts +62 -0
- package/dist/api-BI2VLYQ6.d.ts +62 -0
- package/dist/chrome.cjs +2569 -0
- package/dist/chrome.cjs.map +1 -0
- package/dist/chrome.d.cts +96 -0
- package/dist/chrome.d.ts +96 -0
- package/dist/chrome.js +2556 -0
- package/dist/chrome.js.map +1 -0
- package/dist/collab.cjs +770 -0
- package/dist/collab.cjs.map +1 -0
- package/dist/collab.d.cts +248 -0
- package/dist/collab.d.ts +248 -0
- package/dist/collab.js +737 -0
- package/dist/collab.js.map +1 -0
- package/dist/embed/embed-runtime.js +128 -128
- package/dist/embed.cjs +166 -0
- package/dist/embed.cjs.map +1 -1
- package/dist/embed.d.cts +78 -3
- package/dist/embed.d.ts +78 -3
- package/dist/embed.js +166 -0
- package/dist/embed.js.map +1 -1
- package/dist/index.cjs +262 -165
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -3
- package/dist/index.d.ts +4 -3
- package/dist/index.js +271 -168
- package/dist/index.js.map +1 -1
- package/dist/{protocol--KyBQUjU.d.cts → protocol-Cq4Cdoyi.d.cts} +19 -2
- package/dist/{protocol-cEzy7S0i.d.ts → protocol-DqDaG2yG.d.ts} +19 -2
- package/dist/sheets.cjs +102 -16
- package/dist/sheets.cjs.map +1 -1
- package/dist/sheets.d.cts +49 -63
- package/dist/sheets.d.ts +49 -63
- package/dist/sheets.js +111 -19
- package/dist/sheets.js.map +1 -1
- package/package.json +28 -3
- package/src/chrome/AutoSumPicker.tsx +176 -0
- package/src/chrome/BordersPicker.tsx +171 -0
- package/src/chrome/ChromeBottom.tsx +18 -0
- package/src/chrome/ChromeTop.tsx +21 -0
- package/src/chrome/ColorPicker.tsx +220 -0
- package/src/chrome/FindReplace.tsx +370 -0
- package/src/chrome/FormulaBar.tsx +378 -0
- package/src/chrome/Icon.tsx +43 -0
- package/src/chrome/MenuBar.tsx +336 -0
- package/src/chrome/NameBox.tsx +347 -0
- package/src/chrome/SheetTabs.tsx +346 -0
- package/src/chrome/StatusBar.tsx +232 -0
- package/src/chrome/Toolbar.tsx +401 -0
- package/src/chrome/fonts.ts +42 -0
- package/src/chrome/index.ts +24 -0
- package/src/collab/attachCollab.ts +151 -0
- package/src/collab/bridge-helpers.ts +97 -0
- package/src/collab/bridge.ts +885 -0
- package/src/collab/bridge.unit.test.ts +160 -0
- package/src/collab/index.ts +38 -0
- package/src/collab/replay-retry.ts +137 -0
- package/src/collab/replay-retry.unit.test.ts +223 -0
- package/src/collab/ws-url.ts +20 -0
- package/src/collab/ws-url.unit.test.ts +35 -0
- package/src/embed/EmbedHostTransport.ts +16 -1
- package/src/embed/EmbedTransport.ts +16 -0
- package/src/embed/EmbedTransport.unit.test.ts +88 -2
- package/src/embed/index.ts +7 -0
- package/src/embed/protocol.ts +34 -0
- package/src/embed-runtime/index.tsx +20 -0
- package/src/sheets/CasualSheets.tsx +204 -33
package/dist/collab.d.ts
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import * as Y from 'yjs';
|
|
2
|
+
import { HocuspocusProvider } from '@hocuspocus/provider';
|
|
3
|
+
import { IWorkbookData } from '@univerjs/core';
|
|
4
|
+
import { FUniver } from '@univerjs/core/facade';
|
|
5
|
+
import { C as CasualSheetsAPI } from './api-BI2VLYQ6.js';
|
|
6
|
+
|
|
7
|
+
type ReplayClassification = 'transient' | 'permanent';
|
|
8
|
+
interface ReplayFailureRecord {
|
|
9
|
+
/** Mutation id (e.g. 'sheet.mutation.set-range-values'). */
|
|
10
|
+
id: string;
|
|
11
|
+
/** Mutation params at time of failure (unfiltered; may be large). */
|
|
12
|
+
params: unknown;
|
|
13
|
+
/** Last error's message (string-coerced for log/UI safety). */
|
|
14
|
+
lastError: string;
|
|
15
|
+
/** Total attempts made (1 = first try, no retries). */
|
|
16
|
+
attempts: number;
|
|
17
|
+
/** ms-since-epoch of the FIRST failure for this record. */
|
|
18
|
+
firstFailedAt: number;
|
|
19
|
+
/** ms-since-epoch of the most recent failure for this record. */
|
|
20
|
+
lastFailedAt: number;
|
|
21
|
+
/** Whether the classifier called this transient or permanent. */
|
|
22
|
+
classification: ReplayClassification;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Decide whether a replay error is worth retrying.
|
|
26
|
+
*
|
|
27
|
+
* Transient class: dynamic-import / chunk-load failures. These come
|
|
28
|
+
* from the lazy-plugin gate fetching a webpack chunk over the
|
|
29
|
+
* network; a flap on the user's connection drops the fetch and the
|
|
30
|
+
* lazy `import()` rejects with one of several distinct shapes:
|
|
31
|
+
*
|
|
32
|
+
* - Webpack 5: `ChunkLoadError` with name === 'ChunkLoadError'
|
|
33
|
+
* - Webpack 4 / vite: 'Loading chunk N failed.'
|
|
34
|
+
* - Vite / native ESM: 'Failed to fetch dynamically imported module'
|
|
35
|
+
* - Native fetch under offline: 'NetworkError when attempting to fetch'
|
|
36
|
+
*
|
|
37
|
+
* All four resolve on retry once the network recovers. Everything
|
|
38
|
+
* else (executeCommand rejections from bad params, missing handlers,
|
|
39
|
+
* out-of-bounds ranges) is permanent — retrying just burns the same
|
|
40
|
+
* stack trace N more times.
|
|
41
|
+
*
|
|
42
|
+
* Conservative bias: when in doubt, classify permanent. A false
|
|
43
|
+
* negative (transient → permanent) costs us a dead-letter entry the
|
|
44
|
+
* user can re-trigger by reloading; a false positive (permanent →
|
|
45
|
+
* transient) wastes 4 s of retries on a known-broken mutation.
|
|
46
|
+
*/
|
|
47
|
+
declare function classifyReplayError(err: unknown): ReplayClassification;
|
|
48
|
+
|
|
49
|
+
type Awareness = {
|
|
50
|
+
getStates(): Map<number, any>;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Allowlist of mutation ids we sync. Listed explicitly to keep
|
|
54
|
+
* undocumented / version-volatile mutations out of the log — anything
|
|
55
|
+
* not here just stays local. Easier to add new ids than to debug a
|
|
56
|
+
* silent corruption from a mutation that secretly references local
|
|
57
|
+
* state (selections, render skeletons, etc.).
|
|
58
|
+
*/
|
|
59
|
+
declare const SYNCED_MUTATIONS: ReadonlySet<string>;
|
|
60
|
+
/** Mutation ids for which the bridge captures undo params before the
|
|
61
|
+
* redo runs. Used by the HistoryPanel's revert action. Restricted to
|
|
62
|
+
* cell-level mutations because the existing Univer factories cover
|
|
63
|
+
* them and they're the dominant case for "undo my edit"; structural
|
|
64
|
+
* ops (insert-row / move-range / sort) need their own factories and
|
|
65
|
+
* are out of scope for v1 revert.
|
|
66
|
+
*/
|
|
67
|
+
declare const REVERTABLE_MUTATIONS: ReadonlySet<string>;
|
|
68
|
+
type BridgeHandle = {
|
|
69
|
+
/** Underlying Yjs document — exposed so tests / devtools can introspect. */
|
|
70
|
+
doc: Y.Doc;
|
|
71
|
+
/** Stop listening and detach. */
|
|
72
|
+
dispose: () => void;
|
|
73
|
+
/**
|
|
74
|
+
* How many remote mutations have thrown during replay since the
|
|
75
|
+
* bridge started. Each one is a candidate divergence — the local
|
|
76
|
+
* state didn't accept the peer's change, so the two state vectors
|
|
77
|
+
* are now off by at least that mutation. The CollabDriver
|
|
78
|
+
* subscribes (see `subscribeReplayFailures`) so the indicator can
|
|
79
|
+
* warn the user before they discover it the hard way.
|
|
80
|
+
*/
|
|
81
|
+
getReplayFailures: () => number;
|
|
82
|
+
/**
|
|
83
|
+
* Subscribe to replay-failure count changes. Fires after every
|
|
84
|
+
* increment with the new total. Returns a teardown that unhooks
|
|
85
|
+
* the subscriber. No initial-value fire — caller can read
|
|
86
|
+
* `getReplayFailures()` once at subscribe time if they need it.
|
|
87
|
+
*/
|
|
88
|
+
subscribeReplayFailures: (cb: (count: number) => void) => () => void;
|
|
89
|
+
/**
|
|
90
|
+
* Snapshot of the dead-letter ring buffer — mutations that
|
|
91
|
+
* exhausted retries (transient class) or failed immediately
|
|
92
|
+
* (permanent class). Capped at DEAD_LETTER_CAP entries; oldest
|
|
93
|
+
* evicts on overflow. UI consumes this to render the per-failure
|
|
94
|
+
* detail panel.
|
|
95
|
+
*/
|
|
96
|
+
getReplayDeadLetter: () => readonly ReplayFailureRecord[];
|
|
97
|
+
/**
|
|
98
|
+
* Subscribe to dead-letter changes. Fires after every push with a
|
|
99
|
+
* fresh array (reference change — React state updates see it).
|
|
100
|
+
* Returns a teardown.
|
|
101
|
+
*/
|
|
102
|
+
subscribeReplayDeadLetter: (cb: (entries: readonly ReplayFailureRecord[]) => void) => () => void;
|
|
103
|
+
};
|
|
104
|
+
type BridgeOptions = {
|
|
105
|
+
/**
|
|
106
|
+
* `view` clients only RECEIVE remote updates — local mutations don't
|
|
107
|
+
* append to the log, so peers never see them. Client-side gate only;
|
|
108
|
+
* a determined user can run the bridge in write mode by editing the
|
|
109
|
+
* URL. Server-side enforcement is a follow-up hardening pass.
|
|
110
|
+
*/
|
|
111
|
+
role?: 'view' | 'write';
|
|
112
|
+
/**
|
|
113
|
+
* Provider's Yjs awareness. Used (a) to determine which peer is the
|
|
114
|
+
* designated compactor (lowest known clientId — deterministic and
|
|
115
|
+
* race-free) and (b) so view-only clients don't try to compact. If
|
|
116
|
+
* omitted, compaction is disabled.
|
|
117
|
+
*/
|
|
118
|
+
awareness?: Awareness;
|
|
119
|
+
/**
|
|
120
|
+
* Hand a fresh workbook snapshot to the host when a compaction
|
|
121
|
+
* record arrives from a peer. The bridge can't call
|
|
122
|
+
* `replaceWorkbook` directly (it lives in React state); the host
|
|
123
|
+
* (`CollabDriver`) wires this through.
|
|
124
|
+
*
|
|
125
|
+
* MAY return a promise. Replay of subsequent op-log entries is
|
|
126
|
+
* paused until the promise resolves — without that, mutations
|
|
127
|
+
* land on the OLD unit before Univer's async unit-swap completes,
|
|
128
|
+
* which silently forks state on late joiners.
|
|
129
|
+
*/
|
|
130
|
+
onSnapshotReceived?: (wb: IWorkbookData) => void | Promise<void>;
|
|
131
|
+
};
|
|
132
|
+
declare function startBridge(api: FUniver, doc: Y.Doc, opts?: BridgeOptions): BridgeHandle;
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* attachCollab — opt-in real-time co-editing for `<CasualSheets>`.
|
|
136
|
+
*
|
|
137
|
+
* The editor ships **collab-unaware**: it boots, edits, and persists with no
|
|
138
|
+
* knowledge of rooms or peers. A host that wants co-editing calls
|
|
139
|
+
* `attachCollab(api, { room, server })` once after `onReady` and gets back a
|
|
140
|
+
* detach handle. That's the entire public surface — everything else (presence
|
|
141
|
+
* UI, password prompts, room preflight, reconnect banners) is the host's to
|
|
142
|
+
* build on top of the returned `provider` / `doc`.
|
|
143
|
+
*
|
|
144
|
+
* Transport: Yjs + Hocuspocus, exactly as the reference host (`apps/web`) uses
|
|
145
|
+
* it. The non-negotiable Univer hooks live in `./bridge` — it subscribes to
|
|
146
|
+
* `ICommandService.onMutationExecutedForCollab`, applies remote mutations with
|
|
147
|
+
* `IExecutionOptions.fromCollab` (echo-loop prevention), and guards
|
|
148
|
+
* `params.__splitChunk__`.
|
|
149
|
+
*
|
|
150
|
+
* Persistence note: Yjs/Hocuspocus is the **realtime transport only**. The
|
|
151
|
+
* authoritative document is still saved by the host via WOPI / its own backend
|
|
152
|
+
* (the save/exit event contract) — collab does not turn the SDK into a store.
|
|
153
|
+
*/
|
|
154
|
+
|
|
155
|
+
/** Either the SDK's imperative API (`onReady`) or the bare FUniver facade.
|
|
156
|
+
* Collab only needs the facade, so a host that holds the raw FUniver (the
|
|
157
|
+
* reference app does) can attach without constructing a CasualSheetsAPI. */
|
|
158
|
+
type CollabAttachable = CasualSheetsAPI | FUniver;
|
|
159
|
+
/** `write` peers broadcast their edits; `view` peers only receive. The
|
|
160
|
+
* client-side gate is belt-and-braces — real enforcement is the server's
|
|
161
|
+
* `role` check on the WS upgrade. */
|
|
162
|
+
type CollabRole = 'view' | 'write';
|
|
163
|
+
/** Coarse connection state mapped from Hocuspocus's provider status. */
|
|
164
|
+
type CollabConnectionStatus = 'connecting' | 'live' | 'offline';
|
|
165
|
+
interface AttachCollabOptions {
|
|
166
|
+
/** Room / document id. Becomes the Hocuspocus document name. */
|
|
167
|
+
room: string;
|
|
168
|
+
/** Base WebSocket URL of the collab server, e.g. `wss://host/yjs`. */
|
|
169
|
+
server: string;
|
|
170
|
+
/** Room password, if the server gates the room. Sent on the WS URL as
|
|
171
|
+
* `p=…` so the upgrade handler can validate before the protocol handshake. */
|
|
172
|
+
password?: string;
|
|
173
|
+
/** Auth token for the Hocuspocus handshake. The provider only sends its
|
|
174
|
+
* auth submessage when this is truthy; servers with an `onAuthenticate`
|
|
175
|
+
* hook keep the connection queued without it. Defaults to `'anon'` (the
|
|
176
|
+
* reference server's hook only reads the `role` query param). */
|
|
177
|
+
token?: string;
|
|
178
|
+
/** `view` joins read-only. Defaults to `'write'`. */
|
|
179
|
+
role?: CollabRole;
|
|
180
|
+
/**
|
|
181
|
+
* Called when a peer's compaction snapshot arrives — the host swaps the
|
|
182
|
+
* workbook (typically `api.loadSnapshot(wb)`). MAY return a promise; the
|
|
183
|
+
* bridge pauses op-log replay until it resolves so later mutations don't
|
|
184
|
+
* land on the pre-swap unit.
|
|
185
|
+
*/
|
|
186
|
+
onSnapshot?: (wb: IWorkbookData) => void | Promise<void>;
|
|
187
|
+
/** Connection-status transitions — drive a status pill / offline banner. */
|
|
188
|
+
onStatus?: (status: CollabConnectionStatus) => void;
|
|
189
|
+
}
|
|
190
|
+
interface CollabHandle {
|
|
191
|
+
/** The underlying Yjs document — introspection / devtools / extra maps. */
|
|
192
|
+
readonly doc: Y.Doc;
|
|
193
|
+
/** The Hocuspocus provider — `awareness` for presence, `on('status')`, etc. */
|
|
194
|
+
readonly provider: HocuspocusProvider;
|
|
195
|
+
/** The mutation bridge — replay-failure diagnostics live here. */
|
|
196
|
+
readonly bridge: BridgeHandle;
|
|
197
|
+
/** Last known connection status. */
|
|
198
|
+
status(): CollabConnectionStatus;
|
|
199
|
+
/** Detach: tear down bridge + provider + doc. Idempotent. */
|
|
200
|
+
detach(): void;
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Attach real-time collab to a live editor. Call once after `onReady`.
|
|
204
|
+
* Returns a {@link CollabHandle}; call `.detach()` to leave the room
|
|
205
|
+
* (and always before the editor unmounts).
|
|
206
|
+
*/
|
|
207
|
+
declare function attachCollab(api: CollabAttachable, opts: AttachCollabOptions): CollabHandle;
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Pure helpers extracted from bridge.ts so they're testable without
|
|
211
|
+
* pulling Univer / Yjs / Vite-only globals into a Node test runner.
|
|
212
|
+
*
|
|
213
|
+
* Anything stateless and Univer-free that bridge.ts needs should live
|
|
214
|
+
* here. Stateful bridge logic (Yjs observers, command service hooks,
|
|
215
|
+
* compaction) stays in bridge.ts itself.
|
|
216
|
+
*/
|
|
217
|
+
/**
|
|
218
|
+
* Substitute `localUnitId` for every `unitId` string property anywhere
|
|
219
|
+
* in `value` — including nested objects and arrays — and return a
|
|
220
|
+
* structurally cloned copy. Returns the input by reference when no
|
|
221
|
+
* change is needed, so callers can identity-compare before re-encoding.
|
|
222
|
+
*
|
|
223
|
+
* Walks plain objects and arrays only. Class instances are left alone
|
|
224
|
+
* because Univer mutation params are required to be JSON-friendly.
|
|
225
|
+
*/
|
|
226
|
+
/**
|
|
227
|
+
* Drawing mutations (sheets-drawing + drawing plugins) carry an
|
|
228
|
+
* `op` field that's a json1 patch — a positional array whose first
|
|
229
|
+
* element is the unitId. `deepRewriteUnitId` only rewrites the
|
|
230
|
+
* `unitId` KEY in objects; the json1 path is a bare array of strings
|
|
231
|
+
* so the unitId at position 0 stays as the OWNER's id and the apply
|
|
232
|
+
* fails on the joiner with no useful message ("Error" at
|
|
233
|
+
* json1.type.apply).
|
|
234
|
+
*
|
|
235
|
+
* This walks the op shape — single JSONOp (array of mixed strings +
|
|
236
|
+
* numbers + a final mutation component object) OR JSONOpList (array
|
|
237
|
+
* of JSONOps) — and substitutes the leading unitId where it matches.
|
|
238
|
+
* Returns a fresh structure on change so peers can identity-compare.
|
|
239
|
+
*
|
|
240
|
+
* Scope deliberately narrow: only checks element [0] of each op
|
|
241
|
+
* (where the unitId always lives in our path schema). A deeper
|
|
242
|
+
* path-rewrite would invent semantics the Univer source doesn't
|
|
243
|
+
* document.
|
|
244
|
+
*/
|
|
245
|
+
declare function rewriteJson1OpPathUnitId(op: unknown, oldUnitId: string, newUnitId: string): unknown;
|
|
246
|
+
declare function deepRewriteUnitId(value: unknown, localUnitId: string): unknown;
|
|
247
|
+
|
|
248
|
+
export { type AttachCollabOptions, type BridgeHandle, type BridgeOptions, type CollabAttachable, type CollabConnectionStatus, type CollabHandle, type CollabRole, REVERTABLE_MUTATIONS, type ReplayClassification, type ReplayFailureRecord, SYNCED_MUTATIONS, attachCollab, classifyReplayError, deepRewriteUnitId, rewriteJson1OpPathUnitId, startBridge };
|