@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.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/components.tsx
2
- import { useEffect as useEffect4, useId as useId2, useMemo as useMemo6, useRef as useRef4, useState as useState4 } from "react";
2
+ import { useEffect as useEffect4, useId as useId2, useMemo as useMemo6, useRef as useRef5, useState as useState4 } from "react";
3
3
  import { visuallyHiddenStyle as visuallyHiddenStyle2 } from "@lessonkit/accessibility";
4
4
 
5
5
  // src/context.tsx
@@ -152,6 +152,9 @@ import {
152
152
  resetStoragePortForTests
153
153
  } from "@lessonkit/core";
154
154
 
155
+ // src/provider/useLessonkitProviderRuntime.ts
156
+ import { resetSharedVolatileSessionIdForTests } from "@lessonkit/core";
157
+
155
158
  // src/runtime/progress.ts
156
159
  import { createProgressController } from "@lessonkit/core";
157
160
 
@@ -295,9 +298,10 @@ async function emitCourseStartedToTracking(tracking, storage, sessionId, courseI
295
298
  try {
296
299
  if (shouldCommit && !shouldCommit()) return false;
297
300
  tracking.track(event);
298
- await tracking.flush?.();
299
- if (shouldCommit && !shouldCommit()) return false;
300
301
  markCourseStartedEmittedToTracking(storage, sessionId, courseId);
302
+ const delivered = await tracking.flush?.();
303
+ if (delivered === false) return false;
304
+ if (shouldCommit && !shouldCommit()) return false;
301
305
  return true;
302
306
  } catch {
303
307
  return false;
@@ -424,13 +428,14 @@ function assertTrackingSinkConfig(tracking) {
424
428
 
425
429
  // src/runtime/telemetry.ts
426
430
  import { createTrackingClient } from "@lessonkit/core";
427
- function createTrackingClientFromConfig(config) {
431
+ function createTrackingClientFromConfig(config, observability) {
428
432
  if (config.tracking?.enabled === false) return createTrackingClient();
429
433
  if (config.tracking?.createClient) return config.tracking.createClient();
430
434
  return createTrackingClient({
431
435
  sink: config.tracking?.sink,
432
436
  batchSink: config.tracking?.batchSink,
433
- batch: config.tracking?.batch
437
+ batch: config.tracking?.batch,
438
+ onBufferDrop: observability?.onTelemetryBufferDrop
434
439
  });
435
440
  }
436
441
  async function disposeTrackingClient(client) {
@@ -512,7 +517,8 @@ function useLessonkitProviderRuntime(config) {
512
517
  courseId: normalizedCourseId,
513
518
  runtimeVersion: "v2",
514
519
  session: normalizedConfig.session,
515
- plugins: pluginHostRef.current ?? normalizedConfig.plugins
520
+ plugins: pluginHostRef.current ?? normalizedConfig.plugins,
521
+ deferPluginSetup: true
516
522
  });
517
523
  progressRef.current = headlessRef.current.progress;
518
524
  } else {
@@ -527,7 +533,8 @@ function useLessonkitProviderRuntime(config) {
527
533
  courseId: normalizedCourseId,
528
534
  runtimeVersion: "v2",
529
535
  session: normalizedConfig.session,
530
- plugins: pluginHostRef.current ?? normalizedConfig.plugins
536
+ plugins: pluginHostRef.current ?? normalizedConfig.plugins,
537
+ deferPluginSetup: true
531
538
  });
532
539
  }
533
540
  if (prevCourseIdForProgressRef.current !== normalizedCourseId) {
@@ -672,9 +679,12 @@ function useLessonkitProviderRuntime(config) {
672
679
  }
673
680
  return userBatchSink(perEventForBatch);
674
681
  } : userBatchSink;
675
- const next = createTrackingClientFromConfig({
676
- tracking: { ...normalizedConfig.tracking, sink, batchSink }
677
- });
682
+ const next = createTrackingClientFromConfig(
683
+ {
684
+ tracking: { ...normalizedConfig.tracking, sink, batchSink }
685
+ },
686
+ observabilityRef.current
687
+ );
678
688
  trackingRef.current = next;
679
689
  trackingClientForUnmountRef.current = next;
680
690
  setTracking(next);
@@ -992,6 +1002,7 @@ function useLessonkitProviderRuntime(config) {
992
1002
  config: normalizedConfig,
993
1003
  tracking,
994
1004
  xapi,
1005
+ storage: defaultStorage,
995
1006
  session: { sessionId: sessionIdRef.current, attemptId: attemptIdRef.current, user: userRef.current },
996
1007
  progress,
997
1008
  setActiveLesson,
@@ -1127,7 +1138,7 @@ function getLessonMountCount(lessonId) {
1127
1138
  }
1128
1139
 
1129
1140
  // src/components/Quiz.tsx
1130
- import { forwardRef, useEffect as useEffect3, useId, useMemo as useMemo5, useRef as useRef3, useState as useState3 } from "react";
1141
+ import { forwardRef, useEffect as useEffect3, useId, useMemo as useMemo5, useRef as useRef4, useState as useState3 } from "react";
1131
1142
  import { visuallyHiddenStyle } from "@lessonkit/accessibility";
1132
1143
 
1133
1144
  // src/assessment/AssessmentLessonGuard.tsx
@@ -1186,6 +1197,12 @@ function readStringField(state, key) {
1186
1197
  if (typeof value === "string" || value === null) return value;
1187
1198
  return void 0;
1188
1199
  }
1200
+ function readNumberField(state, key) {
1201
+ const value = state[key];
1202
+ if (typeof value === "number" && Number.isFinite(value)) return value;
1203
+ if (value === null) return null;
1204
+ return void 0;
1205
+ }
1189
1206
  function readBooleanStateField(state, key, apply) {
1190
1207
  const value = state[key];
1191
1208
  if (typeof value === "boolean") apply(value);
@@ -1195,53 +1212,81 @@ function readBooleanStateField(state, key, apply) {
1195
1212
  import { useImperativeHandle as useImperativeHandle2 } from "react";
1196
1213
 
1197
1214
  // src/compound/CompoundProvider.tsx
1198
- import React3, { createContext as createContext3, useCallback as useCallback2, useContext as useContext3, useImperativeHandle, useMemo as useMemo4, useRef as useRef2, useState as useState2 } from "react";
1215
+ import React5, { createContext as createContext5, useCallback as useCallback2, useContext as useContext5, useImperativeHandle, useMemo as useMemo4, useRef as useRef3, useState as useState2 } from "react";
1199
1216
  import { clampCompoundPageIndex, createCompoundResumeState } from "@lessonkit/core";
1200
1217
 
1201
1218
  // src/compound/aggregateScores.ts
1202
- function aggregateAssessmentScores(handles) {
1219
+ function aggregateAssessmentScores(handles, opts) {
1203
1220
  let score = 0;
1204
1221
  let maxScore = 0;
1205
1222
  let allAnswered = true;
1206
- for (const handle of handles) {
1223
+ for (const entry of handles) {
1224
+ const handle = "handle" in entry ? entry.handle : entry;
1225
+ const pageIndex = "handle" in entry ? entry.pageIndex : void 0;
1207
1226
  score += handle.getScore();
1208
1227
  maxScore += handle.getMaxScore();
1209
- if (!handle.getAnswerGiven()) allAnswered = false;
1228
+ const countsForAnswerGiven = opts?.answerPageIndex === void 0 || pageIndex === void 0 || pageIndex === opts.answerPageIndex;
1229
+ if (countsForAnswerGiven && !handle.getAnswerGiven()) allAnswered = false;
1210
1230
  }
1211
1231
  return { score, maxScore, allAnswered };
1212
1232
  }
1213
1233
 
1214
- // src/compound/resumeChildHandles.ts
1215
- function resumeChildHandles(handles, childStates, opts) {
1216
- if (opts?.waitForHandles && handles.size === 0 && Object.keys(childStates).length > 0) {
1217
- return false;
1218
- }
1219
- for (const [checkId, handle] of handles) {
1220
- const child = childStates[checkId];
1221
- if (child && handle.resume) handle.resume(child);
1222
- }
1223
- return true;
1234
+ // src/compound/CompoundHydrationBridge.tsx
1235
+ import { createContext as createContext3, useContext as useContext3, useRef as useRef2 } from "react";
1236
+ import { jsx as jsx3 } from "react/jsx-runtime";
1237
+ var CompoundHydrationBridgeContext = createContext3(
1238
+ null
1239
+ );
1240
+ function CompoundHydrationBridgeProvider({ children }) {
1241
+ const bridgeRef = useRef2(null);
1242
+ return /* @__PURE__ */ jsx3(CompoundHydrationBridgeContext.Provider, { value: bridgeRef, children });
1243
+ }
1244
+ function useCompoundHydrationBridgeRef() {
1245
+ return useContext3(CompoundHydrationBridgeContext);
1246
+ }
1247
+
1248
+ // src/compound/CompoundPageIndexContext.tsx
1249
+ import { createContext as createContext4, useContext as useContext4 } from "react";
1250
+ import { jsx as jsx4 } from "react/jsx-runtime";
1251
+ var CompoundPageIndexContext = createContext4(void 0);
1252
+ function CompoundPageIndexProvider({
1253
+ pageIndex,
1254
+ children
1255
+ }) {
1256
+ return /* @__PURE__ */ jsx4(CompoundPageIndexContext.Provider, { value: pageIndex, children });
1257
+ }
1258
+ function useCompoundPageIndex() {
1259
+ return useContext4(CompoundPageIndexContext);
1224
1260
  }
1225
1261
 
1226
1262
  // src/compound/CompoundProvider.tsx
1227
- import { jsx as jsx3 } from "react/jsx-runtime";
1228
- var CompoundRegistryContext = createContext3(null);
1229
- var CompoundHandlesVersionContext = createContext3(0);
1263
+ import { jsx as jsx5 } from "react/jsx-runtime";
1264
+ var CompoundRegistryContext = createContext5(null);
1265
+ var CompoundHandlesVersionContext = createContext5(0);
1230
1266
  function CompoundProvider({
1231
1267
  children,
1232
1268
  activePageIndex: _activePageIndex,
1233
1269
  onActivePageIndexChange: _onActivePageIndexChange
1234
1270
  }) {
1235
- const registryRef = useRef2(/* @__PURE__ */ new Map());
1271
+ const registryRef = useRef3(/* @__PURE__ */ new Map());
1236
1272
  const [handlesVersion, setHandlesVersion] = useState2(0);
1237
- const register = useCallback2((checkId, handle) => {
1273
+ const register = useCallback2((checkId, handle, pageIndex) => {
1238
1274
  const prev = registryRef.current.get(checkId);
1239
- registryRef.current.set(checkId, handle);
1240
- if (prev !== handle) {
1275
+ if (prev && prev.handle !== handle) {
1276
+ const message = `[lessonkit] duplicate checkId "${checkId}" registered in the same compound container; the previous handle was replaced.`;
1277
+ if (isDevEnvironment4()) {
1278
+ console.error(message);
1279
+ } else {
1280
+ console.warn(message);
1281
+ }
1282
+ }
1283
+ registryRef.current.set(checkId, { handle, pageIndex });
1284
+ if (prev?.handle !== handle || prev?.pageIndex !== pageIndex) {
1241
1285
  setHandlesVersion((v) => v + 1);
1242
1286
  }
1243
1287
  return () => {
1244
- if (registryRef.current.get(checkId) === handle) {
1288
+ const current = registryRef.current.get(checkId);
1289
+ if (current?.handle === handle) {
1245
1290
  registryRef.current.delete(checkId);
1246
1291
  setHandlesVersion((v) => v + 1);
1247
1292
  }
@@ -1250,30 +1295,39 @@ function CompoundProvider({
1250
1295
  const registryValue = useMemo4(
1251
1296
  () => ({
1252
1297
  register,
1253
- getHandles: () => registryRef.current
1298
+ getHandles: () => {
1299
+ const handles = /* @__PURE__ */ new Map();
1300
+ for (const [checkId, entry] of registryRef.current) {
1301
+ handles.set(checkId, entry.handle);
1302
+ }
1303
+ return handles;
1304
+ },
1305
+ getRegisteredHandles: () => registryRef.current
1254
1306
  }),
1255
1307
  [register]
1256
1308
  );
1257
- return /* @__PURE__ */ jsx3(CompoundRegistryContext.Provider, { value: registryValue, children: /* @__PURE__ */ jsx3(CompoundHandlesVersionContext.Provider, { value: handlesVersion, children }) });
1309
+ return /* @__PURE__ */ jsx5(CompoundHydrationBridgeProvider, { children: /* @__PURE__ */ jsx5(CompoundRegistryContext.Provider, { value: registryValue, children: /* @__PURE__ */ jsx5(CompoundHandlesVersionContext.Provider, { value: handlesVersion, children }) }) });
1258
1310
  }
1259
1311
  function useCompoundRegistry() {
1260
- const registry = useContext3(CompoundRegistryContext);
1261
- const handlesVersion = useContext3(CompoundHandlesVersionContext);
1312
+ const registry = useContext5(CompoundRegistryContext);
1313
+ const handlesVersion = useContext5(CompoundHandlesVersionContext);
1262
1314
  if (!registry) return null;
1263
1315
  return { ...registry, handlesVersion };
1264
1316
  }
1265
1317
  function useCompoundHandlesVersion() {
1266
- return useContext3(CompoundHandlesVersionContext);
1318
+ return useContext5(CompoundHandlesVersionContext);
1267
1319
  }
1268
1320
  function useRegisterAssessmentHandle(checkId, handle) {
1269
- const registry = useContext3(CompoundRegistryContext);
1270
- React3.useEffect(() => {
1321
+ const registry = useContext5(CompoundRegistryContext);
1322
+ const pageIndex = useCompoundPageIndex();
1323
+ React5.useLayoutEffect(() => {
1271
1324
  if (!registry || !handle) return;
1272
- return registry.register(checkId, handle);
1273
- }, [registry, checkId, handle]);
1325
+ return registry.register(checkId, handle, pageIndex);
1326
+ }, [registry, checkId, handle, pageIndex]);
1274
1327
  }
1275
1328
  function useCompoundHandleRef(ref, opts) {
1276
- const { activePageIndex, setActivePageIndex, getHandles, pageCount } = opts;
1329
+ const { activePageIndex, setActivePageIndex, getHandles, getRegisteredHandles, pageCount } = opts;
1330
+ const bridgeRef = useCompoundHydrationBridgeRef();
1277
1331
  const setIndexClamped = useCallback2(
1278
1332
  (index) => {
1279
1333
  const next = pageCount !== void 0 ? clampCompoundPageIndex(index, pageCount) : Math.max(0, Math.floor(index));
@@ -1284,31 +1338,32 @@ function useCompoundHandleRef(ref, opts) {
1284
1338
  useImperativeHandle(
1285
1339
  ref,
1286
1340
  () => ({
1287
- getScore: () => aggregateAssessmentScores(getHandles().values()).score,
1288
- getMaxScore: () => aggregateAssessmentScores(getHandles().values()).maxScore,
1289
- getAnswerGiven: () => aggregateAssessmentScores(getHandles().values()).allAnswered,
1341
+ getScore: () => aggregateAssessmentScores(getRegisteredHandles().values()).score,
1342
+ getMaxScore: () => aggregateAssessmentScores(getRegisteredHandles().values()).maxScore,
1343
+ getAnswerGiven: () => aggregateAssessmentScores(getRegisteredHandles().values(), {
1344
+ answerPageIndex: activePageIndex
1345
+ }).allAnswered,
1290
1346
  resetTask: () => {
1291
- for (const handle of getHandles().values()) handle.resetTask();
1347
+ for (const entry of getRegisteredHandles().values()) entry.handle.resetTask();
1292
1348
  },
1293
1349
  showSolutions: () => {
1294
1350
  if (!opts.enableSolutionsButton) return;
1295
- for (const handle of getHandles().values()) handle.showSolutions();
1351
+ for (const entry of getRegisteredHandles().values()) entry.handle.showSolutions();
1296
1352
  },
1297
1353
  getCurrentState: () => {
1298
1354
  const childStates = {};
1299
- for (const [checkId, handle] of getHandles()) {
1300
- if (handle.getCurrentState) {
1301
- childStates[checkId] = handle.getCurrentState();
1355
+ for (const [checkId, entry] of getRegisteredHandles()) {
1356
+ if (entry.handle.getCurrentState) {
1357
+ childStates[checkId] = entry.handle.getCurrentState();
1302
1358
  }
1303
1359
  }
1304
1360
  return createCompoundResumeState({ activePageIndex, childStates });
1305
1361
  },
1306
1362
  resume: (state) => {
1307
- setIndexClamped(state.activePageIndex);
1308
- resumeChildHandles(getHandles(), state.childStates);
1363
+ bridgeRef?.current?.notifyImperativeResume(state);
1309
1364
  }
1310
1365
  }),
1311
- [activePageIndex, setIndexClamped, getHandles, opts.enableSolutionsButton]
1366
+ [activePageIndex, setIndexClamped, getHandles, getRegisteredHandles, opts.enableSolutionsButton, bridgeRef]
1312
1367
  );
1313
1368
  }
1314
1369
 
@@ -1378,7 +1433,7 @@ function usePluginScoring(checkId, lessonId) {
1378
1433
  }
1379
1434
 
1380
1435
  // src/components/Quiz.tsx
1381
- import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
1436
+ import { jsx as jsx6, jsxs as jsxs2 } from "react/jsx-runtime";
1382
1437
  function QuizInner(props, ref) {
1383
1438
  const { enclosingLessonId } = props;
1384
1439
  const checkId = useMemo5(() => normalizeComponentId(props.checkId, "checkId"), [props.checkId]);
@@ -1387,44 +1442,85 @@ function QuizInner(props, ref) {
1387
1442
  const [selected, setSelected] = useState3(null);
1388
1443
  const [selectionCorrect, setSelectionCorrect] = useState3(null);
1389
1444
  const [quizPassed, setQuizPassed] = useState3(false);
1390
- const completedRef = useRef3(false);
1445
+ const [completedScore, setCompletedScore] = useState3(null);
1446
+ const [completedMaxScore, setCompletedMaxScore] = useState3(null);
1447
+ const completedRef = useRef4(false);
1448
+ const telemetryReplayedRef = useRef4(false);
1391
1449
  const questionId = useId();
1392
1450
  const choicesKey = props.choices.join("\0");
1393
1451
  useEffect3(() => {
1394
1452
  completedRef.current = false;
1453
+ telemetryReplayedRef.current = false;
1395
1454
  setQuizPassed(false);
1396
1455
  setSelected(null);
1397
1456
  setSelectionCorrect(null);
1457
+ setCompletedScore(null);
1458
+ setCompletedMaxScore(null);
1398
1459
  }, [checkId, props.answer, props.question, choicesKey]);
1399
1460
  const passed = quizPassed;
1461
+ const resolveScores = () => {
1462
+ const maxScore = completedMaxScore ?? 1;
1463
+ if (quizPassed) {
1464
+ return { score: completedScore ?? maxScore, maxScore };
1465
+ }
1466
+ if (selected !== null && selectionCorrect) {
1467
+ return { score: completedMaxScore ?? maxScore, maxScore };
1468
+ }
1469
+ return { score: 0, maxScore };
1470
+ };
1471
+ const replayTelemetry = (nextSelected, nextCorrect, nextPassed, nextScore, nextMaxScore) => {
1472
+ if (!nextPassed || telemetryReplayedRef.current) return;
1473
+ telemetryReplayedRef.current = true;
1474
+ if (nextSelected !== null) {
1475
+ quiz.answer({
1476
+ checkId,
1477
+ question: props.question,
1478
+ choice: nextSelected,
1479
+ correct: nextCorrect ?? false
1480
+ });
1481
+ }
1482
+ quiz.complete({
1483
+ checkId,
1484
+ score: nextScore,
1485
+ maxScore: nextMaxScore,
1486
+ passingScore: props.passingScore ?? nextMaxScore
1487
+ });
1488
+ };
1400
1489
  const handle = useMemo5(
1401
1490
  () => buildAssessmentHandle({
1402
1491
  checkId,
1403
- getScore: () => {
1404
- const maxScore = 1;
1405
- if (quizPassed && selected !== null) return maxScore;
1406
- if (selected === null) return 0;
1407
- return selectionCorrect ? maxScore : 0;
1408
- },
1409
- getMaxScore: () => 1,
1492
+ getScore: () => resolveScores().score,
1493
+ getMaxScore: () => resolveScores().maxScore,
1410
1494
  getAnswerGiven: () => selected !== null,
1411
1495
  resetTask: () => {
1412
1496
  completedRef.current = false;
1497
+ telemetryReplayedRef.current = false;
1413
1498
  setQuizPassed(false);
1414
1499
  setSelected(null);
1415
1500
  setSelectionCorrect(null);
1501
+ setCompletedScore(null);
1502
+ setCompletedMaxScore(null);
1416
1503
  },
1417
1504
  showSolutions: () => {
1418
1505
  },
1419
- getXAPIData: () => ({
1420
- checkId,
1421
- interactionType: "mcq",
1422
- response: selected ?? void 0,
1423
- correct: selectionCorrect ?? void 0,
1424
- score: quizPassed && selected !== null ? 1 : selected === null ? 0 : selectionCorrect ? 1 : 0,
1425
- maxScore: 1
1506
+ getXAPIData: () => {
1507
+ const { score, maxScore } = resolveScores();
1508
+ return {
1509
+ checkId,
1510
+ interactionType: "mcq",
1511
+ response: selected ?? void 0,
1512
+ correct: selectionCorrect ?? void 0,
1513
+ score,
1514
+ maxScore
1515
+ };
1516
+ },
1517
+ getCurrentState: () => ({
1518
+ selected,
1519
+ selectionCorrect,
1520
+ quizPassed,
1521
+ completedScore,
1522
+ completedMaxScore
1426
1523
  }),
1427
- getCurrentState: () => ({ selected, selectionCorrect, quizPassed }),
1428
1524
  resume: (state) => {
1429
1525
  const nextSelected = readStringField(state, "selected");
1430
1526
  if (typeof nextSelected === "string" || nextSelected === null) setSelected(nextSelected);
@@ -1432,21 +1528,47 @@ function QuizInner(props, ref) {
1432
1528
  if (nextCorrect === true || nextCorrect === false || nextCorrect === null) {
1433
1529
  setSelectionCorrect(nextCorrect);
1434
1530
  }
1435
- readBooleanStateField(state, "quizPassed", (value) => {
1436
- setQuizPassed(value);
1437
- completedRef.current = value;
1438
- });
1531
+ const nextCompletedScore = readNumberField(state, "completedScore");
1532
+ if (typeof nextCompletedScore === "number") setCompletedScore(nextCompletedScore);
1533
+ const nextCompletedMaxScore = readNumberField(state, "completedMaxScore");
1534
+ if (typeof nextCompletedMaxScore === "number") setCompletedMaxScore(nextCompletedMaxScore);
1535
+ const nextPassed = readBooleanField(state, "quizPassed");
1536
+ if (nextPassed === true || nextPassed === false) {
1537
+ setQuizPassed(nextPassed);
1538
+ completedRef.current = nextPassed;
1539
+ if (nextPassed) {
1540
+ const maxScore = nextCompletedMaxScore ?? completedMaxScore ?? 1;
1541
+ const score = nextCompletedScore ?? completedScore ?? maxScore;
1542
+ replayTelemetry(
1543
+ nextSelected ?? null,
1544
+ nextCorrect ?? null,
1545
+ nextPassed,
1546
+ score,
1547
+ maxScore
1548
+ );
1549
+ }
1550
+ }
1439
1551
  }
1440
1552
  }),
1441
- [checkId, quizPassed, selected, selectionCorrect]
1553
+ [
1554
+ checkId,
1555
+ completedMaxScore,
1556
+ completedScore,
1557
+ props.passingScore,
1558
+ props.question,
1559
+ quiz,
1560
+ quizPassed,
1561
+ selected,
1562
+ selectionCorrect
1563
+ ]
1442
1564
  );
1443
1565
  useAssessmentHandleRegistration(checkId, handle, ref);
1444
1566
  return /* @__PURE__ */ jsxs2("section", { "aria-label": "Quiz", "data-lk-check-id": checkId, children: [
1445
- /* @__PURE__ */ jsx4("p", { id: questionId, children: props.question }),
1567
+ /* @__PURE__ */ jsx6("p", { id: questionId, children: props.question }),
1446
1568
  /* @__PURE__ */ jsxs2("fieldset", { "aria-labelledby": questionId, children: [
1447
- /* @__PURE__ */ jsx4("legend", { style: visuallyHiddenStyle, children: "Quiz choices" }),
1569
+ /* @__PURE__ */ jsx6("legend", { style: visuallyHiddenStyle, children: "Quiz choices" }),
1448
1570
  props.choices.map((c, i) => /* @__PURE__ */ jsxs2("label", { style: { display: "block" }, children: [
1449
- /* @__PURE__ */ jsx4(
1571
+ /* @__PURE__ */ jsx6(
1450
1572
  "input",
1451
1573
  {
1452
1574
  type: "radio",
@@ -1471,9 +1593,12 @@ function QuizInner(props, ref) {
1471
1593
  completedRef.current = true;
1472
1594
  setQuizPassed(true);
1473
1595
  const maxScore = custom?.maxScore ?? 1;
1596
+ const score = custom?.score ?? maxScore;
1597
+ setCompletedScore(score);
1598
+ setCompletedMaxScore(maxScore);
1474
1599
  quiz.complete({
1475
1600
  checkId,
1476
- score: custom?.score ?? maxScore,
1601
+ score,
1477
1602
  maxScore,
1478
1603
  passingScore: props.passingScore ?? maxScore
1479
1604
  });
@@ -1484,15 +1609,15 @@ function QuizInner(props, ref) {
1484
1609
  c
1485
1610
  ] }, `${questionId}-${i}`))
1486
1611
  ] }),
1487
- selected && selectionCorrect !== null ? /* @__PURE__ */ jsx4("p", { role: "status", "aria-live": "polite", children: selectionCorrect ? "Correct" : "Try again" }) : null
1612
+ selected && selectionCorrect !== null ? /* @__PURE__ */ jsx6("p", { role: "status", "aria-live": "polite", children: selectionCorrect ? "Correct" : "Try again" }) : null
1488
1613
  ] });
1489
1614
  }
1490
1615
  var QuizInnerForwarded = forwardRef(QuizInner);
1491
1616
  var Quiz = forwardRef(function Quiz2(props, ref) {
1492
- return /* @__PURE__ */ jsx4(AssessmentLessonGuard, { blockLabel: "Quiz", checkId: props.checkId, children: (lessonId) => /* @__PURE__ */ jsx4(QuizInnerForwarded, { ...props, enclosingLessonId: lessonId, ref }) });
1617
+ return /* @__PURE__ */ jsx6(AssessmentLessonGuard, { blockLabel: "Quiz", checkId: props.checkId, children: (lessonId) => /* @__PURE__ */ jsx6(QuizInnerForwarded, { ...props, enclosingLessonId: lessonId, ref }) });
1493
1618
  });
1494
1619
  function KnowledgeCheck(props) {
1495
- return /* @__PURE__ */ jsx4(
1620
+ return /* @__PURE__ */ jsx6(
1496
1621
  Quiz,
1497
1622
  {
1498
1623
  checkId: props.checkId,
@@ -1508,16 +1633,16 @@ function resetQuizWarningsForTests() {
1508
1633
  }
1509
1634
 
1510
1635
  // src/components.tsx
1511
- import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
1636
+ import { jsx as jsx7, jsxs as jsxs3 } from "react/jsx-runtime";
1512
1637
  function Course(props) {
1513
1638
  const courseId = useMemo6(() => normalizeComponentId(props.courseId, "courseId"), [props.courseId]);
1514
1639
  const providerConfig = useMemo6(
1515
1640
  () => ({ ...props.config, courseId }),
1516
1641
  [props.config, courseId]
1517
1642
  );
1518
- return /* @__PURE__ */ jsx5(LessonkitProvider, { config: providerConfig, children: /* @__PURE__ */ jsxs3("section", { "aria-label": props.title, children: [
1519
- /* @__PURE__ */ jsx5("h1", { children: props.title }),
1520
- /* @__PURE__ */ jsx5("div", { children: props.children })
1643
+ return /* @__PURE__ */ jsx7(LessonkitProvider, { config: providerConfig, children: /* @__PURE__ */ jsxs3("section", { "aria-label": props.title, children: [
1644
+ /* @__PURE__ */ jsx7("h1", { children: props.title }),
1645
+ /* @__PURE__ */ jsx7("div", { children: props.children })
1521
1646
  ] }) });
1522
1647
  }
1523
1648
  function Lesson(props) {
@@ -1525,8 +1650,8 @@ function Lesson(props) {
1525
1650
  const autoComplete = props.autoCompleteOnUnmount !== false;
1526
1651
  const { setActiveLesson, config } = useLessonkit();
1527
1652
  const { completeLesson } = useCompletion();
1528
- const lessonMountGenerationRef = useRef4(0);
1529
- const liveCourseIdRef = useRef4(config.courseId);
1653
+ const lessonMountGenerationRef = useRef5(0);
1654
+ const liveCourseIdRef = useRef5(config.courseId);
1530
1655
  liveCourseIdRef.current = config.courseId;
1531
1656
  useEffect4(() => {
1532
1657
  const unregister = registerLessonMount(lessonId);
@@ -1553,9 +1678,9 @@ function Lesson(props) {
1553
1678
  });
1554
1679
  };
1555
1680
  }, [lessonId, config.courseId, setActiveLesson, completeLesson, autoComplete]);
1556
- return /* @__PURE__ */ jsx5(LessonContext.Provider, { value: lessonId, children: /* @__PURE__ */ jsxs3("article", { "aria-label": props.title, children: [
1557
- /* @__PURE__ */ jsx5("h2", { children: props.title }),
1558
- /* @__PURE__ */ jsx5("div", { children: props.children })
1681
+ return /* @__PURE__ */ jsx7(LessonContext.Provider, { value: lessonId, children: /* @__PURE__ */ jsxs3("article", { "aria-label": props.title, children: [
1682
+ /* @__PURE__ */ jsx7("h2", { children: props.title }),
1683
+ /* @__PURE__ */ jsx7("div", { children: props.children })
1559
1684
  ] }) });
1560
1685
  }
1561
1686
  function Scenario(props) {
@@ -1563,7 +1688,7 @@ function Scenario(props) {
1563
1688
  () => props.blockId !== void 0 ? normalizeComponentId(props.blockId, "blockId") : void 0,
1564
1689
  [props.blockId]
1565
1690
  );
1566
- return /* @__PURE__ */ jsx5("section", { "aria-label": "Scenario", "data-lk-block-id": blockId, children: props.children });
1691
+ return /* @__PURE__ */ jsx7("section", { "aria-label": "Scenario", "data-lk-block-id": blockId, children: props.children });
1567
1692
  }
1568
1693
  function Reflection(props) {
1569
1694
  const blockId = useMemo6(
@@ -1580,10 +1705,10 @@ function Reflection(props) {
1580
1705
  props.onChange?.(event.target.value);
1581
1706
  };
1582
1707
  return /* @__PURE__ */ jsxs3("section", { "aria-label": "Reflection", "data-lk-block-id": blockId, children: [
1583
- props.prompt ? /* @__PURE__ */ jsx5("p", { id: promptId, children: props.prompt }) : null,
1584
- props.hint ? /* @__PURE__ */ jsx5("p", { id: hintId, style: visuallyHiddenStyle2, children: props.hint }) : null,
1708
+ props.prompt ? /* @__PURE__ */ jsx7("p", { id: promptId, children: props.prompt }) : null,
1709
+ props.hint ? /* @__PURE__ */ jsx7("p", { id: hintId, style: visuallyHiddenStyle2, children: props.hint }) : null,
1585
1710
  props.children,
1586
- /* @__PURE__ */ jsx5(
1711
+ /* @__PURE__ */ jsx7(
1587
1712
  "textarea",
1588
1713
  {
1589
1714
  value,
@@ -1601,7 +1726,7 @@ function ProgressTracker(props) {
1601
1726
  if (props.totalLessons != null) {
1602
1727
  const total = props.totalLessons;
1603
1728
  const displayed = Math.min(completed, total);
1604
- return /* @__PURE__ */ jsx5("aside", { "aria-label": "Progress", children: /* @__PURE__ */ jsx5(
1729
+ return /* @__PURE__ */ jsx7("aside", { "aria-label": "Progress", children: /* @__PURE__ */ jsx7(
1605
1730
  "div",
1606
1731
  {
1607
1732
  role: "progressbar",
@@ -1618,15 +1743,15 @@ function ProgressTracker(props) {
1618
1743
  }
1619
1744
  ) });
1620
1745
  }
1621
- return /* @__PURE__ */ jsx5("aside", { "aria-label": "Progress", role: "status", children: /* @__PURE__ */ jsxs3("p", { children: [
1746
+ return /* @__PURE__ */ jsx7("aside", { "aria-label": "Progress", role: "status", children: /* @__PURE__ */ jsxs3("p", { children: [
1622
1747
  "Lessons completed: ",
1623
1748
  completed
1624
1749
  ] }) });
1625
1750
  }
1626
1751
 
1627
1752
  // src/blocks/TrueFalse.tsx
1628
- import React7, { forwardRef as forwardRef2, useEffect as useEffect5, useMemo as useMemo7, useRef as useRef5, useState as useState5 } from "react";
1629
- import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
1753
+ import React9, { forwardRef as forwardRef2, useEffect as useEffect5, useMemo as useMemo7, useRef as useRef6, useState as useState5 } from "react";
1754
+ import { jsx as jsx8, jsxs as jsxs4 } from "react/jsx-runtime";
1630
1755
  var INTERACTION = "trueFalse";
1631
1756
  function TrueFalseInner(props, ref) {
1632
1757
  const { enclosingLessonId } = props;
@@ -1638,38 +1763,81 @@ function TrueFalseInner(props, ref) {
1638
1763
  const [selectionCorrect, setSelectionCorrect] = useState5(null);
1639
1764
  const [showSolutions, setShowSolutions] = useState5(false);
1640
1765
  const [passed, setPassed] = useState5(false);
1641
- const completedRef = useRef5(false);
1642
- const questionId = React7.useId();
1766
+ const [completedScore, setCompletedScore] = useState5(null);
1767
+ const [completedMaxScore, setCompletedMaxScore] = useState5(null);
1768
+ const completedRef = useRef6(false);
1769
+ const telemetryReplayedRef = useRef6(false);
1770
+ const questionId = React9.useId();
1643
1771
  const reset = () => {
1644
1772
  completedRef.current = false;
1773
+ telemetryReplayedRef.current = false;
1645
1774
  setPassed(false);
1646
1775
  setSelected(null);
1647
1776
  setSelectionCorrect(null);
1648
1777
  setShowSolutions(false);
1778
+ setCompletedScore(null);
1779
+ setCompletedMaxScore(null);
1649
1780
  };
1650
1781
  useEffect5(() => {
1651
1782
  reset();
1652
1783
  }, [checkId, props.answer, props.question, config.courseId, enclosingLessonId]);
1784
+ const resolveScores = () => {
1785
+ const maxScore = completedMaxScore ?? 1;
1786
+ if (passed) {
1787
+ return { score: completedScore ?? maxScore, maxScore };
1788
+ }
1789
+ if (selectionCorrect) {
1790
+ return { score: completedMaxScore ?? maxScore, maxScore };
1791
+ }
1792
+ return { score: 0, maxScore };
1793
+ };
1794
+ const replayTelemetry = (nextSelected, nextCorrect, nextPassed, nextScore, nextMaxScore) => {
1795
+ if (!nextPassed || telemetryReplayedRef.current) return;
1796
+ telemetryReplayedRef.current = true;
1797
+ if (nextSelected !== null) {
1798
+ assessment.answer({
1799
+ checkId,
1800
+ interactionType: INTERACTION,
1801
+ question: props.question,
1802
+ response: nextSelected,
1803
+ correct: nextCorrect ?? false
1804
+ });
1805
+ }
1806
+ assessment.complete({
1807
+ checkId,
1808
+ interactionType: INTERACTION,
1809
+ score: nextScore,
1810
+ maxScore: nextMaxScore,
1811
+ passingScore: props.passingScore ?? nextMaxScore
1812
+ });
1813
+ };
1653
1814
  const handle = useMemo7(
1654
1815
  () => buildAssessmentHandle({
1655
1816
  checkId,
1656
- getScore: () => {
1657
- const maxScore = 1;
1658
- return passed ? maxScore : selected === null ? 0 : selected === props.answer ? maxScore : 0;
1659
- },
1660
- getMaxScore: () => 1,
1817
+ getScore: () => resolveScores().score,
1818
+ getMaxScore: () => resolveScores().maxScore,
1661
1819
  getAnswerGiven: () => selected !== null,
1662
1820
  resetTask: reset,
1663
1821
  showSolutions: () => setShowSolutions(true),
1664
- getXAPIData: () => ({
1665
- checkId,
1666
- interactionType: INTERACTION,
1667
- response: selected ?? void 0,
1668
- correct: selected === props.answer,
1669
- score: passed ? 1 : selected === null ? 0 : selected === props.answer ? 1 : 0,
1670
- maxScore: 1
1822
+ getXAPIData: () => {
1823
+ const { score, maxScore } = resolveScores();
1824
+ return {
1825
+ checkId,
1826
+ interactionType: INTERACTION,
1827
+ response: selected ?? void 0,
1828
+ correct: selectionCorrect ?? void 0,
1829
+ score,
1830
+ maxScore
1831
+ };
1832
+ },
1833
+ getCurrentState: () => ({
1834
+ selected,
1835
+ selectionCorrect,
1836
+ passed,
1837
+ showSolutions,
1838
+ completedScore,
1839
+ completedMaxScore
1671
1840
  }),
1672
- getCurrentState: () => ({ selected, selectionCorrect, passed, showSolutions }),
1673
1841
  resume: (state) => {
1674
1842
  const nextSelected = readBooleanField(state, "selected");
1675
1843
  if (nextSelected === true || nextSelected === false || nextSelected === null) {
@@ -1679,14 +1847,35 @@ function TrueFalseInner(props, ref) {
1679
1847
  if (nextCorrect === true || nextCorrect === false || nextCorrect === null) {
1680
1848
  setSelectionCorrect(nextCorrect);
1681
1849
  }
1682
- readBooleanStateField(state, "passed", (value) => {
1683
- setPassed(value);
1684
- completedRef.current = value;
1685
- });
1850
+ const nextCompletedScore = readNumberField(state, "completedScore");
1851
+ if (typeof nextCompletedScore === "number") setCompletedScore(nextCompletedScore);
1852
+ const nextCompletedMaxScore = readNumberField(state, "completedMaxScore");
1853
+ if (typeof nextCompletedMaxScore === "number") setCompletedMaxScore(nextCompletedMaxScore);
1854
+ const nextPassed = readBooleanField(state, "passed");
1855
+ if (nextPassed === true || nextPassed === false) {
1856
+ setPassed(nextPassed);
1857
+ completedRef.current = nextPassed;
1858
+ if (nextPassed) {
1859
+ const maxScore = nextCompletedMaxScore ?? completedMaxScore ?? 1;
1860
+ const score = nextCompletedScore ?? completedScore ?? maxScore;
1861
+ replayTelemetry(nextSelected ?? null, nextCorrect ?? null, nextPassed, score, maxScore);
1862
+ }
1863
+ }
1686
1864
  readBooleanStateField(state, "showSolutions", setShowSolutions);
1687
1865
  }
1688
1866
  }),
1689
- [checkId, passed, props.answer, selected, selectionCorrect, showSolutions]
1867
+ [
1868
+ assessment,
1869
+ checkId,
1870
+ completedMaxScore,
1871
+ completedScore,
1872
+ passed,
1873
+ props.passingScore,
1874
+ props.question,
1875
+ selected,
1876
+ selectionCorrect,
1877
+ showSolutions
1878
+ ]
1690
1879
  );
1691
1880
  useAssessmentHandleRegistration(checkId, handle, ref);
1692
1881
  const submit = (value) => {
@@ -1705,6 +1894,8 @@ function TrueFalseInner(props, ref) {
1705
1894
  if (scored.passed && !completedRef.current) {
1706
1895
  completedRef.current = true;
1707
1896
  setPassed(true);
1897
+ setCompletedScore(scored.score);
1898
+ setCompletedMaxScore(scored.maxScore);
1708
1899
  assessment.complete({
1709
1900
  checkId,
1710
1901
  interactionType: INTERACTION,
@@ -1716,11 +1907,11 @@ function TrueFalseInner(props, ref) {
1716
1907
  };
1717
1908
  const reveal = showSolutions || passed && props.enableSolutionsButton;
1718
1909
  return /* @__PURE__ */ jsxs4("section", { "aria-label": "True or False", "data-lk-check-id": checkId, children: [
1719
- /* @__PURE__ */ jsx6("p", { id: questionId, children: props.question }),
1910
+ /* @__PURE__ */ jsx8("p", { id: questionId, children: props.question }),
1720
1911
  /* @__PURE__ */ jsxs4("fieldset", { "aria-labelledby": questionId, children: [
1721
- /* @__PURE__ */ jsx6("legend", { className: "lk-visually-hidden", children: "True or False" }),
1912
+ /* @__PURE__ */ jsx8("legend", { className: "lk-visually-hidden", children: "True or False" }),
1722
1913
  /* @__PURE__ */ jsxs4("label", { style: { display: "block", marginRight: "1rem" }, children: [
1723
- /* @__PURE__ */ jsx6(
1914
+ /* @__PURE__ */ jsx8(
1724
1915
  "input",
1725
1916
  {
1726
1917
  type: "radio",
@@ -1733,7 +1924,7 @@ function TrueFalseInner(props, ref) {
1733
1924
  "True"
1734
1925
  ] }),
1735
1926
  /* @__PURE__ */ jsxs4("label", { style: { display: "block" }, children: [
1736
- /* @__PURE__ */ jsx6(
1927
+ /* @__PURE__ */ jsx8(
1737
1928
  "input",
1738
1929
  {
1739
1930
  type: "radio",
@@ -1748,21 +1939,21 @@ function TrueFalseInner(props, ref) {
1748
1939
  ] }),
1749
1940
  reveal ? /* @__PURE__ */ jsxs4("p", { children: [
1750
1941
  "Correct answer: ",
1751
- /* @__PURE__ */ jsx6("strong", { children: props.answer ? "True" : "False" })
1942
+ /* @__PURE__ */ jsx8("strong", { children: props.answer ? "True" : "False" })
1752
1943
  ] }) : null,
1753
- selected !== null && selectionCorrect !== null ? /* @__PURE__ */ jsx6("p", { role: "status", "aria-live": "polite", children: selectionCorrect ? "Correct" : "Try again" }) : null,
1754
- props.enableRetry && passed ? /* @__PURE__ */ jsx6("button", { type: "button", onClick: reset, children: "Try again" }) : null,
1755
- props.enableSolutionsButton && !reveal ? /* @__PURE__ */ jsx6("button", { type: "button", onClick: () => setShowSolutions(true), children: "Show solution" }) : null
1944
+ selected !== null && selectionCorrect !== null ? /* @__PURE__ */ jsx8("p", { role: "status", "aria-live": "polite", children: selectionCorrect ? "Correct" : "Try again" }) : null,
1945
+ props.enableRetry && passed ? /* @__PURE__ */ jsx8("button", { type: "button", onClick: reset, children: "Try again" }) : null,
1946
+ props.enableSolutionsButton && !reveal ? /* @__PURE__ */ jsx8("button", { type: "button", onClick: () => setShowSolutions(true), children: "Show solution" }) : null
1756
1947
  ] });
1757
1948
  }
1758
1949
  var TrueFalseInnerForwarded = forwardRef2(TrueFalseInner);
1759
1950
  var TrueFalse = forwardRef2(function TrueFalse2(props, ref) {
1760
- return /* @__PURE__ */ jsx6(AssessmentLessonGuard, { blockLabel: "TrueFalse", checkId: props.checkId, children: (lessonId) => /* @__PURE__ */ jsx6(TrueFalseInnerForwarded, { ...props, enclosingLessonId: lessonId, ref }) });
1951
+ return /* @__PURE__ */ jsx8(AssessmentLessonGuard, { blockLabel: "TrueFalse", checkId: props.checkId, children: (lessonId) => /* @__PURE__ */ jsx8(TrueFalseInnerForwarded, { ...props, enclosingLessonId: lessonId, ref }) });
1761
1952
  });
1762
1953
 
1763
1954
  // src/blocks/MarkTheWords.tsx
1764
- import React8, { forwardRef as forwardRef3, useEffect as useEffect6, useMemo as useMemo8, useRef as useRef6, useState as useState6 } from "react";
1765
- import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
1955
+ import React10, { forwardRef as forwardRef3, useEffect as useEffect6, useMemo as useMemo8, useRef as useRef7, useState as useState6 } from "react";
1956
+ import { jsx as jsx9, jsxs as jsxs5 } from "react/jsx-runtime";
1766
1957
  var INTERACTION2 = "markTheWords";
1767
1958
  function tokenize(text) {
1768
1959
  return text.split(/(\s+)/).filter((t) => t.length > 0);
@@ -1778,7 +1969,7 @@ function MarkTheWordsInner(props, ref) {
1778
1969
  const [marked, setMarked] = useState6(() => /* @__PURE__ */ new Set());
1779
1970
  const [passed, setPassed] = useState6(false);
1780
1971
  const [showSolutions, setShowSolutions] = useState6(false);
1781
- const completedRef = useRef6(false);
1972
+ const completedRef = useRef7(false);
1782
1973
  const reset = () => {
1783
1974
  completedRef.current = false;
1784
1975
  setPassed(false);
@@ -1857,7 +2048,7 @@ function MarkTheWordsInner(props, ref) {
1857
2048
  interactionType: INTERACTION2,
1858
2049
  question: props.text,
1859
2050
  response: [...marked].map((i) => tokens[i]),
1860
- correct: true
2051
+ correct: passedThreshold
1861
2052
  });
1862
2053
  assessment.complete({
1863
2054
  checkId,
@@ -1882,17 +2073,17 @@ function MarkTheWordsInner(props, ref) {
1882
2073
  return /* @__PURE__ */ jsxs5("section", { "aria-label": "Mark the Words", "data-lk-check-id": checkId, children: [
1883
2074
  !hasTargets ? /* @__PURE__ */ jsxs5("p", { role: "alert", children: [
1884
2075
  "No words in this sentence match ",
1885
- /* @__PURE__ */ jsx7("code", { children: "correctWords" }),
2076
+ /* @__PURE__ */ jsx9("code", { children: "correctWords" }),
1886
2077
  ". Check spelling and capitalization in the source text."
1887
2078
  ] }) : null,
1888
- /* @__PURE__ */ jsx7("p", { id: `${checkId}-hint`, children: "Select the correct words in the sentence." }),
1889
- /* @__PURE__ */ jsx7("p", { "aria-describedby": `${checkId}-hint`, children: tokens.map((token, i) => {
2079
+ /* @__PURE__ */ jsx9("p", { id: `${checkId}-hint`, children: "Select the correct words in the sentence." }),
2080
+ /* @__PURE__ */ jsx9("p", { "aria-describedby": `${checkId}-hint`, children: tokens.map((token, i) => {
1890
2081
  const isWord = !/^\s+$/.test(token);
1891
2082
  const isTarget = isWord && correctSet.has(token.toLowerCase());
1892
- if (!isTarget) return /* @__PURE__ */ jsx7(React8.Fragment, { children: token }, i);
2083
+ if (!isTarget) return /* @__PURE__ */ jsx9(React10.Fragment, { children: token }, i);
1893
2084
  const selected = marked.has(i);
1894
2085
  const solution = showSolutions || passed && props.enableSolutionsButton;
1895
- return /* @__PURE__ */ jsx7(
2086
+ return /* @__PURE__ */ jsx9(
1896
2087
  "button",
1897
2088
  {
1898
2089
  type: "button",
@@ -1910,18 +2101,18 @@ function MarkTheWordsInner(props, ref) {
1910
2101
  i
1911
2102
  );
1912
2103
  }) }),
1913
- allMarked ? /* @__PURE__ */ jsx7("p", { role: "status", "aria-live": "polite", children: "Correct" }) : null,
1914
- props.enableRetry && passed ? /* @__PURE__ */ jsx7("button", { type: "button", onClick: reset, children: "Try again" }) : null,
1915
- props.enableSolutionsButton && !showSolutions ? /* @__PURE__ */ jsx7("button", { type: "button", onClick: () => setShowSolutions(true), children: "Show solution" }) : null
2104
+ allMarked ? /* @__PURE__ */ jsx9("p", { role: "status", "aria-live": "polite", children: "Correct" }) : null,
2105
+ props.enableRetry && passed ? /* @__PURE__ */ jsx9("button", { type: "button", onClick: reset, children: "Try again" }) : null,
2106
+ props.enableSolutionsButton && !showSolutions ? /* @__PURE__ */ jsx9("button", { type: "button", onClick: () => setShowSolutions(true), children: "Show solution" }) : null
1916
2107
  ] });
1917
2108
  }
1918
2109
  var MarkTheWordsInnerForwarded = forwardRef3(MarkTheWordsInner);
1919
2110
  var MarkTheWords = forwardRef3(function MarkTheWords2(props, ref) {
1920
- return /* @__PURE__ */ jsx7(AssessmentLessonGuard, { blockLabel: "MarkTheWords", checkId: props.checkId, children: (lessonId) => /* @__PURE__ */ jsx7(MarkTheWordsInnerForwarded, { ...props, enclosingLessonId: lessonId, ref }) });
2111
+ return /* @__PURE__ */ jsx9(AssessmentLessonGuard, { blockLabel: "MarkTheWords", checkId: props.checkId, children: (lessonId) => /* @__PURE__ */ jsx9(MarkTheWordsInnerForwarded, { ...props, enclosingLessonId: lessonId, ref }) });
1921
2112
  });
1922
2113
 
1923
2114
  // src/blocks/FillInTheBlanks.tsx
1924
- import React9, { forwardRef as forwardRef4, useEffect as useEffect7, useMemo as useMemo9, useRef as useRef7, useState as useState7 } from "react";
2115
+ import React11, { forwardRef as forwardRef4, useEffect as useEffect7, useMemo as useMemo9, useRef as useRef8, useState as useState7 } from "react";
1925
2116
 
1926
2117
  // src/assessment/internal/parseStarDelimitedTemplate.ts
1927
2118
  function parseStarDelimitedTemplate(template, idPrefix) {
@@ -1942,7 +2133,7 @@ function parseStarDelimitedTemplate(template, idPrefix) {
1942
2133
  }
1943
2134
 
1944
2135
  // src/blocks/FillInTheBlanks.tsx
1945
- import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
2136
+ import { jsx as jsx10, jsxs as jsxs6 } from "react/jsx-runtime";
1946
2137
  var INTERACTION3 = "fillInBlanks";
1947
2138
  function parseTemplate(template) {
1948
2139
  const { parts, values } = parseStarDelimitedTemplate(template, "blank");
@@ -1961,14 +2152,16 @@ function FillInTheBlanksInner(props, ref) {
1961
2152
  );
1962
2153
  const [passed, setPassed] = useState7(false);
1963
2154
  const [showSolutions, setShowSolutions] = useState7(false);
1964
- const completedRef = useRef7(false);
1965
- const answeredRef = useRef7(false);
2155
+ const [submitted, setSubmitted] = useState7(false);
2156
+ const completedRef = useRef8(false);
2157
+ const answeredRef = useRef8(false);
1966
2158
  const reset = () => {
1967
2159
  completedRef.current = false;
1968
2160
  answeredRef.current = false;
1969
2161
  setPassed(false);
1970
2162
  setValues(Object.fromEntries(blanks.map((b) => [b.id, ""])));
1971
2163
  setShowSolutions(false);
2164
+ setSubmitted(false);
1972
2165
  };
1973
2166
  useEffect7(() => {
1974
2167
  reset();
@@ -1997,7 +2190,7 @@ function FillInTheBlanksInner(props, ref) {
1997
2190
  score,
1998
2191
  maxScore: maxScore || 1
1999
2192
  }),
2000
- getCurrentState: () => ({ values, passed, showSolutions }),
2193
+ getCurrentState: () => ({ values, passed, showSolutions, submitted }),
2001
2194
  resume: (state) => {
2002
2195
  const raw = state.values;
2003
2196
  if (raw && typeof raw === "object") setValues({ ...raw });
@@ -2007,9 +2200,13 @@ function FillInTheBlanksInner(props, ref) {
2007
2200
  answeredRef.current = value;
2008
2201
  });
2009
2202
  readBooleanStateField(state, "showSolutions", setShowSolutions);
2203
+ readBooleanStateField(state, "submitted", (value) => {
2204
+ setSubmitted(value);
2205
+ if (value) answeredRef.current = true;
2206
+ });
2010
2207
  }
2011
2208
  }),
2012
- [allFilled, checkId, maxScore, passed, passedThreshold, score, showSolutions, values]
2209
+ [allFilled, checkId, maxScore, passed, passedThreshold, score, showSolutions, submitted, values]
2013
2210
  );
2014
2211
  useAssessmentHandleRegistration(checkId, handle, ref);
2015
2212
  const check = () => {
@@ -2020,16 +2217,16 @@ function FillInTheBlanksInner(props, ref) {
2020
2217
  return;
2021
2218
  }
2022
2219
  if (!allFilled) return;
2023
- if (!answeredRef.current) {
2024
- answeredRef.current = true;
2025
- assessment.answer({
2026
- checkId,
2027
- interactionType: INTERACTION3,
2028
- question: props.template,
2029
- response: values,
2030
- correct: passedThreshold
2031
- });
2032
- }
2220
+ if (answeredRef.current || submitted) return;
2221
+ answeredRef.current = true;
2222
+ setSubmitted(true);
2223
+ assessment.answer({
2224
+ checkId,
2225
+ interactionType: INTERACTION3,
2226
+ question: props.template,
2227
+ response: values,
2228
+ correct: passedThreshold
2229
+ });
2033
2230
  if (passedThreshold && !completedRef.current) {
2034
2231
  completedRef.current = true;
2035
2232
  setPassed(true);
@@ -2043,19 +2240,22 @@ function FillInTheBlanksInner(props, ref) {
2043
2240
  }
2044
2241
  };
2045
2242
  useEffect7(() => {
2046
- if (!allFilled) answeredRef.current = false;
2243
+ if (!allFilled) {
2244
+ answeredRef.current = false;
2245
+ setSubmitted(false);
2246
+ }
2047
2247
  }, [allFilled]);
2048
2248
  useEffect7(() => {
2049
2249
  if (props.autoCheck && allFilled) check();
2050
2250
  }, [allFilled, props.autoCheck, values, passedThreshold]);
2051
2251
  const reveal = showSolutions || passed && props.enableSolutionsButton;
2052
2252
  return /* @__PURE__ */ jsxs6("section", { "aria-label": "Fill in the Blanks", "data-lk-check-id": checkId, children: [
2053
- /* @__PURE__ */ jsx8("p", { children: parsed.parts.map((part, i) => {
2253
+ /* @__PURE__ */ jsx10("p", { children: parsed.parts.map((part, i) => {
2054
2254
  const blank = blanks.find((b) => b.id === part);
2055
- if (!blank) return /* @__PURE__ */ jsx8(React9.Fragment, { children: part }, i);
2255
+ if (!blank) return /* @__PURE__ */ jsx10(React11.Fragment, { children: part }, i);
2056
2256
  return /* @__PURE__ */ jsxs6("label", { style: { margin: "0 0.25em" }, children: [
2057
- /* @__PURE__ */ jsx8("span", { className: "lk-visually-hidden", children: blank.answer }),
2058
- /* @__PURE__ */ jsx8(
2257
+ /* @__PURE__ */ jsx10("span", { className: "lk-visually-hidden", children: blank.answer }),
2258
+ /* @__PURE__ */ jsx10(
2059
2259
  "input",
2060
2260
  {
2061
2261
  type: "text",
@@ -2071,23 +2271,23 @@ function FillInTheBlanksInner(props, ref) {
2071
2271
  )
2072
2272
  ] }, blank.id);
2073
2273
  }) }),
2074
- !props.autoCheck ? /* @__PURE__ */ jsx8("button", { type: "button", "data-testid": "check-blanks", disabled: !allFilled || passed, onClick: check, children: "Check" }) : null,
2075
- !hasBlanks ? /* @__PURE__ */ jsx8("p", { role: "alert", children: "This activity has no blanks. Add text wrapped in asterisks, e.g. The *answer* here." }) : null,
2076
- allFilled ? /* @__PURE__ */ jsx8("p", { role: "status", "aria-live": "polite", children: passed || passedThreshold ? "Correct" : "Try again" }) : null,
2077
- props.enableRetry && passed ? /* @__PURE__ */ jsx8("button", { type: "button", onClick: reset, children: "Try again" }) : null,
2078
- props.enableSolutionsButton && !reveal ? /* @__PURE__ */ jsx8("button", { type: "button", onClick: () => setShowSolutions(true), children: "Show solution" }) : null
2274
+ !props.autoCheck ? /* @__PURE__ */ jsx10("button", { type: "button", "data-testid": "check-blanks", disabled: !allFilled || passed, onClick: check, children: "Check" }) : null,
2275
+ !hasBlanks ? /* @__PURE__ */ jsx10("p", { role: "alert", children: "This activity has no blanks. Add text wrapped in asterisks, e.g. The *answer* here." }) : null,
2276
+ submitted ? /* @__PURE__ */ jsx10("p", { role: "status", "aria-live": "polite", children: passed || passedThreshold ? "Correct" : "Try again" }) : null,
2277
+ props.enableRetry && passed ? /* @__PURE__ */ jsx10("button", { type: "button", onClick: reset, children: "Try again" }) : null,
2278
+ props.enableSolutionsButton && !reveal ? /* @__PURE__ */ jsx10("button", { type: "button", onClick: () => setShowSolutions(true), children: "Show solution" }) : null
2079
2279
  ] });
2080
2280
  }
2081
2281
  var FillInTheBlanksInnerForwarded = forwardRef4(FillInTheBlanksInner);
2082
2282
  var FillInTheBlanks = forwardRef4(
2083
2283
  function FillInTheBlanks2(props, ref) {
2084
- return /* @__PURE__ */ jsx8(AssessmentLessonGuard, { blockLabel: "FillInTheBlanks", checkId: props.checkId, children: (lessonId) => /* @__PURE__ */ jsx8(FillInTheBlanksInnerForwarded, { ...props, enclosingLessonId: lessonId, ref }) });
2284
+ return /* @__PURE__ */ jsx10(AssessmentLessonGuard, { blockLabel: "FillInTheBlanks", checkId: props.checkId, children: (lessonId) => /* @__PURE__ */ jsx10(FillInTheBlanksInnerForwarded, { ...props, enclosingLessonId: lessonId, ref }) });
2085
2285
  }
2086
2286
  );
2087
2287
 
2088
2288
  // src/blocks/DragTheWords.tsx
2089
- import React10, { forwardRef as forwardRef5, useEffect as useEffect8, useMemo as useMemo10, useRef as useRef8, useState as useState8 } from "react";
2090
- import { jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
2289
+ import React12, { forwardRef as forwardRef5, useEffect as useEffect8, useMemo as useMemo10, useRef as useRef9, useState as useState8 } from "react";
2290
+ import { jsx as jsx11, jsxs as jsxs7 } from "react/jsx-runtime";
2091
2291
  var INTERACTION4 = "dragTheWords";
2092
2292
  function parseZones(template) {
2093
2293
  const { parts, values } = parseStarDelimitedTemplate(template, "zone");
@@ -2103,12 +2303,14 @@ function DragTheWordsInner(props, ref) {
2103
2303
  const [pool, setPool] = useState8(() => [...props.words]);
2104
2304
  const [keyboardWord, setKeyboardWord] = useState8(null);
2105
2305
  const [passed, setPassed] = useState8(false);
2106
- const completedRef = useRef8(false);
2107
- const answeredRef = useRef8(false);
2306
+ const [submitted, setSubmitted] = useState8(false);
2307
+ const completedRef = useRef9(false);
2308
+ const answeredRef = useRef9(false);
2108
2309
  const reset = () => {
2109
2310
  completedRef.current = false;
2110
2311
  answeredRef.current = false;
2111
2312
  setPassed(false);
2313
+ setSubmitted(false);
2112
2314
  setZones(Object.fromEntries(answers.map((_, i) => [`zone-${i}`, ""])));
2113
2315
  setPool([...props.words]);
2114
2316
  setKeyboardWord(null);
@@ -2141,7 +2343,7 @@ function DragTheWordsInner(props, ref) {
2141
2343
  score,
2142
2344
  maxScore: maxScore || 1
2143
2345
  }),
2144
- getCurrentState: () => ({ zones, pool, passed, keyboardWord }),
2346
+ getCurrentState: () => ({ zones, pool, passed, keyboardWord, submitted }),
2145
2347
  resume: (state) => {
2146
2348
  const rawZones = state.zones;
2147
2349
  if (rawZones && typeof rawZones === "object") setZones({ ...rawZones });
@@ -2151,11 +2353,15 @@ function DragTheWordsInner(props, ref) {
2151
2353
  completedRef.current = value;
2152
2354
  answeredRef.current = value;
2153
2355
  });
2356
+ readBooleanStateField(state, "submitted", (value) => {
2357
+ setSubmitted(value);
2358
+ if (value) answeredRef.current = true;
2359
+ });
2154
2360
  const kw = state.keyboardWord;
2155
2361
  if (kw === null || typeof kw === "string") setKeyboardWord(kw ?? null);
2156
2362
  }
2157
2363
  }),
2158
- [allFilled, checkId, keyboardWord, maxScore, passed, passedThreshold, pool, score, zones]
2364
+ [allFilled, checkId, keyboardWord, maxScore, passed, passedThreshold, pool, score, submitted, zones]
2159
2365
  );
2160
2366
  useAssessmentHandleRegistration(checkId, handle, ref);
2161
2367
  const placeInZone = (zoneId, word) => {
@@ -2185,16 +2391,16 @@ function DragTheWordsInner(props, ref) {
2185
2391
  return;
2186
2392
  }
2187
2393
  if (!allFilled) return;
2188
- if (!answeredRef.current) {
2189
- answeredRef.current = true;
2190
- assessment.answer({
2191
- checkId,
2192
- interactionType: INTERACTION4,
2193
- question: props.template,
2194
- response: zones,
2195
- correct: passedThreshold
2196
- });
2197
- }
2394
+ if (answeredRef.current || submitted) return;
2395
+ answeredRef.current = true;
2396
+ setSubmitted(true);
2397
+ assessment.answer({
2398
+ checkId,
2399
+ interactionType: INTERACTION4,
2400
+ question: props.template,
2401
+ response: zones,
2402
+ correct: passedThreshold
2403
+ });
2198
2404
  if (passedThreshold && !completedRef.current) {
2199
2405
  completedRef.current = true;
2200
2406
  setPassed(true);
@@ -2208,14 +2414,17 @@ function DragTheWordsInner(props, ref) {
2208
2414
  }
2209
2415
  };
2210
2416
  useEffect8(() => {
2211
- if (!allFilled) answeredRef.current = false;
2417
+ if (!allFilled) {
2418
+ answeredRef.current = false;
2419
+ setSubmitted(false);
2420
+ }
2212
2421
  }, [allFilled]);
2213
2422
  useEffect8(() => {
2214
2423
  if (props.autoCheck && allFilled) check();
2215
2424
  }, [allFilled, props.autoCheck, zones, passedThreshold]);
2216
2425
  return /* @__PURE__ */ jsxs7("section", { "aria-label": "Drag the Words", "data-lk-check-id": checkId, children: [
2217
- /* @__PURE__ */ jsx9("p", { children: "Drag words into the blanks (or select a word, then activate a blank)." }),
2218
- /* @__PURE__ */ jsx9("div", { role: "list", "aria-label": "Word bank", "data-testid": "word-bank", children: pool.map((word) => /* @__PURE__ */ jsx9(
2426
+ /* @__PURE__ */ jsx11("p", { children: "Drag words into the blanks (or select a word, then activate a blank)." }),
2427
+ /* @__PURE__ */ jsx11("div", { role: "list", "aria-label": "Word bank", "data-testid": "word-bank", children: pool.map((word) => /* @__PURE__ */ jsx11(
2219
2428
  "button",
2220
2429
  {
2221
2430
  type: "button",
@@ -2229,9 +2438,9 @@ function DragTheWordsInner(props, ref) {
2229
2438
  },
2230
2439
  word
2231
2440
  )) }),
2232
- /* @__PURE__ */ jsx9("p", { children: parts.map((part, i) => {
2233
- if (!part.startsWith("zone-")) return /* @__PURE__ */ jsx9(React10.Fragment, { children: part }, i);
2234
- return /* @__PURE__ */ jsx9(
2441
+ /* @__PURE__ */ jsx11("p", { children: parts.map((part, i) => {
2442
+ if (!part.startsWith("zone-")) return /* @__PURE__ */ jsx11(React12.Fragment, { children: part }, i);
2443
+ return /* @__PURE__ */ jsx11(
2235
2444
  "span",
2236
2445
  {
2237
2446
  role: "button",
@@ -2255,19 +2464,19 @@ function DragTheWordsInner(props, ref) {
2255
2464
  part
2256
2465
  );
2257
2466
  }) }),
2258
- /* @__PURE__ */ jsx9("button", { type: "button", "data-testid": "check-drag-words", disabled: !allFilled || passed, onClick: check, children: "Check" }),
2259
- !hasZones ? /* @__PURE__ */ jsx9("p", { role: "alert", children: "This activity has no drop zones. Wrap answers in asterisks in the template." }) : null,
2260
- allFilled ? /* @__PURE__ */ jsx9("p", { role: "status", "aria-live": "polite", children: passed || passedThreshold ? "Correct" : "Try again" }) : null
2467
+ /* @__PURE__ */ jsx11("button", { type: "button", "data-testid": "check-drag-words", disabled: !allFilled || passed, onClick: check, children: "Check" }),
2468
+ !hasZones ? /* @__PURE__ */ jsx11("p", { role: "alert", children: "This activity has no drop zones. Wrap answers in asterisks in the template." }) : null,
2469
+ submitted ? /* @__PURE__ */ jsx11("p", { role: "status", "aria-live": "polite", children: passed || passedThreshold ? "Correct" : "Try again" }) : null
2261
2470
  ] });
2262
2471
  }
2263
2472
  var DragTheWordsInnerForwarded = forwardRef5(DragTheWordsInner);
2264
2473
  var DragTheWords = forwardRef5(function DragTheWords2(props, ref) {
2265
- return /* @__PURE__ */ jsx9(AssessmentLessonGuard, { blockLabel: "DragTheWords", checkId: props.checkId, children: (lessonId) => /* @__PURE__ */ jsx9(DragTheWordsInnerForwarded, { ...props, enclosingLessonId: lessonId, ref }) });
2474
+ return /* @__PURE__ */ jsx11(AssessmentLessonGuard, { blockLabel: "DragTheWords", checkId: props.checkId, children: (lessonId) => /* @__PURE__ */ jsx11(DragTheWordsInnerForwarded, { ...props, enclosingLessonId: lessonId, ref }) });
2266
2475
  });
2267
2476
 
2268
2477
  // src/blocks/DragAndDrop.tsx
2269
- import { forwardRef as forwardRef6, useEffect as useEffect9, useMemo as useMemo11, useRef as useRef9, useState as useState9 } from "react";
2270
- import { jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
2478
+ import { forwardRef as forwardRef6, useEffect as useEffect9, useMemo as useMemo11, useRef as useRef10, useState as useState9 } from "react";
2479
+ import { jsx as jsx12, jsxs as jsxs8 } from "react/jsx-runtime";
2271
2480
  var INTERACTION5 = "dragAndDrop";
2272
2481
  function DragAndDropInner(props, ref) {
2273
2482
  const checkId = useMemo11(() => normalizeComponentId(props.checkId, "checkId"), [props.checkId]);
@@ -2278,10 +2487,12 @@ function DragAndDropInner(props, ref) {
2278
2487
  const [pool, setPool] = useState9(() => props.items.map((i) => i.id));
2279
2488
  const [keyboardItem, setKeyboardItem] = useState9(null);
2280
2489
  const [passed, setPassed] = useState9(false);
2281
- const completedRef = useRef9(false);
2490
+ const [checked, setChecked] = useState9(false);
2491
+ const completedRef = useRef10(false);
2282
2492
  const reset = () => {
2283
2493
  completedRef.current = false;
2284
2494
  setPassed(false);
2495
+ setChecked(false);
2285
2496
  setAssignments(Object.fromEntries(props.targets.map((t) => [t.id, ""])));
2286
2497
  setPool(props.items.map((i) => i.id));
2287
2498
  setKeyboardItem(null);
@@ -2289,19 +2500,20 @@ function DragAndDropInner(props, ref) {
2289
2500
  useEffect9(() => {
2290
2501
  reset();
2291
2502
  }, [checkId, props.items.map((i) => i.id).join(","), props.targets.map((t) => t.id).join(",")]);
2292
- const allFilled = props.targets.every((t) => (assignments[t.id] ?? "").length > 0);
2293
- const allCorrect = props.targets.every((t) => assignments[t.id] === t.accepts);
2503
+ const hasTargets = props.targets.length > 0;
2504
+ const allFilled = hasTargets && props.targets.every((t) => (assignments[t.id] ?? "").length > 0);
2505
+ let score = 0;
2506
+ props.targets.forEach((t) => {
2507
+ if (assignments[t.id] === t.accepts) score += 1;
2508
+ });
2509
+ const maxScore = props.targets.length || 1;
2510
+ const passedThreshold = meetsPassingThreshold(score, maxScore, props.passingScore);
2294
2511
  const handle = useMemo11(() => {
2295
- const maxScore = props.targets.length || 1;
2296
- let score = 0;
2297
- props.targets.forEach((t) => {
2298
- if (assignments[t.id] === t.accepts) score += 1;
2299
- });
2300
2512
  return buildAssessmentHandle({
2301
2513
  checkId,
2302
2514
  getScore: () => score,
2303
2515
  getMaxScore: () => maxScore,
2304
- getAnswerGiven: () => allFilled,
2516
+ getAnswerGiven: () => hasTargets && allFilled,
2305
2517
  resetTask: reset,
2306
2518
  showSolutions: () => {
2307
2519
  },
@@ -2309,11 +2521,11 @@ function DragAndDropInner(props, ref) {
2309
2521
  checkId,
2310
2522
  interactionType: INTERACTION5,
2311
2523
  response: assignments,
2312
- correct: allCorrect,
2524
+ correct: passedThreshold,
2313
2525
  score,
2314
2526
  maxScore
2315
2527
  }),
2316
- getCurrentState: () => ({ assignments, pool, passed, keyboardItem }),
2528
+ getCurrentState: () => ({ assignments, pool, passed, checked, keyboardItem }),
2317
2529
  resume: (state) => {
2318
2530
  const rawAssignments = state.assignments;
2319
2531
  if (rawAssignments && typeof rawAssignments === "object") {
@@ -2324,14 +2536,16 @@ function DragAndDropInner(props, ref) {
2324
2536
  setPassed(value);
2325
2537
  completedRef.current = value;
2326
2538
  });
2539
+ readBooleanStateField(state, "checked", setChecked);
2327
2540
  const item = state.keyboardItem;
2328
2541
  if (item === null || typeof item === "string") setKeyboardItem(item ?? null);
2329
2542
  }
2330
2543
  });
2331
- }, [allCorrect, allFilled, assignments, checkId, keyboardItem, passed, pool, props.targets]);
2544
+ }, [allFilled, assignments, checkId, checked, hasTargets, keyboardItem, maxScore, passed, passedThreshold, pool, props.targets, score]);
2332
2545
  useAssessmentHandleRegistration(checkId, handle, ref);
2333
2546
  const place = (targetId, itemId) => {
2334
2547
  if (passed && !props.enableRetry) return;
2548
+ setChecked(false);
2335
2549
  const prev = assignments[targetId];
2336
2550
  setAssignments((a) => ({ ...a, [targetId]: itemId }));
2337
2551
  setPool((p) => {
@@ -2343,29 +2557,31 @@ function DragAndDropInner(props, ref) {
2343
2557
  };
2344
2558
  const check = () => {
2345
2559
  if (!allFilled) return;
2560
+ setChecked(true);
2346
2561
  assessment.answer({
2347
2562
  checkId,
2348
2563
  interactionType: INTERACTION5,
2349
2564
  response: assignments,
2350
- correct: allCorrect
2565
+ correct: passedThreshold
2351
2566
  });
2352
- if (allCorrect && !completedRef.current) {
2567
+ if (passedThreshold && !completedRef.current) {
2353
2568
  completedRef.current = true;
2354
2569
  setPassed(true);
2355
2570
  assessment.complete({
2356
2571
  checkId,
2357
2572
  interactionType: INTERACTION5,
2358
- score: props.targets.length,
2359
- maxScore: props.targets.length,
2360
- passingScore: props.passingScore ?? props.targets.length
2573
+ score,
2574
+ maxScore,
2575
+ passingScore: props.passingScore ?? maxScore
2361
2576
  });
2362
2577
  }
2363
2578
  };
2364
2579
  return /* @__PURE__ */ jsxs8("section", { "aria-label": "Drag and Drop", "data-lk-check-id": checkId, children: [
2365
- /* @__PURE__ */ jsx10("p", { children: "Match each item to the correct target (drag or use keyboard: select item, then activate target)." }),
2366
- /* @__PURE__ */ jsx10("div", { role: "list", "aria-label": "Draggable items", children: pool.map((id) => {
2580
+ /* @__PURE__ */ jsx12("p", { children: "Match each item to the correct target (drag or use keyboard: select item, then activate target)." }),
2581
+ /* @__PURE__ */ jsx12("div", { role: "list", "aria-label": "Draggable items", children: pool.flatMap((id) => {
2367
2582
  const item = props.items.find((i) => i.id === id);
2368
- return /* @__PURE__ */ jsx10(
2583
+ if (!item) return [];
2584
+ return /* @__PURE__ */ jsx12(
2369
2585
  "button",
2370
2586
  {
2371
2587
  type: "button",
@@ -2380,13 +2596,13 @@ function DragAndDropInner(props, ref) {
2380
2596
  id
2381
2597
  );
2382
2598
  }) }),
2383
- /* @__PURE__ */ jsx10("ul", { children: props.targets.map((target) => {
2599
+ /* @__PURE__ */ jsx12("ul", { children: props.targets.map((target) => {
2384
2600
  const assigned = assignments[target.id];
2385
2601
  const label = assigned ? props.items.find((i) => i.id === assigned)?.label ?? assigned : "Drop here";
2386
2602
  return /* @__PURE__ */ jsxs8("li", { children: [
2387
- /* @__PURE__ */ jsx10("strong", { children: target.label }),
2603
+ /* @__PURE__ */ jsx12("strong", { children: target.label }),
2388
2604
  " ",
2389
- /* @__PURE__ */ jsx10(
2605
+ /* @__PURE__ */ jsx12(
2390
2606
  "span",
2391
2607
  {
2392
2608
  role: "button",
@@ -2413,17 +2629,18 @@ function DragAndDropInner(props, ref) {
2413
2629
  )
2414
2630
  ] }, target.id);
2415
2631
  }) }),
2416
- /* @__PURE__ */ jsx10("button", { type: "button", "data-testid": "check-drag-drop", disabled: !allFilled || passed, onClick: check, children: "Check" }),
2417
- allFilled ? /* @__PURE__ */ jsx10("p", { role: "status", "aria-live": "polite", children: passed || allCorrect ? "Correct" : "Try again" }) : null
2632
+ /* @__PURE__ */ jsx12("button", { type: "button", "data-testid": "check-drag-drop", disabled: !hasTargets || !allFilled || passed, onClick: check, children: "Check" }),
2633
+ checked ? /* @__PURE__ */ jsx12("p", { role: "status", "aria-live": "polite", children: passedThreshold ? "Correct" : "Try again" }) : null
2418
2634
  ] });
2419
2635
  }
2420
2636
  var DragAndDropInnerForwarded = forwardRef6(DragAndDropInner);
2421
2637
  var DragAndDrop = forwardRef6(function DragAndDrop2(props, ref) {
2422
- return /* @__PURE__ */ jsx10(AssessmentLessonGuard, { blockLabel: "DragAndDrop", checkId: props.checkId, children: (lessonId) => /* @__PURE__ */ jsx10(DragAndDropInnerForwarded, { ...props, enclosingLessonId: lessonId, ref }) });
2638
+ return /* @__PURE__ */ jsx12(AssessmentLessonGuard, { blockLabel: "DragAndDrop", checkId: props.checkId, children: (lessonId) => /* @__PURE__ */ jsx12(DragAndDropInnerForwarded, { ...props, enclosingLessonId: lessonId, ref }) });
2423
2639
  });
2424
2640
 
2425
2641
  // src/blocks/AssessmentSequence.tsx
2426
- import React14, { forwardRef as forwardRef7, useCallback as useCallback7, useEffect as useEffect12, useMemo as useMemo13, useState as useState10 } from "react";
2642
+ import React16, { forwardRef as forwardRef7, useCallback as useCallback7, useEffect as useEffect12, useId as useId3, useMemo as useMemo13, useRef as useRef13, useState as useState10 } from "react";
2643
+ import { deriveId } from "@lessonkit/core";
2427
2644
 
2428
2645
  // src/compound/useCompoundShell.ts
2429
2646
  import { useMemo as useMemo12 } from "react";
@@ -2450,7 +2667,7 @@ function useCompoundNavigation(pageCount, index, setIndex) {
2450
2667
  }
2451
2668
 
2452
2669
  // src/compound/useCompoundPersistence.ts
2453
- import { useCallback as useCallback6, useEffect as useEffect11, useRef as useRef11 } from "react";
2670
+ import { useCallback as useCallback6, useContext as useContext7, useEffect as useEffect11, useRef as useRef12 } from "react";
2454
2671
  import {
2455
2672
  clampCompoundPageIndex as clampCompoundPageIndex2,
2456
2673
  createCompoundResumeState as createCompoundResumeState2,
@@ -2458,14 +2675,80 @@ import {
2458
2675
  loadCompoundState as loadCompoundState2
2459
2676
  } from "@lessonkit/core";
2460
2677
 
2678
+ // src/compound/resumeChildHandles.ts
2679
+ function filterRegisteredChildStates(handles, childStates) {
2680
+ const filtered = {};
2681
+ for (const [key, value] of Object.entries(childStates)) {
2682
+ if (handles.has(key)) {
2683
+ filtered[key] = value;
2684
+ }
2685
+ }
2686
+ return filtered;
2687
+ }
2688
+ function resumeChildHandles(handles, childStates, opts) {
2689
+ const pendingKeys = Object.keys(childStates);
2690
+ const alreadyResumed = opts?.alreadyResumed;
2691
+ if (opts?.waitForHandles && pendingKeys.length > 0) {
2692
+ if (handles.size === 0) return false;
2693
+ const registeredPending = pendingKeys.filter((k) => handles.has(k));
2694
+ if (registeredPending.length === 0) {
2695
+ return false;
2696
+ }
2697
+ if (registeredPending.length < pendingKeys.length) {
2698
+ for (const key of registeredPending) {
2699
+ if (alreadyResumed?.has(key)) continue;
2700
+ const handle = handles.get(key);
2701
+ const child = childStates[key];
2702
+ if (handle?.resume && child) {
2703
+ handle.resume(child);
2704
+ alreadyResumed?.add(key);
2705
+ }
2706
+ }
2707
+ return false;
2708
+ }
2709
+ }
2710
+ for (const [checkId, handle] of handles) {
2711
+ if (alreadyResumed?.has(checkId)) continue;
2712
+ const child = childStates[checkId];
2713
+ if (child && handle.resume) {
2714
+ handle.resume(child);
2715
+ alreadyResumed?.add(checkId);
2716
+ }
2717
+ }
2718
+ return true;
2719
+ }
2720
+
2461
2721
  // src/compound/useCompoundResume.ts
2462
- import { useCallback as useCallback5, useEffect as useEffect10, useRef as useRef10 } from "react";
2722
+ import { useCallback as useCallback5, useContext as useContext6, useEffect as useEffect10, useRef as useRef11 } from "react";
2463
2723
  import { loadCompoundState, saveCompoundState } from "@lessonkit/core";
2464
2724
  import { createSessionStoragePort as createSessionStoragePort2 } from "@lessonkit/core";
2725
+ var warnedCompoundPersistFailure = false;
2726
+ function warnCompoundPersistFailure() {
2727
+ if (warnedCompoundPersistFailure || !isDevEnvironment4()) return;
2728
+ warnedCompoundPersistFailure = true;
2729
+ console.warn(
2730
+ "[lessonkit] compound resume state could not be saved to sessionStorage (quota or privacy mode); progress may be lost on reload."
2731
+ );
2732
+ }
2465
2733
  function useCompoundResume(opts) {
2466
- const storageRef = useRef10(opts.storage ?? createSessionStoragePort2());
2467
- const resumedRef = useRef10(false);
2734
+ const lessonkitCtx = useContext6(LessonkitContext);
2735
+ const storageRef = useRef11(opts.storage ?? lessonkitCtx?.storage ?? createSessionStoragePort2());
2736
+ const resumedRef = useRef11(false);
2737
+ const resumeKeyRef = useRef11("");
2738
+ const prevEnabledRef = useRef11(opts.enabled);
2739
+ useEffect10(() => {
2740
+ storageRef.current = opts.storage ?? lessonkitCtx?.storage ?? createSessionStoragePort2();
2741
+ }, [opts.storage, lessonkitCtx?.storage]);
2468
2742
  useEffect10(() => {
2743
+ if (!prevEnabledRef.current && opts.enabled) {
2744
+ resumedRef.current = false;
2745
+ }
2746
+ prevEnabledRef.current = opts.enabled;
2747
+ const key = `${opts.courseId ?? ""}:${opts.compoundId}`;
2748
+ if (resumeKeyRef.current !== key) {
2749
+ resumeKeyRef.current = key;
2750
+ resumedRef.current = false;
2751
+ }
2469
2752
  if (!opts.enabled || !opts.courseId || resumedRef.current) return;
2470
2753
  const saved = loadCompoundState(storageRef.current, opts.courseId, opts.compoundId);
2471
2754
  if (saved) {
@@ -2476,7 +2759,8 @@ function useCompoundResume(opts) {
2476
2759
  return useCallback5(
2477
2760
  (state) => {
2478
2761
  if (!opts.enabled || !opts.courseId) return;
2479
- saveCompoundState(storageRef.current, opts.courseId, opts.compoundId, state);
2762
+ const persisted = saveCompoundState(storageRef.current, opts.courseId, opts.compoundId, state);
2763
+ if (!persisted) warnCompoundPersistFailure();
2480
2764
  },
2481
2765
  [opts.enabled, opts.courseId, opts.compoundId]
2482
2766
  );
@@ -2489,19 +2773,46 @@ function readCompoundInitialIndex(courseId, compoundId, pageCount, enabled, stor
2489
2773
  if (!saved) return 0;
2490
2774
  return clampCompoundPageIndex2(saved.activePageIndex, pageCount);
2491
2775
  }
2776
+ function stripOrphanChildStates(handles, childStates) {
2777
+ return filterRegisteredChildStates(handles, childStates);
2778
+ }
2492
2779
  function useCompoundPersistence(opts) {
2493
- const storage = opts.storage ?? createSessionStoragePort3();
2780
+ const lessonkitCtx = useContext7(LessonkitContext);
2781
+ const storage = opts.storage ?? lessonkitCtx?.storage ?? createSessionStoragePort3();
2494
2782
  const ctx = useCompoundRegistry();
2495
2783
  const handlesVersion = useCompoundHandlesVersion();
2496
- const pendingChildResumeRef = useRef11(null);
2497
- const loadedChildStatesRef = useRef11({});
2498
- const skipSaveUntilHydratedRef = useRef11(false);
2784
+ const bridgeRef = useCompoundHydrationBridgeRef();
2785
+ const pendingChildResumeRef = useRef12(null);
2786
+ const resumedChildKeysRef = useRef12(/* @__PURE__ */ new Set());
2787
+ const loadedChildStatesRef = useRef12({});
2788
+ const skipSaveUntilHydratedRef = useRef12(false);
2789
+ const hydrationKeyRef = useRef12("");
2790
+ const hydrationInitRef = useRef12(false);
2791
+ const hydrationKey = `${opts.courseId ?? ""}:${opts.compoundId}`;
2792
+ if (hydrationKeyRef.current !== hydrationKey) {
2793
+ hydrationKeyRef.current = hydrationKey;
2794
+ hydrationInitRef.current = false;
2795
+ loadedChildStatesRef.current = {};
2796
+ skipSaveUntilHydratedRef.current = false;
2797
+ pendingChildResumeRef.current = null;
2798
+ resumedChildKeysRef.current = /* @__PURE__ */ new Set();
2799
+ }
2800
+ if (!hydrationInitRef.current && opts.enabled && opts.courseId) {
2801
+ hydrationInitRef.current = true;
2802
+ const saved = loadCompoundState2(storage, opts.courseId, opts.compoundId);
2803
+ if (saved && Object.keys(saved.childStates).length > 0) {
2804
+ loadedChildStatesRef.current = { ...saved.childStates };
2805
+ skipSaveUntilHydratedRef.current = true;
2806
+ pendingChildResumeRef.current = saved;
2807
+ }
2808
+ }
2499
2809
  const buildState = useCallback6(() => {
2500
2810
  const childStates = {
2501
2811
  ...loadedChildStatesRef.current
2502
2812
  };
2503
2813
  if (ctx) {
2504
- for (const [checkId, handle] of ctx.getHandles()) {
2814
+ for (const [checkId, entry] of ctx.getRegisteredHandles()) {
2815
+ const handle = entry.handle;
2505
2816
  if (handle.getCurrentState) {
2506
2817
  childStates[checkId] = handle.getCurrentState();
2507
2818
  delete loadedChildStatesRef.current[checkId];
@@ -2513,14 +2824,44 @@ function useCompoundPersistence(opts) {
2513
2824
  childStates
2514
2825
  });
2515
2826
  }, [ctx, opts.index, opts.pageCount]);
2827
+ const buildStateRef = useRef12(buildState);
2828
+ buildStateRef.current = buildState;
2829
+ const finalizeHydration = useCallback6(
2830
+ (childStates) => {
2831
+ loadedChildStatesRef.current = {
2832
+ ...loadedChildStatesRef.current,
2833
+ ...childStates
2834
+ };
2835
+ skipSaveUntilHydratedRef.current = false;
2836
+ pendingChildResumeRef.current = null;
2837
+ },
2838
+ []
2839
+ );
2516
2840
  const applyPendingChildResume = useCallback6(() => {
2517
2841
  const pending = pendingChildResumeRef.current;
2518
2842
  if (!pending || !ctx) return;
2519
- const applied = resumeChildHandles(ctx.getHandles(), pending.childStates, { waitForHandles: true });
2520
- if (!applied) return;
2521
- pendingChildResumeRef.current = null;
2522
- skipSaveUntilHydratedRef.current = false;
2523
- }, [ctx]);
2843
+ const handles = ctx.getHandles();
2844
+ const applied = resumeChildHandles(handles, pending.childStates, {
2845
+ waitForHandles: true,
2846
+ alreadyResumed: resumedChildKeysRef.current
2847
+ });
2848
+ if (!applied) {
2849
+ const handlesAtWait = handles.size;
2850
+ queueMicrotask(() => {
2851
+ if (pendingChildResumeRef.current !== pending) return;
2852
+ const handlesNow = ctx.getHandles();
2853
+ if (handlesNow.size !== handlesAtWait) return;
2854
+ const registeredOnly2 = stripOrphanChildStates(handlesNow, pending.childStates);
2855
+ resumeChildHandles(handlesNow, registeredOnly2, {
2856
+ alreadyResumed: resumedChildKeysRef.current
2857
+ });
2858
+ finalizeHydration(registeredOnly2);
2859
+ });
2860
+ return;
2861
+ }
2862
+ const registeredOnly = stripOrphanChildStates(handles, pending.childStates);
2863
+ finalizeHydration(registeredOnly);
2864
+ }, [ctx, finalizeHydration]);
2524
2865
  const saveResume = useCompoundResume({
2525
2866
  courseId: opts.courseId,
2526
2867
  compoundId: opts.compoundId,
@@ -2531,26 +2872,54 @@ function useCompoundPersistence(opts) {
2531
2872
  loadedChildStatesRef.current = { ...state.childStates };
2532
2873
  skipSaveUntilHydratedRef.current = Object.keys(state.childStates).length > 0;
2533
2874
  opts.setIndex(clamped);
2534
- pendingChildResumeRef.current = { ...state, activePageIndex: clamped };
2875
+ resumedChildKeysRef.current = /* @__PURE__ */ new Set();
2876
+ pendingChildResumeRef.current = { ...state, activePageIndex: clamped, childStates: state.childStates };
2535
2877
  queueMicrotask(() => applyPendingChildResume());
2536
2878
  }
2537
2879
  });
2538
- useEffect11(() => {
2880
+ const persistNow = useCallback6(() => {
2539
2881
  if (!opts.enabled || !opts.courseId) return;
2540
- if (skipSaveUntilHydratedRef.current) return;
2541
- saveResume(buildState());
2542
- }, [
2543
- opts.enabled,
2544
- opts.courseId,
2545
- opts.index,
2546
- opts.pageCount,
2547
- handlesVersion,
2548
- saveResume,
2549
- buildState
2550
- ]);
2882
+ saveResume(buildStateRef.current());
2883
+ }, [opts.enabled, opts.courseId, saveResume]);
2884
+ const notifyImperativeResume = useCallback6(
2885
+ (state) => {
2886
+ const clamped = clampCompoundPageIndex2(state.activePageIndex, opts.pageCount);
2887
+ loadedChildStatesRef.current = { ...state.childStates };
2888
+ skipSaveUntilHydratedRef.current = Object.keys(state.childStates).length > 0;
2889
+ opts.setIndex(clamped);
2890
+ resumedChildKeysRef.current = /* @__PURE__ */ new Set();
2891
+ pendingChildResumeRef.current = { ...state, activePageIndex: clamped, childStates: state.childStates };
2892
+ queueMicrotask(() => applyPendingChildResume());
2893
+ },
2894
+ [opts.pageCount, opts.setIndex, applyPendingChildResume]
2895
+ );
2896
+ useEffect11(() => {
2897
+ if (!bridgeRef) return;
2898
+ bridgeRef.current = { notifyImperativeResume };
2899
+ return () => {
2900
+ if (bridgeRef.current?.notifyImperativeResume === notifyImperativeResume) {
2901
+ bridgeRef.current = null;
2902
+ }
2903
+ };
2904
+ }, [bridgeRef, notifyImperativeResume]);
2905
+ useEffect11(() => {
2906
+ persistNow();
2907
+ }, [persistNow, opts.index, opts.pageCount, handlesVersion]);
2551
2908
  useEffect11(() => {
2552
2909
  applyPendingChildResume();
2553
2910
  }, [opts.index, handlesVersion, applyPendingChildResume]);
2911
+ useEffect11(() => {
2912
+ if (!opts.enabled || !opts.courseId || typeof document === "undefined") return;
2913
+ const flushOnExit = () => {
2914
+ if (document.visibilityState === "hidden") persistNow();
2915
+ };
2916
+ document.addEventListener("visibilitychange", flushOnExit);
2917
+ window.addEventListener("pagehide", flushOnExit);
2918
+ return () => {
2919
+ document.removeEventListener("visibilitychange", flushOnExit);
2920
+ window.removeEventListener("pagehide", flushOnExit);
2921
+ };
2922
+ }, [opts.enabled, opts.courseId, persistNow]);
2554
2923
  }
2555
2924
 
2556
2925
  // src/compound/useCompoundShell.ts
@@ -2571,6 +2940,7 @@ function useCompoundShell(opts) {
2571
2940
  activePageIndex: visibleIndex,
2572
2941
  setActivePageIndex: opts.setIndex,
2573
2942
  getHandles: () => ctx?.getHandles() ?? /* @__PURE__ */ new Map(),
2943
+ getRegisteredHandles: () => ctx?.getRegisteredHandles() ?? /* @__PURE__ */ new Map(),
2574
2944
  pageCount: opts.pageCount,
2575
2945
  enableSolutionsButton: opts.enableSolutionsButton
2576
2946
  });
@@ -2590,7 +2960,7 @@ function useCompoundInitialIndex(opts) {
2590
2960
  }
2591
2961
 
2592
2962
  // src/compound/validateChildren.ts
2593
- import React13 from "react";
2963
+ import React15 from "react";
2594
2964
  import {
2595
2965
  ACCORDION_FORBIDDEN_CHILD_TYPES,
2596
2966
  COMPOUND_MAX_NESTING_DEPTH,
@@ -2619,6 +2989,8 @@ var warnedPairs = /* @__PURE__ */ new Set();
2619
2989
  var COMPOUND_CONTAINER_TYPES = /* @__PURE__ */ new Set([
2620
2990
  "Page",
2621
2991
  "InteractiveBook",
2992
+ "Slide",
2993
+ "SlideDeck",
2622
2994
  "AssessmentSequence"
2623
2995
  ]);
2624
2996
  function warnOrThrow(msg, strict) {
@@ -2629,8 +3001,8 @@ function warnOrThrow(msg, strict) {
2629
3001
  }
2630
3002
  }
2631
3003
  function validateNode(parent, node, depth, strict) {
2632
- React13.Children.forEach(node, (child) => {
2633
- if (!React13.isValidElement(child)) return;
3004
+ React15.Children.forEach(node, (child) => {
3005
+ if (!React15.isValidElement(child)) return;
2634
3006
  const blockType = getLessonkitBlockType(child.type);
2635
3007
  if (!blockType) {
2636
3008
  if (child.props && typeof child.props === "object" && "children" in child.props) {
@@ -2670,8 +3042,8 @@ function validateNode(parent, node, depth, strict) {
2670
3042
  });
2671
3043
  }
2672
3044
  function validateSubtreeForForbidden(node, forbidden, strict) {
2673
- React13.Children.forEach(node, (child) => {
2674
- if (!React13.isValidElement(child)) return;
3045
+ React15.Children.forEach(node, (child) => {
3046
+ if (!React15.isValidElement(child)) return;
2675
3047
  const blockType = getLessonkitBlockType(child.type);
2676
3048
  if (blockType && forbidden.includes(blockType)) {
2677
3049
  warnOrThrow(`[lessonkit] Block "${blockType}" must not nest inside Accordion`, strict);
@@ -2711,7 +3083,7 @@ function warnSharedCompoundStorageKey(opts) {
2711
3083
  }
2712
3084
 
2713
3085
  // src/blocks/AssessmentSequence.tsx
2714
- import { jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
3086
+ import { jsx as jsx13, jsxs as jsxs9 } from "react/jsx-runtime";
2715
3087
  var AssessmentSequenceInner = forwardRef7(
2716
3088
  function AssessmentSequenceInner2(props, ref) {
2717
3089
  const { compoundId, childArray, index, setIndex, persistEnabled } = props;
@@ -2729,7 +3101,7 @@ var AssessmentSequenceInner = forwardRef7(
2729
3101
  });
2730
3102
  validateCompoundChildren("AssessmentSequence", props.children);
2731
3103
  if (!sequential) {
2732
- return /* @__PURE__ */ jsx11("section", { "aria-label": "Assessment sequence", "data-testid": "assessment-sequence", children: props.children });
3104
+ return /* @__PURE__ */ jsx13("section", { "aria-label": "Assessment sequence", "data-testid": "assessment-sequence", children: props.children });
2733
3105
  }
2734
3106
  return /* @__PURE__ */ jsxs9("section", { "aria-label": "Assessment sequence", "data-testid": "assessment-sequence", children: [
2735
3107
  /* @__PURE__ */ jsxs9("p", { children: [
@@ -2738,9 +3110,9 @@ var AssessmentSequenceInner = forwardRef7(
2738
3110
  " of ",
2739
3111
  progress.total
2740
3112
  ] }),
2741
- /* @__PURE__ */ jsx11("div", { "data-testid": "assessment-sequence-step", children: childArray.map((child, i) => /* @__PURE__ */ jsx11("div", { hidden: i !== visibleIndex, children: child }, child.key ?? i)) }),
3113
+ /* @__PURE__ */ jsx13("div", { "data-testid": "assessment-sequence-step", children: childArray.map((child, i) => /* @__PURE__ */ jsx13("div", { hidden: i !== visibleIndex, children: /* @__PURE__ */ jsx13(CompoundPageIndexProvider, { pageIndex: i, children: child }) }, child.key ?? i)) }),
2742
3114
  /* @__PURE__ */ jsxs9("nav", { "aria-label": "Sequence navigation", children: [
2743
- /* @__PURE__ */ jsx11(
3115
+ /* @__PURE__ */ jsx13(
2744
3116
  "button",
2745
3117
  {
2746
3118
  type: "button",
@@ -2750,7 +3122,7 @@ var AssessmentSequenceInner = forwardRef7(
2750
3122
  children: "Previous"
2751
3123
  }
2752
3124
  ),
2753
- /* @__PURE__ */ jsx11(
3125
+ /* @__PURE__ */ jsx13(
2754
3126
  "button",
2755
3127
  {
2756
3128
  type: "button",
@@ -2766,14 +3138,19 @@ var AssessmentSequenceInner = forwardRef7(
2766
3138
  );
2767
3139
  var AssessmentSequence = forwardRef7(
2768
3140
  function AssessmentSequence2(props, ref) {
3141
+ const reactInstanceId = useId3();
3142
+ const autoCompoundIdRef = useRef13(null);
3143
+ if (!props.blockId && !autoCompoundIdRef.current) {
3144
+ autoCompoundIdRef.current = deriveId(`assessment-sequence-${reactInstanceId}`);
3145
+ }
2769
3146
  const compoundId = useMemo13(
2770
- () => props.blockId ? normalizeComponentId(props.blockId, "blockId") : DEFAULT_ASSESSMENT_SEQUENCE_COMPOUND_ID,
3147
+ () => props.blockId ? normalizeComponentId(props.blockId, "blockId") : autoCompoundIdRef.current ?? DEFAULT_ASSESSMENT_SEQUENCE_COMPOUND_ID,
2771
3148
  [props.blockId]
2772
3149
  );
2773
- const childArray = React14.Children.toArray(props.children).filter(
2774
- React14.isValidElement
3150
+ const childArray = React16.Children.toArray(props.children).filter(
3151
+ React16.isValidElement
2775
3152
  );
2776
- const { config } = useLessonkit();
3153
+ const { config, storage } = useLessonkit();
2777
3154
  const persistEnabled = config.session?.persistCompoundState !== false;
2778
3155
  useEffect12(() => {
2779
3156
  warnSharedCompoundStorageKey({
@@ -2786,11 +3163,15 @@ var AssessmentSequence = forwardRef7(
2786
3163
  courseId: config.courseId,
2787
3164
  compoundId,
2788
3165
  pageCount: childArray.length,
2789
- persistEnabled
3166
+ persistEnabled,
3167
+ storage
2790
3168
  });
2791
3169
  const [index, setIndex] = useState10(initialIndex);
2792
3170
  const setIndexStable = useCallback7((i) => setIndex(i), []);
2793
- return /* @__PURE__ */ jsx11(CompoundProvider, { activePageIndex: index, onActivePageIndexChange: setIndexStable, children: /* @__PURE__ */ jsx11(
3171
+ useEffect12(() => {
3172
+ setIndex(initialIndex);
3173
+ }, [config.courseId, compoundId, initialIndex]);
3174
+ return /* @__PURE__ */ jsx13(CompoundProvider, { activePageIndex: index, onActivePageIndexChange: setIndexStable, children: /* @__PURE__ */ jsx13(
2794
3175
  AssessmentSequenceInner,
2795
3176
  {
2796
3177
  ...props,
@@ -2808,24 +3189,24 @@ setLessonkitBlockType(AssessmentSequence, "AssessmentSequence");
2808
3189
 
2809
3190
  // src/blocks/Text.tsx
2810
3191
  import "react";
2811
- import { jsx as jsx12 } from "react/jsx-runtime";
3192
+ import { jsx as jsx14 } from "react/jsx-runtime";
2812
3193
  function Text(props) {
2813
- return /* @__PURE__ */ jsx12("p", { "data-lk-block-id": props.blockId, "data-testid": props.blockId ? `text-${props.blockId}` : "text", children: props.children });
3194
+ return /* @__PURE__ */ jsx14("p", { "data-lk-block-id": props.blockId, "data-testid": props.blockId ? `text-${props.blockId}` : "text", children: props.children });
2814
3195
  }
2815
3196
  setLessonkitBlockType(Text, "Text");
2816
3197
 
2817
3198
  // src/blocks/Heading.tsx
2818
- import { jsx as jsx13 } from "react/jsx-runtime";
3199
+ import { jsx as jsx15 } from "react/jsx-runtime";
2819
3200
  function Heading(props) {
2820
3201
  const Tag = `h${props.level}`;
2821
- return /* @__PURE__ */ jsx13(Tag, { "data-lk-block-id": props.blockId, "data-testid": props.blockId ? `heading-${props.blockId}` : "heading", children: props.children });
3202
+ return /* @__PURE__ */ jsx15(Tag, { "data-lk-block-id": props.blockId, "data-testid": props.blockId ? `heading-${props.blockId}` : "heading", children: props.children });
2822
3203
  }
2823
3204
  setLessonkitBlockType(Heading, "Heading");
2824
3205
 
2825
3206
  // src/blocks/Image.tsx
2826
- import { jsx as jsx14 } from "react/jsx-runtime";
3207
+ import { jsx as jsx16 } from "react/jsx-runtime";
2827
3208
  function Image(props) {
2828
- return /* @__PURE__ */ jsx14(
3209
+ return /* @__PURE__ */ jsx16(
2829
3210
  "img",
2830
3211
  {
2831
3212
  src: props.src,
@@ -2840,13 +3221,13 @@ setLessonkitBlockType(Image, "Image");
2840
3221
 
2841
3222
  // src/blocks/Page.tsx
2842
3223
  import { useEffect as useEffect13 } from "react";
2843
- import { jsx as jsx15, jsxs as jsxs10 } from "react/jsx-runtime";
3224
+ import { jsx as jsx17, jsxs as jsxs10 } from "react/jsx-runtime";
2844
3225
  function Page(props) {
2845
3226
  validateCompoundChildren("Page", props.children);
2846
3227
  const { track } = useLessonkit();
2847
3228
  const lessonId = useEnclosingLessonId();
2848
3229
  useEffect13(() => {
2849
- if (props.hidden || !lessonId) return;
3230
+ if (props.hidden || !lessonId || props.parentType) return;
2850
3231
  track(
2851
3232
  "compound_page_viewed",
2852
3233
  {
@@ -2865,8 +3246,8 @@ function Page(props) {
2865
3246
  "data-testid": `page-${props.blockId}`,
2866
3247
  hidden: props.hidden ? true : void 0,
2867
3248
  children: [
2868
- props.title ? /* @__PURE__ */ jsx15("h3", { children: props.title }) : null,
2869
- /* @__PURE__ */ jsx15("div", { children: props.children })
3249
+ props.title ? /* @__PURE__ */ jsx17("h3", { children: props.title }) : null,
3250
+ /* @__PURE__ */ jsx17(CompoundPageIndexProvider, { pageIndex: props.pageIndex ?? 0, children: /* @__PURE__ */ jsx17("div", { children: props.children }) })
2870
3251
  ]
2871
3252
  }
2872
3253
  );
@@ -2874,8 +3255,8 @@ function Page(props) {
2874
3255
  setLessonkitBlockType(Page, "Page");
2875
3256
 
2876
3257
  // src/blocks/InteractiveBook.tsx
2877
- import React17, { forwardRef as forwardRef8, useCallback as useCallback8, useEffect as useEffect14, useMemo as useMemo14, useState as useState11 } from "react";
2878
- import { jsx as jsx16, jsxs as jsxs11 } from "react/jsx-runtime";
3258
+ import React19, { forwardRef as forwardRef8, useCallback as useCallback8, useEffect as useEffect14, useMemo as useMemo14, useState as useState11 } from "react";
3259
+ import { jsx as jsx18, jsxs as jsxs11 } from "react/jsx-runtime";
2879
3260
  var InteractiveBookInner = forwardRef8(
2880
3261
  function InteractiveBookInner2(props, ref) {
2881
3262
  const { blockId, pages, index, setIndex, persistEnabled } = props;
@@ -2908,7 +3289,7 @@ var InteractiveBookInner = forwardRef8(
2908
3289
  );
2909
3290
  }, [visibleIndex, blockId, lessonId, pages.length, pageTitles, track]);
2910
3291
  return /* @__PURE__ */ jsxs11("section", { "aria-label": props.title, "data-testid": "interactive-book", "data-lk-block-id": blockId, children: [
2911
- /* @__PURE__ */ jsx16("h3", { children: props.title }),
3292
+ /* @__PURE__ */ jsx18("h3", { children: props.title }),
2912
3293
  /* @__PURE__ */ jsxs11("p", { children: [
2913
3294
  "Page ",
2914
3295
  progress.current,
@@ -2922,8 +3303,8 @@ var InteractiveBookInner = forwardRef8(
2922
3303
  " ",
2923
3304
  Array.from(ctx.getHandles().values()).reduce((s, h) => s + h.getMaxScore(), 0)
2924
3305
  ] }) : null,
2925
- /* @__PURE__ */ jsx16("div", { "data-testid": "interactive-book-page", children: pages.map(
2926
- (page, i) => React17.cloneElement(page, {
3306
+ /* @__PURE__ */ jsx18("div", { "data-testid": "interactive-book-page", children: pages.map(
3307
+ (page, i) => React19.cloneElement(page, {
2927
3308
  key: page.key ?? page.props.blockId,
2928
3309
  hidden: i !== visibleIndex,
2929
3310
  pageIndex: i,
@@ -2931,7 +3312,7 @@ var InteractiveBookInner = forwardRef8(
2931
3312
  })
2932
3313
  ) }),
2933
3314
  /* @__PURE__ */ jsxs11("nav", { "aria-label": "Book navigation", children: [
2934
- /* @__PURE__ */ jsx16(
3315
+ /* @__PURE__ */ jsx18(
2935
3316
  "button",
2936
3317
  {
2937
3318
  type: "button",
@@ -2941,7 +3322,7 @@ var InteractiveBookInner = forwardRef8(
2941
3322
  children: "Previous"
2942
3323
  }
2943
3324
  ),
2944
- /* @__PURE__ */ jsx16(
3325
+ /* @__PURE__ */ jsx18(
2945
3326
  "button",
2946
3327
  {
2947
3328
  type: "button",
@@ -2960,20 +3341,24 @@ var InteractiveBook = forwardRef8(function InteractiveBook2(props, ref) {
2960
3341
  () => normalizeComponentId(props.blockId, "blockId"),
2961
3342
  [props.blockId]
2962
3343
  );
2963
- const pages = React17.Children.toArray(props.children).filter(
2964
- React17.isValidElement
3344
+ const pages = React19.Children.toArray(props.children).filter(
3345
+ React19.isValidElement
2965
3346
  );
2966
- const { config } = useLessonkit();
3347
+ const { config, storage } = useLessonkit();
2967
3348
  const persistEnabled = config.session?.persistCompoundState !== false;
2968
3349
  const initialIndex = useCompoundInitialIndex({
2969
3350
  courseId: config.courseId,
2970
3351
  compoundId: blockId,
2971
3352
  pageCount: pages.length,
2972
- persistEnabled
3353
+ persistEnabled,
3354
+ storage
2973
3355
  });
2974
3356
  const [index, setIndex] = useState11(initialIndex);
2975
3357
  const setIndexStable = useCallback8((i) => setIndex(i), []);
2976
- return /* @__PURE__ */ jsx16(CompoundProvider, { activePageIndex: index, onActivePageIndexChange: setIndexStable, children: /* @__PURE__ */ jsx16(
3358
+ useEffect14(() => {
3359
+ setIndex(initialIndex);
3360
+ }, [config.courseId, blockId, initialIndex]);
3361
+ return /* @__PURE__ */ jsx18(CompoundProvider, { activePageIndex: index, onActivePageIndexChange: setIndexStable, children: /* @__PURE__ */ jsx18(
2977
3362
  InteractiveBookInner,
2978
3363
  {
2979
3364
  ...props,
@@ -2988,17 +3373,249 @@ var InteractiveBook = forwardRef8(function InteractiveBook2(props, ref) {
2988
3373
  });
2989
3374
  setLessonkitBlockType(InteractiveBook, "InteractiveBook");
2990
3375
 
3376
+ // src/blocks/Slide.tsx
3377
+ import { useEffect as useEffect15 } from "react";
3378
+ import { jsx as jsx19, jsxs as jsxs12 } from "react/jsx-runtime";
3379
+ function Slide(props) {
3380
+ validateCompoundChildren("Slide", props.children);
3381
+ const { track } = useLessonkit();
3382
+ const lessonId = useEnclosingLessonId();
3383
+ useEffect15(() => {
3384
+ if (props.hidden || !lessonId || props.parentType) return;
3385
+ track(
3386
+ "compound_page_viewed",
3387
+ {
3388
+ blockId: props.blockId,
3389
+ pageIndex: props.slideIndex ?? 0,
3390
+ parentType: props.parentType
3391
+ },
3392
+ { lessonId }
3393
+ );
3394
+ }, [props.hidden, props.slideIndex, props.parentType, props.blockId, lessonId, track]);
3395
+ return /* @__PURE__ */ jsxs12(
3396
+ "section",
3397
+ {
3398
+ "aria-label": props.title ?? "Slide",
3399
+ "data-lk-block-id": props.blockId,
3400
+ "data-testid": `slide-${props.blockId}`,
3401
+ hidden: props.hidden ? true : void 0,
3402
+ children: [
3403
+ props.title ? /* @__PURE__ */ jsx19("h3", { children: props.title }) : null,
3404
+ /* @__PURE__ */ jsx19(CompoundPageIndexProvider, { pageIndex: props.slideIndex ?? 0, children: /* @__PURE__ */ jsx19("div", { children: props.children }) })
3405
+ ]
3406
+ }
3407
+ );
3408
+ }
3409
+ setLessonkitBlockType(Slide, "Slide");
3410
+
3411
+ // src/blocks/SlideDeck.tsx
3412
+ import React21, { forwardRef as forwardRef9, useCallback as useCallback9, useEffect as useEffect17, useMemo as useMemo15, useRef as useRef14, useState as useState12 } from "react";
3413
+
3414
+ // src/compound/useCompoundKeyboardNav.ts
3415
+ import { useEffect as useEffect16 } from "react";
3416
+ var INTERACTIVE_TAGS = /* @__PURE__ */ new Set(["INPUT", "TEXTAREA", "SELECT", "BUTTON"]);
3417
+ function isEditableTarget(target) {
3418
+ if (!(target instanceof HTMLElement)) return false;
3419
+ if (INTERACTIVE_TAGS.has(target.tagName)) return true;
3420
+ if (target.isContentEditable) return true;
3421
+ if (target.closest("[role='slider'], [role='listbox'], [data-lk-assessment-interactive]")) {
3422
+ return true;
3423
+ }
3424
+ return false;
3425
+ }
3426
+ function useCompoundKeyboardNav(opts) {
3427
+ const { containerRef, visibleIndex, pageCount, goNext, goPrev, setIndex } = opts;
3428
+ useEffect16(() => {
3429
+ const el = containerRef.current;
3430
+ if (!el || pageCount === 0) return;
3431
+ const onKeyDown = (event) => {
3432
+ if (!el.contains(document.activeElement) && document.activeElement !== document.body) {
3433
+ return;
3434
+ }
3435
+ if (isEditableTarget(event.target)) return;
3436
+ switch (event.key) {
3437
+ case "ArrowRight":
3438
+ case "ArrowDown":
3439
+ if (visibleIndex < pageCount - 1) {
3440
+ event.preventDefault();
3441
+ goNext();
3442
+ }
3443
+ break;
3444
+ case "ArrowLeft":
3445
+ case "ArrowUp":
3446
+ if (visibleIndex > 0) {
3447
+ event.preventDefault();
3448
+ goPrev();
3449
+ }
3450
+ break;
3451
+ case "Home":
3452
+ if (visibleIndex !== 0) {
3453
+ event.preventDefault();
3454
+ setIndex(0);
3455
+ }
3456
+ break;
3457
+ case "End":
3458
+ if (visibleIndex !== pageCount - 1) {
3459
+ event.preventDefault();
3460
+ setIndex(pageCount - 1);
3461
+ }
3462
+ break;
3463
+ default:
3464
+ break;
3465
+ }
3466
+ };
3467
+ el.addEventListener("keydown", onKeyDown);
3468
+ return () => el.removeEventListener("keydown", onKeyDown);
3469
+ }, [containerRef, visibleIndex, pageCount, goNext, goPrev, setIndex]);
3470
+ }
3471
+
3472
+ // src/blocks/SlideDeck.tsx
3473
+ import { jsx as jsx20, jsxs as jsxs13 } from "react/jsx-runtime";
3474
+ var SlideDeckInner = forwardRef9(function SlideDeckInner2(props, ref) {
3475
+ const { blockId, slides, index, setIndex, persistEnabled } = props;
3476
+ validateCompoundChildren("SlideDeck", slides);
3477
+ const { config, track } = useLessonkit();
3478
+ const lessonId = useEnclosingLessonId();
3479
+ const containerRef = useRef14(null);
3480
+ const { visibleIndex, goNext, goPrev, progress, ctx } = useCompoundShell({
3481
+ courseId: config.courseId,
3482
+ compoundId: blockId,
3483
+ pageCount: slides.length,
3484
+ index,
3485
+ setIndex,
3486
+ persistEnabled,
3487
+ ref
3488
+ });
3489
+ const setIndexStable = useCallback9((i) => setIndex(i), [setIndex]);
3490
+ useCompoundKeyboardNav({
3491
+ containerRef,
3492
+ visibleIndex,
3493
+ pageCount: slides.length,
3494
+ goNext,
3495
+ goPrev,
3496
+ setIndex: setIndexStable
3497
+ });
3498
+ const slideTitles = useMemo15(
3499
+ () => slides.map((slide) => slide.props.title),
3500
+ [slides]
3501
+ );
3502
+ useEffect17(() => {
3503
+ if (!lessonId || slides.length === 0) return;
3504
+ track(
3505
+ "slide_viewed",
3506
+ {
3507
+ blockId,
3508
+ slideIndex: visibleIndex,
3509
+ slideTitle: slideTitles[visibleIndex]
3510
+ },
3511
+ { lessonId }
3512
+ );
3513
+ }, [visibleIndex, blockId, lessonId, slides.length, slideTitles, track]);
3514
+ return /* @__PURE__ */ jsxs13(
3515
+ "section",
3516
+ {
3517
+ ref: containerRef,
3518
+ tabIndex: -1,
3519
+ "aria-label": props.title,
3520
+ "data-testid": "slide-deck",
3521
+ "data-lk-block-id": blockId,
3522
+ children: [
3523
+ /* @__PURE__ */ jsx20("h3", { children: props.title }),
3524
+ /* @__PURE__ */ jsxs13("p", { children: [
3525
+ "Slide ",
3526
+ progress.current,
3527
+ " of ",
3528
+ progress.total
3529
+ ] }),
3530
+ props.showDeckScore && ctx ? /* @__PURE__ */ jsxs13("p", { "data-testid": "deck-score", children: [
3531
+ "Score: ",
3532
+ Array.from(ctx.getHandles().values()).reduce((s, h) => s + h.getScore(), 0),
3533
+ " /",
3534
+ " ",
3535
+ Array.from(ctx.getHandles().values()).reduce((s, h) => s + h.getMaxScore(), 0)
3536
+ ] }) : null,
3537
+ /* @__PURE__ */ jsx20("div", { "data-testid": "slide-deck-slide", children: slides.map(
3538
+ (slide, i) => React21.cloneElement(slide, {
3539
+ key: slide.key ?? slide.props.blockId,
3540
+ hidden: i !== visibleIndex,
3541
+ slideIndex: i,
3542
+ parentType: "SlideDeck"
3543
+ })
3544
+ ) }),
3545
+ /* @__PURE__ */ jsxs13("nav", { "aria-label": "Slide navigation", children: [
3546
+ /* @__PURE__ */ jsx20(
3547
+ "button",
3548
+ {
3549
+ type: "button",
3550
+ "data-testid": "slide-prev",
3551
+ disabled: visibleIndex === 0 || slides.length === 0,
3552
+ onClick: goPrev,
3553
+ children: "Previous slide"
3554
+ }
3555
+ ),
3556
+ /* @__PURE__ */ jsx20(
3557
+ "button",
3558
+ {
3559
+ type: "button",
3560
+ "data-testid": "slide-next",
3561
+ disabled: visibleIndex >= slides.length - 1 || slides.length === 0,
3562
+ onClick: goNext,
3563
+ children: "Next slide"
3564
+ }
3565
+ )
3566
+ ] })
3567
+ ]
3568
+ }
3569
+ );
3570
+ });
3571
+ var SlideDeck = forwardRef9(function SlideDeck2(props, ref) {
3572
+ const blockId = useMemo15(
3573
+ () => normalizeComponentId(props.blockId, "blockId"),
3574
+ [props.blockId]
3575
+ );
3576
+ const slides = React21.Children.toArray(props.children).filter(
3577
+ React21.isValidElement
3578
+ );
3579
+ const { config, storage } = useLessonkit();
3580
+ const persistEnabled = config.session?.persistCompoundState !== false;
3581
+ const initialIndex = useCompoundInitialIndex({
3582
+ courseId: config.courseId,
3583
+ compoundId: blockId,
3584
+ pageCount: slides.length,
3585
+ persistEnabled,
3586
+ storage
3587
+ });
3588
+ const [index, setIndex] = useState12(initialIndex);
3589
+ const setIndexStable = useCallback9((i) => setIndex(i), []);
3590
+ useEffect17(() => {
3591
+ setIndex(initialIndex);
3592
+ }, [config.courseId, blockId, initialIndex]);
3593
+ return /* @__PURE__ */ jsx20(CompoundProvider, { activePageIndex: index, onActivePageIndexChange: setIndexStable, children: /* @__PURE__ */ jsx20(
3594
+ SlideDeckInner,
3595
+ {
3596
+ ...props,
3597
+ ref,
3598
+ blockId,
3599
+ slides,
3600
+ index,
3601
+ setIndex,
3602
+ persistEnabled
3603
+ }
3604
+ ) });
3605
+ });
3606
+ setLessonkitBlockType(SlideDeck, "SlideDeck");
3607
+
2991
3608
  // src/blocks/Accordion.tsx
2992
- import { useId as useId3, useState as useState12 } from "react";
2993
- import { jsx as jsx17, jsxs as jsxs12 } from "react/jsx-runtime";
3609
+ import { useId as useId4, useState as useState13 } from "react";
3610
+ import { jsx as jsx21, jsxs as jsxs14 } from "react/jsx-runtime";
2994
3611
  function Accordion(props) {
2995
3612
  if (isDevEnvironment4()) {
2996
3613
  validateAccordionSections(props.sections);
2997
3614
  }
2998
- const [open, setOpen] = useState12(/* @__PURE__ */ new Set());
3615
+ const [open, setOpen] = useState13(/* @__PURE__ */ new Set());
2999
3616
  const { track } = useLessonkit();
3000
3617
  const lessonId = useEnclosingLessonId();
3001
- const baseId = useId3();
3618
+ const baseId = useId4();
3002
3619
  const toggle = (sectionId) => {
3003
3620
  setOpen((prev) => {
3004
3621
  const next = new Set(prev);
@@ -3013,12 +3630,12 @@ function Accordion(props) {
3013
3630
  return next;
3014
3631
  });
3015
3632
  };
3016
- return /* @__PURE__ */ jsx17("section", { "aria-label": "Accordion", "data-lk-block-id": props.blockId, "data-testid": "accordion", children: props.sections.map((section) => {
3633
+ return /* @__PURE__ */ jsx21("section", { "aria-label": "Accordion", "data-lk-block-id": props.blockId, "data-testid": "accordion", children: props.sections.map((section) => {
3017
3634
  const expanded = open.has(section.id);
3018
3635
  const panelId = `${baseId}-${section.id}`;
3019
3636
  const triggerId = `${baseId}-trigger-${section.id}`;
3020
- return /* @__PURE__ */ jsxs12("div", { "data-testid": `accordion-section-${section.id}`, children: [
3021
- /* @__PURE__ */ jsx17("h4", { children: /* @__PURE__ */ jsx17(
3637
+ return /* @__PURE__ */ jsxs14("div", { "data-testid": `accordion-section-${section.id}`, children: [
3638
+ /* @__PURE__ */ jsx21("h4", { children: /* @__PURE__ */ jsx21(
3022
3639
  "button",
3023
3640
  {
3024
3641
  id: triggerId,
@@ -3030,28 +3647,28 @@ function Accordion(props) {
3030
3647
  children: section.title
3031
3648
  }
3032
3649
  ) }),
3033
- expanded ? /* @__PURE__ */ jsx17("div", { id: panelId, role: "region", "aria-labelledby": triggerId, children: section.content }) : null
3650
+ expanded ? /* @__PURE__ */ jsx21("div", { id: panelId, role: "region", "aria-labelledby": triggerId, children: section.content }) : null
3034
3651
  ] }, section.id);
3035
3652
  }) });
3036
3653
  }
3037
3654
  setLessonkitBlockType(Accordion, "Accordion");
3038
3655
 
3039
3656
  // src/blocks/DialogCards.tsx
3040
- import { useState as useState13 } from "react";
3041
- import { jsx as jsx18, jsxs as jsxs13 } from "react/jsx-runtime";
3657
+ import { useState as useState14 } from "react";
3658
+ import { jsx as jsx22, jsxs as jsxs15 } from "react/jsx-runtime";
3042
3659
  function DialogCards(props) {
3043
- const [index, setIndex] = useState13(0);
3044
- const [flipped, setFlipped] = useState13(false);
3660
+ const [index, setIndex] = useState14(0);
3661
+ const [flipped, setFlipped] = useState14(false);
3045
3662
  const card = props.cards[index];
3046
3663
  if (!card) return null;
3047
- return /* @__PURE__ */ jsxs13("section", { "aria-label": "Dialog cards", "data-lk-block-id": props.blockId, "data-testid": "dialog-cards", children: [
3048
- /* @__PURE__ */ jsxs13("p", { children: [
3664
+ return /* @__PURE__ */ jsxs15("section", { "aria-label": "Dialog cards", "data-lk-block-id": props.blockId, "data-testid": "dialog-cards", children: [
3665
+ /* @__PURE__ */ jsxs15("p", { children: [
3049
3666
  "Card ",
3050
3667
  index + 1,
3051
3668
  " of ",
3052
3669
  props.cards.length
3053
3670
  ] }),
3054
- /* @__PURE__ */ jsx18(
3671
+ /* @__PURE__ */ jsx22(
3055
3672
  "button",
3056
3673
  {
3057
3674
  type: "button",
@@ -3062,8 +3679,8 @@ function DialogCards(props) {
3062
3679
  children: flipped ? card.back : card.front
3063
3680
  }
3064
3681
  ),
3065
- /* @__PURE__ */ jsxs13("nav", { "aria-label": "Card navigation", children: [
3066
- /* @__PURE__ */ jsx18(
3682
+ /* @__PURE__ */ jsxs15("nav", { "aria-label": "Card navigation", children: [
3683
+ /* @__PURE__ */ jsx22(
3067
3684
  "button",
3068
3685
  {
3069
3686
  type: "button",
@@ -3076,7 +3693,7 @@ function DialogCards(props) {
3076
3693
  children: "Previous"
3077
3694
  }
3078
3695
  ),
3079
- /* @__PURE__ */ jsx18(
3696
+ /* @__PURE__ */ jsx22(
3080
3697
  "button",
3081
3698
  {
3082
3699
  type: "button",
@@ -3095,11 +3712,11 @@ function DialogCards(props) {
3095
3712
  setLessonkitBlockType(DialogCards, "DialogCards");
3096
3713
 
3097
3714
  // src/blocks/Flashcards.tsx
3098
- import { useState as useState14 } from "react";
3099
- import { jsx as jsx19, jsxs as jsxs14 } from "react/jsx-runtime";
3715
+ import { useState as useState15 } from "react";
3716
+ import { jsx as jsx23, jsxs as jsxs16 } from "react/jsx-runtime";
3100
3717
  function Flashcards(props) {
3101
- const [index, setIndex] = useState14(0);
3102
- const [face, setFace] = useState14("front");
3718
+ const [index, setIndex] = useState15(0);
3719
+ const [face, setFace] = useState15("front");
3103
3720
  const { track } = useLessonkit();
3104
3721
  const lessonId = useEnclosingLessonId();
3105
3722
  const card = props.cards[index];
@@ -3113,10 +3730,10 @@ function Flashcards(props) {
3113
3730
  lessonId ? { lessonId } : void 0
3114
3731
  );
3115
3732
  };
3116
- return /* @__PURE__ */ jsxs14("section", { "aria-label": "Flashcards", "data-lk-block-id": props.blockId, "data-testid": "flashcards", children: [
3117
- /* @__PURE__ */ jsx19("button", { type: "button", "data-testid": "flashcard-flip", onClick: flip, style: { minHeight: "6rem", width: "100%" }, children: face === "front" ? card.front : card.back }),
3118
- props.selfScore ? /* @__PURE__ */ jsx19("p", { "data-testid": "flashcard-self-score", children: "Self-score mode enabled" }) : null,
3119
- /* @__PURE__ */ jsx19(
3733
+ return /* @__PURE__ */ jsxs16("section", { "aria-label": "Flashcards", "data-lk-block-id": props.blockId, "data-testid": "flashcards", children: [
3734
+ /* @__PURE__ */ jsx23("button", { type: "button", "data-testid": "flashcard-flip", onClick: flip, style: { minHeight: "6rem", width: "100%" }, children: face === "front" ? card.front : card.back }),
3735
+ props.selfScore ? /* @__PURE__ */ jsx23("p", { "data-testid": "flashcard-self-score", children: "Self-score mode enabled" }) : null,
3736
+ /* @__PURE__ */ jsx23(
3120
3737
  "button",
3121
3738
  {
3122
3739
  type: "button",
@@ -3134,10 +3751,10 @@ function Flashcards(props) {
3134
3751
  setLessonkitBlockType(Flashcards, "Flashcards");
3135
3752
 
3136
3753
  // src/blocks/ImageHotspots.tsx
3137
- import { useState as useState15 } from "react";
3138
- import { jsx as jsx20, jsxs as jsxs15 } from "react/jsx-runtime";
3754
+ import { useState as useState16 } from "react";
3755
+ import { jsx as jsx24, jsxs as jsxs17 } from "react/jsx-runtime";
3139
3756
  function ImageHotspots(props) {
3140
- const [active, setActive] = useState15(null);
3757
+ const [active, setActive] = useState16(null);
3141
3758
  const { track } = useLessonkit();
3142
3759
  const lessonId = useEnclosingLessonId();
3143
3760
  const open = (hotspotId) => {
@@ -3148,10 +3765,10 @@ function ImageHotspots(props) {
3148
3765
  lessonId ? { lessonId } : void 0
3149
3766
  );
3150
3767
  };
3151
- return /* @__PURE__ */ jsxs15("section", { "aria-label": "Image hotspots", "data-lk-block-id": props.blockId, "data-testid": "image-hotspots", children: [
3152
- /* @__PURE__ */ jsxs15("div", { style: { position: "relative", display: "inline-block" }, children: [
3153
- /* @__PURE__ */ jsx20("img", { src: props.src, alt: props.alt, style: { maxWidth: "100%" } }),
3154
- props.hotspots.map((h) => /* @__PURE__ */ jsx20(
3768
+ return /* @__PURE__ */ jsxs17("section", { "aria-label": "Image hotspots", "data-lk-block-id": props.blockId, "data-testid": "image-hotspots", children: [
3769
+ /* @__PURE__ */ jsxs17("div", { style: { position: "relative", display: "inline-block" }, children: [
3770
+ /* @__PURE__ */ jsx24("img", { src: props.src, alt: props.alt, style: { maxWidth: "100%" } }),
3771
+ props.hotspots.map((h) => /* @__PURE__ */ jsx24(
3155
3772
  "button",
3156
3773
  {
3157
3774
  type: "button",
@@ -3170,19 +3787,19 @@ function ImageHotspots(props) {
3170
3787
  h.id
3171
3788
  ))
3172
3789
  ] }),
3173
- active ? /* @__PURE__ */ jsxs15("div", { role: "dialog", "aria-label": "Hotspot details", "data-testid": "hotspot-popover", children: [
3790
+ active ? /* @__PURE__ */ jsxs17("div", { role: "dialog", "aria-label": "Hotspot details", "data-testid": "hotspot-popover", children: [
3174
3791
  props.hotspots.find((h) => h.id === active)?.content,
3175
- /* @__PURE__ */ jsx20("button", { type: "button", onClick: () => setActive(null), children: "Close" })
3792
+ /* @__PURE__ */ jsx24("button", { type: "button", onClick: () => setActive(null), children: "Close" })
3176
3793
  ] }) : null
3177
3794
  ] });
3178
3795
  }
3179
3796
  setLessonkitBlockType(ImageHotspots, "ImageHotspots");
3180
3797
 
3181
3798
  // src/blocks/ImageSlider.tsx
3182
- import { useState as useState16 } from "react";
3183
- import { jsx as jsx21, jsxs as jsxs16 } from "react/jsx-runtime";
3799
+ import { useState as useState17 } from "react";
3800
+ import { jsx as jsx25, jsxs as jsxs18 } from "react/jsx-runtime";
3184
3801
  function ImageSlider(props) {
3185
- const [index, setIndex] = useState16(0);
3802
+ const [index, setIndex] = useState17(0);
3186
3803
  const { track } = useLessonkit();
3187
3804
  const lessonId = useEnclosingLessonId();
3188
3805
  const slide = props.slides[index];
@@ -3195,11 +3812,11 @@ function ImageSlider(props) {
3195
3812
  lessonId ? { lessonId } : void 0
3196
3813
  );
3197
3814
  };
3198
- return /* @__PURE__ */ jsxs16("section", { "aria-label": "Image slider", "data-lk-block-id": props.blockId, "data-testid": "image-slider", children: [
3199
- /* @__PURE__ */ jsx21("img", { src: slide.src, alt: slide.alt, style: { maxWidth: "100%" } }),
3200
- slide.caption ? /* @__PURE__ */ jsx21("p", { children: slide.caption }) : null,
3201
- /* @__PURE__ */ jsxs16("nav", { "aria-label": "Slide navigation", children: [
3202
- /* @__PURE__ */ jsx21(
3815
+ return /* @__PURE__ */ jsxs18("section", { "aria-label": "Image slider", "data-lk-block-id": props.blockId, "data-testid": "image-slider", children: [
3816
+ /* @__PURE__ */ jsx25("img", { src: slide.src, alt: slide.alt, style: { maxWidth: "100%" } }),
3817
+ slide.caption ? /* @__PURE__ */ jsx25("p", { children: slide.caption }) : null,
3818
+ /* @__PURE__ */ jsxs18("nav", { "aria-label": "Slide navigation", children: [
3819
+ /* @__PURE__ */ jsx25(
3203
3820
  "button",
3204
3821
  {
3205
3822
  type: "button",
@@ -3209,12 +3826,12 @@ function ImageSlider(props) {
3209
3826
  children: "Previous"
3210
3827
  }
3211
3828
  ),
3212
- /* @__PURE__ */ jsxs16("span", { children: [
3829
+ /* @__PURE__ */ jsxs18("span", { children: [
3213
3830
  index + 1,
3214
3831
  " / ",
3215
3832
  props.slides.length
3216
3833
  ] }),
3217
- /* @__PURE__ */ jsx21(
3834
+ /* @__PURE__ */ jsx25(
3218
3835
  "button",
3219
3836
  {
3220
3837
  type: "button",
@@ -3230,16 +3847,21 @@ function ImageSlider(props) {
3230
3847
  setLessonkitBlockType(ImageSlider, "ImageSlider");
3231
3848
 
3232
3849
  // src/blocks/FindHotspot.tsx
3233
- import { forwardRef as forwardRef9, useMemo as useMemo15, useState as useState17 } from "react";
3234
- import { jsx as jsx22, jsxs as jsxs17 } from "react/jsx-runtime";
3850
+ import { forwardRef as forwardRef10, useEffect as useEffect18, useMemo as useMemo16, useState as useState18 } from "react";
3851
+ import { jsx as jsx26, jsxs as jsxs19 } from "react/jsx-runtime";
3235
3852
  var INTERACTION6 = "findHotspot";
3236
3853
  function FindHotspotInner(props, ref) {
3237
- const checkId = useMemo15(() => normalizeComponentId(props.checkId, "checkId"), [props.checkId]);
3238
- const [selected, setSelected] = useState17(null);
3239
- const [checked, setChecked] = useState17(false);
3854
+ const checkId = useMemo16(() => normalizeComponentId(props.checkId, "checkId"), [props.checkId]);
3855
+ const [selected, setSelected] = useState18(null);
3856
+ const [checked, setChecked] = useState18(false);
3240
3857
  const assessment = useAssessmentState(props.enclosingLessonId);
3858
+ const targetIdsKey = props.targets.map((t) => t.id).join("\0");
3859
+ useEffect18(() => {
3860
+ setSelected(null);
3861
+ setChecked(false);
3862
+ }, [checkId, props.correctTargetId, targetIdsKey]);
3241
3863
  const correct = selected === props.correctTargetId;
3242
- const handle = useMemo15(
3864
+ const handle = useMemo16(
3243
3865
  () => buildAssessmentHandle({
3244
3866
  checkId,
3245
3867
  getScore: () => checked && correct ? 1 : 0,
@@ -3261,15 +3883,22 @@ function FindHotspotInner(props, ref) {
3261
3883
  getCurrentState: () => ({ selected, checked }),
3262
3884
  resume: (state) => {
3263
3885
  const nextSelected = readStringField(state, "selected");
3264
- if (typeof nextSelected === "string") setSelected(nextSelected);
3886
+ if (typeof nextSelected === "string" || nextSelected === null) {
3887
+ const valid = nextSelected === null || props.targets.some((t) => t.id === nextSelected);
3888
+ setSelected(valid ? nextSelected : null);
3889
+ }
3265
3890
  readBooleanStateField(state, "checked", setChecked);
3266
3891
  }
3267
3892
  }),
3268
- [checkId, selected, checked, correct, props.correctTargetId]
3893
+ [checkId, selected, checked, correct, props.correctTargetId, props.targets]
3269
3894
  );
3270
3895
  useAssessmentHandleRegistration(checkId, handle, ref);
3896
+ const selectTarget = (id) => {
3897
+ setSelected(id);
3898
+ setChecked(false);
3899
+ };
3271
3900
  const submit = () => {
3272
- if (!selected) return;
3901
+ if (!selected || checked) return;
3273
3902
  setChecked(true);
3274
3903
  assessment.answer({
3275
3904
  checkId,
@@ -3283,14 +3912,14 @@ function FindHotspotInner(props, ref) {
3283
3912
  interactionType: INTERACTION6,
3284
3913
  score: 1,
3285
3914
  maxScore: 1,
3286
- passingScore: props.passingScore
3915
+ passingScore: props.passingScore ?? 1
3287
3916
  });
3288
3917
  }
3289
3918
  };
3290
- return /* @__PURE__ */ jsxs17("section", { "aria-label": "Find the hotspot", "data-lk-check-id": checkId, "data-testid": "find-hotspot", children: [
3291
- /* @__PURE__ */ jsxs17("div", { style: { position: "relative", display: "inline-block" }, children: [
3292
- /* @__PURE__ */ jsx22("img", { src: props.src, alt: props.alt, style: { maxWidth: "100%" } }),
3293
- props.targets.map((t) => /* @__PURE__ */ jsx22(
3919
+ return /* @__PURE__ */ jsxs19("section", { "aria-label": "Find the hotspot", "data-lk-check-id": checkId, "data-testid": "find-hotspot", children: [
3920
+ /* @__PURE__ */ jsxs19("div", { style: { position: "relative", display: "inline-block" }, children: [
3921
+ /* @__PURE__ */ jsx26("img", { src: props.src, alt: props.alt, style: { maxWidth: "100%" } }),
3922
+ props.targets.map((t) => /* @__PURE__ */ jsx26(
3294
3923
  "button",
3295
3924
  {
3296
3925
  type: "button",
@@ -3303,30 +3932,30 @@ function FindHotspotInner(props, ref) {
3303
3932
  top: `${t.y}%`,
3304
3933
  transform: "translate(-50%, -50%)"
3305
3934
  },
3306
- onClick: () => setSelected(t.id),
3935
+ onClick: () => selectTarget(t.id),
3307
3936
  children: t.label
3308
3937
  },
3309
3938
  t.id
3310
3939
  ))
3311
3940
  ] }),
3312
- /* @__PURE__ */ jsx22("button", { type: "button", "data-testid": "check-hotspot", disabled: !selected, onClick: submit, children: "Check" }),
3313
- checked ? /* @__PURE__ */ jsx22("p", { role: "status", children: correct ? "Correct" : "Try again" }) : null
3941
+ /* @__PURE__ */ jsx26("button", { type: "button", "data-testid": "check-hotspot", disabled: !selected, onClick: submit, children: "Check" }),
3942
+ checked ? /* @__PURE__ */ jsx26("p", { role: "status", children: correct ? "Correct" : "Try again" }) : null
3314
3943
  ] });
3315
3944
  }
3316
- var FindHotspotInnerForwarded = forwardRef9(FindHotspotInner);
3317
- var FindHotspot = forwardRef9(function FindHotspot2(props, ref) {
3318
- return /* @__PURE__ */ jsx22(AssessmentLessonGuard, { blockLabel: "FindHotspot", checkId: props.checkId, children: (enclosingLessonId) => /* @__PURE__ */ jsx22(FindHotspotInnerForwarded, { ...props, enclosingLessonId, ref }) });
3945
+ var FindHotspotInnerForwarded = forwardRef10(FindHotspotInner);
3946
+ var FindHotspot = forwardRef10(function FindHotspot2(props, ref) {
3947
+ return /* @__PURE__ */ jsx26(AssessmentLessonGuard, { blockLabel: "FindHotspot", checkId: props.checkId, children: (enclosingLessonId) => /* @__PURE__ */ jsx26(FindHotspotInnerForwarded, { ...props, enclosingLessonId, ref }) });
3319
3948
  });
3320
3949
  setLessonkitBlockType(FindHotspot, "FindHotspot");
3321
3950
 
3322
3951
  // src/blocks/FindMultipleHotspots.tsx
3323
- import { forwardRef as forwardRef10, useMemo as useMemo16, useState as useState18 } from "react";
3324
- import { jsx as jsx23, jsxs as jsxs18 } from "react/jsx-runtime";
3952
+ import { forwardRef as forwardRef11, useMemo as useMemo17, useState as useState19 } from "react";
3953
+ import { jsx as jsx27, jsxs as jsxs20 } from "react/jsx-runtime";
3325
3954
  var INTERACTION7 = "findMultipleHotspots";
3326
3955
  function FindMultipleHotspotsInner(props, ref) {
3327
- const checkId = useMemo16(() => normalizeComponentId(props.checkId, "checkId"), [props.checkId]);
3328
- const [selected, setSelected] = useState18(/* @__PURE__ */ new Set());
3329
- const [checked, setChecked] = useState18(false);
3956
+ const checkId = useMemo17(() => normalizeComponentId(props.checkId, "checkId"), [props.checkId]);
3957
+ const [selected, setSelected] = useState19(/* @__PURE__ */ new Set());
3958
+ const [checked, setChecked] = useState19(false);
3330
3959
  const assessment = useAssessmentState(props.enclosingLessonId);
3331
3960
  const toggle = (id) => {
3332
3961
  setSelected((prev) => {
@@ -3335,9 +3964,10 @@ function FindMultipleHotspotsInner(props, ref) {
3335
3964
  else next.add(id);
3336
3965
  return next;
3337
3966
  });
3967
+ setChecked(false);
3338
3968
  };
3339
3969
  const correct = selected.size === props.correctTargetIds.length && props.correctTargetIds.every((id) => selected.has(id));
3340
- const handle = useMemo16(
3970
+ const handle = useMemo17(
3341
3971
  () => buildAssessmentHandle({
3342
3972
  checkId,
3343
3973
  getScore: () => checked && correct ? 1 : 0,
@@ -3367,7 +3997,7 @@ function FindMultipleHotspotsInner(props, ref) {
3367
3997
  );
3368
3998
  useAssessmentHandleRegistration(checkId, handle, ref);
3369
3999
  const submit = () => {
3370
- if (selected.size === 0) return;
4000
+ if (selected.size === 0 || checked) return;
3371
4001
  setChecked(true);
3372
4002
  assessment.answer({
3373
4003
  checkId,
@@ -3381,14 +4011,14 @@ function FindMultipleHotspotsInner(props, ref) {
3381
4011
  interactionType: INTERACTION7,
3382
4012
  score: 1,
3383
4013
  maxScore: 1,
3384
- passingScore: props.passingScore
4014
+ passingScore: props.passingScore ?? 1
3385
4015
  });
3386
4016
  }
3387
4017
  };
3388
- return /* @__PURE__ */ jsxs18("section", { "aria-label": "Find multiple hotspots", "data-lk-check-id": checkId, "data-testid": "find-multiple-hotspots", children: [
3389
- /* @__PURE__ */ jsxs18("div", { style: { position: "relative", display: "inline-block" }, children: [
3390
- /* @__PURE__ */ jsx23("img", { src: props.src, alt: props.alt, style: { maxWidth: "100%" } }),
3391
- props.targets.map((t) => /* @__PURE__ */ jsx23(
4018
+ return /* @__PURE__ */ jsxs20("section", { "aria-label": "Find multiple hotspots", "data-lk-check-id": checkId, "data-testid": "find-multiple-hotspots", children: [
4019
+ /* @__PURE__ */ jsxs20("div", { style: { position: "relative", display: "inline-block" }, children: [
4020
+ /* @__PURE__ */ jsx27("img", { src: props.src, alt: props.alt, style: { maxWidth: "100%" } }),
4021
+ props.targets.map((t) => /* @__PURE__ */ jsx27(
3392
4022
  "button",
3393
4023
  {
3394
4024
  type: "button",
@@ -3407,14 +4037,14 @@ function FindMultipleHotspotsInner(props, ref) {
3407
4037
  t.id
3408
4038
  ))
3409
4039
  ] }),
3410
- /* @__PURE__ */ jsx23("button", { type: "button", "data-testid": "check-hotspots", disabled: selected.size === 0, onClick: submit, children: "Check" }),
3411
- checked ? /* @__PURE__ */ jsx23("p", { role: "status", children: correct ? "Correct" : "Try again" }) : null
4040
+ /* @__PURE__ */ jsx27("button", { type: "button", "data-testid": "check-hotspots", disabled: selected.size === 0, onClick: submit, children: "Check" }),
4041
+ checked ? /* @__PURE__ */ jsx27("p", { role: "status", children: correct ? "Correct" : "Try again" }) : null
3412
4042
  ] });
3413
4043
  }
3414
- var FindMultipleHotspotsInnerForwarded = forwardRef10(FindMultipleHotspotsInner);
3415
- var FindMultipleHotspots = forwardRef10(
4044
+ var FindMultipleHotspotsInnerForwarded = forwardRef11(FindMultipleHotspotsInner);
4045
+ var FindMultipleHotspots = forwardRef11(
3416
4046
  function FindMultipleHotspots2(props, ref) {
3417
- return /* @__PURE__ */ jsx23(AssessmentLessonGuard, { blockLabel: "FindMultipleHotspots", checkId: props.checkId, children: (enclosingLessonId) => /* @__PURE__ */ jsx23(FindMultipleHotspotsInnerForwarded, { ...props, enclosingLessonId, ref }) });
4047
+ return /* @__PURE__ */ jsx27(AssessmentLessonGuard, { blockLabel: "FindMultipleHotspots", checkId: props.checkId, children: (enclosingLessonId) => /* @__PURE__ */ jsx27(FindMultipleHotspotsInnerForwarded, { ...props, enclosingLessonId, ref }) });
3418
4048
  }
3419
4049
  );
3420
4050
  setLessonkitBlockType(FindMultipleHotspots, "FindMultipleHotspots");
@@ -3431,14 +4061,14 @@ import {
3431
4061
  } from "@lessonkit/core";
3432
4062
 
3433
4063
  // src/theme/ThemeProvider.tsx
3434
- import React25, {
3435
- createContext as createContext4,
3436
- useCallback as useCallback9,
3437
- useContext as useContext4,
4064
+ import React29, {
4065
+ createContext as createContext6,
4066
+ useCallback as useCallback10,
4067
+ useContext as useContext8,
3438
4068
  useLayoutEffect as useLayoutEffect2,
3439
- useMemo as useMemo17,
3440
- useRef as useRef12,
3441
- useState as useState19
4069
+ useMemo as useMemo18,
4070
+ useRef as useRef15,
4071
+ useState as useState20
3442
4072
  } from "react";
3443
4073
  import {
3444
4074
  brandThemeOverrides,
@@ -3465,11 +4095,11 @@ function applyCssVariables(target, vars, previousKeys) {
3465
4095
  }
3466
4096
 
3467
4097
  // src/theme/ThemeProvider.tsx
3468
- import { jsx as jsx24 } from "react/jsx-runtime";
3469
- var ThemeContext = createContext4(null);
4098
+ import { jsx as jsx28 } from "react/jsx-runtime";
4099
+ var ThemeContext = createContext6(null);
3470
4100
  var useIsoLayoutEffect2 = (
3471
4101
  /* v8 ignore next -- SSR uses useEffect when window is unavailable */
3472
- typeof window !== "undefined" ? useLayoutEffect2 : React25.useEffect
4102
+ typeof window !== "undefined" ? useLayoutEffect2 : React29.useEffect
3473
4103
  );
3474
4104
  function getSystemMode() {
3475
4105
  if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
@@ -3488,7 +4118,7 @@ function ThemeProvider(props) {
3488
4118
  const preset = props.preset ?? "default";
3489
4119
  const mode = props.mode ?? "light";
3490
4120
  const targetKind = props.target ?? "document";
3491
- const [resolvedMode, setResolvedMode] = useState19(
4121
+ const [resolvedMode, setResolvedMode] = useState20(
3492
4122
  () => mode === "system" ? getSystemMode() : mode
3493
4123
  );
3494
4124
  useIsoLayoutEffect2(() => {
@@ -3504,20 +4134,20 @@ function ThemeProvider(props) {
3504
4134
  return () => mq.removeEventListener("change", onChange);
3505
4135
  }, [mode]);
3506
4136
  const dataTheme = mode === "system" ? resolvedMode : mode === "dark" ? "dark" : "light";
3507
- const effectiveTheme = useMemo17(() => {
4137
+ const effectiveTheme = useMemo18(() => {
3508
4138
  const modeBase = resolveModeBase(mode, dataTheme);
3509
4139
  const base = preset === "default" ? modeBase : preset === "brand" ? mergeThemes(modeBase, brandThemeOverrides) : mergeThemes(modeBase, getPresetTheme(preset));
3510
4140
  return mergeThemes(base, props.theme ?? {});
3511
4141
  }, [preset, mode, dataTheme, props.theme]);
3512
- const hostRef = useRef12(null);
3513
- const appliedKeysRef = useRef12(/* @__PURE__ */ new Set());
4142
+ const hostRef = useRef15(null);
4143
+ const appliedKeysRef = useRef15(/* @__PURE__ */ new Set());
3514
4144
  useIsoLayoutEffect2(() => {
3515
4145
  if (targetKind === "document" && typeof document !== "undefined") {
3516
4146
  document.documentElement.setAttribute("data-lk-theme", dataTheme);
3517
4147
  return () => document.documentElement.removeAttribute("data-lk-theme");
3518
4148
  }
3519
4149
  }, [targetKind, dataTheme]);
3520
- const inject = useCallback9(() => {
4150
+ const inject = useCallback10(() => {
3521
4151
  const vars = themeToCssVariables(effectiveTheme);
3522
4152
  const el = targetKind === "document" && typeof document !== "undefined" ? document.documentElement : hostRef.current;
3523
4153
  if (!el) return;
@@ -3534,7 +4164,7 @@ function ThemeProvider(props) {
3534
4164
  appliedKeysRef.current = /* @__PURE__ */ new Set();
3535
4165
  };
3536
4166
  }, [inject, targetKind]);
3537
- const value = useMemo17(
4167
+ const value = useMemo18(
3538
4168
  () => ({
3539
4169
  theme: effectiveTheme,
3540
4170
  preset,
@@ -3544,12 +4174,12 @@ function ThemeProvider(props) {
3544
4174
  [effectiveTheme, preset, mode, dataTheme]
3545
4175
  );
3546
4176
  if (targetKind === "document") {
3547
- return /* @__PURE__ */ jsx24(ThemeContext.Provider, { value, children: /* @__PURE__ */ jsx24("div", { "data-lk-theme": dataTheme, style: { display: "contents" }, children: props.children }) });
4177
+ return /* @__PURE__ */ jsx28(ThemeContext.Provider, { value, children: /* @__PURE__ */ jsx28("div", { "data-lk-theme": dataTheme, style: { display: "contents" }, children: props.children }) });
3548
4178
  }
3549
- return /* @__PURE__ */ jsx24(ThemeContext.Provider, { value, children: /* @__PURE__ */ jsx24("div", { ref: hostRef, "data-lk-theme": dataTheme, children: props.children }) });
4179
+ return /* @__PURE__ */ jsx28(ThemeContext.Provider, { value, children: /* @__PURE__ */ jsx28("div", { ref: hostRef, "data-lk-theme": dataTheme, children: props.children }) });
3550
4180
  }
3551
4181
  function useTheme() {
3552
- const ctx = useContext4(ThemeContext);
4182
+ const ctx = useContext8(ThemeContext);
3553
4183
  if (!ctx) {
3554
4184
  throw new Error("useTheme must be used within a ThemeProvider");
3555
4185
  }
@@ -3561,9 +4191,18 @@ import {
3561
4191
  ASSESSMENT_SEQUENCE_ALLOWED_CHILD_TYPES,
3562
4192
  INTERACTIVE_BOOK_ALLOWED_CHILD_TYPES,
3563
4193
  PAGE_ALLOWED_CHILD_TYPES,
4194
+ SLIDE_ALLOWED_CHILD_TYPES,
4195
+ SLIDE_DECK_ALLOWED_CHILD_TYPES,
3564
4196
  COMPOUND_MAX_NESTING_DEPTH as COMPOUND_MAX_NESTING_DEPTH2
3565
4197
  } from "@lessonkit/core";
3566
- var COMPOUND_PARENTS = ["Lesson", "Page", "InteractiveBook", "AssessmentSequence"];
4198
+ var COMPOUND_PARENTS = [
4199
+ "Lesson",
4200
+ "Page",
4201
+ "InteractiveBook",
4202
+ "Slide",
4203
+ "SlideDeck",
4204
+ "AssessmentSequence"
4205
+ ];
3567
4206
  function extendParents(entry) {
3568
4207
  if (!entry.parentConstraints?.length) return entry;
3569
4208
  const merged = /* @__PURE__ */ new Set([...entry.parentConstraints, ...COMPOUND_PARENTS]);
@@ -3667,6 +4306,53 @@ var v3CompoundAndContentEntries = [
3667
4306
  theming: { surface: "global-inherit", stylingNotes: "Book chrome." },
3668
4307
  telemetry: { emits: ["book_page_viewed"], requiresActiveLesson: true }
3669
4308
  },
4309
+ {
4310
+ type: "Slide",
4311
+ category: "container",
4312
+ compoundContract: true,
4313
+ h5pMachineName: "H5P.CoursePresentation",
4314
+ h5pAlias: "Course Presentation slide",
4315
+ description: "Single slide row in a SlideDeck. Planned allowlist expansion: Video, Summary.",
4316
+ allowedChildTypes: [...SLIDE_ALLOWED_CHILD_TYPES],
4317
+ maxNestingDepth: COMPOUND_MAX_NESTING_DEPTH2.Slide,
4318
+ props: [
4319
+ { name: "blockId", type: "BlockId", required: true, description: "Stable block id." },
4320
+ { name: "title", type: "string", required: false, description: "Slide title." },
4321
+ { name: "children", type: "ReactNode", required: true, description: "Slide content." }
4322
+ ],
4323
+ requiredIds: [],
4324
+ optionalIds: ["blockId"],
4325
+ parentConstraints: ["SlideDeck"],
4326
+ a11y: { element: "section", ariaLabel: "Slide", keyboard: "N/A", notes: "H5P Course Presentation slide row." },
4327
+ theming: { surface: "global-inherit", stylingNotes: "Container." },
4328
+ telemetry: { emits: ["compound_page_viewed"], requiresActiveLesson: true }
4329
+ },
4330
+ {
4331
+ type: "SlideDeck",
4332
+ category: "container",
4333
+ compoundContract: true,
4334
+ h5pMachineName: "H5P.CoursePresentation",
4335
+ h5pAlias: "Course Presentation",
4336
+ description: "Multi-slide presentation with keyboard navigation.",
4337
+ allowedChildTypes: [...SLIDE_DECK_ALLOWED_CHILD_TYPES],
4338
+ maxNestingDepth: COMPOUND_MAX_NESTING_DEPTH2.SlideDeck,
4339
+ props: [
4340
+ { name: "blockId", type: "BlockId", required: true, description: "Stable block id." },
4341
+ { name: "title", type: "string", required: true, description: "Deck title." },
4342
+ { name: "showDeckScore", type: "boolean", required: false, description: "Show aggregate score." },
4343
+ { name: "children", type: "Slide[]", required: true, description: "Slides." }
4344
+ ],
4345
+ requiredIds: ["blockId"],
4346
+ parentConstraints: ["Lesson"],
4347
+ a11y: {
4348
+ element: "section",
4349
+ ariaLabel: "Slide deck",
4350
+ keyboard: "Arrow keys, Home, End, Previous/Next slide buttons.",
4351
+ notes: "H5P Course Presentation equivalent."
4352
+ },
4353
+ theming: { surface: "global-inherit", stylingNotes: "Deck chrome." },
4354
+ telemetry: { emits: ["slide_viewed"], requiresActiveLesson: true }
4355
+ },
3670
4356
  {
3671
4357
  type: "Accordion",
3672
4358
  category: "content",
@@ -4212,6 +4898,8 @@ export {
4212
4898
  Quiz,
4213
4899
  Reflection,
4214
4900
  Scenario,
4901
+ Slide,
4902
+ SlideDeck,
4215
4903
  Text,
4216
4904
  ThemeProvider,
4217
4905
  TrueFalse,