@lessonkit/core 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -55,7 +55,9 @@ type LessonkitUrnParts = {
55
55
  declare function buildLessonkitUrn(parts: LessonkitUrnParts): LessonkitUrn;
56
56
 
57
57
  /** H5P-aligned interaction kinds for assessment telemetry and xAPI. */
58
- type AssessmentInteractionType = "mcq" | "trueFalse" | "fillInBlanks" | "markTheWords" | "dragTheWords" | "dragAndDrop" | "assessmentSequence";
58
+ type AssessmentInteractionType = "mcq" | "trueFalse" | "fillInBlanks" | "markTheWords" | "dragTheWords" | "dragAndDrop" | "assessmentSequence" | "findHotspot" | "findMultipleHotspots";
59
+ /** Serializable resume blob for a single assessment block. */
60
+ type AssessmentResumeState = Record<string, unknown>;
59
61
  /** Behaviour flags aligned with H5P question types. */
60
62
  type AssessmentBehaviour = {
61
63
  enableRetry?: boolean;
@@ -82,13 +84,15 @@ type AssessmentHandle = {
82
84
  resetTask: () => void;
83
85
  showSolutions: () => void;
84
86
  getXAPIData: () => AssessmentXAPIData;
87
+ getCurrentState?: () => AssessmentResumeState;
88
+ resume?: (state: AssessmentResumeState) => void;
85
89
  };
86
90
  type AssessmentBaseProps = AssessmentBehaviour & {
87
91
  checkId: CheckId;
88
92
  passingScore?: number;
89
93
  };
90
94
 
91
- type TelemetryEventName = "course_started" | "course_completed" | "lesson_started" | "lesson_completed" | "lesson_time_on_task" | "quiz_answered" | "quiz_completed" | "assessment_answered" | "assessment_completed" | "interaction";
95
+ type TelemetryEventName = "course_started" | "course_completed" | "lesson_started" | "lesson_completed" | "lesson_time_on_task" | "quiz_answered" | "quiz_completed" | "assessment_answered" | "assessment_completed" | "interaction" | "book_page_viewed" | "compound_page_viewed" | "hotspot_opened" | "accordion_section_toggled" | "flashcard_flipped" | "image_slider_changed";
92
96
  type TelemetryUser = {
93
97
  id?: string;
94
98
  email?: string;
@@ -141,6 +145,34 @@ type InteractionData = {
141
145
  payload?: Record<string, unknown>;
142
146
  [key: string]: unknown;
143
147
  };
148
+ type BookPageViewedData = {
149
+ blockId: BlockId;
150
+ pageIndex: number;
151
+ pageTitle?: string;
152
+ };
153
+ type CompoundPageViewedData = {
154
+ blockId: BlockId;
155
+ pageIndex: number;
156
+ parentType?: string;
157
+ };
158
+ type HotspotOpenedData = {
159
+ blockId: BlockId;
160
+ hotspotId: string;
161
+ };
162
+ type AccordionSectionToggledData = {
163
+ blockId: BlockId;
164
+ sectionId: string;
165
+ expanded: boolean;
166
+ };
167
+ type FlashcardFlippedData = {
168
+ blockId: BlockId;
169
+ cardIndex: number;
170
+ face: "front" | "back";
171
+ };
172
+ type ImageSliderChangedData = {
173
+ blockId: BlockId;
174
+ slideIndex: number;
175
+ };
144
176
  type TelemetryEvent = (TelemetryEventBase & {
145
177
  name: "course_started";
146
178
  lessonId?: LessonId;
@@ -181,6 +213,30 @@ type TelemetryEvent = (TelemetryEventBase & {
181
213
  name: "interaction";
182
214
  lessonId?: LessonId;
183
215
  data?: InteractionData;
216
+ }) | (TelemetryEventBase & {
217
+ name: "book_page_viewed";
218
+ lessonId: LessonId;
219
+ data: BookPageViewedData;
220
+ }) | (TelemetryEventBase & {
221
+ name: "compound_page_viewed";
222
+ lessonId: LessonId;
223
+ data: CompoundPageViewedData;
224
+ }) | (TelemetryEventBase & {
225
+ name: "hotspot_opened";
226
+ lessonId?: LessonId;
227
+ data: HotspotOpenedData;
228
+ }) | (TelemetryEventBase & {
229
+ name: "accordion_section_toggled";
230
+ lessonId?: LessonId;
231
+ data: AccordionSectionToggledData;
232
+ }) | (TelemetryEventBase & {
233
+ name: "flashcard_flipped";
234
+ lessonId?: LessonId;
235
+ data: FlashcardFlippedData;
236
+ }) | (TelemetryEventBase & {
237
+ name: "image_slider_changed";
238
+ lessonId?: LessonId;
239
+ data: ImageSliderChangedData;
184
240
  });
185
241
  /** Payload shape for a telemetry event name. */
186
242
  type TelemetryDataFor<N extends TelemetryEventName> = Extract<TelemetryEvent, {
@@ -196,6 +252,78 @@ type TrackingClient = {
196
252
  dispose?: () => void | Promise<void>;
197
253
  };
198
254
 
255
+ declare const COMPOUND_RESUME_SCHEMA_VERSION: 1;
256
+ /** Serializable resume blob for a compound container (InteractiveBook, AssessmentSequence, …). */
257
+ type CompoundResumeState = {
258
+ schemaVersion: typeof COMPOUND_RESUME_SCHEMA_VERSION;
259
+ activePageIndex: number;
260
+ /** Optional chapter index when nested inside InteractiveBook. */
261
+ activeChapterIndex?: number;
262
+ childStates: Record<string, AssessmentResumeState>;
263
+ };
264
+ type CompoundResumeInput = {
265
+ activePageIndex?: number;
266
+ activeChapterIndex?: number;
267
+ childStates?: Record<CheckId, AssessmentResumeState>;
268
+ };
269
+ declare function createCompoundResumeState(input?: CompoundResumeInput): CompoundResumeState;
270
+ /** Clamp page index to valid range for a compound with `pageCount` pages. */
271
+ declare function clampCompoundPageIndex(index: number, pageCount: number): number;
272
+ declare function parseCompoundResumeState(raw: unknown): CompoundResumeState | null;
273
+ /**
274
+ * Imperative handle for compound containers (H5P compound analogue).
275
+ * Parents aggregate child AssessmentHandle scores and persist navigation state.
276
+ */
277
+ type CompoundHandle = {
278
+ getScore: () => number;
279
+ getMaxScore: () => number;
280
+ getAnswerGiven: () => boolean;
281
+ resetTask: () => void;
282
+ showSolutions: () => void;
283
+ getCurrentState: () => CompoundResumeState;
284
+ resume: (state: CompoundResumeState) => void;
285
+ };
286
+ type CompoundBaseProps = {
287
+ blockId: BlockId;
288
+ };
289
+
290
+ type StoragePort = {
291
+ getItem: (key: string) => string | null;
292
+ setItem: (key: string, value: string) => void;
293
+ removeItem?: (key: string) => void;
294
+ /** @internal Test helper to clear in-memory fallback state. */
295
+ resetForTests?: () => void;
296
+ };
297
+ type ClockPort = {
298
+ nowMs: () => number;
299
+ nowIso: () => string;
300
+ };
301
+ type TimerPort = {
302
+ setInterval: (fn: () => void, ms: number) => ReturnType<typeof globalThis.setInterval>;
303
+ clearInterval: (id: ReturnType<typeof globalThis.setInterval>) => void;
304
+ };
305
+ declare function createDefaultClock(): ClockPort;
306
+ declare function createNoopStorage(): StoragePort;
307
+ declare function resetStoragePortForTests(storage: StoragePort): void;
308
+ declare function createSessionStoragePort(): StoragePort;
309
+ declare function createGlobalTimer(): TimerPort;
310
+
311
+ declare function compoundStateStorageKey(courseId: CourseId, compoundId: BlockId): string;
312
+ declare function loadCompoundState(storage: StoragePort, courseId: CourseId, compoundId: BlockId): CompoundResumeState | null;
313
+ declare function saveCompoundState(storage: StoragePort, courseId: CourseId, compoundId: BlockId, state: CompoundResumeState): void;
314
+ declare function clearCompoundState(storage: StoragePort, courseId: CourseId, compoundId: BlockId): void;
315
+
316
+ /** Canonical compound child allowlists (H5P sub-content curation). */
317
+ declare const PAGE_ALLOWED_CHILD_TYPES: readonly ["Text", "Heading", "Image", "Scenario", "Reflection", "Quiz", "KnowledgeCheck", "TrueFalse", "FillInTheBlanks", "DragAndDrop", "DragTheWords", "MarkTheWords", "Accordion", "DialogCards", "Flashcards", "ImageHotspots", "FindHotspot", "FindMultipleHotspots", "ImageSlider", "ProgressTracker"];
318
+ declare const INTERACTIVE_BOOK_ALLOWED_CHILD_TYPES: readonly ["Page"];
319
+ declare const ASSESSMENT_SEQUENCE_ALLOWED_CHILD_TYPES: readonly ["TrueFalse", "FillInTheBlanks", "DragAndDrop", "DragTheWords", "MarkTheWords", "Quiz", "KnowledgeCheck", "FindHotspot", "FindMultipleHotspots"];
320
+ type CompoundParentType = "Page" | "InteractiveBook" | "AssessmentSequence";
321
+ declare const COMPOUND_MAX_NESTING_DEPTH: Record<CompoundParentType, number>;
322
+ declare function getAllowedChildTypes(parent: CompoundParentType): readonly string[];
323
+ declare function isChildTypeAllowed(parent: CompoundParentType, childType: string): boolean;
324
+ /** Blocks that must not nest inside Accordion (policy: no accordion-in-accordion). */
325
+ declare const ACCORDION_FORBIDDEN_CHILD_TYPES: readonly ["Accordion"];
326
+
199
327
  declare const telemetryCatalogVersion: 1;
200
328
  type TelemetryCatalogEntry = {
201
329
  name: TelemetryEventName;
@@ -220,6 +348,19 @@ type TelemetryCatalogV2Entry = {
220
348
  declare const TELEMETRY_EVENT_CATALOG_V2: TelemetryCatalogV2Entry[];
221
349
  declare function buildTelemetryCatalogV2(): TelemetryCatalogV2Entry[];
222
350
 
351
+ declare const telemetryCatalogV3Version: 3;
352
+ type TelemetryCatalogV3EventName = Extract<TelemetryEventName, "book_page_viewed" | "compound_page_viewed" | "hotspot_opened" | "accordion_section_toggled" | "flashcard_flipped" | "image_slider_changed">;
353
+ type TelemetryCatalogV3Entry = {
354
+ name: TelemetryCatalogV3EventName;
355
+ description: string;
356
+ requiredFields: string[];
357
+ dataFields: string[];
358
+ xapiVerb: string;
359
+ urnPattern: string;
360
+ };
361
+ declare const TELEMETRY_EVENT_CATALOG_V3: TelemetryCatalogV3Entry[];
362
+ declare function buildTelemetryCatalogV3(): TelemetryCatalogV3Entry[];
363
+
223
364
  declare function createTrackingClient(opts?: {
224
365
  sink?: TelemetrySink;
225
366
  batch?: {
@@ -281,7 +422,32 @@ type BuildTelemetryEventInput = (BuildTelemetryEventContext & {
281
422
  name: "interaction";
282
423
  lessonId?: LessonId;
283
424
  data?: InteractionData;
425
+ }) | (BuildTelemetryEventContext & {
426
+ name: "book_page_viewed";
427
+ lessonId?: LessonId;
428
+ data: BookPageViewedData;
429
+ }) | (BuildTelemetryEventContext & {
430
+ name: "compound_page_viewed";
431
+ lessonId?: LessonId;
432
+ data: CompoundPageViewedData;
433
+ }) | (BuildTelemetryEventContext & {
434
+ name: "hotspot_opened";
435
+ lessonId?: LessonId;
436
+ data: HotspotOpenedData;
437
+ }) | (BuildTelemetryEventContext & {
438
+ name: "accordion_section_toggled";
439
+ lessonId?: LessonId;
440
+ data: AccordionSectionToggledData;
441
+ }) | (BuildTelemetryEventContext & {
442
+ name: "flashcard_flipped";
443
+ lessonId?: LessonId;
444
+ data: FlashcardFlippedData;
445
+ }) | (BuildTelemetryEventContext & {
446
+ name: "image_slider_changed";
447
+ lessonId?: LessonId;
448
+ data: ImageSliderChangedData;
284
449
  });
450
+
285
451
  /** Reset dev-warning state (tests only). */
286
452
  declare function resetTelemetryBuilderWarningsForTests(): void;
287
453
  /**
@@ -311,27 +477,6 @@ type TelemetryPipeline = {
311
477
  declare function createTelemetryPipeline(sinks: TelemetryPipelineSink[]): TelemetryPipeline;
312
478
  declare function createTrackingPipelineSink(id: string, track: (event: TelemetryEvent) => void): TelemetryPipelineSink;
313
479
 
314
- type StoragePort = {
315
- getItem: (key: string) => string | null;
316
- setItem: (key: string, value: string) => void;
317
- removeItem?: (key: string) => void;
318
- /** @internal Test helper to clear in-memory fallback state. */
319
- resetForTests?: () => void;
320
- };
321
- type ClockPort = {
322
- nowMs: () => number;
323
- nowIso: () => string;
324
- };
325
- type TimerPort = {
326
- setInterval: (fn: () => void, ms: number) => ReturnType<typeof globalThis.setInterval>;
327
- clearInterval: (id: ReturnType<typeof globalThis.setInterval>) => void;
328
- };
329
- declare function createDefaultClock(): ClockPort;
330
- declare function createNoopStorage(): StoragePort;
331
- declare function resetStoragePortForTests(storage: StoragePort): void;
332
- declare function createSessionStoragePort(): StoragePort;
333
- declare function createGlobalTimer(): TimerPort;
334
-
335
480
  type ProgressState = {
336
481
  activeLessonId?: LessonId;
337
482
  completedLessonIds: ReadonlySet<LessonId>;
@@ -410,7 +555,10 @@ type AssessmentPlugin = PluginIdentity & {
410
555
  kind: "assessment";
411
556
  scoreAssessment?: (input: AssessmentScoreInput, ctx: LessonkitPluginContext) => AssessmentScoreResult | null;
412
557
  };
413
- /** Narrow interaction metadata plugin (ISP). */
558
+ /**
559
+ * Narrow interaction metadata plugin (ISP).
560
+ * @experimental Not wired into PluginHost; reserved for a future release.
561
+ */
414
562
  type InteractionPlugin = PluginIdentity & {
415
563
  interactionBlocks?: InteractionBlockRegistration[];
416
564
  };
@@ -464,6 +612,7 @@ declare function completeCourseWithTelemetry(opts: {
464
612
  }): boolean;
465
613
 
466
614
  type LessonkitRuntimeVersion = "v1" | "v2";
615
+ type HeadlessLessonkitPlugins = readonly LessonkitPlugin[] | PluginRegistry | null | undefined;
467
616
  type HeadlessLessonkitConfig = {
468
617
  courseId: CourseId;
469
618
  runtimeVersion?: LessonkitRuntimeVersion;
@@ -472,7 +621,8 @@ type HeadlessLessonkitConfig = {
472
621
  attemptId?: string;
473
622
  user?: TelemetryUser;
474
623
  };
475
- plugins?: PluginRegistry | null;
624
+ /** Plugin list or registry; hooks run on {@link HeadlessLessonkitRuntime.track} and lifecycle emits. */
625
+ plugins?: HeadlessLessonkitPlugins;
476
626
  };
477
627
  type HeadlessRuntimePorts = {
478
628
  storage?: StoragePort;
@@ -484,6 +634,7 @@ type TelemetryEmitFn = {
484
634
  type HeadlessLessonkitRuntime = {
485
635
  readonly config: HeadlessLessonkitConfig;
486
636
  readonly progress: ProgressController;
637
+ readonly pluginHost: PluginHost | null;
487
638
  getProgressState: () => ProgressState;
488
639
  getSession: () => {
489
640
  sessionId: string;
@@ -495,14 +646,26 @@ type HeadlessLessonkitRuntime = {
495
646
  completeLesson: (lessonId: LessonId, emit: TelemetryEmitFn) => void;
496
647
  completeCourse: (emit: TelemetryEmitFn) => void;
497
648
  track: <N extends TelemetryEventName>(name: N, data: TelemetryDataFor<N> | undefined, emit: (event: TelemetryEvent) => void, lessonId?: LessonId) => void;
649
+ scoreAssessment: (input: AssessmentScoreInput, lessonId?: LessonId) => AssessmentScoreResult | null;
498
650
  resetForCourseChange: (courseId: CourseId) => void;
651
+ dispose: () => void;
499
652
  };
500
653
  declare function createLessonkitRuntime(config: HeadlessLessonkitConfig, ports?: HeadlessRuntimePorts): HeadlessLessonkitRuntime;
501
654
 
502
655
  declare function createPluginRegistry(plugins?: readonly LessonkitPlugin[]): PluginRegistry;
503
656
 
657
+ /** Identity helper for telemetry plugins; does not validate or register at import time. */
504
658
  declare function defineTelemetryPlugin(plugin: TelemetryPlugin): LessonkitPlugin;
659
+ /** Identity helper for assessment plugins; does not validate or register at import time. */
505
660
  declare function defineAssessmentPlugin(plugin: AssessmentPlugin): LessonkitPlugin;
661
+ /** Identity helper for lifecycle plugins; does not validate or register at import time. */
506
662
  declare function defineLifecyclePlugin(plugin: LifecyclePlugin): LessonkitPlugin;
507
663
 
508
- export { type AssessmentAnsweredData, type AssessmentBaseProps, type AssessmentBehaviour, type AssessmentCompletedData, type AssessmentHandle, type AssessmentInteractionType, type AssessmentPlugin, type AssessmentScoreInput, type AssessmentScoreResult, type AssessmentXAPIData, type BlockId, type BuildTelemetryEventInput, type CheckId, type ClockPort, type CourseId, type CourseLifecycleContext, type CourseLifecycleDeps, type EmitContext, type HeadlessLessonkitConfig, type HeadlessLessonkitRuntime, type HeadlessRuntimePorts, ID_MAX_LENGTH, ID_PATTERN, type IdentityIdPath, type IdentityValidationIssue, type IdentityValidationResult, type InteractionBlockRegistration, type InteractionData, type InteractionPlugin, type LessonCompletionEmitter, type LessonId, type LessonLifecycleData, type LessonkitPlugin, type LessonkitPluginContext, type LessonkitPluginKind, type LessonkitRuntimeVersion, type LessonkitUrn, type LessonkitUrnParts, type LifecyclePlugin, type PluginHost, type PluginIdentity, type PluginRegistry, type ProgressController, type ProgressState, type QuizAnsweredData, type QuizCompletedData, SESSION_STORAGE_KEY, type StoragePort, TELEMETRY_EVENT_CATALOG, TELEMETRY_EVENT_CATALOG_V2, type TelemetryBatchSink, type TelemetryCatalogEntry, type TelemetryCatalogV2Entry, type TelemetryDataFor, type TelemetryEmitFn, type TelemetryEvent, type TelemetryEventBase, type TelemetryEventName, type TelemetryPipeline, type TelemetryPipelineSink, type TelemetryPlugin, type TelemetrySink, type TelemetryUser, type TimerPort, type TrackingClient, assertNever, assertValidId, buildCourseStartedTelemetryEvent, buildLessonkitUrn, buildTelemetryCatalog, buildTelemetryCatalogV2, buildTelemetryEvent, completeCourseWithTelemetry, completeLessonWithTelemetry, createDefaultClock, createGlobalTimer, createLessonkitRuntime, createNoopStorage, createPluginRegistry, createProgressController, createSessionId, createSessionStoragePort, createTelemetryPipeline, createTrackingClient, createTrackingPipelineSink, defineAssessmentPlugin, defineLifecyclePlugin, defineTelemetryPlugin, deriveId, getTabSessionId, hasCourseStarted, hasCourseStartedEmittedToTracking, hasCourseStartedPipelineDelivered, markCourseStarted, markCourseStartedEmittedToTracking, markCourseStartedPipelineDelivered, migrateCourseStartedMark, nowIso, parseBlockId, parseCheckId, parseCourseId, parseLessonId, resetStoragePortForTests, resetTelemetryBuilderWarningsForTests, resolveSessionId, slugifyId, telemetryCatalogV2Version, telemetryCatalogVersion, tryBuildTelemetryEvent, tryEmitCourseStarted, validateId };
664
+ declare function buildPluginContext(opts: {
665
+ courseId: CourseId;
666
+ sessionId?: string;
667
+ attemptId?: string;
668
+ user?: TelemetryUser;
669
+ }): LessonkitPluginContext;
670
+
671
+ export { ACCORDION_FORBIDDEN_CHILD_TYPES, ASSESSMENT_SEQUENCE_ALLOWED_CHILD_TYPES, type AccordionSectionToggledData, type AssessmentAnsweredData, type AssessmentBaseProps, type AssessmentBehaviour, type AssessmentCompletedData, type AssessmentHandle, type AssessmentInteractionType, type AssessmentPlugin, type AssessmentResumeState, type AssessmentScoreInput, type AssessmentScoreResult, type AssessmentXAPIData, type BlockId, type BookPageViewedData, type BuildTelemetryEventInput, COMPOUND_MAX_NESTING_DEPTH, COMPOUND_RESUME_SCHEMA_VERSION, type CheckId, type ClockPort, type CompoundBaseProps, type CompoundHandle, type CompoundPageViewedData, type CompoundParentType, type CompoundResumeInput, type CompoundResumeState, type CourseId, type CourseLifecycleContext, type CourseLifecycleDeps, type EmitContext, type FlashcardFlippedData, type HeadlessLessonkitConfig, type HeadlessLessonkitRuntime, type HeadlessRuntimePorts, type HotspotOpenedData, ID_MAX_LENGTH, ID_PATTERN, INTERACTIVE_BOOK_ALLOWED_CHILD_TYPES, type IdentityIdPath, type IdentityValidationIssue, type IdentityValidationResult, type ImageSliderChangedData, type InteractionBlockRegistration, type InteractionData, type InteractionPlugin, type LessonCompletionEmitter, type LessonId, type LessonLifecycleData, type LessonkitPlugin, type LessonkitPluginContext, type LessonkitPluginKind, type LessonkitRuntimeVersion, type LessonkitUrn, type LessonkitUrnParts, type LifecyclePlugin, PAGE_ALLOWED_CHILD_TYPES, type PluginHost, type PluginIdentity, type PluginRegistry, type ProgressController, type ProgressState, type QuizAnsweredData, type QuizCompletedData, SESSION_STORAGE_KEY, type StoragePort, TELEMETRY_EVENT_CATALOG, TELEMETRY_EVENT_CATALOG_V2, TELEMETRY_EVENT_CATALOG_V3, type TelemetryBatchSink, type TelemetryCatalogEntry, type TelemetryCatalogV2Entry, type TelemetryCatalogV3Entry, type TelemetryDataFor, type TelemetryEmitFn, type TelemetryEvent, type TelemetryEventBase, type TelemetryEventName, type TelemetryPipeline, type TelemetryPipelineSink, type TelemetryPlugin, type TelemetrySink, type TelemetryUser, type TimerPort, type TrackingClient, assertNever, assertValidId, buildCourseStartedTelemetryEvent, buildLessonkitUrn, buildPluginContext, buildTelemetryCatalog, buildTelemetryCatalogV2, buildTelemetryCatalogV3, buildTelemetryEvent, clampCompoundPageIndex, clearCompoundState, completeCourseWithTelemetry, completeLessonWithTelemetry, compoundStateStorageKey, createCompoundResumeState, createDefaultClock, createGlobalTimer, createLessonkitRuntime, createNoopStorage, createPluginRegistry, createProgressController, createSessionId, createSessionStoragePort, createTelemetryPipeline, createTrackingClient, createTrackingPipelineSink, defineAssessmentPlugin, defineLifecyclePlugin, defineTelemetryPlugin, deriveId, getAllowedChildTypes, getTabSessionId, hasCourseStarted, hasCourseStartedEmittedToTracking, hasCourseStartedPipelineDelivered, isChildTypeAllowed, loadCompoundState, markCourseStarted, markCourseStartedEmittedToTracking, markCourseStartedPipelineDelivered, migrateCourseStartedMark, nowIso, parseBlockId, parseCheckId, parseCompoundResumeState, parseCourseId, parseLessonId, resetStoragePortForTests, resetTelemetryBuilderWarningsForTests, resolveSessionId, saveCompoundState, slugifyId, telemetryCatalogV2Version, telemetryCatalogV3Version, telemetryCatalogVersion, tryBuildTelemetryEvent, tryEmitCourseStarted, validateId };
package/dist/index.d.ts CHANGED
@@ -55,7 +55,9 @@ type LessonkitUrnParts = {
55
55
  declare function buildLessonkitUrn(parts: LessonkitUrnParts): LessonkitUrn;
56
56
 
57
57
  /** H5P-aligned interaction kinds for assessment telemetry and xAPI. */
58
- type AssessmentInteractionType = "mcq" | "trueFalse" | "fillInBlanks" | "markTheWords" | "dragTheWords" | "dragAndDrop" | "assessmentSequence";
58
+ type AssessmentInteractionType = "mcq" | "trueFalse" | "fillInBlanks" | "markTheWords" | "dragTheWords" | "dragAndDrop" | "assessmentSequence" | "findHotspot" | "findMultipleHotspots";
59
+ /** Serializable resume blob for a single assessment block. */
60
+ type AssessmentResumeState = Record<string, unknown>;
59
61
  /** Behaviour flags aligned with H5P question types. */
60
62
  type AssessmentBehaviour = {
61
63
  enableRetry?: boolean;
@@ -82,13 +84,15 @@ type AssessmentHandle = {
82
84
  resetTask: () => void;
83
85
  showSolutions: () => void;
84
86
  getXAPIData: () => AssessmentXAPIData;
87
+ getCurrentState?: () => AssessmentResumeState;
88
+ resume?: (state: AssessmentResumeState) => void;
85
89
  };
86
90
  type AssessmentBaseProps = AssessmentBehaviour & {
87
91
  checkId: CheckId;
88
92
  passingScore?: number;
89
93
  };
90
94
 
91
- type TelemetryEventName = "course_started" | "course_completed" | "lesson_started" | "lesson_completed" | "lesson_time_on_task" | "quiz_answered" | "quiz_completed" | "assessment_answered" | "assessment_completed" | "interaction";
95
+ type TelemetryEventName = "course_started" | "course_completed" | "lesson_started" | "lesson_completed" | "lesson_time_on_task" | "quiz_answered" | "quiz_completed" | "assessment_answered" | "assessment_completed" | "interaction" | "book_page_viewed" | "compound_page_viewed" | "hotspot_opened" | "accordion_section_toggled" | "flashcard_flipped" | "image_slider_changed";
92
96
  type TelemetryUser = {
93
97
  id?: string;
94
98
  email?: string;
@@ -141,6 +145,34 @@ type InteractionData = {
141
145
  payload?: Record<string, unknown>;
142
146
  [key: string]: unknown;
143
147
  };
148
+ type BookPageViewedData = {
149
+ blockId: BlockId;
150
+ pageIndex: number;
151
+ pageTitle?: string;
152
+ };
153
+ type CompoundPageViewedData = {
154
+ blockId: BlockId;
155
+ pageIndex: number;
156
+ parentType?: string;
157
+ };
158
+ type HotspotOpenedData = {
159
+ blockId: BlockId;
160
+ hotspotId: string;
161
+ };
162
+ type AccordionSectionToggledData = {
163
+ blockId: BlockId;
164
+ sectionId: string;
165
+ expanded: boolean;
166
+ };
167
+ type FlashcardFlippedData = {
168
+ blockId: BlockId;
169
+ cardIndex: number;
170
+ face: "front" | "back";
171
+ };
172
+ type ImageSliderChangedData = {
173
+ blockId: BlockId;
174
+ slideIndex: number;
175
+ };
144
176
  type TelemetryEvent = (TelemetryEventBase & {
145
177
  name: "course_started";
146
178
  lessonId?: LessonId;
@@ -181,6 +213,30 @@ type TelemetryEvent = (TelemetryEventBase & {
181
213
  name: "interaction";
182
214
  lessonId?: LessonId;
183
215
  data?: InteractionData;
216
+ }) | (TelemetryEventBase & {
217
+ name: "book_page_viewed";
218
+ lessonId: LessonId;
219
+ data: BookPageViewedData;
220
+ }) | (TelemetryEventBase & {
221
+ name: "compound_page_viewed";
222
+ lessonId: LessonId;
223
+ data: CompoundPageViewedData;
224
+ }) | (TelemetryEventBase & {
225
+ name: "hotspot_opened";
226
+ lessonId?: LessonId;
227
+ data: HotspotOpenedData;
228
+ }) | (TelemetryEventBase & {
229
+ name: "accordion_section_toggled";
230
+ lessonId?: LessonId;
231
+ data: AccordionSectionToggledData;
232
+ }) | (TelemetryEventBase & {
233
+ name: "flashcard_flipped";
234
+ lessonId?: LessonId;
235
+ data: FlashcardFlippedData;
236
+ }) | (TelemetryEventBase & {
237
+ name: "image_slider_changed";
238
+ lessonId?: LessonId;
239
+ data: ImageSliderChangedData;
184
240
  });
185
241
  /** Payload shape for a telemetry event name. */
186
242
  type TelemetryDataFor<N extends TelemetryEventName> = Extract<TelemetryEvent, {
@@ -196,6 +252,78 @@ type TrackingClient = {
196
252
  dispose?: () => void | Promise<void>;
197
253
  };
198
254
 
255
+ declare const COMPOUND_RESUME_SCHEMA_VERSION: 1;
256
+ /** Serializable resume blob for a compound container (InteractiveBook, AssessmentSequence, …). */
257
+ type CompoundResumeState = {
258
+ schemaVersion: typeof COMPOUND_RESUME_SCHEMA_VERSION;
259
+ activePageIndex: number;
260
+ /** Optional chapter index when nested inside InteractiveBook. */
261
+ activeChapterIndex?: number;
262
+ childStates: Record<string, AssessmentResumeState>;
263
+ };
264
+ type CompoundResumeInput = {
265
+ activePageIndex?: number;
266
+ activeChapterIndex?: number;
267
+ childStates?: Record<CheckId, AssessmentResumeState>;
268
+ };
269
+ declare function createCompoundResumeState(input?: CompoundResumeInput): CompoundResumeState;
270
+ /** Clamp page index to valid range for a compound with `pageCount` pages. */
271
+ declare function clampCompoundPageIndex(index: number, pageCount: number): number;
272
+ declare function parseCompoundResumeState(raw: unknown): CompoundResumeState | null;
273
+ /**
274
+ * Imperative handle for compound containers (H5P compound analogue).
275
+ * Parents aggregate child AssessmentHandle scores and persist navigation state.
276
+ */
277
+ type CompoundHandle = {
278
+ getScore: () => number;
279
+ getMaxScore: () => number;
280
+ getAnswerGiven: () => boolean;
281
+ resetTask: () => void;
282
+ showSolutions: () => void;
283
+ getCurrentState: () => CompoundResumeState;
284
+ resume: (state: CompoundResumeState) => void;
285
+ };
286
+ type CompoundBaseProps = {
287
+ blockId: BlockId;
288
+ };
289
+
290
+ type StoragePort = {
291
+ getItem: (key: string) => string | null;
292
+ setItem: (key: string, value: string) => void;
293
+ removeItem?: (key: string) => void;
294
+ /** @internal Test helper to clear in-memory fallback state. */
295
+ resetForTests?: () => void;
296
+ };
297
+ type ClockPort = {
298
+ nowMs: () => number;
299
+ nowIso: () => string;
300
+ };
301
+ type TimerPort = {
302
+ setInterval: (fn: () => void, ms: number) => ReturnType<typeof globalThis.setInterval>;
303
+ clearInterval: (id: ReturnType<typeof globalThis.setInterval>) => void;
304
+ };
305
+ declare function createDefaultClock(): ClockPort;
306
+ declare function createNoopStorage(): StoragePort;
307
+ declare function resetStoragePortForTests(storage: StoragePort): void;
308
+ declare function createSessionStoragePort(): StoragePort;
309
+ declare function createGlobalTimer(): TimerPort;
310
+
311
+ declare function compoundStateStorageKey(courseId: CourseId, compoundId: BlockId): string;
312
+ declare function loadCompoundState(storage: StoragePort, courseId: CourseId, compoundId: BlockId): CompoundResumeState | null;
313
+ declare function saveCompoundState(storage: StoragePort, courseId: CourseId, compoundId: BlockId, state: CompoundResumeState): void;
314
+ declare function clearCompoundState(storage: StoragePort, courseId: CourseId, compoundId: BlockId): void;
315
+
316
+ /** Canonical compound child allowlists (H5P sub-content curation). */
317
+ declare const PAGE_ALLOWED_CHILD_TYPES: readonly ["Text", "Heading", "Image", "Scenario", "Reflection", "Quiz", "KnowledgeCheck", "TrueFalse", "FillInTheBlanks", "DragAndDrop", "DragTheWords", "MarkTheWords", "Accordion", "DialogCards", "Flashcards", "ImageHotspots", "FindHotspot", "FindMultipleHotspots", "ImageSlider", "ProgressTracker"];
318
+ declare const INTERACTIVE_BOOK_ALLOWED_CHILD_TYPES: readonly ["Page"];
319
+ declare const ASSESSMENT_SEQUENCE_ALLOWED_CHILD_TYPES: readonly ["TrueFalse", "FillInTheBlanks", "DragAndDrop", "DragTheWords", "MarkTheWords", "Quiz", "KnowledgeCheck", "FindHotspot", "FindMultipleHotspots"];
320
+ type CompoundParentType = "Page" | "InteractiveBook" | "AssessmentSequence";
321
+ declare const COMPOUND_MAX_NESTING_DEPTH: Record<CompoundParentType, number>;
322
+ declare function getAllowedChildTypes(parent: CompoundParentType): readonly string[];
323
+ declare function isChildTypeAllowed(parent: CompoundParentType, childType: string): boolean;
324
+ /** Blocks that must not nest inside Accordion (policy: no accordion-in-accordion). */
325
+ declare const ACCORDION_FORBIDDEN_CHILD_TYPES: readonly ["Accordion"];
326
+
199
327
  declare const telemetryCatalogVersion: 1;
200
328
  type TelemetryCatalogEntry = {
201
329
  name: TelemetryEventName;
@@ -220,6 +348,19 @@ type TelemetryCatalogV2Entry = {
220
348
  declare const TELEMETRY_EVENT_CATALOG_V2: TelemetryCatalogV2Entry[];
221
349
  declare function buildTelemetryCatalogV2(): TelemetryCatalogV2Entry[];
222
350
 
351
+ declare const telemetryCatalogV3Version: 3;
352
+ type TelemetryCatalogV3EventName = Extract<TelemetryEventName, "book_page_viewed" | "compound_page_viewed" | "hotspot_opened" | "accordion_section_toggled" | "flashcard_flipped" | "image_slider_changed">;
353
+ type TelemetryCatalogV3Entry = {
354
+ name: TelemetryCatalogV3EventName;
355
+ description: string;
356
+ requiredFields: string[];
357
+ dataFields: string[];
358
+ xapiVerb: string;
359
+ urnPattern: string;
360
+ };
361
+ declare const TELEMETRY_EVENT_CATALOG_V3: TelemetryCatalogV3Entry[];
362
+ declare function buildTelemetryCatalogV3(): TelemetryCatalogV3Entry[];
363
+
223
364
  declare function createTrackingClient(opts?: {
224
365
  sink?: TelemetrySink;
225
366
  batch?: {
@@ -281,7 +422,32 @@ type BuildTelemetryEventInput = (BuildTelemetryEventContext & {
281
422
  name: "interaction";
282
423
  lessonId?: LessonId;
283
424
  data?: InteractionData;
425
+ }) | (BuildTelemetryEventContext & {
426
+ name: "book_page_viewed";
427
+ lessonId?: LessonId;
428
+ data: BookPageViewedData;
429
+ }) | (BuildTelemetryEventContext & {
430
+ name: "compound_page_viewed";
431
+ lessonId?: LessonId;
432
+ data: CompoundPageViewedData;
433
+ }) | (BuildTelemetryEventContext & {
434
+ name: "hotspot_opened";
435
+ lessonId?: LessonId;
436
+ data: HotspotOpenedData;
437
+ }) | (BuildTelemetryEventContext & {
438
+ name: "accordion_section_toggled";
439
+ lessonId?: LessonId;
440
+ data: AccordionSectionToggledData;
441
+ }) | (BuildTelemetryEventContext & {
442
+ name: "flashcard_flipped";
443
+ lessonId?: LessonId;
444
+ data: FlashcardFlippedData;
445
+ }) | (BuildTelemetryEventContext & {
446
+ name: "image_slider_changed";
447
+ lessonId?: LessonId;
448
+ data: ImageSliderChangedData;
284
449
  });
450
+
285
451
  /** Reset dev-warning state (tests only). */
286
452
  declare function resetTelemetryBuilderWarningsForTests(): void;
287
453
  /**
@@ -311,27 +477,6 @@ type TelemetryPipeline = {
311
477
  declare function createTelemetryPipeline(sinks: TelemetryPipelineSink[]): TelemetryPipeline;
312
478
  declare function createTrackingPipelineSink(id: string, track: (event: TelemetryEvent) => void): TelemetryPipelineSink;
313
479
 
314
- type StoragePort = {
315
- getItem: (key: string) => string | null;
316
- setItem: (key: string, value: string) => void;
317
- removeItem?: (key: string) => void;
318
- /** @internal Test helper to clear in-memory fallback state. */
319
- resetForTests?: () => void;
320
- };
321
- type ClockPort = {
322
- nowMs: () => number;
323
- nowIso: () => string;
324
- };
325
- type TimerPort = {
326
- setInterval: (fn: () => void, ms: number) => ReturnType<typeof globalThis.setInterval>;
327
- clearInterval: (id: ReturnType<typeof globalThis.setInterval>) => void;
328
- };
329
- declare function createDefaultClock(): ClockPort;
330
- declare function createNoopStorage(): StoragePort;
331
- declare function resetStoragePortForTests(storage: StoragePort): void;
332
- declare function createSessionStoragePort(): StoragePort;
333
- declare function createGlobalTimer(): TimerPort;
334
-
335
480
  type ProgressState = {
336
481
  activeLessonId?: LessonId;
337
482
  completedLessonIds: ReadonlySet<LessonId>;
@@ -410,7 +555,10 @@ type AssessmentPlugin = PluginIdentity & {
410
555
  kind: "assessment";
411
556
  scoreAssessment?: (input: AssessmentScoreInput, ctx: LessonkitPluginContext) => AssessmentScoreResult | null;
412
557
  };
413
- /** Narrow interaction metadata plugin (ISP). */
558
+ /**
559
+ * Narrow interaction metadata plugin (ISP).
560
+ * @experimental Not wired into PluginHost; reserved for a future release.
561
+ */
414
562
  type InteractionPlugin = PluginIdentity & {
415
563
  interactionBlocks?: InteractionBlockRegistration[];
416
564
  };
@@ -464,6 +612,7 @@ declare function completeCourseWithTelemetry(opts: {
464
612
  }): boolean;
465
613
 
466
614
  type LessonkitRuntimeVersion = "v1" | "v2";
615
+ type HeadlessLessonkitPlugins = readonly LessonkitPlugin[] | PluginRegistry | null | undefined;
467
616
  type HeadlessLessonkitConfig = {
468
617
  courseId: CourseId;
469
618
  runtimeVersion?: LessonkitRuntimeVersion;
@@ -472,7 +621,8 @@ type HeadlessLessonkitConfig = {
472
621
  attemptId?: string;
473
622
  user?: TelemetryUser;
474
623
  };
475
- plugins?: PluginRegistry | null;
624
+ /** Plugin list or registry; hooks run on {@link HeadlessLessonkitRuntime.track} and lifecycle emits. */
625
+ plugins?: HeadlessLessonkitPlugins;
476
626
  };
477
627
  type HeadlessRuntimePorts = {
478
628
  storage?: StoragePort;
@@ -484,6 +634,7 @@ type TelemetryEmitFn = {
484
634
  type HeadlessLessonkitRuntime = {
485
635
  readonly config: HeadlessLessonkitConfig;
486
636
  readonly progress: ProgressController;
637
+ readonly pluginHost: PluginHost | null;
487
638
  getProgressState: () => ProgressState;
488
639
  getSession: () => {
489
640
  sessionId: string;
@@ -495,14 +646,26 @@ type HeadlessLessonkitRuntime = {
495
646
  completeLesson: (lessonId: LessonId, emit: TelemetryEmitFn) => void;
496
647
  completeCourse: (emit: TelemetryEmitFn) => void;
497
648
  track: <N extends TelemetryEventName>(name: N, data: TelemetryDataFor<N> | undefined, emit: (event: TelemetryEvent) => void, lessonId?: LessonId) => void;
649
+ scoreAssessment: (input: AssessmentScoreInput, lessonId?: LessonId) => AssessmentScoreResult | null;
498
650
  resetForCourseChange: (courseId: CourseId) => void;
651
+ dispose: () => void;
499
652
  };
500
653
  declare function createLessonkitRuntime(config: HeadlessLessonkitConfig, ports?: HeadlessRuntimePorts): HeadlessLessonkitRuntime;
501
654
 
502
655
  declare function createPluginRegistry(plugins?: readonly LessonkitPlugin[]): PluginRegistry;
503
656
 
657
+ /** Identity helper for telemetry plugins; does not validate or register at import time. */
504
658
  declare function defineTelemetryPlugin(plugin: TelemetryPlugin): LessonkitPlugin;
659
+ /** Identity helper for assessment plugins; does not validate or register at import time. */
505
660
  declare function defineAssessmentPlugin(plugin: AssessmentPlugin): LessonkitPlugin;
661
+ /** Identity helper for lifecycle plugins; does not validate or register at import time. */
506
662
  declare function defineLifecyclePlugin(plugin: LifecyclePlugin): LessonkitPlugin;
507
663
 
508
- export { type AssessmentAnsweredData, type AssessmentBaseProps, type AssessmentBehaviour, type AssessmentCompletedData, type AssessmentHandle, type AssessmentInteractionType, type AssessmentPlugin, type AssessmentScoreInput, type AssessmentScoreResult, type AssessmentXAPIData, type BlockId, type BuildTelemetryEventInput, type CheckId, type ClockPort, type CourseId, type CourseLifecycleContext, type CourseLifecycleDeps, type EmitContext, type HeadlessLessonkitConfig, type HeadlessLessonkitRuntime, type HeadlessRuntimePorts, ID_MAX_LENGTH, ID_PATTERN, type IdentityIdPath, type IdentityValidationIssue, type IdentityValidationResult, type InteractionBlockRegistration, type InteractionData, type InteractionPlugin, type LessonCompletionEmitter, type LessonId, type LessonLifecycleData, type LessonkitPlugin, type LessonkitPluginContext, type LessonkitPluginKind, type LessonkitRuntimeVersion, type LessonkitUrn, type LessonkitUrnParts, type LifecyclePlugin, type PluginHost, type PluginIdentity, type PluginRegistry, type ProgressController, type ProgressState, type QuizAnsweredData, type QuizCompletedData, SESSION_STORAGE_KEY, type StoragePort, TELEMETRY_EVENT_CATALOG, TELEMETRY_EVENT_CATALOG_V2, type TelemetryBatchSink, type TelemetryCatalogEntry, type TelemetryCatalogV2Entry, type TelemetryDataFor, type TelemetryEmitFn, type TelemetryEvent, type TelemetryEventBase, type TelemetryEventName, type TelemetryPipeline, type TelemetryPipelineSink, type TelemetryPlugin, type TelemetrySink, type TelemetryUser, type TimerPort, type TrackingClient, assertNever, assertValidId, buildCourseStartedTelemetryEvent, buildLessonkitUrn, buildTelemetryCatalog, buildTelemetryCatalogV2, buildTelemetryEvent, completeCourseWithTelemetry, completeLessonWithTelemetry, createDefaultClock, createGlobalTimer, createLessonkitRuntime, createNoopStorage, createPluginRegistry, createProgressController, createSessionId, createSessionStoragePort, createTelemetryPipeline, createTrackingClient, createTrackingPipelineSink, defineAssessmentPlugin, defineLifecyclePlugin, defineTelemetryPlugin, deriveId, getTabSessionId, hasCourseStarted, hasCourseStartedEmittedToTracking, hasCourseStartedPipelineDelivered, markCourseStarted, markCourseStartedEmittedToTracking, markCourseStartedPipelineDelivered, migrateCourseStartedMark, nowIso, parseBlockId, parseCheckId, parseCourseId, parseLessonId, resetStoragePortForTests, resetTelemetryBuilderWarningsForTests, resolveSessionId, slugifyId, telemetryCatalogV2Version, telemetryCatalogVersion, tryBuildTelemetryEvent, tryEmitCourseStarted, validateId };
664
+ declare function buildPluginContext(opts: {
665
+ courseId: CourseId;
666
+ sessionId?: string;
667
+ attemptId?: string;
668
+ user?: TelemetryUser;
669
+ }): LessonkitPluginContext;
670
+
671
+ export { ACCORDION_FORBIDDEN_CHILD_TYPES, ASSESSMENT_SEQUENCE_ALLOWED_CHILD_TYPES, type AccordionSectionToggledData, type AssessmentAnsweredData, type AssessmentBaseProps, type AssessmentBehaviour, type AssessmentCompletedData, type AssessmentHandle, type AssessmentInteractionType, type AssessmentPlugin, type AssessmentResumeState, type AssessmentScoreInput, type AssessmentScoreResult, type AssessmentXAPIData, type BlockId, type BookPageViewedData, type BuildTelemetryEventInput, COMPOUND_MAX_NESTING_DEPTH, COMPOUND_RESUME_SCHEMA_VERSION, type CheckId, type ClockPort, type CompoundBaseProps, type CompoundHandle, type CompoundPageViewedData, type CompoundParentType, type CompoundResumeInput, type CompoundResumeState, type CourseId, type CourseLifecycleContext, type CourseLifecycleDeps, type EmitContext, type FlashcardFlippedData, type HeadlessLessonkitConfig, type HeadlessLessonkitRuntime, type HeadlessRuntimePorts, type HotspotOpenedData, ID_MAX_LENGTH, ID_PATTERN, INTERACTIVE_BOOK_ALLOWED_CHILD_TYPES, type IdentityIdPath, type IdentityValidationIssue, type IdentityValidationResult, type ImageSliderChangedData, type InteractionBlockRegistration, type InteractionData, type InteractionPlugin, type LessonCompletionEmitter, type LessonId, type LessonLifecycleData, type LessonkitPlugin, type LessonkitPluginContext, type LessonkitPluginKind, type LessonkitRuntimeVersion, type LessonkitUrn, type LessonkitUrnParts, type LifecyclePlugin, PAGE_ALLOWED_CHILD_TYPES, type PluginHost, type PluginIdentity, type PluginRegistry, type ProgressController, type ProgressState, type QuizAnsweredData, type QuizCompletedData, SESSION_STORAGE_KEY, type StoragePort, TELEMETRY_EVENT_CATALOG, TELEMETRY_EVENT_CATALOG_V2, TELEMETRY_EVENT_CATALOG_V3, type TelemetryBatchSink, type TelemetryCatalogEntry, type TelemetryCatalogV2Entry, type TelemetryCatalogV3Entry, type TelemetryDataFor, type TelemetryEmitFn, type TelemetryEvent, type TelemetryEventBase, type TelemetryEventName, type TelemetryPipeline, type TelemetryPipelineSink, type TelemetryPlugin, type TelemetrySink, type TelemetryUser, type TimerPort, type TrackingClient, assertNever, assertValidId, buildCourseStartedTelemetryEvent, buildLessonkitUrn, buildPluginContext, buildTelemetryCatalog, buildTelemetryCatalogV2, buildTelemetryCatalogV3, buildTelemetryEvent, clampCompoundPageIndex, clearCompoundState, completeCourseWithTelemetry, completeLessonWithTelemetry, compoundStateStorageKey, createCompoundResumeState, createDefaultClock, createGlobalTimer, createLessonkitRuntime, createNoopStorage, createPluginRegistry, createProgressController, createSessionId, createSessionStoragePort, createTelemetryPipeline, createTrackingClient, createTrackingPipelineSink, defineAssessmentPlugin, defineLifecyclePlugin, defineTelemetryPlugin, deriveId, getAllowedChildTypes, getTabSessionId, hasCourseStarted, hasCourseStartedEmittedToTracking, hasCourseStartedPipelineDelivered, isChildTypeAllowed, loadCompoundState, markCourseStarted, markCourseStartedEmittedToTracking, markCourseStartedPipelineDelivered, migrateCourseStartedMark, nowIso, parseBlockId, parseCheckId, parseCompoundResumeState, parseCourseId, parseLessonId, resetStoragePortForTests, resetTelemetryBuilderWarningsForTests, resolveSessionId, saveCompoundState, slugifyId, telemetryCatalogV2Version, telemetryCatalogV3Version, telemetryCatalogVersion, tryBuildTelemetryEvent, tryEmitCourseStarted, validateId };