@pie-players/pie-assessment-toolkit 0.3.64 → 0.3.66
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 +81 -33
- package/dist/components/ItemToolBar.custom-element.js +1 -1
- package/dist/components/PieAssessmentToolkit.custom-element.js +9 -8
- package/dist/components/SectionToolBar.custom-element.js +1 -1
- package/dist/components/chunks/ItemToolBar-8jgdz50p.js +51 -0
- package/dist/components/chunks/ItemToolBar-cvs646j3.js +36 -0
- package/dist/index.d.ts +7 -8
- package/dist/index.js +10 -5
- package/dist/policy/core/PolicySource.d.ts +2 -2
- package/dist/policy/core/PolicySource.js +2 -2
- package/dist/policy/core/ToolPolicyEngine.d.ts +7 -3
- package/dist/policy/core/ToolPolicyEngine.js +11 -3
- package/dist/policy/core/compose-decision.d.ts +3 -2
- package/dist/policy/core/compose-decision.js +3 -2
- package/dist/policy/core/decision-types.d.ts +2 -2
- package/dist/policy/core/decision-types.js +2 -2
- package/dist/policy/core/feature-decision.d.ts +28 -1
- package/dist/policy/core/feature-decision.js +25 -3
- package/dist/policy/core/pnp-policy-inputs.d.ts +2 -2
- package/dist/policy/core/pnp-policy-inputs.js +2 -2
- package/dist/policy/core/provenance.d.ts +4 -1
- package/dist/policy/core/provenance.js +4 -1
- package/dist/policy/sources/PnpPolicySource.d.ts +2 -1
- package/dist/policy/sources/PnpPolicySource.js +2 -1
- package/dist/runtime/composition-emit-scheduler.d.ts +78 -0
- package/dist/runtime/composition-emit-scheduler.js +154 -0
- package/dist/runtime/core/engine-resolver.d.ts +1 -1
- package/dist/runtime/core/engine-transition.js +3 -1
- package/dist/services/AccessibilityCatalogResolver.d.ts +49 -10
- package/dist/services/AccessibilityCatalogResolver.js +180 -11
- package/dist/services/TTSService.d.ts +11 -0
- package/dist/services/TTSService.js +220 -32
- package/dist/services/ToolRegistry.d.ts +259 -8
- package/dist/services/ToolRegistry.js +181 -11
- package/dist/services/ToolkitCoordinator.d.ts +20 -2
- package/dist/services/ToolkitCoordinator.js +47 -8
- package/dist/services/catalog-owner.d.ts +71 -0
- package/dist/services/catalog-owner.js +64 -0
- package/dist/services/createDefaultToolRegistry.d.ts +25 -58
- package/dist/services/createDefaultToolRegistry.js +24 -104
- package/dist/services/defaultPersonalNeedsProfile.d.ts +16 -14
- package/dist/services/defaultPersonalNeedsProfile.js +17 -38
- package/dist/services/framework-error.d.ts +1 -1
- package/dist/services/interfaces.d.ts +15 -3
- package/dist/services/pnp-standard-features.d.ts +1 -1
- package/dist/services/tool-config-defaults.d.ts +7 -23
- package/dist/services/tool-config-defaults.js +7 -46
- package/dist/services/tool-config-validation.d.ts +1 -1
- package/dist/services/tool-config-validation.js +44 -4
- package/dist/services/tts/browser-provider.js +191 -24
- package/dist/services/tts-runtime-config.js +7 -2
- package/dist/tools/internal.d.ts +35 -0
- package/dist/tools/internal.js +33 -0
- package/dist/tools/tool-tag-map.d.ts +15 -3
- package/dist/tools/tool-tag-map.js +21 -18
- package/package.json +14 -10
- package/dist/components/chunks/ItemToolBar-3cppre9r.js +0 -51
- package/dist/components/chunks/ItemToolBar-7rq2gj8b.js +0 -22
- package/dist/runtime/catalog-registration.d.ts +0 -67
- package/dist/runtime/catalog-registration.js +0 -86
- package/dist/services/sign-language-cards.d.ts +0 -82
- package/dist/services/sign-language-cards.js +0 -133
- package/dist/tools/registrations/accessibility-tools.d.ts +0 -34
- package/dist/tools/registrations/accessibility-tools.js +0 -217
- package/dist/tools/registrations/calculator.d.ts +0 -20
- package/dist/tools/registrations/calculator.js +0 -228
- package/dist/tools/registrations/interaction-tools.d.ts +0 -27
- package/dist/tools/registrations/interaction-tools.js +0 -143
- package/dist/tools/registrations/measurement-tools.d.ts +0 -24
- package/dist/tools/registrations/measurement-tools.js +0 -130
- package/dist/tools/registrations/subject-specific-tools.d.ts +0 -27
- package/dist/tools/registrations/subject-specific-tools.js +0 -158
- package/dist/tools/registrations/tts.d.ts +0 -21
- package/dist/tools/registrations/tts.js +0 -184
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { catalogOwnerContextFor, collectOwnerCatalogRegistrations, } from "./catalog-owner.js";
|
|
1
2
|
import { sanitizeSsmlString } from "./SSMLExtractor.js";
|
|
2
3
|
/**
|
|
3
4
|
* The catalog types PIE names, plus the rule for the ones it does not.
|
|
@@ -39,6 +40,39 @@ export function isKnownCatalogType(type) {
|
|
|
39
40
|
// "this token is not a thing", and repeating it per card or per lookup would bury
|
|
40
41
|
// it under itself.
|
|
41
42
|
const reportedUnknownTypes = new Set();
|
|
43
|
+
function cloneAndFreezeCatalogValue(value, seen = new Set()) {
|
|
44
|
+
if (value === null ||
|
|
45
|
+
typeof value === "string" ||
|
|
46
|
+
typeof value === "number" ||
|
|
47
|
+
typeof value === "boolean" ||
|
|
48
|
+
typeof value === "undefined") {
|
|
49
|
+
return value;
|
|
50
|
+
}
|
|
51
|
+
if (typeof value !== "object") {
|
|
52
|
+
throw new TypeError("catalog cards must contain JSON-compatible values");
|
|
53
|
+
}
|
|
54
|
+
if (seen.has(value)) {
|
|
55
|
+
throw new TypeError("catalog cards must not contain cycles");
|
|
56
|
+
}
|
|
57
|
+
seen.add(value);
|
|
58
|
+
try {
|
|
59
|
+
if (Array.isArray(value)) {
|
|
60
|
+
return Object.freeze(value.map((entry) => cloneAndFreezeCatalogValue(entry, seen)));
|
|
61
|
+
}
|
|
62
|
+
const prototype = Object.getPrototypeOf(value);
|
|
63
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
64
|
+
throw new TypeError("catalog cards must contain only plain objects and arrays");
|
|
65
|
+
}
|
|
66
|
+
const cloned = {};
|
|
67
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
68
|
+
cloned[key] = cloneAndFreezeCatalogValue(entry, seen);
|
|
69
|
+
}
|
|
70
|
+
return Object.freeze(cloned);
|
|
71
|
+
}
|
|
72
|
+
finally {
|
|
73
|
+
seen.delete(value);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
42
76
|
function reportUnknownCatalogType(type, side, where) {
|
|
43
77
|
const key = `${side}|${type}`;
|
|
44
78
|
if (reportedUnknownTypes.has(key))
|
|
@@ -93,6 +127,7 @@ export class AccessibilityCatalogResolver {
|
|
|
93
127
|
// pass per unique string (sanitizeSsmlString is idempotent).
|
|
94
128
|
sanitizedSpokenCache = new Map();
|
|
95
129
|
catalogChangeListeners = new Set();
|
|
130
|
+
reportedSnapshotProblems = new Set();
|
|
96
131
|
constructor(assessmentCatalogs, defaultLanguage = "en-US") {
|
|
97
132
|
this.defaultLanguage = defaultLanguage;
|
|
98
133
|
this.indexCatalogs(assessmentCatalogs ?? [], "assessment");
|
|
@@ -162,9 +197,9 @@ export class AccessibilityCatalogResolver {
|
|
|
162
197
|
targetMap.set(catalog.identifier, catalog);
|
|
163
198
|
}
|
|
164
199
|
}
|
|
165
|
-
|
|
200
|
+
insertScopedCatalogs(context, catalogs) {
|
|
166
201
|
if (!catalogs || catalogs.length === 0)
|
|
167
|
-
return
|
|
202
|
+
return null;
|
|
168
203
|
const key = this.getOwnerKey(context);
|
|
169
204
|
const existing = this.scopedCatalogs.get(key);
|
|
170
205
|
const scoped = existing?.catalogs ?? new Map();
|
|
@@ -177,33 +212,102 @@ export class AccessibilityCatalogResolver {
|
|
|
177
212
|
scoped.set(catalog.identifier, catalog);
|
|
178
213
|
insertedIds.push(catalog.identifier);
|
|
179
214
|
}
|
|
180
|
-
if (!existing) {
|
|
215
|
+
if (!existing && insertedIds.length > 0) {
|
|
181
216
|
this.scopedCatalogs.set(key, {
|
|
182
217
|
context: { ...context },
|
|
183
218
|
catalogs: scoped,
|
|
184
219
|
});
|
|
185
220
|
}
|
|
221
|
+
return { key, context: { ...context }, catalogs: scoped, insertedIds };
|
|
222
|
+
}
|
|
223
|
+
removeScopedInsertion(insertion) {
|
|
224
|
+
const current = this.scopedCatalogs.get(insertion.key);
|
|
225
|
+
if (current?.catalogs !== insertion.catalogs)
|
|
226
|
+
return false;
|
|
227
|
+
let removed = false;
|
|
228
|
+
for (const insertedId of insertion.insertedIds) {
|
|
229
|
+
removed = current.catalogs.delete(insertedId) || removed;
|
|
230
|
+
}
|
|
231
|
+
if (current.catalogs.size === 0) {
|
|
232
|
+
this.scopedCatalogs.delete(insertion.key);
|
|
233
|
+
}
|
|
234
|
+
return removed;
|
|
235
|
+
}
|
|
236
|
+
registerCatalogs(context, catalogs) {
|
|
237
|
+
const insertion = this.insertScopedCatalogs(context, catalogs);
|
|
186
238
|
// Only when something was actually inserted: an all-duplicates call changes
|
|
187
239
|
// nothing, and waking every reader to re-resolve for that would make the
|
|
188
240
|
// signal untrustworthy.
|
|
189
|
-
if (insertedIds.length > 0) {
|
|
241
|
+
if (insertion && insertion.insertedIds.length > 0) {
|
|
190
242
|
this.emitCatalogsChange({ reason: "scoped-registered", context });
|
|
191
243
|
}
|
|
244
|
+
let active = true;
|
|
192
245
|
return () => {
|
|
193
|
-
|
|
194
|
-
if (current?.catalogs !== scoped)
|
|
246
|
+
if (!active)
|
|
195
247
|
return;
|
|
196
|
-
|
|
197
|
-
|
|
248
|
+
active = false;
|
|
249
|
+
if (insertion && this.removeScopedInsertion(insertion)) {
|
|
250
|
+
this.emitCatalogsChange({ reason: "scoped-removed", context });
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Register every catalog carried by one mounted item or passage as one
|
|
256
|
+
* owner-level transaction.
|
|
257
|
+
*
|
|
258
|
+
* The resolver owns the entity walk and emits one post-mutation signal, so a
|
|
259
|
+
* reader never observes only the root or only the model half of an owner.
|
|
260
|
+
*/
|
|
261
|
+
registerOwner(registration) {
|
|
262
|
+
const { owner, entity } = registration;
|
|
263
|
+
const context = catalogOwnerContextFor(owner);
|
|
264
|
+
const insertions = [];
|
|
265
|
+
let registrations = [];
|
|
266
|
+
try {
|
|
267
|
+
registrations = collectOwnerCatalogRegistrations(entity, owner);
|
|
268
|
+
}
|
|
269
|
+
catch (error) {
|
|
270
|
+
console.warn("[AccessibilityCatalogResolver] A mounted content owner could not be traversed for catalogs; the primary content remains available.", error);
|
|
271
|
+
}
|
|
272
|
+
for (const entry of registrations) {
|
|
273
|
+
try {
|
|
274
|
+
const insertion = this.insertScopedCatalogs(entry.context, entry.catalogs);
|
|
275
|
+
if (insertion)
|
|
276
|
+
insertions.push(insertion);
|
|
198
277
|
}
|
|
199
|
-
|
|
200
|
-
|
|
278
|
+
catch (error) {
|
|
279
|
+
console.warn("[AccessibilityCatalogResolver] Invalid catalogs on a mounted content owner were ignored; the primary content and other valid catalogs remain available.", error);
|
|
201
280
|
}
|
|
202
|
-
|
|
281
|
+
}
|
|
282
|
+
const changed = insertions.some((insertion) => insertion.insertedIds.length > 0);
|
|
283
|
+
if (changed) {
|
|
284
|
+
this.emitCatalogsChange({ reason: "scoped-registered", context });
|
|
285
|
+
}
|
|
286
|
+
let active = true;
|
|
287
|
+
return () => {
|
|
288
|
+
if (!active)
|
|
289
|
+
return;
|
|
290
|
+
active = false;
|
|
291
|
+
let removed = false;
|
|
292
|
+
for (const insertion of insertions) {
|
|
293
|
+
removed = this.removeScopedInsertion(insertion) || removed;
|
|
294
|
+
}
|
|
295
|
+
if (removed) {
|
|
203
296
|
this.emitCatalogsChange({ reason: "scoped-removed", context });
|
|
204
297
|
}
|
|
205
298
|
};
|
|
206
299
|
}
|
|
300
|
+
/** Bind catalog reads and change observation to one content owner. */
|
|
301
|
+
forOwner(context) {
|
|
302
|
+
const ownerContext = { ...context };
|
|
303
|
+
return Object.freeze({
|
|
304
|
+
snapshot: () => this.createOwnerSnapshot(ownerContext),
|
|
305
|
+
onChange: (listener) => this.onCatalogsChange((event) => {
|
|
306
|
+
if (this.catalogChangeAffectsOwner(event, ownerContext))
|
|
307
|
+
listener();
|
|
308
|
+
}),
|
|
309
|
+
});
|
|
310
|
+
}
|
|
207
311
|
/**
|
|
208
312
|
* Add item-level catalogs (called when rendering a new item)
|
|
209
313
|
*/
|
|
@@ -353,6 +457,61 @@ export class AccessibilityCatalogResolver {
|
|
|
353
457
|
}
|
|
354
458
|
return true;
|
|
355
459
|
}
|
|
460
|
+
ownerScopedCatalogEntries(context) {
|
|
461
|
+
const entries = [];
|
|
462
|
+
const exact = this.scopedCatalogs.get(this.getOwnerKey(context));
|
|
463
|
+
if (exact)
|
|
464
|
+
entries.push(...exact.catalogs.entries());
|
|
465
|
+
for (const owner of this.scopedCatalogs.values()) {
|
|
466
|
+
if (owner === exact)
|
|
467
|
+
continue;
|
|
468
|
+
if (!this.isCompatibleOwnerContext(owner.context, context))
|
|
469
|
+
continue;
|
|
470
|
+
entries.push(...owner.catalogs.entries());
|
|
471
|
+
}
|
|
472
|
+
return entries;
|
|
473
|
+
}
|
|
474
|
+
createOwnerSnapshot(context) {
|
|
475
|
+
const cards = [];
|
|
476
|
+
const entries = this.ownerScopedCatalogEntries(context);
|
|
477
|
+
if (context.ownerKind === "global") {
|
|
478
|
+
entries.push(...this.assessmentCatalogs.entries());
|
|
479
|
+
}
|
|
480
|
+
for (const [catalogId, catalog] of entries) {
|
|
481
|
+
for (const card of catalog.cards) {
|
|
482
|
+
try {
|
|
483
|
+
cards.push(Object.freeze({
|
|
484
|
+
catalogId,
|
|
485
|
+
card: cloneAndFreezeCatalogValue(card),
|
|
486
|
+
}));
|
|
487
|
+
}
|
|
488
|
+
catch (error) {
|
|
489
|
+
const key = `${this.getOwnerKey(context)}\u0000${catalogId}`;
|
|
490
|
+
if (!this.reportedSnapshotProblems.has(key)) {
|
|
491
|
+
this.reportedSnapshotProblems.add(key);
|
|
492
|
+
console.warn(`[AccessibilityCatalogResolver] Catalog "${catalogId}" contains a card that cannot be exposed in an owner snapshot; that card was ignored.`, error);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
return Object.freeze({ cards: Object.freeze(cards) });
|
|
498
|
+
}
|
|
499
|
+
catalogChangeAffectsOwner(event, context) {
|
|
500
|
+
if (!event.context)
|
|
501
|
+
return false;
|
|
502
|
+
if (this.isCompatibleOwnerContext(event.context, context))
|
|
503
|
+
return true;
|
|
504
|
+
// `registerOwner` emits once at the entity root after registering root and
|
|
505
|
+
// model catalogs transactionally. A model-bound reader belongs to that same
|
|
506
|
+
// owner family and must observe the root event too.
|
|
507
|
+
if (context.ownerKind === "itemModel" &&
|
|
508
|
+
context.modelId &&
|
|
509
|
+
event.context.ownerKind === "itemModel" &&
|
|
510
|
+
!event.context.modelId) {
|
|
511
|
+
return this.isCompatibleOwnerContext({ ...event.context, modelId: context.modelId }, context);
|
|
512
|
+
}
|
|
513
|
+
return false;
|
|
514
|
+
}
|
|
356
515
|
// The single funnel every registration path runs through — the constructor and
|
|
357
516
|
// `addItemCatalogs` by way of `indexCatalogs`, and `registerCatalogs`
|
|
358
517
|
// directly — which is why the unknown-type report lives here rather than at
|
|
@@ -487,6 +646,15 @@ export class AccessibilityCatalogResolver {
|
|
|
487
646
|
allLanguages.add(card.language);
|
|
488
647
|
}
|
|
489
648
|
}
|
|
649
|
+
for (const owner of this.scopedCatalogs.values()) {
|
|
650
|
+
for (const catalog of owner.catalogs.values()) {
|
|
651
|
+
for (const card of catalog.cards) {
|
|
652
|
+
allTypes.add(card.catalog);
|
|
653
|
+
if (card.language)
|
|
654
|
+
allLanguages.add(card.language);
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
}
|
|
490
658
|
return {
|
|
491
659
|
totalCatalogs: this.assessmentCatalogs.size +
|
|
492
660
|
this.itemCatalogs.size +
|
|
@@ -535,6 +703,7 @@ export class AccessibilityCatalogResolver {
|
|
|
535
703
|
this.itemCatalogs.clear();
|
|
536
704
|
this.scopedCatalogs.clear();
|
|
537
705
|
this.sanitizedSpokenCache.clear();
|
|
706
|
+
this.reportedSnapshotProblems.clear();
|
|
538
707
|
}
|
|
539
708
|
/**
|
|
540
709
|
* Destroy and cleanup
|
|
@@ -68,6 +68,13 @@ export declare class TTSService {
|
|
|
68
68
|
private sentenceHighlightSegments;
|
|
69
69
|
private currentSeekSegmentIndex;
|
|
70
70
|
private activeSentenceStartOffset;
|
|
71
|
+
private playbackStartDeferredRunId;
|
|
72
|
+
private pendingPlaybackStartHighlights;
|
|
73
|
+
private playbackStartBarrierCleanup;
|
|
74
|
+
private playbackRateWriteQueue;
|
|
75
|
+
private playbackRateRequestId;
|
|
76
|
+
private pendingPlaybackRateRequest;
|
|
77
|
+
private activePlaybackRate;
|
|
71
78
|
private activeHighlightMode;
|
|
72
79
|
private lastRenderedRegionTarget;
|
|
73
80
|
private telemetryReporter;
|
|
@@ -75,6 +82,10 @@ export declare class TTSService {
|
|
|
75
82
|
private static readonly debugEnabled;
|
|
76
83
|
constructor();
|
|
77
84
|
private debugLog;
|
|
85
|
+
private clearPlaybackStartBarrier;
|
|
86
|
+
private notifyPlaybackStarted;
|
|
87
|
+
private installPlaybackStartBarrier;
|
|
88
|
+
private runWhenPlaybackStarts;
|
|
78
89
|
private emitTelemetry;
|
|
79
90
|
/**
|
|
80
91
|
* Initialize TTS service with a provider
|