@lessonkit/react 1.4.0 → 1.5.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/testing.cjs CHANGED
@@ -186,9 +186,6 @@ var import_core10 = require("@lessonkit/core");
186
186
  // src/runtime/observability.ts
187
187
  var import_xapi = require("@lessonkit/xapi");
188
188
 
189
- // src/provider/useLessonkitProviderRuntime.ts
190
- var import_xapi5 = require("@lessonkit/xapi");
191
-
192
189
  // src/runtime/emitTelemetry.ts
193
190
  var import_core4 = require("@lessonkit/core");
194
191
 
@@ -199,45 +196,56 @@ var import_xapi2 = require("@lessonkit/xapi");
199
196
  // src/runtime/lxpackBridge.ts
200
197
  var import_bridge = require("@lessonkit/lxpack/bridge");
201
198
 
202
- // src/runtime/ports.ts
203
- var import_core5 = require("@lessonkit/core");
199
+ // src/runtime/courseStartedPipeline.ts
200
+ var import_xapi3 = require("@lessonkit/xapi");
204
201
 
205
- // src/provider/useLessonkitProviderRuntime.ts
206
- var import_core11 = require("@lessonkit/core");
202
+ // src/runtime/plugins.ts
203
+ var import_core5 = require("@lessonkit/core");
204
+ function buildPluginContext(opts) {
205
+ return (0, import_core5.buildPluginContext)(opts);
206
+ }
207
207
 
208
- // src/runtime/progress.ts
208
+ // src/runtime/session.ts
209
209
  var import_core6 = require("@lessonkit/core");
210
210
 
211
- // src/runtime/xapi.ts
212
- var import_xapi3 = require("@lessonkit/xapi");
211
+ // src/provider/courseStarted/emit.ts
212
+ function createCourseStartedFlightScope() {
213
+ return {
214
+ trackingFlights: /* @__PURE__ */ new Map(),
215
+ emitFlights: /* @__PURE__ */ new Map()
216
+ };
217
+ }
218
+ var defaultFlightScope = createCourseStartedFlightScope();
219
+ function resetCourseStartedTrackingFlightForTests() {
220
+ defaultFlightScope.trackingFlights.clear();
221
+ defaultFlightScope.emitFlights.clear();
222
+ }
213
223
 
214
- // src/runtime/session.ts
224
+ // src/provider/useLessonkitProviderRuntime.ts
225
+ var import_xapi5 = require("@lessonkit/xapi");
226
+
227
+ // src/runtime/ports.ts
215
228
  var import_core7 = require("@lessonkit/core");
216
229
 
217
- // src/runtime/courseStartedPipeline.ts
218
- var import_xapi4 = require("@lessonkit/xapi");
230
+ // src/provider/useLessonkitProviderRuntime.ts
231
+ var import_core11 = require("@lessonkit/core");
219
232
 
220
- // src/runtime/plugins.ts
233
+ // src/runtime/progress.ts
221
234
  var import_core8 = require("@lessonkit/core");
222
- function buildPluginContext(opts) {
223
- return (0, import_core8.buildPluginContext)(opts);
224
- }
225
235
 
226
- // src/provider/courseStarted/emit.ts
227
- var courseStartedTrackingFlights = /* @__PURE__ */ new Map();
228
- var courseStartedEmitFlights = /* @__PURE__ */ new Map();
229
- function resetCourseStartedTrackingFlightForTests() {
230
- courseStartedTrackingFlights.clear();
231
- courseStartedEmitFlights.clear();
232
- }
236
+ // src/runtime/xapi.ts
237
+ var import_xapi4 = require("@lessonkit/xapi");
233
238
 
234
239
  // src/runtime/telemetry.ts
235
240
  var import_core9 = require("@lessonkit/core");
236
241
 
237
242
  // src/provider/useLessonkitProviderRuntime.ts
238
- var defaultStorage = (0, import_core5.createSessionStoragePort)();
243
+ var providerStoragesForTests = /* @__PURE__ */ new Set();
239
244
  function resetLessonkitProviderStorageForTests() {
240
- (0, import_core5.resetStoragePortForTests)(defaultStorage);
245
+ for (const storage of providerStoragesForTests) {
246
+ (0, import_core7.resetStoragePortForTests)(storage);
247
+ }
248
+ providerStoragesForTests.clear();
241
249
  (0, import_core11.resetSharedVolatileSessionIdForTests)();
242
250
  }
