@pie-players/pie-assessment-toolkit 0.3.73 → 0.3.74

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.
Files changed (108) hide show
  1. package/README.md +171 -56
  2. package/dist/attempt/AssessmentSession.d.ts +1 -5
  3. package/dist/attempt/AssessmentSession.js +0 -29
  4. package/dist/components/ItemToolBar.custom-element.d.ts +1 -0
  5. package/dist/components/ItemToolBar.custom-element.js +1 -1
  6. package/dist/components/PieAssessmentToolkit.custom-element.d.ts +1 -0
  7. package/dist/components/PieAssessmentToolkit.custom-element.js +14 -14
  8. package/dist/components/SectionToolBar.custom-element.d.ts +1 -0
  9. package/dist/components/SectionToolBar.custom-element.js +1 -1
  10. package/dist/components/chunks/ItemToolBar-b9dz5jvd.js +40 -0
  11. package/dist/components/chunks/ItemToolBar-gmrryv2v.js +2 -0
  12. package/dist/components/chunks/ItemToolBar-h4xap0t0.js +4 -0
  13. package/dist/components/chunks/ItemToolBar-wr2myaah.js +2 -0
  14. package/dist/components/chunks/sre-engine-wf9s43gg.js +1 -0
  15. package/dist/context/assessment-toolkit-context.d.ts +5 -0
  16. package/dist/context/runtime-context-consumer.js +5 -38
  17. package/dist/index.d.ts +6 -6
  18. package/dist/index.js +8 -9
  19. package/dist/policy/core/ToolPolicyEngine.d.ts +8 -2
  20. package/dist/policy/core/ToolPolicyEngine.js +12 -0
  21. package/dist/runtime/adapter/SectionEngineAdapter.d.ts +3 -3
  22. package/dist/runtime/adapter/SectionEngineAdapter.js +3 -3
  23. package/dist/runtime/registration-events.d.ts +3 -0
  24. package/dist/runtime/section-runtime-engine-host-context.d.ts +3 -9
  25. package/dist/runtime/section-runtime-engine-host-context.js +5 -43
  26. package/dist/runtime/tool-host-contract.d.ts +20 -0
  27. package/dist/runtime/tool-host-contract.js +21 -0
  28. package/dist/services/HighlightCoordinator.d.ts +1 -7
  29. package/dist/services/HighlightCoordinator.js +1 -3
  30. package/dist/services/TTSService.d.ts +15 -1
  31. package/dist/services/TTSService.js +88 -35
  32. package/dist/services/ToolCoordinator.d.ts +1 -7
  33. package/dist/services/ToolCoordinator.js +1 -3
  34. package/dist/services/ToolRegistry.d.ts +13 -6
  35. package/dist/services/ToolRegistry.js +19 -7
  36. package/dist/services/ToolkitCoordinator.d.ts +110 -18
  37. package/dist/services/ToolkitCoordinator.js +273 -85
  38. package/dist/services/catalog-owner.d.ts +11 -0
  39. package/dist/services/catalog-owner.js +25 -0
  40. package/dist/services/create-tools-config.d.ts +2 -2
  41. package/dist/services/framework-error.d.ts +8 -1
  42. package/dist/services/interfaces.d.ts +9 -9
  43. package/dist/services/pnp-standard-features.d.ts +1 -1
  44. package/dist/services/section-controller-types.d.ts +15 -0
  45. package/dist/services/spoken-audio-cards.js +1 -1
  46. package/dist/services/tool-config-validation.d.ts +13 -2
  47. package/dist/services/tool-config-validation.js +33 -16
  48. package/dist/services/tool-context.js +29 -13
  49. package/dist/services/tool-providers/TTSToolProvider.d.ts +27 -65
  50. package/dist/services/tool-providers/TTSToolProvider.js +47 -9
  51. package/dist/services/tool-providers/ToolProviderRegistry.d.ts +4 -4
  52. package/dist/services/tool-providers/ToolProviderRegistry.js +4 -16
  53. package/dist/services/tool-providers/index.d.ts +0 -9
  54. package/dist/services/tool-providers/index.js +2 -4
  55. package/dist/services/tools-config-normalizer.d.ts +20 -1
  56. package/dist/services/tts/catalog-span-alignment.d.ts +2 -17
  57. package/dist/services/tts/catalog-span-alignment.js +17 -142
  58. package/dist/services/tts/generated-speech/types.d.ts +0 -12
  59. package/dist/services/tts/highlight-pipeline/boundary-normalizer.js +12 -62
  60. package/dist/services/tts/highlight-pipeline/create-highlight-plan.js +26 -1
  61. package/dist/services/tts/highlight-pipeline/normalize-speech-chunks.js +3 -6
  62. package/dist/services/tts/math-alignment/index.d.ts +0 -5
  63. package/dist/services/tts/math-alignment/index.js +5 -25
  64. package/dist/services/tts/math-alignment/speech-tokenizer.d.ts +9 -1
  65. package/dist/services/tts/math-alignment/speech-tokenizer.js +38 -36
  66. package/dist/services/tts/math-alignment/types.d.ts +4 -2
  67. package/dist/services/tts/math-aware-text-processing.js +44 -2
  68. package/dist/services/tts/math-speech.d.ts +5 -1
  69. package/dist/services/tts/math-speech.js +9 -6
  70. package/dist/services/tts/sre-engine.d.ts +7 -0
  71. package/dist/services/tts/sre-engine.js +11 -0
  72. package/dist/services/tts/sre-locales.d.ts +14 -0
  73. package/dist/services/tts/sre-locales.js +57 -0
  74. package/dist/services/tts/text-processing.d.ts +0 -1
  75. package/dist/services/tts/text-processing.js +0 -1
  76. package/dist/services/tts-runtime-config.d.ts +21 -3
  77. package/dist/services/tts-runtime-config.js +11 -3
  78. package/dist/tools/client.d.ts +1 -3
  79. package/dist/tools/client.js +1 -5
  80. package/dist/tools/internal.d.ts +6 -5
  81. package/dist/tools/internal.js +6 -8
  82. package/package.json +17 -36
  83. package/dist/components/chunks/ItemToolBar-byq80z42.js +0 -40
  84. package/dist/components/chunks/ItemToolBar-d36g3aw4.js +0 -49
  85. package/dist/services/ToolConfigResolver.d.ts +0 -161
  86. package/dist/services/ToolConfigResolver.js +0 -229
  87. package/dist/services/catalog-media.d.ts +0 -56
  88. package/dist/services/catalog-media.js +0 -190
  89. package/dist/services/createDefaultToolRegistry.d.ts +0 -53
  90. package/dist/services/createDefaultToolRegistry.js +0 -45
  91. package/dist/services/tool-providers/CortexToolProvider.d.ts +0 -13
  92. package/dist/services/tool-providers/CortexToolProvider.js +0 -32
  93. package/dist/services/tool-providers/DesmosToolProvider.d.ts +0 -14
  94. package/dist/services/tool-providers/DesmosToolProvider.js +0 -32
  95. package/dist/services/tool-providers/GeoGebraToolProvider.d.ts +0 -16
  96. package/dist/services/tool-providers/GeoGebraToolProvider.js +0 -32
  97. package/dist/services/tool-providers/LazyCalculatorToolProvider.d.ts +0 -35
  98. package/dist/services/tool-providers/LazyCalculatorToolProvider.js +0 -95
  99. package/dist/services/tts/visual-line-ranges.d.ts +0 -18
  100. package/dist/services/tts/visual-line-ranges.js +0 -226
  101. package/dist/tools/index.d.ts +0 -8
  102. package/dist/tools/index.js +0 -10
  103. package/dist/tools/response-discovery.d.ts +0 -61
  104. package/dist/tools/response-discovery.js +0 -113
  105. package/dist/tools/types.d.ts +0 -337
  106. package/dist/tools/types.js +0 -12
  107. package/dist/tools/variant-resolver.d.ts +0 -41
  108. package/dist/tools/variant-resolver.js +0 -207
