@intellectif/lk-react 7.0.1 → 8.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 +106 -0
- package/README.md +8 -3
- package/dist/{WrittenResponse-zWT-Go1g.d.cts → WrittenResponse-BJr9gBpY.d.cts} +3 -1
- package/dist/{WrittenResponse-BJL4dGsR.d.ts → WrittenResponse-Du5CZnwn.d.ts} +3 -1
- package/dist/{chunk-UFWF3BKA.cjs → chunk-2VLKSNYJ.cjs} +13 -24
- package/dist/chunk-2VLKSNYJ.cjs.map +1 -0
- package/dist/{chunk-PPJ47THY.cjs → chunk-4AHZBTBF.cjs} +27 -29
- package/dist/chunk-4AHZBTBF.cjs.map +1 -0
- package/dist/{chunk-PGEG7UZJ.cjs → chunk-4XPKKNJQ.cjs} +5 -5
- package/dist/chunk-4XPKKNJQ.cjs.map +1 -0
- package/dist/{chunk-52A5UNCH.js → chunk-7OHPBLPH.js} +13 -24
- package/dist/chunk-7OHPBLPH.js.map +1 -0
- package/dist/{chunk-ABHJW4LC.cjs → chunk-7VIXWVFN.cjs} +11 -14
- package/dist/chunk-7VIXWVFN.cjs.map +1 -0
- package/dist/chunk-AHEV52U7.cjs +122 -0
- package/dist/chunk-AHEV52U7.cjs.map +1 -0
- package/dist/{chunk-7YLY3G3E.js → chunk-BLQBFI7Z.js} +23 -25
- package/dist/chunk-BLQBFI7Z.js.map +1 -0
- package/dist/{chunk-VHULVDPG.js → chunk-EQ5BZTLV.js} +29 -14
- package/dist/chunk-EQ5BZTLV.js.map +1 -0
- package/dist/{chunk-6F4X2NKL.js → chunk-ESWHIC4Z.js} +5 -5
- package/dist/chunk-ESWHIC4Z.js.map +1 -0
- package/dist/chunk-G3QPOYDR.cjs +198 -0
- package/dist/chunk-G3QPOYDR.cjs.map +1 -0
- package/dist/{chunk-XS2SZINH.js → chunk-HEJPEJW7.js} +10 -13
- package/dist/chunk-HEJPEJW7.js.map +1 -0
- package/dist/{chunk-C3HR55JD.js → chunk-MMFHKCWF.js} +24 -13
- package/dist/chunk-MMFHKCWF.js.map +1 -0
- package/dist/{chunk-3VQV7ICQ.js → chunk-OI4IKH2Z.js} +18 -18
- package/dist/chunk-OI4IKH2Z.js.map +1 -0
- package/dist/{chunk-XGQKID2P.cjs → chunk-SGIUPGDA.cjs} +29 -18
- package/dist/chunk-SGIUPGDA.cjs.map +1 -0
- package/dist/chunk-T4CKODCJ.js +115 -0
- package/dist/chunk-T4CKODCJ.js.map +1 -0
- package/dist/{chunk-6DSA75TA.cjs → chunk-VVZ7SAE7.cjs} +35 -20
- package/dist/chunk-VVZ7SAE7.cjs.map +1 -0
- package/dist/chunk-WJTCIW5R.js +196 -0
- package/dist/chunk-WJTCIW5R.js.map +1 -0
- package/dist/{chunk-FB6XKQFV.cjs → chunk-ZDB3SLYR.cjs} +23 -23
- package/dist/chunk-ZDB3SLYR.cjs.map +1 -0
- package/dist/components/ActivityPreview.cjs +20 -0
- package/dist/components/ActivityPreview.cjs.map +1 -0
- package/dist/components/ActivityPreview.d.cts +70 -0
- package/dist/components/ActivityPreview.d.ts +70 -0
- package/dist/components/ActivityPreview.js +11 -0
- package/dist/components/ActivityPreview.js.map +1 -0
- package/dist/components/ActivitySequence.cjs +9 -8
- package/dist/components/ActivitySequence.d.cts +19 -6
- package/dist/components/ActivitySequence.d.ts +19 -6
- package/dist/components/ActivitySequence.js +8 -7
- package/dist/components/FillInTheBlanks.cjs +5 -4
- package/dist/components/FillInTheBlanks.d.cts +1 -1
- package/dist/components/FillInTheBlanks.d.ts +1 -1
- package/dist/components/FillInTheBlanks.js +4 -3
- package/dist/components/MultipleChoice.cjs +5 -4
- package/dist/components/MultipleChoice.d.cts +1 -1
- package/dist/components/MultipleChoice.d.ts +1 -1
- package/dist/components/MultipleChoice.js +4 -3
- package/dist/components/StimulusPanel.cjs +4 -3
- package/dist/components/StimulusPanel.d.cts +4 -2
- package/dist/components/StimulusPanel.d.ts +4 -2
- package/dist/components/StimulusPanel.js +3 -2
- package/dist/components/WrittenResponse.cjs +5 -4
- package/dist/components/WrittenResponse.d.cts +3 -3
- package/dist/components/WrittenResponse.d.ts +3 -3
- package/dist/components/WrittenResponse.js +4 -3
- package/dist/i18n/LkIntlProvider.cjs +33 -0
- package/dist/i18n/LkIntlProvider.cjs.map +1 -0
- package/dist/i18n/LkIntlProvider.d.cts +71 -0
- package/dist/i18n/LkIntlProvider.d.ts +71 -0
- package/dist/i18n/LkIntlProvider.js +4 -0
- package/dist/i18n/LkIntlProvider.js.map +1 -0
- package/dist/index.cjs +47 -17
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -3
- package/dist/index.d.ts +5 -3
- package/dist/index.js +10 -8
- package/dist/index.js.map +1 -1
- package/dist/{types-eXX4hzSj.d.ts → strings-vQDqESTS.d.cts} +175 -11
- package/dist/{types-eXX4hzSj.d.cts → strings-vQDqESTS.d.ts} +175 -11
- package/dist/theme/skin.css +4 -4
- package/package.json +23 -3
- package/dist/chunk-3VQV7ICQ.js.map +0 -1
- package/dist/chunk-52A5UNCH.js.map +0 -1
- package/dist/chunk-6DSA75TA.cjs.map +0 -1
- package/dist/chunk-6F4X2NKL.js.map +0 -1
- package/dist/chunk-7YLY3G3E.js.map +0 -1
- package/dist/chunk-ABHJW4LC.cjs.map +0 -1
- package/dist/chunk-C3HR55JD.js.map +0 -1
- package/dist/chunk-FB6XKQFV.cjs.map +0 -1
- package/dist/chunk-PGEG7UZJ.cjs.map +0 -1
- package/dist/chunk-PPJ47THY.cjs.map +0 -1
- package/dist/chunk-UFWF3BKA.cjs.map +0 -1
- package/dist/chunk-VHULVDPG.js.map +0 -1
- package/dist/chunk-XGQKID2P.cjs.map +0 -1
- package/dist/chunk-XS2SZINH.js.map +0 -1
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
-
var
|
|
5
|
-
var
|
|
4
|
+
var chunk4XPKKNJQ_cjs = require('./chunk-4XPKKNJQ.cjs');
|
|
5
|
+
var chunk2VLKSNYJ_cjs = require('./chunk-2VLKSNYJ.cjs');
|
|
6
|
+
var chunkAHEV52U7_cjs = require('./chunk-AHEV52U7.cjs');
|
|
6
7
|
var chunkXOR4MPEN_cjs = require('./chunk-XOR4MPEN.cjs');
|
|
7
8
|
var lkCore = require('@intellectif/lk-core');
|
|
8
9
|
var react = require('react');
|
|
@@ -57,6 +58,7 @@ function FillInTheBlanks({
|
|
|
57
58
|
sanitizeHtml,
|
|
58
59
|
mediaBudget,
|
|
59
60
|
mediaStrings,
|
|
61
|
+
strings,
|
|
60
62
|
theme,
|
|
61
63
|
locale,
|
|
62
64
|
disabled,
|
|
@@ -64,8 +66,9 @@ function FillInTheBlanks({
|
|
|
64
66
|
}) {
|
|
65
67
|
const isExam = renderMode === "exam";
|
|
66
68
|
const isReview = renderMode === "review";
|
|
69
|
+
const s = chunkAHEV52U7_cjs.useLkStrings(strings);
|
|
67
70
|
const devError = react.useMemo(() => {
|
|
68
|
-
if (!
|
|
71
|
+
if (!chunk4XPKKNJQ_cjs.isDevelopment()) {
|
|
69
72
|
return null;
|
|
70
73
|
}
|
|
71
74
|
if (data.redacted === true) {
|
|
@@ -113,7 +116,7 @@ function FillInTheBlanks({
|
|
|
113
116
|
reset(defaultSubmittedRef.current === true ? "completed" : "idle");
|
|
114
117
|
}, [data, reset]);
|
|
115
118
|
react.useEffect(() => {
|
|
116
|
-
if (
|
|
119
|
+
if (chunk4XPKKNJQ_cjs.isDevelopment() && sanitizeHtml !== void 0 && data.passageHtml !== void 0) {
|
|
117
120
|
console.warn(
|
|
118
121
|
"[lk-react] FillInTheBlanks ignores `passageHtml`: the passage hosts the answer inputs, so rich HTML cannot be split at {{placeholders}} without voiding the sanitiser. Rendering the plain `passage` instead."
|
|
119
122
|
);
|
|
@@ -178,7 +181,7 @@ function FillInTheBlanks({
|
|
|
178
181
|
onSubmit?.(response);
|
|
179
182
|
if (isExam) {
|
|
180
183
|
complete();
|
|
181
|
-
setSummary(
|
|
184
|
+
setSummary(s.answerSubmitted);
|
|
182
185
|
fireInteraction("submitted", { answers });
|
|
183
186
|
return;
|
|
184
187
|
}
|
|
@@ -186,9 +189,9 @@ function FillInTheBlanks({
|
|
|
186
189
|
complete();
|
|
187
190
|
const timeSpent = getTimeSpent();
|
|
188
191
|
const xapiStatement = lkCore.xAPIBuilder.buildAnsweredStatement({
|
|
189
|
-
actor:
|
|
192
|
+
actor: chunk4XPKKNJQ_cjs.ANONYMOUS_ACTOR,
|
|
190
193
|
object: {
|
|
191
|
-
id:
|
|
194
|
+
id: chunk4XPKKNJQ_cjs.objectIdFor(data.id),
|
|
192
195
|
name: { [data.locale ?? "en-US"]: data.title },
|
|
193
196
|
...lkCore.xapiDefinitionFor(data)
|
|
194
197
|
},
|
|
@@ -206,7 +209,10 @@ function FillInTheBlanks({
|
|
|
206
209
|
});
|
|
207
210
|
const overall = scoringResult.feedback;
|
|
208
211
|
setSummary(
|
|
209
|
-
|
|
212
|
+
`${s.answerSubmitted} ${s.scoreAnnouncement(
|
|
213
|
+
Math.round(scoringResult.score * 100),
|
|
214
|
+
scoringResult.passed
|
|
215
|
+
)}${overall ? ` ${overall}` : ""}`
|
|
210
216
|
);
|
|
211
217
|
fireInteraction("submitted", { answers, score: scoringResult.score });
|
|
212
218
|
};
|
|
@@ -224,10 +230,17 @@ function FillInTheBlanks({
|
|
|
224
230
|
return null;
|
|
225
231
|
}
|
|
226
232
|
if (outcome.status === "scored") {
|
|
227
|
-
return
|
|
233
|
+
return `${s.scoreAnnouncement(Math.round(outcome.score * 100), outcome.passed)}${outcome.feedback ? ` ${outcome.feedback}` : ""}`;
|
|
228
234
|
}
|
|
229
|
-
|
|
230
|
-
|
|
235
|
+
if (outcome.status === "deferred") {
|
|
236
|
+
return s.notGradedYet;
|
|
237
|
+
}
|
|
238
|
+
if (outcome.status === "graded") {
|
|
239
|
+
const percent = outcome.maxScore > 0 ? Math.round(outcome.score / outcome.maxScore * 100) : Math.round(outcome.score * 100);
|
|
240
|
+
return `${s.scoreAnnouncement(percent, outcome.passed)}${outcome.feedback ? ` ${outcome.feedback}` : ""}`;
|
|
241
|
+
}
|
|
242
|
+
return s.noGradeAvailable;
|
|
243
|
+
}, [isReview, outcome, s]);
|
|
231
244
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
232
245
|
"form",
|
|
233
246
|
{
|
|
@@ -239,12 +252,13 @@ function FillInTheBlanks({
|
|
|
239
252
|
onSubmit: handleSubmit,
|
|
240
253
|
children: [
|
|
241
254
|
data.media ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
242
|
-
|
|
255
|
+
chunk2VLKSNYJ_cjs.ActivityMedia,
|
|
243
256
|
{
|
|
244
257
|
media: data.media,
|
|
245
258
|
renderMode,
|
|
246
259
|
...mediaBudget !== void 0 ? { mediaBudget } : {},
|
|
247
260
|
...mediaStrings !== void 0 ? { mediaStrings } : {},
|
|
261
|
+
...strings !== void 0 ? { strings } : {},
|
|
248
262
|
...onInteraction !== void 0 ? { onInteraction } : {},
|
|
249
263
|
...locale !== void 0 ? { locale } : {}
|
|
250
264
|
}
|
|
@@ -271,7 +285,7 @@ function FillInTheBlanks({
|
|
|
271
285
|
"input",
|
|
272
286
|
{
|
|
273
287
|
type: "text",
|
|
274
|
-
"aria-label":
|
|
288
|
+
"aria-label": s.blankLabel(seg.ordinal),
|
|
275
289
|
"aria-describedby": blank.hint ? hintId : void 0,
|
|
276
290
|
value: answers[seg.id] ?? "",
|
|
277
291
|
disabled: inactive,
|
|
@@ -288,7 +302,7 @@ function FillInTheBlanks({
|
|
|
288
302
|
className: "lk-fib-hint-btn",
|
|
289
303
|
"aria-controls": hintId,
|
|
290
304
|
"aria-expanded": revealed.has(seg.id),
|
|
291
|
-
"aria-label": revealed.has(seg.id) ?
|
|
305
|
+
"aria-label": revealed.has(seg.id) ? s.hideHint : s.showHint,
|
|
292
306
|
disabled: inactive,
|
|
293
307
|
onClick: () => toggleHint(seg.id),
|
|
294
308
|
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -324,7 +338,7 @@ function FillInTheBlanks({
|
|
|
324
338
|
) : null
|
|
325
339
|
] }, seg.id);
|
|
326
340
|
}) }),
|
|
327
|
-
isReview ? null : /* @__PURE__ */ jsxRuntime.jsx("button", { type: "submit", disabled: inactive, children: isExam ?
|
|
341
|
+
isReview ? null : /* @__PURE__ */ jsxRuntime.jsx("button", { type: "submit", disabled: inactive, children: isExam ? s.submitAnswers : s.checkAnswers })
|
|
328
342
|
] }),
|
|
329
343
|
revealing && data.blanks.some((b) => b.feedback) ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
330
344
|
"button",
|
|
@@ -333,18 +347,19 @@ function FillInTheBlanks({
|
|
|
333
347
|
className: "lk-fib-feedback-toggle",
|
|
334
348
|
"aria-expanded": !feedbackHidden,
|
|
335
349
|
onClick: () => setFeedbackHidden((h) => !h),
|
|
336
|
-
children: feedbackHidden ?
|
|
350
|
+
children: feedbackHidden ? s.showFeedback : s.hideFeedback
|
|
337
351
|
}
|
|
338
352
|
) : null,
|
|
339
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
353
|
+
/* @__PURE__ */ jsxRuntime.jsx(chunk4XPKKNJQ_cjs.FeedbackRegion, { id: `${data.id}-feedback`, children: isReview ? reviewSummary : summary })
|
|
340
354
|
]
|
|
341
355
|
}
|
|
342
356
|
);
|
|
343
357
|
}
|
|
344
358
|
function FillInTheBlanks2(props) {
|
|
345
|
-
|
|
359
|
+
const strings = chunkAHEV52U7_cjs.useLkStrings(props.strings);
|
|
360
|
+
return /* @__PURE__ */ jsxRuntime.jsx(chunk4XPKKNJQ_cjs.ActivityErrorBoundary, { activityTitle: props.data?.title, strings, children: /* @__PURE__ */ jsxRuntime.jsx(FillInTheBlanks, { ...props }) });
|
|
346
361
|
}
|
|
347
362
|
|
|
348
363
|
exports.FillInTheBlanks = FillInTheBlanks2;
|
|
349
|
-
//# sourceMappingURL=chunk-
|
|
350
|
-
//# sourceMappingURL=chunk-
|
|
364
|
+
//# sourceMappingURL=chunk-VVZ7SAE7.cjs.map
|
|
365
|
+
//# sourceMappingURL=chunk-VVZ7SAE7.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/FillInTheBlanks/FillInTheBlanks.tsx","../src/components/FillInTheBlanks/index.tsx"],"names":["useLkStrings","useMemo","isDevelopment","assertRedacted","result","validateActivity","ActivitySchemaError","useActivityState","useState","useRef","useEffect","score","xAPIBuilder","ANONYMOUS_ACTOR","objectIdFor","xapiDefinitionFor","jsxs","jsx","ActivityMedia","Fragment","FeedbackRegion","FillInTheBlanks","ActivityErrorBoundary"],"mappings":"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","file":"chunk-VVZ7SAE7.cjs","sourcesContent":["'use client';\n\nimport {\n ActivitySchemaError,\n assertRedacted,\n type FillInTheBlanksData,\n type FillInTheBlanksLearnerResponse,\n type LearnerResponse,\n type ScoringDetail,\n type ScoringResult,\n score,\n validateActivity,\n xAPIBuilder,\n xapiDefinitionFor,\n} from '@intellectif/lk-core';\nimport { type CSSProperties, useEffect, useMemo, useRef, useState } from 'react';\nimport { useActivityState } from '../../hooks/useActivityState.js';\nimport { useLkStrings } from '../../i18n/LkIntlProvider.js';\nimport { ANONYMOUS_ACTOR, isDevelopment, objectIdFor } from '../_internal.js';\nimport { ActivityMedia } from '../shared/ActivityMedia.js';\nimport { FeedbackRegion } from '../shared/FeedbackRegion.js';\nimport type { ActivityProps } from '../types.js';\n\nexport interface FillInTheBlanksProps extends ActivityProps<FillInTheBlanksData> {\n /**\n * After submission, replace each incorrectly-answered blank with its first\n * accepted answer, styled distinctly (Req 5.7). Component-specific prop: it\n * is presentation, not activity data, and is outside the fixed Req-3.1 set.\n *\n * Ignored in `exam` mode — revealing the key is precisely what an exam must\n * not do, and on a `redact()` projection there is no key to reveal. In\n * `review` mode it applies only to blanks the supplied `outcome` marks\n * incorrect, and only when the caller passed data that still carries the key\n * (e.g. `redact(data, { reveal: 'after-submit' })`).\n */\n showCorrectAnswers?: boolean;\n}\n\nconst PLACEHOLDER = /\\{\\{\\s*([^{}]+?)\\s*\\}\\}/g;\n\ntype Segment = { kind: 'text'; value: string } | { kind: 'blank'; id: string; ordinal: number };\n\nfunction parsePassage(passage: string): Segment[] {\n const segments: Segment[] = [];\n const re = new RegExp(PLACEHOLDER);\n let last = 0;\n let ordinal = 0;\n let match = re.exec(passage);\n while (match !== null) {\n if (match.index > last) {\n segments.push({ kind: 'text', value: passage.slice(last, match.index) });\n }\n ordinal += 1;\n segments.push({ kind: 'blank', id: match[1] as string, ordinal });\n last = match.index + match[0].length;\n match = re.exec(passage);\n }\n if (last < passage.length) {\n segments.push({ kind: 'text', value: passage.slice(last) });\n }\n return segments;\n}\n\n/** Shared empty map: keeps the \"no response supplied\" path allocation-free. */\nconst NO_ANSWERS: Record<string, string> = {};\n\n/**\n * Reads the answer map out of a `LearnerResponse`. A response of another\n * activity type is treated as \"no answers\" rather than throwing: a controlled\n * host that has not yet swapped its state on an activity change would\n * otherwise crash the attempt.\n */\nfunction answersOf(response: LearnerResponse | undefined): Record<string, string> {\n return response !== undefined && response.type === 'fill-in-the-blanks'\n ? response.answers\n : NO_ANSWERS;\n}\n\n/**\n * Per-item correctness keyed by blank id. Prefers the unambiguous `outcome`\n * field (always written by the built-in scorers since lk-core 0.3.0) and\n * falls back to the deprecated `correct` boolean so a 0.2-era\n * consumer-constructed `ItemOutcome` still marks correctly in `review`.\n */\nfunction correctnessByItem(details: readonly ScoringDetail[]): Map<string, boolean> {\n const map = new Map<string, boolean>();\n for (const detail of details) {\n map.set(\n detail.itemId,\n detail.outcome !== undefined ? detail.outcome === 'correct' : detail.correct,\n );\n }\n return map;\n}\n\nexport function FillInTheBlanks({\n data,\n onComplete,\n onSubmit,\n onChange,\n onInteraction,\n value,\n defaultValue,\n defaultSubmitted,\n renderMode = 'practice',\n outcome,\n sanitizeHtml,\n mediaBudget,\n mediaStrings,\n strings,\n theme,\n locale,\n disabled,\n showCorrectAnswers,\n}: FillInTheBlanksProps) {\n const isExam = renderMode === 'exam';\n const isReview = renderMode === 'review';\n\n // Dev-only boundary validation (Req 2.3); throws in render so the wrapping\n // ActivityErrorBoundary catches it. Re-runs only when data changes.\n const s = useLkStrings(strings);\n\n const devError = useMemo(() => {\n if (!isDevelopment()) {\n return null;\n }\n if (data.redacted === true) {\n // A redact() projection cannot satisfy the full content schema — the\n // answer key is gone by design. Validate it against the STRICT redacted\n // schema instead, which additionally proves the payload is learner-safe.\n try {\n assertRedacted(data);\n return null;\n } catch (error) {\n return error instanceof Error ? error : new Error(String(error));\n }\n }\n const result = validateActivity('fill-in-the-blanks', data);\n return result.success ? null : new ActivitySchemaError('fill-in-the-blanks', result.errors);\n }, [data]);\n\n const { state, start, complete, getTimeSpent, reset } = useActivityState(\n defaultSubmitted === true ? 'completed' : 'idle',\n );\n // Controlled when `value` is supplied: the answers rendered are ALWAYS the\n // caller's, and internal state is never read. Uncontrolled otherwise,\n // seeded from `defaultValue`.\n const isControlled = value !== undefined;\n const [internalAnswers, setInternalAnswers] = useState<Record<string, string>>(() =>\n answersOf(defaultValue),\n );\n const [revealed, setRevealed] = useState<Set<string>>(() => new Set());\n const [result, setResult] = useState<ScoringResult | null>(null);\n const [summary, setSummary] = useState<string | null>(null);\n const [feedbackHidden, setFeedbackHidden] = useState(false);\n\n const answers = isControlled ? answersOf(value) : internalAnswers;\n\n // `defaultValue` is read only by the reset effect, and it is commonly an\n // inline object literal — putting it in that effect's deps would re-seed\n // (and wipe) the learner's typing on every render. Mirror it into a ref in\n // its own effect, which commits before the reset effect below.\n const defaultValueRef = useRef(defaultValue);\n useEffect(() => {\n defaultValueRef.current = defaultValue;\n }, [defaultValue]);\n\n // Mirror `defaultSubmitted` so the reset below returns to the SEEDED state.\n // Resetting unconditionally to idle unlocked an item the learner had already\n // committed — which made `defaultSubmitted` a no-op here, since this effect\n // runs right after the first paint.\n const defaultSubmittedRef = useRef(defaultSubmitted);\n useEffect(() => {\n defaultSubmittedRef.current = defaultSubmitted;\n }, [defaultSubmitted]);\n\n // Identity guard so the MOUNT run is a no-op. Without it this effect fires\n // after the first paint and undoes every seed it was just given; with it,\n // only a real `data` change resets. A parent that rebuilds structurally\n // identical entries in render (`activities={raw.map(redact)}`) must not\n // unlock or revert a restored answer — the pager already remounts a slot\n // whose activity actually changed, via its key.\n const lastDataRef = useRef(data);\n // `data` is an intentional reset trigger (Req 3.7), not read in the body.\n // biome-ignore lint/correctness/useExhaustiveDependencies: data is the reset trigger (Req 3.7)\n useEffect(() => {\n if (lastDataRef.current === data) {\n return;\n }\n lastDataRef.current = data;\n setInternalAnswers(answersOf(defaultValueRef.current));\n setRevealed(new Set());\n setResult(null);\n setSummary(null);\n setFeedbackHidden(false);\n reset(defaultSubmittedRef.current === true ? 'completed' : 'idle');\n }, [data, reset]);\n\n /*\n * RICH TEXT — deliberate, documented limitation (see `sanitizeHtml`).\n *\n * Every other activity can render its `*Html` field through the caller's\n * sanitiser, because the rich text is a LEAF: the sanitised string is\n * handed to one `dangerouslySetInnerHTML` node and nothing is inserted into\n * it. A fill-in-the-blanks passage is not a leaf — it is the CONTAINER of\n * the React-controlled `<input>`s, one per `{{id}}` placeholder.\n *\n * Placing those inputs inside sanitised HTML means slicing the sanitised\n * string at the placeholders and injecting each slice as its own HTML\n * fragment, which is unsafe and broken on both counts:\n * 1. Broken: the slices are unbalanced. `<p>A {{x}} B</p>` yields `<p>A `\n * and ` B</p>`; the fragment parser auto-closes both, so the input\n * lands OUTSIDE the paragraph and the authored block structure is\n * destroyed.\n * 2. Unsafe: the sanitiser's guarantee covers the document it returned,\n * not arbitrary substrings of it. A placeholder inside an attribute\n * (`<img alt=\"{{x}}\">`) splits mid-attribute, and re-parsing the\n * halves in a fresh context is exactly the mutation (mXSS) class of\n * transformation that voids a sanitiser's output guarantee. An exam\n * runner must never depend on that.\n *\n * The safe alternative — parse the sanitised string once into a detached\n * DOM, split only TEXT nodes at placeholders, and portal the inputs into\n * anchors — needs a DOM at render time (no SSR) and a large imperative\n * subsystem. Until that lands, the plain-text `passage` is rendered (always\n * escaped by React), which is correct, answerable, and safe in every mode.\n * `data.passageHtml` is carried and redacted by lk-core regardless, so no\n * authored content is lost — only this component declines to render it.\n */\n useEffect(() => {\n if (isDevelopment() && sanitizeHtml !== undefined && data.passageHtml !== undefined) {\n console.warn(\n '[lk-react] FillInTheBlanks ignores `passageHtml`: the passage hosts the answer inputs, ' +\n 'so rich HTML cannot be split at {{placeholders}} without voiding the sanitiser. ' +\n 'Rendering the plain `passage` instead.',\n );\n }\n }, [sanitizeHtml, data.passageHtml]);\n\n const segments = useMemo(() => parsePassage(data.passage), [data.passage]);\n const blankById = useMemo(() => new Map(data.blanks.map((b) => [b.id, b])), [data.blanks]);\n\n if (devError) {\n throw devError;\n }\n // Practice grades locally, which a redacted projection cannot support: the\n // submit handler below calls `score()`, which throws RedactedScoringError —\n // from an event handler, where no error boundary can catch it, AFTER the\n // learner has typed their answers. Fail loudly at render instead, in\n // production too. This mirrors the identical guard in MultipleChoice;\n // without it the two built-ins disagreed about the same accident, and\n // wiring an exam item into the self-grading mode is precisely what\n // `renderMode` exists to prevent.\n if (data.redacted === true && renderMode === 'practice') {\n throw new Error(\n `Fill-in-the-Blanks \"${data.id}\" received redacted activity data in renderMode \"practice\", ` +\n 'which grades locally and has no answer key to grade against. ' +\n 'Render redacted data with renderMode=\"exam\" (server grades) or \"review\" (pass `outcome`).',\n );\n }\n\n const submitted = state === 'completed';\n const inactive = disabled === true || submitted || isReview;\n\n /**\n * Whether the component may show answer-key-derived information (per-blank\n * correctness, authored feedback, correct answers). NEVER in `exam`; in\n * `practice` only after the component scored locally; in `review` only once\n * the caller supplies a SCORED outcome — an item that is still awaiting a\n * grade must not have its key revealed just because it is being read back.\n */\n const revealing = isReview ? outcome?.status === 'scored' : !isExam && submitted;\n\n const fireInteraction = (\n type: 'blank-filled' | 'hint-requested' | 'submitted',\n payload: Record<string, unknown>,\n ): void => {\n onInteraction?.({ type, activityId: data.id, timestamp: Date.now(), payload });\n };\n\n const handleChange = (blankId: string, text: string): void => {\n if (inactive) {\n return;\n }\n if (state === 'idle') {\n start();\n }\n const next = { ...answers, [blankId]: text };\n if (!isControlled) {\n setInternalAnswers(next);\n }\n onChange?.({ type: 'fill-in-the-blanks', answers: next });\n fireInteraction('blank-filled', { blankId, value: text });\n };\n\n const toggleHint = (blankId: string): void => {\n if (inactive) {\n return;\n }\n if (state === 'idle') {\n start();\n }\n setRevealed((prev) => {\n const next = new Set(prev);\n if (next.has(blankId)) {\n next.delete(blankId);\n return next;\n }\n next.add(blankId);\n return next;\n });\n // Only a reveal is a \"hint requested\"; hiding is not a new request.\n if (!revealed.has(blankId)) {\n fireInteraction('hint-requested', { blankId });\n }\n };\n\n const handleSubmit = (event: React.FormEvent): void => {\n event.preventDefault();\n if (inactive) {\n return;\n }\n const response: FillInTheBlanksLearnerResponse = { type: 'fill-in-the-blanks', answers };\n // Fires in every submitting mode and always BEFORE onComplete, so an exam\n // runner can persist the raw response regardless of local grading.\n onSubmit?.(response);\n\n if (isExam) {\n // EXAM: the client neither grades nor reveals. No score()/evaluate(), no\n // read of any answer-key field, no onComplete — and no xAPI statement,\n // because its `correctResponsesPattern` IS the answer key. The server\n // owns the grade; it emits the statement.\n complete();\n setSummary(s.answerSubmitted);\n fireInteraction('submitted', { answers });\n return;\n }\n\n // PRACTICE: unchanged v1 behaviour. `data` carries the key here — exam\n // mode is the only path that may be handed a redact() projection.\n const scoringResult = score('fill-in-the-blanks', data as FillInTheBlanksData, 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 ...xapiDefinitionFor(data),\n },\n scoringResult,\n timeSpentMs: timeSpent,\n response: JSON.stringify(answers),\n });\n setResult(scoringResult);\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', { answers, score: scoringResult.score });\n };\n\n const correctByBlank = useMemo(() => {\n if (isExam) {\n // Nothing is ever marked client-side in an exam.\n return new Map<string, boolean>();\n }\n if (isReview) {\n // Correctness comes ONLY from the server-computed outcome; the component\n // never scores in review, so an absent/ungraded outcome marks nothing.\n return outcome?.status === 'scored'\n ? correctnessByItem(outcome.details)\n : new Map<string, boolean>();\n }\n return correctnessByItem(result?.details ?? []);\n }, [isExam, isReview, outcome, result]);\n\n /** Review has no local score: its summary is read off `outcome`, or nothing. */\n const reviewSummary = useMemo(() => {\n if (!isReview || outcome === undefined) {\n return null;\n }\n if (outcome.status === 'scored') {\n return `${s.scoreAnnouncement(Math.round(outcome.score * 100), outcome.passed)}${\n outcome.feedback ? ` ${outcome.feedback}` : ''\n }`;\n }\n if (outcome.status === 'deferred') {\n return s.notGradedYet;\n }\n // `graded` used to fall into the arm below and announce \"No grade\n // available.\" over a grade that had come back. Normalised by maxScore so a\n // grader payload of 8.5 / 10 does not read as 850% — same guard as\n // `<WrittenResponse>`.\n if (outcome.status === 'graded') {\n const percent =\n outcome.maxScore > 0\n ? Math.round((outcome.score / outcome.maxScore) * 100)\n : Math.round(outcome.score * 100);\n return `${s.scoreAnnouncement(percent, outcome.passed)}${\n outcome.feedback ? ` ${outcome.feedback}` : ''\n }`;\n }\n return s.noGradeAvailable;\n }, [isReview, outcome, s]);\n\n return (\n <form\n className=\"lk-fib\"\n aria-label={data.title}\n lang={locale}\n data-render-mode={renderMode}\n style={theme as CSSProperties | undefined}\n onSubmit={handleSubmit}\n >\n {data.media ? (\n <ActivityMedia\n media={data.media}\n renderMode={renderMode}\n {...(mediaBudget !== undefined ? { mediaBudget } : {})}\n {...(mediaStrings !== undefined ? { mediaStrings } : {})}\n {...(strings !== undefined ? { strings } : {})}\n {...(onInteraction !== undefined ? { onInteraction } : {})}\n {...(locale !== undefined ? { locale } : {})}\n />\n ) : null}\n <fieldset disabled={inactive}>\n <p className=\"lk-fib-passage\">\n {segments.map((seg, i) => {\n if (seg.kind === 'text') {\n // biome-ignore lint/suspicious/noArrayIndexKey: passage segments are positional and static\n return <span key={`t${i}`}>{seg.value}</span>;\n }\n const blank = blankById.get(seg.id);\n if (!blank) {\n return null;\n }\n // `Renderable` only widens `scoringStrategy` at the type level, so\n // the answer-key fields still LOOK required here while a redact()\n // projection omits them at runtime. Read them defensively, and\n // never at all in exam mode.\n const acceptedAnswers: string[] | undefined = isExam\n ? undefined\n : blank.acceptedAnswers;\n const blankFeedback = isExam ? undefined : blank.feedback;\n const correctness = correctByBlank.get(seg.id);\n const hintId = `${data.id}-hint-${seg.id}`;\n const correctAnswer = acceptedAnswers?.[0];\n if (revealing && showCorrectAnswers && correctness === false && correctAnswer) {\n return (\n <span key={seg.id} className=\"lk-fib-answer\" data-correct=\"false\">\n {correctAnswer}\n </span>\n );\n }\n return (\n <span key={seg.id} className=\"lk-fib-blank\">\n <input\n type=\"text\"\n aria-label={s.blankLabel(seg.ordinal)}\n aria-describedby={blank.hint ? hintId : undefined}\n value={answers[seg.id] ?? ''}\n disabled={inactive}\n aria-disabled={inactive || undefined}\n data-correct={\n revealing && correctness !== undefined ? String(correctness) : undefined\n }\n onChange={(e) => handleChange(seg.id, e.target.value)}\n />\n {/*\n Hints stay available in exam mode by design: lk-core's field\n policy classifies `blanks.hint` as `public`, so it survives\n redact() and is not part of the answer key.\n */}\n {blank.hint ? (\n <>\n {/*\n Default affordance is an icon; the accessible NAME stays\n text via aria-label (screen readers + tests rely on it).\n The glyph is restylable/replaceable via .lk-fib-hint-btn.\n */}\n <button\n type=\"button\"\n className=\"lk-fib-hint-btn\"\n aria-controls={hintId}\n aria-expanded={revealed.has(seg.id)}\n aria-label={revealed.has(seg.id) ? s.hideHint : s.showHint}\n disabled={inactive}\n onClick={() => toggleHint(seg.id)}\n >\n <svg\n className=\"lk-fib-hint-icon\"\n viewBox=\"0 0 24 24\"\n width=\"16\"\n height=\"16\"\n aria-hidden=\"true\"\n focusable=\"false\"\n >\n <path\n fill=\"currentColor\"\n d=\"M9 21h6v-1H9v1Zm3-19a7 7 0 0 0-4 12.74V17h8v-2.26A7 7 0 0 0 12 2Z\"\n />\n </svg>\n </button>\n {/*\n Not role=\"tooltip\": a real ARIA tooltip is a named\n hover/focus popup with strict WCAG 1.4.13 constraints.\n This is an accessible click-to-toggle disclosure that is\n the input's aria-describedby target and is announced via\n aria-live; the skin gives it a tooltip-like *visual*\n without the tooltip *semantics*. (Refines Task 15.3.)\n */}\n <span id={hintId} aria-live=\"polite\">\n {revealed.has(seg.id) ? blank.hint : ''}\n </span>\n </>\n ) : null}\n {revealing && !feedbackHidden && blankFeedback ? (\n <span\n className=\"lk-fib-blank-feedback\"\n role=\"note\"\n data-correct={correctness !== undefined ? String(correctness) : undefined}\n >\n {blankFeedback}\n </span>\n ) : null}\n </span>\n );\n })}\n </p>\n {/* Review is read-only: there is nothing to submit. */}\n {isReview ? null : (\n <button type=\"submit\" disabled={inactive}>\n {isExam ? s.submitAnswers : s.checkAnswers}\n </button>\n )}\n </fieldset>\n {revealing && data.blanks.some((b) => b.feedback) ? (\n <button\n type=\"button\"\n className=\"lk-fib-feedback-toggle\"\n aria-expanded={!feedbackHidden}\n onClick={() => setFeedbackHidden((h) => !h)}\n >\n {feedbackHidden ? s.showFeedback : s.hideFeedback}\n </button>\n ) : null}\n <FeedbackRegion id={`${data.id}-feedback`}>\n {isReview ? reviewSummary : summary}\n </FeedbackRegion>\n </form>\n );\n}\n","'use client';\n\nimport { useLkStrings } from '../../i18n/LkIntlProvider.js';\nimport { ActivityErrorBoundary } from '../ActivityErrorBoundary.js';\nimport {\n FillInTheBlanks as FillInTheBlanksCore,\n type FillInTheBlanksProps,\n} from './FillInTheBlanks.js';\n\nexport type { FillInTheBlanksProps } from './FillInTheBlanks.js';\n\n/**\n * Public Fill-in-the-Blanks activity: the core wrapped in\n * `ActivityErrorBoundary` so a render failure (incl. the dev schema-validation\n * throw) degrades to an accessible fallback instead of crashing the host.\n */\nexport function FillInTheBlanks(props: FillInTheBlanksProps) {\n // Read here rather than in the boundary: a class component cannot call a\n // hook, and the fallback it renders is learner-facing text like any other.\n const strings = useLkStrings(props.strings);\n return (\n <ActivityErrorBoundary activityTitle={props.data?.title} strings={strings}>\n <FillInTheBlanksCore {...props} />\n </ActivityErrorBoundary>\n );\n}\n"]}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { MultipleChoice } from './chunk-MMFHKCWF.js';
|
|
3
|
+
import { FillInTheBlanks } from './chunk-EQ5BZTLV.js';
|
|
4
|
+
import { WrittenResponse } from './chunk-BLQBFI7Z.js';
|
|
5
|
+
import { ActivityErrorBoundary } from './chunk-ESWHIC4Z.js';
|
|
6
|
+
import { useLkStrings } from './chunk-T4CKODCJ.js';
|
|
7
|
+
import { validateDraft, evaluate, slotMediaKey } from '@intellectif/lk-core';
|
|
8
|
+
import { useMemo } from 'react';
|
|
9
|
+
import { jsx, Fragment } from 'react/jsx-runtime';
|
|
10
|
+
|
|
11
|
+
var PREVIEW_SHUFFLE_SEED = "lk-activity-preview";
|
|
12
|
+
var PREVIEW_SLOT_ID = "preview";
|
|
13
|
+
function ActivityPreview({
|
|
14
|
+
draft,
|
|
15
|
+
renderMode = "practice",
|
|
16
|
+
response,
|
|
17
|
+
outcome,
|
|
18
|
+
fallback,
|
|
19
|
+
renderers,
|
|
20
|
+
shuffleSeed,
|
|
21
|
+
sanitizeHtml,
|
|
22
|
+
strings,
|
|
23
|
+
theme,
|
|
24
|
+
locale
|
|
25
|
+
}) {
|
|
26
|
+
const s = useLkStrings(strings);
|
|
27
|
+
const type = isRecord(draft) && typeof draft.type === "string" ? draft.type : void 0;
|
|
28
|
+
const contentKey = contentKeyOf(draft);
|
|
29
|
+
const result = useMemo(
|
|
30
|
+
() => type === void 0 ? void 0 : validateDraft(type, draft),
|
|
31
|
+
[type, contentKey]
|
|
32
|
+
);
|
|
33
|
+
const activity = result?.status === "complete" ? result.data : void 0;
|
|
34
|
+
const responseFits = response === void 0 || response.type === type;
|
|
35
|
+
const responseKey = response === void 0 ? "none" : contentKeyOf(response);
|
|
36
|
+
const marking = useMemo(() => {
|
|
37
|
+
if (renderMode !== "review" || activity === void 0) {
|
|
38
|
+
return void 0;
|
|
39
|
+
}
|
|
40
|
+
if (outcome !== void 0) {
|
|
41
|
+
return { kind: "marked", outcome };
|
|
42
|
+
}
|
|
43
|
+
if (response === void 0 || !responseFits) {
|
|
44
|
+
return void 0;
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
return { kind: "marked", outcome: evaluate(activity, response) };
|
|
48
|
+
} catch (thrown) {
|
|
49
|
+
return {
|
|
50
|
+
kind: "failed",
|
|
51
|
+
error: thrown instanceof Error ? thrown : new Error(String(thrown))
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
}, [renderMode, activity, outcome, responseKey, responseFits]);
|
|
55
|
+
const activityId = activity?.id;
|
|
56
|
+
const mediaBudget = useMemo(
|
|
57
|
+
() => ({
|
|
58
|
+
key: slotMediaKey(PREVIEW_SLOT_ID),
|
|
59
|
+
slotId: PREVIEW_SLOT_ID,
|
|
60
|
+
index: 0,
|
|
61
|
+
...activityId !== void 0 ? { activityId } : {}
|
|
62
|
+
}),
|
|
63
|
+
[activityId]
|
|
64
|
+
);
|
|
65
|
+
if (type === void 0 || result === void 0) {
|
|
66
|
+
throw new Error(
|
|
67
|
+
"<ActivityPreview> needs a draft object with a string `type`, to know which activity to check and render."
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
if (response !== void 0 && !responseFits) {
|
|
71
|
+
throw new Error(
|
|
72
|
+
`<ActivityPreview> was given a "${response.type}" response for a "${type}" activity.`
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
if (result.status !== "complete") {
|
|
76
|
+
if (fallback !== void 0) {
|
|
77
|
+
return /* @__PURE__ */ jsx(Fragment, { children: fallback(result) });
|
|
78
|
+
}
|
|
79
|
+
return /* @__PURE__ */ jsx(
|
|
80
|
+
"div",
|
|
81
|
+
{
|
|
82
|
+
className: "lk-preview-unavailable",
|
|
83
|
+
role: "note",
|
|
84
|
+
"data-status": result.status,
|
|
85
|
+
...locale !== void 0 ? { lang: locale } : {},
|
|
86
|
+
children: result.status === "invalid" ? s.previewInvalid : s.previewIncomplete
|
|
87
|
+
}
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
const data = result.data;
|
|
91
|
+
const key = `${renderMode}:${responseKey}:${recordingKeyOf(data)}`;
|
|
92
|
+
if (marking?.kind === "failed") {
|
|
93
|
+
return /* @__PURE__ */ jsx(ActivityErrorBoundary, { activityTitle: data.title, strings: s, children: /* @__PURE__ */ jsx(Rethrow, { error: marking.error }) }, key);
|
|
94
|
+
}
|
|
95
|
+
const shared = {
|
|
96
|
+
renderMode,
|
|
97
|
+
// Without a binding a budgeted recording counts nothing in `practice`, and in
|
|
98
|
+
// `exam` the media component refuses to render at all — rightly, on a real
|
|
99
|
+
// paper, where nothing would persist the count. A preview persists nothing by
|
|
100
|
+
// design, so it keeps the count in memory and says so.
|
|
101
|
+
mediaBudget,
|
|
102
|
+
...sanitizeHtml !== void 0 ? { sanitizeHtml } : {},
|
|
103
|
+
...strings !== void 0 ? { strings } : {},
|
|
104
|
+
...theme !== void 0 ? { theme } : {},
|
|
105
|
+
...locale !== void 0 ? { locale } : {},
|
|
106
|
+
// Seeded rather than controlled, in every mode, exactly as `<ActivitySequence>`
|
|
107
|
+
// hands a restored answer to its slots — so a renderer written for the
|
|
108
|
+
// sequence reads it here too. `review` is read-only either way, and `key`
|
|
109
|
+
// above re-seeds whenever the response changes.
|
|
110
|
+
...response !== void 0 ? { defaultValue: response } : {},
|
|
111
|
+
...marking !== void 0 ? { outcome: marking.outcome } : {}
|
|
112
|
+
};
|
|
113
|
+
const Custom = renderers?.[data.type];
|
|
114
|
+
if (Custom !== void 0) {
|
|
115
|
+
return /* @__PURE__ */ jsx(Custom, { data, ...shared }, key);
|
|
116
|
+
}
|
|
117
|
+
switch (data.type) {
|
|
118
|
+
case "multiple-choice":
|
|
119
|
+
return /* @__PURE__ */ jsx(
|
|
120
|
+
MultipleChoice,
|
|
121
|
+
{
|
|
122
|
+
data,
|
|
123
|
+
shuffleSeed: shuffleSeed ?? PREVIEW_SHUFFLE_SEED,
|
|
124
|
+
...shared
|
|
125
|
+
},
|
|
126
|
+
key
|
|
127
|
+
);
|
|
128
|
+
case "fill-in-the-blanks":
|
|
129
|
+
return /* @__PURE__ */ jsx(FillInTheBlanks, { data, ...shared }, key);
|
|
130
|
+
case "written-response":
|
|
131
|
+
return /* @__PURE__ */ jsx(WrittenResponse, { data, ...shared }, key);
|
|
132
|
+
default:
|
|
133
|
+
return /* @__PURE__ */ jsx("div", { className: "lk-preview-unsupported", role: "note", children: s.unsupportedActivity });
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
function Rethrow({ error }) {
|
|
137
|
+
throw error;
|
|
138
|
+
}
|
|
139
|
+
function isRecord(value) {
|
|
140
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
141
|
+
}
|
|
142
|
+
function recordingKeyOf(data) {
|
|
143
|
+
const media = isRecord(data) ? data.media : void 0;
|
|
144
|
+
return isRecord(media) ? contentKeyOf([media.type, media.url, media.playback]) : "";
|
|
145
|
+
}
|
|
146
|
+
var identities = /* @__PURE__ */ new WeakMap();
|
|
147
|
+
var lastIdentity = 0;
|
|
148
|
+
function identityKeyOf(value) {
|
|
149
|
+
let identity = identities.get(value);
|
|
150
|
+
if (identity === void 0) {
|
|
151
|
+
lastIdentity += 1;
|
|
152
|
+
identity = lastIdentity;
|
|
153
|
+
identities.set(value, identity);
|
|
154
|
+
}
|
|
155
|
+
return `#${identity}`;
|
|
156
|
+
}
|
|
157
|
+
function contentKeyOf(value, enclosing = /* @__PURE__ */ new Set()) {
|
|
158
|
+
switch (typeof value) {
|
|
159
|
+
case "string":
|
|
160
|
+
return JSON.stringify(value);
|
|
161
|
+
case "number":
|
|
162
|
+
return Object.is(value, -0) ? "-0" : String(value);
|
|
163
|
+
case "bigint":
|
|
164
|
+
return `${value}n`;
|
|
165
|
+
case "boolean":
|
|
166
|
+
case "undefined":
|
|
167
|
+
return String(value);
|
|
168
|
+
case "symbol":
|
|
169
|
+
return `Symbol(${JSON.stringify(value.description ?? null)})`;
|
|
170
|
+
case "function":
|
|
171
|
+
return identityKeyOf(value);
|
|
172
|
+
}
|
|
173
|
+
if (value === null || typeof value !== "object") {
|
|
174
|
+
return "null";
|
|
175
|
+
}
|
|
176
|
+
const prototype = Object.getPrototypeOf(value);
|
|
177
|
+
const plain = Array.isArray(value) || prototype === Object.prototype || prototype === null;
|
|
178
|
+
if (!plain || enclosing.has(value)) {
|
|
179
|
+
return identityKeyOf(value);
|
|
180
|
+
}
|
|
181
|
+
enclosing.add(value);
|
|
182
|
+
try {
|
|
183
|
+
if (Array.isArray(value)) {
|
|
184
|
+
return `[${Array.from(value, (item) => contentKeyOf(item, enclosing)).join(",")}]`;
|
|
185
|
+
}
|
|
186
|
+
const record = value;
|
|
187
|
+
const fields = Object.keys(record).sort().map((field) => `${JSON.stringify(field)}:${contentKeyOf(record[field], enclosing)}`);
|
|
188
|
+
return `{${fields.join(",")}}`;
|
|
189
|
+
} finally {
|
|
190
|
+
enclosing.delete(value);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export { ActivityPreview };
|
|
195
|
+
//# sourceMappingURL=chunk-WJTCIW5R.js.map
|
|
196
|
+
//# sourceMappingURL=chunk-WJTCIW5R.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/ActivityPreview/ActivityPreview.tsx"],"names":[],"mappings":";;;;;;;;;AA2BA,IAAM,oBAAA,GAAuB,qBAAA;AAO7B,IAAM,eAAA,GAAkB,SAAA;AAiEjB,SAAS,eAAA,CAAgB;AAAA,EAC9B,KAAA;AAAA,EACA,UAAA,GAAa,UAAA;AAAA,EACb,QAAA;AAAA,EACA,OAAA;AAAA,EACA,QAAA;AAAA,EACA,SAAA;AAAA,EACA,WAAA;AAAA,EACA,YAAA;AAAA,EACA,OAAA;AAAA,EACA,KAAA;AAAA,EACA;AACF,CAAA,EAAyB;AACvB,EAAA,MAAM,CAAA,GAAI,aAAa,OAAO,CAAA;AAC9B,EAAA,MAAM,IAAA,GAAO,SAAS,KAAK,CAAA,IAAK,OAAO,KAAA,CAAM,IAAA,KAAS,QAAA,GAAW,KAAA,CAAM,IAAA,GAAO,MAAA;AAO9E,EAAA,MAAM,UAAA,GAAa,aAAa,KAAK,CAAA;AAErC,EAAA,MAAM,MAAA,GAAS,OAAA;AAAA,IACb,MAAO,IAAA,KAAS,MAAA,GAAY,MAAA,GAAY,aAAA,CAAc,MAAsB,KAAK,CAAA;AAAA,IACjF,CAAC,MAAM,UAAU;AAAA,GACnB;AACA,EAAA,MAAM,QAAA,GAAW,MAAA,EAAQ,MAAA,KAAW,UAAA,GAAa,OAAO,IAAA,GAAO,MAAA;AAC/D,EAAA,MAAM,YAAA,GAAe,QAAA,KAAa,MAAA,IAAa,QAAA,CAAS,IAAA,KAAS,IAAA;AACjE,EAAA,MAAM,WAAA,GAAc,QAAA,KAAa,MAAA,GAAY,MAAA,GAAS,aAAa,QAAQ,CAAA;AAG3E,EAAA,MAAM,OAAA,GAAU,QAAQ,MAA2B;AACjD,IAAA,IAAI,UAAA,KAAe,QAAA,IAAY,QAAA,KAAa,MAAA,EAAW;AACrD,MAAA,OAAO,MAAA;AAAA,IACT;AACA,IAAA,IAAI,YAAY,MAAA,EAAW;AACzB,MAAA,OAAO,EAAE,IAAA,EAAM,QAAA,EAAU,OAAA,EAAQ;AAAA,IACnC;AACA,IAAA,IAAI,QAAA,KAAa,MAAA,IAAa,CAAC,YAAA,EAAc;AAC3C,MAAA,OAAO,MAAA;AAAA,IACT;AACA,IAAA,IAAI;AACF,MAAA,OAAO,EAAE,IAAA,EAAM,QAAA,EAAU,SAAS,QAAA,CAAS,QAAA,EAAU,QAAQ,CAAA,EAAE;AAAA,IACjE,SAAS,MAAA,EAAQ;AAGf,MAAA,OAAO;AAAA,QACL,IAAA,EAAM,QAAA;AAAA,QACN,KAAA,EAAO,kBAAkB,KAAA,GAAQ,MAAA,GAAS,IAAI,KAAA,CAAM,MAAA,CAAO,MAAM,CAAC;AAAA,OACpE;AAAA,IACF;AAAA,EACF,GAAG,CAAC,UAAA,EAAY,UAAU,OAAA,EAAS,WAAA,EAAa,YAAY,CAAC,CAAA;AAE7D,EAAA,MAAM,aAAa,QAAA,EAAU,EAAA;AAC7B,EAAA,MAAM,WAAA,GAAc,OAAA;AAAA,IAClB,OAAO;AAAA,MACL,GAAA,EAAK,aAAa,eAAe,CAAA;AAAA,MACjC,MAAA,EAAQ,eAAA;AAAA,MACR,KAAA,EAAO,CAAA;AAAA,MACP,GAAI,UAAA,KAAe,MAAA,GAAY,EAAE,UAAA,KAAe;AAAC,KACnD,CAAA;AAAA,IACA,CAAC,UAAU;AAAA,GACb;AAGA,EAAA,IAAI,IAAA,KAAS,MAAA,IAAa,MAAA,KAAW,MAAA,EAAW;AAC9C,IAAA,MAAM,IAAI,KAAA;AAAA,MACR;AAAA,KACF;AAAA,EACF;AAKA,EAAA,IAAI,QAAA,KAAa,MAAA,IAAa,CAAC,YAAA,EAAc;AAC3C,IAAA,MAAM,IAAI,KAAA;AAAA,MACR,CAAA,+BAAA,EAAkC,QAAA,CAAS,IAAI,CAAA,kBAAA,EAAqB,IAAI,CAAA,WAAA;AAAA,KAC1E;AAAA,EACF;AACA,EAAA,IAAI,MAAA,CAAO,WAAW,UAAA,EAAY;AAChC,IAAA,IAAI,aAAa,MAAA,EAAW;AAC1B,MAAA,uBAAO,GAAA,CAAA,QAAA,EAAA,EAAG,QAAA,EAAA,QAAA,CAAS,MAAM,CAAA,EAAE,CAAA;AAAA,IAC7B;AACA,IAAA,uBACE,GAAA;AAAA,MAAC,KAAA;AAAA,MAAA;AAAA,QACC,SAAA,EAAU,wBAAA;AAAA,QACV,IAAA,EAAK,MAAA;AAAA,QACL,eAAa,MAAA,CAAO,MAAA;AAAA,QACnB,GAAI,MAAA,KAAW,MAAA,GAAY,EAAE,IAAA,EAAM,MAAA,KAAW,EAAC;AAAA,QAE/C,QAAA,EAAA,MAAA,CAAO,MAAA,KAAW,SAAA,GAAY,CAAA,CAAE,iBAAiB,CAAA,CAAE;AAAA;AAAA,KACtD;AAAA,EAEJ;AACA,EAAA,MAAM,OAAO,MAAA,CAAO,IAAA;AAOpB,EAAA,MAAM,GAAA,GAAM,GAAG,UAAU,CAAA,CAAA,EAAI,WAAW,CAAA,CAAA,EAAI,cAAA,CAAe,IAAI,CAAC,CAAA,CAAA;AAEhE,EAAA,IAAI,OAAA,EAAS,SAAS,QAAA,EAAU;AAG9B,IAAA,uBACE,GAAA,CAAC,qBAAA,EAAA,EAAgC,aAAA,EAAe,IAAA,CAAK,KAAA,EAAO,OAAA,EAAS,CAAA,EACnE,QAAA,kBAAA,GAAA,CAAC,OAAA,EAAA,EAAQ,KAAA,EAAO,OAAA,CAAQ,KAAA,EAAO,KADL,GAE5B,CAAA;AAAA,EAEJ;AAEA,EAAA,MAAM,MAAA,GAAS;AAAA,IACb,UAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAKA,WAAA;AAAA,IACA,GAAI,YAAA,KAAiB,MAAA,GAAY,EAAE,YAAA,KAAiB,EAAC;AAAA,IACrD,GAAI,OAAA,KAAY,MAAA,GAAY,EAAE,OAAA,KAAY,EAAC;AAAA,IAC3C,GAAI,KAAA,KAAU,MAAA,GAAY,EAAE,KAAA,KAAU,EAAC;AAAA,IACvC,GAAI,MAAA,KAAW,MAAA,GAAY,EAAE,MAAA,KAAW,EAAC;AAAA;AAAA;AAAA;AAAA;AAAA,IAKzC,GAAI,QAAA,KAAa,MAAA,GAAY,EAAE,YAAA,EAAc,QAAA,KAAa,EAAC;AAAA,IAC3D,GAAI,YAAY,MAAA,GAAY,EAAE,SAAS,OAAA,CAAQ,OAAA,KAAY;AAAC,GAC9D;AAEA,EAAA,MAAM,MAAA,GAAS,SAAA,GAAY,IAAA,CAAK,IAAI,CAAA;AACpC,EAAA,IAAI,WAAW,MAAA,EAAW;AACxB,IAAA,uBAAO,GAAA,CAAC,MAAA,EAAA,EAAiB,IAAA,EAAa,GAAG,UAArB,GAA6B,CAAA;AAAA,EACnD;AACA,EAAA,QAAQ,KAAK,IAAA;AAAM,IACjB,KAAK,iBAAA;AACH,MAAA,uBACE,GAAA;AAAA,QAAC,cAAA;AAAA,QAAA;AAAA,UAEC,IAAA;AAAA,UACA,aAAa,WAAA,IAAe,oBAAA;AAAA,UAC3B,GAAG;AAAA,SAAA;AAAA,QAHC;AAAA,OAIP;AAAA,IAEJ,KAAK,oBAAA;AACH,MAAA,uBAAO,GAAA,CAAC,eAAA,EAAA,EAA0B,IAAA,EAAa,GAAG,UAArB,GAA6B,CAAA;AAAA,IAC5D,KAAK,kBAAA;AACH,MAAA,uBAAO,GAAA,CAAC,eAAA,EAAA,EAA0B,IAAA,EAAa,GAAG,UAArB,GAA6B,CAAA;AAAA,IAC5D;AACE,MAAA,2BACG,KAAA,EAAA,EAAI,SAAA,EAAU,0BAAyB,IAAA,EAAK,MAAA,EAC1C,YAAE,mBAAA,EACL,CAAA;AAAA;AAGR;AAGA,SAAS,OAAA,CAAQ,EAAE,KAAA,EAAM,EAA4B;AACnD,EAAA,MAAM,KAAA;AACR;AAEA,SAAS,SAAS,KAAA,EAA4D;AAC5E,EAAA,OAAO,OAAO,UAAU,QAAA,IAAY,KAAA,KAAU,QAAQ,CAAC,KAAA,CAAM,QAAQ,KAAK,CAAA;AAC5E;AAGA,SAAS,eAAe,IAAA,EAAuB;AAC7C,EAAA,MAAM,KAAA,GAAQ,QAAA,CAAS,IAAI,CAAA,GAAI,KAAK,KAAA,GAAQ,MAAA;AAC5C,EAAA,OAAO,QAAA,CAAS,KAAK,CAAA,GAAI,YAAA,CAAa,CAAC,KAAA,CAAM,IAAA,EAAM,KAAA,CAAM,GAAA,EAAK,KAAA,CAAM,QAAQ,CAAC,CAAA,GAAI,EAAA;AACnF;AAEA,IAAM,UAAA,uBAAiB,OAAA,EAAwB;AAC/C,IAAI,YAAA,GAAe,CAAA;AAGnB,SAAS,cAAc,KAAA,EAAuB;AAC5C,EAAA,IAAI,QAAA,GAAW,UAAA,CAAW,GAAA,CAAI,KAAK,CAAA;AACnC,EAAA,IAAI,aAAa,MAAA,EAAW;AAC1B,IAAA,YAAA,IAAgB,CAAA;AAChB,IAAA,QAAA,GAAW,YAAA;AACX,IAAA,UAAA,CAAW,GAAA,CAAI,OAAO,QAAQ,CAAA;AAAA,EAChC;AACA,EAAA,OAAO,IAAI,QAAQ,CAAA,CAAA;AACrB;AAYA,SAAS,YAAA,CAAa,KAAA,EAAgB,SAAA,mBAAyB,IAAI,KAAI,EAAW;AAChF,EAAA,QAAQ,OAAO,KAAA;AAAO,IACpB,KAAK,QAAA;AACH,MAAA,OAAO,IAAA,CAAK,UAAU,KAAK,CAAA;AAAA,IAC7B,KAAK,QAAA;AAGH,MAAA,OAAO,OAAO,EAAA,CAAG,KAAA,EAAO,EAAE,CAAA,GAAI,IAAA,GAAO,OAAO,KAAK,CAAA;AAAA,IACnD,KAAK,QAAA;AACH,MAAA,OAAO,GAAG,KAAK,CAAA,CAAA,CAAA;AAAA,IACjB,KAAK,SAAA;AAAA,IACL,KAAK,WAAA;AACH,MAAA,OAAO,OAAO,KAAK,CAAA;AAAA,IACrB,KAAK,QAAA;AACH,MAAA,OAAO,UAAU,IAAA,CAAK,SAAA,CAAU,KAAA,CAAM,WAAA,IAAe,IAAI,CAAC,CAAA,CAAA,CAAA;AAAA,IAC5D,KAAK,UAAA;AACH,MAAA,OAAO,cAAc,KAAK,CAAA;AAAA;AAG9B,EAAA,IAAI,KAAA,KAAU,IAAA,IAAQ,OAAO,KAAA,KAAU,QAAA,EAAU;AAC/C,IAAA,OAAO,MAAA;AAAA,EACT;AACA,EAAA,MAAM,SAAA,GAAY,MAAA,CAAO,cAAA,CAAe,KAAK,CAAA;AAC7C,EAAA,MAAM,KAAA,GAAQ,MAAM,OAAA,CAAQ,KAAK,KAAK,SAAA,KAAc,MAAA,CAAO,aAAa,SAAA,KAAc,IAAA;AACtF,EAAA,IAAI,CAAC,KAAA,IAAS,SAAA,CAAU,GAAA,CAAI,KAAK,CAAA,EAAG;AAClC,IAAA,OAAO,cAAc,KAAK,CAAA;AAAA,EAC5B;AACA,EAAA,SAAA,CAAU,IAAI,KAAK,CAAA;AACnB,EAAA,IAAI;AACF,IAAA,IAAI,KAAA,CAAM,OAAA,CAAQ,KAAK,CAAA,EAAG;AACxB,MAAA,OAAO,CAAA,CAAA,EAAI,KAAA,CAAM,IAAA,CAAK,KAAA,EAAO,CAAC,IAAA,KAAS,YAAA,CAAa,IAAA,EAAM,SAAS,CAAC,CAAA,CAAE,IAAA,CAAK,GAAG,CAAC,CAAA,CAAA,CAAA;AAAA,IACjF;AACA,IAAA,MAAM,MAAA,GAAS,KAAA;AACf,IAAA,MAAM,MAAA,GAAS,OAAO,IAAA,CAAK,MAAM,EAC9B,IAAA,EAAK,CACL,GAAA,CAAI,CAAC,KAAA,KAAU,CAAA,EAAG,KAAK,SAAA,CAAU,KAAK,CAAC,CAAA,CAAA,EAAI,YAAA,CAAa,OAAO,KAAK,CAAA,EAAG,SAAS,CAAC,CAAA,CAAE,CAAA;AACtF,IAAA,OAAO,CAAA,CAAA,EAAI,MAAA,CAAO,IAAA,CAAK,GAAG,CAAC,CAAA,CAAA,CAAA;AAAA,EAC7B,CAAA,SAAE;AACA,IAAA,SAAA,CAAU,OAAO,KAAK,CAAA;AAAA,EACxB;AACF","file":"chunk-WJTCIW5R.js","sourcesContent":["'use client';\n\nimport {\n type ActivityType,\n type DraftNotComplete,\n evaluate,\n type ItemOutcome,\n type LearnerResponse,\n slotMediaKey,\n type ThemeTokens,\n validateDraft,\n} from '@intellectif/lk-core';\nimport { type ReactNode, useMemo } from 'react';\nimport { useLkStrings } from '../../i18n/LkIntlProvider.js';\nimport type { LkStringsOverride } from '../../i18n/strings.js';\nimport { ActivityErrorBoundary } from '../ActivityErrorBoundary.js';\nimport type { ActivityRenderer } from '../ActivitySequence/index.js';\nimport { FillInTheBlanks } from '../FillInTheBlanks/index.js';\nimport { MultipleChoice } from '../MultipleChoice/index.js';\nimport type { HtmlSanitizer, MediaBudgetBinding, RenderMode } from '../types.js';\nimport { WrittenResponse } from '../WrittenResponse/index.js';\n\n/**\n * The option-shuffle seed a preview uses when none is given. Fixed, so a\n * shuffled question holds still while its author edits it instead of\n * re-ordering on every remount.\n */\nconst PREVIEW_SHUFFLE_SEED = 'lk-activity-preview';\n\n/**\n * The slot a previewed activity stands in for. A recording's play budget is\n * keyed to it and lives only in this mount: the preview counts plays so an\n * author hears the limit a learner gets, and records none of them.\n */\nconst PREVIEW_SLOT_ID = 'preview';\n\nexport interface ActivityPreviewProps {\n /**\n * The activity as an editor holds it right now, finished or not. It is checked\n * with `validateDraft` before anything renders — by content, on every render —\n * so a half-written question never reaches a component that assumes a valid\n * one.\n */\n draft: unknown;\n /** The mode to preview in. Defaults to `practice`. */\n renderMode?: RenderMode;\n /**\n * A learner response to show as if a learner had given it. In `practice` and\n * `exam` it seeds the answer, which stays editable; in `review` it is the\n * submitted answer, marked by `evaluate()` unless `outcome` is given.\n */\n response?: LearnerResponse;\n /**\n * The outcome `review` marks the response with, instead of the one\n * `evaluate()` computes — which is how to preview a returned grade on a written\n * response, since the SDK cannot score one. Ignored in other modes.\n */\n outcome?: ItemOutcome;\n /**\n * Rendered in place of the activity while the draft is not complete, with the\n * issues `validateDraft` found — so an editor can list them in its own words.\n * The issue messages are English, and a preview that rendered them would put\n * untranslatable text on screen. Without it, the preview shows the\n * `previewIncomplete` or `previewInvalid` string.\n */\n fallback?: (result: DraftNotComplete) => ReactNode;\n /** Renderers for activity types beyond the built-ins, keyed by `type`, as on `<ActivitySequence>`. */\n renderers?: Readonly<Record<string, ActivityRenderer>>;\n /**\n * Seeds `<MultipleChoice>`'s option shuffle. Defaults to a fixed seed, so the\n * order holds while options are edited; adding or removing one deals them\n * again, because the shuffle orders by position. A `renderers` entry is given\n * no seed, as on `<ActivitySequence>`.\n */\n shuffleSeed?: string;\n /** Renders author-supplied rich text. See `HtmlSanitizer`. */\n sanitizeHtml?: HtmlSanitizer;\n /** Overrides the SDK's chrome text, for the preview's notice and the activity alike. */\n strings?: LkStringsOverride;\n /** Per-instance token overrides, passed to the activity. */\n theme?: Partial<ThemeTokens>;\n /** BCP 47 tag of the interface language, passed to the activity. */\n locale?: string;\n}\n\ntype Marking = { kind: 'marked'; outcome: ItemOutcome } | { kind: 'failed'; error: Error };\n\n/**\n * Renders an activity from an editor's draft, in any render mode, with a\n * simulated learner response — and renders a notice instead while the draft is\n * not an activity yet.\n *\n * Nothing it renders is recorded: no callback is wired, so a practice attempt in\n * a preview scores locally and goes nowhere, and a recording's play limit is\n * counted in memory only.\n *\n * A draft with no string `type`, a type nobody registered, and a `response` of a\n * different type from the draft are wiring errors, and throw.\n */\nexport function ActivityPreview({\n draft,\n renderMode = 'practice',\n response,\n outcome,\n fallback,\n renderers,\n shuffleSeed,\n sanitizeHtml,\n strings,\n theme,\n locale,\n}: ActivityPreviewProps) {\n const s = useLkStrings(strings);\n const type = isRecord(draft) && typeof draft.type === 'string' ? draft.type : undefined;\n\n // Validated on the draft's CONTENT, not its identity. The activity components\n // restart whenever `data` changes identity, and an editor that rebuilds its\n // payload on every change would otherwise restart the question an author is\n // trying out each time anything else on the page was edited. The key is read\n // on every render, so a draft changed in place is checked again too.\n const contentKey = contentKeyOf(draft);\n // biome-ignore lint/correctness/useExhaustiveDependencies: the draft's content (contentKey) is the trigger, not its identity\n const result = useMemo(\n () => (type === undefined ? undefined : validateDraft(type as ActivityType, draft)),\n [type, contentKey],\n );\n const activity = result?.status === 'complete' ? result.data : undefined;\n const responseFits = response === undefined || response.type === type;\n const responseKey = response === undefined ? 'none' : contentKeyOf(response);\n\n // biome-ignore lint/correctness/useExhaustiveDependencies: the response's content (responseKey) is the trigger, not its identity\n const marking = useMemo((): Marking | undefined => {\n if (renderMode !== 'review' || activity === undefined) {\n return undefined;\n }\n if (outcome !== undefined) {\n return { kind: 'marked', outcome };\n }\n if (response === undefined || !responseFits) {\n return undefined;\n }\n try {\n return { kind: 'marked', outcome: evaluate(activity, response) };\n } catch (thrown) {\n // Scored here, during this component's own render, where no error boundary\n // inside the activity can catch it — so it is handed to one below.\n return {\n kind: 'failed',\n error: thrown instanceof Error ? thrown : new Error(String(thrown)),\n };\n }\n }, [renderMode, activity, outcome, responseKey, responseFits]);\n\n const activityId = activity?.id;\n const mediaBudget = useMemo<MediaBudgetBinding>(\n () => ({\n key: slotMediaKey(PREVIEW_SLOT_ID),\n slotId: PREVIEW_SLOT_ID,\n index: 0,\n ...(activityId !== undefined ? { activityId } : {}),\n }),\n [activityId],\n );\n\n // All hooks are called before these throws, so hook order stays stable.\n if (type === undefined || result === undefined) {\n throw new Error(\n '<ActivityPreview> needs a draft object with a string `type`, to know which activity to check and render.',\n );\n }\n // Checked against the draft's own type, whatever state the draft is in. A\n // mis-wired response is a wiring error however unfinished the draft is, and\n // waiting for the draft to be complete would throw on the very edit that\n // completes it.\n if (response !== undefined && !responseFits) {\n throw new Error(\n `<ActivityPreview> was given a \"${response.type}\" response for a \"${type}\" activity.`,\n );\n }\n if (result.status !== 'complete') {\n if (fallback !== undefined) {\n return <>{fallback(result)}</>;\n }\n return (\n <div\n className=\"lk-preview-unavailable\"\n role=\"note\"\n data-status={result.status}\n {...(locale !== undefined ? { lang: locale } : {})}\n >\n {result.status === 'invalid' ? s.previewInvalid : s.previewIncomplete}\n </div>\n );\n }\n const data = result.data;\n\n // A different simulated response, or a different mode, mounts the activity\n // afresh, play count included: a seeded answer is read at mount, and a practice\n // attempt the author submitted must not carry over into an exam preview. So\n // does a different recording or playback policy, which is a different listening\n // budget — the transport reads its count once, when it mounts.\n const key = `${renderMode}:${responseKey}:${recordingKeyOf(data)}`;\n\n if (marking?.kind === 'failed') {\n // The response is one the scorer cannot read, or a registered scorer threw.\n // Shown as the activity's error fallback, as a render failure would be.\n return (\n <ActivityErrorBoundary key={key} activityTitle={data.title} strings={s}>\n <Rethrow error={marking.error} />\n </ActivityErrorBoundary>\n );\n }\n\n const shared = {\n renderMode,\n // Without a binding a budgeted recording counts nothing in `practice`, and in\n // `exam` the media component refuses to render at all — rightly, on a real\n // paper, where nothing would persist the count. A preview persists nothing by\n // design, so it keeps the count in memory and says so.\n mediaBudget,\n ...(sanitizeHtml !== undefined ? { sanitizeHtml } : {}),\n ...(strings !== undefined ? { strings } : {}),\n ...(theme !== undefined ? { theme } : {}),\n ...(locale !== undefined ? { locale } : {}),\n // Seeded rather than controlled, in every mode, exactly as `<ActivitySequence>`\n // hands a restored answer to its slots — so a renderer written for the\n // sequence reads it here too. `review` is read-only either way, and `key`\n // above re-seeds whenever the response changes.\n ...(response !== undefined ? { defaultValue: response } : {}),\n ...(marking !== undefined ? { outcome: marking.outcome } : {}),\n };\n\n const Custom = renderers?.[data.type];\n if (Custom !== undefined) {\n return <Custom key={key} data={data} {...shared} />;\n }\n switch (data.type) {\n case 'multiple-choice':\n return (\n <MultipleChoice\n key={key}\n data={data}\n shuffleSeed={shuffleSeed ?? PREVIEW_SHUFFLE_SEED}\n {...shared}\n />\n );\n case 'fill-in-the-blanks':\n return <FillInTheBlanks key={key} data={data} {...shared} />;\n case 'written-response':\n return <WrittenResponse key={key} data={data} {...shared} />;\n default:\n return (\n <div className=\"lk-preview-unsupported\" role=\"note\">\n {s.unsupportedActivity}\n </div>\n );\n }\n}\n\n/** Throws `error` while rendering, so the error boundary around it shows its fallback. */\nfunction Rethrow({ error }: { error: Error }): never {\n throw error;\n}\n\nfunction isRecord(value: unknown): value is Readonly<Record<string, unknown>> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/** The recording and its playback policy — not its description, which changes no budget. */\nfunction recordingKeyOf(data: unknown): string {\n const media = isRecord(data) ? data.media : undefined;\n return isRecord(media) ? contentKeyOf([media.type, media.url, media.playback]) : '';\n}\n\nconst identities = new WeakMap<object, number>();\nlet lastIdentity = 0;\n\n/** A key for a value compared by identity: the same object always gets the same one. */\nfunction identityKeyOf(value: object): string {\n let identity = identities.get(value);\n if (identity === undefined) {\n lastIdentity += 1;\n identity = lastIdentity;\n identities.set(value, identity);\n }\n return `#${identity}`;\n}\n\n/**\n * A string that changes exactly when a value's content does.\n *\n * Object keys are sorted, because key order is not content: a draft saved to a\n * JSON column comes back with its keys reordered. Every value is written in a\n * form no other value shares — JSON would write a non-finite number as `null`\n * and drop `undefined`, and those decide different issues. What JSON does not\n * describe — an instance of a class, a function, a reference back to an\n * enclosing object — is compared by identity, and a symbol by its description.\n */\nfunction contentKeyOf(value: unknown, enclosing: Set<object> = new Set()): string {\n switch (typeof value) {\n case 'string':\n return JSON.stringify(value);\n case 'number':\n // `String` keeps NaN, the infinities and every other number apart; only -0\n // needs saying.\n return Object.is(value, -0) ? '-0' : String(value);\n case 'bigint':\n return `${value}n`;\n case 'boolean':\n case 'undefined':\n return String(value);\n case 'symbol':\n return `Symbol(${JSON.stringify(value.description ?? null)})`;\n case 'function':\n return identityKeyOf(value);\n }\n // Every other `typeof` has returned above: this is `null`, or an object.\n if (value === null || typeof value !== 'object') {\n return 'null';\n }\n const prototype = Object.getPrototypeOf(value);\n const plain = Array.isArray(value) || prototype === Object.prototype || prototype === null;\n if (!plain || enclosing.has(value)) {\n return identityKeyOf(value);\n }\n enclosing.add(value);\n try {\n if (Array.isArray(value)) {\n return `[${Array.from(value, (item) => contentKeyOf(item, enclosing)).join(',')}]`;\n }\n const record = value as Record<string, unknown>;\n const fields = Object.keys(record)\n .sort()\n .map((field) => `${JSON.stringify(field)}:${contentKeyOf(record[field], enclosing)}`);\n return `{${fields.join(',')}}`;\n } finally {\n enclosing.delete(value);\n }\n}\n"]}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
-
var
|
|
5
|
-
var
|
|
6
|
-
var
|
|
7
|
-
var
|
|
8
|
-
var
|
|
4
|
+
var chunkSGIUPGDA_cjs = require('./chunk-SGIUPGDA.cjs');
|
|
5
|
+
var chunkVVZ7SAE7_cjs = require('./chunk-VVZ7SAE7.cjs');
|
|
6
|
+
var chunk4AHZBTBF_cjs = require('./chunk-4AHZBTBF.cjs');
|
|
7
|
+
var chunk4XPKKNJQ_cjs = require('./chunk-4XPKKNJQ.cjs');
|
|
8
|
+
var chunk7VIXWVFN_cjs = require('./chunk-7VIXWVFN.cjs');
|
|
9
|
+
var chunkAHEV52U7_cjs = require('./chunk-AHEV52U7.cjs');
|
|
9
10
|
var lkCore = require('@intellectif/lk-core');
|
|
10
11
|
var react = require('react');
|
|
11
12
|
var jsxRuntime = require('react/jsx-runtime');
|
|
@@ -59,9 +60,11 @@ function ActivitySequence({
|
|
|
59
60
|
theme,
|
|
60
61
|
locale,
|
|
61
62
|
disabled,
|
|
62
|
-
mediaBudget
|
|
63
|
+
mediaBudget,
|
|
64
|
+
strings
|
|
63
65
|
}) {
|
|
64
66
|
const sessionIdRef = react.useRef(null);
|
|
67
|
+
const s = chunkAHEV52U7_cjs.useLkStrings(strings);
|
|
65
68
|
const mediaPlays = mediaBudget?.plays;
|
|
66
69
|
const shuffleEntries = shuffle === "entries";
|
|
67
70
|
const needsSeed = shuffleEntries || activities.some(
|
|
@@ -74,7 +77,7 @@ function ActivitySequence({
|
|
|
74
77
|
}
|
|
75
78
|
const seed = needsSeed ? (
|
|
76
79
|
// biome-ignore lint/suspicious/noAssignInExpressions: sanctioned lazy ref initialization
|
|
77
|
-
shuffleSeed ?? (sessionIdRef.current ??=
|
|
80
|
+
shuffleSeed ?? (sessionIdRef.current ??= chunk4XPKKNJQ_cjs.randomSessionId())
|
|
78
81
|
) : void 0;
|
|
79
82
|
const slots = react.useMemo(
|
|
80
83
|
() => lkCore.flattenSequence(activities, { shuffleEntries, ...seed !== void 0 ? { seed } : {} }),
|
|
@@ -246,6 +249,7 @@ function ActivitySequence({
|
|
|
246
249
|
...onInteraction ? { onInteraction } : {},
|
|
247
250
|
renderMode,
|
|
248
251
|
...sanitizeHtml ? { sanitizeHtml } : {},
|
|
252
|
+
...strings !== void 0 ? { strings } : {},
|
|
249
253
|
...theme ? { theme } : {},
|
|
250
254
|
...locale ? { locale } : {},
|
|
251
255
|
...disabled ? { disabled } : {}
|
|
@@ -280,7 +284,7 @@ function ActivitySequence({
|
|
|
280
284
|
}
|
|
281
285
|
if (activity.type === "multiple-choice") {
|
|
282
286
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
283
|
-
|
|
287
|
+
chunkSGIUPGDA_cjs.MultipleChoice,
|
|
284
288
|
{
|
|
285
289
|
data: activity,
|
|
286
290
|
...shuffleSeed !== void 0 ? { shuffleSeed } : {},
|
|
@@ -289,12 +293,12 @@ function ActivitySequence({
|
|
|
289
293
|
);
|
|
290
294
|
}
|
|
291
295
|
if (activity.type === "fill-in-the-blanks") {
|
|
292
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
296
|
+
return /* @__PURE__ */ jsxRuntime.jsx(chunkVVZ7SAE7_cjs.FillInTheBlanks, { data: activity, ...childProps });
|
|
293
297
|
}
|
|
294
298
|
if (activity.type === "written-response") {
|
|
295
299
|
const { onComplete: _neverGrades, ...writtenResponseProps } = childProps;
|
|
296
300
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
297
|
-
|
|
301
|
+
chunk4AHZBTBF_cjs.WrittenResponse,
|
|
298
302
|
{
|
|
299
303
|
data: activity,
|
|
300
304
|
...writtenResponseProps,
|
|
@@ -302,16 +306,11 @@ function ActivitySequence({
|
|
|
302
306
|
}
|
|
303
307
|
);
|
|
304
308
|
}
|
|
305
|
-
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "lk-seq-unsupported", role: "note", children:
|
|
309
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "lk-seq-unsupported", role: "note", children: s.unsupportedActivity });
|
|
306
310
|
};
|
|
307
311
|
const currentEntryKey = entryKeyOf(current);
|
|
308
312
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "lk-seq", lang: locale, children: [
|
|
309
|
-
/* @__PURE__ */ jsxRuntime.
|
|
310
|
-
"Question ",
|
|
311
|
-
index + 1,
|
|
312
|
-
" of ",
|
|
313
|
-
total
|
|
314
|
-
] }),
|
|
313
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "lk-seq-progress", "aria-live": "polite", role: "status", children: s.questionProgress(index + 1, total) }),
|
|
315
314
|
stimulusMounts.map(({ entryKey, group, first, last }) => {
|
|
316
315
|
const firstSlot = slots[first];
|
|
317
316
|
const stimulusBinding = firstSlot === void 0 ? void 0 : bindingFor(lkCore.stimulusMediaKey(firstSlot.slotId), firstSlot);
|
|
@@ -321,7 +320,7 @@ function ActivitySequence({
|
|
|
321
320
|
className: "lk-seq-stimulus",
|
|
322
321
|
hidden: entryKey !== currentEntryKey,
|
|
323
322
|
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
324
|
-
|
|
323
|
+
chunk7VIXWVFN_cjs.StimulusPanel,
|
|
325
324
|
{
|
|
326
325
|
stimulus: group.stimulus,
|
|
327
326
|
range: { first: first + 1, last: last + 1 },
|
|
@@ -330,6 +329,7 @@ function ActivitySequence({
|
|
|
330
329
|
...locale ? { locale } : {},
|
|
331
330
|
...onInteraction ? { onInteraction } : {},
|
|
332
331
|
...disabled ? { disabled } : {},
|
|
332
|
+
...strings !== void 0 ? { strings } : {},
|
|
333
333
|
...stimulusBinding !== void 0 ? { mediaBudget: stimulusBinding } : {}
|
|
334
334
|
}
|
|
335
335
|
)
|
|
@@ -343,7 +343,7 @@ function ActivitySequence({
|
|
|
343
343
|
className: "lk-seq-question",
|
|
344
344
|
ref: regionRef,
|
|
345
345
|
tabIndex: -1,
|
|
346
|
-
"aria-label":
|
|
346
|
+
"aria-label": s.questionProgress(index + 1, total),
|
|
347
347
|
children: slots.map((slot) => {
|
|
348
348
|
const slotKey = `${setKey}::slot-${slot.slotId}-${slot.activity.id}`;
|
|
349
349
|
return /* @__PURE__ */ jsxRuntime.jsx(SequencePane, { className: "lk-seq-slot", hidden: slot.index !== index, children: renderSlot(slot) }, slotKey);
|
|
@@ -358,7 +358,7 @@ function ActivitySequence({
|
|
|
358
358
|
className: "lk-seq-prev",
|
|
359
359
|
onClick: () => go(index - 1),
|
|
360
360
|
disabled: index === 0,
|
|
361
|
-
children:
|
|
361
|
+
children: s.previous
|
|
362
362
|
}
|
|
363
363
|
),
|
|
364
364
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -368,7 +368,7 @@ function ActivitySequence({
|
|
|
368
368
|
className: "lk-seq-next",
|
|
369
369
|
onClick: () => go(index + 1),
|
|
370
370
|
disabled: index === total - 1,
|
|
371
|
-
children:
|
|
371
|
+
children: s.next
|
|
372
372
|
}
|
|
373
373
|
)
|
|
374
374
|
] })
|
|
@@ -376,5 +376,5 @@ function ActivitySequence({
|
|
|
376
376
|
}
|
|
377
377
|
|
|
378
378
|
exports.ActivitySequence = ActivitySequence;
|
|
379
|
-
//# sourceMappingURL=chunk-
|
|
380
|
-
//# sourceMappingURL=chunk-
|
|
379
|
+
//# sourceMappingURL=chunk-ZDB3SLYR.cjs.map
|
|
380
|
+
//# sourceMappingURL=chunk-ZDB3SLYR.cjs.map
|