@lessonkit/react 1.2.0 → 1.3.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 +180 -5
- package/dist/index.cjs +1246 -558
- package/dist/index.d.cts +26 -2
- package/dist/index.d.ts +26 -2
- package/dist/index.js +1080 -392
- package/package.json +6 -6
package/dist/index.cjs
CHANGED
|
@@ -57,6 +57,8 @@ __export(index_exports, {
|
|
|
57
57
|
Quiz: () => Quiz,
|
|
58
58
|
Reflection: () => Reflection,
|
|
59
59
|
Scenario: () => Scenario,
|
|
60
|
+
Slide: () => Slide,
|
|
61
|
+
SlideDeck: () => SlideDeck,
|
|
60
62
|
Text: () => Text,
|
|
61
63
|
ThemeProvider: () => ThemeProvider,
|
|
62
64
|
TrueFalse: () => TrueFalse,
|
|
@@ -64,13 +66,13 @@ __export(index_exports, {
|
|
|
64
66
|
blockCatalogV3Version: () => blockCatalogV3Version,
|
|
65
67
|
blockCatalogVersion: () => blockCatalogVersion,
|
|
66
68
|
buildBlockCatalog: () => buildBlockCatalog,
|
|
67
|
-
buildTelemetryEvent: () =>
|
|
68
|
-
createLessonkitRuntime: () =>
|
|
69
|
-
createPluginRegistry: () =>
|
|
70
|
-
createTelemetryPipeline: () =>
|
|
71
|
-
defineAssessmentPlugin: () =>
|
|
72
|
-
defineLifecyclePlugin: () =>
|
|
73
|
-
defineTelemetryPlugin: () =>
|
|
69
|
+
buildTelemetryEvent: () => import_core19.buildTelemetryEvent,
|
|
70
|
+
createLessonkitRuntime: () => import_core19.createLessonkitRuntime,
|
|
71
|
+
createPluginRegistry: () => import_core19.createPluginRegistry,
|
|
72
|
+
createTelemetryPipeline: () => import_core19.createTelemetryPipeline,
|
|
73
|
+
defineAssessmentPlugin: () => import_core19.defineAssessmentPlugin,
|
|
74
|
+
defineLifecyclePlugin: () => import_core19.defineLifecyclePlugin,
|
|
75
|
+
defineTelemetryPlugin: () => import_core19.defineTelemetryPlugin,
|
|
74
76
|
getBlockCatalogEntry: () => getBlockCatalogEntry,
|
|
75
77
|
resetAssessmentWarningsForTests: () => resetAssessmentWarningsForTests,
|
|
76
78
|
resetQuizWarningsForTests: () => resetQuizWarningsForTests,
|
|
@@ -85,7 +87,7 @@ __export(index_exports, {
|
|
|
85
87
|
module.exports = __toCommonJS(index_exports);
|
|
86
88
|
|
|
87
89
|
// src/components.tsx
|
|
88
|
-
var
|
|
90
|
+
var import_react13 = require("react");
|
|
89
91
|
var import_accessibility2 = require("@lessonkit/accessibility");
|
|
90
92
|
|
|
91
93
|
// src/context.tsx
|
|
@@ -216,6 +218,9 @@ function emitTelemetry(tracking, xapi, event, opts) {
|
|
|
216
218
|
// src/runtime/ports.ts
|
|
217
219
|
var import_core3 = require("@lessonkit/core");
|
|
218
220
|
|
|
221
|
+
// src/provider/useLessonkitProviderRuntime.ts
|
|
222
|
+
var import_core9 = require("@lessonkit/core");
|
|
223
|
+
|
|
219
224
|
// src/runtime/progress.ts
|
|
220
225
|
var import_core4 = require("@lessonkit/core");
|
|
221
226
|
|
|
@@ -348,9 +353,10 @@ async function emitCourseStartedToTracking(tracking, storage, sessionId, courseI
|
|
|
348
353
|
try {
|
|
349
354
|
if (shouldCommit && !shouldCommit()) return false;
|
|
350
355
|
tracking.track(event);
|
|
351
|
-
await tracking.flush?.();
|
|
352
|
-
if (shouldCommit && !shouldCommit()) return false;
|
|
353
356
|
(0, import_core5.markCourseStartedEmittedToTracking)(storage, sessionId, courseId);
|
|
357
|
+
const delivered = await tracking.flush?.();
|
|
358
|
+
if (delivered === false) return false;
|
|
359
|
+
if (shouldCommit && !shouldCommit()) return false;
|
|
354
360
|
return true;
|
|
355
361
|
} catch {
|
|
356
362
|
return false;
|
|
@@ -477,13 +483,14 @@ function assertTrackingSinkConfig(tracking) {
|
|
|
477
483
|
|
|
478
484
|
// src/runtime/telemetry.ts
|
|
479
485
|
var import_core7 = require("@lessonkit/core");
|
|
480
|
-
function createTrackingClientFromConfig(config) {
|
|
486
|
+
function createTrackingClientFromConfig(config, observability) {
|
|
481
487
|
if (config.tracking?.enabled === false) return (0, import_core7.createTrackingClient)();
|
|
482
488
|
if (config.tracking?.createClient) return config.tracking.createClient();
|
|
483
489
|
return (0, import_core7.createTrackingClient)({
|
|
484
490
|
sink: config.tracking?.sink,
|
|
485
491
|
batchSink: config.tracking?.batchSink,
|
|
486
|
-
batch: config.tracking?.batch
|
|
492
|
+
batch: config.tracking?.batch,
|
|
493
|
+
onBufferDrop: observability?.onTelemetryBufferDrop
|
|
487
494
|
});
|
|
488
495
|
}
|
|
489
496
|
async function disposeTrackingClient(client) {
|
|
@@ -565,7 +572,8 @@ function useLessonkitProviderRuntime(config) {
|
|
|
565
572
|
courseId: normalizedCourseId,
|
|
566
573
|
runtimeVersion: "v2",
|
|
567
574
|
session: normalizedConfig.session,
|
|
568
|
-
plugins: pluginHostRef.current ?? normalizedConfig.plugins
|
|
575
|
+
plugins: pluginHostRef.current ?? normalizedConfig.plugins,
|
|
576
|
+
deferPluginSetup: true
|
|
569
577
|
});
|
|
570
578
|
progressRef.current = headlessRef.current.progress;
|
|
571
579
|
} else {
|
|
@@ -580,7 +588,8 @@ function useLessonkitProviderRuntime(config) {
|
|
|
580
588
|
courseId: normalizedCourseId,
|
|
581
589
|
runtimeVersion: "v2",
|
|
582
590
|
session: normalizedConfig.session,
|
|
583
|
-
plugins: pluginHostRef.current ?? normalizedConfig.plugins
|
|
591
|
+
plugins: pluginHostRef.current ?? normalizedConfig.plugins,
|
|
592
|
+
deferPluginSetup: true
|
|
584
593
|
});
|
|
585
594
|
}
|
|
586
595
|
if (prevCourseIdForProgressRef.current !== normalizedCourseId) {
|
|
@@ -725,9 +734,12 @@ function useLessonkitProviderRuntime(config) {
|
|
|
725
734
|
}
|
|
726
735
|
return userBatchSink(perEventForBatch);
|
|
727
736
|
} : userBatchSink;
|
|
728
|
-
const next = createTrackingClientFromConfig(
|
|
729
|
-
|
|
730
|
-
|
|
737
|
+
const next = createTrackingClientFromConfig(
|
|
738
|
+
{
|
|
739
|
+
tracking: { ...normalizedConfig.tracking, sink, batchSink }
|
|
740
|
+
},
|
|
741
|
+
observabilityRef.current
|
|
742
|
+
);
|
|
731
743
|
trackingRef.current = next;
|
|
732
744
|
trackingClientForUnmountRef.current = next;
|
|
733
745
|
setTracking(next);
|
|
@@ -1045,6 +1057,7 @@ function useLessonkitProviderRuntime(config) {
|
|
|
1045
1057
|
config: normalizedConfig,
|
|
1046
1058
|
tracking,
|
|
1047
1059
|
xapi,
|
|
1060
|
+
storage: defaultStorage,
|
|
1048
1061
|
session: { sessionId: sessionIdRef.current, attemptId: attemptIdRef.current, user: userRef.current },
|
|
1049
1062
|
progress,
|
|
1050
1063
|
setActiveLesson,
|
|
@@ -1142,17 +1155,17 @@ function useEnclosingLessonId() {
|
|
|
1142
1155
|
}
|
|
1143
1156
|
|
|
1144
1157
|
// src/runtime/validateComponentId.ts
|
|
1145
|
-
var
|
|
1158
|
+
var import_core10 = require("@lessonkit/core");
|
|
1146
1159
|
function isDevEnvironment4() {
|
|
1147
1160
|
const g = globalThis;
|
|
1148
1161
|
return typeof g.process !== "undefined" && g.process.env?.NODE_ENV !== "production";
|
|
1149
1162
|
}
|
|
1150
1163
|
function normalizeComponentId(id, path) {
|
|
1151
|
-
if (path === "courseId") return (0,
|
|
1152
|
-
if (path === "lessonId") return (0,
|
|
1153
|
-
if (path === "checkId") return (0,
|
|
1154
|
-
if (path === "blockId") return (0,
|
|
1155
|
-
return (0,
|
|
1164
|
+
if (path === "courseId") return (0, import_core10.assertValidId)(id, "courseId");
|
|
1165
|
+
if (path === "lessonId") return (0, import_core10.assertValidId)(id, "lessonId");
|
|
1166
|
+
if (path === "checkId") return (0, import_core10.assertValidId)(id, "checkId");
|
|
1167
|
+
if (path === "blockId") return (0, import_core10.assertValidId)(id, "blockId");
|
|
1168
|
+
return (0, import_core10.assertValidId)(id, path);
|
|
1156
1169
|
}
|
|
1157
1170
|
|
|
1158
1171
|
// src/runtime/lessonMountRegistry.ts
|
|
@@ -1180,7 +1193,7 @@ function getLessonMountCount(lessonId) {
|
|
|
1180
1193
|
}
|
|
1181
1194
|
|
|
1182
1195
|
// src/components/Quiz.tsx
|
|
1183
|
-
var
|
|
1196
|
+
var import_react12 = require("react");
|
|
1184
1197
|
var import_accessibility = require("@lessonkit/accessibility");
|
|
1185
1198
|
|
|
1186
1199
|
// src/assessment/AssessmentLessonGuard.tsx
|
|
@@ -1239,140 +1252,184 @@ function readStringField(state, key) {
|
|
|
1239
1252
|
if (typeof value === "string" || value === null) return value;
|
|
1240
1253
|
return void 0;
|
|
1241
1254
|
}
|
|
1255
|
+
function readNumberField(state, key) {
|
|
1256
|
+
const value = state[key];
|
|
1257
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
1258
|
+
if (value === null) return null;
|
|
1259
|
+
return void 0;
|
|
1260
|
+
}
|
|
1242
1261
|
function readBooleanStateField(state, key, apply) {
|
|
1243
1262
|
const value = state[key];
|
|
1244
1263
|
if (typeof value === "boolean") apply(value);
|
|
1245
1264
|
}
|
|
1246
1265
|
|
|
1247
1266
|
// src/assessment/internal/useAssessmentHandleRegistration.ts
|
|
1248
|
-
var
|
|
1267
|
+
var import_react10 = require("react");
|
|
1249
1268
|
|
|
1250
1269
|
// src/compound/CompoundProvider.tsx
|
|
1251
|
-
var
|
|
1252
|
-
var
|
|
1270
|
+
var import_react9 = __toESM(require("react"), 1);
|
|
1271
|
+
var import_core11 = require("@lessonkit/core");
|
|
1253
1272
|
|
|
1254
1273
|
// src/compound/aggregateScores.ts
|
|
1255
|
-
function aggregateAssessmentScores(handles) {
|
|
1274
|
+
function aggregateAssessmentScores(handles, opts) {
|
|
1256
1275
|
let score = 0;
|
|
1257
1276
|
let maxScore = 0;
|
|
1258
1277
|
let allAnswered = true;
|
|
1259
|
-
for (const
|
|
1278
|
+
for (const entry of handles) {
|
|
1279
|
+
const handle = "handle" in entry ? entry.handle : entry;
|
|
1280
|
+
const pageIndex = "handle" in entry ? entry.pageIndex : void 0;
|
|
1260
1281
|
score += handle.getScore();
|
|
1261
1282
|
maxScore += handle.getMaxScore();
|
|
1262
|
-
|
|
1283
|
+
const countsForAnswerGiven = opts?.answerPageIndex === void 0 || pageIndex === void 0 || pageIndex === opts.answerPageIndex;
|
|
1284
|
+
if (countsForAnswerGiven && !handle.getAnswerGiven()) allAnswered = false;
|
|
1263
1285
|
}
|
|
1264
1286
|
return { score, maxScore, allAnswered };
|
|
1265
1287
|
}
|
|
1266
1288
|
|
|
1267
|
-
// src/compound/
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
}
|
|
1276
|
-
|
|
1289
|
+
// src/compound/CompoundHydrationBridge.tsx
|
|
1290
|
+
var import_react7 = require("react");
|
|
1291
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
1292
|
+
var CompoundHydrationBridgeContext = (0, import_react7.createContext)(
|
|
1293
|
+
null
|
|
1294
|
+
);
|
|
1295
|
+
function CompoundHydrationBridgeProvider({ children }) {
|
|
1296
|
+
const bridgeRef = (0, import_react7.useRef)(null);
|
|
1297
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(CompoundHydrationBridgeContext.Provider, { value: bridgeRef, children });
|
|
1298
|
+
}
|
|
1299
|
+
function useCompoundHydrationBridgeRef() {
|
|
1300
|
+
return (0, import_react7.useContext)(CompoundHydrationBridgeContext);
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
// src/compound/CompoundPageIndexContext.tsx
|
|
1304
|
+
var import_react8 = require("react");
|
|
1305
|
+
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
1306
|
+
var CompoundPageIndexContext = (0, import_react8.createContext)(void 0);
|
|
1307
|
+
function CompoundPageIndexProvider({
|
|
1308
|
+
pageIndex,
|
|
1309
|
+
children
|
|
1310
|
+
}) {
|
|
1311
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(CompoundPageIndexContext.Provider, { value: pageIndex, children });
|
|
1312
|
+
}
|
|
1313
|
+
function useCompoundPageIndex() {
|
|
1314
|
+
return (0, import_react8.useContext)(CompoundPageIndexContext);
|
|
1277
1315
|
}
|
|
1278
1316
|
|
|
1279
1317
|
// src/compound/CompoundProvider.tsx
|
|
1280
|
-
var
|
|
1281
|
-
var CompoundRegistryContext = (0,
|
|
1282
|
-
var CompoundHandlesVersionContext = (0,
|
|
1318
|
+
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
1319
|
+
var CompoundRegistryContext = (0, import_react9.createContext)(null);
|
|
1320
|
+
var CompoundHandlesVersionContext = (0, import_react9.createContext)(0);
|
|
1283
1321
|
function CompoundProvider({
|
|
1284
1322
|
children,
|
|
1285
1323
|
activePageIndex: _activePageIndex,
|
|
1286
1324
|
onActivePageIndexChange: _onActivePageIndexChange
|
|
1287
1325
|
}) {
|
|
1288
|
-
const registryRef = (0,
|
|
1289
|
-
const [handlesVersion, setHandlesVersion] = (0,
|
|
1290
|
-
const register = (0,
|
|
1326
|
+
const registryRef = (0, import_react9.useRef)(/* @__PURE__ */ new Map());
|
|
1327
|
+
const [handlesVersion, setHandlesVersion] = (0, import_react9.useState)(0);
|
|
1328
|
+
const register = (0, import_react9.useCallback)((checkId, handle, pageIndex) => {
|
|
1291
1329
|
const prev = registryRef.current.get(checkId);
|
|
1292
|
-
|
|
1293
|
-
|
|
1330
|
+
if (prev && prev.handle !== handle) {
|
|
1331
|
+
const message = `[lessonkit] duplicate checkId "${checkId}" registered in the same compound container; the previous handle was replaced.`;
|
|
1332
|
+
if (isDevEnvironment4()) {
|
|
1333
|
+
console.error(message);
|
|
1334
|
+
} else {
|
|
1335
|
+
console.warn(message);
|
|
1336
|
+
}
|
|
1337
|
+
}
|
|
1338
|
+
registryRef.current.set(checkId, { handle, pageIndex });
|
|
1339
|
+
if (prev?.handle !== handle || prev?.pageIndex !== pageIndex) {
|
|
1294
1340
|
setHandlesVersion((v) => v + 1);
|
|
1295
1341
|
}
|
|
1296
1342
|
return () => {
|
|
1297
|
-
|
|
1343
|
+
const current = registryRef.current.get(checkId);
|
|
1344
|
+
if (current?.handle === handle) {
|
|
1298
1345
|
registryRef.current.delete(checkId);
|
|
1299
1346
|
setHandlesVersion((v) => v + 1);
|
|
1300
1347
|
}
|
|
1301
1348
|
};
|
|
1302
1349
|
}, []);
|
|
1303
|
-
const registryValue = (0,
|
|
1350
|
+
const registryValue = (0, import_react9.useMemo)(
|
|
1304
1351
|
() => ({
|
|
1305
1352
|
register,
|
|
1306
|
-
getHandles: () =>
|
|
1353
|
+
getHandles: () => {
|
|
1354
|
+
const handles = /* @__PURE__ */ new Map();
|
|
1355
|
+
for (const [checkId, entry] of registryRef.current) {
|
|
1356
|
+
handles.set(checkId, entry.handle);
|
|
1357
|
+
}
|
|
1358
|
+
return handles;
|
|
1359
|
+
},
|
|
1360
|
+
getRegisteredHandles: () => registryRef.current
|
|
1307
1361
|
}),
|
|
1308
1362
|
[register]
|
|
1309
1363
|
);
|
|
1310
|
-
return /* @__PURE__ */ (0,
|
|
1364
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(CompoundHydrationBridgeProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(CompoundRegistryContext.Provider, { value: registryValue, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(CompoundHandlesVersionContext.Provider, { value: handlesVersion, children }) }) });
|
|
1311
1365
|
}
|
|
1312
1366
|
function useCompoundRegistry() {
|
|
1313
|
-
const registry = (0,
|
|
1314
|
-
const handlesVersion = (0,
|
|
1367
|
+
const registry = (0, import_react9.useContext)(CompoundRegistryContext);
|
|
1368
|
+
const handlesVersion = (0, import_react9.useContext)(CompoundHandlesVersionContext);
|
|
1315
1369
|
if (!registry) return null;
|
|
1316
1370
|
return { ...registry, handlesVersion };
|
|
1317
1371
|
}
|
|
1318
1372
|
function useCompoundHandlesVersion() {
|
|
1319
|
-
return (0,
|
|
1373
|
+
return (0, import_react9.useContext)(CompoundHandlesVersionContext);
|
|
1320
1374
|
}
|
|
1321
1375
|
function useRegisterAssessmentHandle(checkId, handle) {
|
|
1322
|
-
const registry = (0,
|
|
1323
|
-
|
|
1376
|
+
const registry = (0, import_react9.useContext)(CompoundRegistryContext);
|
|
1377
|
+
const pageIndex = useCompoundPageIndex();
|
|
1378
|
+
import_react9.default.useLayoutEffect(() => {
|
|
1324
1379
|
if (!registry || !handle) return;
|
|
1325
|
-
return registry.register(checkId, handle);
|
|
1326
|
-
}, [registry, checkId, handle]);
|
|
1380
|
+
return registry.register(checkId, handle, pageIndex);
|
|
1381
|
+
}, [registry, checkId, handle, pageIndex]);
|
|
1327
1382
|
}
|
|
1328
1383
|
function useCompoundHandleRef(ref, opts) {
|
|
1329
|
-
const { activePageIndex, setActivePageIndex, getHandles, pageCount } = opts;
|
|
1330
|
-
const
|
|
1384
|
+
const { activePageIndex, setActivePageIndex, getHandles, getRegisteredHandles, pageCount } = opts;
|
|
1385
|
+
const bridgeRef = useCompoundHydrationBridgeRef();
|
|
1386
|
+
const setIndexClamped = (0, import_react9.useCallback)(
|
|
1331
1387
|
(index) => {
|
|
1332
|
-
const next = pageCount !== void 0 ? (0,
|
|
1388
|
+
const next = pageCount !== void 0 ? (0, import_core11.clampCompoundPageIndex)(index, pageCount) : Math.max(0, Math.floor(index));
|
|
1333
1389
|
setActivePageIndex(next);
|
|
1334
1390
|
},
|
|
1335
1391
|
[pageCount, setActivePageIndex]
|
|
1336
1392
|
);
|
|
1337
|
-
(0,
|
|
1393
|
+
(0, import_react9.useImperativeHandle)(
|
|
1338
1394
|
ref,
|
|
1339
1395
|
() => ({
|
|
1340
|
-
getScore: () => aggregateAssessmentScores(
|
|
1341
|
-
getMaxScore: () => aggregateAssessmentScores(
|
|
1342
|
-
getAnswerGiven: () => aggregateAssessmentScores(
|
|
1396
|
+
getScore: () => aggregateAssessmentScores(getRegisteredHandles().values()).score,
|
|
1397
|
+
getMaxScore: () => aggregateAssessmentScores(getRegisteredHandles().values()).maxScore,
|
|
1398
|
+
getAnswerGiven: () => aggregateAssessmentScores(getRegisteredHandles().values(), {
|
|
1399
|
+
answerPageIndex: activePageIndex
|
|
1400
|
+
}).allAnswered,
|
|
1343
1401
|
resetTask: () => {
|
|
1344
|
-
for (const
|
|
1402
|
+
for (const entry of getRegisteredHandles().values()) entry.handle.resetTask();
|
|
1345
1403
|
},
|
|
1346
1404
|
showSolutions: () => {
|
|
1347
1405
|
if (!opts.enableSolutionsButton) return;
|
|
1348
|
-
for (const
|
|
1406
|
+
for (const entry of getRegisteredHandles().values()) entry.handle.showSolutions();
|
|
1349
1407
|
},
|
|
1350
1408
|
getCurrentState: () => {
|
|
1351
1409
|
const childStates = {};
|
|
1352
|
-
for (const [checkId,
|
|
1353
|
-
if (handle.getCurrentState) {
|
|
1354
|
-
childStates[checkId] = handle.getCurrentState();
|
|
1410
|
+
for (const [checkId, entry] of getRegisteredHandles()) {
|
|
1411
|
+
if (entry.handle.getCurrentState) {
|
|
1412
|
+
childStates[checkId] = entry.handle.getCurrentState();
|
|
1355
1413
|
}
|
|
1356
1414
|
}
|
|
1357
|
-
return (0,
|
|
1415
|
+
return (0, import_core11.createCompoundResumeState)({ activePageIndex, childStates });
|
|
1358
1416
|
},
|
|
1359
1417
|
resume: (state) => {
|
|
1360
|
-
|
|
1361
|
-
resumeChildHandles(getHandles(), state.childStates);
|
|
1418
|
+
bridgeRef?.current?.notifyImperativeResume(state);
|
|
1362
1419
|
}
|
|
1363
1420
|
}),
|
|
1364
|
-
[activePageIndex, setIndexClamped, getHandles, opts.enableSolutionsButton]
|
|
1421
|
+
[activePageIndex, setIndexClamped, getHandles, getRegisteredHandles, opts.enableSolutionsButton, bridgeRef]
|
|
1365
1422
|
);
|
|
1366
1423
|
}
|
|
1367
1424
|
|
|
1368
1425
|
// src/assessment/internal/useAssessmentHandleRegistration.ts
|
|
1369
1426
|
function useAssessmentHandleRegistration(checkId, handle, ref) {
|
|
1370
|
-
(0,
|
|
1427
|
+
(0, import_react10.useImperativeHandle)(ref, () => handle, [handle]);
|
|
1371
1428
|
useRegisterAssessmentHandle(checkId, handle);
|
|
1372
1429
|
}
|
|
1373
1430
|
|
|
1374
1431
|
// src/assessment/internal/usePluginScoring.ts
|
|
1375
|
-
var
|
|
1432
|
+
var import_react11 = require("react");
|
|
1376
1433
|
|
|
1377
1434
|
// src/assessment/scoring.ts
|
|
1378
1435
|
function resolvePassingThreshold(passingScore, maxScore) {
|
|
@@ -1400,7 +1457,7 @@ function scoreFromCustom(custom, fallbackCorrect, fallbackMax = 1, passingScore)
|
|
|
1400
1457
|
// src/assessment/internal/usePluginScoring.ts
|
|
1401
1458
|
function usePluginScoring(checkId, lessonId) {
|
|
1402
1459
|
const { plugins, config, session } = useLessonkit();
|
|
1403
|
-
const getPluginScore = (0,
|
|
1460
|
+
const getPluginScore = (0, import_react11.useCallback)(
|
|
1404
1461
|
(response) => {
|
|
1405
1462
|
const pluginCtx = buildPluginContext({
|
|
1406
1463
|
courseId: config.courseId,
|
|
@@ -1412,11 +1469,11 @@ function usePluginScoring(checkId, lessonId) {
|
|
|
1412
1469
|
},
|
|
1413
1470
|
[checkId, config.courseId, lessonId, plugins, session.attemptId, session.sessionId, session.user]
|
|
1414
1471
|
);
|
|
1415
|
-
const scoreResponse = (0,
|
|
1472
|
+
const scoreResponse = (0, import_react11.useCallback)(
|
|
1416
1473
|
(response, defaultCorrect, maxScore = 1, passingScore) => scoreFromCustom(getPluginScore(response), defaultCorrect, maxScore, passingScore),
|
|
1417
1474
|
[getPluginScore]
|
|
1418
1475
|
);
|
|
1419
|
-
const isChoiceCorrect = (0,
|
|
1476
|
+
const isChoiceCorrect = (0, import_react11.useCallback)(
|
|
1420
1477
|
(choice, answer, custom, passingScore) => {
|
|
1421
1478
|
if (!custom) return choice === answer;
|
|
1422
1479
|
if (custom.passed !== void 0) return custom.passed;
|
|
@@ -1431,53 +1488,94 @@ function usePluginScoring(checkId, lessonId) {
|
|
|
1431
1488
|
}
|
|
1432
1489
|
|
|
1433
1490
|
// src/components/Quiz.tsx
|
|
1434
|
-
var
|
|
1491
|
+
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
1435
1492
|
function QuizInner(props, ref) {
|
|
1436
1493
|
const { enclosingLessonId } = props;
|
|
1437
|
-
const checkId = (0,
|
|
1494
|
+
const checkId = (0, import_react12.useMemo)(() => normalizeComponentId(props.checkId, "checkId"), [props.checkId]);
|
|
1438
1495
|
const quiz = useQuizState(enclosingLessonId);
|
|
1439
1496
|
const { getPluginScore, isChoiceCorrect } = usePluginScoring(checkId, enclosingLessonId);
|
|
1440
|
-
const [selected, setSelected] = (0,
|
|
1441
|
-
const [selectionCorrect, setSelectionCorrect] = (0,
|
|
1442
|
-
const [quizPassed, setQuizPassed] = (0,
|
|
1443
|
-
const
|
|
1444
|
-
const
|
|
1497
|
+
const [selected, setSelected] = (0, import_react12.useState)(null);
|
|
1498
|
+
const [selectionCorrect, setSelectionCorrect] = (0, import_react12.useState)(null);
|
|
1499
|
+
const [quizPassed, setQuizPassed] = (0, import_react12.useState)(false);
|
|
1500
|
+
const [completedScore, setCompletedScore] = (0, import_react12.useState)(null);
|
|
1501
|
+
const [completedMaxScore, setCompletedMaxScore] = (0, import_react12.useState)(null);
|
|
1502
|
+
const completedRef = (0, import_react12.useRef)(false);
|
|
1503
|
+
const telemetryReplayedRef = (0, import_react12.useRef)(false);
|
|
1504
|
+
const questionId = (0, import_react12.useId)();
|
|
1445
1505
|
const choicesKey = props.choices.join("\0");
|
|
1446
|
-
(0,
|
|
1506
|
+
(0, import_react12.useEffect)(() => {
|
|
1447
1507
|
completedRef.current = false;
|
|
1508
|
+
telemetryReplayedRef.current = false;
|
|
1448
1509
|
setQuizPassed(false);
|
|
1449
1510
|
setSelected(null);
|
|
1450
1511
|
setSelectionCorrect(null);
|
|
1512
|
+
setCompletedScore(null);
|
|
1513
|
+
setCompletedMaxScore(null);
|
|
1451
1514
|
}, [checkId, props.answer, props.question, choicesKey]);
|
|
1452
1515
|
const passed = quizPassed;
|
|
1453
|
-
const
|
|
1516
|
+
const resolveScores = () => {
|
|
1517
|
+
const maxScore = completedMaxScore ?? 1;
|
|
1518
|
+
if (quizPassed) {
|
|
1519
|
+
return { score: completedScore ?? maxScore, maxScore };
|
|
1520
|
+
}
|
|
1521
|
+
if (selected !== null && selectionCorrect) {
|
|
1522
|
+
return { score: completedMaxScore ?? maxScore, maxScore };
|
|
1523
|
+
}
|
|
1524
|
+
return { score: 0, maxScore };
|
|
1525
|
+
};
|
|
1526
|
+
const replayTelemetry = (nextSelected, nextCorrect, nextPassed, nextScore, nextMaxScore) => {
|
|
1527
|
+
if (!nextPassed || telemetryReplayedRef.current) return;
|
|
1528
|
+
telemetryReplayedRef.current = true;
|
|
1529
|
+
if (nextSelected !== null) {
|
|
1530
|
+
quiz.answer({
|
|
1531
|
+
checkId,
|
|
1532
|
+
question: props.question,
|
|
1533
|
+
choice: nextSelected,
|
|
1534
|
+
correct: nextCorrect ?? false
|
|
1535
|
+
});
|
|
1536
|
+
}
|
|
1537
|
+
quiz.complete({
|
|
1538
|
+
checkId,
|
|
1539
|
+
score: nextScore,
|
|
1540
|
+
maxScore: nextMaxScore,
|
|
1541
|
+
passingScore: props.passingScore ?? nextMaxScore
|
|
1542
|
+
});
|
|
1543
|
+
};
|
|
1544
|
+
const handle = (0, import_react12.useMemo)(
|
|
1454
1545
|
() => buildAssessmentHandle({
|
|
1455
1546
|
checkId,
|
|
1456
|
-
getScore: () =>
|
|
1457
|
-
|
|
1458
|
-
if (quizPassed && selected !== null) return maxScore;
|
|
1459
|
-
if (selected === null) return 0;
|
|
1460
|
-
return selectionCorrect ? maxScore : 0;
|
|
1461
|
-
},
|
|
1462
|
-
getMaxScore: () => 1,
|
|
1547
|
+
getScore: () => resolveScores().score,
|
|
1548
|
+
getMaxScore: () => resolveScores().maxScore,
|
|
1463
1549
|
getAnswerGiven: () => selected !== null,
|
|
1464
1550
|
resetTask: () => {
|
|
1465
1551
|
completedRef.current = false;
|
|
1552
|
+
telemetryReplayedRef.current = false;
|
|
1466
1553
|
setQuizPassed(false);
|
|
1467
1554
|
setSelected(null);
|
|
1468
1555
|
setSelectionCorrect(null);
|
|
1556
|
+
setCompletedScore(null);
|
|
1557
|
+
setCompletedMaxScore(null);
|
|
1469
1558
|
},
|
|
1470
1559
|
showSolutions: () => {
|
|
1471
1560
|
},
|
|
1472
|
-
getXAPIData: () =>
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1561
|
+
getXAPIData: () => {
|
|
1562
|
+
const { score, maxScore } = resolveScores();
|
|
1563
|
+
return {
|
|
1564
|
+
checkId,
|
|
1565
|
+
interactionType: "mcq",
|
|
1566
|
+
response: selected ?? void 0,
|
|
1567
|
+
correct: selectionCorrect ?? void 0,
|
|
1568
|
+
score,
|
|
1569
|
+
maxScore
|
|
1570
|
+
};
|
|
1571
|
+
},
|
|
1572
|
+
getCurrentState: () => ({
|
|
1573
|
+
selected,
|
|
1574
|
+
selectionCorrect,
|
|
1575
|
+
quizPassed,
|
|
1576
|
+
completedScore,
|
|
1577
|
+
completedMaxScore
|
|
1479
1578
|
}),
|
|
1480
|
-
getCurrentState: () => ({ selected, selectionCorrect, quizPassed }),
|
|
1481
1579
|
resume: (state) => {
|
|
1482
1580
|
const nextSelected = readStringField(state, "selected");
|
|
1483
1581
|
if (typeof nextSelected === "string" || nextSelected === null) setSelected(nextSelected);
|
|
@@ -1485,21 +1583,47 @@ function QuizInner(props, ref) {
|
|
|
1485
1583
|
if (nextCorrect === true || nextCorrect === false || nextCorrect === null) {
|
|
1486
1584
|
setSelectionCorrect(nextCorrect);
|
|
1487
1585
|
}
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1586
|
+
const nextCompletedScore = readNumberField(state, "completedScore");
|
|
1587
|
+
if (typeof nextCompletedScore === "number") setCompletedScore(nextCompletedScore);
|
|
1588
|
+
const nextCompletedMaxScore = readNumberField(state, "completedMaxScore");
|
|
1589
|
+
if (typeof nextCompletedMaxScore === "number") setCompletedMaxScore(nextCompletedMaxScore);
|
|
1590
|
+
const nextPassed = readBooleanField(state, "quizPassed");
|
|
1591
|
+
if (nextPassed === true || nextPassed === false) {
|
|
1592
|
+
setQuizPassed(nextPassed);
|
|
1593
|
+
completedRef.current = nextPassed;
|
|
1594
|
+
if (nextPassed) {
|
|
1595
|
+
const maxScore = nextCompletedMaxScore ?? completedMaxScore ?? 1;
|
|
1596
|
+
const score = nextCompletedScore ?? completedScore ?? maxScore;
|
|
1597
|
+
replayTelemetry(
|
|
1598
|
+
nextSelected ?? null,
|
|
1599
|
+
nextCorrect ?? null,
|
|
1600
|
+
nextPassed,
|
|
1601
|
+
score,
|
|
1602
|
+
maxScore
|
|
1603
|
+
);
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1492
1606
|
}
|
|
1493
1607
|
}),
|
|
1494
|
-
[
|
|
1608
|
+
[
|
|
1609
|
+
checkId,
|
|
1610
|
+
completedMaxScore,
|
|
1611
|
+
completedScore,
|
|
1612
|
+
props.passingScore,
|
|
1613
|
+
props.question,
|
|
1614
|
+
quiz,
|
|
1615
|
+
quizPassed,
|
|
1616
|
+
selected,
|
|
1617
|
+
selectionCorrect
|
|
1618
|
+
]
|
|
1495
1619
|
);
|
|
1496
1620
|
useAssessmentHandleRegistration(checkId, handle, ref);
|
|
1497
|
-
return /* @__PURE__ */ (0,
|
|
1498
|
-
/* @__PURE__ */ (0,
|
|
1499
|
-
/* @__PURE__ */ (0,
|
|
1500
|
-
/* @__PURE__ */ (0,
|
|
1501
|
-
props.choices.map((c, i) => /* @__PURE__ */ (0,
|
|
1502
|
-
/* @__PURE__ */ (0,
|
|
1621
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("section", { "aria-label": "Quiz", "data-lk-check-id": checkId, children: [
|
|
1622
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { id: questionId, children: props.question }),
|
|
1623
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("fieldset", { "aria-labelledby": questionId, children: [
|
|
1624
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("legend", { style: import_accessibility.visuallyHiddenStyle, children: "Quiz choices" }),
|
|
1625
|
+
props.choices.map((c, i) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("label", { style: { display: "block" }, children: [
|
|
1626
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1503
1627
|
"input",
|
|
1504
1628
|
{
|
|
1505
1629
|
type: "radio",
|
|
@@ -1524,9 +1648,12 @@ function QuizInner(props, ref) {
|
|
|
1524
1648
|
completedRef.current = true;
|
|
1525
1649
|
setQuizPassed(true);
|
|
1526
1650
|
const maxScore = custom?.maxScore ?? 1;
|
|
1651
|
+
const score = custom?.score ?? maxScore;
|
|
1652
|
+
setCompletedScore(score);
|
|
1653
|
+
setCompletedMaxScore(maxScore);
|
|
1527
1654
|
quiz.complete({
|
|
1528
1655
|
checkId,
|
|
1529
|
-
score
|
|
1656
|
+
score,
|
|
1530
1657
|
maxScore,
|
|
1531
1658
|
passingScore: props.passingScore ?? maxScore
|
|
1532
1659
|
});
|
|
@@ -1537,15 +1664,15 @@ function QuizInner(props, ref) {
|
|
|
1537
1664
|
c
|
|
1538
1665
|
] }, `${questionId}-${i}`))
|
|
1539
1666
|
] }),
|
|
1540
|
-
selected && selectionCorrect !== null ? /* @__PURE__ */ (0,
|
|
1667
|
+
selected && selectionCorrect !== null ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { role: "status", "aria-live": "polite", children: selectionCorrect ? "Correct" : "Try again" }) : null
|
|
1541
1668
|
] });
|
|
1542
1669
|
}
|
|
1543
|
-
var QuizInnerForwarded = (0,
|
|
1544
|
-
var Quiz = (0,
|
|
1545
|
-
return /* @__PURE__ */ (0,
|
|
1670
|
+
var QuizInnerForwarded = (0, import_react12.forwardRef)(QuizInner);
|
|
1671
|
+
var Quiz = (0, import_react12.forwardRef)(function Quiz2(props, ref) {
|
|
1672
|
+
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 }) });
|
|
1546
1673
|
});
|
|
1547
1674
|
function KnowledgeCheck(props) {
|
|
1548
|
-
return /* @__PURE__ */ (0,
|
|
1675
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1549
1676
|
Quiz,
|
|
1550
1677
|
{
|
|
1551
1678
|
checkId: props.checkId,
|
|
@@ -1561,27 +1688,27 @@ function resetQuizWarningsForTests() {
|
|
|
1561
1688
|
}
|
|
1562
1689
|
|
|
1563
1690
|
// src/components.tsx
|
|
1564
|
-
var
|
|
1691
|
+
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
1565
1692
|
function Course(props) {
|
|
1566
|
-
const courseId = (0,
|
|
1567
|
-
const providerConfig = (0,
|
|
1693
|
+
const courseId = (0, import_react13.useMemo)(() => normalizeComponentId(props.courseId, "courseId"), [props.courseId]);
|
|
1694
|
+
const providerConfig = (0, import_react13.useMemo)(
|
|
1568
1695
|
() => ({ ...props.config, courseId }),
|
|
1569
1696
|
[props.config, courseId]
|
|
1570
1697
|
);
|
|
1571
|
-
return /* @__PURE__ */ (0,
|
|
1572
|
-
/* @__PURE__ */ (0,
|
|
1573
|
-
/* @__PURE__ */ (0,
|
|
1698
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(LessonkitProvider, { config: providerConfig, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("section", { "aria-label": props.title, children: [
|
|
1699
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("h1", { children: props.title }),
|
|
1700
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { children: props.children })
|
|
1574
1701
|
] }) });
|
|
1575
1702
|
}
|
|
1576
1703
|
function Lesson(props) {
|
|
1577
|
-
const lessonId = (0,
|
|
1704
|
+
const lessonId = (0, import_react13.useMemo)(() => normalizeComponentId(props.lessonId, "lessonId"), [props.lessonId]);
|
|
1578
1705
|
const autoComplete = props.autoCompleteOnUnmount !== false;
|
|
1579
1706
|
const { setActiveLesson, config } = useLessonkit();
|
|
1580
1707
|
const { completeLesson } = useCompletion();
|
|
1581
|
-
const lessonMountGenerationRef = (0,
|
|
1582
|
-
const liveCourseIdRef = (0,
|
|
1708
|
+
const lessonMountGenerationRef = (0, import_react13.useRef)(0);
|
|
1709
|
+
const liveCourseIdRef = (0, import_react13.useRef)(config.courseId);
|
|
1583
1710
|
liveCourseIdRef.current = config.courseId;
|
|
1584
|
-
(0,
|
|
1711
|
+
(0, import_react13.useEffect)(() => {
|
|
1585
1712
|
const unregister = registerLessonMount(lessonId);
|
|
1586
1713
|
const generation = ++lessonMountGenerationRef.current;
|
|
1587
1714
|
const mountedCourseId = config.courseId;
|
|
@@ -1606,37 +1733,37 @@ function Lesson(props) {
|
|
|
1606
1733
|
});
|
|
1607
1734
|
};
|
|
1608
1735
|
}, [lessonId, config.courseId, setActiveLesson, completeLesson, autoComplete]);
|
|
1609
|
-
return /* @__PURE__ */ (0,
|
|
1610
|
-
/* @__PURE__ */ (0,
|
|
1611
|
-
/* @__PURE__ */ (0,
|
|
1736
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(LessonContext.Provider, { value: lessonId, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("article", { "aria-label": props.title, children: [
|
|
1737
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("h2", { children: props.title }),
|
|
1738
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { children: props.children })
|
|
1612
1739
|
] }) });
|
|
1613
1740
|
}
|
|
1614
1741
|
function Scenario(props) {
|
|
1615
|
-
const blockId = (0,
|
|
1742
|
+
const blockId = (0, import_react13.useMemo)(
|
|
1616
1743
|
() => props.blockId !== void 0 ? normalizeComponentId(props.blockId, "blockId") : void 0,
|
|
1617
1744
|
[props.blockId]
|
|
1618
1745
|
);
|
|
1619
|
-
return /* @__PURE__ */ (0,
|
|
1746
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("section", { "aria-label": "Scenario", "data-lk-block-id": blockId, children: props.children });
|
|
1620
1747
|
}
|
|
1621
1748
|
function Reflection(props) {
|
|
1622
|
-
const blockId = (0,
|
|
1749
|
+
const blockId = (0, import_react13.useMemo)(
|
|
1623
1750
|
() => props.blockId !== void 0 ? normalizeComponentId(props.blockId, "blockId") : void 0,
|
|
1624
1751
|
[props.blockId]
|
|
1625
1752
|
);
|
|
1626
|
-
const promptId = (0,
|
|
1627
|
-
const hintId = (0,
|
|
1628
|
-
const [internalValue, setInternalValue] = (0,
|
|
1753
|
+
const promptId = (0, import_react13.useId)();
|
|
1754
|
+
const hintId = (0, import_react13.useId)();
|
|
1755
|
+
const [internalValue, setInternalValue] = (0, import_react13.useState)("");
|
|
1629
1756
|
const isControlled = props.value !== void 0;
|
|
1630
1757
|
const value = isControlled ? props.value : internalValue;
|
|
1631
1758
|
const handleChange = (event) => {
|
|
1632
1759
|
if (!isControlled) setInternalValue(event.target.value);
|
|
1633
1760
|
props.onChange?.(event.target.value);
|
|
1634
1761
|
};
|
|
1635
|
-
return /* @__PURE__ */ (0,
|
|
1636
|
-
props.prompt ? /* @__PURE__ */ (0,
|
|
1637
|
-
props.hint ? /* @__PURE__ */ (0,
|
|
1762
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("section", { "aria-label": "Reflection", "data-lk-block-id": blockId, children: [
|
|
1763
|
+
props.prompt ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { id: promptId, children: props.prompt }) : null,
|
|
1764
|
+
props.hint ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { id: hintId, style: import_accessibility2.visuallyHiddenStyle, children: props.hint }) : null,
|
|
1638
1765
|
props.children,
|
|
1639
|
-
/* @__PURE__ */ (0,
|
|
1766
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1640
1767
|
"textarea",
|
|
1641
1768
|
{
|
|
1642
1769
|
value,
|
|
@@ -1654,7 +1781,7 @@ function ProgressTracker(props) {
|
|
|
1654
1781
|
if (props.totalLessons != null) {
|
|
1655
1782
|
const total = props.totalLessons;
|
|
1656
1783
|
const displayed = Math.min(completed, total);
|
|
1657
|
-
return /* @__PURE__ */ (0,
|
|
1784
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("aside", { "aria-label": "Progress", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1658
1785
|
"div",
|
|
1659
1786
|
{
|
|
1660
1787
|
role: "progressbar",
|
|
@@ -1662,7 +1789,7 @@ function ProgressTracker(props) {
|
|
|
1662
1789
|
"aria-valuemax": total,
|
|
1663
1790
|
"aria-valuenow": displayed,
|
|
1664
1791
|
"aria-label": "Lessons completed",
|
|
1665
|
-
children: /* @__PURE__ */ (0,
|
|
1792
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("p", { children: [
|
|
1666
1793
|
"Lessons completed: ",
|
|
1667
1794
|
displayed,
|
|
1668
1795
|
" of ",
|
|
@@ -1671,58 +1798,101 @@ function ProgressTracker(props) {
|
|
|
1671
1798
|
}
|
|
1672
1799
|
) });
|
|
1673
1800
|
}
|
|
1674
|
-
return /* @__PURE__ */ (0,
|
|
1801
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("aside", { "aria-label": "Progress", role: "status", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("p", { children: [
|
|
1675
1802
|
"Lessons completed: ",
|
|
1676
1803
|
completed
|
|
1677
1804
|
] }) });
|
|
1678
1805
|
}
|
|
1679
1806
|
|
|
1680
1807
|
// src/blocks/TrueFalse.tsx
|
|
1681
|
-
var
|
|
1682
|
-
var
|
|
1808
|
+
var import_react14 = __toESM(require("react"), 1);
|
|
1809
|
+
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
1683
1810
|
var INTERACTION = "trueFalse";
|
|
1684
1811
|
function TrueFalseInner(props, ref) {
|
|
1685
1812
|
const { enclosingLessonId } = props;
|
|
1686
|
-
const checkId = (0,
|
|
1813
|
+
const checkId = (0, import_react14.useMemo)(() => normalizeComponentId(props.checkId, "checkId"), [props.checkId]);
|
|
1687
1814
|
const assessment = useAssessmentState(enclosingLessonId);
|
|
1688
1815
|
const { config } = useLessonkit();
|
|
1689
1816
|
const { scoreResponse } = usePluginScoring(checkId, enclosingLessonId);
|
|
1690
|
-
const [selected, setSelected] = (0,
|
|
1691
|
-
const [selectionCorrect, setSelectionCorrect] = (0,
|
|
1692
|
-
const [showSolutions, setShowSolutions] = (0,
|
|
1693
|
-
const [passed, setPassed] = (0,
|
|
1694
|
-
const
|
|
1695
|
-
const
|
|
1817
|
+
const [selected, setSelected] = (0, import_react14.useState)(null);
|
|
1818
|
+
const [selectionCorrect, setSelectionCorrect] = (0, import_react14.useState)(null);
|
|
1819
|
+
const [showSolutions, setShowSolutions] = (0, import_react14.useState)(false);
|
|
1820
|
+
const [passed, setPassed] = (0, import_react14.useState)(false);
|
|
1821
|
+
const [completedScore, setCompletedScore] = (0, import_react14.useState)(null);
|
|
1822
|
+
const [completedMaxScore, setCompletedMaxScore] = (0, import_react14.useState)(null);
|
|
1823
|
+
const completedRef = (0, import_react14.useRef)(false);
|
|
1824
|
+
const telemetryReplayedRef = (0, import_react14.useRef)(false);
|
|
1825
|
+
const questionId = import_react14.default.useId();
|
|
1696
1826
|
const reset = () => {
|
|
1697
1827
|
completedRef.current = false;
|
|
1828
|
+
telemetryReplayedRef.current = false;
|
|
1698
1829
|
setPassed(false);
|
|
1699
1830
|
setSelected(null);
|
|
1700
1831
|
setSelectionCorrect(null);
|
|
1701
1832
|
setShowSolutions(false);
|
|
1833
|
+
setCompletedScore(null);
|
|
1834
|
+
setCompletedMaxScore(null);
|
|
1702
1835
|
};
|
|
1703
|
-
(0,
|
|
1836
|
+
(0, import_react14.useEffect)(() => {
|
|
1704
1837
|
reset();
|
|
1705
1838
|
}, [checkId, props.answer, props.question, config.courseId, enclosingLessonId]);
|
|
1706
|
-
const
|
|
1839
|
+
const resolveScores = () => {
|
|
1840
|
+
const maxScore = completedMaxScore ?? 1;
|
|
1841
|
+
if (passed) {
|
|
1842
|
+
return { score: completedScore ?? maxScore, maxScore };
|
|
1843
|
+
}
|
|
1844
|
+
if (selectionCorrect) {
|
|
1845
|
+
return { score: completedMaxScore ?? maxScore, maxScore };
|
|
1846
|
+
}
|
|
1847
|
+
return { score: 0, maxScore };
|
|
1848
|
+
};
|
|
1849
|
+
const replayTelemetry = (nextSelected, nextCorrect, nextPassed, nextScore, nextMaxScore) => {
|
|
1850
|
+
if (!nextPassed || telemetryReplayedRef.current) return;
|
|
1851
|
+
telemetryReplayedRef.current = true;
|
|
1852
|
+
if (nextSelected !== null) {
|
|
1853
|
+
assessment.answer({
|
|
1854
|
+
checkId,
|
|
1855
|
+
interactionType: INTERACTION,
|
|
1856
|
+
question: props.question,
|
|
1857
|
+
response: nextSelected,
|
|
1858
|
+
correct: nextCorrect ?? false
|
|
1859
|
+
});
|
|
1860
|
+
}
|
|
1861
|
+
assessment.complete({
|
|
1862
|
+
checkId,
|
|
1863
|
+
interactionType: INTERACTION,
|
|
1864
|
+
score: nextScore,
|
|
1865
|
+
maxScore: nextMaxScore,
|
|
1866
|
+
passingScore: props.passingScore ?? nextMaxScore
|
|
1867
|
+
});
|
|
1868
|
+
};
|
|
1869
|
+
const handle = (0, import_react14.useMemo)(
|
|
1707
1870
|
() => buildAssessmentHandle({
|
|
1708
1871
|
checkId,
|
|
1709
|
-
getScore: () =>
|
|
1710
|
-
|
|
1711
|
-
return passed ? maxScore : selected === null ? 0 : selected === props.answer ? maxScore : 0;
|
|
1712
|
-
},
|
|
1713
|
-
getMaxScore: () => 1,
|
|
1872
|
+
getScore: () => resolveScores().score,
|
|
1873
|
+
getMaxScore: () => resolveScores().maxScore,
|
|
1714
1874
|
getAnswerGiven: () => selected !== null,
|
|
1715
1875
|
resetTask: reset,
|
|
1716
1876
|
showSolutions: () => setShowSolutions(true),
|
|
1717
|
-
getXAPIData: () =>
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1877
|
+
getXAPIData: () => {
|
|
1878
|
+
const { score, maxScore } = resolveScores();
|
|
1879
|
+
return {
|
|
1880
|
+
checkId,
|
|
1881
|
+
interactionType: INTERACTION,
|
|
1882
|
+
response: selected ?? void 0,
|
|
1883
|
+
correct: selectionCorrect ?? void 0,
|
|
1884
|
+
score,
|
|
1885
|
+
maxScore
|
|
1886
|
+
};
|
|
1887
|
+
},
|
|
1888
|
+
getCurrentState: () => ({
|
|
1889
|
+
selected,
|
|
1890
|
+
selectionCorrect,
|
|
1891
|
+
passed,
|
|
1892
|
+
showSolutions,
|
|
1893
|
+
completedScore,
|
|
1894
|
+
completedMaxScore
|
|
1724
1895
|
}),
|
|
1725
|
-
getCurrentState: () => ({ selected, selectionCorrect, passed, showSolutions }),
|
|
1726
1896
|
resume: (state) => {
|
|
1727
1897
|
const nextSelected = readBooleanField(state, "selected");
|
|
1728
1898
|
if (nextSelected === true || nextSelected === false || nextSelected === null) {
|
|
@@ -1732,14 +1902,35 @@ function TrueFalseInner(props, ref) {
|
|
|
1732
1902
|
if (nextCorrect === true || nextCorrect === false || nextCorrect === null) {
|
|
1733
1903
|
setSelectionCorrect(nextCorrect);
|
|
1734
1904
|
}
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1905
|
+
const nextCompletedScore = readNumberField(state, "completedScore");
|
|
1906
|
+
if (typeof nextCompletedScore === "number") setCompletedScore(nextCompletedScore);
|
|
1907
|
+
const nextCompletedMaxScore = readNumberField(state, "completedMaxScore");
|
|
1908
|
+
if (typeof nextCompletedMaxScore === "number") setCompletedMaxScore(nextCompletedMaxScore);
|
|
1909
|
+
const nextPassed = readBooleanField(state, "passed");
|
|
1910
|
+
if (nextPassed === true || nextPassed === false) {
|
|
1911
|
+
setPassed(nextPassed);
|
|
1912
|
+
completedRef.current = nextPassed;
|
|
1913
|
+
if (nextPassed) {
|
|
1914
|
+
const maxScore = nextCompletedMaxScore ?? completedMaxScore ?? 1;
|
|
1915
|
+
const score = nextCompletedScore ?? completedScore ?? maxScore;
|
|
1916
|
+
replayTelemetry(nextSelected ?? null, nextCorrect ?? null, nextPassed, score, maxScore);
|
|
1917
|
+
}
|
|
1918
|
+
}
|
|
1739
1919
|
readBooleanStateField(state, "showSolutions", setShowSolutions);
|
|
1740
1920
|
}
|
|
1741
1921
|
}),
|
|
1742
|
-
[
|
|
1922
|
+
[
|
|
1923
|
+
assessment,
|
|
1924
|
+
checkId,
|
|
1925
|
+
completedMaxScore,
|
|
1926
|
+
completedScore,
|
|
1927
|
+
passed,
|
|
1928
|
+
props.passingScore,
|
|
1929
|
+
props.question,
|
|
1930
|
+
selected,
|
|
1931
|
+
selectionCorrect,
|
|
1932
|
+
showSolutions
|
|
1933
|
+
]
|
|
1743
1934
|
);
|
|
1744
1935
|
useAssessmentHandleRegistration(checkId, handle, ref);
|
|
1745
1936
|
const submit = (value) => {
|
|
@@ -1758,6 +1949,8 @@ function TrueFalseInner(props, ref) {
|
|
|
1758
1949
|
if (scored.passed && !completedRef.current) {
|
|
1759
1950
|
completedRef.current = true;
|
|
1760
1951
|
setPassed(true);
|
|
1952
|
+
setCompletedScore(scored.score);
|
|
1953
|
+
setCompletedMaxScore(scored.maxScore);
|
|
1761
1954
|
assessment.complete({
|
|
1762
1955
|
checkId,
|
|
1763
1956
|
interactionType: INTERACTION,
|
|
@@ -1768,12 +1961,12 @@ function TrueFalseInner(props, ref) {
|
|
|
1768
1961
|
}
|
|
1769
1962
|
};
|
|
1770
1963
|
const reveal = showSolutions || passed && props.enableSolutionsButton;
|
|
1771
|
-
return /* @__PURE__ */ (0,
|
|
1772
|
-
/* @__PURE__ */ (0,
|
|
1773
|
-
/* @__PURE__ */ (0,
|
|
1774
|
-
/* @__PURE__ */ (0,
|
|
1775
|
-
/* @__PURE__ */ (0,
|
|
1776
|
-
/* @__PURE__ */ (0,
|
|
1964
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("section", { "aria-label": "True or False", "data-lk-check-id": checkId, children: [
|
|
1965
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { id: questionId, children: props.question }),
|
|
1966
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("fieldset", { "aria-labelledby": questionId, children: [
|
|
1967
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("legend", { className: "lk-visually-hidden", children: "True or False" }),
|
|
1968
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("label", { style: { display: "block", marginRight: "1rem" }, children: [
|
|
1969
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
1777
1970
|
"input",
|
|
1778
1971
|
{
|
|
1779
1972
|
type: "radio",
|
|
@@ -1785,8 +1978,8 @@ function TrueFalseInner(props, ref) {
|
|
|
1785
1978
|
),
|
|
1786
1979
|
"True"
|
|
1787
1980
|
] }),
|
|
1788
|
-
/* @__PURE__ */ (0,
|
|
1789
|
-
/* @__PURE__ */ (0,
|
|
1981
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("label", { style: { display: "block" }, children: [
|
|
1982
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
1790
1983
|
"input",
|
|
1791
1984
|
{
|
|
1792
1985
|
type: "radio",
|
|
@@ -1799,49 +1992,49 @@ function TrueFalseInner(props, ref) {
|
|
|
1799
1992
|
"False"
|
|
1800
1993
|
] })
|
|
1801
1994
|
] }),
|
|
1802
|
-
reveal ? /* @__PURE__ */ (0,
|
|
1995
|
+
reveal ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("p", { children: [
|
|
1803
1996
|
"Correct answer: ",
|
|
1804
|
-
/* @__PURE__ */ (0,
|
|
1997
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("strong", { children: props.answer ? "True" : "False" })
|
|
1805
1998
|
] }) : null,
|
|
1806
|
-
selected !== null && selectionCorrect !== null ? /* @__PURE__ */ (0,
|
|
1807
|
-
props.enableRetry && passed ? /* @__PURE__ */ (0,
|
|
1808
|
-
props.enableSolutionsButton && !reveal ? /* @__PURE__ */ (0,
|
|
1999
|
+
selected !== null && selectionCorrect !== null ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { role: "status", "aria-live": "polite", children: selectionCorrect ? "Correct" : "Try again" }) : null,
|
|
2000
|
+
props.enableRetry && passed ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", onClick: reset, children: "Try again" }) : null,
|
|
2001
|
+
props.enableSolutionsButton && !reveal ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", onClick: () => setShowSolutions(true), children: "Show solution" }) : null
|
|
1809
2002
|
] });
|
|
1810
2003
|
}
|
|
1811
|
-
var TrueFalseInnerForwarded = (0,
|
|
1812
|
-
var TrueFalse = (0,
|
|
1813
|
-
return /* @__PURE__ */ (0,
|
|
2004
|
+
var TrueFalseInnerForwarded = (0, import_react14.forwardRef)(TrueFalseInner);
|
|
2005
|
+
var TrueFalse = (0, import_react14.forwardRef)(function TrueFalse2(props, ref) {
|
|
2006
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(AssessmentLessonGuard, { blockLabel: "TrueFalse", checkId: props.checkId, children: (lessonId) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(TrueFalseInnerForwarded, { ...props, enclosingLessonId: lessonId, ref }) });
|
|
1814
2007
|
});
|
|
1815
2008
|
|
|
1816
2009
|
// src/blocks/MarkTheWords.tsx
|
|
1817
|
-
var
|
|
1818
|
-
var
|
|
2010
|
+
var import_react15 = __toESM(require("react"), 1);
|
|
2011
|
+
var import_jsx_runtime9 = require("react/jsx-runtime");
|
|
1819
2012
|
var INTERACTION2 = "markTheWords";
|
|
1820
2013
|
function tokenize(text) {
|
|
1821
2014
|
return text.split(/(\s+)/).filter((t) => t.length > 0);
|
|
1822
2015
|
}
|
|
1823
2016
|
function MarkTheWordsInner(props, ref) {
|
|
1824
|
-
const checkId = (0,
|
|
2017
|
+
const checkId = (0, import_react15.useMemo)(() => normalizeComponentId(props.checkId, "checkId"), [props.checkId]);
|
|
1825
2018
|
const assessment = useAssessmentState(props.enclosingLessonId);
|
|
1826
|
-
const tokens = (0,
|
|
1827
|
-
const correctSet = (0,
|
|
2019
|
+
const tokens = (0, import_react15.useMemo)(() => tokenize(props.text), [props.text]);
|
|
2020
|
+
const correctSet = (0, import_react15.useMemo)(
|
|
1828
2021
|
() => new Set(props.correctWords.map((w) => w.toLowerCase())),
|
|
1829
2022
|
[props.correctWords]
|
|
1830
2023
|
);
|
|
1831
|
-
const [marked, setMarked] = (0,
|
|
1832
|
-
const [passed, setPassed] = (0,
|
|
1833
|
-
const [showSolutions, setShowSolutions] = (0,
|
|
1834
|
-
const completedRef = (0,
|
|
2024
|
+
const [marked, setMarked] = (0, import_react15.useState)(() => /* @__PURE__ */ new Set());
|
|
2025
|
+
const [passed, setPassed] = (0, import_react15.useState)(false);
|
|
2026
|
+
const [showSolutions, setShowSolutions] = (0, import_react15.useState)(false);
|
|
2027
|
+
const completedRef = (0, import_react15.useRef)(false);
|
|
1835
2028
|
const reset = () => {
|
|
1836
2029
|
completedRef.current = false;
|
|
1837
2030
|
setPassed(false);
|
|
1838
2031
|
setMarked(/* @__PURE__ */ new Set());
|
|
1839
2032
|
setShowSolutions(false);
|
|
1840
2033
|
};
|
|
1841
|
-
(0,
|
|
2034
|
+
(0, import_react15.useEffect)(() => {
|
|
1842
2035
|
reset();
|
|
1843
2036
|
}, [checkId, props.text, props.correctWords.join("\0")]);
|
|
1844
|
-
const selectableIndices = (0,
|
|
2037
|
+
const selectableIndices = (0, import_react15.useMemo)(() => {
|
|
1845
2038
|
const indices = [];
|
|
1846
2039
|
tokens.forEach((t, i) => {
|
|
1847
2040
|
if (!/^\s+$/.test(t) && correctSet.has(t.toLowerCase())) indices.push(i);
|
|
@@ -1853,7 +2046,7 @@ function MarkTheWordsInner(props, ref) {
|
|
|
1853
2046
|
const maxScore = selectableIndices.length;
|
|
1854
2047
|
const score = allMarked ? maxScore : marked.size;
|
|
1855
2048
|
const passedThreshold = meetsPassingThreshold(score, maxScore || 1, props.passingScore);
|
|
1856
|
-
const handle = (0,
|
|
2049
|
+
const handle = (0, import_react15.useMemo)(
|
|
1857
2050
|
() => buildAssessmentHandle({
|
|
1858
2051
|
checkId,
|
|
1859
2052
|
getScore: () => score,
|
|
@@ -1892,7 +2085,7 @@ function MarkTheWordsInner(props, ref) {
|
|
|
1892
2085
|
return next;
|
|
1893
2086
|
});
|
|
1894
2087
|
};
|
|
1895
|
-
(0,
|
|
2088
|
+
(0, import_react15.useEffect)(() => {
|
|
1896
2089
|
if (!hasTargets) {
|
|
1897
2090
|
if (isDevEnvironment4()) {
|
|
1898
2091
|
console.warn(
|
|
@@ -1910,7 +2103,7 @@ function MarkTheWordsInner(props, ref) {
|
|
|
1910
2103
|
interactionType: INTERACTION2,
|
|
1911
2104
|
question: props.text,
|
|
1912
2105
|
response: [...marked].map((i) => tokens[i]),
|
|
1913
|
-
correct:
|
|
2106
|
+
correct: passedThreshold
|
|
1914
2107
|
});
|
|
1915
2108
|
assessment.complete({
|
|
1916
2109
|
checkId,
|
|
@@ -1932,20 +2125,20 @@ function MarkTheWordsInner(props, ref) {
|
|
|
1932
2125
|
score,
|
|
1933
2126
|
tokens
|
|
1934
2127
|
]);
|
|
1935
|
-
return /* @__PURE__ */ (0,
|
|
1936
|
-
!hasTargets ? /* @__PURE__ */ (0,
|
|
2128
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("section", { "aria-label": "Mark the Words", "data-lk-check-id": checkId, children: [
|
|
2129
|
+
!hasTargets ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("p", { role: "alert", children: [
|
|
1937
2130
|
"No words in this sentence match ",
|
|
1938
|
-
/* @__PURE__ */ (0,
|
|
2131
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("code", { children: "correctWords" }),
|
|
1939
2132
|
". Check spelling and capitalization in the source text."
|
|
1940
2133
|
] }) : null,
|
|
1941
|
-
/* @__PURE__ */ (0,
|
|
1942
|
-
/* @__PURE__ */ (0,
|
|
2134
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { id: `${checkId}-hint`, children: "Select the correct words in the sentence." }),
|
|
2135
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { "aria-describedby": `${checkId}-hint`, children: tokens.map((token, i) => {
|
|
1943
2136
|
const isWord = !/^\s+$/.test(token);
|
|
1944
2137
|
const isTarget = isWord && correctSet.has(token.toLowerCase());
|
|
1945
|
-
if (!isTarget) return /* @__PURE__ */ (0,
|
|
2138
|
+
if (!isTarget) return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react15.default.Fragment, { children: token }, i);
|
|
1946
2139
|
const selected = marked.has(i);
|
|
1947
2140
|
const solution = showSolutions || passed && props.enableSolutionsButton;
|
|
1948
|
-
return /* @__PURE__ */ (0,
|
|
2141
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
1949
2142
|
"button",
|
|
1950
2143
|
{
|
|
1951
2144
|
type: "button",
|
|
@@ -1963,18 +2156,18 @@ function MarkTheWordsInner(props, ref) {
|
|
|
1963
2156
|
i
|
|
1964
2157
|
);
|
|
1965
2158
|
}) }),
|
|
1966
|
-
allMarked ? /* @__PURE__ */ (0,
|
|
1967
|
-
props.enableRetry && passed ? /* @__PURE__ */ (0,
|
|
1968
|
-
props.enableSolutionsButton && !showSolutions ? /* @__PURE__ */ (0,
|
|
2159
|
+
allMarked ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { role: "status", "aria-live": "polite", children: "Correct" }) : null,
|
|
2160
|
+
props.enableRetry && passed ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { type: "button", onClick: reset, children: "Try again" }) : null,
|
|
2161
|
+
props.enableSolutionsButton && !showSolutions ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { type: "button", onClick: () => setShowSolutions(true), children: "Show solution" }) : null
|
|
1969
2162
|
] });
|
|
1970
2163
|
}
|
|
1971
|
-
var MarkTheWordsInnerForwarded = (0,
|
|
1972
|
-
var MarkTheWords = (0,
|
|
1973
|
-
return /* @__PURE__ */ (0,
|
|
2164
|
+
var MarkTheWordsInnerForwarded = (0, import_react15.forwardRef)(MarkTheWordsInner);
|
|
2165
|
+
var MarkTheWords = (0, import_react15.forwardRef)(function MarkTheWords2(props, ref) {
|
|
2166
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(AssessmentLessonGuard, { blockLabel: "MarkTheWords", checkId: props.checkId, children: (lessonId) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(MarkTheWordsInnerForwarded, { ...props, enclosingLessonId: lessonId, ref }) });
|
|
1974
2167
|
});
|
|
1975
2168
|
|
|
1976
2169
|
// src/blocks/FillInTheBlanks.tsx
|
|
1977
|
-
var
|
|
2170
|
+
var import_react16 = __toESM(require("react"), 1);
|
|
1978
2171
|
|
|
1979
2172
|
// src/assessment/internal/parseStarDelimitedTemplate.ts
|
|
1980
2173
|
function parseStarDelimitedTemplate(template, idPrefix) {
|
|
@@ -1995,7 +2188,7 @@ function parseStarDelimitedTemplate(template, idPrefix) {
|
|
|
1995
2188
|
}
|
|
1996
2189
|
|
|
1997
2190
|
// src/blocks/FillInTheBlanks.tsx
|
|
1998
|
-
var
|
|
2191
|
+
var import_jsx_runtime10 = require("react/jsx-runtime");
|
|
1999
2192
|
var INTERACTION3 = "fillInBlanks";
|
|
2000
2193
|
function parseTemplate(template) {
|
|
2001
2194
|
const { parts, values } = parseStarDelimitedTemplate(template, "blank");
|
|
@@ -2005,25 +2198,27 @@ function parseTemplate(template) {
|
|
|
2005
2198
|
};
|
|
2006
2199
|
}
|
|
2007
2200
|
function FillInTheBlanksInner(props, ref) {
|
|
2008
|
-
const checkId = (0,
|
|
2201
|
+
const checkId = (0, import_react16.useMemo)(() => normalizeComponentId(props.checkId, "checkId"), [props.checkId]);
|
|
2009
2202
|
const assessment = useAssessmentState(props.enclosingLessonId);
|
|
2010
|
-
const parsed = (0,
|
|
2203
|
+
const parsed = (0, import_react16.useMemo)(() => parseTemplate(props.template), [props.template]);
|
|
2011
2204
|
const blanks = props.blanks ?? parsed.blanks;
|
|
2012
|
-
const [values, setValues] = (0,
|
|
2205
|
+
const [values, setValues] = (0, import_react16.useState)(
|
|
2013
2206
|
() => Object.fromEntries(blanks.map((b) => [b.id, ""]))
|
|
2014
2207
|
);
|
|
2015
|
-
const [passed, setPassed] = (0,
|
|
2016
|
-
const [showSolutions, setShowSolutions] = (0,
|
|
2017
|
-
const
|
|
2018
|
-
const
|
|
2208
|
+
const [passed, setPassed] = (0, import_react16.useState)(false);
|
|
2209
|
+
const [showSolutions, setShowSolutions] = (0, import_react16.useState)(false);
|
|
2210
|
+
const [submitted, setSubmitted] = (0, import_react16.useState)(false);
|
|
2211
|
+
const completedRef = (0, import_react16.useRef)(false);
|
|
2212
|
+
const answeredRef = (0, import_react16.useRef)(false);
|
|
2019
2213
|
const reset = () => {
|
|
2020
2214
|
completedRef.current = false;
|
|
2021
2215
|
answeredRef.current = false;
|
|
2022
2216
|
setPassed(false);
|
|
2023
2217
|
setValues(Object.fromEntries(blanks.map((b) => [b.id, ""])));
|
|
2024
2218
|
setShowSolutions(false);
|
|
2219
|
+
setSubmitted(false);
|
|
2025
2220
|
};
|
|
2026
|
-
(0,
|
|
2221
|
+
(0, import_react16.useEffect)(() => {
|
|
2027
2222
|
reset();
|
|
2028
2223
|
}, [checkId, props.template, blanks.map((b) => b.answer).join("\0")]);
|
|
2029
2224
|
const hasBlanks = blanks.length > 0;
|
|
@@ -2034,7 +2229,7 @@ function FillInTheBlanksInner(props, ref) {
|
|
|
2034
2229
|
});
|
|
2035
2230
|
const maxScore = blanks.length;
|
|
2036
2231
|
const passedThreshold = meetsPassingThreshold(score, maxScore || 1, props.passingScore);
|
|
2037
|
-
const handle = (0,
|
|
2232
|
+
const handle = (0, import_react16.useMemo)(
|
|
2038
2233
|
() => buildAssessmentHandle({
|
|
2039
2234
|
checkId,
|
|
2040
2235
|
getScore: () => score,
|
|
@@ -2050,7 +2245,7 @@ function FillInTheBlanksInner(props, ref) {
|
|
|
2050
2245
|
score,
|
|
2051
2246
|
maxScore: maxScore || 1
|
|
2052
2247
|
}),
|
|
2053
|
-
getCurrentState: () => ({ values, passed, showSolutions }),
|
|
2248
|
+
getCurrentState: () => ({ values, passed, showSolutions, submitted }),
|
|
2054
2249
|
resume: (state) => {
|
|
2055
2250
|
const raw = state.values;
|
|
2056
2251
|
if (raw && typeof raw === "object") setValues({ ...raw });
|
|
@@ -2060,9 +2255,13 @@ function FillInTheBlanksInner(props, ref) {
|
|
|
2060
2255
|
answeredRef.current = value;
|
|
2061
2256
|
});
|
|
2062
2257
|
readBooleanStateField(state, "showSolutions", setShowSolutions);
|
|
2258
|
+
readBooleanStateField(state, "submitted", (value) => {
|
|
2259
|
+
setSubmitted(value);
|
|
2260
|
+
if (value) answeredRef.current = true;
|
|
2261
|
+
});
|
|
2063
2262
|
}
|
|
2064
2263
|
}),
|
|
2065
|
-
[allFilled, checkId, maxScore, passed, passedThreshold, score, showSolutions, values]
|
|
2264
|
+
[allFilled, checkId, maxScore, passed, passedThreshold, score, showSolutions, submitted, values]
|
|
2066
2265
|
);
|
|
2067
2266
|
useAssessmentHandleRegistration(checkId, handle, ref);
|
|
2068
2267
|
const check = () => {
|
|
@@ -2073,16 +2272,16 @@ function FillInTheBlanksInner(props, ref) {
|
|
|
2073
2272
|
return;
|
|
2074
2273
|
}
|
|
2075
2274
|
if (!allFilled) return;
|
|
2076
|
-
if (
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
}
|
|
2275
|
+
if (answeredRef.current || submitted) return;
|
|
2276
|
+
answeredRef.current = true;
|
|
2277
|
+
setSubmitted(true);
|
|
2278
|
+
assessment.answer({
|
|
2279
|
+
checkId,
|
|
2280
|
+
interactionType: INTERACTION3,
|
|
2281
|
+
question: props.template,
|
|
2282
|
+
response: values,
|
|
2283
|
+
correct: passedThreshold
|
|
2284
|
+
});
|
|
2086
2285
|
if (passedThreshold && !completedRef.current) {
|
|
2087
2286
|
completedRef.current = true;
|
|
2088
2287
|
setPassed(true);
|
|
@@ -2095,20 +2294,23 @@ function FillInTheBlanksInner(props, ref) {
|
|
|
2095
2294
|
});
|
|
2096
2295
|
}
|
|
2097
2296
|
};
|
|
2098
|
-
(0,
|
|
2099
|
-
if (!allFilled)
|
|
2297
|
+
(0, import_react16.useEffect)(() => {
|
|
2298
|
+
if (!allFilled) {
|
|
2299
|
+
answeredRef.current = false;
|
|
2300
|
+
setSubmitted(false);
|
|
2301
|
+
}
|
|
2100
2302
|
}, [allFilled]);
|
|
2101
|
-
(0,
|
|
2303
|
+
(0, import_react16.useEffect)(() => {
|
|
2102
2304
|
if (props.autoCheck && allFilled) check();
|
|
2103
2305
|
}, [allFilled, props.autoCheck, values, passedThreshold]);
|
|
2104
2306
|
const reveal = showSolutions || passed && props.enableSolutionsButton;
|
|
2105
|
-
return /* @__PURE__ */ (0,
|
|
2106
|
-
/* @__PURE__ */ (0,
|
|
2307
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("section", { "aria-label": "Fill in the Blanks", "data-lk-check-id": checkId, children: [
|
|
2308
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { children: parsed.parts.map((part, i) => {
|
|
2107
2309
|
const blank = blanks.find((b) => b.id === part);
|
|
2108
|
-
if (!blank) return /* @__PURE__ */ (0,
|
|
2109
|
-
return /* @__PURE__ */ (0,
|
|
2110
|
-
/* @__PURE__ */ (0,
|
|
2111
|
-
/* @__PURE__ */ (0,
|
|
2310
|
+
if (!blank) return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react16.default.Fragment, { children: part }, i);
|
|
2311
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("label", { style: { margin: "0 0.25em" }, children: [
|
|
2312
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "lk-visually-hidden", children: blank.answer }),
|
|
2313
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
2112
2314
|
"input",
|
|
2113
2315
|
{
|
|
2114
2316
|
type: "text",
|
|
@@ -2124,49 +2326,51 @@ function FillInTheBlanksInner(props, ref) {
|
|
|
2124
2326
|
)
|
|
2125
2327
|
] }, blank.id);
|
|
2126
2328
|
}) }),
|
|
2127
|
-
!props.autoCheck ? /* @__PURE__ */ (0,
|
|
2128
|
-
!hasBlanks ? /* @__PURE__ */ (0,
|
|
2129
|
-
|
|
2130
|
-
props.enableRetry && passed ? /* @__PURE__ */ (0,
|
|
2131
|
-
props.enableSolutionsButton && !reveal ? /* @__PURE__ */ (0,
|
|
2329
|
+
!props.autoCheck ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { type: "button", "data-testid": "check-blanks", disabled: !allFilled || passed, onClick: check, children: "Check" }) : null,
|
|
2330
|
+
!hasBlanks ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { role: "alert", children: "This activity has no blanks. Add text wrapped in asterisks, e.g. The *answer* here." }) : null,
|
|
2331
|
+
submitted ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { role: "status", "aria-live": "polite", children: passed || passedThreshold ? "Correct" : "Try again" }) : null,
|
|
2332
|
+
props.enableRetry && passed ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { type: "button", onClick: reset, children: "Try again" }) : null,
|
|
2333
|
+
props.enableSolutionsButton && !reveal ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { type: "button", onClick: () => setShowSolutions(true), children: "Show solution" }) : null
|
|
2132
2334
|
] });
|
|
2133
2335
|
}
|
|
2134
|
-
var FillInTheBlanksInnerForwarded = (0,
|
|
2135
|
-
var FillInTheBlanks = (0,
|
|
2336
|
+
var FillInTheBlanksInnerForwarded = (0, import_react16.forwardRef)(FillInTheBlanksInner);
|
|
2337
|
+
var FillInTheBlanks = (0, import_react16.forwardRef)(
|
|
2136
2338
|
function FillInTheBlanks2(props, ref) {
|
|
2137
|
-
return /* @__PURE__ */ (0,
|
|
2339
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(AssessmentLessonGuard, { blockLabel: "FillInTheBlanks", checkId: props.checkId, children: (lessonId) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(FillInTheBlanksInnerForwarded, { ...props, enclosingLessonId: lessonId, ref }) });
|
|
2138
2340
|
}
|
|
2139
2341
|
);
|
|
2140
2342
|
|
|
2141
2343
|
// src/blocks/DragTheWords.tsx
|
|
2142
|
-
var
|
|
2143
|
-
var
|
|
2344
|
+
var import_react17 = __toESM(require("react"), 1);
|
|
2345
|
+
var import_jsx_runtime11 = require("react/jsx-runtime");
|
|
2144
2346
|
var INTERACTION4 = "dragTheWords";
|
|
2145
2347
|
function parseZones(template) {
|
|
2146
2348
|
const { parts, values } = parseStarDelimitedTemplate(template, "zone");
|
|
2147
2349
|
return { parts, answers: values };
|
|
2148
2350
|
}
|
|
2149
2351
|
function DragTheWordsInner(props, ref) {
|
|
2150
|
-
const checkId = (0,
|
|
2352
|
+
const checkId = (0, import_react17.useMemo)(() => normalizeComponentId(props.checkId, "checkId"), [props.checkId]);
|
|
2151
2353
|
const assessment = useAssessmentState(props.enclosingLessonId);
|
|
2152
|
-
const { parts, answers } = (0,
|
|
2153
|
-
const [zones, setZones] = (0,
|
|
2354
|
+
const { parts, answers } = (0, import_react17.useMemo)(() => parseZones(props.template), [props.template]);
|
|
2355
|
+
const [zones, setZones] = (0, import_react17.useState)(
|
|
2154
2356
|
() => Object.fromEntries(answers.map((_, i) => [`zone-${i}`, ""]))
|
|
2155
2357
|
);
|
|
2156
|
-
const [pool, setPool] = (0,
|
|
2157
|
-
const [keyboardWord, setKeyboardWord] = (0,
|
|
2158
|
-
const [passed, setPassed] = (0,
|
|
2159
|
-
const
|
|
2160
|
-
const
|
|
2358
|
+
const [pool, setPool] = (0, import_react17.useState)(() => [...props.words]);
|
|
2359
|
+
const [keyboardWord, setKeyboardWord] = (0, import_react17.useState)(null);
|
|
2360
|
+
const [passed, setPassed] = (0, import_react17.useState)(false);
|
|
2361
|
+
const [submitted, setSubmitted] = (0, import_react17.useState)(false);
|
|
2362
|
+
const completedRef = (0, import_react17.useRef)(false);
|
|
2363
|
+
const answeredRef = (0, import_react17.useRef)(false);
|
|
2161
2364
|
const reset = () => {
|
|
2162
2365
|
completedRef.current = false;
|
|
2163
2366
|
answeredRef.current = false;
|
|
2164
2367
|
setPassed(false);
|
|
2368
|
+
setSubmitted(false);
|
|
2165
2369
|
setZones(Object.fromEntries(answers.map((_, i) => [`zone-${i}`, ""])));
|
|
2166
2370
|
setPool([...props.words]);
|
|
2167
2371
|
setKeyboardWord(null);
|
|
2168
2372
|
};
|
|
2169
|
-
(0,
|
|
2373
|
+
(0, import_react17.useEffect)(() => {
|
|
2170
2374
|
reset();
|
|
2171
2375
|
}, [checkId, props.template, props.words.join("\0")]);
|
|
2172
2376
|
const hasZones = answers.length > 0;
|
|
@@ -2177,7 +2381,7 @@ function DragTheWordsInner(props, ref) {
|
|
|
2177
2381
|
});
|
|
2178
2382
|
const maxScore = answers.length;
|
|
2179
2383
|
const passedThreshold = meetsPassingThreshold(score, maxScore || 1, props.passingScore);
|
|
2180
|
-
const handle = (0,
|
|
2384
|
+
const handle = (0, import_react17.useMemo)(
|
|
2181
2385
|
() => buildAssessmentHandle({
|
|
2182
2386
|
checkId,
|
|
2183
2387
|
getScore: () => score,
|
|
@@ -2194,7 +2398,7 @@ function DragTheWordsInner(props, ref) {
|
|
|
2194
2398
|
score,
|
|
2195
2399
|
maxScore: maxScore || 1
|
|
2196
2400
|
}),
|
|
2197
|
-
getCurrentState: () => ({ zones, pool, passed, keyboardWord }),
|
|
2401
|
+
getCurrentState: () => ({ zones, pool, passed, keyboardWord, submitted }),
|
|
2198
2402
|
resume: (state) => {
|
|
2199
2403
|
const rawZones = state.zones;
|
|
2200
2404
|
if (rawZones && typeof rawZones === "object") setZones({ ...rawZones });
|
|
@@ -2204,11 +2408,15 @@ function DragTheWordsInner(props, ref) {
|
|
|
2204
2408
|
completedRef.current = value;
|
|
2205
2409
|
answeredRef.current = value;
|
|
2206
2410
|
});
|
|
2411
|
+
readBooleanStateField(state, "submitted", (value) => {
|
|
2412
|
+
setSubmitted(value);
|
|
2413
|
+
if (value) answeredRef.current = true;
|
|
2414
|
+
});
|
|
2207
2415
|
const kw = state.keyboardWord;
|
|
2208
2416
|
if (kw === null || typeof kw === "string") setKeyboardWord(kw ?? null);
|
|
2209
2417
|
}
|
|
2210
2418
|
}),
|
|
2211
|
-
[allFilled, checkId, keyboardWord, maxScore, passed, passedThreshold, pool, score, zones]
|
|
2419
|
+
[allFilled, checkId, keyboardWord, maxScore, passed, passedThreshold, pool, score, submitted, zones]
|
|
2212
2420
|
);
|
|
2213
2421
|
useAssessmentHandleRegistration(checkId, handle, ref);
|
|
2214
2422
|
const placeInZone = (zoneId, word) => {
|
|
@@ -2238,16 +2446,16 @@ function DragTheWordsInner(props, ref) {
|
|
|
2238
2446
|
return;
|
|
2239
2447
|
}
|
|
2240
2448
|
if (!allFilled) return;
|
|
2241
|
-
if (
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
}
|
|
2449
|
+
if (answeredRef.current || submitted) return;
|
|
2450
|
+
answeredRef.current = true;
|
|
2451
|
+
setSubmitted(true);
|
|
2452
|
+
assessment.answer({
|
|
2453
|
+
checkId,
|
|
2454
|
+
interactionType: INTERACTION4,
|
|
2455
|
+
question: props.template,
|
|
2456
|
+
response: zones,
|
|
2457
|
+
correct: passedThreshold
|
|
2458
|
+
});
|
|
2251
2459
|
if (passedThreshold && !completedRef.current) {
|
|
2252
2460
|
completedRef.current = true;
|
|
2253
2461
|
setPassed(true);
|
|
@@ -2260,15 +2468,18 @@ function DragTheWordsInner(props, ref) {
|
|
|
2260
2468
|
});
|
|
2261
2469
|
}
|
|
2262
2470
|
};
|
|
2263
|
-
(0,
|
|
2264
|
-
if (!allFilled)
|
|
2471
|
+
(0, import_react17.useEffect)(() => {
|
|
2472
|
+
if (!allFilled) {
|
|
2473
|
+
answeredRef.current = false;
|
|
2474
|
+
setSubmitted(false);
|
|
2475
|
+
}
|
|
2265
2476
|
}, [allFilled]);
|
|
2266
|
-
(0,
|
|
2477
|
+
(0, import_react17.useEffect)(() => {
|
|
2267
2478
|
if (props.autoCheck && allFilled) check();
|
|
2268
2479
|
}, [allFilled, props.autoCheck, zones, passedThreshold]);
|
|
2269
|
-
return /* @__PURE__ */ (0,
|
|
2270
|
-
/* @__PURE__ */ (0,
|
|
2271
|
-
/* @__PURE__ */ (0,
|
|
2480
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("section", { "aria-label": "Drag the Words", "data-lk-check-id": checkId, children: [
|
|
2481
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { children: "Drag words into the blanks (or select a word, then activate a blank)." }),
|
|
2482
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { role: "list", "aria-label": "Word bank", "data-testid": "word-bank", children: pool.map((word) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
2272
2483
|
"button",
|
|
2273
2484
|
{
|
|
2274
2485
|
type: "button",
|
|
@@ -2282,9 +2493,9 @@ function DragTheWordsInner(props, ref) {
|
|
|
2282
2493
|
},
|
|
2283
2494
|
word
|
|
2284
2495
|
)) }),
|
|
2285
|
-
/* @__PURE__ */ (0,
|
|
2286
|
-
if (!part.startsWith("zone-")) return /* @__PURE__ */ (0,
|
|
2287
|
-
return /* @__PURE__ */ (0,
|
|
2496
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { children: parts.map((part, i) => {
|
|
2497
|
+
if (!part.startsWith("zone-")) return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_react17.default.Fragment, { children: part }, i);
|
|
2498
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
2288
2499
|
"span",
|
|
2289
2500
|
{
|
|
2290
2501
|
role: "button",
|
|
@@ -2308,53 +2519,56 @@ function DragTheWordsInner(props, ref) {
|
|
|
2308
2519
|
part
|
|
2309
2520
|
);
|
|
2310
2521
|
}) }),
|
|
2311
|
-
/* @__PURE__ */ (0,
|
|
2312
|
-
!hasZones ? /* @__PURE__ */ (0,
|
|
2313
|
-
|
|
2522
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("button", { type: "button", "data-testid": "check-drag-words", disabled: !allFilled || passed, onClick: check, children: "Check" }),
|
|
2523
|
+
!hasZones ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { role: "alert", children: "This activity has no drop zones. Wrap answers in asterisks in the template." }) : null,
|
|
2524
|
+
submitted ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { role: "status", "aria-live": "polite", children: passed || passedThreshold ? "Correct" : "Try again" }) : null
|
|
2314
2525
|
] });
|
|
2315
2526
|
}
|
|
2316
|
-
var DragTheWordsInnerForwarded = (0,
|
|
2317
|
-
var DragTheWords = (0,
|
|
2318
|
-
return /* @__PURE__ */ (0,
|
|
2527
|
+
var DragTheWordsInnerForwarded = (0, import_react17.forwardRef)(DragTheWordsInner);
|
|
2528
|
+
var DragTheWords = (0, import_react17.forwardRef)(function DragTheWords2(props, ref) {
|
|
2529
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(AssessmentLessonGuard, { blockLabel: "DragTheWords", checkId: props.checkId, children: (lessonId) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(DragTheWordsInnerForwarded, { ...props, enclosingLessonId: lessonId, ref }) });
|
|
2319
2530
|
});
|
|
2320
2531
|
|
|
2321
2532
|
// src/blocks/DragAndDrop.tsx
|
|
2322
|
-
var
|
|
2323
|
-
var
|
|
2533
|
+
var import_react18 = require("react");
|
|
2534
|
+
var import_jsx_runtime12 = require("react/jsx-runtime");
|
|
2324
2535
|
var INTERACTION5 = "dragAndDrop";
|
|
2325
2536
|
function DragAndDropInner(props, ref) {
|
|
2326
|
-
const checkId = (0,
|
|
2537
|
+
const checkId = (0, import_react18.useMemo)(() => normalizeComponentId(props.checkId, "checkId"), [props.checkId]);
|
|
2327
2538
|
const assessment = useAssessmentState(props.enclosingLessonId);
|
|
2328
|
-
const [assignments, setAssignments] = (0,
|
|
2539
|
+
const [assignments, setAssignments] = (0, import_react18.useState)(
|
|
2329
2540
|
() => Object.fromEntries(props.targets.map((t) => [t.id, ""]))
|
|
2330
2541
|
);
|
|
2331
|
-
const [pool, setPool] = (0,
|
|
2332
|
-
const [keyboardItem, setKeyboardItem] = (0,
|
|
2333
|
-
const [passed, setPassed] = (0,
|
|
2334
|
-
const
|
|
2542
|
+
const [pool, setPool] = (0, import_react18.useState)(() => props.items.map((i) => i.id));
|
|
2543
|
+
const [keyboardItem, setKeyboardItem] = (0, import_react18.useState)(null);
|
|
2544
|
+
const [passed, setPassed] = (0, import_react18.useState)(false);
|
|
2545
|
+
const [checked, setChecked] = (0, import_react18.useState)(false);
|
|
2546
|
+
const completedRef = (0, import_react18.useRef)(false);
|
|
2335
2547
|
const reset = () => {
|
|
2336
2548
|
completedRef.current = false;
|
|
2337
2549
|
setPassed(false);
|
|
2550
|
+
setChecked(false);
|
|
2338
2551
|
setAssignments(Object.fromEntries(props.targets.map((t) => [t.id, ""])));
|
|
2339
2552
|
setPool(props.items.map((i) => i.id));
|
|
2340
2553
|
setKeyboardItem(null);
|
|
2341
2554
|
};
|
|
2342
|
-
(0,
|
|
2555
|
+
(0, import_react18.useEffect)(() => {
|
|
2343
2556
|
reset();
|
|
2344
2557
|
}, [checkId, props.items.map((i) => i.id).join(","), props.targets.map((t) => t.id).join(",")]);
|
|
2345
|
-
const
|
|
2346
|
-
const
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2558
|
+
const hasTargets = props.targets.length > 0;
|
|
2559
|
+
const allFilled = hasTargets && props.targets.every((t) => (assignments[t.id] ?? "").length > 0);
|
|
2560
|
+
let score = 0;
|
|
2561
|
+
props.targets.forEach((t) => {
|
|
2562
|
+
if (assignments[t.id] === t.accepts) score += 1;
|
|
2563
|
+
});
|
|
2564
|
+
const maxScore = props.targets.length || 1;
|
|
2565
|
+
const passedThreshold = meetsPassingThreshold(score, maxScore, props.passingScore);
|
|
2566
|
+
const handle = (0, import_react18.useMemo)(() => {
|
|
2353
2567
|
return buildAssessmentHandle({
|
|
2354
2568
|
checkId,
|
|
2355
2569
|
getScore: () => score,
|
|
2356
2570
|
getMaxScore: () => maxScore,
|
|
2357
|
-
getAnswerGiven: () => allFilled,
|
|
2571
|
+
getAnswerGiven: () => hasTargets && allFilled,
|
|
2358
2572
|
resetTask: reset,
|
|
2359
2573
|
showSolutions: () => {
|
|
2360
2574
|
},
|
|
@@ -2362,11 +2576,11 @@ function DragAndDropInner(props, ref) {
|
|
|
2362
2576
|
checkId,
|
|
2363
2577
|
interactionType: INTERACTION5,
|
|
2364
2578
|
response: assignments,
|
|
2365
|
-
correct:
|
|
2579
|
+
correct: passedThreshold,
|
|
2366
2580
|
score,
|
|
2367
2581
|
maxScore
|
|
2368
2582
|
}),
|
|
2369
|
-
getCurrentState: () => ({ assignments, pool, passed, keyboardItem }),
|
|
2583
|
+
getCurrentState: () => ({ assignments, pool, passed, checked, keyboardItem }),
|
|
2370
2584
|
resume: (state) => {
|
|
2371
2585
|
const rawAssignments = state.assignments;
|
|
2372
2586
|
if (rawAssignments && typeof rawAssignments === "object") {
|
|
@@ -2377,14 +2591,16 @@ function DragAndDropInner(props, ref) {
|
|
|
2377
2591
|
setPassed(value);
|
|
2378
2592
|
completedRef.current = value;
|
|
2379
2593
|
});
|
|
2594
|
+
readBooleanStateField(state, "checked", setChecked);
|
|
2380
2595
|
const item = state.keyboardItem;
|
|
2381
2596
|
if (item === null || typeof item === "string") setKeyboardItem(item ?? null);
|
|
2382
2597
|
}
|
|
2383
2598
|
});
|
|
2384
|
-
}, [
|
|
2599
|
+
}, [allFilled, assignments, checkId, checked, hasTargets, keyboardItem, maxScore, passed, passedThreshold, pool, props.targets, score]);
|
|
2385
2600
|
useAssessmentHandleRegistration(checkId, handle, ref);
|
|
2386
2601
|
const place = (targetId, itemId) => {
|
|
2387
2602
|
if (passed && !props.enableRetry) return;
|
|
2603
|
+
setChecked(false);
|
|
2388
2604
|
const prev = assignments[targetId];
|
|
2389
2605
|
setAssignments((a) => ({ ...a, [targetId]: itemId }));
|
|
2390
2606
|
setPool((p) => {
|
|
@@ -2396,29 +2612,31 @@ function DragAndDropInner(props, ref) {
|
|
|
2396
2612
|
};
|
|
2397
2613
|
const check = () => {
|
|
2398
2614
|
if (!allFilled) return;
|
|
2615
|
+
setChecked(true);
|
|
2399
2616
|
assessment.answer({
|
|
2400
2617
|
checkId,
|
|
2401
2618
|
interactionType: INTERACTION5,
|
|
2402
2619
|
response: assignments,
|
|
2403
|
-
correct:
|
|
2620
|
+
correct: passedThreshold
|
|
2404
2621
|
});
|
|
2405
|
-
if (
|
|
2622
|
+
if (passedThreshold && !completedRef.current) {
|
|
2406
2623
|
completedRef.current = true;
|
|
2407
2624
|
setPassed(true);
|
|
2408
2625
|
assessment.complete({
|
|
2409
2626
|
checkId,
|
|
2410
2627
|
interactionType: INTERACTION5,
|
|
2411
|
-
score
|
|
2412
|
-
maxScore
|
|
2413
|
-
passingScore: props.passingScore ??
|
|
2628
|
+
score,
|
|
2629
|
+
maxScore,
|
|
2630
|
+
passingScore: props.passingScore ?? maxScore
|
|
2414
2631
|
});
|
|
2415
2632
|
}
|
|
2416
2633
|
};
|
|
2417
|
-
return /* @__PURE__ */ (0,
|
|
2418
|
-
/* @__PURE__ */ (0,
|
|
2419
|
-
/* @__PURE__ */ (0,
|
|
2634
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("section", { "aria-label": "Drag and Drop", "data-lk-check-id": checkId, children: [
|
|
2635
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { children: "Match each item to the correct target (drag or use keyboard: select item, then activate target)." }),
|
|
2636
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { role: "list", "aria-label": "Draggable items", children: pool.flatMap((id) => {
|
|
2420
2637
|
const item = props.items.find((i) => i.id === id);
|
|
2421
|
-
|
|
2638
|
+
if (!item) return [];
|
|
2639
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
2422
2640
|
"button",
|
|
2423
2641
|
{
|
|
2424
2642
|
type: "button",
|
|
@@ -2433,13 +2651,13 @@ function DragAndDropInner(props, ref) {
|
|
|
2433
2651
|
id
|
|
2434
2652
|
);
|
|
2435
2653
|
}) }),
|
|
2436
|
-
/* @__PURE__ */ (0,
|
|
2654
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("ul", { children: props.targets.map((target) => {
|
|
2437
2655
|
const assigned = assignments[target.id];
|
|
2438
2656
|
const label = assigned ? props.items.find((i) => i.id === assigned)?.label ?? assigned : "Drop here";
|
|
2439
|
-
return /* @__PURE__ */ (0,
|
|
2440
|
-
/* @__PURE__ */ (0,
|
|
2657
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("li", { children: [
|
|
2658
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("strong", { children: target.label }),
|
|
2441
2659
|
" ",
|
|
2442
|
-
/* @__PURE__ */ (0,
|
|
2660
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
2443
2661
|
"span",
|
|
2444
2662
|
{
|
|
2445
2663
|
role: "button",
|
|
@@ -2466,30 +2684,31 @@ function DragAndDropInner(props, ref) {
|
|
|
2466
2684
|
)
|
|
2467
2685
|
] }, target.id);
|
|
2468
2686
|
}) }),
|
|
2469
|
-
/* @__PURE__ */ (0,
|
|
2470
|
-
|
|
2687
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("button", { type: "button", "data-testid": "check-drag-drop", disabled: !hasTargets || !allFilled || passed, onClick: check, children: "Check" }),
|
|
2688
|
+
checked ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { role: "status", "aria-live": "polite", children: passedThreshold ? "Correct" : "Try again" }) : null
|
|
2471
2689
|
] });
|
|
2472
2690
|
}
|
|
2473
|
-
var DragAndDropInnerForwarded = (0,
|
|
2474
|
-
var DragAndDrop = (0,
|
|
2475
|
-
return /* @__PURE__ */ (0,
|
|
2691
|
+
var DragAndDropInnerForwarded = (0, import_react18.forwardRef)(DragAndDropInner);
|
|
2692
|
+
var DragAndDrop = (0, import_react18.forwardRef)(function DragAndDrop2(props, ref) {
|
|
2693
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(AssessmentLessonGuard, { blockLabel: "DragAndDrop", checkId: props.checkId, children: (lessonId) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(DragAndDropInnerForwarded, { ...props, enclosingLessonId: lessonId, ref }) });
|
|
2476
2694
|
});
|
|
2477
2695
|
|
|
2478
2696
|
// src/blocks/AssessmentSequence.tsx
|
|
2479
|
-
var
|
|
2697
|
+
var import_react24 = __toESM(require("react"), 1);
|
|
2698
|
+
var import_core17 = require("@lessonkit/core");
|
|
2480
2699
|
|
|
2481
2700
|
// src/compound/useCompoundShell.ts
|
|
2482
|
-
var
|
|
2483
|
-
var
|
|
2701
|
+
var import_react22 = require("react");
|
|
2702
|
+
var import_core15 = require("@lessonkit/core");
|
|
2484
2703
|
|
|
2485
2704
|
// src/compound/useCompoundNavigation.ts
|
|
2486
|
-
var
|
|
2705
|
+
var import_react19 = require("react");
|
|
2487
2706
|
function useCompoundNavigation(pageCount, index, setIndex) {
|
|
2488
|
-
const goNext = (0,
|
|
2707
|
+
const goNext = (0, import_react19.useCallback)(() => {
|
|
2489
2708
|
if (pageCount < 1) return;
|
|
2490
2709
|
setIndex((i) => Math.min(i + 1, pageCount - 1));
|
|
2491
2710
|
}, [pageCount, setIndex]);
|
|
2492
|
-
const goPrev = (0,
|
|
2711
|
+
const goPrev = (0, import_react19.useCallback)(() => {
|
|
2493
2712
|
setIndex((i) => Math.max(i - 1, 0));
|
|
2494
2713
|
}, [setIndex]);
|
|
2495
2714
|
const clampedIndex = pageCount < 1 ? 0 : Math.min(index, pageCount - 1);
|
|
@@ -2503,102 +2722,254 @@ function useCompoundNavigation(pageCount, index, setIndex) {
|
|
|
2503
2722
|
}
|
|
2504
2723
|
|
|
2505
2724
|
// src/compound/useCompoundPersistence.ts
|
|
2506
|
-
var
|
|
2507
|
-
var
|
|
2725
|
+
var import_react21 = require("react");
|
|
2726
|
+
var import_core14 = require("@lessonkit/core");
|
|
2727
|
+
|
|
2728
|
+
// src/compound/resumeChildHandles.ts
|
|
2729
|
+
function filterRegisteredChildStates(handles, childStates) {
|
|
2730
|
+
const filtered = {};
|
|
2731
|
+
for (const [key, value] of Object.entries(childStates)) {
|
|
2732
|
+
if (handles.has(key)) {
|
|
2733
|
+
filtered[key] = value;
|
|
2734
|
+
}
|
|
2735
|
+
}
|
|
2736
|
+
return filtered;
|
|
2737
|
+
}
|
|
2738
|
+
function resumeChildHandles(handles, childStates, opts) {
|
|
2739
|
+
const pendingKeys = Object.keys(childStates);
|
|
2740
|
+
const alreadyResumed = opts?.alreadyResumed;
|
|
2741
|
+
if (opts?.waitForHandles && pendingKeys.length > 0) {
|
|
2742
|
+
if (handles.size === 0) return false;
|
|
2743
|
+
const registeredPending = pendingKeys.filter((k) => handles.has(k));
|
|
2744
|
+
if (registeredPending.length === 0) {
|
|
2745
|
+
return false;
|
|
2746
|
+
}
|
|
2747
|
+
if (registeredPending.length < pendingKeys.length) {
|
|
2748
|
+
for (const key of registeredPending) {
|
|
2749
|
+
if (alreadyResumed?.has(key)) continue;
|
|
2750
|
+
const handle = handles.get(key);
|
|
2751
|
+
const child = childStates[key];
|
|
2752
|
+
if (handle?.resume && child) {
|
|
2753
|
+
handle.resume(child);
|
|
2754
|
+
alreadyResumed?.add(key);
|
|
2755
|
+
}
|
|
2756
|
+
}
|
|
2757
|
+
return false;
|
|
2758
|
+
}
|
|
2759
|
+
}
|
|
2760
|
+
for (const [checkId, handle] of handles) {
|
|
2761
|
+
if (alreadyResumed?.has(checkId)) continue;
|
|
2762
|
+
const child = childStates[checkId];
|
|
2763
|
+
if (child && handle.resume) {
|
|
2764
|
+
handle.resume(child);
|
|
2765
|
+
alreadyResumed?.add(checkId);
|
|
2766
|
+
}
|
|
2767
|
+
}
|
|
2768
|
+
return true;
|
|
2769
|
+
}
|
|
2508
2770
|
|
|
2509
2771
|
// src/compound/useCompoundResume.ts
|
|
2510
|
-
var
|
|
2511
|
-
var import_core11 = require("@lessonkit/core");
|
|
2772
|
+
var import_react20 = require("react");
|
|
2512
2773
|
var import_core12 = require("@lessonkit/core");
|
|
2774
|
+
var import_core13 = require("@lessonkit/core");
|
|
2775
|
+
var warnedCompoundPersistFailure = false;
|
|
2776
|
+
function warnCompoundPersistFailure() {
|
|
2777
|
+
if (warnedCompoundPersistFailure || !isDevEnvironment4()) return;
|
|
2778
|
+
warnedCompoundPersistFailure = true;
|
|
2779
|
+
console.warn(
|
|
2780
|
+
"[lessonkit] compound resume state could not be saved to sessionStorage (quota or privacy mode); progress may be lost on reload."
|
|
2781
|
+
);
|
|
2782
|
+
}
|
|
2513
2783
|
function useCompoundResume(opts) {
|
|
2514
|
-
const
|
|
2515
|
-
const
|
|
2516
|
-
(0,
|
|
2784
|
+
const lessonkitCtx = (0, import_react20.useContext)(LessonkitContext);
|
|
2785
|
+
const storageRef = (0, import_react20.useRef)(opts.storage ?? lessonkitCtx?.storage ?? (0, import_core13.createSessionStoragePort)());
|
|
2786
|
+
const resumedRef = (0, import_react20.useRef)(false);
|
|
2787
|
+
const resumeKeyRef = (0, import_react20.useRef)("");
|
|
2788
|
+
const prevEnabledRef = (0, import_react20.useRef)(opts.enabled);
|
|
2789
|
+
(0, import_react20.useEffect)(() => {
|
|
2790
|
+
storageRef.current = opts.storage ?? lessonkitCtx?.storage ?? (0, import_core13.createSessionStoragePort)();
|
|
2791
|
+
}, [opts.storage, lessonkitCtx?.storage]);
|
|
2792
|
+
(0, import_react20.useEffect)(() => {
|
|
2793
|
+
if (!prevEnabledRef.current && opts.enabled) {
|
|
2794
|
+
resumedRef.current = false;
|
|
2795
|
+
}
|
|
2796
|
+
prevEnabledRef.current = opts.enabled;
|
|
2797
|
+
const key = `${opts.courseId ?? ""}:${opts.compoundId}`;
|
|
2798
|
+
if (resumeKeyRef.current !== key) {
|
|
2799
|
+
resumeKeyRef.current = key;
|
|
2800
|
+
resumedRef.current = false;
|
|
2801
|
+
}
|
|
2517
2802
|
if (!opts.enabled || !opts.courseId || resumedRef.current) return;
|
|
2518
|
-
const saved = (0,
|
|
2803
|
+
const saved = (0, import_core12.loadCompoundState)(storageRef.current, opts.courseId, opts.compoundId);
|
|
2519
2804
|
if (saved) {
|
|
2520
2805
|
resumedRef.current = true;
|
|
2521
2806
|
opts.onResume?.(saved);
|
|
2522
2807
|
}
|
|
2523
2808
|
}, [opts.enabled, opts.courseId, opts.compoundId, opts.onResume]);
|
|
2524
|
-
return (0,
|
|
2809
|
+
return (0, import_react20.useCallback)(
|
|
2525
2810
|
(state) => {
|
|
2526
2811
|
if (!opts.enabled || !opts.courseId) return;
|
|
2527
|
-
(0,
|
|
2812
|
+
const persisted = (0, import_core12.saveCompoundState)(storageRef.current, opts.courseId, opts.compoundId, state);
|
|
2813
|
+
if (!persisted) warnCompoundPersistFailure();
|
|
2528
2814
|
},
|
|
2529
2815
|
[opts.enabled, opts.courseId, opts.compoundId]
|
|
2530
2816
|
);
|
|
2531
2817
|
}
|
|
2532
2818
|
|
|
2533
2819
|
// src/compound/useCompoundPersistence.ts
|
|
2534
|
-
function readCompoundInitialIndex(courseId, compoundId, pageCount, enabled, storage = (0,
|
|
2820
|
+
function readCompoundInitialIndex(courseId, compoundId, pageCount, enabled, storage = (0, import_core14.createSessionStoragePort)()) {
|
|
2535
2821
|
if (!enabled || !courseId || pageCount < 1) return 0;
|
|
2536
|
-
const saved = (0,
|
|
2822
|
+
const saved = (0, import_core14.loadCompoundState)(storage, courseId, compoundId);
|
|
2537
2823
|
if (!saved) return 0;
|
|
2538
|
-
return (0,
|
|
2824
|
+
return (0, import_core14.clampCompoundPageIndex)(saved.activePageIndex, pageCount);
|
|
2825
|
+
}
|
|
2826
|
+
function stripOrphanChildStates(handles, childStates) {
|
|
2827
|
+
return filterRegisteredChildStates(handles, childStates);
|
|
2539
2828
|
}
|
|
2540
2829
|
function useCompoundPersistence(opts) {
|
|
2541
|
-
const
|
|
2830
|
+
const lessonkitCtx = (0, import_react21.useContext)(LessonkitContext);
|
|
2831
|
+
const storage = opts.storage ?? lessonkitCtx?.storage ?? (0, import_core14.createSessionStoragePort)();
|
|
2542
2832
|
const ctx = useCompoundRegistry();
|
|
2543
2833
|
const handlesVersion = useCompoundHandlesVersion();
|
|
2544
|
-
const
|
|
2545
|
-
const
|
|
2546
|
-
const
|
|
2547
|
-
const
|
|
2834
|
+
const bridgeRef = useCompoundHydrationBridgeRef();
|
|
2835
|
+
const pendingChildResumeRef = (0, import_react21.useRef)(null);
|
|
2836
|
+
const resumedChildKeysRef = (0, import_react21.useRef)(/* @__PURE__ */ new Set());
|
|
2837
|
+
const loadedChildStatesRef = (0, import_react21.useRef)({});
|
|
2838
|
+
const skipSaveUntilHydratedRef = (0, import_react21.useRef)(false);
|
|
2839
|
+
const hydrationKeyRef = (0, import_react21.useRef)("");
|
|
2840
|
+
const hydrationInitRef = (0, import_react21.useRef)(false);
|
|
2841
|
+
const hydrationKey = `${opts.courseId ?? ""}:${opts.compoundId}`;
|
|
2842
|
+
if (hydrationKeyRef.current !== hydrationKey) {
|
|
2843
|
+
hydrationKeyRef.current = hydrationKey;
|
|
2844
|
+
hydrationInitRef.current = false;
|
|
2845
|
+
loadedChildStatesRef.current = {};
|
|
2846
|
+
skipSaveUntilHydratedRef.current = false;
|
|
2847
|
+
pendingChildResumeRef.current = null;
|
|
2848
|
+
resumedChildKeysRef.current = /* @__PURE__ */ new Set();
|
|
2849
|
+
}
|
|
2850
|
+
if (!hydrationInitRef.current && opts.enabled && opts.courseId) {
|
|
2851
|
+
hydrationInitRef.current = true;
|
|
2852
|
+
const saved = (0, import_core14.loadCompoundState)(storage, opts.courseId, opts.compoundId);
|
|
2853
|
+
if (saved && Object.keys(saved.childStates).length > 0) {
|
|
2854
|
+
loadedChildStatesRef.current = { ...saved.childStates };
|
|
2855
|
+
skipSaveUntilHydratedRef.current = true;
|
|
2856
|
+
pendingChildResumeRef.current = saved;
|
|
2857
|
+
}
|
|
2858
|
+
}
|
|
2859
|
+
const buildState = (0, import_react21.useCallback)(() => {
|
|
2548
2860
|
const childStates = {
|
|
2549
2861
|
...loadedChildStatesRef.current
|
|
2550
2862
|
};
|
|
2551
2863
|
if (ctx) {
|
|
2552
|
-
for (const [checkId,
|
|
2864
|
+
for (const [checkId, entry] of ctx.getRegisteredHandles()) {
|
|
2865
|
+
const handle = entry.handle;
|
|
2553
2866
|
if (handle.getCurrentState) {
|
|
2554
2867
|
childStates[checkId] = handle.getCurrentState();
|
|
2555
2868
|
delete loadedChildStatesRef.current[checkId];
|
|
2556
2869
|
}
|
|
2557
2870
|
}
|
|
2558
2871
|
}
|
|
2559
|
-
return (0,
|
|
2560
|
-
activePageIndex: (0,
|
|
2872
|
+
return (0, import_core14.createCompoundResumeState)({
|
|
2873
|
+
activePageIndex: (0, import_core14.clampCompoundPageIndex)(opts.index, opts.pageCount),
|
|
2561
2874
|
childStates
|
|
2562
2875
|
});
|
|
2563
2876
|
}, [ctx, opts.index, opts.pageCount]);
|
|
2564
|
-
const
|
|
2877
|
+
const buildStateRef = (0, import_react21.useRef)(buildState);
|
|
2878
|
+
buildStateRef.current = buildState;
|
|
2879
|
+
const finalizeHydration = (0, import_react21.useCallback)(
|
|
2880
|
+
(childStates) => {
|
|
2881
|
+
loadedChildStatesRef.current = {
|
|
2882
|
+
...loadedChildStatesRef.current,
|
|
2883
|
+
...childStates
|
|
2884
|
+
};
|
|
2885
|
+
skipSaveUntilHydratedRef.current = false;
|
|
2886
|
+
pendingChildResumeRef.current = null;
|
|
2887
|
+
},
|
|
2888
|
+
[]
|
|
2889
|
+
);
|
|
2890
|
+
const applyPendingChildResume = (0, import_react21.useCallback)(() => {
|
|
2565
2891
|
const pending = pendingChildResumeRef.current;
|
|
2566
2892
|
if (!pending || !ctx) return;
|
|
2567
|
-
const
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2893
|
+
const handles = ctx.getHandles();
|
|
2894
|
+
const applied = resumeChildHandles(handles, pending.childStates, {
|
|
2895
|
+
waitForHandles: true,
|
|
2896
|
+
alreadyResumed: resumedChildKeysRef.current
|
|
2897
|
+
});
|
|
2898
|
+
if (!applied) {
|
|
2899
|
+
const handlesAtWait = handles.size;
|
|
2900
|
+
queueMicrotask(() => {
|
|
2901
|
+
if (pendingChildResumeRef.current !== pending) return;
|
|
2902
|
+
const handlesNow = ctx.getHandles();
|
|
2903
|
+
if (handlesNow.size !== handlesAtWait) return;
|
|
2904
|
+
const registeredOnly2 = stripOrphanChildStates(handlesNow, pending.childStates);
|
|
2905
|
+
resumeChildHandles(handlesNow, registeredOnly2, {
|
|
2906
|
+
alreadyResumed: resumedChildKeysRef.current
|
|
2907
|
+
});
|
|
2908
|
+
finalizeHydration(registeredOnly2);
|
|
2909
|
+
});
|
|
2910
|
+
return;
|
|
2911
|
+
}
|
|
2912
|
+
const registeredOnly = stripOrphanChildStates(handles, pending.childStates);
|
|
2913
|
+
finalizeHydration(registeredOnly);
|
|
2914
|
+
}, [ctx, finalizeHydration]);
|
|
2572
2915
|
const saveResume = useCompoundResume({
|
|
2573
2916
|
courseId: opts.courseId,
|
|
2574
2917
|
compoundId: opts.compoundId,
|
|
2575
2918
|
enabled: opts.enabled,
|
|
2576
2919
|
storage,
|
|
2577
2920
|
onResume: (state) => {
|
|
2578
|
-
const clamped = (0,
|
|
2921
|
+
const clamped = (0, import_core14.clampCompoundPageIndex)(state.activePageIndex, opts.pageCount);
|
|
2579
2922
|
loadedChildStatesRef.current = { ...state.childStates };
|
|
2580
2923
|
skipSaveUntilHydratedRef.current = Object.keys(state.childStates).length > 0;
|
|
2581
2924
|
opts.setIndex(clamped);
|
|
2582
|
-
|
|
2925
|
+
resumedChildKeysRef.current = /* @__PURE__ */ new Set();
|
|
2926
|
+
pendingChildResumeRef.current = { ...state, activePageIndex: clamped, childStates: state.childStates };
|
|
2583
2927
|
queueMicrotask(() => applyPendingChildResume());
|
|
2584
2928
|
}
|
|
2585
2929
|
});
|
|
2586
|
-
(0,
|
|
2930
|
+
const persistNow = (0, import_react21.useCallback)(() => {
|
|
2587
2931
|
if (!opts.enabled || !opts.courseId) return;
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2932
|
+
saveResume(buildStateRef.current());
|
|
2933
|
+
}, [opts.enabled, opts.courseId, saveResume]);
|
|
2934
|
+
const notifyImperativeResume = (0, import_react21.useCallback)(
|
|
2935
|
+
(state) => {
|
|
2936
|
+
const clamped = (0, import_core14.clampCompoundPageIndex)(state.activePageIndex, opts.pageCount);
|
|
2937
|
+
loadedChildStatesRef.current = { ...state.childStates };
|
|
2938
|
+
skipSaveUntilHydratedRef.current = Object.keys(state.childStates).length > 0;
|
|
2939
|
+
opts.setIndex(clamped);
|
|
2940
|
+
resumedChildKeysRef.current = /* @__PURE__ */ new Set();
|
|
2941
|
+
pendingChildResumeRef.current = { ...state, activePageIndex: clamped, childStates: state.childStates };
|
|
2942
|
+
queueMicrotask(() => applyPendingChildResume());
|
|
2943
|
+
},
|
|
2944
|
+
[opts.pageCount, opts.setIndex, applyPendingChildResume]
|
|
2945
|
+
);
|
|
2946
|
+
(0, import_react21.useEffect)(() => {
|
|
2947
|
+
if (!bridgeRef) return;
|
|
2948
|
+
bridgeRef.current = { notifyImperativeResume };
|
|
2949
|
+
return () => {
|
|
2950
|
+
if (bridgeRef.current?.notifyImperativeResume === notifyImperativeResume) {
|
|
2951
|
+
bridgeRef.current = null;
|
|
2952
|
+
}
|
|
2953
|
+
};
|
|
2954
|
+
}, [bridgeRef, notifyImperativeResume]);
|
|
2955
|
+
(0, import_react21.useEffect)(() => {
|
|
2956
|
+
persistNow();
|
|
2957
|
+
}, [persistNow, opts.index, opts.pageCount, handlesVersion]);
|
|
2958
|
+
(0, import_react21.useEffect)(() => {
|
|
2600
2959
|
applyPendingChildResume();
|
|
2601
2960
|
}, [opts.index, handlesVersion, applyPendingChildResume]);
|
|
2961
|
+
(0, import_react21.useEffect)(() => {
|
|
2962
|
+
if (!opts.enabled || !opts.courseId || typeof document === "undefined") return;
|
|
2963
|
+
const flushOnExit = () => {
|
|
2964
|
+
if (document.visibilityState === "hidden") persistNow();
|
|
2965
|
+
};
|
|
2966
|
+
document.addEventListener("visibilitychange", flushOnExit);
|
|
2967
|
+
window.addEventListener("pagehide", flushOnExit);
|
|
2968
|
+
return () => {
|
|
2969
|
+
document.removeEventListener("visibilitychange", flushOnExit);
|
|
2970
|
+
window.removeEventListener("pagehide", flushOnExit);
|
|
2971
|
+
};
|
|
2972
|
+
}, [opts.enabled, opts.courseId, persistNow]);
|
|
2602
2973
|
}
|
|
2603
2974
|
|
|
2604
2975
|
// src/compound/useCompoundShell.ts
|
|
@@ -2614,18 +2985,19 @@ function useCompoundShell(opts) {
|
|
|
2614
2985
|
storage: opts.storage
|
|
2615
2986
|
});
|
|
2616
2987
|
const { goNext, goPrev, progress } = useCompoundNavigation(opts.pageCount, opts.index, opts.setIndex);
|
|
2617
|
-
const visibleIndex = (0,
|
|
2988
|
+
const visibleIndex = (0, import_core15.clampCompoundPageIndex)(opts.index, opts.pageCount);
|
|
2618
2989
|
useCompoundHandleRef(opts.ref, {
|
|
2619
2990
|
activePageIndex: visibleIndex,
|
|
2620
2991
|
setActivePageIndex: opts.setIndex,
|
|
2621
2992
|
getHandles: () => ctx?.getHandles() ?? /* @__PURE__ */ new Map(),
|
|
2993
|
+
getRegisteredHandles: () => ctx?.getRegisteredHandles() ?? /* @__PURE__ */ new Map(),
|
|
2622
2994
|
pageCount: opts.pageCount,
|
|
2623
2995
|
enableSolutionsButton: opts.enableSolutionsButton
|
|
2624
2996
|
});
|
|
2625
2997
|
return { visibleIndex, goNext, goPrev, progress, ctx };
|
|
2626
2998
|
}
|
|
2627
2999
|
function useCompoundInitialIndex(opts) {
|
|
2628
|
-
return (0,
|
|
3000
|
+
return (0, import_react22.useMemo)(
|
|
2629
3001
|
() => readCompoundInitialIndex(
|
|
2630
3002
|
opts.courseId,
|
|
2631
3003
|
opts.compoundId,
|
|
@@ -2638,8 +3010,8 @@ function useCompoundInitialIndex(opts) {
|
|
|
2638
3010
|
}
|
|
2639
3011
|
|
|
2640
3012
|
// src/compound/validateChildren.ts
|
|
2641
|
-
var
|
|
2642
|
-
var
|
|
3013
|
+
var import_react23 = __toESM(require("react"), 1);
|
|
3014
|
+
var import_core16 = require("@lessonkit/core");
|
|
2643
3015
|
|
|
2644
3016
|
// src/compound/blockType.ts
|
|
2645
3017
|
var LESSONKIT_BLOCK_TYPE = /* @__PURE__ */ Symbol.for("lessonkit.blockType");
|
|
@@ -2663,6 +3035,8 @@ var warnedPairs = /* @__PURE__ */ new Set();
|
|
|
2663
3035
|
var COMPOUND_CONTAINER_TYPES = /* @__PURE__ */ new Set([
|
|
2664
3036
|
"Page",
|
|
2665
3037
|
"InteractiveBook",
|
|
3038
|
+
"Slide",
|
|
3039
|
+
"SlideDeck",
|
|
2666
3040
|
"AssessmentSequence"
|
|
2667
3041
|
]);
|
|
2668
3042
|
function warnOrThrow(msg, strict) {
|
|
@@ -2673,8 +3047,8 @@ function warnOrThrow(msg, strict) {
|
|
|
2673
3047
|
}
|
|
2674
3048
|
}
|
|
2675
3049
|
function validateNode(parent, node, depth, strict) {
|
|
2676
|
-
|
|
2677
|
-
if (!
|
|
3050
|
+
import_react23.default.Children.forEach(node, (child) => {
|
|
3051
|
+
if (!import_react23.default.isValidElement(child)) return;
|
|
2678
3052
|
const blockType = getLessonkitBlockType(child.type);
|
|
2679
3053
|
if (!blockType) {
|
|
2680
3054
|
if (child.props && typeof child.props === "object" && "children" in child.props) {
|
|
@@ -2682,7 +3056,7 @@ function validateNode(parent, node, depth, strict) {
|
|
|
2682
3056
|
}
|
|
2683
3057
|
return;
|
|
2684
3058
|
}
|
|
2685
|
-
if (!(0,
|
|
3059
|
+
if (!(0, import_core16.isChildTypeAllowed)(parent, blockType)) {
|
|
2686
3060
|
const key = `${parent}:${blockType}`;
|
|
2687
3061
|
if (!warnedPairs.has(key)) {
|
|
2688
3062
|
warnedPairs.add(key);
|
|
@@ -2692,7 +3066,7 @@ function validateNode(parent, node, depth, strict) {
|
|
|
2692
3066
|
}
|
|
2693
3067
|
}
|
|
2694
3068
|
if (COMPOUND_CONTAINER_TYPES.has(blockType)) {
|
|
2695
|
-
const maxDepth =
|
|
3069
|
+
const maxDepth = import_core16.COMPOUND_MAX_NESTING_DEPTH[parent];
|
|
2696
3070
|
if (depth >= maxDepth) {
|
|
2697
3071
|
warnOrThrow(
|
|
2698
3072
|
`[lessonkit] Block "${blockType}" exceeds max nesting depth (${maxDepth}) for "${parent}"`,
|
|
@@ -2707,15 +3081,15 @@ function validateNode(parent, node, depth, strict) {
|
|
|
2707
3081
|
} else if (child.props && typeof child.props === "object" && "children" in child.props) {
|
|
2708
3082
|
validateSubtreeForForbidden(
|
|
2709
3083
|
child.props.children,
|
|
2710
|
-
|
|
3084
|
+
import_core16.ACCORDION_FORBIDDEN_CHILD_TYPES,
|
|
2711
3085
|
strict
|
|
2712
3086
|
);
|
|
2713
3087
|
}
|
|
2714
3088
|
});
|
|
2715
3089
|
}
|
|
2716
3090
|
function validateSubtreeForForbidden(node, forbidden, strict) {
|
|
2717
|
-
|
|
2718
|
-
if (!
|
|
3091
|
+
import_react23.default.Children.forEach(node, (child) => {
|
|
3092
|
+
if (!import_react23.default.isValidElement(child)) return;
|
|
2719
3093
|
const blockType = getLessonkitBlockType(child.type);
|
|
2720
3094
|
if (blockType && forbidden.includes(blockType)) {
|
|
2721
3095
|
warnOrThrow(`[lessonkit] Block "${blockType}" must not nest inside Accordion`, strict);
|
|
@@ -2737,7 +3111,7 @@ function validateSubtreeForForbidden(node, forbidden, strict) {
|
|
|
2737
3111
|
function validateAccordionSections(sections, strict) {
|
|
2738
3112
|
if (!isDevEnvironment4() && !strict) return;
|
|
2739
3113
|
for (const section of sections) {
|
|
2740
|
-
validateSubtreeForForbidden(section.content,
|
|
3114
|
+
validateSubtreeForForbidden(section.content, import_core16.ACCORDION_FORBIDDEN_CHILD_TYPES, strict);
|
|
2741
3115
|
}
|
|
2742
3116
|
}
|
|
2743
3117
|
function validateCompoundChildren(parent, children, strict) {
|
|
@@ -2755,8 +3129,8 @@ function warnSharedCompoundStorageKey(opts) {
|
|
|
2755
3129
|
}
|
|
2756
3130
|
|
|
2757
3131
|
// src/blocks/AssessmentSequence.tsx
|
|
2758
|
-
var
|
|
2759
|
-
var AssessmentSequenceInner = (0,
|
|
3132
|
+
var import_jsx_runtime13 = require("react/jsx-runtime");
|
|
3133
|
+
var AssessmentSequenceInner = (0, import_react24.forwardRef)(
|
|
2760
3134
|
function AssessmentSequenceInner2(props, ref) {
|
|
2761
3135
|
const { compoundId, childArray, index, setIndex, persistEnabled } = props;
|
|
2762
3136
|
const sequential = props.sequential !== false;
|
|
@@ -2773,18 +3147,18 @@ var AssessmentSequenceInner = (0, import_react22.forwardRef)(
|
|
|
2773
3147
|
});
|
|
2774
3148
|
validateCompoundChildren("AssessmentSequence", props.children);
|
|
2775
3149
|
if (!sequential) {
|
|
2776
|
-
return /* @__PURE__ */ (0,
|
|
3150
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("section", { "aria-label": "Assessment sequence", "data-testid": "assessment-sequence", children: props.children });
|
|
2777
3151
|
}
|
|
2778
|
-
return /* @__PURE__ */ (0,
|
|
2779
|
-
/* @__PURE__ */ (0,
|
|
3152
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("section", { "aria-label": "Assessment sequence", "data-testid": "assessment-sequence", children: [
|
|
3153
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("p", { children: [
|
|
2780
3154
|
"Question ",
|
|
2781
3155
|
progress.current,
|
|
2782
3156
|
" of ",
|
|
2783
3157
|
progress.total
|
|
2784
3158
|
] }),
|
|
2785
|
-
/* @__PURE__ */ (0,
|
|
2786
|
-
/* @__PURE__ */ (0,
|
|
2787
|
-
/* @__PURE__ */ (0,
|
|
3159
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { "data-testid": "assessment-sequence-step", children: childArray.map((child, i) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { hidden: i !== visibleIndex, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(CompoundPageIndexProvider, { pageIndex: i, children: child }) }, child.key ?? i)) }),
|
|
3160
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("nav", { "aria-label": "Sequence navigation", children: [
|
|
3161
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
2788
3162
|
"button",
|
|
2789
3163
|
{
|
|
2790
3164
|
type: "button",
|
|
@@ -2794,7 +3168,7 @@ var AssessmentSequenceInner = (0, import_react22.forwardRef)(
|
|
|
2794
3168
|
children: "Previous"
|
|
2795
3169
|
}
|
|
2796
3170
|
),
|
|
2797
|
-
/* @__PURE__ */ (0,
|
|
3171
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
2798
3172
|
"button",
|
|
2799
3173
|
{
|
|
2800
3174
|
type: "button",
|
|
@@ -2808,18 +3182,23 @@ var AssessmentSequenceInner = (0, import_react22.forwardRef)(
|
|
|
2808
3182
|
] });
|
|
2809
3183
|
}
|
|
2810
3184
|
);
|
|
2811
|
-
var AssessmentSequence = (0,
|
|
3185
|
+
var AssessmentSequence = (0, import_react24.forwardRef)(
|
|
2812
3186
|
function AssessmentSequence2(props, ref) {
|
|
2813
|
-
const
|
|
2814
|
-
|
|
3187
|
+
const reactInstanceId = (0, import_react24.useId)();
|
|
3188
|
+
const autoCompoundIdRef = (0, import_react24.useRef)(null);
|
|
3189
|
+
if (!props.blockId && !autoCompoundIdRef.current) {
|
|
3190
|
+
autoCompoundIdRef.current = (0, import_core17.deriveId)(`assessment-sequence-${reactInstanceId}`);
|
|
3191
|
+
}
|
|
3192
|
+
const compoundId = (0, import_react24.useMemo)(
|
|
3193
|
+
() => props.blockId ? normalizeComponentId(props.blockId, "blockId") : autoCompoundIdRef.current ?? DEFAULT_ASSESSMENT_SEQUENCE_COMPOUND_ID,
|
|
2815
3194
|
[props.blockId]
|
|
2816
3195
|
);
|
|
2817
|
-
const childArray =
|
|
2818
|
-
|
|
3196
|
+
const childArray = import_react24.default.Children.toArray(props.children).filter(
|
|
3197
|
+
import_react24.default.isValidElement
|
|
2819
3198
|
);
|
|
2820
|
-
const { config } = useLessonkit();
|
|
3199
|
+
const { config, storage } = useLessonkit();
|
|
2821
3200
|
const persistEnabled = config.session?.persistCompoundState !== false;
|
|
2822
|
-
(0,
|
|
3201
|
+
(0, import_react24.useEffect)(() => {
|
|
2823
3202
|
warnSharedCompoundStorageKey({
|
|
2824
3203
|
persistEnabled,
|
|
2825
3204
|
hasExplicitBlockId: Boolean(props.blockId),
|
|
@@ -2830,11 +3209,15 @@ var AssessmentSequence = (0, import_react22.forwardRef)(
|
|
|
2830
3209
|
courseId: config.courseId,
|
|
2831
3210
|
compoundId,
|
|
2832
3211
|
pageCount: childArray.length,
|
|
2833
|
-
persistEnabled
|
|
3212
|
+
persistEnabled,
|
|
3213
|
+
storage
|
|
2834
3214
|
});
|
|
2835
|
-
const [index, setIndex] = (0,
|
|
2836
|
-
const setIndexStable = (0,
|
|
2837
|
-
|
|
3215
|
+
const [index, setIndex] = (0, import_react24.useState)(initialIndex);
|
|
3216
|
+
const setIndexStable = (0, import_react24.useCallback)((i) => setIndex(i), []);
|
|
3217
|
+
(0, import_react24.useEffect)(() => {
|
|
3218
|
+
setIndex(initialIndex);
|
|
3219
|
+
}, [config.courseId, compoundId, initialIndex]);
|
|
3220
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(CompoundProvider, { activePageIndex: index, onActivePageIndexChange: setIndexStable, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
2838
3221
|
AssessmentSequenceInner,
|
|
2839
3222
|
{
|
|
2840
3223
|
...props,
|
|
@@ -2851,25 +3234,25 @@ var AssessmentSequence = (0, import_react22.forwardRef)(
|
|
|
2851
3234
|
setLessonkitBlockType(AssessmentSequence, "AssessmentSequence");
|
|
2852
3235
|
|
|
2853
3236
|
// src/blocks/Text.tsx
|
|
2854
|
-
var
|
|
2855
|
-
var
|
|
3237
|
+
var import_react25 = require("react");
|
|
3238
|
+
var import_jsx_runtime14 = require("react/jsx-runtime");
|
|
2856
3239
|
function Text(props) {
|
|
2857
|
-
return /* @__PURE__ */ (0,
|
|
3240
|
+
return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("p", { "data-lk-block-id": props.blockId, "data-testid": props.blockId ? `text-${props.blockId}` : "text", children: props.children });
|
|
2858
3241
|
}
|
|
2859
3242
|
setLessonkitBlockType(Text, "Text");
|
|
2860
3243
|
|
|
2861
3244
|
// src/blocks/Heading.tsx
|
|
2862
|
-
var
|
|
3245
|
+
var import_jsx_runtime15 = require("react/jsx-runtime");
|
|
2863
3246
|
function Heading(props) {
|
|
2864
3247
|
const Tag = `h${props.level}`;
|
|
2865
|
-
return /* @__PURE__ */ (0,
|
|
3248
|
+
return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Tag, { "data-lk-block-id": props.blockId, "data-testid": props.blockId ? `heading-${props.blockId}` : "heading", children: props.children });
|
|
2866
3249
|
}
|
|
2867
3250
|
setLessonkitBlockType(Heading, "Heading");
|
|
2868
3251
|
|
|
2869
3252
|
// src/blocks/Image.tsx
|
|
2870
|
-
var
|
|
3253
|
+
var import_jsx_runtime16 = require("react/jsx-runtime");
|
|
2871
3254
|
function Image(props) {
|
|
2872
|
-
return /* @__PURE__ */ (0,
|
|
3255
|
+
return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
2873
3256
|
"img",
|
|
2874
3257
|
{
|
|
2875
3258
|
src: props.src,
|
|
@@ -2883,14 +3266,14 @@ function Image(props) {
|
|
|
2883
3266
|
setLessonkitBlockType(Image, "Image");
|
|
2884
3267
|
|
|
2885
3268
|
// src/blocks/Page.tsx
|
|
2886
|
-
var
|
|
2887
|
-
var
|
|
3269
|
+
var import_react26 = require("react");
|
|
3270
|
+
var import_jsx_runtime17 = require("react/jsx-runtime");
|
|
2888
3271
|
function Page(props) {
|
|
2889
3272
|
validateCompoundChildren("Page", props.children);
|
|
2890
3273
|
const { track } = useLessonkit();
|
|
2891
3274
|
const lessonId = useEnclosingLessonId();
|
|
2892
|
-
(0,
|
|
2893
|
-
if (props.hidden || !lessonId) return;
|
|
3275
|
+
(0, import_react26.useEffect)(() => {
|
|
3276
|
+
if (props.hidden || !lessonId || props.parentType) return;
|
|
2894
3277
|
track(
|
|
2895
3278
|
"compound_page_viewed",
|
|
2896
3279
|
{
|
|
@@ -2901,7 +3284,7 @@ function Page(props) {
|
|
|
2901
3284
|
{ lessonId }
|
|
2902
3285
|
);
|
|
2903
3286
|
}, [props.hidden, props.pageIndex, props.parentType, props.blockId, lessonId, track]);
|
|
2904
|
-
return /* @__PURE__ */ (0,
|
|
3287
|
+
return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
|
|
2905
3288
|
"section",
|
|
2906
3289
|
{
|
|
2907
3290
|
"aria-label": props.title ?? "Page",
|
|
@@ -2909,8 +3292,8 @@ function Page(props) {
|
|
|
2909
3292
|
"data-testid": `page-${props.blockId}`,
|
|
2910
3293
|
hidden: props.hidden ? true : void 0,
|
|
2911
3294
|
children: [
|
|
2912
|
-
props.title ? /* @__PURE__ */ (0,
|
|
2913
|
-
/* @__PURE__ */ (0,
|
|
3295
|
+
props.title ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("h3", { children: props.title }) : null,
|
|
3296
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(CompoundPageIndexProvider, { pageIndex: props.pageIndex ?? 0, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { children: props.children }) })
|
|
2914
3297
|
]
|
|
2915
3298
|
}
|
|
2916
3299
|
);
|
|
@@ -2918,9 +3301,9 @@ function Page(props) {
|
|
|
2918
3301
|
setLessonkitBlockType(Page, "Page");
|
|
2919
3302
|
|
|
2920
3303
|
// src/blocks/InteractiveBook.tsx
|
|
2921
|
-
var
|
|
2922
|
-
var
|
|
2923
|
-
var InteractiveBookInner = (0,
|
|
3304
|
+
var import_react27 = __toESM(require("react"), 1);
|
|
3305
|
+
var import_jsx_runtime18 = require("react/jsx-runtime");
|
|
3306
|
+
var InteractiveBookInner = (0, import_react27.forwardRef)(
|
|
2924
3307
|
function InteractiveBookInner2(props, ref) {
|
|
2925
3308
|
const { blockId, pages, index, setIndex, persistEnabled } = props;
|
|
2926
3309
|
validateCompoundChildren("InteractiveBook", pages);
|
|
@@ -2935,11 +3318,11 @@ var InteractiveBookInner = (0, import_react25.forwardRef)(
|
|
|
2935
3318
|
persistEnabled,
|
|
2936
3319
|
ref
|
|
2937
3320
|
});
|
|
2938
|
-
const pageTitles = (0,
|
|
3321
|
+
const pageTitles = (0, import_react27.useMemo)(
|
|
2939
3322
|
() => pages.map((page) => page.props.title),
|
|
2940
3323
|
[pages]
|
|
2941
3324
|
);
|
|
2942
|
-
(0,
|
|
3325
|
+
(0, import_react27.useEffect)(() => {
|
|
2943
3326
|
if (!lessonId || pages.length === 0) return;
|
|
2944
3327
|
track(
|
|
2945
3328
|
"book_page_viewed",
|
|
@@ -2951,31 +3334,31 @@ var InteractiveBookInner = (0, import_react25.forwardRef)(
|
|
|
2951
3334
|
{ lessonId }
|
|
2952
3335
|
);
|
|
2953
3336
|
}, [visibleIndex, blockId, lessonId, pages.length, pageTitles, track]);
|
|
2954
|
-
return /* @__PURE__ */ (0,
|
|
2955
|
-
/* @__PURE__ */ (0,
|
|
2956
|
-
/* @__PURE__ */ (0,
|
|
3337
|
+
return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("section", { "aria-label": props.title, "data-testid": "interactive-book", "data-lk-block-id": blockId, children: [
|
|
3338
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("h3", { children: props.title }),
|
|
3339
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("p", { children: [
|
|
2957
3340
|
"Page ",
|
|
2958
3341
|
progress.current,
|
|
2959
3342
|
" of ",
|
|
2960
3343
|
progress.total
|
|
2961
3344
|
] }),
|
|
2962
|
-
props.showBookScore && ctx ? /* @__PURE__ */ (0,
|
|
3345
|
+
props.showBookScore && ctx ? /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("p", { "data-testid": "book-score", children: [
|
|
2963
3346
|
"Score: ",
|
|
2964
3347
|
Array.from(ctx.getHandles().values()).reduce((s, h) => s + h.getScore(), 0),
|
|
2965
3348
|
" /",
|
|
2966
3349
|
" ",
|
|
2967
3350
|
Array.from(ctx.getHandles().values()).reduce((s, h) => s + h.getMaxScore(), 0)
|
|
2968
3351
|
] }) : null,
|
|
2969
|
-
/* @__PURE__ */ (0,
|
|
2970
|
-
(page, i) =>
|
|
3352
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { "data-testid": "interactive-book-page", children: pages.map(
|
|
3353
|
+
(page, i) => import_react27.default.cloneElement(page, {
|
|
2971
3354
|
key: page.key ?? page.props.blockId,
|
|
2972
3355
|
hidden: i !== visibleIndex,
|
|
2973
3356
|
pageIndex: i,
|
|
2974
3357
|
parentType: "InteractiveBook"
|
|
2975
3358
|
})
|
|
2976
3359
|
) }),
|
|
2977
|
-
/* @__PURE__ */ (0,
|
|
2978
|
-
/* @__PURE__ */ (0,
|
|
3360
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("nav", { "aria-label": "Book navigation", children: [
|
|
3361
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
|
|
2979
3362
|
"button",
|
|
2980
3363
|
{
|
|
2981
3364
|
type: "button",
|
|
@@ -2985,7 +3368,7 @@ var InteractiveBookInner = (0, import_react25.forwardRef)(
|
|
|
2985
3368
|
children: "Previous"
|
|
2986
3369
|
}
|
|
2987
3370
|
),
|
|
2988
|
-
/* @__PURE__ */ (0,
|
|
3371
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
|
|
2989
3372
|
"button",
|
|
2990
3373
|
{
|
|
2991
3374
|
type: "button",
|
|
@@ -2999,25 +3382,29 @@ var InteractiveBookInner = (0, import_react25.forwardRef)(
|
|
|
2999
3382
|
] });
|
|
3000
3383
|
}
|
|
3001
3384
|
);
|
|
3002
|
-
var InteractiveBook = (0,
|
|
3003
|
-
const blockId = (0,
|
|
3385
|
+
var InteractiveBook = (0, import_react27.forwardRef)(function InteractiveBook2(props, ref) {
|
|
3386
|
+
const blockId = (0, import_react27.useMemo)(
|
|
3004
3387
|
() => normalizeComponentId(props.blockId, "blockId"),
|
|
3005
3388
|
[props.blockId]
|
|
3006
3389
|
);
|
|
3007
|
-
const pages =
|
|
3008
|
-
|
|
3390
|
+
const pages = import_react27.default.Children.toArray(props.children).filter(
|
|
3391
|
+
import_react27.default.isValidElement
|
|
3009
3392
|
);
|
|
3010
|
-
const { config } = useLessonkit();
|
|
3393
|
+
const { config, storage } = useLessonkit();
|
|
3011
3394
|
const persistEnabled = config.session?.persistCompoundState !== false;
|
|
3012
3395
|
const initialIndex = useCompoundInitialIndex({
|
|
3013
3396
|
courseId: config.courseId,
|
|
3014
3397
|
compoundId: blockId,
|
|
3015
3398
|
pageCount: pages.length,
|
|
3016
|
-
persistEnabled
|
|
3399
|
+
persistEnabled,
|
|
3400
|
+
storage
|
|
3017
3401
|
});
|
|
3018
|
-
const [index, setIndex] = (0,
|
|
3019
|
-
const setIndexStable = (0,
|
|
3020
|
-
|
|
3402
|
+
const [index, setIndex] = (0, import_react27.useState)(initialIndex);
|
|
3403
|
+
const setIndexStable = (0, import_react27.useCallback)((i) => setIndex(i), []);
|
|
3404
|
+
(0, import_react27.useEffect)(() => {
|
|
3405
|
+
setIndex(initialIndex);
|
|
3406
|
+
}, [config.courseId, blockId, initialIndex]);
|
|
3407
|
+
return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(CompoundProvider, { activePageIndex: index, onActivePageIndexChange: setIndexStable, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
|
|
3021
3408
|
InteractiveBookInner,
|
|
3022
3409
|
{
|
|
3023
3410
|
...props,
|
|
@@ -3032,17 +3419,249 @@ var InteractiveBook = (0, import_react25.forwardRef)(function InteractiveBook2(p
|
|
|
3032
3419
|
});
|
|
3033
3420
|
setLessonkitBlockType(InteractiveBook, "InteractiveBook");
|
|
3034
3421
|
|
|
3422
|
+
// src/blocks/Slide.tsx
|
|
3423
|
+
var import_react28 = require("react");
|
|
3424
|
+
var import_jsx_runtime19 = require("react/jsx-runtime");
|
|
3425
|
+
function Slide(props) {
|
|
3426
|
+
validateCompoundChildren("Slide", props.children);
|
|
3427
|
+
const { track } = useLessonkit();
|
|
3428
|
+
const lessonId = useEnclosingLessonId();
|
|
3429
|
+
(0, import_react28.useEffect)(() => {
|
|
3430
|
+
if (props.hidden || !lessonId || props.parentType) return;
|
|
3431
|
+
track(
|
|
3432
|
+
"compound_page_viewed",
|
|
3433
|
+
{
|
|
3434
|
+
blockId: props.blockId,
|
|
3435
|
+
pageIndex: props.slideIndex ?? 0,
|
|
3436
|
+
parentType: props.parentType
|
|
3437
|
+
},
|
|
3438
|
+
{ lessonId }
|
|
3439
|
+
);
|
|
3440
|
+
}, [props.hidden, props.slideIndex, props.parentType, props.blockId, lessonId, track]);
|
|
3441
|
+
return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
|
|
3442
|
+
"section",
|
|
3443
|
+
{
|
|
3444
|
+
"aria-label": props.title ?? "Slide",
|
|
3445
|
+
"data-lk-block-id": props.blockId,
|
|
3446
|
+
"data-testid": `slide-${props.blockId}`,
|
|
3447
|
+
hidden: props.hidden ? true : void 0,
|
|
3448
|
+
children: [
|
|
3449
|
+
props.title ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("h3", { children: props.title }) : null,
|
|
3450
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)(CompoundPageIndexProvider, { pageIndex: props.slideIndex ?? 0, children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { children: props.children }) })
|
|
3451
|
+
]
|
|
3452
|
+
}
|
|
3453
|
+
);
|
|
3454
|
+
}
|
|
3455
|
+
setLessonkitBlockType(Slide, "Slide");
|
|
3456
|
+
|
|
3457
|
+
// src/blocks/SlideDeck.tsx
|
|
3458
|
+
var import_react30 = __toESM(require("react"), 1);
|
|
3459
|
+
|
|
3460
|
+
// src/compound/useCompoundKeyboardNav.ts
|
|
3461
|
+
var import_react29 = require("react");
|
|
3462
|
+
var INTERACTIVE_TAGS = /* @__PURE__ */ new Set(["INPUT", "TEXTAREA", "SELECT", "BUTTON"]);
|
|
3463
|
+
function isEditableTarget(target) {
|
|
3464
|
+
if (!(target instanceof HTMLElement)) return false;
|
|
3465
|
+
if (INTERACTIVE_TAGS.has(target.tagName)) return true;
|
|
3466
|
+
if (target.isContentEditable) return true;
|
|
3467
|
+
if (target.closest("[role='slider'], [role='listbox'], [data-lk-assessment-interactive]")) {
|
|
3468
|
+
return true;
|
|
3469
|
+
}
|
|
3470
|
+
return false;
|
|
3471
|
+
}
|
|
3472
|
+
function useCompoundKeyboardNav(opts) {
|
|
3473
|
+
const { containerRef, visibleIndex, pageCount, goNext, goPrev, setIndex } = opts;
|
|
3474
|
+
(0, import_react29.useEffect)(() => {
|
|
3475
|
+
const el = containerRef.current;
|
|
3476
|
+
if (!el || pageCount === 0) return;
|
|
3477
|
+
const onKeyDown = (event) => {
|
|
3478
|
+
if (!el.contains(document.activeElement) && document.activeElement !== document.body) {
|
|
3479
|
+
return;
|
|
3480
|
+
}
|
|
3481
|
+
if (isEditableTarget(event.target)) return;
|
|
3482
|
+
switch (event.key) {
|
|
3483
|
+
case "ArrowRight":
|
|
3484
|
+
case "ArrowDown":
|
|
3485
|
+
if (visibleIndex < pageCount - 1) {
|
|
3486
|
+
event.preventDefault();
|
|
3487
|
+
goNext();
|
|
3488
|
+
}
|
|
3489
|
+
break;
|
|
3490
|
+
case "ArrowLeft":
|
|
3491
|
+
case "ArrowUp":
|
|
3492
|
+
if (visibleIndex > 0) {
|
|
3493
|
+
event.preventDefault();
|
|
3494
|
+
goPrev();
|
|
3495
|
+
}
|
|
3496
|
+
break;
|
|
3497
|
+
case "Home":
|
|
3498
|
+
if (visibleIndex !== 0) {
|
|
3499
|
+
event.preventDefault();
|
|
3500
|
+
setIndex(0);
|
|
3501
|
+
}
|
|
3502
|
+
break;
|
|
3503
|
+
case "End":
|
|
3504
|
+
if (visibleIndex !== pageCount - 1) {
|
|
3505
|
+
event.preventDefault();
|
|
3506
|
+
setIndex(pageCount - 1);
|
|
3507
|
+
}
|
|
3508
|
+
break;
|
|
3509
|
+
default:
|
|
3510
|
+
break;
|
|
3511
|
+
}
|
|
3512
|
+
};
|
|
3513
|
+
el.addEventListener("keydown", onKeyDown);
|
|
3514
|
+
return () => el.removeEventListener("keydown", onKeyDown);
|
|
3515
|
+
}, [containerRef, visibleIndex, pageCount, goNext, goPrev, setIndex]);
|
|
3516
|
+
}
|
|
3517
|
+
|
|
3518
|
+
// src/blocks/SlideDeck.tsx
|
|
3519
|
+
var import_jsx_runtime20 = require("react/jsx-runtime");
|
|
3520
|
+
var SlideDeckInner = (0, import_react30.forwardRef)(function SlideDeckInner2(props, ref) {
|
|
3521
|
+
const { blockId, slides, index, setIndex, persistEnabled } = props;
|
|
3522
|
+
validateCompoundChildren("SlideDeck", slides);
|
|
3523
|
+
const { config, track } = useLessonkit();
|
|
3524
|
+
const lessonId = useEnclosingLessonId();
|
|
3525
|
+
const containerRef = (0, import_react30.useRef)(null);
|
|
3526
|
+
const { visibleIndex, goNext, goPrev, progress, ctx } = useCompoundShell({
|
|
3527
|
+
courseId: config.courseId,
|
|
3528
|
+
compoundId: blockId,
|
|
3529
|
+
pageCount: slides.length,
|
|
3530
|
+
index,
|
|
3531
|
+
setIndex,
|
|
3532
|
+
persistEnabled,
|
|
3533
|
+
ref
|
|
3534
|
+
});
|
|
3535
|
+
const setIndexStable = (0, import_react30.useCallback)((i) => setIndex(i), [setIndex]);
|
|
3536
|
+
useCompoundKeyboardNav({
|
|
3537
|
+
containerRef,
|
|
3538
|
+
visibleIndex,
|
|
3539
|
+
pageCount: slides.length,
|
|
3540
|
+
goNext,
|
|
3541
|
+
goPrev,
|
|
3542
|
+
setIndex: setIndexStable
|
|
3543
|
+
});
|
|
3544
|
+
const slideTitles = (0, import_react30.useMemo)(
|
|
3545
|
+
() => slides.map((slide) => slide.props.title),
|
|
3546
|
+
[slides]
|
|
3547
|
+
);
|
|
3548
|
+
(0, import_react30.useEffect)(() => {
|
|
3549
|
+
if (!lessonId || slides.length === 0) return;
|
|
3550
|
+
track(
|
|
3551
|
+
"slide_viewed",
|
|
3552
|
+
{
|
|
3553
|
+
blockId,
|
|
3554
|
+
slideIndex: visibleIndex,
|
|
3555
|
+
slideTitle: slideTitles[visibleIndex]
|
|
3556
|
+
},
|
|
3557
|
+
{ lessonId }
|
|
3558
|
+
);
|
|
3559
|
+
}, [visibleIndex, blockId, lessonId, slides.length, slideTitles, track]);
|
|
3560
|
+
return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
|
|
3561
|
+
"section",
|
|
3562
|
+
{
|
|
3563
|
+
ref: containerRef,
|
|
3564
|
+
tabIndex: -1,
|
|
3565
|
+
"aria-label": props.title,
|
|
3566
|
+
"data-testid": "slide-deck",
|
|
3567
|
+
"data-lk-block-id": blockId,
|
|
3568
|
+
children: [
|
|
3569
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)("h3", { children: props.title }),
|
|
3570
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("p", { children: [
|
|
3571
|
+
"Slide ",
|
|
3572
|
+
progress.current,
|
|
3573
|
+
" of ",
|
|
3574
|
+
progress.total
|
|
3575
|
+
] }),
|
|
3576
|
+
props.showDeckScore && ctx ? /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("p", { "data-testid": "deck-score", children: [
|
|
3577
|
+
"Score: ",
|
|
3578
|
+
Array.from(ctx.getHandles().values()).reduce((s, h) => s + h.getScore(), 0),
|
|
3579
|
+
" /",
|
|
3580
|
+
" ",
|
|
3581
|
+
Array.from(ctx.getHandles().values()).reduce((s, h) => s + h.getMaxScore(), 0)
|
|
3582
|
+
] }) : null,
|
|
3583
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { "data-testid": "slide-deck-slide", children: slides.map(
|
|
3584
|
+
(slide, i) => import_react30.default.cloneElement(slide, {
|
|
3585
|
+
key: slide.key ?? slide.props.blockId,
|
|
3586
|
+
hidden: i !== visibleIndex,
|
|
3587
|
+
slideIndex: i,
|
|
3588
|
+
parentType: "SlideDeck"
|
|
3589
|
+
})
|
|
3590
|
+
) }),
|
|
3591
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("nav", { "aria-label": "Slide navigation", children: [
|
|
3592
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
|
|
3593
|
+
"button",
|
|
3594
|
+
{
|
|
3595
|
+
type: "button",
|
|
3596
|
+
"data-testid": "slide-prev",
|
|
3597
|
+
disabled: visibleIndex === 0 || slides.length === 0,
|
|
3598
|
+
onClick: goPrev,
|
|
3599
|
+
children: "Previous slide"
|
|
3600
|
+
}
|
|
3601
|
+
),
|
|
3602
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
|
|
3603
|
+
"button",
|
|
3604
|
+
{
|
|
3605
|
+
type: "button",
|
|
3606
|
+
"data-testid": "slide-next",
|
|
3607
|
+
disabled: visibleIndex >= slides.length - 1 || slides.length === 0,
|
|
3608
|
+
onClick: goNext,
|
|
3609
|
+
children: "Next slide"
|
|
3610
|
+
}
|
|
3611
|
+
)
|
|
3612
|
+
] })
|
|
3613
|
+
]
|
|
3614
|
+
}
|
|
3615
|
+
);
|
|
3616
|
+
});
|
|
3617
|
+
var SlideDeck = (0, import_react30.forwardRef)(function SlideDeck2(props, ref) {
|
|
3618
|
+
const blockId = (0, import_react30.useMemo)(
|
|
3619
|
+
() => normalizeComponentId(props.blockId, "blockId"),
|
|
3620
|
+
[props.blockId]
|
|
3621
|
+
);
|
|
3622
|
+
const slides = import_react30.default.Children.toArray(props.children).filter(
|
|
3623
|
+
import_react30.default.isValidElement
|
|
3624
|
+
);
|
|
3625
|
+
const { config, storage } = useLessonkit();
|
|
3626
|
+
const persistEnabled = config.session?.persistCompoundState !== false;
|
|
3627
|
+
const initialIndex = useCompoundInitialIndex({
|
|
3628
|
+
courseId: config.courseId,
|
|
3629
|
+
compoundId: blockId,
|
|
3630
|
+
pageCount: slides.length,
|
|
3631
|
+
persistEnabled,
|
|
3632
|
+
storage
|
|
3633
|
+
});
|
|
3634
|
+
const [index, setIndex] = (0, import_react30.useState)(initialIndex);
|
|
3635
|
+
const setIndexStable = (0, import_react30.useCallback)((i) => setIndex(i), []);
|
|
3636
|
+
(0, import_react30.useEffect)(() => {
|
|
3637
|
+
setIndex(initialIndex);
|
|
3638
|
+
}, [config.courseId, blockId, initialIndex]);
|
|
3639
|
+
return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(CompoundProvider, { activePageIndex: index, onActivePageIndexChange: setIndexStable, children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
|
|
3640
|
+
SlideDeckInner,
|
|
3641
|
+
{
|
|
3642
|
+
...props,
|
|
3643
|
+
ref,
|
|
3644
|
+
blockId,
|
|
3645
|
+
slides,
|
|
3646
|
+
index,
|
|
3647
|
+
setIndex,
|
|
3648
|
+
persistEnabled
|
|
3649
|
+
}
|
|
3650
|
+
) });
|
|
3651
|
+
});
|
|
3652
|
+
setLessonkitBlockType(SlideDeck, "SlideDeck");
|
|
3653
|
+
|
|
3035
3654
|
// src/blocks/Accordion.tsx
|
|
3036
|
-
var
|
|
3037
|
-
var
|
|
3655
|
+
var import_react31 = require("react");
|
|
3656
|
+
var import_jsx_runtime21 = require("react/jsx-runtime");
|
|
3038
3657
|
function Accordion(props) {
|
|
3039
3658
|
if (isDevEnvironment4()) {
|
|
3040
3659
|
validateAccordionSections(props.sections);
|
|
3041
3660
|
}
|
|
3042
|
-
const [open, setOpen] = (0,
|
|
3661
|
+
const [open, setOpen] = (0, import_react31.useState)(/* @__PURE__ */ new Set());
|
|
3043
3662
|
const { track } = useLessonkit();
|
|
3044
3663
|
const lessonId = useEnclosingLessonId();
|
|
3045
|
-
const baseId = (0,
|
|
3664
|
+
const baseId = (0, import_react31.useId)();
|
|
3046
3665
|
const toggle = (sectionId) => {
|
|
3047
3666
|
setOpen((prev) => {
|
|
3048
3667
|
const next = new Set(prev);
|
|
@@ -3057,12 +3676,12 @@ function Accordion(props) {
|
|
|
3057
3676
|
return next;
|
|
3058
3677
|
});
|
|
3059
3678
|
};
|
|
3060
|
-
return /* @__PURE__ */ (0,
|
|
3679
|
+
return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("section", { "aria-label": "Accordion", "data-lk-block-id": props.blockId, "data-testid": "accordion", children: props.sections.map((section) => {
|
|
3061
3680
|
const expanded = open.has(section.id);
|
|
3062
3681
|
const panelId = `${baseId}-${section.id}`;
|
|
3063
3682
|
const triggerId = `${baseId}-trigger-${section.id}`;
|
|
3064
|
-
return /* @__PURE__ */ (0,
|
|
3065
|
-
/* @__PURE__ */ (0,
|
|
3683
|
+
return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { "data-testid": `accordion-section-${section.id}`, children: [
|
|
3684
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)("h4", { children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
|
|
3066
3685
|
"button",
|
|
3067
3686
|
{
|
|
3068
3687
|
id: triggerId,
|
|
@@ -3074,28 +3693,28 @@ function Accordion(props) {
|
|
|
3074
3693
|
children: section.title
|
|
3075
3694
|
}
|
|
3076
3695
|
) }),
|
|
3077
|
-
expanded ? /* @__PURE__ */ (0,
|
|
3696
|
+
expanded ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { id: panelId, role: "region", "aria-labelledby": triggerId, children: section.content }) : null
|
|
3078
3697
|
] }, section.id);
|
|
3079
3698
|
}) });
|
|
3080
3699
|
}
|
|
3081
3700
|
setLessonkitBlockType(Accordion, "Accordion");
|
|
3082
3701
|
|
|
3083
3702
|
// src/blocks/DialogCards.tsx
|
|
3084
|
-
var
|
|
3085
|
-
var
|
|
3703
|
+
var import_react32 = require("react");
|
|
3704
|
+
var import_jsx_runtime22 = require("react/jsx-runtime");
|
|
3086
3705
|
function DialogCards(props) {
|
|
3087
|
-
const [index, setIndex] = (0,
|
|
3088
|
-
const [flipped, setFlipped] = (0,
|
|
3706
|
+
const [index, setIndex] = (0, import_react32.useState)(0);
|
|
3707
|
+
const [flipped, setFlipped] = (0, import_react32.useState)(false);
|
|
3089
3708
|
const card = props.cards[index];
|
|
3090
3709
|
if (!card) return null;
|
|
3091
|
-
return /* @__PURE__ */ (0,
|
|
3092
|
-
/* @__PURE__ */ (0,
|
|
3710
|
+
return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("section", { "aria-label": "Dialog cards", "data-lk-block-id": props.blockId, "data-testid": "dialog-cards", children: [
|
|
3711
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("p", { children: [
|
|
3093
3712
|
"Card ",
|
|
3094
3713
|
index + 1,
|
|
3095
3714
|
" of ",
|
|
3096
3715
|
props.cards.length
|
|
3097
3716
|
] }),
|
|
3098
|
-
/* @__PURE__ */ (0,
|
|
3717
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
|
|
3099
3718
|
"button",
|
|
3100
3719
|
{
|
|
3101
3720
|
type: "button",
|
|
@@ -3106,8 +3725,8 @@ function DialogCards(props) {
|
|
|
3106
3725
|
children: flipped ? card.back : card.front
|
|
3107
3726
|
}
|
|
3108
3727
|
),
|
|
3109
|
-
/* @__PURE__ */ (0,
|
|
3110
|
-
/* @__PURE__ */ (0,
|
|
3728
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("nav", { "aria-label": "Card navigation", children: [
|
|
3729
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
|
|
3111
3730
|
"button",
|
|
3112
3731
|
{
|
|
3113
3732
|
type: "button",
|
|
@@ -3120,7 +3739,7 @@ function DialogCards(props) {
|
|
|
3120
3739
|
children: "Previous"
|
|
3121
3740
|
}
|
|
3122
3741
|
),
|
|
3123
|
-
/* @__PURE__ */ (0,
|
|
3742
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
|
|
3124
3743
|
"button",
|
|
3125
3744
|
{
|
|
3126
3745
|
type: "button",
|
|
@@ -3139,11 +3758,11 @@ function DialogCards(props) {
|
|
|
3139
3758
|
setLessonkitBlockType(DialogCards, "DialogCards");
|
|
3140
3759
|
|
|
3141
3760
|
// src/blocks/Flashcards.tsx
|
|
3142
|
-
var
|
|
3143
|
-
var
|
|
3761
|
+
var import_react33 = require("react");
|
|
3762
|
+
var import_jsx_runtime23 = require("react/jsx-runtime");
|
|
3144
3763
|
function Flashcards(props) {
|
|
3145
|
-
const [index, setIndex] = (0,
|
|
3146
|
-
const [face, setFace] = (0,
|
|
3764
|
+
const [index, setIndex] = (0, import_react33.useState)(0);
|
|
3765
|
+
const [face, setFace] = (0, import_react33.useState)("front");
|
|
3147
3766
|
const { track } = useLessonkit();
|
|
3148
3767
|
const lessonId = useEnclosingLessonId();
|
|
3149
3768
|
const card = props.cards[index];
|
|
@@ -3157,10 +3776,10 @@ function Flashcards(props) {
|
|
|
3157
3776
|
lessonId ? { lessonId } : void 0
|
|
3158
3777
|
);
|
|
3159
3778
|
};
|
|
3160
|
-
return /* @__PURE__ */ (0,
|
|
3161
|
-
/* @__PURE__ */ (0,
|
|
3162
|
-
props.selfScore ? /* @__PURE__ */ (0,
|
|
3163
|
-
/* @__PURE__ */ (0,
|
|
3779
|
+
return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("section", { "aria-label": "Flashcards", "data-lk-block-id": props.blockId, "data-testid": "flashcards", children: [
|
|
3780
|
+
/* @__PURE__ */ (0, import_jsx_runtime23.jsx)("button", { type: "button", "data-testid": "flashcard-flip", onClick: flip, style: { minHeight: "6rem", width: "100%" }, children: face === "front" ? card.front : card.back }),
|
|
3781
|
+
props.selfScore ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("p", { "data-testid": "flashcard-self-score", children: "Self-score mode enabled" }) : null,
|
|
3782
|
+
/* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
|
|
3164
3783
|
"button",
|
|
3165
3784
|
{
|
|
3166
3785
|
type: "button",
|
|
@@ -3178,10 +3797,10 @@ function Flashcards(props) {
|
|
|
3178
3797
|
setLessonkitBlockType(Flashcards, "Flashcards");
|
|
3179
3798
|
|
|
3180
3799
|
// src/blocks/ImageHotspots.tsx
|
|
3181
|
-
var
|
|
3182
|
-
var
|
|
3800
|
+
var import_react34 = require("react");
|
|
3801
|
+
var import_jsx_runtime24 = require("react/jsx-runtime");
|
|
3183
3802
|
function ImageHotspots(props) {
|
|
3184
|
-
const [active, setActive] = (0,
|
|
3803
|
+
const [active, setActive] = (0, import_react34.useState)(null);
|
|
3185
3804
|
const { track } = useLessonkit();
|
|
3186
3805
|
const lessonId = useEnclosingLessonId();
|
|
3187
3806
|
const open = (hotspotId) => {
|
|
@@ -3192,10 +3811,10 @@ function ImageHotspots(props) {
|
|
|
3192
3811
|
lessonId ? { lessonId } : void 0
|
|
3193
3812
|
);
|
|
3194
3813
|
};
|
|
3195
|
-
return /* @__PURE__ */ (0,
|
|
3196
|
-
/* @__PURE__ */ (0,
|
|
3197
|
-
/* @__PURE__ */ (0,
|
|
3198
|
-
props.hotspots.map((h) => /* @__PURE__ */ (0,
|
|
3814
|
+
return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("section", { "aria-label": "Image hotspots", "data-lk-block-id": props.blockId, "data-testid": "image-hotspots", children: [
|
|
3815
|
+
/* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { style: { position: "relative", display: "inline-block" }, children: [
|
|
3816
|
+
/* @__PURE__ */ (0, import_jsx_runtime24.jsx)("img", { src: props.src, alt: props.alt, style: { maxWidth: "100%" } }),
|
|
3817
|
+
props.hotspots.map((h) => /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
|
|
3199
3818
|
"button",
|
|
3200
3819
|
{
|
|
3201
3820
|
type: "button",
|
|
@@ -3214,19 +3833,19 @@ function ImageHotspots(props) {
|
|
|
3214
3833
|
h.id
|
|
3215
3834
|
))
|
|
3216
3835
|
] }),
|
|
3217
|
-
active ? /* @__PURE__ */ (0,
|
|
3836
|
+
active ? /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { role: "dialog", "aria-label": "Hotspot details", "data-testid": "hotspot-popover", children: [
|
|
3218
3837
|
props.hotspots.find((h) => h.id === active)?.content,
|
|
3219
|
-
/* @__PURE__ */ (0,
|
|
3838
|
+
/* @__PURE__ */ (0, import_jsx_runtime24.jsx)("button", { type: "button", onClick: () => setActive(null), children: "Close" })
|
|
3220
3839
|
] }) : null
|
|
3221
3840
|
] });
|
|
3222
3841
|
}
|
|
3223
3842
|
setLessonkitBlockType(ImageHotspots, "ImageHotspots");
|
|
3224
3843
|
|
|
3225
3844
|
// src/blocks/ImageSlider.tsx
|
|
3226
|
-
var
|
|
3227
|
-
var
|
|
3845
|
+
var import_react35 = require("react");
|
|
3846
|
+
var import_jsx_runtime25 = require("react/jsx-runtime");
|
|
3228
3847
|
function ImageSlider(props) {
|
|
3229
|
-
const [index, setIndex] = (0,
|
|
3848
|
+
const [index, setIndex] = (0, import_react35.useState)(0);
|
|
3230
3849
|
const { track } = useLessonkit();
|
|
3231
3850
|
const lessonId = useEnclosingLessonId();
|
|
3232
3851
|
const slide = props.slides[index];
|
|
@@ -3239,11 +3858,11 @@ function ImageSlider(props) {
|
|
|
3239
3858
|
lessonId ? { lessonId } : void 0
|
|
3240
3859
|
);
|
|
3241
3860
|
};
|
|
3242
|
-
return /* @__PURE__ */ (0,
|
|
3243
|
-
/* @__PURE__ */ (0,
|
|
3244
|
-
slide.caption ? /* @__PURE__ */ (0,
|
|
3245
|
-
/* @__PURE__ */ (0,
|
|
3246
|
-
/* @__PURE__ */ (0,
|
|
3861
|
+
return /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("section", { "aria-label": "Image slider", "data-lk-block-id": props.blockId, "data-testid": "image-slider", children: [
|
|
3862
|
+
/* @__PURE__ */ (0, import_jsx_runtime25.jsx)("img", { src: slide.src, alt: slide.alt, style: { maxWidth: "100%" } }),
|
|
3863
|
+
slide.caption ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("p", { children: slide.caption }) : null,
|
|
3864
|
+
/* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("nav", { "aria-label": "Slide navigation", children: [
|
|
3865
|
+
/* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
|
|
3247
3866
|
"button",
|
|
3248
3867
|
{
|
|
3249
3868
|
type: "button",
|
|
@@ -3253,12 +3872,12 @@ function ImageSlider(props) {
|
|
|
3253
3872
|
children: "Previous"
|
|
3254
3873
|
}
|
|
3255
3874
|
),
|
|
3256
|
-
/* @__PURE__ */ (0,
|
|
3875
|
+
/* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("span", { children: [
|
|
3257
3876
|
index + 1,
|
|
3258
3877
|
" / ",
|
|
3259
3878
|
props.slides.length
|
|
3260
3879
|
] }),
|
|
3261
|
-
/* @__PURE__ */ (0,
|
|
3880
|
+
/* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
|
|
3262
3881
|
"button",
|
|
3263
3882
|
{
|
|
3264
3883
|
type: "button",
|
|
@@ -3274,16 +3893,21 @@ function ImageSlider(props) {
|
|
|
3274
3893
|
setLessonkitBlockType(ImageSlider, "ImageSlider");
|
|
3275
3894
|
|
|
3276
3895
|
// src/blocks/FindHotspot.tsx
|
|
3277
|
-
var
|
|
3278
|
-
var
|
|
3896
|
+
var import_react36 = require("react");
|
|
3897
|
+
var import_jsx_runtime26 = require("react/jsx-runtime");
|
|
3279
3898
|
var INTERACTION6 = "findHotspot";
|
|
3280
3899
|
function FindHotspotInner(props, ref) {
|
|
3281
|
-
const checkId = (0,
|
|
3282
|
-
const [selected, setSelected] = (0,
|
|
3283
|
-
const [checked, setChecked] = (0,
|
|
3900
|
+
const checkId = (0, import_react36.useMemo)(() => normalizeComponentId(props.checkId, "checkId"), [props.checkId]);
|
|
3901
|
+
const [selected, setSelected] = (0, import_react36.useState)(null);
|
|
3902
|
+
const [checked, setChecked] = (0, import_react36.useState)(false);
|
|
3284
3903
|
const assessment = useAssessmentState(props.enclosingLessonId);
|
|
3904
|
+
const targetIdsKey = props.targets.map((t) => t.id).join("\0");
|
|
3905
|
+
(0, import_react36.useEffect)(() => {
|
|
3906
|
+
setSelected(null);
|
|
3907
|
+
setChecked(false);
|
|
3908
|
+
}, [checkId, props.correctTargetId, targetIdsKey]);
|
|
3285
3909
|
const correct = selected === props.correctTargetId;
|
|
3286
|
-
const handle = (0,
|
|
3910
|
+
const handle = (0, import_react36.useMemo)(
|
|
3287
3911
|
() => buildAssessmentHandle({
|
|
3288
3912
|
checkId,
|
|
3289
3913
|
getScore: () => checked && correct ? 1 : 0,
|
|
@@ -3305,15 +3929,22 @@ function FindHotspotInner(props, ref) {
|
|
|
3305
3929
|
getCurrentState: () => ({ selected, checked }),
|
|
3306
3930
|
resume: (state) => {
|
|
3307
3931
|
const nextSelected = readStringField(state, "selected");
|
|
3308
|
-
if (typeof nextSelected === "string"
|
|
3932
|
+
if (typeof nextSelected === "string" || nextSelected === null) {
|
|
3933
|
+
const valid = nextSelected === null || props.targets.some((t) => t.id === nextSelected);
|
|
3934
|
+
setSelected(valid ? nextSelected : null);
|
|
3935
|
+
}
|
|
3309
3936
|
readBooleanStateField(state, "checked", setChecked);
|
|
3310
3937
|
}
|
|
3311
3938
|
}),
|
|
3312
|
-
[checkId, selected, checked, correct, props.correctTargetId]
|
|
3939
|
+
[checkId, selected, checked, correct, props.correctTargetId, props.targets]
|
|
3313
3940
|
);
|
|
3314
3941
|
useAssessmentHandleRegistration(checkId, handle, ref);
|
|
3942
|
+
const selectTarget = (id) => {
|
|
3943
|
+
setSelected(id);
|
|
3944
|
+
setChecked(false);
|
|
3945
|
+
};
|
|
3315
3946
|
const submit = () => {
|
|
3316
|
-
if (!selected) return;
|
|
3947
|
+
if (!selected || checked) return;
|
|
3317
3948
|
setChecked(true);
|
|
3318
3949
|
assessment.answer({
|
|
3319
3950
|
checkId,
|
|
@@ -3327,14 +3958,14 @@ function FindHotspotInner(props, ref) {
|
|
|
3327
3958
|
interactionType: INTERACTION6,
|
|
3328
3959
|
score: 1,
|
|
3329
3960
|
maxScore: 1,
|
|
3330
|
-
passingScore: props.passingScore
|
|
3961
|
+
passingScore: props.passingScore ?? 1
|
|
3331
3962
|
});
|
|
3332
3963
|
}
|
|
3333
3964
|
};
|
|
3334
|
-
return /* @__PURE__ */ (0,
|
|
3335
|
-
/* @__PURE__ */ (0,
|
|
3336
|
-
/* @__PURE__ */ (0,
|
|
3337
|
-
props.targets.map((t) => /* @__PURE__ */ (0,
|
|
3965
|
+
return /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("section", { "aria-label": "Find the hotspot", "data-lk-check-id": checkId, "data-testid": "find-hotspot", children: [
|
|
3966
|
+
/* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { style: { position: "relative", display: "inline-block" }, children: [
|
|
3967
|
+
/* @__PURE__ */ (0, import_jsx_runtime26.jsx)("img", { src: props.src, alt: props.alt, style: { maxWidth: "100%" } }),
|
|
3968
|
+
props.targets.map((t) => /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
|
|
3338
3969
|
"button",
|
|
3339
3970
|
{
|
|
3340
3971
|
type: "button",
|
|
@@ -3347,30 +3978,30 @@ function FindHotspotInner(props, ref) {
|
|
|
3347
3978
|
top: `${t.y}%`,
|
|
3348
3979
|
transform: "translate(-50%, -50%)"
|
|
3349
3980
|
},
|
|
3350
|
-
onClick: () =>
|
|
3981
|
+
onClick: () => selectTarget(t.id),
|
|
3351
3982
|
children: t.label
|
|
3352
3983
|
},
|
|
3353
3984
|
t.id
|
|
3354
3985
|
))
|
|
3355
3986
|
] }),
|
|
3356
|
-
/* @__PURE__ */ (0,
|
|
3357
|
-
checked ? /* @__PURE__ */ (0,
|
|
3987
|
+
/* @__PURE__ */ (0, import_jsx_runtime26.jsx)("button", { type: "button", "data-testid": "check-hotspot", disabled: !selected, onClick: submit, children: "Check" }),
|
|
3988
|
+
checked ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("p", { role: "status", children: correct ? "Correct" : "Try again" }) : null
|
|
3358
3989
|
] });
|
|
3359
3990
|
}
|
|
3360
|
-
var FindHotspotInnerForwarded = (0,
|
|
3361
|
-
var FindHotspot = (0,
|
|
3362
|
-
return /* @__PURE__ */ (0,
|
|
3991
|
+
var FindHotspotInnerForwarded = (0, import_react36.forwardRef)(FindHotspotInner);
|
|
3992
|
+
var FindHotspot = (0, import_react36.forwardRef)(function FindHotspot2(props, ref) {
|
|
3993
|
+
return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(AssessmentLessonGuard, { blockLabel: "FindHotspot", checkId: props.checkId, children: (enclosingLessonId) => /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(FindHotspotInnerForwarded, { ...props, enclosingLessonId, ref }) });
|
|
3363
3994
|
});
|
|
3364
3995
|
setLessonkitBlockType(FindHotspot, "FindHotspot");
|
|
3365
3996
|
|
|
3366
3997
|
// src/blocks/FindMultipleHotspots.tsx
|
|
3367
|
-
var
|
|
3368
|
-
var
|
|
3998
|
+
var import_react37 = require("react");
|
|
3999
|
+
var import_jsx_runtime27 = require("react/jsx-runtime");
|
|
3369
4000
|
var INTERACTION7 = "findMultipleHotspots";
|
|
3370
4001
|
function FindMultipleHotspotsInner(props, ref) {
|
|
3371
|
-
const checkId = (0,
|
|
3372
|
-
const [selected, setSelected] = (0,
|
|
3373
|
-
const [checked, setChecked] = (0,
|
|
4002
|
+
const checkId = (0, import_react37.useMemo)(() => normalizeComponentId(props.checkId, "checkId"), [props.checkId]);
|
|
4003
|
+
const [selected, setSelected] = (0, import_react37.useState)(/* @__PURE__ */ new Set());
|
|
4004
|
+
const [checked, setChecked] = (0, import_react37.useState)(false);
|
|
3374
4005
|
const assessment = useAssessmentState(props.enclosingLessonId);
|
|
3375
4006
|
const toggle = (id) => {
|
|
3376
4007
|
setSelected((prev) => {
|
|
@@ -3379,9 +4010,10 @@ function FindMultipleHotspotsInner(props, ref) {
|
|
|
3379
4010
|
else next.add(id);
|
|
3380
4011
|
return next;
|
|
3381
4012
|
});
|
|
4013
|
+
setChecked(false);
|
|
3382
4014
|
};
|
|
3383
4015
|
const correct = selected.size === props.correctTargetIds.length && props.correctTargetIds.every((id) => selected.has(id));
|
|
3384
|
-
const handle = (0,
|
|
4016
|
+
const handle = (0, import_react37.useMemo)(
|
|
3385
4017
|
() => buildAssessmentHandle({
|
|
3386
4018
|
checkId,
|
|
3387
4019
|
getScore: () => checked && correct ? 1 : 0,
|
|
@@ -3411,7 +4043,7 @@ function FindMultipleHotspotsInner(props, ref) {
|
|
|
3411
4043
|
);
|
|
3412
4044
|
useAssessmentHandleRegistration(checkId, handle, ref);
|
|
3413
4045
|
const submit = () => {
|
|
3414
|
-
if (selected.size === 0) return;
|
|
4046
|
+
if (selected.size === 0 || checked) return;
|
|
3415
4047
|
setChecked(true);
|
|
3416
4048
|
assessment.answer({
|
|
3417
4049
|
checkId,
|
|
@@ -3425,14 +4057,14 @@ function FindMultipleHotspotsInner(props, ref) {
|
|
|
3425
4057
|
interactionType: INTERACTION7,
|
|
3426
4058
|
score: 1,
|
|
3427
4059
|
maxScore: 1,
|
|
3428
|
-
passingScore: props.passingScore
|
|
4060
|
+
passingScore: props.passingScore ?? 1
|
|
3429
4061
|
});
|
|
3430
4062
|
}
|
|
3431
4063
|
};
|
|
3432
|
-
return /* @__PURE__ */ (0,
|
|
3433
|
-
/* @__PURE__ */ (0,
|
|
3434
|
-
/* @__PURE__ */ (0,
|
|
3435
|
-
props.targets.map((t) => /* @__PURE__ */ (0,
|
|
4064
|
+
return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("section", { "aria-label": "Find multiple hotspots", "data-lk-check-id": checkId, "data-testid": "find-multiple-hotspots", children: [
|
|
4065
|
+
/* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { style: { position: "relative", display: "inline-block" }, children: [
|
|
4066
|
+
/* @__PURE__ */ (0, import_jsx_runtime27.jsx)("img", { src: props.src, alt: props.alt, style: { maxWidth: "100%" } }),
|
|
4067
|
+
props.targets.map((t) => /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
|
|
3436
4068
|
"button",
|
|
3437
4069
|
{
|
|
3438
4070
|
type: "button",
|
|
@@ -3451,23 +4083,23 @@ function FindMultipleHotspotsInner(props, ref) {
|
|
|
3451
4083
|
t.id
|
|
3452
4084
|
))
|
|
3453
4085
|
] }),
|
|
3454
|
-
/* @__PURE__ */ (0,
|
|
3455
|
-
checked ? /* @__PURE__ */ (0,
|
|
4086
|
+
/* @__PURE__ */ (0, import_jsx_runtime27.jsx)("button", { type: "button", "data-testid": "check-hotspots", disabled: selected.size === 0, onClick: submit, children: "Check" }),
|
|
4087
|
+
checked ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("p", { role: "status", children: correct ? "Correct" : "Try again" }) : null
|
|
3456
4088
|
] });
|
|
3457
4089
|
}
|
|
3458
|
-
var FindMultipleHotspotsInnerForwarded = (0,
|
|
3459
|
-
var FindMultipleHotspots = (0,
|
|
4090
|
+
var FindMultipleHotspotsInnerForwarded = (0, import_react37.forwardRef)(FindMultipleHotspotsInner);
|
|
4091
|
+
var FindMultipleHotspots = (0, import_react37.forwardRef)(
|
|
3460
4092
|
function FindMultipleHotspots2(props, ref) {
|
|
3461
|
-
return /* @__PURE__ */ (0,
|
|
4093
|
+
return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(AssessmentLessonGuard, { blockLabel: "FindMultipleHotspots", checkId: props.checkId, children: (enclosingLessonId) => /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(FindMultipleHotspotsInnerForwarded, { ...props, enclosingLessonId, ref }) });
|
|
3462
4094
|
}
|
|
3463
4095
|
);
|
|
3464
4096
|
setLessonkitBlockType(FindMultipleHotspots, "FindMultipleHotspots");
|
|
3465
4097
|
|
|
3466
4098
|
// src/index.tsx
|
|
3467
|
-
var
|
|
4099
|
+
var import_core19 = require("@lessonkit/core");
|
|
3468
4100
|
|
|
3469
4101
|
// src/theme/ThemeProvider.tsx
|
|
3470
|
-
var
|
|
4102
|
+
var import_react38 = __toESM(require("react"), 1);
|
|
3471
4103
|
var import_themes = require("@lessonkit/themes");
|
|
3472
4104
|
|
|
3473
4105
|
// src/theme/applyCssVariables.ts
|
|
@@ -3486,11 +4118,11 @@ function applyCssVariables(target, vars, previousKeys) {
|
|
|
3486
4118
|
}
|
|
3487
4119
|
|
|
3488
4120
|
// src/theme/ThemeProvider.tsx
|
|
3489
|
-
var
|
|
3490
|
-
var ThemeContext = (0,
|
|
4121
|
+
var import_jsx_runtime28 = require("react/jsx-runtime");
|
|
4122
|
+
var ThemeContext = (0, import_react38.createContext)(null);
|
|
3491
4123
|
var useIsoLayoutEffect2 = (
|
|
3492
4124
|
/* v8 ignore next -- SSR uses useEffect when window is unavailable */
|
|
3493
|
-
typeof window !== "undefined" ?
|
|
4125
|
+
typeof window !== "undefined" ? import_react38.useLayoutEffect : import_react38.default.useEffect
|
|
3494
4126
|
);
|
|
3495
4127
|
function getSystemMode() {
|
|
3496
4128
|
if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
|
|
@@ -3509,7 +4141,7 @@ function ThemeProvider(props) {
|
|
|
3509
4141
|
const preset = props.preset ?? "default";
|
|
3510
4142
|
const mode = props.mode ?? "light";
|
|
3511
4143
|
const targetKind = props.target ?? "document";
|
|
3512
|
-
const [resolvedMode, setResolvedMode] = (0,
|
|
4144
|
+
const [resolvedMode, setResolvedMode] = (0, import_react38.useState)(
|
|
3513
4145
|
() => mode === "system" ? getSystemMode() : mode
|
|
3514
4146
|
);
|
|
3515
4147
|
useIsoLayoutEffect2(() => {
|
|
@@ -3525,20 +4157,20 @@ function ThemeProvider(props) {
|
|
|
3525
4157
|
return () => mq.removeEventListener("change", onChange);
|
|
3526
4158
|
}, [mode]);
|
|
3527
4159
|
const dataTheme = mode === "system" ? resolvedMode : mode === "dark" ? "dark" : "light";
|
|
3528
|
-
const effectiveTheme = (0,
|
|
4160
|
+
const effectiveTheme = (0, import_react38.useMemo)(() => {
|
|
3529
4161
|
const modeBase = resolveModeBase(mode, dataTheme);
|
|
3530
4162
|
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));
|
|
3531
4163
|
return (0, import_themes.mergeThemes)(base, props.theme ?? {});
|
|
3532
4164
|
}, [preset, mode, dataTheme, props.theme]);
|
|
3533
|
-
const hostRef = (0,
|
|
3534
|
-
const appliedKeysRef = (0,
|
|
4165
|
+
const hostRef = (0, import_react38.useRef)(null);
|
|
4166
|
+
const appliedKeysRef = (0, import_react38.useRef)(/* @__PURE__ */ new Set());
|
|
3535
4167
|
useIsoLayoutEffect2(() => {
|
|
3536
4168
|
if (targetKind === "document" && typeof document !== "undefined") {
|
|
3537
4169
|
document.documentElement.setAttribute("data-lk-theme", dataTheme);
|
|
3538
4170
|
return () => document.documentElement.removeAttribute("data-lk-theme");
|
|
3539
4171
|
}
|
|
3540
4172
|
}, [targetKind, dataTheme]);
|
|
3541
|
-
const inject = (0,
|
|
4173
|
+
const inject = (0, import_react38.useCallback)(() => {
|
|
3542
4174
|
const vars = (0, import_themes.themeToCssVariables)(effectiveTheme);
|
|
3543
4175
|
const el = targetKind === "document" && typeof document !== "undefined" ? document.documentElement : hostRef.current;
|
|
3544
4176
|
if (!el) return;
|
|
@@ -3555,7 +4187,7 @@ function ThemeProvider(props) {
|
|
|
3555
4187
|
appliedKeysRef.current = /* @__PURE__ */ new Set();
|
|
3556
4188
|
};
|
|
3557
4189
|
}, [inject, targetKind]);
|
|
3558
|
-
const value = (0,
|
|
4190
|
+
const value = (0, import_react38.useMemo)(
|
|
3559
4191
|
() => ({
|
|
3560
4192
|
theme: effectiveTheme,
|
|
3561
4193
|
preset,
|
|
@@ -3565,12 +4197,12 @@ function ThemeProvider(props) {
|
|
|
3565
4197
|
[effectiveTheme, preset, mode, dataTheme]
|
|
3566
4198
|
);
|
|
3567
4199
|
if (targetKind === "document") {
|
|
3568
|
-
return /* @__PURE__ */ (0,
|
|
4200
|
+
return /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(ThemeContext.Provider, { value, children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { "data-lk-theme": dataTheme, style: { display: "contents" }, children: props.children }) });
|
|
3569
4201
|
}
|
|
3570
|
-
return /* @__PURE__ */ (0,
|
|
4202
|
+
return /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(ThemeContext.Provider, { value, children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { ref: hostRef, "data-lk-theme": dataTheme, children: props.children }) });
|
|
3571
4203
|
}
|
|
3572
4204
|
function useTheme() {
|
|
3573
|
-
const ctx = (0,
|
|
4205
|
+
const ctx = (0, import_react38.useContext)(ThemeContext);
|
|
3574
4206
|
if (!ctx) {
|
|
3575
4207
|
throw new Error("useTheme must be used within a ThemeProvider");
|
|
3576
4208
|
}
|
|
@@ -3578,8 +4210,15 @@ function useTheme() {
|
|
|
3578
4210
|
}
|
|
3579
4211
|
|
|
3580
4212
|
// src/catalogV3Entries.ts
|
|
3581
|
-
var
|
|
3582
|
-
var COMPOUND_PARENTS = [
|
|
4213
|
+
var import_core18 = require("@lessonkit/core");
|
|
4214
|
+
var COMPOUND_PARENTS = [
|
|
4215
|
+
"Lesson",
|
|
4216
|
+
"Page",
|
|
4217
|
+
"InteractiveBook",
|
|
4218
|
+
"Slide",
|
|
4219
|
+
"SlideDeck",
|
|
4220
|
+
"AssessmentSequence"
|
|
4221
|
+
];
|
|
3583
4222
|
function extendParents(entry) {
|
|
3584
4223
|
if (!entry.parentConstraints?.length) return entry;
|
|
3585
4224
|
const merged = /* @__PURE__ */ new Set([...entry.parentConstraints, ...COMPOUND_PARENTS]);
|
|
@@ -3643,8 +4282,8 @@ var v3CompoundAndContentEntries = [
|
|
|
3643
4282
|
h5pMachineName: "H5P.Column",
|
|
3644
4283
|
h5pAlias: "Column",
|
|
3645
4284
|
description: "Column layout container (H5P Column / Page).",
|
|
3646
|
-
allowedChildTypes: [...
|
|
3647
|
-
maxNestingDepth:
|
|
4285
|
+
allowedChildTypes: [...import_core18.PAGE_ALLOWED_CHILD_TYPES],
|
|
4286
|
+
maxNestingDepth: import_core18.COMPOUND_MAX_NESTING_DEPTH.Page,
|
|
3648
4287
|
props: [
|
|
3649
4288
|
{ name: "blockId", type: "BlockId", required: true, description: "Stable block id." },
|
|
3650
4289
|
{ name: "title", type: "string", required: false, description: "Page title." },
|
|
@@ -3664,8 +4303,8 @@ var v3CompoundAndContentEntries = [
|
|
|
3664
4303
|
h5pMachineName: "H5P.InteractiveBook",
|
|
3665
4304
|
h5pAlias: "Interactive Book",
|
|
3666
4305
|
description: "Multi-page book with chapter navigation.",
|
|
3667
|
-
allowedChildTypes: [...
|
|
3668
|
-
maxNestingDepth:
|
|
4306
|
+
allowedChildTypes: [...import_core18.INTERACTIVE_BOOK_ALLOWED_CHILD_TYPES],
|
|
4307
|
+
maxNestingDepth: import_core18.COMPOUND_MAX_NESTING_DEPTH.InteractiveBook,
|
|
3669
4308
|
props: [
|
|
3670
4309
|
{ name: "blockId", type: "BlockId", required: true, description: "Stable block id." },
|
|
3671
4310
|
{ name: "title", type: "string", required: true, description: "Book title." },
|
|
@@ -3683,6 +4322,53 @@ var v3CompoundAndContentEntries = [
|
|
|
3683
4322
|
theming: { surface: "global-inherit", stylingNotes: "Book chrome." },
|
|
3684
4323
|
telemetry: { emits: ["book_page_viewed"], requiresActiveLesson: true }
|
|
3685
4324
|
},
|
|
4325
|
+
{
|
|
4326
|
+
type: "Slide",
|
|
4327
|
+
category: "container",
|
|
4328
|
+
compoundContract: true,
|
|
4329
|
+
h5pMachineName: "H5P.CoursePresentation",
|
|
4330
|
+
h5pAlias: "Course Presentation slide",
|
|
4331
|
+
description: "Single slide row in a SlideDeck. Planned allowlist expansion: Video, Summary.",
|
|
4332
|
+
allowedChildTypes: [...import_core18.SLIDE_ALLOWED_CHILD_TYPES],
|
|
4333
|
+
maxNestingDepth: import_core18.COMPOUND_MAX_NESTING_DEPTH.Slide,
|
|
4334
|
+
props: [
|
|
4335
|
+
{ name: "blockId", type: "BlockId", required: true, description: "Stable block id." },
|
|
4336
|
+
{ name: "title", type: "string", required: false, description: "Slide title." },
|
|
4337
|
+
{ name: "children", type: "ReactNode", required: true, description: "Slide content." }
|
|
4338
|
+
],
|
|
4339
|
+
requiredIds: [],
|
|
4340
|
+
optionalIds: ["blockId"],
|
|
4341
|
+
parentConstraints: ["SlideDeck"],
|
|
4342
|
+
a11y: { element: "section", ariaLabel: "Slide", keyboard: "N/A", notes: "H5P Course Presentation slide row." },
|
|
4343
|
+
theming: { surface: "global-inherit", stylingNotes: "Container." },
|
|
4344
|
+
telemetry: { emits: ["compound_page_viewed"], requiresActiveLesson: true }
|
|
4345
|
+
},
|
|
4346
|
+
{
|
|
4347
|
+
type: "SlideDeck",
|
|
4348
|
+
category: "container",
|
|
4349
|
+
compoundContract: true,
|
|
4350
|
+
h5pMachineName: "H5P.CoursePresentation",
|
|
4351
|
+
h5pAlias: "Course Presentation",
|
|
4352
|
+
description: "Multi-slide presentation with keyboard navigation.",
|
|
4353
|
+
allowedChildTypes: [...import_core18.SLIDE_DECK_ALLOWED_CHILD_TYPES],
|
|
4354
|
+
maxNestingDepth: import_core18.COMPOUND_MAX_NESTING_DEPTH.SlideDeck,
|
|
4355
|
+
props: [
|
|
4356
|
+
{ name: "blockId", type: "BlockId", required: true, description: "Stable block id." },
|
|
4357
|
+
{ name: "title", type: "string", required: true, description: "Deck title." },
|
|
4358
|
+
{ name: "showDeckScore", type: "boolean", required: false, description: "Show aggregate score." },
|
|
4359
|
+
{ name: "children", type: "Slide[]", required: true, description: "Slides." }
|
|
4360
|
+
],
|
|
4361
|
+
requiredIds: ["blockId"],
|
|
4362
|
+
parentConstraints: ["Lesson"],
|
|
4363
|
+
a11y: {
|
|
4364
|
+
element: "section",
|
|
4365
|
+
ariaLabel: "Slide deck",
|
|
4366
|
+
keyboard: "Arrow keys, Home, End, Previous/Next slide buttons.",
|
|
4367
|
+
notes: "H5P Course Presentation equivalent."
|
|
4368
|
+
},
|
|
4369
|
+
theming: { surface: "global-inherit", stylingNotes: "Deck chrome." },
|
|
4370
|
+
telemetry: { emits: ["slide_viewed"], requiresActiveLesson: true }
|
|
4371
|
+
},
|
|
3686
4372
|
{
|
|
3687
4373
|
type: "Accordion",
|
|
3688
4374
|
category: "content",
|
|
@@ -3816,8 +4502,8 @@ function buildV3CatalogFromV2(v2) {
|
|
|
3816
4502
|
return {
|
|
3817
4503
|
...base,
|
|
3818
4504
|
compoundContract: true,
|
|
3819
|
-
allowedChildTypes: [...
|
|
3820
|
-
maxNestingDepth:
|
|
4505
|
+
allowedChildTypes: [...import_core18.ASSESSMENT_SEQUENCE_ALLOWED_CHILD_TYPES],
|
|
4506
|
+
maxNestingDepth: import_core18.COMPOUND_MAX_NESTING_DEPTH.AssessmentSequence
|
|
3821
4507
|
};
|
|
3822
4508
|
}
|
|
3823
4509
|
return base;
|
|
@@ -4229,6 +4915,8 @@ function getBlockCatalogEntry(type, opts) {
|
|
|
4229
4915
|
Quiz,
|
|
4230
4916
|
Reflection,
|
|
4231
4917
|
Scenario,
|
|
4918
|
+
Slide,
|
|
4919
|
+
SlideDeck,
|
|
4232
4920
|
Text,
|
|
4233
4921
|
ThemeProvider,
|
|
4234
4922
|
TrueFalse,
|