@intellectif/lk-react 19.0.0 → 21.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (176) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/README.md +31 -2
  3. package/dist/{WrittenResponse-DT_Yqbq1.d.cts → WrittenResponse-BVPmrmQL.d.cts} +8 -2
  4. package/dist/{WrittenResponse-CeHY5u3W.d.ts → WrittenResponse-IAJvvL-g.d.ts} +8 -2
  5. package/dist/ai/LkAiProvider.cjs +3 -3
  6. package/dist/ai/LkAiProvider.d.cts +5 -3
  7. package/dist/ai/LkAiProvider.d.ts +5 -3
  8. package/dist/ai/LkAiProvider.js +1 -1
  9. package/dist/ai/useAiHelp.cjs +5 -5
  10. package/dist/ai/useAiHelp.d.cts +8 -2
  11. package/dist/ai/useAiHelp.d.ts +8 -2
  12. package/dist/ai/useAiHelp.js +3 -3
  13. package/dist/{chunk-7NNMDZUU.js → chunk-5MO7ACSB.js} +10 -10
  14. package/dist/{chunk-7NNMDZUU.js.map → chunk-5MO7ACSB.js.map} +1 -1
  15. package/dist/{chunk-7K4B2EMU.js → chunk-5Q2KFIZQ.js} +4 -4
  16. package/dist/{chunk-7K4B2EMU.js.map → chunk-5Q2KFIZQ.js.map} +1 -1
  17. package/dist/{chunk-APZ6JFN6.cjs → chunk-6SNNMVBV.cjs} +5 -5
  18. package/dist/{chunk-APZ6JFN6.cjs.map → chunk-6SNNMVBV.cjs.map} +1 -1
  19. package/dist/{chunk-L7WY5YFQ.cjs → chunk-7JBXDQTD.cjs} +5 -5
  20. package/dist/{chunk-L7WY5YFQ.cjs.map → chunk-7JBXDQTD.cjs.map} +1 -1
  21. package/dist/{chunk-UXV3V5NH.js → chunk-AY3WN4FB.js} +27 -15
  22. package/dist/chunk-AY3WN4FB.js.map +1 -0
  23. package/dist/{chunk-XAIJNGTU.cjs → chunk-CB2A56Q7.cjs} +137 -51
  24. package/dist/chunk-CB2A56Q7.cjs.map +1 -0
  25. package/dist/chunk-CQ37UD4T.cjs +251 -0
  26. package/dist/chunk-CQ37UD4T.cjs.map +1 -0
  27. package/dist/{chunk-YRNEOBXA.js → chunk-EYLBSS4Y.js} +11 -2
  28. package/dist/chunk-EYLBSS4Y.js.map +1 -0
  29. package/dist/{chunk-AHGB5HH6.cjs → chunk-GHKMX5UT.cjs} +11 -2
  30. package/dist/chunk-GHKMX5UT.cjs.map +1 -0
  31. package/dist/chunk-GND3OBVE.js +241 -0
  32. package/dist/chunk-GND3OBVE.js.map +1 -0
  33. package/dist/{chunk-WKA6FUR4.cjs → chunk-GPGCBDBN.cjs} +66 -26
  34. package/dist/chunk-GPGCBDBN.cjs.map +1 -0
  35. package/dist/{chunk-55W2472T.cjs → chunk-GW3YIPDE.cjs} +18 -18
  36. package/dist/{chunk-55W2472T.cjs.map → chunk-GW3YIPDE.cjs.map} +1 -1
  37. package/dist/{chunk-JVWLXVYG.cjs → chunk-J2QIHZ3H.cjs} +2 -2
  38. package/dist/chunk-J2QIHZ3H.cjs.map +1 -0
  39. package/dist/{chunk-SEQTGOOU.js → chunk-KRM6DX47.js} +151 -50
  40. package/dist/chunk-KRM6DX47.js.map +1 -0
  41. package/dist/{chunk-2X2NZF46.cjs → chunk-LHFC2BCM.cjs} +104 -27
  42. package/dist/chunk-LHFC2BCM.cjs.map +1 -0
  43. package/dist/{chunk-G7AGM2Y7.js → chunk-M3K6BF5M.js} +4 -4
  44. package/dist/{chunk-G7AGM2Y7.js.map → chunk-M3K6BF5M.js.map} +1 -1
  45. package/dist/chunk-M4UJBLVR.cjs +55 -0
  46. package/dist/chunk-M4UJBLVR.cjs.map +1 -0
  47. package/dist/{chunk-YNDKAYW3.js → chunk-MCTNLUKO.js} +2 -2
  48. package/dist/{chunk-YNDKAYW3.js.map → chunk-MCTNLUKO.js.map} +1 -1
  49. package/dist/{chunk-ZDYNAWDZ.cjs → chunk-MPHX3Y42.cjs} +2 -2
  50. package/dist/{chunk-ZDYNAWDZ.cjs.map → chunk-MPHX3Y42.cjs.map} +1 -1
  51. package/dist/{chunk-YIYMBCPV.js → chunk-NDIKHMLJ.js} +19 -10
  52. package/dist/chunk-NDIKHMLJ.js.map +1 -0
  53. package/dist/{chunk-LX2X4KC7.cjs → chunk-NILHU555.cjs} +7 -7
  54. package/dist/{chunk-LX2X4KC7.cjs.map → chunk-NILHU555.cjs.map} +1 -1
  55. package/dist/{chunk-ZME5FMED.cjs → chunk-OKEN6NJL.cjs} +157 -54
  56. package/dist/chunk-OKEN6NJL.cjs.map +1 -0
  57. package/dist/chunk-PJMXHDY5.js +51 -0
  58. package/dist/chunk-PJMXHDY5.js.map +1 -0
  59. package/dist/{chunk-OWV7VRMQ.js → chunk-U2BX2RWY.js} +93 -16
  60. package/dist/chunk-U2BX2RWY.js.map +1 -0
  61. package/dist/{chunk-TEAVPUMX.js → chunk-U4KTOH7C.js} +2 -2
  62. package/dist/chunk-U4KTOH7C.js.map +1 -0
  63. package/dist/{chunk-FEOFX4QW.js → chunk-U6SEII5V.js} +3 -3
  64. package/dist/{chunk-FEOFX4QW.js.map → chunk-U6SEII5V.js.map} +1 -1
  65. package/dist/{chunk-XUQ4KRTT.cjs → chunk-W2P5RILE.cjs} +156 -55
  66. package/dist/chunk-W2P5RILE.cjs.map +1 -0
  67. package/dist/{chunk-XHF376ES.js → chunk-W6TWQY6S.js} +152 -49
  68. package/dist/chunk-W6TWQY6S.js.map +1 -0
  69. package/dist/{chunk-NGFTSRKO.cjs → chunk-WGHMWBEJ.cjs} +126 -48
  70. package/dist/chunk-WGHMWBEJ.cjs.map +1 -0
  71. package/dist/{chunk-5QCZZBOX.js → chunk-WNDNQ6NR.js} +120 -42
  72. package/dist/chunk-WNDNQ6NR.js.map +1 -0
  73. package/dist/{chunk-NZM3M5IR.cjs → chunk-WXESKL75.cjs} +47 -35
  74. package/dist/chunk-WXESKL75.cjs.map +1 -0
  75. package/dist/{chunk-BY4UQH2Y.js → chunk-X4YWXZ4J.js} +3 -3
  76. package/dist/{chunk-BY4UQH2Y.js.map → chunk-X4YWXZ4J.js.map} +1 -1
  77. package/dist/{chunk-KOFA54C7.js → chunk-XB2AHEU5.js} +55 -15
  78. package/dist/chunk-XB2AHEU5.js.map +1 -0
  79. package/dist/{chunk-NLTU5JFD.js → chunk-XM5Q2ZCH.js} +132 -46
  80. package/dist/chunk-XM5Q2ZCH.js.map +1 -0
  81. package/dist/{chunk-CQNKZ435.cjs → chunk-XZCMUDFP.cjs} +14 -12
  82. package/dist/chunk-XZCMUDFP.cjs.map +1 -0
  83. package/dist/{chunk-SXU26SFO.cjs → chunk-Y4IDF5UO.cjs} +20 -11
  84. package/dist/chunk-Y4IDF5UO.cjs.map +1 -0
  85. package/dist/{chunk-3FBBP5QR.cjs → chunk-YWJ5XJDU.cjs} +6 -6
  86. package/dist/{chunk-3FBBP5QR.cjs.map → chunk-YWJ5XJDU.cjs.map} +1 -1
  87. package/dist/{chunk-4NERCBOW.js → chunk-ZDDBWUUA.js} +10 -8
  88. package/dist/chunk-ZDDBWUUA.js.map +1 -0
  89. package/dist/components/ActivityPreview.cjs +17 -17
  90. package/dist/components/ActivityPreview.d.cts +2 -2
  91. package/dist/components/ActivityPreview.d.ts +2 -2
  92. package/dist/components/ActivityPreview.js +16 -16
  93. package/dist/components/ActivitySequence.cjs +18 -18
  94. package/dist/components/ActivitySequence.d.cts +34 -4
  95. package/dist/components/ActivitySequence.d.ts +34 -4
  96. package/dist/components/ActivitySequence.js +17 -17
  97. package/dist/components/Dictation.cjs +10 -10
  98. package/dist/components/Dictation.d.cts +1 -1
  99. package/dist/components/Dictation.d.ts +1 -1
  100. package/dist/components/Dictation.js +9 -9
  101. package/dist/components/FillInTheBlanks.cjs +10 -10
  102. package/dist/components/FillInTheBlanks.d.cts +1 -1
  103. package/dist/components/FillInTheBlanks.d.ts +1 -1
  104. package/dist/components/FillInTheBlanks.js +9 -9
  105. package/dist/components/GapSelect.cjs +10 -10
  106. package/dist/components/GapSelect.d.cts +1 -1
  107. package/dist/components/GapSelect.d.ts +1 -1
  108. package/dist/components/GapSelect.js +9 -9
  109. package/dist/components/InteractiveVideo.cjs +17 -17
  110. package/dist/components/InteractiveVideo.d.cts +32 -3
  111. package/dist/components/InteractiveVideo.d.ts +32 -3
  112. package/dist/components/InteractiveVideo.js +15 -15
  113. package/dist/components/MultipleChoice.cjs +10 -10
  114. package/dist/components/MultipleChoice.d.cts +1 -1
  115. package/dist/components/MultipleChoice.d.ts +1 -1
  116. package/dist/components/MultipleChoice.js +9 -9
  117. package/dist/components/PronunciationFeedback.cjs +4 -4
  118. package/dist/components/PronunciationFeedback.d.cts +1 -1
  119. package/dist/components/PronunciationFeedback.d.ts +1 -1
  120. package/dist/components/PronunciationFeedback.js +3 -3
  121. package/dist/components/ReadAloud.cjs +8 -8
  122. package/dist/components/ReadAloud.d.cts +2 -2
  123. package/dist/components/ReadAloud.d.ts +2 -2
  124. package/dist/components/ReadAloud.js +7 -7
  125. package/dist/components/StimulusPanel.cjs +4 -4
  126. package/dist/components/StimulusPanel.d.cts +1 -1
  127. package/dist/components/StimulusPanel.d.ts +1 -1
  128. package/dist/components/StimulusPanel.js +3 -3
  129. package/dist/components/WrittenResponse.cjs +7 -6
  130. package/dist/components/WrittenResponse.d.cts +3 -3
  131. package/dist/components/WrittenResponse.d.ts +3 -3
  132. package/dist/components/WrittenResponse.js +6 -5
  133. package/dist/i18n/LkIntlProvider.cjs +7 -7
  134. package/dist/i18n/LkIntlProvider.d.cts +2 -2
  135. package/dist/i18n/LkIntlProvider.d.ts +2 -2
  136. package/dist/i18n/LkIntlProvider.js +1 -1
  137. package/dist/index.cjs +41 -41
  138. package/dist/index.cjs.map +1 -1
  139. package/dist/index.d.cts +2 -2
  140. package/dist/index.d.ts +2 -2
  141. package/dist/index.js +19 -19
  142. package/dist/index.js.map +1 -1
  143. package/dist/theme/skin.css +55 -0
  144. package/dist/{types-BY-4S5Rd.d.ts → types-7lGlo0zE.d.ts} +56 -1
  145. package/dist/{types-DtPYG1hh.d.cts → types-Botf4Wqj.d.cts} +56 -1
  146. package/package.json +3 -3
  147. package/dist/chunk-2X2NZF46.cjs.map +0 -1
  148. package/dist/chunk-4NERCBOW.js.map +0 -1
  149. package/dist/chunk-5QCZZBOX.js.map +0 -1
  150. package/dist/chunk-AHGB5HH6.cjs.map +0 -1
  151. package/dist/chunk-BRPEB43C.cjs +0 -13
  152. package/dist/chunk-BRPEB43C.cjs.map +0 -1
  153. package/dist/chunk-CQNKZ435.cjs.map +0 -1
  154. package/dist/chunk-JVWLXVYG.cjs.map +0 -1
  155. package/dist/chunk-KGDGYRBL.cjs +0 -117
  156. package/dist/chunk-KGDGYRBL.cjs.map +0 -1
  157. package/dist/chunk-KOFA54C7.js.map +0 -1
  158. package/dist/chunk-NGFTSRKO.cjs.map +0 -1
  159. package/dist/chunk-NLTU5JFD.js.map +0 -1
  160. package/dist/chunk-NZM3M5IR.cjs.map +0 -1
  161. package/dist/chunk-OWV7VRMQ.js.map +0 -1
  162. package/dist/chunk-QRT2W5N5.js +0 -114
  163. package/dist/chunk-QRT2W5N5.js.map +0 -1
  164. package/dist/chunk-RCMYEWBE.js +0 -11
  165. package/dist/chunk-RCMYEWBE.js.map +0 -1
  166. package/dist/chunk-SEQTGOOU.js.map +0 -1
  167. package/dist/chunk-SXU26SFO.cjs.map +0 -1
  168. package/dist/chunk-TEAVPUMX.js.map +0 -1
  169. package/dist/chunk-UXV3V5NH.js.map +0 -1
  170. package/dist/chunk-WKA6FUR4.cjs.map +0 -1
  171. package/dist/chunk-XAIJNGTU.cjs.map +0 -1
  172. package/dist/chunk-XHF376ES.js.map +0 -1
  173. package/dist/chunk-XUQ4KRTT.cjs.map +0 -1
  174. package/dist/chunk-YIYMBCPV.js.map +0 -1
  175. package/dist/chunk-YRNEOBXA.js.map +0 -1
  176. package/dist/chunk-ZME5FMED.cjs.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/i18n/strings.ts","../src/i18n/LkIntlProvider.tsx"],"names":["createContext","useMemo","jsx","useContext"],"mappings":"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","file":"chunk-GHKMX5UT.cjs","sourcesContent":["import type { ReadAloudDimension, ReadAloudWordState, Stimulus } from '@intellectif/lk-core';\nimport type { CaptionSize } from '../components/InteractiveVideo/prefs.js';\nimport type { VideoShortcutAction } from '../components/InteractiveVideo/shortcuts.js';\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 // ── Tries and what they cost (a scoring policy, in practice) ───────────\n /** The button that reopens a graded answer for another try. */\n tryAgain: string;\n /** Ends the tries on a question and shows the right answer. */\n showAnswer: string;\n /** Ends the tries on a question where the paper shows no right answers. */\n keepAnswer: string;\n /**\n * After a graded try, how many more there are, and what each costs: a whole\n * percentage of the question's marks, `0` when a try costs nothing.\n */\n triesLeft: (tries: number, costPercent: number) => string;\n /**\n * When an earlier try is the one that counts: which rule chose it, and what\n * the try just made scored, as a whole percentage.\n */\n countedTry: (counts: 'first' | 'best', percent: number) => string;\n /**\n * When hints or tries cost this answer something: what it scored before\n * that, as a whole percentage. The score sentence before it is what it counts.\n */\n scoreBeforeCosts: (percent: number) => string;\n /** Beside the hints on a paper where they cost marks, before one is asked for. */\n hintCost: (percent: number) => 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 /**\n * A wrong word, where the right answer may not show — the paper withholds\n * solutions, or the learner is offered another try: it is wrong, and not\n * what it should be.\n */\n dictationWordWrongUnnamed: (word: string) => string;\n /** A missing word, where the right answer may not show: missing, and not named. */\n dictationWordMissingUnnamed: 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 // ── Interactive video ──────────────────────────────────────────────────\n // Play, pause, mute, volume, speed, the progress bar's name and the time\n // readout are the `media` strings below: the same controls, the same words.\n /** Jump buttons, e.g. \"Back 10 seconds\". */\n videoBack: (seconds: number) => string;\n videoForward: (seconds: number) => string;\n /** The centre button once the video has ended. */\n videoReplay: string;\n /** The time readout flips between elapsed and remaining; these name what a press shows next. */\n videoShowRemaining: string;\n videoShowElapsed: string;\n /** The captions button, named by what a press does. */\n videoCaptionsShow: string;\n videoCaptionsHide: string;\n /** In the captions menu when a caption file could not be read. */\n videoCaptionsFailed: string;\n /** The same, for the second language: the first line keeps working. */\n videoSecondaryCaptionsFailed: string;\n /** A speed in the speed menu, e.g. \"1.25×\". */\n videoSpeedValue: (rate: number) => string;\n videoSettings: string;\n videoCaptionLanguage: string;\n /** The captions page's item, and page, for a second caption line under the first. */\n videoSecondCaptionLanguage: string;\n /**\n * The captions row of the settings menu when two languages show, from the\n * tracks' own labels: \"English + Español\". A translation may order them as its\n * language reads, but the first argument is always the upper line.\n */\n videoCaptionPair: (primary: string, secondary: string) => string;\n /** Announced when Shift + C finds no other language to show. */\n videoNoSecondLanguage: string;\n videoCaptionSize: string;\n videoCaptionSizeValue: (size: CaptionSize) => string;\n videoCaptionBackground: string;\n videoKeyboardShortcuts: string;\n videoShortcutList: string;\n /** Closes the shortcut list. */\n videoClose: string;\n /** A setting's state in the settings menu. */\n videoOn: string;\n videoOff: string;\n /** What each row of the shortcut list does. */\n videoShortcut: (action: VideoShortcutAction) => string;\n /** The button that opens the panel below the video, and its two tabs. */\n videoPanel: string;\n videoContents: string;\n videoTranscript: string;\n videoTranscriptSearch: string;\n videoTranscriptNoMatch: string;\n videoPictureInPicture: string;\n videoFullscreen: string;\n videoExitFullscreen: string;\n /**\n * Why the video will not play, by `MediaError.code` (1 aborted, 2 network,\n * 3 decode, 4 source), with 0 for anything else.\n */\n videoError: (code: number) => string;\n videoTryAgain: string;\n /** A quiz with no title of its own. */\n videoQuiz: string;\n /** Where the learner is inside a quiz, e.g. \"Question 2 of 5\". */\n videoQuestionProgress: (index: number, total: number) => string;\n /** A step of the quiz's step indicator, for assistive technology. */\n videoQuestionStep: (index: number, answered: boolean) => string;\n videoNextQuestion: string;\n videoContinue: string;\n videoSkipQuiz: string;\n videoRewatch: string;\n /** Exam mode, once a question is submitted: saved, not graded. */\n videoAnswerSaved: string;\n /** The end card, while an answer is still being stored or graded: why Finish waits. */\n videoAnswerPending: string;\n videoRequired: string;\n /** A quiz's progress in the contents panel and on the end screen. */\n videoQuizProgress: (answered: number, total: number) => string;\n /** Announced when a quiz opens. */\n videoQuizOpened: (time: string, questions: number) => string;\n /** Announced when a seek stopped at a required quiz. */\n videoHeldAtQuiz: (time: string) => string;\n /** Announced when a seek stopped at the furthest point reached. */\n videoHeldAhead: string;\n videoEnded: string;\n /** Practice end screen: questions answered, and the score. */\n videoEndScore: (answered: number, total: number, percent: number) => string;\n /** Exam end screen: questions answered, no score. */\n videoEndAnswered: (answered: number, total: number) => string;\n videoWatchAgain: string;\n videoFinish: 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 // ── AI help ────────────────────────────────────────────────────────────\n /** The button that asks for an explanation of a graded answer. */\n aiExplain: string;\n /** The same button while the explanation is being written. */\n aiExplaining: string;\n /** The heading over an explanation, and its accessible name. */\n aiExplanation: string;\n /** When no explanation can be shown: the port failed, or its answer was refused. */\n aiExplanationUnavailable: string;\n /** The button that asks for a hint. */\n aiHint: string;\n /** The same button while the hint is being written. */\n aiHintLoading: string;\n /** The label before each hint. */\n aiHintNumber: (hintNumber: number) => string;\n /** The accessible name of the list of hints. */\n aiHints: string;\n /** When no hint can be shown: the port failed, or its hint gave the answer away. */\n aiHintUnavailable: string;\n /** In place of the hint button once every hint has been used. */\n aiNoMoreHints: string;\n /** Beneath every AI text: who wrote it. */\n aiNotice: 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 tryAgain: 'Try again',\n showAnswer: 'Show answer',\n keepAnswer: 'Keep this answer',\n triesLeft: (tries, costPercent) =>\n `${tries} ${tries === 1 ? 'try' : 'tries'} left.${\n costPercent > 0 ? ` Each costs ${costPercent}% of the marks.` : ''\n }`,\n countedTry: (counts, percent) =>\n `Your ${counts === 'first' ? 'first' : 'best'} try counts. This one scored ${percent}%.`,\n scoreBeforeCosts: (percent) => `Before hints and tries, this answer scored ${percent}%.`,\n hintCost: (percent) => `Each hint costs ${percent}% of this question's marks.`,\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 dictationWordWrongUnnamed: (word) => `“${word}” is wrong`,\n dictationWordMissingUnnamed: 'A word 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 videoBack: (seconds) => `Back ${seconds} seconds`,\n videoForward: (seconds) => `Forward ${seconds} seconds`,\n videoReplay: 'Replay',\n videoShowRemaining: 'Show time remaining',\n videoShowElapsed: 'Show time elapsed',\n videoCaptionsShow: 'Show captions',\n videoCaptionsHide: 'Hide captions',\n videoCaptionsFailed: 'Captions could not be loaded.',\n videoSecondaryCaptionsFailed: 'The second language could not be loaded.',\n videoSpeedValue: (rate) => (rate === 1 ? 'Normal' : `${rate}×`),\n videoSettings: 'Settings',\n videoCaptionLanguage: 'Captions',\n videoSecondCaptionLanguage: 'Second language',\n videoCaptionPair: (primary, secondary) => `${primary} + ${secondary}`,\n videoNoSecondLanguage: 'No second language for this video.',\n videoCaptionSize: 'Caption size',\n videoCaptionSizeValue: (size) =>\n size === 'small' ? 'Small' : size === 'large' ? 'Large' : 'Medium',\n videoCaptionBackground: 'Caption background',\n videoKeyboardShortcuts: 'Keyboard shortcuts',\n videoShortcutList: 'Shortcut list',\n videoClose: 'Close',\n videoOn: 'On',\n videoOff: 'Off',\n videoShortcut: (action) => {\n switch (action) {\n case 'play-pause':\n return 'Play or pause';\n case 'jump-10':\n return 'Back or forward 10 seconds';\n case 'jump-5':\n return 'Back or forward 5 seconds';\n case 'jump-1':\n return 'Back or forward 1 second';\n case 'frame':\n return 'Previous or next frame, while paused';\n case 'speed':\n return 'Slower or faster';\n case 'percent':\n return 'Jump to 0% – 90%';\n case 'start-end':\n return 'Start or end';\n case 'chapter':\n return 'Previous or next chapter or quiz';\n case 'mute':\n return 'Mute';\n case 'captions':\n return 'Captions';\n case 'secondary-captions':\n return 'Second caption language on/off';\n case 'transcript':\n return 'Transcript';\n case 'picture-in-picture':\n return 'Picture in picture';\n case 'fullscreen':\n return 'Fullscreen';\n default:\n return 'This list';\n }\n },\n videoPanel: 'Contents and transcript',\n videoContents: 'Contents',\n videoTranscript: 'Transcript',\n videoTranscriptSearch: 'Search the transcript',\n videoTranscriptNoMatch: 'No lines match.',\n videoPictureInPicture: 'Picture in picture',\n videoFullscreen: 'Fullscreen',\n videoExitFullscreen: 'Exit fullscreen',\n videoError: (code) =>\n code === 2\n ? 'The connection dropped while the video was loading.'\n : code === 3\n ? 'This video file could not be played.'\n : code === 4\n ? 'The video could not be loaded.'\n : 'The video stopped before it could play.',\n videoTryAgain: 'Try again',\n videoQuiz: 'Quiz',\n videoQuestionProgress: (index, total) => `Question ${index} of ${total}`,\n videoQuestionStep: (index, answered) => `Question ${index}${answered ? ', answered' : ''}`,\n videoNextQuestion: 'Next question',\n videoContinue: 'Continue video',\n videoSkipQuiz: 'Skip quiz',\n videoRewatch: 'Rewatch',\n videoAnswerSaved: 'Answer saved',\n videoAnswerPending: 'Saving your answer…',\n videoRequired: 'Required',\n videoQuizProgress: (answered, total) =>\n answered === total\n ? 'Answered'\n : answered === 0\n ? 'Not started'\n : `${answered} of ${total} answered`,\n videoQuizOpened: (time, questions) =>\n `Video paused. Quiz at ${time}, ${questions} question${questions === 1 ? '' : 's'}.`,\n videoHeldAtQuiz: (time) => `Finish the quiz at ${time} to continue.`,\n videoHeldAhead: 'You can rewind, but not skip ahead.',\n videoEnded: 'You reached the end',\n videoEndScore: (answered, total, percent) => `You answered ${answered} of ${total} · ${percent}%`,\n videoEndAnswered: (answered, total) => `You answered ${answered} of ${total} questions`,\n videoWatchAgain: 'Watch again',\n videoFinish: 'Finish',\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 aiExplain: 'Explain my answer',\n aiExplaining: 'Explaining…',\n aiExplanation: 'Explanation',\n aiExplanationUnavailable: 'No explanation is available right now.',\n aiHint: 'Get a hint',\n aiHintLoading: 'Finding a hint…',\n aiHintNumber: (hintNumber) => `Hint ${hintNumber}`,\n aiHints: 'Hints',\n aiHintUnavailable: 'No hint is available right now.',\n aiNoMoreHints: 'No more hints for this question.',\n aiNotice: 'Written by AI. It can make mistakes.',\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"]}
