@pie-players/pie-assessment-toolkit 0.2.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.
Files changed (194) hide show
  1. package/README.md +520 -0
  2. package/dist/attempt/TestSession.d.ts +90 -0
  3. package/dist/attempt/TestSession.d.ts.map +1 -0
  4. package/dist/attempt/TestSession.js +174 -0
  5. package/dist/attempt/TestSession.js.map +1 -0
  6. package/dist/core/TypedEventBus.d.ts +48 -0
  7. package/dist/core/TypedEventBus.d.ts.map +1 -0
  8. package/dist/core/TypedEventBus.js +63 -0
  9. package/dist/core/TypedEventBus.js.map +1 -0
  10. package/dist/index.d.ts +37 -0
  11. package/dist/index.d.ts.map +1 -0
  12. package/dist/index.js +44 -0
  13. package/dist/index.js.map +1 -0
  14. package/dist/item-loader.d.ts +45 -0
  15. package/dist/item-loader.d.ts.map +1 -0
  16. package/dist/item-loader.js +62 -0
  17. package/dist/item-loader.js.map +1 -0
  18. package/dist/player/AssessmentPlayer.d.ts +388 -0
  19. package/dist/player/AssessmentPlayer.d.ts.map +1 -0
  20. package/dist/player/AssessmentPlayer.js +965 -0
  21. package/dist/player/AssessmentPlayer.js.map +1 -0
  22. package/dist/player/index.d.ts +17 -0
  23. package/dist/player/index.d.ts.map +1 -0
  24. package/dist/player/index.js +15 -0
  25. package/dist/player/index.js.map +1 -0
  26. package/dist/player/navigation-types.d.ts +63 -0
  27. package/dist/player/navigation-types.d.ts.map +1 -0
  28. package/dist/player/navigation-types.js +7 -0
  29. package/dist/player/navigation-types.js.map +1 -0
  30. package/dist/player/qti-navigation.d.ts +29 -0
  31. package/dist/player/qti-navigation.d.ts.map +1 -0
  32. package/dist/player/qti-navigation.js +160 -0
  33. package/dist/player/qti-navigation.js.map +1 -0
  34. package/dist/reference-layout/index.d.ts +19 -0
  35. package/dist/reference-layout/index.d.ts.map +1 -0
  36. package/dist/reference-layout/index.js +20 -0
  37. package/dist/reference-layout/index.js.map +1 -0
  38. package/dist/services/AccessibilityCatalogResolver.d.ts +141 -0
  39. package/dist/services/AccessibilityCatalogResolver.d.ts.map +1 -0
  40. package/dist/services/AccessibilityCatalogResolver.js +249 -0
  41. package/dist/services/AccessibilityCatalogResolver.js.map +1 -0
  42. package/dist/services/AssessmentAuthoringService.d.ts +60 -0
  43. package/dist/services/AssessmentAuthoringService.d.ts.map +1 -0
  44. package/dist/services/AssessmentAuthoringService.js +183 -0
  45. package/dist/services/AssessmentAuthoringService.js.map +1 -0
  46. package/dist/services/ContextVariableStore.d.ts +114 -0
  47. package/dist/services/ContextVariableStore.d.ts.map +1 -0
  48. package/dist/services/ContextVariableStore.js +207 -0
  49. package/dist/services/ContextVariableStore.js.map +1 -0
  50. package/dist/services/ElementToolStateStore.d.ts +125 -0
  51. package/dist/services/ElementToolStateStore.d.ts.map +1 -0
  52. package/dist/services/ElementToolStateStore.js +200 -0
  53. package/dist/services/ElementToolStateStore.js.map +1 -0
  54. package/dist/services/HighlightCoordinator.d.ts +179 -0
  55. package/dist/services/HighlightCoordinator.d.ts.map +1 -0
  56. package/dist/services/HighlightCoordinator.js +446 -0
  57. package/dist/services/HighlightCoordinator.js.map +1 -0
  58. package/dist/services/I18nService.d.ts +121 -0
  59. package/dist/services/I18nService.d.ts.map +1 -0
  60. package/dist/services/I18nService.js +276 -0
  61. package/dist/services/I18nService.js.map +1 -0
  62. package/dist/services/PNPMapper.d.ts +71 -0
  63. package/dist/services/PNPMapper.d.ts.map +1 -0
  64. package/dist/services/PNPMapper.js +98 -0
  65. package/dist/services/PNPMapper.js.map +1 -0
  66. package/dist/services/PNPToolResolver.d.ts +127 -0
  67. package/dist/services/PNPToolResolver.d.ts.map +1 -0
  68. package/dist/services/PNPToolResolver.js +222 -0
  69. package/dist/services/PNPToolResolver.js.map +1 -0
  70. package/dist/services/SSMLExtractor.d.ts +61 -0
  71. package/dist/services/SSMLExtractor.d.ts.map +1 -0
  72. package/dist/services/SSMLExtractor.js +181 -0
  73. package/dist/services/SSMLExtractor.js.map +1 -0
  74. package/dist/services/TTSService.d.ts +162 -0
  75. package/dist/services/TTSService.d.ts.map +1 -0
  76. package/dist/services/TTSService.js +465 -0
  77. package/dist/services/TTSService.js.map +1 -0
  78. package/dist/services/ThemeProvider.d.ts +95 -0
  79. package/dist/services/ThemeProvider.d.ts.map +1 -0
  80. package/dist/services/ThemeProvider.js +299 -0
  81. package/dist/services/ThemeProvider.js.map +1 -0
  82. package/dist/services/ToolConfigResolver.d.ts +162 -0
  83. package/dist/services/ToolConfigResolver.d.ts.map +1 -0
  84. package/dist/services/ToolConfigResolver.js +230 -0
  85. package/dist/services/ToolConfigResolver.js.map +1 -0
  86. package/dist/services/ToolCoordinator.d.ts +137 -0
  87. package/dist/services/ToolCoordinator.d.ts.map +1 -0
  88. package/dist/services/ToolCoordinator.js +318 -0
  89. package/dist/services/ToolCoordinator.js.map +1 -0
  90. package/dist/services/ToolkitCoordinator.d.ts +188 -0
  91. package/dist/services/ToolkitCoordinator.d.ts.map +1 -0
  92. package/dist/services/ToolkitCoordinator.js +252 -0
  93. package/dist/services/ToolkitCoordinator.js.map +1 -0
  94. package/dist/services/interfaces.d.ts +391 -0
  95. package/dist/services/interfaces.d.ts.map +1 -0
  96. package/dist/services/interfaces.js +12 -0
  97. package/dist/services/interfaces.js.map +1 -0
  98. package/dist/services/tool-providers/DesmosToolProvider.d.ts +103 -0
  99. package/dist/services/tool-providers/DesmosToolProvider.d.ts.map +1 -0
  100. package/dist/services/tool-providers/DesmosToolProvider.js +127 -0
  101. package/dist/services/tool-providers/DesmosToolProvider.js.map +1 -0
  102. package/dist/services/tool-providers/IToolProvider.d.ts +137 -0
  103. package/dist/services/tool-providers/IToolProvider.d.ts.map +1 -0
  104. package/dist/services/tool-providers/IToolProvider.js +13 -0
  105. package/dist/services/tool-providers/IToolProvider.js.map +1 -0
  106. package/dist/services/tool-providers/TIToolProvider.d.ts +107 -0
  107. package/dist/services/tool-providers/TIToolProvider.d.ts.map +1 -0
  108. package/dist/services/tool-providers/TIToolProvider.js +123 -0
  109. package/dist/services/tool-providers/TIToolProvider.js.map +1 -0
  110. package/dist/services/tool-providers/TTSToolProvider.d.ts +143 -0
  111. package/dist/services/tool-providers/TTSToolProvider.d.ts.map +1 -0
  112. package/dist/services/tool-providers/TTSToolProvider.js +168 -0
  113. package/dist/services/tool-providers/TTSToolProvider.js.map +1 -0
  114. package/dist/services/tool-providers/ToolProviderRegistry.d.ts +165 -0
  115. package/dist/services/tool-providers/ToolProviderRegistry.d.ts.map +1 -0
  116. package/dist/services/tool-providers/ToolProviderRegistry.js +240 -0
  117. package/dist/services/tool-providers/ToolProviderRegistry.js.map +1 -0
  118. package/dist/services/tool-providers/index.d.ts +18 -0
  119. package/dist/services/tool-providers/index.d.ts.map +1 -0
  120. package/dist/services/tool-providers/index.js +15 -0
  121. package/dist/services/tool-providers/index.js.map +1 -0
  122. package/dist/services/tts/browser-provider.d.ts +24 -0
  123. package/dist/services/tts/browser-provider.d.ts.map +1 -0
  124. package/dist/services/tts/browser-provider.js +177 -0
  125. package/dist/services/tts/browser-provider.js.map +1 -0
  126. package/dist/services/tts/provider-interface.d.ts +131 -0
  127. package/dist/services/tts/provider-interface.d.ts.map +1 -0
  128. package/dist/services/tts/provider-interface.js +10 -0
  129. package/dist/services/tts/provider-interface.js.map +1 -0
  130. package/dist/tools/calculators/desmos-provider.d.ts +54 -0
  131. package/dist/tools/calculators/desmos-provider.d.ts.map +1 -0
  132. package/dist/tools/calculators/desmos-provider.js +384 -0
  133. package/dist/tools/calculators/desmos-provider.js.map +1 -0
  134. package/dist/tools/calculators/mathjs-provider.d.ts +50 -0
  135. package/dist/tools/calculators/mathjs-provider.d.ts.map +1 -0
  136. package/dist/tools/calculators/mathjs-provider.js +660 -0
  137. package/dist/tools/calculators/mathjs-provider.js.map +1 -0
  138. package/dist/tools/calculators/ti-provider.d.ts +122 -0
  139. package/dist/tools/calculators/ti-provider.d.ts.map +1 -0
  140. package/dist/tools/calculators/ti-provider.js +587 -0
  141. package/dist/tools/calculators/ti-provider.js.map +1 -0
  142. package/dist/tools/client.d.ts +17 -0
  143. package/dist/tools/client.d.ts.map +1 -0
  144. package/dist/tools/client.js +26 -0
  145. package/dist/tools/client.js.map +1 -0
  146. package/dist/tools/index.d.ts +8 -0
  147. package/dist/tools/index.d.ts.map +1 -0
  148. package/dist/tools/index.js +10 -0
  149. package/dist/tools/index.js.map +1 -0
  150. package/dist/tools/library-loader.d.ts +63 -0
  151. package/dist/tools/library-loader.d.ts.map +1 -0
  152. package/dist/tools/library-loader.js +292 -0
  153. package/dist/tools/library-loader.js.map +1 -0
  154. package/dist/tools/response-discovery.d.ts +72 -0
  155. package/dist/tools/response-discovery.d.ts.map +1 -0
  156. package/dist/tools/response-discovery.js +183 -0
  157. package/dist/tools/response-discovery.js.map +1 -0
  158. package/dist/tools/tool-coordinator.d.ts +76 -0
  159. package/dist/tools/tool-coordinator.d.ts.map +1 -0
  160. package/dist/tools/tool-coordinator.js +197 -0
  161. package/dist/tools/tool-coordinator.js.map +1 -0
  162. package/dist/tools/types.d.ts +427 -0
  163. package/dist/tools/types.d.ts.map +1 -0
  164. package/dist/tools/types.js +13 -0
  165. package/dist/tools/types.js.map +1 -0
  166. package/dist/tools/variant-resolver.d.ts +48 -0
  167. package/dist/tools/variant-resolver.d.ts.map +1 -0
  168. package/dist/tools/variant-resolver.js +214 -0
  169. package/dist/tools/variant-resolver.js.map +1 -0
  170. package/dist/types/events.d.ts +157 -0
  171. package/dist/types/events.d.ts.map +1 -0
  172. package/dist/types/events.js +11 -0
  173. package/dist/types/events.js.map +1 -0
  174. package/dist/utils/logger.d.ts +7 -0
  175. package/dist/utils/logger.d.ts.map +1 -0
  176. package/dist/utils/logger.js +11 -0
  177. package/dist/utils/logger.js.map +1 -0
  178. package/package.json +71 -0
  179. package/src/README.md +626 -0
  180. package/src/components/QuestionToolBar.svelte +406 -0
  181. package/src/player/AssessmentLayout.svelte +68 -0
  182. package/src/player/README.md +154 -0
  183. package/src/reference-layout/README.md +135 -0
  184. package/src/reference-layout/ReferenceLayout.svelte +209 -0
  185. package/src/reference-layout/components/AssessmentContent.svelte +259 -0
  186. package/src/reference-layout/components/AssessmentFooter.svelte +89 -0
  187. package/src/reference-layout/components/AssessmentHeader.svelte +250 -0
  188. package/src/reference-layout/components/AssessmentNavigation.svelte +134 -0
  189. package/src/reference-layout/components/AssessmentToolsBar.svelte +176 -0
  190. package/src/reference-layout/components/ItemPanel.svelte +200 -0
  191. package/src/reference-layout/components/NotesPanel.svelte +179 -0
  192. package/src/reference-layout/components/PassagePanel.svelte +76 -0
  193. package/src/tools/README.md +616 -0
  194. package/src/tools/calculators/README.md +395 -0
