@pie-players/pie-players-shared 0.3.74 → 1.0.0-beta.0

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 (86) hide show
  1. package/README.md +88 -75
  2. package/dist/components/vendor/nds/README.md +4 -5
  3. package/dist/config/profile.d.ts +1 -1
  4. package/dist/config/profile.js +1 -1
  5. package/dist/i18n/index.d.ts +1 -1
  6. package/dist/i18n/index.js +1 -1
  7. package/dist/i18n/language-tags.d.ts +9 -1
  8. package/dist/i18n/language-tags.js +11 -1
  9. package/dist/i18n/messages/en-US.d.ts +33 -28
  10. package/dist/i18n/messages/en-US.js +34 -29
  11. package/dist/i18n/messages/nl-NL.d.ts +33 -28
  12. package/dist/i18n/messages/nl-NL.js +34 -29
  13. package/dist/i18n/provider.d.ts +14 -7
  14. package/dist/i18n/provider.js +26 -29
  15. package/dist/i18n/types.d.ts +4 -5
  16. package/dist/index.d.ts +1 -1
  17. package/dist/index.js +0 -1
  18. package/dist/instrumentation/providers/BaseInstrumentationProvider.js +0 -14
  19. package/dist/loader-config.d.ts +0 -2
  20. package/dist/loader-config.js +0 -2
  21. package/dist/loaders/element-loader.js +13 -6
  22. package/dist/loaders/esm-adapter.d.ts +11 -0
  23. package/dist/loaders/esm-adapter.js +74 -45
  24. package/dist/loaders/iife-adapter.d.ts +3 -3
  25. package/dist/loaders/iife-adapter.js +36 -35
  26. package/dist/loaders/index.d.ts +4 -2
  27. package/dist/loaders/index.js +2 -1
  28. package/dist/loaders/mathjax-events.d.ts +22 -0
  29. package/dist/loaders/mathjax-events.js +65 -0
  30. package/dist/loaders/preloaded-registration.d.ts +18 -2
  31. package/dist/loaders/preloaded-registration.js +14 -2
  32. package/dist/media/index.d.ts +4 -6
  33. package/dist/media/index.js +6 -8
  34. package/dist/object/index.d.ts +8 -3
  35. package/dist/object/index.js +8 -6
  36. package/dist/pie/asset-handler.d.ts +0 -17
  37. package/dist/pie/asset-handler.js +1 -1
  38. package/dist/pie/authoring.js +5 -7
  39. package/dist/pie/elements-rendered.d.ts +23 -0
  40. package/dist/pie/elements-rendered.js +96 -0
  41. package/dist/pie/index.d.ts +4 -6
  42. package/dist/pie/index.js +4 -7
  43. package/dist/pie/initialization.d.ts +13 -14
  44. package/dist/pie/initialization.js +61 -75
  45. package/dist/pie/instrumentation-event-bridge.js +23 -1
  46. package/dist/pie/instrumentation-event-map.js +11 -18
  47. package/dist/pie/item-controller.js +4 -0
  48. package/dist/pie/item-session-contract.js +7 -9
  49. package/dist/pie/logger.d.ts +6 -0
  50. package/dist/pie/logger.js +10 -0
  51. package/dist/pie/math-assets.d.ts +48 -0
  52. package/dist/pie/math-assets.js +81 -0
  53. package/dist/pie/resource-monitor.d.ts +34 -32
  54. package/dist/pie/resource-monitor.js +152 -231
  55. package/dist/pie/scoring.d.ts +1 -6
  56. package/dist/pie/scoring.js +3 -8
  57. package/dist/pie/session-commit.js +1 -1
  58. package/dist/pie/stages.d.ts +11 -47
  59. package/dist/pie/stages.js +11 -47
  60. package/dist/pie/updates.js +5 -4
  61. package/dist/pie/utils.d.ts +0 -2
  62. package/dist/pie/utils.js +0 -2
  63. package/dist/security/sanitize-item-markup.d.ts +8 -4
  64. package/dist/security/sanitize-item-markup.js +52 -6
  65. package/dist/security/sanitize-svg-icon.js +2 -2
  66. package/dist/security/wrap-overwide-images.d.ts +1 -1
  67. package/dist/security/wrap-overwide-images.js +20 -1
  68. package/dist/security/wrap-overwide.d.ts +8 -0
  69. package/dist/security/wrap-overwide.js +47 -3
  70. package/dist/timed-media/media-element-source.js +1 -2
  71. package/dist/types/index.d.ts +88 -153
  72. package/dist/types/index.js +2 -2
  73. package/dist/ui/attribute-coercion.d.ts +5 -0
  74. package/dist/ui/attribute-coercion.js +6 -4
  75. package/dist/ui/content-styles.d.ts +6 -10
  76. package/dist/ui/content-styles.js +8 -13
  77. package/dist/ui/first-focusable.d.ts +0 -1
  78. package/dist/ui/first-focusable.js +0 -1
  79. package/dist/ui/focus-trap.js +2 -3
  80. package/dist/ui/scope-css.d.ts +23 -26
  81. package/dist/ui/scope-css.js +23 -26
  82. package/package.json +6 -4
  83. package/dist/loaders/mathjax-version-conflict.d.ts +0 -17
  84. package/dist/loaders/mathjax-version-conflict.js +0 -51
  85. package/dist/pie/stage-tracker.d.ts +0 -50
  86. package/dist/pie/stage-tracker.js +0 -105
