@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,709 @@
1
+ 'use client';
2
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
3
+ //#region src/hooks/speech-recorder/machine.ts
4
+ /** The state a recorder mounts in, on a server as on a client. */
5
+ const IDLE_RECORDER = {
6
+ status: "idle",
7
+ error: null,
8
+ level: 0,
9
+ elapsedMs: 0,
10
+ take: null,
11
+ takesUsed: 0
12
+ };
13
+ /**
14
+ * Applies one event. Returns the state it was given, unchanged and identical,
15
+ * when the event moves nothing — so a block arriving after teardown costs no
16
+ * render.
17
+ */
18
+ function reduceRecorder(state, event) {
19
+ switch (event.kind) {
20
+ case "requested": return {
21
+ ...state,
22
+ status: "requesting-permission",
23
+ error: null,
24
+ level: 0,
25
+ elapsedMs: 0,
26
+ take: null
27
+ };
28
+ case "started": return state.status === "requesting-permission" ? {
29
+ ...state,
30
+ status: "recording"
31
+ } : state;
32
+ case "progress": return state.status === "recording" ? {
33
+ ...state,
34
+ level: event.level,
35
+ elapsedMs: event.elapsedMs
36
+ } : state;
37
+ case "captured": return {
38
+ ...state,
39
+ status: "recorded",
40
+ error: null,
41
+ level: 0,
42
+ elapsedMs: event.take.durationMs,
43
+ take: event.take,
44
+ takesUsed: state.takesUsed + 1
45
+ };
46
+ case "failed": return {
47
+ ...state,
48
+ status: "error",
49
+ error: event.error,
50
+ level: 0,
51
+ take: null
52
+ };
53
+ case "discarded": return {
54
+ ...state,
55
+ status: "idle",
56
+ error: null,
57
+ level: 0,
58
+ elapsedMs: 0,
59
+ take: null
60
+ };
61
+ case "reset": return state === IDLE_RECORDER ? state : IDLE_RECORDER;
62
+ }
63
+ }
64
+ //#endregion
65
+ //#region src/hooks/speech-recorder/resample.ts
66
+ /**
67
+ * Turning the blocks a capture node delivers into one mono 16 kHz signal.
68
+ *
69
+ * 16 kHz mono is the rate every mainstream assessor accepts and the rate
70
+ * `inspectWav` measures exactly, while a microphone is opened at whatever rate
71
+ * the device runs at — 44.1 or 48 kHz on most, already 16 kHz on some. The
72
+ * three steps are separated and pure so the arithmetic that decides what a
73
+ * server will measure can be tested without a browser.
74
+ */
75
+ /** The capture rate every mainstream assessor accepts. */
76
+ const TARGET_SAMPLE_RATE = 16e3;
77
+ /**
78
+ * Where the anti-alias filter stops passing, as a fraction of the target rate.
79
+ *
80
+ * Below 0.5 on purpose: a filter cutting at exactly the target's Nyquist is
81
+ * only 6 dB down there and its transition band folds straight back into the
82
+ * signal. Cutting at 7.2 kHz leaves the transition band room to finish before
83
+ * 8 kHz, and speech carries next to nothing in the 800 Hz that costs.
84
+ */
85
+ const CUTOFF_FRACTION = .45;
86
+ /**
87
+ * Half the kernel width, counted in target-rate sample periods. Sixteen
88
+ * periods of taps is enough Hann-windowed sinc to put a tone an octave above
89
+ * the cutoff far below anything a meter or an assessor would notice, and short
90
+ * enough that filtering a whole take costs a fraction of a second.
91
+ */
92
+ const KERNEL_HALF_PERIODS = 8;
93
+ /**
94
+ * Averages the channels of one capture block into mono.
95
+ *
96
+ * Always allocates, never returns what it was given: a `ScriptProcessorNode`
97
+ * hands out the same buffer on every callback and overwrites it in place, so a
98
+ * mono capture that passed the input straight through would find every block
99
+ * it had kept replaced by the last one.
100
+ *
101
+ * Channels of unequal length are read to the shortest. Nothing should produce
102
+ * them, and the alternative to a bound is reading past the end of one and
103
+ * poisoning the whole take with NaN.
104
+ */
105
+ function downmix(channels) {
106
+ if (channels.length === 0) return /* @__PURE__ */ new Float32Array(0);
107
+ let frames = channels[0].length;
108
+ for (const channel of channels) if (channel.length < frames) frames = channel.length;
109
+ const mono = new Float32Array(frames);
110
+ for (const channel of channels) for (let frame = 0; frame < frames; frame += 1) mono[frame] += channel[frame];
111
+ for (let frame = 0; frame < frames; frame += 1) mono[frame] /= channels.length;
112
+ return mono;
113
+ }
114
+ /** Concatenates captured blocks into the single signal a take is encoded from. */
115
+ function joinBlocks(blocks, frames) {
116
+ const joined = new Float32Array(frames);
117
+ let at = 0;
118
+ for (const block of blocks) {
119
+ joined.set(block, at);
120
+ at += block.length;
121
+ }
122
+ return joined;
123
+ }
124
+ /**
125
+ * A windowed-sinc low-pass, applied at the input rate before any samples are
126
+ * dropped.
127
+ *
128
+ * Without it, resampling is aliasing: every component above the target's
129
+ * Nyquist folds back down into the speech band as a tone that was never
130
+ * spoken, and an assessor scores what it hears. The window is Hann and the
131
+ * kernel is normalised to unit gain, so a signal already inside the band comes
132
+ * through at its own amplitude.
133
+ *
134
+ * Edges are extended by repeating the first and last sample rather than by
135
+ * zero-padding, which would put a step — and so a click — at each end of every
136
+ * take.
137
+ */
138
+ function lowPass(samples, inputRate) {
139
+ const cutoff = TARGET_SAMPLE_RATE * CUTOFF_FRACTION / inputRate;
140
+ const half = Math.max(1, Math.round(KERNEL_HALF_PERIODS * inputRate / TARGET_SAMPLE_RATE));
141
+ const taps = new Float32Array(half * 2 + 1);
142
+ let gain = 0;
143
+ for (let tap = 0; tap < taps.length; tap += 1) {
144
+ const at = tap - half;
145
+ const angle = 2 * Math.PI * cutoff * at;
146
+ const sinc = at === 0 ? 2 * cutoff : Math.sin(angle) / (Math.PI * at);
147
+ const window = .5 - .5 * Math.cos(2 * Math.PI * tap / (taps.length - 1));
148
+ taps[tap] = sinc * window;
149
+ gain += taps[tap];
150
+ }
151
+ for (let tap = 0; tap < taps.length; tap += 1) taps[tap] /= gain;
152
+ const last = samples.length - 1;
153
+ const filtered = new Float32Array(samples.length);
154
+ for (let index = 0; index < samples.length; index += 1) {
155
+ let total = 0;
156
+ for (let tap = 0; tap < taps.length; tap += 1) {
157
+ const at = index + tap - half;
158
+ total += taps[tap] * samples[at < 0 ? 0 : at > last ? last : at];
159
+ }
160
+ filtered[index] = total;
161
+ }
162
+ return filtered;
163
+ }
164
+ /**
165
+ * Resamples mono samples from `inputRate` to {@link TARGET_SAMPLE_RATE}.
166
+ *
167
+ * Every path returns an array the caller owns, the equal-rate one included, so
168
+ * no caller has to know whether it was handed its own buffer back.
169
+ *
170
+ * The filter runs only when the rate is coming down. Interpolating upwards
171
+ * invents no frequencies above the input's own Nyquist, so there is nothing
172
+ * for an anti-alias filter to remove and applying one would only dull a
173
+ * recording a 16 kHz device already captured cleanly.
174
+ */
175
+ function resampleToTarget(samples, inputRate) {
176
+ if (samples.length === 0) return /* @__PURE__ */ new Float32Array(0);
177
+ if (inputRate === 16e3) return samples.slice();
178
+ const source = inputRate > 16e3 ? lowPass(samples, inputRate) : samples;
179
+ const length = Math.round(samples.length * TARGET_SAMPLE_RATE / inputRate);
180
+ const resampled = new Float32Array(length);
181
+ const step = inputRate / TARGET_SAMPLE_RATE;
182
+ const last = source.length - 1;
183
+ for (let index = 0; index < length; index += 1) {
184
+ const at = index * step;
185
+ const left = Math.min(Math.floor(at), last);
186
+ const right = Math.min(left + 1, last);
187
+ const fraction = at - left;
188
+ resampled[index] = source[left] * (1 - fraction) + source[right] * fraction;
189
+ }
190
+ return resampled;
191
+ }
192
+ //#endregion
193
+ //#region src/hooks/speech-recorder/wav.ts
194
+ /**
195
+ * 16-bit PCM WAV, and the two level measurements taken over the same samples.
196
+ *
197
+ * This is the one format the SDK's `inspectWav` reads, so these bytes are the
198
+ * bytes a server measures a take with — anything else would need a decoder on
199
+ * the application's side before a grade could exist. Nothing here touches Web
200
+ * Audio, which is why the encoder can be unit tested against `inspectWav`
201
+ * itself rather than against a browser.
202
+ */
203
+ /** The media type of what {@link encodeWav16} returns. */
204
+ const WAV_MIME_TYPE = "audio/wav";
205
+ /** RIFF + a 16-byte `fmt ` + `data`, every field fixed-size. */
206
+ const HEADER_BYTES = 44;
207
+ /** Writes four ASCII characters, which is all a RIFF tag ever is. */
208
+ function writeTag(view, offset, tag) {
209
+ for (let index = 0; index < tag.length; index += 1) view.setUint8(offset + index, tag.charCodeAt(index));
210
+ }
211
+ /**
212
+ * Encodes mono samples as a 16-bit PCM WAV.
213
+ *
214
+ * The two scale factors are not a typo. Signed 16-bit runs from -32768 to
215
+ * 32767, so a single factor cannot reach full scale in both directions: 0x7fff
216
+ * throughout leaves a full-scale negative peak reading -0.99997, and 0x8000
217
+ * throughout overflows into the positive at +1. Each sign gets the factor that
218
+ * lands exactly on its own limit.
219
+ *
220
+ * The buffer is named in the return type because a `Blob` refuses a view that
221
+ * might be backed by a `SharedArrayBuffer`, and these bytes go straight into
222
+ * one.
223
+ */
224
+ function encodeWav16(samples, sampleRate) {
225
+ const dataBytes = samples.length * 2;
226
+ const bytes = new Uint8Array(HEADER_BYTES + dataBytes);
227
+ const view = new DataView(bytes.buffer);
228
+ writeTag(view, 0, "RIFF");
229
+ view.setUint32(4, 36 + dataBytes, true);
230
+ writeTag(view, 8, "WAVE");
231
+ writeTag(view, 12, "fmt ");
232
+ view.setUint32(16, 16, true);
233
+ view.setUint16(20, 1, true);
234
+ view.setUint16(22, 1, true);
235
+ view.setUint32(24, sampleRate, true);
236
+ view.setUint32(28, sampleRate * 2, true);
237
+ view.setUint16(32, 2, true);
238
+ view.setUint16(34, 16, true);
239
+ writeTag(view, 36, "data");
240
+ view.setUint32(40, dataBytes, true);
241
+ for (let index = 0; index < samples.length; index += 1) {
242
+ const sample = samples[index];
243
+ const clamped = sample < -1 ? -1 : sample > 1 ? 1 : sample;
244
+ view.setInt16(HEADER_BYTES + index * 2, Math.round(clamped * (clamped < 0 ? 32768 : 32767)), true);
245
+ }
246
+ return bytes;
247
+ }
248
+ /** The loudest sample, 0..1 — what a take reports as its `peakLevel`. */
249
+ function peakOf(samples) {
250
+ let peak = 0;
251
+ for (let index = 0; index < samples.length; index += 1) {
252
+ const magnitude = Math.abs(samples[index]);
253
+ if (magnitude > peak) peak = magnitude;
254
+ }
255
+ return peak > 1 ? 1 : peak;
256
+ }
257
+ /**
258
+ * Root mean square, 0..1 — what the recorder reports as its `level`.
259
+ *
260
+ * RMS rather than the peak, because a meter driven by peaks sits near full
261
+ * scale on any speech at all and tells a learner nothing about whether they
262
+ * are being heard. It is also the measure `inspectWav` decides voiced time
263
+ * with, so what the meter shows and what the server later counts as speech are
264
+ * the same quantity.
265
+ */
266
+ function rmsOf(samples) {
267
+ if (samples.length === 0) return 0;
268
+ let total = 0;
269
+ for (let index = 0; index < samples.length; index += 1) total += samples[index] * samples[index];
270
+ const rms = Math.sqrt(total / samples.length);
271
+ return rms > 1 ? 1 : rms;
272
+ }
273
+ //#endregion
274
+ //#region src/hooks/speech-recorder/worklet.ts
275
+ /**
276
+ * The audio worklet processor, and the module URL it is loaded through.
277
+ *
278
+ * The processor must run inside `AudioWorkletGlobalScope`, which can only be
279
+ * reached by URL. Source text turned into a Blob URL is the only form of that
280
+ * which needs nothing of the consuming application: `import.meta` is not
281
+ * available in every target this package is bundled for — the CommonJS build
282
+ * among them — and a separate asset file would have to be copied into the
283
+ * public directory of every application that installs the SDK.
284
+ *
285
+ * An application whose Content-Security-Policy refuses that URL self-hosts the
286
+ * same source and passes its URL as `workletUrl` instead. The source is public
287
+ * for exactly that reason — see {@link CAPTURE_PROCESSOR_SOURCE}.
288
+ */
289
+ /** The name the processor registers itself under, and is constructed by. */
290
+ const CAPTURE_PROCESSOR = "lk-speech-capture";
291
+ /**
292
+ * The audio worklet module `useSpeechRecorder` captures through, as source
293
+ * text — exported so an application with a strict Content-Security-Policy can
294
+ * serve it itself.
295
+ *
296
+ * By default the hook loads this text from a `blob:` URL. A worklet module is
297
+ * loaded as a script, so it is `script-src` that must allow `blob:` —
298
+ * `worker-src` does not govern it. Where that is refused, write this string to
299
+ * a file your policy's `script-src` does allow, at build time and from the
300
+ * installed package so it moves with the version you run, and pass that file's
301
+ * URL as `workletUrl`:
302
+ *
303
+ * ```ts
304
+ * import { writeFileSync } from 'node:fs';
305
+ * import { CAPTURE_PROCESSOR_SOURCE } from '@intellectif/lk-react/hooks/useSpeechRecorder';
306
+ *
307
+ * writeFileSync('public/lk-speech-capture.js', CAPTURE_PROCESSOR_SOURCE);
308
+ * ```
309
+ *
310
+ * **The contract** a module at `workletUrl` must meet, which this source does:
311
+ * register an `AudioWorkletProcessor` under the name `lk-speech-capture`, and
312
+ * from its `process(inputs)` post the first input's channels to its `port` as
313
+ * an array with one `Float32Array` per channel, **copied** — the render
314
+ * quantum's buffers belong to the audio thread and are overwritten on the next
315
+ * quantum, so a block posted without copying arrives as whatever the
316
+ * microphone heard some milliseconds later. Return `true`, which keeps the
317
+ * processor alive while its source is silent. Anything else changes what is
318
+ * recorded, so serve this text unchanged rather than a copy of your own.
319
+ */
320
+ const CAPTURE_PROCESSOR_SOURCE = `class LkSpeechCapture extends AudioWorkletProcessor {
321
+ process(inputs) {
322
+ const input = inputs[0];
323
+ if (input === undefined || input.length === 0) {
324
+ return true;
325
+ }
326
+ const block = [];
327
+ for (let channel = 0; channel < input.length; channel += 1) {
328
+ block.push(input[channel].slice());
329
+ }
330
+ this.port.postMessage(block);
331
+ return true;
332
+ }
333
+ }
334
+ registerProcessor(${JSON.stringify(CAPTURE_PROCESSOR)}, LkSpeechCapture);
335
+ `;
336
+ /**
337
+ * Wraps {@link CAPTURE_PROCESSOR_SOURCE} in an object URL.
338
+ *
339
+ * Returns `null` where object URLs are unavailable — a server render, a test
340
+ * environment, a hardened embedder — rather than throwing, because the caller
341
+ * has a working fallback for exactly that case and a missing URL factory is
342
+ * not a failed recording.
343
+ *
344
+ * Called from `start()` and never at module scope: `URL.createObjectURL` is a
345
+ * browser global, and reading one while a module is evaluated is what breaks a
346
+ * server render.
347
+ */
348
+ function createProcessorUrl() {
349
+ const urls = globalThis.URL;
350
+ if (urls === void 0) return null;
351
+ const create = urls.createObjectURL;
352
+ const revoke = urls.revokeObjectURL;
353
+ if (create === void 0 || revoke === void 0) return null;
354
+ const url = create.call(urls, new Blob([CAPTURE_PROCESSOR_SOURCE], { type: "text/javascript" }));
355
+ return {
356
+ url,
357
+ release: () => {
358
+ revoke.call(urls, url);
359
+ }
360
+ };
361
+ }
362
+ //#endregion
363
+ //#region src/hooks/useSpeechRecorder.ts
364
+ /**
365
+ * A `ScriptProcessorNode`'s buffer, in frames.
366
+ *
367
+ * This fallback runs its callback on the main thread, so the buffer has to
368
+ * hold whatever a render blocks it for; 4096 frames is about 85 ms at 48 kHz,
369
+ * and a smaller one drops samples out of the middle of a take whenever the
370
+ * page is busy. The worklet path, which runs on the audio thread, needs none
371
+ * of this.
372
+ */
373
+ const PROCESSOR_BUFFER_FRAMES = 4096;
374
+ /** How often the meter and the elapsed time may move. */
375
+ const LEVEL_UPDATES_PER_SECOND = 10;
376
+ function stopTracks(stream) {
377
+ for (const track of stream.getTracks()) track.stop();
378
+ }
379
+ function closeQuietly(context) {
380
+ context.close().catch(() => {});
381
+ }
382
+ /**
383
+ * Which refusal `getUserMedia` made.
384
+ *
385
+ * Read from `name`, not from the message: messages are localised and are
386
+ * rewritten between browser versions, while the names are specified. Anything
387
+ * unrecognised is `failed` rather than a guess — telling a learner their
388
+ * permission was denied when their device was busy sends them to the wrong
389
+ * setting.
390
+ */
391
+ function microphoneError(error) {
392
+ switch (error?.name) {
393
+ case "NotAllowedError":
394
+ case "SecurityError": return "permission-denied";
395
+ case "NotFoundError": return "no-device";
396
+ case "NotSupportedError": return "unsupported";
397
+ default: return "failed";
398
+ }
399
+ }
400
+ /**
401
+ * The worklet capture node, or `null` when this engine cannot build one.
402
+ *
403
+ * `null` rather than a throw for every one of its reasons — no
404
+ * `AudioWorkletNode`, no worklet thread, an object URL the page's
405
+ * Content-Security-Policy refuses, a module that fails to load — because none
406
+ * of them is a failed recording: the caller has a working fallback for all of
407
+ * them, and only the absence of both is an unsupported browser.
408
+ */
409
+ async function createWorkletNode(context, workletUrl, onBlock) {
410
+ const AudioWorkletNodeConstructor = globalThis.AudioWorkletNode;
411
+ const worklet = context.audioWorklet;
412
+ if (AudioWorkletNodeConstructor === void 0 || worklet === void 0) return null;
413
+ const minted = workletUrl === void 0 ? createProcessorUrl() : null;
414
+ const url = workletUrl ?? minted?.url;
415
+ if (url === void 0) return null;
416
+ try {
417
+ await worklet.addModule(url);
418
+ const node = new AudioWorkletNodeConstructor(context, CAPTURE_PROCESSOR);
419
+ node.port.onmessage = (event) => {
420
+ onBlock(event.data);
421
+ };
422
+ return {
423
+ node,
424
+ detach: () => {
425
+ node.port.onmessage = null;
426
+ }
427
+ };
428
+ } catch {
429
+ return null;
430
+ } finally {
431
+ minted?.release();
432
+ }
433
+ }
434
+ /** The `ScriptProcessorNode` fallback, or `null` on an engine without one. */
435
+ function createProcessorNode(context, onBlock) {
436
+ const create = context.createScriptProcessor;
437
+ if (create === void 0) return null;
438
+ const node = create.call(context, PROCESSOR_BUFFER_FRAMES, 1, 1);
439
+ node.onaudioprocess = (event) => {
440
+ const buffer = event.inputBuffer;
441
+ const channels = [];
442
+ for (let channel = 0; channel < buffer.numberOfChannels; channel += 1) channels.push(buffer.getChannelData(channel));
443
+ onBlock(channels);
444
+ };
445
+ return {
446
+ node,
447
+ detach: () => {
448
+ node.onaudioprocess = null;
449
+ }
450
+ };
451
+ }
452
+ /**
453
+ * Records a take of speech as mono 16-bit PCM WAV at 16 kHz.
454
+ *
455
+ * That is the one format the SDK measures directly: `inspectWav` reads it
456
+ * without a decoding library, and it is what every mainstream pronunciation
457
+ * assessor accepts. A take is captured at whatever rate the device runs at,
458
+ * downmixed to mono, low-pass filtered and resampled, then encoded — so what a
459
+ * server measures is what this hook produced, not a claim about it.
460
+ *
461
+ * **Server rendering.** `status`, `error` and `canRecord` are derived from the
462
+ * take count alone and never from a browser global, so the markup a server
463
+ * produces is the markup the browser's first render produces. Support is
464
+ * discovered inside {@link SpeechRecorder.start} and reported as
465
+ * `error: 'unsupported'`; the obvious `!!navigator.mediaDevices` guard at
466
+ * render time would make a server render and its hydration disagree.
467
+ *
468
+ * **Identity.** `start`, `stop`, `discard` and `reset` keep one identity for the
469
+ * life of the hook, so they are safe in a dependency array. The returned object keeps
470
+ * one identity for as long as none of its values changes, so a render caused
471
+ * by something else does not invalidate an effect that depends on it.
472
+ *
473
+ * `start()` does nothing when no take remains, so `canRecord` is presentation:
474
+ * mark the control `aria-disabled` rather than `disabled`, which would take it
475
+ * out of the tab order and leave a learner nothing to read.
476
+ *
477
+ * ```tsx
478
+ * const recorder = useSpeechRecorder({ maxDurationMs: 20_000, maxTakes: 3 });
479
+ * <button
480
+ * type="button"
481
+ * aria-disabled={!recorder.canRecord}
482
+ * onClick={() => {
483
+ * void recorder.start();
484
+ * }}
485
+ * >
486
+ * Record
487
+ * </button>
488
+ * ```
489
+ */
490
+ function useSpeechRecorder(options) {
491
+ const optionsRef = useRef(options);
492
+ optionsRef.current = options;
493
+ const [state, setState] = useState(IDLE_RECORDER);
494
+ const stateRef = useRef(IDLE_RECORDER);
495
+ const sessionRef = useRef(null);
496
+ const startingRef = useRef(false);
497
+ const attemptRef = useRef(0);
498
+ const dispatch = useCallback((event) => {
499
+ const next = reduceRecorder(stateRef.current, event);
500
+ if (next === stateRef.current) return;
501
+ stateRef.current = next;
502
+ setState(next);
503
+ }, []);
504
+ const endSession = useCallback(() => {
505
+ const session = sessionRef.current;
506
+ sessionRef.current = null;
507
+ session?.release();
508
+ }, []);
509
+ const finish = useCallback(() => {
510
+ const session = sessionRef.current;
511
+ if (session === null) return;
512
+ sessionRef.current = null;
513
+ session.release();
514
+ const { minDurationMs } = optionsRef.current;
515
+ const samples = resampleToTarget(joinBlocks(session.blocks, session.frames), session.rate);
516
+ const durationMs = samples.length / TARGET_SAMPLE_RATE * 1e3;
517
+ if (minDurationMs !== void 0 && durationMs < minDurationMs) {
518
+ dispatch({
519
+ kind: "failed",
520
+ error: "too-short"
521
+ });
522
+ return;
523
+ }
524
+ dispatch({
525
+ kind: "captured",
526
+ take: {
527
+ blob: new Blob([encodeWav16(samples, TARGET_SAMPLE_RATE)], { type: WAV_MIME_TYPE }),
528
+ mimeType: WAV_MIME_TYPE,
529
+ durationMs,
530
+ peakLevel: peakOf(samples)
531
+ }
532
+ });
533
+ }, [dispatch]);
534
+ const handleBlock = useCallback((channels) => {
535
+ const session = sessionRef.current;
536
+ if (session === null) return;
537
+ let block = downmix(channels);
538
+ const room = session.maxFrames - session.frames;
539
+ if (block.length > room) block = block.subarray(0, room);
540
+ session.blocks.push(block);
541
+ session.frames += block.length;
542
+ const level = rmsOf(block);
543
+ if (level > session.peak) session.peak = level;
544
+ if (session.frames >= session.maxFrames) {
545
+ finish();
546
+ return;
547
+ }
548
+ if (session.frames - session.reportedAt < session.rate / LEVEL_UPDATES_PER_SECOND) return;
549
+ session.reportedAt = session.frames;
550
+ const peak = session.peak;
551
+ session.peak = 0;
552
+ dispatch({
553
+ kind: "progress",
554
+ level: peak,
555
+ elapsedMs: session.frames / session.rate * 1e3
556
+ });
557
+ }, [dispatch, finish]);
558
+ const start = useCallback(async () => {
559
+ const { maxDurationMs, maxTakes, workletUrl } = optionsRef.current;
560
+ if (startingRef.current || sessionRef.current !== null) return;
561
+ if (maxTakes !== void 0 && stateRef.current.takesUsed >= maxTakes) return;
562
+ const attempt = attemptRef.current;
563
+ startingRef.current = true;
564
+ dispatch({ kind: "requested" });
565
+ let release = () => {};
566
+ try {
567
+ const view = globalThis;
568
+ const devices = view.navigator?.mediaDevices;
569
+ const getUserMedia = devices?.getUserMedia;
570
+ const AudioContextConstructor = view.AudioContext;
571
+ if (devices === void 0 || getUserMedia === void 0 || AudioContextConstructor === void 0) {
572
+ dispatch({
573
+ kind: "failed",
574
+ error: "unsupported"
575
+ });
576
+ return;
577
+ }
578
+ let stream;
579
+ try {
580
+ stream = await getUserMedia.call(devices, { audio: true });
581
+ } catch (error) {
582
+ dispatch({
583
+ kind: "failed",
584
+ error: microphoneError(error)
585
+ });
586
+ return;
587
+ }
588
+ release = () => {
589
+ stopTracks(stream);
590
+ };
591
+ if (attempt !== attemptRef.current) {
592
+ release();
593
+ dispatch({ kind: "discarded" });
594
+ return;
595
+ }
596
+ let context;
597
+ try {
598
+ context = new AudioContextConstructor();
599
+ } catch {
600
+ release();
601
+ dispatch({
602
+ kind: "failed",
603
+ error: "unsupported"
604
+ });
605
+ return;
606
+ }
607
+ const open = context;
608
+ release = () => {
609
+ stopTracks(stream);
610
+ closeQuietly(open);
611
+ };
612
+ const attached = await createWorkletNode(open, workletUrl, handleBlock) ?? createProcessorNode(open, handleBlock);
613
+ if (attached === null) {
614
+ release();
615
+ dispatch({
616
+ kind: "failed",
617
+ error: "unsupported"
618
+ });
619
+ return;
620
+ }
621
+ const source = open.createMediaStreamSource(stream);
622
+ const sink = open.createGain();
623
+ sink.gain.value = 0;
624
+ source.connect(attached.node);
625
+ attached.node.connect(sink);
626
+ sink.connect(open.destination);
627
+ release = () => {
628
+ attached.detach();
629
+ attached.node.disconnect();
630
+ source.disconnect();
631
+ sink.disconnect();
632
+ stopTracks(stream);
633
+ closeQuietly(open);
634
+ };
635
+ if (attempt !== attemptRef.current) {
636
+ release();
637
+ dispatch({ kind: "discarded" });
638
+ return;
639
+ }
640
+ sessionRef.current = {
641
+ blocks: [],
642
+ frames: 0,
643
+ rate: open.sampleRate,
644
+ maxFrames: Math.max(1, Math.floor(maxDurationMs / 1e3 * open.sampleRate)),
645
+ peak: 0,
646
+ reportedAt: 0,
647
+ release
648
+ };
649
+ dispatch({ kind: "started" });
650
+ } catch {
651
+ release();
652
+ sessionRef.current = null;
653
+ dispatch({
654
+ kind: "failed",
655
+ error: "failed"
656
+ });
657
+ } finally {
658
+ startingRef.current = false;
659
+ }
660
+ }, [dispatch, handleBlock]);
661
+ const stop = useCallback(() => {
662
+ if (sessionRef.current === null) {
663
+ attemptRef.current += 1;
664
+ return;
665
+ }
666
+ finish();
667
+ }, [finish]);
668
+ const discard = useCallback(() => {
669
+ attemptRef.current += 1;
670
+ endSession();
671
+ dispatch({ kind: "discarded" });
672
+ }, [dispatch, endSession]);
673
+ const reset = useCallback(() => {
674
+ attemptRef.current += 1;
675
+ endSession();
676
+ dispatch({ kind: "reset" });
677
+ }, [dispatch, endSession]);
678
+ useEffect(() => {
679
+ return () => {
680
+ attemptRef.current += 1;
681
+ endSession();
682
+ };
683
+ }, [endSession]);
684
+ const { maxTakes } = options;
685
+ return useMemo(() => ({
686
+ status: state.status,
687
+ error: state.error,
688
+ level: state.level,
689
+ elapsedMs: state.elapsedMs,
690
+ take: state.take,
691
+ takesUsed: state.takesUsed,
692
+ canRecord: maxTakes === void 0 || state.takesUsed < maxTakes,
693
+ start,
694
+ stop,
695
+ discard,
696
+ reset
697
+ }), [
698
+ state,
699
+ maxTakes,
700
+ start,
701
+ stop,
702
+ discard,
703
+ reset
704
+ ]);
705
+ }
706
+ //#endregion
707
+ export { CAPTURE_PROCESSOR_SOURCE as n, useSpeechRecorder as t };
708
+
709
+ //# sourceMappingURL=useSpeechRecorder-zIi9Dlw8.js.map