@@ -0,0 +1,406 @@
1
+ <svelte:options
2
+ customElement={{
3
+ tag: 'pie-question-toolbar',
4
+ shadow: 'none',
5
+ props: {
6
+ itemId: { type: 'String', attribute: 'item-id' },
7
+ catalogId: { type: 'String', attribute: 'catalog-id' },
8
+ tools: { type: 'String', attribute: 'tools' },
9
+ size: { type: 'String', attribute: 'size' },
10
+ language: { type: 'String', attribute: 'language' },
11
+
12
+ // Services (passed as JS properties, not attributes)
13
+ ttsService: { type: 'Object', reflect: false },
14
+ toolCoordinator: { type: 'Object', reflect: false },
15
+ highlightCoordinator: { type: 'Object', reflect: false },
16
+ scopeElement: { type: 'Object', reflect: false },
17
+ elementToolStateStore: { type: 'Object', reflect: false },
18
+
19
+ // IDs for element-level state (passed as JS properties, not attributes)
20
+ assessmentId: { type: 'String', reflect: false },
21
+ sectionId: { type: 'String', reflect: false }
22
+ }
23
+ }}
24
+ />
25
+
26
+ <!--
27
+ QuestionToolBar - Inline toolbar for question/passage headers
28
+
29
+ Displays tool buttons (TTS, Answer Eliminator, etc.) in a compact,
30
+ horizontal layout suitable for embedding in question headers.
31
+
32
+ Similar to the SchoolCity pattern where tool buttons appear next to
33
+ question titles rather than in a global sidebar.
34
+ -->
35
+ <script lang="ts">
36
+ import type {
37
+ IToolCoordinator,
38
+ ITTSService,
39
+ IHighlightCoordinator,
40
+ } from '../services/interfaces';
41
+
42
+ // NOTE: Tool web components must be imported by parent components
43
+ // that use QuestionToolBar (to avoid circular dependencies).
44
+ // Parent components should:
45
+ // import '@pie-players/pie-tool-answer-eliminator';
46
+ // import '@pie-players/pie-tool-tts-inline';
47
+ // import '@pie-players/pie-tool-calculator-inline';
48
+
49
+ const isBrowser = typeof window !== 'undefined';
50
+
51
+ // Track if tools have been loaded
52
+ let toolsLoaded = $state(false);
53
+
54
+ /**
55
+ * Detect if the scoped element contains compatible answer choices for the answer eliminator.
56
+ * Checks for PIE multiple-choice, EBSR, and inline-dropdown elements.
57
+ */
58
+ function hasCompatibleAnswerChoices(element: HTMLElement): boolean {
59
+ // Check for multiple-choice elements
60
+ const hasMultipleChoice = element.querySelector('.corespring-checkbox, .corespring-radio-button, .multiple-choice');
61
+ if (hasMultipleChoice) return true;
62
+
63
+ // Check for EBSR parts (extended constructed response with selectable parts)
64
+ const hasEBSR = element.querySelector('[class*="ebsr"]');
65
+ if (hasEBSR) return true;
66
+
67
+ // Check for inline-dropdown elements
68
+ const hasInlineDropdown = element.querySelector('inline-dropdown, .inline-dropdown, [class*="inline-dropdown"]');
69
+ if (hasInlineDropdown) return true;
70
+
71
+ return false;
72
+ }
73
+
74
+ // Props
75
+ let {
76
+ itemId = '',
77
+ catalogId = '',
78
+ tools = 'calculator,tts,answerEliminator',
79
+ ttsService,
80
+ toolCoordinator,
81
+ highlightCoordinator,
82
+ scopeElement = null,
83
+ elementToolStateStore,
84
+ assessmentId = '',
85
+ sectionId = '',
86
+ class: className = '',
87
+ size = 'md' as 'sm' | 'md' | 'lg',
88
+ language = 'en-US'
89
+ }: {
90
+ itemId?: string;
91
+ catalogId?: string;
92
+ tools?: string;
93
+ ttsService?: ITTSService;
94
+ toolCoordinator?: IToolCoordinator;
95
+ highlightCoordinator?: IHighlightCoordinator;
96
+ scopeElement?: HTMLElement | null;
97
+ elementToolStateStore?: any;
98
+ assessmentId?: string;
99
+ sectionId?: string;
100
+ class?: string;
101
+ size?: 'sm' | 'md' | 'lg';
102
+ language?: string;
103
+ } = $props();
104
+
105
+ // Generate globalElementId using store utility
106
+ let globalElementId = $derived.by(() => {
107
+ if (!elementToolStateStore || !assessmentId || !sectionId || !itemId) return null;
108
+ return elementToolStateStore.getGlobalElementId(assessmentId, sectionId, itemId, itemId);
109
+ // Note: Using itemId as elementId since toolbar is scoped to one item/element
110
+ });
111
+
112
+ // Parse enabled tools
113
+ let enabledTools = $derived(
114
+ tools
115
+ .split(',')
116
+ .map((t) => t.trim())
117
+ .filter(Boolean),
118
+ );
119
+
120
+ // Detect if current item has compatible choices for answer eliminator
121
+ let hasCompatibleChoices = $state(false);
122
+
123
+ $effect(() => {
124
+ if (!isBrowser || !scopeElement) {
125
+ hasCompatibleChoices = false;
126
+ return;
127
+ }
128
+
129
+ // Use MutationObserver to detect when PIE elements are rendered
130
+ const checkChoices = () => {
131
+ const hasChoices = hasCompatibleAnswerChoices(scopeElement);
132
+ hasCompatibleChoices = hasChoices;
133
+ console.debug('[QuestionToolBar] Checked choices:', {
134
+ scopeElementTag: scopeElement.tagName,
135
+ hasCompatibleChoices
136
+ });
137
+ };
138
+
139
+ // Initial check
140
+ checkChoices();
141
+
142
+ // Watch for DOM changes (PIE elements load asynchronously)
143
+ const observer = new MutationObserver(() => {
144
+ checkChoices();
145
+ });
146
+
147
+ observer.observe(scopeElement, {
148
+ childList: true,
149
+ subtree: true
150
+ });
151
+
152
+ return () => observer.disconnect();
153
+ });
154
+
155
+ // Dynamically load tool components based on enabled tools
156
+ $effect(() => {
157
+ if (!isBrowser || toolsLoaded) return;
158
+
159
+ (async () => {
160
+ const loadPromises = [];
161
+
162
+ if (enabledTools.includes('tts')) {
163
+ loadPromises.push(import('@pie-players/pie-tool-tts-inline'));
164
+ }
165
+ if (enabledTools.includes('answerEliminator')) {
166
+ loadPromises.push(import('@pie-players/pie-tool-answer-eliminator'));
167
+ }
168
+ if (enabledTools.includes('calculator')) {
169
+ loadPromises.push(
170
+ import('@pie-players/pie-tool-calculator-inline'),
171
+ import('@pie-players/pie-tool-calculator')
172
+ );
173
+ }
174
+
175
+ await Promise.all(loadPromises);
176
+ toolsLoaded = true;
177
+ })();
178
+ });
179
+
180
+ // Should show each tool
181
+ let showTTS = $derived(enabledTools.includes('tts') && ttsService && toolsLoaded);
182
+ let showAnswerEliminator = $derived(
183
+ enabledTools.includes('answerEliminator') &&
184
+ toolCoordinator &&
185
+ hasCompatibleChoices &&
186
+ toolsLoaded
187
+ );
188
+ let showCalculator = $derived(enabledTools.includes('calculator') && toolCoordinator && toolsLoaded);
189
+
190
+ // Track answer eliminator visibility state
191
+ let answerEliminatorVisible = $state(false);
192
+
193
+ // Subscribe to tool coordinator to update button state
194
+ $effect(() => {
195
+ if (!isBrowser || !toolCoordinator) return;
196
+
197
+ const unsubscribe = toolCoordinator.subscribe(() => {
198
+ answerEliminatorVisible = toolCoordinator.isToolVisible(
199
+ `answerEliminator-${itemId}`,
200
+ );
201
+ });
202
+
203
+ // Initial update
204
+ answerEliminatorVisible = toolCoordinator.isToolVisible(
205
+ `answerEliminator-${itemId}`,
206
+ );
207
+
208
+ return unsubscribe;
209
+ });
210
+
211
+ // TTS element reference for service binding
212
+ let ttsToolElement = $state<HTMLElement | null>(null);
213
+ let ttsBound = $state(false);
214
+
215
+ // Bind services to TTS tool
216
+ $effect(() => {
217
+ if (ttsToolElement && !ttsBound) {
218
+ if (toolCoordinator) {
219
+ (ttsToolElement as any).coordinator = toolCoordinator;
220
+ }
221
+ if (ttsService) {
222
+ (ttsToolElement as any).ttsService = ttsService;
223
+ }
224
+ if (highlightCoordinator) {
225
+ (ttsToolElement as any).highlightCoordinator = highlightCoordinator;
226
+ }
227
+ ttsBound = true;
228
+ }
229
+ });
230
+
231
+ // Answer eliminator element reference for scope binding
232
+ let answerEliminatorElement = $state<HTMLElement | null>(null);
233
+ let eliminatorBound = $state(false);
234
+
235
+ // Bind scope, coordinator, store, and globalElementId to answer eliminator
236
+ $effect(() => {
237
+ if (answerEliminatorElement && !eliminatorBound) {
238
+ if (toolCoordinator) {
239
+ (answerEliminatorElement as any).coordinator = toolCoordinator;
240
+ }
241
+ if (scopeElement) {
242
+ (answerEliminatorElement as any).scopeElement = scopeElement;
243
+ }
244
+ if (elementToolStateStore && globalElementId) {
245
+ (answerEliminatorElement as any).elementToolStateStore = elementToolStateStore;
246
+ (answerEliminatorElement as any).globalElementId = globalElementId;
247
+ }
248
+ eliminatorBound = true;
249
+ }
250
+ });
251
+
252
+ // Handle answer eliminator toggle
253
+ function toggleAnswerEliminator() {
254
+ if (!toolCoordinator) return;
255
+ toolCoordinator.toggleTool(`answerEliminator-${itemId}`);
256
+ }
257
+
258
+ // Calculator element reference for coordinator binding
259
+ let calculatorInlineElement = $state<HTMLElement | null>(null);
260
+ let calculatorBound = $state(false);
261
+
262
+ // Bind coordinator to calculator inline tool
263
+ $effect(() => {
264
+ if (calculatorInlineElement && !calculatorBound) {
265
+ if (toolCoordinator) {
266
+ (calculatorInlineElement as any).coordinator = toolCoordinator;
267
+ }
268
+ calculatorBound = true;
269
+ }
270
+ });
271
+ </script>
272
+
273
+ {#if isBrowser}
274
+ <div class="question-toolbar {className} question-toolbar--{size}">
275
+ <!-- Calculator Button (inline tool) -->
276
+ {#if showCalculator}
277
+ <pie-tool-calculator-inline
278
+ bind:this={calculatorInlineElement}
279
+ tool-id="calculator-inline-{itemId}"
280
+ calculator-type="scientific"
281
+ available-types="basic,scientific,graphing"
282
+ size={size}
283
+ ></pie-tool-calculator-inline>
284
+ {/if}
285
+
286
+ <!-- TTS Button (inline tool) -->
287
+ {#if showTTS}
288
+ <pie-tool-tts-inline
289
+ bind:this={ttsToolElement}
290
+ tool-id="tts-{itemId}"
291
+ catalog-id={catalogId || itemId}
292
+ language={language}
293
+ size={size}
294
+ ></pie-tool-tts-inline>
295
+ {/if}
296
+
297
+ <!-- Answer Eliminator Toggle Button -->
298
+ {#if showAnswerEliminator}
299
+ <button
300
+ type="button"
301
+ class="question-toolbar__button"
302
+ class:question-toolbar__button--active={answerEliminatorVisible}
303
+ onclick={toggleAnswerEliminator}
304
+ aria-label="Answer Eliminator"
305
+ aria-pressed={answerEliminatorVisible}
306
+ title="Toggle answer elimination mode"
307
+ >
308
+ <!-- X in Circle Icon (matches SchoolCity) -->
309
+ <svg
310
+ xmlns="http://www.w3.org/2000/svg"
311
+ viewBox="0 0 24 24"
312
+ width="20"
313
+ height="20"
314
+ fill="currentColor"
315
+ aria-hidden="true"
316
+ >
317
+ <path
318
+ d="M19,3H16.3H7.7H5A2,2 0 0,0 3,5V7.7V16.4V19A2,2 0 0,0 5,21H7.7H16.4H19A2,2 0 0,0 21,19V16.3V7.7V5A2,2 0 0,0 19,3M15.6,17L12,13.4L8.4,17L7,15.6L10.6,12L7,8.4L8.4,7L12,10.6L15.6,7L17,8.4L13.4,12L17,15.6L15.6,17Z"
319
+ />
320
+ </svg>
321
+ </button>
322
+
323
+ <!-- Answer Eliminator Tool Instance (hidden, manages state) -->
324
+ <pie-tool-answer-eliminator
325
+ bind:this={answerEliminatorElement}
326
+ visible={answerEliminatorVisible}
327
+ tool-id="answerEliminator-{itemId}"
328
+ strategy="strikethrough"
329
+ button-alignment="inline"
330
+ coordinator={toolCoordinator}
331
+ ></pie-tool-answer-eliminator>
332
+ {/if}
333
+ </div>
334
+ {/if}
335
+
336
+ <style>
337
+ .question-toolbar {
338
+ display: flex;
339
+ align-items: center;
340
+ gap: 0.5rem;
341
+ }
342
+
343
+ .question-toolbar__button {
344
+ display: flex;
345
+ align-items: center;
346
+ justify-content: center;
347
+ width: 2rem;
348
+ height: 2rem;
349
+ padding: 0.25rem;
350
+ border: 1px solid var(--pie-border, #ccc);
351
+ border-radius: 0.25rem;
352
+ background-color: var(--pie-background, white);
353
+ color: var(--pie-text, #333);
354
+ cursor: pointer;
355
+ transition: all 0.15s ease;
356
+ }
357
+
358
+ .question-toolbar--sm .question-toolbar__button {
359
+ width: 1.5rem;
360
+ height: 1.5rem;
361
+ }
362
+
363
+ .question-toolbar--lg .question-toolbar__button {
364
+ width: 2.5rem;
365
+ height: 2.5rem;
366
+ }
367
+
368
+ .question-toolbar__button:hover:not(:disabled) {
369
+ background-color: var(
370
+ --pie-secondary-background,
371
+ #f5f5f5
372
+ );
373
+ transform: translateY(-1px);
374
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
375
+ }
376
+
377
+ .question-toolbar__button:active:not(:disabled) {
378
+ transform: translateY(0);
379
+ box-shadow: none;
380
+ }
381
+
382
+ .question-toolbar__button--active {
383
+ background-color: var(--pie-primary, #1976d2);
384
+ color: white;
385
+ border-color: var(--pie-primary, #1976d2);
386
+ }
387
+
388
+ .question-toolbar__button--active:hover:not(:disabled) {
389
+ background-color: var(--pie-primary-dark, #1565c0);
390
+ }
391
+
392
+ .question-toolbar__button:focus-visible {
393
+ outline: 2px solid var(--pie-primary, #1976d2);
394
+ outline-offset: 2px;
395
+ }
396
+
397
+ .question-toolbar__button:disabled {
398
+ opacity: 0.5;
399
+ cursor: not-allowed;
400
+ }
401
+
402
+ .question-toolbar__button svg {
403
+ width: 100%;
404
+ height: 100%;
405
+ }
406
+ </style>
@@ -0,0 +1,68 @@
1
+ <script lang="ts">
2
+ import type { AssessmentEntity } from '@pie-players/pie-players-shared';
3
+ import { onDestroy, onMount } from 'svelte';
4
+ import { ReferenceLayout } from '../reference-layout';
5
+ import { AssessmentPlayer } from './AssessmentPlayer';
6
+
7
+ let {
8
+ assessment,
9
+ bundleHost,
10
+ organizationId,
11
+ userName
12
+ }: {
13
+ assessment: AssessmentEntity;
14
+ bundleHost?: string;
15
+ organizationId?: string | null;
16
+ userName?: string;
17
+ } = $props();
18
+
19
+ let player = $state<AssessmentPlayer | null>(null);
20
+
21
+ onMount(() => {
22
+ // Initialize reference player
23
+ player = new AssessmentPlayer({
24
+ assessment,
25
+ organizationId: organizationId || undefined,
26
+ bundleHost,
27
+ mode: 'gather',
28
+ role: 'student',
29
+ enableTTS: true,
30
+ theme: {
31
+ highContrast: false,
32
+ fontSize: 'normal'
33
+ }
34
+ });
35
+
36
+ // Start at first item
37
+ player.start();
38
+ });
39
+
40
+ onDestroy(() => {
41
+ player?.destroy();
42
+ });
43
+ </script>
44
+
45
+ {#if player}
46
+ <ReferenceLayout {player} {assessment} {bundleHost} {organizationId} {userName} />
47
+ {:else}
48
+ <div class="loading-container">
49
+ <span class="loading loading-spinner loading-lg"></span>
50
+ <p>Loading assessment...</p>
51
+ </div>
52
+ {/if}
53
+
54
+ <style>
55
+ .loading-container {
56
+ display: flex;
57
+ flex-direction: column;
58
+ align-items: center;
59
+ justify-content: center;
60
+ height: 100vh;
61
+ gap: 1rem;
62
+ }
63
+
64
+ .loading-container p {
65
+ color: var(--pie-text-secondary, #666);
66
+ }
67
+ </style>
68
+
@@ -0,0 +1,154 @@
1
+ # PIE Assessment Toolkit - Assessment Player
2
+
3
+ This directory contains an **optional assessment player implementation** showing how to wire toolkit services together.
4
+
5
+ ## Purpose
6
+
7
+ - **Learning**: See how services work together
8
+ - **Quick Start**: Products can use as-is or extend
9
+ - **Customization**: Extend or copy patterns
10
+
11
+ Products like Quiz Engine can ignore this and use toolkit services directly.
12
+
13
+ ## What's Included
14
+
15
+ - **AssessmentPlayer** - Core player class with navigation and state management
16
+ - **ReferenceAssessmentLayout** - Complete assessment UI using reference layout
17
+ - Linear navigation (sequential items)
18
+ - Basic session state management
19
+ - Event wiring examples
20
+
21
+ ## Architecture
22
+
23
+ The default implementation consists of two parts:
24
+
25
+ 1. **AssessmentPlayer** (`AssessmentPlayer.ts`)
26
+ - Core business logic
27
+ - Navigation and state management
28
+ - Integration with assessment toolkit services
29
+ - Framework-agnostic (plain TypeScript class)
30
+
31
+ 2. **ReferenceAssessmentLayout** (TBD)
32
+ - Visual presentation layer
33
+ - Uses `ReferenceLayout` from `../reference-layout`
34
+ - Wires AssessmentPlayer to visual components
35
+ - Demonstrates complete assessment UI
36
+
37
+ This separation allows:
38
+ - **AssessmentPlayer** to be used with any layout
39
+ - **ReferenceLayout** to be used independently or with other players
40
+ - Products to mix and match components as needed
41
+
42
+ ## What's Not Included (Product-Specific)
43
+
44
+ - Backend integration (each product different)
45
+ - Adaptive navigation algorithms
46
+ - Branching logic
47
+ - Scoring/grading
48
+ - LTI integration
49
+ - Authentication/authorization
50
+
51
+ ## Usage
52
+
53
+ ### Using ReferenceAssessmentLayout (Recommended)
54
+
55
+ The simplest way to get a complete assessment UI:
56
+
57
+ ```svelte
58
+ <script>
59
+ import { ReferenceAssessmentLayout } from '@pie-framework/pie-assessment-toolkit/assessment-player/Layout.svelte';
60
+ </script>
61
+
62
+ <ReferenceAssessmentLayout
63
+ {assessment}
64
+ {organizationId}
65
+ bundleHost="prod"
66
+ />
67
+ ```
68
+
69
+ ### Using AssessmentPlayer Directly
70
+
71
+ If you want to use your own layout:
72
+
73
+ ```typescript
74
+ import { AssessmentPlayer } from '@pie-framework/pie-assessment-toolkit/assessment-player';
75
+
76
+ const player = new AssessmentPlayer({
77
+ assessment,
78
+ organizationId,
79
+ bundleHost,
80
+ mode: 'gather',
81
+ role: 'student',
82
+ // ...
83
+ });
84
+
85
+ // Subscribe to state changes
86
+ player.onNavigationChange((state) => {
87
+ // Handle navigation state
88
+ });
89
+
90
+ player.onItemChange((item) => {
91
+ // Handle item changes
92
+ });
93
+
94
+ // Navigate to first item
95
+ await player.start();
96
+ ```
97
+
98
+ ### Using ReferenceLayout Directly
99
+
100
+ If you want to use the reference layout with a different player:
101
+
102
+ ```svelte
103
+ <script>
104
+ import { ReferenceLayout } from '$lib/assessment-toolkit/reference-layout';
105
+ // ... create your own player instance
106
+ </script>
107
+
108
+ <ReferenceLayout {player} {assessment} />
109
+ ```
110
+
111
+ ### Using Services Directly
112
+
113
+ If you want complete control:
114
+
115
+ ```typescript
116
+ import { TypedEventBus, ToolCoordinator, TTSService } from '$lib/assessment-toolkit';
117
+
118
+ // Wire up services yourself
119
+ const eventBus = new TypedEventBus();
120
+ const toolCoordinator = new ToolCoordinator();
121
+ const ttsService = TTSService.getInstance();
122
+ // ...
123
+ ```
124
+
125
+ ## Directory Structure
126
+
127
+ ```
128
+ reference-implementation/
129
+ ├── index.ts # Exports AssessmentPlayer and layout
130
+ ├── AssessmentPlayer.ts # Core player class (business logic)
131
+ ├── ReferenceAssessmentLayout.svelte # Complete UI wrapper (TBD)
132
+ └── README.md # This file
133
+
134
+ reference-layout/ # Reference layout implementation
135
+ ├── ReferenceLayout.svelte # Main layout component
136
+ ├── components/ # Sub-components
137
+ └── README.md # Layout-specific docs
138
+ ```
139
+
140
+ ## Status
141
+
142
+ 🚧 **Under development** - Implementation in progress.
143
+
144
+ ### Completed
145
+ - ✅ AssessmentPlayer class
146
+ - ✅ Navigation and state management
147
+ - ✅ Integration with assessment toolkit services
148
+
149
+ ### In Progress
150
+ - 🚧 ReferenceAssessmentLayout wrapper
151
+ - 🚧 ReferenceLayout implementation
152
+ - 🚧 Visual components
153
+
154
+ See [../reference-layout/LAYOUT-PLAN.md](../reference-layout/LAYOUT-PLAN.md) for detailed implementation plan.