@intellectif/lk-react 23.1.0 → 23.1.2

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 (360) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/README.md +9 -5
  3. package/dist/ActivityErrorBoundary-B379hvtL.cjs +39 -0
  4. package/dist/ActivityErrorBoundary-B379hvtL.cjs.map +1 -0
  5. package/dist/ActivityErrorBoundary-Dw6SbWv3.js +34 -0
  6. package/dist/ActivityErrorBoundary-Dw6SbWv3.js.map +1 -0
  7. package/dist/ActivityMedia-CQQTRNl7.cjs +641 -0
  8. package/dist/ActivityMedia-CQQTRNl7.cjs.map +1 -0
  9. package/dist/ActivityMedia-vDiP-Psa.js +636 -0
  10. package/dist/ActivityMedia-vDiP-Psa.js.map +1 -0
  11. package/dist/ActivityPreview-1hhTkYuj.js +240 -0
  12. package/dist/ActivityPreview-1hhTkYuj.js.map +1 -0
  13. package/dist/ActivityPreview-BKr34exz.cjs +245 -0
  14. package/dist/ActivityPreview-BKr34exz.cjs.map +1 -0
  15. package/dist/ActivitySequence-CkX87pfB.js +839 -0
  16. package/dist/ActivitySequence-CkX87pfB.js.map +1 -0
  17. package/dist/ActivitySequence-CmoSyDnp.cjs +844 -0
  18. package/dist/ActivitySequence-CmoSyDnp.cjs.map +1 -0
  19. package/dist/Dictation-BlxRp-3k.js +926 -0
  20. package/dist/Dictation-BlxRp-3k.js.map +1 -0
  21. package/dist/Dictation-jECzL88E.cjs +931 -0
  22. package/dist/Dictation-jECzL88E.cjs.map +1 -0
  23. package/dist/FeedbackRegion-BNNMtTY-.cjs +108 -0
  24. package/dist/FeedbackRegion-BNNMtTY-.cjs.map +1 -0
  25. package/dist/FeedbackRegion-CyPSp8bA.js +85 -0
  26. package/dist/FeedbackRegion-CyPSp8bA.js.map +1 -0
  27. package/dist/FillInTheBlanks-B6xzqYXx.js +474 -0
  28. package/dist/FillInTheBlanks-B6xzqYXx.js.map +1 -0
  29. package/dist/FillInTheBlanks-BpKhdHg2.cjs +479 -0
  30. package/dist/FillInTheBlanks-BpKhdHg2.cjs.map +1 -0
  31. package/dist/GapSelect-DUSiaEQH.js +441 -0
  32. package/dist/GapSelect-DUSiaEQH.js.map +1 -0
  33. package/dist/GapSelect-payTq_Zn.cjs +446 -0
  34. package/dist/GapSelect-payTq_Zn.cjs.map +1 -0
  35. package/dist/InteractiveVideo-BguGv-HA.cjs +2955 -0
  36. package/dist/InteractiveVideo-BguGv-HA.cjs.map +1 -0
  37. package/dist/InteractiveVideo-Cg2jbU3j.js +2944 -0
  38. package/dist/InteractiveVideo-Cg2jbU3j.js.map +1 -0
  39. package/dist/LkAiProvider-DAPpA3PQ.d.cts +84 -0
  40. package/dist/LkAiProvider-DAPpA3PQ.d.cts.map +1 -0
  41. package/dist/LkAiProvider-QdEcIhoo.d.ts +84 -0
  42. package/dist/LkAiProvider-QdEcIhoo.d.ts.map +1 -0
  43. package/dist/LkIntlProvider-D_FdYXjQ.js +375 -0
  44. package/dist/LkIntlProvider-D_FdYXjQ.js.map +1 -0
  45. package/dist/LkIntlProvider-oi57lYB0.cjs +410 -0
  46. package/dist/LkIntlProvider-oi57lYB0.cjs.map +1 -0
  47. package/dist/MultipleChoice-COn5MtKY.cjs +433 -0
  48. package/dist/MultipleChoice-COn5MtKY.cjs.map +1 -0
  49. package/dist/MultipleChoice-Dra0_KYD.js +428 -0
  50. package/dist/MultipleChoice-Dra0_KYD.js.map +1 -0
  51. package/dist/PronunciationFeedback-DlMPGjkR.cjs +831 -0
  52. package/dist/PronunciationFeedback-DlMPGjkR.cjs.map +1 -0
  53. package/dist/PronunciationFeedback-Dxd6XPkO.js +772 -0
  54. package/dist/PronunciationFeedback-Dxd6XPkO.js.map +1 -0
  55. package/dist/ReadAloud-C8RhaUqV.js +1092 -0
  56. package/dist/{chunk-DC4CQCK5.js.map → ReadAloud-C8RhaUqV.js.map} +1 -1
  57. package/dist/ReadAloud-akDehitT.cjs +1103 -0
  58. package/dist/ReadAloud-akDehitT.cjs.map +1 -0
  59. package/dist/StimulusPanel-BgzByfkb.js +74 -0
  60. package/dist/StimulusPanel-BgzByfkb.js.map +1 -0
  61. package/dist/StimulusPanel-b66gaN3i.cjs +79 -0
  62. package/dist/StimulusPanel-b66gaN3i.cjs.map +1 -0
  63. package/dist/ThemeProvider-B6DyGqzh.d.cts +62 -0
  64. package/dist/ThemeProvider-B6DyGqzh.d.cts.map +1 -0
  65. package/dist/ThemeProvider-BCrxBaY-.d.ts +62 -0
  66. package/dist/ThemeProvider-BCrxBaY-.d.ts.map +1 -0
  67. package/dist/ThemeProvider-BRN1tj4n.cjs +187 -0
  68. package/dist/ThemeProvider-BRN1tj4n.cjs.map +1 -0
  69. package/dist/ThemeProvider-C7K9iMLX.js +158 -0
  70. package/dist/ThemeProvider-C7K9iMLX.js.map +1 -0
  71. package/dist/WrittenResponse-Bj3eFmGl.cjs +491 -0
  72. package/dist/WrittenResponse-Bj3eFmGl.cjs.map +1 -0
  73. package/dist/WrittenResponse-Cl8G6nO_.js +486 -0
  74. package/dist/WrittenResponse-Cl8G6nO_.js.map +1 -0
  75. package/dist/ai/LkAiProvider.cjs +34 -14
  76. package/dist/ai/LkAiProvider.cjs.map +1 -1
  77. package/dist/ai/LkAiProvider.d.cts +2 -83
  78. package/dist/ai/LkAiProvider.d.ts +2 -83
  79. package/dist/ai/LkAiProvider.js +33 -2
  80. package/dist/ai/LkAiProvider.js.map +1 -1
  81. package/dist/ai/useAiHelp.cjs +7 -25
  82. package/dist/ai/useAiHelp.d.cts +175 -177
  83. package/dist/ai/useAiHelp.d.cts.map +1 -0
  84. package/dist/ai/useAiHelp.d.ts +175 -177
  85. package/dist/ai/useAiHelp.d.ts.map +1 -0
  86. package/dist/ai/useAiHelp.js +3 -4
  87. package/dist/components/ActivityPreview.cjs +3 -29
  88. package/dist/components/ActivityPreview.d.cts +2 -81
  89. package/dist/components/ActivityPreview.d.ts +2 -81
  90. package/dist/components/ActivityPreview.js +2 -20
  91. package/dist/components/ActivitySequence.cjs +3 -30
  92. package/dist/components/ActivitySequence.d.cts +2 -432
  93. package/dist/components/ActivitySequence.d.ts +2 -432
  94. package/dist/components/ActivitySequence.js +2 -21
  95. package/dist/components/Dictation.cjs +4 -21
  96. package/dist/components/Dictation.d.cts +2 -19
  97. package/dist/components/Dictation.d.ts +2 -19
  98. package/dist/components/Dictation.js +3 -12
  99. package/dist/components/FillInTheBlanks.cjs +4 -20
  100. package/dist/components/FillInTheBlanks.d.cts +2 -30
  101. package/dist/components/FillInTheBlanks.d.ts +2 -30
  102. package/dist/components/FillInTheBlanks.js +3 -11
  103. package/dist/components/GapSelect.cjs +4 -20
  104. package/dist/components/GapSelect.d.cts +2 -27
  105. package/dist/components/GapSelect.d.ts +2 -27
  106. package/dist/components/GapSelect.js +3 -11
  107. package/dist/components/InteractiveVideo.cjs +5 -32
  108. package/dist/components/InteractiveVideo.d.cts +3 -291
  109. package/dist/components/InteractiveVideo.d.ts +3 -291
  110. package/dist/components/InteractiveVideo.js +3 -19
  111. package/dist/components/MultipleChoice.cjs +4 -20
  112. package/dist/components/MultipleChoice.d.cts +2 -26
  113. package/dist/components/MultipleChoice.d.ts +2 -26
  114. package/dist/components/MultipleChoice.js +3 -11
  115. package/dist/components/PronunciationFeedback.cjs +4 -17
  116. package/dist/components/PronunciationFeedback.d.cts +2 -90
  117. package/dist/components/PronunciationFeedback.d.ts +2 -90
  118. package/dist/components/PronunciationFeedback.js +3 -8
  119. package/dist/components/ReadAloud.cjs +4 -22
  120. package/dist/components/ReadAloud.d.cts +7 -11
  121. package/dist/components/ReadAloud.d.cts.map +1 -0
  122. package/dist/components/ReadAloud.d.ts +7 -11
  123. package/dist/components/ReadAloud.d.ts.map +1 -0
  124. package/dist/components/ReadAloud.js +3 -13
  125. package/dist/components/StimulusPanel.cjs +3 -14
  126. package/dist/components/StimulusPanel.d.cts +2 -51
  127. package/dist/components/StimulusPanel.d.ts +2 -51
  128. package/dist/components/StimulusPanel.js +2 -5
  129. package/dist/components/WrittenResponse.cjs +4 -19
  130. package/dist/components/WrittenResponse.d.cts +3 -18
  131. package/dist/components/WrittenResponse.d.ts +3 -18
  132. package/dist/components/WrittenResponse.js +3 -10
  133. package/dist/direction-Dq0_HtA5.js +183 -0
  134. package/dist/direction-Dq0_HtA5.js.map +1 -0
  135. package/dist/direction-K1pj0HCA.cjs +200 -0
  136. package/dist/direction-K1pj0HCA.cjs.map +1 -0
  137. package/dist/hooks/useActivityState.cjs +28 -10
  138. package/dist/hooks/useActivityState.cjs.map +1 -1
  139. package/dist/hooks/useActivityState.d.cts +25 -24
  140. package/dist/hooks/useActivityState.d.cts.map +1 -0
  141. package/dist/hooks/useActivityState.d.ts +25 -24
  142. package/dist/hooks/useActivityState.d.ts.map +1 -0
  143. package/dist/hooks/useActivityState.js +28 -2
  144. package/dist/hooks/useActivityState.js.map +1 -1
  145. package/dist/hooks/useSpeechRecorder.cjs +5 -16
  146. package/dist/hooks/useSpeechRecorder.d.cts +2 -127
  147. package/dist/hooks/useSpeechRecorder.d.ts +2 -127
  148. package/dist/hooks/useSpeechRecorder.js +3 -3
  149. package/dist/hooks/useXAPI.cjs +126 -10
  150. package/dist/hooks/useXAPI.cjs.map +1 -1
  151. package/dist/hooks/useXAPI.d.cts +14 -14
  152. package/dist/hooks/useXAPI.d.cts.map +1 -0
  153. package/dist/hooks/useXAPI.d.ts +14 -14
  154. package/dist/hooks/useXAPI.d.ts.map +1 -0
  155. package/dist/hooks/useXAPI.js +126 -2
  156. package/dist/hooks/useXAPI.js.map +1 -1
  157. package/dist/i18n/LkIntlProvider.cjs +9 -32
  158. package/dist/i18n/LkIntlProvider.d.cts +34 -38
  159. package/dist/i18n/LkIntlProvider.d.cts.map +1 -0
  160. package/dist/i18n/LkIntlProvider.d.ts +34 -38
  161. package/dist/i18n/LkIntlProvider.d.ts.map +1 -0
  162. package/dist/i18n/LkIntlProvider.js +3 -3
  163. package/dist/index-6P4rJHrJ.d.cts +432 -0
  164. package/dist/index-6P4rJHrJ.d.cts.map +1 -0
  165. package/dist/index-90CX504k.d.ts +90 -0
  166. package/dist/index-90CX504k.d.ts.map +1 -0
  167. package/dist/index-B_SgnjsC.d.cts +291 -0
  168. package/dist/index-B_SgnjsC.d.cts.map +1 -0
  169. package/dist/index-BavVW5z5.d.cts +25 -0
  170. package/dist/index-BavVW5z5.d.cts.map +1 -0
  171. package/dist/index-BhHv6Fiu.d.cts +28 -0
  172. package/dist/index-BhHv6Fiu.d.cts.map +1 -0
  173. package/dist/index-Bsu8VciA.d.ts +432 -0
  174. package/dist/index-Bsu8VciA.d.ts.map +1 -0
  175. package/dist/index-C1KGpD1k.d.ts +49 -0
  176. package/dist/index-C1KGpD1k.d.ts.map +1 -0
  177. package/dist/index-CKTpYTCC.d.cts +49 -0
  178. package/dist/index-CKTpYTCC.d.cts.map +1 -0
  179. package/dist/index-CO_SRFJj.d.ts +16 -0
  180. package/dist/index-CO_SRFJj.d.ts.map +1 -0
  181. package/dist/index-CSsuUsP7.d.cts +124 -0
  182. package/dist/index-CSsuUsP7.d.cts.map +1 -0
  183. package/dist/index-CunUyeuN.d.cts +78 -0
  184. package/dist/index-CunUyeuN.d.cts.map +1 -0
  185. package/dist/index-CzfRzks6.d.cts +90 -0
  186. package/dist/index-CzfRzks6.d.cts.map +1 -0
  187. package/dist/index-D14xtpBS.d.cts +24 -0
  188. package/dist/index-D14xtpBS.d.cts.map +1 -0
  189. package/dist/index-DHRPAPZf.d.ts +291 -0
  190. package/dist/index-DHRPAPZf.d.ts.map +1 -0
  191. package/dist/index-DLV6cK26.d.cts +16 -0
  192. package/dist/index-DLV6cK26.d.cts.map +1 -0
  193. package/dist/index-DbCCQ9cP.d.ts +124 -0
  194. package/dist/index-DbCCQ9cP.d.ts.map +1 -0
  195. package/dist/index-DeV5B2m5.d.ts +24 -0
  196. package/dist/index-DeV5B2m5.d.ts.map +1 -0
  197. package/dist/index-TZCJVSge.d.ts +28 -0
  198. package/dist/index-TZCJVSge.d.ts.map +1 -0
  199. package/dist/index-ZVvLHsd3.d.ts +25 -0
  200. package/dist/index-ZVvLHsd3.d.ts.map +1 -0
  201. package/dist/index-eQwBgpSw.d.ts +78 -0
  202. package/dist/index-eQwBgpSw.d.ts.map +1 -0
  203. package/dist/index.cjs +118 -163
  204. package/dist/index.cjs.map +1 -1
  205. package/dist/index.d.cts +20 -24
  206. package/dist/index.d.ts +20 -24
  207. package/dist/index.js +84 -29
  208. package/dist/index.js.map +1 -1
  209. package/dist/theme/ThemeProvider.cjs +8 -28
  210. package/dist/theme/ThemeProvider.d.cts +2 -59
  211. package/dist/theme/ThemeProvider.d.ts +2 -59
  212. package/dist/theme/ThemeProvider.js +3 -3
  213. package/dist/tries-CbbtZkdG.cjs +363 -0
  214. package/dist/tries-CbbtZkdG.cjs.map +1 -0
  215. package/dist/tries-DTHSrLCm.js +310 -0
  216. package/dist/tries-DTHSrLCm.js.map +1 -0
  217. package/dist/types-BOQd93x3.d.cts +994 -0
  218. package/dist/types-BOQd93x3.d.cts.map +1 -0
  219. package/dist/types-HIrgF_mS.d.ts +994 -0
  220. package/dist/types-HIrgF_mS.d.ts.map +1 -0
  221. package/dist/useAiHelp-B-ccDL5Q.js +906 -0
  222. package/dist/useAiHelp-B-ccDL5Q.js.map +1 -0
  223. package/dist/useAiHelp-h9yOLFqt.cjs +995 -0
  224. package/dist/useAiHelp-h9yOLFqt.cjs.map +1 -0
  225. package/dist/useSpeechRecorder--O_f-xvt.cjs +720 -0
  226. package/dist/useSpeechRecorder--O_f-xvt.cjs.map +1 -0
  227. package/dist/useSpeechRecorder-DtAYvl02.d.cts +154 -0
  228. package/dist/useSpeechRecorder-DtAYvl02.d.cts.map +1 -0
  229. package/dist/useSpeechRecorder-DtAYvl02.d.ts +154 -0
  230. package/dist/useSpeechRecorder-DtAYvl02.d.ts.map +1 -0
  231. package/dist/useSpeechRecorder-zIi9Dlw8.js +709 -0
  232. package/dist/useSpeechRecorder-zIi9Dlw8.js.map +1 -0
  233. package/package.json +10 -11
  234. package/dist/WrittenResponse-B7A5gHMj.d.ts +0 -115
  235. package/dist/WrittenResponse-BpL9p2Hn.d.cts +0 -115
  236. package/dist/ai/useAiHelp.cjs.map +0 -1
  237. package/dist/ai/useAiHelp.js.map +0 -1
  238. package/dist/chunk-2PNSVBLQ.cjs +0 -54
  239. package/dist/chunk-2PNSVBLQ.cjs.map +0 -1
  240. package/dist/chunk-2TW6WLBC.js +0 -80
  241. package/dist/chunk-2TW6WLBC.js.map +0 -1
  242. package/dist/chunk-4ZZWZSWI.js +0 -61
  243. package/dist/chunk-4ZZWZSWI.js.map +0 -1
  244. package/dist/chunk-6N43WDVG.js +0 -29
  245. package/dist/chunk-6N43WDVG.js.map +0 -1
  246. package/dist/chunk-7ALBSPYR.cjs +0 -436
  247. package/dist/chunk-7ALBSPYR.cjs.map +0 -1
  248. package/dist/chunk-7NFY3M3W.js +0 -234
  249. package/dist/chunk-7NFY3M3W.js.map +0 -1
  250. package/dist/chunk-7SB2MJA4.js +0 -29
  251. package/dist/chunk-7SB2MJA4.js.map +0 -1
  252. package/dist/chunk-ABJJ3LII.js +0 -434
  253. package/dist/chunk-ABJJ3LII.js.map +0 -1
  254. package/dist/chunk-BIGGXHAW.cjs +0 -342
  255. package/dist/chunk-BIGGXHAW.cjs.map +0 -1
  256. package/dist/chunk-C7QZE5BI.js +0 -430
  257. package/dist/chunk-C7QZE5BI.js.map +0 -1
  258. package/dist/chunk-CWTUWNV3.cjs +0 -2937
  259. package/dist/chunk-CWTUWNV3.cjs.map +0 -1
  260. package/dist/chunk-DC4CQCK5.js +0 -915
  261. package/dist/chunk-DS6HN3YR.cjs +0 -496
  262. package/dist/chunk-DS6HN3YR.cjs.map +0 -1
  263. package/dist/chunk-FDQR4GAZ.js +0 -240
  264. package/dist/chunk-FDQR4GAZ.js.map +0 -1
  265. package/dist/chunk-FKGMSOJF.js +0 -394
  266. package/dist/chunk-FKGMSOJF.js.map +0 -1
  267. package/dist/chunk-G6PXJ2SA.js +0 -823
  268. package/dist/chunk-G6PXJ2SA.js.map +0 -1
  269. package/dist/chunk-HNLRVRQR.js +0 -335
  270. package/dist/chunk-HNLRVRQR.js.map +0 -1
  271. package/dist/chunk-J3C72RPK.cjs +0 -31
  272. package/dist/chunk-J3C72RPK.cjs.map +0 -1
  273. package/dist/chunk-K63VPFOV.cjs +0 -134
  274. package/dist/chunk-K63VPFOV.cjs.map +0 -1
  275. package/dist/chunk-KFLIDTDU.cjs +0 -825
  276. package/dist/chunk-KFLIDTDU.cjs.map +0 -1
  277. package/dist/chunk-KHKH5LCT.cjs +0 -250
  278. package/dist/chunk-KHKH5LCT.cjs.map +0 -1
  279. package/dist/chunk-LBEKRN6Y.cjs +0 -664
  280. package/dist/chunk-LBEKRN6Y.cjs.map +0 -1
  281. package/dist/chunk-LPUWXIPR.cjs +0 -578
  282. package/dist/chunk-LPUWXIPR.cjs.map +0 -1
  283. package/dist/chunk-MCO52PV5.js +0 -520
  284. package/dist/chunk-MCO52PV5.js.map +0 -1
  285. package/dist/chunk-MKXEM2C5.cjs +0 -396
  286. package/dist/chunk-MKXEM2C5.cjs.map +0 -1
  287. package/dist/chunk-MXNC5UNO.js +0 -563
  288. package/dist/chunk-MXNC5UNO.js.map +0 -1
  289. package/dist/chunk-NAK55BYU.cjs +0 -859
  290. package/dist/chunk-NAK55BYU.cjs.map +0 -1
  291. package/dist/chunk-NDBMKGQ5.cjs +0 -599
  292. package/dist/chunk-NDBMKGQ5.cjs.map +0 -1
  293. package/dist/chunk-OD65OZIW.cjs +0 -159
  294. package/dist/chunk-OD65OZIW.cjs.map +0 -1
  295. package/dist/chunk-ORT3KLGI.js +0 -50
  296. package/dist/chunk-ORT3KLGI.js.map +0 -1
  297. package/dist/chunk-PNNUPUH4.cjs +0 -82
  298. package/dist/chunk-PNNUPUH4.cjs.map +0 -1
  299. package/dist/chunk-PWHECW6N.js +0 -2934
  300. package/dist/chunk-PWHECW6N.js.map +0 -1
  301. package/dist/chunk-RBS5A2DF.js +0 -857
  302. package/dist/chunk-RBS5A2DF.js.map +0 -1
  303. package/dist/chunk-RPWUDXG5.cjs +0 -63
  304. package/dist/chunk-RPWUDXG5.cjs.map +0 -1
  305. package/dist/chunk-TCDJY3H6.js +0 -494
  306. package/dist/chunk-TCDJY3H6.js.map +0 -1
  307. package/dist/chunk-TP5UMRAU.cjs +0 -918
  308. package/dist/chunk-TP5UMRAU.cjs.map +0 -1
  309. package/dist/chunk-TS7J6DTY.js +0 -597
  310. package/dist/chunk-TS7J6DTY.js.map +0 -1
  311. package/dist/chunk-TUVENMXB.cjs +0 -20
  312. package/dist/chunk-TUVENMXB.cjs.map +0 -1
  313. package/dist/chunk-UDCQTOVU.cjs +0 -523
  314. package/dist/chunk-UDCQTOVU.cjs.map +0 -1
  315. package/dist/chunk-UHKZXOXD.js +0 -128
  316. package/dist/chunk-UHKZXOXD.js.map +0 -1
  317. package/dist/chunk-V6BA44DS.cjs +0 -236
  318. package/dist/chunk-V6BA44DS.cjs.map +0 -1
  319. package/dist/chunk-XO5R73Y4.js +0 -653
  320. package/dist/chunk-XO5R73Y4.js.map +0 -1
  321. package/dist/chunk-XOR4MPEN.cjs +0 -31
  322. package/dist/chunk-XOR4MPEN.cjs.map +0 -1
  323. package/dist/chunk-YORQQXGO.js +0 -155
  324. package/dist/chunk-YORQQXGO.js.map +0 -1
  325. package/dist/chunk-YY4PFR3Z.cjs +0 -432
  326. package/dist/chunk-YY4PFR3Z.cjs.map +0 -1
  327. package/dist/chunk-ZPI6Q42R.js +0 -17
  328. package/dist/chunk-ZPI6Q42R.js.map +0 -1
  329. package/dist/components/ActivityPreview.cjs.map +0 -1
  330. package/dist/components/ActivityPreview.js.map +0 -1
  331. package/dist/components/ActivitySequence.cjs.map +0 -1
  332. package/dist/components/ActivitySequence.js.map +0 -1
  333. package/dist/components/Dictation.cjs.map +0 -1
  334. package/dist/components/Dictation.js.map +0 -1
  335. package/dist/components/FillInTheBlanks.cjs.map +0 -1
  336. package/dist/components/FillInTheBlanks.js.map +0 -1
  337. package/dist/components/GapSelect.cjs.map +0 -1
  338. package/dist/components/GapSelect.js.map +0 -1
  339. package/dist/components/InteractiveVideo.cjs.map +0 -1
  340. package/dist/components/InteractiveVideo.js.map +0 -1
  341. package/dist/components/MultipleChoice.cjs.map +0 -1
  342. package/dist/components/MultipleChoice.js.map +0 -1
  343. package/dist/components/PronunciationFeedback.cjs.map +0 -1
  344. package/dist/components/PronunciationFeedback.js.map +0 -1
  345. package/dist/components/ReadAloud.cjs.map +0 -1
  346. package/dist/components/ReadAloud.js.map +0 -1
  347. package/dist/components/StimulusPanel.cjs.map +0 -1
  348. package/dist/components/StimulusPanel.js.map +0 -1
  349. package/dist/components/WrittenResponse.cjs.map +0 -1
  350. package/dist/components/WrittenResponse.js.map +0 -1
  351. package/dist/hooks/useSpeechRecorder.cjs.map +0 -1
  352. package/dist/hooks/useSpeechRecorder.js.map +0 -1
  353. package/dist/i18n/LkIntlProvider.cjs.map +0 -1
  354. package/dist/i18n/LkIntlProvider.js.map +0 -1
  355. package/dist/machine-CuwKqWXC.d.cts +0 -26
  356. package/dist/machine-CuwKqWXC.d.ts +0 -26
  357. package/dist/theme/ThemeProvider.cjs.map +0 -1
  358. package/dist/theme/ThemeProvider.js.map +0 -1
  359. package/dist/types-C4IsUQsK.d.ts +0 -989
  360. package/dist/types-CJgZWtl4.d.cts +0 -989
