@casualoffice/sheets 0.13.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.
@@ -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.setReadOnly':
238
+ case 'casual.command.set.readonly':
239
239
  await this.handlers.onCommandSetReadOnly?.(env.data as CommandSetReadOnlyData);
240
240
  return;
241
- case 'casual.command.setTheme':
241
+ case 'casual.command.set.theme':
242
242
  await this.handlers.onCommandSetTheme?.(env.data as CommandSetThemeData);
243
243
  return;
244
- case 'casual.command.setLocale':
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
- return { app, docId, viewMode };
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 transport={transport} docId={config.docId} initialViewMode={config.viewMode} />,
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,14 +144,34 @@ 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
176
  // Editor mode renders the SDK's OWN React chrome (`chrome="full"`): the
136
177
  // menu bar (Edit/Insert/Format/Data/View), the rich formatting toolbar,
@@ -149,6 +190,7 @@ function EmbeddedSheets({
149
190
 
150
191
  useEffect(() => {
151
192
  transport.on({
193
+ onCommandSetTheme: ({ theme: next }) => setTheme(next),
152
194
  onCommandSetViewMode: ({ viewMode: next }) => setViewMode(next),
153
195
  });
154
196
  }, [transport]);
@@ -227,6 +269,7 @@ function EmbeddedSheets({
227
269
  key={viewMode}
228
270
  initialData={data}
229
271
  chrome={chrome}
272
+ appearance={appearance}
230
273
  ui={ui}
231
274
  // Seed the en-US string bundle. Without `locales`, Univer's
232
275
  // LocaleService throws "Locale not initialized" and the workbench
@@ -235,12 +278,17 @@ function EmbeddedSheets({
235
278
  // this from the host's `locales` prop; the iframe has no host to
236
279
  // pass one, so the self-contained runtime bundles a minimal set.
237
280
  locales={EMBED_LOCALES}
238
- // The embed runtime is bundled as ONE self-contained file (tsup
239
- // `noExternal: /.*/`). Lazy plugins would emit per-feature dynamic
240
- // chunks into dist/embed/, breaking that single-file deployment, so the
241
- // iframe ships the minimal editor. Hosts that need feature plugins use
242
- // the React `<CasualSheets>` component directly (lazyPlugins defaults on).
243
- lazyPlugins={false}
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}
244
292
  // Phase 2 save/exit contract — the iframe surface of the same
245
293
  // `onSave` / `onExit` hooks the React component exposes. The editor
246
294
  // never persists; it hands the snapshot to the host over postMessage