@intellectif/lk-react 2.1.0 → 4.0.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 (76) hide show
  1. package/README.md +3 -1
  2. package/dist/WrittenResponse-Dkg7FUt-.d.ts +87 -0
  3. package/dist/WrittenResponse-DqQ0NjPK.d.cts +87 -0
  4. package/dist/{chunk-3X7MYAFT.js → chunk-3DNJDP73.js} +11 -9
  5. package/dist/chunk-3DNJDP73.js.map +1 -0
  6. package/dist/chunk-4HZ7LQIH.cjs +40 -0
  7. package/dist/chunk-4HZ7LQIH.cjs.map +1 -0
  8. package/dist/chunk-525EWDUT.cjs +249 -0
  9. package/dist/chunk-525EWDUT.cjs.map +1 -0
  10. package/dist/{chunk-ZVFWJTT4.js → chunk-5SYL5CJ2.js} +10 -28
  11. package/dist/chunk-5SYL5CJ2.js.map +1 -0
  12. package/dist/{chunk-UXO6KYSZ.cjs → chunk-67RIIDZX.cjs} +17 -15
  13. package/dist/chunk-67RIIDZX.cjs.map +1 -0
  14. package/dist/chunk-6F4X2NKL.js +54 -0
  15. package/dist/chunk-6F4X2NKL.js.map +1 -0
  16. package/dist/chunk-GB5URWM4.js +38 -0
  17. package/dist/chunk-GB5URWM4.js.map +1 -0
  18. package/dist/chunk-GMU77L52.js +247 -0
  19. package/dist/chunk-GMU77L52.js.map +1 -0
  20. package/dist/chunk-NEWGUDA5.cjs +49 -0
  21. package/dist/chunk-NEWGUDA5.cjs.map +1 -0
  22. package/dist/{chunk-QHOV2A6M.cjs → chunk-PGEG7UZJ.cjs} +2 -34
  23. package/dist/chunk-PGEG7UZJ.cjs.map +1 -0
  24. package/dist/{chunk-XTJ22JAP.js → chunk-SWZRB3VY.js} +48 -14
  25. package/dist/chunk-SWZRB3VY.js.map +1 -0
  26. package/dist/chunk-UQ3BBEIT.js +47 -0
  27. package/dist/chunk-UQ3BBEIT.js.map +1 -0
  28. package/dist/{chunk-W6BH2YMZ.cjs → chunk-WCVD5LVG.cjs} +16 -34
  29. package/dist/chunk-WCVD5LVG.cjs.map +1 -0
  30. package/dist/{chunk-7BT6OXAR.cjs → chunk-XQJFLAMD.cjs} +53 -19
  31. package/dist/chunk-XQJFLAMD.cjs.map +1 -0
  32. package/dist/components/ActivitySequence.cjs +8 -5
  33. package/dist/components/ActivitySequence.d.cts +134 -8
  34. package/dist/components/ActivitySequence.d.ts +134 -8
  35. package/dist/components/ActivitySequence.js +7 -4
  36. package/dist/components/FillInTheBlanks.cjs +4 -3
  37. package/dist/components/FillInTheBlanks.d.cts +1 -1
  38. package/dist/components/FillInTheBlanks.d.ts +1 -1
  39. package/dist/components/FillInTheBlanks.js +3 -2
  40. package/dist/components/MultipleChoice.cjs +4 -3
  41. package/dist/components/MultipleChoice.d.cts +1 -1
  42. package/dist/components/MultipleChoice.d.ts +1 -1
  43. package/dist/components/MultipleChoice.js +3 -2
  44. package/dist/components/StimulusPanel.cjs +13 -0
  45. package/dist/components/StimulusPanel.cjs.map +1 -0
  46. package/dist/components/StimulusPanel.d.cts +30 -0
  47. package/dist/components/StimulusPanel.d.ts +30 -0
  48. package/dist/components/StimulusPanel.js +4 -0
  49. package/dist/components/StimulusPanel.js.map +1 -0
  50. package/dist/components/WrittenResponse.cjs +4 -3
  51. package/dist/components/WrittenResponse.d.cts +5 -86
  52. package/dist/components/WrittenResponse.d.ts +5 -86
  53. package/dist/components/WrittenResponse.js +3 -2
  54. package/dist/index.cjs +36 -26
  55. package/dist/index.cjs.map +1 -1
  56. package/dist/index.d.cts +6 -4
  57. package/dist/index.d.ts +6 -4
  58. package/dist/index.js +12 -7
  59. package/dist/index.js.map +1 -1
  60. package/dist/theme/skin.css +124 -0
  61. package/dist/{types-CNmjqD6M.d.cts → types-CrURyd9-.d.cts} +51 -3
  62. package/dist/{types-CNmjqD6M.d.ts → types-CrURyd9-.d.ts} +51 -3
  63. package/package.json +81 -28
  64. package/dist/chunk-3X7MYAFT.js.map +0 -1
  65. package/dist/chunk-7BT6OXAR.cjs.map +0 -1
  66. package/dist/chunk-QHOV2A6M.cjs.map +0 -1
  67. package/dist/chunk-STLRKR2I.cjs +0 -111
  68. package/dist/chunk-STLRKR2I.cjs.map +0 -1
  69. package/dist/chunk-UASXUWHH.js +0 -109
  70. package/dist/chunk-UASXUWHH.js.map +0 -1
  71. package/dist/chunk-UIGIYYGY.js +0 -85
  72. package/dist/chunk-UIGIYYGY.js.map +0 -1
  73. package/dist/chunk-UXO6KYSZ.cjs.map +0 -1
  74. package/dist/chunk-W6BH2YMZ.cjs.map +0 -1
  75. package/dist/chunk-XTJ22JAP.js.map +0 -1
  76. package/dist/chunk-ZVFWJTT4.js.map +0 -1
