@pie-players/pie-players-shared 0.3.72 → 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 +6 -4
  57. package/dist/pie/index.js +6 -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 +47 -0
  65. package/dist/pie/item-session-contract.js +196 -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 +121 -0
  77. package/dist/pie/session-commit.js +420 -0
  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
@@ -0,0 +1,166 @@
1
+ /**
2
+ * Device-local snapshot of a committed session.
3
+ *
4
+ * A crash or an OS kill fires no lifecycle event, so the only state that
5
+ * survives it is state already written. The snapshot writes each committed
6
+ * session to device storage and offers it back on the next load.
7
+ *
8
+ * It is offered, never applied. School devices are shared, and a player cannot
9
+ * tell a legitimate recovery from a previous student's draft, so the host
10
+ * decides.
11
+ *
12
+ * `sessionStorage` is the default, matching the annotation toolbar's existing
13
+ * use of it for learner annotations: it is tab-scoped and cleared when the tab
14
+ * closes, so a response does not outlive the sitting, and browser session
15
+ * restore brings it back after a crash. A host that wants recovery across a
16
+ * full browser restart injects a `localStorage`-backed store and owns the
17
+ * retention consequences.
18
+ */
19
+ const KEY_PREFIX = "pie-session-snapshot";
20
+ /**
21
+ * Whether an identity names one sitting.
22
+ *
23
+ * `sessionId` is the only field that distinguishes one learner's attempt from
24
+ * another's. Keyed on the item alone, a shared device offers the previous
25
+ * student's draft to the next one - and the offer is itself a disclosure, before
26
+ * the host ever decides whether to apply it. So without a `sessionId` there is
27
+ * no snapshot, and a host driving the player by props alone opts in by passing
28
+ * an explicit `key`.
29
+ */
30
+ export function isSnapshotIdentitySpecific(identity) {
31
+ return Boolean(identity.sessionId);
32
+ }
33
+ /**
34
+ * Key built from the delivery identity, so a snapshot is only offered back to
35
+ * the sitting that wrote it.
36
+ */
37
+ export function sessionSnapshotKey(identity) {
38
+ const parts = [
39
+ identity.assignmentId || "",
40
+ identity.sessionId || "",
41
+ identity.itemId || "",
42
+ ];
43
+ return `${KEY_PREFIX}:${parts.join("|")}`;
44
+ }
45
+ /**
46
+ * `sessionStorage`-backed store. Every access is guarded: private mode and
47
+ * blocked site data make the accessor throw, and a player must keep working
48
+ * without a snapshot rather than fail.
49
+ */
50
+ export function sessionStorageSnapshotStore() {
51
+ return {
52
+ read(key) {
53
+ try {
54
+ if (typeof sessionStorage === "undefined")
55
+ return null;
56
+ return sessionStorage.getItem(key);
57
+ }
58
+ catch {
59
+ return null;
60
+ }
61
+ },
62
+ write(key, value) {
63
+ try {
64
+ if (typeof sessionStorage === "undefined")
65
+ return;
66
+ sessionStorage.setItem(key, value);
67
+ }
68
+ catch {
69
+ // Quota, private mode, blocked site data: no snapshot this sitting.
70
+ }
71
+ },
72
+ clear(key) {
73
+ try {
74
+ if (typeof sessionStorage === "undefined")
75
+ return;
76
+ sessionStorage.removeItem(key);
77
+ }
78
+ catch {
79
+ // ignore
80
+ }
81
+ },
82
+ };
83
+ }
84
+ export function isSessionSnapshotEnabled(config) {
85
+ if (typeof config === "boolean")
86
+ return config;
87
+ if (!config)
88
+ return false;
89
+ return config.enabled !== false;
90
+ }
91
+ /**
92
+ * Returns `null` when the host has not enabled a snapshot, and when the
93
+ * identity does not name one sitting. PIE writes learner responses to the
94
+ * device only on an explicit opt-in, and only under a key no other learner
95
+ * shares.
96
+ */
97
+ export function createSessionSnapshot(args) {
98
+ if (!isSessionSnapshotEnabled(args.config))
99
+ return null;
100
+ const settings = typeof args.config === "object" && args.config ? args.config : {};
101
+ if (!settings.key && !isSnapshotIdentitySpecific(args.identity))
102
+ return null;
103
+ const key = settings.key || sessionSnapshotKey(args.identity);
104
+ const store = settings.store || sessionStorageSnapshotStore();
105
+ // A host-injected store is guarded the same way the default one is: a player
106
+ // keeps working without a snapshot rather than failing on storage.
107
+ const safeRead = () => {
108
+ try {
109
+ return store.read(key);
110
+ }
111
+ catch {
112
+ return null;
113
+ }
114
+ };
115
+ const safeWrite = (value) => {
116
+ try {
117
+ store.write(key, value);
118
+ }
119
+ catch {
120
+ // no snapshot this sitting
121
+ }
122
+ };
123
+ const safeClear = () => {
124
+ try {
125
+ store.clear(key);
126
+ }
127
+ catch {
128
+ // no snapshot this sitting
129
+ }
130
+ };
131
+ return {
132
+ key,
133
+ write(session) {
134
+ let payload;
135
+ try {
136
+ payload = JSON.stringify({ session, timestamp: Date.now() });
137
+ }
138
+ catch {
139
+ return;
140
+ }
141
+ safeWrite(payload);
142
+ },
143
+ read() {
144
+ const raw = safeRead();
145
+ if (!raw)
146
+ return null;
147
+ try {
148
+ const parsed = JSON.parse(raw);
149
+ if (!parsed || typeof parsed !== "object" || !("session" in parsed)) {
150
+ return null;
151
+ }
152
+ return {
153
+ key,
154
+ session: parsed.session,
155
+ timestamp: typeof parsed.timestamp === "number" ? parsed.timestamp : 0,
156
+ };
157
+ }
158
+ catch {
159
+ return null;
160
+ }
161
+ },
162
+ clear() {
163
+ safeClear();
164
+ },
165
+ };
166
+ }
@@ -15,3 +15,4 @@ export declare const toViewTag: (baseTag: string, view: PieViewMode | string, cu
15
15
  * Build a safe print tag by appending a deterministic, positive hash suffix.
16
16
  */
17
17
  export declare const toPrintHashedTag: (baseTag: string, hashSource: string, hashFn: (value: string) => number) => string;
18
+ export { parseVersionedTagName } from "./versioned-tag.js";
@@ -59,3 +59,4 @@ export const toPrintHashedTag = (baseTag, hashSource, hashFn) => {
59
59
  const hash = Math.abs(hashFn(hashSource));
60
60
  return `${baseTag}-print-${hash}`;
61
61
  };
62
+ export { parseVersionedTagName } from "./versioned-tag.js";
@@ -5,10 +5,6 @@
5
5
  * This module has NO runtime dependencies on other PIE modules.
6
6
  */
7
7
  import type { ConfigEntity, Env, PieController, PieModel } from "../types/index.js";
8
- /**
9
- * Player modes
10
- */
11
- export type PlayerMode = "gather" | "view" | "evaluate" | "author";
12
8
  /**
13
9
  * PIE custom element interface
14
10
  */
@@ -52,7 +48,8 @@ export interface AssetHandler {
52
48
  export declare enum BundleType {
53
49
  player = "player.js",// Elements only (no controllers)
54
50
  clientPlayer = "client-player.js",// Elements + controllers
55
- editor = "editor.js"
51
+ editor = "editor.js",// Editor UI
52
+ esm = "esm"
56
53
  }
57
54
  /**
58
55
  * Internal PIE registry entry status
@@ -93,6 +90,8 @@ export interface UpdatePieElementOptions {
93
90
  session: any[];
94
91
  env?: Env;
95
92
  invokeControllerForModel?: boolean;
93
+ /** The delivering player's bundle type, see `findPieController`. */
94
+ bundleType?: BundleType;
96
95
  eventListeners?: EventListeners;
97
96
  container?: Element | Document;
98
97
  /**
package/dist/pie/types.js CHANGED
@@ -12,6 +12,7 @@ export var BundleType;
12
12
  BundleType["player"] = "player.js";
13
13
  BundleType["clientPlayer"] = "client-player.js";
14
14
  BundleType["editor"] = "editor.js";
15
+ BundleType["esm"] = "esm";
15
16
  })(BundleType || (BundleType = {}));
16
17
  /**
17
18
  * Internal PIE registry entry status
@@ -4,7 +4,7 @@
4
4
  * Functions for updating PIE elements with new models, sessions, and env.
5
5
  */
6
6
  import type { ConfigEntity, Env } from "../types/index.js";
7
- import type { UpdatePieElementOptions } from "./types.js";
7
+ import type { BundleType, UpdatePieElementOptions } from "./types.js";
8
8
  /**
9
9
  * Update a PIE element by ref (direct Element reference)
10
10
  */
@@ -16,4 +16,4 @@ export declare const updatePieElement: (elName: string, opts: UpdatePieElementOp
16
16
  /**
17
17
  * Update all PIE elements in a config
18
18
  */
19
- export declare const updatePieElements: (config: ConfigEntity, session: any[], env: Env, container?: Element | Document, onElementSessionUpdate?: UpdatePieElementOptions["onElementSessionUpdate"]) => Promise<void>;
19
+ export declare const updatePieElements: (config: ConfigEntity, session: any[], env: Env, container?: Element | Document, onElementSessionUpdate?: UpdatePieElementOptions["onElementSessionUpdate"], bundleType?: BundleType) => Promise<void>;
@@ -4,7 +4,6 @@
4
4
  * Functions for updating PIE elements with new models, sessions, and env.
5
5
  */
6
6
  import { mergeObjectsIgnoringNullUndefined } from "../object/index.js";
7
- import { wrapModelRichContent } from "../security/wrap-model-rich-content.js";
8
7
  import { createPieLogger, isGlobalDebugEnabled } from "./logger.js";
9
8
  import { findPieController } from "./scoring.js";
10
9
  import { defaultPieElementOptions } from "./types.js";
@@ -55,11 +54,23 @@ const emitControllerError = (pieElement, detail) => {
55
54
  composed: true,
56
55
  }));
57
56
  };