@@ -0,0 +1,241 @@
1
+ 'use client';
2
+ import { useAiExplanation, useAiHints } from './chunk-NDIKHMLJ.js';
3
+ import { SequenceSlotContext } from './chunk-U4KTOH7C.js';
4
+ import { useRef, useEffect, useContext, useMemo, useState, useCallback } from 'react';
5
+ import { jsxs, jsx } from 'react/jsx-runtime';
6
+ import { resolveItemScoringPolicy, scoreTries } from '@intellectif/lk-core';
7
+
8
+ function situation(input) {
9
+ const { data, response, submitted, renderMode, ai, locale, onInteraction, delivery } = input;
10
+ return {
11
+ data,
12
+ response,
13
+ submitted,
14
+ renderMode,
15
+ delivery,
16
+ ...ai !== void 0 ? { ai } : {},
17
+ ...locale !== void 0 ? { locale } : {},
18
+ ...onInteraction !== void 0 ? { onInteraction } : {}
19
+ };
20
+ }
21
+ function AiExplanation({
22
+ outcome,
23
+ ...rest
24
+ }) {
25
+ const s = rest.strings;
26
+ const help = useAiExplanation({
27
+ ...situation(rest),
28
+ ...outcome !== void 0 ? { outcome } : {}
29
+ });
30
+ const buttonRef = useRef(null);
31
+ const panelRef = useRef(null);
32
+ const refocus = useRef(false);
33
+ useEffect(() => {
34
+ if (help.status === "shown" && refocus.current) {
35
+ refocus.current = false;
36
+ panelRef.current?.focus();
37
+ }
38
+ if (help.status === "unavailable") {
39
+ refocus.current = false;
40
+ }
41
+ }, [help.status]);
42
+ if (!help.offered) {
43
+ return null;
44
+ }
45
+ const ask = () => {
46
+ refocus.current = typeof document !== "undefined" && document.activeElement === buttonRef.current;
47
+ help.ask();
48
+ };
49
+ const loading = help.status === "loading";
50
+ return /* @__PURE__ */ jsxs("div", { className: "lk-ai", "data-state": help.status, children: [
51
+ help.explanation === null ? /* @__PURE__ */ jsx(
52
+ "button",
53
+ {
54
+ ref: buttonRef,
55
+ type: "button",
56
+ className: "lk-ai-button",
57
+ "aria-busy": loading || void 0,
58
+ "aria-disabled": loading || void 0,
59
+ onClick: ask,
60
+ children: loading ? s.aiExplaining : s.aiExplain
61
+ }
62
+ ) : null,
63
+ /* @__PURE__ */ jsx("div", { "aria-live": "polite", children: help.explanation !== null ? /* @__PURE__ */ jsxs(
64
+ "section",
65
+ {
66
+ ref: panelRef,
67
+ className: "lk-ai-panel",
68
+ tabIndex: -1,
69
+ "aria-label": s.aiExplanation,
70
+ children: [
71
+ /* @__PURE__ */ jsx("p", { className: "lk-ai-heading", children: s.aiExplanation }),
72
+ /* @__PURE__ */ jsx("p", { className: "lk-ai-text", children: help.explanation.text }),
73
+ /* @__PURE__ */ jsx("p", { className: "lk-ai-notice", children: s.aiNotice })
74
+ ]
75
+ }
76
+ ) : help.status === "unavailable" ? /* @__PURE__ */ jsx("p", { className: "lk-ai-unavailable", children: s.aiExplanationUnavailable }) : null })
77
+ ] });
78
+ }
79
+ function useComponentAiHints({
80
+ disabled,
81
+ ...rest
82
+ }) {
83
+ return useAiHints({ ...situation(rest), disabled });
84
+ }
85
+ function AiHints({
86
+ help,
87
+ ai,
88
+ strings: s
89
+ }) {
90
+ const used = help.used;
91
+ if (ai?.hint === void 0 || !help.offered && used === 0) {
92
+ return null;
93
+ }
94
+ const loading = help.status === "loading";
95
+ return /* @__PURE__ */ jsxs("div", { className: "lk-ai lk-ai-hint-area", "data-state": help.status, children: [
96
+ /* @__PURE__ */ jsxs("div", { "aria-live": "polite", children: [
97
+ used > 0 ? /* @__PURE__ */ jsx("ol", { className: "lk-ai-hints", "aria-label": s.aiHints, children: help.hints.map((hint, index) => (
98
+ // Hints are only ever appended, so a position is a stable key.
99
+ // biome-ignore lint/suspicious/noArrayIndexKey: an append-only list
100
+ /* @__PURE__ */ jsxs("li", { className: "lk-ai-hint", children: [
101
+ /* @__PURE__ */ jsx("span", { className: "lk-ai-hint-label", children: s.aiHintNumber(index + 1) }),
102
+ " ",
103
+ /* @__PURE__ */ jsx("span", { className: "lk-ai-text", children: hint.text })
104
+ ] }, index)
105
+ )) }) : null,
106
+ help.status === "unavailable" ? /* @__PURE__ */ jsx("p", { className: "lk-ai-unavailable", children: s.aiHintUnavailable }) : null
107
+ ] }),
108
+ help.offered ? used < help.limit ? /* @__PURE__ */ jsx(
109
+ "button",
110
+ {
111
+ type: "button",
112
+ className: "lk-ai-button",
113
+ "aria-busy": loading || void 0,
114
+ "aria-disabled": loading || void 0,
115
+ onClick: help.ask,
116
+ children: loading ? s.aiHintLoading : s.aiHint
117
+ }
118
+ ) : /* @__PURE__ */ jsx("p", { className: "lk-ai-note", children: s.aiNoMoreHints }) : null,
119
+ used > 0 ? /* @__PURE__ */ jsx("p", { className: "lk-ai-notice", children: s.aiNotice }) : null
120
+ ] });
121
+ }
122
+ function useItemScoringPolicy(given) {
123
+ const around = useContext(SequenceSlotContext)?.scoring;
124
+ const key = policyKey(given);
125
+ const own = useMemo(
126
+ () => around === void 0 ? resolveItemScoringPolicy(given) : null,
127
+ [around, key]
128
+ );
129
+ return around ?? own;
130
+ }
131
+ function policyKey(given) {
132
+ try {
133
+ return JSON.stringify(given) ?? "";
134
+ } catch {
135
+ return String(given);
136
+ }
137
+ }
138
+ function hintsOf(response) {
139
+ const count = response?.hintsRevealed;
140
+ return typeof count === "number" && Number.isSafeInteger(count) && count > 0 ? count : 0;
141
+ }
142
+ var fullMarks = (one) => one.score / one.maxScore >= 1 - 1e-9;
143
+ function offersAfter(now, made, shut) {
144
+ const last = made[made.length - 1];
145
+ return now.graded && !now.disabled && !shut && now.channel?.triesClosed !== true && last !== void 0 && made.length < 1 + now.policy.retries && !fullMarks(last);
146
+ }
147
+ function useTries({ policy, graded, submitted, disabled }) {
148
+ const channel = useContext(SequenceSlotContext);
149
+ const [tries, setTries] = useState([]);
150
+ const [closed, setClosed] = useState(false);
151
+ const triesRef = useRef([]);
152
+ const closedRef = useRef(false);
153
+ const latest = useRef({ policy, graded, disabled, channel });
154
+ latest.current = { policy, graded, disabled, channel };
155
+ const record = useCallback((one) => {
156
+ const next = [...triesRef.current, one];
157
+ triesRef.current = next;
158
+ setTries(next);
159
+ if (offersAfter(latest.current, next, closedRef.current)) {
160
+ latest.current.channel?.triesState("offered");
161
+ }
162
+ return scoreTries(next, latest.current.policy);
163
+ }, []);
164
+ const close = useCallback(() => {
165
+ closedRef.current = true;
166
+ setClosed(true);
167
+ }, []);
168
+ const reset = useCallback(() => {
169
+ triesRef.current = [];
170
+ closedRef.current = false;
171
+ setTries([]);
172
+ setClosed(false);
173
+ }, []);
174
+ const allowed = 1 + policy.retries;
175
+ const left = Math.max(0, allowed - tries.length);
176
+ const open = submitted && offersAfter(latest.current, tries, closed);
177
+ const retrying = !submitted && tries.length > 0 && !closed && graded && !disabled && channel?.triesClosed !== true;
178
+ const state = open ? "offered" : retrying ? "retrying" : "none";
179
+ const report = channel?.triesState;
180
+ useEffect(() => {
181
+ report?.(state);
182
+ }, [report, state]);
183
+ useEffect(() => () => report?.("none"), [report]);
184
+ const counted = useMemo(
185
+ () => tries.length === 0 ? null : scoreTries(tries, policy),
186
+ [tries, policy]
187
+ );
188
+ return { tries, counted, open, left, record, close, reset };
189
+ }
190
+ var percentOf = (fraction) => Math.round(fraction * 100);
191
+ function triesSummary(s, policy, counted, offered) {
192
+ const parts = [];
193
+ const latest = counted.tries.length - 1;
194
+ const mine = counted.tries[latest];
195
+ if (mine !== void 0) {
196
+ if (counted.counted !== latest) {
197
+ parts.push(
198
+ s.countedTry(
199
+ policy.counts === "best" ? "best" : "first",
200
+ percentOf(mine.scored / mine.maxScore)
201
+ )
202
+ );
203
+ } else if (mine.scored !== mine.score) {
204
+ parts.push(s.scoreBeforeCosts(percentOf(mine.score / mine.maxScore)));
205
+ }
206
+ }
207
+ if (offered) {
208
+ parts.push(
209
+ s.triesLeft(policy.retries + 1 - counted.tries.length, percentOf(policy.retryPenalty))
210
+ );
211
+ }
212
+ return parts.join(" ");
213
+ }
214
+ function TryActions({
215
+ tries,
216
+ solutions,
217
+ strings: s,
218
+ onRetry,
219
+ onClose
220
+ }) {
221
+ if (!tries.open) {
222
+ return null;
223
+ }
224
+ return /* @__PURE__ */ jsxs("div", { className: "lk-tries", children: [
225
+ /* @__PURE__ */ jsx("button", { type: "button", className: "lk-tries-again", onClick: onRetry, children: s.tryAgain }),
226
+ /* @__PURE__ */ jsx("button", { type: "button", className: "lk-tries-close", onClick: onClose, children: solutions ? s.showAnswer : s.keepAnswer })
227
+ ] });
228
+ }
229
+ function HintCost({
230
+ policy,
231
+ strings: s
232
+ }) {
233
+ if (policy.hintPenalty <= 0) {
234
+ return null;
235
+ }
236
+ return /* @__PURE__ */ jsx("p", { className: "lk-hint-cost", children: s.hintCost(percentOf(policy.hintPenalty)) });
237
+ }
238
+
239
+ export { AiExplanation, AiHints, HintCost, TryActions, hintsOf, triesSummary, useComponentAiHints, useItemScoringPolicy, useTries };
240
+ //# sourceMappingURL=chunk-GND3OBVE.js.map
241
+ //# sourceMappingURL=chunk-GND3OBVE.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/shared/AiHelp.tsx","../src/components/shared/tries.tsx"],"names":["useRef","useEffect","jsxs","jsx"],"mappings":"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","file":"chunk-GND3OBVE.js","sourcesContent":["'use client';\n\nimport type {\n InteractionEvent,\n ItemOutcome,\n LearnerResponse,\n ResolvedDeliveryPolicy,\n} from '@intellectif/lk-core';\nimport { useEffect, useRef } from 'react';\nimport type { LearnerAi } from '../../ai/LkAiProvider.js';\nimport { type AiHintsHelp, useAiExplanation, useAiHints } from '../../ai/useAiHelp.js';\nimport type { LkStrings } from '../../i18n/strings.js';\nimport type { RenderableActivity, RenderMode } from '../types.js';\n\ninterface Situation {\n ai: LearnerAi | undefined;\n data: RenderableActivity;\n renderMode: RenderMode;\n submitted: boolean;\n /** The learner's answer as it stands: submitted, restored, or being written. */\n response: LearnerResponse;\n /** The component's `locale`, the default language for what is written. */\n locale: string | undefined;\n onInteraction: ((event: InteractionEvent) => void) | undefined;\n strings: LkStrings;\n /** The policy the component resolved, the paper's included. */\n delivery: ResolvedDeliveryPolicy;\n}\n\n/** What the hooks take, from what a component holds: the optional half, without `undefined` values. */\nfunction situation(input: Situation): {\n data: RenderableActivity;\n response: LearnerResponse;\n submitted: boolean;\n renderMode: RenderMode;\n ai?: LearnerAi;\n locale?: string;\n onInteraction?: (event: InteractionEvent) => void;\n delivery: ResolvedDeliveryPolicy;\n} {\n const { data, response, submitted, renderMode, ai, locale, onInteraction, delivery } = input;\n return {\n data,\n response,\n submitted,\n renderMode,\n delivery,\n ...(ai !== undefined ? { ai } : {}),\n ...(locale !== undefined ? { locale } : {}),\n ...(onInteraction !== undefined ? { onInteraction } : {}),\n };\n}\n\n/**\n * \"Explain my answer\": the SDK's own surface over {@link useAiExplanation} — a\n * button, the explanation with who wrote it, and \"not available\" when a call\n * fails or its answer is refused.\n *\n * Nothing renders without a port, or where the rules say no explanation.\n */\nexport function AiExplanation({\n outcome,\n ...rest\n}: Situation & { outcome: ItemOutcome | undefined }) {\n const s = rest.strings;\n const help = useAiExplanation({\n ...situation(rest),\n ...(outcome !== undefined ? { outcome } : {}),\n });\n const buttonRef = useRef<HTMLButtonElement>(null);\n const panelRef = useRef<HTMLElement>(null);\n const refocus = useRef(false);\n\n // The button that asked goes away when the explanation arrives; focus moves\n // to what it asked for rather than falling to the page.\n useEffect(() => {\n if (help.status === 'shown' && refocus.current) {\n refocus.current = false;\n panelRef.current?.focus();\n }\n if (help.status === 'unavailable') {\n refocus.current = false;\n }\n }, [help.status]);\n\n if (!help.offered) {\n return null;\n }\n\n const ask = (): void => {\n refocus.current =\n typeof document !== 'undefined' && document.activeElement === buttonRef.current;\n help.ask();\n };\n\n const loading = help.status === 'loading';\n return (\n <div className=\"lk-ai\" data-state={help.status}>\n {help.explanation === null ? (\n <button\n ref={buttonRef}\n type=\"button\"\n className=\"lk-ai-button\"\n aria-busy={loading || undefined}\n aria-disabled={loading || undefined}\n onClick={ask}\n >\n {loading ? s.aiExplaining : s.aiExplain}\n </button>\n ) : null}\n <div aria-live=\"polite\">\n {help.explanation !== null ? (\n <section\n ref={panelRef}\n className=\"lk-ai-panel\"\n tabIndex={-1}\n aria-label={s.aiExplanation}\n >\n <p className=\"lk-ai-heading\">{s.aiExplanation}</p>\n <p className=\"lk-ai-text\">{help.explanation.text}</p>\n <p className=\"lk-ai-notice\">{s.aiNotice}</p>\n </section>\n ) : help.status === 'unavailable' ? (\n <p className=\"lk-ai-unavailable\">{s.aiExplanationUnavailable}</p>\n ) : null}\n </div>\n </div>\n );\n}\n\n/**\n * The AI hints of one of the SDK's own questions: {@link useAiHints}, called\n * by the component itself so it can count the hints its learner was shown.\n */\nexport function useComponentAiHints({\n disabled,\n ...rest\n}: Situation & { disabled: boolean }): AiHintsHelp {\n return useAiHints({ ...situation(rest), disabled });\n}\n\n/**\n * \"Get a hint\": the SDK's own surface over {@link useAiHints} — a button, the\n * hints given so far as a numbered list, and who wrote them.\n *\n * The hints stay listed after submit, so the learner can see what they were\n * given, but no more can be asked for — until a \"Try again\" reopens the\n * answer, when the same list carries on. Another question starts with none.\n */\nexport function AiHints({\n help,\n ai,\n strings: s,\n}: {\n help: AiHintsHelp;\n ai: LearnerAi | undefined;\n strings: LkStrings;\n}) {\n const used = help.used;\n\n if (ai?.hint === undefined || (!help.offered && used === 0)) {\n return null;\n }\n\n const loading = help.status === 'loading';\n return (\n <div className=\"lk-ai lk-ai-hint-area\" data-state={help.status}>\n <div aria-live=\"polite\">\n {used > 0 ? (\n <ol className=\"lk-ai-hints\" aria-label={s.aiHints}>\n {help.hints.map((hint, index) => (\n // Hints are only ever appended, so a position is a stable key.\n // biome-ignore lint/suspicious/noArrayIndexKey: an append-only list\n <li key={index} className=\"lk-ai-hint\">\n <span className=\"lk-ai-hint-label\">{s.aiHintNumber(index + 1)}</span>{' '}\n <span className=\"lk-ai-text\">{hint.text}</span>\n </li>\n ))}\n </ol>\n ) : null}\n {help.status === 'unavailable' ? (\n <p className=\"lk-ai-unavailable\">{s.aiHintUnavailable}</p>\n ) : null}\n </div>\n {help.offered ? (\n used < help.limit ? (\n <button\n type=\"button\"\n className=\"lk-ai-button\"\n aria-busy={loading || undefined}\n aria-disabled={loading || undefined}\n onClick={help.ask}\n >\n {loading ? s.aiHintLoading : s.aiHint}\n </button>\n ) : (\n <p className=\"lk-ai-note\">{s.aiNoMoreHints}</p>\n )\n ) : null}\n {used > 0 ? <p className=\"lk-ai-notice\">{s.aiNotice}</p> : null}\n </div>\n );\n}\n","'use client';\n\nimport {\n type ItemScoringPolicy,\n type ItemTriesScore,\n type ItemTry,\n type ResolvedItemScoringPolicy,\n resolveItemScoringPolicy,\n scoreTries,\n} from '@intellectif/lk-core';\nimport { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';\nimport type { LkStrings } from '../../i18n/strings.js';\nimport { SequenceSlotContext, type TriesState } from './sequence-slot.js';\n\n/**\n * The scoring policy a question is under: the paper's, when the paper around\n * it was given one, else its own.\n *\n * Not combined, as delivery policies are: a cost and a count of tries have no\n * \"stricter\" to pick. The paper decides, as it decides the mode — so a\n * question drawn inside a set is scored by the set's rules whatever it was\n * handed.\n *\n * A policy `validateItemScoringPolicy` refuses throws here, at render. Every\n * setting in it moves a grade, and there is no safe way to read one wrongly:\n * the question fails loudly rather than grade by a guess.\n */\nexport function useItemScoringPolicy(\n given: ItemScoringPolicy | null | undefined,\n): ResolvedItemScoringPolicy {\n const around = useContext(SequenceSlotContext)?.scoring;\n const key = policyKey(given);\n // biome-ignore lint/correctness/useExhaustiveDependencies: the policy's content is the trigger, not its identity\n const own = useMemo(\n () => (around === undefined ? resolveItemScoringPolicy(given) : null),\n [around, key],\n );\n return around ?? (own as ResolvedItemScoringPolicy);\n}\n\nfunction policyKey(given: unknown): string {\n try {\n return JSON.stringify(given) ?? '';\n } catch {\n return String(given);\n }\n}\n\n/** A hint count as a response carries it: anything but a whole number above 0 is none. */\nexport function hintsOf(response: unknown): number {\n const count = (response as { hintsRevealed?: unknown } | null | undefined)?.hintsRevealed;\n return typeof count === 'number' && Number.isSafeInteger(count) && count > 0 ? count : 0;\n}\n\n/** Whether a try earned every mark there was: float noise above the maximum included. */\nconst fullMarks = (one: ItemTry): boolean => one.score / one.maxScore >= 1 - 1e-9;\n\n/** Whether, after the tries `made`, a question offers another: the rule `open` reads. */\nfunction offersAfter(\n now: {\n policy: ResolvedItemScoringPolicy;\n graded: boolean;\n disabled: boolean;\n channel: { triesClosed: boolean } | null;\n },\n made: readonly ItemTry[],\n shut: boolean,\n): boolean {\n const last = made[made.length - 1];\n return (\n now.graded &&\n !now.disabled &&\n !shut &&\n now.channel?.triesClosed !== true &&\n last !== undefined &&\n made.length < 1 + now.policy.retries &&\n !fullMarks(last)\n );\n}\n\nexport interface TriesInput {\n policy: ResolvedItemScoringPolicy;\n /**\n * Whether the component grades this answer itself and the learner may see\n * how it went: `practice`, with `feedback` on. \"Try again\" anywhere else\n * would be meaningless, or would say an answer was wrong on a paper that\n * does not say so.\n */\n graded: boolean;\n submitted: boolean;\n disabled: boolean;\n}\n\nexport interface Tries {\n /** Every graded try so far, in order. */\n tries: readonly ItemTry[];\n /** The question's score under the policy, once a try has been graded. */\n counted: ItemTriesScore | null;\n /**\n * Whether the learner is being offered another try at a graded answer: the\n * last try earned less than full marks, the policy has tries left, and\n * nothing has closed the question. While it is, the question shows no right\n * answer and no explanation — either would hand over the answer the next try\n * is for.\n */\n open: boolean;\n /** Tries still to come after the last graded one. */\n left: number;\n /** Records a graded try and returns the question's score under the policy. */\n record: (graded: ItemTry) => ItemTriesScore;\n /** Ends the question's tries: \"Show answer\". */\n close: () => void;\n /** Forgets every try: a different question. */\n reset: () => void;\n}\n\n/**\n * The tries a question has had and whether it offers another: the SDK's own\n * components' rules for \"Try again\", in one place.\n *\n * Reports whether a try is on offer to the paper around the question, which\n * waits for it before reporting the set while the question is on screen — and\n * closes every question's tries once it has reported.\n */\nexport function useTries({ policy, graded, submitted, disabled }: TriesInput): Tries {\n const channel = useContext(SequenceSlotContext);\n const [tries, setTries] = useState<readonly ItemTry[]>([]);\n const [closed, setClosed] = useState(false);\n // Written synchronously, so a submit that records a try and reports it reads\n // every try before it, whatever React has rendered.\n const triesRef = useRef<readonly ItemTry[]>([]);\n const closedRef = useRef(false);\n const latest = useRef({ policy, graded, disabled, channel });\n latest.current = { policy, graded, disabled, channel };\n\n const record = useCallback((one: ItemTry): ItemTriesScore => {\n const next = [...triesRef.current, one];\n triesRef.current = next;\n setTries(next);\n // An offer of another try is reported before the grade is handed on, not\n // after the next render: the paper reports its set when a grade fills its\n // last slot, and must already know to wait. The end of the tries is not:\n // said now, it would let the paper report the grade this try is replacing.\n // The effect below says it once this grade has landed.\n if (offersAfter(latest.current, next, closedRef.current)) {\n latest.current.channel?.triesState('offered');\n }\n return scoreTries(next, latest.current.policy);\n }, []);\n const close = useCallback(() => {\n closedRef.current = true;\n setClosed(true);\n }, []);\n const reset = useCallback(() => {\n triesRef.current = [];\n closedRef.current = false;\n setTries([]);\n setClosed(false);\n }, []);\n\n const allowed = 1 + policy.retries;\n const left = Math.max(0, allowed - tries.length);\n const open = submitted && offersAfter(latest.current, tries, closed);\n // A try the learner is making now — after \"Try again\", before the answer is\n // in again. Reporting the set then would hand on the grade the learner is in\n // the middle of replacing.\n const retrying =\n !submitted &&\n tries.length > 0 &&\n !closed &&\n graded &&\n !disabled &&\n channel?.triesClosed !== true;\n const state: TriesState = open ? 'offered' : retrying ? 'retrying' : 'none';\n\n const report = channel?.triesState;\n useEffect(() => {\n report?.(state);\n }, [report, state]);\n // A question that goes away offers nothing: the paper must not wait for it.\n useEffect(() => () => report?.('none'), [report]);\n\n const counted = useMemo(\n () => (tries.length === 0 ? null : scoreTries(tries, policy)),\n [tries, policy],\n );\n return { tries, counted, open, left, record, close, reset };\n}\n\n/** A fraction of the marks as the whole percentage a learner reads. */\nexport const percentOf = (fraction: number): number => Math.round(fraction * 100);\n\n/**\n * The sentences after the score, on a question whose policy changed it: which\n * try counts when it is not the one just made, what this answer scored before\n * its costs, and how many tries are left.\n */\nexport function triesSummary(\n s: LkStrings,\n policy: ResolvedItemScoringPolicy,\n counted: ItemTriesScore,\n offered: boolean,\n): string {\n const parts: string[] = [];\n const latest = counted.tries.length - 1;\n const mine = counted.tries[latest];\n if (mine !== undefined) {\n if (counted.counted !== latest) {\n parts.push(\n s.countedTry(\n policy.counts === 'best' ? 'best' : 'first',\n percentOf(mine.scored / mine.maxScore),\n ),\n );\n } else if (mine.scored !== mine.score) {\n parts.push(s.scoreBeforeCosts(percentOf(mine.score / mine.maxScore)));\n }\n }\n if (offered) {\n parts.push(\n s.triesLeft(policy.retries + 1 - counted.tries.length, percentOf(policy.retryPenalty)),\n );\n }\n return parts.join(' ');\n}\n\n/**\n * \"Try again\" and \"Show answer\" — or \"Keep this answer\", on a paper that shows\n * no right answers — after a graded try that did not earn full marks.\n */\nexport function TryActions({\n tries,\n solutions,\n strings: s,\n onRetry,\n onClose,\n}: {\n tries: Tries;\n /** Whether closing the question will show the right answer. */\n solutions: boolean;\n strings: LkStrings;\n onRetry: () => void;\n onClose: () => void;\n}): React.JSX.Element | null {\n if (!tries.open) {\n return null;\n }\n return (\n <div className=\"lk-tries\">\n <button type=\"button\" className=\"lk-tries-again\" onClick={onRetry}>\n {s.tryAgain}\n </button>\n <button type=\"button\" className=\"lk-tries-close\" onClick={onClose}>\n {solutions ? s.showAnswer : s.keepAnswer}\n </button>\n </div>\n );\n}\n\n/** What a hint costs, said before the learner asks for one. */\nexport function HintCost({\n policy,\n strings: s,\n}: {\n policy: ResolvedItemScoringPolicy;\n strings: LkStrings;\n}): React.JSX.Element | null {\n if (policy.hintPenalty <= 0) {\n return null;\n }\n return <p className=\"lk-hint-cost\">{s.hintCost(percentOf(policy.hintPenalty))}</p>;\n}\n"]}
@@ -1,16 +1,16 @@
1
1
  'use client';
