@tinyweb_dev/oe-exam-sdk 0.1.28 → 0.1.29

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 (796) hide show
  1. package/README.md +4 -4
  2. package/dist/api/contest-rooms.js +54 -1
  3. package/dist/api/contest-rooms.types.js +1 -1
  4. package/dist/api/exam-create.js +179 -1
  5. package/dist/api/exam-create.types.js +1 -1
  6. package/dist/api/exam-entry.js +68 -1
  7. package/dist/api/exam-entry.types.js +1 -1
  8. package/dist/api/exam-questions.js +149 -1
  9. package/dist/api/exam-questions.types.js +1 -1
  10. package/dist/api/exam-taking.js +84 -1
  11. package/dist/api/exam-taking.types.d.ts +7 -0
  12. package/dist/api/exam-taking.types.js +1 -1
  13. package/dist/api/index.js +8 -1
  14. package/dist/api/result-review.js +62 -1
  15. package/dist/api/result-review.types.js +16 -1
  16. package/dist/components/CueCardEditor.js +58 -1
  17. package/dist/components/common/ResolvedImage.js +15 -1
  18. package/dist/components/exams/CompletionDialog.js +8 -1
  19. package/dist/components/exams/CreateExamInfoForm.js +84 -1
  20. package/dist/components/exams/CreateExamPageContainer.js +110 -1
  21. package/dist/components/exams/ExamCreator.js +287 -1
  22. package/dist/components/exams/ExamPartTabs.js +138 -1
  23. package/dist/components/exams/ExamPreviewDialog.d.ts +6 -7
  24. package/dist/components/exams/ExamPreviewDialog.js +121 -1
  25. package/dist/components/exams/ExamPreviewPartContent.d.ts +18 -0
  26. package/dist/components/exams/ExamPreviewPartContent.js +27 -0
  27. package/dist/components/exams/ExamPreviewSidebar.d.ts +17 -0
  28. package/dist/components/exams/ExamPreviewSidebar.js +37 -0
  29. package/dist/components/exams/ExamQuestionGrid.js +37 -1
  30. package/dist/components/exams/ExamQuestionsPageContainer.js +112 -1
  31. package/dist/components/exams/ExamViewDialog.js +61 -1
  32. package/dist/components/exams/PartAudioUploader.js +9 -1
  33. package/dist/components/exams/TemplateSelector.js +76 -1
  34. package/dist/components/exams/UnsavedChangesDialog.js +7 -1
  35. package/dist/components/exams/api.js +3 -1
  36. package/dist/components/exams/create-exam.texts.js +1 -1
  37. package/dist/components/exams/create-exam.validation.js +18 -1
  38. package/dist/components/exams/entry/EntryRestartDialog.js +11 -1
  39. package/dist/components/exams/entry/StudentExamEntryPageContainer.js +358 -1
  40. package/dist/components/exams/entry/exam-entry.states.js +26 -1
  41. package/dist/components/exams/entry/exam-entry.utils.js +55 -1
  42. package/dist/components/exams/entry/fullscreen-loading.js +18 -1
  43. package/dist/components/exams/entry/index.js +4 -1
  44. package/dist/components/exams/exam-preview.types.d.ts +30 -0
  45. package/dist/components/exams/exam-preview.types.js +62 -0
  46. package/dist/components/exams/exam-question-status.js +45 -1
  47. package/dist/components/exams/exam-template.utils.js +18 -1
  48. package/dist/components/exams/index.d.ts +1 -0
  49. package/dist/components/exams/index.js +19 -1
  50. package/dist/components/exams/rooms/StudentContestRoomsPageContainer.js +183 -1
  51. package/dist/components/exams/rooms/contest-rooms.utils.js +93 -1
  52. package/dist/components/exams/rooms/index.js +2 -1
  53. package/dist/components/exams/take/ExamTakingApiContext.js +25 -1
  54. package/dist/components/exams/take/ExamTakingPageContainer.js +955 -1
  55. package/dist/components/exams/take/GenericQuestionRenderer.js +14 -1
  56. package/dist/components/exams/take/components/QuestionRenderer.js +207 -1
  57. package/dist/components/exams/take/components/index.js +11 -1
  58. package/dist/components/exams/take/components/question-renderers/ArticlesViewer.js +8 -1
  59. package/dist/components/exams/take/components/question-renderers/MoversAnswerTheQuestionRenderer.js +30 -1
  60. package/dist/components/exams/take/components/question-renderers/MoversChooseAdjectiveRenderer.js +63 -1
  61. package/dist/components/exams/take/components/question-renderers/MoversChooseBestAnswerRenderer.js +136 -1
  62. package/dist/components/exams/take/components/question-renderers/MoversColoringRenderer.js +379 -1
  63. package/dist/components/exams/take/components/question-renderers/MoversCrossOutWordGroupRenderer.d.ts +20 -0
  64. package/dist/components/exams/take/components/question-renderers/MoversCrossOutWordGroupRenderer.js +141 -0
  65. package/dist/components/exams/take/components/question-renderers/MoversFillWordHintRenderer.js +131 -1
  66. package/dist/components/exams/take/components/question-renderers/MoversGridFillRenderer.js +76 -1
  67. package/dist/components/exams/take/components/question-renderers/MoversLabelThePictureRenderer.js +68 -1
  68. package/dist/components/exams/take/components/question-renderers/MoversListenAndWriteRenderer.js +42 -1
  69. package/dist/components/exams/take/components/question-renderers/MoversMatchByWritingAnswerRenderer.js +131 -1
  70. package/dist/components/exams/take/components/question-renderers/MoversPictureChooseRenderer.js +76 -1
  71. package/dist/components/exams/take/components/question-renderers/MoversPictureFillBlankChooseRenderer.js +68 -1
  72. package/dist/components/exams/take/components/question-renderers/MoversReadingPassageRenderer.js +62 -1
  73. package/dist/components/exams/take/components/question-renderers/MoversTrueFalseRenderer.d.ts +1 -1
  74. package/dist/components/exams/take/components/question-renderers/MoversTrueFalseRenderer.js +51 -1
  75. package/dist/components/exams/take/components/question-renderers/MoversWordFillParagraphRenderer.js +171 -1
  76. package/dist/components/exams/take/components/question-renderers/MoversWordFillStructuredFormRenderer.js +198 -1
  77. package/dist/components/exams/take/components/question-renderers/MoversWordOrderAndMatchGroupRenderer.d.ts +15 -0
  78. package/dist/components/exams/take/components/question-renderers/MoversWordOrderAndMatchGroupRenderer.js +36 -0
  79. package/dist/components/exams/take/components/question-renderers/MoversWordOrderingRenderer.js +92 -1
  80. package/dist/components/exams/take/components/question-renderers/MoversWriteSentencesRenderer.js +11 -1
  81. package/dist/components/exams/take/components/question-renderers/MoversWritingRenderer.js +56 -1
  82. package/dist/components/exams/take/components/question-renderers/index.d.ts +2 -0
  83. package/dist/components/exams/take/components/question-renderers/index.js +25 -1
  84. package/dist/components/exams/take/components/speaking/SpeakingAudioPlayer.js +85 -1
  85. package/dist/components/exams/take/components/speaking/SpeakingContentFrame.js +10 -1
  86. package/dist/components/exams/take/components/speaking/SpeakingExamLayout.js +15 -1
  87. package/dist/components/exams/take/components/speaking/SpeakingHeader.js +6 -1
  88. package/dist/components/exams/take/components/speaking/SpeakingInstructionBar.js +8 -1
  89. package/dist/components/exams/take/components/speaking/SpeakingMicButton.js +59 -1
  90. package/dist/components/exams/take/components/speaking/SpeakingNavButton.js +7 -1
  91. package/dist/components/exams/take/components/speaking/SpeakingPartBadge.js +5 -1
  92. package/dist/components/exams/take/components/speaking/SpeakingReadDisplayedContent.module.css +120 -0
  93. package/dist/components/exams/take/components/speaking/SpeakingRecordingPreview.js +73 -1
  94. package/dist/components/exams/take/components/speaking/SpeakingReportErrorButton.js +8 -1
  95. package/dist/components/exams/take/components/speaking/SpeakingSpontaneousQA.module.css +344 -0
  96. package/dist/components/exams/take/components/speaking/SpeakingVideoIntro.js +32 -1
  97. package/dist/components/exams/take/components/speaking/index.js +1 -1
  98. package/dist/components/exams/take/components/speaking/renderers/SpeakingChooseAnswerGroupRenderer.js +63 -1
  99. package/dist/components/exams/take/components/speaking/renderers/SpeakingChooseObjectsRenderer.js +95 -1
  100. package/dist/components/exams/take/components/speaking/renderers/SpeakingCompareImagesRenderer.js +35 -1
  101. package/dist/components/exams/take/components/speaking/renderers/SpeakingConversationRenderer.js +147 -1
  102. package/dist/components/exams/take/components/speaking/renderers/SpeakingCueCardRenderer.js +232 -1
  103. package/dist/components/exams/take/components/speaking/renderers/SpeakingDescribeImageRenderer.js +193 -1
  104. package/dist/components/exams/take/components/speaking/renderers/SpeakingDragObjectsRenderer.js +155 -1
  105. package/dist/components/exams/take/components/speaking/renderers/SpeakingGNInterviewRenderer.js +212 -1
  106. package/dist/components/exams/take/components/speaking/renderers/SpeakingInfoExchangeRenderer.js +77 -1
  107. package/dist/components/exams/take/components/speaking/renderers/SpeakingListenAndSpeakAnswerRenderer.js +64 -1
  108. package/dist/components/exams/take/components/speaking/renderers/SpeakingOddOneOutRenderer.js +69 -1
  109. package/dist/components/exams/take/components/speaking/renderers/SpeakingQuestionListRenderer.js +64 -1
  110. package/dist/components/exams/take/components/speaking/renderers/SpeakingQuestionRenderer.js +92 -1
  111. package/dist/components/exams/take/components/speaking/renderers/SpeakingReadDisplayedContentRenderer.js +202 -1
  112. package/dist/components/exams/take/components/speaking/renderers/SpeakingSpontaneousQARenderer.js +339 -1
  113. package/dist/components/exams/take/components/speaking/renderers/SpeakingSpontaneousQaGroupRenderer.d.ts +19 -0
  114. package/dist/components/exams/take/components/speaking/renderers/SpeakingSpontaneousQaGroupRenderer.js +76 -0
  115. package/dist/components/exams/take/components/speaking/renderers/SpeakingStoryImagesRenderer.js +56 -1
  116. package/dist/components/exams/take/components/speaking/renderers/SpontaneousQACambridgeLayout.d.ts +24 -0
  117. package/dist/components/exams/take/components/speaking/renderers/SpontaneousQACambridgeLayout.js +120 -0
  118. package/dist/components/exams/take/components/speaking/renderers/SpontaneousQAInlineAudioPlayer.d.ts +8 -0
  119. package/dist/components/exams/take/components/speaking/renderers/SpontaneousQAInlineAudioPlayer.js +94 -0
  120. package/dist/components/exams/take/components/speaking/renderers/SpontaneousQASummerSkyAudioPlayer.d.ts +8 -0
  121. package/dist/components/exams/take/components/speaking/renderers/SpontaneousQASummerSkyAudioPlayer.js +101 -0
  122. package/dist/components/exams/take/components/speaking/renderers/index.d.ts +1 -0
  123. package/dist/components/exams/take/components/speaking/renderers/index.js +14 -1
  124. package/dist/components/exams/take/exam-taking.utils.d.ts +13 -0
  125. package/dist/components/exams/take/exam-taking.utils.js +203 -1
  126. package/dist/components/exams/take/hooks/index.js +2 -1
  127. package/dist/components/exams/take/hooks/useAnswerAutosave.js +71 -1
  128. package/dist/components/exams/take/hooks/useAttemptSubmit.js +38 -1
  129. package/dist/components/exams/take/hooks/useAutoSave.js +83 -1
  130. package/dist/components/exams/take/hooks/useExamCountdown.js +35 -1
  131. package/dist/components/exams/take/hooks/useExamTimer.js +97 -1
  132. package/dist/components/exams/take/hooks/useProctoringCamera.js +118 -1
  133. package/dist/components/exams/take/hooks/useProctoringSnapshot.js +76 -1
  134. package/dist/components/exams/take/hooks/useSpeakingConversationAudio.js +178 -1
  135. package/dist/components/exams/take/hooks/useSpeakingNavigation.js +51 -1
  136. package/dist/components/exams/take/hooks/useSpeakingRecorder.js +406 -1
  137. package/dist/components/exams/take/hooks/useSpeakingRendererSetup.js +136 -1
  138. package/dist/components/exams/take/hooks/useSpontaneousQaGroupRecorder.d.ts +11 -0
  139. package/dist/components/exams/take/hooks/useSpontaneousQaGroupRecorder.js +352 -0
  140. package/dist/components/exams/take/hooks/useSpontaneousQaGroupRendererSetup.d.ts +42 -0
  141. package/dist/components/exams/take/hooks/useSpontaneousQaGroupRendererSetup.js +166 -0
  142. package/dist/components/exams/take/hooks/useToeicAudio.js +35 -1
  143. package/dist/components/exams/take/index.js +6 -1
  144. package/dist/components/exams/take/types/speaking-cue-card.types.js +1 -1
  145. package/dist/components/exams/take/types/speaking.types.js +7 -1
  146. package/dist/components/exams/take/types.js +4 -1
  147. package/dist/components/exams/take/utils/answer-transformers.js +452 -1
  148. package/dist/components/exams/take/utils/cambridge-yle-inline-speaking.d.ts +21 -0
  149. package/dist/components/exams/take/utils/cambridge-yle-inline-speaking.js +44 -0
  150. package/dist/components/exams/take/utils/coloring-assignments.js +40 -1
  151. package/dist/components/exams/take/utils/cross-out-word-group.transformer.d.ts +21 -0
  152. package/dist/components/exams/take/utils/cross-out-word-group.transformer.js +83 -0
  153. package/dist/components/exams/take/utils/question-transformers.d.ts +43 -2
  154. package/dist/components/exams/take/utils/question-transformers.js +1137 -1
  155. package/dist/components/features/exam-entry/entry-state-cards.js +19 -1
  156. package/dist/components/features/exam-entry/index.js +2 -1
  157. package/dist/components/features/exam-entry/themed/CambridgeYleWaitingRoom.js +121 -1
  158. package/dist/components/features/exam-entry/themed/ThemedWaitingRoom.js +37 -1
  159. package/dist/components/features/exam-entry/themed/ToeicWaitingRoom.js +15 -1
  160. package/dist/components/features/exam-entry/themed/cambridge-yle-waiting-parts.js +80 -1
  161. package/dist/components/features/exam-entry/themed/components/CloseButton.js +6 -1
  162. package/dist/components/features/exam-entry/themed/components/ExamInfoPanel.js +17 -1
  163. package/dist/components/features/exam-entry/themed/components/ModeSelector.js +6 -1
  164. package/dist/components/features/exam-entry/themed/components/StartButton.js +12 -1
  165. package/dist/components/features/exam-entry/themed/components/index.js +4 -1
  166. package/dist/components/features/exam-entry/themed/index.js +3 -1
  167. package/dist/components/index.d.ts +1 -0
  168. package/dist/components/index.js +43 -1
  169. package/dist/components/questions/_shared/config/question-types.config.js +362 -1
  170. package/dist/components/questions/_shared/constants/color-palette.js +21 -1
  171. package/dist/components/questions/_shared/hooks/useAnswerGroupQuestionGroup.js +105 -1
  172. package/dist/components/questions/_shared/hooks/useBasicQuestionGroup.js +222 -1
  173. package/dist/components/questions/_shared/hooks/useCompareImagesGroup.js +140 -1
  174. package/dist/components/questions/_shared/hooks/useImageQuestionGroup.js +123 -1
  175. package/dist/components/questions/_shared/hooks/useInfoExchangeGroup.js +50 -1
  176. package/dist/components/questions/_shared/hooks/useQuestionFormState.js +54 -1
  177. package/dist/components/questions/_shared/hooks/useQuestionInitialization.js +123 -1
  178. package/dist/components/questions/_shared/hooks/useQuestionListGroup.js +121 -1
  179. package/dist/components/questions/_shared/hooks/useQuestionOnChange.js +70 -1
  180. package/dist/components/questions/_shared/hooks/useSceneQuestionGroup.js +194 -1
  181. package/dist/components/questions/_shared/index.js +7 -1
  182. package/dist/components/questions/_shared/types/answer-the-question.type.js +1 -1
  183. package/dist/components/questions/_shared/types/arrange-letters-into-words.type.js +1 -1
  184. package/dist/components/questions/_shared/types/choose-correct-adjective.type.js +3 -1
  185. package/dist/components/questions/_shared/types/choose-the-correct-answer-group.type.d.ts +1 -0
  186. package/dist/components/questions/_shared/types/choose-the-correct-answer-group.type.js +1 -1
  187. package/dist/components/questions/_shared/types/color-region.config.js +1 -1
  188. package/dist/components/questions/_shared/types/cross-out-word-group.type.d.ts +2 -0
  189. package/dist/components/questions/_shared/types/cross-out-word-group.type.js +1 -1
  190. package/dist/components/questions/_shared/types/fill-in-blank.type.js +1 -1
  191. package/dist/components/questions/_shared/types/fill-missing-words-in-grid.type.js +1 -1
  192. package/dist/components/questions/_shared/types/gn-speaking-interview.type.js +1 -1
  193. package/dist/components/questions/_shared/types/index.d.ts +1 -0
  194. package/dist/components/questions/_shared/types/index.js +14 -1
  195. package/dist/components/questions/_shared/types/label-the-picture.type.js +1 -1
  196. package/dist/components/questions/_shared/types/listen-and-choose-from-answer-group.type.js +1 -1
  197. package/dist/components/questions/_shared/types/listen-and-choose-objects-in-scene.type.js +1 -1
  198. package/dist/components/questions/_shared/types/listen-and-drag-objects-into-scene.type.js +1 -1
  199. package/dist/components/questions/_shared/types/listen-and-speak-answer.type.js +1 -1
  200. package/dist/components/questions/_shared/types/listen-and-speak-compare-images.type.js +1 -1
  201. package/dist/components/questions/_shared/types/listen-and-speak-image-group.type.js +1 -1
  202. package/dist/components/questions/_shared/types/listen-and-speak-info-exchange.type.js +1 -1
  203. package/dist/components/questions/_shared/types/listen-and-speak-odd-one-out.type.js +3 -1
  204. package/dist/components/questions/_shared/types/listen-and-speak-question-list.type.js +1 -1
  205. package/dist/components/questions/_shared/types/listen-and-speak-with-story-images.type.js +3 -1
  206. package/dist/components/questions/_shared/types/listen-and-tick-answer.type.js +1 -1
  207. package/dist/components/questions/_shared/types/listening-drawline.type.js +1 -1
  208. package/dist/components/questions/_shared/types/look-and-choose-answer.type.js +1 -1
  209. package/dist/components/questions/_shared/types/look-picture-fill-word-hint.type.js +1 -1
  210. package/dist/components/questions/_shared/types/match-by-writing-answer.type.js +1 -1
  211. package/dist/components/questions/_shared/types/multiple-choice.type.d.ts +5 -0
  212. package/dist/components/questions/_shared/types/multiple-choice.type.js +1 -1
  213. package/dist/components/questions/_shared/types/question-group.type.js +11 -1
  214. package/dist/components/questions/_shared/types/read-and-color-objects.type.js +1 -1
  215. package/dist/components/questions/_shared/types/read-displayed-content.type.js +1 -1
  216. package/dist/components/questions/_shared/types/read-passage-and-answer-questions.type.js +1 -1
  217. package/dist/components/questions/_shared/types/speaking-describe-image.type.js +1 -1
  218. package/dist/components/questions/_shared/types/spontaneous-qa-group.type.d.ts +37 -0
  219. package/dist/components/questions/_shared/types/spontaneous-qa-group.type.js +1 -0
  220. package/dist/components/questions/_shared/types/spontaneous-qa.type.js +1 -1
  221. package/dist/components/questions/_shared/types/true-false-group.type.js +1 -1
  222. package/dist/components/questions/_shared/types/word-fill-paragraph-with-options.type.js +1 -1
  223. package/dist/components/questions/_shared/types/word-fill-paragraph.type.js +1 -1
  224. package/dist/components/questions/_shared/types/word-fill-structured-form.type.d.ts +20 -11
  225. package/dist/components/questions/_shared/types/word-fill-structured-form.type.js +1 -1
  226. package/dist/components/questions/_shared/types/word-order-and-match-group.type.d.ts +38 -0
  227. package/dist/components/questions/_shared/types/word-order-and-match-group.type.js +1 -0
  228. package/dist/components/questions/_shared/types/word-ordering.type.js +1 -1
  229. package/dist/components/questions/_shared/types/write-a-short-letter.type.js +1 -1
  230. package/dist/components/questions/_shared/types/write-correct-verb-form.type.js +3 -1
  231. package/dist/components/questions/_shared/types/write-sentences.type.js +1 -1
  232. package/dist/components/questions/_shared/types/write-short-paragraph.type.js +1 -1
  233. package/dist/components/questions/_shared/utils/answer-builder.js +79 -1
  234. package/dist/components/questions/_shared/utils/createColorRegionRegistration.js +39 -1
  235. package/dist/components/questions/_shared/utils/id-generator.js +3 -1
  236. package/dist/components/questions/_shared/utils/parseFillBlankStem.d.ts +30 -0
  237. package/dist/components/questions/_shared/utils/parseFillBlankStem.js +69 -0
  238. package/dist/components/questions/_shared/utils/question-validation.js +21 -1
  239. package/dist/components/questions/components/ColorNode.js +85 -1
  240. package/dist/components/questions/components/ColorRegionClient.js +545 -1
  241. package/dist/components/questions/components/ColorRegionCreator.js +420 -1
  242. package/dist/components/questions/components/ColorRegionNode.js +59 -1
  243. package/dist/components/questions/components/CreatorGuide.js +103 -1
  244. package/dist/components/questions/components/NameNode.js +26 -1
  245. package/dist/components/questions/components/QuestionBankOption.js +46 -1
  246. package/dist/components/questions/components/QuestionBasicInfoForm.js +9 -1
  247. package/dist/components/questions/components/QuestionCommonFields.js +33 -1
  248. package/dist/components/questions/components/QuestionSearchDropdown.js +258 -1
  249. package/dist/components/questions/components/QuestionTypeSelector.js +32 -1
  250. package/dist/components/questions/components/QuestionTypeSpecificForms.js +6 -1
  251. package/dist/components/questions/components/RegionNode.js +94 -1
  252. package/dist/components/questions/components/index.js +14 -1
  253. package/dist/components/questions/creator/QuestionCreator.js +90 -1
  254. package/dist/components/questions/creator/QuestionGroupCreator.js +119 -1
  255. package/dist/components/questions/creator/dedicated-component-router.js +79 -1
  256. package/dist/components/questions/creator/index.js +3 -1
  257. package/dist/components/questions/creator/question-type-registry.js +90 -1
  258. package/dist/components/questions/groups/ArticlesGroupCard.js +121 -1
  259. package/dist/components/questions/groups/BasicQuestionGroupCard.js +50 -1
  260. package/dist/components/questions/groups/DocumentGroupCard.js +114 -1
  261. package/dist/components/questions/groups/MatchAnswerGroupCard.js +25 -1
  262. package/dist/components/questions/index.d.ts +1 -0
  263. package/dist/components/questions/index.js +11 -1
  264. package/dist/components/questions/question-bank/EditQuestionBankDialog.d.ts +2 -0
  265. package/dist/components/questions/question-bank/EditQuestionBankDialog.js +114 -0
  266. package/dist/components/questions/question-bank/QuestionEditorRenderer.d.ts +23 -0
  267. package/dist/components/questions/question-bank/QuestionEditorRenderer.js +75 -0
  268. package/dist/components/questions/question-bank/difficulty.d.ts +15 -0
  269. package/dist/components/questions/question-bank/difficulty.js +33 -0
  270. package/dist/components/questions/question-bank/editor-adapters/choice-adapters.d.ts +5 -0
  271. package/dist/components/questions/question-bank/editor-adapters/choice-adapters.js +101 -0
  272. package/dist/components/questions/question-bank/editor-adapters/index.d.ts +5 -0
  273. package/dist/components/questions/question-bank/editor-adapters/index.js +4 -0
  274. package/dist/components/questions/question-bank/editor-adapters/media-adapters.d.ts +6 -0
  275. package/dist/components/questions/question-bank/editor-adapters/media-adapters.js +140 -0
  276. package/dist/components/questions/question-bank/editor-adapters/types.d.ts +1 -0
  277. package/dist/components/questions/question-bank/editor-adapters/types.js +1 -0
  278. package/dist/components/questions/question-bank/editor-adapters/unsupported-fallback.d.ts +8 -0
  279. package/dist/components/questions/question-bank/editor-adapters/unsupported-fallback.js +6 -0
  280. package/dist/components/questions/question-bank/editor-adapters/writing-adapters.d.ts +5 -0
  281. package/dist/components/questions/question-bank/editor-adapters/writing-adapters.js +114 -0
  282. package/dist/components/questions/question-bank/index.d.ts +4 -0
  283. package/dist/components/questions/question-bank/index.js +3 -0
  284. package/dist/components/questions/question-bank/types.d.ts +59 -0
  285. package/dist/components/questions/question-bank/types.js +1 -0
  286. package/dist/components/questions/types/answer-the-question/AnswerTheQuestionClient.js +37 -1
  287. package/dist/components/questions/types/answer-the-question/AnswerTheQuestionCreator.js +351 -1
  288. package/dist/components/questions/types/answer-the-question/register.js +67 -1
  289. package/dist/components/questions/types/answer-the-question/transform.js +36 -1
  290. package/dist/components/questions/types/arrange-letters-into-words/ArrangeLettersIntoWordsClient.js +141 -1
  291. package/dist/components/questions/types/arrange-letters-into-words/ArrangeLettersIntoWordsCreator.js +118 -1
  292. package/dist/components/questions/types/arrange-letters-into-words/index.js +2 -1
  293. package/dist/components/questions/types/arrange-letters-into-words/register.js +4 -1
  294. package/dist/components/questions/types/arrange-letters-into-words/transform.js +24 -1
  295. package/dist/components/questions/types/choose-correct-adjective/ChooseCorrectAdjectiveClient.js +74 -1
  296. package/dist/components/questions/types/choose-correct-adjective/ChooseCorrectAdjectiveCreator.js +169 -1
  297. package/dist/components/questions/types/choose-correct-adjective/index.js +2 -1
  298. package/dist/components/questions/types/choose-correct-adjective/register.js +55 -1
  299. package/dist/components/questions/types/choose-correct-adjective/transform.js +16 -1
  300. package/dist/components/questions/types/choose-the-correct-answer/ChooseTheCorrectAnswerClient.js +149 -1
  301. package/dist/components/questions/types/choose-the-correct-answer/ChooseTheCorrectAnswerCreator.js +568 -1
  302. package/dist/components/questions/types/choose-the-correct-answer/index.js +3 -1
  303. package/dist/components/questions/types/choose-the-correct-answer/normalize.d.ts +9 -0
  304. package/dist/components/questions/types/choose-the-correct-answer/normalize.js +88 -1
  305. package/dist/components/questions/types/choose-the-correct-answer/register.js +99 -1
  306. package/dist/components/questions/types/choose-the-correct-answer/transform.js +70 -1
  307. package/dist/components/questions/types/choose-the-correct-answer-group/ChooseTheCorrectAnswerGroupClient.js +22 -1
  308. package/dist/components/questions/types/choose-the-correct-answer-group/ChooseTheCorrectAnswerGroupCreator.js +163 -1
  309. package/dist/components/questions/types/choose-the-correct-answer-group/index.js +3 -1
  310. package/dist/components/questions/types/choose-the-correct-answer-group/map-choose-the-correct-answer-group-data.js +94 -1
  311. package/dist/components/questions/types/choose-the-correct-answer-group/register.js +36 -1
  312. package/dist/components/questions/types/choose-the-correct-answer-group/transform.js +55 -1
  313. package/dist/components/questions/types/cross-out-word-group/CrossOutWordGroupClient.js +27 -1
  314. package/dist/components/questions/types/cross-out-word-group/CrossOutWordGroupCreator.js +231 -1
  315. package/dist/components/questions/types/cross-out-word-group/index.js +5 -1
  316. package/dist/components/questions/types/cross-out-word-group/map-cross-out-word-group-data.d.ts +1 -1
  317. package/dist/components/questions/types/cross-out-word-group/map-cross-out-word-group-data.js +110 -1
  318. package/dist/components/questions/types/cross-out-word-group/register.js +36 -1
  319. package/dist/components/questions/types/cross-out-word-group/transform.js +67 -1
  320. package/dist/components/questions/types/fill-in-blank/FillInBlankClient.js +275 -1
  321. package/dist/components/questions/types/fill-in-blank/FillInBlankCreator.js +464 -1
  322. package/dist/components/questions/types/fill-in-blank/index.js +2 -1
  323. package/dist/components/questions/types/fill-in-blank/register.js +148 -1
  324. package/dist/components/questions/types/fill-in-blank/transform.js +88 -1
  325. package/dist/components/questions/types/fill-missing-words-in-grid/FillMissingWordsInGridClient.js +117 -1
  326. package/dist/components/questions/types/fill-missing-words-in-grid/FillMissingWordsInGridCreator.js +671 -1
  327. package/dist/components/questions/types/fill-missing-words-in-grid/index.js +2 -1
  328. package/dist/components/questions/types/fill-missing-words-in-grid/register.js +37 -1
  329. package/dist/components/questions/types/fill-missing-words-in-grid/transform.js +15 -1
  330. package/dist/components/questions/types/gn-speaking-interview/GNSpeakingInterviewCreator.js +262 -1
  331. package/dist/components/questions/types/gn-speaking-interview/register.js +121 -1
  332. package/dist/components/questions/types/gn-speaking-interview/transform.js +48 -1
  333. package/dist/components/questions/types/image-object-matching/ImageObjectMatchingClient.js +16 -1
  334. package/dist/components/questions/types/image-object-matching/ImageObjectMatchingCreator.js +27 -1
  335. package/dist/components/questions/types/image-object-matching/index.js +2 -1
  336. package/dist/components/questions/types/image-object-matching/register.js +3 -1
  337. package/dist/components/questions/types/image-object-matching/transform.js +2 -1
  338. package/dist/components/questions/types/label-the-picture/LabelThePictureClient.js +102 -1
  339. package/dist/components/questions/types/label-the-picture/LabelThePictureCreator.js +344 -1
  340. package/dist/components/questions/types/label-the-picture/index.js +2 -1
  341. package/dist/components/questions/types/label-the-picture/register.js +54 -1
  342. package/dist/components/questions/types/label-the-picture/transform.js +28 -1
  343. package/dist/components/questions/types/listen-and-choose-from-answer-group/ListenAndChooseFromAnswerGroupClient.js +78 -1
  344. package/dist/components/questions/types/listen-and-choose-from-answer-group/ListenAndChooseFromAnswerGroupCreator.js +232 -1
  345. package/dist/components/questions/types/listen-and-choose-from-answer-group/index.js +2 -1
  346. package/dist/components/questions/types/listen-and-choose-from-answer-group/register.js +82 -1
  347. package/dist/components/questions/types/listen-and-choose-from-answer-group/transform.js +27 -1
  348. package/dist/components/questions/types/listen-and-choose-objects-in-scene/ListenAndChooseObjectsInSceneClient.js +70 -1
  349. package/dist/components/questions/types/listen-and-choose-objects-in-scene/ListenAndChooseObjectsInSceneCreator.js +386 -1
  350. package/dist/components/questions/types/listen-and-choose-objects-in-scene/index.js +2 -1
  351. package/dist/components/questions/types/listen-and-choose-objects-in-scene/register.js +100 -1
  352. package/dist/components/questions/types/listen-and-choose-objects-in-scene/transform.js +33 -1
  353. package/dist/components/questions/types/listen-and-drag-objects-into-scene/ListenAndDragObjectsIntoSceneClient.js +106 -1
  354. package/dist/components/questions/types/listen-and-drag-objects-into-scene/ListenAndDragObjectsIntoSceneCreator.js +457 -1
  355. package/dist/components/questions/types/listen-and-drag-objects-into-scene/index.js +2 -1
  356. package/dist/components/questions/types/listen-and-drag-objects-into-scene/register.js +110 -1
  357. package/dist/components/questions/types/listen-and-drag-objects-into-scene/transform.js +35 -1
  358. package/dist/components/questions/types/listen-and-speak-answer/ArrowIndicator.js +8 -1
  359. package/dist/components/questions/types/listen-and-speak-answer/ListenAndSpeakAnswerClient.js +22 -1
  360. package/dist/components/questions/types/listen-and-speak-answer/ListenAndSpeakAnswerCreator.js +291 -1
  361. package/dist/components/questions/types/listen-and-speak-answer/index.js +2 -1
  362. package/dist/components/questions/types/listen-and-speak-answer/register.js +84 -1
  363. package/dist/components/questions/types/listen-and-speak-answer/transform.js +27 -1
  364. package/dist/components/questions/types/listen-and-speak-compare-images/ListenAndSpeakCompareImagesClient.js +32 -1
  365. package/dist/components/questions/types/listen-and-speak-compare-images/ListenAndSpeakCompareImagesCreator.js +295 -1
  366. package/dist/components/questions/types/listen-and-speak-compare-images/register.js +79 -1
  367. package/dist/components/questions/types/listen-and-speak-compare-images/transform.js +27 -1
  368. package/dist/components/questions/types/listen-and-speak-image-group/ListenAndSpeakImageGroupClient.js +34 -1
  369. package/dist/components/questions/types/listen-and-speak-image-group/ListenAndSpeakImageGroupCreator.js +285 -1
  370. package/dist/components/questions/types/listen-and-speak-image-group/register.js +81 -1
  371. package/dist/components/questions/types/listen-and-speak-image-group/transform.js +26 -1
  372. package/dist/components/questions/types/listen-and-speak-info-exchange/ListenAndSpeakInfoExchangeClient.js +38 -1
  373. package/dist/components/questions/types/listen-and-speak-info-exchange/ListenAndSpeakInfoExchangeCreator.js +385 -1
  374. package/dist/components/questions/types/listen-and-speak-info-exchange/register.js +87 -1
  375. package/dist/components/questions/types/listen-and-speak-info-exchange/transform.js +32 -1
  376. package/dist/components/questions/types/listen-and-speak-odd-one-out/ListenAndSpeakOddOneOutClient.js +26 -1
  377. package/dist/components/questions/types/listen-and-speak-odd-one-out/ListenAndSpeakOddOneOutCreator.js +247 -1
  378. package/dist/components/questions/types/listen-and-speak-odd-one-out/register.js +59 -1
  379. package/dist/components/questions/types/listen-and-speak-odd-one-out/transform.js +18 -1
  380. package/dist/components/questions/types/listen-and-speak-question-list/ListenAndSpeakQuestionListClient.js +26 -1
  381. package/dist/components/questions/types/listen-and-speak-question-list/ListenAndSpeakQuestionListCreator.js +256 -1
  382. package/dist/components/questions/types/listen-and-speak-question-list/register.js +81 -1
  383. package/dist/components/questions/types/listen-and-speak-question-list/transform.js +26 -1
  384. package/dist/components/questions/types/listen-and-speak-with-story-images/ListenAndSpeakWithStoryImagesClient.js +26 -1
  385. package/dist/components/questions/types/listen-and-speak-with-story-images/ListenAndSpeakWithStoryImagesCreator.js +230 -1
  386. package/dist/components/questions/types/listen-and-speak-with-story-images/register.js +56 -1
  387. package/dist/components/questions/types/listen-and-speak-with-story-images/transform.js +16 -1
  388. package/dist/components/questions/types/look-picture-choose-correct-answer/LookPictureChooseCorrectAnswerClient.js +72 -1
  389. package/dist/components/questions/types/look-picture-choose-correct-answer/LookPictureChooseCorrectAnswerCreator.js +230 -1
  390. package/dist/components/questions/types/look-picture-choose-correct-answer/index.js +1 -1
  391. package/dist/components/questions/types/look-picture-choose-correct-answer/register.js +66 -1
  392. package/dist/components/questions/types/look-picture-choose-correct-answer/transform.js +42 -1
  393. package/dist/components/questions/types/look-picture-fill-blank-choose-answer/LookPictureFillBlankChooseAnswerClient.js +96 -1
  394. package/dist/components/questions/types/look-picture-fill-blank-choose-answer/LookPictureFillBlankChooseAnswerCreator.js +238 -1
  395. package/dist/components/questions/types/look-picture-fill-blank-choose-answer/index.js +2 -1
  396. package/dist/components/questions/types/look-picture-fill-blank-choose-answer/register.js +76 -1
  397. package/dist/components/questions/types/look-picture-fill-blank-choose-answer/transform.js +19 -1
  398. package/dist/components/questions/types/look-picture-fill-word-hint/LookPictureFillWordHintClient.js +134 -1
  399. package/dist/components/questions/types/look-picture-fill-word-hint/LookPictureFillWordHintCreator.js +617 -1
  400. package/dist/components/questions/types/look-picture-fill-word-hint/index.js +2 -1
  401. package/dist/components/questions/types/look-picture-fill-word-hint/register.js +82 -1
  402. package/dist/components/questions/types/look-picture-fill-word-hint/transform.js +29 -1
  403. package/dist/components/questions/types/match-by-writing-answer/MatchByWritingAnswerClient.js +38 -1
  404. package/dist/components/questions/types/match-by-writing-answer/MatchByWritingAnswerCreator.js +359 -1
  405. package/dist/components/questions/types/match-by-writing-answer/index.js +3 -1
  406. package/dist/components/questions/types/match-by-writing-answer/register.js +58 -1
  407. package/dist/components/questions/types/match-by-writing-answer/transform.js +61 -1
  408. package/dist/components/questions/types/read-and-color-objects/ReadAndColorObjectsClient.js +16 -1
  409. package/dist/components/questions/types/read-and-color-objects/ReadAndColorObjectsCreator.js +23 -1
  410. package/dist/components/questions/types/read-and-color-objects/index.js +2 -1
  411. package/dist/components/questions/types/read-and-color-objects/register.js +3 -1
  412. package/dist/components/questions/types/read-and-color-objects/transform.js +36 -1
  413. package/dist/components/questions/types/read-displayed-content/ReadDisplayedContentCreator.js +125 -1
  414. package/dist/components/questions/types/read-displayed-content/register.js +64 -1
  415. package/dist/components/questions/types/read-displayed-content/transform.js +12 -1
  416. package/dist/components/questions/types/read-passage-and-answer-questions/ReadPassageAndAnswerQuestionsClient.js +65 -1
  417. package/dist/components/questions/types/read-passage-and-answer-questions/ReadPassageAndAnswerQuestionsCreator.js +511 -1
  418. package/dist/components/questions/types/read-passage-and-answer-questions/index.js +2 -1
  419. package/dist/components/questions/types/read-passage-and-answer-questions/register.js +58 -1
  420. package/dist/components/questions/types/read-passage-and-answer-questions/transform.js +28 -1
  421. package/dist/components/questions/types/speaking-conversation/SpeakingConversationCreator.js +373 -1
  422. package/dist/components/questions/types/speaking-conversation/SpeakingConversationPreviewClient.js +41 -1
  423. package/dist/components/questions/types/speaking-conversation/register.js +110 -1
  424. package/dist/components/questions/types/speaking-conversation/transform.js +34 -1
  425. package/dist/components/questions/types/speaking-cue-card/SpeakingCueCardCreator.js +74 -1
  426. package/dist/components/questions/types/speaking-cue-card/register.js +52 -1
  427. package/dist/components/questions/types/speaking-cue-card/transform.js +12 -1
  428. package/dist/components/questions/types/speaking-describe-image/SpeakingDescribeImageClient.js +10 -1
  429. package/dist/components/questions/types/speaking-describe-image/SpeakingDescribeImageCreator.js +161 -1
  430. package/dist/components/questions/types/speaking-describe-image/index.js +2 -1
  431. package/dist/components/questions/types/speaking-describe-image/register.js +69 -1
  432. package/dist/components/questions/types/speaking-describe-image/transform.js +16 -1
  433. package/dist/components/questions/types/spontaneous-qa/SpontaneousQACreator.js +234 -1
  434. package/dist/components/questions/types/spontaneous-qa/register.js +93 -1
  435. package/dist/components/questions/types/spontaneous-qa/transform.js +34 -1
  436. package/dist/components/questions/types/spontaneous-qa-group/SpontaneousQaGroupClient.d.ts +2 -0
  437. package/dist/components/questions/types/spontaneous-qa-group/SpontaneousQaGroupClient.js +9 -0
  438. package/dist/components/questions/types/spontaneous-qa-group/SpontaneousQaGroupCreator.d.ts +2 -0
  439. package/dist/components/questions/types/spontaneous-qa-group/SpontaneousQaGroupCreator.js +179 -0
  440. package/dist/components/questions/types/spontaneous-qa-group/creator-helpers.d.ts +5 -0
  441. package/dist/components/questions/types/spontaneous-qa-group/creator-helpers.js +43 -0
  442. package/dist/components/questions/types/spontaneous-qa-group/flatten.d.ts +14 -0
  443. package/dist/components/questions/types/spontaneous-qa-group/flatten.js +69 -0
  444. package/dist/components/questions/types/spontaneous-qa-group/index.d.ts +2 -0
  445. package/dist/components/questions/types/spontaneous-qa-group/index.js +2 -0
  446. package/dist/components/questions/types/spontaneous-qa-group/register.d.ts +2 -0
  447. package/dist/components/questions/types/spontaneous-qa-group/register.js +139 -0
  448. package/dist/components/questions/types/spontaneous-qa-group/transform.d.ts +2 -0
  449. package/dist/components/questions/types/spontaneous-qa-group/transform.js +52 -0
  450. package/dist/components/questions/types/true-false/TrueFalseClient.js +75 -1
  451. package/dist/components/questions/types/true-false/TrueFalseCreator.js +244 -1
  452. package/dist/components/questions/types/true-false/index.js +2 -1
  453. package/dist/components/questions/types/true-false/register.js +77 -1
  454. package/dist/components/questions/types/true-false/transform.js +32 -1
  455. package/dist/components/questions/types/true-false-group/TrueFalseGroupClient.js +25 -1
  456. package/dist/components/questions/types/true-false-group/TrueFalseGroupCreator.js +129 -1
  457. package/dist/components/questions/types/true-false-group/index.js +5 -1
  458. package/dist/components/questions/types/true-false-group/map-true-false-group-data.js +69 -1
  459. package/dist/components/questions/types/true-false-group/register.js +36 -1
  460. package/dist/components/questions/types/true-false-group/transform.js +43 -1
  461. package/dist/components/questions/types/word-fill-paragraph/WordFillParagraphClient.js +233 -1
  462. package/dist/components/questions/types/word-fill-paragraph/WordFillParagraphCreator.js +631 -1
  463. package/dist/components/questions/types/word-fill-paragraph/WordFillParagraphWrapper.js +28 -1
  464. package/dist/components/questions/types/word-fill-paragraph/index.js +2 -1
  465. package/dist/components/questions/types/word-fill-paragraph/register.js +136 -1
  466. package/dist/components/questions/types/word-fill-paragraph/transform.js +56 -1
  467. package/dist/components/questions/types/word-fill-structured-form/WfsfWordBankEditor.js +86 -1
  468. package/dist/components/questions/types/word-fill-structured-form/WordFillStructuredFormClient.js +246 -1
  469. package/dist/components/questions/types/word-fill-structured-form/WordFillStructuredFormCreator.d.ts +2 -0
  470. package/dist/components/questions/types/word-fill-structured-form/WordFillStructuredFormCreator.js +484 -1
  471. package/dist/components/questions/types/word-fill-structured-form/index.js +3 -1
  472. package/dist/components/questions/types/word-fill-structured-form/map-wfsf-question-data.js +113 -1
  473. package/dist/components/questions/types/word-fill-structured-form/register.js +36 -1
  474. package/dist/components/questions/types/word-fill-structured-form/transform.js +77 -1
  475. package/dist/components/questions/types/word-fill-structured-form/wfsf-word-bank.utils.js +110 -1
  476. package/dist/components/questions/types/word-order-and-match-group/WordOrderAndMatchGroupClient.d.ts +3 -0
  477. package/dist/components/questions/types/word-order-and-match-group/WordOrderAndMatchGroupClient.js +190 -0
  478. package/dist/components/questions/types/word-order-and-match-group/WordOrderAndMatchGroupCreator.d.ts +3 -0
  479. package/dist/components/questions/types/word-order-and-match-group/WordOrderAndMatchGroupCreator.js +182 -0
  480. package/dist/components/questions/types/word-order-and-match-group/index.d.ts +4 -0
  481. package/dist/components/questions/types/word-order-and-match-group/index.js +4 -0
  482. package/dist/components/questions/types/word-order-and-match-group/register.d.ts +2 -0
  483. package/dist/components/questions/types/word-order-and-match-group/register.js +121 -0
  484. package/dist/components/questions/types/word-order-and-match-group/transform.d.ts +2 -0
  485. package/dist/components/questions/types/word-order-and-match-group/transform.js +70 -0
  486. package/dist/components/questions/types/word-ordering/WordOrderingClient.js +84 -1
  487. package/dist/components/questions/types/word-ordering/WordOrderingCreator.js +192 -1
  488. package/dist/components/questions/types/word-ordering/index.js +2 -1
  489. package/dist/components/questions/types/word-ordering/register.js +46 -1
  490. package/dist/components/questions/types/word-ordering/transform.js +24 -1
  491. package/dist/components/questions/types/write-a-short-letter/WriteAShortLetterClient.js +99 -1
  492. package/dist/components/questions/types/write-a-short-letter/WriteAShortLetterCreator.js +320 -1
  493. package/dist/components/questions/types/write-a-short-letter/index.js +2 -1
  494. package/dist/components/questions/types/write-a-short-letter/register.js +68 -1
  495. package/dist/components/questions/types/write-a-short-letter/transform.js +25 -1
  496. package/dist/components/questions/types/write-correct-verb-form/WriteCorrectVerbFormClient.js +61 -1
  497. package/dist/components/questions/types/write-correct-verb-form/WriteCorrectVerbFormCreator.js +196 -1
  498. package/dist/components/questions/types/write-correct-verb-form/index.js +2 -1
  499. package/dist/components/questions/types/write-correct-verb-form/register.js +62 -1
  500. package/dist/components/questions/types/write-correct-verb-form/transform.js +17 -1
  501. package/dist/components/questions/types/write-sentences/WriteSentencesClient.js +22 -1
  502. package/dist/components/questions/types/write-sentences/WriteSentencesCreator.js +174 -1
  503. package/dist/components/questions/types/write-sentences/index.js +2 -1
  504. package/dist/components/questions/types/write-sentences/register.js +58 -1
  505. package/dist/components/questions/types/write-sentences/transform.js +28 -1
  506. package/dist/components/questions/types/write-short-paragraph/WriteShortParagraphClient.js +96 -1
  507. package/dist/components/questions/types/write-short-paragraph/WriteShortParagraphCreator.js +161 -1
  508. package/dist/components/questions/types/write-short-paragraph/index.js +2 -1
  509. package/dist/components/questions/types/write-short-paragraph/register.js +4 -1
  510. package/dist/components/questions/types/write-short-paragraph/transform.js +21 -1
  511. package/dist/components/questions/viewer/ChooseTheCorrectAnswerGroupViewer.js +11 -1
  512. package/dist/components/questions/viewer/CrossOutWordGroupViewer.js +8 -1
  513. package/dist/components/questions/viewer/QuestionGroupViewer.js +36 -1
  514. package/dist/components/questions/viewer/QuestionViewer.js +692 -1
  515. package/dist/components/questions/viewer/SpontaneousQaGroupViewer.d.ts +2 -1
  516. package/dist/components/questions/viewer/SpontaneousQaGroupViewer.js +82 -1
  517. package/dist/components/questions/viewer/TrueFalseGroupViewer.js +11 -1
  518. package/dist/components/questions/viewer/WordFillStructuredFormViewer.js +14 -1
  519. package/dist/components/questions/viewer/WordOrderAndMatchGroupViewer.d.ts +3 -1
  520. package/dist/components/questions/viewer/WordOrderAndMatchGroupViewer.js +95 -1
  521. package/dist/components/questions/viewer/index.js +8 -1
  522. package/dist/components/results/ResultReviewPageContainer.js +220 -1
  523. package/dist/components/results/ReviewQuestionRenderer.js +55 -1
  524. package/dist/components/results/hooks/useResultReviewPolling.js +76 -1
  525. package/dist/components/results/index.js +22 -1
  526. package/dist/components/results/renderers/QuestionExplanations.js +19 -1
  527. package/dist/components/results/renderers/ReviewChooseAdjectiveRenderer.js +94 -1
  528. package/dist/components/results/renderers/ReviewChooseBestAnswerRenderer.js +75 -1
  529. package/dist/components/results/renderers/ReviewColoringRenderer.js +110 -1
  530. package/dist/components/results/renderers/ReviewListenAndWriteRenderer.js +48 -1
  531. package/dist/components/results/renderers/ReviewListenChooseAnswerGroupRenderer.js +87 -1
  532. package/dist/components/results/renderers/ReviewListenChooseObjectsRenderer.js +104 -1
  533. package/dist/components/results/renderers/ReviewListenDragObjectsRenderer.js +123 -1
  534. package/dist/components/results/renderers/ReviewListeningHeader.js +104 -1
  535. package/dist/components/results/renderers/ReviewSpeakingAIPanel.js +116 -1
  536. package/dist/components/results/renderers/ReviewSpeakingMedia.js +48 -1
  537. package/dist/components/results/renderers/ReviewSpeakingQuestionItem.js +42 -1
  538. package/dist/components/results/renderers/ReviewSpeakingRenderer.js +12 -1
  539. package/dist/components/results/renderers/ReviewSpeakingRenderer.types.js +85 -1
  540. package/dist/components/results/renderers/review-question-body-dedicated.js +104 -1
  541. package/dist/components/results/renderers/review-question-body-movers.js +148 -1
  542. package/dist/components/results/renderers/review-speaking-map.js +226 -1
  543. package/dist/components/results/renderers/review-spontaneous-qa-group.d.ts +4 -0
  544. package/dist/components/results/renderers/review-spontaneous-qa-group.js +74 -0
  545. package/dist/components/results/report/BadgeTitle.js +5 -1
  546. package/dist/components/results/report/ExamSkillRow.js +20 -1
  547. package/dist/components/results/report/OceanEduExamReport.js +58 -1
  548. package/dist/components/results/report/OceanEduSpeakingReport.js +62 -1
  549. package/dist/components/results/report/OverallAssessmentSection.js +17 -1
  550. package/dist/components/results/report/ResultErrorState.js +18 -1
  551. package/dist/components/results/report/ResultLoadingState.js +10 -1
  552. package/dist/components/results/report/ResultNotPublicState.js +27 -1
  553. package/dist/components/results/report/SpeakingReportParts.js +14 -1
  554. package/dist/components/results/report/SpeakingReportShared.js +86 -1
  555. package/dist/components/results/report/SpeakingRubricSection.js +171 -1
  556. package/dist/components/results/report/speaking-report.utils.js +198 -1
  557. package/dist/components/results/result-review.utils.js +6 -1
  558. package/dist/components/results/review-data.js +222 -1
  559. package/dist/components/results/review-renderer.utils.js +97 -1
  560. package/dist/components/results/review-status.utils.js +36 -1
  561. package/dist/components/results/review-types.js +1 -1
  562. package/dist/components/results/speaking/SpeakingResultAudioPlayer.js +77 -1
  563. package/dist/components/results/speaking/SpeakingReviewResult.js +48 -1
  564. package/dist/components/results/utils/review-score.utils.js +72 -1
  565. package/dist/components/results/utils/speaking-summary.js +224 -1
  566. package/dist/components/results/utils/transform-speaking-review.js +183 -1
  567. package/dist/components/shared/RichTextEditor.d.ts +9 -0
  568. package/dist/components/shared/RichTextEditor.js +47 -0
  569. package/dist/components/shared/audio/SpeakingAudioPlayer.js +100 -1
  570. package/dist/components/themes/cambridge-yle/CambridgeYleAudioPlayer.js +140 -1
  571. package/dist/components/themes/cambridge-yle/CambridgeYleBottomNav.js +10 -1
  572. package/dist/components/themes/cambridge-yle/CambridgeYleExamLayout.d.ts +1 -1
  573. package/dist/components/themes/cambridge-yle/CambridgeYleExamLayout.js +18 -1
  574. package/dist/components/themes/cambridge-yle/CambridgeYleExampleText.js +11 -1
  575. package/dist/components/themes/cambridge-yle/CambridgeYleHeader.js +11 -1
  576. package/dist/components/themes/cambridge-yle/CambridgeYleInstructionBanner.d.ts +2 -1
  577. package/dist/components/themes/cambridge-yle/CambridgeYleInstructionBanner.js +19 -1
  578. package/dist/components/themes/cambridge-yle/CambridgeYleNavButton.js +13 -1
  579. package/dist/components/themes/cambridge-yle/CambridgeYlePartBanner.d.ts +1 -1
  580. package/dist/components/themes/cambridge-yle/CambridgeYlePartBanner.js +3 -1
  581. package/dist/components/themes/cambridge-yle/CambridgeYleProgressBar.js +6 -1
  582. package/dist/components/themes/cambridge-yle/CambridgeYleQuestionCard.js +5 -1
  583. package/dist/components/themes/cambridge-yle/CambridgeYleQuestionCardFullWidth.js +9 -1
  584. package/dist/components/themes/cambridge-yle/CambridgeYleSidebar.d.ts +2 -1
  585. package/dist/components/themes/cambridge-yle/CambridgeYleSidebar.js +61 -1
  586. package/dist/components/themes/cambridge-yle/CambridgeYleTimer.d.ts +1 -1
  587. package/dist/components/themes/cambridge-yle/CambridgeYleTimer.js +25 -1
  588. package/dist/components/themes/cambridge-yle/index.js +13 -1
  589. package/dist/components/themes/english-certification/EcAnswerTheQuestion.js +25 -1
  590. package/dist/components/themes/english-certification/EcMatchByWritingAnswer.js +89 -1
  591. package/dist/components/themes/english-certification/EcWordFillParagraph.js +56 -1
  592. package/dist/components/themes/english-certification/EcWordFillStructuredForm.js +192 -1
  593. package/dist/components/themes/english-certification/EcWritingTask.js +60 -1
  594. package/dist/components/themes/english-certification/EnglishCertificationAudioPlayer.js +108 -1
  595. package/dist/components/themes/english-certification/EnglishCertificationBrand.js +11 -1
  596. package/dist/components/themes/english-certification/EnglishCertificationExamLayout.js +34 -1
  597. package/dist/components/themes/english-certification/EnglishCertificationFlagButton.js +15 -1
  598. package/dist/components/themes/english-certification/EnglishCertificationFooterBar.js +19 -1
  599. package/dist/components/themes/english-certification/EnglishCertificationGroupedQuestion.js +166 -1
  600. package/dist/components/themes/english-certification/EnglishCertificationHeader.js +31 -1
  601. package/dist/components/themes/english-certification/EnglishCertificationInstructionBanner.js +79 -1
  602. package/dist/components/themes/english-certification/EnglishCertificationNavigationPanel.js +71 -1
  603. package/dist/components/themes/english-certification/EnglishCertificationOptionSelector.js +79 -1
  604. package/dist/components/themes/english-certification/EnglishCertificationPartHeader.js +47 -1
  605. package/dist/components/themes/english-certification/EnglishCertificationPassagePanel.js +77 -1
  606. package/dist/components/themes/english-certification/EnglishCertificationQuestionCard.js +25 -1
  607. package/dist/components/themes/english-certification/EnglishCertificationQuestionItem.js +36 -1
  608. package/dist/components/themes/english-certification/EnglishCertificationQuestionRenderer.js +81 -1
  609. package/dist/components/themes/english-certification/EnglishCertificationReadingSection.js +95 -1
  610. package/dist/components/themes/english-certification/EnglishCertificationSingleQuestion.js +18 -1
  611. package/dist/components/themes/english-certification/EnglishCertificationSingleQuestionList.js +32 -1
  612. package/dist/components/themes/english-certification/english-cert-scroll.js +163 -1
  613. package/dist/components/themes/english-certification/english-certification-theme.js +58 -1
  614. package/dist/components/themes/english-certification/grouped-question-utils.js +243 -1
  615. package/dist/components/themes/english-certification/index.js +24 -1
  616. package/dist/components/themes/index.js +25 -1
  617. package/dist/components/themes/summer-sky/SummerSkyAudioPlayer.js +170 -1
  618. package/dist/components/themes/summer-sky/SummerSkyExitModal.js +21 -1
  619. package/dist/components/themes/summer-sky/SummerSkyLayout.js +221 -1
  620. package/dist/components/themes/summer-sky/SummerSkySubmitModal.js +18 -1
  621. package/dist/components/themes/summer-sky/SummerSkyWaitingRoom.js +193 -1
  622. package/dist/components/themes/types.js +1 -1
  623. package/dist/components/ui/alert-dialog.js +26 -1
  624. package/dist/components/ui/alert.js +22 -1
  625. package/dist/components/ui/avatar.js +12 -1
  626. package/dist/components/ui/badge.js +38 -1
  627. package/dist/components/ui/button.js +35 -1
  628. package/dist/components/ui/calendar.js +76 -1
  629. package/dist/components/ui/card.js +16 -1
  630. package/dist/components/ui/carousel.js +91 -1
  631. package/dist/components/ui/checkbox.js +9 -1
  632. package/dist/components/ui/collapsible.js +13 -1
  633. package/dist/components/ui/command.js +34 -1
  634. package/dist/components/ui/confirm-dialog.js +49 -1
  635. package/dist/components/ui/dialog.js +23 -1
  636. package/dist/components/ui/drawer.js +35 -1
  637. package/dist/components/ui/dropdown-menu.js +33 -1
  638. package/dist/components/ui/file-upload.js +155 -1
  639. package/dist/components/ui/form.js +62 -1
  640. package/dist/components/ui/hover-card.js +10 -1
  641. package/dist/components/ui/image-preview.js +29 -1
  642. package/dist/components/ui/input.js +8 -1
  643. package/dist/components/ui/label.js +9 -1
  644. package/dist/components/ui/language-switcher.js +15 -1
  645. package/dist/components/ui/multi-select.js +90 -1
  646. package/dist/components/ui/points-input.js +33 -1
  647. package/dist/components/ui/popover.js +26 -1
  648. package/dist/components/ui/progress.js +16 -1
  649. package/dist/components/ui/radio-group.js +12 -1
  650. package/dist/components/ui/scroll-area.js +11 -1
  651. package/dist/components/ui/select.js +38 -1
  652. package/dist/components/ui/separator.js +8 -1
  653. package/dist/components/ui/sheet.js +40 -1
  654. package/dist/components/ui/single-select.js +76 -1
  655. package/dist/components/ui/skeleton.js +6 -1
  656. package/dist/components/ui/switch.js +8 -1
  657. package/dist/components/ui/table.js +20 -1
  658. package/dist/components/ui/tabs.js +17 -1
  659. package/dist/components/ui/textarea.js +8 -1
  660. package/dist/components/ui/tooltip.js +11 -1
  661. package/dist/index.d.ts +3 -0
  662. package/dist/index.js +24 -1
  663. package/dist/shared/constants/ApiConstant.d.ts +1 -0
  664. package/dist/shared/constants/ApiConstant.js +278 -1
  665. package/dist/shared/constants/QueryKeyConstant.js +112 -1
  666. package/dist/shared/constants/exam-level.enum.js +41 -1
  667. package/dist/shared/constants/landings/placement-test.constants.js +431 -1
  668. package/dist/shared/constants/question-skills.js +277 -1
  669. package/dist/shared/constants/routes.js +81 -1
  670. package/dist/shared/index.js +10 -1
  671. package/dist/shared/lib/hooks/index.js +10 -1
  672. package/dist/shared/lib/hooks/useAlertDialog.js +57 -1
  673. package/dist/shared/lib/hooks/useAudioPlayCounter.js +30 -1
  674. package/dist/shared/lib/hooks/useAudioPreview.js +81 -1
  675. package/dist/shared/lib/hooks/useAudioUpload.js +24 -1
  676. package/dist/shared/lib/hooks/useConfirmDialog.js +89 -1
  677. package/dist/shared/lib/hooks/useDebouncedCallback.js +32 -1
  678. package/dist/shared/lib/hooks/useDelayedAutoPlay.js +12 -1
  679. package/dist/shared/lib/hooks/useFileUpload.js +24 -1
  680. package/dist/shared/lib/hooks/useImageUrl.js +5 -1
  681. package/dist/shared/lib/hooks/usePresignedFileUrl.js +108 -1
  682. package/dist/shared/lib/hooks/useReactHookForm.js +31 -1
  683. package/dist/shared/lib/hooks/useToast.js +62 -1
  684. package/dist/shared/lib/html-content.js +22 -1
  685. package/dist/shared/lib/i18n/index.js +54 -1
  686. package/dist/shared/lib/i18n/messages/en/admin.d.ts +11 -4
  687. package/dist/shared/lib/i18n/messages/en/admin.js +2111 -1
  688. package/dist/shared/lib/i18n/messages/en/auth.js +86 -1
  689. package/dist/shared/lib/i18n/messages/en/common.js +140 -1
  690. package/dist/shared/lib/i18n/messages/en/file-upload.js +18 -1
  691. package/dist/shared/lib/i18n/messages/en/student.js +133 -1
  692. package/dist/shared/lib/i18n/messages/en/terms.js +37 -1
  693. package/dist/shared/lib/i18n/messages/en.d.ts +11 -4
  694. package/dist/shared/lib/i18n/messages/en.js +15 -1
  695. package/dist/shared/lib/i18n/messages/vi/admin.d.ts +11 -4
  696. package/dist/shared/lib/i18n/messages/vi/admin.js +2111 -1
  697. package/dist/shared/lib/i18n/messages/vi/auth.js +86 -1
  698. package/dist/shared/lib/i18n/messages/vi/common.js +140 -1
  699. package/dist/shared/lib/i18n/messages/vi/file-upload.js +18 -1
  700. package/dist/shared/lib/i18n/messages/vi/student.js +133 -1
  701. package/dist/shared/lib/i18n/messages/vi/terms.js +37 -1
  702. package/dist/shared/lib/i18n/messages/vi.d.ts +11 -4
  703. package/dist/shared/lib/i18n/messages/vi.js +15 -1
  704. package/dist/shared/lib/i18n/types.js +1 -1
  705. package/dist/shared/lib/stores/examQuestionStore.js +212 -1
  706. package/dist/shared/lib/stores/examTakingStore.js +240 -1
  707. package/dist/shared/lib/stores/gradingTransientStore.js +39 -1
  708. package/dist/shared/lib/stores/localeStore.js +10 -1
  709. package/dist/shared/lib/themes/config/starters.config.js +71 -1
  710. package/dist/shared/lib/themes/index.js +49 -1
  711. package/dist/shared/lib/themes/types.js +2 -1
  712. package/dist/shared/lib/toast.js +43 -1
  713. package/dist/shared/lib/utils/public-page.js +44 -1
  714. package/dist/shared/lib/utils/question-reverse-transform.js +438 -1
  715. package/dist/shared/lib/utils/question-transform-types.js +1 -1
  716. package/dist/shared/lib/utils/question-transform-validation.js +194 -1
  717. package/dist/shared/lib/utils/question-transform.js +199 -1
  718. package/dist/shared/lib/utils.js +168 -1
  719. package/dist/shared/lib/validation/choose-correct-answer.validation.js +111 -1
  720. package/dist/shared/lib/validation/label-the-picture.validation.js +19 -1
  721. package/dist/shared/lib/validation/listen-and-choose-from-answer-group.validation.js +46 -1
  722. package/dist/shared/lib/validation/listen-and-choose-objects-in-scene.validation.js +21 -1
  723. package/dist/shared/lib/validation/listen-and-drag-objects-into-scene.validation.js +34 -1
  724. package/dist/shared/lib/validation/listen-and-speak-answer.validation.js +40 -1
  725. package/dist/shared/lib/validation/listen-and-speak-compare-images.validation.js +45 -1
  726. package/dist/shared/lib/validation/listen-and-speak-image-group.validation.js +19 -1
  727. package/dist/shared/lib/validation/listen-and-speak-info-exchange.validation.js +39 -1
  728. package/dist/shared/lib/validation/listen-and-speak-odd-one-out.validation.js +32 -1
  729. package/dist/shared/lib/validation/listen-and-speak-question-list.validation.js +19 -1
  730. package/dist/shared/lib/validation/listen-and-speak-with-story-images.validation.js +28 -1
  731. package/dist/shared/lib/validation/look-picture-choose-correct-answer.validation.js +12 -1
  732. package/dist/shared/lib/validation/look-picture-fill-blank-choose-answer.validation.js +47 -1
  733. package/dist/shared/lib/validation/read-and-color-objects.validation.js +29 -1
  734. package/dist/shared/lib/validation/read-displayed-content.validation.js +28 -1
  735. package/dist/shared/lib/validation/speaking-conversation.validation.js +75 -1
  736. package/dist/shared/lib/validation/speaking-describe-image.validation.js +31 -1
  737. package/dist/shared/lib/validation/spontaneous-qa.validation.js +48 -1
  738. package/dist/shared/lib/validation/word-fill-paragraph-default.validation.js +38 -1
  739. package/dist/shared/lib/validation/word-fill-paragraph-with-options.validation.js +39 -1
  740. package/dist/shared/lib/validation/word-fill-paragraph.validation.js +43 -1
  741. package/dist/shared/lib/validation/word-ordering.validation.js +33 -1
  742. package/dist/shared/types/branch.types.js +6 -1
  743. package/dist/shared/types/common.types.js +49 -1
  744. package/dist/shared/types/entities/exam-schedule.types.js +5 -1
  745. package/dist/shared/types/entities/exam.types.js +1 -1
  746. package/dist/shared/types/entities/question.types.js +4 -1
  747. package/dist/shared/types/entities/result.types.js +4 -1
  748. package/dist/shared/types/entities/user.types.js +4 -1
  749. package/dist/shared/types/exam-taking.types.js +2 -1
  750. package/dist/shared/types/history.types.js +34 -1
  751. package/dist/shared/types/index.js +1 -1
  752. package/dist/shared/types/questions/choose-correct-adjective.js +4 -1
  753. package/dist/shared/types/questions/choose-the-correct-answer-group.d.ts +1 -0
  754. package/dist/shared/types/questions/choose-the-correct-answer-group.js +1 -1
  755. package/dist/shared/types/questions/choose-the-correct-answer.js +9 -1
  756. package/dist/shared/types/questions/cross-out-word-group.d.ts +2 -0
  757. package/dist/shared/types/questions/cross-out-word-group.js +1 -1
  758. package/dist/shared/types/questions/fill-missing-words-in-grid.js +11 -1
  759. package/dist/shared/types/questions/index.d.ts +1 -0
  760. package/dist/shared/types/questions/index.js +46 -1
  761. package/dist/shared/types/questions/listen-and-speak-answer.js +1 -1
  762. package/dist/shared/types/questions/listen-and-speak-compare-images.js +1 -1
  763. package/dist/shared/types/questions/listen-and-speak-image-group.js +1 -1
  764. package/dist/shared/types/questions/listen-and-speak-info-exchange.js +3 -1
  765. package/dist/shared/types/questions/listen-and-speak-odd-one-out.js +3 -1
  766. package/dist/shared/types/questions/listen-and-speak-question-list.js +1 -1
  767. package/dist/shared/types/questions/listen-and-speak-with-story-images.js +3 -1
  768. package/dist/shared/types/questions/look-picture-choose-correct-answer.js +5 -1
  769. package/dist/shared/types/questions/look-picture-fill-blank-choose-answer.js +9 -1
  770. package/dist/shared/types/questions/look-picture-fill-word-hint.js +7 -1
  771. package/dist/shared/types/questions/read-displayed-content.js +1 -1
  772. package/dist/shared/types/questions/speaking-conversation.js +7 -1
  773. package/dist/shared/types/questions/speaking-describe-image.js +1 -1
  774. package/dist/shared/types/questions/spontaneous-qa.js +1 -1
  775. package/dist/shared/types/questions/word-order-and-match-group.d.ts +33 -0
  776. package/dist/shared/types/questions/word-order-and-match-group.js +1 -0
  777. package/dist/shared/types/questions/write-correct-verb-form.js +12 -1
  778. package/dist/shared/types/role.types.js +1 -1
  779. package/dist/shared/types/structure-management.js +28 -1
  780. package/dist/shared/types/student.types.js +57 -1
  781. package/dist/shared/types/teacher-dashboard-rooms.js +2 -1
  782. package/package.json +184 -166
  783. package/dist/mcp/config.d.ts +0 -12
  784. package/dist/mcp/config.js +0 -1
  785. package/dist/mcp/http-client.d.ts +0 -6
  786. package/dist/mcp/http-client.js +0 -1
  787. package/dist/mcp/index.d.ts +0 -6
  788. package/dist/mcp/index.js +0 -2
  789. package/dist/mcp/tools/_helpers.d.ts +0 -4
  790. package/dist/mcp/tools/_helpers.js +0 -1
  791. package/dist/mcp/tools/exams.d.ts +0 -2
  792. package/dist/mcp/tools/exams.js +0 -1
  793. package/dist/mcp/tools/index.d.ts +0 -2
  794. package/dist/mcp/tools/index.js +0 -1
  795. package/dist/mcp/tools/results.d.ts +0 -2
  796. package/dist/mcp/tools/results.js +0 -1