@@ -1,7 +1,7 @@
1
1
  /**
2
- * A problem using the spread operator merging objects is that undefined properties may override
3
- * defined properties, which is often NOT what you want (e.g. when providing default values). This
4
- * function merges objects, but ignores properties that are null or undefined.
2
+ * Merges objects left to right, skipping properties whose value is `null` or
3
+ * `undefined`, so a later object's unset field never overrides an earlier
4
+ * default the way object spread does.
5
5
  * @param objects The objects to merge.
6
6
  */
7
7
  export declare const mergeObjectsIgnoringNullUndefined: <T extends object>(...objects: T[]) => T;
@@ -9,3 +9,8 @@ export declare const mergeObjectsIgnoringNullUndefined: <T extends object>(...ob
9
9
  * Deep clone an object.
10
10
  */
11
11
  export declare const cloneDeep: <T>(value: T) => T;
12
+ /**
13
+ * A non-null object that is not an array. The prototype is not checked, so
14
+ * class instances pass.
15
+ */
16
+ export declare const isPlainRecord: (value: unknown) => value is Record<string, unknown>;
@@ -1,7 +1,7 @@
1
1
  /**
2
- * A problem using the spread operator merging objects is that undefined properties may override
3
- * defined properties, which is often NOT what you want (e.g. when providing default values). This
4
- * function merges objects, but ignores properties that are null or undefined.
2
+ * Merges objects left to right, skipping properties whose value is `null` or
3
+ * `undefined`, so a later object's unset field never overrides an earlier
4
+ * default the way object spread does.
5
5
  * @param objects The objects to merge.
6
6
  */
7
7
  export const mergeObjectsIgnoringNullUndefined = (...objects) => {
@@ -21,14 +21,11 @@ export const mergeObjectsIgnoringNullUndefined = (...objects) => {
21
21
  */
22
22
  export const cloneDeep = (value) => {
23
23
  if (value === null || typeof value !== "object") {
24
- // Primitive value (or null), just return it
25
24
  return value;
26
25
  }
27
26
  if (Array.isArray(value)) {
28
- // Array: clone each element recursively
29
27
  return value.map(cloneDeep);
30
28
  }
31
- // Object: create a new object and clone its properties
32
29
  const copy = {};
33
30
  for (const key in value) {
34
31
  if (Object.hasOwn(value, key)) {
@@ -37,3 +34,8 @@ export const cloneDeep = (value) => {
37
34
  }
38
35
  return copy;
39
36
  };
37
+ /**
38
+ * A non-null object that is not an array. The prototype is not checked, so
39
+ * class instances pass.
40
+ */
41
+ export const isPlainRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
@@ -5,23 +5,6 @@
5
5
  * Provides default DataURL-based implementation and event management.
6
6
  */
7
7
  import { type ImageHandler, type SoundHandler } from "../types/index.js";
8
- import type { AssetHandler } from "./types.js";
9
- /**
10
- * DataURL-based asset handler (default implementation)
11
- *
12
- * Converts uploaded files to base64 data URLs.
13
- * Suitable for demos and local development.
14
- */
15
- export declare class DataURLAssetHandler implements AssetHandler {
16
- private onComplete;
17
- private cancelled;
18
- private fileReader;
19
- constructor(onComplete: (src: string) => void);
20
- cancel(): void;
21
- done(err?: Error, src?: string): void;
22
- fileChosen(file: File): void;
23
- progress(percent: number, bytes: number, total: number): void;
24
- }
25
8
  /**
26
9
  * Asset Event Manager
27
10
  *
@@ -13,7 +13,7 @@ const logger = createPieLogger("asset-handler", () => isGlobalDebugEnabled());
13
13
  * Converts uploaded files to base64 data URLs.
14
14
  * Suitable for demos and local development.
15
15
  */
16
- export class DataURLAssetHandler {
16
+ class DataURLAssetHandler {
17
17
  onComplete;
18
18
  cancelled = false;
19
19
  fileReader = null;
@@ -2,10 +2,8 @@ import { AssetEventManager } from "./asset-handler.js";
2
2
  import { createPieLogger, isGlobalDebugEnabled } from "./logger.js";
3
3
  import { findPieController } from "./scoring.js";
4
4
  import { parsePackageName } from "./utils.js";
5
+ import { isPlainRecord } from "../object/index.js";
5
6
  const logger = createPieLogger("authoring", () => isGlobalDebugEnabled());
6
- function isRecord(value) {
7
- return !!value && typeof value === "object" && !Array.isArray(value);
8
- }
9
7
  function packageNameWithoutVersion(packageSpec) {
10
8
  try {
11
9
  return parsePackageName(packageSpec).name;
@@ -19,7 +17,7 @@ function packageBaseName(packageName) {
19
17
  return lastSegment || packageName;
20
18
  }
21
19
  function lookupOwn(source, keys) {
22
- if (!isRecord(source))
20
+ if (!isPlainRecord(source))
23
21
  return undefined;
24
22
  for (const key of keys) {
25
23
  if (Object.hasOwn(source, key)) {
@@ -32,7 +30,7 @@ function mergeConfiguration(deliveryConfig, authoringConfig) {
32
30
  if (authoringConfig === undefined) {
33
31
  return deliveryConfig ?? {};
34
32
  }
35
- if (isRecord(deliveryConfig) && isRecord(authoringConfig)) {
33
+ if (isPlainRecord(deliveryConfig) && isPlainRecord(authoringConfig)) {
36
34
  return {
37
35
  ...deliveryConfig,
38
36
  ...authoringConfig,
@@ -108,9 +106,9 @@ function resolveValidationController(configureElement) {
108
106
  // A controller's `validate` returns a field → message map. ebsr returns one map
109
107
  // per part, so an empty ebsr result is `{ partA: {}, partB: {} }`.
110
108
  function hasValidationErrors(errors) {
111
- if (!isRecord(errors))
109
+ if (!isPlainRecord(errors))
112
110
  return false;
113
- if (isRecord(errors.partA) && isRecord(errors.partB)) {
111
+ if (isPlainRecord(errors.partA) && isPlainRecord(errors.partB)) {
114
112
  return (Object.keys(errors.partA).length > 0 ||
115
113
  Object.keys(errors.partB).length > 0);
116
114
  }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * When the PIE elements of a player have rendered.
3
+ *
4
+ * An element renders after its model and session are assigned, in a task of
5
+ * its own: legacy React elements debounce the render and commit through the
6
+ * React scheduler, so the commit lands tens of milliseconds after the
7
+ * assignment. No event marks it in both element generations — `model-set` is
8
+ * dispatched by about half of them, some before rendering — so this reads the
9
+ * DOM.
10
+ *
11
+ * An element has rendered once it holds content, and so has every custom
12
+ * element defined below it: an `ebsr` paints its parts, which then render
13
+ * themselves. Some elements render nothing, a rubric for a student among them,
14
+ * so an element still empty when the subtree has had no mutation for
15
+ * `quietMs` counts as rendered. A render slower than that window, with no
16
+ * mutation before it, is missed rather than waited for.
17
+ */
18
+ import type { ConfigEntity } from "../types/index.js";
19
+ /**
20
+ * Resolves once every element of `configs` in `root` has rendered, or when
21
+ * `signal` aborts. Never rejects.
22
+ */
23
+ export declare function elementsRendered(root: Element, configs: ConfigEntity[], signal: AbortSignal, quietMs?: number): Promise<void>;
@@ -0,0 +1,96 @@
1
+ /**
2
+ * When the PIE elements of a player have rendered.
3
+ *
4
+ * An element renders after its model and session are assigned, in a task of
5
+ * its own: legacy React elements debounce the render and commit through the
6
+ * React scheduler, so the commit lands tens of milliseconds after the
7
+ * assignment. No event marks it in both element generations — `model-set` is
8
+ * dispatched by about half of them, some before rendering — so this reads the
9
+ * DOM.
10
+ *
11
+ * An element has rendered once it holds content, and so has every custom
12
+ * element defined below it: an `ebsr` paints its parts, which then render
13
+ * themselves. Some elements render nothing, a rubric for a student among them,
14
+ * so an element still empty when the subtree has had no mutation for
15
+ * `quietMs` counts as rendered. A render slower than that window, with no
16
+ * mutation before it, is missed rather than waited for.
17
+ */
18
+ /** Longer than the 50ms render debounce legacy elements use, plus a scheduler task. */
19
+ const ELEMENT_RENDER_QUIET_MS = 200;
20
+ function hasContent(element) {
21
+ if (element.firstElementChild || element.shadowRoot?.firstChild)
22
+ return true;
23
+ return Boolean(element.textContent?.trim());
24
+ }
25
+ function isDefinedCustomElement(element) {
26
+ return (element.localName.includes("-") &&
27
+ customElements.get(element.localName) !== undefined);
28
+ }
29
+ function hasRendered(element) {
30
+ if (!element.isConnected)
31
+ return true;
32
+ if (!hasContent(element))
33
+ return false;
34
+ for (const nested of element.querySelectorAll("*")) {
35
+ if (isDefinedCustomElement(nested) && !hasContent(nested))
36
+ return false;
37
+ }
38
+ return true;
39
+ }
40
+ /** The elements of `configs` in `root`, found as `updatePieElements` finds them. */
41
+ function renderedElements(root, configs) {
42
+ const elements = [];
43
+ for (const config of configs) {
44
+ const ids = new Set((config.models ?? []).map((model) => model?.id));
45
+ for (const tag of Object.keys(config.elements ?? {})) {
46
+ // An undefined tag renders nothing until its bundle loads.
47
+ if (!customElements.get(tag))
48
+ continue;
49
+ for (const element of root.querySelectorAll(tag)) {
50
+ if (ids.has(element.id))
51
+ elements.push(element);
52
+ }
53
+ }
54
+ }
55
+ return elements;
56
+ }
57
+ /**
58
+ * Resolves once every element of `configs` in `root` has rendered, or when
59
+ * `signal` aborts. Never rejects.
60
+ */
61
+ export function elementsRendered(root, configs, signal, quietMs = ELEMENT_RENDER_QUIET_MS) {
62
+ const elements = renderedElements(root, configs);
63
+ const allRendered = () => elements.every(hasRendered);
64
+ if (signal.aborted ||
65
+ allRendered() ||
66
+ typeof MutationObserver === "undefined") {
67
+ return Promise.resolve();
68
+ }
69
+ return new Promise((resolve) => {
70
+ let quiet;
71
+ const observer = new MutationObserver(() => {
72
+ if (allRendered()) {
73
+ done();
74
+ return;
75
+ }
76
+ armQuiet();
77
+ });
78
+ function armQuiet() {
79
+ clearTimeout(quiet);
80
+ quiet = setTimeout(done, quietMs);
81
+ }
82
+ function done() {
83
+ clearTimeout(quiet);
84
+ observer.disconnect();
85
+ signal.removeEventListener("abort", done);
86
+ resolve();
87
+ }
88
+ observer.observe(root, {
89
+ childList: true,
90
+ subtree: true,
91
+ characterData: true,
92
+ });
93
+ signal.addEventListener("abort", done);
94
+ armQuiet();
95
+ });
96
+ }
@@ -18,16 +18,14 @@ export { initializeMathRendering, setMathRenderer, } from "./math-rendering.js";
18
18
  export { attachInstrumentationEventBridge } from "./instrumentation-event-bridge.js";
19
19
  export type { AttachInstrumentationEventBridgeArgs } from "./instrumentation-event-bridge.js";
20
20
  export { resolveInstrumentationProvider } from "./instrumentation-provider-resolution.js";
21
- export { STAGES, applicableStages, stageOrdinal, } from "./stages.js";
22
- export type { LoadingCompleteDetail, Stage, StageChangeDetail, StageSourceCe, StageStatus, } from "./stages.js";
23
- export { createStageTracker } from "./stage-tracker.js";
24
- export type { CreateStageTrackerOptions, StageTracker, } from "./stage-tracker.js";
21
+ export { STAGES } from "./stages.js";
22
+ export type { LoadingCompleteDetail, Stage, StageChangeDetail, StageStatus, } from "./stages.js";
25
23
  export { ASSESSMENT_INSTRUMENTATION_EVENT_MAP, ITEM_INSTRUMENTATION_EVENT_MAP, SECTION_INSTRUMENTATION_EVENT_MAP, TOOLKIT_INSTRUMENTATION_EVENT_MAP, } from "./instrumentation-event-map.js";
26
24
  export type { InstrumentationEventMapping } from "./instrumentation-event-map.js";
27
25
  export type { ItemControllerOptions } from "./item-controller.js";
28
26
  export { ItemController, normalizeItemSessionContainer, } from "./item-controller.js";
29
27
  export type { ItemSessionContainer as CanonicalItemSessionContainer, ItemSessionUpdateIntent, NormalizedItemSessionChange, } from "./item-session-contract.js";
30
- export { ensureHostSessionEntries, hasLearnerResponse, hasResponseValue, normalizeItemSessionChange, projectSessionIntoHostContainer, } from "./item-session-contract.js";
28
+ export { ensureHostSessionEntries, hasLearnerResponse, hasResponseField, hasResponseValue, normalizeItemSessionChange, projectSessionIntoHostContainer, } from "./item-session-contract.js";
31
29
  export type { ItemSessionContainer, ItemSessionStorageStrategy, } from "./item-controller-storage.js";
32
30
  export { MemoryItemSessionStorage, SessionStorageItemSessionStorage, } from "./item-controller-storage.js";
33
31
  export { SESSION_COMMIT_METHOD, bindPageLifecycleCommit, commitPendingSessions, flushPendingSessionNotifications, noteSessionBaseline, noteSessionObserved, } from "./session-commit.js";
@@ -35,7 +33,7 @@ export type { BindPageLifecycleCommitOptions, CommitPendingSessionsOptions, Comm
35
33
  export { createSessionSnapshot, isSessionSnapshotEnabled, isSnapshotIdentitySpecific, sessionSnapshotKey, sessionStorageSnapshotStore, } from "./session-snapshot.js";
36
34
  export type { SessionSnapshot, SessionSnapshotConfig, SessionSnapshotIdentity, SessionSnapshotRecord, SessionSnapshotStore, } from "./session-snapshot.js";
37
35
  export type { PieLogger } from "./logger.js";
38
- export { createPieLogger, isGlobalDebugEnabled } from "./logger.js";
36
+ export { createPieLogger, isGlobalDebugEnabled, isTtsDebugEnabled, } from "./logger.js";
39
37
  export { pieRegistry } from "./registry.js";
40
38
  export { findPieController, scorePieItem } from "./scoring.js";
41
39
  export type { Entry, EventListeners, EventListenersMap, LoadPieElementsOptions, PieElement, PieRegistry, UpdatePieElementOptions, } from "./types.js";
package/dist/pie/index.js CHANGED
@@ -23,20 +23,17 @@ export { createAuthoringAssetEventManager, initializeAuthoringConfigures, resolv
23
23
  export { initializeMathRendering, setMathRenderer, } from "./math-rendering.js";
24
24
  export { attachInstrumentationEventBridge } from "./instrumentation-event-bridge.js";
25
25
  export { resolveInstrumentationProvider } from "./instrumentation-provider-resolution.js";
26
- // Canonical stage vocabulary (M6). Both `<pie-section-player-*>` and
27
- // `<pie-assessment-toolkit>` share this primitive so the
28
- // `pie-stage-change` event family stays coherent across CE shapes.
29
- export { STAGES, applicableStages, stageOrdinal, } from "./stages.js";
30
- export { createStageTracker } from "./stage-tracker.js";
26
+ // Stage vocabulary of the section player's `pie-stage-change` event.
27
+ export { STAGES } from "./stages.js";
31
28
  export { ASSESSMENT_INSTRUMENTATION_EVENT_MAP, ITEM_INSTRUMENTATION_EVENT_MAP, SECTION_INSTRUMENTATION_EVENT_MAP, TOOLKIT_INSTRUMENTATION_EVENT_MAP, } from "./instrumentation-event-map.js";
32
29
  export { ItemController, normalizeItemSessionContainer, } from "./item-controller.js";
33
- export { ensureHostSessionEntries, hasLearnerResponse, hasResponseValue, normalizeItemSessionChange, projectSessionIntoHostContainer, } from "./item-session-contract.js";
30
+ export { ensureHostSessionEntries, hasLearnerResponse, hasResponseField, hasResponseValue, normalizeItemSessionChange, projectSessionIntoHostContainer, } from "./item-session-contract.js";
34
31
  export { MemoryItemSessionStorage, SessionStorageItemSessionStorage, } from "./item-controller-storage.js";
35
32
  export { SESSION_COMMIT_METHOD, bindPageLifecycleCommit, commitPendingSessions, flushPendingSessionNotifications, noteSessionBaseline, noteSessionObserved, } from "./session-commit.js";
36
33
  // Opt-in device-local snapshot of a committed session, offered back to the host
37
34
  // after a crash rather than applied.
38
35
  export { createSessionSnapshot, isSessionSnapshotEnabled, isSnapshotIdentitySpecific, sessionSnapshotKey, sessionStorageSnapshotStore, } from "./session-snapshot.js";
39
- export { createPieLogger, isGlobalDebugEnabled } from "./logger.js";
36
+ export { createPieLogger, isGlobalDebugEnabled, isTtsDebugEnabled, } from "./logger.js";
40
37
  // Registry
41
38
  export { pieRegistry } from "./registry.js";
42
39
  // Scoring
@@ -1,8 +1,9 @@
1
1
  /**
2
2
  * PIE Initialization Module
3
3
  *
4
- * Bundle loading and element initialization logic.
5
- * This is the core of the PIE player system.
4
+ * Loads IIFE bundles into `window.pie`, defines their elements and records
5
+ * them in the PIE registry, and binds config and session to the elements in
6
+ * the container.
6
7
  */
7
8
  import type { ConfigEntity } from "../types/index.js";
8
9
  import type { LoadPieElementsOptions } from "./types.js";
@@ -26,13 +27,11 @@ import type { LoadPieElementsOptions } from "./types.js";
26
27
  * the existing `MutationObserver` / `updatePieElements` flow binds
27
28
  * them on the next reactive pass.
28
29
  *
29
- * The original blanket `window.pie not found; was the bundle inlined
30
- * correctly?` error has been removed: in the section-player + item-player
31
- * composition it produced a confusing red stack trace for a routine
32
- * timing condition that the host already handles. Genuine failures
33
- * (bundle not loaded by *anyone*) still surface — every unregistered tag
34
- * gets its own warning, and `updatePieElements` later reports any tag
35
- * that never resolves.
30
+ * A missing `window.pie` raises no error: in the section-player and
31
+ * item-player composition it is a routine timing condition the host already
32
+ * handles. A bundle nobody loaded still surfaces, as a warning per
33
+ * unregistered tag and as an `updatePieElements` report for any tag that
34
+ * never resolves.
36
35
  *
37
36
  * Binds the elements present in `opts.container` now. Elements that arrive
38
37
  * later are the container owner's concern: a player with a lifecycle calls
@@ -53,13 +52,13 @@ export declare const loadPieModule: (config: ConfigEntity, session: any[], opts?
53
52
  session: any[];
54
53
  }>;
55
54
  /**
56
- * Load a PIE bundle from a JavaScript string into window.pie (IIFE bundles only)
57
- * This only registers elements and controllers - does NOT initialize them.
58
- * For initialization, use initializePiesFromLoadedBundle after loading.
55
+ * Executes an IIFE bundle from a JavaScript string, which populates
56
+ * `window.pie`. Defines no elements: `initializePiesFromLoadedBundle`
57
+ * registers and binds them afterwards.
59
58
  */
60
59
  export declare const loadBundleFromString: (bundleJs: string) => Promise<void>;
61
60
  /**
62
- * Load a PIE bundle from a JavaScript string and initialize elements
63
- * Convenience wrapper around loadBundleFromString + loadPieModule
61
+ * Loads a PIE bundle from a JavaScript string and initializes its elements:
62
+ * `loadPieModule` over a blob URL of the source.
64
63
  */
65
64
  export declare const loadPieModuleFromString: (bundleJs: string, config: ConfigEntity, session: any[], opts?: LoadPieElementsOptions) => Promise<void>;
@@ -1,8 +1,9 @@
1
1
  /**
2
2
  * PIE Initialization Module
3
3
  *
4
- * Bundle loading and element initialization logic.
5
- * This is the core of the PIE player system.
4
+ * Loads IIFE bundles into `window.pie`, defines their elements and records
5
+ * them in the PIE registry, and binds config and session to the elements in
6
+ * the container.
6
7
  */
7
8
  import { BUILDER_BUNDLE_URL } from "../config/profile.js";
8
9
  import { DEFAULT_IIFE_BUNDLE_RETRY_CONFIG } from "../loader-config.js";
@@ -17,7 +18,6 @@ import { validateCustomElementTag } from "./tag-names.js";
17
18
  import { BundleType, isCustomElementConstructor, isPieAvailable, Status, } from "./types.js";
18
19
  import { updatePieElement } from "./updates.js";
19
20
  import { getPackageWithoutVersion, getPieElementBundlesUrl, } from "./utils.js";
20
- // Create module-level logger (respects global debug flag - pass function for dynamic checking)
21
21
  const logger = createPieLogger("pie-initialization", () => isGlobalDebugEnabled());
22
22
  /**
23
23
  * Deadline for `loadPieModule`'s bundle `<script>` load when the caller
@@ -26,7 +26,6 @@ const logger = createPieLogger("pie-initialization", () => isGlobalDebugEnabled(
26
26
  * path a host loads it through.
27
27
  */
28
28
  const DEFAULT_LOAD_TIMEOUT_MS = DEFAULT_IIFE_BUNDLE_RETRY_CONFIG.timeoutMs;
29
- // Default options for loading PIE elements
30
29
  const defaultOptions = {
31
30
  buildServiceBase: BUILDER_BUNDLE_URL,
32
31
  bundleType: BundleType.player, // Default to player.js (no controllers, server-processed models)
@@ -46,12 +45,10 @@ const updateRegisteredElement = (elementTagName, config, session, options, omitE
46
45
  });
47
46
  };
48
47
  /**
49
- * Shared element registration logic
50
- * Extracted from initializePiesFromLoadedBundle and loadPieModule to eliminate ~200 lines of duplication
51
- *
52
- * Binds the elements already present in `options.container`. Elements that
53
- * arrive later are bound by the container owner's observer — see
54
- * `element-observer.ts`.
48
+ * Defines the bundle's elements for `config.elements`, records them in the
49
+ * registry, and binds the elements already present in `options.container`.
50
+ * Elements that arrive later are bound by the container owner's observer —
51
+ * see `element-observer.ts`. Returns one promise per defined tag.
55
52
  *
56
53
  * `elementModule` may be `null`. In that case we cannot register *new*
57
54
  * tags (no element constructor source), but we can still update tags
@@ -120,59 +117,55 @@ const registerPieElementsFromBundle = (elementModule, config, session, registry,
120
117
  : [],
121
118
  bundleType: options.bundleType,
122
119
  });
123
- {
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.
127
- logger.debug(`[registerPieElementsFromBundle] Registering ${elName} in registry${elementData.controller
128
- ? " with controller"
129
- : " (no controller; its model is used as given)"}`);
130
- writeRegistryEntry({
131
- package: pkg,
132
- status: Status.loading,
133
- tagName: elementTagName,
134
- controller: elementData.controller || null,
135
- config: elementData.config,
120
+ // A package without a controller, such as a legacy
121
+ // `@pie-element/protractor`, renders the model it is given under
122
+ // client-player.js too.
123
+ logger.debug(`[registerPieElementsFromBundle] Registering ${elName} in registry${elementData.controller
124
+ ? " with controller"
125
+ : " (no controller; its model is used as given)"}`);
126
+ writeRegistryEntry({
127
+ package: pkg,
128
+ status: Status.loading,
129
+ tagName: elementTagName,
130
+ controller: elementData.controller || null,
131
+ config: elementData.config,
132
+ bundleType: options.bundleType,
133
+ });
134
+ if (!isCustomElementConstructor(elementData.Element)) {
135
+ logger.error(`[registerPieElementsFromBundle] pie.Element for ${pkgStripped} is not a valid custom element constructor.`);
136
+ return;
137
+ }
138
+ defineCustomElementSafely(elementTagName, elementData.Element, `element tag in config.elements for ${String(pkg)}`);
139
+ // Initialize existing elements
140
+ const searchRoot = options.container || document;
141
+ const elements = searchRoot.querySelectorAll(elementTagName);
142
+ logger.debug(`[registerPieElementsFromBundle] Found ${elements.length} elements for tag '${elementTagName}'`);
143
+ elements.forEach((el) => {
144
+ initializePieElement(el, {
145
+ config,
146
+ session,
147
+ env: options.env,
148
+ eventListeners: options.eventListeners?.[elementTagName],
136
149
  bundleType: options.bundleType,
137
150
  });
138
- if (isCustomElementConstructor(elementData.Element)) {
139
- defineCustomElementSafely(elementTagName, elementData.Element, `element tag in config.elements for ${String(pkg)}`);
140
- // Initialize existing elements
141
- const searchRoot = options.container || document;
142
- const elements = searchRoot.querySelectorAll(elementTagName);
143
- logger.debug(`[registerPieElementsFromBundle] Found ${elements.length} elements for tag '${elementTagName}'`);
144
- elements.forEach((el) => {
145
- initializePieElement(el, {
146
- config,
147
- session,
148
- env: options.env,
149
- eventListeners: options.eventListeners?.[elementTagName],
150
- bundleType: options.bundleType,
151
- });
152
- });
153
- writeRegistryEntry({
154
- ...registry[elementTagName],
155
- status: Status.loaded,
156
- });
157
- promises.push(customElements.whenDefined(elementTagName).then(() => {
158
- logger.debug("[registerPieElementsFromBundle] defined custom PIE element: %s", elementTagName);
151
+ });
152
+ writeRegistryEntry({
153
+ ...registry[elementTagName],
154
+ status: Status.loaded,
155
+ });
156
+ promises.push(customElements.whenDefined(elementTagName).then(() => {
157
+ logger.debug("[registerPieElementsFromBundle] defined custom PIE element: %s", elementTagName);
158
+ }));
159
+ if (options.bundleType === BundleType.editor) {
160
+ if (isCustomElementConstructor(elementData.Configure)) {
161
+ const editorElName = getEditorElementTagName(elementTagName, String(pkg));
162
+ defineCustomElementSafely(editorElName, elementData.Configure, `editor element tag for ${String(pkg)}`);
163
+ promises.push(customElements.whenDefined(editorElName).then(() => {
164
+ logger.debug(`[registerPieElementsFromBundle] defined custom PIE editor element: ${editorElName}`);
159
165
  }));
160
- // Handle editor elements if needed
161
- if (options.bundleType === BundleType.editor) {
162
- if (isCustomElementConstructor(elementData.Configure)) {
163
- const editorElName = getEditorElementTagName(elementTagName, String(pkg));
164
- defineCustomElementSafely(editorElName, elementData.Configure, `editor element tag for ${String(pkg)}`);
165
- promises.push(customElements.whenDefined(editorElName).then(() => {
166
- logger.debug(`[registerPieElementsFromBundle] defined custom PIE editor element: ${editorElName}`);
167
- }));
168
- }
169
- else {
170
- logger.error(`[registerPieElementsFromBundle] pie.Configure for ${pkgStripped} is not a valid custom element constructor.`, elementData.configure);
171
- }
172
- }
173
166
  }
174
167
  else {
175
- logger.error(`[registerPieElementsFromBundle] pie.Element for ${pkgStripped} is not a valid custom element constructor.`);
168
+ logger.error(`[registerPieElementsFromBundle] pie.Configure for ${pkgStripped} is not a valid custom element constructor.`, elementData.configure);
176
169
  }
177
170
  }
178
171
  });
@@ -198,13 +191,11 @@ const registerPieElementsFromBundle = (elementModule, config, session, registry,
198
191
  * the existing `MutationObserver` / `updatePieElements` flow binds
199
192
  * them on the next reactive pass.
200
193
  *
201
- * The original blanket `window.pie not found; was the bundle inlined
202
- * correctly?` error has been removed: in the section-player + item-player
203
- * composition it produced a confusing red stack trace for a routine
204
- * timing condition that the host already handles. Genuine failures
205
- * (bundle not loaded by *anyone*) still surface — every unregistered tag
206
- * gets its own warning, and `updatePieElements` later reports any tag
207
- * that never resolves.
194
+ * A missing `window.pie` raises no error: in the section-player and
195
+ * item-player composition it is a routine timing condition the host already
196
+ * handles. A bundle nobody loaded still surfaces, as a warning per
197
+ * unregistered tag and as an `updatePieElements` report for any tag that
198
+ * never resolves.
208
199
  *
209
200
  * Binds the elements present in `opts.container` now. Elements that arrive
210
201
  * later are the container owner's concern: a player with a lifecycle calls
@@ -282,9 +273,7 @@ export const loadPieModule = async (config, session, opts = {}) => {
282
273
  logger.debug("[loadPieModule] window.pie available");
283
274
  const elementModule = window.pie.default;
284
275
  try {
285
- // Use shared registration logic (returns array of promises)
286
276
  const registrationPromises = registerPieElementsFromBundle(elementModule, config, session, registry, options);
287
- // Wait for all element definitions to complete
288
277
  Promise.all(registrationPromises).then(succeed, fail);
289
278
  }
290
279
  catch (error) {
@@ -321,18 +310,16 @@ export const loadPieModule = async (config, session, opts = {}) => {
321
310
  return { session };
322
311
  };
323
312
  /**
324
- * Load a PIE bundle from a JavaScript string into window.pie (IIFE bundles only)
325
- * This only registers elements and controllers - does NOT initialize them.
326
- * For initialization, use initializePiesFromLoadedBundle after loading.
313
+ * Executes an IIFE bundle from a JavaScript string, which populates
314
+ * `window.pie`. Defines no elements: `initializePiesFromLoadedBundle`
315
+ * registers and binds them afterwards.
327
316
  */
328
317
  export const loadBundleFromString = async (bundleJs) => {
329
318
  await initializeMathRendering();
330
319
  await withBlobBundleUrl(bundleJs, { stripSourceMapComment: true }, async (bundleUrl) => {
331
- // Create a script tag to execute the bundle
332
320
  const script = document.createElement("script");
333
321
  script.src = bundleUrl;
334
322
  script.type = "text/javascript"; // IIFE bundles are standard JS
335
- // Wait for script to load
336
323
  await new Promise((resolve, reject) => {
337
324
  script.onload = () => resolve();
338
325
  script.onerror = () => reject(new Error("Failed to load bundle"));
@@ -342,12 +329,11 @@ export const loadBundleFromString = async (bundleJs) => {
342
329
  });
343
330
  };
344
331
  /**
345
- * Load a PIE bundle from a JavaScript string and initialize elements
346
- * Convenience wrapper around loadBundleFromString + loadPieModule
332
+ * Loads a PIE bundle from a JavaScript string and initializes its elements:
333
+ * `loadPieModule` over a blob URL of the source.
347
334
  */
348
335
  export const loadPieModuleFromString = async (bundleJs, config, session, opts = {}) => {
349
336
  await withBlobBundleUrl(bundleJs, {}, async (bundleUrl) => {
350
- // Use existing loadPieModule with the blob URL
351
337
  await loadPieModule(config, session, { ...opts, bundleUrl });
352
338
  });
353
339
  };
@@ -1,9 +1,31 @@
1
1
  import { isInstrumentationProvider } from "../instrumentation/provider-guards.js";
2
2
  import { createPieLogger, isGlobalDebugEnabled } from "./logger.js";
3
3
  const logger = createPieLogger("instrumentation-event-bridge", () => isGlobalDebugEnabled());
4
+ /**
5
+ * Telemetry attributes are data. A detail's live objects (a coordinator on
6
+ * `toolkit-ready`, an `Error` cause, a function) stay on the event for its
7
+ * listeners and are left out of the attributes; primitives, arrays and plain
8
+ * objects pass.
9
+ */
10
+ function isAttributeValue(value) {
11
+ if (value === null)
12
+ return true;
13
+ const type = typeof value;
14
+ if (type === "function" || type === "symbol")
15
+ return false;
16
+ if (type !== "object" || Array.isArray(value))
17
+ return true;
18
+ const prototype = Object.getPrototypeOf(value);
19
+ return prototype === Object.prototype || prototype === null;
20
+ }
4
21
  function normalizeEventDetail(detail) {
5
22
  if (detail && typeof detail === "object") {
6
- return detail;
23
+ const attributes = {};
24
+ for (const [key, value] of Object.entries(detail)) {
25
+ if (isAttributeValue(value))
26
+ attributes[key] = value;
27
+ }
28
+ return attributes;
7
29
  }
8
30
  if (typeof detail === "undefined") {
9
31
  return {};