243
251
 
@@ -280,14 +288,15 @@ function meetsPassingThreshold(score, maxScore, passingScore) {
280
288
  }
281
289
  function scoreFromCustom(custom, fallbackCorrect, fallbackMax = 1, passingScore) {
282
290
  const maxScore = custom?.maxScore ?? fallbackMax;
291
+ const hasNumericScore = custom?.score != null && Number.isFinite(custom.score);
292
+ if (hasNumericScore) {
293
+ const passed2 = custom.passed !== void 0 ? custom.passed : meetsPassingThreshold(custom.score, maxScore, passingScore);
294
+ return { score: custom.score, maxScore, passed: passed2 };
295
+ }
283
296
  if (custom?.passed !== void 0) {
284
- const score2 = custom.passed ? custom.score ?? maxScore : custom.score ?? 0;
297
+ const score2 = custom.passed ? maxScore : 0;
285
298
  return { score: score2, maxScore, passed: custom.passed };
286
299
  }
287
- if (custom?.maxScore != null && custom.maxScore > 0 && custom.score != null) {
288
- const passed2 = meetsPassingThreshold(custom.score, custom.maxScore, passingScore);
289
- return { score: custom.score, maxScore: custom.maxScore, passed: passed2 };
290
- }
291
300
  const score = fallbackCorrect ? maxScore : 0;
292
301
  const passed = meetsPassingThreshold(score, maxScore, passingScore);
293
302
  return { score, maxScore, passed };
@@ -332,7 +341,8 @@ function QuizInner(props, ref) {
332
341
  const { enclosingLessonId } = props;
333
342
  const checkId = (0, import_react12.useMemo)(() => normalizeComponentId(props.checkId, "checkId"), [props.checkId]);
334
343
  const quiz = useQuizState(enclosingLessonId);
335
- const { getPluginScore, isChoiceCorrect } = usePluginScoring(checkId, enclosingLessonId);
344
+ const { config } = useLessonkit();
345
+ const { scoreResponse } = usePluginScoring(checkId, enclosingLessonId);
336
346
  const [selected, setSelected] = (0, import_react12.useState)(null);
337
347
  const [selectionCorrect, setSelectionCorrect] = (0, import_react12.useState)(null);
338
348
  const [quizPassed, setQuizPassed] = (0, import_react12.useState)(false);
@@ -430,9 +440,9 @@ function QuizInner(props, ref) {
430
440
  if (nextPassed === true || nextPassed === false) {
431
441
  setQuizPassed(nextPassed);
432
442
  completedRef.current = nextPassed;
433
- if (nextPassed) {
434
- const maxScore = nextCompletedMaxScore ?? completedMaxScore ?? 1;
435
- const score = nextCompletedScore ?? completedScore ?? maxScore;
443
+ if (nextPassed && config.tracking?.replayResumeEvents === true) {
444
+ const maxScore = nextCompletedMaxScore ?? 1;
445
+ const score = nextCompletedScore ?? (nextPassed ? maxScore : 0);
436
446
  replayTelemetry(
437
447
  nextSelected ?? null,
438
448
  nextCorrect ?? null,
@@ -448,6 +458,7 @@ function QuizInner(props, ref) {
448
458
  checkId,
449
459
  completedMaxScore,
450
460
  completedScore,
461
+ config.tracking?.replayResumeEvents,
451
462
  props.passingScore,
452
463
  props.question,
453
464
  quiz,
@@ -469,32 +480,40 @@ function QuizInner(props, ref) {
469
480
  name: questionId,
470
481
  value: c,
471
482
  checked: selected === c,
472
- disabled: passed,
483
+ disabled: passed && !props.enableRetry,
473
484
  "aria-invalid": selected === c && selectionCorrect === false ? true : void 0,
474
485
  onChange: () => {
475
- if (passed) return;
486
+ if (passed && !props.enableRetry) return;
476
487
  setSelected(c);
477
- const custom = getPluginScore(c);
478
- const correct = isChoiceCorrect(c, props.answer, custom, props.passingScore);
479
- setSelectionCorrect(correct);
488
+ const defaultCorrect = c === props.answer;
489
+ const scored = scoreResponse(c, defaultCorrect, 1, props.passingScore);
490
+ setSelectionCorrect(scored.passed);
480
491
  quiz.answer({
481
492
  checkId,
482
493
  question: props.question,
483
494
  choice: c,
484
- correct
495
+ correct: scored.passed
485
496
  });
486
- if (correct && !completedRef.current) {
497
+ if (scored.passed && !completedRef.current) {
487
498
  completedRef.current = true;
488
499
  setQuizPassed(true);
489
- const maxScore = custom?.maxScore ?? 1;
490
- const score = custom?.score ?? maxScore;
491
- setCompletedScore(score);
492
- setCompletedMaxScore(maxScore);
500
+ setCompletedScore(scored.score);
501
+ setCompletedMaxScore(scored.maxScore);
502
+ quiz.complete({
503
+ checkId,
504
+ score: scored.score,
505
+ maxScore: scored.maxScore,
506
+ passingScore: props.passingScore ?? scored.maxScore
507
+ });
508
+ } else if (!scored.passed && props.enableRetry === false && !completedRef.current) {
509
+ completedRef.current = true;
510
+ setCompletedScore(scored.score);
511
+ setCompletedMaxScore(scored.maxScore);
493
512
  quiz.complete({
494
513
  checkId,
495
- score,
496
- maxScore,
497
- passingScore: props.passingScore ?? maxScore
514
+ score: scored.score,
515
+ maxScore: scored.maxScore,
516
+ passingScore: props.passingScore ?? scored.maxScore
498
517
  });
499
518
  }
500
519
  }
@@ -503,7 +522,24 @@ function QuizInner(props, ref) {
503
522
  c
504
523
  ] }, `${questionId}-${i}`))
505
524
  ] }),
506
- selected && selectionCorrect !== null ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { role: "status", "aria-live": "polite", children: selectionCorrect ? "Correct" : "Try again" }) : null
525
+ selected && selectionCorrect !== null ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { role: "status", "aria-live": "polite", children: selectionCorrect ? "Correct" : "Try again" }) : null,
526
+ props.enableRetry && passed ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
527
+ "button",
528
+ {
529
+ type: "button",
530
+ "data-testid": "quiz-retry",
531
+ onClick: () => {
532
+ completedRef.current = false;
533
+ telemetryReplayedRef.current = false;
534
+ setQuizPassed(false);
535
+ setSelected(null);
536
+ setSelectionCorrect(null);
537
+ setCompletedScore(null);
538
+ setCompletedMaxScore(null);
539
+ },
540
+ children: "Try again"
541
+ }
542
+ ) : null
507
543
  ] });