@@ -1 +1,77 @@
1
- "use client";import{jsx as e,jsxs as t}from"react/jsx-runtime";import{ResolvedImage as l}from"../../../components/common/ResolvedImage";import{useImageUrl as n}from"../../../shared/lib/hooks/useImageUrl";import{injectBlankPlaceholders as a}from"./grouped-question-utils";const r="ec-passage-html text-[17px] leading-[1.8] text-[#111827] [&_h1]:mb-2 [&_h1]:text-[1.6em] [&_h1]:font-black [&_h1]:leading-tight [&_h1]:text-[#111827] [&_h2]:mb-2 [&_h2]:text-[1.25em] [&_h2]:font-bold [&_h2]:leading-snug [&_h2]:text-[#111827] [&_h3]:mb-1.5 [&_h3]:text-[1.1em] [&_h3]:font-bold [&_h3]:text-[#111827] [&_p]:mb-3 [&_p]:whitespace-pre-wrap [&_strong]:font-bold [&_ul]:my-3 [&_ul]:list-disc [&_ul]:pl-6 [&_ol]:my-3 [&_ol]:list-decimal [&_ol]:pl-6 [&_li]:mb-1.5 [&_table]:my-3 [&_td]:border [&_td]:border-[#d9e4f2] [&_td]:px-2 [&_td]:py-1";function i({article:n,label:i}){const d=Boolean(i)||Boolean(n.name?.trim());return t("div",{className:"border-t border-[#e6eefb] pt-4 first:border-t-0 first:pt-0",children:[d&&t("div",{className:"mb-2 flex items-center gap-2",children:[i&&e("span",{className:"grid h-7 w-7 shrink-0 place-items-center rounded-full bg-[#1167e8] text-xs font-bold text-white",children:i}),n.name?.trim()&&e("h3",{className:"text-base font-bold text-[#061a55]",children:n.name})]}),n.imageUrl&&e("div",{className:"mb-3 overflow-hidden rounded-lg border border-[#d9e4f2]",children:e(l,{src:n.imageUrl,alt:n.name||"passage",className:"h-auto w-full object-contain"})}),n.content?.trim()&&e("div",{className:r,dangerouslySetInnerHTML:{__html:a(n.content)}})]})}export function EnglishCertificationPassagePanel({passage:l,passageTitle:d,passageSubtitle:s,passageImageUrl:o,articles:c,passageIsHtml:m}){const b=n(o||null),p=a(l),h=c??[],x=h.length>1;return t("article",{className:"flex h-full flex-col overflow-hidden rounded-xl border border-[#d9e4f2] bg-white/90 shadow-[0_14px_35px_rgba(13,71,161,0.12)] backdrop-blur-md",children:[e("div",{className:"flex h-[52px] items-end border-b border-[#d9e4f2] bg-white/70 px-5",children:e("span",{className:"flex h-full items-center border-b-[3px] border-[#0b57d0] text-sm font-black tracking-wide text-[#0a3d9a]",children:"PASSAGE"})}),e("div",{className:"flex-1 overflow-y-auto custom-scrollbar",children:t("div",{className:"px-6 pt-6 leading-relaxed",children:[d&&e("h1",{className:"mb-1 text-[clamp(20px,1.6vw,28px)] font-black leading-tight text-[#061a55]",children:d}),s&&e("p",{className:"mb-4 text-[15px] font-semibold text-[#475569]",children:s}),b&&e("div",{className:"mb-4 overflow-hidden rounded-lg border border-[#d9e4f2]",children:e("img",{src:b,alt:"passage",className:"h-auto w-full object-cover"})}),l.trim()&&e("div",m?{className:r,dangerouslySetInnerHTML:{__html:p}}:{className:"whitespace-pre-wrap text-[17px] leading-[1.8] text-[#1e293b]",children:p}),h.length>0&&e("div",{className:"flex flex-col gap-4",children:h.map((t,l)=>e(i,{article:t,label:x?String.fromCharCode(65+l):void 0},l))})]})}),e("style",{children:"\n /* Wrapper: subtle pill around the entire blank unit */\n .ec-blank-placeholder {\n display: inline-flex;\n align-items: center;\n gap: 0;\n vertical-align: middle;\n white-space: nowrap;\n margin: 0 2px;\n padding: 1px 5px;\n background: rgba(11, 87, 208, 0.07);\n border: 1px solid rgba(11, 87, 208, 0.20);\n border-radius: 999px;\n }\n\n /* Flat line (not border-bottom) so it sits at vertical center correctly */\n .ec-blank-line {\n display: inline-block;\n width: 20px;\n height: 2px;\n background: #0b57d0;\n border-radius: 1px;\n opacity: 0.6;\n margin: 0 4px;\n }\n\n /* Number badge — plain bold blue text, no background */\n .ec-blank-label {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n padding: 0 3px;\n color: #0b57d0;\n font-size: 15px;\n font-weight: 800;\n line-height: 1;\n letter-spacing: 0;\n }\n "})]})}
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { ResolvedImage } from '../../../components/common/ResolvedImage';
4
+ import { useImageUrl } from '../../../shared/lib/hooks/useImageUrl';
5
+ import { injectBlankPlaceholders } from './grouped-question-utils';
6
+ /** Shared prose styling for HTML passage/article bodies. */
7
+ const EC_PROSE_CLASS = 'ec-passage-html text-[17px] leading-[1.8] text-[#111827] [&_h1]:mb-2 [&_h1]:text-[1.6em] [&_h1]:font-black [&_h1]:leading-tight [&_h1]:text-[#111827] [&_h2]:mb-2 [&_h2]:text-[1.25em] [&_h2]:font-bold [&_h2]:leading-snug [&_h2]:text-[#111827] [&_h3]:mb-1.5 [&_h3]:text-[1.1em] [&_h3]:font-bold [&_h3]:text-[#111827] [&_p]:mb-3 [&_p]:whitespace-pre-wrap [&_strong]:font-bold [&_ul]:my-3 [&_ul]:list-disc [&_ul]:pl-6 [&_ol]:my-3 [&_ol]:list-decimal [&_ol]:pl-6 [&_li]:mb-1.5 [&_table]:my-3 [&_td]:border [&_td]:border-[#d9e4f2] [&_td]:px-2 [&_td]:py-1';
8
+ /**
9
+ * Renders a single reading article: optional letter label + name heading, its
10
+ * own image (S3 keys resolved via {@link ResolvedImage}), then HTML body.
11
+ * A/B/C labels only appear for multi-passage sets (double/triple).
12
+ */
13
+ function PassageArticleView({ article, label, }) {
14
+ const hasHeader = Boolean(label) || Boolean(article.name?.trim());
15
+ return (_jsxs("div", { className: "border-t border-[#e6eefb] pt-4 first:border-t-0 first:pt-0", children: [hasHeader && (_jsxs("div", { className: "mb-2 flex items-center gap-2", children: [label && (_jsx("span", { className: "grid h-7 w-7 shrink-0 place-items-center rounded-full bg-[#1167e8] text-xs font-bold text-white", children: label })), article.name?.trim() && (_jsx("h3", { className: "text-base font-bold text-[#061a55]", children: article.name }))] })), article.imageUrl && (_jsx("div", { className: "mb-3 overflow-hidden rounded-lg border border-[#d9e4f2]", children: _jsx(ResolvedImage, { src: article.imageUrl, alt: article.name || 'passage', className: "h-auto w-full object-contain" }) })), article.content?.trim() && (_jsx("div", { className: EC_PROSE_CLASS, dangerouslySetInnerHTML: { __html: injectBlankPlaceholders(article.content) } }))] }));
16
+ }
17
+ /**
18
+ * Reading passage panel for grouped English Certification parts.
19
+ *
20
+ * Extracted into its own component so it can call {@link useImageUrl} to resolve
21
+ * the passage image. The API persists images as S3 keys (e.g.
22
+ * `questions/images/...`) which must be exchanged for a presigned URL before
23
+ * they can be rendered. Local paths (`/...`) and absolute URLs pass through
24
+ * unchanged.
25
+ *
26
+ * `{number}` tokens (e.g. `{141}`) in the passage are converted to styled
27
+ * blank-line spans so students see fill-in-the-blank positions clearly.
28
+ */
29
+ export function EnglishCertificationPassagePanel({ passage, passageTitle, passageSubtitle, passageImageUrl, articles, passageIsHtml, }) {
30
+ const resolvedImageUrl = useImageUrl(passageImageUrl || null);
31
+ // Replace {141}, {142}, … tokens with styled blank spans.
32
+ const processedPassage = injectBlankPlaceholders(passage);
33
+ // TOEIC Part 7 stores the passage in `articles[]`; a set with more than one
34
+ // article (double/triple passages) gets A/B/C letter labels.
35
+ const articleList = articles ?? [];
36
+ const showArticleLabels = articleList.length > 1;
37
+ return (_jsxs("article", { className: "flex h-full flex-col overflow-hidden rounded-xl border border-[#d9e4f2] bg-white/90 shadow-[0_14px_35px_rgba(13,71,161,0.12)] backdrop-blur-md", children: [_jsx("div", { className: "flex h-[52px] items-end border-b border-[#d9e4f2] bg-white/70 px-5", children: _jsx("span", { className: "flex h-full items-center border-b-[3px] border-[#0b57d0] text-sm font-black tracking-wide text-[#0a3d9a]", children: "PASSAGE" }) }), _jsx("div", { className: "flex-1 overflow-y-auto custom-scrollbar", children: _jsxs("div", { className: "px-6 pt-6 leading-relaxed", children: [passageTitle && (_jsx("h1", { className: "mb-1 text-[clamp(20px,1.6vw,28px)] font-black leading-tight text-[#061a55]", children: passageTitle })), passageSubtitle && (_jsx("p", { className: "mb-4 text-[15px] font-semibold text-[#475569]", children: passageSubtitle })), resolvedImageUrl && (_jsx("div", { className: "mb-4 overflow-hidden rounded-lg border border-[#d9e4f2]", children: _jsx("img", { src: resolvedImageUrl, alt: "passage", className: "h-auto w-full object-cover" }) })), passage.trim() &&
38
+ (passageIsHtml ? (_jsx("div", { className: EC_PROSE_CLASS, dangerouslySetInnerHTML: { __html: processedPassage } })) : (_jsx("div", { className: "whitespace-pre-wrap text-[17px] leading-[1.8] text-[#1e293b]", children: processedPassage }))), articleList.length > 0 && (_jsx("div", { className: "flex flex-col gap-4", children: articleList.map((article, index) => (_jsx(PassageArticleView, { article: article, label: showArticleLabels ? String.fromCharCode(65 + index) : undefined }, index))) }))] }) }), _jsx("style", { children: `
39
+ /* Wrapper: subtle pill around the entire blank unit */
40
+ .ec-blank-placeholder {
41
+ display: inline-flex;
42
+ align-items: center;
43
+ gap: 0;
44
+ vertical-align: middle;
45
+ white-space: nowrap;
46
+ margin: 0 2px;
47
+ padding: 1px 5px;
48
+ background: rgba(11, 87, 208, 0.07);
49
+ border: 1px solid rgba(11, 87, 208, 0.20);
50
+ border-radius: 999px;
51
+ }
52
+
53
+ /* Flat line (not border-bottom) so it sits at vertical center correctly */
54
+ .ec-blank-line {
55
+ display: inline-block;
56
+ width: 20px;
57
+ height: 2px;
58
+ background: #0b57d0;
59
+ border-radius: 1px;
60
+ opacity: 0.6;
61
+ margin: 0 4px;
62
+ }
63
+
64
+ /* Number badge — plain bold blue text, no background */
65
+ .ec-blank-label {
66
+ display: inline-flex;
67
+ align-items: center;
68
+ justify-content: center;
69
+ padding: 0 3px;
70
+ color: #0b57d0;
71
+ font-size: 15px;
72
+ font-weight: 800;
73
+ line-height: 1;
74
+ letter-spacing: 0;
75
+ }
76
+ ` })] }));
77
+ }
@@ -1 +1,25 @@
1
- "use client";import{jsx as e,jsxs as i}from"react/jsx-runtime";import{EnglishCertificationOptionSelector as t}from"./EnglishCertificationOptionSelector";import{EnglishCertificationFlagButton as n}from"./EnglishCertificationFlagButton";export function EnglishCertificationQuestionCard({question:o,globalQuestionNumber:s,answer:r,isFlagged:a,sharedInstruction:l,onAnswerChange:d,onToggleFlag:c,isReviewMode:g=!1,domId:m}){const p=o.content||{},f=p.options||[],u=p.question?.trim()??"",b=u.length>0&&u!==l;return e("article",{id:m,className:"scroll-mt-24 rounded-xl border border-[#d9e4f2] bg-white/90 px-4 py-4 shadow-[0_5px_16px_rgba(13,71,161,0.08)] backdrop-blur-md transition-colors",children:i("div",{className:"grid grid-cols-[44px_1fr_32px] gap-1",children:[e("div",{className:"grid h-9 w-9 place-items-center rounded-full bg-[#eef5ff] text-base font-black leading-none text-[#0b57d0]",children:s}),i("div",{className:"min-w-0",children:[b&&e("div",{className:"mb-2 text-base font-black leading-snug text-[#10172f]",children:u}),e(t,{options:f.map((e,i)=>({id:String(e?.id||`${o.id}-opt-${i}`),label:String.fromCharCode(65+i),text:e?.text||String(e??"")})),selectedOptionId:r?.optionId??null,onSelect:e=>d(o.id,{optionId:e}),isReviewMode:g,disabled:g,variant:"compact"})]}),e(n,{isFlagged:a,onToggle:()=>c(o.id),size:"sm",className:"self-start justify-self-end"})]})})}
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { EnglishCertificationOptionSelector } from './EnglishCertificationOptionSelector';
4
+ import { EnglishCertificationFlagButton } from './EnglishCertificationFlagButton';
5
+ /**
6
+ * Single MCQ rendered as a standalone Ocean Edu white card.
7
+ *
8
+ * Replaces the former shared-section/divider layout so each question matches the
9
+ * target mockup (own card, number chip, flag button, compact options).
10
+ */
11
+ export function EnglishCertificationQuestionCard({ question, globalQuestionNumber, answer, isFlagged, sharedInstruction, onAnswerChange, onToggleFlag, isReviewMode = false, domId, }) {
12
+ const content = question.content || {};
13
+ const rawOptions = content.options || [];
14
+ const questionText = content.question?.trim() ?? '';
15
+ // Skip per-question text when it merely repeats the shared part instruction.
16
+ const showQuestionText = questionText.length > 0 && questionText !== sharedInstruction;
17
+ return (_jsx("article", { id: domId, className: "scroll-mt-24 rounded-xl border border-[#d9e4f2] bg-white/90 px-4 py-4 shadow-[0_5px_16px_rgba(13,71,161,0.08)] backdrop-blur-md transition-colors", children: _jsxs("div", { className: "grid grid-cols-[44px_1fr_32px] gap-1", children: [_jsx("div", { className: "grid h-9 w-9 place-items-center rounded-full bg-[#eef5ff] text-base font-black leading-none text-[#0b57d0]", children: globalQuestionNumber }), _jsxs("div", { className: "min-w-0", children: [showQuestionText && (_jsx("div", { className: "mb-2 text-base font-black leading-snug text-[#10172f]", children: questionText })), _jsx(EnglishCertificationOptionSelector, { options: rawOptions.map((option, optIdx) => ({
18
+ // Options are plain strings without ids and correct_answer is
19
+ // index-based, so use the synthetic "<qid>-opt-N" id that the
20
+ // transformer converts to { answer: N }. Mirrors QuestionItem.
21
+ id: String(option?.id || `${question.id}-opt-${optIdx}`),
22
+ label: String.fromCharCode(65 + optIdx),
23
+ text: option?.text || String(option ?? ''),
24
+ })), selectedOptionId: answer?.optionId ?? null, onSelect: (optionId) => onAnswerChange(question.id, { optionId }), isReviewMode: isReviewMode, disabled: isReviewMode, variant: "compact" })] }), _jsx(EnglishCertificationFlagButton, { isFlagged: isFlagged, onToggle: () => onToggleFlag(question.id), size: "sm", className: "self-start justify-self-end" })] }) }));
25
+ }
@@ -1 +1,36 @@
1
- "use client";import{jsx as e,jsxs as o}from"react/jsx-runtime";import{useMemo as i,useState as t}from"react";import{ZoomIn as n}from"lucide-react";import{Dialog as r,DialogContent as a,DialogHeader as l,DialogTitle as s}from"../../../components/ui/dialog";import{ResolvedImage as c}from"../../../components/common/ResolvedImage";import{EnglishCertificationOptionSelector as d}from"./EnglishCertificationOptionSelector";import{EnglishCertificationFlagButton as m}from"./EnglishCertificationFlagButton";import{ENGLISH_CERT_CONTENT_WIDTH_CLASS as g}from"./english-certification-theme";export function EnglishCertificationQuestionItem({question:p,questionNumber:u,questionConfig:h,defaultQuestion:f,answer:b,isFlagged:x,onAnswerChange:w,onToggleFlag:v,isReviewMode:N=!1,domId:C}){const[_,k]=t(!1),j=b?.optionId??null,y=i(()=>{const e=p.content?.options;return Array.isArray(e)?e.map((e,o)=>({id:String(e?.id||`${p.id}-opt-${o}`),label:String.fromCharCode(65+o),text:String(e?.text||e?.value||e||"")})):[]},[p.content?.options,p.id]),I=h.includes("imageUrl"),Q=p.content?.imageUrl,S=I&&Boolean(Q),$=p.content?.question?.trim()||f||"";return o("div",{id:C,className:`${g} scroll-mt-24`,children:[e("article",{className:"rounded-xl border border-[#d9e4f2] bg-white/90 px-4 py-4 shadow-[0_5px_16px_rgba(13,71,161,0.08)] backdrop-blur-md transition-colors",children:o("div",{className:"grid grid-cols-[44px_1fr_32px] gap-1",children:[e("div",{className:"grid h-9 w-9 place-items-center rounded-full bg-[#eef5ff] text-base font-black leading-none text-[#0b57d0]",children:u}),o("div",{className:"min-w-0",children:[$&&e("div",{className:"mb-2 text-base font-black leading-snug text-[#10172f]",children:$}),S&&Q&&o("button",{type:"button",onClick:()=>k(!0),className:"group relative mb-3 block w-full overflow-hidden rounded-xl border border-[#cbdcf0] shadow-[0_8px_20px_rgba(6,26,85,0.10)]","aria-label":"Open image in large view",children:[e(c,{src:Q,alt:`Question ${u} visual prompt`,className:"h-auto max-h-[260px] w-full object-contain transition-transform group-hover:scale-[1.01]"}),e("span",{className:"absolute right-2 top-2 grid h-8 w-8 place-items-center rounded-full bg-black/50 text-white transition-colors group-hover:bg-black/70",children:e(n,{className:"h-3.5 w-3.5"})})]}),e(d,{options:y,selectedOptionId:j,onSelect:e=>w(p.id,{optionId:e}),disabled:N,isReviewMode:N,variant:"compact"})]}),e(m,{isFlagged:x,onToggle:()=>v(p.id),size:"sm",className:"self-start justify-self-end"})]})}),S&&Q&&e(r,{open:_,onOpenChange:k,children:o(a,{className:"max-w-4xl border-none bg-transparent p-0 shadow-none",children:[e(l,{className:"sr-only",children:e(s,{children:"Question image"})}),e("div",{className:"overflow-hidden rounded-lg bg-white",children:e(c,{src:Q,alt:`Question ${u} enlarged visual prompt`,className:"h-auto w-full object-contain"})})]})})]})}
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useMemo, useState } from 'react';
4
+ import { ZoomIn } from 'lucide-react';
5
+ import { Dialog, DialogContent, DialogHeader, DialogTitle, } from '../../../components/ui/dialog';
6
+ import { ResolvedImage } from '../../../components/common/ResolvedImage';
7
+ import { EnglishCertificationOptionSelector } from './EnglishCertificationOptionSelector';
8
+ import { EnglishCertificationFlagButton } from './EnglishCertificationFlagButton';
9
+ import { ENGLISH_CERT_CONTENT_WIDTH_CLASS } from './english-certification-theme';
10
+ /**
11
+ * Ocean Edu branded card for a single multiple-choice question.
12
+ *
13
+ * Visual style matches EnglishCertificationQuestionCard (Part 7):
14
+ * compact grid layout, circular number badge, lightweight shadow.
15
+ * Audio is not rendered here — listening parts show one shared audio bar
16
+ * at the top of the list.
17
+ */
18
+ export function EnglishCertificationQuestionItem({ question, questionNumber, questionConfig, defaultQuestion, answer, isFlagged, onAnswerChange, onToggleFlag, isReviewMode = false, domId, }) {
19
+ const [isImageOpen, setIsImageOpen] = useState(false);
20
+ const selectedOptionId = answer?.optionId ?? null;
21
+ const options = useMemo(() => {
22
+ const rawOptions = question.content?.options;
23
+ if (!Array.isArray(rawOptions))
24
+ return [];
25
+ return rawOptions.map((opt, idx) => ({
26
+ id: String(opt?.id || `${question.id}-opt-${idx}`),
27
+ label: String.fromCharCode(65 + idx),
28
+ text: String(opt?.text || opt?.value || opt || ''),
29
+ }));
30
+ }, [question.content?.options, question.id]);
31
+ const hasImageConfig = questionConfig.includes('imageUrl');
32
+ const imageUrl = question.content?.imageUrl;
33
+ const shouldShowImage = hasImageConfig && Boolean(imageUrl);
34
+ const questionText = question.content?.question?.trim() || defaultQuestion || '';
35
+ return (_jsxs("div", { id: domId, className: `${ENGLISH_CERT_CONTENT_WIDTH_CLASS} scroll-mt-24`, children: [_jsx("article", { className: "rounded-xl border border-[#d9e4f2] bg-white/90 px-4 py-4 shadow-[0_5px_16px_rgba(13,71,161,0.08)] backdrop-blur-md transition-colors", children: _jsxs("div", { className: "grid grid-cols-[44px_1fr_32px] gap-1", children: [_jsx("div", { className: "grid h-9 w-9 place-items-center rounded-full bg-[#eef5ff] text-base font-black leading-none text-[#0b57d0]", children: questionNumber }), _jsxs("div", { className: "min-w-0", children: [questionText && (_jsx("div", { className: "mb-2 text-base font-black leading-snug text-[#10172f]", children: questionText })), shouldShowImage && imageUrl && (_jsxs("button", { type: "button", onClick: () => setIsImageOpen(true), className: "group relative mb-3 block w-full overflow-hidden rounded-xl border border-[#cbdcf0] shadow-[0_8px_20px_rgba(6,26,85,0.10)]", "aria-label": "Open image in large view", children: [_jsx(ResolvedImage, { src: imageUrl, alt: `Question ${questionNumber} visual prompt`, className: "h-auto max-h-[260px] w-full object-contain transition-transform group-hover:scale-[1.01]" }), _jsx("span", { className: "absolute right-2 top-2 grid h-8 w-8 place-items-center rounded-full bg-black/50 text-white transition-colors group-hover:bg-black/70", children: _jsx(ZoomIn, { className: "h-3.5 w-3.5" }) })] })), _jsx(EnglishCertificationOptionSelector, { options: options, selectedOptionId: selectedOptionId, onSelect: (optionId) => onAnswerChange(question.id, { optionId }), disabled: isReviewMode, isReviewMode: isReviewMode, variant: "compact" })] }), _jsx(EnglishCertificationFlagButton, { isFlagged: isFlagged, onToggle: () => onToggleFlag(question.id), size: "sm", className: "self-start justify-self-end" })] }) }), shouldShowImage && imageUrl && (_jsx(Dialog, { open: isImageOpen, onOpenChange: setIsImageOpen, children: _jsxs(DialogContent, { className: "max-w-4xl border-none bg-transparent p-0 shadow-none", children: [_jsx(DialogHeader, { className: "sr-only", children: _jsx(DialogTitle, { children: "Question image" }) }), _jsx("div", { className: "overflow-hidden rounded-lg bg-white", children: _jsx(ResolvedImage, { src: imageUrl, alt: `Question ${questionNumber} enlarged visual prompt`, className: "h-auto w-full object-contain" }) })] }) }))] }));
36
+ }
@@ -1 +1,81 @@
1
- "use client";import{jsx as e,jsxs as r}from"react/jsx-runtime";import{SkillEnum as i}from"../../../shared/types/common.types";import{EnglishCertificationSingleQuestionList as o}from"./EnglishCertificationSingleQuestionList";import{EnglishCertificationGroupedQuestion as t}from"./EnglishCertificationGroupedQuestion";import{EnglishCertificationAudioPlayer as n}from"./EnglishCertificationAudioPlayer";import{EnglishCertificationPartHeader as s}from"./EnglishCertificationPartHeader";import{EnglishCertificationInstructionBanner as a}from"./EnglishCertificationInstructionBanner";import{ENGLISH_CERT_CONTENT_WIDTH_CLASS as l}from"./english-certification-theme";import{ENGLISH_CERT_STICKY_AUDIO_ATTR as d}from"./english-cert-scroll";import{EcWordFillStructuredForm as c}from"./EcWordFillStructuredForm";import{EcMatchByWritingAnswer as u}from"./EcMatchByWritingAnswer";import{EcAnswerTheQuestion as g}from"./EcAnswerTheQuestion";import{EcWordFillParagraph as m}from"./EcWordFillParagraph";import{EcWritingTask as p}from"./EcWritingTask";const f=new Set(["CHOOSE_THE_CORRECT_ANSWER"]);export function EnglishCertificationQuestionRenderer({part:h,partConfig:w,answers:A,flaggedQuestions:E,onAnswerChange:R,onToggleFlag:C,isReviewMode:N=!1,isAudioPlayed:T=!1,onAudioFirstPlay:y}){if(!h.questions.length)return e("div",{className:"flex h-full items-center justify-center rounded-xl border border-red-200 bg-red-50 p-8 text-red-600",children:"No questions found for this part"});const _=w.category===i.LISTENING,v=_?h.audioUrl??w.audioUrl??"":void 0,L=!N&&T,F=h.questionType,P=Boolean(h.isGroup)&&"DOCUMENT"===h.group?.type,x=w.isGroup&&f.has(F),M=Boolean(h.sections?.length);if(P||x||M)return e("div",{className:"h-full",children:e(t,{part:h,partConfig:w,answers:A,flaggedQuestions:E,onAnswerChange:R,onToggleFlag:C,isReviewMode:N,isListeningPart:_,partAudioUrl:v,isAudioLocked:L,onAudioFirstPlay:y})});if(f.has(F))return e(o,{part:h,partConfig:w,answers:A,flaggedQuestions:E,onAnswerChange:R,onToggleFlag:C,isReviewMode:N,isListeningPart:_,partAudioUrl:v,isAudioLocked:L,onAudioFirstPlay:y});const b=(h.questions[0]?.questionGroup?.payload?.instructions??h.instructions??w.instructions??"").trim();return r("div",{className:"flex flex-col items-center gap-6 pb-4",children:[e(s,{partName:h.name??w.name,partNo:h.partNo,skill:w.category}),e(a,{instruction:b,isListeningPart:_}),_&&e("div",{[d]:"",className:`sticky top-0 z-20 ${l}`,children:e(n,{audioUrl:v??"",partNumber:h.partNo,isLocked:L,onFirstPlay:y})}),e("div",{className:`${l} w-full`,children:(()=>{switch(F){case"WORD_FILL_STRUCTURED_FORM":return e(c,{part:h,answers:A,onAnswerChange:R,isReviewMode:N});case"MATCH_BY_WRITING_ANSWER":return e(u,{part:h,answers:A,onAnswerChange:R,isReviewMode:N});case"ANSWER_THE_QUESTION":return e(g,{part:h,answers:A,onAnswerChange:R,isReviewMode:N});case"WORD_FILL_PARAGRAPH":return e(m,{part:h,answers:A,onAnswerChange:R,isReviewMode:N});case"WRITE_A_SHORT_LETTER":return e(p,{part:h,answers:A,onAnswerChange:R,isReviewMode:N});default:return r("div",{className:"rounded-xl border border-amber-200 bg-amber-50 p-6 text-sm text-amber-700",children:['Loại câu hỏi "',F,'" chưa được hỗ trợ trong giao diện này.']})}})()})]})}
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { SkillEnum } from '../../../shared/types/common.types';
4
+ import { EnglishCertificationSingleQuestionList } from './EnglishCertificationSingleQuestionList';
5
+ import { EnglishCertificationGroupedQuestion } from './EnglishCertificationGroupedQuestion';
6
+ import { EnglishCertificationAudioPlayer } from './EnglishCertificationAudioPlayer';
7
+ import { EnglishCertificationPartHeader } from './EnglishCertificationPartHeader';
8
+ import { EnglishCertificationInstructionBanner } from './EnglishCertificationInstructionBanner';
9
+ import { ENGLISH_CERT_CONTENT_WIDTH_CLASS } from './english-certification-theme';
10
+ import { ENGLISH_CERT_STICKY_AUDIO_ATTR } from './english-cert-scroll';
11
+ import { EcWordFillStructuredForm } from './EcWordFillStructuredForm';
12
+ import { EcMatchByWritingAnswer } from './EcMatchByWritingAnswer';
13
+ import { EcAnswerTheQuestion } from './EcAnswerTheQuestion';
14
+ import { EcWordFillParagraph } from './EcWordFillParagraph';
15
+ import { EcWritingTask } from './EcWritingTask';
16
+ /**
17
+ * Question types that the branded English Certification theme renders natively
18
+ * as multiple-choice cards (TOEIC + IELTS MCQ variants such as TRUE/FALSE/NOT GIVEN).
19
+ */
20
+ const EC_NATIVE_MCQ_TYPES = new Set(['CHOOSE_THE_CORRECT_ANSWER']);
21
+ /**
22
+ * Dispatcher for English Certification question rendering based on part config.
23
+ *
24
+ * - CHOOSE_THE_CORRECT_ANSWER → branded EC MCQ list / grouped split-view.
25
+ * - DOCUMENT-group Reading parts → branded split-view (passage + questions).
26
+ * - All other IELTS types are rendered by DEDICATED branded EC renderers
27
+ * (full-width, structured to match TOEIC) — never delegated to the shared
28
+ * Movers renderer, which squeezed IELTS tables into narrow columns.
29
+ */
30
+ export function EnglishCertificationQuestionRenderer({ part, partConfig, answers, flaggedQuestions, onAnswerChange, onToggleFlag, isReviewMode = false, isAudioPlayed = false, onAudioFirstPlay, }) {
31
+ if (!part.questions.length) {
32
+ return (_jsx("div", { className: "flex h-full items-center justify-center rounded-xl border border-red-200 bg-red-50 p-8 text-red-600", children: "No questions found for this part" }));
33
+ }
34
+ // Part-level audio (Listening sections always show the audio bar so students
35
+ // see the player even before the admin uploads the audio file).
36
+ const isListeningPart = partConfig.category === SkillEnum.LISTENING;
37
+ const partAudioUrl = isListeningPart
38
+ ? part.audioUrl ?? partConfig.audioUrl ?? ''
39
+ : undefined;
40
+ // Play-once lock never applies in review mode (exam is already over).
41
+ const isAudioLocked = !isReviewMode && isAudioPlayed;
42
+ const questionType = part.questionType;
43
+ // Reading parts with a DOCUMENT passage (TOEIC Part 6/7 and IELTS Reading)
44
+ // use the branded split view: passage on the left, all questions on the
45
+ // right. IELTS Reading mixes sub-section types, handled inside the renderer.
46
+ const hasDocumentGroup = Boolean(part.isGroup) && part.group?.type === 'DOCUMENT';
47
+ const isGroupedMcq = partConfig.isGroup && EC_NATIVE_MCQ_TYPES.has(questionType);
48
+ // Mixed multi-section parts (e.g. Listening SECTION 2) are kept whole by
49
+ // groupQuestionsByPart with their sections attached. Route them to the
50
+ // grouped renderer, which stacks each sub-section via ReadingSection.
51
+ const hasSections = Boolean(part.sections?.length);
52
+ if (hasDocumentGroup || isGroupedMcq || hasSections) {
53
+ return (_jsx("div", { className: "h-full", children: _jsx(EnglishCertificationGroupedQuestion, { part: part, partConfig: partConfig, answers: answers, flaggedQuestions: flaggedQuestions, onAnswerChange: onAnswerChange, onToggleFlag: onToggleFlag, isReviewMode: isReviewMode, isListeningPart: isListeningPart, partAudioUrl: partAudioUrl, isAudioLocked: isAudioLocked, onAudioFirstPlay: onAudioFirstPlay }) }));
54
+ }
55
+ // Native branded MCQ list (non-grouped CHOOSE_THE_CORRECT_ANSWER).
56
+ if (EC_NATIVE_MCQ_TYPES.has(questionType)) {
57
+ return (_jsx(EnglishCertificationSingleQuestionList, { part: part, partConfig: partConfig, answers: answers, flaggedQuestions: flaggedQuestions, onAnswerChange: onAnswerChange, onToggleFlag: onToggleFlag, isReviewMode: isReviewMode, isListeningPart: isListeningPart, partAudioUrl: partAudioUrl, isAudioLocked: isAudioLocked, onAudioFirstPlay: onAudioFirstPlay }));
58
+ }
59
+ // Dedicated branded EC renderers for the remaining IELTS question types.
60
+ const renderDedicated = () => {
61
+ switch (questionType) {
62
+ case 'WORD_FILL_STRUCTURED_FORM':
63
+ return (_jsx(EcWordFillStructuredForm, { part: part, answers: answers, onAnswerChange: onAnswerChange, isReviewMode: isReviewMode }));
64
+ case 'MATCH_BY_WRITING_ANSWER':
65
+ return (_jsx(EcMatchByWritingAnswer, { part: part, answers: answers, onAnswerChange: onAnswerChange, isReviewMode: isReviewMode }));
66
+ case 'ANSWER_THE_QUESTION':
67
+ return (_jsx(EcAnswerTheQuestion, { part: part, answers: answers, onAnswerChange: onAnswerChange, isReviewMode: isReviewMode }));
68
+ case 'WORD_FILL_PARAGRAPH':
69
+ return (_jsx(EcWordFillParagraph, { part: part, answers: answers, onAnswerChange: onAnswerChange, isReviewMode: isReviewMode }));
70
+ case 'WRITE_A_SHORT_LETTER':
71
+ return (_jsx(EcWritingTask, { part: part, answers: answers, onAnswerChange: onAnswerChange, isReviewMode: isReviewMode }));
72
+ default:
73
+ return (_jsxs("div", { className: "rounded-xl border border-amber-200 bg-amber-50 p-6 text-sm text-amber-700", children: ["Lo\u1EA1i c\u00E2u h\u1ECFi \"", questionType, "\" ch\u01B0a \u0111\u01B0\u1EE3c h\u1ED7 tr\u1EE3 trong giao di\u1EC7n n\u00E0y."] }));
74
+ }
75
+ };
76
+ const sharedInstruction = (part.questions[0]?.questionGroup?.payload?.instructions ??
77
+ part.instructions ??
78
+ partConfig.instructions ??
79
+ '').trim();
80
+ return (_jsxs("div", { className: "flex flex-col items-center gap-6 pb-4", children: [_jsx(EnglishCertificationPartHeader, { partName: part.name ?? partConfig.name, partNo: part.partNo, skill: partConfig.category }), _jsx(EnglishCertificationInstructionBanner, { instruction: sharedInstruction, isListeningPart: isListeningPart }), isListeningPart && (_jsx("div", { [ENGLISH_CERT_STICKY_AUDIO_ATTR]: '', className: `sticky top-0 z-20 ${ENGLISH_CERT_CONTENT_WIDTH_CLASS}`, children: _jsx(EnglishCertificationAudioPlayer, { audioUrl: partAudioUrl ?? '', partNumber: part.partNo, isLocked: isAudioLocked, onFirstPlay: onAudioFirstPlay }) })), _jsx("div", { className: `${ENGLISH_CERT_CONTENT_WIDTH_CLASS} w-full`, children: renderDedicated() })] }));
81
+ }
@@ -1 +1,95 @@
1
- "use client";import{jsx as e,jsxs as i}from"react/jsx-runtime";import{EnglishCertificationOptionSelector as t}from"./EnglishCertificationOptionSelector";import{EnglishCertificationFlagButton as n}from"./EnglishCertificationFlagButton";import{EcWordFillStructuredForm as r}from"./EcWordFillStructuredForm";import{EcMatchByWritingAnswer as s}from"./EcMatchByWritingAnswer";import{EcAnswerTheQuestion as o}from"./EcAnswerTheQuestion";import{EcWordFillParagraph as a}from"./EcWordFillParagraph";import{EcWritingTask as d}from"./EcWritingTask";import{englishCertQuestionDomId as l}from"./english-cert-scroll";function c(e,i){return e.map((e,t)=>{const n=String.fromCharCode(65+t),r=`${i}-opt-${t}`;return"string"==typeof e?{id:r,label:n,text:e}:{id:String(e?.id||r),label:n,text:String(e?.text??e?.value??e??"")}})}export function EnglishCertificationReadingSection({part:m,section:p,questions:u,startIndex:g,answers:f,flaggedQuestions:x,onAnswerChange:h,onToggleFlag:w,isReviewMode:R=!1}){if(!u.length)return null;const v=(p.instructions||p.name||"").trim();if("CHOOSE_THE_CORRECT_ANSWER"===p.questionType)return i("div",{className:"flex flex-col gap-3",children:[v&&e("p",{className:"text-sm font-semibold leading-relaxed text-[#0b57d0]",children:v}),e("div",{className:"divide-y divide-[#e8eef7]",children:u.map(r=>{const s=m.questions.indexOf(r)+g+1,o=r.content||{},a=Array.isArray(o.options)?o.options:p.defaultValue??[],d=o.question?.trim()??"",u=f[r.id];return i("article",{id:l(r.id),className:"scroll-mt-24 grid grid-cols-[44px_1fr_32px] gap-1 py-4",children:[i("div",{className:"text-[18px] font-black leading-snug text-[#0b57d0]",children:[s,"."]}),i("div",{className:"min-w-0",children:[d&&e("div",{className:"mb-2 text-[15px] font-black leading-snug text-[#10172f]",children:d}),e(t,{options:c(a,r.id),selectedOptionId:u?.optionId??null,onSelect:e=>h(r.id,{optionId:e}),isReviewMode:R,disabled:R,variant:"compact"})]}),e(n,{isFlagged:x.has(r.id),onToggle:()=>w(r.id),size:"sm",className:"self-start justify-self-end"})]},r.id)})})]});const T={...m,questionType:p.questionType,questions:u,instructions:v||m.instructions,isGroup:!1,group:void 0,sections:void 0,viewMode:p.viewMode,wordBankConfig:p.wordBankConfig,startIndex:p.startIndex,endIndex:p.endIndex};return i("div",{className:"flex flex-col gap-3",children:[v&&e("p",{className:"text-sm font-semibold leading-relaxed text-[#0b57d0]",children:v}),(()=>{switch(p.questionType){case"WORD_FILL_STRUCTURED_FORM":return e(r,{part:T,answers:f,onAnswerChange:h,isReviewMode:R});case"MATCH_BY_WRITING_ANSWER":return e(s,{part:T,answers:f,onAnswerChange:h,isReviewMode:R});case"ANSWER_THE_QUESTION":return e(o,{part:T,answers:f,onAnswerChange:h,isReviewMode:R});case"WORD_FILL_PARAGRAPH":return e(a,{part:T,answers:f,onAnswerChange:h,isReviewMode:R});case"WRITE_A_SHORT_LETTER":return e(d,{part:T,answers:f,onAnswerChange:h,isReviewMode:R});default:return i("div",{className:"rounded-xl border border-amber-200 bg-amber-50 p-4 text-sm text-amber-700",children:['Loại câu hỏi "',p.questionType,'" chưa được hỗ trợ.']})}})()]})}
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { EnglishCertificationOptionSelector } from './EnglishCertificationOptionSelector';
4
+ import { EnglishCertificationFlagButton } from './EnglishCertificationFlagButton';
5
+ import { EcWordFillStructuredForm } from './EcWordFillStructuredForm';
6
+ import { EcMatchByWritingAnswer } from './EcMatchByWritingAnswer';
7
+ import { EcAnswerTheQuestion } from './EcAnswerTheQuestion';
8
+ import { EcWordFillParagraph } from './EcWordFillParagraph';
9
+ import { EcWritingTask } from './EcWritingTask';
10
+ import { englishCertQuestionDomId } from './english-cert-scroll';
11
+ /**
12
+ * Maps a question's options to the selector shape.
13
+ *
14
+ * Handles three shapes:
15
+ * - `{ id, text }` objects (standard MCQ options).
16
+ * - plain strings from `section.defaultValue` (IELTS TRUE/FALSE/NOT GIVEN),
17
+ * where the string itself is both the stored value and the displayed text.
18
+ */
19
+ function mapOptions(rawOptions, questionId) {
20
+ return rawOptions.map((option, optIdx) => {
21
+ const letter = String.fromCharCode(65 + optIdx);
22
+ // correct_answer for CHOOSE_THE_CORRECT_ANSWER is the option INDEX, so every
23
+ // option carries the synthetic "<questionId>-opt-N" id that the transformer
24
+ // maps to { answer: N }. Mirrors QuestionItem/QuestionCard for consistency.
25
+ const synthId = `${questionId}-opt-${optIdx}`;
26
+ if (typeof option === 'string') {
27
+ // Plain string options, incl. IELTS TRUE/FALSE/NOT GIVEN.
28
+ return { id: synthId, label: letter, text: option };
29
+ }
30
+ return {
31
+ id: String(option?.id || synthId),
32
+ label: letter,
33
+ text: String(option?.text ?? option?.value ?? option ?? ''),
34
+ };
35
+ });
36
+ }
37
+ /**
38
+ * Renders one sub-section of an IELTS Reading part inside the right-hand
39
+ * question column.
40
+ *
41
+ * - CHOOSE_THE_CORRECT_ANSWER (incl. TRUE/FALSE/NOT GIVEN) → branded MCQ cards.
42
+ * - Any other type (ANSWER_THE_QUESTION, WORD_FILL_PARAGRAPH,
43
+ * WORD_FILL_STRUCTURED_FORM, MATCH_BY_WRITING_ANSWER, …) is delegated to the
44
+ * shared TOEIC-style renderer so the proven transformers/answer shapes apply.
45
+ */
46
+ export function EnglishCertificationReadingSection({ part, section, questions, startIndex, answers, flaggedQuestions, onAnswerChange, onToggleFlag, isReviewMode = false, }) {
47
+ if (!questions.length)
48
+ return null;
49
+ const sectionInstruction = (section.instructions || section.name || '').trim();
50
+ const isMcq = section.questionType === 'CHOOSE_THE_CORRECT_ANSWER';
51
+ if (isMcq) {
52
+ return (_jsxs("div", { className: "flex flex-col gap-3", children: [sectionInstruction && (_jsx("p", { className: "text-sm font-semibold leading-relaxed text-[#0b57d0]", children: sectionInstruction })), _jsx("div", { className: "divide-y divide-[#e8eef7]", children: questions.map((question) => {
53
+ const globalNumber = part.questions.indexOf(question) + startIndex + 1;
54
+ const content = question.content || {};
55
+ const rawOptions = Array.isArray(content.options)
56
+ ? content.options
57
+ : section.defaultValue ?? [];
58
+ const questionText = content.question?.trim() ?? '';
59
+ const answer = answers[question.id];
60
+ return (_jsxs("article", { id: englishCertQuestionDomId(question.id), className: "scroll-mt-24 grid grid-cols-[44px_1fr_32px] gap-1 py-4", children: [_jsxs("div", { className: "text-[18px] font-black leading-snug text-[#0b57d0]", children: [globalNumber, "."] }), _jsxs("div", { className: "min-w-0", children: [questionText && (_jsx("div", { className: "mb-2 text-[15px] font-black leading-snug text-[#10172f]", children: questionText })), _jsx(EnglishCertificationOptionSelector, { options: mapOptions(rawOptions, question.id), selectedOptionId: answer?.optionId ?? null, onSelect: (optionId) => onAnswerChange(question.id, { optionId }), isReviewMode: isReviewMode, disabled: isReviewMode, variant: "compact" })] }), _jsx(EnglishCertificationFlagButton, { isFlagged: flaggedQuestions.has(question.id), onToggle: () => onToggleFlag(question.id), size: "sm", className: "self-start justify-self-end" })] }, question.id));
61
+ }) })] }));
62
+ }
63
+ // Build a synthetic sub-part carrying only this section's questions so the
64
+ // dedicated branded EC renderers can transform/render it standalone.
65
+ const subPart = {
66
+ ...part,
67
+ questionType: section.questionType,
68
+ questions,
69
+ instructions: sectionInstruction || part.instructions,
70
+ isGroup: false,
71
+ group: undefined,
72
+ sections: undefined,
73
+ viewMode: section.viewMode,
74
+ wordBankConfig: section.wordBankConfig,
75
+ startIndex: section.startIndex,
76
+ endIndex: section.endIndex,
77
+ };
78
+ const renderSubSection = () => {
79
+ switch (section.questionType) {
80
+ case 'WORD_FILL_STRUCTURED_FORM':
81
+ return (_jsx(EcWordFillStructuredForm, { part: subPart, answers: answers, onAnswerChange: onAnswerChange, isReviewMode: isReviewMode }));
82
+ case 'MATCH_BY_WRITING_ANSWER':
83
+ return (_jsx(EcMatchByWritingAnswer, { part: subPart, answers: answers, onAnswerChange: onAnswerChange, isReviewMode: isReviewMode }));
84
+ case 'ANSWER_THE_QUESTION':
85
+ return (_jsx(EcAnswerTheQuestion, { part: subPart, answers: answers, onAnswerChange: onAnswerChange, isReviewMode: isReviewMode }));
86
+ case 'WORD_FILL_PARAGRAPH':
87
+ return (_jsx(EcWordFillParagraph, { part: subPart, answers: answers, onAnswerChange: onAnswerChange, isReviewMode: isReviewMode }));
88
+ case 'WRITE_A_SHORT_LETTER':
89
+ return (_jsx(EcWritingTask, { part: subPart, answers: answers, onAnswerChange: onAnswerChange, isReviewMode: isReviewMode }));
90
+ default:
91
+ return (_jsxs("div", { className: "rounded-xl border border-amber-200 bg-amber-50 p-4 text-sm text-amber-700", children: ["Lo\u1EA1i c\u00E2u h\u1ECFi \"", section.questionType, "\" ch\u01B0a \u0111\u01B0\u1EE3c h\u1ED7 tr\u1EE3."] }));
92
+ }
93
+ };
94
+ return (_jsxs("div", { className: "flex flex-col gap-3", children: [sectionInstruction && (_jsx("p", { className: "text-sm font-semibold leading-relaxed text-[#0b57d0]", children: sectionInstruction })), renderSubSection()] }));
95
+ }
@@ -1 +1,18 @@
1
- "use client";import{jsx as e,jsxs as i}from"react/jsx-runtime";import{EnglishCertificationAudioPlayer as n}from"./EnglishCertificationAudioPlayer";import{EnglishCertificationQuestionItem as o}from"./EnglishCertificationQuestionItem";import{ENGLISH_CERT_CONTENT_WIDTH_CLASS as t}from"./english-certification-theme";export function EnglishCertificationSingleQuestion({question:s,questionNumber:r,questionConfig:a,defaultQuestion:l,partAudioUrl:u,answer:g,isFlagged:m,onAnswerChange:c,onToggleFlag:f,isReviewMode:d=!1}){const h=s.audio_url||u||"";return i("div",{className:"flex flex-col items-center gap-5",children:[h&&e("div",{className:t,children:e(n,{audioUrl:h,partNumber:s.part_no})}),e(o,{question:s,questionNumber:r,questionConfig:a,defaultQuestion:l,answer:g,isFlagged:m,onAnswerChange:c,onToggleFlag:f,isReviewMode:d})]})}
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { EnglishCertificationAudioPlayer } from './EnglishCertificationAudioPlayer';
4
+ import { EnglishCertificationQuestionItem } from './EnglishCertificationQuestionItem';
5
+ import { ENGLISH_CERT_CONTENT_WIDTH_CLASS } from './english-certification-theme';
6
+ /**
7
+ * Ocean Edu branded renderer for a single (non-grouped) question.
8
+ *
9
+ * Thin wrapper around {@link EnglishCertificationQuestionItem} that prepends the
10
+ * shared audio bar for listening parts. Kept for review mode and any
11
+ * one-question-per-screen flow; multi-question screens use the question list.
12
+ */
13
+ export function EnglishCertificationSingleQuestion({ question, questionNumber, questionConfig, defaultQuestion, partAudioUrl, answer, isFlagged, onAnswerChange, onToggleFlag, isReviewMode = false, }) {
14
+ // Resolve effective audio: prefer the question's own audio, otherwise fall
15
+ // back to the part-level audio (TOEIC Listening Part 1/2 define audio per part).
16
+ const effectiveAudioUrl = question.audio_url || partAudioUrl || '';
17
+ return (_jsxs("div", { className: "flex flex-col items-center gap-5", children: [effectiveAudioUrl && (_jsx("div", { className: ENGLISH_CERT_CONTENT_WIDTH_CLASS, children: _jsx(EnglishCertificationAudioPlayer, { audioUrl: effectiveAudioUrl, partNumber: question.part_no }) })), _jsx(EnglishCertificationQuestionItem, { question: question, questionNumber: questionNumber, questionConfig: questionConfig, defaultQuestion: defaultQuestion, answer: answer, isFlagged: isFlagged, onAnswerChange: onAnswerChange, onToggleFlag: onToggleFlag, isReviewMode: isReviewMode })] }));
18
+ }
@@ -1 +1,32 @@
1
- "use client";import{jsx as i,jsxs as t}from"react/jsx-runtime";import{EnglishCertificationAudioPlayer as e}from"./EnglishCertificationAudioPlayer";import{EnglishCertificationQuestionItem as o}from"./EnglishCertificationQuestionItem";import{EnglishCertificationInstructionBanner as r}from"./EnglishCertificationInstructionBanner";import{EnglishCertificationPartHeader as n}from"./EnglishCertificationPartHeader";import{ENGLISH_CERT_CONTENT_WIDTH_CLASS as s}from"./english-certification-theme";import{ENGLISH_CERT_STICKY_AUDIO_ATTR as a,englishCertQuestionDomId as l}from"./english-cert-scroll";export function EnglishCertificationSingleQuestionList({part:u,partConfig:m,answers:c,flaggedQuestions:g,onAnswerChange:d,onToggleFlag:f,isReviewMode:p=!1,isListeningPart:h=!1,partAudioUrl:C,isAudioLocked:A=!1,onAudioFirstPlay:y}){const N=m.startIndex??0,w=Array.isArray(m.questionConfig)?m.questionConfig:[],x=m.defaultQuestion,q=h&&Boolean(C),P=(u.instructions??m.instructions)?.trim()||"",E=u.name??m.name;return t("div",{className:"flex flex-col items-center gap-6 pb-4",children:[i(n,{partName:E,partNo:u.partNo,skill:m.category}),i(r,{instruction:P,isListeningPart:h}),q&&i("div",{[a]:"",className:`sticky top-0 z-20 ${s}`,children:i(e,{audioUrl:C,partNumber:u.partNo,isLocked:A,onFirstPlay:y})}),u.questions.map((t,e)=>{const r=t.audio_url?Array.from(new Set([...w,"audioUrl"])):w;return i(o,{domId:l(t.id),question:t,questionNumber:N+e+1,questionConfig:r,defaultQuestion:x,answer:c[t.id],isFlagged:g.has(t.id),onAnswerChange:d,onToggleFlag:f,isReviewMode:p},t.id)})]})}
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { EnglishCertificationAudioPlayer } from './EnglishCertificationAudioPlayer';
4
+ import { EnglishCertificationQuestionItem } from './EnglishCertificationQuestionItem';
5
+ import { EnglishCertificationInstructionBanner } from './EnglishCertificationInstructionBanner';
6
+ import { EnglishCertificationPartHeader } from './EnglishCertificationPartHeader';
7
+ import { ENGLISH_CERT_CONTENT_WIDTH_CLASS } from './english-certification-theme';
8
+ import { ENGLISH_CERT_STICKY_AUDIO_ATTR, englishCertQuestionDomId, } from './english-cert-scroll';
9
+ /**
10
+ * Renders every question of a single-question part (or section) on one screen.
11
+ *
12
+ * Listening parts show a single shared audio bar at the top; all questions then
13
+ * stack vertically. Navigation across parts/sections is handled by the footer.
14
+ */
15
+ export function EnglishCertificationSingleQuestionList({ part, partConfig, answers, flaggedQuestions, onAnswerChange, onToggleFlag, isReviewMode = false, isListeningPart = false, partAudioUrl, isAudioLocked = false, onAudioFirstPlay, }) {
16
+ const startIndex = partConfig.startIndex ?? 0;
17
+ const questionConfig = Array.isArray(partConfig.questionConfig)
18
+ ? partConfig.questionConfig
19
+ : [];
20
+ const defaultQuestion = partConfig.defaultQuestion;
21
+ const showAudio = isListeningPart && Boolean(partAudioUrl);
22
+ // Part-level directions (e.g. "Directions: In this section you will hear…").
23
+ // Shown once at the top so students see the task, matching the grouped view.
24
+ const partInstruction = (part.instructions ?? partConfig.instructions)?.trim() || '';
25
+ const partName = part.name ?? partConfig.name;
26
+ return (_jsxs("div", { className: "flex flex-col items-center gap-6 pb-4", children: [_jsx(EnglishCertificationPartHeader, { partName: partName, partNo: part.partNo, skill: partConfig.category }), _jsx(EnglishCertificationInstructionBanner, { instruction: partInstruction, isListeningPart: isListeningPart }), showAudio && (_jsx("div", { [ENGLISH_CERT_STICKY_AUDIO_ATTR]: '', className: `sticky top-0 z-20 ${ENGLISH_CERT_CONTENT_WIDTH_CLASS}`, children: _jsx(EnglishCertificationAudioPlayer, { audioUrl: partAudioUrl, partNumber: part.partNo, isLocked: isAudioLocked, onFirstPlay: onAudioFirstPlay }) })), part.questions.map((question, idx) => {
27
+ const effectiveQuestionConfig = question.audio_url
28
+ ? Array.from(new Set([...questionConfig, 'audioUrl']))
29
+ : questionConfig;
30
+ return (_jsx(EnglishCertificationQuestionItem, { domId: englishCertQuestionDomId(question.id), question: question, questionNumber: startIndex + idx + 1, questionConfig: effectiveQuestionConfig, defaultQuestion: defaultQuestion, answer: answers[question.id], isFlagged: flaggedQuestions.has(question.id), onAnswerChange: onAnswerChange, onToggleFlag: onToggleFlag, isReviewMode: isReviewMode }, question.id));
31
+ })] }));
32
+ }
@@ -1 +1,163 @@
1
- export function englishCertQuestionDomId(t){return`ec-question-${t}`}export const ENGLISH_CERT_STICKY_AUDIO_ATTR="data-ec-sticky-audio";const t=[0,30,80,150,300,500,800];function e(t){const e=window.getComputedStyle(t).overflowY;return("auto"===e||"scroll"===e||"overlay"===e)&&t.scrollHeight>t.clientHeight+1}export function findScrollableParent(t){let n=t.parentElement;for(;n;){if(e(n))return n;n=n.parentElement}return document.scrollingElement??document.documentElement}export function findAllScrollableParents(t){const n=[];let o=t.parentElement;for(;o;)e(o)&&n.push(o),o=o.parentElement;const l=document.scrollingElement??document.documentElement;return l&&!n.includes(l)&&l.scrollHeight>l.clientHeight+1&&n.push(l),n.length?n:[l]}export function scrollElementIntoView(t,e){const n=e?.behavior??"smooth",o=e?.block??"start",l=findAllScrollableParents(t);if(!l.length)return;l.map(e=>{const n=e.getBoundingClientRect(),l=t.getBoundingClientRect(),r=e.scrollTop,i=l.top-n.top+r,c=function(t,e){const n=t.querySelectorAll("[data-ec-sticky-audio]");if(!n.length)return 0;let o=0;return n.forEach(n=>{if(n===e||n.contains(e))return;if(!t.contains(n))return;const l=n.getBoundingClientRect().height;l>o&&(o=l)}),o>0?o+12:0}(e,t);let s=i;if("center"===o)s=i-e.clientHeight/2+l.height/2;else if("end"===o)s=i-e.clientHeight+l.height;else if("nearest"===o){const t=r+c,n=r+e.clientHeight,o=i+l.height;s=i>=t&&o<=n?r:i<t?i-c:o-e.clientHeight}else s=i-c;return"start"===o&&0===c&&(s=Math.max(0,s-12)),{parent:e,targetTop:Math.max(0,s)}}).forEach(({parent:t,targetTop:e})=>{t.scrollTo({top:e,behavior:n})})}export function scrollToEnglishCertQuestion(e,n){if("undefined"==typeof document||!e)return;const o=englishCertQuestionDomId(e),l=()=>{const t=document.getElementById(o);return!!t&&(scrollElementIntoView(t,n),!0)};if(l())return;let r=1;const i=()=>{l()||(r+=1,r<t.length&&window.setTimeout(i,t[r]))};window.setTimeout(i,t[1])}
1
+ /**
2
+ * Scroll helpers for English Certification Answer Sheet navigation.
3
+ *
4
+ * Question cards use a stable DOM id (`ec-question-{id}`) so the sidebar can
5
+ * jump to the chosen question after `goToQuestion` re-renders the active part.
6
+ *
7
+ * Listening parts pin the audio bar with `position: sticky`. Manual scroll must
8
+ * subtract that bar's height so the target card is not hidden underneath it.
9
+ */
10
+ /** Stable DOM id for a question card / row, used for Answer-Sheet scroll-to. */
11
+ export function englishCertQuestionDomId(questionId) {
12
+ return `ec-question-${questionId}`;
13
+ }
14
+ /** Marker on sticky audio wrappers so scroll offset can be measured at runtime. */
15
+ export const ENGLISH_CERT_STICKY_AUDIO_ATTR = 'data-ec-sticky-audio';
16
+ const SCROLL_RETRY_MS = [0, 30, 80, 150, 300, 500, 800];
17
+ /** Extra gap below sticky chrome so the card is not flush against the audio bar. */
18
+ const STICKY_CLEARANCE_PX = 12;
19
+ function isScrollable(el) {
20
+ const style = window.getComputedStyle(el);
21
+ const overflowY = style.overflowY;
22
+ if (overflowY !== 'auto' && overflowY !== 'scroll' && overflowY !== 'overlay') {
23
+ return false;
24
+ }
25
+ return el.scrollHeight > el.clientHeight + 1;
26
+ }
27
+ /** Nearest ancestor that actually scrolls (falls back to document scrolling element). */
28
+ export function findScrollableParent(el) {
29
+ let node = el.parentElement;
30
+ while (node) {
31
+ if (isScrollable(node))
32
+ return node;
33
+ node = node.parentElement;
34
+ }
35
+ return document.scrollingElement ?? document.documentElement;
36
+ }
37
+ /**
38
+ * Find all scrollable ancestors of `el` up to the root scroll container.
39
+ * Returns array ordered from innermost parent to outermost parent.
40
+ */
41
+ export function findAllScrollableParents(el) {
42
+ const parents = [];
43
+ let node = el.parentElement;
44
+ while (node) {
45
+ if (isScrollable(node)) {
46
+ parents.push(node);
47
+ }
48
+ node = node.parentElement;
49
+ }
50
+ const rootScroll = document.scrollingElement ?? document.documentElement;
51
+ if (rootScroll && !parents.includes(rootScroll)) {
52
+ if (rootScroll.scrollHeight > rootScroll.clientHeight + 1) {
53
+ parents.push(rootScroll);
54
+ }
55
+ }
56
+ return parents.length ? parents : [rootScroll];
57
+ }
58
+ /**
59
+ * Height of sticky chrome (Listening audio bar) that sits at the top of the
60
+ * same scroll container as `el`. Returns 0 when no sticky audio is present.
61
+ */
62
+ function measureStickyTopOffset(scrollParent, target) {
63
+ const stickies = scrollParent.querySelectorAll(`[${ENGLISH_CERT_STICKY_AUDIO_ATTR}]`);
64
+ if (!stickies.length)
65
+ return 0;
66
+ let maxHeight = 0;
67
+ stickies.forEach((sticky) => {
68
+ // Only count sticky bars that share this scroll parent and are not the target itself.
69
+ if (sticky === target || sticky.contains(target))
70
+ return;
71
+ if (!scrollParent.contains(sticky))
72
+ return;
73
+ const height = sticky.getBoundingClientRect().height;
74
+ if (height > maxHeight)
75
+ maxHeight = height;
76
+ });
77
+ return maxHeight > 0 ? maxHeight + STICKY_CLEARANCE_PX : 0;
78
+ }
79
+ /**
80
+ * Scroll `el` into view across all its nested scrollable ancestors.
81
+ * Handles split-view columns (Reading Part 7 / Articles), parent document wrappers,
82
+ * main scroll area, and sticky Listening audio bar clearance.
83
+ */
84
+ export function scrollElementIntoView(el, options) {
85
+ const behavior = options?.behavior ?? 'smooth';
86
+ const block = options?.block ?? 'start';
87
+ const parents = findAllScrollableParents(el);
88
+ if (!parents.length)
89
+ return;
90
+ // Calculate scroll targets for each container before applying any scrollTo,
91
+ // using the initial bounding rect snapshot so intermediate layout movements
92
+ // don't distort measurements.
93
+ const targets = parents.map((parent) => {
94
+ const parentRect = parent.getBoundingClientRect();
95
+ const elRect = el.getBoundingClientRect();
96
+ const currentTop = parent.scrollTop;
97
+ const offsetTop = elRect.top - parentRect.top + currentTop;
98
+ const stickyOffset = measureStickyTopOffset(parent, el);
99
+ let nextTop = offsetTop;
100
+ if (block === 'center') {
101
+ nextTop = offsetTop - parent.clientHeight / 2 + elRect.height / 2;
102
+ }
103
+ else if (block === 'end') {
104
+ nextTop = offsetTop - parent.clientHeight + elRect.height;
105
+ }
106
+ else if (block === 'nearest') {
107
+ const visibleTop = currentTop + stickyOffset;
108
+ const visibleBottom = currentTop + parent.clientHeight;
109
+ const elBottom = offsetTop + elRect.height;
110
+ if (offsetTop >= visibleTop && elBottom <= visibleBottom) {
111
+ nextTop = currentTop;
112
+ }
113
+ else if (offsetTop < visibleTop) {
114
+ nextTop = offsetTop - stickyOffset;
115
+ }
116
+ else {
117
+ nextTop = elBottom - parent.clientHeight;
118
+ }
119
+ }
120
+ else {
121
+ // block === 'start' (default): sit just below sticky chrome
122
+ nextTop = offsetTop - stickyOffset;
123
+ }
124
+ // Tiny padding when no sticky bar so the card is not flush with top edge.
125
+ if (block === 'start' && stickyOffset === 0) {
126
+ nextTop = Math.max(0, nextTop - STICKY_CLEARANCE_PX);
127
+ }
128
+ return { parent, targetTop: Math.max(0, nextTop) };
129
+ });
130
+ // Apply scroll to all scrollable parents in the hierarchy simultaneously
131
+ targets.forEach(({ parent, targetTop }) => {
132
+ parent.scrollTo({ top: targetTop, behavior });
133
+ });
134
+ }
135
+ /**
136
+ * Find the question anchor by id and scroll it into view.
137
+ * Retries briefly so cross-part navigation can wait for React to mount the card.
138
+ */
139
+ export function scrollToEnglishCertQuestion(questionId, options) {
140
+ if (typeof document === 'undefined' || !questionId)
141
+ return;
142
+ const domId = englishCertQuestionDomId(questionId);
143
+ const tryScroll = () => {
144
+ const el = document.getElementById(domId);
145
+ if (el) {
146
+ scrollElementIntoView(el, options);
147
+ return true;
148
+ }
149
+ return false;
150
+ };
151
+ if (tryScroll())
152
+ return;
153
+ let attempt = 1;
154
+ const scheduleNext = () => {
155
+ if (tryScroll())
156
+ return;
157
+ attempt += 1;
158
+ if (attempt < SCROLL_RETRY_MS.length) {
159
+ window.setTimeout(scheduleNext, SCROLL_RETRY_MS[attempt]);
160
+ }
161
+ };
162
+ window.setTimeout(scheduleNext, SCROLL_RETRY_MS[1]);
163
+ }