@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
|
@@ -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
|
*
|
|
@@ -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+
|
|
@@ -383,31 +381,55 @@ export class HighlightCoordinator {
|
|
|
383
381
|
this.applyAdaptiveTTSStyle();
|
|
384
382
|
this.setupThemeObservation();
|
|
385
383
|
}
|
|
384
|
+
/**
|
|
385
|
+
* Re-adapt the TTS colors when the page's theme moves: on the document
|
|
386
|
+
* element, on a `<pie-theme>` host, and when a `<pie-theme>` mounts, which
|
|
387
|
+
* themes its target as it connects.
|
|
388
|
+
*/
|
|
386
389
|
setupThemeObservation() {
|
|
387
390
|
if (typeof document === "undefined")
|
|
388
391
|
return;
|
|
389
392
|
if (typeof MutationObserver === "undefined")
|
|
390
393
|
return;
|
|
391
|
-
const
|
|
392
|
-
|
|
393
|
-
|
|
394
|
+
const themeHosts = document.getElementsByTagName("pie-theme");
|
|
395
|
+
const observedHosts = new WeakSet();
|
|
396
|
+
const observeNewHosts = () => {
|
|
397
|
+
let found = false;
|
|
398
|
+
for (const host of themeHosts) {
|
|
399
|
+
if (observedHosts.has(host))
|
|
400
|
+
continue;
|
|
401
|
+
observedHosts.add(host);
|
|
402
|
+
observer.observe(host, {
|
|
403
|
+
attributes: true,
|
|
404
|
+
attributeFilter: [
|
|
405
|
+
"theme",
|
|
406
|
+
"scheme",
|
|
407
|
+
"provider",
|
|
408
|
+
"variables",
|
|
409
|
+
"style",
|
|
410
|
+
"data-theme",
|
|
411
|
+
"data-color-scheme",
|
|
412
|
+
],
|
|
413
|
+
});
|
|
414
|
+
found = true;
|
|
415
|
+
}
|
|
416
|
+
return found;
|
|
417
|
+
};
|
|
418
|
+
const observer = new MutationObserver((records) => {
|
|
419
|
+
const mounted = observeNewHosts();
|
|
420
|
+
if (mounted || records.some((record) => record.type === "attributes")) {
|
|
421
|
+
this.applyAdaptiveTTSStyle();
|
|
422
|
+
}
|
|
423
|
+
});
|
|
424
|
+
this.themeObserver = observer;
|
|
425
|
+
observer.observe(document.documentElement, {
|
|
394
426
|
attributes: true,
|
|
395
427
|
attributeFilter: ["style", "data-theme", "data-color-scheme", "class"],
|
|
396
428
|
});
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
"theme",
|
|
402
|
-
"scheme",
|
|
403
|
-
"provider",
|
|
404
|
-
"variables",
|
|
405
|
-
"style",
|
|
406
|
-
"data-theme",
|
|
407
|
-
"data-color-scheme",
|
|
408
|
-
],
|
|
409
|
-
});
|
|
410
|
-
}
|
|
429
|
+
// Insertions, so a `<pie-theme>` mounted later is found. An insertion
|
|
430
|
+
// costs a walk of the live host list, which holds a handful at most.
|
|
431
|
+
observer.observe(document, { childList: true, subtree: true });
|
|
432
|
+
observeNewHosts();
|
|
411
433
|
}
|
|
412
434
|
parseColor(input) {
|
|
413
435
|
if (!input)
|
|
@@ -511,6 +533,8 @@ export class HighlightCoordinator {
|
|
|
511
533
|
: [0.8, 0.72, 0.68, 0.62, 0.56, 0.5];
|
|
512
534
|
let selectedOpacity = opacityCandidates[opacityCandidates.length - 1];
|
|
513
535
|
let bestScore = -Infinity;
|
|
536
|
+
// The first opacity whose blend clears 1.25:1 against the background and
|
|
537
|
+
// 2.4:1 against the text wins; otherwise the best weighted score.
|
|
514
538
|
for (const opacity of opacityCandidates) {
|
|
515
539
|
const blended = this.blend(accent, background, opacity);
|
|
516
540
|
const backgroundDelta = this.contrastRatio(blended, background);
|
|
@@ -593,7 +617,6 @@ export class HighlightCoordinator {
|
|
|
593
617
|
return;
|
|
594
618
|
if (typeof document === "undefined")
|
|
595
619
|
return; // SSR guard
|
|
596
|
-
// Check if styles already exist
|
|
597
620
|
if (document.getElementById("pie-highlight-styles"))
|
|
598
621
|
return;
|
|
599
622
|
const style = document.createElement("style");
|
|
@@ -633,12 +656,10 @@ export class HighlightCoordinator {
|
|
|
633
656
|
* lives in a font-driven pseudo-element a CSS range cannot paint) and
|
|
634
657
|
* whole-expression fallbacks.
|
|
635
658
|
*
|
|
636
|
-
* The supplied element is painted exactly
|
|
637
|
-
*
|
|
638
|
-
*
|
|
639
|
-
*
|
|
640
|
-
* genuine expression fallback (where the element already is the container)
|
|
641
|
-
* should paint the whole expression.
|
|
659
|
+
* The supplied element is painted exactly, with no walk up to a containing
|
|
660
|
+
* `<math>` / `<mjx-container>`: a resolved token paints as a token, and only
|
|
661
|
+
* an expression fallback, where the element already is the container, paints
|
|
662
|
+
* the whole expression.
|
|
642
663
|
*/
|
|
643
664
|
highlightTTSWordElement(element) {
|
|
644
665
|
if (!this.supported)
|
|
@@ -660,7 +681,6 @@ export class HighlightCoordinator {
|
|
|
660
681
|
if (!this.supported)
|
|
661
682
|
return;
|
|
662
683
|
this.applyAdaptiveTTSStyle(startElementOf(ranges));
|
|
663
|
-
// Clear previous sentence highlight
|
|
664
684
|
this.clearTTSSentence();
|
|
665
685
|
const highlight = sharedHighlight(TTS_SENTENCE_HIGHLIGHT);
|
|
666
686
|
for (const range of ranges) {
|
|
@@ -689,12 +709,11 @@ export class HighlightCoordinator {
|
|
|
689
709
|
// The read-along (sentence) band legitimately covers whole equations and
|
|
690
710
|
// replaced elements, so it escalates to them.
|
|
691
711
|
static SENTENCE_FALLBACK_SELECTOR = "math, mjx-container, svg, img, canvas, [role='img']";
|
|
692
|
-
// The word layer
|
|
693
|
-
//
|
|
694
|
-
//
|
|
695
|
-
// glyph nodes
|
|
696
|
-
//
|
|
697
|
-
// fallback because a CSS range cannot paint them.
|
|
712
|
+
// The word layer falls back to the element only for replaced elements
|
|
713
|
+
// (svg/img/canvas), which a CSS range cannot paint. Escalating to the
|
|
714
|
+
// enclosing `<math>` / `<mjx-container>` flashed the whole equation whenever
|
|
715
|
+
// a word crossed several glyph nodes, so math is tracked per token, held or
|
|
716
|
+
// by region in the highlight pipeline.
|
|
698
717
|
static WORD_FALLBACK_SELECTOR = "svg, img, canvas, [role='img']";
|
|
699
718
|
highlightTTSElementFallbacks(ranges) {
|
|
700
719
|
if (typeof Element === "undefined")
|
|
@@ -796,10 +815,8 @@ export class HighlightCoordinator {
|
|
|
796
815
|
*/
|
|
797
816
|
addAnnotation(range, color = HighlightColor.YELLOW) {
|
|
798
817
|
const id = `annotation-${this.nextAnnotationId++}`;
|
|
799
|
-
// Clone the range to store
|
|
800
818
|
const clonedRange = range.cloneRange();
|
|
801
819
|
adoptHighlightStylesFor(clonedRange.startContainer);
|
|
802
|
-
// Store annotation data
|
|
803
820
|
const annotation = {
|
|
804
821
|
id,
|
|
805
822
|
range: clonedRange,
|
|
@@ -913,44 +930,6 @@ export class HighlightCoordinator {
|
|
|
913
930
|
// ============================================================================
|
|
914
931
|
// HighlightCoordinatorApi interface implementation
|
|
915
932
|
// ============================================================================
|
|
916
|
-
/**
|
|
917
|
-
* Highlight a range as the given type: the TTS types paint as
|
|
918
|
-
* {@link highlightTTSWord} and {@link highlightTTSSentence} do.
|
|
919
|
-
*/
|
|
920
|
-
highlightRange(range, type, color = HighlightColor.YELLOW) {
|
|
921
|
-
if (!this.supported)
|
|
922
|
-
return;
|
|
923
|
-
switch (type) {
|
|
924
|
-
case HighlightType.TTS_WORD:
|
|
925
|
-
this.highlightTTSWord([range]);
|
|
926
|
-
break;
|
|
927
|
-
case HighlightType.TTS_SENTENCE:
|
|
928
|
-
this.highlightTTSSentence([range]);
|
|
929
|
-
break;
|
|
930
|
-
case HighlightType.ANNOTATION:
|
|
931
|
-
// For annotations, use the existing method
|
|
932
|
-
this.addAnnotation(range, color);
|
|
933
|
-
break;
|
|
934
|
-
}
|
|
935
|
-
}
|
|
936
|
-
/**
|
|
937
|
-
* Clear highlights of a specific type (interface method)
|
|
938
|
-
*/
|
|
939
|
-
clearHighlights(type) {
|
|
940
|
-
if (!this.supported)
|
|
941
|
-
return;
|
|
942
|
-
switch (type) {
|
|
943
|
-
case HighlightType.TTS_WORD:
|
|
944
|
-
this.clearTTSWord();
|
|
945
|
-
break;
|
|
946
|
-
case HighlightType.TTS_SENTENCE:
|
|
947
|
-
this.clearTTSSentence();
|
|
948
|
-
break;
|
|
949
|
-
case HighlightType.ANNOTATION:
|
|
950
|
-
this.clearAnnotations();
|
|
951
|
-
break;
|
|
952
|
-
}
|
|
953
|
-
}
|
|
954
933
|
/**
|
|
955
934
|
* Clear all highlights (interface method)
|
|
956
935
|
*/
|
|
@@ -960,12 +939,6 @@ export class HighlightCoordinator {
|
|
|
960
939
|
this.clearTTS();
|
|
961
940
|
this.clearAnnotations();
|
|
962
941
|
}
|
|
963
|
-
/**
|
|
964
|
-
* Check if CSS Highlight API is supported (interface method)
|
|
965
|
-
*/
|
|
966
|
-
isSupported() {
|
|
967
|
-
return this.supported;
|
|
968
|
-
}
|
|
969
942
|
/**
|
|
970
943
|
* Update TTS highlight style dynamically
|
|
971
944
|
*
|
|
@@ -980,7 +953,6 @@ export class HighlightCoordinator {
|
|
|
980
953
|
const styleEl = document.getElementById("pie-highlight-styles");
|
|
981
954
|
if (!styleEl)
|
|
982
955
|
return;
|
|
983
|
-
// Convert hex to rgba
|
|
984
956
|
const hexToRgb = (hex) => {
|
|
985
957
|
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
|
986
958
|
return result
|