package/dist/index.cjs CHANGED
@@ -1,59 +1,69 @@
1
1
  'use strict';
2
2
 
3
- var chunkSTLRKR2I_cjs = require('./chunk-STLRKR2I.cjs');
4
- var chunkW6BH2YMZ_cjs = require('./chunk-W6BH2YMZ.cjs');
5
- var chunkUXO6KYSZ_cjs = require('./chunk-UXO6KYSZ.cjs');
6
- var chunk7BT6OXAR_cjs = require('./chunk-7BT6OXAR.cjs');
7
- require('./chunk-QHOV2A6M.cjs');
3
+ var chunkCPF2JJG6_cjs = require('./chunk-CPF2JJG6.cjs');
4
+ var chunk525EWDUT_cjs = require('./chunk-525EWDUT.cjs');
5
+ var chunkWCVD5LVG_cjs = require('./chunk-WCVD5LVG.cjs');
6
+ var chunk67RIIDZX_cjs = require('./chunk-67RIIDZX.cjs');
7
+ var chunkXQJFLAMD_cjs = require('./chunk-XQJFLAMD.cjs');
8
+ require('./chunk-PGEG7UZJ.cjs');
9
+ var chunkNEWGUDA5_cjs = require('./chunk-NEWGUDA5.cjs');
10
+ require('./chunk-4HZ7LQIH.cjs');
8
11
  var chunkHD2AIQRD_cjs = require('./chunk-HD2AIQRD.cjs');
9
12
  var chunkIYLQSNLE_cjs = require('./chunk-IYLQSNLE.cjs');
10
- var chunkCPF2JJG6_cjs = require('./chunk-CPF2JJG6.cjs');
11
13
 
12
14
  // src/components/types.ts
13
15
  function asRenderable(redacted) {
14
16
  return redacted;
15
17
  }
18
+ function asRenderableSequence(entries) {
19
+ return entries;
20
+ }
16
21
 
