@lessonkit/react 1.0.2 → 1.1.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.v2.json +770 -0
- package/block-contract.v2.json +104 -0
- package/dist/index.cjs +1153 -76
- package/dist/index.d.cts +116 -9
- package/dist/index.d.ts +116 -9
- package/dist/index.js +1133 -66
- package/package.json +12 -8
package/dist/index.cjs
CHANGED
|
@@ -30,16 +30,24 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/index.tsx
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
|
+
AssessmentSequence: () => AssessmentSequence,
|
|
33
34
|
BLOCK_CATALOG: () => BLOCK_CATALOG,
|
|
35
|
+
BLOCK_CATALOG_V2: () => BLOCK_CATALOG_V2,
|
|
34
36
|
Course: () => Course,
|
|
37
|
+
DragAndDrop: () => DragAndDrop,
|
|
38
|
+
DragTheWords: () => DragTheWords,
|
|
39
|
+
FillInTheBlanks: () => FillInTheBlanks,
|
|
35
40
|
KnowledgeCheck: () => KnowledgeCheck,
|
|
36
41
|
Lesson: () => Lesson,
|
|
37
42
|
LessonkitProvider: () => LessonkitProvider,
|
|
43
|
+
MarkTheWords: () => MarkTheWords,
|
|
38
44
|
ProgressTracker: () => ProgressTracker,
|
|
39
45
|
Quiz: () => Quiz,
|
|
40
46
|
Reflection: () => Reflection,
|
|
41
47
|
Scenario: () => Scenario,
|
|
42
48
|
ThemeProvider: () => ThemeProvider,
|
|
49
|
+
TrueFalse: () => TrueFalse,
|
|
50
|
+
blockCatalogV2Version: () => blockCatalogV2Version,
|
|
43
51
|
blockCatalogVersion: () => blockCatalogVersion,
|
|
44
52
|
buildBlockCatalog: () => buildBlockCatalog,
|
|
45
53
|
buildTelemetryEvent: () => import_core10.buildTelemetryEvent,
|
|
@@ -50,7 +58,9 @@ __export(index_exports, {
|
|
|
50
58
|
defineLifecyclePlugin: () => import_core10.defineLifecyclePlugin,
|
|
51
59
|
defineTelemetryPlugin: () => import_core10.defineTelemetryPlugin,
|
|
52
60
|
getBlockCatalogEntry: () => getBlockCatalogEntry,
|
|
61
|
+
resetAssessmentWarningsForTests: () => resetAssessmentWarningsForTests,
|
|
53
62
|
resetQuizWarningsForTests: () => resetQuizWarningsForTests,
|
|
63
|
+
useAssessmentState: () => useAssessmentState,
|
|
54
64
|
useCompletion: () => useCompletion,
|
|
55
65
|
useLessonkit: () => useLessonkit,
|
|
56
66
|
useProgress: () => useProgress,
|
|
@@ -61,9 +71,32 @@ __export(index_exports, {
|
|
|
61
71
|
module.exports = __toCommonJS(index_exports);
|
|
62
72
|
|
|
63
73
|
// src/components.tsx
|
|
64
|
-
var
|
|
74
|
+
var import_react6 = require("react");
|
|
65
75
|
var import_accessibility = require("@lessonkit/accessibility");
|
|
66
76
|
|
|
77
|
+
// src/assessment/scoring.ts
|
|
78
|
+
function resolvePassingThreshold(passingScore, maxScore) {
|
|
79
|
+
return passingScore ?? maxScore;
|
|
80
|
+
}
|
|
81
|
+
function meetsPassingThreshold(score, maxScore, passingScore) {
|
|
82
|
+
const threshold = resolvePassingThreshold(passingScore, maxScore);
|
|
83
|
+
return score >= threshold;
|
|
84
|
+
}
|
|
85
|
+
function scoreFromCustom(custom, fallbackCorrect, fallbackMax = 1, passingScore) {
|
|
86
|
+
const maxScore = custom?.maxScore ?? fallbackMax;
|
|
87
|
+
if (custom?.passed !== void 0) {
|
|
88
|
+
const score2 = custom.passed ? custom.score ?? maxScore : custom.score ?? 0;
|
|
89
|
+
return { score: score2, maxScore, passed: custom.passed };
|
|
90
|
+
}
|
|
91
|
+
if (custom?.maxScore != null && custom.maxScore > 0 && custom.score != null) {
|
|
92
|
+
const passed2 = meetsPassingThreshold(custom.score, custom.maxScore, passingScore);
|
|
93
|
+
return { score: custom.score, maxScore: custom.maxScore, passed: passed2 };
|
|
94
|
+
}
|
|
95
|
+
const score = fallbackCorrect ? maxScore : 0;
|
|
96
|
+
const passed = meetsPassingThreshold(score, maxScore, passingScore);
|
|
97
|
+
return { score, maxScore, passed };
|
|
98
|
+
}
|
|
99
|
+
|
|
67
100
|
// src/context.tsx
|
|
68
101
|
var import_react2 = require("react");
|
|
69
102
|
|
|
@@ -269,7 +302,10 @@ async function disposeTrackingClient(client) {
|
|
|
269
302
|
}
|
|
270
303
|
|
|
271
304
|
// src/provider/useLessonkitProviderRuntime.ts
|
|
272
|
-
var useIsoLayoutEffect =
|
|
305
|
+
var useIsoLayoutEffect = (
|
|
306
|
+
/* v8 ignore next -- SSR uses useEffect when window is unavailable */
|
|
307
|
+
typeof window !== "undefined" ? import_react.useLayoutEffect : import_react.useEffect
|
|
308
|
+
);
|
|
273
309
|
var defaultStorage = (0, import_core3.createSessionStoragePort)();
|
|
274
310
|
var courseStartedTrackingFlightKey = null;
|
|
275
311
|
function isTrackingActive(tracking) {
|
|
@@ -342,22 +378,15 @@ async function emitCourseStartedPipelineOnly(opts) {
|
|
|
342
378
|
async function emitCourseStarted(opts) {
|
|
343
379
|
const event = buildCourseStartedEvent(opts);
|
|
344
380
|
if (event === null) return "filtered";
|
|
345
|
-
const
|
|
381
|
+
const tracked = await emitCourseStartedToTracking(
|
|
382
|
+
opts.tracking,
|
|
346
383
|
opts.storage,
|
|
347
384
|
opts.sessionId,
|
|
348
|
-
opts.courseId
|
|
385
|
+
opts.courseId,
|
|
386
|
+
event,
|
|
387
|
+
opts.shouldCommit
|
|
349
388
|
);
|
|
350
|
-
if (!
|
|
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";
|
|
360
|
-
}
|
|
389
|
+
if (!tracked) return "failed";
|
|
361
390
|
return emitCourseStartedPipelineOnly({
|
|
362
391
|
...opts,
|
|
363
392
|
event,
|
|
@@ -369,22 +398,15 @@ async function emitCourseStarted(opts) {
|
|
|
369
398
|
async function emitCourseStartedToTrackingOnly(opts) {
|
|
370
399
|
const event = buildCourseStartedEvent(opts);
|
|
371
400
|
if (event === null) return "filtered";
|
|
372
|
-
const
|
|
401
|
+
const tracked = await emitCourseStartedToTracking(
|
|
402
|
+
opts.tracking,
|
|
373
403
|
opts.storage,
|
|
374
404
|
opts.sessionId,
|
|
375
|
-
opts.courseId
|
|
405
|
+
opts.courseId,
|
|
406
|
+
event,
|
|
407
|
+
opts.shouldCommit
|
|
376
408
|
);
|
|
377
|
-
if (!
|
|
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";
|
|
387
|
-
}
|
|
409
|
+
if (!tracked) return "failed";
|
|
388
410
|
try {
|
|
389
411
|
if (opts.shouldCommit && !opts.shouldCommit()) return "failed";
|
|
390
412
|
await emitCourseStartedNonTrackingPipeline({
|
|
@@ -423,6 +445,9 @@ async function emitPendingCourseStarted(opts) {
|
|
|
423
445
|
opts.sessionId,
|
|
424
446
|
opts.courseId
|
|
425
447
|
);
|
|
448
|
+
if (sessionStarted && trackingEmitted && pipelineDelivered) {
|
|
449
|
+
return "emitted";
|
|
450
|
+
}
|
|
426
451
|
if (sessionStarted && trackingEmitted && !pipelineDelivered) {
|
|
427
452
|
const event = buildCourseStartedEvent(opts);
|
|
428
453
|
if (event === null) return "filtered";
|
|
@@ -626,7 +651,10 @@ function useLessonkitProviderRuntime(config) {
|
|
|
626
651
|
const baseSink = normalizedConfig.tracking?.sink;
|
|
627
652
|
const userBatchSink = normalizedConfig.tracking?.batchSink;
|
|
628
653
|
assertTrackingSinkConfig(normalizedConfig.tracking);
|
|
629
|
-
const sink = pluginHostRef.current && baseSink ?
|
|
654
|
+
const sink = pluginHostRef.current && baseSink ? (
|
|
655
|
+
/* v8 ignore next -- composeTrackingSink may return null; fall back to base sink */
|
|
656
|
+
pluginHostRef.current.composeTrackingSink(baseSink, buildCurrentPluginCtx) ?? baseSink
|
|
657
|
+
) : baseSink;
|
|
630
658
|
const batchSink = pluginHostRef.current && userBatchSink ? async (events) => {
|
|
631
659
|
const host = pluginHostRef.current;
|
|
632
660
|
const ctx = buildCurrentPluginCtx();
|
|
@@ -963,9 +991,29 @@ function LessonkitProvider(props) {
|
|
|
963
991
|
}
|
|
964
992
|
|
|
965
993
|
// src/hooks.ts
|
|
994
|
+
var import_react4 = require("react");
|
|
995
|
+
|
|
996
|
+
// src/assessment/useAssessmentState.ts
|
|
966
997
|
var import_react3 = require("react");
|
|
998
|
+
function useAssessmentState(enclosingLessonId) {
|
|
999
|
+
const { track } = useLessonkit();
|
|
1000
|
+
const trackOpts = enclosingLessonId ? { lessonId: enclosingLessonId } : void 0;
|
|
1001
|
+
return (0, import_react3.useMemo)(
|
|
1002
|
+
() => ({
|
|
1003
|
+
answer: (data) => {
|
|
1004
|
+
track("assessment_answered", data, trackOpts);
|
|
1005
|
+
},
|
|
1006
|
+
complete: (data) => {
|
|
1007
|
+
track("assessment_completed", data, trackOpts);
|
|
1008
|
+
}
|
|
1009
|
+
}),
|
|
1010
|
+
[track, enclosingLessonId]
|
|
1011
|
+
);
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
// src/hooks.ts
|
|
967
1015
|
function useLessonkit() {
|
|
968
|
-
const ctx = (0,
|
|
1016
|
+
const ctx = (0, import_react4.useContext)(LessonkitContext);
|
|
969
1017
|
if (!ctx) throw new Error("LessonKit: missing LessonkitProvider");
|
|
970
1018
|
return ctx;
|
|
971
1019
|
}
|
|
@@ -975,16 +1023,16 @@ function useProgress() {
|
|
|
975
1023
|
}
|
|
976
1024
|
function useTracking() {
|
|
977
1025
|
const { track } = useLessonkit();
|
|
978
|
-
return (0,
|
|
1026
|
+
return (0, import_react4.useMemo)(() => ({ track }), [track]);
|
|
979
1027
|
}
|
|
980
1028
|
function useCompletion() {
|
|
981
1029
|
const { completeLesson, completeCourse } = useLessonkit();
|
|
982
|
-
return (0,
|
|
1030
|
+
return (0, import_react4.useMemo)(() => ({ completeLesson, completeCourse }), [completeLesson, completeCourse]);
|
|
983
1031
|
}
|
|
984
1032
|
function useQuizState(enclosingLessonId) {
|
|
985
1033
|
const { track } = useLessonkit();
|
|
986
1034
|
const trackOpts = enclosingLessonId ? { lessonId: enclosingLessonId } : void 0;
|
|
987
|
-
return (0,
|
|
1035
|
+
return (0, import_react4.useMemo)(
|
|
988
1036
|
() => ({
|
|
989
1037
|
answer: (opts) => {
|
|
990
1038
|
track("quiz_answered", opts, trackOpts);
|
|
@@ -998,10 +1046,10 @@ function useQuizState(enclosingLessonId) {
|
|
|
998
1046
|
}
|
|
999
1047
|
|
|
1000
1048
|
// src/lessonContext.tsx
|
|
1001
|
-
var
|
|
1002
|
-
var LessonContext = (0,
|
|
1049
|
+
var import_react5 = require("react");
|
|
1050
|
+
var LessonContext = (0, import_react5.createContext)(void 0);
|
|
1003
1051
|
function useEnclosingLessonId() {
|
|
1004
|
-
return (0,
|
|
1052
|
+
return (0, import_react5.useContext)(LessonContext);
|
|
1005
1053
|
}
|
|
1006
1054
|
|
|
1007
1055
|
// src/runtime/validateComponentId.ts
|
|
@@ -1049,8 +1097,8 @@ function resetQuizWarningsForTests() {
|
|
|
1049
1097
|
warnedQuizOutsideLesson = false;
|
|
1050
1098
|
}
|
|
1051
1099
|
function Course(props) {
|
|
1052
|
-
const courseId = (0,
|
|
1053
|
-
const providerConfig = (0,
|
|
1100
|
+
const courseId = (0, import_react6.useMemo)(() => normalizeComponentId(props.courseId, "courseId"), [props.courseId]);
|
|
1101
|
+
const providerConfig = (0, import_react6.useMemo)(
|
|
1054
1102
|
() => ({ ...props.config, courseId }),
|
|
1055
1103
|
[props.config, courseId]
|
|
1056
1104
|
);
|
|
@@ -1060,14 +1108,14 @@ function Course(props) {
|
|
|
1060
1108
|
] }) });
|
|
1061
1109
|
}
|
|
1062
1110
|
function Lesson(props) {
|
|
1063
|
-
const lessonId = (0,
|
|
1111
|
+
const lessonId = (0, import_react6.useMemo)(() => normalizeComponentId(props.lessonId, "lessonId"), [props.lessonId]);
|
|
1064
1112
|
const autoComplete = props.autoCompleteOnUnmount !== false;
|
|
1065
1113
|
const { setActiveLesson, config } = useLessonkit();
|
|
1066
1114
|
const { completeLesson } = useCompletion();
|
|
1067
|
-
const lessonMountGenerationRef = (0,
|
|
1068
|
-
const liveCourseIdRef = (0,
|
|
1115
|
+
const lessonMountGenerationRef = (0, import_react6.useRef)(0);
|
|
1116
|
+
const liveCourseIdRef = (0, import_react6.useRef)(config.courseId);
|
|
1069
1117
|
liveCourseIdRef.current = config.courseId;
|
|
1070
|
-
(0,
|
|
1118
|
+
(0, import_react6.useEffect)(() => {
|
|
1071
1119
|
const unregister = registerLessonMount(lessonId);
|
|
1072
1120
|
const generation = ++lessonMountGenerationRef.current;
|
|
1073
1121
|
const mountedCourseId = config.courseId;
|
|
@@ -1098,20 +1146,20 @@ function Lesson(props) {
|
|
|
1098
1146
|
] }) });
|
|
1099
1147
|
}
|
|
1100
1148
|
function Scenario(props) {
|
|
1101
|
-
const blockId = (0,
|
|
1149
|
+
const blockId = (0, import_react6.useMemo)(
|
|
1102
1150
|
() => props.blockId !== void 0 ? normalizeComponentId(props.blockId, "blockId") : void 0,
|
|
1103
1151
|
[props.blockId]
|
|
1104
1152
|
);
|
|
1105
1153
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("section", { "aria-label": "Scenario", "data-lk-block-id": blockId, children: props.children });
|
|
1106
1154
|
}
|
|
1107
1155
|
function Reflection(props) {
|
|
1108
|
-
const blockId = (0,
|
|
1156
|
+
const blockId = (0, import_react6.useMemo)(
|
|
1109
1157
|
() => props.blockId !== void 0 ? normalizeComponentId(props.blockId, "blockId") : void 0,
|
|
1110
1158
|
[props.blockId]
|
|
1111
1159
|
);
|
|
1112
|
-
const promptId = (0,
|
|
1113
|
-
const hintId = (0,
|
|
1114
|
-
const [internalValue, setInternalValue] = (0,
|
|
1160
|
+
const promptId = (0, import_react6.useId)();
|
|
1161
|
+
const hintId = (0, import_react6.useId)();
|
|
1162
|
+
const [internalValue, setInternalValue] = (0, import_react6.useState)("");
|
|
1115
1163
|
const isControlled = props.value !== void 0;
|
|
1116
1164
|
const value = isControlled ? props.value : internalValue;
|
|
1117
1165
|
const handleChange = (event) => {
|
|
@@ -1149,7 +1197,7 @@ function KnowledgeCheck(props) {
|
|
|
1149
1197
|
function Quiz(props) {
|
|
1150
1198
|
const enclosingLessonId = useEnclosingLessonId();
|
|
1151
1199
|
const missingLesson = enclosingLessonId === void 0;
|
|
1152
|
-
(0,
|
|
1200
|
+
(0, import_react6.useEffect)(() => {
|
|
1153
1201
|
if (!missingLesson || isDevEnvironment4()) return;
|
|
1154
1202
|
if (!warnedQuizOutsideLesson) {
|
|
1155
1203
|
warnedQuizOutsideLesson = true;
|
|
@@ -1168,16 +1216,16 @@ function Quiz(props) {
|
|
|
1168
1216
|
}
|
|
1169
1217
|
function QuizInner(props) {
|
|
1170
1218
|
const { enclosingLessonId } = props;
|
|
1171
|
-
const checkId = (0,
|
|
1219
|
+
const checkId = (0, import_react6.useMemo)(() => normalizeComponentId(props.checkId, "checkId"), [props.checkId]);
|
|
1172
1220
|
const quiz = useQuizState(enclosingLessonId);
|
|
1173
1221
|
const { plugins, config, session } = useLessonkit();
|
|
1174
|
-
const [selected, setSelected] = (0,
|
|
1175
|
-
const [selectionCorrect, setSelectionCorrect] = (0,
|
|
1176
|
-
const [quizPassed, setQuizPassed] = (0,
|
|
1177
|
-
const completedRef = (0,
|
|
1178
|
-
const questionId = (0,
|
|
1222
|
+
const [selected, setSelected] = (0, import_react6.useState)(null);
|
|
1223
|
+
const [selectionCorrect, setSelectionCorrect] = (0, import_react6.useState)(null);
|
|
1224
|
+
const [quizPassed, setQuizPassed] = (0, import_react6.useState)(false);
|
|
1225
|
+
const completedRef = (0, import_react6.useRef)(false);
|
|
1226
|
+
const questionId = (0, import_react6.useId)();
|
|
1179
1227
|
const choicesKey = props.choices.join("\0");
|
|
1180
|
-
(0,
|
|
1228
|
+
(0, import_react6.useEffect)(() => {
|
|
1181
1229
|
completedRef.current = false;
|
|
1182
1230
|
setQuizPassed(false);
|
|
1183
1231
|
setSelected(null);
|
|
@@ -1186,8 +1234,8 @@ function QuizInner(props) {
|
|
|
1186
1234
|
const isChoiceCorrect = (choice, custom) => {
|
|
1187
1235
|
if (!custom) return choice === props.answer;
|
|
1188
1236
|
if (custom.passed !== void 0) return custom.passed;
|
|
1189
|
-
if (custom.maxScore != null && custom.maxScore > 0) {
|
|
1190
|
-
return custom.score
|
|
1237
|
+
if (custom.maxScore != null && custom.maxScore > 0 && custom.score != null) {
|
|
1238
|
+
return meetsPassingThreshold(custom.score, custom.maxScore, props.passingScore);
|
|
1191
1239
|
}
|
|
1192
1240
|
return choice === props.answer;
|
|
1193
1241
|
};
|
|
@@ -1237,7 +1285,7 @@ function QuizInner(props) {
|
|
|
1237
1285
|
const maxScore = custom?.maxScore ?? 1;
|
|
1238
1286
|
quiz.complete({
|
|
1239
1287
|
checkId,
|
|
1240
|
-
score: custom?.score ??
|
|
1288
|
+
score: custom?.score ?? maxScore,
|
|
1241
1289
|
maxScore,
|
|
1242
1290
|
passingScore: props.passingScore ?? maxScore
|
|
1243
1291
|
});
|
|
@@ -1280,11 +1328,864 @@ function ProgressTracker(props) {
|
|
|
1280
1328
|
] }) });
|
|
1281
1329
|
}
|
|
1282
1330
|
|
|
1331
|
+
// src/blocks/TrueFalse.tsx
|
|
1332
|
+
var import_react9 = __toESM(require("react"), 1);
|
|
1333
|
+
|
|
1334
|
+
// src/assessment/AssessmentLessonGuard.tsx
|
|
1335
|
+
var import_react7 = require("react");
|
|
1336
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
1337
|
+
var warnedAssessmentOutsideLesson = false;
|
|
1338
|
+
function resetAssessmentWarningsForTests() {
|
|
1339
|
+
warnedAssessmentOutsideLesson = false;
|
|
1340
|
+
}
|
|
1341
|
+
function AssessmentLessonGuard(props) {
|
|
1342
|
+
const enclosingLessonId = useEnclosingLessonId();
|
|
1343
|
+
const missingLesson = enclosingLessonId === void 0;
|
|
1344
|
+
(0, import_react7.useEffect)(() => {
|
|
1345
|
+
if (!missingLesson || isDevEnvironment4()) return;
|
|
1346
|
+
if (!warnedAssessmentOutsideLesson) {
|
|
1347
|
+
warnedAssessmentOutsideLesson = true;
|
|
1348
|
+
console.error(
|
|
1349
|
+
`[lessonkit] <${props.blockLabel}> must be wrapped in <Lesson>; assessment telemetry will not be emitted.`
|
|
1350
|
+
);
|
|
1351
|
+
}
|
|
1352
|
+
}, [missingLesson, props.blockLabel]);
|
|
1353
|
+
if (missingLesson && isDevEnvironment4()) {
|
|
1354
|
+
throw new Error(`[lessonkit] <${props.blockLabel}> must be wrapped in <Lesson>`);
|
|
1355
|
+
}
|
|
1356
|
+
if (missingLesson) {
|
|
1357
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("section", { role: "alert", "aria-label": `${props.blockLabel} configuration error`, "data-lk-check-id": props.checkId, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("p", { children: [
|
|
1358
|
+
props.blockLabel,
|
|
1359
|
+
" must be placed inside a Lesson."
|
|
1360
|
+
] }) });
|
|
1361
|
+
}
|
|
1362
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_jsx_runtime3.Fragment, { children: props.children(enclosingLessonId) });
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
// src/assessment/AssessmentSequenceContext.tsx
|
|
1366
|
+
var import_react8 = __toESM(require("react"), 1);
|
|
1367
|
+
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
1368
|
+
var AssessmentSequenceContext = (0, import_react8.createContext)(null);
|
|
1369
|
+
function AssessmentSequenceProvider({ children }) {
|
|
1370
|
+
const registryRef = (0, import_react8.useRef)(/* @__PURE__ */ new Map());
|
|
1371
|
+
const register = (0, import_react8.useCallback)((checkId, handle) => {
|
|
1372
|
+
registryRef.current.set(checkId, handle);
|
|
1373
|
+
return () => {
|
|
1374
|
+
registryRef.current.delete(checkId);
|
|
1375
|
+
};
|
|
1376
|
+
}, []);
|
|
1377
|
+
const value = (0, import_react8.useMemo)(
|
|
1378
|
+
() => ({
|
|
1379
|
+
register,
|
|
1380
|
+
getHandles: () => registryRef.current
|
|
1381
|
+
}),
|
|
1382
|
+
[register]
|
|
1383
|
+
);
|
|
1384
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(AssessmentSequenceContext.Provider, { value, children });
|
|
1385
|
+
}
|
|
1386
|
+
function useAssessmentSequenceRegistry() {
|
|
1387
|
+
return (0, import_react8.useContext)(AssessmentSequenceContext);
|
|
1388
|
+
}
|
|
1389
|
+
function useRegisterAssessmentHandle(checkId, handle) {
|
|
1390
|
+
const ctx = useAssessmentSequenceRegistry();
|
|
1391
|
+
import_react8.default.useEffect(() => {
|
|
1392
|
+
if (!ctx || !handle) return;
|
|
1393
|
+
return ctx.register(checkId, handle);
|
|
1394
|
+
}, [ctx, checkId, handle]);
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
// src/blocks/TrueFalse.tsx
|
|
1398
|
+
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
1399
|
+
var INTERACTION = "trueFalse";
|
|
1400
|
+
function TrueFalseInner(props, ref) {
|
|
1401
|
+
const { enclosingLessonId } = props;
|
|
1402
|
+
const checkId = (0, import_react9.useMemo)(() => normalizeComponentId(props.checkId, "checkId"), [props.checkId]);
|
|
1403
|
+
const assessment = useAssessmentState(enclosingLessonId);
|
|
1404
|
+
const { plugins, config, session } = useLessonkit();
|
|
1405
|
+
const [selected, setSelected] = (0, import_react9.useState)(null);
|
|
1406
|
+
const [selectionCorrect, setSelectionCorrect] = (0, import_react9.useState)(null);
|
|
1407
|
+
const [showSolutions, setShowSolutions] = (0, import_react9.useState)(false);
|
|
1408
|
+
const [passed, setPassed] = (0, import_react9.useState)(false);
|
|
1409
|
+
const completedRef = (0, import_react9.useRef)(false);
|
|
1410
|
+
const questionId = import_react9.default.useId();
|
|
1411
|
+
const reset = () => {
|
|
1412
|
+
completedRef.current = false;
|
|
1413
|
+
setPassed(false);
|
|
1414
|
+
setSelected(null);
|
|
1415
|
+
setSelectionCorrect(null);
|
|
1416
|
+
setShowSolutions(false);
|
|
1417
|
+
};
|
|
1418
|
+
(0, import_react9.useEffect)(() => {
|
|
1419
|
+
reset();
|
|
1420
|
+
}, [checkId, props.answer, props.question, config.courseId, enclosingLessonId]);
|
|
1421
|
+
const handle = (0, import_react9.useMemo)(() => {
|
|
1422
|
+
const maxScore = 1;
|
|
1423
|
+
const score = passed ? maxScore : selected === null ? 0 : selected === props.answer ? maxScore : 0;
|
|
1424
|
+
return {
|
|
1425
|
+
getScore: () => score,
|
|
1426
|
+
getMaxScore: () => maxScore,
|
|
1427
|
+
getAnswerGiven: () => selected !== null,
|
|
1428
|
+
resetTask: reset,
|
|
1429
|
+
showSolutions: () => setShowSolutions(true),
|
|
1430
|
+
getXAPIData: () => ({
|
|
1431
|
+
checkId,
|
|
1432
|
+
interactionType: INTERACTION,
|
|
1433
|
+
response: selected ?? void 0,
|
|
1434
|
+
correct: selected === props.answer,
|
|
1435
|
+
score,
|
|
1436
|
+
maxScore
|
|
1437
|
+
})
|
|
1438
|
+
};
|
|
1439
|
+
}, [checkId, passed, props.answer, selected]);
|
|
1440
|
+
(0, import_react9.useImperativeHandle)(ref, () => handle, [handle]);
|
|
1441
|
+
useRegisterAssessmentHandle(checkId, handle);
|
|
1442
|
+
const submit = (value) => {
|
|
1443
|
+
if (passed && !props.enableRetry) return;
|
|
1444
|
+
setSelected(value);
|
|
1445
|
+
const pluginCtx = buildPluginContext({
|
|
1446
|
+
courseId: config.courseId,
|
|
1447
|
+
sessionId: session.sessionId,
|
|
1448
|
+
attemptId: session.attemptId,
|
|
1449
|
+
user: session.user
|
|
1450
|
+
});
|
|
1451
|
+
const custom = plugins?.scoreAssessment(
|
|
1452
|
+
{ checkId, lessonId: enclosingLessonId, response: value },
|
|
1453
|
+
pluginCtx
|
|
1454
|
+
) ?? null;
|
|
1455
|
+
const correct = value === props.answer;
|
|
1456
|
+
const scored = scoreFromCustom(custom, correct, 1, props.passingScore);
|
|
1457
|
+
setSelectionCorrect(scored.passed);
|
|
1458
|
+
assessment.answer({
|
|
1459
|
+
checkId,
|
|
1460
|
+
interactionType: INTERACTION,
|
|
1461
|
+
question: props.question,
|
|
1462
|
+
response: value,
|
|
1463
|
+
correct: scored.passed
|
|
1464
|
+
});
|
|
1465
|
+
if (scored.passed && !completedRef.current) {
|
|
1466
|
+
completedRef.current = true;
|
|
1467
|
+
setPassed(true);
|
|
1468
|
+
assessment.complete({
|
|
1469
|
+
checkId,
|
|
1470
|
+
interactionType: INTERACTION,
|
|
1471
|
+
score: scored.score,
|
|
1472
|
+
maxScore: scored.maxScore,
|
|
1473
|
+
passingScore: props.passingScore ?? scored.maxScore
|
|
1474
|
+
});
|
|
1475
|
+
}
|
|
1476
|
+
};
|
|
1477
|
+
const reveal = showSolutions || passed && props.enableSolutionsButton;
|
|
1478
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("section", { "aria-label": "True or False", "data-lk-check-id": checkId, children: [
|
|
1479
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { id: questionId, children: props.question }),
|
|
1480
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("fieldset", { "aria-labelledby": questionId, children: [
|
|
1481
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("legend", { className: "lk-visually-hidden", children: "True or False" }),
|
|
1482
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("label", { style: { display: "block", marginRight: "1rem" }, children: [
|
|
1483
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
1484
|
+
"input",
|
|
1485
|
+
{
|
|
1486
|
+
type: "radio",
|
|
1487
|
+
name: `${questionId}-tf`,
|
|
1488
|
+
checked: selected === true,
|
|
1489
|
+
disabled: passed && !props.enableRetry,
|
|
1490
|
+
onChange: () => submit(true)
|
|
1491
|
+
}
|
|
1492
|
+
),
|
|
1493
|
+
"True"
|
|
1494
|
+
] }),
|
|
1495
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("label", { style: { display: "block" }, children: [
|
|
1496
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
1497
|
+
"input",
|
|
1498
|
+
{
|
|
1499
|
+
type: "radio",
|
|
1500
|
+
name: `${questionId}-tf`,
|
|
1501
|
+
checked: selected === false,
|
|
1502
|
+
disabled: passed && !props.enableRetry,
|
|
1503
|
+
onChange: () => submit(false)
|
|
1504
|
+
}
|
|
1505
|
+
),
|
|
1506
|
+
"False"
|
|
1507
|
+
] })
|
|
1508
|
+
] }),
|
|
1509
|
+
reveal ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("p", { children: [
|
|
1510
|
+
"Correct answer: ",
|
|
1511
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("strong", { children: props.answer ? "True" : "False" })
|
|
1512
|
+
] }) : null,
|
|
1513
|
+
selected !== null && selectionCorrect !== null ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { role: "status", "aria-live": "polite", children: selectionCorrect ? "Correct" : "Try again" }) : null,
|
|
1514
|
+
props.enableRetry && passed ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", onClick: reset, children: "Try again" }) : null,
|
|
1515
|
+
props.enableSolutionsButton && !reveal ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", onClick: () => setShowSolutions(true), children: "Show solution" }) : null
|
|
1516
|
+
] });
|
|
1517
|
+
}
|
|
1518
|
+
var TrueFalseInnerForwarded = (0, import_react9.forwardRef)(TrueFalseInner);
|
|
1519
|
+
var TrueFalse = (0, import_react9.forwardRef)(function TrueFalse2(props, ref) {
|
|
1520
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(AssessmentLessonGuard, { blockLabel: "TrueFalse", checkId: props.checkId, children: (lessonId) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(TrueFalseInnerForwarded, { ...props, enclosingLessonId: lessonId, ref }) });
|
|
1521
|
+
});
|
|
1522
|
+
|
|
1523
|
+
// src/blocks/MarkTheWords.tsx
|
|
1524
|
+
var import_react10 = __toESM(require("react"), 1);
|
|
1525
|
+
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
1526
|
+
var INTERACTION2 = "markTheWords";
|
|
1527
|
+
function tokenize(text) {
|
|
1528
|
+
return text.split(/(\s+)/).filter((t) => t.length > 0);
|
|
1529
|
+
}
|
|
1530
|
+
function MarkTheWordsInner(props, ref) {
|
|
1531
|
+
const checkId = (0, import_react10.useMemo)(() => normalizeComponentId(props.checkId, "checkId"), [props.checkId]);
|
|
1532
|
+
const assessment = useAssessmentState(props.enclosingLessonId);
|
|
1533
|
+
const tokens = (0, import_react10.useMemo)(() => tokenize(props.text), [props.text]);
|
|
1534
|
+
const correctSet = (0, import_react10.useMemo)(
|
|
1535
|
+
() => new Set(props.correctWords.map((w) => w.toLowerCase())),
|
|
1536
|
+
[props.correctWords]
|
|
1537
|
+
);
|
|
1538
|
+
const [marked, setMarked] = (0, import_react10.useState)(() => /* @__PURE__ */ new Set());
|
|
1539
|
+
const [passed, setPassed] = (0, import_react10.useState)(false);
|
|
1540
|
+
const [showSolutions, setShowSolutions] = (0, import_react10.useState)(false);
|
|
1541
|
+
const completedRef = (0, import_react10.useRef)(false);
|
|
1542
|
+
const reset = () => {
|
|
1543
|
+
completedRef.current = false;
|
|
1544
|
+
setPassed(false);
|
|
1545
|
+
setMarked(/* @__PURE__ */ new Set());
|
|
1546
|
+
setShowSolutions(false);
|
|
1547
|
+
};
|
|
1548
|
+
(0, import_react10.useEffect)(() => {
|
|
1549
|
+
reset();
|
|
1550
|
+
}, [checkId, props.text, props.correctWords.join("\0")]);
|
|
1551
|
+
const selectableIndices = (0, import_react10.useMemo)(() => {
|
|
1552
|
+
const indices = [];
|
|
1553
|
+
tokens.forEach((t, i) => {
|
|
1554
|
+
if (!/^\s+$/.test(t) && correctSet.has(t.toLowerCase())) indices.push(i);
|
|
1555
|
+
});
|
|
1556
|
+
return indices;
|
|
1557
|
+
}, [tokens, correctSet]);
|
|
1558
|
+
const hasTargets = selectableIndices.length > 0;
|
|
1559
|
+
const allMarked = hasTargets && selectableIndices.every((i) => marked.has(i));
|
|
1560
|
+
const maxScore = selectableIndices.length;
|
|
1561
|
+
const score = allMarked ? maxScore : marked.size;
|
|
1562
|
+
const passedThreshold = meetsPassingThreshold(score, maxScore || 1, props.passingScore);
|
|
1563
|
+
const handle = (0, import_react10.useMemo)(() => {
|
|
1564
|
+
const handleMax = maxScore || 1;
|
|
1565
|
+
return {
|
|
1566
|
+
getScore: () => score,
|
|
1567
|
+
getMaxScore: () => handleMax,
|
|
1568
|
+
getAnswerGiven: () => marked.size > 0,
|
|
1569
|
+
resetTask: reset,
|
|
1570
|
+
showSolutions: () => setShowSolutions(true),
|
|
1571
|
+
getXAPIData: () => ({
|
|
1572
|
+
checkId,
|
|
1573
|
+
interactionType: INTERACTION2,
|
|
1574
|
+
response: [...marked].map((i) => tokens[i]),
|
|
1575
|
+
correct: passedThreshold,
|
|
1576
|
+
score,
|
|
1577
|
+
maxScore: handleMax
|
|
1578
|
+
})
|
|
1579
|
+
};
|
|
1580
|
+
}, [checkId, marked, maxScore, passedThreshold, score, tokens]);
|
|
1581
|
+
(0, import_react10.useImperativeHandle)(ref, () => handle, [handle]);
|
|
1582
|
+
useRegisterAssessmentHandle(checkId, handle);
|
|
1583
|
+
const toggle = (index) => {
|
|
1584
|
+
if (passed && !props.enableRetry) return;
|
|
1585
|
+
setMarked((prev) => {
|
|
1586
|
+
const next = new Set(prev);
|
|
1587
|
+
if (next.has(index)) next.delete(index);
|
|
1588
|
+
else next.add(index);
|
|
1589
|
+
return next;
|
|
1590
|
+
});
|
|
1591
|
+
};
|
|
1592
|
+
(0, import_react10.useEffect)(() => {
|
|
1593
|
+
if (!hasTargets) {
|
|
1594
|
+
if (isDevEnvironment4()) {
|
|
1595
|
+
console.warn(
|
|
1596
|
+
"[lessonkit] MarkTheWords: no tokens match correctWords",
|
|
1597
|
+
props.correctWords
|
|
1598
|
+
);
|
|
1599
|
+
}
|
|
1600
|
+
return;
|
|
1601
|
+
}
|
|
1602
|
+
if (!passedThreshold || completedRef.current) return;
|
|
1603
|
+
completedRef.current = true;
|
|
1604
|
+
setPassed(true);
|
|
1605
|
+
assessment.answer({
|
|
1606
|
+
checkId,
|
|
1607
|
+
interactionType: INTERACTION2,
|
|
1608
|
+
question: props.text,
|
|
1609
|
+
response: [...marked].map((i) => tokens[i]),
|
|
1610
|
+
correct: true
|
|
1611
|
+
});
|
|
1612
|
+
assessment.complete({
|
|
1613
|
+
checkId,
|
|
1614
|
+
interactionType: INTERACTION2,
|
|
1615
|
+
score,
|
|
1616
|
+
maxScore,
|
|
1617
|
+
passingScore: props.passingScore ?? maxScore
|
|
1618
|
+
});
|
|
1619
|
+
}, [
|
|
1620
|
+
assessment,
|
|
1621
|
+
checkId,
|
|
1622
|
+
hasTargets,
|
|
1623
|
+
marked,
|
|
1624
|
+
maxScore,
|
|
1625
|
+
passedThreshold,
|
|
1626
|
+
props.passingScore,
|
|
1627
|
+
props.correctWords,
|
|
1628
|
+
props.text,
|
|
1629
|
+
score,
|
|
1630
|
+
tokens
|
|
1631
|
+
]);
|
|
1632
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("section", { "aria-label": "Mark the Words", "data-lk-check-id": checkId, children: [
|
|
1633
|
+
!hasTargets ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("p", { role: "alert", children: [
|
|
1634
|
+
"No words in this sentence match ",
|
|
1635
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("code", { children: "correctWords" }),
|
|
1636
|
+
". Check spelling and capitalization in the source text."
|
|
1637
|
+
] }) : null,
|
|
1638
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { id: `${checkId}-hint`, children: "Select the correct words in the sentence." }),
|
|
1639
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { "aria-describedby": `${checkId}-hint`, children: tokens.map((token, i) => {
|
|
1640
|
+
const isWord = !/^\s+$/.test(token);
|
|
1641
|
+
const isTarget = isWord && correctSet.has(token.toLowerCase());
|
|
1642
|
+
if (!isTarget) return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react10.default.Fragment, { children: token }, i);
|
|
1643
|
+
const selected = marked.has(i);
|
|
1644
|
+
const solution = showSolutions || passed && props.enableSolutionsButton;
|
|
1645
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1646
|
+
"button",
|
|
1647
|
+
{
|
|
1648
|
+
type: "button",
|
|
1649
|
+
"data-testid": `mark-word-${i}`,
|
|
1650
|
+
"aria-pressed": selected,
|
|
1651
|
+
disabled: passed && !props.enableRetry,
|
|
1652
|
+
onClick: () => toggle(i),
|
|
1653
|
+
style: {
|
|
1654
|
+
margin: "0 0.1em",
|
|
1655
|
+
textDecoration: solution ? "underline" : void 0,
|
|
1656
|
+
fontWeight: selected || solution ? "bold" : void 0
|
|
1657
|
+
},
|
|
1658
|
+
children: token
|
|
1659
|
+
},
|
|
1660
|
+
i
|
|
1661
|
+
);
|
|
1662
|
+
}) }),
|
|
1663
|
+
allMarked ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { role: "status", "aria-live": "polite", children: "Correct" }) : null,
|
|
1664
|
+
props.enableRetry && passed ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", onClick: reset, children: "Try again" }) : null,
|
|
1665
|
+
props.enableSolutionsButton && !showSolutions ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", onClick: () => setShowSolutions(true), children: "Show solution" }) : null
|
|
1666
|
+
] });
|
|
1667
|
+
}
|
|
1668
|
+
var MarkTheWordsInnerForwarded = (0, import_react10.forwardRef)(MarkTheWordsInner);
|
|
1669
|
+
var MarkTheWords = (0, import_react10.forwardRef)(function MarkTheWords2(props, ref) {
|
|
1670
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(AssessmentLessonGuard, { blockLabel: "MarkTheWords", checkId: props.checkId, children: (lessonId) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(MarkTheWordsInnerForwarded, { ...props, enclosingLessonId: lessonId, ref }) });
|
|
1671
|
+
});
|
|
1672
|
+
|
|
1673
|
+
// src/blocks/FillInTheBlanks.tsx
|
|
1674
|
+
var import_react11 = __toESM(require("react"), 1);
|
|
1675
|
+
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
1676
|
+
var INTERACTION3 = "fillInBlanks";
|
|
1677
|
+
function parseTemplate(template) {
|
|
1678
|
+
const parts = [];
|
|
1679
|
+
const blanks = [];
|
|
1680
|
+
const re = /\*([^*]+)\*/g;
|
|
1681
|
+
let last = 0;
|
|
1682
|
+
let match;
|
|
1683
|
+
let n = 0;
|
|
1684
|
+
while ((match = re.exec(template)) !== null) {
|
|
1685
|
+
parts.push(template.slice(last, match.index));
|
|
1686
|
+
const id = `blank-${n++}`;
|
|
1687
|
+
blanks.push({ id, answer: match[1].trim() });
|
|
1688
|
+
parts.push(id);
|
|
1689
|
+
last = match.index + match[0].length;
|
|
1690
|
+
}
|
|
1691
|
+
parts.push(template.slice(last));
|
|
1692
|
+
return { parts, blanks };
|
|
1693
|
+
}
|
|
1694
|
+
function FillInTheBlanksInner(props, ref) {
|
|
1695
|
+
const checkId = (0, import_react11.useMemo)(() => normalizeComponentId(props.checkId, "checkId"), [props.checkId]);
|
|
1696
|
+
const assessment = useAssessmentState(props.enclosingLessonId);
|
|
1697
|
+
const parsed = (0, import_react11.useMemo)(() => parseTemplate(props.template), [props.template]);
|
|
1698
|
+
const blanks = props.blanks ?? parsed.blanks;
|
|
1699
|
+
const [values, setValues] = (0, import_react11.useState)(
|
|
1700
|
+
() => Object.fromEntries(blanks.map((b) => [b.id, ""]))
|
|
1701
|
+
);
|
|
1702
|
+
const [passed, setPassed] = (0, import_react11.useState)(false);
|
|
1703
|
+
const [showSolutions, setShowSolutions] = (0, import_react11.useState)(false);
|
|
1704
|
+
const completedRef = (0, import_react11.useRef)(false);
|
|
1705
|
+
const answeredRef = (0, import_react11.useRef)(false);
|
|
1706
|
+
const reset = () => {
|
|
1707
|
+
completedRef.current = false;
|
|
1708
|
+
answeredRef.current = false;
|
|
1709
|
+
setPassed(false);
|
|
1710
|
+
setValues(Object.fromEntries(blanks.map((b) => [b.id, ""])));
|
|
1711
|
+
setShowSolutions(false);
|
|
1712
|
+
};
|
|
1713
|
+
(0, import_react11.useEffect)(() => {
|
|
1714
|
+
reset();
|
|
1715
|
+
}, [checkId, props.template, blanks.map((b) => b.answer).join("\0")]);
|
|
1716
|
+
const hasBlanks = blanks.length > 0;
|
|
1717
|
+
const allFilled = hasBlanks && blanks.every((b) => (values[b.id] ?? "").trim().length > 0);
|
|
1718
|
+
let score = 0;
|
|
1719
|
+
blanks.forEach((b) => {
|
|
1720
|
+
if ((values[b.id] ?? "").trim().toLowerCase() === b.answer.toLowerCase()) score += 1;
|
|
1721
|
+
});
|
|
1722
|
+
const maxScore = blanks.length;
|
|
1723
|
+
const passedThreshold = meetsPassingThreshold(score, maxScore || 1, props.passingScore);
|
|
1724
|
+
const handle = (0, import_react11.useMemo)(() => {
|
|
1725
|
+
const handleMax = maxScore || 1;
|
|
1726
|
+
return {
|
|
1727
|
+
getScore: () => score,
|
|
1728
|
+
getMaxScore: () => handleMax,
|
|
1729
|
+
getAnswerGiven: () => allFilled,
|
|
1730
|
+
resetTask: reset,
|
|
1731
|
+
showSolutions: () => setShowSolutions(true),
|
|
1732
|
+
getXAPIData: () => ({
|
|
1733
|
+
checkId,
|
|
1734
|
+
interactionType: INTERACTION3,
|
|
1735
|
+
response: values,
|
|
1736
|
+
correct: passedThreshold,
|
|
1737
|
+
score,
|
|
1738
|
+
maxScore: handleMax
|
|
1739
|
+
})
|
|
1740
|
+
};
|
|
1741
|
+
}, [allFilled, blanks.length, checkId, maxScore, passedThreshold, score, values]);
|
|
1742
|
+
(0, import_react11.useImperativeHandle)(ref, () => handle, [handle]);
|
|
1743
|
+
useRegisterAssessmentHandle(checkId, handle);
|
|
1744
|
+
const check = () => {
|
|
1745
|
+
if (!hasBlanks) {
|
|
1746
|
+
if (isDevEnvironment4()) {
|
|
1747
|
+
console.warn("[lessonkit] FillInTheBlanks has no blanks in template");
|
|
1748
|
+
}
|
|
1749
|
+
return;
|
|
1750
|
+
}
|
|
1751
|
+
if (!allFilled) return;
|
|
1752
|
+
if (!answeredRef.current) {
|
|
1753
|
+
answeredRef.current = true;
|
|
1754
|
+
assessment.answer({
|
|
1755
|
+
checkId,
|
|
1756
|
+
interactionType: INTERACTION3,
|
|
1757
|
+
question: props.template,
|
|
1758
|
+
response: values,
|
|
1759
|
+
correct: passedThreshold
|
|
1760
|
+
});
|
|
1761
|
+
}
|
|
1762
|
+
if (passedThreshold && !completedRef.current) {
|
|
1763
|
+
completedRef.current = true;
|
|
1764
|
+
setPassed(true);
|
|
1765
|
+
assessment.complete({
|
|
1766
|
+
checkId,
|
|
1767
|
+
interactionType: INTERACTION3,
|
|
1768
|
+
score,
|
|
1769
|
+
maxScore,
|
|
1770
|
+
passingScore: props.passingScore ?? maxScore
|
|
1771
|
+
});
|
|
1772
|
+
}
|
|
1773
|
+
};
|
|
1774
|
+
(0, import_react11.useEffect)(() => {
|
|
1775
|
+
if (!allFilled) answeredRef.current = false;
|
|
1776
|
+
}, [allFilled]);
|
|
1777
|
+
(0, import_react11.useEffect)(() => {
|
|
1778
|
+
if (props.autoCheck && allFilled) check();
|
|
1779
|
+
}, [allFilled, props.autoCheck, values, passedThreshold]);
|
|
1780
|
+
const reveal = showSolutions || passed && props.enableSolutionsButton;
|
|
1781
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("section", { "aria-label": "Fill in the Blanks", "data-lk-check-id": checkId, children: [
|
|
1782
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { children: parsed.parts.map((part, i) => {
|
|
1783
|
+
const blank = blanks.find((b) => b.id === part);
|
|
1784
|
+
if (!blank) return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react11.default.Fragment, { children: part }, i);
|
|
1785
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("label", { style: { margin: "0 0.25em" }, children: [
|
|
1786
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "lk-visually-hidden", children: blank.answer }),
|
|
1787
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1788
|
+
"input",
|
|
1789
|
+
{
|
|
1790
|
+
type: "text",
|
|
1791
|
+
"data-testid": `blank-${blank.id}`,
|
|
1792
|
+
"aria-label": `Blank ${blank.id}`,
|
|
1793
|
+
value: reveal ? blank.answer : values[blank.id] ?? "",
|
|
1794
|
+
readOnly: reveal,
|
|
1795
|
+
disabled: passed && !props.enableRetry,
|
|
1796
|
+
onChange: (e) => setValues((v) => ({ ...v, [blank.id]: e.target.value })),
|
|
1797
|
+
onBlur: () => props.autoCheck && check(),
|
|
1798
|
+
size: Math.max(8, blank.answer.length + 2)
|
|
1799
|
+
}
|
|
1800
|
+
)
|
|
1801
|
+
] }, blank.id);
|
|
1802
|
+
}) }),
|
|
1803
|
+
!props.autoCheck ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { type: "button", "data-testid": "check-blanks", disabled: !allFilled || passed, onClick: check, children: "Check" }) : null,
|
|
1804
|
+
!hasBlanks ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { role: "alert", children: "This activity has no blanks. Add text wrapped in asterisks, e.g. The *answer* here." }) : null,
|
|
1805
|
+
allFilled ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { role: "status", "aria-live": "polite", children: passed || passedThreshold ? "Correct" : "Try again" }) : null,
|
|
1806
|
+
props.enableRetry && passed ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { type: "button", onClick: reset, children: "Try again" }) : null,
|
|
1807
|
+
props.enableSolutionsButton && !reveal ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { type: "button", onClick: () => setShowSolutions(true), children: "Show solution" }) : null
|
|
1808
|
+
] });
|
|
1809
|
+
}
|
|
1810
|
+
var FillInTheBlanksInnerForwarded = (0, import_react11.forwardRef)(FillInTheBlanksInner);
|
|
1811
|
+
var FillInTheBlanks = (0, import_react11.forwardRef)(
|
|
1812
|
+
function FillInTheBlanks2(props, ref) {
|
|
1813
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(AssessmentLessonGuard, { blockLabel: "FillInTheBlanks", checkId: props.checkId, children: (lessonId) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(FillInTheBlanksInnerForwarded, { ...props, enclosingLessonId: lessonId, ref }) });
|
|
1814
|
+
}
|
|
1815
|
+
);
|
|
1816
|
+
|
|
1817
|
+
// src/blocks/DragTheWords.tsx
|
|
1818
|
+
var import_react12 = __toESM(require("react"), 1);
|
|
1819
|
+
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
1820
|
+
var INTERACTION4 = "dragTheWords";
|
|
1821
|
+
function parseZones(template) {
|
|
1822
|
+
const parts = [];
|
|
1823
|
+
const answers = [];
|
|
1824
|
+
const re = /\*([^*]+)\*/g;
|
|
1825
|
+
let last = 0;
|
|
1826
|
+
let match;
|
|
1827
|
+
let n = 0;
|
|
1828
|
+
while ((match = re.exec(template)) !== null) {
|
|
1829
|
+
parts.push(template.slice(last, match.index));
|
|
1830
|
+
answers.push(match[1].trim());
|
|
1831
|
+
parts.push(`zone-${n++}`);
|
|
1832
|
+
last = match.index + match[0].length;
|
|
1833
|
+
}
|
|
1834
|
+
parts.push(template.slice(last));
|
|
1835
|
+
return { parts, answers };
|
|
1836
|
+
}
|
|
1837
|
+
function DragTheWordsInner(props, ref) {
|
|
1838
|
+
const checkId = (0, import_react12.useMemo)(() => normalizeComponentId(props.checkId, "checkId"), [props.checkId]);
|
|
1839
|
+
const assessment = useAssessmentState(props.enclosingLessonId);
|
|
1840
|
+
const { parts, answers } = (0, import_react12.useMemo)(() => parseZones(props.template), [props.template]);
|
|
1841
|
+
const [zones, setZones] = (0, import_react12.useState)(
|
|
1842
|
+
() => Object.fromEntries(answers.map((_, i) => [`zone-${i}`, ""]))
|
|
1843
|
+
);
|
|
1844
|
+
const [pool, setPool] = (0, import_react12.useState)(() => [...props.words]);
|
|
1845
|
+
const [keyboardWord, setKeyboardWord] = (0, import_react12.useState)(null);
|
|
1846
|
+
const [passed, setPassed] = (0, import_react12.useState)(false);
|
|
1847
|
+
const completedRef = (0, import_react12.useRef)(false);
|
|
1848
|
+
const answeredRef = (0, import_react12.useRef)(false);
|
|
1849
|
+
const reset = () => {
|
|
1850
|
+
completedRef.current = false;
|
|
1851
|
+
answeredRef.current = false;
|
|
1852
|
+
setPassed(false);
|
|
1853
|
+
setZones(Object.fromEntries(answers.map((_, i) => [`zone-${i}`, ""])));
|
|
1854
|
+
setPool([...props.words]);
|
|
1855
|
+
setKeyboardWord(null);
|
|
1856
|
+
};
|
|
1857
|
+
(0, import_react12.useEffect)(() => {
|
|
1858
|
+
reset();
|
|
1859
|
+
}, [checkId, props.template, props.words.join("\0")]);
|
|
1860
|
+
const hasZones = answers.length > 0;
|
|
1861
|
+
const allFilled = hasZones && answers.every((_, i) => (zones[`zone-${i}`] ?? "").length > 0);
|
|
1862
|
+
let score = 0;
|
|
1863
|
+
answers.forEach((ans, i) => {
|
|
1864
|
+
if ((zones[`zone-${i}`] ?? "").trim().toLowerCase() === ans.toLowerCase()) score += 1;
|
|
1865
|
+
});
|
|
1866
|
+
const maxScore = answers.length;
|
|
1867
|
+
const passedThreshold = meetsPassingThreshold(score, maxScore || 1, props.passingScore);
|
|
1868
|
+
const handle = (0, import_react12.useMemo)(() => {
|
|
1869
|
+
const handleMax = maxScore || 1;
|
|
1870
|
+
return {
|
|
1871
|
+
getScore: () => score,
|
|
1872
|
+
getMaxScore: () => handleMax,
|
|
1873
|
+
getAnswerGiven: () => allFilled,
|
|
1874
|
+
resetTask: reset,
|
|
1875
|
+
showSolutions: () => {
|
|
1876
|
+
},
|
|
1877
|
+
getXAPIData: () => ({
|
|
1878
|
+
checkId,
|
|
1879
|
+
interactionType: INTERACTION4,
|
|
1880
|
+
response: zones,
|
|
1881
|
+
correct: passedThreshold,
|
|
1882
|
+
score,
|
|
1883
|
+
maxScore: handleMax
|
|
1884
|
+
})
|
|
1885
|
+
};
|
|
1886
|
+
}, [allFilled, answers.length, checkId, maxScore, passedThreshold, score, zones]);
|
|
1887
|
+
(0, import_react12.useImperativeHandle)(ref, () => handle, [handle]);
|
|
1888
|
+
useRegisterAssessmentHandle(checkId, handle);
|
|
1889
|
+
const placeInZone = (zoneId, word) => {
|
|
1890
|
+
if (passed && !props.enableRetry) return;
|
|
1891
|
+
const prev = zones[zoneId];
|
|
1892
|
+
setZones((z) => ({ ...z, [zoneId]: word }));
|
|
1893
|
+
setPool((p) => {
|
|
1894
|
+
const next = p.filter((w) => w !== word);
|
|
1895
|
+
if (prev) next.push(prev);
|
|
1896
|
+
return next;
|
|
1897
|
+
});
|
|
1898
|
+
setKeyboardWord(null);
|
|
1899
|
+
};
|
|
1900
|
+
const onDragStart = (word) => (e) => {
|
|
1901
|
+
e.dataTransfer.setData("text/plain", word);
|
|
1902
|
+
};
|
|
1903
|
+
const onDrop = (zoneId) => (e) => {
|
|
1904
|
+
e.preventDefault();
|
|
1905
|
+
const word = e.dataTransfer.getData("text/plain");
|
|
1906
|
+
if (word) placeInZone(zoneId, word);
|
|
1907
|
+
};
|
|
1908
|
+
const check = () => {
|
|
1909
|
+
if (!hasZones) {
|
|
1910
|
+
if (isDevEnvironment4()) {
|
|
1911
|
+
console.warn("[lessonkit] DragTheWords has no drop zones in template");
|
|
1912
|
+
}
|
|
1913
|
+
return;
|
|
1914
|
+
}
|
|
1915
|
+
if (!allFilled) return;
|
|
1916
|
+
if (!answeredRef.current) {
|
|
1917
|
+
answeredRef.current = true;
|
|
1918
|
+
assessment.answer({
|
|
1919
|
+
checkId,
|
|
1920
|
+
interactionType: INTERACTION4,
|
|
1921
|
+
question: props.template,
|
|
1922
|
+
response: zones,
|
|
1923
|
+
correct: passedThreshold
|
|
1924
|
+
});
|
|
1925
|
+
}
|
|
1926
|
+
if (passedThreshold && !completedRef.current) {
|
|
1927
|
+
completedRef.current = true;
|
|
1928
|
+
setPassed(true);
|
|
1929
|
+
assessment.complete({
|
|
1930
|
+
checkId,
|
|
1931
|
+
interactionType: INTERACTION4,
|
|
1932
|
+
score,
|
|
1933
|
+
maxScore,
|
|
1934
|
+
passingScore: props.passingScore ?? maxScore
|
|
1935
|
+
});
|
|
1936
|
+
}
|
|
1937
|
+
};
|
|
1938
|
+
(0, import_react12.useEffect)(() => {
|
|
1939
|
+
if (!allFilled) answeredRef.current = false;
|
|
1940
|
+
}, [allFilled]);
|
|
1941
|
+
(0, import_react12.useEffect)(() => {
|
|
1942
|
+
if (props.autoCheck && allFilled) check();
|
|
1943
|
+
}, [allFilled, props.autoCheck, zones, passedThreshold]);
|
|
1944
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("section", { "aria-label": "Drag the Words", "data-lk-check-id": checkId, children: [
|
|
1945
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { children: "Drag words into the blanks (or select a word, then activate a blank)." }),
|
|
1946
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { role: "list", "aria-label": "Word bank", "data-testid": "word-bank", children: pool.map((word) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
1947
|
+
"button",
|
|
1948
|
+
{
|
|
1949
|
+
type: "button",
|
|
1950
|
+
draggable: true,
|
|
1951
|
+
"data-testid": `word-${word}`,
|
|
1952
|
+
"aria-pressed": keyboardWord === word,
|
|
1953
|
+
onDragStart: onDragStart(word),
|
|
1954
|
+
onClick: () => setKeyboardWord(keyboardWord === word ? null : word),
|
|
1955
|
+
style: { margin: "0.25rem" },
|
|
1956
|
+
children: word
|
|
1957
|
+
},
|
|
1958
|
+
word
|
|
1959
|
+
)) }),
|
|
1960
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { children: parts.map((part, i) => {
|
|
1961
|
+
if (!part.startsWith("zone-")) return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react12.default.Fragment, { children: part }, i);
|
|
1962
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
1963
|
+
"span",
|
|
1964
|
+
{
|
|
1965
|
+
role: "button",
|
|
1966
|
+
tabIndex: 0,
|
|
1967
|
+
"data-testid": part,
|
|
1968
|
+
onDragOver: (e) => e.preventDefault(),
|
|
1969
|
+
onDrop: onDrop(part),
|
|
1970
|
+
onClick: () => keyboardWord && placeInZone(part, keyboardWord),
|
|
1971
|
+
onKeyDown: (e) => {
|
|
1972
|
+
if (e.key === "Enter" && keyboardWord) placeInZone(part, keyboardWord);
|
|
1973
|
+
},
|
|
1974
|
+
style: {
|
|
1975
|
+
display: "inline-block",
|
|
1976
|
+
minWidth: "6em",
|
|
1977
|
+
border: "1px dashed currentColor",
|
|
1978
|
+
padding: "0.2em 0.5em",
|
|
1979
|
+
margin: "0 0.2em"
|
|
1980
|
+
},
|
|
1981
|
+
children: zones[part] || "___"
|
|
1982
|
+
},
|
|
1983
|
+
part
|
|
1984
|
+
);
|
|
1985
|
+
}) }),
|
|
1986
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", "data-testid": "check-drag-words", disabled: !allFilled || passed, onClick: check, children: "Check" }),
|
|
1987
|
+
!hasZones ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { role: "alert", children: "This activity has no drop zones. Wrap answers in asterisks in the template." }) : null,
|
|
1988
|
+
allFilled ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { role: "status", "aria-live": "polite", children: passed || passedThreshold ? "Correct" : "Try again" }) : null
|
|
1989
|
+
] });
|
|
1990
|
+
}
|
|
1991
|
+
var DragTheWordsInnerForwarded = (0, import_react12.forwardRef)(DragTheWordsInner);
|
|
1992
|
+
var DragTheWords = (0, import_react12.forwardRef)(function DragTheWords2(props, ref) {
|
|
1993
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(AssessmentLessonGuard, { blockLabel: "DragTheWords", checkId: props.checkId, children: (lessonId) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(DragTheWordsInnerForwarded, { ...props, enclosingLessonId: lessonId, ref }) });
|
|
1994
|
+
});
|
|
1995
|
+
|
|
1996
|
+
// src/blocks/DragAndDrop.tsx
|
|
1997
|
+
var import_react13 = require("react");
|
|
1998
|
+
var import_jsx_runtime9 = require("react/jsx-runtime");
|
|
1999
|
+
var INTERACTION5 = "dragAndDrop";
|
|
2000
|
+
function DragAndDropInner(props, ref) {
|
|
2001
|
+
const checkId = (0, import_react13.useMemo)(() => normalizeComponentId(props.checkId, "checkId"), [props.checkId]);
|
|
2002
|
+
const assessment = useAssessmentState(props.enclosingLessonId);
|
|
2003
|
+
const [assignments, setAssignments] = (0, import_react13.useState)(
|
|
2004
|
+
() => Object.fromEntries(props.targets.map((t) => [t.id, ""]))
|
|
2005
|
+
);
|
|
2006
|
+
const [pool, setPool] = (0, import_react13.useState)(() => props.items.map((i) => i.id));
|
|
2007
|
+
const [keyboardItem, setKeyboardItem] = (0, import_react13.useState)(null);
|
|
2008
|
+
const [passed, setPassed] = (0, import_react13.useState)(false);
|
|
2009
|
+
const completedRef = (0, import_react13.useRef)(false);
|
|
2010
|
+
const reset = () => {
|
|
2011
|
+
completedRef.current = false;
|
|
2012
|
+
setPassed(false);
|
|
2013
|
+
setAssignments(Object.fromEntries(props.targets.map((t) => [t.id, ""])));
|
|
2014
|
+
setPool(props.items.map((i) => i.id));
|
|
2015
|
+
setKeyboardItem(null);
|
|
2016
|
+
};
|
|
2017
|
+
(0, import_react13.useEffect)(() => {
|
|
2018
|
+
reset();
|
|
2019
|
+
}, [checkId, props.items.map((i) => i.id).join(","), props.targets.map((t) => t.id).join(",")]);
|
|
2020
|
+
const allFilled = props.targets.every((t) => (assignments[t.id] ?? "").length > 0);
|
|
2021
|
+
const allCorrect = props.targets.every((t) => assignments[t.id] === t.accepts);
|
|
2022
|
+
const handle = (0, import_react13.useMemo)(() => {
|
|
2023
|
+
const maxScore = props.targets.length || 1;
|
|
2024
|
+
let score = 0;
|
|
2025
|
+
props.targets.forEach((t) => {
|
|
2026
|
+
if (assignments[t.id] === t.accepts) score += 1;
|
|
2027
|
+
});
|
|
2028
|
+
return {
|
|
2029
|
+
getScore: () => score,
|
|
2030
|
+
getMaxScore: () => maxScore,
|
|
2031
|
+
getAnswerGiven: () => allFilled,
|
|
2032
|
+
resetTask: reset,
|
|
2033
|
+
showSolutions: () => {
|
|
2034
|
+
},
|
|
2035
|
+
getXAPIData: () => ({
|
|
2036
|
+
checkId,
|
|
2037
|
+
interactionType: INTERACTION5,
|
|
2038
|
+
response: assignments,
|
|
2039
|
+
correct: allCorrect,
|
|
2040
|
+
score,
|
|
2041
|
+
maxScore
|
|
2042
|
+
})
|
|
2043
|
+
};
|
|
2044
|
+
}, [allCorrect, allFilled, assignments, checkId, props.targets]);
|
|
2045
|
+
(0, import_react13.useImperativeHandle)(ref, () => handle, [handle]);
|
|
2046
|
+
useRegisterAssessmentHandle(checkId, handle);
|
|
2047
|
+
const place = (targetId, itemId) => {
|
|
2048
|
+
if (passed && !props.enableRetry) return;
|
|
2049
|
+
const prev = assignments[targetId];
|
|
2050
|
+
setAssignments((a) => ({ ...a, [targetId]: itemId }));
|
|
2051
|
+
setPool((p) => {
|
|
2052
|
+
const next = p.filter((id) => id !== itemId);
|
|
2053
|
+
if (prev) next.push(prev);
|
|
2054
|
+
return next;
|
|
2055
|
+
});
|
|
2056
|
+
setKeyboardItem(null);
|
|
2057
|
+
};
|
|
2058
|
+
const check = () => {
|
|
2059
|
+
if (!allFilled) return;
|
|
2060
|
+
assessment.answer({
|
|
2061
|
+
checkId,
|
|
2062
|
+
interactionType: INTERACTION5,
|
|
2063
|
+
response: assignments,
|
|
2064
|
+
correct: allCorrect
|
|
2065
|
+
});
|
|
2066
|
+
if (allCorrect && !completedRef.current) {
|
|
2067
|
+
completedRef.current = true;
|
|
2068
|
+
setPassed(true);
|
|
2069
|
+
assessment.complete({
|
|
2070
|
+
checkId,
|
|
2071
|
+
interactionType: INTERACTION5,
|
|
2072
|
+
score: props.targets.length,
|
|
2073
|
+
maxScore: props.targets.length,
|
|
2074
|
+
passingScore: props.passingScore ?? props.targets.length
|
|
2075
|
+
});
|
|
2076
|
+
}
|
|
2077
|
+
};
|
|
2078
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("section", { "aria-label": "Drag and Drop", "data-lk-check-id": checkId, children: [
|
|
2079
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { children: "Match each item to the correct target (drag or use keyboard: select item, then activate target)." }),
|
|
2080
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { role: "list", "aria-label": "Draggable items", children: pool.map((id) => {
|
|
2081
|
+
const item = props.items.find((i) => i.id === id);
|
|
2082
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
2083
|
+
"button",
|
|
2084
|
+
{
|
|
2085
|
+
type: "button",
|
|
2086
|
+
draggable: true,
|
|
2087
|
+
"data-testid": `drag-item-${id}`,
|
|
2088
|
+
"aria-pressed": keyboardItem === id,
|
|
2089
|
+
onDragStart: (e) => e.dataTransfer.setData("text/plain", id),
|
|
2090
|
+
onClick: () => setKeyboardItem(keyboardItem === id ? null : id),
|
|
2091
|
+
style: { margin: "0.25rem" },
|
|
2092
|
+
children: item.label
|
|
2093
|
+
},
|
|
2094
|
+
id
|
|
2095
|
+
);
|
|
2096
|
+
}) }),
|
|
2097
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("ul", { children: props.targets.map((target) => {
|
|
2098
|
+
const assigned = assignments[target.id];
|
|
2099
|
+
const label = assigned ? props.items.find((i) => i.id === assigned)?.label ?? assigned : "Drop here";
|
|
2100
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("li", { children: [
|
|
2101
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("strong", { children: target.label }),
|
|
2102
|
+
" ",
|
|
2103
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
2104
|
+
"span",
|
|
2105
|
+
{
|
|
2106
|
+
role: "button",
|
|
2107
|
+
tabIndex: 0,
|
|
2108
|
+
"data-testid": `drop-${target.id}`,
|
|
2109
|
+
onDragOver: (e) => e.preventDefault(),
|
|
2110
|
+
onDrop: (e) => {
|
|
2111
|
+
e.preventDefault();
|
|
2112
|
+
const id = e.dataTransfer.getData("text/plain");
|
|
2113
|
+
if (id) place(target.id, id);
|
|
2114
|
+
},
|
|
2115
|
+
onClick: () => keyboardItem && place(target.id, keyboardItem),
|
|
2116
|
+
onKeyDown: (e) => {
|
|
2117
|
+
if (e.key === "Enter" && keyboardItem) place(target.id, keyboardItem);
|
|
2118
|
+
},
|
|
2119
|
+
style: {
|
|
2120
|
+
display: "inline-block",
|
|
2121
|
+
minWidth: "8em",
|
|
2122
|
+
border: "1px dashed currentColor",
|
|
2123
|
+
padding: "0.25em"
|
|
2124
|
+
},
|
|
2125
|
+
children: label
|
|
2126
|
+
}
|
|
2127
|
+
)
|
|
2128
|
+
] }, target.id);
|
|
2129
|
+
}) }),
|
|
2130
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { type: "button", "data-testid": "check-drag-drop", disabled: !allFilled || passed, onClick: check, children: "Check" }),
|
|
2131
|
+
allFilled ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { role: "status", "aria-live": "polite", children: passed || allCorrect ? "Correct" : "Try again" }) : null
|
|
2132
|
+
] });
|
|
2133
|
+
}
|
|
2134
|
+
var DragAndDropInnerForwarded = (0, import_react13.forwardRef)(DragAndDropInner);
|
|
2135
|
+
var DragAndDrop = (0, import_react13.forwardRef)(function DragAndDrop2(props, ref) {
|
|
2136
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(AssessmentLessonGuard, { blockLabel: "DragAndDrop", checkId: props.checkId, children: (lessonId) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(DragAndDropInnerForwarded, { ...props, enclosingLessonId: lessonId, ref }) });
|
|
2137
|
+
});
|
|
2138
|
+
|
|
2139
|
+
// src/blocks/AssessmentSequence.tsx
|
|
2140
|
+
var import_react14 = __toESM(require("react"), 1);
|
|
2141
|
+
var import_jsx_runtime10 = require("react/jsx-runtime");
|
|
2142
|
+
function AssessmentSequence(props) {
|
|
2143
|
+
const sequential = props.sequential !== false;
|
|
2144
|
+
const childArray = import_react14.default.Children.toArray(props.children).filter(import_react14.default.isValidElement);
|
|
2145
|
+
const [index, setIndex] = (0, import_react14.useState)(0);
|
|
2146
|
+
const current = childArray[index] ?? null;
|
|
2147
|
+
const goNext = (0, import_react14.useCallback)(() => {
|
|
2148
|
+
setIndex((i) => Math.min(i + 1, childArray.length - 1));
|
|
2149
|
+
}, [childArray.length]);
|
|
2150
|
+
const goPrev = (0, import_react14.useCallback)(() => {
|
|
2151
|
+
setIndex((i) => Math.max(i - 1, 0));
|
|
2152
|
+
}, []);
|
|
2153
|
+
const progress = (0, import_react14.useMemo)(
|
|
2154
|
+
() => ({ current: index + 1, total: childArray.length }),
|
|
2155
|
+
[index, childArray.length]
|
|
2156
|
+
);
|
|
2157
|
+
if (!sequential) {
|
|
2158
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(AssessmentSequenceProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("section", { "aria-label": "Assessment sequence", children: props.children }) });
|
|
2159
|
+
}
|
|
2160
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(AssessmentSequenceProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("section", { "aria-label": "Assessment sequence", "data-testid": "assessment-sequence", children: [
|
|
2161
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("p", { children: [
|
|
2162
|
+
"Question ",
|
|
2163
|
+
progress.current,
|
|
2164
|
+
" of ",
|
|
2165
|
+
progress.total
|
|
2166
|
+
] }),
|
|
2167
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { "data-testid": "assessment-sequence-step", children: current }),
|
|
2168
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("nav", { "aria-label": "Sequence navigation", children: [
|
|
2169
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { type: "button", "data-testid": "sequence-prev", disabled: index === 0, onClick: goPrev, children: "Previous" }),
|
|
2170
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
2171
|
+
"button",
|
|
2172
|
+
{
|
|
2173
|
+
type: "button",
|
|
2174
|
+
"data-testid": "sequence-next",
|
|
2175
|
+
disabled: index >= childArray.length - 1,
|
|
2176
|
+
onClick: goNext,
|
|
2177
|
+
children: "Next"
|
|
2178
|
+
}
|
|
2179
|
+
)
|
|
2180
|
+
] })
|
|
2181
|
+
] }) });
|
|
2182
|
+
}
|
|
2183
|
+
|
|
1283
2184
|
// src/index.tsx
|
|
1284
2185
|
var import_core10 = require("@lessonkit/core");
|
|
1285
2186
|
|
|
1286
2187
|
// src/theme/ThemeProvider.tsx
|
|
1287
|
-
var
|
|
2188
|
+
var import_react15 = __toESM(require("react"), 1);
|
|
1288
2189
|
var import_themes = require("@lessonkit/themes");
|
|
1289
2190
|
|
|
1290
2191
|
// src/theme/applyCssVariables.ts
|
|
@@ -1303,9 +2204,12 @@ function applyCssVariables(target, vars, previousKeys) {
|
|
|
1303
2204
|
}
|
|
1304
2205
|
|
|
1305
2206
|
// src/theme/ThemeProvider.tsx
|
|
1306
|
-
var
|
|
1307
|
-
var ThemeContext = (0,
|
|
1308
|
-
var useIsoLayoutEffect2 =
|
|
2207
|
+
var import_jsx_runtime11 = require("react/jsx-runtime");
|
|
2208
|
+
var ThemeContext = (0, import_react15.createContext)(null);
|
|
2209
|
+
var useIsoLayoutEffect2 = (
|
|
2210
|
+
/* v8 ignore next -- SSR uses useEffect when window is unavailable */
|
|
2211
|
+
typeof window !== "undefined" ? import_react15.useLayoutEffect : import_react15.default.useEffect
|
|
2212
|
+
);
|
|
1309
2213
|
function getSystemMode() {
|
|
1310
2214
|
if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
|
|
1311
2215
|
return "light";
|
|
@@ -1323,7 +2227,7 @@ function ThemeProvider(props) {
|
|
|
1323
2227
|
const preset = props.preset ?? "default";
|
|
1324
2228
|
const mode = props.mode ?? "light";
|
|
1325
2229
|
const targetKind = props.target ?? "document";
|
|
1326
|
-
const [resolvedMode, setResolvedMode] = (0,
|
|
2230
|
+
const [resolvedMode, setResolvedMode] = (0, import_react15.useState)(
|
|
1327
2231
|
() => mode === "system" ? getSystemMode() : mode
|
|
1328
2232
|
);
|
|
1329
2233
|
useIsoLayoutEffect2(() => {
|
|
@@ -1339,20 +2243,20 @@ function ThemeProvider(props) {
|
|
|
1339
2243
|
return () => mq.removeEventListener("change", onChange);
|
|
1340
2244
|
}, [mode]);
|
|
1341
2245
|
const dataTheme = mode === "system" ? resolvedMode : mode === "dark" ? "dark" : "light";
|
|
1342
|
-
const effectiveTheme = (0,
|
|
2246
|
+
const effectiveTheme = (0, import_react15.useMemo)(() => {
|
|
1343
2247
|
const modeBase = resolveModeBase(mode, dataTheme);
|
|
1344
2248
|
const base = preset === "default" ? modeBase : preset === "brand" ? (0, import_themes.mergeThemes)(modeBase, import_themes.brandThemeOverrides) : (0, import_themes.mergeThemes)(modeBase, (0, import_themes.getPresetTheme)(preset));
|
|
1345
2249
|
return (0, import_themes.mergeThemes)(base, props.theme ?? {});
|
|
1346
2250
|
}, [preset, mode, dataTheme, props.theme]);
|
|
1347
|
-
const hostRef = (0,
|
|
1348
|
-
const appliedKeysRef = (0,
|
|
2251
|
+
const hostRef = (0, import_react15.useRef)(null);
|
|
2252
|
+
const appliedKeysRef = (0, import_react15.useRef)(/* @__PURE__ */ new Set());
|
|
1349
2253
|
useIsoLayoutEffect2(() => {
|
|
1350
2254
|
if (targetKind === "document" && typeof document !== "undefined") {
|
|
1351
2255
|
document.documentElement.setAttribute("data-lk-theme", dataTheme);
|
|
1352
2256
|
return () => document.documentElement.removeAttribute("data-lk-theme");
|
|
1353
2257
|
}
|
|
1354
2258
|
}, [targetKind, dataTheme]);
|
|
1355
|
-
const inject = (0,
|
|
2259
|
+
const inject = (0, import_react15.useCallback)(() => {
|
|
1356
2260
|
const vars = (0, import_themes.themeToCssVariables)(effectiveTheme);
|
|
1357
2261
|
const el = targetKind === "document" && typeof document !== "undefined" ? document.documentElement : hostRef.current;
|
|
1358
2262
|
if (!el) return;
|
|
@@ -1369,7 +2273,7 @@ function ThemeProvider(props) {
|
|
|
1369
2273
|
appliedKeysRef.current = /* @__PURE__ */ new Set();
|
|
1370
2274
|
};
|
|
1371
2275
|
}, [inject, targetKind]);
|
|
1372
|
-
const value = (0,
|
|
2276
|
+
const value = (0, import_react15.useMemo)(
|
|
1373
2277
|
() => ({
|
|
1374
2278
|
theme: effectiveTheme,
|
|
1375
2279
|
preset,
|
|
@@ -1379,12 +2283,12 @@ function ThemeProvider(props) {
|
|
|
1379
2283
|
[effectiveTheme, preset, mode, dataTheme]
|
|
1380
2284
|
);
|
|
1381
2285
|
if (targetKind === "document") {
|
|
1382
|
-
return /* @__PURE__ */ (0,
|
|
2286
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(ThemeContext.Provider, { value, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { "data-lk-theme": dataTheme, style: { display: "contents" }, children: props.children }) });
|
|
1383
2287
|
}
|
|
1384
|
-
return /* @__PURE__ */ (0,
|
|
2288
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(ThemeContext.Provider, { value, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { ref: hostRef, "data-lk-theme": dataTheme, children: props.children }) });
|
|
1385
2289
|
}
|
|
1386
2290
|
function useTheme() {
|
|
1387
|
-
const ctx = (0,
|
|
2291
|
+
const ctx = (0, import_react15.useContext)(ThemeContext);
|
|
1388
2292
|
if (!ctx) {
|
|
1389
2293
|
throw new Error("useTheme must be used within a ThemeProvider");
|
|
1390
2294
|
}
|
|
@@ -1393,6 +2297,7 @@ function useTheme() {
|
|
|
1393
2297
|
|
|
1394
2298
|
// src/blockCatalog.ts
|
|
1395
2299
|
var blockCatalogVersion = 1;
|
|
2300
|
+
var blockCatalogV2Version = 2;
|
|
1396
2301
|
var BLOCK_CATALOG = [
|
|
1397
2302
|
{
|
|
1398
2303
|
type: "Course",
|
|
@@ -1579,8 +2484,163 @@ var BLOCK_CATALOG = [
|
|
|
1579
2484
|
}
|
|
1580
2485
|
}
|
|
1581
2486
|
];
|
|
1582
|
-
|
|
1583
|
-
|
|
2487
|
+
var assessmentBehaviourProps = [
|
|
2488
|
+
{ name: "enableRetry", type: "boolean", required: false, description: "Allow retry after completion." },
|
|
2489
|
+
{ name: "enableSolutionsButton", type: "boolean", required: false, description: "Show solution control." },
|
|
2490
|
+
{ name: "autoCheck", type: "boolean", required: false, description: "Check answers automatically when possible." },
|
|
2491
|
+
{ name: "passingScore", type: "number", required: false, description: "Minimum score to pass." }
|
|
2492
|
+
];
|
|
2493
|
+
var v2AssessmentEntries = [
|
|
2494
|
+
{
|
|
2495
|
+
type: "TrueFalse",
|
|
2496
|
+
category: "assessment",
|
|
2497
|
+
assessmentContract: true,
|
|
2498
|
+
h5pMachineName: "H5P.TrueFalse",
|
|
2499
|
+
h5pAlias: "True/False",
|
|
2500
|
+
description: "Binary true/false question with assessment contract.",
|
|
2501
|
+
props: [
|
|
2502
|
+
{ name: "checkId", type: "CheckId", required: true, description: "Stable check id." },
|
|
2503
|
+
{ name: "question", type: "string", required: true, description: "Question text." },
|
|
2504
|
+
{ name: "answer", type: "boolean", required: true, description: "Correct answer." },
|
|
2505
|
+
...assessmentBehaviourProps
|
|
2506
|
+
],
|
|
2507
|
+
requiredIds: ["checkId"],
|
|
2508
|
+
parentConstraints: ["Lesson", "AssessmentSequence"],
|
|
2509
|
+
a11y: {
|
|
2510
|
+
element: "section",
|
|
2511
|
+
ariaLabel: "True or False",
|
|
2512
|
+
keyboard: "Radio group with True/False options.",
|
|
2513
|
+
liveRegions: "role='status' for feedback.",
|
|
2514
|
+
notes: "H5P True/False equivalent."
|
|
2515
|
+
},
|
|
2516
|
+
theming: { surface: "global-inherit", dataAttributes: ["data-lk-check-id"], stylingNotes: "Uses data-lk-check-id." },
|
|
2517
|
+
telemetry: { emits: ["assessment_answered", "assessment_completed"], requiresActiveLesson: true }
|
|
2518
|
+
},
|
|
2519
|
+
{
|
|
2520
|
+
type: "FillInTheBlanks",
|
|
2521
|
+
category: "assessment",
|
|
2522
|
+
assessmentContract: true,
|
|
2523
|
+
h5pMachineName: "H5P.Blanks",
|
|
2524
|
+
h5pAlias: "Fill in the Blanks",
|
|
2525
|
+
description: "Fill-in-the-blank text with *answer* markers in template.",
|
|
2526
|
+
props: [
|
|
2527
|
+
{ name: "checkId", type: "CheckId", required: true, description: "Stable check id." },
|
|
2528
|
+
{ name: "template", type: "string", required: true, description: "Text with *blank* markers." },
|
|
2529
|
+
{ name: "blanks", type: "FillInBlankSpec[]", required: false, description: "Explicit blank specs." },
|
|
2530
|
+
...assessmentBehaviourProps
|
|
2531
|
+
],
|
|
2532
|
+
requiredIds: ["checkId"],
|
|
2533
|
+
parentConstraints: ["Lesson", "AssessmentSequence"],
|
|
2534
|
+
a11y: {
|
|
2535
|
+
element: "section",
|
|
2536
|
+
ariaLabel: "Fill in the Blanks",
|
|
2537
|
+
keyboard: "Tab between text inputs.",
|
|
2538
|
+
notes: "H5P Fill in the Blanks equivalent."
|
|
2539
|
+
},
|
|
2540
|
+
theming: { surface: "global-inherit", dataAttributes: ["data-lk-check-id"], stylingNotes: "Uses data-lk-check-id." },
|
|
2541
|
+
telemetry: { emits: ["assessment_answered", "assessment_completed"], requiresActiveLesson: true }
|
|
2542
|
+
},
|
|
2543
|
+
{
|
|
2544
|
+
type: "DragAndDrop",
|
|
2545
|
+
category: "assessment",
|
|
2546
|
+
assessmentContract: true,
|
|
2547
|
+
h5pMachineName: "H5P.DragQuestion",
|
|
2548
|
+
h5pAlias: "Drag and Drop",
|
|
2549
|
+
description: "Drag items onto labeled targets.",
|
|
2550
|
+
props: [
|
|
2551
|
+
{ name: "checkId", type: "CheckId", required: true, description: "Stable check id." },
|
|
2552
|
+
{ name: "items", type: "DragItem[]", required: true, description: "Draggable items." },
|
|
2553
|
+
{ name: "targets", type: "DropTarget[]", required: true, description: "Drop targets." },
|
|
2554
|
+
...assessmentBehaviourProps
|
|
2555
|
+
],
|
|
2556
|
+
requiredIds: ["checkId"],
|
|
2557
|
+
parentConstraints: ["Lesson", "AssessmentSequence"],
|
|
2558
|
+
a11y: {
|
|
2559
|
+
element: "section",
|
|
2560
|
+
ariaLabel: "Drag and Drop",
|
|
2561
|
+
keyboard: "Select item then activate target; drag also supported.",
|
|
2562
|
+
notes: "H5P Drag and Drop equivalent."
|
|
2563
|
+
},
|
|
2564
|
+
theming: { surface: "global-inherit", dataAttributes: ["data-lk-check-id"], stylingNotes: "Uses data-lk-check-id." },
|
|
2565
|
+
telemetry: { emits: ["assessment_answered", "assessment_completed"], requiresActiveLesson: true }
|
|
2566
|
+
},
|
|
2567
|
+
{
|
|
2568
|
+
type: "DragTheWords",
|
|
2569
|
+
category: "assessment",
|
|
2570
|
+
assessmentContract: true,
|
|
2571
|
+
h5pMachineName: "H5P.DragText",
|
|
2572
|
+
h5pAlias: "Drag the Words",
|
|
2573
|
+
description: "Drag words into inline blanks.",
|
|
2574
|
+
props: [
|
|
2575
|
+
{ name: "checkId", type: "CheckId", required: true, description: "Stable check id." },
|
|
2576
|
+
{ name: "template", type: "string", required: true, description: "Sentence with *blank* zones." },
|
|
2577
|
+
{ name: "words", type: "string[]", required: true, description: "Draggable word bank." },
|
|
2578
|
+
...assessmentBehaviourProps
|
|
2579
|
+
],
|
|
2580
|
+
requiredIds: ["checkId"],
|
|
2581
|
+
parentConstraints: ["Lesson", "AssessmentSequence"],
|
|
2582
|
+
a11y: {
|
|
2583
|
+
element: "section",
|
|
2584
|
+
ariaLabel: "Drag the Words",
|
|
2585
|
+
keyboard: "Select word then activate zone.",
|
|
2586
|
+
notes: "H5P Drag the Words equivalent."
|
|
2587
|
+
},
|
|
2588
|
+
theming: { surface: "global-inherit", dataAttributes: ["data-lk-check-id"], stylingNotes: "Uses data-lk-check-id." },
|
|
2589
|
+
telemetry: { emits: ["assessment_answered", "assessment_completed"], requiresActiveLesson: true }
|
|
2590
|
+
},
|
|
2591
|
+
{
|
|
2592
|
+
type: "MarkTheWords",
|
|
2593
|
+
category: "assessment",
|
|
2594
|
+
assessmentContract: true,
|
|
2595
|
+
h5pMachineName: "H5P.MarkTheWords",
|
|
2596
|
+
h5pAlias: "Mark the Words",
|
|
2597
|
+
description: "Select correct words in a sentence.",
|
|
2598
|
+
props: [
|
|
2599
|
+
{ name: "checkId", type: "CheckId", required: true, description: "Stable check id." },
|
|
2600
|
+
{ name: "text", type: "string", required: true, description: "Source text." },
|
|
2601
|
+
{ name: "correctWords", type: "string[]", required: true, description: "Words to mark." },
|
|
2602
|
+
...assessmentBehaviourProps
|
|
2603
|
+
],
|
|
2604
|
+
requiredIds: ["checkId"],
|
|
2605
|
+
parentConstraints: ["Lesson", "AssessmentSequence"],
|
|
2606
|
+
a11y: {
|
|
2607
|
+
element: "section",
|
|
2608
|
+
ariaLabel: "Mark the Words",
|
|
2609
|
+
keyboard: "Toggle words with buttons.",
|
|
2610
|
+
notes: "H5P Mark the Words equivalent."
|
|
2611
|
+
},
|
|
2612
|
+
theming: { surface: "global-inherit", dataAttributes: ["data-lk-check-id"], stylingNotes: "Uses data-lk-check-id." },
|
|
2613
|
+
telemetry: { emits: ["assessment_answered", "assessment_completed"], requiresActiveLesson: true }
|
|
2614
|
+
},
|
|
2615
|
+
{
|
|
2616
|
+
type: "AssessmentSequence",
|
|
2617
|
+
category: "container",
|
|
2618
|
+
h5pMachineName: "H5P.QuestionSet",
|
|
2619
|
+
h5pAlias: "Question Set",
|
|
2620
|
+
description: "Ordered sequence of contract-compliant assessments.",
|
|
2621
|
+
props: [
|
|
2622
|
+
{ name: "children", type: "ReactNode", required: true, description: "Assessment blocks." },
|
|
2623
|
+
{ name: "sequential", type: "boolean", required: false, description: "One question at a time." },
|
|
2624
|
+
...assessmentBehaviourProps.filter((p) => p.name !== "passingScore")
|
|
2625
|
+
],
|
|
2626
|
+
requiredIds: [],
|
|
2627
|
+
parentConstraints: ["Lesson"],
|
|
2628
|
+
a11y: {
|
|
2629
|
+
element: "section",
|
|
2630
|
+
ariaLabel: "Assessment sequence",
|
|
2631
|
+
keyboard: "Previous/Next navigation between steps.",
|
|
2632
|
+
notes: "H5P Question Set equivalent."
|
|
2633
|
+
},
|
|
2634
|
+
theming: { surface: "global-inherit", stylingNotes: "Container for assessments." },
|
|
2635
|
+
telemetry: { emits: [], manualTracking: "Child assessments emit assessment_* events." }
|
|
2636
|
+
}
|
|
2637
|
+
];
|
|
2638
|
+
var BLOCK_CATALOG_V2 = [
|
|
2639
|
+
...BLOCK_CATALOG,
|
|
2640
|
+
...v2AssessmentEntries
|
|
2641
|
+
];
|
|
2642
|
+
function cloneCatalogEntry(entry) {
|
|
2643
|
+
return {
|
|
1584
2644
|
...entry,
|
|
1585
2645
|
props: entry.props.map((p) => ({ ...p })),
|
|
1586
2646
|
aliases: entry.aliases ? [...entry.aliases] : void 0,
|
|
@@ -1595,23 +2655,38 @@ function buildBlockCatalog() {
|
|
|
1595
2655
|
...entry.telemetry,
|
|
1596
2656
|
emits: [...entry.telemetry.emits]
|
|
1597
2657
|
}
|
|
1598
|
-
}
|
|
2658
|
+
};
|
|
2659
|
+
}
|
|
2660
|
+
function buildBlockCatalog(opts) {
|
|
2661
|
+
const version = opts?.version ?? 2;
|
|
2662
|
+
const source = version === 2 ? BLOCK_CATALOG_V2 : BLOCK_CATALOG;
|
|
2663
|
+
return source.map((entry) => cloneCatalogEntry(entry));
|
|
1599
2664
|
}
|
|
1600
|
-
function getBlockCatalogEntry(type) {
|
|
1601
|
-
|
|
2665
|
+
function getBlockCatalogEntry(type, opts) {
|
|
2666
|
+
const version = opts?.version ?? 2;
|
|
2667
|
+
const source = version === 2 ? BLOCK_CATALOG_V2 : BLOCK_CATALOG;
|
|
2668
|
+
return source.find((entry) => entry.type === type || entry.aliases?.includes(type));
|
|
1602
2669
|
}
|
|
1603
2670
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1604
2671
|
0 && (module.exports = {
|
|
2672
|
+
AssessmentSequence,
|
|
1605
2673
|
BLOCK_CATALOG,
|
|
2674
|
+
BLOCK_CATALOG_V2,
|
|
1606
2675
|
Course,
|
|
2676
|
+
DragAndDrop,
|
|
2677
|
+
DragTheWords,
|
|
2678
|
+
FillInTheBlanks,
|
|
1607
2679
|
KnowledgeCheck,
|
|
1608
2680
|
Lesson,
|
|
1609
2681
|
LessonkitProvider,
|
|
2682
|
+
MarkTheWords,
|
|
1610
2683
|
ProgressTracker,
|
|
1611
2684
|
Quiz,
|
|
1612
2685
|
Reflection,
|
|
1613
2686
|
Scenario,
|
|
1614
2687
|
ThemeProvider,
|
|
2688
|
+
TrueFalse,
|
|
2689
|
+
blockCatalogV2Version,
|
|
1615
2690
|
blockCatalogVersion,
|
|
1616
2691
|
buildBlockCatalog,
|
|
1617
2692
|
buildTelemetryEvent,
|
|
@@ -1622,7 +2697,9 @@ function getBlockCatalogEntry(type) {
|
|
|
1622
2697
|
defineLifecyclePlugin,
|
|
1623
2698
|
defineTelemetryPlugin,
|
|
1624
2699
|
getBlockCatalogEntry,
|
|
2700
|
+
resetAssessmentWarningsForTests,
|
|
1625
2701
|
resetQuizWarningsForTests,
|
|
2702
|
+
useAssessmentState,
|
|
1626
2703
|
useCompletion,
|
|
1627
2704
|
useLessonkit,
|
|
1628
2705
|
useProgress,
|