@intellectif/lk-react 2.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-32WWQOG3.js → chunk-3X7MYAFT.js} +99 -26
- package/dist/chunk-3X7MYAFT.js.map +1 -0
- package/dist/{chunk-KJNQLAB5.cjs → chunk-7BT6OXAR.cjs} +84 -17
- package/dist/chunk-7BT6OXAR.cjs.map +1 -0
- package/dist/{chunk-SH7BRP4M.cjs → chunk-STLRKR2I.cjs} +5 -5
- package/dist/{chunk-SH7BRP4M.cjs.map → chunk-STLRKR2I.cjs.map} +1 -1
- package/dist/{chunk-OS7MO2OW.js → chunk-UASXUWHH.js} +4 -4
- package/dist/{chunk-OS7MO2OW.js.map → chunk-UASXUWHH.js.map} +1 -1
- package/dist/{chunk-73XCWYKD.cjs → chunk-UXO6KYSZ.cjs} +97 -24
- package/dist/chunk-UXO6KYSZ.cjs.map +1 -0
- package/dist/{chunk-MKH5THDR.cjs → chunk-W6BH2YMZ.cjs} +119 -36
- package/dist/chunk-W6BH2YMZ.cjs.map +1 -0
- package/dist/{chunk-V5EN3ESV.js → chunk-XTJ22JAP.js} +85 -18
- package/dist/chunk-XTJ22JAP.js.map +1 -0
- package/dist/{chunk-G5ZEWJRA.js → chunk-ZVFWJTT4.js} +119 -36
- package/dist/chunk-ZVFWJTT4.js.map +1 -0
- package/dist/components/ActivitySequence.cjs +4 -4
- package/dist/components/ActivitySequence.js +3 -3
- package/dist/components/FillInTheBlanks.cjs +2 -2
- package/dist/components/FillInTheBlanks.d.cts +7 -1
- package/dist/components/FillInTheBlanks.d.ts +7 -1
- package/dist/components/FillInTheBlanks.js +1 -1
- package/dist/components/MultipleChoice.cjs +2 -2
- package/dist/components/MultipleChoice.d.cts +14 -3
- package/dist/components/MultipleChoice.d.ts +14 -3
- package/dist/components/MultipleChoice.js +1 -1
- package/dist/components/WrittenResponse.cjs +2 -2
- package/dist/components/WrittenResponse.d.cts +53 -5
- package/dist/components/WrittenResponse.d.ts +53 -5
- package/dist/components/WrittenResponse.js +1 -1
- package/dist/index.cjs +13 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +11 -4
- package/dist/index.js.map +1 -1
- package/dist/types-CNmjqD6M.d.cts +108 -0
- package/dist/types-CNmjqD6M.d.ts +108 -0
- package/package.json +3 -3
- package/dist/chunk-32WWQOG3.js.map +0 -1
- package/dist/chunk-73XCWYKD.cjs.map +0 -1
- package/dist/chunk-G5ZEWJRA.js.map +0 -1
- package/dist/chunk-KJNQLAB5.cjs.map +0 -1
- package/dist/chunk-MKH5THDR.cjs.map +0 -1
- package/dist/chunk-V5EN3ESV.js.map +0 -1
- package/dist/types-iknDq5tf.d.cts +0 -17
- package/dist/types-iknDq5tf.d.ts +0 -17
|
@@ -22,9 +22,44 @@ function seededShuffle(items, seed) {
|
|
|
22
22
|
}
|
|
23
23
|
return out;
|
|
24
24
|
}
|
|
25
|
+
function selectionOf(response) {
|
|
26
|
+
return response?.type === "multiple-choice" ? response.selectedOptionIds : [];
|
|
27
|
+
}
|
|
28
|
+
function isAnswerOption(detail, wasSelected) {
|
|
29
|
+
switch (detail.outcome) {
|
|
30
|
+
case "correct":
|
|
31
|
+
case "incorrect-omission":
|
|
32
|
+
return true;
|
|
33
|
+
case "incorrect":
|
|
34
|
+
case "correct-omission":
|
|
35
|
+
return false;
|
|
36
|
+
default:
|
|
37
|
+
return wasSelected ? detail.correct : !detail.correct;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function reviewAnnouncement(outcome) {
|
|
41
|
+
if (outcome === void 0) {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
if (outcome.status === "scored") {
|
|
45
|
+
const overall = outcome.feedback;
|
|
46
|
+
return `Score ${Math.round(outcome.score * 100)}%. ${outcome.passed ? "Passed." : "Not passed."}${overall ? ` ${overall}` : ""}`;
|
|
47
|
+
}
|
|
48
|
+
if (outcome.status === "deferred") {
|
|
49
|
+
return "Not graded yet.";
|
|
50
|
+
}
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
25
53
|
function MultipleChoice({
|
|
26
54
|
data,
|
|
27
55
|
onComplete,
|
|
56
|
+
onSubmit,
|
|
57
|
+
value,
|
|
58
|
+
defaultValue,
|
|
59
|
+
onChange,
|
|
60
|
+
renderMode = "practice",
|
|
61
|
+
outcome,
|
|
62
|
+
sanitizeHtml,
|
|
28
63
|
onInteraction,
|
|
29
64
|
theme,
|
|
30
65
|
locale,
|
|
@@ -35,15 +70,25 @@ function MultipleChoice({
|
|
|
35
70
|
if (!isDevelopment()) {
|
|
36
71
|
return null;
|
|
37
72
|
}
|
|
73
|
+
if (data.redacted === true) {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
38
76
|
const result = validateActivity("multiple-choice", data);
|
|
39
77
|
return result.success ? null : new ActivitySchemaError("multiple-choice", result.errors);
|
|
40
78
|
}, [data]);
|
|
41
79
|
const sessionIdRef = useRef(null);
|
|
42
80
|
const { state, start, complete, getTimeSpent, reset } = useActivityState();
|
|
43
|
-
const [
|
|
81
|
+
const [internalSelection, setInternalSelection] = useState(
|
|
82
|
+
() => selectionOf(defaultValue)
|
|
83
|
+
);
|
|
44
84
|
const [summary, setSummary] = useState(null);
|
|
85
|
+
const lastDataRef = useRef(data);
|
|
45
86
|
useEffect(() => {
|
|
46
|
-
|
|
87
|
+
if (lastDataRef.current === data) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
lastDataRef.current = data;
|
|
91
|
+
setInternalSelection([]);
|
|
47
92
|
setSummary(null);
|
|
48
93
|
reset();
|
|
49
94
|
}, [data, reset]);
|
|
@@ -54,15 +99,37 @@ function MultipleChoice({
|
|
|
54
99
|
const seedSource = shuffleSeed ?? (sessionIdRef.current ??= randomSessionId());
|
|
55
100
|
return seededShuffle(data.options, hashSeed(`${seedSource}:${data.id}`));
|
|
56
101
|
}, [data, shuffleSeed]);
|
|
102
|
+
const reviewDetails = useMemo(() => {
|
|
103
|
+
if (renderMode !== "review" || outcome === void 0 || outcome.status !== "scored") {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
return new Map(outcome.details.map((detail) => [detail.itemId, detail]));
|
|
107
|
+
}, [renderMode, outcome]);
|
|
57
108
|
if (devError) {
|
|
58
109
|
throw devError;
|
|
59
110
|
}
|
|
111
|
+
if (data.redacted === true && renderMode === "practice") {
|
|
112
|
+
throw new Error(
|
|
113
|
+
`Multiple Choice "${data.id}" received redacted activity data in renderMode "practice", which grades locally and has no answer key to grade against. Render redacted data with renderMode="exam" (server grades) or "review" (pass \`outcome\`).`
|
|
114
|
+
);
|
|
115
|
+
}
|
|
60
116
|
const isSingle = data.mode === "single";
|
|
117
|
+
const isExam = renderMode === "exam";
|
|
118
|
+
const isReview = renderMode === "review";
|
|
61
119
|
const submitted = state === "completed";
|
|
62
|
-
const inactive = disabled === true || submitted;
|
|
120
|
+
const inactive = disabled === true || submitted || isReview;
|
|
121
|
+
const isControlled = value !== void 0;
|
|
122
|
+
const selected = isControlled ? selectionOf(value) : internalSelection;
|
|
123
|
+
const reveal = isReview || renderMode === "practice" && submitted;
|
|
63
124
|
const fireInteraction = (type, payload) => {
|
|
64
125
|
onInteraction?.({ type, activityId: data.id, timestamp: Date.now(), payload });
|
|
65
126
|
};
|
|
127
|
+
const emitChange = (selectedOptionIds) => {
|
|
128
|
+
if (!isControlled) {
|
|
129
|
+
setInternalSelection(selectedOptionIds);
|
|
130
|
+
}
|
|
131
|
+
onChange?.({ type: "multiple-choice", selectedOptionIds });
|
|
132
|
+
};
|
|
66
133
|
const selectSingle = (optionId) => {
|
|
67
134
|
if (inactive) {
|
|
68
135
|
return;
|
|
@@ -70,7 +137,7 @@ function MultipleChoice({
|
|
|
70
137
|
if (state === "idle") {
|
|
71
138
|
start();
|
|
72
139
|
}
|
|
73
|
-
|
|
140
|
+
emitChange([optionId]);
|
|
74
141
|
fireInteraction("option-selected", { optionId });
|
|
75
142
|
};
|
|
76
143
|
const toggleMulti = (optionId, checked) => {
|
|
@@ -80,7 +147,7 @@ function MultipleChoice({
|
|
|
80
147
|
if (state === "idle") {
|
|
81
148
|
start();
|
|
82
149
|
}
|
|
83
|
-
|
|
150
|
+
emitChange(checked ? [...selected, optionId] : selected.filter((id) => id !== optionId));
|
|
84
151
|
fireInteraction(checked ? "option-selected" : "option-deselected", { optionId });
|
|
85
152
|
};
|
|
86
153
|
const handleSubmit = (event) => {
|
|
@@ -88,7 +155,17 @@ function MultipleChoice({
|
|
|
88
155
|
if (inactive) {
|
|
89
156
|
return;
|
|
90
157
|
}
|
|
91
|
-
const response = {
|
|
158
|
+
const response = {
|
|
159
|
+
type: "multiple-choice",
|
|
160
|
+
selectedOptionIds: selected
|
|
161
|
+
};
|
|
162
|
+
onSubmit?.(response);
|
|
163
|
+
if (isExam) {
|
|
164
|
+
complete();
|
|
165
|
+
setSummary("Answer submitted.");
|
|
166
|
+
fireInteraction("submitted", { selectedOptionIds: selected });
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
92
169
|
const scoringResult = score("multiple-choice", data, response);
|
|
93
170
|
complete();
|
|
94
171
|
const timeSpent = getTimeSpent();
|
|
@@ -111,7 +188,7 @@ function MultipleChoice({
|
|
|
111
188
|
timeSpentMs: timeSpent,
|
|
112
189
|
response: selected.join(",")
|
|
113
190
|
});
|
|
114
|
-
onComplete({
|
|
191
|
+
onComplete?.({
|
|
115
192
|
score: scoringResult.score,
|
|
116
193
|
maxScore: scoringResult.maxScore,
|
|
117
194
|
passed: scoringResult.passed,
|
|
@@ -128,41 +205,47 @@ function MultipleChoice({
|
|
|
128
205
|
});
|
|
129
206
|
};
|
|
130
207
|
const questionId = `${data.id}-question`;
|
|
208
|
+
const questionHtml = sanitizeHtml !== void 0 && data.questionHtml !== void 0 ? sanitizeHtml(data.questionHtml) : null;
|
|
131
209
|
const optionList = displayedOptions.map((option) => {
|
|
132
210
|
const checked = selected.includes(option.id);
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
),
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
);
|
|
211
|
+
let correctness;
|
|
212
|
+
if (isReview) {
|
|
213
|
+
const detail = reviewDetails?.get(option.id);
|
|
214
|
+
correctness = detail === void 0 ? void 0 : String(isAnswerOption(detail, checked));
|
|
215
|
+
} else if (reveal) {
|
|
216
|
+
correctness = String(option.isCorrect);
|
|
217
|
+
}
|
|
218
|
+
return /* @__PURE__ */ jsxs("label", { className: "lk-mc-option", "data-correct": correctness, children: [
|
|
219
|
+
/* @__PURE__ */ jsx(
|
|
220
|
+
"input",
|
|
221
|
+
{
|
|
222
|
+
type: isSingle ? "radio" : "checkbox",
|
|
223
|
+
name: isSingle ? `${data.id}-options` : void 0,
|
|
224
|
+
value: option.id,
|
|
225
|
+
checked,
|
|
226
|
+
disabled: inactive,
|
|
227
|
+
"aria-disabled": inactive || void 0,
|
|
228
|
+
onChange: (e) => isSingle ? selectSingle(option.id) : toggleMulti(option.id, e.target.checked)
|
|
229
|
+
}
|
|
230
|
+
),
|
|
231
|
+
/* @__PURE__ */ jsx("span", { children: option.text }),
|
|
232
|
+
reveal && option.feedback ? /* @__PURE__ */ jsx("span", { className: "lk-mc-option-feedback", role: "note", children: option.feedback }) : null
|
|
233
|
+
] }, option.id);
|
|
157
234
|
});
|
|
158
235
|
return /* @__PURE__ */ jsxs("div", { className: "lk-mc", lang: locale, style: theme, children: [
|
|
159
236
|
data.media ? /* @__PURE__ */ jsx(ActivityMedia, { media: data.media }) : null,
|
|
160
237
|
/* @__PURE__ */ jsx("form", { onSubmit: handleSubmit, children: /* @__PURE__ */ jsxs("fieldset", { disabled: inactive, children: [
|
|
161
|
-
/* @__PURE__ */ jsx("legend", { id: questionId, children: data.question })
|
|
238
|
+
questionHtml === null ? /* @__PURE__ */ jsx("legend", { id: questionId, children: data.question }) : /* @__PURE__ */ jsx(
|
|
239
|
+
"legend",
|
|
240
|
+
{
|
|
241
|
+
id: questionId,
|
|
242
|
+
dangerouslySetInnerHTML: { __html: questionHtml }
|
|
243
|
+
}
|
|
244
|
+
),
|
|
162
245
|
isSingle ? /* @__PURE__ */ jsx("div", { role: "radiogroup", "aria-labelledby": questionId, children: optionList }) : /* @__PURE__ */ jsx("div", { children: optionList }),
|
|
163
|
-
/* @__PURE__ */ jsx("button", { type: "submit", disabled: inactive, children: "Submit" })
|
|
246
|
+
isReview ? null : /* @__PURE__ */ jsx("button", { type: "submit", disabled: inactive, children: "Submit" })
|
|
164
247
|
] }) }),
|
|
165
|
-
/* @__PURE__ */ jsx(FeedbackRegion, { id: `${data.id}-feedback`, children: summary })
|
|
248
|
+
/* @__PURE__ */ jsx(FeedbackRegion, { id: `${data.id}-feedback`, children: isReview ? reviewAnnouncement(outcome) : summary })
|
|
166
249
|
] });
|
|
167
250
|
}
|
|
168
251
|
function MultipleChoice2(props) {
|
|
@@ -170,5 +253,5 @@ function MultipleChoice2(props) {
|
|
|
170
253
|
}
|
|
171
254
|
|
|
172
255
|
export { MultipleChoice2 as MultipleChoice };
|
|
173
|
-
//# sourceMappingURL=chunk-
|
|
174
|
-
//# sourceMappingURL=chunk-
|
|
256
|
+
//# sourceMappingURL=chunk-ZVFWJTT4.js.map
|
|
257
|
+
//# sourceMappingURL=chunk-ZVFWJTT4.js.map
|
|
@@ -0,0 +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-ZVFWJTT4.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 validateActivity,\n xAPIBuilder,\n} from '@intellectif/lk-core';\nimport { type CSSProperties, useEffect, useMemo, useRef, useState } from 'react';\nimport { useActivityState } from '../../hooks/useActivityState.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/** FNV-1a hash → 32-bit seed. */\nfunction hashSeed(input: string): number {\n let h = 2166136261 >>> 0;\n for (let i = 0; i < input.length; i += 1) {\n h ^= input.charCodeAt(i);\n h = Math.imul(h, 16777619) >>> 0;\n }\n return h;\n}\n\n/** Deterministic LCG Fisher–Yates; returns a permutation (no loss/dupe). */\nfunction seededShuffle<T>(items: readonly T[], seed: number): T[] {\n const out = [...items];\n let s = seed >>> 0 || 1;\n for (let i = out.length - 1; i > 0; i -= 1) {\n s = (Math.imul(s, 1664525) + 1013904223) >>> 0;\n const j = s % (i + 1);\n [out[i], out[j]] = [out[j] as T, out[i] as T];\n }\n return out;\n}\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/** Announcement for `review` mode, built only from the server-supplied outcome. */\nfunction reviewAnnouncement(outcome: ItemOutcome | undefined): string | null {\n if (outcome === undefined) {\n return null;\n }\n if (outcome.status === 'scored') {\n const overall = outcome.feedback;\n return `Score ${Math.round(outcome.score * 100)}%. ${\n outcome.passed ? 'Passed.' : 'Not passed.'\n }${overall ? ` ${overall}` : ''}`;\n }\n if (outcome.status === 'deferred') {\n // Never \"0%\": ungraded is not the same as wrong.\n return 'Not graded yet.';\n }\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 v1 behaviour).\n */\n shuffleSeed?: string;\n}\n\nexport function MultipleChoice({\n data,\n onComplete,\n onSubmit,\n value,\n defaultValue,\n onChange,\n renderMode = 'practice',\n outcome,\n sanitizeHtml,\n onInteraction,\n theme,\n locale,\n disabled,\n shuffleSeed,\n}: MultipleChoiceProps) {\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 // 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 // Clears to empty rather than back to `defaultValue`: carrying a stale\n // answer into a different question is the one failure mode a summative\n // component must never have.\n const lastDataRef = useRef(data);\n useEffect(() => {\n if (lastDataRef.current === data) {\n return;\n }\n lastDataRef.current = data;\n setInternalSelection([]);\n setSummary(null);\n reset();\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 return seededShuffle(data.options, hashSeed(`${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('Answer submitted.');\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 type: 'http://adlnet.gov/expapi/activities/cmi.interaction',\n interactionType: 'choice',\n correctResponsesPattern: [\n data.options\n .filter((option) => option.isCorrect)\n .map((option) => option.id)\n .join('[,]'),\n ],\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 `Answer submitted. Score ${Math.round(scoringResult.score * 100)}%. ${\n scoringResult.passed ? 'Passed.' : 'Not 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 return (\n <label key={option.id} 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 {reveal && option.feedback ? (\n <span className=\"lk-mc-option-feedback\" role=\"note\">\n {option.feedback}\n </span>\n ) : null}\n </label>\n );\n });\n\n return (\n <div className=\"lk-mc\" lang={locale} style={theme as CSSProperties | undefined}>\n {data.media ? <ActivityMedia media={data.media} /> : 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 Submit\n </button>\n )}\n </fieldset>\n </form>\n <FeedbackRegion id={`${data.id}-feedback`}>\n {isReview ? reviewAnnouncement(outcome) : summary}\n </FeedbackRegion>\n </div>\n );\n}\n","'use client';\n\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 return (\n <ActivityErrorBoundary activityTitle={props.data?.title}>\n <MultipleChoiceCore {...props} />\n </ActivityErrorBoundary>\n );\n}\n"]}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
4
|
-
require('../chunk-
|
|
5
|
-
require('../chunk-
|
|
3
|
+
var chunkSTLRKR2I_cjs = require('../chunk-STLRKR2I.cjs');
|
|
4
|
+
require('../chunk-W6BH2YMZ.cjs');
|
|
5
|
+
require('../chunk-UXO6KYSZ.cjs');
|
|
6
6
|
require('../chunk-QHOV2A6M.cjs');
|
|
7
7
|
require('../chunk-HD2AIQRD.cjs');
|
|
8
8
|
|
|
@@ -10,7 +10,7 @@ require('../chunk-HD2AIQRD.cjs');
|
|
|
10
10
|
|
|
11
11
|
Object.defineProperty(exports, "ActivitySequence", {
|
|
12
12
|
enumerable: true,
|
|
13
|
-
get: function () { return
|
|
13
|
+
get: function () { return chunkSTLRKR2I_cjs.ActivitySequence; }
|
|
14
14
|
});
|
|
15
15
|
//# sourceMappingURL=ActivitySequence.cjs.map
|
|
16
16
|
//# sourceMappingURL=ActivitySequence.cjs.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
export { ActivitySequence } from '../chunk-
|
|
2
|
-
import '../chunk-
|
|
3
|
-
import '../chunk-
|
|
1
|
+
export { ActivitySequence } from '../chunk-UASXUWHH.js';
|
|
2
|
+
import '../chunk-ZVFWJTT4.js';
|
|
3
|
+
import '../chunk-3X7MYAFT.js';
|
|
4
4
|
import '../chunk-UIGIYYGY.js';
|
|
5
5
|
import '../chunk-5HVUKXF3.js';
|
|
6
6
|
//# sourceMappingURL=ActivitySequence.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunkUXO6KYSZ_cjs = require('../chunk-UXO6KYSZ.cjs');
|
|
4
4
|
require('../chunk-QHOV2A6M.cjs');
|
|
5
5
|
require('../chunk-HD2AIQRD.cjs');
|
|
6
6
|
|
|
@@ -8,7 +8,7 @@ require('../chunk-HD2AIQRD.cjs');
|
|
|
8
8
|
|
|
9
9
|
Object.defineProperty(exports, "FillInTheBlanks", {
|
|
10
10
|
enumerable: true,
|
|
11
|
-
get: function () { return
|
|
11
|
+
get: function () { return chunkUXO6KYSZ_cjs.FillInTheBlanks; }
|
|
12
12
|
});
|
|
13
13
|
//# sourceMappingURL=FillInTheBlanks.cjs.map
|
|
14
14
|
//# sourceMappingURL=FillInTheBlanks.cjs.map
|
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import { FillInTheBlanksData } from '@intellectif/lk-core';
|
|
3
|
-
import { A as ActivityProps } from '../types-
|
|
3
|
+
import { A as ActivityProps } from '../types-CNmjqD6M.cjs';
|
|
4
4
|
|
|
5
5
|
interface FillInTheBlanksProps extends ActivityProps<FillInTheBlanksData> {
|
|
6
6
|
/**
|
|
7
7
|
* After submission, replace each incorrectly-answered blank with its first
|
|
8
8
|
* accepted answer, styled distinctly (Req 5.7). Component-specific prop: it
|
|
9
9
|
* is presentation, not activity data, and is outside the fixed Req-3.1 set.
|
|
10
|
+
*
|
|
11
|
+
* Ignored in `exam` mode — revealing the key is precisely what an exam must
|
|
12
|
+
* not do, and on a `redact()` projection there is no key to reveal. In
|
|
13
|
+
* `review` mode it applies only to blanks the supplied `outcome` marks
|
|
14
|
+
* incorrect, and only when the caller passed data that still carries the key
|
|
15
|
+
* (e.g. `redact(data, { reveal: 'after-submit' })`).
|
|
10
16
|
*/
|
|
11
17
|
showCorrectAnswers?: boolean;
|
|
12
18
|
}
|
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import { FillInTheBlanksData } from '@intellectif/lk-core';
|
|
3
|
-
import { A as ActivityProps } from '../types-
|
|
3
|
+
import { A as ActivityProps } from '../types-CNmjqD6M.js';
|
|
4
4
|
|
|
5
5
|
interface FillInTheBlanksProps extends ActivityProps<FillInTheBlanksData> {
|
|
6
6
|
/**
|
|
7
7
|
* After submission, replace each incorrectly-answered blank with its first
|
|
8
8
|
* accepted answer, styled distinctly (Req 5.7). Component-specific prop: it
|
|
9
9
|
* is presentation, not activity data, and is outside the fixed Req-3.1 set.
|
|
10
|
+
*
|
|
11
|
+
* Ignored in `exam` mode — revealing the key is precisely what an exam must
|
|
12
|
+
* not do, and on a `redact()` projection there is no key to reveal. In
|
|
13
|
+
* `review` mode it applies only to blanks the supplied `outcome` marks
|
|
14
|
+
* incorrect, and only when the caller passed data that still carries the key
|
|
15
|
+
* (e.g. `redact(data, { reveal: 'after-submit' })`).
|
|
10
16
|
*/
|
|
11
17
|
showCorrectAnswers?: boolean;
|
|
12
18
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunkW6BH2YMZ_cjs = require('../chunk-W6BH2YMZ.cjs');
|
|
4
4
|
require('../chunk-QHOV2A6M.cjs');
|
|
5
5
|
require('../chunk-HD2AIQRD.cjs');
|
|
6
6
|
|
|
@@ -8,7 +8,7 @@ require('../chunk-HD2AIQRD.cjs');
|
|
|
8
8
|
|
|
9
9
|
Object.defineProperty(exports, "MultipleChoice", {
|
|
10
10
|
enumerable: true,
|
|
11
|
-
get: function () { return
|
|
11
|
+
get: function () { return chunkW6BH2YMZ_cjs.MultipleChoice; }
|
|
12
12
|
});
|
|
13
13
|
//# sourceMappingURL=MultipleChoice.cjs.map
|
|
14
14
|
//# sourceMappingURL=MultipleChoice.cjs.map
|
|
@@ -1,12 +1,23 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import { MultipleChoiceData } from '@intellectif/lk-core';
|
|
3
|
-
import { A as ActivityProps } from '../types-
|
|
3
|
+
import { A as ActivityProps } from '../types-CNmjqD6M.cjs';
|
|
4
|
+
|
|
5
|
+
interface MultipleChoiceProps extends ActivityProps<MultipleChoiceData> {
|
|
6
|
+
/**
|
|
7
|
+
* Seed for the deterministic option shuffle. Supply one (e.g. the attempt
|
|
8
|
+
* id) to make the shuffled order reproducible server-side, stable across
|
|
9
|
+
* page reloads, and identical between SSR and hydration. When absent, a
|
|
10
|
+
* random per-mount session seed is used (order stable within the mount
|
|
11
|
+
* only — the v1 behaviour).
|
|
12
|
+
*/
|
|
13
|
+
shuffleSeed?: string;
|
|
14
|
+
}
|
|
4
15
|
|
|
5
16
|
/**
|
|
6
17
|
* Public Multiple Choice activity: the core component wrapped in
|
|
7
18
|
* `ActivityErrorBoundary` so a render failure (incl. the dev schema-validation
|
|
8
19
|
* throw) degrades to an accessible fallback instead of crashing the host.
|
|
9
20
|
*/
|
|
10
|
-
declare function MultipleChoice(props:
|
|
21
|
+
declare function MultipleChoice(props: MultipleChoiceProps): react_jsx_runtime.JSX.Element;
|
|
11
22
|
|
|
12
|
-
export {
|
|
23
|
+
export { MultipleChoice, type MultipleChoiceProps };
|
|
@@ -1,12 +1,23 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import { MultipleChoiceData } from '@intellectif/lk-core';
|
|
3
|
-
import { A as ActivityProps } from '../types-
|
|
3
|
+
import { A as ActivityProps } from '../types-CNmjqD6M.js';
|
|
4
|
+
|
|
5
|
+
interface MultipleChoiceProps extends ActivityProps<MultipleChoiceData> {
|
|
6
|
+
/**
|
|
7
|
+
* Seed for the deterministic option shuffle. Supply one (e.g. the attempt
|
|
8
|
+
* id) to make the shuffled order reproducible server-side, stable across
|
|
9
|
+
* page reloads, and identical between SSR and hydration. When absent, a
|
|
10
|
+
* random per-mount session seed is used (order stable within the mount
|
|
11
|
+
* only — the v1 behaviour).
|
|
12
|
+
*/
|
|
13
|
+
shuffleSeed?: string;
|
|
14
|
+
}
|
|
4
15
|
|
|
5
16
|
/**
|
|
6
17
|
* Public Multiple Choice activity: the core component wrapped in
|
|
7
18
|
* `ActivityErrorBoundary` so a render failure (incl. the dev schema-validation
|
|
8
19
|
* throw) degrades to an accessible fallback instead of crashing the host.
|
|
9
20
|
*/
|
|
10
|
-
declare function MultipleChoice(props:
|
|
21
|
+
declare function MultipleChoice(props: MultipleChoiceProps): react_jsx_runtime.JSX.Element;
|
|
11
22
|
|
|
12
|
-
export {
|
|
23
|
+
export { MultipleChoice, type MultipleChoiceProps };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunk7BT6OXAR_cjs = require('../chunk-7BT6OXAR.cjs');
|
|
4
4
|
require('../chunk-QHOV2A6M.cjs');
|
|
5
5
|
require('../chunk-HD2AIQRD.cjs');
|
|
6
6
|
|
|
@@ -8,7 +8,7 @@ require('../chunk-HD2AIQRD.cjs');
|
|
|
8
8
|
|
|
9
9
|
Object.defineProperty(exports, "WrittenResponse", {
|
|
10
10
|
enumerable: true,
|
|
11
|
-
get: function () { return
|
|
11
|
+
get: function () { return chunk7BT6OXAR_cjs.WrittenResponse; }
|
|
12
12
|
});
|
|
13
13
|
//# sourceMappingURL=WrittenResponse.cjs.map
|
|
14
14
|
//# sourceMappingURL=WrittenResponse.cjs.map
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
-
import { WrittenResponseData, XAPIStatement, InteractionEvent, ThemeTokens } from '@intellectif/lk-core';
|
|
2
|
+
import { WrittenResponseData, XAPIStatement, LearnerResponse, ItemOutcome, InteractionEvent, ThemeTokens } from '@intellectif/lk-core';
|
|
3
|
+
import { a as Renderable, R as RenderMode, H as HtmlSanitizer } from '../types-CNmjqD6M.cjs';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Payload delivered when the learner submits a written response. Grading is
|
|
@@ -25,11 +26,58 @@ interface WrittenResponseSubmission {
|
|
|
25
26
|
* {@link WrittenResponseSubmission} (`onSubmitted`), not a scored
|
|
26
27
|
* `ActivityResult` (`onComplete`) — deviation from Req 22.8 recorded in the
|
|
27
28
|
* roadmap (§3.1): fabricating a score of 0 for ungraded work is the exact
|
|
28
|
-
* bug this type exists to fix.
|
|
29
|
+
* bug this type exists to fix. Everything else — controlled value, render
|
|
30
|
+
* mode, server outcome, rich text — follows the shared `ActivityProps`
|
|
31
|
+
* contract verbatim.
|
|
29
32
|
*/
|
|
30
33
|
interface WrittenResponseProps {
|
|
31
|
-
|
|
32
|
-
|
|
34
|
+
/** Activity content. Accepts a `redact()` projection in `exam` mode. */
|
|
35
|
+
data: Renderable<WrittenResponseData>;
|
|
36
|
+
/**
|
|
37
|
+
* Called on submit with the ungraded submission (text, recomputed word
|
|
38
|
+
* count, bounds flag, timing, xAPI statement). Fires in `practice` and
|
|
39
|
+
* `exam` mode, after `onSubmit`; never in `review`.
|
|
40
|
+
*
|
|
41
|
+
* Optional since v0.4 so a `review`-mode render (which has no submit
|
|
42
|
+
* control) and an exam runner that only wants the raw response through
|
|
43
|
+
* `onSubmit` do not have to pass a no-op. Supply it in `practice`/`exam`
|
|
44
|
+
* unless `onSubmit` already captures everything you persist.
|
|
45
|
+
*/
|
|
46
|
+
onSubmitted?: (submission: WrittenResponseSubmission) => void;
|
|
47
|
+
/**
|
|
48
|
+
* Called on submit with the raw learner response and no grade, BEFORE
|
|
49
|
+
* `onSubmitted`. This is the only submit callback an exam runner needs:
|
|
50
|
+
* the server grades the text and returns an {@link ItemOutcome} later.
|
|
51
|
+
*/
|
|
52
|
+
onSubmit?: (response: LearnerResponse) => void;
|
|
53
|
+
/**
|
|
54
|
+
* Controlled value: the learner's current response. When present the
|
|
55
|
+
* textarea renders from it (never from internal state) and every keystroke
|
|
56
|
+
* goes out through `onChange` — restore an in-progress attempt, autosave a
|
|
57
|
+
* delta, or drive a review render with it.
|
|
58
|
+
*/
|
|
59
|
+
value?: LearnerResponse;
|
|
60
|
+
/** Initial response for an uncontrolled component (ignored when `value` is set). */
|
|
61
|
+
defaultValue?: LearnerResponse;
|
|
62
|
+
/** Fires on every change to the learner's response. Required for a controlled component. */
|
|
63
|
+
onChange?: (response: LearnerResponse) => void;
|
|
64
|
+
/**
|
|
65
|
+
* Presentation mode, default `practice`. `practice` and `exam` are nearly
|
|
66
|
+
* identical here — a written response is never graded client-side, so
|
|
67
|
+
* there is no local score to withhold; `exam` additionally never calls
|
|
68
|
+
* `evaluate()` and renders happily from a `redact()` projection.
|
|
69
|
+
* `review` is read-only and shows the server's `outcome`.
|
|
70
|
+
*/
|
|
71
|
+
renderMode?: RenderMode;
|
|
72
|
+
/**
|
|
73
|
+
* Server-computed outcome, rendered by `review` mode: `deferred` shows a
|
|
74
|
+
* "not graded yet" affordance, `scored` shows the returned grade and
|
|
75
|
+
* feedback. Ignored in other modes — this component never manufactures an
|
|
76
|
+
* outcome of its own.
|
|
77
|
+
*/
|
|
78
|
+
outcome?: ItemOutcome;
|
|
79
|
+
/** Renders `data.promptHtml` when provided. See `HtmlSanitizer`. */
|
|
80
|
+
sanitizeHtml?: HtmlSanitizer;
|
|
33
81
|
onInteraction?: (event: InteractionEvent) => void;
|
|
34
82
|
/** Per-instance token overrides, applied as inline CSS vars on the root. */
|
|
35
83
|
theme?: Partial<ThemeTokens>;
|
|
@@ -45,4 +93,4 @@ interface WrittenResponseProps {
|
|
|
45
93
|
*/
|
|
46
94
|
declare function WrittenResponse(props: WrittenResponseProps): react_jsx_runtime.JSX.Element;
|
|
47
95
|
|
|
48
|
-
export { WrittenResponse, type WrittenResponseProps, type WrittenResponseSubmission };
|
|
96
|
+
export { HtmlSanitizer, RenderMode, Renderable, WrittenResponse, type WrittenResponseProps, type WrittenResponseSubmission };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
-
import { WrittenResponseData, XAPIStatement, InteractionEvent, ThemeTokens } from '@intellectif/lk-core';
|
|
2
|
+
import { WrittenResponseData, XAPIStatement, LearnerResponse, ItemOutcome, InteractionEvent, ThemeTokens } from '@intellectif/lk-core';
|
|
3
|
+
import { a as Renderable, R as RenderMode, H as HtmlSanitizer } from '../types-CNmjqD6M.js';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Payload delivered when the learner submits a written response. Grading is
|
|
@@ -25,11 +26,58 @@ interface WrittenResponseSubmission {
|
|
|
25
26
|
* {@link WrittenResponseSubmission} (`onSubmitted`), not a scored
|
|
26
27
|
* `ActivityResult` (`onComplete`) — deviation from Req 22.8 recorded in the
|
|
27
28
|
* roadmap (§3.1): fabricating a score of 0 for ungraded work is the exact
|
|
28
|
-
* bug this type exists to fix.
|
|
29
|
+
* bug this type exists to fix. Everything else — controlled value, render
|
|
30
|
+
* mode, server outcome, rich text — follows the shared `ActivityProps`
|
|
31
|
+
* contract verbatim.
|
|
29
32
|
*/
|
|
30
33
|
interface WrittenResponseProps {
|
|
31
|
-
|
|
32
|
-
|
|
34
|
+
/** Activity content. Accepts a `redact()` projection in `exam` mode. */
|
|
35
|
+
data: Renderable<WrittenResponseData>;
|
|
36
|
+
/**
|
|
37
|
+
* Called on submit with the ungraded submission (text, recomputed word
|
|
38
|
+
* count, bounds flag, timing, xAPI statement). Fires in `practice` and
|
|
39
|
+
* `exam` mode, after `onSubmit`; never in `review`.
|
|
40
|
+
*
|
|
41
|
+
* Optional since v0.4 so a `review`-mode render (which has no submit
|
|
42
|
+
* control) and an exam runner that only wants the raw response through
|
|
43
|
+
* `onSubmit` do not have to pass a no-op. Supply it in `practice`/`exam`
|
|
44
|
+
* unless `onSubmit` already captures everything you persist.
|
|
45
|
+
*/
|
|
46
|
+
onSubmitted?: (submission: WrittenResponseSubmission) => void;
|
|
47
|
+
/**
|
|
48
|
+
* Called on submit with the raw learner response and no grade, BEFORE
|
|
49
|
+
* `onSubmitted`. This is the only submit callback an exam runner needs:
|
|
50
|
+
* the server grades the text and returns an {@link ItemOutcome} later.
|
|
51
|
+
*/
|
|
52
|
+
onSubmit?: (response: LearnerResponse) => void;
|
|
53
|
+
/**
|
|
54
|
+
* Controlled value: the learner's current response. When present the
|
|
55
|
+
* textarea renders from it (never from internal state) and every keystroke
|
|
56
|
+
* goes out through `onChange` — restore an in-progress attempt, autosave a
|
|
57
|
+
* delta, or drive a review render with it.
|
|
58
|
+
*/
|
|
59
|
+
value?: LearnerResponse;
|
|
60
|
+
/** Initial response for an uncontrolled component (ignored when `value` is set). */
|
|
61
|
+
defaultValue?: LearnerResponse;
|
|
62
|
+
/** Fires on every change to the learner's response. Required for a controlled component. */
|
|
63
|
+
onChange?: (response: LearnerResponse) => void;
|
|
64
|
+
/**
|
|
65
|
+
* Presentation mode, default `practice`. `practice` and `exam` are nearly
|
|
66
|
+
* identical here — a written response is never graded client-side, so
|
|
67
|
+
* there is no local score to withhold; `exam` additionally never calls
|
|
68
|
+
* `evaluate()` and renders happily from a `redact()` projection.
|
|
69
|
+
* `review` is read-only and shows the server's `outcome`.
|
|
70
|
+
*/
|
|
71
|
+
renderMode?: RenderMode;
|
|
72
|
+
/**
|
|
73
|
+
* Server-computed outcome, rendered by `review` mode: `deferred` shows a
|
|
74
|
+
* "not graded yet" affordance, `scored` shows the returned grade and
|
|
75
|
+
* feedback. Ignored in other modes — this component never manufactures an
|
|
76
|
+
* outcome of its own.
|
|
77
|
+
*/
|
|
78
|
+
outcome?: ItemOutcome;
|
|
79
|
+
/** Renders `data.promptHtml` when provided. See `HtmlSanitizer`. */
|
|
80
|
+
sanitizeHtml?: HtmlSanitizer;
|
|
33
81
|
onInteraction?: (event: InteractionEvent) => void;
|
|
34
82
|
/** Per-instance token overrides, applied as inline CSS vars on the root. */
|
|
35
83
|
theme?: Partial<ThemeTokens>;
|
|
@@ -45,4 +93,4 @@ interface WrittenResponseProps {
|
|
|
45
93
|
*/
|
|
46
94
|
declare function WrittenResponse(props: WrittenResponseProps): react_jsx_runtime.JSX.Element;
|
|
47
95
|
|
|
48
|
-
export { WrittenResponse, type WrittenResponseProps, type WrittenResponseSubmission };
|
|
96
|
+
export { HtmlSanitizer, RenderMode, Renderable, WrittenResponse, type WrittenResponseProps, type WrittenResponseSubmission };
|