@intellectif/lk-react 23.1.0 → 23.1.2

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