@intellectif/lk-react 23.1.2 → 23.2.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 +14 -0
- package/dist/{ActivityErrorBoundary-Dw6SbWv3.js → ActivityErrorBoundary-BBPjMsqG.js} +2 -2
- package/dist/{ActivityErrorBoundary-Dw6SbWv3.js.map → ActivityErrorBoundary-BBPjMsqG.js.map} +1 -1
- package/dist/{ActivityErrorBoundary-B379hvtL.cjs → ActivityErrorBoundary-DEHmEXQw.cjs} +2 -2
- package/dist/{ActivityErrorBoundary-B379hvtL.cjs.map → ActivityErrorBoundary-DEHmEXQw.cjs.map} +1 -1
- package/dist/{ActivityPreview-1hhTkYuj.js → ActivityPreview-CiQ31WX8.js} +9 -9
- package/dist/{ActivityPreview-1hhTkYuj.js.map → ActivityPreview-CiQ31WX8.js.map} +1 -1
- package/dist/{ActivityPreview-BKr34exz.cjs → ActivityPreview-DWT4nSAN.cjs} +9 -9
- package/dist/{ActivityPreview-BKr34exz.cjs.map → ActivityPreview-DWT4nSAN.cjs.map} +1 -1
- package/dist/{ActivitySequence-CkX87pfB.js → ActivitySequence-D6bRMUWU.js} +13 -8
- package/dist/ActivitySequence-D6bRMUWU.js.map +1 -0
- package/dist/{ActivitySequence-CmoSyDnp.cjs → ActivitySequence-OlZpo7WW.cjs} +13 -8
- package/dist/ActivitySequence-OlZpo7WW.cjs.map +1 -0
- package/dist/{Dictation-jECzL88E.cjs → Dictation-Bq6Fn001.cjs} +5 -5
- package/dist/{Dictation-jECzL88E.cjs.map → Dictation-Bq6Fn001.cjs.map} +1 -1
- package/dist/{Dictation-BlxRp-3k.js → Dictation-KM4BboAS.js} +5 -5
- package/dist/{Dictation-BlxRp-3k.js.map → Dictation-KM4BboAS.js.map} +1 -1
- package/dist/{FeedbackRegion-CyPSp8bA.js → FeedbackRegion-DuVolWLX.js} +2 -2
- package/dist/{FeedbackRegion-CyPSp8bA.js.map → FeedbackRegion-DuVolWLX.js.map} +1 -1
- package/dist/{FeedbackRegion-BNNMtTY-.cjs → FeedbackRegion-wfTuezJo.cjs} +2 -2
- package/dist/{FeedbackRegion-BNNMtTY-.cjs.map → FeedbackRegion-wfTuezJo.cjs.map} +1 -1
- package/dist/{FillInTheBlanks-B6xzqYXx.js → FillInTheBlanks-CRA7MPyq.js} +5 -5
- package/dist/{FillInTheBlanks-B6xzqYXx.js.map → FillInTheBlanks-CRA7MPyq.js.map} +1 -1
- package/dist/{FillInTheBlanks-BpKhdHg2.cjs → FillInTheBlanks-Cr_QOukp.cjs} +5 -5
- package/dist/{FillInTheBlanks-BpKhdHg2.cjs.map → FillInTheBlanks-Cr_QOukp.cjs.map} +1 -1
- package/dist/{GapSelect-DUSiaEQH.js → GapSelect-CddiUhaR.js} +5 -5
- package/dist/{GapSelect-DUSiaEQH.js.map → GapSelect-CddiUhaR.js.map} +1 -1
- package/dist/{GapSelect-payTq_Zn.cjs → GapSelect-DlL8eggv.cjs} +5 -5
- package/dist/{GapSelect-payTq_Zn.cjs.map → GapSelect-DlL8eggv.cjs.map} +1 -1
- package/dist/{InteractiveVideo-Cg2jbU3j.js → InteractiveVideo-CZHhtRX1.js} +9 -8
- package/dist/{InteractiveVideo-Cg2jbU3j.js.map → InteractiveVideo-CZHhtRX1.js.map} +1 -1
- package/dist/{InteractiveVideo-BguGv-HA.cjs → InteractiveVideo-DJK6xago.cjs} +9 -8
- package/dist/{InteractiveVideo-BguGv-HA.cjs.map → InteractiveVideo-DJK6xago.cjs.map} +1 -1
- package/dist/{MultipleChoice-COn5MtKY.cjs → MultipleChoice-BJMAgxQd.cjs} +5 -5
- package/dist/{MultipleChoice-COn5MtKY.cjs.map → MultipleChoice-BJMAgxQd.cjs.map} +1 -1
- package/dist/{MultipleChoice-Dra0_KYD.js → MultipleChoice-PlJ_hL_E.js} +5 -5
- package/dist/{MultipleChoice-Dra0_KYD.js.map → MultipleChoice-PlJ_hL_E.js.map} +1 -1
- package/dist/{PronunciationFeedback-DlMPGjkR.cjs → PronunciationFeedback-B3iErah7.cjs} +3 -3
- package/dist/{PronunciationFeedback-DlMPGjkR.cjs.map → PronunciationFeedback-B3iErah7.cjs.map} +1 -1
- package/dist/{PronunciationFeedback-Dxd6XPkO.js → PronunciationFeedback-CGGgPA7i.js} +3 -3
- package/dist/{PronunciationFeedback-Dxd6XPkO.js.map → PronunciationFeedback-CGGgPA7i.js.map} +1 -1
- package/dist/{ReadAloud-C8RhaUqV.js → ReadAloud-NMSLzstD.js} +5 -5
- package/dist/{ReadAloud-C8RhaUqV.js.map → ReadAloud-NMSLzstD.js.map} +1 -1
- package/dist/{ReadAloud-akDehitT.cjs → ReadAloud-xbRcLkro.cjs} +5 -5
- package/dist/{ReadAloud-akDehitT.cjs.map → ReadAloud-xbRcLkro.cjs.map} +1 -1
- package/dist/{WrittenResponse-Bj3eFmGl.cjs → WrittenResponse-BOsB3DKv.cjs} +4 -4
- package/dist/{WrittenResponse-Bj3eFmGl.cjs.map → WrittenResponse-BOsB3DKv.cjs.map} +1 -1
- package/dist/{WrittenResponse-Cl8G6nO_.js → WrittenResponse-DU10_GFF.js} +4 -4
- package/dist/{WrittenResponse-Cl8G6nO_.js.map → WrittenResponse-DU10_GFF.js.map} +1 -1
- package/dist/ai/useAiHelp.cjs +1 -1
- package/dist/ai/useAiHelp.d.cts +9 -1
- package/dist/ai/useAiHelp.d.cts.map +1 -1
- package/dist/ai/useAiHelp.d.ts +9 -1
- package/dist/ai/useAiHelp.d.ts.map +1 -1
- package/dist/ai/useAiHelp.js +1 -1
- package/dist/components/ActivityPreview.cjs +1 -1
- package/dist/components/ActivityPreview.js +1 -1
- package/dist/components/ActivitySequence.cjs +1 -1
- package/dist/components/ActivitySequence.js +1 -1
- package/dist/components/Dictation.cjs +1 -1
- package/dist/components/Dictation.js +1 -1
- package/dist/components/FillInTheBlanks.cjs +1 -1
- package/dist/components/FillInTheBlanks.js +1 -1
- package/dist/components/GapSelect.cjs +1 -1
- package/dist/components/GapSelect.js +1 -1
- package/dist/components/InteractiveVideo.cjs +1 -1
- package/dist/components/InteractiveVideo.js +1 -1
- package/dist/components/MultipleChoice.cjs +1 -1
- package/dist/components/MultipleChoice.js +1 -1
- package/dist/components/PronunciationFeedback.cjs +1 -1
- package/dist/components/PronunciationFeedback.js +1 -1
- package/dist/components/ReadAloud.cjs +1 -1
- package/dist/components/ReadAloud.js +1 -1
- package/dist/components/WrittenResponse.cjs +1 -1
- package/dist/components/WrittenResponse.js +1 -1
- package/dist/index.cjs +11 -11
- package/dist/index.js +11 -11
- package/dist/{tries-CbbtZkdG.cjs → tries-5JLYRKCa.cjs} +2 -2
- package/dist/{tries-CbbtZkdG.cjs.map → tries-5JLYRKCa.cjs.map} +1 -1
- package/dist/{tries-DTHSrLCm.js → tries-CTT1URXx.js} +2 -2
- package/dist/{tries-DTHSrLCm.js.map → tries-CTT1URXx.js.map} +1 -1
- package/dist/{useAiHelp-B-ccDL5Q.js → useAiHelp-BJy05hqV.js} +18 -1
- package/dist/useAiHelp-BJy05hqV.js.map +1 -0
- package/dist/{useAiHelp-h9yOLFqt.cjs → useAiHelp-MBQiNgst.cjs} +18 -1
- package/dist/useAiHelp-MBQiNgst.cjs.map +1 -0
- package/package.json +11 -11
- package/dist/ActivitySequence-CkX87pfB.js.map +0 -1
- package/dist/ActivitySequence-CmoSyDnp.cjs.map +0 -1
- package/dist/useAiHelp-B-ccDL5Q.js.map +0 -1
- package/dist/useAiHelp-h9yOLFqt.cjs.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# @intellectif/lk-react
|
|
2
2
|
|
|
3
|
+
## 23.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#107](https://github.com/intellectif/learning-kit/pull/107) [`8bf7d6e`](https://github.com/intellectif/learning-kit/commit/8bf7d6edb76ff45ce32e425ef83663692127acc9) Thanks [@diego28e](https://github.com/diego28e)! - **AI help reads a question's group**: hints, explanations and feedback on writing now carry the passage, or the shared instructions, a grouped question is read beside.
|
|
8
|
+
|
|
9
|
+
**Action required:** none. Requests gain an optional `material`, which your prompt uses once you tell it to. A port that sends the whole request to its model sends the material too: up to 12,000 code points more per call on a question in a group.
|
|
10
|
+
|
|
11
|
+
- **lk-core:** `aiExplanationRequest`, `aiHintRequest` and `aiWritingFeedbackRequest` take an optional `group` (an `ItemGroup`, a redacted one, or your own `{ title, stimulus }`) and add `material`: the kind, the title, the text, the media type and its alt text, the attribution and the language. The text is cut past `AI_MATERIAL_MAX_LENGTH` (12,000 code points), at a word boundary, and marked `truncated`. The author's transcript and media addresses never go, so a whole group and a redacted one give the same material. The checks are unchanged: a hint that quotes the passage where it prints the answer is refused like any other.
|
|
12
|
+
- **lk-react:** inside `<ActivitySequence>` and `<InteractiveVideo>`, the SDK's own components, and a question you draw with `useAiHints`, `useAiExplanation` or `useAiWritingFeedback`, send their group's material by themselves. Outside a pager, pass `group` to the hooks. lk-react 23.2.0 peers on lk-core `^1.4.0`, so upgrade the two together. Coaching on a reading is unchanged.
|
|
13
|
+
- **`@intellectif/lk-core/ai-check`** has four more cases in item groups: a reading question explained, and hinted at, from a passage that prints its answer; a word-formation blank under its group's instructions; and a summary of the passage.
|
|
14
|
+
|
|
15
|
+
Guide: https://github.com/intellectif/learning-kit/blob/main/docs/ai.md#a-question-in-an-item-group-140
|
|
16
|
+
|
|
3
17
|
## 23.1.2
|
|
4
18
|
|
|
5
19
|
### Patch Changes
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import { c as isDevelopment } from "./useAiHelp-
|
|
2
|
+
import { c as isDevelopment } from "./useAiHelp-BJy05hqV.js";
|
|
3
3
|
import { a as DEFAULT_STRINGS } from "./LkIntlProvider-D_FdYXjQ.js";
|
|
4
4
|
import { Component } from "react";
|
|
5
5
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -31,4 +31,4 @@ var ActivityErrorBoundary = class extends Component {
|
|
|
31
31
|
//#endregion
|
|
32
32
|
export { ActivityErrorBoundary as t };
|
|
33
33
|
|
|
34
|
-
//# sourceMappingURL=ActivityErrorBoundary-
|
|
34
|
+
//# sourceMappingURL=ActivityErrorBoundary-BBPjMsqG.js.map
|
package/dist/{ActivityErrorBoundary-Dw6SbWv3.js.map → ActivityErrorBoundary-BBPjMsqG.js.map}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ActivityErrorBoundary-
|
|
1
|
+
{"version":3,"file":"ActivityErrorBoundary-BBPjMsqG.js","names":[],"sources":["../src/components/ActivityErrorBoundary.tsx"],"sourcesContent":["'use client';\n\nimport { Component, type ReactNode } from 'react';\nimport type { LkStrings } from '../i18n/strings.js';\nimport { DEFAULT_STRINGS } from '../i18n/strings.js';\nimport { isDevelopment } from './_internal.js';\n\nexport interface ActivityErrorBoundaryProps {\n /** Shown in the production fallback when a render error occurs. */\n activityTitle?: string;\n /**\n * Chrome text. A class component cannot call `useLkStrings`, so the public\n * wrapper reads the hook and hands the resolved dictionary down.\n */\n strings?: LkStrings;\n children: ReactNode;\n}\n\ninterface ActivityErrorBoundaryState {\n error: Error | null;\n}\n\n/**\n * Catches render errors in an activity subtree. In development it renders the\n * full error stack to aid debugging; in production it renders an accessible,\n * generic fallback (optionally naming the activity) so a single broken\n * activity never crashes the host application.\n */\nexport class ActivityErrorBoundary extends Component<\n ActivityErrorBoundaryProps,\n ActivityErrorBoundaryState\n> {\n state: ActivityErrorBoundaryState = { error: null };\n\n static getDerivedStateFromError(error: Error): ActivityErrorBoundaryState {\n return { error };\n }\n\n render(): ReactNode {\n const { error } = this.state;\n if (!error) {\n return this.props.children;\n }\n\n // The same answer every development check reads, from the one function\n // that gives it: a boundary with a reading of its own showed a learner a\n // stack trace in every browser production build.\n if (isDevelopment()) {\n return (\n <div role=\"alert\">\n <strong>{(this.props.strings ?? DEFAULT_STRINGS).activityFailed}</strong>\n <pre>{error.stack ?? error.message}</pre>\n </div>\n );\n }\n\n return (\n <div role=\"alert\">\n {this.props.activityTitle\n ? (this.props.strings ?? DEFAULT_STRINGS).activityFailedNamed(this.props.activityTitle)\n : (this.props.strings ?? DEFAULT_STRINGS).activityFailedUnnamed}\n </div>\n );\n }\n}\n"],"mappings":";;;;;;;;;;;AA4BA,IAAa,wBAAb,cAA2C,UAGzC;CACA,QAAoC,EAAE,OAAO,KAAK;CAElD,OAAO,yBAAyB,OAA0C;EACxE,OAAO,EAAE,MAAM;CACjB;CAEA,SAAoB;EAClB,MAAM,EAAE,UAAU,KAAK;EACvB,IAAI,CAAC,OACH,OAAO,KAAK,MAAM;EAMpB,IAAI,cAAc,GAChB,OACE,qBAAC,OAAD;GAAK,MAAK;GAAV,UAAA,CACE,oBAAC,UAAD,EAAA,WAAU,KAAK,MAAM,WAAW,gBAAA,CAAiB,eAAuB,CAAA,GACxE,oBAAC,OAAD,EAAA,UAAM,MAAM,SAAS,MAAM,QAAa,CAAA,CACrC;;EAIT,OACE,oBAAC,OAAD;GAAK,MAAK;GACP,UAAA,KAAK,MAAM,iBACP,KAAK,MAAM,WAAW,gBAAA,CAAiB,oBAAoB,KAAK,MAAM,aAAa,KACnF,KAAK,MAAM,WAAW,gBAAA,CAAiB;EACzC,CAAA;CAET;AACF"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
const require_useAiHelp = require("./useAiHelp-
|
|
2
|
+
const require_useAiHelp = require("./useAiHelp-MBQiNgst.cjs");
|
|
3
3
|
const require_LkIntlProvider = require("./LkIntlProvider-oi57lYB0.cjs");
|
|
4
4
|
let react = require("react");
|
|
5
5
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
@@ -36,4 +36,4 @@ Object.defineProperty(exports, "ActivityErrorBoundary", {
|
|
|
36
36
|
}
|
|
37
37
|
});
|
|
38
38
|
|
|
39
|
-
//# sourceMappingURL=ActivityErrorBoundary-
|
|
39
|
+
//# sourceMappingURL=ActivityErrorBoundary-DEHmEXQw.cjs.map
|
package/dist/{ActivityErrorBoundary-B379hvtL.cjs.map → ActivityErrorBoundary-DEHmEXQw.cjs.map}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ActivityErrorBoundary-
|
|
1
|
+
{"version":3,"file":"ActivityErrorBoundary-DEHmEXQw.cjs","names":["Component","isDevelopment","DEFAULT_STRINGS"],"sources":["../src/components/ActivityErrorBoundary.tsx"],"sourcesContent":["'use client';\n\nimport { Component, type ReactNode } from 'react';\nimport type { LkStrings } from '../i18n/strings.js';\nimport { DEFAULT_STRINGS } from '../i18n/strings.js';\nimport { isDevelopment } from './_internal.js';\n\nexport interface ActivityErrorBoundaryProps {\n /** Shown in the production fallback when a render error occurs. */\n activityTitle?: string;\n /**\n * Chrome text. A class component cannot call `useLkStrings`, so the public\n * wrapper reads the hook and hands the resolved dictionary down.\n */\n strings?: LkStrings;\n children: ReactNode;\n}\n\ninterface ActivityErrorBoundaryState {\n error: Error | null;\n}\n\n/**\n * Catches render errors in an activity subtree. In development it renders the\n * full error stack to aid debugging; in production it renders an accessible,\n * generic fallback (optionally naming the activity) so a single broken\n * activity never crashes the host application.\n */\nexport class ActivityErrorBoundary extends Component<\n ActivityErrorBoundaryProps,\n ActivityErrorBoundaryState\n> {\n state: ActivityErrorBoundaryState = { error: null };\n\n static getDerivedStateFromError(error: Error): ActivityErrorBoundaryState {\n return { error };\n }\n\n render(): ReactNode {\n const { error } = this.state;\n if (!error) {\n return this.props.children;\n }\n\n // The same answer every development check reads, from the one function\n // that gives it: a boundary with a reading of its own showed a learner a\n // stack trace in every browser production build.\n if (isDevelopment()) {\n return (\n <div role=\"alert\">\n <strong>{(this.props.strings ?? DEFAULT_STRINGS).activityFailed}</strong>\n <pre>{error.stack ?? error.message}</pre>\n </div>\n );\n }\n\n return (\n <div role=\"alert\">\n {this.props.activityTitle\n ? (this.props.strings ?? DEFAULT_STRINGS).activityFailedNamed(this.props.activityTitle)\n : (this.props.strings ?? DEFAULT_STRINGS).activityFailedUnnamed}\n </div>\n );\n }\n}\n"],"mappings":";;;;;;;;;;;AA4BA,IAAa,wBAAb,cAA2CA,MAAAA,UAGzC;CACA,QAAoC,EAAE,OAAO,KAAK;CAElD,OAAO,yBAAyB,OAA0C;EACxE,OAAO,EAAE,MAAM;CACjB;CAEA,SAAoB;EAClB,MAAM,EAAE,UAAU,KAAK;EACvB,IAAI,CAAC,OACH,OAAO,KAAK,MAAM;EAMpB,IAAIC,kBAAAA,cAAc,GAChB,OACE,iBAAA,GAAA,kBAAA,KAAA,CAAC,OAAD;GAAK,MAAK;GAAV,UAAA,CACE,iBAAA,GAAA,kBAAA,IAAA,CAAC,UAAD,EAAA,WAAU,KAAK,MAAM,WAAWC,uBAAAA,gBAAAA,CAAiB,eAAuB,CAAA,GACxE,iBAAA,GAAA,kBAAA,IAAA,CAAC,OAAD,EAAA,UAAM,MAAM,SAAS,MAAM,QAAa,CAAA,CACrC;;EAIT,OACE,iBAAA,GAAA,kBAAA,IAAA,CAAC,OAAD;GAAK,MAAK;GACP,UAAA,KAAK,MAAM,iBACP,KAAK,MAAM,WAAWA,uBAAAA,gBAAAA,CAAiB,oBAAoB,KAAK,MAAM,aAAa,KACnF,KAAK,MAAM,WAAWA,uBAAAA,gBAAAA,CAAiB;EACzC,CAAA;CAET;AACF"}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import "./useAiHelp-
|
|
2
|
+
import "./useAiHelp-BJy05hqV.js";
|
|
3
3
|
import { i as useLkStrings } from "./LkIntlProvider-D_FdYXjQ.js";
|
|
4
|
-
import { t as ActivityErrorBoundary } from "./ActivityErrorBoundary-
|
|
5
|
-
import { t as Dictation } from "./Dictation-
|
|
6
|
-
import { t as FillInTheBlanks } from "./FillInTheBlanks-
|
|
7
|
-
import { t as GapSelect } from "./GapSelect-
|
|
8
|
-
import { t as MultipleChoice } from "./MultipleChoice-
|
|
9
|
-
import { t as ReadAloud } from "./ReadAloud-
|
|
10
|
-
import { t as WrittenResponse } from "./WrittenResponse-
|
|
4
|
+
import { t as ActivityErrorBoundary } from "./ActivityErrorBoundary-BBPjMsqG.js";
|
|
5
|
+
import { t as Dictation } from "./Dictation-KM4BboAS.js";
|
|
6
|
+
import { t as FillInTheBlanks } from "./FillInTheBlanks-CRA7MPyq.js";
|
|
7
|
+
import { t as GapSelect } from "./GapSelect-CddiUhaR.js";
|
|
8
|
+
import { t as MultipleChoice } from "./MultipleChoice-PlJ_hL_E.js";
|
|
9
|
+
import { t as ReadAloud } from "./ReadAloud-NMSLzstD.js";
|
|
10
|
+
import { t as WrittenResponse } from "./WrittenResponse-DU10_GFF.js";
|
|
11
11
|
import { useMemo } from "react";
|
|
12
12
|
import { Fragment as Fragment$1, jsx } from "react/jsx-runtime";
|
|
13
13
|
import { evaluate, slotMediaKey, validateDraft } from "@intellectif/lk-core";
|
|
@@ -237,4 +237,4 @@ function contentKeyOf(value, enclosing = /* @__PURE__ */ new Set()) {
|
|
|
237
237
|
//#endregion
|
|
238
238
|
export { ActivityPreview as t };
|
|
239
239
|
|
|
240
|
-
//# sourceMappingURL=ActivityPreview-
|
|
240
|
+
//# sourceMappingURL=ActivityPreview-CiQ31WX8.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ActivityPreview-1hhTkYuj.js","names":[],"sources":["../src/components/ActivityPreview/ActivityPreview.tsx"],"sourcesContent":["'use client';\n\nimport {\n type ActivityType,\n type DraftNotComplete,\n evaluate,\n type ItemOutcome,\n type LearnerResponse,\n slotMediaKey,\n type ThemeTokens,\n validateDraft,\n} from '@intellectif/lk-core';\nimport { type ReactNode, useMemo } from 'react';\nimport { useLkStrings } from '../../i18n/LkIntlProvider.js';\nimport type { LkStringsOverride } from '../../i18n/strings.js';\nimport { everyBuiltInTypeHandled } from '../_internal.js';\nimport { ActivityErrorBoundary } from '../ActivityErrorBoundary.js';\nimport type { ActivityRenderer } from '../ActivitySequence/index.js';\nimport { Dictation } from '../Dictation/index.js';\nimport { FillInTheBlanks } from '../FillInTheBlanks/index.js';\nimport { GapSelect } from '../GapSelect/index.js';\nimport { MultipleChoice } from '../MultipleChoice/index.js';\nimport { ReadAloud } from '../ReadAloud/index.js';\nimport type { RecordingBinding } from '../ReadAloud/ReadAloud.js';\nimport type { HtmlSanitizer, MediaBudgetBinding, RenderMode } from '../types.js';\nimport { WrittenResponse } from '../WrittenResponse/index.js';\n\n/**\n * The option-shuffle seed a preview uses when none is given. Fixed, so a\n * shuffled question holds still while its author edits it instead of\n * re-ordering on every remount.\n */\nconst PREVIEW_SHUFFLE_SEED = 'lk-activity-preview';\n\n/**\n * The slot a previewed activity stands in for. A recording's play budget is\n * keyed to it and lives only in this mount: the preview counts plays so an\n * author hears the limit a learner gets, and records none of them.\n */\nconst PREVIEW_SLOT_ID = 'preview';\n\n/** Numbers the takes a preview mints keys for, so two are never the same take. */\nlet previewTakeCount = 0;\n\n/**\n * The store a previewed read-aloud records into: it keys the take so the whole\n * flow — record, play it back, submit — runs for the author, and keeps it\n * nowhere, like every other thing a preview does not record.\n *\n * It judges nothing, deliberately. `assess` is optional precisely so this can\n * exist: an author trying a reading out has no assessor to point at, and a\n * preview that demanded one would refuse to render the most common case. The\n * default `practice` mode then shows the \"assessment is not available\" notice,\n * which is the truth about this binding rather than a failure.\n */\nconst PREVIEW_RECORDING: RecordingBinding = {\n upload: async (take) => {\n previewTakeCount += 1;\n return {\n key: `${PREVIEW_SLOT_ID}-take-${previewTakeCount}`,\n mimeType: take.mimeType,\n durationMs: take.durationMs,\n };\n },\n};\n\nexport interface ActivityPreviewProps {\n /**\n * The activity as an editor holds it right now, finished or not. It is checked\n * with `validateDraft` before anything renders — by content, on every render —\n * so a half-written question never reaches a component that assumes a valid\n * one.\n */\n draft: unknown;\n /** The mode to preview in. Defaults to `practice`. */\n renderMode?: RenderMode;\n /**\n * A learner response to show as if a learner had given it. In `practice` and\n * `exam` it seeds the answer, which stays editable; in `review` it is the\n * submitted answer, marked by `evaluate()` unless `outcome` is given.\n */\n response?: LearnerResponse;\n /**\n * The outcome `review` marks the response with, instead of the one\n * `evaluate()` computes — which is how to preview a returned grade on a written\n * response, since the SDK cannot score one. Ignored in other modes.\n */\n outcome?: ItemOutcome;\n /**\n * Rendered in place of the activity while the draft is not complete, with the\n * issues `validateDraft` found — so an editor can list them in its own words.\n * The issue messages are English, and a preview that rendered them would put\n * untranslatable text on screen. Without it, the preview shows the\n * `previewIncomplete` or `previewInvalid` string.\n */\n fallback?: (result: DraftNotComplete) => ReactNode;\n /**\n * Renderers for activity types beyond the built-ins, keyed by `type`, as on\n * `<ActivitySequence>`. An entry receives the shared prop contract and\n * nothing else, so — like the shuffle seed below — the in-memory recording\n * binding a previewed read-aloud is given does not reach one. Nor does the\n * `question` a sequence hands a renderer: a preview is one question with no\n * set around it, so there is no slot to report to and nothing to page away\n * from. A renderer that uses it must treat it as optional, which its type\n * already says.\n */\n renderers?: Readonly<Record<string, ActivityRenderer>>;\n /**\n * Seeds `<MultipleChoice>`'s option shuffle. Defaults to a fixed seed, so the\n * order holds while options are edited; adding or removing one deals them\n * again, because the shuffle orders by position. A `renderers` entry is given\n * no seed, as on `<ActivitySequence>`.\n */\n shuffleSeed?: string;\n /** Renders author-supplied rich text. See `HtmlSanitizer`. */\n sanitizeHtml?: HtmlSanitizer;\n /** Overrides the SDK's chrome text, for the preview's notice and the activity alike. */\n strings?: LkStringsOverride;\n /** Per-instance token overrides, passed to the activity. */\n theme?: Partial<ThemeTokens>;\n /** BCP 47 tag of the interface language, passed to the activity. */\n locale?: string;\n}\n\ntype Marking = { kind: 'marked'; outcome: ItemOutcome } | { kind: 'failed'; error: Error };\n\n/**\n * Renders an activity from an editor's draft, in any render mode, with a\n * simulated learner response — and renders a notice instead while the draft is\n * not an activity yet.\n *\n * Nothing it renders is recorded: no callback is wired, so a practice attempt in\n * a preview scores locally and goes nowhere, and a recording's play limit is\n * counted in memory only.\n *\n * A draft with no string `type`, a type nobody registered, and a `response` of a\n * different type from the draft are wiring errors, and throw.\n */\nexport function ActivityPreview({\n draft,\n renderMode = 'practice',\n response,\n outcome,\n fallback,\n renderers,\n shuffleSeed,\n sanitizeHtml,\n strings,\n theme,\n locale,\n}: ActivityPreviewProps): React.JSX.Element {\n const s = useLkStrings(strings);\n const type = isRecord(draft) && typeof draft.type === 'string' ? draft.type : undefined;\n\n // Validated on the draft's CONTENT, not its identity. The activity components\n // restart whenever `data` changes identity, and an editor that rebuilds its\n // payload on every change would otherwise restart the question an author is\n // trying out each time anything else on the page was edited. The key is read\n // on every render, so a draft changed in place is checked again too.\n const contentKey = contentKeyOf(draft);\n // biome-ignore lint/correctness/useExhaustiveDependencies: the draft's content (contentKey) is the trigger, not its identity\n const result = useMemo(\n () => (type === undefined ? undefined : validateDraft(type as ActivityType, draft)),\n [type, contentKey],\n );\n const activity = result?.status === 'complete' ? result.data : undefined;\n const responseFits = response === undefined || response.type === type;\n const responseKey = response === undefined ? 'none' : contentKeyOf(response);\n\n // biome-ignore lint/correctness/useExhaustiveDependencies: the response's content (responseKey) is the trigger, not its identity\n const marking = useMemo((): Marking | undefined => {\n if (renderMode !== 'review' || activity === undefined) {\n return undefined;\n }\n if (outcome !== undefined) {\n return { kind: 'marked', outcome };\n }\n if (response === undefined || !responseFits) {\n return undefined;\n }\n try {\n return { kind: 'marked', outcome: evaluate(activity, response) };\n } catch (thrown) {\n // Scored here, during this component's own render, where no error boundary\n // inside the activity can catch it — so it is handed to one below.\n return {\n kind: 'failed',\n error: thrown instanceof Error ? thrown : new Error(String(thrown)),\n };\n }\n }, [renderMode, activity, outcome, responseKey, responseFits]);\n\n const activityId = activity?.id;\n const mediaBudget = useMemo<MediaBudgetBinding>(\n () => ({\n key: slotMediaKey(PREVIEW_SLOT_ID),\n slotId: PREVIEW_SLOT_ID,\n index: 0,\n ...(activityId !== undefined ? { activityId } : {}),\n }),\n [activityId],\n );\n\n // All hooks are called before these throws, so hook order stays stable.\n if (type === undefined || result === undefined) {\n throw new Error(\n '<ActivityPreview> needs a draft object with a string `type`, to know which activity to check and render.',\n );\n }\n // Checked against the draft's own type, whatever state the draft is in. A\n // mis-wired response is a wiring error however unfinished the draft is, and\n // waiting for the draft to be complete would throw on the very edit that\n // completes it.\n if (response !== undefined && !responseFits) {\n throw new Error(\n `<ActivityPreview> was given a \"${response.type}\" response for a \"${type}\" activity.`,\n );\n }\n if (result.status !== 'complete') {\n if (fallback !== undefined) {\n return <>{fallback(result)}</>;\n }\n return (\n <div\n className=\"lk-preview-unavailable\"\n role=\"note\"\n data-status={result.status}\n {...(locale !== undefined ? { lang: locale } : {})}\n >\n {result.status === 'invalid' ? s.previewInvalid : s.previewIncomplete}\n </div>\n );\n }\n const data = result.data;\n\n // A different simulated response, or a different mode, mounts the activity\n // afresh, play count included: a seeded answer is read at mount, and a practice\n // attempt the author submitted must not carry over into an exam preview. So\n // does a different recording or playback policy, which is a different listening\n // budget — the transport reads its count once, when it mounts.\n const key = `${renderMode}:${responseKey}:${recordingKeyOf(data)}`;\n\n if (marking?.kind === 'failed') {\n // The response is one the scorer cannot read, or a registered scorer threw.\n // Shown as the activity's error fallback, as a render failure would be.\n return (\n <ActivityErrorBoundary key={key} activityTitle={data.title} strings={s}>\n <Rethrow error={marking.error} />\n </ActivityErrorBoundary>\n );\n }\n\n const shared = {\n renderMode,\n // Without a binding a budgeted recording counts nothing in `practice`, and in\n // `exam` the media component refuses to render at all — rightly, on a real\n // paper, where nothing would persist the count. A preview persists nothing by\n // design, so it keeps the count in memory and says so.\n mediaBudget,\n ...(sanitizeHtml !== undefined ? { sanitizeHtml } : {}),\n ...(strings !== undefined ? { strings } : {}),\n ...(theme !== undefined ? { theme } : {}),\n ...(locale !== undefined ? { locale } : {}),\n // Seeded rather than controlled, in every mode, exactly as `<ActivitySequence>`\n // hands a restored answer to its slots — so a renderer written for the\n // sequence reads it here too. `review` is read-only either way, and `key`\n // above re-seeds whenever the response changes.\n ...(response !== undefined ? { defaultValue: response } : {}),\n ...(marking !== undefined ? { outcome: marking.outcome } : {}),\n };\n\n const Custom = renderers?.[data.type];\n if (Custom !== undefined) {\n return <Custom key={key} data={data} {...shared} />;\n }\n switch (data.type) {\n case 'multiple-choice':\n return (\n <MultipleChoice\n key={key}\n data={data}\n shuffleSeed={shuffleSeed ?? PREVIEW_SHUFFLE_SEED}\n {...shared}\n />\n );\n case 'fill-in-the-blanks':\n return <FillInTheBlanks key={key} data={data} {...shared} />;\n case 'gap-select':\n return (\n <GapSelect\n key={key}\n data={data}\n shuffleSeed={shuffleSeed ?? PREVIEW_SHUFFLE_SEED}\n {...shared}\n />\n );\n case 'written-response':\n return <WrittenResponse key={key} data={data} {...shared} />;\n case 'dictation':\n return <Dictation key={key} data={data} {...shared} />;\n case 'read-aloud':\n return <ReadAloud key={key} data={data} recordingBinding={PREVIEW_RECORDING} {...shared} />;\n default:\n everyBuiltInTypeHandled(data);\n return (\n <div className=\"lk-preview-unsupported\" role=\"note\">\n {s.unsupportedActivity}\n </div>\n );\n }\n}\n\n/** Throws `error` while rendering, so the error boundary around it shows its fallback. */\nfunction Rethrow({ error }: { error: Error }): never {\n throw error;\n}\n\nfunction isRecord(value: unknown): value is Readonly<Record<string, unknown>> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/**\n * The recording and its playback policy — not its description, which changes\n * no budget — plus the slower file a dictation or a read-aloud carries, which\n * follows the same policy and must remount when it is swapped for another.\n */\nfunction recordingKeyOf(data: unknown): string {\n const media = isRecord(data) ? data.media : undefined;\n const slowMedia = isRecord(data) ? data.slowMedia : undefined;\n const slowUrl = isRecord(slowMedia) ? slowMedia.url : undefined;\n if (!isRecord(media) && slowUrl === undefined) {\n return '';\n }\n return contentKeyOf([\n isRecord(media) ? media.type : undefined,\n isRecord(media) ? media.url : undefined,\n isRecord(media) ? media.playback : undefined,\n slowUrl,\n ]);\n}\n\nconst identities = new WeakMap<object, number>();\nlet lastIdentity = 0;\n\n/** A key for a value compared by identity: the same object always gets the same one. */\nfunction identityKeyOf(value: object): string {\n let identity = identities.get(value);\n if (identity === undefined) {\n lastIdentity += 1;\n identity = lastIdentity;\n identities.set(value, identity);\n }\n return `#${identity}`;\n}\n\n/**\n * A string that changes exactly when a value's content does.\n *\n * Object keys are sorted, because key order is not content: a draft saved to a\n * JSON column comes back with its keys reordered. Every value is written in a\n * form no other value shares — JSON would write a non-finite number as `null`\n * and drop `undefined`, and those decide different issues. What JSON does not\n * describe — an instance of a class, a function, a reference back to an\n * enclosing object — is compared by identity, and a symbol by its description.\n */\nfunction contentKeyOf(value: unknown, enclosing: Set<object> = new Set()): string {\n switch (typeof value) {\n case 'string':\n return JSON.stringify(value);\n case 'number':\n // `String` keeps NaN, the infinities and every other number apart; only -0\n // needs saying.\n return Object.is(value, -0) ? '-0' : String(value);\n case 'bigint':\n return `${value}n`;\n case 'boolean':\n case 'undefined':\n return String(value);\n case 'symbol':\n return `Symbol(${JSON.stringify(value.description ?? null)})`;\n case 'function':\n return identityKeyOf(value);\n }\n // Every other `typeof` has returned above: this is `null`, or an object.\n if (value === null || typeof value !== 'object') {\n return 'null';\n }\n const prototype = Object.getPrototypeOf(value);\n const plain = Array.isArray(value) || prototype === Object.prototype || prototype === null;\n if (!plain || enclosing.has(value)) {\n return identityKeyOf(value);\n }\n enclosing.add(value);\n try {\n if (Array.isArray(value)) {\n return `[${Array.from(value, (item) => contentKeyOf(item, enclosing)).join(',')}]`;\n }\n const record = value as Record<string, unknown>;\n const fields = Object.keys(record)\n .sort()\n .map((field) => `${JSON.stringify(field)}:${contentKeyOf(record[field], enclosing)}`);\n return `{${fields.join(',')}}`;\n } finally {\n enclosing.delete(value);\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ActivityPreview-CiQ31WX8.js","names":[],"sources":["../src/components/ActivityPreview/ActivityPreview.tsx"],"sourcesContent":["'use client';\n\nimport {\n type ActivityType,\n type DraftNotComplete,\n evaluate,\n type ItemOutcome,\n type LearnerResponse,\n slotMediaKey,\n type ThemeTokens,\n validateDraft,\n} from '@intellectif/lk-core';\nimport { type ReactNode, useMemo } from 'react';\nimport { useLkStrings } from '../../i18n/LkIntlProvider.js';\nimport type { LkStringsOverride } from '../../i18n/strings.js';\nimport { everyBuiltInTypeHandled } from '../_internal.js';\nimport { ActivityErrorBoundary } from '../ActivityErrorBoundary.js';\nimport type { ActivityRenderer } from '../ActivitySequence/index.js';\nimport { Dictation } from '../Dictation/index.js';\nimport { FillInTheBlanks } from '../FillInTheBlanks/index.js';\nimport { GapSelect } from '../GapSelect/index.js';\nimport { MultipleChoice } from '../MultipleChoice/index.js';\nimport { ReadAloud } from '../ReadAloud/index.js';\nimport type { RecordingBinding } from '../ReadAloud/ReadAloud.js';\nimport type { HtmlSanitizer, MediaBudgetBinding, RenderMode } from '../types.js';\nimport { WrittenResponse } from '../WrittenResponse/index.js';\n\n/**\n * The option-shuffle seed a preview uses when none is given. Fixed, so a\n * shuffled question holds still while its author edits it instead of\n * re-ordering on every remount.\n */\nconst PREVIEW_SHUFFLE_SEED = 'lk-activity-preview';\n\n/**\n * The slot a previewed activity stands in for. A recording's play budget is\n * keyed to it and lives only in this mount: the preview counts plays so an\n * author hears the limit a learner gets, and records none of them.\n */\nconst PREVIEW_SLOT_ID = 'preview';\n\n/** Numbers the takes a preview mints keys for, so two are never the same take. */\nlet previewTakeCount = 0;\n\n/**\n * The store a previewed read-aloud records into: it keys the take so the whole\n * flow — record, play it back, submit — runs for the author, and keeps it\n * nowhere, like every other thing a preview does not record.\n *\n * It judges nothing, deliberately. `assess` is optional precisely so this can\n * exist: an author trying a reading out has no assessor to point at, and a\n * preview that demanded one would refuse to render the most common case. The\n * default `practice` mode then shows the \"assessment is not available\" notice,\n * which is the truth about this binding rather than a failure.\n */\nconst PREVIEW_RECORDING: RecordingBinding = {\n upload: async (take) => {\n previewTakeCount += 1;\n return {\n key: `${PREVIEW_SLOT_ID}-take-${previewTakeCount}`,\n mimeType: take.mimeType,\n durationMs: take.durationMs,\n };\n },\n};\n\nexport interface ActivityPreviewProps {\n /**\n * The activity as an editor holds it right now, finished or not. It is checked\n * with `validateDraft` before anything renders — by content, on every render —\n * so a half-written question never reaches a component that assumes a valid\n * one.\n */\n draft: unknown;\n /** The mode to preview in. Defaults to `practice`. */\n renderMode?: RenderMode;\n /**\n * A learner response to show as if a learner had given it. In `practice` and\n * `exam` it seeds the answer, which stays editable; in `review` it is the\n * submitted answer, marked by `evaluate()` unless `outcome` is given.\n */\n response?: LearnerResponse;\n /**\n * The outcome `review` marks the response with, instead of the one\n * `evaluate()` computes — which is how to preview a returned grade on a written\n * response, since the SDK cannot score one. Ignored in other modes.\n */\n outcome?: ItemOutcome;\n /**\n * Rendered in place of the activity while the draft is not complete, with the\n * issues `validateDraft` found — so an editor can list them in its own words.\n * The issue messages are English, and a preview that rendered them would put\n * untranslatable text on screen. Without it, the preview shows the\n * `previewIncomplete` or `previewInvalid` string.\n */\n fallback?: (result: DraftNotComplete) => ReactNode;\n /**\n * Renderers for activity types beyond the built-ins, keyed by `type`, as on\n * `<ActivitySequence>`. An entry receives the shared prop contract and\n * nothing else, so — like the shuffle seed below — the in-memory recording\n * binding a previewed read-aloud is given does not reach one. Nor does the\n * `question` a sequence hands a renderer: a preview is one question with no\n * set around it, so there is no slot to report to and nothing to page away\n * from. A renderer that uses it must treat it as optional, which its type\n * already says.\n */\n renderers?: Readonly<Record<string, ActivityRenderer>>;\n /**\n * Seeds `<MultipleChoice>`'s option shuffle. Defaults to a fixed seed, so the\n * order holds while options are edited; adding or removing one deals them\n * again, because the shuffle orders by position. A `renderers` entry is given\n * no seed, as on `<ActivitySequence>`.\n */\n shuffleSeed?: string;\n /** Renders author-supplied rich text. See `HtmlSanitizer`. */\n sanitizeHtml?: HtmlSanitizer;\n /** Overrides the SDK's chrome text, for the preview's notice and the activity alike. */\n strings?: LkStringsOverride;\n /** Per-instance token overrides, passed to the activity. */\n theme?: Partial<ThemeTokens>;\n /** BCP 47 tag of the interface language, passed to the activity. */\n locale?: string;\n}\n\ntype Marking = { kind: 'marked'; outcome: ItemOutcome } | { kind: 'failed'; error: Error };\n\n/**\n * Renders an activity from an editor's draft, in any render mode, with a\n * simulated learner response — and renders a notice instead while the draft is\n * not an activity yet.\n *\n * Nothing it renders is recorded: no callback is wired, so a practice attempt in\n * a preview scores locally and goes nowhere, and a recording's play limit is\n * counted in memory only.\n *\n * A draft with no string `type`, a type nobody registered, and a `response` of a\n * different type from the draft are wiring errors, and throw.\n */\nexport function ActivityPreview({\n draft,\n renderMode = 'practice',\n response,\n outcome,\n fallback,\n renderers,\n shuffleSeed,\n sanitizeHtml,\n strings,\n theme,\n locale,\n}: ActivityPreviewProps): React.JSX.Element {\n const s = useLkStrings(strings);\n const type = isRecord(draft) && typeof draft.type === 'string' ? draft.type : undefined;\n\n // Validated on the draft's CONTENT, not its identity. The activity components\n // restart whenever `data` changes identity, and an editor that rebuilds its\n // payload on every change would otherwise restart the question an author is\n // trying out each time anything else on the page was edited. The key is read\n // on every render, so a draft changed in place is checked again too.\n const contentKey = contentKeyOf(draft);\n // biome-ignore lint/correctness/useExhaustiveDependencies: the draft's content (contentKey) is the trigger, not its identity\n const result = useMemo(\n () => (type === undefined ? undefined : validateDraft(type as ActivityType, draft)),\n [type, contentKey],\n );\n const activity = result?.status === 'complete' ? result.data : undefined;\n const responseFits = response === undefined || response.type === type;\n const responseKey = response === undefined ? 'none' : contentKeyOf(response);\n\n // biome-ignore lint/correctness/useExhaustiveDependencies: the response's content (responseKey) is the trigger, not its identity\n const marking = useMemo((): Marking | undefined => {\n if (renderMode !== 'review' || activity === undefined) {\n return undefined;\n }\n if (outcome !== undefined) {\n return { kind: 'marked', outcome };\n }\n if (response === undefined || !responseFits) {\n return undefined;\n }\n try {\n return { kind: 'marked', outcome: evaluate(activity, response) };\n } catch (thrown) {\n // Scored here, during this component's own render, where no error boundary\n // inside the activity can catch it — so it is handed to one below.\n return {\n kind: 'failed',\n error: thrown instanceof Error ? thrown : new Error(String(thrown)),\n };\n }\n }, [renderMode, activity, outcome, responseKey, responseFits]);\n\n const activityId = activity?.id;\n const mediaBudget = useMemo<MediaBudgetBinding>(\n () => ({\n key: slotMediaKey(PREVIEW_SLOT_ID),\n slotId: PREVIEW_SLOT_ID,\n index: 0,\n ...(activityId !== undefined ? { activityId } : {}),\n }),\n [activityId],\n );\n\n // All hooks are called before these throws, so hook order stays stable.\n if (type === undefined || result === undefined) {\n throw new Error(\n '<ActivityPreview> needs a draft object with a string `type`, to know which activity to check and render.',\n );\n }\n // Checked against the draft's own type, whatever state the draft is in. A\n // mis-wired response is a wiring error however unfinished the draft is, and\n // waiting for the draft to be complete would throw on the very edit that\n // completes it.\n if (response !== undefined && !responseFits) {\n throw new Error(\n `<ActivityPreview> was given a \"${response.type}\" response for a \"${type}\" activity.`,\n );\n }\n if (result.status !== 'complete') {\n if (fallback !== undefined) {\n return <>{fallback(result)}</>;\n }\n return (\n <div\n className=\"lk-preview-unavailable\"\n role=\"note\"\n data-status={result.status}\n {...(locale !== undefined ? { lang: locale } : {})}\n >\n {result.status === 'invalid' ? s.previewInvalid : s.previewIncomplete}\n </div>\n );\n }\n const data = result.data;\n\n // A different simulated response, or a different mode, mounts the activity\n // afresh, play count included: a seeded answer is read at mount, and a practice\n // attempt the author submitted must not carry over into an exam preview. So\n // does a different recording or playback policy, which is a different listening\n // budget — the transport reads its count once, when it mounts.\n const key = `${renderMode}:${responseKey}:${recordingKeyOf(data)}`;\n\n if (marking?.kind === 'failed') {\n // The response is one the scorer cannot read, or a registered scorer threw.\n // Shown as the activity's error fallback, as a render failure would be.\n return (\n <ActivityErrorBoundary key={key} activityTitle={data.title} strings={s}>\n <Rethrow error={marking.error} />\n </ActivityErrorBoundary>\n );\n }\n\n const shared = {\n renderMode,\n // Without a binding a budgeted recording counts nothing in `practice`, and in\n // `exam` the media component refuses to render at all — rightly, on a real\n // paper, where nothing would persist the count. A preview persists nothing by\n // design, so it keeps the count in memory and says so.\n mediaBudget,\n ...(sanitizeHtml !== undefined ? { sanitizeHtml } : {}),\n ...(strings !== undefined ? { strings } : {}),\n ...(theme !== undefined ? { theme } : {}),\n ...(locale !== undefined ? { locale } : {}),\n // Seeded rather than controlled, in every mode, exactly as `<ActivitySequence>`\n // hands a restored answer to its slots — so a renderer written for the\n // sequence reads it here too. `review` is read-only either way, and `key`\n // above re-seeds whenever the response changes.\n ...(response !== undefined ? { defaultValue: response } : {}),\n ...(marking !== undefined ? { outcome: marking.outcome } : {}),\n };\n\n const Custom = renderers?.[data.type];\n if (Custom !== undefined) {\n return <Custom key={key} data={data} {...shared} />;\n }\n switch (data.type) {\n case 'multiple-choice':\n return (\n <MultipleChoice\n key={key}\n data={data}\n shuffleSeed={shuffleSeed ?? PREVIEW_SHUFFLE_SEED}\n {...shared}\n />\n );\n case 'fill-in-the-blanks':\n return <FillInTheBlanks key={key} data={data} {...shared} />;\n case 'gap-select':\n return (\n <GapSelect\n key={key}\n data={data}\n shuffleSeed={shuffleSeed ?? PREVIEW_SHUFFLE_SEED}\n {...shared}\n />\n );\n case 'written-response':\n return <WrittenResponse key={key} data={data} {...shared} />;\n case 'dictation':\n return <Dictation key={key} data={data} {...shared} />;\n case 'read-aloud':\n return <ReadAloud key={key} data={data} recordingBinding={PREVIEW_RECORDING} {...shared} />;\n default:\n everyBuiltInTypeHandled(data);\n return (\n <div className=\"lk-preview-unsupported\" role=\"note\">\n {s.unsupportedActivity}\n </div>\n );\n }\n}\n\n/** Throws `error` while rendering, so the error boundary around it shows its fallback. */\nfunction Rethrow({ error }: { error: Error }): never {\n throw error;\n}\n\nfunction isRecord(value: unknown): value is Readonly<Record<string, unknown>> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/**\n * The recording and its playback policy — not its description, which changes\n * no budget — plus the slower file a dictation or a read-aloud carries, which\n * follows the same policy and must remount when it is swapped for another.\n */\nfunction recordingKeyOf(data: unknown): string {\n const media = isRecord(data) ? data.media : undefined;\n const slowMedia = isRecord(data) ? data.slowMedia : undefined;\n const slowUrl = isRecord(slowMedia) ? slowMedia.url : undefined;\n if (!isRecord(media) && slowUrl === undefined) {\n return '';\n }\n return contentKeyOf([\n isRecord(media) ? media.type : undefined,\n isRecord(media) ? media.url : undefined,\n isRecord(media) ? media.playback : undefined,\n slowUrl,\n ]);\n}\n\nconst identities = new WeakMap<object, number>();\nlet lastIdentity = 0;\n\n/** A key for a value compared by identity: the same object always gets the same one. */\nfunction identityKeyOf(value: object): string {\n let identity = identities.get(value);\n if (identity === undefined) {\n lastIdentity += 1;\n identity = lastIdentity;\n identities.set(value, identity);\n }\n return `#${identity}`;\n}\n\n/**\n * A string that changes exactly when a value's content does.\n *\n * Object keys are sorted, because key order is not content: a draft saved to a\n * JSON column comes back with its keys reordered. Every value is written in a\n * form no other value shares — JSON would write a non-finite number as `null`\n * and drop `undefined`, and those decide different issues. What JSON does not\n * describe — an instance of a class, a function, a reference back to an\n * enclosing object — is compared by identity, and a symbol by its description.\n */\nfunction contentKeyOf(value: unknown, enclosing: Set<object> = new Set()): string {\n switch (typeof value) {\n case 'string':\n return JSON.stringify(value);\n case 'number':\n // `String` keeps NaN, the infinities and every other number apart; only -0\n // needs saying.\n return Object.is(value, -0) ? '-0' : String(value);\n case 'bigint':\n return `${value}n`;\n case 'boolean':\n case 'undefined':\n return String(value);\n case 'symbol':\n return `Symbol(${JSON.stringify(value.description ?? null)})`;\n case 'function':\n return identityKeyOf(value);\n }\n // Every other `typeof` has returned above: this is `null`, or an object.\n if (value === null || typeof value !== 'object') {\n return 'null';\n }\n const prototype = Object.getPrototypeOf(value);\n const plain = Array.isArray(value) || prototype === Object.prototype || prototype === null;\n if (!plain || enclosing.has(value)) {\n return identityKeyOf(value);\n }\n enclosing.add(value);\n try {\n if (Array.isArray(value)) {\n return `[${Array.from(value, (item) => contentKeyOf(item, enclosing)).join(',')}]`;\n }\n const record = value as Record<string, unknown>;\n const fields = Object.keys(record)\n .sort()\n .map((field) => `${JSON.stringify(field)}:${contentKeyOf(record[field], enclosing)}`);\n return `{${fields.join(',')}}`;\n } finally {\n enclosing.delete(value);\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
require("./useAiHelp-
|
|
2
|
+
require("./useAiHelp-MBQiNgst.cjs");
|
|
3
3
|
const require_LkIntlProvider = require("./LkIntlProvider-oi57lYB0.cjs");
|
|
4
|
-
const require_ActivityErrorBoundary = require("./ActivityErrorBoundary-
|
|
5
|
-
const require_Dictation = require("./Dictation-
|
|
6
|
-
const require_FillInTheBlanks = require("./FillInTheBlanks-
|
|
7
|
-
const require_GapSelect = require("./GapSelect-
|
|
8
|
-
const require_MultipleChoice = require("./MultipleChoice-
|
|
9
|
-
const require_ReadAloud = require("./ReadAloud-
|
|
10
|
-
const require_WrittenResponse = require("./WrittenResponse-
|
|
4
|
+
const require_ActivityErrorBoundary = require("./ActivityErrorBoundary-DEHmEXQw.cjs");
|
|
5
|
+
const require_Dictation = require("./Dictation-Bq6Fn001.cjs");
|
|
6
|
+
const require_FillInTheBlanks = require("./FillInTheBlanks-Cr_QOukp.cjs");
|
|
7
|
+
const require_GapSelect = require("./GapSelect-DlL8eggv.cjs");
|
|
8
|
+
const require_MultipleChoice = require("./MultipleChoice-BJMAgxQd.cjs");
|
|
9
|
+
const require_ReadAloud = require("./ReadAloud-xbRcLkro.cjs");
|
|
10
|
+
const require_WrittenResponse = require("./WrittenResponse-BOsB3DKv.cjs");
|
|
11
11
|
let react = require("react");
|
|
12
12
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
13
13
|
let _intellectif_lk_core = require("@intellectif/lk-core");
|
|
@@ -242,4 +242,4 @@ Object.defineProperty(exports, "ActivityPreview", {
|
|
|
242
242
|
}
|
|
243
243
|
});
|
|
244
244
|
|
|
245
|
-
//# sourceMappingURL=ActivityPreview-
|
|
245
|
+
//# sourceMappingURL=ActivityPreview-DWT4nSAN.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ActivityPreview-BKr34exz.cjs","names":["useLkStrings","useMemo","validateDraft","evaluate","slotMediaKey","ActivityErrorBoundary","MultipleChoice","FillInTheBlanks","GapSelect","WrittenResponse","Dictation","ReadAloud"],"sources":["../src/components/ActivityPreview/ActivityPreview.tsx"],"sourcesContent":["'use client';\n\nimport {\n type ActivityType,\n type DraftNotComplete,\n evaluate,\n type ItemOutcome,\n type LearnerResponse,\n slotMediaKey,\n type ThemeTokens,\n validateDraft,\n} from '@intellectif/lk-core';\nimport { type ReactNode, useMemo } from 'react';\nimport { useLkStrings } from '../../i18n/LkIntlProvider.js';\nimport type { LkStringsOverride } from '../../i18n/strings.js';\nimport { everyBuiltInTypeHandled } from '../_internal.js';\nimport { ActivityErrorBoundary } from '../ActivityErrorBoundary.js';\nimport type { ActivityRenderer } from '../ActivitySequence/index.js';\nimport { Dictation } from '../Dictation/index.js';\nimport { FillInTheBlanks } from '../FillInTheBlanks/index.js';\nimport { GapSelect } from '../GapSelect/index.js';\nimport { MultipleChoice } from '../MultipleChoice/index.js';\nimport { ReadAloud } from '../ReadAloud/index.js';\nimport type { RecordingBinding } from '../ReadAloud/ReadAloud.js';\nimport type { HtmlSanitizer, MediaBudgetBinding, RenderMode } from '../types.js';\nimport { WrittenResponse } from '../WrittenResponse/index.js';\n\n/**\n * The option-shuffle seed a preview uses when none is given. Fixed, so a\n * shuffled question holds still while its author edits it instead of\n * re-ordering on every remount.\n */\nconst PREVIEW_SHUFFLE_SEED = 'lk-activity-preview';\n\n/**\n * The slot a previewed activity stands in for. A recording's play budget is\n * keyed to it and lives only in this mount: the preview counts plays so an\n * author hears the limit a learner gets, and records none of them.\n */\nconst PREVIEW_SLOT_ID = 'preview';\n\n/** Numbers the takes a preview mints keys for, so two are never the same take. */\nlet previewTakeCount = 0;\n\n/**\n * The store a previewed read-aloud records into: it keys the take so the whole\n * flow — record, play it back, submit — runs for the author, and keeps it\n * nowhere, like every other thing a preview does not record.\n *\n * It judges nothing, deliberately. `assess` is optional precisely so this can\n * exist: an author trying a reading out has no assessor to point at, and a\n * preview that demanded one would refuse to render the most common case. The\n * default `practice` mode then shows the \"assessment is not available\" notice,\n * which is the truth about this binding rather than a failure.\n */\nconst PREVIEW_RECORDING: RecordingBinding = {\n upload: async (take) => {\n previewTakeCount += 1;\n return {\n key: `${PREVIEW_SLOT_ID}-take-${previewTakeCount}`,\n mimeType: take.mimeType,\n durationMs: take.durationMs,\n };\n },\n};\n\nexport interface ActivityPreviewProps {\n /**\n * The activity as an editor holds it right now, finished or not. It is checked\n * with `validateDraft` before anything renders — by content, on every render —\n * so a half-written question never reaches a component that assumes a valid\n * one.\n */\n draft: unknown;\n /** The mode to preview in. Defaults to `practice`. */\n renderMode?: RenderMode;\n /**\n * A learner response to show as if a learner had given it. In `practice` and\n * `exam` it seeds the answer, which stays editable; in `review` it is the\n * submitted answer, marked by `evaluate()` unless `outcome` is given.\n */\n response?: LearnerResponse;\n /**\n * The outcome `review` marks the response with, instead of the one\n * `evaluate()` computes — which is how to preview a returned grade on a written\n * response, since the SDK cannot score one. Ignored in other modes.\n */\n outcome?: ItemOutcome;\n /**\n * Rendered in place of the activity while the draft is not complete, with the\n * issues `validateDraft` found — so an editor can list them in its own words.\n * The issue messages are English, and a preview that rendered them would put\n * untranslatable text on screen. Without it, the preview shows the\n * `previewIncomplete` or `previewInvalid` string.\n */\n fallback?: (result: DraftNotComplete) => ReactNode;\n /**\n * Renderers for activity types beyond the built-ins, keyed by `type`, as on\n * `<ActivitySequence>`. An entry receives the shared prop contract and\n * nothing else, so — like the shuffle seed below — the in-memory recording\n * binding a previewed read-aloud is given does not reach one. Nor does the\n * `question` a sequence hands a renderer: a preview is one question with no\n * set around it, so there is no slot to report to and nothing to page away\n * from. A renderer that uses it must treat it as optional, which its type\n * already says.\n */\n renderers?: Readonly<Record<string, ActivityRenderer>>;\n /**\n * Seeds `<MultipleChoice>`'s option shuffle. Defaults to a fixed seed, so the\n * order holds while options are edited; adding or removing one deals them\n * again, because the shuffle orders by position. A `renderers` entry is given\n * no seed, as on `<ActivitySequence>`.\n */\n shuffleSeed?: string;\n /** Renders author-supplied rich text. See `HtmlSanitizer`. */\n sanitizeHtml?: HtmlSanitizer;\n /** Overrides the SDK's chrome text, for the preview's notice and the activity alike. */\n strings?: LkStringsOverride;\n /** Per-instance token overrides, passed to the activity. */\n theme?: Partial<ThemeTokens>;\n /** BCP 47 tag of the interface language, passed to the activity. */\n locale?: string;\n}\n\ntype Marking = { kind: 'marked'; outcome: ItemOutcome } | { kind: 'failed'; error: Error };\n\n/**\n * Renders an activity from an editor's draft, in any render mode, with a\n * simulated learner response — and renders a notice instead while the draft is\n * not an activity yet.\n *\n * Nothing it renders is recorded: no callback is wired, so a practice attempt in\n * a preview scores locally and goes nowhere, and a recording's play limit is\n * counted in memory only.\n *\n * A draft with no string `type`, a type nobody registered, and a `response` of a\n * different type from the draft are wiring errors, and throw.\n */\nexport function ActivityPreview({\n draft,\n renderMode = 'practice',\n response,\n outcome,\n fallback,\n renderers,\n shuffleSeed,\n sanitizeHtml,\n strings,\n theme,\n locale,\n}: ActivityPreviewProps): React.JSX.Element {\n const s = useLkStrings(strings);\n const type = isRecord(draft) && typeof draft.type === 'string' ? draft.type : undefined;\n\n // Validated on the draft's CONTENT, not its identity. The activity components\n // restart whenever `data` changes identity, and an editor that rebuilds its\n // payload on every change would otherwise restart the question an author is\n // trying out each time anything else on the page was edited. The key is read\n // on every render, so a draft changed in place is checked again too.\n const contentKey = contentKeyOf(draft);\n // biome-ignore lint/correctness/useExhaustiveDependencies: the draft's content (contentKey) is the trigger, not its identity\n const result = useMemo(\n () => (type === undefined ? undefined : validateDraft(type as ActivityType, draft)),\n [type, contentKey],\n );\n const activity = result?.status === 'complete' ? result.data : undefined;\n const responseFits = response === undefined || response.type === type;\n const responseKey = response === undefined ? 'none' : contentKeyOf(response);\n\n // biome-ignore lint/correctness/useExhaustiveDependencies: the response's content (responseKey) is the trigger, not its identity\n const marking = useMemo((): Marking | undefined => {\n if (renderMode !== 'review' || activity === undefined) {\n return undefined;\n }\n if (outcome !== undefined) {\n return { kind: 'marked', outcome };\n }\n if (response === undefined || !responseFits) {\n return undefined;\n }\n try {\n return { kind: 'marked', outcome: evaluate(activity, response) };\n } catch (thrown) {\n // Scored here, during this component's own render, where no error boundary\n // inside the activity can catch it — so it is handed to one below.\n return {\n kind: 'failed',\n error: thrown instanceof Error ? thrown : new Error(String(thrown)),\n };\n }\n }, [renderMode, activity, outcome, responseKey, responseFits]);\n\n const activityId = activity?.id;\n const mediaBudget = useMemo<MediaBudgetBinding>(\n () => ({\n key: slotMediaKey(PREVIEW_SLOT_ID),\n slotId: PREVIEW_SLOT_ID,\n index: 0,\n ...(activityId !== undefined ? { activityId } : {}),\n }),\n [activityId],\n );\n\n // All hooks are called before these throws, so hook order stays stable.\n if (type === undefined || result === undefined) {\n throw new Error(\n '<ActivityPreview> needs a draft object with a string `type`, to know which activity to check and render.',\n );\n }\n // Checked against the draft's own type, whatever state the draft is in. A\n // mis-wired response is a wiring error however unfinished the draft is, and\n // waiting for the draft to be complete would throw on the very edit that\n // completes it.\n if (response !== undefined && !responseFits) {\n throw new Error(\n `<ActivityPreview> was given a \"${response.type}\" response for a \"${type}\" activity.`,\n );\n }\n if (result.status !== 'complete') {\n if (fallback !== undefined) {\n return <>{fallback(result)}</>;\n }\n return (\n <div\n className=\"lk-preview-unavailable\"\n role=\"note\"\n data-status={result.status}\n {...(locale !== undefined ? { lang: locale } : {})}\n >\n {result.status === 'invalid' ? s.previewInvalid : s.previewIncomplete}\n </div>\n );\n }\n const data = result.data;\n\n // A different simulated response, or a different mode, mounts the activity\n // afresh, play count included: a seeded answer is read at mount, and a practice\n // attempt the author submitted must not carry over into an exam preview. So\n // does a different recording or playback policy, which is a different listening\n // budget — the transport reads its count once, when it mounts.\n const key = `${renderMode}:${responseKey}:${recordingKeyOf(data)}`;\n\n if (marking?.kind === 'failed') {\n // The response is one the scorer cannot read, or a registered scorer threw.\n // Shown as the activity's error fallback, as a render failure would be.\n return (\n <ActivityErrorBoundary key={key} activityTitle={data.title} strings={s}>\n <Rethrow error={marking.error} />\n </ActivityErrorBoundary>\n );\n }\n\n const shared = {\n renderMode,\n // Without a binding a budgeted recording counts nothing in `practice`, and in\n // `exam` the media component refuses to render at all — rightly, on a real\n // paper, where nothing would persist the count. A preview persists nothing by\n // design, so it keeps the count in memory and says so.\n mediaBudget,\n ...(sanitizeHtml !== undefined ? { sanitizeHtml } : {}),\n ...(strings !== undefined ? { strings } : {}),\n ...(theme !== undefined ? { theme } : {}),\n ...(locale !== undefined ? { locale } : {}),\n // Seeded rather than controlled, in every mode, exactly as `<ActivitySequence>`\n // hands a restored answer to its slots — so a renderer written for the\n // sequence reads it here too. `review` is read-only either way, and `key`\n // above re-seeds whenever the response changes.\n ...(response !== undefined ? { defaultValue: response } : {}),\n ...(marking !== undefined ? { outcome: marking.outcome } : {}),\n };\n\n const Custom = renderers?.[data.type];\n if (Custom !== undefined) {\n return <Custom key={key} data={data} {...shared} />;\n }\n switch (data.type) {\n case 'multiple-choice':\n return (\n <MultipleChoice\n key={key}\n data={data}\n shuffleSeed={shuffleSeed ?? PREVIEW_SHUFFLE_SEED}\n {...shared}\n />\n );\n case 'fill-in-the-blanks':\n return <FillInTheBlanks key={key} data={data} {...shared} />;\n case 'gap-select':\n return (\n <GapSelect\n key={key}\n data={data}\n shuffleSeed={shuffleSeed ?? PREVIEW_SHUFFLE_SEED}\n {...shared}\n />\n );\n case 'written-response':\n return <WrittenResponse key={key} data={data} {...shared} />;\n case 'dictation':\n return <Dictation key={key} data={data} {...shared} />;\n case 'read-aloud':\n return <ReadAloud key={key} data={data} recordingBinding={PREVIEW_RECORDING} {...shared} />;\n default:\n everyBuiltInTypeHandled(data);\n return (\n <div className=\"lk-preview-unsupported\" role=\"note\">\n {s.unsupportedActivity}\n </div>\n );\n }\n}\n\n/** Throws `error` while rendering, so the error boundary around it shows its fallback. */\nfunction Rethrow({ error }: { error: Error }): never {\n throw error;\n}\n\nfunction isRecord(value: unknown): value is Readonly<Record<string, unknown>> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/**\n * The recording and its playback policy — not its description, which changes\n * no budget — plus the slower file a dictation or a read-aloud carries, which\n * follows the same policy and must remount when it is swapped for another.\n */\nfunction recordingKeyOf(data: unknown): string {\n const media = isRecord(data) ? data.media : undefined;\n const slowMedia = isRecord(data) ? data.slowMedia : undefined;\n const slowUrl = isRecord(slowMedia) ? slowMedia.url : undefined;\n if (!isRecord(media) && slowUrl === undefined) {\n return '';\n }\n return contentKeyOf([\n isRecord(media) ? media.type : undefined,\n isRecord(media) ? media.url : undefined,\n isRecord(media) ? media.playback : undefined,\n slowUrl,\n ]);\n}\n\nconst identities = new WeakMap<object, number>();\nlet lastIdentity = 0;\n\n/** A key for a value compared by identity: the same object always gets the same one. */\nfunction identityKeyOf(value: object): string {\n let identity = identities.get(value);\n if (identity === undefined) {\n lastIdentity += 1;\n identity = lastIdentity;\n identities.set(value, identity);\n }\n return `#${identity}`;\n}\n\n/**\n * A string that changes exactly when a value's content does.\n *\n * Object keys are sorted, because key order is not content: a draft saved to a\n * JSON column comes back with its keys reordered. Every value is written in a\n * form no other value shares — JSON would write a non-finite number as `null`\n * and drop `undefined`, and those decide different issues. What JSON does not\n * describe — an instance of a class, a function, a reference back to an\n * enclosing object — is compared by identity, and a symbol by its description.\n */\nfunction contentKeyOf(value: unknown, enclosing: Set<object> = new Set()): string {\n switch (typeof value) {\n case 'string':\n return JSON.stringify(value);\n case 'number':\n // `String` keeps NaN, the infinities and every other number apart; only -0\n // needs saying.\n return Object.is(value, -0) ? '-0' : String(value);\n case 'bigint':\n return `${value}n`;\n case 'boolean':\n case 'undefined':\n return String(value);\n case 'symbol':\n return `Symbol(${JSON.stringify(value.description ?? null)})`;\n case 'function':\n return identityKeyOf(value);\n }\n // Every other `typeof` has returned above: this is `null`, or an object.\n if (value === null || typeof value !== 'object') {\n return 'null';\n }\n const prototype = Object.getPrototypeOf(value);\n const plain = Array.isArray(value) || prototype === Object.prototype || prototype === null;\n if (!plain || enclosing.has(value)) {\n return identityKeyOf(value);\n }\n enclosing.add(value);\n try {\n if (Array.isArray(value)) {\n return `[${Array.from(value, (item) => contentKeyOf(item, enclosing)).join(',')}]`;\n }\n const record = value as Record<string, unknown>;\n const fields = Object.keys(record)\n .sort()\n .map((field) => `${JSON.stringify(field)}:${contentKeyOf(record[field], enclosing)}`);\n return `{${fields.join(',')}}`;\n } finally {\n enclosing.delete(value);\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ActivityPreview-DWT4nSAN.cjs","names":["useLkStrings","useMemo","validateDraft","evaluate","slotMediaKey","ActivityErrorBoundary","MultipleChoice","FillInTheBlanks","GapSelect","WrittenResponse","Dictation","ReadAloud"],"sources":["../src/components/ActivityPreview/ActivityPreview.tsx"],"sourcesContent":["'use client';\n\nimport {\n type ActivityType,\n type DraftNotComplete,\n evaluate,\n type ItemOutcome,\n type LearnerResponse,\n slotMediaKey,\n type ThemeTokens,\n validateDraft,\n} from '@intellectif/lk-core';\nimport { type ReactNode, useMemo } from 'react';\nimport { useLkStrings } from '../../i18n/LkIntlProvider.js';\nimport type { LkStringsOverride } from '../../i18n/strings.js';\nimport { everyBuiltInTypeHandled } from '../_internal.js';\nimport { ActivityErrorBoundary } from '../ActivityErrorBoundary.js';\nimport type { ActivityRenderer } from '../ActivitySequence/index.js';\nimport { Dictation } from '../Dictation/index.js';\nimport { FillInTheBlanks } from '../FillInTheBlanks/index.js';\nimport { GapSelect } from '../GapSelect/index.js';\nimport { MultipleChoice } from '../MultipleChoice/index.js';\nimport { ReadAloud } from '../ReadAloud/index.js';\nimport type { RecordingBinding } from '../ReadAloud/ReadAloud.js';\nimport type { HtmlSanitizer, MediaBudgetBinding, RenderMode } from '../types.js';\nimport { WrittenResponse } from '../WrittenResponse/index.js';\n\n/**\n * The option-shuffle seed a preview uses when none is given. Fixed, so a\n * shuffled question holds still while its author edits it instead of\n * re-ordering on every remount.\n */\nconst PREVIEW_SHUFFLE_SEED = 'lk-activity-preview';\n\n/**\n * The slot a previewed activity stands in for. A recording's play budget is\n * keyed to it and lives only in this mount: the preview counts plays so an\n * author hears the limit a learner gets, and records none of them.\n */\nconst PREVIEW_SLOT_ID = 'preview';\n\n/** Numbers the takes a preview mints keys for, so two are never the same take. */\nlet previewTakeCount = 0;\n\n/**\n * The store a previewed read-aloud records into: it keys the take so the whole\n * flow — record, play it back, submit — runs for the author, and keeps it\n * nowhere, like every other thing a preview does not record.\n *\n * It judges nothing, deliberately. `assess` is optional precisely so this can\n * exist: an author trying a reading out has no assessor to point at, and a\n * preview that demanded one would refuse to render the most common case. The\n * default `practice` mode then shows the \"assessment is not available\" notice,\n * which is the truth about this binding rather than a failure.\n */\nconst PREVIEW_RECORDING: RecordingBinding = {\n upload: async (take) => {\n previewTakeCount += 1;\n return {\n key: `${PREVIEW_SLOT_ID}-take-${previewTakeCount}`,\n mimeType: take.mimeType,\n durationMs: take.durationMs,\n };\n },\n};\n\nexport interface ActivityPreviewProps {\n /**\n * The activity as an editor holds it right now, finished or not. It is checked\n * with `validateDraft` before anything renders — by content, on every render —\n * so a half-written question never reaches a component that assumes a valid\n * one.\n */\n draft: unknown;\n /** The mode to preview in. Defaults to `practice`. */\n renderMode?: RenderMode;\n /**\n * A learner response to show as if a learner had given it. In `practice` and\n * `exam` it seeds the answer, which stays editable; in `review` it is the\n * submitted answer, marked by `evaluate()` unless `outcome` is given.\n */\n response?: LearnerResponse;\n /**\n * The outcome `review` marks the response with, instead of the one\n * `evaluate()` computes — which is how to preview a returned grade on a written\n * response, since the SDK cannot score one. Ignored in other modes.\n */\n outcome?: ItemOutcome;\n /**\n * Rendered in place of the activity while the draft is not complete, with the\n * issues `validateDraft` found — so an editor can list them in its own words.\n * The issue messages are English, and a preview that rendered them would put\n * untranslatable text on screen. Without it, the preview shows the\n * `previewIncomplete` or `previewInvalid` string.\n */\n fallback?: (result: DraftNotComplete) => ReactNode;\n /**\n * Renderers for activity types beyond the built-ins, keyed by `type`, as on\n * `<ActivitySequence>`. An entry receives the shared prop contract and\n * nothing else, so — like the shuffle seed below — the in-memory recording\n * binding a previewed read-aloud is given does not reach one. Nor does the\n * `question` a sequence hands a renderer: a preview is one question with no\n * set around it, so there is no slot to report to and nothing to page away\n * from. A renderer that uses it must treat it as optional, which its type\n * already says.\n */\n renderers?: Readonly<Record<string, ActivityRenderer>>;\n /**\n * Seeds `<MultipleChoice>`'s option shuffle. Defaults to a fixed seed, so the\n * order holds while options are edited; adding or removing one deals them\n * again, because the shuffle orders by position. A `renderers` entry is given\n * no seed, as on `<ActivitySequence>`.\n */\n shuffleSeed?: string;\n /** Renders author-supplied rich text. See `HtmlSanitizer`. */\n sanitizeHtml?: HtmlSanitizer;\n /** Overrides the SDK's chrome text, for the preview's notice and the activity alike. */\n strings?: LkStringsOverride;\n /** Per-instance token overrides, passed to the activity. */\n theme?: Partial<ThemeTokens>;\n /** BCP 47 tag of the interface language, passed to the activity. */\n locale?: string;\n}\n\ntype Marking = { kind: 'marked'; outcome: ItemOutcome } | { kind: 'failed'; error: Error };\n\n/**\n * Renders an activity from an editor's draft, in any render mode, with a\n * simulated learner response — and renders a notice instead while the draft is\n * not an activity yet.\n *\n * Nothing it renders is recorded: no callback is wired, so a practice attempt in\n * a preview scores locally and goes nowhere, and a recording's play limit is\n * counted in memory only.\n *\n * A draft with no string `type`, a type nobody registered, and a `response` of a\n * different type from the draft are wiring errors, and throw.\n */\nexport function ActivityPreview({\n draft,\n renderMode = 'practice',\n response,\n outcome,\n fallback,\n renderers,\n shuffleSeed,\n sanitizeHtml,\n strings,\n theme,\n locale,\n}: ActivityPreviewProps): React.JSX.Element {\n const s = useLkStrings(strings);\n const type = isRecord(draft) && typeof draft.type === 'string' ? draft.type : undefined;\n\n // Validated on the draft's CONTENT, not its identity. The activity components\n // restart whenever `data` changes identity, and an editor that rebuilds its\n // payload on every change would otherwise restart the question an author is\n // trying out each time anything else on the page was edited. The key is read\n // on every render, so a draft changed in place is checked again too.\n const contentKey = contentKeyOf(draft);\n // biome-ignore lint/correctness/useExhaustiveDependencies: the draft's content (contentKey) is the trigger, not its identity\n const result = useMemo(\n () => (type === undefined ? undefined : validateDraft(type as ActivityType, draft)),\n [type, contentKey],\n );\n const activity = result?.status === 'complete' ? result.data : undefined;\n const responseFits = response === undefined || response.type === type;\n const responseKey = response === undefined ? 'none' : contentKeyOf(response);\n\n // biome-ignore lint/correctness/useExhaustiveDependencies: the response's content (responseKey) is the trigger, not its identity\n const marking = useMemo((): Marking | undefined => {\n if (renderMode !== 'review' || activity === undefined) {\n return undefined;\n }\n if (outcome !== undefined) {\n return { kind: 'marked', outcome };\n }\n if (response === undefined || !responseFits) {\n return undefined;\n }\n try {\n return { kind: 'marked', outcome: evaluate(activity, response) };\n } catch (thrown) {\n // Scored here, during this component's own render, where no error boundary\n // inside the activity can catch it — so it is handed to one below.\n return {\n kind: 'failed',\n error: thrown instanceof Error ? thrown : new Error(String(thrown)),\n };\n }\n }, [renderMode, activity, outcome, responseKey, responseFits]);\n\n const activityId = activity?.id;\n const mediaBudget = useMemo<MediaBudgetBinding>(\n () => ({\n key: slotMediaKey(PREVIEW_SLOT_ID),\n slotId: PREVIEW_SLOT_ID,\n index: 0,\n ...(activityId !== undefined ? { activityId } : {}),\n }),\n [activityId],\n );\n\n // All hooks are called before these throws, so hook order stays stable.\n if (type === undefined || result === undefined) {\n throw new Error(\n '<ActivityPreview> needs a draft object with a string `type`, to know which activity to check and render.',\n );\n }\n // Checked against the draft's own type, whatever state the draft is in. A\n // mis-wired response is a wiring error however unfinished the draft is, and\n // waiting for the draft to be complete would throw on the very edit that\n // completes it.\n if (response !== undefined && !responseFits) {\n throw new Error(\n `<ActivityPreview> was given a \"${response.type}\" response for a \"${type}\" activity.`,\n );\n }\n if (result.status !== 'complete') {\n if (fallback !== undefined) {\n return <>{fallback(result)}</>;\n }\n return (\n <div\n className=\"lk-preview-unavailable\"\n role=\"note\"\n data-status={result.status}\n {...(locale !== undefined ? { lang: locale } : {})}\n >\n {result.status === 'invalid' ? s.previewInvalid : s.previewIncomplete}\n </div>\n );\n }\n const data = result.data;\n\n // A different simulated response, or a different mode, mounts the activity\n // afresh, play count included: a seeded answer is read at mount, and a practice\n // attempt the author submitted must not carry over into an exam preview. So\n // does a different recording or playback policy, which is a different listening\n // budget — the transport reads its count once, when it mounts.\n const key = `${renderMode}:${responseKey}:${recordingKeyOf(data)}`;\n\n if (marking?.kind === 'failed') {\n // The response is one the scorer cannot read, or a registered scorer threw.\n // Shown as the activity's error fallback, as a render failure would be.\n return (\n <ActivityErrorBoundary key={key} activityTitle={data.title} strings={s}>\n <Rethrow error={marking.error} />\n </ActivityErrorBoundary>\n );\n }\n\n const shared = {\n renderMode,\n // Without a binding a budgeted recording counts nothing in `practice`, and in\n // `exam` the media component refuses to render at all — rightly, on a real\n // paper, where nothing would persist the count. A preview persists nothing by\n // design, so it keeps the count in memory and says so.\n mediaBudget,\n ...(sanitizeHtml !== undefined ? { sanitizeHtml } : {}),\n ...(strings !== undefined ? { strings } : {}),\n ...(theme !== undefined ? { theme } : {}),\n ...(locale !== undefined ? { locale } : {}),\n // Seeded rather than controlled, in every mode, exactly as `<ActivitySequence>`\n // hands a restored answer to its slots — so a renderer written for the\n // sequence reads it here too. `review` is read-only either way, and `key`\n // above re-seeds whenever the response changes.\n ...(response !== undefined ? { defaultValue: response } : {}),\n ...(marking !== undefined ? { outcome: marking.outcome } : {}),\n };\n\n const Custom = renderers?.[data.type];\n if (Custom !== undefined) {\n return <Custom key={key} data={data} {...shared} />;\n }\n switch (data.type) {\n case 'multiple-choice':\n return (\n <MultipleChoice\n key={key}\n data={data}\n shuffleSeed={shuffleSeed ?? PREVIEW_SHUFFLE_SEED}\n {...shared}\n />\n );\n case 'fill-in-the-blanks':\n return <FillInTheBlanks key={key} data={data} {...shared} />;\n case 'gap-select':\n return (\n <GapSelect\n key={key}\n data={data}\n shuffleSeed={shuffleSeed ?? PREVIEW_SHUFFLE_SEED}\n {...shared}\n />\n );\n case 'written-response':\n return <WrittenResponse key={key} data={data} {...shared} />;\n case 'dictation':\n return <Dictation key={key} data={data} {...shared} />;\n case 'read-aloud':\n return <ReadAloud key={key} data={data} recordingBinding={PREVIEW_RECORDING} {...shared} />;\n default:\n everyBuiltInTypeHandled(data);\n return (\n <div className=\"lk-preview-unsupported\" role=\"note\">\n {s.unsupportedActivity}\n </div>\n );\n }\n}\n\n/** Throws `error` while rendering, so the error boundary around it shows its fallback. */\nfunction Rethrow({ error }: { error: Error }): never {\n throw error;\n}\n\nfunction isRecord(value: unknown): value is Readonly<Record<string, unknown>> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/**\n * The recording and its playback policy — not its description, which changes\n * no budget — plus the slower file a dictation or a read-aloud carries, which\n * follows the same policy and must remount when it is swapped for another.\n */\nfunction recordingKeyOf(data: unknown): string {\n const media = isRecord(data) ? data.media : undefined;\n const slowMedia = isRecord(data) ? data.slowMedia : undefined;\n const slowUrl = isRecord(slowMedia) ? slowMedia.url : undefined;\n if (!isRecord(media) && slowUrl === undefined) {\n return '';\n }\n return contentKeyOf([\n isRecord(media) ? media.type : undefined,\n isRecord(media) ? media.url : undefined,\n isRecord(media) ? media.playback : undefined,\n slowUrl,\n ]);\n}\n\nconst identities = new WeakMap<object, number>();\nlet lastIdentity = 0;\n\n/** A key for a value compared by identity: the same object always gets the same one. */\nfunction identityKeyOf(value: object): string {\n let identity = identities.get(value);\n if (identity === undefined) {\n lastIdentity += 1;\n identity = lastIdentity;\n identities.set(value, identity);\n }\n return `#${identity}`;\n}\n\n/**\n * A string that changes exactly when a value's content does.\n *\n * Object keys are sorted, because key order is not content: a draft saved to a\n * JSON column comes back with its keys reordered. Every value is written in a\n * form no other value shares — JSON would write a non-finite number as `null`\n * and drop `undefined`, and those decide different issues. What JSON does not\n * describe — an instance of a class, a function, a reference back to an\n * enclosing object — is compared by identity, and a symbol by its description.\n */\nfunction contentKeyOf(value: unknown, enclosing: Set<object> = new Set()): string {\n switch (typeof value) {\n case 'string':\n return JSON.stringify(value);\n case 'number':\n // `String` keeps NaN, the infinities and every other number apart; only -0\n // needs saying.\n return Object.is(value, -0) ? '-0' : String(value);\n case 'bigint':\n return `${value}n`;\n case 'boolean':\n case 'undefined':\n return String(value);\n case 'symbol':\n return `Symbol(${JSON.stringify(value.description ?? null)})`;\n case 'function':\n return identityKeyOf(value);\n }\n // Every other `typeof` has returned above: this is `null`, or an object.\n if (value === null || typeof value !== 'object') {\n return 'null';\n }\n const prototype = Object.getPrototypeOf(value);\n const plain = Array.isArray(value) || prototype === Object.prototype || prototype === null;\n if (!plain || enclosing.has(value)) {\n return identityKeyOf(value);\n }\n enclosing.add(value);\n try {\n if (Array.isArray(value)) {\n return `[${Array.from(value, (item) => contentKeyOf(item, enclosing)).join(',')}]`;\n }\n const record = value as Record<string, unknown>;\n const fields = Object.keys(record)\n .sort()\n .map((field) => `${JSON.stringify(field)}:${contentKeyOf(record[field], enclosing)}`);\n return `{${fields.join(',')}}`;\n } finally {\n enclosing.delete(value);\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import { d as randomSessionId, f as SequenceSlotContext, h as takeOf, p as mintTake } from "./useAiHelp-
|
|
2
|
+
import { d as randomSessionId, f as SequenceSlotContext, h as takeOf, p as mintTake } from "./useAiHelp-BJy05hqV.js";
|
|
3
3
|
import { i as useLkStrings } from "./LkIntlProvider-D_FdYXjQ.js";
|
|
4
|
-
import { t as Dictation } from "./Dictation-
|
|
5
|
-
import { t as FillInTheBlanks } from "./FillInTheBlanks-
|
|
6
|
-
import { t as GapSelect } from "./GapSelect-
|
|
7
|
-
import { t as MultipleChoice } from "./MultipleChoice-
|
|
8
|
-
import { n as stopCaptureGroup, t as ReadAloud } from "./ReadAloud-
|
|
9
|
-
import { t as WrittenResponse } from "./WrittenResponse-
|
|
4
|
+
import { t as Dictation } from "./Dictation-KM4BboAS.js";
|
|
5
|
+
import { t as FillInTheBlanks } from "./FillInTheBlanks-CRA7MPyq.js";
|
|
6
|
+
import { t as GapSelect } from "./GapSelect-CddiUhaR.js";
|
|
7
|
+
import { t as MultipleChoice } from "./MultipleChoice-PlJ_hL_E.js";
|
|
8
|
+
import { n as stopCaptureGroup, t as ReadAloud } from "./ReadAloud-NMSLzstD.js";
|
|
9
|
+
import { t as WrittenResponse } from "./WrittenResponse-DU10_GFF.js";
|
|
10
10
|
import { t as StimulusPanel } from "./StimulusPanel-BgzByfkb.js";
|
|
11
11
|
import { useEffect, useMemo, useRef, useState } from "react";
|
|
12
12
|
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -137,6 +137,7 @@ function ActivitySequence({ activities, renderers, onActivityComplete, onComplet
|
|
|
137
137
|
const indexRef = useRef(index);
|
|
138
138
|
const reportedRef = useRef(false);
|
|
139
139
|
const channelsRef = useRef(/* @__PURE__ */ new Map());
|
|
140
|
+
const groupsRef = useRef(/* @__PURE__ */ new Map());
|
|
140
141
|
const callsRef = useRef(/* @__PURE__ */ new Map());
|
|
141
142
|
const hostPendingRef = useRef(/* @__PURE__ */ new Set());
|
|
142
143
|
const [portals, setPortals] = useState(() => /* @__PURE__ */ new Map());
|
|
@@ -581,6 +582,9 @@ function ActivitySequence({ activities, renderers, onActivityComplete, onComplet
|
|
|
581
582
|
get scoring() {
|
|
582
583
|
return scoringRef.current;
|
|
583
584
|
},
|
|
585
|
+
get group() {
|
|
586
|
+
return groupsRef.current.get(key);
|
|
587
|
+
},
|
|
584
588
|
get triesClosed() {
|
|
585
589
|
return reportedRef.current;
|
|
586
590
|
},
|
|
@@ -593,6 +597,7 @@ function ActivitySequence({ activities, renderers, onActivityComplete, onComplet
|
|
|
593
597
|
};
|
|
594
598
|
channelsRef.current.set(key, channel);
|
|
595
599
|
}
|
|
600
|
+
groupsRef.current.set(key, slot.group);
|
|
596
601
|
return channel;
|
|
597
602
|
};
|
|
598
603
|
if (!current) return /* @__PURE__ */ jsx("div", { className: "lk-seq" });
|
|
@@ -836,4 +841,4 @@ function ActivitySequence({ activities, renderers, onActivityComplete, onComplet
|
|
|
836
841
|
//#endregion
|
|
837
842
|
export { ActivitySequence as t };
|
|
838
843
|
|
|
839
|
-
//# sourceMappingURL=ActivitySequence-
|
|
844
|
+
//# sourceMappingURL=ActivitySequence-D6bRMUWU.js.map
|