@intellectif/lk-react 13.0.0 → 14.1.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 (164) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/README.md +11 -3
  3. package/dist/{WrittenResponse-B5p_-RnJ.d.ts → WrittenResponse-DrzI89Wg.d.ts} +1 -1
  4. package/dist/{WrittenResponse-DWtS4tOL.d.cts → WrittenResponse-rRpOr9W1.d.cts} +1 -1
  5. package/dist/{chunk-UU2NV6SU.cjs → chunk-4Y5TELVU.cjs} +14 -13
  6. package/dist/chunk-4Y5TELVU.cjs.map +1 -0
  7. package/dist/{chunk-XAGWBVK3.js → chunk-5CP2Z6IS.js} +6 -5
  8. package/dist/chunk-5CP2Z6IS.js.map +1 -0
  9. package/dist/{chunk-NVUFEMPZ.cjs → chunk-5JCTJFQU.cjs} +30 -16
  10. package/dist/chunk-5JCTJFQU.cjs.map +1 -0
  11. package/dist/chunk-5JFMXBH7.js +888 -0
  12. package/dist/chunk-5JFMXBH7.js.map +1 -0
  13. package/dist/{chunk-NVP6UEQQ.js → chunk-AS2QGND5.js} +4 -4
  14. package/dist/{chunk-NVP6UEQQ.js.map → chunk-AS2QGND5.js.map} +1 -1
  15. package/dist/{chunk-3Q4I4KT6.js → chunk-AWQ53DON.js} +78 -2
  16. package/dist/chunk-AWQ53DON.js.map +1 -0
  17. package/dist/{chunk-DQERNAWI.js → chunk-B4BCEQNO.js} +26 -31
  18. package/dist/chunk-B4BCEQNO.js.map +1 -0
  19. package/dist/chunk-BRPEB43C.cjs +13 -0
  20. package/dist/chunk-BRPEB43C.cjs.map +1 -0
  21. package/dist/{chunk-74QXBWCI.cjs → chunk-CTW3G3EH.cjs} +6 -6
  22. package/dist/{chunk-74QXBWCI.cjs.map → chunk-CTW3G3EH.cjs.map} +1 -1
  23. package/dist/{chunk-AMGK7DDM.cjs → chunk-D4DQFTA7.cjs} +2 -2
  24. package/dist/chunk-D4DQFTA7.cjs.map +1 -0
  25. package/dist/{chunk-R7DI5UMU.js → chunk-DB4DLHN6.js} +22 -4
  26. package/dist/chunk-DB4DLHN6.js.map +1 -0
  27. package/dist/{chunk-K3EA547J.cjs → chunk-DPEY7DSE.cjs} +27 -33
  28. package/dist/chunk-DPEY7DSE.cjs.map +1 -0
  29. package/dist/{chunk-W4MXRZAB.js → chunk-IDYZ2FA2.js} +6 -5
  30. package/dist/chunk-IDYZ2FA2.js.map +1 -0
  31. package/dist/{chunk-K7NMNOPI.cjs → chunk-JG4DWAUJ.cjs} +15 -14
  32. package/dist/chunk-JG4DWAUJ.cjs.map +1 -0
  33. package/dist/chunk-KIO6QXGT.cjs +894 -0
  34. package/dist/chunk-KIO6QXGT.cjs.map +1 -0
  35. package/dist/chunk-MCO52PV5.js +520 -0
  36. package/dist/chunk-MCO52PV5.js.map +1 -0
  37. package/dist/{chunk-HT4FFZZQ.cjs → chunk-MZITNWZ2.cjs} +233 -38
  38. package/dist/chunk-MZITNWZ2.cjs.map +1 -0
  39. package/dist/{chunk-Z63JRG4M.cjs → chunk-N536RUCZ.cjs} +15 -14
  40. package/dist/chunk-N536RUCZ.cjs.map +1 -0
  41. package/dist/{chunk-PSHNTDKG.js → chunk-NJAF34GN.js} +13 -161
  42. package/dist/chunk-NJAF34GN.js.map +1 -0
  43. package/dist/chunk-OD65OZIW.cjs +159 -0
  44. package/dist/chunk-OD65OZIW.cjs.map +1 -0
  45. package/dist/{chunk-2GXSA322.cjs → chunk-PSUKWVKU.cjs} +15 -14
  46. package/dist/chunk-PSUKWVKU.cjs.map +1 -0
  47. package/dist/chunk-RCMYEWBE.js +11 -0
  48. package/dist/chunk-RCMYEWBE.js.map +1 -0
  49. package/dist/{chunk-PY53ABWC.js → chunk-RCPW6HVY.js} +23 -9
  50. package/dist/chunk-RCPW6HVY.js.map +1 -0
  51. package/dist/{chunk-DQNVAXG6.js → chunk-RXS455MR.js} +2 -2
  52. package/dist/chunk-RXS455MR.js.map +1 -0
  53. package/dist/chunk-TUVEJLOT.js +537 -0
  54. package/dist/chunk-TUVEJLOT.js.map +1 -0
  55. package/dist/{chunk-YZWXVZB3.js → chunk-U432HTUW.js} +6 -5
  56. package/dist/chunk-U432HTUW.js.map +1 -0
  57. package/dist/chunk-UDCQTOVU.cjs +523 -0
  58. package/dist/chunk-UDCQTOVU.cjs.map +1 -0
  59. package/dist/{chunk-5N7OLFHB.js → chunk-UDQS5INQ.js} +6 -5
  60. package/dist/chunk-UDQS5INQ.js.map +1 -0
  61. package/dist/{chunk-YILH4ETT.js → chunk-UDZWYHDV.js} +226 -31
  62. package/dist/chunk-UDZWYHDV.js.map +1 -0
  63. package/dist/{chunk-SBIJJ374.cjs → chunk-UMDWVMB5.cjs} +24 -6
  64. package/dist/chunk-UMDWVMB5.cjs.map +1 -0
  65. package/dist/{chunk-MA7GXUWC.cjs → chunk-VSFHJ7FB.cjs} +78 -2
  66. package/dist/chunk-VSFHJ7FB.cjs.map +1 -0
  67. package/dist/{chunk-J73TDY7J.cjs → chunk-WC4OD3EE.cjs} +25 -173
  68. package/dist/chunk-WC4OD3EE.cjs.map +1 -0
  69. package/dist/chunk-YM64MS4G.cjs +547 -0
  70. package/dist/chunk-YM64MS4G.cjs.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 +115 -4
  79. package/dist/components/ActivitySequence.d.ts +115 -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 +67 -0
  100. package/dist/components/PronunciationFeedback.d.ts +67 -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.cts → strings-l8Wv1Xax.d.ts} +230 -4
  136. package/dist/{strings-DPTyLpHt.d.ts → strings-wjAF2ONm.d.cts} +230 -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 +612 -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-R7DI5UMU.js.map +0 -1
  158. package/dist/chunk-SBIJJ374.cjs.map +0 -1
  159. package/dist/chunk-UU2NV6SU.cjs.map +0 -1
  160. package/dist/chunk-W4MXRZAB.js.map +0 -1
  161. package/dist/chunk-XAGWBVK3.js.map +0 -1
  162. package/dist/chunk-YILH4ETT.js.map +0 -1
  163. package/dist/chunk-YZWXVZB3.js.map +0 -1
  164. package/dist/chunk-Z63JRG4M.cjs.map +0 -1
