@intellectif/lk-react 7.0.1 → 8.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 (96) hide show
  1. package/CHANGELOG.md +106 -0
  2. package/README.md +8 -3
  3. package/dist/{WrittenResponse-zWT-Go1g.d.cts → WrittenResponse-BJr9gBpY.d.cts} +3 -1
  4. package/dist/{WrittenResponse-BJL4dGsR.d.ts → WrittenResponse-Du5CZnwn.d.ts} +3 -1
  5. package/dist/{chunk-UFWF3BKA.cjs → chunk-2VLKSNYJ.cjs} +13 -24
  6. package/dist/chunk-2VLKSNYJ.cjs.map +1 -0
  7. package/dist/{chunk-PPJ47THY.cjs → chunk-4AHZBTBF.cjs} +27 -29
  8. package/dist/chunk-4AHZBTBF.cjs.map +1 -0
  9. package/dist/{chunk-PGEG7UZJ.cjs → chunk-4XPKKNJQ.cjs} +5 -5
  10. package/dist/chunk-4XPKKNJQ.cjs.map +1 -0
  11. package/dist/{chunk-52A5UNCH.js → chunk-7OHPBLPH.js} +13 -24
  12. package/dist/chunk-7OHPBLPH.js.map +1 -0
  13. package/dist/{chunk-ABHJW4LC.cjs → chunk-7VIXWVFN.cjs} +11 -14
  14. package/dist/chunk-7VIXWVFN.cjs.map +1 -0
  15. package/dist/chunk-AHEV52U7.cjs +122 -0
  16. package/dist/chunk-AHEV52U7.cjs.map +1 -0
  17. package/dist/{chunk-7YLY3G3E.js → chunk-BLQBFI7Z.js} +23 -25
  18. package/dist/chunk-BLQBFI7Z.js.map +1 -0
  19. package/dist/{chunk-VHULVDPG.js → chunk-EQ5BZTLV.js} +29 -14
  20. package/dist/chunk-EQ5BZTLV.js.map +1 -0
  21. package/dist/{chunk-6F4X2NKL.js → chunk-ESWHIC4Z.js} +5 -5
  22. package/dist/chunk-ESWHIC4Z.js.map +1 -0
  23. package/dist/chunk-G3QPOYDR.cjs +198 -0
  24. package/dist/chunk-G3QPOYDR.cjs.map +1 -0
  25. package/dist/{chunk-XS2SZINH.js → chunk-HEJPEJW7.js} +10 -13
  26. package/dist/chunk-HEJPEJW7.js.map +1 -0
  27. package/dist/{chunk-C3HR55JD.js → chunk-MMFHKCWF.js} +24 -13
  28. package/dist/chunk-MMFHKCWF.js.map +1 -0
  29. package/dist/{chunk-3VQV7ICQ.js → chunk-OI4IKH2Z.js} +18 -18
  30. package/dist/chunk-OI4IKH2Z.js.map +1 -0
  31. package/dist/{chunk-XGQKID2P.cjs → chunk-SGIUPGDA.cjs} +29 -18
  32. package/dist/chunk-SGIUPGDA.cjs.map +1 -0
  33. package/dist/chunk-T4CKODCJ.js +115 -0
  34. package/dist/chunk-T4CKODCJ.js.map +1 -0
  35. package/dist/{chunk-6DSA75TA.cjs → chunk-VVZ7SAE7.cjs} +35 -20
  36. package/dist/chunk-VVZ7SAE7.cjs.map +1 -0
  37. package/dist/chunk-WJTCIW5R.js +196 -0
  38. package/dist/chunk-WJTCIW5R.js.map +1 -0
  39. package/dist/{chunk-FB6XKQFV.cjs → chunk-ZDB3SLYR.cjs} +23 -23
  40. package/dist/chunk-ZDB3SLYR.cjs.map +1 -0
  41. package/dist/components/ActivityPreview.cjs +20 -0
  42. package/dist/components/ActivityPreview.cjs.map +1 -0
  43. package/dist/components/ActivityPreview.d.cts +70 -0
  44. package/dist/components/ActivityPreview.d.ts +70 -0
  45. package/dist/components/ActivityPreview.js +11 -0
  46. package/dist/components/ActivityPreview.js.map +1 -0
  47. package/dist/components/ActivitySequence.cjs +9 -8
  48. package/dist/components/ActivitySequence.d.cts +19 -6
  49. package/dist/components/ActivitySequence.d.ts +19 -6
  50. package/dist/components/ActivitySequence.js +8 -7
  51. package/dist/components/FillInTheBlanks.cjs +5 -4
  52. package/dist/components/FillInTheBlanks.d.cts +1 -1
  53. package/dist/components/FillInTheBlanks.d.ts +1 -1
  54. package/dist/components/FillInTheBlanks.js +4 -3
  55. package/dist/components/MultipleChoice.cjs +5 -4
  56. package/dist/components/MultipleChoice.d.cts +1 -1
  57. package/dist/components/MultipleChoice.d.ts +1 -1
  58. package/dist/components/MultipleChoice.js +4 -3
  59. package/dist/components/StimulusPanel.cjs +4 -3
  60. package/dist/components/StimulusPanel.d.cts +4 -2
  61. package/dist/components/StimulusPanel.d.ts +4 -2
  62. package/dist/components/StimulusPanel.js +3 -2
  63. package/dist/components/WrittenResponse.cjs +5 -4
  64. package/dist/components/WrittenResponse.d.cts +3 -3
  65. package/dist/components/WrittenResponse.d.ts +3 -3
  66. package/dist/components/WrittenResponse.js +4 -3
  67. package/dist/i18n/LkIntlProvider.cjs +33 -0
  68. package/dist/i18n/LkIntlProvider.cjs.map +1 -0
  69. package/dist/i18n/LkIntlProvider.d.cts +71 -0
  70. package/dist/i18n/LkIntlProvider.d.ts +71 -0
  71. package/dist/i18n/LkIntlProvider.js +4 -0
  72. package/dist/i18n/LkIntlProvider.js.map +1 -0
  73. package/dist/index.cjs +47 -17
  74. package/dist/index.cjs.map +1 -1
  75. package/dist/index.d.cts +5 -3
  76. package/dist/index.d.ts +5 -3
  77. package/dist/index.js +10 -8
  78. package/dist/index.js.map +1 -1
  79. package/dist/{types-eXX4hzSj.d.ts → strings-vQDqESTS.d.cts} +175 -11
  80. package/dist/{types-eXX4hzSj.d.cts → strings-vQDqESTS.d.ts} +175 -11
  81. package/dist/theme/skin.css +4 -4
  82. package/package.json +23 -3
  83. package/dist/chunk-3VQV7ICQ.js.map +0 -1
  84. package/dist/chunk-52A5UNCH.js.map +0 -1
  85. package/dist/chunk-6DSA75TA.cjs.map +0 -1
  86. package/dist/chunk-6F4X2NKL.js.map +0 -1
  87. package/dist/chunk-7YLY3G3E.js.map +0 -1
  88. package/dist/chunk-ABHJW4LC.cjs.map +0 -1
  89. package/dist/chunk-C3HR55JD.js.map +0 -1
  90. package/dist/chunk-FB6XKQFV.cjs.map +0 -1
  91. package/dist/chunk-PGEG7UZJ.cjs.map +0 -1
  92. package/dist/chunk-PPJ47THY.cjs.map +0 -1
  93. package/dist/chunk-UFWF3BKA.cjs.map +0 -1
  94. package/dist/chunk-VHULVDPG.js.map +0 -1
  95. package/dist/chunk-XGQKID2P.cjs.map +0 -1
  96. package/dist/chunk-XS2SZINH.js.map +0 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,111 @@
1
1
  # @intellectif/lk-react
2
2
 
