@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 +65 -0
- package/index.d.ts +7 -10
- package/index.js +152 -15
- package/index.js.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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 =
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
|
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,
|