@pie-players/pie-players-shared 0.3.74 → 0.3.75
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 +3 -1
- package/dist/i18n/messages/en-US.d.ts +1 -24
- package/dist/i18n/messages/en-US.js +1 -24
- package/dist/i18n/messages/nl-NL.d.ts +1 -24
- package/dist/i18n/messages/nl-NL.js +1 -24
- package/dist/i18n/types.d.ts +4 -5
- package/dist/index.d.ts +1 -1
- package/dist/loaders/esm-adapter.d.ts +11 -0
- package/dist/loaders/esm-adapter.js +26 -8
- package/dist/loaders/index.d.ts +4 -2
- package/dist/loaders/index.js +2 -1
- package/dist/loaders/mathjax-events.d.ts +22 -0
- package/dist/loaders/mathjax-events.js +65 -0
- package/dist/loaders/preloaded-registration.d.ts +18 -2
- package/dist/loaders/preloaded-registration.js +14 -2
- package/dist/pie/elements-rendered.d.ts +25 -0
- package/dist/pie/elements-rendered.js +96 -0
- package/dist/pie/index.d.ts +4 -6
- package/dist/pie/index.js +4 -7
- package/dist/pie/instrumentation-event-bridge.js +23 -1
- package/dist/pie/instrumentation-event-map.js +11 -18
- package/dist/pie/item-controller.js +4 -0
- package/dist/pie/logger.d.ts +6 -0
- package/dist/pie/logger.js +10 -0
- package/dist/pie/math-assets.d.ts +48 -0
- package/dist/pie/math-assets.js +81 -0
- package/dist/pie/scoring.d.ts +1 -6
- package/dist/pie/scoring.js +3 -8
- package/dist/pie/session-commit.js +1 -1
- package/dist/pie/stages.d.ts +10 -47
- package/dist/pie/stages.js +10 -47
- package/dist/security/sanitize-item-markup.d.ts +4 -0
- package/dist/security/sanitize-item-markup.js +48 -2
- package/dist/security/wrap-overwide-images.js +19 -0
- package/dist/security/wrap-overwide.d.ts +8 -0
- package/dist/security/wrap-overwide.js +47 -3
- package/dist/types/index.d.ts +37 -112
- package/package.json +4 -3
- package/dist/loaders/mathjax-version-conflict.d.ts +0 -17
- package/dist/loaders/mathjax-version-conflict.js +0 -51
- package/dist/pie/stage-tracker.d.ts +0 -50
- package/dist/pie/stage-tracker.js +0 -105
package/README.md
CHANGED
|
@@ -128,7 +128,9 @@ jsDelivr is the default npm CDN provider. Hosts can opt into `esm.sh` with
|
|
|
128
128
|
Provider names are open-ended: custom/internal providers can pass their own name
|
|
129
129
|
when they follow the jsDelivr-compatible package-file URL layout, or pass a
|
|
130
130
|
provider object when package artifacts and shared dependencies need different
|
|
131
|
-
route builders.
|
|
131
|
+
route builders. Under `esm` the item player's own MathJax takes the provider's
|
|
132
|
+
package-file root as its asset root when the page sets none; see
|
|
133
|
+
[MathJax assets](../../docs/item-player/loading-strategies.md#mathjax-assets).
|
|
132
134
|
|
|
133
135
|
`dependencies` and `peerDependencies` are not used as fallback runtime contracts.
|
|
134
136
|
If multiple elements request different minor or patch versions of a shared
|
|
@@ -393,30 +393,6 @@ declare const enUS: {
|
|
|
393
393
|
textToSpeech: {
|
|
394
394
|
name: string;
|
|
395
395
|
description: string;
|
|
396
|
-
toolA11y: string;
|
|
397
|
-
title: string;
|
|
398
|
-
initializing: string;
|
|
399
|
-
initFailed: string;
|
|
400
|
-
selectText: string;
|
|
401
|
-
speed: string;
|
|
402
|
-
play: string;
|
|
403
|
-
pause: string;
|
|
404
|
-
resume: string;
|
|
405
|
-
stop: string;
|
|
406
|
-
speaking: string;
|
|
407
|
-
paused: string;
|
|
408
|
-
charactersSelected: {
|
|
409
|
-
one: string;
|
|
410
|
-
other: string;
|
|
411
|
-
};
|
|
412
|
-
rate: {
|
|
413
|
-
slow: string;
|
|
414
|
-
slower: string;
|
|
415
|
-
normal: string;
|
|
416
|
-
faster: string;
|
|
417
|
-
fast: string;
|
|
418
|
-
veryFast: string;
|
|
419
|
-
};
|
|
420
396
|
/** Inline reading controls and their live-region announcements. */
|
|
421
397
|
inline: {
|
|
422
398
|
controlsA11y: string;
|
|
@@ -538,6 +514,7 @@ declare const enUS: {
|
|
|
538
514
|
resolvedTools: string;
|
|
539
515
|
provenanceSummary: string;
|
|
540
516
|
perToolDecisions: string;
|
|
517
|
+
policyDiagnostics: string;
|
|
541
518
|
profileReadOnly: string;
|
|
542
519
|
};
|
|
543
520
|
/** TTS settings panel. */
|
|
@@ -407,30 +407,6 @@ const enUS = {
|
|
|
407
407
|
textToSpeech: {
|
|
408
408
|
name: "Text to Speech",
|
|
409
409
|
description: "Read content aloud",
|
|
410
|
-
toolA11y: "Text-to-speech tool",
|
|
411
|
-
title: "Text-to-Speech",
|
|
412
|
-
initializing: "Initializing…",
|
|
413
|
-
initFailed: "Failed to initialize text-to-speech.",
|
|
414
|
-
selectText: "Select text on the page to read it aloud.",
|
|
415
|
-
speed: "Speed:",
|
|
416
|
-
play: "Play",
|
|
417
|
-
pause: "Pause",
|
|
418
|
-
resume: "Resume",
|
|
419
|
-
stop: "Stop",
|
|
420
|
-
speaking: "Speaking…",
|
|
421
|
-
paused: "Paused",
|
|
422
|
-
charactersSelected: {
|
|
423
|
-
one: "{count} character selected",
|
|
424
|
-
other: "{count} characters selected",
|
|
425
|
-
},
|
|
426
|
-
rate: {
|
|
427
|
-
slow: "Slow",
|
|
428
|
-
slower: "Slower",
|
|
429
|
-
normal: "Normal",
|
|
430
|
-
faster: "Faster",
|
|
431
|
-
fast: "Fast",
|
|
432
|
-
veryFast: "Very Fast",
|
|
433
|
-
},
|
|
434
410
|
/** Inline reading controls and their live-region announcements. */
|
|
435
411
|
inline: {
|
|
436
412
|
controlsA11y: "Reading controls",
|
|
@@ -552,6 +528,7 @@ const enUS = {
|
|
|
552
528
|
resolvedTools: "Resolved Tools (toolkit)",
|
|
553
529
|
provenanceSummary: "Tool Policy Provenance Summary",
|
|
554
530
|
perToolDecisions: "Per-Tool Decisions",
|
|
531
|
+
policyDiagnostics: "Policy Diagnostics",
|
|
555
532
|
profileReadOnly: "PNP Profile (read-only)",
|
|
556
533
|
},
|
|
557
534
|
/** TTS settings panel. */
|
|
@@ -321,30 +321,6 @@ declare const nlNL: {
|
|
|
321
321
|
textToSpeech: {
|
|
322
322
|
name: string;
|
|
323
323
|
description: string;
|
|
324
|
-
toolA11y: string;
|
|
325
|
-
title: string;
|
|
326
|
-
initializing: string;
|
|
327
|
-
initFailed: string;
|
|
328
|
-
selectText: string;
|
|
329
|
-
speed: string;
|
|
330
|
-
play: string;
|
|
331
|
-
pause: string;
|
|
332
|
-
resume: string;
|
|
333
|
-
stop: string;
|
|
334
|
-
speaking: string;
|
|
335
|
-
paused: string;
|
|
336
|
-
charactersSelected: {
|
|
337
|
-
one: string;
|
|
338
|
-
other: string;
|
|
339
|
-
};
|
|
340
|
-
rate: {
|
|
341
|
-
slow: string;
|
|
342
|
-
slower: string;
|
|
343
|
-
normal: string;
|
|
344
|
-
faster: string;
|
|
345
|
-
fast: string;
|
|
346
|
-
veryFast: string;
|
|
347
|
-
};
|
|
348
324
|
inline: {
|
|
349
325
|
controlsA11y: string;
|
|
350
326
|
playbackSpeedA11y: string;
|
|
@@ -450,6 +426,7 @@ declare const nlNL: {
|
|
|
450
426
|
resolvedTools: string;
|
|
451
427
|
provenanceSummary: string;
|
|
452
428
|
perToolDecisions: string;
|
|
429
|
+
policyDiagnostics: string;
|
|
453
430
|
profileReadOnly: string;
|
|
454
431
|
};
|
|
455
432
|
tts: {
|
|
@@ -321,30 +321,6 @@ const nlNL = {
|
|
|
321
321
|
textToSpeech: {
|
|
322
322
|
name: "Voorlezen",
|
|
323
323
|
description: "Inhoud laten voorlezen",
|
|
324
|
-
toolA11y: "Voorleeshulp",
|
|
325
|
-
title: "Voorlezen",
|
|
326
|
-
initializing: "Wordt gestart…",
|
|
327
|
-
initFailed: "Voorlezen kon niet worden gestart.",
|
|
328
|
-
selectText: "Selecteer tekst op de pagina om die te laten voorlezen.",
|
|
329
|
-
speed: "Snelheid:",
|
|
330
|
-
play: "Voorlezen",
|
|
331
|
-
pause: "Pauzeren",
|
|
332
|
-
resume: "Hervatten",
|
|
333
|
-
stop: "Stoppen",
|
|
334
|
-
speaking: "Aan het voorlezen…",
|
|
335
|
-
paused: "Gepauzeerd",
|
|
336
|
-
charactersSelected: {
|
|
337
|
-
one: "{count} teken geselecteerd",
|
|
338
|
-
other: "{count} tekens geselecteerd",
|
|
339
|
-
},
|
|
340
|
-
rate: {
|
|
341
|
-
slow: "Langzaam",
|
|
342
|
-
slower: "Langzamer",
|
|
343
|
-
normal: "Normaal",
|
|
344
|
-
faster: "Sneller",
|
|
345
|
-
fast: "Snel",
|
|
346
|
-
veryFast: "Zeer snel",
|
|
347
|
-
},
|
|
348
324
|
inline: {
|
|
349
325
|
controlsA11y: "Voorleesknoppen",
|
|
350
326
|
playbackSpeedA11y: "Voorleessnelheid",
|
|
@@ -450,6 +426,7 @@ const nlNL = {
|
|
|
450
426
|
resolvedTools: "Toegekende hulpmiddelen (toolkit)",
|
|
451
427
|
provenanceSummary: "Overzicht van herkomst van het hulpmiddelenbeleid",
|
|
452
428
|
perToolDecisions: "Beslissingen per hulpmiddel",
|
|
429
|
+
policyDiagnostics: "Diagnose van het hulpmiddelenbeleid",
|
|
453
430
|
profileReadOnly: "PNP-profiel (alleen lezen)",
|
|
454
431
|
},
|
|
455
432
|
tts: {
|
package/dist/i18n/types.d.ts
CHANGED
|
@@ -52,11 +52,10 @@ type PluralCategoryKey = keyof PluralTranslation;
|
|
|
52
52
|
* Dot-notation keys of a catalog shape.
|
|
53
53
|
*
|
|
54
54
|
* A plural group terminates the path at the group itself: `plural()` is called
|
|
55
|
-
* with `
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
* category union.
|
|
55
|
+
* with `player.formative.triesLeft`, never with `…triesLeft.other`. A group is
|
|
56
|
+
* recognized by every one of its keys being a CLDR plural category — the
|
|
57
|
+
* containment has to run this way round, because a group carrying only
|
|
58
|
+
* `one`/`other` does not contain the whole category union.
|
|
60
59
|
*/
|
|
61
60
|
type NestedKeys<T> = {
|
|
62
61
|
[K in keyof T & string]: T[K] extends string ? K : keyof T[K] extends PluralCategoryKey ? K : T[K] extends object ? K | `${K}.${NestedKeys<T[K]>}` : K;
|
package/dist/index.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ export * from "./security/index.js";
|
|
|
6
6
|
export * from "./loaders/index.js";
|
|
7
7
|
export * from "./object/index.js";
|
|
8
8
|
export * from "./pie/index.js";
|
|
9
|
-
export type { AssessmentEntity, AssessmentItemRef, AssessmentSection, AdvancedItemConfig, ConfigEntity, ConfigResource, Env, FormativeDeliveryPolicy, FormativeItemPolicy, TimedMediaSectionData, ItemConfig, ItemEntity, ItemSession, OutcomeResponse, PassageEntity, PieController, PieContent, PieDefaultModel, PieItemElement, PieModel, PieUpdateSession, RubricBlock, TestPart, } from "./types/index.js";
|
|
9
|
+
export type { AssessmentEntity, AssessmentItemRef, AssessmentSection, AdvancedItemConfig, ConfigEntity, ConfigResource, Env, FormativeDeliveryPolicy, FormativeItemPolicy, TimedMediaSectionData, ItemConfig, ItemEntity, ItemSession, OutcomeResponse, PassageEntity, PieController, PieContent, PieDefaultModel, PieItemElement, PieModel, PieUpdateSession, ReferencedItemEntity, ReferencedPassageEntity, RubricBlock, TestPart, } from "./types/index.js";
|
|
10
10
|
export { editorPostFix } from "./types/index.js";
|
|
11
11
|
export * from "./ui/attribute-coercion.js";
|
|
12
12
|
export * from "./ui/content-styles.js";
|
|
@@ -73,4 +73,15 @@ export declare function resolveEsmRuntimeSupportUrl(packageVersion: string, opti
|
|
|
73
73
|
cdnBaseUrl: string;
|
|
74
74
|
cdnProvider?: EsmCdnProviderOption;
|
|
75
75
|
}): string | undefined;
|
|
76
|
+
/**
|
|
77
|
+
* The npm root the ESM CDN the players load elements from serves raw package
|
|
78
|
+
* files under, `<root>/<package>@<version>/<path>`: the base URL for jsDelivr,
|
|
79
|
+
* raw.esm.sh for esm.sh, and for a custom provider whatever its
|
|
80
|
+
* `packageJsonUrl` puts before the package. `undefined` when that URL names
|
|
81
|
+
* no such root.
|
|
82
|
+
*/
|
|
83
|
+
export declare function resolveEsmAssetRoot(options: {
|
|
84
|
+
cdnBaseUrl: string;
|
|
85
|
+
cdnProvider?: EsmCdnProviderOption;
|
|
86
|
+
}): string | undefined;
|
|
76
87
|
export declare function pickElementClass(module: any, view: string): unknown;
|
|
@@ -21,7 +21,7 @@ import { BundleType, isCustomElementConstructor, Status, } from "../pie/types.js
|
|
|
21
21
|
import { parsePackageName } from "../pie/utils.js";
|
|
22
22
|
import { AdapterFailure, } from "./element-loader-types.js";
|
|
23
23
|
import { isExactSemver } from "./element-package-policy.js";
|
|
24
|
-
import {
|
|
24
|
+
import { forwardMathjaxEvents } from "./mathjax-events.js";
|
|
25
25
|
export const BUILT_IN_VIEWS = {
|
|
26
26
|
delivery: { subpath: "", tagSuffix: "" },
|
|
27
27
|
author: { subpath: "/author", tagSuffix: "-config", fallback: "delivery" },
|
|
@@ -41,8 +41,8 @@ export function createEsmBackend(config) {
|
|
|
41
41
|
const editorRuntimeVariants = new Map();
|
|
42
42
|
/** Packages that do not publish the browser ESM exports this backend loads. */
|
|
43
43
|
const unpublishedPackages = new Map();
|
|
44
|
-
/** Windows whose MathJax
|
|
45
|
-
const
|
|
44
|
+
/** Windows whose MathJax events this backend forwards. */
|
|
45
|
+
const mathjaxEventViews = new WeakSet();
|
|
46
46
|
let sharedDependencyVersions = {};
|
|
47
47
|
let importer = defaultImporter;
|
|
48
48
|
let packageMetadataLoader = defaultPackageMetadataLoader;
|
|
@@ -82,7 +82,7 @@ export function createEsmBackend(config) {
|
|
|
82
82
|
if (moduleResolution === "import-map") {
|
|
83
83
|
assertImportMapSupported();
|
|
84
84
|
}
|
|
85
|
-
|
|
85
|
+
forwardMathjaxEventsOf(context.doc);
|
|
86
86
|
const newEntries = {};
|
|
87
87
|
for (const [tag, pkg] of Object.entries(elements)) {
|
|
88
88
|
const packageName = parsePackageName(pkg).name;
|
|
@@ -481,12 +481,12 @@ export function createEsmBackend(config) {
|
|
|
481
481
|
return undefined;
|
|
482
482
|
return provider;
|
|
483
483
|
}
|
|
484
|
-
function
|
|
484
|
+
function forwardMathjaxEventsOf(doc) {
|
|
485
485
|
const view = doc.defaultView;
|
|
486
|
-
if (!view ||
|
|
486
|
+
if (!view || mathjaxEventViews.has(view))
|
|
487
487
|
return;
|
|
488
|
-
|
|
489
|
-
|
|
488
|
+
mathjaxEventViews.add(view);
|
|
489
|
+
forwardMathjaxEvents(view, getInstrumentationProvider);
|
|
490
490
|
}
|
|
491
491
|
function reportSharedDependencyConflict(attributes, message = `[pie-esm] Shared dependency version conflict resolved for ${String(attributes.dependencyName)}`) {
|
|
492
492
|
if (typeof console !== "undefined" && console.warn) {
|
|
@@ -608,6 +608,24 @@ export function resolveEsmRuntimeSupportUrl(packageVersion, options) {
|
|
|
608
608
|
? `${packageJsonUrl.slice(0, -"package.json".length)}${RUNTIME_SUPPORT_PATH}`
|
|
609
609
|
: undefined;
|
|
610
610
|
}
|
|
611
|
+
/** A package the probe below names, so a provider URL ends in its `<package>@<version>` path. */
|
|
612
|
+
const ROOT_PROBE_PACKAGE = "@pie-element/shared-math-rendering-mathjax@0.0.0";
|
|
613
|
+
/**
|
|
614
|
+
* The npm root the ESM CDN the players load elements from serves raw package
|
|
615
|
+
* files under, `<root>/<package>@<version>/<path>`: the base URL for jsDelivr,
|
|
616
|
+
* raw.esm.sh for esm.sh, and for a custom provider whatever its
|
|
617
|
+
* `packageJsonUrl` puts before the package. `undefined` when that URL names
|
|
618
|
+
* no such root.
|
|
619
|
+
*/
|
|
620
|
+
export function resolveEsmAssetRoot(options) {
|
|
621
|
+
const cdnBaseUrl = options.cdnBaseUrl.replace(/\/+$/, "");
|
|
622
|
+
const provider = resolveCdnProvider(options.cdnProvider, cdnBaseUrl);
|
|
623
|
+
const suffix = `/${ROOT_PROBE_PACKAGE}/package.json`;
|
|
624
|
+
const packageJsonUrl = provider.packageJsonUrl(ROOT_PROBE_PACKAGE);
|
|
625
|
+
return packageJsonUrl.endsWith(suffix)
|
|
626
|
+
? packageJsonUrl.slice(0, -suffix.length) || undefined
|
|
627
|
+
: undefined;
|
|
628
|
+
}
|
|
611
629
|
function resolveCdnProvider(provider, cdnBaseUrl) {
|
|
612
630
|
if (isEsmCdnProvider(provider))
|
|
613
631
|
return provider;
|
package/dist/loaders/index.d.ts
CHANGED
|
@@ -37,12 +37,14 @@ export { assertElementPackagesAllowed, ElementPackagePolicyError, } from "./elem
|
|
|
37
37
|
export type { BackendContext, BackendOption, ElementLoaderBackend, ElementTag, EnsureRegisteredOptions, EsmBackendConfig, IifeBackendConfig, RegistrationFailureReason, } from "./element-loader.js";
|
|
38
38
|
export { AdapterFailure, ElementAssertionError, ElementLoaderError, assertRegistered, describeRegistrationFailures, ensureRegistered, } from "./element-loader.js";
|
|
39
39
|
export type { BuiltInEsmCdnProviderName, EsmBackend, EsmCdnProvider, EsmCdnProviderName, EsmCdnProviderOption, ViewConfig, } from "./esm-adapter.js";
|
|
40
|
-
export { BUILT_IN_VIEWS, createEsmBackend, mapEsmViewElements, resolveEsmRuntimeSupportUrl, } from "./esm-adapter.js";
|
|
40
|
+
export { BUILT_IN_VIEWS, createEsmBackend, mapEsmViewElements, resolveEsmAssetRoot, resolveEsmRuntimeSupportUrl, } from "./esm-adapter.js";
|
|
41
41
|
export type { IifeBackend, IifeBundleRetryStatus, } from "./iife-adapter.js";
|
|
42
|
+
export { forwardMathjaxEvents, MATHJAX_NO_ASSET_ROOT_EVENT, MATHJAX_VERSION_CONFLICT_EVENT, } from "./mathjax-events.js";
|
|
42
43
|
export { DEFAULT_BUNDLE_HOST, DEFAULT_ESM_CDN_URL } from "./defaults.js";
|
|
43
44
|
export { createIifeBackend } from "./iife-adapter.js";
|
|
44
45
|
export { resolveLoadControllers } from "./controller-loading.js";
|
|
45
|
-
export type { PreloadedController, PreloadedElement, } from "./preloaded-registration.js";
|
|
46
|
+
export type { PreloadedController, PreloadedElement, PreloadedRegistrationOptions, } from "./preloaded-registration.js";
|
|
47
|
+
export type { MathAssetOptions } from "../pie/math-assets.js";
|
|
46
48
|
export { registerPreloadedElements } from "./preloaded-registration.js";
|
|
47
49
|
export type { PreloadedElementSpecs } from "./preloaded-alignment.js";
|
|
48
50
|
export { alignPreloadedElementVersions } from "./preloaded-alignment.js";
|
package/dist/loaders/index.js
CHANGED
|
@@ -33,7 +33,8 @@
|
|
|
33
33
|
export { aggregateElements } from "./ElementLoader.js";
|
|
34
34
|
export { assertElementPackagesAllowed, ElementPackagePolicyError, } from "./element-package-policy.js";
|
|
35
35
|
export { AdapterFailure, ElementAssertionError, ElementLoaderError, assertRegistered, describeRegistrationFailures, ensureRegistered, } from "./element-loader.js";
|
|
36
|
-
export { BUILT_IN_VIEWS, createEsmBackend, mapEsmViewElements, resolveEsmRuntimeSupportUrl, } from "./esm-adapter.js";
|
|
36
|
+
export { BUILT_IN_VIEWS, createEsmBackend, mapEsmViewElements, resolveEsmAssetRoot, resolveEsmRuntimeSupportUrl, } from "./esm-adapter.js";
|
|
37
|
+
export { forwardMathjaxEvents, MATHJAX_NO_ASSET_ROOT_EVENT, MATHJAX_VERSION_CONFLICT_EVENT, } from "./mathjax-events.js";
|
|
37
38
|
export { DEFAULT_BUNDLE_HOST, DEFAULT_ESM_CDN_URL } from "./defaults.js";
|
|
38
39
|
export { createIifeBackend } from "./iife-adapter.js";
|
|
39
40
|
export { resolveLoadControllers } from "./controller-loading.js";
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Forwards the MathJax adapter's page events to instrumentation.
|
|
3
|
+
*
|
|
4
|
+
* Every copy of the MathJax 4 adapter that browser ESM elements render with
|
|
5
|
+
* dispatches these on `window`, once per condition per page:
|
|
6
|
+
* `pie-mathjax-version-conflict` when MathJax 3 runs on the same page — an
|
|
7
|
+
* unsupported page, see
|
|
8
|
+
* docs/item-player/loading-strategies.md#one-mathjax-version-per-page — and
|
|
9
|
+
* `pie-mathjax-no-asset-root` when a copy has nowhere to load MathJax's files
|
|
10
|
+
* from, see docs/item-player/loading-strategies.md#mathjax-assets. The event
|
|
11
|
+
* names are repeated here because players do not depend on the element
|
|
12
|
+
* packages.
|
|
13
|
+
*/
|
|
14
|
+
import type { InstrumentationProvider } from "../instrumentation/types.js";
|
|
15
|
+
export declare const MATHJAX_VERSION_CONFLICT_EVENT = "pie-mathjax-version-conflict";
|
|
16
|
+
export declare const MATHJAX_NO_ASSET_ROOT_EVENT = "pie-mathjax-no-asset-root";
|
|
17
|
+
/**
|
|
18
|
+
* Tracks each MathJax event on `target` with the provider `getProvider`
|
|
19
|
+
* returns when it fires, once per provider. Returns the function that stops
|
|
20
|
+
* forwarding.
|
|
21
|
+
*/
|
|
22
|
+
export declare function forwardMathjaxEvents(target: EventTarget, getProvider: () => InstrumentationProvider | undefined): () => void;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Forwards the MathJax adapter's page events to instrumentation.
|
|
3
|
+
*
|
|
4
|
+
* Every copy of the MathJax 4 adapter that browser ESM elements render with
|
|
5
|
+
* dispatches these on `window`, once per condition per page:
|
|
6
|
+
* `pie-mathjax-version-conflict` when MathJax 3 runs on the same page — an
|
|
7
|
+
* unsupported page, see
|
|
8
|
+
* docs/item-player/loading-strategies.md#one-mathjax-version-per-page — and
|
|
9
|
+
* `pie-mathjax-no-asset-root` when a copy has nowhere to load MathJax's files
|
|
10
|
+
* from, see docs/item-player/loading-strategies.md#mathjax-assets. The event
|
|
11
|
+
* names are repeated here because players do not depend on the element
|
|
12
|
+
* packages.
|
|
13
|
+
*/
|
|
14
|
+
export const MATHJAX_VERSION_CONFLICT_EVENT = "pie-mathjax-version-conflict";
|
|
15
|
+
export const MATHJAX_NO_ASSET_ROOT_EVENT = "pie-mathjax-no-asset-root";
|
|
16
|
+
/** The detail fields each event is tracked with. */
|
|
17
|
+
const TRACKED_FIELDS = {
|
|
18
|
+
[MATHJAX_VERSION_CONFLICT_EVENT]: ["condition", "docsUrl"],
|
|
19
|
+
[MATHJAX_NO_ASSET_ROOT_EVENT]: ["effect", "docsUrl"],
|
|
20
|
+
};
|
|
21
|
+
// Every player on a page listens, and each player bundle carries its own copy
|
|
22
|
+
// of this module, so the providers that tracked an event are kept on the page.
|
|
23
|
+
const TRACKED = Symbol.for("@pie-players/pie-players-shared/mathjax-version-conflict-tracked");
|
|
24
|
+
function providersThatTracked(event) {
|
|
25
|
+
const registry = globalThis;
|
|
26
|
+
registry[TRACKED] ??= new WeakMap();
|
|
27
|
+
let providers = registry[TRACKED].get(event);
|
|
28
|
+
if (!providers) {
|
|
29
|
+
providers = new WeakSet();
|
|
30
|
+
registry[TRACKED].set(event, providers);
|
|
31
|
+
}
|
|
32
|
+
return providers;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Tracks each MathJax event on `target` with the provider `getProvider`
|
|
36
|
+
* returns when it fires, once per provider. Returns the function that stops
|
|
37
|
+
* forwarding.
|
|
38
|
+
*/
|
|
39
|
+
export function forwardMathjaxEvents(target, getProvider) {
|
|
40
|
+
const forward = (event) => {
|
|
41
|
+
const provider = getProvider();
|
|
42
|
+
if (!provider)
|
|
43
|
+
return;
|
|
44
|
+
const tracked = providersThatTracked(event);
|
|
45
|
+
if (tracked.has(provider))
|
|
46
|
+
return;
|
|
47
|
+
tracked.add(provider);
|
|
48
|
+
const detail = event.detail;
|
|
49
|
+
const fields = (detail && typeof detail === "object" ? detail : {});
|
|
50
|
+
try {
|
|
51
|
+
provider.trackEvent(event.type, Object.fromEntries(TRACKED_FIELDS[event.type].map((field) => [field, fields[field]])));
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
// Swallow: instrumentation must never break rendering.
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
for (const type of Object.keys(TRACKED_FIELDS)) {
|
|
58
|
+
target.addEventListener(type, forward);
|
|
59
|
+
}
|
|
60
|
+
return () => {
|
|
61
|
+
for (const type of Object.keys(TRACKED_FIELDS)) {
|
|
62
|
+
target.removeEventListener(type, forward);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
}
|
|
@@ -15,7 +15,12 @@
|
|
|
15
15
|
* player that is not hosted runs the controller's `model()` in the browser, so
|
|
16
16
|
* it needs the `controller` an entry registers; the item player warns about
|
|
17
17
|
* each tag it renders without one.
|
|
18
|
+
*
|
|
19
|
+
* Bundled by the host, the elements' copies of the MathJax adapter find no npm
|
|
20
|
+
* root in their module URLs, so the registration also takes where they load
|
|
21
|
+
* MathJax's fonts and speech from (`options.math`): a root, or each file's URL.
|
|
18
22
|
*/
|
|
23
|
+
import { type MathAssetOptions } from "../pie/math-assets.js";
|
|
19
24
|
/** A controller module: the `model()` a player that is not hosted runs. */
|
|
20
25
|
export type PreloadedController = {
|
|
21
26
|
readonly model: (...args: any[]) => unknown;
|
|
@@ -43,6 +48,16 @@ export interface PreloadedElement {
|
|
|
43
48
|
readonly default: PreloadedController;
|
|
44
49
|
};
|
|
45
50
|
}
|
|
51
|
+
export interface PreloadedRegistrationOptions {
|
|
52
|
+
/**
|
|
53
|
+
* Where the elements' MathJax loads its fonts, its speech worker and, on the
|
|
54
|
+
* adapter's npm build, MathJax itself from, and whether math is in the tab
|
|
55
|
+
* order, written to `window['@pie-lib/math-rendering@2'].opts`. With neither
|
|
56
|
+
* an `assetRoot` nor the fonts in `assetUrls`, math renders without web fonts
|
|
57
|
+
* and speech.
|
|
58
|
+
*/
|
|
59
|
+
math?: MathAssetOptions;
|
|
60
|
+
}
|
|
46
61
|
/**
|
|
47
62
|
* Register host-bundled elements for the `preloaded` strategy.
|
|
48
63
|
*
|
|
@@ -51,6 +66,7 @@ export interface PreloadedElement {
|
|
|
51
66
|
* registry entry, which only gains a controller it lacked. Every entry is
|
|
52
67
|
* validated before any is registered. A package registers at one version per
|
|
53
68
|
* page, because the players align every authored version of a package to the
|
|
54
|
-
* registered one.
|
|
69
|
+
* registered one. `options.math` replaces the page's math asset options it
|
|
70
|
+
* sets, and keeps the others.
|
|
55
71
|
*/
|
|
56
|
-
export declare function registerPreloadedElements(elements: readonly PreloadedElement[]): void;
|
|
72
|
+
export declare function registerPreloadedElements(elements: readonly PreloadedElement[], options?: PreloadedRegistrationOptions): void;
|
|
@@ -15,8 +15,13 @@
|
|
|
15
15
|
* player that is not hosted runs the controller's `model()` in the browser, so
|
|
16
16
|
* it needs the `controller` an entry registers; the item player warns about
|
|
17
17
|
* each tag it renders without one.
|
|
18
|
+
*
|
|
19
|
+
* Bundled by the host, the elements' copies of the MathJax adapter find no npm
|
|
20
|
+
* root in their module URLs, so the registration also takes where they load
|
|
21
|
+
* MathJax's fonts and speech from (`options.math`): a root, or each file's URL.
|
|
18
22
|
*/
|
|
19
23
|
import { defineCustomElementSafely } from "../pie/custom-element-define.js";
|
|
24
|
+
import { mathAssetOptionsError, setMathAssetOptions, } from "../pie/math-assets.js";
|
|
20
25
|
import { writeRegistryEntry } from "../pie/registry.js";
|
|
21
26
|
import { BundleType, isCustomElementConstructor, Status } from "../pie/types.js";
|
|
22
27
|
import { parsePackageName } from "../pie/utils.js";
|
|
@@ -31,15 +36,22 @@ import { pickElementClass } from "./esm-adapter.js";
|
|
|
31
36
|
* registry entry, which only gains a controller it lacked. Every entry is
|
|
32
37
|
* validated before any is registered. A package registers at one version per
|
|
33
38
|
* page, because the players align every authored version of a package to the
|
|
34
|
-
* registered one.
|
|
39
|
+
* registered one. `options.math` replaces the page's math asset options it
|
|
40
|
+
* sets, and keeps the others.
|
|
35
41
|
*/
|
|
36
|
-
export function registerPreloadedElements(elements) {
|
|
42
|
+
export function registerPreloadedElements(elements, options = {}) {
|
|
37
43
|
if (typeof window === "undefined" || typeof customElements === "undefined") {
|
|
38
44
|
throw new Error("[registerPreloadedElements] requires a browser with customElements");
|
|
39
45
|
}
|
|
40
46
|
const preloaded = preloadedPackageSpecs();
|
|
41
47
|
const resolved = elements.map(resolveElement);
|
|
42
48
|
assertOneVersionPerPackage(resolved, preloaded);
|
|
49
|
+
const mathError = options.math === undefined ? undefined : mathAssetOptionsError(options.math);
|
|
50
|
+
if (mathError) {
|
|
51
|
+
throw new Error(`[registerPreloadedElements] options.math: ${mathError}`);
|
|
52
|
+
}
|
|
53
|
+
if (options.math)
|
|
54
|
+
setMathAssetOptions(options.math);
|
|
43
55
|
for (const { packageName, spec, tagName, elementClass, controller } of resolved) {
|
|
44
56
|
defineCustomElementSafely(tagName, class extends elementClass {
|
|
45
57
|
}, `preloaded element tag for ${packageName}`);
|
|
@@ -0,0 +1,25 @@
|
|
|
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
|
+
/** Longer than the 50ms render debounce legacy elements use, plus a scheduler task. */
|
|
20
|
+
export declare const ELEMENT_RENDER_QUIET_MS = 200;
|
|
21
|
+
/**
|
|
22
|
+
* Resolves once every element of `configs` in `root` has rendered, or when
|
|
23
|
+
* `signal` aborts. Never rejects.
|
|
24
|
+
*/
|
|
25
|
+
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
|
+
export 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
|
+
}
|
package/dist/pie/index.d.ts
CHANGED
|
@@ -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
|
|
22
|
-
export type { LoadingCompleteDetail, Stage, StageChangeDetail,
|
|
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";
|