@pie-players/pie-assessment-toolkit 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 +449 -541
- package/custom-elements.json +1315 -0
- package/dist/attempt/TestSession.d.ts +0 -1
- package/dist/attempt/TestSession.js +10 -5
- package/dist/components/ItemScope.custom-element.js +1 -1
- package/dist/components/ItemToolBar.custom-element.js +1 -1
- package/dist/components/PieAssessmentToolkit.custom-element.js +8 -8
- package/dist/components/SectionToolBar.custom-element.js +1 -1
- package/dist/components/chunks/{ItemToolBar-7tt7v2xp.js → ItemToolBar-3zrc4p3d.js} +4 -4
- package/dist/components/chunks/{ItemToolBar-jx38w99s.js → ItemToolBar-66hcwmfw.js} +2 -2
- package/dist/components/chunks/ItemToolBar-9gqhfxtn.js +40 -0
- package/dist/components/chunks/ItemToolBar-hzc5wyjp.js +2 -0
- package/dist/components/chunks/{ItemToolBar-97hx60z4.js → ItemToolBar-kkj69ptr.js} +1 -1
- package/dist/components/chunks/sre-engine-bj997w4b.js +1 -0
- package/dist/context/assessment-toolkit-context.d.ts +6 -0
- package/dist/index.d.ts +7 -7
- package/dist/index.js +4 -10
- package/dist/policy/core/PolicySource.d.ts +1 -1
- package/dist/policy/core/ToolPolicyEngine.d.ts +46 -7
- package/dist/policy/core/ToolPolicyEngine.js +73 -16
- package/dist/policy/core/compose-decision.d.ts +3 -3
- package/dist/policy/core/compose-decision.js +30 -30
- package/dist/policy/core/decision-types.d.ts +44 -32
- package/dist/policy/core/feature-decision.d.ts +17 -15
- package/dist/policy/core/feature-decision.js +11 -13
- package/dist/policy/core/pnp-policy-inputs.d.ts +5 -2
- package/dist/policy/core/pnp-policy-inputs.js +12 -32
- package/dist/policy/core/provenance.d.ts +1 -1
- package/dist/policy/core/tool-parameters.d.ts +11 -0
- package/dist/policy/core/tool-parameters.js +15 -0
- package/dist/policy/engine.d.ts +1 -1
- package/dist/policy/sources/PnpPolicySource.d.ts +14 -22
- package/dist/policy/sources/PnpPolicySource.js +30 -28
- package/dist/runtime/SectionControllerBinding.d.ts +6 -2
- package/dist/runtime/SectionControllerBinding.js +21 -15
- package/dist/runtime/adapter/subscriber-fanout.d.ts +0 -1
- package/dist/runtime/adapter/subscriber-fanout.js +1 -4
- package/dist/runtime/composition-emit-scheduler.d.ts +3 -3
- package/dist/runtime/composition-emit-scheduler.js +1 -1
- package/dist/runtime/content-region.d.ts +0 -4
- package/dist/runtime/content-region.js +2 -2
- package/dist/runtime/core/SectionEngineCore.d.ts +0 -2
- package/dist/runtime/core/SectionEngineCore.js +0 -4
- package/dist/runtime/core/cohort.d.ts +0 -5
- package/dist/runtime/core/cohort.js +0 -9
- package/dist/runtime/core/engine-output.d.ts +3 -2
- package/dist/runtime/core/engine-transition.js +3 -2
- package/dist/runtime/registration-events.d.ts +11 -14
- package/dist/runtime/runtime-id.d.ts +1 -1
- package/dist/runtime/runtime-id.js +1 -1
- package/dist/runtime/shell-scope.d.ts +4 -4
- package/dist/runtime/shell-scope.js +15 -9
- package/dist/runtime/tool-host-contract.d.ts +0 -4
- package/dist/runtime/tool-host-contract.js +1 -1
- package/dist/services/AccessibilityCatalogResolver.d.ts +21 -31
- package/dist/services/AccessibilityCatalogResolver.js +29 -42
- package/dist/services/ElementToolStateStore.d.ts +32 -61
- package/dist/services/ElementToolStateStore.js +37 -67
- package/dist/services/HighlightCoordinator.d.ts +9 -21
- package/dist/services/HighlightCoordinator.js +52 -80
- package/dist/services/RangeSerializer.d.ts +4 -35
- package/dist/services/RangeSerializer.js +4 -48
- package/dist/services/SSMLExtractor.d.ts +8 -31
- package/dist/services/SSMLExtractor.js +18 -40
- package/dist/services/TTSService.d.ts +49 -56
- package/dist/services/TTSService.js +308 -283
- package/dist/services/ToolCoordinator.d.ts +6 -49
- package/dist/services/ToolCoordinator.js +25 -61
- package/dist/services/ToolRegistry.d.ts +82 -80
- package/dist/services/ToolRegistry.js +91 -67
- package/dist/services/ToolkitCoordinator.d.ts +83 -81
- package/dist/services/ToolkitCoordinator.js +186 -174
- package/dist/services/framework-error.d.ts +30 -7
- package/dist/services/framework-error.js +10 -13
- package/dist/services/interfaces.d.ts +47 -71
- package/dist/services/section-controller-types.d.ts +7 -7
- package/dist/services/section-session-assignment.js +6 -8
- package/dist/services/spoken-audio-cards.d.ts +2 -4
- package/dist/services/spoken-audio-cards.js +2 -4
- package/dist/services/tool-config-validation.d.ts +5 -7
- package/dist/services/tool-config-validation.js +11 -26
- package/dist/services/tool-context.d.ts +7 -23
- package/dist/services/tool-context.js +99 -53
- package/dist/services/tool-failure.d.ts +30 -0
- package/dist/services/tool-failure.js +11 -0
- package/dist/services/tool-icons.d.ts +1 -2
- package/dist/services/tool-icons.js +2 -2
- package/dist/services/tool-instance-id.d.ts +8 -6
- package/dist/services/tool-instance-id.js +12 -18
- package/dist/services/tool-providers/TTSToolProvider.d.ts +6 -43
- package/dist/services/tool-providers/TTSToolProvider.js +7 -63
- package/dist/services/tool-providers/ToolProviderApi.d.ts +0 -7
- package/dist/services/tool-providers/ToolProviderRegistry.d.ts +7 -42
- package/dist/services/tool-providers/ToolProviderRegistry.js +21 -63
- package/dist/services/tool-request.d.ts +4 -0
- package/dist/services/tool-request.js +7 -2
- package/dist/services/toolbar-decided-tools.d.ts +1 -0
- package/dist/services/toolbar-decided-tools.js +1 -0
- package/dist/services/toolbar-items.js +2 -5
- package/dist/services/tools-config-normalizer.d.ts +18 -9
- package/dist/services/tools-config-normalizer.js +25 -19
- package/dist/services/tts/browser-provider.d.ts +2 -5
- package/dist/services/tts/browser-provider.js +4 -20
- package/dist/services/tts/catalog-span-alignment.js +14 -7
- package/dist/services/tts/flat-tree.d.ts +0 -7
- package/dist/services/tts/flat-tree.js +2 -2
- package/dist/services/tts/generated-speech/dom/to-chunk-inputs.d.ts +2 -2
- package/dist/services/tts/generated-speech/index.d.ts +1 -1
- package/dist/services/tts/generated-speech/index.js +1 -1
- package/dist/services/tts/generated-speech/types.d.ts +1 -1
- package/dist/services/tts/generated-speech/types.js +1 -1
- package/dist/services/tts/highlight-pipeline/create-highlight-plan.js +10 -12
- package/dist/services/tts/highlight-target-resolver.d.ts +12 -0
- package/dist/services/tts/math-aware-text-processing.js +10 -29
- package/dist/services/tts/math-speech.d.ts +1 -1
- package/dist/services/tts/math-speech.js +3 -0
- package/dist/services/tts/mathml-sanitization.d.ts +7 -0
- package/dist/services/tts/mathml-sanitization.js +9 -1
- package/dist/services/tts/provider-options.d.ts +12 -1
- package/dist/services/tts/ssml/spoken-text.js +1 -1
- package/dist/services/tts/start-failure.d.ts +0 -7
- package/dist/services/tts/start-failure.js +1 -1
- package/dist/services/tts/text-segmentation.d.ts +5 -0
- package/dist/services/tts/text-segmentation.js +13 -10
- package/dist/services/tts-runtime-config.d.ts +32 -12
- package/dist/services/tts-runtime-config.js +14 -20
- package/dist/tools/content-capability-resolution.d.ts +2 -2
- package/dist/tools/registration.d.ts +6 -5
- package/dist/tools/registration.js +10 -9
- package/dist/tools/registrations/toolbar-registration-helpers.d.ts +6 -0
- package/dist/tools/registrations/toolbar-registration-helpers.js +6 -0
- package/dist/tools/tool-surface-host.js +1 -8
- package/package.json +8 -6
- package/dist/components/chunks/ItemToolBar-bxwyhr5p.js +0 -2
- package/dist/components/chunks/ItemToolBar-rd2bb4p6.js +0 -40
- package/dist/components/chunks/sre-engine-jd8cc08x.js +0 -1
- package/dist/services/ContextVariableStore.d.ts +0 -113
- package/dist/services/ContextVariableStore.js +0 -206
- package/dist/services/tts-control-events.d.ts +0 -13
- package/dist/services/tts-control-events.js +0 -7
- package/dist/utils/logger.d.ts +0 -6
- package/dist/utils/logger.js +0 -10
|
@@ -25,7 +25,6 @@ import type { ToolkitCoordinator } from "../services/ToolkitCoordinator.js";
|
|
|
25
25
|
import type { SectionControllerEvent, SectionControllerHandle, SectionControllerSessionState } from "../services/section-controller-types.js";
|
|
26
26
|
import type { MediaTimeSource } from "@pie-players/pie-players-shared/timed-media";
|
|
27
27
|
import type { RuntimeRegistrationDetail } from "./registration-events.js";
|
|
28
|
-
import { RuntimeRegistry } from "./RuntimeRegistry.js";
|
|
29
28
|
/**
|
|
30
29
|
* Structural view of the resolved section controller, widened with the
|
|
31
30
|
* extra methods the toolkit's runtime CE calls directly. Optional so
|
|
@@ -273,7 +272,12 @@ export declare class SectionControllerBinding {
|
|
|
273
272
|
requestMediaPauseForCompetingAudio(): boolean;
|
|
274
273
|
persist(): Promise<void>;
|
|
275
274
|
hydrate(): Promise<void>;
|
|
276
|
-
|
|
275
|
+
/**
|
|
276
|
+
* Releases the binding and disposes the controller of every section it
|
|
277
|
+
* bound, so a coordinator the host passes or an outer toolkit lends keeps
|
|
278
|
+
* none of this toolkit's sections, and a later mount of one restores it from
|
|
279
|
+
* its session. The coordinator itself stays with its owner.
|
|
280
|
+
*/
|
|
277
281
|
dispose(): Promise<void>;
|
|
278
282
|
}
|
|
279
283
|
export {};
|
|
@@ -43,6 +43,11 @@ function readRenderableIds(model) {
|
|
|
43
43
|
}
|
|
44
44
|
return ids;
|
|
45
45
|
}
|
|
46
|
+
/** Whether `ids` holds the shell's runtime or canonical id. */
|
|
47
|
+
function rendersShell(ids, shell) {
|
|
48
|
+
return (ids.has(shell.itemId) ||
|
|
49
|
+
(!!shell.canonicalItemId && ids.has(shell.canonicalItemId)));
|
|
50
|
+
}
|
|
46
51
|
export class SectionControllerBinding {
|
|
47
52
|
registry = new RuntimeRegistry();
|
|
48
53
|
controller = null;
|
|
@@ -213,10 +218,7 @@ export class SectionControllerBinding {
|
|
|
213
218
|
*/
|
|
214
219
|
isSectionRenderable(args) {
|
|
215
220
|
const ids = this.sectionRenderableIds;
|
|
216
|
-
|
|
217
|
-
return true;
|
|
218
|
-
return (ids.has(args.itemId) ||
|
|
219
|
-
(!!args.canonicalItemId && ids.has(args.canonicalItemId)));
|
|
221
|
+
return !ids || rendersShell(ids, args);
|
|
220
222
|
}
|
|
221
223
|
getSectionKey(sectionId, attemptId) {
|
|
222
224
|
return `${sectionId}\u0000${attemptId ?? ""}`;
|
|
@@ -236,10 +238,8 @@ export class SectionControllerBinding {
|
|
|
236
238
|
for (const [key, section] of sections) {
|
|
237
239
|
if (key === current || !section.renderableIds)
|
|
238
240
|
continue;
|
|
239
|
-
if (!section.renderableIds
|
|
240
|
-
!(args.canonicalItemId && section.renderableIds.has(args.canonicalItemId))) {
|
|
241
|
+
if (!rendersShell(section.renderableIds, args))
|
|
241
242
|
continue;
|
|
242
|
-
}
|
|
243
243
|
const owner = this.coordinator?.getSectionController({
|
|
244
244
|
sectionId: section.sectionId,
|
|
245
245
|
attemptId: section.attemptId,
|
|
@@ -427,18 +427,24 @@ export class SectionControllerBinding {
|
|
|
427
427
|
async hydrate() {
|
|
428
428
|
await this.controller?.hydrate?.();
|
|
429
429
|
}
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
430
|
+
/**
|
|
431
|
+
* Releases the binding and disposes the controller of every section it
|
|
432
|
+
* bound, so a coordinator the host passes or an outer toolkit lends keeps
|
|
433
|
+
* none of this toolkit's sections, and a later mount of one restores it from
|
|
434
|
+
* its session. The coordinator itself stays with its owner.
|
|
435
|
+
*/
|
|
433
436
|
async dispose() {
|
|
434
437
|
this.activeInitToken += 1;
|
|
435
438
|
this.unsubscribeController?.();
|
|
436
439
|
this.unsubscribeController = null;
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
440
|
+
const coordinator = this.coordinator;
|
|
441
|
+
if (coordinator) {
|
|
442
|
+
for (const section of this.boundSections.values()) {
|
|
443
|
+
await coordinator.disposeSectionController({
|
|
444
|
+
sectionId: section.sectionId,
|
|
445
|
+
attemptId: section.attemptId,
|
|
446
|
+
});
|
|
447
|
+
}
|
|
442
448
|
}
|
|
443
449
|
this.registry.clear();
|
|
444
450
|
this.loadedRenderableKeys.clear();
|
|
@@ -28,6 +28,5 @@ export interface SubscriberFanoutHandle {
|
|
|
28
28
|
emit(outputs: readonly SectionEngineOutput[]): void;
|
|
29
29
|
subscribe(listener: EngineOutputListener): () => void;
|
|
30
30
|
dispose(): void;
|
|
31
|
-
getListenerCount(): number;
|
|
32
31
|
}
|
|
33
32
|
export declare function createSubscriberFanout(): SubscriberFanoutHandle;
|
|
@@ -51,8 +51,5 @@ export function createSubscriberFanout() {
|
|
|
51
51
|
function dispose() {
|
|
52
52
|
listeners.clear();
|
|
53
53
|
}
|
|
54
|
-
|
|
55
|
-
return listeners.size;
|
|
56
|
-
}
|
|
57
|
-
return { emit, subscribe, dispose, getListenerCount };
|
|
54
|
+
return { emit, subscribe, dispose };
|
|
58
55
|
}
|
|
@@ -44,9 +44,9 @@ export declare const DEFAULT_FRAME_DEADLINE_MS = 100;
|
|
|
44
44
|
* Timing primitives, injectable so tests can drive the race deterministically.
|
|
45
45
|
*
|
|
46
46
|
* An omitted primitive is resolved from `globalThis` at `schedule()` time
|
|
47
|
-
* rather than captured at construction
|
|
48
|
-
*
|
|
49
|
-
*
|
|
47
|
+
* rather than captured at construction, because a host can install a
|
|
48
|
+
* `requestAnimationFrame` shim after the toolkit is constructed. An explicit
|
|
49
|
+
* `null` opts the primitive out.
|
|
50
50
|
*/
|
|
51
51
|
export interface CompositionEmitSchedulerTiming {
|
|
52
52
|
requestFrame?: ((callback: () => void) => number) | null;
|
|
@@ -77,7 +77,7 @@ export function createCompositionEmitScheduler(timing = {}) {
|
|
|
77
77
|
*
|
|
78
78
|
* The latch is released before the flush callback runs, so a re-entrant
|
|
79
79
|
* `schedule()` from inside the flush arms a fresh cycle instead of being
|
|
80
|
-
* swallowed.
|
|
80
|
+
* swallowed.
|
|
81
81
|
*/
|
|
82
82
|
function resolve(firedFrom) {
|
|
83
83
|
if (!pending)
|
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
/** Marks the part of a shell scope that holds the content tools read and annotate. */
|
|
2
|
-
export declare const CONTENT_REGION_SELECTOR = "[data-region='content']";
|
|
3
|
-
/** Marks the host of a shell scope (`<pie-item-scope>`, `<pie-passage-shell>`). */
|
|
4
|
-
export declare const SHELL_SCOPE_HOST_SELECTOR = "[data-pie-shell-root]";
|
|
5
1
|
/**
|
|
6
2
|
* The content region of `scope` that tools read and annotate.
|
|
7
3
|
*
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { composedClosest, flatQuerySelector, } from "../services/tts/flat-tree.js";
|
|
2
2
|
/** Marks the part of a shell scope that holds the content tools read and annotate. */
|
|
3
|
-
|
|
3
|
+
const CONTENT_REGION_SELECTOR = "[data-region='content']";
|
|
4
4
|
/** Marks the host of a shell scope (`<pie-item-scope>`, `<pie-passage-shell>`). */
|
|
5
|
-
|
|
5
|
+
const SHELL_SCOPE_HOST_SELECTOR = "[data-pie-shell-root]";
|
|
6
6
|
/**
|
|
7
7
|
* The content region of `scope` that tools read and annotate.
|
|
8
8
|
*
|
|
@@ -45,7 +45,5 @@ export declare class SectionEngineCore {
|
|
|
45
45
|
* construction time if you need every output from `idle` onward.
|
|
46
46
|
*/
|
|
47
47
|
subscribe(listener: SectionEngineCoreListener): () => void;
|
|
48
|
-
/** Test-only: detach every listener. */
|
|
49
|
-
disposeListeners(): void;
|
|
50
48
|
private fanOut;
|
|
51
49
|
}
|
|
@@ -61,10 +61,6 @@ export class SectionEngineCore {
|
|
|
61
61
|
this.listeners.delete(listener);
|
|
62
62
|
};
|
|
63
63
|
}
|
|
64
|
-
/** Test-only: detach every listener. */
|
|
65
|
-
disposeListeners() {
|
|
66
|
-
this.listeners.clear();
|
|
67
|
-
}
|
|
68
64
|
fanOut(outputs) {
|
|
69
65
|
const snapshot = Array.from(this.listeners);
|
|
70
66
|
const frozen = Object.freeze(outputs.slice());
|
|
@@ -17,11 +17,6 @@ export interface CohortKey {
|
|
|
17
17
|
/** Attempt identifier; empty string if the host did not provide one. */
|
|
18
18
|
attemptId: string;
|
|
19
19
|
}
|
|
20
|
-
/**
|
|
21
|
-
* Stable string form of the cohort tuple used as a key for the
|
|
22
|
-
* `loadingCompleteEmittedForCohort` latch and equality checks.
|
|
23
|
-
*/
|
|
24
|
-
export declare function cohortKey(cohort: CohortKey | null): string;
|
|
25
20
|
/**
|
|
26
21
|
* `true` iff the two cohorts have the same `(sectionId, attemptId)` pair.
|
|
27
22
|
* Treats both inputs as identifying the cohort by value, never by
|
|
@@ -11,15 +11,6 @@
|
|
|
11
11
|
* layout-CE prop shape) but the engine carries it as the empty string
|
|
12
12
|
* when absent so the cohort key is total.
|
|
13
13
|
*/
|
|
14
|
-
/**
|
|
15
|
-
* Stable string form of the cohort tuple used as a key for the
|
|
16
|
-
* `loadingCompleteEmittedForCohort` latch and equality checks.
|
|
17
|
-
*/
|
|
18
|
-
export function cohortKey(cohort) {
|
|
19
|
-
if (!cohort)
|
|
20
|
-
return "";
|
|
21
|
-
return `${cohort.sectionId}|${cohort.attemptId}`;
|
|
22
|
-
}
|
|
23
14
|
/**
|
|
24
15
|
* `true` iff the two cohorts have the same `(sectionId, attemptId)` pair.
|
|
25
16
|
* Treats both inputs as identifying the cohort by value, never by
|
|
@@ -26,8 +26,9 @@ export type EngineOutputStageChange = {
|
|
|
26
26
|
};
|
|
27
27
|
/**
|
|
28
28
|
* Canonical `pie-loading-complete`. One-shot per cohort, emitted when the
|
|
29
|
-
* readiness signals satisfy `allLoadingComplete
|
|
30
|
-
*
|
|
29
|
+
* readiness signals satisfy `allLoadingComplete`: section ready plus the host's
|
|
30
|
+
* loading signal, which the section player kernel raises once the element
|
|
31
|
+
* pre-warm resolves. `itemCount` is the composition's item count.
|
|
31
32
|
*/
|
|
32
33
|
export type EngineOutputLoadingComplete = {
|
|
33
34
|
kind: "loading-complete";
|
|
@@ -111,8 +111,9 @@ function applyReadinessUpdate(state, args) {
|
|
|
111
111
|
emitStageChange(outputs, phase, state.cohort);
|
|
112
112
|
}
|
|
113
113
|
// Canonical `loading-complete` emission. One-shot per cohort, gated
|
|
114
|
-
// on
|
|
115
|
-
//
|
|
114
|
+
// on `allLoadingComplete`: section ready plus the host's loading signal,
|
|
115
|
+
// which the section player kernel raises once the element pre-warm
|
|
116
|
+
// resolves.
|
|
116
117
|
if (detail.allLoadingComplete && state.cohort && !loadingCompleteEmitted) {
|
|
117
118
|
loadingCompleteEmitted = true;
|
|
118
119
|
outputs.push({
|
|
@@ -71,16 +71,14 @@ export interface InternalFormativeActionDetail {
|
|
|
71
71
|
/**
|
|
72
72
|
* A Media Time Source becoming available or going away.
|
|
73
73
|
*
|
|
74
|
-
* The
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
* rather than aspirational.
|
|
74
|
+
* The only seam through which a timed-media section reaches media: the stimulus
|
|
75
|
+
* card dispatches it with a native `<video>` adapter, and a host wrapping a
|
|
76
|
+
* third-party player dispatches it with its own port. One code path with two
|
|
77
|
+
* producers is what lets a host supply its own media element without shipping a
|
|
78
|
+
* PIE element.
|
|
80
79
|
*
|
|
81
|
-
* `source`
|
|
82
|
-
*
|
|
83
|
-
* on `pie-register`.
|
|
80
|
+
* `source` is a live object. The event never crosses a realm, like the element
|
|
81
|
+
* reference on `pie-register`.
|
|
84
82
|
*/
|
|
85
83
|
export interface InternalMediaTimeSourceDetail {
|
|
86
84
|
/** The renderable that owns the media, for matching against `stimulusRef`. */
|
|
@@ -93,11 +91,10 @@ export interface InternalMediaTimeSourceDetail {
|
|
|
93
91
|
* wiring its own player, and omitting the field reads as `"host"` because a
|
|
94
92
|
* caller constructing this event by hand is one.
|
|
95
93
|
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
* failure this contract exists to prevent.
|
|
94
|
+
* Precedence depends on it: the card re-runs its discovery whenever its content
|
|
95
|
+
* changes, and without the field the native element would replace a host's
|
|
96
|
+
* third-party port mid-session, flipping the capabilities back with it, so a
|
|
97
|
+
* policy would read as enforced while it is not.
|
|
101
98
|
*/
|
|
102
99
|
origin?: "native-adapter" | "host";
|
|
103
100
|
}
|
|
@@ -2,6 +2,6 @@
|
|
|
2
2
|
* Stable runtime-instance id generator.
|
|
3
3
|
*
|
|
4
4
|
* Produces a per-runtime id used to scope DOM attribute names and
|
|
5
|
-
* telemetry tags.
|
|
5
|
+
* telemetry tags.
|
|
6
6
|
*/
|
|
7
7
|
export declare function createRuntimeId(prefix?: string): string;
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Stable runtime-instance id generator.
|
|
3
3
|
*
|
|
4
4
|
* Produces a per-runtime id used to scope DOM attribute names and
|
|
5
|
-
* telemetry tags.
|
|
5
|
+
* telemetry tags.
|
|
6
6
|
*/
|
|
7
7
|
export function createRuntimeId(prefix = "runtime") {
|
|
8
8
|
return `${prefix}-${Math.random().toString(16).slice(2)}-${Date.now()}`;
|
|
@@ -35,10 +35,10 @@ export interface ShellScope {
|
|
|
35
35
|
* answered the scope in its detail, so that runtime claims it by id. Once
|
|
36
36
|
* the host has left the document, as in a teardown, the event is dispatched
|
|
37
37
|
* on the runtime's element instead, which the scope captured when the
|
|
38
|
-
* runtime answered. Events
|
|
39
|
-
*
|
|
40
|
-
* {@link MAX_HELD_SHELL_EVENTS}, and delivered in order
|
|
41
|
-
* `disconnect` and `publish(null)` drop them.
|
|
38
|
+
* runtime answered. Events sent before a runtime has answered, or while the
|
|
39
|
+
* runtime's element is out of the document and the host is in it, are held,
|
|
40
|
+
* the newest {@link MAX_HELD_SHELL_EVENTS}, and delivered in order to the
|
|
41
|
+
* runtime that answers next. `disconnect` and `publish(null)` drop them.
|
|
42
42
|
*/
|
|
43
43
|
send: (type: string, detail: object) => void;
|
|
44
44
|
}
|
|
@@ -83,10 +83,14 @@ export function createShellScope() {
|
|
|
83
83
|
if (runtime !== null)
|
|
84
84
|
registration.sync(identity, runtime);
|
|
85
85
|
};
|
|
86
|
+
// A runtime element that left the document while the shell stays in it no
|
|
87
|
+
// longer sits above the shell, so nothing would claim the shell's events.
|
|
88
|
+
const runtimeDeparted = (shellHost, address) => shellHost.isConnected &&
|
|
89
|
+
address.eventTarget.isConnected === false;
|
|
86
90
|
function send(type, detail) {
|
|
87
91
|
if (type === PIE_INTERNAL_CONTENT_LOADED_EVENT)
|
|
88
92
|
loaded = detail;
|
|
89
|
-
if (host && runtime !== null) {
|
|
93
|
+
if (host && runtime !== null && !runtimeDeparted(host, runtime)) {
|
|
90
94
|
dispatchCrossBoundaryEvent(shellEventTarget(host, runtime), type, {
|
|
91
95
|
...detail,
|
|
92
96
|
runtimeId: runtime.runtimeId,
|
|
@@ -97,20 +101,22 @@ export function createShellScope() {
|
|
|
97
101
|
if (held.length > MAX_HELD_SHELL_EVENTS)
|
|
98
102
|
held.shift();
|
|
99
103
|
}
|
|
100
|
-
// The subscription re-answers whenever the runtime republishes its context
|
|
101
|
-
//
|
|
104
|
+
// The subscription re-answers whenever the runtime republishes its context:
|
|
105
|
+
// under the same id it only delivers what the scope held meanwhile.
|
|
102
106
|
function onRuntime(value) {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
107
|
+
const moved = runtime !== null && value.runtimeId !== runtime.runtimeId;
|
|
108
|
+
if (value.runtimeId !== runtime?.runtimeId) {
|
|
109
|
+
runtime = { runtimeId: value.runtimeId, eventTarget: value.eventTarget };
|
|
110
|
+
syncRegistration();
|
|
111
|
+
}
|
|
108
112
|
const pending = held;
|
|
109
113
|
held = [];
|
|
110
114
|
for (const event of pending)
|
|
111
115
|
send(event.type, event.detail);
|
|
112
|
-
|
|
116
|
+
const replayed = pending.some((event) => event.type === PIE_INTERNAL_CONTENT_LOADED_EVENT);
|
|
117
|
+
if (moved && loaded && !replayed) {
|
|
113
118
|
send(PIE_INTERNAL_CONTENT_LOADED_EVENT, loaded);
|
|
119
|
+
}
|
|
114
120
|
}
|
|
115
121
|
function disconnect() {
|
|
116
122
|
for (const provided of [shell, region])
|
|
@@ -2,10 +2,6 @@ import { type AssessmentToolkitHostRuntimeContext, type AssessmentToolkitRegionS
|
|
|
2
2
|
import type { ZIndexLayer } from "../services/ToolCoordinator.js";
|
|
3
3
|
import type { ToolCoordinatorApi } from "../services/interfaces.js";
|
|
4
4
|
type BaseEventInit = Pick<EventInit, "bubbles" | "composed" | "cancelable">;
|
|
5
|
-
/**
|
|
6
|
-
* Contract helper for all toolkit/tool events that must cross custom-element boundaries.
|
|
7
|
-
*/
|
|
8
|
-
export declare function createCrossBoundaryEvent<T>(name: string, detail: T, init?: Partial<BaseEventInit>): CustomEvent<T>;
|
|
9
5
|
/**
|
|
10
6
|
* Dispatches a cross-boundary event with toolkit defaults.
|
|
11
7
|
*/
|
|
@@ -8,7 +8,7 @@ const CROSS_BOUNDARY_EVENT_INIT = {
|
|
|
8
8
|
/**
|
|
9
9
|
* Contract helper for all toolkit/tool events that must cross custom-element boundaries.
|
|
10
10
|
*/
|
|
11
|
-
|
|
11
|
+
function createCrossBoundaryEvent(name, detail, init = {}) {
|
|
12
12
|
return new CustomEvent(name, {
|
|
13
13
|
detail,
|
|
14
14
|
...CROSS_BOUNDARY_EVENT_INIT,
|
|
@@ -55,25 +55,17 @@ export type CatalogType = "spoken" | "sign-language" | "transcript" | "braille"
|
|
|
55
55
|
/**
|
|
56
56
|
* The catalog types PIE names, plus the rule for the ones it does not.
|
|
57
57
|
*
|
|
58
|
-
*
|
|
59
|
-
* extensible, and
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
* though — the named literals were documentation only, so a card written
|
|
63
|
-
* `"spokn"` was a perfectly valid `CatalogType` that no reader would ever ask
|
|
64
|
-
* for, and it failed by being invisible rather than by failing. That is what
|
|
65
|
-
* `isKnownCatalogType` and the warnings below are for: the openness stays, the
|
|
66
|
-
* silence does not.
|
|
58
|
+
* `CatalogType` stays open because QTI treats the support vocabulary as
|
|
59
|
+
* extensible, and catalogs arrive as authored JSON this type never validates.
|
|
60
|
+
* The trade: a card written `"spokn"` is a valid `CatalogType` no reader asks
|
|
61
|
+
* for, so `isKnownCatalogType` and the warnings below report it.
|
|
67
62
|
*/
|
|
68
63
|
export declare const KNOWN_CATALOG_TYPES: ReadonlySet<string>;
|
|
69
64
|
export declare function isKnownCatalogType(type: string): boolean;
|
|
70
65
|
/**
|
|
71
|
-
* Which of a card's two content slots it fills.
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
* says which form it is by carrying `content` or `payload`, and the
|
|
75
|
-
* exactly-one-of invariant is what makes that unambiguous. This names the
|
|
76
|
-
* distinction so a lookup can ask for one.
|
|
66
|
+
* Which of a card's two content slots it fills. The card says so by carrying
|
|
67
|
+
* `content` or `payload`, and the exactly-one-of invariant makes that
|
|
68
|
+
* unambiguous; this type names the distinction so a lookup can ask for one.
|
|
77
69
|
*/
|
|
78
70
|
export type CatalogCardForm = "content" | "payload";
|
|
79
71
|
export declare const catalogCardForm: (card: CatalogCard) => CatalogCardForm;
|
|
@@ -93,17 +85,15 @@ export interface CatalogLookupOptions {
|
|
|
93
85
|
* Preferred content form, when one catalog type legitimately has both on the
|
|
94
86
|
* same node.
|
|
95
87
|
*
|
|
96
|
-
* The case
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
* so whichever of the two was written second in the array was unreachable and
|
|
102
|
-
* nothing said so.
|
|
88
|
+
* The case is a `spoken` node carrying both a reading script and a recording
|
|
89
|
+
* of it, which is APIP's authoring pattern and what QTI 3's migration
|
|
90
|
+
* guidance keeps: the script is the source the audio was generated from and
|
|
91
|
+
* the fallback when it cannot play. Without a form, a lookup returns the
|
|
92
|
+
* first card matching type and language.
|
|
103
93
|
*
|
|
104
94
|
* A preference, not a filter: if the requested form is not present, the other
|
|
105
|
-
* one is still returned. Callers that cannot use a form
|
|
106
|
-
*
|
|
95
|
+
* one is still returned. Callers that cannot use a form check what they got,
|
|
96
|
+
* as they do for a card of a type they did not expect.
|
|
107
97
|
*/
|
|
108
98
|
form?: CatalogCardForm;
|
|
109
99
|
}
|
|
@@ -241,9 +231,11 @@ export declare class AccessibilityCatalogResolver {
|
|
|
241
231
|
*/
|
|
242
232
|
hasCatalog(catalogId: string): boolean;
|
|
243
233
|
/**
|
|
244
|
-
* Get alternative content for a catalog identifier
|
|
234
|
+
* Get alternative content for a catalog identifier.
|
|
245
235
|
*
|
|
246
|
-
*
|
|
236
|
+
* With a `context`, the catalog registered for exactly that owner comes
|
|
237
|
+
* first, then the one compatible scoped catalog (several are ambiguous and
|
|
238
|
+
* resolve to null). Item-level catalogs come next, then assessment-level.
|
|
247
239
|
*/
|
|
248
240
|
getAlternative(catalogId: string, options: CatalogLookupOptions): ResolvedCatalog | null;
|
|
249
241
|
private resolveCard;
|
|
@@ -264,11 +256,9 @@ export declare class AccessibilityCatalogResolver {
|
|
|
264
256
|
* Get all available alternatives for a catalog identifier
|
|
265
257
|
*
|
|
266
258
|
* Every card goes through `resolveCard`, the same projection `getAlternative`
|
|
267
|
-
* uses, so enumeration
|
|
268
|
-
*
|
|
269
|
-
*
|
|
270
|
-
* that arrived under the alias rendered correctly and was still reported as
|
|
271
|
-
* carrying no payload by anything asking what alternates exist.
|
|
259
|
+
* uses, so enumeration describes a card exactly as the resolution that
|
|
260
|
+
* renders it. A hand-rolled copy here drifted: the `signLanguage` alias was
|
|
261
|
+
* folded in on the resolution path only.
|
|
272
262
|
*/
|
|
273
263
|
getAllAlternatives(catalogId: string): ResolvedCatalog[];
|
|
274
264
|
/**
|
|
@@ -4,15 +4,10 @@ import { sanitizeSsmlString } from "./SSMLExtractor.js";
|
|
|
4
4
|
/**
|
|
5
5
|
* The catalog types PIE names, plus the rule for the ones it does not.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
* extensible, and
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* though — the named literals were documentation only, so a card written
|
|
12
|
-
* `"spokn"` was a perfectly valid `CatalogType` that no reader would ever ask
|
|
13
|
-
* for, and it failed by being invisible rather than by failing. That is what
|
|
14
|
-
* `isKnownCatalogType` and the warnings below are for: the openness stays, the
|
|
15
|
-
* silence does not.
|
|
7
|
+
* `CatalogType` stays open because QTI treats the support vocabulary as
|
|
8
|
+
* extensible, and catalogs arrive as authored JSON this type never validates.
|
|
9
|
+
* The trade: a card written `"spokn"` is a valid `CatalogType` no reader asks
|
|
10
|
+
* for, so `isKnownCatalogType` and the warnings below report it.
|
|
16
11
|
*/
|
|
17
12
|
export const KNOWN_CATALOG_TYPES = new Set([
|
|
18
13
|
"spoken",
|
|
@@ -336,9 +331,11 @@ export class AccessibilityCatalogResolver {
|
|
|
336
331
|
Array.from(this.scopedCatalogs.values()).some((owner) => owner.catalogs.has(catalogId)));
|
|
337
332
|
}
|
|
338
333
|
/**
|
|
339
|
-
* Get alternative content for a catalog identifier
|
|
334
|
+
* Get alternative content for a catalog identifier.
|
|
340
335
|
*
|
|
341
|
-
*
|
|
336
|
+
* With a `context`, the catalog registered for exactly that owner comes
|
|
337
|
+
* first, then the one compatible scoped catalog (several are ambiguous and
|
|
338
|
+
* resolve to null). Item-level catalogs come next, then assessment-level.
|
|
342
339
|
*/
|
|
343
340
|
getAlternative(catalogId, options) {
|
|
344
341
|
// A typo on this side is as silent as one on a card: the lookup simply finds
|
|
@@ -513,10 +510,10 @@ export class AccessibilityCatalogResolver {
|
|
|
513
510
|
}
|
|
514
511
|
return false;
|
|
515
512
|
}
|
|
516
|
-
// The single funnel every registration path runs through
|
|
517
|
-
// `addItemCatalogs` by way of `indexCatalogs`,
|
|
518
|
-
//
|
|
519
|
-
//
|
|
513
|
+
// The single funnel every registration path runs through (the constructor and
|
|
514
|
+
// `addItemCatalogs` by way of `indexCatalogs`, `registerCatalogs` and
|
|
515
|
+
// `registerOwner` by way of `insertScopedCatalogs`), so the unknown-type
|
|
516
|
+
// report lives here.
|
|
520
517
|
sanitizeCatalogs(catalogs) {
|
|
521
518
|
return catalogs.map((catalog) => ({
|
|
522
519
|
...catalog,
|
|
@@ -541,11 +538,10 @@ export class AccessibilityCatalogResolver {
|
|
|
541
538
|
// Language rungs, most specific first: the requested language, then the
|
|
542
539
|
// default language, then any.
|
|
543
540
|
//
|
|
544
|
-
// Each requested tag expands into its RFC 4647 lookup sequence
|
|
545
|
-
// tries `es-mx` and then `es` before falling
|
|
546
|
-
//
|
|
547
|
-
// emits
|
|
548
|
-
// no-constraint rung, i.e. by accident.
|
|
541
|
+
// Each requested tag expands into its RFC 4647 lookup sequence over
|
|
542
|
+
// normalized tags, so `es-MX` tries `es-mx` and then `es` before falling
|
|
543
|
+
// through to the default, and a POSIX `es_ES` card (what the Learnosity
|
|
544
|
+
// transform emits) matches an `es-ES` request on its own rung.
|
|
549
545
|
const languageRungs = [];
|
|
550
546
|
const pushLookupRungs = (tag) => {
|
|
551
547
|
for (const step of languageTagLookupSequence(tag)) {
|
|
@@ -563,18 +559,16 @@ export class AccessibilityCatalogResolver {
|
|
|
563
559
|
const candidates = catalog.cards.filter((card) => card.catalog === type && matchesLanguage(card));
|
|
564
560
|
if (candidates.length === 0)
|
|
565
561
|
continue;
|
|
566
|
-
//
|
|
567
|
-
//
|
|
568
|
-
//
|
|
569
|
-
//
|
|
570
|
-
// audio, which is worse than answering it with Spanish text.
|
|
562
|
+
// Language outranks form: form is preferred inside a language rung and
|
|
563
|
+
// never across them, so a Spanish lookup preferring a recording gets a
|
|
564
|
+
// Spanish recording first, then Spanish text, and never English audio
|
|
565
|
+
// ahead of either.
|
|
571
566
|
if (form) {
|
|
572
567
|
const preferred = candidates.find((card) => catalogCardForm(card) === form);
|
|
573
568
|
if (preferred)
|
|
574
569
|
return preferred;
|
|
575
570
|
}
|
|
576
|
-
// No preference
|
|
577
|
-
// which is what every caller got before form preference existed.
|
|
571
|
+
// No preference, or the preferred form is absent: the first match.
|
|
578
572
|
return candidates[0];
|
|
579
573
|
}
|
|
580
574
|
return null;
|
|
@@ -583,25 +577,20 @@ export class AccessibilityCatalogResolver {
|
|
|
583
577
|
* Get all available alternatives for a catalog identifier
|
|
584
578
|
*
|
|
585
579
|
* Every card goes through `resolveCard`, the same projection `getAlternative`
|
|
586
|
-
* uses, so enumeration
|
|
587
|
-
*
|
|
588
|
-
*
|
|
589
|
-
* that arrived under the alias rendered correctly and was still reported as
|
|
590
|
-
* carrying no payload by anything asking what alternates exist.
|
|
580
|
+
* uses, so enumeration describes a card exactly as the resolution that
|
|
581
|
+
* renders it. A hand-rolled copy here drifted: the `signLanguage` alias was
|
|
582
|
+
* folded in on the resolution path only.
|
|
591
583
|
*/
|
|
592
584
|
getAllAlternatives(catalogId) {
|
|
593
585
|
const results = [];
|
|
594
|
-
//
|
|
595
|
-
//
|
|
596
|
-
//
|
|
597
|
-
// and language alone dropped the second of those on the floor, so anything
|
|
598
|
-
// asking what alternates exist under-reported them.
|
|
586
|
+
// Keyed on type, language and form: one catalog identifier can carry cards
|
|
587
|
+
// of one type in several languages, and a script and a recording of one
|
|
588
|
+
// type in the same language.
|
|
599
589
|
const claimed = new Set();
|
|
600
590
|
const add = (card, source) => {
|
|
601
591
|
// The language part is normalized, so a POSIX `es_ES` card and a BCP-47
|
|
602
|
-
// `es-ES` card collapse to one entry
|
|
603
|
-
//
|
|
604
|
-
// alternates where resolution can only ever return one.
|
|
592
|
+
// `es-ES` card collapse to one entry, as on the resolution path, which
|
|
593
|
+
// only ever returns one of them.
|
|
605
594
|
const key = `${card.catalog}|${normalizeLanguageTag(card.language)}|${catalogCardForm(card)}`;
|
|
606
595
|
if (claimed.has(key))
|
|
607
596
|
return;
|
|
@@ -646,7 +635,6 @@ export class AccessibilityCatalogResolver {
|
|
|
646
635
|
getStatistics() {
|
|
647
636
|
const allTypes = new Set();
|
|
648
637
|
const allLanguages = new Set();
|
|
649
|
-
// Collect from assessment catalogs
|
|
650
638
|
for (const catalog of this.assessmentCatalogs.values()) {
|
|
651
639
|
for (const card of catalog.cards) {
|
|
652
640
|
allTypes.add(card.catalog);
|
|
@@ -654,7 +642,6 @@ export class AccessibilityCatalogResolver {
|
|
|
654
642
|
allLanguages.add(card.language);
|
|
655
643
|
}
|
|
656
644
|
}
|
|
657
|
-
// Collect from item catalogs
|
|
658
645
|
for (const catalog of this.itemCatalogs.values()) {
|
|
659
646
|
for (const card of catalog.cards) {
|
|
660
647
|
allTypes.add(card.catalog);
|