@doenet/doenetml-iframe 0.7.16 → 0.7.17-dev.20260513000951.fa93129

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
@@ -4,6 +4,71 @@ This workspace contains a DoenetML viewer and editor that render inside of an if
4
4
  This allows DoenetML to be used without affecting the surrounding page.
5
5
  It also allows multiple versions of DoenetML to be used at the same time.
6
6
 
7
+ ## DoenetEditor
8
+
9
+ ### Programmatic control of the diagnostics panel
10
+
11
+ The iframe `<DoenetEditor>` accepts the same `initialOpenTab` prop and
12
+ `DoenetEditorHandle` ref API as the in-process `<DoenetEditor>` from
13
+ `@doenet/doenetml`. Calls bridge into the iframe via ComLink. If the
14
+ ref handle is invoked before the iframe has finished loading, the call
15
+ is queued in the outer wrapper and replayed once the iframe is ready —
16
+ consumers do not need to coordinate timing.
17
+
18
+ ```tsx
19
+ import { useRef } from "react";
20
+ import {
21
+ DoenetEditor,
22
+ type DoenetEditorHandle,
23
+ } from "@doenet/doenetml-iframe";
24
+
25
+ function App() {
26
+ const editorRef = useRef<DoenetEditorHandle>(null);
27
+ return (
28
+ <>
29
+ <button
30
+ onClick={() =>
31
+ editorRef.current?.openDiagnosticsTab("accessibility")
32
+ }
33
+ >
34
+ Show accessibility violations
35
+ </button>
36
+ <DoenetEditor ref={editorRef} doenetML="..." />
37
+ </>
38
+ );
39
+ }
40
+ ```
41
+
42
+ Mount-time form (panel opens on the requested tab on first paint):
43
+
44
+ ```tsx
45
+ <DoenetEditor doenetML="..." initialOpenTab="accessibility" />
46
+ ```
47
+
48
+ Valid tab IDs: `"errors" | "warnings" | "info" | "accessibility" | "responses"`.
49
+ See the `@doenet/doenetml` README for usage patterns including the lazy-mount
50
+ "link in a different panel" scenario.
51
+
52
+ ### Programmatically updating the rendered view
53
+
54
+ The handle also exposes `updateRenderedView()`, which forwards across the
55
+ iframe to "press" the editor's Update button. Pair it with
56
+ `diagnosticsSummaryCallback` (which receives the source the viewer
57
+ rendered against as its second argument) to ensure diagnostics reflect the
58
+ latest editor buffer:
59
+
60
+ ```tsx
61
+ <button onClick={() => editorRef.current?.updateRenderedView()}>
62
+ Update viewer
63
+ </button>
64
+ ```
65
+
66
+ > **Note:** The handle methods are fire-and-forget across the iframe boundary.
67
+ > Although they share the same `DoenetEditorHandle` type as the in-process
68
+ > editor (so consumers can swap implementations), the iframe variant cannot
69
+ > surface a completion signal or error to the caller — failures from the
70
+ > underlying ComLink RPC are logged to the console rather than thrown.
71
+
7
72
  ## Development
8
73
 
9
74
  Source code in `src/iframe-viewer-index.ts` and `src/iframe-editor-index.ts`
package/index.d.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  import { default as default_2 } from 'react';
2
2
  import { DiagnosticRecord } from '../packages/utils/dist';
3
+ import { DiagnosticsTabId } from '../packages/doenetml/dist';
3
4
  import { DoenetEditor as DoenetEditor_2 } from '../packages/doenetml/dist';
5
+ import { DoenetEditorHandle } from '../packages/doenetml/dist';
4
6
  import { DoenetViewer as DoenetViewer_2 } from '../packages/doenetml/dist';
5
7
  import { ErrorRecord } from '../packages/utils/dist';
6
8
  import { mathjaxConfig } from '../packages/utils/dist';
@@ -8,16 +10,11 @@ import { WarningRecord } from '../packages/utils/dist';
8
10
 
9
11
  export { DiagnosticRecord }
10
12
 
11
- /**
12
- * Render Doenet Editor constrained to an iframe. A URL pointing to a version of DoenetML
13
- * standalone must be provided (along with a URL to the corresponding CSS file).
14
- *
15
- * Parameters being passed to the underlying DoenetML component are passed via the `DoenetEditorProps` prop.
16
- * However, only serializable parameters may be passed. E.g., callbacks **cannot** be passed to the underlying
17
- * DoenetML component. Instead you must use the message passing interface of `DoenetEditor` to communicate
18
- * with the underlying DoenetML component.
19
- */
20
- export declare function DoenetEditor({ doenetML, standaloneUrl: specifiedStandaloneUrl, cssUrl: specifiedCssUrl, doenetmlVersion: specifiedDoenetmlVersion, width, height, autodetectVersion, ...doenetEditorProps }: DoenetEditorIframeProps): default_2.JSX.Element | null;
13
+ export { DiagnosticsTabId }
14
+
15
+ export declare const DoenetEditor: default_2.ForwardRefExoticComponent<Omit<DoenetEditorIframeProps, "ref"> & default_2.RefAttributes<DoenetEditorHandle>>;
16
+
17
+ export { DoenetEditorHandle }
21
18
 
