@pie-players/pie-assessment-toolkit 0.2.7 → 0.2.9

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 (220) hide show
  1. package/README.md +200 -67
  2. package/dist/attempt/TestSession.d.ts +23 -21
  3. package/dist/attempt/TestSession.d.ts.map +1 -1
  4. package/dist/attempt/TestSession.js +17 -15
  5. package/dist/attempt/TestSession.js.map +1 -1
  6. package/dist/attempt/adapters/activity-to-test-attempt-session.d.ts +50 -0
  7. package/dist/attempt/adapters/activity-to-test-attempt-session.d.ts.map +1 -0
  8. package/dist/attempt/adapters/activity-to-test-attempt-session.js +135 -0
  9. package/dist/attempt/adapters/activity-to-test-attempt-session.js.map +1 -0
  10. package/dist/components/ItemToolBar.custom-element.js +628 -0
  11. package/dist/components/PieAssessmentToolkit.custom-element.js +670 -0
  12. package/dist/components/item-toolbar-element.d.ts +3 -0
  13. package/dist/components/item-toolbar-element.d.ts.map +1 -0
  14. package/dist/components/item-toolbar-element.js +2 -0
  15. package/dist/components/item-toolbar-element.js.map +1 -0
  16. package/dist/components/pie-assessment-toolkit-element.d.ts +3 -0
  17. package/dist/components/pie-assessment-toolkit-element.d.ts.map +1 -0
  18. package/dist/components/pie-assessment-toolkit-element.js +2 -0
  19. package/dist/components/pie-assessment-toolkit-element.js.map +1 -0
  20. package/dist/context/assessment-toolkit-context.d.ts +52 -0
  21. package/dist/context/assessment-toolkit-context.d.ts.map +1 -0
  22. package/dist/context/assessment-toolkit-context.js +6 -0
  23. package/dist/context/assessment-toolkit-context.js.map +1 -0
  24. package/dist/context/runtime-context-consumer.d.ts +14 -0
  25. package/dist/context/runtime-context-consumer.d.ts.map +1 -0
  26. package/dist/context/runtime-context-consumer.js +52 -0
  27. package/dist/context/runtime-context-consumer.js.map +1 -0
  28. package/dist/index.d.ts +16 -5
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +13 -11
  31. package/dist/index.js.map +1 -1
  32. package/dist/runtime/RuntimeRegistry.d.ts +13 -0
  33. package/dist/runtime/RuntimeRegistry.d.ts.map +1 -0
  34. package/dist/runtime/RuntimeRegistry.js +50 -0
  35. package/dist/runtime/RuntimeRegistry.js.map +1 -0
  36. package/dist/runtime/SectionRuntimeEngine.d.ts +45 -0
  37. package/dist/runtime/SectionRuntimeEngine.d.ts.map +1 -0
  38. package/dist/runtime/SectionRuntimeEngine.js +81 -0
  39. package/dist/runtime/SectionRuntimeEngine.js.map +1 -0
  40. package/dist/runtime/registration-events.d.ts +19 -0
  41. package/dist/runtime/registration-events.d.ts.map +1 -0
  42. package/dist/runtime/registration-events.js +4 -0
  43. package/dist/runtime/registration-events.js.map +1 -0
  44. package/dist/runtime/runtime-event-guards.d.ts +3 -0
  45. package/dist/runtime/runtime-event-guards.d.ts.map +1 -0
  46. package/dist/runtime/runtime-event-guards.js +9 -0
  47. package/dist/runtime/runtime-event-guards.js.map +1 -0
  48. package/dist/runtime/tool-host-contract.d.ts +30 -0
  49. package/dist/runtime/tool-host-contract.d.ts.map +1 -0
  50. package/dist/runtime/tool-host-contract.js +48 -0
  51. package/dist/runtime/tool-host-contract.js.map +1 -0
  52. package/dist/services/TTSService.d.ts +3 -1
  53. package/dist/services/TTSService.d.ts.map +1 -1
  54. package/dist/services/TTSService.js +13 -12
  55. package/dist/services/TTSService.js.map +1 -1
  56. package/dist/services/ThemeProvider.d.ts +1 -0
  57. package/dist/services/ThemeProvider.d.ts.map +1 -1
  58. package/dist/services/ThemeProvider.js +82 -46
  59. package/dist/services/ThemeProvider.js.map +1 -1
  60. package/dist/services/ToolConfigResolver.d.ts +1 -1
  61. package/dist/services/ToolConfigResolver.js +1 -1
  62. package/dist/services/ToolCoordinator.d.ts +1 -1
  63. package/dist/services/ToolCoordinator.d.ts.map +1 -1
  64. package/dist/services/ToolRegistry.d.ts +64 -67
  65. package/dist/services/ToolRegistry.d.ts.map +1 -1
  66. package/dist/services/ToolRegistry.js +72 -22
  67. package/dist/services/ToolRegistry.js.map +1 -1
  68. package/dist/services/ToolkitCoordinator.d.ts +106 -8
  69. package/dist/services/ToolkitCoordinator.d.ts.map +1 -1
  70. package/dist/services/ToolkitCoordinator.js +502 -71
  71. package/dist/services/ToolkitCoordinator.js.map +1 -1
  72. package/dist/services/createDefaultToolRegistry.d.ts +12 -7
  73. package/dist/services/createDefaultToolRegistry.d.ts.map +1 -1
  74. package/dist/services/createDefaultToolRegistry.js +6 -10
  75. package/dist/services/createDefaultToolRegistry.js.map +1 -1
  76. package/dist/services/defaultPersonalNeedsProfile.d.ts +4 -0
  77. package/dist/services/defaultPersonalNeedsProfile.d.ts.map +1 -0
  78. package/dist/services/defaultPersonalNeedsProfile.js +25 -0
  79. package/dist/services/defaultPersonalNeedsProfile.js.map +1 -0
  80. package/dist/services/interfaces.d.ts +61 -1
  81. package/dist/services/interfaces.d.ts.map +1 -1
  82. package/dist/services/interfaces.js.map +1 -1
  83. package/dist/services/pnp-standard-features.d.ts +3 -4
  84. package/dist/services/pnp-standard-features.d.ts.map +1 -1
  85. package/dist/services/pnp-standard-features.js +0 -3
  86. package/dist/services/pnp-standard-features.js.map +1 -1
  87. package/dist/services/section-controller-types.d.ts +32 -0
  88. package/dist/services/section-controller-types.d.ts.map +1 -0
  89. package/dist/services/section-controller-types.js +2 -0
  90. package/dist/services/section-controller-types.js.map +1 -0
  91. package/dist/services/tool-context.d.ts +4 -4
  92. package/dist/services/tool-context.d.ts.map +1 -1
  93. package/dist/services/tool-context.js +106 -17
  94. package/dist/services/tool-context.js.map +1 -1
  95. package/dist/services/tool-instance-id.d.ts +17 -0
  96. package/dist/services/tool-instance-id.d.ts.map +1 -0
  97. package/dist/services/tool-instance-id.js +61 -0
  98. package/dist/services/tool-instance-id.js.map +1 -0
  99. package/dist/services/tool-providers/IToolProvider.d.ts +2 -3
  100. package/dist/services/tool-providers/IToolProvider.d.ts.map +1 -1
  101. package/dist/services/tool-providers/IToolProvider.js +1 -2
  102. package/dist/services/tool-providers/IToolProvider.js.map +1 -1
  103. package/dist/services/tool-providers/MathJsToolProvider.d.ts +25 -0
  104. package/dist/services/tool-providers/MathJsToolProvider.d.ts.map +1 -0
  105. package/dist/services/tool-providers/MathJsToolProvider.js +53 -0
  106. package/dist/services/tool-providers/MathJsToolProvider.js.map +1 -0
  107. package/dist/services/tool-providers/index.d.ts +2 -0
  108. package/dist/services/tool-providers/index.d.ts.map +1 -1
  109. package/dist/services/tool-providers/index.js +1 -0
  110. package/dist/services/tool-providers/index.js.map +1 -1
  111. package/dist/services/tools-config-normalizer.d.ts +26 -0
  112. package/dist/services/tools-config-normalizer.d.ts.map +1 -0
  113. package/dist/services/tools-config-normalizer.js +63 -0
  114. package/dist/services/tools-config-normalizer.js.map +1 -0
  115. package/dist/tools/calculators/ti-provider.d.ts.map +1 -1
  116. package/dist/tools/calculators/ti-provider.js +127 -4
  117. package/dist/tools/calculators/ti-provider.js.map +1 -1
  118. package/dist/tools/client.d.ts +2 -1
  119. package/dist/tools/client.d.ts.map +1 -1
  120. package/dist/tools/client.js +2 -1
  121. package/dist/tools/client.js.map +1 -1
  122. package/dist/tools/default-tool-module-loaders.d.ts +9 -0
  123. package/dist/tools/default-tool-module-loaders.d.ts.map +1 -0
  124. package/dist/tools/default-tool-module-loaders.js +17 -0
  125. package/dist/tools/default-tool-module-loaders.js.map +1 -0
  126. package/dist/tools/index.d.ts +2 -1
  127. package/dist/tools/index.d.ts.map +1 -1
  128. package/dist/tools/index.js +2 -1
  129. package/dist/tools/index.js.map +1 -1
  130. package/dist/tools/library-loader.d.ts +1 -1
  131. package/dist/tools/library-loader.d.ts.map +1 -1
  132. package/dist/tools/library-loader.js +7 -2
  133. package/dist/tools/library-loader.js.map +1 -1
  134. package/dist/tools/registrations/accessibility-tools.d.ts +1 -9
  135. package/dist/tools/registrations/accessibility-tools.d.ts.map +1 -1
  136. package/dist/tools/registrations/accessibility-tools.js +95 -113
  137. package/dist/tools/registrations/accessibility-tools.js.map +1 -1
  138. package/dist/tools/registrations/calculator.d.ts +2 -2
  139. package/dist/tools/registrations/calculator.d.ts.map +1 -1
  140. package/dist/tools/registrations/calculator.js +34 -55
  141. package/dist/tools/registrations/calculator.js.map +1 -1
  142. package/dist/tools/registrations/interaction-tools.d.ts.map +1 -1
  143. package/dist/tools/registrations/interaction-tools.js +77 -52
  144. package/dist/tools/registrations/interaction-tools.js.map +1 -1
  145. package/dist/tools/registrations/measurement-tools.d.ts.map +1 -1
  146. package/dist/tools/registrations/measurement-tools.js +63 -38
  147. package/dist/tools/registrations/measurement-tools.js.map +1 -1
  148. package/dist/tools/registrations/subject-specific-tools.d.ts.map +1 -1
  149. package/dist/tools/registrations/subject-specific-tools.js +63 -38
  150. package/dist/tools/registrations/subject-specific-tools.js.map +1 -1
  151. package/dist/tools/registrations/tts.d.ts +1 -1
  152. package/dist/tools/registrations/tts.d.ts.map +1 -1
  153. package/dist/tools/registrations/tts.js +27 -41
  154. package/dist/tools/registrations/tts.js.map +1 -1
  155. package/dist/tools/response-discovery.d.ts +12 -14
  156. package/dist/tools/response-discovery.d.ts.map +1 -1
  157. package/dist/tools/response-discovery.js +23 -80
  158. package/dist/tools/response-discovery.js.map +1 -1
  159. package/dist/tools/tool-tag-map.d.ts +3 -3
  160. package/dist/tools/tool-tag-map.d.ts.map +1 -1
  161. package/dist/tools/tool-tag-map.js +2 -3
  162. package/dist/tools/tool-tag-map.js.map +1 -1
  163. package/dist/tools/types.d.ts +3 -1
  164. package/dist/tools/types.d.ts.map +1 -1
  165. package/package.json +25 -28
  166. package/src/README.md +16 -32
  167. package/src/components/ItemToolBar.svelte +489 -0
  168. package/src/components/PNPProvenanceViewer.svelte +14 -4
  169. package/src/components/PieAssessmentToolkit.svelte +517 -0
  170. package/src/components/ToolButton.svelte +11 -11
  171. package/src/components/ToolButtonGroup.svelte +27 -11
  172. package/src/tools/README.md +6 -5
  173. package/src/tools/calculators/README.md +4 -4
  174. package/dist/player/AssessmentPlayer.d.ts +0 -397
  175. package/dist/player/AssessmentPlayer.d.ts.map +0 -1
  176. package/dist/player/AssessmentPlayer.js +0 -974
  177. package/dist/player/AssessmentPlayer.js.map +0 -1
  178. package/dist/player/index.d.ts +0 -17
  179. package/dist/player/index.d.ts.map +0 -1
  180. package/dist/player/index.js +0 -15
  181. package/dist/player/index.js.map +0 -1
  182. package/dist/player/navigation-types.d.ts +0 -63
  183. package/dist/player/navigation-types.d.ts.map +0 -1
  184. package/dist/player/navigation-types.js +0 -7
  185. package/dist/player/navigation-types.js.map +0 -1
  186. package/dist/player/qti-navigation.d.ts +0 -29
  187. package/dist/player/qti-navigation.d.ts.map +0 -1
  188. package/dist/player/qti-navigation.js +0 -160
  189. package/dist/player/qti-navigation.js.map +0 -1
  190. package/dist/reference-layout/index.d.ts +0 -19
  191. package/dist/reference-layout/index.d.ts.map +0 -1
  192. package/dist/reference-layout/index.js +0 -20
  193. package/dist/reference-layout/index.js.map +0 -1
  194. package/dist/services/AnnotationToolbarConfig.d.ts +0 -134
  195. package/dist/services/AnnotationToolbarConfig.d.ts.map +0 -1
  196. package/dist/services/AnnotationToolbarConfig.js +0 -100
  197. package/dist/services/AnnotationToolbarConfig.js.map +0 -1
  198. package/dist/services/AssessmentAuthoringService.d.ts +0 -60
  199. package/dist/services/AssessmentAuthoringService.d.ts.map +0 -1
  200. package/dist/services/AssessmentAuthoringService.js +0 -183
  201. package/dist/services/AssessmentAuthoringService.js.map +0 -1
  202. package/dist/services/PNPMapper.d.ts +0 -81
  203. package/dist/services/PNPMapper.d.ts.map +0 -1
  204. package/dist/services/PNPMapper.js +0 -108
  205. package/dist/services/PNPMapper.js.map +0 -1
  206. package/src/components/PNPProfileTester.example.svelte +0 -188
  207. package/src/components/PNPProfileTester.svelte +0 -397
  208. package/src/components/QuestionToolBar.svelte +0 -483
  209. package/src/player/AssessmentLayout.svelte +0 -68
  210. package/src/player/README.md +0 -154
  211. package/src/reference-layout/README.md +0 -135
  212. package/src/reference-layout/ReferenceLayout.svelte +0 -201
  213. package/src/reference-layout/components/AssessmentContent.svelte +0 -259
  214. package/src/reference-layout/components/AssessmentFooter.svelte +0 -89
  215. package/src/reference-layout/components/AssessmentHeader.svelte +0 -250
  216. package/src/reference-layout/components/AssessmentNavigation.svelte +0 -134
  217. package/src/reference-layout/components/AssessmentToolsBar.svelte +0 -176
  218. package/src/reference-layout/components/ItemPanel.svelte +0 -200
  219. package/src/reference-layout/components/NotesPanel.svelte +0 -179
  220. package/src/reference-layout/components/PassagePanel.svelte +0 -76
