@intellectif/lk-react 13.0.0 → 14.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 (162) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/README.md +11 -3
  3. package/dist/{WrittenResponse-B5p_-RnJ.d.ts → WrittenResponse-C0P34En1.d.ts} +1 -1
  4. package/dist/{WrittenResponse-DWtS4tOL.d.cts → WrittenResponse-pB1ik8hg.d.cts} +1 -1
  5. package/dist/chunk-36FTYJWJ.cjs +869 -0
  6. package/dist/chunk-36FTYJWJ.cjs.map +1 -0
  7. package/dist/{chunk-PSHNTDKG.js → chunk-47HHC4GD.js} +13 -161
  8. package/dist/chunk-47HHC4GD.js.map +1 -0
  9. package/dist/chunk-62AKSD5Y.js +509 -0
  10. package/dist/chunk-62AKSD5Y.js.map +1 -0
  11. package/dist/{chunk-XAGWBVK3.js → chunk-72UGRIMM.js} +6 -5
  12. package/dist/chunk-72UGRIMM.js.map +1 -0
  13. package/dist/{chunk-SBIJJ374.cjs → chunk-B2XQLU2E.cjs} +5 -5
  14. package/dist/{chunk-SBIJJ374.cjs.map → chunk-B2XQLU2E.cjs.map} +1 -1
  15. package/dist/{chunk-DQERNAWI.js → chunk-BO6P723E.js} +26 -31
  16. package/dist/chunk-BO6P723E.js.map +1 -0
  17. package/dist/chunk-BRPEB43C.cjs +13 -0
  18. package/dist/chunk-BRPEB43C.cjs.map +1 -0
  19. package/dist/{chunk-NVUFEMPZ.cjs → chunk-BVRUUYYR.cjs} +30 -16
  20. package/dist/chunk-BVRUUYYR.cjs.map +1 -0
  21. package/dist/{chunk-AMGK7DDM.cjs → chunk-D4DQFTA7.cjs} +2 -2
  22. package/dist/chunk-D4DQFTA7.cjs.map +1 -0
  23. package/dist/chunk-D57QZILZ.js +863 -0
  24. package/dist/chunk-D57QZILZ.js.map +1 -0
  25. package/dist/chunk-F4TBCRGI.cjs +518 -0
  26. package/dist/chunk-F4TBCRGI.cjs.map +1 -0
  27. package/dist/{chunk-YILH4ETT.js → chunk-I33FOKZ7.js} +224 -31
  28. package/dist/chunk-I33FOKZ7.js.map +1 -0
  29. package/dist/{chunk-HT4FFZZQ.cjs → chunk-J2CDPIRC.cjs} +231 -38
  30. package/dist/chunk-J2CDPIRC.cjs.map +1 -0
  31. package/dist/{chunk-MA7GXUWC.cjs → chunk-LB3FEV7Z.cjs} +77 -2
  32. package/dist/chunk-LB3FEV7Z.cjs.map +1 -0
  33. package/dist/{chunk-5N7OLFHB.js → chunk-LGCUOLP6.js} +6 -5
  34. package/dist/chunk-LGCUOLP6.js.map +1 -0
  35. package/dist/{chunk-YZWXVZB3.js → chunk-LLXCX5KK.js} +6 -5
  36. package/dist/chunk-LLXCX5KK.js.map +1 -0
  37. package/dist/chunk-MCO52PV5.js +520 -0
  38. package/dist/chunk-MCO52PV5.js.map +1 -0
  39. package/dist/{chunk-K7NMNOPI.cjs → chunk-NDQD3ZRY.cjs} +15 -14
  40. package/dist/chunk-NDQD3ZRY.cjs.map +1 -0
  41. package/dist/chunk-OD65OZIW.cjs +159 -0
  42. package/dist/chunk-OD65OZIW.cjs.map +1 -0
  43. package/dist/chunk-RCMYEWBE.js +11 -0
  44. package/dist/chunk-RCMYEWBE.js.map +1 -0
  45. package/dist/{chunk-74QXBWCI.cjs → chunk-RRH7ZPXC.cjs} +6 -6
  46. package/dist/{chunk-74QXBWCI.cjs.map → chunk-RRH7ZPXC.cjs.map} +1 -1
  47. package/dist/{chunk-DQNVAXG6.js → chunk-RXS455MR.js} +2 -2
  48. package/dist/chunk-RXS455MR.js.map +1 -0
  49. package/dist/{chunk-Z63JRG4M.cjs → chunk-SE6WWDVW.cjs} +15 -14
  50. package/dist/chunk-SE6WWDVW.cjs.map +1 -0
  51. package/dist/{chunk-NVP6UEQQ.js → chunk-SWY6PWXK.js} +4 -4
  52. package/dist/{chunk-NVP6UEQQ.js.map → chunk-SWY6PWXK.js.map} +1 -1
  53. package/dist/{chunk-3Q4I4KT6.js → chunk-TDJEKXEP.js} +77 -2
  54. package/dist/chunk-TDJEKXEP.js.map +1 -0
  55. package/dist/{chunk-2GXSA322.cjs → chunk-U3APKQ7P.cjs} +15 -14
  56. package/dist/chunk-U3APKQ7P.cjs.map +1 -0
  57. package/dist/chunk-UDCQTOVU.cjs +523 -0
  58. package/dist/chunk-UDCQTOVU.cjs.map +1 -0
  59. package/dist/{chunk-K3EA547J.cjs → chunk-USBANZ7K.cjs} +27 -33
  60. package/dist/chunk-USBANZ7K.cjs.map +1 -0
  61. package/dist/{chunk-J73TDY7J.cjs → chunk-WTS34S5E.cjs} +25 -173
  62. package/dist/chunk-WTS34S5E.cjs.map +1 -0
  63. package/dist/{chunk-R7DI5UMU.js → chunk-XIBOFTOL.js} +3 -3
  64. package/dist/{chunk-R7DI5UMU.js.map → chunk-XIBOFTOL.js.map} +1 -1
  65. package/dist/{chunk-W4MXRZAB.js → chunk-XUEIUEPI.js} +6 -5
  66. package/dist/chunk-XUEIUEPI.js.map +1 -0
  67. package/dist/{chunk-UU2NV6SU.cjs → chunk-YEYKJFEC.cjs} +14 -13
  68. package/dist/chunk-YEYKJFEC.cjs.map +1 -0
  69. package/dist/{chunk-PY53ABWC.js → chunk-YMBBM5K7.js} +23 -9
  70. package/dist/chunk-YMBBM5K7.js.map +1 -0
  71. package/dist/chunk-YORQQXGO.js +155 -0
  72. package/dist/chunk-YORQQXGO.js.map +1 -0
  73. package/dist/components/ActivityPreview.cjs +15 -10
  74. package/dist/components/ActivityPreview.d.cts +9 -3
  75. package/dist/components/ActivityPreview.d.ts +9 -3
  76. package/dist/components/ActivityPreview.js +14 -9
  77. package/dist/components/ActivitySequence.cjs +16 -11
  78. package/dist/components/ActivitySequence.d.cts +108 -4
  79. package/dist/components/ActivitySequence.d.ts +108 -4
  80. package/dist/components/ActivitySequence.js +15 -10
  81. package/dist/components/Dictation.cjs +7 -5
  82. package/dist/components/Dictation.d.cts +2 -1
  83. package/dist/components/Dictation.d.ts +2 -1
  84. package/dist/components/Dictation.js +6 -4
  85. package/dist/components/FillInTheBlanks.cjs +6 -5
  86. package/dist/components/FillInTheBlanks.d.cts +2 -1
  87. package/dist/components/FillInTheBlanks.d.ts +2 -1
  88. package/dist/components/FillInTheBlanks.js +5 -4
  89. package/dist/components/GapSelect.cjs +6 -5
  90. package/dist/components/GapSelect.d.cts +2 -1
  91. package/dist/components/GapSelect.d.ts +2 -1
  92. package/dist/components/GapSelect.js +5 -4
  93. package/dist/components/MultipleChoice.cjs +6 -5
  94. package/dist/components/MultipleChoice.d.cts +2 -1
  95. package/dist/components/MultipleChoice.d.ts +2 -1
  96. package/dist/components/MultipleChoice.js +5 -4
  97. package/dist/components/PronunciationFeedback.cjs +16 -0
  98. package/dist/components/PronunciationFeedback.cjs.map +1 -0
  99. package/dist/components/PronunciationFeedback.d.cts +64 -0
  100. package/dist/components/PronunciationFeedback.d.ts +64 -0
  101. package/dist/components/PronunciationFeedback.js +7 -0
  102. package/dist/components/PronunciationFeedback.js.map +1 -0
  103. package/dist/components/ReadAloud.cjs +21 -0
  104. package/dist/components/ReadAloud.cjs.map +1 -0
  105. package/dist/components/ReadAloud.d.cts +15 -0
  106. package/dist/components/ReadAloud.d.ts +15 -0
  107. package/dist/components/ReadAloud.js +12 -0
  108. package/dist/components/ReadAloud.js.map +1 -0
  109. package/dist/components/StimulusPanel.cjs +4 -4
  110. package/dist/components/StimulusPanel.d.cts +2 -1
  111. package/dist/components/StimulusPanel.d.ts +2 -1
  112. package/dist/components/StimulusPanel.js +3 -3
  113. package/dist/components/WrittenResponse.cjs +6 -5
  114. package/dist/components/WrittenResponse.d.cts +4 -3
  115. package/dist/components/WrittenResponse.d.ts +4 -3
  116. package/dist/components/WrittenResponse.js +5 -4
  117. package/dist/hooks/useSpeechRecorder.cjs +17 -0
  118. package/dist/hooks/useSpeechRecorder.cjs.map +1 -0
  119. package/dist/hooks/useSpeechRecorder.d.cts +127 -0
  120. package/dist/hooks/useSpeechRecorder.d.ts +127 -0
  121. package/dist/hooks/useSpeechRecorder.js +4 -0
  122. package/dist/hooks/useSpeechRecorder.js.map +1 -0
  123. package/dist/i18n/LkIntlProvider.cjs +7 -7
  124. package/dist/i18n/LkIntlProvider.d.cts +3 -2
  125. package/dist/i18n/LkIntlProvider.d.ts +3 -2
  126. package/dist/i18n/LkIntlProvider.js +1 -1
  127. package/dist/index.cjs +53 -32
  128. package/dist/index.cjs.map +1 -1
  129. package/dist/index.d.cts +6 -2
  130. package/dist/index.d.ts +6 -2
  131. package/dist/index.js +17 -12
  132. package/dist/index.js.map +1 -1
  133. package/dist/machine-CuwKqWXC.d.cts +26 -0
  134. package/dist/machine-CuwKqWXC.d.ts +26 -0
  135. package/dist/{strings-DPTyLpHt.d.ts → strings--ADnHcaA.d.cts} +214 -4
  136. package/dist/{strings-DPTyLpHt.d.cts → strings-CtdSf2St.d.ts} +214 -4
  137. package/dist/theme/ThemeProvider.cjs +6 -6
  138. package/dist/theme/ThemeProvider.d.cts +4 -4
  139. package/dist/theme/ThemeProvider.d.ts +4 -4
  140. package/dist/theme/ThemeProvider.js +1 -1
  141. package/dist/theme/skin.css +596 -3
  142. package/package.json +33 -3
  143. package/dist/chunk-2GXSA322.cjs.map +0 -1
  144. package/dist/chunk-3Q4I4KT6.js.map +0 -1
  145. package/dist/chunk-5N7OLFHB.js.map +0 -1
  146. package/dist/chunk-AMGK7DDM.cjs.map +0 -1
  147. package/dist/chunk-DQERNAWI.js.map +0 -1
  148. package/dist/chunk-DQNVAXG6.js.map +0 -1
  149. package/dist/chunk-HT4FFZZQ.cjs.map +0 -1
  150. package/dist/chunk-J73TDY7J.cjs.map +0 -1
  151. package/dist/chunk-K3EA547J.cjs.map +0 -1
  152. package/dist/chunk-K7NMNOPI.cjs.map +0 -1
  153. package/dist/chunk-MA7GXUWC.cjs.map +0 -1
  154. package/dist/chunk-NVUFEMPZ.cjs.map +0 -1
  155. package/dist/chunk-PSHNTDKG.js.map +0 -1
  156. package/dist/chunk-PY53ABWC.js.map +0 -1
  157. package/dist/chunk-UU2NV6SU.cjs.map +0 -1
  158. package/dist/chunk-W4MXRZAB.js.map +0 -1
  159. package/dist/chunk-XAGWBVK3.js.map +0 -1
  160. package/dist/chunk-YILH4ETT.js.map +0 -1
  161. package/dist/chunk-YZWXVZB3.js.map +0 -1
  162. package/dist/chunk-Z63JRG4M.cjs.map +0 -1
