@intellectif/lk-react 5.0.0 → 6.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/CHANGELOG.md +330 -0
  2. package/README.md +43 -12
  3. package/dist/{WrittenResponse-DqQ0NjPK.d.cts → WrittenResponse-BXHidIaK.d.cts} +8 -2
  4. package/dist/{WrittenResponse-Dkg7FUt-.d.ts → WrittenResponse-DJYGuMKk.d.ts} +8 -2
  5. package/dist/{chunk-E4MNA2IQ.js → chunk-2TW6WLBC.js} +3 -2
  6. package/dist/chunk-2TW6WLBC.js.map +1 -0
  7. package/dist/chunk-4HZ7LQIH.cjs +1 -0
  8. package/dist/{chunk-3DNJDP73.js → chunk-57E3E2H4.js} +18 -5
  9. package/dist/chunk-57E3E2H4.js.map +1 -0
  10. package/dist/chunk-6F4X2NKL.js +1 -0
  11. package/dist/{chunk-5HVUKXF3.js → chunk-6N43WDVG.js} +7 -6
  12. package/dist/chunk-6N43WDVG.js.map +1 -0
  13. package/dist/{chunk-CPF2JJG6.cjs → chunk-AMGK7DDM.cjs} +32 -2
  14. package/dist/chunk-AMGK7DDM.cjs.map +1 -0
  15. package/dist/{chunk-WCVD5LVG.cjs → chunk-BE7R2X3S.cjs} +18 -6
  16. package/dist/chunk-BE7R2X3S.cjs.map +1 -0
  17. package/dist/{chunk-B3FUUKZ2.js → chunk-DQNVAXG6.js} +32 -3
  18. package/dist/chunk-DQNVAXG6.js.map +1 -0
  19. package/dist/{chunk-3B4ACY4K.cjs → chunk-DUIWK272.cjs} +64 -10
  20. package/dist/chunk-DUIWK272.cjs.map +1 -0
  21. package/dist/chunk-GB5URWM4.js +1 -0
  22. package/dist/{chunk-NBFTCGQ5.js → chunk-IUODGQXE.js} +62 -8
  23. package/dist/chunk-IUODGQXE.js.map +1 -0
  24. package/dist/{chunk-6COTYW77.cjs → chunk-MDYKYDRN.cjs} +18 -5
  25. package/dist/chunk-MDYKYDRN.cjs.map +1 -0
  26. package/dist/{chunk-67WDTQMG.js → chunk-MDZMKC27.js} +18 -5
  27. package/dist/chunk-MDZMKC27.js.map +1 -0
  28. package/dist/chunk-NEWGUDA5.cjs +1 -0
  29. package/dist/{chunk-67RIIDZX.cjs → chunk-NPY2F7F6.cjs} +18 -5
  30. package/dist/chunk-NPY2F7F6.cjs.map +1 -0
  31. package/dist/chunk-PGEG7UZJ.cjs +1 -0
  32. package/dist/{chunk-IYLQSNLE.cjs → chunk-PNNUPUH4.cjs} +3 -2
  33. package/dist/chunk-PNNUPUH4.cjs.map +1 -0
  34. package/dist/chunk-UQ3BBEIT.js +1 -0
  35. package/dist/{chunk-HD2AIQRD.cjs → chunk-XOR4MPEN.cjs} +7 -6
  36. package/dist/chunk-XOR4MPEN.cjs.map +1 -0
  37. package/dist/{chunk-5SYL5CJ2.js → chunk-ZLUMUJUQ.js} +18 -6
  38. package/dist/chunk-ZLUMUJUQ.js.map +1 -0
  39. package/dist/components/ActivitySequence.cjs +7 -6
  40. package/dist/components/ActivitySequence.d.cts +66 -12
  41. package/dist/components/ActivitySequence.d.ts +66 -12
  42. package/dist/components/ActivitySequence.js +6 -5
  43. package/dist/components/FillInTheBlanks.cjs +4 -3
  44. package/dist/components/FillInTheBlanks.d.cts +1 -1
  45. package/dist/components/FillInTheBlanks.d.ts +1 -1
  46. package/dist/components/FillInTheBlanks.js +3 -2
  47. package/dist/components/MultipleChoice.cjs +4 -3
  48. package/dist/components/MultipleChoice.d.cts +2 -2
  49. package/dist/components/MultipleChoice.d.ts +2 -2
  50. package/dist/components/MultipleChoice.js +3 -2
  51. package/dist/components/StimulusPanel.cjs +1 -0
  52. package/dist/components/StimulusPanel.d.cts +1 -1
  53. package/dist/components/StimulusPanel.d.ts +1 -1
  54. package/dist/components/StimulusPanel.js +1 -0
  55. package/dist/components/WrittenResponse.cjs +4 -3
  56. package/dist/components/WrittenResponse.d.cts +3 -3
  57. package/dist/components/WrittenResponse.d.ts +3 -3
  58. package/dist/components/WrittenResponse.js +3 -2
  59. package/dist/hooks/useActivityState.cjs +3 -2
  60. package/dist/hooks/useActivityState.d.cts +10 -3
  61. package/dist/hooks/useActivityState.d.ts +10 -3
  62. package/dist/hooks/useActivityState.js +2 -1
  63. package/dist/hooks/useXAPI.cjs +3 -2
  64. package/dist/hooks/useXAPI.d.cts +31 -0
  65. package/dist/hooks/useXAPI.d.ts +31 -0
  66. package/dist/hooks/useXAPI.js +2 -1
  67. package/dist/index.cjs +22 -17
  68. package/dist/index.cjs.map +1 -1
  69. package/dist/index.d.cts +3 -3
  70. package/dist/index.d.ts +3 -3
  71. package/dist/index.js +8 -7
  72. package/dist/index.js.map +1 -1
  73. package/dist/theme/ThemeProvider.cjs +10 -5
  74. package/dist/theme/ThemeProvider.d.cts +21 -1
  75. package/dist/theme/ThemeProvider.d.ts +21 -1
  76. package/dist/theme/ThemeProvider.js +2 -1
  77. package/dist/theme/skin.css +7 -7
  78. package/dist/{types-CrURyd9-.d.cts → types-9iS1Vs2I.d.cts} +38 -13
  79. package/dist/{types-CrURyd9-.d.ts → types-9iS1Vs2I.d.ts} +38 -13
  80. package/package.json +28 -5
  81. package/dist/chunk-3B4ACY4K.cjs.map +0 -1
  82. package/dist/chunk-3DNJDP73.js.map +0 -1
  83. package/dist/chunk-5HVUKXF3.js.map +0 -1
  84. package/dist/chunk-5SYL5CJ2.js.map +0 -1
  85. package/dist/chunk-67RIIDZX.cjs.map +0 -1
  86. package/dist/chunk-67WDTQMG.js.map +0 -1
  87. package/dist/chunk-6COTYW77.cjs.map +0 -1
  88. package/dist/chunk-B3FUUKZ2.js.map +0 -1
  89. package/dist/chunk-CPF2JJG6.cjs.map +0 -1
  90. package/dist/chunk-E4MNA2IQ.js.map +0 -1
  91. package/dist/chunk-HD2AIQRD.cjs.map +0 -1
  92. package/dist/chunk-IYLQSNLE.cjs.map +0 -1
  93. package/dist/chunk-NBFTCGQ5.js.map +0 -1
  94. package/dist/chunk-WCVD5LVG.cjs.map +0 -1
@@ -1,15 +1,16 @@
1
+ 'use client';
1
2
  'use strict';
2
3
 
3
- var chunk67RIIDZX_cjs = require('../chunk-67RIIDZX.cjs');
4
+ var chunkNPY2F7F6_cjs = require('../chunk-NPY2F7F6.cjs');
4
5
  require('../chunk-PGEG7UZJ.cjs');
5
6
  require('../chunk-4HZ7LQIH.cjs');
6
- require('../chunk-HD2AIQRD.cjs');
7
+ require('../chunk-XOR4MPEN.cjs');
7
8
 
8
9
 
9
10
 
10
11
  Object.defineProperty(exports, "FillInTheBlanks", {
11
12
  enumerable: true,
12
- get: function () { return chunk67RIIDZX_cjs.FillInTheBlanks; }
13
+ get: function () { return chunkNPY2F7F6_cjs.FillInTheBlanks; }
13
14
  });
14
15
  //# sourceMappingURL=FillInTheBlanks.cjs.map
15
16
  //# sourceMappingURL=FillInTheBlanks.cjs.map
@@ -1,6 +1,6 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import { FillInTheBlanksData } from '@intellectif/lk-core';
3
- import { A as ActivityProps } from '../types-CrURyd9-.cjs';
3
+ import { A as ActivityProps } from '../types-9iS1Vs2I.cjs';
4
4
 
