analytica-frontend-lib 1.6.21 → 1.6.22

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 (405) hide show
  1. package/dist/AccessReportModal/index.js +1 -1
  2. package/dist/AccessReportModal/index.mjs +1 -1
  3. package/dist/AccessibilityWidget/index.js +1 -1
  4. package/dist/AccessibilityWidget/index.mjs +1 -1
  5. package/dist/Accordation/index.js +1 -1
  6. package/dist/Accordation/index.mjs +1 -1
  7. package/dist/ActivitiesHistory/index.js +19 -19
  8. package/dist/ActivitiesHistory/index.mjs +18 -18
  9. package/dist/ActivityCardQuestionBanks/index.js +19 -19
  10. package/dist/ActivityCardQuestionBanks/index.mjs +18 -18
  11. package/dist/ActivityCardQuestionPreview/index.js +19 -19
  12. package/dist/ActivityCardQuestionPreview/index.mjs +18 -18
  13. package/dist/ActivityCreate/ActivityCreate.utils/index.js +3 -3
  14. package/dist/ActivityCreate/ActivityCreate.utils/index.mjs +2 -2
  15. package/dist/ActivityCreate/ActivityCreateContent/index.js +19 -19
  16. package/dist/ActivityCreate/ActivityCreateContent/index.mjs +18 -18
  17. package/dist/ActivityCreate/index.js +19 -19
  18. package/dist/ActivityCreate/index.mjs +18 -18
  19. package/dist/ActivityDetails/index.js +19 -19
  20. package/dist/ActivityDetails/index.mjs +18 -18
  21. package/dist/ActivityFilters/index.js +19 -19
  22. package/dist/ActivityFilters/index.mjs +18 -18
  23. package/dist/ActivityPageLayout/index.js +19 -19
  24. package/dist/ActivityPageLayout/index.mjs +18 -18
  25. package/dist/ActivityPreview/index.js +19 -19
  26. package/dist/ActivityPreview/index.mjs +18 -18
  27. package/dist/Alert/index.js +1 -1
  28. package/dist/Alert/index.mjs +1 -1
  29. package/dist/AlertDialog/index.js +1 -1
  30. package/dist/AlertDialog/index.mjs +1 -1
  31. package/dist/AlertManager/index.js +19 -19
  32. package/dist/AlertManager/index.mjs +18 -18
  33. package/dist/AlertManagerView/index.js +1 -1
  34. package/dist/AlertManagerView/index.mjs +1 -1
  35. package/dist/Alternative/index.js +1 -1
  36. package/dist/Alternative/index.mjs +1 -1
  37. package/dist/AnnouncementModal/index.js +1 -1
  38. package/dist/AnnouncementModal/index.mjs +1 -1
  39. package/dist/AnnouncementsGate/index.js +1 -1
  40. package/dist/AnnouncementsGate/index.mjs +1 -1
  41. package/dist/AnswerKeyModal/index.js +1 -1
  42. package/dist/AnswerKeyModal/index.mjs +1 -1
  43. package/dist/AppHeader/index.js +1 -1
  44. package/dist/AppHeader/index.mjs +1 -1
  45. package/dist/AppLayout/index.js +1 -1
  46. package/dist/AppLayout/index.mjs +1 -1
  47. package/dist/Avatar/index.js +1 -1
  48. package/dist/Avatar/index.mjs +1 -1
  49. package/dist/Badge/index.js +1 -1
  50. package/dist/Badge/index.mjs +1 -1
  51. package/dist/BreadcrumbMenu/index.js +1 -1
  52. package/dist/BreadcrumbMenu/index.mjs +1 -1
  53. package/dist/Button/index.js +1 -1
  54. package/dist/Button/index.mjs +1 -1
  55. package/dist/Calendar/index.js +1 -1
  56. package/dist/Calendar/index.mjs +1 -1
  57. package/dist/CalendarCard/index.js +1 -1
  58. package/dist/CalendarCard/index.mjs +1 -1
  59. package/dist/Card/index.js +1 -1
  60. package/dist/Card/index.mjs +1 -1
  61. package/dist/ChartComponents/index.js +1 -1
  62. package/dist/ChartComponents/index.mjs +1 -1
  63. package/dist/Chat/index.js +1 -1
  64. package/dist/Chat/index.mjs +1 -1
  65. package/dist/Chatbot/index.js +1 -1
  66. package/dist/Chatbot/index.mjs +1 -1
  67. package/dist/CheckBox/index.js +1 -1
  68. package/dist/CheckBox/index.mjs +1 -1
  69. package/dist/CheckBoxGroup/index.js +19 -19
  70. package/dist/CheckBoxGroup/index.mjs +18 -18
  71. package/dist/Chips/index.js +1 -1
  72. package/dist/Chips/index.mjs +1 -1
  73. package/dist/ChoroplethMap/index.js +1 -1
  74. package/dist/ChoroplethMap/index.mjs +1 -1
  75. package/dist/ColorPicker/index.js +1 -1
  76. package/dist/ColorPicker/index.mjs +1 -1
  77. package/dist/ComparatorChart/ComparatorView/index.js +1 -1
  78. package/dist/ComparatorChart/ComparatorView/index.mjs +1 -1
  79. package/dist/CorrectActivityModal/index.js +6 -6
  80. package/dist/CorrectActivityModal/index.mjs +5 -5
  81. package/dist/Divider/index.js +1 -1
  82. package/dist/Divider/index.mjs +1 -1
  83. package/dist/DownloadButton/index.js +1 -1
  84. package/dist/DownloadButton/index.mjs +1 -1
  85. package/dist/DownloadModal/index.js +1 -1
  86. package/dist/DownloadModal/index.mjs +1 -1
  87. package/dist/DragHandleButton/index.js +1 -1
  88. package/dist/DragHandleButton/index.mjs +1 -1
  89. package/dist/DropdownMenu/index.js +1 -1
  90. package/dist/DropdownMenu/index.mjs +1 -1
  91. package/dist/EmptyState/index.js +1 -1
  92. package/dist/EmptyState/index.mjs +1 -1
  93. package/dist/EnemClassroomStartModal/index.d.ts +0 -2
  94. package/dist/EnemClassroomStartModal/index.d.ts.map +1 -1
  95. package/dist/EnemClassroomStartModal/index.js +4 -6
  96. package/dist/EnemClassroomStartModal/index.js.map +1 -1
  97. package/dist/EnemClassroomStartModal/index.mjs +3 -5
  98. package/dist/EssayThemePicker/index.js +1 -1
  99. package/dist/EssayThemePicker/index.mjs +1 -1
  100. package/dist/ExamDetailsLayout/ExamDetailsPage/index.js +19 -19
  101. package/dist/ExamDetailsLayout/ExamDetailsPage/index.mjs +18 -18
  102. package/dist/ExamPageLayout/examsTableConfig/index.js +1 -1
  103. package/dist/ExamPageLayout/examsTableConfig/index.mjs +1 -1
  104. package/dist/ExamPageLayout/index.js +19 -19
  105. package/dist/ExamPageLayout/index.mjs +18 -18
  106. package/dist/FileAttachment/index.js +1 -1
  107. package/dist/FileAttachment/index.mjs +1 -1
  108. package/dist/FileDropzone/index.js +1 -1
  109. package/dist/FileDropzone/index.mjs +1 -1
  110. package/dist/Forum/index.js +4 -4
  111. package/dist/Forum/index.mjs +3 -3
  112. package/dist/HtmlMathRenderer/index.js +1 -1
  113. package/dist/HtmlMathRenderer/index.mjs +1 -1
  114. package/dist/IconButton/index.js +1 -1
  115. package/dist/IconButton/index.mjs +1 -1
  116. package/dist/IconRoundedButton/index.js +1 -1
  117. package/dist/IconRoundedButton/index.mjs +1 -1
  118. package/dist/ImagePreviewCard/index.js +1 -1
  119. package/dist/ImagePreviewCard/index.mjs +1 -1
  120. package/dist/ImageUpload/index.js +1 -1
  121. package/dist/ImageUpload/index.mjs +1 -1
  122. package/dist/LatexRenderer/index.js +1 -1
  123. package/dist/LatexRenderer/index.mjs +1 -1
  124. package/dist/LevelBar/index.js +1 -1
  125. package/dist/LevelBar/index.mjs +1 -1
  126. package/dist/Menu/index.js +1 -1
  127. package/dist/Menu/index.mjs +1 -1
  128. package/dist/MetricBox/index.js +1 -1
  129. package/dist/MetricBox/index.mjs +1 -1
  130. package/dist/Modal/index.js +1 -1
  131. package/dist/Modal/index.mjs +1 -1
  132. package/dist/ModalComponents/index.js +1 -1
  133. package/dist/ModalComponents/index.mjs +1 -1
  134. package/dist/MultiSearchSelect/index.js +1 -1
  135. package/dist/MultiSearchSelect/index.mjs +1 -1
  136. package/dist/MultipleChoice/index.js +1 -1
  137. package/dist/MultipleChoice/index.mjs +1 -1
  138. package/dist/NavButton/index.js +1 -1
  139. package/dist/NavButton/index.mjs +1 -1
  140. package/dist/NoSearchResult/index.js +1 -1
  141. package/dist/NoSearchResult/index.mjs +1 -1
  142. package/dist/NotFound/index.js +1 -1
  143. package/dist/NotFound/index.mjs +1 -1
  144. package/dist/NotificationCard/index.js +1 -1
  145. package/dist/NotificationCard/index.mjs +1 -1
  146. package/dist/PerformanceDistributionChart/index.js +1 -1
  147. package/dist/PerformanceDistributionChart/index.mjs +1 -1
  148. package/dist/PerformanceQuestionsData/index.js +1 -1
  149. package/dist/PerformanceQuestionsData/index.mjs +1 -1
  150. package/dist/PerformanceRanking/index.js +1 -1
  151. package/dist/PerformanceRanking/index.mjs +1 -1
  152. package/dist/PerformanceReport/index.js +1 -1
  153. package/dist/PerformanceReport/index.mjs +1 -1
  154. package/dist/PerformanceReportModal/index.js +1 -1
  155. package/dist/PerformanceReportModal/index.mjs +1 -1
  156. package/dist/PeriodSelector/index.js +1 -1
  157. package/dist/PeriodSelector/index.mjs +1 -1
  158. package/dist/PrintableUsersTable/index.js +19 -19
  159. package/dist/PrintableUsersTable/index.mjs +18 -18
  160. package/dist/ProficiencyChart/index.js +1 -1
  161. package/dist/ProficiencyChart/index.mjs +1 -1
  162. package/dist/ProgressBar/index.js +1 -1
  163. package/dist/ProgressBar/index.mjs +1 -1
  164. package/dist/ProgressCircle/index.js +1 -1
  165. package/dist/ProgressCircle/index.mjs +1 -1
  166. package/dist/ProgressModal/index.js +1 -1
  167. package/dist/ProgressModal/index.mjs +1 -1
  168. package/dist/QuestionsData/index.js +1 -1
  169. package/dist/QuestionsData/index.mjs +1 -1
  170. package/dist/QuestionsPerformanceCard/index.js +19 -19
  171. package/dist/QuestionsPerformanceCard/index.mjs +18 -18
  172. package/dist/Quiz/QuizResult/index.d.ts.map +1 -1
  173. package/dist/Quiz/QuizResult/index.js +4 -4
  174. package/dist/Quiz/QuizResult/index.mjs +3 -3
  175. package/dist/Quiz/TeacherFeedbackSection/index.js +3 -3
  176. package/dist/Quiz/TeacherFeedbackSection/index.mjs +2 -2
  177. package/dist/Quiz/TeacherQuestionComment/index.js +3 -3
  178. package/dist/Quiz/TeacherQuestionComment/index.mjs +2 -2
  179. package/dist/Quiz/index.d.ts.map +1 -1
  180. package/dist/Quiz/index.js +6 -6
  181. package/dist/Quiz/index.mjs +5 -5
  182. package/dist/Quiz/useQuizStore/index.d.ts +6 -0
  183. package/dist/Quiz/useQuizStore/index.d.ts.map +1 -1
  184. package/dist/Quiz/useQuizStore/index.js +2 -2
  185. package/dist/Quiz/useQuizStore/index.mjs +1 -1
  186. package/dist/QuizTimer/index.js +3 -3
  187. package/dist/QuizTimer/index.mjs +2 -2
  188. package/dist/Radio/index.js +1 -1
  189. package/dist/Radio/index.mjs +1 -1
  190. package/dist/RangeGauge/index.js +1 -1
  191. package/dist/RangeGauge/index.mjs +1 -1
  192. package/dist/RecommendedLessonCreate/index.js +19 -19
  193. package/dist/RecommendedLessonCreate/index.mjs +18 -18
  194. package/dist/RecommendedLessonDetails/index.js +1 -1
  195. package/dist/RecommendedLessonDetails/index.mjs +1 -1
  196. package/dist/RecommendedLessonsHistory/index.js +19 -19
  197. package/dist/RecommendedLessonsHistory/index.mjs +18 -18
  198. package/dist/ReportDetailModal/index.js +1 -1
  199. package/dist/ReportDetailModal/index.mjs +1 -1
  200. package/dist/RestrictedAccess/index.js +1 -1
  201. package/dist/RestrictedAccess/index.mjs +1 -1
  202. package/dist/RichEditor/index.js +3 -3
  203. package/dist/RichEditor/index.mjs +2 -2
  204. package/dist/{RichEditorCore-F5QPQLEK.js → RichEditorCore-NEIZKFKE.js} +2 -2
  205. package/dist/{RichEditorCore-F5QPQLEK.js.map → RichEditorCore-NEIZKFKE.js.map} +1 -1
  206. package/dist/{RichEditorCore-QZYPHNRY.mjs → RichEditorCore-TLAFZ5XG.mjs} +2 -2
  207. package/dist/ScoreCircle/index.js +1 -1
  208. package/dist/ScoreCircle/index.mjs +1 -1
  209. package/dist/Search/index.js +1 -1
  210. package/dist/Search/index.mjs +1 -1
  211. package/dist/SearchSelect/index.js +1 -1
  212. package/dist/SearchSelect/index.mjs +1 -1
  213. package/dist/Select/index.js +1 -1
  214. package/dist/Select/index.mjs +1 -1
  215. package/dist/SelectionButton/index.js +1 -1
  216. package/dist/SelectionButton/index.mjs +1 -1
  217. package/dist/SendActivityModal/SendActivityModal.js +19 -19
  218. package/dist/SendActivityModal/SendActivityModal.mjs +18 -18
  219. package/dist/SendActivityModal/index.js +19 -19
  220. package/dist/SendActivityModal/index.mjs +18 -18
  221. package/dist/SendLessonModal/index.js +19 -19
  222. package/dist/SendLessonModal/index.mjs +18 -18
  223. package/dist/SimpleBarChart/index.js +1 -1
  224. package/dist/SimpleBarChart/index.mjs +1 -1
  225. package/dist/SimulatedPerformance/SimulatedPerformanceView/index.js +19 -19
  226. package/dist/SimulatedPerformance/SimulatedPerformanceView/index.mjs +18 -18
  227. package/dist/SimulatedPerformance/useSimulatedPerformance/index.js +1 -1
  228. package/dist/SimulatedPerformance/useSimulatedPerformance/index.mjs +1 -1
  229. package/dist/SimulatedStudentSimulationsModal/index.js +19 -19
  230. package/dist/SimulatedStudentSimulationsModal/index.mjs +5 -5
  231. package/dist/SimulationsPage/index.js +19 -19
  232. package/dist/SimulationsPage/index.mjs +18 -18
  233. package/dist/Skeleton/index.js +1 -1
  234. package/dist/Skeleton/index.mjs +1 -1
  235. package/dist/StatisticsCard/index.js +1 -1
  236. package/dist/StatisticsCard/index.mjs +1 -1
  237. package/dist/Stepper/index.js +1 -1
  238. package/dist/Stepper/index.mjs +1 -1
  239. package/dist/StudentLessonProgressModal/index.js +1 -1
  240. package/dist/StudentLessonProgressModal/index.mjs +1 -1
  241. package/dist/StudentPerformanceDetailsModal/index.js +1 -1
  242. package/dist/StudentPerformanceDetailsModal/index.mjs +1 -1
  243. package/dist/StudentRanking/index.js +1 -1
  244. package/dist/StudentRanking/index.mjs +1 -1
  245. package/dist/SubjectIcons/index.js +1 -1
  246. package/dist/SubjectIcons/index.mjs +1 -1
  247. package/dist/SubjectMenuOverflow/index.js +1 -1
  248. package/dist/SubjectMenuOverflow/index.mjs +1 -1
  249. package/dist/Support/TicketModal/index.js +1 -1
  250. package/dist/Support/TicketModal/index.mjs +1 -1
  251. package/dist/Support/index.js +1 -1
  252. package/dist/Support/index.mjs +1 -1
  253. package/dist/Table/TablePagination/index.js +1 -1
  254. package/dist/Table/TablePagination/index.mjs +1 -1
  255. package/dist/Table/index.js +1 -1
  256. package/dist/Table/index.mjs +1 -1
  257. package/dist/TableProvider/index.js +19 -19
  258. package/dist/TableProvider/index.mjs +18 -18
  259. package/dist/Text/index.js +1 -1
  260. package/dist/Text/index.mjs +1 -1
  261. package/dist/TextArea/index.js +1 -1
  262. package/dist/TextArea/index.mjs +1 -1
  263. package/dist/ThemeToggle/index.js +1 -1
  264. package/dist/ThemeToggle/index.mjs +1 -1
  265. package/dist/TimeChart/index.js +1 -1
  266. package/dist/TimeChart/index.mjs +1 -1
  267. package/dist/TimeReport/index.js +1 -1
  268. package/dist/TimeReport/index.mjs +1 -1
  269. package/dist/Toast/Toaster/index.js +1 -1
  270. package/dist/Toast/Toaster/index.mjs +1 -1
  271. package/dist/Toast/index.js +1 -1
  272. package/dist/Toast/index.mjs +1 -1
  273. package/dist/ToggleSwitch/index.js +1 -1
  274. package/dist/ToggleSwitch/index.mjs +1 -1
  275. package/dist/TokenValidation/index.js +1 -1
  276. package/dist/TokenValidation/index.mjs +1 -1
  277. package/dist/Tooltip/index.js +1 -1
  278. package/dist/Tooltip/index.mjs +1 -1
  279. package/dist/TruncatedText/index.js +1 -1
  280. package/dist/TruncatedText/index.mjs +1 -1
  281. package/dist/TypeSelector/index.js +1 -1
  282. package/dist/TypeSelector/index.mjs +1 -1
  283. package/dist/UnifiedDraftModelPage/index.js +19 -19
  284. package/dist/UnifiedDraftModelPage/index.mjs +18 -18
  285. package/dist/UnifiedHistoryPage/index.js +19 -19
  286. package/dist/UnifiedHistoryPage/index.mjs +18 -18
  287. package/dist/VerticalBarChart/index.js +1 -1
  288. package/dist/VerticalBarChart/index.mjs +1 -1
  289. package/dist/VideoPlayer/index.js +1 -1
  290. package/dist/VideoPlayer/index.mjs +1 -1
  291. package/dist/Whiteboard/index.js +1 -1
  292. package/dist/Whiteboard/index.mjs +1 -1
  293. package/dist/{chunk-JBAGQ7L5.js → chunk-3VG5MEMY.js} +14 -13
  294. package/dist/chunk-3VG5MEMY.js.map +1 -0
  295. package/dist/{chunk-ED23HOE6.mjs → chunk-5NIU4XJY.mjs} +7 -6
  296. package/dist/chunk-5NIU4XJY.mjs.map +1 -0
  297. package/dist/{chunk-DDZDNGBU.mjs → chunk-64WGFT5E.mjs} +3 -1
  298. package/dist/chunk-64WGFT5E.mjs.map +1 -0
  299. package/dist/{chunk-QN32O3J3.js → chunk-6MK23BTK.js} +62 -62
  300. package/dist/{chunk-QN32O3J3.js.map → chunk-6MK23BTK.js.map} +1 -1
  301. package/dist/{chunk-XX4WE4TL.js → chunk-726PIA5M.js} +1 -1
  302. package/dist/chunk-726PIA5M.js.map +1 -0
  303. package/dist/{chunk-VZJKHIHN.mjs → chunk-7KALNPIG.mjs} +4 -4
  304. package/dist/{chunk-NQNEG4KR.mjs → chunk-7VKFZHWP.mjs} +11 -10
  305. package/dist/chunk-7VKFZHWP.mjs.map +1 -0
  306. package/dist/{chunk-GYQUB3VW.js → chunk-BBFV3NDF.js} +8 -6
  307. package/dist/chunk-BBFV3NDF.js.map +1 -0
  308. package/dist/{chunk-I3Z2BHGN.js → chunk-BJHYT75K.js} +3 -3
  309. package/dist/{chunk-I3Z2BHGN.js.map → chunk-BJHYT75K.js.map} +1 -1
  310. package/dist/{chunk-CFTQ24UT.mjs → chunk-CMYBNZ74.mjs} +2 -2
  311. package/dist/{chunk-R7NS5KFE.mjs → chunk-CWUNF7U6.mjs} +3 -3
  312. package/dist/{chunk-NAOHJW6H.mjs → chunk-DLHV7CZN.mjs} +6 -6
  313. package/dist/{chunk-7EXSCXPK.mjs → chunk-DMYETMZG.mjs} +2 -2
  314. package/dist/{chunk-RQPSA4QV.js → chunk-GQUTHDV2.js} +3 -3
  315. package/dist/{chunk-RQPSA4QV.js.map → chunk-GQUTHDV2.js.map} +1 -1
  316. package/dist/{chunk-OZWPOXVU.mjs → chunk-GVGDUCVF.mjs} +2 -2
  317. package/dist/{chunk-QFWY46XU.js → chunk-IMDS5NQL.js} +16 -16
  318. package/dist/{chunk-QFWY46XU.js.map → chunk-IMDS5NQL.js.map} +1 -1
  319. package/dist/{chunk-KYGFYAU7.js → chunk-JHN35HPH.js} +3 -3
  320. package/dist/{chunk-KYGFYAU7.js.map → chunk-JHN35HPH.js.map} +1 -1
  321. package/dist/{chunk-DMZTDCUF.js → chunk-JPQI2RIV.js} +3 -3
  322. package/dist/{chunk-DMZTDCUF.js.map → chunk-JPQI2RIV.js.map} +1 -1
  323. package/dist/{chunk-LXX5KU3O.mjs → chunk-LMDAF6LA.mjs} +2 -2
  324. package/dist/{chunk-FL4W4S6J.mjs → chunk-MOSP7FR5.mjs} +1 -1
  325. package/dist/chunk-MOSP7FR5.mjs.map +1 -0
  326. package/dist/{chunk-HZXXADG4.js → chunk-NLPBW3SW.js} +44 -43
  327. package/dist/chunk-NLPBW3SW.js.map +1 -0
  328. package/dist/{chunk-TAABXGZL.mjs → chunk-O3JP6ZMQ.mjs} +2 -2
  329. package/dist/{chunk-MFO4S7T5.js → chunk-PJOBOBNJ.js} +3 -3
  330. package/dist/{chunk-MFO4S7T5.js.map → chunk-PJOBOBNJ.js.map} +1 -1
  331. package/dist/{chunk-CWGYVJTQ.mjs → chunk-PORNR2NU.mjs} +3 -3
  332. package/dist/chunk-QF3UKRIJ.js +33 -0
  333. package/dist/chunk-QF3UKRIJ.js.map +1 -0
  334. package/dist/chunk-RAHXO5YU.mjs +33 -0
  335. package/dist/chunk-RAHXO5YU.mjs.map +1 -0
  336. package/dist/{chunk-BTZANNVO.js → chunk-RQ65U5G2.js} +3 -3
  337. package/dist/{chunk-BTZANNVO.js.map → chunk-RQ65U5G2.js.map} +1 -1
  338. package/dist/{chunk-Q56I63FO.mjs → chunk-RVGLLMWG.mjs} +2 -2
  339. package/dist/{chunk-VVRRAF6H.js → chunk-S2S7TK3L.js} +49 -36
  340. package/dist/chunk-S2S7TK3L.js.map +1 -0
  341. package/dist/{chunk-INIH23HU.js → chunk-VF6QXUX3.js} +2 -2
  342. package/dist/{chunk-INIH23HU.js.map → chunk-VF6QXUX3.js.map} +1 -1
  343. package/dist/{chunk-QWC5GC2Q.mjs → chunk-WY7V5UJN.mjs} +2 -2
  344. package/dist/{chunk-OCKO7T7L.js → chunk-XQAYZBLL.js} +3 -3
  345. package/dist/{chunk-OCKO7T7L.js.map → chunk-XQAYZBLL.js.map} +1 -1
  346. package/dist/{chunk-ATGLKKXI.js → chunk-Y7ZBWTD7.js} +17 -17
  347. package/dist/{chunk-ATGLKKXI.js.map → chunk-Y7ZBWTD7.js.map} +1 -1
  348. package/dist/{chunk-7XWWI5EY.js → chunk-YFIB3YNQ.js} +11 -11
  349. package/dist/{chunk-7XWWI5EY.js.map → chunk-YFIB3YNQ.js.map} +1 -1
  350. package/dist/{chunk-UCWTF336.mjs → chunk-YWGJAB63.mjs} +2 -2
  351. package/dist/{chunk-4MPXVPBQ.mjs → chunk-YYHS2TM6.mjs} +43 -30
  352. package/dist/chunk-YYHS2TM6.mjs.map +1 -0
  353. package/dist/components/Quiz/Quiz.utils.d.ts +8 -0
  354. package/dist/components/Quiz/Quiz.utils.d.ts.map +1 -1
  355. package/dist/hooks/useAppContent/index.js +19 -19
  356. package/dist/hooks/useAppContent/index.mjs +18 -18
  357. package/dist/hooks/useComparator/index.js +1 -1
  358. package/dist/hooks/useComparator/index.mjs +1 -1
  359. package/dist/hooks/useDraftAutoSave/index.js +3 -3
  360. package/dist/hooks/useDraftAutoSave/index.mjs +2 -2
  361. package/dist/index.js +19 -19
  362. package/dist/index.mjs +18 -18
  363. package/dist/types/enemClassroom/index.d.ts +29 -2
  364. package/dist/types/enemClassroom/index.d.ts.map +1 -1
  365. package/dist/types/enemClassroom/index.js +4 -2
  366. package/dist/types/enemClassroom/index.js.map +1 -1
  367. package/dist/types/enemClassroom/index.mjs +3 -1
  368. package/dist/types/enemClassroom.d.ts +29 -2
  369. package/dist/types/enemClassroom.d.ts.map +1 -1
  370. package/dist/types/questionTypes/index.js +1 -1
  371. package/dist/types/questionTypes/index.mjs +1 -1
  372. package/dist/utils/index.js +1 -1
  373. package/dist/utils/index.mjs +1 -1
  374. package/dist/utils/renderSubjectCell/index.js +1 -1
  375. package/dist/utils/renderSubjectCell/index.mjs +1 -1
  376. package/dist/utils/renderTextCell/index.js +1 -1
  377. package/dist/utils/renderTextCell/index.mjs +1 -1
  378. package/package.json +1 -1
  379. package/dist/chunk-4MPXVPBQ.mjs.map +0 -1
  380. package/dist/chunk-DDZDNGBU.mjs.map +0 -1
  381. package/dist/chunk-ED23HOE6.mjs.map +0 -1
  382. package/dist/chunk-FL4W4S6J.mjs.map +0 -1
  383. package/dist/chunk-GYQUB3VW.js.map +0 -1
  384. package/dist/chunk-HZXXADG4.js.map +0 -1
  385. package/dist/chunk-JBAGQ7L5.js.map +0 -1
  386. package/dist/chunk-ND5M3DZA.mjs +0 -21
  387. package/dist/chunk-ND5M3DZA.mjs.map +0 -1
  388. package/dist/chunk-NQNEG4KR.mjs.map +0 -1
  389. package/dist/chunk-NYKBCQUO.js +0 -21
  390. package/dist/chunk-NYKBCQUO.js.map +0 -1
  391. package/dist/chunk-VVRRAF6H.js.map +0 -1
  392. package/dist/chunk-XX4WE4TL.js.map +0 -1
  393. /package/dist/{RichEditorCore-QZYPHNRY.mjs.map → RichEditorCore-TLAFZ5XG.mjs.map} +0 -0
  394. /package/dist/{chunk-VZJKHIHN.mjs.map → chunk-7KALNPIG.mjs.map} +0 -0
  395. /package/dist/{chunk-CFTQ24UT.mjs.map → chunk-CMYBNZ74.mjs.map} +0 -0
  396. /package/dist/{chunk-R7NS5KFE.mjs.map → chunk-CWUNF7U6.mjs.map} +0 -0
  397. /package/dist/{chunk-NAOHJW6H.mjs.map → chunk-DLHV7CZN.mjs.map} +0 -0
  398. /package/dist/{chunk-7EXSCXPK.mjs.map → chunk-DMYETMZG.mjs.map} +0 -0
  399. /package/dist/{chunk-OZWPOXVU.mjs.map → chunk-GVGDUCVF.mjs.map} +0 -0
  400. /package/dist/{chunk-LXX5KU3O.mjs.map → chunk-LMDAF6LA.mjs.map} +0 -0
  401. /package/dist/{chunk-TAABXGZL.mjs.map → chunk-O3JP6ZMQ.mjs.map} +0 -0
  402. /package/dist/{chunk-CWGYVJTQ.mjs.map → chunk-PORNR2NU.mjs.map} +0 -0
  403. /package/dist/{chunk-Q56I63FO.mjs.map → chunk-RVGLLMWG.mjs.map} +0 -0
  404. /package/dist/{chunk-QWC5GC2Q.mjs.map → chunk-WY7V5UJN.mjs.map} +0 -0
  405. /package/dist/{chunk-UCWTF336.mjs.map → chunk-YWGJAB63.mjs.map} +0 -0
