@doenet/doenetml-iframe 0.7.21-dev.352 → 0.7.21-dev.353

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/README.md CHANGED
@@ -80,7 +80,7 @@ the inner viewer applies them with the same semantics as the in-process
80
80
 
81
81
  | Prop change | Effect |
82
82
  | -------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
83
- | `render`, `darkMode`, `flags`, `answerResponseCounts`, callbacks, … | applied live, no reload (flipping `render` false→true starts the document in the already-loaded realm) |
83
+ | `render`, `darkMode`, `styleOverrides`, `flags`, `answerResponseCounts`, callbacks, … | applied live, no reload (flipping `render` false→true starts the document in the already-loaded realm) |
84
84
  | `doenetML`, `activityId`, `docId`, `attemptNumber`, `requestedVariantIndex` | the document's core re-initializes **inside the same iframe realm** — the multi-MB bundle is not re-parsed |
85
85
  | `initialState`, `forceDisable` and the other `force*` props, `userId` | read at (re-)initialization only, exactly like the in-process viewer — change `docId`/`attemptNumber` or remount via `key=` to apply |
86
86
  | `standaloneUrl`, `cssUrl`, `doenetmlVersion` (or a version change detected in `doenetML`), `useSharedCoreWorker` | a different bundle/realm is required, so the iframe reloads |
@@ -88,6 +88,91 @@ the inner viewer applies them with the same semantics as the in-process
88
88
  To force a full remount (fresh realm and worker), change the component's
89
89
  React `key`.
90
90
 
91
+ ### Reader style overrides (`styleOverrides`)
92
+
93
+ Hosts can let a **reader** remap what each style number looks like — for
94
+ example a color-blind user picking colors they can better tell apart — by
95
+ passing a `styleOverrides` prop (type `ReaderStyleOverrides`, re-exported
96
+ from this package). Overrides win over everything authored in the document
97
+ (`<styleDefinition>` and `<stylePalette>` alike) and update live when the
98
+ prop changes:
99
+
100
+ ```tsx
101
+ <DoenetViewer
102
+ doenetML={source}
103
+ styleOverrides={{
104
+ styles: {
105
+ 1: { lineColor: "#0072b2", markerColor: "#0072b2" },
106
+ 2: { lineColor: "#d55e00", lineWidth: 6 },
107
+ },
108
+ }}
109
+ />
110
+ ```
111
+
112
+ A reader can also switch the whole document to one of the built-in style
113
+ palettes by name (case-insensitive) — for example `grayscale`, four grays
114
+ laddered for maximum luminance separation, for readers who distinguish
115
+ styles by lightness alone, or `okabeito` for common color vision
116
+ deficiencies:
117
+
118
+ ```tsx
119
+ <DoenetViewer
120
+ doenetML={source}
121
+ styleOverrides={{ palette: "grayscale" }}
122
+ />
123
+ ```
124
+
125
+ A reader-selected palette replaces the document's base styles everywhere:
126
+ authored `<stylePalette>` selections and `<styleDefinition>` customizations
127
+ are discarded (they were tuned against different colors), style numbers
128
+ beyond the reader palette's size wrap around onto it, and any `styles`
129
+ overrides apply on top of the reader's palette. Unregistered palette names
130
+ are ignored, so offer readers the palette list as fixed choices.
131
+
132
+ Every style-definition key is overridable except the `*Word` descriptors:
133
+ all colors (including the `*DarkMode` variants), opacities, `lineWidth`,
134
+ `lineStyle`, `markerStyle`, `markerSize`, `markerFilled`, and the fill
135
+ settings. Human-readable color/style words are always re-derived from the
136
+ overridden values, so text style descriptions (e.g. "the blue line") stay
137
+ truthful, and a missing dark-mode color is derived from the reader's
138
+ light-mode color with the same accessibility-aware derivation authored
139
+ styles get. Pass `null` (or omit the prop) to clear all overrides.
140
+
141
+ The same prop exists on `<DoenetEditor>`, where it applies to the editor's
142
+ rendered preview (the code pane and context help keep showing the authored
143
+ values).
144
+
145
+ #### Listing the available palettes (`onStylePalettes`)
146
+
147
+ To build a palette picker, a host needs the palettes that the DoenetML
148
+ version **inside the iframe** supports — this wrapper and the standalone
149
+ bundle ship separately, and a host may pin an older `doenetmlVersion`. The
150
+ `onStylePalettes` callback fires once the bundle has booted, with that
151
+ bundle's palettes:
152
+
153
+ ```tsx
154
+ const [palettes, setPalettes] = React.useState(null);
155
+
156
+ <DoenetViewer
157
+ doenetML={source}
158
+ onStylePalettes={setPalettes}
159
+ styleOverrides={chosenPalette ? { palette: chosenPalette } : undefined}
160
+ />;
161
+ ```
162
+
163
+ Each entry is `{ name, description, styles }`, where `styles` is keyed by
164
+ style number (contiguous from 1, always at least four) and each style is a
165
+ fully-resolved style definition — `lineColor`, `markerColor`, `fillColor`,
166
+ `textColor` and their `*DarkMode` variants for swatches in either theme,
167
+ plus `lineWidth`, `lineStyle`, `markerStyle`, and `markerSize` for the
168
+ non-color distinctions palettes also carry, and the `*Word` fields (e.g.
169
+ `"blue"`) for labelling swatches accessibly. Feed a chosen `name` back in as
170
+ the `palette` field of `styleOverrides`.
171
+
172
+ The callback receives `null` when the booted bundle predates palette
173
+ discovery, which is the host's cue to hide the picker. The same prop exists
174
+ on `<DoenetEditor>`.
175
+
91
176
  Function props (callbacks) are forwarded across the iframe boundary via