3
+ ## 8.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - 8ca07c8: Authoring: tell an unfinished question from a wrong one.
8
+
9
+ `validateActivity` answers "may this be stored?", and to that question a question an author added a second ago and a broken one get the same answer. An editor that used it as its only check reported every new question as an error — enough to disable a whole form's Save button and blank its preview.
10
+
11
+ **lk-core**
12
+
13
+ - `validateDraft(type, draft)` returns `{ status: 'complete', data, issues: [] }` or `{ status: 'incomplete' | 'invalid', issues }`. Each issue is a `ValidationError` plus a `severity`, at the paths `validateActivity` reports. Its `code` is documented per type in the authoring guide for every problem the checks recognise; anything else is the schema's own diagnostic, at `invalid`. There is deliberately no `success` boolean, which would have to call an incomplete draft either a success or a failure.
14
+ - It is stricter than `validateActivity` and never looser. It reports a title or option text that is only whitespace, a blank written-response `prompt` even beside `promptHtml`, a rubric criterion name that is only whitespace, a rubric whose weights are all 0 or add up to more than a number can hold, a fill-in-the-blanks match `locale` that is not a language tag (scoring would throw on it), and `redacted: true`. A `null` in a required field reads as not set; any other `null` the schema refuses is `null_not_allowed`, for every registered type. It does not report `minWords: 0`, which means no lower limit, or a rubric weight above 1.
15
+ - `createDraft(type, { newId })` returns an empty draft that `validateDraft` reports as `incomplete`. It invents no ids, and a new multiple-choice draft marks no option correct, so an untouched correctness control can never become an answer key.
16
+ - `ActivityTypeDescriptor.authoring` — `createDraft` and `checkDraft` — gives a registered type the same support. The three built-in descriptors carry one.
17
+
18
+ **lk-react**
19
+
20
+ - `<ActivityPreview>`, also at `@intellectif/lk-react/components/ActivityPreview`, renders a draft in any `renderMode` with a simulated `response` — marked with `evaluate()` in `review` — and renders a notice, or your `fallback` with the issues, while the draft is not complete. Nothing it renders is recorded, and a recording's play limit is enforced in memory in `practice` and `exam`. It checks the draft by content on every render, whatever order its keys are in, so an editor that rebuilds or reloads its payload does not restart the question being tried.
21
+ - `LkStrings` gains `previewIncomplete` and `previewInvalid`. A dictionary declared as a complete `LkStrings` must add both; partial overrides are unaffected.
22
+
23
+ Nothing that already exists changes behaviour in either package. lk-react's major version comes from its lk-core peer range.
24
+
25
+ ### Patch Changes
26
+
27
+ - Updated dependencies [8ca07c8]
28
+ - @intellectif/lk-core@0.9.0
29
+
30
+ ## 7.1.0
31
+
32
+ ### Minor Changes
33
+
34
+ - db42d91: Translatable UI: `<LkIntlProvider>`, a `strings` prop on every component, and RTL support.
35
+
36
+ Until now every string the SDK rendered itself was an English literal, so a learner
37
+ sitting a Spanish paper read a Spanish passage inside an English scaffold, and a
38
+ listening exam could not say "No quedan reproducciones" at all. All 50 of those
39
+ strings are now one `LkStrings` type that you can replace.
40
+
41
+ ```tsx
42
+ import { LkIntlProvider } from "@intellectif/lk-react/i18n/LkIntlProvider";
43
+
44
+ <LkIntlProvider
45
+ locale="es"
46
+ strings={{
47
+ submit: "Enviar",
48
+ questionProgress: (index, total) => `Pregunta ${index} de ${total}`,
49
+ wordCount: (n) => `${n} ${n === 1 ? "palabra" : "palabras"}`,
50
+ media: { noPlaysRemaining: "No quedan reproducciones" },
51
+ }}
52
+ >
53
+ <ActivitySequence activities={paper} />
54
+ </LkIntlProvider>;
55
+ ```
56
+
57
+ **Nothing changes if you do nothing.** The English defaults are byte-identical to
58
+ what shipped before, a component without a provider still renders them, and the
59
+ `mediaStrings` / `mediaBudget.strings` props from 7.0.0 still work and still win
60
+ over the provider for the audio transport.
61
+
62
+ - **Interpolation and plurals are functions, not format strings.** No parser, no
63
+ catalogue format, no runtime dependency. A translation can reorder its
64
+ arguments — which a positional token cannot — and TypeScript checks the arity.
65
+ - **Precedence**: English defaults → provider → a component's own `strings` prop →
66
+ `mediaStrings` / `mediaBudget.strings`. Later wins key by key, and the `media`
67
+ and `stimulusKind` groups merge one level deep, so overriding one label never
68
+ blanks its siblings.
69
+ - **RTL**: the provider sets `lang` and derives `dir` from the locale (override
70
+ it with `direction`), and declares **neither** when you supplied neither — so
71
+ mounting it inside an `<html dir="rtl">` host purely to translate text cannot
72
+ flip that host's own subtree back to left-to-right. `skin.css` now uses logical
73
+ properties throughout — four physical declarations put the per-blank feedback
74
+ gap and a blank's tooltip on the wrong edge in a right-to-left page.
75
+ - **Only English is bundled.** The mechanism ships; the translations are yours.
76
+ Shipping locales nobody in this repository can review would put unreviewed words
77
+ in front of a learner on a summative paper.
78
+ - **Thrown errors stay English on purpose.** They address the developer who wired
79
+ the component up, not the learner, and translating them would make them
80
+ unsearchable.
81
+
82
+ New exports (barrel and `@intellectif/lk-react/i18n/LkIntlProvider`):
83
+ `LkIntlProvider`, `useLkStrings`, `useLkDirection`, `directionForLocale`,
84
+ `DEFAULT_STRINGS`, `mergeStrings`, and the `LkStrings` / `LkStringsOverride` /
85
+ `LkDirection` / `LkIntlProviderProps` types.
86
+
87
+ **Two learner-facing bugs found while proving the above, and fixed with it:**
88
+
89
+ - **Fill in the Blanks announced "No grade available." over a real grade.** A
90
+ `graded` outcome — a gap-fill marked by a human or an AI grader and handed
91
+ back — fell into the same arm as `unscorable`, so `review` mode told the
92
+ learner no grade existed while showing them one. It now announces the score,
93
+ normalised by `maxScore` so a grader payload of 8.5 / 10 does not read as 850%.
94
+ Multiple Choice gained the matching `unscorable` arm, which was silent.
95
+ - **The exam submit paths announced English regardless of your translation.**
96
+ `<MultipleChoice>` and `<FillInTheBlanks>` read the key in `practice` but held
97
+ a literal in `exam` — the mode a summative paper actually runs — and
98
+ `<WrittenResponse>`'s hand-in confirmation had no key at all. All three now go
99
+ through `LkStrings`, and the coverage test drives every submitting path in
100
+ every render mode so they cannot drift back.
101
+
102
+ Full reference: `docs/i18n.md`.
103
+
104
+ ### Patch Changes
105
+
106
+ - Updated dependencies [e1b9bb9]
107
+ - @intellectif/lk-core@0.8.2
108
+
3
109
  ## 7.0.1
4
110
 
5
111
  ### Patch Changes
