@intellectif/lk-react 23.1.1 → 23.1.2
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 +11 -0
- package/dist/ActivityPreview-1hhTkYuj.js.map +1 -1
- package/dist/ActivityPreview-BKr34exz.cjs.map +1 -1
- package/dist/Dictation-BlxRp-3k.js.map +1 -1
- package/dist/Dictation-jECzL88E.cjs.map +1 -1
- package/dist/FillInTheBlanks-B6xzqYXx.js.map +1 -1
- package/dist/FillInTheBlanks-BpKhdHg2.cjs.map +1 -1
- package/dist/GapSelect-DUSiaEQH.js.map +1 -1
- package/dist/GapSelect-payTq_Zn.cjs.map +1 -1
- package/dist/InteractiveVideo-BguGv-HA.cjs.map +1 -1
- package/dist/InteractiveVideo-Cg2jbU3j.js.map +1 -1
- package/dist/MultipleChoice-COn5MtKY.cjs.map +1 -1
- package/dist/MultipleChoice-Dra0_KYD.js.map +1 -1
- package/dist/PronunciationFeedback-DlMPGjkR.cjs.map +1 -1
- package/dist/PronunciationFeedback-Dxd6XPkO.js.map +1 -1
- package/dist/ReadAloud-C8RhaUqV.js.map +1 -1
- package/dist/ReadAloud-akDehitT.cjs.map +1 -1
- package/dist/{ThemeProvider-CocC8ODJ.d.ts → ThemeProvider-B6DyGqzh.d.cts} +3 -3
- package/dist/{ThemeProvider-DS4KOL86.d.cts.map → ThemeProvider-B6DyGqzh.d.cts.map} +1 -1
- package/dist/{ThemeProvider-DS4KOL86.d.cts → ThemeProvider-BCrxBaY-.d.ts} +3 -3
- package/dist/{ThemeProvider-CocC8ODJ.d.ts.map → ThemeProvider-BCrxBaY-.d.ts.map} +1 -1
- package/dist/ThemeProvider-BRN1tj4n.cjs.map +1 -1
- package/dist/ThemeProvider-C7K9iMLX.js.map +1 -1
- package/dist/WrittenResponse-Bj3eFmGl.cjs.map +1 -1
- package/dist/WrittenResponse-Cl8G6nO_.js.map +1 -1
- package/dist/components/ActivityPreview.d.cts +1 -1
- package/dist/components/ActivityPreview.d.ts +1 -1
- package/dist/components/ActivitySequence.d.cts +1 -1
- package/dist/components/ActivitySequence.d.ts +1 -1
- package/dist/components/Dictation.d.cts +1 -1
- package/dist/components/Dictation.d.ts +1 -1
- package/dist/components/FillInTheBlanks.d.cts +1 -1
- package/dist/components/FillInTheBlanks.d.ts +1 -1
- package/dist/components/GapSelect.d.cts +1 -1
- package/dist/components/GapSelect.d.ts +1 -1
- package/dist/components/InteractiveVideo.d.cts +1 -1
- package/dist/components/InteractiveVideo.d.ts +1 -1
- package/dist/components/MultipleChoice.d.cts +1 -1
- package/dist/components/MultipleChoice.d.ts +1 -1
- package/dist/components/PronunciationFeedback.d.cts +1 -1
- package/dist/components/PronunciationFeedback.d.ts +1 -1
- package/dist/components/ReadAloud.d.cts +1 -1
- package/dist/components/ReadAloud.d.cts.map +1 -1
- package/dist/components/ReadAloud.d.ts +1 -1
- package/dist/components/ReadAloud.d.ts.map +1 -1
- package/dist/components/WrittenResponse.d.cts +1 -1
- package/dist/components/WrittenResponse.d.ts +1 -1
- package/dist/{index-CTlht6OV.d.cts → index-6P4rJHrJ.d.cts} +2 -2
- package/dist/{index-CTlht6OV.d.cts.map → index-6P4rJHrJ.d.cts.map} +1 -1
- package/dist/{index-BAqT5tXN.d.ts → index-90CX504k.d.ts} +2 -2
- package/dist/{index-BAqT5tXN.d.ts.map → index-90CX504k.d.ts.map} +1 -1
- package/dist/{index-n9PKZm7W.d.cts → index-B_SgnjsC.d.cts} +2 -2
- package/dist/{index-n9PKZm7W.d.cts.map → index-B_SgnjsC.d.cts.map} +1 -1
- package/dist/{index-BW7aA5eN.d.cts → index-BavVW5z5.d.cts} +2 -2
- package/dist/{index-BW7aA5eN.d.cts.map → index-BavVW5z5.d.cts.map} +1 -1
- package/dist/{index-D4WYDN_p.d.cts → index-BhHv6Fiu.d.cts} +2 -2
- package/dist/index-BhHv6Fiu.d.cts.map +1 -0
- package/dist/{index-DRMauOVH.d.ts → index-Bsu8VciA.d.ts} +2 -2
- package/dist/{index-DRMauOVH.d.ts.map → index-Bsu8VciA.d.ts.map} +1 -1
- package/dist/{index-CNoAqj5I.d.ts → index-CO_SRFJj.d.ts} +2 -2
- package/dist/{index-CNoAqj5I.d.ts.map → index-CO_SRFJj.d.ts.map} +1 -1
- package/dist/{index-CmgSzapt.d.cts → index-CSsuUsP7.d.cts} +2 -2
- package/dist/{index-CmgSzapt.d.cts.map → index-CSsuUsP7.d.cts.map} +1 -1
- package/dist/{index-DIiacP_z.d.cts → index-CunUyeuN.d.cts} +3 -3
- package/dist/{index-DIiacP_z.d.cts.map → index-CunUyeuN.d.cts.map} +1 -1
- package/dist/{index-2iDJohip.d.cts → index-CzfRzks6.d.cts} +2 -2
- package/dist/{index-2iDJohip.d.cts.map → index-CzfRzks6.d.cts.map} +1 -1
- package/dist/{index-C7cCHEze.d.cts → index-D14xtpBS.d.cts} +2 -2
- package/dist/{index-C7cCHEze.d.cts.map → index-D14xtpBS.d.cts.map} +1 -1
- package/dist/{index-DR0SOJig.d.ts → index-DHRPAPZf.d.ts} +2 -2
- package/dist/{index-DR0SOJig.d.ts.map → index-DHRPAPZf.d.ts.map} +1 -1
- package/dist/{index-bmmRfGad.d.cts → index-DLV6cK26.d.cts} +2 -2
- package/dist/{index-bmmRfGad.d.cts.map → index-DLV6cK26.d.cts.map} +1 -1
- package/dist/{index-BeiVd-qe.d.ts → index-DbCCQ9cP.d.ts} +2 -2
- package/dist/{index-BeiVd-qe.d.ts.map → index-DbCCQ9cP.d.ts.map} +1 -1
- package/dist/{index-zu9H7owf.d.ts → index-DeV5B2m5.d.ts} +2 -2
- package/dist/{index-zu9H7owf.d.ts.map → index-DeV5B2m5.d.ts.map} +1 -1
- package/dist/{index-CN3_TwDL.d.ts → index-TZCJVSge.d.ts} +2 -2
- package/dist/index-TZCJVSge.d.ts.map +1 -0
- package/dist/{index-DAJ6KK49.d.ts → index-ZVvLHsd3.d.ts} +2 -2
- package/dist/{index-DAJ6KK49.d.ts.map → index-ZVvLHsd3.d.ts.map} +1 -1
- package/dist/{index-BRbEfXgE.d.ts → index-eQwBgpSw.d.ts} +3 -3
- package/dist/{index-BRbEfXgE.d.ts.map → index-eQwBgpSw.d.ts.map} +1 -1
- package/dist/index.d.cts +10 -10
- package/dist/index.d.ts +10 -10
- package/dist/theme/ThemeProvider.d.cts +1 -1
- package/dist/theme/ThemeProvider.d.ts +1 -1
- package/package.json +1 -1
- package/dist/index-CN3_TwDL.d.ts.map +0 -1
- package/dist/index-D4WYDN_p.d.cts.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# @intellectif/lk-react
|
|
2
2
|
|
|
3
|
+
## 23.1.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#98](https://github.com/intellectif/learning-kit/pull/98) [`e2b3100`](https://github.com/intellectif/learning-kit/commit/e2b310024f25b7d7e38054f833e324b2978ef7ac) Thanks [@diego28e](https://github.com/diego28e)! - **Components declare their return type**: every component's declaration now reads `React.JSX.Element`. The type is unchanged; only its spelling in the `.d.ts` files is.
|
|
8
|
+
|
|
9
|
+
**Action required:** none.
|
|
10
|
+
|
|
11
|
+
- Ten components (`ActivityPreview`, `Dictation`, `FillInTheBlanks`, `GapSelect`, `InteractiveVideo`, `MultipleChoice`, `PronunciationFeedback`, `ReadAloud`, `ThemeProvider` and `WrittenResponse`) left their return type to inference. The declarations therefore named it after the file TypeScript found it in: `import("react/jsx-runtime").JSX.Element`. `@types/react` 19.3 moved that type into `react`, so the same code built under it declared `import("react").JSX.Element` instead. They now declare `React.JSX.Element`, as the other components already did. Under `@types/react` 19.2 and 19.3, the old and new spellings are one type.
|
|
12
|
+
- The API report now rejects an exported type named through `import("…")`, so a dependency upgrade can no longer change what the package declares.
|
|
13
|
+
|
|
3
14
|
## 23.1.1
|
|
4
15
|
|
|
5
16
|
### Patch Changes
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ActivityPreview-1hhTkYuj.js","names":[],"sources":["../src/components/ActivityPreview/ActivityPreview.tsx"],"sourcesContent":["'use client';\n\nimport {\n type ActivityType,\n type DraftNotComplete,\n evaluate,\n type ItemOutcome,\n type LearnerResponse,\n slotMediaKey,\n type ThemeTokens,\n validateDraft,\n} from '@intellectif/lk-core';\nimport { type ReactNode, useMemo } from 'react';\nimport { useLkStrings } from '../../i18n/LkIntlProvider.js';\nimport type { LkStringsOverride } from '../../i18n/strings.js';\nimport { everyBuiltInTypeHandled } from '../_internal.js';\nimport { ActivityErrorBoundary } from '../ActivityErrorBoundary.js';\nimport type { ActivityRenderer } from '../ActivitySequence/index.js';\nimport { Dictation } from '../Dictation/index.js';\nimport { FillInTheBlanks } from '../FillInTheBlanks/index.js';\nimport { GapSelect } from '../GapSelect/index.js';\nimport { MultipleChoice } from '../MultipleChoice/index.js';\nimport { ReadAloud } from '../ReadAloud/index.js';\nimport type { RecordingBinding } from '../ReadAloud/ReadAloud.js';\nimport type { HtmlSanitizer, MediaBudgetBinding, RenderMode } from '../types.js';\nimport { WrittenResponse } from '../WrittenResponse/index.js';\n\n/**\n * The option-shuffle seed a preview uses when none is given. Fixed, so a\n * shuffled question holds still while its author edits it instead of\n * re-ordering on every remount.\n */\nconst PREVIEW_SHUFFLE_SEED = 'lk-activity-preview';\n\n/**\n * The slot a previewed activity stands in for. A recording's play budget is\n * keyed to it and lives only in this mount: the preview counts plays so an\n * author hears the limit a learner gets, and records none of them.\n */\nconst PREVIEW_SLOT_ID = 'preview';\n\n/** Numbers the takes a preview mints keys for, so two are never the same take. */\nlet previewTakeCount = 0;\n\n/**\n * The store a previewed read-aloud records into: it keys the take so the whole\n * flow — record, play it back, submit — runs for the author, and keeps it\n * nowhere, like every other thing a preview does not record.\n *\n * It judges nothing, deliberately. `assess` is optional precisely so this can\n * exist: an author trying a reading out has no assessor to point at, and a\n * preview that demanded one would refuse to render the most common case. The\n * default `practice` mode then shows the \"assessment is not available\" notice,\n * which is the truth about this binding rather than a failure.\n */\nconst PREVIEW_RECORDING: RecordingBinding = {\n upload: async (take) => {\n previewTakeCount += 1;\n return {\n key: `${PREVIEW_SLOT_ID}-take-${previewTakeCount}`,\n mimeType: take.mimeType,\n durationMs: take.durationMs,\n };\n },\n};\n\nexport interface ActivityPreviewProps {\n /**\n * The activity as an editor holds it right now, finished or not. It is checked\n * with `validateDraft` before anything renders — by content, on every render —\n * so a half-written question never reaches a component that assumes a valid\n * one.\n */\n draft: unknown;\n /** The mode to preview in. Defaults to `practice`. */\n renderMode?: RenderMode;\n /**\n * A learner response to show as if a learner had given it. In `practice` and\n * `exam` it seeds the answer, which stays editable; in `review` it is the\n * submitted answer, marked by `evaluate()` unless `outcome` is given.\n */\n response?: LearnerResponse;\n /**\n * The outcome `review` marks the response with, instead of the one\n * `evaluate()` computes — which is how to preview a returned grade on a written\n * response, since the SDK cannot score one. Ignored in other modes.\n */\n outcome?: ItemOutcome;\n /**\n * Rendered in place of the activity while the draft is not complete, with the\n * issues `validateDraft` found — so an editor can list them in its own words.\n * The issue messages are English, and a preview that rendered them would put\n * untranslatable text on screen. Without it, the preview shows the\n * `previewIncomplete` or `previewInvalid` string.\n */\n fallback?: (result: DraftNotComplete) => ReactNode;\n /**\n * Renderers for activity types beyond the built-ins, keyed by `type`, as on\n * `<ActivitySequence>`. An entry receives the shared prop contract and\n * nothing else, so — like the shuffle seed below — the in-memory recording\n * binding a previewed read-aloud is given does not reach one. Nor does the\n * `question` a sequence hands a renderer: a preview is one question with no\n * set around it, so there is no slot to report to and nothing to page away\n * from. A renderer that uses it must treat it as optional, which its type\n * already says.\n */\n renderers?: Readonly<Record<string, ActivityRenderer>>;\n /**\n * Seeds `<MultipleChoice>`'s option shuffle. Defaults to a fixed seed, so the\n * order holds while options are edited; adding or removing one deals them\n * again, because the shuffle orders by position. A `renderers` entry is given\n * no seed, as on `<ActivitySequence>`.\n */\n shuffleSeed?: string;\n /** Renders author-supplied rich text. See `HtmlSanitizer`. */\n sanitizeHtml?: HtmlSanitizer;\n /** Overrides the SDK's chrome text, for the preview's notice and the activity alike. */\n strings?: LkStringsOverride;\n /** Per-instance token overrides, passed to the activity. */\n theme?: Partial<ThemeTokens>;\n /** BCP 47 tag of the interface language, passed to the activity. */\n locale?: string;\n}\n\ntype Marking = { kind: 'marked'; outcome: ItemOutcome } | { kind: 'failed'; error: Error };\n\n/**\n * Renders an activity from an editor's draft, in any render mode, with a\n * simulated learner response — and renders a notice instead while the draft is\n * not an activity yet.\n *\n * Nothing it renders is recorded: no callback is wired, so a practice attempt in\n * a preview scores locally and goes nowhere, and a recording's play limit is\n * counted in memory only.\n *\n * A draft with no string `type`, a type nobody registered, and a `response` of a\n * different type from the draft are wiring errors, and throw.\n */\nexport function ActivityPreview({\n draft,\n renderMode = 'practice',\n response,\n outcome,\n fallback,\n renderers,\n shuffleSeed,\n sanitizeHtml,\n strings,\n theme,\n locale,\n}: ActivityPreviewProps) {\n const s = useLkStrings(strings);\n const type = isRecord(draft) && typeof draft.type === 'string' ? draft.type : undefined;\n\n // Validated on the draft's CONTENT, not its identity. The activity components\n // restart whenever `data` changes identity, and an editor that rebuilds its\n // payload on every change would otherwise restart the question an author is\n // trying out each time anything else on the page was edited. The key is read\n // on every render, so a draft changed in place is checked again too.\n const contentKey = contentKeyOf(draft);\n // biome-ignore lint/correctness/useExhaustiveDependencies: the draft's content (contentKey) is the trigger, not its identity\n const result = useMemo(\n () => (type === undefined ? undefined : validateDraft(type as ActivityType, draft)),\n [type, contentKey],\n );\n const activity = result?.status === 'complete' ? result.data : undefined;\n const responseFits = response === undefined || response.type === type;\n const responseKey = response === undefined ? 'none' : contentKeyOf(response);\n\n // biome-ignore lint/correctness/useExhaustiveDependencies: the response's content (responseKey) is the trigger, not its identity\n const marking = useMemo((): Marking | undefined => {\n if (renderMode !== 'review' || activity === undefined) {\n return undefined;\n }\n if (outcome !== undefined) {\n return { kind: 'marked', outcome };\n }\n if (response === undefined || !responseFits) {\n return undefined;\n }\n try {\n return { kind: 'marked', outcome: evaluate(activity, response) };\n } catch (thrown) {\n // Scored here, during this component's own render, where no error boundary\n // inside the activity can catch it — so it is handed to one below.\n return {\n kind: 'failed',\n error: thrown instanceof Error ? thrown : new Error(String(thrown)),\n };\n }\n }, [renderMode, activity, outcome, responseKey, responseFits]);\n\n const activityId = activity?.id;\n const mediaBudget = useMemo<MediaBudgetBinding>(\n () => ({\n key: slotMediaKey(PREVIEW_SLOT_ID),\n slotId: PREVIEW_SLOT_ID,\n index: 0,\n ...(activityId !== undefined ? { activityId } : {}),\n }),\n [activityId],\n );\n\n // All hooks are called before these throws, so hook order stays stable.\n if (type === undefined || result === undefined) {\n throw new Error(\n '<ActivityPreview> needs a draft object with a string `type`, to know which activity to check and render.',\n );\n }\n // Checked against the draft's own type, whatever state the draft is in. A\n // mis-wired response is a wiring error however unfinished the draft is, and\n // waiting for the draft to be complete would throw on the very edit that\n // completes it.\n if (response !== undefined && !responseFits) {\n throw new Error(\n `<ActivityPreview> was given a \"${response.type}\" response for a \"${type}\" activity.`,\n );\n }\n if (result.status !== 'complete') {\n if (fallback !== undefined) {\n return <>{fallback(result)}</>;\n }\n return (\n <div\n className=\"lk-preview-unavailable\"\n role=\"note\"\n data-status={result.status}\n {...(locale !== undefined ? { lang: locale } : {})}\n >\n {result.status === 'invalid' ? s.previewInvalid : s.previewIncomplete}\n </div>\n );\n }\n const data = result.data;\n\n // A different simulated response, or a different mode, mounts the activity\n // afresh, play count included: a seeded answer is read at mount, and a practice\n // attempt the author submitted must not carry over into an exam preview. So\n // does a different recording or playback policy, which is a different listening\n // budget — the transport reads its count once, when it mounts.\n const key = `${renderMode}:${responseKey}:${recordingKeyOf(data)}`;\n\n if (marking?.kind === 'failed') {\n // The response is one the scorer cannot read, or a registered scorer threw.\n // Shown as the activity's error fallback, as a render failure would be.\n return (\n <ActivityErrorBoundary key={key} activityTitle={data.title} strings={s}>\n <Rethrow error={marking.error} />\n </ActivityErrorBoundary>\n );\n }\n\n const shared = {\n renderMode,\n // Without a binding a budgeted recording counts nothing in `practice`, and in\n // `exam` the media component refuses to render at all — rightly, on a real\n // paper, where nothing would persist the count. A preview persists nothing by\n // design, so it keeps the count in memory and says so.\n mediaBudget,\n ...(sanitizeHtml !== undefined ? { sanitizeHtml } : {}),\n ...(strings !== undefined ? { strings } : {}),\n ...(theme !== undefined ? { theme } : {}),\n ...(locale !== undefined ? { locale } : {}),\n // Seeded rather than controlled, in every mode, exactly as `<ActivitySequence>`\n // hands a restored answer to its slots — so a renderer written for the\n // sequence reads it here too. `review` is read-only either way, and `key`\n // above re-seeds whenever the response changes.\n ...(response !== undefined ? { defaultValue: response } : {}),\n ...(marking !== undefined ? { outcome: marking.outcome } : {}),\n };\n\n const Custom = renderers?.[data.type];\n if (Custom !== undefined) {\n return <Custom key={key} data={data} {...shared} />;\n }\n switch (data.type) {\n case 'multiple-choice':\n return (\n <MultipleChoice\n key={key}\n data={data}\n shuffleSeed={shuffleSeed ?? PREVIEW_SHUFFLE_SEED}\n {...shared}\n />\n );\n case 'fill-in-the-blanks':\n return <FillInTheBlanks key={key} data={data} {...shared} />;\n case 'gap-select':\n return (\n <GapSelect\n key={key}\n data={data}\n shuffleSeed={shuffleSeed ?? PREVIEW_SHUFFLE_SEED}\n {...shared}\n />\n );\n case 'written-response':\n return <WrittenResponse key={key} data={data} {...shared} />;\n case 'dictation':\n return <Dictation key={key} data={data} {...shared} />;\n case 'read-aloud':\n return <ReadAloud key={key} data={data} recordingBinding={PREVIEW_RECORDING} {...shared} />;\n default:\n everyBuiltInTypeHandled(data);\n return (\n <div className=\"lk-preview-unsupported\" role=\"note\">\n {s.unsupportedActivity}\n </div>\n );\n }\n}\n\n/** Throws `error` while rendering, so the error boundary around it shows its fallback. */\nfunction Rethrow({ error }: { error: Error }): never {\n throw error;\n}\n\nfunction isRecord(value: unknown): value is Readonly<Record<string, unknown>> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/**\n * The recording and its playback policy — not its description, which changes\n * no budget — plus the slower file a dictation or a read-aloud carries, which\n * follows the same policy and must remount when it is swapped for another.\n */\nfunction recordingKeyOf(data: unknown): string {\n const media = isRecord(data) ? data.media : undefined;\n const slowMedia = isRecord(data) ? data.slowMedia : undefined;\n const slowUrl = isRecord(slowMedia) ? slowMedia.url : undefined;\n if (!isRecord(media) && slowUrl === undefined) {\n return '';\n }\n return contentKeyOf([\n isRecord(media) ? media.type : undefined,\n isRecord(media) ? media.url : undefined,\n isRecord(media) ? media.playback : undefined,\n slowUrl,\n ]);\n}\n\nconst identities = new WeakMap<object, number>();\nlet lastIdentity = 0;\n\n/** A key for a value compared by identity: the same object always gets the same one. */\nfunction identityKeyOf(value: object): string {\n let identity = identities.get(value);\n if (identity === undefined) {\n lastIdentity += 1;\n identity = lastIdentity;\n identities.set(value, identity);\n }\n return `#${identity}`;\n}\n\n/**\n * A string that changes exactly when a value's content does.\n *\n * Object keys are sorted, because key order is not content: a draft saved to a\n * JSON column comes back with its keys reordered. Every value is written in a\n * form no other value shares — JSON would write a non-finite number as `null`\n * and drop `undefined`, and those decide different issues. What JSON does not\n * describe — an instance of a class, a function, a reference back to an\n * enclosing object — is compared by identity, and a symbol by its description.\n */\nfunction contentKeyOf(value: unknown, enclosing: Set<object> = new Set()): string {\n switch (typeof value) {\n case 'string':\n return JSON.stringify(value);\n case 'number':\n // `String` keeps NaN, the infinities and every other number apart; only -0\n // needs saying.\n return Object.is(value, -0) ? '-0' : String(value);\n case 'bigint':\n return `${value}n`;\n case 'boolean':\n case 'undefined':\n return String(value);\n case 'symbol':\n return `Symbol(${JSON.stringify(value.description ?? null)})`;\n case 'function':\n return identityKeyOf(value);\n }\n // Every other `typeof` has returned above: this is `null`, or an object.\n if (value === null || typeof value !== 'object') {\n return 'null';\n }\n const prototype = Object.getPrototypeOf(value);\n const plain = Array.isArray(value) || prototype === Object.prototype || prototype === null;\n if (!plain || enclosing.has(value)) {\n return identityKeyOf(value);\n }\n enclosing.add(value);\n try {\n if (Array.isArray(value)) {\n return `[${Array.from(value, (item) => contentKeyOf(item, enclosing)).join(',')}]`;\n }\n const record = value as Record<string, unknown>;\n const fields = Object.keys(record)\n .sort()\n .map((field) => `${JSON.stringify(field)}:${contentKeyOf(record[field], enclosing)}`);\n return `{${fields.join(',')}}`;\n } finally {\n enclosing.delete(value);\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ActivityPreview-1hhTkYuj.js","names":[],"sources":["../src/components/ActivityPreview/ActivityPreview.tsx"],"sourcesContent":["'use client';\n\nimport {\n type ActivityType,\n type DraftNotComplete,\n evaluate,\n type ItemOutcome,\n type LearnerResponse,\n slotMediaKey,\n type ThemeTokens,\n validateDraft,\n} from '@intellectif/lk-core';\nimport { type ReactNode, useMemo } from 'react';\nimport { useLkStrings } from '../../i18n/LkIntlProvider.js';\nimport type { LkStringsOverride } from '../../i18n/strings.js';\nimport { everyBuiltInTypeHandled } from '../_internal.js';\nimport { ActivityErrorBoundary } from '../ActivityErrorBoundary.js';\nimport type { ActivityRenderer } from '../ActivitySequence/index.js';\nimport { Dictation } from '../Dictation/index.js';\nimport { FillInTheBlanks } from '../FillInTheBlanks/index.js';\nimport { GapSelect } from '../GapSelect/index.js';\nimport { MultipleChoice } from '../MultipleChoice/index.js';\nimport { ReadAloud } from '../ReadAloud/index.js';\nimport type { RecordingBinding } from '../ReadAloud/ReadAloud.js';\nimport type { HtmlSanitizer, MediaBudgetBinding, RenderMode } from '../types.js';\nimport { WrittenResponse } from '../WrittenResponse/index.js';\n\n/**\n * The option-shuffle seed a preview uses when none is given. Fixed, so a\n * shuffled question holds still while its author edits it instead of\n * re-ordering on every remount.\n */\nconst PREVIEW_SHUFFLE_SEED = 'lk-activity-preview';\n\n/**\n * The slot a previewed activity stands in for. A recording's play budget is\n * keyed to it and lives only in this mount: the preview counts plays so an\n * author hears the limit a learner gets, and records none of them.\n */\nconst PREVIEW_SLOT_ID = 'preview';\n\n/** Numbers the takes a preview mints keys for, so two are never the same take. */\nlet previewTakeCount = 0;\n\n/**\n * The store a previewed read-aloud records into: it keys the take so the whole\n * flow — record, play it back, submit — runs for the author, and keeps it\n * nowhere, like every other thing a preview does not record.\n *\n * It judges nothing, deliberately. `assess` is optional precisely so this can\n * exist: an author trying a reading out has no assessor to point at, and a\n * preview that demanded one would refuse to render the most common case. The\n * default `practice` mode then shows the \"assessment is not available\" notice,\n * which is the truth about this binding rather than a failure.\n */\nconst PREVIEW_RECORDING: RecordingBinding = {\n upload: async (take) => {\n previewTakeCount += 1;\n return {\n key: `${PREVIEW_SLOT_ID}-take-${previewTakeCount}`,\n mimeType: take.mimeType,\n durationMs: take.durationMs,\n };\n },\n};\n\nexport interface ActivityPreviewProps {\n /**\n * The activity as an editor holds it right now, finished or not. It is checked\n * with `validateDraft` before anything renders — by content, on every render —\n * so a half-written question never reaches a component that assumes a valid\n * one.\n */\n draft: unknown;\n /** The mode to preview in. Defaults to `practice`. */\n renderMode?: RenderMode;\n /**\n * A learner response to show as if a learner had given it. In `practice` and\n * `exam` it seeds the answer, which stays editable; in `review` it is the\n * submitted answer, marked by `evaluate()` unless `outcome` is given.\n */\n response?: LearnerResponse;\n /**\n * The outcome `review` marks the response with, instead of the one\n * `evaluate()` computes — which is how to preview a returned grade on a written\n * response, since the SDK cannot score one. Ignored in other modes.\n */\n outcome?: ItemOutcome;\n /**\n * Rendered in place of the activity while the draft is not complete, with the\n * issues `validateDraft` found — so an editor can list them in its own words.\n * The issue messages are English, and a preview that rendered them would put\n * untranslatable text on screen. Without it, the preview shows the\n * `previewIncomplete` or `previewInvalid` string.\n */\n fallback?: (result: DraftNotComplete) => ReactNode;\n /**\n * Renderers for activity types beyond the built-ins, keyed by `type`, as on\n * `<ActivitySequence>`. An entry receives the shared prop contract and\n * nothing else, so — like the shuffle seed below — the in-memory recording\n * binding a previewed read-aloud is given does not reach one. Nor does the\n * `question` a sequence hands a renderer: a preview is one question with no\n * set around it, so there is no slot to report to and nothing to page away\n * from. A renderer that uses it must treat it as optional, which its type\n * already says.\n */\n renderers?: Readonly<Record<string, ActivityRenderer>>;\n /**\n * Seeds `<MultipleChoice>`'s option shuffle. Defaults to a fixed seed, so the\n * order holds while options are edited; adding or removing one deals them\n * again, because the shuffle orders by position. A `renderers` entry is given\n * no seed, as on `<ActivitySequence>`.\n */\n shuffleSeed?: string;\n /** Renders author-supplied rich text. See `HtmlSanitizer`. */\n sanitizeHtml?: HtmlSanitizer;\n /** Overrides the SDK's chrome text, for the preview's notice and the activity alike. */\n strings?: LkStringsOverride;\n /** Per-instance token overrides, passed to the activity. */\n theme?: Partial<ThemeTokens>;\n /** BCP 47 tag of the interface language, passed to the activity. */\n locale?: string;\n}\n\ntype Marking = { kind: 'marked'; outcome: ItemOutcome } | { kind: 'failed'; error: Error };\n\n/**\n * Renders an activity from an editor's draft, in any render mode, with a\n * simulated learner response — and renders a notice instead while the draft is\n * not an activity yet.\n *\n * Nothing it renders is recorded: no callback is wired, so a practice attempt in\n * a preview scores locally and goes nowhere, and a recording's play limit is\n * counted in memory only.\n *\n * A draft with no string `type`, a type nobody registered, and a `response` of a\n * different type from the draft are wiring errors, and throw.\n */\nexport function ActivityPreview({\n draft,\n renderMode = 'practice',\n response,\n outcome,\n fallback,\n renderers,\n shuffleSeed,\n sanitizeHtml,\n strings,\n theme,\n locale,\n}: ActivityPreviewProps): React.JSX.Element {\n const s = useLkStrings(strings);\n const type = isRecord(draft) && typeof draft.type === 'string' ? draft.type : undefined;\n\n // Validated on the draft's CONTENT, not its identity. The activity components\n // restart whenever `data` changes identity, and an editor that rebuilds its\n // payload on every change would otherwise restart the question an author is\n // trying out each time anything else on the page was edited. The key is read\n // on every render, so a draft changed in place is checked again too.\n const contentKey = contentKeyOf(draft);\n // biome-ignore lint/correctness/useExhaustiveDependencies: the draft's content (contentKey) is the trigger, not its identity\n const result = useMemo(\n () => (type === undefined ? undefined : validateDraft(type as ActivityType, draft)),\n [type, contentKey],\n );\n const activity = result?.status === 'complete' ? result.data : undefined;\n const responseFits = response === undefined || response.type === type;\n const responseKey = response === undefined ? 'none' : contentKeyOf(response);\n\n // biome-ignore lint/correctness/useExhaustiveDependencies: the response's content (responseKey) is the trigger, not its identity\n const marking = useMemo((): Marking | undefined => {\n if (renderMode !== 'review' || activity === undefined) {\n return undefined;\n }\n if (outcome !== undefined) {\n return { kind: 'marked', outcome };\n }\n if (response === undefined || !responseFits) {\n return undefined;\n }\n try {\n return { kind: 'marked', outcome: evaluate(activity, response) };\n } catch (thrown) {\n // Scored here, during this component's own render, where no error boundary\n // inside the activity can catch it — so it is handed to one below.\n return {\n kind: 'failed',\n error: thrown instanceof Error ? thrown : new Error(String(thrown)),\n };\n }\n }, [renderMode, activity, outcome, responseKey, responseFits]);\n\n const activityId = activity?.id;\n const mediaBudget = useMemo<MediaBudgetBinding>(\n () => ({\n key: slotMediaKey(PREVIEW_SLOT_ID),\n slotId: PREVIEW_SLOT_ID,\n index: 0,\n ...(activityId !== undefined ? { activityId } : {}),\n }),\n [activityId],\n );\n\n // All hooks are called before these throws, so hook order stays stable.\n if (type === undefined || result === undefined) {\n throw new Error(\n '<ActivityPreview> needs a draft object with a string `type`, to know which activity to check and render.',\n );\n }\n // Checked against the draft's own type, whatever state the draft is in. A\n // mis-wired response is a wiring error however unfinished the draft is, and\n // waiting for the draft to be complete would throw on the very edit that\n // completes it.\n if (response !== undefined && !responseFits) {\n throw new Error(\n `<ActivityPreview> was given a \"${response.type}\" response for a \"${type}\" activity.`,\n );\n }\n if (result.status !== 'complete') {\n if (fallback !== undefined) {\n return <>{fallback(result)}</>;\n }\n return (\n <div\n className=\"lk-preview-unavailable\"\n role=\"note\"\n data-status={result.status}\n {...(locale !== undefined ? { lang: locale } : {})}\n >\n {result.status === 'invalid' ? s.previewInvalid : s.previewIncomplete}\n </div>\n );\n }\n const data = result.data;\n\n // A different simulated response, or a different mode, mounts the activity\n // afresh, play count included: a seeded answer is read at mount, and a practice\n // attempt the author submitted must not carry over into an exam preview. So\n // does a different recording or playback policy, which is a different listening\n // budget — the transport reads its count once, when it mounts.\n const key = `${renderMode}:${responseKey}:${recordingKeyOf(data)}`;\n\n if (marking?.kind === 'failed') {\n // The response is one the scorer cannot read, or a registered scorer threw.\n // Shown as the activity's error fallback, as a render failure would be.\n return (\n <ActivityErrorBoundary key={key} activityTitle={data.title} strings={s}>\n <Rethrow error={marking.error} />\n </ActivityErrorBoundary>\n );\n }\n\n const shared = {\n renderMode,\n // Without a binding a budgeted recording counts nothing in `practice`, and in\n // `exam` the media component refuses to render at all — rightly, on a real\n // paper, where nothing would persist the count. A preview persists nothing by\n // design, so it keeps the count in memory and says so.\n mediaBudget,\n ...(sanitizeHtml !== undefined ? { sanitizeHtml } : {}),\n ...(strings !== undefined ? { strings } : {}),\n ...(theme !== undefined ? { theme } : {}),\n ...(locale !== undefined ? { locale } : {}),\n // Seeded rather than controlled, in every mode, exactly as `<ActivitySequence>`\n // hands a restored answer to its slots — so a renderer written for the\n // sequence reads it here too. `review` is read-only either way, and `key`\n // above re-seeds whenever the response changes.\n ...(response !== undefined ? { defaultValue: response } : {}),\n ...(marking !== undefined ? { outcome: marking.outcome } : {}),\n };\n\n const Custom = renderers?.[data.type];\n if (Custom !== undefined) {\n return <Custom key={key} data={data} {...shared} />;\n }\n switch (data.type) {\n case 'multiple-choice':\n return (\n <MultipleChoice\n key={key}\n data={data}\n shuffleSeed={shuffleSeed ?? PREVIEW_SHUFFLE_SEED}\n {...shared}\n />\n );\n case 'fill-in-the-blanks':\n return <FillInTheBlanks key={key} data={data} {...shared} />;\n case 'gap-select':\n return (\n <GapSelect\n key={key}\n data={data}\n shuffleSeed={shuffleSeed ?? PREVIEW_SHUFFLE_SEED}\n {...shared}\n />\n );\n case 'written-response':\n return <WrittenResponse key={key} data={data} {...shared} />;\n case 'dictation':\n return <Dictation key={key} data={data} {...shared} />;\n case 'read-aloud':\n return <ReadAloud key={key} data={data} recordingBinding={PREVIEW_RECORDING} {...shared} />;\n default:\n everyBuiltInTypeHandled(data);\n return (\n <div className=\"lk-preview-unsupported\" role=\"note\">\n {s.unsupportedActivity}\n </div>\n );\n }\n}\n\n/** Throws `error` while rendering, so the error boundary around it shows its fallback. */\nfunction Rethrow({ error }: { error: Error }): never {\n throw error;\n}\n\nfunction isRecord(value: unknown): value is Readonly<Record<string, unknown>> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/**\n * The recording and its playback policy — not its description, which changes\n * no budget — plus the slower file a dictation or a read-aloud carries, which\n * follows the same policy and must remount when it is swapped for another.\n */\nfunction recordingKeyOf(data: unknown): string {\n const media = isRecord(data) ? data.media : undefined;\n const slowMedia = isRecord(data) ? data.slowMedia : undefined;\n const slowUrl = isRecord(slowMedia) ? slowMedia.url : undefined;\n if (!isRecord(media) && slowUrl === undefined) {\n return '';\n }\n return contentKeyOf([\n isRecord(media) ? media.type : undefined,\n isRecord(media) ? media.url : undefined,\n isRecord(media) ? media.playback : undefined,\n slowUrl,\n ]);\n}\n\nconst identities = new WeakMap<object, number>();\nlet lastIdentity = 0;\n\n/** A key for a value compared by identity: the same object always gets the same one. */\nfunction identityKeyOf(value: object): string {\n let identity = identities.get(value);\n if (identity === undefined) {\n lastIdentity += 1;\n identity = lastIdentity;\n identities.set(value, identity);\n }\n return `#${identity}`;\n}\n\n/**\n * A string that changes exactly when a value's content does.\n *\n * Object keys are sorted, because key order is not content: a draft saved to a\n * JSON column comes back with its keys reordered. Every value is written in a\n * form no other value shares — JSON would write a non-finite number as `null`\n * and drop `undefined`, and those decide different issues. What JSON does not\n * describe — an instance of a class, a function, a reference back to an\n * enclosing object — is compared by identity, and a symbol by its description.\n */\nfunction contentKeyOf(value: unknown, enclosing: Set<object> = new Set()): string {\n switch (typeof value) {\n case 'string':\n return JSON.stringify(value);\n case 'number':\n // `String` keeps NaN, the infinities and every other number apart; only -0\n // needs saying.\n return Object.is(value, -0) ? '-0' : String(value);\n case 'bigint':\n return `${value}n`;\n case 'boolean':\n case 'undefined':\n return String(value);\n case 'symbol':\n return `Symbol(${JSON.stringify(value.description ?? null)})`;\n case 'function':\n return identityKeyOf(value);\n }\n // Every other `typeof` has returned above: this is `null`, or an object.\n if (value === null || typeof value !== 'object') {\n return 'null';\n }\n const prototype = Object.getPrototypeOf(value);\n const plain = Array.isArray(value) || prototype === Object.prototype || prototype === null;\n if (!plain || enclosing.has(value)) {\n return identityKeyOf(value);\n }\n enclosing.add(value);\n try {\n if (Array.isArray(value)) {\n return `[${Array.from(value, (item) => contentKeyOf(item, enclosing)).join(',')}]`;\n }\n const record = value as Record<string, unknown>;\n const fields = Object.keys(record)\n .sort()\n .map((field) => `${JSON.stringify(field)}:${contentKeyOf(record[field], enclosing)}`);\n return `{${fields.join(',')}}`;\n } finally {\n enclosing.delete(value);\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ActivityPreview-BKr34exz.cjs","names":["useLkStrings","useMemo","validateDraft","evaluate","slotMediaKey","ActivityErrorBoundary","MultipleChoice","FillInTheBlanks","GapSelect","WrittenResponse","Dictation","ReadAloud"],"sources":["../src/components/ActivityPreview/ActivityPreview.tsx"],"sourcesContent":["'use client';\n\nimport {\n type ActivityType,\n type DraftNotComplete,\n evaluate,\n type ItemOutcome,\n type LearnerResponse,\n slotMediaKey,\n type ThemeTokens,\n validateDraft,\n} from '@intellectif/lk-core';\nimport { type ReactNode, useMemo } from 'react';\nimport { useLkStrings } from '../../i18n/LkIntlProvider.js';\nimport type { LkStringsOverride } from '../../i18n/strings.js';\nimport { everyBuiltInTypeHandled } from '../_internal.js';\nimport { ActivityErrorBoundary } from '../ActivityErrorBoundary.js';\nimport type { ActivityRenderer } from '../ActivitySequence/index.js';\nimport { Dictation } from '../Dictation/index.js';\nimport { FillInTheBlanks } from '../FillInTheBlanks/index.js';\nimport { GapSelect } from '../GapSelect/index.js';\nimport { MultipleChoice } from '../MultipleChoice/index.js';\nimport { ReadAloud } from '../ReadAloud/index.js';\nimport type { RecordingBinding } from '../ReadAloud/ReadAloud.js';\nimport type { HtmlSanitizer, MediaBudgetBinding, RenderMode } from '../types.js';\nimport { WrittenResponse } from '../WrittenResponse/index.js';\n\n/**\n * The option-shuffle seed a preview uses when none is given. Fixed, so a\n * shuffled question holds still while its author edits it instead of\n * re-ordering on every remount.\n */\nconst PREVIEW_SHUFFLE_SEED = 'lk-activity-preview';\n\n/**\n * The slot a previewed activity stands in for. A recording's play budget is\n * keyed to it and lives only in this mount: the preview counts plays so an\n * author hears the limit a learner gets, and records none of them.\n */\nconst PREVIEW_SLOT_ID = 'preview';\n\n/** Numbers the takes a preview mints keys for, so two are never the same take. */\nlet previewTakeCount = 0;\n\n/**\n * The store a previewed read-aloud records into: it keys the take so the whole\n * flow — record, play it back, submit — runs for the author, and keeps it\n * nowhere, like every other thing a preview does not record.\n *\n * It judges nothing, deliberately. `assess` is optional precisely so this can\n * exist: an author trying a reading out has no assessor to point at, and a\n * preview that demanded one would refuse to render the most common case. The\n * default `practice` mode then shows the \"assessment is not available\" notice,\n * which is the truth about this binding rather than a failure.\n */\nconst PREVIEW_RECORDING: RecordingBinding = {\n upload: async (take) => {\n previewTakeCount += 1;\n return {\n key: `${PREVIEW_SLOT_ID}-take-${previewTakeCount}`,\n mimeType: take.mimeType,\n durationMs: take.durationMs,\n };\n },\n};\n\nexport interface ActivityPreviewProps {\n /**\n * The activity as an editor holds it right now, finished or not. It is checked\n * with `validateDraft` before anything renders — by content, on every render —\n * so a half-written question never reaches a component that assumes a valid\n * one.\n */\n draft: unknown;\n /** The mode to preview in. Defaults to `practice`. */\n renderMode?: RenderMode;\n /**\n * A learner response to show as if a learner had given it. In `practice` and\n * `exam` it seeds the answer, which stays editable; in `review` it is the\n * submitted answer, marked by `evaluate()` unless `outcome` is given.\n */\n response?: LearnerResponse;\n /**\n * The outcome `review` marks the response with, instead of the one\n * `evaluate()` computes — which is how to preview a returned grade on a written\n * response, since the SDK cannot score one. Ignored in other modes.\n */\n outcome?: ItemOutcome;\n /**\n * Rendered in place of the activity while the draft is not complete, with the\n * issues `validateDraft` found — so an editor can list them in its own words.\n * The issue messages are English, and a preview that rendered them would put\n * untranslatable text on screen. Without it, the preview shows the\n * `previewIncomplete` or `previewInvalid` string.\n */\n fallback?: (result: DraftNotComplete) => ReactNode;\n /**\n * Renderers for activity types beyond the built-ins, keyed by `type`, as on\n * `<ActivitySequence>`. An entry receives the shared prop contract and\n * nothing else, so — like the shuffle seed below — the in-memory recording\n * binding a previewed read-aloud is given does not reach one. Nor does the\n * `question` a sequence hands a renderer: a preview is one question with no\n * set around it, so there is no slot to report to and nothing to page away\n * from. A renderer that uses it must treat it as optional, which its type\n * already says.\n */\n renderers?: Readonly<Record<string, ActivityRenderer>>;\n /**\n * Seeds `<MultipleChoice>`'s option shuffle. Defaults to a fixed seed, so the\n * order holds while options are edited; adding or removing one deals them\n * again, because the shuffle orders by position. A `renderers` entry is given\n * no seed, as on `<ActivitySequence>`.\n */\n shuffleSeed?: string;\n /** Renders author-supplied rich text. See `HtmlSanitizer`. */\n sanitizeHtml?: HtmlSanitizer;\n /** Overrides the SDK's chrome text, for the preview's notice and the activity alike. */\n strings?: LkStringsOverride;\n /** Per-instance token overrides, passed to the activity. */\n theme?: Partial<ThemeTokens>;\n /** BCP 47 tag of the interface language, passed to the activity. */\n locale?: string;\n}\n\ntype Marking = { kind: 'marked'; outcome: ItemOutcome } | { kind: 'failed'; error: Error };\n\n/**\n * Renders an activity from an editor's draft, in any render mode, with a\n * simulated learner response — and renders a notice instead while the draft is\n * not an activity yet.\n *\n * Nothing it renders is recorded: no callback is wired, so a practice attempt in\n * a preview scores locally and goes nowhere, and a recording's play limit is\n * counted in memory only.\n *\n * A draft with no string `type`, a type nobody registered, and a `response` of a\n * different type from the draft are wiring errors, and throw.\n */\nexport function ActivityPreview({\n draft,\n renderMode = 'practice',\n response,\n outcome,\n fallback,\n renderers,\n shuffleSeed,\n sanitizeHtml,\n strings,\n theme,\n locale,\n}: ActivityPreviewProps) {\n const s = useLkStrings(strings);\n const type = isRecord(draft) && typeof draft.type === 'string' ? draft.type : undefined;\n\n // Validated on the draft's CONTENT, not its identity. The activity components\n // restart whenever `data` changes identity, and an editor that rebuilds its\n // payload on every change would otherwise restart the question an author is\n // trying out each time anything else on the page was edited. The key is read\n // on every render, so a draft changed in place is checked again too.\n const contentKey = contentKeyOf(draft);\n // biome-ignore lint/correctness/useExhaustiveDependencies: the draft's content (contentKey) is the trigger, not its identity\n const result = useMemo(\n () => (type === undefined ? undefined : validateDraft(type as ActivityType, draft)),\n [type, contentKey],\n );\n const activity = result?.status === 'complete' ? result.data : undefined;\n const responseFits = response === undefined || response.type === type;\n const responseKey = response === undefined ? 'none' : contentKeyOf(response);\n\n // biome-ignore lint/correctness/useExhaustiveDependencies: the response's content (responseKey) is the trigger, not its identity\n const marking = useMemo((): Marking | undefined => {\n if (renderMode !== 'review' || activity === undefined) {\n return undefined;\n }\n if (outcome !== undefined) {\n return { kind: 'marked', outcome };\n }\n if (response === undefined || !responseFits) {\n return undefined;\n }\n try {\n return { kind: 'marked', outcome: evaluate(activity, response) };\n } catch (thrown) {\n // Scored here, during this component's own render, where no error boundary\n // inside the activity can catch it — so it is handed to one below.\n return {\n kind: 'failed',\n error: thrown instanceof Error ? thrown : new Error(String(thrown)),\n };\n }\n }, [renderMode, activity, outcome, responseKey, responseFits]);\n\n const activityId = activity?.id;\n const mediaBudget = useMemo<MediaBudgetBinding>(\n () => ({\n key: slotMediaKey(PREVIEW_SLOT_ID),\n slotId: PREVIEW_SLOT_ID,\n index: 0,\n ...(activityId !== undefined ? { activityId } : {}),\n }),\n [activityId],\n );\n\n // All hooks are called before these throws, so hook order stays stable.\n if (type === undefined || result === undefined) {\n throw new Error(\n '<ActivityPreview> needs a draft object with a string `type`, to know which activity to check and render.',\n );\n }\n // Checked against the draft's own type, whatever state the draft is in. A\n // mis-wired response is a wiring error however unfinished the draft is, and\n // waiting for the draft to be complete would throw on the very edit that\n // completes it.\n if (response !== undefined && !responseFits) {\n throw new Error(\n `<ActivityPreview> was given a \"${response.type}\" response for a \"${type}\" activity.`,\n );\n }\n if (result.status !== 'complete') {\n if (fallback !== undefined) {\n return <>{fallback(result)}</>;\n }\n return (\n <div\n className=\"lk-preview-unavailable\"\n role=\"note\"\n data-status={result.status}\n {...(locale !== undefined ? { lang: locale } : {})}\n >\n {result.status === 'invalid' ? s.previewInvalid : s.previewIncomplete}\n </div>\n );\n }\n const data = result.data;\n\n // A different simulated response, or a different mode, mounts the activity\n // afresh, play count included: a seeded answer is read at mount, and a practice\n // attempt the author submitted must not carry over into an exam preview. So\n // does a different recording or playback policy, which is a different listening\n // budget — the transport reads its count once, when it mounts.\n const key = `${renderMode}:${responseKey}:${recordingKeyOf(data)}`;\n\n if (marking?.kind === 'failed') {\n // The response is one the scorer cannot read, or a registered scorer threw.\n // Shown as the activity's error fallback, as a render failure would be.\n return (\n <ActivityErrorBoundary key={key} activityTitle={data.title} strings={s}>\n <Rethrow error={marking.error} />\n </ActivityErrorBoundary>\n );\n }\n\n const shared = {\n renderMode,\n // Without a binding a budgeted recording counts nothing in `practice`, and in\n // `exam` the media component refuses to render at all — rightly, on a real\n // paper, where nothing would persist the count. A preview persists nothing by\n // design, so it keeps the count in memory and says so.\n mediaBudget,\n ...(sanitizeHtml !== undefined ? { sanitizeHtml } : {}),\n ...(strings !== undefined ? { strings } : {}),\n ...(theme !== undefined ? { theme } : {}),\n ...(locale !== undefined ? { locale } : {}),\n // Seeded rather than controlled, in every mode, exactly as `<ActivitySequence>`\n // hands a restored answer to its slots — so a renderer written for the\n // sequence reads it here too. `review` is read-only either way, and `key`\n // above re-seeds whenever the response changes.\n ...(response !== undefined ? { defaultValue: response } : {}),\n ...(marking !== undefined ? { outcome: marking.outcome } : {}),\n };\n\n const Custom = renderers?.[data.type];\n if (Custom !== undefined) {\n return <Custom key={key} data={data} {...shared} />;\n }\n switch (data.type) {\n case 'multiple-choice':\n return (\n <MultipleChoice\n key={key}\n data={data}\n shuffleSeed={shuffleSeed ?? PREVIEW_SHUFFLE_SEED}\n {...shared}\n />\n );\n case 'fill-in-the-blanks':\n return <FillInTheBlanks key={key} data={data} {...shared} />;\n case 'gap-select':\n return (\n <GapSelect\n key={key}\n data={data}\n shuffleSeed={shuffleSeed ?? PREVIEW_SHUFFLE_SEED}\n {...shared}\n />\n );\n case 'written-response':\n return <WrittenResponse key={key} data={data} {...shared} />;\n case 'dictation':\n return <Dictation key={key} data={data} {...shared} />;\n case 'read-aloud':\n return <ReadAloud key={key} data={data} recordingBinding={PREVIEW_RECORDING} {...shared} />;\n default:\n everyBuiltInTypeHandled(data);\n return (\n <div className=\"lk-preview-unsupported\" role=\"note\">\n {s.unsupportedActivity}\n </div>\n );\n }\n}\n\n/** Throws `error` while rendering, so the error boundary around it shows its fallback. */\nfunction Rethrow({ error }: { error: Error }): never {\n throw error;\n}\n\nfunction isRecord(value: unknown): value is Readonly<Record<string, unknown>> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/**\n * The recording and its playback policy — not its description, which changes\n * no budget — plus the slower file a dictation or a read-aloud carries, which\n * follows the same policy and must remount when it is swapped for another.\n */\nfunction recordingKeyOf(data: unknown): string {\n const media = isRecord(data) ? data.media : undefined;\n const slowMedia = isRecord(data) ? data.slowMedia : undefined;\n const slowUrl = isRecord(slowMedia) ? slowMedia.url : undefined;\n if (!isRecord(media) && slowUrl === undefined) {\n return '';\n }\n return contentKeyOf([\n isRecord(media) ? media.type : undefined,\n isRecord(media) ? media.url : undefined,\n isRecord(media) ? media.playback : undefined,\n slowUrl,\n ]);\n}\n\nconst identities = new WeakMap<object, number>();\nlet lastIdentity = 0;\n\n/** A key for a value compared by identity: the same object always gets the same one. */\nfunction identityKeyOf(value: object): string {\n let identity = identities.get(value);\n if (identity === undefined) {\n lastIdentity += 1;\n identity = lastIdentity;\n identities.set(value, identity);\n }\n return `#${identity}`;\n}\n\n/**\n * A string that changes exactly when a value's content does.\n *\n * Object keys are sorted, because key order is not content: a draft saved to a\n * JSON column comes back with its keys reordered. Every value is written in a\n * form no other value shares — JSON would write a non-finite number as `null`\n * and drop `undefined`, and those decide different issues. What JSON does not\n * describe — an instance of a class, a function, a reference back to an\n * enclosing object — is compared by identity, and a symbol by its description.\n */\nfunction contentKeyOf(value: unknown, enclosing: Set<object> = new Set()): string {\n switch (typeof value) {\n case 'string':\n return JSON.stringify(value);\n case 'number':\n // `String` keeps NaN, the infinities and every other number apart; only -0\n // needs saying.\n return Object.is(value, -0) ? '-0' : String(value);\n case 'bigint':\n return `${value}n`;\n case 'boolean':\n case 'undefined':\n return String(value);\n case 'symbol':\n return `Symbol(${JSON.stringify(value.description ?? null)})`;\n case 'function':\n return identityKeyOf(value);\n }\n // Every other `typeof` has returned above: this is `null`, or an object.\n if (value === null || typeof value !== 'object') {\n return 'null';\n }\n const prototype = Object.getPrototypeOf(value);\n const plain = Array.isArray(value) || prototype === Object.prototype || prototype === null;\n if (!plain || enclosing.has(value)) {\n return identityKeyOf(value);\n }\n enclosing.add(value);\n try {\n if (Array.isArray(value)) {\n return `[${Array.from(value, (item) => contentKeyOf(item, enclosing)).join(',')}]`;\n }\n const record = value as Record<string, unknown>;\n const fields = Object.keys(record)\n .sort()\n .map((field) => `${JSON.stringify(field)}:${contentKeyOf(record[field], enclosing)}`);\n return `{${fields.join(',')}}`;\n } finally {\n enclosing.delete(value);\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ActivityPreview-BKr34exz.cjs","names":["useLkStrings","useMemo","validateDraft","evaluate","slotMediaKey","ActivityErrorBoundary","MultipleChoice","FillInTheBlanks","GapSelect","WrittenResponse","Dictation","ReadAloud"],"sources":["../src/components/ActivityPreview/ActivityPreview.tsx"],"sourcesContent":["'use client';\n\nimport {\n type ActivityType,\n type DraftNotComplete,\n evaluate,\n type ItemOutcome,\n type LearnerResponse,\n slotMediaKey,\n type ThemeTokens,\n validateDraft,\n} from '@intellectif/lk-core';\nimport { type ReactNode, useMemo } from 'react';\nimport { useLkStrings } from '../../i18n/LkIntlProvider.js';\nimport type { LkStringsOverride } from '../../i18n/strings.js';\nimport { everyBuiltInTypeHandled } from '../_internal.js';\nimport { ActivityErrorBoundary } from '../ActivityErrorBoundary.js';\nimport type { ActivityRenderer } from '../ActivitySequence/index.js';\nimport { Dictation } from '../Dictation/index.js';\nimport { FillInTheBlanks } from '../FillInTheBlanks/index.js';\nimport { GapSelect } from '../GapSelect/index.js';\nimport { MultipleChoice } from '../MultipleChoice/index.js';\nimport { ReadAloud } from '../ReadAloud/index.js';\nimport type { RecordingBinding } from '../ReadAloud/ReadAloud.js';\nimport type { HtmlSanitizer, MediaBudgetBinding, RenderMode } from '../types.js';\nimport { WrittenResponse } from '../WrittenResponse/index.js';\n\n/**\n * The option-shuffle seed a preview uses when none is given. Fixed, so a\n * shuffled question holds still while its author edits it instead of\n * re-ordering on every remount.\n */\nconst PREVIEW_SHUFFLE_SEED = 'lk-activity-preview';\n\n/**\n * The slot a previewed activity stands in for. A recording's play budget is\n * keyed to it and lives only in this mount: the preview counts plays so an\n * author hears the limit a learner gets, and records none of them.\n */\nconst PREVIEW_SLOT_ID = 'preview';\n\n/** Numbers the takes a preview mints keys for, so two are never the same take. */\nlet previewTakeCount = 0;\n\n/**\n * The store a previewed read-aloud records into: it keys the take so the whole\n * flow — record, play it back, submit — runs for the author, and keeps it\n * nowhere, like every other thing a preview does not record.\n *\n * It judges nothing, deliberately. `assess` is optional precisely so this can\n * exist: an author trying a reading out has no assessor to point at, and a\n * preview that demanded one would refuse to render the most common case. The\n * default `practice` mode then shows the \"assessment is not available\" notice,\n * which is the truth about this binding rather than a failure.\n */\nconst PREVIEW_RECORDING: RecordingBinding = {\n upload: async (take) => {\n previewTakeCount += 1;\n return {\n key: `${PREVIEW_SLOT_ID}-take-${previewTakeCount}`,\n mimeType: take.mimeType,\n durationMs: take.durationMs,\n };\n },\n};\n\nexport interface ActivityPreviewProps {\n /**\n * The activity as an editor holds it right now, finished or not. It is checked\n * with `validateDraft` before anything renders — by content, on every render —\n * so a half-written question never reaches a component that assumes a valid\n * one.\n */\n draft: unknown;\n /** The mode to preview in. Defaults to `practice`. */\n renderMode?: RenderMode;\n /**\n * A learner response to show as if a learner had given it. In `practice` and\n * `exam` it seeds the answer, which stays editable; in `review` it is the\n * submitted answer, marked by `evaluate()` unless `outcome` is given.\n */\n response?: LearnerResponse;\n /**\n * The outcome `review` marks the response with, instead of the one\n * `evaluate()` computes — which is how to preview a returned grade on a written\n * response, since the SDK cannot score one. Ignored in other modes.\n */\n outcome?: ItemOutcome;\n /**\n * Rendered in place of the activity while the draft is not complete, with the\n * issues `validateDraft` found — so an editor can list them in its own words.\n * The issue messages are English, and a preview that rendered them would put\n * untranslatable text on screen. Without it, the preview shows the\n * `previewIncomplete` or `previewInvalid` string.\n */\n fallback?: (result: DraftNotComplete) => ReactNode;\n /**\n * Renderers for activity types beyond the built-ins, keyed by `type`, as on\n * `<ActivitySequence>`. An entry receives the shared prop contract and\n * nothing else, so — like the shuffle seed below — the in-memory recording\n * binding a previewed read-aloud is given does not reach one. Nor does the\n * `question` a sequence hands a renderer: a preview is one question with no\n * set around it, so there is no slot to report to and nothing to page away\n * from. A renderer that uses it must treat it as optional, which its type\n * already says.\n */\n renderers?: Readonly<Record<string, ActivityRenderer>>;\n /**\n * Seeds `<MultipleChoice>`'s option shuffle. Defaults to a fixed seed, so the\n * order holds while options are edited; adding or removing one deals them\n * again, because the shuffle orders by position. A `renderers` entry is given\n * no seed, as on `<ActivitySequence>`.\n */\n shuffleSeed?: string;\n /** Renders author-supplied rich text. See `HtmlSanitizer`. */\n sanitizeHtml?: HtmlSanitizer;\n /** Overrides the SDK's chrome text, for the preview's notice and the activity alike. */\n strings?: LkStringsOverride;\n /** Per-instance token overrides, passed to the activity. */\n theme?: Partial<ThemeTokens>;\n /** BCP 47 tag of the interface language, passed to the activity. */\n locale?: string;\n}\n\ntype Marking = { kind: 'marked'; outcome: ItemOutcome } | { kind: 'failed'; error: Error };\n\n/**\n * Renders an activity from an editor's draft, in any render mode, with a\n * simulated learner response — and renders a notice instead while the draft is\n * not an activity yet.\n *\n * Nothing it renders is recorded: no callback is wired, so a practice attempt in\n * a preview scores locally and goes nowhere, and a recording's play limit is\n * counted in memory only.\n *\n * A draft with no string `type`, a type nobody registered, and a `response` of a\n * different type from the draft are wiring errors, and throw.\n */\nexport function ActivityPreview({\n draft,\n renderMode = 'practice',\n response,\n outcome,\n fallback,\n renderers,\n shuffleSeed,\n sanitizeHtml,\n strings,\n theme,\n locale,\n}: ActivityPreviewProps): React.JSX.Element {\n const s = useLkStrings(strings);\n const type = isRecord(draft) && typeof draft.type === 'string' ? draft.type : undefined;\n\n // Validated on the draft's CONTENT, not its identity. The activity components\n // restart whenever `data` changes identity, and an editor that rebuilds its\n // payload on every change would otherwise restart the question an author is\n // trying out each time anything else on the page was edited. The key is read\n // on every render, so a draft changed in place is checked again too.\n const contentKey = contentKeyOf(draft);\n // biome-ignore lint/correctness/useExhaustiveDependencies: the draft's content (contentKey) is the trigger, not its identity\n const result = useMemo(\n () => (type === undefined ? undefined : validateDraft(type as ActivityType, draft)),\n [type, contentKey],\n );\n const activity = result?.status === 'complete' ? result.data : undefined;\n const responseFits = response === undefined || response.type === type;\n const responseKey = response === undefined ? 'none' : contentKeyOf(response);\n\n // biome-ignore lint/correctness/useExhaustiveDependencies: the response's content (responseKey) is the trigger, not its identity\n const marking = useMemo((): Marking | undefined => {\n if (renderMode !== 'review' || activity === undefined) {\n return undefined;\n }\n if (outcome !== undefined) {\n return { kind: 'marked', outcome };\n }\n if (response === undefined || !responseFits) {\n return undefined;\n }\n try {\n return { kind: 'marked', outcome: evaluate(activity, response) };\n } catch (thrown) {\n // Scored here, during this component's own render, where no error boundary\n // inside the activity can catch it — so it is handed to one below.\n return {\n kind: 'failed',\n error: thrown instanceof Error ? thrown : new Error(String(thrown)),\n };\n }\n }, [renderMode, activity, outcome, responseKey, responseFits]);\n\n const activityId = activity?.id;\n const mediaBudget = useMemo<MediaBudgetBinding>(\n () => ({\n key: slotMediaKey(PREVIEW_SLOT_ID),\n slotId: PREVIEW_SLOT_ID,\n index: 0,\n ...(activityId !== undefined ? { activityId } : {}),\n }),\n [activityId],\n );\n\n // All hooks are called before these throws, so hook order stays stable.\n if (type === undefined || result === undefined) {\n throw new Error(\n '<ActivityPreview> needs a draft object with a string `type`, to know which activity to check and render.',\n );\n }\n // Checked against the draft's own type, whatever state the draft is in. A\n // mis-wired response is a wiring error however unfinished the draft is, and\n // waiting for the draft to be complete would throw on the very edit that\n // completes it.\n if (response !== undefined && !responseFits) {\n throw new Error(\n `<ActivityPreview> was given a \"${response.type}\" response for a \"${type}\" activity.`,\n );\n }\n if (result.status !== 'complete') {\n if (fallback !== undefined) {\n return <>{fallback(result)}</>;\n }\n return (\n <div\n className=\"lk-preview-unavailable\"\n role=\"note\"\n data-status={result.status}\n {...(locale !== undefined ? { lang: locale } : {})}\n >\n {result.status === 'invalid' ? s.previewInvalid : s.previewIncomplete}\n </div>\n );\n }\n const data = result.data;\n\n // A different simulated response, or a different mode, mounts the activity\n // afresh, play count included: a seeded answer is read at mount, and a practice\n // attempt the author submitted must not carry over into an exam preview. So\n // does a different recording or playback policy, which is a different listening\n // budget — the transport reads its count once, when it mounts.\n const key = `${renderMode}:${responseKey}:${recordingKeyOf(data)}`;\n\n if (marking?.kind === 'failed') {\n // The response is one the scorer cannot read, or a registered scorer threw.\n // Shown as the activity's error fallback, as a render failure would be.\n return (\n <ActivityErrorBoundary key={key} activityTitle={data.title} strings={s}>\n <Rethrow error={marking.error} />\n </ActivityErrorBoundary>\n );\n }\n\n const shared = {\n renderMode,\n // Without a binding a budgeted recording counts nothing in `practice`, and in\n // `exam` the media component refuses to render at all — rightly, on a real\n // paper, where nothing would persist the count. A preview persists nothing by\n // design, so it keeps the count in memory and says so.\n mediaBudget,\n ...(sanitizeHtml !== undefined ? { sanitizeHtml } : {}),\n ...(strings !== undefined ? { strings } : {}),\n ...(theme !== undefined ? { theme } : {}),\n ...(locale !== undefined ? { locale } : {}),\n // Seeded rather than controlled, in every mode, exactly as `<ActivitySequence>`\n // hands a restored answer to its slots — so a renderer written for the\n // sequence reads it here too. `review` is read-only either way, and `key`\n // above re-seeds whenever the response changes.\n ...(response !== undefined ? { defaultValue: response } : {}),\n ...(marking !== undefined ? { outcome: marking.outcome } : {}),\n };\n\n const Custom = renderers?.[data.type];\n if (Custom !== undefined) {\n return <Custom key={key} data={data} {...shared} />;\n }\n switch (data.type) {\n case 'multiple-choice':\n return (\n <MultipleChoice\n key={key}\n data={data}\n shuffleSeed={shuffleSeed ?? PREVIEW_SHUFFLE_SEED}\n {...shared}\n />\n );\n case 'fill-in-the-blanks':\n return <FillInTheBlanks key={key} data={data} {...shared} />;\n case 'gap-select':\n return (\n <GapSelect\n key={key}\n data={data}\n shuffleSeed={shuffleSeed ?? PREVIEW_SHUFFLE_SEED}\n {...shared}\n />\n );\n case 'written-response':\n return <WrittenResponse key={key} data={data} {...shared} />;\n case 'dictation':\n return <Dictation key={key} data={data} {...shared} />;\n case 'read-aloud':\n return <ReadAloud key={key} data={data} recordingBinding={PREVIEW_RECORDING} {...shared} />;\n default:\n everyBuiltInTypeHandled(data);\n return (\n <div className=\"lk-preview-unsupported\" role=\"note\">\n {s.unsupportedActivity}\n </div>\n );\n }\n}\n\n/** Throws `error` while rendering, so the error boundary around it shows its fallback. */\nfunction Rethrow({ error }: { error: Error }): never {\n throw error;\n}\n\nfunction isRecord(value: unknown): value is Readonly<Record<string, unknown>> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/**\n * The recording and its playback policy — not its description, which changes\n * no budget — plus the slower file a dictation or a read-aloud carries, which\n * follows the same policy and must remount when it is swapped for another.\n */\nfunction recordingKeyOf(data: unknown): string {\n const media = isRecord(data) ? data.media : undefined;\n const slowMedia = isRecord(data) ? data.slowMedia : undefined;\n const slowUrl = isRecord(slowMedia) ? slowMedia.url : undefined;\n if (!isRecord(media) && slowUrl === undefined) {\n return '';\n }\n return contentKeyOf([\n isRecord(media) ? media.type : undefined,\n isRecord(media) ? media.url : undefined,\n isRecord(media) ? media.playback : undefined,\n slowUrl,\n ]);\n}\n\nconst identities = new WeakMap<object, number>();\nlet lastIdentity = 0;\n\n/** A key for a value compared by identity: the same object always gets the same one. */\nfunction identityKeyOf(value: object): string {\n let identity = identities.get(value);\n if (identity === undefined) {\n lastIdentity += 1;\n identity = lastIdentity;\n identities.set(value, identity);\n }\n return `#${identity}`;\n}\n\n/**\n * A string that changes exactly when a value's content does.\n *\n * Object keys are sorted, because key order is not content: a draft saved to a\n * JSON column comes back with its keys reordered. Every value is written in a\n * form no other value shares — JSON would write a non-finite number as `null`\n * and drop `undefined`, and those decide different issues. What JSON does not\n * describe — an instance of a class, a function, a reference back to an\n * enclosing object — is compared by identity, and a symbol by its description.\n */\nfunction contentKeyOf(value: unknown, enclosing: Set<object> = new Set()): string {\n switch (typeof value) {\n case 'string':\n return JSON.stringify(value);\n case 'number':\n // `String` keeps NaN, the infinities and every other number apart; only -0\n // needs saying.\n return Object.is(value, -0) ? '-0' : String(value);\n case 'bigint':\n return `${value}n`;\n case 'boolean':\n case 'undefined':\n return String(value);\n case 'symbol':\n return `Symbol(${JSON.stringify(value.description ?? null)})`;\n case 'function':\n return identityKeyOf(value);\n }\n // Every other `typeof` has returned above: this is `null`, or an object.\n if (value === null || typeof value !== 'object') {\n return 'null';\n }\n const prototype = Object.getPrototypeOf(value);\n const plain = Array.isArray(value) || prototype === Object.prototype || prototype === null;\n if (!plain || enclosing.has(value)) {\n return identityKeyOf(value);\n }\n enclosing.add(value);\n try {\n if (Array.isArray(value)) {\n return `[${Array.from(value, (item) => contentKeyOf(item, enclosing)).join(',')}]`;\n }\n const record = value as Record<string, unknown>;\n const fields = Object.keys(record)\n .sort()\n .map((field) => `${JSON.stringify(field)}:${contentKeyOf(record[field], enclosing)}`);\n return `{${fields.join(',')}}`;\n } finally {\n enclosing.delete(value);\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Dictation-BlxRp-3k.js","names":["Dictation","DictationCore"],"sources":["../src/components/Dictation/Dictation.tsx","../src/components/Dictation/index.tsx"],"sourcesContent":["'use client';\n\nimport {\n type ActivityMedia as ActivityMediaData,\n ActivitySchemaError,\n alignDictation,\n assertRedacted,\n computePassThreshold,\n DICTATION_MAX_TEXT_LENGTH,\n type DictationAlignment,\n type DictationCharOp,\n type DictationData,\n type DictationLearnerResponse,\n type DictationWordAlignment,\n diffDictationChars,\n type LearnerResponse,\n type MediaPlaybackPolicy,\n type ScoringDetail,\n type ScoringResult,\n score,\n validateActivity,\n xAPIBuilder,\n xapiDefinitionFor,\n} from '@intellectif/lk-core';\nimport { type CSSProperties, useEffect, useId, useMemo, useRef, useState } from 'react';\nimport { useLearnerAi } from '../../ai/LkAiProvider.js';\nimport { useActivityState } from '../../hooks/useActivityState.js';\nimport { characterDirectionOf, localeDirectionOf, textDirectionOf } from '../../i18n/direction.js';\nimport { useLkStrings } from '../../i18n/LkIntlProvider.js';\nimport { ANONYMOUS_ACTOR, detailIsRight, isDevelopment, objectIdFor } from '../_internal.js';\nimport { ActivityMedia } from '../shared/ActivityMedia.js';\nimport { AiExplanation } from '../shared/AiHelp.js';\nimport { feedbackAnnouncement, useDeliveryPolicy } from '../shared/delivery.js';\nimport { FeedbackRegion } from '../shared/FeedbackRegion.js';\nimport { mintTake, stampTake } from '../shared/sequence-slot.js';\nimport {\n HintCost,\n TryActions,\n triesSummary,\n useItemScoringPolicy,\n useTries,\n} from '../shared/tries.js';\nimport type { ActivityProps, Renderable } from '../types.js';\n\n// Nothing shuffles and nothing is seeded, so the shared contract is the whole\n// contract. Kept as an interface so a later prop lands here, not in a union.\nexport interface DictationProps extends ActivityProps<DictationData> {}\n\n/** How long the learner has to stop typing before one `text-changed` is emitted. */\nconst TEXT_CHANGED_DEBOUNCE_MS = 500;\n\n/**\n * Present for assistive technology, invisible on screen. Inline rather than\n * left to the optional skin: the per-word sentences are the marks' only\n * channel for a screen reader, and without this a page that does not load the\n * skin would show every one of them beside its word.\n */\nconst 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/**\n * Names playback groups. Per mount and never derived from `useId`: two\n * separately hydrated roots derive the same id for the same tree position, and\n * their recordings would pause each other. The name never reaches the DOM.\n */\nlet recordingsGroupCount = 0;\n\n/**\n * A token with nothing spelled in it, as the scorer reads it once composed:\n * only punctuation, control, format and default-ignorable characters, the acute\n * accent, grave accent (and its fullwidth form) and modifier apostrophe the\n * scorer folds into an apostrophe, and the Arabic tatweel and N'Ko lajanyalan\n * it removes.\n */\nconst UNSPELLED_RE = new RegExp(\n `^[\\\\p{P}\\\\p{Cc}\\\\p{Cf}\\\\p{Default_Ignorable_Code_Point}${String.fromCodePoint(0xb4, 0x60, 0xff40, 0x2bc, 0x640, 0x7fa)}]*$`,\n 'u',\n);\n/**\n * Spacing as the scorer reads it: whitespace and NEXT LINE — but not the\n * byte-order mark, which JavaScript counts as whitespace and the scorer\n * removes as a format character, joining the letters on either side.\n */\nconst HINT_SPACE_RE = new RegExp(\n `(?:(?!${String.fromCodePoint(0xfeff)})\\\\s|${String.fromCodePoint(0x85)})+`,\n 'u',\n);\n/** A title with something in it a screen reader can say. */\nconst VISIBLE_TEXT_RE = /[^\\p{White_Space}\\p{Cc}\\p{Cf}\\p{Default_Ignorable_Code_Point}]/u;\n\nconst BULLET = '•';\nconst LEFT_TO_RIGHT_MARK = String.fromCodePoint(0x200e);\nconst RIGHT_TO_LEFT_MARK = String.fromCodePoint(0x200f);\n/**\n * Code points a font draws onto the character before them rather than beside\n * it — combining marks (variation selectors among them), the joiners, emoji\n * skin tones, tag characters, the vowel and final jamo of an old Hangul\n * syllable, and the Thai and Lao SARA AM, whose ring the font sets on the\n * consonant before — so their own span can have no width to decorate.\n */\nconst EXTENDING_RE = new RegExp(\n `^[\\\\p{M}${String.fromCodePoint(0x200c, 0x200d, 0xe33, 0xeb3)}${String.fromCodePoint(0x1f3fb)}-${String.fromCodePoint(0x1f3ff)}${String.fromCodePoint(0xe0020)}-${String.fromCodePoint(0xe007f)}${String.fromCodePoint(0x1160)}-${String.fromCodePoint(0x11ff)}${String.fromCodePoint(0xd7b0)}-${String.fromCodePoint(0xd7ff)}]$`,\n 'u',\n);\n/**\n * Viramas that stack the letter after them into one glyph without asking —\n * Devanagari, Bengali, Gujarati, Oriya, Telugu, Kannada and Malayalam, the\n * Burmese stacker, the Khmer coeng, and the Tai Tham, Balinese, Javanese,\n * Chakma and Brahmi signs. Not the Tamil, Gurmukhi or Sinhala virama, which is\n * drawn visibly and leaves most letters after it on their own.\n */\nconst STACKING_VIRAMAS = new Set(\n [\n 0x94d, 0x9cd, 0xacd, 0xb4d, 0xc4d, 0xccd, 0xd4d, 0x1039, 0x17d2, 0x1a60, 0x1b44, 0xa9c0,\n 0x11046, 0x11133,\n ].map((point) => String.fromCodePoint(point)),\n);\n/**\n * The letters a visible virama still draws into the letter before it: the\n * Gurmukhi RA, YA and VA, which fonts set under the consonant, and the Tamil\n * conjuncts KSSA (க்ஷ) and SHRII (ஸ்ரீ), which fonts draw as one glyph —\n * each keyed by its letter, virama and the letter before the virama.\n */\nconst SUBJOINED_AFTER = new Map<string, (before: readonly string[]) => boolean>([\n ...[0xa30, 0xa2f, 0xa35].map(\n (point) =>\n [\n String.fromCodePoint(point),\n (before: readonly string[]) => before.at(-1) === String.fromCodePoint(0xa4d),\n ] as const,\n ),\n [\n String.fromCodePoint(0xbb7),\n (before: readonly string[]) =>\n before.at(-1) === String.fromCodePoint(0xbcd) &&\n before.at(-2) === String.fromCodePoint(0xb95),\n ],\n [\n String.fromCodePoint(0xbb0),\n (before: readonly string[]) =>\n before.at(-1) === String.fromCodePoint(0xbcd) &&\n before.at(-2) === String.fromCodePoint(0xbb8),\n ],\n]);\n/** Viramas after which a zero-width joiner asks for a conjunct: every stacking one, and the Sinhala. */\nconst JOINABLE_VIRAMAS = new Set([...STACKING_VIRAMAS, String.fromCodePoint(0xdca)]);\nconst ZERO_WIDTH_JOINER = String.fromCodePoint(0x200d);\n/**\n * Characters the font draws into the letter after them: MALAYALAM LETTER DOT\n * REPH, set on the consonant after it, and the MONGOLIAN VOWEL SEPARATOR, which\n * has no width of its own and changes the shape of the vowel after it.\n */\nconst PREPENDED = new Set([String.fromCodePoint(0xd4e), String.fromCodePoint(0x180e)]);\nconst ARABIC_LAM = String.fromCodePoint(0x644);\n/** The alefs a lam before them fuses with into one glyph, in every Arabic font. */\nconst LIGATING_ALEFS = new Set(\n [0x622, 0x623, 0x625, 0x627, 0x671, 0x672, 0x673, 0x675].map((point) =>\n String.fromCodePoint(point),\n ),\n);\nconst REGIONAL_INDICATOR_RE = /^[\\u{1f1e6}-\\u{1f1ff}]$/u;\nconst LETTER_RE = /^\\p{L}$/u;\n/** A letter of any script but Latin, Greek or Cyrillic, whose common ligatures are only decorative. */\nconst LETTER_WITH_REQUIRED_LIGATURES_RE =\n /(?![\\p{Script=Latin}\\p{Script=Greek}\\p{Script=Cyrillic}])\\p{L}/u;\nconst DIGIT_RE = /^\\p{Nd}$/u;\n/**\n * Signs laid out with the European number beside them (bidirectional class\n * ET): the currency signs, the degree, plus-minus and per-mille signs.\n */\nconst NUMBER_SIGN_RE = new RegExp(\n `^[\\\\p{Sc}${String.fromCodePoint(0xb0, 0xb1, 0x2030, 0x2031, 0x212e, 0x2213)}]$`,\n 'u',\n);\n/** Letters of the Arabic, Syriac and Thaana blocks and their presentation forms: before them, a European number is not one. */\nconst ARABIC_LETTER_BLOCKS: readonly (readonly [number, number])[] = [\n [0x0600, 0x07bf],\n [0x0860, 0x08ff],\n [0xfb50, 0xfdff],\n [0xfe70, 0xfeff],\n];\n\n/**\n * The text out of a `LearnerResponse`. A response of another activity type\n * reads as nothing typed rather than throwing — a controlled host that has not\n * yet swapped its state on an activity change would otherwise crash the attempt.\n */\nfunction textOf(response: LearnerResponse | undefined): string {\n return response !== undefined && response.type === 'dictation' ? response.text : '';\n}\n\n/** The hint count out of a `LearnerResponse`; absent or another type reads as none. */\nfunction hintsOf(response: LearnerResponse | undefined): number {\n if (response === undefined || response.type !== 'dictation') {\n return 0;\n }\n const revealed = response.hintsRevealed;\n return typeof revealed === 'number' && Number.isInteger(revealed) && revealed > 0 ? revealed : 0;\n}\n\n/** The response this component emits; `hintsRevealed` is written only when there is one. */\nfunction responseOf(text: string, hintsRevealed: number): DictationLearnerResponse {\n return { type: 'dictation', text, ...(hintsRevealed > 0 ? { hintsRevealed } : {}) };\n}\n\n/**\n * The slower recording as an `ActivityMedia`: it follows the recording's\n * playback policy minus `maxPlays` (`controls`, `seek`, `rate`, the native\n * hints), so a locked scrubber or a fixed speed binds both files — and it never\n * carries a budget of its own, so `<ActivityMedia>` never demands a binding\n * for it. `undefined` when there is no slow recording.\n */\nfunction slowMediaFor(\n data: Renderable<DictationData>,\n label: string,\n): ActivityMediaData | undefined {\n const slow = data.slowMedia;\n if (slow === undefined) {\n return undefined;\n }\n const policy: MediaPlaybackPolicy = data.media?.playback ?? {};\n const { maxPlays: _budget, ...inherited } = policy;\n const hasPolicy = Object.values(inherited).some((value) => value !== undefined);\n return {\n type: 'audio',\n url: slow.url,\n alt: slow.alt ?? label,\n ...(hasPolicy ? { playback: inherited } : {}),\n };\n}\n\n/**\n * The transcript's words as a learner would read them: raw, in order, split at\n * whitespace. A token with nothing in it that is spelled — a dash, an ellipsis\n * — is joined to the word before it (or to the first word, when it leads), so\n * every reveal shows a word. A script written without spaces is one token, so\n * one reveal shows the whole sentence.\n */\nfunction hintWordsOf(transcript: string): string[] {\n const words: string[] = [];\n let leading = '';\n for (const token of transcript.split(HINT_SPACE_RE)) {\n if (token === '') {\n continue;\n }\n const last = words.length - 1;\n // Composed first: the Greek varia and oxia decompose to the grave and acute\n // accents, which the scorer folds into an apostrophe and removes.\n if (!UNSPELLED_RE.test(token.normalize('NFC'))) {\n words.push(leading === '' ? token : `${leading} ${token}`);\n leading = '';\n } else if (last >= 0) {\n words[last] = `${words[last]} ${token}`;\n } else {\n leading = leading === '' ? token : `${leading} ${token}`;\n }\n }\n return words;\n}\n\n/**\n * Word marks rebuilt from stored scoring details — a server-recorded mark\n * shown after an exam, with no transcript on the client. Extras are not in the\n * details (the scorer writes one per transcript word), so none appear.\n */\nfunction wordsFromDetails(details: readonly ScoringDetail[]): DictationWordAlignment[] | null {\n if (details.length === 0 || !details.every((detail) => /^w\\d+$/.test(detail.itemId))) {\n return null;\n }\n return details.map((detail) => {\n const attempt = Array.isArray(detail.learnerResponse)\n ? detail.learnerResponse.join(' ')\n : detail.learnerResponse;\n const reference = Array.isArray(detail.correctResponse)\n ? detail.correctResponse.join(' ')\n : detail.correctResponse;\n const correct = detailIsRight(detail);\n const missing = detail.outcome === 'incorrect-omission' || (!correct && attempt === '');\n return {\n itemId: detail.itemId,\n reference,\n attempt,\n similarity: detail.score ?? (correct ? 1 : 0),\n status: correct ? 'correct' : missing ? 'missing' : 'incorrect',\n };\n });\n}\n\n/** A run of character operations drawn as one glyph cluster, and how it is marked. */\ninterface MarkedCluster {\n op: DictationCharOp['op'];\n text: string;\n}\n\n/**\n * Whether `shown`, the character an operation displays, is drawn into the\n * cluster whose characters so far are `before`: a mark, a joiner or a skin tone\n * onto its base, a letter stacked under a virama, an alef fused with the lam\n * before it, the second half of a flag.\n */\nfunction extendsCluster(before: readonly string[], shown: string): boolean {\n const previous = before.at(-1) as string;\n if (EXTENDING_RE.test(shown)) {\n return true;\n }\n if (LETTER_RE.test(shown)) {\n if (\n STACKING_VIRAMAS.has(previous) ||\n PREPENDED.has(previous) ||\n SUBJOINED_AFTER.get(shown)?.(before) === true\n ) {\n return true;\n }\n if (previous === ZERO_WIDTH_JOINER && JOINABLE_VIRAMAS.has(before.at(-2) as string)) {\n return true;\n }\n }\n if (previous === ARABIC_LAM && LIGATING_ALEFS.has(shown)) {\n return true;\n }\n return (\n REGIONAL_INDICATOR_RE.test(shown) &&\n before.length % 2 === 1 &&\n before.every((character) => REGIONAL_INDICATOR_RE.test(character))\n );\n}\n\n/** The character an operation stands for: what should have been typed, or what was typed in excess. */\nfunction characterOf(op: DictationCharOp): string {\n return op.reference === '' ? op.attempt : op.reference;\n}\n\nfunction isArabicLetter(character: string | undefined): boolean {\n const point = character?.codePointAt(0);\n return (\n point !== undefined &&\n LETTER_RE.test(character as string) &&\n ARABIC_LETTER_BLOCKS.some(([first, last]) => point >= first && point <= last)\n );\n}\n\n/**\n * The directions missing characters' bullets are laid out in, by operation\n * index, worked out in two passes over the operations rather than a search\n * per bullet: a word of 2,000 emoji missing its letters took seconds to draw.\n *\n * A bullet takes the character's own direction when it has one — a digit runs\n * left to right, unless it is a digit of a right-to-left script. A currency,\n * degree or per-mille sign beside a digit is laid out with that number, left to\n * right, unless an Arabic letter comes before it, after which the number is not\n * a European one. Otherwise, for a space or a punctuation mark, it takes the\n * direction of the characters on both sides when they agree, and the content's\n * when they do not. A bare bullet is a neutral, which the bidirectional\n * algorithm would otherwise attach to the wrong side of a number or a word of\n * the other direction.\n */\nexport function bulletDirections(\n ops: readonly DictationCharOp[],\n contentDir: 'ltr' | 'rtl' | undefined,\n): Map<number, 'ltr' | 'rtl' | undefined> {\n const own = ops.map((op) => characterDirectionOf(characterOf(op)));\n const before: ('ltr' | 'rtl' | undefined)[] = [];\n const arabicBefore: boolean[] = [];\n let lastDirection: 'ltr' | 'rtl' | undefined;\n let lastLetterArabic = false;\n ops.forEach((op, index) => {\n before[index] = lastDirection;\n arabicBefore[index] = lastLetterArabic;\n lastDirection = own[index] ?? lastDirection;\n if (LETTER_RE.test(characterOf(op))) {\n lastLetterArabic = isArabicLetter(characterOf(op));\n }\n });\n const after: ('ltr' | 'rtl' | undefined)[] = [];\n let nextDirection: 'ltr' | 'rtl' | undefined;\n for (let index = ops.length - 1; index >= 0; index -= 1) {\n after[index] = nextDirection;\n nextDirection = own[index] ?? nextDirection;\n }\n const directions = new Map<number, 'ltr' | 'rtl' | undefined>();\n ops.forEach((op, index) => {\n if (op.op !== 'missing') {\n return;\n }\n const neighbourIsDigit = [ops[index - 1], ops[index + 1]].some(\n (neighbour) => neighbour !== undefined && DIGIT_RE.test(characterOf(neighbour)),\n );\n directions.set(\n index,\n own[index] ??\n (NUMBER_SIGN_RE.test(characterOf(op)) && neighbourIsDigit && !arabicBefore[index]\n ? 'ltr'\n : before[index] !== undefined && before[index] === after[index]\n ? before[index]\n : (contentDir ?? before[index] ?? after[index])),\n );\n });\n return directions;\n}\n\n/**\n * How a cluster is marked. One led by a missing character's bullet is missing,\n * whatever marks sit on the bullet: the character they belong to is not there.\n * Otherwise a cluster is marked as its wrong operations are when they agree —\n * a correct letter with an extra mark is `extra` — and as a substitution when\n * they do not.\n */\nfunction clusterOpOf(ops: readonly DictationCharOp[]): DictationCharOp['op'] {\n if ((ops[0] as DictationCharOp).op === 'missing') {\n return 'missing';\n }\n const wrong = new Set(ops.map((op) => op.op).filter((kind) => kind !== 'equal'));\n if (wrong.size === 0) {\n return 'equal';\n }\n return wrong.size === 1 ? ([...wrong][0] as DictationCharOp['op']) : 'substitute';\n}\n\n/**\n * Character operations grouped into what is drawn as one cluster, so a wrong\n * combining mark, joiner or stacked letter marks the glyph it is drawn into —\n * alone, its span has no width, and its underline or strike would not be\n * painted at all. A missing character's bullet carries only the marks drawn\n * onto it, never a letter that is drawn apart. A missing character's bullet is\n * wrapped in the direction mark of the character it stands for. Consecutive\n * correct clusters are one run: an engine that shapes each element on its own\n * would otherwise draw a correctly typed conjunct or a joined Mongolian word\n * broken apart.\n */\nfunction clustersOf(\n ops: readonly DictationCharOp[],\n contentDir: 'ltr' | 'rtl' | undefined,\n): MarkedCluster[] {\n const bullets = bulletDirections(ops, contentDir);\n const groups: { ops: DictationCharOp[]; shown: string[]; bullet?: 'ltr' | 'rtl' }[] = [];\n ops.forEach((op, index) => {\n const shown = op.op === 'missing' ? BULLET : op.attempt;\n const current = groups.at(-1);\n if (\n current !== undefined &&\n op.op !== 'missing' &&\n ((current.ops[0] as DictationCharOp).op === 'missing'\n ? EXTENDING_RE.test(shown)\n : extendsCluster(current.shown, shown))\n ) {\n current.ops.push(op);\n current.shown.push(shown);\n return;\n }\n const direction = bullets.get(index);\n groups.push({\n ops: [op],\n shown: [shown],\n ...(direction !== undefined ? { bullet: direction } : {}),\n });\n });\n const clusters: MarkedCluster[] = [];\n for (const group of groups) {\n const mark =\n group.bullet === undefined\n ? ''\n : group.bullet === 'rtl'\n ? RIGHT_TO_LEFT_MARK\n : LEFT_TO_RIGHT_MARK;\n const cluster = { op: clusterOpOf(group.ops), text: `${mark}${group.shown.join('')}${mark}` };\n const previous = clusters.at(-1);\n if (previous !== undefined && previous.op === 'equal' && cluster.op === 'equal') {\n previous.text += cluster.text;\n } else {\n clusters.push(cluster);\n }\n }\n return clusters;\n}\n\n/**\n * Character-level edit operations as decoration. Hidden from assistive\n * technology as a whole: a screen reader must never verbalise surrogate or\n * combining fragments one by one, and the word list already carries the\n * meaning. Sighted colour-blind and forced-colors users still get one\n * decoration per state, which the skin draws and the legend explains.\n */\nfunction CharOps({\n ops,\n contentDir,\n}: {\n ops: readonly DictationCharOp[];\n contentDir: 'ltr' | 'rtl' | undefined;\n}) {\n return clustersOf(ops, contentDir).map((cluster, index) => (\n <span\n // biome-ignore lint/suspicious/noArrayIndexKey: clusters are positional and recomputed whole\n key={index}\n className=\"lk-dc-op\"\n data-op={cluster.op}\n // A run with no letter whose script needs its common ligatures: the skin\n // turns them off there, so a font's \"fi\" cannot draw a wrong \"i\" into one glyph.\n {...(LETTER_WITH_REQUIRED_LIGATURES_RE.test(cluster.text)\n ? {}\n : { 'data-ligatures': 'decorative' })}\n aria-hidden=\"true\"\n >\n {cluster.text}\n </span>\n ));\n}\n\n/** What the live region says: the SDK's own sentences, then any authored feedback. */\ninterface Announcement {\n text: string;\n feedback: string | null;\n}\n\n/**\n * The announcement, with authored feedback in its own isolated span: its\n * direction is its own, so an English \"Well heard.\" in an Arabic interface\n * keeps its full stop at its end. Its language is not guessed — feedback may be\n * written in the interface's language or the dictation's.\n */\nfunction AnnouncementText({ announcement }: { announcement: Announcement | null }) {\n if (announcement === null) {\n return null;\n }\n return (\n <>\n {announcement.text}\n {announcement.feedback ? (\n <>\n {' '}\n <span dir=\"auto\">{announcement.feedback}</span>\n </>\n ) : null}\n </>\n );\n}\n\nexport function Dictation({\n data,\n onComplete,\n onSubmit,\n onChange,\n onInteraction,\n value,\n defaultValue,\n defaultSubmitted,\n renderMode = 'practice',\n outcome,\n mediaBudget,\n mediaStrings,\n strings,\n theme,\n locale,\n disabled,\n ai: aiProp,\n delivery,\n scoring,\n}: DictationProps) {\n const isExam = renderMode === 'exam';\n const isReview = renderMode === 'review';\n const s = useLkStrings(strings);\n const ai = useLearnerAi(aiProp);\n const policy = useDeliveryPolicy(delivery);\n const scoringPolicy = useItemScoringPolicy(scoring);\n\n // Whether the payload carries the transcript at all — the KEY, not the\n // `redacted` marker. `redact(data, { reveal: 'after-submit' })` stamps\n // `redacted: true` on a projection that still has its transcript, and that\n // projection is exactly what a review screen hands this component. A type\n // test only: no content is read here, and none is read in `exam` below.\n const hasKey = typeof (data as { transcript?: unknown }).transcript === 'string';\n\n const devError = useMemo(() => {\n if (!isDevelopment()) {\n return null;\n }\n if (data.redacted === true) {\n if (hasKey) {\n // An after-submit projection: the strict redacted schema has no\n // `transcript`, so `assertRedacted` would throw on the one payload\n // this component exists to render in review. Check the public subset.\n const shape = data as { type?: unknown; id?: unknown; title?: unknown };\n return shape.type === 'dictation' &&\n typeof shape.id === 'string' &&\n typeof shape.title === 'string'\n ? null\n : new Error(\n 'Dictation received a revealed projection (redacted: true with a transcript) that ' +\n 'is not a dictation: `type`, `id` and `title` are required.',\n );\n }\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('dictation', data);\n return result.success ? null : new ActivitySchemaError('dictation', result.errors);\n }, [data, hasKey]);\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 const isControlled = value !== undefined;\n const [internalText, setInternalText] = useState<string>(() => textOf(defaultValue));\n const [internalRevealed, setInternalRevealed] = useState<number>(() => hintsOf(defaultValue));\n const [result, setResult] = useState<ScoringResult | null>(null);\n const [summary, setSummary] = useState<Announcement | null>(null);\n const [solutionShown, setSolutionShown] = useState(false);\n const formRef = useRef<HTMLFormElement>(null);\n const feedbackRef = useRef<HTMLDivElement>(null);\n const focusAfterRef = useRef<'answer' | 'feedback' | null>(null);\n\n const text = isControlled ? textOf(value) : internalText;\n const revealed = isControlled ? hintsOf(value) : internalRevealed;\n\n const defaultValueRef = useRef(defaultValue);\n useEffect(() => {\n defaultValueRef.current = defaultValue;\n }, [defaultValue]);\n\n const defaultSubmittedRef = useRef(defaultSubmitted);\n useEffect(() => {\n defaultSubmittedRef.current = defaultSubmitted;\n }, [defaultSubmitted]);\n\n // Identity-guarded so the mount run is a no-op: without it this fires after\n // the first paint and undoes every seed it was just given.\n const lastDataRef = useRef(data);\n useEffect(() => {\n if (lastDataRef.current === data) {\n return;\n }\n lastDataRef.current = data;\n setInternalText(textOf(defaultValueRef.current));\n setInternalRevealed(hintsOf(defaultValueRef.current));\n setResult(null);\n setSummary(null);\n setSolutionShown(false);\n resetTries();\n reset(defaultSubmittedRef.current === true ? 'completed' : 'idle');\n }, [data, reset, resetTries]);\n\n // `text-changed` is debounced so an autosaving host is not told about every\n // keystroke; only the length travels, never the text.\n const textChangedRef = useRef<{ timer: ReturnType<typeof setTimeout>; length: number } | null>(\n null,\n );\n useEffect(\n () => () => {\n if (textChangedRef.current !== null) {\n clearTimeout(textChangedRef.current.timer);\n }\n },\n [],\n );\n\n const revealButtonRef = useRef<HTMLButtonElement>(null);\n const ids = useId();\n const hintId = `${ids}-hint`;\n const titleId = `${ids}-title`;\n const solutionId = `${ids}-solution`;\n // Per mount, not per activity id: two renderings of one item on a page (a\n // review list of several attempts, a preview beside a sequence) are\n // separate players, and neither should pause the other's recordings.\n const [recordingsGroup] = useState(() => {\n recordingsGroupCount += 1;\n return `lk-dc-recordings-${recordingsGroupCount}`;\n });\n\n const slowMedia = useMemo(\n () => slowMediaFor(data, s.dictationSlowRecording),\n [data, s.dictationSlowRecording],\n );\n\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>('textarea')?.focus();\n } else {\n feedbackRef.current?.focus();\n }\n });\n\n // All hooks are called before these throws, so hook order stays stable.\n if (devError) {\n throw devError;\n }\n // Practice grades locally, and a redacted projection has no answer key to\n // grade against — `score()` would throw RedactedScoringError from the submit\n // handler, where no error boundary can reach it and after the learner has\n // typed. Fail at render instead, in production too, as every built-in does.\n if (data.redacted === true && renderMode === 'practice') {\n throw new Error(\n `Dictation \"${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 answer-key-derived information may be shown. Never in `exam`; in\n * `practice` only once this component has scored; in `review` only once the\n * caller supplies a SCORED outcome.\n */\n const revealing = isReview ? outcome?.status === 'scored' : !isExam && submitted;\n // What the delivery policy lets that reveal show: the marks, the score and\n // the author's feedback — and, only where solutions show too, the \"Show\n // solution\" panel, since the transcript is the whole answer.\n const marking = revealing && policy.feedback;\n // Whether the right answer may show: not where the paper withholds it, and\n // not while the learner is offered another try, which is for finding it.\n // Without it the marks still say which words are right, wrong, missing or\n // extra — but never what a wrong or missing word should have been.\n const solutionsShown = policy.solutions && !tries.open;\n\n // The transcript, read only outside `exam`. `Renderable` says it is a\n // string; a plain redacted projection has none, so it is read defensively\n // and every consumer of it goes through `alignDictation`'s own guards.\n const transcript: string | undefined =\n isExam || !hasKey ? undefined : (data as { transcript: string }).transcript;\n\n // Hints exist only where the transcript does and nothing has been graded:\n // practice, before submit. A plain redacted projection has no words to give.\n const hintWords = useMemo(\n () => (transcript === undefined ? [] : hintWordsOf(transcript)),\n [transcript],\n );\n const hintsOffered =\n policy.hints &&\n data.hints?.mode === 'progressive-words' &&\n !isExam &&\n !isReview &&\n transcript !== undefined;\n const hintsEnabled = hintsOffered && !submitted;\n const hintTotal = hintWords.length;\n const hintsShown = Math.min(revealed, hintTotal);\n // What a response reports: the words actually shown, and none where no hint\n // can be — whatever count a seeded or controlled value carries.\n const reportedHints = hintsOffered ? hintsShown : 0;\n\n const fireInteraction = (\n type: 'text-changed' | 'hint-requested' | 'submitted',\n payload: Record<string, unknown>,\n ): void => {\n onInteraction?.({ type, activityId: data.id, timestamp: Date.now(), payload });\n };\n\n const flushTextChanged = (): void => {\n const pending = textChangedRef.current;\n if (pending === null) {\n return;\n }\n clearTimeout(pending.timer);\n textChangedRef.current = null;\n fireInteraction('text-changed', { length: pending.length });\n };\n\n const handleTextChange = (next: string): void => {\n if (inactive) {\n return;\n }\n if (state === 'idle') {\n start();\n }\n if (!isControlled) {\n setInternalText(next);\n }\n onChange?.(responseOf(next, reportedHints));\n if (onInteraction === undefined) {\n return;\n }\n if (textChangedRef.current !== null) {\n clearTimeout(textChangedRef.current.timer);\n }\n textChangedRef.current = {\n length: next.length,\n timer: setTimeout(() => {\n const pending = textChangedRef.current;\n textChangedRef.current = null;\n if (pending !== null) {\n fireInteraction('text-changed', { length: pending.length });\n }\n }, TEXT_CHANGED_DEBOUNCE_MS),\n };\n };\n\n const setRevealed = (next: number): void => {\n if (!isControlled) {\n setInternalRevealed(next);\n }\n onChange?.(responseOf(text, next));\n };\n\n const revealNextHint = (): void => {\n if (inactive || hintsShown >= hintTotal) {\n return;\n }\n if (state === 'idle') {\n start();\n }\n const next = hintsShown + 1;\n setRevealed(next);\n fireInteraction('hint-requested', { revealed: next, total: hintTotal });\n };\n\n const resetHints = (): void => {\n // Under a hint cost the words shown stay shown: hiding them would not\n // unsee them, and the count they are charged by would say they were not.\n if (inactive || hintsShown === 0 || scoringPolicy.hintPenalty > 0) {\n return;\n }\n // The reset button leaves the page once nothing is revealed; hand focus to\n // the reveal button first, or the keyboard user lands on the page body.\n revealButtonRef.current?.focus();\n setRevealed(0);\n };\n\n const handleSubmit = (event: React.FormEvent): void => {\n event.preventDefault();\n if (inactive) {\n return;\n }\n const response = responseOf(text, reportedHints);\n onSubmit?.(response);\n flushTextChanged();\n\n if (isExam) {\n // The client neither grades nor reveals: no score(), no read of the\n // transcript, no onComplete, and no xAPI statement — its\n // correctResponsesPattern IS the answer key.\n complete();\n setSummary({ text: s.answerSubmitted, feedback: null });\n fireInteraction('submitted', { length: text.length, hintsRevealed: reportedHints });\n return;\n }\n\n const answer = score('dictation', data as DictationData, 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: reportedHints,\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 : { ...answer, score: costed, passed: computePassThreshold(data as DictationData, costed) };\n const passed =\n counted.score === scoringResult.score\n ? scoringResult.passed\n : computePassThreshold(data as DictationData, 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: response.text,\n });\n setResult(answer);\n onComplete?.(\n stampTake(\n { score: counted.score, maxScore: counted.maxScore, passed, timeSpent, xapiStatement },\n mintTake(),\n ),\n );\n const correctWords = answer.details.filter(detailIsRight).length;\n setSolutionShown(false);\n setSummary({\n text: `${s.answerSubmitted} ${s.scoreAnnouncement(\n Math.round(counted.score * 100),\n passed,\n )} ${s.dictationWordsSummary(correctWords, answer.details.length)}`,\n feedback: answer.feedback,\n });\n fireInteraction('submitted', {\n length: text.length,\n hintsRevealed: reportedHints,\n score: scoringResult.score,\n });\n };\n\n /** \"Try again\": the text stays, to be corrected; the marks go. Hints shown stay shown. */\n const retry = (): void => {\n reset('idle');\n setSummary(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 // A review may hold the key but not the learner's text — an outcome recorded\n // without its response. Recomputing from an empty box would contradict the\n // stored score, so the stored details are the record then.\n const given = isControlled ? value : defaultValue;\n const hasResponse = given !== undefined && given.type === 'dictation';\n // The dictation's language, and the direction its tag names when it names\n // one. Read defensively: stale data may carry a locale that is not a string.\n // A tag written with underscores reads as its hyphenated form, the only one `lang` accepts.\n const contentLang =\n typeof data.locale === 'string' ? data.locale.replaceAll('_', '-') : undefined;\n const localeDir = localeDirectionOf(data.locale);\n\n // The same comparison the scorer made — computed only when it may be shown.\n // The character diff is UI-only and needs memory proportional to the product\n // of the two lengths, so it lives here and nowhere near `score()`.\n const alignment = useMemo<DictationAlignment | null>(\n () =>\n marking && transcript !== undefined && (!isReview || hasResponse)\n ? alignDictation(data, text)\n : null,\n [marking, transcript, isReview, hasResponse, data, text],\n );\n // The character diff draws the transcript's letters beside the learner's, so\n // it is the right answer: only where that may show.\n const sentenceDiff = useMemo<DictationCharOp[] | null>(\n () =>\n alignment !== null && alignment.attempt !== '' && solutionsShown\n ? diffDictationChars(alignment.reference, alignment.attempt)\n : null,\n [alignment, solutionsShown],\n );\n\n // Review with no transcript on the client, or no response to recompute from:\n // a server-recorded mark still renders its word list from the stored details.\n const detailWords = useMemo<DictationWordAlignment[] | null>(\n () =>\n isReview && alignment === null && outcome?.status === 'scored'\n ? wordsFromDetails(outcome.details)\n : null,\n [isReview, alignment, outcome],\n );\n\n const words: DictationWordAlignment[] | null = alignment?.words ?? detailWords;\n const marksShown = marking && words !== null;\n // The authored content's direction: from its language, or else from the first\n // letter of the transcript (or of the stored words, when only they are here).\n // Never `auto` on a container: its first strong character could be a hidden\n // interface sentence, or whatever the learner typed. `undefined` — no letter\n // anywhere — inherits the interface's.\n const contentDir =\n localeDir ??\n textDirectionOf(transcript ?? words?.map((word) => word.reference).join(' ') ?? '');\n // Nothing survived normalisation: from the alignment when there is one, and\n // from the stored details otherwise (every transcript word unanswered).\n const nothingTyped =\n alignment !== null\n ? alignment.attempt === ''\n : (words?.every((word) => word.attempt === '') ?? false);\n\n const passed = isReview\n ? outcome?.status === 'scored' || outcome?.status === 'graded'\n ? outcome.passed\n : undefined\n : result?.passed;\n\n const reviewSummary = useMemo<Announcement | null>(() => {\n if (!isReview || outcome === undefined) {\n return null;\n }\n if (outcome.status === 'scored') {\n return {\n text: s.scoreAnnouncement(Math.round(outcome.score * 100), outcome.passed),\n feedback: outcome.feedback,\n };\n }\n if (outcome.status === 'deferred') {\n return { text: s.notGradedYet, feedback: null };\n }\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 {\n text: s.scoreAnnouncement(percent, outcome.passed),\n feedback: outcome.feedback ?? null,\n };\n }\n return { text: s.noGradeAvailable, feedback: null };\n }, [isReview, outcome, s]);\n\n const sentenceFor = (word: DictationWordAlignment): string => {\n switch (word.status) {\n case 'correct':\n return s.dictationWordCorrect(word.attempt);\n case 'incorrect':\n return solutionsShown\n ? s.dictationWordWrong(word.attempt, word.reference)\n : s.dictationWordWrongUnnamed(word.attempt);\n case 'missing':\n return solutionsShown\n ? s.dictationWordMissing(word.reference)\n : s.dictationWordMissingUnnamed;\n default:\n return s.dictationWordExtra(word.attempt);\n }\n };\n\n const acceptedTranscripts: readonly string[] | undefined =\n transcript === undefined || !Array.isArray(data.acceptedTranscripts)\n ? undefined\n : data.acceptedTranscripts.filter((entry): entry is string => typeof entry === 'string');\n\n const extra =\n !isReview && policy.feedback && submitted && tries.counted !== null\n ? triesSummary(s, scoringPolicy, tries.counted, tries.open)\n : '';\n\n return (\n <form\n ref={formRef}\n className=\"lk-dc\"\n // A title with nothing to say would name the form with silence.\n aria-labelledby={VISIBLE_TEXT_RE.test(data.title) ? titleId : undefined}\n lang={locale}\n data-render-mode={renderMode}\n style={theme as CSSProperties | undefined}\n onSubmit={handleSubmit}\n >\n {/*\n The form's name: read from here, it keeps the content's language. A\n paragraph of its own, so without a language to go by its own first\n letter decides its direction.\n */}\n <p id={titleId} className=\"lk-dc-title\" dir={localeDir ?? 'auto'} lang={contentLang}>\n {data.title}\n </p>\n\n {data.media !== undefined || slowMedia !== undefined ? (\n <div className=\"lk-dc-recordings\">\n {/*\n Each recording is a named group (a fieldset with a legend, which is\n what `role=\"group\"` plus a label spells natively), so the two\n transports are told apart by name and not only by position.\n */}\n {data.media !== undefined ? (\n <fieldset className=\"lk-dc-recording\">\n <legend className=\"lk-dc-recording-label\">{s.dictationRecording}</legend>\n {/* The ONLY budgeted recording: the binding is whatever the pager or the caller passes. */}\n <ActivityMedia\n media={data.media}\n renderMode={renderMode}\n playbackGroup={recordingsGroup}\n {...(mediaBudget !== undefined ? { mediaBudget } : {})}\n {...(mediaStrings !== undefined ? { mediaStrings } : {})}\n {...(strings !== undefined ? { strings } : {})}\n {...(onInteraction !== undefined ? { onInteraction } : {})}\n {...(locale !== undefined ? { locale } : {})}\n {...(disabled !== undefined ? { disabled } : {})}\n />\n </fieldset>\n ) : null}\n {slowMedia !== undefined ? (\n <fieldset className=\"lk-dc-recording\" data-slow=\"true\">\n <legend className=\"lk-dc-recording-label\">{s.dictationSlowRecording}</legend>\n {/* No budget of its own, by contract — so no binding is passed and none is demanded. */}\n <ActivityMedia\n media={slowMedia}\n renderMode={renderMode}\n playbackGroup={recordingsGroup}\n {...(mediaStrings !== undefined ? { mediaStrings } : {})}\n {...(strings !== undefined ? { strings } : {})}\n {...(locale !== undefined ? { locale } : {})}\n {...(disabled !== undefined ? { disabled } : {})}\n />\n </fieldset>\n ) : null}\n </div>\n ) : null}\n\n {hintsEnabled ? (\n <div className=\"lk-dc-hints\">\n {/*\n aria-disabled, never disabled, once every word is shown: a natively\n disabled button drops keyboard focus to the page body.\n */}\n <button\n ref={revealButtonRef}\n type=\"button\"\n className=\"lk-dc-hint-btn\"\n disabled={inactive}\n aria-disabled={hintsShown >= hintTotal || undefined}\n onClick={revealNextHint}\n >\n {s.dictationRevealNextWord(hintsShown, hintTotal)}\n </button>\n <HintCost policy={scoringPolicy} strings={s} />\n {hintsShown > 0 && scoringPolicy.hintPenalty <= 0 ? (\n <button\n type=\"button\"\n className=\"lk-dc-hint-reset\"\n disabled={inactive}\n onClick={resetHints}\n >\n {s.dictationResetHints}\n </button>\n ) : null}\n {/* Rendered before it has content, so the live region exists when the first word lands. */}\n <p id={hintId} className=\"lk-dc-hint\" role=\"status\" dir={contentDir} lang={contentLang}>\n {hintsShown > 0\n ? `${hintWords.slice(0, hintsShown).join(' ')}${hintsShown < hintTotal ? ' …' : ''}`\n : ''}\n </p>\n </div>\n ) : null}\n\n <fieldset disabled={inactive}>\n <textarea\n className=\"lk-dc-input\"\n aria-label={s.dictationInputLabel}\n aria-describedby={hintsEnabled && hintsShown > 0 ? hintId : undefined}\n // No content lang here: the element's accessible name is interface\n // text, and a screen reader voices a name in its element's language.\n dir=\"auto\"\n rows={3}\n maxLength={DICTATION_MAX_TEXT_LENGTH}\n // Assessment integrity, not style: a corrected or completed word is\n // not the learner's spelling.\n autoComplete=\"off\"\n autoCapitalize=\"none\"\n autoCorrect=\"off\"\n spellCheck={false}\n value={text}\n disabled={inactive}\n aria-disabled={inactive || undefined}\n data-correct={\n marking && passed !== undefined && (!isReview || hasResponse)\n ? String(passed)\n : undefined\n }\n onChange={(event) => handleTextChange(event.target.value)}\n />\n {isReview ? null : (\n // Enabled on empty text: an omission is a recorded answer.\n <button type=\"submit\" disabled={inactive}>\n {isExam ? s.submitAnswers : s.checkAnswers}\n </button>\n )}\n </fieldset>\n\n {marksShown ? (\n <div className=\"lk-dc-result\">\n {nothingTyped ? (\n <p className=\"lk-dc-nothing\" role=\"note\">\n {s.dictationNothingTyped}\n </p>\n ) : (\n <>\n {/*\n The word list is the assistive-technology channel: one hidden\n sentence per word says what happened to it. The visible token,\n its glyph and its character marks are decoration.\n */}\n {/*\n The list's name and the hidden sentences are interface text, so\n they keep the interface language; only the typed tokens carry\n the dictation's. Its direction is the dictation's, never\n resolved from the list's own text: the first strong character\n there would be a hidden interface sentence, or whatever the\n learner typed. Each word's own text may resolve from itself.\n */}\n <ol className=\"lk-dc-words\" aria-label={s.dictationMarksLabel} dir={contentDir}>\n {words?.map((word, index) => {\n const sentence = sentenceFor(word);\n return (\n <li\n // biome-ignore lint/suspicious/noArrayIndexKey: alignment order is the identity; words repeat\n key={index}\n className=\"lk-dc-word\"\n data-state={word.status}\n title={sentence}\n >\n <span className=\"lk-visually-hidden\" style={VISUALLY_HIDDEN}>\n {sentence}\n </span>\n <span\n className=\"lk-dc-word-text\"\n aria-hidden=\"true\"\n dir={contentDir ?? 'auto'}\n lang={contentLang}\n // A wrong word drawn as its character marks: the marks\n // carry the decoration, so the wrong characters stand\n // out from the right ones by shape, not colour alone.\n {...(word.status === 'incorrect' && alignment !== null && solutionsShown\n ? { 'data-marks': 'characters' }\n : {})}\n >\n {word.status === 'incorrect' && alignment !== null && solutionsShown ? (\n <CharOps\n ops={diffDictationChars(word.reference, word.attempt)}\n contentDir={contentDir}\n />\n ) : word.status === 'missing' ? (\n // Where the answer may not show, a gap the size of\n // nothing in particular: the word is missing, not named.\n solutionsShown ? (\n word.reference\n ) : (\n '…'\n )\n ) : (\n word.attempt\n )}\n </span>\n </li>\n );\n })}\n </ol>\n {sentenceDiff !== null ? (\n <p className=\"lk-dc-diff\" aria-hidden=\"true\" dir={contentDir} lang={contentLang}>\n <CharOps ops={sentenceDiff} contentDir={contentDir} />\n </p>\n ) : null}\n <ul className=\"lk-dc-legend\">\n <li data-state=\"correct\">{s.dictationLegendCorrect}</li>\n <li data-state=\"incorrect\">{s.dictationLegendWrong}</li>\n <li data-state=\"missing\">{s.dictationLegendMissing}</li>\n {alignment !== null ? <li data-state=\"extra\">{s.dictationLegendExtra}</li> : null}\n </ul>\n <p className=\"lk-dc-diff-note\">{s.dictationDiffNote}</p>\n </>\n )}\n {/*\n Offered after an empty answer too: a learner who could make out\n nothing is the one who most needs to read the sentence, and a\n single typed letter would reveal it anyway.\n */}\n {transcript !== undefined && solutionsShown ? (\n <>\n <button\n type=\"button\"\n className=\"lk-dc-solution-toggle\"\n aria-expanded={solutionShown}\n aria-controls={solutionId}\n onClick={() => setSolutionShown((shown) => !shown)}\n >\n {solutionShown ? s.hideSolution : s.showSolution}\n </button>\n {solutionShown ? (\n <blockquote\n id={solutionId}\n className=\"lk-dc-solution\"\n aria-label={s.dictationSolutionLabel}\n dir={contentDir}\n >\n <p lang={contentLang}>{transcript}</p>\n {acceptedTranscripts !== undefined && acceptedTranscripts.length > 0 ? (\n <ul className=\"lk-dc-solution-alternatives\" lang={contentLang}>\n {acceptedTranscripts.map((alternative) => (\n <li key={alternative}>{alternative}</li>\n ))}\n </ul>\n ) : null}\n </blockquote>\n ) : null}\n </>\n ) : null}\n </div>\n ) : null}\n\n <FeedbackRegion\n id={`${data.id}-feedback`}\n {...(scoringPolicy.retries > 0 ? { ref: feedbackRef } : {})}\n >\n <AnnouncementText\n announcement={feedbackAnnouncement({\n review: isReview,\n feedback: policy.feedback,\n outcome,\n readBack: reviewSummary,\n submitted: summary !== null,\n result:\n summary === null || extra === ''\n ? summary\n : { ...summary, text: `${summary.text} ${extra}` },\n received: { text: s.answerSubmitted, feedback: null },\n })}\n />\n </FeedbackRegion>\n <TryActions\n tries={tries}\n solutions={policy.solutions && transcript !== undefined}\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={responseOf(text, reportedHints)}\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 { Dictation as DictationCore, type DictationProps } from './Dictation.js';\n\nexport type { DictationProps } from './Dictation.js';\n\n/**\n * Public Dictation activity: the core wrapped in `ActivityErrorBoundary` so a\n * render failure (including the dev schema-validation throw and the\n * redacted-in-`practice` guard) degrades to an accessible fallback instead of\n * crashing the host.\n */\nexport function Dictation(props: DictationProps) {\n const strings = useLkStrings(props.strings);\n return (\n <ActivityErrorBoundary activityTitle={props.data?.title} strings={strings}>\n <DictationCore {...props} />\n </ActivityErrorBoundary>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Dictation-BlxRp-3k.js","names":["Dictation","DictationCore"],"sources":["../src/components/Dictation/Dictation.tsx","../src/components/Dictation/index.tsx"],"sourcesContent":["'use client';\n\nimport {\n type ActivityMedia as ActivityMediaData,\n ActivitySchemaError,\n alignDictation,\n assertRedacted,\n computePassThreshold,\n DICTATION_MAX_TEXT_LENGTH,\n type DictationAlignment,\n type DictationCharOp,\n type DictationData,\n type DictationLearnerResponse,\n type DictationWordAlignment,\n diffDictationChars,\n type LearnerResponse,\n type MediaPlaybackPolicy,\n type ScoringDetail,\n type ScoringResult,\n score,\n validateActivity,\n xAPIBuilder,\n xapiDefinitionFor,\n} from '@intellectif/lk-core';\nimport { type CSSProperties, useEffect, useId, useMemo, useRef, useState } from 'react';\nimport { useLearnerAi } from '../../ai/LkAiProvider.js';\nimport { useActivityState } from '../../hooks/useActivityState.js';\nimport { characterDirectionOf, localeDirectionOf, textDirectionOf } from '../../i18n/direction.js';\nimport { useLkStrings } from '../../i18n/LkIntlProvider.js';\nimport { ANONYMOUS_ACTOR, detailIsRight, isDevelopment, objectIdFor } from '../_internal.js';\nimport { ActivityMedia } from '../shared/ActivityMedia.js';\nimport { AiExplanation } from '../shared/AiHelp.js';\nimport { feedbackAnnouncement, useDeliveryPolicy } from '../shared/delivery.js';\nimport { FeedbackRegion } from '../shared/FeedbackRegion.js';\nimport { mintTake, stampTake } from '../shared/sequence-slot.js';\nimport {\n HintCost,\n TryActions,\n triesSummary,\n useItemScoringPolicy,\n useTries,\n} from '../shared/tries.js';\nimport type { ActivityProps, Renderable } from '../types.js';\n\n// Nothing shuffles and nothing is seeded, so the shared contract is the whole\n// contract. Kept as an interface so a later prop lands here, not in a union.\nexport interface DictationProps extends ActivityProps<DictationData> {}\n\n/** How long the learner has to stop typing before one `text-changed` is emitted. */\nconst TEXT_CHANGED_DEBOUNCE_MS = 500;\n\n/**\n * Present for assistive technology, invisible on screen. Inline rather than\n * left to the optional skin: the per-word sentences are the marks' only\n * channel for a screen reader, and without this a page that does not load the\n * skin would show every one of them beside its word.\n */\nconst 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/**\n * Names playback groups. Per mount and never derived from `useId`: two\n * separately hydrated roots derive the same id for the same tree position, and\n * their recordings would pause each other. The name never reaches the DOM.\n */\nlet recordingsGroupCount = 0;\n\n/**\n * A token with nothing spelled in it, as the scorer reads it once composed:\n * only punctuation, control, format and default-ignorable characters, the acute\n * accent, grave accent (and its fullwidth form) and modifier apostrophe the\n * scorer folds into an apostrophe, and the Arabic tatweel and N'Ko lajanyalan\n * it removes.\n */\nconst UNSPELLED_RE = new RegExp(\n `^[\\\\p{P}\\\\p{Cc}\\\\p{Cf}\\\\p{Default_Ignorable_Code_Point}${String.fromCodePoint(0xb4, 0x60, 0xff40, 0x2bc, 0x640, 0x7fa)}]*$`,\n 'u',\n);\n/**\n * Spacing as the scorer reads it: whitespace and NEXT LINE — but not the\n * byte-order mark, which JavaScript counts as whitespace and the scorer\n * removes as a format character, joining the letters on either side.\n */\nconst HINT_SPACE_RE = new RegExp(\n `(?:(?!${String.fromCodePoint(0xfeff)})\\\\s|${String.fromCodePoint(0x85)})+`,\n 'u',\n);\n/** A title with something in it a screen reader can say. */\nconst VISIBLE_TEXT_RE = /[^\\p{White_Space}\\p{Cc}\\p{Cf}\\p{Default_Ignorable_Code_Point}]/u;\n\nconst BULLET = '•';\nconst LEFT_TO_RIGHT_MARK = String.fromCodePoint(0x200e);\nconst RIGHT_TO_LEFT_MARK = String.fromCodePoint(0x200f);\n/**\n * Code points a font draws onto the character before them rather than beside\n * it — combining marks (variation selectors among them), the joiners, emoji\n * skin tones, tag characters, the vowel and final jamo of an old Hangul\n * syllable, and the Thai and Lao SARA AM, whose ring the font sets on the\n * consonant before — so their own span can have no width to decorate.\n */\nconst EXTENDING_RE = new RegExp(\n `^[\\\\p{M}${String.fromCodePoint(0x200c, 0x200d, 0xe33, 0xeb3)}${String.fromCodePoint(0x1f3fb)}-${String.fromCodePoint(0x1f3ff)}${String.fromCodePoint(0xe0020)}-${String.fromCodePoint(0xe007f)}${String.fromCodePoint(0x1160)}-${String.fromCodePoint(0x11ff)}${String.fromCodePoint(0xd7b0)}-${String.fromCodePoint(0xd7ff)}]$`,\n 'u',\n);\n/**\n * Viramas that stack the letter after them into one glyph without asking —\n * Devanagari, Bengali, Gujarati, Oriya, Telugu, Kannada and Malayalam, the\n * Burmese stacker, the Khmer coeng, and the Tai Tham, Balinese, Javanese,\n * Chakma and Brahmi signs. Not the Tamil, Gurmukhi or Sinhala virama, which is\n * drawn visibly and leaves most letters after it on their own.\n */\nconst STACKING_VIRAMAS = new Set(\n [\n 0x94d, 0x9cd, 0xacd, 0xb4d, 0xc4d, 0xccd, 0xd4d, 0x1039, 0x17d2, 0x1a60, 0x1b44, 0xa9c0,\n 0x11046, 0x11133,\n ].map((point) => String.fromCodePoint(point)),\n);\n/**\n * The letters a visible virama still draws into the letter before it: the\n * Gurmukhi RA, YA and VA, which fonts set under the consonant, and the Tamil\n * conjuncts KSSA (க்ஷ) and SHRII (ஸ்ரீ), which fonts draw as one glyph —\n * each keyed by its letter, virama and the letter before the virama.\n */\nconst SUBJOINED_AFTER = new Map<string, (before: readonly string[]) => boolean>([\n ...[0xa30, 0xa2f, 0xa35].map(\n (point) =>\n [\n String.fromCodePoint(point),\n (before: readonly string[]) => before.at(-1) === String.fromCodePoint(0xa4d),\n ] as const,\n ),\n [\n String.fromCodePoint(0xbb7),\n (before: readonly string[]) =>\n before.at(-1) === String.fromCodePoint(0xbcd) &&\n before.at(-2) === String.fromCodePoint(0xb95),\n ],\n [\n String.fromCodePoint(0xbb0),\n (before: readonly string[]) =>\n before.at(-1) === String.fromCodePoint(0xbcd) &&\n before.at(-2) === String.fromCodePoint(0xbb8),\n ],\n]);\n/** Viramas after which a zero-width joiner asks for a conjunct: every stacking one, and the Sinhala. */\nconst JOINABLE_VIRAMAS = new Set([...STACKING_VIRAMAS, String.fromCodePoint(0xdca)]);\nconst ZERO_WIDTH_JOINER = String.fromCodePoint(0x200d);\n/**\n * Characters the font draws into the letter after them: MALAYALAM LETTER DOT\n * REPH, set on the consonant after it, and the MONGOLIAN VOWEL SEPARATOR, which\n * has no width of its own and changes the shape of the vowel after it.\n */\nconst PREPENDED = new Set([String.fromCodePoint(0xd4e), String.fromCodePoint(0x180e)]);\nconst ARABIC_LAM = String.fromCodePoint(0x644);\n/** The alefs a lam before them fuses with into one glyph, in every Arabic font. */\nconst LIGATING_ALEFS = new Set(\n [0x622, 0x623, 0x625, 0x627, 0x671, 0x672, 0x673, 0x675].map((point) =>\n String.fromCodePoint(point),\n ),\n);\nconst REGIONAL_INDICATOR_RE = /^[\\u{1f1e6}-\\u{1f1ff}]$/u;\nconst LETTER_RE = /^\\p{L}$/u;\n/** A letter of any script but Latin, Greek or Cyrillic, whose common ligatures are only decorative. */\nconst LETTER_WITH_REQUIRED_LIGATURES_RE =\n /(?![\\p{Script=Latin}\\p{Script=Greek}\\p{Script=Cyrillic}])\\p{L}/u;\nconst DIGIT_RE = /^\\p{Nd}$/u;\n/**\n * Signs laid out with the European number beside them (bidirectional class\n * ET): the currency signs, the degree, plus-minus and per-mille signs.\n */\nconst NUMBER_SIGN_RE = new RegExp(\n `^[\\\\p{Sc}${String.fromCodePoint(0xb0, 0xb1, 0x2030, 0x2031, 0x212e, 0x2213)}]$`,\n 'u',\n);\n/** Letters of the Arabic, Syriac and Thaana blocks and their presentation forms: before them, a European number is not one. */\nconst ARABIC_LETTER_BLOCKS: readonly (readonly [number, number])[] = [\n [0x0600, 0x07bf],\n [0x0860, 0x08ff],\n [0xfb50, 0xfdff],\n [0xfe70, 0xfeff],\n];\n\n/**\n * The text out of a `LearnerResponse`. A response of another activity type\n * reads as nothing typed rather than throwing — a controlled host that has not\n * yet swapped its state on an activity change would otherwise crash the attempt.\n */\nfunction textOf(response: LearnerResponse | undefined): string {\n return response !== undefined && response.type === 'dictation' ? response.text : '';\n}\n\n/** The hint count out of a `LearnerResponse`; absent or another type reads as none. */\nfunction hintsOf(response: LearnerResponse | undefined): number {\n if (response === undefined || response.type !== 'dictation') {\n return 0;\n }\n const revealed = response.hintsRevealed;\n return typeof revealed === 'number' && Number.isInteger(revealed) && revealed > 0 ? revealed : 0;\n}\n\n/** The response this component emits; `hintsRevealed` is written only when there is one. */\nfunction responseOf(text: string, hintsRevealed: number): DictationLearnerResponse {\n return { type: 'dictation', text, ...(hintsRevealed > 0 ? { hintsRevealed } : {}) };\n}\n\n/**\n * The slower recording as an `ActivityMedia`: it follows the recording's\n * playback policy minus `maxPlays` (`controls`, `seek`, `rate`, the native\n * hints), so a locked scrubber or a fixed speed binds both files — and it never\n * carries a budget of its own, so `<ActivityMedia>` never demands a binding\n * for it. `undefined` when there is no slow recording.\n */\nfunction slowMediaFor(\n data: Renderable<DictationData>,\n label: string,\n): ActivityMediaData | undefined {\n const slow = data.slowMedia;\n if (slow === undefined) {\n return undefined;\n }\n const policy: MediaPlaybackPolicy = data.media?.playback ?? {};\n const { maxPlays: _budget, ...inherited } = policy;\n const hasPolicy = Object.values(inherited).some((value) => value !== undefined);\n return {\n type: 'audio',\n url: slow.url,\n alt: slow.alt ?? label,\n ...(hasPolicy ? { playback: inherited } : {}),\n };\n}\n\n/**\n * The transcript's words as a learner would read them: raw, in order, split at\n * whitespace. A token with nothing in it that is spelled — a dash, an ellipsis\n * — is joined to the word before it (or to the first word, when it leads), so\n * every reveal shows a word. A script written without spaces is one token, so\n * one reveal shows the whole sentence.\n */\nfunction hintWordsOf(transcript: string): string[] {\n const words: string[] = [];\n let leading = '';\n for (const token of transcript.split(HINT_SPACE_RE)) {\n if (token === '') {\n continue;\n }\n const last = words.length - 1;\n // Composed first: the Greek varia and oxia decompose to the grave and acute\n // accents, which the scorer folds into an apostrophe and removes.\n if (!UNSPELLED_RE.test(token.normalize('NFC'))) {\n words.push(leading === '' ? token : `${leading} ${token}`);\n leading = '';\n } else if (last >= 0) {\n words[last] = `${words[last]} ${token}`;\n } else {\n leading = leading === '' ? token : `${leading} ${token}`;\n }\n }\n return words;\n}\n\n/**\n * Word marks rebuilt from stored scoring details — a server-recorded mark\n * shown after an exam, with no transcript on the client. Extras are not in the\n * details (the scorer writes one per transcript word), so none appear.\n */\nfunction wordsFromDetails(details: readonly ScoringDetail[]): DictationWordAlignment[] | null {\n if (details.length === 0 || !details.every((detail) => /^w\\d+$/.test(detail.itemId))) {\n return null;\n }\n return details.map((detail) => {\n const attempt = Array.isArray(detail.learnerResponse)\n ? detail.learnerResponse.join(' ')\n : detail.learnerResponse;\n const reference = Array.isArray(detail.correctResponse)\n ? detail.correctResponse.join(' ')\n : detail.correctResponse;\n const correct = detailIsRight(detail);\n const missing = detail.outcome === 'incorrect-omission' || (!correct && attempt === '');\n return {\n itemId: detail.itemId,\n reference,\n attempt,\n similarity: detail.score ?? (correct ? 1 : 0),\n status: correct ? 'correct' : missing ? 'missing' : 'incorrect',\n };\n });\n}\n\n/** A run of character operations drawn as one glyph cluster, and how it is marked. */\ninterface MarkedCluster {\n op: DictationCharOp['op'];\n text: string;\n}\n\n/**\n * Whether `shown`, the character an operation displays, is drawn into the\n * cluster whose characters so far are `before`: a mark, a joiner or a skin tone\n * onto its base, a letter stacked under a virama, an alef fused with the lam\n * before it, the second half of a flag.\n */\nfunction extendsCluster(before: readonly string[], shown: string): boolean {\n const previous = before.at(-1) as string;\n if (EXTENDING_RE.test(shown)) {\n return true;\n }\n if (LETTER_RE.test(shown)) {\n if (\n STACKING_VIRAMAS.has(previous) ||\n PREPENDED.has(previous) ||\n SUBJOINED_AFTER.get(shown)?.(before) === true\n ) {\n return true;\n }\n if (previous === ZERO_WIDTH_JOINER && JOINABLE_VIRAMAS.has(before.at(-2) as string)) {\n return true;\n }\n }\n if (previous === ARABIC_LAM && LIGATING_ALEFS.has(shown)) {\n return true;\n }\n return (\n REGIONAL_INDICATOR_RE.test(shown) &&\n before.length % 2 === 1 &&\n before.every((character) => REGIONAL_INDICATOR_RE.test(character))\n );\n}\n\n/** The character an operation stands for: what should have been typed, or what was typed in excess. */\nfunction characterOf(op: DictationCharOp): string {\n return op.reference === '' ? op.attempt : op.reference;\n}\n\nfunction isArabicLetter(character: string | undefined): boolean {\n const point = character?.codePointAt(0);\n return (\n point !== undefined &&\n LETTER_RE.test(character as string) &&\n ARABIC_LETTER_BLOCKS.some(([first, last]) => point >= first && point <= last)\n );\n}\n\n/**\n * The directions missing characters' bullets are laid out in, by operation\n * index, worked out in two passes over the operations rather than a search\n * per bullet: a word of 2,000 emoji missing its letters took seconds to draw.\n *\n * A bullet takes the character's own direction when it has one — a digit runs\n * left to right, unless it is a digit of a right-to-left script. A currency,\n * degree or per-mille sign beside a digit is laid out with that number, left to\n * right, unless an Arabic letter comes before it, after which the number is not\n * a European one. Otherwise, for a space or a punctuation mark, it takes the\n * direction of the characters on both sides when they agree, and the content's\n * when they do not. A bare bullet is a neutral, which the bidirectional\n * algorithm would otherwise attach to the wrong side of a number or a word of\n * the other direction.\n */\nexport function bulletDirections(\n ops: readonly DictationCharOp[],\n contentDir: 'ltr' | 'rtl' | undefined,\n): Map<number, 'ltr' | 'rtl' | undefined> {\n const own = ops.map((op) => characterDirectionOf(characterOf(op)));\n const before: ('ltr' | 'rtl' | undefined)[] = [];\n const arabicBefore: boolean[] = [];\n let lastDirection: 'ltr' | 'rtl' | undefined;\n let lastLetterArabic = false;\n ops.forEach((op, index) => {\n before[index] = lastDirection;\n arabicBefore[index] = lastLetterArabic;\n lastDirection = own[index] ?? lastDirection;\n if (LETTER_RE.test(characterOf(op))) {\n lastLetterArabic = isArabicLetter(characterOf(op));\n }\n });\n const after: ('ltr' | 'rtl' | undefined)[] = [];\n let nextDirection: 'ltr' | 'rtl' | undefined;\n for (let index = ops.length - 1; index >= 0; index -= 1) {\n after[index] = nextDirection;\n nextDirection = own[index] ?? nextDirection;\n }\n const directions = new Map<number, 'ltr' | 'rtl' | undefined>();\n ops.forEach((op, index) => {\n if (op.op !== 'missing') {\n return;\n }\n const neighbourIsDigit = [ops[index - 1], ops[index + 1]].some(\n (neighbour) => neighbour !== undefined && DIGIT_RE.test(characterOf(neighbour)),\n );\n directions.set(\n index,\n own[index] ??\n (NUMBER_SIGN_RE.test(characterOf(op)) && neighbourIsDigit && !arabicBefore[index]\n ? 'ltr'\n : before[index] !== undefined && before[index] === after[index]\n ? before[index]\n : (contentDir ?? before[index] ?? after[index])),\n );\n });\n return directions;\n}\n\n/**\n * How a cluster is marked. One led by a missing character's bullet is missing,\n * whatever marks sit on the bullet: the character they belong to is not there.\n * Otherwise a cluster is marked as its wrong operations are when they agree —\n * a correct letter with an extra mark is `extra` — and as a substitution when\n * they do not.\n */\nfunction clusterOpOf(ops: readonly DictationCharOp[]): DictationCharOp['op'] {\n if ((ops[0] as DictationCharOp).op === 'missing') {\n return 'missing';\n }\n const wrong = new Set(ops.map((op) => op.op).filter((kind) => kind !== 'equal'));\n if (wrong.size === 0) {\n return 'equal';\n }\n return wrong.size === 1 ? ([...wrong][0] as DictationCharOp['op']) : 'substitute';\n}\n\n/**\n * Character operations grouped into what is drawn as one cluster, so a wrong\n * combining mark, joiner or stacked letter marks the glyph it is drawn into —\n * alone, its span has no width, and its underline or strike would not be\n * painted at all. A missing character's bullet carries only the marks drawn\n * onto it, never a letter that is drawn apart. A missing character's bullet is\n * wrapped in the direction mark of the character it stands for. Consecutive\n * correct clusters are one run: an engine that shapes each element on its own\n * would otherwise draw a correctly typed conjunct or a joined Mongolian word\n * broken apart.\n */\nfunction clustersOf(\n ops: readonly DictationCharOp[],\n contentDir: 'ltr' | 'rtl' | undefined,\n): MarkedCluster[] {\n const bullets = bulletDirections(ops, contentDir);\n const groups: { ops: DictationCharOp[]; shown: string[]; bullet?: 'ltr' | 'rtl' }[] = [];\n ops.forEach((op, index) => {\n const shown = op.op === 'missing' ? BULLET : op.attempt;\n const current = groups.at(-1);\n if (\n current !== undefined &&\n op.op !== 'missing' &&\n ((current.ops[0] as DictationCharOp).op === 'missing'\n ? EXTENDING_RE.test(shown)\n : extendsCluster(current.shown, shown))\n ) {\n current.ops.push(op);\n current.shown.push(shown);\n return;\n }\n const direction = bullets.get(index);\n groups.push({\n ops: [op],\n shown: [shown],\n ...(direction !== undefined ? { bullet: direction } : {}),\n });\n });\n const clusters: MarkedCluster[] = [];\n for (const group of groups) {\n const mark =\n group.bullet === undefined\n ? ''\n : group.bullet === 'rtl'\n ? RIGHT_TO_LEFT_MARK\n : LEFT_TO_RIGHT_MARK;\n const cluster = { op: clusterOpOf(group.ops), text: `${mark}${group.shown.join('')}${mark}` };\n const previous = clusters.at(-1);\n if (previous !== undefined && previous.op === 'equal' && cluster.op === 'equal') {\n previous.text += cluster.text;\n } else {\n clusters.push(cluster);\n }\n }\n return clusters;\n}\n\n/**\n * Character-level edit operations as decoration. Hidden from assistive\n * technology as a whole: a screen reader must never verbalise surrogate or\n * combining fragments one by one, and the word list already carries the\n * meaning. Sighted colour-blind and forced-colors users still get one\n * decoration per state, which the skin draws and the legend explains.\n */\nfunction CharOps({\n ops,\n contentDir,\n}: {\n ops: readonly DictationCharOp[];\n contentDir: 'ltr' | 'rtl' | undefined;\n}) {\n return clustersOf(ops, contentDir).map((cluster, index) => (\n <span\n // biome-ignore lint/suspicious/noArrayIndexKey: clusters are positional and recomputed whole\n key={index}\n className=\"lk-dc-op\"\n data-op={cluster.op}\n // A run with no letter whose script needs its common ligatures: the skin\n // turns them off there, so a font's \"fi\" cannot draw a wrong \"i\" into one glyph.\n {...(LETTER_WITH_REQUIRED_LIGATURES_RE.test(cluster.text)\n ? {}\n : { 'data-ligatures': 'decorative' })}\n aria-hidden=\"true\"\n >\n {cluster.text}\n </span>\n ));\n}\n\n/** What the live region says: the SDK's own sentences, then any authored feedback. */\ninterface Announcement {\n text: string;\n feedback: string | null;\n}\n\n/**\n * The announcement, with authored feedback in its own isolated span: its\n * direction is its own, so an English \"Well heard.\" in an Arabic interface\n * keeps its full stop at its end. Its language is not guessed — feedback may be\n * written in the interface's language or the dictation's.\n */\nfunction AnnouncementText({ announcement }: { announcement: Announcement | null }) {\n if (announcement === null) {\n return null;\n }\n return (\n <>\n {announcement.text}\n {announcement.feedback ? (\n <>\n {' '}\n <span dir=\"auto\">{announcement.feedback}</span>\n </>\n ) : null}\n </>\n );\n}\n\nexport function Dictation({\n data,\n onComplete,\n onSubmit,\n onChange,\n onInteraction,\n value,\n defaultValue,\n defaultSubmitted,\n renderMode = 'practice',\n outcome,\n mediaBudget,\n mediaStrings,\n strings,\n theme,\n locale,\n disabled,\n ai: aiProp,\n delivery,\n scoring,\n}: DictationProps) {\n const isExam = renderMode === 'exam';\n const isReview = renderMode === 'review';\n const s = useLkStrings(strings);\n const ai = useLearnerAi(aiProp);\n const policy = useDeliveryPolicy(delivery);\n const scoringPolicy = useItemScoringPolicy(scoring);\n\n // Whether the payload carries the transcript at all — the KEY, not the\n // `redacted` marker. `redact(data, { reveal: 'after-submit' })` stamps\n // `redacted: true` on a projection that still has its transcript, and that\n // projection is exactly what a review screen hands this component. A type\n // test only: no content is read here, and none is read in `exam` below.\n const hasKey = typeof (data as { transcript?: unknown }).transcript === 'string';\n\n const devError = useMemo(() => {\n if (!isDevelopment()) {\n return null;\n }\n if (data.redacted === true) {\n if (hasKey) {\n // An after-submit projection: the strict redacted schema has no\n // `transcript`, so `assertRedacted` would throw on the one payload\n // this component exists to render in review. Check the public subset.\n const shape = data as { type?: unknown; id?: unknown; title?: unknown };\n return shape.type === 'dictation' &&\n typeof shape.id === 'string' &&\n typeof shape.title === 'string'\n ? null\n : new Error(\n 'Dictation received a revealed projection (redacted: true with a transcript) that ' +\n 'is not a dictation: `type`, `id` and `title` are required.',\n );\n }\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('dictation', data);\n return result.success ? null : new ActivitySchemaError('dictation', result.errors);\n }, [data, hasKey]);\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 const isControlled = value !== undefined;\n const [internalText, setInternalText] = useState<string>(() => textOf(defaultValue));\n const [internalRevealed, setInternalRevealed] = useState<number>(() => hintsOf(defaultValue));\n const [result, setResult] = useState<ScoringResult | null>(null);\n const [summary, setSummary] = useState<Announcement | null>(null);\n const [solutionShown, setSolutionShown] = useState(false);\n const formRef = useRef<HTMLFormElement>(null);\n const feedbackRef = useRef<HTMLDivElement>(null);\n const focusAfterRef = useRef<'answer' | 'feedback' | null>(null);\n\n const text = isControlled ? textOf(value) : internalText;\n const revealed = isControlled ? hintsOf(value) : internalRevealed;\n\n const defaultValueRef = useRef(defaultValue);\n useEffect(() => {\n defaultValueRef.current = defaultValue;\n }, [defaultValue]);\n\n const defaultSubmittedRef = useRef(defaultSubmitted);\n useEffect(() => {\n defaultSubmittedRef.current = defaultSubmitted;\n }, [defaultSubmitted]);\n\n // Identity-guarded so the mount run is a no-op: without it this fires after\n // the first paint and undoes every seed it was just given.\n const lastDataRef = useRef(data);\n useEffect(() => {\n if (lastDataRef.current === data) {\n return;\n }\n lastDataRef.current = data;\n setInternalText(textOf(defaultValueRef.current));\n setInternalRevealed(hintsOf(defaultValueRef.current));\n setResult(null);\n setSummary(null);\n setSolutionShown(false);\n resetTries();\n reset(defaultSubmittedRef.current === true ? 'completed' : 'idle');\n }, [data, reset, resetTries]);\n\n // `text-changed` is debounced so an autosaving host is not told about every\n // keystroke; only the length travels, never the text.\n const textChangedRef = useRef<{ timer: ReturnType<typeof setTimeout>; length: number } | null>(\n null,\n );\n useEffect(\n () => () => {\n if (textChangedRef.current !== null) {\n clearTimeout(textChangedRef.current.timer);\n }\n },\n [],\n );\n\n const revealButtonRef = useRef<HTMLButtonElement>(null);\n const ids = useId();\n const hintId = `${ids}-hint`;\n const titleId = `${ids}-title`;\n const solutionId = `${ids}-solution`;\n // Per mount, not per activity id: two renderings of one item on a page (a\n // review list of several attempts, a preview beside a sequence) are\n // separate players, and neither should pause the other's recordings.\n const [recordingsGroup] = useState(() => {\n recordingsGroupCount += 1;\n return `lk-dc-recordings-${recordingsGroupCount}`;\n });\n\n const slowMedia = useMemo(\n () => slowMediaFor(data, s.dictationSlowRecording),\n [data, s.dictationSlowRecording],\n );\n\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>('textarea')?.focus();\n } else {\n feedbackRef.current?.focus();\n }\n });\n\n // All hooks are called before these throws, so hook order stays stable.\n if (devError) {\n throw devError;\n }\n // Practice grades locally, and a redacted projection has no answer key to\n // grade against — `score()` would throw RedactedScoringError from the submit\n // handler, where no error boundary can reach it and after the learner has\n // typed. Fail at render instead, in production too, as every built-in does.\n if (data.redacted === true && renderMode === 'practice') {\n throw new Error(\n `Dictation \"${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 answer-key-derived information may be shown. Never in `exam`; in\n * `practice` only once this component has scored; in `review` only once the\n * caller supplies a SCORED outcome.\n */\n const revealing = isReview ? outcome?.status === 'scored' : !isExam && submitted;\n // What the delivery policy lets that reveal show: the marks, the score and\n // the author's feedback — and, only where solutions show too, the \"Show\n // solution\" panel, since the transcript is the whole answer.\n const marking = revealing && policy.feedback;\n // Whether the right answer may show: not where the paper withholds it, and\n // not while the learner is offered another try, which is for finding it.\n // Without it the marks still say which words are right, wrong, missing or\n // extra — but never what a wrong or missing word should have been.\n const solutionsShown = policy.solutions && !tries.open;\n\n // The transcript, read only outside `exam`. `Renderable` says it is a\n // string; a plain redacted projection has none, so it is read defensively\n // and every consumer of it goes through `alignDictation`'s own guards.\n const transcript: string | undefined =\n isExam || !hasKey ? undefined : (data as { transcript: string }).transcript;\n\n // Hints exist only where the transcript does and nothing has been graded:\n // practice, before submit. A plain redacted projection has no words to give.\n const hintWords = useMemo(\n () => (transcript === undefined ? [] : hintWordsOf(transcript)),\n [transcript],\n );\n const hintsOffered =\n policy.hints &&\n data.hints?.mode === 'progressive-words' &&\n !isExam &&\n !isReview &&\n transcript !== undefined;\n const hintsEnabled = hintsOffered && !submitted;\n const hintTotal = hintWords.length;\n const hintsShown = Math.min(revealed, hintTotal);\n // What a response reports: the words actually shown, and none where no hint\n // can be — whatever count a seeded or controlled value carries.\n const reportedHints = hintsOffered ? hintsShown : 0;\n\n const fireInteraction = (\n type: 'text-changed' | 'hint-requested' | 'submitted',\n payload: Record<string, unknown>,\n ): void => {\n onInteraction?.({ type, activityId: data.id, timestamp: Date.now(), payload });\n };\n\n const flushTextChanged = (): void => {\n const pending = textChangedRef.current;\n if (pending === null) {\n return;\n }\n clearTimeout(pending.timer);\n textChangedRef.current = null;\n fireInteraction('text-changed', { length: pending.length });\n };\n\n const handleTextChange = (next: string): void => {\n if (inactive) {\n return;\n }\n if (state === 'idle') {\n start();\n }\n if (!isControlled) {\n setInternalText(next);\n }\n onChange?.(responseOf(next, reportedHints));\n if (onInteraction === undefined) {\n return;\n }\n if (textChangedRef.current !== null) {\n clearTimeout(textChangedRef.current.timer);\n }\n textChangedRef.current = {\n length: next.length,\n timer: setTimeout(() => {\n const pending = textChangedRef.current;\n textChangedRef.current = null;\n if (pending !== null) {\n fireInteraction('text-changed', { length: pending.length });\n }\n }, TEXT_CHANGED_DEBOUNCE_MS),\n };\n };\n\n const setRevealed = (next: number): void => {\n if (!isControlled) {\n setInternalRevealed(next);\n }\n onChange?.(responseOf(text, next));\n };\n\n const revealNextHint = (): void => {\n if (inactive || hintsShown >= hintTotal) {\n return;\n }\n if (state === 'idle') {\n start();\n }\n const next = hintsShown + 1;\n setRevealed(next);\n fireInteraction('hint-requested', { revealed: next, total: hintTotal });\n };\n\n const resetHints = (): void => {\n // Under a hint cost the words shown stay shown: hiding them would not\n // unsee them, and the count they are charged by would say they were not.\n if (inactive || hintsShown === 0 || scoringPolicy.hintPenalty > 0) {\n return;\n }\n // The reset button leaves the page once nothing is revealed; hand focus to\n // the reveal button first, or the keyboard user lands on the page body.\n revealButtonRef.current?.focus();\n setRevealed(0);\n };\n\n const handleSubmit = (event: React.FormEvent): void => {\n event.preventDefault();\n if (inactive) {\n return;\n }\n const response = responseOf(text, reportedHints);\n onSubmit?.(response);\n flushTextChanged();\n\n if (isExam) {\n // The client neither grades nor reveals: no score(), no read of the\n // transcript, no onComplete, and no xAPI statement — its\n // correctResponsesPattern IS the answer key.\n complete();\n setSummary({ text: s.answerSubmitted, feedback: null });\n fireInteraction('submitted', { length: text.length, hintsRevealed: reportedHints });\n return;\n }\n\n const answer = score('dictation', data as DictationData, 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: reportedHints,\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 : { ...answer, score: costed, passed: computePassThreshold(data as DictationData, costed) };\n const passed =\n counted.score === scoringResult.score\n ? scoringResult.passed\n : computePassThreshold(data as DictationData, 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: response.text,\n });\n setResult(answer);\n onComplete?.(\n stampTake(\n { score: counted.score, maxScore: counted.maxScore, passed, timeSpent, xapiStatement },\n mintTake(),\n ),\n );\n const correctWords = answer.details.filter(detailIsRight).length;\n setSolutionShown(false);\n setSummary({\n text: `${s.answerSubmitted} ${s.scoreAnnouncement(\n Math.round(counted.score * 100),\n passed,\n )} ${s.dictationWordsSummary(correctWords, answer.details.length)}`,\n feedback: answer.feedback,\n });\n fireInteraction('submitted', {\n length: text.length,\n hintsRevealed: reportedHints,\n score: scoringResult.score,\n });\n };\n\n /** \"Try again\": the text stays, to be corrected; the marks go. Hints shown stay shown. */\n const retry = (): void => {\n reset('idle');\n setSummary(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 // A review may hold the key but not the learner's text — an outcome recorded\n // without its response. Recomputing from an empty box would contradict the\n // stored score, so the stored details are the record then.\n const given = isControlled ? value : defaultValue;\n const hasResponse = given !== undefined && given.type === 'dictation';\n // The dictation's language, and the direction its tag names when it names\n // one. Read defensively: stale data may carry a locale that is not a string.\n // A tag written with underscores reads as its hyphenated form, the only one `lang` accepts.\n const contentLang =\n typeof data.locale === 'string' ? data.locale.replaceAll('_', '-') : undefined;\n const localeDir = localeDirectionOf(data.locale);\n\n // The same comparison the scorer made — computed only when it may be shown.\n // The character diff is UI-only and needs memory proportional to the product\n // of the two lengths, so it lives here and nowhere near `score()`.\n const alignment = useMemo<DictationAlignment | null>(\n () =>\n marking && transcript !== undefined && (!isReview || hasResponse)\n ? alignDictation(data, text)\n : null,\n [marking, transcript, isReview, hasResponse, data, text],\n );\n // The character diff draws the transcript's letters beside the learner's, so\n // it is the right answer: only where that may show.\n const sentenceDiff = useMemo<DictationCharOp[] | null>(\n () =>\n alignment !== null && alignment.attempt !== '' && solutionsShown\n ? diffDictationChars(alignment.reference, alignment.attempt)\n : null,\n [alignment, solutionsShown],\n );\n\n // Review with no transcript on the client, or no response to recompute from:\n // a server-recorded mark still renders its word list from the stored details.\n const detailWords = useMemo<DictationWordAlignment[] | null>(\n () =>\n isReview && alignment === null && outcome?.status === 'scored'\n ? wordsFromDetails(outcome.details)\n : null,\n [isReview, alignment, outcome],\n );\n\n const words: DictationWordAlignment[] | null = alignment?.words ?? detailWords;\n const marksShown = marking && words !== null;\n // The authored content's direction: from its language, or else from the first\n // letter of the transcript (or of the stored words, when only they are here).\n // Never `auto` on a container: its first strong character could be a hidden\n // interface sentence, or whatever the learner typed. `undefined` — no letter\n // anywhere — inherits the interface's.\n const contentDir =\n localeDir ??\n textDirectionOf(transcript ?? words?.map((word) => word.reference).join(' ') ?? '');\n // Nothing survived normalisation: from the alignment when there is one, and\n // from the stored details otherwise (every transcript word unanswered).\n const nothingTyped =\n alignment !== null\n ? alignment.attempt === ''\n : (words?.every((word) => word.attempt === '') ?? false);\n\n const passed = isReview\n ? outcome?.status === 'scored' || outcome?.status === 'graded'\n ? outcome.passed\n : undefined\n : result?.passed;\n\n const reviewSummary = useMemo<Announcement | null>(() => {\n if (!isReview || outcome === undefined) {\n return null;\n }\n if (outcome.status === 'scored') {\n return {\n text: s.scoreAnnouncement(Math.round(outcome.score * 100), outcome.passed),\n feedback: outcome.feedback,\n };\n }\n if (outcome.status === 'deferred') {\n return { text: s.notGradedYet, feedback: null };\n }\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 {\n text: s.scoreAnnouncement(percent, outcome.passed),\n feedback: outcome.feedback ?? null,\n };\n }\n return { text: s.noGradeAvailable, feedback: null };\n }, [isReview, outcome, s]);\n\n const sentenceFor = (word: DictationWordAlignment): string => {\n switch (word.status) {\n case 'correct':\n return s.dictationWordCorrect(word.attempt);\n case 'incorrect':\n return solutionsShown\n ? s.dictationWordWrong(word.attempt, word.reference)\n : s.dictationWordWrongUnnamed(word.attempt);\n case 'missing':\n return solutionsShown\n ? s.dictationWordMissing(word.reference)\n : s.dictationWordMissingUnnamed;\n default:\n return s.dictationWordExtra(word.attempt);\n }\n };\n\n const acceptedTranscripts: readonly string[] | undefined =\n transcript === undefined || !Array.isArray(data.acceptedTranscripts)\n ? undefined\n : data.acceptedTranscripts.filter((entry): entry is string => typeof entry === 'string');\n\n const extra =\n !isReview && policy.feedback && submitted && tries.counted !== null\n ? triesSummary(s, scoringPolicy, tries.counted, tries.open)\n : '';\n\n return (\n <form\n ref={formRef}\n className=\"lk-dc\"\n // A title with nothing to say would name the form with silence.\n aria-labelledby={VISIBLE_TEXT_RE.test(data.title) ? titleId : undefined}\n lang={locale}\n data-render-mode={renderMode}\n style={theme as CSSProperties | undefined}\n onSubmit={handleSubmit}\n >\n {/*\n The form's name: read from here, it keeps the content's language. A\n paragraph of its own, so without a language to go by its own first\n letter decides its direction.\n */}\n <p id={titleId} className=\"lk-dc-title\" dir={localeDir ?? 'auto'} lang={contentLang}>\n {data.title}\n </p>\n\n {data.media !== undefined || slowMedia !== undefined ? (\n <div className=\"lk-dc-recordings\">\n {/*\n Each recording is a named group (a fieldset with a legend, which is\n what `role=\"group\"` plus a label spells natively), so the two\n transports are told apart by name and not only by position.\n */}\n {data.media !== undefined ? (\n <fieldset className=\"lk-dc-recording\">\n <legend className=\"lk-dc-recording-label\">{s.dictationRecording}</legend>\n {/* The ONLY budgeted recording: the binding is whatever the pager or the caller passes. */}\n <ActivityMedia\n media={data.media}\n renderMode={renderMode}\n playbackGroup={recordingsGroup}\n {...(mediaBudget !== undefined ? { mediaBudget } : {})}\n {...(mediaStrings !== undefined ? { mediaStrings } : {})}\n {...(strings !== undefined ? { strings } : {})}\n {...(onInteraction !== undefined ? { onInteraction } : {})}\n {...(locale !== undefined ? { locale } : {})}\n {...(disabled !== undefined ? { disabled } : {})}\n />\n </fieldset>\n ) : null}\n {slowMedia !== undefined ? (\n <fieldset className=\"lk-dc-recording\" data-slow=\"true\">\n <legend className=\"lk-dc-recording-label\">{s.dictationSlowRecording}</legend>\n {/* No budget of its own, by contract — so no binding is passed and none is demanded. */}\n <ActivityMedia\n media={slowMedia}\n renderMode={renderMode}\n playbackGroup={recordingsGroup}\n {...(mediaStrings !== undefined ? { mediaStrings } : {})}\n {...(strings !== undefined ? { strings } : {})}\n {...(locale !== undefined ? { locale } : {})}\n {...(disabled !== undefined ? { disabled } : {})}\n />\n </fieldset>\n ) : null}\n </div>\n ) : null}\n\n {hintsEnabled ? (\n <div className=\"lk-dc-hints\">\n {/*\n aria-disabled, never disabled, once every word is shown: a natively\n disabled button drops keyboard focus to the page body.\n */}\n <button\n ref={revealButtonRef}\n type=\"button\"\n className=\"lk-dc-hint-btn\"\n disabled={inactive}\n aria-disabled={hintsShown >= hintTotal || undefined}\n onClick={revealNextHint}\n >\n {s.dictationRevealNextWord(hintsShown, hintTotal)}\n </button>\n <HintCost policy={scoringPolicy} strings={s} />\n {hintsShown > 0 && scoringPolicy.hintPenalty <= 0 ? (\n <button\n type=\"button\"\n className=\"lk-dc-hint-reset\"\n disabled={inactive}\n onClick={resetHints}\n >\n {s.dictationResetHints}\n </button>\n ) : null}\n {/* Rendered before it has content, so the live region exists when the first word lands. */}\n <p id={hintId} className=\"lk-dc-hint\" role=\"status\" dir={contentDir} lang={contentLang}>\n {hintsShown > 0\n ? `${hintWords.slice(0, hintsShown).join(' ')}${hintsShown < hintTotal ? ' …' : ''}`\n : ''}\n </p>\n </div>\n ) : null}\n\n <fieldset disabled={inactive}>\n <textarea\n className=\"lk-dc-input\"\n aria-label={s.dictationInputLabel}\n aria-describedby={hintsEnabled && hintsShown > 0 ? hintId : undefined}\n // No content lang here: the element's accessible name is interface\n // text, and a screen reader voices a name in its element's language.\n dir=\"auto\"\n rows={3}\n maxLength={DICTATION_MAX_TEXT_LENGTH}\n // Assessment integrity, not style: a corrected or completed word is\n // not the learner's spelling.\n autoComplete=\"off\"\n autoCapitalize=\"none\"\n autoCorrect=\"off\"\n spellCheck={false}\n value={text}\n disabled={inactive}\n aria-disabled={inactive || undefined}\n data-correct={\n marking && passed !== undefined && (!isReview || hasResponse)\n ? String(passed)\n : undefined\n }\n onChange={(event) => handleTextChange(event.target.value)}\n />\n {isReview ? null : (\n // Enabled on empty text: an omission is a recorded answer.\n <button type=\"submit\" disabled={inactive}>\n {isExam ? s.submitAnswers : s.checkAnswers}\n </button>\n )}\n </fieldset>\n\n {marksShown ? (\n <div className=\"lk-dc-result\">\n {nothingTyped ? (\n <p className=\"lk-dc-nothing\" role=\"note\">\n {s.dictationNothingTyped}\n </p>\n ) : (\n <>\n {/*\n The word list is the assistive-technology channel: one hidden\n sentence per word says what happened to it. The visible token,\n its glyph and its character marks are decoration.\n */}\n {/*\n The list's name and the hidden sentences are interface text, so\n they keep the interface language; only the typed tokens carry\n the dictation's. Its direction is the dictation's, never\n resolved from the list's own text: the first strong character\n there would be a hidden interface sentence, or whatever the\n learner typed. Each word's own text may resolve from itself.\n */}\n <ol className=\"lk-dc-words\" aria-label={s.dictationMarksLabel} dir={contentDir}>\n {words?.map((word, index) => {\n const sentence = sentenceFor(word);\n return (\n <li\n // biome-ignore lint/suspicious/noArrayIndexKey: alignment order is the identity; words repeat\n key={index}\n className=\"lk-dc-word\"\n data-state={word.status}\n title={sentence}\n >\n <span className=\"lk-visually-hidden\" style={VISUALLY_HIDDEN}>\n {sentence}\n </span>\n <span\n className=\"lk-dc-word-text\"\n aria-hidden=\"true\"\n dir={contentDir ?? 'auto'}\n lang={contentLang}\n // A wrong word drawn as its character marks: the marks\n // carry the decoration, so the wrong characters stand\n // out from the right ones by shape, not colour alone.\n {...(word.status === 'incorrect' && alignment !== null && solutionsShown\n ? { 'data-marks': 'characters' }\n : {})}\n >\n {word.status === 'incorrect' && alignment !== null && solutionsShown ? (\n <CharOps\n ops={diffDictationChars(word.reference, word.attempt)}\n contentDir={contentDir}\n />\n ) : word.status === 'missing' ? (\n // Where the answer may not show, a gap the size of\n // nothing in particular: the word is missing, not named.\n solutionsShown ? (\n word.reference\n ) : (\n '…'\n )\n ) : (\n word.attempt\n )}\n </span>\n </li>\n );\n })}\n </ol>\n {sentenceDiff !== null ? (\n <p className=\"lk-dc-diff\" aria-hidden=\"true\" dir={contentDir} lang={contentLang}>\n <CharOps ops={sentenceDiff} contentDir={contentDir} />\n </p>\n ) : null}\n <ul className=\"lk-dc-legend\">\n <li data-state=\"correct\">{s.dictationLegendCorrect}</li>\n <li data-state=\"incorrect\">{s.dictationLegendWrong}</li>\n <li data-state=\"missing\">{s.dictationLegendMissing}</li>\n {alignment !== null ? <li data-state=\"extra\">{s.dictationLegendExtra}</li> : null}\n </ul>\n <p className=\"lk-dc-diff-note\">{s.dictationDiffNote}</p>\n </>\n )}\n {/*\n Offered after an empty answer too: a learner who could make out\n nothing is the one who most needs to read the sentence, and a\n single typed letter would reveal it anyway.\n */}\n {transcript !== undefined && solutionsShown ? (\n <>\n <button\n type=\"button\"\n className=\"lk-dc-solution-toggle\"\n aria-expanded={solutionShown}\n aria-controls={solutionId}\n onClick={() => setSolutionShown((shown) => !shown)}\n >\n {solutionShown ? s.hideSolution : s.showSolution}\n </button>\n {solutionShown ? (\n <blockquote\n id={solutionId}\n className=\"lk-dc-solution\"\n aria-label={s.dictationSolutionLabel}\n dir={contentDir}\n >\n <p lang={contentLang}>{transcript}</p>\n {acceptedTranscripts !== undefined && acceptedTranscripts.length > 0 ? (\n <ul className=\"lk-dc-solution-alternatives\" lang={contentLang}>\n {acceptedTranscripts.map((alternative) => (\n <li key={alternative}>{alternative}</li>\n ))}\n </ul>\n ) : null}\n </blockquote>\n ) : null}\n </>\n ) : null}\n </div>\n ) : null}\n\n <FeedbackRegion\n id={`${data.id}-feedback`}\n {...(scoringPolicy.retries > 0 ? { ref: feedbackRef } : {})}\n >\n <AnnouncementText\n announcement={feedbackAnnouncement({\n review: isReview,\n feedback: policy.feedback,\n outcome,\n readBack: reviewSummary,\n submitted: summary !== null,\n result:\n summary === null || extra === ''\n ? summary\n : { ...summary, text: `${summary.text} ${extra}` },\n received: { text: s.answerSubmitted, feedback: null },\n })}\n />\n </FeedbackRegion>\n <TryActions\n tries={tries}\n solutions={policy.solutions && transcript !== undefined}\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={responseOf(text, reportedHints)}\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 { Dictation as DictationCore, type DictationProps } from './Dictation.js';\n\nexport type { DictationProps } from './Dictation.js';\n\n/**\n * Public Dictation activity: the core wrapped in `ActivityErrorBoundary` so a\n * render failure (including the dev schema-validation throw and the\n * redacted-in-`practice` guard) degrades to an accessible fallback instead of\n * crashing the host.\n */\nexport function Dictation(props: DictationProps): React.JSX.Element {\n const strings = useLkStrings(props.strings);\n return (\n <ActivityErrorBoundary activityTitle={props.data?.title} strings={strings}>\n <DictationCore {...props} />\n </ActivityErrorBoundary>\n );\n}\n"],"mappings":"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"}
|