@intellectif/lk-react 20.0.0 → 21.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (168) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +30 -2
  3. package/dist/{WrittenResponse-obIDDYDH.d.cts → WrittenResponse-BVPmrmQL.d.cts} +1 -1
  4. package/dist/{WrittenResponse-D2YI8xOK.d.ts → WrittenResponse-IAJvvL-g.d.ts} +1 -1
  5. package/dist/ai/LkAiProvider.cjs +3 -3
  6. package/dist/ai/LkAiProvider.d.cts +5 -3
  7. package/dist/ai/LkAiProvider.d.ts +5 -3
  8. package/dist/ai/LkAiProvider.js +1 -1
  9. package/dist/ai/useAiHelp.cjs +5 -5
  10. package/dist/ai/useAiHelp.d.cts +1 -1
  11. package/dist/ai/useAiHelp.d.ts +1 -1
  12. package/dist/ai/useAiHelp.js +3 -3
  13. package/dist/{chunk-W6DMHN33.js → chunk-5MO7ACSB.js} +10 -10
  14. package/dist/{chunk-W6DMHN33.js.map → chunk-5MO7ACSB.js.map} +1 -1
  15. package/dist/{chunk-7K4B2EMU.js → chunk-5Q2KFIZQ.js} +4 -4
  16. package/dist/{chunk-7K4B2EMU.js.map → chunk-5Q2KFIZQ.js.map} +1 -1
  17. package/dist/{chunk-APZ6JFN6.cjs → chunk-6SNNMVBV.cjs} +5 -5
  18. package/dist/{chunk-APZ6JFN6.cjs.map → chunk-6SNNMVBV.cjs.map} +1 -1
  19. package/dist/{chunk-L7WY5YFQ.cjs → chunk-7JBXDQTD.cjs} +5 -5
  20. package/dist/{chunk-L7WY5YFQ.cjs.map → chunk-7JBXDQTD.cjs.map} +1 -1
  21. package/dist/{chunk-NWNQ2PHT.js → chunk-AY3WN4FB.js} +8 -8
  22. package/dist/{chunk-NWNQ2PHT.js.map → chunk-AY3WN4FB.js.map} +1 -1
  23. package/dist/{chunk-CEUEXISZ.cjs → chunk-CB2A56Q7.cjs} +131 -50
  24. package/dist/chunk-CB2A56Q7.cjs.map +1 -0
  25. package/dist/chunk-CQ37UD4T.cjs +251 -0
  26. package/dist/chunk-CQ37UD4T.cjs.map +1 -0
  27. package/dist/{chunk-YRNEOBXA.js → chunk-EYLBSS4Y.js} +11 -2
  28. package/dist/chunk-EYLBSS4Y.js.map +1 -0
  29. package/dist/{chunk-AHGB5HH6.cjs → chunk-GHKMX5UT.cjs} +11 -2
  30. package/dist/chunk-GHKMX5UT.cjs.map +1 -0
  31. package/dist/chunk-GND3OBVE.js +241 -0
  32. package/dist/chunk-GND3OBVE.js.map +1 -0
  33. package/dist/{chunk-DZVF3IGB.cjs → chunk-GPGCBDBN.cjs} +59 -25
  34. package/dist/chunk-GPGCBDBN.cjs.map +1 -0
  35. package/dist/{chunk-LK5VXNMR.cjs → chunk-GW3YIPDE.cjs} +18 -18
  36. package/dist/{chunk-LK5VXNMR.cjs.map → chunk-GW3YIPDE.cjs.map} +1 -1
  37. package/dist/{chunk-BJLQKJZX.cjs → chunk-J2QIHZ3H.cjs} +2 -2
  38. package/dist/chunk-J2QIHZ3H.cjs.map +1 -0
  39. package/dist/{chunk-3XP6VO4G.js → chunk-KRM6DX47.js} +139 -45
  40. package/dist/chunk-KRM6DX47.js.map +1 -0
  41. package/dist/{chunk-PWLJDP3X.cjs → chunk-LHFC2BCM.cjs} +95 -27
  42. package/dist/chunk-LHFC2BCM.cjs.map +1 -0
  43. package/dist/{chunk-G7AGM2Y7.js → chunk-M3K6BF5M.js} +4 -4
  44. package/dist/{chunk-G7AGM2Y7.js.map → chunk-M3K6BF5M.js.map} +1 -1
  45. package/dist/{chunk-IXMT7X3E.cjs → chunk-M4UJBLVR.cjs} +17 -6
  46. package/dist/chunk-M4UJBLVR.cjs.map +1 -0
  47. package/dist/{chunk-YNDKAYW3.js → chunk-MCTNLUKO.js} +2 -2
  48. package/dist/{chunk-YNDKAYW3.js.map → chunk-MCTNLUKO.js.map} +1 -1
  49. package/dist/{chunk-ZDYNAWDZ.cjs → chunk-MPHX3Y42.cjs} +2 -2
  50. package/dist/{chunk-ZDYNAWDZ.cjs.map → chunk-MPHX3Y42.cjs.map} +1 -1
  51. package/dist/{chunk-AQIAQHJC.js → chunk-NDIKHMLJ.js} +4 -4
  52. package/dist/{chunk-AQIAQHJC.js.map → chunk-NDIKHMLJ.js.map} +1 -1
  53. package/dist/{chunk-LX2X4KC7.cjs → chunk-NILHU555.cjs} +7 -7
  54. package/dist/{chunk-LX2X4KC7.cjs.map → chunk-NILHU555.cjs.map} +1 -1
  55. package/dist/{chunk-JLDSCHY5.cjs → chunk-OKEN6NJL.cjs} +150 -61
  56. package/dist/chunk-OKEN6NJL.cjs.map +1 -0
  57. package/dist/{chunk-ORBU3XKH.js → chunk-PJMXHDY5.js} +16 -5
  58. package/dist/chunk-PJMXHDY5.js.map +1 -0
  59. package/dist/{chunk-QKW2GGTV.js → chunk-U2BX2RWY.js} +84 -16
  60. package/dist/chunk-U2BX2RWY.js.map +1 -0
  61. package/dist/{chunk-WBLYD5QA.js → chunk-U4KTOH7C.js} +2 -2
  62. package/dist/chunk-U4KTOH7C.js.map +1 -0
  63. package/dist/{chunk-FEOFX4QW.js → chunk-U6SEII5V.js} +3 -3
  64. package/dist/{chunk-FEOFX4QW.js.map → chunk-U6SEII5V.js.map} +1 -1
  65. package/dist/{chunk-6S2CKZ4H.cjs → chunk-W2P5RILE.cjs} +145 -51
  66. package/dist/chunk-W2P5RILE.cjs.map +1 -0
  67. package/dist/{chunk-AG7MWZYZ.js → chunk-W6TWQY6S.js} +144 -55
  68. package/dist/chunk-W6TWQY6S.js.map +1 -0
  69. package/dist/{chunk-HREA5SMB.cjs → chunk-WGHMWBEJ.cjs} +116 -47
  70. package/dist/chunk-WGHMWBEJ.cjs.map +1 -0
  71. package/dist/{chunk-5GI53C6T.js → chunk-WNDNQ6NR.js} +109 -40
  72. package/dist/chunk-WNDNQ6NR.js.map +1 -0
  73. package/dist/{chunk-RT2PP4SO.cjs → chunk-WXESKL75.cjs} +30 -30
  74. package/dist/{chunk-RT2PP4SO.cjs.map → chunk-WXESKL75.cjs.map} +1 -1
  75. package/dist/{chunk-BY4UQH2Y.js → chunk-X4YWXZ4J.js} +3 -3
  76. package/dist/{chunk-BY4UQH2Y.js.map → chunk-X4YWXZ4J.js.map} +1 -1
  77. package/dist/{chunk-HI6BUUIJ.js → chunk-XB2AHEU5.js} +48 -14
  78. package/dist/chunk-XB2AHEU5.js.map +1 -0
  79. package/dist/{chunk-IQ55QNBI.js → chunk-XM5Q2ZCH.js} +125 -44
  80. package/dist/chunk-XM5Q2ZCH.js.map +1 -0
  81. package/dist/{chunk-UHIOVOFK.cjs → chunk-XZCMUDFP.cjs} +12 -12
  82. package/dist/{chunk-UHIOVOFK.cjs.map → chunk-XZCMUDFP.cjs.map} +1 -1
  83. package/dist/{chunk-TRMWBJWT.cjs → chunk-Y4IDF5UO.cjs} +8 -8
  84. package/dist/{chunk-TRMWBJWT.cjs.map → chunk-Y4IDF5UO.cjs.map} +1 -1
  85. package/dist/{chunk-3FBBP5QR.cjs → chunk-YWJ5XJDU.cjs} +6 -6
  86. package/dist/{chunk-3FBBP5QR.cjs.map → chunk-YWJ5XJDU.cjs.map} +1 -1
  87. package/dist/{chunk-BGA2IVQY.js → chunk-ZDDBWUUA.js} +6 -6
  88. package/dist/{chunk-BGA2IVQY.js.map → chunk-ZDDBWUUA.js.map} +1 -1
  89. package/dist/components/ActivityPreview.cjs +17 -17
  90. package/dist/components/ActivityPreview.d.cts +2 -2
  91. package/dist/components/ActivityPreview.d.ts +2 -2
  92. package/dist/components/ActivityPreview.js +16 -16
  93. package/dist/components/ActivitySequence.cjs +18 -18
  94. package/dist/components/ActivitySequence.d.cts +22 -4
  95. package/dist/components/ActivitySequence.d.ts +22 -4
  96. package/dist/components/ActivitySequence.js +17 -17
  97. package/dist/components/Dictation.cjs +10 -10
  98. package/dist/components/Dictation.d.cts +1 -1
  99. package/dist/components/Dictation.d.ts +1 -1
  100. package/dist/components/Dictation.js +9 -9
  101. package/dist/components/FillInTheBlanks.cjs +10 -10
  102. package/dist/components/FillInTheBlanks.d.cts +1 -1
  103. package/dist/components/FillInTheBlanks.d.ts +1 -1
  104. package/dist/components/FillInTheBlanks.js +9 -9
  105. package/dist/components/GapSelect.cjs +10 -10
  106. package/dist/components/GapSelect.d.cts +1 -1
  107. package/dist/components/GapSelect.d.ts +1 -1
  108. package/dist/components/GapSelect.js +9 -9
  109. package/dist/components/InteractiveVideo.cjs +17 -17
  110. package/dist/components/InteractiveVideo.d.cts +19 -3
  111. package/dist/components/InteractiveVideo.d.ts +19 -3
  112. package/dist/components/InteractiveVideo.js +15 -15
  113. package/dist/components/MultipleChoice.cjs +10 -10
  114. package/dist/components/MultipleChoice.d.cts +1 -1
  115. package/dist/components/MultipleChoice.d.ts +1 -1
  116. package/dist/components/MultipleChoice.js +9 -9
  117. package/dist/components/PronunciationFeedback.cjs +4 -4
  118. package/dist/components/PronunciationFeedback.d.cts +1 -1
  119. package/dist/components/PronunciationFeedback.d.ts +1 -1
  120. package/dist/components/PronunciationFeedback.js +3 -3
  121. package/dist/components/ReadAloud.cjs +8 -8
  122. package/dist/components/ReadAloud.d.cts +2 -2
  123. package/dist/components/ReadAloud.d.ts +2 -2
  124. package/dist/components/ReadAloud.js +7 -7
  125. package/dist/components/StimulusPanel.cjs +4 -4
  126. package/dist/components/StimulusPanel.d.cts +1 -1
  127. package/dist/components/StimulusPanel.d.ts +1 -1
  128. package/dist/components/StimulusPanel.js +3 -3
  129. package/dist/components/WrittenResponse.cjs +7 -7
  130. package/dist/components/WrittenResponse.d.cts +3 -3
  131. package/dist/components/WrittenResponse.d.ts +3 -3
  132. package/dist/components/WrittenResponse.js +6 -6
  133. package/dist/i18n/LkIntlProvider.cjs +7 -7
  134. package/dist/i18n/LkIntlProvider.d.cts +2 -2
  135. package/dist/i18n/LkIntlProvider.d.ts +2 -2
  136. package/dist/i18n/LkIntlProvider.js +1 -1
  137. package/dist/index.cjs +41 -41
  138. package/dist/index.cjs.map +1 -1
  139. package/dist/index.d.cts +2 -2
  140. package/dist/index.d.ts +2 -2
  141. package/dist/index.js +19 -19
  142. package/dist/index.js.map +1 -1
  143. package/dist/theme/skin.css +55 -0
  144. package/dist/{types-C-GMCJ0g.d.ts → types-7lGlo0zE.d.ts} +48 -1
  145. package/dist/{types-B_PCcqO-.d.cts → types-Botf4Wqj.d.cts} +48 -1
  146. package/package.json +3 -3
  147. package/dist/chunk-3XP6VO4G.js.map +0 -1
  148. package/dist/chunk-5GI53C6T.js.map +0 -1
  149. package/dist/chunk-6S2CKZ4H.cjs.map +0 -1
  150. package/dist/chunk-AG7MWZYZ.js.map +0 -1
  151. package/dist/chunk-AHGB5HH6.cjs.map +0 -1
  152. package/dist/chunk-BJLQKJZX.cjs.map +0 -1
  153. package/dist/chunk-CEUEXISZ.cjs.map +0 -1
  154. package/dist/chunk-DZVF3IGB.cjs.map +0 -1
  155. package/dist/chunk-HI6BUUIJ.js.map +0 -1
  156. package/dist/chunk-HREA5SMB.cjs.map +0 -1
  157. package/dist/chunk-IQ55QNBI.js.map +0 -1
  158. package/dist/chunk-IXMT7X3E.cjs.map +0 -1
  159. package/dist/chunk-JLDSCHY5.cjs.map +0 -1
  160. package/dist/chunk-NOC2M6LU.cjs +0 -118
  161. package/dist/chunk-NOC2M6LU.cjs.map +0 -1
  162. package/dist/chunk-ORBU3XKH.js.map +0 -1
  163. package/dist/chunk-PWLJDP3X.cjs.map +0 -1
  164. package/dist/chunk-QKW2GGTV.js.map +0 -1
  165. package/dist/chunk-RUS42NMN.js +0 -115
  166. package/dist/chunk-RUS42NMN.js.map +0 -1
  167. package/dist/chunk-WBLYD5QA.js.map +0 -1
  168. package/dist/chunk-YRNEOBXA.js.map +0 -1
