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