@intellectif/lk-react 20.0.0 → 21.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 +21 -0
- package/README.md +30 -2
- package/dist/{WrittenResponse-obIDDYDH.d.cts → WrittenResponse-BVPmrmQL.d.cts} +1 -1
- package/dist/{WrittenResponse-D2YI8xOK.d.ts → WrittenResponse-IAJvvL-g.d.ts} +1 -1
- package/dist/ai/LkAiProvider.cjs +3 -3
- package/dist/ai/LkAiProvider.d.cts +5 -3
- package/dist/ai/LkAiProvider.d.ts +5 -3
- package/dist/ai/LkAiProvider.js +1 -1
- package/dist/ai/useAiHelp.cjs +5 -5
- package/dist/ai/useAiHelp.d.cts +1 -1
- package/dist/ai/useAiHelp.d.ts +1 -1
- package/dist/ai/useAiHelp.js +3 -3
- package/dist/{chunk-W6DMHN33.js → chunk-5MO7ACSB.js} +10 -10
- package/dist/{chunk-W6DMHN33.js.map → chunk-5MO7ACSB.js.map} +1 -1
- package/dist/{chunk-7K4B2EMU.js → chunk-5Q2KFIZQ.js} +4 -4
- package/dist/{chunk-7K4B2EMU.js.map → chunk-5Q2KFIZQ.js.map} +1 -1
- package/dist/{chunk-APZ6JFN6.cjs → chunk-6SNNMVBV.cjs} +5 -5
- package/dist/{chunk-APZ6JFN6.cjs.map → chunk-6SNNMVBV.cjs.map} +1 -1
- package/dist/{chunk-L7WY5YFQ.cjs → chunk-7JBXDQTD.cjs} +5 -5
- package/dist/{chunk-L7WY5YFQ.cjs.map → chunk-7JBXDQTD.cjs.map} +1 -1
- package/dist/{chunk-NWNQ2PHT.js → chunk-AY3WN4FB.js} +8 -8
- package/dist/{chunk-NWNQ2PHT.js.map → chunk-AY3WN4FB.js.map} +1 -1
- package/dist/{chunk-CEUEXISZ.cjs → chunk-CB2A56Q7.cjs} +131 -50
- package/dist/chunk-CB2A56Q7.cjs.map +1 -0
- package/dist/chunk-CQ37UD4T.cjs +251 -0
- package/dist/chunk-CQ37UD4T.cjs.map +1 -0
- package/dist/{chunk-YRNEOBXA.js → chunk-EYLBSS4Y.js} +11 -2
- package/dist/chunk-EYLBSS4Y.js.map +1 -0
- package/dist/{chunk-AHGB5HH6.cjs → chunk-GHKMX5UT.cjs} +11 -2
- package/dist/chunk-GHKMX5UT.cjs.map +1 -0
- package/dist/chunk-GND3OBVE.js +241 -0
- package/dist/chunk-GND3OBVE.js.map +1 -0
- package/dist/{chunk-DZVF3IGB.cjs → chunk-GPGCBDBN.cjs} +59 -25
- package/dist/chunk-GPGCBDBN.cjs.map +1 -0
- package/dist/{chunk-LK5VXNMR.cjs → chunk-GW3YIPDE.cjs} +18 -18
- package/dist/{chunk-LK5VXNMR.cjs.map → chunk-GW3YIPDE.cjs.map} +1 -1
- package/dist/{chunk-BJLQKJZX.cjs → chunk-J2QIHZ3H.cjs} +2 -2
- package/dist/chunk-J2QIHZ3H.cjs.map +1 -0
- package/dist/{chunk-3XP6VO4G.js → chunk-KRM6DX47.js} +139 -45
- package/dist/chunk-KRM6DX47.js.map +1 -0
- package/dist/{chunk-PWLJDP3X.cjs → chunk-LHFC2BCM.cjs} +95 -27
- package/dist/chunk-LHFC2BCM.cjs.map +1 -0
- package/dist/{chunk-G7AGM2Y7.js → chunk-M3K6BF5M.js} +4 -4
- package/dist/{chunk-G7AGM2Y7.js.map → chunk-M3K6BF5M.js.map} +1 -1
- package/dist/{chunk-IXMT7X3E.cjs → chunk-M4UJBLVR.cjs} +17 -6
- package/dist/chunk-M4UJBLVR.cjs.map +1 -0
- package/dist/{chunk-YNDKAYW3.js → chunk-MCTNLUKO.js} +2 -2
- package/dist/{chunk-YNDKAYW3.js.map → chunk-MCTNLUKO.js.map} +1 -1
- package/dist/{chunk-ZDYNAWDZ.cjs → chunk-MPHX3Y42.cjs} +2 -2
- package/dist/{chunk-ZDYNAWDZ.cjs.map → chunk-MPHX3Y42.cjs.map} +1 -1
- package/dist/{chunk-AQIAQHJC.js → chunk-NDIKHMLJ.js} +4 -4
- package/dist/{chunk-AQIAQHJC.js.map → chunk-NDIKHMLJ.js.map} +1 -1
- package/dist/{chunk-LX2X4KC7.cjs → chunk-NILHU555.cjs} +7 -7
- package/dist/{chunk-LX2X4KC7.cjs.map → chunk-NILHU555.cjs.map} +1 -1
- package/dist/{chunk-JLDSCHY5.cjs → chunk-OKEN6NJL.cjs} +150 -61
- package/dist/chunk-OKEN6NJL.cjs.map +1 -0
- package/dist/{chunk-ORBU3XKH.js → chunk-PJMXHDY5.js} +16 -5
- package/dist/chunk-PJMXHDY5.js.map +1 -0
- package/dist/{chunk-QKW2GGTV.js → chunk-U2BX2RWY.js} +84 -16
- package/dist/chunk-U2BX2RWY.js.map +1 -0
- package/dist/{chunk-WBLYD5QA.js → chunk-U4KTOH7C.js} +2 -2
- package/dist/chunk-U4KTOH7C.js.map +1 -0
- package/dist/{chunk-FEOFX4QW.js → chunk-U6SEII5V.js} +3 -3
- package/dist/{chunk-FEOFX4QW.js.map → chunk-U6SEII5V.js.map} +1 -1
- package/dist/{chunk-6S2CKZ4H.cjs → chunk-W2P5RILE.cjs} +145 -51
- package/dist/chunk-W2P5RILE.cjs.map +1 -0
- package/dist/{chunk-AG7MWZYZ.js → chunk-W6TWQY6S.js} +144 -55
- package/dist/chunk-W6TWQY6S.js.map +1 -0
- package/dist/{chunk-HREA5SMB.cjs → chunk-WGHMWBEJ.cjs} +116 -47
- package/dist/chunk-WGHMWBEJ.cjs.map +1 -0
- package/dist/{chunk-5GI53C6T.js → chunk-WNDNQ6NR.js} +109 -40
- package/dist/chunk-WNDNQ6NR.js.map +1 -0
- package/dist/{chunk-RT2PP4SO.cjs → chunk-WXESKL75.cjs} +30 -30
- package/dist/{chunk-RT2PP4SO.cjs.map → chunk-WXESKL75.cjs.map} +1 -1
- package/dist/{chunk-BY4UQH2Y.js → chunk-X4YWXZ4J.js} +3 -3
- package/dist/{chunk-BY4UQH2Y.js.map → chunk-X4YWXZ4J.js.map} +1 -1
- package/dist/{chunk-HI6BUUIJ.js → chunk-XB2AHEU5.js} +48 -14
- package/dist/chunk-XB2AHEU5.js.map +1 -0
- package/dist/{chunk-IQ55QNBI.js → chunk-XM5Q2ZCH.js} +125 -44
- package/dist/chunk-XM5Q2ZCH.js.map +1 -0
- package/dist/{chunk-UHIOVOFK.cjs → chunk-XZCMUDFP.cjs} +12 -12
- package/dist/{chunk-UHIOVOFK.cjs.map → chunk-XZCMUDFP.cjs.map} +1 -1
- package/dist/{chunk-TRMWBJWT.cjs → chunk-Y4IDF5UO.cjs} +8 -8
- package/dist/{chunk-TRMWBJWT.cjs.map → chunk-Y4IDF5UO.cjs.map} +1 -1
- package/dist/{chunk-3FBBP5QR.cjs → chunk-YWJ5XJDU.cjs} +6 -6
- package/dist/{chunk-3FBBP5QR.cjs.map → chunk-YWJ5XJDU.cjs.map} +1 -1
- package/dist/{chunk-BGA2IVQY.js → chunk-ZDDBWUUA.js} +6 -6
- package/dist/{chunk-BGA2IVQY.js.map → chunk-ZDDBWUUA.js.map} +1 -1
- package/dist/components/ActivityPreview.cjs +17 -17
- package/dist/components/ActivityPreview.d.cts +2 -2
- package/dist/components/ActivityPreview.d.ts +2 -2
- package/dist/components/ActivityPreview.js +16 -16
- package/dist/components/ActivitySequence.cjs +18 -18
- package/dist/components/ActivitySequence.d.cts +22 -4
- package/dist/components/ActivitySequence.d.ts +22 -4
- package/dist/components/ActivitySequence.js +17 -17
- package/dist/components/Dictation.cjs +10 -10
- package/dist/components/Dictation.d.cts +1 -1
- package/dist/components/Dictation.d.ts +1 -1
- package/dist/components/Dictation.js +9 -9
- package/dist/components/FillInTheBlanks.cjs +10 -10
- package/dist/components/FillInTheBlanks.d.cts +1 -1
- package/dist/components/FillInTheBlanks.d.ts +1 -1
- package/dist/components/FillInTheBlanks.js +9 -9
- package/dist/components/GapSelect.cjs +10 -10
- package/dist/components/GapSelect.d.cts +1 -1
- package/dist/components/GapSelect.d.ts +1 -1
- package/dist/components/GapSelect.js +9 -9
- package/dist/components/InteractiveVideo.cjs +17 -17
- package/dist/components/InteractiveVideo.d.cts +19 -3
- package/dist/components/InteractiveVideo.d.ts +19 -3
- package/dist/components/InteractiveVideo.js +15 -15
- package/dist/components/MultipleChoice.cjs +10 -10
- package/dist/components/MultipleChoice.d.cts +1 -1
- package/dist/components/MultipleChoice.d.ts +1 -1
- package/dist/components/MultipleChoice.js +9 -9
- package/dist/components/PronunciationFeedback.cjs +4 -4
- package/dist/components/PronunciationFeedback.d.cts +1 -1
- package/dist/components/PronunciationFeedback.d.ts +1 -1
- package/dist/components/PronunciationFeedback.js +3 -3
- package/dist/components/ReadAloud.cjs +8 -8
- package/dist/components/ReadAloud.d.cts +2 -2
- package/dist/components/ReadAloud.d.ts +2 -2
- package/dist/components/ReadAloud.js +7 -7
- package/dist/components/StimulusPanel.cjs +4 -4
- package/dist/components/StimulusPanel.d.cts +1 -1
- package/dist/components/StimulusPanel.d.ts +1 -1
- package/dist/components/StimulusPanel.js +3 -3
- package/dist/components/WrittenResponse.cjs +7 -7
- package/dist/components/WrittenResponse.d.cts +3 -3
- package/dist/components/WrittenResponse.d.ts +3 -3
- package/dist/components/WrittenResponse.js +6 -6
- package/dist/i18n/LkIntlProvider.cjs +7 -7
- package/dist/i18n/LkIntlProvider.d.cts +2 -2
- package/dist/i18n/LkIntlProvider.d.ts +2 -2
- package/dist/i18n/LkIntlProvider.js +1 -1
- package/dist/index.cjs +41 -41
- 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 +19 -19
- package/dist/index.js.map +1 -1
- package/dist/theme/skin.css +55 -0
- package/dist/{types-C-GMCJ0g.d.ts → types-7lGlo0zE.d.ts} +48 -1
- package/dist/{types-B_PCcqO-.d.cts → types-Botf4Wqj.d.cts} +48 -1
- package/package.json +3 -3
- package/dist/chunk-3XP6VO4G.js.map +0 -1
- package/dist/chunk-5GI53C6T.js.map +0 -1
- package/dist/chunk-6S2CKZ4H.cjs.map +0 -1
- package/dist/chunk-AG7MWZYZ.js.map +0 -1
- package/dist/chunk-AHGB5HH6.cjs.map +0 -1
- package/dist/chunk-BJLQKJZX.cjs.map +0 -1
- package/dist/chunk-CEUEXISZ.cjs.map +0 -1
- package/dist/chunk-DZVF3IGB.cjs.map +0 -1
- package/dist/chunk-HI6BUUIJ.js.map +0 -1
- package/dist/chunk-HREA5SMB.cjs.map +0 -1
- package/dist/chunk-IQ55QNBI.js.map +0 -1
- package/dist/chunk-IXMT7X3E.cjs.map +0 -1
- package/dist/chunk-JLDSCHY5.cjs.map +0 -1
- package/dist/chunk-NOC2M6LU.cjs +0 -118
- package/dist/chunk-NOC2M6LU.cjs.map +0 -1
- package/dist/chunk-ORBU3XKH.js.map +0 -1
- package/dist/chunk-PWLJDP3X.cjs.map +0 -1
- package/dist/chunk-QKW2GGTV.js.map +0 -1
- package/dist/chunk-RUS42NMN.js +0 -115
- package/dist/chunk-RUS42NMN.js.map +0 -1
- package/dist/chunk-WBLYD5QA.js.map +0 -1
- package/dist/chunk-YRNEOBXA.js.map +0 -1
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
-
var
|
|
5
|
-
var
|
|
6
|
-
var
|
|
7
|
-
var
|
|
8
|
-
var
|
|
9
|
-
var
|
|
10
|
-
var
|
|
11
|
-
var
|
|
4
|
+
var chunkXZCMUDFP_cjs = require('./chunk-XZCMUDFP.cjs');
|
|
5
|
+
var chunkOKEN6NJL_cjs = require('./chunk-OKEN6NJL.cjs');
|
|
6
|
+
var chunkW2P5RILE_cjs = require('./chunk-W2P5RILE.cjs');
|
|
7
|
+
var chunkCB2A56Q7_cjs = require('./chunk-CB2A56Q7.cjs');
|
|
8
|
+
var chunkWGHMWBEJ_cjs = require('./chunk-WGHMWBEJ.cjs');
|
|
9
|
+
var chunkWXESKL75_cjs = require('./chunk-WXESKL75.cjs');
|
|
10
|
+
var chunk6SNNMVBV_cjs = require('./chunk-6SNNMVBV.cjs');
|
|
11
|
+
var chunkGHKMX5UT_cjs = require('./chunk-GHKMX5UT.cjs');
|
|
12
12
|
var lkCore = require('@intellectif/lk-core');
|
|
13
13
|
var react = require('react');
|
|
14
14
|
var jsxRuntime = require('react/jsx-runtime');
|
|
@@ -39,7 +39,7 @@ function ActivityPreview({
|
|
|
39
39
|
theme,
|
|
40
40
|
locale
|
|
41
41
|
}) {
|
|
42
|
-
const s =
|
|
42
|
+
const s = chunkGHKMX5UT_cjs.useLkStrings(strings);
|
|
43
43
|
const type = isRecord(draft) && typeof draft.type === "string" ? draft.type : void 0;
|
|
44
44
|
const contentKey = contentKeyOf(draft);
|
|
45
45
|
const result = react.useMemo(
|
|
@@ -106,7 +106,7 @@ function ActivityPreview({
|
|
|
106
106
|
const data = result.data;
|
|
107
107
|
const key = `${renderMode}:${responseKey}:${recordingKeyOf(data)}`;
|
|
108
108
|
if (marking?.kind === "failed") {
|
|
109
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
109
|
+
return /* @__PURE__ */ jsxRuntime.jsx(chunk6SNNMVBV_cjs.ActivityErrorBoundary, { activityTitle: data.title, strings: s, children: /* @__PURE__ */ jsxRuntime.jsx(Rethrow, { error: marking.error }) }, key);
|
|
110
110
|
}
|
|
111
111
|
const shared = {
|
|
112
112
|
renderMode,
|
|
@@ -133,7 +133,7 @@ function ActivityPreview({
|
|
|
133
133
|
switch (data.type) {
|
|
134
134
|
case "multiple-choice":
|
|
135
135
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
136
|
-
|
|
136
|
+
chunkOKEN6NJL_cjs.MultipleChoice,
|
|
137
137
|
{
|
|
138
138
|
data,
|
|
139
139
|
shuffleSeed: shuffleSeed ?? PREVIEW_SHUFFLE_SEED,
|
|
@@ -142,10 +142,10 @@ function ActivityPreview({
|
|
|
142
142
|
key
|
|
143
143
|
);
|
|
144
144
|
case "fill-in-the-blanks":
|
|
145
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
145
|
+
return /* @__PURE__ */ jsxRuntime.jsx(chunkW2P5RILE_cjs.FillInTheBlanks, { data, ...shared }, key);
|
|
146
146
|
case "gap-select":
|
|
147
147
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
148
|
-
|
|
148
|
+
chunkCB2A56Q7_cjs.GapSelect,
|
|
149
149
|
{
|
|
150
150
|
data,
|
|
151
151
|
shuffleSeed: shuffleSeed ?? PREVIEW_SHUFFLE_SEED,
|
|
@@ -154,11 +154,11 @@ function ActivityPreview({
|
|
|
154
154
|
key
|
|
155
155
|
);
|
|
156
156
|
case "written-response":
|
|
157
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
157
|
+
return /* @__PURE__ */ jsxRuntime.jsx(chunkXZCMUDFP_cjs.WrittenResponse, { data, ...shared }, key);
|
|
158
158
|
case "dictation":
|
|
159
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
159
|
+
return /* @__PURE__ */ jsxRuntime.jsx(chunkWGHMWBEJ_cjs.Dictation, { data, ...shared }, key);
|
|
160
160
|
case "read-aloud":
|
|
161
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
161
|
+
return /* @__PURE__ */ jsxRuntime.jsx(chunkWXESKL75_cjs.ReadAloud, { data, recordingBinding: PREVIEW_RECORDING, ...shared }, key);
|
|
162
162
|
default:
|
|
163
163
|
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "lk-preview-unsupported", role: "note", children: s.unsupportedActivity });
|
|
164
164
|
}
|
|
@@ -232,5 +232,5 @@ function contentKeyOf(value, enclosing = /* @__PURE__ */ new Set()) {
|
|
|
232
232
|
}
|
|
233
233
|
|
|
234
234
|
exports.ActivityPreview = ActivityPreview;
|
|
235
|
-
//# sourceMappingURL=chunk-
|
|
236
|
-
//# sourceMappingURL=chunk-
|
|
235
|
+
//# sourceMappingURL=chunk-GW3YIPDE.cjs.map
|
|
236
|
+
//# sourceMappingURL=chunk-GW3YIPDE.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/ActivityPreview/ActivityPreview.tsx"],"names":["useLkStrings","useMemo","validateDraft","evaluate","slotMediaKey","jsx","Fragment","ActivityErrorBoundary","MultipleChoice","FillInTheBlanks","GapSelect","WrittenResponse","Dictation","ReadAloud"],"mappings":"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","file":"chunk-LK5VXNMR.cjs","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 { Dictation } from '../Dictation/index.js';\nimport { FillInTheBlanks } from '../FillInTheBlanks/index.js';\nimport { GapSelect } from '../GapSelect/index.js';\nimport { MultipleChoice } from '../MultipleChoice/index.js';\nimport { ReadAloud } from '../ReadAloud/index.js';\nimport type { RecordingBinding } from '../ReadAloud/ReadAloud.js';\nimport type { HtmlSanitizer, MediaBudgetBinding, RenderMode } from '../types.js';\nimport { WrittenResponse } from '../WrittenResponse/index.js';\n\n/**\n * The option-shuffle seed a preview uses when none is given. Fixed, so a\n * shuffled question holds still while its author edits it instead of\n * re-ordering on every remount.\n */\nconst PREVIEW_SHUFFLE_SEED = 'lk-activity-preview';\n\n/**\n * The slot a previewed activity stands in for. A recording's play budget is\n * keyed to it and lives only in this mount: the preview counts plays so an\n * author hears the limit a learner gets, and records none of them.\n */\nconst PREVIEW_SLOT_ID = 'preview';\n\n/** Numbers the takes a preview mints keys for, so two are never the same take. */\nlet previewTakeCount = 0;\n\n/**\n * The store a previewed read-aloud records into: it keys the take so the whole\n * flow — record, play it back, submit — runs for the author, and keeps it\n * nowhere, like every other thing a preview does not record.\n *\n * It judges nothing, deliberately. `assess` is optional precisely so this can\n * exist: an author trying a reading out has no assessor to point at, and a\n * preview that demanded one would refuse to render the most common case. The\n * default `practice` mode then shows the \"assessment is not available\" notice,\n * which is the truth about this binding rather than a failure.\n */\nconst PREVIEW_RECORDING: RecordingBinding = {\n upload: async (take) => {\n previewTakeCount += 1;\n return {\n key: `${PREVIEW_SLOT_ID}-take-${previewTakeCount}`,\n mimeType: take.mimeType,\n durationMs: take.durationMs,\n };\n },\n};\n\nexport interface ActivityPreviewProps {\n /**\n * The activity as an editor holds it right now, finished or not. It is checked\n * with `validateDraft` before anything renders — by content, on every render —\n * so a half-written question never reaches a component that assumes a valid\n * one.\n */\n draft: unknown;\n /** The mode to preview in. Defaults to `practice`. */\n renderMode?: RenderMode;\n /**\n * A learner response to show as if a learner had given it. In `practice` and\n * `exam` it seeds the answer, which stays editable; in `review` it is the\n * submitted answer, marked by `evaluate()` unless `outcome` is given.\n */\n response?: LearnerResponse;\n /**\n * The outcome `review` marks the response with, instead of the one\n * `evaluate()` computes — which is how to preview a returned grade on a written\n * response, since the SDK cannot score one. Ignored in other modes.\n */\n outcome?: ItemOutcome;\n /**\n * Rendered in place of the activity while the draft is not complete, with the\n * issues `validateDraft` found — so an editor can list them in its own words.\n * The issue messages are English, and a preview that rendered them would put\n * untranslatable text on screen. Without it, the preview shows the\n * `previewIncomplete` or `previewInvalid` string.\n */\n fallback?: (result: DraftNotComplete) => ReactNode;\n /**\n * Renderers for activity types beyond the built-ins, keyed by `type`, as on\n * `<ActivitySequence>`. An entry receives the shared prop contract and\n * nothing else, so — like the shuffle seed below — the in-memory recording\n * binding a previewed read-aloud is given does not reach one. Nor does the\n * `question` a sequence hands a renderer: a preview is one question with no\n * set around it, so there is no slot to report to and nothing to page away\n * from. A renderer that uses it must treat it as optional, which its type\n * already says.\n */\n renderers?: Readonly<Record<string, ActivityRenderer>>;\n /**\n * Seeds `<MultipleChoice>`'s option shuffle. Defaults to a fixed seed, so the\n * order holds while options are edited; adding or removing one deals them\n * again, because the shuffle orders by position. A `renderers` entry is given\n * no seed, as on `<ActivitySequence>`.\n */\n shuffleSeed?: string;\n /** Renders author-supplied rich text. See `HtmlSanitizer`. */\n sanitizeHtml?: HtmlSanitizer;\n /** Overrides the SDK's chrome text, for the preview's notice and the activity alike. */\n strings?: LkStringsOverride;\n /** Per-instance token overrides, passed to the activity. */\n theme?: Partial<ThemeTokens>;\n /** BCP 47 tag of the interface language, passed to the activity. */\n locale?: string;\n}\n\ntype Marking = { kind: 'marked'; outcome: ItemOutcome } | { kind: 'failed'; error: Error };\n\n/**\n * Renders an activity from an editor's draft, in any render mode, with a\n * simulated learner response — and renders a notice instead while the draft is\n * not an activity yet.\n *\n * Nothing it renders is recorded: no callback is wired, so a practice attempt in\n * a preview scores locally and goes nowhere, and a recording's play limit is\n * counted in memory only.\n *\n * A draft with no string `type`, a type nobody registered, and a `response` of a\n * different type from the draft are wiring errors, and throw.\n */\nexport function ActivityPreview({\n draft,\n renderMode = 'practice',\n response,\n outcome,\n fallback,\n renderers,\n shuffleSeed,\n sanitizeHtml,\n strings,\n theme,\n locale,\n}: ActivityPreviewProps) {\n const s = useLkStrings(strings);\n const type = isRecord(draft) && typeof draft.type === 'string' ? draft.type : undefined;\n\n // Validated on the draft's CONTENT, not its identity. The activity components\n // restart whenever `data` changes identity, and an editor that rebuilds its\n // payload on every change would otherwise restart the question an author is\n // trying out each time anything else on the page was edited. The key is read\n // on every render, so a draft changed in place is checked again too.\n const contentKey = contentKeyOf(draft);\n // biome-ignore lint/correctness/useExhaustiveDependencies: the draft's content (contentKey) is the trigger, not its identity\n const result = useMemo(\n () => (type === undefined ? undefined : validateDraft(type as ActivityType, draft)),\n [type, contentKey],\n );\n const activity = result?.status === 'complete' ? result.data : undefined;\n const responseFits = response === undefined || response.type === type;\n const responseKey = response === undefined ? 'none' : contentKeyOf(response);\n\n // biome-ignore lint/correctness/useExhaustiveDependencies: the response's content (responseKey) is the trigger, not its identity\n const marking = useMemo((): Marking | undefined => {\n if (renderMode !== 'review' || activity === undefined) {\n return undefined;\n }\n if (outcome !== undefined) {\n return { kind: 'marked', outcome };\n }\n if (response === undefined || !responseFits) {\n return undefined;\n }\n try {\n return { kind: 'marked', outcome: evaluate(activity, response) };\n } catch (thrown) {\n // Scored here, during this component's own render, where no error boundary\n // inside the activity can catch it — so it is handed to one below.\n return {\n kind: 'failed',\n error: thrown instanceof Error ? thrown : new Error(String(thrown)),\n };\n }\n }, [renderMode, activity, outcome, responseKey, responseFits]);\n\n const activityId = activity?.id;\n const mediaBudget = useMemo<MediaBudgetBinding>(\n () => ({\n key: slotMediaKey(PREVIEW_SLOT_ID),\n slotId: PREVIEW_SLOT_ID,\n index: 0,\n ...(activityId !== undefined ? { activityId } : {}),\n }),\n [activityId],\n );\n\n // All hooks are called before these throws, so hook order stays stable.\n if (type === undefined || result === undefined) {\n throw new Error(\n '<ActivityPreview> needs a draft object with a string `type`, to know which activity to check and render.',\n );\n }\n // Checked against the draft's own type, whatever state the draft is in. A\n // mis-wired response is a wiring error however unfinished the draft is, and\n // waiting for the draft to be complete would throw on the very edit that\n // completes it.\n if (response !== undefined && !responseFits) {\n throw new Error(\n `<ActivityPreview> was given a \"${response.type}\" response for a \"${type}\" activity.`,\n );\n }\n if (result.status !== 'complete') {\n if (fallback !== undefined) {\n return <>{fallback(result)}</>;\n }\n return (\n <div\n className=\"lk-preview-unavailable\"\n role=\"note\"\n data-status={result.status}\n {...(locale !== undefined ? { lang: locale } : {})}\n >\n {result.status === 'invalid' ? s.previewInvalid : s.previewIncomplete}\n </div>\n );\n }\n const data = result.data;\n\n // A different simulated response, or a different mode, mounts the activity\n // afresh, play count included: a seeded answer is read at mount, and a practice\n // attempt the author submitted must not carry over into an exam preview. So\n // does a different recording or playback policy, which is a different listening\n // budget — the transport reads its count once, when it mounts.\n const key = `${renderMode}:${responseKey}:${recordingKeyOf(data)}`;\n\n if (marking?.kind === 'failed') {\n // The response is one the scorer cannot read, or a registered scorer threw.\n // Shown as the activity's error fallback, as a render failure would be.\n return (\n <ActivityErrorBoundary key={key} activityTitle={data.title} strings={s}>\n <Rethrow error={marking.error} />\n </ActivityErrorBoundary>\n );\n }\n\n const shared = {\n renderMode,\n // Without a binding a budgeted recording counts nothing in `practice`, and in\n // `exam` the media component refuses to render at all — rightly, on a real\n // paper, where nothing would persist the count. A preview persists nothing by\n // design, so it keeps the count in memory and says so.\n mediaBudget,\n ...(sanitizeHtml !== undefined ? { sanitizeHtml } : {}),\n ...(strings !== undefined ? { strings } : {}),\n ...(theme !== undefined ? { theme } : {}),\n ...(locale !== undefined ? { locale } : {}),\n // Seeded rather than controlled, in every mode, exactly as `<ActivitySequence>`\n // hands a restored answer to its slots — so a renderer written for the\n // sequence reads it here too. `review` is read-only either way, and `key`\n // above re-seeds whenever the response changes.\n ...(response !== undefined ? { defaultValue: response } : {}),\n ...(marking !== undefined ? { outcome: marking.outcome } : {}),\n };\n\n const Custom = renderers?.[data.type];\n if (Custom !== undefined) {\n return <Custom key={key} data={data} {...shared} />;\n }\n switch (data.type) {\n case 'multiple-choice':\n return (\n <MultipleChoice\n key={key}\n data={data}\n shuffleSeed={shuffleSeed ?? PREVIEW_SHUFFLE_SEED}\n {...shared}\n />\n );\n case 'fill-in-the-blanks':\n return <FillInTheBlanks key={key} data={data} {...shared} />;\n case 'gap-select':\n return (\n <GapSelect\n key={key}\n data={data}\n shuffleSeed={shuffleSeed ?? PREVIEW_SHUFFLE_SEED}\n {...shared}\n />\n );\n case 'written-response':\n return <WrittenResponse key={key} data={data} {...shared} />;\n case 'dictation':\n return <Dictation key={key} data={data} {...shared} />;\n case 'read-aloud':\n return <ReadAloud key={key} data={data} recordingBinding={PREVIEW_RECORDING} {...shared} />;\n default:\n return (\n <div className=\"lk-preview-unsupported\" role=\"note\">\n {s.unsupportedActivity}\n </div>\n );\n }\n}\n\n/** Throws `error` while rendering, so the error boundary around it shows its fallback. */\nfunction Rethrow({ error }: { error: Error }): never {\n throw error;\n}\n\nfunction isRecord(value: unknown): value is Readonly<Record<string, unknown>> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/**\n * The recording and its playback policy — not its description, which changes\n * no budget — plus the slower file a dictation or a read-aloud carries, which\n * follows the same policy and must remount when it is swapped for another.\n */\nfunction recordingKeyOf(data: unknown): string {\n const media = isRecord(data) ? data.media : undefined;\n const slowMedia = isRecord(data) ? data.slowMedia : undefined;\n const slowUrl = isRecord(slowMedia) ? slowMedia.url : undefined;\n if (!isRecord(media) && slowUrl === undefined) {\n return '';\n }\n return contentKeyOf([\n isRecord(media) ? media.type : undefined,\n isRecord(media) ? media.url : undefined,\n isRecord(media) ? media.playback : undefined,\n slowUrl,\n ]);\n}\n\nconst identities = new WeakMap<object, number>();\nlet lastIdentity = 0;\n\n/** A key for a value compared by identity: the same object always gets the same one. */\nfunction identityKeyOf(value: object): string {\n let identity = identities.get(value);\n if (identity === undefined) {\n lastIdentity += 1;\n identity = lastIdentity;\n identities.set(value, identity);\n }\n return `#${identity}`;\n}\n\n/**\n * A string that changes exactly when a value's content does.\n *\n * Object keys are sorted, because key order is not content: a draft saved to a\n * JSON column comes back with its keys reordered. Every value is written in a\n * form no other value shares — JSON would write a non-finite number as `null`\n * and drop `undefined`, and those decide different issues. What JSON does not\n * describe — an instance of a class, a function, a reference back to an\n * enclosing object — is compared by identity, and a symbol by its description.\n */\nfunction contentKeyOf(value: unknown, enclosing: Set<object> = new Set()): string {\n switch (typeof value) {\n case 'string':\n return JSON.stringify(value);\n case 'number':\n // `String` keeps NaN, the infinities and every other number apart; only -0\n // needs saying.\n return Object.is(value, -0) ? '-0' : String(value);\n case 'bigint':\n return `${value}n`;\n case 'boolean':\n case 'undefined':\n return String(value);\n case 'symbol':\n return `Symbol(${JSON.stringify(value.description ?? null)})`;\n case 'function':\n return identityKeyOf(value);\n }\n // Every other `typeof` has returned above: this is `null`, or an object.\n if (value === null || typeof value !== 'object') {\n return 'null';\n }\n const prototype = Object.getPrototypeOf(value);\n const plain = Array.isArray(value) || prototype === Object.prototype || prototype === null;\n if (!plain || enclosing.has(value)) {\n return identityKeyOf(value);\n }\n enclosing.add(value);\n try {\n if (Array.isArray(value)) {\n return `[${Array.from(value, (item) => contentKeyOf(item, enclosing)).join(',')}]`;\n }\n const record = value as Record<string, unknown>;\n const fields = Object.keys(record)\n .sort()\n .map((field) => `${JSON.stringify(field)}:${contentKeyOf(record[field], enclosing)}`);\n return `{${fields.join(',')}}`;\n } finally {\n enclosing.delete(value);\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/components/ActivityPreview/ActivityPreview.tsx"],"names":["useLkStrings","useMemo","validateDraft","evaluate","slotMediaKey","jsx","Fragment","ActivityErrorBoundary","MultipleChoice","FillInTheBlanks","GapSelect","WrittenResponse","Dictation","ReadAloud"],"mappings":"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","file":"chunk-GW3YIPDE.cjs","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 { Dictation } from '../Dictation/index.js';\nimport { FillInTheBlanks } from '../FillInTheBlanks/index.js';\nimport { GapSelect } from '../GapSelect/index.js';\nimport { MultipleChoice } from '../MultipleChoice/index.js';\nimport { ReadAloud } from '../ReadAloud/index.js';\nimport type { RecordingBinding } from '../ReadAloud/ReadAloud.js';\nimport type { HtmlSanitizer, MediaBudgetBinding, RenderMode } from '../types.js';\nimport { WrittenResponse } from '../WrittenResponse/index.js';\n\n/**\n * The option-shuffle seed a preview uses when none is given. Fixed, so a\n * shuffled question holds still while its author edits it instead of\n * re-ordering on every remount.\n */\nconst PREVIEW_SHUFFLE_SEED = 'lk-activity-preview';\n\n/**\n * The slot a previewed activity stands in for. A recording's play budget is\n * keyed to it and lives only in this mount: the preview counts plays so an\n * author hears the limit a learner gets, and records none of them.\n */\nconst PREVIEW_SLOT_ID = 'preview';\n\n/** Numbers the takes a preview mints keys for, so two are never the same take. */\nlet previewTakeCount = 0;\n\n/**\n * The store a previewed read-aloud records into: it keys the take so the whole\n * flow — record, play it back, submit — runs for the author, and keeps it\n * nowhere, like every other thing a preview does not record.\n *\n * It judges nothing, deliberately. `assess` is optional precisely so this can\n * exist: an author trying a reading out has no assessor to point at, and a\n * preview that demanded one would refuse to render the most common case. The\n * default `practice` mode then shows the \"assessment is not available\" notice,\n * which is the truth about this binding rather than a failure.\n */\nconst PREVIEW_RECORDING: RecordingBinding = {\n upload: async (take) => {\n previewTakeCount += 1;\n return {\n key: `${PREVIEW_SLOT_ID}-take-${previewTakeCount}`,\n mimeType: take.mimeType,\n durationMs: take.durationMs,\n };\n },\n};\n\nexport interface ActivityPreviewProps {\n /**\n * The activity as an editor holds it right now, finished or not. It is checked\n * with `validateDraft` before anything renders — by content, on every render —\n * so a half-written question never reaches a component that assumes a valid\n * one.\n */\n draft: unknown;\n /** The mode to preview in. Defaults to `practice`. */\n renderMode?: RenderMode;\n /**\n * A learner response to show as if a learner had given it. In `practice` and\n * `exam` it seeds the answer, which stays editable; in `review` it is the\n * submitted answer, marked by `evaluate()` unless `outcome` is given.\n */\n response?: LearnerResponse;\n /**\n * The outcome `review` marks the response with, instead of the one\n * `evaluate()` computes — which is how to preview a returned grade on a written\n * response, since the SDK cannot score one. Ignored in other modes.\n */\n outcome?: ItemOutcome;\n /**\n * Rendered in place of the activity while the draft is not complete, with the\n * issues `validateDraft` found — so an editor can list them in its own words.\n * The issue messages are English, and a preview that rendered them would put\n * untranslatable text on screen. Without it, the preview shows the\n * `previewIncomplete` or `previewInvalid` string.\n */\n fallback?: (result: DraftNotComplete) => ReactNode;\n /**\n * Renderers for activity types beyond the built-ins, keyed by `type`, as on\n * `<ActivitySequence>`. An entry receives the shared prop contract and\n * nothing else, so — like the shuffle seed below — the in-memory recording\n * binding a previewed read-aloud is given does not reach one. Nor does the\n * `question` a sequence hands a renderer: a preview is one question with no\n * set around it, so there is no slot to report to and nothing to page away\n * from. A renderer that uses it must treat it as optional, which its type\n * already says.\n */\n renderers?: Readonly<Record<string, ActivityRenderer>>;\n /**\n * Seeds `<MultipleChoice>`'s option shuffle. Defaults to a fixed seed, so the\n * order holds while options are edited; adding or removing one deals them\n * again, because the shuffle orders by position. A `renderers` entry is given\n * no seed, as on `<ActivitySequence>`.\n */\n shuffleSeed?: string;\n /** Renders author-supplied rich text. See `HtmlSanitizer`. */\n sanitizeHtml?: HtmlSanitizer;\n /** Overrides the SDK's chrome text, for the preview's notice and the activity alike. */\n strings?: LkStringsOverride;\n /** Per-instance token overrides, passed to the activity. */\n theme?: Partial<ThemeTokens>;\n /** BCP 47 tag of the interface language, passed to the activity. */\n locale?: string;\n}\n\ntype Marking = { kind: 'marked'; outcome: ItemOutcome } | { kind: 'failed'; error: Error };\n\n/**\n * Renders an activity from an editor's draft, in any render mode, with a\n * simulated learner response — and renders a notice instead while the draft is\n * not an activity yet.\n *\n * Nothing it renders is recorded: no callback is wired, so a practice attempt in\n * a preview scores locally and goes nowhere, and a recording's play limit is\n * counted in memory only.\n *\n * A draft with no string `type`, a type nobody registered, and a `response` of a\n * different type from the draft are wiring errors, and throw.\n */\nexport function ActivityPreview({\n draft,\n renderMode = 'practice',\n response,\n outcome,\n fallback,\n renderers,\n shuffleSeed,\n sanitizeHtml,\n strings,\n theme,\n locale,\n}: ActivityPreviewProps) {\n const s = useLkStrings(strings);\n const type = isRecord(draft) && typeof draft.type === 'string' ? draft.type : undefined;\n\n // Validated on the draft's CONTENT, not its identity. The activity components\n // restart whenever `data` changes identity, and an editor that rebuilds its\n // payload on every change would otherwise restart the question an author is\n // trying out each time anything else on the page was edited. The key is read\n // on every render, so a draft changed in place is checked again too.\n const contentKey = contentKeyOf(draft);\n // biome-ignore lint/correctness/useExhaustiveDependencies: the draft's content (contentKey) is the trigger, not its identity\n const result = useMemo(\n () => (type === undefined ? undefined : validateDraft(type as ActivityType, draft)),\n [type, contentKey],\n );\n const activity = result?.status === 'complete' ? result.data : undefined;\n const responseFits = response === undefined || response.type === type;\n const responseKey = response === undefined ? 'none' : contentKeyOf(response);\n\n // biome-ignore lint/correctness/useExhaustiveDependencies: the response's content (responseKey) is the trigger, not its identity\n const marking = useMemo((): Marking | undefined => {\n if (renderMode !== 'review' || activity === undefined) {\n return undefined;\n }\n if (outcome !== undefined) {\n return { kind: 'marked', outcome };\n }\n if (response === undefined || !responseFits) {\n return undefined;\n }\n try {\n return { kind: 'marked', outcome: evaluate(activity, response) };\n } catch (thrown) {\n // Scored here, during this component's own render, where no error boundary\n // inside the activity can catch it — so it is handed to one below.\n return {\n kind: 'failed',\n error: thrown instanceof Error ? thrown : new Error(String(thrown)),\n };\n }\n }, [renderMode, activity, outcome, responseKey, responseFits]);\n\n const activityId = activity?.id;\n const mediaBudget = useMemo<MediaBudgetBinding>(\n () => ({\n key: slotMediaKey(PREVIEW_SLOT_ID),\n slotId: PREVIEW_SLOT_ID,\n index: 0,\n ...(activityId !== undefined ? { activityId } : {}),\n }),\n [activityId],\n );\n\n // All hooks are called before these throws, so hook order stays stable.\n if (type === undefined || result === undefined) {\n throw new Error(\n '<ActivityPreview> needs a draft object with a string `type`, to know which activity to check and render.',\n );\n }\n // Checked against the draft's own type, whatever state the draft is in. A\n // mis-wired response is a wiring error however unfinished the draft is, and\n // waiting for the draft to be complete would throw on the very edit that\n // completes it.\n if (response !== undefined && !responseFits) {\n throw new Error(\n `<ActivityPreview> was given a \"${response.type}\" response for a \"${type}\" activity.`,\n );\n }\n if (result.status !== 'complete') {\n if (fallback !== undefined) {\n return <>{fallback(result)}</>;\n }\n return (\n <div\n className=\"lk-preview-unavailable\"\n role=\"note\"\n data-status={result.status}\n {...(locale !== undefined ? { lang: locale } : {})}\n >\n {result.status === 'invalid' ? s.previewInvalid : s.previewIncomplete}\n </div>\n );\n }\n const data = result.data;\n\n // A different simulated response, or a different mode, mounts the activity\n // afresh, play count included: a seeded answer is read at mount, and a practice\n // attempt the author submitted must not carry over into an exam preview. So\n // does a different recording or playback policy, which is a different listening\n // budget — the transport reads its count once, when it mounts.\n const key = `${renderMode}:${responseKey}:${recordingKeyOf(data)}`;\n\n if (marking?.kind === 'failed') {\n // The response is one the scorer cannot read, or a registered scorer threw.\n // Shown as the activity's error fallback, as a render failure would be.\n return (\n <ActivityErrorBoundary key={key} activityTitle={data.title} strings={s}>\n <Rethrow error={marking.error} />\n </ActivityErrorBoundary>\n );\n }\n\n const shared = {\n renderMode,\n // Without a binding a budgeted recording counts nothing in `practice`, and in\n // `exam` the media component refuses to render at all — rightly, on a real\n // paper, where nothing would persist the count. A preview persists nothing by\n // design, so it keeps the count in memory and says so.\n mediaBudget,\n ...(sanitizeHtml !== undefined ? { sanitizeHtml } : {}),\n ...(strings !== undefined ? { strings } : {}),\n ...(theme !== undefined ? { theme } : {}),\n ...(locale !== undefined ? { locale } : {}),\n // Seeded rather than controlled, in every mode, exactly as `<ActivitySequence>`\n // hands a restored answer to its slots — so a renderer written for the\n // sequence reads it here too. `review` is read-only either way, and `key`\n // above re-seeds whenever the response changes.\n ...(response !== undefined ? { defaultValue: response } : {}),\n ...(marking !== undefined ? { outcome: marking.outcome } : {}),\n };\n\n const Custom = renderers?.[data.type];\n if (Custom !== undefined) {\n return <Custom key={key} data={data} {...shared} />;\n }\n switch (data.type) {\n case 'multiple-choice':\n return (\n <MultipleChoice\n key={key}\n data={data}\n shuffleSeed={shuffleSeed ?? PREVIEW_SHUFFLE_SEED}\n {...shared}\n />\n );\n case 'fill-in-the-blanks':\n return <FillInTheBlanks key={key} data={data} {...shared} />;\n case 'gap-select':\n return (\n <GapSelect\n key={key}\n data={data}\n shuffleSeed={shuffleSeed ?? PREVIEW_SHUFFLE_SEED}\n {...shared}\n />\n );\n case 'written-response':\n return <WrittenResponse key={key} data={data} {...shared} />;\n case 'dictation':\n return <Dictation key={key} data={data} {...shared} />;\n case 'read-aloud':\n return <ReadAloud key={key} data={data} recordingBinding={PREVIEW_RECORDING} {...shared} />;\n default:\n return (\n <div className=\"lk-preview-unsupported\" role=\"note\">\n {s.unsupportedActivity}\n </div>\n );\n }\n}\n\n/** Throws `error` while rendering, so the error boundary around it shows its fallback. */\nfunction Rethrow({ error }: { error: Error }): never {\n throw error;\n}\n\nfunction isRecord(value: unknown): value is Readonly<Record<string, unknown>> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/**\n * The recording and its playback policy — not its description, which changes\n * no budget — plus the slower file a dictation or a read-aloud carries, which\n * follows the same policy and must remount when it is swapped for another.\n */\nfunction recordingKeyOf(data: unknown): string {\n const media = isRecord(data) ? data.media : undefined;\n const slowMedia = isRecord(data) ? data.slowMedia : undefined;\n const slowUrl = isRecord(slowMedia) ? slowMedia.url : undefined;\n if (!isRecord(media) && slowUrl === undefined) {\n return '';\n }\n return contentKeyOf([\n isRecord(media) ? media.type : undefined,\n isRecord(media) ? media.url : undefined,\n isRecord(media) ? media.playback : undefined,\n slowUrl,\n ]);\n}\n\nconst identities = new WeakMap<object, number>();\nlet lastIdentity = 0;\n\n/** A key for a value compared by identity: the same object always gets the same one. */\nfunction identityKeyOf(value: object): string {\n let identity = identities.get(value);\n if (identity === undefined) {\n lastIdentity += 1;\n identity = lastIdentity;\n identities.set(value, identity);\n }\n return `#${identity}`;\n}\n\n/**\n * A string that changes exactly when a value's content does.\n *\n * Object keys are sorted, because key order is not content: a draft saved to a\n * JSON column comes back with its keys reordered. Every value is written in a\n * form no other value shares — JSON would write a non-finite number as `null`\n * and drop `undefined`, and those decide different issues. What JSON does not\n * describe — an instance of a class, a function, a reference back to an\n * enclosing object — is compared by identity, and a symbol by its description.\n */\nfunction contentKeyOf(value: unknown, enclosing: Set<object> = new Set()): string {\n switch (typeof value) {\n case 'string':\n return JSON.stringify(value);\n case 'number':\n // `String` keeps NaN, the infinities and every other number apart; only -0\n // needs saying.\n return Object.is(value, -0) ? '-0' : String(value);\n case 'bigint':\n return `${value}n`;\n case 'boolean':\n case 'undefined':\n return String(value);\n case 'symbol':\n return `Symbol(${JSON.stringify(value.description ?? null)})`;\n case 'function':\n return identityKeyOf(value);\n }\n // Every other `typeof` has returned above: this is `null`, or an object.\n if (value === null || typeof value !== 'object') {\n return 'null';\n }\n const prototype = Object.getPrototypeOf(value);\n const plain = Array.isArray(value) || prototype === Object.prototype || prototype === null;\n if (!plain || enclosing.has(value)) {\n return identityKeyOf(value);\n }\n enclosing.add(value);\n try {\n if (Array.isArray(value)) {\n return `[${Array.from(value, (item) => contentKeyOf(item, enclosing)).join(',')}]`;\n }\n const record = value as Record<string, unknown>;\n const fields = Object.keys(record)\n .sort()\n .map((field) => `${JSON.stringify(field)}:${contentKeyOf(record[field], enclosing)}`);\n return `{${fields.join(',')}}`;\n } finally {\n enclosing.delete(value);\n }\n}\n"]}
|
|
@@ -23,5 +23,5 @@ exports.SequenceSlotContext = SequenceSlotContext;
|
|
|
23
23
|
exports.mintTake = mintTake;
|
|
24
24
|
exports.stampTake = stampTake;
|
|
25
25
|
exports.takeOf = takeOf;
|
|
26
|
-
//# sourceMappingURL=chunk-
|
|
27
|
-
//# sourceMappingURL=chunk-
|
|
26
|
+
//# sourceMappingURL=chunk-J2QIHZ3H.cjs.map
|
|
27
|
+
//# sourceMappingURL=chunk-J2QIHZ3H.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/shared/sequence-slot.ts"],"names":["createContext"],"mappings":";;;;;AAoFO,IAAM,mBAAA,GAAsBA,oBAA0C,IAAI;AASjF,IAAI,QAAA,GAAW,CAAA;AAGR,SAAS,QAAA,GAAmB;AACjC,EAAA,QAAA,IAAY,CAAA;AACZ,EAAA,OAAO,QAAA;AACT;AAOA,IAAM,KAAA,uBAAY,OAAA,EAAwB;AAGnC,SAAS,SAAA,CAA4B,OAAU,IAAA,EAAiB;AACrE,EAAA,KAAA,CAAM,GAAA,CAAI,OAAO,IAAI,CAAA;AACrB,EAAA,OAAO,KAAA;AACT;AAGO,SAAS,OAAO,KAAA,EAAoC;AACzD,EAAA,OAAO,OAAO,UAAU,QAAA,IAAY,KAAA,KAAU,OAAO,KAAA,CAAM,GAAA,CAAI,KAAK,CAAA,GAAI,MAAA;AAC1E","file":"chunk-J2QIHZ3H.cjs","sourcesContent":["'use client';\n\nimport type { ResolvedDeliveryPolicy, ResolvedItemScoringPolicy } from '@intellectif/lk-core';\nimport { createContext } from 'react';\nimport type { RenderMode } from '../types.js';\n\n/**\n * How far a take that was handed in has got, as a pager deciding whether its\n * set is finished needs to know it.\n *\n * - `in-flight` — being stored or being judged. The set waits for it.\n * - `retryable` — it failed, and the learner is being offered another try at\n * the SAME take. The set waits for it while the learner is on that question.\n * - `settled` — nothing more will come of it: a grade, a take with no grade, a\n * failure nobody can retry, or a take the learner walked away from.\n */\nexport type TakeState = 'in-flight' | 'retryable' | 'settled';\n\n/**\n * Where a question is with its tries, as a pager deciding whether its set is\n * finished needs to know it.\n *\n * - `offered` — a graded answer short of full marks, with another try on offer.\n * The set waits for it while the learner is on that question: leaving it is\n * declining the try.\n * - `retrying` — the learner pressed \"Try again\" and has not answered again.\n * The set waits for it wherever the learner is, as it waits for any question\n * not yet answered: the grade it holds is the one being replaced.\n * - `none` — neither.\n */\nexport type TriesState = 'none' | 'offered' | 'retrying';\n\n/**\n * What a pager hands the one slot it renders, and only through\n * {@link SequenceSlotContext} — never as a prop, so none of it can appear on a\n * public component's signature. A consumer string in any of it could stop\n * another component's microphone or finish another paper's set.\n */\nexport interface SequenceSlotChannel {\n /**\n * The capture-registry group whose captures stop when the slot's pane is\n * hidden. Minted per pager and per slot, so no two pagers share one.\n */\n readonly captureGroup: string;\n /**\n * The mode the PAPER around this question is being delivered in, read live.\n *\n * It is how the AI hooks know an exam they were not told about. A host that\n * draws its own question passes `renderMode` on to them, but nothing makes\n * it: the hooks would then default to `practice` and, under an\n * `LkAiProvider` somewhere above, offer hints on a paper of record. The set\n * around the question decides, and an `exam` here wins over anything the\n * caller passes or forgets to pass.\n */\n readonly renderMode: RenderMode;\n /**\n * The delivery policy of the PAPER around this question, read live, for the\n * same reason as the mode: whatever a host's own question was handed, the\n * paper's restrictions hold in it. See `useDeliveryPolicy`.\n */\n readonly delivery: ResolvedDeliveryPolicy;\n /**\n * The scoring policy of the PAPER around this question, read live, when the\n * paper was given one: it holds for every question in it, however drawn.\n * `undefined` when the paper has none, so a question's own applies.\n */\n readonly scoring: ResolvedItemScoringPolicy | undefined;\n /**\n * Whether the paper has closed every question's tries: it reported its set,\n * or its learner finished. A \"Try again\" after that would change a grade the\n * paper has already handed on.\n */\n readonly triesClosed: boolean;\n /** Tells the paper where this question is with its tries: see {@link TriesState}. */\n triesState(state: TriesState): void;\n /**\n * Reports the state of the take numbered `take`, a number from\n * {@link mintTake}. A take numbered below the slot's latest is a take the\n * learner has since replaced, and the pager ignores it.\n */\n takeState(take: number, state: TakeState): void;\n}\n\n/** `null` outside a pager: a standalone component belongs to no slot. */\nexport const SequenceSlotContext = createContext<SequenceSlotChannel | null>(null);\n\n/**\n * The last take number handed out. Module-level and never reset, so a number\n * is unique for the life of the page: across mounts, across papers, and across\n * two pagers side by side. A result can therefore only ever match the take that\n * produced it, whatever slot and activity ids a later paper reuses — ids a host\n * chooses, and can repeat, never decide it.\n */\nlet lastTake = 0;\n\n/** A take number no other take on this page has had, or will have. */\nexport function mintTake(): number {\n lastTake += 1;\n return lastTake;\n}\n\n/**\n * The take each reported value belongs to. A `WeakMap` rather than a field, so\n * a response or a result a host receives carries no extra key it would persist\n * or compare, and the number never outlives the object.\n */\nconst takes = new WeakMap<object, number>();\n\n/** Marks `value` as produced by take `take`, and returns it. */\nexport function stampTake<T extends object>(value: T, take: number): T {\n takes.set(value, take);\n return value;\n}\n\n/** The take `value` was produced by, or `undefined` when nothing stamped it. */\nexport function takeOf(value: unknown): number | undefined {\n return typeof value === 'object' && value !== null ? takes.get(value) : undefined;\n}\n"]}
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import { AiHints, AiExplanation } from './chunk-
|
|
3
|
-
import { useLearnerAi } from './chunk-
|
|
4
|
-
import { useDeliveryPolicy, FeedbackRegion, outcomeShowsMarks } from './chunk-
|
|
5
|
-
import {
|
|
2
|
+
import { useItemScoringPolicy, useTries, hintsOf, useComponentAiHints, triesSummary, HintCost, AiHints, TryActions, AiExplanation } from './chunk-GND3OBVE.js';
|
|
3
|
+
import { useLearnerAi } from './chunk-MCTNLUKO.js';
|
|
4
|
+
import { useDeliveryPolicy, FeedbackRegion, outcomeShowsMarks } from './chunk-PJMXHDY5.js';
|
|
5
|
+
import { stampTake, mintTake } from './chunk-U4KTOH7C.js';
|
|
6
|
+
import { ActivityErrorBoundary } from './chunk-U6SEII5V.js';
|
|
6
7
|
import { isDevelopment, ANONYMOUS_ACTOR, objectIdFor } from './chunk-PPFVTXGJ.js';
|
|
7
|
-
import { ActivityMedia } from './chunk-
|
|
8
|
+
import { ActivityMedia } from './chunk-X4YWXZ4J.js';
|
|
8
9
|
import { useActivityState } from './chunk-6N43WDVG.js';
|
|
9
|
-
import { useLkStrings } from './chunk-
|
|
10
|
-
import { assertRedacted, validateActivity, ActivitySchemaError, score, xAPIBuilder, xapiDefinitionFor } from '@intellectif/lk-core';
|
|
10
|
+
import { useLkStrings } from './chunk-EYLBSS4Y.js';
|
|
11
|
+
import { assertRedacted, validateActivity, ActivitySchemaError, score, computePassThreshold, xAPIBuilder, xapiDefinitionFor } from '@intellectif/lk-core';
|
|
11
12
|
import { useMemo, useState, useRef, useEffect } from 'react';
|
|
12
13
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
13
14
|
|
|
@@ -66,13 +67,15 @@ function FillInTheBlanks({
|
|
|
66
67
|
disabled,
|
|
67
68
|
showCorrectAnswers,
|
|
68
69
|
ai: aiProp,
|
|
69
|
-
delivery
|
|
70
|
+
delivery,
|
|
71
|
+
scoring
|
|
70
72
|
}) {
|
|
71
73
|
const isExam = renderMode === "exam";
|
|
72
74
|
const isReview = renderMode === "review";
|
|
73
75
|
const s = useLkStrings(strings);
|
|
74
76
|
const ai = useLearnerAi(aiProp);
|
|
75
77
|
const policy = useDeliveryPolicy(delivery);
|
|
78
|
+
const scoringPolicy = useItemScoringPolicy(scoring);
|
|
76
79
|
const devError = useMemo(() => {
|
|
77
80
|
if (!isDevelopment()) {
|
|
78
81
|
return null;
|
|
@@ -91,14 +94,27 @@ function FillInTheBlanks({
|
|
|
91
94
|
const { state, start, complete, getTimeSpent, reset } = useActivityState(
|
|
92
95
|
defaultSubmitted === true ? "completed" : "idle"
|
|
93
96
|
);
|
|
97
|
+
const tries = useTries({
|
|
98
|
+
policy: scoringPolicy,
|
|
99
|
+
graded: renderMode === "practice" && policy.feedback,
|
|
100
|
+
submitted: state === "completed",
|
|
101
|
+
disabled: disabled === true
|
|
102
|
+
});
|
|
103
|
+
const resetTries = tries.reset;
|
|
94
104
|
const isControlled = value !== void 0;
|
|
95
105
|
const [internalAnswers, setInternalAnswers] = useState(
|
|
96
106
|
() => answersOf(defaultValue)
|
|
97
107
|
);
|
|
98
108
|
const [revealed, setRevealed] = useState(() => /* @__PURE__ */ new Set());
|
|
109
|
+
const [seen, setSeen] = useState(() => /* @__PURE__ */ new Set());
|
|
99
110
|
const [result, setResult] = useState(null);
|
|
100
111
|
const [summary, setSummary] = useState(null);
|
|
112
|
+
const [overall, setOverall] = useState(null);
|
|
101
113
|
const [feedbackHidden, setFeedbackHidden] = useState(false);
|
|
114
|
+
const seedHintsRef = useRef(hintsOf(value ?? defaultValue));
|
|
115
|
+
const formRef = useRef(null);
|
|
116
|
+
const feedbackRef = useRef(null);
|
|
117
|
+
const focusAfterRef = useRef(null);
|
|
102
118
|
const answers = isControlled ? answersOf(value) : internalAnswers;
|
|
103
119
|
const defaultValueRef = useRef(defaultValue);
|
|
104
120
|
useEffect(() => {
|
|
@@ -116,11 +132,15 @@ function FillInTheBlanks({
|
|
|
116
132
|
lastDataRef.current = data;
|
|
117
133
|
setInternalAnswers(answersOf(defaultValueRef.current));
|
|
118
134
|
setRevealed(/* @__PURE__ */ new Set());
|
|
135
|
+
setSeen(/* @__PURE__ */ new Set());
|
|
119
136
|
setResult(null);
|
|
120
137
|
setSummary(null);
|
|
138
|
+
setOverall(null);
|
|
121
139
|
setFeedbackHidden(false);
|
|
140
|
+
seedHintsRef.current = hintsOf(defaultValueRef.current);
|
|
141
|
+
resetTries();
|
|
122
142
|
reset(defaultSubmittedRef.current === true ? "completed" : "idle");
|
|
123
|
-
}, [data, reset]);
|
|
143
|
+
}, [data, reset, resetTries]);
|
|
124
144
|
useEffect(() => {
|
|
125
145
|
if (isDevelopment() && sanitizeHtml !== void 0 && data.passageHtml !== void 0) {
|
|
126
146
|
console.warn(
|
|
@@ -130,6 +150,47 @@ function FillInTheBlanks({
|
|
|
130
150
|
}, [sanitizeHtml, data.passageHtml]);
|
|
131
151
|
const segments = useMemo(() => parsePassage(data.passage), [data.passage]);
|
|
132
152
|
const blankById = useMemo(() => new Map(data.blanks.map((b) => [b.id, b])), [data.blanks]);
|
|
153
|
+
const answered = state === "completed";
|
|
154
|
+
const aiHints = useComponentAiHints({
|
|
155
|
+
ai,
|
|
156
|
+
data,
|
|
157
|
+
renderMode,
|
|
158
|
+
submitted: answered,
|
|
159
|
+
disabled: disabled === true,
|
|
160
|
+
response: { type: "fill-in-the-blanks", answers },
|
|
161
|
+
locale,
|
|
162
|
+
onInteraction,
|
|
163
|
+
strings: s,
|
|
164
|
+
delivery: policy
|
|
165
|
+
});
|
|
166
|
+
const hintsRevealed = renderMode === "practice" ? seedHintsRef.current + seen.size + aiHints.used : 0;
|
|
167
|
+
const responseOf = (next, hints = hintsRevealed) => ({
|
|
168
|
+
type: "fill-in-the-blanks",
|
|
169
|
+
answers: next,
|
|
170
|
+
...hints > 0 ? { hintsRevealed: hints } : {}
|
|
171
|
+
});
|
|
172
|
+
const aiSeenRef = useRef(aiHints.used);
|
|
173
|
+
useEffect(() => {
|
|
174
|
+
if (aiSeenRef.current === aiHints.used) {
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
aiSeenRef.current = aiHints.used;
|
|
178
|
+
if (aiHints.used > 0) {
|
|
179
|
+
onChange?.(responseOf(answers));
|
|
180
|
+
}
|
|
181
|
+
}, [aiHints.used]);
|
|
182
|
+
useEffect(() => {
|
|
183
|
+
const target = focusAfterRef.current;
|
|
184
|
+
if (target === null) {
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
focusAfterRef.current = null;
|
|
188
|
+
if (target === "answer") {
|
|
189
|
+
formRef.current?.querySelector("input:not(:disabled)")?.focus();
|
|
190
|
+
} else {
|
|
191
|
+
feedbackRef.current?.focus();
|
|
192
|
+
}
|
|
193
|
+
});
|
|
133
194
|
if (devError) {
|
|
134
195
|
throw devError;
|
|
135
196
|
}
|
|
@@ -142,7 +203,7 @@ function FillInTheBlanks({
|
|
|
142
203
|
const inactive = disabled === true || submitted || isReview;
|
|
143
204
|
const revealing = isReview ? outcome?.status === "scored" : !isExam && submitted;
|
|
144
205
|
const marking = revealing && policy.feedback;
|
|
145
|
-
const showingAnswers = marking && policy.solutions && showCorrectAnswers === true;
|
|
206
|
+
const showingAnswers = marking && policy.solutions && showCorrectAnswers === true && !tries.open;
|
|
146
207
|
const fireInteraction = (type, payload) => {
|
|
147
208
|
onInteraction?.({ type, activityId: data.id, timestamp: Date.now(), payload });
|
|
148
209
|
};
|
|
@@ -157,7 +218,7 @@ function FillInTheBlanks({
|
|
|
157
218
|
if (!isControlled) {
|
|
158
219
|
setInternalAnswers(next);
|
|
159
220
|
}
|
|
160
|
-
onChange?.(
|
|
221
|
+
onChange?.(responseOf(next));
|
|
161
222
|
fireInteraction("blank-filled", { blankId, value: text });
|
|
162
223
|
};
|
|
163
224
|
const toggleHint = (blankId) => {
|
|
@@ -179,13 +240,19 @@ function FillInTheBlanks({
|
|
|
179
240
|
if (!revealed.has(blankId)) {
|
|
180
241
|
fireInteraction("hint-requested", { blankId });
|
|
181
242
|
}
|
|
243
|
+
if (!seen.has(blankId)) {
|
|
244
|
+
setSeen((prev) => new Set(prev).add(blankId));
|
|
245
|
+
if (renderMode === "practice") {
|
|
246
|
+
onChange?.(responseOf(answers, hintsRevealed + 1));
|
|
247
|
+
}
|
|
248
|
+
}
|
|
182
249
|
};
|
|
183
250
|
const handleSubmit = (event) => {
|
|
184
251
|
event.preventDefault();
|
|
185
252
|
if (inactive) {
|
|
186
253
|
return;
|
|
187
254
|
}
|
|
188
|
-
const response =
|
|
255
|
+
const response = responseOf(answers);
|
|
189
256
|
onSubmit?.(response);
|
|
190
257
|
if (isExam) {
|
|
191
258
|
complete();
|
|
@@ -193,9 +260,22 @@ function FillInTheBlanks({
|
|
|
193
260
|
fireInteraction("submitted", { answers });
|
|
194
261
|
return;
|
|
195
262
|
}
|
|
196
|
-
const
|
|
263
|
+
const answer = score("fill-in-the-blanks", data, response);
|
|
197
264
|
complete();
|
|
198
265
|
const timeSpent = getTimeSpent();
|
|
266
|
+
const counted = tries.record({
|
|
267
|
+
score: answer.score,
|
|
268
|
+
maxScore: answer.maxScore,
|
|
269
|
+
hintsRevealed
|
|
270
|
+
});
|
|
271
|
+
const mine = counted.tries[counted.tries.length - 1];
|
|
272
|
+
const costed = mine === void 0 ? answer.score : mine.scored;
|
|
273
|
+
const scoringResult = costed === answer.score ? answer : {
|
|
274
|
+
...answer,
|
|
275
|
+
score: costed,
|
|
276
|
+
passed: computePassThreshold(data, costed)
|
|
277
|
+
};
|
|
278
|
+
const passed = counted.score === scoringResult.score ? scoringResult.passed : computePassThreshold(data, counted.score);
|
|
199
279
|
const xapiStatement = xAPIBuilder.buildAnsweredStatement({
|
|
200
280
|
actor: ANONYMOUS_ACTOR,
|
|
201
281
|
object: {
|
|
@@ -207,23 +287,29 @@ function FillInTheBlanks({
|
|
|
207
287
|
timeSpentMs: timeSpent,
|
|
208
288
|
response: JSON.stringify(answers)
|
|
209
289
|
});
|
|
210
|
-
setResult(
|
|
211
|
-
onComplete?.(
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
const overall = scoringResult.feedback;
|
|
290
|
+
setResult(answer);
|
|
291
|
+
onComplete?.(
|
|
292
|
+
stampTake(
|
|
293
|
+
{ score: counted.score, maxScore: counted.maxScore, passed, timeSpent, xapiStatement },
|
|
294
|
+
mintTake()
|
|
295
|
+
)
|
|
296
|
+
);
|
|
297
|
+
setOverall(answer.feedback);
|
|
219
298
|
setSummary(
|
|
220
|
-
`${s.answerSubmitted} ${s.scoreAnnouncement(
|
|
221
|
-
Math.round(scoringResult.score * 100),
|
|
222
|
-
scoringResult.passed
|
|
223
|
-
)}${overall ? ` ${overall}` : ""}`
|
|
299
|
+
`${s.answerSubmitted} ${s.scoreAnnouncement(Math.round(counted.score * 100), passed)}`
|
|
224
300
|
);
|
|
225
301
|
fireInteraction("submitted", { answers, score: scoringResult.score });
|
|
226
302
|
};
|
|
303
|
+
const retry = () => {
|
|
304
|
+
reset("idle");
|
|
305
|
+
setSummary(null);
|
|
306
|
+
setOverall(null);
|
|
307
|
+
focusAfterRef.current = "answer";
|
|
308
|
+
};
|
|
309
|
+
const closeTries = () => {
|
|
310
|
+
tries.close();
|
|
311
|
+
focusAfterRef.current = "feedback";
|
|
312
|
+
};
|
|
227
313
|
const correctByBlank = useMemo(() => {
|
|
228
314
|
if (isExam) {
|
|
229
315
|
return /* @__PURE__ */ new Map();
|
|
@@ -249,9 +335,13 @@ function FillInTheBlanks({
|
|
|
249
335
|
}
|
|
250
336
|
return s.noGradeAvailable;
|
|
251
337
|
}, [isReview, outcome, s]);
|
|
338
|
+
const extra = !isReview && policy.feedback && submitted && tries.counted !== null ? triesSummary(s, scoringPolicy, tries.counted, tries.open) : "";
|
|
339
|
+
const announced = summary === null ? null : `${summary}${extra ? ` ${extra}` : ""}${overall ? ` ${overall}` : ""}`;
|
|
340
|
+
const hintsOffered = renderMode === "practice" && !submitted && (aiHints.offered || policy.hints && data.blanks.some((blank) => blank.hint));
|
|
252
341
|
return /* @__PURE__ */ jsxs(
|
|
253
342
|
"form",
|
|
254
343
|
{
|
|
344
|
+
ref: formRef,
|
|
255
345
|
className: "lk-fib",
|
|
256
346
|
"aria-label": data.title,
|
|
257
347
|
lang: locale,
|
|
@@ -347,21 +437,8 @@ function FillInTheBlanks({
|
|
|
347
437
|
) : null
|
|
348
438
|
] }, seg.id);
|
|
349
439
|
}) }),
|
|
350
|
-
/* @__PURE__ */ jsx(
|
|
351
|
-
|
|
352
|
-
{
|
|
353
|
-
ai,
|
|
354
|
-
data,
|
|
355
|
-
renderMode,
|
|
356
|
-
submitted,
|
|
357
|
-
disabled: disabled === true,
|
|
358
|
-
response: { type: "fill-in-the-blanks", answers },
|
|
359
|
-
locale,
|
|
360
|
-
onInteraction,
|
|
361
|
-
strings: s,
|
|
362
|
-
delivery: policy
|
|
363
|
-
}
|
|
364
|
-
),
|
|
440
|
+
hintsOffered ? /* @__PURE__ */ jsx(HintCost, { policy: scoringPolicy, strings: s }) : null,
|
|
441
|
+
/* @__PURE__ */ jsx(AiHints, { help: aiHints, ai, strings: s }),
|
|
365
442
|
isReview ? null : /* @__PURE__ */ jsx("button", { type: "submit", disabled: inactive, children: isExam ? s.submitAnswers : s.checkAnswers })
|
|
366
443
|
] }),
|
|
367
444
|
marking && data.blanks.some((b) => b.feedback) ? /* @__PURE__ */ jsx(
|
|
@@ -374,7 +451,24 @@ function FillInTheBlanks({
|
|
|
374
451
|
children: feedbackHidden ? s.showFeedback : s.hideFeedback
|
|
375
452
|
}
|
|
376
453
|
) : null,
|
|
377
|
-
/* @__PURE__ */ jsx(
|
|
454
|
+
/* @__PURE__ */ jsx(
|
|
455
|
+
FeedbackRegion,
|
|
456
|
+
{
|
|
457
|
+
id: `${data.id}-feedback`,
|
|
458
|
+
...scoringPolicy.retries > 0 ? { ref: feedbackRef } : {},
|
|
459
|
+
children: isReview ? policy.feedback || !outcomeShowsMarks(outcome) ? reviewSummary : null : policy.feedback || summary === null ? announced : s.answerSubmitted
|
|
460
|
+
}
|
|
461
|
+
),
|
|
462
|
+
/* @__PURE__ */ jsx(
|
|
463
|
+
TryActions,
|
|
464
|
+
{
|
|
465
|
+
tries,
|
|
466
|
+
solutions: policy.solutions && showCorrectAnswers === true,
|
|
467
|
+
strings: s,
|
|
468
|
+
onRetry: retry,
|
|
469
|
+
onClose: closeTries
|
|
470
|
+
}
|
|
471
|
+
),
|
|
378
472
|
/* @__PURE__ */ jsx(
|
|
379
473
|
AiExplanation,
|
|
380
474
|
{
|
|
@@ -387,7 +481,7 @@ function FillInTheBlanks({
|
|
|
387
481
|
locale,
|
|
388
482
|
onInteraction,
|
|
389
483
|
strings: s,
|
|
390
|
-
delivery: policy
|
|
484
|
+
delivery: tries.open ? { ...policy, solutions: false } : policy
|
|
391
485
|
}
|
|
392
486
|
)
|
|
393
487
|
]
|
|
@@ -400,5 +494,5 @@ function FillInTheBlanks2(props) {
|
|
|
400
494
|
}
|
|
401
495
|
|
|
402
496
|
export { FillInTheBlanks2 as FillInTheBlanks };
|
|
403
|
-
//# sourceMappingURL=chunk-
|
|
404
|
-
//# sourceMappingURL=chunk-
|
|
497
|
+
//# sourceMappingURL=chunk-KRM6DX47.js.map
|
|
498
|
+
//# sourceMappingURL=chunk-KRM6DX47.js.map
|