@intellectif/lk-react 21.0.0 → 23.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.
- package/CHANGELOG.md +41 -0
- package/README.md +4 -4
- package/dist/{WrittenResponse-BVPmrmQL.d.cts → WrittenResponse-CgRrjM4S.d.ts} +14 -4
- package/dist/{WrittenResponse-IAJvvL-g.d.ts → WrittenResponse-D2K1VW_V.d.cts} +14 -4
- package/dist/ai/LkAiProvider.cjs +3 -3
- package/dist/ai/LkAiProvider.d.cts +12 -1
- package/dist/ai/LkAiProvider.d.ts +12 -1
- package/dist/ai/LkAiProvider.js +1 -1
- package/dist/ai/useAiHelp.cjs +9 -5
- package/dist/ai/useAiHelp.d.cts +58 -3
- package/dist/ai/useAiHelp.d.ts +58 -3
- package/dist/ai/useAiHelp.js +3 -3
- package/dist/{chunk-M3K6BF5M.js → chunk-2OP5YHRR.js} +13 -8
- package/dist/chunk-2OP5YHRR.js.map +1 -0
- package/dist/{chunk-AY3WN4FB.js → chunk-4MXECBZT.js} +9 -9
- package/dist/chunk-4MXECBZT.js.map +1 -0
- package/dist/{chunk-EANGKAKG.cjs → chunk-54DX6SLS.cjs} +12 -2
- package/dist/chunk-54DX6SLS.cjs.map +1 -0
- package/dist/{chunk-WNDNQ6NR.js → chunk-6GQLCCGN.js} +11 -13
- package/dist/chunk-6GQLCCGN.js.map +1 -0
- package/dist/{chunk-Y4IDF5UO.cjs → chunk-6RIM5EQP.cjs} +140 -9
- package/dist/chunk-6RIM5EQP.cjs.map +1 -0
- package/dist/{chunk-NILHU555.cjs → chunk-7F2YQWMW.cjs} +17 -12
- package/dist/chunk-7F2YQWMW.cjs.map +1 -0
- package/dist/{chunk-YWJ5XJDU.cjs → chunk-B4JRBV56.cjs} +6 -6
- package/dist/{chunk-YWJ5XJDU.cjs.map → chunk-B4JRBV56.cjs.map} +1 -1
- package/dist/{chunk-CB2A56Q7.cjs → chunk-B7JU52YQ.cjs} +32 -35
- package/dist/chunk-B7JU52YQ.cjs.map +1 -0
- package/dist/{chunk-EYLBSS4Y.js → chunk-C4PSJ3ZC.js} +10 -2
- package/dist/chunk-C4PSJ3ZC.js.map +1 -0
- package/dist/{chunk-NDIKHMLJ.js → chunk-CXIP7ERK.js} +138 -8
- package/dist/chunk-CXIP7ERK.js.map +1 -0
- package/dist/{chunk-ZDDBWUUA.js → chunk-DPNCCWOV.js} +120 -27
- package/dist/chunk-DPNCCWOV.js.map +1 -0
- package/dist/{chunk-PPFVTXGJ.js → chunk-DXFCYNST.js} +11 -3
- package/dist/chunk-DXFCYNST.js.map +1 -0
- package/dist/{chunk-WXESKL75.cjs → chunk-DZ7VLLDQ.cjs} +30 -30
- package/dist/chunk-DZ7VLLDQ.cjs.map +1 -0
- package/dist/{chunk-U6SEII5V.js → chunk-E4UF4XZM.js} +4 -4
- package/dist/{chunk-U6SEII5V.js.map → chunk-E4UF4XZM.js.map} +1 -1
- package/dist/{chunk-XM5Q2ZCH.js → chunk-ERV666AR.js} +10 -13
- package/dist/chunk-ERV666AR.js.map +1 -0
- package/dist/{chunk-6SNNMVBV.cjs → chunk-FISEZBLF.cjs} +7 -7
- package/dist/{chunk-6SNNMVBV.cjs.map → chunk-FISEZBLF.cjs.map} +1 -1
- package/dist/{chunk-X4YWXZ4J.js → chunk-HYOUMTPW.js} +3 -3
- package/dist/{chunk-X4YWXZ4J.js.map → chunk-HYOUMTPW.js.map} +1 -1
- package/dist/{chunk-5Q2KFIZQ.js → chunk-HZIT24BK.js} +4 -4
- package/dist/{chunk-5Q2KFIZQ.js.map → chunk-HZIT24BK.js.map} +1 -1
- package/dist/{chunk-GPGCBDBN.cjs → chunk-IYGF7PZY.cjs} +24 -24
- package/dist/{chunk-GPGCBDBN.cjs.map → chunk-IYGF7PZY.cjs.map} +1 -1
- package/dist/{chunk-XB2AHEU5.js → chunk-JMEZYYCE.js} +11 -11
- package/dist/{chunk-XB2AHEU5.js.map → chunk-JMEZYYCE.js.map} +1 -1
- package/dist/{chunk-KRM6DX47.js → chunk-KI6DJ5ZF.js} +10 -13
- package/dist/chunk-KI6DJ5ZF.js.map +1 -0
- package/dist/{chunk-WGHMWBEJ.cjs → chunk-MXZHL4U5.cjs} +29 -31
- package/dist/chunk-MXZHL4U5.cjs.map +1 -0
- package/dist/{chunk-GHKMX5UT.cjs → chunk-NFGUJ52G.cjs} +10 -2
- package/dist/chunk-NFGUJ52G.cjs.map +1 -0
- package/dist/{chunk-W6TWQY6S.js → chunk-O7MINPKZ.js} +10 -10
- package/dist/chunk-O7MINPKZ.js.map +1 -0
- package/dist/{chunk-MPHX3Y42.cjs → chunk-OJGTJOWI.cjs} +2 -2
- package/dist/chunk-OJGTJOWI.cjs.map +1 -0
- package/dist/{chunk-W2P5RILE.cjs → chunk-PGAPZ6UE.cjs} +32 -35
- package/dist/chunk-PGAPZ6UE.cjs.map +1 -0
- package/dist/{chunk-OKEN6NJL.cjs → chunk-QAZO3ANW.cjs} +32 -32
- package/dist/chunk-QAZO3ANW.cjs.map +1 -0
- package/dist/{chunk-MCTNLUKO.js → chunk-RX3A6KIY.js} +2 -2
- package/dist/chunk-RX3A6KIY.js.map +1 -0
- package/dist/{chunk-LHFC2BCM.cjs → chunk-SHDSOOBF.cjs} +21 -21
- package/dist/{chunk-LHFC2BCM.cjs.map → chunk-SHDSOOBF.cjs.map} +1 -1
- package/dist/{chunk-U2BX2RWY.js → chunk-SPSKDNEE.js} +11 -11
- package/dist/{chunk-U2BX2RWY.js.map → chunk-SPSKDNEE.js.map} +1 -1
- package/dist/{chunk-5MO7ACSB.js → chunk-TGTP5DKF.js} +10 -10
- package/dist/{chunk-5MO7ACSB.js.map → chunk-TGTP5DKF.js.map} +1 -1
- package/dist/{chunk-GND3OBVE.js → chunk-U5VFJVQ2.js} +3 -3
- package/dist/{chunk-GND3OBVE.js.map → chunk-U5VFJVQ2.js.map} +1 -1
- package/dist/{chunk-7JBXDQTD.cjs → chunk-UKUHVCZX.cjs} +5 -5
- package/dist/{chunk-7JBXDQTD.cjs.map → chunk-UKUHVCZX.cjs.map} +1 -1
- package/dist/{chunk-M4UJBLVR.cjs → chunk-VPU6UY4O.cjs} +4 -4
- package/dist/{chunk-M4UJBLVR.cjs.map → chunk-VPU6UY4O.cjs.map} +1 -1
- package/dist/{chunk-CQ37UD4T.cjs → chunk-W6UMY7PF.cjs} +5 -5
- package/dist/{chunk-CQ37UD4T.cjs.map → chunk-W6UMY7PF.cjs.map} +1 -1
- package/dist/{chunk-GW3YIPDE.cjs → chunk-WZ2TBXVB.cjs} +18 -18
- package/dist/{chunk-GW3YIPDE.cjs.map → chunk-WZ2TBXVB.cjs.map} +1 -1
- package/dist/{chunk-XZCMUDFP.cjs → chunk-XS6OYYVR.cjs} +129 -36
- package/dist/chunk-XS6OYYVR.cjs.map +1 -0
- package/dist/{chunk-PJMXHDY5.js → chunk-XYJBRKBZ.js} +3 -3
- package/dist/{chunk-PJMXHDY5.js.map → chunk-XYJBRKBZ.js.map} +1 -1
- package/dist/components/ActivityPreview.cjs +17 -17
- package/dist/components/ActivityPreview.d.cts +2 -2
- package/dist/components/ActivityPreview.d.ts +2 -2
- package/dist/components/ActivityPreview.js +16 -16
- package/dist/components/ActivitySequence.cjs +18 -18
- package/dist/components/ActivitySequence.d.cts +2 -2
- package/dist/components/ActivitySequence.d.ts +2 -2
- package/dist/components/ActivitySequence.js +17 -17
- package/dist/components/Dictation.cjs +10 -10
- package/dist/components/Dictation.d.cts +1 -1
- package/dist/components/Dictation.d.ts +1 -1
- package/dist/components/Dictation.js +9 -9
- package/dist/components/FillInTheBlanks.cjs +10 -10
- package/dist/components/FillInTheBlanks.d.cts +1 -1
- package/dist/components/FillInTheBlanks.d.ts +1 -1
- package/dist/components/FillInTheBlanks.js +9 -9
- package/dist/components/GapSelect.cjs +10 -10
- package/dist/components/GapSelect.d.cts +1 -1
- package/dist/components/GapSelect.d.ts +1 -1
- package/dist/components/GapSelect.js +9 -9
- package/dist/components/InteractiveVideo.cjs +17 -17
- package/dist/components/InteractiveVideo.d.cts +2 -2
- package/dist/components/InteractiveVideo.d.ts +2 -2
- package/dist/components/InteractiveVideo.js +15 -15
- package/dist/components/MultipleChoice.cjs +10 -10
- package/dist/components/MultipleChoice.d.cts +1 -1
- package/dist/components/MultipleChoice.d.ts +1 -1
- package/dist/components/MultipleChoice.js +9 -9
- package/dist/components/PronunciationFeedback.cjs +5 -5
- package/dist/components/PronunciationFeedback.d.cts +1 -1
- package/dist/components/PronunciationFeedback.d.ts +1 -1
- package/dist/components/PronunciationFeedback.js +4 -4
- package/dist/components/ReadAloud.cjs +8 -8
- package/dist/components/ReadAloud.d.cts +2 -2
- package/dist/components/ReadAloud.d.ts +2 -2
- package/dist/components/ReadAloud.js +7 -7
- package/dist/components/StimulusPanel.cjs +4 -4
- package/dist/components/StimulusPanel.d.cts +1 -1
- package/dist/components/StimulusPanel.d.ts +1 -1
- package/dist/components/StimulusPanel.js +3 -3
- package/dist/components/WrittenResponse.cjs +9 -7
- package/dist/components/WrittenResponse.d.cts +3 -3
- package/dist/components/WrittenResponse.d.ts +3 -3
- package/dist/components/WrittenResponse.js +8 -6
- package/dist/i18n/LkIntlProvider.cjs +7 -7
- package/dist/i18n/LkIntlProvider.d.cts +2 -2
- package/dist/i18n/LkIntlProvider.d.ts +2 -2
- package/dist/i18n/LkIntlProvider.js +1 -1
- package/dist/index.cjs +45 -41
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +19 -19
- package/dist/theme/skin.css +23 -0
- package/dist/{types-Botf4Wqj.d.cts → types-CO0XLEN6.d.cts} +19 -0
- package/dist/{types-7lGlo0zE.d.ts → types-CrtIcRcK.d.ts} +19 -0
- package/package.json +3 -3
- package/dist/chunk-AY3WN4FB.js.map +0 -1
- package/dist/chunk-CB2A56Q7.cjs.map +0 -1
- package/dist/chunk-EANGKAKG.cjs.map +0 -1
- package/dist/chunk-EYLBSS4Y.js.map +0 -1
- package/dist/chunk-GHKMX5UT.cjs.map +0 -1
- package/dist/chunk-KRM6DX47.js.map +0 -1
- package/dist/chunk-M3K6BF5M.js.map +0 -1
- package/dist/chunk-MCTNLUKO.js.map +0 -1
- package/dist/chunk-MPHX3Y42.cjs.map +0 -1
- package/dist/chunk-NDIKHMLJ.js.map +0 -1
- package/dist/chunk-NILHU555.cjs.map +0 -1
- package/dist/chunk-OKEN6NJL.cjs.map +0 -1
- package/dist/chunk-PPFVTXGJ.js.map +0 -1
- package/dist/chunk-W2P5RILE.cjs.map +0 -1
- package/dist/chunk-W6TWQY6S.js.map +0 -1
- package/dist/chunk-WGHMWBEJ.cjs.map +0 -1
- package/dist/chunk-WNDNQ6NR.js.map +0 -1
- package/dist/chunk-WXESKL75.cjs.map +0 -1
- package/dist/chunk-XM5Q2ZCH.js.map +0 -1
- package/dist/chunk-XZCMUDFP.cjs.map +0 -1
- package/dist/chunk-Y4IDF5UO.cjs.map +0 -1
- package/dist/chunk-ZDDBWUUA.js.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
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"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/FillInTheBlanks/FillInTheBlanks.tsx","../src/components/FillInTheBlanks/index.tsx"],"names":["result","FillInTheBlanks","jsx"],"mappings":";;;;;;;;;;;;;AAmDA,IAAM,WAAA,GAAc,0BAAA;AAIpB,SAAS,aAAa,OAAA,EAA4B;AAChD,EAAA,MAAM,WAAsB,EAAC;AAC7B,EAAA,MAAM,EAAA,GAAK,IAAI,MAAA,CAAO,WAAW,CAAA;AACjC,EAAA,IAAI,IAAA,GAAO,CAAA;AACX,EAAA,IAAI,OAAA,GAAU,CAAA;AACd,EAAA,IAAI,KAAA,GAAQ,EAAA,CAAG,IAAA,CAAK,OAAO,CAAA;AAC3B,EAAA,OAAO,UAAU,IAAA,EAAM;AACrB,IAAA,IAAI,KAAA,CAAM,QAAQ,IAAA,EAAM;AACtB,MAAA,QAAA,CAAS,IAAA,CAAK,EAAE,IAAA,EAAM,MAAA,EAAQ,KAAA,EAAO,OAAA,CAAQ,KAAA,CAAM,IAAA,EAAM,KAAA,CAAM,KAAK,CAAA,EAAG,CAAA;AAAA,IACzE;AACA,IAAA,OAAA,IAAW,CAAA;AACX,IAAA,QAAA,CAAS,IAAA,CAAK,EAAE,IAAA,EAAM,OAAA,EAAS,IAAI,KAAA,CAAM,CAAC,CAAA,EAAa,OAAA,EAAS,CAAA;AAChE,IAAA,IAAA,GAAO,KAAA,CAAM,KAAA,GAAQ,KAAA,CAAM,CAAC,CAAA,CAAE,MAAA;AAC9B,IAAA,KAAA,GAAQ,EAAA,CAAG,KAAK,OAAO,CAAA;AAAA,EACzB;AACA,EAAA,IAAI,IAAA,GAAO,QAAQ,MAAA,EAAQ;AACzB,IAAA,QAAA,CAAS,IAAA,CAAK,EAAE,IAAA,EAAM,MAAA,EAAQ,OAAO,OAAA,CAAQ,KAAA,CAAM,IAAI,CAAA,EAAG,CAAA;AAAA,EAC5D;AACA,EAAA,OAAO,QAAA;AACT;AAGA,IAAM,aAAqC,EAAC;AAQ5C,SAAS,UAAU,QAAA,EAA+D;AAChF,EAAA,OAAO,aAAa,MAAA,IAAa,QAAA,CAAS,IAAA,KAAS,oBAAA,GAC/C,SAAS,OAAA,GACT,UAAA;AACN;AAQA,SAAS,kBAAkB,OAAA,EAAyD;AAClF,EAAA,MAAM,GAAA,uBAAU,GAAA,EAAqB;AACrC,EAAA,KAAA,MAAW,UAAU,OAAA,EAAS;AAC5B,IAAA,GAAA,CAAI,GAAA;AAAA,MACF,MAAA,CAAO,MAAA;AAAA,MACP,OAAO,OAAA,KAAY,MAAA,GAAY,MAAA,CAAO,OAAA,KAAY,YAAY,MAAA,CAAO;AAAA,KACvE;AAAA,EACF;AACA,EAAA,OAAO,GAAA;AACT;AAEO,SAAS,eAAA,CAAgB;AAAA,EAC9B,IAAA;AAAA,EACA,UAAA;AAAA,EACA,QAAA;AAAA,EACA,QAAA;AAAA,EACA,aAAA;AAAA,EACA,KAAA;AAAA,EACA,YAAA;AAAA,EACA,gBAAA;AAAA,EACA,UAAA,GAAa,UAAA;AAAA,EACb,OAAA;AAAA,EACA,YAAA;AAAA,EACA,WAAA;AAAA,EACA,YAAA;AAAA,EACA,OAAA;AAAA,EACA,KAAA;AAAA,EACA,MAAA;AAAA,EACA,QAAA;AAAA,EACA,kBAAA;AAAA,EACA,EAAA,EAAI,MAAA;AAAA,EACJ,QAAA;AAAA,EACA;AACF,CAAA,EAAyB;AACvB,EAAA,MAAM,SAAS,UAAA,KAAe,MAAA;AAC9B,EAAA,MAAM,WAAW,UAAA,KAAe,QAAA;AAIhC,EAAA,MAAM,CAAA,GAAI,aAAa,OAAO,CAAA;AAC9B,EAAA,MAAM,EAAA,GAAK,aAAa,MAAM,CAAA;AAC9B,EAAA,MAAM,MAAA,GAAS,kBAAkB,QAAQ,CAAA;AACzC,EAAA,MAAM,aAAA,GAAgB,qBAAqB,OAAO,CAAA;AAElD,EAAA,MAAM,QAAA,GAAW,QAAQ,MAAM;AAC7B,IAAA,IAAI,CAAC,eAAc,EAAG;AACpB,MAAA,OAAO,IAAA;AAAA,IACT;AACA,IAAA,IAAI,IAAA,CAAK,aAAa,IAAA,EAAM;AAI1B,MAAA,IAAI;AACF,QAAA,cAAA,CAAe,IAAI,CAAA;AACnB,QAAA,OAAO,IAAA;AAAA,MACT,SAAS,KAAA,EAAO;AACd,QAAA,OAAO,iBAAiB,KAAA,GAAQ,KAAA,GAAQ,IAAI,KAAA,CAAM,MAAA,CAAO,KAAK,CAAC,CAAA;AAAA,MACjE;AAAA,IACF;AACA,IAAA,MAAMA,OAAAA,GAAS,gBAAA,CAAiB,oBAAA,EAAsB,IAAI,CAAA;AAC1D,IAAA,OAAOA,QAAO,OAAA,GAAU,IAAA,GAAO,IAAI,mBAAA,CAAoB,oBAAA,EAAsBA,QAAO,MAAM,CAAA;AAAA,EAC5F,CAAA,EAAG,CAAC,IAAI,CAAC,CAAA;AAET,EAAA,MAAM,EAAE,KAAA,EAAO,KAAA,EAAO,QAAA,EAAU,YAAA,EAAc,OAAM,GAAI,gBAAA;AAAA,IACtD,gBAAA,KAAqB,OAAO,WAAA,GAAc;AAAA,GAC5C;AAEA,EAAA,MAAM,QAAQ,QAAA,CAAS;AAAA,IACrB,MAAA,EAAQ,aAAA;AAAA,IACR,MAAA,EAAQ,UAAA,KAAe,UAAA,IAAc,MAAA,CAAO,QAAA;AAAA,IAC5C,WAAW,KAAA,KAAU,WAAA;AAAA,IACrB,UAAU,QAAA,KAAa;AAAA,GACxB,CAAA;AACD,EAAA,MAAM,aAAa,KAAA,CAAM,KAAA;AAIzB,EAAA,MAAM,eAAe,KAAA,KAAU,MAAA;AAC/B,EAAA,MAAM,CAAC,eAAA,EAAiB,kBAAkB,CAAA,GAAI,QAAA;AAAA,IAAiC,MAC7E,UAAU,YAAY;AAAA,GACxB;AACA,EAAA,MAAM,CAAC,UAAU,WAAW,CAAA,GAAI,SAAsB,sBAAM,IAAI,KAAK,CAAA;AAGrE,EAAA,MAAM,CAAC,MAAM,OAAO,CAAA,GAAI,SAA8B,sBAAM,IAAI,KAAK,CAAA;AACrE,EAAA,MAAM,CAAC,MAAA,EAAQ,SAAS,CAAA,GAAI,SAA+B,IAAI,CAAA;AAC/D,EAAA,MAAM,CAAC,OAAA,EAAS,UAAU,CAAA,GAAI,SAAwB,IAAI,CAAA;AAC1D,EAAA,MAAM,CAAC,OAAA,EAAS,UAAU,CAAA,GAAI,SAAwB,IAAI,CAAA;AAC1D,EAAA,MAAM,CAAC,cAAA,EAAgB,iBAAiB,CAAA,GAAI,SAAS,KAAK,CAAA;AAG1D,EAAA,MAAM,YAAA,GAAe,MAAA,CAAO,OAAA,CAAQ,KAAA,IAAS,YAAY,CAAC,CAAA;AAC1D,EAAA,MAAM,OAAA,GAAU,OAAwB,IAAI,CAAA;AAC5C,EAAA,MAAM,WAAA,GAAc,OAAuB,IAAI,CAAA;AAC/C,EAAA,MAAM,aAAA,GAAgB,OAAqC,IAAI,CAAA;AAE/D,EAAA,MAAM,OAAA,GAAU,YAAA,GAAe,SAAA,CAAU,KAAK,CAAA,GAAI,eAAA;AAMlD,EAAA,MAAM,eAAA,GAAkB,OAAO,YAAY,CAAA;AAC3C,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,eAAA,CAAgB,OAAA,GAAU,YAAA;AAAA,EAC5B,CAAA,EAAG,CAAC,YAAY,CAAC,CAAA;AAMjB,EAAA,MAAM,mBAAA,GAAsB,OAAO,gBAAgB,CAAA;AACnD,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,mBAAA,CAAoB,OAAA,GAAU,gBAAA;AAAA,EAChC,CAAA,EAAG,CAAC,gBAAgB,CAAC,CAAA;AAQrB,EAAA,MAAM,WAAA,GAAc,OAAO,IAAI,CAAA;AAG/B,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,WAAA,CAAY,YAAY,IAAA,EAAM;AAChC,MAAA;AAAA,IACF;AACA,IAAA,WAAA,CAAY,OAAA,GAAU,IAAA;AACtB,IAAA,kBAAA,CAAmB,SAAA,CAAU,eAAA,CAAgB,OAAO,CAAC,CAAA;AACrD,IAAA,WAAA,iBAAY,IAAI,KAAK,CAAA;AACrB,IAAA,OAAA,iBAAQ,IAAI,KAAK,CAAA;AACjB,IAAA,SAAA,CAAU,IAAI,CAAA;AACd,IAAA,UAAA,CAAW,IAAI,CAAA;AACf,IAAA,UAAA,CAAW,IAAI,CAAA;AACf,IAAA,iBAAA,CAAkB,KAAK,CAAA;AACvB,IAAA,YAAA,CAAa,OAAA,GAAU,OAAA,CAAQ,eAAA,CAAgB,OAAO,CAAA;AACtD,IAAA,UAAA,EAAW;AACX,IAAA,KAAA,CAAM,mBAAA,CAAoB,OAAA,KAAY,IAAA,GAAO,WAAA,GAAc,MAAM,CAAA;AAAA,EACnE,CAAA,EAAG,CAAC,IAAA,EAAM,KAAA,EAAO,UAAU,CAAC,CAAA;AAiC5B,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,eAAc,IAAK,YAAA,KAAiB,MAAA,IAAa,IAAA,CAAK,gBAAgB,MAAA,EAAW;AACnF,MAAA,OAAA,CAAQ,IAAA;AAAA,QACN;AAAA,OAGF;AAAA,IACF;AAAA,EACF,CAAA,EAAG,CAAC,YAAA,EAAc,IAAA,CAAK,WAAW,CAAC,CAAA;AAEnC,EAAA,MAAM,QAAA,GAAW,OAAA,CAAQ,MAAM,YAAA,CAAa,IAAA,CAAK,OAAO,CAAA,EAAG,CAAC,IAAA,CAAK,OAAO,CAAC,CAAA;AACzE,EAAA,MAAM,SAAA,GAAY,QAAQ,MAAM,IAAI,IAAI,IAAA,CAAK,MAAA,CAAO,IAAI,CAAC,CAAA,KAAM,CAAC,CAAA,CAAE,EAAA,EAAI,CAAC,CAAC,CAAC,GAAG,CAAC,IAAA,CAAK,MAAM,CAAC,CAAA;AAGzF,EAAA,MAAM,WAAW,KAAA,KAAU,WAAA;AAC3B,EAAA,MAAM,UAAU,mBAAA,CAAoB;AAAA,IAClC,EAAA;AAAA,IACA,IAAA;AAAA,IACA,UAAA;AAAA,IACA,SAAA,EAAW,QAAA;AAAA,IACX,UAAU,QAAA,KAAa,IAAA;AAAA,IACvB,QAAA,EAAU,EAAE,IAAA,EAAM,oBAAA,EAAsB,OAAA,EAAQ;AAAA,IAChD,MAAA;AAAA,IACA,aAAA;AAAA,IACA,OAAA,EAAS,CAAA;AAAA,IACT,QAAA,EAAU;AAAA,GACX,CAAA;AAID,EAAA,MAAM,aAAA,GACJ,eAAe,UAAA,GAAa,YAAA,CAAa,UAAU,IAAA,CAAK,IAAA,GAAO,QAAQ,IAAA,GAAO,CAAA;AAChF,EAAA,MAAM,UAAA,GAAa,CACjB,IAAA,EACA,KAAA,GAAgB,aAAA,MACoB;AAAA,IACpC,IAAA,EAAM,oBAAA;AAAA,IACN,OAAA,EAAS,IAAA;AAAA,IACT,GAAI,KAAA,GAAQ,CAAA,GAAI,EAAE,aAAA,EAAe,KAAA,KAAU;AAAC,GAC9C,CAAA;AAEA,EAAA,MAAM,SAAA,GAAY,MAAA,CAAO,OAAA,CAAQ,IAAI,CAAA;AAErC,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,SAAA,CAAU,OAAA,KAAY,OAAA,CAAQ,IAAA,EAAM;AACtC,MAAA;AAAA,IACF;AACA,IAAA,SAAA,CAAU,UAAU,OAAA,CAAQ,IAAA;AAC5B,IAAA,IAAI,OAAA,CAAQ,OAAO,CAAA,EAAG;AACpB,MAAA,QAAA,GAAW,UAAA,CAAW,OAAO,CAAC,CAAA;AAAA,IAChC;AAAA,EACF,CAAA,EAAG,CAAC,OAAA,CAAQ,IAAI,CAAC,CAAA;AACjB,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,MAAM,SAAS,aAAA,CAAc,OAAA;AAC7B,IAAA,IAAI,WAAW,IAAA,EAAM;AACnB,MAAA;AAAA,IACF;AACA,IAAA,aAAA,CAAc,OAAA,GAAU,IAAA;AACxB,IAAA,IAAI,WAAW,QAAA,EAAU;AACvB,MAAA,OAAA,CAAQ,OAAA,EAAS,aAAA,CAA2B,sBAAsB,CAAA,EAAG,KAAA,EAAM;AAAA,IAC7E,CAAA,MAAO;AACL,MAAA,WAAA,CAAY,SAAS,KAAA,EAAM;AAAA,IAC7B;AAAA,EACF,CAAC,CAAA;AAED,EAAA,IAAI,QAAA,EAAU;AACZ,IAAA,MAAM,QAAA;AAAA,EACR;AASA,EAAA,IAAI,IAAA,CAAK,QAAA,KAAa,IAAA,IAAQ,UAAA,KAAe,UAAA,EAAY;AACvD,IAAA,MAAM,IAAI,KAAA;AAAA,MACR,CAAA,oBAAA,EAAuB,KAAK,EAAE,CAAA,oNAAA;AAAA,KAGhC;AAAA,EACF;AAEA,EAAA,MAAM,YAAY,KAAA,KAAU,WAAA;AAC5B,EAAA,MAAM,QAAA,GAAW,QAAA,KAAa,IAAA,IAAQ,SAAA,IAAa,QAAA;AASnD,EAAA,MAAM,YAAY,QAAA,GAAW,OAAA,EAAS,MAAA,KAAW,QAAA,GAAW,CAAC,MAAA,IAAU,SAAA;AAIvE,EAAA,MAAM,OAAA,GAAU,aAAa,MAAA,CAAO,QAAA;AAEpC,EAAA,MAAM,iBAAiB,OAAA,IAAW,MAAA,CAAO,aAAa,kBAAA,KAAuB,IAAA,IAAQ,CAAC,KAAA,CAAM,IAAA;AAE5F,EAAA,MAAM,eAAA,GAAkB,CACtB,IAAA,EACA,OAAA,KACS;AACT,IAAA,aAAA,GAAgB,EAAE,IAAA,EAAM,UAAA,EAAY,IAAA,CAAK,EAAA,EAAI,WAAW,IAAA,CAAK,GAAA,EAAI,EAAG,OAAA,EAAS,CAAA;AAAA,EAC/E,CAAA;AAEA,EAAA,MAAM,YAAA,GAAe,CAAC,OAAA,EAAiB,IAAA,KAAuB;AAC5D,IAAA,IAAI,QAAA,EAAU;AACZ,MAAA;AAAA,IACF;AACA,IAAA,IAAI,UAAU,MAAA,EAAQ;AACpB,MAAA,KAAA,EAAM;AAAA,IACR;AACA,IAAA,MAAM,OAAO,EAAE,GAAG,SAAS,CAAC,OAAO,GAAG,IAAA,EAAK;AAC3C,IAAA,IAAI,CAAC,YAAA,EAAc;AACjB,MAAA,kBAAA,CAAmB,IAAI,CAAA;AAAA,IACzB;AACA,IAAA,QAAA,GAAW,UAAA,CAAW,IAAI,CAAC,CAAA;AAC3B,IAAA,eAAA,CAAgB,cAAA,EAAgB,EAAE,OAAA,EAAS,KAAA,EAAO,MAAM,CAAA;AAAA,EAC1D,CAAA;AAEA,EAAA,MAAM,UAAA,GAAa,CAAC,OAAA,KAA0B;AAC5C,IAAA,IAAI,QAAA,EAAU;AACZ,MAAA;AAAA,IACF;AACA,IAAA,IAAI,UAAU,MAAA,EAAQ;AACpB,MAAA,KAAA,EAAM;AAAA,IACR;AACA,IAAA,WAAA,CAAY,CAAC,IAAA,KAAS;AACpB,MAAA,MAAM,IAAA,GAAO,IAAI,GAAA,CAAI,IAAI,CAAA;AACzB,MAAA,IAAI,IAAA,CAAK,GAAA,CAAI,OAAO,CAAA,EAAG;AACrB,QAAA,IAAA,CAAK,OAAO,OAAO,CAAA;AACnB,QAAA,OAAO,IAAA;AAAA,MACT;AACA,MAAA,IAAA,CAAK,IAAI,OAAO,CAAA;AAChB,MAAA,OAAO,IAAA;AAAA,IACT,CAAC,CAAA;AAED,IAAA,IAAI,CAAC,QAAA,CAAS,GAAA,CAAI,OAAO,CAAA,EAAG;AAC1B,MAAA,eAAA,CAAgB,gBAAA,EAAkB,EAAE,OAAA,EAAS,CAAA;AAAA,IAC/C;AAEA,IAAA,IAAI,CAAC,IAAA,CAAK,GAAA,CAAI,OAAO,CAAA,EAAG;AACtB,MAAA,OAAA,CAAQ,CAAC,SAAS,IAAI,GAAA,CAAI,IAAI,CAAA,CAAE,GAAA,CAAI,OAAO,CAAC,CAAA;AAC5C,MAAA,IAAI,eAAe,UAAA,EAAY;AAC7B,QAAA,QAAA,GAAW,UAAA,CAAW,OAAA,EAAS,aAAA,GAAgB,CAAC,CAAC,CAAA;AAAA,MACnD;AAAA,IACF;AAAA,EACF,CAAA;AAEA,EAAA,MAAM,YAAA,GAAe,CAAC,KAAA,KAAiC;AACrD,IAAA,KAAA,CAAM,cAAA,EAAe;AACrB,IAAA,IAAI,QAAA,EAAU;AACZ,MAAA;AAAA,IACF;AACA,IAAA,MAAM,QAAA,GAAW,WAAW,OAAO,CAAA;AAGnC,IAAA,QAAA,GAAW,QAAQ,CAAA;AAEnB,IAAA,IAAI,MAAA,EAAQ;AAKV,MAAA,QAAA,EAAS;AACT,MAAA,UAAA,CAAW,EAAE,eAAe,CAAA;AAC5B,MAAA,eAAA,CAAgB,WAAA,EAAa,EAAE,OAAA,EAAS,CAAA;AACxC,MAAA;AAAA,IACF;AAIA,IAAA,MAAM,MAAA,GAAS,KAAA,CAAM,oBAAA,EAAsB,IAAA,EAA6B,QAAQ,CAAA;AAChF,IAAA,QAAA,EAAS;AACT,IAAA,MAAM,YAAY,YAAA,EAAa;AAE/B,IAAA,MAAM,OAAA,GAAU,MAAM,MAAA,CAAO;AAAA,MAC3B,OAAO,MAAA,CAAO,KAAA;AAAA,MACd,UAAU,MAAA,CAAO,QAAA;AAAA,MACjB;AAAA,KACD,CAAA;AACD,IAAA,MAAM,OAAO,OAAA,CAAQ,KAAA,CAAM,OAAA,CAAQ,KAAA,CAAM,SAAS,CAAC,CAAA;AACnD,IAAA,MAAM,MAAA,GAAS,IAAA,KAAS,MAAA,GAAY,MAAA,CAAO,QAAQ,IAAA,CAAK,MAAA;AACxD,IAAA,MAAM,aAAA,GACJ,MAAA,KAAW,MAAA,CAAO,KAAA,GACd,MAAA,GACA;AAAA,MACE,GAAG,MAAA;AAAA,MACH,KAAA,EAAO,MAAA;AAAA,MACP,MAAA,EAAQ,oBAAA,CAAqB,IAAA,EAA6B,MAAM;AAAA,KAClE;AACN,IAAA,MAAM,MAAA,GACJ,OAAA,CAAQ,KAAA,KAAU,aAAA,CAAc,KAAA,GAC5B,cAAc,MAAA,GACd,oBAAA,CAAqB,IAAA,EAA6B,OAAA,CAAQ,KAAK,CAAA;AACrE,IAAA,MAAM,aAAA,GAAgB,YAAY,sBAAA,CAAuB;AAAA,MACvD,KAAA,EAAO,eAAA;AAAA,MACP,MAAA,EAAQ;AAAA,QACN,EAAA,EAAI,WAAA,CAAY,IAAA,CAAK,EAAE,CAAA;AAAA,QACvB,IAAA,EAAM,EAAE,CAAC,IAAA,CAAK,UAAU,OAAO,GAAG,KAAK,KAAA,EAAM;AAAA,QAC7C,GAAG,kBAAkB,IAAI;AAAA,OAC3B;AAAA,MACA,aAAA;AAAA,MACA,WAAA,EAAa,SAAA;AAAA,MACb,QAAA,EAAU,IAAA,CAAK,SAAA,CAAU,OAAO;AAAA,KACjC,CAAA;AACD,IAAA,SAAA,CAAU,MAAM,CAAA;AAChB,IAAA,UAAA;AAAA,MACE,SAAA;AAAA,QACE,EAAE,OAAO,OAAA,CAAQ,KAAA,EAAO,UAAU,OAAA,CAAQ,QAAA,EAAU,MAAA,EAAQ,SAAA,EAAW,aAAA,EAAc;AAAA,QACrF,QAAA;AAAS;AACX,KACF;AAEA,IAAA,UAAA,CAAW,OAAO,QAAQ,CAAA;AAC1B,IAAA,UAAA;AAAA,MACE,CAAA,EAAG,CAAA,CAAE,eAAe,CAAA,CAAA,EAAI,CAAA,CAAE,iBAAA,CAAkB,IAAA,CAAK,KAAA,CAAM,OAAA,CAAQ,KAAA,GAAQ,GAAG,CAAA,EAAG,MAAM,CAAC,CAAA;AAAA,KACtF;AACA,IAAA,eAAA,CAAgB,aAAa,EAAE,OAAA,EAAS,KAAA,EAAO,aAAA,CAAc,OAAO,CAAA;AAAA,EACtE,CAAA;AAGA,EAAA,MAAM,QAAQ,MAAY;AACxB,IAAA,KAAA,CAAM,MAAM,CAAA;AACZ,IAAA,UAAA,CAAW,IAAI,CAAA;AACf,IAAA,UAAA,CAAW,IAAI,CAAA;AACf,IAAA,aAAA,CAAc,OAAA,GAAU,QAAA;AAAA,EAC1B,CAAA;AAGA,EAAA,MAAM,aAAa,MAAY;AAC7B,IAAA,KAAA,CAAM,KAAA,EAAM;AACZ,IAAA,aAAA,CAAc,OAAA,GAAU,UAAA;AAAA,EAC1B,CAAA;AAEA,EAAA,MAAM,cAAA,GAAiB,QAAQ,MAAM;AACnC,IAAA,IAAI,MAAA,EAAQ;AAEV,MAAA,2BAAW,GAAA,EAAqB;AAAA,IAClC;AACA,IAAA,IAAI,QAAA,EAAU;AAGZ,MAAA,OAAO,OAAA,EAAS,WAAW,QAAA,GACvB,iBAAA,CAAkB,QAAQ,OAAO,CAAA,uBAC7B,GAAA,EAAqB;AAAA,IAC/B;AACA,IAAA,OAAO,iBAAA,CAAkB,MAAA,EAAQ,OAAA,IAAW,EAAE,CAAA;AAAA,EAChD,GAAG,CAAC,MAAA,EAAQ,QAAA,EAAU,OAAA,EAAS,MAAM,CAAC,CAAA;AAGtC,EAAA,MAAM,aAAA,GAAgB,QAAQ,MAAM;AAClC,IAAA,IAAI,CAAC,QAAA,IAAY,OAAA,KAAY,MAAA,EAAW;AACtC,MAAA,OAAO,IAAA;AAAA,IACT;AACA,IAAA,IAAI,OAAA,CAAQ,WAAW,QAAA,EAAU;AAC/B,MAAA,OAAO,GAAG,CAAA,CAAE,iBAAA,CAAkB,KAAK,KAAA,CAAM,OAAA,CAAQ,QAAQ,GAAG,CAAA,EAAG,QAAQ,MAAM,CAAC,GAC5E,OAAA,CAAQ,QAAA,GAAW,IAAI,OAAA,CAAQ,QAAQ,KAAK,EAC9C,CAAA,CAAA;AAAA,IACF;AACA,IAAA,IAAI,OAAA,CAAQ,WAAW,UAAA,EAAY;AACjC,MAAA,OAAO,CAAA,CAAE,YAAA;AAAA,IACX;AAKA,IAAA,IAAI,OAAA,CAAQ,WAAW,QAAA,EAAU;AAC/B,MAAA,MAAM,UACJ,OAAA,CAAQ,QAAA,GAAW,CAAA,GACf,IAAA,CAAK,MAAO,OAAA,CAAQ,KAAA,GAAQ,OAAA,CAAQ,QAAA,GAAY,GAAG,CAAA,GACnD,IAAA,CAAK,KAAA,CAAM,OAAA,CAAQ,QAAQ,GAAG,CAAA;AACpC,MAAA,OAAO,CAAA,EAAG,CAAA,CAAE,iBAAA,CAAkB,OAAA,EAAS,QAAQ,MAAM,CAAC,CAAA,EACpD,OAAA,CAAQ,QAAA,GAAW,CAAA,CAAA,EAAI,OAAA,CAAQ,QAAQ,KAAK,EAC9C,CAAA,CAAA;AAAA,IACF;AACA,IAAA,OAAO,CAAA,CAAE,gBAAA;AAAA,EACX,CAAA,EAAG,CAAC,QAAA,EAAU,OAAA,EAAS,CAAC,CAAC,CAAA;AAEzB,EAAA,MAAM,QACJ,CAAC,QAAA,IAAY,MAAA,CAAO,QAAA,IAAY,aAAa,KAAA,CAAM,OAAA,KAAY,IAAA,GAC3D,YAAA,CAAa,GAAG,aAAA,EAAe,KAAA,CAAM,OAAA,EAAS,KAAA,CAAM,IAAI,CAAA,GACxD,EAAA;AACN,EAAA,MAAM,YACJ,OAAA,KAAY,IAAA,GACR,IAAA,GACA,CAAA,EAAG,OAAO,CAAA,EAAG,KAAA,GAAQ,CAAA,CAAA,EAAI,KAAK,KAAK,EAAE,CAAA,EAAG,UAAU,CAAA,CAAA,EAAI,OAAO,KAAK,EAAE,CAAA,CAAA;AAC1E,EAAA,MAAM,YAAA,GACJ,UAAA,KAAe,UAAA,IACf,CAAC,cACA,OAAA,CAAQ,OAAA,IAAY,MAAA,CAAO,KAAA,IAAS,KAAK,MAAA,CAAO,IAAA,CAAK,CAAC,KAAA,KAAU,MAAM,IAAI,CAAA,CAAA;AAE7E,EAAA,uBACE,IAAA;AAAA,IAAC,MAAA;AAAA,IAAA;AAAA,MACC,GAAA,EAAK,OAAA;AAAA,MACL,SAAA,EAAU,QAAA;AAAA,MACV,cAAY,IAAA,CAAK,KAAA;AAAA,MACjB,IAAA,EAAM,MAAA;AAAA,MACN,kBAAA,EAAkB,UAAA;AAAA,MAClB,KAAA,EAAO,KAAA;AAAA,MACP,QAAA,EAAU,YAAA;AAAA,MAET,QAAA,EAAA;AAAA,QAAA,IAAA,CAAK,KAAA,mBACJ,GAAA;AAAA,UAAC,aAAA;AAAA,UAAA;AAAA,YACC,OAAO,IAAA,CAAK,KAAA;AAAA,YACZ,UAAA;AAAA,YACC,GAAI,WAAA,KAAgB,MAAA,GAAY,EAAE,WAAA,KAAgB,EAAC;AAAA,YACnD,GAAI,YAAA,KAAiB,MAAA,GAAY,EAAE,YAAA,KAAiB,EAAC;AAAA,YACrD,GAAI,OAAA,KAAY,MAAA,GAAY,EAAE,OAAA,KAAY,EAAC;AAAA,YAC3C,GAAI,aAAA,KAAkB,MAAA,GAAY,EAAE,aAAA,KAAkB,EAAC;AAAA,YACvD,GAAI,MAAA,KAAW,MAAA,GAAY,EAAE,MAAA,KAAW;AAAC;AAAA,SAC5C,GACE,IAAA;AAAA,wBACJ,IAAA,CAAC,UAAA,EAAA,EAAS,QAAA,EAAU,QAAA,EAClB,QAAA,EAAA;AAAA,0BAAA,GAAA,CAAC,OAAE,SAAA,EAAU,gBAAA,EACV,mBAAS,GAAA,CAAI,CAAC,KAAK,CAAA,KAAM;AACxB,YAAA,IAAI,GAAA,CAAI,SAAS,MAAA,EAAQ;AAEvB,cAAA,2BAAQ,MAAA,EAAA,EAAoB,QAAA,EAAA,GAAA,CAAI,KAAA,EAAA,EAAd,CAAA,CAAA,EAAI,CAAC,CAAA,CAAe,CAAA;AAAA,YACxC;AACA,YAAA,MAAM,KAAA,GAAQ,SAAA,CAAU,GAAA,CAAI,GAAA,CAAI,EAAE,CAAA;AAClC,YAAA,IAAI,CAAC,KAAA,EAAO;AACV,cAAA,OAAO,IAAA;AAAA,YACT;AAKA,YAAA,MAAM,eAAA,GAAwC,MAAA,GAC1C,MAAA,GACA,KAAA,CAAM,eAAA;AACV,YAAA,MAAM,aAAA,GAAgB,MAAA,GAAS,MAAA,GAAY,KAAA,CAAM,QAAA;AACjD,YAAA,MAAM,WAAA,GAAc,cAAA,CAAe,GAAA,CAAI,GAAA,CAAI,EAAE,CAAA;AAC7C,YAAA,MAAM,SAAS,CAAA,EAAG,IAAA,CAAK,EAAE,CAAA,MAAA,EAAS,IAAI,EAAE,CAAA,CAAA;AAGxC,YAAA,MAAM,IAAA,GAAO,MAAA,CAAO,KAAA,GAAQ,KAAA,CAAM,IAAA,GAAO,MAAA;AACzC,YAAA,MAAM,aAAA,GAAgB,kBAAkB,CAAC,CAAA;AACzC,YAAA,IAAI,cAAA,IAAkB,WAAA,KAAgB,KAAA,IAAS,aAAA,EAAe;AAC5D,cAAA,uBACE,GAAA,CAAC,UAAkB,SAAA,EAAU,eAAA,EAAgB,gBAAa,OAAA,EACvD,QAAA,EAAA,aAAA,EAAA,EADQ,IAAI,EAEf,CAAA;AAAA,YAEJ;AACA,YAAA,uBACE,IAAA,CAAC,MAAA,EAAA,EAAkB,SAAA,EAAU,cAAA,EAC3B,QAAA,EAAA;AAAA,8BAAA,GAAA;AAAA,gBAAC,OAAA;AAAA,gBAAA;AAAA,kBACC,IAAA,EAAK,MAAA;AAAA,kBACL,YAAA,EAAY,CAAA,CAAE,UAAA,CAAW,GAAA,CAAI,OAAO,CAAA;AAAA,kBACpC,kBAAA,EAAkB,OAAO,MAAA,GAAS,MAAA;AAAA,kBAClC,KAAA,EAAO,OAAA,CAAQ,GAAA,CAAI,EAAE,CAAA,IAAK,EAAA;AAAA,kBAC1B,QAAA,EAAU,QAAA;AAAA,kBACV,iBAAe,QAAA,IAAY,MAAA;AAAA,kBAC3B,gBACE,OAAA,IAAW,WAAA,KAAgB,MAAA,GAAY,MAAA,CAAO,WAAW,CAAA,GAAI,MAAA;AAAA,kBAE/D,QAAA,EAAU,CAAC,CAAA,KAAM,YAAA,CAAa,IAAI,EAAA,EAAI,CAAA,CAAE,OAAO,KAAK;AAAA;AAAA,eACtD;AAAA,cAMC,uBACC,IAAA,CAAA,QAAA,EAAA,EAME,QAAA,EAAA;AAAA,gCAAA,GAAA;AAAA,kBAAC,QAAA;AAAA,kBAAA;AAAA,oBACC,IAAA,EAAK,QAAA;AAAA,oBACL,SAAA,EAAU,iBAAA;AAAA,oBACV,eAAA,EAAe,MAAA;AAAA,oBACf,eAAA,EAAe,QAAA,CAAS,GAAA,CAAI,GAAA,CAAI,EAAE,CAAA;AAAA,oBAClC,YAAA,EAAY,SAAS,GAAA,CAAI,GAAA,CAAI,EAAE,CAAA,GAAI,CAAA,CAAE,WAAW,CAAA,CAAE,QAAA;AAAA,oBAClD,QAAA,EAAU,QAAA;AAAA,oBACV,OAAA,EAAS,MAAM,UAAA,CAAW,GAAA,CAAI,EAAE,CAAA;AAAA,oBAEhC,QAAA,kBAAA,GAAA;AAAA,sBAAC,KAAA;AAAA,sBAAA;AAAA,wBACC,SAAA,EAAU,kBAAA;AAAA,wBACV,OAAA,EAAQ,WAAA;AAAA,wBACR,KAAA,EAAM,IAAA;AAAA,wBACN,MAAA,EAAO,IAAA;AAAA,wBACP,aAAA,EAAY,MAAA;AAAA,wBACZ,SAAA,EAAU,OAAA;AAAA,wBAEV,QAAA,kBAAA,GAAA;AAAA,0BAAC,MAAA;AAAA,0BAAA;AAAA,4BACC,IAAA,EAAK,cAAA;AAAA,4BACL,CAAA,EAAE;AAAA;AAAA;AACJ;AAAA;AACF;AAAA,iBACF;AAAA,gCASA,GAAA,CAAC,MAAA,EAAA,EAAK,EAAA,EAAI,MAAA,EAAQ,WAAA,EAAU,QAAA,EACzB,QAAA,EAAA,QAAA,CAAS,GAAA,CAAI,GAAA,CAAI,EAAE,CAAA,GAAI,IAAA,GAAO,EAAA,EACjC;AAAA,eAAA,EACF,CAAA,GACE,IAAA;AAAA,cACH,OAAA,IAAW,CAAC,cAAA,IAAkB,aAAA,mBAC7B,GAAA;AAAA,gBAAC,MAAA;AAAA,gBAAA;AAAA,kBACC,SAAA,EAAU,uBAAA;AAAA,kBACV,IAAA,EAAK,MAAA;AAAA,kBACL,cAAA,EAAc,WAAA,KAAgB,MAAA,GAAY,MAAA,CAAO,WAAW,CAAA,GAAI,MAAA;AAAA,kBAE/D,QAAA,EAAA;AAAA;AAAA,eACH,GACE;AAAA,aAAA,EAAA,EArEK,IAAI,EAsEf,CAAA;AAAA,UAEJ,CAAC,CAAA,EACH,CAAA;AAAA,UACC,+BAAe,GAAA,CAAC,QAAA,EAAA,EAAS,QAAQ,aAAA,EAAe,OAAA,EAAS,GAAG,CAAA,GAAK,IAAA;AAAA,8BACjE,OAAA,EAAA,EAAQ,IAAA,EAAM,OAAA,EAAS,EAAA,EAAQ,SAAS,CAAA,EAAG,CAAA;AAAA,UAE3C,QAAA,GAAW,IAAA,mBACV,GAAA,CAAC,QAAA,EAAA,EAAO,IAAA,EAAK,QAAA,EAAS,QAAA,EAAU,QAAA,EAC7B,QAAA,EAAA,MAAA,GAAS,CAAA,CAAE,aAAA,GAAgB,CAAA,CAAE,YAAA,EAChC;AAAA,SAAA,EAEJ,CAAA;AAAA,QACC,OAAA,IAAW,KAAK,MAAA,CAAO,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,QAAQ,CAAA,mBAC5C,GAAA;AAAA,UAAC,QAAA;AAAA,UAAA;AAAA,YACC,IAAA,EAAK,QAAA;AAAA,YACL,SAAA,EAAU,wBAAA;AAAA,YACV,iBAAe,CAAC,cAAA;AAAA,YAChB,SAAS,MAAM,iBAAA,CAAkB,CAAC,CAAA,KAAM,CAAC,CAAC,CAAA;AAAA,YAEzC,QAAA,EAAA,cAAA,GAAiB,CAAA,CAAE,YAAA,GAAe,CAAA,CAAE;AAAA;AAAA,SACvC,GACE,IAAA;AAAA,wBACJ,GAAA;AAAA,UAAC,cAAA;AAAA,UAAA;AAAA,YACC,EAAA,EAAI,CAAA,EAAG,IAAA,CAAK,EAAE,CAAA,SAAA,CAAA;AAAA,YACb,GAAI,cAAc,OAAA,GAAU,CAAA,GAAI,EAAE,GAAA,EAAK,WAAA,KAAgB,EAAC;AAAA,YAExD,QAAA,EAAA,QAAA,GACG,MAAA,CAAO,QAAA,IAAY,CAAC,kBAAkB,OAAO,CAAA,GAC3C,aAAA,GACA,IAAA,GACF,MAAA,CAAO,QAAA,IAAY,OAAA,KAAY,IAAA,GAC7B,YACA,CAAA,CAAE;AAAA;AAAA,SACV;AAAA,wBACA,GAAA;AAAA,UAAC,UAAA;AAAA,UAAA;AAAA,YACC,KAAA;AAAA,YACA,SAAA,EAAW,MAAA,CAAO,SAAA,IAAa,kBAAA,KAAuB,IAAA;AAAA,YACtD,OAAA,EAAS,CAAA;AAAA,YACT,OAAA,EAAS,KAAA;AAAA,YACT,OAAA,EAAS;AAAA;AAAA,SACX;AAAA,wBACA,GAAA;AAAA,UAAC,aAAA;AAAA,UAAA;AAAA,YACC,EAAA;AAAA,YACA,IAAA;AAAA,YACA,UAAA;AAAA,YACA,SAAA;AAAA,YACA,QAAA,EAAU,EAAE,IAAA,EAAM,oBAAA,EAAsB,OAAA,EAAQ;AAAA,YAChD,OAAA;AAAA,YACA,MAAA;AAAA,YACA,aAAA;AAAA,YACA,OAAA,EAAS,CAAA;AAAA,YAGT,QAAA,EAAU,MAAM,IAAA,GAAO,EAAE,GAAG,MAAA,EAAQ,SAAA,EAAW,OAAM,GAAI;AAAA;AAAA;AAC3D;AAAA;AAAA,GACF;AAEJ;AC5tBO,SAASC,iBAAgB,KAAA,EAA6B;AAG3D,EAAA,MAAM,OAAA,GAAU,YAAA,CAAa,KAAA,CAAM,OAAO,CAAA;AAC1C,EAAA,uBACEC,GAAAA,CAAC,qBAAA,EAAA,EAAsB,aAAA,EAAe,KAAA,CAAM,IAAA,EAAM,KAAA,EAAO,OAAA,EACvD,QAAA,kBAAAA,GAAAA,CAAC,eAAA,EAAA,EAAqB,GAAG,OAAO,CAAA,EAClC,CAAA;AAEJ","file":"chunk-KRM6DX47.js","sourcesContent":["'use client';\n\nimport {\n ActivitySchemaError,\n assertRedacted,\n computePassThreshold,\n type FillInTheBlanksData,\n type FillInTheBlanksLearnerResponse,\n type LearnerResponse,\n type ScoringDetail,\n type ScoringResult,\n score,\n validateActivity,\n xAPIBuilder,\n xapiDefinitionFor,\n} from '@intellectif/lk-core';\nimport { type CSSProperties, useEffect, useMemo, useRef, useState } from 'react';\nimport { useLearnerAi } from '../../ai/LkAiProvider.js';\nimport { useActivityState } from '../../hooks/useActivityState.js';\nimport { useLkStrings } from '../../i18n/LkIntlProvider.js';\nimport { ANONYMOUS_ACTOR, isDevelopment, objectIdFor } from '../_internal.js';\nimport { ActivityMedia } from '../shared/ActivityMedia.js';\nimport { AiExplanation, AiHints, useComponentAiHints } from '../shared/AiHelp.js';\nimport { outcomeShowsMarks, useDeliveryPolicy } from '../shared/delivery.js';\nimport { FeedbackRegion } from '../shared/FeedbackRegion.js';\nimport { mintTake, stampTake } from '../shared/sequence-slot.js';\nimport {\n HintCost,\n hintsOf,\n TryActions,\n triesSummary,\n useItemScoringPolicy,\n useTries,\n} from '../shared/tries.js';\nimport type { ActivityProps } from '../types.js';\n\nexport interface FillInTheBlanksProps extends ActivityProps<FillInTheBlanksData> {\n /**\n * After submission, replace each incorrectly-answered blank with its first\n * accepted answer, styled distinctly (Req 5.7). Component-specific prop: it\n * is presentation, not activity data, and is outside the fixed Req-3.1 set.\n *\n * Ignored in `exam` mode — revealing the key is precisely what an exam must\n * not do, and on a `redact()` projection there is no key to reveal. In\n * `review` mode it applies only to blanks the supplied `outcome` marks\n * incorrect, and only when the caller passed data that still carries the key\n * (e.g. `redact(data, { reveal: 'after-submit' })`).\n */\n showCorrectAnswers?: boolean;\n}\n\nconst PLACEHOLDER = /\\{\\{\\s*([^{}]+?)\\s*\\}\\}/g;\n\ntype Segment = { kind: 'text'; value: string } | { kind: 'blank'; id: string; ordinal: number };\n\nfunction parsePassage(passage: string): Segment[] {\n const segments: Segment[] = [];\n const re = new RegExp(PLACEHOLDER);\n let last = 0;\n let ordinal = 0;\n let match = re.exec(passage);\n while (match !== null) {\n if (match.index > last) {\n segments.push({ kind: 'text', value: passage.slice(last, match.index) });\n }\n ordinal += 1;\n segments.push({ kind: 'blank', id: match[1] as string, ordinal });\n last = match.index + match[0].length;\n match = re.exec(passage);\n }\n if (last < passage.length) {\n segments.push({ kind: 'text', value: passage.slice(last) });\n }\n return segments;\n}\n\n/** Shared empty map: keeps the \"no response supplied\" path allocation-free. */\nconst NO_ANSWERS: Record<string, string> = {};\n\n/**\n * Reads the answer map out of a `LearnerResponse`. A response of another\n * activity type is treated as \"no answers\" rather than throwing: a controlled\n * host that has not yet swapped its state on an activity change would\n * otherwise crash the attempt.\n */\nfunction answersOf(response: LearnerResponse | undefined): Record<string, string> {\n return response !== undefined && response.type === 'fill-in-the-blanks'\n ? response.answers\n : NO_ANSWERS;\n}\n\n/**\n * Per-item correctness keyed by blank id. Prefers the unambiguous `outcome`\n * field (always written by the built-in scorers since lk-core 0.3.0) and\n * falls back to the deprecated `correct` boolean so a 0.2-era\n * consumer-constructed `ItemOutcome` still marks correctly in `review`.\n */\nfunction correctnessByItem(details: readonly ScoringDetail[]): Map<string, boolean> {\n const map = new Map<string, boolean>();\n for (const detail of details) {\n map.set(\n detail.itemId,\n detail.outcome !== undefined ? detail.outcome === 'correct' : detail.correct,\n );\n }\n return map;\n}\n\nexport function FillInTheBlanks({\n data,\n onComplete,\n onSubmit,\n onChange,\n onInteraction,\n value,\n defaultValue,\n defaultSubmitted,\n renderMode = 'practice',\n outcome,\n sanitizeHtml,\n mediaBudget,\n mediaStrings,\n strings,\n theme,\n locale,\n disabled,\n showCorrectAnswers,\n ai: aiProp,\n delivery,\n scoring,\n}: FillInTheBlanksProps) {\n const isExam = renderMode === 'exam';\n const isReview = renderMode === 'review';\n\n // Dev-only boundary validation (Req 2.3); throws in render so the wrapping\n // ActivityErrorBoundary catches it. Re-runs only when data changes.\n const s = useLkStrings(strings);\n const ai = useLearnerAi(aiProp);\n const policy = useDeliveryPolicy(delivery);\n const scoringPolicy = useItemScoringPolicy(scoring);\n\n const devError = useMemo(() => {\n if (!isDevelopment()) {\n return null;\n }\n if (data.redacted === true) {\n // A redact() projection cannot satisfy the full content schema — the\n // answer key is gone by design. Validate it against the STRICT redacted\n // schema instead, which additionally proves the payload is learner-safe.\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('fill-in-the-blanks', data);\n return result.success ? null : new ActivitySchemaError('fill-in-the-blanks', result.errors);\n }, [data]);\n\n const { state, start, complete, getTimeSpent, reset } = useActivityState(\n defaultSubmitted === true ? 'completed' : 'idle',\n );\n // Before anything that resets them: a new question starts with no tries.\n const tries = useTries({\n policy: scoringPolicy,\n graded: renderMode === 'practice' && policy.feedback,\n submitted: state === 'completed',\n disabled: disabled === true,\n });\n const resetTries = tries.reset;\n // Controlled when `value` is supplied: the answers rendered are ALWAYS the\n // caller's, and internal state is never read. Uncontrolled otherwise,\n // seeded from `defaultValue`.\n const isControlled = value !== undefined;\n const [internalAnswers, setInternalAnswers] = useState<Record<string, string>>(() =>\n answersOf(defaultValue),\n );\n const [revealed, setRevealed] = useState<Set<string>>(() => new Set());\n // Every blank whose hint has been shown on this mount, hidden again or not:\n // hiding a hint does not unsee it.\n const [seen, setSeen] = useState<ReadonlySet<string>>(() => new Set());\n const [result, setResult] = useState<ScoringResult | null>(null);\n const [summary, setSummary] = useState<string | null>(null);\n const [overall, setOverall] = useState<string | null>(null);\n const [feedbackHidden, setFeedbackHidden] = useState(false);\n // Hints the learner had been shown before this mount: a restored answer's\n // count stands, and the hints shown here add to it.\n const seedHintsRef = useRef(hintsOf(value ?? defaultValue));\n const formRef = useRef<HTMLFormElement>(null);\n const feedbackRef = useRef<HTMLDivElement>(null);\n const focusAfterRef = useRef<'answer' | 'feedback' | null>(null);\n\n const answers = isControlled ? answersOf(value) : internalAnswers;\n\n // `defaultValue` is read only by the reset effect, and it is commonly an\n // inline object literal — putting it in that effect's deps would re-seed\n // (and wipe) the learner's typing on every render. Mirror it into a ref in\n // its own effect, which commits before the reset effect below.\n const defaultValueRef = useRef(defaultValue);\n useEffect(() => {\n defaultValueRef.current = defaultValue;\n }, [defaultValue]);\n\n // Mirror `defaultSubmitted` so the reset below returns to the SEEDED state.\n // Resetting unconditionally to idle unlocked an item the learner had already\n // committed — which made `defaultSubmitted` a no-op here, since this effect\n // runs right after the first paint.\n const defaultSubmittedRef = useRef(defaultSubmitted);\n useEffect(() => {\n defaultSubmittedRef.current = defaultSubmitted;\n }, [defaultSubmitted]);\n\n // Identity guard so the MOUNT run is a no-op. Without it this effect fires\n // after the first paint and undoes every seed it was just given; with it,\n // only a real `data` change resets. A parent that rebuilds structurally\n // identical entries in render (`activities={raw.map(redact)}`) must not\n // unlock or revert a restored answer — the pager already remounts a slot\n // whose activity actually changed, via its key.\n const lastDataRef = useRef(data);\n // `data` is an intentional reset trigger (Req 3.7); it is already in the\n // dependency list. The identity guard below skips the mount run.\n useEffect(() => {\n if (lastDataRef.current === data) {\n return;\n }\n lastDataRef.current = data;\n setInternalAnswers(answersOf(defaultValueRef.current));\n setRevealed(new Set());\n setSeen(new Set());\n setResult(null);\n setSummary(null);\n setOverall(null);\n setFeedbackHidden(false);\n seedHintsRef.current = hintsOf(defaultValueRef.current);\n resetTries();\n reset(defaultSubmittedRef.current === true ? 'completed' : 'idle');\n }, [data, reset, resetTries]);\n\n /*\n * RICH TEXT — deliberate, documented limitation (see `sanitizeHtml`).\n *\n * Every other activity can render its `*Html` field through the caller's\n * sanitiser, because the rich text is a LEAF: the sanitised string is\n * handed to one `dangerouslySetInnerHTML` node and nothing is inserted into\n * it. A fill-in-the-blanks passage is not a leaf — it is the CONTAINER of\n * the React-controlled `<input>`s, one per `{{id}}` placeholder.\n *\n * Placing those inputs inside sanitised HTML means slicing the sanitised\n * string at the placeholders and injecting each slice as its own HTML\n * fragment, which is unsafe and broken on both counts:\n * 1. Broken: the slices are unbalanced. `<p>A {{x}} B</p>` yields `<p>A `\n * and ` B</p>`; the fragment parser auto-closes both, so the input\n * lands OUTSIDE the paragraph and the authored block structure is\n * destroyed.\n * 2. Unsafe: the sanitiser's guarantee covers the document it returned,\n * not arbitrary substrings of it. A placeholder inside an attribute\n * (`<img alt=\"{{x}}\">`) splits mid-attribute, and re-parsing the\n * halves in a fresh context is exactly the mutation (mXSS) class of\n * transformation that voids a sanitiser's output guarantee. An exam\n * runner must never depend on that.\n *\n * The safe alternative — parse the sanitised string once into a detached\n * DOM, split only TEXT nodes at placeholders, and portal the inputs into\n * anchors — needs a DOM at render time (no SSR) and a large imperative\n * subsystem. Until that lands, the plain-text `passage` is rendered (always\n * escaped by React), which is correct, answerable, and safe in every mode.\n * `data.passageHtml` is carried and redacted by lk-core regardless, so no\n * authored content is lost — only this component declines to render it.\n */\n useEffect(() => {\n if (isDevelopment() && sanitizeHtml !== undefined && data.passageHtml !== undefined) {\n console.warn(\n '[lk-react] FillInTheBlanks ignores `passageHtml`: the passage hosts the answer inputs, ' +\n 'so rich HTML cannot be split at {{placeholders}} without voiding the sanitiser. ' +\n 'Rendering the plain `passage` instead.',\n );\n }\n }, [sanitizeHtml, data.passageHtml]);\n\n const segments = useMemo(() => parsePassage(data.passage), [data.passage]);\n const blankById = useMemo(() => new Map(data.blanks.map((b) => [b.id, b])), [data.blanks]);\n\n // Hints are a hook, so they are read here, before the throws below.\n const answered = state === 'completed';\n const aiHints = useComponentAiHints({\n ai,\n data,\n renderMode,\n submitted: answered,\n disabled: disabled === true,\n response: { type: 'fill-in-the-blanks', answers },\n locale,\n onInteraction,\n strings: s,\n delivery: policy,\n });\n // Every hint the learner has been shown on this question, counted from its\n // start: the author's, one per blank, and AI hints. What a scoring policy\n // charges for — in practice only: an exam's hints are free and uncounted.\n const hintsRevealed =\n renderMode === 'practice' ? seedHintsRef.current + seen.size + aiHints.used : 0;\n const responseOf = (\n next: Record<string, string>,\n hints: number = hintsRevealed,\n ): FillInTheBlanksLearnerResponse => ({\n type: 'fill-in-the-blanks',\n answers: next,\n ...(hints > 0 ? { hintsRevealed: hints } : {}),\n });\n // An AI hint shown is part of the answer's record: say so as it happens.\n const aiSeenRef = useRef(aiHints.used);\n // biome-ignore lint/correctness/useExhaustiveDependencies: a new hint is the trigger, and the response it reports is this render's\n useEffect(() => {\n if (aiSeenRef.current === aiHints.used) {\n return;\n }\n aiSeenRef.current = aiHints.used;\n if (aiHints.used > 0) {\n onChange?.(responseOf(answers));\n }\n }, [aiHints.used]);\n useEffect(() => {\n const target = focusAfterRef.current;\n if (target === null) {\n return;\n }\n focusAfterRef.current = null;\n if (target === 'answer') {\n formRef.current?.querySelector<HTMLElement>('input:not(:disabled)')?.focus();\n } else {\n feedbackRef.current?.focus();\n }\n });\n\n if (devError) {\n throw devError;\n }\n // Practice grades locally, which a redacted projection cannot support: the\n // submit handler below calls `score()`, which throws RedactedScoringError —\n // from an event handler, where no error boundary can catch it, AFTER the\n // learner has typed their answers. Fail loudly at render instead, in\n // production too. This mirrors the identical guard in MultipleChoice;\n // without it the two built-ins disagreed about the same accident, and\n // wiring an exam item into the self-grading mode is precisely what\n // `renderMode` exists to prevent.\n if (data.redacted === true && renderMode === 'practice') {\n throw new Error(\n `Fill-in-the-Blanks \"${data.id}\" received redacted activity data in renderMode \"practice\", ` +\n 'which grades locally and has no answer key to grade against. ' +\n 'Render redacted data with renderMode=\"exam\" (server grades) or \"review\" (pass `outcome`).',\n );\n }\n\n const submitted = state === 'completed';\n const inactive = disabled === true || submitted || isReview;\n\n /**\n * Whether the component may show answer-key-derived information (per-blank\n * correctness, authored feedback, correct answers). NEVER in `exam`; in\n * `practice` only after the component scored locally; in `review` only once\n * the caller supplies a SCORED outcome — an item that is still awaiting a\n * grade must not have its key revealed just because it is being read back.\n */\n const revealing = isReview ? outcome?.status === 'scored' : !isExam && submitted;\n // What the delivery policy lets that reveal show: right and wrong at all,\n // with the author's feedback, and the correct answers `showCorrectAnswers`\n // would write into a wrong blank.\n const marking = revealing && policy.feedback;\n // Not while the learner is offered another try: the next try is for finding them.\n const showingAnswers = marking && policy.solutions && showCorrectAnswers === true && !tries.open;\n\n const fireInteraction = (\n type: 'blank-filled' | 'hint-requested' | 'submitted',\n payload: Record<string, unknown>,\n ): void => {\n onInteraction?.({ type, activityId: data.id, timestamp: Date.now(), payload });\n };\n\n const handleChange = (blankId: string, text: string): void => {\n if (inactive) {\n return;\n }\n if (state === 'idle') {\n start();\n }\n const next = { ...answers, [blankId]: text };\n if (!isControlled) {\n setInternalAnswers(next);\n }\n onChange?.(responseOf(next));\n fireInteraction('blank-filled', { blankId, value: text });\n };\n\n const toggleHint = (blankId: string): void => {\n if (inactive) {\n return;\n }\n if (state === 'idle') {\n start();\n }\n setRevealed((prev) => {\n const next = new Set(prev);\n if (next.has(blankId)) {\n next.delete(blankId);\n return next;\n }\n next.add(blankId);\n return next;\n });\n // Only a reveal is a \"hint requested\"; hiding is not a new request.\n if (!revealed.has(blankId)) {\n fireInteraction('hint-requested', { blankId });\n }\n // The first time a blank's hint shows, the answer's record says so.\n if (!seen.has(blankId)) {\n setSeen((prev) => new Set(prev).add(blankId));\n if (renderMode === 'practice') {\n onChange?.(responseOf(answers, hintsRevealed + 1));\n }\n }\n };\n\n const handleSubmit = (event: React.FormEvent): void => {\n event.preventDefault();\n if (inactive) {\n return;\n }\n const response = responseOf(answers);\n // Fires in every submitting mode and always BEFORE onComplete, so an exam\n // runner can persist the raw response regardless of local grading.\n onSubmit?.(response);\n\n if (isExam) {\n // EXAM: the client neither grades nor reveals. No score()/evaluate(), no\n // read of any answer-key field, no onComplete — and no xAPI statement,\n // because its `correctResponsesPattern` IS the answer key. The server\n // owns the grade; it emits the statement.\n complete();\n setSummary(s.answerSubmitted);\n fireInteraction('submitted', { answers });\n return;\n }\n\n // PRACTICE: unchanged v1 behaviour. `data` carries the key here — exam\n // mode is the only path that may be handed a redact() projection.\n const answer = score('fill-in-the-blanks', data as FillInTheBlanksData, response);\n complete();\n const timeSpent = getTimeSpent();\n // The question's score under the policy — see MultipleChoice.\n const counted = tries.record({\n score: answer.score,\n maxScore: answer.maxScore,\n hintsRevealed,\n });\n const mine = counted.tries[counted.tries.length - 1];\n const costed = mine === undefined ? answer.score : mine.scored;\n const scoringResult =\n costed === answer.score\n ? answer\n : {\n ...answer,\n score: costed,\n passed: computePassThreshold(data as FillInTheBlanksData, costed),\n };\n const passed =\n counted.score === scoringResult.score\n ? scoringResult.passed\n : computePassThreshold(data as FillInTheBlanksData, counted.score);\n const 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 scoringResult,\n timeSpentMs: timeSpent,\n response: JSON.stringify(answers),\n });\n setResult(answer);\n onComplete?.(\n stampTake(\n { score: counted.score, maxScore: counted.maxScore, passed, timeSpent, xapiStatement },\n mintTake(),\n ),\n );\n // Core selects the authored overall feedback on `passed` (B3 fix): the answer's.\n setOverall(answer.feedback);\n setSummary(\n `${s.answerSubmitted} ${s.scoreAnnouncement(Math.round(counted.score * 100), passed)}`,\n );\n fireInteraction('submitted', { answers, score: scoringResult.score });\n };\n\n /** \"Try again\": the answers stay, to be changed; the marks go. */\n const retry = (): void => {\n reset('idle');\n setSummary(null);\n setOverall(null);\n focusAfterRef.current = 'answer';\n };\n\n /** \"Show answer\": no more tries; the question shows what a finished one shows. */\n const closeTries = (): void => {\n tries.close();\n focusAfterRef.current = 'feedback';\n };\n\n const correctByBlank = useMemo(() => {\n if (isExam) {\n // Nothing is ever marked client-side in an exam.\n return new Map<string, boolean>();\n }\n if (isReview) {\n // Correctness comes ONLY from the server-computed outcome; the component\n // never scores in review, so an absent/ungraded outcome marks nothing.\n return outcome?.status === 'scored'\n ? correctnessByItem(outcome.details)\n : new Map<string, boolean>();\n }\n return correctnessByItem(result?.details ?? []);\n }, [isExam, isReview, outcome, result]);\n\n /** Review has no local score: its summary is read off `outcome`, or nothing. */\n const reviewSummary = useMemo(() => {\n if (!isReview || outcome === undefined) {\n return null;\n }\n if (outcome.status === 'scored') {\n return `${s.scoreAnnouncement(Math.round(outcome.score * 100), outcome.passed)}${\n outcome.feedback ? ` ${outcome.feedback}` : ''\n }`;\n }\n if (outcome.status === 'deferred') {\n return s.notGradedYet;\n }\n // `graded` used to fall into the arm below and announce \"No grade\n // available.\" over a grade that had come back. Normalised by maxScore so a\n // grader payload of 8.5 / 10 does not read as 850% — same guard as\n // `<WrittenResponse>`.\n if (outcome.status === 'graded') {\n const percent =\n outcome.maxScore > 0\n ? Math.round((outcome.score / outcome.maxScore) * 100)\n : Math.round(outcome.score * 100);\n return `${s.scoreAnnouncement(percent, outcome.passed)}${\n outcome.feedback ? ` ${outcome.feedback}` : ''\n }`;\n }\n return s.noGradeAvailable;\n }, [isReview, outcome, s]);\n\n const extra =\n !isReview && policy.feedback && submitted && tries.counted !== null\n ? triesSummary(s, scoringPolicy, tries.counted, tries.open)\n : '';\n const announced =\n summary === null\n ? null\n : `${summary}${extra ? ` ${extra}` : ''}${overall ? ` ${overall}` : ''}`;\n const hintsOffered =\n renderMode === 'practice' &&\n !submitted &&\n (aiHints.offered || (policy.hints && data.blanks.some((blank) => blank.hint)));\n\n return (\n <form\n ref={formRef}\n className=\"lk-fib\"\n aria-label={data.title}\n lang={locale}\n data-render-mode={renderMode}\n style={theme as CSSProperties | undefined}\n onSubmit={handleSubmit}\n >\n {data.media ? (\n <ActivityMedia\n media={data.media}\n renderMode={renderMode}\n {...(mediaBudget !== undefined ? { mediaBudget } : {})}\n {...(mediaStrings !== undefined ? { mediaStrings } : {})}\n {...(strings !== undefined ? { strings } : {})}\n {...(onInteraction !== undefined ? { onInteraction } : {})}\n {...(locale !== undefined ? { locale } : {})}\n />\n ) : null}\n <fieldset disabled={inactive}>\n <p className=\"lk-fib-passage\">\n {segments.map((seg, i) => {\n if (seg.kind === 'text') {\n // biome-ignore lint/suspicious/noArrayIndexKey: passage segments are positional and static\n return <span key={`t${i}`}>{seg.value}</span>;\n }\n const blank = blankById.get(seg.id);\n if (!blank) {\n return null;\n }\n // `Renderable` only widens `scoringStrategy` at the type level, so\n // the answer-key fields still LOOK required here while a redact()\n // projection omits them at runtime. Read them defensively, and\n // never at all in exam mode.\n const acceptedAnswers: string[] | undefined = isExam\n ? undefined\n : blank.acceptedAnswers;\n const blankFeedback = isExam ? undefined : blank.feedback;\n const correctness = correctByBlank.get(seg.id);\n const hintId = `${data.id}-hint-${seg.id}`;\n // The author's hint, where the delivery policy offers hints. On in\n // every mode by default — including `exam`, where it is public.\n const hint = policy.hints ? blank.hint : undefined;\n const correctAnswer = acceptedAnswers?.[0];\n if (showingAnswers && correctness === false && correctAnswer) {\n return (\n <span key={seg.id} className=\"lk-fib-answer\" data-correct=\"false\">\n {correctAnswer}\n </span>\n );\n }\n return (\n <span key={seg.id} className=\"lk-fib-blank\">\n <input\n type=\"text\"\n aria-label={s.blankLabel(seg.ordinal)}\n aria-describedby={hint ? hintId : undefined}\n value={answers[seg.id] ?? ''}\n disabled={inactive}\n aria-disabled={inactive || undefined}\n data-correct={\n marking && correctness !== undefined ? String(correctness) : undefined\n }\n onChange={(e) => handleChange(seg.id, e.target.value)}\n />\n {/*\n Hints stay available in exam mode by design: lk-core's field\n policy classifies `blanks.hint` as `public`, so it survives\n redact() and is not part of the answer key.\n */}\n {hint ? (\n <>\n {/*\n Default affordance is an icon; the accessible NAME stays\n text via aria-label (screen readers + tests rely on it).\n The glyph is restylable/replaceable via .lk-fib-hint-btn.\n */}\n <button\n type=\"button\"\n className=\"lk-fib-hint-btn\"\n aria-controls={hintId}\n aria-expanded={revealed.has(seg.id)}\n aria-label={revealed.has(seg.id) ? s.hideHint : s.showHint}\n disabled={inactive}\n onClick={() => toggleHint(seg.id)}\n >\n <svg\n className=\"lk-fib-hint-icon\"\n viewBox=\"0 0 24 24\"\n width=\"16\"\n height=\"16\"\n aria-hidden=\"true\"\n focusable=\"false\"\n >\n <path\n fill=\"currentColor\"\n d=\"M9 21h6v-1H9v1Zm3-19a7 7 0 0 0-4 12.74V17h8v-2.26A7 7 0 0 0 12 2Z\"\n />\n </svg>\n </button>\n {/*\n Not role=\"tooltip\": a real ARIA tooltip is a named\n hover/focus popup with strict WCAG 1.4.13 constraints.\n This is an accessible click-to-toggle disclosure that is\n the input's aria-describedby target and is announced via\n aria-live; the skin gives it a tooltip-like *visual*\n without the tooltip *semantics*. (Refines Task 15.3.)\n */}\n <span id={hintId} aria-live=\"polite\">\n {revealed.has(seg.id) ? hint : ''}\n </span>\n </>\n ) : null}\n {marking && !feedbackHidden && blankFeedback ? (\n <span\n className=\"lk-fib-blank-feedback\"\n role=\"note\"\n data-correct={correctness !== undefined ? String(correctness) : undefined}\n >\n {blankFeedback}\n </span>\n ) : null}\n </span>\n );\n })}\n </p>\n {hintsOffered ? <HintCost policy={scoringPolicy} strings={s} /> : null}\n <AiHints help={aiHints} ai={ai} strings={s} />\n {/* Review is read-only: there is nothing to submit. */}\n {isReview ? null : (\n <button type=\"submit\" disabled={inactive}>\n {isExam ? s.submitAnswers : s.checkAnswers}\n </button>\n )}\n </fieldset>\n {marking && data.blanks.some((b) => b.feedback) ? (\n <button\n type=\"button\"\n className=\"lk-fib-feedback-toggle\"\n aria-expanded={!feedbackHidden}\n onClick={() => setFeedbackHidden((h) => !h)}\n >\n {feedbackHidden ? s.showFeedback : s.hideFeedback}\n </button>\n ) : null}\n <FeedbackRegion\n id={`${data.id}-feedback`}\n {...(scoringPolicy.retries > 0 ? { ref: feedbackRef } : {})}\n >\n {isReview\n ? policy.feedback || !outcomeShowsMarks(outcome)\n ? reviewSummary\n : null\n : policy.feedback || summary === null\n ? announced\n : s.answerSubmitted}\n </FeedbackRegion>\n <TryActions\n tries={tries}\n solutions={policy.solutions && showCorrectAnswers === true}\n strings={s}\n onRetry={retry}\n onClose={closeTries}\n />\n <AiExplanation\n ai={ai}\n data={data}\n renderMode={renderMode}\n submitted={submitted}\n response={{ type: 'fill-in-the-blanks', answers }}\n outcome={outcome}\n locale={locale}\n onInteraction={onInteraction}\n strings={s}\n // An explanation all but always names the answer: not while another\n // try is on offer.\n delivery={tries.open ? { ...policy, solutions: false } : policy}\n />\n </form>\n );\n}\n","'use client';\n\nimport { useLkStrings } from '../../i18n/LkIntlProvider.js';\nimport { ActivityErrorBoundary } from '../ActivityErrorBoundary.js';\nimport {\n FillInTheBlanks as FillInTheBlanksCore,\n type FillInTheBlanksProps,\n} from './FillInTheBlanks.js';\n\nexport type { FillInTheBlanksProps } from './FillInTheBlanks.js';\n\n/**\n * Public Fill-in-the-Blanks activity: the core wrapped in\n * `ActivityErrorBoundary` so a render failure (incl. the dev schema-validation\n * throw) degrades to an accessible fallback instead of crashing the host.\n */\nexport function FillInTheBlanks(props: FillInTheBlanksProps) {\n // Read here rather than in the boundary: a class component cannot call a\n // hook, and the fallback it renders is learner-facing text like any other.\n const strings = useLkStrings(props.strings);\n return (\n <ActivityErrorBoundary activityTitle={props.data?.title} strings={strings}>\n <FillInTheBlanksCore {...props} />\n </ActivityErrorBoundary>\n );\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/shared/playback-refusal.ts","../src/components/shared/read-grade.ts","../src/components/PronunciationFeedback/PronunciationFeedback.tsx","../src/components/PronunciationFeedback/index.tsx"],"names":["useState","PronunciationFeedback","jsx"],"mappings":";;;;;;;;AAsBO,SAAS,mBAAmB,GAAA,EAGjC;AACA,EAAA,MAAM,CAAC,UAAA,EAAY,aAAa,CAAA,GAAI,SAAwB,IAAI,CAAA;AAChE,EAAA,MAAM,MAAA,GAAS,WAAA,CAAY,CAAC,OAAA,KAA8B;AACxD,IAAA,MAAM,MAAA,GAAS,OAAA,CAAQ,YAAA,CAAa,KAAK,CAAA;AACzC,IAAA,IAAI,WAAW,IAAA,EAAM;AACnB,MAAA,aAAA,CAAc,MAAM,CAAA;AAAA,IACtB;AAAA,EACF,CAAA,EAAG,EAAE,CAAA;AACL,EAAA,OAAO,EAAE,SAAS,GAAA,KAAQ,IAAA,IAAQ,QAAQ,MAAA,IAAa,GAAA,KAAQ,YAAY,MAAA,EAAO;AACpF;AAOO,SAAS,gBAAgB,KAAA,EAAyB;AACvD,EAAA,OACE,OAAO,KAAA,KAAU,QAAA,IACjB,KAAA,KAAU,IAAA,IACT,MAA6B,IAAA,KAAS,mBAAA;AAE3C;;;ACiDA,IAAM,UAAA,GAA4C;AAAA,EAChD,UAAA;AAAA,EACA,SAAA;AAAA,EACA,cAAA;AAAA,EACA;AACF,CAAA;AAEA,IAAM,QAAA,GAAsC;AAAA,EAC1C,SAAA;AAAA,EACA,WAAA;AAAA,EACA,kBAAA;AAAA,EACA;AACF,CAAA;AASA,IAAM,SAAA,GAAY,IAAA;AAElB,SAAS,SAAS,KAAA,EAAkD;AAClE,EAAA,OAAO,OAAO,UAAU,QAAA,IAAY,KAAA,KAAU,QAAQ,CAAC,KAAA,CAAM,QAAQ,KAAK,CAAA;AAC5E;AAEA,SAAS,eAAe,KAAA,EAAiC;AACvD,EAAA,OAAO,OAAO,KAAA,KAAU,QAAA,IAAY,MAAA,CAAO,SAAS,KAAK,CAAA;AAC3D;AAGA,SAAS,SAAS,KAAA,EAA2C;AAC3D,EAAA,OAAO,KAAA,KAAU,UAAa,KAAA,KAAU,IAAA;AAC1C;AAGA,SAAS,SAAA,CAAU,OAAgB,GAAA,EAA4B;AAC7D,EAAA,IAAI,CAAC,cAAA,CAAe,KAAK,CAAA,IAAK,QAAQ,CAAA,EAAG;AACvC,IAAA,OAAO,IAAA;AAAA,EACT;AACA,EAAA,IAAI,SAAS,GAAA,EAAK;AAChB,IAAA,OAAO,KAAA;AAAA,EACT;AACA,EAAA,OAAO,KAAA,GAAQ,GAAA,IAAO,GAAA,GAAM,SAAA,GAAY,GAAA,GAAM,IAAA;AAChD;AAGA,SAAS,WAAW,KAAA,EAA4C;AAC9D,EAAA,OACE,OAAO,KAAA,KAAU,QAAA,IAChB,KAAA,CAAM,OAAA,CAAQ,KAAK,CAAA,IAAK,KAAA,CAAM,KAAA,CAAM,CAAC,KAAA,KAAU,OAAO,UAAU,QAAQ,CAAA;AAE7E;AAGA,SAAS,OAAA,CAAW,MAAe,OAAA,EAAe;AAChD,EAAA,IAAI;AACF,IAAA,OAAO,IAAA,EAAK;AAAA,EACd,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,OAAA;AAAA,EACT;AACF;AAEA,SAAS,cAAc,KAAA,EAAuC;AAC5D,EAAA,IAAI,CAAC,QAAA,CAAS,KAAK,KAAK,OAAO,KAAA,CAAM,SAAS,QAAA,EAAU;AACtD,IAAA,OAAO,IAAA;AAAA,EACT;AACA,EAAA,MAAM,EAAE,QAAA,EAAU,aAAA,EAAc,GAAI,KAAA;AACpC,EAAA,IAAI,CAAC,SAAS,QAAQ,CAAA,IAAK,EAAE,cAAA,CAAe,QAAQ,CAAA,IAAK,QAAA,GAAW,CAAA,CAAA,EAAI;AACtE,IAAA,OAAO,IAAA;AAAA,EACT;AACA,EAAA,IAAI,CAAC,QAAA,CAAS,aAAa,CAAA,IAAK,OAAO,kBAAkB,SAAA,EAAW;AAClE,IAAA,OAAO,IAAA;AAAA,EACT;AAEA,EAAA,MAAM,KAAA,GAAQ,QAAA,CAAS,KAAA,CAAM,KAAK,CAAA,GAAI,SAAY,SAAA,CAAU,KAAA,CAAM,KAAA,EAAO,QAAA,IAAY,CAAC,CAAA;AACtF,EAAA,IAAI,UAAU,IAAA,EAAM;AAClB,IAAA,OAAO,IAAA;AAAA,EACT;AACA,EAAA,OAAO;AAAA,IACL,MAAM,KAAA,CAAM,IAAA;AAAA,IACZ,GAAI,KAAA,KAAU,MAAA,GAAY,EAAE,KAAA,KAAU,EAAC;AAAA,IACvC,GAAI,CAAC,QAAA,CAAS,QAAQ,IAAI,EAAE,QAAA,KAAa,EAAC;AAAA,IAC1C,GAAI,aAAA,KAAkB,IAAA,GAAO,EAAE,aAAA,KAAkB;AAAC,GACpD;AACF;AAEA,SAAS,WAAW,KAAA,EAAsC;AACxD,EAAA,IAAI,CAAC,QAAA,CAAS,KAAK,CAAA,EAAG;AACpB,IAAA,OAAO,IAAA;AAAA,EACT;AACA,EAAA,MAAM,EAAE,QAAQ,OAAA,EAAS,OAAA,EAAS,iBAAiB,eAAA,EAAiB,MAAA,EAAQ,OAAM,GAAI,KAAA;AACtF,EAAA,IACE,OAAO,MAAA,KAAW,QAAA,IAClB,OAAO,OAAA,KAAY,SAAA,IACnB,CAAC,UAAA,CAAW,eAAe,CAAA,IAC3B,CAAC,UAAA,CAAW,eAAe,CAAA,EAC3B;AACA,IAAA,OAAO,IAAA;AAAA,EACT;AACA,EAAA,MAAM,iBAAiB,QAAA,CAAS,IAAA,CAAK,CAAC,KAAA,KAAU,UAAU,OAAO,CAAA;AACjE,EAAA,IAAI,CAAC,QAAA,CAAS,OAAO,CAAA,IAAK,mBAAmB,MAAA,EAAW;AACtD,IAAA,OAAO,IAAA;AAAA,EACT;AACA,EAAA,IAAI,CAAC,QAAA,CAAS,MAAM,KAAK,CAAC,cAAA,CAAe,MAAM,CAAA,EAAG;AAChD,IAAA,OAAO,IAAA;AAAA,EACT;AAEA,EAAA,MAAM,eAAe,QAAA,CAAS,KAAK,IAAI,MAAA,GAAY,SAAA,CAAU,OAAO,CAAC,CAAA;AACrE,EAAA,IAAI,iBAAiB,IAAA,EAAM;AACzB,IAAA,OAAO,IAAA;AAAA,EACT;AACA,EAAA,OAAO;AAAA,IACL,MAAA;AAAA,IACA,OAAA;AAAA,IACA,eAAA;AAAA,IACA,eAAA;AAAA,IACA,GAAI,cAAA,KAAmB,MAAA,GAAY,EAAE,OAAA,EAAS,cAAA,KAAmB,EAAC;AAAA,IAClE,GAAI,CAAC,QAAA,CAAS,MAAM,IAAI,EAAE,MAAA,KAAW,EAAC;AAAA,IACtC,GAAI,YAAA,KAAiB,MAAA,GAAY,EAAE,KAAA,EAAO,YAAA,KAAiB;AAAC,GAC9D;AACF;AAEA,SAAS,YAAY,KAAA,EAA6C;AAChE,EAAA,IAAI,CAAC,KAAA,CAAM,OAAA,CAAQ,KAAK,CAAA,EAAG;AACzB,IAAA,OAAO,MAAA;AAAA,EACT;AACA,EAAA,MAAM,UAA2B,EAAC;AAClC,EAAA,KAAA,MAAW,SAAS,KAAA,EAAO;AACzB,IAAA,MAAM,MAAA,GAAS,WAAW,KAAK,CAAA;AAC/B,IAAA,IAAI,WAAW,IAAA,EAAM;AACnB,MAAA,OAAO,MAAA;AAAA,IACT;AACA,IAAA,OAAA,CAAQ,KAAK,MAAM,CAAA;AAAA,EACrB;AACA,EAAA,OAAO,OAAA;AACT;AAMO,SAAS,UAAU,KAAA,EAAkC;AAC1D,EAAA,OAAO,QAAQ,MAAM;AACnB,IAAA,IAAI,CAAC,QAAA,CAAS,KAAK,CAAA,EAAG;AACpB,MAAA,OAAO,IAAA;AAAA,IACT;AACA,IAAA,MAAM,EAAE,QAAA,EAAU,MAAA,EAAO,GAAI,KAAA;AAC7B,IAAA,IAAI,CAAC,eAAe,QAAQ,CAAA,IAAK,YAAY,CAAA,IAAK,OAAO,WAAW,SAAA,EAAW;AAC7E,MAAA,OAAO,IAAA;AAAA,IACT;AACA,IAAA,MAAM,KAAA,GAAQ,SAAA,CAAU,KAAA,CAAM,KAAA,EAAO,QAAQ,CAAA;AAC7C,IAAA,IAAI,UAAU,IAAA,EAAM;AAClB,MAAA,OAAO,IAAA;AAAA,IACT;AACA,IAAA,MAAM,QAAA,GAAW,KAAA,CAAM,OAAA,CAAQ,KAAA,CAAM,QAAQ,IACzC,KAAA,CAAM,QAAA,CAAS,OAAA,CAAQ,CAAC,KAAA,KAAU;AAChC,MAAA,MAAM,SAAA,GAAY,cAAc,KAAK,CAAA;AACrC,MAAA,OAAO,SAAA,KAAc,IAAA,GAAO,EAAC,GAAI,CAAC,SAAS,CAAA;AAAA,IAC7C,CAAC,CAAA,GACD,KAAA,CAAA;AACJ,IAAA,MAAM,OAAA,GAAU,WAAA,CAAY,KAAA,CAAM,OAAO,CAAA;AACzC,IAAA,MAAM,KAAA,GAAqB;AAAA,MACzB,KAAA;AAAA,MACA,QAAA;AAAA,MACA,MAAA;AAAA,MACA,UAAU,OAAO,KAAA,CAAM,QAAA,KAAa,QAAA,GAAW,MAAM,QAAA,GAAW,IAAA;AAAA,MAChE,GAAI,QAAA,KAAa,KAAA,CAAA,GAAY,EAAE,QAAA,KAAa,EAAC;AAAA,MAC7C,GAAI,OAAA,KAAY,KAAA,CAAA,GAAY,EAAE,OAAA,KAAY;AAAC,KAC7C;AACA,IAAA,OAAO,KAAA;AAAA,EACT,GAAG,IAAI,CAAA;AACT;AAGO,SAAS,gBAAgB,KAAA,EAA0B;AACxD,EAAA,OAAO,KAAA,CAAM,QAAQ,KAAA,CAAM,QAAA;AAC7B;AAGO,SAAS,eAAe,KAAA,EAA0B;AACvD,EAAA,OAAO,IAAA,CAAK,KAAA,CAAM,eAAA,CAAgB,KAAK,IAAI,GAAG,CAAA;AAChD;AAYO,SAAS,YAAY,KAAA,EAA4C;AACtE,EAAA,IAAI,QAAA,CAAS,KAAK,CAAA,EAAG;AACnB,IAAA,OAAO,MAAA;AAAA,EACT;AACA,EAAA,OAAO,OAAA;AAAA,IACL,MAAsB;AACpB,MAAA,IAAI,CAAC,QAAA,CAAS,KAAK,CAAA,EAAG;AACpB,QAAA,OAAO,EAAE,IAAA,EAAM,YAAA,EAAc,MAAA,EAAQ,KAAA,CAAA,EAAU;AAAA,MACjD;AACA,MAAA,QAAQ,MAAM,MAAA;AAAQ,QACpB,KAAK,UAAA;AACH,UAAA,OAAO,EAAE,MAAM,UAAA,EAAW;AAAA,QAC5B,KAAK,YAAA;AACH,UAAA,OAAO;AAAA,YACL,IAAA,EAAM,YAAA;AAAA,YACN,MAAM,OAAO,KAAA,CAAM,IAAA,KAAS,QAAA,GAAW,MAAM,IAAA,GAAO,KAAA;AAAA,WACtD;AAAA,QACF,KAAK,QAAA,EAAU;AACb,UAAA,MAAM,KAAA,GAAQ,UAAU,KAAK,CAAA;AAC7B,UAAA,OAAO,KAAA,KAAU,IAAA,GACb,EAAE,IAAA,EAAM,YAAA,EAAc,MAAA,EAAQ,QAAA,EAAS,GACvC,EAAE,IAAA,EAAM,QAAA,EAAU,MAAA,EAAQ,UAAU,KAAA,EAAM;AAAA,QAChD;AAAA,QACA,KAAK,QAAA,EAAU;AACb,UAAA,MAAM,KAAA,GAAQ,SAAA,CAAU,KAAA,CAAM,KAAK,CAAA;AACnC,UAAA,OAAO,UAAU,IAAA,IAAQ,SAAA,CAAU,KAAK,CAAA,KAAM,OAC1C,EAAE,IAAA,EAAM,YAAA,EAAc,MAAA,EAAQ,UAAS,GACvC,EAAE,MAAM,QAAA,EAAU,MAAA,EAAQ,UAAU,KAAA,EAAM;AAAA,QAChD;AAAA,QACA;AACE,UAAA,OAAO,EAAE,IAAA,EAAM,YAAA,EAAc,MAAA,EAAQ,KAAA,CAAA,EAAU;AAAA;AACnD,IACF,CAAA;AAAA,IACA,EAAE,IAAA,EAAM,YAAA,EAAc,MAAA,EAAQ,MAAA;AAAU,GAC1C;AACF;AAYO,SAAS,iBAAiB,KAAA,EAA+B;AAC9D,EAAA,MAAM,OAAA,GAAyB;AAAA,IAC7B,MAAA,EAAQ,QAAA;AAAA,IACR,KAAA,EAAO,IAAA;AAAA,IACP,UAAA,EAAY,IAAA;AAAA,IACZ,cAAA,EAAgB;AAAA,GAClB;AACA,EAAA,OAAO,QAAQ,MAAqB;AAClC,IAAA,IAAI,CAAC,QAAA,CAAS,KAAK,CAAA,EAAG;AACpB,MAAA,OAAO,OAAA;AAAA,IACT;AACA,IAAA,IAAI,KAAA,CAAM,WAAW,QAAA,EAAU;AAC7B,MAAA,OAAO,EAAE,MAAA,EAAQ,QAAA,EAAU,SAAA,EAAW,KAAA,CAAM,cAAc,IAAA,EAAK;AAAA,IACjE;AACA,IAAA,IAAI,KAAA,CAAM,WAAW,YAAA,EAAc;AACjC,MAAA,OAAO;AAAA,QACL,MAAA,EAAQ,YAAA;AAAA,QACR,MAAM,OAAO,KAAA,CAAM,IAAA,KAAS,QAAA,GAAW,MAAM,IAAA,GAAO,KAAA;AAAA,OACtD;AAAA,IACF;AACA,IAAA,IAAI,KAAA,CAAM,WAAW,QAAA,EAAU;AAC7B,MAAA,OAAO,OAAA;AAAA,IACT;AACA,IAAA,MAAM,UAAA,GAAa,YAAA,CAAa,KAAA,CAAM,UAAU,CAAA;AAChD,IAAA,MAAM,cAAA,GAAiB,OAAA;AAAA,MACrB,MACE,OAAO,UAAA,EAAY,cAAA,KAAmB,QAAA,GAAW,WAAW,cAAA,GAAiB,KAAA,CAAA;AAAA,MAC/E,KAAA;AAAA,KACF;AACA,IAAA,OAAO,EAAE,QAAQ,QAAA,EAAU,KAAA,EAAO,UAAU,KAAA,CAAM,KAAK,CAAA,EAAG,UAAA,EAAY,cAAA,EAAe;AAAA,EACvF,GAAG,OAAO,CAAA;AACZ;AAMO,SAAS,aAAa,KAAA,EAAyC;AACpE,EAAA,OAAO,QAAA,CAAS,KAAK,CAAA,GAAK,KAAA,GAAwC,IAAA;AACpE;AAGA,SAAS,mBAAmB,SAAA,EAA+C;AACzE,EAAA,IAAI,SAAA,CAAU,aAAA,KAAkB,IAAA,IAAQ,SAAA,CAAU,UAAU,MAAA,EAAW;AACrE,IAAA,OAAO,MAAA;AAAA,EACT;AACA,EAAA,OAAO,KAAK,KAAA,CAAO,SAAA,CAAU,SAAS,SAAA,CAAU,QAAA,IAAY,KAAM,GAAG,CAAA;AACvE;AAaO,SAAS,cAAA,CACd,YACA,KAAA,EACoB;AACpB,EAAA,MAAM,QAAA,GAAW,KAAA,EAAO,QAAA,IAAY,EAAC;AACrC,EAAA,IAAI,QAAA,CAAS,SAAS,CAAA,EAAG;AACvB,IAAA,OAAO,UAAA,CAAW,GAAA,CAAI,CAAC,SAAA,KAAc;AACnC,MAAA,MAAM,YAAY,QAAA,CAAS,IAAA,CAAK,CAAC,KAAA,KAAU,KAAA,CAAM,SAAS,SAAS,CAAA;AACnE,MAAA,OAAO;AAAA,QACL,SAAA;AAAA,QACA,OAAA,EAAS,SAAA,KAAc,MAAA,GAAY,MAAA,GAAY,mBAAmB,SAAS;AAAA,OAC7E;AAAA,IACF,CAAC,CAAA;AAAA,EACH;AACA,EAAA,MAAM,MAAA,GAAS,QAAQ,MAAO,QAAA,CAAS,UAAU,CAAA,GAAI,UAAA,CAAW,MAAA,GAAS,KAAA,CAAA,EAAY,MAAS,CAAA;AAC9F,EAAA,OAAO,UAAA,CAAW,GAAA,CAAI,CAAC,SAAA,KAAc;AACnC,IAAA,MAAM,KAAA,GAAQ,SAAA;AAAA,MACZ,OAAA,CAAQ,MAAO,QAAA,CAAS,MAAM,IAAI,MAAA,CAAO,SAAS,CAAA,GAAI,KAAA,CAAA,EAAY,MAAS,CAAA;AAAA,MAC3E;AAAA,KACF;AAEA,IAAA,OAAO,EAAE,WAAW,OAAA,EAAS,KAAA,KAAU,OAAO,MAAA,GAAY,IAAA,CAAK,KAAA,CAAM,KAAK,CAAA,EAAE;AAAA,EAC9E,CAAC,CAAA;AACH;ACtYO,IAAM,eAAA,GAAiC;AAAA,EAC5C,QAAA,EAAU,UAAA;AAAA,EACV,KAAA,EAAO,CAAA;AAAA,EACP,MAAA,EAAQ,CAAA;AAAA,EACR,MAAA,EAAQ,EAAA;AAAA,EACR,OAAA,EAAS,CAAA;AAAA,EACT,QAAA,EAAU,QAAA;AAAA,EACV,IAAA,EAAM,eAAA;AAAA,EACN,QAAA,EAAU,YAAA;AAAA,EACV,UAAA,EAAY,QAAA;AAAA,EACZ,MAAA,EAAQ;AACV;AAGA,IAAM,aAAA,GAA4D;AAAA,EAChE,SAAA;AAAA,EACA,eAAA;AAAA,EACA,SAAA;AAAA,EACA;AACF,CAAA;AAyDA,SAAS,cAAc,UAAA,EAAyD;AAC9E,EAAA,IAAI;AACF,IAAA,OAAO,yBAAyB,UAAU,CAAA;AAAA,EAC5C,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO;AAAA,MACL,OAAA,EAAS,KAAA;AAAA,MACT,MAAA,EAAQ,CAAC,EAAE,IAAA,EAAM,IAAI,OAAA,EAAS,mCAAA,EAAqC,IAAA,EAAM,YAAA,EAAc;AAAA,KACzF;AAAA,EACF;AACF;AAGA,SAAS,UAAU,IAAA,EAAkD;AACnE,EAAA,IAAI,SAAS,MAAA,EAAW;AACtB,IAAA,OAAO,KAAA;AAAA,EACT;AACA,EAAA,OACE,IAAA,CAAK,QAAA,KAAa,MAAA,IACjB,IAAA,CAAK,SAAA,KAAc,MAAA,IAAa,IAAA,CAAK,SAAA,CAAU,MAAA,GAAS,CAAA,IACxD,IAAA,CAAK,QAAA,KAAa,MAAA,IAAa,KAAK,QAAA,CAAS,MAAA,GAAS,CAAA,IACvD,IAAA,CAAK,MAAA,KAAW,MAAA,IACf,OAAO,IAAA,CAAK,OAAA,KAAY,QAAA,IAAY,OAAO,IAAA,CAAK,UAAA,KAAe,QAAA;AAEpE;AAGA,SAAS,IAAA,CAAK,YAAgC,SAAA,EAAwC;AACpF,EAAA,OACE,SAAA,KAAc,UACd,OAAO,UAAA,KAAe,YACtB,MAAA,CAAO,QAAA,CAAS,UAAU,CAAA,IAC1B,UAAA,GAAa,SAAA;AAEjB;AAGA,SAAS,YAAA,CAAa,OAAA,EAAwB,KAAA,EAAe,CAAA,EAAsB;AACjF,EAAA,OAAO,OAAA,CAAQ,MAAA,IAAU,CAAA,CAAE,4BAAA,CAA6B,QAAQ,CAAC,CAAA;AACnE;AAaO,SAAS,YAAA,CAAa,OAA+B,CAAA,EAAsB;AAChF,EAAA,QAAQ,MAAM,KAAA;AAAO,IACnB,KAAK,SAAA;AACH,MAAA,OAAO,CAAA,CAAE,wBAAA,CAAyB,KAAA,CAAM,SAAS,CAAA;AAAA,IACnD,KAAK,eAAA;AACH,MAAA,OAAO,CAAA,CAAE,8BAAA,CAA+B,KAAA,CAAM,SAAS,CAAA;AAAA,IACzD,KAAK,SAAA;AACH,MAAA,OAAO,CAAA,CAAE,wBAAA,CAAyB,KAAA,CAAM,SAAS,CAAA;AAAA,IACnD;AACE,MAAA,OAAO,CAAA,CAAE,yBAAA,CAA0B,KAAA,CAAM,KAAK,CAAA;AAAA;AAEpD;AAoCO,SAAS,qBAAA,CAAsB;AAAA,EACpC,IAAA;AAAA,EACA,UAAA;AAAA,EACA,KAAA;AAAA,EACA,QAAA;AAAA,EACA,cAAA;AAAA,EACA,iBAAA;AAAA,EACA,MAAA;AAAA,EACA,KAAA;AAAA,EACA;AACF,CAAA,EAA+B;AAC7B,EAAA,MAAM,CAAA,GAAI,aAAa,OAAO,CAAA;AAC9B,EAAA,MAAM,MAAM,KAAA,EAAM;AAClB,EAAA,MAAM,CAAC,QAAA,EAAU,WAAW,CAAA,GAAIA,SAAwB,IAAI,CAAA;AAE5D,EAAA,MAAM,OAAA,GAAU,QAAQ,MAAM,aAAA,CAAc,UAAU,CAAA,EAAG,CAAC,UAAU,CAAC,CAAA;AAErE,EAAA,MAAM,QAAA,GAAW,OAAA;AAAA,IACf,MACE,aAAA,EAAc,IAAK,CAAC,OAAA,CAAQ,OAAA,GACxB,IAAI,mBAAA,CAAoB,YAAA,EAAc,OAAA,CAAQ,MAAM,CAAA,GACpD,IAAA;AAAA,IACN,CAAC,OAAO;AAAA,GACV;AAKA,EAAA,MAAM,KAAA,GAAQ,OAAA;AAAA,IACZ,MAAO,OAAA,CAAQ,OAAA,GAAU,eAAe,IAAA,EAAM,OAAA,CAAQ,IAAI,CAAA,GAAI,IAAA;AAAA,IAC9D,CAAC,SAAS,IAAI;AAAA,GAChB;AAKA,EAAA,MAAM,OAAA,GAAU,OAAA;AAAA,IACd,MAAO,KAAA,KAAU,MAAA,IAAa,UAAU,IAAA,GAAO,MAAA,GAAY,UAAU,KAAK,CAAA;AAAA,IAC1E,CAAC,KAAK;AAAA,GACR;AACA,EAAA,MAAM,UAAA,GAAa,OAAA,CAAQ,MAAM,cAAA,CAAe,UAAA,EAAY,OAAO,CAAA,EAAG,CAAC,UAAA,EAAY,OAAO,CAAC,CAAA;AAI3F,EAAA,MAAM,QAAA,GAAW,OAAyB,IAAI,CAAA;AAC9C,EAAA,MAAM,OAAA,GAAU,OAA6C,IAAI,CAAA;AACjE,EAAA,MAAM,QAAA,GAAW,mBAAmB,QAAQ,CAAA;AAG5C,EAAA,MAAM,QAAA,GAAW,QAAA,KAAa,MAAA,IAAa,CAAC,QAAA,CAAS,OAAA;AACrD,EAAA,SAAA;AAAA,IACE,MAAM,MAAM;AACV,MAAA,IAAI,OAAA,CAAQ,YAAY,IAAA,EAAM;AAC5B,QAAA,YAAA,CAAa,QAAQ,OAAO,CAAA;AAAA,MAC9B;AAAA,IACF,CAAA;AAAA,IACA;AAAC,GACH;AAGA,EAAA,IAAI,QAAA,EAAU;AACZ,IAAA,MAAM,QAAA;AAAA,EACR;AAGA,EAAA,MAAM,QAAA,GAAW,CAAC,OAAA,EAAiB,UAAA,KAA6B;AAC9D,IAAA,MAAM,UAAU,QAAA,CAAS,OAAA;AACzB,IAAA,IAAI,YAAY,IAAA,EAAM;AACpB,MAAA;AAAA,IACF;AACA,IAAA,IAAI,OAAA,CAAQ,YAAY,IAAA,EAAM;AAC5B,MAAA,YAAA,CAAa,QAAQ,OAAO,CAAA;AAAA,IAC9B;AACA,IAAA,OAAA,CAAQ,cAAc,OAAA,GAAU,GAAA;AAChC,IAAA,IAAI;AACF,MAAA,KAAK,OAAA,CAAQ,IAAA,EAAK,CAAE,KAAA,CAAM,CAAC,KAAA,KAAmB;AAI5C,QAAA,IAAI,eAAA,CAAgB,KAAK,CAAA,EAAG;AAC1B,UAAA,QAAA,CAAS,OAAO,OAAO,CAAA;AAAA,QACzB;AAAA,MACF,CAAC,CAAA;AAAA,IACH,CAAA,CAAA,MAAQ;AAAA,IAER;AACA,IAAA,OAAA,CAAQ,OAAA,GAAU,WAAW,MAAM;AACjC,MAAA,OAAA,CAAQ,OAAA,GAAU,IAAA;AAClB,MAAA,OAAA,CAAQ,KAAA,EAAM;AAAA,IAChB,GAAG,UAAU,CAAA;AAAA,EACf,CAAA;AAMA,EAAA,MAAM,cAAc,OAAO,IAAA,CAAK,MAAA,KAAW,QAAA,GAAW,KAAK,MAAA,GAAS,MAAA;AACpE,EAAA,MAAM,UAAA,GAAa,iBAAA,CAAkB,IAAA,CAAK,MAAM,CAAA;AAEhD,EAAA,MAAM,QAAA,GAAW,OAAA,CAAQ,OAAA,GAAU,OAAA,CAAQ,KAAK,eAAA,GAAkB,MAAA;AAClE,EAAA,MAAM,WAAW,OAAA,CAAQ,OAAA,GAAU,OAAA,CAAQ,IAAA,CAAK,SAAS,kBAAA,GAAqB,MAAA;AAG9E,EAAA,MAAM,gBAAuC,OAAA,CAAQ,OAAA,GAAU,OAAA,CAAQ,IAAA,CAAK,QAAQ,EAAC;AAErF,EAAA,uBACE,IAAA;AAAA,IAAC,SAAA;AAAA,IAAA;AAAA,MACC,SAAA,EAAU,OAAA;AAAA,MACV,cAAY,CAAA,CAAE,0BAAA;AAAA,MACd,IAAA,EAAM,MAAA;AAAA,MACN,KAAA,EAAO,KAAA;AAAA,MAEN,QAAA,EAAA;AAAA,QAAA,OAAA,KAAY,MAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,0BASX,IAAA;AAAA,YAAC,KAAA;AAAA,YAAA;AAAA,cACC,SAAA,EAAU,aAAA;AAAA,cACT,GAAI,OAAA,KAAY,IAAA,GAAO,EAAE,aAAA,EAAe,OAAO,OAAA,CAAQ,MAAM,CAAA,EAAE,GAAI,EAAC;AAAA,cAErE,QAAA,EAAA;AAAA,gCAAA,GAAA,CAAC,GAAA,EAAA,EAAE,SAAA,EAAU,aAAA,EACV,QAAA,EAAA,OAAA,KAAY,OACT,CAAA,CAAE,gBAAA,GACF,CAAA,CAAE,iBAAA,CAAkB,cAAA,CAAe,OAAO,CAAA,EAAG,OAAA,CAAQ,MAAM,CAAA,EACjE,CAAA;AAAA,gBAKC,OAAA,EAAS,QAAA,mBACR,GAAA,CAAC,GAAA,EAAA,EAAE,SAAA,EAAU,wBAAuB,GAAA,EAAI,MAAA,EACrC,QAAA,EAAA,OAAA,CAAQ,QAAA,EACX,CAAA,GACE;AAAA;AAAA;AAAA;AACN,YACE,IAAA;AAAA,4BAEH,IAAA,EAAA,EAAG,SAAA,EAAU,kBAAA,EACX,QAAA,EAAA,UAAA,CAAW,IAAI,CAAC,EAAE,SAAA,EAAW,OAAA,uBAC5B,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,iBAAA,EAAkC,kBAAgB,SAAA,EAC/D,QAAA,EAAA;AAAA,0BAAA,GAAA,CAAC,QAAG,SAAA,EAAU,sBAAA,EAAwB,QAAA,EAAA,CAAA,CAAE,sBAAA,CAAuB,SAAS,CAAA,EAAE,CAAA;AAAA,0BAI1E,GAAA,CAAC,IAAA,EAAA,EAAG,SAAA,EAAU,uBAAA,EACX,QAAA,EAAA,OAAA,KAAY,SAAY,CAAA,CAAE,wBAAA,GAA2B,CAAA,EAAG,OAAO,CAAA,CAAA,CAAA,EAClE;AAAA,SAAA,EAAA,EAPoC,SAQtC,CACD,CAAA,EACH,CAAA;AAAA,QAEC,KAAA,KAAU,uBACT,IAAA,CAAA,QAAA,EAAA,EAKE,QAAA,EAAA;AAAA,0BAAA,GAAA,CAAC,IAAA,EAAA,EAAG,SAAA,EAAU,aAAA,EAAc,YAAA,EAAY,CAAA,CAAE,uBAAA,EAAyB,GAAA,EAAK,UAAA,EACrE,QAAA,EAAA,KAAA,CAAM,GAAA,CAAI,CAAC,KAAA,EAAO,KAAA,KAAU;AAC3B,YAAA,MAAM,QAAA,GAAW,YAAA,CAAa,KAAA,EAAO,CAAC,CAAA;AACtC,YAAA,MAAM,QAAQ,KAAA,CAAM,KAAA,KAAU,UAAA,GAAa,KAAA,CAAM,QAAQ,KAAA,CAAM,SAAA;AAC/D,YAAA,MAAM,IAAA,GACJ,MAAM,SAAA,KAAc,MAAA,GAAY,SAAY,aAAA,CAAc,EAAA,CAAG,MAAM,SAAS,CAAA;AAC9E,YAAA,MAAM,OAAO,QAAA,KAAa,KAAA;AAC1B,YAAA,MAAM,OAAA,GAAU,CAAA,EAAG,GAAG,CAAA,EAAA,EAAK,KAAK,CAAA,CAAA;AAChC,YAAA,uBACE,IAAA;AAAA,cAAC,IAAA;AAAA,cAAA;AAAA,gBAGC,SAAA,EAAU,YAAA;AAAA,gBACV,cAAY,KAAA,CAAM,KAAA;AAAA,gBAMlB,QAAA,EAAA;AAAA,kCAAA,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,oBAAA,EAAqB,KAAA,EAAO,iBACzC,QAAA,EAAA,QAAA,EACH,CAAA;AAAA,kCAIA,GAAA;AAAA,oBAAC,MAAA;AAAA,oBAAA;AAAA,sBACC,SAAA,EAAU,iBAAA;AAAA,sBACV,aAAA,EAAY,MAAA;AAAA,sBACZ,KAAK,UAAA,IAAc,MAAA;AAAA,sBACnB,IAAA,EAAM,WAAA;AAAA,sBAEL,QAAA,EAAA;AAAA;AAAA,mBACH;AAAA,kBACC,SAAA,CAAU,IAAI,CAAA,mBACb,IAAA,CAAA,QAAA,EAAA,EACE,QAAA,EAAA;AAAA,oCAAA,GAAA;AAAA,sBAAC,QAAA;AAAA,sBAAA;AAAA,wBACC,IAAA,EAAK,QAAA;AAAA,wBACL,SAAA,EAAU,mBAAA;AAAA,wBACV,eAAA,EAAe,IAAA;AAAA,wBACf,eAAA,EAAe,OAAA;AAAA,wBACf,OAAA,EAAS,MAAM,WAAA,CAAY,IAAA,GAAO,OAAO,KAAK,CAAA;AAAA,wBAE7C,QAAA,EAAA,CAAA,CAAE,yBAAyB,KAAK;AAAA;AAAA,qBACnC;AAAA,oBACC,uBACC,IAAA,CAAC,KAAA,EAAA,EAAI,EAAA,EAAI,OAAA,EAAS,WAAU,mBAAA,EAC1B,QAAA,EAAA;AAAA,sCAAA,IAAA,CAAC,IAAA,EAAA,EAAG,WAAU,kBAAA,EACZ,QAAA,EAAA;AAAA,wCAAA,GAAA,CAAC,IAAA,EAAA,EAAI,QAAA,EAAA,CAAA,CAAE,sBAAA,CAAuB,UAAU,CAAA,EAAE,CAAA;AAAA,wCAC1C,GAAA,CAAC,IAAA,EAAA,EACE,QAAA,EAAA,IAAA,CAAK,QAAA,KAAa,MAAA,GACf,CAAA,CAAE,wBAAA,GACF,CAAA,EAAG,IAAA,CAAK,KAAA,CAAM,IAAA,CAAK,QAAQ,CAAC,CAAA,CAAA,CAAA,EAClC,CAAA;AAAA,wBACC,KAAK,SAAA,KAAc,MAAA,IAAa,KAAK,SAAA,CAAU,MAAA,GAAS,oBACvD,IAAA,CAAA,QAAA,EAAA,EACE,QAAA,EAAA;AAAA,0CAAA,GAAA,CAAC,IAAA,EAAA,EAAI,YAAE,sBAAA,EAAuB,CAAA;AAAA,0CAC9B,GAAA,CAAC,IAAA,EAAA,EACC,QAAA,kBAAA,GAAA,CAAC,IAAA,EAAA,EAAG,SAAA,EAAU,iBAAA,EACX,QAAA,EAAA,IAAA,CAAK,SAAA,CAAU,GAAA,CAAI,CAAC,QAAA,EAAU,QAAA,qBAC7B,GAAA;AAAA,4BAAC,IAAA;AAAA,4BAAA;AAAA,8BAGC,KAAK,UAAA,IAAc,MAAA;AAAA,8BACnB,IAAA,EAAM,WAAA;AAAA,8BAEL,QAAA,EAAA,QAAA,CAAS,QAAA,KAAa,MAAA,GACnB,QAAA,CAAS,OACT,CAAA,CAAE,6BAAA;AAAA,gCACA,QAAA,CAAS,IAAA;AAAA,gCACT,QAAA,CAAS;AAAA;AACX,6BAAA;AAAA,4BATC;AAAA,2BAWR,GACH,CAAA,EACF;AAAA,yBAAA,EACF,CAAA,GACE,IAAA;AAAA,wBACH,KAAK,QAAA,KAAa,MAAA,IAAa,KAAK,QAAA,CAAS,MAAA,GAAS,oBACrD,IAAA,CAAA,QAAA,EAAA,EACE,QAAA,EAAA;AAAA,0CAAA,GAAA,CAAC,IAAA,EAAA,EAAI,YAAE,qBAAA,EAAsB,CAAA;AAAA,0CAC7B,GAAA,CAAC,IAAA,EAAA,EACC,QAAA,kBAAA,GAAA,CAAC,IAAA,EAAA,EAAG,SAAA,EAAU,gBAAA,EACX,QAAA,EAAA,IAAA,CAAK,QAAA,CAAS,GAAA,CAAI,CAAC,OAAA,EAAS,QAAA,qBAC3B,IAAA;AAAA,4BAAC,IAAA;AAAA,4BAAA;AAAA,8BAGC,SAAA,EAAU,eAAA;AAAA,8BAEV,QAAA,EAAA;AAAA,gDAAA,GAAA,CAAC,UAAK,SAAA,EAAU,sBAAA,EACb,uBAAa,OAAA,EAAS,QAAA,EAAU,CAAC,CAAA,EACpC,CAAA;AAAA,gCACC,QAAQ,OAAA,KAAY,MAAA,IACrB,QAAQ,OAAA,CAAQ,MAAA,GAAS,oBACvB,IAAA,CAAA,QAAA,EAAA,EACE,QAAA,EAAA;AAAA,kDAAA,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,mBAAA,EACb,QAAA,EAAA,CAAA,CAAE,oBAAA,EACL,CAAA;AAAA,kDACA,GAAA,CAAC,QAAG,SAAA,EAAU,gBAAA,EACX,kBAAQ,OAAA,CAAQ,GAAA,CAAI,CAAC,SAAA,EAAW,IAAA,qBAC/B,GAAA;AAAA,oCAAC,IAAA;AAAA,oCAAA;AAAA,sCAIE,QAAA,EAAA,SAAA,CAAU;AAAA,qCAAA;AAAA,oCAFN;AAAA,mCAIR,CAAA,EACH;AAAA,iCAAA,EACF,CAAA,GACE;AAAA;AAAA,6BAAA;AAAA,4BAvBC;AAAA,2BAyBR,GACH,CAAA,EACF;AAAA,yBAAA,EACF,CAAA,GACE;AAAA,uBAAA,EACN,CAAA;AAAA,sBACC,QAAA,IACD,OAAO,IAAA,CAAK,OAAA,KAAY,YACxB,OAAO,IAAA,CAAK,eAAe,QAAA,mBACzB,GAAA;AAAA,wBAAC,QAAA;AAAA,wBAAA;AAAA,0BACC,IAAA,EAAK,QAAA;AAAA,0BACL,SAAA,EAAU,iBAAA;AAAA,0BACV,SAAS,MACP,QAAA,CAAS,IAAA,CAAK,OAAA,EAAmB,KAAK,UAAoB,CAAA;AAAA,0BAG3D,QAAA,EAAA,CAAA,CAAE,sBAAsB,KAAK;AAAA;AAAA,uBAChC,GACE,IAAA;AAAA,sBACH,IAAA,CAAK,IAAA,CAAK,MAAA,EAAQ,UAAA,EAAY,cAAc,CAAA,mBAC3C,GAAA,CAAC,GAAA,EAAA,EAAE,SAAA,EAAU,aAAA,EAAc,YAAA,EAAW,YAAA,EACnC,QAAA,EAAA,CAAA,CAAE,8BACL,CAAA,GACE,IAAA;AAAA,sBACH,IAAA,CAAK,IAAA,CAAK,MAAA,EAAQ,OAAA,EAAS,cAAc,CAAA,mBACxC,GAAA,CAAC,GAAA,EAAA,EAAE,SAAA,EAAU,aAAA,EAAc,YAAA,EAAW,SAAA,EACnC,QAAA,EAAA,CAAA,CAAE,2BACL,CAAA,GACE;AAAA,qBAAA,EACN,CAAA,GACE;AAAA,mBAAA,EACN,CAAA,GACE;AAAA;AAAA,eAAA;AAAA,cAnIC;AAAA,aAoIP;AAAA,UAEJ,CAAC,CAAA,EACH,CAAA;AAAA,8BACC,IAAA,EAAA,EAAG,SAAA,EAAU,gBACX,QAAA,EAAA,aAAA,CAAc,GAAA,CAAI,CAAC,KAAA,qBAClB,GAAA,CAAC,IAAA,EAAA,EAAe,YAAA,EAAY,OACzB,QAAA,EAAA,CAAA,CAAE,mBAAA,CAAoB,KAAK,CAAA,EAAA,EADrB,KAET,CACD,CAAA,EACH,CAAA;AAAA,UAIC,QAAA,KAAa,wBAAQ,GAAA,CAAC,GAAA,EAAA,EAAE,WAAU,gBAAA,EAAkB,QAAA,EAAA,CAAA,CAAE,sBAAqB,CAAA,GAAO;AAAA,SAAA,EACrF,CAAA,GACE,IAAA;AAAA,QAEH,IAAA,CAAK,QAAA,EAAU,iBAAiB,CAAA,mBAC/B,GAAA,CAAC,GAAA,EAAA,EAAE,SAAA,EAAU,gBAAA,EAAiB,IAAA,EAAK,MAAA,EAChC,QAAA,EAAA,CAAA,CAAE,qBAAA,EACL,CAAA,GACE,IAAA;AAAA,QAEH,QAAA,KAAa,MAAA,GAAY,IAAA,GAAO,QAAA,CAAS,OAAA,mBACxC,GAAA,CAAC,GAAA,EAAA,EAAE,SAAA,EAAU,yBAAA,EAA0B,IAAA,EAAK,MAAA,EACzC,QAAA,EAAA,CAAA,CAAE,4BAAA,EACL,CAAA;AAAA;AAAA,0BAGA,GAAA;AAAA,YAAC,OAAA;AAAA,YAAA;AAAA,cACC,GAAA,EAAK,QAAA;AAAA,cACL,SAAA,EAAU,aAAA;AAAA,cACV,GAAA,EAAK,QAAA;AAAA,cACL,OAAA,EAAQ,UAAA;AAAA,cACR,SAAS,CAAC,KAAA,KAAU,QAAA,CAAS,MAAA,CAAO,MAAM,aAAa;AAAA;AAAA;AACzD;AAAA;AAAA;AAAA,GAEJ;AAEJ;ACjhBO,SAASC,uBAAsB,KAAA,EAAmC;AACvE,EAAA,MAAM,OAAA,GAAU,YAAA,CAAa,KAAA,CAAM,OAAO,CAAA;AAC1C,EAAA,uBACEC,IAAC,qBAAA,EAAA,EAAsB,OAAA,EACrB,0BAAAA,GAAAA,CAAC,qBAAA,EAAA,EAA2B,GAAG,KAAA,EAAO,CAAA,EACxC,CAAA;AAEJ","file":"chunk-M3K6BF5M.js","sourcesContent":["'use client';\n\nimport { useCallback, useState } from 'react';\n\n/**\n * Whether the page refused to play the learner's own take, and the handler\n * every element that plays it reports a refusal through.\n *\n * The take is played from a `blob:` URL — the preview of a take not yet sent —\n * or from a link a review minted, and a page can refuse either without a word:\n * a Content-Security-Policy whose `media-src` does not allow `blob:` blocks the\n * load, the element fires `error`, and every control that plays it goes on\n * looking as if it works. So the refusal is decided in one place, by the URL\n * that was refused: a new take is a new URL and starts out playable again, and\n * a refusal an element reports after it was handed another URL cannot mark the\n * new one.\n *\n * `refuse` takes the element, not the URL, and reads the address the element\n * is loading at the moment it fails: an `error` belongs to the source the\n * element holds when it fires, and a closure's URL may already be a render\n * behind it.\n */\nexport function usePlaybackRefusal(url: string | null | undefined): {\n refused: boolean;\n refuse: (element: HTMLMediaElement) => void;\n} {\n const [refusedUrl, setRefusedUrl] = useState<string | null>(null);\n const refuse = useCallback((element: HTMLMediaElement) => {\n const source = element.getAttribute('src');\n if (source !== null) {\n setRefusedUrl(source);\n }\n }, []);\n return { refused: url !== null && url !== undefined && url === refusedUrl, refuse };\n}\n\n/**\n * Whether a rejected `play()` is the page refusing the source, rather than an\n * autoplay policy refusing the gesture. Only the first is the take's to report:\n * an autoplay refusal says nothing about whether the take can be played.\n */\nexport function isSourceRefusal(error: unknown): boolean {\n return (\n typeof error === 'object' &&\n error !== null &&\n (error as { name?: unknown }).name === 'NotSupportedError'\n );\n}\n","'use client';\n\nimport type {\n CriterionScore,\n GradeRecord,\n ReadAloudDimension,\n ScoringDetail,\n ScoringOutcome,\n SpeechAssessment,\n} from '@intellectif/lk-core';\n\n/**\n * The one reader of a grade that arrives from a host.\n *\n * Nothing validates a `GradeRecord`, an `ItemOutcome` or an assess result on\n * the way in: each is the application's, it crosses a network, and a field a\n * backend serialised as `null`, as a string or as an object is the ordinary way\n * to meet one. Every render path used to dereference them in its own way, so\n * every guard added for one shape left its sibling open — `criteria: null`\n * guarded and `criteria: [null]` not, a numeric `score` guarded and a string\n * `maxScore` shown as 8200%, `feedback` as an object taking a whole activity\n * down, `passed: null` dropping a grade from every callback in development and\n * sending an invalid statement in production.\n *\n * So the question \"what did the host send?\" has one answer, given here. The\n * panel, the outcome summary, the announcement, the decision about who shows\n * the score, `onComplete` and the xAPI statement read ONLY what these functions\n * return, and a payload they refuse is shown as could-not-be-graded — never the\n * error boundary, never `NaN`, never a percentage invented from a string.\n *\n * The rules, once:\n *\n * - **A required field is well-formed or the grade is refused.** `score` and\n * `maxScore` are finite numbers, `maxScore` above 0 and `score` between 0 and\n * it; `passed` is a boolean. A numeric string is not a number: `\"100\"` is how\n * a grade out of 100 turned into 8200%, and coercing it would only move the\n * guess somewhere else.\n * - **An optional field is absent when it is `undefined` or `null`** — how a\n * backend writes an absent optional — and otherwise well-formed or dropped.\n * `feedback` that is not a string is no feedback.\n * - **`criteria` keeps each well-formed entry and drops the rest.** Criteria are\n * named rows, each independent: an entry that cannot be read names no row.\n * - **`details` is read whole or not at all.** It is the word marks in order,\n * and a list with one word quietly missing would tell a learner they never\n * read a word they did.\n * - **Nothing here throws.** A payload whose getter throws is a payload that\n * cannot be read.\n */\n\n/** Marks a grade as read by {@link readGrade}. Type-only: it never exists at run time. */\ndeclare const READ: unique symbol;\n\n/**\n * A grade {@link readGrade} has checked: `0 <= score <= maxScore`, `maxScore >\n * 0`, both finite, `passed` a boolean, `feedback` a string or `null`, and\n * `criteria` and `details` (when present) holding only well-formed entries.\n * Still a `GradeRecord`, so it can be handed to anything that takes one — and\n * reading it again gives the same grade.\n */\nexport type ReadGrade = GradeRecord & { readonly [READ]: true };\n\n/** A stored outcome, as a renderer may show it. */\nexport type OutcomeReading =\n /** A grade to show: the outcome's own for `scored`, its `grade` for `graded`. */\n | { kind: 'graded'; status: 'scored' | 'graded'; grade: ReadGrade }\n | { kind: 'deferred' }\n /** `code` is a developer's label: put on an attribute, never shown. */\n | { kind: 'unscorable'; code: string | undefined }\n /** Claimed a grade, or claimed nothing a renderer knows, and could not be read. */\n | { kind: 'unreadable'; status: 'scored' | 'graded' | undefined };\n\n/** An assessor's answer, as a renderer and a statement may use it. */\nexport type AssessReading =\n | {\n status: 'graded';\n /** `null` when what came back cannot be read as a grade. */\n grade: ReadGrade | null;\n /** The evidence when it is an object at all; whether it VALIDATES is the panel's question. */\n assessment: SpeechAssessment | null;\n recognizedText: string | undefined;\n }\n | { status: 'unscorable'; code: string | undefined }\n | { status: 'failed'; retryable: boolean };\n\n/** One dimension row: a whole percentage, or `undefined` for a dimension nobody measured. */\nexport interface DimensionReading {\n dimension: ReadAloudDimension;\n percent: number | undefined;\n}\n\n/**\n * Every dimension a read-aloud grade can weigh, in the order a learner reads\n * them: what was said, how it flowed, how much of it was there, and how it was\n * pitched. Fixed rather than derived from the evidence, so a dimension the\n * engine did not measure is reported as unmeasured instead of vanishing.\n */\nconst DIMENSIONS: readonly ReadAloudDimension[] = [\n 'accuracy',\n 'fluency',\n 'completeness',\n 'prosody',\n];\n\nconst OUTCOMES: readonly ScoringOutcome[] = [\n 'correct',\n 'incorrect',\n 'correct-omission',\n 'incorrect-omission',\n];\n\n/**\n * How far past its own maximum a score may sit and still be read as that\n * maximum. A grader that sums weighted parts lands on `1.0000000000000002`\n * for a perfect take as readily as on `1`, and \"could not be graded\" is the\n * wrong thing to tell the learner who earned it. Far below any difference a\n * whole percentage can show.\n */\nconst OVERSHOOT = 1e-9;\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction isFiniteNumber(value: unknown): value is number {\n return typeof value === 'number' && Number.isFinite(value);\n}\n\n/** An optional field a backend left out, written either way it writes one. */\nfunction isAbsent(value: unknown): value is null | undefined {\n return value === undefined || value === null;\n}\n\n/** A score on `0..max`, a hair of overshoot read as `max`; `null` for anything else. */\nfunction withinMax(score: unknown, max: number): number | null {\n if (!isFiniteNumber(score) || score < 0) {\n return null;\n }\n if (score <= max) {\n return score;\n }\n return score - max <= max * OVERSHOOT ? max : null;\n}\n\n/** A learner's or a reference response: a string, or strings. */\nfunction isResponse(value: unknown): value is string | string[] {\n return (\n typeof value === 'string' ||\n (Array.isArray(value) && value.every((entry) => typeof entry === 'string'))\n );\n}\n\n/** Runs a read that touches a host's object, whose getters may throw. */\nfunction guarded<T>(read: () => T, refused: T): T {\n try {\n return read();\n } catch {\n return refused;\n }\n}\n\nfunction readCriterion(value: unknown): CriterionScore | null {\n if (!isRecord(value) || typeof value.name !== 'string') {\n return null;\n }\n const { maxScore, notApplicable } = value;\n if (!isAbsent(maxScore) && !(isFiniteNumber(maxScore) && maxScore > 0)) {\n return null;\n }\n if (!isAbsent(notApplicable) && typeof notApplicable !== 'boolean') {\n return null;\n }\n // `CriterionScore.maxScore` defaults to 1, the SDK's scaled convention.\n const score = isAbsent(value.score) ? undefined : withinMax(value.score, maxScore ?? 1);\n if (score === null) {\n return null;\n }\n return {\n name: value.name,\n ...(score !== undefined ? { score } : {}),\n ...(!isAbsent(maxScore) ? { maxScore } : {}),\n ...(notApplicable === true ? { notApplicable } : {}),\n };\n}\n\nfunction readDetail(value: unknown): ScoringDetail | null {\n if (!isRecord(value)) {\n return null;\n }\n const { itemId, correct, outcome, learnerResponse, correctResponse, weight, score } = value;\n if (\n typeof itemId !== 'string' ||\n typeof correct !== 'boolean' ||\n !isResponse(learnerResponse) ||\n !isResponse(correctResponse)\n ) {\n return null;\n }\n const checkedOutcome = OUTCOMES.find((known) => known === outcome);\n if (!isAbsent(outcome) && checkedOutcome === undefined) {\n return null;\n }\n if (!isAbsent(weight) && !isFiniteNumber(weight)) {\n return null;\n }\n // A detail's own score is a fraction of 1.\n const checkedScore = isAbsent(score) ? undefined : withinMax(score, 1);\n if (checkedScore === null) {\n return null;\n }\n return {\n itemId,\n correct,\n learnerResponse,\n correctResponse,\n ...(checkedOutcome !== undefined ? { outcome: checkedOutcome } : {}),\n ...(!isAbsent(weight) ? { weight } : {}),\n ...(checkedScore !== undefined ? { score: checkedScore } : {}),\n };\n}\n\nfunction readDetails(value: unknown): ScoringDetail[] | undefined {\n if (!Array.isArray(value)) {\n return undefined;\n }\n const details: ScoringDetail[] = [];\n for (const entry of value) {\n const detail = readDetail(entry);\n if (detail === null) {\n return undefined;\n }\n details.push(detail);\n }\n return details;\n}\n\n/**\n * A host's grade as a checked copy, or `null` when it carries no grade that can\n * be shown or reported. See the module notes for the rules.\n */\nexport function readGrade(value: unknown): ReadGrade | null {\n return guarded(() => {\n if (!isRecord(value)) {\n return null;\n }\n const { maxScore, passed } = value;\n if (!isFiniteNumber(maxScore) || maxScore <= 0 || typeof passed !== 'boolean') {\n return null;\n }\n const score = withinMax(value.score, maxScore);\n if (score === null) {\n return null;\n }\n const criteria = Array.isArray(value.criteria)\n ? value.criteria.flatMap((entry) => {\n const criterion = readCriterion(entry);\n return criterion === null ? [] : [criterion];\n })\n : undefined;\n const details = readDetails(value.details);\n const grade: GradeRecord = {\n score,\n maxScore,\n passed,\n feedback: typeof value.feedback === 'string' ? value.feedback : null,\n ...(criteria !== undefined ? { criteria } : {}),\n ...(details !== undefined ? { details } : {}),\n };\n return grade as ReadGrade;\n }, null);\n}\n\n/** A read grade as a fraction of 1: in `0..1`, because the reader made it so. */\nexport function fractionOfGrade(grade: ReadGrade): number {\n return grade.score / grade.maxScore;\n}\n\n/** A read grade as a whole percentage: in `0..100`, because the reader made it so. */\nexport function percentOfGrade(grade: ReadGrade): number {\n return Math.round(fractionOfGrade(grade) * 100);\n}\n\n/**\n * A stored outcome as a renderer may show it, or `undefined` for none —\n * `null` included, which is how a backend writes an absent optional.\n *\n * A `graded` outcome is read through its `grade`, the record\n * `outcomeFromGrade` mirrors onto it, so the summary and the panel beside it\n * can never show two different numbers; and it must be well-formed as a whole,\n * its own `score`, `maxScore` and `passed` included — a payload with a corrupt\n * copy of the grade is not one to pick a side of.\n */\nexport function readOutcome(value: unknown): OutcomeReading | undefined {\n if (isAbsent(value)) {\n return undefined;\n }\n return guarded(\n (): OutcomeReading => {\n if (!isRecord(value)) {\n return { kind: 'unreadable', status: undefined };\n }\n switch (value.status) {\n case 'deferred':\n return { kind: 'deferred' };\n case 'unscorable':\n return {\n kind: 'unscorable',\n code: typeof value.code === 'string' ? value.code : undefined,\n };\n case 'scored': {\n const grade = readGrade(value);\n return grade === null\n ? { kind: 'unreadable', status: 'scored' }\n : { kind: 'graded', status: 'scored', grade };\n }\n case 'graded': {\n const grade = readGrade(value.grade);\n return grade === null || readGrade(value) === null\n ? { kind: 'unreadable', status: 'graded' }\n : { kind: 'graded', status: 'graded', grade };\n }\n default:\n return { kind: 'unreadable', status: undefined };\n }\n },\n { kind: 'unreadable', status: undefined },\n );\n}\n\n/**\n * An assessor's answer as a renderer and a statement may use it.\n *\n * `failed` and `unscorable` need little: a `retryable` that is not `true`\n * offers no retry, which never spends an application's money on a guess, and a\n * `code` that is not a string is no code. Everything else — a `graded` result,\n * and anything that is not an object or names no status this component knows —\n * is read as a judgement: its grade when one can be read, its evidence when it\n * is an object. A judgement with neither is shown as could-not-be-graded.\n */\nexport function readAssessResult(value: unknown): AssessReading {\n const nothing: AssessReading = {\n status: 'graded',\n grade: null,\n assessment: null,\n recognizedText: undefined,\n };\n return guarded((): AssessReading => {\n if (!isRecord(value)) {\n return nothing;\n }\n if (value.status === 'failed') {\n return { status: 'failed', retryable: value.retryable === true };\n }\n if (value.status === 'unscorable') {\n return {\n status: 'unscorable',\n code: typeof value.code === 'string' ? value.code : undefined,\n };\n }\n if (value.status !== 'graded') {\n return nothing;\n }\n const assessment = readEvidence(value.assessment);\n const recognizedText = guarded(\n () =>\n typeof assessment?.recognizedText === 'string' ? assessment.recognizedText : undefined,\n undefined,\n );\n return { status: 'graded', grade: readGrade(value.grade), assessment, recognizedText };\n }, nothing);\n}\n\n/**\n * Evidence worth handing to the panel: an object. Whether it validates is the\n * panel's to decide, because what a refusal costs differs by mode.\n */\nexport function readEvidence(value: unknown): SpeechAssessment | null {\n return isRecord(value) ? (value as unknown as SpeechAssessment) : null;\n}\n\n/** One criterion's whole percentage of its own maximum, or `undefined` when it carries none. */\nfunction percentOfCriterion(criterion: CriterionScore): number | undefined {\n if (criterion.notApplicable === true || criterion.score === undefined) {\n return undefined;\n }\n return Math.round((criterion.score / (criterion.maxScore ?? 1)) * 100);\n}\n\n/**\n * The dimension rows, from one source for the whole set and never a mix: the\n * grade's criteria when it has any it can read, and the evidence's own scores\n * otherwise. A dimension pulled out of the evidence and set beside graded ones\n * would read as if it had counted towards the grade.\n *\n * The evidence is read as defensively as the grade: in production the panel\n * shows these rows even for evidence the validator refused, which can carry\n * anything at all under `scores`. `scale` is 100 by schema, so an evidence\n * score is already a percentage — and one outside `0..100` is not one.\n */\nexport function readDimensions(\n assessment: unknown,\n grade: ReadGrade | null | undefined,\n): DimensionReading[] {\n const criteria = grade?.criteria ?? [];\n if (criteria.length > 0) {\n return DIMENSIONS.map((dimension) => {\n const criterion = criteria.find((entry) => entry.name === dimension);\n return {\n dimension,\n percent: criterion === undefined ? undefined : percentOfCriterion(criterion),\n };\n });\n }\n const scores = guarded(() => (isRecord(assessment) ? assessment.scores : undefined), undefined);\n return DIMENSIONS.map((dimension) => {\n const score = withinMax(\n guarded(() => (isRecord(scores) ? scores[dimension] : undefined), undefined),\n 100,\n );\n // Rounded rather than reformatted: the engine's own number is what was graded.\n return { dimension, percent: score === null ? undefined : Math.round(score) };\n });\n}\n","'use client';\n\nimport {\n ActivitySchemaError,\n alignReadAloud,\n type GradeRecord,\n type ReadAloudData,\n type ReadAloudWordAlignment,\n type SpeechAssessment,\n type SpeechPhoneme,\n type SpeechWord,\n type ThemeTokens,\n type ValidationResult,\n validateSpeechAssessment,\n} from '@intellectif/lk-core';\nimport { type CSSProperties, useEffect, useId, useMemo, useRef, useState } from 'react';\nimport { localeDirectionOf } from '../../i18n/direction.js';\nimport { useLkStrings } from '../../i18n/LkIntlProvider.js';\nimport type { LkStrings, LkStringsOverride } from '../../i18n/strings.js';\nimport { isDevelopment } from '../_internal.js';\nimport { isSourceRefusal, usePlaybackRefusal } from '../shared/playback-refusal.js';\nimport { percentOfGrade, readDimensions, readGrade } from '../shared/read-grade.js';\n\n/**\n * Present for assistive technology, invisible on screen. Inline rather than\n * left to the optional skin, for the reason `<Dictation>` gives: the per-word\n * sentences are the marks' only channel for a screen reader, and a page that\n * does not load the skin would otherwise show every one of them beside its\n * word. Exported so `<ReadAloud>`, whose review fallback renders the same\n * marks from stored details, carries one definition rather than a copy.\n */\nexport const VISUALLY_HIDDEN: CSSProperties = {\n position: 'absolute',\n width: 1,\n height: 1,\n margin: -1,\n padding: 0,\n overflow: 'hidden',\n clip: 'rect(0 0 0 0)',\n clipPath: 'inset(50%)',\n whiteSpace: 'nowrap',\n border: 0,\n};\n\n/** The four marking states, in the order the legend lists them. */\nconst LEGEND_STATES: readonly ReadAloudWordAlignment['state'][] = [\n 'correct',\n 'mispronounced',\n 'omitted',\n 'inserted',\n];\n\nexport interface PronunciationFeedbackProps {\n /**\n * The item's reading and its language. Only these two fields, because they\n * are the only ones a mark depends on — everything else about the activity\n * belongs to whoever rendered it.\n */\n data: Pick<ReadAloudData, 'referenceText' | 'locale'>;\n /**\n * The assessor's evidence. Checked with `validateSpeechAssessment` before it\n * is aligned: see the note on this component about what a bad one costs.\n */\n assessment: SpeechAssessment;\n /**\n * The grade the evidence produced, when one exists. Its `criteria` are what\n * the dimensions are read from, because they are the numbers the learner was\n * actually graded on. A grade that cannot be read — a score that is not a\n * number between 0 and its maximum, a `passed` that is not a boolean — is\n * shown as could-not-be-graded, never as a percentage made up from it.\n */\n grade?: GradeRecord;\n /**\n * The learner's own take, playable. With it, each word that carries timings\n * gets a button that plays just that word. A take the page refuses to play —\n * a `blob:` URL under a Content-Security-Policy whose `media-src` does not\n * allow one, most often — offers no such button, and the panel says the take\n * cannot be played here.\n */\n audioUrl?: string;\n /**\n * Confidence above which a break note is shown, 0..1. **No default**: how\n * confident an engine has to be before a pause is worth telling a learner\n * about is a calibration, and the SDK has no calibrated answer. Without it no\n * break note is shown at all.\n */\n breakThreshold?: number;\n /** The same, for the monotone note read off `assessment.prosody`. */\n monotoneThreshold?: number;\n /**\n * BCP 47 tag stamped as `lang` on the root: the INTERFACE language, the one\n * this component's own sentences are written in. It is not `data.locale`,\n * which is the language of the reading and is put on the words themselves.\n */\n locale?: string;\n /** Per-instance token overrides, applied as inline CSS vars on the root. */\n theme?: Partial<ThemeTokens>;\n /** Overrides the SDK's chrome text for this panel. See {@link LkIntlProvider}. */\n strings?: LkStringsOverride;\n}\n\n/**\n * What the validator says about evidence that may not even be readable. Its\n * cross-field rules read the value directly, so a getter that throws escapes\n * it — and evidence that cannot be read is evidence refused, not a reason to\n * take the grade beside it down.\n */\nfunction checkEvidence(assessment: unknown): ValidationResult<SpeechAssessment> {\n try {\n return validateSpeechAssessment(assessment);\n } catch {\n return {\n success: false,\n errors: [{ path: [], message: 'The assessment could not be read.', code: 'unreadable' }],\n };\n }\n}\n\n/** Whether a word carries anything a disclosure would have to show. */\nfunction hasDetail(word: SpeechWord | undefined): word is SpeechWord {\n if (word === undefined) {\n return false;\n }\n return (\n word.accuracy !== undefined ||\n (word.syllables !== undefined && word.syllables.length > 0) ||\n (word.phonemes !== undefined && word.phonemes.length > 0) ||\n word.breaks !== undefined ||\n (typeof word.startMs === 'number' && typeof word.durationMs === 'number')\n );\n}\n\n/** A confidence that clears the caller's threshold. No threshold, no note. */\nfunction past(confidence: number | undefined, threshold: number | undefined): boolean {\n return (\n threshold !== undefined &&\n typeof confidence === 'number' &&\n Number.isFinite(confidence) &&\n confidence > threshold\n );\n}\n\n/** One phoneme, named by its symbol or — when the engine named none — by its place. */\nfunction phonemeLabel(phoneme: SpeechPhoneme, index: number, s: LkStrings): string {\n return phoneme.symbol ?? s.pronunciationPhonemePosition(index + 1);\n}\n\n/**\n * What a screen reader hears for one marked word — the marks' only channel for\n * assistive technology, since the glyph and the decoration are both hidden from\n * it. Exported because `<ReadAloud>`'s review fallback marks the same four\n * states from stored details, and a second copy of these four sentences would\n * be a second thing to translate.\n *\n * Branches on `state` and **never** on `heard === ''`: an inserted word whose\n * text normalises to nothing carries the empty string too, and would otherwise\n * be announced as an omission.\n */\nexport function markSentence(entry: ReadAloudWordAlignment, s: LkStrings): string {\n switch (entry.state) {\n case 'correct':\n return s.pronunciationWordCorrect(entry.reference);\n case 'mispronounced':\n return s.pronunciationWordMispronounced(entry.reference);\n case 'omitted':\n return s.pronunciationWordOmitted(entry.reference);\n default:\n return s.pronunciationWordInserted(entry.heard);\n }\n}\n\n/**\n * The per-word marks of a read-aloud take, plus the dimensions they were\n * graded on.\n *\n * **A bad assessment never destroys a good grade.** `alignReadAloud` throws a\n * `TypeError` for evidence `validateSpeechAssessment` refuses, so this\n * component runs the validator first. In development the refusal is thrown as\n * an `ActivitySchemaError`, which is the package's dev-error convention and\n * puts the problem in front of whoever wired the adapter up. In production the\n * dimensions and the grade are rendered and the word list is left out — a\n * learner who earned 82% should see 82%, not \"this activity could not be\n * displayed\" from an error boundary.\n *\n * **Nor does a bad grade take the evidence down.** `grade` is read, not\n * trusted: a score that is not a number between 0 and its maximum, or a\n * `passed` that is not a boolean, is shown as could-not-be-graded beside the\n * dimensions and the marks, and criteria that cannot be read name no dimension.\n *\n * There is **no `renderMode`**: nothing here submits, grades or reveals an\n * answer key. A read-aloud item has none, and the marks exist only once a\n * server has graded the take, so there is no mode in which they must be\n * withheld.\n *\n * ```tsx\n * <PronunciationFeedback\n * data={item}\n * assessment={assessment}\n * grade={grade}\n * audioUrl={takeUrl}\n * breakThreshold={0.75}\n * monotoneThreshold={0.6}\n * />\n * ```\n */\nexport function PronunciationFeedback({\n data,\n assessment,\n grade,\n audioUrl,\n breakThreshold,\n monotoneThreshold,\n locale,\n theme,\n strings,\n}: PronunciationFeedbackProps) {\n const s = useLkStrings(strings);\n const ids = useId();\n const [openWord, setOpenWord] = useState<number | null>(null);\n\n const checked = useMemo(() => checkEvidence(assessment), [assessment]);\n\n const devError = useMemo(\n () =>\n isDevelopment() && !checked.success\n ? new ActivitySchemaError('read-aloud', checked.errors)\n : null,\n [checked],\n );\n\n // The checked copy, never the argument, for the same reason lk-core aligns\n // from it: a property getter cannot answer the validator one word and the\n // aligner another.\n const words = useMemo<ReadAloudWordAlignment[] | null>(\n () => (checked.success ? alignReadAloud(data, checked.data) : null),\n [checked, data],\n );\n\n // The grade as the one reader of a host's grade reads it: `undefined` when\n // none was given, and `null` when one was and cannot be shown. Everything\n // below that shows a number reads this and the dimension rows, never `grade`.\n const reading = useMemo(\n () => (grade === undefined || grade === null ? undefined : readGrade(grade)),\n [grade],\n );\n const dimensions = useMemo(() => readDimensions(assessment, reading), [assessment, reading]);\n\n // The take, for the per-word play buttons. A ref and an effect, never a read\n // during render: this component server-renders like every other one here.\n const audioRef = useRef<HTMLAudioElement>(null);\n const stopRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const playback = usePlaybackRefusal(audioUrl);\n // A take the page refused is offered to no button: pressed, each would do\n // nothing, which is the silent failure the note below replaces.\n const playable = audioUrl !== undefined && !playback.refused;\n useEffect(\n () => () => {\n if (stopRef.current !== null) {\n clearTimeout(stopRef.current);\n }\n },\n [],\n );\n\n // All hooks are called before this throw, so hook order stays stable.\n if (devError) {\n throw devError;\n }\n\n /** Plays one word out of the take, and stops at the end of it. */\n const playWord = (startMs: number, durationMs: number): void => {\n const element = audioRef.current;\n if (element === null) {\n return;\n }\n if (stopRef.current !== null) {\n clearTimeout(stopRef.current);\n }\n element.currentTime = startMs / 1000;\n try {\n void element.play().catch((error: unknown) => {\n // An autoplay refusal is not this component's to report; a source the\n // page will not load is, and the element's own `error` may not have\n // fired yet for an engine that loads nothing until asked.\n if (isSourceRefusal(error)) {\n playback.refuse(element);\n }\n });\n } catch {\n /* an engine with no media stack at all; the button simply does nothing */\n }\n stopRef.current = setTimeout(() => {\n stopRef.current = null;\n element.pause();\n }, durationMs);\n };\n\n // The reading's own language and the direction its tag names, put on the\n // words and on nothing else: the sentences beside them are interface text.\n // No `_`→`-` normalisation, unlike `<Dictation>` — `ReadAloudData.locale` is\n // canonical by 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 const alphabet = checked.success ? checked.data.phonemeAlphabet : undefined;\n const monotone = checked.success ? checked.data.prosody?.monotoneConfidence : undefined;\n // Read through `at`, not through an index: `wordIndex` belongs to a public\n // type a caller may have built by hand, and it is allowed to point nowhere.\n const evidenceWords: readonly SpeechWord[] = checked.success ? checked.data.words : [];\n\n return (\n <section\n className=\"lk-pf\"\n aria-label={s.pronunciationFeedbackLabel}\n lang={locale}\n style={theme as CSSProperties | undefined}\n >\n {reading !== undefined ? (\n // `.lk-pf-grade` is load-bearing beyond the skin: `<ReadAloud>` looks\n // for it after the commit to decide whether it must render the score\n // itself, so a grade is never lost between the two components. It\n // hands this panel only a grade it has already read, so the block it\n // finds always carries the score.\n //\n // `data-passed` only where there is a verdict: a grade that could not\n // be read has none, and styling it as a fail would invent one.\n <div\n className=\"lk-pf-grade\"\n {...(reading !== null ? { 'data-passed': String(reading.passed) } : {})}\n >\n <p className=\"lk-pf-score\">\n {reading === null\n ? s.couldNotBeGraded\n : s.scoreAnnouncement(percentOfGrade(reading), reading.passed)}\n </p>\n {/* The grader's words, isolated: their direction is their own, so an\n English comment in an Arabic interface keeps its full stop at its\n end. Their language is not guessed — feedback may be written in\n either the interface's language or the reading's. */}\n {reading?.feedback ? (\n <p className=\"lk-pf-grade-feedback\" dir=\"auto\">\n {reading.feedback}\n </p>\n ) : null}\n </div>\n ) : null}\n\n <dl className=\"lk-pf-dimensions\">\n {dimensions.map(({ dimension, percent }) => (\n <div className=\"lk-pf-dimension\" key={dimension} data-dimension={dimension}>\n <dt className=\"lk-pf-dimension-name\">{s.pronunciationDimension(dimension)}</dt>\n {/* An absent score is NEVER a 0: a dimension nobody measured and a\n dimension measured at nothing are different findings, and only\n one of them is the learner's. */}\n <dd className=\"lk-pf-dimension-score\">\n {percent === undefined ? s.pronunciationNotAssessed : `${percent}%`}\n </dd>\n </div>\n ))}\n </dl>\n\n {words !== null ? (\n <>\n {/* The list's name and the hidden sentences are interface text and\n keep the interface language; only the words carry the reading's.\n Its direction is the reading's, never resolved from the list's own\n text: the first strong character there is a hidden sentence. */}\n <ol className=\"lk-pf-words\" aria-label={s.pronunciationWordsLabel} dir={contentDir}>\n {words.map((entry, index) => {\n const sentence = markSentence(entry, s);\n const shown = entry.state === 'inserted' ? entry.heard : entry.reference;\n const word =\n entry.wordIndex === undefined ? undefined : evidenceWords.at(entry.wordIndex);\n const open = openWord === index;\n const panelId = `${ids}-w${index}`;\n return (\n <li\n // biome-ignore lint/suspicious/noArrayIndexKey: alignment order is the identity; words repeat\n key={index}\n className=\"lk-pf-word\"\n data-state={entry.state}\n >\n {/* The state's only channel for assistive technology, and\n exactly one channel: the same sentence as a `title` is an\n accessible description, which a screen reader reads\n straight after the name it duplicates. */}\n <span className=\"lk-visually-hidden\" style={VISUALLY_HIDDEN}>\n {sentence}\n </span>\n {/* Decoration: the glyph the skin draws and the word itself.\n Hidden from assistive technology, which already has the\n sentence above — and the glyph is never the only channel. */}\n <span\n className=\"lk-pf-word-text\"\n aria-hidden=\"true\"\n dir={contentDir ?? 'auto'}\n lang={contentLang}\n >\n {shown}\n </span>\n {hasDetail(word) ? (\n <>\n <button\n type=\"button\"\n className=\"lk-pf-word-toggle\"\n aria-expanded={open}\n aria-controls={panelId}\n onClick={() => setOpenWord(open ? null : index)}\n >\n {s.pronunciationWordDetails(shown)}\n </button>\n {open ? (\n <div id={panelId} className=\"lk-pf-word-detail\">\n <dl className=\"lk-pf-word-facts\">\n <dt>{s.pronunciationDimension('accuracy')}</dt>\n <dd>\n {word.accuracy === undefined\n ? s.pronunciationNotAssessed\n : `${Math.round(word.accuracy)}%`}\n </dd>\n {word.syllables !== undefined && word.syllables.length > 0 ? (\n <>\n <dt>{s.pronunciationSyllables}</dt>\n <dd>\n <ul className=\"lk-pf-syllables\">\n {word.syllables.map((syllable, position) => (\n <li\n // biome-ignore lint/suspicious/noArrayIndexKey: syllables are positional and repeat within a word\n key={position}\n dir={contentDir ?? 'auto'}\n lang={contentLang}\n >\n {syllable.grapheme === undefined\n ? syllable.text\n : s.pronunciationSyllableSpelling(\n syllable.text,\n syllable.grapheme,\n )}\n </li>\n ))}\n </ul>\n </dd>\n </>\n ) : null}\n {word.phonemes !== undefined && word.phonemes.length > 0 ? (\n <>\n <dt>{s.pronunciationPhonemes}</dt>\n <dd>\n <ul className=\"lk-pf-phonemes\">\n {word.phonemes.map((phoneme, position) => (\n <li\n // biome-ignore lint/suspicious/noArrayIndexKey: a word may sound the same phoneme twice\n key={position}\n className=\"lk-pf-phoneme\"\n >\n <span className=\"lk-pf-phoneme-symbol\">\n {phonemeLabel(phoneme, position, s)}\n </span>\n {phoneme.heardAs !== undefined &&\n phoneme.heardAs.length > 0 ? (\n <>\n <span className=\"lk-pf-heard-label\">\n {s.pronunciationHeardAs}\n </span>\n <ul className=\"lk-pf-heard-as\">\n {phoneme.heardAs.map((candidate, rank) => (\n <li\n // biome-ignore lint/suspicious/noArrayIndexKey: candidates are ranked and a symbol may repeat\n key={rank}\n >\n {candidate.symbol}\n </li>\n ))}\n </ul>\n </>\n ) : null}\n </li>\n ))}\n </ul>\n </dd>\n </>\n ) : null}\n </dl>\n {playable &&\n typeof word.startMs === 'number' &&\n typeof word.durationMs === 'number' ? (\n <button\n type=\"button\"\n className=\"lk-pf-word-play\"\n onClick={() =>\n playWord(word.startMs as number, word.durationMs as number)\n }\n >\n {s.pronunciationPlayWord(shown)}\n </button>\n ) : null}\n {past(word.breaks?.unexpected, breakThreshold) ? (\n <p className=\"lk-pf-break\" data-break=\"unexpected\">\n {s.pronunciationBreakUnexpected}\n </p>\n ) : null}\n {past(word.breaks?.missing, breakThreshold) ? (\n <p className=\"lk-pf-break\" data-break=\"missing\">\n {s.pronunciationBreakMissing}\n </p>\n ) : null}\n </div>\n ) : null}\n </>\n ) : null}\n </li>\n );\n })}\n </ol>\n <ul className=\"lk-pf-legend\">\n {LEGEND_STATES.map((state) => (\n <li key={state} data-state={state}>\n {s.pronunciationLegend(state)}\n </li>\n ))}\n </ul>\n {/* Only where there are symbols to explain, and only for the alphabet\n this note names. An engine that reported another one, or none at\n all, gets no note rather than a wrong one. */}\n {alphabet === 'ipa' ? <p className=\"lk-pf-alphabet\">{s.pronunciationIpaNote}</p> : null}\n </>\n ) : null}\n\n {past(monotone, monotoneThreshold) ? (\n <p className=\"lk-pf-monotone\" role=\"note\">\n {s.pronunciationMonotone}\n </p>\n ) : null}\n\n {audioUrl === undefined ? null : playback.refused ? (\n <p className=\"lk-pf-audio-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, and this element is never played as content — it carries no controls and exists only so one word can be replayed from a button that names it\n <audio\n ref={audioRef}\n className=\"lk-pf-audio\"\n src={audioUrl}\n preload=\"metadata\"\n onError={(event) => playback.refuse(event.currentTarget)}\n />\n )}\n </section>\n );\n}\n","'use client';\n\nimport { useLkStrings } from '../../i18n/LkIntlProvider.js';\nimport { ActivityErrorBoundary } from '../ActivityErrorBoundary.js';\nimport {\n PronunciationFeedback as PronunciationFeedbackCore,\n type PronunciationFeedbackProps,\n} from './PronunciationFeedback.js';\n\nexport type { PronunciationFeedbackProps } from './PronunciationFeedback.js';\n\n/**\n * Public pronunciation-feedback panel: the core wrapped in\n * `ActivityErrorBoundary` so a render failure (the dev throw for evidence\n * `validateSpeechAssessment` refuses among them) degrades to an accessible\n * fallback instead of crashing the host.\n *\n * **No `activityTitle`.** Its `data` is two fields of a read-aloud item and\n * carries no title, so the production fallback is the unnamed one. Naming it\n * would mean inventing a title for a panel that never had one.\n */\nexport function PronunciationFeedback(props: PronunciationFeedbackProps) {\n const strings = useLkStrings(props.strings);\n return (\n <ActivityErrorBoundary strings={strings}>\n <PronunciationFeedbackCore {...props} />\n </ActivityErrorBoundary>\n );\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/ai/LkAiProvider.tsx"],"names":[],"mappings":";;;;AAyCA,IAAM,WAAA,GAAc,cAAgC,IAAI,CAAA;AAqBjD,SAAS,YAAA,CAAa,EAAE,EAAA,EAAI,QAAA,EAAS,EAAyC;AACnF,EAAA,2BAAQ,WAAA,CAAY,QAAA,EAAZ,EAAqB,KAAA,EAAO,IAAK,QAAA,EAAS,CAAA;AACpD;AAGO,SAAS,aAAa,QAAA,EAA6C;AACxE,EAAA,MAAM,OAAA,GAAU,WAAW,WAAW,CAAA;AACtC,EAAA,OAAO,YAAY,OAAA,IAAW,MAAA;AAChC","file":"chunk-MCTNLUKO.js","sourcesContent":["'use client';\n\nimport type { AiExplanationRequest, AiHintRequest, AiTextResult } from '@intellectif/lk-core';\nimport { createContext, type ReactNode, useContext } from 'react';\n\n/**\n * The model a host runs for learners, as the components call it. The SDK never\n * calls a model, holds a key or writes a prompt: each port is the host's\n * function — usually a request to its own server, which holds the key and the\n * prompt and chooses the model.\n *\n * A port is called only when the learner asks — a button, never on render —\n * and gets an `AbortSignal` that fires when the answer it was asked about goes\n * away. Whatever it resolves to is checked before a learner sees it: see\n * `checkAiExplanation` and `checkAiHint` in lk-core. A port that rejects, or\n * resolves to something refused, costs the learner the help, never the\n * question.\n *\n * Leave a port out to switch that feature off. Nothing AI renders in `exam`,\n * whatever is passed.\n */\nexport interface LearnerAi {\n /** Explains a graded answer. */\n explain?: (\n request: AiExplanationRequest,\n options: { signal: AbortSignal },\n ) => Promise<AiTextResult>;\n /** Writes the next hint for an answer not yet submitted. */\n hint?: (request: AiHintRequest, options: { signal: AbortSignal }) => Promise<AiTextResult>;\n /** The most hints a learner can ask for on one question. Default 3, at most 10. */\n maxHints?: number;\n /**\n * The language to write explanations and hints in, as a tag (`es`,\n * `pt-BR`). Defaults to the component's `locale`: the interface language,\n * which the buttons around the help are in, and usually the right one. In a\n * language course it need not be the item's — English items, explained in\n * Spanish — and the item's own language reaches your port as `facts.locale`.\n */\n learnerLocale?: string;\n}\n\nconst LkAiContext = createContext<LearnerAi | null>(null);\n\nexport interface LkAiProviderProps {\n ai: LearnerAi;\n children: ReactNode;\n}\n\n/**\n * Supplies the host's AI ports to every activity beneath, as `LkIntlProvider`\n * supplies strings. A component's own `ai` prop wins over it, whole.\n *\n * ```tsx\n * <LkAiProvider ai={{\n * explain: (request, { signal }) => api.explain(request, signal),\n * hint: (request, { signal }) => api.hint(request, signal),\n * learnerLocale: learner.language,\n * }}>\n * <ActivitySequence … />\n * </LkAiProvider>\n * ```\n */\nexport function LkAiProvider({ ai, children }: LkAiProviderProps): React.JSX.Element {\n return <LkAiContext.Provider value={ai}>{children}</LkAiContext.Provider>;\n}\n\n/** The ports in force: a component's own, else the provider's, else none. */\nexport function useLearnerAi(override?: LearnerAi): LearnerAi | undefined {\n const context = useContext(LkAiContext);\n return override ?? context ?? undefined;\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/ai/LkAiProvider.tsx"],"names":["createContext","useContext"],"mappings":";;;;;;AAyCA,IAAM,WAAA,GAAcA,oBAAgC,IAAI,CAAA;AAqBjD,SAAS,YAAA,CAAa,EAAE,EAAA,EAAI,QAAA,EAAS,EAAyC;AACnF,EAAA,sCAAQ,WAAA,CAAY,QAAA,EAAZ,EAAqB,KAAA,EAAO,IAAK,QAAA,EAAS,CAAA;AACpD;AAGO,SAAS,aAAa,QAAA,EAA6C;AACxE,EAAA,MAAM,OAAA,GAAUC,iBAAW,WAAW,CAAA;AACtC,EAAA,OAAO,YAAY,OAAA,IAAW,MAAA;AAChC","file":"chunk-MPHX3Y42.cjs","sourcesContent":["'use client';\n\nimport type { AiExplanationRequest, AiHintRequest, AiTextResult } from '@intellectif/lk-core';\nimport { createContext, type ReactNode, useContext } from 'react';\n\n/**\n * The model a host runs for learners, as the components call it. The SDK never\n * calls a model, holds a key or writes a prompt: each port is the host's\n * function — usually a request to its own server, which holds the key and the\n * prompt and chooses the model.\n *\n * A port is called only when the learner asks — a button, never on render —\n * and gets an `AbortSignal` that fires when the answer it was asked about goes\n * away. Whatever it resolves to is checked before a learner sees it: see\n * `checkAiExplanation` and `checkAiHint` in lk-core. A port that rejects, or\n * resolves to something refused, costs the learner the help, never the\n * question.\n *\n * Leave a port out to switch that feature off. Nothing AI renders in `exam`,\n * whatever is passed.\n */\nexport interface LearnerAi {\n /** Explains a graded answer. */\n explain?: (\n request: AiExplanationRequest,\n options: { signal: AbortSignal },\n ) => Promise<AiTextResult>;\n /** Writes the next hint for an answer not yet submitted. */\n hint?: (request: AiHintRequest, options: { signal: AbortSignal }) => Promise<AiTextResult>;\n /** The most hints a learner can ask for on one question. Default 3, at most 10. */\n maxHints?: number;\n /**\n * The language to write explanations and hints in, as a tag (`es`,\n * `pt-BR`). Defaults to the component's `locale`: the interface language,\n * which the buttons around the help are in, and usually the right one. In a\n * language course it need not be the item's — English items, explained in\n * Spanish — and the item's own language reaches your port as `facts.locale`.\n */\n learnerLocale?: string;\n}\n\nconst LkAiContext = createContext<LearnerAi | null>(null);\n\nexport interface LkAiProviderProps {\n ai: LearnerAi;\n children: ReactNode;\n}\n\n/**\n * Supplies the host's AI ports to every activity beneath, as `LkIntlProvider`\n * supplies strings. A component's own `ai` prop wins over it, whole.\n *\n * ```tsx\n * <LkAiProvider ai={{\n * explain: (request, { signal }) => api.explain(request, signal),\n * hint: (request, { signal }) => api.hint(request, signal),\n * learnerLocale: learner.language,\n * }}>\n * <ActivitySequence … />\n * </LkAiProvider>\n * ```\n */\nexport function LkAiProvider({ ai, children }: LkAiProviderProps): React.JSX.Element {\n return <LkAiContext.Provider value={ai}>{children}</LkAiContext.Provider>;\n}\n\n/** The ports in force: a component's own, else the provider's, else none. */\nexport function useLearnerAi(override?: LearnerAi): LearnerAi | undefined {\n const context = useContext(LkAiContext);\n return override ?? context ?? undefined;\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/ai/rules.ts","../src/ai/useAiHelp.ts"],"names":[],"mappings":"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","file":"chunk-NDIKHMLJ.js","sourcesContent":["/**\n * When AI help is offered, and what the SDK does with what a port answers.\n *\n * Internal: these are the rules behind {@link useAiHints} and\n * {@link useAiExplanation}, which are what a host calls. They live apart from\n * the hooks so the hooks' module exports nothing but the hooks themselves.\n */\nimport {\n type AiRefusal,\n aiAllowedByContent,\n aiSupports,\n type ItemOutcome,\n type LearnerResponse,\n OPEN_DELIVERY_POLICY,\n type ResolvedDeliveryPolicy,\n} from '@intellectif/lk-core';\nimport { isDevelopment } from '../components/_internal.js';\nimport type { RenderableActivity, RenderMode } from '../components/types.js';\nimport type { LearnerAi } from './LkAiProvider.js';\n\n/** The most hints a question gives when the host names no number, and the most it may name. */\nconst DEFAULT_MAX_HINTS = 3;\nconst MAX_HINTS_CEILING = 10;\n\n/** `maxHints` as a whole number from 1 to 10; anything else is the default. */\nexport function hintLimit(ai: LearnerAi | undefined): number {\n const asked = ai?.maxHints;\n return typeof asked === 'number' && Number.isInteger(asked) && asked >= 1\n ? Math.min(asked, MAX_HINTS_CEILING)\n : DEFAULT_MAX_HINTS;\n}\n\n/**\n * Whether the learner may ask for a hint: `practice`, before submit, on a\n * question that is not disabled, of a type that takes hints, whose author left\n * hints on, and whole — a redacted item is an exam's, and gives none.\n */\nexport function hintOffered(input: {\n data: RenderableActivity;\n renderMode: RenderMode;\n submitted: boolean;\n disabled: boolean;\n /** The paper's policy: hints at all, and AI hints in particular. Open when absent. */\n delivery?: ResolvedDeliveryPolicy;\n}): boolean {\n const { data, renderMode, submitted, disabled } = input;\n const delivery = input.delivery ?? OPEN_DELIVERY_POLICY;\n return (\n delivery.hints &&\n delivery.ai.hints &&\n renderMode === 'practice' &&\n !submitted &&\n !disabled &&\n data.redacted !== true &&\n aiSupports(data.type, 'hint') &&\n aiAllowedByContent(data, 'hint')\n );\n}\n\n/**\n * Whether the learner may ask for an explanation: of a graded answer — in\n * `practice` once submitted, or in `review` when the grade of record is\n * scored — of a type the SDK explains, whose author left explanations on.\n * Never in `exam`.\n */\nexport function explanationOffered(input: {\n data: RenderableActivity;\n renderMode: RenderMode;\n submitted: boolean;\n outcome: ItemOutcome | undefined;\n /**\n * The paper's policy. An explanation needs AI explanations on, and more: it\n * explains a grade, so it needs the grade shown (`feedback`), and it all but\n * always says what the right answer was, so it needs `solutions` too. On a\n * paper that hides the answer, \"Explain my answer\" would hand it over.\n */\n delivery?: ResolvedDeliveryPolicy;\n}): boolean {\n const { data, renderMode, submitted, outcome } = input;\n const delivery = input.delivery ?? OPEN_DELIVERY_POLICY;\n if (!delivery.feedback || !delivery.solutions || !delivery.ai.explanations) {\n return false;\n }\n const graded =\n (renderMode === 'practice' && submitted) ||\n (renderMode === 'review' && outcome?.status === 'scored');\n return graded && aiSupports(data.type, 'explanation') && aiAllowedByContent(data, 'explanation');\n}\n\n/** Calls a port, turning a synchronous throw into a rejection. */\nexport function callPort<T>(call: () => Promise<T>): Promise<T> {\n try {\n return Promise.resolve(call());\n } catch (error) {\n return Promise.reject(error);\n }\n}\n\n/** Tells a developer why a port's answer was not shown. Silent in production. */\nexport function warnRefused(\n feature: 'explanation' | 'hint',\n activityId: string,\n reason: AiRefusal,\n): void {\n if (isDevelopment()) {\n console.warn(\n `learning-kit: the AI ${feature} for \"${activityId}\" was not shown (${reason}). The learner saw \"not available\" instead.`,\n );\n }\n}\n\n/** The language to write in: the host's choice for the learner, else the interface language. */\nexport const localeOf = (\n ai: LearnerAi | undefined,\n locale: string | undefined,\n): { learnerLocale?: string } => {\n const chosen = ai?.learnerLocale ?? locale;\n return chosen === undefined ? {} : { learnerLocale: chosen };\n};\n\n/** What an explanation is about: when any of it changes, the explanation shown no longer is. */\nexport function explanationKey(\n renderMode: RenderMode,\n submitted: boolean,\n response: LearnerResponse,\n outcome: ItemOutcome | undefined,\n): string {\n let graded = '';\n if (outcome !== undefined) {\n graded = `${outcome.status}:${'score' in outcome ? outcome.score : ''}`;\n }\n return `${renderMode}|${submitted}|${graded}|${JSON.stringify(response)}`;\n}\n","'use client';\n\nimport {\n type AiTextResult,\n aiExplanationRequest,\n aiHintRequest,\n checkAiExplanation,\n checkAiHint,\n combineDeliveryPolicies,\n type DeliveryPolicy,\n type InteractionEvent,\n type ItemOutcome,\n type LearnerResponse,\n} from '@intellectif/lk-core';\nimport { useCallback, useContext, useEffect, useRef, useState } from 'react';\nimport { SequenceSlotContext } from '../components/shared/sequence-slot.js';\nimport type { RenderableActivity, RenderMode } from '../components/types.js';\nimport { type LearnerAi, useLearnerAi } from './LkAiProvider.js';\nimport {\n callPort,\n explanationKey,\n explanationOffered,\n hintLimit,\n hintOffered,\n localeOf,\n warnRefused,\n} from './rules.js';\n\n/**\n * The question AI help is being asked about, as the SDK's own components know\n * it. A host drawing its own question passes what it has; everything optional\n * defaults to what the components pass.\n */\nexport interface AiHelpSituation {\n /** The question, as `data` is given to any activity component. */\n data: RenderableActivity;\n /** The learner's answer as it stands: submitted, restored, or being written. */\n response: LearnerResponse;\n /** Whether the learner has committed that answer. */\n submitted: boolean;\n /**\n * Pass on the `renderMode` your question was given. Left out, the paper\n * around the question answers for it — a question inside `<ActivitySequence>`\n * or `<InteractiveVideo>` knows its mode — and `practice` only where there is\n * no paper at all. An `exam` around the question wins over anything passed\n * here.\n */\n renderMode?: RenderMode;\n /**\n * The AI ports to use, overriding {@link LkAiProvider}'s for this question.\n * A question inside `<ActivitySequence>` or `<InteractiveVideo>` is handed\n * the ports in force as `ai` — pass that straight in.\n */\n ai?: LearnerAi;\n /** The interface language: the default language for what a model writes. */\n locale?: string;\n /** Where `ai-hint-shown` and `ai-explanation-shown` go. */\n onInteraction?: (event: InteractionEvent) => void;\n /**\n * The delivery policy your question was handed. The paper around the\n * question holds as well — a set that switched AI hints off keeps them off\n * here, whatever is passed — so leaving it out never turns anything on.\n */\n delivery?: DeliveryPolicy | null;\n}\n\nexport interface AiExplanationInput extends AiHelpSituation {\n /** The grade of record, which `review` explains. */\n outcome?: ItemOutcome;\n}\n\nexport interface AiExplanationHelp {\n /**\n * Whether to offer the learner an explanation at all: a port is present, the\n * answer is graded, the type is one the SDK explains, and the author left\n * explanations on. False in `exam`, always.\n */\n offered: boolean;\n status: 'idle' | 'loading' | 'shown' | 'unavailable';\n /** What to show, once it has arrived and been checked. Its `text` is text, never HTML. */\n explanation: AiTextResult | null;\n /**\n * Asks the port, when the learner presses something. Does nothing while a\n * call is on its way, and nothing at all where `offered` is false — so an\n * exam cannot reach a model through this hook, whatever ports are in scope.\n *\n * One identity for the life of the question, so it is safe in an effect's\n * dependencies.\n */\n ask: () => void;\n}\n\nexport interface AiHintsInput extends AiHelpSituation {\n /** A disabled question gives no hints, as it takes no answer. */\n disabled?: boolean;\n}\n\nexport interface AiHintsHelp {\n /**\n * Whether the learner may ask for hints here at all: a port is present, the\n * mode is `practice`, the answer is not in, the question is not disabled or\n * redacted, the type takes hints and the author left them on. It stays true\n * once `used` reaches `limit` — there are no more to give, but the question\n * is still one that gives them.\n */\n offered: boolean;\n status: 'idle' | 'loading' | 'unavailable';\n /** Every hint shown for this question, in the order they were given. */\n hints: readonly AiTextResult[];\n /** The host's `maxHints`, as a whole number from 1 to 10 (3 by default). */\n limit: number;\n /** How many hints have been shown. A refused hint is not counted. */\n used: number;\n /**\n * Asks for the next hint. Does nothing at the limit, while a call is on its\n * way, or where `offered` is false. One identity for the life of the\n * question.\n */\n ask: () => void;\n}\n\n/**\n * The mode a question is answered in: what the caller says, and what the paper\n * around it says.\n *\n * A host drawing its own question is given `renderMode` like every other prop\n * and is expected to pass it on, but nothing makes it — and a hook that took\n * `practice` on trust would offer hints on a paper of record, as soon as an\n * `LkAiProvider` sat anywhere above the pager. So an `exam` around the question\n * wins, and where the caller says nothing the paper answers for it. A question\n * standing on its own belongs to no paper, and is `practice` as before.\n */\nfunction modeInForce(asked: RenderMode | undefined, around: RenderMode | undefined): RenderMode {\n if (around === 'exam') {\n return 'exam';\n }\n return asked ?? around ?? 'practice';\n}\n\n/**\n * What the record of help a learner was shown carries beside the fact of it:\n * who wrote it, and what the call cost — both only where the port sent them.\n *\n * The text is deliberately absent. A host that wants to keep what a learner\n * read has it in its own port; an interaction is a record of what happened, and\n * is kept for every learner and every question.\n */\nfunction shownPayload(result: AiTextResult): Record<string, unknown> {\n return {\n ...(result.provenance !== undefined ? { provenance: result.provenance } : {}),\n ...(result.usage !== undefined ? { usage: result.usage } : {}),\n };\n}\n\ntype ExplanationState =\n | { key: string; status: 'idle' | 'loading' | 'unavailable' }\n | { key: string; status: 'shown'; result: AiTextResult };\n\n/**\n * \"Explain my answer\", without the SDK's own button and panel: the rules, the\n * call, the checks and the record, for a question you draw yourself.\n *\n * It is what `<MultipleChoice>` and the others use, so a host's own question\n * offers help on exactly the same terms: never in `exam`, never where the\n * item's author switched explanations off, and never an explanation whose\n * verdict contradicts the grade the SDK computed.\n *\n * ```tsx\n * const { offered, status, explanation, ask } = useAiExplanation({\n * data, response, submitted, renderMode, outcome, ai: question.ai,\n * });\n * if (!offered) return null;\n * return explanation === null ? (\n * <button type=\"button\" onClick={ask} aria-busy={status === 'loading'}>Explain my answer</button>\n * ) : (\n * <section>\n * <p>{explanation.text}</p>\n * <p>Written by AI. It can make mistakes.</p>\n * </section>\n * );\n * ```\n *\n * **Say who wrote it.** The SDK's own panel carries \"Written by AI. It can make\n * mistakes.\"; a page that drops that line passes a model's words off as the\n * course's.\n *\n * When the answer or its grade changes, the explanation is dropped and a call\n * still on its way is abandoned — a late explanation never lands beside an\n * answer it was not written for — as it is when the caller unmounts.\n */\nexport function useAiExplanation(input: AiExplanationInput): AiExplanationHelp {\n const { data, response, submitted, outcome } = input;\n const around = useContext(SequenceSlotContext);\n const renderMode = modeInForce(input.renderMode, around?.renderMode);\n const delivery = combineDeliveryPolicies(input.delivery, around?.delivery);\n const ai = useLearnerAi(input.ai);\n const resetKey = explanationKey(renderMode, submitted, response, outcome);\n const [state, setState] = useState<ExplanationState>({ key: resetKey, status: 'idle' });\n const run = useRef(0);\n const controller = useRef<AbortController | null>(null);\n const current: ExplanationState =\n state.key === resetKey ? state : { key: resetKey, status: 'idle' };\n const offered =\n ai?.explain !== undefined &&\n explanationOffered({ data, renderMode, submitted, outcome, delivery });\n\n // What `ask` reads, as of this render, so `ask` itself can keep one identity\n // for the life of the question. The state goes in too: an event handler runs\n // after the render that set it, so the ref is what the learner last saw.\n const latest = useRef({ input, renderMode, ai, resetKey, offered, current });\n latest.current = { input, renderMode, ai, resetKey, offered, current };\n\n // Another answer, or the caller going away: abandon the call in flight.\n // biome-ignore lint/correctness/useExhaustiveDependencies: the key is the trigger — its cleanup runs when the answer changes\n useEffect(() => {\n return () => {\n run.current += 1;\n controller.current?.abort();\n controller.current = null;\n };\n }, [resetKey]);\n\n const ask = useCallback((): void => {\n const now = latest.current;\n const port = now.ai?.explain;\n if (port === undefined || !now.offered || now.current.status === 'loading') {\n return;\n }\n const key = now.resetKey;\n const request = aiExplanationRequest({\n data: now.input.data,\n response: now.input.response,\n ...(now.renderMode === 'review' && now.input.outcome !== undefined\n ? { outcome: now.input.outcome }\n : {}),\n ...localeOf(now.ai, now.input.locale),\n });\n if (request === null) {\n setState({ key, status: 'unavailable' });\n return;\n }\n run.current += 1;\n const mine = run.current;\n controller.current?.abort();\n const abort = new AbortController();\n controller.current = abort;\n setState({ key, status: 'loading' });\n callPort(() => port(request, { signal: abort.signal })).then(\n (raw) => {\n if (run.current !== mine) {\n return;\n }\n const checked = checkAiExplanation(raw, request);\n if (!checked.ok) {\n warnRefused('explanation', now.input.data.id, checked.refusal);\n setState({ key, status: 'unavailable' });\n latest.current.input.onInteraction?.({\n type: 'ai-help-refused',\n activityId: now.input.data.id,\n timestamp: Date.now(),\n payload: { feature: 'explanation', reason: checked.refusal },\n });\n return;\n }\n setState({ key, status: 'shown', result: checked.result });\n latest.current.input.onInteraction?.({\n type: 'ai-explanation-shown',\n activityId: now.input.data.id,\n timestamp: Date.now(),\n payload: shownPayload(checked.result),\n });\n },\n () => {\n if (run.current === mine) {\n setState({ key, status: 'unavailable' });\n }\n },\n );\n }, []);\n\n return {\n offered,\n status: current.status,\n explanation: current.status === 'shown' ? current.result : null,\n ask,\n };\n}\n\ninterface HintState {\n key: string;\n hints: AiTextResult[];\n status: 'idle' | 'loading' | 'unavailable';\n}\n\n/**\n * \"Get a hint\", without the SDK's own button and list: the rules, the call, the\n * checks and the record, for a question you draw yourself.\n *\n * It is what `<MultipleChoice>` and the others use, so a host's own question\n * gives hints on exactly the same terms: only in `practice` before submit, up\n * to the host's limit, never on a redacted item or a type the SDK does not hint\n * for, and never a hint that gives the answer away — which is refused rather\n * than shown, and not counted.\n *\n * ```tsx\n * const { offered, status, hints, used, limit, ask } = useAiHints({\n * data, response, submitted, renderMode, ai: question.ai,\n * });\n * ```\n *\n * **Say who wrote them**, as the SDK's own list does: \"Written by AI. It can\n * make mistakes.\"\n *\n * Keep the hints on screen after submit — the learner can see what they were\n * given — while `offered` turns false. Another question starts with none.\n */\nexport function useAiHints(input: AiHintsInput): AiHintsHelp {\n const { data, submitted } = input;\n const around = useContext(SequenceSlotContext);\n const renderMode = modeInForce(input.renderMode, around?.renderMode);\n const delivery = combineDeliveryPolicies(input.delivery, around?.delivery);\n const disabled = input.disabled === true;\n const ai = useLearnerAi(input.ai);\n const resetKey = data.id;\n const [state, setState] = useState<HintState>({ key: resetKey, hints: [], status: 'idle' });\n const run = useRef(0);\n const controller = useRef<AbortController | null>(null);\n const current: HintState =\n state.key === resetKey ? state : { key: resetKey, hints: [], status: 'idle' };\n const offered =\n ai?.hint !== undefined && hintOffered({ data, renderMode, submitted, disabled, delivery });\n const limit = hintLimit(ai);\n\n const latest = useRef({ input, renderMode, ai, offered, limit, current });\n latest.current = { input, renderMode, ai, offered, limit, current };\n\n // Another question, or the caller going away: abandon the call in flight.\n // biome-ignore lint/correctness/useExhaustiveDependencies: the key is the trigger — its cleanup runs when the question changes\n useEffect(() => {\n return () => {\n run.current += 1;\n controller.current?.abort();\n controller.current = null;\n };\n }, [resetKey]);\n\n // Submitted, or disabled, while a hint was on its way: it would arrive for an\n // answer that can no longer use it.\n useEffect(() => {\n if (!offered) {\n run.current += 1;\n controller.current?.abort();\n controller.current = null;\n setState((previous) =>\n previous.status === 'loading' ? { ...previous, status: 'idle' } : previous,\n );\n }\n }, [offered]);\n\n const ask = useCallback((): void => {\n const now = latest.current;\n const port = now.ai?.hint;\n const held = now.current;\n if (\n port === undefined ||\n !now.offered ||\n held.status === 'loading' ||\n held.hints.length >= now.limit\n ) {\n return;\n }\n const key = held.key;\n const request = aiHintRequest({\n data: now.input.data,\n response: now.input.response,\n previousHints: held.hints.map((hint) => hint.text),\n ...localeOf(now.ai, now.input.locale),\n });\n if (request === null) {\n setState({ ...held, status: 'unavailable' });\n return;\n }\n run.current += 1;\n const mine = run.current;\n controller.current?.abort();\n const abort = new AbortController();\n controller.current = abort;\n setState({ ...held, status: 'loading' });\n callPort(() => port(request, { signal: abort.signal })).then(\n (raw) => {\n if (run.current !== mine) {\n return;\n }\n const checked = checkAiHint(raw, request);\n if (!checked.ok) {\n warnRefused('hint', now.input.data.id, checked.refusal);\n setState((shown) => ({ ...shown, status: 'unavailable' }));\n latest.current.input.onInteraction?.({\n type: 'ai-help-refused',\n activityId: now.input.data.id,\n timestamp: Date.now(),\n payload: {\n feature: 'hint',\n reason: checked.refusal,\n hintNumber: request.hintNumber,\n },\n });\n return;\n }\n setState((shown) => ({\n ...shown,\n hints: shown.key === key ? [...shown.hints, checked.result] : shown.hints,\n status: 'idle',\n }));\n latest.current.input.onInteraction?.({\n type: 'ai-hint-shown',\n activityId: now.input.data.id,\n timestamp: Date.now(),\n payload: { hintNumber: request.hintNumber, ...shownPayload(checked.result) },\n });\n },\n () => {\n if (run.current === mine) {\n setState((shown) => ({ ...shown, status: 'unavailable' }));\n }\n },\n );\n }, []);\n\n return {\n offered,\n status: current.status,\n hints: current.hints,\n limit,\n used: current.hints.length,\n ask,\n };\n}\n"]}
|