5
5
  interface FillInTheBlanksProps extends ActivityProps<FillInTheBlanksData> {
6
6
  /**
@@ -1,6 +1,6 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import { FillInTheBlanksData } from '@intellectif/lk-core';
3
- import { A as ActivityProps } from '../types-CrURyd9-.js';
3
+ import { A as ActivityProps } from '../types-9iS1Vs2I.js';
4
4
 
5
5
  interface FillInTheBlanksProps extends ActivityProps<FillInTheBlanksData> {
6
6
  /**
@@ -1,6 +1,7 @@
1
- export { FillInTheBlanks } from '../chunk-3DNJDP73.js';
1
+ 'use client';
2
+ export { FillInTheBlanks } from '../chunk-57E3E2H4.js';
2
3
  import '../chunk-6F4X2NKL.js';
3
4
  import '../chunk-GB5URWM4.js';
4
- import '../chunk-5HVUKXF3.js';
5
+ import '../chunk-6N43WDVG.js';
5
6
  //# sourceMappingURL=FillInTheBlanks.js.map
6
7
  //# sourceMappingURL=FillInTheBlanks.js.map
@@ -1,15 +1,16 @@
1
+ 'use client';
1
2
  'use strict';
2
3
 
3
- var chunkWCVD5LVG_cjs = require('../chunk-WCVD5LVG.cjs');
4
+ var chunkBE7R2X3S_cjs = require('../chunk-BE7R2X3S.cjs');
4
5
  require('../chunk-PGEG7UZJ.cjs');
5
6
  require('../chunk-4HZ7LQIH.cjs');
6
- require('../chunk-HD2AIQRD.cjs');
7
+ require('../chunk-XOR4MPEN.cjs');
7
8
 
8
9
 
9
10
 
10
11
  Object.defineProperty(exports, "MultipleChoice", {
11
12
  enumerable: true,
12
- get: function () { return chunkWCVD5LVG_cjs.MultipleChoice; }
13
+ get: function () { return chunkBE7R2X3S_cjs.MultipleChoice; }
13
14
  });
14
15
  //# sourceMappingURL=MultipleChoice.cjs.map
15
16
  //# sourceMappingURL=MultipleChoice.cjs.map
@@ -1,6 +1,6 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import { MultipleChoiceData } from '@intellectif/lk-core';
3
- import { A as ActivityProps } from '../types-CrURyd9-.cjs';
3
+ import { A as ActivityProps } from '../types-9iS1Vs2I.cjs';
4
4
 
5
5
  interface MultipleChoiceProps extends ActivityProps<MultipleChoiceData> {
6
6
  /**
@@ -8,7 +8,7 @@ interface MultipleChoiceProps extends ActivityProps<MultipleChoiceData> {
8
8
  * id) to make the shuffled order reproducible server-side, stable across
9
9
  * page reloads, and identical between SSR and hydration. When absent, a
10
10
  * random per-mount session seed is used (order stable within the mount
11
- * only — the v1 behaviour).
11
+ * only — the behaviour before `shuffleSeed` existed).
12
12
  */
13
13
  shuffleSeed?: string;
14
14
  }
@@ -1,6 +1,6 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import { MultipleChoiceData } from '@intellectif/lk-core';
3
- import { A as ActivityProps } from '../types-CrURyd9-.js';
3
+ import { A as ActivityProps } from '../types-9iS1Vs2I.js';
4
4
 
5
5
  interface MultipleChoiceProps extends ActivityProps<MultipleChoiceData> {
6
6
  /**
@@ -8,7 +8,7 @@ interface MultipleChoiceProps extends ActivityProps<MultipleChoiceData> {
8
8
  * id) to make the shuffled order reproducible server-side, stable across
9
9
  * page reloads, and identical between SSR and hydration. When absent, a
10
10
  * random per-mount session seed is used (order stable within the mount
11
- * only — the v1 behaviour).
11
+ * only — the behaviour before `shuffleSeed` existed).
12
12
  */
13
13
  shuffleSeed?: string;
14
14
  }
@@ -1,6 +1,7 @@
1
- export { MultipleChoice } from '../chunk-5SYL5CJ2.js';
1
+ 'use client';
2
+ export { MultipleChoice } from '../chunk-ZLUMUJUQ.js';
2
3
  import '../chunk-6F4X2NKL.js';
3
4
  import '../chunk-GB5URWM4.js';
4
- import '../chunk-5HVUKXF3.js';
5
+ import '../chunk-6N43WDVG.js';
5
6
  //# sourceMappingURL=MultipleChoice.js.map
6
7
  //# sourceMappingURL=MultipleChoice.js.map
@@ -1,3 +1,4 @@
1
+ 'use client';
1
2
  'use strict';
2
3
 
3
4
  var chunkNEWGUDA5_cjs = require('../chunk-NEWGUDA5.cjs');
@@ -1,5 +1,5 @@
1
1
  import { Stimulus } from '@intellectif/lk-core';
2
- import { H as HtmlSanitizer } from '../types-CrURyd9-.cjs';
2
+ import { H as HtmlSanitizer } from '../types-9iS1Vs2I.cjs';
3
3
 