package/README.md CHANGED
@@ -75,6 +75,7 @@ export function Demo() {
75
75
  - **`asRenderable()` / `asRenderableSequence()`** — the one documented bridge from a server's `redact()` projection to the `data` prop, so `as unknown as` stays out of your code.
76
76
  - **`shuffleSeed`** — one seed drives question order *and* each item's option order, so a review render reproduces exactly the arrangement the learner sat. Shuffling in `exam` / `review` mode **requires** it.
77
77
  - **`<StimulusPanel>`** — a shared passage / recording / image (an item group's stimulus) as a landmark region; the sequence uses it, and a custom runner can too.
78
+ - **`<ActivityPreview>`** — an editor's preview. Renders a draft in any `renderMode` with a simulated `response` (marked with `evaluate()` in `review`), and a translatable notice — or your `fallback`, handed the issues — while `validateDraft` says the draft is not finished. Nothing it renders is recorded, and a recording's play limit is counted in memory only.
78
79
  - **Custom activity types** — `renderers={{ 'my-type': MyRenderer }}` on the sequence; a key matching a built-in overrides it, so you can replace a bundled renderer without forking the sequencer.
79
80
  - **Rich text, opt-in** — `sanitizeHtml` renders author-supplied `questionHtml` / `promptHtml`. The SDK ships **no** sanitiser and injects no HTML without one; without it the escaped plain-text field is used. (`FillInTheBlanks` ignores `passageHtml` by design — its passage hosts the answer inputs.)
80
81
  - **Media per question** — optional `image` / `audio` / `video` / `embed` (YouTube/Vimeo iframe) above the question; alt-text required for `image`/`embed` (WCAG).
@@ -84,6 +85,7 @@ export function Demo() {
84
85
  - **`useActivityState()`** — `idle → in-progress → completed → reviewing` machine with `getTimeSpent()`.
85
86
  - **`<ThemeProvider>` + `defaults.css`** — `--lk-*` design-token system; automatic dark mode via `prefers-color-scheme` (SSR-safe with `useSyncExternalStore`).
86
87
  - **`createTailwindTheme(theme)`** — optional Tailwind interop; consume the SDK palette from your own utilities.
88
+ - **`<LkIntlProvider>`** — all 52 strings the SDK's own chrome renders, replaceable in one place, with a per-component `strings` prop for the exceptions. Interpolation and plurals are **functions**, not format strings, so your `Intl.PluralRules` does the work and TypeScript checks the arity. `locale` sets `lang` and derives `dir`; the skin uses logical properties, so RTL follows. The SDK ships the mechanism and **English only** — see [docs/i18n.md](https://github.com/intellectif/learning-kit/blob/main/docs/i18n.md).
87
89
  - **WCAG 2.2 AA** — axe-clean unit + Playwright e2e tests; full keyboard operability; numerically-verified contrast.
88
90
  - **RSC-compatible** — every component carries `'use client'` and hydrates inside a React Server Component tree.
89
91
 
@@ -95,16 +97,18 @@ export function Demo() {
95
97
  | `@intellectif/lk-react/components/MultipleChoice` | `<MultipleChoice>` (boundary-wrapped) |
96
98
  | `@intellectif/lk-react/components/FillInTheBlanks` | `<FillInTheBlanks>` (boundary-wrapped) |
97
99
  | `@intellectif/lk-react/components/WrittenResponse` | `<WrittenResponse>` (boundary-wrapped) |
100
+ | `@intellectif/lk-react/components/ActivityPreview` | `<ActivityPreview>` draft preview for editors |
98
101
  | `@intellectif/lk-react/components/ActivitySequence` | `<ActivitySequence>` question-set pager |
99
102
  | `@intellectif/lk-react/components/StimulusPanel` | `<StimulusPanel>` shared-stimulus region |
100
103
  | `@intellectif/lk-react/hooks/useActivityState` | Lifecycle + timing |
101
104
  | `@intellectif/lk-react/hooks/useXAPI` | LRS delivery (retry, never-throws) |
105
+ | `@intellectif/lk-react/i18n/LkIntlProvider` | `<LkIntlProvider>`, `useLkStrings`, `useLkDirection`, `DEFAULT_STRINGS`, `mergeStrings`, `directionForLocale` |
102
106
  | `@intellectif/lk-react/theme/ThemeProvider` | `<ThemeProvider>`, `darkTheme`, `useTheme`, `createTailwindTheme` |
103
107
  | `@intellectif/lk-react/theme/defaults.css` | Tokens (required) |
104
108
  | `@intellectif/lk-react/theme/skin.css` | Optional polished skin |
105
109
 
106
110
  ESM + CJS + `.d.ts` for every JS entry (the two `.css` entries are plain stylesheets).
107
- Tree-shakeable. Node >= 20, React `^19`.
111
+ Tree-shakeable. Node >= 20, React `^19`. In browsers, the SDK's own code needs `Object.hasOwn` — Chrome and Edge 93, Firefox 92, Safari 15.4 — because output targets ES2022 and nothing is polyfilled.
108
112
 
109
113
  `asRenderable`, `asRenderableSequence`, `RenderMode`, `ActivityProps` and the other
110
114
  shared types are exported from the **barrel** (`@intellectif/lk-react`); they have no
@@ -120,10 +124,10 @@ subpath of their own.
120
124
  | **Retry** | Pass a new `data` reference or change the React `key` → the activity resets to **its seeded state**, not to blank. With `defaultValue` / `defaultSubmitted` set, a new `key` and a new `data` reference are indistinguishable: both return the restored answer, still locked as submitted. For a genuinely fresh attempt, change the `key` **and stop passing the seeds**. No built-in button — retry *policy* is yours. |
121
125
  | **Persistence / resume** | Storage is yours — capture `onSubmit` / `onChange` / `onInteraction` / `onIndexChange` and persist as you wish. Re-hydration is supported at **both** levels: `defaultValue` / `value` + `onChange` on a component (since 2.1.0), and `defaultIndex` / `responses` / `submittedSlotIds` on `<ActivitySequence>` (since 6.0.0). |
122
126
  | **Rich text** | Rendered only when you pass `sanitizeHtml`; the SDK bundles no sanitiser and injects no HTML without one. `FillInTheBlanks` deliberately **ignores** `passageHtml` — the passage hosts the answer inputs, so it is built from `passage` plus the blanks (dev-mode warning if you pass it). |
123
- | **i18n** | `locale` sets the `lang` attribute on the rendered region and names xAPI statements. UI strings are English and not yet overridable, and there is no RTL-specific styling. |
127
+ | **i18n** | Every SDK-rendered string is replaceable through `<LkIntlProvider>` or a per-component `strings` prop (lk-react 7.1.0); `locale` sets `lang` and names xAPI statements. **No locale but English is bundled** — the SDK ships the mechanism and the English defaults, because it cannot review a translation it does not speak. RTL is supported: the provider derives `dir` from the locale — and declares neither `lang` nor `dir` when you supplied neither, so it cannot flip an RTL host back — and the skin uses logical properties throughout. Thrown errors stay English on purpose — they address the developer, not the learner. |
124
128
  | **Media** | `<audio>` / `<video>` are paused when the pager navigates away, preserving `currentTime` so a group resumes where the learner left it; nothing ever auto-plays. A provider `embed` (iframe) **cannot** be paused this way — controlling a third-party player needs its own JS API. Use `audio` / `video` for anything that must stop when the learner navigates. |
125
129
  | **Playback policy** | Audio only. `maxPlays` / `seek` / `rate` are enforced by the SDK's own transport — a refused play is stopped inside the browser's `play` event, before a sample is audible. `nativeControlHints` is **advisory**: it emits `controlsList`, which some engines ignore, and never prevents a download. A budget is durable only if you persist it through `mediaBudget.onPlayConsumed`; the SDK stores nothing. Nothing here survives devtools. Not implemented for `video` or `embed`. |
126
- | **Authoring / content storage / CDN / auth** | Consumer responsibility typed schemas + `validateActivity` + JSON Schema export are provided for you to build authoring on. |
130
+ | **Authoring / content storage / CDN / auth** | Consumer responsibility. The contracts to build an editor on are provided: typed schemas, `validateActivity` and JSON Schema export, `validateDraft` / `createDraft` in `lk-core`, and `<ActivityPreview>` here. |
127
131
  | **SSR / RSC** | Fully supported; every component carries `'use client'`. |
128
132
 
129
133
  ### Versioning note
@@ -143,6 +147,7 @@ before assuming a migration is needed.
143
147
  - [Changelog](https://github.com/intellectif/learning-kit/blob/main/packages/lk-react/CHANGELOG.md) — every release, with the reasoning.
144
148
  - [Authoring & content storage](https://github.com/intellectif/learning-kit/blob/main/docs/authoring.md)
145
149
  - [Styling](https://github.com/intellectif/learning-kit/blob/main/docs/styling.md) — tokens, the skin, overrides, dark mode, Tailwind.
150
+ - [Internationalisation](https://github.com/intellectif/learning-kit/blob/main/docs/i18n.md) — the full string surface, precedence, plurals, RTL.
146
151
  - [Project README](https://github.com/intellectif/learning-kit#readme) — full picture & monorepo layout.
147
152
 
148
153
  ## License
@@ -1,5 +1,5 @@
1
1
  import { XAPIStatement, WrittenResponseData, LearnerResponse, ItemOutcome, InteractionEvent, ThemeTokens } from '@intellectif/lk-core';
2
- import { b as Renderable, R as RenderMode, H as HtmlSanitizer, M as MediaBudgetBinding, a as MediaTransportStrings } from './types-eXX4hzSj.cjs';
2
+ import { c as Renderable, R as RenderMode, H as HtmlSanitizer, M as MediaBudgetBinding, b as MediaTransportStrings, a as LkStringsOverride } from './strings-vQDqESTS.cjs';
3
3
 
4
4
  /**
5
5
  * Payload delivered when the learner submits a written response. Grading is
@@ -87,6 +87,8 @@ interface WrittenResponseProps {
87
87
  mediaBudget?: MediaBudgetBinding;
88
88
  /** Translations for the audio transport chrome. See {@link MediaTransportStrings}. */
89
89
  mediaStrings?: Partial<MediaTransportStrings>;
90
+ /** Overrides the SDK's chrome text for this activity. See {@link LkIntlProvider}. */
91
+ strings?: LkStringsOverride;
90
92
  onInteraction?: (event: InteractionEvent) => void;
91
93
  /** Per-instance token overrides, applied as inline CSS vars on the root. */
92
94
  theme?: Partial<ThemeTokens>;
@@ -1,5 +1,5 @@
1
1
  import { XAPIStatement, WrittenResponseData, LearnerResponse, ItemOutcome, InteractionEvent, ThemeTokens } from '@intellectif/lk-core';
2
- import { b as Renderable, R as RenderMode, H as HtmlSanitizer, M as MediaBudgetBinding, a as MediaTransportStrings } from './types-eXX4hzSj.js';
2
+ import { c as Renderable, R as RenderMode, H as HtmlSanitizer, M as MediaBudgetBinding, b as MediaTransportStrings, a as LkStringsOverride } from './strings-vQDqESTS.js';
3
3
 
4
4
  /**
5
5
  * Payload delivered when the learner submits a written response. Grading is
@@ -87,6 +87,8 @@ interface WrittenResponseProps {
87
87
  mediaBudget?: MediaBudgetBinding;
88
88
  /** Translations for the audio transport chrome. See {@link MediaTransportStrings}. */
89
89
  mediaStrings?: Partial<MediaTransportStrings>;
90
+ /** Overrides the SDK's chrome text for this activity. See {@link LkIntlProvider}. */
91
+ strings?: LkStringsOverride;
90
92
  onInteraction?: (event: InteractionEvent) => void;
91
93
  /** Per-instance token overrides, applied as inline CSS vars on the root. */
92
94
  theme?: Partial<ThemeTokens>;
@@ -1,29 +1,11 @@
1
1
  'use client';
2
2
  'use strict';
3
3
 
4
+ var chunkAHEV52U7_cjs = require('./chunk-AHEV52U7.cjs');
4
5
  var lkCore = require('@intellectif/lk-core');
5
6
  var react = require('react');
6
7
  var jsxRuntime = require('react/jsx-runtime');
7
8
 
8
- // src/components/shared/ActivityMedia.tsx
9
- var DEFAULT_STRINGS = {
10
- play: "Play",
11
- pause: "Pause",
12
- preparing: "Preparing\u2026",
13
- mute: "Mute",
14
- unmute: "Unmute",
15
- volume: "Volume",
16
- speed: "Playback speed",
17
- seek: "Seek",
18
- playsRemaining: (remaining, max) => `${remaining} of ${max} play${max === 1 ? "" : "s"} remaining`,
19
- noPlaysRemaining: "No plays remaining",
20
- lastPlayConfirm: "This is your last play. Start it now?",
21
- lastPlayStart: "Start last play",
22
- lastPlayCancel: "Not yet",
23
- seekBlocked: "Rewinding is not available for this recording.",
24
- rateBlocked: "Playback speed is fixed for this recording.",
25
- playFailed: "The recording could not be started. Try again."
26
- };
27
9
  var RESUME_EPSILON = 0.25;
28
10
  var SEEK_EPSILON = 0.35;
29
11
  var NO_PROGRESS = 0.25;
@@ -42,12 +24,18 @@ function AudioTransport({
42
24
  locale,
43
25
  mediaBudget,
44
26
  mediaStrings,
27
+ strings,
45
28
  onInteraction
46
29
  }) {
47
30
  const elementRef = react.useRef(null);
48
31
  const statusId = react.useId();
49
32
  const suppliedStrings = mediaBudget?.strings ?? mediaStrings;
50
- const s = { ...DEFAULT_STRINGS, ...mediaStrings, ...mediaBudget?.strings };
33
+ const fromProvider = chunkAHEV52U7_cjs.useLkStrings(strings);
34
+ const s = {
35
+ ...fromProvider.media,
36
+ ...mediaStrings,
37
+ ...mediaBudget?.strings
38
+ };
51
39
  const enforced = mediaBudget?.enforced ?? renderMode !== "review";
52
40
  const budgeted = enforced && policy.maxPlays !== null && mediaBudget !== void 0;
53
41
  const maxPlays = policy.maxPlays ?? 0;
@@ -399,7 +387,7 @@ function AudioTransport({
399
387
  step: 0.1,
400
388
  value: Math.min(elapsed, duration || 0),
401
389
  "aria-label": s.seek,
402
- "aria-valuetext": `${formatTime(elapsed)} of ${formatTime(duration)}`,
390
+ "aria-valuetext": s.timeValue(formatTime(elapsed), formatTime(duration)),
403
391
  onChange: (event) => {
404
392
  const element = elementRef.current;
405
393
  if (element !== null) {
@@ -486,6 +474,7 @@ function AudioTransport({
486
474
  }
487
475
  function ActivityMedia(props) {
488
476
  const { media, renderMode = "practice" } = props;
477
+ const s = chunkAHEV52U7_cjs.useLkStrings(props.strings);
489
478
  if (media.type === "image") {
490
479
  return /* @__PURE__ */ jsxRuntime.jsx("figure", { className: "lk-media", children: /* @__PURE__ */ jsxRuntime.jsx("img", { className: "lk-media-el", src: media.url, alt: media.alt ?? "" }) });
491
480
  }
@@ -522,7 +511,7 @@ function ActivityMedia(props) {
522
511
  {
523
512
  className: "lk-media-el",
524
513
  src: media.url,
525
- title: media.alt ?? "Embedded media",
514
+ title: media.alt ?? s.embeddedMedia,
526
515
  loading: "lazy",
527
516
  referrerPolicy: "strict-origin-when-cross-origin",
528
517
  sandbox: "allow-scripts allow-same-origin allow-presentation",
@@ -538,5 +527,5 @@ function ActivityMedia(props) {
538
527
  }
539
528
 
540
529
  exports.ActivityMedia = ActivityMedia;
541
- //# sourceMappingURL=chunk-UFWF3BKA.cjs.map
542
- //# sourceMappingURL=chunk-UFWF3BKA.cjs.map
530
+ //# sourceMappingURL=chunk-2VLKSNYJ.cjs.map
531
+ //# sourceMappingURL=chunk-2VLKSNYJ.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/shared/AudioTransport.tsx","../src/components/shared/ActivityMedia.tsx"],"names":["useRef","useId","useLkStrings","useState","useCallback","useEffect","jsxs","jsx","resolvePlaybackPolicy"],"mappings":"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","file":"chunk-2VLKSNYJ.cjs","sourcesContent":["'use client';\n\nimport type {\n ActivityMedia as ActivityMediaData,\n MediaPlayClaim,\n ResolvedPlaybackPolicy,\n} from '@intellectif/lk-core';\nimport { useCallback, useEffect, useId, useRef, useState } from 'react';\nimport { useLkStrings } from '../../i18n/LkIntlProvider.js';\nimport type { LkStringsOverride } from '../../i18n/strings.js';\nimport type { MediaBudgetBinding, MediaTransportStrings } from '../types.js';\n\n/**\n * The SDK's own audio player, used whenever a policy has something to enforce.\n *\n * The browser's control bar cannot express a spent budget — its play button\n * stays enabled — and a control that looks operable and does nothing is a\n * WCAG 3.2.2 / 4.1.3 failure, so an enforcing policy replaces the bar rather\n * than lying with it. What replaces it is a SUPERSET minus what the policy\n * removes on purpose: play/pause, elapsed and total time, mute, volume, an\n * optional speed control, an optional scrubber, and a live plays-remaining\n * status. Volume and mute are never restricted by any policy — they affect no\n * assessment property, and a learner in a lab with a locked OS volume has no\n * other lever once the native bar is gone.\n */\n\n/** A resume is a start within a quarter-second of where playback last stopped. */\nconst RESUME_EPSILON = 0.25;\n/** Below this, a seek is the browser settling rather than the learner moving. */\nconst SEEK_EPSILON = 0.35;\n/** A charged play that errors before this much audio played produced nothing. */\nconst NO_PROGRESS = 0.25;\n\nconst formatTime = (seconds: number): string => {\n if (!Number.isFinite(seconds) || seconds < 0) {\n return '0:00';\n }\n const whole = Math.floor(seconds);\n return `${Math.floor(whole / 60)}:${String(whole % 60).padStart(2, '0')}`;\n};\n\nexport interface AudioTransportProps {\n media: ActivityMediaData;\n policy: ResolvedPlaybackPolicy;\n renderMode?: 'practice' | 'exam' | 'review';\n disabled?: boolean;\n locale?: string;\n mediaBudget?: MediaBudgetBinding;\n mediaStrings?: Partial<MediaTransportStrings>;\n /** Overrides the SDK's chrome text. See {@link LkIntlProvider}. */\n strings?: LkStringsOverride;\n onInteraction?: (event: {\n type: string;\n activityId: string;\n timestamp: number;\n payload: Record<string, unknown>;\n }) => void;\n}\n\nexport function AudioTransport({\n media,\n policy,\n renderMode = 'practice',\n disabled = false,\n locale,\n mediaBudget,\n mediaStrings,\n strings,\n onInteraction,\n}: AudioTransportProps): React.JSX.Element {\n const elementRef = useRef<HTMLAudioElement>(null);\n const statusId = useId();\n\n // Provider first, then the narrower props that shipped in 0.8.0 — so an\n // existing `mediaStrings` call site still wins over a provider-wide default\n // and keeps behaving exactly as it did.\n const suppliedStrings = mediaBudget?.strings ?? mediaStrings;\n const fromProvider = useLkStrings(strings);\n const s: MediaTransportStrings = {\n ...fromProvider.media,\n ...mediaStrings,\n ...mediaBudget?.strings,\n };\n\n const enforced = mediaBudget?.enforced ?? renderMode !== 'review';\n const budgeted = enforced && policy.maxPlays !== null && mediaBudget !== undefined;\n const maxPlays = policy.maxPlays ?? 0;\n\n // Written synchronously inside handlers and mirrored into state for\n // rendering, for the same reason `ActivitySequence` keeps `outcomesRef` in a\n // ref: two events in one tick would otherwise both read the pre-update value.\n const usedRef = useRef(mediaBudget?.entry?.plays ?? 0);\n const pendingRef = useRef(false);\n const resumeAtRef = useRef<number | null>(null);\n const highWaterRef = useRef(0);\n const chargeRef = useRef<{ claim: MediaPlayClaim; at: number } | null>(null);\n const seededRef = useRef(false);\n const lastPositionRef = useRef(-1);\n // Marks a `play()` this component itself issued after charging. The element's\n // `play` event is where the budget is enforced — so that a media key or a\n // scripted `play()` is charged too — which means our OWN start re-enters it.\n // In the confirmed tier `pendingRef` covered that; in the optimistic tier\n // nothing did, and `remaining` is still the pre-charge value in that closure,\n // so a single press was charged twice.\n const selfStartRef = useRef(false);\n\n const [used, setUsed] = useState(usedRef.current);\n const [playing, setPlaying] = useState(false);\n const [pending, setPending] = useState(false);\n const [elapsed, setElapsed] = useState(0);\n const [duration, setDuration] = useState(0);\n const [muted, setMuted] = useState(false);\n const [volume, setVolume] = useState(1);\n const [confirming, setConfirming] = useState(false);\n const [notice, setNotice] = useState('');\n // A repeated identical message must announce again, so the alert node is\n // REPLACED rather than mutated. Screen readers ignore an unchanged one.\n const [noticeNonce, setNoticeNonce] = useState(0);\n\n const remaining = Math.max(0, maxPlays - used);\n\n const announce = useCallback((message: string) => {\n setNotice(message);\n setNoticeNonce((n) => n + 1);\n }, []);\n\n const emit = useCallback(\n (type: string, playsUsed: number) => {\n if (!budgeted || mediaBudget === undefined || onInteraction === undefined) {\n return;\n }\n onInteraction({\n type,\n activityId: mediaBudget.activityId ?? mediaBudget.slotId,\n timestamp: Date.now(),\n payload: {\n mediaKey: mediaBudget.key,\n mediaType: 'audio',\n playsUsed,\n maxPlays,\n playsRemaining: Math.max(0, maxPlays - playsUsed),\n },\n });\n },\n [budgeted, mediaBudget, maxPlays, onInteraction],\n );\n\n const reportPosition = useCallback(\n (seconds: number) => {\n mediaBudget?.onPosition?.(mediaBudget.key, seconds);\n },\n [mediaBudget],\n );\n\n /** A start within a hair of where playback last stopped is a resume, not a play. */\n const isResume = useCallback((element: HTMLAudioElement): boolean => {\n const at = resumeAtRef.current;\n return at !== null && Math.abs(element.currentTime - at) < RESUME_EPSILON;\n }, []);\n\n const buildClaim = useCallback((): MediaPlayClaim | null => {\n if (mediaBudget === undefined) {\n return null;\n }\n return {\n key: mediaBudget.key,\n previousPlaysUsed: usedRef.current,\n playsUsed: usedRef.current + 1,\n maxPlays,\n playsRemaining: Math.max(0, maxPlays - (usedRef.current + 1)),\n slotId: mediaBudget.slotId,\n index: mediaBudget.index,\n ...(mediaBudget.activityId !== undefined ? { activityId: mediaBudget.activityId } : {}),\n };\n }, [mediaBudget, maxPlays]);\n\n const refuse = useCallback(() => {\n announce(s.noPlaysRemaining);\n emit('media-play-refused', usedRef.current);\n }, [announce, emit, s.noPlaysRemaining]);\n\n const startPlayback = useCallback(() => {\n const element = elementRef.current;\n if (element === null) {\n return;\n }\n highWaterRef.current = element.currentTime;\n selfStartRef.current = true;\n void element.play().catch(() => {\n selfStartRef.current = false;\n /* autoplay policies and races surface through the `error` handler */\n });\n }, []);\n\n const commit = useCallback(\n (claim: MediaPlayClaim) => {\n const element = elementRef.current;\n chargeRef.current = { claim, at: element?.currentTime ?? 0 };\n usedRef.current = claim.playsUsed;\n setUsed(claim.playsUsed);\n emit('media-play-consumed', claim.playsUsed);\n },\n [emit],\n );\n\n /**\n * Charges a play, then starts it.\n *\n * The confirmed tier holds playback until the consumer's atomic write\n * settles, which is the only construction that catches a second tab: two\n * mounts seeded at 0 both claim 1, and only the server can tell them apart.\n */\n const claimAndStart = useCallback(\n (element: HTMLAudioElement, alreadyPlaying: boolean) => {\n const claim = buildClaim();\n if (claim === null) {\n return;\n }\n const result = mediaBudget?.onPlayConsumed?.(claim);\n\n if (result !== undefined && typeof (result as Promise<unknown>).then === 'function') {\n if (alreadyPlaying) {\n element.pause();\n }\n pendingRef.current = true;\n setPending(true);\n void (result as Promise<{ playsUsed: number } | undefined>)\n .then((grant) => {\n const granted =\n grant !== undefined && Number.isInteger(grant.playsUsed)\n ? grant.playsUsed\n : claim.playsUsed;\n if (granted > maxPlays) {\n usedRef.current = granted;\n setUsed(granted);\n refuse();\n return;\n }\n commit({ ...claim, playsUsed: granted });\n startPlayback();\n })\n .catch(() => {\n // Nothing charged: the learner may retry.\n announce(s.playFailed);\n emit('media-play-errored', usedRef.current);\n })\n .finally(() => {\n pendingRef.current = false;\n setPending(false);\n });\n return;\n }\n\n commit(claim);\n if (!alreadyPlaying) {\n startPlayback();\n }\n },\n [\n buildClaim,\n mediaBudget,\n maxPlays,\n commit,\n startPlayback,\n refuse,\n announce,\n emit,\n s.playFailed,\n ],\n );\n\n const handleButton = useCallback(() => {\n const element = elementRef.current;\n if (element === null || pendingRef.current || disabled) {\n return;\n }\n if (!element.paused) {\n element.pause();\n return;\n }\n if (!budgeted || isResume(element)) {\n startPlayback();\n return;\n }\n if (remaining === 0) {\n refuse();\n return;\n }\n if (remaining === 1 && !confirming) {\n setConfirming(true);\n return;\n }\n setConfirming(false);\n claimAndStart(element, false);\n }, [disabled, budgeted, isResume, startPlayback, remaining, refuse, confirming, claimAndStart]);\n\n // Seed the restored position once metadata is known, BEFORE the seek guard\n // can see it — otherwise the guard reverts the very restore it was given.\n const handleLoadedMetadata = useCallback(() => {\n const element = elementRef.current;\n if (element === null) {\n return;\n }\n setDuration(Number.isFinite(element.duration) ? element.duration : 0);\n const at = mediaBudget?.entry?.at;\n if (!seededRef.current && typeof at === 'number' && Number.isFinite(at) && at > 0) {\n seededRef.current = true;\n highWaterRef.current = at;\n resumeAtRef.current = at;\n element.currentTime = at;\n setElapsed(at);\n }\n }, [mediaBudget]);\n\n /**\n * The budget lives on the ELEMENT's `play` event, not on the button, so a\n * hardware media key, a browser extension or a scripted `el.play()` is\n * charged too — and an exhausted budget stops playback inside the event,\n * before a sample is audible.\n */\n const handlePlay = useCallback(() => {\n const element = elementRef.current;\n if (element === null) {\n return;\n }\n if (disabled) {\n element.pause();\n return;\n }\n if (selfStartRef.current) {\n selfStartRef.current = false;\n setPlaying(true);\n return;\n }\n if (pendingRef.current) {\n setPlaying(true);\n return;\n }\n if (isResume(element)) {\n setPlaying(true);\n resumeAtRef.current = null;\n return;\n }\n if (!budgeted) {\n setPlaying(true);\n return;\n }\n if (remaining === 0) {\n element.pause();\n refuse();\n return;\n }\n setPlaying(true);\n claimAndStart(element, true);\n }, [disabled, isResume, budgeted, remaining, refuse, claimAndStart]);\n\n const handlePause = useCallback(() => {\n const element = elementRef.current;\n setPlaying(false);\n if (element === null) {\n return;\n }\n // Every pause records the position — learner, browser, or the pager's own\n // pause as a pane hides. Without this, paging through a six-question\n // listening group and back would exhaust the budget by navigation alone.\n resumeAtRef.current = element.currentTime;\n reportPosition(element.currentTime);\n }, [reportPosition]);\n\n const handleEnded = useCallback(() => {\n setPlaying(false);\n resumeAtRef.current = null;\n highWaterRef.current = 0;\n chargeRef.current = null;\n setElapsed(0);\n reportPosition(0);\n }, [reportPosition]);\n\n const handleTimeUpdate = useCallback(() => {\n const element = elementRef.current;\n if (element === null) {\n return;\n }\n setElapsed(element.currentTime);\n highWaterRef.current = Math.max(highWaterRef.current, element.currentTime);\n const whole = Math.floor(element.currentTime);\n if (whole !== lastPositionRef.current) {\n lastPositionRef.current = whole;\n reportPosition(element.currentTime);\n }\n }, [reportPosition]);\n\n const handleSeeking = useCallback(() => {\n const element = elementRef.current;\n if (element === null || policy.seek !== 'none') {\n return;\n }\n if (Math.abs(element.currentTime - highWaterRef.current) > SEEK_EPSILON) {\n element.currentTime = highWaterRef.current;\n announce(s.seekBlocked);\n }\n }, [policy.seek, announce, s.seekBlocked]);\n\n const handleRateChange = useCallback(() => {\n const element = elementRef.current;\n if (element === null || policy.rate !== 'fixed' || element.playbackRate === 1) {\n return;\n }\n element.playbackRate = 1;\n announce(s.rateBlocked);\n }, [policy.rate, announce, s.rateBlocked]);\n\n const handleError = useCallback(() => {\n const charge = chargeRef.current;\n const element = elementRef.current;\n // Ordering matters: an element can error with nothing charged (a bad URL\n // the learner never pressed play on), so the charge must be proven to\n // exist before its position is read.\n const progressed =\n charge !== null && element !== null && element.currentTime - charge.at >= NO_PROGRESS;\n if (charge !== null && !progressed) {\n if (mediaBudget?.onPlayRefunded !== undefined) {\n usedRef.current = charge.claim.previousPlaysUsed;\n setUsed(charge.claim.previousPlaysUsed);\n mediaBudget.onPlayRefunded(charge.claim);\n emit('media-play-refunded', charge.claim.previousPlaysUsed);\n } else {\n emit('media-play-errored', usedRef.current);\n }\n }\n chargeRef.current = null;\n setPlaying(false);\n announce(s.playFailed);\n }, [mediaBudget, emit, announce, s.playFailed]);\n\n // Keep the element's volume in step with the controls.\n useEffect(() => {\n const element = elementRef.current;\n if (element !== null) {\n element.volume = volume;\n element.muted = muted;\n }\n }, [volume, muted]);\n\n const exhausted = budgeted && remaining === 0;\n const buttonLabel = pending ? s.preparing : playing ? s.pause : s.play;\n\n return (\n <figure className=\"lk-media\" data-controls=\"minimal\">\n {/* biome-ignore lint/a11y/useMediaCaption: captions are optional in the data contract — a <track> is rendered when captionsUrl is provided; absence is the author's documented choice (Req 14.5) */}\n <audio\n ref={elementRef}\n className=\"lk-media-el\"\n preload=\"metadata\"\n aria-label={media.alt || undefined}\n onLoadedMetadata={handleLoadedMetadata}\n onPlay={handlePlay}\n onPause={handlePause}\n onEnded={handleEnded}\n onTimeUpdate={handleTimeUpdate}\n onSeeking={handleSeeking}\n onRateChange={handleRateChange}\n onError={handleError}\n >\n <source src={media.url} />\n {media.captionsUrl ? <track kind=\"captions\" src={media.captionsUrl} default /> : null}\n </audio>\n\n {/* A timeline runs with the audio, not with the script. Forcing LTR here\n fixes seek direction for RTL locales by construction; the surrounding\n text keeps the document direction. */}\n <div\n className=\"lk-media-transport\"\n dir=\"ltr\"\n {...(suppliedStrings !== undefined && locale !== undefined ? { lang: locale } : {})}\n >\n <button\n type=\"button\"\n className=\"lk-media-play\"\n onClick={handleButton}\n // NEVER `disabled`: a natively disabled button leaves the focus\n // order, so a learner who tabs into nothing is told nothing — and\n // disabling it mid-attempt would drop focus to <body>. Kept\n // focusable, it re-announces the refusal when pressed.\n aria-disabled={exhausted || disabled || undefined}\n aria-busy={pending || undefined}\n aria-label={exhausted ? `${s.play} — ${s.noPlaysRemaining}` : undefined}\n aria-describedby={budgeted ? statusId : undefined}\n >\n {buttonLabel}\n </button>\n\n <span className=\"lk-media-time\">\n {formatTime(elapsed)} / {formatTime(duration)}\n </span>\n\n {policy.seek === 'allow' ? (\n <input\n type=\"range\"\n className=\"lk-media-scrub\"\n min={0}\n max={duration || 0}\n step={0.1}\n value={Math.min(elapsed, duration || 0)}\n aria-label={s.seek}\n aria-valuetext={s.timeValue(formatTime(elapsed), formatTime(duration))}\n onChange={(event) => {\n const element = elementRef.current;\n if (element !== null) {\n element.currentTime = Number(event.target.value);\n }\n }}\n />\n ) : null}\n\n <button\n type=\"button\"\n className=\"lk-media-mute\"\n aria-pressed={muted}\n onClick={() => setMuted((m) => !m)}\n >\n {muted ? s.unmute : s.mute}\n </button>\n\n <input\n type=\"range\"\n className=\"lk-media-volume\"\n min={0}\n max={1}\n step={0.05}\n value={volume}\n aria-label={s.volume}\n onChange={(event) => setVolume(Number(event.target.value))}\n />\n\n {policy.rate === 'allow' ? (\n <select\n className=\"lk-media-rate\"\n aria-label={s.speed}\n defaultValue=\"1\"\n onChange={(event) => {\n const element = elementRef.current;\n if (element !== null) {\n element.playbackRate = Number(event.target.value);\n }\n }}\n >\n {['0.75', '1', '1.25', '1.5'].map((rate) => (\n <option key={rate} value={rate}>\n {rate}×\n </option>\n ))}\n </select>\n ) : null}\n </div>\n\n {budgeted ? (\n <p className=\"lk-media-plays\" id={statusId} role=\"status\">\n {remaining > 0 ? s.playsRemaining(remaining, maxPlays) : s.noPlaysRemaining}\n </p>\n ) : null}\n\n {/* Separate from the polite status on purpose: a refusal is a direct\n response to a user action and must not be the update a screen reader\n drops when two polite regions compete. */}\n <p className=\"lk-media-notice\" role=\"alert\" key={noticeNonce}>\n {notice}\n </p>\n\n {confirming ? (\n <fieldset className=\"lk-media-confirm\">\n <legend>{s.lastPlayConfirm}</legend>\n <button\n type=\"button\"\n className=\"lk-media-confirm-start\"\n // biome-ignore lint/a11y/noAutofocus: focus must land on the confirm action the press opened, or a keyboard learner is stranded on a button whose meaning just changed\n autoFocus\n onClick={() => {\n const element = elementRef.current;\n setConfirming(false);\n if (element !== null) {\n claimAndStart(element, false);\n }\n }}\n >\n {s.lastPlayStart}\n </button>\n <button\n type=\"button\"\n className=\"lk-media-confirm-cancel\"\n onClick={() => setConfirming(false)}\n >\n {s.lastPlayCancel}\n </button>\n </fieldset>\n ) : null}\n </figure>\n );\n}\n","import type { ActivityMedia as ActivityMediaData, InteractionEvent } from '@intellectif/lk-core';\nimport { resolvePlaybackPolicy } from '@intellectif/lk-core';\nimport { useLkStrings } from '../../i18n/LkIntlProvider.js';\nimport type { LkStringsOverride } from '../../i18n/strings.js';\nimport type { MediaBudgetBinding, MediaTransportStrings, RenderMode } from '../types.js';\nimport { AudioTransport } from './AudioTransport.js';\n\n/**\n * Presentational media block shown above a question or passage. URL-only:\n * the SDK does not host media (consumer responsibility). Accessibility:\n * images use `alt` (schema requires it non-empty); audio/video expose an\n * optional label and a captions `<track>` when `captionsUrl` is provided;\n * `embed` renders a responsive sandboxed iframe with `alt` as its required\n * accessible `title` (Req 14.5).\n *\n * Audio additionally carries a playback policy (`media.playback`). With no\n * policy the emitted element is character-identical to what every release\n * before 0.8.0 produced; with something to enforce, the browser's control bar\n * is replaced by {@link AudioTransport}.\n */\nexport interface ActivityMediaProps {\n media: ActivityMediaData;\n renderMode?: RenderMode;\n disabled?: boolean;\n locale?: string;\n mediaBudget?: MediaBudgetBinding;\n mediaStrings?: Partial<MediaTransportStrings>;\n /** Overrides the SDK's chrome text. See {@link LkIntlProvider}. */\n strings?: LkStringsOverride;\n onInteraction?: (event: InteractionEvent) => void;\n}\n\nexport function ActivityMedia(props: ActivityMediaProps): React.JSX.Element {\n const { media, renderMode = 'practice' } = props;\n // Read before the branches: every return below is conditional, and the one\n // string this component owns itself (the embed's accessible name) is on the\n // last of them.\n const s = useLkStrings(props.strings);\n\n if (media.type === 'image') {\n return (\n <figure className=\"lk-media\">\n <img className=\"lk-media-el\" src={media.url} alt={media.alt ?? ''} />\n </figure>\n );\n }\n\n if (media.type === 'audio') {\n const policy = resolvePlaybackPolicy(media);\n\n // BEFORE the branch below, not after it. A budgeted policy always resolves\n // to `controls: 'minimal'`, so a guard placed after that early return\n // could never fire for the one case it exists to catch — the transport\n // rendered happily with no binding, counting nothing, while the paper\n // believed it had a budget.\n if (policy.maxPlays !== null && renderMode === 'exam' && props.mediaBudget === undefined) {\n throw new Error(\n `ActivityMedia: audio ${JSON.stringify(media.url)} declares maxPlays and is rendered in ` +\n 'renderMode \"exam\" without a `mediaBudget` binding. Nothing would persist the count, so ' +\n 'a refresh restores the full budget while the page says plays remain. Render it through ' +\n '<ActivitySequence mediaBudget={…}> or pass `mediaBudget` yourself.',\n );\n }\n\n // REVIEW ALWAYS GETS THE NATIVE BAR. The paper is graded; a learner\n // reviewing it cannot change an answer by listening again, so taking away\n // their scrubber, their speed control and their browser-localized controls\n // buys nothing — and costs a learner with a processing disability the\n // ability to work out what they got wrong.\n if (policy.controls === 'minimal' && renderMode !== 'review') {\n return <AudioTransport {...props} media={media} policy={policy} />;\n }\n\n const hints = renderMode === 'review' ? [] : policy.nativeControlHints;\n return (\n <figure className=\"lk-media\">\n {/* biome-ignore lint/a11y/useMediaCaption: captions are optional in the data contract — a <track> is rendered when captionsUrl is provided; absence is the author's documented choice (Req 14.5) */}\n <audio\n className=\"lk-media-el\"\n controls\n aria-label={media.alt || undefined}\n {...(hints.length > 0\n ? {\n controlsList: hints\n .map((hint) => (hint === 'hide-download' ? 'nodownload' : 'noplaybackrate'))\n .join(' '),\n }\n : {})}\n >\n <source src={media.url} />\n {media.captionsUrl ? <track kind=\"captions\" src={media.captionsUrl} default /> : null}\n </audio>\n </figure>\n );\n }\n\n if (media.type === 'embed') {\n return (\n <figure className=\"lk-media\">\n <div className=\"lk-media-embed\">\n <iframe\n className=\"lk-media-el\"\n src={media.url}\n title={media.alt ?? s.embeddedMedia}\n loading=\"lazy\"\n referrerPolicy=\"strict-origin-when-cross-origin\"\n // The docblock always promised a sandboxed iframe; now it is one.\n // allow-scripts + allow-same-origin are required by provider\n // players (YouTube/Vimeo); top-navigation, forms, downloads and\n // popups stay blocked. Scheme allow-listing in MediaSchema is the\n // primary defense; this is depth.\n sandbox=\"allow-scripts allow-same-origin allow-presentation\"\n allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\"\n allowFullScreen\n />\n </div>\n </figure>\n );\n }\n\n return (\n <figure className=\"lk-media\">\n {/* biome-ignore lint/a11y/useMediaCaption: captions are optional in the data contract — a <track> is rendered when captionsUrl is provided; absence is the author's documented choice (Req 14.5) */}\n <video className=\"lk-media-el\" controls aria-label={media.alt || undefined}>\n <source src={media.url} />\n {media.captionsUrl ? <track kind=\"captions\" src={media.captionsUrl} default /> : null}\n </video>\n </figure>\n );\n}\n"]}
@@ -1,8 +1,9 @@
1
1
  'use client';
2
2
  'use strict';
3
3
 
4
- var chunkPGEG7UZJ_cjs = require('./chunk-PGEG7UZJ.cjs');
5
- var chunkUFWF3BKA_cjs = require('./chunk-UFWF3BKA.cjs');
4
+ var chunk4XPKKNJQ_cjs = require('./chunk-4XPKKNJQ.cjs');
5
+ var chunk2VLKSNYJ_cjs = require('./chunk-2VLKSNYJ.cjs');
6
+ var chunkAHEV52U7_cjs = require('./chunk-AHEV52U7.cjs');
6
7
  var chunkXOR4MPEN_cjs = require('./chunk-XOR4MPEN.cjs');
7
8
  var lkCore = require('@intellectif/lk-core');
8
9
  var react = require('react');
@@ -15,7 +16,7 @@ function criterionPercent(criterion) {
15
16
  const max = criterion.maxScore !== void 0 && criterion.maxScore > 0 ? criterion.maxScore : 1;
16
17
  return `${Math.round(criterion.score / max * 100)}%`;
17
18
  }
18
- function GradeBody({ grade }) {
19
+ function GradeBody({ grade, s }) {
19
20
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
20
21
  grade.feedback ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "lk-wr-grade-feedback", children: grade.feedback }) : null,
21
22
  grade.criteria && grade.criteria.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "lk-wr-criteria", children: grade.criteria.map((criterion) => /* @__PURE__ */ jsxRuntime.jsxs(
@@ -25,7 +26,7 @@ function GradeBody({ grade }) {
25
26
  "data-na": String(criterion.notApplicable === true),
26
27
  children: [
27
28
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "lk-wr-criterion-name", children: criterion.name }),
28
- criterion.notApplicable === true ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "lk-wr-criterion-score", children: "Not applicable" }) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "lk-wr-criterion-score", children: criterion.band ?? (typeof criterion.score === "number" ? criterionPercent(criterion) : "") }),
29
+ criterion.notApplicable === true ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "lk-wr-criterion-score", children: s.notApplicable }) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "lk-wr-criterion-score", children: criterion.band ?? (typeof criterion.score === "number" ? criterionPercent(criterion) : "") }),
29
30
  criterion.comment ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "lk-wr-criterion-comment", children: criterion.comment }) : null
30
31
  ]
31
32
  },
@@ -37,10 +38,10 @@ function GradeBody({ grade }) {
37
38
  /* @__PURE__ */ jsxRuntime.jsx("ins", { className: "lk-wr-correction-corrected", children: correction.corrected }),
38
39
  correction.explanation ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "lk-wr-correction-explanation", children: correction.explanation }) : null
39
40
  ] }, `${correction.original}:${correction.corrected}`)) }) : null,
40
- grade.requiresHumanReview === true ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "lk-wr-review-flag", children: "This grade is awaiting review by a teacher." }) : null
41
+ grade.requiresHumanReview === true ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "lk-wr-review-flag", children: s.awaitingHumanReview }) : null
41
42
  ] });