@@ -1,14 +1,15 @@
1
1
  'use client';
2
2
  'use strict';
3
3
 
4
- var chunkNOC2M6LU_cjs = require('./chunk-NOC2M6LU.cjs');
5
- var chunkZDYNAWDZ_cjs = require('./chunk-ZDYNAWDZ.cjs');
6
- var chunkIXMT7X3E_cjs = require('./chunk-IXMT7X3E.cjs');
7
- var chunkAPZ6JFN6_cjs = require('./chunk-APZ6JFN6.cjs');
4
+ var chunkCQ37UD4T_cjs = require('./chunk-CQ37UD4T.cjs');
5
+ var chunkMPHX3Y42_cjs = require('./chunk-MPHX3Y42.cjs');
6
+ var chunkM4UJBLVR_cjs = require('./chunk-M4UJBLVR.cjs');
7
+ var chunkJ2QIHZ3H_cjs = require('./chunk-J2QIHZ3H.cjs');
8
+ var chunk6SNNMVBV_cjs = require('./chunk-6SNNMVBV.cjs');
8
9
  var chunkEANGKAKG_cjs = require('./chunk-EANGKAKG.cjs');
9
- var chunkL7WY5YFQ_cjs = require('./chunk-L7WY5YFQ.cjs');
10
+ var chunk7JBXDQTD_cjs = require('./chunk-7JBXDQTD.cjs');
10
11
  var chunkXOR4MPEN_cjs = require('./chunk-XOR4MPEN.cjs');
