@intellectif/lk-react 12.0.1 → 13.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +29 -0
- package/README.md +8 -5
- package/dist/{WrittenResponse-BgcpEM-e.d.ts → WrittenResponse-B5p_-RnJ.d.ts} +1 -1
- package/dist/{WrittenResponse-ByzXYEoW.d.cts → WrittenResponse-DWtS4tOL.d.cts} +1 -1
- package/dist/{chunk-NK5EVL5C.cjs → chunk-2GXSA322.cjs} +14 -14
- package/dist/{chunk-NK5EVL5C.cjs.map → chunk-2GXSA322.cjs.map} +1 -1
- package/dist/{chunk-CK7Z275S.js → chunk-3Q4I4KT6.js} +22 -2
- package/dist/chunk-3Q4I4KT6.js.map +1 -0
- package/dist/{chunk-QXYAOILF.js → chunk-5N7OLFHB.js} +5 -5
- package/dist/{chunk-QXYAOILF.js.map → chunk-5N7OLFHB.js.map} +1 -1
- package/dist/{chunk-KSXEBV2H.cjs → chunk-74QXBWCI.cjs} +6 -6
- package/dist/{chunk-KSXEBV2H.cjs.map → chunk-74QXBWCI.cjs.map} +1 -1
- package/dist/{chunk-ZFWC4KZC.js → chunk-DQERNAWI.js} +3 -3
- package/dist/{chunk-ZFWC4KZC.js.map → chunk-DQERNAWI.js.map} +1 -1
- package/dist/{chunk-GVBEI5R3.cjs → chunk-HT4FFZZQ.cjs} +20 -16
- package/dist/chunk-HT4FFZZQ.cjs.map +1 -0
- package/dist/chunk-J73TDY7J.cjs +913 -0
- package/dist/chunk-J73TDY7J.cjs.map +1 -0
- package/dist/{chunk-EWTAMY6L.cjs → chunk-K3EA547J.cjs} +5 -5
- package/dist/{chunk-EWTAMY6L.cjs.map → chunk-K3EA547J.cjs.map} +1 -1
- package/dist/{chunk-G6PUWKQC.cjs → chunk-K7NMNOPI.cjs} +14 -14
- package/dist/{chunk-G6PUWKQC.cjs.map → chunk-K7NMNOPI.cjs.map} +1 -1
- package/dist/{chunk-KYOCDRUO.cjs → chunk-MA7GXUWC.cjs} +22 -2
- package/dist/chunk-MA7GXUWC.cjs.map +1 -0
- package/dist/{chunk-5IH3NRXE.js → chunk-NVP6UEQQ.js} +4 -4
- package/dist/{chunk-5IH3NRXE.js.map → chunk-NVP6UEQQ.js.map} +1 -1
- package/dist/{chunk-6KMEB7TF.cjs → chunk-NVUFEMPZ.cjs} +28 -15
- package/dist/chunk-NVUFEMPZ.cjs.map +1 -0
- package/dist/chunk-PSHNTDKG.js +911 -0
- package/dist/chunk-PSHNTDKG.js.map +1 -0
- package/dist/{chunk-5S2YDMEJ.js → chunk-PY53ABWC.js} +22 -9
- package/dist/chunk-PY53ABWC.js.map +1 -0
- package/dist/{chunk-2Y6V5E7H.js → chunk-R7DI5UMU.js} +57 -7
- package/dist/chunk-R7DI5UMU.js.map +1 -0
- package/dist/{chunk-FX7VMZHQ.cjs → chunk-SBIJJ374.cjs} +58 -8
- package/dist/chunk-SBIJJ374.cjs.map +1 -0
- package/dist/{chunk-64AJVB6O.cjs → chunk-UU2NV6SU.cjs} +13 -13
- package/dist/{chunk-64AJVB6O.cjs.map → chunk-UU2NV6SU.cjs.map} +1 -1
- package/dist/{chunk-ZFSRMGWO.js → chunk-W4MXRZAB.js} +5 -5
- package/dist/{chunk-ZFSRMGWO.js.map → chunk-W4MXRZAB.js.map} +1 -1
- package/dist/{chunk-XKGBD3UK.js → chunk-XAGWBVK3.js} +5 -5
- package/dist/{chunk-XKGBD3UK.js.map → chunk-XAGWBVK3.js.map} +1 -1
- package/dist/{chunk-V6M57YMK.js → chunk-YILH4ETT.js} +13 -9
- package/dist/chunk-YILH4ETT.js.map +1 -0
- package/dist/{chunk-GWD75Q25.js → chunk-YZWXVZB3.js} +5 -5
- package/dist/{chunk-GWD75Q25.js.map → chunk-YZWXVZB3.js.map} +1 -1
- package/dist/{chunk-LIHKQEIP.cjs → chunk-Z63JRG4M.cjs} +14 -14
- package/dist/{chunk-LIHKQEIP.cjs.map → chunk-Z63JRG4M.cjs.map} +1 -1
- package/dist/components/ActivityPreview.cjs +10 -9
- package/dist/components/ActivityPreview.d.cts +2 -2
- package/dist/components/ActivityPreview.d.ts +2 -2
- package/dist/components/ActivityPreview.js +9 -8
- package/dist/components/ActivitySequence.cjs +11 -10
- package/dist/components/ActivitySequence.d.cts +6 -4
- package/dist/components/ActivitySequence.d.ts +6 -4
- package/dist/components/ActivitySequence.js +10 -9
- package/dist/components/Dictation.cjs +17 -0
- package/dist/components/Dictation.cjs.map +1 -0
- package/dist/components/Dictation.d.cts +16 -0
- package/dist/components/Dictation.d.ts +16 -0
- package/dist/components/Dictation.js +8 -0
- package/dist/components/Dictation.js.map +1 -0
- package/dist/components/FillInTheBlanks.cjs +5 -5
- package/dist/components/FillInTheBlanks.d.cts +1 -1
- package/dist/components/FillInTheBlanks.d.ts +1 -1
- package/dist/components/FillInTheBlanks.js +4 -4
- package/dist/components/GapSelect.cjs +5 -5
- package/dist/components/GapSelect.d.cts +1 -1
- package/dist/components/GapSelect.d.ts +1 -1
- package/dist/components/GapSelect.js +4 -4
- package/dist/components/MultipleChoice.cjs +5 -5
- package/dist/components/MultipleChoice.d.cts +1 -1
- package/dist/components/MultipleChoice.d.ts +1 -1
- package/dist/components/MultipleChoice.js +4 -4
- package/dist/components/StimulusPanel.cjs +4 -4
- package/dist/components/StimulusPanel.d.cts +1 -1
- package/dist/components/StimulusPanel.d.ts +1 -1
- package/dist/components/StimulusPanel.js +3 -3
- package/dist/components/WrittenResponse.cjs +5 -5
- package/dist/components/WrittenResponse.d.cts +3 -3
- package/dist/components/WrittenResponse.d.ts +3 -3
- package/dist/components/WrittenResponse.js +4 -4
- package/dist/i18n/LkIntlProvider.cjs +7 -7
- package/dist/i18n/LkIntlProvider.d.cts +2 -2
- package/dist/i18n/LkIntlProvider.d.ts +2 -2
- package/dist/i18n/LkIntlProvider.js +1 -1
- package/dist/index.cjs +31 -26
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.js +11 -10
- package/dist/index.js.map +1 -1
- package/dist/{strings-CN7n-BlE.d.cts → strings-DPTyLpHt.d.cts} +49 -6
- package/dist/{strings-CN7n-BlE.d.ts → strings-DPTyLpHt.d.ts} +49 -6
- package/dist/theme/skin.css +454 -0
- package/package.json +13 -3
- package/dist/chunk-2Y6V5E7H.js.map +0 -1
- package/dist/chunk-5S2YDMEJ.js.map +0 -1
- package/dist/chunk-6KMEB7TF.cjs.map +0 -1
- package/dist/chunk-CK7Z275S.js.map +0 -1
- package/dist/chunk-FX7VMZHQ.cjs.map +0 -1
- package/dist/chunk-GVBEI5R3.cjs.map +0 -1
- package/dist/chunk-KYOCDRUO.cjs.map +0 -1
- package/dist/chunk-V6M57YMK.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/MultipleChoice/MultipleChoice.tsx","../src/components/MultipleChoice/index.tsx"],"names":["MultipleChoice","jsx"],"mappings":"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","file":"chunk-QXYAOILF.js","sourcesContent":["'use client';\n\nimport {\n ActivitySchemaError,\n type ItemOutcome,\n type LearnerResponse,\n type MultipleChoiceData,\n type MultipleChoiceLearnerResponse,\n type MultipleChoiceOption,\n type ScoringDetail,\n score,\n seededShuffle,\n validateActivity,\n xAPIBuilder,\n xapiDefinitionFor,\n} from '@intellectif/lk-core';\nimport { type CSSProperties, Fragment, useEffect, useMemo, useRef, useState } from 'react';\nimport { useActivityState } from '../../hooks/useActivityState.js';\nimport { useLkStrings } from '../../i18n/LkIntlProvider.js';\nimport type { LkStrings } from '../../i18n/strings.js';\nimport { ANONYMOUS_ACTOR, isDevelopment, objectIdFor, randomSessionId } from '../_internal.js';\nimport { ActivityMedia } from '../shared/ActivityMedia.js';\nimport { FeedbackRegion } from '../shared/FeedbackRegion.js';\nimport type { ActivityProps } from '../types.js';\n\n/**\n * Selected option ids carried by a learner response. Any other response shape\n * (a caller wiring the wrong item's state through `value`) reads as \"nothing\n * selected\" rather than throwing mid-exam.\n */\nfunction selectionOf(response: LearnerResponse | undefined): string[] {\n return response?.type === 'multiple-choice' ? response.selectedOptionIds : [];\n}\n\n/**\n * Whether an option is part of the correct answer, derived from the SERVER's\n * outcome — review mode never holds an answer key of its own.\n * {@link ScoringDetail.outcome} states this unambiguously. For 0.2-era details\n * that carry only the deprecated `correct` flag (which means \"the learner\n * ACTED correctly on this option\", not \"this option is the answer\") the fact\n * is still recoverable, because we know whether the learner selected it:\n * `isCorrect = wasSelected ? correct : !correct`.\n */\nfunction isAnswerOption(detail: ScoringDetail, wasSelected: boolean): boolean {\n switch (detail.outcome) {\n case 'correct':\n case 'incorrect-omission':\n return true;\n case 'incorrect':\n case 'correct-omission':\n return false;\n default:\n return wasSelected ? detail.correct : !detail.correct;\n }\n}\n\n/**\n * Announcement for `review` mode, built only from the server-supplied outcome.\n *\n * The authored overall feedback is appended by this function rather than being\n * baked into the score sentence, so a translation of \"Score 80%. Passed.\" never\n * has to carry the author's words with it.\n */\nfunction reviewAnnouncement(outcome: ItemOutcome | undefined, s: LkStrings): string | null {\n if (outcome === undefined) {\n return null;\n }\n if (outcome.status === 'scored') {\n const overall = outcome.feedback;\n return `${s.scoreAnnouncement(Math.round(outcome.score * 100), outcome.passed)}${\n overall ? ` ${overall}` : ''\n }`;\n }\n if (outcome.status === 'deferred') {\n // Never \"0%\": ungraded is not the same as wrong.\n return s.notGradedYet;\n }\n if (outcome.status === 'unscorable') {\n return s.noGradeAvailable;\n }\n // `graded` is left silent here deliberately: a returned GradeRecord on a\n // multiple-choice item has no rubric to render, and announcing a bare\n // percentage would duplicate what the marking already shows.\n return null;\n}\n\nexport interface MultipleChoiceProps extends ActivityProps<MultipleChoiceData> {\n /**\n * Seed for the deterministic option shuffle. Supply one (e.g. the attempt\n * id) to make the shuffled order reproducible server-side, stable across\n * page reloads, and identical between SSR and hydration. When absent, a\n * random per-mount session seed is used (order stable within the mount\n * only — the behaviour before `shuffleSeed` existed).\n */\n shuffleSeed?: string;\n}\n\nexport function MultipleChoice({\n data,\n onComplete,\n onSubmit,\n value,\n defaultValue,\n defaultSubmitted,\n onChange,\n renderMode = 'practice',\n outcome,\n sanitizeHtml,\n mediaBudget,\n mediaStrings,\n onInteraction,\n theme,\n locale,\n disabled,\n shuffleSeed,\n strings,\n}: MultipleChoiceProps) {\n const s = useLkStrings(strings);\n\n // Dev-only boundary validation (Req 2.3). Throwing during render lets\n // ActivityErrorBoundary catch it. Memoised so it only re-runs on data change.\n const devError = useMemo(() => {\n if (!isDevelopment()) {\n return null;\n }\n // A `redact()` projection cannot satisfy the full content schema — the\n // answer key is gone by design — and must not be checked against the\n // strict redacted schema either, since a `reveal: 'after-submit'`\n // projection legitimately carries answer-key fields for review renders.\n if (data.redacted === true) {\n return null;\n }\n const result = validateActivity('multiple-choice', data);\n return result.success ? null : new ActivitySchemaError('multiple-choice', result.errors);\n }, [data]);\n\n // Lazily created only when shuffling without a caller seed: avoids calling\n // crypto.randomUUID (absent on non-secure http origins) unless needed.\n const sessionIdRef = useRef<string | null>(null);\n const { state, start, complete, getTimeSpent, reset } = useActivityState(\n defaultSubmitted === true ? 'completed' : 'idle',\n );\n // Uncontrolled state. `defaultValue` seeds the mount only (React convention);\n // to re-seed later, remount with a `key` or drive the component with `value`.\n const [internalSelection, setInternalSelection] = useState<string[]>(() =>\n selectionOf(defaultValue),\n );\n const [summary, setSummary] = useState<string | null>(null);\n\n // Reset on data-prop CHANGE (Req 3.7). The identity guard makes the mount\n // run a no-op, which it always was before `defaultValue` existed — without\n // it this effect would wipe the seed immediately after the first render.\n // (It also makes the reset StrictMode-safe: a remount with unchanged data\n // no longer discards the learner's selection.)\n // Resets back to `defaultValue`, not to empty. Clearing looked safer — it\n // cannot carry a stale answer into a different question — but `data`\n // identity is a poor proxy for \"different question\": a parent that builds\n // entries in render (`activities={raw.map(redact)}`, the documented exam\n // pattern) hands over a new object every render, and clearing wiped every\n // RESTORED answer on the first unrelated re-render, silently. `defaultValue`\n // is by definition the caller's seed for the CURRENT data, which is what\n // FillInTheBlanks already reset to; the two now agree.\n const defaultValueRef = useRef(defaultValue);\n useEffect(() => {\n defaultValueRef.current = defaultValue;\n }, [defaultValue]);\n // A data change must return the item to its SEED, submitted state included —\n // resetting to idle unlocked work the learner had already committed.\n const defaultSubmittedRef = useRef(defaultSubmitted);\n useEffect(() => {\n defaultSubmittedRef.current = defaultSubmitted;\n }, [defaultSubmitted]);\n const lastDataRef = useRef(data);\n useEffect(() => {\n if (lastDataRef.current === data) {\n return;\n }\n lastDataRef.current = data;\n setInternalSelection(selectionOf(defaultValueRef.current));\n setSummary(null);\n reset(defaultSubmittedRef.current === true ? 'completed' : 'idle');\n }, [data, reset]);\n\n const displayedOptions = useMemo<MultipleChoiceOption[]>(() => {\n if (!data.shuffle) {\n return [...data.options];\n }\n // biome-ignore lint/suspicious/noAssignInExpressions: sanctioned lazy ref initialization\n const seedSource = shuffleSeed ?? (sessionIdRef.current ??= randomSessionId());\n // The seed string is unchanged from when the algorithm lived in this file,\n // so an order a consumer recorded against a seed still reproduces.\n return seededShuffle(data.options, `${seedSource}:${data.id}`);\n }, [data, shuffleSeed]);\n\n // Per-option correctness for `review`, indexed by option id. Null unless the\n // caller supplied a SCORED outcome — the only source of truth this mode has.\n const reviewDetails = useMemo(() => {\n if (renderMode !== 'review' || outcome === undefined || outcome.status !== 'scored') {\n return null;\n }\n return new Map(outcome.details.map((detail) => [detail.itemId, detail]));\n }, [renderMode, outcome]);\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, which a redacted projection cannot support:\n // `score()` would throw from the submit handler — where no error boundary\n // can catch it — AFTER the learner has answered. Fail loudly at render\n // instead, in production too. Wiring an exam item into the self-grading\n // mode is precisely the accident `renderMode` exists to prevent.\n if (data.redacted === true && renderMode === 'practice') {\n throw new Error(\n `Multiple Choice \"${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 isSingle = data.mode === 'single';\n const isExam = renderMode === 'exam';\n const isReview = renderMode === 'review';\n const submitted = state === 'completed';\n const inactive = disabled === true || submitted || isReview;\n const isControlled = value !== undefined;\n const selected = isControlled ? selectionOf(value) : internalSelection;\n /**\n * Whether correctness and authored feedback may be shown. Practice earns it\n * by grading locally after submit; review shows what the server already\n * graded. Exam never reveals anything — not before, not after submit.\n */\n const reveal = isReview || (renderMode === 'practice' && submitted);\n\n const fireInteraction = (\n type: 'option-selected' | 'option-deselected' | 'submitted',\n payload: Record<string, unknown>,\n ): void => {\n onInteraction?.({ type, activityId: data.id, timestamp: Date.now(), payload });\n };\n\n /** Single funnel for every response change: owns state only when uncontrolled. */\n const emitChange = (selectedOptionIds: string[]): void => {\n if (!isControlled) {\n setInternalSelection(selectedOptionIds);\n }\n onChange?.({ type: 'multiple-choice', selectedOptionIds });\n };\n\n const selectSingle = (optionId: string): void => {\n if (inactive) {\n return;\n }\n if (state === 'idle') {\n start();\n }\n emitChange([optionId]);\n fireInteraction('option-selected', { optionId });\n };\n\n const toggleMulti = (optionId: string, checked: boolean): void => {\n if (inactive) {\n return;\n }\n if (state === 'idle') {\n start();\n }\n emitChange(checked ? [...selected, optionId] : selected.filter((id) => id !== optionId));\n fireInteraction(checked ? 'option-selected' : 'option-deselected', { optionId });\n };\n\n const handleSubmit = (event: React.FormEvent): void => {\n event.preventDefault();\n if (inactive) {\n return;\n }\n const response: MultipleChoiceLearnerResponse = {\n type: 'multiple-choice',\n selectedOptionIds: selected,\n };\n // Always first, and before anything that can throw: an exam runner must be\n // able to persist the raw response no matter what happens after.\n onSubmit?.(response);\n\n if (isExam) {\n // No score(), no answer key read, no onComplete, no xAPI — the server\n // grades. The announcement deliberately carries no correctness signal.\n complete();\n setSummary(s.answerSubmitted);\n fireInteraction('submitted', { selectedOptionIds: selected });\n return;\n }\n\n const scoringResult = score('multiple-choice', data as MultipleChoiceData, response);\n complete();\n const timeSpent = getTimeSpent();\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 // Activity-type IRI, interaction type and correct-responses pattern\n // come from the REGISTERED descriptor, so a consumer-registered type\n // gets correct interop without touching this component.\n ...xapiDefinitionFor(data),\n choices: data.options.map((option) => ({\n id: option.id,\n description: { [data.locale ?? 'en-US']: option.text },\n })),\n },\n scoringResult,\n timeSpentMs: timeSpent,\n response: selected.join(','),\n });\n onComplete?.({\n score: scoringResult.score,\n maxScore: scoringResult.maxScore,\n passed: scoringResult.passed,\n timeSpent,\n xapiStatement,\n });\n // Core selects the authored overall feedback on `passed` (B3 fix).\n const overall = scoringResult.feedback;\n setSummary(\n `${s.answerSubmitted} ${s.scoreAnnouncement(\n Math.round(scoringResult.score * 100),\n scoringResult.passed,\n )}${overall ? ` ${overall}` : ''}`,\n );\n fireInteraction('submitted', {\n selectedOptionIds: selected,\n score: scoringResult.score,\n });\n };\n\n const questionId = `${data.id}-question`;\n // Rich text renders ONLY through a caller-supplied sanitiser; without one we\n // fall back to the plain-text field, escaped by React (types.ts HtmlSanitizer).\n const questionHtml =\n sanitizeHtml !== undefined && data.questionHtml !== undefined\n ? sanitizeHtml(data.questionHtml)\n : null;\n\n const optionList = displayedOptions.map((option) => {\n const checked = selected.includes(option.id);\n // `data-correct` means \"this option is part of the correct answer\".\n let correctness: string | undefined;\n if (isReview) {\n const detail = reviewDetails?.get(option.id);\n correctness = detail === undefined ? undefined : String(isAnswerOption(detail, checked));\n } else if (reveal) {\n correctness = String(option.isCorrect);\n }\n const media = option.media;\n // A picture goes INSIDE the label: it has no controls of its own, clicking\n // it is how a learner picks that option, and its `alt` joins the option's\n // accessible name. A recording cannot — see below.\n const picture =\n media?.type === 'image' ? (\n <img className=\"lk-mc-option-image\" src={media.url} alt={media.alt ?? ''} />\n ) : null;\n\n const label = (\n <label className=\"lk-mc-option\" data-correct={correctness}>\n <input\n type={isSingle ? 'radio' : 'checkbox'}\n name={isSingle ? `${data.id}-options` : undefined}\n value={option.id}\n checked={checked}\n disabled={inactive}\n aria-disabled={inactive || undefined}\n onChange={(e) =>\n isSingle ? selectSingle(option.id) : toggleMulti(option.id, e.target.checked)\n }\n />\n <span>{option.text}</span>\n {picture}\n {reveal && option.feedback ? (\n <span className=\"lk-mc-option-feedback\" role=\"note\">\n {option.feedback}\n </span>\n ) : null}\n </label>\n );\n\n if (media?.type !== 'audio') {\n return <Fragment key={option.id}>{label}</Fragment>;\n }\n\n // A recording sits OUTSIDE the label, deliberately. A <label> activates its\n // control for any click inside it, so an <audio> nested in one would select\n // the option the moment the learner pressed play — and a listening item is\n // answered by comparing all the recordings before choosing any of them.\n // Selecting an option must stay something the learner does on purpose.\n return (\n <div className=\"lk-mc-option-media\" key={option.id}>\n {label}\n {/* biome-ignore lint/a11y/useMediaCaption: captions are optional in the data contract — a <track> is rendered when captionsUrl is provided; absence is the author's documented choice (Req 14.5) */}\n <audio\n className=\"lk-mc-option-audio\"\n src={media.url}\n controls\n preload=\"none\"\n aria-label={media.alt ?? option.text}\n >\n {media.captionsUrl ? (\n <track kind=\"captions\" src={media.captionsUrl} label={media.alt ?? option.text} />\n ) : null}\n </audio>\n </div>\n );\n });\n\n return (\n <div className=\"lk-mc\" lang={locale} style={theme as CSSProperties | undefined}>\n {data.media ? (\n <ActivityMedia\n media={data.media}\n renderMode={renderMode}\n {...(mediaBudget !== undefined ? { mediaBudget } : {})}\n {...(mediaStrings !== undefined ? { mediaStrings } : {})}\n {...(strings !== undefined ? { strings } : {})}\n {...(onInteraction !== undefined ? { onInteraction } : {})}\n {...(locale !== undefined ? { locale } : {})}\n />\n ) : null}\n <form onSubmit={handleSubmit}>\n <fieldset disabled={inactive}>\n {questionHtml === null ? (\n <legend id={questionId}>{data.question}</legend>\n ) : (\n <legend\n id={questionId}\n // biome-ignore lint/security/noDangerouslySetInnerHtml: HTML is the output of the caller-supplied sanitizeHtml (types.ts HtmlSanitizer contract)\n dangerouslySetInnerHTML={{ __html: questionHtml }}\n />\n )}\n {/*\n single: an explicit radiogroup is meaningful (fieldset's implicit\n role is `group`, not `radiogroup`). multi: the fieldset + legend\n already provide a named group — an explicit role=\"group\" here would\n be a redundant, duplicate same-named group in the a11y tree, so we\n use a plain layout div. (Refines the design ARIA sketch.)\n */}\n {isSingle ? (\n <div role=\"radiogroup\" aria-labelledby={questionId}>\n {optionList}\n </div>\n ) : (\n <div>{optionList}</div>\n )}\n {/* review is read-only: there is nothing left to submit. */}\n {isReview ? null : (\n <button type=\"submit\" disabled={inactive}>\n {s.submit}\n </button>\n )}\n </fieldset>\n </form>\n <FeedbackRegion id={`${data.id}-feedback`}>\n {isReview ? reviewAnnouncement(outcome, s) : summary}\n </FeedbackRegion>\n </div>\n );\n}\n","'use client';\n\nimport { useLkStrings } from '../../i18n/LkIntlProvider.js';\nimport { ActivityErrorBoundary } from '../ActivityErrorBoundary.js';\nimport {\n MultipleChoice as MultipleChoiceCore,\n type MultipleChoiceProps,\n} from './MultipleChoice.js';\n\nexport type { MultipleChoiceProps } from './MultipleChoice.js';\n\n/**\n * Public Multiple Choice activity: the core component wrapped in\n * `ActivityErrorBoundary` so a render failure (incl. the dev schema-validation\n * throw) degrades to an accessible fallback instead of crashing the host.\n */\nexport function MultipleChoice(props: MultipleChoiceProps) {\n // Read here rather than in the boundary: a class component cannot call a\n // hook, and the fallback it renders is learner-facing text like any other.\n const strings = useLkStrings(props.strings);\n return (\n <ActivityErrorBoundary activityTitle={props.data?.title} strings={strings}>\n <MultipleChoiceCore {...props} />\n </ActivityErrorBoundary>\n );\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/components/MultipleChoice/MultipleChoice.tsx","../src/components/MultipleChoice/index.tsx"],"names":["MultipleChoice","jsx"],"mappings":"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","file":"chunk-5N7OLFHB.js","sourcesContent":["'use client';\n\nimport {\n ActivitySchemaError,\n type ItemOutcome,\n type LearnerResponse,\n type MultipleChoiceData,\n type MultipleChoiceLearnerResponse,\n type MultipleChoiceOption,\n type ScoringDetail,\n score,\n seededShuffle,\n validateActivity,\n xAPIBuilder,\n xapiDefinitionFor,\n} from '@intellectif/lk-core';\nimport { type CSSProperties, Fragment, useEffect, useMemo, useRef, useState } from 'react';\nimport { useActivityState } from '../../hooks/useActivityState.js';\nimport { useLkStrings } from '../../i18n/LkIntlProvider.js';\nimport type { LkStrings } from '../../i18n/strings.js';\nimport { ANONYMOUS_ACTOR, isDevelopment, objectIdFor, randomSessionId } from '../_internal.js';\nimport { ActivityMedia } from '../shared/ActivityMedia.js';\nimport { FeedbackRegion } from '../shared/FeedbackRegion.js';\nimport type { ActivityProps } from '../types.js';\n\n/**\n * Selected option ids carried by a learner response. Any other response shape\n * (a caller wiring the wrong item's state through `value`) reads as \"nothing\n * selected\" rather than throwing mid-exam.\n */\nfunction selectionOf(response: LearnerResponse | undefined): string[] {\n return response?.type === 'multiple-choice' ? response.selectedOptionIds : [];\n}\n\n/**\n * Whether an option is part of the correct answer, derived from the SERVER's\n * outcome — review mode never holds an answer key of its own.\n * {@link ScoringDetail.outcome} states this unambiguously. For 0.2-era details\n * that carry only the deprecated `correct` flag (which means \"the learner\n * ACTED correctly on this option\", not \"this option is the answer\") the fact\n * is still recoverable, because we know whether the learner selected it:\n * `isCorrect = wasSelected ? correct : !correct`.\n */\nfunction isAnswerOption(detail: ScoringDetail, wasSelected: boolean): boolean {\n switch (detail.outcome) {\n case 'correct':\n case 'incorrect-omission':\n return true;\n case 'incorrect':\n case 'correct-omission':\n return false;\n default:\n return wasSelected ? detail.correct : !detail.correct;\n }\n}\n\n/**\n * Announcement for `review` mode, built only from the server-supplied outcome.\n *\n * The authored overall feedback is appended by this function rather than being\n * baked into the score sentence, so a translation of \"Score 80%. Passed.\" never\n * has to carry the author's words with it.\n */\nfunction reviewAnnouncement(outcome: ItemOutcome | undefined, s: LkStrings): string | null {\n if (outcome === undefined) {\n return null;\n }\n if (outcome.status === 'scored') {\n const overall = outcome.feedback;\n return `${s.scoreAnnouncement(Math.round(outcome.score * 100), outcome.passed)}${\n overall ? ` ${overall}` : ''\n }`;\n }\n if (outcome.status === 'deferred') {\n // Never \"0%\": ungraded is not the same as wrong.\n return s.notGradedYet;\n }\n if (outcome.status === 'unscorable') {\n return s.noGradeAvailable;\n }\n // `graded` is left silent here deliberately: a returned GradeRecord on a\n // multiple-choice item has no rubric to render, and announcing a bare\n // percentage would duplicate what the marking already shows.\n return null;\n}\n\nexport interface MultipleChoiceProps extends ActivityProps<MultipleChoiceData> {\n /**\n * Seed for the deterministic option shuffle. Supply one (e.g. the attempt\n * id) to make the shuffled order reproducible server-side, stable across\n * page reloads, and identical between SSR and hydration. When absent, a\n * random per-mount session seed is used (order stable within the mount\n * only — the behaviour before `shuffleSeed` existed).\n */\n shuffleSeed?: string;\n}\n\nexport function MultipleChoice({\n data,\n onComplete,\n onSubmit,\n value,\n defaultValue,\n defaultSubmitted,\n onChange,\n renderMode = 'practice',\n outcome,\n sanitizeHtml,\n mediaBudget,\n mediaStrings,\n onInteraction,\n theme,\n locale,\n disabled,\n shuffleSeed,\n strings,\n}: MultipleChoiceProps) {\n const s = useLkStrings(strings);\n\n // Dev-only boundary validation (Req 2.3). Throwing during render lets\n // ActivityErrorBoundary catch it. Memoised so it only re-runs on data change.\n const devError = useMemo(() => {\n if (!isDevelopment()) {\n return null;\n }\n // A `redact()` projection cannot satisfy the full content schema — the\n // answer key is gone by design — and must not be checked against the\n // strict redacted schema either, since a `reveal: 'after-submit'`\n // projection legitimately carries answer-key fields for review renders.\n if (data.redacted === true) {\n return null;\n }\n const result = validateActivity('multiple-choice', data);\n return result.success ? null : new ActivitySchemaError('multiple-choice', result.errors);\n }, [data]);\n\n // Lazily created only when shuffling without a caller seed: avoids calling\n // crypto.randomUUID (absent on non-secure http origins) unless needed.\n const sessionIdRef = useRef<string | null>(null);\n const { state, start, complete, getTimeSpent, reset } = useActivityState(\n defaultSubmitted === true ? 'completed' : 'idle',\n );\n // Uncontrolled state. `defaultValue` seeds the mount only (React convention);\n // to re-seed later, remount with a `key` or drive the component with `value`.\n const [internalSelection, setInternalSelection] = useState<string[]>(() =>\n selectionOf(defaultValue),\n );\n const [summary, setSummary] = useState<string | null>(null);\n\n // Reset on data-prop CHANGE (Req 3.7). The identity guard makes the mount\n // run a no-op, which it always was before `defaultValue` existed — without\n // it this effect would wipe the seed immediately after the first render.\n // (It also makes the reset StrictMode-safe: a remount with unchanged data\n // no longer discards the learner's selection.)\n // Resets back to `defaultValue`, not to empty. Clearing looked safer — it\n // cannot carry a stale answer into a different question — but `data`\n // identity is a poor proxy for \"different question\": a parent that builds\n // entries in render (`activities={raw.map(redact)}`, the documented exam\n // pattern) hands over a new object every render, and clearing wiped every\n // RESTORED answer on the first unrelated re-render, silently. `defaultValue`\n // is by definition the caller's seed for the CURRENT data, which is what\n // FillInTheBlanks already reset to; the two now agree.\n const defaultValueRef = useRef(defaultValue);\n useEffect(() => {\n defaultValueRef.current = defaultValue;\n }, [defaultValue]);\n // A data change must return the item to its SEED, submitted state included —\n // resetting to idle unlocked work the learner had already committed.\n const defaultSubmittedRef = useRef(defaultSubmitted);\n useEffect(() => {\n defaultSubmittedRef.current = defaultSubmitted;\n }, [defaultSubmitted]);\n const lastDataRef = useRef(data);\n useEffect(() => {\n if (lastDataRef.current === data) {\n return;\n }\n lastDataRef.current = data;\n setInternalSelection(selectionOf(defaultValueRef.current));\n setSummary(null);\n reset(defaultSubmittedRef.current === true ? 'completed' : 'idle');\n }, [data, reset]);\n\n const displayedOptions = useMemo<MultipleChoiceOption[]>(() => {\n if (!data.shuffle) {\n return [...data.options];\n }\n // biome-ignore lint/suspicious/noAssignInExpressions: sanctioned lazy ref initialization\n const seedSource = shuffleSeed ?? (sessionIdRef.current ??= randomSessionId());\n // The seed string is unchanged from when the algorithm lived in this file,\n // so an order a consumer recorded against a seed still reproduces.\n return seededShuffle(data.options, `${seedSource}:${data.id}`);\n }, [data, shuffleSeed]);\n\n // Per-option correctness for `review`, indexed by option id. Null unless the\n // caller supplied a SCORED outcome — the only source of truth this mode has.\n const reviewDetails = useMemo(() => {\n if (renderMode !== 'review' || outcome === undefined || outcome.status !== 'scored') {\n return null;\n }\n return new Map(outcome.details.map((detail) => [detail.itemId, detail]));\n }, [renderMode, outcome]);\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, which a redacted projection cannot support:\n // `score()` would throw from the submit handler — where no error boundary\n // can catch it — AFTER the learner has answered. Fail loudly at render\n // instead, in production too. Wiring an exam item into the self-grading\n // mode is precisely the accident `renderMode` exists to prevent.\n if (data.redacted === true && renderMode === 'practice') {\n throw new Error(\n `Multiple Choice \"${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 isSingle = data.mode === 'single';\n const isExam = renderMode === 'exam';\n const isReview = renderMode === 'review';\n const submitted = state === 'completed';\n const inactive = disabled === true || submitted || isReview;\n const isControlled = value !== undefined;\n const selected = isControlled ? selectionOf(value) : internalSelection;\n /**\n * Whether correctness and authored feedback may be shown. Practice earns it\n * by grading locally after submit; review shows what the server already\n * graded. Exam never reveals anything — not before, not after submit.\n */\n const reveal = isReview || (renderMode === 'practice' && submitted);\n\n const fireInteraction = (\n type: 'option-selected' | 'option-deselected' | 'submitted',\n payload: Record<string, unknown>,\n ): void => {\n onInteraction?.({ type, activityId: data.id, timestamp: Date.now(), payload });\n };\n\n /** Single funnel for every response change: owns state only when uncontrolled. */\n const emitChange = (selectedOptionIds: string[]): void => {\n if (!isControlled) {\n setInternalSelection(selectedOptionIds);\n }\n onChange?.({ type: 'multiple-choice', selectedOptionIds });\n };\n\n const selectSingle = (optionId: string): void => {\n if (inactive) {\n return;\n }\n if (state === 'idle') {\n start();\n }\n emitChange([optionId]);\n fireInteraction('option-selected', { optionId });\n };\n\n const toggleMulti = (optionId: string, checked: boolean): void => {\n if (inactive) {\n return;\n }\n if (state === 'idle') {\n start();\n }\n emitChange(checked ? [...selected, optionId] : selected.filter((id) => id !== optionId));\n fireInteraction(checked ? 'option-selected' : 'option-deselected', { optionId });\n };\n\n const handleSubmit = (event: React.FormEvent): void => {\n event.preventDefault();\n if (inactive) {\n return;\n }\n const response: MultipleChoiceLearnerResponse = {\n type: 'multiple-choice',\n selectedOptionIds: selected,\n };\n // Always first, and before anything that can throw: an exam runner must be\n // able to persist the raw response no matter what happens after.\n onSubmit?.(response);\n\n if (isExam) {\n // No score(), no answer key read, no onComplete, no xAPI — the server\n // grades. The announcement deliberately carries no correctness signal.\n complete();\n setSummary(s.answerSubmitted);\n fireInteraction('submitted', { selectedOptionIds: selected });\n return;\n }\n\n const scoringResult = score('multiple-choice', data as MultipleChoiceData, response);\n complete();\n const timeSpent = getTimeSpent();\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 // Activity-type IRI, interaction type and correct-responses pattern\n // come from the REGISTERED descriptor, so a consumer-registered type\n // gets correct interop without touching this component.\n ...xapiDefinitionFor(data),\n choices: data.options.map((option) => ({\n id: option.id,\n description: { [data.locale ?? 'en-US']: option.text },\n })),\n },\n scoringResult,\n timeSpentMs: timeSpent,\n response: selected.join(','),\n });\n onComplete?.({\n score: scoringResult.score,\n maxScore: scoringResult.maxScore,\n passed: scoringResult.passed,\n timeSpent,\n xapiStatement,\n });\n // Core selects the authored overall feedback on `passed` (B3 fix).\n const overall = scoringResult.feedback;\n setSummary(\n `${s.answerSubmitted} ${s.scoreAnnouncement(\n Math.round(scoringResult.score * 100),\n scoringResult.passed,\n )}${overall ? ` ${overall}` : ''}`,\n );\n fireInteraction('submitted', {\n selectedOptionIds: selected,\n score: scoringResult.score,\n });\n };\n\n const questionId = `${data.id}-question`;\n // Rich text renders ONLY through a caller-supplied sanitiser; without one we\n // fall back to the plain-text field, escaped by React (types.ts HtmlSanitizer).\n const questionHtml =\n sanitizeHtml !== undefined && data.questionHtml !== undefined\n ? sanitizeHtml(data.questionHtml)\n : null;\n\n const optionList = displayedOptions.map((option) => {\n const checked = selected.includes(option.id);\n // `data-correct` means \"this option is part of the correct answer\".\n let correctness: string | undefined;\n if (isReview) {\n const detail = reviewDetails?.get(option.id);\n correctness = detail === undefined ? undefined : String(isAnswerOption(detail, checked));\n } else if (reveal) {\n correctness = String(option.isCorrect);\n }\n const media = option.media;\n // A picture goes INSIDE the label: it has no controls of its own, clicking\n // it is how a learner picks that option, and its `alt` joins the option's\n // accessible name. A recording cannot — see below.\n const picture =\n media?.type === 'image' ? (\n <img className=\"lk-mc-option-image\" src={media.url} alt={media.alt ?? ''} />\n ) : null;\n\n const label = (\n <label className=\"lk-mc-option\" data-correct={correctness}>\n <input\n type={isSingle ? 'radio' : 'checkbox'}\n name={isSingle ? `${data.id}-options` : undefined}\n value={option.id}\n checked={checked}\n disabled={inactive}\n aria-disabled={inactive || undefined}\n onChange={(e) =>\n isSingle ? selectSingle(option.id) : toggleMulti(option.id, e.target.checked)\n }\n />\n <span>{option.text}</span>\n {picture}\n {reveal && option.feedback ? (\n <span className=\"lk-mc-option-feedback\" role=\"note\">\n {option.feedback}\n </span>\n ) : null}\n </label>\n );\n\n if (media?.type !== 'audio') {\n return <Fragment key={option.id}>{label}</Fragment>;\n }\n\n // A recording sits OUTSIDE the label, deliberately. A <label> activates its\n // control for any click inside it, so an <audio> nested in one would select\n // the option the moment the learner pressed play — and a listening item is\n // answered by comparing all the recordings before choosing any of them.\n // Selecting an option must stay something the learner does on purpose.\n return (\n <div className=\"lk-mc-option-media\" key={option.id}>\n {label}\n {/* biome-ignore lint/a11y/useMediaCaption: captions are optional in the data contract — a <track> is rendered when captionsUrl is provided; absence is the author's documented choice (Req 14.5) */}\n <audio\n className=\"lk-mc-option-audio\"\n src={media.url}\n controls\n preload=\"none\"\n aria-label={media.alt ?? option.text}\n >\n {media.captionsUrl ? (\n <track kind=\"captions\" src={media.captionsUrl} label={media.alt ?? option.text} />\n ) : null}\n </audio>\n </div>\n );\n });\n\n return (\n <div className=\"lk-mc\" lang={locale} style={theme as CSSProperties | undefined}>\n {data.media ? (\n <ActivityMedia\n media={data.media}\n renderMode={renderMode}\n {...(mediaBudget !== undefined ? { mediaBudget } : {})}\n {...(mediaStrings !== undefined ? { mediaStrings } : {})}\n {...(strings !== undefined ? { strings } : {})}\n {...(onInteraction !== undefined ? { onInteraction } : {})}\n {...(locale !== undefined ? { locale } : {})}\n />\n ) : null}\n <form onSubmit={handleSubmit}>\n <fieldset disabled={inactive}>\n {questionHtml === null ? (\n <legend id={questionId}>{data.question}</legend>\n ) : (\n <legend\n id={questionId}\n // biome-ignore lint/security/noDangerouslySetInnerHtml: HTML is the output of the caller-supplied sanitizeHtml (types.ts HtmlSanitizer contract)\n dangerouslySetInnerHTML={{ __html: questionHtml }}\n />\n )}\n {/*\n single: an explicit radiogroup is meaningful (fieldset's implicit\n role is `group`, not `radiogroup`). multi: the fieldset + legend\n already provide a named group — an explicit role=\"group\" here would\n be a redundant, duplicate same-named group in the a11y tree, so we\n use a plain layout div. (Refines the design ARIA sketch.)\n */}\n {isSingle ? (\n <div role=\"radiogroup\" aria-labelledby={questionId}>\n {optionList}\n </div>\n ) : (\n <div>{optionList}</div>\n )}\n {/* review is read-only: there is nothing left to submit. */}\n {isReview ? null : (\n <button type=\"submit\" disabled={inactive}>\n {s.submit}\n </button>\n )}\n </fieldset>\n </form>\n <FeedbackRegion id={`${data.id}-feedback`}>\n {isReview ? reviewAnnouncement(outcome, s) : summary}\n </FeedbackRegion>\n </div>\n );\n}\n","'use client';\n\nimport { useLkStrings } from '../../i18n/LkIntlProvider.js';\nimport { ActivityErrorBoundary } from '../ActivityErrorBoundary.js';\nimport {\n MultipleChoice as MultipleChoiceCore,\n type MultipleChoiceProps,\n} from './MultipleChoice.js';\n\nexport type { MultipleChoiceProps } from './MultipleChoice.js';\n\n/**\n * Public Multiple Choice activity: the core component wrapped in\n * `ActivityErrorBoundary` so a render failure (incl. the dev schema-validation\n * throw) degrades to an accessible fallback instead of crashing the host.\n */\nexport function MultipleChoice(props: MultipleChoiceProps) {\n // Read here rather than in the boundary: a class component cannot call a\n // hook, and the fallback it renders is learner-facing text like any other.\n const strings = useLkStrings(props.strings);\n return (\n <ActivityErrorBoundary activityTitle={props.data?.title} strings={strings}>\n <MultipleChoiceCore {...props} />\n </ActivityErrorBoundary>\n );\n}\n"]}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
-
var
|
|
5
|
-
var
|
|
4
|
+
var chunkSBIJJ374_cjs = require('./chunk-SBIJJ374.cjs');
|
|
5
|
+
var chunkMA7GXUWC_cjs = require('./chunk-MA7GXUWC.cjs');
|
|
6
6
|
var react = require('react');
|
|
7
7
|
var jsxRuntime = require('react/jsx-runtime');
|
|
8
8
|
|
|
@@ -19,7 +19,7 @@ function StimulusPanel({
|
|
|
19
19
|
strings
|
|
20
20
|
}) {
|
|
21
21
|
const titleId = react.useId();
|
|
22
|
-
const s =
|
|
22
|
+
const s = chunkMA7GXUWC_cjs.useLkStrings(strings);
|
|
23
23
|
const bodyHtml = react.useMemo(() => {
|
|
24
24
|
if (sanitizeHtml === void 0 || typeof stimulus.bodyHtml !== "string") {
|
|
25
25
|
return null;
|
|
@@ -33,7 +33,7 @@ function StimulusPanel({
|
|
|
33
33
|
stimulus.title !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "lk-stimulus-title", id: titleId, ...contentLang, children: stimulus.title }) : null,
|
|
34
34
|
rangeText !== null ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "lk-stimulus-range", children: rangeText }) : null,
|
|
35
35
|
stimulus.media !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
36
|
-
|
|
36
|
+
chunkSBIJJ374_cjs.ActivityMedia,
|
|
37
37
|
{
|
|
38
38
|
media: stimulus.media,
|
|
39
39
|
...renderMode !== void 0 ? { renderMode } : {},
|
|
@@ -59,5 +59,5 @@ function StimulusPanel({
|
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
exports.StimulusPanel = StimulusPanel;
|
|
62
|
-
//# sourceMappingURL=chunk-
|
|
63
|
-
//# sourceMappingURL=chunk-
|
|
62
|
+
//# sourceMappingURL=chunk-74QXBWCI.cjs.map
|
|
63
|
+
//# sourceMappingURL=chunk-74QXBWCI.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/StimulusPanel/StimulusPanel.tsx"],"names":["useId","useLkStrings","useMemo","jsxs","jsx","ActivityMedia"],"mappings":"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","file":"chunk-
|
|
1
|
+
{"version":3,"sources":["../src/components/StimulusPanel/StimulusPanel.tsx"],"names":["useId","useLkStrings","useMemo","jsxs","jsx","ActivityMedia"],"mappings":"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","file":"chunk-74QXBWCI.cjs","sourcesContent":["'use client';\n\nimport type { InteractionEvent, Stimulus } from '@intellectif/lk-core';\nimport { useId, useMemo } from 'react';\nimport { useLkStrings } from '../../i18n/LkIntlProvider.js';\nimport type { LkStringsOverride } from '../../i18n/strings.js';\nimport { ActivityMedia } from '../shared/ActivityMedia.js';\nimport type {\n HtmlSanitizer,\n MediaBudgetBinding,\n MediaTransportStrings,\n RenderMode,\n} from '../types.js';\n\nexport interface StimulusPanelProps {\n stimulus: Stimulus;\n /**\n * 1-based presented positions of the first and last question this material\n * serves, rendered as \"Questions 3–8\" so a learner knows how far the\n * passage carries.\n */\n range?: { first: number; last: number };\n /** Renders `stimulus.bodyHtml` when provided. See `HtmlSanitizer`. */\n sanitizeHtml?: HtmlSanitizer;\n locale?: string;\n /**\n * Forwarded to the stimulus's own media. `review` keeps the browser's control\n * bar even under an enforcing policy — a graded paper cannot be changed by\n * listening again.\n */\n renderMode?: RenderMode;\n /**\n * Binds this group's recording to a play budget the consumer persists. One\n * recording serves every question in the group, so the key is derived from\n * the ENTRY rather than the slot — see `stimulusMediaKey()` in lk-core.\n */\n mediaBudget?: MediaBudgetBinding;\n /** Translations for the audio transport chrome. */\n mediaStrings?: Partial<MediaTransportStrings>;\n onInteraction?: (event: InteractionEvent) => void;\n disabled?: boolean;\n /** Overrides the SDK's chrome text for this panel. See {@link LkIntlProvider}. */\n strings?: LkStringsOverride;\n}\n\n/**\n * Presents a shared stimulus — the passage, recording or image an item group's\n * questions refer to. A landmark region named by its title (or by its kind),\n * so a screen-reader user can jump back to the material from any question.\n *\n * Rich text follows the SDK-wide rule: `bodyHtml` is rendered only through a\n * caller-supplied sanitiser, and the plain `body` is the fallback — which the\n * schema guarantees exists whenever `bodyHtml` does.\n */\nexport function StimulusPanel({\n stimulus,\n range,\n sanitizeHtml,\n locale,\n renderMode,\n mediaBudget,\n mediaStrings,\n onInteraction,\n disabled,\n strings,\n}: StimulusPanelProps): React.JSX.Element {\n const titleId = useId();\n const s = useLkStrings(strings);\n\n const bodyHtml = useMemo(() => {\n if (sanitizeHtml === undefined || typeof stimulus.bodyHtml !== 'string') {\n return null;\n }\n return sanitizeHtml(stimulus.bodyHtml);\n }, [stimulus.bodyHtml, sanitizeHtml]);\n\n const rangeText = range === undefined ? null : s.stimulusRange(range.first, range.last);\n\n const naming =\n stimulus.title !== undefined\n ? { 'aria-labelledby': titleId }\n : { 'aria-label': s.stimulusKind[stimulus.kind] };\n\n // The AUTHORED parts carry the stimulus language; the SDK's own chrome does\n // not. Declaring `stimulus.locale` on the whole region made a screen reader\n // read \"Recording\" and \"Questions 3–5\" — English strings this package ships\n // — in the passage's voice, so a Spanish listening panel announced its own\n // name and the only orientation text in it with Spanish phonetics\n // (WCAG 3.1.2 Language of Parts). Scope the declaration to the content it\n // actually describes.\n const contentLang = stimulus.locale !== undefined ? { lang: stimulus.locale } : {};\n\n return (\n <section className=\"lk-stimulus\" data-kind={stimulus.kind} lang={locale} {...naming}>\n {stimulus.title !== undefined ? (\n <p className=\"lk-stimulus-title\" id={titleId} {...contentLang}>\n {stimulus.title}\n </p>\n ) : null}\n {rangeText !== null ? <p className=\"lk-stimulus-range\">{rangeText}</p> : null}\n {stimulus.media !== undefined ? (\n <ActivityMedia\n media={stimulus.media}\n {...(renderMode !== undefined ? { renderMode } : {})}\n {...(mediaBudget !== undefined ? { mediaBudget } : {})}\n {...(mediaStrings !== undefined ? { mediaStrings } : {})}\n {...(strings !== undefined ? { strings } : {})}\n {...(onInteraction !== undefined ? { onInteraction } : {})}\n {...(disabled !== undefined ? { disabled } : {})}\n {...(locale !== undefined ? { locale } : {})}\n />\n ) : null}\n {bodyHtml !== null ? (\n <div\n className=\"lk-stimulus-body\"\n data-format=\"html\"\n {...contentLang}\n // biome-ignore lint/security/noDangerouslySetInnerHtml: rendered only through the caller-supplied HtmlSanitizer — the SDK never injects unsanitised markup\n dangerouslySetInnerHTML={{ __html: bodyHtml }}\n />\n ) : stimulus.body !== undefined ? (\n <div className=\"lk-stimulus-body\" data-format=\"text\" {...contentLang}>\n {stimulus.body}\n </div>\n ) : null}\n {stimulus.attribution !== undefined ? (\n <p className=\"lk-stimulus-attribution\" {...contentLang}>\n <cite>{stimulus.attribution}</cite>\n </p>\n ) : null}\n </section>\n );\n}\n"]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import { DEFAULT_STRINGS } from './chunk-
|
|
2
|
+
import { DEFAULT_STRINGS } from './chunk-3Q4I4KT6.js';
|
|
3
3
|
import { Component } from 'react';
|
|
4
4
|
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
5
5
|
|
|
@@ -51,5 +51,5 @@ function FeedbackRegion({ id, className, children }) {
|
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
export { ANONYMOUS_ACTOR, ActivityErrorBoundary, FeedbackRegion, isDevelopment, objectIdFor, randomSessionId };
|
|
54
|
-
//# sourceMappingURL=chunk-
|
|
55
|
-
//# sourceMappingURL=chunk-
|
|
54
|
+
//# sourceMappingURL=chunk-DQERNAWI.js.map
|
|
55
|
+
//# sourceMappingURL=chunk-DQERNAWI.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/ActivityErrorBoundary.tsx","../src/components/_internal.ts","../src/components/shared/FeedbackRegion.tsx"],"names":["jsx"],"mappings":";;;;AAMA,SAAS,YAAA,GAAwB;AAC/B,EAAA,MAAM,CAAA,GAAI,UAAA;AACV,EAAA,OAAO,CAAA,CAAE,OAAA,EAAS,GAAA,EAAK,QAAA,KAAa,YAAA;AACtC;AAuBO,IAAM,qBAAA,GAAN,cAAoC,SAAA,CAGzC;AAAA,EACA,KAAA,GAAoC,EAAE,KAAA,EAAO,IAAA,EAAK;AAAA,EAElD,OAAO,yBAAyB,KAAA,EAA0C;AACxE,IAAA,OAAO,EAAE,KAAA,EAAM;AAAA,EACjB;AAAA,EAEA,MAAA,GAAoB;AAClB,IAAA,MAAM,EAAE,KAAA,EAAM,GAAI,IAAA,CAAK,KAAA;AACvB,IAAA,IAAI,CAAC,KAAA,EAAO;AACV,MAAA,OAAO,KAAK,KAAA,CAAM,QAAA;AAAA,IACpB;AAEA,IAAA,IAAI,CAAC,cAAa,EAAG;AACnB,MAAA,uBACE,IAAA,CAAC,KAAA,EAAA,EAAI,IAAA,EAAK,OAAA,EACR,QAAA,EAAA;AAAA,wBAAA,GAAA,CAAC,QAAA,EAAA,EAAS,QAAA,EAAA,CAAA,IAAA,CAAK,KAAA,CAAM,OAAA,IAAW,iBAAiB,cAAA,EAAe,CAAA;AAAA,wBAChE,GAAA,CAAC,KAAA,EAAA,EAAK,QAAA,EAAA,KAAA,CAAM,KAAA,IAAS,MAAM,OAAA,EAAQ;AAAA,OAAA,EACrC,CAAA;AAAA,IAEJ;AAEA,IAAA,uBACE,GAAA,CAAC,SAAI,IAAA,EAAK,OAAA,EACP,eAAK,KAAA,CAAM,aAAA,GAAA,CACP,KAAK,KAAA,CAAM,OAAA,IAAW,iBAAiB,mBAAA,CAAoB,IAAA,CAAK,MAAM,aAAa,CAAA,GAAA,CACnF,KAAK,KAAA,CAAM,OAAA,IAAW,iBAAiB,qBAAA,EAC9C,CAAA;AAAA,EAEJ;AACF;;;AC9DO,SAAS,aAAA,GAAyB;AACvC,EAAA,MAAM,CAAA,GAAI,UAAA;AACV,EAAA,OAAO,CAAA,CAAE,OAAA,EAAS,GAAA,EAAK,QAAA,KAAa,YAAA;AACtC;AAOO,IAAM,eAAA,GAA6B;AAAA,EACxC,UAAA,EAAY,OAAA;AAAA,EACZ,OAAA,EAAS,EAAE,QAAA,EAAU,6CAAA,EAA+C,MAAM,WAAA;AAC5E;AAGO,SAAS,YAAY,EAAA,EAAoB;AAC9C,EAAA,OAAO,CAAA,0BAAA,EAA6B,kBAAA,CAAmB,EAAE,CAAC,CAAA,CAAA;AAC5D;AAQO,SAAS,eAAA,GAA0B;AACxC,EAAA,MAAM,IAAK,UAAA,CAA0D,MAAA;AACrE,EAAA,IAAI,GAAG,UAAA,EAAY;AACjB,IAAA,OAAO,EAAE,UAAA,EAAW;AAAA,EACtB;AACA,EAAA,OAAO,KAAK,IAAA,CAAK,MAAA,GAAS,QAAA,CAAS,EAAE,EAAE,KAAA,CAAM,CAAC,CAAC,CAAA,EAAG,IAAA,CAAK,QAAO,CAAE,QAAA,CAAS,EAAE,CAAA,CAAE,KAAA,CAAM,CAAC,CAAC,CAAA,CAAA;AACvF;ACrBO,SAAS,cAAA,CAAe,EAAE,EAAA,EAAI,SAAA,EAAW,UAAS,EAAwB;AAC/E,EAAA,uBACEA,IAAC,KAAA,EAAA,EAAI,EAAA,EAAQ,WAAsB,WAAA,EAAU,QAAA,EAAS,aAAA,EAAY,MAAA,EAC/D,QAAA,EACH,CAAA;AAEJ","file":"chunk-
|
|
1
|
+
{"version":3,"sources":["../src/components/ActivityErrorBoundary.tsx","../src/components/_internal.ts","../src/components/shared/FeedbackRegion.tsx"],"names":["jsx"],"mappings":";;;;AAMA,SAAS,YAAA,GAAwB;AAC/B,EAAA,MAAM,CAAA,GAAI,UAAA;AACV,EAAA,OAAO,CAAA,CAAE,OAAA,EAAS,GAAA,EAAK,QAAA,KAAa,YAAA;AACtC;AAuBO,IAAM,qBAAA,GAAN,cAAoC,SAAA,CAGzC;AAAA,EACA,KAAA,GAAoC,EAAE,KAAA,EAAO,IAAA,EAAK;AAAA,EAElD,OAAO,yBAAyB,KAAA,EAA0C;AACxE,IAAA,OAAO,EAAE,KAAA,EAAM;AAAA,EACjB;AAAA,EAEA,MAAA,GAAoB;AAClB,IAAA,MAAM,EAAE,KAAA,EAAM,GAAI,IAAA,CAAK,KAAA;AACvB,IAAA,IAAI,CAAC,KAAA,EAAO;AACV,MAAA,OAAO,KAAK,KAAA,CAAM,QAAA;AAAA,IACpB;AAEA,IAAA,IAAI,CAAC,cAAa,EAAG;AACnB,MAAA,uBACE,IAAA,CAAC,KAAA,EAAA,EAAI,IAAA,EAAK,OAAA,EACR,QAAA,EAAA;AAAA,wBAAA,GAAA,CAAC,QAAA,EAAA,EAAS,QAAA,EAAA,CAAA,IAAA,CAAK,KAAA,CAAM,OAAA,IAAW,iBAAiB,cAAA,EAAe,CAAA;AAAA,wBAChE,GAAA,CAAC,KAAA,EAAA,EAAK,QAAA,EAAA,KAAA,CAAM,KAAA,IAAS,MAAM,OAAA,EAAQ;AAAA,OAAA,EACrC,CAAA;AAAA,IAEJ;AAEA,IAAA,uBACE,GAAA,CAAC,SAAI,IAAA,EAAK,OAAA,EACP,eAAK,KAAA,CAAM,aAAA,GAAA,CACP,KAAK,KAAA,CAAM,OAAA,IAAW,iBAAiB,mBAAA,CAAoB,IAAA,CAAK,MAAM,aAAa,CAAA,GAAA,CACnF,KAAK,KAAA,CAAM,OAAA,IAAW,iBAAiB,qBAAA,EAC9C,CAAA;AAAA,EAEJ;AACF;;;AC9DO,SAAS,aAAA,GAAyB;AACvC,EAAA,MAAM,CAAA,GAAI,UAAA;AACV,EAAA,OAAO,CAAA,CAAE,OAAA,EAAS,GAAA,EAAK,QAAA,KAAa,YAAA;AACtC;AAOO,IAAM,eAAA,GAA6B;AAAA,EACxC,UAAA,EAAY,OAAA;AAAA,EACZ,OAAA,EAAS,EAAE,QAAA,EAAU,6CAAA,EAA+C,MAAM,WAAA;AAC5E;AAGO,SAAS,YAAY,EAAA,EAAoB;AAC9C,EAAA,OAAO,CAAA,0BAAA,EAA6B,kBAAA,CAAmB,EAAE,CAAC,CAAA,CAAA;AAC5D;AAQO,SAAS,eAAA,GAA0B;AACxC,EAAA,MAAM,IAAK,UAAA,CAA0D,MAAA;AACrE,EAAA,IAAI,GAAG,UAAA,EAAY;AACjB,IAAA,OAAO,EAAE,UAAA,EAAW;AAAA,EACtB;AACA,EAAA,OAAO,KAAK,IAAA,CAAK,MAAA,GAAS,QAAA,CAAS,EAAE,EAAE,KAAA,CAAM,CAAC,CAAC,CAAA,EAAG,IAAA,CAAK,QAAO,CAAE,QAAA,CAAS,EAAE,CAAA,CAAE,KAAA,CAAM,CAAC,CAAC,CAAA,CAAA;AACvF;ACrBO,SAAS,cAAA,CAAe,EAAE,EAAA,EAAI,SAAA,EAAW,UAAS,EAAwB;AAC/E,EAAA,uBACEA,IAAC,KAAA,EAAA,EAAI,EAAA,EAAQ,WAAsB,WAAA,EAAU,QAAA,EAAS,aAAA,EAAY,MAAA,EAC/D,QAAA,EACH,CAAA;AAEJ","file":"chunk-DQERNAWI.js","sourcesContent":["'use client';\n\nimport { Component, type ReactNode } from 'react';\nimport type { LkStrings } from '../i18n/strings.js';\nimport { DEFAULT_STRINGS } from '../i18n/strings.js';\n\nfunction isProduction(): boolean {\n const g = globalThis as { process?: { env?: Record<string, string | undefined> } };\n return g.process?.env?.NODE_ENV === 'production';\n}\n\nexport interface ActivityErrorBoundaryProps {\n /** Shown in the production fallback when a render error occurs. */\n activityTitle?: string;\n /**\n * Chrome text. A class component cannot call `useLkStrings`, so the public\n * wrapper reads the hook and hands the resolved dictionary down.\n */\n strings?: LkStrings;\n children: ReactNode;\n}\n\ninterface ActivityErrorBoundaryState {\n error: Error | null;\n}\n\n/**\n * Catches render errors in an activity subtree. In development it renders the\n * full error stack to aid debugging; in production it renders an accessible,\n * generic fallback (optionally naming the activity) so a single broken\n * activity never crashes the host application.\n */\nexport class ActivityErrorBoundary extends Component<\n ActivityErrorBoundaryProps,\n ActivityErrorBoundaryState\n> {\n state: ActivityErrorBoundaryState = { error: null };\n\n static getDerivedStateFromError(error: Error): ActivityErrorBoundaryState {\n return { error };\n }\n\n render(): ReactNode {\n const { error } = this.state;\n if (!error) {\n return this.props.children;\n }\n\n if (!isProduction()) {\n return (\n <div role=\"alert\">\n <strong>{(this.props.strings ?? DEFAULT_STRINGS).activityFailed}</strong>\n <pre>{error.stack ?? error.message}</pre>\n </div>\n );\n }\n\n return (\n <div role=\"alert\">\n {this.props.activityTitle\n ? (this.props.strings ?? DEFAULT_STRINGS).activityFailedNamed(this.props.activityTitle)\n : (this.props.strings ?? DEFAULT_STRINGS).activityFailedUnnamed}\n </div>\n );\n }\n}\n","import type { XAPIActor } from '@intellectif/lk-core';\n\n/** Dev = anything other than an explicit production NODE_ENV (browser-safe). */\nexport function isDevelopment(): boolean {\n const g = globalThis as { process?: { env?: Record<string, string | undefined> } };\n return g.process?.env?.NODE_ENV !== 'production';\n}\n\n/**\n * Activity components cannot know the learner's identity (Req 3.1 fixes the\n * prop set). They emit a structurally-valid statement with this anonymous\n * actor; real identity is applied by the useXAPI/LRS layer (XAPIConfig.actor).\n */\nexport const ANONYMOUS_ACTOR: XAPIActor = {\n objectType: 'Agent',\n account: { homePage: 'https://github.com/intellectif/learning-kit', name: 'anonymous' },\n};\n\n/** `data.id` is not an IRI; xAPI object ids must be. Wrap it as a URN. */\nexport function objectIdFor(id: string): string {\n return `urn:learning-kit:activity:${encodeURIComponent(id)}`;\n}\n\n/**\n * Random per-mount session id for the shuffle seed. `crypto.randomUUID` is\n * unavailable outside secure contexts (plain-http LAN/staging hosts), so a\n * Math.random fallback keeps the component from crashing there — the seed\n * only randomises presentation order, so cryptographic strength is not needed.\n */\nexport function randomSessionId(): string {\n const c = (globalThis as { crypto?: { randomUUID?: () => string } }).crypto;\n if (c?.randomUUID) {\n return c.randomUUID();\n }\n return `s-${Math.random().toString(36).slice(2)}${Math.random().toString(36).slice(2)}`;\n}\n","import type { ReactNode } from 'react';\n\nexport interface FeedbackRegionProps {\n /** Identifier so activity inputs can reference this region via aria. */\n id?: string;\n className?: string;\n children?: ReactNode;\n}\n\n/**\n * A polite ARIA live region. Screen readers announce content injected here\n * (score, feedback, state changes) without moving focus. Pure/presentational\n * — safe to render in any environment.\n */\nexport function FeedbackRegion({ id, className, children }: FeedbackRegionProps) {\n return (\n <div id={id} className={className} aria-live=\"polite\" aria-atomic=\"true\">\n {children}\n </div>\n );\n}\n"]}
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
-
var
|
|
5
|
-
var
|
|
6
|
-
var
|
|
7
|
-
var
|
|
8
|
-
var
|
|
9
|
-
var
|
|
10
|
-
var
|
|
4
|
+
var chunk74QXBWCI_cjs = require('./chunk-74QXBWCI.cjs');
|
|
5
|
+
var chunkK7NMNOPI_cjs = require('./chunk-K7NMNOPI.cjs');
|
|
6
|
+
var chunkZ63JRG4M_cjs = require('./chunk-Z63JRG4M.cjs');
|
|
7
|
+
var chunk2GXSA322_cjs = require('./chunk-2GXSA322.cjs');
|
|
8
|
+
var chunkJ73TDY7J_cjs = require('./chunk-J73TDY7J.cjs');
|
|
9
|
+
var chunkUU2NV6SU_cjs = require('./chunk-UU2NV6SU.cjs');
|
|
10
|
+
var chunkK3EA547J_cjs = require('./chunk-K3EA547J.cjs');
|
|
11
|
+
var chunkMA7GXUWC_cjs = require('./chunk-MA7GXUWC.cjs');
|
|
11
12
|
var lkCore = require('@intellectif/lk-core');
|
|
12
13
|
var react = require('react');
|
|
13
14
|
var jsxRuntime = require('react/jsx-runtime');
|
|
@@ -66,7 +67,7 @@ function ActivitySequence({
|
|
|
66
67
|
strings
|
|
67
68
|
}) {
|
|
68
69
|
const sessionIdRef = react.useRef(null);
|
|
69
|
-
const s =
|
|
70
|
+
const s = chunkMA7GXUWC_cjs.useLkStrings(strings);
|
|
70
71
|
const mediaPlays = mediaBudget?.plays;
|
|
71
72
|
const shuffleEntries = shuffle === "entries";
|
|
72
73
|
const needsSeed = shuffleEntries || activities.some(
|
|
@@ -79,7 +80,7 @@ function ActivitySequence({
|
|
|
79
80
|
}
|
|
80
81
|
const seed = needsSeed ? (
|
|
81
82
|
// biome-ignore lint/suspicious/noAssignInExpressions: sanctioned lazy ref initialization
|
|
82
|
-
shuffleSeed ?? (sessionIdRef.current ??=
|
|
83
|
+
shuffleSeed ?? (sessionIdRef.current ??= chunkK3EA547J_cjs.randomSessionId())
|
|
83
84
|
) : void 0;
|
|
84
85
|
const slots = react.useMemo(
|
|
85
86
|
() => lkCore.flattenSequence(activities, { shuffleEntries, ...seed !== void 0 ? { seed } : {} }),
|
|
@@ -286,7 +287,7 @@ function ActivitySequence({
|
|
|
286
287
|
}
|
|
287
288
|
if (activity.type === "multiple-choice") {
|
|
288
289
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
289
|
-
|
|
290
|
+
chunkK7NMNOPI_cjs.MultipleChoice,
|
|
290
291
|
{
|
|
291
292
|
data: activity,
|
|
292
293
|
...shuffleSeed !== void 0 ? { shuffleSeed } : {},
|
|
@@ -295,11 +296,11 @@ function ActivitySequence({
|
|
|
295
296
|
);
|
|
296
297
|
}
|
|
297
298
|
if (activity.type === "fill-in-the-blanks") {
|
|
298
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
299
|
+
return /* @__PURE__ */ jsxRuntime.jsx(chunkZ63JRG4M_cjs.FillInTheBlanks, { data: activity, ...childProps });
|
|
299
300
|
}
|
|
300
301
|
if (activity.type === "gap-select") {
|
|
301
302
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
302
|
-
|
|
303
|
+
chunk2GXSA322_cjs.GapSelect,
|
|
303
304
|
{
|
|
304
305
|
data: activity,
|
|
305
306
|
...shuffleSeed !== void 0 ? { shuffleSeed } : {},
|
|
@@ -307,10 +308,13 @@ function ActivitySequence({
|
|
|
307
308
|
}
|
|
308
309
|
);
|
|
309
310
|
}
|
|
311
|
+
if (activity.type === "dictation") {
|
|
312
|
+
return /* @__PURE__ */ jsxRuntime.jsx(chunkJ73TDY7J_cjs.Dictation, { data: activity, ...childProps });
|
|
313
|
+
}
|
|
310
314
|
if (activity.type === "written-response") {
|
|
311
315
|
const { onComplete: _neverGrades, ...writtenResponseProps } = childProps;
|
|
312
316
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
313
|
-
|
|
317
|
+
chunkUU2NV6SU_cjs.WrittenResponse,
|
|
314
318
|
{
|
|
315
319
|
data: activity,
|
|
316
320
|
...writtenResponseProps,
|
|
@@ -332,7 +336,7 @@ function ActivitySequence({
|
|
|
332
336
|
className: "lk-seq-stimulus",
|
|
333
337
|
hidden: entryKey !== currentEntryKey,
|
|
334
338
|
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
335
|
-
|
|
339
|
+
chunk74QXBWCI_cjs.StimulusPanel,
|
|
336
340
|
{
|
|
337
341
|
stimulus: group.stimulus,
|
|
338
342
|
range: { first: first + 1, last: last + 1 },
|
|
@@ -388,5 +392,5 @@ function ActivitySequence({
|
|
|
388
392
|
}
|
|
389
393
|
|
|
390
394
|
exports.ActivitySequence = ActivitySequence;
|
|
391
|
-
//# sourceMappingURL=chunk-
|
|
392
|
-
//# sourceMappingURL=chunk-
|
|
395
|
+
//# sourceMappingURL=chunk-HT4FFZZQ.cjs.map
|
|
396
|
+
//# sourceMappingURL=chunk-HT4FFZZQ.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/ActivitySequence/ActivitySequence.tsx"],"names":["useRef","useEffect","jsx","useLkStrings","isItemGroup","randomSessionId","useMemo","flattenSequence","useState","current","resolvePlaybackPolicy","slotMediaKey","stimulusMediaKey","MultipleChoice","FillInTheBlanks","GapSelect","Dictation","WrittenResponse","jsxs","StimulusPanel"],"mappings":"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","file":"chunk-HT4FFZZQ.cjs","sourcesContent":["'use client';\n\nimport {\n type ActivityMedia,\n type ActivityResult,\n flattenSequence,\n type InteractionEvent,\n type ItemOutcome,\n isItemGroup,\n type LearnerResponse,\n resolvePlaybackPolicy,\n type SequenceEntry,\n type SequenceSlot,\n type SequenceSlotGroup,\n slotMediaKey,\n stimulusMediaKey,\n type ThemeTokens,\n} from '@intellectif/lk-core';\nimport { type ComponentType, useEffect, useMemo, useRef, useState } from 'react';\nimport { useLkStrings } from '../../i18n/LkIntlProvider.js';\nimport type { LkStringsOverride } from '../../i18n/strings.js';\nimport { randomSessionId } from '../_internal.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 { StimulusPanel } from '../StimulusPanel/index.js';\nimport type {\n ActivityProps,\n HtmlSanitizer,\n MediaBudgetBinding,\n RenderableActivity,\n RenderMode,\n SequenceMediaBudget,\n} from '../types.js';\nimport { WrittenResponse, type WrittenResponseSubmission } from '../WrittenResponse/index.js';\n\n/**\n * What one finished slot in a sequence produced. Two kinds, because two kinds\n * of activity exist: those the SDK scores at submit time, and those a grader\n * scores later. Collapsing them would mean inventing a score for ungraded\n * work — the exact defect the deferred outcome exists to prevent.\n */\nexport type SequenceItemOutcome =\n | {\n kind: 'scored';\n /** Presented position. */\n index: number;\n /** Slot identity from `flattenSequence` — stable under shuffling; the `slotId` to score with. */\n slotId: string;\n activityId: string;\n result: ActivityResult;\n }\n | {\n kind: 'submitted';\n index: number;\n slotId: string;\n activityId: string;\n /** Ungraded submission; the grade arrives asynchronously. */\n submission: WrittenResponseSubmission;\n }\n | {\n kind: 'restored';\n index: number;\n slotId: string;\n activityId: string;\n /**\n * The answer as restored, when one was. A slot can be submitted with no\n * stored response (a blank the learner committed), so this is optional —\n * inventing one would be worse than admitting it is absent.\n */\n response?: LearnerResponse;\n }\n | {\n kind: 'responded';\n index: number;\n slotId: string;\n activityId: string;\n /**\n * The raw answer, with no grade of any kind. This is what an `exam` slot\n * produces: the client is forbidden to score, so a response is all there\n * is until the server grades it.\n */\n response: LearnerResponse;\n };\n\n/**\n * A component that can render one activity inside a sequence. Register one per\n * activity `type` to put a consumer-defined type on screen — the React half of\n * the activity-type registry, matching `registerActivityType` in lk-core.\n */\nexport type ActivityRenderer = ComponentType<ActivityProps>;\n\nexport interface ActivitySequenceProps {\n /**\n * The ordered set of entries to present, one question at a time: loose\n * activities, and item groups — a shared passage, recording or image with\n * the questions that refer to it. A group's stimulus stays on screen\n * alongside each of its questions.\n *\n * Accepts `redact()` projections. For data straight off a server, pass it\n * through `asRenderableSequence()` — see that helper for why a cast is\n * unavoidable there — and set `renderMode=\"exam\"`.\n */\n activities: readonly SequenceEntry<RenderableActivity>[];\n /**\n * Renderers for activity types beyond the built-ins, keyed by `data.type`.\n * A key matching a built-in overrides it, so a consumer can replace the\n * bundled renderer without forking the sequencer.\n */\n renderers?: Readonly<Record<string, ActivityRenderer>>;\n /**\n * Called whenever an individual activity is scored at submit time — the\n * incremental persist hook.\n *\n * `slotId` is the identity to store the result against, NOT `index`.\n * `index` is where the question was PRESENTED, which is not stable: it moves\n * under shuffling, and it already differs from the slot identity whenever a\n * group is present (the second entry of a sequence is presented at index 1\n * but is slot `\"1.0\"` if it is a group's first question). `composeAssessmentScore`\n * scores by `slotId`, so persisting `index` cannot be reconciled with it.\n */\n onActivityComplete?: (result: ActivityResult, index: number, slotId: string) => void;\n /**\n * Called on every submit with the learner's raw response and the identity of\n * the slot that produced it — before any grading, in every render mode.\n *\n * **This is the only response channel an `exam` sequence has.** In `exam`\n * mode the components never grade, so `onActivityComplete` cannot fire and\n * `onComplete` never will; without this prop nothing a learner submitted\n * would reach the consumer at all. Persist `response` against `slotId`.\n */\n onSubmit?: (\n response: LearnerResponse,\n slot: { slotId: string; index: number; activityId: string },\n ) => void;\n /**\n * Called once every activity has been completed, with the scored results.\n *\n * Fires only when EVERY item produced a score. A set containing a\n * deferred-graded activity (a written response) can never satisfy that, so\n * use `onFinished` for mixed sets — it is the general completion signal and\n * reports both kinds of outcome. It also carries `slotId` per item, which\n * this callback's bare array cannot.\n */\n onComplete?: (results: ActivityResult[]) => void;\n /**\n * Called once every activity has been completed, whether it was scored at\n * submit time or submitted for later grading. Use this for any set that\n * mixes graded and deferred-graded activities.\n */\n onFinished?: (items: SequenceItemOutcome[]) => void;\n /** Forwarded to each activity. */\n onInteraction?: (event: InteractionEvent) => void;\n /** Forwarded to each activity. `exam` and `review` disable local scoring. */\n renderMode?: RenderMode;\n /**\n * Binds every budgeted recording in this sequence to a play ledger the\n * consumer persists.\n *\n * The SDK refuses a play; it does not remember one. `plays` seeds the counts\n * at mount (pass `restoreMediaPlayLedger(plan, stored).entries`), and\n * `onPlayConsumed` is how a play becomes durable — see\n * {@link SequenceMediaBudget}.\n */\n mediaBudget?: SequenceMediaBudget;\n /** Overrides the SDK's chrome text for this sequence. See {@link LkIntlProvider}. */\n strings?: LkStringsOverride;\n /**\n * `entries` shuffles the top-level entries; a group moves as one block, and\n * the order INSIDE a group follows the group's own `shuffle` setting.\n * Default `none`: authored order.\n */\n shuffle?: 'none' | 'entries';\n /**\n * Seed for every shuffle in this sequence (`shuffle: 'entries'` and any\n * group with `shuffle: 'within-group'`). Supply the attempt id, so the\n * server's `flattenSequence(entries, { seed })` derives the same order this\n * pager shows.\n *\n * **Required whenever this sequence shuffles** — `shuffle: 'entries'`, or any\n * group with `shuffle: 'within-group'` — in `exam` or `review` mode; omitting\n * it then throws. An order nobody can reproduce cannot be reconciled with a\n * recorded attempt, and failing at render is the only way that mistake\n * surfaces before a learner sits the paper. A sequence that does not shuffle\n * needs no seed in any mode.\n *\n * The guard covers an activity's own `data.shuffle` too, a group's items\n * included: `<MultipleChoice>` would otherwise fall back to a per-mount seed,\n * in every mode.\n *\n * In `practice` mode it stays optional: a random per-mount seed is used,\n * stable within the mount and deliberately not reproducible. That fallback is\n * also not SSR-safe (server and client would invent different orders and\n * hydration would mismatch), so supply a seed for any server-rendered\n * sequence regardless of mode.\n */\n shuffleSeed?: string;\n /**\n * Where to open. Defaults to the first question; pass a stored\n * `AttemptState.index` to reopen an interrupted attempt where it was left.\n * Read at mount only, like any `default*` prop.\n */\n defaultIndex?: number;\n /**\n * Fires whenever the learner moves. Persist it and the next resume reopens\n * on the right question — without it, the pager's position is the one piece\n * of an attempt a consumer cannot recover.\n */\n onIndexChange?: (index: number) => void;\n /**\n * Answers to restore, keyed by `slotId`, seeded into each slot as its\n * `defaultValue`. This is the other half of resume: `AttemptState.responses`\n * goes straight in.\n *\n * Read at mount only. To restore a different attempt, remount with a `key`.\n */\n responses?: Readonly<Record<string, LearnerResponse>>;\n /**\n * Slots the learner had already submitted, from `AttemptState.submittedSlotIds`.\n * Each is mounted already submitted, so a resumed paper does not reopen a\n * locked question as answerable — without this a learner can change and\n * re-submit work they had already committed.\n *\n * Read at mount only, alongside `responses`.\n */\n submittedSlotIds?: readonly string[];\n /**\n * Server-computed outcomes keyed by `slotId`, forwarded to each slot. In\n * `review` mode this is what marks correctness — the client never scores, so\n * without it a review render has nothing to show.\n */\n outcomes?: Readonly<Record<string, ItemOutcome>>;\n /** Renders author-supplied rich text; forwarded to the stimulus panel and every activity. */\n sanitizeHtml?: HtmlSanitizer;\n theme?: Partial<ThemeTokens>;\n /**\n * BCP 47 tag stamped as `lang` on this component's root. This is the\n * INTERFACE language — the SDK's own chrome renders inside that element — so\n * it should carry the same value you give `<LkIntlProvider locale>`. Passing\n * a different one re-declares the language of every SDK string in this\n * subtree without changing the words.\n *\n * It is NOT `data.locale`, which labels xAPI statements — and, on a\n * dictation, places the dictation's own words: `<Dictation>` puts it, and\n * the direction it names, on its title, hints, marks and solution. Other\n * authored content in another language belongs on `stimulus.locale`, which\n * `<StimulusPanel>` puts on the passage alone.\n */\n locale?: string;\n disabled?: boolean;\n}\n\n/** One stimulus panel to keep mounted: the group, and the presented span of its questions. */\ninterface StimulusMount {\n /** Authored entry position — unique per group in the sequence even if two groups share an id. */\n entryKey: string;\n group: SequenceSlotGroup;\n first: number;\n last: number;\n}\n\n/** The authored-entry prefix of a slot id: `\"3\"` for `\"3\"` and for `\"3.1\"`. */\nfunction entryKeyOf(slot: SequenceSlot<RenderableActivity>): string {\n return slot.slotId.split('.')[0] ?? slot.slotId;\n}\n\n/**\n * A pane that is kept MOUNTED but shown only when it is the current one, and\n * that stops any media it contains on the way out.\n *\n * `hidden` alone is not enough. It resolves to `display: none`, and CSS does\n * not touch playback: an `<audio>` or `<video>` inside a hidden subtree keeps\n * playing to the end. (The HTML spec pauses a media element when it is\n * REMOVED from the document — a different thing, and the thing this pager\n * deliberately does not do, because unmounting is what used to destroy the\n * learner's answers.) So a recording started for one question would carry on\n * underneath the next, which for a listening paper means the passage plays\n * while the learner is somewhere else entirely.\n *\n * Pausing preserves `currentTime`: coming back to a group resumes where the\n * learner left off. Nothing auto-plays on the way in — starting audio the\n * learner did not ask for is its own defect.\n *\n * `embed` media (a provider iframe) cannot be paused this way: controlling a\n * third-party player needs its own JS API, which the SDK has no reliable\n * access to because the author supplies the embed URL. Use `audio`/`video`\n * media for anything that must stop when the learner navigates.\n */\nfunction SequencePane({\n className,\n hidden,\n children,\n}: {\n className: string;\n hidden: boolean;\n children: React.ReactNode;\n}): React.JSX.Element {\n const ref = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n if (!hidden) {\n return;\n }\n const node = ref.current;\n if (node === null) {\n return;\n }\n for (const media of node.querySelectorAll('audio, video')) {\n const element = media as HTMLMediaElement;\n if (!element.paused) {\n element.pause();\n }\n }\n }, [hidden]);\n\n return (\n <div className={className} hidden={hidden} ref={ref}>\n {children}\n </div>\n );\n}\n\n/**\n * Presents a set of activities as an in-place pager: one question visible at a\n * time, navigated with Prev/Next (no page scrolling). Linear free navigation —\n * the learner may move back and forth; activities keep their own submit and\n * scoring. On navigation, focus moves to the question region and the position\n * is announced via an aria-live region.\n *\n * Item groups are flattened into consecutive questions; the group's stimulus\n * is mounted ONCE, beside the question region rather than inside it, and shown\n * alongside every question in the group — so a passage stays put, and a\n * recording keeps its position, as the learner moves between its questions.\n */\n/**\n * Whether an activity shuffles its own options, and therefore needs a seed of\n * its own. Read structurally: `shuffle` is a per-type content field, not part\n * of the sequence entry contract.\n */\nfunction shufflesItsOwnOptions(entry: unknown): boolean {\n const item = entry as { shuffle?: unknown; shuffleChoices?: unknown } | null;\n // Two content fields, one question: does this item deal an order of its own?\n // `shuffle` is MultipleChoice's; `shuffleChoices` is Gap Select's. A type\n // added without being named here shuffles unseeded in an exam — and one named\n // here but not in the seed guard's message sends its author looking for a\n // shuffle they never wrote.\n return item?.shuffle === true || item?.shuffleChoices === true;\n}\n\nexport function ActivitySequence({\n activities,\n renderers,\n onActivityComplete,\n onComplete,\n onFinished,\n onSubmit,\n onInteraction,\n renderMode = 'practice',\n shuffle,\n shuffleSeed,\n defaultIndex,\n onIndexChange,\n responses,\n submittedSlotIds,\n outcomes,\n sanitizeHtml,\n theme,\n locale,\n disabled,\n mediaBudget,\n strings,\n}: ActivitySequenceProps): React.JSX.Element {\n const sessionIdRef = useRef<string | null>(null);\n const s = useLkStrings(strings);\n const mediaPlays = mediaBudget?.plays;\n\n // The presented order comes from lk-core, never computed here: the server\n // that records an attempt calls the same function with the same seed.\n const shuffleEntries = shuffle === 'entries';\n // Every shuffle this sequence can produce, not just the ones it performs\n // itself. An ACTIVITY's own `data.shuffle` is the third source: MultipleChoice\n // falls back to a per-mount seed when none reaches it, so an unseeded item\n // shuffle used to render happily under `exam` and hand the learner a\n // different option order on every mount — exactly the unreproducible\n // arrangement this guard exists to refuse, arriving through the one door it\n // did not watch.\n const needsSeed =\n shuffleEntries ||\n activities.some((entry) =>\n isItemGroup(entry)\n ? entry.shuffle === 'within-group' || entry.items.some(shufflesItsOwnOptions)\n : shufflesItsOwnOptions(entry),\n );\n\n // An unreproducible order is a practice-only affordance. Under `exam` or\n // `review` the server has to be able to rebuild exactly what the learner\n // saw, so a missing seed is an error rather than something to paper over —\n // the same reason `flattenSequence` refuses to invent one.\n if (needsSeed && shuffleSeed === undefined && renderMode !== 'practice') {\n // Every door `needsSeed` watches, named: a reader whose only shuffle is an\n // item's own field must find that field here. And no single function\n // rebuilds all of these orders — `flattenSequence` deals entries and group\n // items, while an item deals its own options from the same seed — so the\n // message promises the seed, not a function.\n throw new Error(\n `ActivitySequence: renderMode \"${renderMode}\" requires a \\`shuffleSeed\\` when anything ` +\n 'in the sequence shuffles: shuffle=\"entries\", a group with shuffle: \"within-group\", or ' +\n \"an activity's own `data.shuffle` (multiple choice) or `data.shuffleChoices` (gap \" +\n 'select), including an activity inside a group. Pass the attempt id, so the server can ' +\n 'rebuild every order the learner saw from that one seed.',\n );\n }\n\n // Lazily created only when shuffling in practice without a caller seed (same\n // contract as MultipleChoice.shuffleSeed): a per-mount session seed, stable\n // across re-renders, never reproducible.\n const seed = needsSeed\n ? // biome-ignore lint/suspicious/noAssignInExpressions: sanctioned lazy ref initialization\n (shuffleSeed ?? (sessionIdRef.current ??= randomSessionId()))\n : undefined;\n const slots = useMemo(\n () => flattenSequence(activities, { shuffleEntries, ...(seed !== undefined ? { seed } : {}) }),\n [activities, shuffleEntries, seed],\n );\n\n // Clamped rather than trusted: a stored position from a paper that has since\n // lost its last question would open the pager on a slot that is not there,\n // which renders as an empty shell with no way forward.\n const [index, setIndex] = useState(() => {\n // NaN passes through Math.min/Math.max unchanged, so it survived every\n // clamp and indexed the slots with NaN — the pager then rendered the empty\n // shell this clamp exists to prevent. `NaN` is a `number`, so the prop type\n // gives no protection, and `Number(row.last_index)` on a NULL column\n // produces exactly that.\n const requested = Math.trunc(defaultIndex ?? 0);\n if (!Number.isFinite(requested)) {\n return 0;\n }\n return Math.min(Math.max(0, requested), Math.max(0, slots.length - 1));\n });\n // Outcomes live in a ref, not state: nothing renders from them, and a ref is\n // written SYNCHRONOUSLY. Reading them from a `useState` closure meant two\n // slots completing in the same tick both saw the pre-update array — the\n // second overwrote the first, so one answer vanished and completion never\n // fired. Every slot is mounted at once here, so same-tick completions are\n // reachable (a custom renderer that completes from a mount effect).\n const outcomesRef = useRef<(SequenceItemOutcome | null)[] | null>(null);\n if (outcomesRef.current === null) {\n // Slots the learner had already submitted are seeded as `restored`. They\n // will not submit again — they mount locked — so leaving them null meant\n // `onFinished` waited forever on outcomes that could never arrive, and a\n // resumed attempt could never signal completion however many of the\n // remaining questions the learner answered. Seeding fires no callback: the\n // attempt was already this far along before this mount existed.\n const submittedAtMount = new Set(submittedSlotIds ?? []);\n outcomesRef.current = slots.map((slot) =>\n submittedAtMount.has(slot.slotId)\n ? {\n kind: 'restored' as const,\n index: slot.index,\n slotId: slot.slotId,\n activityId: slot.activity.id,\n ...(responses !== undefined && Object.hasOwn(responses, slot.slotId)\n ? { response: responses[slot.slotId] as LearnerResponse }\n : {}),\n }\n : null,\n );\n }\n const regionRef = useRef<HTMLDivElement>(null);\n const mountedRef = useRef(false);\n\n // Reset pager position and outcomes when the presented SET changes (B8):\n // without this, results from the previous set leak into the new one and can\n // fire completion with a mixed old/new array. Keyed on the set's CONTENT\n // identity (ordered slot and activity ids), not the array reference — a\n // parent that re-creates a structurally identical array on every render\n // (inline literals, .map() in render) must not wipe in-progress answers.\n // JSON.stringify (not join) so ids containing the separator cannot collide.\n const setKey = JSON.stringify(slots.map((slot) => [slot.slotId, slot.activity.id]));\n const [prevSetKey, setPrevSetKey] = useState(setKey);\n // The set this component mounted with. `responses` seeds slots BY slotId,\n // and slot ids are short and repeat across papers (\"0\", \"1.0\"), so applying\n // them after the set changed would drop one paper's answers under another\n // paper's questions — a review modal stepping to the next attempt did\n // exactly that. Seeding stops at the first set change; to show a different\n // attempt, remount with a `key`, which is what \"read at mount only\" means.\n // `mediaBudget.resumeKey` participates so an invigilator can hand a play\n // back — re-seeding the budgets in place — without remounting the pager and\n // costing the learner their focus, their scroll position and an unsaved\n // answer.\n const seedKey = `${setKey}::${mediaBudget?.resumeKey ?? ''}`;\n const mountSetKeyRef = useRef(setKey);\n const mountSeedKeyRef = useRef(seedKey);\n const seedsApply = mountSetKeyRef.current === setKey;\n const mediaSeedsApply = mountSeedKeyRef.current === seedKey || seedsApply;\n if (mountSeedKeyRef.current !== seedKey) {\n mountSeedKeyRef.current = seedKey;\n }\n const submitted = useMemo(() => new Set(submittedSlotIds ?? []), [submittedSlotIds]);\n if (prevSetKey !== setKey) {\n setPrevSetKey(setKey);\n setIndex(0);\n outcomesRef.current = slots.map(() => null);\n }\n\n // After a navigation (not the initial mount) move focus to the question\n // region so screen-reader / keyboard users land on the new question.\n // biome-ignore lint/correctness/useExhaustiveDependencies: index is the intended trigger\n useEffect(() => {\n if (mountedRef.current) {\n regionRef.current?.focus();\n } else {\n mountedRef.current = true;\n }\n }, [index]);\n\n // Report where the pager actually IS, from an effect rather than from the\n // navigation handler. Two positions the learner never chose are still\n // positions a consumer has to persist: a `defaultIndex` that was clamped\n // (stored 99, showing 2) and the reset a set change performs. Reporting only\n // clicks left storage disagreeing with the screen, and the next autosave\n // then wrote a position the plan does not have.\n const reportedIndexRef = useRef<number | null>(null);\n useEffect(() => {\n if (reportedIndexRef.current === index) {\n return;\n }\n const first = reportedIndexRef.current === null;\n reportedIndexRef.current = index;\n // On mount, stay silent unless the requested position was corrected.\n if (first && index === Math.trunc(defaultIndex ?? 0)) {\n return;\n }\n onIndexChange?.(index);\n }, [index, defaultIndex, onIndexChange]);\n\n // One panel per group, spanning the presented positions of its questions.\n const stimulusMounts = useMemo<StimulusMount[]>(() => {\n const mounts = new Map<string, StimulusMount>();\n for (const slot of slots) {\n if (slot.group === undefined) {\n continue;\n }\n const entryKey = entryKeyOf(slot);\n const existing = mounts.get(entryKey);\n if (existing === undefined) {\n mounts.set(entryKey, { entryKey, group: slot.group, first: slot.index, last: slot.index });\n } else {\n existing.last = slot.index;\n }\n }\n return [...mounts.values()];\n }, [slots]);\n\n const total = slots.length;\n const current = slots[index];\n\n const go = (next: number): void => {\n setIndex(next);\n };\n\n /**\n * Records a finished slot and fires completion callbacks when the set is\n * done. A slot that already holds an outcome is FINAL: re-recording it would\n * fire completion a second time for one attempt, so a consumer persisting on\n * `onFinished` would write the same attempt twice.\n */\n const record = (outcome: SequenceItemOutcome): void => {\n const current = outcomesRef.current;\n if (current === null || current[outcome.index] != null) {\n return;\n }\n const next = current.slice();\n next[outcome.index] = outcome;\n // Commit before firing anything: a second record() in the same tick must\n // see this one.\n outcomesRef.current = next;\n\n if (outcome.kind === 'scored') {\n onActivityComplete?.(outcome.result, outcome.index, outcome.slotId);\n }\n\n if (!next.every((item): item is SequenceItemOutcome => item !== null)) {\n return;\n }\n onFinished?.(next);\n\n // `onComplete` predates deferred grading and promises ActivityResult[].\n // Fire it only when every slot really was scored, rather than inventing a\n // result for work nobody has graded.\n const scored: ActivityResult[] = [];\n for (const item of next) {\n if (item.kind !== 'scored') {\n return;\n }\n scored.push(item.result);\n }\n onComplete?.(scored);\n };\n\n if (!current) {\n return <div className=\"lk-seq\" />;\n }\n\n // Every budgeted recording this paper presents, and the key it is budgeted\n // under. Derived from the resolved policy, so the pager, the plan and the\n // schema cannot disagree about what is budgeted.\n const budgetedMedia = slots.flatMap((slot) => {\n const found: { key: string; url: string }[] = [];\n const own = (slot.activity as { media?: ActivityMedia }).media;\n if (own !== undefined && resolvePlaybackPolicy(own).maxPlays !== null) {\n found.push({ key: slotMediaKey(slot.slotId), url: own.url });\n }\n const stimulus = slot.group?.stimulus.media;\n if (stimulus !== undefined && resolvePlaybackPolicy(stimulus).maxPlays !== null) {\n found.push({ key: stimulusMediaKey(slot.slotId), url: stimulus.url });\n }\n return found;\n });\n\n const budgetEnforced = mediaBudget?.enforced ?? renderMode !== 'review';\n\n if (budgetedMedia.length > 0 && renderMode === 'exam' && budgetEnforced) {\n if (mediaBudget?.onPlayConsumed === undefined) {\n throw new Error(\n 'ActivitySequence: renderMode \"exam\" with media that declares `maxPlays` requires ' +\n '`mediaBudget.onPlayConsumed`. The SDK persists nothing, so without it the count lives ' +\n 'only in this mount: a refresh silently restores the full budget, and a paper that ' +\n 'grants unlimited plays while showing \"2 plays remaining\" is indistinguishable — to the ' +\n 'learner and to an appeal — from one that works. Budgeted media: ' +\n `${[...new Set(budgetedMedia.map((m) => m.key))].join(', ')}.`,\n );\n }\n const resuming =\n (responses !== undefined && Object.keys(responses).length > 0) ||\n (submittedSlotIds !== undefined && submittedSlotIds.length > 0);\n if (resuming && mediaBudget.plays === undefined) {\n throw new Error(\n 'ActivitySequence: this is a resumed attempt (`responses`/`submittedSlotIds` were ' +\n 'supplied) but `mediaBudget.plays` is absent. A resume that omits the ledger hands the ' +\n 'learner a fresh budget at exactly the moment it matters. Pass ' +\n '`restoreMediaPlayLedger(plan, stored).entries`, or `{}` to state explicitly that ' +\n 'nothing was spent.',\n );\n }\n }\n\n // One recording must not carry two budgets, in any mode — six questions each\n // holding the same clip at maxPlays 2 is twelve plays of one recording.\n const keysByUrl = new Map<string, Set<string>>();\n for (const { key, url } of budgetedMedia) {\n const keys = keysByUrl.get(url) ?? new Set<string>();\n keys.add(key);\n keysByUrl.set(url, keys);\n }\n for (const [url, keys] of keysByUrl) {\n if (keys.size > 1) {\n throw new Error(\n `ActivitySequence: media ${JSON.stringify(url)} is budgeted under ${keys.size} separate ` +\n `keys (${[...keys].join(', ')}), so one recording grants ${keys.size} × maxPlays. Put ` +\n \"the questions that share a recording in an item group — a group's stimulus is one \" +\n 'recording with one budget.',\n );\n }\n }\n\n /** The per-media binding handed to a component or to the stimulus panel. */\n const bindingFor = (\n key: string,\n slot: { slotId: string; index: number; activity: { id: string } },\n ): MediaBudgetBinding | undefined => {\n if (mediaBudget === undefined) {\n return undefined;\n }\n const entry =\n mediaSeedsApply && mediaPlays !== undefined && Object.hasOwn(mediaPlays, key)\n ? mediaPlays[key]\n : undefined;\n return {\n key,\n slotId: slot.slotId,\n index: slot.index,\n activityId: slot.activity.id,\n ...(entry !== undefined ? { entry } : {}),\n ...(mediaBudget.enforced !== undefined ? { enforced: mediaBudget.enforced } : {}),\n ...(mediaBudget.onPlayConsumed !== undefined\n ? { onPlayConsumed: mediaBudget.onPlayConsumed }\n : {}),\n ...(mediaBudget.onPlayRefunded !== undefined\n ? { onPlayRefunded: mediaBudget.onPlayRefunded }\n : {}),\n ...(mediaBudget.onPosition !== undefined ? { onPosition: mediaBudget.onPosition } : {}),\n ...(mediaBudget.strings !== undefined ? { strings: mediaBudget.strings } : {}),\n };\n };\n\n const forwarded = {\n ...(onInteraction ? { onInteraction } : {}),\n renderMode,\n ...(sanitizeHtml ? { sanitizeHtml } : {}),\n ...(strings !== undefined ? { strings } : {}),\n ...(theme ? { theme } : {}),\n ...(locale ? { locale } : {}),\n ...(disabled ? { disabled } : {}),\n };\n\n const renderSlot = (slot: SequenceSlot<RenderableActivity>): React.JSX.Element => {\n const activity = slot.activity;\n const { slotId, index: slotIndex } = slot;\n const activityId = activity.id;\n const restored =\n seedsApply && responses !== undefined && Object.hasOwn(responses, slotId)\n ? responses[slotId]\n : undefined;\n const slotOutcome =\n outcomes !== undefined && Object.hasOwn(outcomes, slotId) ? outcomes[slotId] : undefined;\n const slotBinding = bindingFor(slotMediaKey(slotId), slot);\n const childProps = {\n // `defaultValue`, not `value`: the learner must be able to keep editing\n // a restored answer. A controlled `value` would freeze it unless the\n // consumer also threaded state back, which is not what resume means.\n ...(restored !== undefined ? { defaultValue: restored } : {}),\n ...(seedsApply && submitted.has(slotId) ? { defaultSubmitted: true } : {}),\n ...(slotOutcome !== undefined ? { outcome: slotOutcome } : {}),\n onComplete: (result: ActivityResult) =>\n record({ kind: 'scored', index: slotIndex, slotId, activityId, result }),\n onSubmit: (response: LearnerResponse) => {\n onSubmit?.(response, { slotId, index: slotIndex, activityId });\n // Outside `practice` the components do not grade, so a raw response is\n // the ONLY outcome this slot will ever produce — record it, or the set\n // could never complete and `onFinished` would be dead in exam mode.\n // Written responses are excluded because they report something richer\n // through `onSubmitted` a moment later, and the first outcome wins.\n if (renderMode !== 'practice' && activity.type !== 'written-response') {\n record({ kind: 'responded', index: slotIndex, slotId, activityId, response });\n }\n },\n ...forwarded,\n ...(slotBinding !== undefined ? { mediaBudget: slotBinding } : {}),\n };\n\n const CustomRenderer = renderers?.[activity.type];\n if (CustomRenderer) {\n return <CustomRenderer data={activity} {...childProps} />;\n }\n if (activity.type === 'multiple-choice') {\n return (\n <MultipleChoice\n data={activity}\n // The sequence seed reaches the OPTION shuffle too. Without it a\n // resumed or reviewed item invented a fresh per-mount order, so the\n // learner saw their answers against a different arrangement than the\n // one they sat — the exact reproducibility the attempt id is for.\n {...(shuffleSeed !== undefined ? { shuffleSeed } : {})}\n {...childProps}\n />\n );\n }\n if (activity.type === 'fill-in-the-blanks') {\n return <FillInTheBlanks data={activity} {...childProps} />;\n }\n if (activity.type === 'gap-select') {\n return (\n <GapSelect\n data={activity}\n // The sequence seed reaches the per-gap choice shuffle too, for the\n // same reason it reaches MultipleChoice's options: a resumed or\n // reviewed item that dealt a fresh order would show the learner\n // their answers against an arrangement they never sat.\n {...(shuffleSeed !== undefined ? { shuffleSeed } : {})}\n {...childProps}\n />\n );\n }\n if (activity.type === 'dictation') {\n // Nothing to seed: a dictation shuffles nothing. Outside `practice` its\n // raw response is recorded through `onSubmit` above, like MC/FIB/GS.\n return <Dictation data={activity} {...childProps} />;\n }\n if (activity.type === 'written-response') {\n // Derived from the SAME bag the other two get, minus the one prop this\n // component genuinely does not have. Listing the props by hand is what\n // made this the branch left behind three separate times —\n // `defaultSubmitted`, the redacted-in-`practice` guard and the media\n // budget were each added to `childProps` and each silently missed here.\n // Subtracting from the shared bag cannot forget a prop that is added to\n // it; listing them can.\n const { onComplete: _neverGrades, ...writtenResponseProps } = childProps;\n return (\n <WrittenResponse\n data={activity}\n {...writtenResponseProps}\n onSubmitted={(submission) =>\n record({ kind: 'submitted', index: slotIndex, slotId, activityId, submission })\n }\n />\n );\n }\n return (\n <div className=\"lk-seq-unsupported\" role=\"note\">\n {s.unsupportedActivity}\n </div>\n );\n };\n\n const currentEntryKey = entryKeyOf(current);\n\n return (\n <div className=\"lk-seq\" lang={locale}>\n <div className=\"lk-seq-progress\" aria-live=\"polite\" role=\"status\">\n {s.questionProgress(index + 1, total)}\n </div>\n\n {/*\n Each group's stimulus is mounted ONCE and only hidden while the current\n question is outside its group. That — not re-rendering it per slot — is\n what makes the passage the same DOM node throughout, and what lets a\n recording keep its position between questions of one group.\n\n It sits BESIDE the question region, not inside it. Inside, every\n navigation dropped focus onto a container whose first content was the\n whole passage: the region promised \"Question 3 of 5\" and then began\n with the passage, keyboard users tabbed through the stimulus (audio\n controls included) before reaching the question, and the passage was a\n landmark nested inside another landmark. As a sibling it stays a\n landmark the learner can jump back to, while focus lands on the\n question that was actually navigated to.\n */}\n {stimulusMounts.map(({ entryKey, group, first, last }) => {\n // `setKey` leads for the same reason it does on a slot pane: entry keys\n // are short and repeat across papers, so a set change to a DIFFERENT\n // paper that happens to reuse an entry key would not remount this\n // panel — and a mount-only budget seed would leak into the next paper.\n const firstSlot = slots[first];\n const stimulusBinding =\n firstSlot === undefined\n ? undefined\n : bindingFor(stimulusMediaKey(firstSlot.slotId), firstSlot);\n return (\n <SequencePane\n className=\"lk-seq-stimulus\"\n hidden={entryKey !== currentEntryKey}\n key={`${setKey}::group-${entryKey}`}\n >\n <StimulusPanel\n stimulus={group.stimulus}\n range={{ first: first + 1, last: last + 1 }}\n renderMode={renderMode}\n {...(sanitizeHtml ? { sanitizeHtml } : {})}\n {...(locale ? { locale } : {})}\n {...(onInteraction ? { onInteraction } : {})}\n {...(disabled ? { disabled } : {})}\n {...(strings !== undefined ? { strings } : {})}\n {...(stimulusBinding !== undefined ? { mediaBudget: stimulusBinding } : {})}\n />\n </SequencePane>\n );\n })}\n\n <section\n className=\"lk-seq-question\"\n ref={regionRef}\n tabIndex={-1}\n aria-label={s.questionProgress(index + 1, total)}\n >\n {/*\n Every slot stays MOUNTED; only the current one is visible. Rendering\n just the current activity unmounted the others, so navigating back to\n an answered question showed it blank and re-answerable — the learner\n silently lost their answer and a second submit fired completion\n again. `hidden` removes the inactive slots from the accessibility\n tree and from tab order, so only the current question is reachable.\n */}\n {slots.map((slot) => {\n // The slot id (authored position) plus the activity id: the same\n // activity may legitimately fill two slots, and each must keep its\n // own answer state; and a different activity in the same slot must\n // remount rather than inherit the previous one's state.\n //\n // `setKey` leads, so a set change remounts every child. Without it a\n // pure REORDER changed `setKey` — clearing the outcomes — while each\n // child kept its key and therefore its `completed` state. Those\n // slots could not be answered again and no outcome would ever refill\n // them, so the sequence could never finish. A reset has to reset\n // both halves or neither. A parent re-creating a structurally\n // identical array leaves `setKey` untouched, so answers still\n // survive that.\n const slotKey = `${setKey}::slot-${slot.slotId}-${slot.activity.id}`;\n return (\n <SequencePane className=\"lk-seq-slot\" hidden={slot.index !== index} key={slotKey}>\n {renderSlot(slot)}\n </SequencePane>\n );\n })}\n </section>\n\n <div className=\"lk-seq-nav\">\n <button\n type=\"button\"\n className=\"lk-seq-prev\"\n onClick={() => go(index - 1)}\n disabled={index === 0}\n >\n {s.previous}\n </button>\n <button\n type=\"button\"\n className=\"lk-seq-next\"\n onClick={() => go(index + 1)}\n disabled={index === total - 1}\n >\n {s.next}\n </button>\n </div>\n </div>\n );\n}\n"]}
|