@lessonkit/react 0.6.0 → 0.7.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.cjs CHANGED
@@ -250,6 +250,9 @@ function createProgressController() {
250
250
  completeLesson: (lessonId, completedAtMs) => {
251
251
  if (completedLessonIds.has(lessonId)) return { didComplete: false };
252
252
  completedLessonIds = new Set(completedLessonIds).add(lessonId);
253
+ if (activeLessonId === lessonId) {
254
+ activeLessonId = void 0;
255
+ }
253
256
  const startedAt = lessonStartTimes.get(lessonId);
254
257
  lessonStartTimes.delete(lessonId);
255
258
  const durationMs = typeof startedAt === "number" ? Math.max(0, completedAtMs - startedAt) : void 0;
@@ -416,7 +419,9 @@ function LessonkitProvider(props) {
416
419
  );
417
420
  }
418
421
  return () => {
419
- disposeTrackingClient(prev);
422
+ if (prev !== trackingRef.current) {
423
+ disposeTrackingClient(prev);
424
+ }
420
425
  };
421
426
  }, [
422
427
  trackingEnabled,
@@ -453,9 +458,15 @@ function LessonkitProvider(props) {
453
458
  const prevCourseIdRef = (0, import_react.useRef)(config.courseId);
454
459
  (0, import_react.useEffect)(() => {
455
460
  if (prevCourseIdRef.current === config.courseId) return;
461
+ const previousActiveLesson = progressRef.current.getState().activeLessonId;
456
462
  prevCourseIdRef.current = config.courseId;
457
463
  progressRef.current = createProgressController();
458
464
  syncProgress();
465
+ if (previousActiveLesson) {
466
+ progressRef.current.setActiveLesson(previousActiveLesson, Date.now());
467
+ syncProgress();
468
+ track("lesson_started", { lessonId: previousActiveLesson }, { lessonId: previousActiveLesson });
469
+ }
459
470
  const sessionId = sessionIdRef.current;
460
471
  const cid = config.courseId;
461
472
  if (!hasCourseStarted(defaultStorage, sessionId, cid)) {
@@ -473,13 +484,7 @@ function LessonkitProvider(props) {
473
484
  { lxpackBridge: lxpackBridgeModeRef.current }
474
485
  );
475
486
  }
476
- }, [config.courseId, syncProgress]);
477
- (0, import_react.useEffect)(() => {
478
- return () => {
479
- trackingRef.current?.flush?.();
480
- void xapiRef.current?.flush();
481
- };
482
- }, []);
487
+ }, [config.courseId, syncProgress, track]);
483
488
  const emitLessonCompleted = (0, import_react.useCallback)(
484
489
  (lessonId, durationMs) => {
485
490
  track("lesson_completed", { lessonId, durationMs }, { lessonId });
@@ -495,9 +500,22 @@ function LessonkitProvider(props) {
495
500
  if (!result.didComplete) return;
496
501
  syncProgress();
497
502
  emitLessonCompleted(lessonId, result.durationMs);
503
+ void trackingRef.current?.flush?.();
498
504
  },
499
505
  [syncProgress, emitLessonCompleted]
500
506
  );
507
+ (0, import_react.useEffect)(() => {
508
+ return () => {
509
+ const client = trackingRef.current;
510
+ void xapiRef.current?.flush();
511
+ setTimeout(() => {
512
+ client?.flush?.();
513
+ setTimeout(() => {
514
+ client?.dispose?.();
515
+ }, 0);
516
+ }, 0);
517
+ };
518
+ }, []);
501
519
  const setActiveLesson = (0, import_react.useCallback)(
502
520
  (lessonId) => {
503
521
  const current = progressRef.current.getState();
@@ -620,24 +638,21 @@ function Course(props) {
620
638
  }
621
639
  function Lesson(props) {
622
640
  warnInvalidComponentId(props.lessonId, "lessonId");
623
- const { setActiveLesson } = useLessonkit();
641
+ const { setActiveLesson, config } = useLessonkit();
624
642
  const { completeLesson } = useCompletion();
625
643
  const id = props.lessonId;
626
- const pendingCompleteRef = (0, import_react3.useRef)(null);
644
+ const lessonMountGenerationRef = (0, import_react3.useRef)(0);
627
645
  (0, import_react3.useEffect)(() => {
628
- if (pendingCompleteRef.current !== null) {
629
- clearTimeout(pendingCompleteRef.current);
630
- pendingCompleteRef.current = null;
631
- }
646
+ const generation = ++lessonMountGenerationRef.current;
632
647
  setActiveLesson(id);
633
648
  return () => {
634
649
  const lessonId = id;
635
- pendingCompleteRef.current = setTimeout(() => {
636
- pendingCompleteRef.current = null;
650
+ queueMicrotask(() => {
651
+ if (lessonMountGenerationRef.current !== generation) return;
637
652
  completeLesson(lessonId);
638
- }, 0);
653
+ });
639
654
  };
640
- }, [id, setActiveLesson, completeLesson]);
655
+ }, [id, config.courseId, setActiveLesson, completeLesson]);
641
656
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("article", { "aria-label": props.title, children: [
642
657
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h2", { children: props.title }),
643
658
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { children: props.children })
package/dist/index.js CHANGED
@@ -211,6 +211,9 @@ function createProgressController() {
211
211
  completeLesson: (lessonId, completedAtMs) => {
212
212
  if (completedLessonIds.has(lessonId)) return { didComplete: false };
213
213
  completedLessonIds = new Set(completedLessonIds).add(lessonId);
214
+ if (activeLessonId === lessonId) {
215
+ activeLessonId = void 0;
216
+ }
214
217
  const startedAt = lessonStartTimes.get(lessonId);
215
218
  lessonStartTimes.delete(lessonId);
216
219
  const durationMs = typeof startedAt === "number" ? Math.max(0, completedAtMs - startedAt) : void 0;
@@ -377,7 +380,9 @@ function LessonkitProvider(props) {
377
380
  );
378
381
  }
379
382
  return () => {
380
- disposeTrackingClient(prev);
383
+ if (prev !== trackingRef.current) {
384
+ disposeTrackingClient(prev);
385
+ }
381
386
  };
382
387
  }, [
383
388
  trackingEnabled,
@@ -414,9 +419,15 @@ function LessonkitProvider(props) {
414
419
  const prevCourseIdRef = useRef(config.courseId);
415
420
  useEffect(() => {
416
421
  if (prevCourseIdRef.current === config.courseId) return;
422
+ const previousActiveLesson = progressRef.current.getState().activeLessonId;
417
423
  prevCourseIdRef.current = config.courseId;
418
424
  progressRef.current = createProgressController();
419
425
  syncProgress();
426
+ if (previousActiveLesson) {
427
+ progressRef.current.setActiveLesson(previousActiveLesson, Date.now());
428
+ syncProgress();
429
+ track("lesson_started", { lessonId: previousActiveLesson }, { lessonId: previousActiveLesson });
430
+ }
420
431
  const sessionId = sessionIdRef.current;
421
432
  const cid = config.courseId;
422
433
  if (!hasCourseStarted(defaultStorage, sessionId, cid)) {
@@ -434,13 +445,7 @@ function LessonkitProvider(props) {
434
445
  { lxpackBridge: lxpackBridgeModeRef.current }
435
446
  );
436
447
  }
437
- }, [config.courseId, syncProgress]);
438
- useEffect(() => {
439
- return () => {
440
- trackingRef.current?.flush?.();
441
- void xapiRef.current?.flush();
442
- };
443
- }, []);
448
+ }, [config.courseId, syncProgress, track]);
444
449
  const emitLessonCompleted = useCallback(
445
450
  (lessonId, durationMs) => {
446
451
  track("lesson_completed", { lessonId, durationMs }, { lessonId });
@@ -456,9 +461,22 @@ function LessonkitProvider(props) {
456
461
  if (!result.didComplete) return;
457
462
  syncProgress();
458
463
  emitLessonCompleted(lessonId, result.durationMs);
464
+ void trackingRef.current?.flush?.();
459
465
  },
460
466
  [syncProgress, emitLessonCompleted]
461
467
  );
468
+ useEffect(() => {
469
+ return () => {
470
+ const client = trackingRef.current;
471
+ void xapiRef.current?.flush();
472
+ setTimeout(() => {
473
+ client?.flush?.();
474
+ setTimeout(() => {
475
+ client?.dispose?.();
476
+ }, 0);
477
+ }, 0);
478
+ };
479
+ }, []);
462
480
  const setActiveLesson = useCallback(
463
481
  (lessonId) => {
464
482
  const current = progressRef.current.getState();
@@ -581,24 +599,21 @@ function Course(props) {
581
599
  }
582
600
  function Lesson(props) {
583
601
  warnInvalidComponentId(props.lessonId, "lessonId");
584
- const { setActiveLesson } = useLessonkit();
602
+ const { setActiveLesson, config } = useLessonkit();
585
603
  const { completeLesson } = useCompletion();
586
604
  const id = props.lessonId;
587
- const pendingCompleteRef = useRef2(null);
605
+ const lessonMountGenerationRef = useRef2(0);
588
606
  useEffect2(() => {
589
- if (pendingCompleteRef.current !== null) {
590
- clearTimeout(pendingCompleteRef.current);
591
- pendingCompleteRef.current = null;
592
- }
607
+ const generation = ++lessonMountGenerationRef.current;
593
608
  setActiveLesson(id);
594
609
  return () => {
595
610
  const lessonId = id;
596
- pendingCompleteRef.current = setTimeout(() => {
597
- pendingCompleteRef.current = null;
611
+ queueMicrotask(() => {
612
+ if (lessonMountGenerationRef.current !== generation) return;
598
613
  completeLesson(lessonId);
599
- }, 0);
614
+ });
600
615
  };
601
- }, [id, setActiveLesson, completeLesson]);
616
+ }, [id, config.courseId, setActiveLesson, completeLesson]);
602
617
  return /* @__PURE__ */ jsxs("article", { "aria-label": props.title, children: [
603
618
  /* @__PURE__ */ jsx2("h2", { children: props.title }),
604
619
  /* @__PURE__ */ jsx2("div", { children: props.children })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lessonkit/react",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "private": false,
5
5
  "description": "React components and hooks for building learning experiences with LessonKit.",
6
6
  "license": "Apache-2.0",
@@ -50,11 +50,11 @@
50
50
  "react-dom": ">=18"
51
51
  },
52
52
  "dependencies": {
53
- "@lessonkit/accessibility": "0.6.0",
54
- "@lessonkit/core": "0.6.0",
55
- "@lessonkit/lxpack": "0.6.0",
56
- "@lessonkit/themes": "0.6.0",
57
- "@lessonkit/xapi": "0.6.0"
53
+ "@lessonkit/accessibility": "0.7.0",
54
+ "@lessonkit/core": "0.7.0",
55
+ "@lessonkit/lxpack": "0.7.0",
56
+ "@lessonkit/themes": "0.7.0",
57
+ "@lessonkit/xapi": "0.7.0"
58
58
  },
59
59
  "devDependencies": {
60
60
  "@testing-library/react": "^16.3.0",