@doenet/doenetml-iframe 0.7.20-dev.324 → 0.7.20-dev.326
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 +77 -0
- package/index.d.ts +90 -5
- package/index.js +639 -139
- package/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -71,6 +71,83 @@ latest editor buffer:
|
|
|
71
71
|
|
|
72
72
|
## DoenetViewer
|
|
73
73
|
|
|
74
|
+
### Prop changes and iframe reloads
|
|
75
|
+
|
|
76
|
+
Changing props on a mounted `<DoenetViewer>` does **not** reload the iframe:
|
|
77
|
+
the wrapper pushes changes into the already-loaded iframe as messages, and
|
|
78
|
+
the inner viewer applies them with the same semantics as the in-process
|
|
79
|
+
`<DoenetViewer>` from `@doenet/doenetml`. In particular:
|
|
80
|
+
|
|
81
|
+
| Prop change | Effect |
|
|
82
|
+
| -------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
|
|
83
|
+
| `render`, `darkMode`, `flags`, `answerResponseCounts`, callbacks, … | applied live, no reload (flipping `render` false→true starts the document in the already-loaded realm) |
|
|
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
|
+
| `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
|
+
| `standaloneUrl`, `cssUrl`, `doenetmlVersion` (or a version change detected in `doenetML`), `useSharedCoreWorker` | a different bundle/realm is required, so the iframe reloads |
|
|
87
|
+
|
|
88
|
+
To force a full remount (fresh realm and worker), change the component's
|
|
89
|
+
React `key`.
|
|
90
|
+
|
|
91
|
+
Function props (callbacks) are forwarded across the iframe boundary via
|
|
92
|
+
Comlink proxies and always follow the latest identity passed — parents may
|
|
93
|
+
pass inline arrow functions freely; identity churn does not re-render the
|
|
94
|
+
inner viewer.
|
|
95
|
+
|
|
96
|
+
> **Note:** in-place updates require a standalone bundle ≥ 0.7.18. When an
|
|
97
|
+
> older version is pinned (via `doenetmlVersion` or detected from the
|
|
98
|
+
> document's `xmlns`), the wrapper falls back to its historical behavior of
|
|
99
|
+
> reloading the iframe on any prop change.
|
|
100
|
+
|
|
101
|
+
### Windowed mounting (`mountPolicy`)
|
|
102
|
+
|
|
103
|
+
Pages that embed many viewers (assignment pages, textbook chapters) pay
|
|
104
|
+
memory for every mounted iframe, whether or not the student can see it. The
|
|
105
|
+
opt-in `mountPolicy` prop bounds this: at most `maxLiveViewers` viewers stay
|
|
106
|
+
live page-wide, and off-screen viewers beyond the budget are **parked** —
|
|
107
|
+
their state is flushed (via the `SPLICE.flushState` machinery) and their
|
|
108
|
+
iframe is replaced by a placeholder of the same height, so the page layout
|
|
109
|
+
doesn't shift. Scrolling a parked viewer back near the viewport restores it,
|
|
110
|
+
seeded with the flushed state: typed work survives the round trip with no
|
|
111
|
+
user interaction.
|
|
112
|
+
|
|
113
|
+
```tsx
|
|
114
|
+
<DoenetViewer
|
|
115
|
+
doenetML={doenetML}
|
|
116
|
+
flags={{ allowSaveState: true }}
|
|
117
|
+
mountPolicy={{ mode: "windowed", maxLiveViewers: 3 }}
|
|
118
|
+
/>
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
- `maxLiveViewers` (default 3) — page-wide budget, shared by every windowed
|
|
122
|
+
viewer (the smallest value wins when viewers disagree). The budget is
|
|
123
|
+
soft: currently-visible viewers are never parked, even over budget.
|
|
124
|
+
- `visibleMargin` (default `"1000px"`) — how far outside the viewport a
|
|
125
|
+
viewer still counts as visible (the `IntersectionObserver` rootMargin).
|
|
126
|
+
- `parkDelayMs` (default 2000) — how long a viewer must stay off-screen
|
|
127
|
+
before it may be parked (debounce against scroll flicker).
|
|
128
|
+
- `flushTimeoutMs` (default 5000) — how long to wait for the pre-park state
|
|
129
|
+
flush to be acknowledged before parking anyway.
|
|
130
|
+
|
|
131
|
+
**Parking requires a persistence path** so no student work can be lost:
|
|
132
|
+
either `flags.allowSaveState` (the wrapper snapshots the flushed
|
|
133
|
+
`reportScoreAndState` and seeds `initialState` on restore) or
|
|
134
|
+
`flags.allowLocalState` (IndexedDB restores on reboot). Windowed viewers
|
|
135
|
+
with neither flag always stay live (a console warning points this out).
|
|
136
|
+
|
|
137
|
+
Notes:
|
|
138
|
+
|
|
139
|
+
- Eviction is least-recently-visible first.
|
|
140
|
+
- While parked, a viewer emits no reports (its state was flushed at park
|
|
141
|
+
time). A host `SPLICE.flushState` broadcast is answered by the wrapper on
|
|
142
|
+
the parked viewer's behalf, so pre-navigation flush round-trips don't
|
|
143
|
+
hang.
|
|
144
|
+
- When `requestedVariantIndex` is not specified, windowed viewers pin a
|
|
145
|
+
random variant once per mount so a restore cannot reroll the document.
|
|
146
|
+
- Restoring pays a fresh iframe boot (bundle evaluation + core boot, ~1–2 s
|
|
147
|
+
warm). Pair with `useSharedCoreWorker` to make both live and restored
|
|
148
|
+
viewers cheaper.
|
|
149
|
+
- `mountPolicy` is read at mount; changing it afterwards is not supported.
|
|
150
|
+
|
|
74
151
|
### Host message protocol (SPLICE)
|
|
75
152
|
|
|
76
153
|
The viewer exchanges JSON messages with the host page via `postMessage`.
|
package/index.d.ts
CHANGED
|
@@ -70,12 +70,23 @@ declare type DoenetEditorProps = Omit<React.ComponentProps<typeof DoenetEditor_2
|
|
|
70
70
|
* Render Doenet viewer constrained to an iframe. A URL pointing to a version of DoenetML
|
|
71
71
|
* standalone must be provided (along with a URL to the corresponding CSS file).
|
|
72
72
|
*
|
|
73
|
-
* Parameters
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
73
|
+
* Parameters for the underlying `DoenetViewer` component are passed via props.
|
|
74
|
+
* Serializable prop changes after mount are pushed into the iframe as
|
|
75
|
+
* messages and applied in place with the same semantics as the in-process
|
|
76
|
+
* `<DoenetViewer>`: e.g. flipping `render` starts the document without a
|
|
77
|
+
* reload, and changing `doenetML` (or `activityId`/`docId`/`attemptNumber`/
|
|
78
|
+
* `requestedVariantIndex`) re-initializes the document's core inside the
|
|
79
|
+
* same iframe realm — the multi-MB standalone bundle is not re-parsed. Only
|
|
80
|
+
* a change of the bundle itself (`standaloneUrl`/`cssUrl`/`doenetmlVersion`,
|
|
81
|
+
* a version change detected in `doenetML`, or `useSharedCoreWorker`) reloads
|
|
82
|
+
* the iframe. To force a full remount instead, change the component's `key`.
|
|
83
|
+
*
|
|
84
|
+
* Function props are forwarded across the iframe boundary via Comlink
|
|
85
|
+
* proxies and follow the latest identity passed. (Bundles older than
|
|
86
|
+
* v0.7.18 cannot re-render in place; for those the wrapper falls back to
|
|
87
|
+
* reloading the iframe on any prop change, its historical behavior.)
|
|
77
88
|
*/
|
|
78
|
-
export declare function DoenetViewer({ doenetML, standaloneUrl: specifiedStandaloneUrl, cssUrl: specifiedCssUrl, doenetmlVersion: specifiedDoenetmlVersion, autodetectVersion, useSharedCoreWorker, ...doenetViewerProps }: DoenetViewerIframeProps): default_2.JSX.Element | null;
|
|
89
|
+
export declare function DoenetViewer({ doenetML, standaloneUrl: specifiedStandaloneUrl, cssUrl: specifiedCssUrl, doenetmlVersion: specifiedDoenetmlVersion, autodetectVersion, useSharedCoreWorker, mountPolicy, ...doenetViewerProps }: DoenetViewerIframeProps): default_2.JSX.Element | null;
|
|
79
90
|
|
|
80
91
|
export declare type DoenetViewerIframeProps = DoenetViewerProps & {
|
|
81
92
|
doenetML: string;
|
|
@@ -111,6 +122,20 @@ export declare type DoenetViewerIframeProps = DoenetViewerProps & {
|
|
|
111
122
|
* quarantined so retries boot fresh ones). Default off.
|
|
112
123
|
*/
|
|
113
124
|
useSharedCoreWorker?: boolean;
|
|
125
|
+
/**
|
|
126
|
+
* Opt-in windowed mounting (#1441, stream B): keep at most
|
|
127
|
+
* `maxLiveViewers` viewers live on the page. Off-screen viewers beyond
|
|
128
|
+
* the budget are *parked* — their state is flushed (`SPLICE.flushState`)
|
|
129
|
+
* and their iframe is replaced by a fixed-height placeholder — and
|
|
130
|
+
* restored when scrolled back near the viewport. Parking requires a
|
|
131
|
+
* persistence path: it only activates for viewers with
|
|
132
|
+
* `flags.allowSaveState` (the wrapper snapshots the flushed
|
|
133
|
+
* `reportScoreAndState` and seeds `initialState` on restore) or
|
|
134
|
+
* `flags.allowLocalState` (IndexedDB restores on reboot); otherwise the
|
|
135
|
+
* viewer always stays live. The policy is read at mount; changing it
|
|
136
|
+
* afterwards is not supported. Default off (no prop = today's behavior).
|
|
137
|
+
*/
|
|
138
|
+
mountPolicy?: MountPolicy;
|
|
114
139
|
};
|
|
115
140
|
|
|
116
141
|
declare type DoenetViewerProps = Omit<React.ComponentProps<typeof DoenetViewer_2>, "doenetML" | "externalVirtualKeyboardProvided">;
|
|
@@ -121,6 +146,14 @@ export { getMediaLicenseDisplay }
|
|
|
121
146
|
|
|
122
147
|
export { getMediaLicenseInfo }
|
|
123
148
|
|
|
149
|
+
/** Snapshot for tests and diagnostics. */
|
|
150
|
+
export declare function getViewerLifecycleStats(): {
|
|
151
|
+
registered: number;
|
|
152
|
+
live: number;
|
|
153
|
+
parking: number;
|
|
154
|
+
parked: number;
|
|
155
|
+
};
|
|
156
|
+
|
|
124
157
|
export { mathjaxConfig }
|
|
125
158
|
|
|
126
159
|
export { MediaLicenseDisplay }
|
|
@@ -131,6 +164,58 @@ export { MediaLicenseKind }
|
|
|
131
164
|
|
|
132
165
|
export { mediaLicenses }
|
|
133
166
|
|
|
167
|
+
/**
|
|
168
|
+
* Page-wide lifecycle manager for windowed `<DoenetViewer>` mounting
|
|
169
|
+
* (#1441, stream B).
|
|
170
|
+
*
|
|
171
|
+
* Viewers that opt in via the `mountPolicy` prop register here. The manager
|
|
172
|
+
* enforces a page-wide budget of live viewers: when more than
|
|
173
|
+
* `maxLiveViewers` are live at once, the least-recently-visible off-screen
|
|
174
|
+
* viewers are asked to park (flush their state and replace their iframe with
|
|
175
|
+
* a placeholder), so idle memory tracks what the user can see rather than
|
|
176
|
+
* how many documents the page embeds.
|
|
177
|
+
*
|
|
178
|
+
* This module is pure policy: *when* to park. The mechanics of parking
|
|
179
|
+
* (flushing state, swapping the iframe for a placeholder, restoring) live in
|
|
180
|
+
* the `DoenetViewer` wrapper, which registers callbacks here. Module-level
|
|
181
|
+
* state is intentional — the budget is shared by every windowed viewer on
|
|
182
|
+
* the page (same pattern as `shared-core-pool.ts`).
|
|
183
|
+
*
|
|
184
|
+
* Rules:
|
|
185
|
+
* - A currently-visible viewer is never parked (the budget is soft: if more
|
|
186
|
+
* than `maxLiveViewers` are visible at once, all of them stay live).
|
|
187
|
+
* - A viewer only becomes eligible to park after it has been off-screen for
|
|
188
|
+
* its `parkDelayMs` (debounce against scroll flicker).
|
|
189
|
+
* - Viewers whose `canPark` returns false (no persistence path — parking
|
|
190
|
+
* would lose student work) are never parked.
|
|
191
|
+
* - Eviction order is least-recently-visible first.
|
|
192
|
+
*/
|
|
193
|
+
export declare type MountPolicy = {
|
|
194
|
+
/** The only mode currently defined. */
|
|
195
|
+
mode: "windowed";
|
|
196
|
+
/**
|
|
197
|
+
* Page-wide budget of simultaneously live (iframe-mounted) viewers.
|
|
198
|
+
* When viewers on the same page specify different values, the smallest
|
|
199
|
+
* wins. Default 3.
|
|
200
|
+
*/
|
|
201
|
+
maxLiveViewers?: number;
|
|
202
|
+
/**
|
|
203
|
+
* `rootMargin` for the visibility observer — how far outside the
|
|
204
|
+
* viewport a viewer still counts as "visible". Default "1000px".
|
|
205
|
+
*/
|
|
206
|
+
visibleMargin?: string;
|
|
207
|
+
/**
|
|
208
|
+
* How long to wait for the viewer to acknowledge the pre-park state
|
|
209
|
+
* flush before parking anyway. Default 5000.
|
|
210
|
+
*/
|
|
211
|
+
flushTimeoutMs?: number;
|
|
212
|
+
/**
|
|
213
|
+
* How long a viewer must be continuously off-screen before it may be
|
|
214
|
+
* parked. Default 2000.
|
|
215
|
+
*/
|
|
216
|
+
parkDelayMs?: number;
|
|
217
|
+
};
|
|
218
|
+
|
|
134
219
|
export declare const version: string;
|
|
135
220
|
|
|
136
221
|
export { WarningRecord }
|