@pie-players/pie-assessment-toolkit 0.3.3 → 0.3.4

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 (90) hide show
  1. package/README.md +5 -8
  2. package/dist/components/ItemToolBar.custom-element.js +539 -94
  3. package/dist/index.d.ts +1 -2
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +1 -1
  6. package/dist/index.js.map +1 -1
  7. package/dist/services/PNPToolResolver.d.ts.map +1 -1
  8. package/dist/services/PNPToolResolver.js +4 -1
  9. package/dist/services/PNPToolResolver.js.map +1 -1
  10. package/dist/services/ToolRegistry.d.ts +51 -0
  11. package/dist/services/ToolRegistry.d.ts.map +1 -1
  12. package/dist/services/ToolRegistry.js +60 -0
  13. package/dist/services/ToolRegistry.js.map +1 -1
  14. package/dist/services/ToolkitCoordinator.d.ts +12 -24
  15. package/dist/services/ToolkitCoordinator.d.ts.map +1 -1
  16. package/dist/services/ToolkitCoordinator.js +60 -99
  17. package/dist/services/ToolkitCoordinator.js.map +1 -1
  18. package/dist/services/defaultPersonalNeedsProfile.js +2 -2
  19. package/dist/services/defaultPersonalNeedsProfile.js.map +1 -1
  20. package/dist/services/interfaces.d.ts +8 -3
  21. package/dist/services/interfaces.d.ts.map +1 -1
  22. package/dist/services/interfaces.js.map +1 -1
  23. package/dist/services/pnp-standard-features.d.ts +1 -1
  24. package/dist/services/tool-providers/index.d.ts +0 -4
  25. package/dist/services/tool-providers/index.d.ts.map +1 -1
  26. package/dist/services/tool-providers/index.js +0 -2
  27. package/dist/services/tool-providers/index.js.map +1 -1
  28. package/dist/services/tools-config-normalizer.d.ts +40 -3
  29. package/dist/services/tools-config-normalizer.d.ts.map +1 -1
  30. package/dist/services/tools-config-normalizer.js +86 -11
  31. package/dist/services/tools-config-normalizer.js.map +1 -1
  32. package/dist/tools/client.d.ts +0 -2
  33. package/dist/tools/client.d.ts.map +1 -1
  34. package/dist/tools/client.js +0 -4
  35. package/dist/tools/client.js.map +1 -1
  36. package/dist/tools/default-tool-module-loaders.js +1 -1
  37. package/dist/tools/default-tool-module-loaders.js.map +1 -1
  38. package/dist/tools/library-loader.d.ts.map +1 -1
  39. package/dist/tools/library-loader.js +0 -35
  40. package/dist/tools/library-loader.js.map +1 -1
  41. package/dist/tools/registrations/accessibility-tools.d.ts.map +1 -1
  42. package/dist/tools/registrations/accessibility-tools.js +44 -41
  43. package/dist/tools/registrations/accessibility-tools.js.map +1 -1
  44. package/dist/tools/registrations/calculator.d.ts.map +1 -1
  45. package/dist/tools/registrations/calculator.js +50 -2
  46. package/dist/tools/registrations/calculator.js.map +1 -1
  47. package/dist/tools/registrations/interaction-tools.d.ts.map +1 -1
  48. package/dist/tools/registrations/interaction-tools.js +22 -28
  49. package/dist/tools/registrations/interaction-tools.js.map +1 -1
  50. package/dist/tools/registrations/measurement-tools.d.ts.map +1 -1
  51. package/dist/tools/registrations/measurement-tools.js +19 -27
  52. package/dist/tools/registrations/measurement-tools.js.map +1 -1
  53. package/dist/tools/registrations/subject-specific-tools.d.ts.map +1 -1
  54. package/dist/tools/registrations/subject-specific-tools.js +53 -37
  55. package/dist/tools/registrations/subject-specific-tools.js.map +1 -1
  56. package/dist/tools/registrations/toolbar-registration-helpers.d.ts +15 -0
  57. package/dist/tools/registrations/toolbar-registration-helpers.d.ts.map +1 -0
  58. package/dist/tools/registrations/toolbar-registration-helpers.js +22 -0
  59. package/dist/tools/registrations/toolbar-registration-helpers.js.map +1 -0
  60. package/dist/tools/registrations/tts.d.ts.map +1 -1
  61. package/dist/tools/registrations/tts.js +49 -0
  62. package/dist/tools/registrations/tts.js.map +1 -1
  63. package/dist/tools/types.d.ts +1 -1
  64. package/dist/tools/types.d.ts.map +1 -1
  65. package/dist/tools/types.js.map +1 -1
  66. package/package.json +8 -10
  67. package/dist/services/tool-providers/MathJsToolProvider.d.ts +0 -25
  68. package/dist/services/tool-providers/MathJsToolProvider.d.ts.map +0 -1
  69. package/dist/services/tool-providers/MathJsToolProvider.js +0 -53
  70. package/dist/services/tool-providers/MathJsToolProvider.js.map +0 -1
  71. package/dist/services/tool-providers/TIToolProvider.d.ts +0 -107
  72. package/dist/services/tool-providers/TIToolProvider.d.ts.map +0 -1
  73. package/dist/services/tool-providers/TIToolProvider.js +0 -123
  74. package/dist/services/tool-providers/TIToolProvider.js.map +0 -1
  75. package/dist/tools/calculators/mathjs-provider.d.ts +0 -50
  76. package/dist/tools/calculators/mathjs-provider.d.ts.map +0 -1
  77. package/dist/tools/calculators/mathjs-provider.js +0 -660
  78. package/dist/tools/calculators/mathjs-provider.js.map +0 -1
  79. package/dist/tools/calculators/ti-provider.d.ts +0 -122
  80. package/dist/tools/calculators/ti-provider.d.ts.map +0 -1
  81. package/dist/tools/calculators/ti-provider.js +0 -710
  82. package/dist/tools/calculators/ti-provider.js.map +0 -1
  83. package/src/README.md +0 -626
  84. package/src/components/ItemToolBar.svelte +0 -717
  85. package/src/components/PieAssessmentToolkit.svelte +0 -681
  86. package/src/components/SectionToolBar.svelte +0 -58
  87. package/src/components/ToolButton.svelte +0 -146
  88. package/src/components/ToolButtonGroup.svelte +0 -125
  89. package/src/tools/README.md +0 -617
  90. package/src/tools/calculators/README.md +0 -395