11
- var chunkAHGB5HH6_cjs = require('./chunk-AHGB5HH6.cjs');
12
+ var chunkGHKMX5UT_cjs = require('./chunk-GHKMX5UT.cjs');
12
13
  var lkCore = require('@intellectif/lk-core');
13
14
  var react = require('react');
14
15
  var jsxRuntime = require('react/jsx-runtime');
@@ -73,13 +74,15 @@ function GapSelect({
73
74
  disabled,
74
75
  shuffleSeed,
75
76
  ai: aiProp,
76
- delivery
77
+ delivery,
78
+ scoring
77
79
  }) {
78
80
  const isExam = renderMode === "exam";
79
81
  const isReview = renderMode === "review";
80
- const s = chunkAHGB5HH6_cjs.useLkStrings(strings);
81
- const ai = chunkZDYNAWDZ_cjs.useLearnerAi(aiProp);
82
- const policy = chunkIXMT7X3E_cjs.useDeliveryPolicy(delivery);
82
+ const s = chunkGHKMX5UT_cjs.useLkStrings(strings);
83
+ const ai = chunkMPHX3Y42_cjs.useLearnerAi(aiProp);
84
+ const policy = chunkM4UJBLVR_cjs.useDeliveryPolicy(delivery);
85
+ const scoringPolicy = chunkCQ37UD4T_cjs.useItemScoringPolicy(scoring);
83
86
  const devError = react.useMemo(() => {
84
87
  if (!chunkEANGKAKG_cjs.isDevelopment()) {
85
88
  return null;
@@ -98,13 +101,25 @@ function GapSelect({
98
101
  const { state, start, complete, getTimeSpent, reset } = chunkXOR4MPEN_cjs.useActivityState(
99
102
  defaultSubmitted === true ? "completed" : "idle"
100
103
  );
104
+ const tries = chunkCQ37UD4T_cjs.useTries({
105
+ policy: scoringPolicy,
106
+ graded: renderMode === "practice" && policy.feedback,
107
+ submitted: state === "completed",
108
+ disabled: disabled === true
109
+ });
110
+ const resetTries = tries.reset;
101
111
  const isControlled = value !== void 0;
102
112
  const [internalSelections, setInternalSelections] = react.useState(
103
113
  () => selectionsOf(defaultValue)
104
114
  );
105
115
  const [result, setResult] = react.useState(null);
106
116
  const [summary, setSummary] = react.useState(null);
117
+ const [overall, setOverall] = react.useState(null);
107
118
  const [feedbackHidden, setFeedbackHidden] = react.useState(false);
119
+ const seedHintsRef = react.useRef(chunkCQ37UD4T_cjs.hintsOf(value ?? defaultValue));
120
+ const formRef = react.useRef(null);
121
+ const feedbackRef = react.useRef(null);
122
+ const focusAfterRef = react.useRef(null);
108
123
  const selections = isControlled ? selectionsOf(value) : internalSelections;
109
124
  const defaultValueRef = react.useRef(defaultValue);
110
125
  react.useEffect(() => {
@@ -123,9 +138,12 @@ function GapSelect({
123
138
  setInternalSelections(selectionsOf(defaultValueRef.current));
124
139
  setResult(null);
125
140
  setSummary(null);
141
+ setOverall(null);
126
142
  setFeedbackHidden(false);
143
+ seedHintsRef.current = chunkCQ37UD4T_cjs.hintsOf(defaultValueRef.current);
144
+ resetTries();
127
145
  reset(defaultSubmittedRef.current === true ? "completed" : "idle");
128
- }, [data, reset]);
146
+ }, [data, reset, resetTries]);
129
147
  const segments = react.useMemo(() => parsePassage(data.passage), [data.passage]);
130
148
  const gapById = react.useMemo(() => new Map(data.gaps.map((gap) => [gap.id, gap])), [data.gaps]);
131
149
  const sessionIdRef = react.useRef(null);
@@ -141,6 +159,47 @@ function GapSelect({
141
159
  }
142
160
  return map;
143
161
  }, [data, shuffleSeed]);
162
+ const answered = state === "completed";
163
+ const aiHints = chunkCQ37UD4T_cjs.useComponentAiHints({
164
+ ai,
165
+ data,
166
+ renderMode,
167
+ submitted: answered,
168
+ disabled: disabled === true,
169
+ response: { type: "gap-select", selections },
170
+ locale,
171
+ onInteraction,
172
+ strings: s,
173
+ delivery: policy
174
+ });
175
+ const hintsRevealed = renderMode === "practice" ? seedHintsRef.current + aiHints.used : 0;
176
+ const responseOf = (next) => ({
177
+ type: "gap-select",
178
+ selections: next,
179
+ ...hintsRevealed > 0 ? { hintsRevealed } : {}
180
+ });
181
+ const hintsSeenRef = react.useRef(aiHints.used);
182
+ react.useEffect(() => {
183
+ if (hintsSeenRef.current === aiHints.used) {
184
+ return;
185
+ }
186
+ hintsSeenRef.current = aiHints.used;
187
+ if (aiHints.used > 0) {
188
+ onChange?.(responseOf(selections));
189
+ }
190
+ }, [aiHints.used]);
191
+ react.useEffect(() => {
192
+ const target = focusAfterRef.current;
193
+ if (target === null) {
194
+ return;
195
+ }
196
+ focusAfterRef.current = null;
197
+ if (target === "answer") {
198
+ formRef.current?.querySelector("select:not(:disabled)")?.focus();
199
+ } else {
200
+ feedbackRef.current?.focus();
201
+ }
202
+ });
144
203
  if (devError) {
145
204
  throw devError;
146
205
  }
@@ -167,7 +226,7 @@ function GapSelect({
167
226
  if (!isControlled) {
168
227
  setInternalSelections(next);
169
228
  }
170
- onChange?.({ type: "gap-select", selections: next });
229
+ onChange?.(responseOf(next));
171
230
  fireInteraction("gap-selected", { gapId, choiceId });
172
231
  };
173
232
  const handleSubmit = (event) => {
@@ -175,7 +234,7 @@ function GapSelect({
175
234
  if (inactive) {
176
235
  return;
177
236
  }
178
- const response = { type: "gap-select", selections };
237
+ const response = responseOf(selections);
179
238
  onSubmit?.(response);
180
239
  if (isExam) {
181
240
  complete();
@@ -183,9 +242,18 @@ function GapSelect({
183
242
  fireInteraction("submitted", { selections });
184
243
  return;
185
244
  }
186
- const scoringResult = lkCore.score("gap-select", data, response);
245
+ const answer = lkCore.score("gap-select", data, response);
187
246
  complete();
188
247
  const timeSpent = getTimeSpent();
248
+ const counted = tries.record({
249
+ score: answer.score,
250
+ maxScore: answer.maxScore,
251
+ hintsRevealed
252
+ });
253
+ const mine = counted.tries[counted.tries.length - 1];
254
+ const costed = mine === void 0 ? answer.score : mine.scored;
255
+ const scoringResult = costed === answer.score ? answer : { ...answer, score: costed, passed: lkCore.computePassThreshold(data, costed) };
256
+ const passed = counted.score === scoringResult.score ? scoringResult.passed : lkCore.computePassThreshold(data, counted.score);
189
257
  const xapiStatement = lkCore.xAPIBuilder.buildAnsweredStatement({
190
258
  actor: chunkEANGKAKG_cjs.ANONYMOUS_ACTOR,
191
259
  object: {
@@ -197,23 +265,29 @@ function GapSelect({
197
265
  timeSpentMs: timeSpent,
198
266
  response: JSON.stringify(selections)
199
267
  });
200
- setResult(scoringResult);
201
- onComplete?.({
202
- score: scoringResult.score,
203
- maxScore: scoringResult.maxScore,
204
- passed: scoringResult.passed,
205
- timeSpent,
206
- xapiStatement
207
- });
208
- const overall = scoringResult.feedback;
268
+ setResult(answer);
269
+ onComplete?.(
270
+ chunkJ2QIHZ3H_cjs.stampTake(
271
+ { score: counted.score, maxScore: counted.maxScore, passed, timeSpent, xapiStatement },
272
+ chunkJ2QIHZ3H_cjs.mintTake()
273
+ )
274
+ );
275
+ setOverall(answer.feedback);
209
276
  setSummary(
210
- `${s.answerSubmitted} ${s.scoreAnnouncement(
211
- Math.round(scoringResult.score * 100),
212
- scoringResult.passed
213
- )}${overall ? ` ${overall}` : ""}`
277
+ `${s.answerSubmitted} ${s.scoreAnnouncement(Math.round(counted.score * 100), passed)}`
214
278
  );
215
279
  fireInteraction("submitted", { selections, score: scoringResult.score });
216
280
  };
281
+ const retry = () => {
282
+ reset("idle");
283
+ setSummary(null);
284
+ setOverall(null);
285
+ focusAfterRef.current = "answer";
286
+ };
287
+ const closeTries = () => {
288
+ tries.close();
289
+ focusAfterRef.current = "feedback";
290
+ };
217
291
  const correctByGap = react.useMemo(() => {
218
292
  if (isExam) {
219
293
  return /* @__PURE__ */ new Map();
@@ -240,9 +314,12 @@ function GapSelect({
240
314
  return s.noGradeAvailable;
241
315
  }, [isReview, outcome, s]);
242
316
  const anyFeedback = data.gaps.some((gap) => gap.feedback !== void 0);
317
+ const extra = !isReview && policy.feedback && submitted && tries.counted !== null ? chunkCQ37UD4T_cjs.triesSummary(s, scoringPolicy, tries.counted, tries.open) : "";
318
+ const announced = summary === null ? null : `${summary}${extra ? ` ${extra}` : ""}${overall ? ` ${overall}` : ""}`;
243
319
  return /* @__PURE__ */ jsxRuntime.jsxs(
244
320
  "form",
245
321
  {
322
+ ref: formRef,
246
323
  className: "lk-gs",
247
324
  "aria-label": data.title,
248
325
  lang: locale,
@@ -251,7 +328,7 @@ function GapSelect({
251
328
  onSubmit: handleSubmit,
252
329
  children: [
253
330
  data.media ? /* @__PURE__ */ jsxRuntime.jsx(
254
- chunkL7WY5YFQ_cjs.ActivityMedia,
331
+ chunk7JBXDQTD_cjs.ActivityMedia,
255
332
  {
256
333
  media: data.media,
257
334
  renderMode,
@@ -302,21 +379,8 @@ function GapSelect({
302
379
  ) : null
303
380
  ] }, seg.id);
304
381
  }) }),
305
- /* @__PURE__ */ jsxRuntime.jsx(
306
- chunkNOC2M6LU_cjs.AiHints,
307
- {
308
- ai,
309
- data,
310
- renderMode,
311
- submitted,
312
- disabled: disabled === true,
313
- response: { type: "gap-select", selections },
314
- locale,
315
- onInteraction,
316
- strings: s,
317
- delivery: policy
318
- }
319
- ),
382
+ aiHints.offered && renderMode === "practice" ? /* @__PURE__ */ jsxRuntime.jsx(chunkCQ37UD4T_cjs.HintCost, { policy: scoringPolicy, strings: s }) : null,
383
+ /* @__PURE__ */ jsxRuntime.jsx(chunkCQ37UD4T_cjs.AiHints, { help: aiHints, ai, strings: s }),
320
384
  isReview ? null : /* @__PURE__ */ jsxRuntime.jsx("button", { type: "submit", disabled: inactive, children: isExam ? s.submitAnswers : s.checkAnswers })
321
385
  ] }),
322
386
  marking && anyFeedback ? /* @__PURE__ */ jsxRuntime.jsx(
@@ -329,9 +393,26 @@ function GapSelect({
329
393
  children: feedbackHidden ? s.showFeedback : s.hideFeedback
330
394
  }
331
395
  ) : null,
332
- /* @__PURE__ */ jsxRuntime.jsx(chunkIXMT7X3E_cjs.FeedbackRegion, { id: `${data.id}-feedback`, children: isReview ? policy.feedback || !chunkIXMT7X3E_cjs.outcomeShowsMarks(outcome) ? reviewSummary : null : policy.feedback || summary === null ? summary : s.answerSubmitted }),
333
396
  /* @__PURE__ */ jsxRuntime.jsx(
334
- chunkNOC2M6LU_cjs.AiExplanation,
397
+ chunkM4UJBLVR_cjs.FeedbackRegion,
398
+ {
399
+ id: `${data.id}-feedback`,
400
+ ...scoringPolicy.retries > 0 ? { ref: feedbackRef } : {},
401
+ children: isReview ? policy.feedback || !chunkM4UJBLVR_cjs.outcomeShowsMarks(outcome) ? reviewSummary : null : policy.feedback || summary === null ? announced : s.answerSubmitted
402
+ }
403
+ ),
404
+ /* @__PURE__ */ jsxRuntime.jsx(
405
+ chunkCQ37UD4T_cjs.TryActions,
406
+ {
407
+ tries,
408
+ solutions: false,
409
+ strings: s,
410
+ onRetry: retry,
411
+ onClose: closeTries
412
+ }
413
+ ),
414
+ /* @__PURE__ */ jsxRuntime.jsx(
415
+ chunkCQ37UD4T_cjs.AiExplanation,
335
416
  {
336
417
  ai,
337
418
  data,
@@ -342,7 +423,7 @@ function GapSelect({
342
423
  locale,
343
424
  onInteraction,
344
425
  strings: s,
345
- delivery: policy
426
+ delivery: tries.open ? { ...policy, solutions: false } : policy
346
427
  }
347
428
  )
348
429
  ]
@@ -350,10 +431,10 @@ function GapSelect({
350
431
  );
351
432
  }
352
433
  function GapSelect2(props) {
353
- const strings = chunkAHGB5HH6_cjs.useLkStrings(props.strings);
354
- return /* @__PURE__ */ jsxRuntime.jsx(chunkAPZ6JFN6_cjs.ActivityErrorBoundary, { activityTitle: props.data?.title, strings, children: /* @__PURE__ */ jsxRuntime.jsx(GapSelect, { ...props }) });
434
+ const strings = chunkGHKMX5UT_cjs.useLkStrings(props.strings);
435
+ return /* @__PURE__ */ jsxRuntime.jsx(chunk6SNNMVBV_cjs.ActivityErrorBoundary, { activityTitle: props.data?.title, strings, children: /* @__PURE__ */ jsxRuntime.jsx(GapSelect, { ...props }) });
355
436
  }
356
437
 
357
438
  exports.GapSelect = GapSelect2;
358
- //# sourceMappingURL=chunk-CEUEXISZ.cjs.map
359
- //# sourceMappingURL=chunk-CEUEXISZ.cjs.map
439
+ //# sourceMappingURL=chunk-CB2A56Q7.cjs.map
440
+ //# sourceMappingURL=chunk-CB2A56Q7.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/GapSelect/GapSelect.tsx","../src/components/GapSelect/index.tsx"],"names":["useLkStrings","useLearnerAi","useDeliveryPolicy","useItemScoringPolicy","useMemo","isDevelopment","assertRedacted","result","validateActivity","ActivitySchemaError","useActivityState","useTries","useState","useRef","hintsOf","useEffect","randomSessionId","seededShuffle","useComponentAiHints","score","computePassThreshold","xAPIBuilder","ANONYMOUS_ACTOR","objectIdFor","xapiDefinitionFor","stampTake","mintTake","triesSummary","jsxs","jsx","ActivityMedia","HintCost","AiHints","FeedbackRegion","outcomeShowsMarks","TryActions","AiExplanation","GapSelect","ActivityErrorBoundary"],"mappings":"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","file":"chunk-CB2A56Q7.cjs","sourcesContent":["'use client';\n\nimport {\n ActivitySchemaError,\n assertRedacted,\n computePassThreshold,\n type GapSelectBank,\n type GapSelectChoice,\n type GapSelectData,\n type GapSelectGap,\n type GapSelectLearnerResponse,\n type LearnerResponse,\n type ScoringDetail,\n type ScoringResult,\n score,\n seededShuffle,\n validateActivity,\n xAPIBuilder,\n xapiDefinitionFor,\n} from '@intellectif/lk-core';\nimport { type CSSProperties, useEffect, useMemo, useRef, useState } from 'react';\nimport { useLearnerAi } from '../../ai/LkAiProvider.js';\nimport { useActivityState } from '../../hooks/useActivityState.js';\nimport { useLkStrings } from '../../i18n/LkIntlProvider.js';\nimport { ANONYMOUS_ACTOR, isDevelopment, objectIdFor, randomSessionId } from '../_internal.js';\nimport { ActivityMedia } from '../shared/ActivityMedia.js';\nimport { AiExplanation, AiHints, useComponentAiHints } from '../shared/AiHelp.js';\nimport { outcomeShowsMarks, useDeliveryPolicy } from '../shared/delivery.js';\nimport { FeedbackRegion } from '../shared/FeedbackRegion.js';\nimport { mintTake, stampTake } from '../shared/sequence-slot.js';\nimport {\n HintCost,\n hintsOf,\n TryActions,\n triesSummary,\n useItemScoringPolicy,\n useTries,\n} from '../shared/tries.js';\nimport type { ActivityProps } from '../types.js';\n\nexport interface GapSelectProps extends ActivityProps<GapSelectData> {\n /**\n * Seeds the per-gap choice shuffle when `data.shuffleChoices` is set. Pass\n * the attempt id, exactly as on `<MultipleChoice>`: an order nobody can\n * reproduce cannot be reconciled with the attempt a server recorded. Without\n * one, a random per-mount seed is used — stable within the mount, and\n * deliberately not reproducible.\n */\n shuffleSeed?: string;\n}\n\nconst PLACEHOLDER = /\\{\\{\\s*([^{}]+?)\\s*\\}\\}/g;\n\ntype Segment = { kind: 'text'; value: string } | { kind: 'gap'; id: string; ordinal: number };\n\n/** Splits a passage into literal text and its `{{id}}` gaps, in order. */\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: 'gap', 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\nconst NO_SELECTIONS: Record<string, string> = {};\n\n/**\n * The selections out of a `LearnerResponse`. A response of another activity\n * type reads as \"nothing selected\" rather than throwing — a controlled host\n * that has not yet swapped its state on an activity change would otherwise\n * crash the attempt.\n */\nfunction selectionsOf(response: LearnerResponse | undefined): Record<string, string> {\n return response !== undefined && response.type === 'gap-select'\n ? response.selections\n : NO_SELECTIONS;\n}\n\n/** Per-gap correctness keyed by gap id, preferring the unambiguous `outcome`. */\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\n/**\n * The choices a gap offers: its own list, or the bank it names.\n *\n * Both survive redaction — they are what the learner picks from — so this is\n * the one resolution that works identically on full and redacted data.\n */\nfunction choicesFor(\n banks: readonly GapSelectBank[] | undefined,\n gap: GapSelectGap,\n): readonly GapSelectChoice[] {\n if (gap.choices !== undefined) {\n return gap.choices;\n }\n return banks?.find((bank) => bank.id === gap.bankId)?.choices ?? [];\n}\n\nexport function GapSelect({\n data,\n onComplete,\n onSubmit,\n onChange,\n onInteraction,\n value,\n defaultValue,\n defaultSubmitted,\n renderMode = 'practice',\n outcome,\n mediaBudget,\n mediaStrings,\n strings,\n theme,\n locale,\n disabled,\n shuffleSeed,\n ai: aiProp,\n delivery,\n scoring,\n}: GapSelectProps) {\n const isExam = renderMode === 'exam';\n const isReview = renderMode === 'review';\n const s = useLkStrings(strings);\n const ai = useLearnerAi(aiProp);\n const policy = useDeliveryPolicy(delivery);\n const scoringPolicy = useItemScoringPolicy(scoring);\n\n const devError = useMemo(() => {\n if (!isDevelopment()) {\n return null;\n }\n if (data.redacted === true) {\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('gap-select', data);\n return result.success ? null : new ActivitySchemaError('gap-select', result.errors);\n }, [data]);\n\n const { state, start, complete, getTimeSpent, reset } = useActivityState(\n defaultSubmitted === true ? 'completed' : 'idle',\n );\n // Before anything that resets them: a new question starts with no tries.\n const tries = useTries({\n policy: scoringPolicy,\n graded: renderMode === 'practice' && policy.feedback,\n submitted: state === 'completed',\n disabled: disabled === true,\n });\n const resetTries = tries.reset;\n const isControlled = value !== undefined;\n const [internalSelections, setInternalSelections] = useState<Record<string, string>>(() =>\n selectionsOf(defaultValue),\n );\n const [result, setResult] = useState<ScoringResult | null>(null);\n const [summary, setSummary] = useState<string | null>(null);\n const [overall, setOverall] = useState<string | null>(null);\n const [feedbackHidden, setFeedbackHidden] = useState(false);\n // Hints the learner had been shown before this mount: a restored answer's\n // count stands, and the hints shown here add to it.\n const seedHintsRef = useRef(hintsOf(value ?? defaultValue));\n const formRef = useRef<HTMLFormElement>(null);\n const feedbackRef = useRef<HTMLDivElement>(null);\n const focusAfterRef = useRef<'answer' | 'feedback' | null>(null);\n\n const selections = isControlled ? selectionsOf(value) : internalSelections;\n\n const defaultValueRef = useRef(defaultValue);\n useEffect(() => {\n defaultValueRef.current = defaultValue;\n }, [defaultValue]);\n\n const defaultSubmittedRef = useRef(defaultSubmitted);\n useEffect(() => {\n defaultSubmittedRef.current = defaultSubmitted;\n }, [defaultSubmitted]);\n\n // Identity-guarded so the mount run is a no-op: without it this fires after\n // the first paint and undoes every seed it was just given (Req 3.7).\n const lastDataRef = useRef(data);\n useEffect(() => {\n if (lastDataRef.current === data) {\n return;\n }\n lastDataRef.current = data;\n setInternalSelections(selectionsOf(defaultValueRef.current));\n setResult(null);\n setSummary(null);\n setOverall(null);\n setFeedbackHidden(false);\n seedHintsRef.current = hintsOf(defaultValueRef.current);\n resetTries();\n reset(defaultSubmittedRef.current === true ? 'completed' : 'idle');\n }, [data, reset, resetTries]);\n\n const segments = useMemo(() => parsePassage(data.passage), [data.passage]);\n const gapById = useMemo(() => new Map(data.gaps.map((gap) => [gap.id, gap])), [data.gaps]);\n\n const sessionIdRef = useRef<string | null>(null);\n /**\n * The choices each gap shows, in the order it shows them.\n *\n * Seeded PER GAP, not once for the activity: two gaps drawing on the same\n * word bank must not be dealt the same order, or the bank's distractors line\n * up column-wise and the second gap becomes easier than the first.\n */\n const choicesByGap = useMemo(() => {\n const map = new Map<string, readonly GapSelectChoice[]>();\n // biome-ignore lint/suspicious/noAssignInExpressions: sanctioned lazy ref initialization\n const seedSource = shuffleSeed ?? (sessionIdRef.current ??= randomSessionId());\n for (const gap of data.gaps) {\n const choices = choicesFor(data.banks, gap);\n map.set(\n gap.id,\n data.shuffleChoices === true\n ? seededShuffle(choices, `${seedSource}:${data.id}:${gap.id}`)\n : choices,\n );\n }\n return map;\n }, [data, shuffleSeed]);\n\n // Hints are a hook, so they are read here, before the throws below.\n const answered = state === 'completed';\n const aiHints = useComponentAiHints({\n ai,\n data,\n renderMode,\n submitted: answered,\n disabled: disabled === true,\n response: { type: 'gap-select', selections },\n locale,\n onInteraction,\n strings: s,\n delivery: policy,\n });\n // Every hint the learner has been shown on this question, counted from its\n // start: what a scoring policy charges for. Hints exist only in practice.\n const hintsRevealed = renderMode === 'practice' ? seedHintsRef.current + aiHints.used : 0;\n const responseOf = (next: Record<string, string>): GapSelectLearnerResponse => ({\n type: 'gap-select',\n selections: next,\n ...(hintsRevealed > 0 ? { hintsRevealed } : {}),\n });\n // A hint shown is part of the answer's record: say so as it happens.\n const hintsSeenRef = useRef(aiHints.used);\n // biome-ignore lint/correctness/useExhaustiveDependencies: a new hint is the trigger, and the response it reports is this render's\n useEffect(() => {\n if (hintsSeenRef.current === aiHints.used) {\n return;\n }\n hintsSeenRef.current = aiHints.used;\n if (aiHints.used > 0) {\n onChange?.(responseOf(selections));\n }\n }, [aiHints.used]);\n useEffect(() => {\n const target = focusAfterRef.current;\n if (target === null) {\n return;\n }\n focusAfterRef.current = null;\n if (target === 'answer') {\n formRef.current?.querySelector<HTMLElement>('select:not(:disabled)')?.focus();\n } else {\n feedbackRef.current?.focus();\n }\n });\n\n // All hooks are called before these throws, so hook order stays stable.\n if (devError) {\n throw devError;\n }\n // Practice grades locally, and a redacted projection has no answer key to\n // grade against — `score()` would throw RedactedScoringError from the submit\n // handler, where no error boundary can reach it and after the learner has\n // answered. Fail at render instead, in production too, exactly as every other\n // built-in does.\n if (data.redacted === true && renderMode === 'practice') {\n throw new Error(\n `Gap Select \"${data.id}\" received redacted activity data in renderMode \"practice\", ` +\n 'which grades locally and has no answer key to grade against. ' +\n 'Render redacted data with renderMode=\"exam\" (server grades) or \"review\" (pass `outcome`).',\n );\n }\n\n const submitted = state === 'completed';\n const inactive = disabled === true || submitted || isReview;\n\n /**\n * Whether answer-key-derived information may be shown. Never in `exam`; in\n * `practice` only once this component has scored; in `review` only once the\n * caller supplies a SCORED outcome.\n */\n const revealing = isReview ? outcome?.status === 'scored' : !isExam && submitted;\n // What the delivery policy lets that reveal show: right and wrong, and the\n // author's feedback. A gap select never writes in the right choice, so\n // `solutions` has nothing here to hide.\n const marking = revealing && policy.feedback;\n\n const fireInteraction = (\n type: 'gap-selected' | 'submitted',\n payload: Record<string, unknown>,\n ): void => {\n onInteraction?.({ type, activityId: data.id, timestamp: Date.now(), payload });\n };\n\n const handleSelect = (gapId: string, choiceId: string): void => {\n if (inactive) {\n return;\n }\n if (state === 'idle') {\n start();\n }\n const next = { ...selections, [gapId]: choiceId };\n if (!isControlled) {\n setInternalSelections(next);\n }\n onChange?.(responseOf(next));\n fireInteraction('gap-selected', { gapId, choiceId });\n };\n\n const handleSubmit = (event: React.FormEvent): void => {\n event.preventDefault();\n if (inactive) {\n return;\n }\n const response = responseOf(selections);\n onSubmit?.(response);\n\n if (isExam) {\n // The client neither grades nor reveals: no score(), no read of any\n // answer-key field, no onComplete, and no xAPI statement — its\n // correctResponsesPattern IS the answer key.\n complete();\n setSummary(s.answerSubmitted);\n fireInteraction('submitted', { selections });\n return;\n }\n\n const answer = score('gap-select', data as GapSelectData, response);\n complete();\n const timeSpent = getTimeSpent();\n // The question's score under the policy — see MultipleChoice.\n const counted = tries.record({\n score: answer.score,\n maxScore: answer.maxScore,\n hintsRevealed,\n });\n const mine = counted.tries[counted.tries.length - 1];\n const costed = mine === undefined ? answer.score : mine.scored;\n const scoringResult =\n costed === answer.score\n ? answer\n : { ...answer, score: costed, passed: computePassThreshold(data as GapSelectData, costed) };\n const passed =\n counted.score === scoringResult.score\n ? scoringResult.passed\n : computePassThreshold(data as GapSelectData, counted.score);\n const xapiStatement = xAPIBuilder.buildAnsweredStatement({\n actor: ANONYMOUS_ACTOR,\n object: {\n id: objectIdFor(data.id),\n name: { [data.locale ?? 'en-US']: data.title },\n ...xapiDefinitionFor(data),\n },\n scoringResult,\n timeSpentMs: timeSpent,\n response: JSON.stringify(selections),\n });\n setResult(answer);\n onComplete?.(\n stampTake(\n { score: counted.score, maxScore: counted.maxScore, passed, timeSpent, xapiStatement },\n mintTake(),\n ),\n );\n setOverall(answer.feedback);\n setSummary(\n `${s.answerSubmitted} ${s.scoreAnnouncement(Math.round(counted.score * 100), passed)}`,\n );\n fireInteraction('submitted', { selections, score: scoringResult.score });\n };\n\n /** \"Try again\": the choices stay, to be changed; the marks go. */\n const retry = (): void => {\n reset('idle');\n setSummary(null);\n setOverall(null);\n focusAfterRef.current = 'answer';\n };\n\n /** \"Keep this answer\": no more tries. A gap select never writes in the right choice. */\n const closeTries = (): void => {\n tries.close();\n focusAfterRef.current = 'feedback';\n };\n\n const correctByGap = useMemo(() => {\n if (isExam) {\n return new Map<string, boolean>();\n }\n if (isReview) {\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 const reviewSummary = useMemo(() => {\n if (!isReview || outcome === undefined) {\n return null;\n }\n if (outcome.status === 'scored') {\n return `${s.scoreAnnouncement(Math.round(outcome.score * 100), outcome.passed)}${\n outcome.feedback ? ` ${outcome.feedback}` : ''\n }`;\n }\n if (outcome.status === 'deferred') {\n return s.notGradedYet;\n }\n if (outcome.status === 'graded') {\n const percent =\n outcome.maxScore > 0\n ? Math.round((outcome.score / outcome.maxScore) * 100)\n : Math.round(outcome.score * 100);\n return `${s.scoreAnnouncement(percent, outcome.passed)}${\n outcome.feedback ? ` ${outcome.feedback}` : ''\n }`;\n }\n return s.noGradeAvailable;\n }, [isReview, outcome, s]);\n\n const anyFeedback = data.gaps.some((gap) => gap.feedback !== undefined);\n const extra =\n !isReview && policy.feedback && submitted && tries.counted !== null\n ? triesSummary(s, scoringPolicy, tries.counted, tries.open)\n : '';\n const announced =\n summary === null\n ? null\n : `${summary}${extra ? ` ${extra}` : ''}${overall ? ` ${overall}` : ''}`;\n\n return (\n <form\n ref={formRef}\n className=\"lk-gs\"\n aria-label={data.title}\n lang={locale}\n data-render-mode={renderMode}\n style={theme as CSSProperties | undefined}\n onSubmit={handleSubmit}\n >\n {data.media ? (\n <ActivityMedia\n media={data.media}\n renderMode={renderMode}\n {...(mediaBudget !== undefined ? { mediaBudget } : {})}\n {...(mediaStrings !== undefined ? { mediaStrings } : {})}\n {...(strings !== undefined ? { strings } : {})}\n {...(onInteraction !== undefined ? { onInteraction } : {})}\n {...(locale !== undefined ? { locale } : {})}\n />\n ) : null}\n <fieldset disabled={inactive}>\n <p className=\"lk-gs-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 gap = gapById.get(seg.id);\n if (!gap) {\n return null;\n }\n const choices = choicesByGap.get(seg.id) ?? [];\n const correctness = correctByGap.get(seg.id);\n // Read defensively and never in exam: a redact() projection omits\n // both at runtime while the type still says they are there.\n const gapFeedback = isExam ? undefined : gap.feedback;\n return (\n <span key={seg.id} className=\"lk-gs-gap\">\n <select\n className=\"lk-gs-select\"\n aria-label={s.gapLabel(seg.ordinal)}\n value={selections[seg.id] ?? ''}\n disabled={inactive}\n aria-disabled={inactive || undefined}\n data-correct={\n marking && correctness !== undefined ? String(correctness) : undefined\n }\n onChange={(e) => handleSelect(seg.id, e.target.value)}\n >\n {/*\n The empty entry is part of the contract, not a decoration.\n It is how a learner leaves a gap alone — and how they take\n an answer back — so \"not answered\" stays distinguishable\n from \"answered wrongly\" all the way into ItemOutcome, where\n the scorer reports it as `incorrect-omission`.\n */}\n <option value=\"\">{s.gapPlaceholder}</option>\n {choices.map((choice) => (\n <option key={choice.id} value={choice.id}>\n {choice.text}\n </option>\n ))}\n </select>\n {marking && !feedbackHidden && gapFeedback ? (\n <span\n className=\"lk-gs-gap-feedback\"\n role=\"note\"\n data-correct={correctness !== undefined ? String(correctness) : undefined}\n >\n {gapFeedback}\n </span>\n ) : null}\n </span>\n );\n })}\n </p>\n {aiHints.offered && renderMode === 'practice' ? (\n <HintCost policy={scoringPolicy} strings={s} />\n ) : null}\n <AiHints help={aiHints} ai={ai} strings={s} />\n {isReview ? null : (\n <button type=\"submit\" disabled={inactive}>\n {isExam ? s.submitAnswers : s.checkAnswers}\n </button>\n )}\n </fieldset>\n {marking && anyFeedback ? (\n <button\n type=\"button\"\n className=\"lk-gs-feedback-toggle\"\n aria-expanded={!feedbackHidden}\n onClick={() => setFeedbackHidden((h) => !h)}\n >\n {feedbackHidden ? s.showFeedback : s.hideFeedback}\n </button>\n ) : null}\n <FeedbackRegion\n id={`${data.id}-feedback`}\n {...(scoringPolicy.retries > 0 ? { ref: feedbackRef } : {})}\n >\n {isReview\n ? policy.feedback || !outcomeShowsMarks(outcome)\n ? reviewSummary\n : null\n : policy.feedback || summary === null\n ? announced\n : s.answerSubmitted}\n </FeedbackRegion>\n <TryActions\n tries={tries}\n solutions={false}\n strings={s}\n onRetry={retry}\n onClose={closeTries}\n />\n <AiExplanation\n ai={ai}\n data={data}\n renderMode={renderMode}\n submitted={submitted}\n response={{ type: 'gap-select', selections }}\n outcome={outcome}\n locale={locale}\n onInteraction={onInteraction}\n strings={s}\n // An explanation all but always names the answer: not while another\n // try is on offer.\n delivery={tries.open ? { ...policy, solutions: false } : policy}\n />\n </form>\n );\n}\n","'use client';\n\nimport { useLkStrings } from '../../i18n/LkIntlProvider.js';\nimport { ActivityErrorBoundary } from '../ActivityErrorBoundary.js';\nimport { GapSelect as GapSelectCore, type GapSelectProps } from './GapSelect.js';\n\nexport type { GapSelectProps } from './GapSelect.js';\n\n/**\n * Public Gap Select activity: the core wrapped in `ActivityErrorBoundary` so a\n * render failure (including the dev schema-validation throw and the\n * redacted-in-`practice` guard) degrades to an accessible fallback instead of\n * crashing the host.\n */\nexport function GapSelect(props: GapSelectProps) {\n const strings = useLkStrings(props.strings);\n return (\n <ActivityErrorBoundary activityTitle={props.data?.title} strings={strings}>\n <GapSelectCore {...props} />\n </ActivityErrorBoundary>\n );\n}\n"]}
@@ -0,0 +1,251 @@
1
+ 'use client';
2
+ 'use strict';
3
+
4
+ var chunkY4IDF5UO_cjs = require('./chunk-Y4IDF5UO.cjs');
5
+ var chunkJ2QIHZ3H_cjs = require('./chunk-J2QIHZ3H.cjs');
6
+ var react = require('react');
7
+ var jsxRuntime = require('react/jsx-runtime');
8
+ var lkCore = require('@intellectif/lk-core');
9
+
10
+ function situation(input) {
11
+ const { data, response, submitted, renderMode, ai, locale, onInteraction, delivery } = input;
12
+ return {
13
+ data,
14
+ response,
15
+ submitted,
16
+ renderMode,
17
+ delivery,
18
+ ...ai !== void 0 ? { ai } : {},
19
+ ...locale !== void 0 ? { locale } : {},
20
+ ...onInteraction !== void 0 ? { onInteraction } : {}
21
+ };
22
+ }
23
+ function AiExplanation({
24
+ outcome,
25
+ ...rest
26
+ }) {
27
+ const s = rest.strings;
28
+ const help = chunkY4IDF5UO_cjs.useAiExplanation({
29
+ ...situation(rest),
30
+ ...outcome !== void 0 ? { outcome } : {}
31
+ });
32
+ const buttonRef = react.useRef(null);
33
+ const panelRef = react.useRef(null);
34
+ const refocus = react.useRef(false);
35
+ react.useEffect(() => {
36
+ if (help.status === "shown" && refocus.current) {
37
+ refocus.current = false;
38
+ panelRef.current?.focus();
39
+ }
40
+ if (help.status === "unavailable") {
41
+ refocus.current = false;
42
+ }
43
+ }, [help.status]);
44
+ if (!help.offered) {
45
+ return null;
46
+ }
47
+ const ask = () => {
48
+ refocus.current = typeof document !== "undefined" && document.activeElement === buttonRef.current;
49
+ help.ask();
50
+ };
51
+ const loading = help.status === "loading";
52
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "lk-ai", "data-state": help.status, children: [
53
+ help.explanation === null ? /* @__PURE__ */ jsxRuntime.jsx(
54
+ "button",
55
+ {
56
+ ref: buttonRef,
57
+ type: "button",
58
+ className: "lk-ai-button",
59
+ "aria-busy": loading || void 0,
60
+ "aria-disabled": loading || void 0,
61
+ onClick: ask,
62
+ children: loading ? s.aiExplaining : s.aiExplain
63
+ }
64
+ ) : null,
65
+ /* @__PURE__ */ jsxRuntime.jsx("div", { "aria-live": "polite", children: help.explanation !== null ? /* @__PURE__ */ jsxRuntime.jsxs(
66
+ "section",
67
+ {
68
+ ref: panelRef,
69
+ className: "lk-ai-panel",
70
+ tabIndex: -1,
71
+ "aria-label": s.aiExplanation,
72
+ children: [
73
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "lk-ai-heading", children: s.aiExplanation }),
74
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "lk-ai-text", children: help.explanation.text }),
75
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "lk-ai-notice", children: s.aiNotice })
76
+ ]
77
+ }
78
+ ) : help.status === "unavailable" ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "lk-ai-unavailable", children: s.aiExplanationUnavailable }) : null })
79
+ ] });
80
+ }
81
+ function useComponentAiHints({
82
+ disabled,
83
+ ...rest
84
+ }) {
85
+ return chunkY4IDF5UO_cjs.useAiHints({ ...situation(rest), disabled });
86
+ }
87
+ function AiHints({
88
+ help,
89
+ ai,
90
+ strings: s
91
+ }) {
92
+ const used = help.used;
93
+ if (ai?.hint === void 0 || !help.offered && used === 0) {
94
+ return null;
95
+ }
96
+ const loading = help.status === "loading";
97
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "lk-ai lk-ai-hint-area", "data-state": help.status, children: [
98
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { "aria-live": "polite", children: [
99
+ used > 0 ? /* @__PURE__ */ jsxRuntime.jsx("ol", { className: "lk-ai-hints", "aria-label": s.aiHints, children: help.hints.map((hint, index) => (
100
+ // Hints are only ever appended, so a position is a stable key.
101
+ // biome-ignore lint/suspicious/noArrayIndexKey: an append-only list
102
+ /* @__PURE__ */ jsxRuntime.jsxs("li", { className: "lk-ai-hint", children: [
103
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "lk-ai-hint-label", children: s.aiHintNumber(index + 1) }),
104
+ " ",
105
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "lk-ai-text", children: hint.text })
106
+ ] }, index)
107
+ )) }) : null,
108
+ help.status === "unavailable" ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "lk-ai-unavailable", children: s.aiHintUnavailable }) : null
109
+ ] }),
110
+ help.offered ? used < help.limit ? /* @__PURE__ */ jsxRuntime.jsx(
111
+ "button",
112
+ {
113
+ type: "button",
114
+ className: "lk-ai-button",
115
+ "aria-busy": loading || void 0,
116
+ "aria-disabled": loading || void 0,
117
+ onClick: help.ask,
118
+ children: loading ? s.aiHintLoading : s.aiHint
119
+ }
120
+ ) : /* @__PURE__ */ jsxRuntime.jsx("p", { className: "lk-ai-note", children: s.aiNoMoreHints }) : null,
121
+ used > 0 ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "lk-ai-notice", children: s.aiNotice }) : null
122
+ ] });
123
+ }
124
+ function useItemScoringPolicy(given) {
125
+ const around = react.useContext(chunkJ2QIHZ3H_cjs.SequenceSlotContext)?.scoring;
126
+ const key = policyKey(given);
127
+ const own = react.useMemo(
128
+ () => around === void 0 ? lkCore.resolveItemScoringPolicy(given) : null,
129
+ [around, key]
130
+ );
131
+ return around ?? own;
132
+ }
133
+ function policyKey(given) {
134
+ try {
135
+ return JSON.stringify(given) ?? "";
136
+ } catch {
137
+ return String(given);
138
+ }
139
+ }
140
+ function hintsOf(response) {
141
+ const count = response?.hintsRevealed;
142
+ return typeof count === "number" && Number.isSafeInteger(count) && count > 0 ? count : 0;
143
+ }
144
+ var fullMarks = (one) => one.score / one.maxScore >= 1 - 1e-9;
145
+ function offersAfter(now, made, shut) {
146
+ const last = made[made.length - 1];
147
+ return now.graded && !now.disabled && !shut && now.channel?.triesClosed !== true && last !== void 0 && made.length < 1 + now.policy.retries && !fullMarks(last);
148
+ }
149
+ function useTries({ policy, graded, submitted, disabled }) {
150
+ const channel = react.useContext(chunkJ2QIHZ3H_cjs.SequenceSlotContext);
151
+ const [tries, setTries] = react.useState([]);
152
+ const [closed, setClosed] = react.useState(false);
153
+ const triesRef = react.useRef([]);
154
+ const closedRef = react.useRef(false);
155
+ const latest = react.useRef({ policy, graded, disabled, channel });
156
+ latest.current = { policy, graded, disabled, channel };
157
+ const record = react.useCallback((one) => {
158
+ const next = [...triesRef.current, one];
159
+ triesRef.current = next;
160
+ setTries(next);
161
+ if (offersAfter(latest.current, next, closedRef.current)) {
162
+ latest.current.channel?.triesState("offered");
163
+ }
164
+ return lkCore.scoreTries(next, latest.current.policy);
165
+ }, []);
166
+ const close = react.useCallback(() => {
167
+ closedRef.current = true;
168
+ setClosed(true);
169
+ }, []);
170
+ const reset = react.useCallback(() => {
171
+ triesRef.current = [];
172
+ closedRef.current = false;
173
+ setTries([]);
174
+ setClosed(false);
175
+ }, []);
176
+ const allowed = 1 + policy.retries;
177
+ const left = Math.max(0, allowed - tries.length);
178
+ const open = submitted && offersAfter(latest.current, tries, closed);
179
+ const retrying = !submitted && tries.length > 0 && !closed && graded && !disabled && channel?.triesClosed !== true;
180
+ const state = open ? "offered" : retrying ? "retrying" : "none";
181
+ const report = channel?.triesState;
182
+ react.useEffect(() => {
183
+ report?.(state);
184
+ }, [report, state]);
185
+ react.useEffect(() => () => report?.("none"), [report]);
186
+ const counted = react.useMemo(
187
+ () => tries.length === 0 ? null : lkCore.scoreTries(tries, policy),
188
+ [tries, policy]
189
+ );
190
+ return { tries, counted, open, left, record, close, reset };
191
+ }
192
+ var percentOf = (fraction) => Math.round(fraction * 100);
193
+ function triesSummary(s, policy, counted, offered) {
194
+ const parts = [];
195
+ const latest = counted.tries.length - 1;
196
+ const mine = counted.tries[latest];
197
+ if (mine !== void 0) {
198
+ if (counted.counted !== latest) {
199
+ parts.push(
200
+ s.countedTry(
201
+ policy.counts === "best" ? "best" : "first",
202
+ percentOf(mine.scored / mine.maxScore)
203
+ )
204
+ );
205
+ } else if (mine.scored !== mine.score) {
206
+ parts.push(s.scoreBeforeCosts(percentOf(mine.score / mine.maxScore)));
207
+ }
208
+ }
209
+ if (offered) {
210
+ parts.push(
211
+ s.triesLeft(policy.retries + 1 - counted.tries.length, percentOf(policy.retryPenalty))
212
+ );
213
+ }
214
+ return parts.join(" ");
215
+ }
216
+ function TryActions({
217
+ tries,
218
+ solutions,
219
+ strings: s,
220
+ onRetry,
221
+ onClose
222
+ }) {
223
+ if (!tries.open) {
224
+ return null;
225
+ }
226
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "lk-tries", children: [
227
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "lk-tries-again", onClick: onRetry, children: s.tryAgain }),
228
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "lk-tries-close", onClick: onClose, children: solutions ? s.showAnswer : s.keepAnswer })
229
+ ] });
230
+ }
231
+ function HintCost({
232
+ policy,
233
+ strings: s
234
+ }) {
235
+ if (policy.hintPenalty <= 0) {
236
+ return null;
237
+ }
238
+ return /* @__PURE__ */ jsxRuntime.jsx("p", { className: "lk-hint-cost", children: s.hintCost(percentOf(policy.hintPenalty)) });
239
+ }
240
+
241
+ exports.AiExplanation = AiExplanation;
242
+ exports.AiHints = AiHints;
243
+ exports.HintCost = HintCost;
244
+ exports.TryActions = TryActions;
245
+ exports.hintsOf = hintsOf;
246
+ exports.triesSummary = triesSummary;
247
+ exports.useComponentAiHints = useComponentAiHints;
248
+ exports.useItemScoringPolicy = useItemScoringPolicy;
249
+ exports.useTries = useTries;
250
+ //# sourceMappingURL=chunk-CQ37UD4T.cjs.map
251
+ //# sourceMappingURL=chunk-CQ37UD4T.cjs.map