@@ -1,161 +0,0 @@
1
- /**
2
- * ToolConfigResolver
3
- *
4
- * Implements 3-tier hierarchy for tool configuration:
5
- * Item requirements > Roster allowances > Student accommodations
6
- *
7
- * Ensures IEP/504 compliance by properly handling accommodation requirements
8
- * while respecting item-level and test-level restrictions.
9
- *
10
- * Features:
11
- * - 3-tier configuration hierarchy
12
- * - IEP/504 accommodation support
13
- * - Item-specific tool requirements
14
- * - Test-level tool restrictions
15
- * - Type-safe configuration
16
- *
17
- * Part of PIE Assessment Toolkit.
18
- */
19
- /**
20
- * Tool allowance: "0" = blocked, "1" = allowed
21
- */
22
- export type ToolAllowance = "0" | "1";
23
- /**
24
- * Item-level tool configuration (most specific)
25
- */
26
- export interface ItemToolConfig {
27
- [toolId: string]: {
28
- type?: string;
29
- required?: boolean;
30
- settings?: any;
31
- };
32
- }
33
- /**
34
- * Roster/Test-level tool allowances (assessment-wide rules)
35
- */
36
- export interface RosterToolConfig {
37
- [toolId: string]: ToolAllowance;
38
- }
39
- /**
40
- * Student accommodation profile (least specific)
41
- */
42
- export interface StudentAccommodations {
43
- accommodations: string[];
44
- }
45
- /**
46
- * Resolved tool configuration
47
- */
48
- export interface ResolvedToolConfig {
49
- enabled: boolean;
50
- type?: string;
51
- required?: boolean;
52
- settings?: any;
53
- source: "item" | "roster" | "student" | "default";
54
- }
55
- /**
56
- * Full configuration input
57
- */
58
- export interface ToolConfigInput {
59
- itemConfig?: ItemToolConfig;
60
- rosterConfig?: RosterToolConfig;
61
- studentProfile?: StudentAccommodations;
62
- }
63
- export declare class ToolConfigResolver {
64
- /**
65
- * Resolve configuration for a specific tool
66
- *
67
- * Hierarchy (highest to lowest priority):
68
- * 1. Item level: Item requires scientific calculator → must provide
69
- * 2. Roster level: Test blocks a tool → blocked regardless of accommodation
70
- * 3. Student level: IEP requires TTS → enabled unless blocked above
71
- *
72
- * @param toolId Tool identifier
73
- * @param itemConfig Item-level tool configuration
74
- * @param rosterConfig Roster-level tool allowances
75
- * @param studentProfile Student accommodation profile
76
- * @returns Resolved configuration or null if tool not available
77
- */
78
- resolveTool(toolId: string, itemConfig?: ItemToolConfig, rosterConfig?: RosterToolConfig, studentProfile?: StudentAccommodations): ResolvedToolConfig | null;
79
- /**
80
- * Resolve all tools from configuration
81
- *
82
- * @param input Configuration input
83
- * @returns Map of tool ID to resolved configuration
84
- */
85
- resolveAll(input: ToolConfigInput): Map<string, ResolvedToolConfig>;
86
- /**
87
- * Check if a tool is enabled
88
- *
89
- * @param toolId Tool identifier
90
- * @param input Configuration input
91
- * @returns true if tool is enabled
92
- */
93
- isToolEnabled(toolId: string, input: ToolConfigInput): boolean;
94
- /**
95
- * Check if a tool is required
96
- *
97
- * @param toolId Tool identifier
98
- * @param input Configuration input
99
- * @returns true if tool is required
100
- */
101
- isToolRequired(toolId: string, input: ToolConfigInput): boolean;
102
- /**
103
- * Get tool type (e.g., 'scientific' for calculator)
104
- *
105
- * @param toolId Tool identifier
106
- * @param input Configuration input
107
- * @returns Tool type or null
108
- */
109
- getToolType(toolId: string, input: ToolConfigInput): string | null;
110
- /**
111
- * Get tool settings
112
- *
113
- * @param toolId Tool identifier
114
- * @param input Configuration input
115
- * @returns Tool settings or null
116
- */
117
- getToolSettings(toolId: string, input: ToolConfigInput): any | null;
118
- /**
119
- * Get list of all enabled tools
120
- *
121
- * @param input Configuration input
122
- * @returns Array of enabled tool IDs
123
- */
124
- getEnabledTools(input: ToolConfigInput): string[];
125
- /**
126
- * Get list of required tools
127
- *
128
- * @param input Configuration input
129
- * @returns Array of required tool IDs
130
- */
131
- getRequiredTools(input: ToolConfigInput): string[];
132
- /**
133
- * Validate that all required tools are available
134
- *
135
- * @param availableTools Set of available tool IDs
136
- * @param input Configuration input
137
- * @returns Object with validation result
138
- */
139
- validate(availableTools: Set<string>, input: ToolConfigInput): {
140
- valid: boolean;
141
- missingTools: string[];
142
- };
143
- /**
144
- * Merge multiple roster configurations (for complex assessments)
145
- *
146
- * More restrictive settings win (blocked > allowed).
147
- *
148
- * @param configs Array of roster configurations
149
- * @returns Merged configuration
150
- */
151
- mergeRosterConfigs(...configs: RosterToolConfig[]): RosterToolConfig;
152
- /**
153
- * Merge multiple student accommodation profiles
154
- *
155
- * Union of all accommodations.
156
- *
157
- * @param profiles Array of student profiles
158
- * @returns Merged profile
159
- */
160
- mergeStudentProfiles(...profiles: StudentAccommodations[]): StudentAccommodations;
161
- }
@@ -1,229 +0,0 @@
1
- /**
2
- * ToolConfigResolver
3
- *
4
- * Implements 3-tier hierarchy for tool configuration:
5
- * Item requirements > Roster allowances > Student accommodations
6
- *
7
- * Ensures IEP/504 compliance by properly handling accommodation requirements
8
- * while respecting item-level and test-level restrictions.
9
- *
10
- * Features:
11
- * - 3-tier configuration hierarchy
12
- * - IEP/504 accommodation support
13
- * - Item-specific tool requirements
14
- * - Test-level tool restrictions
15
- * - Type-safe configuration
16
- *
17
- * Part of PIE Assessment Toolkit.
18
- */
19
- export class ToolConfigResolver {
20
- /**
21
- * Resolve configuration for a specific tool
22
- *
23
- * Hierarchy (highest to lowest priority):
24
- * 1. Item level: Item requires scientific calculator → must provide
25
- * 2. Roster level: Test blocks a tool → blocked regardless of accommodation
26
- * 3. Student level: IEP requires TTS → enabled unless blocked above
27
- *
28
- * @param toolId Tool identifier
29
- * @param itemConfig Item-level tool configuration
30
- * @param rosterConfig Roster-level tool allowances
31
- * @param studentProfile Student accommodation profile
32
- * @returns Resolved configuration or null if tool not available
33
- */
34
- resolveTool(toolId, itemConfig, rosterConfig, studentProfile) {
35
- // Check item level (highest priority)
36
- const itemTool = itemConfig?.[toolId];
37
- if (itemTool) {
38
- // Item explicitly configures this tool
39
- return {
40
- enabled: true,
41
- type: itemTool.type,
42
- required: itemTool.required ?? false,
43
- settings: itemTool.settings,
44
- source: "item",
45
- };
46
- }
47
- // Check roster level (blocks take precedence)
48
- const rosterAllowance = rosterConfig?.[toolId];
49
- if (rosterAllowance === "0") {
50
- // Roster explicitly blocks this tool
51
- return null;
52
- }
53
- if (rosterAllowance === "1") {
54
- // Roster allows this tool
55
- return {
56
- enabled: true,
57
- required: false,
58
- source: "roster",
59
- };
60
- }
61
- // Check student level (lowest priority)
62
- const hasAccommodation = studentProfile?.accommodations.includes(toolId) ?? false;
63
- if (hasAccommodation) {
64
- // Student has accommodation for this tool
65
- return {
66
- enabled: true,
67
- required: false,
68
- source: "student",
69
- };
70
- }
71
- // Tool not configured at any level
72
- return null;
73
- }
74
- /**
75
- * Resolve all tools from configuration
76
- *
77
- * @param input Configuration input
78
- * @returns Map of tool ID to resolved configuration
79
- */
80
- resolveAll(input) {
81
- const resolved = new Map();
82
- // Collect all tool IDs from all levels
83
- const allToolIds = new Set();
84
- // From item config
85
- if (input.itemConfig) {
86
- Object.keys(input.itemConfig).forEach((id) => allToolIds.add(id));
87
- }
88
- // From roster config
89
- if (input.rosterConfig) {
90
- Object.keys(input.rosterConfig).forEach((id) => allToolIds.add(id));
91
- }
92
- // From student accommodations
93
- if (input.studentProfile) {
94
- input.studentProfile.accommodations.forEach((id) => allToolIds.add(id));
95
- }
96
- // Resolve each tool
97
- for (const toolId of allToolIds) {
98
- const config = this.resolveTool(toolId, input.itemConfig, input.rosterConfig, input.studentProfile);
99
- if (config) {
100
- resolved.set(toolId, config);
101
- }
102
- }
103
- return resolved;
104
- }
105
- /**
106
- * Check if a tool is enabled
107
- *
108
- * @param toolId Tool identifier
109
- * @param input Configuration input
110
- * @returns true if tool is enabled
111
- */
112
- isToolEnabled(toolId, input) {
113
- const config = this.resolveTool(toolId, input.itemConfig, input.rosterConfig, input.studentProfile);
114
- return config?.enabled ?? false;
115
- }
116
- /**
117
- * Check if a tool is required
118
- *
119
- * @param toolId Tool identifier
120
- * @param input Configuration input
121
- * @returns true if tool is required
122
- */
123
- isToolRequired(toolId, input) {
124
- const config = this.resolveTool(toolId, input.itemConfig, input.rosterConfig, input.studentProfile);
125
- return config?.required ?? false;
126
- }
127
- /**
128
- * Get tool type (e.g., 'scientific' for calculator)
129
- *
130
- * @param toolId Tool identifier
131
- * @param input Configuration input
132
- * @returns Tool type or null
133
- */
134
- getToolType(toolId, input) {
135
- const config = this.resolveTool(toolId, input.itemConfig, input.rosterConfig, input.studentProfile);
136
- return config?.type ?? null;
137
- }
138
- /**
139
- * Get tool settings
140
- *
141
- * @param toolId Tool identifier
142
- * @param input Configuration input
143
- * @returns Tool settings or null
144
- */
145
- getToolSettings(toolId, input) {
146
- const config = this.resolveTool(toolId, input.itemConfig, input.rosterConfig, input.studentProfile);
147
- return config?.settings ?? null;
148
- }
149
- /**
150
- * Get list of all enabled tools
151
- *
152
- * @param input Configuration input
153
- * @returns Array of enabled tool IDs
154
- */
155
- getEnabledTools(input) {
156
- const resolved = this.resolveAll(input);
157
- return Array.from(resolved.entries())
158
- .filter(([_, config]) => config.enabled)
159
- .map(([toolId]) => toolId);
160
- }
161
- /**
162
- * Get list of required tools
163
- *
164
- * @param input Configuration input
165
- * @returns Array of required tool IDs
166
- */
167
- getRequiredTools(input) {
168
- const resolved = this.resolveAll(input);
169
- return Array.from(resolved.entries())
170
- .filter(([_, config]) => config.required)
171
- .map(([toolId]) => toolId);
172
- }
173
- /**
174
- * Validate that all required tools are available
175
- *
176
- * @param availableTools Set of available tool IDs
177
- * @param input Configuration input
178
- * @returns Object with validation result
179
- */
180
- validate(availableTools, input) {
181
- const requiredTools = this.getRequiredTools(input);
182
- const missingTools = requiredTools.filter((toolId) => !availableTools.has(toolId));
183
- return {
184
- valid: missingTools.length === 0,
185
- missingTools,
186
- };
187
- }
188
- /**
189
- * Merge multiple roster configurations (for complex assessments)
190
- *
191
- * More restrictive settings win (blocked > allowed).
192
- *
193
- * @param configs Array of roster configurations
194
- * @returns Merged configuration
195
- */
196
- mergeRosterConfigs(...configs) {
197
- const merged = {};
198
- for (const config of configs) {
199
- for (const [toolId, allowance] of Object.entries(config)) {
200
- // If any config blocks the tool, it's blocked
201
- if (allowance === "0") {
202
- merged[toolId] = "0";
203
- }
204
- else if (!merged[toolId]) {
205
- // If not already set, use this allowance
206
- merged[toolId] = allowance;
207
- }
208
- }
209
- }
210
- return merged;
211
- }
212
- /**
213
- * Merge multiple student accommodation profiles
214
- *
215
- * Union of all accommodations.
216
- *
217
- * @param profiles Array of student profiles
218
- * @returns Merged profile
219
- */
220
- mergeStudentProfiles(...profiles) {
221
- const allAccommodations = new Set();
222
- for (const profile of profiles) {
223
- profile.accommodations.forEach((acc) => allAccommodations.add(acc));
224
- }
225
- return {
226
- accommodations: Array.from(allAccommodations),
227
- };
228
- }
229
- }
@@ -1,56 +0,0 @@
1
- /**
2
- * Validation primitives shared by every catalog card whose payload references
3
- * media.
4
- *
5
- * Extracted from `sign-language-cards.ts` when `spoken` cards gained a recorded
6
- * audio form: both card types take authored, wire-facing URLs and hand them to a
7
- * media element in the learner's browser, so both need the same scheme
8
- * allow-list and the same "treat as absent, never as partially valid" posture.
9
- * Two copies of a URL allow-list is one copy that gets a fix and one that does
10
- * not.
11
- *
12
- * Part of PIE Assessment Toolkit.
13
- */
14
- import type { MediaFragmentRange, MediaSource } from "@pie-players/pie-players-shared/types";
15
- /**
16
- * The only `MediaAssetRef.version` this build renders.
17
- *
18
- * `media-asset-contract.md` requires unknown-version rejection for runtime
19
- * rendering: bumping to `2` obliges every consumer to accept both for as long as
20
- * any producer emits `1`, so a card claiming a version this build does not
21
- * implement must not be rendered on a guess at which fields still mean what they
22
- * did.
23
- */
24
- export declare const SUPPORTED_MEDIA_ASSET_VERSION = 1;
25
- /**
26
- * Whether a media reference claims a version this build cannot render.
27
- *
28
- * An absent `version` is accepted rather than rejected — the same posture the
29
- * rest of this module takes toward absent fields, and the one `media.kind` and
30
- * `matchesRequestedSignLanguage` already take: only a positive claim of
31
- * something else is refused. Producers predate the field, and treating its
32
- * absence as a rejection would drop cards that are otherwise wholly valid.
33
- */
34
- export declare function isUnsupportedMediaAssetVersion(version: unknown): boolean;
35
- export declare function isSafeMediaSrc(raw: unknown): raw is string;
36
- export declare function normalizeMediaSources(raw: unknown): MediaSource[];
37
- export declare function normalizeMediaFragment(raw: unknown): MediaFragmentRange | undefined;
38
- export declare function trimmedOrUndefined(value: unknown): string | undefined;
39
- /**
40
- * Hold a media element to one fragment's range, and return the disposer.
41
- *
42
- * The `#t=` URI `applyMediaFragment` writes is a hint browsers honour at neither
43
- * bound reliably, so every consumer that means "play only this slice" enforces it
44
- * here instead of reimplementing the pair. Both shipped consumers do —
45
- * `SignLanguageMediaRegion` pausing at the end, `TTSService.playRecordedAudio`
46
- * ending the clip so the chunk sequence advances — which is why the end action is
47
- * the caller's and only the arithmetic is shared.
48
- *
49
- * The start seek is forward only: a browser that did honour the URI has already
50
- * positioned past the start, and seeking back would replay audio the learner has
51
- * heard. `onReachedEnd` may fire more than once and must tolerate it; polling
52
- * stops while the element is paused, so an element left sitting past its end bound
53
- * goes quiet rather than being told repeatedly.
54
- */
55
- export declare function enforceMediaFragment(element: HTMLMediaElement, fragment: MediaFragmentRange | undefined, onReachedEnd: () => void): () => void;
56
- export declare function applyMediaFragment(src: string, fragment?: MediaFragmentRange): string;
@@ -1,190 +0,0 @@
1
- /**
2
- * Validation primitives shared by every catalog card whose payload references
3
- * media.
4
- *
5
- * Extracted from `sign-language-cards.ts` when `spoken` cards gained a recorded
6
- * audio form: both card types take authored, wire-facing URLs and hand them to a
7
- * media element in the learner's browser, so both need the same scheme
8
- * allow-list and the same "treat as absent, never as partially valid" posture.
9
- * Two copies of a URL allow-list is one copy that gets a fix and one that does
10
- * not.
11
- *
12
- * Part of PIE Assessment Toolkit.
13
- */
14
- /**
15
- * Media source URLs are handed to a media element in the learner's browser. Only
16
- * schemes such an element can actually fetch are allowed; anything else is
17
- * dropped so an authored `javascript:` / `file:` URL cannot ride into the DOM.
18
- * Relative and protocol-relative URLs are allowed — host content is commonly
19
- * served from the same origin as the player.
20
- */
21
- const DISALLOWED_SRC_SCHEME = /^[a-z][a-z0-9+.-]*:/i;
22
- const ALLOWED_SRC_SCHEMES = new Set(["http:", "https:", "data:", "blob:"]);
23
- /**
24
- * The only `MediaAssetRef.version` this build renders.
25
- *
26
- * `media-asset-contract.md` requires unknown-version rejection for runtime
27
- * rendering: bumping to `2` obliges every consumer to accept both for as long as
28
- * any producer emits `1`, so a card claiming a version this build does not
29
- * implement must not be rendered on a guess at which fields still mean what they
30
- * did.
31
- */
32
- export const SUPPORTED_MEDIA_ASSET_VERSION = 1;
33
- /**
34
- * Whether a media reference claims a version this build cannot render.
35
- *
36
- * An absent `version` is accepted rather than rejected — the same posture the
37
- * rest of this module takes toward absent fields, and the one `media.kind` and
38
- * `matchesRequestedSignLanguage` already take: only a positive claim of
39
- * something else is refused. Producers predate the field, and treating its
40
- * absence as a rejection would drop cards that are otherwise wholly valid.
41
- */
42
- export function isUnsupportedMediaAssetVersion(version) {
43
- return version !== undefined && version !== SUPPORTED_MEDIA_ASSET_VERSION;
44
- }
45
- export function isSafeMediaSrc(raw) {
46
- if (typeof raw !== "string")
47
- return false;
48
- const src = raw.trim();
49
- if (!src)
50
- return false;
51
- // Relative ("/video.mp4", "video.mp4") and protocol-relative ("//cdn/x.mp4")
52
- // forms carry no scheme to check and inherit the document's.
53
- if (src.startsWith("//") || !DISALLOWED_SRC_SCHEME.test(src))
54
- return true;
55
- const scheme = src.slice(0, src.indexOf(":") + 1).toLowerCase();
56
- return ALLOWED_SRC_SCHEMES.has(scheme);
57
- }
58
- export function normalizeMediaSources(raw) {
59
- if (!Array.isArray(raw))
60
- return [];
61
- const sources = [];
62
- for (const entry of raw) {
63
- if (!entry || typeof entry !== "object")
64
- continue;
65
- const candidate = entry;
66
- if (!isSafeMediaSrc(candidate.src))
67
- continue;
68
- const source = { src: candidate.src.trim() };
69
- if (typeof candidate.type === "string" && candidate.type.trim()) {
70
- source.type = candidate.type.trim();
71
- }
72
- if (Number.isFinite(candidate.width))
73
- source.width = candidate.width;
74
- if (Number.isFinite(candidate.height))
75
- source.height = candidate.height;
76
- // Deduplicated by `src`, because the signing region renders `<source>`
77
- // elements in an `{#each}` keyed on exactly that: an authored card listing
78
- // one URL twice — the same file under two MIME types is the plausible way —
79
- // would otherwise throw Svelte's duplicate-key error and take the whole
80
- // region down rather than degrade. The first entry wins, so authored order
81
- // still decides which encoding the browser is offered first.
82
- if (sources.some((existing) => existing.src === source.src))
83
- continue;
84
- sources.push(source);
85
- }
86
- return sources;
87
- }
88
- export function normalizeMediaFragment(raw) {
89
- if (!raw || typeof raw !== "object")
90
- return undefined;
91
- const candidate = raw;
92
- const start = Number(candidate.startSeconds);
93
- if (!Number.isFinite(start) || start < 0)
94
- return undefined;
95
- const end = Number(candidate.endSeconds);
96
- // An end at or before the start would produce a zero/negative slice; treat
97
- // it as "no end" rather than a range that can never play.
98
- if (!Number.isFinite(end) || end <= start)
99
- return { startSeconds: start };
100
- return { startSeconds: start, endSeconds: end };
101
- }
102
- export function trimmedOrUndefined(value) {
103
- if (typeof value !== "string")
104
- return undefined;
105
- const trimmed = value.trim();
106
- return trimmed || undefined;
107
- }
108
- /**
109
- * Apply a fragment range to a source URL as a Media Fragments URI, so one
110
- * recording can serve several content nodes.
111
- *
112
- * The URI is a hint only: browsers honour both bounds inconsistently, so the
113
- * caller enforces the range itself — seek forward to the start once metadata is
114
- * available, and stop at the end. `SignLanguageMediaRegion` and
115
- * `TTSService.playRecordedAudio` are the two shipped consumers that do so.
116
- */
117
- /**
118
- * How often the end bound is re-checked while a slice is playing. `timeupdate`
119
- * alone fires about four times a second, which is loose enough to leak a sliver
120
- * of the next node's recording.
121
- */
122
- const END_CHECK_INTERVAL_MS = 100;
123
- /** `HTMLMediaElement.HAVE_METADATA`, which not every DOM implementation exposes. */
124
- const HAVE_METADATA = 1;
125
- /**
126
- * Hold a media element to one fragment's range, and return the disposer.
127
- *
128
- * The `#t=` URI `applyMediaFragment` writes is a hint browsers honour at neither
129
- * bound reliably, so every consumer that means "play only this slice" enforces it
130
- * here instead of reimplementing the pair. Both shipped consumers do —
131
- * `SignLanguageMediaRegion` pausing at the end, `TTSService.playRecordedAudio`
132
- * ending the clip so the chunk sequence advances — which is why the end action is
133
- * the caller's and only the arithmetic is shared.
134
- *
135
- * The start seek is forward only: a browser that did honour the URI has already
136
- * positioned past the start, and seeking back would replay audio the learner has
137
- * heard. `onReachedEnd` may fire more than once and must tolerate it; polling
138
- * stops while the element is paused, so an element left sitting past its end bound
139
- * goes quiet rather than being told repeatedly.
140
- */
141
- export function enforceMediaFragment(element, fragment, onReachedEnd) {
142
- if (!fragment)
143
- return () => { };
144
- const { startSeconds, endSeconds } = fragment;
145
- let seekToStart;
146
- if (startSeconds > 0) {
147
- seekToStart = () => {
148
- if (element.currentTime < startSeconds) {
149
- element.currentTime = startSeconds;
150
- }
151
- };
152
- // Seeking before metadata is available only sets the default start
153
- // position, so wait for it unless it already landed.
154
- if (element.readyState >= HAVE_METADATA)
155
- seekToStart();
156
- else
157
- element.addEventListener("loadedmetadata", seekToStart);
158
- }
159
- let checkEnd;
160
- let endPoll;
161
- if (endSeconds !== undefined) {
162
- checkEnd = () => {
163
- if (element.currentTime >= endSeconds)
164
- onReachedEnd();
165
- };
166
- element.addEventListener("timeupdate", checkEnd);
167
- endPoll = setInterval(() => {
168
- if (!element.paused)
169
- checkEnd?.();
170
- }, END_CHECK_INTERVAL_MS);
171
- }
172
- return () => {
173
- if (seekToStart)
174
- element.removeEventListener("loadedmetadata", seekToStart);
175
- if (checkEnd)
176
- element.removeEventListener("timeupdate", checkEnd);
177
- if (endPoll !== undefined)
178
- clearInterval(endPoll);
179
- };
180
- }
181
- export function applyMediaFragment(src, fragment) {
182
- if (!fragment)
183
- return src;
184
- // Never stack a second fragment onto a URL that already carries one — the
185
- // authored value wins.
186
- if (src.includes("#"))
187
- return src;
188
- const end = fragment.endSeconds !== undefined ? `,${fragment.endSeconds}` : "";
189
- return `${src}#t=${fragment.startSeconds}${end}`;
190
- }
@@ -1,53 +0,0 @@
1
- /**
2
- * Tool Registry Factory
3
- *
4
- * Creates an empty `ToolRegistry` and installs whatever component overrides and
5
- * lazy module loaders the caller supplies.
6
- *
7
- * It registers nothing by itself, deliberately. Eleven concrete registrations
8
- * used to live in this package, which made the policy and registry core know
9
- * every capability by name and left a host unable to contribute one without a PR
10
- * against this package. The packaged set now lives in the composition layer:
11
- * `createPackagedToolRegistry` from `@pie-players/pie-default-tool-loaders`.
12
- */
13
- import type { ToolRegistration, ToolModuleLoader } from "./ToolRegistry.js";
14
- import { ToolRegistry } from "./ToolRegistry.js";
15
- import type { ToolComponentFactory, ToolComponentFactoryMap, ToolTagMap } from "../tools/tool-tag-map.js";
16
- import { DEFAULT_TOOL_PLACEMENT } from "./tool-config-defaults.js";
17
- export interface DefaultToolRegistryOptions {
18
- /**
19
- * Registrations to register, keyed by toolId. Nothing is registered when this
20
- * is absent.
21
- */
22
- registrations?: Partial<Record<string, ToolRegistration>>;
23
- /**
24
- * Element tag mapping for the registrations being installed. There is no
25
- * built-in default: a tag map names capabilities, so it belongs to whoever
26
- * decides which capabilities exist.
27
- */
28
- toolTagMap?: Partial<ToolTagMap>;
29
- /**
30
- * Override component creation globally (all tools) or per tool.
31
- */
32
- toolComponentFactory?: ToolComponentFactory;
33
- toolComponentFactories?: Partial<ToolComponentFactoryMap>;
34
- /**
35
- * Optional lazy module loaders keyed by toolId.
36
- */
37
- toolModuleLoaders?: Partial<Record<string, ToolModuleLoader>>;
38
- }
39
- /**
40
- * Create a tool registry.
41
- *
42
- * @returns an empty `ToolRegistry` unless `registrations` is supplied
43
- */
44
- export declare function createDefaultToolRegistry(options?: DefaultToolRegistryOptions): ToolRegistry;
45
- /**
46
- * Placement configuration with every level empty.
47
- *
48
- * The only placement default this package can hold: a populated one would name
49
- * capabilities. `PACKAGED_TOOL_PLACEMENT` and
50
- * `SECTION_PLAYER_PREFERRED_TOOL_PLACEMENT` are in
51
- * `@pie-players/pie-default-tool-loaders`.
52
- */
53
- export { DEFAULT_TOOL_PLACEMENT };