@lessonkit/react 1.0.1 → 1.0.2
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 +149 -53
- package/dist/index.d.cts +6 -2
- package/dist/index.d.ts +6 -2
- package/dist/index.js +149 -53
- package/package.json +7 -7
package/dist/index.cjs
CHANGED
|
@@ -171,7 +171,40 @@ var import_core5 = require("@lessonkit/core");
|
|
|
171
171
|
|
|
172
172
|
// src/runtime/courseStartedPipeline.ts
|
|
173
173
|
var import_xapi3 = require("@lessonkit/xapi");
|
|
174
|
-
function
|
|
174
|
+
function isDevEnvironment3() {
|
|
175
|
+
const g = globalThis;
|
|
176
|
+
return typeof g.process !== "undefined" && g.process.env?.NODE_ENV !== "production";
|
|
177
|
+
}
|
|
178
|
+
function warnExtraSinkFailure(sinkId, err) {
|
|
179
|
+
if (isDevEnvironment3()) {
|
|
180
|
+
console.warn(
|
|
181
|
+
`[lessonkit] course_started extra sink "${sinkId}" failed:`,
|
|
182
|
+
err instanceof Error ? err.message : err
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
async function emitExtraSinks(sinks, event, emitCtx) {
|
|
187
|
+
await Promise.all(
|
|
188
|
+
sinks.map(async (sink) => {
|
|
189
|
+
let result;
|
|
190
|
+
try {
|
|
191
|
+
result = sink.emit(event, emitCtx);
|
|
192
|
+
} catch (err) {
|
|
193
|
+
warnExtraSinkFailure(sink.id, err);
|
|
194
|
+
throw err;
|
|
195
|
+
}
|
|
196
|
+
if (result != null && typeof result.then === "function") {
|
|
197
|
+
try {
|
|
198
|
+
await result;
|
|
199
|
+
} catch (err) {
|
|
200
|
+
warnExtraSinkFailure(sink.id, err);
|
|
201
|
+
throw err;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
})
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
async function emitCourseStartedNonTrackingPipeline(opts) {
|
|
175
208
|
let xapiStatementSent = false;
|
|
176
209
|
if (!opts.skipXapi && opts.xapi) {
|
|
177
210
|
const statement = (0, import_xapi3.telemetryEventToXAPIStatement)(opts.event);
|
|
@@ -186,9 +219,7 @@ function emitCourseStartedNonTrackingPipeline(opts) {
|
|
|
186
219
|
sessionId: opts.event.sessionId,
|
|
187
220
|
attemptId: opts.event.attemptId
|
|
188
221
|
};
|
|
189
|
-
|
|
190
|
-
sink.emit(opts.event, emitCtx);
|
|
191
|
-
}
|
|
222
|
+
await emitExtraSinks(opts.extraSinks ?? [], opts.event, emitCtx);
|
|
192
223
|
return { xapiStatementSent };
|
|
193
224
|
}
|
|
194
225
|
|
|
@@ -240,6 +271,7 @@ async function disposeTrackingClient(client) {
|
|
|
240
271
|
// src/provider/useLessonkitProviderRuntime.ts
|
|
241
272
|
var useIsoLayoutEffect = typeof window !== "undefined" ? import_react.useLayoutEffect : import_react.useEffect;
|
|
242
273
|
var defaultStorage = (0, import_core3.createSessionStoragePort)();
|
|
274
|
+
var courseStartedTrackingFlightKey = null;
|
|
243
275
|
function isTrackingActive(tracking) {
|
|
244
276
|
return tracking?.enabled !== false;
|
|
245
277
|
}
|
|
@@ -262,15 +294,41 @@ function buildCourseStartedEvent(opts) {
|
|
|
262
294
|
});
|
|
263
295
|
return opts.pluginHost ? opts.pluginHost.runTelemetry(built, pluginCtx) : built;
|
|
264
296
|
}
|
|
265
|
-
function
|
|
297
|
+
async function emitCourseStartedToTracking(tracking, storage, sessionId, courseId, event, shouldCommit) {
|
|
298
|
+
const flightKey = `${sessionId}:${courseId}`;
|
|
299
|
+
if ((0, import_core5.hasCourseStartedEmittedToTracking)(storage, sessionId, courseId)) {
|
|
300
|
+
return true;
|
|
301
|
+
}
|
|
302
|
+
if (courseStartedTrackingFlightKey === flightKey) {
|
|
303
|
+
return false;
|
|
304
|
+
}
|
|
305
|
+
courseStartedTrackingFlightKey = flightKey;
|
|
306
|
+
try {
|
|
307
|
+
if (shouldCommit && !shouldCommit()) return false;
|
|
308
|
+
tracking.track(event);
|
|
309
|
+
await tracking.flush?.();
|
|
310
|
+
if (shouldCommit && !shouldCommit()) return false;
|
|
311
|
+
(0, import_core5.markCourseStartedEmittedToTracking)(storage, sessionId, courseId);
|
|
312
|
+
return true;
|
|
313
|
+
} catch {
|
|
314
|
+
return false;
|
|
315
|
+
} finally {
|
|
316
|
+
if (courseStartedTrackingFlightKey === flightKey) {
|
|
317
|
+
courseStartedTrackingFlightKey = null;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
async function emitCourseStartedPipelineOnly(opts) {
|
|
266
322
|
try {
|
|
267
|
-
|
|
323
|
+
if (opts.shouldCommit && !opts.shouldCommit()) return "failed";
|
|
324
|
+
const { xapiStatementSent } = await emitCourseStartedNonTrackingPipeline({
|
|
268
325
|
event: opts.event,
|
|
269
326
|
xapi: opts.xapi,
|
|
270
327
|
lxpackBridge: opts.lxpackBridge,
|
|
271
328
|
extraSinks: opts.extraSinks,
|
|
272
329
|
skipXapi: opts.skipXapi
|
|
273
330
|
});
|
|
331
|
+
if (opts.shouldCommit && !opts.shouldCommit()) return "failed";
|
|
274
332
|
(0, import_core5.markCourseStarted)(opts.storage, opts.sessionId, opts.courseId);
|
|
275
333
|
(0, import_core5.markCourseStartedPipelineDelivered)(opts.storage, opts.sessionId, opts.courseId);
|
|
276
334
|
if (xapiStatementSent) {
|
|
@@ -281,7 +339,7 @@ function emitCourseStartedPipelineOnly(opts) {
|
|
|
281
339
|
return "failed";
|
|
282
340
|
}
|
|
283
341
|
}
|
|
284
|
-
function emitCourseStarted(opts) {
|
|
342
|
+
async function emitCourseStarted(opts) {
|
|
285
343
|
const event = buildCourseStartedEvent(opts);
|
|
286
344
|
if (event === null) return "filtered";
|
|
287
345
|
const trackingAlreadyEmitted = (0, import_core5.hasCourseStartedEmittedToTracking)(
|
|
@@ -290,21 +348,25 @@ function emitCourseStarted(opts) {
|
|
|
290
348
|
opts.courseId
|
|
291
349
|
);
|
|
292
350
|
if (!trackingAlreadyEmitted) {
|
|
293
|
-
|
|
294
|
-
opts.tracking
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
351
|
+
const tracked = await emitCourseStartedToTracking(
|
|
352
|
+
opts.tracking,
|
|
353
|
+
opts.storage,
|
|
354
|
+
opts.sessionId,
|
|
355
|
+
opts.courseId,
|
|
356
|
+
event,
|
|
357
|
+
opts.shouldCommit
|
|
358
|
+
);
|
|
359
|
+
if (!tracked) return "failed";
|
|
299
360
|
}
|
|
300
361
|
return emitCourseStartedPipelineOnly({
|
|
301
362
|
...opts,
|
|
302
363
|
event,
|
|
303
364
|
skipXapi: opts.skipXapi,
|
|
304
|
-
onXapiStatementSent: opts.onXapiStatementSent
|
|
365
|
+
onXapiStatementSent: opts.onXapiStatementSent,
|
|
366
|
+
shouldCommit: opts.shouldCommit
|
|
305
367
|
});
|
|
306
368
|
}
|
|
307
|
-
function emitCourseStartedToTrackingOnly(opts) {
|
|
369
|
+
async function emitCourseStartedToTrackingOnly(opts) {
|
|
308
370
|
const event = buildCourseStartedEvent(opts);
|
|
309
371
|
if (event === null) return "filtered";
|
|
310
372
|
const trackingAlreadyEmitted = (0, import_core5.hasCourseStartedEmittedToTracking)(
|
|
@@ -313,15 +375,19 @@ function emitCourseStartedToTrackingOnly(opts) {
|
|
|
313
375
|
opts.courseId
|
|
314
376
|
);
|
|
315
377
|
if (!trackingAlreadyEmitted) {
|
|
316
|
-
|
|
317
|
-
opts.tracking
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
378
|
+
const tracked = await emitCourseStartedToTracking(
|
|
379
|
+
opts.tracking,
|
|
380
|
+
opts.storage,
|
|
381
|
+
opts.sessionId,
|
|
382
|
+
opts.courseId,
|
|
383
|
+
event,
|
|
384
|
+
opts.shouldCommit
|
|
385
|
+
);
|
|
386
|
+
if (!tracked) return "failed";
|
|
322
387
|
}
|
|
323
388
|
try {
|
|
324
|
-
|
|
389
|
+
if (opts.shouldCommit && !opts.shouldCommit()) return "failed";
|
|
390
|
+
await emitCourseStartedNonTrackingPipeline({
|
|
325
391
|
event,
|
|
326
392
|
xapi: null,
|
|
327
393
|
lxpackBridge: opts.lxpackBridge,
|
|
@@ -334,7 +400,7 @@ function emitCourseStartedToTrackingOnly(opts) {
|
|
|
334
400
|
return "failed";
|
|
335
401
|
}
|
|
336
402
|
}
|
|
337
|
-
function emitPendingCourseStarted(opts) {
|
|
403
|
+
async function emitPendingCourseStarted(opts) {
|
|
338
404
|
const trackingEmitted = (0, import_core5.hasCourseStartedEmittedToTracking)(
|
|
339
405
|
opts.storage,
|
|
340
406
|
opts.sessionId,
|
|
@@ -408,6 +474,7 @@ function useLessonkitProviderRuntime(config) {
|
|
|
408
474
|
pluginHostRef.current = pluginHost;
|
|
409
475
|
const progressRef = (0, import_react.useRef)((0, import_core4.createProgressController)());
|
|
410
476
|
const courseStartedEmittedToSinkRef = (0, import_react.useRef)(false);
|
|
477
|
+
const courseStartedEmitGenerationRef = (0, import_react.useRef)(0);
|
|
411
478
|
const prevCourseIdForProgressRef = (0, import_react.useRef)(normalizedCourseId);
|
|
412
479
|
const pendingCourseIdResetRef = (0, import_react.useRef)(false);
|
|
413
480
|
const prevUseV2RuntimeRef = (0, import_react.useRef)(useV2Runtime);
|
|
@@ -427,6 +494,7 @@ function useLessonkitProviderRuntime(config) {
|
|
|
427
494
|
}
|
|
428
495
|
pendingCourseIdResetRef.current = true;
|
|
429
496
|
courseStartedEmittedToSinkRef.current = false;
|
|
497
|
+
courseStartedEmitGenerationRef.current += 1;
|
|
430
498
|
} else if (useV2Runtime && !headlessRef.current) {
|
|
431
499
|
headlessRef.current = (0, import_core8.createLessonkitRuntime)({
|
|
432
500
|
courseId: normalizedCourseId,
|
|
@@ -444,6 +512,7 @@ function useLessonkitProviderRuntime(config) {
|
|
|
444
512
|
}
|
|
445
513
|
pendingCourseIdResetRef.current = true;
|
|
446
514
|
courseStartedEmittedToSinkRef.current = false;
|
|
515
|
+
courseStartedEmitGenerationRef.current += 1;
|
|
447
516
|
}
|
|
448
517
|
if (useV2Runtime && headlessRef.current) {
|
|
449
518
|
progressRef.current = headlessRef.current.progress;
|
|
@@ -581,30 +650,39 @@ function useLessonkitProviderRuntime(config) {
|
|
|
581
650
|
const sessionId = sessionIdRef.current;
|
|
582
651
|
const cid = courseIdRef.current;
|
|
583
652
|
const trackingActive = isTrackingActive(normalizedConfig.tracking);
|
|
653
|
+
const courseStartedFullySettled = (0, import_core5.hasCourseStartedEmittedToTracking)(defaultStorage, sessionId, cid) && (0, import_core5.hasCourseStarted)(defaultStorage, sessionId, cid) && (0, import_core5.hasCourseStartedPipelineDelivered)(defaultStorage, sessionId, cid);
|
|
584
654
|
if (!trackingActive) {
|
|
585
655
|
courseStartedEmittedToSinkRef.current = false;
|
|
586
|
-
} else if (
|
|
587
|
-
const result = emitPendingCourseStarted({
|
|
588
|
-
pluginHost: pluginHostRef.current,
|
|
589
|
-
tracking: next,
|
|
590
|
-
xapi: xapiRef.current,
|
|
591
|
-
storage: defaultStorage,
|
|
592
|
-
sessionId,
|
|
593
|
-
courseId: cid,
|
|
594
|
-
attemptId: attemptIdRef.current,
|
|
595
|
-
user: userRef.current,
|
|
596
|
-
lxpackBridge: lxpackBridgeModeRef.current,
|
|
597
|
-
extraSinks: extraSinksRef.current,
|
|
598
|
-
skipXapi: xapiCourseStartedSentOnClientRef.current,
|
|
599
|
-
onXapiStatementSent: () => {
|
|
600
|
-
xapiCourseStartedSentOnClientRef.current = true;
|
|
601
|
-
}
|
|
602
|
-
});
|
|
603
|
-
courseStartedEmittedToSinkRef.current = isCourseStartedSinkSettled(result);
|
|
604
|
-
} else if (trackingActive) {
|
|
656
|
+
} else if (courseStartedFullySettled) {
|
|
605
657
|
courseStartedEmittedToSinkRef.current = true;
|
|
658
|
+
} else if (!courseStartedEmittedToSinkRef.current) {
|
|
659
|
+
const generation = ++courseStartedEmitGenerationRef.current;
|
|
660
|
+
const shouldCommit = () => generation === courseStartedEmitGenerationRef.current;
|
|
661
|
+
void (async () => {
|
|
662
|
+
if (generation !== courseStartedEmitGenerationRef.current) return;
|
|
663
|
+
const result = await emitPendingCourseStarted({
|
|
664
|
+
pluginHost: pluginHostRef.current,
|
|
665
|
+
tracking: next,
|
|
666
|
+
xapi: xapiRef.current,
|
|
667
|
+
storage: defaultStorage,
|
|
668
|
+
sessionId,
|
|
669
|
+
courseId: cid,
|
|
670
|
+
attemptId: attemptIdRef.current,
|
|
671
|
+
user: userRef.current,
|
|
672
|
+
lxpackBridge: lxpackBridgeModeRef.current,
|
|
673
|
+
extraSinks: extraSinksRef.current,
|
|
674
|
+
skipXapi: xapiCourseStartedSentOnClientRef.current,
|
|
675
|
+
onXapiStatementSent: () => {
|
|
676
|
+
xapiCourseStartedSentOnClientRef.current = true;
|
|
677
|
+
},
|
|
678
|
+
shouldCommit
|
|
679
|
+
});
|
|
680
|
+
if (generation !== courseStartedEmitGenerationRef.current) return;
|
|
681
|
+
courseStartedEmittedToSinkRef.current = isCourseStartedSinkSettled(result);
|
|
682
|
+
})();
|
|
606
683
|
}
|
|
607
684
|
return () => {
|
|
685
|
+
courseStartedEmitGenerationRef.current += 1;
|
|
608
686
|
if (prev !== trackingRef.current) {
|
|
609
687
|
void disposeTrackingClient(prev);
|
|
610
688
|
}
|
|
@@ -681,7 +759,7 @@ function useLessonkitProviderRuntime(config) {
|
|
|
681
759
|
} catch {
|
|
682
760
|
}
|
|
683
761
|
if (!courseStartedEmittedToSinkRef.current) {
|
|
684
|
-
const result = emitPendingCourseStarted({
|
|
762
|
+
const result = await emitPendingCourseStarted({
|
|
685
763
|
pluginHost: pluginHostRef.current,
|
|
686
764
|
tracking: trackingRef.current,
|
|
687
765
|
xapi: xapiRef.current,
|
|
@@ -707,7 +785,10 @@ function useLessonkitProviderRuntime(config) {
|
|
|
707
785
|
[track]
|
|
708
786
|
);
|
|
709
787
|
const completeLesson = (0, import_react.useCallback)(
|
|
710
|
-
(lessonId) => {
|
|
788
|
+
(lessonId, opts) => {
|
|
789
|
+
if (opts?.courseId !== void 0 && opts.courseId !== courseIdRef.current) {
|
|
790
|
+
return;
|
|
791
|
+
}
|
|
711
792
|
if (useV2Runtime && headlessRef.current) {
|
|
712
793
|
headlessRef.current.completeLesson(lessonId, emitLifecycleEvent);
|
|
713
794
|
syncProgress();
|
|
@@ -925,7 +1006,7 @@ function useEnclosingLessonId() {
|
|
|
925
1006
|
|
|
926
1007
|
// src/runtime/validateComponentId.ts
|
|
927
1008
|
var import_core9 = require("@lessonkit/core");
|
|
928
|
-
function
|
|
1009
|
+
function isDevEnvironment4() {
|
|
929
1010
|
const g = globalThis;
|
|
930
1011
|
return typeof g.process !== "undefined" && g.process.env?.NODE_ENV !== "production";
|
|
931
1012
|
}
|
|
@@ -941,7 +1022,7 @@ function normalizeComponentId(id, path) {
|
|
|
941
1022
|
var mountCounts = /* @__PURE__ */ new Map();
|
|
942
1023
|
var warnedConcurrentLessons = false;
|
|
943
1024
|
function registerLessonMount(lessonId) {
|
|
944
|
-
if (
|
|
1025
|
+
if (isDevEnvironment4() && mountCounts.size > 0 && !mountCounts.has(lessonId) && !warnedConcurrentLessons) {
|
|
945
1026
|
warnedConcurrentLessons = true;
|
|
946
1027
|
console.warn(
|
|
947
1028
|
"[lessonkit] Multiple <Lesson> components are mounted; only one should be active at a time. Set autoCompleteOnUnmount={false} on routed lessons or unmount the previous lesson before showing the next."
|
|
@@ -984,9 +1065,18 @@ function Lesson(props) {
|
|
|
984
1065
|
const { setActiveLesson, config } = useLessonkit();
|
|
985
1066
|
const { completeLesson } = useCompletion();
|
|
986
1067
|
const lessonMountGenerationRef = (0, import_react5.useRef)(0);
|
|
1068
|
+
const liveCourseIdRef = (0, import_react5.useRef)(config.courseId);
|
|
1069
|
+
liveCourseIdRef.current = config.courseId;
|
|
987
1070
|
(0, import_react5.useEffect)(() => {
|
|
988
1071
|
const unregister = registerLessonMount(lessonId);
|
|
989
1072
|
const generation = ++lessonMountGenerationRef.current;
|
|
1073
|
+
const mountedCourseId = config.courseId;
|
|
1074
|
+
let effectSurvivedTick = false;
|
|
1075
|
+
queueMicrotask(() => {
|
|
1076
|
+
queueMicrotask(() => {
|
|
1077
|
+
effectSurvivedTick = true;
|
|
1078
|
+
});
|
|
1079
|
+
});
|
|
990
1080
|
setActiveLesson(lessonId);
|
|
991
1081
|
return () => {
|
|
992
1082
|
unregister();
|
|
@@ -995,8 +1085,10 @@ function Lesson(props) {
|
|
|
995
1085
|
}
|
|
996
1086
|
if (!autoComplete) return;
|
|
997
1087
|
queueMicrotask(() => {
|
|
1088
|
+
if (!effectSurvivedTick) return;
|
|
998
1089
|
if (lessonMountGenerationRef.current !== generation) return;
|
|
999
|
-
|
|
1090
|
+
if (liveCourseIdRef.current !== mountedCourseId) return;
|
|
1091
|
+
completeLesson(lessonId, { courseId: mountedCourseId });
|
|
1000
1092
|
});
|
|
1001
1093
|
};
|
|
1002
1094
|
}, [lessonId, config.courseId, setActiveLesson, completeLesson, autoComplete]);
|
|
@@ -1055,11 +1147,10 @@ function KnowledgeCheck(props) {
|
|
|
1055
1147
|
);
|
|
1056
1148
|
}
|
|
1057
1149
|
function Quiz(props) {
|
|
1058
|
-
const checkId = (0, import_react5.useMemo)(() => normalizeComponentId(props.checkId, "checkId"), [props.checkId]);
|
|
1059
1150
|
const enclosingLessonId = useEnclosingLessonId();
|
|
1060
1151
|
const missingLesson = enclosingLessonId === void 0;
|
|
1061
1152
|
(0, import_react5.useEffect)(() => {
|
|
1062
|
-
if (!missingLesson ||
|
|
1153
|
+
if (!missingLesson || isDevEnvironment4()) return;
|
|
1063
1154
|
if (!warnedQuizOutsideLesson) {
|
|
1064
1155
|
warnedQuizOutsideLesson = true;
|
|
1065
1156
|
console.error(
|
|
@@ -1067,9 +1158,17 @@ function Quiz(props) {
|
|
|
1067
1158
|
);
|
|
1068
1159
|
}
|
|
1069
1160
|
}, [missingLesson]);
|
|
1070
|
-
if (missingLesson &&
|
|
1161
|
+
if (missingLesson && isDevEnvironment4()) {
|
|
1071
1162
|
throw new Error("[lessonkit] <Quiz> must be wrapped in <Lesson>");
|
|
1072
1163
|
}
|
|
1164
|
+
if (missingLesson) {
|
|
1165
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("section", { role: "alert", "aria-label": "Quiz configuration error", "data-lk-check-id": props.checkId, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { children: "Quiz must be placed inside a Lesson." }) });
|
|
1166
|
+
}
|
|
1167
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(QuizInner, { ...props, enclosingLessonId });
|
|
1168
|
+
}
|
|
1169
|
+
function QuizInner(props) {
|
|
1170
|
+
const { enclosingLessonId } = props;
|
|
1171
|
+
const checkId = (0, import_react5.useMemo)(() => normalizeComponentId(props.checkId, "checkId"), [props.checkId]);
|
|
1073
1172
|
const quiz = useQuizState(enclosingLessonId);
|
|
1074
1173
|
const { plugins, config, session } = useLessonkit();
|
|
1075
1174
|
const [selected, setSelected] = (0, import_react5.useState)(null);
|
|
@@ -1092,9 +1191,6 @@ function Quiz(props) {
|
|
|
1092
1191
|
}
|
|
1093
1192
|
return choice === props.answer;
|
|
1094
1193
|
};
|
|
1095
|
-
if (missingLesson) {
|
|
1096
|
-
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("section", { role: "alert", "aria-label": "Quiz configuration error", "data-lk-check-id": checkId, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { children: "Quiz must be placed inside a Lesson." }) });
|
|
1097
|
-
}
|
|
1098
1194
|
const passed = quizPassed;
|
|
1099
1195
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("section", { "aria-label": "Quiz", "data-lk-check-id": checkId, children: [
|
|
1100
1196
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { id: questionId, children: props.question }),
|
package/dist/index.d.cts
CHANGED
|
@@ -58,7 +58,9 @@ type LessonkitRuntime = {
|
|
|
58
58
|
user?: TelemetryUser;
|
|
59
59
|
};
|
|
60
60
|
setActiveLesson: (lessonId: LessonId) => void;
|
|
61
|
-
completeLesson: (lessonId: LessonId
|
|
61
|
+
completeLesson: (lessonId: LessonId, opts?: {
|
|
62
|
+
courseId?: CourseId;
|
|
63
|
+
}) => void;
|
|
62
64
|
completeCourse: () => void;
|
|
63
65
|
track: <N extends TelemetryEventName>(name: N, data?: TelemetryDataFor<N>, opts?: {
|
|
64
66
|
lessonId?: LessonId;
|
|
@@ -115,7 +117,9 @@ declare function useTracking(): {
|
|
|
115
117
|
}) => void;
|
|
116
118
|
};
|
|
117
119
|
declare function useCompletion(): {
|
|
118
|
-
completeLesson: (lessonId: LessonId
|
|
120
|
+
completeLesson: (lessonId: LessonId, opts?: {
|
|
121
|
+
courseId?: _lessonkit_core.CourseId;
|
|
122
|
+
}) => void;
|
|
119
123
|
completeCourse: () => void;
|
|
120
124
|
};
|
|
121
125
|
declare function useQuizState(enclosingLessonId?: LessonId): {
|
package/dist/index.d.ts
CHANGED
|
@@ -58,7 +58,9 @@ type LessonkitRuntime = {
|
|
|
58
58
|
user?: TelemetryUser;
|
|
59
59
|
};
|
|
60
60
|
setActiveLesson: (lessonId: LessonId) => void;
|
|
61
|
-
completeLesson: (lessonId: LessonId
|
|
61
|
+
completeLesson: (lessonId: LessonId, opts?: {
|
|
62
|
+
courseId?: CourseId;
|
|
63
|
+
}) => void;
|
|
62
64
|
completeCourse: () => void;
|
|
63
65
|
track: <N extends TelemetryEventName>(name: N, data?: TelemetryDataFor<N>, opts?: {
|
|
64
66
|
lessonId?: LessonId;
|
|
@@ -115,7 +117,9 @@ declare function useTracking(): {
|
|
|
115
117
|
}) => void;
|
|
116
118
|
};
|
|
117
119
|
declare function useCompletion(): {
|
|
118
|
-
completeLesson: (lessonId: LessonId
|
|
120
|
+
completeLesson: (lessonId: LessonId, opts?: {
|
|
121
|
+
courseId?: _lessonkit_core.CourseId;
|
|
122
|
+
}) => void;
|
|
119
123
|
completeCourse: () => void;
|
|
120
124
|
};
|
|
121
125
|
declare function useQuizState(enclosingLessonId?: LessonId): {
|
package/dist/index.js
CHANGED
|
@@ -142,7 +142,40 @@ import {
|
|
|
142
142
|
|
|
143
143
|
// src/runtime/courseStartedPipeline.ts
|
|
144
144
|
import { telemetryEventToXAPIStatement as telemetryEventToXAPIStatement2 } from "@lessonkit/xapi";
|
|
145
|
-
function
|
|
145
|
+
function isDevEnvironment3() {
|
|
146
|
+
const g = globalThis;
|
|
147
|
+
return typeof g.process !== "undefined" && g.process.env?.NODE_ENV !== "production";
|
|
148
|
+
}
|
|
149
|
+
function warnExtraSinkFailure(sinkId, err) {
|
|
150
|
+
if (isDevEnvironment3()) {
|
|
151
|
+
console.warn(
|
|
152
|
+
`[lessonkit] course_started extra sink "${sinkId}" failed:`,
|
|
153
|
+
err instanceof Error ? err.message : err
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
async function emitExtraSinks(sinks, event, emitCtx) {
|
|
158
|
+
await Promise.all(
|
|
159
|
+
sinks.map(async (sink) => {
|
|
160
|
+
let result;
|
|
161
|
+
try {
|
|
162
|
+
result = sink.emit(event, emitCtx);
|
|
163
|
+
} catch (err) {
|
|
164
|
+
warnExtraSinkFailure(sink.id, err);
|
|
165
|
+
throw err;
|
|
166
|
+
}
|
|
167
|
+
if (result != null && typeof result.then === "function") {
|
|
168
|
+
try {
|
|
169
|
+
await result;
|
|
170
|
+
} catch (err) {
|
|
171
|
+
warnExtraSinkFailure(sink.id, err);
|
|
172
|
+
throw err;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
})
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
async function emitCourseStartedNonTrackingPipeline(opts) {
|
|
146
179
|
let xapiStatementSent = false;
|
|
147
180
|
if (!opts.skipXapi && opts.xapi) {
|
|
148
181
|
const statement = telemetryEventToXAPIStatement2(opts.event);
|
|
@@ -157,9 +190,7 @@ function emitCourseStartedNonTrackingPipeline(opts) {
|
|
|
157
190
|
sessionId: opts.event.sessionId,
|
|
158
191
|
attemptId: opts.event.attemptId
|
|
159
192
|
};
|
|
160
|
-
|
|
161
|
-
sink.emit(opts.event, emitCtx);
|
|
162
|
-
}
|
|
193
|
+
await emitExtraSinks(opts.extraSinks ?? [], opts.event, emitCtx);
|
|
163
194
|
return { xapiStatementSent };
|
|
164
195
|
}
|
|
165
196
|
|
|
@@ -211,6 +242,7 @@ async function disposeTrackingClient(client) {
|
|
|
211
242
|
// src/provider/useLessonkitProviderRuntime.ts
|
|
212
243
|
var useIsoLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect;
|
|
213
244
|
var defaultStorage = createSessionStoragePort();
|
|
245
|
+
var courseStartedTrackingFlightKey = null;
|
|
214
246
|
function isTrackingActive(tracking) {
|
|
215
247
|
return tracking?.enabled !== false;
|
|
216
248
|
}
|
|
@@ -233,15 +265,41 @@ function buildCourseStartedEvent(opts) {
|
|
|
233
265
|
});
|
|
234
266
|
return opts.pluginHost ? opts.pluginHost.runTelemetry(built, pluginCtx) : built;
|
|
235
267
|
}
|
|
236
|
-
function
|
|
268
|
+
async function emitCourseStartedToTracking(tracking, storage, sessionId, courseId, event, shouldCommit) {
|
|
269
|
+
const flightKey = `${sessionId}:${courseId}`;
|
|
270
|
+
if (hasCourseStartedEmittedToTracking(storage, sessionId, courseId)) {
|
|
271
|
+
return true;
|
|
272
|
+
}
|
|
273
|
+
if (courseStartedTrackingFlightKey === flightKey) {
|
|
274
|
+
return false;
|
|
275
|
+
}
|
|
276
|
+
courseStartedTrackingFlightKey = flightKey;
|
|
277
|
+
try {
|
|
278
|
+
if (shouldCommit && !shouldCommit()) return false;
|
|
279
|
+
tracking.track(event);
|
|
280
|
+
await tracking.flush?.();
|
|
281
|
+
if (shouldCommit && !shouldCommit()) return false;
|
|
282
|
+
markCourseStartedEmittedToTracking(storage, sessionId, courseId);
|
|
283
|
+
return true;
|
|
284
|
+
} catch {
|
|
285
|
+
return false;
|
|
286
|
+
} finally {
|
|
287
|
+
if (courseStartedTrackingFlightKey === flightKey) {
|
|
288
|
+
courseStartedTrackingFlightKey = null;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
async function emitCourseStartedPipelineOnly(opts) {
|
|
237
293
|
try {
|
|
238
|
-
|
|
294
|
+
if (opts.shouldCommit && !opts.shouldCommit()) return "failed";
|
|
295
|
+
const { xapiStatementSent } = await emitCourseStartedNonTrackingPipeline({
|
|
239
296
|
event: opts.event,
|
|
240
297
|
xapi: opts.xapi,
|
|
241
298
|
lxpackBridge: opts.lxpackBridge,
|
|
242
299
|
extraSinks: opts.extraSinks,
|
|
243
300
|
skipXapi: opts.skipXapi
|
|
244
301
|
});
|
|
302
|
+
if (opts.shouldCommit && !opts.shouldCommit()) return "failed";
|
|
245
303
|
markCourseStarted(opts.storage, opts.sessionId, opts.courseId);
|
|
246
304
|
markCourseStartedPipelineDelivered(opts.storage, opts.sessionId, opts.courseId);
|
|
247
305
|
if (xapiStatementSent) {
|
|
@@ -252,7 +310,7 @@ function emitCourseStartedPipelineOnly(opts) {
|
|
|
252
310
|
return "failed";
|
|
253
311
|
}
|
|
254
312
|
}
|
|
255
|
-
function emitCourseStarted(opts) {
|
|
313
|
+
async function emitCourseStarted(opts) {
|
|
256
314
|
const event = buildCourseStartedEvent(opts);
|
|
257
315
|
if (event === null) return "filtered";
|
|
258
316
|
const trackingAlreadyEmitted = hasCourseStartedEmittedToTracking(
|
|
@@ -261,21 +319,25 @@ function emitCourseStarted(opts) {
|
|
|
261
319
|
opts.courseId
|
|
262
320
|
);
|
|
263
321
|
if (!trackingAlreadyEmitted) {
|
|
264
|
-
|
|
265
|
-
opts.tracking
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
322
|
+
const tracked = await emitCourseStartedToTracking(
|
|
323
|
+
opts.tracking,
|
|
324
|
+
opts.storage,
|
|
325
|
+
opts.sessionId,
|
|
326
|
+
opts.courseId,
|
|
327
|
+
event,
|
|
328
|
+
opts.shouldCommit
|
|
329
|
+
);
|
|
330
|
+
if (!tracked) return "failed";
|
|
270
331
|
}
|
|
271
332
|
return emitCourseStartedPipelineOnly({
|
|
272
333
|
...opts,
|
|
273
334
|
event,
|
|
274
335
|
skipXapi: opts.skipXapi,
|
|
275
|
-
onXapiStatementSent: opts.onXapiStatementSent
|
|
336
|
+
onXapiStatementSent: opts.onXapiStatementSent,
|
|
337
|
+
shouldCommit: opts.shouldCommit
|
|
276
338
|
});
|
|
277
339
|
}
|
|
278
|
-
function emitCourseStartedToTrackingOnly(opts) {
|
|
340
|
+
async function emitCourseStartedToTrackingOnly(opts) {
|
|
279
341
|
const event = buildCourseStartedEvent(opts);
|
|
280
342
|
if (event === null) return "filtered";
|
|
281
343
|
const trackingAlreadyEmitted = hasCourseStartedEmittedToTracking(
|
|
@@ -284,15 +346,19 @@ function emitCourseStartedToTrackingOnly(opts) {
|
|
|
284
346
|
opts.courseId
|
|
285
347
|
);
|
|
286
348
|
if (!trackingAlreadyEmitted) {
|
|
287
|
-
|
|
288
|
-
opts.tracking
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
349
|
+
const tracked = await emitCourseStartedToTracking(
|
|
350
|
+
opts.tracking,
|
|
351
|
+
opts.storage,
|
|
352
|
+
opts.sessionId,
|
|
353
|
+
opts.courseId,
|
|
354
|
+
event,
|
|
355
|
+
opts.shouldCommit
|
|
356
|
+
);
|
|
357
|
+
if (!tracked) return "failed";
|
|
293
358
|
}
|
|
294
359
|
try {
|
|
295
|
-
|
|
360
|
+
if (opts.shouldCommit && !opts.shouldCommit()) return "failed";
|
|
361
|
+
await emitCourseStartedNonTrackingPipeline({
|
|
296
362
|
event,
|
|
297
363
|
xapi: null,
|
|
298
364
|
lxpackBridge: opts.lxpackBridge,
|
|
@@ -305,7 +371,7 @@ function emitCourseStartedToTrackingOnly(opts) {
|
|
|
305
371
|
return "failed";
|
|
306
372
|
}
|
|
307
373
|
}
|
|
308
|
-
function emitPendingCourseStarted(opts) {
|
|
374
|
+
async function emitPendingCourseStarted(opts) {
|
|
309
375
|
const trackingEmitted = hasCourseStartedEmittedToTracking(
|
|
310
376
|
opts.storage,
|
|
311
377
|
opts.sessionId,
|
|
@@ -379,6 +445,7 @@ function useLessonkitProviderRuntime(config) {
|
|
|
379
445
|
pluginHostRef.current = pluginHost;
|
|
380
446
|
const progressRef = useRef(createProgressController());
|
|
381
447
|
const courseStartedEmittedToSinkRef = useRef(false);
|
|
448
|
+
const courseStartedEmitGenerationRef = useRef(0);
|
|
382
449
|
const prevCourseIdForProgressRef = useRef(normalizedCourseId);
|
|
383
450
|
const pendingCourseIdResetRef = useRef(false);
|
|
384
451
|
const prevUseV2RuntimeRef = useRef(useV2Runtime);
|
|
@@ -398,6 +465,7 @@ function useLessonkitProviderRuntime(config) {
|
|
|
398
465
|
}
|
|
399
466
|
pendingCourseIdResetRef.current = true;
|
|
400
467
|
courseStartedEmittedToSinkRef.current = false;
|
|
468
|
+
courseStartedEmitGenerationRef.current += 1;
|
|
401
469
|
} else if (useV2Runtime && !headlessRef.current) {
|
|
402
470
|
headlessRef.current = createLessonkitRuntime({
|
|
403
471
|
courseId: normalizedCourseId,
|
|
@@ -415,6 +483,7 @@ function useLessonkitProviderRuntime(config) {
|
|
|
415
483
|
}
|
|
416
484
|
pendingCourseIdResetRef.current = true;
|
|
417
485
|
courseStartedEmittedToSinkRef.current = false;
|
|
486
|
+
courseStartedEmitGenerationRef.current += 1;
|
|
418
487
|
}
|
|
419
488
|
if (useV2Runtime && headlessRef.current) {
|
|
420
489
|
progressRef.current = headlessRef.current.progress;
|
|
@@ -552,30 +621,39 @@ function useLessonkitProviderRuntime(config) {
|
|
|
552
621
|
const sessionId = sessionIdRef.current;
|
|
553
622
|
const cid = courseIdRef.current;
|
|
554
623
|
const trackingActive = isTrackingActive(normalizedConfig.tracking);
|
|
624
|
+
const courseStartedFullySettled = hasCourseStartedEmittedToTracking(defaultStorage, sessionId, cid) && hasCourseStarted(defaultStorage, sessionId, cid) && hasCourseStartedPipelineDelivered(defaultStorage, sessionId, cid);
|
|
555
625
|
if (!trackingActive) {
|
|
556
626
|
courseStartedEmittedToSinkRef.current = false;
|
|
557
|
-
} else if (
|
|
558
|
-
const result = emitPendingCourseStarted({
|
|
559
|
-
pluginHost: pluginHostRef.current,
|
|
560
|
-
tracking: next,
|
|
561
|
-
xapi: xapiRef.current,
|
|
562
|
-
storage: defaultStorage,
|
|
563
|
-
sessionId,
|
|
564
|
-
courseId: cid,
|
|
565
|
-
attemptId: attemptIdRef.current,
|
|
566
|
-
user: userRef.current,
|
|
567
|
-
lxpackBridge: lxpackBridgeModeRef.current,
|
|
568
|
-
extraSinks: extraSinksRef.current,
|
|
569
|
-
skipXapi: xapiCourseStartedSentOnClientRef.current,
|
|
570
|
-
onXapiStatementSent: () => {
|
|
571
|
-
xapiCourseStartedSentOnClientRef.current = true;
|
|
572
|
-
}
|
|
573
|
-
});
|
|
574
|
-
courseStartedEmittedToSinkRef.current = isCourseStartedSinkSettled(result);
|
|
575
|
-
} else if (trackingActive) {
|
|
627
|
+
} else if (courseStartedFullySettled) {
|
|
576
628
|
courseStartedEmittedToSinkRef.current = true;
|
|
629
|
+
} else if (!courseStartedEmittedToSinkRef.current) {
|
|
630
|
+
const generation = ++courseStartedEmitGenerationRef.current;
|
|
631
|
+
const shouldCommit = () => generation === courseStartedEmitGenerationRef.current;
|
|
632
|
+
void (async () => {
|
|
633
|
+
if (generation !== courseStartedEmitGenerationRef.current) return;
|
|
634
|
+
const result = await emitPendingCourseStarted({
|
|
635
|
+
pluginHost: pluginHostRef.current,
|
|
636
|
+
tracking: next,
|
|
637
|
+
xapi: xapiRef.current,
|
|
638
|
+
storage: defaultStorage,
|
|
639
|
+
sessionId,
|
|
640
|
+
courseId: cid,
|
|
641
|
+
attemptId: attemptIdRef.current,
|
|
642
|
+
user: userRef.current,
|
|
643
|
+
lxpackBridge: lxpackBridgeModeRef.current,
|
|
644
|
+
extraSinks: extraSinksRef.current,
|
|
645
|
+
skipXapi: xapiCourseStartedSentOnClientRef.current,
|
|
646
|
+
onXapiStatementSent: () => {
|
|
647
|
+
xapiCourseStartedSentOnClientRef.current = true;
|
|
648
|
+
},
|
|
649
|
+
shouldCommit
|
|
650
|
+
});
|
|
651
|
+
if (generation !== courseStartedEmitGenerationRef.current) return;
|
|
652
|
+
courseStartedEmittedToSinkRef.current = isCourseStartedSinkSettled(result);
|
|
653
|
+
})();
|
|
577
654
|
}
|
|
578
655
|
return () => {
|
|
656
|
+
courseStartedEmitGenerationRef.current += 1;
|
|
579
657
|
if (prev !== trackingRef.current) {
|
|
580
658
|
void disposeTrackingClient(prev);
|
|
581
659
|
}
|
|
@@ -652,7 +730,7 @@ function useLessonkitProviderRuntime(config) {
|
|
|
652
730
|
} catch {
|
|
653
731
|
}
|
|
654
732
|
if (!courseStartedEmittedToSinkRef.current) {
|
|
655
|
-
const result = emitPendingCourseStarted({
|
|
733
|
+
const result = await emitPendingCourseStarted({
|
|
656
734
|
pluginHost: pluginHostRef.current,
|
|
657
735
|
tracking: trackingRef.current,
|
|
658
736
|
xapi: xapiRef.current,
|
|
@@ -678,7 +756,10 @@ function useLessonkitProviderRuntime(config) {
|
|
|
678
756
|
[track]
|
|
679
757
|
);
|
|
680
758
|
const completeLesson = useCallback(
|
|
681
|
-
(lessonId) => {
|
|
759
|
+
(lessonId, opts) => {
|
|
760
|
+
if (opts?.courseId !== void 0 && opts.courseId !== courseIdRef.current) {
|
|
761
|
+
return;
|
|
762
|
+
}
|
|
682
763
|
if (useV2Runtime && headlessRef.current) {
|
|
683
764
|
headlessRef.current.completeLesson(lessonId, emitLifecycleEvent);
|
|
684
765
|
syncProgress();
|
|
@@ -896,7 +977,7 @@ function useEnclosingLessonId() {
|
|
|
896
977
|
|
|
897
978
|
// src/runtime/validateComponentId.ts
|
|
898
979
|
import { assertValidId as assertValidId2 } from "@lessonkit/core";
|
|
899
|
-
function
|
|
980
|
+
function isDevEnvironment4() {
|
|
900
981
|
const g = globalThis;
|
|
901
982
|
return typeof g.process !== "undefined" && g.process.env?.NODE_ENV !== "production";
|
|
902
983
|
}
|
|
@@ -912,7 +993,7 @@ function normalizeComponentId(id, path) {
|
|
|
912
993
|
var mountCounts = /* @__PURE__ */ new Map();
|
|
913
994
|
var warnedConcurrentLessons = false;
|
|
914
995
|
function registerLessonMount(lessonId) {
|
|
915
|
-
if (
|
|
996
|
+
if (isDevEnvironment4() && mountCounts.size > 0 && !mountCounts.has(lessonId) && !warnedConcurrentLessons) {
|
|
916
997
|
warnedConcurrentLessons = true;
|
|
917
998
|
console.warn(
|
|
918
999
|
"[lessonkit] Multiple <Lesson> components are mounted; only one should be active at a time. Set autoCompleteOnUnmount={false} on routed lessons or unmount the previous lesson before showing the next."
|
|
@@ -955,9 +1036,18 @@ function Lesson(props) {
|
|
|
955
1036
|
const { setActiveLesson, config } = useLessonkit();
|
|
956
1037
|
const { completeLesson } = useCompletion();
|
|
957
1038
|
const lessonMountGenerationRef = useRef2(0);
|
|
1039
|
+
const liveCourseIdRef = useRef2(config.courseId);
|
|
1040
|
+
liveCourseIdRef.current = config.courseId;
|
|
958
1041
|
useEffect2(() => {
|
|
959
1042
|
const unregister = registerLessonMount(lessonId);
|
|
960
1043
|
const generation = ++lessonMountGenerationRef.current;
|
|
1044
|
+
const mountedCourseId = config.courseId;
|
|
1045
|
+
let effectSurvivedTick = false;
|
|
1046
|
+
queueMicrotask(() => {
|
|
1047
|
+
queueMicrotask(() => {
|
|
1048
|
+
effectSurvivedTick = true;
|
|
1049
|
+
});
|
|
1050
|
+
});
|
|
961
1051
|
setActiveLesson(lessonId);
|
|
962
1052
|
return () => {
|
|
963
1053
|
unregister();
|
|
@@ -966,8 +1056,10 @@ function Lesson(props) {
|
|
|
966
1056
|
}
|
|
967
1057
|
if (!autoComplete) return;
|
|
968
1058
|
queueMicrotask(() => {
|
|
1059
|
+
if (!effectSurvivedTick) return;
|
|
969
1060
|
if (lessonMountGenerationRef.current !== generation) return;
|
|
970
|
-
|
|
1061
|
+
if (liveCourseIdRef.current !== mountedCourseId) return;
|
|
1062
|
+
completeLesson(lessonId, { courseId: mountedCourseId });
|
|
971
1063
|
});
|
|
972
1064
|
};
|
|
973
1065
|
}, [lessonId, config.courseId, setActiveLesson, completeLesson, autoComplete]);
|
|
@@ -1026,11 +1118,10 @@ function KnowledgeCheck(props) {
|
|
|
1026
1118
|
);
|
|
1027
1119
|
}
|
|
1028
1120
|
function Quiz(props) {
|
|
1029
|
-
const checkId = useMemo3(() => normalizeComponentId(props.checkId, "checkId"), [props.checkId]);
|
|
1030
1121
|
const enclosingLessonId = useEnclosingLessonId();
|
|
1031
1122
|
const missingLesson = enclosingLessonId === void 0;
|
|
1032
1123
|
useEffect2(() => {
|
|
1033
|
-
if (!missingLesson ||
|
|
1124
|
+
if (!missingLesson || isDevEnvironment4()) return;
|
|
1034
1125
|
if (!warnedQuizOutsideLesson) {
|
|
1035
1126
|
warnedQuizOutsideLesson = true;
|
|
1036
1127
|
console.error(
|
|
@@ -1038,9 +1129,17 @@ function Quiz(props) {
|
|
|
1038
1129
|
);
|
|
1039
1130
|
}
|
|
1040
1131
|
}, [missingLesson]);
|
|
1041
|
-
if (missingLesson &&
|
|
1132
|
+
if (missingLesson && isDevEnvironment4()) {
|
|
1042
1133
|
throw new Error("[lessonkit] <Quiz> must be wrapped in <Lesson>");
|
|
1043
1134
|
}
|
|
1135
|
+
if (missingLesson) {
|
|
1136
|
+
return /* @__PURE__ */ jsx2("section", { role: "alert", "aria-label": "Quiz configuration error", "data-lk-check-id": props.checkId, children: /* @__PURE__ */ jsx2("p", { children: "Quiz must be placed inside a Lesson." }) });
|
|
1137
|
+
}
|
|
1138
|
+
return /* @__PURE__ */ jsx2(QuizInner, { ...props, enclosingLessonId });
|
|
1139
|
+
}
|
|
1140
|
+
function QuizInner(props) {
|
|
1141
|
+
const { enclosingLessonId } = props;
|
|
1142
|
+
const checkId = useMemo3(() => normalizeComponentId(props.checkId, "checkId"), [props.checkId]);
|
|
1044
1143
|
const quiz = useQuizState(enclosingLessonId);
|
|
1045
1144
|
const { plugins, config, session } = useLessonkit();
|
|
1046
1145
|
const [selected, setSelected] = useState2(null);
|
|
@@ -1063,9 +1162,6 @@ function Quiz(props) {
|
|
|
1063
1162
|
}
|
|
1064
1163
|
return choice === props.answer;
|
|
1065
1164
|
};
|
|
1066
|
-
if (missingLesson) {
|
|
1067
|
-
return /* @__PURE__ */ jsx2("section", { role: "alert", "aria-label": "Quiz configuration error", "data-lk-check-id": checkId, children: /* @__PURE__ */ jsx2("p", { children: "Quiz must be placed inside a Lesson." }) });
|
|
1068
|
-
}
|
|
1069
1165
|
const passed = quizPassed;
|
|
1070
1166
|
return /* @__PURE__ */ jsxs("section", { "aria-label": "Quiz", "data-lk-check-id": checkId, children: [
|
|
1071
1167
|
/* @__PURE__ */ jsx2("p", { id: questionId, children: props.question }),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lessonkit/react",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "React components and hooks for building learning experiences with LessonKit.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -56,11 +56,11 @@
|
|
|
56
56
|
"react-dom": ">=18"
|
|
57
57
|
},
|
|
58
58
|
"dependencies": {
|
|
59
|
-
"@lessonkit/accessibility": "1.0.
|
|
60
|
-
"@lessonkit/core": "1.0.
|
|
61
|
-
"@lessonkit/lxpack": "1.0.
|
|
62
|
-
"@lessonkit/themes": "1.0.
|
|
63
|
-
"@lessonkit/xapi": "1.0.
|
|
59
|
+
"@lessonkit/accessibility": "1.0.2",
|
|
60
|
+
"@lessonkit/core": "1.0.2",
|
|
61
|
+
"@lessonkit/lxpack": "1.0.2",
|
|
62
|
+
"@lessonkit/themes": "1.0.2",
|
|
63
|
+
"@lessonkit/xapi": "1.0.2"
|
|
64
64
|
},
|
|
65
65
|
"devDependencies": {
|
|
66
66
|
"@storybook/addon-essentials": "8.6.18",
|
|
@@ -80,6 +80,6 @@
|
|
|
80
80
|
"tsup": "^8.5.0",
|
|
81
81
|
"typescript": "^5.8.3",
|
|
82
82
|
"vite": "^6.3.5",
|
|
83
|
-
"vitest": "^
|
|
83
|
+
"vitest": "^4.1.8"
|
|
84
84
|
}
|
|
85
85
|
}
|