@lessonkit/react 1.3.1 → 1.4.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.
@@ -0,0 +1,540 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/testing.ts
31
+ var testing_exports = {};
32
+ __export(testing_exports, {
33
+ resetAssessmentWarningsForTests: () => resetAssessmentWarningsForTests,
34
+ resetCompoundValidationWarningsForTests: () => resetCompoundValidationWarningsForTests,
35
+ resetCourseStartedTrackingFlightForTests: () => resetCourseStartedTrackingFlightForTests,
36
+ resetLessonMountRegistryForTests: () => resetLessonMountRegistryForTests,
37
+ resetLessonkitProviderStorageForTests: () => resetLessonkitProviderStorageForTests,
38
+ resetQuizWarningsForTests: () => resetQuizWarningsForTests
39
+ });
40
+ module.exports = __toCommonJS(testing_exports);
41
+
42
+ // src/components/Quiz.tsx
43
+ var import_react12 = require("react");
44
+ var import_accessibility = require("@lessonkit/accessibility");
45
+
46
+ // src/assessment/AssessmentLessonGuard.tsx
47
+ var import_react2 = require("react");
48
+
49
+ // src/lessonContext.tsx
50
+ var import_react = require("react");
51
+ var LessonContext = (0, import_react.createContext)(void 0);
52
+ function useEnclosingLessonId() {
53
+ return (0, import_react.useContext)(LessonContext);
54
+ }
55
+
56
+ // src/runtime/validateComponentId.ts
57
+ var import_core = require("@lessonkit/core");
58
+ function isDevEnvironment() {
59
+ const g = globalThis;
60
+ return typeof g.process !== "undefined" && g.process.env?.NODE_ENV !== "production";
61
+ }
62
+ function normalizeComponentId(id, path) {
63
+ if (path === "courseId") return (0, import_core.assertValidId)(id, "courseId");
64
+ if (path === "lessonId") return (0, import_core.assertValidId)(id, "lessonId");
65
+ if (path === "checkId") return (0, import_core.assertValidId)(id, "checkId");
66
+ if (path === "blockId") return (0, import_core.assertValidId)(id, "blockId");
67
+ return (0, import_core.assertValidId)(id, path);
68
+ }
69
+
70
+ // src/assessment/AssessmentLessonGuard.tsx
71
+ var import_jsx_runtime = require("react/jsx-runtime");
72
+ var warnedAssessmentOutsideLesson = false;
73
+ function resetAssessmentWarningsForTests() {
74
+ warnedAssessmentOutsideLesson = false;
75
+ }
76
+ function AssessmentLessonGuard(props) {
77
+ const enclosingLessonId = useEnclosingLessonId();
78
+ const missingLesson = enclosingLessonId === void 0;
79
+ (0, import_react2.useEffect)(() => {
80
+ if (!missingLesson || isDevEnvironment()) return;
81
+ if (!warnedAssessmentOutsideLesson) {
82
+ warnedAssessmentOutsideLesson = true;
83
+ console.error(
84
+ `[lessonkit] <${props.blockLabel}> must be wrapped in <Lesson>; assessment telemetry will not be emitted.`
85
+ );
86
+ }
87
+ }, [missingLesson, props.blockLabel]);
88
+ if (missingLesson && isDevEnvironment()) {
89
+ throw new Error(`[lessonkit] <${props.blockLabel}> must be wrapped in <Lesson>`);
90
+ }
91
+ if (missingLesson) {
92
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("section", { role: "alert", "aria-label": `${props.blockLabel} configuration error`, "data-lk-check-id": props.checkId, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { children: [
93
+ props.blockLabel,
94
+ " must be placed inside a Lesson."
95
+ ] }) });
96
+ }
97
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: props.children(enclosingLessonId) });
98
+ }
99
+
100
+ // src/assessment/internal/buildAssessmentHandle.ts
101
+ function buildAssessmentHandle(opts) {
102
+ return {
103
+ getScore: opts.getScore,
104
+ getMaxScore: opts.getMaxScore,
105
+ getAnswerGiven: opts.getAnswerGiven,
106
+ resetTask: opts.resetTask,
107
+ showSolutions: opts.showSolutions,
108
+ getXAPIData: opts.getXAPIData,
109
+ ...opts.getCurrentState ? { getCurrentState: opts.getCurrentState } : {},
110
+ ...opts.resume ? { resume: opts.resume } : {}
111
+ };
112
+ }
113
+
114
+ // src/assessment/internal/resumeState.ts
115
+ function readBooleanField(state, key) {
116
+ const value = state[key];
117
+ if (value === true || value === false || value === null) return value;
118
+ return void 0;
119
+ }
120
+ function readStringField(state, key) {
121
+ const value = state[key];
122
+ if (typeof value === "string" || value === null) return value;
123
+ return void 0;
124
+ }
125
+ function readNumberField(state, key) {
126
+ const value = state[key];
127
+ if (typeof value === "number" && Number.isFinite(value)) return value;
128
+ if (value === null) return null;
129
+ return void 0;
130
+ }
131
+
132
+ // src/assessment/internal/useAssessmentHandleRegistration.ts
133
+ var import_react6 = require("react");
134
+
135
+ // src/compound/CompoundProvider.tsx
136
+ var import_react5 = __toESM(require("react"), 1);
137
+ var import_core2 = require("@lessonkit/core");
138
+
139
+ // src/compound/CompoundHydrationBridge.tsx
140
+ var import_react3 = require("react");
141
+ var import_jsx_runtime2 = require("react/jsx-runtime");
142
+ var CompoundHydrationBridgeContext = (0, import_react3.createContext)(
143
+ null
144
+ );
145
+
146
+ // src/compound/CompoundPageIndexContext.tsx
147
+ var import_react4 = require("react");
148
+ var import_jsx_runtime3 = require("react/jsx-runtime");
149
+ var CompoundPageIndexContext = (0, import_react4.createContext)(void 0);
150
+ function useCompoundPageIndex() {
151
+ return (0, import_react4.useContext)(CompoundPageIndexContext);
152
+ }
153
+
154
+ // src/compound/CompoundProvider.tsx
155
+ var import_jsx_runtime4 = require("react/jsx-runtime");
156
+ var CompoundRegistryContext = (0, import_react5.createContext)(null);
157
+ var CompoundHandlesVersionContext = (0, import_react5.createContext)(0);
158
+ function useRegisterAssessmentHandle(checkId, handle) {
159
+ const registry = (0, import_react5.useContext)(CompoundRegistryContext);
160
+ const pageIndex = useCompoundPageIndex();
161
+ import_react5.default.useLayoutEffect(() => {
162
+ if (!registry || !handle) return;
163
+ return registry.register(checkId, handle, pageIndex);
164
+ }, [registry, checkId, handle, pageIndex]);
165
+ }
166
+
167
+ // src/assessment/internal/useAssessmentHandleRegistration.ts
168
+ function useAssessmentHandleRegistration(checkId, handle, ref) {
169
+ (0, import_react6.useImperativeHandle)(ref, () => handle, [handle]);
170
+ useRegisterAssessmentHandle(checkId, handle);
171
+ }
172
+
173
+ // src/assessment/internal/usePluginScoring.ts
174
+ var import_react11 = require("react");
175
+
176
+ // src/hooks.ts
177
+ var import_react10 = require("react");
178
+
179
+ // src/context.tsx
180
+ var import_react8 = require("react");
181
+
182
+ // src/provider/useLessonkitProviderRuntime.ts
183
+ var import_react7 = require("react");
184
+ var import_core10 = require("@lessonkit/core");
185
+
186
+ // src/runtime/observability.ts
187
+ var import_xapi = require("@lessonkit/xapi");
188
+
189
+ // src/provider/useLessonkitProviderRuntime.ts
190
+ var import_xapi5 = require("@lessonkit/xapi");
191
+
192
+ // src/runtime/emitTelemetry.ts
193
+ var import_core4 = require("@lessonkit/core");
194
+
195
+ // src/runtime/telemetryPipeline.ts
196
+ var import_core3 = require("@lessonkit/core");
197
+ var import_xapi2 = require("@lessonkit/xapi");
198
+
199
+ // src/runtime/lxpackBridge.ts
200
+ var import_bridge = require("@lessonkit/lxpack/bridge");
201
+
202
+ // src/runtime/ports.ts
203
+ var import_core5 = require("@lessonkit/core");
204
+
205
+ // src/provider/useLessonkitProviderRuntime.ts
206
+ var import_core11 = require("@lessonkit/core");
207
+
208
+ // src/runtime/progress.ts
209
+ var import_core6 = require("@lessonkit/core");
210
+
211
+ // src/runtime/xapi.ts
212
+ var import_xapi3 = require("@lessonkit/xapi");
213
+
214
+ // src/runtime/session.ts
215
+ var import_core7 = require("@lessonkit/core");
216
+
217
+ // src/runtime/courseStartedPipeline.ts
218
+ var import_xapi4 = require("@lessonkit/xapi");
219
+
220
+ // src/runtime/plugins.ts
221
+ var import_core8 = require("@lessonkit/core");
222
+ function buildPluginContext(opts) {
223
+ return (0, import_core8.buildPluginContext)(opts);
224
+ }
225
+
226
+ // src/provider/courseStarted/emit.ts
227
+ var courseStartedTrackingFlights = /* @__PURE__ */ new Map();
228
+ var courseStartedEmitFlights = /* @__PURE__ */ new Map();
229
+ function resetCourseStartedTrackingFlightForTests() {
230
+ courseStartedTrackingFlights.clear();
231
+ courseStartedEmitFlights.clear();
232
+ }
233
+
234
+ // src/runtime/telemetry.ts
235
+ var import_core9 = require("@lessonkit/core");
236
+
237
+ // src/provider/useLessonkitProviderRuntime.ts
238
+ var defaultStorage = (0, import_core5.createSessionStoragePort)();
239
+ function resetLessonkitProviderStorageForTests() {
240
+ (0, import_core5.resetStoragePortForTests)(defaultStorage);
241
+ (0, import_core11.resetSharedVolatileSessionIdForTests)();
242
+ }
243
+
244
+ // src/context.tsx
245
+ var import_jsx_runtime5 = require("react/jsx-runtime");
246
+ var LessonkitContext = (0, import_react8.createContext)(null);
247
+
248
+ // src/assessment/useAssessmentState.ts
249
+ var import_react9 = require("react");
250
+
251
+ // src/hooks.ts
252
+ function useLessonkit() {
253
+ const ctx = (0, import_react10.useContext)(LessonkitContext);
254
+ if (!ctx) throw new Error("LessonKit: missing LessonkitProvider");
255
+ return ctx;
256
+ }
257
+ function useQuizState(enclosingLessonId) {
258
+ const { track } = useLessonkit();
259
+ const trackOpts = enclosingLessonId ? { lessonId: enclosingLessonId } : void 0;
260
+ return (0, import_react10.useMemo)(
261
+ () => ({
262
+ answer: (opts) => {
263
+ track("quiz_answered", opts, trackOpts);
264
+ },
265
+ complete: (opts) => {
266
+ track("quiz_completed", opts, trackOpts);
267
+ }
268
+ }),
269
+ [track, enclosingLessonId]
270
+ );
271
+ }
272
+
273
+ // src/assessment/scoring.ts
274
+ function resolvePassingThreshold(passingScore, maxScore) {
275
+ return passingScore ?? maxScore;
276
+ }
277
+ function meetsPassingThreshold(score, maxScore, passingScore) {
278
+ const threshold = resolvePassingThreshold(passingScore, maxScore);
279
+ return score >= threshold;
280
+ }
281
+ function scoreFromCustom(custom, fallbackCorrect, fallbackMax = 1, passingScore) {
282
+ const maxScore = custom?.maxScore ?? fallbackMax;
283
+ if (custom?.passed !== void 0) {
284
+ const score2 = custom.passed ? custom.score ?? maxScore : custom.score ?? 0;
285
+ return { score: score2, maxScore, passed: custom.passed };
286
+ }
287
+ if (custom?.maxScore != null && custom.maxScore > 0 && custom.score != null) {
288
+ const passed2 = meetsPassingThreshold(custom.score, custom.maxScore, passingScore);
289
+ return { score: custom.score, maxScore: custom.maxScore, passed: passed2 };
290
+ }
291
+ const score = fallbackCorrect ? maxScore : 0;
292
+ const passed = meetsPassingThreshold(score, maxScore, passingScore);
293
+ return { score, maxScore, passed };
294
+ }
295
+
296
+ // src/assessment/internal/usePluginScoring.ts
297
+ function usePluginScoring(checkId, lessonId) {
298
+ const { plugins, config, session } = useLessonkit();
299
+ const getPluginScore = (0, import_react11.useCallback)(
300
+ (response) => {
301
+ const pluginCtx = buildPluginContext({
302
+ courseId: config.courseId,
303
+ sessionId: session.sessionId,
304
+ attemptId: session.attemptId,
305
+ user: session.user
306
+ });
307
+ return plugins?.scoreAssessment({ checkId, lessonId, response }, pluginCtx) ?? null;
308
+ },
309
+ [checkId, config.courseId, lessonId, plugins, session.attemptId, session.sessionId, session.user]
310
+ );
311
+ const scoreResponse = (0, import_react11.useCallback)(
312
+ (response, defaultCorrect, maxScore = 1, passingScore) => scoreFromCustom(getPluginScore(response), defaultCorrect, maxScore, passingScore),
313
+ [getPluginScore]
314
+ );
315
+ const isChoiceCorrect = (0, import_react11.useCallback)(
316
+ (choice, answer, custom, passingScore) => {
317
+ if (!custom) return choice === answer;
318
+ if (custom.passed !== void 0) return custom.passed;
319
+ if (custom.maxScore != null && custom.maxScore > 0 && custom.score != null) {
320
+ return meetsPassingThreshold(custom.score, custom.maxScore, passingScore);
321
+ }
322
+ return choice === answer;
323
+ },
324
+ []
325
+ );
326
+ return { getPluginScore, scoreResponse, isChoiceCorrect };
327
+ }
328
+
329
+ // src/components/Quiz.tsx
330
+ var import_jsx_runtime6 = require("react/jsx-runtime");
331
+ function QuizInner(props, ref) {
332
+ const { enclosingLessonId } = props;
333
+ const checkId = (0, import_react12.useMemo)(() => normalizeComponentId(props.checkId, "checkId"), [props.checkId]);
334
+ const quiz = useQuizState(enclosingLessonId);
335
+ const { getPluginScore, isChoiceCorrect } = usePluginScoring(checkId, enclosingLessonId);
336
+ const [selected, setSelected] = (0, import_react12.useState)(null);
337
+ const [selectionCorrect, setSelectionCorrect] = (0, import_react12.useState)(null);
338
+ const [quizPassed, setQuizPassed] = (0, import_react12.useState)(false);
339
+ const [completedScore, setCompletedScore] = (0, import_react12.useState)(null);
340
+ const [completedMaxScore, setCompletedMaxScore] = (0, import_react12.useState)(null);
341
+ const completedRef = (0, import_react12.useRef)(false);
342
+ const telemetryReplayedRef = (0, import_react12.useRef)(false);
343
+ const questionId = (0, import_react12.useId)();
344
+ const choicesKey = props.choices.join("\0");
345
+ (0, import_react12.useEffect)(() => {
346
+ completedRef.current = false;
347
+ telemetryReplayedRef.current = false;
348
+ setQuizPassed(false);
349
+ setSelected(null);
350
+ setSelectionCorrect(null);
351
+ setCompletedScore(null);
352
+ setCompletedMaxScore(null);
353
+ }, [checkId, props.answer, props.question, choicesKey]);
354
+ const passed = quizPassed;
355
+ const resolveScores = () => {
356
+ const maxScore = completedMaxScore ?? 1;
357
+ if (quizPassed) {
358
+ return { score: completedScore ?? maxScore, maxScore };
359
+ }
360
+ if (selected !== null && selectionCorrect) {
361
+ return { score: completedMaxScore ?? maxScore, maxScore };
362
+ }
363
+ return { score: 0, maxScore };
364
+ };
365
+ const replayTelemetry = (nextSelected, nextCorrect, nextPassed, nextScore, nextMaxScore) => {
366
+ if (!nextPassed || telemetryReplayedRef.current) return;
367
+ telemetryReplayedRef.current = true;
368
+ if (nextSelected !== null) {
369
+ quiz.answer({
370
+ checkId,
371
+ question: props.question,
372
+ choice: nextSelected,
373
+ correct: nextCorrect ?? false
374
+ });
375
+ }
376
+ quiz.complete({
377
+ checkId,
378
+ score: nextScore,
379
+ maxScore: nextMaxScore,
380
+ passingScore: props.passingScore ?? nextMaxScore
381
+ });
382
+ };
383
+ const handle = (0, import_react12.useMemo)(
384
+ () => buildAssessmentHandle({
385
+ checkId,
386
+ getScore: () => resolveScores().score,
387
+ getMaxScore: () => resolveScores().maxScore,
388
+ getAnswerGiven: () => selected !== null,
389
+ resetTask: () => {
390
+ completedRef.current = false;
391
+ telemetryReplayedRef.current = false;
392
+ setQuizPassed(false);
393
+ setSelected(null);
394
+ setSelectionCorrect(null);
395
+ setCompletedScore(null);
396
+ setCompletedMaxScore(null);
397
+ },
398
+ showSolutions: () => {
399
+ },
400
+ getXAPIData: () => {
401
+ const { score, maxScore } = resolveScores();
402
+ return {
403
+ checkId,
404
+ interactionType: "mcq",
405
+ response: selected ?? void 0,
406
+ correct: selectionCorrect ?? void 0,
407
+ score,
408
+ maxScore
409
+ };
410
+ },
411
+ getCurrentState: () => ({
412
+ selected,
413
+ selectionCorrect,
414
+ quizPassed,
415
+ completedScore,
416
+ completedMaxScore
417
+ }),
418
+ resume: (state) => {
419
+ const nextSelected = readStringField(state, "selected");
420
+ if (typeof nextSelected === "string" || nextSelected === null) setSelected(nextSelected);
421
+ const nextCorrect = readBooleanField(state, "selectionCorrect");
422
+ if (nextCorrect === true || nextCorrect === false || nextCorrect === null) {
423
+ setSelectionCorrect(nextCorrect);
424
+ }
425
+ const nextCompletedScore = readNumberField(state, "completedScore");
426
+ if (typeof nextCompletedScore === "number") setCompletedScore(nextCompletedScore);
427
+ const nextCompletedMaxScore = readNumberField(state, "completedMaxScore");
428
+ if (typeof nextCompletedMaxScore === "number") setCompletedMaxScore(nextCompletedMaxScore);
429
+ const nextPassed = readBooleanField(state, "quizPassed");
430
+ if (nextPassed === true || nextPassed === false) {
431
+ setQuizPassed(nextPassed);
432
+ completedRef.current = nextPassed;
433
+ if (nextPassed) {
434
+ const maxScore = nextCompletedMaxScore ?? completedMaxScore ?? 1;
435
+ const score = nextCompletedScore ?? completedScore ?? maxScore;
436
+ replayTelemetry(
437
+ nextSelected ?? null,
438
+ nextCorrect ?? null,
439
+ nextPassed,
440
+ score,
441
+ maxScore
442
+ );
443
+ }
444
+ }
445
+ }
446
+ }),
447
+ [
448
+ checkId,
449
+ completedMaxScore,
450
+ completedScore,
451
+ props.passingScore,
452
+ props.question,
453
+ quiz,
454
+ quizPassed,
455
+ selected,
456
+ selectionCorrect
457
+ ]
458
+ );
459
+ useAssessmentHandleRegistration(checkId, handle, ref);
460
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("section", { "aria-label": "Quiz", "data-lk-check-id": checkId, children: [
461
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { id: questionId, children: props.question }),
462
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("fieldset", { "aria-labelledby": questionId, children: [
463
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("legend", { style: import_accessibility.visuallyHiddenStyle, children: "Quiz choices" }),
464
+ props.choices.map((c, i) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("label", { style: { display: "block" }, children: [
465
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
466
+ "input",
467
+ {
468
+ type: "radio",
469
+ name: questionId,
470
+ value: c,
471
+ checked: selected === c,
472
+ disabled: passed,
473
+ "aria-invalid": selected === c && selectionCorrect === false ? true : void 0,
474
+ onChange: () => {
475
+ if (passed) return;
476
+ setSelected(c);
477
+ const custom = getPluginScore(c);
478
+ const correct = isChoiceCorrect(c, props.answer, custom, props.passingScore);
479
+ setSelectionCorrect(correct);
480
+ quiz.answer({
481
+ checkId,
482
+ question: props.question,
483
+ choice: c,
484
+ correct
485
+ });
486
+ if (correct && !completedRef.current) {
487
+ completedRef.current = true;
488
+ setQuizPassed(true);
489
+ const maxScore = custom?.maxScore ?? 1;
490
+ const score = custom?.score ?? maxScore;
491
+ setCompletedScore(score);
492
+ setCompletedMaxScore(maxScore);
493
+ quiz.complete({
494
+ checkId,
495
+ score,
496
+ maxScore,
497
+ passingScore: props.passingScore ?? maxScore
498
+ });
499
+ }
500
+ }
501
+ }
502
+ ),
503
+ c
504
+ ] }, `${questionId}-${i}`))
505
+ ] }),
506
+ selected && selectionCorrect !== null ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { role: "status", "aria-live": "polite", children: selectionCorrect ? "Correct" : "Try again" }) : null
507
+ ] });
508
+ }
509
+ var QuizInnerForwarded = (0, import_react12.forwardRef)(QuizInner);
510
+ var Quiz = (0, import_react12.forwardRef)(function Quiz2(props, ref) {
511
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(AssessmentLessonGuard, { blockLabel: "Quiz", checkId: props.checkId, children: (lessonId) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(QuizInnerForwarded, { ...props, enclosingLessonId: lessonId, ref }) });
512
+ });
513
+ function resetQuizWarningsForTests() {
514
+ resetAssessmentWarningsForTests();
515
+ }
516
+
517
+ // src/runtime/lessonMountRegistry.ts
518
+ var mountCounts = /* @__PURE__ */ new Map();
519
+ var warnedConcurrentLessons = false;
520
+ function resetLessonMountRegistryForTests() {
521
+ mountCounts.clear();
522
+ warnedConcurrentLessons = false;
523
+ }
524
+
525
+ // src/compound/validateChildren.ts
526
+ var import_react13 = __toESM(require("react"), 1);
527
+ var import_core12 = require("@lessonkit/core");
528
+ var warnedPairs = /* @__PURE__ */ new Set();
529
+ function resetCompoundValidationWarningsForTests() {
530
+ warnedPairs.clear();
531
+ }
532
+ // Annotate the CommonJS export names for ESM import in node:
533
+ 0 && (module.exports = {
534
+ resetAssessmentWarningsForTests,
535
+ resetCompoundValidationWarningsForTests,
536
+ resetCourseStartedTrackingFlightForTests,
537
+ resetLessonMountRegistryForTests,
538
+ resetLessonkitProviderStorageForTests,
539
+ resetQuizWarningsForTests
540
+ });
@@ -0,0 +1,16 @@
1
+ export { r as resetAssessmentWarningsForTests, b as resetQuizWarningsForTests } from './AssessmentLessonGuard-D2Plzybb.cjs';
2
+ import '@lessonkit/core';
3
+ import 'react';
4
+
5
+ /** Reset registry state between tests. */
6
+ declare function resetLessonMountRegistryForTests(): void;
7
+
8
+ /** @internal Reset dev warnings between tests. */
9
+ declare function resetCompoundValidationWarningsForTests(): void;
10
+
11
+ /** @internal Reset in-flight course_started tracking guard between tests. */
12
+ declare function resetCourseStartedTrackingFlightForTests(): void;
13
+
14
+ declare function resetLessonkitProviderStorageForTests(): void;
15
+
16
+ export { resetCompoundValidationWarningsForTests, resetCourseStartedTrackingFlightForTests, resetLessonMountRegistryForTests, resetLessonkitProviderStorageForTests };
@@ -0,0 +1,16 @@
1
+ export { r as resetAssessmentWarningsForTests, b as resetQuizWarningsForTests } from './AssessmentLessonGuard-D2Plzybb.js';
2
+ import '@lessonkit/core';
3
+ import 'react';
4
+
5
+ /** Reset registry state between tests. */
6
+ declare function resetLessonMountRegistryForTests(): void;
7
+
8
+ /** @internal Reset dev warnings between tests. */
9
+ declare function resetCompoundValidationWarningsForTests(): void;
10
+
11
+ /** @internal Reset in-flight course_started tracking guard between tests. */
12
+ declare function resetCourseStartedTrackingFlightForTests(): void;
13
+
14
+ declare function resetLessonkitProviderStorageForTests(): void;
15
+
16
+ export { resetCompoundValidationWarningsForTests, resetCourseStartedTrackingFlightForTests, resetLessonMountRegistryForTests, resetLessonkitProviderStorageForTests };
@@ -0,0 +1,18 @@
1
+ import {
2
+ resetLessonMountRegistryForTests,
3
+ resetQuizWarningsForTests
4
+ } from "./chunk-UUTXECVW.js";
5
+ import {
6
+ resetAssessmentWarningsForTests,
7
+ resetCompoundValidationWarningsForTests,
8
+ resetCourseStartedTrackingFlightForTests,
9
+ resetLessonkitProviderStorageForTests
10
+ } from "./chunk-TDM3ARE7.js";
11
+ export {
12
+ resetAssessmentWarningsForTests,
13
+ resetCompoundValidationWarningsForTests,
14
+ resetCourseStartedTrackingFlightForTests,
15
+ resetLessonMountRegistryForTests,
16
+ resetLessonkitProviderStorageForTests,
17
+ resetQuizWarningsForTests
18
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lessonkit/react",
3
- "version": "1.3.1",
3
+ "version": "1.4.0",
4
4
  "private": false,
5
5
  "description": "React components and hooks for building learning experiences with LessonKit.",
6
6
  "license": "Apache-2.0",
@@ -32,6 +32,16 @@
32
32
  "import": "./dist/index.js",
33
33
  "require": "./dist/index.cjs"
34
34
  },
