@pie-players/pie-players-shared 0.3.75 → 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.
- package/README.md +88 -77
- package/dist/components/vendor/nds/README.md +4 -5
- package/dist/config/profile.d.ts +1 -1
- package/dist/config/profile.js +1 -1
- package/dist/i18n/index.d.ts +1 -1
- package/dist/i18n/index.js +1 -1
- package/dist/i18n/language-tags.d.ts +9 -1
- package/dist/i18n/language-tags.js +11 -1
- package/dist/i18n/messages/en-US.d.ts +33 -5
- package/dist/i18n/messages/en-US.js +33 -5
- package/dist/i18n/messages/nl-NL.d.ts +33 -5
- package/dist/i18n/messages/nl-NL.js +33 -5
- package/dist/i18n/provider.d.ts +14 -7
- package/dist/i18n/provider.js +26 -29
- package/dist/index.js +0 -1
- package/dist/instrumentation/providers/BaseInstrumentationProvider.js +0 -14
- package/dist/loader-config.d.ts +0 -2
- package/dist/loader-config.js +0 -2
- package/dist/loaders/element-loader.js +13 -6
- package/dist/loaders/esm-adapter.js +48 -37
- package/dist/loaders/iife-adapter.d.ts +3 -3
- package/dist/loaders/iife-adapter.js +36 -35
- package/dist/media/index.d.ts +4 -6
- package/dist/media/index.js +6 -8
- package/dist/object/index.d.ts +8 -3
- package/dist/object/index.js +8 -6
- package/dist/pie/asset-handler.d.ts +0 -17
- package/dist/pie/asset-handler.js +1 -1
- package/dist/pie/authoring.js +5 -7
- package/dist/pie/elements-rendered.d.ts +0 -2
- package/dist/pie/elements-rendered.js +1 -1
- package/dist/pie/initialization.d.ts +13 -14
- package/dist/pie/initialization.js +61 -75
- package/dist/pie/item-session-contract.js +7 -9
- package/dist/pie/resource-monitor.d.ts +34 -32
- package/dist/pie/resource-monitor.js +152 -231
- package/dist/pie/stages.d.ts +4 -3
- package/dist/pie/stages.js +4 -3
- package/dist/pie/updates.js +5 -4
- package/dist/pie/utils.d.ts +0 -2
- package/dist/pie/utils.js +0 -2
- package/dist/security/sanitize-item-markup.d.ts +4 -4
- package/dist/security/sanitize-item-markup.js +4 -4
- package/dist/security/sanitize-svg-icon.js +2 -2
- package/dist/security/wrap-overwide-images.d.ts +1 -1
- package/dist/security/wrap-overwide-images.js +1 -1
- package/dist/timed-media/media-element-source.js +1 -2
- package/dist/types/index.d.ts +62 -52
- package/dist/types/index.js +2 -2
- package/dist/ui/attribute-coercion.d.ts +5 -0
- package/dist/ui/attribute-coercion.js +6 -4
- package/dist/ui/content-styles.d.ts +6 -10
- package/dist/ui/content-styles.js +8 -13
- package/dist/ui/first-focusable.d.ts +0 -1
- package/dist/ui/first-focusable.js +0 -1
- package/dist/ui/focus-trap.js +2 -3
- package/dist/ui/scope-css.d.ts +23 -26
- package/dist/ui/scope-css.js +23 -26
- package/package.json +3 -2
package/dist/media/index.d.ts
CHANGED
|
@@ -2,12 +2,10 @@
|
|
|
2
2
|
* Validation primitives shared by every catalog card whose payload references
|
|
3
3
|
* media.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* Two copies of a URL allow-list is one copy that gets a fix and one that does
|
|
10
|
-
* not.
|
|
5
|
+
* `sign-language` and recorded `spoken` cards both hand authored, wire-facing
|
|
6
|
+
* URLs to a media element in the learner's browser, so both take the same
|
|
7
|
+
* scheme allow-list and the same "treat as absent, never as partially valid"
|
|
8
|
+
* posture from here.
|
|
11
9
|
*
|
|
12
10
|
* Lives here, behind its own subpath, so a PIE element can validate authored
|
|
13
11
|
* media without importing the assessment toolkit, whose root entry declares
|
package/dist/media/index.js
CHANGED
|
@@ -2,12 +2,10 @@
|
|
|
2
2
|
* Validation primitives shared by every catalog card whose payload references
|
|
3
3
|
* media.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* Two copies of a URL allow-list is one copy that gets a fix and one that does
|
|
10
|
-
* not.
|
|
5
|
+
* `sign-language` and recorded `spoken` cards both hand authored, wire-facing
|
|
6
|
+
* URLs to a media element in the learner's browser, so both take the same
|
|
7
|
+
* scheme allow-list and the same "treat as absent, never as partially valid"
|
|
8
|
+
* posture from here.
|
|
11
9
|
*
|
|
12
10
|
* Lives here, behind its own subpath, so a PIE element can validate authored
|
|
13
11
|
* media without importing the assessment toolkit, whose root entry declares
|
|
@@ -21,7 +19,7 @@
|
|
|
21
19
|
* Relative and protocol-relative URLs are allowed — host content is commonly
|
|
22
20
|
* served from the same origin as the player.
|
|
23
21
|
*/
|
|
24
|
-
const
|
|
22
|
+
const URL_SCHEME_PREFIX = /^[a-z][a-z0-9+.-]*:/i;
|
|
25
23
|
const ALLOWED_SRC_SCHEMES = new Set(["http:", "https:", "data:", "blob:"]);
|
|
26
24
|
/**
|
|
27
25
|
* The only `MediaAssetRef.version` this build renders.
|
|
@@ -53,7 +51,7 @@ export function isSafeMediaSrc(raw) {
|
|
|
53
51
|
return false;
|
|
54
52
|
// Relative ("/video.mp4", "video.mp4") and protocol-relative ("//cdn/x.mp4")
|
|
55
53
|
// forms carry no scheme to check and inherit the document's.
|
|
56
|
-
if (src.startsWith("//") || !
|
|
54
|
+
if (src.startsWith("//") || !URL_SCHEME_PREFIX.test(src))
|
|
57
55
|
return true;
|
|
58
56
|
const scheme = src.slice(0, src.indexOf(":") + 1).toLowerCase();
|
|
59
57
|
return ALLOWED_SRC_SCHEMES.has(scheme);
|
package/dist/object/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
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>;
|
package/dist/object/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
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
|
-
|
|
16
|
+
class DataURLAssetHandler {
|
|
17
17
|
onComplete;
|
|
18
18
|
cancelled = false;
|
|
19
19
|
fileReader = null;
|
package/dist/pie/authoring.js
CHANGED
|
@@ -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 (!
|
|
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 (
|
|
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 (!
|
|
109
|
+
if (!isPlainRecord(errors))
|
|
112
110
|
return false;
|
|
113
|
-
if (
|
|
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
|
}
|
|
@@ -16,8 +16,6 @@
|
|
|
16
16
|
* mutation before it, is missed rather than waited for.
|
|
17
17
|
*/
|
|
18
18
|
import type { ConfigEntity } from "../types/index.js";
|
|
19
|
-
/** Longer than the 50ms render debounce legacy elements use, plus a scheduler task. */
|
|
20
|
-
export declare const ELEMENT_RENDER_QUIET_MS = 200;
|
|
21
19
|
/**
|
|
22
20
|
* Resolves once every element of `configs` in `root` has rendered, or when
|
|
23
21
|
* `signal` aborts. Never rejects.
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* mutation before it, is missed rather than waited for.
|
|
17
17
|
*/
|
|
18
18
|
/** Longer than the 50ms render debounce legacy elements use, plus a scheduler task. */
|
|
19
|
-
|
|
19
|
+
const ELEMENT_RENDER_QUIET_MS = 200;
|
|
20
20
|
function hasContent(element) {
|
|
21
21
|
if (element.firstElementChild || element.shadowRoot?.firstChild)
|
|
22
22
|
return true;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* PIE Initialization Module
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
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
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
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
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
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
|
-
*
|
|
63
|
-
*
|
|
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
|
-
*
|
|
5
|
-
*
|
|
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
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
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
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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.
|
|
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
|
-
*
|
|
202
|
-
*
|
|
203
|
-
*
|
|
204
|
-
*
|
|
205
|
-
*
|
|
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
|
-
*
|
|
325
|
-
*
|
|
326
|
-
*
|
|
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
|
-
*
|
|
346
|
-
*
|
|
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,3 +1,4 @@
|
|
|
1
|
+
import { isPlainRecord } from "../object/index.js";
|
|
1
2
|
const DEFAULT_SESSION_ID = "";
|
|
2
3
|
const METADATA_ONLY_SESSION_KEYS = new Set([
|
|
3
4
|
"complete",
|
|
@@ -146,9 +147,6 @@ export function hasResponseField(value) {
|
|
|
146
147
|
}
|
|
147
148
|
return false;
|
|
148
149
|
}
|
|
149
|
-
function isPlainObject(value) {
|
|
150
|
-
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
151
|
-
}
|
|
152
150
|
function getMetadataComponent(sessionDetail, candidate) {
|
|
153
151
|
if (typeof sessionDetail.component === "string")
|
|
154
152
|
return sessionDetail.component;
|
|
@@ -194,7 +192,7 @@ function mergeSessionEntry(existing, incoming) {
|
|
|
194
192
|
for (const [key, value] of Object.entries(incoming)) {
|
|
195
193
|
const previous = merged[key];
|
|
196
194
|
merged[key] =
|
|
197
|
-
|
|
195
|
+
isPlainRecord(previous) && isPlainRecord(value)
|
|
198
196
|
? mergeSessionEntry(previous, value)
|
|
199
197
|
: value;
|
|
200
198
|
}
|
|
@@ -334,7 +332,7 @@ function classifyItemSessionChange(args) {
|
|
|
334
332
|
};
|
|
335
333
|
}
|
|
336
334
|
function hostContainerEntries(hostContainer) {
|
|
337
|
-
if (!
|
|
335
|
+
if (!isPlainRecord(hostContainer)) {
|
|
338
336
|
return null;
|
|
339
337
|
}
|
|
340
338
|
// A frozen container is not a live view of anything, and a shared immutable
|
|
@@ -355,7 +353,7 @@ function hostContainerEntries(hostContainer) {
|
|
|
355
353
|
return null;
|
|
356
354
|
}
|
|
357
355
|
function sessionEntryId(value) {
|
|
358
|
-
if (!
|
|
356
|
+
if (!isPlainRecord(value)) {
|
|
359
357
|
return null;
|
|
360
358
|
}
|
|
361
359
|
const id = value.id;
|
|
@@ -405,7 +403,7 @@ export function ensureHostSessionEntries(hostContainer, entryIds) {
|
|
|
405
403
|
*/
|
|
406
404
|
export function projectSessionIntoHostContainer(hostContainer, liveSession) {
|
|
407
405
|
const entries = hostContainerEntries(hostContainer);
|
|
408
|
-
if (!entries || !
|
|
406
|
+
if (!entries || !isPlainRecord(hostContainer)) {
|
|
409
407
|
return false;
|
|
410
408
|
}
|
|
411
409
|
const live = normalizeItemSessionContainer(liveSession);
|
|
@@ -417,11 +415,11 @@ export function projectSessionIntoHostContainer(hostContainer, liveSession) {
|
|
|
417
415
|
}
|
|
418
416
|
for (const nextEntry of live.data) {
|
|
419
417
|
const id = sessionEntryId(nextEntry);
|
|
420
|
-
if (!id || !
|
|
418
|
+
if (!id || !isPlainRecord(nextEntry)) {
|
|
421
419
|
continue;
|
|
422
420
|
}
|
|
423
421
|
const target = entries.find((entry) => sessionEntryId(entry) === id);
|
|
424
|
-
if (!
|
|
422
|
+
if (!isPlainRecord(target)) {
|
|
425
423
|
entries.push({ ...nextEntry });
|
|
426
424
|
wrote = true;
|
|
427
425
|
continue;
|