@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.
- package/CHANGELOG.md +44 -0
- package/README.md +11 -3
- package/dist/{WrittenResponse-B5p_-RnJ.d.ts → WrittenResponse-C0P34En1.d.ts} +1 -1
- package/dist/{WrittenResponse-DWtS4tOL.d.cts → WrittenResponse-pB1ik8hg.d.cts} +1 -1
- package/dist/chunk-36FTYJWJ.cjs +869 -0
- package/dist/chunk-36FTYJWJ.cjs.map +1 -0
- package/dist/{chunk-PSHNTDKG.js → chunk-47HHC4GD.js} +13 -161
- package/dist/chunk-47HHC4GD.js.map +1 -0
- package/dist/chunk-62AKSD5Y.js +509 -0
- package/dist/chunk-62AKSD5Y.js.map +1 -0
- package/dist/{chunk-XAGWBVK3.js → chunk-72UGRIMM.js} +6 -5
- package/dist/chunk-72UGRIMM.js.map +1 -0
- package/dist/{chunk-SBIJJ374.cjs → chunk-B2XQLU2E.cjs} +5 -5
- package/dist/{chunk-SBIJJ374.cjs.map → chunk-B2XQLU2E.cjs.map} +1 -1
- package/dist/{chunk-DQERNAWI.js → chunk-BO6P723E.js} +26 -31
- package/dist/chunk-BO6P723E.js.map +1 -0
- package/dist/chunk-BRPEB43C.cjs +13 -0
- package/dist/chunk-BRPEB43C.cjs.map +1 -0
- package/dist/{chunk-NVUFEMPZ.cjs → chunk-BVRUUYYR.cjs} +30 -16
- package/dist/chunk-BVRUUYYR.cjs.map +1 -0
- package/dist/{chunk-AMGK7DDM.cjs → chunk-D4DQFTA7.cjs} +2 -2
- package/dist/chunk-D4DQFTA7.cjs.map +1 -0
- package/dist/chunk-D57QZILZ.js +863 -0
- package/dist/chunk-D57QZILZ.js.map +1 -0
- package/dist/chunk-F4TBCRGI.cjs +518 -0
- package/dist/chunk-F4TBCRGI.cjs.map +1 -0
- package/dist/{chunk-YILH4ETT.js → chunk-I33FOKZ7.js} +224 -31
- package/dist/chunk-I33FOKZ7.js.map +1 -0
- package/dist/{chunk-HT4FFZZQ.cjs → chunk-J2CDPIRC.cjs} +231 -38
- package/dist/chunk-J2CDPIRC.cjs.map +1 -0
- package/dist/{chunk-MA7GXUWC.cjs → chunk-LB3FEV7Z.cjs} +77 -2
- package/dist/chunk-LB3FEV7Z.cjs.map +1 -0
- package/dist/{chunk-5N7OLFHB.js → chunk-LGCUOLP6.js} +6 -5
- package/dist/chunk-LGCUOLP6.js.map +1 -0
- package/dist/{chunk-YZWXVZB3.js → chunk-LLXCX5KK.js} +6 -5
- package/dist/chunk-LLXCX5KK.js.map +1 -0
- package/dist/chunk-MCO52PV5.js +520 -0
- package/dist/chunk-MCO52PV5.js.map +1 -0
- package/dist/{chunk-K7NMNOPI.cjs → chunk-NDQD3ZRY.cjs} +15 -14
- package/dist/chunk-NDQD3ZRY.cjs.map +1 -0
- package/dist/chunk-OD65OZIW.cjs +159 -0
- package/dist/chunk-OD65OZIW.cjs.map +1 -0
- package/dist/chunk-RCMYEWBE.js +11 -0
- package/dist/chunk-RCMYEWBE.js.map +1 -0
- package/dist/{chunk-74QXBWCI.cjs → chunk-RRH7ZPXC.cjs} +6 -6
- package/dist/{chunk-74QXBWCI.cjs.map → chunk-RRH7ZPXC.cjs.map} +1 -1
- package/dist/{chunk-DQNVAXG6.js → chunk-RXS455MR.js} +2 -2
- package/dist/chunk-RXS455MR.js.map +1 -0
- package/dist/{chunk-Z63JRG4M.cjs → chunk-SE6WWDVW.cjs} +15 -14
- package/dist/chunk-SE6WWDVW.cjs.map +1 -0
- package/dist/{chunk-NVP6UEQQ.js → chunk-SWY6PWXK.js} +4 -4
- package/dist/{chunk-NVP6UEQQ.js.map → chunk-SWY6PWXK.js.map} +1 -1
- package/dist/{chunk-3Q4I4KT6.js → chunk-TDJEKXEP.js} +77 -2
- package/dist/chunk-TDJEKXEP.js.map +1 -0
- package/dist/{chunk-2GXSA322.cjs → chunk-U3APKQ7P.cjs} +15 -14
- package/dist/chunk-U3APKQ7P.cjs.map +1 -0
- package/dist/chunk-UDCQTOVU.cjs +523 -0
- package/dist/chunk-UDCQTOVU.cjs.map +1 -0
- package/dist/{chunk-K3EA547J.cjs → chunk-USBANZ7K.cjs} +27 -33
- package/dist/chunk-USBANZ7K.cjs.map +1 -0
- package/dist/{chunk-J73TDY7J.cjs → chunk-WTS34S5E.cjs} +25 -173
- package/dist/chunk-WTS34S5E.cjs.map +1 -0
- package/dist/{chunk-R7DI5UMU.js → chunk-XIBOFTOL.js} +3 -3
- package/dist/{chunk-R7DI5UMU.js.map → chunk-XIBOFTOL.js.map} +1 -1
- package/dist/{chunk-W4MXRZAB.js → chunk-XUEIUEPI.js} +6 -5
- package/dist/chunk-XUEIUEPI.js.map +1 -0
- package/dist/{chunk-UU2NV6SU.cjs → chunk-YEYKJFEC.cjs} +14 -13
- package/dist/chunk-YEYKJFEC.cjs.map +1 -0
- package/dist/{chunk-PY53ABWC.js → chunk-YMBBM5K7.js} +23 -9
- package/dist/chunk-YMBBM5K7.js.map +1 -0
- package/dist/chunk-YORQQXGO.js +155 -0
- package/dist/chunk-YORQQXGO.js.map +1 -0
- package/dist/components/ActivityPreview.cjs +15 -10
- package/dist/components/ActivityPreview.d.cts +9 -3
- package/dist/components/ActivityPreview.d.ts +9 -3
- package/dist/components/ActivityPreview.js +14 -9
- package/dist/components/ActivitySequence.cjs +16 -11
- package/dist/components/ActivitySequence.d.cts +108 -4
- package/dist/components/ActivitySequence.d.ts +108 -4
- package/dist/components/ActivitySequence.js +15 -10
- package/dist/components/Dictation.cjs +7 -5
- package/dist/components/Dictation.d.cts +2 -1
- package/dist/components/Dictation.d.ts +2 -1
- package/dist/components/Dictation.js +6 -4
- package/dist/components/FillInTheBlanks.cjs +6 -5
- package/dist/components/FillInTheBlanks.d.cts +2 -1
- package/dist/components/FillInTheBlanks.d.ts +2 -1
- package/dist/components/FillInTheBlanks.js +5 -4
- package/dist/components/GapSelect.cjs +6 -5
- package/dist/components/GapSelect.d.cts +2 -1
- package/dist/components/GapSelect.d.ts +2 -1
- package/dist/components/GapSelect.js +5 -4
- package/dist/components/MultipleChoice.cjs +6 -5
- package/dist/components/MultipleChoice.d.cts +2 -1
- package/dist/components/MultipleChoice.d.ts +2 -1
- package/dist/components/MultipleChoice.js +5 -4
- package/dist/components/PronunciationFeedback.cjs +16 -0
- package/dist/components/PronunciationFeedback.cjs.map +1 -0
- package/dist/components/PronunciationFeedback.d.cts +64 -0
- package/dist/components/PronunciationFeedback.d.ts +64 -0
- package/dist/components/PronunciationFeedback.js +7 -0
- package/dist/components/PronunciationFeedback.js.map +1 -0
- package/dist/components/ReadAloud.cjs +21 -0
- package/dist/components/ReadAloud.cjs.map +1 -0
- package/dist/components/ReadAloud.d.cts +15 -0
- package/dist/components/ReadAloud.d.ts +15 -0
- package/dist/components/ReadAloud.js +12 -0
- package/dist/components/ReadAloud.js.map +1 -0
- package/dist/components/StimulusPanel.cjs +4 -4
- package/dist/components/StimulusPanel.d.cts +2 -1
- package/dist/components/StimulusPanel.d.ts +2 -1
- package/dist/components/StimulusPanel.js +3 -3
- package/dist/components/WrittenResponse.cjs +6 -5
- package/dist/components/WrittenResponse.d.cts +4 -3
- package/dist/components/WrittenResponse.d.ts +4 -3
- package/dist/components/WrittenResponse.js +5 -4
- package/dist/hooks/useSpeechRecorder.cjs +17 -0
- package/dist/hooks/useSpeechRecorder.cjs.map +1 -0
- package/dist/hooks/useSpeechRecorder.d.cts +127 -0
- package/dist/hooks/useSpeechRecorder.d.ts +127 -0
- package/dist/hooks/useSpeechRecorder.js +4 -0
- package/dist/hooks/useSpeechRecorder.js.map +1 -0
- package/dist/i18n/LkIntlProvider.cjs +7 -7
- package/dist/i18n/LkIntlProvider.d.cts +3 -2
- package/dist/i18n/LkIntlProvider.d.ts +3 -2
- package/dist/i18n/LkIntlProvider.js +1 -1
- package/dist/index.cjs +53 -32
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -2
- package/dist/index.d.ts +6 -2
- package/dist/index.js +17 -12
- package/dist/index.js.map +1 -1
- package/dist/machine-CuwKqWXC.d.cts +26 -0
- package/dist/machine-CuwKqWXC.d.ts +26 -0
- package/dist/{strings-DPTyLpHt.d.ts → strings--ADnHcaA.d.cts} +214 -4
- package/dist/{strings-DPTyLpHt.d.cts → strings-CtdSf2St.d.ts} +214 -4
- package/dist/theme/ThemeProvider.cjs +6 -6
- package/dist/theme/ThemeProvider.d.cts +4 -4
- package/dist/theme/ThemeProvider.d.ts +4 -4
- package/dist/theme/ThemeProvider.js +1 -1
- package/dist/theme/skin.css +596 -3
- package/package.json +33 -3
- package/dist/chunk-2GXSA322.cjs.map +0 -1
- package/dist/chunk-3Q4I4KT6.js.map +0 -1
- package/dist/chunk-5N7OLFHB.js.map +0 -1
- package/dist/chunk-AMGK7DDM.cjs.map +0 -1
- package/dist/chunk-DQERNAWI.js.map +0 -1
- package/dist/chunk-DQNVAXG6.js.map +0 -1
- package/dist/chunk-HT4FFZZQ.cjs.map +0 -1
- package/dist/chunk-J73TDY7J.cjs.map +0 -1
- package/dist/chunk-K3EA547J.cjs.map +0 -1
- package/dist/chunk-K7NMNOPI.cjs.map +0 -1
- package/dist/chunk-MA7GXUWC.cjs.map +0 -1
- package/dist/chunk-NVUFEMPZ.cjs.map +0 -1
- package/dist/chunk-PSHNTDKG.js.map +0 -1
- package/dist/chunk-PY53ABWC.js.map +0 -1
- package/dist/chunk-UU2NV6SU.cjs.map +0 -1
- package/dist/chunk-W4MXRZAB.js.map +0 -1
- package/dist/chunk-XAGWBVK3.js.map +0 -1
- package/dist/chunk-YILH4ETT.js.map +0 -1
- package/dist/chunk-YZWXVZB3.js.map +0 -1
- package/dist/chunk-Z63JRG4M.cjs.map +0 -1
|
@@ -0,0 +1,518 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
var chunkOD65OZIW_cjs = require('./chunk-OD65OZIW.cjs');
|
|
5
|
+
var chunkUSBANZ7K_cjs = require('./chunk-USBANZ7K.cjs');
|
|
6
|
+
var chunkLB3FEV7Z_cjs = require('./chunk-LB3FEV7Z.cjs');
|
|
7
|
+
var lkCore = require('@intellectif/lk-core');
|
|
8
|
+
var react = require('react');
|
|
9
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
10
|
+
|
|
11
|
+
// src/components/shared/read-grade.ts
|
|
12
|
+
var DIMENSIONS = [
|
|
13
|
+
"accuracy",
|
|
14
|
+
"fluency",
|
|
15
|
+
"completeness",
|
|
16
|
+
"prosody"
|
|
17
|
+
];
|
|
18
|
+
var OUTCOMES = [
|
|
19
|
+
"correct",
|
|
20
|
+
"incorrect",
|
|
21
|
+
"correct-omission",
|
|
22
|
+
"incorrect-omission"
|
|
23
|
+
];
|
|
24
|
+
var OVERSHOOT = 1e-9;
|
|
25
|
+
function isRecord(value) {
|
|
26
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
27
|
+
}
|
|
28
|
+
function isFiniteNumber(value) {
|
|
29
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
30
|
+
}
|
|
31
|
+
function isAbsent(value) {
|
|
32
|
+
return value === void 0 || value === null;
|
|
33
|
+
}
|
|
34
|
+
function withinMax(score, max) {
|
|
35
|
+
if (!isFiniteNumber(score) || score < 0) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
if (score <= max) {
|
|
39
|
+
return score;
|
|
40
|
+
}
|
|
41
|
+
return score - max <= max * OVERSHOOT ? max : null;
|
|
42
|
+
}
|
|
43
|
+
function isResponse(value) {
|
|
44
|
+
return typeof value === "string" || Array.isArray(value) && value.every((entry) => typeof entry === "string");
|
|
45
|
+
}
|
|
46
|
+
function guarded(read, refused) {
|
|
47
|
+
try {
|
|
48
|
+
return read();
|
|
49
|
+
} catch {
|
|
50
|
+
return refused;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function readCriterion(value) {
|
|
54
|
+
if (!isRecord(value) || typeof value.name !== "string") {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
const { maxScore, notApplicable } = value;
|
|
58
|
+
if (!isAbsent(maxScore) && !(isFiniteNumber(maxScore) && maxScore > 0)) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
if (!isAbsent(notApplicable) && typeof notApplicable !== "boolean") {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
const score = isAbsent(value.score) ? void 0 : withinMax(value.score, maxScore ?? 1);
|
|
65
|
+
if (score === null) {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
name: value.name,
|
|
70
|
+
...score !== void 0 ? { score } : {},
|
|
71
|
+
...!isAbsent(maxScore) ? { maxScore } : {},
|
|
72
|
+
...notApplicable === true ? { notApplicable } : {}
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
function readDetail(value) {
|
|
76
|
+
if (!isRecord(value)) {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
const { itemId, correct, outcome, learnerResponse, correctResponse, weight, score } = value;
|
|
80
|
+
if (typeof itemId !== "string" || typeof correct !== "boolean" || !isResponse(learnerResponse) || !isResponse(correctResponse)) {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
const checkedOutcome = OUTCOMES.find((known) => known === outcome);
|
|
84
|
+
if (!isAbsent(outcome) && checkedOutcome === void 0) {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
if (!isAbsent(weight) && !isFiniteNumber(weight)) {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
const checkedScore = isAbsent(score) ? void 0 : withinMax(score, 1);
|
|
91
|
+
if (checkedScore === null) {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
itemId,
|
|
96
|
+
correct,
|
|
97
|
+
learnerResponse,
|
|
98
|
+
correctResponse,
|
|
99
|
+
...checkedOutcome !== void 0 ? { outcome: checkedOutcome } : {},
|
|
100
|
+
...!isAbsent(weight) ? { weight } : {},
|
|
101
|
+
...checkedScore !== void 0 ? { score: checkedScore } : {}
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
function readDetails(value) {
|
|
105
|
+
if (!Array.isArray(value)) {
|
|
106
|
+
return void 0;
|
|
107
|
+
}
|
|
108
|
+
const details = [];
|
|
109
|
+
for (const entry of value) {
|
|
110
|
+
const detail = readDetail(entry);
|
|
111
|
+
if (detail === null) {
|
|
112
|
+
return void 0;
|
|
113
|
+
}
|
|
114
|
+
details.push(detail);
|
|
115
|
+
}
|
|
116
|
+
return details;
|
|
117
|
+
}
|
|
118
|
+
function readGrade(value) {
|
|
119
|
+
return guarded(() => {
|
|
120
|
+
if (!isRecord(value)) {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
const { maxScore, passed } = value;
|
|
124
|
+
if (!isFiniteNumber(maxScore) || maxScore <= 0 || typeof passed !== "boolean") {
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
const score = withinMax(value.score, maxScore);
|
|
128
|
+
if (score === null) {
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
const criteria = Array.isArray(value.criteria) ? value.criteria.flatMap((entry) => {
|
|
132
|
+
const criterion = readCriterion(entry);
|
|
133
|
+
return criterion === null ? [] : [criterion];
|
|
134
|
+
}) : void 0;
|
|
135
|
+
const details = readDetails(value.details);
|
|
136
|
+
const grade = {
|
|
137
|
+
score,
|
|
138
|
+
maxScore,
|
|
139
|
+
passed,
|
|
140
|
+
feedback: typeof value.feedback === "string" ? value.feedback : null,
|
|
141
|
+
...criteria !== void 0 ? { criteria } : {},
|
|
142
|
+
...details !== void 0 ? { details } : {}
|
|
143
|
+
};
|
|
144
|
+
return grade;
|
|
145
|
+
}, null);
|
|
146
|
+
}
|
|
147
|
+
function fractionOfGrade(grade) {
|
|
148
|
+
return grade.score / grade.maxScore;
|
|
149
|
+
}
|
|
150
|
+
function percentOfGrade(grade) {
|
|
151
|
+
return Math.round(fractionOfGrade(grade) * 100);
|
|
152
|
+
}
|
|
153
|
+
function readOutcome(value) {
|
|
154
|
+
if (isAbsent(value)) {
|
|
155
|
+
return void 0;
|
|
156
|
+
}
|
|
157
|
+
return guarded(
|
|
158
|
+
() => {
|
|
159
|
+
if (!isRecord(value)) {
|
|
160
|
+
return { kind: "unreadable", status: void 0 };
|
|
161
|
+
}
|
|
162
|
+
switch (value.status) {
|
|
163
|
+
case "deferred":
|
|
164
|
+
return { kind: "deferred" };
|
|
165
|
+
case "unscorable":
|
|
166
|
+
return {
|
|
167
|
+
kind: "unscorable",
|
|
168
|
+
code: typeof value.code === "string" ? value.code : void 0
|
|
169
|
+
};
|
|
170
|
+
case "scored": {
|
|
171
|
+
const grade = readGrade(value);
|
|
172
|
+
return grade === null ? { kind: "unreadable", status: "scored" } : { kind: "graded", status: "scored", grade };
|
|
173
|
+
}
|
|
174
|
+
case "graded": {
|
|
175
|
+
const grade = readGrade(value.grade);
|
|
176
|
+
return grade === null || readGrade(value) === null ? { kind: "unreadable", status: "graded" } : { kind: "graded", status: "graded", grade };
|
|
177
|
+
}
|
|
178
|
+
default:
|
|
179
|
+
return { kind: "unreadable", status: void 0 };
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
{ kind: "unreadable", status: void 0 }
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
function readAssessResult(value) {
|
|
186
|
+
const nothing = {
|
|
187
|
+
status: "graded",
|
|
188
|
+
grade: null,
|
|
189
|
+
assessment: null,
|
|
190
|
+
recognizedText: void 0
|
|
191
|
+
};
|
|
192
|
+
return guarded(() => {
|
|
193
|
+
if (!isRecord(value)) {
|
|
194
|
+
return nothing;
|
|
195
|
+
}
|
|
196
|
+
if (value.status === "failed") {
|
|
197
|
+
return { status: "failed", retryable: value.retryable === true };
|
|
198
|
+
}
|
|
199
|
+
if (value.status === "unscorable") {
|
|
200
|
+
return {
|
|
201
|
+
status: "unscorable",
|
|
202
|
+
code: typeof value.code === "string" ? value.code : void 0
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
if (value.status !== "graded") {
|
|
206
|
+
return nothing;
|
|
207
|
+
}
|
|
208
|
+
const assessment = readEvidence(value.assessment);
|
|
209
|
+
const recognizedText = guarded(
|
|
210
|
+
() => typeof assessment?.recognizedText === "string" ? assessment.recognizedText : void 0,
|
|
211
|
+
void 0
|
|
212
|
+
);
|
|
213
|
+
return { status: "graded", grade: readGrade(value.grade), assessment, recognizedText };
|
|
214
|
+
}, nothing);
|
|
215
|
+
}
|
|
216
|
+
function readEvidence(value) {
|
|
217
|
+
return isRecord(value) ? value : null;
|
|
218
|
+
}
|
|
219
|
+
function percentOfCriterion(criterion) {
|
|
220
|
+
if (criterion.notApplicable === true || criterion.score === void 0) {
|
|
221
|
+
return void 0;
|
|
222
|
+
}
|
|
223
|
+
return Math.round(criterion.score / (criterion.maxScore ?? 1) * 100);
|
|
224
|
+
}
|
|
225
|
+
function readDimensions(assessment, grade) {
|
|
226
|
+
const criteria = grade?.criteria ?? [];
|
|
227
|
+
if (criteria.length > 0) {
|
|
228
|
+
return DIMENSIONS.map((dimension) => {
|
|
229
|
+
const criterion = criteria.find((entry) => entry.name === dimension);
|
|
230
|
+
return {
|
|
231
|
+
dimension,
|
|
232
|
+
percent: criterion === void 0 ? void 0 : percentOfCriterion(criterion)
|
|
233
|
+
};
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
const scores = guarded(() => isRecord(assessment) ? assessment.scores : void 0, void 0);
|
|
237
|
+
return DIMENSIONS.map((dimension) => {
|
|
238
|
+
const score = withinMax(
|
|
239
|
+
guarded(() => isRecord(scores) ? scores[dimension] : void 0, void 0),
|
|
240
|
+
100
|
|
241
|
+
);
|
|
242
|
+
return { dimension, percent: score === null ? void 0 : Math.round(score) };
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
var VISUALLY_HIDDEN = {
|
|
246
|
+
position: "absolute",
|
|
247
|
+
width: 1,
|
|
248
|
+
height: 1,
|
|
249
|
+
margin: -1,
|
|
250
|
+
padding: 0,
|
|
251
|
+
overflow: "hidden",
|
|
252
|
+
clip: "rect(0 0 0 0)",
|
|
253
|
+
clipPath: "inset(50%)",
|
|
254
|
+
whiteSpace: "nowrap",
|
|
255
|
+
border: 0
|
|
256
|
+
};
|
|
257
|
+
var LEGEND_STATES = [
|
|
258
|
+
"correct",
|
|
259
|
+
"mispronounced",
|
|
260
|
+
"omitted",
|
|
261
|
+
"inserted"
|
|
262
|
+
];
|
|
263
|
+
function checkEvidence(assessment) {
|
|
264
|
+
try {
|
|
265
|
+
return lkCore.validateSpeechAssessment(assessment);
|
|
266
|
+
} catch {
|
|
267
|
+
return {
|
|
268
|
+
success: false,
|
|
269
|
+
errors: [{ path: [], message: "The assessment could not be read.", code: "unreadable" }]
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
function hasDetail(word) {
|
|
274
|
+
if (word === void 0) {
|
|
275
|
+
return false;
|
|
276
|
+
}
|
|
277
|
+
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";
|
|
278
|
+
}
|
|
279
|
+
function past(confidence, threshold) {
|
|
280
|
+
return threshold !== void 0 && typeof confidence === "number" && Number.isFinite(confidence) && confidence > threshold;
|
|
281
|
+
}
|
|
282
|
+
function phonemeLabel(phoneme, index, s) {
|
|
283
|
+
return phoneme.symbol ?? s.pronunciationPhonemePosition(index + 1);
|
|
284
|
+
}
|
|
285
|
+
function markSentence(entry, s) {
|
|
286
|
+
switch (entry.state) {
|
|
287
|
+
case "correct":
|
|
288
|
+
return s.pronunciationWordCorrect(entry.reference);
|
|
289
|
+
case "mispronounced":
|
|
290
|
+
return s.pronunciationWordMispronounced(entry.reference);
|
|
291
|
+
case "omitted":
|
|
292
|
+
return s.pronunciationWordOmitted(entry.reference);
|
|
293
|
+
default:
|
|
294
|
+
return s.pronunciationWordInserted(entry.heard);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
function PronunciationFeedback({
|
|
298
|
+
data,
|
|
299
|
+
assessment,
|
|
300
|
+
grade,
|
|
301
|
+
audioUrl,
|
|
302
|
+
breakThreshold,
|
|
303
|
+
monotoneThreshold,
|
|
304
|
+
locale,
|
|
305
|
+
theme,
|
|
306
|
+
strings
|
|
307
|
+
}) {
|
|
308
|
+
const s = chunkLB3FEV7Z_cjs.useLkStrings(strings);
|
|
309
|
+
const ids = react.useId();
|
|
310
|
+
const [openWord, setOpenWord] = react.useState(null);
|
|
311
|
+
const checked = react.useMemo(() => checkEvidence(assessment), [assessment]);
|
|
312
|
+
const devError = react.useMemo(
|
|
313
|
+
() => chunkUSBANZ7K_cjs.isDevelopment() && !checked.success ? new lkCore.ActivitySchemaError("read-aloud", checked.errors) : null,
|
|
314
|
+
[checked]
|
|
315
|
+
);
|
|
316
|
+
const words = react.useMemo(
|
|
317
|
+
() => checked.success ? lkCore.alignReadAloud(data, checked.data) : null,
|
|
318
|
+
[checked, data]
|
|
319
|
+
);
|
|
320
|
+
const reading = react.useMemo(
|
|
321
|
+
() => grade === void 0 || grade === null ? void 0 : readGrade(grade),
|
|
322
|
+
[grade]
|
|
323
|
+
);
|
|
324
|
+
const dimensions = react.useMemo(() => readDimensions(assessment, reading), [assessment, reading]);
|
|
325
|
+
const audioRef = react.useRef(null);
|
|
326
|
+
const stopRef = react.useRef(null);
|
|
327
|
+
react.useEffect(
|
|
328
|
+
() => () => {
|
|
329
|
+
if (stopRef.current !== null) {
|
|
330
|
+
clearTimeout(stopRef.current);
|
|
331
|
+
}
|
|
332
|
+
},
|
|
333
|
+
[]
|
|
334
|
+
);
|
|
335
|
+
if (devError) {
|
|
336
|
+
throw devError;
|
|
337
|
+
}
|
|
338
|
+
const playWord = (startMs, durationMs) => {
|
|
339
|
+
const element = audioRef.current;
|
|
340
|
+
if (element === null) {
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
if (stopRef.current !== null) {
|
|
344
|
+
clearTimeout(stopRef.current);
|
|
345
|
+
}
|
|
346
|
+
element.currentTime = startMs / 1e3;
|
|
347
|
+
try {
|
|
348
|
+
void element.play().catch(() => {
|
|
349
|
+
});
|
|
350
|
+
} catch {
|
|
351
|
+
}
|
|
352
|
+
stopRef.current = setTimeout(() => {
|
|
353
|
+
stopRef.current = null;
|
|
354
|
+
element.pause();
|
|
355
|
+
}, durationMs);
|
|
356
|
+
};
|
|
357
|
+
const contentLang = typeof data.locale === "string" ? data.locale : void 0;
|
|
358
|
+
const contentDir = chunkOD65OZIW_cjs.localeDirectionOf(data.locale);
|
|
359
|
+
const alphabet = checked.success ? checked.data.phonemeAlphabet : void 0;
|
|
360
|
+
const monotone = checked.success ? checked.data.prosody?.monotoneConfidence : void 0;
|
|
361
|
+
const evidenceWords = checked.success ? checked.data.words : [];
|
|
362
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
363
|
+
"section",
|
|
364
|
+
{
|
|
365
|
+
className: "lk-pf",
|
|
366
|
+
"aria-label": s.pronunciationFeedbackLabel,
|
|
367
|
+
lang: locale,
|
|
368
|
+
style: theme,
|
|
369
|
+
children: [
|
|
370
|
+
reading !== void 0 ? (
|
|
371
|
+
// `.lk-pf-grade` is load-bearing beyond the skin: `<ReadAloud>` looks
|
|
372
|
+
// for it after the commit to decide whether it must render the score
|
|
373
|
+
// itself, so a grade is never lost between the two components. It
|
|
374
|
+
// hands this panel only a grade it has already read, so the block it
|
|
375
|
+
// finds always carries the score.
|
|
376
|
+
//
|
|
377
|
+
// `data-passed` only where there is a verdict: a grade that could not
|
|
378
|
+
// be read has none, and styling it as a fail would invent one.
|
|
379
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
380
|
+
"div",
|
|
381
|
+
{
|
|
382
|
+
className: "lk-pf-grade",
|
|
383
|
+
...reading !== null ? { "data-passed": String(reading.passed) } : {},
|
|
384
|
+
children: [
|
|
385
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "lk-pf-score", children: reading === null ? s.couldNotBeGraded : s.scoreAnnouncement(percentOfGrade(reading), reading.passed) }),
|
|
386
|
+
reading?.feedback ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "lk-pf-grade-feedback", dir: "auto", children: reading.feedback }) : null
|
|
387
|
+
]
|
|
388
|
+
}
|
|
389
|
+
)
|
|
390
|
+
) : null,
|
|
391
|
+
/* @__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: [
|
|
392
|
+
/* @__PURE__ */ jsxRuntime.jsx("dt", { className: "lk-pf-dimension-name", children: s.pronunciationDimension(dimension) }),
|
|
393
|
+
/* @__PURE__ */ jsxRuntime.jsx("dd", { className: "lk-pf-dimension-score", children: percent === void 0 ? s.pronunciationNotAssessed : `${percent}%` })
|
|
394
|
+
] }, dimension)) }),
|
|
395
|
+
words !== null ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
396
|
+
/* @__PURE__ */ jsxRuntime.jsx("ol", { className: "lk-pf-words", "aria-label": s.pronunciationWordsLabel, dir: contentDir, children: words.map((entry, index) => {
|
|
397
|
+
const sentence = markSentence(entry, s);
|
|
398
|
+
const shown = entry.state === "inserted" ? entry.heard : entry.reference;
|
|
399
|
+
const word = entry.wordIndex === void 0 ? void 0 : evidenceWords.at(entry.wordIndex);
|
|
400
|
+
const open = openWord === index;
|
|
401
|
+
const panelId = `${ids}-w${index}`;
|
|
402
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
403
|
+
"li",
|
|
404
|
+
{
|
|
405
|
+
className: "lk-pf-word",
|
|
406
|
+
"data-state": entry.state,
|
|
407
|
+
children: [
|
|
408
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "lk-visually-hidden", style: VISUALLY_HIDDEN, children: sentence }),
|
|
409
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
410
|
+
"span",
|
|
411
|
+
{
|
|
412
|
+
className: "lk-pf-word-text",
|
|
413
|
+
"aria-hidden": "true",
|
|
414
|
+
dir: contentDir ?? "auto",
|
|
415
|
+
lang: contentLang,
|
|
416
|
+
children: shown
|
|
417
|
+
}
|
|
418
|
+
),
|
|
419
|
+
hasDetail(word) ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
420
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
421
|
+
"button",
|
|
422
|
+
{
|
|
423
|
+
type: "button",
|
|
424
|
+
className: "lk-pf-word-toggle",
|
|
425
|
+
"aria-expanded": open,
|
|
426
|
+
"aria-controls": panelId,
|
|
427
|
+
onClick: () => setOpenWord(open ? null : index),
|
|
428
|
+
children: s.pronunciationWordDetails(shown)
|
|
429
|
+
}
|
|
430
|
+
),
|
|
431
|
+
open ? /* @__PURE__ */ jsxRuntime.jsxs("div", { id: panelId, className: "lk-pf-word-detail", children: [
|
|
432
|
+
/* @__PURE__ */ jsxRuntime.jsxs("dl", { className: "lk-pf-word-facts", children: [
|
|
433
|
+
/* @__PURE__ */ jsxRuntime.jsx("dt", { children: s.pronunciationDimension("accuracy") }),
|
|
434
|
+
/* @__PURE__ */ jsxRuntime.jsx("dd", { children: word.accuracy === void 0 ? s.pronunciationNotAssessed : `${Math.round(word.accuracy)}%` }),
|
|
435
|
+
word.syllables !== void 0 && word.syllables.length > 0 ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
436
|
+
/* @__PURE__ */ jsxRuntime.jsx("dt", { children: s.pronunciationSyllables }),
|
|
437
|
+
/* @__PURE__ */ jsxRuntime.jsx("dd", { children: /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "lk-pf-syllables", children: word.syllables.map((syllable, position) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
438
|
+
"li",
|
|
439
|
+
{
|
|
440
|
+
dir: contentDir ?? "auto",
|
|
441
|
+
lang: contentLang,
|
|
442
|
+
children: syllable.grapheme === void 0 ? syllable.text : s.pronunciationSyllableSpelling(
|
|
443
|
+
syllable.text,
|
|
444
|
+
syllable.grapheme
|
|
445
|
+
)
|
|
446
|
+
},
|
|
447
|
+
position
|
|
448
|
+
)) }) })
|
|
449
|
+
] }) : null,
|
|
450
|
+
word.phonemes !== void 0 && word.phonemes.length > 0 ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
451
|
+
/* @__PURE__ */ jsxRuntime.jsx("dt", { children: s.pronunciationPhonemes }),
|
|
452
|
+
/* @__PURE__ */ jsxRuntime.jsx("dd", { children: /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "lk-pf-phonemes", children: word.phonemes.map((phoneme, position) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
453
|
+
"li",
|
|
454
|
+
{
|
|
455
|
+
className: "lk-pf-phoneme",
|
|
456
|
+
children: [
|
|
457
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "lk-pf-phoneme-symbol", children: phonemeLabel(phoneme, position, s) }),
|
|
458
|
+
phoneme.heardAs !== void 0 && phoneme.heardAs.length > 0 ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
459
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "lk-pf-heard-label", children: s.pronunciationHeardAs }),
|
|
460
|
+
/* @__PURE__ */ jsxRuntime.jsx("ul", { className: "lk-pf-heard-as", children: phoneme.heardAs.map((candidate, rank) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
461
|
+
"li",
|
|
462
|
+
{
|
|
463
|
+
children: candidate.symbol
|
|
464
|
+
},
|
|
465
|
+
rank
|
|
466
|
+
)) })
|
|
467
|
+
] }) : null
|
|
468
|
+
]
|
|
469
|
+
},
|
|
470
|
+
position
|
|
471
|
+
)) }) })
|
|
472
|
+
] }) : null
|
|
473
|
+
] }),
|
|
474
|
+
audioUrl !== void 0 && typeof word.startMs === "number" && typeof word.durationMs === "number" ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
475
|
+
"button",
|
|
476
|
+
{
|
|
477
|
+
type: "button",
|
|
478
|
+
className: "lk-pf-word-play",
|
|
479
|
+
onClick: () => playWord(word.startMs, word.durationMs),
|
|
480
|
+
children: s.pronunciationPlayWord(shown)
|
|
481
|
+
}
|
|
482
|
+
) : null,
|
|
483
|
+
past(word.breaks?.unexpected, breakThreshold) ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "lk-pf-break", "data-break": "unexpected", children: s.pronunciationBreakUnexpected }) : null,
|
|
484
|
+
past(word.breaks?.missing, breakThreshold) ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "lk-pf-break", "data-break": "missing", children: s.pronunciationBreakMissing }) : null
|
|
485
|
+
] }) : null
|
|
486
|
+
] }) : null
|
|
487
|
+
]
|
|
488
|
+
},
|
|
489
|
+
index
|
|
490
|
+
);
|
|
491
|
+
}) }),
|
|
492
|
+
/* @__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)) }),
|
|
493
|
+
alphabet === "ipa" ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "lk-pf-alphabet", children: s.pronunciationIpaNote }) : null
|
|
494
|
+
] }) : null,
|
|
495
|
+
past(monotone, monotoneThreshold) ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "lk-pf-monotone", role: "note", children: s.pronunciationMonotone }) : null,
|
|
496
|
+
audioUrl !== void 0 ? (
|
|
497
|
+
// 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
|
|
498
|
+
/* @__PURE__ */ jsxRuntime.jsx("audio", { ref: audioRef, className: "lk-pf-audio", src: audioUrl, preload: "metadata" })
|
|
499
|
+
) : null
|
|
500
|
+
]
|
|
501
|
+
}
|
|
502
|
+
);
|
|
503
|
+
}
|
|
504
|
+
function PronunciationFeedback2(props) {
|
|
505
|
+
const strings = chunkLB3FEV7Z_cjs.useLkStrings(props.strings);
|
|
506
|
+
return /* @__PURE__ */ jsxRuntime.jsx(chunkUSBANZ7K_cjs.ActivityErrorBoundary, { strings, children: /* @__PURE__ */ jsxRuntime.jsx(PronunciationFeedback, { ...props }) });
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
exports.PronunciationFeedback = PronunciationFeedback2;
|
|
510
|
+
exports.VISUALLY_HIDDEN = VISUALLY_HIDDEN;
|
|
511
|
+
exports.fractionOfGrade = fractionOfGrade;
|
|
512
|
+
exports.markSentence = markSentence;
|
|
513
|
+
exports.percentOfGrade = percentOfGrade;
|
|
514
|
+
exports.readAssessResult = readAssessResult;
|
|
515
|
+
exports.readEvidence = readEvidence;
|
|
516
|
+
exports.readOutcome = readOutcome;
|
|
517
|
+
//# sourceMappingURL=chunk-F4TBCRGI.cjs.map
|
|
518
|
+
//# sourceMappingURL=chunk-F4TBCRGI.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/shared/read-grade.ts","../src/components/PronunciationFeedback/PronunciationFeedback.tsx","../src/components/PronunciationFeedback/index.tsx"],"names":["validateSpeechAssessment","useLkStrings","useId","useState","useMemo","isDevelopment","ActivitySchemaError","alignReadAloud","useRef","useEffect","localeDirectionOf","jsxs","jsx","Fragment","PronunciationFeedback","ActivityErrorBoundary"],"mappings":"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","file":"chunk-F4TBCRGI.cjs","sourcesContent":["'use client';\n\nimport type {\n CriterionScore,\n GradeRecord,\n ReadAloudDimension,\n ScoringDetail,\n ScoringOutcome,\n SpeechAssessment,\n} from '@intellectif/lk-core';\n\n/**\n * The one reader of a grade that arrives from a host.\n *\n * Nothing validates a `GradeRecord`, an `ItemOutcome` or an assess result on\n * the way in: each is the application's, it crosses a network, and a field a\n * backend serialised as `null`, as a string or as an object is the ordinary way\n * to meet one. Every render path used to dereference them in its own way, so\n * every guard added for one shape left its sibling open — `criteria: null`\n * guarded and `criteria: [null]` not, a numeric `score` guarded and a string\n * `maxScore` shown as 8200%, `feedback` as an object taking a whole activity\n * down, `passed: null` dropping a grade from every callback in development and\n * sending an invalid statement in production.\n *\n * So the question \"what did the host send?\" has one answer, given here. The\n * panel, the outcome summary, the announcement, the decision about who shows\n * the score, `onComplete` and the xAPI statement read ONLY what these functions\n * return, and a payload they refuse is shown as could-not-be-graded — never the\n * error boundary, never `NaN`, never a percentage invented from a string.\n *\n * The rules, once:\n *\n * - **A required field is well-formed or the grade is refused.** `score` and\n * `maxScore` are finite numbers, `maxScore` above 0 and `score` between 0 and\n * it; `passed` is a boolean. A numeric string is not a number: `\"100\"` is how\n * a grade out of 100 turned into 8200%, and coercing it would only move the\n * guess somewhere else.\n * - **An optional field is absent when it is `undefined` or `null`** — how a\n * backend writes an absent optional — and otherwise well-formed or dropped.\n * `feedback` that is not a string is no feedback.\n * - **`criteria` keeps each well-formed entry and drops the rest.** Criteria are\n * named rows, each independent: an entry that cannot be read names no row.\n * - **`details` is read whole or not at all.** It is the word marks in order,\n * and a list with one word quietly missing would tell a learner they never\n * read a word they did.\n * - **Nothing here throws.** A payload whose getter throws is a payload that\n * cannot be read.\n */\n\n/** Marks a grade as read by {@link readGrade}. Type-only: it never exists at run time. */\ndeclare const READ: unique symbol;\n\n/**\n * A grade {@link readGrade} has checked: `0 <= score <= maxScore`, `maxScore >\n * 0`, both finite, `passed` a boolean, `feedback` a string or `null`, and\n * `criteria` and `details` (when present) holding only well-formed entries.\n * Still a `GradeRecord`, so it can be handed to anything that takes one — and\n * reading it again gives the same grade.\n */\nexport type ReadGrade = GradeRecord & { readonly [READ]: true };\n\n/** A stored outcome, as a renderer may show it. */\nexport type OutcomeReading =\n /** A grade to show: the outcome's own for `scored`, its `grade` for `graded`. */\n | { kind: 'graded'; status: 'scored' | 'graded'; grade: ReadGrade }\n | { kind: 'deferred' }\n /** `code` is a developer's label: put on an attribute, never shown. */\n | { kind: 'unscorable'; code: string | undefined }\n /** Claimed a grade, or claimed nothing a renderer knows, and could not be read. */\n | { kind: 'unreadable'; status: 'scored' | 'graded' | undefined };\n\n/** An assessor's answer, as a renderer and a statement may use it. */\nexport type AssessReading =\n | {\n status: 'graded';\n /** `null` when what came back cannot be read as a grade. */\n grade: ReadGrade | null;\n /** The evidence when it is an object at all; whether it VALIDATES is the panel's question. */\n assessment: SpeechAssessment | null;\n recognizedText: string | undefined;\n }\n | { status: 'unscorable'; code: string | undefined }\n | { status: 'failed'; retryable: boolean };\n\n/** One dimension row: a whole percentage, or `undefined` for a dimension nobody measured. */\nexport interface DimensionReading {\n dimension: ReadAloudDimension;\n percent: number | undefined;\n}\n\n/**\n * Every dimension a read-aloud grade can weigh, in the order a learner reads\n * them: what was said, how it flowed, how much of it was there, and how it was\n * pitched. Fixed rather than derived from the evidence, so a dimension the\n * engine did not measure is reported as unmeasured instead of vanishing.\n */\nconst DIMENSIONS: readonly ReadAloudDimension[] = [\n 'accuracy',\n 'fluency',\n 'completeness',\n 'prosody',\n];\n\nconst OUTCOMES: readonly ScoringOutcome[] = [\n 'correct',\n 'incorrect',\n 'correct-omission',\n 'incorrect-omission',\n];\n\n/**\n * How far past its own maximum a score may sit and still be read as that\n * maximum. A grader that sums weighted parts lands on `1.0000000000000002`\n * for a perfect take as readily as on `1`, and \"could not be graded\" is the\n * wrong thing to tell the learner who earned it. Far below any difference a\n * whole percentage can show.\n */\nconst OVERSHOOT = 1e-9;\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction isFiniteNumber(value: unknown): value is number {\n return typeof value === 'number' && Number.isFinite(value);\n}\n\n/** An optional field a backend left out, written either way it writes one. */\nfunction isAbsent(value: unknown): value is null | undefined {\n return value === undefined || value === null;\n}\n\n/** A score on `0..max`, a hair of overshoot read as `max`; `null` for anything else. */\nfunction withinMax(score: unknown, max: number): number | null {\n if (!isFiniteNumber(score) || score < 0) {\n return null;\n }\n if (score <= max) {\n return score;\n }\n return score - max <= max * OVERSHOOT ? max : null;\n}\n\n/** A learner's or a reference response: a string, or strings. */\nfunction isResponse(value: unknown): value is string | string[] {\n return (\n typeof value === 'string' ||\n (Array.isArray(value) && value.every((entry) => typeof entry === 'string'))\n );\n}\n\n/** Runs a read that touches a host's object, whose getters may throw. */\nfunction guarded<T>(read: () => T, refused: T): T {\n try {\n return read();\n } catch {\n return refused;\n }\n}\n\nfunction readCriterion(value: unknown): CriterionScore | null {\n if (!isRecord(value) || typeof value.name !== 'string') {\n return null;\n }\n const { maxScore, notApplicable } = value;\n if (!isAbsent(maxScore) && !(isFiniteNumber(maxScore) && maxScore > 0)) {\n return null;\n }\n if (!isAbsent(notApplicable) && typeof notApplicable !== 'boolean') {\n return null;\n }\n // `CriterionScore.maxScore` defaults to 1, the SDK's scaled convention.\n const score = isAbsent(value.score) ? undefined : withinMax(value.score, maxScore ?? 1);\n if (score === null) {\n return null;\n }\n return {\n name: value.name,\n ...(score !== undefined ? { score } : {}),\n ...(!isAbsent(maxScore) ? { maxScore } : {}),\n ...(notApplicable === true ? { notApplicable } : {}),\n };\n}\n\nfunction readDetail(value: unknown): ScoringDetail | null {\n if (!isRecord(value)) {\n return null;\n }\n const { itemId, correct, outcome, learnerResponse, correctResponse, weight, score } = value;\n if (\n typeof itemId !== 'string' ||\n typeof correct !== 'boolean' ||\n !isResponse(learnerResponse) ||\n !isResponse(correctResponse)\n ) {\n return null;\n }\n const checkedOutcome = OUTCOMES.find((known) => known === outcome);\n if (!isAbsent(outcome) && checkedOutcome === undefined) {\n return null;\n }\n if (!isAbsent(weight) && !isFiniteNumber(weight)) {\n return null;\n }\n // A detail's own score is a fraction of 1.\n const checkedScore = isAbsent(score) ? undefined : withinMax(score, 1);\n if (checkedScore === null) {\n return null;\n }\n return {\n itemId,\n correct,\n learnerResponse,\n correctResponse,\n ...(checkedOutcome !== undefined ? { outcome: checkedOutcome } : {}),\n ...(!isAbsent(weight) ? { weight } : {}),\n ...(checkedScore !== undefined ? { score: checkedScore } : {}),\n };\n}\n\nfunction readDetails(value: unknown): ScoringDetail[] | undefined {\n if (!Array.isArray(value)) {\n return undefined;\n }\n const details: ScoringDetail[] = [];\n for (const entry of value) {\n const detail = readDetail(entry);\n if (detail === null) {\n return undefined;\n }\n details.push(detail);\n }\n return details;\n}\n\n/**\n * A host's grade as a checked copy, or `null` when it carries no grade that can\n * be shown or reported. See the module notes for the rules.\n */\nexport function readGrade(value: unknown): ReadGrade | null {\n return guarded(() => {\n if (!isRecord(value)) {\n return null;\n }\n const { maxScore, passed } = value;\n if (!isFiniteNumber(maxScore) || maxScore <= 0 || typeof passed !== 'boolean') {\n return null;\n }\n const score = withinMax(value.score, maxScore);\n if (score === null) {\n return null;\n }\n const criteria = Array.isArray(value.criteria)\n ? value.criteria.flatMap((entry) => {\n const criterion = readCriterion(entry);\n return criterion === null ? [] : [criterion];\n })\n : undefined;\n const details = readDetails(value.details);\n const grade: GradeRecord = {\n score,\n maxScore,\n passed,\n feedback: typeof value.feedback === 'string' ? value.feedback : null,\n ...(criteria !== undefined ? { criteria } : {}),\n ...(details !== undefined ? { details } : {}),\n };\n return grade as ReadGrade;\n }, null);\n}\n\n/** A read grade as a fraction of 1: in `0..1`, because the reader made it so. */\nexport function fractionOfGrade(grade: ReadGrade): number {\n return grade.score / grade.maxScore;\n}\n\n/** A read grade as a whole percentage: in `0..100`, because the reader made it so. */\nexport function percentOfGrade(grade: ReadGrade): number {\n return Math.round(fractionOfGrade(grade) * 100);\n}\n\n/**\n * A stored outcome as a renderer may show it, or `undefined` for none —\n * `null` included, which is how a backend writes an absent optional.\n *\n * A `graded` outcome is read through its `grade`, the record\n * `outcomeFromGrade` mirrors onto it, so the summary and the panel beside it\n * can never show two different numbers; and it must be well-formed as a whole,\n * its own `score`, `maxScore` and `passed` included — a payload with a corrupt\n * copy of the grade is not one to pick a side of.\n */\nexport function readOutcome(value: unknown): OutcomeReading | undefined {\n if (isAbsent(value)) {\n return undefined;\n }\n return guarded(\n (): OutcomeReading => {\n if (!isRecord(value)) {\n return { kind: 'unreadable', status: undefined };\n }\n switch (value.status) {\n case 'deferred':\n return { kind: 'deferred' };\n case 'unscorable':\n return {\n kind: 'unscorable',\n code: typeof value.code === 'string' ? value.code : undefined,\n };\n case 'scored': {\n const grade = readGrade(value);\n return grade === null\n ? { kind: 'unreadable', status: 'scored' }\n : { kind: 'graded', status: 'scored', grade };\n }\n case 'graded': {\n const grade = readGrade(value.grade);\n return grade === null || readGrade(value) === null\n ? { kind: 'unreadable', status: 'graded' }\n : { kind: 'graded', status: 'graded', grade };\n }\n default:\n return { kind: 'unreadable', status: undefined };\n }\n },\n { kind: 'unreadable', status: undefined },\n );\n}\n\n/**\n * An assessor's answer as a renderer and a statement may use it.\n *\n * `failed` and `unscorable` need little: a `retryable` that is not `true`\n * offers no retry, which never spends an application's money on a guess, and a\n * `code` that is not a string is no code. Everything else — a `graded` result,\n * and anything that is not an object or names no status this component knows —\n * is read as a judgement: its grade when one can be read, its evidence when it\n * is an object. A judgement with neither is shown as could-not-be-graded.\n */\nexport function readAssessResult(value: unknown): AssessReading {\n const nothing: AssessReading = {\n status: 'graded',\n grade: null,\n assessment: null,\n recognizedText: undefined,\n };\n return guarded((): AssessReading => {\n if (!isRecord(value)) {\n return nothing;\n }\n if (value.status === 'failed') {\n return { status: 'failed', retryable: value.retryable === true };\n }\n if (value.status === 'unscorable') {\n return {\n status: 'unscorable',\n code: typeof value.code === 'string' ? value.code : undefined,\n };\n }\n if (value.status !== 'graded') {\n return nothing;\n }\n const assessment = readEvidence(value.assessment);\n const recognizedText = guarded(\n () =>\n typeof assessment?.recognizedText === 'string' ? assessment.recognizedText : undefined,\n undefined,\n );\n return { status: 'graded', grade: readGrade(value.grade), assessment, recognizedText };\n }, nothing);\n}\n\n/**\n * Evidence worth handing to the panel: an object. Whether it validates is the\n * panel's to decide, because what a refusal costs differs by mode.\n */\nexport function readEvidence(value: unknown): SpeechAssessment | null {\n return isRecord(value) ? (value as unknown as SpeechAssessment) : null;\n}\n\n/** One criterion's whole percentage of its own maximum, or `undefined` when it carries none. */\nfunction percentOfCriterion(criterion: CriterionScore): number | undefined {\n if (criterion.notApplicable === true || criterion.score === undefined) {\n return undefined;\n }\n return Math.round((criterion.score / (criterion.maxScore ?? 1)) * 100);\n}\n\n/**\n * The dimension rows, from one source for the whole set and never a mix: the\n * grade's criteria when it has any it can read, and the evidence's own scores\n * otherwise. A dimension pulled out of the evidence and set beside graded ones\n * would read as if it had counted towards the grade.\n *\n * The evidence is read as defensively as the grade: in production the panel\n * shows these rows even for evidence the validator refused, which can carry\n * anything at all under `scores`. `scale` is 100 by schema, so an evidence\n * score is already a percentage — and one outside `0..100` is not one.\n */\nexport function readDimensions(\n assessment: unknown,\n grade: ReadGrade | null | undefined,\n): DimensionReading[] {\n const criteria = grade?.criteria ?? [];\n if (criteria.length > 0) {\n return DIMENSIONS.map((dimension) => {\n const criterion = criteria.find((entry) => entry.name === dimension);\n return {\n dimension,\n percent: criterion === undefined ? undefined : percentOfCriterion(criterion),\n };\n });\n }\n const scores = guarded(() => (isRecord(assessment) ? assessment.scores : undefined), undefined);\n return DIMENSIONS.map((dimension) => {\n const score = withinMax(\n guarded(() => (isRecord(scores) ? scores[dimension] : undefined), undefined),\n 100,\n );\n // Rounded rather than reformatted: the engine's own number is what was graded.\n return { dimension, percent: score === null ? undefined : Math.round(score) };\n });\n}\n","'use client';\n\nimport {\n ActivitySchemaError,\n alignReadAloud,\n type GradeRecord,\n type ReadAloudData,\n type ReadAloudWordAlignment,\n type SpeechAssessment,\n type SpeechPhoneme,\n type SpeechWord,\n type ThemeTokens,\n type ValidationResult,\n validateSpeechAssessment,\n} from '@intellectif/lk-core';\nimport { type CSSProperties, useEffect, useId, useMemo, useRef, useState } from 'react';\nimport { localeDirectionOf } from '../../i18n/direction.js';\nimport { useLkStrings } from '../../i18n/LkIntlProvider.js';\nimport type { LkStrings, LkStringsOverride } from '../../i18n/strings.js';\nimport { isDevelopment } from '../_internal.js';\nimport { percentOfGrade, readDimensions, readGrade } from '../shared/read-grade.js';\n\n/**\n * Present for assistive technology, invisible on screen. Inline rather than\n * left to the optional skin, for the reason `<Dictation>` gives: the per-word\n * sentences are the marks' only channel for a screen reader, and a page that\n * does not load the skin would otherwise show every one of them beside its\n * word. Exported so `<ReadAloud>`, whose review fallback renders the same\n * marks from stored details, carries one definition rather than a copy.\n */\nexport const VISUALLY_HIDDEN: CSSProperties = {\n position: 'absolute',\n width: 1,\n height: 1,\n margin: -1,\n padding: 0,\n overflow: 'hidden',\n clip: 'rect(0 0 0 0)',\n clipPath: 'inset(50%)',\n whiteSpace: 'nowrap',\n border: 0,\n};\n\n/** The four marking states, in the order the legend lists them. */\nconst LEGEND_STATES: readonly ReadAloudWordAlignment['state'][] = [\n 'correct',\n 'mispronounced',\n 'omitted',\n 'inserted',\n];\n\nexport interface PronunciationFeedbackProps {\n /**\n * The item's reading and its language. Only these two fields, because they\n * are the only ones a mark depends on — everything else about the activity\n * belongs to whoever rendered it.\n */\n data: Pick<ReadAloudData, 'referenceText' | 'locale'>;\n /**\n * The assessor's evidence. Checked with `validateSpeechAssessment` before it\n * is aligned: see the note on this component about what a bad one costs.\n */\n assessment: SpeechAssessment;\n /**\n * The grade the evidence produced, when one exists. Its `criteria` are what\n * the dimensions are read from, because they are the numbers the learner was\n * actually graded on. A grade that cannot be read — a score that is not a\n * number between 0 and its maximum, a `passed` that is not a boolean — is\n * shown as could-not-be-graded, never as a percentage made up from it.\n */\n grade?: GradeRecord;\n /**\n * The learner's own take, playable. With it, each word that carries timings\n * gets a button that plays just that word.\n */\n audioUrl?: string;\n /**\n * Confidence above which a break note is shown, 0..1. **No default**: how\n * confident an engine has to be before a pause is worth telling a learner\n * about is a calibration, and the SDK has no calibrated answer. Without it no\n * break note is shown at all.\n */\n breakThreshold?: number;\n /** The same, for the monotone note read off `assessment.prosody`. */\n monotoneThreshold?: number;\n /**\n * BCP 47 tag stamped as `lang` on the root: the INTERFACE language, the one\n * this component's own sentences are written in. It is not `data.locale`,\n * which is the language of the reading and is put on the words themselves.\n */\n locale?: string;\n /** Per-instance token overrides, applied as inline CSS vars on the root. */\n theme?: Partial<ThemeTokens>;\n /** Overrides the SDK's chrome text for this panel. See {@link LkIntlProvider}. */\n strings?: LkStringsOverride;\n}\n\n/**\n * What the validator says about evidence that may not even be readable. Its\n * cross-field rules read the value directly, so a getter that throws escapes\n * it — and evidence that cannot be read is evidence refused, not a reason to\n * take the grade beside it down.\n */\nfunction checkEvidence(assessment: unknown): ValidationResult<SpeechAssessment> {\n try {\n return validateSpeechAssessment(assessment);\n } catch {\n return {\n success: false,\n errors: [{ path: [], message: 'The assessment could not be read.', code: 'unreadable' }],\n };\n }\n}\n\n/** Whether a word carries anything a disclosure would have to show. */\nfunction hasDetail(word: SpeechWord | undefined): word is SpeechWord {\n if (word === undefined) {\n return false;\n }\n return (\n word.accuracy !== undefined ||\n (word.syllables !== undefined && word.syllables.length > 0) ||\n (word.phonemes !== undefined && word.phonemes.length > 0) ||\n word.breaks !== undefined ||\n (typeof word.startMs === 'number' && typeof word.durationMs === 'number')\n );\n}\n\n/** A confidence that clears the caller's threshold. No threshold, no note. */\nfunction past(confidence: number | undefined, threshold: number | undefined): boolean {\n return (\n threshold !== undefined &&\n typeof confidence === 'number' &&\n Number.isFinite(confidence) &&\n confidence > threshold\n );\n}\n\n/** One phoneme, named by its symbol or — when the engine named none — by its place. */\nfunction phonemeLabel(phoneme: SpeechPhoneme, index: number, s: LkStrings): string {\n return phoneme.symbol ?? s.pronunciationPhonemePosition(index + 1);\n}\n\n/**\n * What a screen reader hears for one marked word — the marks' only channel for\n * assistive technology, since the glyph and the decoration are both hidden from\n * it. Exported because `<ReadAloud>`'s review fallback marks the same four\n * states from stored details, and a second copy of these four sentences would\n * be a second thing to translate.\n *\n * Branches on `state` and **never** on `heard === ''`: an inserted word whose\n * text normalises to nothing carries the empty string too, and would otherwise\n * be announced as an omission.\n */\nexport function markSentence(entry: ReadAloudWordAlignment, s: LkStrings): string {\n switch (entry.state) {\n case 'correct':\n return s.pronunciationWordCorrect(entry.reference);\n case 'mispronounced':\n return s.pronunciationWordMispronounced(entry.reference);\n case 'omitted':\n return s.pronunciationWordOmitted(entry.reference);\n default:\n return s.pronunciationWordInserted(entry.heard);\n }\n}\n\n/**\n * The per-word marks of a read-aloud take, plus the dimensions they were\n * graded on.\n *\n * **A bad assessment never destroys a good grade.** `alignReadAloud` throws a\n * `TypeError` for evidence `validateSpeechAssessment` refuses, so this\n * component runs the validator first. In development the refusal is thrown as\n * an `ActivitySchemaError`, which is the package's dev-error convention and\n * puts the problem in front of whoever wired the adapter up. In production the\n * dimensions and the grade are rendered and the word list is left out — a\n * learner who earned 82% should see 82%, not \"this activity could not be\n * displayed\" from an error boundary.\n *\n * **Nor does a bad grade take the evidence down.** `grade` is read, not\n * trusted: a score that is not a number between 0 and its maximum, or a\n * `passed` that is not a boolean, is shown as could-not-be-graded beside the\n * dimensions and the marks, and criteria that cannot be read name no dimension.\n *\n * There is **no `renderMode`**: nothing here submits, grades or reveals an\n * answer key. A read-aloud item has none, and the marks exist only once a\n * server has graded the take, so there is no mode in which they must be\n * withheld.\n *\n * ```tsx\n * <PronunciationFeedback\n * data={item}\n * assessment={assessment}\n * grade={grade}\n * audioUrl={takeUrl}\n * breakThreshold={0.75}\n * monotoneThreshold={0.6}\n * />\n * ```\n */\nexport function PronunciationFeedback({\n data,\n assessment,\n grade,\n audioUrl,\n breakThreshold,\n monotoneThreshold,\n locale,\n theme,\n strings,\n}: PronunciationFeedbackProps) {\n const s = useLkStrings(strings);\n const ids = useId();\n const [openWord, setOpenWord] = useState<number | null>(null);\n\n const checked = useMemo(() => checkEvidence(assessment), [assessment]);\n\n const devError = useMemo(\n () =>\n isDevelopment() && !checked.success\n ? new ActivitySchemaError('read-aloud', checked.errors)\n : null,\n [checked],\n );\n\n // The checked copy, never the argument, for the same reason lk-core aligns\n // from it: a property getter cannot answer the validator one word and the\n // aligner another.\n const words = useMemo<ReadAloudWordAlignment[] | null>(\n () => (checked.success ? alignReadAloud(data, checked.data) : null),\n [checked, data],\n );\n\n // The grade as the one reader of a host's grade reads it: `undefined` when\n // none was given, and `null` when one was and cannot be shown. Everything\n // below that shows a number reads this and the dimension rows, never `grade`.\n const reading = useMemo(\n () => (grade === undefined || grade === null ? undefined : readGrade(grade)),\n [grade],\n );\n const dimensions = useMemo(() => readDimensions(assessment, reading), [assessment, reading]);\n\n // The take, for the per-word play buttons. A ref and an effect, never a read\n // during render: this component server-renders like every other one here.\n const audioRef = useRef<HTMLAudioElement>(null);\n const stopRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n useEffect(\n () => () => {\n if (stopRef.current !== null) {\n clearTimeout(stopRef.current);\n }\n },\n [],\n );\n\n // All hooks are called before this throw, so hook order stays stable.\n if (devError) {\n throw devError;\n }\n\n /** Plays one word out of the take, and stops at the end of it. */\n const playWord = (startMs: number, durationMs: number): void => {\n const element = audioRef.current;\n if (element === null) {\n return;\n }\n if (stopRef.current !== null) {\n clearTimeout(stopRef.current);\n }\n element.currentTime = startMs / 1000;\n try {\n void element.play().catch(() => {\n /* an autoplay refusal is not this component's to report */\n });\n } catch {\n /* an engine with no media stack at all; the button simply does nothing */\n }\n stopRef.current = setTimeout(() => {\n stopRef.current = null;\n element.pause();\n }, durationMs);\n };\n\n // The reading's own language and the direction its tag names, put on the\n // words and on nothing else: the sentences beside them are interface text.\n // No `_`→`-` normalisation, unlike `<Dictation>` — `ReadAloudData.locale` is\n // canonical by schema, so there is nothing to normalise.\n const contentLang = typeof data.locale === 'string' ? data.locale : undefined;\n const contentDir = localeDirectionOf(data.locale);\n\n const alphabet = checked.success ? checked.data.phonemeAlphabet : undefined;\n const monotone = checked.success ? checked.data.prosody?.monotoneConfidence : undefined;\n // Read through `at`, not through an index: `wordIndex` belongs to a public\n // type a caller may have built by hand, and it is allowed to point nowhere.\n const evidenceWords: readonly SpeechWord[] = checked.success ? checked.data.words : [];\n\n return (\n <section\n className=\"lk-pf\"\n aria-label={s.pronunciationFeedbackLabel}\n lang={locale}\n style={theme as CSSProperties | undefined}\n >\n {reading !== undefined ? (\n // `.lk-pf-grade` is load-bearing beyond the skin: `<ReadAloud>` looks\n // for it after the commit to decide whether it must render the score\n // itself, so a grade is never lost between the two components. It\n // hands this panel only a grade it has already read, so the block it\n // finds always carries the score.\n //\n // `data-passed` only where there is a verdict: a grade that could not\n // be read has none, and styling it as a fail would invent one.\n <div\n className=\"lk-pf-grade\"\n {...(reading !== null ? { 'data-passed': String(reading.passed) } : {})}\n >\n <p className=\"lk-pf-score\">\n {reading === null\n ? s.couldNotBeGraded\n : s.scoreAnnouncement(percentOfGrade(reading), reading.passed)}\n </p>\n {/* The grader's words, isolated: their direction is their own, so an\n English comment in an Arabic interface keeps its full stop at its\n end. Their language is not guessed — feedback may be written in\n either the interface's language or the reading's. */}\n {reading?.feedback ? (\n <p className=\"lk-pf-grade-feedback\" dir=\"auto\">\n {reading.feedback}\n </p>\n ) : null}\n </div>\n ) : null}\n\n <dl className=\"lk-pf-dimensions\">\n {dimensions.map(({ dimension, percent }) => (\n <div className=\"lk-pf-dimension\" key={dimension} data-dimension={dimension}>\n <dt className=\"lk-pf-dimension-name\">{s.pronunciationDimension(dimension)}</dt>\n {/* An absent score is NEVER a 0: a dimension nobody measured and a\n dimension measured at nothing are different findings, and only\n one of them is the learner's. */}\n <dd className=\"lk-pf-dimension-score\">\n {percent === undefined ? s.pronunciationNotAssessed : `${percent}%`}\n </dd>\n </div>\n ))}\n </dl>\n\n {words !== null ? (\n <>\n {/* The list's name and the hidden sentences are interface text and\n keep the interface language; only the words carry the reading's.\n Its direction is the reading's, never resolved from the list's own\n text: the first strong character there is a hidden sentence. */}\n <ol className=\"lk-pf-words\" aria-label={s.pronunciationWordsLabel} dir={contentDir}>\n {words.map((entry, index) => {\n const sentence = markSentence(entry, s);\n const shown = entry.state === 'inserted' ? entry.heard : entry.reference;\n const word =\n entry.wordIndex === undefined ? undefined : evidenceWords.at(entry.wordIndex);\n const open = openWord === index;\n const panelId = `${ids}-w${index}`;\n return (\n <li\n // biome-ignore lint/suspicious/noArrayIndexKey: alignment order is the identity; words repeat\n key={index}\n className=\"lk-pf-word\"\n data-state={entry.state}\n >\n {/* The state's only channel for assistive technology, and\n exactly one channel: the same sentence as a `title` is an\n accessible description, which a screen reader reads\n straight after the name it duplicates. */}\n <span className=\"lk-visually-hidden\" style={VISUALLY_HIDDEN}>\n {sentence}\n </span>\n {/* Decoration: the glyph the skin draws and the word itself.\n Hidden from assistive technology, which already has the\n sentence above — and the glyph is never the only channel. */}\n <span\n className=\"lk-pf-word-text\"\n aria-hidden=\"true\"\n dir={contentDir ?? 'auto'}\n lang={contentLang}\n >\n {shown}\n </span>\n {hasDetail(word) ? (\n <>\n <button\n type=\"button\"\n className=\"lk-pf-word-toggle\"\n aria-expanded={open}\n aria-controls={panelId}\n onClick={() => setOpenWord(open ? null : index)}\n >\n {s.pronunciationWordDetails(shown)}\n </button>\n {open ? (\n <div id={panelId} className=\"lk-pf-word-detail\">\n <dl className=\"lk-pf-word-facts\">\n <dt>{s.pronunciationDimension('accuracy')}</dt>\n <dd>\n {word.accuracy === undefined\n ? s.pronunciationNotAssessed\n : `${Math.round(word.accuracy)}%`}\n </dd>\n {word.syllables !== undefined && word.syllables.length > 0 ? (\n <>\n <dt>{s.pronunciationSyllables}</dt>\n <dd>\n <ul className=\"lk-pf-syllables\">\n {word.syllables.map((syllable, position) => (\n <li\n // biome-ignore lint/suspicious/noArrayIndexKey: syllables are positional and repeat within a word\n key={position}\n dir={contentDir ?? 'auto'}\n lang={contentLang}\n >\n {syllable.grapheme === undefined\n ? syllable.text\n : s.pronunciationSyllableSpelling(\n syllable.text,\n syllable.grapheme,\n )}\n </li>\n ))}\n </ul>\n </dd>\n </>\n ) : null}\n {word.phonemes !== undefined && word.phonemes.length > 0 ? (\n <>\n <dt>{s.pronunciationPhonemes}</dt>\n <dd>\n <ul className=\"lk-pf-phonemes\">\n {word.phonemes.map((phoneme, position) => (\n <li\n // biome-ignore lint/suspicious/noArrayIndexKey: a word may sound the same phoneme twice\n key={position}\n className=\"lk-pf-phoneme\"\n >\n <span className=\"lk-pf-phoneme-symbol\">\n {phonemeLabel(phoneme, position, s)}\n </span>\n {phoneme.heardAs !== undefined &&\n phoneme.heardAs.length > 0 ? (\n <>\n <span className=\"lk-pf-heard-label\">\n {s.pronunciationHeardAs}\n </span>\n <ul className=\"lk-pf-heard-as\">\n {phoneme.heardAs.map((candidate, rank) => (\n <li\n // biome-ignore lint/suspicious/noArrayIndexKey: candidates are ranked and a symbol may repeat\n key={rank}\n >\n {candidate.symbol}\n </li>\n ))}\n </ul>\n </>\n ) : null}\n </li>\n ))}\n </ul>\n </dd>\n </>\n ) : null}\n </dl>\n {audioUrl !== undefined &&\n typeof word.startMs === 'number' &&\n typeof word.durationMs === 'number' ? (\n <button\n type=\"button\"\n className=\"lk-pf-word-play\"\n onClick={() =>\n playWord(word.startMs as number, word.durationMs as number)\n }\n >\n {s.pronunciationPlayWord(shown)}\n </button>\n ) : null}\n {past(word.breaks?.unexpected, breakThreshold) ? (\n <p className=\"lk-pf-break\" data-break=\"unexpected\">\n {s.pronunciationBreakUnexpected}\n </p>\n ) : null}\n {past(word.breaks?.missing, breakThreshold) ? (\n <p className=\"lk-pf-break\" data-break=\"missing\">\n {s.pronunciationBreakMissing}\n </p>\n ) : null}\n </div>\n ) : null}\n </>\n ) : null}\n </li>\n );\n })}\n </ol>\n <ul className=\"lk-pf-legend\">\n {LEGEND_STATES.map((state) => (\n <li key={state} data-state={state}>\n {s.pronunciationLegend(state)}\n </li>\n ))}\n </ul>\n {/* Only where there are symbols to explain, and only for the alphabet\n this note names. An engine that reported another one, or none at\n all, gets no note rather than a wrong one. */}\n {alphabet === 'ipa' ? <p className=\"lk-pf-alphabet\">{s.pronunciationIpaNote}</p> : null}\n </>\n ) : null}\n\n {past(monotone, monotoneThreshold) ? (\n <p className=\"lk-pf-monotone\" role=\"note\">\n {s.pronunciationMonotone}\n </p>\n ) : null}\n\n {audioUrl !== undefined ? (\n // 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\n <audio ref={audioRef} className=\"lk-pf-audio\" src={audioUrl} preload=\"metadata\" />\n ) : null}\n </section>\n );\n}\n","'use client';\n\nimport { useLkStrings } from '../../i18n/LkIntlProvider.js';\nimport { ActivityErrorBoundary } from '../ActivityErrorBoundary.js';\nimport {\n PronunciationFeedback as PronunciationFeedbackCore,\n type PronunciationFeedbackProps,\n} from './PronunciationFeedback.js';\n\nexport type { PronunciationFeedbackProps } from './PronunciationFeedback.js';\n\n/**\n * Public pronunciation-feedback panel: the core wrapped in\n * `ActivityErrorBoundary` so a render failure (the dev throw for evidence\n * `validateSpeechAssessment` refuses among them) degrades to an accessible\n * fallback instead of crashing the host.\n *\n * **No `activityTitle`.** Its `data` is two fields of a read-aloud item and\n * carries no title, so the production fallback is the unnamed one. Naming it\n * would mean inventing a title for a panel that never had one.\n */\nexport function PronunciationFeedback(props: PronunciationFeedbackProps) {\n const strings = useLkStrings(props.strings);\n return (\n <ActivityErrorBoundary strings={strings}>\n <PronunciationFeedbackCore {...props} />\n </ActivityErrorBoundary>\n );\n}\n"]}
|