@lessonkit/react 1.3.0 → 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.
- package/block-catalog.v3.json +1005 -107
- package/dist/AssessmentLessonGuard-D2Plzybb.d.cts +21 -0
- package/dist/AssessmentLessonGuard-D2Plzybb.d.ts +21 -0
- package/dist/blocks-entry.cjs +4563 -0
- package/dist/blocks-entry.d.cts +411 -0
- package/dist/blocks-entry.d.ts +411 -0
- package/dist/blocks-entry.js +69 -0
- package/dist/chunk-4LQ4TTEE.js +4018 -0
- package/dist/chunk-TDM3ARE7.js +1775 -0
- package/dist/chunk-UUTXECVW.js +252 -0
- package/dist/index.cjs +2555 -313
- package/dist/index.d.cts +36 -282
- package/dist/index.d.ts +36 -282
- package/dist/index.js +433 -4065
- package/dist/testing.cjs +540 -0
- package/dist/testing.d.cts +16 -0
- package/dist/testing.d.ts +16 -0
- package/dist/testing.js +18 -0
- package/package.json +33 -16
package/dist/testing.cjs
ADDED
|
@@ -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 };
|
package/dist/testing.js
ADDED
|
@@ -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
|
+
"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.
|
|
68
|
-
"@lessonkit/core": "1.
|
|
69
|
-
"@lessonkit/lxpack": "1.
|
|
70
|
-
"@lessonkit/themes": "1.
|
|
71
|
-
"@lessonkit/xapi": "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": "^
|
|
82
|
-
"@types/react-dom": "^
|
|
83
|
-
"@vitejs/plugin-react": "^
|
|
84
|
-
"jsdom": "^
|
|
85
|
-
"react": "^
|
|
86
|
-
"react-dom": "^
|
|
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": "^
|
|
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
|
}
|