@pie-players/pie-players-shared 0.3.73 → 0.3.74

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.
Files changed (123) hide show
  1. package/README.md +27 -1
  2. package/dist/components/vendor/nds/README.md +7 -5
  3. package/dist/config/profile.d.ts +0 -2
  4. package/dist/config/profile.js +2 -8
  5. package/dist/i18n/index.d.ts +2 -3
  6. package/dist/i18n/index.js +2 -3
  7. package/dist/i18n/messages/en-US.d.ts +10 -0
  8. package/dist/i18n/messages/en-US.js +12 -2
  9. package/dist/i18n/messages/nl-NL.d.ts +6 -0
  10. package/dist/i18n/messages/nl-NL.js +8 -2
  11. package/dist/i18n/provider.d.ts +4 -5
  12. package/dist/i18n/provider.js +4 -5
  13. package/dist/index.d.ts +4 -2
  14. package/dist/index.js +3 -1
  15. package/dist/instrumentation/index.d.ts +5 -4
  16. package/dist/instrumentation/index.js +5 -4
  17. package/dist/instrumentation/new-relic-agent.d.ts +20 -0
  18. package/dist/instrumentation/new-relic-agent.js +24 -0
  19. package/dist/instrumentation/providers/BaseInstrumentationProvider.d.ts +5 -4
  20. package/dist/instrumentation/providers/NewRelicInstrumentationProvider.d.ts +7 -7
  21. package/dist/instrumentation/providers/NewRelicInstrumentationProvider.js +21 -32
  22. package/dist/instrumentation/types.d.ts +7 -3
  23. package/dist/loader-config.d.ts +16 -6
  24. package/dist/loaders/controller-loading.d.ts +14 -0
  25. package/dist/loaders/controller-loading.js +12 -0
  26. package/dist/loaders/defaults.d.ts +10 -0
  27. package/dist/loaders/defaults.js +10 -0
  28. package/dist/loaders/element-loader.d.ts +20 -8
  29. package/dist/loaders/element-loader.js +102 -36
  30. package/dist/loaders/element-package-policy.d.ts +2 -0
  31. package/dist/loaders/element-package-policy.js +2 -1
  32. package/dist/loaders/esm-adapter.d.ts +18 -0
  33. package/dist/loaders/esm-adapter.js +617 -102
  34. package/dist/loaders/iife-adapter.d.ts +0 -5
  35. package/dist/loaders/iife-adapter.js +3 -9
  36. package/dist/loaders/index.d.ts +10 -3
  37. package/dist/loaders/index.js +8 -3
  38. package/dist/loaders/mathjax-version-conflict.d.ts +17 -0
  39. package/dist/loaders/mathjax-version-conflict.js +51 -0
  40. package/dist/loaders/module-shim.d.ts +12 -0
  41. package/dist/loaders/module-shim.js +12 -0
  42. package/dist/loaders/preloaded-alignment.d.ts +24 -0
  43. package/dist/loaders/preloaded-alignment.js +57 -0
  44. package/dist/loaders/preloaded-authored-tags.d.ts +26 -0
  45. package/dist/loaders/preloaded-authored-tags.js +69 -0
  46. package/dist/loaders/preloaded-registration.d.ts +56 -0
  47. package/dist/loaders/preloaded-registration.js +129 -0
  48. package/dist/media/index.d.ts +69 -0
  49. package/dist/media/index.js +194 -0
  50. package/dist/pie/authoring.d.ts +1 -1
  51. package/dist/pie/authoring.js +25 -18
  52. package/dist/pie/element-announcements.d.ts +39 -0
  53. package/dist/pie/element-announcements.js +67 -0
  54. package/dist/pie/element-observer.d.ts +3 -1
  55. package/dist/pie/element-observer.js +1 -0
  56. package/dist/pie/index.d.ts +4 -4
  57. package/dist/pie/index.js +5 -3
  58. package/dist/pie/initialization.js +13 -22
  59. package/dist/pie/initialize-element.d.ts +3 -0
  60. package/dist/pie/initialize-element.js +3 -4
  61. package/dist/pie/instrumentation-event-map.js +2 -9
  62. package/dist/pie/instrumentation-provider-resolution.d.ts +2 -1
  63. package/dist/pie/instrumentation-provider-resolution.js +0 -1
  64. package/dist/pie/item-session-contract.d.ts +8 -0
  65. package/dist/pie/item-session-contract.js +30 -9
  66. package/dist/pie/markup-math.d.ts +20 -0
  67. package/dist/pie/markup-math.js +118 -0
  68. package/dist/pie/math-rendering.d.ts +10 -21
  69. package/dist/pie/math-rendering.js +50 -35
  70. package/dist/pie/registry.d.ts +13 -1
  71. package/dist/pie/registry.js +39 -1
  72. package/dist/pie/resource-monitor.d.ts +6 -33
  73. package/dist/pie/resource-monitor.js +14 -213
  74. package/dist/pie/scoring.d.ts +11 -2
  75. package/dist/pie/scoring.js +18 -16
  76. package/dist/pie/session-commit.d.ts +16 -0
  77. package/dist/pie/session-commit.js +33 -5
  78. package/dist/pie/session-snapshot.d.ts +80 -0
  79. package/dist/pie/session-snapshot.js +166 -0
  80. package/dist/pie/tag-names.d.ts +1 -0
  81. package/dist/pie/tag-names.js +1 -0
  82. package/dist/pie/types.d.ts +4 -5
  83. package/dist/pie/types.js +1 -0
  84. package/dist/pie/updates.d.ts +2 -2
  85. package/dist/pie/updates.js +33 -15
  86. package/dist/pie/versioned-tag.d.ts +4 -0
  87. package/dist/pie/versioned-tag.js +4 -0
  88. package/dist/security/index.d.ts +2 -3
  89. package/dist/security/index.js +2 -3
  90. package/dist/security/sanitize-item-markup.d.ts +0 -2
  91. package/dist/security/sanitize-item-markup.js +0 -4
  92. package/dist/security/sanitize-svg-icon.d.ts +0 -2
  93. package/dist/security/sanitize-svg-icon.js +0 -5
  94. package/dist/security/wrap-overwide.d.ts +5 -0
  95. package/dist/security/wrap-overwide.js +16 -0
  96. package/dist/timed-media/policy.js +5 -16
  97. package/dist/types/index.d.ts +30 -311
  98. package/dist/types/index.js +0 -68
  99. package/dist/ui/attribute-coercion.d.ts +12 -0
  100. package/dist/ui/attribute-coercion.js +33 -0
  101. package/dist/ui/content-styles.d.ts +18 -16
  102. package/dist/ui/content-styles.js +67 -36
  103. package/dist/ui/overlay-containment.d.ts +31 -0
  104. package/dist/ui/overlay-containment.js +63 -0
  105. package/dist/ui/overlay-placement.d.ts +110 -0
  106. package/dist/ui/overlay-placement.js +222 -0
  107. package/dist/ui/pointer-gesture.d.ts +32 -0
  108. package/dist/ui/pointer-gesture.js +56 -0
  109. package/dist/ui/pointer-rotate.d.ts +29 -0
  110. package/dist/ui/pointer-rotate.js +39 -0
  111. package/package.json +21 -17
  112. package/dist/pie/component-context.d.ts +0 -21
  113. package/dist/pie/component-context.js +0 -29
  114. package/dist/pie/player-initializer.d.ts +0 -54
  115. package/dist/pie/player-initializer.js +0 -122
  116. package/dist/security/wrap-model-rich-content.d.ts +0 -1
  117. package/dist/security/wrap-model-rich-content.js +0 -40
  118. package/dist/server/npm-auth-env.d.ts +0 -13
  119. package/dist/server/npm-auth-env.js +0 -64
  120. package/dist/server/npm-registry.d.ts +0 -7
  121. package/dist/server/npm-registry.js +0 -59
  122. package/dist/ui/debug-panel-persistence.d.ts +0 -17
  123. package/dist/ui/debug-panel-persistence.js +0 -37