17
- Object.defineProperty(exports, "ActivitySequence", {
22
+ Object.defineProperty(exports, "ThemeProvider", {
18
23
  enumerable: true,
19
- get: function () { return chunkSTLRKR2I_cjs.ActivitySequence; }
24
+ get: function () { return chunkCPF2JJG6_cjs.ThemeProvider; }
20
25
  });
21
- Object.defineProperty(exports, "MultipleChoice", {
26
+ Object.defineProperty(exports, "darkTheme", {
22
27
  enumerable: true,
23
- get: function () { return chunkW6BH2YMZ_cjs.MultipleChoice; }
28
+ get: function () { return chunkCPF2JJG6_cjs.darkTheme; }
24
29
  });
25
- Object.defineProperty(exports, "FillInTheBlanks", {
30
+ Object.defineProperty(exports, "defaultTheme", {
26
31
  enumerable: true,
27
- get: function () { return chunkUXO6KYSZ_cjs.FillInTheBlanks; }
32
+ get: function () { return chunkCPF2JJG6_cjs.defaultTheme; }
28
33
  });
29
- Object.defineProperty(exports, "WrittenResponse", {
34
+ Object.defineProperty(exports, "useTheme", {
30
35
  enumerable: true,
31
- get: function () { return chunk7BT6OXAR_cjs.WrittenResponse; }
36
+ get: function () { return chunkCPF2JJG6_cjs.useTheme; }
32
37
  });
33
- Object.defineProperty(exports, "useActivityState", {
38
+ Object.defineProperty(exports, "ActivitySequence", {
34
39
  enumerable: true,
35
- get: function () { return chunkHD2AIQRD_cjs.useActivityState; }
40
+ get: function () { return chunk525EWDUT_cjs.ActivitySequence; }
36
41
  });
37
- Object.defineProperty(exports, "useXAPI", {
42
+ Object.defineProperty(exports, "MultipleChoice", {
38
43
  enumerable: true,
39
- get: function () { return chunkIYLQSNLE_cjs.useXAPI; }
44
+ get: function () { return chunkWCVD5LVG_cjs.MultipleChoice; }
40
45
  });
41
- Object.defineProperty(exports, "ThemeProvider", {
46
+ Object.defineProperty(exports, "FillInTheBlanks", {
42
47
  enumerable: true,
43
- get: function () { return chunkCPF2JJG6_cjs.ThemeProvider; }
48
+ get: function () { return chunk67RIIDZX_cjs.FillInTheBlanks; }
44
49
  });
45
- Object.defineProperty(exports, "darkTheme", {
50
+ Object.defineProperty(exports, "WrittenResponse", {
46
51
  enumerable: true,
47
- get: function () { return chunkCPF2JJG6_cjs.darkTheme; }
52
+ get: function () { return chunkXQJFLAMD_cjs.WrittenResponse; }
48
53
  });
49
- Object.defineProperty(exports, "defaultTheme", {
54
+ Object.defineProperty(exports, "StimulusPanel", {
50
55
  enumerable: true,
51
- get: function () { return chunkCPF2JJG6_cjs.defaultTheme; }
56
+ get: function () { return chunkNEWGUDA5_cjs.StimulusPanel; }
52
57
  });
53
- Object.defineProperty(exports, "useTheme", {
58
+ Object.defineProperty(exports, "useActivityState", {
54
59
  enumerable: true,
55
- get: function () { return chunkCPF2JJG6_cjs.useTheme; }
60
+ get: function () { return chunkHD2AIQRD_cjs.useActivityState; }
61
+ });
62
+ Object.defineProperty(exports, "useXAPI", {
63
+ enumerable: true,
64
+ get: function () { return chunkIYLQSNLE_cjs.useXAPI; }
56
65
  });
57
66
  exports.asRenderable = asRenderable;
67
+ exports.asRenderableSequence = asRenderableSequence;
58
68
  //# sourceMappingURL=index.cjs.map
59
69
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/components/types.ts"],"names":[],"mappings":";;;;;;;;;;;;AAqHO,SAAS,aACd,QAAA,EACmB;AACnB,EAAA,OAAO,QAAA;AACT","file":"index.cjs","sourcesContent":["import type {\n ActivityData,\n ActivityResult,\n InteractionEvent,\n ItemOutcome,\n LearnerResponse,\n RedactedActivityData,\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 * 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: Renderable<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"]}
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"]}
package/dist/index.d.cts CHANGED
@@ -1,11 +1,13 @@
1
- export { ActivitySequence, ActivitySequenceProps } from './components/ActivitySequence.cjs';
1
+ export { ActivityRenderer, ActivitySequence, ActivitySequenceProps, SequenceItemOutcome } from './components/ActivitySequence.cjs';
2
2
  export { FillInTheBlanks, FillInTheBlanksProps } from './components/FillInTheBlanks.cjs';
3
3
  export { MultipleChoice, MultipleChoiceProps } from './components/MultipleChoice.cjs';
4
- export { A as ActivityProps, H as HtmlSanitizer, R as RenderMode, a as Renderable, b as asRenderable } from './types-CNmjqD6M.cjs';
5
- export { WrittenResponse, WrittenResponseProps, WrittenResponseSubmission } from './components/WrittenResponse.cjs';
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';
6
+ export { WrittenResponse } from './components/WrittenResponse.cjs';
6
7
  export { ActivityState, UseActivityStateResult, useActivityState } from './hooks/useActivityState.cjs';
7
8
  export { UseXAPIResult, useXAPI } from './hooks/useXAPI.cjs';
8
9
  export { ThemeProvider, ThemeProviderProps, darkTheme, defaultTheme, useTheme } from './theme/ThemeProvider.cjs';
10
+ export { W as WrittenResponseProps, a as WrittenResponseSubmission } from './WrittenResponse-DqQ0NjPK.cjs';
9
11
  import '@intellectif/lk-core';
10
- import 'react/jsx-runtime';
11
12
  import 'react';
13
+ import 'react/jsx-runtime';
package/dist/index.d.ts CHANGED
@@ -1,11 +1,13 @@
1
- export { ActivitySequence, ActivitySequenceProps } from './components/ActivitySequence.js';
1
+ export { ActivityRenderer, ActivitySequence, ActivitySequenceProps, SequenceItemOutcome } from './components/ActivitySequence.js';
2
2
  export { FillInTheBlanks, FillInTheBlanksProps } from './components/FillInTheBlanks.js';
3
3
  export { MultipleChoice, MultipleChoiceProps } from './components/MultipleChoice.js';
4
- export { A as ActivityProps, H as HtmlSanitizer, R as RenderMode, a as Renderable, b as asRenderable } from './types-CNmjqD6M.js';
5
- export { WrittenResponse, WrittenResponseProps, WrittenResponseSubmission } from './components/WrittenResponse.js';
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';
6
+ export { WrittenResponse } from './components/WrittenResponse.js';
6
7
  export { ActivityState, UseActivityStateResult, useActivityState } from './hooks/useActivityState.js';
7
8
  export { UseXAPIResult, useXAPI } from './hooks/useXAPI.js';
8
9
  export { ThemeProvider, ThemeProviderProps, darkTheme, defaultTheme, useTheme } from './theme/ThemeProvider.js';
10
+ export { W as WrittenResponseProps, a as WrittenResponseSubmission } from './WrittenResponse-Dkg7FUt-.js';
9
11
  import '@intellectif/lk-core';
10
- import 'react/jsx-runtime';
11
12
  import 'react';
13
+ import 'react/jsx-runtime';
package/dist/index.js CHANGED
@@ -1,17 +1,22 @@
1
- export { ActivitySequence } from './chunk-UASXUWHH.js';
2
- export { MultipleChoice } from './chunk-ZVFWJTT4.js';
3
- export { FillInTheBlanks } from './chunk-3X7MYAFT.js';
4
- export { WrittenResponse } from './chunk-XTJ22JAP.js';
5
- import './chunk-UIGIYYGY.js';
1
+ export { ThemeProvider, darkTheme, defaultTheme, useTheme } from './chunk-B3FUUKZ2.js';
2
+ export { ActivitySequence } from './chunk-GMU77L52.js';
3
+ export { MultipleChoice } from './chunk-5SYL5CJ2.js';
4
+ export { FillInTheBlanks } from './chunk-3DNJDP73.js';
5
+ export { WrittenResponse } from './chunk-SWZRB3VY.js';
6
+ import './chunk-6F4X2NKL.js';
7
+ export { StimulusPanel } from './chunk-UQ3BBEIT.js';
8
+ import './chunk-GB5URWM4.js';
6
9
  export { useActivityState } from './chunk-5HVUKXF3.js';
7
10
  export { useXAPI } from './chunk-E4MNA2IQ.js';
8
- export { ThemeProvider, darkTheme, defaultTheme, useTheme } from './chunk-B3FUUKZ2.js';
9
11
 
10
12
  // src/components/types.ts
11
13
  function asRenderable(redacted) {
12
14
  return redacted;
13
15
  }
16
+ function asRenderableSequence(entries) {
17
+ return entries;
18
+ }
14
19
 
15
- export { asRenderable };
20
+ export { asRenderable, asRenderableSequence };
16
21
  //# sourceMappingURL=index.js.map
17
22
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/components/types.ts"],"names":[],"mappings":";;;;;;;;;;AAqHO,SAAS,aACd,QAAA,EACmB;AACnB,EAAA,OAAO,QAAA;AACT","file":"index.js","sourcesContent":["import type {\n ActivityData,\n ActivityResult,\n InteractionEvent,\n ItemOutcome,\n LearnerResponse,\n RedactedActivityData,\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 * 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: Renderable<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"]}
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"]}
@@ -111,6 +111,55 @@
111
111
  cursor: default;
112
112
  }
113
113
 
114
+ /* ── Shared stimulus (an item group's passage / recording / image) ──── */
115
+ .lk-stimulus {
116
+ margin: 0 0 var(--lk-spacing-lg);
117
+ padding: var(--lk-spacing-md) var(--lk-spacing-lg);
118
+ background: var(--lk-color-surface-raised);
119
+ border: 1px solid var(--lk-color-border);
120
+ border-radius: var(--lk-radius-base);
121
+ }
122
+
123
+ .lk-stimulus-title {
124
+ margin: 0 0 var(--lk-spacing-xs);
125
+ font-size: var(--lk-font-size-lg);
126
+ font-weight: var(--lk-font-weight-bold);
127
+ }
128
+
129
+ .lk-stimulus-range {
130
+ margin: 0 0 var(--lk-spacing-md);
131
+ font-size: var(--lk-font-size-sm);
132
+ color: var(--lk-color-text-muted);
133
+ }
134
+
135
+ .lk-stimulus .lk-media {
136
+ margin-bottom: var(--lk-spacing-md);
137
+ }
138
+
139
+ .lk-stimulus-body {
140
+ margin: 0;
141
+ line-height: var(--lk-line-height-base);
142
+ }
143
+
144
+ /* A plain-text passage keeps its authored paragraph breaks. */
145
+ .lk-stimulus-body[data-format="text"] {
146
+ white-space: pre-wrap;
147
+ }
148
+
149
+ .lk-stimulus-body[data-format="html"] > :first-child {
150
+ margin-top: 0;
151
+ }
152
+
153
+ .lk-stimulus-body[data-format="html"] > :last-child {
154
+ margin-bottom: 0;
155
+ }
156
+
157
+ .lk-stimulus-attribution {
158
+ margin: var(--lk-spacing-sm) 0 0;
159
+ font-size: var(--lk-font-size-sm);
160
+ color: var(--lk-color-text-muted);
161
+ }
162
+
114
163
  /* ── Multiple choice options ─────────────────────────────────────────── */
115
164
  .lk-mc-option {
116
165
  display: flex;
@@ -435,3 +484,78 @@
435
484
  }
436
485
  }
437
486
  }
487
+
488
+ @layer lk-skin {
489
+ /* ── Written Response: a returned grade ──────────────────────────────── */
490
+ .lk-wr-outcome {
491
+ display: flex;
492
+ flex-direction: column;
493
+ gap: var(--lk-spacing-sm);
494
+ padding: var(--lk-spacing-md);
495
+ border: 1px solid var(--lk-color-border);
496
+ border-radius: var(--lk-border-radius-md);
497
+ background: var(--lk-color-surface);
498
+ }
499
+
500
+ .lk-wr-outcome[data-passed="true"] {
501
+ border-color: var(--lk-color-success);
502
+ }
503
+ .lk-wr-outcome[data-status="deferred"] {
504
+ border-style: dashed;
505
+ }
506
+
507
+ .lk-wr-grade {
508
+ margin: 0;
509
+ font-weight: 600;
510
+ color: var(--lk-color-text);
511
+ }
512
+ .lk-wr-grade-feedback {
513
+ margin: 0;
514
+ color: var(--lk-color-text-muted);
515
+ }
516
+
517
+ .lk-wr-criteria,
518
+ .lk-wr-corrections {
519
+ display: flex;
520
+ flex-direction: column;
521
+ gap: var(--lk-spacing-xs);
522
+ margin: 0;
523
+ padding: 0;
524
+ list-style: none;
525
+ }
526
+
527
+ .lk-wr-criterion {
528
+ display: flex;
529
+ flex-wrap: wrap;
530
+ gap: var(--lk-spacing-xs);
531
+ font-size: var(--lk-font-size-sm);
532
+ }
533
+
534
+ .lk-wr-criterion-name {
535
+ font-weight: 600;
536
+ }
537
+ .lk-wr-criterion-comment,
538
+ .lk-wr-correction-explanation {
539
+ color: var(--lk-color-text-muted);
540
+ }
541
+ .lk-wr-criterion[data-na="true"] {
542
+ opacity: 0.6;
543
+ }
544
+
545
+ .lk-wr-correction {
546
+ font-size: var(--lk-font-size-sm);
547
+ }
548
+ .lk-wr-correction-original {
549
+ color: var(--lk-color-error);
550
+ }
551
+ .lk-wr-correction-corrected {
552
+ color: var(--lk-color-success);
553
+ text-decoration: none;
554
+ }
555
+
556
+ .lk-wr-review-flag {
557
+ margin: 0;
558
+ font-size: var(--lk-font-size-sm);
559
+ color: var(--lk-color-warning);
560
+ }
561
+ }
@@ -1,4 +1,4 @@
1
- import { ActivityData, ActivityResult, LearnerResponse, ItemOutcome, InteractionEvent, ThemeTokens, RedactedActivityData } from '@intellectif/lk-core';
1
+ import { ActivityData, ActivityResult, LearnerResponse, ItemOutcome, InteractionEvent, ThemeTokens, RedactedActivityData, ItemGroup, SequenceEntry } from '@intellectif/lk-core';
2
2
 
3
3
  /**
4
4
  * How an activity is being presented. This is the single switch that decides
@@ -27,6 +27,23 @@ type Renderable<TData> = Omit<TData, 'scoringStrategy'> & {
27
27
  /** Present on a `redact()` projection. */
28
28
  redacted?: true;
29
29
  };
30
+ /**
31
+ * {@link Renderable} DISTRIBUTED over a union of activity types.
32
+ *
33
+ * This distinction is load-bearing, not cosmetic. `Renderable<T>` is built on
34
+ * `Omit`, and `Omit` does not distribute: `Omit<A | B, K>` collapses to the
35
+ * keys A and B have IN COMMON, so `Renderable<ActivityData>` is a single
36
+ * object type carrying only the fields every activity shares. Narrowing it
37
+ * dies with it — after `if (data.type === 'multiple-choice')` the compiler
38
+ * still refuses `data.options`, because the union it would narrow to no
39
+ * longer exists.
40
+ *
41
+ * The conditional below re-distributes, so `RenderableActivity` is a real
42
+ * union of per-type renderables and `.type` narrows again. Anything that
43
+ * accepts "some renderable activity, I don't know which" — a custom renderer,
44
+ * a sequence entry — must use THIS, not `Renderable<ActivityData>`.
45
+ */
46
+ type RenderableActivity<TData extends ActivityData = ActivityData> = TData extends unknown ? Renderable<TData> : never;
30
47
  /**
31
48
  * Sanitiser for author-supplied rich text (`questionHtml`, `passageHtml`,
32
49
  * `promptHtml`). The SDK deliberately ships NO sanitiser — that would add a
@@ -47,7 +64,7 @@ type HtmlSanitizer = (html: string) => string;
47
64
  */
48
65
  interface ActivityProps<TData extends ActivityData = ActivityData> {
49
66
  /** Activity content. Accepts a `redact()` projection in `exam` mode. */
50
- data: Renderable<TData>;
67
+ data: RenderableActivity<TData>;
51
68
  /**
52
69
  * Called when the component scored the attempt itself. Only ever fires in
53
70
  * `practice` mode — in `exam` mode the client does not grade, so there is
@@ -104,5 +121,36 @@ interface ActivityProps<TData extends ActivityData = ActivityData> {
104
121
  * unnecessary.
105
122
  */
106
123
  declare function asRenderable<TData extends ActivityData>(redacted: RedactedActivityData): Renderable<TData>;
124
+ /**
125
+ * The same bridge as {@link asRenderable}, for a whole sequence: activities
126
+ * and item groups as a server hands them over, ready for `<ActivitySequence>`.
127
+ *
128
+ * Needed for the same reason and no other. `redactItemGroup` returns
129
+ * `ItemGroup<RedactedActivityData>`, and `RedactedActivityData` is an
130
+ * index-signature type whose fields are all `unknown` — so its `question` is
131
+ * not a `string` and it satisfies no per-type renderable, however the prop is
132
+ * widened. Widening alone cannot fix this; a crossing point is required, and
133
+ * having exactly one keeps `as unknown as` out of consumer code.
134
+ *
135
+ * ```tsx
136
+ * const entries = await fetchExam(); // redacted, server-side
137
+ * <ActivitySequence
138
+ * activities={asRenderableSequence(entries)}
139
+ * renderMode="exam" // REQUIRED: see below
140
+ * shuffleSeed={attemptId}
141
+ * onSubmit={persist}
142
+ * />
143
+ * ```
144
+ *
145
+ * Pass `renderMode="exam"` (or `"review"`). Redacted data has no answer key,
146
+ * and the default `practice` mode grades locally — the built-in components
147
+ * throw at render rather than fail at submit time, so a mis-wired exam item is
148
+ * loud, not silent.
149
+ */
150
+ declare function asRenderableSequence(entries: readonly (RedactedActivityData | RedactedItemGroupData)[]): readonly SequenceEntry<RenderableActivity>[];
151
+ /** Structural shape of a `redactItemGroup()` projection, as it arrives from a server. */
152
+ type RedactedItemGroupData = ItemGroup<RedactedActivityData> & {
153
+ redacted: true;
154
+ };
107
155
 
108
- export { type ActivityProps as A, type HtmlSanitizer as H, type RenderMode as R, type Renderable as a, asRenderable as b };
156
+ export { type ActivityProps as A, type HtmlSanitizer as H, type RenderMode as R, type Renderable as a, type RenderableActivity as b, asRenderable as c, asRenderableSequence as d };
@@ -1,4 +1,4 @@
1
- import { ActivityData, ActivityResult, LearnerResponse, ItemOutcome, InteractionEvent, ThemeTokens, RedactedActivityData } from '@intellectif/lk-core';
1
+ import { ActivityData, ActivityResult, LearnerResponse, ItemOutcome, InteractionEvent, ThemeTokens, RedactedActivityData, ItemGroup, SequenceEntry } from '@intellectif/lk-core';
2
2
 
3
3
  /**
4
4
  * How an activity is being presented. This is the single switch that decides
@@ -27,6 +27,23 @@ type Renderable<TData> = Omit<TData, 'scoringStrategy'> & {
27
27
  /** Present on a `redact()` projection. */
28
28
  redacted?: true;
29
29
  };
30
+ /**
31
+ * {@link Renderable} DISTRIBUTED over a union of activity types.
32
+ *
33
+ * This distinction is load-bearing, not cosmetic. `Renderable<T>` is built on
34
+ * `Omit`, and `Omit` does not distribute: `Omit<A | B, K>` collapses to the
35
+ * keys A and B have IN COMMON, so `Renderable<ActivityData>` is a single
36
+ * object type carrying only the fields every activity shares. Narrowing it
37
+ * dies with it — after `if (data.type === 'multiple-choice')` the compiler
38
+ * still refuses `data.options`, because the union it would narrow to no
39
+ * longer exists.
40
+ *
41
+ * The conditional below re-distributes, so `RenderableActivity` is a real
42
+ * union of per-type renderables and `.type` narrows again. Anything that
43
+ * accepts "some renderable activity, I don't know which" — a custom renderer,
44
+ * a sequence entry — must use THIS, not `Renderable<ActivityData>`.
45
+ */
46
+ type RenderableActivity<TData extends ActivityData = ActivityData> = TData extends unknown ? Renderable<TData> : never;
30
47
  /**
31
48
  * Sanitiser for author-supplied rich text (`questionHtml`, `passageHtml`,
32
49
  * `promptHtml`). The SDK deliberately ships NO sanitiser — that would add a
@@ -47,7 +64,7 @@ type HtmlSanitizer = (html: string) => string;
47
64
  */
48
65
  interface ActivityProps<TData extends ActivityData = ActivityData> {
49
66
  /** Activity content. Accepts a `redact()` projection in `exam` mode. */
50
- data: Renderable<TData>;
67
+ data: RenderableActivity<TData>;
51
68
  /**
52
69
  * Called when the component scored the attempt itself. Only ever fires in
53
70
  * `practice` mode — in `exam` mode the client does not grade, so there is
@@ -104,5 +121,36 @@ interface ActivityProps<TData extends ActivityData = ActivityData> {
104
121
  * unnecessary.
105
122
  */
106
123
  declare function asRenderable<TData extends ActivityData>(redacted: RedactedActivityData): Renderable<TData>;
124
+ /**
125
+ * The same bridge as {@link asRenderable}, for a whole sequence: activities
126
+ * and item groups as a server hands them over, ready for `<ActivitySequence>`.
127
+ *
128
+ * Needed for the same reason and no other. `redactItemGroup` returns
129
+ * `ItemGroup<RedactedActivityData>`, and `RedactedActivityData` is an
130
+ * index-signature type whose fields are all `unknown` — so its `question` is
131
+ * not a `string` and it satisfies no per-type renderable, however the prop is
132
+ * widened. Widening alone cannot fix this; a crossing point is required, and
133
+ * having exactly one keeps `as unknown as` out of consumer code.
134
+ *
135
+ * ```tsx
136
+ * const entries = await fetchExam(); // redacted, server-side
137
+ * <ActivitySequence
138
+ * activities={asRenderableSequence(entries)}
139
+ * renderMode="exam" // REQUIRED: see below
140
+ * shuffleSeed={attemptId}
141
+ * onSubmit={persist}
142
+ * />
143
+ * ```
144
+ *
145
+ * Pass `renderMode="exam"` (or `"review"`). Redacted data has no answer key,
146
+ * and the default `practice` mode grades locally — the built-in components
147
+ * throw at render rather than fail at submit time, so a mis-wired exam item is
148
+ * loud, not silent.
149
+ */
150
+ declare function asRenderableSequence(entries: readonly (RedactedActivityData | RedactedItemGroupData)[]): readonly SequenceEntry<RenderableActivity>[];
151
+ /** Structural shape of a `redactItemGroup()` projection, as it arrives from a server. */
152
+ type RedactedItemGroupData = ItemGroup<RedactedActivityData> & {
153
+ redacted: true;
154
+ };
107
155
 
108
- export { type ActivityProps as A, type HtmlSanitizer as H, type RenderMode as R, type Renderable as a, asRenderable as b };
156
+ export { type ActivityProps as A, type HtmlSanitizer as H, type RenderMode as R, type Renderable as a, type RenderableActivity as b, asRenderable as c, asRenderableSequence as d };