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

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 (112) hide show
  1. package/dist/components/ItemToolBar.custom-element.js +425 -93
  2. package/dist/components/PieAssessmentToolkit.custom-element.js +200 -46
  3. package/dist/components/SectionToolBar.custom-element.js +162 -0
  4. package/dist/components/section-toolbar-element.d.ts +3 -0
  5. package/dist/components/section-toolbar-element.d.ts.map +1 -0
  6. package/dist/components/section-toolbar-element.js +2 -0
  7. package/dist/components/section-toolbar-element.js.map +1 -0
  8. package/dist/context/assessment-toolkit-context.d.ts +1 -1
  9. package/dist/context/assessment-toolkit-context.d.ts.map +1 -1
  10. package/dist/index.d.ts +5 -3
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +2 -1
  13. package/dist/index.js.map +1 -1
  14. package/dist/runtime/SectionRuntimeEngine.d.ts +57 -1
  15. package/dist/runtime/SectionRuntimeEngine.d.ts.map +1 -1
  16. package/dist/runtime/SectionRuntimeEngine.js +23 -0
  17. package/dist/runtime/SectionRuntimeEngine.js.map +1 -1
  18. package/dist/runtime/registration-events.d.ts +19 -0
  19. package/dist/runtime/registration-events.d.ts.map +1 -1
  20. package/dist/runtime/registration-events.js +3 -0
  21. package/dist/runtime/registration-events.js.map +1 -1
  22. package/dist/runtime/runtime-event-guards.d.ts +0 -1
  23. package/dist/runtime/runtime-event-guards.d.ts.map +1 -1
  24. package/dist/runtime/runtime-event-guards.js +0 -5
  25. package/dist/runtime/runtime-event-guards.js.map +1 -1
  26. package/dist/runtime/session-event-emitter-policy.d.ts +12 -0
  27. package/dist/runtime/session-event-emitter-policy.d.ts.map +1 -0
  28. package/dist/runtime/session-event-emitter-policy.js +57 -0
  29. package/dist/runtime/session-event-emitter-policy.js.map +1 -0
  30. package/dist/services/HighlightCoordinator.d.ts +9 -0
  31. package/dist/services/HighlightCoordinator.d.ts.map +1 -1
  32. package/dist/services/HighlightCoordinator.js +196 -55
  33. package/dist/services/HighlightCoordinator.js.map +1 -1
  34. package/dist/services/TTSService.d.ts +18 -0
  35. package/dist/services/TTSService.d.ts.map +1 -1
  36. package/dist/services/TTSService.js +343 -105
  37. package/dist/services/TTSService.js.map +1 -1
  38. package/dist/services/ToolRegistry.d.ts +40 -2
  39. package/dist/services/ToolRegistry.d.ts.map +1 -1
  40. package/dist/services/ToolRegistry.js +22 -1
  41. package/dist/services/ToolRegistry.js.map +1 -1
  42. package/dist/services/ToolkitCoordinator.d.ts +34 -1
  43. package/dist/services/ToolkitCoordinator.d.ts.map +1 -1
  44. package/dist/services/ToolkitCoordinator.js +203 -2
  45. package/dist/services/ToolkitCoordinator.js.map +1 -1
  46. package/dist/services/createDefaultToolRegistry.d.ts +5 -11
  47. package/dist/services/createDefaultToolRegistry.d.ts.map +1 -1
  48. package/dist/services/createDefaultToolRegistry.js +5 -48
  49. package/dist/services/createDefaultToolRegistry.js.map +1 -1
  50. package/dist/services/interfaces.d.ts +7 -2
  51. package/dist/services/interfaces.d.ts.map +1 -1
  52. package/dist/services/interfaces.js.map +1 -1
  53. package/dist/services/pnp-standard-features.d.ts +1 -1
  54. package/dist/services/section-controller-types.d.ts +5 -0
  55. package/dist/services/section-controller-types.d.ts.map +1 -1
  56. package/dist/services/tool-config-defaults.d.ts +14 -0
  57. package/dist/services/tool-config-defaults.d.ts.map +1 -0
  58. package/dist/services/tool-config-defaults.js +23 -0
  59. package/dist/services/tool-config-defaults.js.map +1 -0
  60. package/dist/services/tool-context.d.ts.map +1 -1
  61. package/dist/services/tool-context.js +50 -3
  62. package/dist/services/tool-context.js.map +1 -1
  63. package/dist/services/tool-instance-id.d.ts +1 -3
  64. package/dist/services/tool-instance-id.d.ts.map +1 -1
  65. package/dist/services/tool-instance-id.js +4 -10
  66. package/dist/services/tool-instance-id.js.map +1 -1
  67. package/dist/services/toolbar-items.d.ts +26 -0
  68. package/dist/services/toolbar-items.d.ts.map +1 -0
  69. package/dist/services/toolbar-items.js +10 -0
  70. package/dist/services/toolbar-items.js.map +1 -0
  71. package/dist/services/tools-config-normalizer.d.ts.map +1 -1
  72. package/dist/services/tools-config-normalizer.js +5 -6
  73. package/dist/services/tools-config-normalizer.js.map +1 -1
  74. package/dist/services/tts/browser-provider.d.ts.map +1 -1
  75. package/dist/services/tts/browser-provider.js +201 -34
  76. package/dist/services/tts/browser-provider.js.map +1 -1
  77. package/dist/services/tts/text-processing.d.ts +17 -0
  78. package/dist/services/tts/text-processing.d.ts.map +1 -0
  79. package/dist/services/tts/text-processing.js +144 -0
  80. package/dist/services/tts/text-processing.js.map +1 -0
  81. package/dist/tools/default-tool-module-loaders.d.ts.map +1 -1
  82. package/dist/tools/default-tool-module-loaders.js +1 -7
  83. package/dist/tools/default-tool-module-loaders.js.map +1 -1
  84. package/dist/tools/registrations/accessibility-tools.d.ts +7 -2
  85. package/dist/tools/registrations/accessibility-tools.d.ts.map +1 -1
  86. package/dist/tools/registrations/accessibility-tools.js +23 -15
  87. package/dist/tools/registrations/accessibility-tools.js.map +1 -1
  88. package/dist/tools/registrations/calculator.d.ts.map +1 -1
  89. package/dist/tools/registrations/calculator.js +18 -10
  90. package/dist/tools/registrations/calculator.js.map +1 -1
  91. package/dist/tools/registrations/interaction-tools.d.ts.map +1 -1
  92. package/dist/tools/registrations/interaction-tools.js +5 -4
  93. package/dist/tools/registrations/interaction-tools.js.map +1 -1
  94. package/dist/tools/registrations/measurement-tools.js +4 -4
  95. package/dist/tools/registrations/measurement-tools.js.map +1 -1
  96. package/dist/tools/registrations/subject-specific-tools.js +4 -4
  97. package/dist/tools/registrations/subject-specific-tools.js.map +1 -1
  98. package/dist/tools/registrations/tts.d.ts.map +1 -1
  99. package/dist/tools/registrations/tts.js +161 -17
  100. package/dist/tools/registrations/tts.js.map +1 -1
  101. package/dist/tools/tool-tag-map.d.ts.map +1 -1
  102. package/dist/tools/tool-tag-map.js +2 -1
  103. package/dist/tools/tool-tag-map.js.map +1 -1
  104. package/package.json +12 -8
  105. package/src/components/ItemToolBar.svelte +275 -47
  106. package/src/components/PieAssessmentToolkit.svelte +217 -53
  107. package/src/components/SectionToolBar.svelte +58 -0
  108. package/dist/services/tts/provider-interface.d.ts +0 -131
  109. package/dist/services/tts/provider-interface.d.ts.map +0 -1
  110. package/dist/services/tts/provider-interface.js +0 -10
  111. package/dist/services/tts/provider-interface.js.map +0 -1
  112. package/src/components/PNPProvenanceViewer.svelte +0 -270
