@intellectif/lk-react 13.0.0 → 14.1.0

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 (164) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/README.md +11 -3
  3. package/dist/{WrittenResponse-B5p_-RnJ.d.ts → WrittenResponse-DrzI89Wg.d.ts} +1 -1
  4. package/dist/{WrittenResponse-DWtS4tOL.d.cts → WrittenResponse-rRpOr9W1.d.cts} +1 -1
  5. package/dist/{chunk-UU2NV6SU.cjs → chunk-4Y5TELVU.cjs} +14 -13
  6. package/dist/chunk-4Y5TELVU.cjs.map +1 -0
  7. package/dist/{chunk-XAGWBVK3.js → chunk-5CP2Z6IS.js} +6 -5
  8. package/dist/chunk-5CP2Z6IS.js.map +1 -0
  9. package/dist/{chunk-NVUFEMPZ.cjs → chunk-5JCTJFQU.cjs} +30 -16
  10. package/dist/chunk-5JCTJFQU.cjs.map +1 -0
  11. package/dist/chunk-5JFMXBH7.js +888 -0
  12. package/dist/chunk-5JFMXBH7.js.map +1 -0
  13. package/dist/{chunk-NVP6UEQQ.js → chunk-AS2QGND5.js} +4 -4
  14. package/dist/{chunk-NVP6UEQQ.js.map → chunk-AS2QGND5.js.map} +1 -1
  15. package/dist/{chunk-3Q4I4KT6.js → chunk-AWQ53DON.js} +78 -2
  16. package/dist/chunk-AWQ53DON.js.map +1 -0
  17. package/dist/{chunk-DQERNAWI.js → chunk-B4BCEQNO.js} +26 -31
  18. package/dist/chunk-B4BCEQNO.js.map +1 -0
  19. package/dist/chunk-BRPEB43C.cjs +13 -0
  20. package/dist/chunk-BRPEB43C.cjs.map +1 -0
  21. package/dist/{chunk-74QXBWCI.cjs → chunk-CTW3G3EH.cjs} +6 -6
  22. package/dist/{chunk-74QXBWCI.cjs.map → chunk-CTW3G3EH.cjs.map} +1 -1
  23. package/dist/{chunk-AMGK7DDM.cjs → chunk-D4DQFTA7.cjs} +2 -2
  24. package/dist/chunk-D4DQFTA7.cjs.map +1 -0
  25. package/dist/{chunk-R7DI5UMU.js → chunk-DB4DLHN6.js} +22 -4
  26. package/dist/chunk-DB4DLHN6.js.map +1 -0
  27. package/dist/{chunk-K3EA547J.cjs → chunk-DPEY7DSE.cjs} +27 -33
  28. package/dist/chunk-DPEY7DSE.cjs.map +1 -0
  29. package/dist/{chunk-W4MXRZAB.js → chunk-IDYZ2FA2.js} +6 -5
  30. package/dist/chunk-IDYZ2FA2.js.map +1 -0
  31. package/dist/{chunk-K7NMNOPI.cjs → chunk-JG4DWAUJ.cjs} +15 -14
  32. package/dist/chunk-JG4DWAUJ.cjs.map +1 -0
  33. package/dist/chunk-KIO6QXGT.cjs +894 -0
  34. package/dist/chunk-KIO6QXGT.cjs.map +1 -0
  35. package/dist/chunk-MCO52PV5.js +520 -0
  36. package/dist/chunk-MCO52PV5.js.map +1 -0
  37. package/dist/{chunk-HT4FFZZQ.cjs → chunk-MZITNWZ2.cjs} +233 -38
  38. package/dist/chunk-MZITNWZ2.cjs.map +1 -0
  39. package/dist/{chunk-Z63JRG4M.cjs → chunk-N536RUCZ.cjs} +15 -14
  40. package/dist/chunk-N536RUCZ.cjs.map +1 -0
  41. package/dist/{chunk-PSHNTDKG.js → chunk-NJAF34GN.js} +13 -161
  42. package/dist/chunk-NJAF34GN.js.map +1 -0
  43. package/dist/chunk-OD65OZIW.cjs +159 -0
  44. package/dist/chunk-OD65OZIW.cjs.map +1 -0
  45. package/dist/{chunk-2GXSA322.cjs → chunk-PSUKWVKU.cjs} +15 -14
  46. package/dist/chunk-PSUKWVKU.cjs.map +1 -0
  47. package/dist/chunk-RCMYEWBE.js +11 -0
  48. package/dist/chunk-RCMYEWBE.js.map +1 -0
  49. package/dist/{chunk-PY53ABWC.js → chunk-RCPW6HVY.js} +23 -9
  50. package/dist/chunk-RCPW6HVY.js.map +1 -0
  51. package/dist/{chunk-DQNVAXG6.js → chunk-RXS455MR.js} +2 -2
  52. package/dist/chunk-RXS455MR.js.map +1 -0
  53. package/dist/chunk-TUVEJLOT.js +537 -0
  54. package/dist/chunk-TUVEJLOT.js.map +1 -0
  55. package/dist/{chunk-YZWXVZB3.js → chunk-U432HTUW.js} +6 -5
  56. package/dist/chunk-U432HTUW.js.map +1 -0
  57. package/dist/chunk-UDCQTOVU.cjs +523 -0
  58. package/dist/chunk-UDCQTOVU.cjs.map +1 -0
  59. package/dist/{chunk-5N7OLFHB.js → chunk-UDQS5INQ.js} +6 -5
  60. package/dist/chunk-UDQS5INQ.js.map +1 -0
  61. package/dist/{chunk-YILH4ETT.js → chunk-UDZWYHDV.js} +226 -31
  62. package/dist/chunk-UDZWYHDV.js.map +1 -0
  63. package/dist/{chunk-SBIJJ374.cjs → chunk-UMDWVMB5.cjs} +24 -6
  64. package/dist/chunk-UMDWVMB5.cjs.map +1 -0
  65. package/dist/{chunk-MA7GXUWC.cjs → chunk-VSFHJ7FB.cjs} +78 -2
  66. package/dist/chunk-VSFHJ7FB.cjs.map +1 -0
  67. package/dist/{chunk-J73TDY7J.cjs → chunk-WC4OD3EE.cjs} +25 -173
  68. package/dist/chunk-WC4OD3EE.cjs.map +1 -0
  69. package/dist/chunk-YM64MS4G.cjs +547 -0
  70. package/dist/chunk-YM64MS4G.cjs.map +1 -0
  71. package/dist/chunk-YORQQXGO.js +155 -0
  72. package/dist/chunk-YORQQXGO.js.map +1 -0
  73. package/dist/components/ActivityPreview.cjs +15 -10
  74. package/dist/components/ActivityPreview.d.cts +9 -3
  75. package/dist/components/ActivityPreview.d.ts +9 -3
  76. package/dist/components/ActivityPreview.js +14 -9
  77. package/dist/components/ActivitySequence.cjs +16 -11
  78. package/dist/components/ActivitySequence.d.cts +115 -4
  79. package/dist/components/ActivitySequence.d.ts +115 -4
  80. package/dist/components/ActivitySequence.js +15 -10
  81. package/dist/components/Dictation.cjs +7 -5
  82. package/dist/components/Dictation.d.cts +2 -1
  83. package/dist/components/Dictation.d.ts +2 -1
  84. package/dist/components/Dictation.js +6 -4
  85. package/dist/components/FillInTheBlanks.cjs +6 -5
  86. package/dist/components/FillInTheBlanks.d.cts +2 -1
  87. package/dist/components/FillInTheBlanks.d.ts +2 -1
  88. package/dist/components/FillInTheBlanks.js +5 -4
  89. package/dist/components/GapSelect.cjs +6 -5
  90. package/dist/components/GapSelect.d.cts +2 -1
  91. package/dist/components/GapSelect.d.ts +2 -1
  92. package/dist/components/GapSelect.js +5 -4
  93. package/dist/components/MultipleChoice.cjs +6 -5
  94. package/dist/components/MultipleChoice.d.cts +2 -1
  95. package/dist/components/MultipleChoice.d.ts +2 -1
  96. package/dist/components/MultipleChoice.js +5 -4
  97. package/dist/components/PronunciationFeedback.cjs +16 -0
  98. package/dist/components/PronunciationFeedback.cjs.map +1 -0
  99. package/dist/components/PronunciationFeedback.d.cts +67 -0
  100. package/dist/components/PronunciationFeedback.d.ts +67 -0
  101. package/dist/components/PronunciationFeedback.js +7 -0
  102. package/dist/components/PronunciationFeedback.js.map +1 -0
  103. package/dist/components/ReadAloud.cjs +21 -0
  104. package/dist/components/ReadAloud.cjs.map +1 -0
  105. package/dist/components/ReadAloud.d.cts +15 -0
  106. package/dist/components/ReadAloud.d.ts +15 -0
  107. package/dist/components/ReadAloud.js +12 -0
  108. package/dist/components/ReadAloud.js.map +1 -0
  109. package/dist/components/StimulusPanel.cjs +4 -4
  110. package/dist/components/StimulusPanel.d.cts +2 -1
  111. package/dist/components/StimulusPanel.d.ts +2 -1
  112. package/dist/components/StimulusPanel.js +3 -3
  113. package/dist/components/WrittenResponse.cjs +6 -5
  114. package/dist/components/WrittenResponse.d.cts +4 -3
  115. package/dist/components/WrittenResponse.d.ts +4 -3
  116. package/dist/components/WrittenResponse.js +5 -4
  117. package/dist/hooks/useSpeechRecorder.cjs +17 -0
  118. package/dist/hooks/useSpeechRecorder.cjs.map +1 -0
  119. package/dist/hooks/useSpeechRecorder.d.cts +127 -0
  120. package/dist/hooks/useSpeechRecorder.d.ts +127 -0
  121. package/dist/hooks/useSpeechRecorder.js +4 -0
  122. package/dist/hooks/useSpeechRecorder.js.map +1 -0
  123. package/dist/i18n/LkIntlProvider.cjs +7 -7
  124. package/dist/i18n/LkIntlProvider.d.cts +3 -2
  125. package/dist/i18n/LkIntlProvider.d.ts +3 -2
  126. package/dist/i18n/LkIntlProvider.js +1 -1
  127. package/dist/index.cjs +53 -32
  128. package/dist/index.cjs.map +1 -1
  129. package/dist/index.d.cts +6 -2
  130. package/dist/index.d.ts +6 -2
  131. package/dist/index.js +17 -12
  132. package/dist/index.js.map +1 -1
  133. package/dist/machine-CuwKqWXC.d.cts +26 -0
  134. package/dist/machine-CuwKqWXC.d.ts +26 -0
  135. package/dist/{strings-DPTyLpHt.d.cts → strings-l8Wv1Xax.d.ts} +230 -4
  136. package/dist/{strings-DPTyLpHt.d.ts → strings-wjAF2ONm.d.cts} +230 -4
  137. package/dist/theme/ThemeProvider.cjs +6 -6
  138. package/dist/theme/ThemeProvider.d.cts +4 -4
  139. package/dist/theme/ThemeProvider.d.ts +4 -4
  140. package/dist/theme/ThemeProvider.js +1 -1
  141. package/dist/theme/skin.css +612 -3
  142. package/package.json +33 -3
  143. package/dist/chunk-2GXSA322.cjs.map +0 -1
  144. package/dist/chunk-3Q4I4KT6.js.map +0 -1
  145. package/dist/chunk-5N7OLFHB.js.map +0 -1
  146. package/dist/chunk-AMGK7DDM.cjs.map +0 -1
  147. package/dist/chunk-DQERNAWI.js.map +0 -1
  148. package/dist/chunk-DQNVAXG6.js.map +0 -1
  149. package/dist/chunk-HT4FFZZQ.cjs.map +0 -1
  150. package/dist/chunk-J73TDY7J.cjs.map +0 -1
  151. package/dist/chunk-K3EA547J.cjs.map +0 -1
  152. package/dist/chunk-K7NMNOPI.cjs.map +0 -1
  153. package/dist/chunk-MA7GXUWC.cjs.map +0 -1
  154. package/dist/chunk-NVUFEMPZ.cjs.map +0 -1
  155. package/dist/chunk-PSHNTDKG.js.map +0 -1
  156. package/dist/chunk-PY53ABWC.js.map +0 -1
  157. package/dist/chunk-R7DI5UMU.js.map +0 -1
  158. package/dist/chunk-SBIJJ374.cjs.map +0 -1
  159. package/dist/chunk-UU2NV6SU.cjs.map +0 -1
  160. package/dist/chunk-W4MXRZAB.js.map +0 -1
  161. package/dist/chunk-XAGWBVK3.js.map +0 -1
  162. package/dist/chunk-YILH4ETT.js.map +0 -1
  163. package/dist/chunk-YZWXVZB3.js.map +0 -1
  164. package/dist/chunk-Z63JRG4M.cjs.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/MultipleChoice/MultipleChoice.tsx","../src/components/MultipleChoice/index.tsx"],"names":["MultipleChoice","jsx"],"mappings":";;;;;;;;;AA8BA,SAAS,YAAY,QAAA,EAAiD;AACpE,EAAA,OAAO,QAAA,EAAU,IAAA,KAAS,iBAAA,GAAoB,QAAA,CAAS,oBAAoB,EAAC;AAC9E;AAWA,SAAS,cAAA,CAAe,QAAuB,WAAA,EAA+B;AAC5E,EAAA,QAAQ,OAAO,OAAA;AAAS,IACtB,KAAK,SAAA;AAAA,IACL,KAAK,oBAAA;AACH,MAAA,OAAO,IAAA;AAAA,IACT,KAAK,WAAA;AAAA,IACL,KAAK,kBAAA;AACH,MAAA,OAAO,KAAA;AAAA,IACT;AACE,MAAA,OAAO,WAAA,GAAc,MAAA,CAAO,OAAA,GAAU,CAAC,MAAA,CAAO,OAAA;AAAA;AAEpD;AASA,SAAS,kBAAA,CAAmB,SAAkC,CAAA,EAA6B;AACzF,EAAA,IAAI,YAAY,MAAA,EAAW;AACzB,IAAA,OAAO,IAAA;AAAA,EACT;AACA,EAAA,IAAI,OAAA,CAAQ,WAAW,QAAA,EAAU;AAC/B,IAAA,MAAM,UAAU,OAAA,CAAQ,QAAA;AACxB,IAAA,OAAO,GAAG,CAAA,CAAE,iBAAA,CAAkB,IAAA,CAAK,KAAA,CAAM,QAAQ,KAAA,GAAQ,GAAG,CAAA,EAAG,OAAA,CAAQ,MAAM,CAAC,CAAA,EAC5E,UAAU,CAAA,CAAA,EAAI,OAAO,KAAK,EAC5B,CAAA,CAAA;AAAA,EACF;AACA,EAAA,IAAI,OAAA,CAAQ,WAAW,UAAA,EAAY;AAEjC,IAAA,OAAO,CAAA,CAAE,YAAA;AAAA,EACX;AACA,EAAA,IAAI,OAAA,CAAQ,WAAW,YAAA,EAAc;AACnC,IAAA,OAAO,CAAA,CAAE,gBAAA;AAAA,EACX;AAIA,EAAA,OAAO,IAAA;AACT;AAaO,SAAS,cAAA,CAAe;AAAA,EAC7B,IAAA;AAAA,EACA,UAAA;AAAA,EACA,QAAA;AAAA,EACA,KAAA;AAAA,EACA,YAAA;AAAA,EACA,gBAAA;AAAA,EACA,QAAA;AAAA,EACA,UAAA,GAAa,UAAA;AAAA,EACb,OAAA;AAAA,EACA,YAAA;AAAA,EACA,WAAA;AAAA,EACA,YAAA;AAAA,EACA,aAAA;AAAA,EACA,KAAA;AAAA,EACA,MAAA;AAAA,EACA,QAAA;AAAA,EACA,WAAA;AAAA,EACA;AACF,CAAA,EAAwB;AACtB,EAAA,MAAM,CAAA,GAAI,aAAa,OAAO,CAAA;AAI9B,EAAA,MAAM,QAAA,GAAW,QAAQ,MAAM;AAC7B,IAAA,IAAI,CAAC,eAAc,EAAG;AACpB,MAAA,OAAO,IAAA;AAAA,IACT;AAKA,IAAA,IAAI,IAAA,CAAK,aAAa,IAAA,EAAM;AAC1B,MAAA,OAAO,IAAA;AAAA,IACT;AACA,IAAA,MAAM,MAAA,GAAS,gBAAA,CAAiB,iBAAA,EAAmB,IAAI,CAAA;AACvD,IAAA,OAAO,OAAO,OAAA,GAAU,IAAA,GAAO,IAAI,mBAAA,CAAoB,iBAAA,EAAmB,OAAO,MAAM,CAAA;AAAA,EACzF,CAAA,EAAG,CAAC,IAAI,CAAC,CAAA;AAIT,EAAA,MAAM,YAAA,GAAe,OAAsB,IAAI,CAAA;AAC/C,EAAA,MAAM,EAAE,KAAA,EAAO,KAAA,EAAO,QAAA,EAAU,YAAA,EAAc,OAAM,GAAI,gBAAA;AAAA,IACtD,gBAAA,KAAqB,OAAO,WAAA,GAAc;AAAA,GAC5C;AAGA,EAAA,MAAM,CAAC,iBAAA,EAAmB,oBAAoB,CAAA,GAAI,QAAA;AAAA,IAAmB,MACnE,YAAY,YAAY;AAAA,GAC1B;AACA,EAAA,MAAM,CAAC,OAAA,EAAS,UAAU,CAAA,GAAI,SAAwB,IAAI,CAAA;AAe1D,EAAA,MAAM,eAAA,GAAkB,OAAO,YAAY,CAAA;AAC3C,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,eAAA,CAAgB,OAAA,GAAU,YAAA;AAAA,EAC5B,CAAA,EAAG,CAAC,YAAY,CAAC,CAAA;AAGjB,EAAA,MAAM,mBAAA,GAAsB,OAAO,gBAAgB,CAAA;AACnD,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,mBAAA,CAAoB,OAAA,GAAU,gBAAA;AAAA,EAChC,CAAA,EAAG,CAAC,gBAAgB,CAAC,CAAA;AACrB,EAAA,MAAM,WAAA,GAAc,OAAO,IAAI,CAAA;AAC/B,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,WAAA,CAAY,YAAY,IAAA,EAAM;AAChC,MAAA;AAAA,IACF;AACA,IAAA,WAAA,CAAY,OAAA,GAAU,IAAA;AACtB,IAAA,oBAAA,CAAqB,WAAA,CAAY,eAAA,CAAgB,OAAO,CAAC,CAAA;AACzD,IAAA,UAAA,CAAW,IAAI,CAAA;AACf,IAAA,KAAA,CAAM,mBAAA,CAAoB,OAAA,KAAY,IAAA,GAAO,WAAA,GAAc,MAAM,CAAA;AAAA,EACnE,CAAA,EAAG,CAAC,IAAA,EAAM,KAAK,CAAC,CAAA;AAEhB,EAAA,MAAM,gBAAA,GAAmB,QAAgC,MAAM;AAC7D,IAAA,IAAI,CAAC,KAAK,OAAA,EAAS;AACjB,MAAA,OAAO,CAAC,GAAG,IAAA,CAAK,OAAO,CAAA;AAAA,IACzB;AAEA,IAAA,MAAM,UAAA,GAAa,WAAA,KAAgB,YAAA,CAAa,OAAA,KAAY,eAAA,EAAgB,CAAA;AAG5E,IAAA,OAAO,aAAA,CAAc,KAAK,OAAA,EAAS,CAAA,EAAG,UAAU,CAAA,CAAA,EAAI,IAAA,CAAK,EAAE,CAAA,CAAE,CAAA;AAAA,EAC/D,CAAA,EAAG,CAAC,IAAA,EAAM,WAAW,CAAC,CAAA;AAItB,EAAA,MAAM,aAAA,GAAgB,QAAQ,MAAM;AAClC,IAAA,IAAI,eAAe,QAAA,IAAY,OAAA,KAAY,MAAA,IAAa,OAAA,CAAQ,WAAW,QAAA,EAAU;AACnF,MAAA,OAAO,IAAA;AAAA,IACT;AACA,IAAA,OAAO,IAAI,GAAA,CAAI,OAAA,CAAQ,OAAA,CAAQ,GAAA,CAAI,CAAC,MAAA,KAAW,CAAC,MAAA,CAAO,MAAA,EAAQ,MAAM,CAAC,CAAC,CAAA;AAAA,EACzE,CAAA,EAAG,CAAC,UAAA,EAAY,OAAO,CAAC,CAAA;AAGxB,EAAA,IAAI,QAAA,EAAU;AACZ,IAAA,MAAM,QAAA;AAAA,EACR;AAMA,EAAA,IAAI,IAAA,CAAK,QAAA,KAAa,IAAA,IAAQ,UAAA,KAAe,UAAA,EAAY;AACvD,IAAA,MAAM,IAAI,KAAA;AAAA,MACR,CAAA,iBAAA,EAAoB,KAAK,EAAE,CAAA,oNAAA;AAAA,KAG7B;AAAA,EACF;AAEA,EAAA,MAAM,QAAA,GAAW,KAAK,IAAA,KAAS,QAAA;AAC/B,EAAA,MAAM,SAAS,UAAA,KAAe,MAAA;AAC9B,EAAA,MAAM,WAAW,UAAA,KAAe,QAAA;AAChC,EAAA,MAAM,YAAY,KAAA,KAAU,WAAA;AAC5B,EAAA,MAAM,QAAA,GAAW,QAAA,KAAa,IAAA,IAAQ,SAAA,IAAa,QAAA;AACnD,EAAA,MAAM,eAAe,KAAA,KAAU,MAAA;AAC/B,EAAA,MAAM,QAAA,GAAW,YAAA,GAAe,WAAA,CAAY,KAAK,CAAA,GAAI,iBAAA;AAMrD,EAAA,MAAM,MAAA,GAAS,QAAA,IAAa,UAAA,KAAe,UAAA,IAAc,SAAA;AAEzD,EAAA,MAAM,eAAA,GAAkB,CACtB,IAAA,EACA,OAAA,KACS;AACT,IAAA,aAAA,GAAgB,EAAE,IAAA,EAAM,UAAA,EAAY,IAAA,CAAK,EAAA,EAAI,WAAW,IAAA,CAAK,GAAA,EAAI,EAAG,OAAA,EAAS,CAAA;AAAA,EAC/E,CAAA;AAGA,EAAA,MAAM,UAAA,GAAa,CAAC,iBAAA,KAAsC;AACxD,IAAA,IAAI,CAAC,YAAA,EAAc;AACjB,MAAA,oBAAA,CAAqB,iBAAiB,CAAA;AAAA,IACxC;AACA,IAAA,QAAA,GAAW,EAAE,IAAA,EAAM,iBAAA,EAAmB,iBAAA,EAAmB,CAAA;AAAA,EAC3D,CAAA;AAEA,EAAA,MAAM,YAAA,GAAe,CAAC,QAAA,KAA2B;AAC/C,IAAA,IAAI,QAAA,EAAU;AACZ,MAAA;AAAA,IACF;AACA,IAAA,IAAI,UAAU,MAAA,EAAQ;AACpB,MAAA,KAAA,EAAM;AAAA,IACR;AACA,IAAA,UAAA,CAAW,CAAC,QAAQ,CAAC,CAAA;AACrB,IAAA,eAAA,CAAgB,iBAAA,EAAmB,EAAE,QAAA,EAAU,CAAA;AAAA,EACjD,CAAA;AAEA,EAAA,MAAM,WAAA,GAAc,CAAC,QAAA,EAAkB,OAAA,KAA2B;AAChE,IAAA,IAAI,QAAA,EAAU;AACZ,MAAA;AAAA,IACF;AACA,IAAA,IAAI,UAAU,MAAA,EAAQ;AACpB,MAAA,KAAA,EAAM;AAAA,IACR;AACA,IAAA,UAAA,CAAW,OAAA,GAAU,CAAC,GAAG,QAAA,EAAU,QAAQ,CAAA,GAAI,QAAA,CAAS,MAAA,CAAO,CAAC,EAAA,KAAO,EAAA,KAAO,QAAQ,CAAC,CAAA;AACvF,IAAA,eAAA,CAAgB,OAAA,GAAU,iBAAA,GAAoB,mBAAA,EAAqB,EAAE,UAAU,CAAA;AAAA,EACjF,CAAA;AAEA,EAAA,MAAM,YAAA,GAAe,CAAC,KAAA,KAAiC;AACrD,IAAA,KAAA,CAAM,cAAA,EAAe;AACrB,IAAA,IAAI,QAAA,EAAU;AACZ,MAAA;AAAA,IACF;AACA,IAAA,MAAM,QAAA,GAA0C;AAAA,MAC9C,IAAA,EAAM,iBAAA;AAAA,MACN,iBAAA,EAAmB;AAAA,KACrB;AAGA,IAAA,QAAA,GAAW,QAAQ,CAAA;AAEnB,IAAA,IAAI,MAAA,EAAQ;AAGV,MAAA,QAAA,EAAS;AACT,MAAA,UAAA,CAAW,EAAE,eAAe,CAAA;AAC5B,MAAA,eAAA,CAAgB,WAAA,EAAa,EAAE,iBAAA,EAAmB,QAAA,EAAU,CAAA;AAC5D,MAAA;AAAA,IACF;AAEA,IAAA,MAAM,aAAA,GAAgB,KAAA,CAAM,iBAAA,EAAmB,IAAA,EAA4B,QAAQ,CAAA;AACnF,IAAA,QAAA,EAAS;AACT,IAAA,MAAM,YAAY,YAAA,EAAa;AAC/B,IAAA,MAAM,aAAA,GAAgB,YAAY,sBAAA,CAAuB;AAAA,MACvD,KAAA,EAAO,eAAA;AAAA,MACP,MAAA,EAAQ;AAAA,QACN,EAAA,EAAI,WAAA,CAAY,IAAA,CAAK,EAAE,CAAA;AAAA,QACvB,IAAA,EAAM,EAAE,CAAC,IAAA,CAAK,UAAU,OAAO,GAAG,KAAK,KAAA,EAAM;AAAA;AAAA;AAAA;AAAA,QAI7C,GAAG,kBAAkB,IAAI,CAAA;AAAA,QACzB,OAAA,EAAS,IAAA,CAAK,OAAA,CAAQ,GAAA,CAAI,CAAC,MAAA,MAAY;AAAA,UACrC,IAAI,MAAA,CAAO,EAAA;AAAA,UACX,WAAA,EAAa,EAAE,CAAC,IAAA,CAAK,UAAU,OAAO,GAAG,OAAO,IAAA;AAAK,SACvD,CAAE;AAAA,OACJ;AAAA,MACA,aAAA;AAAA,MACA,WAAA,EAAa,SAAA;AAAA,MACb,QAAA,EAAU,QAAA,CAAS,IAAA,CAAK,GAAG;AAAA,KAC5B,CAAA;AACD,IAAA,UAAA,GAAa;AAAA,MACX,OAAO,aAAA,CAAc,KAAA;AAAA,MACrB,UAAU,aAAA,CAAc,QAAA;AAAA,MACxB,QAAQ,aAAA,CAAc,MAAA;AAAA,MACtB,SAAA;AAAA,MACA;AAAA,KACD,CAAA;AAED,IAAA,MAAM,UAAU,aAAA,CAAc,QAAA;AAC9B,IAAA,UAAA;AAAA,MACE,CAAA,EAAG,CAAA,CAAE,eAAe,CAAA,CAAA,EAAI,CAAA,CAAE,iBAAA;AAAA,QACxB,IAAA,CAAK,KAAA,CAAM,aAAA,CAAc,KAAA,GAAQ,GAAG,CAAA;AAAA,QACpC,aAAA,CAAc;AAAA,OACf,CAAA,EAAG,OAAA,GAAU,CAAA,CAAA,EAAI,OAAO,KAAK,EAAE,CAAA;AAAA,KAClC;AACA,IAAA,eAAA,CAAgB,WAAA,EAAa;AAAA,MAC3B,iBAAA,EAAmB,QAAA;AAAA,MACnB,OAAO,aAAA,CAAc;AAAA,KACtB,CAAA;AAAA,EACH,CAAA;AAEA,EAAA,MAAM,UAAA,GAAa,CAAA,EAAG,IAAA,CAAK,EAAE,CAAA,SAAA,CAAA;AAG7B,EAAA,MAAM,YAAA,GACJ,iBAAiB,MAAA,IAAa,IAAA,CAAK,iBAAiB,MAAA,GAChD,YAAA,CAAa,IAAA,CAAK,YAAY,CAAA,GAC9B,IAAA;AAEN,EAAA,MAAM,UAAA,GAAa,gBAAA,CAAiB,GAAA,CAAI,CAAC,MAAA,KAAW;AAClD,IAAA,MAAM,OAAA,GAAU,QAAA,CAAS,QAAA,CAAS,MAAA,CAAO,EAAE,CAAA;AAE3C,IAAA,IAAI,WAAA;AACJ,IAAA,IAAI,QAAA,EAAU;AACZ,MAAA,MAAM,MAAA,GAAS,aAAA,EAAe,GAAA,CAAI,MAAA,CAAO,EAAE,CAAA;AAC3C,MAAA,WAAA,GAAc,WAAW,MAAA,GAAY,MAAA,GAAY,OAAO,cAAA,CAAe,MAAA,EAAQ,OAAO,CAAC,CAAA;AAAA,IACzF,WAAW,MAAA,EAAQ;AACjB,MAAA,WAAA,GAAc,MAAA,CAAO,OAAO,SAAS,CAAA;AAAA,IACvC;AACA,IAAA,MAAM,QAAQ,MAAA,CAAO,KAAA;AAIrB,IAAA,MAAM,OAAA,GACJ,KAAA,EAAO,IAAA,KAAS,OAAA,uBACb,KAAA,EAAA,EAAI,SAAA,EAAU,oBAAA,EAAqB,GAAA,EAAK,MAAM,GAAA,EAAK,GAAA,EAAK,KAAA,CAAM,GAAA,IAAO,IAAI,CAAA,GACxE,IAAA;AAEN,IAAA,MAAM,wBACJ,IAAA,CAAC,OAAA,EAAA,EAAM,SAAA,EAAU,cAAA,EAAe,gBAAc,WAAA,EAC5C,QAAA,EAAA;AAAA,sBAAA,GAAA;AAAA,QAAC,OAAA;AAAA,QAAA;AAAA,UACC,IAAA,EAAM,WAAW,OAAA,GAAU,UAAA;AAAA,UAC3B,IAAA,EAAM,QAAA,GAAW,CAAA,EAAG,IAAA,CAAK,EAAE,CAAA,QAAA,CAAA,GAAa,MAAA;AAAA,UACxC,OAAO,MAAA,CAAO,EAAA;AAAA,UACd,OAAA;AAAA,UACA,QAAA,EAAU,QAAA;AAAA,UACV,iBAAe,QAAA,IAAY,MAAA;AAAA,UAC3B,QAAA,EAAU,CAAC,CAAA,KACT,QAAA,GAAW,YAAA,CAAa,MAAA,CAAO,EAAE,CAAA,GAAI,WAAA,CAAY,MAAA,CAAO,EAAA,EAAI,CAAA,CAAE,OAAO,OAAO;AAAA;AAAA,OAEhF;AAAA,sBACA,GAAA,CAAC,MAAA,EAAA,EAAM,QAAA,EAAA,MAAA,CAAO,IAAA,EAAK,CAAA;AAAA,MAClB,OAAA;AAAA,MACA,MAAA,IAAU,MAAA,CAAO,QAAA,mBAChB,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,uBAAA,EAAwB,IAAA,EAAK,MAAA,EAC1C,QAAA,EAAA,MAAA,CAAO,QAAA,EACV,CAAA,GACE;AAAA,KAAA,EACN,CAAA;AAGF,IAAA,IAAI,KAAA,EAAO,SAAS,OAAA,EAAS;AAC3B,MAAA,uBAAO,GAAA,CAAC,QAAA,EAAA,EAA0B,QAAA,EAAA,KAAA,EAAA,EAAZ,MAAA,CAAO,EAAW,CAAA;AAAA,IAC1C;AAOA,IAAA,uBACE,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,oBAAA,EACZ,QAAA,EAAA;AAAA,MAAA,KAAA;AAAA,sBAED,GAAA;AAAA,QAAC,OAAA;AAAA,QAAA;AAAA,UACC,SAAA,EAAU,oBAAA;AAAA,UACV,KAAK,KAAA,CAAM,GAAA;AAAA,UACX,QAAA,EAAQ,IAAA;AAAA,UACR,OAAA,EAAQ,MAAA;AAAA,UACR,YAAA,EAAY,KAAA,CAAM,GAAA,IAAO,MAAA,CAAO,IAAA;AAAA,UAE/B,QAAA,EAAA,KAAA,CAAM,WAAA,mBACL,GAAA,CAAC,OAAA,EAAA,EAAM,MAAK,UAAA,EAAW,GAAA,EAAK,KAAA,CAAM,WAAA,EAAa,KAAA,EAAO,KAAA,CAAM,GAAA,IAAO,MAAA,CAAO,MAAM,CAAA,GAC9E;AAAA;AAAA;AACN,KAAA,EAAA,EAbuC,OAAO,EAchD,CAAA;AAAA,EAEJ,CAAC,CAAA;AAED,EAAA,4BACG,KAAA,EAAA,EAAI,SAAA,EAAU,SAAQ,IAAA,EAAM,MAAA,EAAQ,OAAO,KAAA,EACzC,QAAA,EAAA;AAAA,IAAA,IAAA,CAAK,KAAA,mBACJ,GAAA;AAAA,MAAC,aAAA;AAAA,MAAA;AAAA,QACC,OAAO,IAAA,CAAK,KAAA;AAAA,QACZ,UAAA;AAAA,QACC,GAAI,WAAA,KAAgB,MAAA,GAAY,EAAE,WAAA,KAAgB,EAAC;AAAA,QACnD,GAAI,YAAA,KAAiB,MAAA,GAAY,EAAE,YAAA,KAAiB,EAAC;AAAA,QACrD,GAAI,OAAA,KAAY,MAAA,GAAY,EAAE,OAAA,KAAY,EAAC;AAAA,QAC3C,GAAI,aAAA,KAAkB,MAAA,GAAY,EAAE,aAAA,KAAkB,EAAC;AAAA,QACvD,GAAI,MAAA,KAAW,MAAA,GAAY,EAAE,MAAA,KAAW;AAAC;AAAA,KAC5C,GACE,IAAA;AAAA,wBACH,MAAA,EAAA,EAAK,QAAA,EAAU,cACd,QAAA,kBAAA,IAAA,CAAC,UAAA,EAAA,EAAS,UAAU,QAAA,EACjB,QAAA,EAAA;AAAA,MAAA,YAAA,KAAiB,uBAChB,GAAA,CAAC,QAAA,EAAA,EAAO,IAAI,UAAA,EAAa,QAAA,EAAA,IAAA,CAAK,UAAS,CAAA,mBAEvC,GAAA;AAAA,QAAC,QAAA;AAAA,QAAA;AAAA,UACC,EAAA,EAAI,UAAA;AAAA,UAEJ,uBAAA,EAAyB,EAAE,MAAA,EAAQ,YAAA;AAAa;AAAA,OAClD;AAAA,MASD,QAAA,mBACC,GAAA,CAAC,KAAA,EAAA,EAAI,IAAA,EAAK,YAAA,EAAa,iBAAA,EAAiB,UAAA,EACrC,QAAA,EAAA,UAAA,EACH,CAAA,mBAEA,GAAA,CAAC,KAAA,EAAA,EAAK,QAAA,EAAA,UAAA,EAAW,CAAA;AAAA,MAGlB,QAAA,GAAW,uBACV,GAAA,CAAC,QAAA,EAAA,EAAO,MAAK,QAAA,EAAS,QAAA,EAAU,QAAA,EAC7B,QAAA,EAAA,CAAA,CAAE,MAAA,EACL;AAAA,KAAA,EAEJ,CAAA,EACF,CAAA;AAAA,oBACA,GAAA,CAAC,cAAA,EAAA,EAAe,EAAA,EAAI,CAAA,EAAG,IAAA,CAAK,EAAE,CAAA,SAAA,CAAA,EAC3B,QAAA,EAAA,QAAA,GAAW,kBAAA,CAAmB,OAAA,EAAS,CAAC,CAAA,GAAI,OAAA,EAC/C;AAAA,GAAA,EACF,CAAA;AAEJ;AChcO,SAASA,gBAAe,KAAA,EAA4B;AAGzD,EAAA,MAAM,OAAA,GAAU,YAAA,CAAa,KAAA,CAAM,OAAO,CAAA;AAC1C,EAAA,uBACEC,GAAAA,CAAC,qBAAA,EAAA,EAAsB,aAAA,EAAe,KAAA,CAAM,IAAA,EAAM,KAAA,EAAO,OAAA,EACvD,QAAA,kBAAAA,GAAAA,CAAC,cAAA,EAAA,EAAoB,GAAG,OAAO,CAAA,EACjC,CAAA;AAEJ","file":"chunk-UDQS5INQ.js","sourcesContent":["'use client';\n\nimport {\n ActivitySchemaError,\n type ItemOutcome,\n type LearnerResponse,\n type MultipleChoiceData,\n type MultipleChoiceLearnerResponse,\n type MultipleChoiceOption,\n type ScoringDetail,\n score,\n seededShuffle,\n validateActivity,\n xAPIBuilder,\n xapiDefinitionFor,\n} from '@intellectif/lk-core';\nimport { type CSSProperties, Fragment, useEffect, useMemo, useRef, useState } from 'react';\nimport { useActivityState } from '../../hooks/useActivityState.js';\nimport { useLkStrings } from '../../i18n/LkIntlProvider.js';\nimport type { LkStrings } from '../../i18n/strings.js';\nimport { ANONYMOUS_ACTOR, isDevelopment, objectIdFor, randomSessionId } from '../_internal.js';\nimport { ActivityMedia } from '../shared/ActivityMedia.js';\nimport { FeedbackRegion } from '../shared/FeedbackRegion.js';\nimport type { ActivityProps } from '../types.js';\n\n/**\n * Selected option ids carried by a learner response. Any other response shape\n * (a caller wiring the wrong item's state through `value`) reads as \"nothing\n * selected\" rather than throwing mid-exam.\n */\nfunction selectionOf(response: LearnerResponse | undefined): string[] {\n return response?.type === 'multiple-choice' ? response.selectedOptionIds : [];\n}\n\n/**\n * Whether an option is part of the correct answer, derived from the SERVER's\n * outcome — review mode never holds an answer key of its own.\n * {@link ScoringDetail.outcome} states this unambiguously. For 0.2-era details\n * that carry only the deprecated `correct` flag (which means \"the learner\n * ACTED correctly on this option\", not \"this option is the answer\") the fact\n * is still recoverable, because we know whether the learner selected it:\n * `isCorrect = wasSelected ? correct : !correct`.\n */\nfunction isAnswerOption(detail: ScoringDetail, wasSelected: boolean): boolean {\n switch (detail.outcome) {\n case 'correct':\n case 'incorrect-omission':\n return true;\n case 'incorrect':\n case 'correct-omission':\n return false;\n default:\n return wasSelected ? detail.correct : !detail.correct;\n }\n}\n\n/**\n * Announcement for `review` mode, built only from the server-supplied outcome.\n *\n * The authored overall feedback is appended by this function rather than being\n * baked into the score sentence, so a translation of \"Score 80%. Passed.\" never\n * has to carry the author's words with it.\n */\nfunction reviewAnnouncement(outcome: ItemOutcome | undefined, s: LkStrings): string | null {\n if (outcome === undefined) {\n return null;\n }\n if (outcome.status === 'scored') {\n const overall = outcome.feedback;\n return `${s.scoreAnnouncement(Math.round(outcome.score * 100), outcome.passed)}${\n overall ? ` ${overall}` : ''\n }`;\n }\n if (outcome.status === 'deferred') {\n // Never \"0%\": ungraded is not the same as wrong.\n return s.notGradedYet;\n }\n if (outcome.status === 'unscorable') {\n return s.noGradeAvailable;\n }\n // `graded` is left silent here deliberately: a returned GradeRecord on a\n // multiple-choice item has no rubric to render, and announcing a bare\n // percentage would duplicate what the marking already shows.\n return null;\n}\n\nexport interface MultipleChoiceProps extends ActivityProps<MultipleChoiceData> {\n /**\n * Seed for the deterministic option shuffle. Supply one (e.g. the attempt\n * id) to make the shuffled order reproducible server-side, stable across\n * page reloads, and identical between SSR and hydration. When absent, a\n * random per-mount session seed is used (order stable within the mount\n * only — the behaviour before `shuffleSeed` existed).\n */\n shuffleSeed?: string;\n}\n\nexport function MultipleChoice({\n data,\n onComplete,\n onSubmit,\n value,\n defaultValue,\n defaultSubmitted,\n onChange,\n renderMode = 'practice',\n outcome,\n sanitizeHtml,\n mediaBudget,\n mediaStrings,\n onInteraction,\n theme,\n locale,\n disabled,\n shuffleSeed,\n strings,\n}: MultipleChoiceProps) {\n const s = useLkStrings(strings);\n\n // Dev-only boundary validation (Req 2.3). Throwing during render lets\n // ActivityErrorBoundary catch it. Memoised so it only re-runs on data change.\n const devError = useMemo(() => {\n if (!isDevelopment()) {\n return null;\n }\n // A `redact()` projection cannot satisfy the full content schema — the\n // answer key is gone by design — and must not be checked against the\n // strict redacted schema either, since a `reveal: 'after-submit'`\n // projection legitimately carries answer-key fields for review renders.\n if (data.redacted === true) {\n return null;\n }\n const result = validateActivity('multiple-choice', data);\n return result.success ? null : new ActivitySchemaError('multiple-choice', result.errors);\n }, [data]);\n\n // Lazily created only when shuffling without a caller seed: avoids calling\n // crypto.randomUUID (absent on non-secure http origins) unless needed.\n const sessionIdRef = useRef<string | null>(null);\n const { state, start, complete, getTimeSpent, reset } = useActivityState(\n defaultSubmitted === true ? 'completed' : 'idle',\n );\n // Uncontrolled state. `defaultValue` seeds the mount only (React convention);\n // to re-seed later, remount with a `key` or drive the component with `value`.\n const [internalSelection, setInternalSelection] = useState<string[]>(() =>\n selectionOf(defaultValue),\n );\n const [summary, setSummary] = useState<string | null>(null);\n\n // Reset on data-prop CHANGE (Req 3.7). The identity guard makes the mount\n // run a no-op, which it always was before `defaultValue` existed — without\n // it this effect would wipe the seed immediately after the first render.\n // (It also makes the reset StrictMode-safe: a remount with unchanged data\n // no longer discards the learner's selection.)\n // Resets back to `defaultValue`, not to empty. Clearing looked safer — it\n // cannot carry a stale answer into a different question — but `data`\n // identity is a poor proxy for \"different question\": a parent that builds\n // entries in render (`activities={raw.map(redact)}`, the documented exam\n // pattern) hands over a new object every render, and clearing wiped every\n // RESTORED answer on the first unrelated re-render, silently. `defaultValue`\n // is by definition the caller's seed for the CURRENT data, which is what\n // FillInTheBlanks already reset to; the two now agree.\n const defaultValueRef = useRef(defaultValue);\n useEffect(() => {\n defaultValueRef.current = defaultValue;\n }, [defaultValue]);\n // A data change must return the item to its SEED, submitted state included —\n // resetting to idle unlocked work the learner had already committed.\n const defaultSubmittedRef = useRef(defaultSubmitted);\n useEffect(() => {\n defaultSubmittedRef.current = defaultSubmitted;\n }, [defaultSubmitted]);\n const lastDataRef = useRef(data);\n useEffect(() => {\n if (lastDataRef.current === data) {\n return;\n }\n lastDataRef.current = data;\n setInternalSelection(selectionOf(defaultValueRef.current));\n setSummary(null);\n reset(defaultSubmittedRef.current === true ? 'completed' : 'idle');\n }, [data, reset]);\n\n const displayedOptions = useMemo<MultipleChoiceOption[]>(() => {\n if (!data.shuffle) {\n return [...data.options];\n }\n // biome-ignore lint/suspicious/noAssignInExpressions: sanctioned lazy ref initialization\n const seedSource = shuffleSeed ?? (sessionIdRef.current ??= randomSessionId());\n // The seed string is unchanged from when the algorithm lived in this file,\n // so an order a consumer recorded against a seed still reproduces.\n return seededShuffle(data.options, `${seedSource}:${data.id}`);\n }, [data, shuffleSeed]);\n\n // Per-option correctness for `review`, indexed by option id. Null unless the\n // caller supplied a SCORED outcome — the only source of truth this mode has.\n const reviewDetails = useMemo(() => {\n if (renderMode !== 'review' || outcome === undefined || outcome.status !== 'scored') {\n return null;\n }\n return new Map(outcome.details.map((detail) => [detail.itemId, detail]));\n }, [renderMode, outcome]);\n\n // All hooks are called before these throws, so hook order stays stable.\n if (devError) {\n throw devError;\n }\n // Practice grades locally, which a redacted projection cannot support:\n // `score()` would throw from the submit handler — where no error boundary\n // can catch it — AFTER the learner has answered. Fail loudly at render\n // instead, in production too. Wiring an exam item into the self-grading\n // mode is precisely the accident `renderMode` exists to prevent.\n if (data.redacted === true && renderMode === 'practice') {\n throw new Error(\n `Multiple Choice \"${data.id}\" received redacted activity data in renderMode \"practice\", ` +\n 'which grades locally and has no answer key to grade against. ' +\n 'Render redacted data with renderMode=\"exam\" (server grades) or \"review\" (pass `outcome`).',\n );\n }\n\n const isSingle = data.mode === 'single';\n const isExam = renderMode === 'exam';\n const isReview = renderMode === 'review';\n const submitted = state === 'completed';\n const inactive = disabled === true || submitted || isReview;\n const isControlled = value !== undefined;\n const selected = isControlled ? selectionOf(value) : internalSelection;\n /**\n * Whether correctness and authored feedback may be shown. Practice earns it\n * by grading locally after submit; review shows what the server already\n * graded. Exam never reveals anything — not before, not after submit.\n */\n const reveal = isReview || (renderMode === 'practice' && submitted);\n\n const fireInteraction = (\n type: 'option-selected' | 'option-deselected' | 'submitted',\n payload: Record<string, unknown>,\n ): void => {\n onInteraction?.({ type, activityId: data.id, timestamp: Date.now(), payload });\n };\n\n /** Single funnel for every response change: owns state only when uncontrolled. */\n const emitChange = (selectedOptionIds: string[]): void => {\n if (!isControlled) {\n setInternalSelection(selectedOptionIds);\n }\n onChange?.({ type: 'multiple-choice', selectedOptionIds });\n };\n\n const selectSingle = (optionId: string): void => {\n if (inactive) {\n return;\n }\n if (state === 'idle') {\n start();\n }\n emitChange([optionId]);\n fireInteraction('option-selected', { optionId });\n };\n\n const toggleMulti = (optionId: string, checked: boolean): void => {\n if (inactive) {\n return;\n }\n if (state === 'idle') {\n start();\n }\n emitChange(checked ? [...selected, optionId] : selected.filter((id) => id !== optionId));\n fireInteraction(checked ? 'option-selected' : 'option-deselected', { optionId });\n };\n\n const handleSubmit = (event: React.FormEvent): void => {\n event.preventDefault();\n if (inactive) {\n return;\n }\n const response: MultipleChoiceLearnerResponse = {\n type: 'multiple-choice',\n selectedOptionIds: selected,\n };\n // Always first, and before anything that can throw: an exam runner must be\n // able to persist the raw response no matter what happens after.\n onSubmit?.(response);\n\n if (isExam) {\n // No score(), no answer key read, no onComplete, no xAPI — the server\n // grades. The announcement deliberately carries no correctness signal.\n complete();\n setSummary(s.answerSubmitted);\n fireInteraction('submitted', { selectedOptionIds: selected });\n return;\n }\n\n const scoringResult = score('multiple-choice', data as MultipleChoiceData, response);\n complete();\n const timeSpent = getTimeSpent();\n const xapiStatement = xAPIBuilder.buildAnsweredStatement({\n actor: ANONYMOUS_ACTOR,\n object: {\n id: objectIdFor(data.id),\n name: { [data.locale ?? 'en-US']: data.title },\n // Activity-type IRI, interaction type and correct-responses pattern\n // come from the REGISTERED descriptor, so a consumer-registered type\n // gets correct interop without touching this component.\n ...xapiDefinitionFor(data),\n choices: data.options.map((option) => ({\n id: option.id,\n description: { [data.locale ?? 'en-US']: option.text },\n })),\n },\n scoringResult,\n timeSpentMs: timeSpent,\n response: selected.join(','),\n });\n onComplete?.({\n score: scoringResult.score,\n maxScore: scoringResult.maxScore,\n passed: scoringResult.passed,\n timeSpent,\n xapiStatement,\n });\n // Core selects the authored overall feedback on `passed` (B3 fix).\n const overall = scoringResult.feedback;\n setSummary(\n `${s.answerSubmitted} ${s.scoreAnnouncement(\n Math.round(scoringResult.score * 100),\n scoringResult.passed,\n )}${overall ? ` ${overall}` : ''}`,\n );\n fireInteraction('submitted', {\n selectedOptionIds: selected,\n score: scoringResult.score,\n });\n };\n\n const questionId = `${data.id}-question`;\n // Rich text renders ONLY through a caller-supplied sanitiser; without one we\n // fall back to the plain-text field, escaped by React (types.ts HtmlSanitizer).\n const questionHtml =\n sanitizeHtml !== undefined && data.questionHtml !== undefined\n ? sanitizeHtml(data.questionHtml)\n : null;\n\n const optionList = displayedOptions.map((option) => {\n const checked = selected.includes(option.id);\n // `data-correct` means \"this option is part of the correct answer\".\n let correctness: string | undefined;\n if (isReview) {\n const detail = reviewDetails?.get(option.id);\n correctness = detail === undefined ? undefined : String(isAnswerOption(detail, checked));\n } else if (reveal) {\n correctness = String(option.isCorrect);\n }\n const media = option.media;\n // A picture goes INSIDE the label: it has no controls of its own, clicking\n // it is how a learner picks that option, and its `alt` joins the option's\n // accessible name. A recording cannot — see below.\n const picture =\n media?.type === 'image' ? (\n <img className=\"lk-mc-option-image\" src={media.url} alt={media.alt ?? ''} />\n ) : null;\n\n const label = (\n <label className=\"lk-mc-option\" data-correct={correctness}>\n <input\n type={isSingle ? 'radio' : 'checkbox'}\n name={isSingle ? `${data.id}-options` : undefined}\n value={option.id}\n checked={checked}\n disabled={inactive}\n aria-disabled={inactive || undefined}\n onChange={(e) =>\n isSingle ? selectSingle(option.id) : toggleMulti(option.id, e.target.checked)\n }\n />\n <span>{option.text}</span>\n {picture}\n {reveal && option.feedback ? (\n <span className=\"lk-mc-option-feedback\" role=\"note\">\n {option.feedback}\n </span>\n ) : null}\n </label>\n );\n\n if (media?.type !== 'audio') {\n return <Fragment key={option.id}>{label}</Fragment>;\n }\n\n // A recording sits OUTSIDE the label, deliberately. A <label> activates its\n // control for any click inside it, so an <audio> nested in one would select\n // the option the moment the learner pressed play — and a listening item is\n // answered by comparing all the recordings before choosing any of them.\n // Selecting an option must stay something the learner does on purpose.\n return (\n <div className=\"lk-mc-option-media\" key={option.id}>\n {label}\n {/* biome-ignore lint/a11y/useMediaCaption: captions are optional in the data contract — a <track> is rendered when captionsUrl is provided; absence is the author's documented choice (Req 14.5) */}\n <audio\n className=\"lk-mc-option-audio\"\n src={media.url}\n controls\n preload=\"none\"\n aria-label={media.alt ?? option.text}\n >\n {media.captionsUrl ? (\n <track kind=\"captions\" src={media.captionsUrl} label={media.alt ?? option.text} />\n ) : null}\n </audio>\n </div>\n );\n });\n\n return (\n <div className=\"lk-mc\" lang={locale} style={theme as CSSProperties | undefined}>\n {data.media ? (\n <ActivityMedia\n media={data.media}\n renderMode={renderMode}\n {...(mediaBudget !== undefined ? { mediaBudget } : {})}\n {...(mediaStrings !== undefined ? { mediaStrings } : {})}\n {...(strings !== undefined ? { strings } : {})}\n {...(onInteraction !== undefined ? { onInteraction } : {})}\n {...(locale !== undefined ? { locale } : {})}\n />\n ) : null}\n <form onSubmit={handleSubmit}>\n <fieldset disabled={inactive}>\n {questionHtml === null ? (\n <legend id={questionId}>{data.question}</legend>\n ) : (\n <legend\n id={questionId}\n // biome-ignore lint/security/noDangerouslySetInnerHtml: HTML is the output of the caller-supplied sanitizeHtml (types.ts HtmlSanitizer contract)\n dangerouslySetInnerHTML={{ __html: questionHtml }}\n />\n )}\n {/*\n single: an explicit radiogroup is meaningful (fieldset's implicit\n role is `group`, not `radiogroup`). multi: the fieldset + legend\n already provide a named group — an explicit role=\"group\" here would\n be a redundant, duplicate same-named group in the a11y tree, so we\n use a plain layout div. (Refines the design ARIA sketch.)\n */}\n {isSingle ? (\n <div role=\"radiogroup\" aria-labelledby={questionId}>\n {optionList}\n </div>\n ) : (\n <div>{optionList}</div>\n )}\n {/* review is read-only: there is nothing left to submit. */}\n {isReview ? null : (\n <button type=\"submit\" disabled={inactive}>\n {s.submit}\n </button>\n )}\n </fieldset>\n </form>\n <FeedbackRegion id={`${data.id}-feedback`}>\n {isReview ? reviewAnnouncement(outcome, s) : summary}\n </FeedbackRegion>\n </div>\n );\n}\n","'use client';\n\nimport { useLkStrings } from '../../i18n/LkIntlProvider.js';\nimport { ActivityErrorBoundary } from '../ActivityErrorBoundary.js';\nimport {\n MultipleChoice as MultipleChoiceCore,\n type MultipleChoiceProps,\n} from './MultipleChoice.js';\n\nexport type { MultipleChoiceProps } from './MultipleChoice.js';\n\n/**\n * Public Multiple Choice activity: the core component wrapped in\n * `ActivityErrorBoundary` so a render failure (incl. the dev schema-validation\n * throw) degrades to an accessible fallback instead of crashing the host.\n */\nexport function MultipleChoice(props: MultipleChoiceProps) {\n // Read here rather than in the boundary: a class component cannot call a\n // hook, and the fallback it renders is learner-facing text like any other.\n const strings = useLkStrings(props.strings);\n return (\n <ActivityErrorBoundary activityTitle={props.data?.title} strings={strings}>\n <MultipleChoiceCore {...props} />\n </ActivityErrorBoundary>\n );\n}\n"]}
@@ -1,29 +1,36 @@
1
1
  'use client';
2
- import { StimulusPanel } from './chunk-NVP6UEQQ.js';
3
- import { MultipleChoice } from './chunk-5N7OLFHB.js';
4
- import { FillInTheBlanks } from './chunk-XAGWBVK3.js';
5
- import { GapSelect } from './chunk-W4MXRZAB.js';
6
- import { Dictation } from './chunk-PSHNTDKG.js';
7
- import { WrittenResponse } from './chunk-YZWXVZB3.js';
8
- import { randomSessionId } from './chunk-DQERNAWI.js';
9
- import { useLkStrings } from './chunk-3Q4I4KT6.js';
2
+ import { StimulusPanel } from './chunk-AS2QGND5.js';
3
+ import { WrittenResponse } from './chunk-U432HTUW.js';
4
+ import { MultipleChoice } from './chunk-UDQS5INQ.js';
5
+ import { FillInTheBlanks } from './chunk-5CP2Z6IS.js';
6
+ import { GapSelect } from './chunk-IDYZ2FA2.js';
7
+ import { Dictation } from './chunk-NJAF34GN.js';
8
+ import { ReadAloud, stopCaptureGroup, SequenceSlotContext, takeOf, mintTake } from './chunk-5JFMXBH7.js';
9
+ import { randomSessionId } from './chunk-B4BCEQNO.js';
10
+ import { useLkStrings } from './chunk-AWQ53DON.js';
10
11
  import { isItemGroup, flattenSequence, resolvePlaybackPolicy, slotMediaKey, stimulusMediaKey } from '@intellectif/lk-core';
11
- import { useRef, useMemo, useState, useEffect } from 'react';
12
+ import { useRef, useState, useMemo, useEffect } from 'react';
12
13
  import { jsx, jsxs } from 'react/jsx-runtime';
13
14
 
14
15
  function entryKeyOf(slot) {
15
16
  return slot.slotId.split(".")[0] ?? slot.slotId;
16
17
  }
18
+ var captureScopeCount = 0;
17
19
  function SequencePane({
18
20
  className,
19
21
  hidden,
22
+ channel,
20
23
  children
21
24
  }) {
22
25
  const ref = useRef(null);
26
+ const captureGroup = channel?.captureGroup;
23
27
  useEffect(() => {
24
28
  if (!hidden) {
25
29
  return;
26
30
  }
31
+ if (captureGroup !== void 0) {
32
+ stopCaptureGroup(captureGroup);
33
+ }
27
34
  const node = ref.current;
28
35
  if (node === null) {
29
36
  return;
@@ -34,8 +41,8 @@ function SequencePane({
34
41
  element.pause();
35
42
  }
36
43
  }
37
- }, [hidden]);
38
- return /* @__PURE__ */ jsx("div", { className, hidden, ref, children });
44
+ }, [hidden, captureGroup]);
45
+ return /* @__PURE__ */ jsx("div", { className, hidden, ref, children: channel === void 0 ? children : /* @__PURE__ */ jsx(SequenceSlotContext.Provider, { value: channel, children }) });
39
46
  }
40
47
  function shufflesItsOwnOptions(entry) {
41
48
  const item = entry;
@@ -62,11 +69,18 @@ function ActivitySequence({
62
69
  locale,
63
70
  disabled,
64
71
  mediaBudget,
72
+ recordingBinding,
73
+ assessments,
74
+ workletUrl,
65
75
  strings
66
76
  }) {
67
77
  const sessionIdRef = useRef(null);
68
78
  const s = useLkStrings(strings);
69
79
  const mediaPlays = mediaBudget?.plays;
80
+ const [captureScope] = useState(() => {
81
+ captureScopeCount += 1;
82
+ return `lk-seq-capture-${captureScopeCount}`;
83
+ });
70
84
  const shuffleEntries = shuffle === "entries";
71
85
  const needsSeed = shuffleEntries || activities.some(
72
86
  (entry) => isItemGroup(entry) ? entry.shuffle === "within-group" || entry.items.some(shufflesItsOwnOptions) : shufflesItsOwnOptions(entry)
@@ -92,6 +106,20 @@ function ActivitySequence({
92
106
  return Math.min(Math.max(0, requested), Math.max(0, slots.length - 1));
93
107
  });
94
108
  const outcomesRef = useRef(null);
109
+ const outcomeSlotsRef = useRef(null);
110
+ const takesRef = useRef(/* @__PURE__ */ new Map());
111
+ const setGenerationRef = useRef(0);
112
+ const indexRef = useRef(index);
113
+ const reportedRef = useRef(false);
114
+ const channelsRef = useRef(/* @__PURE__ */ new Map());
115
+ const latestRef = useRef(null);
116
+ const aliveRef = useRef(true);
117
+ useEffect(() => {
118
+ aliveRef.current = true;
119
+ return () => {
120
+ aliveRef.current = false;
121
+ };
122
+ }, []);
95
123
  if (outcomesRef.current === null) {
96
124
  const submittedAtMount = new Set(submittedSlotIds ?? []);
97
125
  outcomesRef.current = slots.map(
@@ -103,6 +131,7 @@ function ActivitySequence({
103
131
  ...responses !== void 0 && Object.hasOwn(responses, slot.slotId) ? { response: responses[slot.slotId] } : {}
104
132
  } : null
105
133
  );
134
+ outcomeSlotsRef.current = slots;
106
135
  }
107
136
  const regionRef = useRef(null);
108
137
  const mountedRef = useRef(false);
@@ -120,8 +149,15 @@ function ActivitySequence({
120
149
  if (prevSetKey !== setKey) {
121
150
  setPrevSetKey(setKey);
122
151
  setIndex(0);
152
+ indexRef.current = 0;
123
153
  outcomesRef.current = slots.map(() => null);
154
+ outcomeSlotsRef.current = slots;
155
+ takesRef.current = /* @__PURE__ */ new Map();
156
+ setGenerationRef.current += 1;
157
+ channelsRef.current = /* @__PURE__ */ new Map();
158
+ reportedRef.current = false;
124
159
  }
160
+ const setGeneration = setGenerationRef.current;
125
161
  useEffect(() => {
126
162
  if (mountedRef.current) {
127
163
  regionRef.current?.focus();
@@ -141,6 +177,13 @@ function ActivitySequence({
141
177
  }
142
178
  onIndexChange?.(index);
143
179
  }, [index, defaultIndex, onIndexChange]);
180
+ useEffect(() => {
181
+ if (indexRef.current === index) {
182
+ return;
183
+ }
184
+ indexRef.current = index;
185
+ latestRef.current?.reportIfFinished();
186
+ }, [index]);
144
187
  const stimulusMounts = useMemo(() => {
145
188
  const mounts = /* @__PURE__ */ new Map();
146
189
  for (const slot of slots) {
@@ -162,23 +205,36 @@ function ActivitySequence({
162
205
  const go = (next) => {
163
206
  setIndex(next);
164
207
  };
165
- const record = (outcome) => {
166
- const current2 = outcomesRef.current;
167
- if (current2 === null || current2[outcome.index] != null) {
208
+ const reportIfFinished = () => {
209
+ const held = outcomesRef.current;
210
+ const presented = outcomeSlotsRef.current;
211
+ if (held === null || presented === null || reportedRef.current) {
168
212
  return;
169
213
  }
170
- const next = current2.slice();
171
- next[outcome.index] = outcome;
172
- outcomesRef.current = next;
173
- if (outcome.kind === "scored") {
174
- onActivityComplete?.(outcome.result, outcome.index, outcome.slotId);
175
- }
176
- if (!next.every((item) => item !== null)) {
177
- return;
214
+ const items = [];
215
+ for (const [at, outcome] of held.entries()) {
216
+ const take = takesRef.current.get(at);
217
+ if (take?.state === "in-flight" || take?.state === "retryable" && at === indexRef.current) {
218
+ return;
219
+ }
220
+ const slot = presented[at];
221
+ if (outcome !== null) {
222
+ items.push(outcome);
223
+ } else if (take?.state === "retryable" && renderMode === "practice" && slot !== void 0) {
224
+ items.push({
225
+ kind: "unsubmitted",
226
+ index: at,
227
+ slotId: slot.slotId,
228
+ activityId: slot.activity.id
229
+ });
230
+ } else {
231
+ return;
232
+ }
178
233
  }
179
- onFinished?.(next);
234
+ reportedRef.current = true;
235
+ onFinished?.(items);
180
236
  const scored = [];
181
- for (const item of next) {
237
+ for (const item of items) {
182
238
  if (item.kind !== "scored") {
183
239
  return;
184
240
  }
@@ -186,6 +242,79 @@ function ActivitySequence({
186
242
  }
187
243
  onComplete?.(scored);
188
244
  };
245
+ const record = (outcome, generation, take) => {
246
+ const held = outcomesRef.current;
247
+ if (held === null || generation !== setGenerationRef.current) {
248
+ return;
249
+ }
250
+ const at = outcome.index;
251
+ const holding = held[at];
252
+ let accepted = outcome;
253
+ if (take === void 0) {
254
+ if (holding != null) {
255
+ return;
256
+ }
257
+ } else {
258
+ if (holding?.kind === "restored") {
259
+ return;
260
+ }
261
+ const latest = takesRef.current.get(at);
262
+ if (outcome.kind === "responded") {
263
+ if (latest !== void 0 && (take < latest.take || take === latest.take && latest.response !== void 0)) {
264
+ return;
265
+ }
266
+ takesRef.current.set(at, {
267
+ take,
268
+ // A take this pager hears of first through its answer — a consumer's
269
+ // renderer has no channel — has nothing on its way.
270
+ state: latest?.take === take ? latest.state : "settled",
271
+ response: outcome.response
272
+ });
273
+ } else if (outcome.kind === "scored") {
274
+ if (latest?.take !== take || latest.response === void 0 || holding?.kind === "scored") {
275
+ return;
276
+ }
277
+ accepted = { ...outcome, response: latest.response };
278
+ } else {
279
+ return;
280
+ }
281
+ }
282
+ const next = held.slice();
283
+ next[at] = accepted;
284
+ outcomesRef.current = next;
285
+ if (accepted.kind === "scored") {
286
+ onActivityComplete?.(accepted.result, accepted.index, accepted.slotId);
287
+ }
288
+ reportIfFinished();
289
+ };
290
+ const updateTake = (generation, at, take, state) => {
291
+ if (!aliveRef.current || generation !== setGenerationRef.current) {
292
+ return;
293
+ }
294
+ const latest = takesRef.current.get(at);
295
+ if (latest !== void 0 && take < latest.take) {
296
+ return;
297
+ }
298
+ takesRef.current.set(at, latest?.take === take ? { ...latest, state } : { take, state });
299
+ reportIfFinished();
300
+ };
301
+ latestRef.current = { updateTake, reportIfFinished };
302
+ const channelFor = (slot) => {
303
+ const key = `${slot.index}::${slot.slotId}`;
304
+ let channel = channelsRef.current.get(key);
305
+ if (channel === void 0) {
306
+ const generation = setGeneration;
307
+ const at = slot.index;
308
+ channel = {
309
+ captureGroup: `${captureScope}::${slot.slotId}`,
310
+ takeState: (take, state) => {
311
+ latestRef.current?.updateTake(generation, at, take, state);
312
+ }
313
+ };
314
+ channelsRef.current.set(key, channel);
315
+ }
316
+ return channel;
317
+ };
189
318
  if (!current) {
190
319
  return /* @__PURE__ */ jsx("div", { className: "lk-seq" });
191
320
  }
@@ -246,6 +375,22 @@ function ActivitySequence({
246
375
  ...mediaBudget.strings !== void 0 ? { strings: mediaBudget.strings } : {}
247
376
  };
248
377
  };
378
+ const recordingBindingFor = (slot) => {
379
+ if (recordingBinding === void 0) {
380
+ return void 0;
381
+ }
382
+ const at = {
383
+ slotId: slot.slotId,
384
+ index: slot.index,
385
+ activityId: slot.activity.id
386
+ };
387
+ const { assess, playbackUrl } = recordingBinding;
388
+ return {
389
+ upload: (take) => recordingBinding.upload(take, at),
390
+ ...assess !== void 0 ? { assess: (ref) => assess.call(recordingBinding, ref, at) } : {},
391
+ ...playbackUrl !== void 0 ? { playbackUrl: (ref) => playbackUrl.call(recordingBinding, ref, at) } : {}
392
+ };
393
+ };
249
394
  const forwarded = {
250
395
  ...onInteraction ? { onInteraction } : {},
251
396
  renderMode,
@@ -262,6 +407,7 @@ function ActivitySequence({
262
407
  const restored = seedsApply && responses !== void 0 && Object.hasOwn(responses, slotId) ? responses[slotId] : void 0;
263
408
  const slotOutcome = outcomes !== void 0 && Object.hasOwn(outcomes, slotId) ? outcomes[slotId] : void 0;
264
409
  const slotBinding = bindingFor(slotMediaKey(slotId), slot);
410
+ const takesAnswers = renderMode === "practice" && activity.type === "read-aloud";
265
411
  const childProps = {
266
412
  // `defaultValue`, not `value`: the learner must be able to keep editing
267
413
  // a restored answer. A controlled `value` would freeze it unless the
@@ -269,11 +415,34 @@ function ActivitySequence({
269
415
  ...restored !== void 0 ? { defaultValue: restored } : {},
270
416
  ...seedsApply && submitted.has(slotId) ? { defaultSubmitted: true } : {},
271
417
  ...slotOutcome !== void 0 ? { outcome: slotOutcome } : {},
272
- onComplete: (result) => record({ kind: "scored", index: slotIndex, slotId, activityId, result }),
418
+ onComplete: (result) => record(
419
+ { kind: "scored", index: slotIndex, slotId, activityId, result },
420
+ setGeneration,
421
+ // The take the grade is for: stamped by `<ReadAloud>`, and for a
422
+ // consumer's renderer, which cannot stamp one, the slot's latest.
423
+ takesAnswers ? takeOf(result) ?? takesRef.current.get(slotIndex)?.take : void 0
424
+ ),
273
425
  onSubmit: (response) => {
426
+ if (setGeneration !== setGenerationRef.current) {
427
+ return;
428
+ }
274
429
  onSubmit?.(response, { slotId, index: slotIndex, activityId });
275
- if (renderMode !== "practice" && activity.type !== "written-response") {
276
- record({ kind: "responded", index: slotIndex, slotId, activityId, response });
430
+ if (activity.type === "written-response") {
431
+ return;
432
+ }
433
+ if (renderMode !== "practice") {
434
+ record(
435
+ { kind: "responded", index: slotIndex, slotId, activityId, response },
436
+ setGeneration
437
+ );
438
+ return;
439
+ }
440
+ if (takesAnswers) {
441
+ record(
442
+ { kind: "responded", index: slotIndex, slotId, activityId, response },
443
+ setGeneration,
444
+ takeOf(response) ?? mintTake()
445
+ );
277
446
  }
278
447
  },
279
448
  ...forwarded,
@@ -309,6 +478,20 @@ function ActivitySequence({
309
478
  if (activity.type === "dictation") {
310
479
  return /* @__PURE__ */ jsx(Dictation, { data: activity, ...childProps });
311
480
  }
481
+ if (activity.type === "read-aloud") {
482
+ const slotRecordingBinding = recordingBindingFor(slot);
483
+ const slotAssessment = assessments !== void 0 && Object.hasOwn(assessments, slotId) ? assessments[slotId] : void 0;
484
+ return /* @__PURE__ */ jsx(
485
+ ReadAloud,
486
+ {
487
+ data: activity,
488
+ ...childProps,
489
+ ...slotRecordingBinding !== void 0 ? { recordingBinding: slotRecordingBinding } : {},
490
+ ...slotAssessment !== void 0 ? { assessment: slotAssessment } : {},
491
+ ...workletUrl !== void 0 ? { workletUrl } : {}
492
+ }
493
+ );
494
+ }
312
495
  if (activity.type === "written-response") {
313
496
  const { onComplete: _neverGrades, ...writtenResponseProps } = childProps;
314
497
  return /* @__PURE__ */ jsx(
@@ -316,7 +499,10 @@ function ActivitySequence({
316
499
  {
317
500
  data: activity,
318
501
  ...writtenResponseProps,
319
- onSubmitted: (submission) => record({ kind: "submitted", index: slotIndex, slotId, activityId, submission })
502
+ onSubmitted: (submission) => record(
503
+ { kind: "submitted", index: slotIndex, slotId, activityId, submission },
504
+ setGeneration
505
+ )
320
506
  }
321
507
  );
322
508
  }
@@ -360,7 +546,16 @@ function ActivitySequence({
360
546
  "aria-label": s.questionProgress(index + 1, total),
361
547
  children: slots.map((slot) => {
362
548
  const slotKey = `${setKey}::slot-${slot.slotId}-${slot.activity.id}`;
363
- return /* @__PURE__ */ jsx(SequencePane, { className: "lk-seq-slot", hidden: slot.index !== index, children: renderSlot(slot) }, slotKey);
549
+ return /* @__PURE__ */ jsx(
550
+ SequencePane,
551
+ {
552
+ className: "lk-seq-slot",
553
+ hidden: slot.index !== index,
554
+ channel: channelFor(slot),
555
+ children: renderSlot(slot)
556
+ },
557
+ slotKey
558
+ );
364
559
  })
365
560
  }
366
561
  ),
@@ -390,5 +585,5 @@ function ActivitySequence({
390
585
  }
391
586
 
392
587
  export { ActivitySequence };
393
- //# sourceMappingURL=chunk-YILH4ETT.js.map
394
- //# sourceMappingURL=chunk-YILH4ETT.js.map
588
+ //# sourceMappingURL=chunk-UDZWYHDV.js.map
589
+ //# sourceMappingURL=chunk-UDZWYHDV.js.map