@pie-players/pie-assessment-toolkit 0.2.10 → 0.3.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 (42) hide show
  1. package/dist/components/ItemToolBar.custom-element.js +12 -5
  2. package/dist/components/PieAssessmentToolkit.custom-element.js +75 -8
  3. package/dist/index.d.ts +1 -1
  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/runtime/SectionRuntimeEngine.d.ts +56 -0
  8. package/dist/runtime/SectionRuntimeEngine.d.ts.map +1 -1
  9. package/dist/runtime/SectionRuntimeEngine.js +23 -0
  10. package/dist/runtime/SectionRuntimeEngine.js.map +1 -1
  11. package/dist/runtime/registration-events.d.ts +19 -0
  12. package/dist/runtime/registration-events.d.ts.map +1 -1
  13. package/dist/runtime/registration-events.js +3 -0
  14. package/dist/runtime/registration-events.js.map +1 -1
  15. package/dist/runtime/runtime-event-guards.d.ts +0 -1
  16. package/dist/runtime/runtime-event-guards.d.ts.map +1 -1
  17. package/dist/runtime/runtime-event-guards.js +0 -5
  18. package/dist/runtime/runtime-event-guards.js.map +1 -1
  19. package/dist/runtime/session-event-emitter-policy.d.ts +12 -0
  20. package/dist/runtime/session-event-emitter-policy.d.ts.map +1 -0
  21. package/dist/runtime/session-event-emitter-policy.js +57 -0
  22. package/dist/runtime/session-event-emitter-policy.js.map +1 -0
  23. package/dist/services/ToolRegistry.d.ts +26 -0
  24. package/dist/services/ToolRegistry.d.ts.map +1 -1
  25. package/dist/services/ToolRegistry.js +20 -0
  26. package/dist/services/ToolRegistry.js.map +1 -1
  27. package/dist/services/ToolkitCoordinator.d.ts +13 -0
  28. package/dist/services/ToolkitCoordinator.d.ts.map +1 -1
  29. package/dist/services/ToolkitCoordinator.js +97 -0
  30. package/dist/services/ToolkitCoordinator.js.map +1 -1
  31. package/dist/services/interfaces.d.ts +6 -1
  32. package/dist/services/interfaces.d.ts.map +1 -1
  33. package/dist/services/interfaces.js.map +1 -1
  34. package/dist/tools/registrations/accessibility-tools.d.ts.map +1 -1
  35. package/dist/tools/registrations/accessibility-tools.js +2 -0
  36. package/dist/tools/registrations/accessibility-tools.js.map +1 -1
  37. package/dist/tools/registrations/interaction-tools.d.ts.map +1 -1
  38. package/dist/tools/registrations/interaction-tools.js +1 -0
  39. package/dist/tools/registrations/interaction-tools.js.map +1 -1
  40. package/package.json +9 -9
  41. package/src/components/ItemToolBar.svelte +21 -5
  42. package/src/components/PieAssessmentToolkit.svelte +81 -10
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pie-players/pie-assessment-toolkit",
3
- "version": "0.2.10",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "description": "PIE assessment toolkit: composable services + reference implementation for assessment players and tool coordination",
6
6
  "license": "MIT",
@@ -56,7 +56,7 @@
56
56
  "README.md"
57
57
  ],
58
58
  "scripts": {
59
- "build": "rm -rf dist tsconfig.tsbuildinfo && tsc -p tsconfig.json && node ./scripts/build-ce-components.mjs",
59
+ "build": "rm -rf dist tsconfig.tsbuildinfo && tsc -p tsconfig.json && bun ./scripts/build-ce-components.mjs",
60
60
  "dev": "tsc -p tsconfig.json --watch",
61
61
  "typecheck": "tsc -p tsconfig.json --noEmit",
62
62
  "lint": "biome check .",
@@ -64,12 +64,12 @@
64
64
  "test": "bun test"
65
65
  },
66
66
  "dependencies": {
67
- "@pie-players/pie-calculator": "0.1.5",
68
- "@pie-players/pie-calculator-desmos": "0.1.6",
69
- "@pie-players/pie-context": "0.1.2",
70
- "@pie-players/pie-players-shared": "0.2.6",
71
- "@pie-players/pie-tts": "0.1.5",
72
- "@pie-players/tts-client-server": "0.2.5",
67
+ "@pie-players/pie-calculator": "0.3.0",
68
+ "@pie-players/pie-calculator-desmos": "0.3.0",
69
+ "@pie-players/pie-context": "0.3.0",
70
+ "@pie-players/pie-players-shared": "0.3.0",
71
+ "@pie-players/pie-tts": "0.3.0",
72
+ "@pie-players/tts-client-server": "0.3.0",
73
73
  "daisyui": "^5.5.18"
74
74
  },
75
75
  "peerDependencies": {
@@ -84,7 +84,7 @@
84
84
  "url": "https://github.com/pie-framework/pie-players/issues"
85
85
  },