@@ -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;
@@ -61,10 +69,14 @@
61
69
 
62
70
  const runtimeId = createRuntimeId("toolkit");
63
71
  const sectionEngine = new SectionRuntimeEngine();
72
+ const DEFAULT_ENV = {
73
+ mode: "gather",
74
+ role: "student",
75
+ } as const;
64
76
  const DEFAULT_ITEM_PLAYER_BY_TYPE: Record<ItemPlayerType, string> = {
65
- iife: "pie-iife-player",
66
- esm: "pie-esm-player",
67
- fixed: "pie-fixed-player",
77
+ iife: "pie-item-player",
78
+ esm: "pie-item-player",
79
+ preloaded: "pie-item-player",
68
80
  custom: "",
69
81
  };
70
82
 
@@ -98,7 +110,11 @@
98
110
  let compositionVersion = $state(0);
99
111
  let compositionModel = $state<unknown>(null);
100
112
  let runtimeError = $state<unknown>(null);
101
- let lastCompositionSignature = $state<string>("");
113
+ let lastCompositionRevisionKey = $state("");
114
+ let pendingCompositionModel: unknown = null;
115
+ let pendingCompositionEmit = false;
116
+ let compositionEmitRaf: number | null = null;
117
+ const sessionEmitPolicyState = createSessionEmitPolicyState();
102
118
 
