@doenet/doenetml-iframe 0.7.21-dev.360 → 0.7.21-dev.362

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,8 +80,8 @@ 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`, `styleOverrides`, `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 |
83
+ | `render`, `darkMode`, `styleOverrides`, `uiLocale`, `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`, `documentLocale`, `localeResources` | 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 |
87
87
 
@@ -183,6 +183,32 @@ inner viewer.
183
183
  > document's `xmlns`), the wrapper falls back to its historical behavior of
184
184
  > reloading the iframe on any prop change.
185
185
 
186
+ ### Language (`documentLocale` / `uiLocale`)
187
+
188
+ Doenet keeps the language of the **content** apart from the language of the
189
+ **chrome** (buttons, panel headers, diagnostics), because they genuinely
190
+ differ — a Spanish-speaking student may work a French physics problem.
191
+
192
+ - `documentLocale` — BCP-47 tag for the content's language (`"es"`,
193
+ `"es-MX"`). Defaults to `"en"`. An authored `<document lang="es-MX">`
194
+ overrides it: the author knows what language they wrote in, the host only
195
+ knows what it would prefer to receive.
196
+ - `uiLocale` — BCP-47 tag for the chrome's language. Defaults to following
197
+ `documentLocale`, so a fully Spanish activity is fully Spanish without the
198
+ host configuring anything.
199
+ - `localeResources` — FTL message catalogs keyed by locale, for locales other
200
+ than English. English is bundled, so a host that only needs English passes
201
+ nothing. Nothing is translated yet, so there are no catalogs to supply today.
202
+
203
+ When a language is declared — by either route — the rendered container carries
204
+ a matching `lang` attribute, so screen readers pronounce the content with the
205
+ right voice and rules. When neither declares one it carries no `lang` at all
206
+ and inherits the embedding page's, a better guess than asserting English over
207
+ a page that said `<html lang="es">`.
208
+
209
+ Passing `null` (or dropping the prop) clears any of the three, exactly as with
210
+ `styleOverrides`.
211
+
186
212
  ### Windowed mounting (`mountPolicy`)
187
213
 
188
214
  Pages that embed many viewers (assignment pages, textbook chapters) pay
package/index.js CHANGED
@@ -39023,6 +39023,7 @@ function requireClassnames() {
39023
39023
  }
39024
39024
  var classnamesExports = requireClassnames();
39025
39025
  const classNames = /* @__PURE__ */ getDefaultExportFromCjs$1(classnamesExports);