@@ -1 +1 @@
1
- {"version":3,"sources":["/home/runner/work/analytica-frontend-lib/analytica-frontend-lib/dist/chunk-QFWY46XU.js","../src/components/shared/ImageAnswerView.tsx","../src/components/Quiz/QuizContent.tsx","../src/utils/image/imageAnswer.utils.ts"],"names":["jsx","Fragment","jsxs"],"mappings":"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","file":"/home/runner/work/analytica-frontend-lib/analytica-frontend-lib/dist/chunk-QFWY46XU.js","sourcesContent":[null,"import Text from '../Text/Text';\nimport { cn } from '../../utils/utils';\n\n/** A point on the image, in percentage points of its width/height (0-100). */\nexport interface ImagePoint {\n x: number;\n y: number;\n}\n\nexport interface ImageAnswerViewProps {\n /** URL of the image the student had to click on */\n readonly imageUrl: string;\n /** The answer key point. Null when the question carries no key. */\n readonly correctPoint: ImagePoint | null;\n /** Where the student clicked. Null when the question was left blank. */\n readonly studentPoint: ImagePoint | null;\n /**\n * Radius within which a click counts as correct, in the same percentage\n * space. Comes from the backend so the drawn circle matches the grade.\n */\n readonly toleranceRadius: number;\n /** Whether the answer key may be revealed (false while pending) */\n readonly showCorrectness?: boolean;\n}\n\n/** Describe a point for screen readers. */\nconst describePoint = (point: ImagePoint): string =>\n `${Math.round(point.x)}% da esquerda, ${Math.round(point.y)}% do topo`;\n\n/** Euclidean distance between two points in the 0-100 space. */\nconst distanceBetween = (a: ImagePoint, b: ImagePoint): number =>\n Math.hypot(a.x - b.x, a.y - b.y);\n\n/**\n * Read-only view of an image question: the image, the answer key area and where\n * the student clicked.\n *\n * Shared by the activity correction modal, the simulation detail modal and the\n * student's result screen, which used to carry three divergent copies of this —\n * one of them drawing a mock image with a hardcoded answer area, and each one\n * deciding correctness with a different radius of its own. Here there is a\n * single rule, and it is the backend's: a click is correct when its distance to\n * the correct point is at most `toleranceRadius`.\n *\n * The target circle is drawn with the diameter of that tolerance, so what the\n * reader sees is what decided the grade.\n *\n * @param props - Component props\n * @returns JSX element\n *\n * @example\n * ```tsx\n * <ImageAnswerView\n * imageUrl={question.additionalContent ?? ''}\n * correctPoint={{ x: 50, y: 30 }}\n * studentPoint={{ x: 52, y: 28 }}\n * toleranceRadius={10}\n * />\n * ```\n */\nexport const ImageAnswerView = ({\n imageUrl,\n correctPoint,\n studentPoint,\n toleranceRadius,\n showCorrectness = true,\n}: ImageAnswerViewProps) => {\n const canShowCorrectness = showCorrectness && correctPoint !== null;\n const isCorrect =\n correctPoint !== null &&\n studentPoint !== null &&\n distanceBetween(studentPoint, correctPoint) <= toleranceRadius;\n\n /** Full spoken summary of the question, the only non-visual access to it. */\n const spatialSummary = (): string => {\n if (!correctPoint) {\n return studentPoint\n ? `Resposta do aluno em ${describePoint(studentPoint)}.`\n : 'Nenhuma resposta do aluno fornecida.';\n }\n\n const correctDescription = describePoint(correctPoint);\n if (!studentPoint) {\n return `Área correta localizada em ${correctDescription}. Nenhuma resposta do aluno fornecida.`;\n }\n\n const deltaX = studentPoint.x - correctPoint.x;\n const deltaY = studentPoint.y - correctPoint.y;\n let direction: string;\n if (Math.abs(deltaX) > Math.abs(deltaY)) {\n direction = deltaX > 0 ? 'à direita' : 'à esquerda';\n } else {\n direction = deltaY > 0 ? 'abaixo' : 'acima';\n }\n\n const distance = Math.round(distanceBetween(studentPoint, correctPoint));\n const verdict = isCorrect\n ? 'A resposta está dentro da área de tolerância e é considerada correta.'\n : 'A resposta está fora da área de tolerância e é considerada incorreta.';\n\n return `Área correta localizada em ${correctDescription}. Resposta do aluno em ${describePoint(studentPoint)}. A resposta do aluno está ${distance}% de distância ${direction} da área correta. ${verdict}`;\n };\n\n return (\n <div className=\"pt-2 space-y-4\">\n <div className=\"flex items-center gap-4 text-xs\">\n {canShowCorrectness && (\n <div className=\"flex items-center gap-2\">\n <div className=\"w-3 h-3 rounded-full bg-indicator-primary/70 border border-[#F8CC2E]\" />\n <Text size=\"sm\" weight=\"normal\" color=\"text-text-600\">\n Área correta\n </Text>\n </div>\n )}\n {canShowCorrectness && studentPoint && (\n <div className=\"flex items-center gap-2\">\n <div\n className={cn(\n 'w-3 h-3 rounded-full border border-white',\n isCorrect ? 'bg-success-600/70' : 'bg-indicator-error/70'\n )}\n />\n <Text size=\"sm\" weight=\"normal\" color=\"text-text-600\">\n {isCorrect ? 'Resposta correta' : 'Resposta incorreta'}\n </Text>\n </div>\n )}\n </div>\n\n <div className=\"relative w-full\">\n <div className=\"sr-only\">{spatialSummary()}</div>\n\n {imageUrl ? (\n <img\n src={imageUrl}\n alt={\n correctPoint\n ? `Questão de imagem com área correta localizada em ${describePoint(correctPoint)}`\n : 'Questão de imagem'\n }\n className=\"w-full h-auto rounded-md\"\n />\n ) : (\n <div className=\"rounded-md border border-border-100 bg-background-50 p-6 text-center\">\n <Text size=\"sm\" className=\"text-text-600\">\n Imagem da questão indisponível\n </Text>\n </div>\n )}\n\n {canShowCorrectness && correctPoint && (\n <div\n aria-hidden=\"true\"\n data-testid=\"image-correct-area\"\n className=\"absolute rounded-full bg-indicator-primary/70 border-4 border-[#F8CC2E] pointer-events-none -translate-x-1/2 -translate-y-1/2\"\n style={{\n // Diameter = twice the tolerance, so the circle IS the pass mark.\n width: `${toleranceRadius * 2}%`,\n aspectRatio: '1 / 1',\n left: `${correctPoint.x}%`,\n top: `${correctPoint.y}%`,\n }}\n />\n )}\n\n {studentPoint && (\n <div\n aria-hidden=\"true\"\n data-testid=\"image-student-point\"\n className={cn(\n 'absolute rounded-full border-4 pointer-events-none -translate-x-1/2 -translate-y-1/2',\n canShowCorrectness &&\n isCorrect &&\n 'bg-success-600/70 border-white',\n canShowCorrectness &&\n !isCorrect &&\n 'bg-indicator-error/70 border-white',\n !canShowCorrectness && 'bg-indicator-primary/70 border-[#F8CC2E]'\n )}\n style={{\n minWidth: '20px',\n maxWidth: '40px',\n width: '4%',\n aspectRatio: '1 / 1',\n left: `${studentPoint.x}%`,\n top: `${studentPoint.y}%`,\n }}\n />\n )}\n </div>\n </div>\n );\n};\n\nexport default ImageAnswerView;\n","import {\n Fragment,\n forwardRef,\n MouseEvent,\n ReactNode,\n useCallback,\n useEffect,\n useId,\n useMemo,\n useRef,\n useState,\n} from 'react';\nimport { ANSWER_STATUS, useQuizStore } from './useQuizStore';\nimport { QuizVariant } from './Quiz.types';\nimport CorrectionSourceTag from './CorrectionSourceTag';\nimport { TrueFalseEnum } from '../../enums/Quiz';\nimport {\n prependLetterToHtml,\n getTrueOrFalseOptionState,\n shuffleWithSeed,\n} from './Quiz.utils';\nimport { cn } from '../../utils/utils';\nimport { stripHtmlTags } from '../../utils/stringUtils';\nimport Select, {\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '../Select/Select';\nimport TextArea from '../TextArea/TextArea';\nimport { AlternativesList } from '../Alternative/Alternative';\nimport { OptionStatus } from '../../enums/Options';\nimport { MultipleChoiceList } from '../MultipleChoice/MultipleChoice';\nimport Badge from '../Badge/Badge';\nimport { CheckCircleIcon } from '@phosphor-icons/react/dist/csr/CheckCircle';\nimport { XCircleIcon } from '@phosphor-icons/react/dist/csr/XCircle';\nimport ImageQuestion from '../../assets/img/mock-image-question.png';\nimport { ImageAnswerView } from '../shared/ImageAnswerView';\nimport {\n DEFAULT_IMAGE_TOLERANCE,\n parseImageCorrectPoint,\n parseImageStudentPoint,\n} from '../../utils/image/imageAnswer.utils';\nimport Text from '../Text/Text';\nimport { HtmlMathRenderer } from '../HtmlMathRenderer';\nexport const getStatusBadge = (status?: OptionStatus) => {\n switch (status) {\n case OptionStatus.CORRECT:\n return (\n <Badge variant=\"solid\" action=\"success\" iconLeft={<CheckCircleIcon />}>\n Resposta correta\n </Badge>\n );\n case OptionStatus.INCORRECT:\n return (\n <Badge variant=\"solid\" action=\"error\" iconLeft={<XCircleIcon />}>\n Resposta incorreta\n </Badge>\n );\n default:\n return null;\n }\n};\n\nexport const getStatusStyles = (variantCorrect?: OptionStatus) => {\n switch (variantCorrect) {\n case OptionStatus.CORRECT:\n return 'bg-success-background border-success-300';\n case OptionStatus.INCORRECT:\n return 'bg-error-background border-error-300';\n default:\n return '';\n }\n};\n\n/**\n * Normalizes parsed answer data to Record<string, string> format.\n * Handles legacy array format [{ optionId, selectedValue }] by converting to { [optionId]: selectedValue }.\n */\nconst normalizeAnswerData = (parsed: unknown): Record<string, string> => {\n // Handle legacy array format: [{ optionId: string, selectedValue: string }]\n if (Array.isArray(parsed)) {\n const result: Record<string, string> = {};\n for (const item of parsed) {\n if (\n item &&\n typeof item === 'object' &&\n 'optionId' in item &&\n 'selectedValue' in item\n ) {\n result[item.optionId as string] = item.selectedValue as string;\n }\n }\n return result;\n }\n\n // Handle expected object format: { [optionId]: selectedValue }\n if (parsed && typeof parsed === 'object') {\n return parsed as Record<string, string>;\n }\n\n return {};\n};\n\n/**\n * Parses JSON answers from stored answer string.\n * In result mode, uses persisted results. Otherwise, uses current draft answers.\n * Normalizes legacy array format to expected Record<string, string> map.\n */\nconst parseStoredAnswers = (\n variant: QuizVariant,\n resultAnswer: string | null | undefined,\n currentAnswer: string | null | undefined\n): Record<string, string> => {\n if (variant === QuizVariant.RESULT) {\n if (!resultAnswer) return {};\n try {\n const parsed = JSON.parse(resultAnswer);\n return normalizeAnswerData(parsed);\n } catch {\n return {};\n }\n }\n if (currentAnswer) {\n try {\n const parsed = JSON.parse(currentAnswer);\n return normalizeAnswerData(parsed);\n } catch {\n return {};\n }\n }\n return {};\n};\n\n/**\n * Converts isCorrect boolean to status variant string.\n * Returns undefined for null/unanswered to keep neutral styling.\n */\nconst getAnswerStatus = (\n isCorrect: boolean | null\n): OptionStatus | undefined => {\n if (isCorrect === true) return OptionStatus.CORRECT;\n if (isCorrect === false) return OptionStatus.INCORRECT;\n return undefined;\n};\n\ninterface QuizVariantInterface {\n paddingBottom?: string;\n}\n\nconst QuizSubTitle = forwardRef<HTMLDivElement, { subTitle: string }>(\n ({ subTitle, ...props }, ref) => {\n return (\n <div className=\"px-4 pb-2 pt-6\" {...props} ref={ref}>\n <p className=\"font-bold text-lg text-text-950\">{subTitle}</p>\n </div>\n );\n }\n);\n\nconst QuizContainer = forwardRef<\n HTMLDivElement,\n { children: ReactNode; className?: string }\n>(({ children, className, ...props }, ref) => {\n return (\n <div\n ref={ref}\n className={cn(\n 'bg-background rounded-t-xl px-4 pt-4 pb-[80px] h-auto flex flex-col gap-4 mb-auto',\n className\n )}\n {...props}\n >\n {children}\n </div>\n );\n});\n\nconst QuizAlternative = ({ paddingBottom }: QuizVariantInterface) => {\n const {\n getCurrentQuestion,\n selectAnswer,\n getQuestionResultByQuestionId,\n getCurrentAnswer,\n variant,\n } = useQuizStore();\n const currentQuestion = getCurrentQuestion();\n const currentQuestionResult = getQuestionResultByQuestionId(\n currentQuestion?.id || ''\n );\n\n const currentAnswer = getCurrentAnswer();\n const alternatives = currentQuestion?.options?.map((option) => {\n let status: OptionStatus = OptionStatus.NEUTRAL;\n if (variant === QuizVariant.RESULT) {\n const isCorrectOption =\n currentQuestionResult?.options?.find((op) => op.id === option.id)\n ?.isCorrect || false;\n\n const isSelected = currentQuestionResult?.selectedOptions.some(\n (selectedOption) => selectedOption.optionId === option.id\n );\n\n // Only show correct/incorrect status if answer is not pending evaluation\n const shouldShowCorrectAnswers =\n currentQuestionResult?.answerStatus !==\n ANSWER_STATUS.PENDENTE_AVALIACAO &&\n currentQuestionResult?.answerStatus !== ANSWER_STATUS.NAO_RESPONDIDO;\n\n if (shouldShowCorrectAnswers) {\n if (isCorrectOption) {\n status = OptionStatus.CORRECT;\n } else if (isSelected && !isCorrectOption) {\n status = OptionStatus.INCORRECT;\n } else {\n status = OptionStatus.NEUTRAL;\n }\n } else {\n // When pending evaluation, show all options as neutral\n status = OptionStatus.NEUTRAL;\n }\n }\n\n return {\n label: option.option,\n value: option.id,\n status: status,\n };\n });\n\n if (!alternatives)\n return (\n <div>\n <p>Não há Alternativas</p>\n </div>\n );\n\n return (\n <>\n <QuizSubTitle subTitle=\"Alternativas\" />\n\n <QuizContainer className={cn('', paddingBottom)}>\n <div className=\"space-y-4\">\n <AlternativesList\n mode={variant === QuizVariant.DEFAULT ? 'interactive' : 'readonly'}\n key={`question-${currentQuestion?.id || '1'}`}\n name={`question-${currentQuestion?.id || '1'}`}\n layout=\"compact\"\n alternatives={alternatives}\n value={\n variant === QuizVariant.RESULT\n ? currentQuestionResult?.selectedOptions[0]?.optionId || ''\n : currentAnswer?.optionId || ''\n }\n selectedValue={\n variant === QuizVariant.RESULT\n ? currentQuestionResult?.selectedOptions[0]?.optionId || ''\n : currentAnswer?.optionId || ''\n }\n onValueChange={(value) => {\n if (currentQuestion) {\n selectAnswer(currentQuestion.id, value);\n }\n }}\n />\n </div>\n </QuizContainer>\n </>\n );\n};\n\nconst QuizMultipleChoice = ({ paddingBottom }: QuizVariantInterface) => {\n const {\n getCurrentQuestion,\n selectMultipleAnswer,\n getQuestionResultByQuestionId,\n variant,\n } = useQuizStore();\n const currentQuestion = getCurrentQuestion();\n // Derive the current question's answers from the stable `userAnswers` slice\n // instead of calling getAllCurrentAnswer(), which allocates a fresh array on\n // every render. That fresh identity propagated through the memos below and,\n // combined with the store→prop→local-state mirror in MultipleChoiceList,\n // caused a \"Maximum update depth exceeded\" loop on /questionario.\n const userAnswers = useQuizStore((state) => state.userAnswers);\n const allCurrentAnswers = useMemo(\n () =>\n currentQuestion\n ? userAnswers.filter(\n (answer) => answer.questionId === currentQuestion.id\n )\n : [],\n [userAnswers, currentQuestion]\n );\n const currentQuestionResult = getQuestionResultByQuestionId(\n currentQuestion?.id || ''\n );\n // Use ref to track previous values and prevent unnecessary updates\n const prevSelectedValuesRef = useRef<string[]>([]);\n const prevQuestionIdRef = useRef<string>('');\n\n // Memoize the answer IDs to prevent unnecessary re-renders\n // For MULTIPLA_ESCOLHA, we now have a single answer entry with selectedOptionIds array\n const allCurrentAnswerIds = useMemo(() => {\n if (allCurrentAnswers && allCurrentAnswers.length > 0) {\n // Prefer any row already using the new format\n const answerWithSelectedOptionIds = allCurrentAnswers.find((answer) =>\n Array.isArray(answer.selectedOptionIds)\n );\n if (answerWithSelectedOptionIds) {\n return answerWithSelectedOptionIds.selectedOptionIds ?? [];\n }\n // Fallback to old format (multiple entries with optionId)\n return allCurrentAnswers.map((answer) => answer.optionId);\n }\n return [];\n }, [allCurrentAnswers]);\n\n // Memoize the selected values to prevent infinite loops\n const selectedValues = useMemo(() => {\n return allCurrentAnswerIds?.filter((id): id is string => id !== null) || [];\n }, [allCurrentAnswerIds]);\n\n // Only update selectedValues if they actually changed or question changed\n const stableSelectedValues = useMemo(() => {\n // In result mode, always use selectedOptions from the question result\n if (variant === QuizVariant.RESULT) {\n return (\n currentQuestionResult?.selectedOptions?.map((op) => op.optionId) || []\n );\n }\n\n const currentQuestionId = currentQuestion?.id || '';\n const hasQuestionChanged = prevQuestionIdRef.current !== currentQuestionId;\n\n if (hasQuestionChanged) {\n prevQuestionIdRef.current = currentQuestionId;\n prevSelectedValuesRef.current = selectedValues;\n return selectedValues;\n }\n\n // Only update if values actually changed\n const hasValuesChanged =\n JSON.stringify(prevSelectedValuesRef.current) !==\n JSON.stringify(selectedValues);\n if (hasValuesChanged) {\n prevSelectedValuesRef.current = selectedValues;\n return selectedValues;\n }\n\n return prevSelectedValuesRef.current;\n }, [\n selectedValues,\n currentQuestion?.id,\n variant,\n currentQuestionResult?.selectedOptions,\n ]);\n\n // Memoize the callback to prevent unnecessary re-renders\n const handleSelectedValues = useCallback(\n (values: string[]) => {\n if (currentQuestion) {\n selectMultipleAnswer(currentQuestion.id, values);\n }\n },\n [currentQuestion, selectMultipleAnswer]\n );\n\n // Create a stable key to force re-mount when question changes\n const questionKey = useMemo(\n () => `question-${currentQuestion?.id || '1'}`,\n [currentQuestion?.id]\n );\n const choices = currentQuestion?.options?.map((option) => {\n let status: OptionStatus = OptionStatus.NEUTRAL;\n\n if (variant === QuizVariant.RESULT) {\n const isCorrectOption =\n currentQuestionResult?.options?.find((op) => op.id === option.id)\n ?.isCorrect || false;\n\n const isSelected = currentQuestionResult?.selectedOptions?.some(\n (op) => op.optionId === option.id\n );\n\n // Only show correct/incorrect status if answer is not pending evaluation\n const shouldShowCorrectAnswers =\n currentQuestionResult?.answerStatus !==\n ANSWER_STATUS.PENDENTE_AVALIACAO &&\n currentQuestionResult?.answerStatus !== ANSWER_STATUS.NAO_RESPONDIDO;\n\n if (shouldShowCorrectAnswers) {\n if (isCorrectOption) {\n status = OptionStatus.CORRECT;\n } else if (isSelected && !isCorrectOption) {\n status = OptionStatus.INCORRECT;\n } else {\n status = OptionStatus.NEUTRAL;\n }\n } else {\n // When pending evaluation, show all options as neutral\n status = OptionStatus.NEUTRAL;\n }\n }\n\n return {\n label: option.option,\n value: option.id,\n status: status,\n };\n });\n\n if (!choices)\n return (\n <div>\n <p>Não há Escolhas Multiplas</p>\n </div>\n );\n return (\n <>\n <QuizSubTitle subTitle=\"Alternativas\" />\n\n <QuizContainer className={cn('', paddingBottom)}>\n <div className=\"space-y-4\">\n <MultipleChoiceList\n choices={choices}\n key={questionKey}\n name={questionKey}\n selectedValues={stableSelectedValues}\n onHandleSelectedValues={handleSelectedValues}\n mode={variant === QuizVariant.DEFAULT ? 'interactive' : 'readonly'}\n />\n </div>\n </QuizContainer>\n </>\n );\n};\n\nconst QuizDissertative = ({ paddingBottom }: QuizVariantInterface) => {\n const {\n getCurrentQuestion,\n getCurrentAnswer,\n selectDissertativeAnswer,\n getQuestionResultByQuestionId,\n variant,\n getDissertativeCharLimit,\n } = useQuizStore();\n\n const currentQuestion = getCurrentQuestion();\n const currentQuestionResult = getQuestionResultByQuestionId(\n currentQuestion?.id || ''\n );\n\n const currentAnswer = getCurrentAnswer();\n const textareaRef = useRef<HTMLTextAreaElement>(null);\n const charLimit = getDissertativeCharLimit();\n\n const handleAnswerChange = (value: string) => {\n if (currentQuestion) {\n selectDissertativeAnswer(currentQuestion.id, value);\n }\n };\n\n // Auto-resize function\n const adjustTextareaHeight = useCallback(() => {\n if (textareaRef.current) {\n textareaRef.current.style.height = 'auto';\n const scrollHeight = textareaRef.current.scrollHeight;\n const minHeight = 120; // 120px minimum height\n const maxHeight = 400; // 400px maximum height\n const newHeight = Math.min(Math.max(scrollHeight, minHeight), maxHeight);\n textareaRef.current.style.height = `${newHeight}px`;\n }\n }, []);\n\n // Adjust height when currentAnswer changes\n useEffect(() => {\n adjustTextareaHeight();\n }, [currentAnswer, adjustTextareaHeight]);\n\n if (!currentQuestion) {\n return (\n <div className=\"space-y-4\">\n <p className=\"text-text-600 text-md\">Nenhuma questão disponível</p>\n </div>\n );\n }\n\n const localAnswer =\n (variant == 'result'\n ? currentQuestionResult?.answer\n : currentAnswer?.answer) || '';\n return (\n <>\n <div className=\"flex items-center justify-between gap-2 flex-wrap\">\n <QuizSubTitle subTitle=\"Resposta\" />\n {/* Quem corrigiu esta dissertativa. Fica aqui, ao lado da resposta, e\n não só dentro do modal de feedback: o aluno tem que saber que a\n correção veio da IA sem precisar abrir nada. Só aparece no\n resultado — durante a prova não há correção nenhuma. */}\n {variant === QuizVariant.RESULT && (\n <CorrectionSourceTag\n correctionSource={currentQuestionResult?.correctionSource}\n aiCorrectionStatus={currentQuestionResult?.aiCorrectionStatus}\n />\n )}\n </div>\n\n <QuizContainer className={cn(variant != 'result' && paddingBottom)}>\n <div className=\"space-y-4 max-h-[600px] overflow-y-auto\">\n {variant === QuizVariant.DEFAULT ? (\n <div className=\"space-y-4\">\n <TextArea\n ref={textareaRef}\n placeholder=\"Escreva sua resposta\"\n value={localAnswer}\n onChange={(e) => handleAnswerChange(e.target.value)}\n rows={4}\n className=\"min-h-[120px] max-h-[400px] resize-none overflow-y-auto\"\n maxLength={charLimit}\n showCharacterCount={!!charLimit}\n />\n </div>\n ) : (\n <div className=\"space-y-4\">\n <p className=\"text-text-600 text-md whitespace-pre-wrap\">\n {localAnswer || 'Nenhuma resposta fornecida'}\n </p>\n </div>\n )}\n </div>\n </QuizContainer>\n\n {/* The teacher's comment is rendered by `TeacherQuestionComment`, mounted\n once in `QuizContent` for every question type. This block used to\n duplicate it here, so an essay showed the same text twice. */}\n </>\n );\n};\n\nconst QuizTrueOrFalse = ({ paddingBottom }: QuizVariantInterface) => {\n const {\n variant,\n getCurrentQuestion,\n getCurrentAnswer,\n selectDissertativeAnswer,\n getQuestionResultByQuestionId,\n getUserAnswerByQuestionId,\n } = useQuizStore();\n\n const currentQuestion = getCurrentQuestion();\n const currentAnswer = getCurrentAnswer();\n const currentQuestionResult = getQuestionResultByQuestionId(\n currentQuestion?.id || ''\n );\n\n // Get the options - prefer persisted result options in result mode, fallback to live question\n const options = useMemo(() => {\n if (variant === QuizVariant.RESULT && currentQuestionResult?.options) {\n return currentQuestionResult.options;\n }\n return currentQuestion?.options || [];\n }, [variant, currentQuestionResult?.options, currentQuestion?.options]);\n\n // Parse stored answers from JSON string, handling both object and legacy array formats\n const parsedAnswers: Record<string, string> = useMemo(() => {\n return parseStoredAnswers(\n variant,\n currentQuestionResult?.answer,\n currentAnswer?.answer\n );\n }, [variant, currentQuestionResult?.answer, currentAnswer?.answer]);\n\n // Local state to track answers: { \"optionId\": \"V\" | \"F\" }\n const [localAnswers, setLocalAnswers] =\n useState<Record<string, string>>(parsedAnswers);\n\n // Sync local answers when question changes or parsed answers update\n useEffect(() => {\n setLocalAnswers(parsedAnswers);\n }, [parsedAnswers, currentQuestion?.id]);\n\n // Handle select change for an option\n const handleSelectChange = (optionId: string, value: string) => {\n if (!currentQuestion) return;\n\n // Get the LATEST answers directly from the store (not from closure)\n // This is critical to avoid stale closure issues when user selects options quickly\n const latestStoreAnswer = getUserAnswerByQuestionId(currentQuestion.id);\n let baseAnswers: Record<string, string> = {};\n if (latestStoreAnswer?.answer) {\n try {\n const parsed = JSON.parse(latestStoreAnswer.answer);\n baseAnswers = normalizeAnswerData(parsed);\n } catch {\n // Invalid JSON, start fresh\n }\n }\n\n // Merge: store answers + local state + new selection\n const mergedAnswers = {\n ...baseAnswers,\n ...localAnswers,\n [optionId]: value,\n };\n setLocalAnswers(mergedAnswers);\n\n // Save to store as JSON string\n selectDissertativeAnswer(currentQuestion.id, JSON.stringify(mergedAnswers));\n };\n\n // Get the current selection for an option\n const getOptionSelection = (optionId: string): string | undefined => {\n return localAnswers[optionId];\n };\n\n const getLetterByIndex = (index: number) => String.fromCodePoint(97 + index); // 97 = 'a' in ASCII\n\n const isDefaultVariant = variant === QuizVariant.DEFAULT;\n\n // Check if we should show correct/incorrect status\n const shouldShowStatus =\n currentQuestionResult?.answerStatus !== ANSWER_STATUS.PENDENTE_AVALIACAO &&\n currentQuestionResult?.answerStatus !== ANSWER_STATUS.NAO_RESPONDIDO;\n\n if (options.length === 0) {\n return (\n <QuizContainer className={cn('', paddingBottom)}>\n <Text size=\"sm\" className=\"text-text-500 italic\">\n Nenhuma opção disponível para esta questão\n </Text>\n </QuizContainer>\n );\n }\n\n return (\n <>\n <QuizSubTitle subTitle=\"Alternativas\" />\n\n <QuizContainer className={cn('', paddingBottom)}>\n <div className=\"flex flex-col gap-3.5\">\n {options.map((option, index) => {\n // Use helper to compute all option state\n const {\n hasAnswered,\n studentAnswer,\n correctAnswer,\n isStudentCorrect,\n variantCorrect,\n } = getTrueOrFalseOptionState(\n option.id,\n variant,\n currentQuestionResult,\n localAnswers\n );\n\n const contentWithLetter = prependLetterToHtml(\n getLetterByIndex(index),\n option.option\n );\n\n return (\n <section\n key={option.id || `option-${index}`}\n className=\"flex flex-col gap-2\"\n >\n <div\n className={cn(\n 'w-full grid grid-cols-[1fr_auto] items-start gap-4 p-2 rounded-md border',\n !isDefaultVariant && shouldShowStatus && hasAnswered\n ? getStatusStyles(variantCorrect)\n : 'border-transparent'\n )}\n >\n <HtmlMathRenderer\n content={contentWithLetter}\n className=\"text-text-900 text-sm\"\n />\n\n {isDefaultVariant ? (\n <Select\n size=\"medium\"\n value={getOptionSelection(option.id)}\n onValueChange={(value) =>\n handleSelectChange(option.id, value)\n }\n >\n <SelectTrigger className=\"w-[120px]\">\n <SelectValue placeholder=\"Selecione\" />\n </SelectTrigger>\n\n <SelectContent>\n <SelectItem value={TrueFalseEnum.VERDADEIRO}>\n Verdadeiro\n </SelectItem>\n <SelectItem value={TrueFalseEnum.FALSO}>\n Falso\n </SelectItem>\n </SelectContent>\n </Select>\n ) : (\n shouldShowStatus &&\n hasAnswered && <div>{getStatusBadge(variantCorrect)}</div>\n )}\n </div>\n\n {!isDefaultVariant && shouldShowStatus && (\n <span className=\"flex flex-row gap-2 items-center flex-wrap\">\n {hasAnswered ? (\n <>\n <Text size=\"2xs\" className=\"text-text-800\">\n Resposta selecionada: {studentAnswer}\n </Text>\n {!isStudentCorrect && (\n <Text size=\"2xs\" className=\"text-text-800\">\n | Resposta correta: {correctAnswer}\n </Text>\n )}\n </>\n ) : (\n <Text size=\"2xs\" className=\"text-text-800\">\n Não respondida | Resposta correta: {correctAnswer}\n </Text>\n )}\n </span>\n )}\n </section>\n );\n })}\n </div>\n </QuizContainer>\n </>\n );\n};\n\ninterface UserAnswer {\n option: string;\n dotOption: string | null;\n correctOption: string;\n isCorrect: boolean | null;\n}\n\nconst QuizConnectDots = ({ paddingBottom }: QuizVariantInterface) => {\n const {\n variant,\n getCurrentQuestion,\n getCurrentAnswer,\n selectDissertativeAnswer,\n getQuestionResultByQuestionId,\n getUserAnswerByQuestionId,\n } = useQuizStore();\n\n const currentQuestion = getCurrentQuestion();\n const currentQuestionResult = getQuestionResultByQuestionId(\n currentQuestion?.id || ''\n );\n const currentAnswer = getCurrentAnswer();\n\n // Extract options from the current question\n // Each option has: id, option (left column), correctValue (right column)\n const questionOptions = useMemo(() => {\n if (!currentQuestion?.options) return [];\n return currentQuestion.options;\n }, [currentQuestion?.options]);\n\n // Build dotsOptions from correctValue of each option (right column values)\n // Shuffle them so they don't appear in the same order as the left column\n const dotsOptions = useMemo(() => {\n const dots = questionOptions\n .map((opt) => ({ label: opt.correctValue || '' }))\n .filter((opt) => opt.label !== '');\n // Use question ID as seed for consistent shuffle\n return shuffleWithSeed(dots, currentQuestion?.id || '');\n }, [questionOptions, currentQuestion?.id]);\n\n // Build options for display (left column with correct answer mapping)\n const options = useMemo(() => {\n return questionOptions.map((opt) => ({\n id: opt.id,\n label: opt.option,\n correctOption: opt.correctValue || '',\n }));\n }, [questionOptions]);\n\n // Parse stored matching answers\n // In result mode, read from matchingAnswers array, fallback to answer JSON\n // In default mode, read from currentAnswer.answer JSON string\n const parsedAnswers: Record<string, string> = useMemo(() => {\n if (\n variant === QuizVariant.RESULT &&\n currentQuestionResult?.matchingAnswers\n ) {\n // Convert matchingAnswers array to Record<optionId, selectedValue>\n const answers: Record<string, string> = {};\n for (const match of currentQuestionResult.matchingAnswers) {\n answers[match.optionId] = match.selectedValue;\n }\n return answers;\n }\n // Fallback to answer JSON string (for older/partially migrated submissions)\n return parseStoredAnswers(\n variant,\n currentQuestionResult?.answer,\n currentAnswer?.answer\n );\n }, [\n variant,\n currentQuestionResult?.matchingAnswers,\n currentQuestionResult?.answer,\n currentAnswer?.answer,\n ]);\n\n const [userAnswers, setUserAnswers] = useState<UserAnswer[]>([]);\n\n // Initialize userAnswers from options and stored answers\n useEffect(() => {\n if (options.length > 0) {\n setUserAnswers(\n options.map((option) => {\n const storedValue = parsedAnswers[option.id] || null;\n return {\n option: option.label,\n dotOption: storedValue,\n correctOption: option.correctOption,\n isCorrect: storedValue\n ? storedValue === option.correctOption\n : null,\n };\n })\n );\n }\n }, [options, parsedAnswers]);\n\n const handleSelectDot = (optionIndex: number, dotValue: string) => {\n const option = options[optionIndex];\n if (!option || !currentQuestion) return;\n\n // Update local state\n setUserAnswers((prev) => {\n const next = [...prev];\n next[optionIndex] = {\n option: option.label,\n dotOption: dotValue,\n correctOption: option.correctOption,\n isCorrect: dotValue ? dotValue === option.correctOption : null,\n };\n return next;\n });\n\n // Get the LATEST answers directly from the store (not from closure)\n const latestStoreAnswer = getUserAnswerByQuestionId(currentQuestion.id);\n let baseAnswers: Record<string, string> = {};\n if (latestStoreAnswer?.answer) {\n try {\n const parsed = JSON.parse(latestStoreAnswer.answer);\n baseAnswers = normalizeAnswerData(parsed);\n } catch {\n // Invalid JSON, start fresh\n }\n }\n\n // Persist to shared store as JSON\n const newAnswers = {\n ...baseAnswers,\n ...parsedAnswers,\n [option.id]: dotValue,\n };\n selectDissertativeAnswer(currentQuestion.id, JSON.stringify(newAnswers));\n };\n\n const getLetterByIndex = (index: number) => String.fromCodePoint(97 + index); // 'a', 'b', 'c'...\n\n const isDefaultVariant = variant === QuizVariant.DEFAULT;\n const assignedDots = new Set(\n userAnswers.map((a) => a.dotOption).filter(Boolean)\n );\n\n if (options.length === 0) {\n return (\n <QuizContainer className={cn('', paddingBottom)}>\n <Text size=\"sm\" className=\"text-text-500 italic\">\n Nenhuma opção de relacionamento disponível\n </Text>\n </QuizContainer>\n );\n }\n\n return (\n <>\n <QuizSubTitle subTitle=\"Alternativas\" />\n\n <QuizContainer className={cn('', paddingBottom)}>\n <div className=\"flex flex-col gap-3.5\">\n {options.map((option, index) => {\n const answer = userAnswers[index] || {\n option: option.label,\n dotOption: null,\n correctOption: option.correctOption,\n isCorrect: null,\n };\n const variantCorrect = getAnswerStatus(answer.isCorrect);\n return (\n <section key={option.id} className=\"flex flex-col gap-2\">\n <div\n className={cn(\n 'grid grid-cols-[1fr_auto] items-center gap-4 p-2 rounded-md',\n !isDefaultVariant && variantCorrect\n ? getStatusStyles(variantCorrect)\n : ''\n )}\n >\n <HtmlMathRenderer\n content={prependLetterToHtml(\n getLetterByIndex(index),\n option.label\n )}\n className=\"text-text-900 text-sm\"\n />\n\n {isDefaultVariant ? (\n <Select\n size=\"medium\"\n value={answer.dotOption || undefined}\n onValueChange={(value) => handleSelectDot(index, value)}\n >\n <SelectTrigger\n className=\"w-[180px] overflow-hidden truncate\"\n title={\n answer.dotOption\n ? stripHtmlTags(answer.dotOption)\n : undefined\n }\n >\n <SelectValue placeholder=\"Selecione opção\" />\n </SelectTrigger>\n\n <SelectContent>\n {dotsOptions\n .filter(\n (dot) =>\n !assignedDots.has(dot.label) ||\n answer.dotOption === dot.label\n )\n .map((dot) => (\n <SelectItem\n key={dot.label}\n value={dot.label}\n title={stripHtmlTags(dot.label)}\n truncate\n >\n {stripHtmlTags(dot.label)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n ) : (\n <div>\n {answer.isCorrect === null\n ? null\n : getStatusBadge(variantCorrect)}\n </div>\n )}\n </div>\n\n {!isDefaultVariant && (\n <span className=\"flex flex-row gap-2 items-center\">\n <Text size=\"2xs\" className=\"text-text-800\">\n Resposta selecionada:{' '}\n {answer.dotOption\n ? stripHtmlTags(answer.dotOption)\n : 'Nenhuma'}\n </Text>\n {answer.isCorrect === false && (\n <Text size=\"2xs\" className=\"text-text-800\">\n Resposta correta: {stripHtmlTags(answer.correctOption)}\n </Text>\n )}\n </span>\n )}\n </section>\n );\n })}\n </div>\n </QuizContainer>\n </>\n );\n};\n\nconst QuizFill = ({ paddingBottom }: QuizVariantInterface) => {\n const {\n getCurrentQuestion,\n getQuestionResultByQuestionId,\n getCurrentAnswer,\n selectDissertativeAnswer,\n variant,\n getUserAnswerByQuestionId,\n } = useQuizStore();\n\n const currentQuestion = getCurrentQuestion();\n const currentQuestionResult = getQuestionResultByQuestionId(\n currentQuestion?.id || ''\n );\n const currentAnswer = getCurrentAnswer();\n\n const baseId = useId();\n\n // Get the additionalContent from the question (contains text with {optionId} placeholders)\n // In result mode, prefer the persisted snapshot to match parsedAnswers\n const additionalContent =\n variant === QuizVariant.RESULT\n ? currentQuestionResult?.additionalContent ||\n currentQuestion?.additionalContent ||\n ''\n : currentQuestion?.additionalContent || '';\n\n // Get the options from the question\n // In result mode, prefer the persisted snapshot to match parsedAnswers\n const questionOptions =\n variant === QuizVariant.RESULT\n ? currentQuestionResult?.options || currentQuestion?.options || []\n : currentQuestion?.options || [];\n\n // Shuffled options for dropdown display (consistent per question)\n const shuffledOptions = useMemo(() => {\n return shuffleWithSeed(questionOptions, currentQuestion?.id || '');\n }, [questionOptions, currentQuestion?.id]);\n\n // Parse current answers\n // In result mode, read from fillAnswers object, fallback to answer JSON\n // In default mode, read from currentAnswer.answer JSON string\n const parsedAnswers: Record<string, string> = useMemo(() => {\n if (variant === QuizVariant.RESULT && currentQuestionResult?.fillAnswers) {\n // fillAnswers is already a Record<placeholderId, selectedOptionId>\n return currentQuestionResult.fillAnswers;\n }\n // Fallback to answer JSON string (for older/partially migrated submissions)\n return parseStoredAnswers(\n variant,\n currentQuestionResult?.answer,\n currentAnswer?.answer\n );\n }, [\n variant,\n currentQuestionResult?.fillAnswers,\n currentQuestionResult?.answer,\n currentAnswer?.answer,\n ]);\n\n const [localAnswers, setLocalAnswers] =\n useState<Record<string, string>>(parsedAnswers);\n\n // Sync local answers when question changes\n useEffect(() => {\n setLocalAnswers(parsedAnswers);\n }, [parsedAnswers, currentQuestion?.id]);\n\n // Handle select change\n const handleSelectChange = (placeholderId: string, optionId: string) => {\n if (!currentQuestion) return;\n\n // Get the LATEST answers directly from the store (not from closure)\n const latestStoreAnswer = getUserAnswerByQuestionId(currentQuestion.id);\n let baseAnswers: Record<string, string> = {};\n if (latestStoreAnswer?.answer) {\n try {\n const parsed = JSON.parse(latestStoreAnswer.answer);\n baseAnswers = normalizeAnswerData(parsed);\n } catch {\n // Invalid JSON, start fresh\n }\n }\n\n const newAnswers = {\n ...baseAnswers,\n ...localAnswers,\n [placeholderId]: optionId,\n };\n setLocalAnswers(newAnswers);\n\n // Save to store as JSON string\n selectDissertativeAnswer(currentQuestion.id, JSON.stringify(newAnswers));\n };\n\n // Get option text by ID\n const getOptionTextById = (optionId: string): string => {\n const option = questionOptions.find((opt) => opt.id === optionId);\n return stripHtmlTags(option?.option || '');\n };\n\n // Check if an answer is correct (for result mode)\n const isAnswerCorrect = (placeholderId: string): boolean => {\n const selectedOptionId = localAnswers[placeholderId];\n // The placeholder ID is the correct option ID\n return selectedOptionId === placeholderId;\n };\n\n // Render the select for default mode\n const renderDefaultSelect = (placeholderId: string) => {\n const selectedOptionId = localAnswers[placeholderId];\n\n return (\n <span\n key={placeholderId}\n className=\"inline-block align-middle mx-1 my-2\"\n style={{ display: 'inline-block', verticalAlign: 'middle' }}\n >\n <Select\n value={selectedOptionId || undefined}\n onValueChange={(value) => handleSelectChange(placeholderId, value)}\n >\n <SelectTrigger\n className=\"w-auto min-w-[120px] max-w-[200px] h-7 px-2 bg-background border-gray-300 overflow-hidden truncate\"\n title={stripHtmlTags(\n shuffledOptions.find((o) => o.id === selectedOptionId)?.option ||\n ''\n )}\n >\n <SelectValue placeholder=\"Selecione opção\" />\n </SelectTrigger>\n <SelectContent>\n {shuffledOptions.map((option) => (\n <SelectItem\n key={option.id}\n value={option.id}\n title={stripHtmlTags(option.option)}\n truncate\n >\n {stripHtmlTags(option.option)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </span>\n );\n };\n\n // Render the result badge\n const renderResultBadge = (placeholderId: string) => {\n const selectedOptionId = localAnswers[placeholderId];\n const selectedOptionText = getOptionTextById(selectedOptionId);\n const isCorrect = isAnswerCorrect(placeholderId);\n\n if (!selectedOptionId) {\n return (\n <span\n key={placeholderId}\n className=\"inline-block align-middle mx-1 my-2\"\n style={{ display: 'inline-block', verticalAlign: 'middle' }}\n >\n <Badge\n variant=\"solid\"\n action=\"error\"\n iconRight={<XCircleIcon />}\n size=\"large\"\n className=\"py-1 px-2\"\n >\n Não respondido\n </Badge>\n </span>\n );\n }\n\n return (\n <span\n key={placeholderId}\n className=\"inline-block align-middle mx-1 my-2\"\n style={{ display: 'inline-block', verticalAlign: 'middle' }}\n >\n <Badge\n variant=\"solid\"\n action={isCorrect ? 'success' : 'error'}\n iconRight={isCorrect ? <CheckCircleIcon /> : <XCircleIcon />}\n size=\"large\"\n className=\"py-1 px-2\"\n >\n <span className=\"text-text-900\">{selectedOptionText}</span>\n </Badge>\n </span>\n );\n };\n\n // Render the correct answer for resolution\n const renderResolutionAnswer = (placeholderId: string) => {\n // The placeholderId IS the correct option ID\n const correctOptionText = getOptionTextById(placeholderId);\n\n return (\n <span className=\"inline mx-1 text-success-600 font-semibold border-b-2 border-success-600\">\n {correctOptionText}\n </span>\n );\n };\n\n // Parse text and render with selects\n const renderTextWithSelects = (text: string, isResolution?: boolean) => {\n const elements: Array<{ element: string | ReactNode; id: string }> = [];\n let lastIndex = 0;\n const nextId = () => elements.length;\n\n // Match {uuid} placeholders (UUID format or any alphanumeric with hyphens)\n const regex = /\\{([a-zA-Z0-9-]+)\\}/g;\n let match;\n\n while ((match = regex.exec(text)) !== null) {\n const [fullMatch, placeholderId] = match;\n const startIndex = match.index;\n\n // Add text before the placeholder\n if (startIndex > lastIndex) {\n elements.push({\n element: text.slice(lastIndex, startIndex),\n id: `${baseId}-text-${nextId()}`,\n });\n }\n\n // Add the appropriate element based on variant and mode\n if (isResolution) {\n elements.push({\n element: renderResolutionAnswer(placeholderId),\n id: `${baseId}-resolution-${nextId()}`,\n });\n } else if (variant === QuizVariant.DEFAULT) {\n elements.push({\n element: renderDefaultSelect(placeholderId),\n id: `${baseId}-select-${nextId()}`,\n });\n } else {\n elements.push({\n element: renderResultBadge(placeholderId),\n id: `${baseId}-result-${nextId()}`,\n });\n }\n\n lastIndex = match.index + fullMatch.length;\n }\n\n // Add remaining text\n if (lastIndex < text.length) {\n elements.push({\n element: text.slice(lastIndex),\n id: `${baseId}-text-${nextId()}`,\n });\n }\n\n return elements;\n };\n\n // Render HTML content with selects replacing placeholders\n const renderHtmlWithSelects = (\n htmlContent: string,\n isResolution?: boolean\n ) => {\n // For now, we'll strip HTML and render plain text with selects\n // This preserves the placeholder functionality while handling HTML content\n const textContent = stripHtmlTags(htmlContent);\n return renderTextWithSelects(textContent, isResolution);\n };\n\n if (!currentQuestion || !additionalContent) {\n return (\n <>\n <QuizSubTitle subTitle=\"Preenchimento\" />\n <QuizContainer className=\"h-auto pb-0\">\n <div className=\"space-y-6 px-4 h-auto\">\n <Text\n size=\"md\"\n color=\"text-text-600\"\n weight=\"normal\"\n className={cn(paddingBottom)}\n >\n Nenhum conteúdo disponível para esta questão.\n </Text>\n </div>\n </QuizContainer>\n </>\n );\n }\n\n return (\n <>\n <QuizSubTitle subTitle=\"Preencha as lacunas\" />\n\n <QuizContainer\n className={cn('', variant !== QuizVariant.RESULT && paddingBottom)}\n >\n <div className=\"px-4\">\n <Text\n as=\"div\"\n size=\"lg\"\n color=\"text-text-900\"\n weight=\"normal\"\n className=\"leading-[2.5] *:inline\"\n >\n {renderHtmlWithSelects(additionalContent).map((element) => (\n <Fragment key={element.id}>{element.element}</Fragment>\n ))}\n </Text>\n </div>\n </QuizContainer>\n\n {variant === QuizVariant.RESULT && (\n <>\n <QuizSubTitle subTitle=\"Resposta correta\" />\n\n <QuizContainer className={cn('', paddingBottom)}>\n <div className=\"px-4\">\n <Text\n as=\"div\"\n size=\"lg\"\n color=\"text-text-900\"\n weight=\"normal\"\n className=\"leading-[2.5] *:inline\"\n >\n {renderHtmlWithSelects(additionalContent, true).map(\n (element) => (\n <Fragment key={element.id}>{element.element}</Fragment>\n )\n )}\n </Text>\n </div>\n </QuizContainer>\n </>\n )}\n </>\n );\n};\n\nconst QuizImageQuestion = ({ paddingBottom }: QuizVariantInterface) => {\n const {\n variant,\n getCurrentQuestion,\n getCurrentAnswer,\n selectDissertativeAnswer,\n getQuestionResultByQuestionId,\n } = useQuizStore();\n\n const currentQuestion = getCurrentQuestion();\n const currentAnswer = getCurrentAnswer();\n const currentQuestionResult = getQuestionResultByQuestionId(\n currentQuestion?.id || ''\n );\n\n // Get image URL from additionalContent\n const imageUrl = currentQuestion?.additionalContent || '';\n\n // Parse correct coordinates from first option (stored as JSON: {\"x\": number, \"y\": number})\n const correctPositionRelative = useMemo(() => {\n if (!currentQuestion?.options || currentQuestion.options.length === 0) {\n return { x: 0.5, y: 0.5 }; // Default center\n }\n try {\n const coords = JSON.parse(currentQuestion.options[0].option);\n if (typeof coords.x === 'number' && typeof coords.y === 'number') {\n // Coordinates are stored as percentages (0-100), convert to relative (0-1)\n return { x: coords.x / 100, y: coords.y / 100 };\n }\n } catch {\n // Fall back to default\n }\n return { x: 0.5, y: 0.5 };\n }, [currentQuestion?.options]);\n\n // Calculate correctRadiusRelative automatically based on the circle dimensions\n const calculateCorrectRadiusRelative = (): number => {\n // The correct answer circle has width: 15% and height: 30%\n // We'll use the average of these as the radius\n const circleWidthRelative = 0.15; // 15%\n const circleHeightRelative = 0.3; // 30%\n\n // Calculate the average radius (half of the average of width and height)\n const averageRadius = (circleWidthRelative + circleHeightRelative) / 4;\n\n // Add a small tolerance for better user experience\n const tolerance = 0.02; // 2% tolerance\n\n return averageRadius + tolerance;\n };\n\n const correctRadiusRelative = calculateCorrectRadiusRelative();\n\n // Parse user's answer from stored JSON or result\n const storedUserAnswer = useMemo(() => {\n if (variant === QuizVariant.RESULT && currentQuestionResult?.answer) {\n try {\n const coords = JSON.parse(currentQuestionResult.answer);\n if (typeof coords.x === 'number' && typeof coords.y === 'number') {\n return { x: coords.x / 100, y: coords.y / 100 };\n }\n } catch {\n // Fall back to null\n }\n } else if (currentAnswer?.answer) {\n try {\n const coords = JSON.parse(currentAnswer.answer);\n if (typeof coords.x === 'number' && typeof coords.y === 'number') {\n return { x: coords.x / 100, y: coords.y / 100 };\n }\n } catch {\n // Fall back to null\n }\n }\n return null;\n }, [variant, currentQuestionResult?.answer, currentAnswer?.answer]);\n\n const [clickPositionRelative, setClickPositionRelative] = useState<{\n x: number;\n y: number;\n } | null>(storedUserAnswer);\n\n // Sync with stored answer when it changes\n useEffect(() => {\n setClickPositionRelative(storedUserAnswer);\n }, [storedUserAnswer]);\n\n // Helper function to safely convert click coordinates to relative coordinates\n const convertToRelativeCoordinates = (\n x: number,\n y: number,\n rect: DOMRect\n ): { x: number; y: number } => {\n // Guard against division by zero or extremely small dimensions\n const safeWidth = Math.max(rect.width, 0.001);\n const safeHeight = Math.max(rect.height, 0.001);\n\n // Convert to relative coordinates and clamp to [0, 1] range\n const xRelative = Math.max(0, Math.min(1, x / safeWidth));\n const yRelative = Math.max(0, Math.min(1, y / safeHeight));\n\n return { x: xRelative, y: yRelative };\n };\n\n const handleImageClick = (event: MouseEvent<HTMLButtonElement>) => {\n if (variant === QuizVariant.RESULT) return;\n\n const rect = event.currentTarget.getBoundingClientRect();\n const x = event.clientX - rect.left;\n const y = event.clientY - rect.top;\n\n // Use helper function for safe conversion\n const positionRelative = convertToRelativeCoordinates(x, y, rect);\n setClickPositionRelative(positionRelative);\n\n // Save the answer to the store (as percentages 0-100)\n if (currentQuestion) {\n const answerJson = JSON.stringify({\n x: Math.round(positionRelative.x * 100),\n y: Math.round(positionRelative.y * 100),\n });\n selectDissertativeAnswer(currentQuestion.id, answerJson);\n }\n };\n\n const handleKeyboardActivate = () => {\n if (variant === QuizVariant.RESULT) return;\n // Choose a deterministic position for keyboard activation; center is a reasonable default\n const centerPosition = { x: 0.5, y: 0.5 };\n setClickPositionRelative(centerPosition);\n\n // Save the answer to the store (as percentages 0-100)\n if (currentQuestion) {\n const answerJson = JSON.stringify({\n x: Math.round(centerPosition.x * 100),\n y: Math.round(centerPosition.y * 100),\n });\n selectDissertativeAnswer(currentQuestion.id, answerJson);\n }\n };\n\n const isCorrect = () => {\n if (!clickPositionRelative) return false;\n\n const distance = Math.sqrt(\n Math.pow(clickPositionRelative.x - correctPositionRelative.x, 2) +\n Math.pow(clickPositionRelative.y - correctPositionRelative.y, 2)\n );\n\n return distance <= correctRadiusRelative;\n };\n\n const getUserCircleColorClasses = () => {\n if (variant === QuizVariant.DEFAULT) {\n return 'bg-indicator-primary/70 border-[#F8CC2E]';\n }\n\n if (variant === QuizVariant.RESULT) {\n return isCorrect()\n ? 'bg-success-600/70 border-white' // Green for correct answer\n : 'bg-indicator-error/70 border-white'; // Red for incorrect answer\n }\n\n return 'bg-success-600/70 border-white';\n };\n\n // In review the question is read, not answered: the shared view draws the\n // image, the answer area and the student's click with the very tolerance the\n // backend graded with. This screen used to draw its own circle with a radius\n // of its own, so the drawing could disagree with the score.\n if (variant === QuizVariant.RESULT) {\n return (\n <QuizContainer className={cn('', paddingBottom)}>\n <div data-testid=\"quiz-image-container\" className=\"p-3\">\n <ImageAnswerView\n imageUrl={imageUrl}\n correctPoint={parseImageCorrectPoint(currentQuestion?.options)}\n studentPoint={parseImageStudentPoint(currentQuestionResult)}\n toleranceRadius={\n currentQuestionResult?.imageTolerance ?? DEFAULT_IMAGE_TOLERANCE\n }\n />\n </div>\n </QuizContainer>\n );\n }\n\n return (\n <>\n <QuizSubTitle subTitle=\"Clique na área correta\" />\n\n <QuizContainer className={cn('', paddingBottom)}>\n <div\n data-testid=\"quiz-image-container\"\n className=\"space-y-6 p-3 relative inline-block\"\n >\n <button\n data-testid=\"quiz-image-button\"\n type=\"button\"\n className=\"relative cursor-pointer w-full h-full border-0 bg-transparent p-0\"\n onClick={handleImageClick}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n handleKeyboardActivate();\n }\n }}\n aria-label=\"Área da imagem interativa\"\n >\n <img\n data-testid=\"quiz-image\"\n src={imageUrl || ImageQuestion}\n alt=\"Question\"\n className=\"w-full h-auto rounded-md\"\n />\n\n {/* User's answer circle */}\n {clickPositionRelative && (\n <div\n data-testid=\"quiz-user-circle\"\n className={`absolute rounded-full border-4 pointer-events-none ${getUserCircleColorClasses()}`}\n style={{\n minWidth: '30px',\n maxWidth: '52px',\n width: '5%',\n aspectRatio: '1 / 1',\n left: `calc(${clickPositionRelative.x * 100}% - 2.5%)`,\n top: `calc(${clickPositionRelative.y * 100}% - 2.5%)`,\n }}\n />\n )}\n </button>\n </div>\n </QuizContainer>\n </>\n );\n};\n\nexport {\n QuizSubTitle,\n QuizContainer,\n QuizMultipleChoice,\n QuizDissertative,\n QuizTrueOrFalse,\n QuizConnectDots,\n QuizFill,\n QuizImageQuestion,\n QuizAlternative,\n};\n","import type {\n Question,\n QuestionResult,\n} from '../../components/Quiz/useQuizStore';\nimport type { ImagePoint } from '../../components/shared/ImageAnswerView';\n\n/**\n * Fallback radius, in image percentage points, used when the payload does not\n * carry the backend's tolerance.\n *\n * The backend is the authority — it grades with its own value and now sends it\n * as `imageTolerance`. This only covers older payloads, and matches the value\n * the backend has always used.\n */\nexport const DEFAULT_IMAGE_TOLERANCE = 10;\n\n/**\n * Read the answer key point of an IMAGEM question.\n *\n * The key lives in the question's single option, whose text is the JSON\n * `{\"x\": number, \"y\": number}` in percentage points of the image.\n *\n * @param options - The question's options\n * @returns The correct point, or null when there is no usable key\n *\n * @example\n * ```typescript\n * parseImageCorrectPoint([{ id: 'o1', option: '{\"x\":50,\"y\":30}' }]); // { x: 50, y: 30 }\n * ```\n */\nexport const parseImageCorrectPoint = (\n options: Question['options'] | undefined\n): ImagePoint | null => {\n const first = options?.[0];\n if (!first?.option) {\n return null;\n }\n\n try {\n const parsed = JSON.parse(first.option);\n if (typeof parsed?.x === 'number' && typeof parsed?.y === 'number') {\n return { x: parsed.x, y: parsed.y };\n }\n } catch {\n return null;\n }\n\n return null;\n};\n\n/**\n * Read where the student clicked on an IMAGEM question.\n *\n * Prefers the structured `imageAnswer` the backend sends. Falls back to parsing\n * `answer`, which older payloads and the draft flow still use, in either the\n * `{x, y}` or `{coordinateX, coordinateY}` shape.\n *\n * @param result - The student's answer for the question\n * @returns The clicked point in percentage points (0-100), or null\n *\n * @example\n * ```typescript\n * parseImageStudentPoint({ imageAnswer: { coordinateX: 52, coordinateY: 28 } }); // { x: 52, y: 28 }\n * ```\n */\nexport const parseImageStudentPoint = (\n result: QuestionResult['answers'][number] | null | undefined\n): ImagePoint | null => {\n if (!result) {\n return null;\n }\n\n if (result.imageAnswer) {\n const { coordinateX, coordinateY } = result.imageAnswer;\n if (typeof coordinateX === 'number' && typeof coordinateY === 'number') {\n return { x: coordinateX, y: coordinateY };\n }\n }\n\n if (!result.answer) {\n return null;\n }\n\n try {\n const parsed = JSON.parse(result.answer);\n const x =\n typeof parsed?.coordinateX === 'number' ? parsed.coordinateX : parsed?.x;\n const y =\n typeof parsed?.coordinateY === 'number' ? parsed.coordinateY : parsed?.y;\n if (typeof x === 'number' && typeof y === 'number') {\n return { x, y };\n }\n } catch {\n return null;\n }\n\n return null;\n};\n"]}
1
+ {"version":3,"sources":["/home/runner/work/analytica-frontend-lib/analytica-frontend-lib/dist/chunk-IMDS5NQL.js","../src/components/shared/ImageAnswerView.tsx","../src/components/Quiz/QuizContent.tsx","../src/utils/image/imageAnswer.utils.ts"],"names":["jsx","Fragment","jsxs"],"mappings":"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","file":"/home/runner/work/analytica-frontend-lib/analytica-frontend-lib/dist/chunk-IMDS5NQL.js","sourcesContent":[null,"import Text from '../Text/Text';\nimport { cn } from '../../utils/utils';\n\n/** A point on the image, in percentage points of its width/height (0-100). */\nexport interface ImagePoint {\n x: number;\n y: number;\n}\n\nexport interface ImageAnswerViewProps {\n /** URL of the image the student had to click on */\n readonly imageUrl: string;\n /** The answer key point. Null when the question carries no key. */\n readonly correctPoint: ImagePoint | null;\n /** Where the student clicked. Null when the question was left blank. */\n readonly studentPoint: ImagePoint | null;\n /**\n * Radius within which a click counts as correct, in the same percentage\n * space. Comes from the backend so the drawn circle matches the grade.\n */\n readonly toleranceRadius: number;\n /** Whether the answer key may be revealed (false while pending) */\n readonly showCorrectness?: boolean;\n}\n\n/** Describe a point for screen readers. */\nconst describePoint = (point: ImagePoint): string =>\n `${Math.round(point.x)}% da esquerda, ${Math.round(point.y)}% do topo`;\n\n/** Euclidean distance between two points in the 0-100 space. */\nconst distanceBetween = (a: ImagePoint, b: ImagePoint): number =>\n Math.hypot(a.x - b.x, a.y - b.y);\n\n/**\n * Read-only view of an image question: the image, the answer key area and where\n * the student clicked.\n *\n * Shared by the activity correction modal, the simulation detail modal and the\n * student's result screen, which used to carry three divergent copies of this —\n * one of them drawing a mock image with a hardcoded answer area, and each one\n * deciding correctness with a different radius of its own. Here there is a\n * single rule, and it is the backend's: a click is correct when its distance to\n * the correct point is at most `toleranceRadius`.\n *\n * The target circle is drawn with the diameter of that tolerance, so what the\n * reader sees is what decided the grade.\n *\n * @param props - Component props\n * @returns JSX element\n *\n * @example\n * ```tsx\n * <ImageAnswerView\n * imageUrl={question.additionalContent ?? ''}\n * correctPoint={{ x: 50, y: 30 }}\n * studentPoint={{ x: 52, y: 28 }}\n * toleranceRadius={10}\n * />\n * ```\n */\nexport const ImageAnswerView = ({\n imageUrl,\n correctPoint,\n studentPoint,\n toleranceRadius,\n showCorrectness = true,\n}: ImageAnswerViewProps) => {\n const canShowCorrectness = showCorrectness && correctPoint !== null;\n const isCorrect =\n correctPoint !== null &&\n studentPoint !== null &&\n distanceBetween(studentPoint, correctPoint) <= toleranceRadius;\n\n /** Full spoken summary of the question, the only non-visual access to it. */\n const spatialSummary = (): string => {\n if (!correctPoint) {\n return studentPoint\n ? `Resposta do aluno em ${describePoint(studentPoint)}.`\n : 'Nenhuma resposta do aluno fornecida.';\n }\n\n const correctDescription = describePoint(correctPoint);\n if (!studentPoint) {\n return `Área correta localizada em ${correctDescription}. Nenhuma resposta do aluno fornecida.`;\n }\n\n const deltaX = studentPoint.x - correctPoint.x;\n const deltaY = studentPoint.y - correctPoint.y;\n let direction: string;\n if (Math.abs(deltaX) > Math.abs(deltaY)) {\n direction = deltaX > 0 ? 'à direita' : 'à esquerda';\n } else {\n direction = deltaY > 0 ? 'abaixo' : 'acima';\n }\n\n const distance = Math.round(distanceBetween(studentPoint, correctPoint));\n const verdict = isCorrect\n ? 'A resposta está dentro da área de tolerância e é considerada correta.'\n : 'A resposta está fora da área de tolerância e é considerada incorreta.';\n\n return `Área correta localizada em ${correctDescription}. Resposta do aluno em ${describePoint(studentPoint)}. A resposta do aluno está ${distance}% de distância ${direction} da área correta. ${verdict}`;\n };\n\n return (\n <div className=\"pt-2 space-y-4\">\n <div className=\"flex items-center gap-4 text-xs\">\n {canShowCorrectness && (\n <div className=\"flex items-center gap-2\">\n <div className=\"w-3 h-3 rounded-full bg-indicator-primary/70 border border-[#F8CC2E]\" />\n <Text size=\"sm\" weight=\"normal\" color=\"text-text-600\">\n Área correta\n </Text>\n </div>\n )}\n {canShowCorrectness && studentPoint && (\n <div className=\"flex items-center gap-2\">\n <div\n className={cn(\n 'w-3 h-3 rounded-full border border-white',\n isCorrect ? 'bg-success-600/70' : 'bg-indicator-error/70'\n )}\n />\n <Text size=\"sm\" weight=\"normal\" color=\"text-text-600\">\n {isCorrect ? 'Resposta correta' : 'Resposta incorreta'}\n </Text>\n </div>\n )}\n </div>\n\n <div className=\"relative w-full\">\n <div className=\"sr-only\">{spatialSummary()}</div>\n\n {imageUrl ? (\n <img\n src={imageUrl}\n alt={\n correctPoint\n ? `Questão de imagem com área correta localizada em ${describePoint(correctPoint)}`\n : 'Questão de imagem'\n }\n className=\"w-full h-auto rounded-md\"\n />\n ) : (\n <div className=\"rounded-md border border-border-100 bg-background-50 p-6 text-center\">\n <Text size=\"sm\" className=\"text-text-600\">\n Imagem da questão indisponível\n </Text>\n </div>\n )}\n\n {canShowCorrectness && correctPoint && (\n <div\n aria-hidden=\"true\"\n data-testid=\"image-correct-area\"\n className=\"absolute rounded-full bg-indicator-primary/70 border-4 border-[#F8CC2E] pointer-events-none -translate-x-1/2 -translate-y-1/2\"\n style={{\n // Diameter = twice the tolerance, so the circle IS the pass mark.\n width: `${toleranceRadius * 2}%`,\n aspectRatio: '1 / 1',\n left: `${correctPoint.x}%`,\n top: `${correctPoint.y}%`,\n }}\n />\n )}\n\n {studentPoint && (\n <div\n aria-hidden=\"true\"\n data-testid=\"image-student-point\"\n className={cn(\n 'absolute rounded-full border-4 pointer-events-none -translate-x-1/2 -translate-y-1/2',\n canShowCorrectness &&\n isCorrect &&\n 'bg-success-600/70 border-white',\n canShowCorrectness &&\n !isCorrect &&\n 'bg-indicator-error/70 border-white',\n !canShowCorrectness && 'bg-indicator-primary/70 border-[#F8CC2E]'\n )}\n style={{\n minWidth: '20px',\n maxWidth: '40px',\n width: '4%',\n aspectRatio: '1 / 1',\n left: `${studentPoint.x}%`,\n top: `${studentPoint.y}%`,\n }}\n />\n )}\n </div>\n </div>\n );\n};\n\nexport default ImageAnswerView;\n","import {\n Fragment,\n forwardRef,\n MouseEvent,\n ReactNode,\n useCallback,\n useEffect,\n useId,\n useMemo,\n useRef,\n useState,\n} from 'react';\nimport { ANSWER_STATUS, useQuizStore } from './useQuizStore';\nimport { QuizVariant } from './Quiz.types';\nimport CorrectionSourceTag from './CorrectionSourceTag';\nimport { TrueFalseEnum } from '../../enums/Quiz';\nimport {\n prependLetterToHtml,\n getTrueOrFalseOptionState,\n shuffleWithSeed,\n} from './Quiz.utils';\nimport { cn } from '../../utils/utils';\nimport { stripHtmlTags } from '../../utils/stringUtils';\nimport Select, {\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '../Select/Select';\nimport TextArea from '../TextArea/TextArea';\nimport { AlternativesList } from '../Alternative/Alternative';\nimport { OptionStatus } from '../../enums/Options';\nimport { MultipleChoiceList } from '../MultipleChoice/MultipleChoice';\nimport Badge from '../Badge/Badge';\nimport { CheckCircleIcon } from '@phosphor-icons/react/dist/csr/CheckCircle';\nimport { XCircleIcon } from '@phosphor-icons/react/dist/csr/XCircle';\nimport ImageQuestion from '../../assets/img/mock-image-question.png';\nimport { ImageAnswerView } from '../shared/ImageAnswerView';\nimport {\n DEFAULT_IMAGE_TOLERANCE,\n parseImageCorrectPoint,\n parseImageStudentPoint,\n} from '../../utils/image/imageAnswer.utils';\nimport Text from '../Text/Text';\nimport { HtmlMathRenderer } from '../HtmlMathRenderer';\nexport const getStatusBadge = (status?: OptionStatus) => {\n switch (status) {\n case OptionStatus.CORRECT:\n return (\n <Badge variant=\"solid\" action=\"success\" iconLeft={<CheckCircleIcon />}>\n Resposta correta\n </Badge>\n );\n case OptionStatus.INCORRECT:\n return (\n <Badge variant=\"solid\" action=\"error\" iconLeft={<XCircleIcon />}>\n Resposta incorreta\n </Badge>\n );\n default:\n return null;\n }\n};\n\nexport const getStatusStyles = (variantCorrect?: OptionStatus) => {\n switch (variantCorrect) {\n case OptionStatus.CORRECT:\n return 'bg-success-background border-success-300';\n case OptionStatus.INCORRECT:\n return 'bg-error-background border-error-300';\n default:\n return '';\n }\n};\n\n/**\n * Normalizes parsed answer data to Record<string, string> format.\n * Handles legacy array format [{ optionId, selectedValue }] by converting to { [optionId]: selectedValue }.\n */\nconst normalizeAnswerData = (parsed: unknown): Record<string, string> => {\n // Handle legacy array format: [{ optionId: string, selectedValue: string }]\n if (Array.isArray(parsed)) {\n const result: Record<string, string> = {};\n for (const item of parsed) {\n if (\n item &&\n typeof item === 'object' &&\n 'optionId' in item &&\n 'selectedValue' in item\n ) {\n result[item.optionId as string] = item.selectedValue as string;\n }\n }\n return result;\n }\n\n // Handle expected object format: { [optionId]: selectedValue }\n if (parsed && typeof parsed === 'object') {\n return parsed as Record<string, string>;\n }\n\n return {};\n};\n\n/**\n * Parses JSON answers from stored answer string.\n * In result mode, uses persisted results. Otherwise, uses current draft answers.\n * Normalizes legacy array format to expected Record<string, string> map.\n */\nconst parseStoredAnswers = (\n variant: QuizVariant,\n resultAnswer: string | null | undefined,\n currentAnswer: string | null | undefined\n): Record<string, string> => {\n if (variant === QuizVariant.RESULT) {\n if (!resultAnswer) return {};\n try {\n const parsed = JSON.parse(resultAnswer);\n return normalizeAnswerData(parsed);\n } catch {\n return {};\n }\n }\n if (currentAnswer) {\n try {\n const parsed = JSON.parse(currentAnswer);\n return normalizeAnswerData(parsed);\n } catch {\n return {};\n }\n }\n return {};\n};\n\n/**\n * Converts isCorrect boolean to status variant string.\n * Returns undefined for null/unanswered to keep neutral styling.\n */\nconst getAnswerStatus = (\n isCorrect: boolean | null\n): OptionStatus | undefined => {\n if (isCorrect === true) return OptionStatus.CORRECT;\n if (isCorrect === false) return OptionStatus.INCORRECT;\n return undefined;\n};\n\ninterface QuizVariantInterface {\n paddingBottom?: string;\n}\n\nconst QuizSubTitle = forwardRef<HTMLDivElement, { subTitle: string }>(\n ({ subTitle, ...props }, ref) => {\n return (\n <div className=\"px-4 pb-2 pt-6\" {...props} ref={ref}>\n <p className=\"font-bold text-lg text-text-950\">{subTitle}</p>\n </div>\n );\n }\n);\n\nconst QuizContainer = forwardRef<\n HTMLDivElement,\n { children: ReactNode; className?: string }\n>(({ children, className, ...props }, ref) => {\n return (\n <div\n ref={ref}\n className={cn(\n 'bg-background rounded-t-xl px-4 pt-4 pb-[80px] h-auto flex flex-col gap-4 mb-auto',\n className\n )}\n {...props}\n >\n {children}\n </div>\n );\n});\n\nconst QuizAlternative = ({ paddingBottom }: QuizVariantInterface) => {\n const {\n getCurrentQuestion,\n selectAnswer,\n getQuestionResultByQuestionId,\n getCurrentAnswer,\n variant,\n } = useQuizStore();\n const currentQuestion = getCurrentQuestion();\n const currentQuestionResult = getQuestionResultByQuestionId(\n currentQuestion?.id || ''\n );\n\n const currentAnswer = getCurrentAnswer();\n const alternatives = currentQuestion?.options?.map((option) => {\n let status: OptionStatus = OptionStatus.NEUTRAL;\n if (variant === QuizVariant.RESULT) {\n const isCorrectOption =\n currentQuestionResult?.options?.find((op) => op.id === option.id)\n ?.isCorrect || false;\n\n const isSelected = currentQuestionResult?.selectedOptions.some(\n (selectedOption) => selectedOption.optionId === option.id\n );\n\n // Only show correct/incorrect status if answer is not pending evaluation\n const shouldShowCorrectAnswers =\n currentQuestionResult?.answerStatus !==\n ANSWER_STATUS.PENDENTE_AVALIACAO &&\n currentQuestionResult?.answerStatus !== ANSWER_STATUS.NAO_RESPONDIDO;\n\n if (shouldShowCorrectAnswers) {\n if (isCorrectOption) {\n status = OptionStatus.CORRECT;\n } else if (isSelected && !isCorrectOption) {\n status = OptionStatus.INCORRECT;\n } else {\n status = OptionStatus.NEUTRAL;\n }\n } else {\n // When pending evaluation, show all options as neutral\n status = OptionStatus.NEUTRAL;\n }\n }\n\n return {\n label: option.option,\n value: option.id,\n status: status,\n };\n });\n\n if (!alternatives)\n return (\n <div>\n <p>Não há Alternativas</p>\n </div>\n );\n\n return (\n <>\n <QuizSubTitle subTitle=\"Alternativas\" />\n\n <QuizContainer className={cn('', paddingBottom)}>\n <div className=\"space-y-4\">\n <AlternativesList\n mode={variant === QuizVariant.DEFAULT ? 'interactive' : 'readonly'}\n key={`question-${currentQuestion?.id || '1'}`}\n name={`question-${currentQuestion?.id || '1'}`}\n layout=\"compact\"\n alternatives={alternatives}\n value={\n variant === QuizVariant.RESULT\n ? currentQuestionResult?.selectedOptions[0]?.optionId || ''\n : currentAnswer?.optionId || ''\n }\n selectedValue={\n variant === QuizVariant.RESULT\n ? currentQuestionResult?.selectedOptions[0]?.optionId || ''\n : currentAnswer?.optionId || ''\n }\n onValueChange={(value) => {\n if (currentQuestion) {\n selectAnswer(currentQuestion.id, value);\n }\n }}\n />\n </div>\n </QuizContainer>\n </>\n );\n};\n\nconst QuizMultipleChoice = ({ paddingBottom }: QuizVariantInterface) => {\n const {\n getCurrentQuestion,\n selectMultipleAnswer,\n getQuestionResultByQuestionId,\n variant,\n } = useQuizStore();\n const currentQuestion = getCurrentQuestion();\n // Derive the current question's answers from the stable `userAnswers` slice\n // instead of calling getAllCurrentAnswer(), which allocates a fresh array on\n // every render. That fresh identity propagated through the memos below and,\n // combined with the store→prop→local-state mirror in MultipleChoiceList,\n // caused a \"Maximum update depth exceeded\" loop on /questionario.\n const userAnswers = useQuizStore((state) => state.userAnswers);\n const allCurrentAnswers = useMemo(\n () =>\n currentQuestion\n ? userAnswers.filter(\n (answer) => answer.questionId === currentQuestion.id\n )\n : [],\n [userAnswers, currentQuestion]\n );\n const currentQuestionResult = getQuestionResultByQuestionId(\n currentQuestion?.id || ''\n );\n // Use ref to track previous values and prevent unnecessary updates\n const prevSelectedValuesRef = useRef<string[]>([]);\n const prevQuestionIdRef = useRef<string>('');\n\n // Memoize the answer IDs to prevent unnecessary re-renders\n // For MULTIPLA_ESCOLHA, we now have a single answer entry with selectedOptionIds array\n const allCurrentAnswerIds = useMemo(() => {\n if (allCurrentAnswers && allCurrentAnswers.length > 0) {\n // Prefer any row already using the new format\n const answerWithSelectedOptionIds = allCurrentAnswers.find((answer) =>\n Array.isArray(answer.selectedOptionIds)\n );\n if (answerWithSelectedOptionIds) {\n return answerWithSelectedOptionIds.selectedOptionIds ?? [];\n }\n // Fallback to old format (multiple entries with optionId)\n return allCurrentAnswers.map((answer) => answer.optionId);\n }\n return [];\n }, [allCurrentAnswers]);\n\n // Memoize the selected values to prevent infinite loops\n const selectedValues = useMemo(() => {\n return allCurrentAnswerIds?.filter((id): id is string => id !== null) || [];\n }, [allCurrentAnswerIds]);\n\n // Only update selectedValues if they actually changed or question changed\n const stableSelectedValues = useMemo(() => {\n // In result mode, always use selectedOptions from the question result\n if (variant === QuizVariant.RESULT) {\n return (\n currentQuestionResult?.selectedOptions?.map((op) => op.optionId) || []\n );\n }\n\n const currentQuestionId = currentQuestion?.id || '';\n const hasQuestionChanged = prevQuestionIdRef.current !== currentQuestionId;\n\n if (hasQuestionChanged) {\n prevQuestionIdRef.current = currentQuestionId;\n prevSelectedValuesRef.current = selectedValues;\n return selectedValues;\n }\n\n // Only update if values actually changed\n const hasValuesChanged =\n JSON.stringify(prevSelectedValuesRef.current) !==\n JSON.stringify(selectedValues);\n if (hasValuesChanged) {\n prevSelectedValuesRef.current = selectedValues;\n return selectedValues;\n }\n\n return prevSelectedValuesRef.current;\n }, [\n selectedValues,\n currentQuestion?.id,\n variant,\n currentQuestionResult?.selectedOptions,\n ]);\n\n // Memoize the callback to prevent unnecessary re-renders\n const handleSelectedValues = useCallback(\n (values: string[]) => {\n if (currentQuestion) {\n selectMultipleAnswer(currentQuestion.id, values);\n }\n },\n [currentQuestion, selectMultipleAnswer]\n );\n\n // Create a stable key to force re-mount when question changes\n const questionKey = useMemo(\n () => `question-${currentQuestion?.id || '1'}`,\n [currentQuestion?.id]\n );\n const choices = currentQuestion?.options?.map((option) => {\n let status: OptionStatus = OptionStatus.NEUTRAL;\n\n if (variant === QuizVariant.RESULT) {\n const isCorrectOption =\n currentQuestionResult?.options?.find((op) => op.id === option.id)\n ?.isCorrect || false;\n\n const isSelected = currentQuestionResult?.selectedOptions?.some(\n (op) => op.optionId === option.id\n );\n\n // Only show correct/incorrect status if answer is not pending evaluation\n const shouldShowCorrectAnswers =\n currentQuestionResult?.answerStatus !==\n ANSWER_STATUS.PENDENTE_AVALIACAO &&\n currentQuestionResult?.answerStatus !== ANSWER_STATUS.NAO_RESPONDIDO;\n\n if (shouldShowCorrectAnswers) {\n if (isCorrectOption) {\n status = OptionStatus.CORRECT;\n } else if (isSelected && !isCorrectOption) {\n status = OptionStatus.INCORRECT;\n } else {\n status = OptionStatus.NEUTRAL;\n }\n } else {\n // When pending evaluation, show all options as neutral\n status = OptionStatus.NEUTRAL;\n }\n }\n\n return {\n label: option.option,\n value: option.id,\n status: status,\n };\n });\n\n if (!choices)\n return (\n <div>\n <p>Não há Escolhas Multiplas</p>\n </div>\n );\n return (\n <>\n <QuizSubTitle subTitle=\"Alternativas\" />\n\n <QuizContainer className={cn('', paddingBottom)}>\n <div className=\"space-y-4\">\n <MultipleChoiceList\n choices={choices}\n key={questionKey}\n name={questionKey}\n selectedValues={stableSelectedValues}\n onHandleSelectedValues={handleSelectedValues}\n mode={variant === QuizVariant.DEFAULT ? 'interactive' : 'readonly'}\n />\n </div>\n </QuizContainer>\n </>\n );\n};\n\nconst QuizDissertative = ({ paddingBottom }: QuizVariantInterface) => {\n const {\n getCurrentQuestion,\n getCurrentAnswer,\n selectDissertativeAnswer,\n getQuestionResultByQuestionId,\n variant,\n getDissertativeCharLimit,\n } = useQuizStore();\n\n const currentQuestion = getCurrentQuestion();\n const currentQuestionResult = getQuestionResultByQuestionId(\n currentQuestion?.id || ''\n );\n\n const currentAnswer = getCurrentAnswer();\n const textareaRef = useRef<HTMLTextAreaElement>(null);\n const charLimit = getDissertativeCharLimit();\n\n const handleAnswerChange = (value: string) => {\n if (currentQuestion) {\n selectDissertativeAnswer(currentQuestion.id, value);\n }\n };\n\n // Auto-resize function\n const adjustTextareaHeight = useCallback(() => {\n if (textareaRef.current) {\n textareaRef.current.style.height = 'auto';\n const scrollHeight = textareaRef.current.scrollHeight;\n const minHeight = 120; // 120px minimum height\n const maxHeight = 400; // 400px maximum height\n const newHeight = Math.min(Math.max(scrollHeight, minHeight), maxHeight);\n textareaRef.current.style.height = `${newHeight}px`;\n }\n }, []);\n\n // Adjust height when currentAnswer changes\n useEffect(() => {\n adjustTextareaHeight();\n }, [currentAnswer, adjustTextareaHeight]);\n\n if (!currentQuestion) {\n return (\n <div className=\"space-y-4\">\n <p className=\"text-text-600 text-md\">Nenhuma questão disponível</p>\n </div>\n );\n }\n\n const localAnswer =\n (variant == 'result'\n ? currentQuestionResult?.answer\n : currentAnswer?.answer) || '';\n return (\n <>\n <div className=\"flex items-center justify-between gap-2 flex-wrap\">\n <QuizSubTitle subTitle=\"Resposta\" />\n {/* Quem corrigiu esta dissertativa. Fica aqui, ao lado da resposta, e\n não só dentro do modal de feedback: o aluno tem que saber que a\n correção veio da IA sem precisar abrir nada. Só aparece no\n resultado — durante a prova não há correção nenhuma. */}\n {variant === QuizVariant.RESULT && (\n <CorrectionSourceTag\n correctionSource={currentQuestionResult?.correctionSource}\n aiCorrectionStatus={currentQuestionResult?.aiCorrectionStatus}\n />\n )}\n </div>\n\n <QuizContainer className={cn(variant != 'result' && paddingBottom)}>\n <div className=\"space-y-4 max-h-[600px] overflow-y-auto\">\n {variant === QuizVariant.DEFAULT ? (\n <div className=\"space-y-4\">\n <TextArea\n ref={textareaRef}\n placeholder=\"Escreva sua resposta\"\n value={localAnswer}\n onChange={(e) => handleAnswerChange(e.target.value)}\n rows={4}\n className=\"min-h-[120px] max-h-[400px] resize-none overflow-y-auto\"\n maxLength={charLimit}\n showCharacterCount={!!charLimit}\n />\n </div>\n ) : (\n <div className=\"space-y-4\">\n <p className=\"text-text-600 text-md whitespace-pre-wrap\">\n {localAnswer || 'Nenhuma resposta fornecida'}\n </p>\n </div>\n )}\n </div>\n </QuizContainer>\n\n {/* The teacher's comment is rendered by `TeacherQuestionComment`, mounted\n once in `QuizContent` for every question type. This block used to\n duplicate it here, so an essay showed the same text twice. */}\n </>\n );\n};\n\nconst QuizTrueOrFalse = ({ paddingBottom }: QuizVariantInterface) => {\n const {\n variant,\n getCurrentQuestion,\n getCurrentAnswer,\n selectDissertativeAnswer,\n getQuestionResultByQuestionId,\n getUserAnswerByQuestionId,\n } = useQuizStore();\n\n const currentQuestion = getCurrentQuestion();\n const currentAnswer = getCurrentAnswer();\n const currentQuestionResult = getQuestionResultByQuestionId(\n currentQuestion?.id || ''\n );\n\n // Get the options - prefer persisted result options in result mode, fallback to live question\n const options = useMemo(() => {\n if (variant === QuizVariant.RESULT && currentQuestionResult?.options) {\n return currentQuestionResult.options;\n }\n return currentQuestion?.options || [];\n }, [variant, currentQuestionResult?.options, currentQuestion?.options]);\n\n // Parse stored answers from JSON string, handling both object and legacy array formats\n const parsedAnswers: Record<string, string> = useMemo(() => {\n return parseStoredAnswers(\n variant,\n currentQuestionResult?.answer,\n currentAnswer?.answer\n );\n }, [variant, currentQuestionResult?.answer, currentAnswer?.answer]);\n\n // Local state to track answers: { \"optionId\": \"V\" | \"F\" }\n const [localAnswers, setLocalAnswers] =\n useState<Record<string, string>>(parsedAnswers);\n\n // Sync local answers when question changes or parsed answers update\n useEffect(() => {\n setLocalAnswers(parsedAnswers);\n }, [parsedAnswers, currentQuestion?.id]);\n\n // Handle select change for an option\n const handleSelectChange = (optionId: string, value: string) => {\n if (!currentQuestion) return;\n\n // Get the LATEST answers directly from the store (not from closure)\n // This is critical to avoid stale closure issues when user selects options quickly\n const latestStoreAnswer = getUserAnswerByQuestionId(currentQuestion.id);\n let baseAnswers: Record<string, string> = {};\n if (latestStoreAnswer?.answer) {\n try {\n const parsed = JSON.parse(latestStoreAnswer.answer);\n baseAnswers = normalizeAnswerData(parsed);\n } catch {\n // Invalid JSON, start fresh\n }\n }\n\n // Merge: store answers + local state + new selection\n const mergedAnswers = {\n ...baseAnswers,\n ...localAnswers,\n [optionId]: value,\n };\n setLocalAnswers(mergedAnswers);\n\n // Save to store as JSON string\n selectDissertativeAnswer(currentQuestion.id, JSON.stringify(mergedAnswers));\n };\n\n // Get the current selection for an option\n const getOptionSelection = (optionId: string): string | undefined => {\n return localAnswers[optionId];\n };\n\n const getLetterByIndex = (index: number) => String.fromCodePoint(97 + index); // 97 = 'a' in ASCII\n\n const isDefaultVariant = variant === QuizVariant.DEFAULT;\n\n // Check if we should show correct/incorrect status\n const shouldShowStatus =\n currentQuestionResult?.answerStatus !== ANSWER_STATUS.PENDENTE_AVALIACAO &&\n currentQuestionResult?.answerStatus !== ANSWER_STATUS.NAO_RESPONDIDO;\n\n if (options.length === 0) {\n return (\n <QuizContainer className={cn('', paddingBottom)}>\n <Text size=\"sm\" className=\"text-text-500 italic\">\n Nenhuma opção disponível para esta questão\n </Text>\n </QuizContainer>\n );\n }\n\n return (\n <>\n <QuizSubTitle subTitle=\"Alternativas\" />\n\n <QuizContainer className={cn('', paddingBottom)}>\n <div className=\"flex flex-col gap-3.5\">\n {options.map((option, index) => {\n // Use helper to compute all option state\n const {\n hasAnswered,\n studentAnswer,\n correctAnswer,\n isStudentCorrect,\n variantCorrect,\n } = getTrueOrFalseOptionState(\n option.id,\n variant,\n currentQuestionResult,\n localAnswers\n );\n\n const contentWithLetter = prependLetterToHtml(\n getLetterByIndex(index),\n option.option\n );\n\n return (\n <section\n key={option.id || `option-${index}`}\n className=\"flex flex-col gap-2\"\n >\n <div\n className={cn(\n 'w-full grid grid-cols-[1fr_auto] items-start gap-4 p-2 rounded-md border',\n !isDefaultVariant && shouldShowStatus && hasAnswered\n ? getStatusStyles(variantCorrect)\n : 'border-transparent'\n )}\n >\n <HtmlMathRenderer\n content={contentWithLetter}\n className=\"text-text-900 text-sm\"\n />\n\n {isDefaultVariant ? (\n <Select\n size=\"medium\"\n value={getOptionSelection(option.id)}\n onValueChange={(value) =>\n handleSelectChange(option.id, value)\n }\n >\n <SelectTrigger className=\"w-[120px]\">\n <SelectValue placeholder=\"Selecione\" />\n </SelectTrigger>\n\n <SelectContent>\n <SelectItem value={TrueFalseEnum.VERDADEIRO}>\n Verdadeiro\n </SelectItem>\n <SelectItem value={TrueFalseEnum.FALSO}>\n Falso\n </SelectItem>\n </SelectContent>\n </Select>\n ) : (\n shouldShowStatus &&\n hasAnswered && <div>{getStatusBadge(variantCorrect)}</div>\n )}\n </div>\n\n {!isDefaultVariant && shouldShowStatus && (\n <span className=\"flex flex-row gap-2 items-center flex-wrap\">\n {hasAnswered ? (\n <>\n <Text size=\"2xs\" className=\"text-text-800\">\n Resposta selecionada: {studentAnswer}\n </Text>\n {!isStudentCorrect && (\n <Text size=\"2xs\" className=\"text-text-800\">\n | Resposta correta: {correctAnswer}\n </Text>\n )}\n </>\n ) : (\n <Text size=\"2xs\" className=\"text-text-800\">\n Não respondida | Resposta correta: {correctAnswer}\n </Text>\n )}\n </span>\n )}\n </section>\n );\n })}\n </div>\n </QuizContainer>\n </>\n );\n};\n\ninterface UserAnswer {\n option: string;\n dotOption: string | null;\n correctOption: string;\n isCorrect: boolean | null;\n}\n\nconst QuizConnectDots = ({ paddingBottom }: QuizVariantInterface) => {\n const {\n variant,\n getCurrentQuestion,\n getCurrentAnswer,\n selectDissertativeAnswer,\n getQuestionResultByQuestionId,\n getUserAnswerByQuestionId,\n } = useQuizStore();\n\n const currentQuestion = getCurrentQuestion();\n const currentQuestionResult = getQuestionResultByQuestionId(\n currentQuestion?.id || ''\n );\n const currentAnswer = getCurrentAnswer();\n\n // Extract options from the current question\n // Each option has: id, option (left column), correctValue (right column)\n const questionOptions = useMemo(() => {\n if (!currentQuestion?.options) return [];\n return currentQuestion.options;\n }, [currentQuestion?.options]);\n\n // Build dotsOptions from correctValue of each option (right column values)\n // Shuffle them so they don't appear in the same order as the left column\n const dotsOptions = useMemo(() => {\n const dots = questionOptions\n .map((opt) => ({ label: opt.correctValue || '' }))\n .filter((opt) => opt.label !== '');\n // Use question ID as seed for consistent shuffle\n return shuffleWithSeed(dots, currentQuestion?.id || '');\n }, [questionOptions, currentQuestion?.id]);\n\n // Build options for display (left column with correct answer mapping)\n const options = useMemo(() => {\n return questionOptions.map((opt) => ({\n id: opt.id,\n label: opt.option,\n correctOption: opt.correctValue || '',\n }));\n }, [questionOptions]);\n\n // Parse stored matching answers\n // In result mode, read from matchingAnswers array, fallback to answer JSON\n // In default mode, read from currentAnswer.answer JSON string\n const parsedAnswers: Record<string, string> = useMemo(() => {\n if (\n variant === QuizVariant.RESULT &&\n currentQuestionResult?.matchingAnswers\n ) {\n // Convert matchingAnswers array to Record<optionId, selectedValue>\n const answers: Record<string, string> = {};\n for (const match of currentQuestionResult.matchingAnswers) {\n answers[match.optionId] = match.selectedValue;\n }\n return answers;\n }\n // Fallback to answer JSON string (for older/partially migrated submissions)\n return parseStoredAnswers(\n variant,\n currentQuestionResult?.answer,\n currentAnswer?.answer\n );\n }, [\n variant,\n currentQuestionResult?.matchingAnswers,\n currentQuestionResult?.answer,\n currentAnswer?.answer,\n ]);\n\n const [userAnswers, setUserAnswers] = useState<UserAnswer[]>([]);\n\n // Initialize userAnswers from options and stored answers\n useEffect(() => {\n if (options.length > 0) {\n setUserAnswers(\n options.map((option) => {\n const storedValue = parsedAnswers[option.id] || null;\n return {\n option: option.label,\n dotOption: storedValue,\n correctOption: option.correctOption,\n isCorrect: storedValue\n ? storedValue === option.correctOption\n : null,\n };\n })\n );\n }\n }, [options, parsedAnswers]);\n\n const handleSelectDot = (optionIndex: number, dotValue: string) => {\n const option = options[optionIndex];\n if (!option || !currentQuestion) return;\n\n // Update local state\n setUserAnswers((prev) => {\n const next = [...prev];\n next[optionIndex] = {\n option: option.label,\n dotOption: dotValue,\n correctOption: option.correctOption,\n isCorrect: dotValue ? dotValue === option.correctOption : null,\n };\n return next;\n });\n\n // Get the LATEST answers directly from the store (not from closure)\n const latestStoreAnswer = getUserAnswerByQuestionId(currentQuestion.id);\n let baseAnswers: Record<string, string> = {};\n if (latestStoreAnswer?.answer) {\n try {\n const parsed = JSON.parse(latestStoreAnswer.answer);\n baseAnswers = normalizeAnswerData(parsed);\n } catch {\n // Invalid JSON, start fresh\n }\n }\n\n // Persist to shared store as JSON\n const newAnswers = {\n ...baseAnswers,\n ...parsedAnswers,\n [option.id]: dotValue,\n };\n selectDissertativeAnswer(currentQuestion.id, JSON.stringify(newAnswers));\n };\n\n const getLetterByIndex = (index: number) => String.fromCodePoint(97 + index); // 'a', 'b', 'c'...\n\n const isDefaultVariant = variant === QuizVariant.DEFAULT;\n const assignedDots = new Set(\n userAnswers.map((a) => a.dotOption).filter(Boolean)\n );\n\n if (options.length === 0) {\n return (\n <QuizContainer className={cn('', paddingBottom)}>\n <Text size=\"sm\" className=\"text-text-500 italic\">\n Nenhuma opção de relacionamento disponível\n </Text>\n </QuizContainer>\n );\n }\n\n return (\n <>\n <QuizSubTitle subTitle=\"Alternativas\" />\n\n <QuizContainer className={cn('', paddingBottom)}>\n <div className=\"flex flex-col gap-3.5\">\n {options.map((option, index) => {\n const answer = userAnswers[index] || {\n option: option.label,\n dotOption: null,\n correctOption: option.correctOption,\n isCorrect: null,\n };\n const variantCorrect = getAnswerStatus(answer.isCorrect);\n return (\n <section key={option.id} className=\"flex flex-col gap-2\">\n <div\n className={cn(\n 'grid grid-cols-[1fr_auto] items-center gap-4 p-2 rounded-md',\n !isDefaultVariant && variantCorrect\n ? getStatusStyles(variantCorrect)\n : ''\n )}\n >\n <HtmlMathRenderer\n content={prependLetterToHtml(\n getLetterByIndex(index),\n option.label\n )}\n className=\"text-text-900 text-sm\"\n />\n\n {isDefaultVariant ? (\n <Select\n size=\"medium\"\n value={answer.dotOption || undefined}\n onValueChange={(value) => handleSelectDot(index, value)}\n >\n <SelectTrigger\n className=\"w-[180px] overflow-hidden truncate\"\n title={\n answer.dotOption\n ? stripHtmlTags(answer.dotOption)\n : undefined\n }\n >\n <SelectValue placeholder=\"Selecione opção\" />\n </SelectTrigger>\n\n <SelectContent>\n {dotsOptions\n .filter(\n (dot) =>\n !assignedDots.has(dot.label) ||\n answer.dotOption === dot.label\n )\n .map((dot) => (\n <SelectItem\n key={dot.label}\n value={dot.label}\n title={stripHtmlTags(dot.label)}\n truncate\n >\n {stripHtmlTags(dot.label)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n ) : (\n <div>\n {answer.isCorrect === null\n ? null\n : getStatusBadge(variantCorrect)}\n </div>\n )}\n </div>\n\n {!isDefaultVariant && (\n <span className=\"flex flex-row gap-2 items-center\">\n <Text size=\"2xs\" className=\"text-text-800\">\n Resposta selecionada:{' '}\n {answer.dotOption\n ? stripHtmlTags(answer.dotOption)\n : 'Nenhuma'}\n </Text>\n {answer.isCorrect === false && (\n <Text size=\"2xs\" className=\"text-text-800\">\n Resposta correta: {stripHtmlTags(answer.correctOption)}\n </Text>\n )}\n </span>\n )}\n </section>\n );\n })}\n </div>\n </QuizContainer>\n </>\n );\n};\n\nconst QuizFill = ({ paddingBottom }: QuizVariantInterface) => {\n const {\n getCurrentQuestion,\n getQuestionResultByQuestionId,\n getCurrentAnswer,\n selectDissertativeAnswer,\n variant,\n getUserAnswerByQuestionId,\n } = useQuizStore();\n\n const currentQuestion = getCurrentQuestion();\n const currentQuestionResult = getQuestionResultByQuestionId(\n currentQuestion?.id || ''\n );\n const currentAnswer = getCurrentAnswer();\n\n const baseId = useId();\n\n // Get the additionalContent from the question (contains text with {optionId} placeholders)\n // In result mode, prefer the persisted snapshot to match parsedAnswers\n const additionalContent =\n variant === QuizVariant.RESULT\n ? currentQuestionResult?.additionalContent ||\n currentQuestion?.additionalContent ||\n ''\n : currentQuestion?.additionalContent || '';\n\n // Get the options from the question\n // In result mode, prefer the persisted snapshot to match parsedAnswers\n const questionOptions =\n variant === QuizVariant.RESULT\n ? currentQuestionResult?.options || currentQuestion?.options || []\n : currentQuestion?.options || [];\n\n // Shuffled options for dropdown display (consistent per question)\n const shuffledOptions = useMemo(() => {\n return shuffleWithSeed(questionOptions, currentQuestion?.id || '');\n }, [questionOptions, currentQuestion?.id]);\n\n // Parse current answers\n // In result mode, read from fillAnswers object, fallback to answer JSON\n // In default mode, read from currentAnswer.answer JSON string\n const parsedAnswers: Record<string, string> = useMemo(() => {\n if (variant === QuizVariant.RESULT && currentQuestionResult?.fillAnswers) {\n // fillAnswers is already a Record<placeholderId, selectedOptionId>\n return currentQuestionResult.fillAnswers;\n }\n // Fallback to answer JSON string (for older/partially migrated submissions)\n return parseStoredAnswers(\n variant,\n currentQuestionResult?.answer,\n currentAnswer?.answer\n );\n }, [\n variant,\n currentQuestionResult?.fillAnswers,\n currentQuestionResult?.answer,\n currentAnswer?.answer,\n ]);\n\n const [localAnswers, setLocalAnswers] =\n useState<Record<string, string>>(parsedAnswers);\n\n // Sync local answers when question changes\n useEffect(() => {\n setLocalAnswers(parsedAnswers);\n }, [parsedAnswers, currentQuestion?.id]);\n\n // Handle select change\n const handleSelectChange = (placeholderId: string, optionId: string) => {\n if (!currentQuestion) return;\n\n // Get the LATEST answers directly from the store (not from closure)\n const latestStoreAnswer = getUserAnswerByQuestionId(currentQuestion.id);\n let baseAnswers: Record<string, string> = {};\n if (latestStoreAnswer?.answer) {\n try {\n const parsed = JSON.parse(latestStoreAnswer.answer);\n baseAnswers = normalizeAnswerData(parsed);\n } catch {\n // Invalid JSON, start fresh\n }\n }\n\n const newAnswers = {\n ...baseAnswers,\n ...localAnswers,\n [placeholderId]: optionId,\n };\n setLocalAnswers(newAnswers);\n\n // Save to store as JSON string\n selectDissertativeAnswer(currentQuestion.id, JSON.stringify(newAnswers));\n };\n\n // Get option text by ID\n const getOptionTextById = (optionId: string): string => {\n const option = questionOptions.find((opt) => opt.id === optionId);\n return stripHtmlTags(option?.option || '');\n };\n\n // Check if an answer is correct (for result mode)\n const isAnswerCorrect = (placeholderId: string): boolean => {\n const selectedOptionId = localAnswers[placeholderId];\n // The placeholder ID is the correct option ID\n return selectedOptionId === placeholderId;\n };\n\n // Render the select for default mode\n const renderDefaultSelect = (placeholderId: string) => {\n const selectedOptionId = localAnswers[placeholderId];\n\n return (\n <span\n key={placeholderId}\n className=\"inline-block align-middle mx-1 my-2\"\n style={{ display: 'inline-block', verticalAlign: 'middle' }}\n >\n <Select\n value={selectedOptionId || undefined}\n onValueChange={(value) => handleSelectChange(placeholderId, value)}\n >\n <SelectTrigger\n className=\"w-auto min-w-[120px] max-w-[200px] h-7 px-2 bg-background border-gray-300 overflow-hidden truncate\"\n title={stripHtmlTags(\n shuffledOptions.find((o) => o.id === selectedOptionId)?.option ||\n ''\n )}\n >\n <SelectValue placeholder=\"Selecione opção\" />\n </SelectTrigger>\n <SelectContent>\n {shuffledOptions.map((option) => (\n <SelectItem\n key={option.id}\n value={option.id}\n title={stripHtmlTags(option.option)}\n truncate\n >\n {stripHtmlTags(option.option)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </span>\n );\n };\n\n // Render the result badge\n const renderResultBadge = (placeholderId: string) => {\n const selectedOptionId = localAnswers[placeholderId];\n const selectedOptionText = getOptionTextById(selectedOptionId);\n const isCorrect = isAnswerCorrect(placeholderId);\n\n if (!selectedOptionId) {\n return (\n <span\n key={placeholderId}\n className=\"inline-block align-middle mx-1 my-2\"\n style={{ display: 'inline-block', verticalAlign: 'middle' }}\n >\n <Badge\n variant=\"solid\"\n action=\"error\"\n iconRight={<XCircleIcon />}\n size=\"large\"\n className=\"py-1 px-2\"\n >\n Não respondido\n </Badge>\n </span>\n );\n }\n\n return (\n <span\n key={placeholderId}\n className=\"inline-block align-middle mx-1 my-2\"\n style={{ display: 'inline-block', verticalAlign: 'middle' }}\n >\n <Badge\n variant=\"solid\"\n action={isCorrect ? 'success' : 'error'}\n iconRight={isCorrect ? <CheckCircleIcon /> : <XCircleIcon />}\n size=\"large\"\n className=\"py-1 px-2\"\n >\n <span className=\"text-text-900\">{selectedOptionText}</span>\n </Badge>\n </span>\n );\n };\n\n // Render the correct answer for resolution\n const renderResolutionAnswer = (placeholderId: string) => {\n // The placeholderId IS the correct option ID\n const correctOptionText = getOptionTextById(placeholderId);\n\n return (\n <span className=\"inline mx-1 text-success-600 font-semibold border-b-2 border-success-600\">\n {correctOptionText}\n </span>\n );\n };\n\n // Parse text and render with selects\n const renderTextWithSelects = (text: string, isResolution?: boolean) => {\n const elements: Array<{ element: string | ReactNode; id: string }> = [];\n let lastIndex = 0;\n const nextId = () => elements.length;\n\n // Match {uuid} placeholders (UUID format or any alphanumeric with hyphens)\n const regex = /\\{([a-zA-Z0-9-]+)\\}/g;\n let match;\n\n while ((match = regex.exec(text)) !== null) {\n const [fullMatch, placeholderId] = match;\n const startIndex = match.index;\n\n // Add text before the placeholder\n if (startIndex > lastIndex) {\n elements.push({\n element: text.slice(lastIndex, startIndex),\n id: `${baseId}-text-${nextId()}`,\n });\n }\n\n // Add the appropriate element based on variant and mode\n if (isResolution) {\n elements.push({\n element: renderResolutionAnswer(placeholderId),\n id: `${baseId}-resolution-${nextId()}`,\n });\n } else if (variant === QuizVariant.DEFAULT) {\n elements.push({\n element: renderDefaultSelect(placeholderId),\n id: `${baseId}-select-${nextId()}`,\n });\n } else {\n elements.push({\n element: renderResultBadge(placeholderId),\n id: `${baseId}-result-${nextId()}`,\n });\n }\n\n lastIndex = match.index + fullMatch.length;\n }\n\n // Add remaining text\n if (lastIndex < text.length) {\n elements.push({\n element: text.slice(lastIndex),\n id: `${baseId}-text-${nextId()}`,\n });\n }\n\n return elements;\n };\n\n // Render HTML content with selects replacing placeholders\n const renderHtmlWithSelects = (\n htmlContent: string,\n isResolution?: boolean\n ) => {\n // For now, we'll strip HTML and render plain text with selects\n // This preserves the placeholder functionality while handling HTML content\n const textContent = stripHtmlTags(htmlContent);\n return renderTextWithSelects(textContent, isResolution);\n };\n\n if (!currentQuestion || !additionalContent) {\n return (\n <>\n <QuizSubTitle subTitle=\"Preenchimento\" />\n <QuizContainer className=\"h-auto pb-0\">\n <div className=\"space-y-6 px-4 h-auto\">\n <Text\n size=\"md\"\n color=\"text-text-600\"\n weight=\"normal\"\n className={cn(paddingBottom)}\n >\n Nenhum conteúdo disponível para esta questão.\n </Text>\n </div>\n </QuizContainer>\n </>\n );\n }\n\n return (\n <>\n <QuizSubTitle subTitle=\"Preencha as lacunas\" />\n\n <QuizContainer\n className={cn('', variant !== QuizVariant.RESULT && paddingBottom)}\n >\n <div className=\"px-4\">\n <Text\n as=\"div\"\n size=\"lg\"\n color=\"text-text-900\"\n weight=\"normal\"\n className=\"leading-[2.5] *:inline\"\n >\n {renderHtmlWithSelects(additionalContent).map((element) => (\n <Fragment key={element.id}>{element.element}</Fragment>\n ))}\n </Text>\n </div>\n </QuizContainer>\n\n {variant === QuizVariant.RESULT && (\n <>\n <QuizSubTitle subTitle=\"Resposta correta\" />\n\n <QuizContainer className={cn('', paddingBottom)}>\n <div className=\"px-4\">\n <Text\n as=\"div\"\n size=\"lg\"\n color=\"text-text-900\"\n weight=\"normal\"\n className=\"leading-[2.5] *:inline\"\n >\n {renderHtmlWithSelects(additionalContent, true).map(\n (element) => (\n <Fragment key={element.id}>{element.element}</Fragment>\n )\n )}\n </Text>\n </div>\n </QuizContainer>\n </>\n )}\n </>\n );\n};\n\nconst QuizImageQuestion = ({ paddingBottom }: QuizVariantInterface) => {\n const {\n variant,\n getCurrentQuestion,\n getCurrentAnswer,\n selectDissertativeAnswer,\n getQuestionResultByQuestionId,\n } = useQuizStore();\n\n const currentQuestion = getCurrentQuestion();\n const currentAnswer = getCurrentAnswer();\n const currentQuestionResult = getQuestionResultByQuestionId(\n currentQuestion?.id || ''\n );\n\n // Get image URL from additionalContent\n const imageUrl = currentQuestion?.additionalContent || '';\n\n // Parse correct coordinates from first option (stored as JSON: {\"x\": number, \"y\": number})\n const correctPositionRelative = useMemo(() => {\n if (!currentQuestion?.options || currentQuestion.options.length === 0) {\n return { x: 0.5, y: 0.5 }; // Default center\n }\n try {\n const coords = JSON.parse(currentQuestion.options[0].option);\n if (typeof coords.x === 'number' && typeof coords.y === 'number') {\n // Coordinates are stored as percentages (0-100), convert to relative (0-1)\n return { x: coords.x / 100, y: coords.y / 100 };\n }\n } catch {\n // Fall back to default\n }\n return { x: 0.5, y: 0.5 };\n }, [currentQuestion?.options]);\n\n // Calculate correctRadiusRelative automatically based on the circle dimensions\n const calculateCorrectRadiusRelative = (): number => {\n // The correct answer circle has width: 15% and height: 30%\n // We'll use the average of these as the radius\n const circleWidthRelative = 0.15; // 15%\n const circleHeightRelative = 0.3; // 30%\n\n // Calculate the average radius (half of the average of width and height)\n const averageRadius = (circleWidthRelative + circleHeightRelative) / 4;\n\n // Add a small tolerance for better user experience\n const tolerance = 0.02; // 2% tolerance\n\n return averageRadius + tolerance;\n };\n\n const correctRadiusRelative = calculateCorrectRadiusRelative();\n\n // Parse user's answer from stored JSON or result\n const storedUserAnswer = useMemo(() => {\n if (variant === QuizVariant.RESULT && currentQuestionResult?.answer) {\n try {\n const coords = JSON.parse(currentQuestionResult.answer);\n if (typeof coords.x === 'number' && typeof coords.y === 'number') {\n return { x: coords.x / 100, y: coords.y / 100 };\n }\n } catch {\n // Fall back to null\n }\n } else if (currentAnswer?.answer) {\n try {\n const coords = JSON.parse(currentAnswer.answer);\n if (typeof coords.x === 'number' && typeof coords.y === 'number') {\n return { x: coords.x / 100, y: coords.y / 100 };\n }\n } catch {\n // Fall back to null\n }\n }\n return null;\n }, [variant, currentQuestionResult?.answer, currentAnswer?.answer]);\n\n const [clickPositionRelative, setClickPositionRelative] = useState<{\n x: number;\n y: number;\n } | null>(storedUserAnswer);\n\n // Sync with stored answer when it changes\n useEffect(() => {\n setClickPositionRelative(storedUserAnswer);\n }, [storedUserAnswer]);\n\n // Helper function to safely convert click coordinates to relative coordinates\n const convertToRelativeCoordinates = (\n x: number,\n y: number,\n rect: DOMRect\n ): { x: number; y: number } => {\n // Guard against division by zero or extremely small dimensions\n const safeWidth = Math.max(rect.width, 0.001);\n const safeHeight = Math.max(rect.height, 0.001);\n\n // Convert to relative coordinates and clamp to [0, 1] range\n const xRelative = Math.max(0, Math.min(1, x / safeWidth));\n const yRelative = Math.max(0, Math.min(1, y / safeHeight));\n\n return { x: xRelative, y: yRelative };\n };\n\n const handleImageClick = (event: MouseEvent<HTMLButtonElement>) => {\n if (variant === QuizVariant.RESULT) return;\n\n const rect = event.currentTarget.getBoundingClientRect();\n const x = event.clientX - rect.left;\n const y = event.clientY - rect.top;\n\n // Use helper function for safe conversion\n const positionRelative = convertToRelativeCoordinates(x, y, rect);\n setClickPositionRelative(positionRelative);\n\n // Save the answer to the store (as percentages 0-100)\n if (currentQuestion) {\n const answerJson = JSON.stringify({\n x: Math.round(positionRelative.x * 100),\n y: Math.round(positionRelative.y * 100),\n });\n selectDissertativeAnswer(currentQuestion.id, answerJson);\n }\n };\n\n const handleKeyboardActivate = () => {\n if (variant === QuizVariant.RESULT) return;\n // Choose a deterministic position for keyboard activation; center is a reasonable default\n const centerPosition = { x: 0.5, y: 0.5 };\n setClickPositionRelative(centerPosition);\n\n // Save the answer to the store (as percentages 0-100)\n if (currentQuestion) {\n const answerJson = JSON.stringify({\n x: Math.round(centerPosition.x * 100),\n y: Math.round(centerPosition.y * 100),\n });\n selectDissertativeAnswer(currentQuestion.id, answerJson);\n }\n };\n\n const isCorrect = () => {\n if (!clickPositionRelative) return false;\n\n const distance = Math.sqrt(\n Math.pow(clickPositionRelative.x - correctPositionRelative.x, 2) +\n Math.pow(clickPositionRelative.y - correctPositionRelative.y, 2)\n );\n\n return distance <= correctRadiusRelative;\n };\n\n const getUserCircleColorClasses = () => {\n if (variant === QuizVariant.DEFAULT) {\n return 'bg-indicator-primary/70 border-[#F8CC2E]';\n }\n\n if (variant === QuizVariant.RESULT) {\n return isCorrect()\n ? 'bg-success-600/70 border-white' // Green for correct answer\n : 'bg-indicator-error/70 border-white'; // Red for incorrect answer\n }\n\n return 'bg-success-600/70 border-white';\n };\n\n // In review the question is read, not answered: the shared view draws the\n // image, the answer area and the student's click with the very tolerance the\n // backend graded with. This screen used to draw its own circle with a radius\n // of its own, so the drawing could disagree with the score.\n if (variant === QuizVariant.RESULT) {\n return (\n <QuizContainer className={cn('', paddingBottom)}>\n <div data-testid=\"quiz-image-container\" className=\"p-3\">\n <ImageAnswerView\n imageUrl={imageUrl}\n correctPoint={parseImageCorrectPoint(currentQuestion?.options)}\n studentPoint={parseImageStudentPoint(currentQuestionResult)}\n toleranceRadius={\n currentQuestionResult?.imageTolerance ?? DEFAULT_IMAGE_TOLERANCE\n }\n />\n </div>\n </QuizContainer>\n );\n }\n\n return (\n <>\n <QuizSubTitle subTitle=\"Clique na área correta\" />\n\n <QuizContainer className={cn('', paddingBottom)}>\n <div\n data-testid=\"quiz-image-container\"\n className=\"space-y-6 p-3 relative inline-block\"\n >\n <button\n data-testid=\"quiz-image-button\"\n type=\"button\"\n className=\"relative cursor-pointer w-full h-full border-0 bg-transparent p-0\"\n onClick={handleImageClick}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n handleKeyboardActivate();\n }\n }}\n aria-label=\"Área da imagem interativa\"\n >\n <img\n data-testid=\"quiz-image\"\n src={imageUrl || ImageQuestion}\n alt=\"Question\"\n className=\"w-full h-auto rounded-md\"\n />\n\n {/* User's answer circle */}\n {clickPositionRelative && (\n <div\n data-testid=\"quiz-user-circle\"\n className={`absolute rounded-full border-4 pointer-events-none ${getUserCircleColorClasses()}`}\n style={{\n minWidth: '30px',\n maxWidth: '52px',\n width: '5%',\n aspectRatio: '1 / 1',\n left: `calc(${clickPositionRelative.x * 100}% - 2.5%)`,\n top: `calc(${clickPositionRelative.y * 100}% - 2.5%)`,\n }}\n />\n )}\n </button>\n </div>\n </QuizContainer>\n </>\n );\n};\n\nexport {\n QuizSubTitle,\n QuizContainer,\n QuizMultipleChoice,\n QuizDissertative,\n QuizTrueOrFalse,\n QuizConnectDots,\n QuizFill,\n QuizImageQuestion,\n QuizAlternative,\n};\n","import type {\n Question,\n QuestionResult,\n} from '../../components/Quiz/useQuizStore';\nimport type { ImagePoint } from '../../components/shared/ImageAnswerView';\n\n/**\n * Fallback radius, in image percentage points, used when the payload does not\n * carry the backend's tolerance.\n *\n * The backend is the authority — it grades with its own value and now sends it\n * as `imageTolerance`. This only covers older payloads, and matches the value\n * the backend has always used.\n */\nexport const DEFAULT_IMAGE_TOLERANCE = 10;\n\n/**\n * Read the answer key point of an IMAGEM question.\n *\n * The key lives in the question's single option, whose text is the JSON\n * `{\"x\": number, \"y\": number}` in percentage points of the image.\n *\n * @param options - The question's options\n * @returns The correct point, or null when there is no usable key\n *\n * @example\n * ```typescript\n * parseImageCorrectPoint([{ id: 'o1', option: '{\"x\":50,\"y\":30}' }]); // { x: 50, y: 30 }\n * ```\n */\nexport const parseImageCorrectPoint = (\n options: Question['options'] | undefined\n): ImagePoint | null => {\n const first = options?.[0];\n if (!first?.option) {\n return null;\n }\n\n try {\n const parsed = JSON.parse(first.option);\n if (typeof parsed?.x === 'number' && typeof parsed?.y === 'number') {\n return { x: parsed.x, y: parsed.y };\n }\n } catch {\n return null;\n }\n\n return null;\n};\n\n/**\n * Read where the student clicked on an IMAGEM question.\n *\n * Prefers the structured `imageAnswer` the backend sends. Falls back to parsing\n * `answer`, which older payloads and the draft flow still use, in either the\n * `{x, y}` or `{coordinateX, coordinateY}` shape.\n *\n * @param result - The student's answer for the question\n * @returns The clicked point in percentage points (0-100), or null\n *\n * @example\n * ```typescript\n * parseImageStudentPoint({ imageAnswer: { coordinateX: 52, coordinateY: 28 } }); // { x: 52, y: 28 }\n * ```\n */\nexport const parseImageStudentPoint = (\n result: QuestionResult['answers'][number] | null | undefined\n): ImagePoint | null => {\n if (!result) {\n return null;\n }\n\n if (result.imageAnswer) {\n const { coordinateX, coordinateY } = result.imageAnswer;\n if (typeof coordinateX === 'number' && typeof coordinateY === 'number') {\n return { x: coordinateX, y: coordinateY };\n }\n }\n\n if (!result.answer) {\n return null;\n }\n\n try {\n const parsed = JSON.parse(result.answer);\n const x =\n typeof parsed?.coordinateX === 'number' ? parsed.coordinateX : parsed?.x;\n const y =\n typeof parsed?.coordinateY === 'number' ? parsed.coordinateY : parsed?.y;\n if (typeof x === 'number' && typeof y === 'number') {\n return { x, y };\n }\n } catch {\n return null;\n }\n\n return null;\n};\n"]}
@@ -1,6 +1,6 @@
1
1
  "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
2
2
 
3
- var _chunkINIH23HUjs = require('./chunk-INIH23HU.js');
3
+ var _chunkVF6QXUX3js = require('./chunk-VF6QXUX3.js');
4
4
 
5
5
 
6
6
  var _chunkHMIMGXGBjs = require('./chunk-HMIMGXGB.js');
@@ -199,7 +199,7 @@ function PostContentModal({
199
199
  richText ? (
200
200
  // key forces remount (and thus reset) whenever the modal opens/closes
201
201
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
202
- _chunkINIH23HUjs.RichEditor,
202
+ _chunkVF6QXUX3js.RichEditor,
203
203
  {
204
204
  content,
205
205
  onChange: (data) => onContentChange(data.html),
@@ -932,4 +932,4 @@ var Forum_default = Forum;
932
932
 
933
933
 
934
934
  exports.Forum = Forum; exports.Forum_default = Forum_default;
935
- //# sourceMappingURL=chunk-KYGFYAU7.js.map
935
+ //# sourceMappingURL=chunk-JHN35HPH.js.map