103
119
  function getHostElement(): HTMLElement | null {
104
120
  if (!anchor) return null;
@@ -115,8 +131,59 @@
115
131
  dispatchCrossBoundaryEvent(host, name, detail);
116
132
  }
117
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
+
118
185
  function isKnownPlayerType(value: unknown): value is ItemPlayerType {
119
- return value === "iife" || value === "esm" || value === "fixed" || value === "custom";
186
+ return value === "iife" || value === "esm" || value === "preloaded" || value === "custom";
120
187
  }
121
188
 
122
189
  function normalizeItemPlayerConfig(
@@ -150,6 +217,26 @@
150
217
  };
151
218
  }
152
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
+
153
240
  async function createDefaultSectionController() {
154
241
  if (createSectionController) {
155
242
  return createSectionController() as any;
@@ -203,6 +290,8 @@
203
290
  const effectiveSectionId = $derived(
204
291
  sectionId || (section as any)?.identifier || `section-${effectiveAssessmentId || "default"}`,
205
292
  );
293
+ const effectiveEnv = $derived.by(() => normalizeEnv(env));
294
+ const effectiveSectionView = $derived.by(() => resolveSectionViewFromEnv(effectiveEnv));
206
295
  const effectiveItemPlayer = $derived.by(() =>
207
296
  normalizeItemPlayerConfig(player, playerType),
208
297
  );
@@ -221,10 +310,10 @@
221
310
  itemPlayer: effectiveItemPlayer,
222
311
  reportSessionChanged: (itemId: string, detail: unknown) => {
223
312
  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,
313
+ emitNormalizedSessionChanged({
314
+ itemId,
315
+ result,
316
+ fallbackSession: detail,
228
317
  });
229
318
  },
230
319
  };
@@ -239,35 +328,12 @@
239
328
  },
240
329
  );
241
330
 
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) {
331
+ function flushCompositionChanged(nextModel: unknown) {
332
+ const nextRevisionKey = getCompositionRevisionKey(nextModel);
333
+ if (nextRevisionKey === lastCompositionRevisionKey) {
268
334
  return;
269
335
  }
270
- lastCompositionSignature = signature;
336
+ lastCompositionRevisionKey = nextRevisionKey;
271
337
  compositionVersion += 1;
272
338
  compositionModel = nextModel;
273
339
  emit("composition-changed", {
@@ -276,6 +342,48 @@
276
342
  });
277
343
  }
278
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
+
279
387
  function isLocalToCurrentRuntime(eventTarget: EventTarget | null): boolean {
280
388
  if (!(eventTarget instanceof HTMLElement)) return false;
281
389
  const sourceRuntime = requestContext(
@@ -312,6 +420,14 @@
312
420
  }
313
421
  });
314
422
 
423
+ $effect(() => {
424
+ const currentSectionId = effectiveSectionId;
425
+ const currentAttemptId = attemptId || "";
426
+ void currentSectionId;
427
+ void currentAttemptId;
428
+ resetSessionEmitPolicyState(sessionEmitPolicyState);
429
+ });
430
+
315
431
  $effect(() => {
316
432
  if (!host || !hostRuntimeContextValue) return;
317
433
  hostRuntimeProvider = new ContextProvider(host, {
@@ -339,8 +455,8 @@
339
455
  if (!host) return;
340
456
  host.setAttribute("data-item-player-type", effectiveItemPlayer.type);
341
457
  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 || ""));
458
+ host.setAttribute("data-env-mode", String((effectiveEnv as any)?.mode || ""));
459
+ host.setAttribute("data-env-role", String((effectiveEnv as any)?.role || ""));
344
460
  });
345
461
 