35
+ "./blocks": {
36
+ "types": "./dist/blocks-entry.d.ts",
37
+ "import": "./dist/blocks-entry.js",
38
+ "require": "./dist/blocks-entry.cjs"
39
+ },
40
+ "./testing": {
41
+ "types": "./dist/testing.d.ts",
42
+ "import": "./dist/testing.js",
43
+ "require": "./dist/testing.cjs"
44
+ },
35
45
  "./block-catalog.v1.json": "./block-catalog.v1.json",
36
46
  "./block-catalog.v2.json": "./block-catalog.v2.json",
37
47
  "./block-catalog.v3.json": "./block-catalog.v3.json",
@@ -49,8 +59,8 @@
49
59
  "block-contract.v3.json"
50
60
  ],
51
61
  "scripts": {
52
- "build": "tsup src/index.tsx --format esm,cjs --dts --external react --external react-dom --external @lessonkit/accessibility --external @lessonkit/lxpack --external @lessonkit/themes",
53
- "dev": "tsup src/index.tsx --format esm,cjs --dts --watch --external react --external react-dom --external @lessonkit/accessibility --external @lessonkit/lxpack --external @lessonkit/themes",
62
+ "build": "tsup src/index.tsx src/testing.ts src/blocks-entry.ts --format esm,cjs --dts --external react --external react-dom --external @lessonkit/accessibility --external @lessonkit/lxpack --external @lessonkit/themes",
63
+ "dev": "tsup src/index.tsx src/testing.ts src/blocks-entry.ts --format esm,cjs --dts --watch --external react --external react-dom --external @lessonkit/accessibility --external @lessonkit/lxpack --external @lessonkit/themes",
54
64
  "storybook": "NODE_PATH=./node_modules:../../node_modules node ../../node_modules/storybook/bin/index.cjs dev -c .storybook -p 6006 --disable-telemetry",
55
65
  "build-storybook": "NODE_PATH=./node_modules:../../node_modules node ../../node_modules/storybook/bin/index.cjs build -c .storybook --disable-telemetry",
56
66
  "prepublishOnly": "npm run build",
@@ -60,15 +70,21 @@
60
70
  "lint": "echo \"(no lint configured yet)\""
61
71
  },
