@intellectif/lk-react 23.0.1 → 23.1.1

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 (369) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/README.md +13 -9
  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/ReadAloud-C8RhaUqV.js.map +1 -0
  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-BRN1tj4n.cjs +187 -0
  64. package/dist/ThemeProvider-BRN1tj4n.cjs.map +1 -0
  65. package/dist/ThemeProvider-C7K9iMLX.js +158 -0
  66. package/dist/ThemeProvider-C7K9iMLX.js.map +1 -0
  67. package/dist/ThemeProvider-CocC8ODJ.d.ts +62 -0
  68. package/dist/ThemeProvider-CocC8ODJ.d.ts.map +1 -0
  69. package/dist/ThemeProvider-DS4KOL86.d.cts +62 -0
  70. package/dist/ThemeProvider-DS4KOL86.d.cts.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 -73
  78. package/dist/ai/LkAiProvider.d.ts +2 -73
  79. package/dist/ai/LkAiProvider.js +33 -2
  80. package/dist/ai/LkAiProvider.js.map +1 -1
  81. package/dist/ai/useAiHelp.cjs +7 -23
  82. package/dist/ai/useAiHelp.d.cts +198 -121
  83. package/dist/ai/useAiHelp.d.cts.map +1 -0
  84. package/dist/ai/useAiHelp.d.ts +198 -121
  85. package/dist/ai/useAiHelp.d.ts.map +1 -0
  86. package/dist/ai/useAiHelp.js +3 -6
  87. package/dist/components/ActivityPreview.cjs +3 -31
  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 -22
  91. package/dist/components/ActivitySequence.cjs +3 -32
  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 -23
  95. package/dist/components/Dictation.cjs +4 -23
  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 -14
  99. package/dist/components/FillInTheBlanks.cjs +4 -22
  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 -13
  103. package/dist/components/GapSelect.cjs +4 -22
  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 -13
  107. package/dist/components/InteractiveVideo.cjs +5 -34
  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 -21
  111. package/dist/components/MultipleChoice.cjs +4 -22
  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 -13
  115. package/dist/components/PronunciationFeedback.cjs +4 -16
  116. package/dist/components/PronunciationFeedback.d.cts +2 -69
  117. package/dist/components/PronunciationFeedback.d.ts +2 -69
  118. package/dist/components/PronunciationFeedback.js +3 -7
  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 -21
  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 -12
  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-2iDJohip.d.cts +90 -0
  164. package/dist/index-2iDJohip.d.cts.map +1 -0
  165. package/dist/index-BAqT5tXN.d.ts +90 -0
  166. package/dist/index-BAqT5tXN.d.ts.map +1 -0
  167. package/dist/index-BRbEfXgE.d.ts +78 -0
  168. package/dist/index-BRbEfXgE.d.ts.map +1 -0
  169. package/dist/index-BW7aA5eN.d.cts +25 -0
  170. package/dist/index-BW7aA5eN.d.cts.map +1 -0
  171. package/dist/index-BeiVd-qe.d.ts +124 -0
  172. package/dist/index-BeiVd-qe.d.ts.map +1 -0
  173. package/dist/index-C1KGpD1k.d.ts +49 -0
  174. package/dist/index-C1KGpD1k.d.ts.map +1 -0
  175. package/dist/index-C7cCHEze.d.cts +24 -0
  176. package/dist/index-C7cCHEze.d.cts.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-CN3_TwDL.d.ts +28 -0
  180. package/dist/index-CN3_TwDL.d.ts.map +1 -0
  181. package/dist/index-CNoAqj5I.d.ts +16 -0
  182. package/dist/index-CNoAqj5I.d.ts.map +1 -0
  183. package/dist/index-CTlht6OV.d.cts +432 -0
  184. package/dist/index-CTlht6OV.d.cts.map +1 -0
  185. package/dist/index-CmgSzapt.d.cts +124 -0
  186. package/dist/index-CmgSzapt.d.cts.map +1 -0
  187. package/dist/index-D4WYDN_p.d.cts +28 -0
  188. package/dist/index-D4WYDN_p.d.cts.map +1 -0
  189. package/dist/index-DAJ6KK49.d.ts +25 -0
  190. package/dist/index-DAJ6KK49.d.ts.map +1 -0
  191. package/dist/index-DIiacP_z.d.cts +78 -0
  192. package/dist/index-DIiacP_z.d.cts.map +1 -0
  193. package/dist/index-DR0SOJig.d.ts +291 -0
  194. package/dist/index-DR0SOJig.d.ts.map +1 -0
  195. package/dist/index-DRMauOVH.d.ts +432 -0
  196. package/dist/index-DRMauOVH.d.ts.map +1 -0
  197. package/dist/index-bmmRfGad.d.cts +16 -0
  198. package/dist/index-bmmRfGad.d.cts.map +1 -0
  199. package/dist/index-n9PKZm7W.d.cts +291 -0
  200. package/dist/index-n9PKZm7W.d.cts.map +1 -0
  201. package/dist/index-zu9H7owf.d.ts +24 -0
  202. package/dist/index-zu9H7owf.d.ts.map +1 -0
  203. package/dist/index.cjs +118 -161
  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 -31
  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/theme/skin.css +46 -1
  214. package/dist/tries-CbbtZkdG.cjs +363 -0
  215. package/dist/{chunk-W6UMY7PF.cjs.map → tries-CbbtZkdG.cjs.map} +1 -1
  216. package/dist/tries-DTHSrLCm.js +310 -0
  217. package/dist/tries-DTHSrLCm.js.map +1 -0
  218. package/dist/types-BOQd93x3.d.cts +994 -0
  219. package/dist/types-BOQd93x3.d.cts.map +1 -0
  220. package/dist/types-HIrgF_mS.d.ts +994 -0
  221. package/dist/types-HIrgF_mS.d.ts.map +1 -0
  222. package/dist/useAiHelp-B-ccDL5Q.js +906 -0
  223. package/dist/useAiHelp-B-ccDL5Q.js.map +1 -0
  224. package/dist/useAiHelp-h9yOLFqt.cjs +995 -0
  225. package/dist/useAiHelp-h9yOLFqt.cjs.map +1 -0
  226. package/dist/useSpeechRecorder--O_f-xvt.cjs +720 -0
  227. package/dist/useSpeechRecorder--O_f-xvt.cjs.map +1 -0
  228. package/dist/useSpeechRecorder-DtAYvl02.d.cts +154 -0
  229. package/dist/useSpeechRecorder-DtAYvl02.d.cts.map +1 -0
  230. package/dist/useSpeechRecorder-DtAYvl02.d.ts +154 -0
  231. package/dist/useSpeechRecorder-DtAYvl02.d.ts.map +1 -0
  232. package/dist/useSpeechRecorder-zIi9Dlw8.js +709 -0
  233. package/dist/useSpeechRecorder-zIi9Dlw8.js.map +1 -0
  234. package/package.json +10 -11
  235. package/dist/WrittenResponse-CgRrjM4S.d.ts +0 -115
  236. package/dist/WrittenResponse-D2K1VW_V.d.cts +0 -115
  237. package/dist/ai/useAiHelp.cjs.map +0 -1
  238. package/dist/ai/useAiHelp.js.map +0 -1
  239. package/dist/chunk-2OP5YHRR.js +0 -543
  240. package/dist/chunk-2OP5YHRR.js.map +0 -1
  241. package/dist/chunk-2TW6WLBC.js +0 -80
  242. package/dist/chunk-2TW6WLBC.js.map +0 -1
  243. package/dist/chunk-4MXECBZT.js +0 -888
  244. package/dist/chunk-4MXECBZT.js.map +0 -1
  245. package/dist/chunk-54DX6SLS.cjs +0 -42
  246. package/dist/chunk-54DX6SLS.cjs.map +0 -1
  247. package/dist/chunk-6GQLCCGN.js +0 -858
  248. package/dist/chunk-6GQLCCGN.js.map +0 -1
  249. package/dist/chunk-6N43WDVG.js +0 -29
  250. package/dist/chunk-6N43WDVG.js.map +0 -1
  251. package/dist/chunk-6RIM5EQP.cjs +0 -393
  252. package/dist/chunk-6RIM5EQP.cjs.map +0 -1
  253. package/dist/chunk-7F2YQWMW.cjs +0 -553
  254. package/dist/chunk-7F2YQWMW.cjs.map +0 -1
  255. package/dist/chunk-B4JRBV56.cjs +0 -63
  256. package/dist/chunk-B4JRBV56.cjs.map +0 -1
  257. package/dist/chunk-B7JU52YQ.cjs +0 -437
  258. package/dist/chunk-B7JU52YQ.cjs.map +0 -1
  259. package/dist/chunk-C4PSJ3ZC.js +0 -329
  260. package/dist/chunk-C4PSJ3ZC.js.map +0 -1
  261. package/dist/chunk-CXIP7ERK.js +0 -389
  262. package/dist/chunk-CXIP7ERK.js.map +0 -1
  263. package/dist/chunk-DPNCCWOV.js +0 -395
  264. package/dist/chunk-DPNCCWOV.js.map +0 -1
  265. package/dist/chunk-DXFCYNST.js +0 -35
  266. package/dist/chunk-DXFCYNST.js.map +0 -1
  267. package/dist/chunk-DZ7VLLDQ.cjs +0 -891
  268. package/dist/chunk-DZ7VLLDQ.cjs.map +0 -1
  269. package/dist/chunk-E4UF4XZM.js +0 -29
  270. package/dist/chunk-E4UF4XZM.js.map +0 -1
  271. package/dist/chunk-ERV666AR.js +0 -435
  272. package/dist/chunk-ERV666AR.js.map +0 -1
  273. package/dist/chunk-FISEZBLF.cjs +0 -31
  274. package/dist/chunk-FISEZBLF.cjs.map +0 -1
  275. package/dist/chunk-HYOUMTPW.js +0 -597
  276. package/dist/chunk-HYOUMTPW.js.map +0 -1
  277. package/dist/chunk-HZIT24BK.js +0 -61
  278. package/dist/chunk-HZIT24BK.js.map +0 -1
  279. package/dist/chunk-IYGF7PZY.cjs +0 -2938
  280. package/dist/chunk-IYGF7PZY.cjs.map +0 -1
  281. package/dist/chunk-J2QIHZ3H.cjs +0 -27
  282. package/dist/chunk-J2QIHZ3H.cjs.map +0 -1
  283. package/dist/chunk-JMEZYYCE.js +0 -2935
  284. package/dist/chunk-JMEZYYCE.js.map +0 -1
  285. package/dist/chunk-K63VPFOV.cjs +0 -134
  286. package/dist/chunk-K63VPFOV.cjs.map +0 -1
  287. package/dist/chunk-KI6DJ5ZF.js +0 -495
  288. package/dist/chunk-KI6DJ5ZF.js.map +0 -1
  289. package/dist/chunk-MCO52PV5.js +0 -520
  290. package/dist/chunk-MCO52PV5.js.map +0 -1
  291. package/dist/chunk-MXZHL4U5.cjs +0 -860
  292. package/dist/chunk-MXZHL4U5.cjs.map +0 -1
  293. package/dist/chunk-NFGUJ52G.cjs +0 -336
  294. package/dist/chunk-NFGUJ52G.cjs.map +0 -1
  295. package/dist/chunk-O7MINPKZ.js +0 -431
  296. package/dist/chunk-O7MINPKZ.js.map +0 -1
  297. package/dist/chunk-OD65OZIW.cjs +0 -159
  298. package/dist/chunk-OD65OZIW.cjs.map +0 -1
  299. package/dist/chunk-OJGTJOWI.cjs +0 -20
  300. package/dist/chunk-OJGTJOWI.cjs.map +0 -1
  301. package/dist/chunk-PGAPZ6UE.cjs +0 -497
  302. package/dist/chunk-PGAPZ6UE.cjs.map +0 -1
  303. package/dist/chunk-PNNUPUH4.cjs +0 -82
  304. package/dist/chunk-PNNUPUH4.cjs.map +0 -1
  305. package/dist/chunk-QAZO3ANW.cjs +0 -433
  306. package/dist/chunk-QAZO3ANW.cjs.map +0 -1
  307. package/dist/chunk-RX3A6KIY.js +0 -17
  308. package/dist/chunk-RX3A6KIY.js.map +0 -1
  309. package/dist/chunk-SHDSOOBF.cjs +0 -826
  310. package/dist/chunk-SHDSOOBF.cjs.map +0 -1
  311. package/dist/chunk-SPSKDNEE.js +0 -824
  312. package/dist/chunk-SPSKDNEE.js.map +0 -1
  313. package/dist/chunk-TGTP5DKF.js +0 -234
  314. package/dist/chunk-TGTP5DKF.js.map +0 -1
  315. package/dist/chunk-U4KTOH7C.js +0 -22
  316. package/dist/chunk-U4KTOH7C.js.map +0 -1
  317. package/dist/chunk-U5VFJVQ2.js +0 -241
  318. package/dist/chunk-U5VFJVQ2.js.map +0 -1
  319. package/dist/chunk-UDCQTOVU.cjs +0 -523
  320. package/dist/chunk-UDCQTOVU.cjs.map +0 -1
  321. package/dist/chunk-UHKZXOXD.js +0 -128
  322. package/dist/chunk-UHKZXOXD.js.map +0 -1
  323. package/dist/chunk-UKUHVCZX.cjs +0 -599
  324. package/dist/chunk-UKUHVCZX.cjs.map +0 -1
  325. package/dist/chunk-VPU6UY4O.cjs +0 -55
  326. package/dist/chunk-VPU6UY4O.cjs.map +0 -1
  327. package/dist/chunk-W6UMY7PF.cjs +0 -251
  328. package/dist/chunk-WZ2TBXVB.cjs +0 -236
  329. package/dist/chunk-WZ2TBXVB.cjs.map +0 -1
  330. package/dist/chunk-XOR4MPEN.cjs +0 -31
  331. package/dist/chunk-XOR4MPEN.cjs.map +0 -1
  332. package/dist/chunk-XS6OYYVR.cjs +0 -397
  333. package/dist/chunk-XS6OYYVR.cjs.map +0 -1
  334. package/dist/chunk-XYJBRKBZ.js +0 -51
  335. package/dist/chunk-XYJBRKBZ.js.map +0 -1
  336. package/dist/chunk-YORQQXGO.js +0 -155
  337. package/dist/chunk-YORQQXGO.js.map +0 -1
  338. package/dist/components/ActivityPreview.cjs.map +0 -1
  339. package/dist/components/ActivityPreview.js.map +0 -1
  340. package/dist/components/ActivitySequence.cjs.map +0 -1
  341. package/dist/components/ActivitySequence.js.map +0 -1
  342. package/dist/components/Dictation.cjs.map +0 -1
  343. package/dist/components/Dictation.js.map +0 -1
  344. package/dist/components/FillInTheBlanks.cjs.map +0 -1
  345. package/dist/components/FillInTheBlanks.js.map +0 -1
  346. package/dist/components/GapSelect.cjs.map +0 -1
  347. package/dist/components/GapSelect.js.map +0 -1
  348. package/dist/components/InteractiveVideo.cjs.map +0 -1
  349. package/dist/components/InteractiveVideo.js.map +0 -1
  350. package/dist/components/MultipleChoice.cjs.map +0 -1
  351. package/dist/components/MultipleChoice.js.map +0 -1
  352. package/dist/components/PronunciationFeedback.cjs.map +0 -1
  353. package/dist/components/PronunciationFeedback.js.map +0 -1
  354. package/dist/components/ReadAloud.cjs.map +0 -1
  355. package/dist/components/ReadAloud.js.map +0 -1
  356. package/dist/components/StimulusPanel.cjs.map +0 -1
  357. package/dist/components/StimulusPanel.js.map +0 -1
  358. package/dist/components/WrittenResponse.cjs.map +0 -1
  359. package/dist/components/WrittenResponse.js.map +0 -1
  360. package/dist/hooks/useSpeechRecorder.cjs.map +0 -1
  361. package/dist/hooks/useSpeechRecorder.js.map +0 -1
  362. package/dist/i18n/LkIntlProvider.cjs.map +0 -1
  363. package/dist/i18n/LkIntlProvider.js.map +0 -1
  364. package/dist/machine-CuwKqWXC.d.cts +0 -26
  365. package/dist/machine-CuwKqWXC.d.ts +0 -26
  366. package/dist/theme/ThemeProvider.cjs.map +0 -1
  367. package/dist/theme/ThemeProvider.js.map +0 -1
  368. package/dist/types-CO0XLEN6.d.cts +0 -973
  369. package/dist/types-CrtIcRcK.d.ts +0 -973