2
2
  'use strict';
3
3
 
4
- var chunkZME5FMED_cjs = require('./chunk-ZME5FMED.cjs');
5
- var chunkXUQ4KRTT_cjs = require('./chunk-XUQ4KRTT.cjs');
6
- var chunkXAIJNGTU_cjs = require('./chunk-XAIJNGTU.cjs');
7
- var chunkNGFTSRKO_cjs = require('./chunk-NGFTSRKO.cjs');
8
- var chunkZDYNAWDZ_cjs = require('./chunk-ZDYNAWDZ.cjs');
9
- var chunkNZM3M5IR_cjs = require('./chunk-NZM3M5IR.cjs');
10
- var chunkJVWLXVYG_cjs = require('./chunk-JVWLXVYG.cjs');
11
- var chunkAPZ6JFN6_cjs = require('./chunk-APZ6JFN6.cjs');
4
+ var chunkOKEN6NJL_cjs = require('./chunk-OKEN6NJL.cjs');
5
+ var chunkW2P5RILE_cjs = require('./chunk-W2P5RILE.cjs');
6
+ var chunkCB2A56Q7_cjs = require('./chunk-CB2A56Q7.cjs');
7
+ var chunkWGHMWBEJ_cjs = require('./chunk-WGHMWBEJ.cjs');
8
+ var chunkMPHX3Y42_cjs = require('./chunk-MPHX3Y42.cjs');
9
+ var chunkWXESKL75_cjs = require('./chunk-WXESKL75.cjs');
10
+ var chunkJ2QIHZ3H_cjs = require('./chunk-J2QIHZ3H.cjs');
11
+ var chunk6SNNMVBV_cjs = require('./chunk-6SNNMVBV.cjs');
12
12
  var chunkEANGKAKG_cjs = require('./chunk-EANGKAKG.cjs');
