@casualoffice/sheets 0.12.0 → 0.14.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/chrome.cjs +1568 -283
- package/dist/chrome.cjs.map +1 -1
- package/dist/chrome.d.cts +35 -2
- package/dist/chrome.d.ts +35 -2
- package/dist/chrome.js +1566 -291
- package/dist/chrome.js.map +1 -1
- package/dist/embed/embed-runtime.js +131 -131
- package/dist/embed.cjs +3 -3
- package/dist/embed.cjs.map +1 -1
- package/dist/embed.js +3 -3
- package/dist/embed.js.map +1 -1
- package/dist/index.cjs +3 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/chrome/MenuBar.tsx +1246 -104
- package/src/chrome/Toolbar.tsx +546 -216
- package/src/embed/EmbedTransport.ts +3 -3
- package/src/embed-runtime/index.tsx +71 -21
|
@@ -235,13 +235,13 @@ export class EmbedTransport {
|
|
|
235
235
|
await this.handlers.onHostHello?.(env.data as HostHelloData);
|
|
236
236
|
this.sendReady();
|
|
237
237
|
return;
|
|
238
|
-
case 'casual.command.
|
|
238
|
+
case 'casual.command.set.readonly':
|
|
239
239
|
await this.handlers.onCommandSetReadOnly?.(env.data as CommandSetReadOnlyData);
|
|
240
240
|
return;
|
|
241
|
-
case 'casual.command.
|
|
241
|
+
case 'casual.command.set.theme':
|
|
242
242
|
await this.handlers.onCommandSetTheme?.(env.data as CommandSetThemeData);
|
|
243
243
|
return;
|
|
244
|
-
case 'casual.command.
|
|
244
|
+
case 'casual.command.set.locale':
|
|
245
245
|
await this.handlers.onCommandSetLocale?.(env.data as CommandSetLocaleData);
|
|
246
246
|
return;
|
|
247
247
|
case 'casual.command.set.viewmode':
|
|
@@ -32,6 +32,7 @@ interface EmbedUrlConfig {
|
|
|
32
32
|
app: CasualApp;
|
|
33
33
|
docId: string;
|
|
34
34
|
viewMode: 'preview' | 'editor';
|
|
35
|
+
theme: 'light' | 'dark' | 'system';
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
function parseUrlConfig(search: string): EmbedUrlConfig {
|
|
@@ -40,7 +41,22 @@ function parseUrlConfig(search: string): EmbedUrlConfig {
|
|
|
40
41
|
const docId = params.get('docId') ?? '';
|
|
41
42
|
const viewModeParam = params.get('viewMode');
|
|
42
43
|
const viewMode: 'preview' | 'editor' = viewModeParam === 'editor' ? 'editor' : 'preview';
|
|
43
|
-
|
|
44
|
+
// The host passes its theme so the embedded editor matches light/dark.
|
|
45
|
+
// Default `system` follows the OS/host colour-scheme; the host can also
|
|
46
|
+
// push live changes over `casual.command.set.theme`.
|
|
47
|
+
const themeParam = params.get('theme');
|
|
48
|
+
const theme: 'light' | 'dark' | 'system' =
|
|
49
|
+
themeParam === 'dark' ? 'dark' : themeParam === 'light' ? 'light' : 'system';
|
|
50
|
+
return { app, docId, viewMode, theme };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Resolve `system` against the iframe's media query; `light`/`dark` pass through. */
|
|
54
|
+
function resolveAppearance(theme: 'light' | 'dark' | 'system'): 'light' | 'dark' {
|
|
55
|
+
if (theme === 'light' || theme === 'dark') return theme;
|
|
56
|
+
if (typeof window !== 'undefined' && typeof window.matchMedia === 'function') {
|
|
57
|
+
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
|
58
|
+
}
|
|
59
|
+
return 'light';
|
|
44
60
|
}
|
|
45
61
|
|
|
46
62
|
export interface MountEmbeddedOptions {
|
|
@@ -112,7 +128,12 @@ export function mountEmbedded(opts: MountEmbeddedOptions): void {
|
|
|
112
128
|
|
|
113
129
|
const reactRoot = createRoot(opts.root);
|
|
114
130
|
reactRoot.render(
|
|
115
|
-
<EmbeddedSheets
|
|
131
|
+
<EmbeddedSheets
|
|
132
|
+
transport={transport}
|
|
133
|
+
docId={config.docId}
|
|
134
|
+
initialViewMode={config.viewMode}
|
|
135
|
+
initialTheme={config.theme}
|
|
136
|
+
/>,
|
|
116
137
|
);
|
|
117
138
|
}
|
|
118
139
|
|
|
@@ -123,31 +144,53 @@ function EmbeddedSheets({
|
|
|
123
144
|
transport,
|
|
124
145
|
docId,
|
|
125
146
|
initialViewMode,
|
|
147
|
+
initialTheme,
|
|
126
148
|
}: {
|
|
127
149
|
transport: EmbedTransport;
|
|
128
150
|
docId: string;
|
|
129
151
|
initialViewMode: 'preview' | 'editor';
|
|
152
|
+
initialTheme: 'light' | 'dark' | 'system';
|
|
130
153
|
}) {
|
|
131
154
|
const [data, setData] = useState<IWorkbookData | null>(null);
|
|
132
155
|
const [errorMsg, setErrorMsg] = useState<string | null>(null);
|
|
133
156
|
|
|
157
|
+
// Theme: track the host's choice (light/dark/system) and resolve `system`
|
|
158
|
+
// live against the iframe's prefers-color-scheme. The host pushes changes
|
|
159
|
+
// over `casual.command.set.theme`; passed to <CasualSheets appearance>,
|
|
160
|
+
// which flips Univer's `univer-dark` class + ThemeService so the canvas,
|
|
161
|
+
// headers, gridlines and chrome all match.
|
|
162
|
+
const [theme, setTheme] = useState<'light' | 'dark' | 'system'>(initialTheme);
|
|
163
|
+
const [appearance, setAppearance] = useState<'light' | 'dark'>(() =>
|
|
164
|
+
resolveAppearance(initialTheme),
|
|
165
|
+
);
|
|
166
|
+
useEffect(() => {
|
|
167
|
+
setAppearance(resolveAppearance(theme));
|
|
168
|
+
if (theme !== 'system' || typeof window === 'undefined' || !window.matchMedia) return;
|
|
169
|
+
const mq = window.matchMedia('(prefers-color-scheme: dark)');
|
|
170
|
+
const onChange = () => setAppearance(mq.matches ? 'dark' : 'light');
|
|
171
|
+
mq.addEventListener('change', onChange);
|
|
172
|
+
return () => mq.removeEventListener('change', onChange);
|
|
173
|
+
}, [theme]);
|
|
174
|
+
|
|
134
175
|
const [viewMode, setViewMode] = useState<'preview' | 'editor'>(initialViewMode);
|
|
135
|
-
// Editor mode
|
|
136
|
-
//
|
|
137
|
-
//
|
|
138
|
-
//
|
|
139
|
-
//
|
|
140
|
-
//
|
|
141
|
-
//
|
|
142
|
-
//
|
|
143
|
-
//
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
176
|
+
// Editor mode renders the SDK's OWN React chrome (`chrome="full"`): the
|
|
177
|
+
// menu bar (Edit/Insert/Format/Data/View), the rich formatting toolbar,
|
|
178
|
+
// the formula bar, sheet tabs and status bar — the full native editor, so
|
|
179
|
+
// hosts embed a complete spreadsheet and only frame/brand it (they do NOT
|
|
180
|
+
// hand-roll a toolbar). We use the SDK chrome, NOT Univer's built-in `ui`
|
|
181
|
+
// toolbar/footer: Univer's ribbon + sheet-tabs slots resolve services
|
|
182
|
+
// (IRPCChannelService, sheet-drawing) the single-file embed doesn't bundle,
|
|
183
|
+
// which throws `[redi]: Cannot find … registered by any injector`. The SDK
|
|
184
|
+
// chrome is plain React over the facade, so it has no such dependency.
|
|
185
|
+
//
|
|
186
|
+
// Preview = `chrome="none"`: bare canvas, made read-only by the veto in
|
|
187
|
+
// onReady. Univer's own chrome stays off in both modes.
|
|
188
|
+
const chrome: 'full' | 'none' = viewMode === 'editor' ? 'full' : 'none';
|
|
189
|
+
const ui = { header: false, toolbar: false, footer: false, contextMenu: true };
|
|
148
190
|
|
|
149
191
|
useEffect(() => {
|
|
150
192
|
transport.on({
|
|
193
|
+
onCommandSetTheme: ({ theme: next }) => setTheme(next),
|
|
151
194
|
onCommandSetViewMode: ({ viewMode: next }) => setViewMode(next),
|
|
152
195
|
});
|
|
153
196
|
}, [transport]);
|
|
@@ -225,6 +268,8 @@ function EmbeddedSheets({
|
|
|
225
268
|
<CasualSheets
|
|
226
269
|
key={viewMode}
|
|
227
270
|
initialData={data}
|
|
271
|
+
chrome={chrome}
|
|
272
|
+
appearance={appearance}
|
|
228
273
|
ui={ui}
|
|
229
274
|
// Seed the en-US string bundle. Without `locales`, Univer's
|
|
230
275
|
// LocaleService throws "Locale not initialized" and the workbench
|
|
@@ -233,12 +278,17 @@ function EmbeddedSheets({
|
|
|
233
278
|
// this from the host's `locales` prop; the iframe has no host to
|
|
234
279
|
// pass one, so the self-contained runtime bundles a minimal set.
|
|
235
280
|
locales={EMBED_LOCALES}
|
|
236
|
-
//
|
|
237
|
-
//
|
|
238
|
-
//
|
|
239
|
-
//
|
|
240
|
-
//
|
|
241
|
-
|
|
281
|
+
// Enable the FULL feature set (tables, sort, filter, conditional
|
|
282
|
+
// formatting, data validation, drawing, hyperlinks, notes, comments,
|
|
283
|
+
// find/replace) so the embed matches the real app. The tsup embed
|
|
284
|
+
// config is `splitting: false` + `noExternal: /.*/`, so the lazy
|
|
285
|
+
// loader's dynamic `import()`s are INLINED into the single
|
|
286
|
+
// embed-runtime.js rather than emitted as separate chunks — the
|
|
287
|
+
// single-file deployment is preserved. With lazyPlugins on, the loader
|
|
288
|
+
// also eager-loads any feature whose data is already in the snapshot
|
|
289
|
+
// (so opening a file with tables/CF never silently drops it) and
|
|
290
|
+
// idle-loads the rest, so the toolbar/menu feature actions resolve.
|
|
291
|
+
lazyPlugins={true}
|
|
242
292
|
// Phase 2 save/exit contract — the iframe surface of the same
|
|
243
293
|
// `onSave` / `onExit` hooks the React component exposes. The editor
|
|
244
294
|
// never persists; it hands the snapshot to the host over postMessage
|