508
544
  }
509
545
  var QuizInnerForwarded = (0, import_react12.forwardRef)(QuizInner);
@@ -1,4 +1,4 @@
1
- export { r as resetAssessmentWarningsForTests, b as resetQuizWarningsForTests } from './AssessmentLessonGuard-D2Plzybb.cjs';
1
+ export { r as resetAssessmentWarningsForTests, a as resetQuizWarningsForTests } from './AssessmentLessonGuard-BzNPbjaV.cjs';
2
2
  import '@lessonkit/core';
3
3
  import 'react';
4
4
 
package/dist/testing.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { r as resetAssessmentWarningsForTests, b as resetQuizWarningsForTests } from './AssessmentLessonGuard-D2Plzybb.js';
1
+ export { r as resetAssessmentWarningsForTests, a as resetQuizWarningsForTests } from './AssessmentLessonGuard-BzNPbjaV.js';
2
2
  import '@lessonkit/core';
3
3
  import 'react';
4
4
 
package/dist/testing.js CHANGED
@@ -1,13 +1,13 @@
1
1
  import {
2
2
  resetLessonMountRegistryForTests,
3
3
  resetQuizWarningsForTests
4
- } from "./chunk-UUTXECVW.js";
4
+ } from "./chunk-ELGQ4XI3.js";
5
5
  import {
6
6
  resetAssessmentWarningsForTests,
7
7
  resetCompoundValidationWarningsForTests,
8
8
  resetCourseStartedTrackingFlightForTests,
9
9
  resetLessonkitProviderStorageForTests
10
- } from "./chunk-TDM3ARE7.js";
10
+ } from "./chunk-7TJQJFYR.js";
11
11
  export {
12
12
  resetAssessmentWarningsForTests,
13
13
  resetCompoundValidationWarningsForTests,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lessonkit/react",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "private": false,
5
5
  "description": "React components and hooks for building learning experiences with LessonKit.",
6
6
  "license": "Apache-2.0",
@@ -61,16 +61,16 @@
61
61
  "scripts": {
62
62
  "build": "tsup src/index.tsx src/testing.ts src/blocks-entry.ts --format esm,cjs --dts --external react --external react-dom --external @lessonkit/accessibility --external @lessonkit/lxpack --external @lessonkit/themes",
63
63
  "dev": "tsup src/index.tsx src/testing.ts src/blocks-entry.ts --format esm,cjs --dts --watch --external react --external react-dom --external @lessonkit/accessibility --external @lessonkit/lxpack --external @lessonkit/themes",
64
- "storybook": "NODE_PATH=./node_modules:../../node_modules node ../../node_modules/storybook/bin/index.cjs dev -c .storybook -p 6006 --disable-telemetry",
65
- "build-storybook": "NODE_PATH=./node_modules:../../node_modules node ../../node_modules/storybook/bin/index.cjs build -c .storybook --disable-telemetry",
64
+ "storybook": "storybook dev -c .storybook -p 6006 --disable-telemetry",
65
+ "build-storybook": "storybook build -c .storybook --disable-telemetry",
66
66
  "prepublishOnly": "npm run build",
67
67
  "typecheck": "tsc -p tsconfig.json",
68
68
  "test": "vitest run --passWithNoTests",
69
69
  "test:coverage": "vitest run --coverage --passWithNoTests=false",
70
- "lint": "echo \"(no lint configured yet)\""
70
+ "lint": "eslint --max-warnings 0 \"src/**/*.{ts,tsx}\" \"test/**/*.{ts,tsx}\" \"stories/**/*.{ts,tsx}\""
71
71
  },
72
72
  "peerDependencies": {
73
- "@lessonkit/lxpack": "1.4.0",
73
+ "@lessonkit/lxpack": "1.5.0",
74
74
  "react": ">=18",
75
75
  "react-dom": ">=18"
76
76
  },
@@ -80,19 +80,16 @@
80
80
  }