86
86
  "engines": {
87
- "node": ">=18.0.0"
87
+ "node": ">=20.0.0"
88
88
  },
89
89
  "sideEffects": true
90
90
  }
@@ -170,17 +170,30 @@
170
170
  const resolved = resolveToolsForLevel(effectiveToolsConfig, placementLevel);
171
171
  return effectiveToolRegistry.normalizeToolIds(resolved).filter(Boolean);
172
172
  });
173
+ const providerConfig = $derived(
174
+ (effectiveToolsConfig?.providers || {}) as Record<
175
+ string,
176
+ { enabled?: boolean } | undefined
177
+ >
178
+ );
179
+ const isProviderEnabled = (toolId: string): boolean =>
180
+ providerConfig[toolId]?.enabled !== false;
173
181
 
174
182
  // Pass 1: determine allowed tools
175
183
  const allowedToolIds = $derived.by(() => {
176
184
  const configuredTools =
177
185
  normalizedExplicitTools.length > 0 ? normalizedExplicitTools : placementAllowedToolIds;
186
+ const providerEnabledConfiguredTools = configuredTools.filter((toolId) =>
187
+ isProviderEnabled(toolId)
188
+ );
178
189
  if (pnpResolver && assessment && itemRef) {
179
190
  const allowedByPnp = pnpResolver.getAllowedToolIds(assessment, itemRef);
180
- if (configuredTools.length === 0) return allowedByPnp;
181
- return allowedByPnp.filter((toolId) => configuredTools.includes(toolId));
191
+ if (providerEnabledConfiguredTools.length === 0) return allowedByPnp;
192
+ return allowedByPnp.filter((toolId) =>
193
+ providerEnabledConfiguredTools.includes(toolId)
194
+ );
182
195
  }
183
- return configuredTools;
196
+ return providerEnabledConfiguredTools;
184
197
  });
185
198
 
186
199
  const contentReady = $derived.by(() => {
@@ -285,6 +298,9 @@
285
298
 
286
299
  return Array.from(visible);
287
300
  });
301
+ const toolbarVisibleToolIds = $derived.by(() =>
302
+ effectiveToolRegistry.filterToolIdsByActivation(visibleToolIds, 'toolbar-toggle')
303
+ );
288
304
 
289
305
  // Dynamically load whatever tools are currently visible.
290
306
  // The registry owns module loader configuration by toolId.
@@ -292,7 +308,7 @@
292
308
  if (!isBrowser) return;
293
309
  let cancelled = false;
294
310
  void effectiveToolRegistry
295
- .ensureToolModulesLoaded(visibleToolIds)
311
+ .ensureToolModulesLoaded(toolbarVisibleToolIds)
296
312
  .then(() => {
297
313
  if (!cancelled) moduleLoadVersion += 1;
298
314
  })
@@ -377,7 +393,7 @@
377
393
  moduleLoadVersion;
378
394
 
379
395
  const rendered: ToolToolbarRenderResult[] = [];