13
- var chunkAHGB5HH6_cjs = require('./chunk-AHGB5HH6.cjs');
13
+ var chunkGHKMX5UT_cjs = require('./chunk-GHKMX5UT.cjs');
14
14
  var lkCore = require('@intellectif/lk-core');
15
15
  var react = require('react');
16
16
  var jsxRuntime = require('react/jsx-runtime');
@@ -1221,8 +1221,15 @@ function Player(props) {
1221
1221
  theme,
1222
1222
  sanitizeHtml
1223
1223
  } = props;
1224
- const strings = chunkAHGB5HH6_cjs.useLkStrings(props.strings);
1225
- const aiInForce = chunkZDYNAWDZ_cjs.useLearnerAi(props.ai);
1224
+ const strings = chunkGHKMX5UT_cjs.useLkStrings(props.strings);
1225
+ const aiInForce = chunkMPHX3Y42_cjs.useLearnerAi(props.ai);
1226
+ const delivery = lkCore.resolveDeliveryPolicy(props.delivery);
1227
+ const scoringKey = JSON.stringify(props.scoring ?? null);
1228
+ const scoring = react.useMemo(
1229
+ () => lkCore.resolveItemScoringPolicy(props.scoring),
1230
+ [scoringKey]
1231
+ );
1232
+ const scoringGiven = props.scoring !== void 0 && props.scoring !== null;
1226
1233
  const group = react.useRef(props.group).current;