@@ -1,681 +0,0 @@
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_INTERNAL_CONTENT_LOADED_EVENT,
42
- PIE_INTERNAL_ITEM_SESSION_CHANGED_EVENT,
43
- PIE_INTERNAL_ITEM_PLAYER_ERROR_EVENT,
44
- PIE_REGISTER_EVENT,
45
- PIE_UNREGISTER_EVENT,
46
- type InternalContentLoadedDetail,
47
- type InternalItemSessionChangedDetail,
48
- type InternalItemPlayerErrorDetail,
49
- type RuntimeRegistrationDetail,
50
- } from "../runtime/registration-events.js";
51
- import { dispatchCrossBoundaryEvent } from "../runtime/tool-host-contract.js";
52
- import { SectionRuntimeEngine } from "../runtime/SectionRuntimeEngine.js";
53
- import {
54
- createRuntimeId,
55
- } from "../runtime/runtime-event-guards.js";
56
- import {
57
- createSessionEmitPolicyState,
58
- resetSessionEmitPolicyState,
59
- shouldEmitCanonicalSessionEvent,
60
- } from "../runtime/session-event-emitter-policy.js";
61
-
62
- type SessionChangedLike = {
63
- eventDetail?: unknown;
64
- };
65
-
66
- type HostItemPlayerInput = Partial<
67
- Pick<ItemPlayerConfig, "type" | "tagName" | "version" | "source">
68
- > | null;
69
-
70
- const runtimeId = createRuntimeId("toolkit");
71
- const sectionEngine = new SectionRuntimeEngine();
72
- const DEFAULT_ENV = {
73
- mode: "gather",
74
- role: "student",
75
- } as const;
76
- const DEFAULT_ITEM_PLAYER_BY_TYPE: Record<ItemPlayerType, string> = {
77
- iife: "pie-item-player",
78
- esm: "pie-item-player",
79
- preloaded: "pie-item-player",
80
- custom: "",
81
- };
82
-
83
- let {
84
- assessmentId = "",
85
- section = null,
86
- sectionId = "",
87
- attemptId = "",
88
- view = "candidate",
89
- env = {},
90
- lazyInit = true,
91
- tools = {},
92
- accessibility = {},
93
- player = null as HostItemPlayerInput,
94
- playerType = "" as ItemPlayerType | "",
95
- coordinator = null as ToolkitCoordinator | null,
96
- createSectionController = null as null | (() => unknown),
97
- isolation = "inherit",
98
- } = $props();
99
-
100
- let anchor = $state<HTMLDivElement | null>(null);
101
- let ownedCoordinator = $state<ToolkitCoordinator | null>(null);
102
- let inheritedRuntime = $state<AssessmentToolkitHostRuntimeContext | null>(null);
103
- let lastOwnership = $state<"owned" | "inherited" | null>(null);
104
- let provider: ContextProvider<typeof assessmentToolkitRuntimeContext> | null = null;
105
- let contextRoot: ContextRoot | null = null;
106
- let hostRuntimeProvider: ContextProvider<
107
- typeof assessmentToolkitHostRuntimeContext
108
- > | null = null;
109
- let hostRuntimeRoot: ContextRoot | null = null;
110
- let compositionVersion = $state(0);
111
- let compositionModel = $state<unknown>(null);
112
- let runtimeError = $state<unknown>(null);
113
- let lastCompositionRevisionKey = $state("");
114
- let pendingCompositionModel: unknown = null;
115
- let pendingCompositionEmit = false;
116
- let compositionEmitRaf: number | null = null;
117
- const sessionEmitPolicyState = createSessionEmitPolicyState();
118
-
119
- function getHostElement(): HTMLElement | null {
120
- if (!anchor) return null;
121
- const rootNode = anchor.getRootNode();
122
- if (rootNode && "host" in rootNode) {
123
- return (rootNode as ShadowRoot).host as HTMLElement;
124
- }
125
- return anchor.parentElement as HTMLElement | null;
126
- }
127
- const host = $derived.by(() => getHostElement());
128
-
129
- function emit(name: string, detail: unknown): void {
130
- if (!host) return;
131
- dispatchCrossBoundaryEvent(host, name, detail);
132
- }
133
-
134
- function hashString(input: string): string {
135
- let hash = 5381;
136
- for (let index = 0; index < input.length; index += 1) {
137
- hash = ((hash << 5) + hash) ^ input.charCodeAt(index);
138
- }
139
- return (hash >>> 0).toString(36);
140
- }
141
-
142
- function getCompositionRevisionKey(model: unknown): string {
143
- const typed = (model || {}) as Record<string, unknown>;
144
- const sectionId =
145
- ((typed.section as Record<string, unknown> | undefined)?.identifier as string) ||
146
- (typed.sectionId as string) ||
147
- "";
148
- const currentItemIndex =
149
- typeof typed.currentItemIndex === "number" ? typed.currentItemIndex : -1;
150
- const renderables = Array.isArray(typed.renderables) ? typed.renderables : [];
151
- const renderableSignature = renderables
152
- .map((entry, index) => {
153
- const row = (entry || {}) as Record<string, unknown>;
154
- const entity = (row.entity || {}) as Record<string, unknown>;
155
- const entityId =
156
- (typeof entity.id === "string" && entity.id) || `renderable-${index}`;
157
- const entityVersion =
158
- (typeof entity.version === "string" && entity.version) ||
159
- (typeof entity.version === "number" ? String(entity.version) : "") ||
160
- (typeof (entity.config as Record<string, unknown> | undefined)?.version === "string"
161
- ? ((entity.config as Record<string, unknown>).version as string)
162
- : "");
163
- return `${entityId}:${entityVersion}`;
164
- })
165
- .join("|");
166
- const sessionsByItem = (typed.itemSessionsByItemId || {}) as Record<string, unknown>;
167
- const itemSessionSignature = Object.keys(sessionsByItem)
168
- .sort((left, right) => left.localeCompare(right))
169
- .map((itemId) => {
170
- const session = sessionsByItem[itemId] as Record<string, unknown> | undefined;
171
- const sessionId = (typeof session?.id === "string" && session.id) || "";
172
- const dataPayload = Array.isArray(session?.data) ? session.data : [];
173
- let payloadSignature = "";
174
- try {
175
- payloadSignature = hashString(JSON.stringify(dataPayload));
176
- } catch {
177
- payloadSignature = String(dataPayload.length);
178
- }
179
- return `${itemId}:${sessionId}:${payloadSignature}`;
180
- })
181
- .join("|");
182
- return `${sectionId}|${currentItemIndex}|${renderableSignature}|${itemSessionSignature}`;
183
- }
184
-
185
- function isKnownPlayerType(value: unknown): value is ItemPlayerType {
186
- return value === "iife" || value === "esm" || value === "preloaded" || value === "custom";
187
- }
188
-
189
- function normalizeItemPlayerConfig(
190
- hostPlayer: HostItemPlayerInput,
191
- hostPlayerType: ItemPlayerType | "",
192
- ): ItemPlayerConfig {
193
- const typeFromConfig = hostPlayer?.type;
194
- const rawType = isKnownPlayerType(typeFromConfig)
195
- ? typeFromConfig
196
- : isKnownPlayerType(hostPlayerType)
197
- ? hostPlayerType
198
- : hostPlayer?.tagName
199
- ? "custom"
200
- : "iife";
201
- const requestedTagName = hostPlayer?.tagName?.trim();
202
- if (rawType === "custom" && !requestedTagName) {
203
- return {
204
- type: "iife",
205
- tagName: DEFAULT_ITEM_PLAYER_BY_TYPE.iife,
206
- version: undefined,
207
- source: undefined,
208
- isDefault: true,
209
- };
210
- }
211
- return {
212
- type: rawType,
213
- tagName: requestedTagName || DEFAULT_ITEM_PLAYER_BY_TYPE[rawType],
214
- version: hostPlayer?.version,
215
- source: hostPlayer?.source,
216
- isDefault: !hostPlayer && !hostPlayerType,
217
- };
218
- }
219
-
220
- function normalizeEnv(input: unknown): { mode: string; role: string } {
221
- const envValue = ((input || {}) as Record<string, unknown>) || {};
222
- const mode =
223
- typeof envValue.mode === "string" && envValue.mode.trim()
224
- ? envValue.mode.trim()
225
- : DEFAULT_ENV.mode;
226
- const role =
227
- typeof envValue.role === "string" && envValue.role.trim()
228
- ? envValue.role.trim()
229
- : DEFAULT_ENV.role;
230
- return { mode, role };
231
- }
232
-
233
- function resolveSectionViewFromEnv(input: unknown): string {
234
- const resolvedEnv = normalizeEnv(input);
235
- if (resolvedEnv.mode === "author") return "author";
236
- if (resolvedEnv.role === "instructor") return "scorer";
237
- return "candidate";
238
- }
239
-
240
- async function createDefaultSectionController() {
241
- if (createSectionController) {
242
- return createSectionController() as any;
243
- }
244
- throw new Error(
245
- "pie-assessment-toolkit requires createSectionController when no coordinator hook provides one",
246
- );
247
- }
248
-
249
- function buildOwnedCoordinator(): ToolkitCoordinator {
250
- const fallbackAssessmentId =
251
- assessmentId ||
252
- (section as any)?.identifier ||
253
- `assessment-${Math.random().toString(16).slice(2)}`;
254
- return new ToolkitCoordinator({
255
- assessmentId: fallbackAssessmentId,
256
- lazyInit,
257
- tools: tools as any,
258
- accessibility: accessibility as any,
259
- });
260
- }
261
-
262
- const effectiveCoordinator = $derived.by(() => {
263
- if (isolation !== "force" && inheritedRuntime?.coordinator) {
264
- return inheritedRuntime.coordinator as ToolkitCoordinator;
265
- }
266
- return coordinator || ownedCoordinator;
267
- });
268
-
269
- $effect(() => {
270
- if (coordinator) {
271
- if (ownedCoordinator) {
272
- ownedCoordinator = null;
273
- }
274
- return;
275
- }
276
- if (isolation !== "force" && inheritedRuntime?.coordinator) {
277
- if (ownedCoordinator) {
278
- ownedCoordinator = null;
279
- }
280
- return;
281
- }
282
- if (!ownedCoordinator) {
283
- ownedCoordinator = buildOwnedCoordinator();
284
- }
285
- });
286
-
287
- const effectiveAssessmentId = $derived(
288
- assessmentId || effectiveCoordinator?.assessmentId || "",
289
- );
290
- const effectiveSectionId = $derived(
291
- sectionId || (section as any)?.identifier || `section-${effectiveAssessmentId || "default"}`,
292
- );
293
- const effectiveEnv = $derived.by(() => normalizeEnv(env));
294
- const effectiveSectionView = $derived.by(() => resolveSectionViewFromEnv(effectiveEnv));
295
- const effectiveItemPlayer = $derived.by(() =>
296
- normalizeItemPlayerConfig(player, playerType),
297
- );
298
- const runtimeContextValue = $derived.by((): AssessmentToolkitRuntimeContext | null => {
299
- if (!effectiveCoordinator) return null;
300
- const services = effectiveCoordinator.getServiceBundle();
301
- return {
302
- toolkitCoordinator: effectiveCoordinator,
303
- toolCoordinator: effectiveCoordinator.toolCoordinator,
304
- ttsService: services.ttsService,
305
- highlightCoordinator: services.highlightCoordinator,
306
- catalogResolver: services.catalogResolver,
307
- elementToolStateStore: services.elementToolStateStore,
308
- assessmentId: effectiveAssessmentId,
309
- sectionId: effectiveSectionId,
310
- itemPlayer: effectiveItemPlayer,
311
- reportSessionChanged: (itemId: string, detail: unknown) => {
312
- const result = sectionEngine.handleItemSessionChanged(itemId, detail);
313
- emitNormalizedSessionChanged({
314
- itemId,
315
- result,
316
- fallbackSession: detail,
317
- });
318
- },
319
- };
320
- });
321
- const hostRuntimeContextValue = $derived.by(
322
- (): AssessmentToolkitHostRuntimeContext | null => {
323
- if (!effectiveCoordinator) return null;
324
- return {
325
- runtimeId,
326
- coordinator: effectiveCoordinator,
327
- };
328
- },
329
- );
330
-
331
- function flushCompositionChanged(nextModel: unknown) {
332
- const nextRevisionKey = getCompositionRevisionKey(nextModel);
333
- if (nextRevisionKey === lastCompositionRevisionKey) {
334
- return;
335
- }
336
- lastCompositionRevisionKey = nextRevisionKey;
337
- compositionVersion += 1;
338
- compositionModel = nextModel;
339
- emit("composition-changed", {
340
- composition: compositionModel,
341
- version: compositionVersion,
342
- });
343
- }
344
-
345
- function emitCompositionChanged(nextModel?: unknown) {
346
- pendingCompositionModel = nextModel ?? sectionEngine.getCompositionModel();
347
- if (pendingCompositionEmit) return;
348
- pendingCompositionEmit = true;
349
- const flush = () => {
350
- pendingCompositionEmit = false;
351
- compositionEmitRaf = null;
352
- flushCompositionChanged(pendingCompositionModel);
353
- };
354
- if (typeof window !== "undefined" && typeof window.requestAnimationFrame === "function") {
355
- compositionEmitRaf = window.requestAnimationFrame(flush);
356
- return;
357
- }
358
- queueMicrotask(flush);
359
- }
360
-
361
- function emitNormalizedSessionChanged(args: {
362
- itemId: string;
363
- canonicalItemId?: string;
364
- result: unknown;
365
- fallbackSession: unknown;
366
- }) {
367
- const normalized =
368
- (args.result as SessionChangedLike | null)?.eventDetail || args.fallbackSession;
369
- const payload = {
370
- ...(normalized as Record<string, unknown>),
371
- itemId: args.itemId,
372
- canonicalItemId: args.canonicalItemId || args.itemId,
373
- sourceRuntimeId: runtimeId,
374
- } as Record<string, unknown>;
375
- if (
376
- !shouldEmitCanonicalSessionEvent({
377
- state: sessionEmitPolicyState,
378
- itemId: args.itemId,
379
- payload,
380
- })
381
- ) {
382
- return;
383
- }
384
- emit("session-changed", payload);
385
- }
386
-
387
- function isLocalToCurrentRuntime(eventTarget: EventTarget | null): boolean {
388
- if (!(eventTarget instanceof HTMLElement)) return false;
389
- const sourceRuntime = requestContext(
390
- eventTarget,
391
- assessmentToolkitHostRuntimeContext,
392
- );
393
- return sourceRuntime?.runtimeId === runtimeId;
394
- }
395
-
396
- $effect(() => {
397
- if (!host) return;
398
- if (isolation === "force") {
399
- inheritedRuntime = null;
400
- return;
401
- }
402
- return connectAssessmentToolkitHostRuntimeContext(host, (value) => {
403
- if (value.runtimeId === runtimeId) {
404
- return;
405
- }
406
- inheritedRuntime = value;
407
- });
408
- });
409
-
410
- $effect(() => {
411
- const parentRuntimeId =
412
- isolation !== "force" && inheritedRuntime ? inheritedRuntime.runtimeId : null;
413
- const ownership: "owned" | "inherited" = parentRuntimeId ? "inherited" : "owned";
414
- if (ownership !== lastOwnership) {
415
- lastOwnership = ownership;
416
- emit(ownership === "inherited" ? "runtime-inherited" : "runtime-owned", {
417
- runtimeId,
418
- parentRuntimeId,
419
- });
420
- }
421
- });
422
-
423
- $effect(() => {
424
- const currentSectionId = effectiveSectionId;
425
- const currentAttemptId = attemptId || "";
426
- void currentSectionId;
427
- void currentAttemptId;
428
- resetSessionEmitPolicyState(sessionEmitPolicyState);
429
- });
430
-
431
- $effect(() => {
432
- if (!host || !hostRuntimeContextValue) return;
433
- hostRuntimeProvider = new ContextProvider(host, {
434
- context: assessmentToolkitHostRuntimeContext,
435
- initialValue: hostRuntimeContextValue,
436
- });
437
- hostRuntimeProvider.connect();
438
- hostRuntimeRoot = new ContextRoot(host);
439
- hostRuntimeRoot.attach();
440
-
441
- return () => {
442
- hostRuntimeRoot?.detach();
443
- hostRuntimeRoot = null;
444
- hostRuntimeProvider?.disconnect();
445
- hostRuntimeProvider = null;
446
- };
447
- });
448
-
449
- $effect(() => {
450
- if (!hostRuntimeContextValue) return;
451
- hostRuntimeProvider?.setValue(hostRuntimeContextValue);
452
- });
453
-
454
- $effect(() => {
455
- if (!host) return;
456
- host.setAttribute("data-item-player-type", effectiveItemPlayer.type);
457
- host.setAttribute("data-item-player-tag", effectiveItemPlayer.tagName);
458
- host.setAttribute("data-env-mode", String((effectiveEnv as any)?.mode || ""));
459
- host.setAttribute("data-env-role", String((effectiveEnv as any)?.role || ""));
460
- });
461
-
462
- $effect(() => {
463
- if (!host || !runtimeContextValue) return;
464
- provider = new ContextProvider(host, {
465
- context: assessmentToolkitRuntimeContext,
466
- initialValue: runtimeContextValue,
467
- });
468
- provider.connect();
469
- contextRoot = new ContextRoot(host);
470
- contextRoot.attach();
471
-
472
- return () => {
473
- contextRoot?.detach();
474
- contextRoot = null;
475
- provider?.disconnect();
476
- provider = null;
477
- };
478
- });
479
-
480
- $effect(() => {
481
- if (runtimeContextValue) {
482
- provider?.setValue(runtimeContextValue);
483
- }
484
- });
485
-
486
- $effect(() => {
487
- if (!section || !effectiveCoordinator) return;
488
- let cancelled = false;
489
-
490
- void sectionEngine
491
- .initialize({
492
- coordinator: effectiveCoordinator,
493
- section,
494
- sectionId: effectiveSectionId,
495
- assessmentId: effectiveAssessmentId,
496
- attemptId: attemptId || undefined,
497
- view: effectiveSectionView,
498
- createDefaultController: createDefaultSectionController,
499
- onCompositionChanged: (nextComposition) => {
500
- if (cancelled) return;
501
- emitCompositionChanged(nextComposition);
502
- },
503
- })
504
- .then(() => {
505
- if (cancelled) return;
506
- emit("toolkit-ready", {
507
- runtimeId,
508
- assessmentId: effectiveAssessmentId,
509
- sectionId: effectiveSectionId,
510
- itemPlayer: effectiveItemPlayer,
511
- coordinator: effectiveCoordinator,
512
- });
513
- emit("section-ready", {
514
- sectionId: effectiveSectionId,
515
- });
516
- })
517
- .catch((error) => {
518
- runtimeError = error;
519
- sectionEngine.reportSectionError({
520
- source: "section-runtime",
521
- error,
522
- timestamp: Date.now(),
523
- });
524
- emit("runtime-error", {
525
- runtimeId,
526
- error,
527
- });
528
- });
529
-
530
- return () => {
531
- cancelled = true;
532
- };
533
- });
534
-
535
- $effect(() => {
536
- if (!host) return;
537
- const localHost = host;
538
-
539
- const onRegister = (event: Event) => {
540
- if (!isLocalToCurrentRuntime(event.target)) return;
541
- const detail = (event as CustomEvent<RuntimeRegistrationDetail>).detail;
542
- if (!detail?.element || !detail?.itemId) return;
543
- const changed = sectionEngine.register(detail);
544
- sectionEngine.handleContentRegistered(detail);
545
- if (changed) emitCompositionChanged();
546
- };
547
-
548
- const onUnregister = (event: Event) => {
549
- if (!isLocalToCurrentRuntime(event.target)) return;
550
- const detail = (event as CustomEvent<RuntimeRegistrationDetail>).detail;
551
- if (!detail?.itemId) return;
552
- const changed = detail?.element
553
- ? sectionEngine.unregister(detail.element)
554
- : false;
555
- sectionEngine.handleContentUnregistered(detail);
556
- if (changed) emitCompositionChanged();
557
- };
558
-
559
- const onItemSessionChanged = (event: Event) => {
560
- if (!isLocalToCurrentRuntime(event.target)) return;
561
- const detail = (event as CustomEvent<InternalItemSessionChangedDetail>).detail;
562
- if (!detail?.itemId) return;
563
- const result = sectionEngine.handleItemSessionChanged(detail.itemId, detail.session);
564
- emitNormalizedSessionChanged({
565
- itemId: detail.itemId,
566
- canonicalItemId: sectionEngine.getCanonicalItemId(detail.itemId),
567
- result,
568
- fallbackSession: detail.session,
569
- });
570
- };
571
- const onContentLoaded = (event: Event) => {
572
- if (!isLocalToCurrentRuntime(event.target)) return;
573
- const detail = (event as CustomEvent<InternalContentLoadedDetail>).detail;
574
- if (!detail?.itemId) return;
575
- sectionEngine.handleContentLoaded({
576
- itemId: detail.itemId,
577
- canonicalItemId: detail.canonicalItemId,
578
- contentKind: detail.contentKind,
579
- detail: detail.detail,
580
- timestamp: Date.now(),
581
- });
582
- };
583
- const onItemPlayerError = (event: Event) => {
584
- if (!isLocalToCurrentRuntime(event.target)) return;
585
- const detail = (event as CustomEvent<InternalItemPlayerErrorDetail>).detail;
586
- if (!detail?.itemId) return;
587
- sectionEngine.handleItemPlayerError({
588
- itemId: detail.itemId,
589
- canonicalItemId: detail.canonicalItemId,
590
- contentKind: detail.contentKind,
591
- error: detail.error,
592
- timestamp: Date.now(),
593
- });
594
- };
595
-
596
- localHost.addEventListener(PIE_REGISTER_EVENT, onRegister);
597
- localHost.addEventListener(PIE_UNREGISTER_EVENT, onUnregister);
598
- localHost.addEventListener(PIE_INTERNAL_ITEM_SESSION_CHANGED_EVENT, onItemSessionChanged);
599
- localHost.addEventListener(PIE_INTERNAL_CONTENT_LOADED_EVENT, onContentLoaded);
600
- localHost.addEventListener(PIE_INTERNAL_ITEM_PLAYER_ERROR_EVENT, onItemPlayerError);
601
- return () => {
602
- localHost.removeEventListener(PIE_REGISTER_EVENT, onRegister);
603
- localHost.removeEventListener(PIE_UNREGISTER_EVENT, onUnregister);
604
- localHost.removeEventListener(
605
- PIE_INTERNAL_ITEM_SESSION_CHANGED_EVENT,
606
- onItemSessionChanged,
607
- );
608
- localHost.removeEventListener(
609
- PIE_INTERNAL_CONTENT_LOADED_EVENT,
610
- onContentLoaded,
611
- );
612
- localHost.removeEventListener(
613
- PIE_INTERNAL_ITEM_PLAYER_ERROR_EVENT,
614
- onItemPlayerError,
615
- );
616
- };
617
- });
618
-
619
- export async function waitUntilReady(): Promise<void> {
620
- if (!effectiveCoordinator) {
621
- throw new Error("Coordinator not initialized");
622
- }
623
- await effectiveCoordinator.waitUntilReady();
624
- }
625
-
626
- export function getServiceBundle() {
627
- if (!effectiveCoordinator) {
628
- throw new Error("Coordinator not initialized");
629
- }
630
- return effectiveCoordinator.getServiceBundle();
631
- }
632
-
633
- export function setHooks(hooks: Record<string, unknown>): void {
634
- if (!effectiveCoordinator) {
635
- throw new Error("Coordinator not initialized");
636
- }
637
- effectiveCoordinator.setHooks(hooks as any);
638
- }
639
-
640
- export function navigateToItem(index: number): unknown {
641
- return sectionEngine.navigateToItem(index);
642
- }
643
-
644
- export function getCompositionModel(): unknown {
645
- return sectionEngine.getCompositionModel();
646
- }
647
-
648
- export function getItemPlayerConfig(): ItemPlayerConfig {
649
- return effectiveItemPlayer;
650
- }
651
-
652
- export async function persist(): Promise<void> {
653
- await sectionEngine.persist();
654
- }
655
-
656
- export async function hydrate(): Promise<void> {
657
- await sectionEngine.hydrate();
658
- }
659
-
660
- $effect(() => {
661
- return () => {
662
- if (compositionEmitRaf !== null && typeof window !== "undefined") {
663
- window.cancelAnimationFrame(compositionEmitRaf);
664
- compositionEmitRaf = null;
665
- }
666
- pendingCompositionEmit = false;
667
- void sectionEngine.dispose().catch((error) => {
668
- runtimeError = error;
669
- });
670
- };
671
- });
672
- </script>
673
-
674
- <div bind:this={anchor} class="pie-assessment-toolkit-anchor" aria-hidden="true"></div>
675
- <slot></slot>
676
-
677
- <style>
678
- .pie-assessment-toolkit-anchor {
679
- display: none;
680
- }
681
- </style>