62
72
  "peerDependencies": {
73
+ "@lessonkit/lxpack": "1.4.0",
63
74
  "react": ">=18",
64
75
  "react-dom": ">=18"
65
76
  },
77
+ "peerDependenciesMeta": {
78
+ "@lessonkit/lxpack": {
79
+ "optional": true
80
+ }
81
+ },
66
82
  "dependencies": {
67
- "@lessonkit/accessibility": "1.3.1",
68
- "@lessonkit/core": "1.3.1",
69
- "@lessonkit/lxpack": "1.3.1",
70
- "@lessonkit/themes": "1.3.1",
71
- "@lessonkit/xapi": "1.3.1"
83
+ "@lessonkit/accessibility": "1.4.0",
84
+ "@lessonkit/core": "1.4.0",
85
+ "@lessonkit/lxpack": "1.4.0",
86
+ "@lessonkit/themes": "1.4.0",
87
+ "@lessonkit/xapi": "1.4.0"
72
88
  },
73
89
  "devDependencies": {
74
90
  "@storybook/addon-essentials": "8.6.18",
@@ -78,16 +94,17 @@
78
94
  "@storybook/react-vite": "8.6.18",
79
95
  "@storybook/test": "8.6.18",
80
96
  "@testing-library/react": "^16.3.0",
81
- "@types/react": "^18.3.23",
82
- "@types/react-dom": "^18.3.7",
83
- "@vitejs/plugin-react": "^4.5.2",
84
- "jsdom": "^26.1.0",
85
- "react": "^18.3.1",
86
- "react-dom": "^18.3.1",
97
+ "@types/react": "^19.2.17",
98
+ "@types/react-dom": "^19.2.3",
99
+ "@vitejs/plugin-react": "^6.0.2",
100
+ "jsdom": "^29.1.1",
101
+ "react": "^19.2.7",
102
+ "react-dom": "^19.2.7",
87
103
  "storybook": "8.6.18",
88
104
  "tsup": "^8.5.0",
89
105
  "typescript": "^5.8.3",
90
- "vite": "^6.3.5",
91
- "vitest": "^4.1.8"
106
+ "vite": "^8.0.11",
107
+ "vitest": "^4.1.8",
108
+ "@testing-library/dom": "^10.4.1"
92
109
  }
93
110
  }