1227
1234
  const timeline = group.timeline ?? { cues: [] };
1228
1235
  const media = group.stimulus.media;
@@ -1393,6 +1400,7 @@ function Player(props) {
1393
1400
  const hideTimer = react.useRef(null);
1394
1401
  const lastPointer = react.useRef("mouse");
1395
1402
  const finishedOnce = react.useRef(false);
1403
+ const [, setFinishedState] = react.useState(false);
1396
1404
  const [openQuiz, setOpenQuiz] = react.useState(null);
1397
1405
  const openQuizRef = react.useRef(openQuiz);
1398
1406
  openQuizRef.current = openQuiz;
@@ -1969,6 +1977,7 @@ function Player(props) {
1969
1977
  return;
1970
1978
  }
1971
1979
  finishedOnce.current = true;
1980
+ setFinishedState(true);
1972
1981
  onFinished?.(summary());
1973
1982
  }, [onFinished, summary]);
1974
1983
  const answerPending = pending.size > 0;
@@ -2011,8 +2020,22 @@ function Player(props) {
2011
2020
  }
2012
2021
  return byId;
2013
2022
  }, [slotsByCue]);
2014
- const reportTo = react.useRef({ onSubmit, onActivityComplete, onInteraction, renderMode });
2015
- reportTo.current = { onSubmit, onActivityComplete, onInteraction, renderMode };
2023
+ const reportTo = react.useRef({
2024
+ onSubmit,
2025
+ onActivityComplete,
2026
+ onInteraction,
2027
+ renderMode,
2028
+ delivery,
2029
+ scoring: scoringGiven ? scoring : void 0
2030
+ });
2031
+ reportTo.current = {
2032
+ onSubmit,
2033
+ onActivityComplete,
2034
+ onInteraction,
2035
+ renderMode,
2036
+ delivery,
2037
+ scoring: scoringGiven ? scoring : void 0
2038
+ };
2016
2039
  const calls = react.useRef(/* @__PURE__ */ new Map());
