@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.
- 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 +2329 -318
- package/dist/index.d.cts +31 -282
- package/dist/index.d.ts +31 -282
- package/dist/index.js +433 -4295
- 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
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AssessmentLessonGuard,
|
|
3
|
+
buildAssessmentHandle,
|
|
4
|
+
isDevEnvironment,
|
|
5
|
+
normalizeComponentId,
|
|
6
|
+
readBooleanField,
|
|
7
|
+
readNumberField,
|
|
8
|
+
readStringField,
|
|
9
|
+
resetAssessmentWarningsForTests,
|
|
10
|
+
useAssessmentHandleRegistration,
|
|
11
|
+
usePluginScoring,
|
|
12
|
+
useQuizState
|
|
13
|
+
} from "./chunk-TDM3ARE7.js";
|
|
14
|
+
|
|
15
|
+
// src/runtime/lessonMountRegistry.ts
|
|
16
|
+
var mountCounts = /* @__PURE__ */ new Map();
|
|
17
|
+
var warnedConcurrentLessons = false;
|
|
18
|
+
function registerLessonMount(lessonId) {
|
|
19
|
+
if (isDevEnvironment() && mountCounts.size > 0 && !mountCounts.has(lessonId) && !warnedConcurrentLessons) {
|
|
20
|
+
warnedConcurrentLessons = true;
|
|
21
|
+
console.warn(
|
|
22
|
+
"[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."
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
mountCounts.set(lessonId, (mountCounts.get(lessonId) ?? 0) + 1);
|
|
26
|
+
return () => {
|
|
27
|
+
const next = (mountCounts.get(lessonId) ?? 1) - 1;
|
|
28
|
+
if (next <= 0) {
|
|
29
|
+
mountCounts.delete(lessonId);
|
|
30
|
+
} else {
|
|
31
|
+
mountCounts.set(lessonId, next);
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function getLessonMountCount(lessonId) {
|
|
36
|
+
return mountCounts.get(lessonId) ?? 0;
|
|
37
|
+
}
|
|
38
|
+
function resetLessonMountRegistryForTests() {
|
|
39
|
+
mountCounts.clear();
|
|
40
|
+
warnedConcurrentLessons = false;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// src/components/Quiz.tsx
|
|
44
|
+
import { forwardRef, useEffect, useId, useMemo, useRef, useState } from "react";
|
|
45
|
+
import { visuallyHiddenStyle } from "@lessonkit/accessibility";
|
|
46
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
47
|
+
function QuizInner(props, ref) {
|
|
48
|
+
const { enclosingLessonId } = props;
|
|
49
|
+
const checkId = useMemo(() => normalizeComponentId(props.checkId, "checkId"), [props.checkId]);
|
|
50
|
+
const quiz = useQuizState(enclosingLessonId);
|
|
51
|
+
const { getPluginScore, isChoiceCorrect } = usePluginScoring(checkId, enclosingLessonId);
|
|
52
|
+
const [selected, setSelected] = useState(null);
|
|
53
|
+
const [selectionCorrect, setSelectionCorrect] = useState(null);
|
|
54
|
+
const [quizPassed, setQuizPassed] = useState(false);
|
|
55
|
+
const [completedScore, setCompletedScore] = useState(null);
|
|
56
|
+
const [completedMaxScore, setCompletedMaxScore] = useState(null);
|
|
57
|
+
const completedRef = useRef(false);
|
|
58
|
+
const telemetryReplayedRef = useRef(false);
|
|
59
|
+
const questionId = useId();
|
|
60
|
+
const choicesKey = props.choices.join("\0");
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
completedRef.current = false;
|
|
63
|
+
telemetryReplayedRef.current = false;
|
|
64
|
+
setQuizPassed(false);
|
|
65
|
+
setSelected(null);
|
|
66
|
+
setSelectionCorrect(null);
|
|
67
|
+
setCompletedScore(null);
|
|
68
|
+
setCompletedMaxScore(null);
|
|
69
|
+
}, [checkId, props.answer, props.question, choicesKey]);
|
|
70
|
+
const passed = quizPassed;
|
|
71
|
+
const resolveScores = () => {
|
|
72
|
+
const maxScore = completedMaxScore ?? 1;
|
|
73
|
+
if (quizPassed) {
|
|
74
|
+
return { score: completedScore ?? maxScore, maxScore };
|
|
75
|
+
}
|
|
76
|
+
if (selected !== null && selectionCorrect) {
|
|
77
|
+
return { score: completedMaxScore ?? maxScore, maxScore };
|
|
78
|
+
}
|
|
79
|
+
return { score: 0, maxScore };
|
|
80
|
+
};
|
|
81
|
+
const replayTelemetry = (nextSelected, nextCorrect, nextPassed, nextScore, nextMaxScore) => {
|
|
82
|
+
if (!nextPassed || telemetryReplayedRef.current) return;
|
|
83
|
+
telemetryReplayedRef.current = true;
|
|
84
|
+
if (nextSelected !== null) {
|
|
85
|
+
quiz.answer({
|
|
86
|
+
checkId,
|
|
87
|
+
question: props.question,
|
|
88
|
+
choice: nextSelected,
|
|
89
|
+
correct: nextCorrect ?? false
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
quiz.complete({
|
|
93
|
+
checkId,
|
|
94
|
+
score: nextScore,
|
|
95
|
+
maxScore: nextMaxScore,
|
|
96
|
+
passingScore: props.passingScore ?? nextMaxScore
|
|
97
|
+
});
|
|
98
|
+
};
|
|
99
|
+
const handle = useMemo(
|
|
100
|
+
() => buildAssessmentHandle({
|
|
101
|
+
checkId,
|
|
102
|
+
getScore: () => resolveScores().score,
|
|
103
|
+
getMaxScore: () => resolveScores().maxScore,
|
|
104
|
+
getAnswerGiven: () => selected !== null,
|
|
105
|
+
resetTask: () => {
|
|
106
|
+
completedRef.current = false;
|
|
107
|
+
telemetryReplayedRef.current = false;
|
|
108
|
+
setQuizPassed(false);
|
|
109
|
+
setSelected(null);
|
|
110
|
+
setSelectionCorrect(null);
|
|
111
|
+
setCompletedScore(null);
|
|
112
|
+
setCompletedMaxScore(null);
|
|
113
|
+
},
|
|
114
|
+
showSolutions: () => {
|
|
115
|
+
},
|
|
116
|
+
getXAPIData: () => {
|
|
117
|
+
const { score, maxScore } = resolveScores();
|
|
118
|
+
return {
|
|
119
|
+
checkId,
|
|
120
|
+
interactionType: "mcq",
|
|
121
|
+
response: selected ?? void 0,
|
|
122
|
+
correct: selectionCorrect ?? void 0,
|
|
123
|
+
score,
|
|
124
|
+
maxScore
|
|
125
|
+
};
|
|
126
|
+
},
|
|
127
|
+
getCurrentState: () => ({
|
|
128
|
+
selected,
|
|
129
|
+
selectionCorrect,
|
|
130
|
+
quizPassed,
|
|
131
|
+
completedScore,
|
|
132
|
+
completedMaxScore
|
|
133
|
+
}),
|
|
134
|
+
resume: (state) => {
|
|
135
|
+
const nextSelected = readStringField(state, "selected");
|
|
136
|
+
if (typeof nextSelected === "string" || nextSelected === null) setSelected(nextSelected);
|
|
137
|
+
const nextCorrect = readBooleanField(state, "selectionCorrect");
|
|
138
|
+
if (nextCorrect === true || nextCorrect === false || nextCorrect === null) {
|
|
139
|
+
setSelectionCorrect(nextCorrect);
|
|
140
|
+
}
|
|
141
|
+
const nextCompletedScore = readNumberField(state, "completedScore");
|
|
142
|
+
if (typeof nextCompletedScore === "number") setCompletedScore(nextCompletedScore);
|
|
143
|
+
const nextCompletedMaxScore = readNumberField(state, "completedMaxScore");
|
|
144
|
+
if (typeof nextCompletedMaxScore === "number") setCompletedMaxScore(nextCompletedMaxScore);
|
|
145
|
+
const nextPassed = readBooleanField(state, "quizPassed");
|
|
146
|
+
if (nextPassed === true || nextPassed === false) {
|
|
147
|
+
setQuizPassed(nextPassed);
|
|
148
|
+
completedRef.current = nextPassed;
|
|
149
|
+
if (nextPassed) {
|
|
150
|
+
const maxScore = nextCompletedMaxScore ?? completedMaxScore ?? 1;
|
|
151
|
+
const score = nextCompletedScore ?? completedScore ?? maxScore;
|
|
152
|
+
replayTelemetry(
|
|
153
|
+
nextSelected ?? null,
|
|
154
|
+
nextCorrect ?? null,
|
|
155
|
+
nextPassed,
|
|
156
|
+
score,
|
|
157
|
+
maxScore
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}),
|
|
163
|
+
[
|
|
164
|
+
checkId,
|
|
165
|
+
completedMaxScore,
|
|
166
|
+
completedScore,
|
|
167
|
+
props.passingScore,
|
|
168
|
+
props.question,
|
|
169
|
+
quiz,
|
|
170
|
+
quizPassed,
|
|
171
|
+
selected,
|
|
172
|
+
selectionCorrect
|
|
173
|
+
]
|
|
174
|
+
);
|
|
175
|
+
useAssessmentHandleRegistration(checkId, handle, ref);
|
|
176
|
+
return /* @__PURE__ */ jsxs("section", { "aria-label": "Quiz", "data-lk-check-id": checkId, children: [
|
|
177
|
+
/* @__PURE__ */ jsx("p", { id: questionId, children: props.question }),
|
|
178
|
+
/* @__PURE__ */ jsxs("fieldset", { "aria-labelledby": questionId, children: [
|
|
179
|
+
/* @__PURE__ */ jsx("legend", { style: visuallyHiddenStyle, children: "Quiz choices" }),
|
|
180
|
+
props.choices.map((c, i) => /* @__PURE__ */ jsxs("label", { style: { display: "block" }, children: [
|
|
181
|
+
/* @__PURE__ */ jsx(
|
|
182
|
+
"input",
|
|
183
|
+
{
|
|
184
|
+
type: "radio",
|
|
185
|
+
name: questionId,
|
|
186
|
+
value: c,
|
|
187
|
+
checked: selected === c,
|
|
188
|
+
disabled: passed,
|
|
189
|
+
"aria-invalid": selected === c && selectionCorrect === false ? true : void 0,
|
|
190
|
+
onChange: () => {
|
|
191
|
+
if (passed) return;
|
|
192
|
+
setSelected(c);
|
|
193
|
+
const custom = getPluginScore(c);
|
|
194
|
+
const correct = isChoiceCorrect(c, props.answer, custom, props.passingScore);
|
|
195
|
+
setSelectionCorrect(correct);
|
|
196
|
+
quiz.answer({
|
|
197
|
+
checkId,
|
|
198
|
+
question: props.question,
|
|
199
|
+
choice: c,
|
|
200
|
+
correct
|
|
201
|
+
});
|
|
202
|
+
if (correct && !completedRef.current) {
|
|
203
|
+
completedRef.current = true;
|
|
204
|
+
setQuizPassed(true);
|
|
205
|
+
const maxScore = custom?.maxScore ?? 1;
|
|
206
|
+
const score = custom?.score ?? maxScore;
|
|
207
|
+
setCompletedScore(score);
|
|
208
|
+
setCompletedMaxScore(maxScore);
|
|
209
|
+
quiz.complete({
|
|
210
|
+
checkId,
|
|
211
|
+
score,
|
|
212
|
+
maxScore,
|
|
213
|
+
passingScore: props.passingScore ?? maxScore
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
),
|
|
219
|
+
c
|
|
220
|
+
] }, `${questionId}-${i}`))
|
|
221
|
+
] }),
|
|
222
|
+
selected && selectionCorrect !== null ? /* @__PURE__ */ jsx("p", { role: "status", "aria-live": "polite", children: selectionCorrect ? "Correct" : "Try again" }) : null
|
|
223
|
+
] });
|
|
224
|
+
}
|
|
225
|
+
var QuizInnerForwarded = forwardRef(QuizInner);
|
|
226
|
+
var Quiz = forwardRef(function Quiz2(props, ref) {
|
|
227
|
+
return /* @__PURE__ */ jsx(AssessmentLessonGuard, { blockLabel: "Quiz", checkId: props.checkId, children: (lessonId) => /* @__PURE__ */ jsx(QuizInnerForwarded, { ...props, enclosingLessonId: lessonId, ref }) });
|
|
228
|
+
});
|
|
229
|
+
function KnowledgeCheck(props) {
|
|
230
|
+
return /* @__PURE__ */ jsx(
|
|
231
|
+
Quiz,
|
|
232
|
+
{
|
|
233
|
+
checkId: props.checkId,
|
|
234
|
+
question: props.question,
|
|
235
|
+
choices: props.choices,
|
|
236
|
+
answer: props.answer,
|
|
237
|
+
passingScore: props.passingScore
|
|
238
|
+
}
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
function resetQuizWarningsForTests() {
|
|
242
|
+
resetAssessmentWarningsForTests();
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export {
|
|
246
|
+
registerLessonMount,
|
|
247
|
+
getLessonMountCount,
|
|
248
|
+
resetLessonMountRegistryForTests,
|
|
249
|
+
Quiz,
|
|
250
|
+
KnowledgeCheck,
|
|
251
|
+
resetQuizWarningsForTests
|
|
252
|
+
};
|