@@ -0,0 +1,547 @@
1
+ 'use client';
2
+ 'use strict';
3
+
4
+ var chunkOD65OZIW_cjs = require('./chunk-OD65OZIW.cjs');
5
+ var chunkDPEY7DSE_cjs = require('./chunk-DPEY7DSE.cjs');
6
+ var chunkVSFHJ7FB_cjs = require('./chunk-VSFHJ7FB.cjs');
7
+ var lkCore = require('@intellectif/lk-core');
8
+ var react = require('react');
9
+ var jsxRuntime = require('react/jsx-runtime');
10
+
11
+ function usePlaybackRefusal(url) {
12
+ const [refusedUrl, setRefusedUrl] = react.useState(null);
13
+ const refuse = react.useCallback((element) => {
14
+ const source = element.getAttribute("src");
15
+ if (source !== null) {
16
+ setRefusedUrl(source);
17
+ }
18
+ }, []);
19
+ return { refused: url !== null && url !== void 0 && url === refusedUrl, refuse };
20
+ }
21
+ function isSourceRefusal(error) {
22
+ return typeof error === "object" && error !== null && error.name === "NotSupportedError";
23
+ }
24
+
25
+ // src/components/shared/read-grade.ts
26
+ var DIMENSIONS = [
27
+ "accuracy",
28
+ "fluency",
29
+ "completeness",
30
+ "prosody"
31
+ ];
32
+ var OUTCOMES = [
33
+ "correct",
34
+ "incorrect",
35
+ "correct-omission",
36
+ "incorrect-omission"
37
+ ];
38
+ var OVERSHOOT = 1e-9;
39
+ function isRecord(value) {
40
+ return typeof value === "object" && value !== null && !Array.isArray(value);
41
+ }
42
+ function isFiniteNumber(value) {
43
+ return typeof value === "number" && Number.isFinite(value);
44
+ }
45
+ function isAbsent(value) {
46
+ return value === void 0 || value === null;
47
+ }
48
+ function withinMax(score, max) {
49
+ if (!isFiniteNumber(score) || score < 0) {
50
+ return null;
51
+ }
52
+ if (score <= max) {
53
+ return score;
54
+ }
55
+ return score - max <= max * OVERSHOOT ? max : null;
56
+ }
57
+ function isResponse(value) {
58
+ return typeof value === "string" || Array.isArray(value) && value.every((entry) => typeof entry === "string");
59
+ }
60
+ function guarded(read, refused) {
61
+ try {
62
+ return read();
63
+ } catch {
64
+ return refused;
65
+ }
66
+ }
67
+ function readCriterion(value) {
68
+ if (!isRecord(value) || typeof value.name !== "string") {
69
+ return null;
70
+ }
71
+ const { maxScore, notApplicable } = value;
72
+ if (!isAbsent(maxScore) && !(isFiniteNumber(maxScore) && maxScore > 0)) {
73
+ return null;
74
+ }
75
+ if (!isAbsent(notApplicable) && typeof notApplicable !== "boolean") {
76
+ return null;
77
+ }
78
+ const score = isAbsent(value.score) ? void 0 : withinMax(value.score, maxScore ?? 1);
79
+ if (score === null) {
80
+ return null;
81
+ }
82
+ return {
83
+ name: value.name,
84
+ ...score !== void 0 ? { score } : {},
85
+ ...!isAbsent(maxScore) ? { maxScore } : {},
86
+ ...notApplicable === true ? { notApplicable } : {}
87
+ };
88
+ }
89
+ function readDetail(value) {
90
+ if (!isRecord(value)) {
91
+ return null;
92
+ }
93
+ const { itemId, correct, outcome, learnerResponse, correctResponse, weight, score } = value;
94
+ if (typeof itemId !== "string" || typeof correct !== "boolean" || !isResponse(learnerResponse) || !isResponse(correctResponse)) {
95
+ return null;
96
+ }
97
+ const checkedOutcome = OUTCOMES.find((known) => known === outcome);
98
+ if (!isAbsent(outcome) && checkedOutcome === void 0) {
99
+ return null;
100
+ }
101
+ if (!isAbsent(weight) && !isFiniteNumber(weight)) {
102
+ return null;
103
+ }
104
+ const checkedScore = isAbsent(score) ? void 0 : withinMax(score, 1);
105
+ if (checkedScore === null) {
106
+ return null;
107
+ }
108
+ return {
109
+ itemId,
110
+ correct,
111
+ learnerResponse,
112
+ correctResponse,
113
+ ...checkedOutcome !== void 0 ? { outcome: checkedOutcome } : {},
114
+ ...!isAbsent(weight) ? { weight } : {},
115
+ ...checkedScore !== void 0 ? { score: checkedScore } : {}
116
+ };
117
+ }
118
+ function readDetails(value) {
119
+ if (!Array.isArray(value)) {
120
+ return void 0;
121
+ }
122
+ const details = [];
123
+ for (const entry of value) {
124
+ const detail = readDetail(entry);
125
+ if (detail === null) {
126
+ return void 0;
127
+ }
128
+ details.push(detail);
129
+ }
130
+ return details;
131
+ }
132
+ function readGrade(value) {
133
+ return guarded(() => {
134
+ if (!isRecord(value)) {
135
+ return null;
136
+ }
137
+ const { maxScore, passed } = value;
138
+ if (!isFiniteNumber(maxScore) || maxScore <= 0 || typeof passed !== "boolean") {
139
+ return null;
140
+ }
141
+ const score = withinMax(value.score, maxScore);
142
+ if (score === null) {
143
+ return null;
144
+ }
145
+ const criteria = Array.isArray(value.criteria) ? value.criteria.flatMap((entry) => {
146
+ const criterion = readCriterion(entry);
147
+ return criterion === null ? [] : [criterion];
148
+ }) : void 0;
149
+ const details = readDetails(value.details);
150
+ const grade = {
151
+ score,
152
+ maxScore,
153
+ passed,
154
+ feedback: typeof value.feedback === "string" ? value.feedback : null,
155
+ ...criteria !== void 0 ? { criteria } : {},
156
+ ...details !== void 0 ? { details } : {}
157
+ };
158
+ return grade;
159
+ }, null);
160
+ }
161
+ function fractionOfGrade(grade) {
162
+ return grade.score / grade.maxScore;
163
+ }
164
+ function percentOfGrade(grade) {
165
+ return Math.round(fractionOfGrade(grade) * 100);
166
+ }
167
+ function readOutcome(value) {
168
+ if (isAbsent(value)) {
169
+ return void 0;
170
+ }
171
+ return guarded(
172
+ () => {
173
+ if (!isRecord(value)) {
174
+ return { kind: "unreadable", status: void 0 };
175
+ }
176
+ switch (value.status) {
177
+ case "deferred":
178
+ return { kind: "deferred" };
179
+ case "unscorable":
180
+ return {
181
+ kind: "unscorable",
182
+ code: typeof value.code === "string" ? value.code : void 0
183
+ };
184
+ case "scored": {
185
+ const grade = readGrade(value);
186
+ return grade === null ? { kind: "unreadable", status: "scored" } : { kind: "graded", status: "scored", grade };
187
+ }
188
+ case "graded": {
189
+ const grade = readGrade(value.grade);
190
+ return grade === null || readGrade(value) === null ? { kind: "unreadable", status: "graded" } : { kind: "graded", status: "graded", grade };
191
+ }
192
+ default:
193
+ return { kind: "unreadable", status: void 0 };
194
+ }
195
+ },
196
+ { kind: "unreadable", status: void 0 }
197
+ );
198
+ }
199
+ function readAssessResult(value) {
200
+ const nothing = {
201
+ status: "graded",
202
+ grade: null,
203
+ assessment: null,
204
+ recognizedText: void 0
205
+ };
206
+ return guarded(() => {
207
+ if (!isRecord(value)) {
208
+ return nothing;
209
+ }
210
+ if (value.status === "failed") {
211
+ return { status: "failed", retryable: value.retryable === true };
212
+ }
213
+ if (value.status === "unscorable") {
214
+ return {
215
+ status: "unscorable",
216
+ code: typeof value.code === "string" ? value.code : void 0
217
+ };
218
+ }
219
+ if (value.status !== "graded") {
220
+ return nothing;
221
+ }
222
+ const assessment = readEvidence(value.assessment);
223
+ const recognizedText = guarded(
224
+ () => typeof assessment?.recognizedText === "string" ? assessment.recognizedText : void 0,
225
+ void 0
226
+ );
227
+ return { status: "graded", grade: readGrade(value.grade), assessment, recognizedText };
228
+ }, nothing);
229
+ }
230
+ function readEvidence(value) {
231
+ return isRecord(value) ? value : null;
232
+ }
233
+ function percentOfCriterion(criterion) {
234
+ if (criterion.notApplicable === true || criterion.score === void 0) {
235
+ return void 0;
236
+ }
237
+ return Math.round(criterion.score / (criterion.maxScore ?? 1) * 100);
238
+ }
239
+ function readDimensions(assessment, grade) {
240
+ const criteria = grade?.criteria ?? [];
241
+ if (criteria.length > 0) {
242
+ return DIMENSIONS.map((dimension) => {
243
+ const criterion = criteria.find((entry) => entry.name === dimension);
244
+ return {
245
+ dimension,
246
+ percent: criterion === void 0 ? void 0 : percentOfCriterion(criterion)
247
+ };
248
+ });
249
+ }
250
+ const scores = guarded(() => isRecord(assessment) ? assessment.scores : void 0, void 0);
251
+ return DIMENSIONS.map((dimension) => {
252
+ const score = withinMax(
253
+ guarded(() => isRecord(scores) ? scores[dimension] : void 0, void 0),
254
+ 100
255
+ );
256
+ return { dimension, percent: score === null ? void 0 : Math.round(score) };
257
+ });
258
+ }
259
+ var VISUALLY_HIDDEN = {
260
+ position: "absolute",
261
+ width: 1,
262
+ height: 1,
263
+ margin: -1,
264
+ padding: 0,
265
+ overflow: "hidden",
266
+ clip: "rect(0 0 0 0)",
267
+ clipPath: "inset(50%)",
268
+ whiteSpace: "nowrap",
269
+ border: 0
270
+ };
271
+ var LEGEND_STATES = [
272
+ "correct",
273
+ "mispronounced",
274
+ "omitted",
275
+ "inserted"
276
+ ];
277
+ function checkEvidence(assessment) {
278
+ try {
279
+ return lkCore.validateSpeechAssessment(assessment);
280
+ } catch {
281
+ return {
282
+ success: false,
283
+ errors: [{ path: [], message: "The assessment could not be read.", code: "unreadable" }]
284
+ };
285
+ }
286
+ }
287
+ function hasDetail(word) {
288
+ if (word === void 0) {
289
+ return false;
290
+ }
291
+ return word.accuracy !== void 0 || word.syllables !== void 0 && word.syllables.length > 0 || word.phonemes !== void 0 && word.phonemes.length > 0 || word.breaks !== void 0 || typeof word.startMs === "number" && typeof word.durationMs === "number";
292
+ }
293
+ function past(confidence, threshold) {
294
+ return threshold !== void 0 && typeof confidence === "number" && Number.isFinite(confidence) && confidence > threshold;
295
+ }
296
+ function phonemeLabel(phoneme, index, s) {
297
+ return phoneme.symbol ?? s.pronunciationPhonemePosition(index + 1);
298
+ }
299
+ function markSentence(entry, s) {
300
+ switch (entry.state) {
301
+ case "correct":
302
+ return s.pronunciationWordCorrect(entry.reference);
303
+ case "mispronounced":
304
+ return s.pronunciationWordMispronounced(entry.reference);
305
+ case "omitted":
306
+ return s.pronunciationWordOmitted(entry.reference);
307
+ default:
308
+ return s.pronunciationWordInserted(entry.heard);
309
+ }
310
+ }
311
+ function PronunciationFeedback({
312
+ data,
313
+ assessment,
314
+ grade,
315
+ audioUrl,
316
+ breakThreshold,
317
+ monotoneThreshold,
318
+ locale,
319
+ theme,
320
+ strings
321
+ }) {
322
+ const s = chunkVSFHJ7FB_cjs.useLkStrings(strings);
323
+ const ids = react.useId();
324
+ const [openWord, setOpenWord] = react.useState(null);
325
+ const checked = react.useMemo(() => checkEvidence(assessment), [assessment]);
326
+ const devError = react.useMemo(
327
+ () => chunkDPEY7DSE_cjs.isDevelopment() && !checked.success ? new lkCore.ActivitySchemaError("read-aloud", checked.errors) : null,
328
+ [checked]
329
+ );
330
+ const words = react.useMemo(
331
+ () => checked.success ? lkCore.alignReadAloud(data, checked.data) : null,
332
+ [checked, data]
333
+ );
334
+ const reading = react.useMemo(
335
+ () => grade === void 0 || grade === null ? void 0 : readGrade(grade),
336
+ [grade]
337
+ );
338
+ const dimensions = react.useMemo(() => readDimensions(assessment, reading), [assessment, reading]);
339
+ const audioRef = react.useRef(null);
340
+ const stopRef = react.useRef(null);
341
+ const playback = usePlaybackRefusal(audioUrl);
342
+ const playable = audioUrl !== void 0 && !playback.refused;
343
+ react.useEffect(
344
+ () => () => {
345
+ if (stopRef.current !== null) {
346
+ clearTimeout(stopRef.current);
347
+ }
348
+ },
349
+ []
350
+ );
351
+ if (devError) {
352
+ throw devError;
353
+ }
354
+ const playWord = (startMs, durationMs) => {
355
+ const element = audioRef.current;
356
+ if (element === null) {
357
+ return;
358
+ }
359
+ if (stopRef.current !== null) {
360
+ clearTimeout(stopRef.current);
361
+ }
362
+ element.currentTime = startMs / 1e3;
363
+ try {
364
+ void element.play().catch((error) => {
365
+ if (isSourceRefusal(error)) {
366
+ playback.refuse(element);
367
+ }
368
+ });
369
+ } catch {
370
+ }
371
+ stopRef.current = setTimeout(() => {
372
+ stopRef.current = null;
373
+ element.pause();
374
+ }, durationMs);
375
+ };
376
+ const contentLang = typeof data.locale === "string" ? data.locale : void 0;
377
+ const contentDir = chunkOD65OZIW_cjs.localeDirectionOf(data.locale);
378
+ const alphabet = checked.success ? checked.data.phonemeAlphabet : void 0;
379
+ const monotone = checked.success ? checked.data.prosody?.monotoneConfidence : void 0;
380
+ const evidenceWords = checked.success ? checked.data.words : [];
381
+ return /* @__PURE__ */ jsxRuntime.jsxs(
382
+ "section",
383
+ {
384
+ className: "lk-pf",
385
+ "aria-label": s.pronunciationFeedbackLabel,
386
+ lang: locale,
387
+ style: theme,
388
+ children: [
389
+ reading !== void 0 ? (
390
+ // `.lk-pf-grade` is load-bearing beyond the skin: `<ReadAloud>` looks
391
+ // for it after the commit to decide whether it must render the score
392
+ // itself, so a grade is never lost between the two components. It
393
+ // hands this panel only a grade it has already read, so the block it
394
+ // finds always carries the score.
395
+ //
396
+ // `data-passed` only where there is a verdict: a grade that could not
397
+ // be read has none, and styling it as a fail would invent one.
398
+ /* @__PURE__ */ jsxRuntime.jsxs(
399
+ "div",
400
+ {
401
+ className: "lk-pf-grade",
402
+ ...reading !== null ? { "data-passed": String(reading.passed) } : {},
403
+ children: [
404
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "lk-pf-score", children: reading === null ? s.couldNotBeGraded : s.scoreAnnouncement(percentOfGrade(reading), reading.passed) }),
405
+ reading?.feedback ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "lk-pf-grade-feedback", dir: "auto", children: reading.feedback }) : null
406
+ ]
407
+ }
408
+ )
409
+ ) : null,
410
+ /* @__PURE__ */ jsxRuntime.jsx("dl", { className: "lk-pf-dimensions", children: dimensions.map(({ dimension, percent }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "lk-pf-dimension", "data-dimension": dimension, children: [
411
+ /* @__PURE__ */ jsxRuntime.jsx("dt", { className: "lk-pf-dimension-name", children: s.pronunciationDimension(dimension) }),
412
+ /* @__PURE__ */ jsxRuntime.jsx("dd", { className: "lk-pf-dimension-score", children: percent === void 0 ? s.pronunciationNotAssessed : `${percent}%` })
413
+ ] }, dimension)) }),
414
+ words !== null ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
415
+ /* @__PURE__ */ jsxRuntime.jsx("ol", { className: "lk-pf-words", "aria-label": s.pronunciationWordsLabel, dir: contentDir, children: words.map((entry, index) => {
416
+ const sentence = markSentence(entry, s);
417
+ const shown = entry.state === "inserted" ? entry.heard : entry.reference;
418
+ const word = entry.wordIndex === void 0 ? void 0 : evidenceWords.at(entry.wordIndex);
419
+ const open = openWord === index;
420
+ const panelId = `${ids}-w${index}`;
421
+ return /* @__PURE__ */ jsxRuntime.jsxs(
422
+ "li",
423
+ {
424
+ className: "lk-pf-word",
425
+ "data-state": entry.state,
426
+ children: [
427
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "lk-visually-hidden", style: VISUALLY_HIDDEN, children: sentence }),
428
+ /* @__PURE__ */ jsxRuntime.jsx(
429
+ "span",
430
+ {
431
+ className: "lk-pf-word-text",
432
+ "aria-hidden": "true",
433
+ dir: contentDir ?? "auto",
434
+ lang: contentLang,
435
+ children: shown
436
+ }
437
+ ),
438
+ hasDetail(word) ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
439
+ /* @__PURE__ */ jsxRuntime.jsx(
440
+ "button",
441
+ {
442
+ type: "button",
443
+ className: "lk-pf-word-toggle",
444
+ "aria-expanded": open,
445
+ "aria-controls": panelId,
446
+ onClick: () => setOpenWord(open ? null : index),
447
+ children: s.pronunciationWordDetails(shown)
448
+ }
449
+ ),
450
+ open ? /* @__PURE__ */ jsxRuntime.jsxs("div", { id: panelId, className: "lk-pf-word-detail", children: [
451
+ /* @__PURE__ */ jsxRuntime.jsxs("dl", { className: "lk-pf-word-facts", children: [
452
+ /* @__PURE__ */ jsxRuntime.jsx("dt", { children: s.pronunciationDimension("accuracy") }),
453
+ /* @__PURE__ */ jsxRuntime.jsx("dd", { children: word.accuracy === void 0 ? s.pronunciationNotAssessed : `${Math.round(word.accuracy)}%` }),
454
+ word.syllables !== void 0 && word.syllables.length > 0 ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
455
+ /* @__PURE__ */ jsxRuntime.jsx("dt", { children: s.pronunciationSyllables }),
456
+ /* @__PURE__ */ jsxRuntime.jsx("dd", { children: /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "lk-pf-syllables", children: word.syllables.map((syllable, position) => /* @__PURE__ */ jsxRuntime.jsx(
457
+ "li",
458
+ {
459
+ dir: contentDir ?? "auto",
460
+ lang: contentLang,
461
+ children: syllable.grapheme === void 0 ? syllable.text : s.pronunciationSyllableSpelling(
462
+ syllable.text,
463
+ syllable.grapheme
464
+ )
465
+ },
466
+ position
467
+ )) }) })
468
+ ] }) : null,
469
+ word.phonemes !== void 0 && word.phonemes.length > 0 ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
470
+ /* @__PURE__ */ jsxRuntime.jsx("dt", { children: s.pronunciationPhonemes }),
471
+ /* @__PURE__ */ jsxRuntime.jsx("dd", { children: /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "lk-pf-phonemes", children: word.phonemes.map((phoneme, position) => /* @__PURE__ */ jsxRuntime.jsxs(
472
+ "li",
473
+ {
474
+ className: "lk-pf-phoneme",
475
+ children: [
476
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "lk-pf-phoneme-symbol", children: phonemeLabel(phoneme, position, s) }),
477
+ phoneme.heardAs !== void 0 && phoneme.heardAs.length > 0 ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
478
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "lk-pf-heard-label", children: s.pronunciationHeardAs }),
479
+ /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "lk-pf-heard-as", children: phoneme.heardAs.map((candidate, rank) => /* @__PURE__ */ jsxRuntime.jsx(
480
+ "li",
481
+ {
482
+ children: candidate.symbol
483
+ },
484
+ rank
485
+ )) })
486
+ ] }) : null
487
+ ]
488
+ },
489
+ position
490
+ )) }) })
491
+ ] }) : null
492
+ ] }),
493
+ playable && typeof word.startMs === "number" && typeof word.durationMs === "number" ? /* @__PURE__ */ jsxRuntime.jsx(
494
+ "button",
495
+ {
496
+ type: "button",
497
+ className: "lk-pf-word-play",
498
+ onClick: () => playWord(word.startMs, word.durationMs),
499
+ children: s.pronunciationPlayWord(shown)
500
+ }
501
+ ) : null,
502
+ past(word.breaks?.unexpected, breakThreshold) ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "lk-pf-break", "data-break": "unexpected", children: s.pronunciationBreakUnexpected }) : null,
503
+ past(word.breaks?.missing, breakThreshold) ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "lk-pf-break", "data-break": "missing", children: s.pronunciationBreakMissing }) : null
504
+ ] }) : null
505
+ ] }) : null
506
+ ]
507
+ },
508
+ index
509
+ );
510
+ }) }),
511
+ /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "lk-pf-legend", children: LEGEND_STATES.map((state) => /* @__PURE__ */ jsxRuntime.jsx("li", { "data-state": state, children: s.pronunciationLegend(state) }, state)) }),
512
+ alphabet === "ipa" ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "lk-pf-alphabet", children: s.pronunciationIpaNote }) : null
513
+ ] }) : null,
514
+ past(monotone, monotoneThreshold) ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "lk-pf-monotone", role: "note", children: s.pronunciationMonotone }) : null,
515
+ audioUrl === void 0 ? null : playback.refused ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "lk-pf-audio-unavailable", role: "note", children: s.readAloudPlaybackUnavailable }) : (
516
+ // biome-ignore lint/a11y/useMediaCaption: the learner's own take has no caption track to offer, and this element is never played as content — it carries no controls and exists only so one word can be replayed from a button that names it
517
+ /* @__PURE__ */ jsxRuntime.jsx(
518
+ "audio",
519
+ {
520
+ ref: audioRef,
521
+ className: "lk-pf-audio",
522
+ src: audioUrl,
523
+ preload: "metadata",
524
+ onError: (event) => playback.refuse(event.currentTarget)
525
+ }
526
+ )
527
+ )
528
+ ]
529
+ }
530
+ );
531
+ }
532
+ function PronunciationFeedback2(props) {
533
+ const strings = chunkVSFHJ7FB_cjs.useLkStrings(props.strings);
534
+ return /* @__PURE__ */ jsxRuntime.jsx(chunkDPEY7DSE_cjs.ActivityErrorBoundary, { strings, children: /* @__PURE__ */ jsxRuntime.jsx(PronunciationFeedback, { ...props }) });
535
+ }
536
+
537
+ exports.PronunciationFeedback = PronunciationFeedback2;
538
+ exports.VISUALLY_HIDDEN = VISUALLY_HIDDEN;
539
+ exports.fractionOfGrade = fractionOfGrade;
540
+ exports.markSentence = markSentence;
541
+ exports.percentOfGrade = percentOfGrade;
542
+ exports.readAssessResult = readAssessResult;
543
+ exports.readEvidence = readEvidence;
544
+ exports.readOutcome = readOutcome;
545
+ exports.usePlaybackRefusal = usePlaybackRefusal;
546
+ //# sourceMappingURL=chunk-YM64MS4G.cjs.map
547
+ //# sourceMappingURL=chunk-YM64MS4G.cjs.map