@intellectif/lk-react 2.0.0 → 3.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.
Files changed (58) hide show
  1. package/dist/WrittenResponse-B50EjVR0.d.cts +87 -0
  2. package/dist/WrittenResponse-BHhP936i.d.ts +87 -0
  3. package/dist/{chunk-32WWQOG3.js → chunk-3X7MYAFT.js} +99 -26
  4. package/dist/chunk-3X7MYAFT.js.map +1 -0
  5. package/dist/chunk-7MW7HGOA.cjs +258 -0
  6. package/dist/chunk-7MW7HGOA.cjs.map +1 -0
  7. package/dist/chunk-AEQQ7VPF.js +256 -0
  8. package/dist/chunk-AEQQ7VPF.js.map +1 -0
  9. package/dist/chunk-BGWXN44S.js +152 -0
  10. package/dist/chunk-BGWXN44S.js.map +1 -0
  11. package/dist/chunk-HO3AC3WQ.cjs +154 -0
  12. package/dist/chunk-HO3AC3WQ.cjs.map +1 -0
  13. package/dist/{chunk-73XCWYKD.cjs → chunk-UXO6KYSZ.cjs} +97 -24
  14. package/dist/chunk-UXO6KYSZ.cjs.map +1 -0
  15. package/dist/{chunk-MKH5THDR.cjs → chunk-W6BH2YMZ.cjs} +119 -36
  16. package/dist/chunk-W6BH2YMZ.cjs.map +1 -0
  17. package/dist/{chunk-G5ZEWJRA.js → chunk-ZVFWJTT4.js} +119 -36
  18. package/dist/chunk-ZVFWJTT4.js.map +1 -0
  19. package/dist/components/ActivitySequence.cjs +5 -4
  20. package/dist/components/ActivitySequence.d.cts +52 -4
  21. package/dist/components/ActivitySequence.d.ts +52 -4
  22. package/dist/components/ActivitySequence.js +4 -3
  23. package/dist/components/FillInTheBlanks.cjs +2 -2
  24. package/dist/components/FillInTheBlanks.d.cts +7 -1
  25. package/dist/components/FillInTheBlanks.d.ts +7 -1
  26. package/dist/components/FillInTheBlanks.js +1 -1
  27. package/dist/components/MultipleChoice.cjs +2 -2
  28. package/dist/components/MultipleChoice.d.cts +14 -3
  29. package/dist/components/MultipleChoice.d.ts +14 -3
  30. package/dist/components/MultipleChoice.js +1 -1
  31. package/dist/components/WrittenResponse.cjs +2 -2
  32. package/dist/components/WrittenResponse.d.cts +5 -38
  33. package/dist/components/WrittenResponse.d.ts +5 -38
  34. package/dist/components/WrittenResponse.js +1 -1
  35. package/dist/index.cjs +13 -9
  36. package/dist/index.cjs.map +1 -1
  37. package/dist/index.d.cts +6 -5
  38. package/dist/index.d.ts +6 -5
  39. package/dist/index.js +11 -4
  40. package/dist/index.js.map +1 -1
  41. package/dist/theme/skin.css +75 -0
  42. package/dist/types-CNmjqD6M.d.cts +108 -0
  43. package/dist/types-CNmjqD6M.d.ts +108 -0
  44. package/package.json +70 -28
  45. package/dist/chunk-32WWQOG3.js.map +0 -1
  46. package/dist/chunk-73XCWYKD.cjs.map +0 -1
  47. package/dist/chunk-G5ZEWJRA.js.map +0 -1
  48. package/dist/chunk-KJNQLAB5.cjs +0 -158
  49. package/dist/chunk-KJNQLAB5.cjs.map +0 -1
  50. package/dist/chunk-MKH5THDR.cjs.map +0 -1
  51. package/dist/chunk-OS7MO2OW.js +0 -109
  52. package/dist/chunk-OS7MO2OW.js.map +0 -1
  53. package/dist/chunk-SH7BRP4M.cjs +0 -111
  54. package/dist/chunk-SH7BRP4M.cjs.map +0 -1
  55. package/dist/chunk-V5EN3ESV.js +0 -156
  56. package/dist/chunk-V5EN3ESV.js.map +0 -1
  57. package/dist/types-iknDq5tf.d.cts +0 -17
  58. package/dist/types-iknDq5tf.d.ts +0 -17
