@casualoffice/sheets 0.9.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/LICENSE +200 -0
- package/dist/embed/embed-runtime.js +537 -0
- package/dist/embed/embed.html +29 -0
- package/dist/embed/parser.worker.js +48474 -0
- package/dist/embed.cjs +225 -0
- package/dist/embed.cjs.map +1 -0
- package/dist/embed.d.cts +100 -0
- package/dist/embed.d.ts +100 -0
- package/dist/embed.js +204 -0
- package/dist/embed.js.map +1 -0
- package/dist/index.cjs +1549 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +9 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +1530 -0
- package/dist/index.js.map +1 -0
- package/dist/parser.worker.cjs +48469 -0
- package/dist/parser.worker.cjs.map +1 -0
- package/dist/parser.worker.js +48474 -0
- package/dist/parser.worker.js.map +1 -0
- package/dist/protocol--KyBQUjU.d.cts +171 -0
- package/dist/protocol-cEzy7S0i.d.ts +171 -0
- package/dist/sheets.cjs +677 -0
- package/dist/sheets.cjs.map +1 -0
- package/dist/sheets.d.cts +177 -0
- package/dist/sheets.d.ts +177 -0
- package/dist/sheets.js +658 -0
- package/dist/sheets.js.map +1 -0
- package/dist/signing.cjs +706 -0
- package/dist/signing.cjs.map +1 -0
- package/dist/signing.d.cts +141 -0
- package/dist/signing.d.ts +141 -0
- package/dist/signing.js +683 -0
- package/dist/signing.js.map +1 -0
- package/dist/styles.cjs +10 -0
- package/dist/styles.cjs.map +1 -0
- package/dist/styles.d.cts +2 -0
- package/dist/styles.d.ts +2 -0
- package/dist/styles.js +8 -0
- package/dist/styles.js.map +1 -0
- package/dist/types-s_O0u6Cg.d.cts +90 -0
- package/dist/types-s_O0u6Cg.d.ts +90 -0
- package/dist/univer.cjs +220 -0
- package/dist/univer.cjs.map +1 -0
- package/dist/univer.d.cts +60 -0
- package/dist/univer.d.ts +60 -0
- package/dist/univer.js +187 -0
- package/dist/univer.js.map +1 -0
- package/dist/xlsx.cjs +3388 -0
- package/dist/xlsx.cjs.map +1 -0
- package/dist/xlsx.d.cts +383 -0
- package/dist/xlsx.d.ts +383 -0
- package/dist/xlsx.js +3383 -0
- package/dist/xlsx.js.map +1 -0
- package/package.json +293 -0
- package/src/embed/EmbedHostTransport.ts +226 -0
- package/src/embed/EmbedTransport.ts +323 -0
- package/src/embed/EmbedTransport.unit.test.ts +161 -0
- package/src/embed/index.ts +40 -0
- package/src/embed/protocol.ts +258 -0
- package/src/embed-runtime/embed.html +29 -0
- package/src/embed-runtime/index.tsx +440 -0
- package/src/index.ts +16 -0
- package/src/sheets/CasualSheets.tsx +319 -0
- package/src/sheets/CasualSheetsIframe.tsx +220 -0
- package/src/sheets/api.ts +108 -0
- package/src/sheets/index.ts +11 -0
- package/src/signing/SigningPane.tsx +374 -0
- package/src/signing/SigningProvider.tsx +126 -0
- package/src/signing/captures.tsx +316 -0
- package/src/signing/controller.ts +151 -0
- package/src/signing/controller.unit.test.ts +133 -0
- package/src/signing/index.ts +44 -0
- package/src/signing/types.ts +89 -0
- package/src/styles.ts +16 -0
- package/src/univer/index.ts +17 -0
- package/src/univer/lazy-plugins.ts +280 -0
- package/src/xlsx/_perf.ts +14 -0
- package/src/xlsx/_snapshot-constants.ts +15 -0
- package/src/xlsx/comments-resource.ts +209 -0
- package/src/xlsx/constants.ts +9 -0
- package/src/xlsx/data-validation-resource.ts +219 -0
- package/src/xlsx/import.ts +35 -0
- package/src/xlsx/index.ts +40 -0
- package/src/xlsx/page-setup-resource.ts +205 -0
- package/src/xlsx/parse-impl.ts +418 -0
- package/src/xlsx/parse-in-worker.ts +82 -0
- package/src/xlsx/parser.worker.ts +39 -0
- package/src/xlsx/passthrough-resource.ts +175 -0
- package/src/xlsx/pivot-passthrough.ts +359 -0
- package/src/xlsx/style-mapping.ts +171 -0
- package/src/xlsx/tables-resource.ts +211 -0
|
@@ -0,0 +1,440 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* embed-runtime — the in-iframe entry point for the sheet SDK's iframe
|
|
3
|
+
* delivery mode. Sheet sibling of `@casualoffice/docs`'s
|
|
4
|
+
* `embed-runtime`; see doc 16 in the parent repo.
|
|
5
|
+
*
|
|
6
|
+
* Lifecycle:
|
|
7
|
+
*
|
|
8
|
+
* 1. Wrapper renders <iframe src="${embedBasePath}/embed.html?...">.
|
|
9
|
+
* 2. iframe loads embed.html which imports + runs `mountEmbedded()`
|
|
10
|
+
* from this module's compiled bundle.
|
|
11
|
+
* 3. `mountEmbedded` parses URL params, opens an EmbedTransport,
|
|
12
|
+
* waits for the host's `casual.hello`, issues a load.request
|
|
13
|
+
* for the docId, parses the returned xlsx bytes via
|
|
14
|
+
* `xlsxToWorkbookData` → IWorkbookData, mounts <CasualSheets>
|
|
15
|
+
* with that snapshot into #casual-embed-root.
|
|
16
|
+
* 4. Selection / autosave / signing events all bubble out via the
|
|
17
|
+
* same transport.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { useEffect, useState } from 'react';
|
|
21
|
+
import { createRoot } from 'react-dom/client';
|
|
22
|
+
|
|
23
|
+
import { EmbedTransport } from '../embed/EmbedTransport';
|
|
24
|
+
import type { CasualApp } from '../embed/protocol';
|
|
25
|
+
import { CasualSheets } from '../sheets/CasualSheets';
|
|
26
|
+
import { xlsxToWorkbookData } from '../xlsx';
|
|
27
|
+
import type { IWorkbookData } from '@univerjs/core';
|
|
28
|
+
|
|
29
|
+
interface EmbedUrlConfig {
|
|
30
|
+
app: CasualApp;
|
|
31
|
+
docId: string;
|
|
32
|
+
viewMode: 'preview' | 'editor';
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function parseUrlConfig(search: string): EmbedUrlConfig {
|
|
36
|
+
const params = new URLSearchParams(search);
|
|
37
|
+
const app: CasualApp = params.get('app') === 'docs' ? 'docs' : 'sheet';
|
|
38
|
+
const docId = params.get('docId') ?? '';
|
|
39
|
+
const viewModeParam = params.get('viewMode');
|
|
40
|
+
const viewMode: 'preview' | 'editor' = viewModeParam === 'editor' ? 'editor' : 'preview';
|
|
41
|
+
return { app, docId, viewMode };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface MountEmbeddedOptions {
|
|
45
|
+
root: HTMLElement;
|
|
46
|
+
search?: string;
|
|
47
|
+
hostOrigin?: string;
|
|
48
|
+
identity?: { version: string; commit: string; capabilities?: string[] };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Public entry — called by embed.html. */
|
|
52
|
+
// Side-effect import: pull in Univer's CSS modules so the embed
|
|
53
|
+
// runtime is fully self-contained. Without these, the embed.html has
|
|
54
|
+
// no <link rel="stylesheet"> and Univer's workbench renders unstyled
|
|
55
|
+
// (the canvas mounts at 0×0 size, all chrome divs have no layout).
|
|
56
|
+
// `injectStyle: true` in tsup turns each import into a runtime
|
|
57
|
+
// <style> tag append.
|
|
58
|
+
import '../styles';
|
|
59
|
+
|
|
60
|
+
// Side-effect imports: augment the FUniver facade with the
|
|
61
|
+
// `getActiveWorkbook` / `getActiveSheet` / `getActiveRange` chain
|
|
62
|
+
// the toolbar bridge calls. The sheet SDK ships these on Univer's
|
|
63
|
+
// sheets / sheets-ui packages as facade extensions; without the
|
|
64
|
+
// import the typings are missing and the runtime calls noop.
|
|
65
|
+
import '@univerjs/sheets/facade';
|
|
66
|
+
import '@univerjs/sheets-ui/facade';
|
|
67
|
+
|
|
68
|
+
export function mountEmbedded(opts: MountEmbeddedOptions): void {
|
|
69
|
+
const search = opts.search ?? (typeof window !== 'undefined' ? window.location.search : '');
|
|
70
|
+
const config = parseUrlConfig(search);
|
|
71
|
+
|
|
72
|
+
const hostOrigin =
|
|
73
|
+
opts.hostOrigin ??
|
|
74
|
+
inferHostOrigin() ??
|
|
75
|
+
(typeof window !== 'undefined' ? window.location.origin : '');
|
|
76
|
+
|
|
77
|
+
const identity = opts.identity ?? {
|
|
78
|
+
version: '0.0.0',
|
|
79
|
+
commit: 'unknown',
|
|
80
|
+
capabilities: ['load', 'save', 'selection', 'signing'],
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const transport = new EmbedTransport({
|
|
84
|
+
app: config.app,
|
|
85
|
+
hostOrigin,
|
|
86
|
+
version: identity.version,
|
|
87
|
+
commit: identity.commit,
|
|
88
|
+
capabilities: identity.capabilities ?? ['load', 'save', 'selection', 'signing'],
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
opts.root.setAttribute('data-view-mode', config.viewMode);
|
|
92
|
+
|
|
93
|
+
transport.on({
|
|
94
|
+
onCommandSetViewMode: ({ viewMode }) => {
|
|
95
|
+
opts.root.setAttribute('data-view-mode', viewMode);
|
|
96
|
+
// v0.5.x will toggle CasualSheets's `ui` prop in response;
|
|
97
|
+
// v0.5.0 only updates the data attribute so iframe-side CSS
|
|
98
|
+
// can react.
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
transport.sendHello();
|
|
103
|
+
// Also send `casual.ready` so the host's `onEditorReady` fires without
|
|
104
|
+
// waiting for the host to send `casual.hello` first. The protocol's
|
|
105
|
+
// sendReady-on-receiving-hello path stays as a fallback for hosts that
|
|
106
|
+
// do send hello eagerly; this just kicks off the handshake on the
|
|
107
|
+
// iframe side so we don't deadlock when the host's strategy is to
|
|
108
|
+
// wait for `casual.ready`.
|
|
109
|
+
transport.sendReady();
|
|
110
|
+
|
|
111
|
+
const reactRoot = createRoot(opts.root);
|
|
112
|
+
reactRoot.render(
|
|
113
|
+
<EmbeddedSheets transport={transport} docId={config.docId} initialViewMode={config.viewMode} />,
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Inner React component — handles the async xlsx load + workbook
|
|
118
|
+
* mount. Splitting it out lets useEffect orchestrate the
|
|
119
|
+
* request/response without blocking the React tree at mount. */
|
|
120
|
+
function EmbeddedSheets({
|
|
121
|
+
transport,
|
|
122
|
+
docId,
|
|
123
|
+
initialViewMode,
|
|
124
|
+
}: {
|
|
125
|
+
transport: EmbedTransport;
|
|
126
|
+
docId: string;
|
|
127
|
+
initialViewMode: 'preview' | 'editor';
|
|
128
|
+
}) {
|
|
129
|
+
const [data, setData] = useState<IWorkbookData | null>(null);
|
|
130
|
+
const [errorMsg, setErrorMsg] = useState<string | null>(null);
|
|
131
|
+
|
|
132
|
+
const [viewMode, setViewMode] = useState<'preview' | 'editor'>(initialViewMode);
|
|
133
|
+
// Editor mode flips `header: true` so Univer's formula bar (A1 cell
|
|
134
|
+
// ref, fx button) and menubar render at the top of the iframe —
|
|
135
|
+
// visually distinct from preview's canvas-only surface. `toolbar` and
|
|
136
|
+
// `footer` stay off: Univer's ribbon and sheet-tabs slot resolve
|
|
137
|
+
// services (IRPCChannelService, sheet-drawing) at construction that
|
|
138
|
+
// the SDK doesn't bundle a worker for, and turning them on lights up
|
|
139
|
+
// `[redi]: Cannot find "Kb" registered by any injector` and the
|
|
140
|
+
// canvas never paints. Cells stay editable in editor mode via direct
|
|
141
|
+
// keyboard input on the focused cell.
|
|
142
|
+
const ui =
|
|
143
|
+
viewMode === 'editor'
|
|
144
|
+
? { header: true, toolbar: false, footer: false, contextMenu: true }
|
|
145
|
+
: { header: false, toolbar: false, footer: false, contextMenu: true };
|
|
146
|
+
|
|
147
|
+
useEffect(() => {
|
|
148
|
+
transport.on({
|
|
149
|
+
onCommandSetViewMode: ({ viewMode: next }) => setViewMode(next),
|
|
150
|
+
});
|
|
151
|
+
}, [transport]);
|
|
152
|
+
|
|
153
|
+
useEffect(() => {
|
|
154
|
+
let cancelled = false;
|
|
155
|
+
void (async () => {
|
|
156
|
+
try {
|
|
157
|
+
const resp = await transport.requestLoad(docId);
|
|
158
|
+
if (cancelled) return;
|
|
159
|
+
if (!resp.ok) {
|
|
160
|
+
setErrorMsg(resp.message ?? `load failed: ${resp.code}`);
|
|
161
|
+
transport.sendError({
|
|
162
|
+
code: 'load_failed',
|
|
163
|
+
message: resp.message ?? resp.code,
|
|
164
|
+
});
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
const snapshot = await xlsxToWorkbookData(resp.bytes);
|
|
168
|
+
if (cancelled) return;
|
|
169
|
+
setData(snapshot);
|
|
170
|
+
} catch (err) {
|
|
171
|
+
if (cancelled) return;
|
|
172
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
173
|
+
setErrorMsg(message);
|
|
174
|
+
transport.sendError({ code: 'parse_failed', message });
|
|
175
|
+
}
|
|
176
|
+
})();
|
|
177
|
+
return () => {
|
|
178
|
+
cancelled = true;
|
|
179
|
+
};
|
|
180
|
+
}, [transport, docId]);
|
|
181
|
+
|
|
182
|
+
if (errorMsg) {
|
|
183
|
+
return (
|
|
184
|
+
<div
|
|
185
|
+
style={{
|
|
186
|
+
width: '100%',
|
|
187
|
+
height: '100%',
|
|
188
|
+
display: 'flex',
|
|
189
|
+
alignItems: 'center',
|
|
190
|
+
justifyContent: 'center',
|
|
191
|
+
padding: 24,
|
|
192
|
+
textAlign: 'center',
|
|
193
|
+
color: 'var(--danger, #d63a2f)',
|
|
194
|
+
fontSize: 13,
|
|
195
|
+
}}
|
|
196
|
+
>
|
|
197
|
+
Failed to load workbook: {errorMsg}
|
|
198
|
+
</div>
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (!data) {
|
|
203
|
+
return (
|
|
204
|
+
<div
|
|
205
|
+
style={{
|
|
206
|
+
width: '100%',
|
|
207
|
+
height: '100%',
|
|
208
|
+
display: 'flex',
|
|
209
|
+
alignItems: 'center',
|
|
210
|
+
justifyContent: 'center',
|
|
211
|
+
color: 'var(--text-muted, #5a5a5a)',
|
|
212
|
+
fontSize: 13,
|
|
213
|
+
}}
|
|
214
|
+
>
|
|
215
|
+
Loading workbook…
|
|
216
|
+
</div>
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Force a remount when viewMode flips — CasualSheets locks the UI
|
|
221
|
+
// config at registerPlugin time and won't pick up new props.
|
|
222
|
+
return (
|
|
223
|
+
<CasualSheets
|
|
224
|
+
key={viewMode}
|
|
225
|
+
initialData={data}
|
|
226
|
+
ui={ui}
|
|
227
|
+
// The embed runtime is bundled as ONE self-contained file (tsup
|
|
228
|
+
// `noExternal: /.*/`). Lazy plugins would emit per-feature dynamic
|
|
229
|
+
// chunks into dist/embed/, breaking that single-file deployment, so the
|
|
230
|
+
// iframe ships the minimal editor. Hosts that need feature plugins use
|
|
231
|
+
// the React `<CasualSheets>` component directly (lazyPlugins defaults on).
|
|
232
|
+
lazyPlugins={false}
|
|
233
|
+
onReady={(api) => {
|
|
234
|
+
// Wire host → editor command.execute (Drive's custom toolbar
|
|
235
|
+
// calls this for bold / italic / undo / …). Maps the small
|
|
236
|
+
// protocol union to the Univer command id the FUniver facade
|
|
237
|
+
// dispatches. The command set is intentionally narrow — v0.6
|
|
238
|
+
// ships the always-relevant operations, font / color / fill
|
|
239
|
+
// land in v0.7 once we lock the cell-mutate payload shape.
|
|
240
|
+
// `api` is now the CasualSheetsAPI imperative ref: executeCommand is a
|
|
241
|
+
// first-class method, and the raw FUniver facade is on `api.univer`.
|
|
242
|
+
// Cast through `unknown` — the FUniver type is augmented via
|
|
243
|
+
// @univerjs/sheets/facade module augmentation (the side-effect
|
|
244
|
+
// import above), but tsc doesn't always pick up the merged
|
|
245
|
+
// signature when the facade is imported as a side effect inside
|
|
246
|
+
// a different module. The runtime behaviour is fine.
|
|
247
|
+
const apiAny = api as unknown as {
|
|
248
|
+
executeCommand(id: string, params?: object): Promise<unknown>;
|
|
249
|
+
univer: { getActiveWorkbook(): { getActiveSheet(): SheetLike | null } | null };
|
|
250
|
+
};
|
|
251
|
+
transport.on({
|
|
252
|
+
onCommandExecute: ({ command, args }) => {
|
|
253
|
+
const id = SHEET_COMMAND_MAP[command];
|
|
254
|
+
if (!id) return;
|
|
255
|
+
const params = buildCommandParams(command, args);
|
|
256
|
+
try {
|
|
257
|
+
void apiAny.executeCommand(id, params);
|
|
258
|
+
} catch {
|
|
259
|
+
/* swallow — bad command id from a stale host shouldn't crash the iframe */
|
|
260
|
+
}
|
|
261
|
+
},
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
// Editor → host: emit format state on a coarse interval. The
|
|
265
|
+
// FUniver facade in 0.24 doesn't expose a stable
|
|
266
|
+
// selection-changed event hook on FWorksheet (the public API
|
|
267
|
+
// is in flux); polling every 200 ms is the smallest reliable
|
|
268
|
+
// surface that keeps the toolbar buttons in sync without
|
|
269
|
+
// hooking the internal command service. Drive throttles its
|
|
270
|
+
// own re-renders, so the wire stays cheap.
|
|
271
|
+
const emit = () => {
|
|
272
|
+
try {
|
|
273
|
+
const wb = apiAny.univer.getActiveWorkbook();
|
|
274
|
+
const sheet = wb?.getActiveSheet();
|
|
275
|
+
const range = sheet?.getActiveRange?.();
|
|
276
|
+
if (!range) return;
|
|
277
|
+
const cell = range.getCell?.(0, 0) ?? range;
|
|
278
|
+
const fmt = readFormatFlags(cell);
|
|
279
|
+
transport.sendSelectionFormatState(fmt);
|
|
280
|
+
} catch {
|
|
281
|
+
/* selection may not exist yet during boot — ignore */
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
emit();
|
|
285
|
+
const interval = setInterval(emit, 200);
|
|
286
|
+
// Best-effort cleanup — there's no unmount hook here, but if
|
|
287
|
+
// the iframe navigates away the interval is GC'd with the
|
|
288
|
+
// closure.
|
|
289
|
+
void interval;
|
|
290
|
+
}}
|
|
291
|
+
/>
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/** Maps the host-facing protocol union to Univer command ids the
|
|
296
|
+
* FUniver facade dispatches. Kept inline so the wire surface and the
|
|
297
|
+
* Univer integration evolve together. */
|
|
298
|
+
const SHEET_COMMAND_MAP: Record<string, string> = {
|
|
299
|
+
undo: 'univer.command.undo',
|
|
300
|
+
redo: 'univer.command.redo',
|
|
301
|
+
bold: 'sheet.command.set-range-bold',
|
|
302
|
+
italic: 'sheet.command.set-range-italic',
|
|
303
|
+
underline: 'sheet.command.set-range-underline',
|
|
304
|
+
// The canonical id is `set-range-stroke` (see sheets-ui's
|
|
305
|
+
// SetRangeStrickThroughCommand). 0.6 shipped the wrong id; fixed here.
|
|
306
|
+
strikethrough: 'sheet.command.set-range-stroke',
|
|
307
|
+
'align-left': 'sheet.command.set-horizontal-text-align',
|
|
308
|
+
'align-center': 'sheet.command.set-horizontal-text-align',
|
|
309
|
+
'align-right': 'sheet.command.set-horizontal-text-align',
|
|
310
|
+
// v0.7 — rich format
|
|
311
|
+
'set-font-family': 'sheet.command.set-range-font-family',
|
|
312
|
+
'set-font-size': 'sheet.command.set-range-fontsize',
|
|
313
|
+
'set-text-color': 'sheet.command.set-range-text-color',
|
|
314
|
+
'reset-text-color': 'sheet.command.reset-range-text-color',
|
|
315
|
+
'set-bg-color': 'sheet.command.set-background-color',
|
|
316
|
+
'reset-bg-color': 'sheet.command.reset-background-color',
|
|
317
|
+
merge: 'sheet.command.add-worksheet-merge',
|
|
318
|
+
unmerge: 'sheet.command.remove-worksheet-merge',
|
|
319
|
+
// v0.8 — number formats + freeze + wrap
|
|
320
|
+
'numfmt-currency': 'sheet.command.numfmt.set.currency',
|
|
321
|
+
'numfmt-percent': 'sheet.command.numfmt.set.percent',
|
|
322
|
+
'numfmt-add-decimal': 'sheet.command.numfmt.add.decimal.command',
|
|
323
|
+
'numfmt-subtract-decimal': 'sheet.command.numfmt.subtract.decimal.command',
|
|
324
|
+
'numfmt-custom': 'sheet.command.numfmt.set.numfmt',
|
|
325
|
+
'wrap-toggle': 'sheet.command.set-text-wrap',
|
|
326
|
+
'freeze-first-row': 'sheet.command.set-first-row-frozen',
|
|
327
|
+
'freeze-first-column': 'sheet.command.set-first-column-frozen',
|
|
328
|
+
'freeze-none': 'sheet.command.set-selection-frozen',
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
/** Build the `params` object the Univer command expects. Most v0.6
|
|
332
|
+
* toggles take no params (the command reads the current cell + flips
|
|
333
|
+
* the bit). Align reads `{ value: 'left'|'center'|'right' }`, font /
|
|
334
|
+
* size / colour read `{ value: <typed value> }`. Bad inputs are
|
|
335
|
+
* filtered out so a stale host can't crash the iframe. */
|
|
336
|
+
function buildCommandParams(
|
|
337
|
+
command: string,
|
|
338
|
+
args?: { family?: string; size?: number; color?: string; pattern?: string },
|
|
339
|
+
): object | undefined {
|
|
340
|
+
switch (command) {
|
|
341
|
+
case 'align-left':
|
|
342
|
+
return { value: 'left' };
|
|
343
|
+
case 'align-center':
|
|
344
|
+
return { value: 'center' };
|
|
345
|
+
case 'align-right':
|
|
346
|
+
return { value: 'right' };
|
|
347
|
+
case 'set-font-family':
|
|
348
|
+
return args?.family ? { value: args.family } : undefined;
|
|
349
|
+
case 'set-font-size':
|
|
350
|
+
return typeof args?.size === 'number' ? { value: args.size } : undefined;
|
|
351
|
+
case 'set-text-color':
|
|
352
|
+
return args?.color ? { value: args.color } : undefined;
|
|
353
|
+
case 'set-bg-color':
|
|
354
|
+
return args?.color ? { value: args.color } : undefined;
|
|
355
|
+
case 'numfmt-custom':
|
|
356
|
+
return args?.pattern ? { value: args.pattern } : undefined;
|
|
357
|
+
case 'wrap-toggle':
|
|
358
|
+
// The set-text-wrap command toggles when called with no args.
|
|
359
|
+
return undefined;
|
|
360
|
+
default:
|
|
361
|
+
return undefined;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
interface CellLike {
|
|
366
|
+
getFontWeight?: () => unknown;
|
|
367
|
+
getFontStyle?: () => unknown;
|
|
368
|
+
getUnderline?: () => unknown;
|
|
369
|
+
getStrikethrough?: () => unknown;
|
|
370
|
+
getHorizontalAlignment?: () => unknown;
|
|
371
|
+
getFontFamily?: () => unknown;
|
|
372
|
+
getFontSize?: () => unknown;
|
|
373
|
+
getFontColor?: () => unknown;
|
|
374
|
+
getBackground?: () => unknown;
|
|
375
|
+
getBackgroundColor?: () => unknown;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
interface RangeLike {
|
|
379
|
+
getCell?(row: number, col: number): unknown;
|
|
380
|
+
getFontWeight?: () => unknown;
|
|
381
|
+
getFontStyle?: () => unknown;
|
|
382
|
+
getUnderline?: () => unknown;
|
|
383
|
+
getStrikethrough?: () => unknown;
|
|
384
|
+
getHorizontalAlignment?: () => unknown;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
interface SheetLike {
|
|
388
|
+
getActiveRange?(): RangeLike | null;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function readFormatFlags(cell: unknown): {
|
|
392
|
+
bold: boolean;
|
|
393
|
+
italic: boolean;
|
|
394
|
+
underline: boolean;
|
|
395
|
+
strikethrough: boolean;
|
|
396
|
+
align: 'left' | 'center' | 'right' | null;
|
|
397
|
+
fontFamily: string | null;
|
|
398
|
+
fontSize: number | null;
|
|
399
|
+
textColor: string | null;
|
|
400
|
+
bgColor: string | null;
|
|
401
|
+
} {
|
|
402
|
+
const c = cell as CellLike;
|
|
403
|
+
const bold = String(c.getFontWeight?.() ?? '').toLowerCase() === 'bold';
|
|
404
|
+
const italic = String(c.getFontStyle?.() ?? '').toLowerCase() === 'italic';
|
|
405
|
+
const underline = !!c.getUnderline?.();
|
|
406
|
+
const strikethrough = !!c.getStrikethrough?.();
|
|
407
|
+
const ha = String(c.getHorizontalAlignment?.() ?? '').toLowerCase();
|
|
408
|
+
const align: 'left' | 'center' | 'right' | null =
|
|
409
|
+
ha === 'left' || ha === 'center' || ha === 'right' ? ha : null;
|
|
410
|
+
const ff = c.getFontFamily?.();
|
|
411
|
+
const fontFamily = typeof ff === 'string' && ff ? ff : null;
|
|
412
|
+
const fs = c.getFontSize?.();
|
|
413
|
+
const fontSize = typeof fs === 'number' && fs > 0 ? fs : null;
|
|
414
|
+
const tc = c.getFontColor?.();
|
|
415
|
+
const textColor = typeof tc === 'string' && tc ? tc : null;
|
|
416
|
+
const bg = c.getBackground?.() ?? c.getBackgroundColor?.();
|
|
417
|
+
const bgColor = typeof bg === 'string' && bg ? bg : null;
|
|
418
|
+
return {
|
|
419
|
+
bold,
|
|
420
|
+
italic,
|
|
421
|
+
underline,
|
|
422
|
+
strikethrough,
|
|
423
|
+
align,
|
|
424
|
+
fontFamily,
|
|
425
|
+
fontSize,
|
|
426
|
+
textColor,
|
|
427
|
+
bgColor,
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
function inferHostOrigin(): string | undefined {
|
|
432
|
+
if (typeof document === 'undefined') return undefined;
|
|
433
|
+
const ref = document.referrer;
|
|
434
|
+
if (!ref) return undefined;
|
|
435
|
+
try {
|
|
436
|
+
return new URL(ref).origin;
|
|
437
|
+
} catch {
|
|
438
|
+
return undefined;
|
|
439
|
+
}
|
|
440
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @casualoffice/sheets — Casual Sheets SDK
|
|
3
|
+
*
|
|
4
|
+
* Three surfaces:
|
|
5
|
+
* - `./signing` — anchored cell signatures (drawn / typed / uploaded).
|
|
6
|
+
* - `./embed` — iframe postMessage protocol for host integrations.
|
|
7
|
+
* - `./sheets` — `CasualSheets` React wrapper around Univer Sheets.
|
|
8
|
+
*
|
|
9
|
+
* The `./styles` side-effect entry brings in the eager plugin CSS:
|
|
10
|
+
*
|
|
11
|
+
* import '@casualoffice/sheets/styles';
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
export * from './signing';
|
|
15
|
+
export * from './embed';
|
|
16
|
+
export * from './sheets';
|