346
462
  $effect(() => {
@@ -378,12 +494,11 @@
378
494
  sectionId: effectiveSectionId,
379
495
  assessmentId: effectiveAssessmentId,
380
496
  attemptId: attemptId || undefined,
381
- view,
497
+ view: effectiveSectionView,
382
498
  createDefaultController: createDefaultSectionController,
383
499
  onCompositionChanged: (nextComposition) => {
384
500
  if (cancelled) return;
385
- compositionModel = nextComposition;
386
- emitCompositionChanged();
501
+ emitCompositionChanged(nextComposition);
387
502
  },
388
503
  })
389
504
  .then(() => {
@@ -393,6 +508,7 @@
393
508
  assessmentId: effectiveAssessmentId,
394
509
  sectionId: effectiveSectionId,
395
510
  itemPlayer: effectiveItemPlayer,
511
+ coordinator: effectiveCoordinator,
396
512
  });
397
513
  emit("section-ready", {
398
514
  sectionId: effectiveSectionId,
@@ -400,6 +516,11 @@
400
516
  })
401
517
  .catch((error) => {
402
518
  runtimeError = error;
519
+ sectionEngine.reportSectionError({
520
+ source: "section-runtime",
521
+ error,
522
+ timestamp: Date.now(),
523
+ });
403
524
  emit("runtime-error", {
404
525
  runtimeId,
405
526
  error,
@@ -420,40 +541,78 @@
420
541
  const detail = (event as CustomEvent<RuntimeRegistrationDetail>).detail;
421
542
  if (!detail?.element || !detail?.itemId) return;
422
543
  const changed = sectionEngine.register(detail);
544
+ sectionEngine.handleContentRegistered(detail);
423
545
  if (changed) emitCompositionChanged();
424
546
  };
425
547
 
426
548
  const onUnregister = (event: Event) => {
427
549
  if (!isLocalToCurrentRuntime(event.target)) return;
428
550
  const detail = (event as CustomEvent<RuntimeRegistrationDetail>).detail;
551
+ if (!detail?.itemId) return;
429
552
  const changed = detail?.element
430
553
  ? sectionEngine.unregister(detail.element)
431
554
  : false;
555
+ sectionEngine.handleContentUnregistered(detail);
432
556
  if (changed) emitCompositionChanged();
433
557
  };
434
558
 
435
559
  const onItemSessionChanged = (event: Event) => {
436
560
  if (!isLocalToCurrentRuntime(event.target)) return;
437
- const detail = (event as CustomEvent<ItemSessionChangedDetail>).detail;
561
+ const detail = (event as CustomEvent<InternalItemSessionChangedDetail>).detail;
438
562
  if (!detail?.itemId) return;
439
- if (!shouldHandleBySourceRuntime(detail.sourceRuntimeId, runtimeId)) return;
440
563
  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>),
564
+ emitNormalizedSessionChanged({
444
565
  itemId: detail.itemId,
445
- canonicalItemId: detail.canonicalItemId || detail.itemId,
446
- sourceRuntimeId: runtimeId,
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(),
447
593
  });
448
594
  };
449
595
 
450
596
  localHost.addEventListener(PIE_REGISTER_EVENT, onRegister);
451
597
  localHost.addEventListener(PIE_UNREGISTER_EVENT, onUnregister);
452
- 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);
453
601
  return () => {
454
602
  localHost.removeEventListener(PIE_REGISTER_EVENT, onRegister);
455
603
  localHost.removeEventListener(PIE_UNREGISTER_EVENT, onUnregister);
456
- 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
+ );
457
616
  };
458
617
  });
459
618
 
@@ -500,6 +659,11 @@
500
659
 