@@ -0,0 +1,517 @@
1
+ <svelte:options
2
+ customElement={{
3
+ tag: "pie-assessment-toolkit",
4
+ shadow: "open",
5
+ props: {
6
+ assessmentId: { attribute: "assessment-id", type: "String" },
7
+ section: { attribute: "section", type: "Object" },
8
+ sectionId: { attribute: "section-id", type: "String" },
9
+ attemptId: { attribute: "attempt-id", type: "String" },
10
+ view: { attribute: "view", type: "String" },
11
+ env: { attribute: "env", type: "Object" },
12
+ lazyInit: { attribute: "lazy-init", type: "Boolean" },
13
+ tools: { attribute: "tools", type: "Object" },
14
+ accessibility: { attribute: "accessibility", type: "Object" },
15
+ player: { attribute: "player", type: "Object" },
16
+ playerType: { attribute: "player-type", type: "String" },
17
+ coordinator: { type: "Object", reflect: false },
18
+ createSectionController: { type: "Object", reflect: false },
19
+ isolation: { attribute: "isolation", type: "String" },
20
+ },
21
+ }}
22
+ />
23
+
24
+ <script lang="ts">
25
+ import {
26
+ ContextProvider,
27
+ ContextRoot,
28
+ requestContext,
29
+ } from "@pie-players/pie-context";
30
+ import {
31
+ assessmentToolkitHostRuntimeContext,
32
+ assessmentToolkitRuntimeContext,
33
+ type AssessmentToolkitHostRuntimeContext,
34
+ type AssessmentToolkitRuntimeContext,
35
+ type ItemPlayerConfig,
36
+ type ItemPlayerType,
37
+ } from "../context/assessment-toolkit-context.js";
38
+ import { connectAssessmentToolkitHostRuntimeContext } from "../context/runtime-context-consumer.js";
39
+ import { ToolkitCoordinator } from "../services/ToolkitCoordinator.js";
40
+ import {
41
+ PIE_ITEM_SESSION_CHANGED_EVENT,
42
+ PIE_REGISTER_EVENT,
43
+ PIE_UNREGISTER_EVENT,
44
+ type ItemSessionChangedDetail,
45
+ type RuntimeRegistrationDetail,
46
+ } from "../runtime/registration-events.js";
47
+ import { dispatchCrossBoundaryEvent } from "../runtime/tool-host-contract.js";
48
+ import { SectionRuntimeEngine } from "../runtime/SectionRuntimeEngine.js";
49
+ import {
50
+ createRuntimeId,
51
+ shouldHandleBySourceRuntime,
52
+ } from "../runtime/runtime-event-guards.js";
53
+
54
+ type SessionChangedLike = {
55
+ eventDetail?: unknown;
56
+ };
57
+
58
+ type HostItemPlayerInput = Partial<
59
+ Pick<ItemPlayerConfig, "type" | "tagName" | "version" | "source">
60
+ > | null;
61
+
62
+ const runtimeId = createRuntimeId("toolkit");
63
+ const sectionEngine = new SectionRuntimeEngine();
64
+ const DEFAULT_ITEM_PLAYER_BY_TYPE: Record<ItemPlayerType, string> = {
65
+ iife: "pie-iife-player",
66
+ esm: "pie-esm-player",
67
+ fixed: "pie-fixed-player",
68
+ custom: "",
69
+ };
70
+
71
+ let {
72
+ assessmentId = "",
73
+ section = null,
74
+ sectionId = "",
75
+ attemptId = "",
76
+ view = "candidate",
77
+ env = {},
78
+ lazyInit = true,
79
+ tools = {},
80
+ accessibility = {},
81
+ player = null as HostItemPlayerInput,
82
+ playerType = "" as ItemPlayerType | "",
83
+ coordinator = null as ToolkitCoordinator | null,
84
+ createSectionController = null as null | (() => unknown),
85
+ isolation = "inherit",
86
+ } = $props();
87
+
88
+ let anchor = $state<HTMLDivElement | null>(null);
89
+ let ownedCoordinator = $state<ToolkitCoordinator | null>(null);
90
+ let inheritedRuntime = $state<AssessmentToolkitHostRuntimeContext | null>(null);
91
+ let lastOwnership = $state<"owned" | "inherited" | null>(null);
92
+ let provider: ContextProvider<typeof assessmentToolkitRuntimeContext> | null = null;
93
+ let contextRoot: ContextRoot | null = null;
94
+ let hostRuntimeProvider: ContextProvider<
95
+ typeof assessmentToolkitHostRuntimeContext
96
+ > | null = null;
97
+ let hostRuntimeRoot: ContextRoot | null = null;
98
+ let compositionVersion = $state(0);
99
+ let compositionModel = $state<unknown>(null);
100
+ let runtimeError = $state<unknown>(null);
101
+ let lastCompositionSignature = $state<string>("");
102
+
103
+ function getHostElement(): HTMLElement | null {
104
+ if (!anchor) return null;
105
+ const rootNode = anchor.getRootNode();
106
+ if (rootNode && "host" in rootNode) {
107
+ return (rootNode as ShadowRoot).host as HTMLElement;
108
+ }
109
+ return anchor.parentElement as HTMLElement | null;
110
+ }
111
+ const host = $derived.by(() => getHostElement());
112
+
113
+ function emit(name: string, detail: unknown): void {
114
+ if (!host) return;
115
+ dispatchCrossBoundaryEvent(host, name, detail);
116
+ }
117
+
118
+ function isKnownPlayerType(value: unknown): value is ItemPlayerType {
119
+ return value === "iife" || value === "esm" || value === "fixed" || value === "custom";
120
+ }
121
+
122
+ function normalizeItemPlayerConfig(
123
+ hostPlayer: HostItemPlayerInput,
124
+ hostPlayerType: ItemPlayerType | "",
125
+ ): ItemPlayerConfig {
126
+ const typeFromConfig = hostPlayer?.type;
127
+ const rawType = isKnownPlayerType(typeFromConfig)
128
+ ? typeFromConfig
129
+ : isKnownPlayerType(hostPlayerType)
130
+ ? hostPlayerType
131
+ : hostPlayer?.tagName
132
+ ? "custom"
133
+ : "iife";
134
+ const requestedTagName = hostPlayer?.tagName?.trim();
135
+ if (rawType === "custom" && !requestedTagName) {
136
+ return {
137
+ type: "iife",
138
+ tagName: DEFAULT_ITEM_PLAYER_BY_TYPE.iife,
139
+ version: undefined,
140
+ source: undefined,
141
+ isDefault: true,
142
+ };
143
+ }
144
+ return {
145
+ type: rawType,
146
+ tagName: requestedTagName || DEFAULT_ITEM_PLAYER_BY_TYPE[rawType],
147
+ version: hostPlayer?.version,
148
+ source: hostPlayer?.source,
149
+ isDefault: !hostPlayer && !hostPlayerType,
150
+ };
151
+ }
152
+
153
+ async function createDefaultSectionController() {
154
+ if (createSectionController) {
155
+ return createSectionController() as any;
156
+ }
157
+ throw new Error(
158
+ "pie-assessment-toolkit requires createSectionController when no coordinator hook provides one",
159
+ );
160
+ }
161
+
162
+ function buildOwnedCoordinator(): ToolkitCoordinator {
163
+ const fallbackAssessmentId =
164
+ assessmentId ||
165
+ (section as any)?.identifier ||
166
+ `assessment-${Math.random().toString(16).slice(2)}`;
167
+ return new ToolkitCoordinator({
168
+ assessmentId: fallbackAssessmentId,
169
+ lazyInit,
170
+ tools: tools as any,
171
+ accessibility: accessibility as any,
172
+ });
173
+ }
174
+
175
+ const effectiveCoordinator = $derived.by(() => {
176
+ if (isolation !== "force" && inheritedRuntime?.coordinator) {
177
+ return inheritedRuntime.coordinator as ToolkitCoordinator;
178
+ }
179
+ return coordinator || ownedCoordinator;
180
+ });
181
+
182
+ $effect(() => {
183
+ if (coordinator) {
184
+ if (ownedCoordinator) {
185
+ ownedCoordinator = null;
186
+ }
187
+ return;
188
+ }
189
+ if (isolation !== "force" && inheritedRuntime?.coordinator) {
190
+ if (ownedCoordinator) {
191
+ ownedCoordinator = null;
192
+ }
193
+ return;
194
+ }
195
+ if (!ownedCoordinator) {
196
+ ownedCoordinator = buildOwnedCoordinator();
197
+ }
198
+ });
199
+
200
+ const effectiveAssessmentId = $derived(
201
+ assessmentId || effectiveCoordinator?.assessmentId || "",
202
+ );
203
+ const effectiveSectionId = $derived(
204
+ sectionId || (section as any)?.identifier || `section-${effectiveAssessmentId || "default"}`,
205
+ );
206
+ const effectiveItemPlayer = $derived.by(() =>
207
+ normalizeItemPlayerConfig(player, playerType),
208
+ );
209
+ const runtimeContextValue = $derived.by((): AssessmentToolkitRuntimeContext | null => {
210
+ if (!effectiveCoordinator) return null;
211
+ const services = effectiveCoordinator.getServiceBundle();
212
+ return {
213
+ toolkitCoordinator: effectiveCoordinator,
214
+ toolCoordinator: effectiveCoordinator.toolCoordinator,
215
+ ttsService: services.ttsService,
216
+ highlightCoordinator: services.highlightCoordinator,
217
+ catalogResolver: services.catalogResolver,
218
+ elementToolStateStore: services.elementToolStateStore,
219
+ assessmentId: effectiveAssessmentId,
220
+ sectionId: effectiveSectionId,
221
+ itemPlayer: effectiveItemPlayer,
222
+ reportSessionChanged: (itemId: string, detail: unknown) => {
223
+ const result = sectionEngine.handleItemSessionChanged(itemId, detail);
224
+ const normalized = (result as SessionChangedLike | null)?.eventDetail || detail;
225
+ emit("session-changed", {
226
+ ...(normalized as Record<string, unknown>),
227
+ sourceRuntimeId: runtimeId,
228
+ });
229
+ },
230
+ };
231
+ });
232
+ const hostRuntimeContextValue = $derived.by(
233
+ (): AssessmentToolkitHostRuntimeContext | null => {
234
+ if (!effectiveCoordinator) return null;
235
+ return {
236
+ runtimeId,
237
+ coordinator: effectiveCoordinator,
238
+ };
239
+ },
240
+ );
241
+
242
+ function getCompositionSignature(model: unknown): string {
243
+ const typed = model as any;
244
+ const itemIds = Array.isArray(typed?.items)
245
+ ? typed.items.map((item: any) => item?.id || "")
246
+ : [];
247
+ const passageIds = Array.isArray(typed?.passages)
248
+ ? typed.passages.map((item: any) => item?.id || "")
249
+ : [];
250
+ const sessionByItem = typed?.itemSessionsByItemId
251
+ ? Object.entries(typed.itemSessionsByItemId)
252
+ .sort(([left], [right]) => left.localeCompare(right))
253
+ .map(([itemId, session]) => [itemId, JSON.stringify(session ?? null)])
254
+ : [];
255
+ return JSON.stringify({
256
+ sectionId: typed?.section?.identifier || typed?.sectionId || "",
257
+ currentItemIndex: typed?.currentItemIndex ?? -1,
258
+ itemIds,
259
+ passageIds,
260
+ sessionByItem,
261
+ });
262
+ }
263
+
264
+ function emitCompositionChanged() {
265
+ const nextModel = sectionEngine.getCompositionModel();
266
+ const signature = getCompositionSignature(nextModel);
267
+ if (signature === lastCompositionSignature) {
268
+ return;
269
+ }
270
+ lastCompositionSignature = signature;
271
+ compositionVersion += 1;
272
+ compositionModel = nextModel;
273
+ emit("composition-changed", {
274
+ composition: compositionModel,
275
+ version: compositionVersion,
276
+ });
277
+ }
278
+
279
+ function isLocalToCurrentRuntime(eventTarget: EventTarget | null): boolean {
280
+ if (!(eventTarget instanceof HTMLElement)) return false;
281
+ const sourceRuntime = requestContext(
282
+ eventTarget,
283
+ assessmentToolkitHostRuntimeContext,
284
+ );
285
+ return sourceRuntime?.runtimeId === runtimeId;
286
+ }
287
+
288
+ $effect(() => {
289
+ if (!host) return;
290
+ if (isolation === "force") {
291
+ inheritedRuntime = null;
292
+ return;
293
+ }
294
+ return connectAssessmentToolkitHostRuntimeContext(host, (value) => {
295
+ if (value.runtimeId === runtimeId) {
296
+ return;
297
+ }
298
+ inheritedRuntime = value;
299
+ });
300
+ });
301
+
302
+ $effect(() => {
303
+ const parentRuntimeId =
304
+ isolation !== "force" && inheritedRuntime ? inheritedRuntime.runtimeId : null;
305
+ const ownership: "owned" | "inherited" = parentRuntimeId ? "inherited" : "owned";
306
+ if (ownership !== lastOwnership) {
307
+ lastOwnership = ownership;
308
+ emit(ownership === "inherited" ? "runtime-inherited" : "runtime-owned", {
309
+ runtimeId,
310
+ parentRuntimeId,
311
+ });
312
+ }
313
+ });
314
+
315
+ $effect(() => {
316
+ if (!host || !hostRuntimeContextValue) return;
317
+ hostRuntimeProvider = new ContextProvider(host, {
318
+ context: assessmentToolkitHostRuntimeContext,
319
+ initialValue: hostRuntimeContextValue,
320
+ });
321
+ hostRuntimeProvider.connect();
322
+ hostRuntimeRoot = new ContextRoot(host);
323
+ hostRuntimeRoot.attach();
324
+
325
+ return () => {
326
+ hostRuntimeRoot?.detach();
327
+ hostRuntimeRoot = null;
328
+ hostRuntimeProvider?.disconnect();
329
+ hostRuntimeProvider = null;
330
+ };
331
+ });
332
+
333
+ $effect(() => {
334
+ if (!hostRuntimeContextValue) return;
335
+ hostRuntimeProvider?.setValue(hostRuntimeContextValue);
336
+ });
337
+
338
+ $effect(() => {
339
+ if (!host) return;
340
+ host.setAttribute("data-item-player-type", effectiveItemPlayer.type);
341
+ host.setAttribute("data-item-player-tag", effectiveItemPlayer.tagName);
342
+ host.setAttribute("data-env-mode", String((env as any)?.mode || ""));
343
+ host.setAttribute("data-env-role", String((env as any)?.role || ""));
344
+ });
345
+
346
+ $effect(() => {
347
+ if (!host || !runtimeContextValue) return;
348
+ provider = new ContextProvider(host, {
349
+ context: assessmentToolkitRuntimeContext,
350
+ initialValue: runtimeContextValue,
351
+ });
352
+ provider.connect();
353
+ contextRoot = new ContextRoot(host);
354
+ contextRoot.attach();
355
+
356
+ return () => {
357
+ contextRoot?.detach();
358
+ contextRoot = null;
359
+ provider?.disconnect();
360
+ provider = null;
361
+ };
362
+ });
363
+
364
+ $effect(() => {
365
+ if (runtimeContextValue) {
366
+ provider?.setValue(runtimeContextValue);
367
+ }
368
+ });
369
+
370
+ $effect(() => {
371
+ if (!section || !effectiveCoordinator) return;
372
+ let cancelled = false;
373
+
374
+ void sectionEngine
375
+ .initialize({
376
+ coordinator: effectiveCoordinator,
377
+ section,
378
+ sectionId: effectiveSectionId,
379
+ assessmentId: effectiveAssessmentId,
380
+ attemptId: attemptId || undefined,
381
+ view,
382
+ createDefaultController: createDefaultSectionController,
383
+ onCompositionChanged: (nextComposition) => {
384
+ if (cancelled) return;
385
+ compositionModel = nextComposition;
386
+ emitCompositionChanged();
387
+ },
388
+ })
389
+ .then(() => {
390
+ if (cancelled) return;
391
+ emit("toolkit-ready", {
392
+ runtimeId,
393
+ assessmentId: effectiveAssessmentId,
394
+ sectionId: effectiveSectionId,
395
+ itemPlayer: effectiveItemPlayer,
396
+ });
397
+ emit("section-ready", {
398
+ sectionId: effectiveSectionId,
399
+ });
400
+ })
401
+ .catch((error) => {
402
+ runtimeError = error;
403
+ emit("runtime-error", {
404
+ runtimeId,
405
+ error,
406
+ });
407
+ });
408
+
409
+ return () => {
410
+ cancelled = true;
411
+ };
412
+ });
413
+
414
+ $effect(() => {
415
+ if (!host) return;
416
+ const localHost = host;
417
+
418
+ const onRegister = (event: Event) => {
419
+ if (!isLocalToCurrentRuntime(event.target)) return;
420
+ const detail = (event as CustomEvent<RuntimeRegistrationDetail>).detail;
421
+ if (!detail?.element || !detail?.itemId) return;
422
+ const changed = sectionEngine.register(detail);
423
+ if (changed) emitCompositionChanged();
424
+ };
425
+
426
+ const onUnregister = (event: Event) => {
427
+ if (!isLocalToCurrentRuntime(event.target)) return;
428
+ const detail = (event as CustomEvent<RuntimeRegistrationDetail>).detail;
429
+ const changed = detail?.element
430
+ ? sectionEngine.unregister(detail.element)
431
+ : false;
432
+ if (changed) emitCompositionChanged();
433
+ };
434
+
435
+ const onItemSessionChanged = (event: Event) => {
436
+ if (!isLocalToCurrentRuntime(event.target)) return;
437
+ const detail = (event as CustomEvent<ItemSessionChangedDetail>).detail;
438
+ if (!detail?.itemId) return;
439
+ if (!shouldHandleBySourceRuntime(detail.sourceRuntimeId, runtimeId)) return;
440
+ const result = sectionEngine.handleItemSessionChanged(detail.itemId, detail.session);
441
+ const normalized = (result as SessionChangedLike | null)?.eventDetail || detail.session;
442
+ emit("session-changed", {
443
+ ...(normalized as Record<string, unknown>),
444
+ itemId: detail.itemId,
445
+ canonicalItemId: detail.canonicalItemId || detail.itemId,
446
+ sourceRuntimeId: runtimeId,
447
+ });
448
+ };
449
+
450
+ localHost.addEventListener(PIE_REGISTER_EVENT, onRegister);
451
+ localHost.addEventListener(PIE_UNREGISTER_EVENT, onUnregister);
452
+ localHost.addEventListener(PIE_ITEM_SESSION_CHANGED_EVENT, onItemSessionChanged);
453
+ return () => {
454
+ localHost.removeEventListener(PIE_REGISTER_EVENT, onRegister);
455
+ localHost.removeEventListener(PIE_UNREGISTER_EVENT, onUnregister);
456
+ localHost.removeEventListener(PIE_ITEM_SESSION_CHANGED_EVENT, onItemSessionChanged);
457
+ };
458
+ });
459
+
460
+ export async function waitUntilReady(): Promise<void> {
461
+ if (!effectiveCoordinator) {
462
+ throw new Error("Coordinator not initialized");
463
+ }
464
+ await effectiveCoordinator.waitUntilReady();
465
+ }
466
+
467
+ export function getServiceBundle() {
468
+ if (!effectiveCoordinator) {
469
+ throw new Error("Coordinator not initialized");
470
+ }
471
+ return effectiveCoordinator.getServiceBundle();
472
+ }
473
+
474
+ export function setHooks(hooks: Record<string, unknown>): void {
475
+ if (!effectiveCoordinator) {
476
+ throw new Error("Coordinator not initialized");
477
+ }
478
+ effectiveCoordinator.setHooks(hooks as any);
479
+ }
480
+
481
+ export function navigateToItem(index: number): unknown {
482
+ return sectionEngine.navigateToItem(index);
483
+ }
484
+
485
+ export function getCompositionModel(): unknown {
486
+ return sectionEngine.getCompositionModel();
487
+ }
488
+
489
+ export function getItemPlayerConfig(): ItemPlayerConfig {
490
+ return effectiveItemPlayer;
491
+ }
492
+
493
+ export async function persist(): Promise<void> {
494
+ await sectionEngine.persist();
495
+ }
496
+
497
+ export async function hydrate(): Promise<void> {
498
+ await sectionEngine.hydrate();
499
+ }
500
+
501
+ $effect(() => {
502
+ return () => {
503
+ void sectionEngine.dispose().catch((error) => {
504
+ runtimeError = error;
505
+ });
506
+ };
507
+ });
508
+ </script>
509
+
510
+ <div bind:this={anchor} class="pie-assessment-toolkit-anchor" aria-hidden="true"></div>
511
+ <slot></slot>
512
+
513
+ <style>
514
+ .pie-assessment-toolkit-anchor {
515
+ display: none;
516
+ }
517
+ </style>
@@ -3,17 +3,17 @@
3
3
  * ToolButton
