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