81
81
  },
82
82
  "dependencies": {
83
- "@lessonkit/accessibility": "1.4.0",
84
- "@lessonkit/core": "1.4.0",
85
- "@lessonkit/lxpack": "1.4.0",
86
- "@lessonkit/themes": "1.4.0",
87
- "@lessonkit/xapi": "1.4.0"
83
+ "@lessonkit/accessibility": "1.5.0",
84
+ "@lessonkit/core": "1.5.0",
85
+ "@lessonkit/lxpack": "1.5.0",
86
+ "@lessonkit/themes": "1.5.0",
87
+ "@lessonkit/xapi": "1.5.0"
88
88
  },
89
89
  "devDependencies": {
90
- "@storybook/addon-essentials": "8.6.18",
91
- "@storybook/addon-interactions": "8.6.18",
92
- "@storybook/builder-vite": "8.6.18",
93
- "@storybook/react": "8.6.18",
94
- "@storybook/react-vite": "8.6.18",
95
- "@storybook/test": "8.6.18",
90
+ "@storybook/builder-vite": "10.4.2",
91
+ "@storybook/react": "10.4.2",
92
+ "@storybook/react-vite": "10.4.2",
96
93
  "@testing-library/react": "^16.3.0",
97
94
  "@types/react": "^19.2.17",
98
95
  "@types/react-dom": "^19.2.3",
@@ -100,9 +97,9 @@
100
97
  "jsdom": "^29.1.1",
101
98
  "react": "^19.2.7",
102
99
  "react-dom": "^19.2.7",
103
- "storybook": "8.6.18",
100
+ "storybook": "10.4.2",
104
101
  "tsup": "^8.5.0",
105
- "typescript": "^5.8.3",
102
+ "typescript": "^6.0.3",
106
103
  "vite": "^8.0.11",
107
104
  "vitest": "^4.1.8",
108
105
  "@testing-library/dom": "^10.4.1"