@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
|
@@ -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);
|
|
@@ -1,21 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* ElementToolStateStore
|
|
2
|
+
* ElementToolStateStore: ephemeral UI tool state (answer eliminations, flags and
|
|
3
|
+
* the like), kept out of the session data sent to the server for scoring.
|
|
3
4
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* - Generic solution for any tool
|
|
5
|
+
* State is keyed by a composite global element id,
|
|
6
|
+
* `assessmentId:sectionId:attemptId:itemId:elementId`, unique across the
|
|
7
|
+
* assessment and per attempt: two attempts at one section never share a key.
|
|
8
|
+
* Subscribers see every change, and an optional persistence callback
|
|
9
|
+
* (`setOnStateChange`) receives the serialized state.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* The parts of a global element id. `attemptId` is `""` when the host names no
|
|
13
|
+
* attempt. A tool whose state belongs to an item or a section rather than to
|
|
14
|
+
* one element leaves `elementId`, and for a section `itemId`, as `""`.
|
|
15
15
|
*/
|
|
16
16
|
export interface ElementIdComponents {
|
|
17
17
|
assessmentId: string;
|
|
18
18
|
sectionId: string;
|
|
19
|
+
attemptId: string;
|
|
19
20
|
itemId: string;
|
|
20
21
|
elementId: string;
|
|
21
22
|
}
|
|
@@ -24,70 +25,51 @@ export declare class ElementToolStateStore {
|
|
|
24
25
|
private listeners;
|
|
25
26
|
private onStateChange;
|
|
26
27
|
/**
|
|
27
|
-
* Generates a globally unique element ID
|
|
28
|
-
* Format: `${assessmentId}:${sectionId}:${itemId}:${elementId}`,
|
|
29
|
-
* `%` written as `%25` and `:` as `%3A`, so an id containing
|
|
30
|
-
* and cannot collide with another split of the same characters.
|
|
28
|
+
* Generates a globally unique element ID from its parts.
|
|
29
|
+
* Format: `${assessmentId}:${sectionId}:${attemptId}:${itemId}:${elementId}`,
|
|
30
|
+
* each part with `%` written as `%25` and `:` as `%3A`, so an id containing
|
|
31
|
+
* `:` round-trips and cannot collide with another split of the same characters.
|
|
31
32
|
*
|
|
32
33
|
* @example
|
|
33
|
-
* getGlobalElementId('demo-assessment', 'section-1',
|
|
34
|
-
*
|
|
34
|
+
* getGlobalElementId({ assessmentId: 'demo-assessment', sectionId: 'section-1',
|
|
35
|
+
* attemptId: 'attempt-1', itemId: 'q1', elementId: 'mc1' })
|
|
36
|
+
* // Returns: "demo-assessment:section-1:attempt-1:q1:mc1"
|
|
35
37
|
*/
|
|
36
|
-
getGlobalElementId(
|
|
38
|
+
getGlobalElementId(parts: ElementIdComponents): string;
|
|
37
39
|
/**
|
|
38
40
|
* Parses a global element ID into its component parts.
|
|
39
41
|
*
|
|
40
42
|
* @example
|
|
41
|
-
* parseGlobalElementId('demo-assessment:section-1:q1:mc1')
|
|
42
|
-
* // Returns: { assessmentId: 'demo-assessment', sectionId: 'section-1',
|
|
43
|
+
* parseGlobalElementId('demo-assessment:section-1:attempt-1:q1:mc1')
|
|
44
|
+
* // Returns: { assessmentId: 'demo-assessment', sectionId: 'section-1',
|
|
45
|
+
* // attemptId: 'attempt-1', itemId: 'q1', elementId: 'mc1' }
|
|
43
46
|
*/
|
|
44
47
|
parseGlobalElementId(globalElementId: string): ElementIdComponents | null;
|
|
45
48
|
/**
|
|
46
49
|
* Sets tool state for a specific element.
|
|
47
50
|
* Notifies all subscribers and triggers persistence callback.
|
|
48
|
-
*
|
|
49
|
-
* @param globalElementId Composite key identifying the element
|
|
50
|
-
* @param toolId Tool identifier (e.g., 'answerEliminator', 'flagging')
|
|
51
|
-
* @param state Tool-specific state object
|
|
52
51
|
*/
|
|
53
52
|
setState(globalElementId: string, toolId: string, state: unknown): void;
|
|
54
|
-
/**
|
|
55
|
-
* Gets tool state for a specific element.
|
|
56
|
-
*
|
|
57
|
-
* @param globalElementId Composite key identifying the element
|
|
58
|
-
* @param toolId Tool identifier
|
|
59
|
-
* @returns Tool state or undefined if not found
|
|
60
|
-
*/
|
|
53
|
+
/** One tool's state for an element, or `undefined` when none is set. */
|
|
61
54
|
getState(globalElementId: string, toolId: string): unknown;
|
|
62
|
-
/**
|
|
63
|
-
* Gets all tool states for a specific element.
|
|
64
|
-
*
|
|
65
|
-
* @param globalElementId Composite key identifying the element
|
|
66
|
-
* @returns Object with tool states keyed by toolId
|
|
67
|
-
*/
|
|
55
|
+
/** Every tool's state for an element, keyed by toolId. */
|
|
68
56
|
getElementState(globalElementId: string): Record<string, unknown>;
|
|
69
57
|
/**
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
* @returns Nested object: { globalElementId: { toolId: state } }
|
|
58
|
+
* All state as `{ globalElementId: { toolId: state } }`, for persistence and
|
|
59
|
+
* debugging.
|
|
74
60
|
*/
|
|
75
61
|
getAllState(): Record<string, Record<string, unknown>>;
|
|
76
62
|
/**
|
|
77
63
|
* Subscribes to state changes.
|
|
78
64
|
* Callback is invoked whenever any element's tool state changes, including
|
|
79
|
-
* a `loadState` restore, with a copy of the state that it may keep.
|
|
80
|
-
*
|
|
81
|
-
* @param callback Function to call on state changes
|
|
82
|
-
* @returns Unsubscribe function
|
|
65
|
+
* a `loadState` restore, with a copy of the state that it may keep. Returns
|
|
66
|
+
* the unsubscribe function.
|
|
83
67
|
*/
|
|
84
68
|
subscribe(callback: (state: Map<string, Map<string, unknown>>) => void): () => void;
|
|
85
69
|
private _notifyListeners;
|
|
86
70
|
/**
|
|
87
71
|
* Sets a callback to be invoked on state changes for persistence integration.
|
|
88
72
|
* Used by demo/app layer to persist tool state to localStorage or server.
|
|
89
|
-
*
|
|
90
|
-
* @param callback Function to call with serialized state on changes
|
|
91
73
|
*/
|
|
92
74
|
setOnStateChange(callback: (state: Record<string, Record<string, unknown>>) => void): void;
|
|
93
75
|
private _notifyStateChange;
|
|
@@ -95,29 +77,18 @@ export declare class ElementToolStateStore {
|
|
|
95
77
|
* Loads tool state from serialized format.
|
|
96
78
|
* Used to restore state from localStorage or server. Subscribers are
|
|
97
79
|
* notified; the persistence callback is not, so a restore never writes back.
|
|
98
|
-
*
|
|
99
|
-
* @param state Serialized state object
|
|
100
80
|
*/
|
|
101
81
|
loadState(state: Record<string, Record<string, unknown>>): void;
|
|
102
|
-
/**
|
|
103
|
-
* Clears all tool state for a specific element.
|
|
104
|
-
*
|
|
105
|
-
* @param globalElementId Composite key identifying the element
|
|
106
|
-
*/
|
|
82
|
+
/** Clears all tool state for a specific element. */
|
|
107
83
|
clearElement(globalElementId: string): void;
|
|
108
84
|
/**
|
|
109
85
|
* Clears state for a specific tool across all elements.
|
|
110
86
|
* Useful when disabling a tool globally.
|
|
111
|
-
*
|
|
112
|
-
* @param toolId Tool identifier
|
|
113
87
|
*/
|
|
114
88
|
clearTool(toolId: string): void;
|
|
115
89
|
/**
|
|
116
|
-
* Clears all tool state for a specific section.
|
|
90
|
+
* Clears all tool state for a specific section, across its attempts.
|
|
117
91
|
* Useful when unmounting/disposing a section.
|
|
118
|
-
*
|
|
119
|
-
* @param assessmentId Assessment identifier
|
|
120
|
-
* @param sectionId Section identifier
|
|
121
92
|
*/
|
|
122
93
|
clearSection(assessmentId: string, sectionId: string): void;
|
|
123
94
|
/**
|
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* ElementToolStateStore
|
|
2
|
+
* ElementToolStateStore: ephemeral UI tool state (answer eliminations, flags and
|
|
3
|
+
* the like), kept out of the session data sent to the server for scoring.
|
|
3
4
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* - Element-level granularity (not item-level)
|
|
10
|
-
* - Composite keys for global uniqueness: `assessmentId:sectionId:itemId:elementId`
|
|
11
|
-
* - Cross-section state persistence
|
|
12
|
-
* - Reactive subscriptions
|
|
13
|
-
* - Optional persistence callback integration
|
|
14
|
-
* - Generic solution for any tool
|
|
5
|
+
* State is keyed by a composite global element id,
|
|
6
|
+
* `assessmentId:sectionId:attemptId:itemId:elementId`, unique across the
|
|
7
|
+
* assessment and per attempt: two attempts at one section never share a key.
|
|
8
|
+
* Subscribers see every change, and an optional persistence callback
|
|
9
|
+
* (`setOnStateChange`) receives the serialized state.
|
|
15
10
|
*/
|
|
11
|
+
const ID_PARTS = [
|
|
12
|
+
"assessmentId",
|
|
13
|
+
"sectionId",
|
|
14
|
+
"attemptId",
|
|
15
|
+
"itemId",
|
|
16
|
+
"elementId",
|
|
17
|
+
];
|
|
16
18
|
function escapeIdPart(part) {
|
|
17
19
|
return part.replace(/%/g, "%25").replace(/:/g, "%3A");
|
|
18
20
|
}
|
|
@@ -24,41 +26,37 @@ export class ElementToolStateStore {
|
|
|
24
26
|
listeners = new Set();
|
|
25
27
|
onStateChange = null;
|
|
26
28
|
/**
|
|
27
|
-
* Generates a globally unique element ID
|
|
28
|
-
* Format: `${assessmentId}:${sectionId}:${itemId}:${elementId}`,
|
|
29
|
-
* `%` written as `%25` and `:` as `%3A`, so an id containing
|
|
30
|
-
* and cannot collide with another split of the same characters.
|
|
29
|
+
* Generates a globally unique element ID from its parts.
|
|
30
|
+
* Format: `${assessmentId}:${sectionId}:${attemptId}:${itemId}:${elementId}`,
|
|
31
|
+
* each part with `%` written as `%25` and `:` as `%3A`, so an id containing
|
|
32
|
+
* `:` round-trips and cannot collide with another split of the same characters.
|
|
31
33
|
*
|
|
32
34
|
* @example
|
|
33
|
-
* getGlobalElementId('demo-assessment', 'section-1',
|
|
34
|
-
*
|
|
35
|
+
* getGlobalElementId({ assessmentId: 'demo-assessment', sectionId: 'section-1',
|
|
36
|
+
* attemptId: 'attempt-1', itemId: 'q1', elementId: 'mc1' })
|
|
37
|
+
* // Returns: "demo-assessment:section-1:attempt-1:q1:mc1"
|
|
35
38
|
*/
|
|
36
|
-
getGlobalElementId(
|
|
37
|
-
return
|
|
38
|
-
.map(escapeIdPart)
|
|
39
|
-
.join(":");
|
|
39
|
+
getGlobalElementId(parts) {
|
|
40
|
+
return ID_PARTS.map((name) => escapeIdPart(parts[name])).join(":");
|
|
40
41
|
}
|
|
41
42
|
/**
|
|
42
43
|
* Parses a global element ID into its component parts.
|
|
43
44
|
*
|
|
44
45
|
* @example
|
|
45
|
-
* parseGlobalElementId('demo-assessment:section-1:q1:mc1')
|
|
46
|
-
* // Returns: { assessmentId: 'demo-assessment', sectionId: 'section-1',
|
|
46
|
+
* parseGlobalElementId('demo-assessment:section-1:attempt-1:q1:mc1')
|
|
47
|
+
* // Returns: { assessmentId: 'demo-assessment', sectionId: 'section-1',
|
|
48
|
+
* // attemptId: 'attempt-1', itemId: 'q1', elementId: 'mc1' }
|
|
47
49
|
*/
|
|
48
50
|
parseGlobalElementId(globalElementId) {
|
|
49
51
|
const parts = globalElementId.split(":");
|
|
50
|
-
if (parts.length !==
|
|
52
|
+
if (parts.length !== ID_PARTS.length)
|
|
51
53
|
return null;
|
|
52
|
-
const [assessmentId, sectionId, itemId, elementId] = parts.map(unescapeIdPart);
|
|
53
|
-
return { assessmentId, sectionId, itemId, elementId };
|
|
54
|
+
const [assessmentId, sectionId, attemptId, itemId, elementId] = parts.map(unescapeIdPart);
|
|
55
|
+
return { assessmentId, sectionId, attemptId, itemId, elementId };
|
|
54
56
|
}
|
|
55
57
|
/**
|
|
56
58
|
* Sets tool state for a specific element.
|
|
57
59
|
* Notifies all subscribers and triggers persistence callback.
|
|
58
|
-
*
|
|
59
|
-
* @param globalElementId Composite key identifying the element
|
|
60
|
-
* @param toolId Tool identifier (e.g., 'answerEliminator', 'flagging')
|
|
61
|
-
* @param state Tool-specific state object
|
|
62
60
|
*/
|
|
63
61
|
setState(globalElementId, toolId, state) {
|
|
64
62
|
let elementState = this.elementStates.get(globalElementId);
|
|
@@ -70,22 +68,11 @@ export class ElementToolStateStore {
|
|
|
70
68
|
this._notifyListeners();
|
|
71
69
|
this._notifyStateChange();
|
|
72
70
|
}
|
|
73
|
-
/**
|
|
74
|
-
* Gets tool state for a specific element.
|
|
75
|
-
*
|
|
76
|
-
* @param globalElementId Composite key identifying the element
|
|
77
|
-
* @param toolId Tool identifier
|
|
78
|
-
* @returns Tool state or undefined if not found
|
|
79
|
-
*/
|
|
71
|
+
/** One tool's state for an element, or `undefined` when none is set. */
|
|
80
72
|
getState(globalElementId, toolId) {
|
|
81
73
|
return this.elementStates.get(globalElementId)?.get(toolId);
|
|
82
74
|
}
|
|
83
|
-
/**
|
|
84
|
-
* Gets all tool states for a specific element.
|
|
85
|
-
*
|
|
86
|
-
* @param globalElementId Composite key identifying the element
|
|
87
|
-
* @returns Object with tool states keyed by toolId
|
|
88
|
-
*/
|
|
75
|
+
/** Every tool's state for an element, keyed by toolId. */
|
|
89
76
|
getElementState(globalElementId) {
|
|
90
77
|
const elementState = this.elementStates.get(globalElementId);
|
|
91
78
|
if (!elementState)
|
|
@@ -93,10 +80,8 @@ export class ElementToolStateStore {
|
|
|
93
80
|
return Object.fromEntries(elementState.entries());
|
|
94
81
|
}
|
|
95
82
|
/**
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
* @returns Nested object: { globalElementId: { toolId: state } }
|
|
83
|
+
* All state as `{ globalElementId: { toolId: state } }`, for persistence and
|
|
84
|
+
* debugging.
|
|
100
85
|
*/
|
|
101
86
|
getAllState() {
|
|
102
87
|
const result = {};
|
|
@@ -108,10 +93,8 @@ export class ElementToolStateStore {
|
|
|
108
93
|
/**
|
|
109
94
|
* Subscribes to state changes.
|
|
110
95
|
* Callback is invoked whenever any element's tool state changes, including
|
|
111
|
-
* a `loadState` restore, with a copy of the state that it may keep.
|
|
112
|
-
*
|
|
113
|
-
* @param callback Function to call on state changes
|
|
114
|
-
* @returns Unsubscribe function
|
|
96
|
+
* a `loadState` restore, with a copy of the state that it may keep. Returns
|
|
97
|
+
* the unsubscribe function.
|
|
115
98
|
*/
|
|
116
99
|
subscribe(callback) {
|
|
117
100
|
this.listeners.add(callback);
|
|
@@ -136,8 +119,6 @@ export class ElementToolStateStore {
|
|
|
136
119
|
/**
|
|
137
120
|
* Sets a callback to be invoked on state changes for persistence integration.
|
|
138
121
|
* Used by demo/app layer to persist tool state to localStorage or server.
|
|
139
|
-
*
|
|
140
|
-
* @param callback Function to call with serialized state on changes
|
|
141
122
|
*/
|
|
142
123
|
setOnStateChange(callback) {
|
|
143
124
|
this.onStateChange = callback;
|
|
@@ -156,8 +137,6 @@ export class ElementToolStateStore {
|
|
|
156
137
|
* Loads tool state from serialized format.
|
|
157
138
|
* Used to restore state from localStorage or server. Subscribers are
|
|
158
139
|
* notified; the persistence callback is not, so a restore never writes back.
|
|
159
|
-
*
|
|
160
|
-
* @param state Serialized state object
|
|
161
140
|
*/
|
|
162
141
|
loadState(state) {
|
|
163
142
|
this.elementStates.clear();
|
|
@@ -167,11 +146,7 @@ export class ElementToolStateStore {
|
|
|
167
146
|
}
|
|
168
147
|
this._notifyListeners();
|
|
169
148
|
}
|
|
170
|
-
/**
|
|
171
|
-
* Clears all tool state for a specific element.
|
|
172
|
-
*
|
|
173
|
-
* @param globalElementId Composite key identifying the element
|
|
174
|
-
*/
|
|
149
|
+
/** Clears all tool state for a specific element. */
|
|
175
150
|
clearElement(globalElementId) {
|
|
176
151
|
this.elementStates.delete(globalElementId);
|
|
177
152
|
this._notifyListeners();
|
|
@@ -180,8 +155,6 @@ export class ElementToolStateStore {
|
|
|
180
155
|
/**
|
|
181
156
|
* Clears state for a specific tool across all elements.
|
|
182
157
|
* Useful when disabling a tool globally.
|
|
183
|
-
*
|
|
184
|
-
* @param toolId Tool identifier
|
|
185
158
|
*/
|
|
186
159
|
clearTool(toolId) {
|
|
187
160
|
for (const elementState of this.elementStates.values()) {
|
|
@@ -191,11 +164,8 @@ export class ElementToolStateStore {
|
|
|
191
164
|
this._notifyStateChange();
|
|
192
165
|
}
|
|
193
166
|
/**
|
|
194
|
-
* Clears all tool state for a specific section.
|
|
167
|
+
* Clears all tool state for a specific section, across its attempts.
|
|
195
168
|
* Useful when unmounting/disposing a section.
|
|
196
|
-
*
|
|
197
|
-
* @param assessmentId Assessment identifier
|
|
198
|
-
* @param sectionId Section identifier
|
|
199
169
|
*/
|
|
200
170
|
clearSection(assessmentId, sectionId) {
|
|
201
171
|
const prefix = `${escapeIdPart(assessmentId)}:${escapeIdPart(sectionId)}:`;
|
|
@@ -11,8 +11,6 @@
|
|
|
11
11
|
* - Configurable colors and styles
|
|
12
12
|
* - Annotation persistence via RangeSerializer
|
|
13
13
|
*
|
|
14
|
-
* Part of PIE Assessment Toolkit.
|
|
15
|
-
*
|
|
16
14
|
* Browser Support:
|
|
17
15
|
* - Chrome/Edge 105+
|
|
18
16
|
* - Safari 17.2+
|
|
@@ -63,6 +61,11 @@ export declare class HighlightCoordinator implements HighlightCoordinatorApi {
|
|
|
63
61
|
private rangeSerializer;
|
|
64
62
|
private themeObserver;
|
|
65
63
|
constructor();
|
|
64
|
+
/**
|
|
65
|
+
* Re-adapt the TTS colors when the page's theme moves: on the document
|
|
66
|
+
* element, on a `<pie-theme>` host, and when a `<pie-theme>` mounts, which
|
|
67
|
+
* themes its target as it connects.
|
|
68
|
+
*/
|
|
66
69
|
private setupThemeObservation;
|
|
67
70
|
private parseColor;
|
|
68
71
|
private relativeLuminance;
|
|
@@ -100,12 +103,10 @@ export declare class HighlightCoordinator implements HighlightCoordinatorApi {
|
|
|
100
103
|
* lives in a font-driven pseudo-element a CSS range cannot paint) and
|
|
101
104
|
* whole-expression fallbacks.
|
|
102
105
|
*
|
|
103
|
-
* The supplied element is painted exactly
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
* genuine expression fallback (where the element already is the container)
|
|
108
|
-
* should paint the whole expression.
|
|
106
|
+
* The supplied element is painted exactly, with no walk up to a containing
|
|
107
|
+
* `<math>` / `<mjx-container>`: a resolved token paints as a token, and only
|
|
108
|
+
* an expression fallback, where the element already is the container, paints
|
|
109
|
+
* the whole expression.
|
|
109
110
|
*/
|
|
110
111
|
highlightTTSWordElement(element: Element): void;
|
|
111
112
|
/**
|
|
@@ -196,23 +197,10 @@ export declare class HighlightCoordinator implements HighlightCoordinatorApi {
|
|
|
196
197
|
color: HighlightColor;
|
|
197
198
|
timestamp?: number;
|
|
198
199
|
}>, root?: Element): number;
|
|
199
|
-
/**
|
|
200
|
-
* Highlight a range as the given type: the TTS types paint as
|
|
201
|
-
* {@link highlightTTSWord} and {@link highlightTTSSentence} do.
|
|
202
|
-
*/
|
|
203
|
-
highlightRange(range: Range, type: HighlightType, color?: HighlightColor): void;
|
|
204
|
-
/**
|
|
205
|
-
* Clear highlights of a specific type (interface method)
|
|
206
|
-
*/
|
|
207
|
-
clearHighlights(type: HighlightType): void;
|
|
208
200
|
/**
|
|
209
201
|
* Clear all highlights (interface method)
|
|
210
202
|
*/
|
|
211
203
|
clearAll(): void;
|
|
212
|
-
/**
|
|
213
|
-
* Check if CSS Highlight API is supported (interface method)
|
|
214
|
-
*/
|
|
215
|
-
isSupported(): boolean;
|
|
216
204
|
/**
|
|
217
205
|
* Update TTS highlight style dynamically
|
|
218
206
|
*
|