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

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
@@ -1004,7 +1004,7 @@ const defaultPalette$1 = {
1004
1004
  }
1005
1005
  };
1006
1006
  const okabeItoPalette$1 = {
1007
- name: "okabeito",
1007
+ name: "okabeIto",
1008
1008
  description: "Colorblind-friendly blues, oranges, greens, and purples adapted from the Okabe-Ito palette, with varied marker shapes and line styles.",
1009
1009
  styles: {
1010
1010
  1: {
@@ -1175,7 +1175,7 @@ const okabeItoPalette$1 = {
1175
1175
  }
1176
1176
  };
1177
1177
  const tolBrightPalette$1 = {
1178
- name: "tolbright",
1178
+ name: "tolBright",
1179
1179
  description: "Colorblind-friendly bright blues, reds, greens, and purples from Paul Tol's bright palette, with varied marker shapes and line styles.",
1180
1180
  styles: {
1181
1181
  1: {
@@ -1309,7 +1309,7 @@ const tolBrightPalette$1 = {
1309
1309
  }
1310
1310
  };
1311
1311
  const tolMutedPalette$1 = {
1312
- name: "tolmuted",
1312
+ name: "tolMuted",
1313
1313
  description: "Nine colorblind-friendly muted styles from Paul Tol's muted palette, with varied marker shapes and line styles.",
1314
1314
  styles: {
1315
1315
  1: {
@@ -1508,7 +1508,7 @@ const tolMutedPalette$1 = {
1508
1508
  }
1509
1509
  };
1510
1510
  const tolHighContrastPalette$1 = {
1511
- name: "tolhighcontrast",
1511
+ name: "tolHighContrast",
1512
1512
  description: "Four maximum-distinction styles from Paul Tol's high-contrast palette, distinguishable even in grayscale.",
1513
1513
  styles: {
1514
1514
  1: {
@@ -2018,7 +2018,7 @@ const categoricalPalette$1 = {
2018
2018
  }
2019
2019
  };
2020
2020
  const grumpyNarwhalPalette$1 = {
2021
- name: "grumpynarwhal",
2021
+ name: "grumpyNarwhal",
2022
2022
  description: "Six saturated hues — burnt orange, forest green, deep pink, arctic teal, gold, and purple — going neon in dark mode.",
2023
2023
  styles: {
2024
2024
  1: {
@@ -2174,24 +2174,27 @@ function deepFreezePalette$1(palette) {
2174
2174
  Object.freeze(palette.styles);
2175
2175
  return Object.freeze(palette);
2176
2176
  }
2177
+ function registerByKey$1(palette) {
2178
+ return [palette.name.toLowerCase(), deepFreezePalette$1(palette)];
2179
+ }
2177
2180
  const STYLE_PALETTES$1 = Object.assign(
2178
2181
  /* @__PURE__ */ Object.create(null),
2179
- {
2180
- [defaultPalette$1.name]: deepFreezePalette$1(defaultPalette$1),
2181
- [okabeItoPalette$1.name]: deepFreezePalette$1(okabeItoPalette$1),
2182
- [tolBrightPalette$1.name]: deepFreezePalette$1(tolBrightPalette$1),
2183
- [tolMutedPalette$1.name]: deepFreezePalette$1(tolMutedPalette$1),
2184
- [tolHighContrastPalette$1.name]: deepFreezePalette$1(
2185
- tolHighContrastPalette$1
2186
- ),
2187
- [ibmPalette$1.name]: deepFreezePalette$1(ibmPalette$1),
2188
- [grayscalePalette$1.name]: deepFreezePalette$1(grayscalePalette$1),
2189
- [categoricalPalette$1.name]: deepFreezePalette$1(categoricalPalette$1),
2190
- [grumpyNarwhalPalette$1.name]: deepFreezePalette$1(grumpyNarwhalPalette$1)
2191
- }
2182
+ Object.fromEntries(
2183
+ [
2184
+ defaultPalette$1,
2185
+ okabeItoPalette$1,
2186
+ tolBrightPalette$1,
2187
+ tolMutedPalette$1,
2188
+ tolHighContrastPalette$1,
2189
+ ibmPalette$1,
2190
+ grayscalePalette$1,
2191
+ categoricalPalette$1,
2192
+ grumpyNarwhalPalette$1
2193
+ ].map(registerByKey$1)
2194
+ )
2192
2195
  );
2193
2196
  Object.freeze(STYLE_PALETTES$1);
2194
- defaultPalette$1.name;
2197
+ defaultPalette$1.name.toLowerCase();
2195
2198
  Object.freeze(
2196
2199
  Object.keys(STYLE_PALETTES$1)
2197
2200
  );
@@ -26957,7 +26960,7 @@ function requireCssesc$1() {
26957
26960
  return cssesc_1$1;
26958
26961
  }
26959
26962
  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';
26963
+ 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
26964
  const editorIframeJsSource = `(function() {
26962
26965
  "use strict";
26963
26966
  let editorControlHandle = null;
@@ -27126,7 +27129,10 @@ const editorIframeJsSource = `(function() {
27126
27129
  }
27127
27130
  (async () => {
27128
27131
  if (await waitForStandaloneBundle(6e4)) {
27129
- messageParentFromEditor({ iframeReady: true });
27132
+ messageParentFromEditor({
27133
+ iframeReady: true,
27134
+ stylePalettes: readStylePalettesFromBundle()
27135
+ });
27130
27136
  } else {
27131
27137
  messageParentFromEditor({
27132
27138
  error: "Invalid DoenetML version or DoenetML package not found"
@@ -27144,6 +27150,17 @@ const editorIframeJsSource = `(function() {
27144
27150
  } catch {
27145
27151
  }
27146
27152
  });
27153
+ function readStylePalettesFromBundle() {
27154
+ try {
27155
+ return window.getDoenetStylePalettes?.() ?? null;
27156
+ } catch (err) {
27157
+ console.error(
27158
+ "iframe DoenetML: failed to read style palettes from the standalone bundle",
27159
+ err
27160
+ );
27161
+ return null;
27162
+ }
27163
+ }
27147
27164
  function messageParentFromEditor(data) {
27148
27165
  window.parent.postMessage(
27149
27166
  {
@@ -40049,7 +40066,7 @@ const defaultPalette = {
40049
40066
  }
40050
40067
  };
40051
40068
  const okabeItoPalette = {
40052
- name: "okabeito",
40069
+ name: "okabeIto",
40053
40070
  description: "Colorblind-friendly blues, oranges, greens, and purples adapted from the Okabe-Ito palette, with varied marker shapes and line styles.",
40054
40071
  styles: {
40055
40072
  1: {
@@ -40220,7 +40237,7 @@ const okabeItoPalette = {
40220
40237
  }
40221
40238
  };
40222
40239
  const tolBrightPalette = {
40223
- name: "tolbright",
40240
+ name: "tolBright",
40224
40241
  description: "Colorblind-friendly bright blues, reds, greens, and purples from Paul Tol's bright palette, with varied marker shapes and line styles.",
40225
40242
  styles: {
40226
40243
  1: {
@@ -40354,7 +40371,7 @@ const tolBrightPalette = {
40354
40371
  }
40355
40372
  };
40356
40373
  const tolMutedPalette = {
40357
- name: "tolmuted",
40374
+ name: "tolMuted",
40358
40375
  description: "Nine colorblind-friendly muted styles from Paul Tol's muted palette, with varied marker shapes and line styles.",
40359
40376
  styles: {
40360
40377
  1: {
@@ -40553,7 +40570,7 @@ const tolMutedPalette = {
40553
40570
  }
40554
40571
  };
40555
40572
  const tolHighContrastPalette = {
40556
- name: "tolhighcontrast",
40573
+ name: "tolHighContrast",
40557
40574
  description: "Four maximum-distinction styles from Paul Tol's high-contrast palette, distinguishable even in grayscale.",
40558
40575
  styles: {
40559
40576
  1: {
@@ -41063,7 +41080,7 @@ const categoricalPalette = {
41063
41080
  }
41064
41081
  };
41065
41082
  const grumpyNarwhalPalette = {
41066
- name: "grumpynarwhal",
41083
+ name: "grumpyNarwhal",
41067
41084
  description: "Six saturated hues — burnt orange, forest green, deep pink, arctic teal, gold, and purple — going neon in dark mode.",
41068
41085
  styles: {
41069
41086
  1: {
@@ -41219,24 +41236,27 @@ function deepFreezePalette(palette) {
41219
41236
  Object.freeze(palette.styles);
41220
41237
  return Object.freeze(palette);
41221
41238
  }
41239
+ function registerByKey(palette) {
41240
+ return [palette.name.toLowerCase(), deepFreezePalette(palette)];
41241
+ }
41222
41242
  const STYLE_PALETTES = Object.assign(
41223
41243
  /* @__PURE__ */ Object.create(null),
41224
- {
41225
- [defaultPalette.name]: deepFreezePalette(defaultPalette),
41226
- [okabeItoPalette.name]: deepFreezePalette(okabeItoPalette),
41227
- [tolBrightPalette.name]: deepFreezePalette(tolBrightPalette),
41228
- [tolMutedPalette.name]: deepFreezePalette(tolMutedPalette),
41229
- [tolHighContrastPalette.name]: deepFreezePalette(
41230
- tolHighContrastPalette
41231
- ),
41232
- [ibmPalette.name]: deepFreezePalette(ibmPalette),
41233
- [grayscalePalette.name]: deepFreezePalette(grayscalePalette),
41234
- [categoricalPalette.name]: deepFreezePalette(categoricalPalette),
41235
- [grumpyNarwhalPalette.name]: deepFreezePalette(grumpyNarwhalPalette)
41236
- }
41244
+ Object.fromEntries(
41245
+ [
41246
+ defaultPalette,
41247
+ okabeItoPalette,
41248
+ tolBrightPalette,
41249
+ tolMutedPalette,
41250
+ tolHighContrastPalette,
41251
+ ibmPalette,
41252
+ grayscalePalette,
41253
+ categoricalPalette,
41254
+ grumpyNarwhalPalette
41255
+ ].map(registerByKey)
41256
+ )
41237
41257
  );
41238
41258
  Object.freeze(STYLE_PALETTES);
41239
- defaultPalette.name;
41259
+ defaultPalette.name.toLowerCase();
41240
41260
  Object.freeze(
41241
41261
  Object.keys(STYLE_PALETTES)
41242
41262
  );
@@ -66089,7 +66109,7 @@ function ExternalVirtualKeyboard({
66089
66109
  }
66090
66110
  );
66091
66111
  }
66092
- const version = "0.7.21-dev.352";
66112
+ const version = "0.7.21-dev.354";
66093
66113
  const latestDoenetmlVersion = version;
66094
66114
  function subscribeToPinnedTheme() {
66095
66115
  return () => {
@@ -66113,6 +66133,7 @@ function DoenetViewer({
66113
66133
  useSharedCoreWorker = false,
66114
66134
  mountPolicy,
66115
66135
  keepLive = false,
66136
+ onStylePalettes,
66116
66137
  ...doenetViewerProps
66117
66138
  }) {
66118
66139
  const [id2, _2] = React__default.useState(() => Math.random().toString(36).slice(2));
@@ -66123,6 +66144,8 @@ function DoenetViewer({
66123
66144
  const lastSentFunctionPropsRef = React__default.useRef(null);
66124
66145
  const doenetViewerPropsRef = React__default.useRef(doenetViewerProps);
66125
66146
  doenetViewerPropsRef.current = doenetViewerProps;
66147
+ const onStylePalettesRef = React__default.useRef(onStylePalettes);
66148
+ onStylePalettesRef.current = onStylePalettes;
66126
66149
  const doenetMLRef = React__default.useRef(doenetML);
66127
66150
  doenetMLRef.current = doenetML;
66128
66151
  const windowed = mountPolicy?.mode === "windowed";
@@ -66369,6 +66392,9 @@ function DoenetViewer({
66369
66392
  }
66370
66393
  return setInErrorState(data.error);
66371
66394
  } else if (data.iframeReady) {
66395
+ onStylePalettesRef.current?.(
66396
+ data.stylePalettes ?? null
66397
+ );
66372
66398
  if (ref.current) {
66373
66399
  const viewerIframe = wrap$3(
66374
66400
  windowEndpoint(ref.current.contentWindow)
@@ -66721,6 +66747,9 @@ function serializePropsSnapshot(seed, props) {
66721
66747
  snapshot[key] = val;
66722
66748
  }
66723
66749
  }
66750
+ if (snapshot.styleOverrides === void 0) {
66751
+ snapshot.styleOverrides = null;
66752
+ }
66724
66753
  return JSON.stringify(snapshot);
66725
66754
  }
66726
66755
  function logComlinkError(component, method) {
@@ -66736,6 +66765,7 @@ const DoenetEditor = React__default.forwardRef(function DoenetEditor2({
66736
66765
  width = "100%",
66737
66766
  height = "500px",
66738
66767
  autodetectVersion = true,
66768
+ onStylePalettes,
66739
66769
  ...doenetEditorProps
66740
66770
  }, forwardedRef) {
66741
66771
  const [id2, _2] = React__default.useState(() => Math.random().toString(36).slice(2));
@@ -66746,6 +66776,8 @@ const DoenetEditor = React__default.forwardRef(function DoenetEditor2({
66746
66776
  const lastSentFunctionPropsRef = React__default.useRef(null);
66747
66777
  const doenetEditorPropsRef = React__default.useRef(doenetEditorProps);
66748
66778
  doenetEditorPropsRef.current = doenetEditorProps;
66779
+ const onStylePalettesRef = React__default.useRef(onStylePalettes);
66780
+ onStylePalettesRef.current = onStylePalettes;
66749
66781
  const [inErrorState, setInErrorState] = React__default.useState(null);
66750
66782
  const [ignoreDetectedVersion, setIgnoreDetectedVersion] = React__default.useState(false);
66751
66783
  const [initialDiagnostics, setInitialDiagnostics] = React__default.useState([]);
@@ -66852,6 +66884,9 @@ const DoenetEditor = React__default.forwardRef(function DoenetEditor2({
66852
66884
  if (data.error) {
66853
66885
  return setInErrorState(data.error);
66854
66886
  } else if (data.iframeReady) {
66887
+ onStylePalettesRef.current?.(
66888
+ data.stylePalettes ?? null
66889
+ );
66855
66890
  if (ref.current) {
66856
66891
  const editorIframe = wrap$3(
66857
66892
  windowEndpoint(ref.current.contentWindow)