380
- for (const toolId of visibleToolIds) {
396
+ for (const toolId of toolbarVisibleToolIds) {
381
397
  const result = effectiveToolRegistry.renderForToolbar(toolId, renderContext, toolbarContext);
382
398
  if (result) {
383
399
  rendered.push(result);
@@ -38,18 +38,26 @@
38
38
  import { connectAssessmentToolkitHostRuntimeContext } from "../context/runtime-context-consumer.js";
39
39
  import { ToolkitCoordinator } from "../services/ToolkitCoordinator.js";
40
40
  import {
41
- PIE_ITEM_SESSION_CHANGED_EVENT,
41
+ PIE_INTERNAL_CONTENT_LOADED_EVENT,
42
+ PIE_INTERNAL_ITEM_SESSION_CHANGED_EVENT,
43
+ PIE_INTERNAL_ITEM_PLAYER_ERROR_EVENT,
42
44
  PIE_REGISTER_EVENT,
43
45
  PIE_UNREGISTER_EVENT,
44
- type ItemSessionChangedDetail,
46
+ type InternalContentLoadedDetail,
47
+ type InternalItemSessionChangedDetail,
48
+ type InternalItemPlayerErrorDetail,
45
49
  type RuntimeRegistrationDetail,
46
50
  } from "../runtime/registration-events.js";
47
51
  import { dispatchCrossBoundaryEvent } from "../runtime/tool-host-contract.js";
48
52
  import { SectionRuntimeEngine } from "../runtime/SectionRuntimeEngine.js";
49
53
  import {
50
54
  createRuntimeId,
51
- shouldHandleBySourceRuntime,
52
55
  } from "../runtime/runtime-event-guards.js";
56
+ import {
57
+ createSessionEmitPolicyState,
58
+ resetSessionEmitPolicyState,
59
+ shouldEmitCanonicalSessionEvent,
60
+ } from "../runtime/session-event-emitter-policy.js";
53
61
 
54
62
  type SessionChangedLike = {
55
63
  eventDetail?: unknown;
@@ -106,6 +114,7 @@ const DEFAULT_ENV = {
106
114
  let pendingCompositionModel: unknown = null;
107
115
  let pendingCompositionEmit = false;
108
116
  let compositionEmitRaf: number | null = null;
117
+ const sessionEmitPolicyState = createSessionEmitPolicyState();
109
118
 
110
119
  function getHostElement(): HTMLElement | null {
111
120
  if (!anchor) return null;
@@ -357,12 +366,22 @@ const DEFAULT_ENV = {
357
366
  }) {
358
367
  const normalized =
359
368
  (args.result as SessionChangedLike | null)?.eventDetail || args.fallbackSession;
360
- emit("session-changed", {
369
+ const payload = {
361
370
  ...(normalized as Record<string, unknown>),
362
371
  itemId: args.itemId,
363
372
  canonicalItemId: args.canonicalItemId || args.itemId,
364
373
  sourceRuntimeId: runtimeId,
365
- });
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);
366
385
  }
367
386
 
368
387
  function isLocalToCurrentRuntime(eventTarget: EventTarget | null): boolean {
@@ -401,6 +420,14 @@ const DEFAULT_ENV = {
401
420
  }
402
421
  });
403
422
 
423
+ $effect(() => {
424
+ const currentSectionId = effectiveSectionId;
425
+ const currentAttemptId = attemptId || "";
426
+ void currentSectionId;
427
+ void currentAttemptId;
428
+ resetSessionEmitPolicyState(sessionEmitPolicyState);
429
+ });
430
+
404
431
  $effect(() => {
405
432
  if (!host || !hostRuntimeContextValue) return;
406
433
  hostRuntimeProvider = new ContextProvider(host, {
@@ -489,6 +516,11 @@ const DEFAULT_ENV = {
489
516
  })
490
517
  .catch((error) => {
491
518
  runtimeError = error;
519
+ sectionEngine.reportSectionError({
520
+ source: "section-runtime",
521
+ error,
522
+ timestamp: Date.now(),
523
+ });
492
524
  emit("runtime-error", {
493
525
  runtimeId,
494
526
  error,
@@ -509,39 +541,78 @@ const DEFAULT_ENV = {
509
541
  const detail = (event as CustomEvent<RuntimeRegistrationDetail>).detail;
510
542
  if (!detail?.element || !detail?.itemId) return;
511
543
  const changed = sectionEngine.register(detail);
544
+ sectionEngine.handleContentRegistered(detail);
512
545
  if (changed) emitCompositionChanged();
513
546
  };
514
547
 
515
548
  const onUnregister = (event: Event) => {
516
549
  if (!isLocalToCurrentRuntime(event.target)) return;
517
550
  const detail = (event as CustomEvent<RuntimeRegistrationDetail>).detail;
551
+ if (!detail?.itemId) return;
518
552
  const changed = detail?.element
519
553
  ? sectionEngine.unregister(detail.element)
520
554
  : false;
555
+ sectionEngine.handleContentUnregistered(detail);
521
556
  if (changed) emitCompositionChanged();
522
557
  };
523
558
 
524
559
  const onItemSessionChanged = (event: Event) => {
525
560
  if (!isLocalToCurrentRuntime(event.target)) return;
526
- const detail = (event as CustomEvent<ItemSessionChangedDetail>).detail;
561
+ const detail = (event as CustomEvent<InternalItemSessionChangedDetail>).detail;
527
562
  if (!detail?.itemId) return;
528
- if (!shouldHandleBySourceRuntime(detail.sourceRuntimeId, runtimeId)) return;
529
563
  const result = sectionEngine.handleItemSessionChanged(detail.itemId, detail.session);
530
564
  emitNormalizedSessionChanged({
531
565
  itemId: detail.itemId,
532
- canonicalItemId: detail.canonicalItemId,
566
+ canonicalItemId: sectionEngine.getCanonicalItemId(detail.itemId),
533
567
  result,
534
568
  fallbackSession: detail.session,
535
569
  });
536
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
+ };
537
595
 
538
596
  localHost.addEventListener(PIE_REGISTER_EVENT, onRegister);
539
597
  localHost.addEventListener(PIE_UNREGISTER_EVENT, onUnregister);
540
- localHost.addEventListener(PIE_ITEM_SESSION_CHANGED_EVENT, onItemSessionChanged);
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);
541
601
  return () => {
542
602
  localHost.removeEventListener(PIE_REGISTER_EVENT, onRegister);
543
603
  localHost.removeEventListener(PIE_UNREGISTER_EVENT, onUnregister);
544
- localHost.removeEventListener(PIE_ITEM_SESSION_CHANGED_EVENT, onItemSessionChanged);
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
+ );
545
616
  };
546
617
  });
547
618