@@ -11,7 +11,7 @@ import { editorPostFix } from "../types/index.js";
11
11
  import { initializePieElement } from "./initialize-element.js";
12
12
  import { createPieLogger, isGlobalDebugEnabled } from "./logger.js";
13
13
  import { initializeMathRendering } from "./math-rendering.js";
14
- import { pieRegistry } from "./registry.js";
14
+ import { pieRegistry, writeRegistryEntry } from "./registry.js";
15
15
  import { defineCustomElementSafely } from "./custom-element-define.js";
16
16
  import { validateCustomElementTag } from "./tag-names.js";
17
17
  import { BundleType, isCustomElementConstructor, isPieAvailable, Status, } from "./types.js";
@@ -38,6 +38,7 @@ const updateRegisteredElement = (elementTagName, config, session, options, omitE
38
38
  config,
39
39
  session,
40
40
  ...(omitEnv ? {} : { env: options.env }),
41
+ bundleType: options.bundleType,
41
42
  container: options.container,
42
43
  ...(options.eventListeners?.[elementTagName] && {
43
44
  eventListeners: options.eventListeners[elementTagName],
@@ -119,30 +120,21 @@ const registerPieElementsFromBundle = (elementModule, config, session, registry,
119
120
  : [],
120
121
  bundleType: options.bundleType,
121
122
  });
122
- // Validate controller presence based on bundle type
123
- if (!elementData.controller) {
124
- if (options.bundleType === BundleType.clientPlayer) {
125
- logger.error(`[registerPieElementsFromBundle] ❌ CRITICAL: No controller found for ${pkgStripped}!`);
126
- logger.error(`[registerPieElementsFromBundle] Bundle type: ${options.bundleType} (controllers required)`);
127
- throw new Error(`No controller found for ${pkgStripped}. client-player.js bundles MUST include controllers!`);
128
- }
129
- else {
130
- logger.debug(`[registerPieElementsFromBundle] ℹ️ No controller found for ${pkgStripped} - using server-processed models (player.js bundle)`);
131
- }
132
- }
133
123
  {
134
- // Register the element in our registry
124
+ // Register the element in our registry. A package without a
125
+ // controller, such as a legacy `@pie-element/protractor`, renders
126
+ // the model it is given under client-player.js too.
135
127
  logger.debug(`[registerPieElementsFromBundle] Registering ${elName} in registry${elementData.controller
136
128
  ? " with controller"
137
- : " (no controller - server-processed models)"}`);
138
- registry[elementTagName] = {
129
+ : " (no controller; its model is used as given)"}`);
130
+ writeRegistryEntry({
139
131
  package: pkg,
140
132
  status: Status.loading,
141
133
  tagName: elementTagName,
142
134
  controller: elementData.controller || null,
143
135
  config: elementData.config,
144
136
  bundleType: options.bundleType,
145
- };
137
+ });
146
138
  if (isCustomElementConstructor(elementData.Element)) {
147
139
  defineCustomElementSafely(elementTagName, elementData.Element, `element tag in config.elements for ${String(pkg)}`);
148
140
  // Initialize existing elements
@@ -155,13 +147,13 @@ const registerPieElementsFromBundle = (elementModule, config, session, registry,
155
147
  session,
156
148
  env: options.env,
157
149
  eventListeners: options.eventListeners?.[elementTagName],
150
+ bundleType: options.bundleType,
158
151
  });
159
152
  });
160
- // Update registry status
161
- registry[elementTagName] = {
153
+ writeRegistryEntry({
162
154
  ...registry[elementTagName],
163
155
  status: Status.loaded,
164
- };
156
+ });
165
157
  promises.push(customElements.whenDefined(elementTagName).then(() => {
166
158
  logger.debug("[registerPieElementsFromBundle] defined custom PIE element: %s", elementTagName);
167
159
  }));
@@ -297,9 +289,8 @@ export const loadPieModule = async (config, session, opts = {}) => {
297
289
  }
298
290
  catch (error) {
299
291
  // `registerPieElementsFromBundle` throws synchronously for a
300
- // package missing from the bundle and for a client-player
301
- // bundle with no controller. Inside a DOM event handler that
302
- // throw reaches the window instead of the caller.
292
+ // package missing from the bundle. Inside a DOM event handler
293
+ // that throw reaches the window instead of the caller.
303
294
  fail(error instanceof Error ? error : new Error(String(error)));
304
295
  }
305
296
  });
@@ -6,6 +6,7 @@
6
6
  */
7
7
  import type { ConfigEntity, Env } from "../types/index.js";
8
8
  import type { EventListeners, PieElement } from "./types.js";
9
+ import { BundleType } from "./types.js";
9
10
  /**
10
11
  * Bind `element` to the model in `options.config` whose `id` matches it.
11
12
  *
@@ -20,4 +21,6 @@ export declare const initializePieElement: (element: PieElement, options: {
20
21
  session: any[];
21
22
  env?: Env;
22
23
  eventListeners?: EventListeners;
24
+ /** The delivering player's bundle type, see `findPieController`. */
25
+ bundleType?: BundleType;
23
26
  }) => boolean;
@@ -4,7 +4,6 @@
4
4
  * Split out of `initialization.ts` so the late-arrival observer in
5
5
  * `element-observer.ts` can reuse it without importing the bundle loaders.
6
6
  */
7
- import { wrapModelRichContent } from "../security/wrap-model-rich-content.js";
8
7
  import { createPieLogger, isGlobalDebugEnabled } from "./logger.js";
9
8
  import { pieRegistry } from "./registry.js";
10
9
  import { findPieController } from "./scoring.js";
@@ -21,7 +20,7 @@ const logger = createPieLogger("pie-initialize-element", () => isGlobalDebugEnab
21
20
  * separately — uses that to stop at the config the element belongs to.
22
21
  */
23
22
  export const initializePieElement = (element, options) => {
24
- const { config, session, env, eventListeners } = options;
23
+ const { config, session, env, eventListeners, bundleType } = options;
25
24
  if (element.__pieInitialized) {
26
25
  return true;
27
26
  }
@@ -45,7 +44,7 @@ export const initializePieElement = (element, options) => {
45
44
  element.__pieInitialized = true;
46
45
  logger.debug(`[initializePieElement] Session set for ${tagName}#${element.id}:`, elementSession);
47
46
  // Set model - use controller if available (client-player.js), or use server-processed model (player.js)
48
- const controller = findPieController(tagName);
47
+ const controller = findPieController(tagName, bundleType);
49
48
  if (!env) {
50
49
  logger.error(`[initializePieElement] ❌ FATAL: No env provided for ${tagName}`);
51
50
  throw new Error(`No env provided for ${tagName}. PIE elements require an env object with mode and role.`);
@@ -61,7 +60,7 @@ export const initializePieElement = (element, options) => {
61
60
  role: env.role,
62
61
  });
63
62
  // Set model directly - server already processed it
64
- element.model = wrapModelRichContent(model);
63
+ element.model = model;
65
64
  }
66
65
  else {
67
66
  // Controller available - run client-side processing (client-player.js bundle)
@@ -37,7 +37,8 @@ export const SECTION_INSTRUMENTATION_EVENT_MAP = [
37
37
  // instrumentation bridge forwards the same names to telemetry. The
38
38
  // readiness alias mappings (`readiness-change`, `interaction-ready`,
39
39
  // `ready`) and the `section-controller-ready` mapping were removed
40
- // alongside their DOM-event surfaces.
40
+ // alongside their DOM-event surfaces. `session-changed` stays off the
41
+ // bridge for the reason the item map gives below.
41
42
  {
42
43
  sourceEventName: "pie-stage-change",
43
44
  instrumentationEventName: "pie-section-stage-change",
@@ -46,14 +47,6 @@ export const SECTION_INSTRUMENTATION_EVENT_MAP = [
46
47
  sourceEventName: "pie-loading-complete",
47
48
  instrumentationEventName: "pie-section-loading-complete",
48
49
  },
49
- {
50
- sourceEventName: "session-changed",
51
- instrumentationEventName: "pie-section-session-changed",
52
- },
53
- {
54
- sourceEventName: "composition-changed",
55
- instrumentationEventName: "pie-section-composition-changed",
56
- },
57
50
  {
58
51
  sourceEventName: "framework-error",
59
52
  instrumentationEventName: "pie-section-framework-error",
@@ -1,6 +1,7 @@
1
+ import type { InstrumentationProvider } from "../instrumentation/types.js";
1
2
  export declare function resolveInstrumentationProvider(args: {
2
3
  runtimePlayer?: unknown;
3
4
  player?: unknown;
4
5
  debug?: boolean;
5
6
  component?: string;
6
- }): unknown;
7
+ }): InstrumentationProvider | undefined;
@@ -37,7 +37,6 @@ const logger = createPieLogger("instrumentation-provider-resolution", () => isGl
37
37
  function getDefaultInstrumentationProvider() {
38
38
  if (!defaultProvider) {
39
39
  defaultProvider = new NewRelicInstrumentationProvider();
40
- // New Relic provider sets readiness based on window.newrelic.
41
40
  void defaultProvider.initialize();
42
41
  }
43
42
  return defaultProvider;
@@ -1,3 +1,4 @@
1
+ import type { SessionCommitReason } from "./session-commit.js";
1
2
  export type ItemSessionContainer = {
2
3
  id: string;
3
4
  data: unknown[];
@@ -8,7 +9,14 @@ export type NormalizedItemSessionChange = {
8
9
  session: ItemSessionContainer | null;
9
10
  intent: ItemSessionUpdateIntent;
10
11
  component?: string;
12
+ /**
13
+ * The id of the element whose change this is: the detail's `elementId`, or
14
+ * the `id` of an element session record carried as `session`.
15
+ */
16
+ elementId?: string;
11
17
  complete?: boolean;
18
+ /** The detail's `sessionCommitReason`, when the change is a commit. */
19
+ sessionCommitReason?: SessionCommitReason;
12
20
  };
13
21
  export declare function normalizeItemSessionContainer(input: unknown, fallbackSessionId?: string): ItemSessionContainer;
14
22
  export declare function hasResponseValue(value: unknown): boolean;
@@ -2,6 +2,7 @@ const DEFAULT_SESSION_ID = "";
2
2
  const METADATA_ONLY_SESSION_KEYS = new Set([
3
3
  "complete",
4
4
  "component",
5
+ "elementId",
5
6
  "timestamp",
6
7
  "sourceRuntimeId",
7
8
  ]);
@@ -162,6 +163,18 @@ function getMetadataComplete(sessionDetail, candidate) {
162
163
  return candidate.complete;
163
164
  return undefined;
164
165
  }
166
+ /**
167
+ * `elementRecord` is an element's own session record, whose `id` is the
168
+ * element's; an item session container's `id` is the item session's.
169
+ */
170
+ function withElementId(sessionDetail, elementRecord) {
171
+ const elementId = typeof sessionDetail.elementId === "string" && sessionDetail.elementId
172
+ ? sessionDetail.elementId
173
+ : typeof elementRecord?.id === "string" && elementRecord.id
174
+ ? elementRecord.id
175
+ : undefined;
176
+ return elementId ? { elementId } : {};
177
+ }
165
178
  function isElementIdentityOnlyPayload(candidate) {
166
179
  const candidateKeys = Object.keys(candidate);
167
180
  const hasIdentityKey = "id" in candidate || "element" in candidate;
@@ -211,6 +224,14 @@ export function mergeElementIntoSession(itemId, previousItemSession, entryId, en
211
224
  };
212
225
  }
213
226
  export function normalizeItemSessionChange(args) {
227
+ const change = classifyItemSessionChange(args);
228
+ const reason = args.sessionDetail
229
+ ?.sessionCommitReason;
230
+ return typeof reason === "string" && reason
231
+ ? { ...change, sessionCommitReason: reason }
232
+ : change;
233
+ }
234
+ function classifyItemSessionChange(args) {
214
235
  const sessionDetail = (args.sessionDetail || {});
215
236
  const actualSession = sessionDetail &&
216
237
  typeof sessionDetail === "object" &&
@@ -229,6 +250,7 @@ export function normalizeItemSessionChange(args) {
229
250
  session: null,
230
251
  intent: "metadata-only",
231
252
  component: getMetadataComponent(sessionDetail),
253
+ ...withElementId(sessionDetail),
232
254
  complete: getMetadataComplete(sessionDetail),
233
255
  };
234
256
  }
@@ -241,11 +263,7 @@ export function normalizeItemSessionChange(args) {
241
263
  const previousForMetadataComparison = alignPreviousSessionIdForRendererSnapshot(normalizedCandidate, previousNormalized);
242
264
  const sessionDetailKeys = Object.keys(sessionDetail);
243
265
  const metadataWithSessionOnly = sessionDetailKeys.length > 0 &&
244
- sessionDetailKeys.every((key) => key === "session" ||
245
- key === "complete" ||
246
- key === "component" ||
247
- key === "timestamp" ||
248
- key === "sourceRuntimeId");
266
+ sessionDetailKeys.every((key) => key === "session" || METADATA_ONLY_SESSION_KEYS.has(key));
249
267
  const hasMetadataBeyondSession = sessionDetailKeys.some((key) => key !== "session");
250
268
  if (metadataWithSessionOnly &&
251
269
  hasMetadataBeyondSession &&
@@ -256,6 +274,7 @@ export function normalizeItemSessionChange(args) {
256
274
  session: null,
257
275
  intent: "metadata-only",
258
276
  component: getMetadataComponent(sessionDetail),
277
+ ...withElementId(sessionDetail),
259
278
  complete: getMetadataComplete(sessionDetail),
260
279
  };
261
280
  }
@@ -266,6 +285,7 @@ export function normalizeItemSessionChange(args) {
266
285
  session: null,
267
286
  intent: "metadata-only",
268
287
  component: getMetadataComponent(sessionDetail),
288
+ ...withElementId(sessionDetail),
269
289
  complete: getMetadataComplete(sessionDetail),
270
290
  };
271
291
  }
@@ -274,21 +294,20 @@ export function normalizeItemSessionChange(args) {
274
294
  session: normalizedCandidate,
275
295
  intent: "replace-item-session",
276
296
  component: getMetadataComponent(sessionDetail),
297
+ ...withElementId(sessionDetail),
277
298
  complete: getMetadataComplete(sessionDetail),
278
299
  };
279
300
  }
280
301
  const candidateKeys = Object.keys(candidate);
281
302
  const isMetadataOnlyPayload = candidateKeys.length > 0 &&
282
- candidateKeys.every((key) => key === "complete" ||
283
- key === "component" ||
284
- key === "timestamp" ||
285
- key === "sourceRuntimeId");
303
+ candidateKeys.every((key) => METADATA_ONLY_SESSION_KEYS.has(key));
286
304
  if (isMetadataOnlyPayload) {
287
305
  return {
288
306
  itemId: safeItemId,
289
307
  session: null,
290
308
  intent: "metadata-only",
291
309
  component: getMetadataComponent(sessionDetail, candidate),
310
+ ...withElementId(sessionDetail),
292
311
  complete: getMetadataComplete(sessionDetail, candidate),
293
312
  };
294
313
  }
@@ -298,6 +317,7 @@ export function normalizeItemSessionChange(args) {
298
317
  session: null,
299
318
  intent: "metadata-only",
300
319
  component: getMetadataComponent(sessionDetail, candidate),
320
+ ...withElementId(sessionDetail, candidate),
301
321
  complete: getMetadataComplete(sessionDetail, candidate),
302
322
  };
303
323
  }
@@ -309,6 +329,7 @@ export function normalizeItemSessionChange(args) {
309
329
  session: merged,
310
330
  intent: "merge-element-session",
311
331
  component,
332
+ ...withElementId(sessionDetail, candidate),
312
333
  complete: getMetadataComplete(sessionDetail, candidate),
313
334
  };
314
335
  }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Math in an item's own markup: what the item and passage markup carry outside
3
+ * every PIE element.
4
+ *
5
+ * Each element typesets its own subtree, so the player hands its renderer only
6
+ * the markup around them. A renderer finds math below the element it is given,
7
+ * so no root holds a PIE element: the markup splits into the elements that hold
8
+ * none, and math sharing a parent with one is wrapped in a span of its own.
9
+ */
10
+ import type { MathRenderer } from "./math-rendering.js";
11
+ /**
12
+ * The parts of `container` holding math and no PIE element, each one a root to
13
+ * typeset. `pieTags` are the tag names of the PIE elements its markup places.
14
+ */
15
+ export declare function collectMarkupMathRoots(container: HTMLElement, pieTags: ReadonlySet<string>): HTMLElement[];
16
+ /**
17
+ * Typesets the math in `containers` outside every PIE element with `render`,
18
+ * one root at a time.
19
+ */
20
+ export declare function typesetMarkupMath(containers: Iterable<HTMLElement>, pieTags: ReadonlySet<string>, render: MathRenderer): Promise<void>;
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Math in an item's own markup: what the item and passage markup carry outside
3
+ * every PIE element.
4
+ *
5
+ * Each element typesets its own subtree, so the player hands its renderer only
6
+ * the markup around them. A renderer finds math below the element it is given,
7
+ * so no root holds a PIE element: the markup splits into the elements that hold
8
+ * none, and math sharing a parent with one is wrapped in a span of its own.
9
+ */
10
+ /** TeX as `@pie-element/shared-math-rendering-mathjax` detects it. */
11
+ const TEX = /\\\(|\\\[|\$\$|\\\$|\\begin\{|\\(?:eq)?ref\{/;
12
+ /** The same, with `$…$` inline math turned on by the legacy opt-in. */
13
+ const TEX_WITH_SINGLE_DOLLAR = /\\\(|\\\[|\$|\\begin\{|\\(?:eq)?ref\{/;
14
+ /** Elements whose content MathJax leaves alone, as both PIE configurations list them. */
15
+ const SKIPPED_ELEMENTS = new Set([
16
+ "script",
17
+ "noscript",
18
+ "style",
19
+ "textarea",
20
+ "pre",
21
+ "code",
22
+ "annotation",
23
+ "annotation-xml",
24
+ "mjx-container",
25
+ "mjx-assistive-mml",
26
+ ]);
27
+ /** The class an author marks content with to keep MathJax out of it. */
28
+ const IGNORED_CLASS = /(?:^|\s)mathjax_ignore(?:\s|$)/;
29
+ const HTML_NAMESPACE = "http://www.w3.org/1999/xhtml";
30
+ function texPattern() {
31
+ const legacy = globalThis["@pie-lib/math-rendering@2"];
32
+ return legacy?.opts?.useSingleDollar ? TEX_WITH_SINGLE_DOLLAR : TEX;
33
+ }
34
+ function isSkipped(element) {
35
+ return (SKIPPED_ELEMENTS.has(element.localName) ||
36
+ IGNORED_CLASS.test(element.getAttribute("class") ?? ""));
37
+ }
38
+ /** Math a renderer would only find on `element` itself, never below it. */
39
+ function holdsOwnMath(element) {
40
+ return (element.localName === "math" ||
41
+ (element.hasAttribute("data-latex") &&
42
+ !element.hasAttribute("data-math-handled")));
43
+ }
44
+ function holdsMath(element, tex) {
45
+ if (holdsOwnMath(element) ||
46
+ element.querySelector("[data-latex]:not([data-math-handled])")) {
47
+ return true;
48
+ }
49
+ for (const math of element.getElementsByTagName("math")) {
50
+ if (!math.closest("mjx-assistive-mml"))
51
+ return true;
52
+ }
53
+ return tex.test(element.textContent ?? "");
54
+ }
55
+ function wrapInSpan(node) {
56
+ const span = (node.ownerDocument ?? document).createElement("span");
57
+ node.replaceWith(span);
58
+ span.append(node);
59
+ return span;
60
+ }
61
+ /**
62
+ * The parts of `container` holding math and no PIE element, each one a root to
63
+ * typeset. `pieTags` are the tag names of the PIE elements its markup places.
64
+ */
65
+ export function collectMarkupMathRoots(container, pieTags) {
66
+ const tex = texPattern();
67
+ const holdsPie = new Set();
68
+ for (const tag of pieTags) {
69
+ for (const element of container.getElementsByTagName(tag)) {
70
+ for (let ancestor = element.parentElement; ancestor && !holdsPie.has(ancestor); ancestor = ancestor.parentElement) {
71
+ holdsPie.add(ancestor);
72
+ if (ancestor === container)
73
+ break;
74
+ }
75
+ }
76
+ }
77
+ if (!holdsPie.has(container)) {
78
+ return holdsMath(container, tex) ? [container] : [];
79
+ }
80
+ const roots = [];
81
+ const visit = (parent) => {
82
+ for (const child of [...parent.childNodes]) {
83
+ if (child.nodeType === Node.TEXT_NODE) {
84
+ if (tex.test(child.data))
85
+ roots.push(wrapInSpan(child));
86
+ continue;
87
+ }
88
+ if (child.nodeType !== Node.ELEMENT_NODE)
89
+ continue;
90
+ const element = child;
91
+ if (pieTags.has(element.localName) || isSkipped(element))
92
+ continue;
93
+ if (holdsPie.has(element)) {
94
+ visit(element);
95
+ }
96
+ else if (holdsMath(element, tex)) {
97
+ if (holdsOwnMath(element)) {
98
+ roots.push(wrapInSpan(element));
99
+ }
100
+ else if (element.namespaceURI === HTML_NAMESPACE) {
101
+ roots.push(element);
102
+ }
103
+ }
104
+ }
105
+ };
106
+ visit(container);
107
+ return roots;
108
+ }
109
+ /**
110
+ * Typesets the math in `containers` outside every PIE element with `render`,
111
+ * one root at a time.
112
+ */
113
+ export async function typesetMarkupMath(containers, pieTags, render) {
114
+ const roots = [...containers].flatMap((container) => collectMarkupMathRoots(container, pieTags));
115
+ for (const root of roots) {
116
+ await render(root);
117
+ }
118
+ }
@@ -4,7 +4,7 @@
4
4
  * PIE elements expect @pie-lib/math-rendering to be available on window.
5
5
  * This module ensures required globals are populated from the upstream
6
6
  * @pie-lib/math-rendering-module package, and also supports overriding with
7
- * a custom renderer object when needed.
7
+ * a custom renderer object through setMathRenderer().
8
8
  */
9
9
  export type MathRenderer = (element: HTMLElement) => void | Promise<void>;
10
10
  export interface MathRenderingAPI {
@@ -14,32 +14,30 @@ export interface MathRenderingAPI {
14
14
  mmlToLatex?: (mathml: string) => string;
15
15
  }
16
16
  /**
17
- * Initialize math rendering with optional custom renderer
18
- *
19
- * If no custom renderer is provided, defaults to MathJax.
17
+ * The page's renderer: the one `initializeMathRendering` installed, or a
18
+ * host's. IIFE elements render with it, and so do ESM elements when the page
19
+ * has one.
20
+ */
21
+ export declare const getMathRenderer: () => MathRenderingAPI | null;
22
+ /**
23
+ * Initialize math rendering, defaulting to MathJax.
20
24
  * For custom renderers, use setMathRenderer() before calling this function.
21
25
  *
22
26
  * Sets TWO window globals that PIE elements expect:
23
27
  * - window["@pie-lib/math-rendering"] (standard key)
24
28
  * - window["_dll_pie_lib__math_rendering"] (SystemJS/DLL key for IIFE bundles)
25
29
  *
26
- * @param customRenderer - Optional custom renderer to use instead of default MathJax
27
- *
28
30
  * @example
29
31
  * ```typescript
30
32
  * // Default MathJax
31
33
  * await initializeMathRendering();
32
34
  *
33
- * // Custom renderer instance implementing MathRenderingAPI
34
- * const customRenderer = await createCustomRenderer();
35
- * await initializeMathRendering(customRenderer);
36
- *
37
- * // Or use setMathRenderer first
35
+ * // Custom renderer: install it first
38
36
  * setMathRenderer(katexRenderer);
39
37
  * await initializeMathRendering();
40
38
  * ```
41
39
  */
42
- export declare function initializeMathRendering(customRenderer?: MathRenderingAPI): Promise<void>;
40
+ export declare function initializeMathRendering(): Promise<void>;
43
41
  /**
44
42
  * Set custom math renderer programmatically
45
43
  *
@@ -60,12 +58,3 @@ export declare function initializeMathRendering(customRenderer?: MathRenderingAP
60
58
  * ```
61
59
  */
62
60
  export declare function setMathRenderer(renderer: MathRenderingAPI): void;
63
- /**
64
- * Render math in the given element
65
- *
66
- * Convenience wrapper that calls the active renderer's renderMath() function.
67
- * Typically called after PIE elements are rendered.
68
- *
69
- * @param element - The element to render math within
70
- */
71
- export declare function renderMath(element: HTMLElement): void;
@@ -4,15 +4,23 @@
4
4
  * PIE elements expect @pie-lib/math-rendering to be available on window.
5
5
  * This module ensures required globals are populated from the upstream
6
6
  * @pie-lib/math-rendering-module package, and also supports overriding with
7
- * a custom renderer object when needed.
7
+ * a custom renderer object through setMathRenderer().
8
8
  */
9
9
  /// <reference path="../shims.d.ts" />
10
10
  import { createPieLogger, isGlobalDebugEnabled } from "./logger.js";
11
11
  const GLOBAL_KEY = "@pie-lib/math-rendering";
12
12
  const GLOBAL_DLL_KEY = "_dll_pie_lib__math_rendering";
13
+ // Every copy of @pie-lib/math-rendering on a page typesets through the MathJax
14
+ // instance kept here, and the first copy to typeset creates it.
15
+ const SHARED_INSTANCE_KEY = "@pie-lib/math-rendering@2";
13
16
  let initPromise = null;
14
17
  const logger = createPieLogger("math-rendering", () => isGlobalDebugEnabled());
15
- const getWindowRenderer = () => {
18
+ /**
19
+ * The page's renderer: the one `initializeMathRendering` installed, or a
20
+ * host's. IIFE elements render with it, and so do ESM elements when the page
21
+ * has one.
22
+ */
23
+ export const getMathRenderer = () => {
16
24
  if (typeof window === "undefined") {
17
25
  return null;
18
26
  }
@@ -26,43 +34,55 @@ const setWindowRenderer = (renderer) => {
26
34
  window[GLOBAL_DLL_KEY] = renderer;
27
35
  };
28
36
  /**
29
- * Initialize math rendering with optional custom renderer
37
+ * Creates the shared MathJax instance from `renderer`, unless the page already
38
+ * holds one.
30
39
  *
31
- * If no custom renderer is provided, defaults to MathJax.
40
+ * Legacy element bundles carry their own copy of math-rendering. A copy that
41
+ * predates the assistive-MathML guard, such as the one in multiple-choice
42
+ * 9.9.1, re-typesets the hidden `<math>` inside `mjx-assistive-mml` on every
43
+ * pass, so if it creates the instance, each re-render nests the expression
44
+ * again for every element on the page. Typesetting a detached element makes the
45
+ * guarded module create it first. Failure leaves the instance to the first
46
+ * element, as before, so it is logged and never fails initialization.
47
+ */
48
+ const createSharedInstance = (renderer) => {
49
+ if (window[SHARED_INSTANCE_KEY]?.instance)
50
+ return;
51
+ if (typeof document === "undefined" || !document.body)
52
+ return;
53
+ const warn = (error) => logger.warn("Could not create the shared MathJax instance:", error);
54
+ try {
55
+ Promise.resolve(renderer.renderMath(document.createElement("div"))).catch(warn);
56
+ }
57
+ catch (error) {
58
+ warn(error);
59
+ }
60
+ };
61
+ /**
62
+ * Initialize math rendering, defaulting to MathJax.
32
63
  * For custom renderers, use setMathRenderer() before calling this function.
33
64
  *
34
65
  * Sets TWO window globals that PIE elements expect:
35
66
  * - window["@pie-lib/math-rendering"] (standard key)
36
67
  * - window["_dll_pie_lib__math_rendering"] (SystemJS/DLL key for IIFE bundles)
37
68
  *
38
- * @param customRenderer - Optional custom renderer to use instead of default MathJax
39
- *
40
69
  * @example
41
70
  * ```typescript
42
71
  * // Default MathJax
43
72
  * await initializeMathRendering();
44
73
  *
45
- * // Custom renderer instance implementing MathRenderingAPI
46
- * const customRenderer = await createCustomRenderer();
47
- * await initializeMathRendering(customRenderer);
48
- *
49
- * // Or use setMathRenderer first
74
+ * // Custom renderer: install it first
50
75
  * setMathRenderer(katexRenderer);
51
76
  * await initializeMathRendering();
52
77
  * ```
53
78
  */
54
- export async function initializeMathRendering(customRenderer) {
79
+ export async function initializeMathRendering() {
55
80
  // Only run in browser
56
81
  if (typeof window === "undefined") {
57
82
  return;
58
83
  }
59
- // Explicit override always wins.
60
- if (customRenderer) {
61
- setWindowRenderer(customRenderer);
62
- return;
63
- }
64
84
  // Already initialized - skip.
65
- if (getWindowRenderer()) {
85
+ if (getMathRenderer()) {
66
86
  return;
67
87
  }
68
88
  if (initPromise) {
@@ -71,11 +91,20 @@ export async function initializeMathRendering(customRenderer) {
71
91
  }
72
92
  initPromise = (async () => {
73
93
  try {
74
- const { _dll_pie_lib__math_rendering } = await import("@pie-lib/math-rendering-module/module");
94
+ // The package has no exports map, so the file is named in full for
95
+ // resolvers that do not complete a directory, such as webpack's
96
+ // fully-specified ESM resolution.
97
+ const mathRenderingModule = await import("@pie-lib/math-rendering-module/module/index.js");
98
+ // The player builds defer the module's evaluation to this call (see
99
+ // math-rendering-module-deferral.mjs); unbundled, it evaluated on import.
100
+ const renderer = mathRenderingModule.evaluateMathRenderingModule
101
+ ? mathRenderingModule.evaluateMathRenderingModule()
102
+ : mathRenderingModule._dll_pie_lib__math_rendering;
75
103
  // A host may install its renderer while the default module is in flight.
76
104
  // The explicit renderer remains authoritative when that happens.
77
- if (!getWindowRenderer()) {
78
- setWindowRenderer(_dll_pie_lib__math_rendering);
105
+ if (!getMathRenderer()) {
106
+ setWindowRenderer(renderer);
107
+ createSharedInstance(renderer);
79
108
  logger.debug("Math rendering module initialized (both globals set)");
80
109
  }
81
110
  }
@@ -113,17 +142,3 @@ export async function initializeMathRendering(customRenderer) {
113
142
  export function setMathRenderer(renderer) {
114
143
  setWindowRenderer(renderer);
115
144
  }
116
- /**
117
- * Render math in the given element
118
- *
119
- * Convenience wrapper that calls the active renderer's renderMath() function.
120
- * Typically called after PIE elements are rendered.
121
- *
122
- * @param element - The element to render math within
123
- */
124
- export function renderMath(element) {
125
- const renderer = getWindowRenderer();
126
- if (renderer && typeof renderer.renderMath === "function") {
127
- renderer.renderMath(element);
128
- }
129
- }