92
177
  Comlink proxies and always follow the latest identity passed — parents may
93
178
  pass inline arrow functions freely; identity churn does not re-render the
package/index.d.ts CHANGED
@@ -15,6 +15,9 @@ import { MediaLicenseDisplay } from '../packages/utils/dist';
15
15
  import { MediaLicenseInfo } from '../packages/utils/dist';
16
16
  import { MediaLicenseKind } from '../packages/utils/dist';
17
17
  import { mediaLicenses } from '../packages/utils/dist';
18
+ import { ReaderStyleOverrides } from '../packages/utils/dist';
19
+ import { ReaderStyleValueOverrides } from '../packages/utils/dist';
20
+ import { StylePaletteInfo } from '../packages/utils/dist';
18
21
  import { WarningRecord } from '../packages/utils/dist';
19
22
 
20
23
  export { CreativeCommonsVersion }
@@ -32,6 +35,15 @@ export declare const DoenetEditor: default_2.ForwardRefExoticComponent<Omit<Doen
32
35
  export { DoenetEditorHandle }
33
36
 
34
37
  export declare type DoenetEditorIframeProps = DoenetEditorProps & {
38
+ /**
39
+ * Called once the standalone bundle inside the iframe has booted, with
40
+ * the style palettes THAT bundle supports — the source of truth for a
41
+ * host rendering a palette picker, since the iframe may run a pinned or
42
+ * older DoenetML version than this wrapper. Receives `null` when the
43
+ * booted bundle predates palette discovery. Feed a chosen palette name
44
+ * back in as the `palette` field of `styleOverrides`.
45
+ */
46
+ onStylePalettes?: (palettes: StylePaletteInfo[] | null) => void;
35
47
  doenetML: string;
36
48
  /**
37
49
  * The URL of a standalone DoenetML bundle. This may be from the CDN.
@@ -86,10 +98,19 @@ declare type DoenetEditorProps = Omit<React.ComponentProps<typeof DoenetEditor_2
86
98
  * v0.7.18 cannot re-render in place; for those the wrapper falls back to
87
99
  * reloading the iframe on any prop change, its historical behavior.)
88
100
  */
89
- export declare function DoenetViewer({ doenetML, standaloneUrl: specifiedStandaloneUrl, cssUrl: specifiedCssUrl, doenetmlVersion: specifiedDoenetmlVersion, autodetectVersion, useSharedCoreWorker, mountPolicy, keepLive, ...doenetViewerProps }: DoenetViewerIframeProps): default_2.JSX.Element | null;
101
+ export declare function DoenetViewer({ doenetML, standaloneUrl: specifiedStandaloneUrl, cssUrl: specifiedCssUrl, doenetmlVersion: specifiedDoenetmlVersion, autodetectVersion, useSharedCoreWorker, mountPolicy, keepLive, onStylePalettes, ...doenetViewerProps }: DoenetViewerIframeProps): default_2.JSX.Element | null;
90
102
 
91
103
  export declare type DoenetViewerIframeProps = DoenetViewerProps & {
92
104
  doenetML: string;
105
+ /**
106
+ * Called once the standalone bundle inside the iframe has booted, with
107
+ * the style palettes THAT bundle supports — the source of truth for a
108
+ * host rendering a palette picker, since the iframe may run a pinned or
109
+ * older DoenetML version than this wrapper. Receives `null` when the
110
+ * booted bundle predates palette discovery. Feed a chosen palette name
111
+ * back in as the `palette` field of `styleOverrides`.
112
+ */
113
+ onStylePalettes?: (palettes: StylePaletteInfo[] | null) => void;
93
114
  /**
94
115
  * The URL of a standalone DoenetML bundle. This may be from the CDN.
95
116
  * If autodetectVersion is `true` and a version is detected, this URL is ignored.
@@ -250,6 +271,12 @@ export declare type MountPolicy = {
250
271
  maxConcurrentBoots?: number;
251
272
  };
252
273
 
274
+ export { ReaderStyleOverrides }
275
+
276
+ export { ReaderStyleValueOverrides }
277
+
278
+ export { StylePaletteInfo }
279
+
253
280
  export declare const version: string;
254
281
 
255
282
  export { WarningRecord }
package/index.js CHANGED
@@ -26957,7 +26957,7 @@ function requireCssesc$1() {
26957
26957
  return cssesc_1$1;
26958
26958
  }
26959
26959
  requireCssesc$1();
26960
- const viewerIframeJsSource = '(function() {\n "use strict";\n let lastAugmentedProps = null;\n let currentFunctionProps = {};\n const functionPropDispatchers = {};\n function getFunctionPropDispatcher(key) {\n let dispatcher = functionPropDispatchers[key];\n if (!dispatcher) {\n if (key === "reportScoreAndStateCallback" && typeof doenetWindowedViewer !== "undefined" && doenetWindowedViewer) {\n dispatcher = (report) => messageParentFromViewer({\n type: "reportScoreAndState",\n report\n });\n } else {\n dispatcher = (...callArgs) => currentFunctionProps[key]?.(...callArgs);\n }\n functionPropDispatchers[key] = dispatcher;\n }\n return dispatcher;\n }\n function functionPropArgsToMap(args) {\n const map = {};\n for (let i = 0; i < args.length; i += 2) {\n const key = args[i];\n const fn = args[i + 1];\n if (typeof key === "string" && typeof fn === "function") {\n map[key] = fn;\n }\n }\n return map;\n }\n function releaseFunctionProxy(fn) {\n try {\n fn?.[ComlinkViewer.releaseProxy]?.();\n } catch (e) {\n console.warn(\n "iframe DoenetViewer: failed to release stale Comlink proxy",\n e\n );\n }\n }\n function setCurrentFunctionProps(incoming) {\n for (const [key, fn] of Object.entries(currentFunctionProps)) {\n if (incoming[key] !== fn) {\n releaseFunctionProxy(fn);\n }\n }\n currentFunctionProps = incoming;\n }\n function defaultRequestScrollTo(offset) {\n messageParentFromViewer({ type: "scrollTo", offset });\n }\n function syncAugmentedFunctionProps() {\n if (!lastAugmentedProps) {\n return;\n }\n const next = {};\n for (const [key, val] of Object.entries(lastAugmentedProps)) {\n if (typeof val !== "function") {\n next[key] = val;\n }\n }\n for (const key of Object.keys(currentFunctionProps)) {\n next[key] = getFunctionPropDispatcher(key);\n }\n if (!("requestScrollTo" in next)) {\n next.requestScrollTo = defaultRequestScrollTo;\n }\n lastAugmentedProps = next;\n }\n let currentDoenetMLSource = null;\n function resolveDoenetMLSource(root) {\n if (currentDoenetMLSource !== null) {\n return currentDoenetMLSource;\n }\n const scriptTag = root.querySelector(\'script[type="text/doenetml"]\');\n currentDoenetMLSource = scriptTag?.innerHTML ?? "";\n return currentDoenetMLSource;\n }\n async function waitForStandaloneBundle(timeoutMs) {\n if (typeof window.renderDoenetViewerToContainer === "function") {\n return true;\n }\n const deadline = Date.now() + timeoutMs;\n while (Date.now() < deadline) {\n await new Promise((resolve) => setTimeout(resolve, 50));\n if (typeof window.renderDoenetViewerToContainer === "function") {\n return true;\n }\n }\n return false;\n }\n ComlinkViewer.expose(\n {\n renderViewerWithFunctionProps,\n updateViewerProps(updatedSerializableProps) {\n if (!lastAugmentedProps) {\n console.warn(\n "iframe DoenetViewer: updateViewerProps arrived before renderViewerWithFunctionProps completed — likely a bug in the iframe wrapper\'s queue/replay sequencing."\n );\n return;\n }\n const { doenetML, ...rest } = updatedSerializableProps;\n if (typeof doenetML === "string") {\n currentDoenetMLSource = doenetML;\n }\n lastAugmentedProps = {\n ...lastAugmentedProps,\n ...rest\n };\n renderWithLastAugmentedProps();\n },\n updateViewerFunctionProps(...args) {\n if (!lastAugmentedProps) {\n console.warn(\n "iframe DoenetViewer: updateViewerFunctionProps arrived before renderViewerWithFunctionProps completed — likely a bug in the iframe wrapper\'s queue/replay sequencing."\n );\n return;\n }\n const incoming = functionPropArgsToMap(args);\n const prevKeys = Object.keys(currentFunctionProps).sort();\n const nextKeys = Object.keys(incoming).sort();\n const keysChanged = prevKeys.length !== nextKeys.length || prevKeys.some((key, i) => key !== nextKeys[i]);\n setCurrentFunctionProps(incoming);\n if (keysChanged) {\n syncAugmentedFunctionProps();\n renderWithLastAugmentedProps();\n }\n }\n },\n ComlinkViewer.windowEndpoint(globalThis.parent)\n );\n function renderViewerWithFunctionProps(...args) {\n setCurrentFunctionProps(functionPropArgsToMap(args));\n const augmentedDoenetViewerProps = { ...doenetViewerProps };\n augmentedDoenetViewerProps.externalVirtualKeyboardProvided = true;\n for (const propName of doenetViewerPropsSpecified) {\n if (!(propName in doenetViewerProps) && propName in currentFunctionProps) {\n augmentedDoenetViewerProps[propName] = getFunctionPropDispatcher(propName);\n }\n }\n if (!doenetViewerPropsSpecified.includes("requestScrollTo")) {\n augmentedDoenetViewerProps.requestScrollTo = defaultRequestScrollTo;\n }\n lastAugmentedProps = augmentedDoenetViewerProps;\n renderWithLastAugmentedProps();\n }\n function renderWithLastAugmentedProps() {\n const root = document.getElementById("root");\n window.renderDoenetViewerToContainer(\n root,\n resolveDoenetMLSource(root),\n lastAugmentedProps\n );\n }\n function installSharedCorePortProvider() {\n let coreCounter = 0;\n window.doenetGlobalConfig.createExternalCoreWorkerPort = () => {\n const coreId = `${viewerId}-core-${++coreCounter}`;\n const channel = new MessageChannel();\n window.parent.postMessage(\n { origin: viewerId, data: { type: "createSharedCore", coreId } },\n window.parent.origin,\n [channel.port2]\n );\n return {\n port: channel.port1,\n destroy: (suspectWedge) => {\n messageParentFromViewer({\n type: "destroySharedCore",\n coreId,\n suspectWedge: Boolean(suspectWedge)\n });\n }\n };\n };\n }\n (async () => {\n if (await waitForStandaloneBundle(6e4)) {\n if (typeof doenetSharedCoreWorker !== "undefined" && doenetSharedCoreWorker) {\n installSharedCorePortProvider();\n }\n messageParentFromViewer({ iframeReady: true });\n } else {\n messageParentFromViewer({\n error: "Invalid DoenetML version or DoenetML package not found"\n });\n }\n })().catch((err) => {\n console.error(\n "iframe DoenetViewer: unexpected failure while signalling iframeReady",\n err\n );\n try {\n messageParentFromViewer({\n error: "iframe viewer failed to initialize"\n });\n } catch {\n }\n });\n function messageParentFromViewer(data) {\n window.parent.postMessage(\n {\n origin: viewerId,\n data\n },\n window.parent.origin\n );\n }\n})();\n';
26960
+ const viewerIframeJsSource = '(function() {\n "use strict";\n let lastAugmentedProps = null;\n let currentFunctionProps = {};\n const functionPropDispatchers = {};\n function getFunctionPropDispatcher(key) {\n let dispatcher = functionPropDispatchers[key];\n if (!dispatcher) {\n if (key === "reportScoreAndStateCallback" && typeof doenetWindowedViewer !== "undefined" && doenetWindowedViewer) {\n dispatcher = (report) => messageParentFromViewer({\n type: "reportScoreAndState",\n report\n });\n } else {\n dispatcher = (...callArgs) => currentFunctionProps[key]?.(...callArgs);\n }\n functionPropDispatchers[key] = dispatcher;\n }\n return dispatcher;\n }\n function functionPropArgsToMap(args) {\n const map = {};\n for (let i = 0; i < args.length; i += 2) {\n const key = args[i];\n const fn = args[i + 1];\n if (typeof key === "string" && typeof fn === "function") {\n map[key] = fn;\n }\n }\n return map;\n }\n function releaseFunctionProxy(fn) {\n try {\n fn?.[ComlinkViewer.releaseProxy]?.();\n } catch (e) {\n console.warn(\n "iframe DoenetViewer: failed to release stale Comlink proxy",\n e\n );\n }\n }\n function setCurrentFunctionProps(incoming) {\n for (const [key, fn] of Object.entries(currentFunctionProps)) {\n if (incoming[key] !== fn) {\n releaseFunctionProxy(fn);\n }\n }\n currentFunctionProps = incoming;\n }\n function defaultRequestScrollTo(offset) {\n messageParentFromViewer({ type: "scrollTo", offset });\n }\n function syncAugmentedFunctionProps() {\n if (!lastAugmentedProps) {\n return;\n }\n const next = {};\n for (const [key, val] of Object.entries(lastAugmentedProps)) {\n if (typeof val !== "function") {\n next[key] = val;\n }\n }\n for (const key of Object.keys(currentFunctionProps)) {\n next[key] = getFunctionPropDispatcher(key);\n }\n if (!("requestScrollTo" in next)) {\n next.requestScrollTo = defaultRequestScrollTo;\n }\n lastAugmentedProps = next;\n }\n let currentDoenetMLSource = null;\n function resolveDoenetMLSource(root) {\n if (currentDoenetMLSource !== null) {\n return currentDoenetMLSource;\n }\n const scriptTag = root.querySelector(\'script[type="text/doenetml"]\');\n currentDoenetMLSource = scriptTag?.innerHTML ?? "";\n return currentDoenetMLSource;\n }\n async function waitForStandaloneBundle(timeoutMs) {\n if (typeof window.renderDoenetViewerToContainer === "function") {\n return true;\n }\n const deadline = Date.now() + timeoutMs;\n while (Date.now() < deadline) {\n await new Promise((resolve) => setTimeout(resolve, 50));\n if (typeof window.renderDoenetViewerToContainer === "function") {\n return true;\n }\n }\n return false;\n }\n ComlinkViewer.expose(\n {\n renderViewerWithFunctionProps,\n updateViewerProps(updatedSerializableProps) {\n if (!lastAugmentedProps) {\n console.warn(\n "iframe DoenetViewer: updateViewerProps arrived before renderViewerWithFunctionProps completed — likely a bug in the iframe wrapper\'s queue/replay sequencing."\n );\n return;\n }\n const { doenetML, ...rest } = updatedSerializableProps;\n if (typeof doenetML === "string") {\n currentDoenetMLSource = doenetML;\n }\n lastAugmentedProps = {\n ...lastAugmentedProps,\n ...rest\n };\n renderWithLastAugmentedProps();\n },\n updateViewerFunctionProps(...args) {\n if (!lastAugmentedProps) {\n console.warn(\n "iframe DoenetViewer: updateViewerFunctionProps arrived before renderViewerWithFunctionProps completed — likely a bug in the iframe wrapper\'s queue/replay sequencing."\n );\n return;\n }\n const incoming = functionPropArgsToMap(args);\n const prevKeys = Object.keys(currentFunctionProps).sort();\n const nextKeys = Object.keys(incoming).sort();\n const keysChanged = prevKeys.length !== nextKeys.length || prevKeys.some((key, i) => key !== nextKeys[i]);\n setCurrentFunctionProps(incoming);\n if (keysChanged) {\n syncAugmentedFunctionProps();\n renderWithLastAugmentedProps();\n }\n }\n },\n ComlinkViewer.windowEndpoint(globalThis.parent)\n );\n function renderViewerWithFunctionProps(...args) {\n setCurrentFunctionProps(functionPropArgsToMap(args));\n const augmentedDoenetViewerProps = { ...doenetViewerProps };\n augmentedDoenetViewerProps.externalVirtualKeyboardProvided = true;\n for (const propName of doenetViewerPropsSpecified) {\n if (!(propName in doenetViewerProps) && propName in currentFunctionProps) {\n augmentedDoenetViewerProps[propName] = getFunctionPropDispatcher(propName);\n }\n }\n if (!doenetViewerPropsSpecified.includes("requestScrollTo")) {\n augmentedDoenetViewerProps.requestScrollTo = defaultRequestScrollTo;\n }\n lastAugmentedProps = augmentedDoenetViewerProps;\n renderWithLastAugmentedProps();\n }\n function renderWithLastAugmentedProps() {\n const root = document.getElementById("root");\n window.renderDoenetViewerToContainer(\n root,\n resolveDoenetMLSource(root),\n lastAugmentedProps\n );\n }\n function installSharedCorePortProvider() {\n let coreCounter = 0;\n window.doenetGlobalConfig.createExternalCoreWorkerPort = () => {\n const coreId = `${viewerId}-core-${++coreCounter}`;\n const channel = new MessageChannel();\n window.parent.postMessage(\n { origin: viewerId, data: { type: "createSharedCore", coreId } },\n window.parent.origin,\n [channel.port2]\n );\n return {\n port: channel.port1,\n destroy: (suspectWedge) => {\n messageParentFromViewer({\n type: "destroySharedCore",\n coreId,\n suspectWedge: Boolean(suspectWedge)\n });\n }\n };\n };\n }\n (async () => {\n if (await waitForStandaloneBundle(6e4)) {\n if (typeof doenetSharedCoreWorker !== "undefined" && doenetSharedCoreWorker) {\n installSharedCorePortProvider();\n }\n messageParentFromViewer({\n iframeReady: true,\n stylePalettes: readStylePalettesFromBundle()\n });\n } else {\n messageParentFromViewer({\n error: "Invalid DoenetML version or DoenetML package not found"\n });\n }\n })().catch((err) => {\n console.error(\n "iframe DoenetViewer: unexpected failure while signalling iframeReady",\n err\n );\n try {\n messageParentFromViewer({\n error: "iframe viewer failed to initialize"\n });\n } catch {\n }\n });\n function readStylePalettesFromBundle() {\n try {\n return window.getDoenetStylePalettes?.() ?? null;\n } catch (err) {\n console.error(\n "iframe DoenetML: failed to read style palettes from the standalone bundle",\n err\n );\n return null;\n }\n }\n function messageParentFromViewer(data) {\n window.parent.postMessage(\n {\n origin: viewerId,\n data\n },\n window.parent.origin\n );\n }\n})();\n';
26961
26961
  const editorIframeJsSource = `(function() {
26962
26962
  "use strict";
26963
26963
  let editorControlHandle = null;
@@ -27126,7 +27126,10 @@ const editorIframeJsSource = `(function() {
27126
27126
  }
27127
27127
  (async () => {
27128
27128
  if (await waitForStandaloneBundle(6e4)) {
27129
- messageParentFromEditor({ iframeReady: true });
27129
+ messageParentFromEditor({
27130
+ iframeReady: true,
27131
+ stylePalettes: readStylePalettesFromBundle()
27132
+ });
27130
27133
  } else {
27131
27134
  messageParentFromEditor({
27132
27135
  error: "Invalid DoenetML version or DoenetML package not found"
@@ -27144,6 +27147,17 @@ const editorIframeJsSource = `(function() {
27144
27147
  } catch {
27145
27148
  }
27146
27149
  });
27150
+ function readStylePalettesFromBundle() {
27151
+ try {
27152
+ return window.getDoenetStylePalettes?.() ?? null;
27153
+ } catch (err) {
27154
+ console.error(
27155
+ "iframe DoenetML: failed to read style palettes from the standalone bundle",
27156
+ err
27157
+ );
27158
+ return null;
27159
+ }
27160
+ }
27147
27161
  function messageParentFromEditor(data) {
27148
27162
  window.parent.postMessage(
27149
27163
  {
@@ -66089,7 +66103,7 @@ function ExternalVirtualKeyboard({
66089
66103
  }
66090
66104
  );
66091
66105
  }
66092
- const version = "0.7.21-dev.352";
66106
+ const version = "0.7.21-dev.353";
66093
66107
  const latestDoenetmlVersion = version;
66094
66108
  function subscribeToPinnedTheme() {
66095
66109
  return () => {
@@ -66113,6 +66127,7 @@ function DoenetViewer({
66113
66127
  useSharedCoreWorker = false,
66114
66128
  mountPolicy,
66115
66129
  keepLive = false,
66130
+ onStylePalettes,
66116
66131
  ...doenetViewerProps
66117
66132
  }) {
66118
66133
  const [id2, _2] = React__default.useState(() => Math.random().toString(36).slice(2));
@@ -66123,6 +66138,8 @@ function DoenetViewer({
66123
66138
  const lastSentFunctionPropsRef = React__default.useRef(null);
66124
66139
  const doenetViewerPropsRef = React__default.useRef(doenetViewerProps);
66125
66140
  doenetViewerPropsRef.current = doenetViewerProps;
66141
+ const onStylePalettesRef = React__default.useRef(onStylePalettes);
66142
+ onStylePalettesRef.current = onStylePalettes;
66126
66143
  const doenetMLRef = React__default.useRef(doenetML);
66127
66144
  doenetMLRef.current = doenetML;
66128
66145
  const windowed = mountPolicy?.mode === "windowed";
@@ -66369,6 +66386,9 @@ function DoenetViewer({
66369
66386
  }
66370
66387
  return setInErrorState(data.error);
66371
66388
  } else if (data.iframeReady) {
66389
+ onStylePalettesRef.current?.(
66390
+ data.stylePalettes ?? null
66391
+ );
66372
66392
  if (ref.current) {
66373
66393
  const viewerIframe = wrap$3(
66374
66394
  windowEndpoint(ref.current.contentWindow)
@@ -66721,6 +66741,9 @@ function serializePropsSnapshot(seed, props) {
66721
66741
  snapshot[key] = val;
66722
66742
  }
66723
66743
  }
66744
+ if (snapshot.styleOverrides === void 0) {
66745
+ snapshot.styleOverrides = null;
66746
+ }
66724
66747
  return JSON.stringify(snapshot);
66725
66748
  }
66726
66749
  function logComlinkError(component, method) {
@@ -66736,6 +66759,7 @@ const DoenetEditor = React__default.forwardRef(function DoenetEditor2({
66736
66759
  width = "100%",
66737
66760
  height = "500px",
66738
66761
  autodetectVersion = true,
66762
+ onStylePalettes,
66739
66763
  ...doenetEditorProps
66740
66764
  }, forwardedRef) {
66741
66765
  const [id2, _2] = React__default.useState(() => Math.random().toString(36).slice(2));
@@ -66746,6 +66770,8 @@ const DoenetEditor = React__default.forwardRef(function DoenetEditor2({
66746
66770
  const lastSentFunctionPropsRef = React__default.useRef(null);
66747
66771
  const doenetEditorPropsRef = React__default.useRef(doenetEditorProps);
66748
66772
  doenetEditorPropsRef.current = doenetEditorProps;
66773
+ const onStylePalettesRef = React__default.useRef(onStylePalettes);
66774
+ onStylePalettesRef.current = onStylePalettes;
66749
66775
  const [inErrorState, setInErrorState] = React__default.useState(null);
66750
66776
  const [ignoreDetectedVersion, setIgnoreDetectedVersion] = React__default.useState(false);
66751
66777
  const [initialDiagnostics, setInitialDiagnostics] = React__default.useState([]);
@@ -66852,6 +66878,9 @@ const DoenetEditor = React__default.forwardRef(function DoenetEditor2({
66852
66878
  if (data.error) {
66853
66879
  return setInErrorState(data.error);
66854
66880
  } else if (data.iframeReady) {
66881
+ onStylePalettesRef.current?.(
66882
+ data.stylePalettes ?? null
66883
+ );
66855
66884
  if (ref.current) {
66856
66885
  const editorIframe = wrap$3(
66857
66886
  windowEndpoint(ref.current.contentWindow)