501
660
  $effect(() => {
502
661
  return () => {
662
+ if (compositionEmitRaf !== null && typeof window !== "undefined") {
663
+ window.cancelAnimationFrame(compositionEmitRaf);
664
+ compositionEmitRaf = null;
665
+ }
666
+ pendingCompositionEmit = false;
503
667
  void sectionEngine.dispose().catch((error) => {
504
668
  runtimeError = error;
505
669
  });
@@ -0,0 +1,58 @@
1
+ <svelte:options
2
+ customElement={{
3
+ tag: "pie-section-toolbar",
4
+ shadow: "none",
5
+ props: {
6
+ enabledTools: { type: "String", attribute: "enabled-tools" },
7
+ position: { type: "String", attribute: "position" },
8
+ sectionId: { type: "String", attribute: "section-id" },
9
+ size: { type: "String", attribute: "size" },
10
+ language: { type: "String", attribute: "language" },
11
+ toolRegistry: { type: "Object", reflect: false },
12
+ pnpResolver: { type: "Object", reflect: false },
13
+ assessment: { type: "Object", reflect: false },
14
+ itemRef: { type: "Object", reflect: false },
15
+ item: { type: "Object", reflect: false },
16
+ hostButtons: { type: "Object", reflect: false },
17
+ },
18
+ }}
19
+ />
20
+
21
+ <script lang="ts">
22
+ import "./item-toolbar-element.js";
23
+ import type { ToolRegistry } from "../services/ToolRegistry.js";
24
+ import type { PNPToolResolver } from "../services/PNPToolResolver.js";
25
+ import type { AssessmentEntity, AssessmentItemRef, ItemEntity } from "@pie-players/pie-players-shared/types";
26
+ import type { ToolbarItem } from "../services/toolbar-items.js";
27
+
28
+ let {
29
+ enabledTools = "",
30
+ position = "bottom",
31
+ sectionId = "",
32
+ size = "md" as "sm" | "md" | "lg",
33
+ language = "en-US",
34
+ toolRegistry = null as ToolRegistry | null,
35
+ pnpResolver = null as PNPToolResolver | null,
36
+ assessment = null as AssessmentEntity | null,
37
+ itemRef = null as AssessmentItemRef | null,
38
+ item = null as ItemEntity | null,
39
+ hostButtons = [] as ToolbarItem[],
40
+ } = $props();
41
+ </script>
42
+
43
+ <pie-item-toolbar
44
+ level="section"
45
+ scope-id={sectionId}
46
+ section-id={sectionId}
47
+ tools={enabledTools}
48
+ content-kind="section"
49
+ position={position}
50
+ {size}
51
+ {language}
52
+ {toolRegistry}
53
+ {pnpResolver}
54
+ {assessment}
55
+ {itemRef}
56
+ {item}
57
+ {hostButtons}
58
+ ></pie-item-toolbar>
@@ -1,131 +0,0 @@
1
- /**
2
- * TTS Provider Interfaces
3
- *
4
- * Defines the contract for text-to-speech providers.
5
- * Providers are stateless factories that create configured TTS implementations.
6
- *
7
- * Part of PIE Assessment Toolkit.
8
- */
9
- import type { TTSConfig } from "../TTSService.js";
10
- /**
11
- * TTS Provider interface
12
- *
13
- * Providers are stateless factories that create TTS implementations.
14
- * They describe capabilities and create configured instances.
15
- */
16
- export interface ITTSProvider {
17
- /**
18
- * Unique identifier for this provider
19
- */
20
- readonly providerId: string;
21
- /**
22
- * Human-readable provider name
23
- */
24
- readonly providerName: string;
25
- /**
26
- * Provider version
27
- */
28
- readonly version: string;
29
- /**
30
- * Initialize and create a configured TTS implementation
31
- */
32
- initialize(config: TTSConfig): Promise<ITTSProviderImplementation>;
33
- /**
34
- * Check if a specific feature is supported
35
- */
36
- supportsFeature(feature: TTSFeature): boolean;
37
- /**
38
- * Get provider capabilities
39
- */
40
- getCapabilities(): TTSProviderCapabilities;
41
- /**
42
- * Clean up provider resources
43
- */
44
- destroy(): void;
45
- }
46
- /**
47
- * TTS Provider Implementation interface
48
- *
49
- * The actual TTS implementation that handles playback.
50
- * Created by ITTSProvider.initialize()
51
- */
52
- export interface ITTSProviderImplementation {
53
- /**
54
- * Speak text
55
- */
56
- speak(text: string): Promise<void>;
57
- /**
58
- * Pause playback
59
- */
60
- pause(): void;
61
- /**
62
- * Resume playback
63
- */
64
- resume(): void;
65
- /**
66
- * Stop playback
67
- */
68
- stop(): void;
69
- /**
70
- * Check if currently playing
71
- */
72
- isPlaying(): boolean;
73
- /**
74
- * Check if paused
75
- */
76
- isPaused(): boolean;
77
- /**
78
- * Update settings dynamically (optional)
79
- * Allows changing rate, pitch, and voice without full reinitialization.
80
- * Changes take effect on the next speak() call.
81
- */
82
- updateSettings?(settings: Partial<TTSConfig>): void | Promise<void>;
83
- /**
84
- * Word boundary callback (optional)
85
- * Called during speech for word highlighting
86
- * @param word - The word being spoken (may be empty for server providers)
87
- * @param position - Character position in the text
88
- * @param length - Optional word length (for server providers with speech marks)
89
- */
90
- onWordBoundary?: (word: string, position: number, length?: number) => void;
91
- }
92
- /**
93
- * TTS Provider capabilities
94
- *
95
- * Describes which features a provider supports
96
- */
97
- export interface TTSProviderCapabilities {
98
- /**
99
- * Supports pause/resume
100
- */
101
- supportsPause: boolean;
102
- /**
103
- * Supports resume after pause
104
- */
105
- supportsResume: boolean;
106
- /**
107
- * Supports word boundary events for highlighting
108
- */
109
- supportsWordBoundary: boolean;
110
- /**
111
- * Supports voice selection
112
- */
113
- supportsVoiceSelection: boolean;
114
- /**
115
- * Supports rate control (speed)
116
- */
117
- supportsRateControl: boolean;
118
- /**
119
- * Supports pitch control
120
- */
121
- supportsPitchControl: boolean;
122
- /**
123
- * Maximum text length (if limited)
124
- */
125
- maxTextLength?: number;
126
- }
127
- /**
128
- * TTS features for capability checking
129
- */
130
- export type TTSFeature = "pause" | "resume" | "wordBoundary" | "voiceSelection" | "rateControl" | "pitchControl";
131
- //# sourceMappingURL=provider-interface.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"provider-interface.d.ts","sourceRoot":"","sources":["../../../src/services/tts/provider-interface.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAElD;;;;;GAKG;AACH,MAAM,WAAW,YAAY;IAC5B;;OAEG;IACH,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAE5B;;OAEG;IACH,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAE9B;;OAEG;IACH,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IAEzB;;OAEG;IACH,UAAU,CAAC,MAAM,EAAE,SAAS,GAAG,OAAO,CAAC,0BAA0B,CAAC,CAAC;IAEnE;;OAEG;IACH,eAAe,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC;IAE9C;;OAEG;IACH,eAAe,IAAI,uBAAuB,CAAC;IAE3C;;OAEG;IACH,OAAO,IAAI,IAAI,CAAC;CAChB;AAED;;;;;GAKG;AACH,MAAM,WAAW,0BAA0B;IAC1C;;OAEG;IACH,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEnC;;OAEG;IACH,KAAK,IAAI,IAAI,CAAC;IAEd;;OAEG;IACH,MAAM,IAAI,IAAI,CAAC;IAEf;;OAEG;IACH,IAAI,IAAI,IAAI,CAAC;IAEb;;OAEG;IACH,SAAS,IAAI,OAAO,CAAC;IAErB;;OAEG;IACH,QAAQ,IAAI,OAAO,CAAC;IAEpB;;;;OAIG;IACH,cAAc,CAAC,CAAC,QAAQ,EAAE,OAAO,CAAC,SAAS,CAAC,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEpE;;;;;;OAMG;IACH,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;CAC3E;AAED;;;;GAIG;AACH,MAAM,WAAW,uBAAuB;IACvC;;OAEG;IACH,aAAa,EAAE,OAAO,CAAC;IAEvB;;OAEG;IACH,cAAc,EAAE,OAAO,CAAC;IAExB;;OAEG;IACH,oBAAoB,EAAE,OAAO,CAAC;IAE9B;;OAEG;IACH,sBAAsB,EAAE,OAAO,CAAC;IAEhC;;OAEG;IACH,mBAAmB,EAAE,OAAO,CAAC;IAE7B;;OAEG;IACH,oBAAoB,EAAE,OAAO,CAAC;IAE9B;;OAEG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,MAAM,UAAU,GACnB,OAAO,GACP,QAAQ,GACR,cAAc,GACd,gBAAgB,GAChB,aAAa,GACb,cAAc,CAAC"}
@@ -1,10 +0,0 @@
1
- /**
2
- * TTS Provider Interfaces
3
- *
4
- * Defines the contract for text-to-speech providers.
5
- * Providers are stateless factories that create configured TTS implementations.
6
- *
7
- * Part of PIE Assessment Toolkit.
8
- */
9
- export {};
10
- //# sourceMappingURL=provider-interface.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"provider-interface.js","sourceRoot":"","sources":["../../../src/services/tts/provider-interface.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG"}