57
+ /**
58
+ * Reports whether an element has left the document since its update started.
59
+ * A legacy element unmounts its React root when it is disconnected, and a
60
+ * write after that throws React error #409 in releases before PIE-703, or
61
+ * renders a tree nothing unmounts in later ones. The controller call is when a
62
+ * host can remove the player. An element that was never inserted still takes
63
+ * the update, so a caller can set one up before inserting it.
64
+ */
65
+ const trackRemoval = (element) => {
66
+ const wasConnected = element.isConnected;
67
+ return () => wasConnected && !element.isConnected;
68
+ };
58
69
  /**
59
70
  * Helper function to apply controller to element
60
71
  * Extracted to eliminate duplication and ensure consistent controller invocation
61
72
  */
62
- const applyControllerToElement = async (element, model, elementSession, controller, env, logPrefix, onElementSessionUpdate) => {
73
+ const applyControllerToElement = async (element, model, elementSession, controller, env, logPrefix, wasRemoved, onElementSessionUpdate) => {
63
74
  logger.debug(`${logPrefix} Using controller, env:`, env);
64
75
  logger.debug(`${logPrefix} Model before filter:`, {
65
76
  id: model.id,
@@ -93,15 +104,16 @@ const applyControllerToElement = async (element, model, elementSession, controll
93
104
  element: model.element,
94
105
  ...controllerResultObject,
95
106
  };
96
- const wrappedModel = wrapModelRichContent(filteredModel);
97
107
  logger.debug(`${logPrefix} ✅ Controller filtered model:`, {
98
- id: wrappedModel.id,
99
- element: wrappedModel.element,
100
- hasCorrectResponse: "correctResponse" in wrappedModel,
108
+ id: filteredModel.id,
109
+ element: filteredModel.element,
110
+ hasCorrectResponse: "correctResponse" in filteredModel,
101
111
  mode: env.mode,
102
112
  role: env.role,
103
113
  });
104
- element.model = wrappedModel;
114
+ if (wasRemoved())
115
+ return;
116
+ element.model = filteredModel;
105
117
  element.session = elementSession;
106
118
  }
107
119
  catch (err) {
@@ -110,7 +122,7 @@ const applyControllerToElement = async (element, model, elementSession, controll
110
122
  }
111
123
  };
112
124
  const resolveAndValidateUpdateOptions = (opts) => {
113
- const { config, session, env, eventListeners, invokeControllerForModel, onElementSessionUpdate, } = mergeObjectsIgnoringNullUndefined(defaultPieElementOptions, opts);
125
+ const { config, session, env, eventListeners, invokeControllerForModel, bundleType, onElementSessionUpdate, } = mergeObjectsIgnoringNullUndefined(defaultPieElementOptions, opts);
114
126
  if (!env) {
115
127
  throw new Error("env is required");
116
128
  }
@@ -126,11 +138,12 @@ const resolveAndValidateUpdateOptions = (opts) => {
126
138
  env,
127
139
  eventListeners,
128
140
  invokeControllerForModel,
141
+ bundleType,
129
142
  onElementSessionUpdate,
130
143
  };
131
144
  };
132
145
  const updateSinglePieElement = async (pieElement, controllerLookupTag, options, logContext) => {
133
- const { config, session, env, eventListeners, invokeControllerForModel, onElementSessionUpdate, } = options;
146
+ const { config, session, env, eventListeners, invokeControllerForModel, bundleType, onElementSessionUpdate, } = options;
134
147
  const model = config.models?.find((m) => m.id === pieElement.id);
135
148
  if (!model) {
136
149
  logger.error(`${logContext} Model not found for`, controllerLookupTag);
@@ -144,10 +157,10 @@ const updateSinglePieElement = async (pieElement, controllerLookupTag, options,
144
157
  });
145
158
  }
146
159
  if (env && invokeControllerForModel) {
147
- const controller = findPieController(controllerLookupTag);
160
+ const controller = findPieController(controllerLookupTag, bundleType);
148
161
  if (!controller) {
149
162
  logger.debug(`${logContext} ℹ️ No controller for ${controllerLookupTag}, using server-processed model`);
150
- pieElement.model = wrapModelRichContent(model);
163
+ pieElement.model = model;
151
164
  pieElement.session = elementSession;
152
165
  return;
153
166
  }
@@ -160,8 +173,9 @@ const updateSinglePieElement = async (pieElement, controllerLookupTag, options,
160
173
  // caller computes/emits the session signature; otherwise a late, async
161
174
  // shuffle write lands after the cycle that read the session and the order
162
175
  // never round-trips (PIE-631).
176
+ const wasRemoved = trackRemoval(pieElement);
163
177
  try {
164
- await applyControllerToElement(pieElement, model, elementSession, controller, env, `${logContext}(${controllerLookupTag}#${pieElement.id})`, onElementSessionUpdate);
178
+ await applyControllerToElement(pieElement, model, elementSession, controller, env, `${logContext}(${controllerLookupTag}#${pieElement.id})`, wasRemoved, onElementSessionUpdate);
165
179
  }
166
180
  catch (err) {
167
181
  const errorMessage = err instanceof Error ? err.message : String(err);
@@ -172,19 +186,22 @@ const updateSinglePieElement = async (pieElement, controllerLookupTag, options,
172
186
  ? "PIE_CONTROLLER_CONTRACT_ERROR"
173
187
  : "PIE_CONTROLLER_RUNTIME_ERROR",
174
188
  message: `${controllerLookupTag} controller failed while applying model for ${pieElement.id}. ${errorMessage}`,
189
+ recoverable: true,
175
190
  elementName: controllerLookupTag,
176
191
  elementId: pieElement.id,
177
192
  controllerShape: describeControllerShape(controller),
178
193
  cause: errorMessage,
179
194
  });
180
195
  // Fall back to raw model on controller error
181
- pieElement.model = wrapModelRichContent(model);
196
+ if (wasRemoved())
197
+ return;
198
+ pieElement.model = model;
182
199
  pieElement.session = elementSession;
183
200
  }
184
201
  }
185
202
  else {
186
203
  logger.debug(`${logContext} Direct model assignment for ${controllerLookupTag}#${pieElement.id} (no controller invocation requested)`);
187
- pieElement.model = wrapModelRichContent(model);
204
+ pieElement.model = model;
188
205
  pieElement.session = elementSession;
189
206
  }
190
207
  };
@@ -215,7 +232,7 @@ export const updatePieElement = (elName, opts) => {
215
232
  /**
216
233
  * Update all PIE elements in a config
217
234
  */
218
- export const updatePieElements = (config, session, env, container, onElementSessionUpdate) => {
235
+ export const updatePieElements = (config, session, env, container, onElementSessionUpdate, bundleType) => {
219
236
  logger.debug("[updatePieElements] Updating all elements with env:", env);
220
237
  return Promise.all(Object.entries(config.elements).map(([elName, _pkg]) => updatePieElement(elName, {
221
238
  config,
@@ -223,5 +240,6 @@ export const updatePieElements = (config, session, env, container, onElementSess
223
240
  env,
224
241
  container,
225
242
  onElementSessionUpdate,
243
+ bundleType,
226
244
  }))).then(() => undefined);
227
245
  };
@@ -1,3 +1,7 @@
1
+ /**
2
+ * Split a runtime tag into its base name and the encoded version that follows
3
+ * `--version-`, when it has one.
4
+ */
1
5
  export declare function parseVersionedTagName(tagName: string): {
2
6
  baseName: string;
3
7
  existingEncodedVersion?: string;
@@ -6,6 +6,10 @@ const encodeVersionForTag = (version) => version
6
6
  .replace(/[.+]/g, "-")
7
7
  .replace(/[^0-9A-Za-z-]/g, "-")
8
8
  .replace(/-{2,}/g, "-");
9
+ /**
10
+ * Split a runtime tag into its base name and the encoded version that follows
11
+ * `--version-`, when it has one.
12
+ */
9
13
  export function parseVersionedTagName(tagName) {
10
14
  const versionMatch = tagName.match(new RegExp(`${VERSION_DELIMITER}(${TAG_VERSION_PATTERN})$`));
11
15
  if (!versionMatch || versionMatch.index === undefined) {
@@ -1,7 +1,6 @@
1
- export { buildAuthoringAllowList, createDefaultItemMarkupSanitizer, resetPurifierForTesting, sanitizeItemMarkup, type ItemMarkupSanitizer, type SanitizeItemMarkupOptions, } from "./sanitize-item-markup.js";
1
+ export { buildAuthoringAllowList, createDefaultItemMarkupSanitizer, sanitizeItemMarkup, type ItemMarkupSanitizer, type SanitizeItemMarkupOptions, } from "./sanitize-item-markup.js";
2
2
  export { parseAllowedStyleOrigins, validateExternalStyleUrl, type StyleUrlValidationError, type StyleUrlValidationOk, type StyleUrlValidationOptions, type StyleUrlValidationResult, } from "./validate-style-url.js";
3
- export { resetSvgSanitizerForTesting, sanitizeSvgIcon, } from "./sanitize-svg-icon.js";
3
+ export { sanitizeSvgIcon } from "./sanitize-svg-icon.js";
4
4
  export { sanitizeStyleAttribute } from "./sanitize-style-attribute.js";
5
5
  export { isOverwideImageWrapMutation, wrapOverwideImages, wrapOverwideImagesInElement, } from "./wrap-overwide-images.js";
6
- export { wrapModelRichContent } from "./wrap-model-rich-content.js";
7
6
  export { isOverwideTableWrapMutation, wrapOverwideTables, wrapOverwideTablesInElement, } from "./wrap-overwide-tables.js";
@@ -1,7 +1,6 @@
1
- export { buildAuthoringAllowList, createDefaultItemMarkupSanitizer, resetPurifierForTesting, sanitizeItemMarkup, } from "./sanitize-item-markup.js";
1
+ export { buildAuthoringAllowList, createDefaultItemMarkupSanitizer, sanitizeItemMarkup, } from "./sanitize-item-markup.js";
2
2
  export { parseAllowedStyleOrigins, validateExternalStyleUrl, } from "./validate-style-url.js";
3
- export { resetSvgSanitizerForTesting, sanitizeSvgIcon, } from "./sanitize-svg-icon.js";
3
+ export { sanitizeSvgIcon } from "./sanitize-svg-icon.js";
4
4
  export { sanitizeStyleAttribute } from "./sanitize-style-attribute.js";
5
5
  export { isOverwideImageWrapMutation, wrapOverwideImages, wrapOverwideImagesInElement, } from "./wrap-overwide-images.js";
6
- export { wrapModelRichContent } from "./wrap-model-rich-content.js";
7
6
  export { isOverwideTableWrapMutation, wrapOverwideTables, wrapOverwideTablesInElement, } from "./wrap-overwide-tables.js";
@@ -52,5 +52,3 @@ export declare function createDefaultItemMarkupSanitizer(options?: SanitizeItemM
52
52
  * keeps the rewritten `-config` tags instead of stripping them.
53
53
  */
54
54
  export declare function buildAuthoringAllowList(elementTagNames: Iterable<string>): string[];
55
- /** Reset the memoised DOMPurify instance. Only intended for tests. */
56
- export declare function resetPurifierForTesting(): void;
@@ -144,7 +144,3 @@ export function buildAuthoringAllowList(elementTagNames) {
144
144
  }
145
145
  return [...out];
146
146
  }
147
- /** Reset the memoised DOMPurify instance. Only intended for tests. */
148
- export function resetPurifierForTesting() {
149
- purifierInstance = null;
150
- }
@@ -10,5 +10,3 @@
10
10
  * or the input is not a string.
11
11
  */
12
12
  export declare function sanitizeSvgIcon(icon: unknown): string;
13
- /** Reset sanitizer state. Intended for tests. */
14
- export declare function resetSvgSanitizerForTesting(): void;
@@ -61,8 +61,3 @@ export function sanitizeSvgIcon(icon) {
61
61
  stringCache.set(icon, str);
62
62
  return str;
63
63
  }
64
- /** Reset sanitizer state. Intended for tests. */
65
- export function resetSvgSanitizerForTesting() {
66
- svgPurifierInstance = null;
67
- stringCache.clear();
68
- }
@@ -14,6 +14,11 @@
14
14
  * the real divergence — an image has `alt`, a table has `<caption>`,
15
15
  * `aria-label` and `aria-labelledby` — which is why it is a function rather than
16
16
  * a template.
17
+ *
18
+ * Content inside an editing host is never wrapped, on either pass. The editor
19
+ * owns that DOM: ProseMirror reads the wrapper as a foreign mutation and redraws
20
+ * the node without it, so an observer-driven wrap and the editor undo each other
21
+ * for as long as the editor holds an image.
17
22
  */
18
23
  /** What one content kind needs in order to be wrapped. */
19
24
  export interface OverwideWrapSpec {
@@ -14,8 +14,22 @@
14
14
  * the real divergence — an image has `alt`, a table has `<caption>`,
15
15
  * `aria-label` and `aria-labelledby` — which is why it is a function rather than
16
16
  * a template.
17
+ *
18
+ * Content inside an editing host is never wrapped, on either pass. The editor
19
+ * owns that DOM: ProseMirror reads the wrapper as a foreign mutation and redraws
20
+ * the node without it, so an observer-driven wrap and the editor undo each other
21
+ * for as long as the editor holds an image.
17
22
  */
18
23
  const PIE_CUSTOM_ELEMENT_TAG_REGEX = /^pie-/i;
24
+ /**
25
+ * An editing host: `contenteditable` in any state but `false`. A
26
+ * `contenteditable="false"` island inside a host still belongs to the host's
27
+ * editor — ProseMirror marks every leaf node that way, images included — so the
28
+ * test looks past such islands to the nearest host. A read-only editor root
29
+ * (`contenteditable="false"`) is no host; ProseMirror registers no DOM mutations
30
+ * while its view is not editable.
31
+ */
32
+ const EDITING_HOST_SELECTOR = '[contenteditable]:not([contenteditable="false" i])';
19
33
  function isInsidePieCustomElement(node, root) {
20
34
  let ancestor = node.parentElement;
21
35
  while (ancestor && ancestor !== root) {
@@ -49,6 +63,8 @@ export function wrapOverwideInElement(root, spec, options = {}) {
49
63
  // Authored-markup pass: leave PIE custom-element internals alone.
50
64
  if (skipPieDescendants && isInsidePieCustomElement(node, root))
51
65
  continue;
66
+ if (node.closest(EDITING_HOST_SELECTOR))
67
+ continue;
52
68
  const wrapper = ownerDocument.createElement(spec.wrapperTag);
53
69
  wrapper.className = spec.wrapperClass;
54
70
  wrapper.setAttribute("tabindex", "0");
@@ -6,6 +6,7 @@
6
6
  * reports them as a framework error and delivers as an ordinary section, which is
7
7
  * loud without costing the learner the content.
8
8
  */
9
+ import { normalizeMediaFragment } from "../media/index.js";
9
10
  /**
10
11
  * Applied when `playbackPolicy` is absent entirely. The restrictive values are
11
12
  * the defaults because a timed-media section is authored to sequence: a section
@@ -43,21 +44,6 @@ function isCorrectnessCondition(condition) {
43
44
  function trimmed(value) {
44
45
  return typeof value === "string" ? value.trim() : "";
45
46
  }
46
- function normalizeRange(value) {
47
- if (!value || typeof value !== "object")
48
- return null;
49
- const raw = value;
50
- const start = Number(raw.startSeconds);
51
- if (!Number.isFinite(start) || start < 0)
52
- return null;
53
- const end = Number(raw.endSeconds);
54
- // An end at or before the start is a window that can never contain a
55
- // position, so it is treated as no end — the same rule
56
- // `normalizeMediaFragment` applies in the toolkit, kept identical on purpose.
57
- if (!Number.isFinite(end) || end <= start)
58
- return { startSeconds: start };
59
- return { startSeconds: start, endSeconds: end };
60
- }
61
47
  function normalizePlaybackPolicy(value) {
62
48
  if (value === undefined || value === null) {
63
49
  return { policy: { ...TIMED_MEDIA_PLAYBACK_POLICY_DEFAULTS }, error: null };
@@ -179,7 +165,10 @@ export function normalizeTimedMediaSectionData(args) {
179
165
  continue;
180
166
  }
181
167
  seenIdentifiers.add(identifier);
182
- const range = normalizeRange(cue.range);
168
+ // The same rule as every other authored media range: an end at or before
169
+ // the start is a window that can never contain a position, so it is
170
+ // treated as no end.
171
+ const range = normalizeMediaFragment(cue.range);
183
172
  if (!range) {
184
173
  errors.push({
185
174
  code: "invalid-cue-range",