4
4
  *
5
5
  * Generic button component for rendering tool buttons in toolbars.
6
- * Uses ToolButtonDefinition from ToolRegistry for consistent tool button rendering.
6
+ * Uses ToolToolbarButtonDefinition from ToolRegistry for consistent rendering.
7
7
  */
8
8
 
9
- import type { ToolButtonDefinition } from "../services/ToolRegistry";
9
+ import type { ToolToolbarButtonDefinition } from "../services/ToolRegistry.js";
10
10
 
11
11
  // Props
12
12
  let {
13
13
  button,
14
14
  class: className = "",
15
15
  }: {
16
- button: ToolButtonDefinition;
16
+ button: ToolToolbarButtonDefinition;
17
17
  class?: string;
18
18
  } = $props();
19
19
 
@@ -69,10 +69,10 @@
69
69
  align-items: center;
70
70
  gap: 0.5rem;
71
71
  padding: 0.5rem 1rem;
72
- background: var(--tool-button-bg, #ffffff);
73
- border: 1px solid var(--tool-button-border, #d1d5db);
72
+ background: var(--pie-button-bg, #ffffff);
73
+ border: 1px solid var(--pie-button-border, #d1d5db);
74
74
  border-radius: 0.375rem;
75
- color: var(--tool-button-color, #374151);
75
+ color: var(--pie-button-color, #374151);
76
76
  font-size: 0.875rem;
77
77
  font-weight: 500;
78
78
  cursor: pointer;
@@ -81,18 +81,18 @@
81
81
  }
82
82
 
83
83
  .tool-button:hover:not(:disabled) {
84
- background: var(--tool-button-hover-bg, #f9fafb);
85
- border-color: var(--tool-button-hover-border, #9ca3af);
86
- color: var(--tool-button-hover-color, #111827);
84
+ background: var(--pie-button-hover-bg, #f9fafb);
85
+ border-color: var(--pie-button-hover-border, #9ca3af);
86
+ color: var(--pie-button-hover-color, #111827);
87
87
  }
88
88
 
89
89
  .tool-button:active:not(:disabled) {
90
- background: var(--tool-button-active-bg, #f3f4f6);
90
+ background: var(--pie-button-active-bg, #f3f4f6);
91
91
  transform: translateY(1px);
92
92
  }
93
93
 
94
94
  .tool-button:focus-visible {
95
- outline: 2px solid var(--tool-button-focus-outline, #3b82f6);
95
+ outline: 2px solid var(--pie-button-focus-outline, #3b82f6);
96
96
  outline-offset: 2px;
97
97
  }
98
98
 
@@ -8,8 +8,12 @@
8
8
  * - Pass 2: Filters by tool relevance using ToolRegistry
9
9
  */
10
10
 
11
- import type { ToolRegistry } from "../services/ToolRegistry";
12
- import type { ToolContext } from "../services/tool-context";
11
+ import type {
12
+ ToolRegistry,
13
+ ToolToolbarButtonDefinition,
14
+ ToolbarContext,
15
+ } from "../services/ToolRegistry.js";
16
+ import type { ToolContext } from "../services/tool-context.js";
13
17
  import ToolButton from "./ToolButton.svelte";
14
18
 
15
19
  // Props
@@ -17,6 +21,7 @@
17
21
  toolRegistry,
18
22
  allowedToolIds,
19
23
  context,
24
+ toolbarContext,
20
25
  onToolClick,
21
26
  orientation = "horizontal",
22
27
  compact = false,
@@ -28,6 +33,8 @@
28
33
  allowedToolIds: string[];
29
34
  /** Context for visibility evaluation (Pass 2) */
30
35
  context: ToolContext;
36
+ /** Context used for toolbar rendering contract */
37
+ toolbarContext: ToolbarContext;
31
38
  /** Callback when tool button is clicked */
32
39
  onToolClick?: (toolId: string) => void;
33
40
  /** Layout orientation */
@@ -44,17 +51,26 @@
44
51
  toolRegistry.filterVisibleInContext(allowedToolIds, context),
45
52
  );
46
53
 
47
- // Create button definitions for visible tools
48
- const buttons = $derived(
49
- visibleTools.map((tool) =>
50
- tool.createButton(context, {
54
+ // Create button definitions for visible tools using the toolbar contract
55
+ const buttons = $derived.by((): ToolToolbarButtonDefinition[] => {
56
+ const rendered = visibleTools
57
+ .map((tool) => toolRegistry.renderForToolbar(tool.toolId, context, toolbarContext))
58
+ .filter((tool): tool is NonNullable<typeof tool> => Boolean(tool));
59
+
60
+ return rendered
61
+ .map((tool) => tool.button)
62
+ .filter((button): button is ToolToolbarButtonDefinition => Boolean(button))
63
+ .map((button) => ({
64
+ ...button,
51
65
  onClick: () => {
52
- onToolClick?.(tool.toolId);
66
+ button.onClick();
67
+ onToolClick?.(button.toolId);
53
68
  },
54
- className: compact ? "tool-button--compact" : "",
55
- }),
56
- ),
57
- );
69
+ className: compact
70
+ ? `${button.className ? `${button.className} ` : ""}tool-button--compact`
71
+ : button.className,
72
+ }));
73
+ });
58
74
 
59
75
  // Derive container classes
60
76
  const containerClasses = $derived(
@@ -139,10 +139,10 @@ src/lib/tags/tool-{name}/
139
139
  {#if visible}
140
140
  <div
141
141
  bind:this={containerEl}
142
- class="my-tool"
142
+ class="pie-tool-my-tool"
143
143
  on:mousedown={() => toolCoordinator.bringToFront(toolId)}
144
144
  >
145
- <div class="tool-header">
145
+ <div class="pie-tool-my-tool__header">
146
146
  <span>My Tool</span>
147
147
  <button on:click={handleClose}>×</button>
148
148
  </div>
@@ -152,7 +152,7 @@ src/lib/tags/tool-{name}/
152
152
  {/if}
153
153
 
154
154
  <style>
155
- .my-tool {
155
+ .pie-tool-my-tool {
156
156
  position: fixed;
157
157
  /* Tool-specific styles */
158
158
  }
@@ -257,6 +257,7 @@ Tools that require external API integration:
257
257
  6. **Make tools draggable** for better UX
258
258
  7. **Add close buttons** to all tools
259
259
  8. **Use consistent styling** (header, body, controls)
260
+ 9. **Use namespaced classes** (`pie-tool-{name}` and `pie-tool-{name}__*`)
260
261
 
261
262
  ## Architectural Enhancement Services
262
263
 
@@ -317,7 +318,7 @@ const roster: RosterToolConfiguration = {
317
318
  rosterId: 'roster-456',
318
319
  toolAllowances: {
319
320
  calculator: '1', // allowed
320
- dictionary: '0', // blocked
321
+ lineReader: '0', // blocked
321
322
  },
322
323
  };
323
324
 
@@ -329,7 +330,7 @@ const item: ItemToolConfig = {
329
330
 
330
331
  // Resolve final tools
331
332
  const resolved = accommodationResolver.resolveToolsForItem(student, roster, item);
332
- // Returns: [calculator, highlighter, protractor] (graphing-calculator blocked, dictionary blocked)
333
+ // Returns: [calculator, highlighter, protractor] (graphing-calculator blocked, lineReader blocked)
333
334
 
334
335
  // Check specific tool
335
336
  const result = accommodationResolver.isToolAllowed('calculator', student, roster, item);