@intellectif/lk-react 2.1.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/dist/WrittenResponse-Dkg7FUt-.d.ts +87 -0
- package/dist/WrittenResponse-DqQ0NjPK.d.cts +87 -0
- package/dist/{chunk-3X7MYAFT.js → chunk-3DNJDP73.js} +11 -9
- package/dist/chunk-3DNJDP73.js.map +1 -0
- package/dist/chunk-4HZ7LQIH.cjs +40 -0
- package/dist/chunk-4HZ7LQIH.cjs.map +1 -0
- package/dist/chunk-525EWDUT.cjs +249 -0
- package/dist/chunk-525EWDUT.cjs.map +1 -0
- package/dist/{chunk-ZVFWJTT4.js → chunk-5SYL5CJ2.js} +10 -28
- package/dist/chunk-5SYL5CJ2.js.map +1 -0
- package/dist/{chunk-UXO6KYSZ.cjs → chunk-67RIIDZX.cjs} +17 -15
- package/dist/chunk-67RIIDZX.cjs.map +1 -0
- package/dist/chunk-6F4X2NKL.js +54 -0
- package/dist/chunk-6F4X2NKL.js.map +1 -0
- package/dist/chunk-GB5URWM4.js +38 -0
- package/dist/chunk-GB5URWM4.js.map +1 -0
- package/dist/chunk-GMU77L52.js +247 -0
- package/dist/chunk-GMU77L52.js.map +1 -0
- package/dist/chunk-NEWGUDA5.cjs +49 -0
- package/dist/chunk-NEWGUDA5.cjs.map +1 -0
- package/dist/{chunk-QHOV2A6M.cjs → chunk-PGEG7UZJ.cjs} +2 -34
- package/dist/chunk-PGEG7UZJ.cjs.map +1 -0
- package/dist/{chunk-XTJ22JAP.js → chunk-SWZRB3VY.js} +48 -14
- package/dist/chunk-SWZRB3VY.js.map +1 -0
- package/dist/chunk-UQ3BBEIT.js +47 -0
- package/dist/chunk-UQ3BBEIT.js.map +1 -0
- package/dist/{chunk-W6BH2YMZ.cjs → chunk-WCVD5LVG.cjs} +16 -34
- package/dist/chunk-WCVD5LVG.cjs.map +1 -0
- package/dist/{chunk-7BT6OXAR.cjs → chunk-XQJFLAMD.cjs} +53 -19
- package/dist/chunk-XQJFLAMD.cjs.map +1 -0
- package/dist/components/ActivitySequence.cjs +8 -5
- package/dist/components/ActivitySequence.d.cts +134 -8
- package/dist/components/ActivitySequence.d.ts +134 -8
- package/dist/components/ActivitySequence.js +7 -4
- package/dist/components/FillInTheBlanks.cjs +4 -3
- package/dist/components/FillInTheBlanks.d.cts +1 -1
- package/dist/components/FillInTheBlanks.d.ts +1 -1
- package/dist/components/FillInTheBlanks.js +3 -2
- package/dist/components/MultipleChoice.cjs +4 -3
- package/dist/components/MultipleChoice.d.cts +1 -1
- package/dist/components/MultipleChoice.d.ts +1 -1
- package/dist/components/MultipleChoice.js +3 -2
- package/dist/components/StimulusPanel.cjs +13 -0
- package/dist/components/StimulusPanel.cjs.map +1 -0
- package/dist/components/StimulusPanel.d.cts +30 -0
- package/dist/components/StimulusPanel.d.ts +30 -0
- package/dist/components/StimulusPanel.js +4 -0
- package/dist/components/StimulusPanel.js.map +1 -0
- package/dist/components/WrittenResponse.cjs +4 -3
- package/dist/components/WrittenResponse.d.cts +5 -86
- package/dist/components/WrittenResponse.d.ts +5 -86
- package/dist/components/WrittenResponse.js +3 -2
- package/dist/index.cjs +36 -26
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -4
- package/dist/index.d.ts +6 -4
- package/dist/index.js +12 -7
- package/dist/index.js.map +1 -1
- package/dist/theme/skin.css +124 -0
- package/dist/{types-CNmjqD6M.d.cts → types-CrURyd9-.d.cts} +51 -3
- package/dist/{types-CNmjqD6M.d.ts → types-CrURyd9-.d.ts} +51 -3
- package/package.json +81 -28
- package/dist/chunk-3X7MYAFT.js.map +0 -1
- package/dist/chunk-7BT6OXAR.cjs.map +0 -1
- package/dist/chunk-QHOV2A6M.cjs.map +0 -1
- package/dist/chunk-STLRKR2I.cjs +0 -111
- package/dist/chunk-STLRKR2I.cjs.map +0 -1
- package/dist/chunk-UASXUWHH.js +0 -109
- package/dist/chunk-UASXUWHH.js.map +0 -1
- package/dist/chunk-UIGIYYGY.js +0 -85
- package/dist/chunk-UIGIYYGY.js.map +0 -1
- package/dist/chunk-UXO6KYSZ.cjs.map +0 -1
- package/dist/chunk-W6BH2YMZ.cjs.map +0 -1
- package/dist/chunk-XTJ22JAP.js.map +0 -1
- package/dist/chunk-ZVFWJTT4.js.map +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@intellectif/lk-react",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0",
|
|
4
4
|
"description": "React 19 activity components, hooks, and theming for learning-kit",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -13,44 +13,94 @@
|
|
|
13
13
|
],
|
|
14
14
|
"exports": {
|
|
15
15
|
".": {
|
|
16
|
-
"
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
"import": {
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"default": "./dist/index.js"
|
|
19
|
+
},
|
|
20
|
+
"require": {
|
|
21
|
+
"types": "./dist/index.d.cts",
|
|
22
|
+
"default": "./dist/index.cjs"
|
|
23
|
+
}
|
|
19
24
|
},
|
|
20
25
|
"./components/ActivitySequence": {
|
|
21
|
-
"
|
|
22
|
-
|
|
23
|
-
|
|
26
|
+
"import": {
|
|
27
|
+
"types": "./dist/components/ActivitySequence.d.ts",
|
|
28
|
+
"default": "./dist/components/ActivitySequence.js"
|
|
29
|
+
},
|
|
30
|
+
"require": {
|
|
31
|
+
"types": "./dist/components/ActivitySequence.d.cts",
|
|
32
|
+
"default": "./dist/components/ActivitySequence.cjs"
|
|
33
|
+
}
|
|
24
34
|
},
|
|
25
35
|
"./components/MultipleChoice": {
|
|
26
|
-
"
|
|
27
|
-
|
|
28
|
-
|
|
36
|
+
"import": {
|
|
37
|
+
"types": "./dist/components/MultipleChoice.d.ts",
|
|
38
|
+
"default": "./dist/components/MultipleChoice.js"
|
|
39
|
+
},
|
|
40
|
+
"require": {
|
|
41
|
+
"types": "./dist/components/MultipleChoice.d.cts",
|
|
42
|
+
"default": "./dist/components/MultipleChoice.cjs"
|
|
43
|
+
}
|
|
29
44
|
},
|
|
30
45
|
"./components/FillInTheBlanks": {
|
|
31
|
-
"
|
|
32
|
-
|
|
33
|
-
|
|
46
|
+
"import": {
|
|
47
|
+
"types": "./dist/components/FillInTheBlanks.d.ts",
|
|
48
|
+
"default": "./dist/components/FillInTheBlanks.js"
|
|
49
|
+
},
|
|
50
|
+
"require": {
|
|
51
|
+
"types": "./dist/components/FillInTheBlanks.d.cts",
|
|
52
|
+
"default": "./dist/components/FillInTheBlanks.cjs"
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
"./components/StimulusPanel": {
|
|
56
|
+
"import": {
|
|
57
|
+
"types": "./dist/components/StimulusPanel.d.ts",
|
|
58
|
+
"default": "./dist/components/StimulusPanel.js"
|
|
59
|
+
},
|
|
60
|
+
"require": {
|
|
61
|
+
"types": "./dist/components/StimulusPanel.d.cts",
|
|
62
|
+
"default": "./dist/components/StimulusPanel.cjs"
|
|
63
|
+
}
|
|
34
64
|
},
|
|
35
65
|
"./components/WrittenResponse": {
|
|
36
|
-
"
|
|
37
|
-
|
|
38
|
-
|
|
66
|
+
"import": {
|
|
67
|
+
"types": "./dist/components/WrittenResponse.d.ts",
|
|
68
|
+
"default": "./dist/components/WrittenResponse.js"
|
|
69
|
+
},
|
|
70
|
+
"require": {
|
|
71
|
+
"types": "./dist/components/WrittenResponse.d.cts",
|
|
72
|
+
"default": "./dist/components/WrittenResponse.cjs"
|
|
73
|
+
}
|
|
39
74
|
},
|
|
40
75
|
"./hooks/useActivityState": {
|
|
41
|
-
"
|
|
42
|
-
|
|
43
|
-
|
|
76
|
+
"import": {
|
|
77
|
+
"types": "./dist/hooks/useActivityState.d.ts",
|
|
78
|
+
"default": "./dist/hooks/useActivityState.js"
|
|
79
|
+
},
|
|
80
|
+
"require": {
|
|
81
|
+
"types": "./dist/hooks/useActivityState.d.cts",
|
|
82
|
+
"default": "./dist/hooks/useActivityState.cjs"
|
|
83
|
+
}
|
|
44
84
|
},
|
|
45
85
|
"./hooks/useXAPI": {
|
|
46
|
-
"
|
|
47
|
-
|
|
48
|
-
|
|
86
|
+
"import": {
|
|
87
|
+
"types": "./dist/hooks/useXAPI.d.ts",
|
|
88
|
+
"default": "./dist/hooks/useXAPI.js"
|
|
89
|
+
},
|
|
90
|
+
"require": {
|
|
91
|
+
"types": "./dist/hooks/useXAPI.d.cts",
|
|
92
|
+
"default": "./dist/hooks/useXAPI.cjs"
|
|
93
|
+
}
|
|
49
94
|
},
|
|
50
95
|
"./theme/ThemeProvider": {
|
|
51
|
-
"
|
|
52
|
-
|
|
53
|
-
|
|
96
|
+
"import": {
|
|
97
|
+
"types": "./dist/theme/ThemeProvider.d.ts",
|
|
98
|
+
"default": "./dist/theme/ThemeProvider.js"
|
|
99
|
+
},
|
|
100
|
+
"require": {
|
|
101
|
+
"types": "./dist/theme/ThemeProvider.d.cts",
|
|
102
|
+
"default": "./dist/theme/ThemeProvider.cjs"
|
|
103
|
+
}
|
|
54
104
|
},
|
|
55
105
|
"./theme/defaults.css": "./dist/theme/defaults.css",
|
|
56
106
|
"./theme/skin.css": "./dist/theme/skin.css"
|
|
@@ -65,7 +115,7 @@
|
|
|
65
115
|
"peerDependencies": {
|
|
66
116
|
"react": "^19",
|
|
67
117
|
"react-dom": "^19",
|
|
68
|
-
"@intellectif/lk-core": "^0.
|
|
118
|
+
"@intellectif/lk-core": "^0.5.0"
|
|
69
119
|
},
|
|
70
120
|
"devDependencies": {
|
|
71
121
|
"@axe-core/react": "^4.10.0",
|
|
@@ -84,7 +134,7 @@
|
|
|
84
134
|
"tsup": "^8.3.0",
|
|
85
135
|
"vitest": "^4.1.6",
|
|
86
136
|
"vitest-axe": "^0.1.0",
|
|
87
|
-
"@intellectif/lk-core": "0.
|
|
137
|
+
"@intellectif/lk-core": "0.5.0"
|
|
88
138
|
},
|
|
89
139
|
"license": "MIT",
|
|
90
140
|
"engines": {
|
|
@@ -95,6 +145,9 @@
|
|
|
95
145
|
"test": "vitest run",
|
|
96
146
|
"coverage": "vitest run --coverage",
|
|
97
147
|
"lint": "biome check .",
|
|
98
|
-
"e2e": "playwright test -c ../../playwright.config.ts"
|
|
148
|
+
"e2e": "playwright test -c ../../playwright.config.ts",
|
|
149
|
+
"publint": "publint",
|
|
150
|
+
"attw": "attw --pack . --ignore-rules no-resolution --exclude-entrypoints \"theme/defaults.css\" \"theme/skin.css\"",
|
|
151
|
+
"typecheck": "tsc -p tsconfig.typecheck.json"
|
|
99
152
|
}
|
|
100
153
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/FillInTheBlanks/FillInTheBlanks.tsx","../src/components/FillInTheBlanks/index.tsx"],"names":["result","FillInTheBlanks","jsx"],"mappings":";;;;;;AAoCA,IAAM,WAAA,GAAc,0BAAA;AAIpB,SAAS,aAAa,OAAA,EAA4B;AAChD,EAAA,MAAM,WAAsB,EAAC;AAC7B,EAAA,MAAM,EAAA,GAAK,IAAI,MAAA,CAAO,WAAW,CAAA;AACjC,EAAA,IAAI,IAAA,GAAO,CAAA;AACX,EAAA,IAAI,OAAA,GAAU,CAAA;AACd,EAAA,IAAI,KAAA,GAAQ,EAAA,CAAG,IAAA,CAAK,OAAO,CAAA;AAC3B,EAAA,OAAO,UAAU,IAAA,EAAM;AACrB,IAAA,IAAI,KAAA,CAAM,QAAQ,IAAA,EAAM;AACtB,MAAA,QAAA,CAAS,IAAA,CAAK,EAAE,IAAA,EAAM,MAAA,EAAQ,KAAA,EAAO,OAAA,CAAQ,KAAA,CAAM,IAAA,EAAM,KAAA,CAAM,KAAK,CAAA,EAAG,CAAA;AAAA,IACzE;AACA,IAAA,OAAA,IAAW,CAAA;AACX,IAAA,QAAA,CAAS,IAAA,CAAK,EAAE,IAAA,EAAM,OAAA,EAAS,IAAI,KAAA,CAAM,CAAC,CAAA,EAAa,OAAA,EAAS,CAAA;AAChE,IAAA,IAAA,GAAO,KAAA,CAAM,KAAA,GAAQ,KAAA,CAAM,CAAC,CAAA,CAAE,MAAA;AAC9B,IAAA,KAAA,GAAQ,EAAA,CAAG,KAAK,OAAO,CAAA;AAAA,EACzB;AACA,EAAA,IAAI,IAAA,GAAO,QAAQ,MAAA,EAAQ;AACzB,IAAA,QAAA,CAAS,IAAA,CAAK,EAAE,IAAA,EAAM,MAAA,EAAQ,OAAO,OAAA,CAAQ,KAAA,CAAM,IAAI,CAAA,EAAG,CAAA;AAAA,EAC5D;AACA,EAAA,OAAO,QAAA;AACT;AAGA,IAAM,aAAqC,EAAC;AAQ5C,SAAS,UAAU,QAAA,EAA+D;AAChF,EAAA,OAAO,aAAa,MAAA,IAAa,QAAA,CAAS,IAAA,KAAS,oBAAA,GAC/C,SAAS,OAAA,GACT,UAAA;AACN;AAQA,SAAS,kBAAkB,OAAA,EAAyD;AAClF,EAAA,MAAM,GAAA,uBAAU,GAAA,EAAqB;AACrC,EAAA,KAAA,MAAW,UAAU,OAAA,EAAS;AAC5B,IAAA,GAAA,CAAI,GAAA;AAAA,MACF,MAAA,CAAO,MAAA;AAAA,MACP,OAAO,OAAA,KAAY,MAAA,GAAY,MAAA,CAAO,OAAA,KAAY,YAAY,MAAA,CAAO;AAAA,KACvE;AAAA,EACF;AACA,EAAA,OAAO,GAAA;AACT;AAEO,SAAS,eAAA,CAAgB;AAAA,EAC9B,IAAA;AAAA,EACA,UAAA;AAAA,EACA,QAAA;AAAA,EACA,QAAA;AAAA,EACA,aAAA;AAAA,EACA,KAAA;AAAA,EACA,YAAA;AAAA,EACA,UAAA,GAAa,UAAA;AAAA,EACb,OAAA;AAAA,EACA,YAAA;AAAA,EACA,KAAA;AAAA,EACA,MAAA;AAAA,EACA,QAAA;AAAA,EACA;AACF,CAAA,EAAyB;AACvB,EAAA,MAAM,SAAS,UAAA,KAAe,MAAA;AAC9B,EAAA,MAAM,WAAW,UAAA,KAAe,QAAA;AAIhC,EAAA,MAAM,QAAA,GAAW,QAAQ,MAAM;AAC7B,IAAA,IAAI,CAAC,eAAc,EAAG;AACpB,MAAA,OAAO,IAAA;AAAA,IACT;AACA,IAAA,IAAI,IAAA,CAAK,aAAa,IAAA,EAAM;AAI1B,MAAA,IAAI;AACF,QAAA,cAAA,CAAe,IAAI,CAAA;AACnB,QAAA,OAAO,IAAA;AAAA,MACT,SAAS,KAAA,EAAO;AACd,QAAA,OAAO,iBAAiB,KAAA,GAAQ,KAAA,GAAQ,IAAI,KAAA,CAAM,MAAA,CAAO,KAAK,CAAC,CAAA;AAAA,MACjE;AAAA,IACF;AACA,IAAA,MAAMA,OAAAA,GAAS,gBAAA,CAAiB,oBAAA,EAAsB,IAAI,CAAA;AAC1D,IAAA,OAAOA,QAAO,OAAA,GAAU,IAAA,GAAO,IAAI,mBAAA,CAAoB,oBAAA,EAAsBA,QAAO,MAAM,CAAA;AAAA,EAC5F,CAAA,EAAG,CAAC,IAAI,CAAC,CAAA;AAET,EAAA,MAAM,EAAE,KAAA,EAAO,KAAA,EAAO,UAAU,YAAA,EAAc,KAAA,KAAU,gBAAA,EAAiB;AAIzE,EAAA,MAAM,eAAe,KAAA,KAAU,MAAA;AAC/B,EAAA,MAAM,CAAC,eAAA,EAAiB,kBAAkB,CAAA,GAAI,QAAA;AAAA,IAAiC,MAC7E,UAAU,YAAY;AAAA,GACxB;AACA,EAAA,MAAM,CAAC,UAAU,WAAW,CAAA,GAAI,SAAsB,sBAAM,IAAI,KAAK,CAAA;AACrE,EAAA,MAAM,CAAC,MAAA,EAAQ,SAAS,CAAA,GAAI,SAA+B,IAAI,CAAA;AAC/D,EAAA,MAAM,CAAC,OAAA,EAAS,UAAU,CAAA,GAAI,SAAwB,IAAI,CAAA;AAC1D,EAAA,MAAM,CAAC,cAAA,EAAgB,iBAAiB,CAAA,GAAI,SAAS,KAAK,CAAA;AAE1D,EAAA,MAAM,OAAA,GAAU,YAAA,GAAe,SAAA,CAAU,KAAK,CAAA,GAAI,eAAA;AAMlD,EAAA,MAAM,eAAA,GAAkB,OAAO,YAAY,CAAA;AAC3C,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,eAAA,CAAgB,OAAA,GAAU,YAAA;AAAA,EAC5B,CAAA,EAAG,CAAC,YAAY,CAAC,CAAA;AAIjB,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,kBAAA,CAAmB,SAAA,CAAU,eAAA,CAAgB,OAAO,CAAC,CAAA;AACrD,IAAA,WAAA,iBAAY,IAAI,KAAK,CAAA;AACrB,IAAA,SAAA,CAAU,IAAI,CAAA;AACd,IAAA,UAAA,CAAW,IAAI,CAAA;AACf,IAAA,iBAAA,CAAkB,KAAK,CAAA;AACvB,IAAA,KAAA,EAAM;AAAA,EACR,CAAA,EAAG,CAAC,IAAA,EAAM,KAAK,CAAC,CAAA;AAiChB,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,eAAc,IAAK,YAAA,KAAiB,MAAA,IAAa,IAAA,CAAK,gBAAgB,MAAA,EAAW;AACnF,MAAA,OAAA,CAAQ,IAAA;AAAA,QACN;AAAA,OAGF;AAAA,IACF;AAAA,EACF,CAAA,EAAG,CAAC,YAAA,EAAc,IAAA,CAAK,WAAW,CAAC,CAAA;AAEnC,EAAA,MAAM,QAAA,GAAW,OAAA,CAAQ,MAAM,YAAA,CAAa,IAAA,CAAK,OAAO,CAAA,EAAG,CAAC,IAAA,CAAK,OAAO,CAAC,CAAA;AACzE,EAAA,MAAM,SAAA,GAAY,QAAQ,MAAM,IAAI,IAAI,IAAA,CAAK,MAAA,CAAO,IAAI,CAAC,CAAA,KAAM,CAAC,CAAA,CAAE,EAAA,EAAI,CAAC,CAAC,CAAC,GAAG,CAAC,IAAA,CAAK,MAAM,CAAC,CAAA;AAEzF,EAAA,IAAI,QAAA,EAAU;AACZ,IAAA,MAAM,QAAA;AAAA,EACR;AAEA,EAAA,MAAM,YAAY,KAAA,KAAU,WAAA;AAC5B,EAAA,MAAM,QAAA,GAAW,QAAA,KAAa,IAAA,IAAQ,SAAA,IAAa,QAAA;AASnD,EAAA,MAAM,YAAY,QAAA,GAAW,OAAA,EAAS,MAAA,KAAW,QAAA,GAAW,CAAC,MAAA,IAAU,SAAA;AAEvE,EAAA,MAAM,eAAA,GAAkB,CACtB,IAAA,EACA,OAAA,KACS;AACT,IAAA,aAAA,GAAgB,EAAE,IAAA,EAAM,UAAA,EAAY,IAAA,CAAK,EAAA,EAAI,WAAW,IAAA,CAAK,GAAA,EAAI,EAAG,OAAA,EAAS,CAAA;AAAA,EAC/E,CAAA;AAEA,EAAA,MAAM,YAAA,GAAe,CAAC,OAAA,EAAiB,IAAA,KAAuB;AAC5D,IAAA,IAAI,QAAA,EAAU;AACZ,MAAA;AAAA,IACF;AACA,IAAA,IAAI,UAAU,MAAA,EAAQ;AACpB,MAAA,KAAA,EAAM;AAAA,IACR;AACA,IAAA,MAAM,OAAO,EAAE,GAAG,SAAS,CAAC,OAAO,GAAG,IAAA,EAAK;AAC3C,IAAA,IAAI,CAAC,YAAA,EAAc;AACjB,MAAA,kBAAA,CAAmB,IAAI,CAAA;AAAA,IACzB;AACA,IAAA,QAAA,GAAW,EAAE,IAAA,EAAM,oBAAA,EAAsB,OAAA,EAAS,MAAM,CAAA;AACxD,IAAA,eAAA,CAAgB,cAAA,EAAgB,EAAE,OAAA,EAAS,KAAA,EAAO,MAAM,CAAA;AAAA,EAC1D,CAAA;AAEA,EAAA,MAAM,UAAA,GAAa,CAAC,OAAA,KAA0B;AAC5C,IAAA,IAAI,QAAA,EAAU;AACZ,MAAA;AAAA,IACF;AACA,IAAA,IAAI,UAAU,MAAA,EAAQ;AACpB,MAAA,KAAA,EAAM;AAAA,IACR;AACA,IAAA,WAAA,CAAY,CAAC,IAAA,KAAS;AACpB,MAAA,MAAM,IAAA,GAAO,IAAI,GAAA,CAAI,IAAI,CAAA;AACzB,MAAA,IAAI,IAAA,CAAK,GAAA,CAAI,OAAO,CAAA,EAAG;AACrB,QAAA,IAAA,CAAK,OAAO,OAAO,CAAA;AACnB,QAAA,OAAO,IAAA;AAAA,MACT;AACA,MAAA,IAAA,CAAK,IAAI,OAAO,CAAA;AAChB,MAAA,OAAO,IAAA;AAAA,IACT,CAAC,CAAA;AAED,IAAA,IAAI,CAAC,QAAA,CAAS,GAAA,CAAI,OAAO,CAAA,EAAG;AAC1B,MAAA,eAAA,CAAgB,gBAAA,EAAkB,EAAE,OAAA,EAAS,CAAA;AAAA,IAC/C;AAAA,EACF,CAAA;AAEA,EAAA,MAAM,YAAA,GAAe,CAAC,KAAA,KAAiC;AACrD,IAAA,KAAA,CAAM,cAAA,EAAe;AACrB,IAAA,IAAI,QAAA,EAAU;AACZ,MAAA;AAAA,IACF;AACA,IAAA,MAAM,QAAA,GAA2C,EAAE,IAAA,EAAM,oBAAA,EAAsB,OAAA,EAAQ;AAGvF,IAAA,QAAA,GAAW,QAAQ,CAAA;AAEnB,IAAA,IAAI,MAAA,EAAQ;AAKV,MAAA,QAAA,EAAS;AACT,MAAA,UAAA,CAAW,mBAAmB,CAAA;AAC9B,MAAA,eAAA,CAAgB,WAAA,EAAa,EAAE,OAAA,EAAS,CAAA;AACxC,MAAA;AAAA,IACF;AAIA,IAAA,MAAM,aAAA,GAAgB,KAAA,CAAM,oBAAA,EAAsB,IAAA,EAA6B,QAAQ,CAAA;AACvF,IAAA,QAAA,EAAS;AACT,IAAA,MAAM,YAAY,YAAA,EAAa;AAC/B,IAAA,MAAM,aAAA,GAAgB,YAAY,sBAAA,CAAuB;AAAA,MACvD,KAAA,EAAO,eAAA;AAAA,MACP,MAAA,EAAQ;AAAA,QACN,EAAA,EAAI,WAAA,CAAY,IAAA,CAAK,EAAE,CAAA;AAAA,QACvB,IAAA,EAAM,EAAE,CAAC,IAAA,CAAK,UAAU,OAAO,GAAG,KAAK,KAAA,EAAM;AAAA,QAC7C,IAAA,EAAM,qDAAA;AAAA,QACN,eAAA,EAAiB,SAAA;AAAA,QACjB,uBAAA,EAAyB;AAAA,UACvB,IAAA,CAAK,MAAA,CAAO,GAAA,CAAI,CAAC,KAAA,KAAU,KAAA,CAAM,eAAA,CAAgB,CAAC,CAAA,IAAK,EAAE,CAAA,CAAE,IAAA,CAAK,KAAK;AAAA;AACvE,OACF;AAAA,MACA,aAAA;AAAA,MACA,WAAA,EAAa,SAAA;AAAA,MACb,QAAA,EAAU,IAAA,CAAK,SAAA,CAAU,OAAO;AAAA,KACjC,CAAA;AACD,IAAA,SAAA,CAAU,aAAa,CAAA;AACvB,IAAA,UAAA,GAAa;AAAA,MACX,OAAO,aAAA,CAAc,KAAA;AAAA,MACrB,UAAU,aAAA,CAAc,QAAA;AAAA,MACxB,QAAQ,aAAA,CAAc,MAAA;AAAA,MACtB,SAAA;AAAA,MACA;AAAA,KACD,CAAA;AAED,IAAA,MAAM,UAAU,aAAA,CAAc,QAAA;AAC9B,IAAA,UAAA;AAAA,MACE,2BAA2B,IAAA,CAAK,KAAA,CAAM,aAAA,CAAc,KAAA,GAAQ,GAAG,CAAC,CAAA,GAAA,EAC9D,aAAA,CAAc,MAAA,GAAS,YAAY,aACrC,CAAA,EAAG,UAAU,CAAA,CAAA,EAAI,OAAO,KAAK,EAAE,CAAA;AAAA,KACjC;AACA,IAAA,eAAA,CAAgB,aAAa,EAAE,OAAA,EAAS,KAAA,EAAO,aAAA,CAAc,OAAO,CAAA;AAAA,EACtE,CAAA;AAEA,EAAA,MAAM,cAAA,GAAiB,QAAQ,MAAM;AACnC,IAAA,IAAI,MAAA,EAAQ;AAEV,MAAA,2BAAW,GAAA,EAAqB;AAAA,IAClC;AACA,IAAA,IAAI,QAAA,EAAU;AAGZ,MAAA,OAAO,OAAA,EAAS,WAAW,QAAA,GACvB,iBAAA,CAAkB,QAAQ,OAAO,CAAA,uBAC7B,GAAA,EAAqB;AAAA,IAC/B;AACA,IAAA,OAAO,iBAAA,CAAkB,MAAA,EAAQ,OAAA,IAAW,EAAE,CAAA;AAAA,EAChD,GAAG,CAAC,MAAA,EAAQ,QAAA,EAAU,OAAA,EAAS,MAAM,CAAC,CAAA;AAGtC,EAAA,MAAM,aAAA,GAAgB,QAAQ,MAAM;AAClC,IAAA,IAAI,CAAC,QAAA,IAAY,OAAA,KAAY,MAAA,EAAW;AACtC,MAAA,OAAO,IAAA;AAAA,IACT;AACA,IAAA,IAAI,OAAA,CAAQ,WAAW,QAAA,EAAU;AAC/B,MAAA,OAAO,SAAS,IAAA,CAAK,KAAA,CAAM,QAAQ,KAAA,GAAQ,GAAG,CAAC,CAAA,GAAA,EAC7C,OAAA,CAAQ,SAAS,SAAA,GAAY,aAC/B,GAAG,OAAA,CAAQ,QAAA,GAAW,IAAI,OAAA,CAAQ,QAAQ,KAAK,EAAE,CAAA,CAAA;AAAA,IACnD;AACA,IAAA,OAAO,OAAA,CAAQ,MAAA,KAAW,UAAA,GAAa,iBAAA,GAAoB,qBAAA;AAAA,EAC7D,CAAA,EAAG,CAAC,QAAA,EAAU,OAAO,CAAC,CAAA;AAEtB,EAAA,uBACE,IAAA;AAAA,IAAC,MAAA;AAAA,IAAA;AAAA,MACC,SAAA,EAAU,QAAA;AAAA,MACV,cAAY,IAAA,CAAK,KAAA;AAAA,MACjB,IAAA,EAAM,MAAA;AAAA,MACN,kBAAA,EAAkB,UAAA;AAAA,MAClB,KAAA,EAAO,KAAA;AAAA,MACP,QAAA,EAAU,YAAA;AAAA,MAET,QAAA,EAAA;AAAA,QAAA,IAAA,CAAK,wBAAQ,GAAA,CAAC,aAAA,EAAA,EAAc,KAAA,EAAO,IAAA,CAAK,OAAO,CAAA,GAAK,IAAA;AAAA,wBACrD,IAAA,CAAC,UAAA,EAAA,EAAS,QAAA,EAAU,QAAA,EAClB,QAAA,EAAA;AAAA,0BAAA,GAAA,CAAC,OAAE,SAAA,EAAU,gBAAA,EACV,mBAAS,GAAA,CAAI,CAAC,KAAK,CAAA,KAAM;AACxB,YAAA,IAAI,GAAA,CAAI,SAAS,MAAA,EAAQ;AAEvB,cAAA,2BAAQ,MAAA,EAAA,EAAoB,QAAA,EAAA,GAAA,CAAI,KAAA,EAAA,EAAd,CAAA,CAAA,EAAI,CAAC,CAAA,CAAe,CAAA;AAAA,YACxC;AACA,YAAA,MAAM,KAAA,GAAQ,SAAA,CAAU,GAAA,CAAI,GAAA,CAAI,EAAE,CAAA;AAClC,YAAA,IAAI,CAAC,KAAA,EAAO;AACV,cAAA,OAAO,IAAA;AAAA,YACT;AAKA,YAAA,MAAM,eAAA,GAAwC,MAAA,GAC1C,MAAA,GACA,KAAA,CAAM,eAAA;AACV,YAAA,MAAM,aAAA,GAAgB,MAAA,GAAS,MAAA,GAAY,KAAA,CAAM,QAAA;AACjD,YAAA,MAAM,WAAA,GAAc,cAAA,CAAe,GAAA,CAAI,GAAA,CAAI,EAAE,CAAA;AAC7C,YAAA,MAAM,SAAS,CAAA,EAAG,IAAA,CAAK,EAAE,CAAA,MAAA,EAAS,IAAI,EAAE,CAAA,CAAA;AACxC,YAAA,MAAM,aAAA,GAAgB,kBAAkB,CAAC,CAAA;AACzC,YAAA,IAAI,SAAA,IAAa,kBAAA,IAAsB,WAAA,KAAgB,KAAA,IAAS,aAAA,EAAe;AAC7E,cAAA,uBACE,GAAA,CAAC,UAAkB,SAAA,EAAU,eAAA,EAAgB,gBAAa,OAAA,EACvD,QAAA,EAAA,aAAA,EAAA,EADQ,IAAI,EAEf,CAAA;AAAA,YAEJ;AACA,YAAA,uBACE,IAAA,CAAC,MAAA,EAAA,EAAkB,SAAA,EAAU,cAAA,EAC3B,QAAA,EAAA;AAAA,8BAAA,GAAA;AAAA,gBAAC,OAAA;AAAA,gBAAA;AAAA,kBACC,IAAA,EAAK,MAAA;AAAA,kBACL,YAAA,EAAY,CAAA,cAAA,EAAiB,GAAA,CAAI,OAAO,CAAA,CAAA;AAAA,kBACxC,kBAAA,EAAkB,KAAA,CAAM,IAAA,GAAO,MAAA,GAAS,MAAA;AAAA,kBACxC,KAAA,EAAO,OAAA,CAAQ,GAAA,CAAI,EAAE,CAAA,IAAK,EAAA;AAAA,kBAC1B,QAAA,EAAU,QAAA;AAAA,kBACV,iBAAe,QAAA,IAAY,MAAA;AAAA,kBAC3B,gBACE,SAAA,IAAa,WAAA,KAAgB,MAAA,GAAY,MAAA,CAAO,WAAW,CAAA,GAAI,MAAA;AAAA,kBAEjE,QAAA,EAAU,CAAC,CAAA,KAAM,YAAA,CAAa,IAAI,EAAA,EAAI,CAAA,CAAE,OAAO,KAAK;AAAA;AAAA,eACtD;AAAA,cAMC,KAAA,CAAM,uBACL,IAAA,CAAA,QAAA,EAAA,EAME,QAAA,EAAA;AAAA,gCAAA,GAAA;AAAA,kBAAC,QAAA;AAAA,kBAAA;AAAA,oBACC,IAAA,EAAK,QAAA;AAAA,oBACL,SAAA,EAAU,iBAAA;AAAA,oBACV,eAAA,EAAe,MAAA;AAAA,oBACf,eAAA,EAAe,QAAA,CAAS,GAAA,CAAI,GAAA,CAAI,EAAE,CAAA;AAAA,oBAClC,cAAY,QAAA,CAAS,GAAA,CAAI,GAAA,CAAI,EAAE,IAAI,WAAA,GAAc,WAAA;AAAA,oBACjD,QAAA,EAAU,QAAA;AAAA,oBACV,OAAA,EAAS,MAAM,UAAA,CAAW,GAAA,CAAI,EAAE,CAAA;AAAA,oBAEhC,QAAA,kBAAA,GAAA;AAAA,sBAAC,KAAA;AAAA,sBAAA;AAAA,wBACC,SAAA,EAAU,kBAAA;AAAA,wBACV,OAAA,EAAQ,WAAA;AAAA,wBACR,KAAA,EAAM,IAAA;AAAA,wBACN,MAAA,EAAO,IAAA;AAAA,wBACP,aAAA,EAAY,MAAA;AAAA,wBACZ,SAAA,EAAU,OAAA;AAAA,wBAEV,QAAA,kBAAA,GAAA;AAAA,0BAAC,MAAA;AAAA,0BAAA;AAAA,4BACC,IAAA,EAAK,cAAA;AAAA,4BACL,CAAA,EAAE;AAAA;AAAA;AACJ;AAAA;AACF;AAAA,iBACF;AAAA,gCASA,GAAA,CAAC,MAAA,EAAA,EAAK,EAAA,EAAI,MAAA,EAAQ,WAAA,EAAU,QAAA,EACzB,QAAA,EAAA,QAAA,CAAS,GAAA,CAAI,GAAA,CAAI,EAAE,CAAA,GAAI,KAAA,CAAM,OAAO,EAAA,EACvC;AAAA,eAAA,EACF,CAAA,GACE,IAAA;AAAA,cACH,SAAA,IAAa,CAAC,cAAA,IAAkB,aAAA,mBAC/B,GAAA;AAAA,gBAAC,MAAA;AAAA,gBAAA;AAAA,kBACC,SAAA,EAAU,uBAAA;AAAA,kBACV,IAAA,EAAK,MAAA;AAAA,kBACL,cAAA,EAAc,WAAA,KAAgB,MAAA,GAAY,MAAA,CAAO,WAAW,CAAA,GAAI,MAAA;AAAA,kBAE/D,QAAA,EAAA;AAAA;AAAA,eACH,GACE;AAAA,aAAA,EAAA,EArEK,IAAI,EAsEf,CAAA;AAAA,UAEJ,CAAC,CAAA,EACH,CAAA;AAAA,UAEC,QAAA,GAAW,IAAA,mBACV,GAAA,CAAC,QAAA,EAAA,EAAO,IAAA,EAAK,UAAS,QAAA,EAAU,QAAA,EAC7B,QAAA,EAAA,MAAA,GAAS,gBAAA,GAAmB,eAAA,EAC/B;AAAA,SAAA,EAEJ,CAAA;AAAA,QACC,SAAA,IAAa,KAAK,MAAA,CAAO,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,QAAQ,CAAA,mBAC9C,GAAA;AAAA,UAAC,QAAA;AAAA,UAAA;AAAA,YACC,IAAA,EAAK,QAAA;AAAA,YACL,SAAA,EAAU,wBAAA;AAAA,YACV,iBAAe,CAAC,cAAA;AAAA,YAChB,SAAS,MAAM,iBAAA,CAAkB,CAAC,CAAA,KAAM,CAAC,CAAC,CAAA;AAAA,YAEzC,2BAAiB,eAAA,GAAkB;AAAA;AAAA,SACtC,GACE,IAAA;AAAA,wBACJ,GAAA,CAAC,kBAAe,EAAA,EAAI,CAAA,EAAG,KAAK,EAAE,CAAA,SAAA,CAAA,EAC3B,QAAA,EAAA,QAAA,GAAW,aAAA,GAAgB,OAAA,EAC9B;AAAA;AAAA;AAAA,GACF;AAEJ;ACheO,SAASC,iBAAgB,KAAA,EAA6B;AAC3D,EAAA,uBACEC,GAAAA,CAAC,qBAAA,EAAA,EAAsB,aAAA,EAAe,KAAA,CAAM,IAAA,EAAM,KAAA,EAChD,QAAA,kBAAAA,GAAAA,CAAC,eAAA,EAAA,EAAqB,GAAG,KAAA,EAAO,CAAA,EAClC,CAAA;AAEJ","file":"chunk-3X7MYAFT.js","sourcesContent":["'use client';\n\nimport {\n ActivitySchemaError,\n assertRedacted,\n type FillInTheBlanksData,\n type FillInTheBlanksLearnerResponse,\n type LearnerResponse,\n type ScoringDetail,\n type ScoringResult,\n score,\n validateActivity,\n xAPIBuilder,\n} from '@intellectif/lk-core';\nimport { type CSSProperties, useEffect, useMemo, useRef, 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';\nimport type { ActivityProps } from '../types.js';\n\nexport interface FillInTheBlanksProps extends ActivityProps<FillInTheBlanksData> {\n /**\n * After submission, replace each incorrectly-answered blank with its first\n * accepted answer, styled distinctly (Req 5.7). Component-specific prop: it\n * is presentation, not activity data, and is outside the fixed Req-3.1 set.\n *\n * Ignored in `exam` mode — revealing the key is precisely what an exam must\n * not do, and on a `redact()` projection there is no key to reveal. In\n * `review` mode it applies only to blanks the supplied `outcome` marks\n * incorrect, and only when the caller passed data that still carries the key\n * (e.g. `redact(data, { reveal: 'after-submit' })`).\n */\n showCorrectAnswers?: boolean;\n}\n\nconst PLACEHOLDER = /\\{\\{\\s*([^{}]+?)\\s*\\}\\}/g;\n\ntype Segment = { kind: 'text'; value: string } | { kind: 'blank'; id: string; ordinal: number };\n\nfunction parsePassage(passage: string): Segment[] {\n const segments: Segment[] = [];\n const re = new RegExp(PLACEHOLDER);\n let last = 0;\n let ordinal = 0;\n let match = re.exec(passage);\n while (match !== null) {\n if (match.index > last) {\n segments.push({ kind: 'text', value: passage.slice(last, match.index) });\n }\n ordinal += 1;\n segments.push({ kind: 'blank', id: match[1] as string, ordinal });\n last = match.index + match[0].length;\n match = re.exec(passage);\n }\n if (last < passage.length) {\n segments.push({ kind: 'text', value: passage.slice(last) });\n }\n return segments;\n}\n\n/** Shared empty map: keeps the \"no response supplied\" path allocation-free. */\nconst NO_ANSWERS: Record<string, string> = {};\n\n/**\n * Reads the answer map out of a `LearnerResponse`. A response of another\n * activity type is treated as \"no answers\" rather than throwing: a controlled\n * host that has not yet swapped its state on an activity change would\n * otherwise crash the attempt.\n */\nfunction answersOf(response: LearnerResponse | undefined): Record<string, string> {\n return response !== undefined && response.type === 'fill-in-the-blanks'\n ? response.answers\n : NO_ANSWERS;\n}\n\n/**\n * Per-item correctness keyed by blank id. Prefers the unambiguous `outcome`\n * field (always written by the built-in scorers since lk-core 0.3.0) and\n * falls back to the deprecated `correct` boolean so a 0.2-era\n * consumer-constructed `ItemOutcome` still marks correctly in `review`.\n */\nfunction correctnessByItem(details: readonly ScoringDetail[]): Map<string, boolean> {\n const map = new Map<string, boolean>();\n for (const detail of details) {\n map.set(\n detail.itemId,\n detail.outcome !== undefined ? detail.outcome === 'correct' : detail.correct,\n );\n }\n return map;\n}\n\nexport function FillInTheBlanks({\n data,\n onComplete,\n onSubmit,\n onChange,\n onInteraction,\n value,\n defaultValue,\n renderMode = 'practice',\n outcome,\n sanitizeHtml,\n theme,\n locale,\n disabled,\n showCorrectAnswers,\n}: FillInTheBlanksProps) {\n const isExam = renderMode === 'exam';\n const isReview = renderMode === 'review';\n\n // Dev-only boundary validation (Req 2.3); throws in render so the wrapping\n // ActivityErrorBoundary catches it. Re-runs only when data changes.\n const devError = useMemo(() => {\n if (!isDevelopment()) {\n return null;\n }\n if (data.redacted === true) {\n // A redact() projection cannot satisfy the full content schema — the\n // answer key is gone by design. Validate it against the STRICT redacted\n // schema instead, which additionally proves the payload is learner-safe.\n try {\n assertRedacted(data);\n return null;\n } catch (error) {\n return error instanceof Error ? error : new Error(String(error));\n }\n }\n const result = validateActivity('fill-in-the-blanks', data);\n return result.success ? null : new ActivitySchemaError('fill-in-the-blanks', result.errors);\n }, [data]);\n\n const { state, start, complete, getTimeSpent, reset } = useActivityState();\n // Controlled when `value` is supplied: the answers rendered are ALWAYS the\n // caller's, and internal state is never read. Uncontrolled otherwise,\n // seeded from `defaultValue`.\n const isControlled = value !== undefined;\n const [internalAnswers, setInternalAnswers] = useState<Record<string, string>>(() =>\n answersOf(defaultValue),\n );\n const [revealed, setRevealed] = useState<Set<string>>(() => new Set());\n const [result, setResult] = useState<ScoringResult | null>(null);\n const [summary, setSummary] = useState<string | null>(null);\n const [feedbackHidden, setFeedbackHidden] = useState(false);\n\n const answers = isControlled ? answersOf(value) : internalAnswers;\n\n // `defaultValue` is read only by the reset effect, and it is commonly an\n // inline object literal — putting it in that effect's deps would re-seed\n // (and wipe) the learner's typing on every render. Mirror it into a ref in\n // its own effect, which commits before the reset effect below.\n const defaultValueRef = useRef(defaultValue);\n useEffect(() => {\n defaultValueRef.current = defaultValue;\n }, [defaultValue]);\n\n // `data` is an intentional reset trigger (Req 3.7), not read in the body.\n // biome-ignore lint/correctness/useExhaustiveDependencies: data is the reset trigger (Req 3.7)\n useEffect(() => {\n setInternalAnswers(answersOf(defaultValueRef.current));\n setRevealed(new Set());\n setResult(null);\n setSummary(null);\n setFeedbackHidden(false);\n reset();\n }, [data, reset]);\n\n /*\n * RICH TEXT — deliberate, documented limitation (see `sanitizeHtml`).\n *\n * Every other activity can render its `*Html` field through the caller's\n * sanitiser, because the rich text is a LEAF: the sanitised string is\n * handed to one `dangerouslySetInnerHTML` node and nothing is inserted into\n * it. A fill-in-the-blanks passage is not a leaf — it is the CONTAINER of\n * the React-controlled `<input>`s, one per `{{id}}` placeholder.\n *\n * Placing those inputs inside sanitised HTML means slicing the sanitised\n * string at the placeholders and injecting each slice as its own HTML\n * fragment, which is unsafe and broken on both counts:\n * 1. Broken: the slices are unbalanced. `<p>A {{x}} B</p>` yields `<p>A `\n * and ` B</p>`; the fragment parser auto-closes both, so the input\n * lands OUTSIDE the paragraph and the authored block structure is\n * destroyed.\n * 2. Unsafe: the sanitiser's guarantee covers the document it returned,\n * not arbitrary substrings of it. A placeholder inside an attribute\n * (`<img alt=\"{{x}}\">`) splits mid-attribute, and re-parsing the\n * halves in a fresh context is exactly the mutation (mXSS) class of\n * transformation that voids a sanitiser's output guarantee. An exam\n * runner must never depend on that.\n *\n * The safe alternative — parse the sanitised string once into a detached\n * DOM, split only TEXT nodes at placeholders, and portal the inputs into\n * anchors — needs a DOM at render time (no SSR) and a large imperative\n * subsystem. Until that lands, the plain-text `passage` is rendered (always\n * escaped by React), which is correct, answerable, and safe in every mode.\n * `data.passageHtml` is carried and redacted by lk-core regardless, so no\n * authored content is lost — only this component declines to render it.\n */\n useEffect(() => {\n if (isDevelopment() && sanitizeHtml !== undefined && data.passageHtml !== undefined) {\n console.warn(\n '[lk-react] FillInTheBlanks ignores `passageHtml`: the passage hosts the answer inputs, ' +\n 'so rich HTML cannot be split at {{placeholders}} without voiding the sanitiser. ' +\n 'Rendering the plain `passage` instead.',\n );\n }\n }, [sanitizeHtml, data.passageHtml]);\n\n const segments = useMemo(() => parsePassage(data.passage), [data.passage]);\n const blankById = useMemo(() => new Map(data.blanks.map((b) => [b.id, b])), [data.blanks]);\n\n if (devError) {\n throw devError;\n }\n\n const submitted = state === 'completed';\n const inactive = disabled === true || submitted || isReview;\n\n /**\n * Whether the component may show answer-key-derived information (per-blank\n * correctness, authored feedback, correct answers). NEVER in `exam`; in\n * `practice` only after the component scored locally; in `review` only once\n * the caller supplies a SCORED outcome — an item that is still awaiting a\n * grade must not have its key revealed just because it is being read back.\n */\n const revealing = isReview ? outcome?.status === 'scored' : !isExam && submitted;\n\n const fireInteraction = (\n type: 'blank-filled' | 'hint-requested' | 'submitted',\n payload: Record<string, unknown>,\n ): void => {\n onInteraction?.({ type, activityId: data.id, timestamp: Date.now(), payload });\n };\n\n const handleChange = (blankId: string, text: string): void => {\n if (inactive) {\n return;\n }\n if (state === 'idle') {\n start();\n }\n const next = { ...answers, [blankId]: text };\n if (!isControlled) {\n setInternalAnswers(next);\n }\n onChange?.({ type: 'fill-in-the-blanks', answers: next });\n fireInteraction('blank-filled', { blankId, value: text });\n };\n\n const toggleHint = (blankId: string): void => {\n if (inactive) {\n return;\n }\n if (state === 'idle') {\n start();\n }\n setRevealed((prev) => {\n const next = new Set(prev);\n if (next.has(blankId)) {\n next.delete(blankId);\n return next;\n }\n next.add(blankId);\n return next;\n });\n // Only a reveal is a \"hint requested\"; hiding is not a new request.\n if (!revealed.has(blankId)) {\n fireInteraction('hint-requested', { blankId });\n }\n };\n\n const handleSubmit = (event: React.FormEvent): void => {\n event.preventDefault();\n if (inactive) {\n return;\n }\n const response: FillInTheBlanksLearnerResponse = { type: 'fill-in-the-blanks', answers };\n // Fires in every submitting mode and always BEFORE onComplete, so an exam\n // runner can persist the raw response regardless of local grading.\n onSubmit?.(response);\n\n if (isExam) {\n // EXAM: the client neither grades nor reveals. No score()/evaluate(), no\n // read of any answer-key field, no onComplete — and no xAPI statement,\n // because its `correctResponsesPattern` IS the answer key. The server\n // owns the grade; it emits the statement.\n complete();\n setSummary('Answer submitted.');\n fireInteraction('submitted', { answers });\n return;\n }\n\n // PRACTICE: unchanged v1 behaviour. `data` carries the key here — exam\n // mode is the only path that may be handed a redact() projection.\n const scoringResult = score('fill-in-the-blanks', data as FillInTheBlanksData, response);\n complete();\n const timeSpent = getTimeSpent();\n const xapiStatement = xAPIBuilder.buildAnsweredStatement({\n actor: ANONYMOUS_ACTOR,\n object: {\n id: objectIdFor(data.id),\n name: { [data.locale ?? 'en-US']: data.title },\n type: 'http://adlnet.gov/expapi/activities/cmi.interaction',\n interactionType: 'fill-in',\n correctResponsesPattern: [\n data.blanks.map((blank) => blank.acceptedAnswers[0] ?? '').join('[,]'),\n ],\n },\n scoringResult,\n timeSpentMs: timeSpent,\n response: JSON.stringify(answers),\n });\n setResult(scoringResult);\n onComplete?.({\n score: scoringResult.score,\n maxScore: scoringResult.maxScore,\n passed: scoringResult.passed,\n timeSpent,\n xapiStatement,\n });\n // Core selects the authored overall feedback on `passed` (B3 fix).\n const overall = scoringResult.feedback;\n setSummary(\n `Answer submitted. Score ${Math.round(scoringResult.score * 100)}%. ${\n scoringResult.passed ? 'Passed.' : 'Not passed.'\n }${overall ? ` ${overall}` : ''}`,\n );\n fireInteraction('submitted', { answers, score: scoringResult.score });\n };\n\n const correctByBlank = useMemo(() => {\n if (isExam) {\n // Nothing is ever marked client-side in an exam.\n return new Map<string, boolean>();\n }\n if (isReview) {\n // Correctness comes ONLY from the server-computed outcome; the component\n // never scores in review, so an absent/ungraded outcome marks nothing.\n return outcome?.status === 'scored'\n ? correctnessByItem(outcome.details)\n : new Map<string, boolean>();\n }\n return correctnessByItem(result?.details ?? []);\n }, [isExam, isReview, outcome, result]);\n\n /** Review has no local score: its summary is read off `outcome`, or nothing. */\n const reviewSummary = useMemo(() => {\n if (!isReview || outcome === undefined) {\n return null;\n }\n if (outcome.status === 'scored') {\n return `Score ${Math.round(outcome.score * 100)}%. ${\n outcome.passed ? 'Passed.' : 'Not passed.'\n }${outcome.feedback ? ` ${outcome.feedback}` : ''}`;\n }\n return outcome.status === 'deferred' ? 'Not graded yet.' : 'No grade available.';\n }, [isReview, outcome]);\n\n return (\n <form\n className=\"lk-fib\"\n aria-label={data.title}\n lang={locale}\n data-render-mode={renderMode}\n style={theme as CSSProperties | undefined}\n onSubmit={handleSubmit}\n >\n {data.media ? <ActivityMedia media={data.media} /> : null}\n <fieldset disabled={inactive}>\n <p className=\"lk-fib-passage\">\n {segments.map((seg, i) => {\n if (seg.kind === 'text') {\n // biome-ignore lint/suspicious/noArrayIndexKey: passage segments are positional and static\n return <span key={`t${i}`}>{seg.value}</span>;\n }\n const blank = blankById.get(seg.id);\n if (!blank) {\n return null;\n }\n // `Renderable` only widens `scoringStrategy` at the type level, so\n // the answer-key fields still LOOK required here while a redact()\n // projection omits them at runtime. Read them defensively, and\n // never at all in exam mode.\n const acceptedAnswers: string[] | undefined = isExam\n ? undefined\n : blank.acceptedAnswers;\n const blankFeedback = isExam ? undefined : blank.feedback;\n const correctness = correctByBlank.get(seg.id);\n const hintId = `${data.id}-hint-${seg.id}`;\n const correctAnswer = acceptedAnswers?.[0];\n if (revealing && showCorrectAnswers && correctness === false && correctAnswer) {\n return (\n <span key={seg.id} className=\"lk-fib-answer\" data-correct=\"false\">\n {correctAnswer}\n </span>\n );\n }\n return (\n <span key={seg.id} className=\"lk-fib-blank\">\n <input\n type=\"text\"\n aria-label={`Fill in blank ${seg.ordinal}`}\n aria-describedby={blank.hint ? hintId : undefined}\n value={answers[seg.id] ?? ''}\n disabled={inactive}\n aria-disabled={inactive || undefined}\n data-correct={\n revealing && correctness !== undefined ? String(correctness) : undefined\n }\n onChange={(e) => handleChange(seg.id, e.target.value)}\n />\n {/*\n Hints stay available in exam mode by design: lk-core's field\n policy classifies `blanks.hint` as `public`, so it survives\n redact() and is not part of the answer key.\n */}\n {blank.hint ? (\n <>\n {/*\n Default affordance is an icon; the accessible NAME stays\n text via aria-label (screen readers + tests rely on it).\n The glyph is restylable/replaceable via .lk-fib-hint-btn.\n */}\n <button\n type=\"button\"\n className=\"lk-fib-hint-btn\"\n aria-controls={hintId}\n aria-expanded={revealed.has(seg.id)}\n aria-label={revealed.has(seg.id) ? 'Hide hint' : 'Show hint'}\n disabled={inactive}\n onClick={() => toggleHint(seg.id)}\n >\n <svg\n className=\"lk-fib-hint-icon\"\n viewBox=\"0 0 24 24\"\n width=\"16\"\n height=\"16\"\n aria-hidden=\"true\"\n focusable=\"false\"\n >\n <path\n fill=\"currentColor\"\n d=\"M9 21h6v-1H9v1Zm3-19a7 7 0 0 0-4 12.74V17h8v-2.26A7 7 0 0 0 12 2Z\"\n />\n </svg>\n </button>\n {/*\n Not role=\"tooltip\": a real ARIA tooltip is a named\n hover/focus popup with strict WCAG 1.4.13 constraints.\n This is an accessible click-to-toggle disclosure that is\n the input's aria-describedby target and is announced via\n aria-live; the skin gives it a tooltip-like *visual*\n without the tooltip *semantics*. (Refines Task 15.3.)\n */}\n <span id={hintId} aria-live=\"polite\">\n {revealed.has(seg.id) ? blank.hint : ''}\n </span>\n </>\n ) : null}\n {revealing && !feedbackHidden && blankFeedback ? (\n <span\n className=\"lk-fib-blank-feedback\"\n role=\"note\"\n data-correct={correctness !== undefined ? String(correctness) : undefined}\n >\n {blankFeedback}\n </span>\n ) : null}\n </span>\n );\n })}\n </p>\n {/* Review is read-only: there is nothing to submit. */}\n {isReview ? null : (\n <button type=\"submit\" disabled={inactive}>\n {isExam ? 'Submit answers' : 'Check answers'}\n </button>\n )}\n </fieldset>\n {revealing && data.blanks.some((b) => b.feedback) ? (\n <button\n type=\"button\"\n className=\"lk-fib-feedback-toggle\"\n aria-expanded={!feedbackHidden}\n onClick={() => setFeedbackHidden((h) => !h)}\n >\n {feedbackHidden ? 'Show feedback' : 'Hide feedback'}\n </button>\n ) : null}\n <FeedbackRegion id={`${data.id}-feedback`}>\n {isReview ? reviewSummary : summary}\n </FeedbackRegion>\n </form>\n );\n}\n","'use client';\n\nimport { ActivityErrorBoundary } from '../ActivityErrorBoundary.js';\nimport {\n FillInTheBlanks as FillInTheBlanksCore,\n type FillInTheBlanksProps,\n} from './FillInTheBlanks.js';\n\nexport type { FillInTheBlanksProps } from './FillInTheBlanks.js';\n\n/**\n * Public Fill-in-the-Blanks activity: the core wrapped in\n * `ActivityErrorBoundary` so a render failure (incl. the dev schema-validation\n * throw) degrades to an accessible fallback instead of crashing the host.\n */\nexport function FillInTheBlanks(props: FillInTheBlanksProps) {\n return (\n <ActivityErrorBoundary activityTitle={props.data?.title}>\n <FillInTheBlanksCore {...props} />\n </ActivityErrorBoundary>\n );\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/WrittenResponse/WrittenResponse.tsx","../src/components/WrittenResponse/index.tsx"],"names":["jsxs","jsx","useMemo","isDevelopment","validateActivity","ActivitySchemaError","useActivityState","useState","useRef","useEffect","countWords","evaluate","xAPIBuilder","ANONYMOUS_ACTOR","objectIdFor","ActivityMedia","FeedbackRegion","WrittenResponse","ActivityErrorBoundary"],"mappings":"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","file":"chunk-7BT6OXAR.cjs","sourcesContent":["'use client';\n\nimport {\n ActivitySchemaError,\n countWords,\n evaluate,\n type InteractionEvent,\n type ItemOutcome,\n type LearnerResponse,\n type ThemeTokens,\n validateActivity,\n type WrittenResponseData,\n type XAPIStatement,\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 } from '../_internal.js';\nimport { ActivityMedia } from '../shared/ActivityMedia.js';\nimport { FeedbackRegion } from '../shared/FeedbackRegion.js';\nimport type { HtmlSanitizer, Renderable, RenderMode } from '../types.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. Everything else — controlled value, render\n * mode, server outcome, rich text — follows the shared `ActivityProps`\n * contract verbatim.\n */\nexport interface WrittenResponseProps {\n /** Activity content. Accepts a `redact()` projection in `exam` mode. */\n data: Renderable<WrittenResponseData>;\n /**\n * Called on submit with the ungraded submission (text, recomputed word\n * count, bounds flag, timing, xAPI statement). Fires in `practice` and\n * `exam` mode, after `onSubmit`; never in `review`.\n *\n * Optional since v0.4 so a `review`-mode render (which has no submit\n * control) and an exam runner that only wants the raw response through\n * `onSubmit` do not have to pass a no-op. Supply it in `practice`/`exam`\n * unless `onSubmit` already captures everything you persist.\n */\n onSubmitted?: (submission: WrittenResponseSubmission) => void;\n /**\n * Called on submit with the raw learner response and no grade, BEFORE\n * `onSubmitted`. This is the only submit callback an exam runner needs:\n * the server grades the text and returns an {@link ItemOutcome} later.\n */\n onSubmit?: (response: LearnerResponse) => void;\n /**\n * Controlled value: the learner's current response. When present the\n * textarea renders from it (never from internal state) and every keystroke\n * goes out through `onChange` — restore an in-progress attempt, autosave a\n * delta, or drive a review render with it.\n */\n value?: LearnerResponse;\n /** Initial response for an uncontrolled component (ignored when `value` is set). */\n defaultValue?: LearnerResponse;\n /** Fires on every change to the learner's response. Required for a controlled component. */\n onChange?: (response: LearnerResponse) => void;\n /**\n * Presentation mode, default `practice`. `practice` and `exam` are nearly\n * identical here — a written response is never graded client-side, so\n * there is no local score to withhold; `exam` additionally never calls\n * `evaluate()` and renders happily from a `redact()` projection.\n * `review` is read-only and shows the server's `outcome`.\n */\n renderMode?: RenderMode;\n /**\n * Server-computed outcome, rendered by `review` mode: `deferred` shows a\n * \"not graded yet\" affordance, `scored` shows the returned grade and\n * feedback. Ignored in other modes — this component never manufactures an\n * outcome of its own.\n */\n outcome?: ItemOutcome;\n /** Renders `data.promptHtml` when provided. See `HtmlSanitizer`. */\n sanitizeHtml?: HtmlSanitizer;\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\n/**\n * Reads the text out of a learner response. Another activity type's response\n * shape (the `value`/`defaultValue` props take the `LearnerResponse` union,\n * so a generic runner can hand any of them over) reads as empty rather than\n * throwing mid-exam.\n */\nfunction textOf(response: LearnerResponse | undefined): string {\n return response !== undefined && response.type === 'written-response' ? response.text : '';\n}\n\n/**\n * Renders the server-computed outcome in `review` mode. The grade shown here\n * always came back from the asynchronous grader — nothing on this path scores,\n * infers, or defaults a grade. A `written-response` outcome carries no\n * per-item `details` (there are no options or blanks to mark), so the render\n * is the scaled score, the pass state, and the grader's feedback.\n */\nfunction OutcomeSummary({ outcome }: { outcome: ItemOutcome }) {\n if (outcome.status === 'scored') {\n // `score` is scaled [0–1] against `maxScore`; normalising by `maxScore`\n // is a no-op for the canonical maxScore of 1 and keeps an unscaled\n // grader payload (8.5 / 10) from rendering as 850%.\n const percent =\n outcome.maxScore > 0\n ? Math.round((outcome.score / outcome.maxScore) * 100)\n : Math.round(outcome.score * 100);\n return (\n <div className=\"lk-wr-outcome\" data-status=\"scored\" data-passed={String(outcome.passed)}>\n <p className=\"lk-wr-grade\">\n Score {percent}%. {outcome.passed ? 'Passed.' : 'Not passed.'}\n </p>\n {outcome.feedback ? <p className=\"lk-wr-grade-feedback\">{outcome.feedback}</p> : null}\n </div>\n );\n }\n if (outcome.status === 'deferred') {\n return (\n <div className=\"lk-wr-outcome\" data-status=\"deferred\">\n <p className=\"lk-wr-grade\">Not graded yet. This response is waiting for its grade.</p>\n </div>\n );\n }\n // `unscorable` reasons are developer-facing strings; surface the state, not\n // the reason, and leave it on the element for diagnostics.\n return (\n <div className=\"lk-wr-outcome\" data-status=\"unscorable\" data-reason={outcome.reason}>\n <p className=\"lk-wr-grade\">This response could not be graded.</p>\n </div>\n );\n}\n\nexport function WrittenResponse({\n data,\n onSubmitted,\n onSubmit,\n value,\n defaultValue,\n onChange,\n renderMode = 'practice',\n outcome,\n sanitizeHtml,\n onInteraction,\n theme,\n locale,\n disabled,\n}: WrittenResponseProps) {\n // Dev-only boundary validation (Req 2.3), same convention as MC/FIB. The\n // content schema is loose, so a `redact()` projection validates too.\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 isControlled = value !== undefined;\n const [innerText, setInnerText] = useState<string>(() => textOf(defaultValue));\n const [summary, setSummary] = useState<string | null>(null);\n\n /**\n * Rich text is rendered ONLY through a caller-supplied sanitiser, and only\n * when the author actually shipped the HTML sidecar. Without both, the\n * plain-text prompt is rendered as an escaped React child.\n */\n const promptHtml = useMemo(() => {\n if (sanitizeHtml === undefined || typeof data.promptHtml !== 'string') {\n return null;\n }\n return sanitizeHtml(data.promptHtml);\n }, [data.promptHtml, sanitizeHtml]);\n\n // Latest seed text, kept in a ref so `defaultValue` is NOT a reset trigger:\n // callers pass an object literal, whose identity changes every render.\n const defaultTextRef = useRef<string>(textOf(defaultValue));\n useEffect(() => {\n defaultTextRef.current = textOf(defaultValue);\n }, [defaultValue]);\n\n // Reset on data-prop change (Req 3.7): back to the seed (empty when there\n // is none — the v1 behaviour), never the previous activity's draft.\n // biome-ignore lint/correctness/useExhaustiveDependencies: data is the reset trigger (Req 3.7)\n useEffect(() => {\n setInnerText(defaultTextRef.current);\n setSummary(null);\n reset();\n }, [data, reset]);\n\n if (devError) {\n throw devError;\n }\n\n const isExam = renderMode === 'exam';\n const isReview = renderMode === 'review';\n\n const text = isControlled ? textOf(value) : innerText;\n const submitted = state === 'completed';\n const inactive = disabled === true || submitted || isReview;\n const wordCount = countWords(text);\n const withinBounds = wordCount >= data.minWords && wordCount <= data.maxWords;\n\n const handleChange = (next: string): void => {\n if (inactive) {\n return;\n }\n if (state === 'idle') {\n start();\n }\n // A controlled component renders from `props.value` only; the caller's\n // state is the single source of truth for the text.\n if (!isControlled) {\n setInnerText(next);\n }\n const nextWordCount = countWords(next);\n onChange?.({ type: 'written-response', text: next, wordCount: nextWordCount });\n onInteraction?.({\n type: 'text-changed',\n activityId: data.id,\n timestamp: Date.now(),\n payload: { wordCount: nextWordCount },\n });\n };\n\n const handleSubmit = (event: React.FormEvent): void => {\n event.preventDefault();\n if (inactive || text.trim().length === 0) {\n return;\n }\n let finalWordCount = wordCount;\n let finalWithinBounds = withinBounds;\n // `exam` mode must not call evaluate(): the component does not grade, and\n // a redacted projection carries no scoring input anyway. The two facts\n // evaluate() returns for a deferred type (recomputed word count, bounds)\n // are exactly what is computed above, so the payload is identical either\n // way; practice keeps going through core so the numbers stay canonical.\n if (!isExam) {\n const itemOutcome = evaluate(data, { type: 'written-response', text, wordCount });\n const partial = itemOutcome.status === 'deferred' ? itemOutcome.partial : undefined;\n if (typeof partial?.wordCount === 'number') {\n finalWordCount = partial.wordCount;\n }\n if (typeof partial?.withinWordBounds === 'boolean') {\n finalWithinBounds = partial.withinWordBounds;\n }\n }\n complete();\n const timeSpent = getTimeSpent();\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 // Raw response first (an exam runner persists this), then the fuller\n // ungraded submission — mirroring `onSubmit` before `onComplete`.\n onSubmit?.({ type: 'written-response', text, wordCount: finalWordCount });\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 data-render-mode={renderMode}\n style={theme as CSSProperties | undefined}\n onSubmit={handleSubmit}\n >\n {data.media ? <ActivityMedia media={data.media} /> : null}\n {promptHtml === null ? (\n <p className=\"lk-wr-prompt\" id={promptId}>\n {data.prompt}\n </p>\n ) : (\n // A div, not a p: sanitised author HTML may contain block content.\n <div\n className=\"lk-wr-prompt\"\n id={promptId}\n // biome-ignore lint/security/noDangerouslySetInnerHtml: rendered only through the caller-supplied HtmlSanitizer — the SDK never injects unsanitised markup\n dangerouslySetInnerHTML={{ __html: promptHtml }}\n />\n )}\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 // `review` uses readOnly rather than disabled: the submitted essay\n // must stay focusable, selectable and scrollable for a screen reader\n // and a keyboard, while remaining uneditable.\n disabled={!isReview && inactive}\n readOnly={isReview}\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 {isReview ? null : (\n <button\n type=\"submit\"\n className=\"lk-wr-submit\"\n disabled={inactive || text.trim().length === 0}\n >\n Submit\n </button>\n )}\n <FeedbackRegion id={`${data.id}-feedback`}>\n {isReview ? outcome ? <OutcomeSummary outcome={outcome} /> : null : summary}\n </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 { HtmlSanitizer, Renderable, RenderMode } from '../types.js';\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/ActivityErrorBoundary.tsx","../src/components/_internal.ts","../src/components/shared/ActivityMedia.tsx","../src/components/shared/FeedbackRegion.tsx"],"names":["Component","jsxs","jsx"],"mappings":"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","file":"chunk-QHOV2A6M.cjs","sourcesContent":["'use client';\n\nimport { Component, type ReactNode } from 'react';\n\nfunction isProduction(): boolean {\n const g = globalThis as { process?: { env?: Record<string, string | undefined> } };\n return g.process?.env?.NODE_ENV === 'production';\n}\n\nexport interface ActivityErrorBoundaryProps {\n /** Shown in the production fallback when a render error occurs. */\n activityTitle?: string;\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 if (!isProduction()) {\n return (\n <div role=\"alert\">\n <strong>Activity failed to render</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.activityTitle}\" could not be displayed.`\n : 'This activity could not be displayed.'}\n </div>\n );\n }\n}\n","import type { XAPIActor } from '@intellectif/lk-core';\n\n/** Dev = anything other than an explicit production NODE_ENV (browser-safe). */\nexport function isDevelopment(): boolean {\n const g = globalThis as { process?: { env?: Record<string, string | undefined> } };\n return g.process?.env?.NODE_ENV !== 'production';\n}\n\n/**\n * Activity components cannot know the learner's identity (Req 3.1 fixes the\n * prop set). They emit a structurally-valid statement with this anonymous\n * actor; real identity is applied by the useXAPI/LRS layer (XAPIConfig.actor).\n */\nexport const ANONYMOUS_ACTOR: XAPIActor = {\n objectType: 'Agent',\n account: { homePage: 'https://github.com/intellectif/learning-kit', name: 'anonymous' },\n};\n\n/** `data.id` is not an IRI; xAPI object ids must be. Wrap it as a URN. */\nexport function objectIdFor(id: string): string {\n return `urn:learning-kit:activity:${encodeURIComponent(id)}`;\n}\n\n/**\n * Random per-mount session id for the shuffle seed. `crypto.randomUUID` is\n * unavailable outside secure contexts (plain-http LAN/staging hosts), so a\n * Math.random fallback keeps the component from crashing there — the seed\n * only randomises presentation order, so cryptographic strength is not needed.\n */\nexport function randomSessionId(): string {\n const c = (globalThis as { crypto?: { randomUUID?: () => string } }).crypto;\n if (c?.randomUUID) {\n return c.randomUUID();\n }\n return `s-${Math.random().toString(36).slice(2)}${Math.random().toString(36).slice(2)}`;\n}\n","import type { ActivityMedia as ActivityMediaData } from '@intellectif/lk-core';\n\n/**\n * Presentational media block shown above a question or passage. URL-only:\n * the SDK does not host media (consumer responsibility). Accessibility:\n * images use `alt` (schema requires it non-empty); audio/video expose an\n * optional label and a captions `<track>` when `captionsUrl` is provided;\n * `embed` renders a responsive sandboxed iframe with `alt` as its required\n * accessible `title` (Req 14.5).\n */\nexport function ActivityMedia({ media }: { media: ActivityMediaData }): React.JSX.Element {\n const { type, url, alt, captionsUrl } = media;\n\n if (type === 'image') {\n return (\n <figure className=\"lk-media\">\n <img className=\"lk-media-el\" src={url} alt={alt ?? ''} />\n </figure>\n );\n }\n\n if (type === 'audio') {\n return (\n <figure className=\"lk-media\">\n {/* biome-ignore lint/a11y/useMediaCaption: captions are optional in the data contract — a <track> is rendered when captionsUrl is provided; absence is the author's documented choice (Req 14.5) */}\n <audio className=\"lk-media-el\" controls aria-label={alt || undefined}>\n <source src={url} />\n {captionsUrl ? <track kind=\"captions\" src={captionsUrl} default /> : null}\n </audio>\n </figure>\n );\n }\n\n if (type === 'embed') {\n return (\n <figure className=\"lk-media\">\n <div className=\"lk-media-embed\">\n <iframe\n className=\"lk-media-el\"\n src={url}\n title={alt ?? 'Embedded media'}\n loading=\"lazy\"\n referrerPolicy=\"strict-origin-when-cross-origin\"\n // The docblock always promised a sandboxed iframe; now it is one.\n // allow-scripts + allow-same-origin are required by provider\n // players (YouTube/Vimeo); top-navigation, forms, downloads and\n // popups stay blocked. Scheme allow-listing in MediaSchema is the\n // primary defense; this is depth.\n sandbox=\"allow-scripts allow-same-origin allow-presentation\"\n allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\"\n allowFullScreen\n />\n </div>\n </figure>\n );\n }\n\n return (\n <figure className=\"lk-media\">\n {/* biome-ignore lint/a11y/useMediaCaption: captions are optional in the data contract — a <track> is rendered when captionsUrl is provided; absence is the author's documented choice (Req 14.5) */}\n <video className=\"lk-media-el\" controls aria-label={alt || undefined}>\n <source src={url} />\n {captionsUrl ? <track kind=\"captions\" src={captionsUrl} default /> : null}\n </video>\n </figure>\n );\n}\n","import type { ReactNode } from 'react';\n\nexport interface FeedbackRegionProps {\n /** Identifier so activity inputs can reference this region via aria. */\n id?: string;\n className?: string;\n children?: ReactNode;\n}\n\n/**\n * A polite ARIA live region. Screen readers announce content injected here\n * (score, feedback, state changes) without moving focus. Pure/presentational\n * — safe to render in any environment.\n */\nexport function FeedbackRegion({ id, className, children }: FeedbackRegionProps) {\n return (\n <div id={id} className={className} aria-live=\"polite\" aria-atomic=\"true\">\n {children}\n </div>\n );\n}\n"]}
|
package/dist/chunk-STLRKR2I.cjs
DELETED
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
var chunkW6BH2YMZ_cjs = require('./chunk-W6BH2YMZ.cjs');
|
|
4
|
-
var chunkUXO6KYSZ_cjs = require('./chunk-UXO6KYSZ.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(chunkW6BH2YMZ_cjs.MultipleChoice, { data: current, ...childProps }, `${index}:${current.id}`) : current.type === "fill-in-the-blanks" ? /* @__PURE__ */ jsxRuntime.jsx(chunkUXO6KYSZ_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-STLRKR2I.cjs.map
|
|
111
|
-
//# sourceMappingURL=chunk-STLRKR2I.cjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/ActivitySequence/ActivitySequence.tsx"],"names":["useState","useRef","useEffect","jsx","jsxs","MultipleChoice","FillInTheBlanks"],"mappings":";;;;;;;AAiCO,SAAS,gBAAA,CAAiB;AAAA,EAC/B,UAAA;AAAA,EACA,kBAAA;AAAA,EACA,UAAA;AAAA,EACA,aAAA;AAAA,EACA,KAAA;AAAA,EACA,MAAA;AAAA,EACA;AACF,CAAA,EAA6C;AAC3C,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAIA,eAAS,CAAC,CAAA;AACpC,EAAA,MAAM,CAAC,OAAA,EAAS,UAAU,CAAA,GAAIA,cAAA;AAAA,IAAoC,MAChE,UAAA,CAAW,GAAA,CAAI,MAAM,IAAI;AAAA,GAC3B;AACA,EAAA,MAAM,SAAA,GAAYC,aAAuB,IAAI,CAAA;AAC7C,EAAA,MAAM,UAAA,GAAaA,aAAO,KAAK,CAAA;AAY/B,EAAA,MAAM,MAAA,GAAS,KAAK,SAAA,CAAU,UAAA,CAAW,IAAI,CAAC,QAAA,KAAa,QAAA,CAAS,EAAE,CAAC,CAAA;AACvE,EAAA,MAAM,CAAC,UAAA,EAAY,aAAa,CAAA,GAAID,eAAS,MAAM,CAAA;AACnD,EAAA,IAAI,eAAe,MAAA,EAAQ;AACzB,IAAA,aAAA,CAAc,MAAM,CAAA;AACpB,IAAA,QAAA,CAAS,CAAC,CAAA;AACV,IAAA,UAAA,CAAW,UAAA,CAAW,GAAA,CAAI,MAAM,IAAI,CAAC,CAAA;AAAA,EACvC;AAKA,EAAAE,eAAA,CAAU,MAAM;AACd,IAAA,IAAI,WAAW,OAAA,EAAS;AACtB,MAAA,SAAA,CAAU,SAAS,KAAA,EAAM;AAAA,IAC3B,CAAA,MAAO;AACL,MAAA,UAAA,CAAW,OAAA,GAAU,IAAA;AAAA,IACvB;AAAA,EACF,CAAA,EAAG,CAAC,KAAK,CAAC,CAAA;AAEV,EAAA,MAAM,QAAQ,UAAA,CAAW,MAAA;AACzB,EAAA,MAAM,OAAA,GAAU,WAAW,KAAK,CAAA;AAEhC,EAAA,MAAM,EAAA,GAAK,CAAC,IAAA,KAAuB;AACjC,IAAA,QAAA,CAAS,IAAI,CAAA;AAAA,EACf,CAAA;AAEA,EAAA,MAAM,cAAA,GAAiB,CAAC,MAAA,KAAiC;AACvD,IAAA,MAAM,WAAA,GAAc,QAAQ,KAAA,EAAM;AAClC,IAAA,WAAA,CAAY,KAAK,CAAA,GAAI,MAAA;AACrB,IAAA,UAAA,CAAW,WAAW,CAAA;AACtB,IAAA,kBAAA,GAAqB,QAAQ,KAAK,CAAA;AAClC,IAAA,IAAI,YAAY,KAAA,CAAM,CAAC,CAAA,KAA2B,CAAA,KAAM,IAAI,CAAA,EAAG;AAC7D,MAAA,UAAA,GAAa,WAAW,CAAA;AAAA,IAC1B;AAAA,EACF,CAAA;AAEA,EAAA,IAAI,CAAC,OAAA,EAAS;AACZ,IAAA,uBAAOC,cAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,QAAA,EAAS,CAAA;AAAA,EACjC;AAEA,EAAA,MAAM,UAAA,GAAa;AAAA,IACjB,UAAA,EAAY,cAAA;AAAA,IACZ,GAAI,aAAA,GAAgB,EAAE,aAAA,KAAkB,EAAC;AAAA,IACzC,GAAI,KAAA,GAAQ,EAAE,KAAA,KAAU,EAAC;AAAA,IACzB,GAAI,MAAA,GAAS,EAAE,MAAA,KAAW,EAAC;AAAA,IAC3B,GAAI,QAAA,GAAW,EAAE,QAAA,KAAa;AAAC,GACjC;AAEA,EAAA,uBACEC,eAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,QAAA,EAAS,MAAM,MAAA,EAC5B,QAAA,EAAA;AAAA,oBAAAA,eAAA,CAAC,SAAI,SAAA,EAAU,iBAAA,EAAkB,WAAA,EAAU,QAAA,EAAS,MAAK,QAAA,EAAS,QAAA,EAAA;AAAA,MAAA,WAAA;AAAA,MACtD,KAAA,GAAQ,CAAA;AAAA,MAAE,MAAA;AAAA,MAAK;AAAA,KAAA,EAC3B,CAAA;AAAA,oBAEAD,cAAA;AAAA,MAAC,SAAA;AAAA,MAAA;AAAA,QACC,SAAA,EAAU,iBAAA;AAAA,QACV,GAAA,EAAK,SAAA;AAAA,QACL,QAAA,EAAU,EAAA;AAAA,QACV,YAAA,EAAY,CAAA,SAAA,EAAY,KAAA,GAAQ,CAAC,OAAO,KAAK,CAAA,CAAA;AAAA,QAE5C,QAAA,EAAA,OAAA,CAAQ,IAAA,KAAS,iBAAA,mBAChBA,cAAA,CAACE,gCAAA,EAAA,EAA8C,IAAA,EAAM,OAAA,EAAU,GAAG,UAAA,EAAA,EAA7C,CAAA,EAAG,KAAK,CAAA,CAAA,EAAI,QAAQ,EAAE,CAAA,CAAmC,CAAA,GAC5E,OAAA,CAAQ,IAAA,KAAS,oBAAA,mBACnBF,cAAA,CAACG,iCAAA,EAAA,EAA+C,IAAA,EAAM,OAAA,EAAU,GAAG,UAAA,EAAA,EAA7C,CAAA,EAAG,KAAK,CAAA,CAAA,EAAI,OAAA,CAAQ,EAAE,CAAA,CAAmC,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA,yCAM9E,KAAA,EAAA,EAAI,SAAA,EAAU,oBAAA,EAAqB,IAAA,EAAK,QAAO,QAAA,EAAA,gEAAA,EAEhD;AAAA;AAAA;AAAA,KAEJ;AAAA,oBAEAF,eAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,YAAA,EACb,QAAA,EAAA;AAAA,sBAAAD,cAAA;AAAA,QAAC,QAAA;AAAA,QAAA;AAAA,UACC,IAAA,EAAK,QAAA;AAAA,UACL,SAAA,EAAU,aAAA;AAAA,UACV,OAAA,EAAS,MAAM,EAAA,CAAG,KAAA,GAAQ,CAAC,CAAA;AAAA,UAC3B,UAAU,KAAA,KAAU,CAAA;AAAA,UACrB,QAAA,EAAA;AAAA;AAAA,OAED;AAAA,sBACAA,cAAA;AAAA,QAAC,QAAA;AAAA,QAAA;AAAA,UACC,IAAA,EAAK,QAAA;AAAA,UACL,SAAA,EAAU,aAAA;AAAA,UACV,OAAA,EAAS,MAAM,EAAA,CAAG,KAAA,GAAQ,CAAC,CAAA;AAAA,UAC3B,QAAA,EAAU,UAAU,KAAA,GAAQ,CAAA;AAAA,UAC7B,QAAA,EAAA;AAAA;AAAA;AAED,KAAA,EACF;AAAA,GAAA,EACF,CAAA;AAEJ","file":"chunk-STLRKR2I.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"]}
|
package/dist/chunk-UASXUWHH.js
DELETED
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
import { MultipleChoice } from './chunk-ZVFWJTT4.js';
|
|
2
|
-
import { FillInTheBlanks } from './chunk-3X7MYAFT.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-UASXUWHH.js.map
|
|
109
|
-
//# sourceMappingURL=chunk-UASXUWHH.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/ActivitySequence/ActivitySequence.tsx"],"names":[],"mappings":"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","file":"chunk-UASXUWHH.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"]}
|