22
19
  export declare type DoenetEditorIframeProps = DoenetEditorProps & {
23
20
  doenetML: string;
package/index.js CHANGED
@@ -25341,7 +25341,92 @@ function requireCssesc$1() {
25341
25341
  }
25342
25342
  requireCssesc$1();
25343
25343
  const viewerIframeJsSource = '(function() {\n "use strict";\n document.addEventListener("DOMContentLoaded", async () => {\n let pause100 = function() {\n return new Promise((resolve, _reject) => {\n setTimeout(resolve, 100);\n });\n };\n for (let i = 0; i < 10; i++) {\n if (typeof window.renderDoenetViewerToContainer === "function") {\n break;\n }\n await pause100();\n }\n if (typeof window.renderDoenetViewerToContainer !== "function") {\n return messageParentFromViewer({\n error: "Invalid DoenetML version or DoenetML package not found"\n });\n }\n });\n ComlinkViewer.expose(\n { renderViewerWithFunctionProps },\n ComlinkViewer.windowEndpoint(globalThis.parent)\n );\n function renderViewerWithFunctionProps(...args) {\n const augmentedDoenetViewerProps = { ...doenetViewerProps };\n augmentedDoenetViewerProps.externalVirtualKeyboardProvided = true;\n for (const propName of doenetViewerPropsSpecified) {\n if (!(propName in doenetViewerProps)) {\n const idx = args.indexOf(propName);\n if (idx !== -1) {\n augmentedDoenetViewerProps[propName] = args[idx + 1];\n }\n }\n }\n if (!doenetViewerPropsSpecified.includes("requestScrollTo")) {\n augmentedDoenetViewerProps.requestScrollTo = (offset) => {\n messageParentFromViewer({ type: "scrollTo", offset });\n };\n }\n window.renderDoenetViewerToContainer(\n document.getElementById("root"),\n void 0,\n augmentedDoenetViewerProps\n );\n }\n messageParentFromViewer({ iframeReady: true });\n function messageParentFromViewer(data) {\n window.parent.postMessage(\n {\n origin: viewerId,\n data\n },\n window.parent.origin\n );\n }\n})();\n';
25344
- const editorIframeJsSource = '(function() {\n "use strict";\n document.addEventListener("DOMContentLoaded", async () => {\n let pause100 = function() {\n return new Promise((resolve, _reject) => {\n setTimeout(resolve, 100);\n });\n };\n for (let i = 0; i < 10; i++) {\n if (typeof window.renderDoenetViewerToContainer === "function") {\n break;\n }\n await pause100();\n }\n if (typeof window.renderDoenetEditorToContainer !== "function") {\n return messageParentFromEditor({\n error: "Invalid DoenetML version or DoenetML package not found"\n });\n }\n });\n ComlinkEditor.expose(\n { renderEditorWithFunctionProps },\n ComlinkEditor.windowEndpoint(globalThis.parent)\n );\n function renderEditorWithFunctionProps(...args) {\n const augmentedDoenetEditorProps = { ...doenetEditorProps };\n augmentedDoenetEditorProps.externalVirtualKeyboardProvided = true;\n for (const propName of doenetEditorPropsSpecified) {\n if (!(propName in doenetEditorProps)) {\n const idx = args.indexOf(propName);\n if (idx !== -1) {\n augmentedDoenetEditorProps[propName] = args[idx + 1];\n }\n }\n }\n window.renderDoenetEditorToContainer(\n document.getElementById("root"),\n void 0,\n augmentedDoenetEditorProps\n );\n }\n messageParentFromEditor({ iframeReady: true });\n function messageParentFromEditor(data) {\n window.parent.postMessage(\n {\n origin: editorId,\n data\n },\n window.parent.origin\n );\n }\n})();\n';
25344
+ const editorIframeJsSource = `(function() {
25345
+ "use strict";
25346
+ let editorControlHandle = null;
25347
+ document.addEventListener("DOMContentLoaded", async () => {
25348
+ let pause100 = function() {
25349
+ return new Promise((resolve, _reject) => {
25350
+ setTimeout(resolve, 100);
25351
+ });
25352
+ };
25353
+ for (let i = 0; i < 10; i++) {
25354
+ if (typeof window.renderDoenetViewerToContainer === "function") {
25355
+ break;
25356
+ }
25357
+ await pause100();
25358
+ }
25359
+ if (typeof window.renderDoenetEditorToContainer !== "function") {
25360
+ return messageParentFromEditor({
25361
+ error: "Invalid DoenetML version or DoenetML package not found"
25362
+ });
25363
+ }
25364
+ });
25365
+ ComlinkEditor.expose(
25366
+ {
25367
+ renderEditorWithFunctionProps,
25368
+ openDiagnosticsTab(tabId) {
25369
+ if (!editorControlHandle) {
25370
+ console.warn(
25371
+ "iframe DoenetEditor: openDiagnosticsTab arrived before renderEditorWithFunctionProps completed — likely a bug in the iframe wrapper's queue/replay sequencing."
25372
+ );
25373
+ return;
25374
+ }
25375
+ editorControlHandle.openDiagnosticsTab(tabId);
25376
+ },
25377
+ closeDiagnosticsPanel() {
25378
+ if (!editorControlHandle) {
25379
+ console.warn(
25380
+ "iframe DoenetEditor: closeDiagnosticsPanel arrived before renderEditorWithFunctionProps completed — likely a bug in the iframe wrapper's queue/replay sequencing."
25381
+ );
25382
+ return;
25383
+ }
25384
+ editorControlHandle.closeDiagnosticsPanel();
25385
+ },
25386
+ updateRenderedView() {
25387
+ if (!editorControlHandle) {
25388
+ console.warn(
25389
+ "iframe DoenetEditor: updateRenderedView arrived before renderEditorWithFunctionProps completed — likely a bug in the iframe wrapper's queue/replay sequencing."
25390
+ );
25391
+ return;
25392
+ }
25393
+ editorControlHandle.updateRenderedView();
25394
+ }
25395
+ },
25396
+ ComlinkEditor.windowEndpoint(globalThis.parent)
25397
+ );
25398
+ function renderEditorWithFunctionProps(...args) {
25399
+ const augmentedDoenetEditorProps = { ...doenetEditorProps };
25400
+ augmentedDoenetEditorProps.externalVirtualKeyboardProvided = true;
25401
+ for (const propName of doenetEditorPropsSpecified) {
25402
+ if (!(propName in doenetEditorProps)) {
25403
+ const idx = args.indexOf(propName);
25404
+ if (idx !== -1) {
25405
+ augmentedDoenetEditorProps[propName] = args[idx + 1];
25406
+ }
25407
+ }
25408
+ }
25409
+ const handle = window.renderDoenetEditorToContainer(
25410
+ document.getElementById("root"),
25411
+ void 0,
25412
+ augmentedDoenetEditorProps
25413
+ );
25414
+ if (handle) {
25415
+ editorControlHandle = handle;
25416
+ }
25417
+ }
25418
+ messageParentFromEditor({ iframeReady: true });
25419
+ function messageParentFromEditor(data) {
25420
+ window.parent.postMessage(
25421
+ {
25422
+ origin: editorId,
25423
+ data
25424
+ },
25425
+ window.parent.origin
25426
+ );
25427
+ }
25428
+ })();
25429
+ `;
25345
25430
  function createHtmlForDoenetViewer(id2, doenetML, doenetViewerProps, standaloneUrl, cssUrl) {
25346
25431
  const doenetViewerPropsSpecified = Object.keys(doenetViewerProps);
25347
25432
  return `
@@ -73675,7 +73760,7 @@ function ExternalVirtualKeyboard() {
73675
73760
  }
73676
73761
  );
73677
73762
  }
73678
- const version = "0.7.16";
73763
+ const version = "0.7.17-dev.20260513000951.fa93129";
73679
73764
  const latestDoenetmlVersion = version;
73680
73765
  function DoenetViewer({
73681
73766
  doenetML,
@@ -73821,7 +73906,12 @@ function DoenetViewer({
73821
73906
  }
73822
73907
  ));
73823
73908
  }
73824
- function DoenetEditor({
73909
+ function logComlinkError(method) {
73910
+ return (err) => {
73911
+ console.warn(`iframe DoenetEditor: ${method} failed`, err);
73912
+ };
73913
+ }
73914
+ const DoenetEditor = React__default__default.forwardRef(function DoenetEditor2({
73825
73915
  doenetML,
73826
73916
  standaloneUrl: specifiedStandaloneUrl,
73827
73917
  cssUrl: specifiedCssUrl,
@@ -73830,12 +73920,50 @@ function DoenetEditor({
73830
73920
  height = "500px",
73831
73921
  autodetectVersion = true,
73832
73922
  ...doenetEditorProps
73833
- }) {
73923
+ }, forwardedRef) {
73834
73924
  const [id2, _2] = React__default__default.useState(() => Math.random().toString(36).slice(2));
73835
73925
  const ref = React__default__default.useRef(null);
73926
+ const editorIframeRef = React__default__default.useRef(null);
73927
+ const pendingActions = React__default__default.useRef([]);
73836
73928
  const [inErrorState, setInErrorState] = React__default__default.useState(null);
73837
73929
  const [ignoreDetectedVersion, setIgnoreDetectedVersion] = React__default__default.useState(false);
73838
73930
  const [initialDiagnostics, setInitialDiagnostics] = React__default__default.useState([]);
73931
+ React__default__default.useImperativeHandle(
73932
+ forwardedRef,
73933
+ () => ({
73934
+ openDiagnosticsTab(tabId) {
73935
+ const action = (remote) => {
73936
+ remote.openDiagnosticsTab(tabId).catch(logComlinkError("openDiagnosticsTab"));
73937
+ };
73938
+ if (editorIframeRef.current) {
73939
+ action(editorIframeRef.current);
73940
+ } else {
73941
+ pendingActions.current.push(action);
73942
+ }
73943
+ },
73944
+ closeDiagnosticsPanel() {
73945
+ const action = (remote) => {
73946
+ remote.closeDiagnosticsPanel().catch(logComlinkError("closeDiagnosticsPanel"));
73947
+ };
73948
+ if (editorIframeRef.current) {
73949
+ action(editorIframeRef.current);
73950
+ } else {
73951
+ pendingActions.current.push(action);
73952
+ }
73953
+ },
73954
+ updateRenderedView() {
73955
+ const action = (remote) => {
73956
+ remote.updateRenderedView().catch(logComlinkError("updateRenderedView"));
73957
+ };
73958
+ if (editorIframeRef.current) {
73959
+ action(editorIframeRef.current);
73960
+ } else {
73961
+ pendingActions.current.push(action);
73962
+ }
73963
+ }
73964
+ }),
73965
+ []
73966
+ );
73839
73967
  const augmentedDoenetEditorProps = { ...doenetEditorProps };
73840
73968
  if (augmentedDoenetEditorProps.initialDiagnostics) {
73841
73969
  augmentedDoenetEditorProps.initialDiagnostics = [
@@ -73890,9 +74018,14 @@ function DoenetEditor({
73890
74018
  proxiedFunctions.push(proxy(prop));
73891
74019
  }
73892
74020
  }
73893
- editorIframe?.renderEditorWithFunctionProps(
73894
- ...proxiedFunctions
74021
+ editorIframe?.renderEditorWithFunctionProps(...proxiedFunctions).catch(
74022
+ logComlinkError("renderEditorWithFunctionProps")
73895
74023
  );
74024
+ editorIframeRef.current = editorIframe;
74025
+ const queued = pendingActions.current.splice(0);
74026
+ for (const action of queued) {
74027
+ action(editorIframe);
74028
+ }
73896
74029
  }
73897
74030
  }
73898
74031
  };
@@ -73903,6 +74036,17 @@ function DoenetEditor({
73903
74036
  window.removeEventListener("message", listener);
73904
74037
  };
73905
74038
  }, []);
74039
+ const srcDoc = createHtmlForDoenetEditor(
74040
+ id2,
74041
+ doenetML,
74042
+ width,
74043
+ augmentedDoenetEditorProps,
74044
+ standaloneUrl,
74045
+ cssUrl
74046
+ );
74047
+ React__default__default.useLayoutEffect(() => {
74048
+ editorIframeRef.current = null;
74049
+ }, [srcDoc]);
73906
74050
  if (inErrorState) {
73907
74051
  if (foundAutoVersion) {
73908
74052
  setIgnoreDetectedVersion(true);
@@ -73948,14 +74092,7 @@ function DoenetEditor({
73948
74092
  {
73949
74093
  title: "Doenet Editor",
73950
74094
  ref,
73951
- srcDoc: createHtmlForDoenetEditor(
73952
- id2,
73953
- doenetML,
73954
- width,
73955
- augmentedDoenetEditorProps,
73956
- standaloneUrl,
73957
- cssUrl
73958
- ),
74095
+ srcDoc,
73959
74096
  style: {
73960
74097
  width,
73961
74098
  boxSizing: "content-box",
@@ -73965,7 +74102,7 @@ function DoenetEditor({
73965
74102
  }
73966
74103
  }
73967
74104
  ));
73968
- }
74105
+ });
73969
74106
  export {
73970
74107
  DoenetEditor,
73971
74108
  DoenetViewer,