2017
2040
  const callsFor = (place) => {
2018
2041
  const made = calls.current.get(place.slotId);
@@ -2099,6 +2122,19 @@ function Player(props) {
2099
2122
  get renderMode() {
2100
2123
  return reportTo.current.renderMode;
2101
2124
  },
2125
+ get delivery() {
2126
+ return reportTo.current.delivery;
2127
+ },
2128
+ get scoring() {
2129
+ return reportTo.current.scoring;
2130
+ },
2131
+ get triesClosed() {
2132
+ return finishedOnce.current;
2133
+ },
2134
+ // The video waits for no try: its Finish is on the end card, where no
2135
+ // question is, and finishing closes every question's tries.
2136
+ triesState: () => {
2137
+ },
2102
2138
  takeState: (take, state) => reportTake(slotId, take, state)
2103
2139
  };
2104
2140
  channels.current.set(slotId, channel);
@@ -2122,6 +2158,8 @@ function Player(props) {
2122
2158
  ...outcome !== void 0 ? { outcome } : {},
2123
2159
  portalContainer,
2124
2160
  ...aiInForce !== void 0 && renderMode !== "exam" ? { ai: aiInForce } : {},
2161
+ delivery,
2162
+ scoring,
2125
2163
  ...callsFor(place)
2126
2164
  };
2127
2165
  };
