@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
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/hooks/speech-recorder/machine.ts","../src/hooks/speech-recorder/resample.ts","../src/hooks/speech-recorder/wav.ts","../src/hooks/speech-recorder/worklet.ts","../src/hooks/useSpeechRecorder.ts"],"names":["useRef","useState","useCallback","maxTakes","useEffect","useMemo"],"mappings":"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","file":"chunk-UDCQTOVU.cjs","sourcesContent":["'use client';\n\n/**\n * The recorder's state, as one pure reduction.\n *\n * Kept out of the hook because the hook's own work — opening a microphone,\n * building a graph, tearing both down — is asynchronous, and a state machine\n * spread across five callbacks is exactly where an auto-stop that races a\n * learner's `stop()` hides. Every transition is here, synchronous and\n * testable without a microphone; the hook only says which event happened.\n *\n * No transition reads a browser global, which is what lets `status`, `error`\n * and `canRecord` be rendered on a server.\n */\n\n/** A recording the learner has made and not yet discarded. */\nexport interface RecordedTake {\n /** Mono 16-bit PCM WAV at 16 kHz — the format `inspectWav` measures. */\n blob: Blob;\n mimeType: string;\n /** The encoded recording's own length, not a wall-clock timing of the attempt. */\n durationMs: number;\n /** The loudest sample of the take, 0..1. */\n peakLevel: number;\n}\n\nexport type SpeechRecorderStatus =\n | 'idle'\n | 'requesting-permission'\n | 'recording'\n | 'recorded'\n | 'error';\n\nexport type SpeechRecorderError =\n | 'permission-denied'\n | 'no-device'\n | 'unsupported'\n | 'too-short'\n | 'failed';\n\nexport interface RecorderState {\n status: SpeechRecorderStatus;\n error: SpeechRecorderError | null;\n level: number;\n elapsedMs: number;\n take: RecordedTake | null;\n takesUsed: number;\n}\n\nexport type RecorderEvent =\n | { kind: 'requested' }\n | { kind: 'started' }\n | { kind: 'progress'; level: number; elapsedMs: number }\n | { kind: 'captured'; take: RecordedTake }\n | { kind: 'failed'; error: SpeechRecorderError }\n | { kind: 'discarded' }\n | { kind: 'reset' };\n\n/** The state a recorder mounts in, on a server as on a client. */\nexport const IDLE_RECORDER: RecorderState = {\n status: 'idle',\n error: null,\n level: 0,\n elapsedMs: 0,\n take: null,\n takesUsed: 0,\n};\n\n/**\n * Applies one event. Returns the state it was given, unchanged and identical,\n * when the event moves nothing — so a block arriving after teardown costs no\n * render.\n */\nexport function reduceRecorder(state: RecorderState, event: RecorderEvent): RecorderState {\n switch (event.kind) {\n case 'requested':\n // A new attempt clears the previous take. The learner has replaced it,\n // and a take left on screen through the next recording would offer a\n // submit button for audio that is no longer the one just made.\n return {\n ...state,\n status: 'requesting-permission',\n error: null,\n level: 0,\n elapsedMs: 0,\n take: null,\n };\n case 'started':\n // Only from a request this machine made. A microphone that opens after\n // the learner discarded the attempt must not put the meter back up.\n return state.status === 'requesting-permission' ? { ...state, status: 'recording' } : state;\n case 'progress':\n return state.status === 'recording'\n ? { ...state, level: event.level, elapsedMs: event.elapsedMs }\n : state;\n case 'captured':\n // The take count rises here and nowhere else, and `discard` never gives\n // one back: a budget a re-record refunded would bound nothing.\n return {\n ...state,\n status: 'recorded',\n error: null,\n level: 0,\n elapsedMs: event.take.durationMs,\n take: event.take,\n takesUsed: state.takesUsed + 1,\n };\n case 'failed':\n // No take was produced, so none was spent — a cough must not cost a\n // learner their only attempt. The elapsed time survives, because a\n // \"too short\" message reads better beside the length that was too short.\n return { ...state, status: 'error', error: event.error, level: 0, take: null };\n case 'discarded':\n return { ...state, status: 'idle', error: null, level: 0, elapsedMs: 0, take: null };\n case 'reset':\n // The budget goes back, which is the whole difference from `discarded`.\n // A discard is the same learner replacing their own take and must pay\n // for it; a reset is a different reading altogether — a new activity in\n // the same mounted component — and the takes it may have are its own.\n return state === IDLE_RECORDER ? state : IDLE_RECORDER;\n }\n}\n","'use client';\n\n/**\n * Turning the blocks a capture node delivers into one mono 16 kHz signal.\n *\n * 16 kHz mono is the rate every mainstream assessor accepts and the rate\n * `inspectWav` measures exactly, while a microphone is opened at whatever rate\n * the device runs at — 44.1 or 48 kHz on most, already 16 kHz on some. The\n * three steps are separated and pure so the arithmetic that decides what a\n * server will measure can be tested without a browser.\n */\n\n/** The capture rate every mainstream assessor accepts. */\nexport const TARGET_SAMPLE_RATE = 16_000;\n\n/**\n * Where the anti-alias filter stops passing, as a fraction of the target rate.\n *\n * Below 0.5 on purpose: a filter cutting at exactly the target's Nyquist is\n * only 6 dB down there and its transition band folds straight back into the\n * signal. Cutting at 7.2 kHz leaves the transition band room to finish before\n * 8 kHz, and speech carries next to nothing in the 800 Hz that costs.\n */\nconst CUTOFF_FRACTION = 0.45;\n\n/**\n * Half the kernel width, counted in target-rate sample periods. Sixteen\n * periods of taps is enough Hann-windowed sinc to put a tone an octave above\n * the cutoff far below anything a meter or an assessor would notice, and short\n * enough that filtering a whole take costs a fraction of a second.\n */\nconst KERNEL_HALF_PERIODS = 8;\n\n/**\n * Averages the channels of one capture block into mono.\n *\n * Always allocates, never returns what it was given: a `ScriptProcessorNode`\n * hands out the same buffer on every callback and overwrites it in place, so a\n * mono capture that passed the input straight through would find every block\n * it had kept replaced by the last one.\n *\n * Channels of unequal length are read to the shortest. Nothing should produce\n * them, and the alternative to a bound is reading past the end of one and\n * poisoning the whole take with NaN.\n */\nexport function downmix(channels: readonly Float32Array[]): Float32Array {\n if (channels.length === 0) {\n return new Float32Array(0);\n }\n let frames = channels[0].length;\n for (const channel of channels) {\n if (channel.length < frames) {\n frames = channel.length;\n }\n }\n const mono = new Float32Array(frames);\n for (const channel of channels) {\n for (let frame = 0; frame < frames; frame += 1) {\n mono[frame] += channel[frame];\n }\n }\n for (let frame = 0; frame < frames; frame += 1) {\n mono[frame] /= channels.length;\n }\n return mono;\n}\n\n/** Concatenates captured blocks into the single signal a take is encoded from. */\nexport function joinBlocks(blocks: readonly Float32Array[], frames: number): Float32Array {\n const joined = new Float32Array(frames);\n let at = 0;\n for (const block of blocks) {\n joined.set(block, at);\n at += block.length;\n }\n return joined;\n}\n\n/**\n * A windowed-sinc low-pass, applied at the input rate before any samples are\n * dropped.\n *\n * Without it, resampling is aliasing: every component above the target's\n * Nyquist folds back down into the speech band as a tone that was never\n * spoken, and an assessor scores what it hears. The window is Hann and the\n * kernel is normalised to unit gain, so a signal already inside the band comes\n * through at its own amplitude.\n *\n * Edges are extended by repeating the first and last sample rather than by\n * zero-padding, which would put a step — and so a click — at each end of every\n * take.\n */\nfunction lowPass(samples: Float32Array, inputRate: number): Float32Array {\n const cutoff = (TARGET_SAMPLE_RATE * CUTOFF_FRACTION) / inputRate;\n const half = Math.max(1, Math.round((KERNEL_HALF_PERIODS * inputRate) / TARGET_SAMPLE_RATE));\n const taps = new Float32Array(half * 2 + 1);\n let gain = 0;\n for (let tap = 0; tap < taps.length; tap += 1) {\n const at = tap - half;\n const angle = 2 * Math.PI * cutoff * at;\n const sinc = at === 0 ? 2 * cutoff : Math.sin(angle) / (Math.PI * at);\n const window = 0.5 - 0.5 * Math.cos((2 * Math.PI * tap) / (taps.length - 1));\n taps[tap] = sinc * window;\n gain += taps[tap];\n }\n for (let tap = 0; tap < taps.length; tap += 1) {\n taps[tap] /= gain;\n }\n\n const last = samples.length - 1;\n const filtered = new Float32Array(samples.length);\n for (let index = 0; index < samples.length; index += 1) {\n let total = 0;\n for (let tap = 0; tap < taps.length; tap += 1) {\n const at = index + tap - half;\n total += taps[tap] * samples[at < 0 ? 0 : at > last ? last : at];\n }\n filtered[index] = total;\n }\n return filtered;\n}\n\n/**\n * Resamples mono samples from `inputRate` to {@link TARGET_SAMPLE_RATE}.\n *\n * Every path returns an array the caller owns, the equal-rate one included, so\n * no caller has to know whether it was handed its own buffer back.\n *\n * The filter runs only when the rate is coming down. Interpolating upwards\n * invents no frequencies above the input's own Nyquist, so there is nothing\n * for an anti-alias filter to remove and applying one would only dull a\n * recording a 16 kHz device already captured cleanly.\n */\nexport function resampleToTarget(samples: Float32Array, inputRate: number): Float32Array {\n if (samples.length === 0) {\n return new Float32Array(0);\n }\n if (inputRate === TARGET_SAMPLE_RATE) {\n return samples.slice();\n }\n const source = inputRate > TARGET_SAMPLE_RATE ? lowPass(samples, inputRate) : samples;\n const length = Math.round((samples.length * TARGET_SAMPLE_RATE) / inputRate);\n const resampled = new Float32Array(length);\n const step = inputRate / TARGET_SAMPLE_RATE;\n const last = source.length - 1;\n for (let index = 0; index < length; index += 1) {\n const at = index * step;\n const left = Math.min(Math.floor(at), last);\n const right = Math.min(left + 1, last);\n const fraction = at - left;\n resampled[index] = source[left] * (1 - fraction) + source[right] * fraction;\n }\n return resampled;\n}\n","'use client';\n\n/**\n * 16-bit PCM WAV, and the two level measurements taken over the same samples.\n *\n * This is the one format the SDK's `inspectWav` reads, so these bytes are the\n * bytes a server measures a take with — anything else would need a decoder on\n * the application's side before a grade could exist. Nothing here touches Web\n * Audio, which is why the encoder can be unit tested against `inspectWav`\n * itself rather than against a browser.\n */\n\n/** The media type of what {@link encodeWav16} returns. */\nexport const WAV_MIME_TYPE = 'audio/wav';\n\n/** RIFF + a 16-byte `fmt ` + `data`, every field fixed-size. */\nconst HEADER_BYTES = 44;\n\n/** Writes four ASCII characters, which is all a RIFF tag ever is. */\nfunction writeTag(view: DataView, offset: number, tag: string): void {\n for (let index = 0; index < tag.length; index += 1) {\n view.setUint8(offset + index, tag.charCodeAt(index));\n }\n}\n\n/**\n * Encodes mono samples as a 16-bit PCM WAV.\n *\n * The two scale factors are not a typo. Signed 16-bit runs from -32768 to\n * 32767, so a single factor cannot reach full scale in both directions: 0x7fff\n * throughout leaves a full-scale negative peak reading -0.99997, and 0x8000\n * throughout overflows into the positive at +1. Each sign gets the factor that\n * lands exactly on its own limit.\n *\n * The buffer is named in the return type because a `Blob` refuses a view that\n * might be backed by a `SharedArrayBuffer`, and these bytes go straight into\n * one.\n */\nexport function encodeWav16(samples: Float32Array, sampleRate: number): Uint8Array<ArrayBuffer> {\n const dataBytes = samples.length * 2;\n const bytes = new Uint8Array(HEADER_BYTES + dataBytes);\n const view = new DataView(bytes.buffer);\n\n writeTag(view, 0, 'RIFF');\n view.setUint32(4, HEADER_BYTES - 8 + dataBytes, true);\n writeTag(view, 8, 'WAVE');\n writeTag(view, 12, 'fmt ');\n view.setUint32(16, 16, true);\n view.setUint16(20, 1, true); // PCM, uncompressed\n view.setUint16(22, 1, true); // mono\n view.setUint32(24, sampleRate, true);\n view.setUint32(28, sampleRate * 2, true); // byte rate: one channel of two bytes\n view.setUint16(32, 2, true); // block align\n view.setUint16(34, 16, true); // bits per sample\n writeTag(view, 36, 'data');\n view.setUint32(40, dataBytes, true);\n\n for (let index = 0; index < samples.length; index += 1) {\n const sample = samples[index];\n const clamped = sample < -1 ? -1 : sample > 1 ? 1 : sample;\n view.setInt16(\n HEADER_BYTES + index * 2,\n Math.round(clamped * (clamped < 0 ? 0x8000 : 0x7fff)),\n true,\n );\n }\n return bytes;\n}\n\n/** The loudest sample, 0..1 — what a take reports as its `peakLevel`. */\nexport function peakOf(samples: Float32Array): number {\n let peak = 0;\n for (let index = 0; index < samples.length; index += 1) {\n const magnitude = Math.abs(samples[index]);\n if (magnitude > peak) {\n peak = magnitude;\n }\n }\n return peak > 1 ? 1 : peak;\n}\n\n/**\n * Root mean square, 0..1 — what the recorder reports as its `level`.\n *\n * RMS rather than the peak, because a meter driven by peaks sits near full\n * scale on any speech at all and tells a learner nothing about whether they\n * are being heard. It is also the measure `inspectWav` decides voiced time\n * with, so what the meter shows and what the server later counts as speech are\n * the same quantity.\n */\nexport function rmsOf(samples: Float32Array): number {\n if (samples.length === 0) {\n return 0;\n }\n let total = 0;\n for (let index = 0; index < samples.length; index += 1) {\n total += samples[index] * samples[index];\n }\n const rms = Math.sqrt(total / samples.length);\n return rms > 1 ? 1 : rms;\n}\n","'use client';\n\n/**\n * The audio worklet processor, and the module URL it is loaded through.\n *\n * The processor must run inside `AudioWorkletGlobalScope`, which can only be\n * reached by URL. Source text turned into a Blob URL is the only form of that\n * which needs nothing of the consuming application: `import.meta` is not\n * available in every target this package is bundled for — the CommonJS build\n * among them — and a separate asset file would have to be copied into the\n * public directory of every application that installs the SDK.\n *\n * An application whose Content-Security-Policy refuses that URL self-hosts the\n * same source and passes its URL as `workletUrl` instead. The source is public\n * for exactly that reason — see {@link CAPTURE_PROCESSOR_SOURCE}.\n */\n\n/** The name the processor registers itself under, and is constructed by. */\nexport const CAPTURE_PROCESSOR = 'lk-speech-capture';\n\n/**\n * The audio worklet module `useSpeechRecorder` captures through, as source\n * text — exported so an application with a strict Content-Security-Policy can\n * serve it itself.\n *\n * By default the hook loads this text from a `blob:` URL. A worklet module is\n * loaded as a script, so it is `script-src` that must allow `blob:` —\n * `worker-src` does not govern it. Where that is refused, write this string to\n * a file your policy's `script-src` does allow, at build time and from the\n * installed package so it moves with the version you run, and pass that file's\n * URL as `workletUrl`:\n *\n * ```ts\n * import { writeFileSync } from 'node:fs';\n * import { CAPTURE_PROCESSOR_SOURCE } from '@intellectif/lk-react/hooks/useSpeechRecorder';\n *\n * writeFileSync('public/lk-speech-capture.js', CAPTURE_PROCESSOR_SOURCE);\n * ```\n *\n * **The contract** a module at `workletUrl` must meet, which this source does:\n * register an `AudioWorkletProcessor` under the name `lk-speech-capture`, and\n * from its `process(inputs)` post the first input's channels to its `port` as\n * an array with one `Float32Array` per channel, **copied** — the render\n * quantum's buffers belong to the audio thread and are overwritten on the next\n * quantum, so a block posted without copying arrives as whatever the\n * microphone heard some milliseconds later. Return `true`, which keeps the\n * processor alive while its source is silent. Anything else changes what is\n * recorded, so serve this text unchanged rather than a copy of your own.\n */\nexport const CAPTURE_PROCESSOR_SOURCE = `class LkSpeechCapture extends AudioWorkletProcessor {\n process(inputs) {\n const input = inputs[0];\n if (input === undefined || input.length === 0) {\n return true;\n }\n const block = [];\n for (let channel = 0; channel < input.length; channel += 1) {\n block.push(input[channel].slice());\n }\n this.port.postMessage(block);\n return true;\n }\n}\nregisterProcessor(${JSON.stringify(CAPTURE_PROCESSOR)}, LkSpeechCapture);\n`;\n\n/** A module URL and the release that frees it. */\nexport interface ProcessorModule {\n url: string;\n release(): void;\n}\n\n/** The object-URL pair, as much of it as an environment happens to have. */\ninterface ObjectUrls {\n createObjectURL?(blob: Blob): string;\n revokeObjectURL?(url: string): void;\n}\n\n/**\n * Wraps {@link CAPTURE_PROCESSOR_SOURCE} in an object URL.\n *\n * Returns `null` where object URLs are unavailable — a server render, a test\n * environment, a hardened embedder — rather than throwing, because the caller\n * has a working fallback for exactly that case and a missing URL factory is\n * not a failed recording.\n *\n * Called from `start()` and never at module scope: `URL.createObjectURL` is a\n * browser global, and reading one while a module is evaluated is what breaks a\n * server render.\n */\nexport function createProcessorUrl(): ProcessorModule | null {\n const urls = (globalThis as { URL?: ObjectUrls }).URL;\n if (urls === undefined) {\n return null;\n }\n const create = urls.createObjectURL;\n const revoke = urls.revokeObjectURL;\n if (create === undefined || revoke === undefined) {\n return null;\n }\n const url = create.call(urls, new Blob([CAPTURE_PROCESSOR_SOURCE], { type: 'text/javascript' }));\n return {\n url,\n release: () => {\n revoke.call(urls, url);\n },\n };\n}\n","'use client';\n\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport {\n IDLE_RECORDER,\n type RecordedTake,\n type RecorderEvent,\n type RecorderState,\n reduceRecorder,\n type SpeechRecorderError,\n type SpeechRecorderStatus,\n} from './speech-recorder/machine.js';\nimport {\n downmix,\n joinBlocks,\n resampleToTarget,\n TARGET_SAMPLE_RATE,\n} from './speech-recorder/resample.js';\nimport { encodeWav16, peakOf, rmsOf, WAV_MIME_TYPE } from './speech-recorder/wav.js';\nimport { CAPTURE_PROCESSOR, createProcessorUrl } from './speech-recorder/worklet.js';\n\n// Public because `workletUrl` is: an option that names a module is only usable\n// if the module's text can be reached without reading a minified chunk.\nexport { CAPTURE_PROCESSOR_SOURCE } from './speech-recorder/worklet.js';\nexport type { RecordedTake, SpeechRecorderError, SpeechRecorderStatus };\n\nexport interface SpeechRecorderOptions {\n /** The longest a take may be. Reached, the recording stops itself. */\n maxDurationMs: number;\n /** Shorter than this and the take is refused as `too-short`. */\n minDurationMs?: number;\n /** How many takes the learner may make. Unbounded when absent. */\n maxTakes?: number;\n /**\n * The URL of a self-hosted copy of {@link CAPTURE_PROCESSOR_SOURCE}, for an\n * application whose Content-Security-Policy does not allow `blob:` in\n * `script-src`. Serve that string unchanged; the contract a module here must\n * meet is written on it.\n *\n * **A URL that does not work is not reported.** If the module fails to load,\n * or loads without registering `lk-speech-capture`, the hook records through\n * the deprecated `ScriptProcessorNode` on the main thread instead — the\n * fallback it keeps for engines without audio worklets — and a recorder with\n * a broken `workletUrl` looks like a working one. Only an engine with neither\n * path reports `error: 'unsupported'`. Check that the module is actually\n * fetched.\n */\n workletUrl?: string;\n}\n\nexport interface SpeechRecorder {\n status: SpeechRecorderStatus;\n error: SpeechRecorderError | null;\n /** The loudest block since the last update, 0..1. */\n level: number;\n /** How much audio has been captured, from the sample count. */\n elapsedMs: number;\n take: RecordedTake | null;\n takesUsed: number;\n /** Whether a take remains. Not whether a `start()` would be accepted now. */\n canRecord: boolean;\n /** Opens the microphone and begins capturing. **Never rejects.** */\n start(): Promise<void>;\n /** Ends the capture and encodes the take. */\n stop(): void;\n /** Throws the take away and releases the microphone. */\n discard(): void;\n /**\n * Starts over: releases the microphone, throws the take away **and gives the\n * budget back**.\n *\n * That last part is the whole difference from {@link SpeechRecorder.discard},\n * which deliberately refunds nothing — a budget a re-record gave back would\n * bound nothing. A reset is for a recorder that has been handed a different\n * reading while it stayed mounted: the takes belong to the activity, so the\n * next activity's are its own. Calling it for the same reading hands a\n * learner takes they have already spent.\n */\n reset(): void;\n}\n\n/**\n * The capture graph, typed by the surface this hook uses rather than by the\n * DOM lib.\n *\n * Two reasons, both load-bearing. The lib declares `audioWorklet` and\n * `createScriptProcessor` non-optional, and the engines this hook falls back\n * for are exactly the ones missing one or the other — comparing a type that\n * cannot be `undefined` against `undefined` is a compile error, so the lib's\n * types would force the fallback to be written blind. And a test harness can\n * implement these six members; it cannot implement `BaseAudioContext`.\n */\ninterface CaptureNode {\n connect(destination: CaptureNode): unknown;\n disconnect(): void;\n}\n\ninterface CaptureGain extends CaptureNode {\n readonly gain: { value: number };\n}\n\ninterface WorkletCaptureNode extends CaptureNode {\n readonly port: { onmessage: ((event: { data: unknown }) => void) | null };\n}\n\ninterface ScriptProcessorCaptureNode extends CaptureNode {\n onaudioprocess:\n | ((event: {\n inputBuffer: { numberOfChannels: number; getChannelData(channel: number): Float32Array };\n }) => void)\n | null;\n}\n\ninterface CaptureStream {\n getTracks(): readonly { stop(): void }[];\n}\n\ninterface CaptureContext {\n readonly sampleRate: number;\n readonly destination: CaptureNode;\n readonly audioWorklet?: { addModule(url: string): Promise<void> };\n createMediaStreamSource(stream: CaptureStream): CaptureNode;\n createGain(): CaptureGain;\n createScriptProcessor?(\n bufferFrames: number,\n inputChannels: number,\n outputChannels: number,\n ): ScriptProcessorCaptureNode;\n close(): Promise<void>;\n}\n\ninterface CaptureGlobals {\n navigator?: {\n mediaDevices?: { getUserMedia?(constraints: { audio: boolean }): Promise<CaptureStream> };\n };\n AudioContext?: new () => CaptureContext;\n AudioWorkletNode?: new (context: CaptureContext, name: string) => WorkletCaptureNode;\n}\n\n/** A capture node and the removal of the handler it was given. */\ninterface AttachedNode {\n node: CaptureNode;\n /** Detaches the handler, so a block delivered after teardown reaches nothing. */\n detach(): void;\n}\n\ntype BlockHandler = (channels: readonly Float32Array[]) => void;\n\n/** One recording in progress. Everything it holds must be released exactly once. */\ninterface CaptureSession {\n blocks: Float32Array[];\n frames: number;\n rate: number;\n /** The auto-stop deadline, as a sample count, beside the samples it bounds. */\n maxFrames: number;\n /** The loudest block since the meter last reported. */\n peak: number;\n /** The frame count at the last meter update. */\n reportedAt: number;\n release(): void;\n}\n\n/**\n * A `ScriptProcessorNode`'s buffer, in frames.\n *\n * This fallback runs its callback on the main thread, so the buffer has to\n * hold whatever a render blocks it for; 4096 frames is about 85 ms at 48 kHz,\n * and a smaller one drops samples out of the middle of a take whenever the\n * page is busy. The worklet path, which runs on the audio thread, needs none\n * of this.\n */\nconst PROCESSOR_BUFFER_FRAMES = 4096;\n\n/** How often the meter and the elapsed time may move. */\nconst LEVEL_UPDATES_PER_SECOND = 10;\n\nfunction stopTracks(stream: CaptureStream): void {\n for (const track of stream.getTracks()) {\n track.stop();\n }\n}\n\nfunction closeQuietly(context: CaptureContext): void {\n void context.close().catch(() => {\n /* a context the page has already closed; nothing depends on the result */\n });\n}\n\n/**\n * Which refusal `getUserMedia` made.\n *\n * Read from `name`, not from the message: messages are localised and are\n * rewritten between browser versions, while the names are specified. Anything\n * unrecognised is `failed` rather than a guess — telling a learner their\n * permission was denied when their device was busy sends them to the wrong\n * setting.\n */\nfunction microphoneError(error: unknown): SpeechRecorderError {\n switch ((error as { name?: string } | null)?.name) {\n case 'NotAllowedError':\n case 'SecurityError':\n return 'permission-denied';\n case 'NotFoundError':\n return 'no-device';\n case 'NotSupportedError':\n return 'unsupported';\n default:\n return 'failed';\n }\n}\n\n/**\n * The worklet capture node, or `null` when this engine cannot build one.\n *\n * `null` rather than a throw for every one of its reasons — no\n * `AudioWorkletNode`, no worklet thread, an object URL the page's\n * Content-Security-Policy refuses, a module that fails to load — because none\n * of them is a failed recording: the caller has a working fallback for all of\n * them, and only the absence of both is an unsupported browser.\n */\nasync function createWorkletNode(\n context: CaptureContext,\n workletUrl: string | undefined,\n onBlock: BlockHandler,\n): Promise<AttachedNode | null> {\n const AudioWorkletNodeConstructor = (globalThis as unknown as CaptureGlobals).AudioWorkletNode;\n const worklet = context.audioWorklet;\n if (AudioWorkletNodeConstructor === undefined || worklet === undefined) {\n return null;\n }\n const minted = workletUrl === undefined ? createProcessorUrl() : null;\n const url = workletUrl ?? minted?.url;\n if (url === undefined) {\n return null;\n }\n try {\n await worklet.addModule(url);\n const node = new AudioWorkletNodeConstructor(context, CAPTURE_PROCESSOR);\n node.port.onmessage = (event) => {\n onBlock(event.data as readonly Float32Array[]);\n };\n return {\n node,\n detach: () => {\n node.port.onmessage = null;\n },\n };\n } catch {\n return null;\n } finally {\n // Revoked as soon as the module has been fetched, whether it loaded or\n // not: an object URL lives until the document is discarded otherwise, and\n // this one is minted once per take.\n minted?.release();\n }\n}\n\n/** The `ScriptProcessorNode` fallback, or `null` on an engine without one. */\nfunction createProcessorNode(context: CaptureContext, onBlock: BlockHandler): AttachedNode | null {\n const create = context.createScriptProcessor;\n if (create === undefined) {\n return null;\n }\n const node = create.call(context, PROCESSOR_BUFFER_FRAMES, 1, 1);\n node.onaudioprocess = (event) => {\n const buffer = event.inputBuffer;\n const channels: Float32Array[] = [];\n for (let channel = 0; channel < buffer.numberOfChannels; channel += 1) {\n channels.push(buffer.getChannelData(channel));\n }\n onBlock(channels);\n };\n return {\n node,\n detach: () => {\n node.onaudioprocess = null;\n },\n };\n}\n\n/**\n * Records a take of speech as mono 16-bit PCM WAV at 16 kHz.\n *\n * That is the one format the SDK measures directly: `inspectWav` reads it\n * without a decoding library, and it is what every mainstream pronunciation\n * assessor accepts. A take is captured at whatever rate the device runs at,\n * downmixed to mono, low-pass filtered and resampled, then encoded — so what a\n * server measures is what this hook produced, not a claim about it.\n *\n * **Server rendering.** `status`, `error` and `canRecord` are derived from the\n * take count alone and never from a browser global, so the markup a server\n * produces is the markup the browser's first render produces. Support is\n * discovered inside {@link SpeechRecorder.start} and reported as\n * `error: 'unsupported'`; the obvious `!!navigator.mediaDevices` guard at\n * render time would make a server render and its hydration disagree.\n *\n * **Identity.** `start`, `stop`, `discard` and `reset` keep one identity for the\n * life of the hook, so they are safe in a dependency array. The returned object keeps\n * one identity for as long as none of its values changes, so a render caused\n * by something else does not invalidate an effect that depends on it.\n *\n * `start()` does nothing when no take remains, so `canRecord` is presentation:\n * mark the control `aria-disabled` rather than `disabled`, which would take it\n * out of the tab order and leave a learner nothing to read.\n *\n * ```tsx\n * const recorder = useSpeechRecorder({ maxDurationMs: 20_000, maxTakes: 3 });\n * <button\n * type=\"button\"\n * aria-disabled={!recorder.canRecord}\n * onClick={() => {\n * void recorder.start();\n * }}\n * >\n * Record\n * </button>\n * ```\n */\nexport function useSpeechRecorder(options: SpeechRecorderOptions): SpeechRecorder {\n // The latest options without changing `start`/`stop`/`discard`'s identity\n // when the caller passes an inline object, exactly as `useXAPI` keeps its\n // config.\n const optionsRef = useRef(options);\n optionsRef.current = options;\n\n const [state, setState] = useState<RecorderState>(IDLE_RECORDER);\n // The state as of this instant, written synchronously. Two events in one\n // tick — an auto-stop reached inside a capture callback and the learner's\n // own `stop()` — would otherwise both read the value React has not committed\n // yet, which is the bug `AudioTransport` and `ActivitySequence` each carry a\n // report of.\n const stateRef = useRef<RecorderState>(IDLE_RECORDER);\n const sessionRef = useRef<CaptureSession | null>(null);\n const startingRef = useRef(false);\n // Bumped by anything that abandons an attempt. `start()` re-reads it after\n // every await: a learner who discards, or a component that unmounts, while\n // the permission prompt is still open must not have a microphone opened\n // behind them and left open for ever.\n const attemptRef = useRef(0);\n\n const dispatch = useCallback((event: RecorderEvent) => {\n const next = reduceRecorder(stateRef.current, event);\n if (next === stateRef.current) {\n return;\n }\n stateRef.current = next;\n setState(next);\n }, []);\n\n const endSession = useCallback(() => {\n const session = sessionRef.current;\n sessionRef.current = null;\n session?.release();\n }, []);\n\n const finish = useCallback(() => {\n const session = sessionRef.current;\n if (session === null) {\n return;\n }\n sessionRef.current = null;\n session.release();\n\n const { minDurationMs } = optionsRef.current;\n const samples = resampleToTarget(joinBlocks(session.blocks, session.frames), session.rate);\n // Taken from the encoded samples, so a take reports the duration a server\n // will measure in its bytes rather than a wall-clock timing of the attempt.\n //\n // No second bound is applied here. The capture was bounded at the device's\n // own rate, and resampling cannot push a whole-millisecond bound over:\n // checked across every rate from 8 to 192 kHz and every bound up to five\n // minutes, the encoded length never exceeds what the bound allows. (A\n // fractional `maxDurationMs` can overshoot by up to a fortieth of a\n // millisecond, which is less than one sample at this rate.)\n const durationMs = (samples.length / TARGET_SAMPLE_RATE) * 1000;\n if (minDurationMs !== undefined && durationMs < minDurationMs) {\n dispatch({ kind: 'failed', error: 'too-short' });\n return;\n }\n dispatch({\n kind: 'captured',\n take: {\n blob: new Blob([encodeWav16(samples, TARGET_SAMPLE_RATE)], { type: WAV_MIME_TYPE }),\n mimeType: WAV_MIME_TYPE,\n durationMs,\n peakLevel: peakOf(samples),\n },\n });\n }, [dispatch]);\n\n const handleBlock = useCallback(\n (channels: readonly Float32Array[]) => {\n const session = sessionRef.current;\n if (session === null) {\n return;\n }\n let block = downmix(channels);\n const room = session.maxFrames - session.frames;\n if (block.length > room) {\n block = block.subarray(0, room);\n }\n session.blocks.push(block);\n session.frames += block.length;\n const level = rmsOf(block);\n if (level > session.peak) {\n session.peak = level;\n }\n\n // Auto-stop is measured from the samples themselves, never from a timer:\n // the samples ARE the recording, so a take cannot run past its bound\n // however the page's timers were scheduled, and there is no deadline\n // left for a learner's own `stop()` to race.\n if (session.frames >= session.maxFrames) {\n finish();\n return;\n }\n if (session.frames - session.reportedAt < session.rate / LEVEL_UPDATES_PER_SECOND) {\n return;\n }\n session.reportedAt = session.frames;\n // The meter reports the loudest block since it last did, not the latest\n // one: at ten updates a second the latest block is one in forty, and a\n // meter that samples speech that thinly flickers instead of reading.\n const peak = session.peak;\n session.peak = 0;\n dispatch({\n kind: 'progress',\n level: peak,\n elapsedMs: (session.frames / session.rate) * 1000,\n });\n },\n [dispatch, finish],\n );\n\n const start = useCallback(async (): Promise<void> => {\n const { maxDurationMs, maxTakes, workletUrl } = optionsRef.current;\n // A second start while one is in flight, or while a capture is running, is\n // a learner pressing twice; a start with no takes left is a control the\n // caller should not have offered. Neither is an error worth a message.\n if (startingRef.current || sessionRef.current !== null) {\n return;\n }\n if (maxTakes !== undefined && stateRef.current.takesUsed >= maxTakes) {\n return;\n }\n const attempt = attemptRef.current;\n startingRef.current = true;\n dispatch({ kind: 'requested' });\n // Everything acquired so far, in the order it must be given back. Each\n // step replaces it, so every early return and the catch release exactly\n // what exists at that point.\n let release = (): void => {};\n\n try {\n // Support is discovered HERE and nowhere else — see the hook's note on\n // server rendering.\n const view = globalThis as unknown as CaptureGlobals;\n const devices = view.navigator?.mediaDevices;\n const getUserMedia = devices?.getUserMedia;\n const AudioContextConstructor = view.AudioContext;\n if (\n devices === undefined ||\n getUserMedia === undefined ||\n AudioContextConstructor === undefined\n ) {\n dispatch({ kind: 'failed', error: 'unsupported' });\n return;\n }\n\n let stream: CaptureStream;\n try {\n stream = await getUserMedia.call(devices, { audio: true });\n } catch (error) {\n dispatch({ kind: 'failed', error: microphoneError(error) });\n return;\n }\n release = () => {\n stopTracks(stream);\n };\n if (attempt !== attemptRef.current) {\n release();\n dispatch({ kind: 'discarded' });\n return;\n }\n\n let context: CaptureContext;\n try {\n context = new AudioContextConstructor();\n } catch {\n release();\n dispatch({ kind: 'failed', error: 'unsupported' });\n return;\n }\n const open = context;\n release = () => {\n stopTracks(stream);\n closeQuietly(open);\n };\n\n const attached =\n (await createWorkletNode(open, workletUrl, handleBlock)) ??\n createProcessorNode(open, handleBlock);\n if (attached === null) {\n release();\n dispatch({ kind: 'failed', error: 'unsupported' });\n return;\n }\n\n const source = open.createMediaStreamSource(stream);\n // A capture node is only pulled while it is reachable from the\n // destination, so the graph ends in a gain of zero: without the\n // connection some engines never call the processor at all, and with a\n // plain one the learner hears themselves through their own speakers.\n const sink = open.createGain();\n sink.gain.value = 0;\n source.connect(attached.node);\n attached.node.connect(sink);\n sink.connect(open.destination);\n release = () => {\n attached.detach();\n attached.node.disconnect();\n source.disconnect();\n sink.disconnect();\n stopTracks(stream);\n closeQuietly(open);\n };\n if (attempt !== attemptRef.current) {\n release();\n dispatch({ kind: 'discarded' });\n return;\n }\n\n sessionRef.current = {\n blocks: [],\n frames: 0,\n rate: open.sampleRate,\n maxFrames: Math.max(1, Math.floor((maxDurationMs / 1000) * open.sampleRate)),\n peak: 0,\n reportedAt: 0,\n release,\n };\n dispatch({ kind: 'started' });\n } catch {\n // `start()` never rejects. A recorder that throws into a click handler\n // leaves the learner with a dead control and no message, so anything\n // unforeseen becomes `failed` — which the caller already has a message\n // for — and gives back whatever had been acquired.\n release();\n sessionRef.current = null;\n dispatch({ kind: 'failed', error: 'failed' });\n } finally {\n startingRef.current = false;\n }\n }, [dispatch, handleBlock]);\n\n const stop = useCallback(() => {\n if (sessionRef.current === null) {\n // Before the microphone opens there is nothing to encode, so a stop is a\n // cancelled attempt: the bump is what makes `start()` give back the\n // stream it is about to be handed.\n attemptRef.current += 1;\n return;\n }\n finish();\n }, [finish]);\n\n const discard = useCallback(() => {\n attemptRef.current += 1;\n endSession();\n dispatch({ kind: 'discarded' });\n }, [dispatch, endSession]);\n\n const reset = useCallback(() => {\n attemptRef.current += 1;\n endSession();\n dispatch({ kind: 'reset' });\n }, [dispatch, endSession]);\n\n useEffect(() => {\n // Cleanup only. Every acquisition happens inside `start()` and is released\n // by the session it created, so the mount run has nothing to do and\n // StrictMode's extra mount/cleanup pair leaks nothing.\n return () => {\n attemptRef.current += 1;\n endSession();\n };\n }, [endSession]);\n\n const { maxTakes } = options;\n return useMemo(\n () => ({\n status: state.status,\n error: state.error,\n level: state.level,\n elapsedMs: state.elapsedMs,\n take: state.take,\n takesUsed: state.takesUsed,\n canRecord: maxTakes === undefined || state.takesUsed < maxTakes,\n start,\n stop,\n discard,\n reset,\n }),\n [state, maxTakes, start, stop, discard, reset],\n );\n}\n"]}
@@ -1,128 +0,0 @@
1
- 'use client';
2
- import { createContext, useSyncExternalStore, useMemo, useContext } from 'react';
3
- import { jsx } from 'react/jsx-runtime';
4
-
5
- // src/theme/ThemeProvider.tsx
6
-
7
- // src/theme/dark-theme.ts
8
- var darkTheme = {
9
- "--lk-color-primary": "#fafafa",
10
- "--lk-color-primary-hover": "#e4e4e7",
11
- "--lk-color-surface": "#09090b",
12
- "--lk-color-surface-raised": "#18181b",
13
- "--lk-color-border": "#27272a",
14
- "--lk-color-text": "#fafafa",
15
- "--lk-color-text-muted": "#a1a1aa",
16
- "--lk-color-success": "#4ade80",
17
- "--lk-color-error": "#f87171",
18
- "--lk-color-warning": "#fbbf24",
19
- "--lk-color-focus-ring": "#fafafa"
20
- };
21
-
22
- // src/theme/tokens.ts
23
- var defaultTheme = {
24
- // ── Colours (light) — neutral monochrome; semantic success/error/warning
25
- // retained for status legibility ────────────────────────────────────────
26
- "--lk-color-primary": "#18181b",
27
- "--lk-color-primary-hover": "#27272a",
28
- "--lk-color-surface": "#ffffff",
29
- "--lk-color-surface-raised": "#fafafa",
30
- "--lk-color-border": "#e4e4e7",
31
- "--lk-color-text": "#18181b",
32
- "--lk-color-text-muted": "#52525b",
33
- "--lk-color-success": "#15803d",
34
- "--lk-color-error": "#b91c1c",
35
- "--lk-color-warning": "#b45309",
36
- "--lk-color-focus-ring": "#18181b",
37
- "--lk-color-media-accent": "#ffffff",
38
- // An interactive video's two caption lines. The second is warm yellow, the
39
- // convention for a second subtitle line, and clears 4.5:1 on the caption box
40
- // whatever the video shows through it (see contrast.test.ts).
41
- "--lk-iv-caption-color": "#ffffff",
42
- "--lk-iv-caption-secondary-color": "#ffe066",
43
- "--lk-iv-caption-secondary-scale": "0.85",
44
- "--lk-iv-caption-gap": "0.25em",
45
- // ── Spacing ──────────────────────────────────────────────────────────────
46
- "--lk-spacing-xs": "4px",
47
- "--lk-spacing-sm": "8px",
48
- "--lk-spacing-md": "16px",
49
- "--lk-spacing-lg": "24px",
50
- "--lk-spacing-xl": "32px",
51
- // ── Typography ───────────────────────────────────────────────────────────
52
- "--lk-font-family-base": 'system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif',
53
- "--lk-font-size-sm": "0.875rem",
54
- "--lk-font-size-base": "1rem",
55
- "--lk-font-size-lg": "1.125rem",
56
- "--lk-font-weight-normal": "400",
57
- "--lk-font-weight-bold": "700",
58
- "--lk-line-height-base": "1.5",
59
- // ── Shape ────────────────────────────────────────────────────────────────
60
- "--lk-radius-sm": "0.25rem",
61
- "--lk-radius-base": "0.5rem",
62
- "--lk-radius-lg": "0.75rem",
63
- // ── Motion ───────────────────────────────────────────────────────────────
64
- "--lk-transition-fast": "150ms ease",
65
- "--lk-transition-base": "250ms ease"
66
- };
67
-
68
- // src/theme/tailwind.ts
69
- var BUCKETS = [
70
- ["--lk-color-", "colors"],
71
- ["--lk-spacing-", "spacing"],
72
- ["--lk-radius-", "borderRadius"],
73
- ["--lk-font-family-", "fontFamily"],
74
- ["--lk-font-size-", "fontSize"]
75
- ];
76
- function createTailwindTheme(theme) {
77
- const merged = { ...defaultTheme, ...theme };
78
- const extension = {
79
- colors: {},
80
- spacing: {},
81
- borderRadius: {},
82
- fontFamily: {},
83
- fontSize: {}
84
- };
85
- for (const [tokenName, fallback] of Object.entries(merged)) {
86
- for (const [prefix, bucket] of BUCKETS) {
87
- if (tokenName.startsWith(prefix)) {
88
- extension[bucket][`lk-${tokenName.slice(prefix.length)}`] = `var(${tokenName}, ${fallback})`;
89
- break;
90
- }
91
- }
92
- }
93
- return extension;
94
- }
95
- var ThemeContext = createContext(defaultTheme);
96
- var DARK_QUERY = "(prefers-color-scheme: dark)";
97
- function subscribePrefersDark(onChange) {
98
- if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
99
- return () => {
100
- };
101
- }
102
- const mql = window.matchMedia(DARK_QUERY);
103
- mql.addEventListener("change", onChange);
104
- return () => mql.removeEventListener("change", onChange);
105
- }
106
- function getPrefersDark() {
107
- if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
108
- return false;
109
- }
110
- return window.matchMedia(DARK_QUERY).matches;
111
- }
112
- function ThemeProvider({ theme, children }) {
113
- const prefersDark = useSyncExternalStore(subscribePrefersDark, getPrefersDark, () => false);
114
- const activeTheme = useMemo(() => {
115
- if (theme !== void 0) {
116
- return { ...defaultTheme, ...theme };
117
- }
118
- return prefersDark ? { ...defaultTheme, ...darkTheme } : defaultTheme;
119
- }, [theme, prefersDark]);
120
- return /* @__PURE__ */ jsx(ThemeContext.Provider, { value: activeTheme, children: /* @__PURE__ */ jsx("div", { style: activeTheme, children }) });
121
- }
122
- function useTheme() {
123
- return useContext(ThemeContext);
124
- }
125
-
126
- export { ThemeProvider, createTailwindTheme, darkTheme, defaultTheme, useTheme };
127
- //# sourceMappingURL=chunk-UHKZXOXD.js.map
128
- //# sourceMappingURL=chunk-UHKZXOXD.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/theme/dark-theme.ts","../src/theme/tokens.ts","../src/theme/tailwind.ts","../src/theme/ThemeProvider.tsx"],"names":[],"mappings":"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","file":"chunk-UHKZXOXD.js","sourcesContent":["import type { ThemeTokens } from '@intellectif/lk-core';\n\n/**\n * Dark-mode colour overrides, merged over {@link defaultTheme} by\n * `ThemeProvider`. Colours only — spacing/typography/shape/motion are\n * theme-invariant. All pairs verified WCAG 2.2 AA against the dark surface.\n */\nexport const darkTheme: Partial<ThemeTokens> = {\n '--lk-color-primary': '#fafafa',\n '--lk-color-primary-hover': '#e4e4e7',\n '--lk-color-surface': '#09090b',\n '--lk-color-surface-raised': '#18181b',\n '--lk-color-border': '#27272a',\n '--lk-color-text': '#fafafa',\n '--lk-color-text-muted': '#a1a1aa',\n '--lk-color-success': '#4ade80',\n '--lk-color-error': '#f87171',\n '--lk-color-warning': '#fbbf24',\n '--lk-color-focus-ring': '#fafafa',\n};\n","import type { ThemeTokens } from '@intellectif/lk-core';\n\n/**\n * Canonical default theme token values. `defaults.css` mirrors these exactly.\n * Palette is a neutral monochrome, deliberately unbranded, so the SDK carries\n * nobody's look out of the box; consumers rebrand via `ThemeProvider`. All\n * colour pairs are verified to meet WCAG 2.2 AA: 4.5:1 for normal text, 3:1 for\n * UI / focus ring (see theme/__tests__/contrast.test.ts).\n */\nexport const defaultTheme: ThemeTokens = {\n // ── Colours (light) — neutral monochrome; semantic success/error/warning\n // retained for status legibility ────────────────────────────────────────\n '--lk-color-primary': '#18181b',\n '--lk-color-primary-hover': '#27272a',\n '--lk-color-surface': '#ffffff',\n '--lk-color-surface-raised': '#fafafa',\n '--lk-color-border': '#e4e4e7',\n '--lk-color-text': '#18181b',\n '--lk-color-text-muted': '#52525b',\n '--lk-color-success': '#15803d',\n '--lk-color-error': '#b91c1c',\n '--lk-color-warning': '#b45309',\n '--lk-color-focus-ring': '#18181b',\n '--lk-color-media-accent': '#ffffff',\n // An interactive video's two caption lines. The second is warm yellow, the\n // convention for a second subtitle line, and clears 4.5:1 on the caption box\n // whatever the video shows through it (see contrast.test.ts).\n '--lk-iv-caption-color': '#ffffff',\n '--lk-iv-caption-secondary-color': '#ffe066',\n '--lk-iv-caption-secondary-scale': '0.85',\n '--lk-iv-caption-gap': '0.25em',\n\n // ── Spacing ──────────────────────────────────────────────────────────────\n '--lk-spacing-xs': '4px',\n '--lk-spacing-sm': '8px',\n '--lk-spacing-md': '16px',\n '--lk-spacing-lg': '24px',\n '--lk-spacing-xl': '32px',\n\n // ── Typography ───────────────────────────────────────────────────────────\n '--lk-font-family-base':\n 'system-ui, -apple-system, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif',\n '--lk-font-size-sm': '0.875rem',\n '--lk-font-size-base': '1rem',\n '--lk-font-size-lg': '1.125rem',\n '--lk-font-weight-normal': '400',\n '--lk-font-weight-bold': '700',\n '--lk-line-height-base': '1.5',\n\n // ── Shape ────────────────────────────────────────────────────────────────\n '--lk-radius-sm': '0.25rem',\n '--lk-radius-base': '0.5rem',\n '--lk-radius-lg': '0.75rem',\n\n // ── Motion ───────────────────────────────────────────────────────────────\n '--lk-transition-fast': '150ms ease',\n '--lk-transition-base': '250ms ease',\n};\n","import type { ThemeTokens } from '@intellectif/lk-core';\nimport { defaultTheme } from './tokens.js';\n\n/**\n * Shape consumed by a Tailwind config's `theme.extend`. Every entry is a\n * `var(--lk-token, <fallback>)` reference: the var keeps utilities live\n * (ThemeProvider / dark-mode override at runtime); the fallback makes them\n * work with zero setup (no provider, no `defaults.css` import).\n */\nexport interface TailwindThemeExtension {\n colors: Record<string, string>;\n spacing: Record<string, string>;\n borderRadius: Record<string, string>;\n fontFamily: Record<string, string>;\n fontSize: Record<string, string>;\n}\n\n/**\n * Token-prefix → Tailwind bucket. `--lk-font-weight-*`, `--lk-line-height-*`\n * and `--lk-transition-*` are intentionally absent: the first two have no\n * matching `theme.extend` group here, and the transition tokens hold a\n * `<duration> <easing>` shorthand that would emit invalid\n * `transition-duration: 150ms ease` if mapped to Tailwind's `transitionDuration`.\n */\nconst BUCKETS = [\n ['--lk-color-', 'colors'],\n ['--lk-spacing-', 'spacing'],\n ['--lk-radius-', 'borderRadius'],\n ['--lk-font-family-', 'fontFamily'],\n ['--lk-font-size-', 'fontSize'],\n] as const;\n\n/**\n * Converts SDK theme tokens into a Tailwind `theme.extend` object so Tailwind\n * projects consume the same `--lk-*` tokens as the components — one shared\n * source of truth rather than a parallel, conflicting palette.\n */\nexport function createTailwindTheme(theme: Partial<ThemeTokens>): TailwindThemeExtension {\n const merged: Record<string, string> = { ...defaultTheme, ...theme };\n const extension: TailwindThemeExtension = {\n colors: {},\n spacing: {},\n borderRadius: {},\n fontFamily: {},\n fontSize: {},\n };\n\n for (const [tokenName, fallback] of Object.entries(merged)) {\n for (const [prefix, bucket] of BUCKETS) {\n if (tokenName.startsWith(prefix)) {\n extension[bucket][`lk-${tokenName.slice(prefix.length)}`] =\n `var(${tokenName}, ${fallback})`;\n break;\n }\n }\n }\n\n return extension;\n}\n","'use client';\n\nimport type { ThemeTokens } from '@intellectif/lk-core';\nimport {\n type CSSProperties,\n createContext,\n type ReactNode,\n useContext,\n useMemo,\n useSyncExternalStore,\n} from 'react';\nimport { darkTheme } from './dark-theme.js';\nimport { defaultTheme } from './tokens.js';\n\nexport { darkTheme } from './dark-theme.js';\nexport { createTailwindTheme, type TailwindThemeExtension } from './tailwind.js';\nexport { defaultTheme } from './tokens.js';\n\nconst ThemeContext = createContext<ThemeTokens>(defaultTheme);\n\nconst DARK_QUERY = '(prefers-color-scheme: dark)';\n\n/** Subscribe to OS dark-mode changes; no-op when there is no `window`. */\nfunction subscribePrefersDark(onChange: () => void): () => void {\n if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') {\n return () => {};\n }\n const mql = window.matchMedia(DARK_QUERY);\n mql.addEventListener('change', onChange);\n return () => mql.removeEventListener('change', onChange);\n}\n\n/** Current OS dark-mode preference; `false` when there is no `window`. */\nfunction getPrefersDark(): boolean {\n if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') {\n return false;\n }\n return window.matchMedia(DARK_QUERY).matches;\n}\n\nexport interface ThemeProviderProps {\n /** Partial token overrides. When provided, dark auto-detection is disabled. */\n theme?: Partial<ThemeTokens>;\n children: ReactNode;\n}\n\n/**\n * Provides theme tokens to descendant activity components as scoped inline CSS\n * custom properties on a wrapper `<div>`, and via {@link useTheme}.\n *\n * Resolution: an explicit `theme` prop merges over {@link defaultTheme} (and\n * suppresses dark auto-detect, per Req 13.5). With no `theme` prop, the OS\n * `prefers-color-scheme: dark` preference auto-applies {@link darkTheme}.\n * SSR-safe: the server snapshot is always light, so hydration never mismatches.\n */\nexport function ThemeProvider({ theme, children }: ThemeProviderProps) {\n const prefersDark = useSyncExternalStore(subscribePrefersDark, getPrefersDark, () => false);\n\n const activeTheme = useMemo<ThemeTokens>(() => {\n if (theme !== undefined) {\n return { ...defaultTheme, ...theme };\n }\n return prefersDark ? { ...defaultTheme, ...darkTheme } : defaultTheme;\n }, [theme, prefersDark]);\n\n return (\n <ThemeContext.Provider value={activeTheme}>\n <div style={activeTheme as CSSProperties}>{children}</div>\n </ThemeContext.Provider>\n );\n}\n\n/** Returns the currently active theme tokens (defaults when no provider). */\nexport function useTheme(): ThemeTokens {\n return useContext(ThemeContext);\n}\n"]}
@@ -1,236 +0,0 @@
1
- 'use client';
2
- 'use strict';
3
-
4
- var chunkMKXEM2C5_cjs = require('./chunk-MKXEM2C5.cjs');
5
- var chunkYY4PFR3Z_cjs = require('./chunk-YY4PFR3Z.cjs');
6
- var chunkDS6HN3YR_cjs = require('./chunk-DS6HN3YR.cjs');
7
- var chunk7ALBSPYR_cjs = require('./chunk-7ALBSPYR.cjs');
8
- var chunkNAK55BYU_cjs = require('./chunk-NAK55BYU.cjs');
9
- var chunkTP5UMRAU_cjs = require('./chunk-TP5UMRAU.cjs');
10
- var chunkJ3C72RPK_cjs = require('./chunk-J3C72RPK.cjs');
11
- var chunkBIGGXHAW_cjs = require('./chunk-BIGGXHAW.cjs');
12
- var lkCore = require('@intellectif/lk-core');
13
- var react = require('react');
14
- var jsxRuntime = require('react/jsx-runtime');
15
-
16
- var PREVIEW_SHUFFLE_SEED = "lk-activity-preview";
17
- var PREVIEW_SLOT_ID = "preview";
18
- var previewTakeCount = 0;
19
- var PREVIEW_RECORDING = {
20
- upload: async (take) => {
21
- previewTakeCount += 1;
22
- return {
23
- key: `${PREVIEW_SLOT_ID}-take-${previewTakeCount}`,
24
- mimeType: take.mimeType,
25
- durationMs: take.durationMs
26
- };
27
- }
28
- };
29
- function ActivityPreview({
30
- draft,
31
- renderMode = "practice",
32
- response,
33
- outcome,
34
- fallback,
35
- renderers,
36
- shuffleSeed,
37
- sanitizeHtml,
38
- strings,
39
- theme,
40
- locale
41
- }) {
42
- const s = chunkBIGGXHAW_cjs.useLkStrings(strings);
43
- const type = isRecord(draft) && typeof draft.type === "string" ? draft.type : void 0;
44
- const contentKey = contentKeyOf(draft);
45
- const result = react.useMemo(
46
- () => type === void 0 ? void 0 : lkCore.validateDraft(type, draft),
47
- [type, contentKey]
48
- );
49
- const activity = result?.status === "complete" ? result.data : void 0;
50
- const responseFits = response === void 0 || response.type === type;
51
- const responseKey = response === void 0 ? "none" : contentKeyOf(response);
52
- const marking = react.useMemo(() => {
53
- if (renderMode !== "review" || activity === void 0) {
54
- return void 0;
55
- }
56
- if (outcome !== void 0) {
57
- return { kind: "marked", outcome };
58
- }
59
- if (response === void 0 || !responseFits) {
60
- return void 0;
61
- }
62
- try {
63
- return { kind: "marked", outcome: lkCore.evaluate(activity, response) };
64
- } catch (thrown) {
65
- return {
66
- kind: "failed",
67
- error: thrown instanceof Error ? thrown : new Error(String(thrown))
68
- };
69
- }
70
- }, [renderMode, activity, outcome, responseKey, responseFits]);
71
- const activityId = activity?.id;
72
- const mediaBudget = react.useMemo(
73
- () => ({
74
- key: lkCore.slotMediaKey(PREVIEW_SLOT_ID),
75
- slotId: PREVIEW_SLOT_ID,
76
- index: 0,
77
- ...activityId !== void 0 ? { activityId } : {}
78
- }),
79
- [activityId]
80
- );
81
- if (type === void 0 || result === void 0) {
82
- throw new Error(
83
- "<ActivityPreview> needs a draft object with a string `type`, to know which activity to check and render."
84
- );
85
- }
86
- if (response !== void 0 && !responseFits) {
87
- throw new Error(
88
- `<ActivityPreview> was given a "${response.type}" response for a "${type}" activity.`
89
- );
90
- }
91
- if (result.status !== "complete") {
92
- if (fallback !== void 0) {
93
- return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: fallback(result) });
94
- }
95
- return /* @__PURE__ */ jsxRuntime.jsx(
96
- "div",
97
- {
98
- className: "lk-preview-unavailable",
99
- role: "note",
100
- "data-status": result.status,
101
- ...locale !== void 0 ? { lang: locale } : {},
102
- children: result.status === "invalid" ? s.previewInvalid : s.previewIncomplete
103
- }
104
- );
105
- }
106
- const data = result.data;
107
- const key = `${renderMode}:${responseKey}:${recordingKeyOf(data)}`;
108
- if (marking?.kind === "failed") {
109
- return /* @__PURE__ */ jsxRuntime.jsx(chunkJ3C72RPK_cjs.ActivityErrorBoundary, { activityTitle: data.title, strings: s, children: /* @__PURE__ */ jsxRuntime.jsx(Rethrow, { error: marking.error }) }, key);
110
- }
111
- const shared = {
112
- renderMode,
113
- // Without a binding a budgeted recording counts nothing in `practice`, and in
114
- // `exam` the media component refuses to render at all — rightly, on a real
115
- // paper, where nothing would persist the count. A preview persists nothing by
116
- // design, so it keeps the count in memory and says so.
117
- mediaBudget,
118
- ...sanitizeHtml !== void 0 ? { sanitizeHtml } : {},
119
- ...strings !== void 0 ? { strings } : {},
120
- ...theme !== void 0 ? { theme } : {},
121
- ...locale !== void 0 ? { locale } : {},
122
- // Seeded rather than controlled, in every mode, exactly as `<ActivitySequence>`
123
- // hands a restored answer to its slots — so a renderer written for the
124
- // sequence reads it here too. `review` is read-only either way, and `key`
125
- // above re-seeds whenever the response changes.
126
- ...response !== void 0 ? { defaultValue: response } : {},
127
- ...marking !== void 0 ? { outcome: marking.outcome } : {}
128
- };
129
- const Custom = renderers?.[data.type];
130
- if (Custom !== void 0) {
131
- return /* @__PURE__ */ jsxRuntime.jsx(Custom, { data, ...shared }, key);
132
- }
133
- switch (data.type) {
134
- case "multiple-choice":
135
- return /* @__PURE__ */ jsxRuntime.jsx(
136
- chunkYY4PFR3Z_cjs.MultipleChoice,
137
- {
138
- data,
139
- shuffleSeed: shuffleSeed ?? PREVIEW_SHUFFLE_SEED,
140
- ...shared
141
- },
142
- key
143
- );
144
- case "fill-in-the-blanks":
145
- return /* @__PURE__ */ jsxRuntime.jsx(chunkDS6HN3YR_cjs.FillInTheBlanks, { data, ...shared }, key);
146
- case "gap-select":
147
- return /* @__PURE__ */ jsxRuntime.jsx(
148
- chunk7ALBSPYR_cjs.GapSelect,
149
- {
150
- data,
151
- shuffleSeed: shuffleSeed ?? PREVIEW_SHUFFLE_SEED,
152
- ...shared
153
- },
154
- key
155
- );
156
- case "written-response":
157
- return /* @__PURE__ */ jsxRuntime.jsx(chunkMKXEM2C5_cjs.WrittenResponse, { data, ...shared }, key);
158
- case "dictation":
159
- return /* @__PURE__ */ jsxRuntime.jsx(chunkNAK55BYU_cjs.Dictation, { data, ...shared }, key);
160
- case "read-aloud":
161
- return /* @__PURE__ */ jsxRuntime.jsx(chunkTP5UMRAU_cjs.ReadAloud, { data, recordingBinding: PREVIEW_RECORDING, ...shared }, key);
162
- default:
163
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "lk-preview-unsupported", role: "note", children: s.unsupportedActivity });
164
- }
165
- }
166
- function Rethrow({ error }) {
167
- throw error;
168
- }
169
- function isRecord(value) {
170
- return typeof value === "object" && value !== null && !Array.isArray(value);
171
- }
172
- function recordingKeyOf(data) {
173
- const media = isRecord(data) ? data.media : void 0;
174
- const slowMedia = isRecord(data) ? data.slowMedia : void 0;
175
- const slowUrl = isRecord(slowMedia) ? slowMedia.url : void 0;
176
- if (!isRecord(media) && slowUrl === void 0) {
177
- return "";
178
- }
179
- return contentKeyOf([
180
- isRecord(media) ? media.type : void 0,
181
- isRecord(media) ? media.url : void 0,
182
- isRecord(media) ? media.playback : void 0,
183
- slowUrl
184
- ]);
185
- }
186
- var identities = /* @__PURE__ */ new WeakMap();
187
- var lastIdentity = 0;
188
- function identityKeyOf(value) {
189
- let identity = identities.get(value);
190
- if (identity === void 0) {
191
- lastIdentity += 1;
192
- identity = lastIdentity;
193
- identities.set(value, identity);
194
- }
195
- return `#${identity}`;
196
- }
197
- function contentKeyOf(value, enclosing = /* @__PURE__ */ new Set()) {
198
- switch (typeof value) {
199
- case "string":
200
- return JSON.stringify(value);
201
- case "number":
202
- return Object.is(value, -0) ? "-0" : String(value);
203
- case "bigint":
204
- return `${value}n`;
205
- case "boolean":
206
- case "undefined":
207
- return String(value);
208
- case "symbol":
209
- return `Symbol(${JSON.stringify(value.description ?? null)})`;
210
- case "function":
211
- return identityKeyOf(value);
212
- }
213
- if (value === null || typeof value !== "object") {
214
- return "null";
215
- }
216
- const prototype = Object.getPrototypeOf(value);
217
- const plain = Array.isArray(value) || prototype === Object.prototype || prototype === null;
218
- if (!plain || enclosing.has(value)) {
219
- return identityKeyOf(value);
220
- }
221
- enclosing.add(value);
222
- try {
223
- if (Array.isArray(value)) {
224
- return `[${Array.from(value, (item) => contentKeyOf(item, enclosing)).join(",")}]`;
225
- }
226
- const record = value;
227
- const fields = Object.keys(record).sort().map((field) => `${JSON.stringify(field)}:${contentKeyOf(record[field], enclosing)}`);
228
- return `{${fields.join(",")}}`;
229
- } finally {
230
- enclosing.delete(value);
231
- }
232
- }
233
-
234
- exports.ActivityPreview = ActivityPreview;
235
- //# sourceMappingURL=chunk-V6BA44DS.cjs.map
236
- //# sourceMappingURL=chunk-V6BA44DS.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/components/ActivityPreview/ActivityPreview.tsx"],"names":["useLkStrings","useMemo","validateDraft","evaluate","slotMediaKey","jsx","Fragment","ActivityErrorBoundary","MultipleChoice","FillInTheBlanks","GapSelect","WrittenResponse","Dictation","ReadAloud"],"mappings":"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","file":"chunk-V6BA44DS.cjs","sourcesContent":["'use client';\n\nimport {\n type ActivityType,\n type DraftNotComplete,\n evaluate,\n type ItemOutcome,\n type LearnerResponse,\n slotMediaKey,\n type ThemeTokens,\n validateDraft,\n} from '@intellectif/lk-core';\nimport { type ReactNode, useMemo } from 'react';\nimport { useLkStrings } from '../../i18n/LkIntlProvider.js';\nimport type { LkStringsOverride } from '../../i18n/strings.js';\nimport { ActivityErrorBoundary } from '../ActivityErrorBoundary.js';\nimport type { ActivityRenderer } from '../ActivitySequence/index.js';\nimport { Dictation } from '../Dictation/index.js';\nimport { FillInTheBlanks } from '../FillInTheBlanks/index.js';\nimport { GapSelect } from '../GapSelect/index.js';\nimport { MultipleChoice } from '../MultipleChoice/index.js';\nimport { ReadAloud } from '../ReadAloud/index.js';\nimport type { RecordingBinding } from '../ReadAloud/ReadAloud.js';\nimport type { HtmlSanitizer, MediaBudgetBinding, RenderMode } from '../types.js';\nimport { WrittenResponse } from '../WrittenResponse/index.js';\n\n/**\n * The option-shuffle seed a preview uses when none is given. Fixed, so a\n * shuffled question holds still while its author edits it instead of\n * re-ordering on every remount.\n */\nconst PREVIEW_SHUFFLE_SEED = 'lk-activity-preview';\n\n/**\n * The slot a previewed activity stands in for. A recording's play budget is\n * keyed to it and lives only in this mount: the preview counts plays so an\n * author hears the limit a learner gets, and records none of them.\n */\nconst PREVIEW_SLOT_ID = 'preview';\n\n/** Numbers the takes a preview mints keys for, so two are never the same take. */\nlet previewTakeCount = 0;\n\n/**\n * The store a previewed read-aloud records into: it keys the take so the whole\n * flow — record, play it back, submit — runs for the author, and keeps it\n * nowhere, like every other thing a preview does not record.\n *\n * It judges nothing, deliberately. `assess` is optional precisely so this can\n * exist: an author trying a reading out has no assessor to point at, and a\n * preview that demanded one would refuse to render the most common case. The\n * default `practice` mode then shows the \"assessment is not available\" notice,\n * which is the truth about this binding rather than a failure.\n */\nconst PREVIEW_RECORDING: RecordingBinding = {\n upload: async (take) => {\n previewTakeCount += 1;\n return {\n key: `${PREVIEW_SLOT_ID}-take-${previewTakeCount}`,\n mimeType: take.mimeType,\n durationMs: take.durationMs,\n };\n },\n};\n\nexport interface ActivityPreviewProps {\n /**\n * The activity as an editor holds it right now, finished or not. It is checked\n * with `validateDraft` before anything renders — by content, on every render —\n * so a half-written question never reaches a component that assumes a valid\n * one.\n */\n draft: unknown;\n /** The mode to preview in. Defaults to `practice`. */\n renderMode?: RenderMode;\n /**\n * A learner response to show as if a learner had given it. In `practice` and\n * `exam` it seeds the answer, which stays editable; in `review` it is the\n * submitted answer, marked by `evaluate()` unless `outcome` is given.\n */\n response?: LearnerResponse;\n /**\n * The outcome `review` marks the response with, instead of the one\n * `evaluate()` computes — which is how to preview a returned grade on a written\n * response, since the SDK cannot score one. Ignored in other modes.\n */\n outcome?: ItemOutcome;\n /**\n * Rendered in place of the activity while the draft is not complete, with the\n * issues `validateDraft` found — so an editor can list them in its own words.\n * The issue messages are English, and a preview that rendered them would put\n * untranslatable text on screen. Without it, the preview shows the\n * `previewIncomplete` or `previewInvalid` string.\n */\n fallback?: (result: DraftNotComplete) => ReactNode;\n /**\n * Renderers for activity types beyond the built-ins, keyed by `type`, as on\n * `<ActivitySequence>`. An entry receives the shared prop contract and\n * nothing else, so — like the shuffle seed below — the in-memory recording\n * binding a previewed read-aloud is given does not reach one. Nor does the\n * `question` a sequence hands a renderer: a preview is one question with no\n * set around it, so there is no slot to report to and nothing to page away\n * from. A renderer that uses it must treat it as optional, which its type\n * already says.\n */\n renderers?: Readonly<Record<string, ActivityRenderer>>;\n /**\n * Seeds `<MultipleChoice>`'s option shuffle. Defaults to a fixed seed, so the\n * order holds while options are edited; adding or removing one deals them\n * again, because the shuffle orders by position. A `renderers` entry is given\n * no seed, as on `<ActivitySequence>`.\n */\n shuffleSeed?: string;\n /** Renders author-supplied rich text. See `HtmlSanitizer`. */\n sanitizeHtml?: HtmlSanitizer;\n /** Overrides the SDK's chrome text, for the preview's notice and the activity alike. */\n strings?: LkStringsOverride;\n /** Per-instance token overrides, passed to the activity. */\n theme?: Partial<ThemeTokens>;\n /** BCP 47 tag of the interface language, passed to the activity. */\n locale?: string;\n}\n\ntype Marking = { kind: 'marked'; outcome: ItemOutcome } | { kind: 'failed'; error: Error };\n\n/**\n * Renders an activity from an editor's draft, in any render mode, with a\n * simulated learner response — and renders a notice instead while the draft is\n * not an activity yet.\n *\n * Nothing it renders is recorded: no callback is wired, so a practice attempt in\n * a preview scores locally and goes nowhere, and a recording's play limit is\n * counted in memory only.\n *\n * A draft with no string `type`, a type nobody registered, and a `response` of a\n * different type from the draft are wiring errors, and throw.\n */\nexport function ActivityPreview({\n draft,\n renderMode = 'practice',\n response,\n outcome,\n fallback,\n renderers,\n shuffleSeed,\n sanitizeHtml,\n strings,\n theme,\n locale,\n}: ActivityPreviewProps) {\n const s = useLkStrings(strings);\n const type = isRecord(draft) && typeof draft.type === 'string' ? draft.type : undefined;\n\n // Validated on the draft's CONTENT, not its identity. The activity components\n // restart whenever `data` changes identity, and an editor that rebuilds its\n // payload on every change would otherwise restart the question an author is\n // trying out each time anything else on the page was edited. The key is read\n // on every render, so a draft changed in place is checked again too.\n const contentKey = contentKeyOf(draft);\n // biome-ignore lint/correctness/useExhaustiveDependencies: the draft's content (contentKey) is the trigger, not its identity\n const result = useMemo(\n () => (type === undefined ? undefined : validateDraft(type as ActivityType, draft)),\n [type, contentKey],\n );\n const activity = result?.status === 'complete' ? result.data : undefined;\n const responseFits = response === undefined || response.type === type;\n const responseKey = response === undefined ? 'none' : contentKeyOf(response);\n\n // biome-ignore lint/correctness/useExhaustiveDependencies: the response's content (responseKey) is the trigger, not its identity\n const marking = useMemo((): Marking | undefined => {\n if (renderMode !== 'review' || activity === undefined) {\n return undefined;\n }\n if (outcome !== undefined) {\n return { kind: 'marked', outcome };\n }\n if (response === undefined || !responseFits) {\n return undefined;\n }\n try {\n return { kind: 'marked', outcome: evaluate(activity, response) };\n } catch (thrown) {\n // Scored here, during this component's own render, where no error boundary\n // inside the activity can catch it — so it is handed to one below.\n return {\n kind: 'failed',\n error: thrown instanceof Error ? thrown : new Error(String(thrown)),\n };\n }\n }, [renderMode, activity, outcome, responseKey, responseFits]);\n\n const activityId = activity?.id;\n const mediaBudget = useMemo<MediaBudgetBinding>(\n () => ({\n key: slotMediaKey(PREVIEW_SLOT_ID),\n slotId: PREVIEW_SLOT_ID,\n index: 0,\n ...(activityId !== undefined ? { activityId } : {}),\n }),\n [activityId],\n );\n\n // All hooks are called before these throws, so hook order stays stable.\n if (type === undefined || result === undefined) {\n throw new Error(\n '<ActivityPreview> needs a draft object with a string `type`, to know which activity to check and render.',\n );\n }\n // Checked against the draft's own type, whatever state the draft is in. A\n // mis-wired response is a wiring error however unfinished the draft is, and\n // waiting for the draft to be complete would throw on the very edit that\n // completes it.\n if (response !== undefined && !responseFits) {\n throw new Error(\n `<ActivityPreview> was given a \"${response.type}\" response for a \"${type}\" activity.`,\n );\n }\n if (result.status !== 'complete') {\n if (fallback !== undefined) {\n return <>{fallback(result)}</>;\n }\n return (\n <div\n className=\"lk-preview-unavailable\"\n role=\"note\"\n data-status={result.status}\n {...(locale !== undefined ? { lang: locale } : {})}\n >\n {result.status === 'invalid' ? s.previewInvalid : s.previewIncomplete}\n </div>\n );\n }\n const data = result.data;\n\n // A different simulated response, or a different mode, mounts the activity\n // afresh, play count included: a seeded answer is read at mount, and a practice\n // attempt the author submitted must not carry over into an exam preview. So\n // does a different recording or playback policy, which is a different listening\n // budget — the transport reads its count once, when it mounts.\n const key = `${renderMode}:${responseKey}:${recordingKeyOf(data)}`;\n\n if (marking?.kind === 'failed') {\n // The response is one the scorer cannot read, or a registered scorer threw.\n // Shown as the activity's error fallback, as a render failure would be.\n return (\n <ActivityErrorBoundary key={key} activityTitle={data.title} strings={s}>\n <Rethrow error={marking.error} />\n </ActivityErrorBoundary>\n );\n }\n\n const shared = {\n renderMode,\n // Without a binding a budgeted recording counts nothing in `practice`, and in\n // `exam` the media component refuses to render at all — rightly, on a real\n // paper, where nothing would persist the count. A preview persists nothing by\n // design, so it keeps the count in memory and says so.\n mediaBudget,\n ...(sanitizeHtml !== undefined ? { sanitizeHtml } : {}),\n ...(strings !== undefined ? { strings } : {}),\n ...(theme !== undefined ? { theme } : {}),\n ...(locale !== undefined ? { locale } : {}),\n // Seeded rather than controlled, in every mode, exactly as `<ActivitySequence>`\n // hands a restored answer to its slots — so a renderer written for the\n // sequence reads it here too. `review` is read-only either way, and `key`\n // above re-seeds whenever the response changes.\n ...(response !== undefined ? { defaultValue: response } : {}),\n ...(marking !== undefined ? { outcome: marking.outcome } : {}),\n };\n\n const Custom = renderers?.[data.type];\n if (Custom !== undefined) {\n return <Custom key={key} data={data} {...shared} />;\n }\n switch (data.type) {\n case 'multiple-choice':\n return (\n <MultipleChoice\n key={key}\n data={data}\n shuffleSeed={shuffleSeed ?? PREVIEW_SHUFFLE_SEED}\n {...shared}\n />\n );\n case 'fill-in-the-blanks':\n return <FillInTheBlanks key={key} data={data} {...shared} />;\n case 'gap-select':\n return (\n <GapSelect\n key={key}\n data={data}\n shuffleSeed={shuffleSeed ?? PREVIEW_SHUFFLE_SEED}\n {...shared}\n />\n );\n case 'written-response':\n return <WrittenResponse key={key} data={data} {...shared} />;\n case 'dictation':\n return <Dictation key={key} data={data} {...shared} />;\n case 'read-aloud':\n return <ReadAloud key={key} data={data} recordingBinding={PREVIEW_RECORDING} {...shared} />;\n default:\n return (\n <div className=\"lk-preview-unsupported\" role=\"note\">\n {s.unsupportedActivity}\n </div>\n );\n }\n}\n\n/** Throws `error` while rendering, so the error boundary around it shows its fallback. */\nfunction Rethrow({ error }: { error: Error }): never {\n throw error;\n}\n\nfunction isRecord(value: unknown): value is Readonly<Record<string, unknown>> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/**\n * The recording and its playback policy — not its description, which changes\n * no budget — plus the slower file a dictation or a read-aloud carries, which\n * follows the same policy and must remount when it is swapped for another.\n */\nfunction recordingKeyOf(data: unknown): string {\n const media = isRecord(data) ? data.media : undefined;\n const slowMedia = isRecord(data) ? data.slowMedia : undefined;\n const slowUrl = isRecord(slowMedia) ? slowMedia.url : undefined;\n if (!isRecord(media) && slowUrl === undefined) {\n return '';\n }\n return contentKeyOf([\n isRecord(media) ? media.type : undefined,\n isRecord(media) ? media.url : undefined,\n isRecord(media) ? media.playback : undefined,\n slowUrl,\n ]);\n}\n\nconst identities = new WeakMap<object, number>();\nlet lastIdentity = 0;\n\n/** A key for a value compared by identity: the same object always gets the same one. */\nfunction identityKeyOf(value: object): string {\n let identity = identities.get(value);\n if (identity === undefined) {\n lastIdentity += 1;\n identity = lastIdentity;\n identities.set(value, identity);\n }\n return `#${identity}`;\n}\n\n/**\n * A string that changes exactly when a value's content does.\n *\n * Object keys are sorted, because key order is not content: a draft saved to a\n * JSON column comes back with its keys reordered. Every value is written in a\n * form no other value shares — JSON would write a non-finite number as `null`\n * and drop `undefined`, and those decide different issues. What JSON does not\n * describe — an instance of a class, a function, a reference back to an\n * enclosing object — is compared by identity, and a symbol by its description.\n */\nfunction contentKeyOf(value: unknown, enclosing: Set<object> = new Set()): string {\n switch (typeof value) {\n case 'string':\n return JSON.stringify(value);\n case 'number':\n // `String` keeps NaN, the infinities and every other number apart; only -0\n // needs saying.\n return Object.is(value, -0) ? '-0' : String(value);\n case 'bigint':\n return `${value}n`;\n case 'boolean':\n case 'undefined':\n return String(value);\n case 'symbol':\n return `Symbol(${JSON.stringify(value.description ?? null)})`;\n case 'function':\n return identityKeyOf(value);\n }\n // Every other `typeof` has returned above: this is `null`, or an object.\n if (value === null || typeof value !== 'object') {\n return 'null';\n }\n const prototype = Object.getPrototypeOf(value);\n const plain = Array.isArray(value) || prototype === Object.prototype || prototype === null;\n if (!plain || enclosing.has(value)) {\n return identityKeyOf(value);\n }\n enclosing.add(value);\n try {\n if (Array.isArray(value)) {\n return `[${Array.from(value, (item) => contentKeyOf(item, enclosing)).join(',')}]`;\n }\n const record = value as Record<string, unknown>;\n const fields = Object.keys(record)\n .sort()\n .map((field) => `${JSON.stringify(field)}:${contentKeyOf(record[field], enclosing)}`);\n return `{${fields.join(',')}}`;\n } finally {\n enclosing.delete(value);\n }\n}\n"]}