@@ -0,0 +1,1092 @@
1
+ 'use client';
2
+ import { a as ANONYMOUS_ACTOR, c as isDevelopment, f as SequenceSlotContext, m as stampTake, o as detailIsRight, p as mintTake, u as objectIdFor } from "./useAiHelp-B-ccDL5Q.js";
3
+ import { i as useLkStrings } from "./LkIntlProvider-D_FdYXjQ.js";
4
+ import { t as ActivityErrorBoundary } from "./ActivityErrorBoundary-Dw6SbWv3.js";
5
+ import { useActivityState } from "./hooks/useActivityState.js";
6
+ import { n as localeDirectionOf } from "./direction-Dq0_HtA5.js";
7
+ import { t as ActivityMedia } from "./ActivityMedia-vDiP-Psa.js";
8
+ import { i as useDeliveryPolicy, t as FeedbackRegion } from "./FeedbackRegion-CyPSp8bA.js";
9
+ import { t as useSpeechRecorder } from "./useSpeechRecorder-zIi9Dlw8.js";
10
+ import { a as percentOfGrade, c as readOutcome, i as fractionOfGrade, l as usePlaybackRefusal, n as VISUALLY_HIDDEN, o as readAssessResult, r as markSentence, s as readEvidence, t as PronunciationFeedback, u as ReadingCoaching } from "./PronunciationFeedback-Dxd6XPkO.js";
11
+ import { useCallback, useContext, useEffect, useId, useMemo, useRef, useState } from "react";
12
+ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
13
+ import { ActivitySchemaError, READ_ALOUD_MAX_SECONDS, READ_ALOUD_MAX_TAKES, assertRedacted, validateActivity, xAPIBuilder, xapiDefinitionFor } from "@intellectif/lk-core";
14
+ //#region src/components/shared/capture-registry.ts
15
+ /**
16
+ * Microphone captures that must stop when the region holding them goes away,
17
+ * keyed by group name.
18
+ *
19
+ * Module-level for the reason `playback-group.ts` is: the thing that knows a
20
+ * pane was hidden — the sequence — is not the thing holding the microphone,
21
+ * and a context would have to be threaded through every activity component to
22
+ * reach one. The caller mints the group name per region; a sequence uses the
23
+ * slot's, so hiding one slot stops that slot's capture and no other's. The name
24
+ * reaches a recorder only through `SequenceSlotContext`, never as a prop, so no
25
+ * consumer string can join another region's group.
26
+ *
27
+ * This **supplements** a recorder's own teardown and never replaces it. The
28
+ * sequence's pane effect runs only on a `hidden` transition, not on unmount,
29
+ * and neither a preview nor a standalone render has panes at all — so a
30
+ * component that records still releases the microphone from its own cleanup,
31
+ * and this registry only covers the case where the component is left mounted
32
+ * and merely hidden.
33
+ *
34
+ * No `useCaptureGroup` hook, unlike playback: that one re-registers after every
35
+ * commit because the element behind a ref can be replaced while the ref object
36
+ * is not, whereas a recorder's `stop` keeps one identity for the life of the
37
+ * hook and a plain effect registers it exactly once.
38
+ */
39
+ const groups = /* @__PURE__ */ new Map();
40
+ /** Adds a capture's stop to a group; returns the matching removal. */
41
+ function joinCaptureGroup(group, stop) {
42
+ let members = groups.get(group);
43
+ if (members === void 0) {
44
+ members = /* @__PURE__ */ new Set();
45
+ groups.set(group, members);
46
+ }
47
+ members.add(stop);
48
+ return () => {
49
+ const current = groups.get(group);
50
+ if (current === void 0) return;
51
+ current.delete(stop);
52
+ if (current.size === 0) groups.delete(group);
53
+ };
54
+ }
55
+ /**
56
+ * Stops every capture in the group.
57
+ *
58
+ * The members are copied before they are called. Each `stop` is expected to
59
+ * run its own removal, and a component that answers one by starting again
60
+ * would otherwise be stopped by the sweep that its own stop provoked.
61
+ */
62
+ function stopCaptureGroup(group) {
63
+ const members = groups.get(group);
64
+ if (members === void 0) return;
65
+ for (const stop of [...members]) stop();
66
+ }
67
+ //#endregion
68
+ //#region src/components/ReadAloud/recording-bounds.ts
69
+ const isNumber = (value) => typeof value === "number" && Number.isFinite(value);
70
+ /**
71
+ * The one reading of an item's `recording`: every bound the component captures
72
+ * under, every sentence that names one, and the key that decides whether a new
73
+ * `data` is a new reading all come from here, so no two of them can read the
74
+ * same field differently.
75
+ *
76
+ * `practice` renders unvalidated content in production, so what comes in may
77
+ * be anything a server stored. What comes out is always a set of bounds the
78
+ * schema accepts, and bounds the schema accepts come out unchanged. Three
79
+ * rules decide the rest, and each errs toward a take the learner can finish:
80
+ *
81
+ * - **A present bound that cannot be read is the schema's own ceiling**, never
82
+ * no bound at all: a `maxSeconds` of `"20"` or `NaN` records up to
83
+ * `READ_ALOUD_MAX_SECONDS`, and a `maxTakes` of `"1"` allows
84
+ * `READ_ALOUD_MAX_TAKES`. Unreadable data can make the recorder stricter
85
+ * than the ceiling, never looser: `maxTakes: "1"` used to mean unlimited
86
+ * takes. `null` is an absent optional, as it is everywhere a host sends JSON.
87
+ * - **A count is a whole number in range**: `0` and `1.5` read as `1`, `999`
88
+ * as the ceiling.
89
+ * - **A minimum the maximum cannot reach is no minimum.** A `minSeconds` of 30
90
+ * under a `maxSeconds` of 20 stopped every take at 20 and then refused it as
91
+ * too short, so the item could never be answered. The schema refuses that
92
+ * pair; a reading that honoured it would leave the learner a dead end.
93
+ */
94
+ function readRecordingBounds(recording) {
95
+ const bounds = typeof recording === "object" && recording !== null ? recording : {};
96
+ const maxSeconds = isNumber(bounds.maxSeconds) && bounds.maxSeconds > 0 ? Math.min(bounds.maxSeconds, READ_ALOUD_MAX_SECONDS) : READ_ALOUD_MAX_SECONDS;
97
+ const minSeconds = isNumber(bounds.minSeconds) && bounds.minSeconds >= 0 && bounds.minSeconds < maxSeconds ? bounds.minSeconds : void 0;
98
+ const takes = bounds.maxTakes;
99
+ return {
100
+ maxSeconds,
101
+ minSeconds,
102
+ maxTakes: takes === void 0 || takes === null ? void 0 : isNumber(takes) ? Math.min(Math.max(1, Math.floor(takes)), READ_ALOUD_MAX_TAKES) : READ_ALOUD_MAX_TAKES
103
+ };
104
+ }
105
+ /**
106
+ * A length of audio as the whole seconds a sentence says beside `maxSeconds`,
107
+ * never more than it. A bound may be fractional — the schema allows 2.5 — and
108
+ * rounding a take that ran to it said "3 of 2.5 seconds". Rounded to the
109
+ * nearest second for a finished take, and down for the counter of one still
110
+ * running, which should not reach its bound before the take does.
111
+ */
112
+ function secondsWithin(milliseconds, maxSeconds, rounding) {
113
+ const seconds = milliseconds / 1e3;
114
+ return Math.min(Math.max(0, rounding === "nearest" ? Math.round(seconds) : Math.floor(seconds)), maxSeconds);
115
+ }
116
+ //#endregion
117
+ //#region src/components/ReadAloud/ReadAloud.tsx
118
+ /**
119
+ * Names playback groups for the model recordings. Per mount and never derived
120
+ * from `useId`: two separately hydrated roots derive the same id for the same
121
+ * tree position, and their recordings would pause each other. The name never
122
+ * reaches the DOM.
123
+ */
124
+ let modelsGroupCount = 0;
125
+ /** A title with something in it a screen reader can say. */
126
+ const VISIBLE_TEXT_RE = /[^\p{White_Space}\p{Cc}\p{Cf}\p{Default_Ignorable_Code_Point}]/u;
127
+ /** A stored mark's id, as `gradeReadAloud` numbers the reference words. */
128
+ const MARK_ID_RE = /^w\d+$/;
129
+ /** The states a mark rebuilt from stored details can carry: insertions are not stored. */
130
+ const STORED_STATES = [
131
+ "correct",
132
+ "mispronounced",
133
+ "omitted"
134
+ ];
135
+ /** The level meter's resolution, in steps the skin can select on. */
136
+ const LEVEL_STEPS = 10;
137
+ /**
138
+ * Whether the screen offers "Try again" for the take this phase describes —
139
+ * the one answer to that question, read by the button that offers it and by
140
+ * the take state a pager is told, so the two cannot disagree. A retry re-sends
141
+ * the SAME take: a take that failed to upload is sent again, and a stored take
142
+ * whose assessment failed is judged again without being uploaded twice.
143
+ */
144
+ function offersRetry(phase, uploaded) {
145
+ return phase.kind === "upload-failed" || phase.kind === "assess-failed" && phase.retryable && uploaded !== null;
146
+ }
147
+ /**
148
+ * The state of the phase's take, as a pager deciding whether its set is
149
+ * finished reads it. Derived from the phase alone, so no path through a submit
150
+ * can leave a pager waiting on a take this component has finished with: every
151
+ * path ends in a phase.
152
+ */
153
+ function takeStateOf(phase, uploaded) {
154
+ if (phase.kind === "uploading" || phase.kind === "assessing") return "in-flight";
155
+ return offersRetry(phase, uploaded) ? "retryable" : "settled";
156
+ }
157
+ /**
158
+ * Whether the recorder is capturing, or about to: the one answer to "is a take
159
+ * being recorded", read by everything that must hold still for one. A
160
+ * permission prompt counts — a prompt answered after the pane went away would
161
+ * otherwise open a microphone nobody is in front of, and a model recording
162
+ * played while the prompt is up would be charged a play and then silenced.
163
+ */
164
+ function capturing(status) {
165
+ return status === "recording" || status === "requesting-permission";
166
+ }
167
+ /**
168
+ * What a take is a take OF: the item, the words, their language and the bounds
169
+ * the take is captured under. A change to any of these is a different reading,
170
+ * and a take of the old one answers nothing.
171
+ *
172
+ * Deliberately NOT the identity of `data`. `redact()` returns a new object on
173
+ * every call, and `activities={raw.map(redact)}` is a documented way to pass a
174
+ * paper, so a host that rebuilds its items on every render is ordinary — and a
175
+ * reset keyed on identity silently threw away every grade still in flight and
176
+ * refunded the take budget on each of those renders, so a `maxTakes: 1` exam
177
+ * accepted a second take. The title, the instructions and the model recordings
178
+ * are presentation; editing them leaves the take a take of the same reading.
179
+ *
180
+ * The bounds are the CLAMPED ones, which are what the recorder is given. Read
181
+ * field by field into strings, so no value unvalidated data carries can make
182
+ * the key throw.
183
+ */
184
+ function readingKeyOf(data, bounds) {
185
+ const text = (value) => typeof value === "string" ? value : null;
186
+ return JSON.stringify([
187
+ text(data.id),
188
+ text(data.referenceText),
189
+ text(data.locale),
190
+ String(bounds.maxSeconds),
191
+ String(bounds.minSeconds),
192
+ String(bounds.maxTakes)
193
+ ]);
194
+ }
195
+ /**
196
+ * The slower model recording as an `ActivityMedia`: it follows the model
197
+ * recording's playback policy minus `maxPlays` (`controls`, `seek`, `rate`,
198
+ * the native hints), so a locked scrubber or a fixed speed binds both files —
199
+ * and it never carries a budget of its own, so `<ActivityMedia>` never demands
200
+ * a binding for it. `undefined` when there is no slow recording.
201
+ */
202
+ function slowMediaFor(data, label) {
203
+ const slow = data.slowMedia;
204
+ if (slow === void 0) return;
205
+ const { maxPlays: _budget, ...inherited } = data.media?.playback ?? {};
206
+ const hasPolicy = Object.values(inherited).some((value) => value !== void 0);
207
+ return {
208
+ type: "audio",
209
+ url: slow.url,
210
+ alt: slow.alt ?? label,
211
+ ...hasPolicy ? { playback: inherited } : {}
212
+ };
213
+ }
214
+ /**
215
+ * Word marks rebuilt from stored scoring details — a server-recorded mark shown
216
+ * after the fact, with no assessment on the client. Inserted words are not in
217
+ * the details (`gradeReadAloud` stores one mark per REFERENCE word, and a word
218
+ * the learner added is not one the item asked for), so none appear.
219
+ *
220
+ * The details are a read grade's, so every entry is well-formed; what is left
221
+ * to decide here is whether they are word marks at all, rather than a rubric's
222
+ * own breakdown.
223
+ */
224
+ function marksFromDetails(details) {
225
+ if (details.length === 0 || !details.every((detail) => MARK_ID_RE.test(detail.itemId))) return null;
226
+ return details.map((detail) => {
227
+ const heard = Array.isArray(detail.learnerResponse) ? detail.learnerResponse.join(" ") : detail.learnerResponse;
228
+ const reference = Array.isArray(detail.correctResponse) ? detail.correctResponse.join(" ") : detail.correctResponse;
229
+ const state = detailIsRight(detail) ? "correct" : detail.outcome === "incorrect-omission" ? "omitted" : "mispronounced";
230
+ return {
231
+ itemId: detail.itemId,
232
+ reference,
233
+ heard,
234
+ state,
235
+ ...typeof detail.score === "number" ? { accuracy: detail.score * 100 } : {}
236
+ };
237
+ });
238
+ }
239
+ /** The stored take a learner response points at, or `null` for a blank or another type. */
240
+ function recordingOf(response) {
241
+ return response !== void 0 && response.type === "read-aloud" ? response.recording : null;
242
+ }
243
+ /** The response this component emits; `takes` is written only when one was made. */
244
+ function responseOf(recording, takes) {
245
+ return {
246
+ type: "read-aloud",
247
+ recording,
248
+ ...takes > 0 ? { takes } : {}
249
+ };
250
+ }
251
+ /**
252
+ * Which learner message an unscorable code earns. The two codes that mean
253
+ * "nothing was heard" send the learner back to a quieter room; every other one,
254
+ * the SDK's and an application's own alike — and no code at all — says only
255
+ * that this take could not be assessed. **The code itself is never shown** — it
256
+ * is a developer's label.
257
+ */
258
+ function unscorableMessage(code, s) {
259
+ return code === "no_speech" || code === "insufficient_voiced_time" ? s.readAloudNotHeard : s.readAloudNotAssessed;
260
+ }
261
+ /**
262
+ * The announcement, with grader feedback in its own isolated span: its
263
+ * direction is its own, so an English "Well read." in an Arabic interface keeps
264
+ * its full stop at its end. Its language is not guessed — feedback may be
265
+ * written in the interface's language or the reading's.
266
+ */
267
+ function AnnouncementText({ announcement }) {
268
+ if (announcement === null) return null;
269
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [announcement.text, announcement.feedback ? /* @__PURE__ */ jsxs(Fragment$1, { children: [" ", /* @__PURE__ */ jsx("span", {
270
+ dir: "auto",
271
+ children: announcement.feedback
272
+ })] }) : null] });
273
+ }
274
+ /**
275
+ * The server's outcome in `review`, as `readOutcome` read it. Nothing on this
276
+ * path scores, infers or defaults a grade.
277
+ *
278
+ * `scoreShown` says whether the grade is already on screen — it is, whenever
279
+ * `<PronunciationFeedback>` was given the same grade — so the sentence is not
280
+ * rendered twice. The wrapper and its attributes are rendered either way,
281
+ * because they are what a stylesheet and a diagnostic read.
282
+ */
283
+ function OutcomeSummary({ reading, s, scoreShown }) {
284
+ switch (reading.kind) {
285
+ case "graded": return /* @__PURE__ */ jsx("div", {
286
+ className: "lk-ra-outcome",
287
+ "data-status": reading.status,
288
+ "data-passed": String(reading.grade.passed),
289
+ children: scoreShown ? null : /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("p", {
290
+ className: "lk-ra-grade",
291
+ children: s.scoreAnnouncement(percentOfGrade(reading.grade), reading.grade.passed)
292
+ }), reading.grade.feedback ? /* @__PURE__ */ jsx("p", {
293
+ className: "lk-ra-grade-feedback",
294
+ dir: "auto",
295
+ children: reading.grade.feedback
296
+ }) : null] })
297
+ });
298
+ case "deferred": return /* @__PURE__ */ jsx("div", {
299
+ className: "lk-ra-outcome",
300
+ "data-status": "deferred",
301
+ children: /* @__PURE__ */ jsx("p", {
302
+ className: "lk-ra-grade",
303
+ children: s.awaitingGrade
304
+ })
305
+ });
306
+ case "unscorable": return /* @__PURE__ */ jsx("div", {
307
+ className: "lk-ra-outcome",
308
+ "data-status": "unscorable",
309
+ ...reading.code !== void 0 ? { "data-code": reading.code } : {},
310
+ children: /* @__PURE__ */ jsx("p", {
311
+ className: "lk-ra-grade",
312
+ children: s.couldNotBeGraded
313
+ })
314
+ });
315
+ default: return /* @__PURE__ */ jsx("div", {
316
+ className: "lk-ra-outcome",
317
+ ...reading.status !== void 0 ? { "data-status": reading.status } : {},
318
+ children: /* @__PURE__ */ jsx("p", {
319
+ className: "lk-ra-grade",
320
+ children: s.couldNotBeGraded
321
+ })
322
+ });
323
+ }
324
+ }
325
+ /**
326
+ * Reads a text aloud and hands the recording to the application.
327
+ *
328
+ * The SDK records; it never judges. A take is captured as mono 16-bit PCM WAV
329
+ * at 16 kHz — the one format `inspectWav` measures directly — handed to
330
+ * `recordingBinding.upload`, and reported through `onSubmit`. Whether anything
331
+ * assesses it, and where, is the application's: in `practice` an optional
332
+ * `assess` may come back with a grade, and in `exam` nothing is ever assessed
333
+ * on the client.
334
+ *
335
+ * **`recordingBinding.upload` is required outside `review`** and its absence
336
+ * throws at render, in production too: a learner must never speak into a
337
+ * control that stores nothing. `assess` is not required — a binding without it
338
+ * records, uploads and submits, and shows a notice in place of the feedback.
339
+ *
340
+ * **A grade that comes back is read, not trusted** — whether `assess` returned
341
+ * it or `outcome` carries it. A score that is not a number between 0 and its
342
+ * maximum, or a `passed` that is not a boolean, is shown as could-not-be-graded
343
+ * and never reaches `onComplete` or a statement; `onComplete` fires exactly when
344
+ * a score is on screen, with that score.
345
+ *
346
+ * **AI coaching**, with a `pronunciationCoaching` port: under the marks, a
347
+ * button asks a model to explain them — in `practice` once a take is graded,
348
+ * and in `review` wherever marks are shown, from kept evidence or from the
349
+ * stored grade's details. Never in `exam`. The marks are the engine's, and
350
+ * coaching on a word it did not mark is refused whole.
351
+ *
352
+ * **Under a strict Content-Security-Policy** the take is played back from a
353
+ * `blob:` URL, so `media-src` must allow `blob:` — refused, the player and the
354
+ * per-word buttons give way to a note saying the take cannot be played here —
355
+ * and the capture module loads from one too, so either `script-src` allows
356
+ * `blob:` or `workletUrl` names a self-hosted copy of `CAPTURE_PROCESSOR_SOURCE`.
357
+ *
358
+ * ```tsx
359
+ * <ReadAloud
360
+ * data={item}
361
+ * recordingBinding={{ upload, assess }}
362
+ * onSubmit={persist}
363
+ * onComplete={record}
364
+ * />
365
+ * ```
366
+ */
367
+ function ReadAloud$1({ data, onComplete, onSubmit, onInteraction, value, defaultValue, defaultSubmitted, renderMode = "practice", outcome, mediaBudget, mediaStrings, strings, theme, locale, disabled, recordingBinding, assessment, breakThreshold, monotoneThreshold, workletUrl, delivery, ai }) {
368
+ const isExam = renderMode === "exam";
369
+ const policy = useDeliveryPolicy(delivery);
370
+ const isReview = renderMode === "review";
371
+ const s = useLkStrings(strings);
372
+ const slot = useContext(SequenceSlotContext);
373
+ const captureGroup = slot?.captureGroup;
374
+ const devError = useMemo(() => {
375
+ if (!isDevelopment()) return null;
376
+ if (data.redacted === true) try {
377
+ assertRedacted(data);
378
+ return null;
379
+ } catch (error) {
380
+ return error instanceof Error ? error : new Error(String(error));
381
+ }
382
+ const result = validateActivity("read-aloud", data);
383
+ return result.success ? null : new ActivitySchemaError("read-aloud", result.errors);
384
+ }, [data]);
385
+ const { maxSeconds, minSeconds, maxTakes } = readRecordingBounds(data.recording);
386
+ const readingKey = readingKeyOf(data, {
387
+ maxSeconds,
388
+ minSeconds,
389
+ maxTakes
390
+ });
391
+ const recorder = useSpeechRecorder(useMemo(() => ({
392
+ maxDurationMs: maxSeconds * 1e3,
393
+ ...minSeconds !== void 0 ? { minDurationMs: minSeconds * 1e3 } : {},
394
+ ...maxTakes !== void 0 ? { maxTakes } : {},
395
+ ...workletUrl !== void 0 ? { workletUrl } : {}
396
+ }), [
397
+ maxSeconds,
398
+ minSeconds,
399
+ maxTakes,
400
+ workletUrl
401
+ ]));
402
+ const { state, start, complete, getTimeSpent, reset } = useActivityState(defaultSubmitted === true ? "completed" : "idle");
403
+ const [phase, setPhase] = useState({ kind: "idle" });
404
+ const [uploaded, setUploaded] = useState(null);
405
+ const [takeUrl, setTakeUrl] = useState(null);
406
+ const [playbackUrl, setPlaybackUrl] = useState(null);
407
+ const [errorNonce, setErrorNonce] = useState(0);
408
+ const titleId = `${useId()}-title`;
409
+ const [modelsGroup] = useState(() => {
410
+ modelsGroupCount += 1;
411
+ return `lk-ra-models-${modelsGroupCount}`;
412
+ });
413
+ const interactionRef = useRef(onInteraction);
414
+ interactionRef.current = onInteraction;
415
+ const bindingRef = useRef(recordingBinding);
416
+ bindingRef.current = recordingBinding;
417
+ const submittingRef = useRef(false);
418
+ const runRef = useRef(0);
419
+ const slotRef = useRef(slot);
420
+ slotRef.current = slot;
421
+ const reportedTakeRef = useRef(null);
422
+ useEffect(() => () => {
423
+ runRef.current += 1;
424
+ const take = reportedTakeRef.current;
425
+ if (take !== null) slotRef.current?.takeState(take, "settled");
426
+ }, []);
427
+ const fire = useCallback((type, payload) => {
428
+ interactionRef.current?.({
429
+ type,
430
+ activityId: data.id,
431
+ timestamp: Date.now(),
432
+ payload
433
+ });
434
+ }, [data.id]);
435
+ const lastStatusRef = useRef(recorder.status);
436
+ useEffect(() => {
437
+ if (lastStatusRef.current === recorder.status) return;
438
+ lastStatusRef.current = recorder.status;
439
+ if (recorder.status === "recording") fire("recording-started", {
440
+ takes: recorder.takesUsed,
441
+ durationMs: 0
442
+ });
443
+ else if (recorder.status === "recorded" && recorder.take !== null) fire("recording-stopped", {
444
+ takes: recorder.takesUsed,
445
+ durationMs: recorder.take.durationMs
446
+ });
447
+ }, [
448
+ recorder.status,
449
+ recorder.take,
450
+ recorder.takesUsed,
451
+ fire
452
+ ]);
453
+ useEffect(() => {
454
+ if (recorder.error !== null) setErrorNonce((nonce) => nonce + 1);
455
+ }, [recorder.error]);
456
+ const take = recorder.take;
457
+ useEffect(() => {
458
+ if (take === null || typeof URL.createObjectURL !== "function") return;
459
+ const url = URL.createObjectURL(take.blob);
460
+ setTakeUrl(url);
461
+ return () => {
462
+ URL.revokeObjectURL(url);
463
+ setTakeUrl(null);
464
+ };
465
+ }, [take]);
466
+ const restored = recordingOf(value ?? defaultValue);
467
+ const restoredKey = restored?.key ?? null;
468
+ useEffect(() => {
469
+ const binding = bindingRef.current;
470
+ if (!isReview || restored === null || binding?.playbackUrl === void 0) return;
471
+ let live = true;
472
+ Promise.resolve(binding.playbackUrl(restored)).then((url) => {
473
+ if (live) setPlaybackUrl(url);
474
+ }, () => {});
475
+ return () => {
476
+ live = false;
477
+ };
478
+ }, [
479
+ isReview,
480
+ restoredKey,
481
+ readingKey
482
+ ]);
483
+ const statusRef = useRef(recorder.status);
484
+ statusRef.current = recorder.status;
485
+ const releaseMicrophone = useCallback(() => {
486
+ if (capturing(statusRef.current)) recorder.discard();
487
+ }, [recorder.discard]);
488
+ useEffect(() => {
489
+ if (captureGroup === void 0) return;
490
+ return joinCaptureGroup(captureGroup, releaseMicrophone);
491
+ }, [captureGroup, releaseMicrophone]);
492
+ const defaultSubmittedRef = useRef(defaultSubmitted);
493
+ useEffect(() => {
494
+ defaultSubmittedRef.current = defaultSubmitted;
495
+ }, [defaultSubmitted]);
496
+ const lastReadingRef = useRef(readingKey);
497
+ useEffect(() => {
498
+ if (lastReadingRef.current === readingKey) return;
499
+ lastReadingRef.current = readingKey;
500
+ submittingRef.current = false;
501
+ runRef.current += 1;
502
+ setPhase({ kind: "idle" });
503
+ setUploaded(null);
504
+ setPlaybackUrl(null);
505
+ recorder.reset();
506
+ reset(defaultSubmittedRef.current === true ? "completed" : "idle");
507
+ }, [
508
+ readingKey,
509
+ reset,
510
+ recorder.reset
511
+ ]);
512
+ const phaseTake = phase.kind === "idle" ? null : phase.take;
513
+ const phaseTakeState = takeStateOf(phase, uploaded);
514
+ useEffect(() => {
515
+ const take = phaseTake ?? reportedTakeRef.current;
516
+ if (slot === null || take === null) return;
517
+ reportedTakeRef.current = take;
518
+ slot.takeState(take, phaseTakeState);
519
+ }, [
520
+ slot,
521
+ phaseTake,
522
+ phaseTakeState
523
+ ]);
524
+ const modelsRef = useRef(null);
525
+ const isCapturing = capturing(recorder.status);
526
+ useEffect(() => {
527
+ if (!isCapturing) return;
528
+ const node = modelsRef.current;
529
+ if (node === null) return;
530
+ const hush = () => {
531
+ for (const media of node.querySelectorAll("audio, video")) {
532
+ const element = media;
533
+ if (!element.paused) element.pause();
534
+ }
535
+ };
536
+ hush();
537
+ node.addEventListener("play", hush, true);
538
+ return () => {
539
+ node.removeEventListener("play", hush, true);
540
+ };
541
+ }, [isCapturing]);
542
+ const slowMedia = useMemo(() => slowMediaFor(data, s.readAloudSlowRecording), [data, s.readAloudSlowRecording]);
543
+ const takeAudio = isReview ? playbackUrl : takeUrl;
544
+ const playback = usePlaybackRefusal(takeAudio);
545
+ const submitRef = useRef(null);
546
+ if (devError) throw devError;
547
+ if (!isReview && recordingBinding?.upload === void 0) throw new Error(`ReadAloud "${data.id}" was rendered in renderMode "${renderMode}" without \`recordingBinding.upload\`. The learner could record a take that is stored nowhere and submitted as nothing, which looks like a working activity the whole way through. Pass a \`recordingBinding\` whose \`upload\` puts the take in your storage and returns its key. \`assess\` is optional: without it the take is still recorded, uploaded and submitted, and a notice replaces the feedback.`);
548
+ const submitted = state === "completed";
549
+ const inactive = disabled === true || submitted || isReview;
550
+ const busy = phase.kind === "uploading" || phase.kind === "assessing";
551
+ const takesLeft = maxTakes === void 0 ? void 0 : Math.max(0, maxTakes - recorder.takesUsed);
552
+ /** Marks a failure so the alert announces even when the sentence repeats. */
553
+ const raise = (next) => {
554
+ setErrorNonce((nonce) => nonce + 1);
555
+ setPhase(next);
556
+ };
557
+ /**
558
+ * Tells the pager slot, if there is one, that `take` is on its way — before
559
+ * anything is awaited. The report a pager's set waits on must exist before
560
+ * the first result it waits for can arrive; the take's later states follow
561
+ * from the phase it reaches (see the effect above).
562
+ */
563
+ const beginTakeReport = (take) => {
564
+ reportedTakeRef.current = take;
565
+ slot?.takeState(take, "in-flight");
566
+ };
567
+ const runAssess = async (ref, current, take) => {
568
+ const assess = bindingRef.current?.assess;
569
+ const durationMs = current?.durationMs ?? 0;
570
+ const takes = recorder.takesUsed;
571
+ const run = runRef.current;
572
+ if (assess === void 0) {
573
+ setPhase({
574
+ kind: "unassessed",
575
+ take
576
+ });
577
+ return;
578
+ }
579
+ setPhase({
580
+ kind: "assessing",
581
+ take
582
+ });
583
+ fire("assessment-requested", {
584
+ takes,
585
+ durationMs
586
+ });
587
+ let answer;
588
+ try {
589
+ answer = await assess(ref);
590
+ } catch {
591
+ if (run !== runRef.current) return;
592
+ raise({
593
+ kind: "assess-failed",
594
+ take,
595
+ retryable: true
596
+ });
597
+ fire("assessment-failed", {
598
+ takes,
599
+ durationMs,
600
+ retryable: true
601
+ });
602
+ return;
603
+ }
604
+ if (run !== runRef.current) return;
605
+ const result = readAssessResult(answer);
606
+ if (result.status === "failed") {
607
+ raise({
608
+ kind: "assess-failed",
609
+ take,
610
+ retryable: result.retryable
611
+ });
612
+ fire("assessment-failed", {
613
+ takes,
614
+ durationMs,
615
+ retryable: result.retryable
616
+ });
617
+ return;
618
+ }
619
+ if (result.status === "unscorable") {
620
+ setPhase({
621
+ kind: "unscorable",
622
+ take,
623
+ code: result.code
624
+ });
625
+ fire("assessment-failed", {
626
+ takes,
627
+ durationMs,
628
+ retryable: true,
629
+ ...result.code !== void 0 ? { code: result.code } : {}
630
+ });
631
+ return;
632
+ }
633
+ const timeSpent = getTimeSpent();
634
+ setPhase({
635
+ kind: "graded",
636
+ take,
637
+ assessment: result.assessment,
638
+ grade: result.grade
639
+ });
640
+ const grade = result.grade;
641
+ if (grade === null) {
642
+ fire("assessment-failed", {
643
+ takes,
644
+ durationMs,
645
+ retryable: true
646
+ });
647
+ return;
648
+ }
649
+ onComplete?.(stampTake({
650
+ score: grade.score,
651
+ maxScore: grade.maxScore,
652
+ passed: grade.passed,
653
+ timeSpent,
654
+ xapiStatement: xAPIBuilder.buildAnsweredStatement({
655
+ actor: ANONYMOUS_ACTOR,
656
+ object: {
657
+ id: objectIdFor(data.id),
658
+ name: { [data.locale ?? "en-US"]: data.title },
659
+ ...xapiDefinitionFor(data)
660
+ },
661
+ timeSpentMs: timeSpent,
662
+ response: result.recognizedText ?? "",
663
+ scoringResult: {
664
+ score: fractionOfGrade(grade),
665
+ maxScore: 1,
666
+ passed: grade.passed,
667
+ feedback: grade.feedback,
668
+ details: grade.details ?? []
669
+ }
670
+ })
671
+ }, take));
672
+ };
673
+ const submitTake = async (current) => {
674
+ const binding = bindingRef.current;
675
+ if (inactive || submittingRef.current || binding === void 0) return;
676
+ submittingRef.current = true;
677
+ const takes = recorder.takesUsed;
678
+ const run = runRef.current;
679
+ const take = mintTake();
680
+ beginTakeReport(take);
681
+ try {
682
+ let ref = null;
683
+ if (current !== null) {
684
+ setPhase({
685
+ kind: "uploading",
686
+ take
687
+ });
688
+ try {
689
+ ref = await binding.upload(current);
690
+ } catch {
691
+ if (run !== runRef.current) return;
692
+ raise({
693
+ kind: "upload-failed",
694
+ take
695
+ });
696
+ fire("recording-upload-failed", {
697
+ takes,
698
+ durationMs: current.durationMs,
699
+ retryable: true
700
+ });
701
+ return;
702
+ }
703
+ if (run !== runRef.current) return;
704
+ setUploaded(ref);
705
+ fire("recording-uploaded", {
706
+ takes,
707
+ durationMs: current.durationMs
708
+ });
709
+ }
710
+ onSubmit?.(stampTake(responseOf(ref, takes), take));
711
+ fire("submitted", {
712
+ takes,
713
+ durationMs: current?.durationMs ?? 0
714
+ });
715
+ if (isExam || ref === null) {
716
+ complete();
717
+ setPhase({
718
+ kind: "submitted",
719
+ take
720
+ });
721
+ return;
722
+ }
723
+ await runAssess(ref, current, take);
724
+ } finally {
725
+ submittingRef.current = false;
726
+ }
727
+ };
728
+ /**
729
+ * Judges the take that is already stored, again — the SAME take, so the same
730
+ * number. Guarded like a submit: an assessment costs an application money,
731
+ * and two presses of one retry must not buy two of them.
732
+ */
733
+ const retryAssess = async (ref, take) => {
734
+ if (submittingRef.current) return;
735
+ submittingRef.current = true;
736
+ beginTakeReport(take);
737
+ try {
738
+ await runAssess(ref, recorder.take, take);
739
+ } finally {
740
+ submittingRef.current = false;
741
+ }
742
+ };
743
+ const handleSubmit = (event) => {
744
+ event.preventDefault();
745
+ if (inactive || busy || recorder.take === null || phase.kind !== "idle") return;
746
+ submitTake(recorder.take);
747
+ };
748
+ const beginTake = () => {
749
+ if (inactive || busy || !recorder.canRecord) return;
750
+ if (recorder.take !== null) fire("recording-discarded", {
751
+ takes: recorder.takesUsed,
752
+ durationMs: recorder.take.durationMs
753
+ });
754
+ setPhase({ kind: "idle" });
755
+ setUploaded(null);
756
+ start();
757
+ recorder.start();
758
+ };
759
+ const contentLang = typeof data.locale === "string" ? data.locale : void 0;
760
+ const contentDir = localeDirectionOf(data.locale);
761
+ const coachingItem = {
762
+ id: data.id,
763
+ title: data.title,
764
+ referenceText: data.referenceText,
765
+ locale: data.locale,
766
+ ...data.instructions !== void 0 ? { instructions: data.instructions } : {},
767
+ ...data.ai !== void 0 ? { ai: data.ai } : {}
768
+ };
769
+ const outcomeReading = useMemo(() => readOutcome(outcome), [outcome]);
770
+ const keptEvidence = useMemo(() => readEvidence(assessment), [assessment]);
771
+ const storedGrade = outcomeReading?.kind === "graded" && outcomeReading.status === "graded" ? outcomeReading.grade : null;
772
+ const storedMarks = useMemo(() => policy.feedback && isReview && keptEvidence === null && storedGrade?.details !== void 0 ? marksFromDetails(storedGrade.details) : null, [
773
+ policy.feedback,
774
+ isReview,
775
+ keptEvidence,
776
+ storedGrade
777
+ ]);
778
+ const feedbackAssessment = !policy.feedback ? null : isReview ? keptEvidence : phase.kind === "graded" ? phase.assessment : null;
779
+ const feedbackGrade = isReview ? storedGrade : phase.kind === "graded" ? phase.grade : null;
780
+ const feedbackAudio = playback.refused ? null : takeAudio;
781
+ const rootRef = useRef(null);
782
+ const [panelLostTheGrade, setPanelLostTheGrade] = useState(false);
783
+ const panelHasGrade = feedbackAssessment !== null && feedbackGrade !== null;
784
+ const gradeShownInPanel = panelHasGrade && !panelLostTheGrade;
785
+ useEffect(() => {
786
+ const root = rootRef.current;
787
+ const lost = panelHasGrade && (root === null || root.querySelector(".lk-pf-grade") === null);
788
+ setPanelLostTheGrade((was) => was === lost ? was : lost);
789
+ });
790
+ const announcement = useMemo(() => {
791
+ if (isReview) return null;
792
+ if (recorder.status === "recording") return {
793
+ text: s.readAloudRecordingStarted,
794
+ feedback: null
795
+ };
796
+ const stopped = recorder.status === "recorded" && take !== null ? {
797
+ text: s.readAloudRecordingStopped(secondsWithin(take.durationMs, maxSeconds, "nearest"), maxSeconds),
798
+ feedback: null
799
+ } : null;
800
+ if (isExam) {
801
+ if (phase.kind === "submitted" || submitted) return {
802
+ text: s.answerSubmitted,
803
+ feedback: null
804
+ };
805
+ return phase.kind === "idle" ? stopped : null;
806
+ }
807
+ switch (phase.kind) {
808
+ case "graded": {
809
+ const grade = phase.grade;
810
+ if (grade === null) return {
811
+ text: `${s.answerSubmitted} ${s.couldNotBeGraded}`,
812
+ feedback: null
813
+ };
814
+ if (!policy.feedback) return {
815
+ text: s.answerSubmitted,
816
+ feedback: null
817
+ };
818
+ return {
819
+ text: `${s.answerSubmitted} ${s.scoreAnnouncement(percentOfGrade(grade), grade.passed)}`,
820
+ feedback: grade.feedback
821
+ };
822
+ }
823
+ case "unscorable": return {
824
+ text: unscorableMessage(phase.code, s),
825
+ feedback: null
826
+ };
827
+ case "unassessed": return {
828
+ text: s.readAloudAssessmentUnavailable,
829
+ feedback: null
830
+ };
831
+ case "idle": return stopped;
832
+ default: return null;
833
+ }
834
+ }, [
835
+ isReview,
836
+ isExam,
837
+ phase,
838
+ submitted,
839
+ s,
840
+ recorder.status,
841
+ take,
842
+ maxSeconds,
843
+ policy.feedback
844
+ ]);
845
+ const alertText = recorder.error !== null ? s.readAloudRecorderError(recorder.error) : phase.kind === "upload-failed" ? s.readAloudUploadFailed : phase.kind === "assess-failed" ? s.readAloudAssessmentFailed : null;
846
+ return /* @__PURE__ */ jsxs("form", {
847
+ className: "lk-ra",
848
+ ref: rootRef,
849
+ "aria-labelledby": VISIBLE_TEXT_RE.test(data.title) ? titleId : void 0,
850
+ lang: locale,
851
+ "data-render-mode": renderMode,
852
+ style: theme,
853
+ onSubmit: handleSubmit,
854
+ children: [
855
+ /* @__PURE__ */ jsx("p", {
856
+ id: titleId,
857
+ className: "lk-ra-title",
858
+ dir: contentDir ?? "auto",
859
+ lang: contentLang,
860
+ children: data.title
861
+ }),
862
+ typeof data.instructions === "string" && data.instructions !== "" ? /* @__PURE__ */ jsx("p", {
863
+ className: "lk-ra-instructions",
864
+ dir: contentDir ?? "auto",
865
+ lang: contentLang,
866
+ children: data.instructions
867
+ }) : null,
868
+ /* @__PURE__ */ jsx("p", {
869
+ className: "lk-ra-reference",
870
+ dir: contentDir ?? "auto",
871
+ lang: contentLang,
872
+ children: data.referenceText
873
+ }),
874
+ data.media !== void 0 || slowMedia !== void 0 ? /* @__PURE__ */ jsxs("div", {
875
+ className: "lk-ra-models",
876
+ ref: modelsRef,
877
+ children: [data.media !== void 0 ? /* @__PURE__ */ jsxs("fieldset", {
878
+ className: "lk-ra-model",
879
+ children: [/* @__PURE__ */ jsx("legend", {
880
+ className: "lk-ra-model-label",
881
+ children: s.readAloudModelRecording
882
+ }), /* @__PURE__ */ jsx(ActivityMedia, {
883
+ media: data.media,
884
+ renderMode,
885
+ playbackGroup: modelsGroup,
886
+ ...mediaBudget !== void 0 ? { mediaBudget } : {},
887
+ ...mediaStrings !== void 0 ? { mediaStrings } : {},
888
+ ...strings !== void 0 ? { strings } : {},
889
+ ...onInteraction !== void 0 ? { onInteraction } : {},
890
+ ...locale !== void 0 ? { locale } : {},
891
+ ...disabled === true || isCapturing ? { disabled: true } : {}
892
+ })]
893
+ }) : null, slowMedia !== void 0 ? /* @__PURE__ */ jsxs("fieldset", {
894
+ className: "lk-ra-model",
895
+ "data-slow": "true",
896
+ children: [/* @__PURE__ */ jsx("legend", {
897
+ className: "lk-ra-model-label",
898
+ children: s.readAloudSlowRecording
899
+ }), /* @__PURE__ */ jsx(ActivityMedia, {
900
+ media: slowMedia,
901
+ renderMode,
902
+ playbackGroup: modelsGroup,
903
+ ...mediaStrings !== void 0 ? { mediaStrings } : {},
904
+ ...strings !== void 0 ? { strings } : {},
905
+ ...locale !== void 0 ? { locale } : {},
906
+ ...disabled === true || isCapturing ? { disabled: true } : {}
907
+ })]
908
+ }) : null]
909
+ }) : null,
910
+ isReview || submitted ? null : /* @__PURE__ */ jsxs("div", {
911
+ className: "lk-ra-recorder",
912
+ "data-status": recorder.status,
913
+ "data-level": recorder.status === "recording" ? Math.min(LEVEL_STEPS, Math.round(recorder.level * LEVEL_STEPS)) : void 0,
914
+ children: [
915
+ recorder.status === "recording" ? /* @__PURE__ */ jsx("button", {
916
+ type: "button",
917
+ className: "lk-ra-stop",
918
+ disabled: inactive,
919
+ onClick: () => recorder.stop(),
920
+ children: s.readAloudStop
921
+ }) : /* @__PURE__ */ jsx("button", {
922
+ type: "button",
923
+ className: "lk-ra-record",
924
+ disabled: inactive,
925
+ "aria-disabled": !recorder.canRecord || busy || void 0,
926
+ onClick: beginTake,
927
+ children: recorder.take === null ? s.readAloudRecord : s.readAloudRerecord
928
+ }),
929
+ recorder.status === "recording" ? /* @__PURE__ */ jsx("p", {
930
+ className: "lk-ra-progress",
931
+ "aria-hidden": "true",
932
+ children: s.readAloudRecordingProgress(secondsWithin(recorder.elapsedMs, maxSeconds, "down"), maxSeconds)
933
+ }) : null,
934
+ takesLeft !== void 0 && maxTakes !== void 0 ? /* @__PURE__ */ jsx("p", {
935
+ className: "lk-ra-takes",
936
+ children: s.readAloudTakesRemaining(takesLeft, maxTakes)
937
+ }) : null
938
+ ]
939
+ }),
940
+ takeAudio === null ? null : playback.refused ? /* @__PURE__ */ jsx("p", {
941
+ className: "lk-ra-take-unavailable",
942
+ role: "note",
943
+ children: s.readAloudPlaybackUnavailable
944
+ }) : /* @__PURE__ */ jsx("audio", {
945
+ className: "lk-ra-take",
946
+ controls: true,
947
+ src: takeAudio,
948
+ "aria-label": s.readAloudYourRecording,
949
+ onError: (event) => playback.refuse(event.currentTarget),
950
+ ...isReview ? {} : { "data-take": recorder.takesUsed }
951
+ }),
952
+ isReview || submitted ? null : /* @__PURE__ */ jsxs("div", {
953
+ className: "lk-ra-actions",
954
+ children: [
955
+ /* @__PURE__ */ jsx("button", {
956
+ ref: submitRef,
957
+ type: "submit",
958
+ className: "lk-ra-submit",
959
+ disabled: disabled === true,
960
+ "aria-disabled": recorder.take === null || phase.kind !== "idle" || void 0,
961
+ "aria-busy": busy || void 0,
962
+ children: s.submit
963
+ }),
964
+ isExam ? /* @__PURE__ */ jsx("button", {
965
+ type: "button",
966
+ className: "lk-ra-submit-blank",
967
+ disabled: inactive,
968
+ "aria-disabled": busy || void 0,
969
+ onClick: () => {
970
+ if (!busy) submitTake(null);
971
+ },
972
+ children: s.readAloudSubmitWithoutRecording
973
+ }) : null,
974
+ offersRetry(phase, uploaded) ? /* @__PURE__ */ jsx("button", {
975
+ type: "button",
976
+ className: "lk-ra-retry",
977
+ onClick: () => {
978
+ submitRef.current?.focus();
979
+ if (phase.kind === "upload-failed") submitTake(recorder.take);
980
+ else if (phase.kind === "assess-failed" && uploaded !== null) retryAssess(uploaded, phase.take);
981
+ },
982
+ children: s.readAloudTryAgain
983
+ }) : null
984
+ ]
985
+ }),
986
+ busy ? /* @__PURE__ */ jsx("p", {
987
+ className: "lk-ra-pending",
988
+ role: "status",
989
+ "aria-busy": "true",
990
+ children: phase.kind === "uploading" ? s.readAloudUploading : s.readAloudAssessing
991
+ }) : null,
992
+ alertText !== null ? /* @__PURE__ */ jsx("p", {
993
+ className: "lk-ra-alert",
994
+ role: "alert",
995
+ children: alertText
996
+ }, errorNonce) : null,
997
+ feedbackAssessment !== null ? /* @__PURE__ */ jsx(PronunciationFeedback, {
998
+ data: coachingItem,
999
+ assessment: feedbackAssessment,
1000
+ ...feedbackGrade !== null ? { grade: feedbackGrade } : {},
1001
+ ...feedbackAudio !== null ? { audioUrl: feedbackAudio } : {},
1002
+ ...breakThreshold !== void 0 ? { breakThreshold } : {},
1003
+ ...monotoneThreshold !== void 0 ? { monotoneThreshold } : {},
1004
+ ...locale !== void 0 ? { locale } : {},
1005
+ ...strings !== void 0 ? { strings } : {},
1006
+ ...ai !== void 0 ? { ai } : {},
1007
+ ...onInteraction !== void 0 ? { onInteraction } : {},
1008
+ renderMode,
1009
+ delivery: policy
1010
+ }) : null,
1011
+ storedMarks !== null ? /* @__PURE__ */ jsxs("div", {
1012
+ className: "lk-ra-marks",
1013
+ children: [
1014
+ /* @__PURE__ */ jsx("ol", {
1015
+ className: "lk-ra-words",
1016
+ "aria-label": s.pronunciationWordsLabel,
1017
+ dir: contentDir,
1018
+ children: storedMarks.map((entry, index) => {
1019
+ const sentence = markSentence(entry, s);
1020
+ return /* @__PURE__ */ jsxs("li", {
1021
+ className: "lk-ra-word",
1022
+ "data-state": entry.state,
1023
+ children: [/* @__PURE__ */ jsx("span", {
1024
+ className: "lk-visually-hidden",
1025
+ style: VISUALLY_HIDDEN,
1026
+ children: sentence
1027
+ }), /* @__PURE__ */ jsx("span", {
1028
+ className: "lk-ra-word-text",
1029
+ "aria-hidden": "true",
1030
+ dir: contentDir ?? "auto",
1031
+ lang: contentLang,
1032
+ children: entry.reference
1033
+ })]
1034
+ }, index);
1035
+ })
1036
+ }),
1037
+ /* @__PURE__ */ jsx("ul", {
1038
+ className: "lk-ra-legend",
1039
+ children: STORED_STATES.map((state) => /* @__PURE__ */ jsx("li", {
1040
+ "data-state": state,
1041
+ children: s.pronunciationLegend(state)
1042
+ }, state))
1043
+ }),
1044
+ /* @__PURE__ */ jsx(ReadingCoaching, {
1045
+ data: coachingItem,
1046
+ grade: storedGrade,
1047
+ renderMode,
1048
+ delivery: policy,
1049
+ ...ai !== void 0 ? { ai } : {},
1050
+ ...locale !== void 0 ? { locale } : {},
1051
+ ...onInteraction !== void 0 ? { onInteraction } : {},
1052
+ strings: s,
1053
+ contentLang,
1054
+ contentDir
1055
+ })
1056
+ ]
1057
+ }) : null,
1058
+ /* @__PURE__ */ jsx(FeedbackRegion, {
1059
+ id: `${data.id}-feedback`,
1060
+ children: isReview ? outcomeReading !== void 0 && (policy.feedback || outcomeReading.kind !== "graded") ? /* @__PURE__ */ jsx(OutcomeSummary, {
1061
+ reading: outcomeReading,
1062
+ s,
1063
+ scoreShown: gradeShownInPanel
1064
+ }) : null : gradeShownInPanel ? /* @__PURE__ */ jsx("span", {
1065
+ className: "lk-visually-hidden",
1066
+ style: VISUALLY_HIDDEN,
1067
+ children: /* @__PURE__ */ jsx(AnnouncementText, { announcement })
1068
+ }) : /* @__PURE__ */ jsx(AnnouncementText, { announcement })
1069
+ })
1070
+ ]
1071
+ });
1072
+ }
1073
+ //#endregion
1074
+ //#region src/components/ReadAloud/index.tsx
1075
+ /**
1076
+ * Public Read Aloud activity: the core wrapped in `ActivityErrorBoundary` so a
1077
+ * render failure (including the dev schema-validation throw and the
1078
+ * missing-`upload` guard) degrades to an accessible fallback instead of
1079
+ * crashing the host.
1080
+ */
1081
+ function ReadAloud(props) {
1082
+ const strings = useLkStrings(props.strings);
1083
+ return /* @__PURE__ */ jsx(ActivityErrorBoundary, {
1084
+ activityTitle: props.data?.title,
1085
+ strings,
1086
+ children: /* @__PURE__ */ jsx(ReadAloud$1, { ...props })
1087
+ });
1088
+ }
1089
+ //#endregion
1090
+ export { stopCaptureGroup as n, ReadAloud as t };
1091
+
1092
+ //# sourceMappingURL=ReadAloud-C8RhaUqV.js.map