@@ -1,2935 +0,0 @@
1
- 'use client';
2
- import { MultipleChoice } from './chunk-O7MINPKZ.js';
3
- import { FillInTheBlanks } from './chunk-KI6DJ5ZF.js';
4
- import { GapSelect } from './chunk-ERV666AR.js';
5
- import { Dictation } from './chunk-6GQLCCGN.js';
6
- import { useLearnerAi } from './chunk-RX3A6KIY.js';
7
- import { stopCaptureGroup, ReadAloud } from './chunk-4MXECBZT.js';
8
- import { SequenceSlotContext } from './chunk-U4KTOH7C.js';
9
- import { ActivityErrorBoundary } from './chunk-E4UF4XZM.js';
10
- import { isDevelopment } from './chunk-DXFCYNST.js';
11
- import { useLkStrings } from './chunk-C4PSJ3ZC.js';
12
- import { resolveDeliveryPolicy, resolveItemScoringPolicy, assertRedactedItemGroup, validateItemGroup, ActivitySchemaError, flattenSequence, readMediaProgress } from '@intellectif/lk-core';
13
- import { useMemo, useRef, useState, useEffect, useCallback } from 'react';
14
- import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
15
-
16
- // src/components/InteractiveVideo/format.ts
17
- var SPEEDS = [0.5, 0.75, 1, 1.25, 1.5, 1.75, 2, 2.5];
18
- function clock(value) {
19
- const total = Number.isFinite(value) && value > 0 ? Math.floor(value) : 0;
20
- const hours = Math.floor(total / 3600);
21
- const minutes = Math.floor(total % 3600 / 60);
22
- const secs = total % 60;
23
- const pad = (n) => String(n).padStart(2, "0");
24
- return hours > 0 ? `${hours}:${pad(minutes)}:${pad(secs)}` : `${minutes}:${pad(secs)}`;
25
- }
26
- function nudgeSpeed(current, direction) {
27
- const at = SPEEDS.indexOf(current);
28
- const from = at === -1 ? SPEEDS.indexOf(1) : at;
29
- return SPEEDS[Math.min(SPEEDS.length - 1, Math.max(0, from + direction))];
30
- }
31
- function Icon({ children, filled = false }) {
32
- return /* @__PURE__ */ jsx(
33
- "svg",
34
- {
35
- className: "lk-iv-icon",
36
- viewBox: "0 0 24 24",
37
- width: "20",
38
- height: "20",
39
- fill: filled ? "currentColor" : "none",
40
- stroke: "currentColor",
41
- strokeWidth: "1.8",
42
- strokeLinecap: "round",
43
- strokeLinejoin: "round",
44
- "aria-hidden": "true",
45
- focusable: "false",
46
- children
47
- }
48
- );
49
- }
50
- var PlayIcon = () => /* @__PURE__ */ jsx(Icon, { filled: true, children: /* @__PURE__ */ jsx("path", { d: "M8 5.5v13a1 1 0 0 0 1.5.86l10.2-6.5a1 1 0 0 0 0-1.72L9.5 4.64A1 1 0 0 0 8 5.5Z" }) });
51
- var PauseIcon = () => /* @__PURE__ */ jsxs(Icon, { filled: true, children: [
52
- /* @__PURE__ */ jsx("rect", { x: "6.5", y: "5", width: "3.5", height: "14", rx: "1" }),
53
- /* @__PURE__ */ jsx("rect", { x: "14", y: "5", width: "3.5", height: "14", rx: "1" })
54
- ] });
55
- var ReplayIcon = () => /* @__PURE__ */ jsxs(Icon, { children: [
56
- /* @__PURE__ */ jsx("path", { d: "M4 12a8 8 0 1 0 2.4-5.7" }),
57
- /* @__PURE__ */ jsx("path", { d: "M4 4v4h4" })
58
- ] });
59
- var BackIcon = () => /* @__PURE__ */ jsxs(Icon, { children: [
60
- /* @__PURE__ */ jsx("path", { d: "M11 6.5 5.5 12l5.5 5.5" }),
61
- /* @__PURE__ */ jsx("path", { d: "M18.5 6.5 13 12l5.5 5.5" })
62
- ] });
63
- var ForwardIcon = () => /* @__PURE__ */ jsxs(Icon, { children: [
64
- /* @__PURE__ */ jsx("path", { d: "m13 6.5 5.5 5.5-5.5 5.5" }),
65
- /* @__PURE__ */ jsx("path", { d: "M5.5 6.5 11 12l-5.5 5.5" })
66
- ] });
67
- var VolumeIcon = ({ level }) => /* @__PURE__ */ jsxs(Icon, { children: [
68
- /* @__PURE__ */ jsx("path", { d: "M4 9.5v5h3.5L12 18.5v-13L7.5 9.5H4Z" }),
69
- level === "muted" ? /* @__PURE__ */ jsx("path", { d: "m16 9.5 5 5m0-5-5 5" }) : /* @__PURE__ */ jsxs(Fragment, { children: [
70
- /* @__PURE__ */ jsx("path", { d: "M15.5 9.5a3.5 3.5 0 0 1 0 5" }),
71
- level === "high" ? /* @__PURE__ */ jsx("path", { d: "M18 7a7 7 0 0 1 0 10" }) : null
72
- ] })
73
- ] });
74
- var CaptionsIcon = ({ on }) => /* @__PURE__ */ jsxs(Icon, { children: [
75
- /* @__PURE__ */ jsx("rect", { x: "3", y: "5.5", width: "18", height: "13", rx: "2.5" }),
76
- /* @__PURE__ */ jsx("path", { d: "M10 10.5a2 2 0 1 0 0 3M16 10.5a2 2 0 1 0 0 3" }),
77
- on ? null : /* @__PURE__ */ jsx("path", { d: "m4 20 16-16" })
78
- ] });
79
- var SettingsIcon = () => /* @__PURE__ */ jsxs(Icon, { children: [
80
- /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "3" }),
81
- /* @__PURE__ */ jsx("path", { d: "M12 3.5v2M12 18.5v2M20.5 12h-2M5.5 12h-2M18 6l-1.4 1.4M7.4 16.6 6 18M18 18l-1.4-1.4M7.4 7.4 6 6" })
82
- ] });
83
- var ContentsIcon = () => /* @__PURE__ */ jsxs(Icon, { children: [
84
- /* @__PURE__ */ jsx("path", { d: "M9 6.5h11M9 12h11M9 17.5h11" }),
85
- /* @__PURE__ */ jsx("path", { d: "M4.5 6.5h.01M4.5 12h.01M4.5 17.5h.01" })
86
- ] });
87
- var PictureInPictureIcon = () => /* @__PURE__ */ jsxs(Icon, { children: [
88
- /* @__PURE__ */ jsx("rect", { x: "3", y: "5", width: "18", height: "14", rx: "2" }),
89
- /* @__PURE__ */ jsx("rect", { x: "12", y: "11.5", width: "6.5", height: "5", rx: "1" })
90
- ] });
91
- var FullscreenIcon = ({ on }) => /* @__PURE__ */ jsx(Icon, { children: on ? /* @__PURE__ */ jsx("path", { d: "M9 4v3a2 2 0 0 1-2 2H4M20 9h-3a2 2 0 0 1-2-2V4M4 15h3a2 2 0 0 1 2 2v3M15 20v-3a2 2 0 0 1 2-2h3" }) : /* @__PURE__ */ jsx("path", { d: "M4 9V6a2 2 0 0 1 2-2h3M15 4h3a2 2 0 0 1 2 2v3M20 15v3a2 2 0 0 1-2 2h-3M9 20H6a2 2 0 0 1-2-2v-3" }) });
92
- var CheckIcon = () => /* @__PURE__ */ jsx(Icon, { children: /* @__PURE__ */ jsx("path", { d: "m5 12.5 4.5 4.5L19 7.5" }) });
93
- var ChevronIcon = ({ direction }) => /* @__PURE__ */ jsx(Icon, { children: /* @__PURE__ */ jsx("path", { d: direction === "right" ? "m9.5 6 6 6-6 6" : "m14.5 6-6 6 6 6" }) });
94
- var CloseIcon = () => /* @__PURE__ */ jsx(Icon, { children: /* @__PURE__ */ jsx("path", { d: "m6.5 6.5 11 11m0-11-11 11" }) });
95
- var AlertIcon = () => /* @__PURE__ */ jsxs(Icon, { children: [
96
- /* @__PURE__ */ jsx("path", { d: "M12 4 2.8 19.5h18.4L12 4Z" }),
97
- /* @__PURE__ */ jsx("path", { d: "M12 10v4M12 17h.01" })
98
- ] });
99
-
100
- // src/components/InteractiveVideo/shortcuts.ts
101
- var SHORTCUT_KEYS = [
102
- { action: "play-pause", keys: "Space / K" },
103
- { action: "jump-10", keys: "J / L" },
104
- { action: "jump-5", keys: "\u2190 / \u2192" },
105
- { action: "jump-1", keys: "Shift + \u2190 / \u2192" },
106
- { action: "frame", keys: ", / ." },
107
- { action: "speed", keys: "< / >" },
108
- { action: "percent", keys: "0 \u2013 9" },
109
- { action: "start-end", keys: "Home / End" },
110
- { action: "chapter", keys: "[ / ]" },
111
- { action: "mute", keys: "M" },
112
- { action: "captions", keys: "C" },
113
- { action: "secondary-captions", keys: "Shift + C" },
114
- { action: "transcript", keys: "T" },
115
- { action: "picture-in-picture", keys: "P" },
116
- { action: "fullscreen", keys: "F" },
117
- { action: "shortcut-list", keys: "?" }
118
- ];
119
- var JUMP_SECONDS = 10;
120
- var FRAME_SECONDS = 1 / 30;
121
-
122
- // src/components/InteractiveVideo/tracks.ts
123
- var primarySubtag = (tag) => (tag.split("-")[0] ?? tag).toLowerCase();
124
- function preferCaptions(tracks) {
125
- return tracks.find((track) => track.kind === "captions") ?? tracks[0];
126
- }
127
- function matchLanguage(tracks, language) {
128
- const wanted = language.toLowerCase();
129
- const exact = tracks.filter((track) => track.srclang.toLowerCase() === wanted);
130
- if (exact.length > 0) {
131
- return preferCaptions(exact);
132
- }
133
- const family = primarySubtag(wanted);
134
- return preferCaptions(tracks.filter((track) => primarySubtag(track.srclang) === family));
135
- }
136
- function drawable(tracks) {
137
- return tracks.filter((track) => track.kind === "captions" || track.kind === "subtitles");
138
- }
139
- function secondaryCandidates(tracks, primary) {
140
- const taken = primary?.srclang.toLowerCase();
141
- return drawable(tracks).filter((track) => track.srclang.toLowerCase() !== taken);
142
- }
143
- function resolveCaptionTracks(tracks, preferences) {
144
- const usable = drawable(tracks);
145
- if (usable.length === 0) {
146
- return {};
147
- }
148
- const primary = (preferences.captionLanguage === null ? void 0 : matchLanguage(usable, preferences.captionLanguage)) ?? usable.find((track) => track.default === true) ?? usable[0];
149
- const second = preferences.secondaryCaptionLanguage;
150
- const secondary = second === null ? void 0 : matchLanguage(secondaryCandidates(usable, primary), second);
151
- return secondary === void 0 ? { primary } : { primary, secondary };
152
- }
153
- var overlap = (a, b) => Math.min(a.end, b.end) - Math.max(a.start, b.start);
154
- var LOOKBACK = 8;
155
- function pairSecondaryCues(primary, secondary) {
156
- const rows = primary.map(() => []);
157
- for (const cue of secondary) {
158
- let low = 0;
159
- let high = primary.length;
160
- while (low < high) {
161
- const middle = low + high >> 1;
162
- if (primary[middle].start < cue.end) {
163
- low = middle + 1;
164
- } else {
165
- high = middle;
166
- }
167
- }
168
- let best = -1;
169
- let bestShare = 0;
170
- let misses = 0;
171
- for (let index = low - 1; index >= 0 && misses <= LOOKBACK; index -= 1) {
172
- const share = overlap(primary[index], cue);
173
- if (share > 0) {
174
- misses = 0;
175
- if (share >= bestShare) {
176
- best = index;
177
- bestShare = share;
178
- }
179
- } else {
180
- misses += 1;
181
- }
182
- }
183
- if (best !== -1) {
184
- rows[best].push(cue);
185
- }
186
- }
187
- return rows;
188
- }
189
- function Menu({
190
- label,
191
- onClose,
192
- children,
193
- returnTo
194
- }) {
195
- const ref = useRef(null);
196
- useEffect(() => {
197
- const items = ref.current?.querySelectorAll('[role^="menuitem"]');
198
- const back = returnTo === void 0 ? null : ref.current?.querySelector(`[data-menu-key="${returnTo}"]`);
199
- const checked = ref.current?.querySelector('[aria-checked="true"]');
200
- (back ?? checked ?? items?.[0])?.focus();
201
- }, [returnTo]);
202
- const onKeyDown = (event) => {
203
- const items = Array.from(
204
- ref.current?.querySelectorAll('[role^="menuitem"]') ?? []
205
- );
206
- const at = items.indexOf(document.activeElement);
207
- const move = (to) => {
208
- event.preventDefault();
209
- items[(to + items.length) % items.length]?.focus();
210
- };
211
- if (event.key === "ArrowDown") {
212
- move(at + 1);
213
- } else if (event.key === "ArrowUp") {
214
- move(at - 1);
215
- } else if (event.key === "Home") {
216
- move(0);
217
- } else if (event.key === "End") {
218
- move(items.length - 1);
219
- } else if (event.key === "Escape") {
220
- event.preventDefault();
221
- event.stopPropagation();
222
- onClose();
223
- }
224
- event.stopPropagation();
225
- };
226
- return /* @__PURE__ */ jsx("div", { ref, className: "lk-iv-menu", role: "menu", "aria-label": label, onKeyDown, children });
227
- }
228
- function SpeedMenu({
229
- speed,
230
- strings,
231
- onChoose,
232
- onClose
233
- }) {
234
- return /* @__PURE__ */ jsx(Menu, { label: strings.media.speed, onClose, children: SPEEDS.map((rate) => /* @__PURE__ */ jsxs(
235
- "button",
236
- {
237
- type: "button",
238
- role: "menuitemradio",
239
- "aria-checked": rate === speed,
240
- className: "lk-iv-menu-item",
241
- onClick: () => onChoose(rate),
242
- children: [
243
- /* @__PURE__ */ jsx("span", { children: strings.videoSpeedValue(rate) }),
244
- rate === speed ? /* @__PURE__ */ jsx(CheckIcon, {}) : null
245
- ]
246
- },
247
- rate
248
- )) });
249
- }
250
- function SettingsMenu({
251
- preferences,
252
- tracks,
253
- primaryTrack,
254
- secondaryTrack,
255
- captionsFailed,
256
- strings,
257
- onChange,
258
- onCaptionTrack,
259
- onSecondaryTrack,
260
- onShowShortcuts,
261
- onClose
262
- }) {
263
- const [page, setPage] = useState("main");
264
- const [returnTo, setReturnTo] = useState(void 0);
265
- const open = (next) => {
266
- setReturnTo(void 0);
267
- setPage(next);
268
- };
269
- const leave = () => {
270
- setReturnTo(page);
271
- setPage(page === "second" ? "captions" : "main");
272
- };
273
- const captionsValue = !preferences.captions || primaryTrack === void 0 ? strings.videoOff : secondaryTrack === void 0 ? primaryTrack.label : strings.videoCaptionPair(primaryTrack.label, secondaryTrack.label);
274
- const partners = secondaryCandidates(tracks, primaryTrack);
275
- const failedNotes = /* @__PURE__ */ jsxs(Fragment, { children: [
276
- captionsFailed.primary && page === "captions" ? /* @__PURE__ */ jsx("p", { className: "lk-iv-menu-note", role: "status", children: strings.videoCaptionsFailed }) : null,
277
- captionsFailed.secondary ? /* @__PURE__ */ jsx("p", { className: "lk-iv-menu-note", role: "status", children: strings.videoSecondaryCaptionsFailed }) : null
278
- ] });
279
- const back = /* @__PURE__ */ jsxs(
280
- "button",
281
- {
282
- type: "button",
283
- role: "menuitem",
284
- className: "lk-iv-menu-item lk-iv-menu-back",
285
- onClick: leave,
286
- children: [
287
- /* @__PURE__ */ jsx(ChevronIcon, { direction: "left" }),
288
- /* @__PURE__ */ jsx("span", { children: page === "captions" ? strings.videoCaptionLanguage : page === "second" ? strings.videoSecondCaptionLanguage : strings.videoCaptionSize })
289
- ]
290
- }
291
- );
292
- const trackRadio = (track, on, choose) => /* @__PURE__ */ jsxs(
293
- "button",
294
- {
295
- type: "button",
296
- role: "menuitemradio",
297
- "aria-checked": on,
298
- className: "lk-iv-menu-item",
299
- lang: track.srclang,
300
- onClick: choose,
301
- children: [
302
- /* @__PURE__ */ jsx("span", { dir: "auto", children: track.label }),
303
- on ? /* @__PURE__ */ jsx(CheckIcon, {}) : null
304
- ]
305
- },
306
- `${track.kind}:${track.srclang}`
307
- );
308
- if (page === "captions") {
309
- return /* @__PURE__ */ jsxs(
310
- Menu,
311
- {
312
- label: strings.videoCaptionLanguage,
313
- onClose,
314
- returnTo,
315
- children: [
316
- back,
317
- /* @__PURE__ */ jsxs(
318
- "button",
319
- {
320
- type: "button",
321
- role: "menuitemradio",
322
- "aria-checked": !preferences.captions,
323
- className: "lk-iv-menu-item",
324
- onClick: () => onChange({ captions: false }),
325
- children: [
326
- /* @__PURE__ */ jsx("span", { children: strings.videoOff }),
327
- !preferences.captions ? /* @__PURE__ */ jsx(CheckIcon, {}) : null
328
- ]
329
- }
330
- ),
331
- tracks.map(
332
- (track) => trackRadio(
333
- track,
334
- preferences.captions && primaryTrack === track,
335
- () => onCaptionTrack(track)
336
- )
337
- ),
338
- partners.length > 0 ? /* @__PURE__ */ jsxs(
339
- "button",
340
- {
341
- type: "button",
342
- role: "menuitem",
343
- className: "lk-iv-menu-item",
344
- "data-menu-key": "second",
345
- onClick: () => open("second"),
346
- children: [
347
- /* @__PURE__ */ jsx("span", { children: strings.videoSecondCaptionLanguage }),
348
- /* @__PURE__ */ jsxs("span", { className: "lk-iv-menu-value", children: [
349
- /* @__PURE__ */ jsx("span", { dir: "auto", lang: secondaryTrack?.srclang, children: secondaryTrack === void 0 ? strings.videoOff : secondaryTrack.label }),
350
- /* @__PURE__ */ jsx(ChevronIcon, { direction: "right" })
351
- ] })
352
- ]
353
- }
354
- ) : null,
355
- failedNotes
356
- ]
357
- },
358
- "captions"
359
- );
360
- }
361
- if (page === "second") {
362
- return /* @__PURE__ */ jsxs(Menu, { label: strings.videoSecondCaptionLanguage, onClose, children: [
363
- back,
364
- /* @__PURE__ */ jsxs(
365
- "button",
366
- {
367
- type: "button",
368
- role: "menuitemradio",
369
- "aria-checked": secondaryTrack === void 0,
370
- className: "lk-iv-menu-item",
371
- onClick: () => onSecondaryTrack(null),
372
- children: [
373
- /* @__PURE__ */ jsx("span", { children: strings.videoOff }),
374
- secondaryTrack === void 0 ? /* @__PURE__ */ jsx(CheckIcon, {}) : null
375
- ]
376
- }
377
- ),
378
- partners.map(
379
- (track) => trackRadio(track, secondaryTrack === track, () => onSecondaryTrack(track))
380
- ),
381
- failedNotes
382
- ] }, "second");
383
- }
384
- if (page === "size") {
385
- return /* @__PURE__ */ jsxs(Menu, { label: strings.videoCaptionSize, onClose, children: [
386
- back,
387
- ["small", "medium", "large"].map((size) => /* @__PURE__ */ jsxs(
388
- "button",
389
- {
390
- type: "button",
391
- role: "menuitemradio",
392
- "aria-checked": preferences.captionSize === size,
393
- className: "lk-iv-menu-item",
394
- onClick: () => onChange({ captionSize: size }),
395
- children: [
396
- /* @__PURE__ */ jsx("span", { children: strings.videoCaptionSizeValue(size) }),
397
- preferences.captionSize === size ? /* @__PURE__ */ jsx(CheckIcon, {}) : null
398
- ]
399
- },
400
- size
401
- ))
402
- ] }, "size");
403
- }
404
- return /* @__PURE__ */ jsxs(Menu, { label: strings.videoSettings, onClose, returnTo, children: [
405
- tracks.length > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
406
- /* @__PURE__ */ jsxs(
407
- "button",
408
- {
409
- type: "button",
410
- role: "menuitem",
411
- className: "lk-iv-menu-item",
412
- "data-menu-key": "captions",
413
- onClick: () => open("captions"),
414
- children: [
415
- /* @__PURE__ */ jsx("span", { children: strings.videoCaptionLanguage }),
416
- /* @__PURE__ */ jsxs("span", { className: "lk-iv-menu-value", children: [
417
- /* @__PURE__ */ jsx("span", { dir: "auto", children: captionsValue }),
418
- /* @__PURE__ */ jsx(ChevronIcon, { direction: "right" })
419
- ] })
420
- ]
421
- }
422
- ),
423
- /* @__PURE__ */ jsxs(
424
- "button",
425
- {
426
- type: "button",
427
- role: "menuitem",
428
- className: "lk-iv-menu-item",
429
- "data-menu-key": "size",
430
- onClick: () => open("size"),
431
- children: [
432
- /* @__PURE__ */ jsx("span", { children: strings.videoCaptionSize }),
433
- /* @__PURE__ */ jsxs("span", { className: "lk-iv-menu-value", children: [
434
- strings.videoCaptionSizeValue(preferences.captionSize),
435
- /* @__PURE__ */ jsx(ChevronIcon, { direction: "right" })
436
- ] })
437
- ]
438
- }
439
- ),
440
- /* @__PURE__ */ jsxs(
441
- "button",
442
- {
443
- type: "button",
444
- role: "menuitemcheckbox",
445
- "aria-checked": preferences.captionBackground,
446
- className: "lk-iv-menu-item",
447
- onClick: () => onChange({ captionBackground: !preferences.captionBackground }),
448
- children: [
449
- /* @__PURE__ */ jsx("span", { children: strings.videoCaptionBackground }),
450
- /* @__PURE__ */ jsx("span", { className: "lk-iv-menu-value", children: preferences.captionBackground ? strings.videoOn : strings.videoOff })
451
- ]
452
- }
453
- )
454
- ] }) : null,
455
- /* @__PURE__ */ jsxs(
456
- "button",
457
- {
458
- type: "button",
459
- role: "menuitemcheckbox",
460
- "aria-checked": preferences.shortcuts,
461
- className: "lk-iv-menu-item",
462
- onClick: () => onChange({ shortcuts: !preferences.shortcuts }),
463
- children: [
464
- /* @__PURE__ */ jsx("span", { children: strings.videoKeyboardShortcuts }),
465
- /* @__PURE__ */ jsx("span", { className: "lk-iv-menu-value", children: preferences.shortcuts ? strings.videoOn : strings.videoOff })
466
- ]
467
- }
468
- ),
469
- /* @__PURE__ */ jsxs("button", { type: "button", role: "menuitem", className: "lk-iv-menu-item", onClick: onShowShortcuts, children: [
470
- /* @__PURE__ */ jsx("span", { children: strings.videoShortcutList }),
471
- /* @__PURE__ */ jsx("kbd", { className: "lk-iv-kbd", children: "?" })
472
- ] })
473
- ] }, "main");
474
- }
475
- function ShortcutSheet({ strings, onClose }) {
476
- const closeRef = useRef(null);
477
- useEffect(() => {
478
- closeRef.current?.focus();
479
- }, []);
480
- return /* @__PURE__ */ jsxs(
481
- "div",
482
- {
483
- className: "lk-iv-sheet",
484
- role: "dialog",
485
- "aria-modal": "false",
486
- "aria-label": strings.videoKeyboardShortcuts,
487
- onKeyDown: (event) => {
488
- if (event.key === "Escape" || event.key === "?") {
489
- event.preventDefault();
490
- event.stopPropagation();
491
- onClose();
492
- }
493
- },
494
- children: [
495
- /* @__PURE__ */ jsxs("div", { className: "lk-iv-sheet-head", children: [
496
- /* @__PURE__ */ jsx("h3", { className: "lk-iv-sheet-title", children: strings.videoKeyboardShortcuts }),
497
- /* @__PURE__ */ jsx(
498
- "button",
499
- {
500
- ref: closeRef,
501
- type: "button",
502
- className: "lk-iv-button",
503
- "aria-label": strings.videoClose,
504
- onClick: onClose,
505
- children: /* @__PURE__ */ jsx(CloseIcon, {})
506
- }
507
- )
508
- ] }),
509
- /* @__PURE__ */ jsx("dl", { className: "lk-iv-shortcuts", children: SHORTCUT_KEYS.map(({ action, keys }) => /* @__PURE__ */ jsxs("div", { className: "lk-iv-shortcut", children: [
510
- /* @__PURE__ */ jsx("dt", { children: /* @__PURE__ */ jsx("kbd", { className: "lk-iv-kbd", children: keys }) }),
511
- /* @__PURE__ */ jsx("dd", { children: strings.videoShortcut(action) })
512
- ] }, action)) })
513
- ]
514
- }
515
- );
516
- }
517
- function ContentsPanel({
518
- idPrefix,
519
- tab,
520
- onTab,
521
- chapters,
522
- quizzes,
523
- cues,
524
- primaryLanguage,
525
- secondaryCues,
526
- secondaryLanguage,
527
- activeCue,
528
- limit,
529
- strings,
530
- onSeek,
531
- onOpenQuiz
532
- }) {
533
- const [query, setQuery] = useState("");
534
- const listRef = useRef(null);
535
- const followUntil = useRef(0);
536
- const hasTranscript = cues.length > 0;
537
- const shownTab = hasTranscript ? tab : "contents";
538
- const paired = useMemo(() => pairSecondaryCues(cues, secondaryCues), [cues, secondaryCues]);
539
- const lines = useMemo(() => {
540
- const trimmed = query.trim().toLowerCase();
541
- return cues.map((cue, index) => ({
542
- cue,
543
- index,
544
- // The spoiler limit holds for the second language as for the first.
545
- second: (paired[index] ?? []).filter((line) => line.start <= limit).map((line) => line.text).join(" ")
546
- })).filter(({ cue }) => cue.start <= limit).filter(
547
- ({ cue, second }) => trimmed === "" || cue.text.toLowerCase().includes(trimmed) || second.toLowerCase().includes(trimmed)
548
- );
549
- }, [cues, paired, limit, query]);
550
- useEffect(() => {
551
- if (shownTab !== "transcript" || query.trim() !== "" || Date.now() < followUntil.current) {
552
- return;
553
- }
554
- const active = listRef.current?.querySelector('[data-active="true"]');
555
- if (active !== null && active !== void 0 && listRef.current !== null) {
556
- const list = listRef.current;
557
- const top = active.offsetTop - list.clientHeight / 3;
558
- list.scrollTo?.({ top, behavior: "smooth" });
559
- }
560
- }, [activeCue, shownTab, query]);
561
- const rows = [
562
- ...chapters.map((chapter) => ({ kind: "chapter", at: chapter.at, chapter })),
563
- ...quizzes.map((quiz) => ({ kind: "quiz", at: quiz.at, quiz }))
564
- ].sort((a, b) => a.at - b.at || (a.kind === "chapter" ? -1 : 1));
565
- const highlight = (text) => {
566
- const trimmed = query.trim();
567
- const at = trimmed === "" ? -1 : text.toLowerCase().indexOf(trimmed.toLowerCase());
568
- if (at === -1) {
569
- return text;
570
- }
571
- return /* @__PURE__ */ jsxs(Fragment, { children: [
572
- text.slice(0, at),
573
- /* @__PURE__ */ jsx("mark", { children: text.slice(at, at + trimmed.length) }),
574
- text.slice(at + trimmed.length)
575
- ] });
576
- };
577
- return /* @__PURE__ */ jsxs("section", { className: "lk-iv-panel", "aria-label": strings.videoPanel, children: [
578
- hasTranscript ? /* @__PURE__ */ jsx("div", { className: "lk-iv-tabs", role: "tablist", "aria-label": strings.videoPanel, children: ["contents", "transcript"].map((name) => /* @__PURE__ */ jsx(
579
- "button",
580
- {
581
- type: "button",
582
- role: "tab",
583
- id: `${idPrefix}-tab-${name}`,
584
- "aria-controls": `${idPrefix}-tabpanel`,
585
- "aria-selected": shownTab === name,
586
- tabIndex: shownTab === name ? 0 : -1,
587
- className: "lk-iv-tab",
588
- onClick: () => onTab(name),
589
- onKeyDown: (event) => {
590
- if (event.key === "ArrowLeft" || event.key === "ArrowRight") {
591
- event.preventDefault();
592
- onTab(name === "contents" ? "transcript" : "contents");
593
- }
594
- },
595
- children: name === "contents" ? strings.videoContents : strings.videoTranscript
596
- },
597
- name
598
- )) }) : null,
599
- shownTab === "contents" ? /* @__PURE__ */ jsx(
600
- "ol",
601
- {
602
- className: "lk-iv-contents",
603
- ...hasTranscript ? {
604
- role: "tabpanel",
605
- id: `${idPrefix}-tabpanel`,
606
- "aria-labelledby": `${idPrefix}-tab-contents`
607
- } : { "aria-label": strings.videoContents },
608
- children: rows.map(
609
- (row) => row.kind === "chapter" ? /* @__PURE__ */ jsx("li", { className: "lk-iv-contents-chapter", children: /* @__PURE__ */ jsxs("button", { type: "button", className: "lk-iv-row", onClick: () => onSeek(row.chapter.at), children: [
610
- /* @__PURE__ */ jsx("span", { className: "lk-iv-row-time", children: clock(row.chapter.at) }),
611
- /* @__PURE__ */ jsx("span", { className: "lk-iv-row-title", children: row.chapter.title })
612
- ] }) }, `c-${row.at}`) : /* @__PURE__ */ jsx(
613
- "li",
614
- {
615
- className: "lk-iv-contents-quiz",
616
- "data-state": row.quiz.state,
617
- children: /* @__PURE__ */ jsxs("button", { type: "button", className: "lk-iv-row", onClick: () => onOpenQuiz(row.quiz.id), children: [
618
- /* @__PURE__ */ jsx("span", { className: "lk-iv-row-time", children: clock(row.quiz.at) }),
619
- /* @__PURE__ */ jsxs("span", { className: "lk-iv-row-title", children: [
620
- /* @__PURE__ */ jsx(
621
- "span",
622
- {
623
- className: "lk-iv-marker-dot",
624
- "data-state": row.quiz.state,
625
- "aria-hidden": "true"
626
- }
627
- ),
628
- row.quiz.title,
629
- row.quiz.required ? /* @__PURE__ */ jsxs(Fragment, { children: [
630
- " ",
631
- /* @__PURE__ */ jsx("span", { className: "lk-iv-tag", children: strings.videoRequired })
632
- ] }) : null
633
- ] }),
634
- /* @__PURE__ */ jsx("span", { className: "lk-iv-row-status", children: strings.videoQuizProgress(row.quiz.answered, row.quiz.total) })
635
- ] })
636
- },
637
- `q-${row.quiz.id}`
638
- )
639
- )
640
- }
641
- ) : /* @__PURE__ */ jsxs(
642
- "div",
643
- {
644
- className: "lk-iv-transcript",
645
- role: "tabpanel",
646
- id: `${idPrefix}-tabpanel`,
647
- "aria-labelledby": `${idPrefix}-tab-transcript`,
648
- children: [
649
- /* @__PURE__ */ jsx(
650
- "input",
651
- {
652
- id: `${idPrefix}-search`,
653
- className: "lk-iv-search",
654
- type: "search",
655
- value: query,
656
- placeholder: strings.videoTranscriptSearch,
657
- "aria-label": strings.videoTranscriptSearch,
658
- onChange: (event) => setQuery(event.target.value)
659
- }
660
- ),
661
- /* @__PURE__ */ jsx(
662
- "div",
663
- {
664
- ref: listRef,
665
- className: "lk-iv-lines",
666
- onWheel: () => {
667
- followUntil.current = Date.now() + 4e3;
668
- },
669
- onTouchMove: () => {
670
- followUntil.current = Date.now() + 4e3;
671
- },
672
- children: lines.length === 0 ? /* @__PURE__ */ jsx("p", { className: "lk-iv-empty", children: strings.videoTranscriptNoMatch }) : lines.map(({ cue, index, second }) => /* @__PURE__ */ jsxs(
673
- "button",
674
- {
675
- type: "button",
676
- className: "lk-iv-line",
677
- "data-active": index === activeCue || void 0,
678
- onClick: () => onSeek(cue.start),
679
- children: [
680
- /* @__PURE__ */ jsx("span", { className: "lk-iv-row-time", children: clock(cue.start) }),
681
- secondaryCues.length === 0 ? /* @__PURE__ */ jsx("span", { lang: primaryLanguage, dir: "auto", children: highlight(cue.text) }) : /* @__PURE__ */ jsxs("span", { className: "lk-iv-line-text", children: [
682
- /* @__PURE__ */ jsx("span", { lang: primaryLanguage, dir: "auto", children: highlight(cue.text) }),
683
- second === "" ? null : /* @__PURE__ */ jsx("span", { className: "lk-iv-line-secondary", lang: secondaryLanguage, dir: "auto", children: highlight(second) })
684
- ] })
685
- ]
686
- },
687
- `${cue.start}-${index}`
688
- ))
689
- }
690
- )
691
- ]
692
- }
693
- )
694
- ] });
695
- }
696
- function EndScreen({
697
- idPrefix,
698
- renderMode,
699
- quizzes,
700
- answered,
701
- total,
702
- percent,
703
- strings,
704
- onOpenQuiz,
705
- onWatchAgain,
706
- pending,
707
- onFinish
708
- }) {
709
- const pendingId = `${idPrefix}-end-pending`;
710
- const headingRef = useRef(null);
711
- useEffect(() => {
712
- headingRef.current?.focus();
713
- }, []);
714
- return /* @__PURE__ */ jsx("section", { className: "lk-iv-end", "aria-labelledby": `${idPrefix}-end-title`, children: /* @__PURE__ */ jsxs("div", { className: "lk-iv-end-card", children: [
715
- /* @__PURE__ */ jsx("h3", { id: `${idPrefix}-end-title`, ref: headingRef, tabIndex: -1, className: "lk-iv-end-title", children: strings.videoEnded }),
716
- total > 0 ? /* @__PURE__ */ jsx("p", { className: "lk-iv-end-summary", children: percent !== null && renderMode !== "exam" ? strings.videoEndScore(answered, total, percent) : strings.videoEndAnswered(answered, total) }) : null,
717
- quizzes.length > 0 ? /* @__PURE__ */ jsx("ol", { className: "lk-iv-end-list", children: quizzes.map((quiz) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
718
- "button",
719
- {
720
- type: "button",
721
- className: "lk-iv-row",
722
- "data-state": quiz.state,
723
- onClick: () => onOpenQuiz(quiz.id),
724
- children: [
725
- /* @__PURE__ */ jsx("span", { className: "lk-iv-row-time", children: clock(quiz.at) }),
726
- /* @__PURE__ */ jsxs("span", { className: "lk-iv-row-title", children: [
727
- /* @__PURE__ */ jsx("span", { className: "lk-iv-marker-dot", "data-state": quiz.state, "aria-hidden": "true" }),
728
- quiz.title
729
- ] }),
730
- /* @__PURE__ */ jsx("span", { className: "lk-iv-row-status", children: strings.videoQuizProgress(quiz.answered, quiz.total) })
731
- ]
732
- }
733
- ) }, quiz.id)) }) : null,
734
- onFinish !== void 0 && pending ? /* @__PURE__ */ jsx("p", { id: pendingId, className: "lk-iv-end-pending", role: "status", children: strings.videoAnswerPending }) : null,
735
- /* @__PURE__ */ jsxs("div", { className: "lk-iv-end-actions", children: [
736
- /* @__PURE__ */ jsxs("button", { type: "button", className: "lk-iv-action", onClick: onWatchAgain, children: [
737
- /* @__PURE__ */ jsx(ReplayIcon, {}),
738
- strings.videoWatchAgain
739
- ] }),
740
- onFinish !== void 0 ? /* @__PURE__ */ jsx(
741
- "button",
742
- {
743
- type: "button",
744
- className: "lk-iv-action lk-iv-action-primary",
745
- "aria-disabled": pending || void 0,
746
- "aria-describedby": pending ? pendingId : void 0,
747
- onClick: onFinish,
748
- children: strings.videoFinish
749
- }
750
- ) : null
751
- ] })
752
- ] }) });
753
- }
754
-
755
- // src/components/InteractiveVideo/prefs.ts
756
- var DEFAULT_PREFERENCES = {
757
- speed: 1,
758
- volume: 1,
759
- muted: false,
760
- captions: true,
761
- captionLanguage: null,
762
- secondaryCaptionLanguage: null,
763
- captionSize: "medium",
764
- captionBackground: true,
765
- panel: false,
766
- shortcuts: true
767
- };
768
- var STORAGE_KEY = "lk.video.v1";
769
- var LANGUAGE_TAG = /^[A-Za-z]{2,3}(-[A-Za-z0-9]{2,8})*$/;
770
- var BOOLEAN_FIELDS = ["muted", "captions", "captionBackground", "panel", "shortcuts"];
771
- var LANGUAGE_FIELDS = ["captionLanguage", "secondaryCaptionLanguage"];
772
- function sanitizePartialPreferences(raw) {
773
- if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
774
- return {};
775
- }
776
- const value = raw;
777
- const has = (key) => Object.hasOwn(value, key);
778
- const out = {};
779
- if (has("speed") && SPEEDS.includes(value.speed)) {
780
- out.speed = value.speed;
781
- }
782
- if (has("volume") && typeof value.volume === "number" && Number.isFinite(value.volume)) {
783
- out.volume = Math.min(1, Math.max(0, value.volume));
784
- }
785
- for (const field of BOOLEAN_FIELDS) {
786
- if (has(field) && typeof value[field] === "boolean") {
787
- out[field] = value[field];
788
- }
789
- }
790
- for (const field of LANGUAGE_FIELDS) {
791
- const tag = value[field];
792
- if (has(field) && (tag === null || typeof tag === "string" && LANGUAGE_TAG.test(tag))) {
793
- out[field] = tag;
794
- }
795
- }
796
- if (has("captionSize") && (value.captionSize === "small" || value.captionSize === "medium" || value.captionSize === "large")) {
797
- out.captionSize = value.captionSize;
798
- }
799
- return out;
800
- }
801
- function normalised(preferences) {
802
- const { captionLanguage: first, secondaryCaptionLanguage: second } = preferences;
803
- return first !== null && second !== null && first.toLowerCase() === second.toLowerCase() ? { ...preferences, secondaryCaptionLanguage: null } : preferences;
804
- }
805
- function resolvePreferences(layers) {
806
- return normalised({
807
- ...DEFAULT_PREFERENCES,
808
- ...sanitizePartialPreferences(layers.defaults),
809
- ...sanitizePartialPreferences(layers.stored),
810
- ...sanitizePartialPreferences(layers.force),
811
- ...sanitizePartialPreferences(layers.chosen)
812
- });
813
- }
814
- function applyPreferenceChange(previous, change) {
815
- return normalised({ ...previous, ...sanitizePartialPreferences(change) });
816
- }
817
- function readStoredPreferences() {
818
- try {
819
- const stored = globalThis.localStorage?.getItem(STORAGE_KEY);
820
- return stored ? sanitizePartialPreferences(JSON.parse(stored)) : {};
821
- } catch {
822
- return {};
823
- }
824
- }
825
- function rememberPreferences(change) {
826
- try {
827
- const next = { ...readStoredPreferences(), ...sanitizePartialPreferences(change) };
828
- globalThis.localStorage?.setItem(STORAGE_KEY, JSON.stringify(next));
829
- } catch {
830
- }
831
- }
832
-
833
- // src/components/InteractiveVideo/quiz-engine.ts
834
- function crossedQuiz(cues, previous, now, handled) {
835
- let earliest;
836
- for (const cue of cues) {
837
- if (handled.has(cue.id) || !(previous < cue.at && cue.at <= now)) {
838
- continue;
839
- }
840
- if (earliest === void 0 || cue.at < earliest.at) {
841
- earliest = cue;
842
- }
843
- }
844
- return earliest;
845
- }
846
- function quizzesAtEnd(cues, duration, handled) {
847
- return cues.filter((cue) => !handled.has(cue.id) && cue.at >= duration - 0.05).sort((a, b) => a.at - b.at);
848
- }
849
- function limitSeek(timeline, from, to, furthest, finished) {
850
- if (to <= from) {
851
- return { at: to };
852
- }
853
- let at = to;
854
- let hold;
855
- if (timeline.navigation === "no-skip-ahead" && to > furthest) {
856
- at = Math.max(from, furthest);
857
- hold = { kind: "no-skip-ahead", furthest };
858
- }
859
- const blocking = timeline.cues.filter((cue) => cue.required === true && cue.at > from && cue.at <= to && !finished(cue.id)).sort((a, b) => a.at - b.at)[0];
860
- if (blocking !== void 0 && blocking.at <= at) {
861
- return { at: blocking.at, hold: { kind: "required", cue: blocking } };
862
- }
863
- return hold === void 0 ? { at } : { at, hold };
864
- }
865
- function limitResume(timeline, at, finished) {
866
- const blocking = timeline.cues.filter((cue) => cue.required === true && cue.at < at && !finished(cue.id)).sort((a, b) => a.at - b.at)[0];
867
- return blocking === void 0 ? at : Math.max(0, blocking.at - 0.25);
868
- }
869
-
870
- // src/components/InteractiveVideo/vtt.ts
871
- var MAX_VTT_LENGTH = 2e6;
872
- var MAX_CUES = 1e4;
873
- var BYTE_ORDER_MARK = String.fromCharCode(65279);
874
- var TIMESTAMP = /^(?:(\d+):)?([0-5]\d):([0-5]\d)\.(\d{3})$/;
875
- function seconds(stamp) {
876
- const match = TIMESTAMP.exec(stamp.trim());
877
- if (match === null) {
878
- return void 0;
879
- }
880
- const [, hours = "0", minutes = "0", secs = "0", millis = "0"] = match;
881
- return Number(hours) * 3600 + Number(minutes) * 60 + Number(secs) + Number(millis) / 1e3;
882
- }
883
- var ENTITIES = {
884
- amp: "&",
885
- lt: "<",
886
- gt: ">",
887
- quot: '"',
888
- apos: "'",
889
- // Built from code points: an escape can be rewritten as the raw, invisible
890
- // character itself, by a formatter or an editor.
891
- nbsp: String.fromCharCode(160),
892
- lrm: String.fromCharCode(8206),
893
- rlm: String.fromCharCode(8207)
894
- };
895
- function plainText(payload) {
896
- return payload.replace(/<[^>]*>/g, "").replace(/&(#x?[0-9a-f]+|[a-z]+);/gi, (whole, name) => {
897
- if (name.startsWith("#")) {
898
- const code = name[1] === "x" || name[1] === "X" ? Number.parseInt(name.slice(2), 16) : Number.parseInt(name.slice(1), 10);
899
- return Number.isFinite(code) && code > 0 && code <= 1114111 ? String.fromCodePoint(code) : whole;
900
- }
901
- return ENTITIES[name.toLowerCase()] ?? whole;
902
- }).replace(/\s*\n\s*/g, " ").trim();
903
- }
904
- function parseWebVtt(input) {
905
- if (typeof input !== "string") {
906
- return [];
907
- }
908
- const raw = input.slice(0, MAX_VTT_LENGTH);
909
- const text = (raw.startsWith(BYTE_ORDER_MARK) ? raw.slice(1) : raw).replace(/\r\n?/g, "\n");
910
- if (!/^WEBVTT(?:[ \t].*)?(?:\n|$)/.test(text)) {
911
- return [];
912
- }
913
- const cues = [];
914
- for (const block of text.split(/\n{2,}/)) {
915
- if (cues.length >= MAX_CUES) {
916
- break;
917
- }
918
- const lines = block.split("\n");
919
- const timingIndex = lines.findIndex((line) => line.includes("-->"));
920
- if (timingIndex === -1) {
921
- continue;
922
- }
923
- const [from = "", rest = ""] = lines[timingIndex].split("-->");
924
- const start = seconds(from);
925
- const end = seconds(rest.trim().split(/\s+/)[0] ?? "");
926
- if (start === void 0 || end === void 0 || end <= start) {
927
- continue;
928
- }
929
- const payload = plainText(lines.slice(timingIndex + 1).join("\n"));
930
- if (payload === "") {
931
- continue;
932
- }
933
- cues.push({ start, end, text: payload });
934
- }
935
- return cues.map((cue, index) => ({ cue, index })).sort((a, b) => a.cue.start - b.cue.start || a.index - b.index).map(({ cue }) => cue);
936
- }
937
- function cueIndexAt(cues, time) {
938
- let low = 0;
939
- let high = cues.length - 1;
940
- let found = -1;
941
- while (low <= high) {
942
- const middle = low + high >> 1;
943
- if (cues[middle].start <= time) {
944
- found = middle;
945
- low = middle + 1;
946
- } else {
947
- high = middle - 1;
948
- }
949
- }
950
- for (let index = found; index >= 0 && index > found - 8; index -= 1) {
951
- const cue = cues[index];
952
- if (time < cue.end) {
953
- return index;
954
- }
955
- }
956
- return -1;
957
- }
958
- var BUBBLE_HALF = 120;
959
- var MARKER_REACH = 8;
960
- function Scrubber({
961
- current,
962
- duration,
963
- buffered,
964
- chapters,
965
- markers,
966
- cues,
967
- previewLimit,
968
- strings,
969
- onSeek,
970
- onScrubChange
971
- }) {
972
- const trackRef = useRef(null);
973
- const [hover, setHover] = useState(null);
974
- const [dragging, setDragging] = useState(false);
975
- const ready = duration > 0;
976
- const percent = (value) => `${ready ? Math.min(100, Math.max(0, value / duration * 100)) : 0}%`;
977
- const measure = useCallback(
978
- (clientX) => {
979
- const rect = trackRef.current?.getBoundingClientRect();
980
- if (rect === void 0 || rect.width === 0 || !ready) {
981
- return null;
982
- }
983
- const ratio = Math.min(1, Math.max(0, (clientX - rect.left) / rect.width));
984
- return { x: ratio * rect.width, width: rect.width, time: ratio * duration };
985
- },
986
- [duration, ready]
987
- );
988
- const onPointerDown = (event) => {
989
- const at = measure(event.clientX);
990
- if (at === null) {
991
- return;
992
- }
993
- event.currentTarget.setPointerCapture(event.pointerId);
994
- setDragging(true);
995
- onScrubChange(true);
996
- setHover(at);
997
- onSeek(at.time);
998
- };
999
- const onPointerMove = (event) => {
1000
- const at = measure(event.clientX);
1001
- if (at === null) {
1002
- return;
1003
- }
1004
- setHover(at);
1005
- if (dragging) {
1006
- onSeek(at.time);
1007
- }
1008
- };
1009
- const endDrag = (event) => {
1010
- if (!dragging) {
1011
- return;
1012
- }
1013
- if (event.currentTarget.hasPointerCapture(event.pointerId)) {
1014
- event.currentTarget.releasePointerCapture(event.pointerId);
1015
- }
1016
- setDragging(false);
1017
- onScrubChange(false);
1018
- };
1019
- const onKeyDown = (event) => {
1020
- if (!ready) {
1021
- return;
1022
- }
1023
- const step = event.shiftKey ? 1 : 5;
1024
- const moves = {
1025
- ArrowLeft: -step,
1026
- ArrowRight: step,
1027
- ArrowDown: -step,
1028
- ArrowUp: step,
1029
- PageDown: -30,
1030
- PageUp: 30
1031
- };
1032
- const move = moves[event.key];
1033
- if (move !== void 0) {
1034
- event.preventDefault();
1035
- event.stopPropagation();
1036
- onSeek(current + move);
1037
- } else if (event.key === "Home" || event.key === "End") {
1038
- event.preventDefault();
1039
- event.stopPropagation();
1040
- onSeek(event.key === "Home" ? 0 : duration);
1041
- }
1042
- };
1043
- const chapterAt = (time) => [...chapters].reverse().find((chapter) => chapter.at <= time);
1044
- const hoverCue = hover !== null && hover.time <= previewLimit ? cues[cueIndexAt(cues, hover.time)] : void 0;
1045
- const hoverMarker = hover === null ? void 0 : markers.find(
1046
- (marker) => Math.abs(marker.at / duration * hover.width - hover.x) <= MARKER_REACH
1047
- );
1048
- const hoverChapter = hover === null ? void 0 : chapterAt(hover.time);
1049
- const bubbleLeft = hover === null ? 0 : hover.width > BUBBLE_HALF * 2 ? Math.min(hover.width - BUBBLE_HALF, Math.max(BUBBLE_HALF, hover.x)) : hover.width / 2;
1050
- const currentChapter = chapterAt(current);
1051
- return /* @__PURE__ */ jsxs(
1052
- "div",
1053
- {
1054
- ref: trackRef,
1055
- className: "lk-iv-scrubber",
1056
- role: "slider",
1057
- tabIndex: ready ? 0 : -1,
1058
- "aria-label": strings.media.seek,
1059
- "aria-valuemin": 0,
1060
- "aria-valuemax": Math.round(duration),
1061
- "aria-valuenow": Math.round(current),
1062
- "aria-valuetext": currentChapter === void 0 ? strings.media.timeValue(clock(current), clock(duration)) : `${strings.media.timeValue(clock(current), clock(duration))}, ${currentChapter.title}`,
1063
- "data-dragging": dragging || void 0,
1064
- onPointerDown,
1065
- onPointerMove,
1066
- onPointerUp: endDrag,
1067
- onPointerCancel: endDrag,
1068
- onPointerLeave: () => {
1069
- if (!dragging) {
1070
- setHover(null);
1071
- }
1072
- },
1073
- onKeyDown,
1074
- children: [
1075
- /* @__PURE__ */ jsxs("div", { className: "lk-iv-scrubber-track", "aria-hidden": "true", children: [
1076
- /* @__PURE__ */ jsx("div", { className: "lk-iv-scrubber-buffered", style: { width: percent(buffered) } }),
1077
- /* @__PURE__ */ jsx("div", { className: "lk-iv-scrubber-played", style: { width: percent(current) } }),
1078
- chapters.filter((chapter) => chapter.at > 0 && chapter.at < duration).map((chapter) => /* @__PURE__ */ jsx(
1079
- "span",
1080
- {
1081
- className: "lk-iv-scrubber-gap",
1082
- style: { insetInlineStart: percent(chapter.at) }
1083
- },
1084
- `chapter-${chapter.at}`
1085
- ))
1086
- ] }),
1087
- /* @__PURE__ */ jsx(
1088
- "div",
1089
- {
1090
- className: "lk-iv-scrubber-thumb",
1091
- "aria-hidden": "true",
1092
- style: { insetInlineStart: percent(current) }
1093
- }
1094
- ),
1095
- markers.map((marker) => /* @__PURE__ */ jsx(
1096
- "span",
1097
- {
1098
- className: "lk-iv-marker",
1099
- "data-state": marker.state,
1100
- "data-required": marker.required || void 0,
1101
- "aria-hidden": "true",
1102
- style: { insetInlineStart: percent(Math.min(marker.at, duration)) }
1103
- },
1104
- marker.id
1105
- )),
1106
- hover !== null ? /* @__PURE__ */ jsxs(
1107
- "div",
1108
- {
1109
- className: "lk-iv-bubble",
1110
- "aria-hidden": "true",
1111
- style: { insetInlineStart: `${bubbleLeft}px` },
1112
- children: [
1113
- /* @__PURE__ */ jsxs("span", { className: "lk-iv-bubble-time", children: [
1114
- clock(hover.time),
1115
- hoverChapter !== void 0 ? /* @__PURE__ */ jsxs("span", { className: "lk-iv-bubble-chapter", children: [
1116
- " \xB7 ",
1117
- hoverChapter.title
1118
- ] }) : null
1119
- ] }),
1120
- hoverMarker !== void 0 ? /* @__PURE__ */ jsx("span", { className: "lk-iv-bubble-quiz", children: hoverMarker.label }) : null,
1121
- hoverCue !== void 0 ? /* @__PURE__ */ jsx("span", { className: "lk-iv-bubble-caption", children: hoverCue.text }) : null
1122
- ]
1123
- }
1124
- ) : null
1125
- ]
1126
- }
1127
- );
1128
- }
1129
- function contentKey(group) {
1130
- try {
1131
- return JSON.stringify([group.id, group.stimulus, group.items, group.timeline]);
1132
- } catch {
1133
- return String(group?.id);
1134
- }
1135
- }
1136
- function InteractiveVideo(props) {
1137
- const key = useMemo(() => contentKey(props.group), [props.group]);
1138
- return /* @__PURE__ */ jsx(Player, { ...props }, key);
1139
- }
1140
- function onNextFrame(run) {
1141
- if (typeof requestAnimationFrame === "function") {
1142
- const frame = requestAnimationFrame(run);
1143
- return () => cancelAnimationFrame(frame);
1144
- }
1145
- const timer = setTimeout(run, 33);
1146
- return () => clearTimeout(timer);
1147
- }
1148
- function trackKey(track) {
1149
- return `${track.kind}:${track.srclang}:${track.src}`;
1150
- }
1151
- var NO_CUES = [];
1152
- function useCaptionLine(track, load, peek) {
1153
- const key = track === void 0 ? void 0 : trackKey(track);
1154
- const [line, setLine] = useState({
1155
- cues: NO_CUES,
1156
- failed: false
1157
- });
1158
- const trackRef = useRef(track);
1159
- trackRef.current = track;
1160
- useEffect(() => {
1161
- const wanted = trackRef.current;
1162
- if (key === void 0 || wanted === void 0) {
1163
- return;
1164
- }
1165
- let live = true;
1166
- load(wanted).then(
1167
- (cues) => {
1168
- if (live) {
1169
- setLine({ key, cues, failed: false });
1170
- }
1171
- },
1172
- () => {
1173
- if (live) {
1174
- setLine({ key, cues: NO_CUES, failed: true });
1175
- }
1176
- }
1177
- );
1178
- return () => {
1179
- live = false;
1180
- };
1181
- }, [key, load]);
1182
- if (key === void 0) {
1183
- return { cues: NO_CUES, failed: false };
1184
- }
1185
- if (line.key === key) {
1186
- return line;
1187
- }
1188
- return { cues: peek(key) ?? NO_CUES, failed: false };
1189
- }
1190
- function captionsShown(tracks, preferences) {
1191
- if (!preferences.captions) {
1192
- return { srclang: null, secondary: null };
1193
- }
1194
- const shown = resolveCaptionTracks(tracks, preferences);
1195
- return { srclang: shown.primary?.srclang ?? null, secondary: shown.secondary?.srclang ?? null };
1196
- }
1197
- var playerCount = 0;
1198
- var ITEM_TYPES = /* @__PURE__ */ new Set([
1199
- "multiple-choice",
1200
- "fill-in-the-blanks",
1201
- "gap-select",
1202
- "dictation",
1203
- "read-aloud"
1204
- ]);
1205
- function Player(props) {
1206
- const {
1207
- renderMode = "practice",
1208
- onProgress,
1209
- onSubmit,
1210
- onActivityComplete,
1211
- onFinished,
1212
- onInteraction,
1213
- recordingBinding,
1214
- workletUrl,
1215
- captionsLoader,
1216
- label,
1217
- shuffleSeed,
1218
- locale,
1219
- theme,
1220
- sanitizeHtml
1221
- } = props;
1222
- const strings = useLkStrings(props.strings);
1223
- const aiInForce = useLearnerAi(props.ai);
1224
- const delivery = resolveDeliveryPolicy(props.delivery);
1225
- const scoringKey = JSON.stringify(props.scoring ?? null);
1226
- const scoring = useMemo(
1227
- () => resolveItemScoringPolicy(props.scoring),
1228
- [scoringKey]
1229
- );
1230
- const scoringGiven = props.scoring !== void 0 && props.scoring !== null;
1231
- const group = useRef(props.group).current;
1232
- const timeline = group.timeline ?? { cues: [] };
1233
- const media = group.stimulus.media;
1234
- const chapters = timeline.chapters ?? [];
1235
- const devError = useMemo(() => {
1236
- if (!isDevelopment()) {
1237
- return null;
1238
- }
1239
- if (group.timeline === void 0) {
1240
- return new Error(
1241
- `InteractiveVideo "${group.id}" was given an item group with no timeline. Give it a timeline \u2014 a list of quizzes, each at a moment of the video \u2014 or render it with <ActivitySequence>.`
1242
- );
1243
- }
1244
- if (group.redacted === true) {
1245
- try {
1246
- assertRedactedItemGroup(group);
1247
- } catch (error) {
1248
- return error;
1249
- }
1250
- return null;
1251
- }
1252
- const result = validateItemGroup(group);
1253
- return result.success ? null : new ActivitySchemaError("item-group", result.errors);
1254
- }, [group]);
1255
- const slots = useMemo(() => {
1256
- try {
1257
- return flattenSequence([group]);
1258
- } catch {
1259
- return [];
1260
- }
1261
- }, [group]);
1262
- const cues = useMemo(
1263
- () => timeline.cues.filter(
1264
- (cue) => slots.some((slot) => slot.group?.cue?.id === cue.id && ITEM_TYPES.has(slot.activity.type))
1265
- ),
1266
- [timeline.cues, slots]
1267
- );
1268
- const slotsByCue = useMemo(() => {
1269
- const byCue = /* @__PURE__ */ new Map();
1270
- for (const slot of slots) {
1271
- const cueId = slot.group?.cue?.id;
1272
- if (cueId !== void 0 && ITEM_TYPES.has(slot.activity.type)) {
1273
- byCue.set(cueId, [...byCue.get(cueId) ?? [], slot]);
1274
- }
1275
- }
1276
- return byCue;
1277
- }, [slots]);
1278
- const [playerId] = useState(() => {
1279
- playerCount += 1;
1280
- return `lk-iv-${playerCount}`;
1281
- });
1282
- const forced = useRef(props.preferences).current;
1283
- const defaultsKey = JSON.stringify(props.defaultPreferences ?? null);
1284
- const defaults = useMemo(() => JSON.parse(defaultsKey), [defaultsKey]);
1285
- const chosen = useRef({});
1286
- const [preferences, setPreferences] = useState(
1287
- () => resolvePreferences({ force: forced, defaults: props.defaultPreferences })
1288
- );
1289
- const preferencesRef = useRef(preferences);
1290
- preferencesRef.current = preferences;
1291
- useEffect(() => {
1292
- setPreferences(
1293
- resolvePreferences({
1294
- force: forced,
1295
- stored: readStoredPreferences(),
1296
- defaults,
1297
- chosen: chosen.current
1298
- })
1299
- );
1300
- }, [forced, defaults]);
1301
- const [submitted] = useState(() => new Set(props.submittedSlotIds ?? []));
1302
- const [answered, setAnswered] = useState(() => {
1303
- const seeded = /* @__PURE__ */ new Set();
1304
- for (const slot of slots) {
1305
- if (submitted.has(slot.slotId) || props.outcomes !== void 0 && Object.hasOwn(props.outcomes, slot.slotId)) {
1306
- seeded.add(slot.slotId);
1307
- }
1308
- }
1309
- return seeded;
1310
- });
1311
- const answeredRef = useRef(answered);
1312
- answeredRef.current = answered;
1313
- const setSlotAnswered = useCallback((slotId, on) => {
1314
- setAnswered((previous) => {
1315
- if (previous.has(slotId) === on) {
1316
- return previous;
1317
- }
1318
- const next = new Set(previous);
1319
- if (on) {
1320
- next.add(slotId);
1321
- } else {
1322
- next.delete(slotId);
1323
- }
1324
- answeredRef.current = next;
1325
- return next;
1326
- });
1327
- }, []);
1328
- const [scores, setScores] = useState(() => /* @__PURE__ */ new Map());
1329
- const setSlotScore = useCallback((slotId, score) => {
1330
- setScores((previous) => {
1331
- if (previous.get(slotId) === score) {
1332
- return previous;
1333
- }
1334
- const next = new Map(previous);
1335
- if (score === void 0) {
1336
- next.delete(slotId);
1337
- } else {
1338
- next.set(slotId, score);
1339
- }
1340
- return next;
1341
- });
1342
- }, []);
1343
- const [pending, setPendingState] = useState(() => /* @__PURE__ */ new Set());
1344
- const pendingRef = useRef(pending);
1345
- const setPendingKey = useCallback((key, on) => {
1346
- setPendingState((previous) => {
1347
- if (previous.has(key) === on) {
1348
- return previous;
1349
- }
1350
- const next = new Set(previous);
1351
- if (on) {
1352
- next.add(key);
1353
- } else {
1354
- next.delete(key);
1355
- }
1356
- pendingRef.current = next;
1357
- return next;
1358
- });
1359
- }, []);
1360
- const latestTakes = useRef(/* @__PURE__ */ new Map());
1361
- const alive = useRef(true);
1362
- useEffect(() => {
1363
- alive.current = true;
1364
- return () => {
1365
- alive.current = false;
1366
- };
1367
- }, []);
1368
- const finished = useCallback(
1369
- (cueId) => (slotsByCue.get(cueId) ?? []).every((slot) => answeredRef.current.has(slot.slotId)),
1370
- [slotsByCue]
1371
- );
1372
- const shellRef = useRef(null);
1373
- const videoRef = useRef(null);
1374
- const playButtonRef = useRef(null);
1375
- const quizHeadingRef = useRef(null);
1376
- const [portalContainer, setPortalContainer] = useState(null);
1377
- const [playing, setPlaying] = useState(false);
1378
- const [waiting, setWaiting] = useState(false);
1379
- const [errorCode, setErrorCode] = useState(null);
1380
- const [current, setCurrent] = useState(0);
1381
- const [duration, setDuration] = useState(0);
1382
- const [buffered, setBuffered] = useState(0);
1383
- const [scrubbing, setScrubbing] = useState(false);
1384
- const [ended, setEnded] = useState(false);
1385
- const [fullscreen, setFullscreen] = useState(false);
1386
- const [pictureInPicture, setPictureInPicture] = useState(false);
1387
- const [countdown, setCountdown] = useState(false);
1388
- const [menu, setMenu] = useState(null);
1389
- const [sheet, setSheet] = useState(false);
1390
- const [panelTab, setPanelTab] = useState("contents");
1391
- const [chromeShown, setChromeShown] = useState(true);
1392
- const [focusInChrome, setFocusInChrome] = useState(false);
1393
- const [announcement, setAnnouncement] = useState("");
1394
- const previousTime = useRef(0);
1395
- const furthest = useRef(0);
1396
- const [furthestShown, setFurthestShown] = useState(0);
1397
- const lastProgressAt = useRef(0);
1398
- const hideTimer = useRef(null);
1399
- const lastPointer = useRef("mouse");
1400
- const finishedOnce = useRef(false);
1401
- const [, setFinishedState] = useState(false);
1402
- const [openQuiz, setOpenQuiz] = useState(null);
1403
- const openQuizRef = useRef(openQuiz);
1404
- openQuizRef.current = openQuiz;
1405
- const [mounted, setMounted] = useState(/* @__PURE__ */ new Set());
1406
- const handled = useRef(/* @__PURE__ */ new Set());
1407
- const announce = useCallback((text) => {
1408
- setAnnouncement("");
1409
- onNextFrame(() => setAnnouncement(text));
1410
- }, []);
1411
- const emit = useCallback(
1412
- (type, payload) => {
1413
- onInteraction?.({
1414
- type,
1415
- activityId: group.id,
1416
- timestamp: Date.now(),
1417
- ...payload !== void 0 ? { payload } : {}
1418
- });
1419
- },
1420
- [onInteraction, group.id]
1421
- );
1422
- const reportProgress = useCallback(
1423
- (force) => {
1424
- const video = videoRef.current;
1425
- if (onProgress === void 0 || video === null) {
1426
- return;
1427
- }
1428
- const now = Date.now();
1429
- if (!force && now - lastProgressAt.current < 5e3) {
1430
- return;
1431
- }
1432
- lastProgressAt.current = now;
1433
- onProgress({ progressVersion: "1.0", at: video.currentTime, furthest: furthest.current });
1434
- },
1435
- [onProgress]
1436
- );
1437
- const quizTitle = useCallback(
1438
- (cue) => cue.title ?? strings.videoQuiz,
1439
- [strings.videoQuiz]
1440
- );
1441
- const openAt = useCallback(
1442
- (cue) => {
1443
- const video = videoRef.current;
1444
- const quizSlots = slotsByCue.get(cue.id) ?? [];
1445
- if (video !== null) {
1446
- video.pause();
1447
- if (Math.abs(video.currentTime - cue.at) > 0.25 && cue.at <= (video.duration || cue.at)) {
1448
- video.currentTime = Math.min(cue.at, video.duration || cue.at);
1449
- }
1450
- previousTime.current = cue.at;
1451
- const webkit = video;
1452
- if (webkit.webkitDisplayingFullscreen) {
1453
- webkit.webkitExitFullscreen?.();
1454
- }
1455
- if (typeof document !== "undefined" && document.pictureInPictureElement === video) {
1456
- void document.exitPictureInPicture?.().catch(() => void 0);
1457
- }
1458
- }
1459
- const firstOpen = quizSlots.findIndex((slot) => !answeredRef.current.has(slot.slotId));
1460
- setMenu(null);
1461
- setSheet(false);
1462
- setEnded(false);
1463
- setMounted((previous) => previous.has(cue.id) ? previous : /* @__PURE__ */ new Set([...previous, cue.id]));
1464
- setOpenQuiz({ cueId: cue.id, step: firstOpen === -1 ? 0 : firstOpen });
1465
- announce(strings.videoQuizOpened(clock(cue.at), quizSlots.length));
1466
- emit("video-quiz-opened", { cueId: cue.id, at: cue.at });
1467
- reportProgress(true);
1468
- },
1469
- [slotsByCue, announce, strings, emit, reportProgress]
1470
- );
1471
- useEffect(() => {
1472
- if (openQuiz !== null) {
1473
- quizHeadingRef.current?.focus();
1474
- }
1475
- }, [openQuiz?.cueId, openQuiz]);
1476
- const closeQuiz = useCallback(
1477
- (how) => {
1478
- const open = openQuizRef.current;
1479
- if (open === null) {
1480
- return;
1481
- }
1482
- const cue = cues.find((candidate) => candidate.id === open.cueId);
1483
- setOpenQuiz(null);
1484
- const video = videoRef.current;
1485
- if (how === "rewatch" && cue !== void 0 && video !== null) {
1486
- const earlier = [...cues.map((c) => c.at), ...chapters.map((c) => c.at)].filter((at) => at < cue.at - 0.5).sort((a, b) => b - a)[0];
1487
- const to = earlier ?? 0;
1488
- video.currentTime = to;
1489
- previousTime.current = to;
1490
- setCurrent(to);
1491
- } else if (cue !== void 0) {
1492
- handled.current.add(cue.id);
1493
- emit(how === "skip" ? "video-quiz-skipped" : "video-quiz-closed", { cueId: cue.id });
1494
- }
1495
- playButtonRef.current?.focus();
1496
- if (video !== null && !(video.ended || duration > 0 && video.currentTime >= duration - 0.05)) {
1497
- void video.play().catch(() => setPlaying(false));
1498
- } else if (video !== null) {
1499
- const next = quizzesAtEnd(cues, duration, handled.current)[0];
1500
- if (next !== void 0) {
1501
- openAt(next);
1502
- } else {
1503
- setEnded(true);
1504
- }
1505
- }
1506
- },
1507
- [cues, chapters, emit, duration, openAt]
1508
- );
1509
- const onTick = useCallback(
1510
- (now) => {
1511
- const before = previousTime.current;
1512
- previousTime.current = now;
1513
- if (now > furthest.current) {
1514
- furthest.current = now;
1515
- }
1516
- if (openQuizRef.current !== null || scrubbing) {
1517
- return;
1518
- }
1519
- const crossed = crossedQuiz(cues, before, now, handled.current);
1520
- if (crossed !== void 0) {
1521
- openAt(crossed);
1522
- }
1523
- },
1524
- [cues, scrubbing, openAt]
1525
- );
1526
- useEffect(() => {
1527
- if (!playing || scrubbing) {
1528
- return;
1529
- }
1530
- let cancel = () => {
1531
- };
1532
- const tick = () => {
1533
- const video = videoRef.current;
1534
- if (video !== null) {
1535
- setCurrent(video.currentTime);
1536
- setBuffered(bufferedAhead(video));
1537
- onTick(video.currentTime);
1538
- if (furthest.current > furthestShown + 1) {
1539
- setFurthestShown(furthest.current);
1540
- }
1541
- reportProgress(false);
1542
- }
1543
- cancel = onNextFrame(tick);
1544
- };
1545
- cancel = onNextFrame(tick);
1546
- return () => cancel();
1547
- }, [playing, scrubbing, onTick, reportProgress, furthestShown]);
1548
- const seek = useCallback(
1549
- (to) => {
1550
- const video = videoRef.current;
1551
- if (video === null || duration <= 0) {
1552
- return;
1553
- }
1554
- const from = video.currentTime;
1555
- const target = Math.min(Math.max(0, to), duration);
1556
- const limited = renderMode === "review" ? { at: target } : limitSeek(timeline, from, target, furthest.current, finished);
1557
- video.currentTime = limited.at;
1558
- previousTime.current = limited.at;
1559
- setCurrent(limited.at);
1560
- setEnded(false);
1561
- emit("video-seeked", { from, to: limited.at, clamped: limited.hold !== void 0 });
1562
- if (limited.hold?.kind === "required") {
1563
- openAt(limited.hold.cue);
1564
- announce(strings.videoHeldAtQuiz(clock(limited.hold.cue.at)));
1565
- } else if (limited.hold?.kind === "no-skip-ahead") {
1566
- announce(strings.videoHeldAhead);
1567
- }
1568
- reportProgress(true);
1569
- },
1570
- [duration, renderMode, timeline, finished, emit, announce, strings, openAt, reportProgress]
1571
- );
1572
- const togglePlay = useCallback(() => {
1573
- const video = videoRef.current;
1574
- if (video === null) {
1575
- return;
1576
- }
1577
- if (ended) {
1578
- setEnded(false);
1579
- handled.current.clear();
1580
- video.currentTime = 0;
1581
- previousTime.current = -1;
1582
- void video.play().catch(() => setPlaying(false));
1583
- return;
1584
- }
1585
- if (video.paused) {
1586
- if (video.currentTime === 0 && previousTime.current <= 0) {
1587
- const atStart = crossedQuiz(cues, -1, 0, handled.current);
1588
- if (atStart !== void 0) {
1589
- previousTime.current = 0;
1590
- openAt(atStart);
1591
- return;
1592
- }
1593
- }
1594
- void video.play().catch(() => setPlaying(false));
1595
- } else {
1596
- video.pause();
1597
- }
1598
- }, [ended, cues, openAt]);
1599
- const tracks = useMemo(() => {
1600
- if (media?.tracks !== void 0 && media.tracks.length > 0) {
1601
- return media.tracks.filter(
1602
- (track) => track.kind === "captions" || track.kind === "subtitles"
1603
- );
1604
- }
1605
- return media?.captionsUrl !== void 0 ? [
1606
- {
1607
- kind: "captions",
1608
- src: media.captionsUrl,
1609
- srclang: locale ?? "en",
1610
- label: strings.videoCaptionLanguage
1611
- }
1612
- ] : [];
1613
- }, [media, locale, strings.videoCaptionLanguage]);
1614
- const { primary: primaryTrack, secondary: secondaryTrack } = useMemo(
1615
- () => resolveCaptionTracks(tracks, {
1616
- captionLanguage: preferences.captionLanguage,
1617
- secondaryCaptionLanguage: preferences.secondaryCaptionLanguage
1618
- }),
1619
- [tracks, preferences.captionLanguage, preferences.secondaryCaptionLanguage]
1620
- );
1621
- const loaderRef = useRef(captionsLoader);
1622
- loaderRef.current = captionsLoader;
1623
- const loading = useRef(/* @__PURE__ */ new Map());
1624
- const loaded = useRef(/* @__PURE__ */ new Map());
1625
- const loadCues = useCallback((track) => {
1626
- const key = trackKey(track);
1627
- const pending2 = loading.current.get(key);
1628
- if (pending2 !== void 0) {
1629
- return pending2;
1630
- }
1631
- const request = Promise.resolve().then(
1632
- () => loaderRef.current ? loaderRef.current(track) : fetch(track.src, { credentials: "same-origin" }).then((response) => {
1633
- if (!response.ok) {
1634
- throw new Error(String(response.status));
1635
- }
1636
- return response.text();
1637
- })
1638
- ).then((text) => {
1639
- const parsed = parseWebVtt(text);
1640
- if (parsed.length === 0) {
1641
- throw new Error("The caption file held no cues.");
1642
- }
1643
- loaded.current.set(key, parsed);
1644
- return parsed;
1645
- });
1646
- loading.current.set(key, request);
1647
- request.catch(() => {
1648
- loading.current.delete(key);
1649
- });
1650
- return request;
1651
- }, []);
1652
- const peekCues = useCallback((key) => loaded.current.get(key), []);
1653
- const primaryLine = useCaptionLine(primaryTrack, loadCues, peekCues);
1654
- const secondaryLine = useCaptionLine(secondaryTrack, loadCues, peekCues);
1655
- const primaryCues = primaryLine.cues;
1656
- const secondaryCues = secondaryLine.cues;
1657
- const captionsFailed = { primary: primaryLine.failed, secondary: secondaryLine.failed };
1658
- const hasCaptions = primaryCues.length > 0 || secondaryCues.length > 0;
1659
- const hasTranscript = primaryCues.length > 0;
1660
- const activeCueIndex = useMemo(() => cueIndexAt(primaryCues, current), [primaryCues, current]);
1661
- const secondaryCueIndex = useMemo(
1662
- () => cueIndexAt(secondaryCues, current),
1663
- [secondaryCues, current]
1664
- );
1665
- const spoilerLimit = timeline.navigation === "no-skip-ahead" && renderMode !== "review" ? Math.max(furthestShown, current) : Number.POSITIVE_INFINITY;
1666
- const onPreferencesChangeRef = useRef(props.onPreferencesChange);
1667
- onPreferencesChangeRef.current = props.onPreferencesChange;
1668
- const tracksRef = useRef(tracks);
1669
- tracksRef.current = tracks;
1670
- const lastSecondary = useRef(null);
1671
- const changePreferences = useCallback(
1672
- (change) => {
1673
- const previous = preferencesRef.current;
1674
- const next = applyPreferenceChange(previous, change);
1675
- if (previous.secondaryCaptionLanguage !== null) {
1676
- lastSecondary.current = previous.secondaryCaptionLanguage;
1677
- }
1678
- chosen.current = { ...chosen.current, ...change };
1679
- preferencesRef.current = next;
1680
- setPreferences(next);
1681
- rememberPreferences(change);
1682
- onPreferencesChangeRef.current?.(next, change);
1683
- const before = captionsShown(tracksRef.current, previous);
1684
- const after = captionsShown(tracksRef.current, next);
1685
- if (before.srclang !== after.srclang || before.secondary !== after.secondary) {
1686
- emit("video-captions-changed", after);
1687
- }
1688
- },
1689
- [emit]
1690
- );
1691
- const chooseCaptionTrack = (track) => {
1692
- const swaps = primaryTrack !== void 0 && secondaryTrack !== void 0 && track.srclang.toLowerCase() === secondaryTrack.srclang.toLowerCase();
1693
- changePreferences({
1694
- captions: true,
1695
- captionLanguage: track.srclang,
1696
- ...swaps ? { secondaryCaptionLanguage: primaryTrack.srclang } : {}
1697
- });
1698
- };
1699
- const chooseSecondaryTrack = (track) => {
1700
- changePreferences(
1701
- track === null ? { secondaryCaptionLanguage: null } : { secondaryCaptionLanguage: track.srclang, captions: true }
1702
- );
1703
- };
1704
- const toggleSecondary = () => {
1705
- if (secondaryTrack !== void 0) {
1706
- changePreferences(
1707
- preferences.captions ? { secondaryCaptionLanguage: null } : { captions: true }
1708
- );
1709
- return;
1710
- }
1711
- const wanted = [
1712
- lastSecondary.current,
1713
- preferences.secondaryCaptionLanguage,
1714
- sanitizePartialPreferences(defaults).secondaryCaptionLanguage ?? null,
1715
- secondaryCandidates(tracks, primaryTrack)[0]?.srclang ?? null
1716
- ];
1717
- for (const language of wanted) {
1718
- if (language === null) {
1719
- continue;
1720
- }
1721
- const { secondary } = resolveCaptionTracks(tracks, {
1722
- captionLanguage: preferences.captionLanguage,
1723
- secondaryCaptionLanguage: language
1724
- });
1725
- if (secondary !== void 0) {
1726
- changePreferences({ secondaryCaptionLanguage: secondary.srclang, captions: true });
1727
- return;
1728
- }
1729
- }
1730
- announce(strings.videoNoSecondLanguage);
1731
- };
1732
- useEffect(() => {
1733
- const video = videoRef.current;
1734
- if (video !== null) {
1735
- video.playbackRate = preferences.speed;
1736
- video.volume = preferences.volume;
1737
- video.muted = preferences.muted;
1738
- }
1739
- }, [preferences.speed, preferences.volume, preferences.muted]);
1740
- useEffect(() => {
1741
- const onChange = () => setFullscreen(document.fullscreenElement === shellRef.current);
1742
- document.addEventListener("fullscreenchange", onChange);
1743
- return () => document.removeEventListener("fullscreenchange", onChange);
1744
- }, []);
1745
- useEffect(() => {
1746
- const video = videoRef.current;
1747
- if (video === null) {
1748
- return;
1749
- }
1750
- const enter = () => {
1751
- setPictureInPicture(true);
1752
- emit("video-pip-changed", { on: true });
1753
- };
1754
- const leave = () => {
1755
- setPictureInPicture(false);
1756
- emit("video-pip-changed", { on: false });
1757
- };
1758
- video.addEventListener("enterpictureinpicture", enter);
1759
- video.addEventListener("leavepictureinpicture", leave);
1760
- return () => {
1761
- video.removeEventListener("enterpictureinpicture", enter);
1762
- video.removeEventListener("leavepictureinpicture", leave);
1763
- };
1764
- }, [emit]);
1765
- useEffect(
1766
- () => () => {
1767
- if (hideTimer.current !== null) {
1768
- clearTimeout(hideTimer.current);
1769
- }
1770
- },
1771
- []
1772
- );
1773
- const revealChrome = useCallback(() => {
1774
- setChromeShown(true);
1775
- if (hideTimer.current !== null) {
1776
- clearTimeout(hideTimer.current);
1777
- }
1778
- hideTimer.current = setTimeout(() => setChromeShown(false), 2400);
1779
- }, []);
1780
- const toggleFullscreen = useCallback(() => {
1781
- const shell = shellRef.current;
1782
- const video = videoRef.current;
1783
- if (typeof document === "undefined" || shell === null) {
1784
- return;
1785
- }
1786
- if (document.fullscreenElement) {
1787
- void document.exitFullscreen().catch(() => void 0);
1788
- } else if (typeof shell.requestFullscreen === "function" && document.fullscreenEnabled !== false) {
1789
- void shell.requestFullscreen().catch(() => void 0);
1790
- } else {
1791
- video?.webkitEnterFullscreen?.();
1792
- }
1793
- emit("video-fullscreen-changed");
1794
- }, [emit]);
1795
- const pictureInPictureSupported = typeof document !== "undefined" && document.pictureInPictureEnabled === true;
1796
- const togglePictureInPicture = useCallback(() => {
1797
- const video = videoRef.current;
1798
- if (video === null) {
1799
- return;
1800
- }
1801
- if (document.pictureInPictureElement) {
1802
- void document.exitPictureInPicture().catch(() => void 0);
1803
- } else {
1804
- void video.requestPictureInPicture?.().catch(() => void 0);
1805
- }
1806
- }, []);
1807
- const jumpPoints = useMemo(
1808
- () => [.../* @__PURE__ */ new Set([...chapters.map((c) => c.at), ...cues.map((c) => c.at)])].sort((a, b) => a - b),
1809
- [chapters, cues]
1810
- );
1811
- const onKeyDown = (event) => {
1812
- if (openQuiz !== null || !preferences.shortcuts) {
1813
- return;
1814
- }
1815
- const target = event.target;
1816
- if (target.isContentEditable || ["INPUT", "TEXTAREA", "SELECT"].includes(target.tagName) || target.tagName === "BUTTON" && (event.key === " " || event.key === "Enter")) {
1817
- return;
1818
- }
1819
- if (event.ctrlKey || event.metaKey || event.altKey) {
1820
- return;
1821
- }
1822
- const video = videoRef.current;
1823
- const key = event.key;
1824
- const done = () => {
1825
- event.preventDefault();
1826
- revealChrome();
1827
- };
1828
- if (/^[0-9]$/.test(key)) {
1829
- done();
1830
- seek(Number(key) / 10 * duration);
1831
- return;
1832
- }
1833
- switch (key) {
1834
- case " ":
1835
- case "k":
1836
- case "K":
1837
- done();
1838
- togglePlay();
1839
- return;
1840
- case "j":
1841
- case "J":
1842
- done();
1843
- seek(current - JUMP_SECONDS);
1844
- return;
1845
- case "l":
1846
- case "L":
1847
- done();
1848
- seek(current + JUMP_SECONDS);
1849
- return;
1850
- case "ArrowLeft":
1851
- done();
1852
- seek(current - (event.shiftKey ? 1 : 5));
1853
- return;
1854
- case "ArrowRight":
1855
- done();
1856
- seek(current + (event.shiftKey ? 1 : 5));
1857
- return;
1858
- case "Home":
1859
- done();
1860
- seek(0);
1861
- return;
1862
- case "End":
1863
- done();
1864
- seek(duration);
1865
- return;
1866
- case ",":
1867
- case ".":
1868
- done();
1869
- video?.pause();
1870
- seek(current + (key === "," ? -FRAME_SECONDS : FRAME_SECONDS));
1871
- return;
1872
- case "<":
1873
- case ">":
1874
- done();
1875
- changePreferences({ speed: nudgeSpeed(preferences.speed, key === "<" ? -1 : 1) });
1876
- return;
1877
- case "[": {
1878
- done();
1879
- const previous = [...jumpPoints].reverse().find((at) => at < current - 1);
1880
- seek(previous ?? 0);
1881
- return;
1882
- }
1883
- case "]": {
1884
- done();
1885
- const next = jumpPoints.find((at) => at > current + 0.5);
1886
- if (next !== void 0) {
1887
- seek(next);
1888
- }
1889
- return;
1890
- }
1891
- case "m":
1892
- case "M":
1893
- done();
1894
- changePreferences({ muted: !preferences.muted });
1895
- return;
1896
- case "c":
1897
- case "C":
1898
- if (event.shiftKey) {
1899
- done();
1900
- toggleSecondary();
1901
- } else if (hasCaptions) {
1902
- done();
1903
- changePreferences({ captions: !preferences.captions });
1904
- }
1905
- return;
1906
- case "t":
1907
- case "T":
1908
- if (hasTranscript) {
1909
- done();
1910
- setPanelTab("transcript");
1911
- changePreferences({ panel: !(preferences.panel && panelTab === "transcript") });
1912
- }
1913
- return;
1914
- case "p":
1915
- case "P":
1916
- if (pictureInPictureSupported) {
1917
- done();
1918
- togglePictureInPicture();
1919
- }
1920
- return;
1921
- case "f":
1922
- case "F":
1923
- done();
1924
- toggleFullscreen();
1925
- return;
1926
- case "?":
1927
- done();
1928
- setSheet((open) => !open);
1929
- return;
1930
- default:
1931
- return;
1932
- }
1933
- };
1934
- const quizzes = cues.map((cue) => {
1935
- const quizSlots = slotsByCue.get(cue.id) ?? [];
1936
- const count = quizSlots.filter((slot) => answered.has(slot.slotId)).length;
1937
- return {
1938
- id: cue.id,
1939
- at: cue.at,
1940
- title: quizTitle(cue),
1941
- answered: count,
1942
- total: quizSlots.length,
1943
- // In review the attempt is over: nothing is required of the learner any
1944
- // more, in the list or on the bar.
1945
- required: cue.required === true && renderMode !== "review",
1946
- state: markerState(quizSlots, answered, renderMode, props.outcomes)
1947
- };
1948
- });
1949
- const markers = quizzes.map((quiz) => ({
1950
- id: quiz.id,
1951
- at: quiz.at,
1952
- label: `${quiz.title} \xB7 ${strings.videoQuizProgress(quiz.answered, quiz.total)}`,
1953
- state: quiz.state,
1954
- required: quiz.required
1955
- }));
1956
- const totalQuestions = quizzes.reduce((sum, quiz) => sum + quiz.total, 0);
1957
- const answeredCount = quizzes.reduce((sum, quiz) => sum + quiz.answered, 0);
1958
- const graded = [...scores.values()];
1959
- const percent = graded.length === 0 ? null : Math.round(graded.reduce((a, b) => a + b, 0) / graded.length * 100);
1960
- const summary = useCallback(() => {
1961
- return {
1962
- slots: slots.filter((slot) => slot.group?.cue !== void 0).map((slot) => {
1963
- const cueId = slot.group?.cue?.id;
1964
- return {
1965
- slotId: slot.slotId,
1966
- activityId: slot.activity.id,
1967
- cueId,
1968
- status: answeredRef.current.has(slot.slotId) ? "answered" : handled.current.has(cueId) ? "skipped" : "unreached"
1969
- };
1970
- })
1971
- };
1972
- }, [slots]);
1973
- const finish = useCallback(() => {
1974
- if (finishedOnce.current) {
1975
- return;
1976
- }
1977
- finishedOnce.current = true;
1978
- setFinishedState(true);
1979
- onFinished?.(summary());
1980
- }, [onFinished, summary]);
1981
- const answerPending = pending.size > 0;
1982
- useEffect(() => {
1983
- if (ended && !answerPending && totalQuestions > 0 && answeredCount === totalQuestions && renderMode !== "review") {
1984
- finish();
1985
- }
1986
- }, [ended, answerPending, totalQuestions, answeredCount, renderMode, finish]);
1987
- const slotBindings = useMemo(() => {
1988
- const bindings = /* @__PURE__ */ new Map();
1989
- if (recordingBinding === void 0) {
1990
- return bindings;
1991
- }
1992
- const { assess, playbackUrl } = recordingBinding;
1993
- for (const slot of slots) {
1994
- const place = {
1995
- slotId: slot.slotId,
1996
- index: slot.index,
1997
- activityId: slot.activity.id
1998
- };
1999
- bindings.set(slot.slotId, {
2000
- upload: (take) => recordingBinding.upload(take, place),
2001
- ...assess !== void 0 ? { assess: (ref) => assess.call(recordingBinding, ref, place) } : {},
2002
- ...playbackUrl !== void 0 ? { playbackUrl: (ref) => playbackUrl.call(recordingBinding, ref, place) } : {}
2003
- });
2004
- }
2005
- return bindings;
2006
- }, [slots, recordingBinding]);
2007
- const places = useMemo(() => {
2008
- const byId = /* @__PURE__ */ new Map();
2009
- for (const [cueId, cueSlots] of slotsByCue) {
2010
- for (const slot of cueSlots) {
2011
- byId.set(slot.slotId, {
2012
- slotId: slot.slotId,
2013
- index: slot.index,
2014
- activityId: slot.activity.id,
2015
- cueId
2016
- });
2017
- }
2018
- }
2019
- return byId;
2020
- }, [slotsByCue]);
2021
- const reportTo = useRef({
2022
- onSubmit,
2023
- onActivityComplete,
2024
- onInteraction,
2025
- renderMode,
2026
- delivery,
2027
- scoring: scoringGiven ? scoring : void 0
2028
- });
2029
- reportTo.current = {
2030
- onSubmit,
2031
- onActivityComplete,
2032
- onInteraction,
2033
- renderMode,
2034
- delivery,
2035
- scoring: scoringGiven ? scoring : void 0
2036
- };
2037
- const calls = useRef(/* @__PURE__ */ new Map());
2038
- const callsFor = (place) => {
2039
- const made = calls.current.get(place.slotId);
2040
- if (made !== void 0) {
2041
- return made;
2042
- }
2043
- const { slotId } = place;
2044
- const warned = /* @__PURE__ */ new Set();
2045
- const refused = (call) => {
2046
- if (reportTo.current.renderMode !== "review") {
2047
- return false;
2048
- }
2049
- if (isDevelopment() && !warned.has(call)) {
2050
- warned.add(call);
2051
- console.warn(
2052
- `InteractiveVideo: \`${call}()\` was called for question "${slotId}" in renderMode "review", where nothing is submittable. It was ignored.`
2053
- );
2054
- }
2055
- return true;
2056
- };
2057
- const next = {
2058
- submit: (response) => {
2059
- if (!alive.current || refused("submit")) {
2060
- return;
2061
- }
2062
- setSlotAnswered(slotId, true);
2063
- reportTo.current.onSubmit?.(response, place);
2064
- },
2065
- complete: (result) => {
2066
- if (!alive.current || refused("complete")) {
2067
- return;
2068
- }
2069
- setSlotAnswered(slotId, true);
2070
- setSlotScore(slotId, result.maxScore > 0 ? result.score / result.maxScore : void 0);
2071
- reportTo.current.onActivityComplete?.(result, place);
2072
- },
2073
- clear: () => {
2074
- if (!alive.current || refused("clear")) {
2075
- return;
2076
- }
2077
- setSlotAnswered(slotId, false);
2078
- setSlotScore(slotId, void 0);
2079
- },
2080
- setPending: (on) => {
2081
- if (!alive.current) {
2082
- return;
2083
- }
2084
- setPendingKey(`host:${slotId}`, on === true);
2085
- },
2086
- emit: (type, payload) => {
2087
- if (!alive.current) {
2088
- return;
2089
- }
2090
- reportTo.current.onInteraction?.({
2091
- type,
2092
- activityId: place.activityId,
2093
- timestamp: Date.now(),
2094
- ...payload !== void 0 ? { payload } : {}
2095
- });
2096
- }
2097
- };
2098
- calls.current.set(slotId, next);
2099
- return next;
2100
- };
2101
- const reportTake = (slotId, take, state) => {
2102
- if (!alive.current) {
2103
- return;
2104
- }
2105
- const latest = latestTakes.current.get(slotId);
2106
- if (latest !== void 0 && take < latest) {
2107
- return;
2108
- }
2109
- latestTakes.current.set(slotId, take);
2110
- setPendingKey(`take:${slotId}`, state === "in-flight");
2111
- };
2112
- const channels = useRef(/* @__PURE__ */ new Map());
2113
- const channelFor = (slotId) => {
2114
- let channel = channels.current.get(slotId);
2115
- if (channel === void 0) {
2116
- channel = {
2117
- captureGroup: `${playerId}::${slotId}`,
2118
- // Read live, so a question drawn by the host cannot be offered AI help
2119
- // by a provider above the player while the video is an exam.
2120
- get renderMode() {
2121
- return reportTo.current.renderMode;
2122
- },
2123
- get delivery() {
2124
- return reportTo.current.delivery;
2125
- },
2126
- get scoring() {
2127
- return reportTo.current.scoring;
2128
- },
2129
- get triesClosed() {
2130
- return finishedOnce.current;
2131
- },
2132
- // The video waits for no try: its Finish is on the end card, where no
2133
- // question is, and finishing closes every question's tries.
2134
- triesState: () => {
2135
- },
2136
- takeState: (take, state) => reportTake(slotId, take, state)
2137
- };
2138
- channels.current.set(slotId, channel);
2139
- }
2140
- return channel;
2141
- };
2142
- const seedsFor = (slotId) => ({
2143
- restored: props.responses !== void 0 && Object.hasOwn(props.responses, slotId) ? props.responses[slotId] : void 0,
2144
- outcome: props.outcomes !== void 0 && Object.hasOwn(props.outcomes, slotId) ? props.outcomes[slotId] : void 0
2145
- });
2146
- const questionFor = (slot, place, active) => {
2147
- const { restored, outcome } = seedsFor(slot.slotId);
2148
- return {
2149
- activity: slot.activity,
2150
- slot: place,
2151
- renderMode,
2152
- active,
2153
- ...locale !== void 0 ? { locale } : {},
2154
- ...restored !== void 0 ? { defaultValue: restored } : {},
2155
- defaultSubmitted: submitted.has(slot.slotId),
2156
- ...outcome !== void 0 ? { outcome } : {},
2157
- portalContainer,
2158
- ...aiInForce !== void 0 && renderMode !== "exam" ? { ai: aiInForce } : {},
2159
- delivery,
2160
- scoring,
2161
- ...callsFor(place)
2162
- };
2163
- };
2164
- const sdkQuestion = (slot, place) => {
2165
- const activity = slot.activity;
2166
- const { restored, outcome } = seedsFor(slot.slotId);
2167
- const reported = callsFor(place);
2168
- const common = {
2169
- renderMode,
2170
- ...restored !== void 0 ? { defaultValue: restored } : {},
2171
- ...submitted.has(slot.slotId) ? { defaultSubmitted: true } : {},
2172
- ...outcome !== void 0 ? { outcome } : {},
2173
- onSubmit: reported.submit,
2174
- onComplete: reported.complete,
2175
- ...onInteraction !== void 0 ? { onInteraction } : {},
2176
- ...sanitizeHtml !== void 0 ? { sanitizeHtml } : {},
2177
- ...props.strings !== void 0 ? { strings: props.strings } : {},
2178
- ...props.ai !== void 0 ? { ai: props.ai } : {},
2179
- ...props.delivery !== void 0 && props.delivery !== null ? { delivery: props.delivery } : {},
2180
- ...scoringGiven ? { scoring: props.scoring } : {},
2181
- ...locale !== void 0 ? { locale } : {}
2182
- };
2183
- switch (activity.type) {
2184
- case "multiple-choice":
2185
- return /* @__PURE__ */ jsx(
2186
- MultipleChoice,
2187
- {
2188
- data: activity,
2189
- ...common,
2190
- ...shuffleSeed !== void 0 ? { shuffleSeed } : {}
2191
- }
2192
- );
2193
- case "fill-in-the-blanks":
2194
- return /* @__PURE__ */ jsx(FillInTheBlanks, { data: activity, ...common });
2195
- case "gap-select":
2196
- return /* @__PURE__ */ jsx(
2197
- GapSelect,
2198
- {
2199
- data: activity,
2200
- ...common,
2201
- ...shuffleSeed !== void 0 ? { shuffleSeed } : {}
2202
- }
2203
- );
2204
- case "dictation":
2205
- return /* @__PURE__ */ jsx(Dictation, { data: activity, ...common });
2206
- case "read-aloud": {
2207
- const binding = slotBindings.get(slot.slotId);
2208
- const assessment = props.assessments !== void 0 && Object.hasOwn(props.assessments, slot.slotId) ? props.assessments[slot.slotId] : void 0;
2209
- return /* @__PURE__ */ jsx(
2210
- ReadAloud,
2211
- {
2212
- data: activity,
2213
- ...common,
2214
- ...binding !== void 0 ? { recordingBinding: binding } : {},
2215
- ...assessment !== void 0 ? { assessment } : {},
2216
- ...workletUrl !== void 0 ? { workletUrl } : {}
2217
- }
2218
- );
2219
- }
2220
- default:
2221
- return null;
2222
- }
2223
- };
2224
- if (devError !== null) {
2225
- throw devError;
2226
- }
2227
- const openCue = openQuiz === null ? void 0 : cues.find((cue) => cue.id === openQuiz.cueId);
2228
- const openSlots = openCue === void 0 ? [] : slotsByCue.get(openCue.id) ?? [];
2229
- const step = openQuiz?.step ?? 0;
2230
- const stepSlot = openSlots[step];
2231
- const stepAnswered = stepSlot !== void 0 && answered.has(stepSlot.slotId);
2232
- const lastStep = step >= openSlots.length - 1;
2233
- const required = openCue?.required === true && renderMode !== "review";
2234
- const canAdvance = !required || stepAnswered;
2235
- const goToStep = (next) => {
2236
- setOpenQuiz((open) => open === null ? open : { ...open, step: next });
2237
- const slot = openSlots[next];
2238
- if (slot !== void 0 && openCue !== void 0) {
2239
- announce(strings.videoQuestionProgress(next + 1, openSlots.length));
2240
- emit("video-quiz-question-shown", { cueId: openCue.id, slotId: slot.slotId });
2241
- }
2242
- };
2243
- const captionsHidden = !preferences.captions || openQuiz !== null || ended || errorCode !== null;
2244
- const primaryText = captionsHidden ? void 0 : primaryCues[activeCueIndex]?.text;
2245
- const secondaryText = captionsHidden ? void 0 : secondaryCues[secondaryCueIndex]?.text;
2246
- const chromeVisible = !playing || chromeShown || menu !== null || scrubbing || focusInChrome || sheet || openQuiz !== null;
2247
- const volumeLevel = preferences.muted || preferences.volume === 0 ? "muted" : preferences.volume < 0.5 ? "low" : "high";
2248
- return (
2249
- // biome-ignore lint/a11y/noStaticElementInteractions: the player's keyboard shortcuts, scoped to the player; each is also a button
2250
- /* @__PURE__ */ jsxs(
2251
- "div",
2252
- {
2253
- ref: shellRef,
2254
- className: "lk-iv",
2255
- tabIndex: -1,
2256
- lang: locale,
2257
- style: theme,
2258
- "data-render-mode": renderMode,
2259
- "data-quiz-open": openQuiz !== null || void 0,
2260
- "data-fullscreen": fullscreen || void 0,
2261
- "data-chrome": chromeVisible ? "shown" : "hidden",
2262
- onKeyDown,
2263
- children: [
2264
- /* @__PURE__ */ jsxs(
2265
- "div",
2266
- {
2267
- className: "lk-iv-stage",
2268
- onPointerDown: (event) => {
2269
- lastPointer.current = event.pointerType;
2270
- },
2271
- onPointerMove: (event) => {
2272
- if (event.pointerType !== "touch") {
2273
- revealChrome();
2274
- }
2275
- },
2276
- onPointerLeave: () => {
2277
- if (playing) {
2278
- setChromeShown(false);
2279
- }
2280
- },
2281
- children: [
2282
- /* @__PURE__ */ jsx(
2283
- "video",
2284
- {
2285
- ref: videoRef,
2286
- className: "lk-iv-video",
2287
- src: media?.url,
2288
- ...media?.poster !== void 0 ? { poster: media.poster } : {},
2289
- preload: "metadata",
2290
- playsInline: true,
2291
- "aria-label": group.title ?? media?.alt,
2292
- onClick: () => {
2293
- if (lastPointer.current === "touch" && !chromeVisible) {
2294
- revealChrome();
2295
- return;
2296
- }
2297
- togglePlay();
2298
- },
2299
- onDoubleClick: toggleFullscreen,
2300
- onPlay: () => {
2301
- setPlaying(true);
2302
- setEnded(false);
2303
- revealChrome();
2304
- emit("video-played", { at: videoRef.current?.currentTime ?? 0 });
2305
- },
2306
- onPause: () => {
2307
- setPlaying(false);
2308
- emit("video-paused", { at: videoRef.current?.currentTime ?? 0 });
2309
- reportProgress(true);
2310
- },
2311
- onWaiting: () => setWaiting(true),
2312
- onPlaying: () => setWaiting(false),
2313
- onProgress: (event) => setBuffered(bufferedAhead(event.currentTarget)),
2314
- onTimeUpdate: (event) => {
2315
- if (!playing && !scrubbing) {
2316
- setCurrent(event.currentTarget.currentTime);
2317
- }
2318
- },
2319
- onRateChange: (event) => emit("video-rate-changed", { rate: event.currentTarget.playbackRate }),
2320
- onLoadedMetadata: (event) => {
2321
- const video = event.currentTarget;
2322
- const length = Number.isFinite(video.duration) ? video.duration : 0;
2323
- setDuration(length);
2324
- video.playbackRate = preferences.speed;
2325
- video.volume = preferences.volume;
2326
- video.muted = preferences.muted;
2327
- const stored = readMediaProgress(props.progress, length);
2328
- if (stored !== null) {
2329
- const at = renderMode === "review" ? stored.at : limitResume(timeline, stored.at, finished);
2330
- furthest.current = Math.max(stored.furthest, at);
2331
- setFurthestShown(furthest.current);
2332
- video.currentTime = at;
2333
- previousTime.current = at;
2334
- setCurrent(at);
2335
- }
2336
- },
2337
- onEnded: () => {
2338
- setPlaying(false);
2339
- setCurrent(duration);
2340
- emit("video-ended");
2341
- reportProgress(true);
2342
- const atEnd = quizzesAtEnd(cues, duration, handled.current);
2343
- const first = atEnd[0];
2344
- if (first !== void 0) {
2345
- openAt(first);
2346
- } else {
2347
- setEnded(true);
2348
- }
2349
- },
2350
- onError: (event) => {
2351
- setWaiting(false);
2352
- setErrorCode(event.currentTarget.error?.code ?? 0);
2353
- }
2354
- }
2355
- ),
2356
- label !== void 0 ? /* @__PURE__ */ jsx("span", { className: "lk-iv-label", children: label }) : null,
2357
- primaryText !== void 0 || secondaryText !== void 0 ? (
2358
- // The lines keep `.lk-iv-caption`, `data-size` and `data-background`
2359
- // as well, so a stylesheet written against 15.x still matches them.
2360
- /* @__PURE__ */ jsxs(
2361
- "div",
2362
- {
2363
- className: "lk-iv-captions",
2364
- "data-size": preferences.captionSize,
2365
- "data-background": preferences.captionBackground || void 0,
2366
- children: [
2367
- primaryText !== void 0 ? /* @__PURE__ */ jsx(
2368
- "div",
2369
- {
2370
- className: "lk-iv-caption",
2371
- "data-role": "primary",
2372
- "data-size": preferences.captionSize,
2373
- "data-background": preferences.captionBackground || void 0,
2374
- lang: primaryTrack?.srclang,
2375
- dir: "auto",
2376
- children: /* @__PURE__ */ jsx("span", { children: primaryText })
2377
- }
2378
- ) : null,
2379
- secondaryText !== void 0 ? /* @__PURE__ */ jsx(
2380
- "div",
2381
- {
2382
- className: "lk-iv-caption",
2383
- "data-role": "secondary",
2384
- "data-size": preferences.captionSize,
2385
- "data-background": preferences.captionBackground || void 0,
2386
- lang: secondaryTrack?.srclang,
2387
- dir: "auto",
2388
- children: /* @__PURE__ */ jsx("span", { children: secondaryText })
2389
- }
2390
- ) : null
2391
- ]
2392
- }
2393
- )
2394
- ) : null,
2395
- waiting && errorCode === null && openQuiz === null ? /* @__PURE__ */ jsx("span", { className: "lk-iv-spinner", "aria-hidden": "true" }) : null,
2396
- errorCode !== null ? /* @__PURE__ */ jsxs("div", { className: "lk-iv-error", role: "alert", children: [
2397
- /* @__PURE__ */ jsx(AlertIcon, {}),
2398
- /* @__PURE__ */ jsx("p", { children: strings.videoError(errorCode) }),
2399
- /* @__PURE__ */ jsx(
2400
- "button",
2401
- {
2402
- type: "button",
2403
- className: "lk-iv-action",
2404
- onClick: () => {
2405
- setErrorCode(null);
2406
- videoRef.current?.load();
2407
- },
2408
- children: strings.videoTryAgain
2409
- }
2410
- )
2411
- ] }) : null,
2412
- !playing && !waiting && errorCode === null && openQuiz === null && !ended ? /* @__PURE__ */ jsx(
2413
- "button",
2414
- {
2415
- type: "button",
2416
- className: "lk-iv-big-play",
2417
- "aria-label": strings.media.play,
2418
- onClick: togglePlay,
2419
- children: /* @__PURE__ */ jsx(PlayIcon, {})
2420
- }
2421
- ) : null,
2422
- /* @__PURE__ */ jsxs(
2423
- "div",
2424
- {
2425
- className: "lk-iv-chrome",
2426
- inert: openQuiz !== null,
2427
- onFocus: () => setFocusInChrome(true),
2428
- onBlur: (event) => {
2429
- if (!event.currentTarget.contains(event.relatedTarget)) {
2430
- setFocusInChrome(false);
2431
- }
2432
- },
2433
- children: [
2434
- /* @__PURE__ */ jsx(
2435
- Scrubber,
2436
- {
2437
- current,
2438
- duration,
2439
- buffered,
2440
- chapters,
2441
- markers,
2442
- cues: primaryCues,
2443
- previewLimit: spoilerLimit,
2444
- strings,
2445
- onSeek: seek,
2446
- onScrubChange: setScrubbing
2447
- }
2448
- ),
2449
- /* @__PURE__ */ jsxs("div", { className: "lk-iv-bar", children: [
2450
- /* @__PURE__ */ jsxs("div", { className: "lk-iv-bar-group", children: [
2451
- /* @__PURE__ */ jsx(
2452
- "button",
2453
- {
2454
- ref: playButtonRef,
2455
- type: "button",
2456
- className: "lk-iv-button",
2457
- "aria-label": ended ? strings.videoReplay : playing ? strings.media.pause : strings.media.play,
2458
- onClick: togglePlay,
2459
- children: ended ? /* @__PURE__ */ jsx(ReplayIcon, {}) : playing ? /* @__PURE__ */ jsx(PauseIcon, {}) : /* @__PURE__ */ jsx(PlayIcon, {})
2460
- }
2461
- ),
2462
- /* @__PURE__ */ jsx(
2463
- "button",
2464
- {
2465
- type: "button",
2466
- className: "lk-iv-button lk-iv-wide",
2467
- "aria-label": strings.videoBack(JUMP_SECONDS),
2468
- onClick: () => seek(current - JUMP_SECONDS),
2469
- children: /* @__PURE__ */ jsx(BackIcon, {})
2470
- }
2471
- ),
2472
- /* @__PURE__ */ jsx(
2473
- "button",
2474
- {
2475
- type: "button",
2476
- className: "lk-iv-button lk-iv-wide",
2477
- "aria-label": strings.videoForward(JUMP_SECONDS),
2478
- onClick: () => seek(current + JUMP_SECONDS),
2479
- children: /* @__PURE__ */ jsx(ForwardIcon, {})
2480
- }
2481
- ),
2482
- /* @__PURE__ */ jsxs("div", { className: "lk-iv-volume", children: [
2483
- /* @__PURE__ */ jsx(
2484
- "button",
2485
- {
2486
- type: "button",
2487
- className: "lk-iv-button",
2488
- "aria-label": preferences.muted ? strings.media.unmute : strings.media.mute,
2489
- "aria-pressed": preferences.muted,
2490
- onClick: () => changePreferences({ muted: !preferences.muted }),
2491
- children: /* @__PURE__ */ jsx(VolumeIcon, { level: volumeLevel })
2492
- }
2493
- ),
2494
- /* @__PURE__ */ jsx(
2495
- "input",
2496
- {
2497
- id: `${playerId}-volume`,
2498
- className: "lk-iv-volume-slider",
2499
- type: "range",
2500
- min: 0,
2501
- max: 1,
2502
- step: 0.05,
2503
- value: preferences.muted ? 0 : preferences.volume,
2504
- "aria-label": strings.media.volume,
2505
- onChange: (event) => changePreferences({ volume: Number(event.target.value), muted: false })
2506
- }
2507
- )
2508
- ] }),
2509
- /* @__PURE__ */ jsx(
2510
- "button",
2511
- {
2512
- type: "button",
2513
- className: "lk-iv-time",
2514
- "aria-label": countdown ? strings.videoShowElapsed : strings.videoShowRemaining,
2515
- onClick: () => setCountdown((on) => !on),
2516
- children: /* @__PURE__ */ jsxs("span", { "aria-hidden": "true", children: [
2517
- countdown ? `-${clock(Math.max(0, duration - current))}` : clock(current),
2518
- /* @__PURE__ */ jsxs("span", { className: "lk-iv-time-total", children: [
2519
- " / ",
2520
- clock(duration)
2521
- ] })
2522
- ] })
2523
- }
2524
- )
2525
- ] }),
2526
- /* @__PURE__ */ jsxs("div", { className: "lk-iv-bar-group", children: [
2527
- quizzes.length > 0 || chapters.length > 0 || hasTranscript ? /* @__PURE__ */ jsx(
2528
- "button",
2529
- {
2530
- type: "button",
2531
- className: "lk-iv-button",
2532
- "aria-label": strings.videoPanel,
2533
- "aria-pressed": preferences.panel,
2534
- onClick: () => changePreferences({ panel: !preferences.panel }),
2535
- children: /* @__PURE__ */ jsx(ContentsIcon, {})
2536
- }
2537
- ) : null,
2538
- hasCaptions ? /* @__PURE__ */ jsx(
2539
- "button",
2540
- {
2541
- type: "button",
2542
- className: "lk-iv-button",
2543
- "aria-label": preferences.captions ? strings.videoCaptionsHide : strings.videoCaptionsShow,
2544
- "aria-pressed": preferences.captions,
2545
- onClick: () => changePreferences({ captions: !preferences.captions }),
2546
- children: /* @__PURE__ */ jsx(CaptionsIcon, { on: preferences.captions })
2547
- }
2548
- ) : null,
2549
- /* @__PURE__ */ jsxs("div", { className: "lk-iv-menu-anchor", children: [
2550
- /* @__PURE__ */ jsx(
2551
- "button",
2552
- {
2553
- type: "button",
2554
- className: "lk-iv-button lk-iv-speed",
2555
- "aria-label": strings.media.speed,
2556
- "aria-haspopup": "menu",
2557
- "aria-expanded": menu === "speed",
2558
- onClick: () => setMenu((open) => open === "speed" ? null : "speed"),
2559
- children: /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: `${preferences.speed}\xD7` })
2560
- }
2561
- ),
2562
- menu === "speed" ? /* @__PURE__ */ jsx(
2563
- SpeedMenu,
2564
- {
2565
- speed: preferences.speed,
2566
- strings,
2567
- onChoose: (speed) => {
2568
- changePreferences({ speed });
2569
- setMenu(null);
2570
- },
2571
- onClose: () => setMenu(null)
2572
- }
2573
- ) : null
2574
- ] }),
2575
- /* @__PURE__ */ jsxs("div", { className: "lk-iv-menu-anchor", children: [
2576
- /* @__PURE__ */ jsx(
2577
- "button",
2578
- {
2579
- type: "button",
2580
- className: "lk-iv-button",
2581
- "aria-label": strings.videoSettings,
2582
- "aria-haspopup": "menu",
2583
- "aria-expanded": menu === "settings",
2584
- onClick: () => setMenu((open) => open === "settings" ? null : "settings"),
2585
- children: /* @__PURE__ */ jsx(SettingsIcon, {})
2586
- }
2587
- ),
2588
- menu === "settings" ? /* @__PURE__ */ jsx(
2589
- SettingsMenu,
2590
- {
2591
- preferences,
2592
- tracks,
2593
- primaryTrack,
2594
- secondaryTrack,
2595
- captionsFailed,
2596
- onCaptionTrack: chooseCaptionTrack,
2597
- onSecondaryTrack: chooseSecondaryTrack,
2598
- strings,
2599
- onChange: changePreferences,
2600
- onShowShortcuts: () => {
2601
- setMenu(null);
2602
- setSheet(true);
2603
- },
2604
- onClose: () => setMenu(null)
2605
- }
2606
- ) : null
2607
- ] }),
2608
- pictureInPictureSupported ? /* @__PURE__ */ jsx(
2609
- "button",
2610
- {
2611
- type: "button",
2612
- className: "lk-iv-button lk-iv-wide",
2613
- "aria-label": strings.videoPictureInPicture,
2614
- "aria-pressed": pictureInPicture,
2615
- onClick: togglePictureInPicture,
2616
- children: /* @__PURE__ */ jsx(PictureInPictureIcon, {})
2617
- }
2618
- ) : null,
2619
- /* @__PURE__ */ jsx(
2620
- "button",
2621
- {
2622
- type: "button",
2623
- className: "lk-iv-button",
2624
- "aria-label": fullscreen ? strings.videoExitFullscreen : strings.videoFullscreen,
2625
- onClick: toggleFullscreen,
2626
- children: /* @__PURE__ */ jsx(FullscreenIcon, { on: fullscreen })
2627
- }
2628
- )
2629
- ] })
2630
- ] })
2631
- ]
2632
- }
2633
- ),
2634
- /* @__PURE__ */ jsxs(
2635
- "div",
2636
- {
2637
- className: "lk-iv-quiz",
2638
- role: "dialog",
2639
- "aria-modal": "false",
2640
- "aria-labelledby": `${playerId}-quiz-title`,
2641
- hidden: openQuiz === null,
2642
- onKeyDown: (event) => {
2643
- if (event.key === "Escape" && !event.defaultPrevented && !required && openQuiz !== null) {
2644
- event.preventDefault();
2645
- closeQuiz("skip");
2646
- }
2647
- },
2648
- children: [
2649
- openCue !== void 0 ? /* @__PURE__ */ jsxs("div", { className: "lk-iv-quiz-head", children: [
2650
- /* @__PURE__ */ jsxs(
2651
- "h3",
2652
- {
2653
- id: `${playerId}-quiz-title`,
2654
- ref: quizHeadingRef,
2655
- tabIndex: -1,
2656
- className: "lk-iv-quiz-title",
2657
- children: [
2658
- quizTitle(openCue),
2659
- /* @__PURE__ */ jsxs("span", { className: "lk-iv-quiz-time", children: [
2660
- " \xB7 ",
2661
- clock(openCue.at)
2662
- ] }),
2663
- required ? /* @__PURE__ */ jsxs(Fragment, { children: [
2664
- " ",
2665
- /* @__PURE__ */ jsx("span", { className: "lk-iv-tag", children: strings.videoRequired })
2666
- ] }) : null
2667
- ]
2668
- }
2669
- ),
2670
- openSlots.length > 1 ? /* @__PURE__ */ jsxs("div", { className: "lk-iv-steps", children: [
2671
- /* @__PURE__ */ jsx("span", { className: "lk-iv-steps-label", children: strings.videoQuestionProgress(step + 1, openSlots.length) }),
2672
- /* @__PURE__ */ jsx("ol", { className: "lk-iv-steps-dots", children: openSlots.map((slot, index) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(
2673
- "button",
2674
- {
2675
- type: "button",
2676
- className: "lk-iv-step",
2677
- "data-current": index === step || void 0,
2678
- "data-answered": answered.has(slot.slotId) || void 0,
2679
- "aria-current": index === step ? "step" : void 0,
2680
- "aria-label": strings.videoQuestionStep(
2681
- index + 1,
2682
- answered.has(slot.slotId)
2683
- ),
2684
- onClick: () => goToStep(index)
2685
- }
2686
- ) }, slot.slotId)) })
2687
- ] }) : null
2688
- ] }) : null,
2689
- /* @__PURE__ */ jsx("div", { className: "lk-iv-quiz-body", children: cues.filter((cue) => mounted.has(cue.id)).map(
2690
- (cue) => (slotsByCue.get(cue.id) ?? []).map((slot, index) => {
2691
- const shown = openQuiz?.cueId === cue.id && step === index;
2692
- const place = places.get(slot.slotId);
2693
- return /* @__PURE__ */ jsx(
2694
- QuestionPane,
2695
- {
2696
- hidden: !shown,
2697
- channel: channelFor(slot.slotId),
2698
- children: /* @__PURE__ */ jsx(
2699
- SlotQuestion,
2700
- {
2701
- title: slot.activity.title,
2702
- strings,
2703
- render: props.renderQuestion,
2704
- question: props.renderQuestion === void 0 ? void 0 : questionFor(slot, place, shown),
2705
- sdk: () => sdkQuestion(slot, place),
2706
- setHostPending: callsFor(place).setPending
2707
- }
2708
- )
2709
- },
2710
- slot.slotId
2711
- );
2712
- })
2713
- ) }),
2714
- openCue !== void 0 ? /* @__PURE__ */ jsxs("div", { className: "lk-iv-quiz-foot", children: [
2715
- /* @__PURE__ */ jsxs("div", { className: "lk-iv-quiz-foot-start", children: [
2716
- /* @__PURE__ */ jsxs("button", { type: "button", className: "lk-iv-link", onClick: () => closeQuiz("rewatch"), children: [
2717
- /* @__PURE__ */ jsx(ReplayIcon, {}),
2718
- strings.videoRewatch
2719
- ] }),
2720
- !required && !lastStep ? /* @__PURE__ */ jsx("button", { type: "button", className: "lk-iv-link", onClick: () => closeQuiz("skip"), children: strings.videoSkipQuiz }) : null
2721
- ] }),
2722
- /* @__PURE__ */ jsxs("div", { className: "lk-iv-quiz-foot-end", children: [
2723
- renderMode === "exam" && stepAnswered ? /* @__PURE__ */ jsx("span", { className: "lk-iv-saved", role: "status", children: strings.videoAnswerSaved }) : null,
2724
- /* @__PURE__ */ jsxs(
2725
- "button",
2726
- {
2727
- type: "button",
2728
- className: stepAnswered || renderMode === "review" ? "lk-iv-action lk-iv-action-primary" : "lk-iv-action",
2729
- "aria-disabled": !canAdvance || void 0,
2730
- onClick: () => {
2731
- if (!canAdvance) {
2732
- return;
2733
- }
2734
- if (lastStep) {
2735
- closeQuiz("continue");
2736
- } else {
2737
- goToStep(step + 1);
2738
- }
2739
- },
2740
- children: [
2741
- lastStep ? strings.videoContinue : strings.videoNextQuestion,
2742
- lastStep ? /* @__PURE__ */ jsx(PlayIcon, {}) : null
2743
- ]
2744
- }
2745
- )
2746
- ] })
2747
- ] }) : null
2748
- ]
2749
- }
2750
- ),
2751
- ended && openQuiz === null ? /* @__PURE__ */ jsx(
2752
- EndScreen,
2753
- {
2754
- idPrefix: playerId,
2755
- renderMode,
2756
- quizzes,
2757
- answered: answeredCount,
2758
- total: totalQuestions,
2759
- percent: delivery.feedback ? percent : null,
2760
- strings,
2761
- onOpenQuiz: (id) => {
2762
- const cue = cues.find((candidate) => candidate.id === id);
2763
- if (cue !== void 0) {
2764
- openAt(cue);
2765
- }
2766
- },
2767
- onWatchAgain: togglePlay,
2768
- pending: answerPending,
2769
- onFinish: renderMode === "review" ? void 0 : () => {
2770
- if (pendingRef.current.size > 0) {
2771
- announce(strings.videoAnswerPending);
2772
- return;
2773
- }
2774
- finish();
2775
- }
2776
- }
2777
- ) : null,
2778
- sheet ? /* @__PURE__ */ jsx(
2779
- ShortcutSheet,
2780
- {
2781
- strings,
2782
- onClose: () => {
2783
- setSheet(false);
2784
- playButtonRef.current?.focus();
2785
- }
2786
- }
2787
- ) : null
2788
- ]
2789
- }
2790
- ),
2791
- preferences.panel ? /* @__PURE__ */ jsx(
2792
- ContentsPanel,
2793
- {
2794
- idPrefix: playerId,
2795
- tab: panelTab,
2796
- onTab: setPanelTab,
2797
- chapters,
2798
- quizzes,
2799
- cues: primaryCues,
2800
- primaryLanguage: primaryTrack?.srclang,
2801
- secondaryCues: secondaryTrack === void 0 ? NO_CUES : secondaryCues,
2802
- secondaryLanguage: secondaryTrack?.srclang,
2803
- activeCue: activeCueIndex,
2804
- limit: spoilerLimit,
2805
- strings,
2806
- onSeek: seek,
2807
- onOpenQuiz: (id) => {
2808
- const cue = cues.find((candidate) => candidate.id === id);
2809
- if (cue === void 0) {
2810
- return;
2811
- }
2812
- if (renderMode !== "review" && limitSeek(
2813
- timeline,
2814
- videoRef.current?.currentTime ?? 0,
2815
- cue.at,
2816
- furthest.current,
2817
- finished
2818
- ).at < cue.at - 0.25) {
2819
- seek(cue.at);
2820
- return;
2821
- }
2822
- openAt(cue);
2823
- }
2824
- }
2825
- ) : null,
2826
- /* @__PURE__ */ jsx("div", { className: "lk-iv-live", "aria-live": "polite", "aria-atomic": "true", children: announcement }),
2827
- /* @__PURE__ */ jsx("div", { ref: setPortalContainer, className: "lk-iv-portal" })
2828
- ]
2829
- }
2830
- )
2831
- );
2832
- }
2833
- function SlotQuestion({
2834
- title,
2835
- strings,
2836
- ...content
2837
- }) {
2838
- return /* @__PURE__ */ jsx(
2839
- ActivityErrorBoundary,
2840
- {
2841
- ...title !== void 0 ? { activityTitle: title } : {},
2842
- strings,
2843
- children: /* @__PURE__ */ jsx(SlotContent, { ...content })
2844
- }
2845
- );
2846
- }
2847
- function SlotContent({
2848
- render,
2849
- question,
2850
- sdk,
2851
- setHostPending
2852
- }) {
2853
- const hosted = render !== void 0 && question !== void 0 ? render(question) : void 0;
2854
- const hosting = hosted !== void 0;
2855
- useEffect(() => {
2856
- if (!hosting) {
2857
- return;
2858
- }
2859
- return () => setHostPending(false);
2860
- }, [hosting, setHostPending]);
2861
- return hosting ? hosted : sdk();
2862
- }
2863
- function QuestionPane({
2864
- hidden,
2865
- channel,
2866
- children
2867
- }) {
2868
- const ref = useRef(null);
2869
- const captureGroup = channel.captureGroup;
2870
- useEffect(() => {
2871
- if (!hidden) {
2872
- return;
2873
- }
2874
- stopCaptureGroup(captureGroup);
2875
- for (const media of ref.current?.querySelectorAll("audio, video") ?? []) {
2876
- const element = media;
2877
- if (!element.paused) {
2878
- element.pause();
2879
- }
2880
- }
2881
- }, [hidden, captureGroup]);
2882
- return /* @__PURE__ */ jsx("div", { ref, className: "lk-iv-question", hidden, children: /* @__PURE__ */ jsx(SequenceSlotContext.Provider, { value: channel, children }) });
2883
- }
2884
- function bufferedAhead(video) {
2885
- const ranges = video.buffered;
2886
- if (ranges === void 0) {
2887
- return 0;
2888
- }
2889
- for (let index = 0; index < ranges.length; index += 1) {
2890
- if (ranges.start(index) <= video.currentTime && video.currentTime <= ranges.end(index)) {
2891
- return ranges.end(index);
2892
- }
2893
- }
2894
- return 0;
2895
- }
2896
- function markerState(quizSlots, answered, renderMode, outcomes) {
2897
- if (renderMode === "review" && outcomes !== void 0) {
2898
- const results = quizSlots.map(
2899
- (slot) => Object.hasOwn(outcomes, slot.slotId) ? outcomes[slot.slotId] : void 0
2900
- );
2901
- if (results.some((outcome) => outcome === void 0 || outcome.status === "deferred")) {
2902
- return "pending";
2903
- }
2904
- const scored = results.filter(
2905
- (outcome) => outcome?.status === "scored"
2906
- );
2907
- if (scored.length === results.length && scored.every((outcome) => outcome.passed)) {
2908
- return "correct";
2909
- }
2910
- return scored.some((outcome) => outcome.passed) ? "partial" : "incorrect";
2911
- }
2912
- const count = quizSlots.filter((slot) => answered.has(slot.slotId)).length;
2913
- if (count === quizSlots.length && count > 0) {
2914
- return "answered";
2915
- }
2916
- if (count > 0) {
2917
- return "open";
2918
- }
2919
- return "unreached";
2920
- }
2921
- function InteractiveVideo2(props) {
2922
- const strings = useLkStrings(props.strings);
2923
- return /* @__PURE__ */ jsx(
2924
- ActivityErrorBoundary,
2925
- {
2926
- ...props.group?.title !== void 0 ? { activityTitle: props.group.title } : {},
2927
- strings,
2928
- children: /* @__PURE__ */ jsx(InteractiveVideo, { ...props })
2929
- }
2930
- );
2931
- }
2932
-
2933
- export { InteractiveVideo2 as InteractiveVideo, resolveCaptionTracks };
2934
- //# sourceMappingURL=chunk-JMEZYYCE.js.map
2935
- //# sourceMappingURL=chunk-JMEZYYCE.js.map