4
4
  interface StimulusPanelProps {
5
5
  stimulus: Stimulus;
@@ -1,5 +1,5 @@
1
1
  import { Stimulus } from '@intellectif/lk-core';
2
- import { H as HtmlSanitizer } from '../types-CrURyd9-.js';
2
+ import { H as HtmlSanitizer } from '../types-9iS1Vs2I.js';
3
3
 
4
4
  interface StimulusPanelProps {
5
5
  stimulus: Stimulus;
@@ -1,3 +1,4 @@
1
+ 'use client';
1
2
  export { StimulusPanel } from '../chunk-UQ3BBEIT.js';
2
3
  import '../chunk-GB5URWM4.js';
3
4
  //# sourceMappingURL=StimulusPanel.js.map
@@ -1,15 +1,16 @@
1
+ 'use client';
1
2
  'use strict';
2
3
 
3
- var chunk6COTYW77_cjs = require('../chunk-6COTYW77.cjs');
4
+ var chunkMDYKYDRN_cjs = require('../chunk-MDYKYDRN.cjs');
4
5
  require('../chunk-PGEG7UZJ.cjs');
5
6
  require('../chunk-4HZ7LQIH.cjs');
6
- require('../chunk-HD2AIQRD.cjs');
7
+ require('../chunk-XOR4MPEN.cjs');
7
8
 
8
9
 
9
10
 
10
11
  Object.defineProperty(exports, "WrittenResponse", {
11
12
  enumerable: true,
12
- get: function () { return chunk6COTYW77_cjs.WrittenResponse; }
13
+ get: function () { return chunkMDYKYDRN_cjs.WrittenResponse; }
13
14
  });
14
15
  //# sourceMappingURL=WrittenResponse.cjs.map
15
16
  //# sourceMappingURL=WrittenResponse.cjs.map
@@ -1,7 +1,7 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import { W as WrittenResponseProps } from '../WrittenResponse-DqQ0NjPK.cjs';
3
- export { a as WrittenResponseSubmission } from '../WrittenResponse-DqQ0NjPK.cjs';
4
- export { H as HtmlSanitizer, R as RenderMode, a as Renderable } from '../types-CrURyd9-.cjs';
2
+ import { W as WrittenResponseProps } from '../WrittenResponse-BXHidIaK.cjs';
3
+ export { a as WrittenResponseSubmission } from '../WrittenResponse-BXHidIaK.cjs';
4
+ export { H as HtmlSanitizer, R as RenderMode, a as Renderable } from '../types-9iS1Vs2I.cjs';
5
5
  import '@intellectif/lk-core';
6
6
 
7
7
  /**
@@ -1,7 +1,7 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import { W as WrittenResponseProps } from '../WrittenResponse-Dkg7FUt-.js';
3
- export { a as WrittenResponseSubmission } from '../WrittenResponse-Dkg7FUt-.js';
4
- export { H as HtmlSanitizer, R as RenderMode, a as Renderable } from '../types-CrURyd9-.js';
2
+ import { W as WrittenResponseProps } from '../WrittenResponse-DJYGuMKk.js';
3
+ export { a as WrittenResponseSubmission } from '../WrittenResponse-DJYGuMKk.js';
4
+ export { H as HtmlSanitizer, R as RenderMode, a as Renderable } from '../types-9iS1Vs2I.js';
5
5
  import '@intellectif/lk-core';
6
6
 
7
7
  /**
@@ -1,6 +1,7 @@
1
- export { WrittenResponse } from '../chunk-67WDTQMG.js';
1
+ 'use client';
2
+ export { WrittenResponse } from '../chunk-MDZMKC27.js';
2
3
  import '../chunk-6F4X2NKL.js';
3
4
  import '../chunk-GB5URWM4.js';
4
- import '../chunk-5HVUKXF3.js';
5
+ import '../chunk-6N43WDVG.js';
5
6
  //# sourceMappingURL=WrittenResponse.js.map
6
7
  //# sourceMappingURL=WrittenResponse.js.map
@@ -1,12 +1,13 @@
1
+ 'use client';
1
2
  'use strict';
2
3
 
3
- var chunkHD2AIQRD_cjs = require('../chunk-HD2AIQRD.cjs');
4
+ var chunkXOR4MPEN_cjs = require('../chunk-XOR4MPEN.cjs');
4
5
 
5
6
 
6
7
 
7
8
  Object.defineProperty(exports, "useActivityState", {
8
9
  enumerable: true,
9
- get: function () { return chunkHD2AIQRD_cjs.useActivityState; }
10
+ get: function () { return chunkXOR4MPEN_cjs.useActivityState; }
10
11
  });
11
12
  //# sourceMappingURL=useActivityState.cjs.map
12
13
  //# sourceMappingURL=useActivityState.cjs.map
@@ -8,8 +8,15 @@ interface UseActivityStateResult {
8
8
  complete: () => void;
9
9
  /** → reviewing (e.g. when showing correct answers). */
10
10
  review: () => void;
11
- /** → idle and clears timing. Call when the activity `data` prop changes. */
12
- reset: () => void;
11
+ /**
12
+ * Clears timing and returns to `to`, defaulting to `idle`. Call when the
13
+ * activity `data` prop changes.
14
+ *
15
+ * The target is a parameter because "reset" for a RESTORED item does not
16
+ * mean idle: an item the learner had already submitted must come back
17
+ * submitted, or a data-prop change quietly unlocks committed work.
18
+ */
19
+ reset: (to?: ActivityState) => void;
13
20
  /** Elapsed ms since `start()`; 0 when not started (or after `reset()`). */
14
21
  getTimeSpent: () => number;
15
22
  }
@@ -17,6 +24,6 @@ interface UseActivityStateResult {
17
24
  * Tracks an activity's lifecycle state and elapsed time. Timing is held in a
18
25
  * ref (no re-render) and `getTimeSpent()` is read imperatively at submission.
19
26
  */
20
- declare function useActivityState(): UseActivityStateResult;
27
+ declare function useActivityState(initialState?: ActivityState): UseActivityStateResult;
21
28
 
22
29
  export { type ActivityState, type UseActivityStateResult, useActivityState };
@@ -8,8 +8,15 @@ interface UseActivityStateResult {
8
8
  complete: () => void;
9
9
  /** → reviewing (e.g. when showing correct answers). */
10
10
  review: () => void;
11
- /** → idle and clears timing. Call when the activity `data` prop changes. */
12
- reset: () => void;
11
+ /**
12
+ * Clears timing and returns to `to`, defaulting to `idle`. Call when the
13
+ * activity `data` prop changes.
14
+ *
15
+ * The target is a parameter because "reset" for a RESTORED item does not
16
+ * mean idle: an item the learner had already submitted must come back
17
+ * submitted, or a data-prop change quietly unlocks committed work.
18
+ */
19
+ reset: (to?: ActivityState) => void;
13
20
  /** Elapsed ms since `start()`; 0 when not started (or after `reset()`). */
14
21
  getTimeSpent: () => number;
15
22
  }
@@ -17,6 +24,6 @@ interface UseActivityStateResult {
17
24
  * Tracks an activity's lifecycle state and elapsed time. Timing is held in a
18
25
  * ref (no re-render) and `getTimeSpent()` is read imperatively at submission.
19
26
  */
20
- declare function useActivityState(): UseActivityStateResult;
27
+ declare function useActivityState(initialState?: ActivityState): UseActivityStateResult;
21
28
 
22
29
  export { type ActivityState, type UseActivityStateResult, useActivityState };
@@ -1,3 +1,4 @@
1
- export { useActivityState } from '../chunk-5HVUKXF3.js';
1
+ 'use client';
2
+ export { useActivityState } from '../chunk-6N43WDVG.js';
2
3
  //# sourceMappingURL=useActivityState.js.map
3
4
  //# sourceMappingURL=useActivityState.js.map
@@ -1,12 +1,13 @@
1
+ 'use client';
1
2
  'use strict';
2
3
 
3
- var chunkIYLQSNLE_cjs = require('../chunk-IYLQSNLE.cjs');
4
+ var chunkPNNUPUH4_cjs = require('../chunk-PNNUPUH4.cjs');
4
5
 
5
6
 
6
7
 
7
8
  Object.defineProperty(exports, "useXAPI", {
8
9
  enumerable: true,
9
- get: function () { return chunkIYLQSNLE_cjs.useXAPI; }
10
+ get: function () { return chunkPNNUPUH4_cjs.useXAPI; }
10
11
  });
11
12
  //# sourceMappingURL=useXAPI.cjs.map
12
13
  //# sourceMappingURL=useXAPI.cjs.map
@@ -10,6 +10,37 @@ interface UseXAPIResult {
10
10
  */
11
11
  sendStatement: (statement: XAPIStatement) => Promise<void>;
12
12
  }
13
+ /**
14
+ * Delivers xAPI statements to a Learning Record Store, fire-and-forget.
15
+ *
16
+ * Telemetry must never break the learner's attempt, so
17
+ * {@link UseXAPIResult.sendStatement} **never rejects**: network errors and 5xx
18
+ * responses are retried (backoff 1s / 2s / 4s, up to 3 retries), a 4xx fails
19
+ * immediately, and any final failure is reported to `config.onError` instead of
20
+ * throwing. See `XAPIConfig` in `@intellectif/lk-core` for the options.
21
+ *
22
+ * The hook is also where learner identity is applied. Activity components
23
+ * cannot know who the learner is, so they emit an anonymous placeholder actor
24
+ * and a `urn:learning-kit:activity:*` object id; this hook substitutes
25
+ * `config.actor` and `config.activityId` on the way out — and only while those
26
+ * placeholders are still in place, so a statement you rewrote yourself is left
27
+ * alone.
28
+ *
29
+ * `sendStatement` keeps a stable identity across renders even when `config` is
30
+ * an inline object, so it is safe in a dependency array.
31
+ *
32
+ * ```tsx
33
+ * const { sendStatement } = useXAPI({
34
+ * endpoint: 'https://your-lrs.example/xapi/statements',
35
+ * auth: { type: 'bearer', token: 'YOUR_TOKEN' },
36
+ * activityId: 'https://your-app.example/quiz/capital-jp',
37
+ * actor: { objectType: 'Agent', mbox: 'mailto:learner@example.com' },
38
+ * onError: (e) => console.error('xAPI send failed', e),
39
+ * });
40
+ *
41
+ * <MultipleChoice data={quiz} onComplete={(r) => void sendStatement(r.xapiStatement)} />
42
+ * ```
43
+ */
13
44
  declare function useXAPI(config: XAPIConfig): UseXAPIResult;
14
45
 
15
46
  export { type UseXAPIResult, useXAPI };
@@ -10,6 +10,37 @@ interface UseXAPIResult {
10
10
  */
11
11
  sendStatement: (statement: XAPIStatement) => Promise<void>;
12
12
  }
13
+ /**
14
+ * Delivers xAPI statements to a Learning Record Store, fire-and-forget.
15
+ *
16
+ * Telemetry must never break the learner's attempt, so
17
+ * {@link UseXAPIResult.sendStatement} **never rejects**: network errors and 5xx
18
+ * responses are retried (backoff 1s / 2s / 4s, up to 3 retries), a 4xx fails
19
+ * immediately, and any final failure is reported to `config.onError` instead of
20
+ * throwing. See `XAPIConfig` in `@intellectif/lk-core` for the options.
21
+ *
22
+ * The hook is also where learner identity is applied. Activity components
23
+ * cannot know who the learner is, so they emit an anonymous placeholder actor
24
+ * and a `urn:learning-kit:activity:*` object id; this hook substitutes
25
+ * `config.actor` and `config.activityId` on the way out — and only while those
26
+ * placeholders are still in place, so a statement you rewrote yourself is left
27
+ * alone.
28
+ *
29
+ * `sendStatement` keeps a stable identity across renders even when `config` is
30
+ * an inline object, so it is safe in a dependency array.
31
+ *
32
+ * ```tsx
33
+ * const { sendStatement } = useXAPI({
34
+ * endpoint: 'https://your-lrs.example/xapi/statements',
35
+ * auth: { type: 'bearer', token: 'YOUR_TOKEN' },
36
+ * activityId: 'https://your-app.example/quiz/capital-jp',
37
+ * actor: { objectType: 'Agent', mbox: 'mailto:learner@example.com' },
38
+ * onError: (e) => console.error('xAPI send failed', e),
39
+ * });
40
+ *
41
+ * <MultipleChoice data={quiz} onComplete={(r) => void sendStatement(r.xapiStatement)} />
42
+ * ```
43
+ */
13
44
  declare function useXAPI(config: XAPIConfig): UseXAPIResult;
14
45
 
15
46
  export { type UseXAPIResult, useXAPI };
@@ -1,3 +1,4 @@
1
- export { useXAPI } from '../chunk-E4MNA2IQ.js';
1
+ 'use client';
2
+ export { useXAPI } from '../chunk-2TW6WLBC.js';
2
3
  //# sourceMappingURL=useXAPI.js.map
3
4
  //# sourceMappingURL=useXAPI.js.map
package/dist/index.cjs CHANGED
@@ -1,15 +1,16 @@
1
+ 'use client';
1
2
  'use strict';
2
3
 
3
- var chunkCPF2JJG6_cjs = require('./chunk-CPF2JJG6.cjs');
4
- var chunk3B4ACY4K_cjs = require('./chunk-3B4ACY4K.cjs');
5
- var chunkWCVD5LVG_cjs = require('./chunk-WCVD5LVG.cjs');
6
- var chunk67RIIDZX_cjs = require('./chunk-67RIIDZX.cjs');
7
- var chunk6COTYW77_cjs = require('./chunk-6COTYW77.cjs');
4
+ var chunkAMGK7DDM_cjs = require('./chunk-AMGK7DDM.cjs');
5
+ var chunkDUIWK272_cjs = require('./chunk-DUIWK272.cjs');
6
+ var chunkBE7R2X3S_cjs = require('./chunk-BE7R2X3S.cjs');
7
+ var chunkNPY2F7F6_cjs = require('./chunk-NPY2F7F6.cjs');
8
+ var chunkMDYKYDRN_cjs = require('./chunk-MDYKYDRN.cjs');
8
9
  require('./chunk-PGEG7UZJ.cjs');
9
10
  var chunkNEWGUDA5_cjs = require('./chunk-NEWGUDA5.cjs');
10
11
  require('./chunk-4HZ7LQIH.cjs');
11
- var chunkHD2AIQRD_cjs = require('./chunk-HD2AIQRD.cjs');
12
- var chunkIYLQSNLE_cjs = require('./chunk-IYLQSNLE.cjs');
12
+ var chunkXOR4MPEN_cjs = require('./chunk-XOR4MPEN.cjs');
13
+ var chunkPNNUPUH4_cjs = require('./chunk-PNNUPUH4.cjs');
13
14
 
14
15
  // src/components/types.ts
15
16
  function asRenderable(redacted) {
@@ -21,35 +22,39 @@ function asRenderableSequence(entries) {
21
22
 
22
23
  Object.defineProperty(exports, "ThemeProvider", {
23
24
  enumerable: true,
24
- get: function () { return chunkCPF2JJG6_cjs.ThemeProvider; }
25
+ get: function () { return chunkAMGK7DDM_cjs.ThemeProvider; }
26
+ });
27
+ Object.defineProperty(exports, "createTailwindTheme", {
28
+ enumerable: true,
29
+ get: function () { return chunkAMGK7DDM_cjs.createTailwindTheme; }
25
30
  });
26
31
  Object.defineProperty(exports, "darkTheme", {
27
32
  enumerable: true,
28
- get: function () { return chunkCPF2JJG6_cjs.darkTheme; }
33
+ get: function () { return chunkAMGK7DDM_cjs.darkTheme; }
29
34
  });
30
35
  Object.defineProperty(exports, "defaultTheme", {
31
36
  enumerable: true,
32
- get: function () { return chunkCPF2JJG6_cjs.defaultTheme; }
37
+ get: function () { return chunkAMGK7DDM_cjs.defaultTheme; }
33
38
  });
34
39
  Object.defineProperty(exports, "useTheme", {
35
40
  enumerable: true,
36
- get: function () { return chunkCPF2JJG6_cjs.useTheme; }
41
+ get: function () { return chunkAMGK7DDM_cjs.useTheme; }
37
42
  });
38
43
  Object.defineProperty(exports, "ActivitySequence", {
39
44
  enumerable: true,
40
- get: function () { return chunk3B4ACY4K_cjs.ActivitySequence; }
45
+ get: function () { return chunkDUIWK272_cjs.ActivitySequence; }
41
46
  });
42
47
  Object.defineProperty(exports, "MultipleChoice", {
43
48
  enumerable: true,
44
- get: function () { return chunkWCVD5LVG_cjs.MultipleChoice; }
49
+ get: function () { return chunkBE7R2X3S_cjs.MultipleChoice; }
45
50
  });
46
51
  Object.defineProperty(exports, "FillInTheBlanks", {
47
52
  enumerable: true,
48
- get: function () { return chunk67RIIDZX_cjs.FillInTheBlanks; }
53
+ get: function () { return chunkNPY2F7F6_cjs.FillInTheBlanks; }
49
54
  });
50
55
  Object.defineProperty(exports, "WrittenResponse", {
51
56
  enumerable: true,
52
- get: function () { return chunk6COTYW77_cjs.WrittenResponse; }
57
+ get: function () { return chunkMDYKYDRN_cjs.WrittenResponse; }
53
58
  });
54
59
  Object.defineProperty(exports, "StimulusPanel", {
55
60
  enumerable: true,
@@ -57,11 +62,11 @@ Object.defineProperty(exports, "StimulusPanel", {
57
62
  });
58
63
  Object.defineProperty(exports, "useActivityState", {
59
64
  enumerable: true,
60
- get: function () { return chunkHD2AIQRD_cjs.useActivityState; }
65
+ get: function () { return chunkXOR4MPEN_cjs.useActivityState; }
61
66
  });
62
67
  Object.defineProperty(exports, "useXAPI", {
63
68
  enumerable: true,
64
- get: function () { return chunkIYLQSNLE_cjs.useXAPI; }
69
+ get: function () { return chunkPNNUPUH4_cjs.useXAPI; }
65
70
  });
66
71
  exports.asRenderable = asRenderable;
67
72
  exports.asRenderableSequence = asRenderableSequence;
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/components/types.ts"],"names":[],"mappings":";;;;;;;;;;;;;;AA2IO,SAAS,aACd,QAAA,EACmB;AACnB,EAAA,OAAO,QAAA;AACT;AA4BO,SAAS,qBACd,OAAA,EAC8C;AAC9C,EAAA,OAAO,OAAA;AACT","file":"index.cjs","sourcesContent":["import type {\n ActivityData,\n ActivityResult,\n InteractionEvent,\n ItemGroup,\n ItemOutcome,\n LearnerResponse,\n RedactedActivityData,\n SequenceEntry,\n ThemeTokens,\n} from '@intellectif/lk-core';\n\n/**\n * How an activity is being presented. This is the single switch that decides\n * whether the component may grade, reveal, or submit — the three things a\n * summative exam must take away from the client.\n *\n * - `practice` (default, v1 behaviour): the component owns the attempt. It\n * scores locally on submit, reveals correctness and feedback, and calls\n * `onComplete` with a full {@link ActivityResult}.\n * - `exam`: the component NEVER scores and NEVER reveals correctness. Submit\n * emits the raw learner response through `onSubmit`; the server grades it.\n * Safe to render redacted data, because nothing here needs the answer key.\n * - `review`: read-only. Renders the learner's submitted answer, and marks\n * correctness only from an `outcome` the caller supplies (which the server\n * computed). Nothing is submittable.\n */\nexport type RenderMode = 'practice' | 'exam' | 'review';\n\n/**\n * Makes the answer-key-bearing parts of an activity optional, so a component\n * can render either full activity data OR a `redact()` projection with the\n * same props. Fields the SDK classifies `answer-key` become optional here;\n * a component in `exam` mode must not read them at all.\n */\nexport type Renderable<TData> = Omit<TData, 'scoringStrategy'> & {\n scoringStrategy?: unknown;\n /** Present on a `redact()` projection. */\n redacted?: true;\n};\n\n/**\n * {@link Renderable} DISTRIBUTED over a union of activity types.\n *\n * This distinction is load-bearing, not cosmetic. `Renderable<T>` is built on\n * `Omit`, and `Omit` does not distribute: `Omit<A | B, K>` collapses to the\n * keys A and B have IN COMMON, so `Renderable<ActivityData>` is a single\n * object type carrying only the fields every activity shares. Narrowing it\n * dies with it — after `if (data.type === 'multiple-choice')` the compiler\n * still refuses `data.options`, because the union it would narrow to no\n * longer exists.\n *\n * The conditional below re-distributes, so `RenderableActivity` is a real\n * union of per-type renderables and `.type` narrows again. Anything that\n * accepts \"some renderable activity, I don't know which\" — a custom renderer,\n * a sequence entry — must use THIS, not `Renderable<ActivityData>`.\n */\nexport type RenderableActivity<TData extends ActivityData = ActivityData> = TData extends unknown\n ? Renderable<TData>\n : never;\n\n/**\n * Sanitiser for author-supplied rich text (`questionHtml`, `passageHtml`,\n * `promptHtml`). The SDK deliberately ships NO sanitiser — that would add a\n * dependency and, worse, a false promise. Rich text is rendered only when you\n * supply this function; without it the component falls back to the plain-text\n * field, which is always escaped. Fail-safe by construction: the SDK never\n * injects HTML it was not explicitly given a sanitiser for.\n */\nexport type HtmlSanitizer = (html: string) => string;\n\n/**\n * The prop contract shared by every activity component (Req 3.1). Defined\n * here (React-specific) rather than in lk-core, which is React-free.\n *\n * Controlled / uncontrolled follows the React convention: pass `value` +\n * `onChange` to own the learner's answer (restore an in-progress attempt,\n * autosave a delta, drive a review); pass `defaultValue` to seed an\n * uncontrolled component; pass neither for the v1 behaviour.\n */\nexport interface ActivityProps<TData extends ActivityData = ActivityData> {\n /** Activity content. Accepts a `redact()` projection in `exam` mode. */\n data: RenderableActivity<TData>;\n /**\n * Called when the component scored the attempt itself. Only ever fires in\n * `practice` mode — in `exam` mode the client does not grade, so there is\n * no `ActivityResult` to give you; use `onSubmit`.\n */\n onComplete?: (result: ActivityResult) => void;\n /**\n * Called on submit with the raw learner response and no grade. Fires in\n * every mode, before `onComplete`, so an exam runner can persist the\n * response and let the server score it.\n */\n onSubmit?: (response: LearnerResponse) => void;\n /** Controlled value: the learner's current response. */\n value?: LearnerResponse;\n /** Initial response for an uncontrolled component (ignored when `value` is set). */\n defaultValue?: LearnerResponse;\n /** Fires on every change to the learner's response. Required for a controlled component. */\n onChange?: (response: LearnerResponse) => void;\n /** Presentation mode. Defaults to `practice`. */\n renderMode?: RenderMode;\n /**\n * Server-computed outcome, used by `review` mode to mark correctness\n * without the client ever scoring. Ignored in other modes.\n */\n outcome?: ItemOutcome;\n /** Renders author-supplied rich text when provided. See {@link HtmlSanitizer}. */\n sanitizeHtml?: HtmlSanitizer;\n onInteraction?: (event: InteractionEvent) => void;\n /** Per-instance token overrides, applied as inline CSS vars on the root. */\n theme?: Partial<ThemeTokens>;\n locale?: string;\n disabled?: boolean;\n}\n\n/**\n * Bridges a server-produced `redact()` projection into the `data` prop.\n *\n * `RedactedActivityData` is deliberately index-signature typed in lk-core — it\n * proves a payload is learner-safe, not what shape it has — so TypeScript\n * cannot know it still carries the public fields a renderer needs. This is the\n * SDK's single, documented crossing of that gap, so an exam runner does not\n * have to write `as unknown as` at every call site:\n *\n * ```tsx\n * <MultipleChoice\n * data={asRenderable<MultipleChoiceData>(redactedFromServer)}\n * renderMode=\"exam\"\n * onSubmit={persist}\n * />\n * ```\n *\n * Safe because `exam` mode reads only public fields; the answer-key fields the\n * type claims are exactly the ones the component is forbidden to touch there.\n * A future release will derive per-type redacted interfaces so this becomes\n * unnecessary.\n */\nexport function asRenderable<TData extends ActivityData>(\n redacted: RedactedActivityData,\n): Renderable<TData> {\n return redacted as unknown as Renderable<TData>;\n}\n\n/**\n * The same bridge as {@link asRenderable}, for a whole sequence: activities\n * and item groups as a server hands them over, ready for `<ActivitySequence>`.\n *\n * Needed for the same reason and no other. `redactItemGroup` returns\n * `ItemGroup<RedactedActivityData>`, and `RedactedActivityData` is an\n * index-signature type whose fields are all `unknown` — so its `question` is\n * not a `string` and it satisfies no per-type renderable, however the prop is\n * widened. Widening alone cannot fix this; a crossing point is required, and\n * having exactly one keeps `as unknown as` out of consumer code.\n *\n * ```tsx\n * const entries = await fetchExam(); // redacted, server-side\n * <ActivitySequence\n * activities={asRenderableSequence(entries)}\n * renderMode=\"exam\" // REQUIRED: see below\n * shuffleSeed={attemptId}\n * onSubmit={persist}\n * />\n * ```\n *\n * Pass `renderMode=\"exam\"` (or `\"review\"`). Redacted data has no answer key,\n * and the default `practice` mode grades locally — the built-in components\n * throw at render rather than fail at submit time, so a mis-wired exam item is\n * loud, not silent.\n */\nexport function asRenderableSequence(\n entries: readonly (RedactedActivityData | RedactedItemGroupData)[],\n): readonly SequenceEntry<RenderableActivity>[] {\n return entries as unknown as readonly SequenceEntry<RenderableActivity>[];\n}\n\n/** Structural shape of a `redactItemGroup()` projection, as it arrives from a server. */\ntype RedactedItemGroupData = ItemGroup<RedactedActivityData> & { redacted: true };\n"]}
1
+ {"version":3,"sources":["../src/components/types.ts"],"names":[],"mappings":";;;;;;;;;;;;;;AAgKO,SAAS,aACd,QAAA,EACmB;AACnB,EAAA,OAAO,QAAA;AACT;AAgCO,SAAS,qBACd,OAAA,EAC8C;AAC9C,EAAA,OAAO,OAAA;AACT","file":"index.cjs","sourcesContent":["import type {\n ActivityData,\n ActivityResult,\n InteractionEvent,\n ItemGroup,\n ItemOutcome,\n LearnerResponse,\n RedactedActivityData,\n SequenceEntry,\n ThemeTokens,\n} from '@intellectif/lk-core';\n\n/**\n * How an activity is being presented. This is the single switch that decides\n * whether the component may grade, reveal, or submit — the three things a\n * summative exam must take away from the client.\n *\n * - `practice` (default, and the only behaviour before `renderMode` existed):\n * the component owns the attempt. It\n * scores locally on submit, reveals correctness and feedback, and calls\n * `onComplete` with a full {@link ActivityResult}.\n * - `exam`: the component NEVER scores and NEVER reveals correctness. Submit\n * emits the raw learner response through `onSubmit`; the server grades it.\n * Safe to render redacted data, because nothing here needs the answer key.\n * - `review`: read-only. Renders the learner's submitted answer, and marks\n * correctness only from an `outcome` the caller supplies (which the server\n * computed). Nothing is submittable.\n */\nexport type RenderMode = 'practice' | 'exam' | 'review';\n\n/**\n * Makes the answer-key-bearing parts of an activity optional, so a component\n * can render either full activity data OR a `redact()` projection with the\n * same props. Fields the SDK classifies `answer-key` become optional here;\n * a component in `exam` mode must not read them at all.\n */\nexport type Renderable<TData> = Omit<TData, 'scoringStrategy'> & {\n scoringStrategy?: unknown;\n /** Present on a `redact()` projection. */\n redacted?: true;\n};\n\n/**\n * {@link Renderable} DISTRIBUTED over a union of activity types.\n *\n * This distinction is load-bearing, not cosmetic. `Renderable<T>` is built on\n * `Omit`, and `Omit` does not distribute: `Omit<A | B, K>` collapses to the\n * keys A and B have IN COMMON, so `Renderable<ActivityData>` is a single\n * object type carrying only the fields every activity shares. Narrowing it\n * dies with it — after `if (data.type === 'multiple-choice')` the compiler\n * still refuses `data.options`, because the union it would narrow to no\n * longer exists.\n *\n * The conditional below re-distributes, so `RenderableActivity` is a real\n * union of per-type renderables and `.type` narrows again. Anything that\n * accepts \"some renderable activity, I don't know which\" — a custom renderer,\n * a sequence entry — must use THIS, not `Renderable<ActivityData>`.\n */\nexport type RenderableActivity<TData extends ActivityData = ActivityData> = TData extends unknown\n ? Renderable<TData>\n : never;\n\n/**\n * Sanitiser for author-supplied rich text (`questionHtml`, `promptHtml`, and a\n * stimulus's `bodyHtml`). The SDK deliberately ships NO sanitiser — that would\n * add a dependency and, worse, a false promise. Rich text is rendered only when\n * you supply this function; without it the component falls back to the\n * plain-text field, which is always escaped. Fail-safe by construction: the SDK\n * never injects HTML it was not explicitly given a sanitiser for.\n *\n * `FillInTheBlanks.passageHtml` is the one exception, and is **never**\n * rendered: the passage hosts the answer inputs, so it cannot be split at the\n * `{{blank}}` placeholders without voiding the sanitiser. The plain `passage`\n * is always used, and passing `passageHtml` warns in development.\n */\nexport type HtmlSanitizer = (html: string) => string;\n\n/**\n * The prop contract shared by every activity component (Req 3.1). Defined\n * here (React-specific) rather than in lk-core, which is React-free.\n *\n * Controlled / uncontrolled follows the React convention: pass `value` +\n * `onChange` to own the learner's answer (restore an in-progress attempt,\n * autosave a delta, drive a review); pass `defaultValue` to seed an\n * uncontrolled component; pass neither to keep the pre-2.1.0 behaviour, where\n * the component owns the answer outright.\n */\nexport interface ActivityProps<TData extends ActivityData = ActivityData> {\n /** Activity content. Accepts a `redact()` projection in `exam` mode. */\n data: RenderableActivity<TData>;\n /**\n * Called when the component scored the attempt itself. Only ever fires in\n * `practice` mode — in `exam` mode the client does not grade, so there is\n * no `ActivityResult` to give you; use `onSubmit`.\n */\n onComplete?: (result: ActivityResult) => void;\n /**\n * Called on submit with the raw learner response and no grade. Fires in\n * every mode, before `onComplete`, so an exam runner can persist the\n * response and let the server score it.\n */\n onSubmit?: (response: LearnerResponse) => void;\n /** Controlled value: the learner's current response. */\n value?: LearnerResponse;\n /** Initial response for an uncontrolled component (ignored when `value` is set). */\n defaultValue?: LearnerResponse;\n /**\n * Mount the component as already submitted — read at mount only, like any\n * `default*` prop.\n *\n * Restoring an attempt without it reopens a question the learner had already\n * submitted as answerable, so on a summative paper they can change and\n * re-submit it. `AttemptState.submittedSlotIds` is what this consumes.\n */\n defaultSubmitted?: boolean;\n /** Fires on every change to the learner's response. Required for a controlled component. */\n onChange?: (response: LearnerResponse) => void;\n /** Presentation mode. Defaults to `practice`. */\n renderMode?: RenderMode;\n /**\n * Server-computed outcome, used by `review` mode to mark correctness\n * without the client ever scoring. Ignored in other modes.\n */\n outcome?: ItemOutcome;\n /** Renders author-supplied rich text when provided. See {@link HtmlSanitizer}. */\n sanitizeHtml?: HtmlSanitizer;\n onInteraction?: (event: InteractionEvent) => void;\n /** Per-instance token overrides, applied as inline CSS vars on the root. */\n theme?: Partial<ThemeTokens>;\n locale?: string;\n disabled?: boolean;\n}\n\n/**\n * Bridges a server-produced `redact()` projection into the `data` prop.\n *\n * `RedactedActivityData` is deliberately index-signature typed in lk-core — it\n * proves a payload is learner-safe, not what shape it has — so TypeScript\n * cannot know it still carries the public fields a renderer needs. This is the\n * SDK's single, documented crossing of that gap, so an exam runner does not\n * have to write `as unknown as` at every call site:\n *\n * ```tsx\n * <MultipleChoice\n * data={asRenderable<MultipleChoiceData>(redactedFromServer)}\n * renderMode=\"exam\"\n * onSubmit={persist}\n * />\n * ```\n *\n * Safe because `exam` mode reads only public fields; the answer-key fields the\n * type claims are exactly the ones the component is forbidden to touch there.\n *\n * Per-type redacted interfaces (`RedactedMultipleChoiceData`, …) ship from\n * lk-core since 0.6.0, but they are not yet *assignable* to `data`:\n * {@link Renderable} widens `scoringStrategy` and leaves nested answer-key\n * fields (`options[].isCorrect`, `blanks[].acceptedAnswers`) required, so a\n * real redacted payload still needs this bridge. Closing that gap is tracked\n * in the roadmap.\n */\nexport function asRenderable<TData extends ActivityData>(\n redacted: RedactedActivityData,\n): Renderable<TData> {\n return redacted as unknown as Renderable<TData>;\n}\n\n/**\n * The same bridge as {@link asRenderable}, for a whole sequence: activities\n * and item groups as a server hands them over, ready for `<ActivitySequence>`.\n *\n * Needed for the same reason and no other. `redactItemGroup` returns\n * `ItemGroup<RedactedActivityData>`, and `RedactedActivityData` is an\n * index-signature type whose fields are all `unknown` — so its `question` is\n * not a `string` and it satisfies no per-type renderable, however the prop is\n * widened. Widening alone cannot fix this; a crossing point is required, and\n * having exactly one keeps `as unknown as` out of consumer code.\n *\n * ```tsx\n * const entries = await fetchExam(); // redacted, server-side\n * <ActivitySequence\n * activities={asRenderableSequence(entries)}\n * renderMode=\"exam\" // REQUIRED: see below\n * shuffleSeed={attemptId}\n * onSubmit={persist}\n * />\n * ```\n *\n * Pass `renderMode=\"exam\"` (or `\"review\"`). Redacted data has no answer key,\n * and the default `practice` mode grades locally — so `<MultipleChoice>` and\n * `<FillInTheBlanks>` throw at render rather than fail at submit time.\n *\n * `<WrittenResponse>` is the exception: it never grades on the client and has\n * no such guard, so a redacted essay renders and stays answerable in\n * `practice`. A mis-wired essay item is therefore SILENT — set `renderMode`\n * explicitly rather than relying on the throw.\n */\nexport function asRenderableSequence(\n entries: readonly (RedactedActivityData | RedactedItemGroupData)[],\n): readonly SequenceEntry<RenderableActivity>[] {\n return entries as unknown as readonly SequenceEntry<RenderableActivity>[];\n}\n\n/** Structural shape of a `redactItemGroup()` projection, as it arrives from a server. */\ntype RedactedItemGroupData = ItemGroup<RedactedActivityData> & { redacted: true };\n"]}
package/dist/index.d.cts CHANGED
@@ -2,12 +2,12 @@ export { ActivityRenderer, ActivitySequence, ActivitySequenceProps, SequenceItem
2
2
  export { FillInTheBlanks, FillInTheBlanksProps } from './components/FillInTheBlanks.cjs';
3
3
  export { MultipleChoice, MultipleChoiceProps } from './components/MultipleChoice.cjs';
4
4
  export { StimulusPanel, StimulusPanelProps } from './components/StimulusPanel.cjs';
5
- export { A as ActivityProps, H as HtmlSanitizer, R as RenderMode, a as Renderable, b as RenderableActivity, c as asRenderable, d as asRenderableSequence } from './types-CrURyd9-.cjs';
5
+ export { A as ActivityProps, H as HtmlSanitizer, R as RenderMode, a as Renderable, b as RenderableActivity, c as asRenderable, d as asRenderableSequence } from './types-9iS1Vs2I.cjs';
6
6
  export { WrittenResponse } from './components/WrittenResponse.cjs';
7
7
  export { ActivityState, UseActivityStateResult, useActivityState } from './hooks/useActivityState.cjs';
8
8
  export { UseXAPIResult, useXAPI } from './hooks/useXAPI.cjs';
9
- export { ThemeProvider, ThemeProviderProps, darkTheme, defaultTheme, useTheme } from './theme/ThemeProvider.cjs';
10
- export { W as WrittenResponseProps, a as WrittenResponseSubmission } from './WrittenResponse-DqQ0NjPK.cjs';
9
+ export { TailwindThemeExtension, ThemeProvider, ThemeProviderProps, createTailwindTheme, darkTheme, defaultTheme, useTheme } from './theme/ThemeProvider.cjs';
10
+ export { W as WrittenResponseProps, a as WrittenResponseSubmission } from './WrittenResponse-BXHidIaK.cjs';
11
11
  import '@intellectif/lk-core';
12
12
  import 'react';
13
13
  import 'react/jsx-runtime';
package/dist/index.d.ts CHANGED
@@ -2,12 +2,12 @@ export { ActivityRenderer, ActivitySequence, ActivitySequenceProps, SequenceItem
2
2
  export { FillInTheBlanks, FillInTheBlanksProps } from './components/FillInTheBlanks.js';
3
3
  export { MultipleChoice, MultipleChoiceProps } from './components/MultipleChoice.js';
4
4
  export { StimulusPanel, StimulusPanelProps } from './components/StimulusPanel.js';
5
- export { A as ActivityProps, H as HtmlSanitizer, R as RenderMode, a as Renderable, b as RenderableActivity, c as asRenderable, d as asRenderableSequence } from './types-CrURyd9-.js';
5
+ export { A as ActivityProps, H as HtmlSanitizer, R as RenderMode, a as Renderable, b as RenderableActivity, c as asRenderable, d as asRenderableSequence } from './types-9iS1Vs2I.js';
6
6
  export { WrittenResponse } from './components/WrittenResponse.js';
7
7
  export { ActivityState, UseActivityStateResult, useActivityState } from './hooks/useActivityState.js';
8
8
  export { UseXAPIResult, useXAPI } from './hooks/useXAPI.js';
9
- export { ThemeProvider, ThemeProviderProps, darkTheme, defaultTheme, useTheme } from './theme/ThemeProvider.js';
10
- export { W as WrittenResponseProps, a as WrittenResponseSubmission } from './WrittenResponse-Dkg7FUt-.js';
9
+ export { TailwindThemeExtension, ThemeProvider, ThemeProviderProps, createTailwindTheme, darkTheme, defaultTheme, useTheme } from './theme/ThemeProvider.js';
10
+ export { W as WrittenResponseProps, a as WrittenResponseSubmission } from './WrittenResponse-DJYGuMKk.js';
11
11
  import '@intellectif/lk-core';
12
12
  import 'react';
13
13
  import 'react/jsx-runtime';
package/dist/index.js CHANGED
@@ -1,13 +1,14 @@
1
- export { ThemeProvider, darkTheme, defaultTheme, useTheme } from './chunk-B3FUUKZ2.js';
2
- export { ActivitySequence } from './chunk-NBFTCGQ5.js';
3
- export { MultipleChoice } from './chunk-5SYL5CJ2.js';
4
- export { FillInTheBlanks } from './chunk-3DNJDP73.js';
5
- export { WrittenResponse } from './chunk-67WDTQMG.js';
1
+ 'use client';
2
+ export { ThemeProvider, createTailwindTheme, darkTheme, defaultTheme, useTheme } from './chunk-DQNVAXG6.js';
3
+ export { ActivitySequence } from './chunk-IUODGQXE.js';
4
+ export { MultipleChoice } from './chunk-ZLUMUJUQ.js';
5
+ export { FillInTheBlanks } from './chunk-57E3E2H4.js';
6
+ export { WrittenResponse } from './chunk-MDZMKC27.js';
6
7
  import './chunk-6F4X2NKL.js';
7
8
  export { StimulusPanel } from './chunk-UQ3BBEIT.js';
8
9
  import './chunk-GB5URWM4.js';
9
- export { useActivityState } from './chunk-5HVUKXF3.js';
10
- export { useXAPI } from './chunk-E4MNA2IQ.js';
10
+ export { useActivityState } from './chunk-6N43WDVG.js';
11
+ export { useXAPI } from './chunk-2TW6WLBC.js';
11
12
 
12
13
  // src/components/types.ts
13
14
  function asRenderable(redacted) {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/components/types.ts"],"names":[],"mappings":";;;;;;;;;;;;AA2IO,SAAS,aACd,QAAA,EACmB;AACnB,EAAA,OAAO,QAAA;AACT;AA4BO,SAAS,qBACd,OAAA,EAC8C;AAC9C,EAAA,OAAO,OAAA;AACT","file":"index.js","sourcesContent":["import type {\n ActivityData,\n ActivityResult,\n InteractionEvent,\n ItemGroup,\n ItemOutcome,\n LearnerResponse,\n RedactedActivityData,\n SequenceEntry,\n ThemeTokens,\n} from '@intellectif/lk-core';\n\n/**\n * How an activity is being presented. This is the single switch that decides\n * whether the component may grade, reveal, or submit — the three things a\n * summative exam must take away from the client.\n *\n * - `practice` (default, v1 behaviour): the component owns the attempt. It\n * scores locally on submit, reveals correctness and feedback, and calls\n * `onComplete` with a full {@link ActivityResult}.\n * - `exam`: the component NEVER scores and NEVER reveals correctness. Submit\n * emits the raw learner response through `onSubmit`; the server grades it.\n * Safe to render redacted data, because nothing here needs the answer key.\n * - `review`: read-only. Renders the learner's submitted answer, and marks\n * correctness only from an `outcome` the caller supplies (which the server\n * computed). Nothing is submittable.\n */\nexport type RenderMode = 'practice' | 'exam' | 'review';\n\n/**\n * Makes the answer-key-bearing parts of an activity optional, so a component\n * can render either full activity data OR a `redact()` projection with the\n * same props. Fields the SDK classifies `answer-key` become optional here;\n * a component in `exam` mode must not read them at all.\n */\nexport type Renderable<TData> = Omit<TData, 'scoringStrategy'> & {\n scoringStrategy?: unknown;\n /** Present on a `redact()` projection. */\n redacted?: true;\n};\n\n/**\n * {@link Renderable} DISTRIBUTED over a union of activity types.\n *\n * This distinction is load-bearing, not cosmetic. `Renderable<T>` is built on\n * `Omit`, and `Omit` does not distribute: `Omit<A | B, K>` collapses to the\n * keys A and B have IN COMMON, so `Renderable<ActivityData>` is a single\n * object type carrying only the fields every activity shares. Narrowing it\n * dies with it — after `if (data.type === 'multiple-choice')` the compiler\n * still refuses `data.options`, because the union it would narrow to no\n * longer exists.\n *\n * The conditional below re-distributes, so `RenderableActivity` is a real\n * union of per-type renderables and `.type` narrows again. Anything that\n * accepts \"some renderable activity, I don't know which\" — a custom renderer,\n * a sequence entry — must use THIS, not `Renderable<ActivityData>`.\n */\nexport type RenderableActivity<TData extends ActivityData = ActivityData> = TData extends unknown\n ? Renderable<TData>\n : never;\n\n/**\n * Sanitiser for author-supplied rich text (`questionHtml`, `passageHtml`,\n * `promptHtml`). The SDK deliberately ships NO sanitiser — that would add a\n * dependency and, worse, a false promise. Rich text is rendered only when you\n * supply this function; without it the component falls back to the plain-text\n * field, which is always escaped. Fail-safe by construction: the SDK never\n * injects HTML it was not explicitly given a sanitiser for.\n */\nexport type HtmlSanitizer = (html: string) => string;\n\n/**\n * The prop contract shared by every activity component (Req 3.1). Defined\n * here (React-specific) rather than in lk-core, which is React-free.\n *\n * Controlled / uncontrolled follows the React convention: pass `value` +\n * `onChange` to own the learner's answer (restore an in-progress attempt,\n * autosave a delta, drive a review); pass `defaultValue` to seed an\n * uncontrolled component; pass neither for the v1 behaviour.\n */\nexport interface ActivityProps<TData extends ActivityData = ActivityData> {\n /** Activity content. Accepts a `redact()` projection in `exam` mode. */\n data: RenderableActivity<TData>;\n /**\n * Called when the component scored the attempt itself. Only ever fires in\n * `practice` mode — in `exam` mode the client does not grade, so there is\n * no `ActivityResult` to give you; use `onSubmit`.\n */\n onComplete?: (result: ActivityResult) => void;\n /**\n * Called on submit with the raw learner response and no grade. Fires in\n * every mode, before `onComplete`, so an exam runner can persist the\n * response and let the server score it.\n */\n onSubmit?: (response: LearnerResponse) => void;\n /** Controlled value: the learner's current response. */\n value?: LearnerResponse;\n /** Initial response for an uncontrolled component (ignored when `value` is set). */\n defaultValue?: LearnerResponse;\n /** Fires on every change to the learner's response. Required for a controlled component. */\n onChange?: (response: LearnerResponse) => void;\n /** Presentation mode. Defaults to `practice`. */\n renderMode?: RenderMode;\n /**\n * Server-computed outcome, used by `review` mode to mark correctness\n * without the client ever scoring. Ignored in other modes.\n */\n outcome?: ItemOutcome;\n /** Renders author-supplied rich text when provided. See {@link HtmlSanitizer}. */\n sanitizeHtml?: HtmlSanitizer;\n onInteraction?: (event: InteractionEvent) => void;\n /** Per-instance token overrides, applied as inline CSS vars on the root. */\n theme?: Partial<ThemeTokens>;\n locale?: string;\n disabled?: boolean;\n}\n\n/**\n * Bridges a server-produced `redact()` projection into the `data` prop.\n *\n * `RedactedActivityData` is deliberately index-signature typed in lk-core — it\n * proves a payload is learner-safe, not what shape it has — so TypeScript\n * cannot know it still carries the public fields a renderer needs. This is the\n * SDK's single, documented crossing of that gap, so an exam runner does not\n * have to write `as unknown as` at every call site:\n *\n * ```tsx\n * <MultipleChoice\n * data={asRenderable<MultipleChoiceData>(redactedFromServer)}\n * renderMode=\"exam\"\n * onSubmit={persist}\n * />\n * ```\n *\n * Safe because `exam` mode reads only public fields; the answer-key fields the\n * type claims are exactly the ones the component is forbidden to touch there.\n * A future release will derive per-type redacted interfaces so this becomes\n * unnecessary.\n */\nexport function asRenderable<TData extends ActivityData>(\n redacted: RedactedActivityData,\n): Renderable<TData> {\n return redacted as unknown as Renderable<TData>;\n}\n\n/**\n * The same bridge as {@link asRenderable}, for a whole sequence: activities\n * and item groups as a server hands them over, ready for `<ActivitySequence>`.\n *\n * Needed for the same reason and no other. `redactItemGroup` returns\n * `ItemGroup<RedactedActivityData>`, and `RedactedActivityData` is an\n * index-signature type whose fields are all `unknown` — so its `question` is\n * not a `string` and it satisfies no per-type renderable, however the prop is\n * widened. Widening alone cannot fix this; a crossing point is required, and\n * having exactly one keeps `as unknown as` out of consumer code.\n *\n * ```tsx\n * const entries = await fetchExam(); // redacted, server-side\n * <ActivitySequence\n * activities={asRenderableSequence(entries)}\n * renderMode=\"exam\" // REQUIRED: see below\n * shuffleSeed={attemptId}\n * onSubmit={persist}\n * />\n * ```\n *\n * Pass `renderMode=\"exam\"` (or `\"review\"`). Redacted data has no answer key,\n * and the default `practice` mode grades locally — the built-in components\n * throw at render rather than fail at submit time, so a mis-wired exam item is\n * loud, not silent.\n */\nexport function asRenderableSequence(\n entries: readonly (RedactedActivityData | RedactedItemGroupData)[],\n): readonly SequenceEntry<RenderableActivity>[] {\n return entries as unknown as readonly SequenceEntry<RenderableActivity>[];\n}\n\n/** Structural shape of a `redactItemGroup()` projection, as it arrives from a server. */\ntype RedactedItemGroupData = ItemGroup<RedactedActivityData> & { redacted: true };\n"]}
1
+ {"version":3,"sources":["../src/components/types.ts"],"names":[],"mappings":";;;;;;;;;;;;AAgKO,SAAS,aACd,QAAA,EACmB;AACnB,EAAA,OAAO,QAAA;AACT;AAgCO,SAAS,qBACd,OAAA,EAC8C;AAC9C,EAAA,OAAO,OAAA;AACT","file":"index.js","sourcesContent":["import type {\n ActivityData,\n ActivityResult,\n InteractionEvent,\n ItemGroup,\n ItemOutcome,\n LearnerResponse,\n RedactedActivityData,\n SequenceEntry,\n ThemeTokens,\n} from '@intellectif/lk-core';\n\n/**\n * How an activity is being presented. This is the single switch that decides\n * whether the component may grade, reveal, or submit — the three things a\n * summative exam must take away from the client.\n *\n * - `practice` (default, and the only behaviour before `renderMode` existed):\n * the component owns the attempt. It\n * scores locally on submit, reveals correctness and feedback, and calls\n * `onComplete` with a full {@link ActivityResult}.\n * - `exam`: the component NEVER scores and NEVER reveals correctness. Submit\n * emits the raw learner response through `onSubmit`; the server grades it.\n * Safe to render redacted data, because nothing here needs the answer key.\n * - `review`: read-only. Renders the learner's submitted answer, and marks\n * correctness only from an `outcome` the caller supplies (which the server\n * computed). Nothing is submittable.\n */\nexport type RenderMode = 'practice' | 'exam' | 'review';\n\n/**\n * Makes the answer-key-bearing parts of an activity optional, so a component\n * can render either full activity data OR a `redact()` projection with the\n * same props. Fields the SDK classifies `answer-key` become optional here;\n * a component in `exam` mode must not read them at all.\n */\nexport type Renderable<TData> = Omit<TData, 'scoringStrategy'> & {\n scoringStrategy?: unknown;\n /** Present on a `redact()` projection. */\n redacted?: true;\n};\n\n/**\n * {@link Renderable} DISTRIBUTED over a union of activity types.\n *\n * This distinction is load-bearing, not cosmetic. `Renderable<T>` is built on\n * `Omit`, and `Omit` does not distribute: `Omit<A | B, K>` collapses to the\n * keys A and B have IN COMMON, so `Renderable<ActivityData>` is a single\n * object type carrying only the fields every activity shares. Narrowing it\n * dies with it — after `if (data.type === 'multiple-choice')` the compiler\n * still refuses `data.options`, because the union it would narrow to no\n * longer exists.\n *\n * The conditional below re-distributes, so `RenderableActivity` is a real\n * union of per-type renderables and `.type` narrows again. Anything that\n * accepts \"some renderable activity, I don't know which\" — a custom renderer,\n * a sequence entry — must use THIS, not `Renderable<ActivityData>`.\n */\nexport type RenderableActivity<TData extends ActivityData = ActivityData> = TData extends unknown\n ? Renderable<TData>\n : never;\n\n/**\n * Sanitiser for author-supplied rich text (`questionHtml`, `promptHtml`, and a\n * stimulus's `bodyHtml`). The SDK deliberately ships NO sanitiser — that would\n * add a dependency and, worse, a false promise. Rich text is rendered only when\n * you supply this function; without it the component falls back to the\n * plain-text field, which is always escaped. Fail-safe by construction: the SDK\n * never injects HTML it was not explicitly given a sanitiser for.\n *\n * `FillInTheBlanks.passageHtml` is the one exception, and is **never**\n * rendered: the passage hosts the answer inputs, so it cannot be split at the\n * `{{blank}}` placeholders without voiding the sanitiser. The plain `passage`\n * is always used, and passing `passageHtml` warns in development.\n */\nexport type HtmlSanitizer = (html: string) => string;\n\n/**\n * The prop contract shared by every activity component (Req 3.1). Defined\n * here (React-specific) rather than in lk-core, which is React-free.\n *\n * Controlled / uncontrolled follows the React convention: pass `value` +\n * `onChange` to own the learner's answer (restore an in-progress attempt,\n * autosave a delta, drive a review); pass `defaultValue` to seed an\n * uncontrolled component; pass neither to keep the pre-2.1.0 behaviour, where\n * the component owns the answer outright.\n */\nexport interface ActivityProps<TData extends ActivityData = ActivityData> {\n /** Activity content. Accepts a `redact()` projection in `exam` mode. */\n data: RenderableActivity<TData>;\n /**\n * Called when the component scored the attempt itself. Only ever fires in\n * `practice` mode — in `exam` mode the client does not grade, so there is\n * no `ActivityResult` to give you; use `onSubmit`.\n */\n onComplete?: (result: ActivityResult) => void;\n /**\n * Called on submit with the raw learner response and no grade. Fires in\n * every mode, before `onComplete`, so an exam runner can persist the\n * response and let the server score it.\n */\n onSubmit?: (response: LearnerResponse) => void;\n /** Controlled value: the learner's current response. */\n value?: LearnerResponse;\n /** Initial response for an uncontrolled component (ignored when `value` is set). */\n defaultValue?: LearnerResponse;\n /**\n * Mount the component as already submitted — read at mount only, like any\n * `default*` prop.\n *\n * Restoring an attempt without it reopens a question the learner had already\n * submitted as answerable, so on a summative paper they can change and\n * re-submit it. `AttemptState.submittedSlotIds` is what this consumes.\n */\n defaultSubmitted?: boolean;\n /** Fires on every change to the learner's response. Required for a controlled component. */\n onChange?: (response: LearnerResponse) => void;\n /** Presentation mode. Defaults to `practice`. */\n renderMode?: RenderMode;\n /**\n * Server-computed outcome, used by `review` mode to mark correctness\n * without the client ever scoring. Ignored in other modes.\n */\n outcome?: ItemOutcome;\n /** Renders author-supplied rich text when provided. See {@link HtmlSanitizer}. */\n sanitizeHtml?: HtmlSanitizer;\n onInteraction?: (event: InteractionEvent) => void;\n /** Per-instance token overrides, applied as inline CSS vars on the root. */\n theme?: Partial<ThemeTokens>;\n locale?: string;\n disabled?: boolean;\n}\n\n/**\n * Bridges a server-produced `redact()` projection into the `data` prop.\n *\n * `RedactedActivityData` is deliberately index-signature typed in lk-core — it\n * proves a payload is learner-safe, not what shape it has — so TypeScript\n * cannot know it still carries the public fields a renderer needs. This is the\n * SDK's single, documented crossing of that gap, so an exam runner does not\n * have to write `as unknown as` at every call site:\n *\n * ```tsx\n * <MultipleChoice\n * data={asRenderable<MultipleChoiceData>(redactedFromServer)}\n * renderMode=\"exam\"\n * onSubmit={persist}\n * />\n * ```\n *\n * Safe because `exam` mode reads only public fields; the answer-key fields the\n * type claims are exactly the ones the component is forbidden to touch there.\n *\n * Per-type redacted interfaces (`RedactedMultipleChoiceData`, …) ship from\n * lk-core since 0.6.0, but they are not yet *assignable* to `data`:\n * {@link Renderable} widens `scoringStrategy` and leaves nested answer-key\n * fields (`options[].isCorrect`, `blanks[].acceptedAnswers`) required, so a\n * real redacted payload still needs this bridge. Closing that gap is tracked\n * in the roadmap.\n */\nexport function asRenderable<TData extends ActivityData>(\n redacted: RedactedActivityData,\n): Renderable<TData> {\n return redacted as unknown as Renderable<TData>;\n}\n\n/**\n * The same bridge as {@link asRenderable}, for a whole sequence: activities\n * and item groups as a server hands them over, ready for `<ActivitySequence>`.\n *\n * Needed for the same reason and no other. `redactItemGroup` returns\n * `ItemGroup<RedactedActivityData>`, and `RedactedActivityData` is an\n * index-signature type whose fields are all `unknown` — so its `question` is\n * not a `string` and it satisfies no per-type renderable, however the prop is\n * widened. Widening alone cannot fix this; a crossing point is required, and\n * having exactly one keeps `as unknown as` out of consumer code.\n *\n * ```tsx\n * const entries = await fetchExam(); // redacted, server-side\n * <ActivitySequence\n * activities={asRenderableSequence(entries)}\n * renderMode=\"exam\" // REQUIRED: see below\n * shuffleSeed={attemptId}\n * onSubmit={persist}\n * />\n * ```\n *\n * Pass `renderMode=\"exam\"` (or `\"review\"`). Redacted data has no answer key,\n * and the default `practice` mode grades locally — so `<MultipleChoice>` and\n * `<FillInTheBlanks>` throw at render rather than fail at submit time.\n *\n * `<WrittenResponse>` is the exception: it never grades on the client and has\n * no such guard, so a redacted essay renders and stays answerable in\n * `practice`. A mis-wired essay item is therefore SILENT — set `renderMode`\n * explicitly rather than relying on the throw.\n */\nexport function asRenderableSequence(\n entries: readonly (RedactedActivityData | RedactedItemGroupData)[],\n): readonly SequenceEntry<RenderableActivity>[] {\n return entries as unknown as readonly SequenceEntry<RenderableActivity>[];\n}\n\n/** Structural shape of a `redactItemGroup()` projection, as it arrives from a server. */\ntype RedactedItemGroupData = ItemGroup<RedactedActivityData> & { redacted: true };\n"]}