@pie-players/pie-assessment-toolkit 0.3.75 → 1.0.0-beta.1
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 +451 -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-asenkvx6.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 +32 -30
- package/dist/policy/core/decision-types.d.ts +51 -34
- 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 +7 -3
- package/dist/policy/core/pnp-policy-inputs.js +17 -33
- package/dist/policy/core/provenance.d.ts +2 -2
- package/dist/policy/core/tool-parameters.d.ts +12 -0
- package/dist/policy/core/tool-parameters.js +27 -0
- package/dist/policy/engine.d.ts +1 -1
- package/dist/policy/sources/PnpPolicySource.d.ts +18 -24
- package/dist/policy/sources/PnpPolicySource.js +57 -29
- 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 +51 -57
- package/dist/services/TTSService.js +323 -284
- package/dist/services/ToolCoordinator.d.ts +6 -49
- package/dist/services/ToolCoordinator.js +25 -61
- package/dist/services/ToolRegistry.d.ts +83 -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 +51 -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 +39 -24
- package/dist/services/tool-context.js +158 -92
- 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 +2 -0
- package/dist/services/toolbar-decided-tools.js +2 -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 +13 -22
- 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 +7 -6
- 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
|
@@ -19,6 +19,7 @@ export class PnpPolicySource {
|
|
|
19
19
|
const allSupports = new Set();
|
|
20
20
|
pnp?.supports?.forEach((s) => allSupports.add(s));
|
|
21
21
|
pnp?.prohibitedSupports?.forEach((s) => allSupports.add(s));
|
|
22
|
+
pnp?.activateAtInit?.forEach((s) => allSupports.add(s));
|
|
22
23
|
districtPolicy?.blockedTools?.forEach((s) => allSupports.add(s));
|
|
23
24
|
districtPolicy?.requiredTools?.forEach((s) => allSupports.add(s));
|
|
24
25
|
for (const s of Object.keys(testAdmin?.toolOverrides ?? {})) {
|
|
@@ -42,8 +43,8 @@ export class PnpPolicySource {
|
|
|
42
43
|
* feature: the returned result carries exactly one decision, and
|
|
43
44
|
* `decisions[0].action` is the verdict.
|
|
44
45
|
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
46
|
+
* It reuses `resolveSupport(...)` because a second copy of the eight levels
|
|
47
|
+
* would drift.
|
|
47
48
|
*/
|
|
48
49
|
resolveFeature(featureId, args) {
|
|
49
50
|
const { ctx, result } = this.prepare(args);
|
|
@@ -61,10 +62,9 @@ export class PnpPolicySource {
|
|
|
61
62
|
const itemSettings = item?.settings;
|
|
62
63
|
const result = {
|
|
63
64
|
blockedToolIds: new Set(),
|
|
64
|
-
|
|
65
|
+
mandates: new Map(),
|
|
65
66
|
perToolFlags: new Map(),
|
|
66
67
|
decisions: [],
|
|
67
|
-
unmappedSupportIds: this.unmappedIds(pnp, settings, itemSettings),
|
|
68
68
|
blockedOverrides: new Map(),
|
|
69
69
|
sources: {},
|
|
70
70
|
};
|
|
@@ -94,7 +94,6 @@ export class PnpPolicySource {
|
|
|
94
94
|
districtPolicy: settings?.districtPolicy,
|
|
95
95
|
testAdmin: settings?.testAdministration,
|
|
96
96
|
itemSettings,
|
|
97
|
-
toolConfigs: settings?.toolConfigs,
|
|
98
97
|
};
|
|
99
98
|
return { ctx, result };
|
|
100
99
|
}
|
|
@@ -164,13 +163,21 @@ export class PnpPolicySource {
|
|
|
164
163
|
});
|
|
165
164
|
return;
|
|
166
165
|
}
|
|
166
|
+
const activateAtInit = ctx.pnp?.activateAtInit?.includes(supportId) === true;
|
|
167
|
+
const mandate = ctx.itemSettings?.requiredTools?.includes(supportId)
|
|
168
|
+
? "item-requirement"
|
|
169
|
+
: ctx.districtPolicy?.requiredTools?.includes(supportId)
|
|
170
|
+
? "district-requirement"
|
|
171
|
+
: undefined;
|
|
167
172
|
// 5. Test administration override `true`: grants the support for the
|
|
168
|
-
// session.
|
|
173
|
+
// session. A requirement naming it keeps its mandate.
|
|
169
174
|
if (override === true) {
|
|
175
|
+
if (mandate)
|
|
176
|
+
out.mandates.set(supportId, mandate);
|
|
170
177
|
out.perToolFlags.set(supportId, {
|
|
171
|
-
required:
|
|
178
|
+
required: mandate !== undefined,
|
|
172
179
|
alwaysAvailable: true,
|
|
173
|
-
|
|
180
|
+
activateAtInit,
|
|
174
181
|
rule: "test-admin-override",
|
|
175
182
|
});
|
|
176
183
|
out.decisions.push({
|
|
@@ -185,12 +192,12 @@ export class PnpPolicySource {
|
|
|
185
192
|
return;
|
|
186
193
|
}
|
|
187
194
|
// 6. Item requirement (forces enable)
|
|
188
|
-
if (
|
|
189
|
-
out.
|
|
195
|
+
if (mandate === "item-requirement") {
|
|
196
|
+
out.mandates.set(supportId, mandate);
|
|
190
197
|
out.perToolFlags.set(supportId, {
|
|
191
198
|
required: true,
|
|
192
199
|
alwaysAvailable: false,
|
|
193
|
-
|
|
200
|
+
activateAtInit,
|
|
194
201
|
rule: "item-requirement",
|
|
195
202
|
});
|
|
196
203
|
out.decisions.push({
|
|
@@ -200,17 +207,17 @@ export class PnpPolicySource {
|
|
|
200
207
|
action: "enable",
|
|
201
208
|
sourceType: "item",
|
|
202
209
|
reason: `Item requires "${supportId}" for this question`,
|
|
203
|
-
value: ctx.itemSettings
|
|
210
|
+
value: ctx.itemSettings?.requiredTools,
|
|
204
211
|
});
|
|
205
212
|
return;
|
|
206
213
|
}
|
|
207
214
|
// 7. District requirement
|
|
208
|
-
if (
|
|
209
|
-
out.
|
|
215
|
+
if (mandate === "district-requirement") {
|
|
216
|
+
out.mandates.set(supportId, mandate);
|
|
210
217
|
out.perToolFlags.set(supportId, {
|
|
211
218
|
required: true,
|
|
212
219
|
alwaysAvailable: false,
|
|
213
|
-
|
|
220
|
+
activateAtInit,
|
|
214
221
|
rule: "district-requirement",
|
|
215
222
|
});
|
|
216
223
|
out.decisions.push({
|
|
@@ -220,16 +227,35 @@ export class PnpPolicySource {
|
|
|
220
227
|
action: "enable",
|
|
221
228
|
sourceType: "assessment",
|
|
222
229
|
reason: `District policy requires "${supportId}" for all assessments`,
|
|
223
|
-
value: ctx.districtPolicy
|
|
230
|
+
value: ctx.districtPolicy?.requiredTools,
|
|
231
|
+
});
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
// 8. Profile supports (student needs). `activateAtInit` grants as
|
|
235
|
+
// `supports` does and names the tool to turn on at start.
|
|
236
|
+
if (activateAtInit) {
|
|
237
|
+
out.perToolFlags.set(supportId, {
|
|
238
|
+
required: false,
|
|
239
|
+
alwaysAvailable: true,
|
|
240
|
+
activateAtInit,
|
|
241
|
+
rule: "pnp-activate-at-init",
|
|
242
|
+
});
|
|
243
|
+
out.decisions.push({
|
|
244
|
+
precedence: 8,
|
|
245
|
+
rule: "pnp-activate-at-init",
|
|
246
|
+
featureId: supportId,
|
|
247
|
+
action: "enable",
|
|
248
|
+
sourceType: "student",
|
|
249
|
+
reason: `Student PNP profile turns on "${supportId}" at start`,
|
|
250
|
+
value: ctx.pnp?.activateAtInit,
|
|
224
251
|
});
|
|
225
252
|
return;
|
|
226
253
|
}
|
|
227
|
-
// 8. Profile supports (student needs)
|
|
228
254
|
if (ctx.pnp?.supports?.includes(supportId)) {
|
|
229
255
|
out.perToolFlags.set(supportId, {
|
|
230
256
|
required: false,
|
|
231
257
|
alwaysAvailable: true,
|
|
232
|
-
|
|
258
|
+
activateAtInit: false,
|
|
233
259
|
rule: "pnp-support",
|
|
234
260
|
});
|
|
235
261
|
out.decisions.push({
|
|
@@ -256,24 +282,31 @@ export class PnpPolicySource {
|
|
|
256
282
|
});
|
|
257
283
|
}
|
|
258
284
|
/**
|
|
259
|
-
*
|
|
260
|
-
*
|
|
261
|
-
*
|
|
285
|
+
* Ids the assessment or any of `items` names that no tool is registered
|
|
286
|
+
* under, each with the rules whose lists name it, in precedence order. A
|
|
287
|
+
* support id is the id of the tool it grants, so an id missing from the
|
|
288
|
+
* registry matches nothing placed; the engine turns each into a
|
|
289
|
+
* `tool-policy.unknownSupportId` diagnostic. Empty when the registry is: an
|
|
290
|
+
* empty registry has nothing to check an id against, and
|
|
291
|
+
* `tool-config-validation` already reports it once.
|
|
262
292
|
*/
|
|
263
|
-
|
|
293
|
+
unknownSupportIds(assessment, items) {
|
|
264
294
|
const unmapped = new Map();
|
|
265
295
|
if (this.toolRegistry.getAllTools().length === 0)
|
|
266
296
|
return unmapped;
|
|
297
|
+
const pnp = assessment?.personalNeedsProfile;
|
|
298
|
+
const settings = assessment?.settings;
|
|
267
299
|
const named = [
|
|
268
300
|
["district-block", settings?.districtPolicy?.blockedTools],
|
|
269
301
|
[
|
|
270
302
|
"test-admin-override",
|
|
271
303
|
Object.keys(settings?.testAdministration?.toolOverrides ?? {}),
|
|
272
304
|
],
|
|
273
|
-
["item-restriction",
|
|
305
|
+
["item-restriction", items.flatMap((item) => item.restrictedTools ?? [])],
|
|
274
306
|
["pnp-prohibited", pnp?.prohibitedSupports],
|
|
275
|
-
["item-requirement",
|
|
307
|
+
["item-requirement", items.flatMap((item) => item.requiredTools ?? [])],
|
|
276
308
|
["district-requirement", settings?.districtPolicy?.requiredTools],
|
|
309
|
+
["pnp-activate-at-init", pnp?.activateAtInit],
|
|
277
310
|
["pnp-support", pnp?.supports],
|
|
278
311
|
];
|
|
279
312
|
for (const [rule, ids] of named) {
|
|
@@ -288,9 +321,4 @@ export class PnpPolicySource {
|
|
|
288
321
|
}
|
|
289
322
|
return unmapped;
|
|
290
323
|
}
|
|
291
|
-
/** Tool parameters: the item's entry, else the assessment's. */
|
|
292
|
-
resolveToolSettings(supportId, ctx) {
|
|
293
|
-
return (ctx.itemSettings?.toolParameters?.[supportId] ??
|
|
294
|
-
ctx.toolConfigs?.[supportId]);
|
|
295
|
-
}
|
|
296
324
|
}
|
|
@@ -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,
|