42
43
  }
43
- function OutcomeSummary({ outcome }) {
44
+ function OutcomeSummary({ outcome, s }) {
44
45
  if (outcome.status === "scored" || outcome.status === "graded") {
45
46
  const percent = outcome.maxScore > 0 ? Math.round(outcome.score / outcome.maxScore * 100) : Math.round(outcome.score * 100);
46
47
  return /* @__PURE__ */ jsxRuntime.jsxs(
@@ -50,21 +51,16 @@ function OutcomeSummary({ outcome }) {
50
51
  "data-status": outcome.status,
51
52
  "data-passed": String(outcome.passed),
52
53
  children: [
53
- /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "lk-wr-grade", children: [
54
- "Score ",
55
- percent,
56
- "%. ",
57
- outcome.passed ? "Passed." : "Not passed."
58
- ] }),
59
- outcome.status === "graded" ? /* @__PURE__ */ jsxRuntime.jsx(GradeBody, { grade: outcome.grade }) : outcome.feedback ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "lk-wr-grade-feedback", children: outcome.feedback }) : null
54
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "lk-wr-grade", children: s.scoreAnnouncement(percent, outcome.passed) }),
55
+ outcome.status === "graded" ? /* @__PURE__ */ jsxRuntime.jsx(GradeBody, { grade: outcome.grade, s }) : outcome.feedback ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "lk-wr-grade-feedback", children: outcome.feedback }) : null
60
56
  ]