39026
+ const untranslated = (key, _args, fallback) => fallback ?? key;
39026
39027
  const KeyboardIcon = () => /* @__PURE__ */ React__default.createElement(
39027
39028
  "svg",
39028
39029
  {
@@ -39040,9 +39041,13 @@ const KeyboardIcon = () => /* @__PURE__ */ React__default.createElement(
39040
39041
  );
39041
39042
  function KeyboardTray({
39042
39043
  onClick,
39043
- theme
39044
+ theme,
39045
+ translate
39044
39046
  }) {
39045
39047
  const [open, setOpen] = React__default.useState(false);
39048
+ const t4 = translate ?? untranslated;
39049
+ const openLabel = t4("keyboard-open", void 0, "Open Keyboard");
39050
+ const closeLabel = t4("keyboard-close", void 0, "Close Keyboard");
39046
39051
  return createPortal(
39047
39052
  /* @__PURE__ */ React__default.createElement(
39048
39053
  "div",
@@ -39066,8 +39071,8 @@ function KeyboardTray({
39066
39071
  {
39067
39072
  className: "open-keyboard-button",
39068
39073
  onClick: () => setOpen((old) => !old),
39069
- title: open ? "Close Keyboard" : "Open Keyboard",
39070
- "aria-label": open ? "Close Keyboard" : "Open Keyboard"
39074
+ title: open ? closeLabel : openLabel,
39075
+ "aria-label": open ? closeLabel : openLabel
39071
39076
  },
39072
39077
  /* @__PURE__ */ React__default.createElement(KeyboardIcon, null)
39073
39078
  ),
@@ -39076,8 +39081,8 @@ function KeyboardTray({
39076
39081
  {
39077
39082
  className: "close-keyboard-button",
39078
39083
  onClick: () => setOpen(false),
39079
- title: "Close Keyboard",
39080
- "aria-label": "Close Keyboard"
39084
+ title: closeLabel,
39085
+ "aria-label": closeLabel
39081
39086
  },
39082
39087
  "×"
39083
39088
  ), /* @__PURE__ */ React__default.createElement(ManagedKeyboard, { onClick }))
@@ -65384,36 +65389,42 @@ function getActiveRegistration() {
65384
65389
  }
65385
65390
  return null;
65386
65391
  }
65387
- function getTrayTheme() {
65392
+ function getTrayRegistration() {
65388
65393
  const activeRegistration = getActiveRegistration();
65389
65394
  if (activeRegistration) {
65390
- return activeRegistration.theme;
65395
+ return activeRegistration;
65391
65396
  }
65392
65397
  const lastActiveRegistration = getRegistrationById(
65393
65398
  virtualKeyboardState.lastActiveRegistrationId
65394
65399
  );
65395
65400
  if (lastActiveRegistration) {
65396
- return lastActiveRegistration.theme;
65401
+ return lastActiveRegistration;
65397
65402
  }
65398
65403
  const registrations = virtualKeyboardState.registrations;
65399
- return registrations[registrations.length - 1]?.theme;
65404
+ return registrations[registrations.length - 1];
65400
65405
  }
65401
65406
  function rerenderTray() {
65402
- const theme = getTrayTheme();
65407
+ const registration = getTrayRegistration();
65408
+ const theme = registration?.theme;
65409
+ const translate = registration?.translate;
65403
65410
  const trayEl = getTrayElement();
65404
65411
  if (trayEl) {
65405
65412
  const currentTheme = trayEl.getAttribute("data-theme") ?? void 0;
65406
- if (currentTheme === theme) {
65413
+ if (currentTheme === theme && virtualKeyboardState.lastRenderedTranslate === translate) {
65407
65414
  return;
65408
65415
  }
65409
65416
  }
65410
- virtualKeyboardState.keyboardReactRoot?.render(renderTray(theme));
65417
+ virtualKeyboardState.lastRenderedTranslate = translate;
65418
+ virtualKeyboardState.keyboardReactRoot?.render(
65419
+ renderTray(theme, translate)
65420
+ );
65411
65421
  }
65412
- function renderTray(theme) {
65422
+ function renderTray(theme, translate) {
65413
65423
  return /* @__PURE__ */ React__default.createElement(MathJaxContext, { config: mathjaxConfig, version: 4 }, /* @__PURE__ */ React__default.createElement(
65414
65424
  KeyboardTray,
65415
65425
  {
65416
65426
  theme,
65427
+ translate,
65417
65428
  onClick: (e2) => {
65418
65429
  getActiveRegistration()?.onClick(e2);
65419
65430
  }
@@ -65423,6 +65434,7 @@ function renderTray(theme) {
65423
65434
  function UniqueKeyboardTray({
65424
65435
  onClick,
65425
65436
  theme,
65437
+ translate,
65426
65438
  ownerRef
65427
65439
  }) {
65428
65440
  const [id2] = React__default.useState(() => {
@@ -65451,6 +65463,7 @@ function UniqueKeyboardTray({
65451
65463
  id: id2,
65452
65464
  onClick,
65453
65465
  theme,
65466
+ translate,
65454
65467
  ownerRef
65455
65468
  });
65456
65469
  rerenderTray();
@@ -65494,10 +65507,11 @@ function UniqueKeyboardTray({
65494
65507
  if (registration) {
65495
65508
  registration.onClick = onClick;
65496
65509
  registration.theme = theme;
65510
+ registration.translate = translate;
65497
65511
  registration.ownerRef = ownerRef;
65498
65512
  rerenderTray();
65499
65513
  }
65500
- }, [onClick, ownerRef, theme]);
65514
+ }, [onClick, ownerRef, theme, translate]);
65501
65515
  return null;
65502
65516
  }
65503
65517
  function ExternalVirtualKeyboard({
@@ -65529,7 +65543,7 @@ function ExternalVirtualKeyboard({
65529
65543
  }
65530
65544
  );
65531
65545
  }
65532
- const version = "0.7.21-dev.360";
65546
+ const version = "0.7.21-dev.362";
65533
65547
  const latestDoenetmlVersion = version;
65534
65548
  function subscribeToPinnedTheme() {
65535
65549
  return () => {
@@ -66160,6 +66174,12 @@ function DoenetViewer({
66160
66174
  }
66161
66175
  return /* @__PURE__ */ React__default.createElement("div", { ref: containerRef, style: { width: "100%" } }, viewerContent);
66162
66176
  }
66177
+ const PROPS_PINNED_TO_NULL = [
66178
+ "styleOverrides",
66179
+ "documentLocale",
66180
+ "uiLocale",
66181
+ "localeResources"
66182
+ ];
66163
66183
  function serializePropsSnapshot(seed, props) {
66164
66184
  const snapshot = { ...seed };
66165
66185
  for (const [key, val] of Object.entries(props)) {
@@ -66167,8 +66187,10 @@ function serializePropsSnapshot(seed, props) {
66167
66187
  snapshot[key] = val;
66168
66188
  }
66169
66189
  }
66170
- if (snapshot.styleOverrides === void 0) {
66171
- snapshot.styleOverrides = null;
66190
+ for (const key of PROPS_PINNED_TO_NULL) {
66191
+ if (snapshot[key] === void 0) {
66192
+ snapshot[key] = null;
66193
+ }
66172
66194
  }
66173
66195
  return JSON.stringify(snapshot);
66174
66196
  }