@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.
Files changed (67) hide show
  1. package/dist/api-BI2VLYQ6.d.cts +62 -0
  2. package/dist/api-BI2VLYQ6.d.ts +62 -0
  3. package/dist/chrome.cjs +2569 -0
  4. package/dist/chrome.cjs.map +1 -0
  5. package/dist/chrome.d.cts +96 -0
  6. package/dist/chrome.d.ts +96 -0
  7. package/dist/chrome.js +2556 -0
  8. package/dist/chrome.js.map +1 -0
  9. package/dist/collab.cjs +770 -0
  10. package/dist/collab.cjs.map +1 -0
  11. package/dist/collab.d.cts +248 -0
  12. package/dist/collab.d.ts +248 -0
  13. package/dist/collab.js +737 -0
  14. package/dist/collab.js.map +1 -0
  15. package/dist/embed/embed-runtime.js +128 -128
  16. package/dist/embed.cjs +166 -0
  17. package/dist/embed.cjs.map +1 -1
  18. package/dist/embed.d.cts +78 -3
  19. package/dist/embed.d.ts +78 -3
  20. package/dist/embed.js +166 -0
  21. package/dist/embed.js.map +1 -1
  22. package/dist/index.cjs +262 -165
  23. package/dist/index.cjs.map +1 -1
  24. package/dist/index.d.cts +4 -3
  25. package/dist/index.d.ts +4 -3
  26. package/dist/index.js +271 -168
  27. package/dist/index.js.map +1 -1
  28. package/dist/{protocol--KyBQUjU.d.cts → protocol-Cq4Cdoyi.d.cts} +19 -2
  29. package/dist/{protocol-cEzy7S0i.d.ts → protocol-DqDaG2yG.d.ts} +19 -2
  30. package/dist/sheets.cjs +102 -16
  31. package/dist/sheets.cjs.map +1 -1
  32. package/dist/sheets.d.cts +49 -63
  33. package/dist/sheets.d.ts +49 -63
  34. package/dist/sheets.js +111 -19
  35. package/dist/sheets.js.map +1 -1
  36. package/package.json +28 -3
  37. package/src/chrome/AutoSumPicker.tsx +176 -0
  38. package/src/chrome/BordersPicker.tsx +171 -0
  39. package/src/chrome/ChromeBottom.tsx +18 -0
  40. package/src/chrome/ChromeTop.tsx +21 -0
  41. package/src/chrome/ColorPicker.tsx +220 -0
  42. package/src/chrome/FindReplace.tsx +370 -0
  43. package/src/chrome/FormulaBar.tsx +378 -0
  44. package/src/chrome/Icon.tsx +43 -0
  45. package/src/chrome/MenuBar.tsx +336 -0
  46. package/src/chrome/NameBox.tsx +347 -0
  47. package/src/chrome/SheetTabs.tsx +346 -0
  48. package/src/chrome/StatusBar.tsx +232 -0
  49. package/src/chrome/Toolbar.tsx +401 -0
  50. package/src/chrome/fonts.ts +42 -0
  51. package/src/chrome/index.ts +24 -0
  52. package/src/collab/attachCollab.ts +151 -0
  53. package/src/collab/bridge-helpers.ts +97 -0
  54. package/src/collab/bridge.ts +885 -0
  55. package/src/collab/bridge.unit.test.ts +160 -0
  56. package/src/collab/index.ts +38 -0
  57. package/src/collab/replay-retry.ts +137 -0
  58. package/src/collab/replay-retry.unit.test.ts +223 -0
  59. package/src/collab/ws-url.ts +20 -0
  60. package/src/collab/ws-url.unit.test.ts +35 -0
  61. package/src/embed/EmbedHostTransport.ts +16 -1
  62. package/src/embed/EmbedTransport.ts +16 -0
  63. package/src/embed/EmbedTransport.unit.test.ts +88 -2
  64. package/src/embed/index.ts +7 -0
  65. package/src/embed/protocol.ts +34 -0
  66. package/src/embed-runtime/index.tsx +20 -0
  67. package/src/sheets/CasualSheets.tsx +204 -33
@@ -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 };