@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/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: () => import_core17.buildTelemetryEvent,
68
- createLessonkitRuntime: () => import_core17.createLessonkitRuntime,
69
- createPluginRegistry: () => import_core17.createPluginRegistry,
70
- createTelemetryPipeline: () => import_core17.createTelemetryPipeline,
71
- defineAssessmentPlugin: () => import_core17.defineAssessmentPlugin,
72
- defineLifecyclePlugin: () => import_core17.defineLifecyclePlugin,
73
- defineTelemetryPlugin: () => import_core17.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 import_react11 = require("react");
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
- tracking: { ...normalizedConfig.tracking, sink, batchSink }
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 import_core9 = require("@lessonkit/core");
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, import_core9.assertValidId)(id, "courseId");
1152
- if (path === "lessonId") return (0, import_core9.assertValidId)(id, "lessonId");
1153
- if (path === "checkId") return (0, import_core9.assertValidId)(id, "checkId");
1154
- if (path === "blockId") return (0, import_core9.assertValidId)(id, "blockId");
1155
- return (0, import_core9.assertValidId)(id, path);
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 import_react10 = require("react");
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 import_react8 = require("react");
1267
+ var import_react10 = require("react");
1249
1268
 
1250
1269
  // src/compound/CompoundProvider.tsx
1251
- var import_react7 = __toESM(require("react"), 1);
1252
- var import_core10 = require("@lessonkit/core");
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 handle of handles) {
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
- if (!handle.getAnswerGiven()) allAnswered = false;
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/resumeChildHandles.ts
1268
- function resumeChildHandles(handles, childStates, opts) {
1269
- if (opts?.waitForHandles && handles.size === 0 && Object.keys(childStates).length > 0) {
1270
- return false;
1271
- }
1272
- for (const [checkId, handle] of handles) {
1273
- const child = childStates[checkId];
1274
- if (child && handle.resume) handle.resume(child);
1275
- }
1276
- return true;
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 import_jsx_runtime3 = require("react/jsx-runtime");
1281
- var CompoundRegistryContext = (0, import_react7.createContext)(null);
1282
- var CompoundHandlesVersionContext = (0, import_react7.createContext)(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, import_react7.useRef)(/* @__PURE__ */ new Map());
1289
- const [handlesVersion, setHandlesVersion] = (0, import_react7.useState)(0);
1290
- const register = (0, import_react7.useCallback)((checkId, handle) => {
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
- registryRef.current.set(checkId, handle);
1293
- if (prev !== handle) {
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
- if (registryRef.current.get(checkId) === handle) {
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, import_react7.useMemo)(
1350
+ const registryValue = (0, import_react9.useMemo)(
1304
1351
  () => ({
1305
1352
  register,
1306
- getHandles: () => registryRef.current
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, import_jsx_runtime3.jsx)(CompoundRegistryContext.Provider, { value: registryValue, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(CompoundHandlesVersionContext.Provider, { value: handlesVersion, children }) });
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, import_react7.useContext)(CompoundRegistryContext);
1314
- const handlesVersion = (0, import_react7.useContext)(CompoundHandlesVersionContext);
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, import_react7.useContext)(CompoundHandlesVersionContext);
1373
+ return (0, import_react9.useContext)(CompoundHandlesVersionContext);
1320
1374
  }
1321
1375
  function useRegisterAssessmentHandle(checkId, handle) {
1322
- const registry = (0, import_react7.useContext)(CompoundRegistryContext);
1323
- import_react7.default.useEffect(() => {
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 setIndexClamped = (0, import_react7.useCallback)(
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, import_core10.clampCompoundPageIndex)(index, pageCount) : Math.max(0, Math.floor(index));
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, import_react7.useImperativeHandle)(
1393
+ (0, import_react9.useImperativeHandle)(
1338
1394
  ref,
1339
1395
  () => ({
1340
- getScore: () => aggregateAssessmentScores(getHandles().values()).score,
1341
- getMaxScore: () => aggregateAssessmentScores(getHandles().values()).maxScore,
1342
- getAnswerGiven: () => aggregateAssessmentScores(getHandles().values()).allAnswered,
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 handle of getHandles().values()) handle.resetTask();
1402
+ for (const entry of getRegisteredHandles().values()) entry.handle.resetTask();
1345
1403
  },
1346
1404
  showSolutions: () => {
1347
1405
  if (!opts.enableSolutionsButton) return;
1348
- for (const handle of getHandles().values()) handle.showSolutions();
1406
+ for (const entry of getRegisteredHandles().values()) entry.handle.showSolutions();
1349
1407
  },
1350
1408
  getCurrentState: () => {
1351
1409
  const childStates = {};
1352
- for (const [checkId, handle] of getHandles()) {
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, import_core10.createCompoundResumeState)({ activePageIndex, childStates });
1415
+ return (0, import_core11.createCompoundResumeState)({ activePageIndex, childStates });
1358
1416
  },
1359
1417
  resume: (state) => {
1360
- setIndexClamped(state.activePageIndex);
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, import_react8.useImperativeHandle)(ref, () => handle, [handle]);
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 import_react9 = require("react");
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, import_react9.useCallback)(
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, import_react9.useCallback)(
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, import_react9.useCallback)(
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 import_jsx_runtime4 = require("react/jsx-runtime");
1491
+ var import_jsx_runtime6 = require("react/jsx-runtime");
1435
1492
  function QuizInner(props, ref) {
1436
1493
  const { enclosingLessonId } = props;
1437
- const checkId = (0, import_react10.useMemo)(() => normalizeComponentId(props.checkId, "checkId"), [props.checkId]);
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, import_react10.useState)(null);
1441
- const [selectionCorrect, setSelectionCorrect] = (0, import_react10.useState)(null);
1442
- const [quizPassed, setQuizPassed] = (0, import_react10.useState)(false);
1443
- const completedRef = (0, import_react10.useRef)(false);
1444
- const questionId = (0, import_react10.useId)();
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, import_react10.useEffect)(() => {
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 handle = (0, import_react10.useMemo)(
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
- const maxScore = 1;
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
- checkId,
1474
- interactionType: "mcq",
1475
- response: selected ?? void 0,
1476
- correct: selectionCorrect ?? void 0,
1477
- score: quizPassed && selected !== null ? 1 : selected === null ? 0 : selectionCorrect ? 1 : 0,
1478
- maxScore: 1
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
- readBooleanStateField(state, "quizPassed", (value) => {
1489
- setQuizPassed(value);
1490
- completedRef.current = value;
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
- [checkId, quizPassed, selected, selectionCorrect]
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, import_jsx_runtime4.jsxs)("section", { "aria-label": "Quiz", "data-lk-check-id": checkId, children: [
1498
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { id: questionId, children: props.question }),
1499
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("fieldset", { "aria-labelledby": questionId, children: [
1500
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("legend", { style: import_accessibility.visuallyHiddenStyle, children: "Quiz choices" }),
1501
- props.choices.map((c, i) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { style: { display: "block" }, children: [
1502
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
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: custom?.score ?? maxScore,
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, import_jsx_runtime4.jsx)("p", { role: "status", "aria-live": "polite", children: selectionCorrect ? "Correct" : "Try again" }) : null
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, import_react10.forwardRef)(QuizInner);
1544
- var Quiz = (0, import_react10.forwardRef)(function Quiz2(props, ref) {
1545
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(AssessmentLessonGuard, { blockLabel: "Quiz", checkId: props.checkId, children: (lessonId) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(QuizInnerForwarded, { ...props, enclosingLessonId: lessonId, ref }) });
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, import_jsx_runtime4.jsx)(
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 import_jsx_runtime5 = require("react/jsx-runtime");
1691
+ var import_jsx_runtime7 = require("react/jsx-runtime");
1565
1692
  function Course(props) {
1566
- const courseId = (0, import_react11.useMemo)(() => normalizeComponentId(props.courseId, "courseId"), [props.courseId]);
1567
- const providerConfig = (0, import_react11.useMemo)(
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, import_jsx_runtime5.jsx)(LessonkitProvider, { config: providerConfig, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("section", { "aria-label": props.title, children: [
1572
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("h1", { children: props.title }),
1573
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { children: props.children })
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, import_react11.useMemo)(() => normalizeComponentId(props.lessonId, "lessonId"), [props.lessonId]);
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, import_react11.useRef)(0);
1582
- const liveCourseIdRef = (0, import_react11.useRef)(config.courseId);
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, import_react11.useEffect)(() => {
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, import_jsx_runtime5.jsx)(LessonContext.Provider, { value: lessonId, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("article", { "aria-label": props.title, children: [
1610
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("h2", { children: props.title }),
1611
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { children: props.children })
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, import_react11.useMemo)(
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, import_jsx_runtime5.jsx)("section", { "aria-label": "Scenario", "data-lk-block-id": blockId, children: props.children });
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, import_react11.useMemo)(
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, import_react11.useId)();
1627
- const hintId = (0, import_react11.useId)();
1628
- const [internalValue, setInternalValue] = (0, import_react11.useState)("");
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, import_jsx_runtime5.jsxs)("section", { "aria-label": "Reflection", "data-lk-block-id": blockId, children: [
1636
- props.prompt ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { id: promptId, children: props.prompt }) : null,
1637
- props.hint ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { id: hintId, style: import_accessibility2.visuallyHiddenStyle, children: props.hint }) : null,
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, import_jsx_runtime5.jsx)(
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, import_jsx_runtime5.jsx)("aside", { "aria-label": "Progress", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
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, import_jsx_runtime5.jsxs)("p", { children: [
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, import_jsx_runtime5.jsx)("aside", { "aria-label": "Progress", role: "status", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("p", { children: [
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 import_react12 = __toESM(require("react"), 1);
1682
- var import_jsx_runtime6 = require("react/jsx-runtime");
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, import_react12.useMemo)(() => normalizeComponentId(props.checkId, "checkId"), [props.checkId]);
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, import_react12.useState)(null);
1691
- const [selectionCorrect, setSelectionCorrect] = (0, import_react12.useState)(null);
1692
- const [showSolutions, setShowSolutions] = (0, import_react12.useState)(false);
1693
- const [passed, setPassed] = (0, import_react12.useState)(false);
1694
- const completedRef = (0, import_react12.useRef)(false);
1695
- const questionId = import_react12.default.useId();
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, import_react12.useEffect)(() => {
1836
+ (0, import_react14.useEffect)(() => {
1704
1837
  reset();
1705
1838
  }, [checkId, props.answer, props.question, config.courseId, enclosingLessonId]);
1706
- const handle = (0, import_react12.useMemo)(
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
- const maxScore = 1;
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
- checkId,
1719
- interactionType: INTERACTION,
1720
- response: selected ?? void 0,
1721
- correct: selected === props.answer,
1722
- score: passed ? 1 : selected === null ? 0 : selected === props.answer ? 1 : 0,
1723
- maxScore: 1
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
- readBooleanStateField(state, "passed", (value) => {
1736
- setPassed(value);
1737
- completedRef.current = value;
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
- [checkId, passed, props.answer, selected, selectionCorrect, showSolutions]
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, import_jsx_runtime6.jsxs)("section", { "aria-label": "True or False", "data-lk-check-id": checkId, children: [
1772
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { id: questionId, children: props.question }),
1773
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("fieldset", { "aria-labelledby": questionId, children: [
1774
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("legend", { className: "lk-visually-hidden", children: "True or False" }),
1775
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("label", { style: { display: "block", marginRight: "1rem" }, children: [
1776
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
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, import_jsx_runtime6.jsxs)("label", { style: { display: "block" }, children: [
1789
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
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, import_jsx_runtime6.jsxs)("p", { children: [
1995
+ reveal ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("p", { children: [
1803
1996
  "Correct answer: ",
1804
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("strong", { children: props.answer ? "True" : "False" })
1997
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("strong", { children: props.answer ? "True" : "False" })
1805
1998
  ] }) : null,
1806
- selected !== null && selectionCorrect !== null ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { role: "status", "aria-live": "polite", children: selectionCorrect ? "Correct" : "Try again" }) : null,
1807
- props.enableRetry && passed ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", onClick: reset, children: "Try again" }) : null,
1808
- props.enableSolutionsButton && !reveal ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", onClick: () => setShowSolutions(true), children: "Show solution" }) : null
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, import_react12.forwardRef)(TrueFalseInner);
1812
- var TrueFalse = (0, import_react12.forwardRef)(function TrueFalse2(props, ref) {
1813
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(AssessmentLessonGuard, { blockLabel: "TrueFalse", checkId: props.checkId, children: (lessonId) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(TrueFalseInnerForwarded, { ...props, enclosingLessonId: lessonId, ref }) });
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 import_react13 = __toESM(require("react"), 1);
1818
- var import_jsx_runtime7 = require("react/jsx-runtime");
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, import_react13.useMemo)(() => normalizeComponentId(props.checkId, "checkId"), [props.checkId]);
2017
+ const checkId = (0, import_react15.useMemo)(() => normalizeComponentId(props.checkId, "checkId"), [props.checkId]);
1825
2018
  const assessment = useAssessmentState(props.enclosingLessonId);
1826
- const tokens = (0, import_react13.useMemo)(() => tokenize(props.text), [props.text]);
1827
- const correctSet = (0, import_react13.useMemo)(
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, import_react13.useState)(() => /* @__PURE__ */ new Set());
1832
- const [passed, setPassed] = (0, import_react13.useState)(false);
1833
- const [showSolutions, setShowSolutions] = (0, import_react13.useState)(false);
1834
- const completedRef = (0, import_react13.useRef)(false);
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, import_react13.useEffect)(() => {
2034
+ (0, import_react15.useEffect)(() => {
1842
2035
  reset();
1843
2036
  }, [checkId, props.text, props.correctWords.join("\0")]);
1844
- const selectableIndices = (0, import_react13.useMemo)(() => {
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, import_react13.useMemo)(
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, import_react13.useEffect)(() => {
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: true
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, import_jsx_runtime7.jsxs)("section", { "aria-label": "Mark the Words", "data-lk-check-id": checkId, children: [
1936
- !hasTargets ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("p", { role: "alert", children: [
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, import_jsx_runtime7.jsx)("code", { children: "correctWords" }),
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, import_jsx_runtime7.jsx)("p", { id: `${checkId}-hint`, children: "Select the correct words in the sentence." }),
1942
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { "aria-describedby": `${checkId}-hint`, children: tokens.map((token, i) => {
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, import_jsx_runtime7.jsx)(import_react13.default.Fragment, { children: token }, i);
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, import_jsx_runtime7.jsx)(
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, import_jsx_runtime7.jsx)("p", { role: "status", "aria-live": "polite", children: "Correct" }) : null,
1967
- props.enableRetry && passed ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { type: "button", onClick: reset, children: "Try again" }) : null,
1968
- props.enableSolutionsButton && !showSolutions ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { type: "button", onClick: () => setShowSolutions(true), children: "Show solution" }) : null
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, import_react13.forwardRef)(MarkTheWordsInner);
1972
- var MarkTheWords = (0, import_react13.forwardRef)(function MarkTheWords2(props, ref) {
1973
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(AssessmentLessonGuard, { blockLabel: "MarkTheWords", checkId: props.checkId, children: (lessonId) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(MarkTheWordsInnerForwarded, { ...props, enclosingLessonId: lessonId, ref }) });
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 import_react14 = __toESM(require("react"), 1);
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 import_jsx_runtime8 = require("react/jsx-runtime");
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, import_react14.useMemo)(() => normalizeComponentId(props.checkId, "checkId"), [props.checkId]);
2201
+ const checkId = (0, import_react16.useMemo)(() => normalizeComponentId(props.checkId, "checkId"), [props.checkId]);
2009
2202
  const assessment = useAssessmentState(props.enclosingLessonId);
2010
- const parsed = (0, import_react14.useMemo)(() => parseTemplate(props.template), [props.template]);
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, import_react14.useState)(
2205
+ const [values, setValues] = (0, import_react16.useState)(
2013
2206
  () => Object.fromEntries(blanks.map((b) => [b.id, ""]))
2014
2207
  );
2015
- const [passed, setPassed] = (0, import_react14.useState)(false);
2016
- const [showSolutions, setShowSolutions] = (0, import_react14.useState)(false);
2017
- const completedRef = (0, import_react14.useRef)(false);
2018
- const answeredRef = (0, import_react14.useRef)(false);
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, import_react14.useEffect)(() => {
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, import_react14.useMemo)(
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 (!answeredRef.current) {
2077
- answeredRef.current = true;
2078
- assessment.answer({
2079
- checkId,
2080
- interactionType: INTERACTION3,
2081
- question: props.template,
2082
- response: values,
2083
- correct: passedThreshold
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, import_react14.useEffect)(() => {
2099
- if (!allFilled) answeredRef.current = false;
2297
+ (0, import_react16.useEffect)(() => {
2298
+ if (!allFilled) {
2299
+ answeredRef.current = false;
2300
+ setSubmitted(false);
2301
+ }
2100
2302
  }, [allFilled]);
2101
- (0, import_react14.useEffect)(() => {
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, import_jsx_runtime8.jsxs)("section", { "aria-label": "Fill in the Blanks", "data-lk-check-id": checkId, children: [
2106
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { children: parsed.parts.map((part, i) => {
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, import_jsx_runtime8.jsx)(import_react14.default.Fragment, { children: part }, i);
2109
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("label", { style: { margin: "0 0.25em" }, children: [
2110
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "lk-visually-hidden", children: blank.answer }),
2111
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
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, import_jsx_runtime8.jsx)("button", { type: "button", "data-testid": "check-blanks", disabled: !allFilled || passed, onClick: check, children: "Check" }) : null,
2128
- !hasBlanks ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { role: "alert", children: "This activity has no blanks. Add text wrapped in asterisks, e.g. The *answer* here." }) : null,
2129
- allFilled ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { role: "status", "aria-live": "polite", children: passed || passedThreshold ? "Correct" : "Try again" }) : null,
2130
- props.enableRetry && passed ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", onClick: reset, children: "Try again" }) : null,
2131
- props.enableSolutionsButton && !reveal ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", onClick: () => setShowSolutions(true), children: "Show solution" }) : null
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, import_react14.forwardRef)(FillInTheBlanksInner);
2135
- var FillInTheBlanks = (0, import_react14.forwardRef)(
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, import_jsx_runtime8.jsx)(AssessmentLessonGuard, { blockLabel: "FillInTheBlanks", checkId: props.checkId, children: (lessonId) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(FillInTheBlanksInnerForwarded, { ...props, enclosingLessonId: lessonId, ref }) });
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 import_react15 = __toESM(require("react"), 1);
2143
- var import_jsx_runtime9 = require("react/jsx-runtime");
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, import_react15.useMemo)(() => normalizeComponentId(props.checkId, "checkId"), [props.checkId]);
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, import_react15.useMemo)(() => parseZones(props.template), [props.template]);
2153
- const [zones, setZones] = (0, import_react15.useState)(
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, import_react15.useState)(() => [...props.words]);
2157
- const [keyboardWord, setKeyboardWord] = (0, import_react15.useState)(null);
2158
- const [passed, setPassed] = (0, import_react15.useState)(false);
2159
- const completedRef = (0, import_react15.useRef)(false);
2160
- const answeredRef = (0, import_react15.useRef)(false);
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, import_react15.useEffect)(() => {
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, import_react15.useMemo)(
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 (!answeredRef.current) {
2242
- answeredRef.current = true;
2243
- assessment.answer({
2244
- checkId,
2245
- interactionType: INTERACTION4,
2246
- question: props.template,
2247
- response: zones,
2248
- correct: passedThreshold
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, import_react15.useEffect)(() => {
2264
- if (!allFilled) answeredRef.current = false;
2471
+ (0, import_react17.useEffect)(() => {
2472
+ if (!allFilled) {
2473
+ answeredRef.current = false;
2474
+ setSubmitted(false);
2475
+ }
2265
2476
  }, [allFilled]);
2266
- (0, import_react15.useEffect)(() => {
2477
+ (0, import_react17.useEffect)(() => {
2267
2478
  if (props.autoCheck && allFilled) check();
2268
2479
  }, [allFilled, props.autoCheck, zones, passedThreshold]);
2269
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("section", { "aria-label": "Drag the Words", "data-lk-check-id": checkId, children: [
2270
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { children: "Drag words into the blanks (or select a word, then activate a blank)." }),
2271
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { role: "list", "aria-label": "Word bank", "data-testid": "word-bank", children: pool.map((word) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
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, import_jsx_runtime9.jsx)("p", { children: parts.map((part, i) => {
2286
- if (!part.startsWith("zone-")) return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react15.default.Fragment, { children: part }, i);
2287
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
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, import_jsx_runtime9.jsx)("button", { type: "button", "data-testid": "check-drag-words", disabled: !allFilled || passed, onClick: check, children: "Check" }),
2312
- !hasZones ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { role: "alert", children: "This activity has no drop zones. Wrap answers in asterisks in the template." }) : null,
2313
- allFilled ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { role: "status", "aria-live": "polite", children: passed || passedThreshold ? "Correct" : "Try again" }) : null
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, import_react15.forwardRef)(DragTheWordsInner);
2317
- var DragTheWords = (0, import_react15.forwardRef)(function DragTheWords2(props, ref) {
2318
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(AssessmentLessonGuard, { blockLabel: "DragTheWords", checkId: props.checkId, children: (lessonId) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(DragTheWordsInnerForwarded, { ...props, enclosingLessonId: lessonId, ref }) });
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 import_react16 = require("react");
2323
- var import_jsx_runtime10 = require("react/jsx-runtime");
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, import_react16.useMemo)(() => normalizeComponentId(props.checkId, "checkId"), [props.checkId]);
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, import_react16.useState)(
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, import_react16.useState)(() => props.items.map((i) => i.id));
2332
- const [keyboardItem, setKeyboardItem] = (0, import_react16.useState)(null);
2333
- const [passed, setPassed] = (0, import_react16.useState)(false);
2334
- const completedRef = (0, import_react16.useRef)(false);
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, import_react16.useEffect)(() => {
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 allFilled = props.targets.every((t) => (assignments[t.id] ?? "").length > 0);
2346
- const allCorrect = props.targets.every((t) => assignments[t.id] === t.accepts);
2347
- const handle = (0, import_react16.useMemo)(() => {
2348
- const maxScore = props.targets.length || 1;
2349
- let score = 0;
2350
- props.targets.forEach((t) => {
2351
- if (assignments[t.id] === t.accepts) score += 1;
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: allCorrect,
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
- }, [allCorrect, allFilled, assignments, checkId, keyboardItem, passed, pool, props.targets]);
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: allCorrect
2620
+ correct: passedThreshold
2404
2621
  });
2405
- if (allCorrect && !completedRef.current) {
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: props.targets.length,
2412
- maxScore: props.targets.length,
2413
- passingScore: props.passingScore ?? props.targets.length
2628
+ score,
2629
+ maxScore,
2630
+ passingScore: props.passingScore ?? maxScore
2414
2631
  });
2415
2632
  }
2416
2633
  };
2417
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("section", { "aria-label": "Drag and Drop", "data-lk-check-id": checkId, children: [
2418
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { children: "Match each item to the correct target (drag or use keyboard: select item, then activate target)." }),
2419
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { role: "list", "aria-label": "Draggable items", children: pool.map((id) => {
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
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
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, import_jsx_runtime10.jsx)("ul", { children: props.targets.map((target) => {
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, import_jsx_runtime10.jsxs)("li", { children: [
2440
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("strong", { children: target.label }),
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, import_jsx_runtime10.jsx)(
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, import_jsx_runtime10.jsx)("button", { type: "button", "data-testid": "check-drag-drop", disabled: !allFilled || passed, onClick: check, children: "Check" }),
2470
- allFilled ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { role: "status", "aria-live": "polite", children: passed || allCorrect ? "Correct" : "Try again" }) : null
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, import_react16.forwardRef)(DragAndDropInner);
2474
- var DragAndDrop = (0, import_react16.forwardRef)(function DragAndDrop2(props, ref) {
2475
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(AssessmentLessonGuard, { blockLabel: "DragAndDrop", checkId: props.checkId, children: (lessonId) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(DragAndDropInnerForwarded, { ...props, enclosingLessonId: lessonId, ref }) });
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 import_react22 = __toESM(require("react"), 1);
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 import_react20 = require("react");
2483
- var import_core14 = require("@lessonkit/core");
2701
+ var import_react22 = require("react");
2702
+ var import_core15 = require("@lessonkit/core");
2484
2703
 
2485
2704
  // src/compound/useCompoundNavigation.ts
2486
- var import_react17 = require("react");
2705
+ var import_react19 = require("react");
2487
2706
  function useCompoundNavigation(pageCount, index, setIndex) {
2488
- const goNext = (0, import_react17.useCallback)(() => {
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, import_react17.useCallback)(() => {
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 import_react19 = require("react");
2507
- var import_core13 = require("@lessonkit/core");
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 import_react18 = require("react");
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 storageRef = (0, import_react18.useRef)(opts.storage ?? (0, import_core12.createSessionStoragePort)());
2515
- const resumedRef = (0, import_react18.useRef)(false);
2516
- (0, import_react18.useEffect)(() => {
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, import_core11.loadCompoundState)(storageRef.current, opts.courseId, opts.compoundId);
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, import_react18.useCallback)(
2809
+ return (0, import_react20.useCallback)(
2525
2810
  (state) => {
2526
2811
  if (!opts.enabled || !opts.courseId) return;
2527
- (0, import_core11.saveCompoundState)(storageRef.current, opts.courseId, opts.compoundId, state);
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, import_core13.createSessionStoragePort)()) {
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, import_core13.loadCompoundState)(storage, courseId, compoundId);
2822
+ const saved = (0, import_core14.loadCompoundState)(storage, courseId, compoundId);
2537
2823
  if (!saved) return 0;
2538
- return (0, import_core13.clampCompoundPageIndex)(saved.activePageIndex, pageCount);
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 storage = opts.storage ?? (0, import_core13.createSessionStoragePort)();
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 pendingChildResumeRef = (0, import_react19.useRef)(null);
2545
- const loadedChildStatesRef = (0, import_react19.useRef)({});
2546
- const skipSaveUntilHydratedRef = (0, import_react19.useRef)(false);
2547
- const buildState = (0, import_react19.useCallback)(() => {
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, handle] of ctx.getHandles()) {
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, import_core13.createCompoundResumeState)({
2560
- activePageIndex: (0, import_core13.clampCompoundPageIndex)(opts.index, opts.pageCount),
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 applyPendingChildResume = (0, import_react19.useCallback)(() => {
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 applied = resumeChildHandles(ctx.getHandles(), pending.childStates, { waitForHandles: true });
2568
- if (!applied) return;
2569
- pendingChildResumeRef.current = null;
2570
- skipSaveUntilHydratedRef.current = false;
2571
- }, [ctx]);
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, import_core13.clampCompoundPageIndex)(state.activePageIndex, opts.pageCount);
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
- pendingChildResumeRef.current = { ...state, activePageIndex: clamped };
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, import_react19.useEffect)(() => {
2930
+ const persistNow = (0, import_react21.useCallback)(() => {
2587
2931
  if (!opts.enabled || !opts.courseId) return;
2588
- if (skipSaveUntilHydratedRef.current) return;
2589
- saveResume(buildState());
2590
- }, [
2591
- opts.enabled,
2592
- opts.courseId,
2593
- opts.index,
2594
- opts.pageCount,
2595
- handlesVersion,
2596
- saveResume,
2597
- buildState
2598
- ]);
2599
- (0, import_react19.useEffect)(() => {
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, import_core14.clampCompoundPageIndex)(opts.index, opts.pageCount);
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, import_react20.useMemo)(
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 import_react21 = __toESM(require("react"), 1);
2642
- var import_core15 = require("@lessonkit/core");
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
- import_react21.default.Children.forEach(node, (child) => {
2677
- if (!import_react21.default.isValidElement(child)) return;
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, import_core15.isChildTypeAllowed)(parent, blockType)) {
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 = import_core15.COMPOUND_MAX_NESTING_DEPTH[parent];
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
- import_core15.ACCORDION_FORBIDDEN_CHILD_TYPES,
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
- import_react21.default.Children.forEach(node, (child) => {
2718
- if (!import_react21.default.isValidElement(child)) return;
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, import_core15.ACCORDION_FORBIDDEN_CHILD_TYPES, strict);
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 import_jsx_runtime11 = require("react/jsx-runtime");
2759
- var AssessmentSequenceInner = (0, import_react22.forwardRef)(
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, import_jsx_runtime11.jsx)("section", { "aria-label": "Assessment sequence", "data-testid": "assessment-sequence", children: props.children });
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, import_jsx_runtime11.jsxs)("section", { "aria-label": "Assessment sequence", "data-testid": "assessment-sequence", children: [
2779
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("p", { children: [
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, import_jsx_runtime11.jsx)("div", { "data-testid": "assessment-sequence-step", children: childArray.map((child, i) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { hidden: i !== visibleIndex, children: child }, child.key ?? i)) }),
2786
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("nav", { "aria-label": "Sequence navigation", children: [
2787
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
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, import_jsx_runtime11.jsx)(
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, import_react22.forwardRef)(
3185
+ var AssessmentSequence = (0, import_react24.forwardRef)(
2812
3186
  function AssessmentSequence2(props, ref) {
2813
- const compoundId = (0, import_react22.useMemo)(
2814
- () => props.blockId ? normalizeComponentId(props.blockId, "blockId") : DEFAULT_ASSESSMENT_SEQUENCE_COMPOUND_ID,
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 = import_react22.default.Children.toArray(props.children).filter(
2818
- import_react22.default.isValidElement
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, import_react22.useEffect)(() => {
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, import_react22.useState)(initialIndex);
2836
- const setIndexStable = (0, import_react22.useCallback)((i) => setIndex(i), []);
2837
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(CompoundProvider, { activePageIndex: index, onActivePageIndexChange: setIndexStable, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
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 import_react23 = require("react");
2855
- var import_jsx_runtime12 = require("react/jsx-runtime");
3237
+ var import_react25 = require("react");
3238
+ var import_jsx_runtime14 = require("react/jsx-runtime");
2856
3239
  function Text(props) {
2857
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { "data-lk-block-id": props.blockId, "data-testid": props.blockId ? `text-${props.blockId}` : "text", children: props.children });
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 import_jsx_runtime13 = require("react/jsx-runtime");
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, import_jsx_runtime13.jsx)(Tag, { "data-lk-block-id": props.blockId, "data-testid": props.blockId ? `heading-${props.blockId}` : "heading", children: props.children });
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 import_jsx_runtime14 = require("react/jsx-runtime");
3253
+ var import_jsx_runtime16 = require("react/jsx-runtime");
2871
3254
  function Image(props) {
2872
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
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 import_react24 = require("react");
2887
- var import_jsx_runtime15 = require("react/jsx-runtime");
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, import_react24.useEffect)(() => {
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, import_jsx_runtime15.jsxs)(
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, import_jsx_runtime15.jsx)("h3", { children: props.title }) : null,
2913
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { children: props.children })
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 import_react25 = __toESM(require("react"), 1);
2922
- var import_jsx_runtime16 = require("react/jsx-runtime");
2923
- var InteractiveBookInner = (0, import_react25.forwardRef)(
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, import_react25.useMemo)(
3321
+ const pageTitles = (0, import_react27.useMemo)(
2939
3322
  () => pages.map((page) => page.props.title),
2940
3323
  [pages]
2941
3324
  );
2942
- (0, import_react25.useEffect)(() => {
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, import_jsx_runtime16.jsxs)("section", { "aria-label": props.title, "data-testid": "interactive-book", "data-lk-block-id": blockId, children: [
2955
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("h3", { children: props.title }),
2956
- /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("p", { children: [
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, import_jsx_runtime16.jsxs)("p", { "data-testid": "book-score", children: [
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, import_jsx_runtime16.jsx)("div", { "data-testid": "interactive-book-page", children: pages.map(
2970
- (page, i) => import_react25.default.cloneElement(page, {
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, import_jsx_runtime16.jsxs)("nav", { "aria-label": "Book navigation", children: [
2978
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
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, import_jsx_runtime16.jsx)(
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, import_react25.forwardRef)(function InteractiveBook2(props, ref) {
3003
- const blockId = (0, import_react25.useMemo)(
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 = import_react25.default.Children.toArray(props.children).filter(
3008
- import_react25.default.isValidElement
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, import_react25.useState)(initialIndex);
3019
- const setIndexStable = (0, import_react25.useCallback)((i) => setIndex(i), []);
3020
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(CompoundProvider, { activePageIndex: index, onActivePageIndexChange: setIndexStable, children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
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 import_react26 = require("react");
3037
- var import_jsx_runtime17 = require("react/jsx-runtime");
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, import_react26.useState)(/* @__PURE__ */ new Set());
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, import_react26.useId)();
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, import_jsx_runtime17.jsx)("section", { "aria-label": "Accordion", "data-lk-block-id": props.blockId, "data-testid": "accordion", children: props.sections.map((section) => {
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, import_jsx_runtime17.jsxs)("div", { "data-testid": `accordion-section-${section.id}`, children: [
3065
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("h4", { children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
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, import_jsx_runtime17.jsx)("div", { id: panelId, role: "region", "aria-labelledby": triggerId, children: section.content }) : null
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 import_react27 = require("react");
3085
- var import_jsx_runtime18 = require("react/jsx-runtime");
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, import_react27.useState)(0);
3088
- const [flipped, setFlipped] = (0, import_react27.useState)(false);
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, import_jsx_runtime18.jsxs)("section", { "aria-label": "Dialog cards", "data-lk-block-id": props.blockId, "data-testid": "dialog-cards", children: [
3092
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("p", { children: [
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, import_jsx_runtime18.jsx)(
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, import_jsx_runtime18.jsxs)("nav", { "aria-label": "Card navigation", children: [
3110
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
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, import_jsx_runtime18.jsx)(
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 import_react28 = require("react");
3143
- var import_jsx_runtime19 = require("react/jsx-runtime");
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, import_react28.useState)(0);
3146
- const [face, setFace] = (0, import_react28.useState)("front");
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, import_jsx_runtime19.jsxs)("section", { "aria-label": "Flashcards", "data-lk-block-id": props.blockId, "data-testid": "flashcards", children: [
3161
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("button", { type: "button", "data-testid": "flashcard-flip", onClick: flip, style: { minHeight: "6rem", width: "100%" }, children: face === "front" ? card.front : card.back }),
3162
- props.selfScore ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("p", { "data-testid": "flashcard-self-score", children: "Self-score mode enabled" }) : null,
3163
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
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 import_react29 = require("react");
3182
- var import_jsx_runtime20 = require("react/jsx-runtime");
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, import_react29.useState)(null);
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, import_jsx_runtime20.jsxs)("section", { "aria-label": "Image hotspots", "data-lk-block-id": props.blockId, "data-testid": "image-hotspots", children: [
3196
- /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { style: { position: "relative", display: "inline-block" }, children: [
3197
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("img", { src: props.src, alt: props.alt, style: { maxWidth: "100%" } }),
3198
- props.hotspots.map((h) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
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, import_jsx_runtime20.jsxs)("div", { role: "dialog", "aria-label": "Hotspot details", "data-testid": "hotspot-popover", children: [
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, import_jsx_runtime20.jsx)("button", { type: "button", onClick: () => setActive(null), children: "Close" })
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 import_react30 = require("react");
3227
- var import_jsx_runtime21 = require("react/jsx-runtime");
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, import_react30.useState)(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, import_jsx_runtime21.jsxs)("section", { "aria-label": "Image slider", "data-lk-block-id": props.blockId, "data-testid": "image-slider", children: [
3243
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("img", { src: slide.src, alt: slide.alt, style: { maxWidth: "100%" } }),
3244
- slide.caption ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("p", { children: slide.caption }) : null,
3245
- /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("nav", { "aria-label": "Slide navigation", children: [
3246
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
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, import_jsx_runtime21.jsxs)("span", { children: [
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, import_jsx_runtime21.jsx)(
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 import_react31 = require("react");
3278
- var import_jsx_runtime22 = require("react/jsx-runtime");
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, import_react31.useMemo)(() => normalizeComponentId(props.checkId, "checkId"), [props.checkId]);
3282
- const [selected, setSelected] = (0, import_react31.useState)(null);
3283
- const [checked, setChecked] = (0, import_react31.useState)(false);
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, import_react31.useMemo)(
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") setSelected(nextSelected);
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, import_jsx_runtime22.jsxs)("section", { "aria-label": "Find the hotspot", "data-lk-check-id": checkId, "data-testid": "find-hotspot", children: [
3335
- /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { style: { position: "relative", display: "inline-block" }, children: [
3336
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("img", { src: props.src, alt: props.alt, style: { maxWidth: "100%" } }),
3337
- props.targets.map((t) => /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
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: () => setSelected(t.id),
3981
+ onClick: () => selectTarget(t.id),
3351
3982
  children: t.label
3352
3983
  },
3353
3984
  t.id
3354
3985
  ))
3355
3986
  ] }),
3356
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("button", { type: "button", "data-testid": "check-hotspot", disabled: !selected, onClick: submit, children: "Check" }),
3357
- checked ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("p", { role: "status", children: correct ? "Correct" : "Try again" }) : null
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, import_react31.forwardRef)(FindHotspotInner);
3361
- var FindHotspot = (0, import_react31.forwardRef)(function FindHotspot2(props, ref) {
3362
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(AssessmentLessonGuard, { blockLabel: "FindHotspot", checkId: props.checkId, children: (enclosingLessonId) => /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(FindHotspotInnerForwarded, { ...props, enclosingLessonId, ref }) });
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 import_react32 = require("react");
3368
- var import_jsx_runtime23 = require("react/jsx-runtime");
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, import_react32.useMemo)(() => normalizeComponentId(props.checkId, "checkId"), [props.checkId]);
3372
- const [selected, setSelected] = (0, import_react32.useState)(/* @__PURE__ */ new Set());
3373
- const [checked, setChecked] = (0, import_react32.useState)(false);
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, import_react32.useMemo)(
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, import_jsx_runtime23.jsxs)("section", { "aria-label": "Find multiple hotspots", "data-lk-check-id": checkId, "data-testid": "find-multiple-hotspots", children: [
3433
- /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { style: { position: "relative", display: "inline-block" }, children: [
3434
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("img", { src: props.src, alt: props.alt, style: { maxWidth: "100%" } }),
3435
- props.targets.map((t) => /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
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, import_jsx_runtime23.jsx)("button", { type: "button", "data-testid": "check-hotspots", disabled: selected.size === 0, onClick: submit, children: "Check" }),
3455
- checked ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("p", { role: "status", children: correct ? "Correct" : "Try again" }) : null
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, import_react32.forwardRef)(FindMultipleHotspotsInner);
3459
- var FindMultipleHotspots = (0, import_react32.forwardRef)(
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, import_jsx_runtime23.jsx)(AssessmentLessonGuard, { blockLabel: "FindMultipleHotspots", checkId: props.checkId, children: (enclosingLessonId) => /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(FindMultipleHotspotsInnerForwarded, { ...props, enclosingLessonId, ref }) });
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 import_core17 = require("@lessonkit/core");
4099
+ var import_core19 = require("@lessonkit/core");
3468
4100
 
3469
4101
  // src/theme/ThemeProvider.tsx
3470
- var import_react33 = __toESM(require("react"), 1);
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 import_jsx_runtime24 = require("react/jsx-runtime");
3490
- var ThemeContext = (0, import_react33.createContext)(null);
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" ? import_react33.useLayoutEffect : import_react33.default.useEffect
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, import_react33.useState)(
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, import_react33.useMemo)(() => {
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, import_react33.useRef)(null);
3534
- const appliedKeysRef = (0, import_react33.useRef)(/* @__PURE__ */ new Set());
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, import_react33.useCallback)(() => {
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, import_react33.useMemo)(
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, import_jsx_runtime24.jsx)(ThemeContext.Provider, { value, children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { "data-lk-theme": dataTheme, style: { display: "contents" }, children: props.children }) });
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, import_jsx_runtime24.jsx)(ThemeContext.Provider, { value, children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { ref: hostRef, "data-lk-theme": dataTheme, children: props.children }) });
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, import_react33.useContext)(ThemeContext);
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 import_core16 = require("@lessonkit/core");
3582
- var COMPOUND_PARENTS = ["Lesson", "Page", "InteractiveBook", "AssessmentSequence"];
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: [...import_core16.PAGE_ALLOWED_CHILD_TYPES],
3647
- maxNestingDepth: import_core16.COMPOUND_MAX_NESTING_DEPTH.Page,
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: [...import_core16.INTERACTIVE_BOOK_ALLOWED_CHILD_TYPES],
3668
- maxNestingDepth: import_core16.COMPOUND_MAX_NESTING_DEPTH.InteractiveBook,
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: [...import_core16.ASSESSMENT_SEQUENCE_ALLOWED_CHILD_TYPES],
3820
- maxNestingDepth: import_core16.COMPOUND_MAX_NESTING_DEPTH.AssessmentSequence
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,