@@ -2140,12 +2178,14 @@ function Player(props) {
2140
2178
  ...sanitizeHtml !== void 0 ? { sanitizeHtml } : {},
2141
2179
  ...props.strings !== void 0 ? { strings: props.strings } : {},
2142
2180
  ...props.ai !== void 0 ? { ai: props.ai } : {},
2181
+ ...props.delivery !== void 0 && props.delivery !== null ? { delivery: props.delivery } : {},
2182
+ ...scoringGiven ? { scoring: props.scoring } : {},
2143
2183
  ...locale !== void 0 ? { locale } : {}
2144
2184
  };
2145
2185
  switch (activity.type) {
2146
2186
  case "multiple-choice":
2147
2187
  return /* @__PURE__ */ jsxRuntime.jsx(
2148
- chunkZME5FMED_cjs.MultipleChoice,
2188
+ chunkOKEN6NJL_cjs.MultipleChoice,
2149
2189
  {
2150
2190
  data: activity,
2151
2191
  ...common,
@@ -2153,10 +2193,10 @@ function Player(props) {
2153
2193
  }
2154
2194
  );
2155
2195
  case "fill-in-the-blanks":
2156
- return /* @__PURE__ */ jsxRuntime.jsx(chunkXUQ4KRTT_cjs.FillInTheBlanks, { data: activity, ...common });
2196
+ return /* @__PURE__ */ jsxRuntime.jsx(chunkW2P5RILE_cjs.FillInTheBlanks, { data: activity, ...common });
2157
2197
  case "gap-select":
2158
2198
  return /* @__PURE__ */ jsxRuntime.jsx(
2159
- chunkXAIJNGTU_cjs.GapSelect,
2199
+ chunkCB2A56Q7_cjs.GapSelect,
2160
2200
  {
2161
2201
  data: activity,
2162
2202
  ...common,
@@ -2164,12 +2204,12 @@ function Player(props) {
2164
2204
  }
2165
2205
  );
2166
2206
  case "dictation":
2167
- return /* @__PURE__ */ jsxRuntime.jsx(chunkNGFTSRKO_cjs.Dictation, { data: activity, ...common });
2207
+ return /* @__PURE__ */ jsxRuntime.jsx(chunkWGHMWBEJ_cjs.Dictation, { data: activity, ...common });
2168
2208
  case "read-aloud": {
2169
2209
  const binding = slotBindings.get(slot.slotId);
2170
2210
  const assessment = props.assessments !== void 0 && Object.hasOwn(props.assessments, slot.slotId) ? props.assessments[slot.slotId] : void 0;
2171
2211
  return /* @__PURE__ */ jsxRuntime.jsx(
2172
- chunkNZM3M5IR_cjs.ReadAloud,
2212
+ chunkWXESKL75_cjs.ReadAloud,
2173
2213
  {
2174
2214
  data: activity,
2175
2215
  ...common,
@@ -2718,7 +2758,7 @@ function Player(props) {
2718
2758
  quizzes,
2719
2759
  answered: answeredCount,
2720
2760
  total: totalQuestions,
2721
- percent,
2761
+ percent: delivery.feedback ? percent : null,
2722
2762
  strings,
2723
2763
  onOpenQuiz: (id) => {
2724
2764
  const cue = cues.find((candidate) => candidate.id === id);
@@ -2798,7 +2838,7 @@ function SlotQuestion({
2798
2838
  ...content
2799
2839
  }) {
2800
2840
  return /* @__PURE__ */ jsxRuntime.jsx(
2801
- chunkAPZ6JFN6_cjs.ActivityErrorBoundary,
2841
+ chunk6SNNMVBV_cjs.ActivityErrorBoundary,
2802
2842
  {
2803
2843
  ...title !== void 0 ? { activityTitle: title } : {},
2804
2844
  strings,
@@ -2833,7 +2873,7 @@ function QuestionPane({
2833
2873
  if (!hidden) {
2834
2874
  return;
2835
2875
  }
2836
- chunkNZM3M5IR_cjs.stopCaptureGroup(captureGroup);
2876
+ chunkWXESKL75_cjs.stopCaptureGroup(captureGroup);
2837
2877
  for (const media of ref.current?.querySelectorAll("audio, video") ?? []) {
2838
2878
  const element = media;
2839
2879
  if (!element.paused) {
@@ -2841,7 +2881,7 @@ function QuestionPane({
2841
2881
  }
2842
2882
  }
2843
2883
  }, [hidden, captureGroup]);
2844
- return /* @__PURE__ */ jsxRuntime.jsx("div", { ref, className: "lk-iv-question", hidden, children: /* @__PURE__ */ jsxRuntime.jsx(chunkJVWLXVYG_cjs.SequenceSlotContext.Provider, { value: channel, children }) });
2884
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { ref, className: "lk-iv-question", hidden, children: /* @__PURE__ */ jsxRuntime.jsx(chunkJ2QIHZ3H_cjs.SequenceSlotContext.Provider, { value: channel, children }) });
2845
2885
  }
2846
2886
  function bufferedAhead(video) {
2847
2887
  const ranges = video.buffered;
@@ -2881,9 +2921,9 @@ function markerState(quizSlots, answered, renderMode, outcomes) {
2881
2921
  return "unreached";
2882
2922
  }
2883
2923
  function InteractiveVideo2(props) {
2884
- const strings = chunkAHGB5HH6_cjs.useLkStrings(props.strings);
2924
+ const strings = chunkGHKMX5UT_cjs.useLkStrings(props.strings);
2885
2925
  return /* @__PURE__ */ jsxRuntime.jsx(
2886
- chunkAPZ6JFN6_cjs.ActivityErrorBoundary,
2926
+ chunk6SNNMVBV_cjs.ActivityErrorBoundary,
2887
2927
  {
2888
2928
  ...props.group?.title !== void 0 ? { activityTitle: props.group.title } : {},
2889
2929
  strings,
@@ -2894,5 +2934,5 @@ function InteractiveVideo2(props) {
2894
2934
 
2895
2935
  exports.InteractiveVideo = InteractiveVideo2;
2896
2936
  exports.resolveCaptionTracks = resolveCaptionTracks;
2897
- //# sourceMappingURL=chunk-WKA6FUR4.cjs.map
2898
- //# sourceMappingURL=chunk-WKA6FUR4.cjs.map
2937
+ //# sourceMappingURL=chunk-GPGCBDBN.cjs.map
2938
+ //# sourceMappingURL=chunk-GPGCBDBN.cjs.map