@@ -1,158 +0,0 @@
1
- 'use strict';
2
-
3
- var chunkQHOV2A6M_cjs = require('./chunk-QHOV2A6M.cjs');
4
- var chunkHD2AIQRD_cjs = require('./chunk-HD2AIQRD.cjs');
5
- var lkCore = require('@intellectif/lk-core');
6
- var react = require('react');
7
- var jsxRuntime = require('react/jsx-runtime');
8
-
9
- function WrittenResponse({
10
- data,
11
- onSubmitted,
12
- onInteraction,
13
- theme,
14
- locale,
15
- disabled
16
- }) {
17
- const devError = react.useMemo(() => {
18
- if (!chunkQHOV2A6M_cjs.isDevelopment()) {
19
- return null;
20
- }
21
- const result = lkCore.validateActivity("written-response", data);
22
- return result.success ? null : new lkCore.ActivitySchemaError("written-response", result.errors);
23
- }, [data]);
24
- const { state, start, complete, getTimeSpent, reset } = chunkHD2AIQRD_cjs.useActivityState();
25
- const [text, setText] = react.useState("");
26
- const [summary, setSummary] = react.useState(null);
27
- react.useEffect(() => {
28
- setText("");
29
- setSummary(null);
30
- reset();
31
- }, [data, reset]);
32
- if (devError) {
33
- throw devError;
34
- }
35
- const submitted = state === "completed";
36
- const inactive = disabled === true || submitted;
37
- const wordCount = lkCore.countWords(text);
38
- const withinBounds = wordCount >= data.minWords && wordCount <= data.maxWords;
39
- const handleChange = (value) => {
40
- if (inactive) {
41
- return;
42
- }
43
- if (state === "idle") {
44
- start();
45
- }
46
- setText(value);
47
- onInteraction?.({
48
- type: "text-changed",
49
- activityId: data.id,
50
- timestamp: Date.now(),
51
- payload: { wordCount: lkCore.countWords(value) }
52
- });
53
- };
54
- const handleSubmit = (event) => {
55
- event.preventDefault();
56
- if (inactive || text.trim().length === 0) {
57
- return;
58
- }
59
- const outcome = lkCore.evaluate(data, { type: "written-response", text, wordCount });
60
- complete();
61
- const timeSpent = getTimeSpent();
62
- const partial = outcome.status === "deferred" ? outcome.partial : void 0;
63
- const finalWordCount = typeof partial?.wordCount === "number" ? partial.wordCount : wordCount;
64
- const finalWithinBounds = typeof partial?.withinWordBounds === "boolean" ? partial.withinWordBounds : withinBounds;
65
- const xapiStatement = lkCore.xAPIBuilder.buildSubmittedStatement({
66
- actor: chunkQHOV2A6M_cjs.ANONYMOUS_ACTOR,
67
- object: {
68
- id: chunkQHOV2A6M_cjs.objectIdFor(data.id),
69
- name: { [data.locale ?? "en-US"]: data.title },
70
- type: "http://adlnet.gov/expapi/activities/cmi.interaction",
71
- interactionType: "long-fill-in"
72
- },
73
- timeSpentMs: timeSpent,
74
- response: text,
75
- resultExtensions: {
76
- "urn:learning-kit:extension:word-count": finalWordCount,
77
- "urn:learning-kit:extension:within-word-bounds": finalWithinBounds
78
- }
79
- });
80
- onSubmitted({
81
- text,
82
- wordCount: finalWordCount,
83
- withinWordBounds: finalWithinBounds,
84
- timeSpent,
85
- xapiStatement
86
- });
87
- setSummary("Response submitted. It will be graded and your result will appear here later.");
88
- onInteraction?.({
89
- type: "submitted",
90
- activityId: data.id,
91
- timestamp: Date.now(),
92
- payload: { wordCount: finalWordCount, withinWordBounds: finalWithinBounds }
93
- });
94
- };
95
- const promptId = `${data.id}-prompt`;
96
- const counterId = `${data.id}-counter`;
97
- const boundsLabel = data.minWords > 0 ? `${data.minWords}\u2013${data.maxWords} words` : `up to ${data.maxWords} words`;
98
- return /* @__PURE__ */ jsxRuntime.jsxs(
99
- "form",
100
- {
101
- className: "lk-wr",
102
- "aria-label": data.title,
103
- lang: locale,
104
- style: theme,
105
- onSubmit: handleSubmit,
106
- children: [
107
- data.media ? /* @__PURE__ */ jsxRuntime.jsx(chunkQHOV2A6M_cjs.ActivityMedia, { media: data.media }) : null,
108
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "lk-wr-prompt", id: promptId, children: data.prompt }),
109
- /* @__PURE__ */ jsxRuntime.jsx(
110
- "textarea",
111
- {
112
- className: "lk-wr-textarea",
113
- "aria-labelledby": promptId,
114
- "aria-describedby": counterId,
115
- value: text,
116
- onChange: (event) => handleChange(event.target.value),
117
- disabled: inactive,
118
- rows: 8
119
- }
120
- ),
121
- /* @__PURE__ */ jsxRuntime.jsxs(
122
- "div",
123
- {
124
- className: "lk-wr-counter",
125
- id: counterId,
126
- "aria-live": "polite",
127
- "data-within-bounds": withinBounds,
128
- children: [
129
- wordCount,
130
- " ",
131
- wordCount === 1 ? "word" : "words",
132
- " (",
133
- boundsLabel,
134
- ")"
135
- ]
136
- }
137
- ),
138
- /* @__PURE__ */ jsxRuntime.jsx(
139
- "button",
140
- {
141
- type: "submit",
142
- className: "lk-wr-submit",
143
- disabled: inactive || text.trim().length === 0,
144
- children: "Submit"
145
- }
146
- ),
147
- /* @__PURE__ */ jsxRuntime.jsx(chunkQHOV2A6M_cjs.FeedbackRegion, { id: `${data.id}-feedback`, children: summary })
148
- ]
149
- }
150
- );
151
- }
152
- function WrittenResponse2(props) {
153
- return /* @__PURE__ */ jsxRuntime.jsx(chunkQHOV2A6M_cjs.ActivityErrorBoundary, { activityTitle: props.data?.title, children: /* @__PURE__ */ jsxRuntime.jsx(WrittenResponse, { ...props }) });
154
- }
155
-
156
- exports.WrittenResponse = WrittenResponse2;
157
- //# sourceMappingURL=chunk-KJNQLAB5.cjs.map
158
- //# sourceMappingURL=chunk-KJNQLAB5.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/components/WrittenResponse/WrittenResponse.tsx","../src/components/WrittenResponse/index.tsx"],"names":["useMemo","isDevelopment","validateActivity","ActivitySchemaError","useActivityState","useState","useEffect","countWords","evaluate","xAPIBuilder","ANONYMOUS_ACTOR","objectIdFor","jsxs","jsx","ActivityMedia","FeedbackRegion","WrittenResponse","ActivityErrorBoundary"],"mappings":"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","file":"chunk-KJNQLAB5.cjs","sourcesContent":["'use client';\n\nimport {\n ActivitySchemaError,\n countWords,\n evaluate,\n type InteractionEvent,\n type ThemeTokens,\n validateActivity,\n type WrittenResponseData,\n type XAPIStatement,\n xAPIBuilder,\n} from '@intellectif/lk-core';\nimport { type CSSProperties, useEffect, useMemo, useState } from 'react';\nimport { useActivityState } from '../../hooks/useActivityState.js';\nimport { ANONYMOUS_ACTOR, isDevelopment, objectIdFor } from '../_internal.js';\nimport { ActivityMedia } from '../shared/ActivityMedia.js';\nimport { FeedbackRegion } from '../shared/FeedbackRegion.js';\n\n/**\n * Payload delivered when the learner submits a written response. Grading is\n * DEFERRED (asynchronous AI/human grading), so there is no score here — and\n * deliberately no fake one: an ungraded submission must never be shown or\n * stored as a 0. The consumer persists `text` and grades it out-of-band.\n */\nexport interface WrittenResponseSubmission {\n /** The submitted text, verbatim. */\n text: string;\n /** Word count of `text`, recomputed with the canonical `countWords()`. */\n wordCount: number;\n /** Whether `wordCount` falls within the activity's `[minWords, maxWords]`. */\n withinWordBounds: boolean;\n /** Time in milliseconds from first interaction to submission. */\n timeSpent: number;\n /** The SUBMITTED-verb xAPI statement built for this submission (no score fields). */\n xapiStatement: XAPIStatement;\n}\n\n/**\n * Props for {@link WrittenResponse}. Mirrors `ActivityProps` except for the\n * completion callback: a deferred-grading activity completes with a\n * {@link WrittenResponseSubmission} (`onSubmitted`), not a scored\n * `ActivityResult` (`onComplete`) — deviation from Req 22.8 recorded in the\n * roadmap (§3.1): fabricating a score of 0 for ungraded work is the exact\n * bug this type exists to fix.\n */\nexport interface WrittenResponseProps {\n data: WrittenResponseData;\n onSubmitted: (submission: WrittenResponseSubmission) => void;\n onInteraction?: (event: InteractionEvent) => void;\n /** Per-instance token overrides, applied as inline CSS vars on the root. */\n theme?: Partial<ThemeTokens>;\n locale?: string;\n disabled?: boolean;\n}\n\nexport function WrittenResponse({\n data,\n onSubmitted,\n onInteraction,\n theme,\n locale,\n disabled,\n}: WrittenResponseProps) {\n // Dev-only boundary validation (Req 2.3), same convention as MC/FIB.\n const devError = useMemo(() => {\n if (!isDevelopment()) {\n return null;\n }\n const result = validateActivity('written-response', data);\n return result.success ? null : new ActivitySchemaError('written-response', result.errors);\n }, [data]);\n\n const { state, start, complete, getTimeSpent, reset } = useActivityState();\n const [text, setText] = useState('');\n const [summary, setSummary] = useState<string | null>(null);\n\n // Reset on data-prop change (Req 3.7).\n // biome-ignore lint/correctness/useExhaustiveDependencies: data is the reset trigger (Req 3.7)\n useEffect(() => {\n setText('');\n setSummary(null);\n reset();\n }, [data, reset]);\n\n if (devError) {\n throw devError;\n }\n\n const submitted = state === 'completed';\n const inactive = disabled === true || submitted;\n const wordCount = countWords(text);\n const withinBounds = wordCount >= data.minWords && wordCount <= data.maxWords;\n\n const handleChange = (value: string): void => {\n if (inactive) {\n return;\n }\n if (state === 'idle') {\n start();\n }\n setText(value);\n onInteraction?.({\n type: 'text-changed',\n activityId: data.id,\n timestamp: Date.now(),\n payload: { wordCount: countWords(value) },\n });\n };\n\n const handleSubmit = (event: React.FormEvent): void => {\n event.preventDefault();\n if (inactive || text.trim().length === 0) {\n return;\n }\n const outcome = evaluate(data, { type: 'written-response', text, wordCount });\n complete();\n const timeSpent = getTimeSpent();\n const partial = outcome.status === 'deferred' ? outcome.partial : undefined;\n const finalWordCount = typeof partial?.wordCount === 'number' ? partial.wordCount : wordCount;\n const finalWithinBounds =\n typeof partial?.withinWordBounds === 'boolean' ? partial.withinWordBounds : withinBounds;\n const xapiStatement = xAPIBuilder.buildSubmittedStatement({\n actor: ANONYMOUS_ACTOR,\n object: {\n id: objectIdFor(data.id),\n name: { [data.locale ?? 'en-US']: data.title },\n type: 'http://adlnet.gov/expapi/activities/cmi.interaction',\n interactionType: 'long-fill-in',\n },\n timeSpentMs: timeSpent,\n response: text,\n resultExtensions: {\n 'urn:learning-kit:extension:word-count': finalWordCount,\n 'urn:learning-kit:extension:within-word-bounds': finalWithinBounds,\n },\n });\n onSubmitted({\n text,\n wordCount: finalWordCount,\n withinWordBounds: finalWithinBounds,\n timeSpent,\n xapiStatement,\n });\n setSummary('Response submitted. It will be graded and your result will appear here later.');\n onInteraction?.({\n type: 'submitted',\n activityId: data.id,\n timestamp: Date.now(),\n payload: { wordCount: finalWordCount, withinWordBounds: finalWithinBounds },\n });\n };\n\n const promptId = `${data.id}-prompt`;\n const counterId = `${data.id}-counter`;\n const boundsLabel =\n data.minWords > 0 ? `${data.minWords}–${data.maxWords} words` : `up to ${data.maxWords} words`;\n\n return (\n <form\n className=\"lk-wr\"\n aria-label={data.title}\n lang={locale}\n style={theme as CSSProperties | undefined}\n onSubmit={handleSubmit}\n >\n {data.media ? <ActivityMedia media={data.media} /> : null}\n <p className=\"lk-wr-prompt\" id={promptId}>\n {data.prompt}\n </p>\n <textarea\n className=\"lk-wr-textarea\"\n aria-labelledby={promptId}\n aria-describedby={counterId}\n value={text}\n onChange={(event) => handleChange(event.target.value)}\n disabled={inactive}\n rows={8}\n />\n <div\n className=\"lk-wr-counter\"\n id={counterId}\n aria-live=\"polite\"\n data-within-bounds={withinBounds}\n >\n {wordCount} {wordCount === 1 ? 'word' : 'words'} ({boundsLabel})\n </div>\n <button\n type=\"submit\"\n className=\"lk-wr-submit\"\n disabled={inactive || text.trim().length === 0}\n >\n Submit\n </button>\n <FeedbackRegion id={`${data.id}-feedback`}>{summary}</FeedbackRegion>\n </form>\n );\n}\n","'use client';\n\nimport { ActivityErrorBoundary } from '../ActivityErrorBoundary.js';\nimport {\n WrittenResponse as WrittenResponseCore,\n type WrittenResponseProps,\n} from './WrittenResponse.js';\n\nexport type { WrittenResponseProps, WrittenResponseSubmission } from './WrittenResponse.js';\n\n/**\n * Public Written Response activity: a labelled textarea with a live word\n * counter for free-text writing graded asynchronously (AI or human). The core\n * is wrapped in `ActivityErrorBoundary` so a render failure (incl. the dev\n * schema-validation throw) degrades to an accessible fallback.\n */\nexport function WrittenResponse(props: WrittenResponseProps) {\n return (\n <ActivityErrorBoundary activityTitle={props.data?.title}>\n <WrittenResponseCore {...props} />\n </ActivityErrorBoundary>\n );\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/components/MultipleChoice/MultipleChoice.tsx","../src/components/MultipleChoice/index.tsx"],"names":["useMemo","isDevelopment","validateActivity","ActivitySchemaError","useRef","useActivityState","useState","useEffect","randomSessionId","score","xAPIBuilder","ANONYMOUS_ACTOR","objectIdFor","jsxs","jsx","ActivityMedia","FeedbackRegion","MultipleChoice","ActivityErrorBoundary"],"mappings":"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","file":"chunk-MKH5THDR.cjs","sourcesContent":["'use client';\n\nimport {\n ActivitySchemaError,\n type MultipleChoiceData,\n type MultipleChoiceOption,\n score,\n validateActivity,\n xAPIBuilder,\n} from '@intellectif/lk-core';\nimport { type CSSProperties, useEffect, useMemo, useRef, useState } from 'react';\nimport { useActivityState } from '../../hooks/useActivityState.js';\nimport { ANONYMOUS_ACTOR, isDevelopment, objectIdFor, randomSessionId } from '../_internal.js';\nimport { ActivityMedia } from '../shared/ActivityMedia.js';\nimport { FeedbackRegion } from '../shared/FeedbackRegion.js';\nimport type { ActivityProps } from '../types.js';\n\n/** FNV-1a hash → 32-bit seed. */\nfunction hashSeed(input: string): number {\n let h = 2166136261 >>> 0;\n for (let i = 0; i < input.length; i += 1) {\n h ^= input.charCodeAt(i);\n h = Math.imul(h, 16777619) >>> 0;\n }\n return h;\n}\n\n/** Deterministic LCG Fisher–Yates; returns a permutation (no loss/dupe). */\nfunction seededShuffle<T>(items: readonly T[], seed: number): T[] {\n const out = [...items];\n let s = seed >>> 0 || 1;\n for (let i = out.length - 1; i > 0; i -= 1) {\n s = (Math.imul(s, 1664525) + 1013904223) >>> 0;\n const j = s % (i + 1);\n [out[i], out[j]] = [out[j] as T, out[i] as T];\n }\n return out;\n}\n\nexport interface MultipleChoiceProps extends ActivityProps<MultipleChoiceData> {\n /**\n * Seed for the deterministic option shuffle. Supply one (e.g. the attempt\n * id) to make the shuffled order reproducible server-side, stable across\n * page reloads, and identical between SSR and hydration. When absent, a\n * random per-mount session seed is used (order stable within the mount\n * only — the v1 behaviour).\n */\n shuffleSeed?: string;\n}\n\nexport function MultipleChoice({\n data,\n onComplete,\n onInteraction,\n theme,\n locale,\n disabled,\n shuffleSeed,\n}: MultipleChoiceProps) {\n // Dev-only boundary validation (Req 2.3). Throwing during render lets\n // ActivityErrorBoundary catch it. Memoised so it only re-runs on data change.\n const devError = useMemo(() => {\n if (!isDevelopment()) {\n return null;\n }\n const result = validateActivity('multiple-choice', data);\n return result.success ? null : new ActivitySchemaError('multiple-choice', result.errors);\n }, [data]);\n\n // Lazily created only when shuffling without a caller seed: avoids calling\n // crypto.randomUUID (absent on non-secure http origins) unless needed.\n const sessionIdRef = useRef<string | null>(null);\n const { state, start, complete, getTimeSpent, reset } = useActivityState();\n const [selected, setSelected] = useState<string[]>([]);\n const [summary, setSummary] = useState<string | null>(null);\n\n // Reset on data-prop change (Req 3.7). `data` is an intentional change\n // trigger (not read in the body); dropping it would break the reset.\n // biome-ignore lint/correctness/useExhaustiveDependencies: data is the reset trigger (Req 3.7)\n useEffect(() => {\n setSelected([]);\n setSummary(null);\n reset();\n }, [data, reset]);\n\n const displayedOptions = useMemo<MultipleChoiceOption[]>(() => {\n if (!data.shuffle) {\n return [...data.options];\n }\n // biome-ignore lint/suspicious/noAssignInExpressions: sanctioned lazy ref initialization\n const seedSource = shuffleSeed ?? (sessionIdRef.current ??= randomSessionId());\n return seededShuffle(data.options, hashSeed(`${seedSource}:${data.id}`));\n }, [data, shuffleSeed]);\n\n // All hooks are called before this throw, so hook order stays stable.\n if (devError) {\n throw devError;\n }\n\n const isSingle = data.mode === 'single';\n const submitted = state === 'completed';\n const inactive = disabled === true || submitted;\n\n const fireInteraction = (\n type: 'option-selected' | 'option-deselected' | 'submitted',\n payload: Record<string, unknown>,\n ): void => {\n onInteraction?.({ type, activityId: data.id, timestamp: Date.now(), payload });\n };\n\n const selectSingle = (optionId: string): void => {\n if (inactive) {\n return;\n }\n if (state === 'idle') {\n start();\n }\n setSelected([optionId]);\n fireInteraction('option-selected', { optionId });\n };\n\n const toggleMulti = (optionId: string, checked: boolean): void => {\n if (inactive) {\n return;\n }\n if (state === 'idle') {\n start();\n }\n setSelected((prev) => (checked ? [...prev, optionId] : prev.filter((id) => id !== optionId)));\n fireInteraction(checked ? 'option-selected' : 'option-deselected', { optionId });\n };\n\n const handleSubmit = (event: React.FormEvent): void => {\n event.preventDefault();\n if (inactive) {\n return;\n }\n const response = { type: 'multiple-choice', selectedOptionIds: selected } as const;\n const scoringResult = score('multiple-choice', data, response);\n complete();\n const timeSpent = getTimeSpent();\n const xapiStatement = xAPIBuilder.buildAnsweredStatement({\n actor: ANONYMOUS_ACTOR,\n object: {\n id: objectIdFor(data.id),\n name: { [data.locale ?? 'en-US']: data.title },\n type: 'http://adlnet.gov/expapi/activities/cmi.interaction',\n interactionType: 'choice',\n correctResponsesPattern: [\n data.options\n .filter((option) => option.isCorrect)\n .map((option) => option.id)\n .join('[,]'),\n ],\n choices: data.options.map((option) => ({\n id: option.id,\n description: { [data.locale ?? 'en-US']: option.text },\n })),\n },\n scoringResult,\n timeSpentMs: timeSpent,\n response: selected.join(','),\n });\n onComplete({\n score: scoringResult.score,\n maxScore: scoringResult.maxScore,\n passed: scoringResult.passed,\n timeSpent,\n xapiStatement,\n });\n // Core selects the authored overall feedback on `passed` (B3 fix).\n const overall = scoringResult.feedback;\n setSummary(\n `Answer submitted. Score ${Math.round(scoringResult.score * 100)}%. ${\n scoringResult.passed ? 'Passed.' : 'Not passed.'\n }${overall ? ` ${overall}` : ''}`,\n );\n fireInteraction('submitted', {\n selectedOptionIds: selected,\n score: scoringResult.score,\n });\n };\n\n const questionId = `${data.id}-question`;\n\n const optionList = displayedOptions.map((option) => {\n const checked = selected.includes(option.id);\n return (\n <label\n key={option.id}\n className=\"lk-mc-option\"\n data-correct={submitted ? String(option.isCorrect) : undefined}\n >\n <input\n type={isSingle ? 'radio' : 'checkbox'}\n name={isSingle ? `${data.id}-options` : undefined}\n value={option.id}\n checked={checked}\n disabled={inactive}\n aria-disabled={inactive || undefined}\n onChange={(e) =>\n isSingle ? selectSingle(option.id) : toggleMulti(option.id, e.target.checked)\n }\n />\n <span>{option.text}</span>\n {submitted && option.feedback ? (\n <span className=\"lk-mc-option-feedback\" role=\"note\">\n {option.feedback}\n </span>\n ) : null}\n </label>\n );\n });\n\n return (\n <div className=\"lk-mc\" lang={locale} style={theme as CSSProperties | undefined}>\n {data.media ? <ActivityMedia media={data.media} /> : null}\n <form onSubmit={handleSubmit}>\n <fieldset disabled={inactive}>\n <legend id={questionId}>{data.question}</legend>\n {/*\n single: an explicit radiogroup is meaningful (fieldset's implicit\n role is `group`, not `radiogroup`). multi: the fieldset + legend\n already provide a named group — an explicit role=\"group\" here would\n be a redundant, duplicate same-named group in the a11y tree, so we\n use a plain layout div. (Refines the design ARIA sketch.)\n */}\n {isSingle ? (\n <div role=\"radiogroup\" aria-labelledby={questionId}>\n {optionList}\n </div>\n ) : (\n <div>{optionList}</div>\n )}\n <button type=\"submit\" disabled={inactive}>\n Submit\n </button>\n </fieldset>\n </form>\n <FeedbackRegion id={`${data.id}-feedback`}>{summary}</FeedbackRegion>\n </div>\n );\n}\n","'use client';\n\nimport type { MultipleChoiceData } from '@intellectif/lk-core';\nimport { ActivityErrorBoundary } from '../ActivityErrorBoundary.js';\nimport type { ActivityProps } from '../types.js';\nimport { MultipleChoice as MultipleChoiceCore } from './MultipleChoice.js';\n\nexport type { ActivityProps } from '../types.js';\n\n/**\n * Public Multiple Choice activity: the core component wrapped in\n * `ActivityErrorBoundary` so a render failure (incl. the dev schema-validation\n * throw) degrades to an accessible fallback instead of crashing the host.\n */\nexport function MultipleChoice(props: ActivityProps<MultipleChoiceData>) {\n return (\n <ActivityErrorBoundary activityTitle={props.data?.title}>\n <MultipleChoiceCore {...props} />\n </ActivityErrorBoundary>\n );\n}\n"]}
@@ -1,109 +0,0 @@
1
- import { MultipleChoice } from './chunk-G5ZEWJRA.js';
2
- import { FillInTheBlanks } from './chunk-32WWQOG3.js';
3
- import { useState, useRef, useEffect } from 'react';
4
- import { jsx, jsxs } from 'react/jsx-runtime';
5
-
6
- function ActivitySequence({
7
- activities,
8
- onActivityComplete,
9
- onComplete,
10
- onInteraction,
11
- theme,
12
- locale,
13
- disabled
14
- }) {
15
- const [index, setIndex] = useState(0);
16
- const [results, setResults] = useState(
17
- () => activities.map(() => null)
18
- );
19
- const regionRef = useRef(null);
20
- const mountedRef = useRef(false);
21
- const setKey = JSON.stringify(activities.map((activity) => activity.id));
22
- const [prevSetKey, setPrevSetKey] = useState(setKey);
23
- if (prevSetKey !== setKey) {
24
- setPrevSetKey(setKey);
25
- setIndex(0);
26
- setResults(activities.map(() => null));
27
- }
28
- useEffect(() => {
29
- if (mountedRef.current) {
30
- regionRef.current?.focus();
31
- } else {
32
- mountedRef.current = true;
33
- }
34
- }, [index]);
35
- const total = activities.length;
36
- const current = activities[index];
37
- const go = (next) => {
38
- setIndex(next);
39
- };
40
- const handleComplete = (result) => {
41
- const nextResults = results.slice();
42
- nextResults[index] = result;
43
- setResults(nextResults);
44
- onActivityComplete?.(result, index);
45
- if (nextResults.every((r) => r !== null)) {
46
- onComplete?.(nextResults);
47
- }
48
- };
49
- if (!current) {
50
- return /* @__PURE__ */ jsx("div", { className: "lk-seq" });
51
- }
52
- const childProps = {
53
- onComplete: handleComplete,
54
- ...onInteraction ? { onInteraction } : {},
55
- ...theme ? { theme } : {},
56
- ...locale ? { locale } : {},
57
- ...disabled ? { disabled } : {}
58
- };
59
- return /* @__PURE__ */ jsxs("div", { className: "lk-seq", lang: locale, children: [
60
- /* @__PURE__ */ jsxs("div", { className: "lk-seq-progress", "aria-live": "polite", role: "status", children: [
61
- "Question ",
62
- index + 1,
63
- " of ",
64
- total
65
- ] }),
66
- /* @__PURE__ */ jsx(
67
- "section",
68
- {
69
- className: "lk-seq-question",
70
- ref: regionRef,
71
- tabIndex: -1,
72
- "aria-label": `Question ${index + 1} of ${total}`,
73
- children: current.type === "multiple-choice" ? /* @__PURE__ */ jsx(MultipleChoice, { data: current, ...childProps }, `${index}:${current.id}`) : current.type === "fill-in-the-blanks" ? /* @__PURE__ */ jsx(FillInTheBlanks, { data: current, ...childProps }, `${index}:${current.id}`) : (
74
- // written-response completes without a score (deferred grading), so it
75
- // cannot fill an ActivityResult slot; sequence support arrives with the
76
- // widened results contract in v0.4. Render standalone <WrittenResponse>
77
- // instead of putting it in a sequence.
78
- /* @__PURE__ */ jsx("div", { className: "lk-seq-unsupported", role: "note", children: "This activity type is not supported inside a question set yet." })
79
- )
80
- }
81
- ),
82
- /* @__PURE__ */ jsxs("div", { className: "lk-seq-nav", children: [
83
- /* @__PURE__ */ jsx(
84
- "button",
85
- {
86
- type: "button",
87
- className: "lk-seq-prev",
88
- onClick: () => go(index - 1),
89
- disabled: index === 0,
90
- children: "Previous"
91
- }
92
- ),
93
- /* @__PURE__ */ jsx(
94
- "button",
95
- {
96
- type: "button",
97
- className: "lk-seq-next",
98
- onClick: () => go(index + 1),
99
- disabled: index === total - 1,
100
- children: "Next"
101
- }
102
- )
103
- ] })
104
- ] });
105
- }
106
-
107
- export { ActivitySequence };
108
- //# sourceMappingURL=chunk-OS7MO2OW.js.map
109
- //# sourceMappingURL=chunk-OS7MO2OW.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/components/ActivitySequence/ActivitySequence.tsx"],"names":[],"mappings":"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","file":"chunk-OS7MO2OW.js","sourcesContent":["'use client';\n\nimport type {\n ActivityData,\n ActivityResult,\n InteractionEvent,\n ThemeTokens,\n} from '@intellectif/lk-core';\nimport { useEffect, useRef, useState } from 'react';\nimport { FillInTheBlanks } from '../FillInTheBlanks/index.js';\nimport { MultipleChoice } from '../MultipleChoice/index.js';\n\nexport interface ActivitySequenceProps {\n /** The ordered set of activities to present, one at a time. */\n activities: ActivityData[];\n /** Called whenever an individual activity is completed. */\n onActivityComplete?: (result: ActivityResult, index: number) => void;\n /** Called once every activity in the set has been completed. */\n onComplete?: (results: ActivityResult[]) => void;\n /** Forwarded to each activity. */\n onInteraction?: (event: InteractionEvent) => void;\n theme?: Partial<ThemeTokens>;\n locale?: string;\n disabled?: boolean;\n}\n\n/**\n * Presents a set of activities as an in-place pager: one question visible at a\n * time, navigated with Prev/Next (no page scrolling). Linear free navigation —\n * the learner may move back and forth; activities keep their own submit and\n * scoring. On navigation, focus moves to the question region and the position\n * is announced via an aria-live region.\n */\nexport function ActivitySequence({\n activities,\n onActivityComplete,\n onComplete,\n onInteraction,\n theme,\n locale,\n disabled,\n}: ActivitySequenceProps): React.JSX.Element {\n const [index, setIndex] = useState(0);\n const [results, setResults] = useState<(ActivityResult | null)[]>(() =>\n activities.map(() => null),\n );\n const regionRef = useRef<HTMLDivElement>(null);\n const mountedRef = useRef(false);\n\n // Reset pager position and results when the activity SET changes (B8):\n // without this, results from the previous set leak into the new one and can\n // fire onComplete with a mixed old/new array. Keyed on the set's CONTENT\n // identity (ordered activity ids), not the array reference — a parent that\n // re-creates a structurally identical array on every render (inline\n // literals, `.map()` in render) must not wipe in-progress answers.\n // Render-time state adjustment per React's \"adjusting state when a prop\n // changes\" pattern.\n // JSON.stringify (not join) so ids containing the separator cannot collide:\n // ['a','bc'] and ['ab','c'] must produce different keys.\n const setKey = JSON.stringify(activities.map((activity) => activity.id));\n const [prevSetKey, setPrevSetKey] = useState(setKey);\n if (prevSetKey !== setKey) {\n setPrevSetKey(setKey);\n setIndex(0);\n setResults(activities.map(() => null));\n }\n\n // After a navigation (not the initial mount) move focus to the question\n // region so screen-reader / keyboard users land on the new question.\n // biome-ignore lint/correctness/useExhaustiveDependencies: index is the intended trigger\n useEffect(() => {\n if (mountedRef.current) {\n regionRef.current?.focus();\n } else {\n mountedRef.current = true;\n }\n }, [index]);\n\n const total = activities.length;\n const current = activities[index];\n\n const go = (next: number): void => {\n setIndex(next);\n };\n\n const handleComplete = (result: ActivityResult): void => {\n const nextResults = results.slice();\n nextResults[index] = result;\n setResults(nextResults);\n onActivityComplete?.(result, index);\n if (nextResults.every((r): r is ActivityResult => r !== null)) {\n onComplete?.(nextResults);\n }\n };\n\n if (!current) {\n return <div className=\"lk-seq\" />;\n }\n\n const childProps = {\n onComplete: handleComplete,\n ...(onInteraction ? { onInteraction } : {}),\n ...(theme ? { theme } : {}),\n ...(locale ? { locale } : {}),\n ...(disabled ? { disabled } : {}),\n };\n\n return (\n <div className=\"lk-seq\" lang={locale}>\n <div className=\"lk-seq-progress\" aria-live=\"polite\" role=\"status\">\n Question {index + 1} of {total}\n </div>\n\n <section\n className=\"lk-seq-question\"\n ref={regionRef}\n tabIndex={-1}\n aria-label={`Question ${index + 1} of ${total}`}\n >\n {current.type === 'multiple-choice' ? (\n <MultipleChoice key={`${index}:${current.id}`} data={current} {...childProps} />\n ) : current.type === 'fill-in-the-blanks' ? (\n <FillInTheBlanks key={`${index}:${current.id}`} data={current} {...childProps} />\n ) : (\n // written-response completes without a score (deferred grading), so it\n // cannot fill an ActivityResult slot; sequence support arrives with the\n // widened results contract in v0.4. Render standalone <WrittenResponse>\n // instead of putting it in a sequence.\n <div className=\"lk-seq-unsupported\" role=\"note\">\n This activity type is not supported inside a question set yet.\n </div>\n )}\n </section>\n\n <div className=\"lk-seq-nav\">\n <button\n type=\"button\"\n className=\"lk-seq-prev\"\n onClick={() => go(index - 1)}\n disabled={index === 0}\n >\n Previous\n </button>\n <button\n type=\"button\"\n className=\"lk-seq-next\"\n onClick={() => go(index + 1)}\n disabled={index === total - 1}\n >\n Next\n </button>\n </div>\n </div>\n );\n}\n"]}
@@ -1,111 +0,0 @@
1
- 'use strict';
2
-
3
- var chunkMKH5THDR_cjs = require('./chunk-MKH5THDR.cjs');
4
- var chunk73XCWYKD_cjs = require('./chunk-73XCWYKD.cjs');
5
- var react = require('react');
6
- var jsxRuntime = require('react/jsx-runtime');
7
-
8
- function ActivitySequence({
9
- activities,
10
- onActivityComplete,
11
- onComplete,
12
- onInteraction,
13
- theme,
14
- locale,
15
- disabled
16
- }) {
17
- const [index, setIndex] = react.useState(0);
18
- const [results, setResults] = react.useState(
19
- () => activities.map(() => null)
20
- );
21
- const regionRef = react.useRef(null);
22
- const mountedRef = react.useRef(false);
23
- const setKey = JSON.stringify(activities.map((activity) => activity.id));
24
- const [prevSetKey, setPrevSetKey] = react.useState(setKey);
25
- if (prevSetKey !== setKey) {
26
- setPrevSetKey(setKey);
27
- setIndex(0);
28
- setResults(activities.map(() => null));
29
- }
30
- react.useEffect(() => {
31
- if (mountedRef.current) {
32
- regionRef.current?.focus();
33
- } else {
34
- mountedRef.current = true;
35
- }
36
- }, [index]);
37
- const total = activities.length;
38
- const current = activities[index];
39
- const go = (next) => {
40
- setIndex(next);
41
- };
42
- const handleComplete = (result) => {
43
- const nextResults = results.slice();
44
- nextResults[index] = result;
45
- setResults(nextResults);
46
- onActivityComplete?.(result, index);
47
- if (nextResults.every((r) => r !== null)) {
48
- onComplete?.(nextResults);
49
- }
50
- };
51
- if (!current) {
52
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "lk-seq" });
53
- }
54
- const childProps = {
55
- onComplete: handleComplete,
56
- ...onInteraction ? { onInteraction } : {},
57
- ...theme ? { theme } : {},
58
- ...locale ? { locale } : {},
59
- ...disabled ? { disabled } : {}
60
- };
61
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "lk-seq", lang: locale, children: [
62
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "lk-seq-progress", "aria-live": "polite", role: "status", children: [
63
- "Question ",
64
- index + 1,
65
- " of ",
66
- total
67
- ] }),
68
- /* @__PURE__ */ jsxRuntime.jsx(
69
- "section",
70
- {
71
- className: "lk-seq-question",
72
- ref: regionRef,
73
- tabIndex: -1,
74
- "aria-label": `Question ${index + 1} of ${total}`,
75
- children: current.type === "multiple-choice" ? /* @__PURE__ */ jsxRuntime.jsx(chunkMKH5THDR_cjs.MultipleChoice, { data: current, ...childProps }, `${index}:${current.id}`) : current.type === "fill-in-the-blanks" ? /* @__PURE__ */ jsxRuntime.jsx(chunk73XCWYKD_cjs.FillInTheBlanks, { data: current, ...childProps }, `${index}:${current.id}`) : (
76
- // written-response completes without a score (deferred grading), so it
77
- // cannot fill an ActivityResult slot; sequence support arrives with the
78
- // widened results contract in v0.4. Render standalone <WrittenResponse>
79
- // instead of putting it in a sequence.
80
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "lk-seq-unsupported", role: "note", children: "This activity type is not supported inside a question set yet." })
81
- )
82
- }
83
- ),
84
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "lk-seq-nav", children: [
85
- /* @__PURE__ */ jsxRuntime.jsx(
86
- "button",
87
- {
88
- type: "button",
89
- className: "lk-seq-prev",
90
- onClick: () => go(index - 1),
91
- disabled: index === 0,
92
- children: "Previous"
93
- }
94
- ),
95
- /* @__PURE__ */ jsxRuntime.jsx(
96
- "button",
97
- {
98
- type: "button",
99
- className: "lk-seq-next",
100
- onClick: () => go(index + 1),
101
- disabled: index === total - 1,
102
- children: "Next"
103
- }
104
- )
105
- ] })
106
- ] });
107
- }
108
-
109
- exports.ActivitySequence = ActivitySequence;
110
- //# sourceMappingURL=chunk-SH7BRP4M.cjs.map
111
- //# sourceMappingURL=chunk-SH7BRP4M.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/components/ActivitySequence/ActivitySequence.tsx"],"names":["useState","useRef","useEffect","jsx","jsxs","MultipleChoice","FillInTheBlanks"],"mappings":"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","file":"chunk-SH7BRP4M.cjs","sourcesContent":["'use client';\n\nimport type {\n ActivityData,\n ActivityResult,\n InteractionEvent,\n ThemeTokens,\n} from '@intellectif/lk-core';\nimport { useEffect, useRef, useState } from 'react';\nimport { FillInTheBlanks } from '../FillInTheBlanks/index.js';\nimport { MultipleChoice } from '../MultipleChoice/index.js';\n\nexport interface ActivitySequenceProps {\n /** The ordered set of activities to present, one at a time. */\n activities: ActivityData[];\n /** Called whenever an individual activity is completed. */\n onActivityComplete?: (result: ActivityResult, index: number) => void;\n /** Called once every activity in the set has been completed. */\n onComplete?: (results: ActivityResult[]) => void;\n /** Forwarded to each activity. */\n onInteraction?: (event: InteractionEvent) => void;\n theme?: Partial<ThemeTokens>;\n locale?: string;\n disabled?: boolean;\n}\n\n/**\n * Presents a set of activities as an in-place pager: one question visible at a\n * time, navigated with Prev/Next (no page scrolling). Linear free navigation —\n * the learner may move back and forth; activities keep their own submit and\n * scoring. On navigation, focus moves to the question region and the position\n * is announced via an aria-live region.\n */\nexport function ActivitySequence({\n activities,\n onActivityComplete,\n onComplete,\n onInteraction,\n theme,\n locale,\n disabled,\n}: ActivitySequenceProps): React.JSX.Element {\n const [index, setIndex] = useState(0);\n const [results, setResults] = useState<(ActivityResult | null)[]>(() =>\n activities.map(() => null),\n );\n const regionRef = useRef<HTMLDivElement>(null);\n const mountedRef = useRef(false);\n\n // Reset pager position and results when the activity SET changes (B8):\n // without this, results from the previous set leak into the new one and can\n // fire onComplete with a mixed old/new array. Keyed on the set's CONTENT\n // identity (ordered activity ids), not the array reference — a parent that\n // re-creates a structurally identical array on every render (inline\n // literals, `.map()` in render) must not wipe in-progress answers.\n // Render-time state adjustment per React's \"adjusting state when a prop\n // changes\" pattern.\n // JSON.stringify (not join) so ids containing the separator cannot collide:\n // ['a','bc'] and ['ab','c'] must produce different keys.\n const setKey = JSON.stringify(activities.map((activity) => activity.id));\n const [prevSetKey, setPrevSetKey] = useState(setKey);\n if (prevSetKey !== setKey) {\n setPrevSetKey(setKey);\n setIndex(0);\n setResults(activities.map(() => null));\n }\n\n // After a navigation (not the initial mount) move focus to the question\n // region so screen-reader / keyboard users land on the new question.\n // biome-ignore lint/correctness/useExhaustiveDependencies: index is the intended trigger\n useEffect(() => {\n if (mountedRef.current) {\n regionRef.current?.focus();\n } else {\n mountedRef.current = true;\n }\n }, [index]);\n\n const total = activities.length;\n const current = activities[index];\n\n const go = (next: number): void => {\n setIndex(next);\n };\n\n const handleComplete = (result: ActivityResult): void => {\n const nextResults = results.slice();\n nextResults[index] = result;\n setResults(nextResults);\n onActivityComplete?.(result, index);\n if (nextResults.every((r): r is ActivityResult => r !== null)) {\n onComplete?.(nextResults);\n }\n };\n\n if (!current) {\n return <div className=\"lk-seq\" />;\n }\n\n const childProps = {\n onComplete: handleComplete,\n ...(onInteraction ? { onInteraction } : {}),\n ...(theme ? { theme } : {}),\n ...(locale ? { locale } : {}),\n ...(disabled ? { disabled } : {}),\n };\n\n return (\n <div className=\"lk-seq\" lang={locale}>\n <div className=\"lk-seq-progress\" aria-live=\"polite\" role=\"status\">\n Question {index + 1} of {total}\n </div>\n\n <section\n className=\"lk-seq-question\"\n ref={regionRef}\n tabIndex={-1}\n aria-label={`Question ${index + 1} of ${total}`}\n >\n {current.type === 'multiple-choice' ? (\n <MultipleChoice key={`${index}:${current.id}`} data={current} {...childProps} />\n ) : current.type === 'fill-in-the-blanks' ? (\n <FillInTheBlanks key={`${index}:${current.id}`} data={current} {...childProps} />\n ) : (\n // written-response completes without a score (deferred grading), so it\n // cannot fill an ActivityResult slot; sequence support arrives with the\n // widened results contract in v0.4. Render standalone <WrittenResponse>\n // instead of putting it in a sequence.\n <div className=\"lk-seq-unsupported\" role=\"note\">\n This activity type is not supported inside a question set yet.\n </div>\n )}\n </section>\n\n <div className=\"lk-seq-nav\">\n <button\n type=\"button\"\n className=\"lk-seq-prev\"\n onClick={() => go(index - 1)}\n disabled={index === 0}\n >\n Previous\n </button>\n <button\n type=\"button\"\n className=\"lk-seq-next\"\n onClick={() => go(index + 1)}\n disabled={index === total - 1}\n >\n Next\n </button>\n </div>\n </div>\n );\n}\n"]}
@@ -1,156 +0,0 @@
1
- import { ActivityErrorBoundary, isDevelopment, ActivityMedia, FeedbackRegion, ANONYMOUS_ACTOR, objectIdFor } from './chunk-UIGIYYGY.js';
2
- import { useActivityState } from './chunk-5HVUKXF3.js';
3
- import { validateActivity, ActivitySchemaError, countWords, evaluate, xAPIBuilder } from '@intellectif/lk-core';
4
- import { useMemo, useState, useEffect } from 'react';
5
- import { jsx, jsxs } from 'react/jsx-runtime';
6
-
7
- function WrittenResponse({
8
- data,
9
- onSubmitted,
10
- onInteraction,
11
- theme,
12
- locale,
13
- disabled
14
- }) {
15
- const devError = useMemo(() => {
16
- if (!isDevelopment()) {
17
- return null;
18
- }
19
- const result = validateActivity("written-response", data);
20
- return result.success ? null : new ActivitySchemaError("written-response", result.errors);
21
- }, [data]);
22
- const { state, start, complete, getTimeSpent, reset } = useActivityState();
23
- const [text, setText] = useState("");
24
- const [summary, setSummary] = useState(null);
25
- useEffect(() => {
26
- setText("");
27
- setSummary(null);
28
- reset();
29
- }, [data, reset]);
30
- if (devError) {
31
- throw devError;
32
- }
33
- const submitted = state === "completed";
34
- const inactive = disabled === true || submitted;
35
- const wordCount = countWords(text);
36
- const withinBounds = wordCount >= data.minWords && wordCount <= data.maxWords;
37
- const handleChange = (value) => {
38
- if (inactive) {
39
- return;
40
- }
41
- if (state === "idle") {
42
- start();
43
- }
44
- setText(value);
45
- onInteraction?.({
46
- type: "text-changed",
47
- activityId: data.id,
48
- timestamp: Date.now(),
49
- payload: { wordCount: countWords(value) }
50
- });
51
- };
52
- const handleSubmit = (event) => {
53
- event.preventDefault();
54
- if (inactive || text.trim().length === 0) {
55
- return;
56
- }
57
- const outcome = evaluate(data, { type: "written-response", text, wordCount });
58
- complete();
59
- const timeSpent = getTimeSpent();
60
- const partial = outcome.status === "deferred" ? outcome.partial : void 0;
61
- const finalWordCount = typeof partial?.wordCount === "number" ? partial.wordCount : wordCount;
62
- const finalWithinBounds = typeof partial?.withinWordBounds === "boolean" ? partial.withinWordBounds : withinBounds;
63
- const xapiStatement = xAPIBuilder.buildSubmittedStatement({
64
- actor: ANONYMOUS_ACTOR,
65
- object: {
66
- id: objectIdFor(data.id),
67
- name: { [data.locale ?? "en-US"]: data.title },
68
- type: "http://adlnet.gov/expapi/activities/cmi.interaction",
69
- interactionType: "long-fill-in"
70
- },
71
- timeSpentMs: timeSpent,
72
- response: text,
73
- resultExtensions: {
74
- "urn:learning-kit:extension:word-count": finalWordCount,
75
- "urn:learning-kit:extension:within-word-bounds": finalWithinBounds
76
- }
77
- });
78
- onSubmitted({
79
- text,
80
- wordCount: finalWordCount,
81
- withinWordBounds: finalWithinBounds,
82
- timeSpent,
83
- xapiStatement
84
- });
85
- setSummary("Response submitted. It will be graded and your result will appear here later.");
86
- onInteraction?.({
87
- type: "submitted",
88
- activityId: data.id,
89
- timestamp: Date.now(),
90
- payload: { wordCount: finalWordCount, withinWordBounds: finalWithinBounds }
91
- });
92
- };
93
- const promptId = `${data.id}-prompt`;
94
- const counterId = `${data.id}-counter`;
95
- const boundsLabel = data.minWords > 0 ? `${data.minWords}\u2013${data.maxWords} words` : `up to ${data.maxWords} words`;
96
- return /* @__PURE__ */ jsxs(
97
- "form",
98
- {
99
- className: "lk-wr",
100
- "aria-label": data.title,
101
- lang: locale,
102
- style: theme,
103
- onSubmit: handleSubmit,
104
- children: [
105
- data.media ? /* @__PURE__ */ jsx(ActivityMedia, { media: data.media }) : null,
106
- /* @__PURE__ */ jsx("p", { className: "lk-wr-prompt", id: promptId, children: data.prompt }),
107
- /* @__PURE__ */ jsx(
108
- "textarea",
109
- {
110
- className: "lk-wr-textarea",
111
- "aria-labelledby": promptId,
112
- "aria-describedby": counterId,
113
- value: text,
114
- onChange: (event) => handleChange(event.target.value),
115
- disabled: inactive,
116
- rows: 8
117
- }
118
- ),
119
- /* @__PURE__ */ jsxs(
120
- "div",
121
- {
122
- className: "lk-wr-counter",
123
- id: counterId,
124
- "aria-live": "polite",
125
- "data-within-bounds": withinBounds,
126
- children: [
127
- wordCount,
128
- " ",
129
- wordCount === 1 ? "word" : "words",
130
- " (",
131
- boundsLabel,
132
- ")"
133
- ]
134
- }
135
- ),
136
- /* @__PURE__ */ jsx(
137
- "button",
138
- {
139
- type: "submit",
140
- className: "lk-wr-submit",
141
- disabled: inactive || text.trim().length === 0,
142
- children: "Submit"
143
- }
144
- ),
145
- /* @__PURE__ */ jsx(FeedbackRegion, { id: `${data.id}-feedback`, children: summary })
146
- ]
147
- }
148
- );
149
- }
150
- function WrittenResponse2(props) {
151
- return /* @__PURE__ */ jsx(ActivityErrorBoundary, { activityTitle: props.data?.title, children: /* @__PURE__ */ jsx(WrittenResponse, { ...props }) });
152
- }
153
-
154
- export { WrittenResponse2 as WrittenResponse };
155
- //# sourceMappingURL=chunk-V5EN3ESV.js.map
156
- //# sourceMappingURL=chunk-V5EN3ESV.js.map