61
57
  }
62
58
  );
63
59
  }
64
60
  if (outcome.status === "deferred") {
65
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "lk-wr-outcome", "data-status": "deferred", children: /* @__PURE__ */ jsxRuntime.jsx("p", { className: "lk-wr-grade", children: "Not graded yet. This response is waiting for its grade." }) });
61
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "lk-wr-outcome", "data-status": "deferred", children: /* @__PURE__ */ jsxRuntime.jsx("p", { className: "lk-wr-grade", children: s.awaitingGrade }) });
66
62
  }
67
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "lk-wr-outcome", "data-status": "unscorable", "data-reason": outcome.reason, children: /* @__PURE__ */ jsxRuntime.jsx("p", { className: "lk-wr-grade", children: "This response could not be graded." }) });
63
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "lk-wr-outcome", "data-status": "unscorable", "data-reason": outcome.reason, children: /* @__PURE__ */ jsxRuntime.jsx("p", { className: "lk-wr-grade", children: s.couldNotBeGraded }) });
68
64
  }
69
65
  function WrittenResponse({
70
66
  data,
@@ -79,13 +75,15 @@ function WrittenResponse({
79
75
  sanitizeHtml,
80
76
  mediaBudget,
81
77
  mediaStrings,
78
+ strings,
82
79
  onInteraction,
83
80
  theme,
84
81
  locale,
85
82
  disabled
86
83
  }) {
84
+ const s = chunkAHEV52U7_cjs.useLkStrings(strings);
87
85
  const devError = react.useMemo(() => {
88
- if (!chunkPGEG7UZJ_cjs.isDevelopment()) {
86
+ if (!chunk4XPKKNJQ_cjs.isDevelopment()) {
89
87
  return null;
90
88
  }
91
89
  const result = lkCore.validateActivity("written-response", data);
@@ -175,9 +173,9 @@ function WrittenResponse({
175
173
  complete();
176
174
  const timeSpent = getTimeSpent();
177
175
  const xapiStatement = lkCore.xAPIBuilder.buildSubmittedStatement({
178
- actor: chunkPGEG7UZJ_cjs.ANONYMOUS_ACTOR,
176
+ actor: chunk4XPKKNJQ_cjs.ANONYMOUS_ACTOR,
179
177
  object: {
180
- id: chunkPGEG7UZJ_cjs.objectIdFor(data.id),
178
+ id: chunk4XPKKNJQ_cjs.objectIdFor(data.id),
181
179
  name: { [data.locale ?? "en-US"]: data.title },
182
180
  type: "http://adlnet.gov/expapi/activities/cmi.interaction",
183
181
  interactionType: "long-fill-in"
@@ -197,7 +195,7 @@ function WrittenResponse({
197
195
  timeSpent,
198
196
  xapiStatement
199
197
  });
200
- setSummary("Response submitted. It will be graded and your result will appear here later.");
198
+ setSummary(s.responseSubmitted);
201
199
  onInteraction?.({
202
200
  type: "submitted",
203
201
  activityId: data.id,
@@ -207,7 +205,7 @@ function WrittenResponse({
207
205
  };
208
206
  const promptId = `${data.id}-prompt`;
209
207
  const counterId = `${data.id}-counter`;
210
- const boundsLabel = data.minWords > 0 ? `${data.minWords}\u2013${data.maxWords} words` : `up to ${data.maxWords} words`;
208
+ const boundsLabel = s.wordBounds(data.minWords, data.maxWords);
211
209
  return /* @__PURE__ */ jsxRuntime.jsxs(
212
210
  "form",
213
211
  {
@@ -219,12 +217,13 @@ function WrittenResponse({
219
217
  onSubmit: handleSubmit,
220
218
  children: [
221
219
  data.media ? /* @__PURE__ */ jsxRuntime.jsx(
222
- chunkUFWF3BKA_cjs.ActivityMedia,
220
+ chunk2VLKSNYJ_cjs.ActivityMedia,
223
221
  {
224
222
  media: data.media,
225
223
  renderMode,
226
224
  ...mediaBudget !== void 0 ? { mediaBudget } : {},
227
225
  ...mediaStrings !== void 0 ? { mediaStrings } : {},
226
+ ...strings !== void 0 ? { strings } : {},
228
227
  ...onInteraction !== void 0 ? { onInteraction } : {},
229
228
  ...locale !== void 0 ? { locale } : {}
230
229
  }
@@ -261,9 +260,7 @@ function WrittenResponse({
261
260
  "aria-live": "polite",
262
261
  "data-within-bounds": withinBounds,
263
262
  children: [
264
- wordCount,
265
- " ",
266
- wordCount === 1 ? "word" : "words",
263
+ s.wordCount(wordCount),
267
264
  " (",
268
265
  boundsLabel,
269
266
  ")"
@@ -276,18 +273,19 @@ function WrittenResponse({
276
273
  type: "submit",
277
274
  className: "lk-wr-submit",
278
275
  disabled: inactive || text.trim().length === 0,
279
- children: "Submit"
276
+ children: s.submit
280
277
  }
281
278
  ),
282
- /* @__PURE__ */ jsxRuntime.jsx(chunkPGEG7UZJ_cjs.FeedbackRegion, { id: `${data.id}-feedback`, children: isReview ? outcome ? /* @__PURE__ */ jsxRuntime.jsx(OutcomeSummary, { outcome }) : null : summary })
279
+ /* @__PURE__ */ jsxRuntime.jsx(chunk4XPKKNJQ_cjs.FeedbackRegion, { id: `${data.id}-feedback`, children: isReview ? outcome ? /* @__PURE__ */ jsxRuntime.jsx(OutcomeSummary, { outcome, s }) : null : summary })
283
280
  ]
284
281
  }
285
282
  );
286
283
  }
287
284
  function WrittenResponse2(props) {
288
- return /* @__PURE__ */ jsxRuntime.jsx(chunkPGEG7UZJ_cjs.ActivityErrorBoundary, { activityTitle: props.data?.title, children: /* @__PURE__ */ jsxRuntime.jsx(WrittenResponse, { ...props }) });
285
+ const strings = chunkAHEV52U7_cjs.useLkStrings(props.strings);
286
+ return /* @__PURE__ */ jsxRuntime.jsx(chunk4XPKKNJQ_cjs.ActivityErrorBoundary, { activityTitle: props.data?.title, strings, children: /* @__PURE__ */ jsxRuntime.jsx(WrittenResponse, { ...props }) });
289
287
  }
290
288
 
291
289
  exports.WrittenResponse = WrittenResponse2;
292
- //# sourceMappingURL=chunk-PPJ47THY.cjs.map
293
- //# sourceMappingURL=chunk-PPJ47THY.cjs.map
290
+ //# sourceMappingURL=chunk-4AHZBTBF.cjs.map
291
+ //# sourceMappingURL=chunk-4AHZBTBF.cjs.map