@intellectif/lk-react 13.0.0 → 14.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (164) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/README.md +11 -3
  3. package/dist/{WrittenResponse-B5p_-RnJ.d.ts → WrittenResponse-DrzI89Wg.d.ts} +1 -1
  4. package/dist/{WrittenResponse-DWtS4tOL.d.cts → WrittenResponse-rRpOr9W1.d.cts} +1 -1
  5. package/dist/{chunk-UU2NV6SU.cjs → chunk-4Y5TELVU.cjs} +14 -13
  6. package/dist/chunk-4Y5TELVU.cjs.map +1 -0
  7. package/dist/{chunk-XAGWBVK3.js → chunk-5CP2Z6IS.js} +6 -5
  8. package/dist/chunk-5CP2Z6IS.js.map +1 -0
  9. package/dist/{chunk-NVUFEMPZ.cjs → chunk-5JCTJFQU.cjs} +30 -16
  10. package/dist/chunk-5JCTJFQU.cjs.map +1 -0
  11. package/dist/chunk-5JFMXBH7.js +888 -0
  12. package/dist/chunk-5JFMXBH7.js.map +1 -0
  13. package/dist/{chunk-NVP6UEQQ.js → chunk-AS2QGND5.js} +4 -4
  14. package/dist/{chunk-NVP6UEQQ.js.map → chunk-AS2QGND5.js.map} +1 -1
  15. package/dist/{chunk-3Q4I4KT6.js → chunk-AWQ53DON.js} +78 -2
  16. package/dist/chunk-AWQ53DON.js.map +1 -0
  17. package/dist/{chunk-DQERNAWI.js → chunk-B4BCEQNO.js} +26 -31
  18. package/dist/chunk-B4BCEQNO.js.map +1 -0
  19. package/dist/chunk-BRPEB43C.cjs +13 -0
  20. package/dist/chunk-BRPEB43C.cjs.map +1 -0
  21. package/dist/{chunk-74QXBWCI.cjs → chunk-CTW3G3EH.cjs} +6 -6
  22. package/dist/{chunk-74QXBWCI.cjs.map → chunk-CTW3G3EH.cjs.map} +1 -1
  23. package/dist/{chunk-AMGK7DDM.cjs → chunk-D4DQFTA7.cjs} +2 -2
  24. package/dist/chunk-D4DQFTA7.cjs.map +1 -0
  25. package/dist/{chunk-R7DI5UMU.js → chunk-DB4DLHN6.js} +22 -4
  26. package/dist/chunk-DB4DLHN6.js.map +1 -0
  27. package/dist/{chunk-K3EA547J.cjs → chunk-DPEY7DSE.cjs} +27 -33
  28. package/dist/chunk-DPEY7DSE.cjs.map +1 -0
  29. package/dist/{chunk-W4MXRZAB.js → chunk-IDYZ2FA2.js} +6 -5
  30. package/dist/chunk-IDYZ2FA2.js.map +1 -0
  31. package/dist/{chunk-K7NMNOPI.cjs → chunk-JG4DWAUJ.cjs} +15 -14
  32. package/dist/chunk-JG4DWAUJ.cjs.map +1 -0
  33. package/dist/chunk-KIO6QXGT.cjs +894 -0
  34. package/dist/chunk-KIO6QXGT.cjs.map +1 -0
  35. package/dist/chunk-MCO52PV5.js +520 -0
  36. package/dist/chunk-MCO52PV5.js.map +1 -0
  37. package/dist/{chunk-HT4FFZZQ.cjs → chunk-MZITNWZ2.cjs} +233 -38
  38. package/dist/chunk-MZITNWZ2.cjs.map +1 -0
  39. package/dist/{chunk-Z63JRG4M.cjs → chunk-N536RUCZ.cjs} +15 -14
  40. package/dist/chunk-N536RUCZ.cjs.map +1 -0
  41. package/dist/{chunk-PSHNTDKG.js → chunk-NJAF34GN.js} +13 -161
  42. package/dist/chunk-NJAF34GN.js.map +1 -0
  43. package/dist/chunk-OD65OZIW.cjs +159 -0
  44. package/dist/chunk-OD65OZIW.cjs.map +1 -0
  45. package/dist/{chunk-2GXSA322.cjs → chunk-PSUKWVKU.cjs} +15 -14
  46. package/dist/chunk-PSUKWVKU.cjs.map +1 -0
  47. package/dist/chunk-RCMYEWBE.js +11 -0
  48. package/dist/chunk-RCMYEWBE.js.map +1 -0
  49. package/dist/{chunk-PY53ABWC.js → chunk-RCPW6HVY.js} +23 -9
  50. package/dist/chunk-RCPW6HVY.js.map +1 -0
  51. package/dist/{chunk-DQNVAXG6.js → chunk-RXS455MR.js} +2 -2
  52. package/dist/chunk-RXS455MR.js.map +1 -0
  53. package/dist/chunk-TUVEJLOT.js +537 -0
  54. package/dist/chunk-TUVEJLOT.js.map +1 -0
  55. package/dist/{chunk-YZWXVZB3.js → chunk-U432HTUW.js} +6 -5
  56. package/dist/chunk-U432HTUW.js.map +1 -0
  57. package/dist/chunk-UDCQTOVU.cjs +523 -0
  58. package/dist/chunk-UDCQTOVU.cjs.map +1 -0
  59. package/dist/{chunk-5N7OLFHB.js → chunk-UDQS5INQ.js} +6 -5
  60. package/dist/chunk-UDQS5INQ.js.map +1 -0
  61. package/dist/{chunk-YILH4ETT.js → chunk-UDZWYHDV.js} +226 -31
  62. package/dist/chunk-UDZWYHDV.js.map +1 -0
  63. package/dist/{chunk-SBIJJ374.cjs → chunk-UMDWVMB5.cjs} +24 -6
  64. package/dist/chunk-UMDWVMB5.cjs.map +1 -0
  65. package/dist/{chunk-MA7GXUWC.cjs → chunk-VSFHJ7FB.cjs} +78 -2
  66. package/dist/chunk-VSFHJ7FB.cjs.map +1 -0
  67. package/dist/{chunk-J73TDY7J.cjs → chunk-WC4OD3EE.cjs} +25 -173
  68. package/dist/chunk-WC4OD3EE.cjs.map +1 -0
  69. package/dist/chunk-YM64MS4G.cjs +547 -0
  70. package/dist/chunk-YM64MS4G.cjs.map +1 -0
  71. package/dist/chunk-YORQQXGO.js +155 -0
  72. package/dist/chunk-YORQQXGO.js.map +1 -0
  73. package/dist/components/ActivityPreview.cjs +15 -10
  74. package/dist/components/ActivityPreview.d.cts +9 -3
  75. package/dist/components/ActivityPreview.d.ts +9 -3
  76. package/dist/components/ActivityPreview.js +14 -9
  77. package/dist/components/ActivitySequence.cjs +16 -11
  78. package/dist/components/ActivitySequence.d.cts +115 -4
  79. package/dist/components/ActivitySequence.d.ts +115 -4
  80. package/dist/components/ActivitySequence.js +15 -10
  81. package/dist/components/Dictation.cjs +7 -5
  82. package/dist/components/Dictation.d.cts +2 -1
  83. package/dist/components/Dictation.d.ts +2 -1
  84. package/dist/components/Dictation.js +6 -4
  85. package/dist/components/FillInTheBlanks.cjs +6 -5
  86. package/dist/components/FillInTheBlanks.d.cts +2 -1
  87. package/dist/components/FillInTheBlanks.d.ts +2 -1
  88. package/dist/components/FillInTheBlanks.js +5 -4
  89. package/dist/components/GapSelect.cjs +6 -5
  90. package/dist/components/GapSelect.d.cts +2 -1
  91. package/dist/components/GapSelect.d.ts +2 -1
  92. package/dist/components/GapSelect.js +5 -4
  93. package/dist/components/MultipleChoice.cjs +6 -5
  94. package/dist/components/MultipleChoice.d.cts +2 -1
  95. package/dist/components/MultipleChoice.d.ts +2 -1
  96. package/dist/components/MultipleChoice.js +5 -4
  97. package/dist/components/PronunciationFeedback.cjs +16 -0
  98. package/dist/components/PronunciationFeedback.cjs.map +1 -0
  99. package/dist/components/PronunciationFeedback.d.cts +67 -0
  100. package/dist/components/PronunciationFeedback.d.ts +67 -0
  101. package/dist/components/PronunciationFeedback.js +7 -0
  102. package/dist/components/PronunciationFeedback.js.map +1 -0
  103. package/dist/components/ReadAloud.cjs +21 -0
  104. package/dist/components/ReadAloud.cjs.map +1 -0
  105. package/dist/components/ReadAloud.d.cts +15 -0
  106. package/dist/components/ReadAloud.d.ts +15 -0
  107. package/dist/components/ReadAloud.js +12 -0
  108. package/dist/components/ReadAloud.js.map +1 -0
  109. package/dist/components/StimulusPanel.cjs +4 -4
  110. package/dist/components/StimulusPanel.d.cts +2 -1
  111. package/dist/components/StimulusPanel.d.ts +2 -1
  112. package/dist/components/StimulusPanel.js +3 -3
  113. package/dist/components/WrittenResponse.cjs +6 -5
  114. package/dist/components/WrittenResponse.d.cts +4 -3
  115. package/dist/components/WrittenResponse.d.ts +4 -3
  116. package/dist/components/WrittenResponse.js +5 -4
  117. package/dist/hooks/useSpeechRecorder.cjs +17 -0
  118. package/dist/hooks/useSpeechRecorder.cjs.map +1 -0
  119. package/dist/hooks/useSpeechRecorder.d.cts +127 -0
  120. package/dist/hooks/useSpeechRecorder.d.ts +127 -0
  121. package/dist/hooks/useSpeechRecorder.js +4 -0
  122. package/dist/hooks/useSpeechRecorder.js.map +1 -0
  123. package/dist/i18n/LkIntlProvider.cjs +7 -7
  124. package/dist/i18n/LkIntlProvider.d.cts +3 -2
  125. package/dist/i18n/LkIntlProvider.d.ts +3 -2
  126. package/dist/i18n/LkIntlProvider.js +1 -1
  127. package/dist/index.cjs +53 -32
  128. package/dist/index.cjs.map +1 -1
  129. package/dist/index.d.cts +6 -2
  130. package/dist/index.d.ts +6 -2
  131. package/dist/index.js +17 -12
  132. package/dist/index.js.map +1 -1
  133. package/dist/machine-CuwKqWXC.d.cts +26 -0
  134. package/dist/machine-CuwKqWXC.d.ts +26 -0
  135. package/dist/{strings-DPTyLpHt.d.cts → strings-l8Wv1Xax.d.ts} +230 -4
  136. package/dist/{strings-DPTyLpHt.d.ts → strings-wjAF2ONm.d.cts} +230 -4
  137. package/dist/theme/ThemeProvider.cjs +6 -6
  138. package/dist/theme/ThemeProvider.d.cts +4 -4
  139. package/dist/theme/ThemeProvider.d.ts +4 -4
  140. package/dist/theme/ThemeProvider.js +1 -1
  141. package/dist/theme/skin.css +612 -3
  142. package/package.json +33 -3
  143. package/dist/chunk-2GXSA322.cjs.map +0 -1
  144. package/dist/chunk-3Q4I4KT6.js.map +0 -1
  145. package/dist/chunk-5N7OLFHB.js.map +0 -1
  146. package/dist/chunk-AMGK7DDM.cjs.map +0 -1
  147. package/dist/chunk-DQERNAWI.js.map +0 -1
  148. package/dist/chunk-DQNVAXG6.js.map +0 -1
  149. package/dist/chunk-HT4FFZZQ.cjs.map +0 -1
  150. package/dist/chunk-J73TDY7J.cjs.map +0 -1
  151. package/dist/chunk-K3EA547J.cjs.map +0 -1
  152. package/dist/chunk-K7NMNOPI.cjs.map +0 -1
  153. package/dist/chunk-MA7GXUWC.cjs.map +0 -1
  154. package/dist/chunk-NVUFEMPZ.cjs.map +0 -1
  155. package/dist/chunk-PSHNTDKG.js.map +0 -1
  156. package/dist/chunk-PY53ABWC.js.map +0 -1
  157. package/dist/chunk-R7DI5UMU.js.map +0 -1
  158. package/dist/chunk-SBIJJ374.cjs.map +0 -1
  159. package/dist/chunk-UU2NV6SU.cjs.map +0 -1
  160. package/dist/chunk-W4MXRZAB.js.map +0 -1
  161. package/dist/chunk-XAGWBVK3.js.map +0 -1
  162. package/dist/chunk-YILH4ETT.js.map +0 -1
  163. package/dist/chunk-YZWXVZB3.js.map +0 -1
  164. package/dist/chunk-Z63JRG4M.cjs.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/shared/capture-registry.ts","../src/components/shared/sequence-slot.ts","../src/components/ReadAloud/recording-bounds.ts","../src/components/ReadAloud/ReadAloud.tsx","../src/components/ReadAloud/index.tsx"],"names":["takes","take","state","ReadAloud","jsx"],"mappings":"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","file":"chunk-5JFMXBH7.js","sourcesContent":["'use client';\n\n/**\n * Microphone captures that must stop when the region holding them goes away,\n * keyed by group name.\n *\n * Module-level for the reason `playback-group.ts` is: the thing that knows a\n * pane was hidden — the sequence — is not the thing holding the microphone,\n * and a context would have to be threaded through every activity component to\n * reach one. The caller mints the group name per region; a sequence uses the\n * slot's, so hiding one slot stops that slot's capture and no other's. The name\n * reaches a recorder only through `SequenceSlotContext`, never as a prop, so no\n * consumer string can join another region's group.\n *\n * This **supplements** a recorder's own teardown and never replaces it. The\n * sequence's pane effect runs only on a `hidden` transition, not on unmount,\n * and neither a preview nor a standalone render has panes at all — so a\n * component that records still releases the microphone from its own cleanup,\n * and this registry only covers the case where the component is left mounted\n * and merely hidden.\n *\n * No `useCaptureGroup` hook, unlike playback: that one re-registers after every\n * commit because the element behind a ref can be replaced while the ref object\n * is not, whereas a recorder's `stop` keeps one identity for the life of the\n * hook and a plain effect registers it exactly once.\n */\nconst groups = new Map<string, Set<() => void>>();\n\n/** Adds a capture's stop to a group; returns the matching removal. */\nexport function joinCaptureGroup(group: string, stop: () => void): () => void {\n let members = groups.get(group);\n if (members === undefined) {\n members = new Set();\n groups.set(group, members);\n }\n members.add(stop);\n return () => {\n const current = groups.get(group);\n if (current === undefined) {\n return;\n }\n current.delete(stop);\n if (current.size === 0) {\n groups.delete(group);\n }\n };\n}\n\n/**\n * Stops every capture in the group.\n *\n * The members are copied before they are called. Each `stop` is expected to\n * run its own removal, and a component that answers one by starting again\n * would otherwise be stopped by the sweep that its own stop provoked.\n */\nexport function stopCaptureGroup(group: string): void {\n const members = groups.get(group);\n if (members === undefined) {\n return;\n }\n for (const stop of [...members]) {\n stop();\n }\n}\n","'use client';\n\nimport { createContext } from 'react';\n\n/**\n * How far a take that was handed in has got, as a pager deciding whether its\n * set is finished needs to know it.\n *\n * - `in-flight` — being stored or being judged. The set waits for it.\n * - `retryable` — it failed, and the learner is being offered another try at\n * the SAME take. The set waits for it while the learner is on that question.\n * - `settled` — nothing more will come of it: a grade, a take with no grade, a\n * failure nobody can retry, or a take the learner walked away from.\n */\nexport type TakeState = 'in-flight' | 'retryable' | 'settled';\n\n/**\n * What a pager hands the one slot it renders, and only through\n * {@link SequenceSlotContext} — never as a prop, so none of it can appear on a\n * public component's signature. A consumer string in any of it could stop\n * another component's microphone or finish another paper's set.\n */\nexport interface SequenceSlotChannel {\n /**\n * The capture-registry group whose captures stop when the slot's pane is\n * hidden. Minted per pager and per slot, so no two pagers share one.\n */\n readonly captureGroup: string;\n /**\n * Reports the state of the take numbered `take`, a number from\n * {@link mintTake}. A take numbered below the slot's latest is a take the\n * learner has since replaced, and the pager ignores it.\n */\n takeState(take: number, state: TakeState): void;\n}\n\n/** `null` outside a pager: a standalone component belongs to no slot. */\nexport const SequenceSlotContext = createContext<SequenceSlotChannel | null>(null);\n\n/**\n * The last take number handed out. Module-level and never reset, so a number\n * is unique for the life of the page: across mounts, across papers, and across\n * two pagers side by side. A result can therefore only ever match the take that\n * produced it, whatever slot and activity ids a later paper reuses — ids a host\n * chooses, and can repeat, never decide it.\n */\nlet lastTake = 0;\n\n/** A take number no other take on this page has had, or will have. */\nexport function mintTake(): number {\n lastTake += 1;\n return lastTake;\n}\n\n/**\n * The take each reported value belongs to. A `WeakMap` rather than a field, so\n * a response or a result a host receives carries no extra key it would persist\n * or compare, and the number never outlives the object.\n */\nconst takes = new WeakMap<object, number>();\n\n/** Marks `value` as produced by take `take`, and returns it. */\nexport function stampTake<T extends object>(value: T, take: number): T {\n takes.set(value, take);\n return value;\n}\n\n/** The take `value` was produced by, or `undefined` when nothing stamped it. */\nexport function takeOf(value: unknown): number | undefined {\n return typeof value === 'object' && value !== null ? takes.get(value) : undefined;\n}\n","'use client';\n\nimport { READ_ALOUD_MAX_SECONDS, READ_ALOUD_MAX_TAKES } from '@intellectif/lk-core';\n\n/** The bounds a take is captured under, as the recorder and the sentences use them. */\nexport interface RecordingBounds {\n /** Above 0, and at most `READ_ALOUD_MAX_SECONDS`. */\n maxSeconds: number;\n /** At least 0 and below `maxSeconds`, or `undefined` for no minimum. */\n minSeconds: number | undefined;\n /** A whole number from 1 to `READ_ALOUD_MAX_TAKES`, or `undefined` for no limit. */\n maxTakes: number | undefined;\n}\n\nconst isNumber = (value: unknown): value is number =>\n typeof value === 'number' && Number.isFinite(value);\n\n/**\n * The one reading of an item's `recording`: every bound the component captures\n * under, every sentence that names one, and the key that decides whether a new\n * `data` is a new reading all come from here, so no two of them can read the\n * same field differently.\n *\n * `practice` renders unvalidated content in production, so what comes in may\n * be anything a server stored. What comes out is always a set of bounds the\n * schema accepts, and bounds the schema accepts come out unchanged. Three\n * rules decide the rest, and each errs toward a take the learner can finish:\n *\n * - **A present bound that cannot be read is the schema's own ceiling**, never\n * no bound at all: a `maxSeconds` of `\"20\"` or `NaN` records up to\n * `READ_ALOUD_MAX_SECONDS`, and a `maxTakes` of `\"1\"` allows\n * `READ_ALOUD_MAX_TAKES`. Unreadable data can make the recorder stricter\n * than the ceiling, never looser: `maxTakes: \"1\"` used to mean unlimited\n * takes. `null` is an absent optional, as it is everywhere a host sends JSON.\n * - **A count is a whole number in range**: `0` and `1.5` read as `1`, `999`\n * as the ceiling.\n * - **A minimum the maximum cannot reach is no minimum.** A `minSeconds` of 30\n * under a `maxSeconds` of 20 stopped every take at 20 and then refused it as\n * too short, so the item could never be answered. The schema refuses that\n * pair; a reading that honoured it would leave the learner a dead end.\n */\nexport function readRecordingBounds(recording: unknown): RecordingBounds {\n const bounds =\n typeof recording === 'object' && recording !== null\n ? (recording as Record<string, unknown>)\n : {};\n const maxSeconds =\n isNumber(bounds.maxSeconds) && bounds.maxSeconds > 0\n ? Math.min(bounds.maxSeconds, READ_ALOUD_MAX_SECONDS)\n : READ_ALOUD_MAX_SECONDS;\n const minSeconds =\n isNumber(bounds.minSeconds) && bounds.minSeconds >= 0 && bounds.minSeconds < maxSeconds\n ? bounds.minSeconds\n : undefined;\n const takes = bounds.maxTakes;\n const maxTakes =\n takes === undefined || takes === null\n ? undefined\n : isNumber(takes)\n ? Math.min(Math.max(1, Math.floor(takes)), READ_ALOUD_MAX_TAKES)\n : READ_ALOUD_MAX_TAKES;\n return { maxSeconds, minSeconds, maxTakes };\n}\n\n/**\n * A length of audio as the whole seconds a sentence says beside `maxSeconds`,\n * never more than it. A bound may be fractional — the schema allows 2.5 — and\n * rounding a take that ran to it said \"3 of 2.5 seconds\". Rounded to the\n * nearest second for a finished take, and down for the counter of one still\n * running, which should not reach its bound before the take does.\n */\nexport function secondsWithin(\n milliseconds: number,\n maxSeconds: number,\n rounding: 'nearest' | 'down',\n): number {\n const seconds = milliseconds / 1000;\n const whole = rounding === 'nearest' ? Math.round(seconds) : Math.floor(seconds);\n return Math.min(Math.max(0, whole), maxSeconds);\n}\n","'use client';\n\nimport {\n type ActivityMedia as ActivityMediaData,\n ActivitySchemaError,\n assertRedacted,\n type GradeRecord,\n type InteractionKind,\n type LearnerResponse,\n type MediaPlaybackPolicy,\n type ReadAloudData,\n type ReadAloudLearnerResponse,\n type ReadAloudWordAlignment,\n type RecordingRef,\n type ScoringDetail,\n type SpeechAssessment,\n validateActivity,\n xAPIBuilder,\n xapiDefinitionFor,\n} from '@intellectif/lk-core';\nimport {\n type CSSProperties,\n useCallback,\n useContext,\n useEffect,\n useId,\n useMemo,\n useRef,\n useState,\n} from 'react';\nimport { useActivityState } from '../../hooks/useActivityState.js';\nimport type { RecordedTake, SpeechRecorderStatus } from '../../hooks/useSpeechRecorder.js';\nimport { useSpeechRecorder } from '../../hooks/useSpeechRecorder.js';\nimport { localeDirectionOf } from '../../i18n/direction.js';\nimport { useLkStrings } from '../../i18n/LkIntlProvider.js';\nimport type { LkStrings } from '../../i18n/strings.js';\nimport { ANONYMOUS_ACTOR, isDevelopment, objectIdFor } from '../_internal.js';\nimport { PronunciationFeedback } from '../PronunciationFeedback/index.js';\nimport { markSentence, VISUALLY_HIDDEN } from '../PronunciationFeedback/PronunciationFeedback.js';\nimport { ActivityMedia } from '../shared/ActivityMedia.js';\nimport { joinCaptureGroup } from '../shared/capture-registry.js';\nimport { FeedbackRegion } from '../shared/FeedbackRegion.js';\nimport { usePlaybackRefusal } from '../shared/playback-refusal.js';\nimport {\n fractionOfGrade,\n type OutcomeReading,\n percentOfGrade,\n type ReadGrade,\n readAssessResult,\n readEvidence,\n readOutcome,\n} from '../shared/read-grade.js';\nimport {\n mintTake,\n SequenceSlotContext,\n stampTake,\n type TakeState,\n} from '../shared/sequence-slot.js';\nimport type { ActivityProps, Renderable } from '../types.js';\nimport { readRecordingBounds, secondsWithin } from './recording-bounds.js';\n\n/**\n * Names playback groups for the model recordings. Per mount and never derived\n * from `useId`: two separately hydrated roots derive the same id for the same\n * tree position, and their recordings would pause each other. The name never\n * reaches the DOM.\n */\nlet modelsGroupCount = 0;\n\n/** A title with something in it a screen reader can say. */\nconst VISIBLE_TEXT_RE = /[^\\p{White_Space}\\p{Cc}\\p{Cf}\\p{Default_Ignorable_Code_Point}]/u;\n\n/** A stored mark's id, as `gradeReadAloud` numbers the reference words. */\nconst MARK_ID_RE = /^w\\d+$/;\n\n/** The states a mark rebuilt from stored details can carry: insertions are not stored. */\nconst STORED_STATES: readonly ReadAloudWordAlignment['state'][] = [\n 'correct',\n 'mispronounced',\n 'omitted',\n];\n\n/** The level meter's resolution, in steps the skin can select on. */\nconst LEVEL_STEPS = 10;\n\n/**\n * What an application's assessor came back with.\n *\n * `code` is a plain `string` and not `SpeechUnscorableCode`: an application's\n * own assessor may refuse a take for a reason the SDK has no name for, and a\n * narrower type would make it unreportable. This component classifies the two\n * codes that mean \"nothing was heard\" and treats every other one the same way.\n */\nexport type ReadAloudAssessResult =\n | { status: 'graded'; assessment: SpeechAssessment; grade: GradeRecord }\n | { status: 'unscorable'; code: string; assessment?: SpeechAssessment }\n | { status: 'failed'; retryable: boolean };\n\n/**\n * How a take reaches the application, and how a judgement comes back.\n *\n * Named `recordingBinding` rather than `recording` because both\n * `ReadAloudData.recording` (the bounds) and `ReadAloudLearnerResponse.recording`\n * (the stored take) already exist: the plain name would read as one of those.\n */\nexport interface RecordingBinding {\n /**\n * Puts the take in your storage and returns its key. **Required** outside\n * `review`: without it a learner can speak into a control that submits\n * nothing. Settle it — a promise that never does leaves the submit pending\n * for ever.\n */\n upload(take: RecordedTake): Promise<RecordingRef>;\n /**\n * Judges the stored take. `practice` only — an exam never assesses on the\n * client. Optional: a binding without it records, uploads and submits, and\n * the feedback is replaced by a notice.\n */\n assess?(ref: RecordingRef): Promise<ReadAloudAssessResult>;\n /** A playable link to a stored take. `review` only. */\n playbackUrl?(ref: RecordingRef): Promise<string>;\n}\n\nexport interface ReadAloudProps extends ActivityProps<ReadAloudData> {\n /** How a take is stored and judged. See {@link RecordingBinding}. */\n recordingBinding?: RecordingBinding;\n /**\n * The evidence behind a stored grade, for `review`. Without it a graded\n * outcome still shows its marks, rebuilt from `outcome.grade.details`; with\n * neither, the score is shown and no marks are.\n */\n assessment?: SpeechAssessment;\n /**\n * Confidence above which a break note is shown, 0..1, forwarded to\n * {@link PronunciationFeedback}. No default — see its own prop.\n */\n breakThreshold?: number;\n /** The same, for the monotone note. */\n monotoneThreshold?: number;\n /**\n * The URL of a self-hosted copy of `CAPTURE_PROCESSOR_SOURCE`, handed to the\n * recorder, for a Content-Security-Policy whose `script-src` does not allow\n * `blob:`. Without it the capture module is loaded from a `blob:` URL, and a\n * policy that refuses one sends every take through the deprecated\n * main-thread `ScriptProcessorNode` — a take that still records, so nothing\n * says so. See `useSpeechRecorder`'s option of the same name.\n */\n workletUrl?: string;\n /**\n * Never called. A read-aloud answer is a key in your storage, and there is\n * none until the take has been uploaded — so there is no intermediate\n * response to report, and reporting the take itself would offer a host a\n * recording it has nowhere to put. `onSubmit` carries the response, once.\n */\n onChange?: (response: LearnerResponse) => void;\n}\n\n/** What the live region says: the SDK's own sentence, then any grader feedback. */\ninterface Announcement {\n text: string;\n feedback: string | null;\n}\n\n/**\n * How far the submitted take has got. `idle` also means \"nothing submitted yet\".\n *\n * Every other phase names its `take`, the number `mintTake` gave the submit\n * that reached it: a phase describes one take, and a pager told about it must\n * be able to tell that take from the one before it.\n */\ntype SubmitPhase =\n | { kind: 'idle' }\n | { kind: 'uploading'; take: number }\n | { kind: 'upload-failed'; take: number }\n | { kind: 'assessing'; take: number }\n | { kind: 'submitted'; take: number }\n | { kind: 'unassessed'; take: number }\n /**\n * The assessor's judgement, as `readAssessResult` read it: `grade` is `null`\n * when what came back cannot be shown as a grade, and `assessment` is `null`\n * when there is no evidence to mark words from.\n */\n | {\n kind: 'graded';\n take: number;\n assessment: SpeechAssessment | null;\n grade: ReadGrade | null;\n }\n | { kind: 'unscorable'; take: number; code: string | undefined }\n | { kind: 'assess-failed'; take: number; retryable: boolean };\n\n/**\n * Whether the screen offers \"Try again\" for the take this phase describes —\n * the one answer to that question, read by the button that offers it and by\n * the take state a pager is told, so the two cannot disagree. A retry re-sends\n * the SAME take: a take that failed to upload is sent again, and a stored take\n * whose assessment failed is judged again without being uploaded twice.\n */\nfunction offersRetry(phase: SubmitPhase, uploaded: RecordingRef | null): boolean {\n return (\n phase.kind === 'upload-failed' ||\n (phase.kind === 'assess-failed' && phase.retryable && uploaded !== null)\n );\n}\n\n/**\n * The state of the phase's take, as a pager deciding whether its set is\n * finished reads it. Derived from the phase alone, so no path through a submit\n * can leave a pager waiting on a take this component has finished with: every\n * path ends in a phase.\n */\nfunction takeStateOf(phase: SubmitPhase, uploaded: RecordingRef | null): TakeState {\n if (phase.kind === 'uploading' || phase.kind === 'assessing') {\n return 'in-flight';\n }\n return offersRetry(phase, uploaded) ? 'retryable' : 'settled';\n}\n\n/**\n * Whether the recorder is capturing, or about to: the one answer to \"is a take\n * being recorded\", read by everything that must hold still for one. A\n * permission prompt counts — a prompt answered after the pane went away would\n * otherwise open a microphone nobody is in front of, and a model recording\n * played while the prompt is up would be charged a play and then silenced.\n */\nfunction capturing(status: SpeechRecorderStatus): boolean {\n return status === 'recording' || status === 'requesting-permission';\n}\n\n/**\n * What a take is a take OF: the item, the words, their language and the bounds\n * the take is captured under. A change to any of these is a different reading,\n * and a take of the old one answers nothing.\n *\n * Deliberately NOT the identity of `data`. `redact()` returns a new object on\n * every call, and `activities={raw.map(redact)}` is a documented way to pass a\n * paper, so a host that rebuilds its items on every render is ordinary — and a\n * reset keyed on identity silently threw away every grade still in flight and\n * refunded the take budget on each of those renders, so a `maxTakes: 1` exam\n * accepted a second take. The title, the instructions and the model recordings\n * are presentation; editing them leaves the take a take of the same reading.\n *\n * The bounds are the CLAMPED ones, which are what the recorder is given. Read\n * field by field into strings, so no value unvalidated data carries can make\n * the key throw.\n */\nfunction readingKeyOf(\n data: Renderable<ReadAloudData>,\n bounds: { maxSeconds: number; minSeconds: number | undefined; maxTakes: number | undefined },\n): string {\n const text = (value: unknown): string | null => (typeof value === 'string' ? value : null);\n return JSON.stringify([\n text(data.id),\n text(data.referenceText),\n text(data.locale),\n String(bounds.maxSeconds),\n String(bounds.minSeconds),\n String(bounds.maxTakes),\n ]);\n}\n\n/**\n * The slower model recording as an `ActivityMedia`: it follows the model\n * recording's playback policy minus `maxPlays` (`controls`, `seek`, `rate`,\n * the native hints), so a locked scrubber or a fixed speed binds both files —\n * and it never carries a budget of its own, so `<ActivityMedia>` never demands\n * a binding for it. `undefined` when there is no slow recording.\n */\nfunction slowMediaFor(\n data: Renderable<ReadAloudData>,\n label: string,\n): ActivityMediaData | undefined {\n const slow = data.slowMedia;\n if (slow === undefined) {\n return undefined;\n }\n const policy: MediaPlaybackPolicy = data.media?.playback ?? {};\n const { maxPlays: _budget, ...inherited } = policy;\n const hasPolicy = Object.values(inherited).some((value) => value !== undefined);\n return {\n type: 'audio',\n url: slow.url,\n alt: slow.alt ?? label,\n ...(hasPolicy ? { playback: inherited } : {}),\n };\n}\n\n/**\n * Word marks rebuilt from stored scoring details — a server-recorded mark shown\n * after the fact, with no assessment on the client. Inserted words are not in\n * the details (`gradeReadAloud` stores one mark per REFERENCE word, and a word\n * the learner added is not one the item asked for), so none appear.\n *\n * The details are a read grade's, so every entry is well-formed; what is left\n * to decide here is whether they are word marks at all, rather than a rubric's\n * own breakdown.\n */\nfunction marksFromDetails(details: readonly ScoringDetail[]): ReadAloudWordAlignment[] | null {\n if (details.length === 0 || !details.every((detail) => MARK_ID_RE.test(detail.itemId))) {\n return null;\n }\n return details.map((detail) => {\n const heard = Array.isArray(detail.learnerResponse)\n ? detail.learnerResponse.join(' ')\n : detail.learnerResponse;\n const reference = Array.isArray(detail.correctResponse)\n ? detail.correctResponse.join(' ')\n : detail.correctResponse;\n const correct = detail.outcome !== undefined ? detail.outcome === 'correct' : detail.correct;\n const state: ReadAloudWordAlignment['state'] = correct\n ? 'correct'\n : detail.outcome === 'incorrect-omission'\n ? 'omitted'\n : 'mispronounced';\n return {\n itemId: detail.itemId,\n reference,\n heard,\n state,\n // `ScoringDetail.score` is the word's accuracy divided by 100; the marks\n // carry it back in the units the assessment used.\n ...(typeof detail.score === 'number' ? { accuracy: detail.score * 100 } : {}),\n };\n });\n}\n\n/** The stored take a learner response points at, or `null` for a blank or another type. */\nfunction recordingOf(response: LearnerResponse | undefined): RecordingRef | null {\n return response !== undefined && response.type === 'read-aloud' ? response.recording : null;\n}\n\n/** The response this component emits; `takes` is written only when one was made. */\nfunction responseOf(recording: RecordingRef | null, takes: number): ReadAloudLearnerResponse {\n return { type: 'read-aloud', recording, ...(takes > 0 ? { takes } : {}) };\n}\n\n/**\n * Which learner message an unscorable code earns. The two codes that mean\n * \"nothing was heard\" send the learner back to a quieter room; every other one,\n * the SDK's and an application's own alike — and no code at all — says only\n * that this take could not be assessed. **The code itself is never shown** — it\n * is a developer's label.\n */\nfunction unscorableMessage(code: string | undefined, s: LkStrings): string {\n return code === 'no_speech' || code === 'insufficient_voiced_time'\n ? s.readAloudNotHeard\n : s.readAloudNotAssessed;\n}\n\n/**\n * The announcement, with grader feedback in its own isolated span: its\n * direction is its own, so an English \"Well read.\" in an Arabic interface keeps\n * its full stop at its end. Its language is not guessed — feedback may be\n * written in the interface's language or the reading's.\n */\nfunction AnnouncementText({ announcement }: { announcement: Announcement | null }) {\n if (announcement === null) {\n return null;\n }\n return (\n <>\n {announcement.text}\n {announcement.feedback ? (\n <>\n {' '}\n <span dir=\"auto\">{announcement.feedback}</span>\n </>\n ) : null}\n </>\n );\n}\n\n/**\n * The server's outcome in `review`, as `readOutcome` read it. Nothing on this\n * path scores, infers or defaults a grade.\n *\n * `scoreShown` says whether the grade is already on screen — it is, whenever\n * `<PronunciationFeedback>` was given the same grade — so the sentence is not\n * rendered twice. The wrapper and its attributes are rendered either way,\n * because they are what a stylesheet and a diagnostic read.\n */\nfunction OutcomeSummary({\n reading,\n s,\n scoreShown,\n}: {\n reading: OutcomeReading;\n s: LkStrings;\n scoreShown: boolean;\n}) {\n switch (reading.kind) {\n case 'graded':\n return (\n <div\n className=\"lk-ra-outcome\"\n data-status={reading.status}\n data-passed={String(reading.grade.passed)}\n >\n {scoreShown ? null : (\n <>\n <p className=\"lk-ra-grade\">\n {s.scoreAnnouncement(percentOfGrade(reading.grade), reading.grade.passed)}\n </p>\n {reading.grade.feedback ? (\n <p className=\"lk-ra-grade-feedback\" dir=\"auto\">\n {reading.grade.feedback}\n </p>\n ) : null}\n </>\n )}\n </div>\n );\n case 'deferred':\n return (\n <div className=\"lk-ra-outcome\" data-status=\"deferred\">\n <p className=\"lk-ra-grade\">{s.awaitingGrade}</p>\n </div>\n );\n case 'unscorable':\n // An unscorable code is a developer-facing label: it goes on the element\n // for diagnostics and routing, and never in front of the learner as text.\n return (\n <div\n className=\"lk-ra-outcome\"\n data-status=\"unscorable\"\n {...(reading.code !== undefined ? { 'data-code': reading.code } : {})}\n >\n <p className=\"lk-ra-grade\">{s.couldNotBeGraded}</p>\n </div>\n );\n default:\n // A stored outcome that claims a grade and carries none that can be read\n // gets the deferred vocabulary's own sentence rather than \"Score NaN%.\":\n // there is a grade, and this screen cannot say what it was. No\n // `data-passed`, because there is no verdict to style.\n return (\n <div\n className=\"lk-ra-outcome\"\n {...(reading.status !== undefined ? { 'data-status': reading.status } : {})}\n >\n <p className=\"lk-ra-grade\">{s.couldNotBeGraded}</p>\n </div>\n );\n }\n}\n\n/**\n * Reads a text aloud and hands the recording to the application.\n *\n * The SDK records; it never judges. A take is captured as mono 16-bit PCM WAV\n * at 16 kHz — the one format `inspectWav` measures directly — handed to\n * `recordingBinding.upload`, and reported through `onSubmit`. Whether anything\n * assesses it, and where, is the application's: in `practice` an optional\n * `assess` may come back with a grade, and in `exam` nothing is ever assessed\n * on the client.\n *\n * **`recordingBinding.upload` is required outside `review`** and its absence\n * throws at render, in production too: a learner must never speak into a\n * control that stores nothing. `assess` is not required — a binding without it\n * records, uploads and submits, and shows a notice in place of the feedback.\n *\n * **A grade that comes back is read, not trusted** — whether `assess` returned\n * it or `outcome` carries it. A score that is not a number between 0 and its\n * maximum, or a `passed` that is not a boolean, is shown as could-not-be-graded\n * and never reaches `onComplete` or a statement; `onComplete` fires exactly when\n * a score is on screen, with that score.\n *\n * **Under a strict Content-Security-Policy** the take is played back from a\n * `blob:` URL, so `media-src` must allow `blob:` — refused, the player and the\n * per-word buttons give way to a note saying the take cannot be played here —\n * and the capture module loads from one too, so either `script-src` allows\n * `blob:` or `workletUrl` names a self-hosted copy of `CAPTURE_PROCESSOR_SOURCE`.\n *\n * ```tsx\n * <ReadAloud\n * data={item}\n * recordingBinding={{ upload, assess }}\n * onSubmit={persist}\n * onComplete={record}\n * />\n * ```\n */\nexport function ReadAloud({\n data,\n onComplete,\n onSubmit,\n onInteraction,\n value,\n defaultValue,\n defaultSubmitted,\n renderMode = 'practice',\n outcome,\n mediaBudget,\n mediaStrings,\n strings,\n theme,\n locale,\n disabled,\n recordingBinding,\n assessment,\n breakThreshold,\n monotoneThreshold,\n workletUrl,\n}: ReadAloudProps) {\n const isExam = renderMode === 'exam';\n const isReview = renderMode === 'review';\n const s = useLkStrings(strings);\n // The pager slot this component sits in, if any: the capture group its pane\n // stops, and where the state of each take is reported. A context and never a\n // prop, so none of it is on this component's published signature — a\n // consumer string in a prop could stop another pager's microphone. `null`\n // for a standalone component, which then belongs to no group at all and\n // releases the microphone from its own unmount alone.\n const slot = useContext(SequenceSlotContext);\n const captureGroup = slot?.captureGroup;\n\n const devError = useMemo(() => {\n if (!isDevelopment()) {\n return null;\n }\n // No `hasKey` branch, unlike `<Dictation>`: a read-aloud item has no answer\n // key, so `redact()` produces a projection the strict redacted schema\n // describes exactly and there is no revealed variant to make room for.\n if (data.redacted === true) {\n try {\n assertRedacted(data);\n return null;\n } catch (error) {\n return error instanceof Error ? error : new Error(String(error));\n }\n }\n const result = validateActivity('read-aloud', data);\n return result.success ? null : new ActivitySchemaError('read-aloud', result.errors);\n }, [data]);\n\n // The bounds a take is captured under, READ and not merely defaulted, because\n // `practice` renders unvalidated content in production: a `maxSeconds` of\n // 100000 arms a recording that never stops itself, a `maxTakes` of \"1\" meant\n // unlimited takes, and a minimum above the maximum refused every take. One\n // reader owns all of it — see `readRecordingBounds`.\n const { maxSeconds, minSeconds, maxTakes } = readRecordingBounds(data.recording);\n const readingKey = readingKeyOf(data, { maxSeconds, minSeconds, maxTakes });\n\n const recorder = useSpeechRecorder(\n useMemo(\n () => ({\n maxDurationMs: maxSeconds * 1000,\n ...(minSeconds !== undefined ? { minDurationMs: minSeconds * 1000 } : {}),\n ...(maxTakes !== undefined ? { maxTakes } : {}),\n // Forwarded, never defaulted: without it the recorder mints its own\n // `blob:` module, which is the one a strict policy refuses.\n ...(workletUrl !== undefined ? { workletUrl } : {}),\n }),\n [maxSeconds, minSeconds, maxTakes, workletUrl],\n ),\n );\n\n const { state, start, complete, getTimeSpent, reset } = useActivityState(\n defaultSubmitted === true ? 'completed' : 'idle',\n );\n const [phase, setPhase] = useState<SubmitPhase>({ kind: 'idle' });\n const [uploaded, setUploaded] = useState<RecordingRef | null>(null);\n const [takeUrl, setTakeUrl] = useState<string | null>(null);\n const [playbackUrl, setPlaybackUrl] = useState<string | null>(null);\n // Re-keys the alert so the SAME message announces again — a learner who is\n // refused the microphone twice hears it twice.\n const [errorNonce, setErrorNonce] = useState(0);\n\n const ids = useId();\n const titleId = `${ids}-title`;\n // Per mount, not per activity id: two renderings of one item on a page (a\n // review list, a preview beside a sequence) are separate players.\n const [modelsGroup] = useState(() => {\n modelsGroupCount += 1;\n return `lk-ra-models-${modelsGroupCount}`;\n });\n\n // The latest callbacks, so an effect can fire an event without depending on a\n // binding or a handler the host rebuilds on every render.\n const interactionRef = useRef(onInteraction);\n interactionRef.current = onInteraction;\n const bindingRef = useRef(recordingBinding);\n bindingRef.current = recordingBinding;\n // Guards a second submit while one is in flight: two clicks would upload the\n // same take twice and charge the application for two assessments.\n const submittingRef = useRef(false);\n // Which reading this instance is on. Everything asynchronous reads it once\n // and re-reads it after every await, so an instance that has been unmounted,\n // or handed a different reading, reports nothing for the one it has left —\n // the treatment the playback-url effect below already gives itself.\n //\n // A counter rather than the boolean `live` that effect uses, because a\n // boolean cleared on the reading reset would stay cleared and leave a\n // perfectly live component unable to submit its next take.\n const runRef = useRef(0);\n // The pager slot as of the latest render, and the last take it was told\n // about, so an unmount can tell it that take is over — a take whose result\n // this instance will now never report must not hold a set open for ever.\n const slotRef = useRef(slot);\n slotRef.current = slot;\n const reportedTakeRef = useRef<number | null>(null);\n useEffect(\n () => () => {\n runRef.current += 1;\n const take = reportedTakeRef.current;\n if (take !== null) {\n slotRef.current?.takeState(take, 'settled');\n }\n },\n [],\n );\n\n const fire = useCallback(\n (type: InteractionKind, payload: Record<string, unknown>): void => {\n interactionRef.current?.({ type, activityId: data.id, timestamp: Date.now(), payload });\n },\n [data.id],\n );\n\n // `recording-started` and `recording-stopped` are reported from the recorder's\n // own status rather than from the click handlers: a take also ends when it\n // reaches its bound, and a handler would report only the ends a learner asked\n // for.\n const lastStatusRef = useRef<SpeechRecorderStatus>(recorder.status);\n useEffect(() => {\n if (lastStatusRef.current === recorder.status) {\n return;\n }\n lastStatusRef.current = recorder.status;\n if (recorder.status === 'recording') {\n fire('recording-started', { takes: recorder.takesUsed, durationMs: 0 });\n } else if (recorder.status === 'recorded' && recorder.take !== null) {\n fire('recording-stopped', {\n takes: recorder.takesUsed,\n durationMs: recorder.take.durationMs,\n });\n }\n }, [recorder.status, recorder.take, recorder.takesUsed, fire]);\n\n useEffect(() => {\n if (recorder.error !== null) {\n setErrorNonce((nonce) => nonce + 1);\n }\n }, [recorder.error]);\n\n // The take preview. Guarded because an embedder may forbid object URLs, and a\n // preview is worth less than the take it previews.\n const take = recorder.take;\n useEffect(() => {\n if (take === null || typeof URL.createObjectURL !== 'function') {\n return;\n }\n const url = URL.createObjectURL(take.blob);\n setTakeUrl(url);\n return () => {\n URL.revokeObjectURL(url);\n setTakeUrl(null);\n };\n }, [take]);\n\n const restored = recordingOf(value ?? defaultValue);\n const restoredKey = restored?.key ?? null;\n // Keyed by the reading as well as the take: the reading reset below clears\n // the link, and a link nothing mints again would leave the stored take with\n // no player for as long as it stays on screen.\n // biome-ignore lint/correctness/useExhaustiveDependencies: keyed by the stored take and the reading, not by the identity of a response object a host rebuilds every render\n useEffect(() => {\n const binding = bindingRef.current;\n if (!isReview || restored === null || binding?.playbackUrl === undefined) {\n return;\n }\n let live = true;\n void Promise.resolve(binding.playbackUrl(restored)).then(\n (url) => {\n if (live) {\n setPlaybackUrl(url);\n }\n },\n () => {\n /* a link that could not be minted; the marks still render without it */\n },\n );\n return () => {\n live = false;\n };\n }, [isReview, restoredKey, readingKey]);\n\n // What the registry may do to this component, and it is narrow: release the\n // DEVICE, and only while one is actually open.\n //\n // The pager stops a hidden slot's captures on EVERY hidden transition, so a\n // join that discarded unconditionally threw away a take that was already\n // encoded — and `discard` deliberately refunds nothing, so Next then Prev\n // left the learner an activity they could no longer answer and no message\n // saying why. That contradicts the pager's own founding principle: slots stay\n // mounted precisely so nobody silently loses an answer. A capture still\n // running has nothing to lose yet; a finished take has everything.\n //\n // The status is read from a ref so this callback keeps ONE identity and the\n // effect below joins the group exactly once, as `playback-group`'s does.\n const statusRef = useRef(recorder.status);\n statusRef.current = recorder.status;\n const releaseMicrophone = useCallback(() => {\n if (capturing(statusRef.current)) {\n recorder.discard();\n }\n }, [recorder.discard]);\n\n // The registry SUPPLEMENTS the recorder's own unmount cleanup; it covers the\n // one case that cleanup cannot, a component left mounted and merely hidden.\n useEffect(() => {\n if (captureGroup === undefined) {\n return;\n }\n return joinCaptureGroup(captureGroup, releaseMicrophone);\n }, [captureGroup, releaseMicrophone]);\n\n // Identity-guarded so the mount run is a no-op: without it this fires after\n // the first paint and undoes every seed it was just given.\n const defaultSubmittedRef = useRef(defaultSubmitted);\n useEffect(() => {\n defaultSubmittedRef.current = defaultSubmitted;\n }, [defaultSubmitted]);\n // Keyed by the READING, never by the identity of `data` — see `readingKeyOf`\n // for the regression that identity caused. A host that rebuilds an\n // unchanged item keeps the take, the budget and any grade still in flight.\n const lastReadingRef = useRef(readingKey);\n useEffect(() => {\n if (lastReadingRef.current === readingKey) {\n return;\n }\n lastReadingRef.current = readingKey;\n submittingRef.current = false;\n // Anything still in flight belongs to the reading that has just left.\n runRef.current += 1;\n setPhase({ kind: 'idle' });\n setUploaded(null);\n setPlaybackUrl(null);\n // `reset`, not `discard`: the takes belong to the READING, and `discard`\n // refunds none of them on purpose. A new reading that inherited the old\n // one's spent takes renders a recorder that looks live and refuses to\n // record — reachable in `<ActivityPreview>`, whose remount key is derived\n // from the media alone, so an author who tries one take and then edits the\n // reference text can never record again without reloading the page.\n recorder.reset();\n reset(defaultSubmittedRef.current === true ? 'completed' : 'idle');\n }, [readingKey, reset, recorder.reset]);\n\n // What became of each take, told to the pager slot this sits in: the one\n // channel through which a sequence learns whether its set can be reported.\n //\n // Read from the committed PHASE, and only from it. Every way a submit can end\n // — a grade, a take with no grade, a failure, a retry, a learner recording\n // again, a reading reset — ends in a phase, so none of them can leave a set\n // waiting on a take this component is done with. And an effect runs after\n // the commit, which is after `onSubmit` and `onComplete` were called for the\n // same phase: a pager told a take has settled has already been handed\n // everything that take produced. The one state that must not wait for a\n // commit — a take on its way — is reported as the submit starts.\n const phaseTake = phase.kind === 'idle' ? null : phase.take;\n const phaseTakeState = takeStateOf(phase, uploaded);\n useEffect(() => {\n // An idle phase settles the take before it: re-recording drops a take\n // whose failure was being offered a retry, and so does a reading reset.\n const take = phaseTake ?? reportedTakeRef.current;\n if (slot === null || take === null) {\n return;\n }\n reportedTakeRef.current = take;\n slot.takeState(take, phaseTakeState);\n }, [slot, phaseTake, phaseTakeState]);\n\n // Model recordings stay silent for as long as the learner records: a\n // microphone that hears the model reading would assess the model's\n // pronunciation, not theirs.\n //\n // Pausing once, when the capture starts, is not enough — the play controls\n // are still on screen and still work throughout the take, so the hazard\n // simply moves one door along. The listener holds the guard for the whole\n // capture and is removed with it; `play` does not bubble, so it is taken in\n // the capture phase. It is what silences the browser's own control bar; a\n // budgeted model is refused before it is charged, by the `disabled` its\n // player is rendered with below.\n const modelsRef = useRef<HTMLDivElement>(null);\n const isCapturing = capturing(recorder.status);\n useEffect(() => {\n if (!isCapturing) {\n return;\n }\n const node = modelsRef.current;\n if (node === null) {\n return;\n }\n const hush = (): void => {\n for (const media of node.querySelectorAll('audio, video')) {\n const element = media as HTMLMediaElement;\n if (!element.paused) {\n element.pause();\n }\n }\n };\n hush();\n node.addEventListener('play', hush, true);\n return () => {\n node.removeEventListener('play', hush, true);\n };\n }, [isCapturing]);\n\n const slowMedia = useMemo(\n () => slowMediaFor(data, s.readAloudSlowRecording),\n [data, s.readAloudSlowRecording],\n );\n\n // The learner's own take, as every element that plays it plays it: the\n // preview before it is sent, the stored link a review mints, and the\n // per-word buttons in the feedback panel. One refusal covers all of them.\n const takeAudio = isReview ? playbackUrl : takeUrl;\n const playback = usePlaybackRefusal(takeAudio);\n // Where focus goes when a retry takes the \"Try again\" button away: the\n // submit it retries, which says it is busy for as long as the retry runs.\n const submitRef = useRef<HTMLButtonElement>(null);\n\n // All hooks are called before these throws, so hook order stays stable.\n if (devError) {\n throw devError;\n }\n // `review` needs no upload: it renders what a server already stored.\n if (!isReview && recordingBinding?.upload === undefined) {\n throw new Error(\n `ReadAloud \"${data.id}\" was rendered in renderMode \"${renderMode}\" without ` +\n '`recordingBinding.upload`. The learner could record a take that is stored ' +\n 'nowhere and submitted as nothing, which looks like a working activity the ' +\n 'whole way through. Pass a `recordingBinding` whose `upload` puts the take in ' +\n 'your storage and returns its key. `assess` is optional: without it the take is ' +\n 'still recorded, uploaded and submitted, and a notice replaces the feedback.',\n );\n }\n\n const submitted = state === 'completed';\n const inactive = disabled === true || submitted || isReview;\n const busy = phase.kind === 'uploading' || phase.kind === 'assessing';\n const takesLeft = maxTakes === undefined ? undefined : Math.max(0, maxTakes - recorder.takesUsed);\n\n /** Marks a failure so the alert announces even when the sentence repeats. */\n const raise = (next: SubmitPhase): void => {\n setErrorNonce((nonce) => nonce + 1);\n setPhase(next);\n };\n\n /**\n * Tells the pager slot, if there is one, that `take` is on its way — before\n * anything is awaited. The report a pager's set waits on must exist before\n * the first result it waits for can arrive; the take's later states follow\n * from the phase it reaches (see the effect above).\n */\n const beginTakeReport = (take: number): void => {\n reportedTakeRef.current = take;\n slot?.takeState(take, 'in-flight');\n };\n\n const runAssess = async (\n ref: RecordingRef,\n current: RecordedTake | null,\n take: number,\n ): Promise<void> => {\n const assess = bindingRef.current?.assess;\n const durationMs = current?.durationMs ?? 0;\n const takes = recorder.takesUsed;\n const run = runRef.current;\n if (assess === undefined) {\n // D5: a binding that stores but does not judge is a supported binding,\n // not a mistake — it is what an authoring preview supplies.\n setPhase({ kind: 'unassessed', take });\n return;\n }\n setPhase({ kind: 'assessing', take });\n fire('assessment-requested', { takes, durationMs });\n let answer: unknown;\n try {\n answer = await assess(ref);\n } catch {\n // Detached while the assessor was working: an instance that has been\n // unmounted, or handed a different reading, reports nothing for the one\n // it has left — not a phase, not an event, and not a grade.\n if (run !== runRef.current) {\n return;\n }\n raise({ kind: 'assess-failed', take, retryable: true });\n fire('assessment-failed', { takes, durationMs, retryable: true });\n return;\n }\n if (run !== runRef.current) {\n return;\n }\n // Read once, here, and nothing below touches `answer` again: the phase the\n // screen renders from, `onComplete` and the statement all take what the\n // reader returned, so they cannot disagree about what came back.\n const result = readAssessResult(answer);\n if (result.status === 'failed') {\n raise({ kind: 'assess-failed', take, retryable: result.retryable });\n fire('assessment-failed', { takes, durationMs, retryable: result.retryable });\n return;\n }\n if (result.status === 'unscorable') {\n // Reported as a failed assessment because that is what it is to a\n // consumer's log — a take that produced no grade — and `code` says which\n // kind. `retryable` is true because another take is offered.\n setPhase({ kind: 'unscorable', take, code: result.code });\n fire('assessment-failed', {\n takes,\n durationMs,\n retryable: true,\n ...(result.code !== undefined ? { code: result.code } : {}),\n });\n return;\n }\n const timeSpent = getTimeSpent();\n setPhase({ kind: 'graded', take, assessment: result.assessment, grade: result.grade });\n const grade = result.grade;\n if (grade === null) {\n // A judgement that carries no grade the reader can show is not an\n // `ActivityResult`: reporting one would mean inventing the score, exactly\n // as an `unscorable` result would. To a consumer's log it is what an\n // unscorable take is — an assessment that produced no grade, with another\n // take offered.\n fire('assessment-failed', { takes, durationMs, retryable: true });\n return;\n }\n\n // xAPI's `score.scaled` is a fraction in [-1, 1], and the SDK's builder\n // writes `ScoringResult.score` into it verbatim, then validates — so a\n // points-based grade (82 out of 100, which `GradeRecord` allows and a\n // grader's own units readily produce) does not merely ship a wrong number:\n // the build THROWS, and the grade never reaches the screen at all. The read\n // grade's fraction is in 0..1 by construction, and `passed` is a boolean by\n // construction, so the statement validates whatever the host sent. The\n // learner's own numbers go to `onComplete` untouched.\n //\n // Stamped with its take, as the response was: a pager records a grade only\n // for the take its slot holds now, so a grade that outlived a newer take\n // can never be filed beside that newer take's response.\n onComplete?.(\n stampTake(\n {\n score: grade.score,\n maxScore: grade.maxScore,\n passed: grade.passed,\n timeSpent,\n xapiStatement: xAPIBuilder.buildAnsweredStatement({\n actor: ANONYMOUS_ACTOR,\n object: {\n id: objectIdFor(data.id),\n name: { [data.locale ?? 'en-US']: data.title },\n ...xapiDefinitionFor(data),\n },\n timeSpentMs: timeSpent,\n response: result.recognizedText ?? '',\n // `ScoringResult.details` is required where `GradeRecord.details` is\n // optional, and an empty list is the honest reading of \"no per-word\n // marks were stored\" — never a mark of its own.\n scoringResult: {\n score: fractionOfGrade(grade),\n maxScore: 1,\n passed: grade.passed,\n feedback: grade.feedback,\n details: grade.details ?? [],\n },\n }),\n },\n take,\n ),\n );\n };\n\n const submitTake = async (current: RecordedTake | null): Promise<void> => {\n const binding = bindingRef.current;\n if (inactive || submittingRef.current || binding === undefined) {\n return;\n }\n submittingRef.current = true;\n const takes = recorder.takesUsed;\n const run = runRef.current;\n // Every submit is a take of its own, a retried upload included: a number\n // no earlier submit on this page had, so whatever an earlier one still\n // brings back cannot be taken for this one's.\n const take = mintTake();\n beginTakeReport(take);\n try {\n let ref: RecordingRef | null = null;\n if (current !== null) {\n setPhase({ kind: 'uploading', take });\n try {\n ref = await binding.upload(current);\n } catch {\n if (run !== runRef.current) {\n return;\n }\n // The take never reached storage, so nothing is submitted: a take\n // that failed to upload must not become a blank response.\n raise({ kind: 'upload-failed', take });\n fire('recording-upload-failed', {\n takes,\n durationMs: current.durationMs,\n retryable: true,\n });\n return;\n }\n // Detached while the take travelled. The bytes are in the application's\n // storage and this instance drops their key, which is a smaller cost\n // than the alternative: `onSubmit` carries no activity id, so a\n // response reported for a reading the learner has left is one a pager\n // would file against whatever is on screen now.\n if (run !== runRef.current) {\n return;\n }\n setUploaded(ref);\n fire('recording-uploaded', { takes, durationMs: current.durationMs });\n }\n onSubmit?.(stampTake(responseOf(ref, takes), take));\n fire('submitted', { takes, durationMs: current?.durationMs ?? 0 });\n if (isExam || ref === null) {\n // Exam never assesses, never scores, never reveals, and builds no xAPI\n // statement: its `correctResponsesPattern` would be the answer key, and\n // a read-aloud is graded on a server from the stored bytes.\n complete();\n setPhase({ kind: 'submitted', take });\n return;\n }\n await runAssess(ref, current, take);\n } finally {\n submittingRef.current = false;\n }\n };\n\n /**\n * Judges the take that is already stored, again — the SAME take, so the same\n * number. Guarded like a submit: an assessment costs an application money,\n * and two presses of one retry must not buy two of them.\n */\n const retryAssess = async (ref: RecordingRef, take: number): Promise<void> => {\n if (submittingRef.current) {\n return;\n }\n submittingRef.current = true;\n beginTakeReport(take);\n try {\n await runAssess(ref, recorder.take, take);\n } finally {\n submittingRef.current = false;\n }\n };\n\n const handleSubmit = (event: React.FormEvent): void => {\n event.preventDefault();\n if (inactive || busy || recorder.take === null || phase.kind !== 'idle') {\n return;\n }\n void submitTake(recorder.take);\n };\n\n const beginTake = (): void => {\n if (inactive || busy || !recorder.canRecord) {\n return;\n }\n if (recorder.take !== null) {\n fire('recording-discarded', {\n takes: recorder.takesUsed,\n durationMs: recorder.take.durationMs,\n });\n }\n setPhase({ kind: 'idle' });\n setUploaded(null);\n start();\n void recorder.start();\n };\n\n // The reading's own language and the direction its tag names. No `_`→`-`\n // normalisation, unlike `<Dictation>`: `ReadAloudData.locale` is canonical by\n // schema, so there is nothing to normalise.\n const contentLang = typeof data.locale === 'string' ? data.locale : undefined;\n const contentDir = localeDirectionOf(data.locale);\n\n // What the host sent, as the one reader of a host's grade reads it. Every\n // path below that shows or decides anything about a grade — the summary, the\n // panel, the marks, who renders the score, the announcement — reads these and\n // never `outcome` or `assessment` directly.\n const outcomeReading = useMemo(() => readOutcome(outcome), [outcome]);\n const keptEvidence = useMemo(() => readEvidence(assessment), [assessment]);\n const storedGrade =\n outcomeReading?.kind === 'graded' && outcomeReading.status === 'graded'\n ? outcomeReading.grade\n : null;\n // The marks a review shows: the assessment when the caller kept one, and the\n // stored details otherwise. With neither, the score is shown and no marks are.\n const storedMarks = useMemo(\n () =>\n isReview && keptEvidence === null && storedGrade?.details !== undefined\n ? marksFromDetails(storedGrade.details)\n : null,\n [isReview, keptEvidence, storedGrade],\n );\n const feedbackAssessment = isReview\n ? keptEvidence\n : phase.kind === 'graded'\n ? phase.assessment\n : null;\n const feedbackGrade = isReview ? storedGrade : phase.kind === 'graded' ? phase.grade : null;\n // A take the page refused to play is handed to no per-word button: each of\n // them would do nothing when pressed, and the note in the player's place\n // already says why.\n const feedbackAudio = playback.refused ? null : takeAudio;\n\n // Whether the score and the grader's words are already on screen inside the\n // feedback panel, which is what decides who renders them here.\n //\n // The props say the panel WAS GIVEN a grade; only the DOM says it rendered\n // one. The panel can fail outright — D2's dev throw for evidence the\n // validator refuses is one way, and nothing on either path validates a host's\n // grade either — and clipping on the props alone then hides a score that\n // exists nowhere, leaving a learner \"this activity could not be displayed\"\n // and no percentage at all. So the props may only ever clip for the single\n // commit before the fact arrives, and the fact may always un-clip: a grade on\n // screen twice is a far smaller defect than a grade nowhere. `.lk-pf-grade`\n // is the panel's own hook for this, named as such in its source.\n const rootRef = useRef<HTMLFormElement>(null);\n const [panelLostTheGrade, setPanelLostTheGrade] = useState(false);\n // Only a READ grade is handed to the panel, and the panel shows a read grade's\n // score without exception — so the prediction is made from the reader's\n // output, and never from a grade the panel would refuse.\n const panelHasGrade = feedbackAssessment !== null && feedbackGrade !== null;\n const gradeShownInPanel = panelHasGrade && !panelLostTheGrade;\n // Deliberately without a dependency list: what it reads is the committed DOM,\n // which no list can name. The updater keeps the settled value a fixed point,\n // so a render that changes nothing here costs one extra pass and then stops.\n useEffect(() => {\n const root = rootRef.current;\n const lost = panelHasGrade && (root === null || root.querySelector('.lk-pf-grade') === null);\n setPanelLostTheGrade((was) => (was === lost ? was : lost));\n });\n\n const announcement = useMemo<Announcement | null>(() => {\n if (isReview) {\n return null;\n }\n // The two moments of a capture, said once each. The per-second counter used\n // to be a live region of its own, so a ten-second take produced ten\n // announcements — and on any device without headphones a screen reader\n // speaks each of them into the take about to be assessed FOR PRONUNCIATION.\n // This component already stops the model recordings for exactly that\n // reason; the counter it generates itself is the same hazard.\n if (recorder.status === 'recording') {\n return { text: s.readAloudRecordingStarted, feedback: null };\n }\n // The length is in the sentence because the counter is no longer announced:\n // a take that ran to its bound reads back as \"20 of 20 seconds\", which is\n // how a learner hears that it stopped itself rather than that they stopped it.\n const stopped: Announcement | null =\n recorder.status === 'recorded' && take !== null\n ? {\n text: s.readAloudRecordingStopped(\n secondsWithin(take.durationMs, maxSeconds, 'nearest'),\n maxSeconds,\n ),\n feedback: null,\n }\n : null;\n if (isExam) {\n if (phase.kind === 'submitted' || submitted) {\n return { text: s.answerSubmitted, feedback: null };\n }\n return phase.kind === 'idle' ? stopped : null;\n }\n switch (phase.kind) {\n case 'graded': {\n const grade = phase.grade;\n // A judgement with no grade that can be read says so, in the same\n // sentence a review uses — never nothing, which reads as a grade\n // still on its way.\n return grade === null\n ? { text: `${s.answerSubmitted} ${s.couldNotBeGraded}`, feedback: null }\n : {\n text: `${s.answerSubmitted} ${s.scoreAnnouncement(percentOfGrade(grade), grade.passed)}`,\n feedback: grade.feedback,\n };\n }\n case 'unscorable':\n return { text: unscorableMessage(phase.code, s), feedback: null };\n case 'unassessed':\n return { text: s.readAloudAssessmentUnavailable, feedback: null };\n case 'idle':\n return stopped;\n default:\n // Uploading and assessing have their own pending sentence, and both\n // failures have an alert; a stale \"recording stopped\" beside either\n // would be the loudest thing on the screen.\n return null;\n }\n }, [isReview, isExam, phase, submitted, s, recorder.status, take, maxSeconds]);\n\n const alertText =\n recorder.error !== null\n ? s.readAloudRecorderError(recorder.error)\n : phase.kind === 'upload-failed'\n ? s.readAloudUploadFailed\n : phase.kind === 'assess-failed'\n ? s.readAloudAssessmentFailed\n : null;\n\n return (\n <form\n className=\"lk-ra\"\n ref={rootRef}\n // A title with nothing to say would name the form with silence.\n aria-labelledby={VISIBLE_TEXT_RE.test(data.title) ? titleId : undefined}\n lang={locale}\n data-render-mode={renderMode}\n style={theme as CSSProperties | undefined}\n onSubmit={handleSubmit}\n >\n {/*\n The form's name: read from here, it keeps the content's language. A\n paragraph of its own, so without a language to go by its own first\n letter decides its direction.\n */}\n <p id={titleId} className=\"lk-ra-title\" dir={contentDir ?? 'auto'} lang={contentLang}>\n {data.title}\n </p>\n\n {typeof data.instructions === 'string' && data.instructions !== '' ? (\n <p className=\"lk-ra-instructions\" dir={contentDir ?? 'auto'} lang={contentLang}>\n {data.instructions}\n </p>\n ) : null}\n\n {/* The reading itself. Never redacted: a read-aloud has no answer key, so\n the learner's copy carries the text in full. */}\n <p className=\"lk-ra-reference\" dir={contentDir ?? 'auto'} lang={contentLang}>\n {data.referenceText}\n </p>\n\n {data.media !== undefined || slowMedia !== undefined ? (\n <div className=\"lk-ra-models\" ref={modelsRef}>\n {/*\n Each model recording is a named group (a fieldset with a legend,\n which is what `role=\"group\"` plus a label spells natively), so the\n two players are told apart by name and not only by position.\n */}\n {data.media !== undefined ? (\n <fieldset className=\"lk-ra-model\">\n <legend className=\"lk-ra-model-label\">{s.readAloudModelRecording}</legend>\n {/* The ONLY budgeted recording: the binding is whatever the pager or the caller passes.\n Disabled while a take is captured, so a press is refused BEFORE a play is\n charged — silenced after the charge, it cost the learner a play they never\n heard. Nothing is announced for the refusal: a screen reader speaking now\n would speak into the take. */}\n <ActivityMedia\n media={data.media}\n renderMode={renderMode}\n playbackGroup={modelsGroup}\n {...(mediaBudget !== undefined ? { mediaBudget } : {})}\n {...(mediaStrings !== undefined ? { mediaStrings } : {})}\n {...(strings !== undefined ? { strings } : {})}\n {...(onInteraction !== undefined ? { onInteraction } : {})}\n {...(locale !== undefined ? { locale } : {})}\n {...(disabled === true || isCapturing ? { disabled: true } : {})}\n />\n </fieldset>\n ) : null}\n {slowMedia !== undefined ? (\n <fieldset className=\"lk-ra-model\" data-slow=\"true\">\n <legend className=\"lk-ra-model-label\">{s.readAloudSlowRecording}</legend>\n {/* No budget of its own, by contract — so no binding is passed and none is demanded. */}\n <ActivityMedia\n media={slowMedia}\n renderMode={renderMode}\n playbackGroup={modelsGroup}\n {...(mediaStrings !== undefined ? { mediaStrings } : {})}\n {...(strings !== undefined ? { strings } : {})}\n {...(locale !== undefined ? { locale } : {})}\n {...(disabled === true || isCapturing ? { disabled: true } : {})}\n />\n </fieldset>\n ) : null}\n </div>\n ) : null}\n\n {/* Gone once the item is committed, rather than left behind disabled: a\n submitted exam answer is not a control a learner should still meet. */}\n {isReview || submitted ? null : (\n <div\n className=\"lk-ra-recorder\"\n data-status={recorder.status}\n data-level={\n recorder.status === 'recording'\n ? Math.min(LEVEL_STEPS, Math.round(recorder.level * LEVEL_STEPS))\n : undefined\n }\n >\n {recorder.status === 'recording' ? (\n <button\n type=\"button\"\n className=\"lk-ra-stop\"\n disabled={inactive}\n onClick={() => recorder.stop()}\n >\n {s.readAloudStop}\n </button>\n ) : (\n // aria-disabled, never disabled, once the takes are spent: a\n // natively disabled button drops keyboard focus to the page body,\n // and this is the control a learner comes back to.\n <button\n type=\"button\"\n className=\"lk-ra-record\"\n disabled={inactive}\n aria-disabled={!recorder.canRecord || busy || undefined}\n onClick={beginTake}\n >\n {recorder.take === null ? s.readAloudRecord : s.readAloudRerecord}\n </button>\n )}\n {recorder.status === 'recording' ? (\n // Visible, and hidden from assistive technology, beside the level\n // meter the recorder's `data-level` drives. Its text changes every\n // whole second, so as a live region it announced ten times in a\n // ten-second take — into the microphone. What a screen-reader\n // learner needs instead is said once each, below.\n <p className=\"lk-ra-progress\" aria-hidden=\"true\">\n {s.readAloudRecordingProgress(\n secondsWithin(recorder.elapsedMs, maxSeconds, 'down'),\n maxSeconds,\n )}\n </p>\n ) : null}\n {takesLeft !== undefined && maxTakes !== undefined ? (\n <p className=\"lk-ra-takes\">{s.readAloudTakesRemaining(takesLeft, maxTakes)}</p>\n ) : null}\n </div>\n )}\n\n {takeAudio === null ? null : playback.refused ? (\n // In the player's place, not beside it: a player the page will not\n // load looks exactly like one that works until it is pressed, and then\n // does nothing. A note rather than an alert — the learner can still\n // submit, and nothing here asks them to act.\n <p className=\"lk-ra-take-unavailable\" role=\"note\">\n {s.readAloudPlaybackUnavailable}\n </p>\n ) : (\n // biome-ignore lint/a11y/useMediaCaption: the learner's own take has no caption track to offer; its accessible name says whose recording it is\n <audio\n className=\"lk-ra-take\"\n controls\n src={takeAudio}\n aria-label={s.readAloudYourRecording}\n onError={(event) => playback.refuse(event.currentTarget)}\n {...(isReview ? {} : { 'data-take': recorder.takesUsed })}\n />\n )}\n\n {isReview || submitted ? null : (\n <div className=\"lk-ra-actions\">\n <button\n ref={submitRef}\n type=\"submit\"\n className=\"lk-ra-submit\"\n disabled={disabled === true}\n aria-disabled={recorder.take === null || phase.kind !== 'idle' || undefined}\n aria-busy={busy || undefined}\n >\n {s.submit}\n </button>\n {isExam ? (\n // Explicit, and only here: an exam answer with no recording is a\n // decision a learner takes, never something a failed upload does\n // for them.\n <button\n type=\"button\"\n className=\"lk-ra-submit-blank\"\n disabled={inactive}\n aria-disabled={busy || undefined}\n onClick={() => {\n if (!busy) {\n void submitTake(null);\n }\n }}\n >\n {s.readAloudSubmitWithoutRecording}\n </button>\n ) : null}\n {offersRetry(phase, uploaded) ? (\n <button\n type=\"button\"\n className=\"lk-ra-retry\"\n onClick={() => {\n // The retry takes this button away with the phase it answers,\n // and focus on a removed element drops to the page body — a\n // keyboard learner is then back at the top of the page with no\n // idea the retry started. It goes to the submit being retried.\n submitRef.current?.focus();\n if (phase.kind === 'upload-failed') {\n void submitTake(recorder.take);\n } else if (phase.kind === 'assess-failed' && uploaded !== null) {\n void retryAssess(uploaded, phase.take);\n }\n }}\n >\n {s.readAloudTryAgain}\n </button>\n ) : null}\n </div>\n )}\n\n {busy ? (\n <p className=\"lk-ra-pending\" role=\"status\" aria-busy=\"true\">\n {phase.kind === 'uploading' ? s.readAloudUploading : s.readAloudAssessing}\n </p>\n ) : null}\n\n {alertText !== null ? (\n <p key={errorNonce} className=\"lk-ra-alert\" role=\"alert\">\n {alertText}\n </p>\n ) : null}\n\n {feedbackAssessment !== null ? (\n <PronunciationFeedback\n data={{ referenceText: data.referenceText, locale: data.locale }}\n assessment={feedbackAssessment}\n {...(feedbackGrade !== null ? { grade: feedbackGrade } : {})}\n {...(feedbackAudio !== null ? { audioUrl: feedbackAudio } : {})}\n {...(breakThreshold !== undefined ? { breakThreshold } : {})}\n {...(monotoneThreshold !== undefined ? { monotoneThreshold } : {})}\n {...(locale !== undefined ? { locale } : {})}\n {...(strings !== undefined ? { strings } : {})}\n />\n ) : null}\n\n {storedMarks !== null ? (\n <div className=\"lk-ra-marks\">\n {/* The list's name and the hidden sentences are interface text; only\n the words carry the reading's language. */}\n <ol className=\"lk-ra-words\" aria-label={s.pronunciationWordsLabel} dir={contentDir}>\n {storedMarks.map((entry, index) => {\n const sentence = markSentence(entry, s);\n return (\n <li\n // biome-ignore lint/suspicious/noArrayIndexKey: mark order is the identity; words repeat\n key={index}\n className=\"lk-ra-word\"\n data-state={entry.state}\n >\n {/* One channel for assistive technology, not two: the same\n sentence as a `title` is an accessible description, read\n straight after the name it repeats. */}\n <span className=\"lk-visually-hidden\" style={VISUALLY_HIDDEN}>\n {sentence}\n </span>\n <span\n className=\"lk-ra-word-text\"\n aria-hidden=\"true\"\n dir={contentDir ?? 'auto'}\n lang={contentLang}\n >\n {entry.reference}\n </span>\n </li>\n );\n })}\n </ol>\n {/* Three rows, not four: a stored mark set carries no insertions. */}\n <ul className=\"lk-ra-legend\">\n {STORED_STATES.map((state) => (\n <li key={state} data-state={state}>\n {s.pronunciationLegend(state)}\n </li>\n ))}\n </ul>\n </div>\n ) : null}\n\n <FeedbackRegion id={`${data.id}-feedback`}>\n {isReview ? (\n outcomeReading !== undefined ? (\n <OutcomeSummary reading={outcomeReading} s={s} scoreShown={gradeShownInPanel} />\n ) : null\n ) : gradeShownInPanel ? (\n // Announced, not repeated. A grade the feedback panel is already\n // showing would otherwise be read on screen twice, feedback and all —\n // but a live region is the only thing that TELLS a screen-reader\n // learner a grade arrived, since nothing takes focus. Clipped rather\n // than `display: none`, which would stop it being announced at all.\n <span className=\"lk-visually-hidden\" style={VISUALLY_HIDDEN}>\n <AnnouncementText announcement={announcement} />\n </span>\n ) : (\n <AnnouncementText announcement={announcement} />\n )}\n </FeedbackRegion>\n </form>\n );\n}\n","'use client';\n\nimport { useLkStrings } from '../../i18n/LkIntlProvider.js';\nimport { ActivityErrorBoundary } from '../ActivityErrorBoundary.js';\nimport { ReadAloud as ReadAloudCore, type ReadAloudProps } from './ReadAloud.js';\n\n// `RecordedTake` travels with `RecordingBinding` because `upload`, the one\n// method a binding must have, takes one: a binding written against this subpath\n// must not need a second import path to name its own argument.\nexport type { RecordedTake } from '../../hooks/useSpeechRecorder.js';\nexport type { ReadAloudAssessResult, ReadAloudProps, RecordingBinding } from './ReadAloud.js';\n\n/**\n * Public Read Aloud activity: the core wrapped in `ActivityErrorBoundary` so a\n * render failure (including the dev schema-validation throw and the\n * missing-`upload` guard) degrades to an accessible fallback instead of\n * crashing the host.\n */\nexport function ReadAloud(props: ReadAloudProps) {\n const strings = useLkStrings(props.strings);\n return (\n <ActivityErrorBoundary activityTitle={props.data?.title} strings={strings}>\n <ReadAloudCore {...props} />\n </ActivityErrorBoundary>\n );\n}\n"]}
@@ -1,6 +1,6 @@
1
1
  'use client';
2
- import { ActivityMedia } from './chunk-R7DI5UMU.js';
3
- import { useLkStrings } from './chunk-3Q4I4KT6.js';
2
+ import { ActivityMedia } from './chunk-DB4DLHN6.js';
3
+ import { useLkStrings } from './chunk-AWQ53DON.js';
4
4
  import { useId, useMemo } from 'react';
5
5
  import { jsxs, jsx } from 'react/jsx-runtime';
6
6
 
@@ -57,5 +57,5 @@ function StimulusPanel({
57
57
  }
58
58
 
59
59
  export { StimulusPanel };
60
- //# sourceMappingURL=chunk-NVP6UEQQ.js.map
61
- //# sourceMappingURL=chunk-NVP6UEQQ.js.map
60
+ //# sourceMappingURL=chunk-AS2QGND5.js.map
61
+ //# sourceMappingURL=chunk-AS2QGND5.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/components/StimulusPanel/StimulusPanel.tsx"],"names":[],"mappings":"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","file":"chunk-NVP6UEQQ.js","sourcesContent":["'use client';\n\nimport type { InteractionEvent, Stimulus } from '@intellectif/lk-core';\nimport { useId, useMemo } from 'react';\nimport { useLkStrings } from '../../i18n/LkIntlProvider.js';\nimport type { LkStringsOverride } from '../../i18n/strings.js';\nimport { ActivityMedia } from '../shared/ActivityMedia.js';\nimport type {\n HtmlSanitizer,\n MediaBudgetBinding,\n MediaTransportStrings,\n RenderMode,\n} from '../types.js';\n\nexport interface StimulusPanelProps {\n stimulus: Stimulus;\n /**\n * 1-based presented positions of the first and last question this material\n * serves, rendered as \"Questions 3–8\" so a learner knows how far the\n * passage carries.\n */\n range?: { first: number; last: number };\n /** Renders `stimulus.bodyHtml` when provided. See `HtmlSanitizer`. */\n sanitizeHtml?: HtmlSanitizer;\n locale?: string;\n /**\n * Forwarded to the stimulus's own media. `review` keeps the browser's control\n * bar even under an enforcing policy — a graded paper cannot be changed by\n * listening again.\n */\n renderMode?: RenderMode;\n /**\n * Binds this group's recording to a play budget the consumer persists. One\n * recording serves every question in the group, so the key is derived from\n * the ENTRY rather than the slot — see `stimulusMediaKey()` in lk-core.\n */\n mediaBudget?: MediaBudgetBinding;\n /** Translations for the audio transport chrome. */\n mediaStrings?: Partial<MediaTransportStrings>;\n onInteraction?: (event: InteractionEvent) => void;\n disabled?: boolean;\n /** Overrides the SDK's chrome text for this panel. See {@link LkIntlProvider}. */\n strings?: LkStringsOverride;\n}\n\n/**\n * Presents a shared stimulus — the passage, recording or image an item group's\n * questions refer to. A landmark region named by its title (or by its kind),\n * so a screen-reader user can jump back to the material from any question.\n *\n * Rich text follows the SDK-wide rule: `bodyHtml` is rendered only through a\n * caller-supplied sanitiser, and the plain `body` is the fallback — which the\n * schema guarantees exists whenever `bodyHtml` does.\n */\nexport function StimulusPanel({\n stimulus,\n range,\n sanitizeHtml,\n locale,\n renderMode,\n mediaBudget,\n mediaStrings,\n onInteraction,\n disabled,\n strings,\n}: StimulusPanelProps): React.JSX.Element {\n const titleId = useId();\n const s = useLkStrings(strings);\n\n const bodyHtml = useMemo(() => {\n if (sanitizeHtml === undefined || typeof stimulus.bodyHtml !== 'string') {\n return null;\n }\n return sanitizeHtml(stimulus.bodyHtml);\n }, [stimulus.bodyHtml, sanitizeHtml]);\n\n const rangeText = range === undefined ? null : s.stimulusRange(range.first, range.last);\n\n const naming =\n stimulus.title !== undefined\n ? { 'aria-labelledby': titleId }\n : { 'aria-label': s.stimulusKind[stimulus.kind] };\n\n // The AUTHORED parts carry the stimulus language; the SDK's own chrome does\n // not. Declaring `stimulus.locale` on the whole region made a screen reader\n // read \"Recording\" and \"Questions 3–5\" — English strings this package ships\n // — in the passage's voice, so a Spanish listening panel announced its own\n // name and the only orientation text in it with Spanish phonetics\n // (WCAG 3.1.2 Language of Parts). Scope the declaration to the content it\n // actually describes.\n const contentLang = stimulus.locale !== undefined ? { lang: stimulus.locale } : {};\n\n return (\n <section className=\"lk-stimulus\" data-kind={stimulus.kind} lang={locale} {...naming}>\n {stimulus.title !== undefined ? (\n <p className=\"lk-stimulus-title\" id={titleId} {...contentLang}>\n {stimulus.title}\n </p>\n ) : null}\n {rangeText !== null ? <p className=\"lk-stimulus-range\">{rangeText}</p> : null}\n {stimulus.media !== undefined ? (\n <ActivityMedia\n media={stimulus.media}\n {...(renderMode !== undefined ? { renderMode } : {})}\n {...(mediaBudget !== undefined ? { mediaBudget } : {})}\n {...(mediaStrings !== undefined ? { mediaStrings } : {})}\n {...(strings !== undefined ? { strings } : {})}\n {...(onInteraction !== undefined ? { onInteraction } : {})}\n {...(disabled !== undefined ? { disabled } : {})}\n {...(locale !== undefined ? { locale } : {})}\n />\n ) : null}\n {bodyHtml !== null ? (\n <div\n className=\"lk-stimulus-body\"\n data-format=\"html\"\n {...contentLang}\n // biome-ignore lint/security/noDangerouslySetInnerHtml: rendered only through the caller-supplied HtmlSanitizer — the SDK never injects unsanitised markup\n dangerouslySetInnerHTML={{ __html: bodyHtml }}\n />\n ) : stimulus.body !== undefined ? (\n <div className=\"lk-stimulus-body\" data-format=\"text\" {...contentLang}>\n {stimulus.body}\n </div>\n ) : null}\n {stimulus.attribution !== undefined ? (\n <p className=\"lk-stimulus-attribution\" {...contentLang}>\n <cite>{stimulus.attribution}</cite>\n </p>\n ) : null}\n </section>\n );\n}\n"]}
1
+ {"version":3,"sources":["../src/components/StimulusPanel/StimulusPanel.tsx"],"names":[],"mappings":"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","file":"chunk-AS2QGND5.js","sourcesContent":["'use client';\n\nimport type { InteractionEvent, Stimulus } from '@intellectif/lk-core';\nimport { useId, useMemo } from 'react';\nimport { useLkStrings } from '../../i18n/LkIntlProvider.js';\nimport type { LkStringsOverride } from '../../i18n/strings.js';\nimport { ActivityMedia } from '../shared/ActivityMedia.js';\nimport type {\n HtmlSanitizer,\n MediaBudgetBinding,\n MediaTransportStrings,\n RenderMode,\n} from '../types.js';\n\nexport interface StimulusPanelProps {\n stimulus: Stimulus;\n /**\n * 1-based presented positions of the first and last question this material\n * serves, rendered as \"Questions 3–8\" so a learner knows how far the\n * passage carries.\n */\n range?: { first: number; last: number };\n /** Renders `stimulus.bodyHtml` when provided. See `HtmlSanitizer`. */\n sanitizeHtml?: HtmlSanitizer;\n locale?: string;\n /**\n * Forwarded to the stimulus's own media. `review` keeps the browser's control\n * bar even under an enforcing policy — a graded paper cannot be changed by\n * listening again.\n */\n renderMode?: RenderMode;\n /**\n * Binds this group's recording to a play budget the consumer persists. One\n * recording serves every question in the group, so the key is derived from\n * the ENTRY rather than the slot — see `stimulusMediaKey()` in lk-core.\n */\n mediaBudget?: MediaBudgetBinding;\n /** Translations for the audio transport chrome. */\n mediaStrings?: Partial<MediaTransportStrings>;\n onInteraction?: (event: InteractionEvent) => void;\n disabled?: boolean;\n /** Overrides the SDK's chrome text for this panel. See {@link LkIntlProvider}. */\n strings?: LkStringsOverride;\n}\n\n/**\n * Presents a shared stimulus — the passage, recording or image an item group's\n * questions refer to. A landmark region named by its title (or by its kind),\n * so a screen-reader user can jump back to the material from any question.\n *\n * Rich text follows the SDK-wide rule: `bodyHtml` is rendered only through a\n * caller-supplied sanitiser, and the plain `body` is the fallback — which the\n * schema guarantees exists whenever `bodyHtml` does.\n */\nexport function StimulusPanel({\n stimulus,\n range,\n sanitizeHtml,\n locale,\n renderMode,\n mediaBudget,\n mediaStrings,\n onInteraction,\n disabled,\n strings,\n}: StimulusPanelProps): React.JSX.Element {\n const titleId = useId();\n const s = useLkStrings(strings);\n\n const bodyHtml = useMemo(() => {\n if (sanitizeHtml === undefined || typeof stimulus.bodyHtml !== 'string') {\n return null;\n }\n return sanitizeHtml(stimulus.bodyHtml);\n }, [stimulus.bodyHtml, sanitizeHtml]);\n\n const rangeText = range === undefined ? null : s.stimulusRange(range.first, range.last);\n\n const naming =\n stimulus.title !== undefined\n ? { 'aria-labelledby': titleId }\n : { 'aria-label': s.stimulusKind[stimulus.kind] };\n\n // The AUTHORED parts carry the stimulus language; the SDK's own chrome does\n // not. Declaring `stimulus.locale` on the whole region made a screen reader\n // read \"Recording\" and \"Questions 3–5\" — English strings this package ships\n // — in the passage's voice, so a Spanish listening panel announced its own\n // name and the only orientation text in it with Spanish phonetics\n // (WCAG 3.1.2 Language of Parts). Scope the declaration to the content it\n // actually describes.\n const contentLang = stimulus.locale !== undefined ? { lang: stimulus.locale } : {};\n\n return (\n <section className=\"lk-stimulus\" data-kind={stimulus.kind} lang={locale} {...naming}>\n {stimulus.title !== undefined ? (\n <p className=\"lk-stimulus-title\" id={titleId} {...contentLang}>\n {stimulus.title}\n </p>\n ) : null}\n {rangeText !== null ? <p className=\"lk-stimulus-range\">{rangeText}</p> : null}\n {stimulus.media !== undefined ? (\n <ActivityMedia\n media={stimulus.media}\n {...(renderMode !== undefined ? { renderMode } : {})}\n {...(mediaBudget !== undefined ? { mediaBudget } : {})}\n {...(mediaStrings !== undefined ? { mediaStrings } : {})}\n {...(strings !== undefined ? { strings } : {})}\n {...(onInteraction !== undefined ? { onInteraction } : {})}\n {...(disabled !== undefined ? { disabled } : {})}\n {...(locale !== undefined ? { locale } : {})}\n />\n ) : null}\n {bodyHtml !== null ? (\n <div\n className=\"lk-stimulus-body\"\n data-format=\"html\"\n {...contentLang}\n // biome-ignore lint/security/noDangerouslySetInnerHtml: rendered only through the caller-supplied HtmlSanitizer — the SDK never injects unsanitised markup\n dangerouslySetInnerHTML={{ __html: bodyHtml }}\n />\n ) : stimulus.body !== undefined ? (\n <div className=\"lk-stimulus-body\" data-format=\"text\" {...contentLang}>\n {stimulus.body}\n </div>\n ) : null}\n {stimulus.attribution !== undefined ? (\n <p className=\"lk-stimulus-attribution\" {...contentLang}>\n <cite>{stimulus.attribution}</cite>\n </p>\n ) : null}\n </section>\n );\n}\n"]}
@@ -51,6 +51,82 @@ var DEFAULT_STRINGS = {
51
51
  dictationLegendExtra: "Extra",
52
52
  dictationNothingTyped: "Nothing to compare: no words were entered.",
53
53
  dictationWordsSummary: (correct, total) => `${correct} of ${total} ${total === 1 ? "word" : "words"} correct.`,
54
+ readAloudModelRecording: "Model recording",
55
+ readAloudSlowRecording: "Slow model recording",
56
+ readAloudRecord: "Record",
57
+ readAloudStop: "Stop recording",
58
+ readAloudRerecord: "Record again",
59
+ readAloudYourRecording: "Your recording",
60
+ readAloudPlaybackUnavailable: "Your recording cannot be played back on this page.",
61
+ readAloudRecordingProgress: (seconds, maxSeconds) => `Recording: ${seconds} of ${maxSeconds} seconds`,
62
+ readAloudRecordingStarted: "Recording started.",
63
+ readAloudRecordingStopped: (seconds, maxSeconds) => `Recording stopped. ${seconds} of ${maxSeconds} seconds recorded.`,
64
+ readAloudTakesRemaining: (remaining, max) => `${remaining} of ${max} recording${max === 1 ? "" : "s"} left`,
65
+ readAloudSubmitWithoutRecording: "Submit without recording",
66
+ readAloudUploading: "Sending your recording\u2026",
67
+ readAloudUploadFailed: "Your recording could not be sent.",
68
+ readAloudTryAgain: "Try again",
69
+ readAloudAssessing: "Checking your pronunciation\u2026",
70
+ readAloudAssessmentUnavailable: "Pronunciation feedback is not available for this activity.",
71
+ readAloudNotHeard: "We could not hear you. Record again somewhere quieter.",
72
+ readAloudNotAssessed: "This recording could not be assessed. Try recording it again.",
73
+ readAloudAssessmentFailed: "Your pronunciation could not be checked.",
74
+ readAloudRecorderError: (reason) => {
75
+ switch (reason) {
76
+ case "permission-denied":
77
+ return "This page is not allowed to use the microphone.";
78
+ case "no-device":
79
+ return "No microphone was found.";
80
+ case "unsupported":
81
+ return "This browser cannot record audio.";
82
+ case "too-short":
83
+ return "That recording was too short.";
84
+ default:
85
+ return "The recording could not be made.";
86
+ }
87
+ },
88
+ pronunciationFeedbackLabel: "Pronunciation feedback",
89
+ pronunciationDimension: (dimension) => {
90
+ switch (dimension) {
91
+ case "accuracy":
92
+ return "Accuracy";
93
+ case "fluency":
94
+ return "Fluency";
95
+ case "completeness":
96
+ return "Completeness";
97
+ default:
98
+ return "Intonation";
99
+ }
100
+ },
101
+ pronunciationNotAssessed: "Not assessed",
102
+ pronunciationWordsLabel: "Your reading, word by word",
103
+ pronunciationLegend: (state) => {
104
+ switch (state) {
105
+ case "correct":
106
+ return "Read correctly";
107
+ case "mispronounced":
108
+ return "Mispronounced";
109
+ case "omitted":
110
+ return "Not read";
111
+ default:
112
+ return "Added";
113
+ }
114
+ },
115
+ pronunciationWordCorrect: (word) => `\u201C${word}\u201D was read correctly`,
116
+ pronunciationWordMispronounced: (word) => `\u201C${word}\u201D was mispronounced`,
117
+ pronunciationWordOmitted: (word) => `\u201C${word}\u201D was not read`,
118
+ pronunciationWordInserted: (word) => `\u201C${word}\u201D was added`,
119
+ pronunciationWordDetails: (word) => `Details for \u201C${word}\u201D`,
120
+ pronunciationSyllables: "Syllables",
121
+ pronunciationPhonemes: "Sounds",
122
+ pronunciationSyllableSpelling: (syllable, grapheme) => `${syllable} (spelled \u201C${grapheme}\u201D)`,
123
+ pronunciationPhonemePosition: (ordinal) => `Sound ${ordinal}`,
124
+ pronunciationHeardAs: "Heard as",
125
+ pronunciationPlayWord: (word) => `Play \u201C${word}\u201D`,
126
+ pronunciationBreakUnexpected: "There was an unexpected pause here.",
127
+ pronunciationBreakMissing: "A pause was expected here.",
128
+ pronunciationMonotone: "Your reading stayed on one note. Try varying your pitch.",
129
+ pronunciationIpaNote: "Sounds are written in the International Phonetic Alphabet.",
54
130
  previewIncomplete: "This activity is not finished yet, so it cannot be previewed.",
55
131
  previewInvalid: "This activity has a problem to fix before it can be previewed.",
56
132
  embeddedMedia: "Embedded media",
@@ -133,5 +209,5 @@ function useLkDirection() {
133
209
  }
134
210
 
135
211
  export { DEFAULT_STRINGS, LkIntlProvider, directionForLocale, mergeStrings, useLkDirection, useLkStrings };
136
- //# sourceMappingURL=chunk-3Q4I4KT6.js.map
137
- //# sourceMappingURL=chunk-3Q4I4KT6.js.map
212
+ //# sourceMappingURL=chunk-AWQ53DON.js.map
213
+ //# sourceMappingURL=chunk-AWQ53DON.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/i18n/strings.ts","../src/i18n/LkIntlProvider.tsx"],"names":[],"mappings":"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","file":"chunk-AWQ53DON.js","sourcesContent":["import type { ReadAloudDimension, ReadAloudWordState, Stimulus } from '@intellectif/lk-core';\nimport type { MediaTransportStrings } from '../components/types.js';\nimport type { SpeechRecorderError } from '../hooks/useSpeechRecorder.js';\n\n/**\n * Every string the SDK's own chrome renders.\n *\n * Until 0.8.1 these were literals, and `locale` set only the `lang` attribute —\n * so a learner sitting a Spanish paper read a Spanish passage inside an English\n * scaffold, and \"No plays remaining\" on a listening exam was untranslatable.\n * This is the whole surface, in one place, so a consumer can replace it.\n *\n * **Interpolation and plurals are functions, not format strings.** A message\n * like `\"Question {n} of {total}\"` needs a parser, and a parser needs plural\n * rules for every locale the SDK does not know. A function hands both problems\n * to the consumer, who has `Intl.PluralRules` and knows their own language:\n *\n * ```ts\n * questionProgress: (index, total) => `Pregunta ${index} de ${total}`,\n * wordCount: (n) => `${n} ${n === 1 ? 'palabra' : 'palabras'}`,\n * ```\n *\n * It also lets a translation reorder its arguments, which a positional token\n * cannot, and TypeScript checks the arity. No parser, no message catalogue\n * format, no runtime dependency.\n *\n * **What is deliberately NOT here: thrown errors.** Every `throw` in this\n * package is addressed to the developer who wired the component up, not to the\n * learner. Translating them would make them unsearchable and would put the one\n * audience who can act on them behind a translation. They stay English.\n */\nexport interface LkStrings {\n // ── Submission ─────────────────────────────────────────────────────────\n /** Multiple Choice submit button, and Written Response. */\n submit: string;\n /** Fill-in-the-Blanks in `practice`: the learner is checking their own work. */\n checkAnswers: string;\n /** Fill-in-the-Blanks in `exam`: nothing is checked on the client. */\n submitAnswers: string;\n\n // ── Outcome, announced after submit ────────────────────────────────────\n /**\n * The scored result, read by a screen reader on submit.\n *\n * `percent` is already rounded to a whole number. The authored overall\n * feedback is appended SEPARATELY by the component, so a translation of this\n * sentence never has to carry the author's words — the two were previously\n * concatenated into one literal, which made the score sentence untranslatable\n * without also touching authored content.\n */\n scoreAnnouncement: (percent: number, passed: boolean) => string;\n /** Prefix announced on submit, before the score sentence. */\n answerSubmitted: string;\n /** An answer submitted for grading that has none yet. Never rendered as 0%. */\n notGradedYet: string;\n /** An outcome that carries no grade and never will. */\n noGradeAvailable: string;\n\n // ── Feedback disclosure ────────────────────────────────────────────────\n showFeedback: string;\n hideFeedback: string;\n\n // ── Fill-in-the-Blanks ─────────────────────────────────────────────────\n /** Accessible name of a blank's input. `ordinal` is 1-based. */\n blankLabel: (ordinal: number) => string;\n\n // ── Gap select ────────────────────────────────────────────────────────────\n /** Accessible name of the selector for one gap in a Gap Select passage. */\n gapLabel: (ordinal: number) => string;\n /**\n * The empty first entry of a gap's selector — how a learner leaves a gap\n * alone, and how they take an answer back. Never a real choice.\n */\n gapPlaceholder: string;\n showHint: string;\n hideHint: string;\n\n // ── Written Response ───────────────────────────────────────────────────\n /**\n * Announced when an essay is handed in. Distinct from {@link LkStrings.awaitingGrade},\n * which is the `review`-mode state of a submission made earlier: this one\n * confirms the act, and is the only thing a learner hears at the moment they\n * commit work that nothing on the client will grade.\n */\n responseSubmitted: string;\n /** Live word counter. Needs the count for languages whose plural rules differ. */\n wordCount: (count: number) => string;\n /** The authored bounds, e.g. \"50–200 words\". `min` is 0 when unbounded below. */\n wordBounds: (min: number, max: number) => string;\n /** A rubric criterion the grader marked not applicable. */\n notApplicable: string;\n /** A returned grade flagged for a human to look at — learner-visible by design. */\n awaitingHumanReview: string;\n /** Submitted, no grade back yet. */\n awaitingGrade: string;\n /** The grader could not produce a grade. */\n couldNotBeGraded: string;\n\n // ── Sequence pager ─────────────────────────────────────────────────────\n previous: string;\n next: string;\n /** Progress label, also the question region's accessible name. `index` is 1-based. */\n questionProgress: (index: number, total: number) => string;\n /** Shown when no renderer is registered for an activity type. */\n unsupportedActivity: string;\n\n // ── Dictation ──────────────────────────────────────────────────────────\n /** Accessible name of the box the learner types the recording into. */\n dictationInputLabel: string;\n /** Accessible name of the group holding the recording. */\n dictationRecording: string;\n /** Accessible name of the group holding the slower recording, and its default `alt`. */\n dictationSlowRecording: string;\n /** The progressive-hint button, with how many transcript words are shown so far. */\n dictationRevealNextWord: (revealed: number, total: number) => string;\n /** Hides every revealed hint word again. */\n dictationResetHints: string;\n /** The solution toggle: shows the transcript after the attempt is marked. */\n showSolution: string;\n hideSolution: string;\n /** Accessible name of the revealed transcript. */\n dictationSolutionLabel: string;\n /** Accessible name of the word-by-word marked result. */\n dictationMarksLabel: string;\n /** Tells the learner the marks compare the normalised text, not what they typed. */\n dictationDiffNote: string;\n /**\n * The four sentences a screen reader hears for a marked word — the only\n * channel the marks have for assistive technology; the glyphs and the\n * character diff are decoration.\n */\n dictationWordCorrect: (word: string) => string;\n dictationWordWrong: (word: string, expected: string) => string;\n dictationWordMissing: (expected: string) => string;\n dictationWordExtra: (word: string) => string;\n /** The legend rows; the character-level marks reuse the same four. */\n dictationLegendCorrect: string;\n dictationLegendWrong: string;\n dictationLegendMissing: string;\n dictationLegendExtra: string;\n /** Marked result of an attempt with no words in it once punctuation and spacing are ignored, an empty one included. */\n dictationNothingTyped: string;\n /**\n * Appended to the score announcement, e.g. \"4 of 6 words correct.\" — a whole\n * sentence, because authored feedback may follow it.\n */\n dictationWordsSummary: (correct: number, total: number) => string;\n\n // ── Read aloud ─────────────────────────────────────────────────────────\n /** Accessible name of the group holding the model recording. */\n readAloudModelRecording: string;\n /** Accessible name of the group holding the slower model recording, and its default `alt`. */\n readAloudSlowRecording: string;\n /** Starts a take. */\n readAloudRecord: string;\n /** Ends the take in progress. */\n readAloudStop: string;\n /** Throws the take away so the learner can start over. */\n readAloudRerecord: string;\n /** Accessible name of the player for the take the learner just made. */\n readAloudYourRecording: string;\n /**\n * In place of the player for the learner's own take, and of the buttons that\n * play one word of it, when the page refuses to play it — most often a\n * Content-Security-Policy whose `media-src` does not allow `blob:`. Said\n * rather than left as controls that do nothing when pressed.\n */\n readAloudPlaybackUnavailable: string;\n /**\n * Live progress while recording. Whole seconds, not a `m:ss` clock: a\n * recording is bounded in seconds by the item, and a translation should not\n * have to reimplement a clock to change the word around it.\n *\n * Shown on screen and hidden from assistive technology: it changes every\n * second, and a screen reader that spoke each change would speak into the\n * take being assessed. The two sentences below are what is announced instead.\n */\n readAloudRecordingProgress: (seconds: number, maxSeconds: number) => string;\n /** Announced once when a capture begins. */\n readAloudRecordingStarted: string;\n /**\n * Announced once when a capture ends, by the learner or at its own bound.\n * Carries the length because the progress counter is not announced: a take\n * that reads back as \"20 of 20 seconds\" is one that stopped itself.\n */\n readAloudRecordingStopped: (seconds: number, maxSeconds: number) => string;\n /** How many takes are left. Called with 0 as well, so the wording is one translation's. */\n readAloudTakesRemaining: (remaining: number, max: number) => string;\n /** `exam` only: hands the item in with no recording at all, deliberately. */\n readAloudSubmitWithoutRecording: string;\n /** Pending, while the take travels to the application's storage. */\n readAloudUploading: string;\n /** The take never reached storage, so nothing was submitted. */\n readAloudUploadFailed: string;\n /** Retries whichever step just failed. */\n readAloudTryAgain: string;\n /** Pending, while the assessor judges the take. */\n readAloudAssessing: string;\n /** The binding stores recordings but judges none, so there is no feedback to show. */\n readAloudAssessmentUnavailable: string;\n /** The assessor found no speech, or too little of it, in the take. */\n readAloudNotHeard: string;\n /** The assessor produced no grade for any other reason. The code itself is never shown. */\n readAloudNotAssessed: string;\n /** The assessment could not be run at all — a failure of the call, not of the reading. */\n readAloudAssessmentFailed: string;\n /**\n * What went wrong with the microphone or with the take. One function rather\n * than five keys, so a translation keeps the five sentences together and the\n * SDK keeps one name for the concept.\n */\n readAloudRecorderError: (reason: SpeechRecorderError) => string;\n\n // ── Pronunciation feedback ─────────────────────────────────────────────\n /** Accessible name of the whole feedback region. */\n pronunciationFeedbackLabel: string;\n /** Name of one scored dimension, as a learner reads it. */\n pronunciationDimension: (dimension: ReadAloudDimension) => string;\n /** A dimension or a word the engine did not measure. NEVER rendered as 0%. */\n pronunciationNotAssessed: string;\n /** Accessible name of the word-by-word marks. */\n pronunciationWordsLabel: string;\n /** One legend row per marking state — the same four states the marks carry. */\n pronunciationLegend: (state: ReadAloudWordState) => string;\n /**\n * The four sentences a screen reader hears for a marked word: the only\n * channel the marks have for assistive technology, since the glyph and the\n * decoration are both hidden from it.\n */\n pronunciationWordCorrect: (word: string) => string;\n pronunciationWordMispronounced: (word: string) => string;\n pronunciationWordOmitted: (word: string) => string;\n pronunciationWordInserted: (word: string) => string;\n /** Opens one word's syllables, sounds and timings. */\n pronunciationWordDetails: (word: string) => string;\n pronunciationSyllables: string;\n pronunciationPhonemes: string;\n /** A syllable the engine also spelled out, e.g. `('ˈhæ', 'ha')`. */\n pronunciationSyllableSpelling: (syllable: string, grapheme: string) => string;\n /** A sound the engine named no symbol for; `ordinal` is 1-based. */\n pronunciationPhonemePosition: (ordinal: number) => string;\n /** Introduces the sounds the engine thought it heard in a sound's place. */\n pronunciationHeardAs: string;\n /** Plays one word out of the learner's own take. */\n pronunciationPlayWord: (word: string) => string;\n pronunciationBreakUnexpected: string;\n pronunciationBreakMissing: string;\n /** Shown only past the caller's own monotone threshold; the SDK sets none. */\n pronunciationMonotone: string;\n /** Names the alphabet the phoneme symbols are written in. */\n pronunciationIpaNote: string;\n\n // ── Authoring preview ──────────────────────────────────────────────────\n /**\n * `<ActivityPreview>`, in place of a draft that still has something missing.\n * Author-facing, not learner-facing — but an editor is translated too.\n */\n previewIncomplete: string;\n /** `<ActivityPreview>`, in place of a draft with something wrong in it. */\n previewInvalid: string;\n\n // ── Embedded media ────────────────────────────────────────────────────────\n /**\n * Accessible name of a provider iframe whose author supplied no `alt`.\n * `MediaSchema` requires `alt` on an embed, so validated content never\n * reaches this — but `validateActivity` is opt-in, and a screen-reader\n * learner meeting an unvalidated one should not meet it in English.\n */\n embeddedMedia: string;\n\n // ── Shared stimulus ────────────────────────────────────────────────────\n /**\n * Accessible name of the stimulus region when the author gave it no title.\n * Keyed by `Stimulus['kind']`, because \"Recording\" and \"Passage\" do not share\n * a gender or an article in every language.\n */\n stimulusKind: Record<Stimulus['kind'], string>;\n /**\n * How far the material carries, e.g. \"Questions 3–8\". Called with equal\n * `first` and `last` for a single question, so a translation can choose its\n * own singular rather than receive a pre-pluralised English one.\n */\n stimulusRange: (first: number, last: number) => string;\n\n // ── Error boundary ─────────────────────────────────────────────────────\n /** Production fallback when an activity throws during render. */\n activityFailed: string;\n /** The same, when the activity's title is known. */\n activityFailedNamed: (title: string) => string;\n /** Production fallback when the activity has no title to name. */\n activityFailedUnnamed: string;\n\n // ── Audio transport ────────────────────────────────────────────────────\n /**\n * The listening-paper transport. Shipped in 0.8.0 as its own `mediaStrings`\n * prop, which still works — this is the same shape, reachable from the one\n * provider so a consumer does not wire two mechanisms.\n */\n media: MediaTransportStrings;\n}\n\n/** A partial override, nested one level for {@link LkStrings.media}. */\nexport type LkStringsOverride = Partial<Omit<LkStrings, 'media' | 'stimulusKind'>> & {\n media?: Partial<MediaTransportStrings>;\n stimulusKind?: Partial<Record<Stimulus['kind'], string>>;\n};\n\n/**\n * English defaults, byte-identical to what every component rendered before the\n * strings surface existed. Supplying no override changes nothing on screen.\n */\nexport const DEFAULT_STRINGS: LkStrings = {\n submit: 'Submit',\n checkAnswers: 'Check answers',\n submitAnswers: 'Submit answers',\n\n scoreAnnouncement: (percent, passed) =>\n `Score ${percent}%. ${passed ? 'Passed.' : 'Not passed.'}`,\n answerSubmitted: 'Answer submitted.',\n notGradedYet: 'Not graded yet.',\n noGradeAvailable: 'No grade available.',\n\n showFeedback: 'Show feedback',\n hideFeedback: 'Hide feedback',\n\n blankLabel: (ordinal) => `Fill in blank ${ordinal}`,\n\n gapLabel: (ordinal) => `Choose the answer for gap ${ordinal}`,\n gapPlaceholder: 'Choose…',\n showHint: 'Show hint',\n hideHint: 'Hide hint',\n\n responseSubmitted:\n 'Response submitted. It will be graded and your result will appear here later.',\n wordCount: (count) => `${count} ${count === 1 ? 'word' : 'words'}`,\n wordBounds: (min, max) => (min > 0 ? `${min}–${max} words` : `up to ${max} words`),\n notApplicable: 'Not applicable',\n awaitingHumanReview: 'This grade is awaiting review by a teacher.',\n awaitingGrade: 'Not graded yet. This response is waiting for its grade.',\n couldNotBeGraded: 'This response could not be graded.',\n\n previous: 'Previous',\n next: 'Next',\n questionProgress: (index, total) => `Question ${index} of ${total}`,\n unsupportedActivity: 'This activity type has no renderer. Supply one through the renderers prop.',\n\n dictationInputLabel: 'Type what you hear',\n dictationRecording: 'Recording',\n dictationSlowRecording: 'Slow recording',\n dictationRevealNextWord: (revealed, total) =>\n `Reveal the next word (${revealed} of ${total} shown)`,\n dictationResetHints: 'Reset hints',\n showSolution: 'Show solution',\n hideSolution: 'Hide solution',\n dictationSolutionLabel: 'Solution',\n dictationMarksLabel: 'Your answer, word by word',\n dictationDiffNote: 'Compared after ignoring case, punctuation and extra spaces.',\n dictationWordCorrect: (word) => `“${word}” is correct`,\n dictationWordWrong: (word, expected) => `“${word}” should be “${expected}”`,\n dictationWordMissing: (expected) => `“${expected}” is missing`,\n dictationWordExtra: (word) => `“${word}” is extra`,\n dictationLegendCorrect: 'Correct',\n dictationLegendWrong: 'Wrong',\n dictationLegendMissing: 'Missing',\n dictationLegendExtra: 'Extra',\n dictationNothingTyped: 'Nothing to compare: no words were entered.',\n dictationWordsSummary: (correct, total) =>\n `${correct} of ${total} ${total === 1 ? 'word' : 'words'} correct.`,\n\n readAloudModelRecording: 'Model recording',\n readAloudSlowRecording: 'Slow model recording',\n readAloudRecord: 'Record',\n readAloudStop: 'Stop recording',\n readAloudRerecord: 'Record again',\n readAloudYourRecording: 'Your recording',\n readAloudPlaybackUnavailable: 'Your recording cannot be played back on this page.',\n readAloudRecordingProgress: (seconds, maxSeconds) =>\n `Recording: ${seconds} of ${maxSeconds} seconds`,\n readAloudRecordingStarted: 'Recording started.',\n readAloudRecordingStopped: (seconds, maxSeconds) =>\n `Recording stopped. ${seconds} of ${maxSeconds} seconds recorded.`,\n readAloudTakesRemaining: (remaining, max) =>\n `${remaining} of ${max} recording${max === 1 ? '' : 's'} left`,\n readAloudSubmitWithoutRecording: 'Submit without recording',\n readAloudUploading: 'Sending your recording…',\n readAloudUploadFailed: 'Your recording could not be sent.',\n readAloudTryAgain: 'Try again',\n readAloudAssessing: 'Checking your pronunciation…',\n readAloudAssessmentUnavailable: 'Pronunciation feedback is not available for this activity.',\n readAloudNotHeard: 'We could not hear you. Record again somewhere quieter.',\n readAloudNotAssessed: 'This recording could not be assessed. Try recording it again.',\n readAloudAssessmentFailed: 'Your pronunciation could not be checked.',\n readAloudRecorderError: (reason) => {\n switch (reason) {\n case 'permission-denied':\n return 'This page is not allowed to use the microphone.';\n case 'no-device':\n return 'No microphone was found.';\n case 'unsupported':\n return 'This browser cannot record audio.';\n case 'too-short':\n return 'That recording was too short.';\n default:\n return 'The recording could not be made.';\n }\n },\n\n pronunciationFeedbackLabel: 'Pronunciation feedback',\n pronunciationDimension: (dimension) => {\n switch (dimension) {\n case 'accuracy':\n return 'Accuracy';\n case 'fluency':\n return 'Fluency';\n case 'completeness':\n return 'Completeness';\n default:\n return 'Intonation';\n }\n },\n pronunciationNotAssessed: 'Not assessed',\n pronunciationWordsLabel: 'Your reading, word by word',\n pronunciationLegend: (state) => {\n switch (state) {\n case 'correct':\n return 'Read correctly';\n case 'mispronounced':\n return 'Mispronounced';\n case 'omitted':\n return 'Not read';\n default:\n return 'Added';\n }\n },\n pronunciationWordCorrect: (word) => `“${word}” was read correctly`,\n pronunciationWordMispronounced: (word) => `“${word}” was mispronounced`,\n pronunciationWordOmitted: (word) => `“${word}” was not read`,\n pronunciationWordInserted: (word) => `“${word}” was added`,\n pronunciationWordDetails: (word) => `Details for “${word}”`,\n pronunciationSyllables: 'Syllables',\n pronunciationPhonemes: 'Sounds',\n pronunciationSyllableSpelling: (syllable, grapheme) => `${syllable} (spelled “${grapheme}”)`,\n pronunciationPhonemePosition: (ordinal) => `Sound ${ordinal}`,\n pronunciationHeardAs: 'Heard as',\n pronunciationPlayWord: (word) => `Play “${word}”`,\n pronunciationBreakUnexpected: 'There was an unexpected pause here.',\n pronunciationBreakMissing: 'A pause was expected here.',\n pronunciationMonotone: 'Your reading stayed on one note. Try varying your pitch.',\n pronunciationIpaNote: 'Sounds are written in the International Phonetic Alphabet.',\n\n previewIncomplete: 'This activity is not finished yet, so it cannot be previewed.',\n previewInvalid: 'This activity has a problem to fix before it can be previewed.',\n\n embeddedMedia: 'Embedded media',\n\n stimulusKind: {\n text: 'Passage',\n audio: 'Recording',\n video: 'Video',\n image: 'Image',\n mixed: 'Material',\n },\n stimulusRange: (first, last) =>\n first === last ? `Question ${first}` : `Questions ${first}–${last}`,\n\n activityFailed: 'Activity failed to render',\n activityFailedNamed: (title) => `\"${title}\" could not be displayed.`,\n activityFailedUnnamed: 'This activity could not be displayed.',\n\n media: {\n play: 'Play',\n pause: 'Pause',\n preparing: 'Preparing…',\n mute: 'Mute',\n unmute: 'Unmute',\n volume: 'Volume',\n speed: 'Playback speed',\n seek: 'Seek',\n timeValue: (elapsed, duration) => `${elapsed} of ${duration}`,\n playsRemaining: (remaining, max) =>\n `${remaining} of ${max} play${max === 1 ? '' : 's'} remaining`,\n noPlaysRemaining: 'No plays remaining',\n lastPlayConfirm: 'This is your last play. Start it now?',\n lastPlayStart: 'Start last play',\n lastPlayCancel: 'Not yet',\n seekBlocked: 'Rewinding is not available for this recording.',\n rateBlocked: 'Playback speed is fixed for this recording.',\n playFailed: 'The recording could not be started. Try again.',\n },\n};\n\n/**\n * Layers an override onto a base dictionary.\n *\n * Two levels deep and no further, because the shape is two levels deep. A\n * generic deep merge would silently accept a nested object where a function\n * belongs and fail at render instead of at the call site.\n */\nexport function mergeStrings(base: LkStrings, override?: LkStringsOverride): LkStrings {\n if (override === undefined) {\n return base;\n }\n const { media, stimulusKind, ...flat } = override;\n return {\n ...base,\n ...flat,\n stimulusKind: { ...base.stimulusKind, ...stimulusKind },\n media: { ...base.media, ...media },\n };\n}\n","'use client';\n\nimport { createContext, useContext, useMemo } from 'react';\nimport type { LkStrings, LkStringsOverride } from './strings.js';\nimport { DEFAULT_STRINGS, mergeStrings } from './strings.js';\n\nexport type { LkStrings, LkStringsOverride } from './strings.js';\nexport { DEFAULT_STRINGS, mergeStrings } from './strings.js';\n\n/** Writing direction of the surrounding UI. `auto` defers to the locale. */\nexport type LkDirection = 'ltr' | 'rtl' | 'auto';\n\ninterface LkIntlValue {\n strings: LkStrings;\n locale: string | undefined;\n direction: 'ltr' | 'rtl';\n}\n\nconst LkIntlContext = createContext<LkIntlValue | null>(null);\n\n/**\n * Locales written right-to-left, by language subtag.\n *\n * A short list rather than a dependency: these are the RTL scripts in living\n * use, and the set has not changed in decades. `Intl.Locale.textInfo` would be\n * better but is not available everywhere this package runs, and an explicit\n * `direction` prop overrides this in either direction anyway.\n */\nconst RTL_LANGUAGES = new Set(['ar', 'arc', 'dv', 'fa', 'he', 'ku', 'ps', 'sd', 'ug', 'ur', 'yi']);\n\n/** Resolves a BCP 47 tag to a writing direction. */\nexport function directionForLocale(locale: string | undefined): 'ltr' | 'rtl' {\n if (locale === undefined) {\n return 'ltr';\n }\n const language = locale.split('-')[0]?.toLowerCase() ?? '';\n return RTL_LANGUAGES.has(language) ? 'rtl' : 'ltr';\n}\n\nexport interface LkIntlProviderProps {\n /**\n * Replaces the SDK's own chrome text. Anything omitted keeps its English\n * default, so a partial translation degrades to mixed rather than to blank.\n *\n * A nested provider REPLACES the one above it rather than layering onto it —\n * as a nested `ThemeProvider` restarts from `defaultTheme`. To layer, use a\n * component's own `strings` prop, or compose with `mergeStrings`.\n */\n strings?: LkStringsOverride;\n /**\n * BCP 47 tag for the SDK's chrome — NOT for the authored content, which\n * carries its own `locale`. Sets `lang` on the wrapper so a screen reader\n * reads \"Question 3 of 10\" in the interface language rather than in the\n * language of the passage beside it (WCAG 3.1.2).\n */\n locale?: string;\n /**\n * Writing direction. Defaults to `auto`, which derives it from `locale`.\n * Set it explicitly when the surrounding application already manages `dir`\n * and you do not want a second element declaring one.\n */\n direction?: LkDirection;\n children: React.ReactNode;\n}\n\n/**\n * Supplies the SDK's chrome text and writing direction to everything beneath.\n *\n * Mirrors `ThemeProvider`: a provider for the whole tree, and a per-component\n * prop for the exceptions. The component prop wins, so a single activity can\n * be relabelled without a second provider.\n *\n * ```tsx\n * <LkIntlProvider locale=\"es\" strings={{\n * submit: 'Enviar',\n * checkAnswers: 'Comprobar respuestas',\n * questionProgress: (i, total) => `Pregunta ${i} de ${total}`,\n * wordCount: (n) => `${n} ${n === 1 ? 'palabra' : 'palabras'}`,\n * media: { noPlaysRemaining: 'No quedan reproducciones' },\n * }}>\n * <ActivitySequence … />\n * </LkIntlProvider>\n * ```\n *\n * The SDK ships **English only**. Bundling Spanish, Portuguese or Arabic would\n * mean shipping translations nobody in this repo can review, and a wrong\n * \"No plays remaining\" on a listening exam is worse than an English one that a\n * teacher can see is untranslated. The mechanism is the deliverable; the words\n * belong to whoever speaks the language.\n */\nexport function LkIntlProvider({\n strings,\n locale,\n direction = 'auto',\n children,\n}: LkIntlProviderProps): React.JSX.Element {\n const value = useMemo<LkIntlValue>(\n () => ({\n strings: mergeStrings(DEFAULT_STRINGS, strings),\n locale,\n direction: direction === 'auto' ? directionForLocale(locale) : direction,\n }),\n [strings, locale, direction],\n );\n\n // Declare `dir` only when something actually told us the direction. With no\n // locale and no explicit `direction`, writing dir=\"ltr\" would flip an RTL\n // host's own subtree back to left-to-right — the context value stays\n // concrete for `useLkDirection`; it is only the DOM declaration we withhold.\n const declared = direction === 'auto' && locale === undefined ? undefined : value.direction;\n\n return (\n <LkIntlContext.Provider value={value}>\n <div className=\"lk-intl\" lang={locale} dir={declared}>\n {children}\n </div>\n </LkIntlContext.Provider>\n );\n}\n\n/**\n * The strings in force, with an optional per-component override layered on top.\n *\n * Usable without a provider: every component calls this, and one rendered\n * standalone still needs its labels. Falling back to the English defaults is\n * what keeps `<MultipleChoice>` working with no setup at all.\n */\nexport function useLkStrings(override?: LkStringsOverride): LkStrings {\n const context = useContext(LkIntlContext);\n const base = context?.strings ?? DEFAULT_STRINGS;\n return useMemo(() => mergeStrings(base, override), [base, override]);\n}\n\n/** The direction in force. `ltr` with no provider. */\nexport function useLkDirection(): 'ltr' | 'rtl' {\n return useContext(LkIntlContext)?.direction ?? 'ltr';\n}\n"]}
@@ -1,11 +1,29 @@
1
1
  'use client';
2
- import { DEFAULT_STRINGS } from './chunk-3Q4I4KT6.js';
2
+ import { DEFAULT_STRINGS } from './chunk-AWQ53DON.js';
3
3
  import { Component } from 'react';
4
4
  import { jsxs, jsx } from 'react/jsx-runtime';
5
5
 
6
- function isProduction() {
7
- const g = globalThis;
8
- return g.process?.env?.NODE_ENV === "production";
6
+ // src/components/_internal.ts
7
+ function isDevelopment() {
8
+ try {
9
+ return process.env.NODE_ENV !== "production";
10
+ } catch {
11
+ return true;
12
+ }
13
+ }
14
+ var ANONYMOUS_ACTOR = {
15
+ objectType: "Agent",
16
+ account: { homePage: "https://github.com/intellectif/learning-kit", name: "anonymous" }
17
+ };
18
+ function objectIdFor(id) {
19
+ return `urn:learning-kit:activity:${encodeURIComponent(id)}`;
20
+ }
21
+ function randomSessionId() {
22
+ const c = globalThis.crypto;
23
+ if (c?.randomUUID) {
24
+ return c.randomUUID();
25
+ }
26
+ return `s-${Math.random().toString(36).slice(2)}${Math.random().toString(36).slice(2)}`;
9
27
  }
10
28
  var ActivityErrorBoundary = class extends Component {
11
29
  state = { error: null };
@@ -17,7 +35,7 @@ var ActivityErrorBoundary = class extends Component {
17
35
  if (!error) {
18
36
  return this.props.children;
19
37
  }
20
- if (!isProduction()) {
38
+ if (isDevelopment()) {
21
39
  return /* @__PURE__ */ jsxs("div", { role: "alert", children: [
22
40
  /* @__PURE__ */ jsx("strong", { children: (this.props.strings ?? DEFAULT_STRINGS).activityFailed }),
23
41
  /* @__PURE__ */ jsx("pre", { children: error.stack ?? error.message })
@@ -27,29 +45,6 @@ var ActivityErrorBoundary = class extends Component {
27
45
  }
28
46
  };
29
47
 
30
- // src/components/_internal.ts
31
- function isDevelopment() {
32
- const g = globalThis;
33
- return g.process?.env?.NODE_ENV !== "production";
34
- }
35
- var ANONYMOUS_ACTOR = {
36
- objectType: "Agent",
37
- account: { homePage: "https://github.com/intellectif/learning-kit", name: "anonymous" }
38
- };
39
- function objectIdFor(id) {
40
- return `urn:learning-kit:activity:${encodeURIComponent(id)}`;
41
- }
42
- function randomSessionId() {
43
- const c = globalThis.crypto;
44
- if (c?.randomUUID) {
45
- return c.randomUUID();
46
- }
47
- return `s-${Math.random().toString(36).slice(2)}${Math.random().toString(36).slice(2)}`;
48
- }
49
- function FeedbackRegion({ id, className, children }) {
50
- return /* @__PURE__ */ jsx("div", { id, className, "aria-live": "polite", "aria-atomic": "true", children });
51
- }
52
-
53
- export { ANONYMOUS_ACTOR, ActivityErrorBoundary, FeedbackRegion, isDevelopment, objectIdFor, randomSessionId };
54
- //# sourceMappingURL=chunk-DQERNAWI.js.map
55
- //# sourceMappingURL=chunk-DQERNAWI.js.map
48
+ export { ANONYMOUS_ACTOR, ActivityErrorBoundary, isDevelopment, objectIdFor, randomSessionId };
49
+ //# sourceMappingURL=chunk-B4BCEQNO.js.map
50
+ //# sourceMappingURL=chunk-B4BCEQNO.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/_internal.ts","../src/components/ActivityErrorBoundary.tsx"],"names":[],"mappings":";;;;;AA8BO,SAAS,aAAA,GAAyB;AACvC,EAAA,IAAI;AACF,IAAA,OAAO,OAAA,CAAQ,IAAI,QAAA,KAAa,YAAA;AAAA,EAClC,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,IAAA;AAAA,EACT;AACF;AAOO,IAAM,eAAA,GAA6B;AAAA,EACxC,UAAA,EAAY,OAAA;AAAA,EACZ,OAAA,EAAS,EAAE,QAAA,EAAU,6CAAA,EAA+C,MAAM,WAAA;AAC5E;AAGO,SAAS,YAAY,EAAA,EAAoB;AAC9C,EAAA,OAAO,CAAA,0BAAA,EAA6B,kBAAA,CAAmB,EAAE,CAAC,CAAA,CAAA;AAC5D;AAQO,SAAS,eAAA,GAA0B;AACxC,EAAA,MAAM,IAAK,UAAA,CAA0D,MAAA;AACrE,EAAA,IAAI,GAAG,UAAA,EAAY;AACjB,IAAA,OAAO,EAAE,UAAA,EAAW;AAAA,EACtB;AACA,EAAA,OAAO,KAAK,IAAA,CAAK,MAAA,GAAS,QAAA,CAAS,EAAE,EAAE,KAAA,CAAM,CAAC,CAAC,CAAA,EAAG,IAAA,CAAK,QAAO,CAAE,QAAA,CAAS,EAAE,CAAA,CAAE,KAAA,CAAM,CAAC,CAAC,CAAA,CAAA;AACvF;ACrCO,IAAM,qBAAA,GAAN,cAAoC,SAAA,CAGzC;AAAA,EACA,KAAA,GAAoC,EAAE,KAAA,EAAO,IAAA,EAAK;AAAA,EAElD,OAAO,yBAAyB,KAAA,EAA0C;AACxE,IAAA,OAAO,EAAE,KAAA,EAAM;AAAA,EACjB;AAAA,EAEA,MAAA,GAAoB;AAClB,IAAA,MAAM,EAAE,KAAA,EAAM,GAAI,IAAA,CAAK,KAAA;AACvB,IAAA,IAAI,CAAC,KAAA,EAAO;AACV,MAAA,OAAO,KAAK,KAAA,CAAM,QAAA;AAAA,IACpB;AAKA,IAAA,IAAI,eAAc,EAAG;AACnB,MAAA,uBACE,IAAA,CAAC,KAAA,EAAA,EAAI,IAAA,EAAK,OAAA,EACR,QAAA,EAAA;AAAA,wBAAA,GAAA,CAAC,QAAA,EAAA,EAAS,QAAA,EAAA,CAAA,IAAA,CAAK,KAAA,CAAM,OAAA,IAAW,iBAAiB,cAAA,EAAe,CAAA;AAAA,wBAChE,GAAA,CAAC,KAAA,EAAA,EAAK,QAAA,EAAA,KAAA,CAAM,KAAA,IAAS,MAAM,OAAA,EAAQ;AAAA,OAAA,EACrC,CAAA;AAAA,IAEJ;AAEA,IAAA,uBACE,GAAA,CAAC,SAAI,IAAA,EAAK,OAAA,EACP,eAAK,KAAA,CAAM,aAAA,GAAA,CACP,KAAK,KAAA,CAAM,OAAA,IAAW,iBAAiB,mBAAA,CAAoB,IAAA,CAAK,MAAM,aAAa,CAAA,GAAA,CACnF,KAAK,KAAA,CAAM,OAAA,IAAW,iBAAiB,qBAAA,EAC9C,CAAA;AAAA,EAEJ;AACF","file":"chunk-B4BCEQNO.js","sourcesContent":["import type { XAPIActor } from '@intellectif/lk-core';\n\n/**\n * The one global this module reads by name. Declared here, not globally: this\n * package compiles without Node's types on purpose, and nothing else in it may\n * come to rely on `process`.\n */\ndeclare const process: { env: { NODE_ENV?: string } };\n\n/**\n * Whether this is anything other than a production build — the one answer\n * every development check and the error boundary's stack trace read.\n *\n * **The literal `process.env.NODE_ENV`, spelled exactly so.** It is the one\n * expression bundlers replace at build time — Vite, webpack, esbuild, Next.js\n * and Rollup's replace plugin all substitute it — and a browser has no\n * `process` to read at run time. The reading this replaces,\n * `globalThis.process?.env?.NODE_ENV`, is not that expression, so no bundler\n * touched it, and in a browser it found no `process` and answered\n * \"development\" in every production app: learners were shown a stack trace\n * where each component's production fallback belonged. Tests running in Node\n * never saw it, because Node has a `process`.\n *\n * This package's own build must leave the literal in its output, for the\n * consumer's bundler to replace. A library build that substituted it would\n * freeze every consumer into whichever mode the library was built in.\n *\n * The `try` covers code that reaches a runtime unbundled, with no `process`\n * at all: that is development, as it always was.\n */\nexport function isDevelopment(): boolean {\n try {\n return process.env.NODE_ENV !== 'production';\n } catch {\n return true;\n }\n}\n\n/**\n * Activity components cannot know the learner's identity (Req 3.1 fixes the\n * prop set). They emit a structurally-valid statement with this anonymous\n * actor; real identity is applied by the useXAPI/LRS layer (XAPIConfig.actor).\n */\nexport const ANONYMOUS_ACTOR: XAPIActor = {\n objectType: 'Agent',\n account: { homePage: 'https://github.com/intellectif/learning-kit', name: 'anonymous' },\n};\n\n/** `data.id` is not an IRI; xAPI object ids must be. Wrap it as a URN. */\nexport function objectIdFor(id: string): string {\n return `urn:learning-kit:activity:${encodeURIComponent(id)}`;\n}\n\n/**\n * Random per-mount session id for the shuffle seed. `crypto.randomUUID` is\n * unavailable outside secure contexts (plain-http LAN/staging hosts), so a\n * Math.random fallback keeps the component from crashing there — the seed\n * only randomises presentation order, so cryptographic strength is not needed.\n */\nexport function randomSessionId(): string {\n const c = (globalThis as { crypto?: { randomUUID?: () => string } }).crypto;\n if (c?.randomUUID) {\n return c.randomUUID();\n }\n return `s-${Math.random().toString(36).slice(2)}${Math.random().toString(36).slice(2)}`;\n}\n","'use client';\n\nimport { Component, type ReactNode } from 'react';\nimport type { LkStrings } from '../i18n/strings.js';\nimport { DEFAULT_STRINGS } from '../i18n/strings.js';\nimport { isDevelopment } from './_internal.js';\n\nexport interface ActivityErrorBoundaryProps {\n /** Shown in the production fallback when a render error occurs. */\n activityTitle?: string;\n /**\n * Chrome text. A class component cannot call `useLkStrings`, so the public\n * wrapper reads the hook and hands the resolved dictionary down.\n */\n strings?: LkStrings;\n children: ReactNode;\n}\n\ninterface ActivityErrorBoundaryState {\n error: Error | null;\n}\n\n/**\n * Catches render errors in an activity subtree. In development it renders the\n * full error stack to aid debugging; in production it renders an accessible,\n * generic fallback (optionally naming the activity) so a single broken\n * activity never crashes the host application.\n */\nexport class ActivityErrorBoundary extends Component<\n ActivityErrorBoundaryProps,\n ActivityErrorBoundaryState\n> {\n state: ActivityErrorBoundaryState = { error: null };\n\n static getDerivedStateFromError(error: Error): ActivityErrorBoundaryState {\n return { error };\n }\n\n render(): ReactNode {\n const { error } = this.state;\n if (!error) {\n return this.props.children;\n }\n\n // The same answer every development check reads, from the one function\n // that gives it: a boundary with a reading of its own showed a learner a\n // stack trace in every browser production build.\n if (isDevelopment()) {\n return (\n <div role=\"alert\">\n <strong>{(this.props.strings ?? DEFAULT_STRINGS).activityFailed}</strong>\n <pre>{error.stack ?? error.message}</pre>\n </div>\n );\n }\n\n return (\n <div role=\"alert\">\n {this.props.activityTitle\n ? (this.props.strings ?? DEFAULT_STRINGS).activityFailedNamed(this.props.activityTitle)\n : (this.props.strings ?? DEFAULT_STRINGS).activityFailedUnnamed}\n </div>\n );\n }\n}\n"]}
@@ -0,0 +1,13 @@
1
+ 'use client';
2
+ 'use strict';
3
+
4
+ var jsxRuntime = require('react/jsx-runtime');
5
+
6
+ // src/components/shared/FeedbackRegion.tsx
7
+ function FeedbackRegion({ id, className, children }) {
8
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { id, className, "aria-live": "polite", "aria-atomic": "true", children });
9
+ }
10
+
11
+ exports.FeedbackRegion = FeedbackRegion;
12
+ //# sourceMappingURL=chunk-BRPEB43C.cjs.map
13
+ //# sourceMappingURL=chunk-BRPEB43C.cjs.map