@@ -0,0 +1,869 @@
1
+ 'use client';
2
+ 'use strict';
3
+
4
+ var chunkF4TBCRGI_cjs = require('./chunk-F4TBCRGI.cjs');
5
+ var chunkUDCQTOVU_cjs = require('./chunk-UDCQTOVU.cjs');
6
+ var chunkOD65OZIW_cjs = require('./chunk-OD65OZIW.cjs');
7
+ var chunkBRPEB43C_cjs = require('./chunk-BRPEB43C.cjs');
8
+ var chunkUSBANZ7K_cjs = require('./chunk-USBANZ7K.cjs');
9
+ var chunkB2XQLU2E_cjs = require('./chunk-B2XQLU2E.cjs');
10
+ var chunkXOR4MPEN_cjs = require('./chunk-XOR4MPEN.cjs');
11
+ var chunkLB3FEV7Z_cjs = require('./chunk-LB3FEV7Z.cjs');
12
+ var lkCore = require('@intellectif/lk-core');
13
+ var react = require('react');
14
+ var jsxRuntime = require('react/jsx-runtime');
15
+
16
+ // src/components/shared/capture-registry.ts
17
+ var groups = /* @__PURE__ */ new Map();
18
+ function joinCaptureGroup(group, stop) {
19
+ let members = groups.get(group);
20
+ if (members === void 0) {
21
+ members = /* @__PURE__ */ new Set();
22
+ groups.set(group, members);
23
+ }
24
+ members.add(stop);
25
+ return () => {
26
+ const current = groups.get(group);
27
+ if (current === void 0) {
28
+ return;
29
+ }
30
+ current.delete(stop);
31
+ if (current.size === 0) {
32
+ groups.delete(group);
33
+ }
34
+ };
35
+ }
36
+ function stopCaptureGroup(group) {
37
+ const members = groups.get(group);
38
+ if (members === void 0) {
39
+ return;
40
+ }
41
+ for (const stop of [...members]) {
42
+ stop();
43
+ }
44
+ }
45
+ var SequenceSlotContext = react.createContext(null);
46
+ var lastTake = 0;
47
+ function mintTake() {
48
+ lastTake += 1;
49
+ return lastTake;
50
+ }
51
+ var takes = /* @__PURE__ */ new WeakMap();
52
+ function stampTake(value, take) {
53
+ takes.set(value, take);
54
+ return value;
55
+ }
56
+ function takeOf(value) {
57
+ return typeof value === "object" && value !== null ? takes.get(value) : void 0;
58
+ }
59
+ var modelsGroupCount = 0;
60
+ var VISIBLE_TEXT_RE = /[^\p{White_Space}\p{Cc}\p{Cf}\p{Default_Ignorable_Code_Point}]/u;
61
+ var MARK_ID_RE = /^w\d+$/;
62
+ var STORED_STATES = [
63
+ "correct",
64
+ "mispronounced",
65
+ "omitted"
66
+ ];
67
+ var LEVEL_STEPS = 10;
68
+ function offersRetry(phase, uploaded) {
69
+ return phase.kind === "upload-failed" || phase.kind === "assess-failed" && phase.retryable && uploaded !== null;
70
+ }
71
+ function takeStateOf(phase, uploaded) {
72
+ if (phase.kind === "uploading" || phase.kind === "assessing") {
73
+ return "in-flight";
74
+ }
75
+ return offersRetry(phase, uploaded) ? "retryable" : "settled";
76
+ }
77
+ function readingKeyOf(data, bounds) {
78
+ const text = (value) => typeof value === "string" ? value : null;
79
+ return JSON.stringify([
80
+ text(data.id),
81
+ text(data.referenceText),
82
+ text(data.locale),
83
+ String(bounds.maxSeconds),
84
+ String(bounds.minSeconds),
85
+ String(bounds.maxTakes)
86
+ ]);
87
+ }
88
+ function slowMediaFor(data, label) {
89
+ const slow = data.slowMedia;
90
+ if (slow === void 0) {
91
+ return void 0;
92
+ }
93
+ const policy = data.media?.playback ?? {};
94
+ const { maxPlays: _budget, ...inherited } = policy;
95
+ const hasPolicy = Object.values(inherited).some((value) => value !== void 0);
96
+ return {
97
+ type: "audio",
98
+ url: slow.url,
99
+ alt: slow.alt ?? label,
100
+ ...hasPolicy ? { playback: inherited } : {}
101
+ };
102
+ }
103
+ function marksFromDetails(details) {
104
+ if (details.length === 0 || !details.every((detail) => MARK_ID_RE.test(detail.itemId))) {
105
+ return null;
106
+ }
107
+ return details.map((detail) => {
108
+ const heard = Array.isArray(detail.learnerResponse) ? detail.learnerResponse.join(" ") : detail.learnerResponse;
109
+ const reference = Array.isArray(detail.correctResponse) ? detail.correctResponse.join(" ") : detail.correctResponse;
110
+ const correct = detail.outcome !== void 0 ? detail.outcome === "correct" : detail.correct;
111
+ const state = correct ? "correct" : detail.outcome === "incorrect-omission" ? "omitted" : "mispronounced";
112
+ return {
113
+ itemId: detail.itemId,
114
+ reference,
115
+ heard,
116
+ state,
117
+ // `ScoringDetail.score` is the word's accuracy divided by 100; the marks
118
+ // carry it back in the units the assessment used.
119
+ ...typeof detail.score === "number" ? { accuracy: detail.score * 100 } : {}
120
+ };
121
+ });
122
+ }
123
+ function recordingOf(response) {
124
+ return response !== void 0 && response.type === "read-aloud" ? response.recording : null;
125
+ }
126
+ function responseOf(recording, takes2) {
127
+ return { type: "read-aloud", recording, ...takes2 > 0 ? { takes: takes2 } : {} };
128
+ }
129
+ function unscorableMessage(code, s) {
130
+ return code === "no_speech" || code === "insufficient_voiced_time" ? s.readAloudNotHeard : s.readAloudNotAssessed;
131
+ }
132
+ function AnnouncementText({ announcement }) {
133
+ if (announcement === null) {
134
+ return null;
135
+ }
136
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
137
+ announcement.text,
138
+ announcement.feedback ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
139
+ " ",
140
+ /* @__PURE__ */ jsxRuntime.jsx("span", { dir: "auto", children: announcement.feedback })
141
+ ] }) : null
142
+ ] });
143
+ }
144
+ function OutcomeSummary({
145
+ reading,
146
+ s,
147
+ scoreShown
148
+ }) {
149
+ switch (reading.kind) {
150
+ case "graded":
151
+ return /* @__PURE__ */ jsxRuntime.jsx(
152
+ "div",
153
+ {
154
+ className: "lk-ra-outcome",
155
+ "data-status": reading.status,
156
+ "data-passed": String(reading.grade.passed),
157
+ children: scoreShown ? null : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
158
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "lk-ra-grade", children: s.scoreAnnouncement(chunkF4TBCRGI_cjs.percentOfGrade(reading.grade), reading.grade.passed) }),
159
+ reading.grade.feedback ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "lk-ra-grade-feedback", dir: "auto", children: reading.grade.feedback }) : null
160
+ ] })
161
+ }
162
+ );
163
+ case "deferred":
164
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "lk-ra-outcome", "data-status": "deferred", children: /* @__PURE__ */ jsxRuntime.jsx("p", { className: "lk-ra-grade", children: s.awaitingGrade }) });
165
+ case "unscorable":
166
+ return /* @__PURE__ */ jsxRuntime.jsx(
167
+ "div",
168
+ {
169
+ className: "lk-ra-outcome",
170
+ "data-status": "unscorable",
171
+ ...reading.code !== void 0 ? { "data-code": reading.code } : {},
172
+ children: /* @__PURE__ */ jsxRuntime.jsx("p", { className: "lk-ra-grade", children: s.couldNotBeGraded })
173
+ }
174
+ );
175
+ default:
176
+ return /* @__PURE__ */ jsxRuntime.jsx(
177
+ "div",
178
+ {
179
+ className: "lk-ra-outcome",
180
+ ...reading.status !== void 0 ? { "data-status": reading.status } : {},
181
+ children: /* @__PURE__ */ jsxRuntime.jsx("p", { className: "lk-ra-grade", children: s.couldNotBeGraded })
182
+ }
183
+ );
184
+ }
185
+ }
186
+ function ReadAloud({
187
+ data,
188
+ onComplete,
189
+ onSubmit,
190
+ onInteraction,
191
+ value,
192
+ defaultValue,
193
+ defaultSubmitted,
194
+ renderMode = "practice",
195
+ outcome,
196
+ mediaBudget,
197
+ mediaStrings,
198
+ strings,
199
+ theme,
200
+ locale,
201
+ disabled,
202
+ recordingBinding,
203
+ assessment,
204
+ breakThreshold,
205
+ monotoneThreshold
206
+ }) {
207
+ const isExam = renderMode === "exam";
208
+ const isReview = renderMode === "review";
209
+ const s = chunkLB3FEV7Z_cjs.useLkStrings(strings);
210
+ const slot = react.useContext(SequenceSlotContext);
211
+ const captureGroup = slot?.captureGroup;
212
+ const devError = react.useMemo(() => {
213
+ if (!chunkUSBANZ7K_cjs.isDevelopment()) {
214
+ return null;
215
+ }
216
+ if (data.redacted === true) {
217
+ try {
218
+ lkCore.assertRedacted(data);
219
+ return null;
220
+ } catch (error) {
221
+ return error instanceof Error ? error : new Error(String(error));
222
+ }
223
+ }
224
+ const result = lkCore.validateActivity("read-aloud", data);
225
+ return result.success ? null : new lkCore.ActivitySchemaError("read-aloud", result.errors);
226
+ }, [data]);
227
+ const bounds = data.recording;
228
+ const maxSeconds = typeof bounds?.maxSeconds === "number" && bounds.maxSeconds > 0 ? Math.min(bounds.maxSeconds, lkCore.READ_ALOUD_MAX_SECONDS) : lkCore.READ_ALOUD_MAX_SECONDS;
229
+ const minSeconds = typeof bounds?.minSeconds === "number" ? bounds.minSeconds : void 0;
230
+ const maxTakes = typeof bounds?.maxTakes === "number" && Number.isFinite(bounds.maxTakes) ? Math.min(Math.max(1, Math.floor(bounds.maxTakes)), lkCore.READ_ALOUD_MAX_TAKES) : void 0;
231
+ const readingKey = readingKeyOf(data, { maxSeconds, minSeconds, maxTakes });
232
+ const recorder = chunkUDCQTOVU_cjs.useSpeechRecorder(
233
+ react.useMemo(
234
+ () => ({
235
+ maxDurationMs: maxSeconds * 1e3,
236
+ ...minSeconds !== void 0 ? { minDurationMs: minSeconds * 1e3 } : {},
237
+ ...maxTakes !== void 0 ? { maxTakes } : {}
238
+ }),
239
+ [maxSeconds, minSeconds, maxTakes]
240
+ )
241
+ );
242
+ const { state, start, complete, getTimeSpent, reset } = chunkXOR4MPEN_cjs.useActivityState(
243
+ defaultSubmitted === true ? "completed" : "idle"
244
+ );
245
+ const [phase, setPhase] = react.useState({ kind: "idle" });
246
+ const [uploaded, setUploaded] = react.useState(null);
247
+ const [takeUrl, setTakeUrl] = react.useState(null);
248
+ const [playbackUrl, setPlaybackUrl] = react.useState(null);
249
+ const [errorNonce, setErrorNonce] = react.useState(0);
250
+ const ids = react.useId();
251
+ const titleId = `${ids}-title`;
252
+ const [modelsGroup] = react.useState(() => {
253
+ modelsGroupCount += 1;
254
+ return `lk-ra-models-${modelsGroupCount}`;
255
+ });
256
+ const interactionRef = react.useRef(onInteraction);
257
+ interactionRef.current = onInteraction;
258
+ const bindingRef = react.useRef(recordingBinding);
259
+ bindingRef.current = recordingBinding;
260
+ const submittingRef = react.useRef(false);
261
+ const runRef = react.useRef(0);
262
+ const slotRef = react.useRef(slot);
263
+ slotRef.current = slot;
264
+ const reportedTakeRef = react.useRef(null);
265
+ react.useEffect(
266
+ () => () => {
267
+ runRef.current += 1;
268
+ const take2 = reportedTakeRef.current;
269
+ if (take2 !== null) {
270
+ slotRef.current?.takeState(take2, "settled");
271
+ }
272
+ },
273
+ []
274
+ );
275
+ const fire = react.useCallback(
276
+ (type, payload) => {
277
+ interactionRef.current?.({ type, activityId: data.id, timestamp: Date.now(), payload });
278
+ },
279
+ [data.id]
280
+ );
281
+ const lastStatusRef = react.useRef(recorder.status);
282
+ react.useEffect(() => {
283
+ if (lastStatusRef.current === recorder.status) {
284
+ return;
285
+ }
286
+ lastStatusRef.current = recorder.status;
287
+ if (recorder.status === "recording") {
288
+ fire("recording-started", { takes: recorder.takesUsed, durationMs: 0 });
289
+ } else if (recorder.status === "recorded" && recorder.take !== null) {
290
+ fire("recording-stopped", {
291
+ takes: recorder.takesUsed,
292
+ durationMs: recorder.take.durationMs
293
+ });
294
+ }
295
+ }, [recorder.status, recorder.take, recorder.takesUsed, fire]);
296
+ react.useEffect(() => {
297
+ if (recorder.error !== null) {
298
+ setErrorNonce((nonce) => nonce + 1);
299
+ }
300
+ }, [recorder.error]);
301
+ const take = recorder.take;
302
+ react.useEffect(() => {
303
+ if (take === null || typeof URL.createObjectURL !== "function") {
304
+ return;
305
+ }
306
+ const url = URL.createObjectURL(take.blob);
307
+ setTakeUrl(url);
308
+ return () => {
309
+ URL.revokeObjectURL(url);
310
+ setTakeUrl(null);
311
+ };
312
+ }, [take]);
313
+ const restored = recordingOf(value ?? defaultValue);
314
+ const restoredKey = restored?.key ?? null;
315
+ react.useEffect(() => {
316
+ const binding = bindingRef.current;
317
+ if (!isReview || restored === null || binding?.playbackUrl === void 0) {
318
+ return;
319
+ }
320
+ let live = true;
321
+ void Promise.resolve(binding.playbackUrl(restored)).then(
322
+ (url) => {
323
+ if (live) {
324
+ setPlaybackUrl(url);
325
+ }
326
+ },
327
+ () => {
328
+ }
329
+ );
330
+ return () => {
331
+ live = false;
332
+ };
333
+ }, [isReview, restoredKey, readingKey]);
334
+ const statusRef = react.useRef(recorder.status);
335
+ statusRef.current = recorder.status;
336
+ const releaseMicrophone = react.useCallback(() => {
337
+ if (statusRef.current === "recording" || statusRef.current === "requesting-permission") {
338
+ recorder.discard();
339
+ }
340
+ }, [recorder.discard]);
341
+ react.useEffect(() => {
342
+ if (captureGroup === void 0) {
343
+ return;
344
+ }
345
+ return joinCaptureGroup(captureGroup, releaseMicrophone);
346
+ }, [captureGroup, releaseMicrophone]);
347
+ const defaultSubmittedRef = react.useRef(defaultSubmitted);
348
+ react.useEffect(() => {
349
+ defaultSubmittedRef.current = defaultSubmitted;
350
+ }, [defaultSubmitted]);
351
+ const lastReadingRef = react.useRef(readingKey);
352
+ react.useEffect(() => {
353
+ if (lastReadingRef.current === readingKey) {
354
+ return;
355
+ }
356
+ lastReadingRef.current = readingKey;
357
+ submittingRef.current = false;
358
+ runRef.current += 1;
359
+ setPhase({ kind: "idle" });
360
+ setUploaded(null);
361
+ setPlaybackUrl(null);
362
+ recorder.reset();
363
+ reset(defaultSubmittedRef.current === true ? "completed" : "idle");
364
+ }, [readingKey, reset, recorder.reset]);
365
+ const phaseTake = phase.kind === "idle" ? null : phase.take;
366
+ const phaseTakeState = takeStateOf(phase, uploaded);
367
+ react.useEffect(() => {
368
+ const take2 = phaseTake ?? reportedTakeRef.current;
369
+ if (slot === null || take2 === null) {
370
+ return;
371
+ }
372
+ reportedTakeRef.current = take2;
373
+ slot.takeState(take2, phaseTakeState);
374
+ }, [slot, phaseTake, phaseTakeState]);
375
+ const modelsRef = react.useRef(null);
376
+ react.useEffect(() => {
377
+ if (recorder.status !== "recording") {
378
+ return;
379
+ }
380
+ const node = modelsRef.current;
381
+ if (node === null) {
382
+ return;
383
+ }
384
+ const hush = () => {
385
+ for (const media of node.querySelectorAll("audio, video")) {
386
+ const element = media;
387
+ if (!element.paused) {
388
+ element.pause();
389
+ }
390
+ }
391
+ };
392
+ hush();
393
+ node.addEventListener("play", hush, true);
394
+ return () => {
395
+ node.removeEventListener("play", hush, true);
396
+ };
397
+ }, [recorder.status]);
398
+ const slowMedia = react.useMemo(
399
+ () => slowMediaFor(data, s.readAloudSlowRecording),
400
+ [data, s.readAloudSlowRecording]
401
+ );
402
+ if (devError) {
403
+ throw devError;
404
+ }
405
+ if (!isReview && recordingBinding?.upload === void 0) {
406
+ throw new Error(
407
+ `ReadAloud "${data.id}" was rendered in renderMode "${renderMode}" without \`recordingBinding.upload\`. The learner could record a take that is stored nowhere and submitted as nothing, which looks like a working activity the whole way through. Pass a \`recordingBinding\` whose \`upload\` puts the take in your storage and returns its key. \`assess\` is optional: without it the take is still recorded, uploaded and submitted, and a notice replaces the feedback.`
408
+ );
409
+ }
410
+ const submitted = state === "completed";
411
+ const inactive = disabled === true || submitted || isReview;
412
+ const busy = phase.kind === "uploading" || phase.kind === "assessing";
413
+ const takesLeft = maxTakes === void 0 ? void 0 : Math.max(0, maxTakes - recorder.takesUsed);
414
+ const raise = (next) => {
415
+ setErrorNonce((nonce) => nonce + 1);
416
+ setPhase(next);
417
+ };
418
+ const beginTakeReport = (take2) => {
419
+ reportedTakeRef.current = take2;
420
+ slot?.takeState(take2, "in-flight");
421
+ };
422
+ const runAssess = async (ref, current, take2) => {
423
+ const assess = bindingRef.current?.assess;
424
+ const durationMs = current?.durationMs ?? 0;
425
+ const takes2 = recorder.takesUsed;
426
+ const run = runRef.current;
427
+ if (assess === void 0) {
428
+ setPhase({ kind: "unassessed", take: take2 });
429
+ return;
430
+ }
431
+ setPhase({ kind: "assessing", take: take2 });
432
+ fire("assessment-requested", { takes: takes2, durationMs });
433
+ let answer;
434
+ try {
435
+ answer = await assess(ref);
436
+ } catch {
437
+ if (run !== runRef.current) {
438
+ return;
439
+ }
440
+ raise({ kind: "assess-failed", take: take2, retryable: true });
441
+ fire("assessment-failed", { takes: takes2, durationMs, retryable: true });
442
+ return;
443
+ }
444
+ if (run !== runRef.current) {
445
+ return;
446
+ }
447
+ const result = chunkF4TBCRGI_cjs.readAssessResult(answer);
448
+ if (result.status === "failed") {
449
+ raise({ kind: "assess-failed", take: take2, retryable: result.retryable });
450
+ fire("assessment-failed", { takes: takes2, durationMs, retryable: result.retryable });
451
+ return;
452
+ }
453
+ if (result.status === "unscorable") {
454
+ setPhase({ kind: "unscorable", take: take2, code: result.code });
455
+ fire("assessment-failed", {
456
+ takes: takes2,
457
+ durationMs,
458
+ retryable: true,
459
+ ...result.code !== void 0 ? { code: result.code } : {}
460
+ });
461
+ return;
462
+ }
463
+ const timeSpent = getTimeSpent();
464
+ setPhase({ kind: "graded", take: take2, assessment: result.assessment, grade: result.grade });
465
+ const grade = result.grade;
466
+ if (grade === null) {
467
+ fire("assessment-failed", { takes: takes2, durationMs, retryable: true });
468
+ return;
469
+ }
470
+ onComplete?.(
471
+ stampTake(
472
+ {
473
+ score: grade.score,
474
+ maxScore: grade.maxScore,
475
+ passed: grade.passed,
476
+ timeSpent,
477
+ xapiStatement: lkCore.xAPIBuilder.buildAnsweredStatement({
478
+ actor: chunkUSBANZ7K_cjs.ANONYMOUS_ACTOR,
479
+ object: {
480
+ id: chunkUSBANZ7K_cjs.objectIdFor(data.id),
481
+ name: { [data.locale ?? "en-US"]: data.title },
482
+ ...lkCore.xapiDefinitionFor(data)
483
+ },
484
+ timeSpentMs: timeSpent,
485
+ response: result.recognizedText ?? "",
486
+ // `ScoringResult.details` is required where `GradeRecord.details` is
487
+ // optional, and an empty list is the honest reading of "no per-word
488
+ // marks were stored" — never a mark of its own.
489
+ scoringResult: {
490
+ score: chunkF4TBCRGI_cjs.fractionOfGrade(grade),
491
+ maxScore: 1,
492
+ passed: grade.passed,
493
+ feedback: grade.feedback,
494
+ details: grade.details ?? []
495
+ }
496
+ })
497
+ },
498
+ take2
499
+ )
500
+ );
501
+ };
502
+ const submitTake = async (current) => {
503
+ const binding = bindingRef.current;
504
+ if (inactive || submittingRef.current || binding === void 0) {
505
+ return;
506
+ }
507
+ submittingRef.current = true;
508
+ const takes2 = recorder.takesUsed;
509
+ const run = runRef.current;
510
+ const take2 = mintTake();
511
+ beginTakeReport(take2);
512
+ try {
513
+ let ref = null;
514
+ if (current !== null) {
515
+ setPhase({ kind: "uploading", take: take2 });
516
+ try {
517
+ ref = await binding.upload(current);
518
+ } catch {
519
+ if (run !== runRef.current) {
520
+ return;
521
+ }
522
+ raise({ kind: "upload-failed", take: take2 });
523
+ fire("recording-upload-failed", {
524
+ takes: takes2,
525
+ durationMs: current.durationMs,
526
+ retryable: true
527
+ });
528
+ return;
529
+ }
530
+ if (run !== runRef.current) {
531
+ return;
532
+ }
533
+ setUploaded(ref);
534
+ fire("recording-uploaded", { takes: takes2, durationMs: current.durationMs });
535
+ }
536
+ onSubmit?.(stampTake(responseOf(ref, takes2), take2));
537
+ fire("submitted", { takes: takes2, durationMs: current?.durationMs ?? 0 });
538
+ if (isExam || ref === null) {
539
+ complete();
540
+ setPhase({ kind: "submitted", take: take2 });
541
+ return;
542
+ }
543
+ await runAssess(ref, current, take2);
544
+ } finally {
545
+ submittingRef.current = false;
546
+ }
547
+ };
548
+ const retryAssess = async (ref, take2) => {
549
+ if (submittingRef.current) {
550
+ return;
551
+ }
552
+ submittingRef.current = true;
553
+ beginTakeReport(take2);
554
+ try {
555
+ await runAssess(ref, recorder.take, take2);
556
+ } finally {
557
+ submittingRef.current = false;
558
+ }
559
+ };
560
+ const handleSubmit = (event) => {
561
+ event.preventDefault();
562
+ if (inactive || busy || recorder.take === null || phase.kind !== "idle") {
563
+ return;
564
+ }
565
+ void submitTake(recorder.take);
566
+ };
567
+ const beginTake = () => {
568
+ if (inactive || busy || !recorder.canRecord) {
569
+ return;
570
+ }
571
+ if (recorder.take !== null) {
572
+ fire("recording-discarded", {
573
+ takes: recorder.takesUsed,
574
+ durationMs: recorder.take.durationMs
575
+ });
576
+ }
577
+ setPhase({ kind: "idle" });
578
+ setUploaded(null);
579
+ start();
580
+ void recorder.start();
581
+ };
582
+ const contentLang = typeof data.locale === "string" ? data.locale : void 0;
583
+ const contentDir = chunkOD65OZIW_cjs.localeDirectionOf(data.locale);
584
+ const outcomeReading = react.useMemo(() => chunkF4TBCRGI_cjs.readOutcome(outcome), [outcome]);
585
+ const keptEvidence = react.useMemo(() => chunkF4TBCRGI_cjs.readEvidence(assessment), [assessment]);
586
+ const storedGrade = outcomeReading?.kind === "graded" && outcomeReading.status === "graded" ? outcomeReading.grade : null;
587
+ const storedMarks = react.useMemo(
588
+ () => isReview && keptEvidence === null && storedGrade?.details !== void 0 ? marksFromDetails(storedGrade.details) : null,
589
+ [isReview, keptEvidence, storedGrade]
590
+ );
591
+ const feedbackAssessment = isReview ? keptEvidence : phase.kind === "graded" ? phase.assessment : null;
592
+ const feedbackGrade = isReview ? storedGrade : phase.kind === "graded" ? phase.grade : null;
593
+ const feedbackAudio = isReview ? playbackUrl : takeUrl;
594
+ const rootRef = react.useRef(null);
595
+ const [panelLostTheGrade, setPanelLostTheGrade] = react.useState(false);
596
+ const panelHasGrade = feedbackAssessment !== null && feedbackGrade !== null;
597
+ const gradeShownInPanel = panelHasGrade && !panelLostTheGrade;
598
+ react.useEffect(() => {
599
+ const root = rootRef.current;
600
+ const lost = panelHasGrade && (root === null || root.querySelector(".lk-pf-grade") === null);
601
+ setPanelLostTheGrade((was) => was === lost ? was : lost);
602
+ });
603
+ const announcement = react.useMemo(() => {
604
+ if (isReview) {
605
+ return null;
606
+ }
607
+ if (recorder.status === "recording") {
608
+ return { text: s.readAloudRecordingStarted, feedback: null };
609
+ }
610
+ const stopped = recorder.status === "recorded" && take !== null ? {
611
+ text: s.readAloudRecordingStopped(Math.round(take.durationMs / 1e3), maxSeconds),
612
+ feedback: null
613
+ } : null;
614
+ if (isExam) {
615
+ if (phase.kind === "submitted" || submitted) {
616
+ return { text: s.answerSubmitted, feedback: null };
617
+ }
618
+ return phase.kind === "idle" ? stopped : null;
619
+ }
620
+ switch (phase.kind) {
621
+ case "graded": {
622
+ const grade = phase.grade;
623
+ return grade === null ? { text: `${s.answerSubmitted} ${s.couldNotBeGraded}`, feedback: null } : {
624
+ text: `${s.answerSubmitted} ${s.scoreAnnouncement(chunkF4TBCRGI_cjs.percentOfGrade(grade), grade.passed)}`,
625
+ feedback: grade.feedback
626
+ };
627
+ }
628
+ case "unscorable":
629
+ return { text: unscorableMessage(phase.code, s), feedback: null };
630
+ case "unassessed":
631
+ return { text: s.readAloudAssessmentUnavailable, feedback: null };
632
+ case "idle":
633
+ return stopped;
634
+ default:
635
+ return null;
636
+ }
637
+ }, [isReview, isExam, phase, submitted, s, recorder.status, take, maxSeconds]);
638
+ const alertText = recorder.error !== null ? s.readAloudRecorderError(recorder.error) : phase.kind === "upload-failed" ? s.readAloudUploadFailed : phase.kind === "assess-failed" ? s.readAloudAssessmentFailed : null;
639
+ return /* @__PURE__ */ jsxRuntime.jsxs(
640
+ "form",
641
+ {
642
+ className: "lk-ra",
643
+ ref: rootRef,
644
+ "aria-labelledby": VISIBLE_TEXT_RE.test(data.title) ? titleId : void 0,
645
+ lang: locale,
646
+ "data-render-mode": renderMode,
647
+ style: theme,
648
+ onSubmit: handleSubmit,
649
+ children: [
650
+ /* @__PURE__ */ jsxRuntime.jsx("p", { id: titleId, className: "lk-ra-title", dir: contentDir ?? "auto", lang: contentLang, children: data.title }),
651
+ typeof data.instructions === "string" && data.instructions !== "" ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "lk-ra-instructions", dir: contentDir ?? "auto", lang: contentLang, children: data.instructions }) : null,
652
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "lk-ra-reference", dir: contentDir ?? "auto", lang: contentLang, children: data.referenceText }),
653
+ data.media !== void 0 || slowMedia !== void 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "lk-ra-models", ref: modelsRef, children: [
654
+ data.media !== void 0 ? /* @__PURE__ */ jsxRuntime.jsxs("fieldset", { className: "lk-ra-model", children: [
655
+ /* @__PURE__ */ jsxRuntime.jsx("legend", { className: "lk-ra-model-label", children: s.readAloudModelRecording }),
656
+ /* @__PURE__ */ jsxRuntime.jsx(
657
+ chunkB2XQLU2E_cjs.ActivityMedia,
658
+ {
659
+ media: data.media,
660
+ renderMode,
661
+ playbackGroup: modelsGroup,
662
+ ...mediaBudget !== void 0 ? { mediaBudget } : {},
663
+ ...mediaStrings !== void 0 ? { mediaStrings } : {},
664
+ ...strings !== void 0 ? { strings } : {},
665
+ ...onInteraction !== void 0 ? { onInteraction } : {},
666
+ ...locale !== void 0 ? { locale } : {},
667
+ ...disabled !== void 0 ? { disabled } : {}
668
+ }
669
+ )
670
+ ] }) : null,
671
+ slowMedia !== void 0 ? /* @__PURE__ */ jsxRuntime.jsxs("fieldset", { className: "lk-ra-model", "data-slow": "true", children: [
672
+ /* @__PURE__ */ jsxRuntime.jsx("legend", { className: "lk-ra-model-label", children: s.readAloudSlowRecording }),
673
+ /* @__PURE__ */ jsxRuntime.jsx(
674
+ chunkB2XQLU2E_cjs.ActivityMedia,
675
+ {
676
+ media: slowMedia,
677
+ renderMode,
678
+ playbackGroup: modelsGroup,
679
+ ...mediaStrings !== void 0 ? { mediaStrings } : {},
680
+ ...strings !== void 0 ? { strings } : {},
681
+ ...locale !== void 0 ? { locale } : {},
682
+ ...disabled !== void 0 ? { disabled } : {}
683
+ }
684
+ )
685
+ ] }) : null
686
+ ] }) : null,
687
+ isReview || submitted ? null : /* @__PURE__ */ jsxRuntime.jsxs(
688
+ "div",
689
+ {
690
+ className: "lk-ra-recorder",
691
+ "data-status": recorder.status,
692
+ "data-level": recorder.status === "recording" ? Math.min(LEVEL_STEPS, Math.round(recorder.level * LEVEL_STEPS)) : void 0,
693
+ children: [
694
+ recorder.status === "recording" ? /* @__PURE__ */ jsxRuntime.jsx(
695
+ "button",
696
+ {
697
+ type: "button",
698
+ className: "lk-ra-stop",
699
+ disabled: inactive,
700
+ onClick: () => recorder.stop(),
701
+ children: s.readAloudStop
702
+ }
703
+ ) : (
704
+ // aria-disabled, never disabled, once the takes are spent: a
705
+ // natively disabled button drops keyboard focus to the page body,
706
+ // and this is the control a learner comes back to.
707
+ /* @__PURE__ */ jsxRuntime.jsx(
708
+ "button",
709
+ {
710
+ type: "button",
711
+ className: "lk-ra-record",
712
+ disabled: inactive,
713
+ "aria-disabled": !recorder.canRecord || busy || void 0,
714
+ onClick: beginTake,
715
+ children: recorder.take === null ? s.readAloudRecord : s.readAloudRerecord
716
+ }
717
+ )
718
+ ),
719
+ recorder.status === "recording" ? (
720
+ // Visible, and hidden from assistive technology, beside the level
721
+ // meter the recorder's `data-level` drives. Its text changes every
722
+ // whole second, so as a live region it announced ten times in a
723
+ // ten-second take — into the microphone. What a screen-reader
724
+ // learner needs instead is said once each, below.
725
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "lk-ra-progress", "aria-hidden": "true", children: s.readAloudRecordingProgress(Math.floor(recorder.elapsedMs / 1e3), maxSeconds) })
726
+ ) : null,
727
+ takesLeft !== void 0 && maxTakes !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "lk-ra-takes", children: s.readAloudTakesRemaining(takesLeft, maxTakes) }) : null
728
+ ]
729
+ }
730
+ ),
731
+ !isReview && takeUrl !== null ? (
732
+ // biome-ignore lint/a11y/useMediaCaption: the learner's own take has no caption track to offer; its accessible name says whose recording it is
733
+ /* @__PURE__ */ jsxRuntime.jsx(
734
+ "audio",
735
+ {
736
+ className: "lk-ra-take",
737
+ controls: true,
738
+ src: takeUrl,
739
+ "aria-label": s.readAloudYourRecording,
740
+ "data-take": recorder.takesUsed
741
+ }
742
+ )
743
+ ) : null,
744
+ isReview && playbackUrl !== null ? (
745
+ // biome-ignore lint/a11y/useMediaCaption: the learner's own take has no caption track to offer; its accessible name says whose recording it is
746
+ /* @__PURE__ */ jsxRuntime.jsx(
747
+ "audio",
748
+ {
749
+ className: "lk-ra-take",
750
+ controls: true,
751
+ src: playbackUrl,
752
+ "aria-label": s.readAloudYourRecording
753
+ }
754
+ )
755
+ ) : null,
756
+ isReview || submitted ? null : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "lk-ra-actions", children: [
757
+ /* @__PURE__ */ jsxRuntime.jsx(
758
+ "button",
759
+ {
760
+ type: "submit",
761
+ className: "lk-ra-submit",
762
+ disabled: disabled === true,
763
+ "aria-disabled": recorder.take === null || phase.kind !== "idle" || void 0,
764
+ "aria-busy": busy || void 0,
765
+ children: s.submit
766
+ }
767
+ ),
768
+ isExam ? (
769
+ // Explicit, and only here: an exam answer with no recording is a
770
+ // decision a learner takes, never something a failed upload does
771
+ // for them.
772
+ /* @__PURE__ */ jsxRuntime.jsx(
773
+ "button",
774
+ {
775
+ type: "button",
776
+ className: "lk-ra-submit-blank",
777
+ disabled: inactive,
778
+ "aria-disabled": busy || void 0,
779
+ onClick: () => {
780
+ if (!busy) {
781
+ void submitTake(null);
782
+ }
783
+ },
784
+ children: s.readAloudSubmitWithoutRecording
785
+ }
786
+ )
787
+ ) : null,
788
+ offersRetry(phase, uploaded) ? /* @__PURE__ */ jsxRuntime.jsx(
789
+ "button",
790
+ {
791
+ type: "button",
792
+ className: "lk-ra-retry",
793
+ onClick: () => {
794
+ if (phase.kind === "upload-failed") {
795
+ void submitTake(recorder.take);
796
+ } else if (phase.kind === "assess-failed" && uploaded !== null) {
797
+ void retryAssess(uploaded, phase.take);
798
+ }
799
+ },
800
+ children: s.readAloudTryAgain
801
+ }
802
+ ) : null
803
+ ] }),
804
+ busy ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "lk-ra-pending", role: "status", "aria-busy": "true", children: phase.kind === "uploading" ? s.readAloudUploading : s.readAloudAssessing }) : null,
805
+ alertText !== null ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "lk-ra-alert", role: "alert", children: alertText }, errorNonce) : null,
806
+ feedbackAssessment !== null ? /* @__PURE__ */ jsxRuntime.jsx(
807
+ chunkF4TBCRGI_cjs.PronunciationFeedback,
808
+ {
809
+ data: { referenceText: data.referenceText, locale: data.locale },
810
+ assessment: feedbackAssessment,
811
+ ...feedbackGrade !== null ? { grade: feedbackGrade } : {},
812
+ ...feedbackAudio !== null ? { audioUrl: feedbackAudio } : {},
813
+ ...breakThreshold !== void 0 ? { breakThreshold } : {},
814
+ ...monotoneThreshold !== void 0 ? { monotoneThreshold } : {},
815
+ ...locale !== void 0 ? { locale } : {},
816
+ ...strings !== void 0 ? { strings } : {}
817
+ }
818
+ ) : null,
819
+ storedMarks !== null ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "lk-ra-marks", children: [
820
+ /* @__PURE__ */ jsxRuntime.jsx("ol", { className: "lk-ra-words", "aria-label": s.pronunciationWordsLabel, dir: contentDir, children: storedMarks.map((entry, index) => {
821
+ const sentence = chunkF4TBCRGI_cjs.markSentence(entry, s);
822
+ return /* @__PURE__ */ jsxRuntime.jsxs(
823
+ "li",
824
+ {
825
+ className: "lk-ra-word",
826
+ "data-state": entry.state,
827
+ children: [
828
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "lk-visually-hidden", style: chunkF4TBCRGI_cjs.VISUALLY_HIDDEN, children: sentence }),
829
+ /* @__PURE__ */ jsxRuntime.jsx(
830
+ "span",
831
+ {
832
+ className: "lk-ra-word-text",
833
+ "aria-hidden": "true",
834
+ dir: contentDir ?? "auto",
835
+ lang: contentLang,
836
+ children: entry.reference
837
+ }
838
+ )
839
+ ]
840
+ },
841
+ index
842
+ );
843
+ }) }),
844
+ /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "lk-ra-legend", children: STORED_STATES.map((state2) => /* @__PURE__ */ jsxRuntime.jsx("li", { "data-state": state2, children: s.pronunciationLegend(state2) }, state2)) })
845
+ ] }) : null,
846
+ /* @__PURE__ */ jsxRuntime.jsx(chunkBRPEB43C_cjs.FeedbackRegion, { id: `${data.id}-feedback`, children: isReview ? outcomeReading !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(OutcomeSummary, { reading: outcomeReading, s, scoreShown: gradeShownInPanel }) : null : gradeShownInPanel ? (
847
+ // Announced, not repeated. A grade the feedback panel is already
848
+ // showing would otherwise be read on screen twice, feedback and all —
849
+ // but a live region is the only thing that TELLS a screen-reader
850
+ // learner a grade arrived, since nothing takes focus. Clipped rather
851
+ // than `display: none`, which would stop it being announced at all.
852
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "lk-visually-hidden", style: chunkF4TBCRGI_cjs.VISUALLY_HIDDEN, children: /* @__PURE__ */ jsxRuntime.jsx(AnnouncementText, { announcement }) })
853
+ ) : /* @__PURE__ */ jsxRuntime.jsx(AnnouncementText, { announcement }) })
854
+ ]
855
+ }
856
+ );
857
+ }
858
+ function ReadAloud2(props) {
859
+ const strings = chunkLB3FEV7Z_cjs.useLkStrings(props.strings);
860
+ return /* @__PURE__ */ jsxRuntime.jsx(chunkUSBANZ7K_cjs.ActivityErrorBoundary, { activityTitle: props.data?.title, strings, children: /* @__PURE__ */ jsxRuntime.jsx(ReadAloud, { ...props }) });
861
+ }
862
+
863
+ exports.ReadAloud = ReadAloud2;
864
+ exports.SequenceSlotContext = SequenceSlotContext;
865
+ exports.mintTake = mintTake;
866
+ exports.stopCaptureGroup = stopCaptureGroup;
867
+ exports.takeOf = takeOf;
868
+ //# sourceMappingURL=chunk-36FTYJWJ.cjs.map
869
+ //# sourceMappingURL=chunk-36FTYJWJ.cjs.map