@khanacademy/perseus 2.1.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (1212) hide show
  1. package/.babelrc.js +1 -1
  2. package/.eslintrc.js +1 -0
  3. package/CHANGELOG.md +22 -0
  4. package/dist/__tests__/mock-asset-loading-widget.d.ts +59 -0
  5. package/dist/__tests__/mock-asset-loading-widget.js.flow +70 -0
  6. package/dist/__tests__/mock-widget.d.ts +30 -0
  7. package/dist/__tests__/mock-widget.js.flow +39 -0
  8. package/dist/__tests__/test-items/image-item.d.ts +14 -0
  9. package/dist/__tests__/test-items/image-item.js.flow +20 -0
  10. package/dist/__tests__/test-items/input-number-1-item.d.ts +9 -0
  11. package/dist/__tests__/test-items/input-number-1-item.js.flow +15 -0
  12. package/dist/__tests__/test-items/input-number-2-item.d.ts +9 -0
  13. package/dist/__tests__/test-items/input-number-2-item.js.flow +15 -0
  14. package/dist/__tests__/test-items/table-item.d.ts +9 -0
  15. package/dist/__tests__/test-items/table-item.js.flow +15 -0
  16. package/dist/a11y.d.ts +5 -0
  17. package/dist/a11y.js.flow +11 -0
  18. package/dist/all-widgets.d.ts +22 -0
  19. package/dist/all-widgets.js.flow +33 -0
  20. package/dist/article-renderer.d.ts +54 -0
  21. package/dist/article-renderer.js.flow +69 -0
  22. package/dist/asset-context.d.ts +14 -0
  23. package/dist/asset-context.js.flow +15 -0
  24. package/dist/basic-widgets.d.ts +22 -0
  25. package/dist/basic-widgets.js.flow +33 -0
  26. package/dist/components/button-group.d.ts +27 -0
  27. package/dist/components/button-group.js.flow +33 -0
  28. package/dist/components/constants.d.ts +7 -0
  29. package/dist/components/constants.js.flow +13 -0
  30. package/dist/components/fixed-to-responsive.d.ts +2 -0
  31. package/dist/components/fixed-to-responsive.js.flow +8 -0
  32. package/dist/components/graph.d.ts +44 -0
  33. package/dist/components/graph.js.flow +52 -0
  34. package/dist/components/graphie-classes.d.ts +13 -0
  35. package/dist/components/graphie-classes.js.flow +19 -0
  36. package/dist/components/graphie-movables.d.ts +16 -0
  37. package/dist/components/graphie-movables.js.flow +22 -0
  38. package/dist/components/graphie.d.ts +44 -0
  39. package/dist/components/graphie.js.flow +56 -0
  40. package/dist/components/highlighting/highlightable-content.d.ts +39 -0
  41. package/dist/components/highlighting/highlightable-content.js.flow +59 -0
  42. package/dist/components/highlighting/highlights.d.ts +30 -0
  43. package/dist/components/highlighting/highlights.js.flow +54 -0
  44. package/dist/components/highlighting/ranges.d.ts +42 -0
  45. package/dist/components/highlighting/ranges.js.flow +60 -0
  46. package/dist/components/highlighting/types.d.ts +64 -0
  47. package/dist/components/highlighting/types.js.flow +70 -0
  48. package/dist/components/highlighting/ui/highlight-renderer.d.ts +34 -0
  49. package/dist/components/highlighting/ui/highlight-renderer.js.flow +45 -0
  50. package/dist/components/highlighting/ui/highlight-set-renderer.d.ts +34 -0
  51. package/dist/components/highlighting/ui/highlight-set-renderer.js.flow +44 -0
  52. package/dist/components/highlighting/ui/highlight-tooltip.d.ts +16 -0
  53. package/dist/components/highlighting/ui/highlight-tooltip.js.flow +24 -0
  54. package/dist/components/highlighting/ui/highlighting-ui.d.ts +19 -0
  55. package/dist/components/highlighting/ui/highlighting-ui.js.flow +30 -0
  56. package/dist/components/highlighting/ui/selection-tracker.d.ts +47 -0
  57. package/dist/components/highlighting/ui/selection-tracker.js.flow +56 -0
  58. package/dist/components/highlighting/ui/types.d.ts +13 -0
  59. package/dist/components/highlighting/ui/types.js.flow +22 -0
  60. package/dist/components/highlighting/ui/util.d.ts +28 -0
  61. package/dist/components/highlighting/ui/util.js.flow +42 -0
  62. package/dist/components/highlighting/word-indexer.d.ts +28 -0
  63. package/dist/components/highlighting/word-indexer.js.flow +21 -0
  64. package/dist/components/hud.d.ts +15 -0
  65. package/dist/components/hud.js.flow +21 -0
  66. package/dist/components/icon.d.ts +56 -0
  67. package/dist/components/icon.js.flow +31 -0
  68. package/dist/components/image-loader.d.ts +52 -0
  69. package/dist/components/image-loader.js.flow +49 -0
  70. package/dist/components/info-tip/info-tip.d.ts +14 -0
  71. package/dist/components/info-tip/info-tip.js.flow +20 -0
  72. package/dist/components/info-tip.d.ts +18 -0
  73. package/dist/components/info-tip.js.flow +18 -0
  74. package/dist/components/inline-icon.d.ts +10 -0
  75. package/dist/components/inline-icon.js.flow +16 -0
  76. package/dist/components/input-with-examples.d.ts +52 -0
  77. package/dist/components/input-with-examples.js.flow +58 -0
  78. package/dist/components/lint.d.ts +23 -0
  79. package/dist/components/lint.js.flow +29 -0
  80. package/dist/components/math-input.d.ts +40 -0
  81. package/dist/components/math-input.js.flow +46 -0
  82. package/dist/components/math-output.d.ts +23 -0
  83. package/dist/components/math-output.js.flow +29 -0
  84. package/dist/components/multi-button-group.d.ts +29 -0
  85. package/dist/components/multi-button-group.js.flow +35 -0
  86. package/dist/components/number-input.d.ts +34 -0
  87. package/dist/components/number-input.js.flow +42 -0
  88. package/dist/components/prop-check-box.d.ts +2 -0
  89. package/dist/components/prop-check-box.js.flow +8 -0
  90. package/dist/components/range-input.d.ts +14 -0
  91. package/dist/components/range-input.js.flow +20 -0
  92. package/dist/components/simple-keypad-input.d.ts +19 -0
  93. package/dist/components/simple-keypad-input.js.flow +16 -0
  94. package/dist/components/sortable.d.ts +68 -0
  95. package/dist/components/sortable.js.flow +78 -0
  96. package/dist/components/stub-tag-editor.d.ts +25 -0
  97. package/dist/components/stub-tag-editor.js.flow +18 -0
  98. package/dist/components/svg-image.d.ts +74 -0
  99. package/dist/components/svg-image.js.flow +83 -0
  100. package/dist/components/tex-buttons.d.ts +20 -0
  101. package/dist/components/tex-buttons.js.flow +26 -0
  102. package/dist/components/tex.d.ts +25 -0
  103. package/dist/components/tex.js.flow +25 -0
  104. package/dist/components/text-input.d.ts +33 -0
  105. package/dist/components/text-input.js.flow +39 -0
  106. package/dist/components/text-list-editor.d.ts +16 -0
  107. package/dist/components/text-list-editor.js.flow +22 -0
  108. package/dist/components/tooltip.d.ts +32 -0
  109. package/dist/components/tooltip.js.flow +38 -0
  110. package/dist/components/visibility-observer/create-visibility-observer.d.ts +34 -0
  111. package/dist/components/visibility-observer/create-visibility-observer.js.flow +23 -0
  112. package/dist/components/visibility-observer/get-scroll-parent.d.ts +1 -0
  113. package/dist/components/visibility-observer/get-scroll-parent.js.flow +7 -0
  114. package/dist/components/visibility-observer/visibility-observer.d.ts +31 -0
  115. package/dist/components/visibility-observer/visibility-observer.js.flow +36 -0
  116. package/dist/components/zoomable-tex.d.ts +18 -0
  117. package/dist/components/zoomable-tex.js.flow +18 -0
  118. package/dist/components/zoomable.d.ts +64 -0
  119. package/dist/components/zoomable.js.flow +70 -0
  120. package/dist/components.d.ts +16 -0
  121. package/dist/components.js.flow +22 -0
  122. package/dist/definition-context.d.ts +21 -0
  123. package/dist/definition-context.js.flow +25 -0
  124. package/dist/dependencies.d.ts +3 -0
  125. package/dist/dependencies.js.flow +9 -0
  126. package/dist/error-boundary.d.ts +13 -0
  127. package/dist/error-boundary.js.flow +21 -0
  128. package/dist/es/index.js +23239 -26937
  129. package/dist/es/index.js.map +1 -1
  130. package/dist/extra-widgets.d.ts +22 -0
  131. package/dist/extra-widgets.js.flow +33 -0
  132. package/dist/hint-renderer.d.ts +23 -0
  133. package/dist/hint-renderer.js.flow +32 -0
  134. package/dist/hints-renderer.d.ts +29 -0
  135. package/dist/hints-renderer.js.flow +41 -0
  136. package/dist/icon-paths.d.ts +117 -0
  137. package/dist/icon-paths.js.flow +123 -0
  138. package/dist/index.d.ts +103 -2
  139. package/dist/index.js +13063 -16571
  140. package/dist/index.js.flow +133 -2
  141. package/dist/index.js.map +1 -1
  142. package/dist/init.d.ts +14 -0
  143. package/dist/init.js.flow +20 -0
  144. package/dist/interaction-tracker.d.ts +23 -0
  145. package/dist/interaction-tracker.js.flow +33 -0
  146. package/dist/interactive2/__tests__/movable-mock.d.ts +5 -0
  147. package/dist/interactive2/__tests__/movable-mock.js.flow +11 -0
  148. package/dist/interactive2/interactive-util.d.ts +31 -0
  149. package/dist/interactive2/interactive-util.js.flow +50 -0
  150. package/dist/interactive2/movable-helper-methods.d.ts +9 -0
  151. package/dist/interactive2/movable-helper-methods.js.flow +15 -0
  152. package/dist/interactive2/movable-line-options.d.ts +34 -0
  153. package/dist/interactive2/movable-line-options.js.flow +44 -0
  154. package/dist/interactive2/movable-line.d.ts +2 -0
  155. package/dist/interactive2/movable-line.js.flow +8 -0
  156. package/dist/interactive2/movable-point-options.d.ts +34 -0
  157. package/dist/interactive2/movable-point-options.js.flow +44 -0
  158. package/dist/interactive2/movable-point.d.ts +2 -0
  159. package/dist/interactive2/movable-point.js.flow +8 -0
  160. package/dist/interactive2/movable-polygon-options.d.ts +40 -0
  161. package/dist/interactive2/movable-polygon-options.js.flow +47 -0
  162. package/dist/interactive2/movable-polygon.d.ts +2 -0
  163. package/dist/interactive2/movable-polygon.js.flow +8 -0
  164. package/dist/interactive2/movable.d.ts +2 -0
  165. package/dist/interactive2/movable.js.flow +8 -0
  166. package/dist/interactive2/objective_.d.ts +13 -0
  167. package/dist/interactive2/objective_.js.flow +25 -0
  168. package/dist/interactive2/types.d.ts +2 -0
  169. package/dist/interactive2/types.js.flow +8 -0
  170. package/dist/interactive2/wrapped-defaults.d.ts +5 -0
  171. package/dist/interactive2/wrapped-defaults.js.flow +11 -0
  172. package/dist/interactive2/wrapped-ellipse.d.ts +3 -0
  173. package/dist/interactive2/wrapped-ellipse.js.flow +14 -0
  174. package/dist/interactive2/wrapped-line.d.ts +3 -0
  175. package/dist/interactive2/wrapped-line.js.flow +14 -0
  176. package/dist/interactive2/wrapped-path.d.ts +3 -0
  177. package/dist/interactive2/wrapped-path.js.flow +13 -0
  178. package/dist/interactive2.d.ts +10 -0
  179. package/dist/interactive2.js.flow +16 -0
  180. package/dist/item-renderer.d.ts +117 -0
  181. package/dist/item-renderer.js.flow +136 -0
  182. package/dist/item-version.d.ts +4 -0
  183. package/dist/item-version.js.flow +11 -0
  184. package/dist/jipt-paragraphs.d.ts +8 -0
  185. package/dist/jipt-paragraphs.js.flow +14 -0
  186. package/dist/keypad-context.d.ts +20 -0
  187. package/dist/keypad-context.js.flow +18 -0
  188. package/dist/loading-context.d.ts +14 -0
  189. package/dist/loading-context.js.flow +12 -0
  190. package/dist/logging/log.d.ts +55 -0
  191. package/dist/logging/log.js.flow +62 -0
  192. package/dist/mixins/changeable.d.ts +30 -0
  193. package/dist/mixins/changeable.js.flow +33 -0
  194. package/dist/mixins/editor-jsonify.d.ts +4 -0
  195. package/dist/mixins/editor-jsonify.js.flow +10 -0
  196. package/dist/mixins/provide-keypad.d.ts +36 -0
  197. package/dist/mixins/provide-keypad.js.flow +33 -0
  198. package/dist/mixins/widget-jsonify-deprecated.d.ts +5 -0
  199. package/dist/mixins/widget-jsonify-deprecated.js.flow +11 -0
  200. package/dist/mixins/widget-prop-denylist.d.ts +14 -0
  201. package/dist/mixins/widget-prop-denylist.js.flow +20 -0
  202. package/dist/multi-items/item-types.d.ts +32 -0
  203. package/dist/multi-items/items.d.ts +53 -0
  204. package/dist/multi-items/items.js.flow +66 -0
  205. package/dist/multi-items/multi-renderer.d.ts +152 -0
  206. package/dist/multi-items/multi-renderer.js.flow +213 -0
  207. package/dist/multi-items/prop-type-builders.d.ts +20 -0
  208. package/dist/multi-items/prop-type-builders.js.flow +22 -0
  209. package/dist/multi-items/shape-types.d.ts +47 -0
  210. package/dist/multi-items/shape-types.js.flow +60 -0
  211. package/dist/multi-items/shapes.d.ts +23 -0
  212. package/dist/multi-items/shapes.js.flow +25 -0
  213. package/dist/multi-items/tree-types.d.ts +34 -0
  214. package/dist/multi-items/tree-types.js.flow +45 -0
  215. package/dist/multi-items/trees.d.ts +146 -0
  216. package/dist/multi-items/trees.js.flow +147 -0
  217. package/dist/multi-items.d.ts +73 -0
  218. package/dist/multi-items.js.flow +36 -0
  219. package/dist/perseus-api.d.ts +128 -0
  220. package/dist/perseus-api.js.flow +139 -0
  221. package/dist/perseus-env.d.ts +3 -0
  222. package/dist/perseus-env.js.flow +6 -0
  223. package/dist/perseus-error.d.ts +11 -0
  224. package/dist/perseus-error.js.flow +16 -0
  225. package/dist/perseus-markdown.d.ts +11 -0
  226. package/dist/perseus-markdown.js.flow +20 -0
  227. package/dist/perseus-types.d.ts +776 -0
  228. package/dist/perseus-types.js.flow +1032 -0
  229. package/dist/question-paragraph.d.ts +11 -0
  230. package/dist/question-paragraph.js.flow +17 -0
  231. package/dist/renderability.d.ts +10 -0
  232. package/dist/renderability.js.flow +19 -0
  233. package/dist/renderer.d.ts +254 -0
  234. package/dist/renderer.js.flow +327 -0
  235. package/dist/server-item-renderer.d.ts +113 -0
  236. package/dist/server-item-renderer.js.flow +136 -0
  237. package/dist/sigfigs.d.ts +32 -0
  238. package/dist/sigfigs.js.flow +41 -0
  239. package/dist/styles/constants.d.ts +56 -0
  240. package/dist/styles/constants.js.flow +62 -0
  241. package/dist/styles/global-constants.d.ts +342 -0
  242. package/dist/styles/global-constants.js.flow +348 -0
  243. package/dist/styles/global-styles.d.ts +47 -0
  244. package/dist/styles/global-styles.js.flow +53 -0
  245. package/dist/styles/media-queries.d.ts +29 -0
  246. package/dist/styles/media-queries.js.flow +35 -0
  247. package/dist/styles/shared.d.ts +3 -0
  248. package/dist/styles/shared.js.flow +9 -0
  249. package/dist/tex-wrangler.d.ts +7 -0
  250. package/dist/tex-wrangler.js.flow +13 -0
  251. package/dist/translation-linter.d.ts +21 -0
  252. package/dist/translation-linter.js.flow +32 -0
  253. package/dist/traversal.d.ts +15 -0
  254. package/dist/traversal.js.flow +26 -0
  255. package/dist/types.d.ts +262 -0
  256. package/dist/types.js.flow +337 -0
  257. package/dist/util/a11y.d.ts +3 -0
  258. package/dist/util/a11y.js.flow +9 -0
  259. package/dist/util/answer-types.d.ts +34 -0
  260. package/dist/util/answer-types.js.flow +49 -0
  261. package/dist/util/colors.d.ts +89 -0
  262. package/dist/util/colors.js.flow +95 -0
  263. package/dist/util/fix-passage-refs.d.ts +2 -0
  264. package/dist/util/fix-passage-refs.js.flow +8 -0
  265. package/dist/util/graph-utils.d.ts +12 -0
  266. package/dist/util/graph-utils.js.flow +9 -0
  267. package/dist/util/graphie.d.ts +2 -0
  268. package/dist/util/graphie.js.flow +8 -0
  269. package/dist/util/interactive.d.ts +9 -0
  270. package/dist/util/interactive.js.flow +9 -0
  271. package/dist/util/katex-preprocess.d.ts +6 -0
  272. package/dist/util/katex-preprocess.js.flow +12 -0
  273. package/dist/util/math.d.ts +28 -0
  274. package/dist/util/math.js.flow +62 -0
  275. package/dist/util/mobile-native-utils.d.ts +7 -0
  276. package/dist/util/mobile-native-utils.js.flow +12 -0
  277. package/dist/util/react-render.d.ts +2 -0
  278. package/dist/util/react-render.js.flow +12 -0
  279. package/dist/util/register-all-widgets-for-testing.d.ts +1 -0
  280. package/dist/util/register-all-widgets-for-testing.js.flow +7 -0
  281. package/dist/util/scroll-utils.d.ts +10 -0
  282. package/dist/util/scroll-utils.js.flow +19 -0
  283. package/dist/util/sizing-utils.d.ts +9 -0
  284. package/dist/util/sizing-utils.js.flow +20 -0
  285. package/dist/util/tex.d.ts +6 -0
  286. package/dist/util/tex.js.flow +17 -0
  287. package/dist/util/url-utils.d.ts +6 -0
  288. package/dist/util/url-utils.js.flow +11 -0
  289. package/dist/util.d.ts +247 -0
  290. package/dist/util.js.flow +320 -0
  291. package/dist/version.d.ts +11 -0
  292. package/dist/version.js.flow +17 -0
  293. package/dist/widget-container.d.ts +27 -0
  294. package/dist/widget-container.js.flow +33 -0
  295. package/dist/widgets/__tests__/renderQuestion.d.ts +13 -0
  296. package/dist/widgets/__tests__/renderQuestion.js.flow +28 -0
  297. package/dist/widgets/categorizer.d.ts +55 -0
  298. package/dist/widgets/categorizer.js.flow +70 -0
  299. package/dist/widgets/cs-program.d.ts +48 -0
  300. package/dist/widgets/cs-program.js.flow +54 -0
  301. package/dist/widgets/definition.d.ts +42 -0
  302. package/dist/widgets/definition.js.flow +57 -0
  303. package/dist/widgets/dropdown.d.ts +51 -0
  304. package/dist/widgets/dropdown.js.flow +63 -0
  305. package/dist/widgets/explanation.d.ts +49 -0
  306. package/dist/widgets/explanation.js.flow +58 -0
  307. package/dist/widgets/expression.d.ts +90 -0
  308. package/dist/widgets/expression.js.flow +115 -0
  309. package/dist/widgets/graded-group-answer-bar.d.ts +13 -0
  310. package/dist/widgets/graded-group-answer-bar.js.flow +24 -0
  311. package/dist/widgets/graded-group-set.d.ts +52 -0
  312. package/dist/widgets/graded-group-set.js.flow +65 -0
  313. package/dist/widgets/graded-group.d.ts +66 -0
  314. package/dist/widgets/graded-group.js.flow +78 -0
  315. package/dist/widgets/grapher/util.d.ts +19 -0
  316. package/dist/widgets/grapher/util.js.flow +38 -0
  317. package/dist/widgets/grapher.d.ts +63 -0
  318. package/dist/widgets/grapher.js.flow +93 -0
  319. package/dist/widgets/group.d.ts +51 -0
  320. package/dist/widgets/group.js.flow +60 -0
  321. package/dist/widgets/iframe.d.ts +52 -0
  322. package/dist/widgets/iframe.js.flow +53 -0
  323. package/dist/widgets/image.d.ts +59 -0
  324. package/dist/widgets/image.js.flow +73 -0
  325. package/dist/widgets/input-number.d.ts +75 -0
  326. package/dist/widgets/input-number.js.flow +94 -0
  327. package/dist/widgets/interaction/arrow-picker.d.ts +12 -0
  328. package/dist/widgets/interaction/arrow-picker.js.flow +21 -0
  329. package/dist/widgets/interaction/color-picker.d.ts +14 -0
  330. package/dist/widgets/interaction/color-picker.js.flow +23 -0
  331. package/dist/widgets/interaction/dash-picker.d.ts +12 -0
  332. package/dist/widgets/interaction/dash-picker.js.flow +21 -0
  333. package/dist/widgets/interaction/element-container.d.ts +21 -0
  334. package/dist/widgets/interaction/element-container.js.flow +33 -0
  335. package/dist/widgets/interaction.d.ts +51 -0
  336. package/dist/widgets/interaction.js.flow +60 -0
  337. package/dist/widgets/interactive-graph.d.ts +180 -0
  338. package/dist/widgets/interactive-graph.js.flow +242 -0
  339. package/dist/widgets/label-image/answer-choices.d.ts +51 -0
  340. package/dist/widgets/label-image/answer-choices.js.flow +55 -0
  341. package/dist/widgets/label-image/marker.d.ts +28 -0
  342. package/dist/widgets/label-image/marker.js.flow +33 -0
  343. package/dist/widgets/label-image/types.d.ts +14 -0
  344. package/dist/widgets/label-image/types.js.flow +23 -0
  345. package/dist/widgets/label-image.d.ts +113 -0
  346. package/dist/widgets/label-image.js.flow +142 -0
  347. package/dist/widgets/lights-puzzle.d.ts +47 -0
  348. package/dist/widgets/lights-puzzle.js.flow +58 -0
  349. package/dist/widgets/matcher.d.ts +82 -0
  350. package/dist/widgets/matcher.js.flow +101 -0
  351. package/dist/widgets/matrix.d.ts +65 -0
  352. package/dist/widgets/matrix.js.flow +80 -0
  353. package/dist/widgets/measurer.d.ts +21 -0
  354. package/dist/widgets/measurer.js.flow +30 -0
  355. package/dist/widgets/molecule/molecule-drawing.d.ts +5 -0
  356. package/dist/widgets/molecule/molecule-drawing.js.flow +11 -0
  357. package/dist/widgets/molecule/molecule-layout.d.ts +82 -0
  358. package/dist/widgets/molecule/molecule-layout.js.flow +96 -0
  359. package/dist/widgets/molecule/smiles-parser.d.ts +19 -0
  360. package/dist/widgets/molecule/smiles-parser.js.flow +26 -0
  361. package/dist/widgets/molecule.d.ts +51 -0
  362. package/dist/widgets/molecule.js.flow +60 -0
  363. package/dist/widgets/number-line.d.ts +90 -0
  364. package/dist/widgets/number-line.js.flow +102 -0
  365. package/dist/widgets/numeric-input.d.ts +80 -0
  366. package/dist/widgets/numeric-input.js.flow +109 -0
  367. package/dist/widgets/orderer.d.ts +69 -0
  368. package/dist/widgets/orderer.js.flow +81 -0
  369. package/dist/widgets/passage/passage-markdown.d.ts +22 -0
  370. package/dist/widgets/passage/passage-markdown.js.flow +33 -0
  371. package/dist/widgets/passage-ref-target.d.ts +41 -0
  372. package/dist/widgets/passage-ref-target.js.flow +52 -0
  373. package/dist/widgets/passage-ref.d.ts +65 -0
  374. package/dist/widgets/passage-ref.js.flow +85 -0
  375. package/dist/widgets/passage.d.ts +125 -0
  376. package/dist/widgets/passage.js.flow +157 -0
  377. package/dist/widgets/plotter.d.ts +68 -0
  378. package/dist/widgets/plotter.js.flow +78 -0
  379. package/dist/widgets/radio/base-radio.d.ts +38 -0
  380. package/dist/widgets/radio/base-radio.js.flow +44 -0
  381. package/dist/widgets/radio/choice-a11y-text.d.ts +2 -0
  382. package/dist/widgets/radio/choice-a11y-text.js.flow +14 -0
  383. package/dist/widgets/radio/choice-icon/choice-icon.d.ts +16 -0
  384. package/dist/widgets/radio/choice-icon/choice-icon.js.flow +22 -0
  385. package/dist/widgets/radio/choice-icon/cross-out-line.d.ts +9 -0
  386. package/dist/widgets/radio/choice-icon/cross-out-line.js.flow +16 -0
  387. package/dist/widgets/radio/choice-icon/shared-styles.d.ts +5 -0
  388. package/dist/widgets/radio/choice-icon/shared-styles.js.flow +11 -0
  389. package/dist/widgets/radio/choice-none-above.d.ts +6 -0
  390. package/dist/widgets/radio/choice-none-above.js.flow +16 -0
  391. package/dist/widgets/radio/choice.d.ts +23 -0
  392. package/dist/widgets/radio/choice.js.flow +32 -0
  393. package/dist/widgets/radio/choice.jsx.fixture.d.ts +1 -0
  394. package/dist/widgets/radio/choice.jsx.fixture.js.flow +7 -0
  395. package/dist/widgets/radio/focus-ring.d.ts +12 -0
  396. package/dist/widgets/radio/focus-ring.js.flow +15 -0
  397. package/dist/widgets/radio/option-status.d.ts +17 -0
  398. package/dist/widgets/radio/option-status.js.flow +16 -0
  399. package/dist/widgets/radio/util.d.ts +6 -0
  400. package/dist/widgets/radio/util.js.flow +12 -0
  401. package/dist/widgets/radio/widget.d.ts +62 -0
  402. package/dist/widgets/radio/widget.js.flow +80 -0
  403. package/dist/widgets/radio.d.ts +22 -0
  404. package/dist/widgets/radio.js.flow +31 -0
  405. package/dist/widgets/reaction-diagram.d.ts +37 -0
  406. package/dist/widgets/reaction-diagram.js.flow +46 -0
  407. package/dist/widgets/sequence.d.ts +79 -0
  408. package/dist/widgets/sequence.js.flow +100 -0
  409. package/dist/widgets/simulator.d.ts +89 -0
  410. package/dist/widgets/simulator.js.flow +106 -0
  411. package/dist/widgets/sorter.d.ts +82 -0
  412. package/dist/widgets/sorter.js.flow +101 -0
  413. package/dist/widgets/table.d.ts +56 -0
  414. package/dist/widgets/table.js.flow +67 -0
  415. package/dist/widgets/transformer.d.ts +94 -0
  416. package/dist/widgets/transformer.js.flow +127 -0
  417. package/dist/widgets/unit.d.ts +77 -0
  418. package/dist/widgets/unit.js.flow +93 -0
  419. package/dist/widgets/video-transcript-link.d.ts +12 -0
  420. package/dist/widgets/video-transcript-link.js.flow +15 -0
  421. package/dist/widgets/video.d.ts +48 -0
  422. package/dist/widgets/video.js.flow +57 -0
  423. package/dist/widgets.d.ts +73 -0
  424. package/dist/widgets.js.flow +108 -0
  425. package/dist/zoom.d.ts +1 -0
  426. package/dist/zoom.js.flow +7 -0
  427. package/package.json +8 -8
  428. package/src/__stories__/hints-renderer.stories.tsx +58 -0
  429. package/src/__stories__/item-renderer.stories.tsx +29 -0
  430. package/src/__stories__/server-item-renderer.stories.tsx +29 -0
  431. package/src/__testdata__/graphie.testdata.ts +51 -0
  432. package/src/__testdata__/item-renderer.testdata.ts +160 -0
  433. package/src/__testdata__/renderer.testdata.ts +122 -0
  434. package/src/__tests__/a11y.test.ts +303 -0
  435. package/src/__tests__/item-renderer.test.tsx +891 -0
  436. package/src/__tests__/jipt-paragraphs.test.ts +32 -0
  437. package/src/__tests__/mock-asset-loading-widget.tsx +56 -0
  438. package/src/__tests__/mock-widget.tsx +33 -0
  439. package/src/__tests__/perseus-markdown.test.ts +764 -0
  440. package/src/__tests__/renderability.test.ts +480 -0
  441. package/src/__tests__/renderer-api.test.tsx +328 -0
  442. package/src/__tests__/renderer.test.tsx +1802 -0
  443. package/src/__tests__/server-item-renderer.test.tsx +518 -0
  444. package/src/__tests__/test-items/image-item.ts +24 -0
  445. package/src/__tests__/test-items/input-number-1-item.ts +26 -0
  446. package/src/__tests__/test-items/input-number-2-item.ts +38 -0
  447. package/src/__tests__/test-items/table-item.ts +29 -0
  448. package/src/__tests__/tex-wrangler.test.ts +82 -0
  449. package/src/__tests__/util.test.ts +40 -0
  450. package/src/a11y.ts +45 -0
  451. package/src/all-widgets.ts +9 -0
  452. package/src/article-renderer.tsx +215 -0
  453. package/src/asset-context.ts +30 -0
  454. package/src/basic-widgets.ts +16 -0
  455. package/src/components/__stories__/button-group.stories.tsx +59 -0
  456. package/src/components/__stories__/fixed-to-responsive.stories.tsx +143 -0
  457. package/src/components/__stories__/graph.stories.tsx +30 -0
  458. package/src/components/__stories__/graphie.stories.tsx +35 -0
  459. package/src/components/__stories__/hud.stories.tsx +39 -0
  460. package/src/components/__stories__/icon.stories.tsx +63 -0
  461. package/src/components/__stories__/image-loader.stories.tsx +62 -0
  462. package/src/components/__stories__/info-tip.stories.tsx +35 -0
  463. package/src/components/__stories__/inline-icon.stories.tsx +44 -0
  464. package/src/components/__stories__/input-with-examples.stories.tsx +57 -0
  465. package/src/components/__stories__/lint.stories.tsx +51 -0
  466. package/src/components/__stories__/math-input.stories.tsx +34 -0
  467. package/src/components/__stories__/math-output.stories.tsx +25 -0
  468. package/src/components/__stories__/multi-button-group.stories.tsx +70 -0
  469. package/src/components/__stories__/number-input.stories.tsx +43 -0
  470. package/src/components/__stories__/prop-check-box.stories.tsx +65 -0
  471. package/src/components/__stories__/range-input.stories.tsx +31 -0
  472. package/src/components/__stories__/simple-keypad-input.stories.tsx +29 -0
  473. package/src/components/__stories__/sortable.stories.tsx +77 -0
  474. package/src/components/__stories__/stub-tag-editor.stories.tsx +49 -0
  475. package/src/components/__stories__/svg-image.stories.tsx +82 -0
  476. package/src/components/__stories__/tex.stories.tsx +25 -0
  477. package/src/components/__stories__/text-buttons.stories.tsx +66 -0
  478. package/src/components/__stories__/text-input.stories.tsx +39 -0
  479. package/src/components/__stories__/text-list-editor.stories.tsx +34 -0
  480. package/src/components/__stories__/zoomable-tex.stories.tsx +39 -0
  481. package/src/components/__stories__/zoomable.stories.tsx +43 -0
  482. package/src/components/__testdata__/sorter.testdata.ts +19 -0
  483. package/src/components/__tests__/number-input.test.tsx +128 -0
  484. package/src/components/__tests__/sortable.test.tsx +234 -0
  485. package/src/components/__tests__/sorter.test.tsx +111 -0
  486. package/src/components/__tests__/zoomable.test.tsx +399 -0
  487. package/src/components/button-group.tsx +130 -0
  488. package/src/components/constants.ts +14 -0
  489. package/src/components/fixed-to-responsive.tsx +165 -0
  490. package/src/components/graph.tsx +421 -0
  491. package/src/components/graphie-classes.ts +153 -0
  492. package/src/components/graphie-movables.ts +338 -0
  493. package/src/components/graphie.tsx +357 -0
  494. package/src/components/highlighting/highlightable-content.tsx +243 -0
  495. package/src/components/highlighting/highlights.ts +207 -0
  496. package/src/components/highlighting/ranges.ts +304 -0
  497. package/src/components/highlighting/types.ts +73 -0
  498. package/src/components/highlighting/ui/highlight-renderer.tsx +175 -0
  499. package/src/components/highlighting/ui/highlight-set-renderer.tsx +188 -0
  500. package/src/components/highlighting/ui/highlight-tooltip.tsx +141 -0
  501. package/src/components/highlighting/ui/highlighting-ui.tsx +153 -0
  502. package/src/components/highlighting/ui/selection-tracker.tsx +189 -0
  503. package/src/components/highlighting/ui/types.ts +21 -0
  504. package/src/components/highlighting/ui/util.ts +165 -0
  505. package/src/components/highlighting/word-indexer.tsx +156 -0
  506. package/src/components/hud.tsx +178 -0
  507. package/src/components/icon.tsx +140 -0
  508. package/src/components/image-loader.tsx +176 -0
  509. package/src/components/info-tip/info-tip.tsx +144 -0
  510. package/src/components/info-tip.tsx +40 -0
  511. package/src/components/inline-icon.tsx +77 -0
  512. package/src/components/input-with-examples.tsx +249 -0
  513. package/src/components/lint.tsx +453 -0
  514. package/src/components/math-input.tsx +299 -0
  515. package/src/components/math-output.tsx +128 -0
  516. package/src/components/multi-button-group.tsx +135 -0
  517. package/src/components/number-input.tsx +250 -0
  518. package/src/components/prop-check-box.tsx +80 -0
  519. package/src/components/range-input.tsx +60 -0
  520. package/src/components/simple-keypad-input.tsx +88 -0
  521. package/src/components/sortable.tsx +914 -0
  522. package/src/components/stub-tag-editor.tsx +53 -0
  523. package/src/components/svg-image.tsx +777 -0
  524. package/src/components/tex-buttons.tsx +321 -0
  525. package/src/components/tex.tsx +67 -0
  526. package/src/components/text-input.tsx +128 -0
  527. package/src/components/text-list-editor.tsx +169 -0
  528. package/src/components/tooltip.tsx +392 -0
  529. package/src/components/visibility-observer/__stories__/visibility-observer.stories.tsx +143 -0
  530. package/src/components/visibility-observer/create-visibility-observer.ts +108 -0
  531. package/src/components/visibility-observer/get-scroll-parent.ts +46 -0
  532. package/src/components/visibility-observer/visibility-observer.tsx +124 -0
  533. package/src/components/zoomable-tex.tsx +78 -0
  534. package/src/components/zoomable.tsx +300 -0
  535. package/src/components.ts +16 -0
  536. package/src/definition-context.tsx +60 -0
  537. package/src/dependencies.ts +21 -0
  538. package/src/error-boundary.tsx +50 -0
  539. package/src/extra-widgets.ts +75 -0
  540. package/src/hint-renderer.tsx +149 -0
  541. package/src/hints-renderer.tsx +377 -0
  542. package/src/icon-paths.ts +119 -0
  543. package/src/index.ts +158 -0
  544. package/src/init.ts +54 -0
  545. package/src/interaction-tracker.ts +61 -0
  546. package/src/interactive2/__tests__/movable-mock.ts +43 -0
  547. package/src/interactive2/__tests__/movable-point.test.ts +145 -0
  548. package/src/interactive2/interactive-util.ts +155 -0
  549. package/src/interactive2/movable-helper-methods.ts +161 -0
  550. package/src/interactive2/movable-line-options.ts +419 -0
  551. package/src/interactive2/movable-line.ts +318 -0
  552. package/src/interactive2/movable-point-options.ts +212 -0
  553. package/src/interactive2/movable-point.tsx +593 -0
  554. package/src/interactive2/movable-polygon-options.ts +390 -0
  555. package/src/interactive2/movable-polygon.ts +368 -0
  556. package/src/interactive2/movable.ts +275 -0
  557. package/src/interactive2/objective_.ts +79 -0
  558. package/src/interactive2/types.ts +2 -0
  559. package/src/interactive2/wrapped-defaults.ts +87 -0
  560. package/src/interactive2/wrapped-ellipse.ts +94 -0
  561. package/src/interactive2/wrapped-line.ts +116 -0
  562. package/src/interactive2/wrapped-path.ts +51 -0
  563. package/src/interactive2.ts +73 -0
  564. package/src/item-renderer.tsx +514 -0
  565. package/src/item-version.ts +10 -0
  566. package/src/jipt-paragraphs.ts +49 -0
  567. package/src/jquery.mobile.vmouse.js +0 -1
  568. package/src/keypad-context.ts +34 -0
  569. package/src/loading-context.ts +21 -0
  570. package/src/logging/log.test.ts +39 -0
  571. package/src/logging/log.ts +97 -0
  572. package/src/mixins/__tests__/widget-prop-denylist.test.ts +21 -0
  573. package/src/mixins/changeable.ts +133 -0
  574. package/src/mixins/editor-jsonify.ts +13 -0
  575. package/src/mixins/provide-keypad.tsx +144 -0
  576. package/src/mixins/widget-jsonify-deprecated.ts +23 -0
  577. package/src/mixins/widget-prop-denylist.ts +48 -0
  578. package/src/multi-items/__stories__/multi-renderer.stories.tsx +87 -0
  579. package/src/multi-items/__testdata__/multi-renderer.testdata.ts +172 -0
  580. package/src/multi-items/__tests__/items.test.ts +250 -0
  581. package/src/multi-items/__tests__/multi-renderer.test.tsx +910 -0
  582. package/src/multi-items/__tests__/prop-type-builders.test.ts +133 -0
  583. package/src/multi-items/__tests__/shapes.test.ts +43 -0
  584. package/src/multi-items/__tests__/trees.test.ts +325 -0
  585. package/src/multi-items/item-types.js.flow +55 -0
  586. package/src/multi-items/item-types.ts +56 -0
  587. package/src/multi-items/items.ts +176 -0
  588. package/src/multi-items/multi-renderer.tsx +564 -0
  589. package/src/multi-items/prop-type-builders.ts +106 -0
  590. package/src/multi-items/shape-types.ts +52 -0
  591. package/src/multi-items/shapes.ts +52 -0
  592. package/src/multi-items/tree-types.ts +47 -0
  593. package/src/multi-items/trees.ts +360 -0
  594. package/src/multi-items.ts +75 -0
  595. package/src/perseus-api.tsx +188 -0
  596. package/src/perseus-env.ts +27 -0
  597. package/src/perseus-error.ts +17 -0
  598. package/src/perseus-markdown.tsx +407 -0
  599. package/src/perseus-types.ts +1474 -0
  600. package/src/question-paragraph.tsx +29 -0
  601. package/src/renderability.ts +109 -0
  602. package/src/renderer.tsx +1992 -0
  603. package/src/server-item-renderer.tsx +465 -0
  604. package/src/sigfigs.ts +490 -0
  605. package/src/styles/constants.ts +94 -0
  606. package/src/styles/global-constants.ts +343 -0
  607. package/src/styles/global-styles.ts +306 -0
  608. package/src/styles/media-queries.ts +47 -0
  609. package/src/styles/shared.ts +130 -0
  610. package/src/tex-wrangler.ts +182 -0
  611. package/src/translation-linter.ts +126 -0
  612. package/src/traversal.ts +138 -0
  613. package/src/types.ts +468 -0
  614. package/src/util/a11y.ts +23 -0
  615. package/src/util/answer-types.test.ts +114 -0
  616. package/src/util/answer-types.ts +876 -0
  617. package/src/util/colors.ts +93 -0
  618. package/src/util/fix-passage-refs.ts +121 -0
  619. package/src/util/graph-utils.ts +15 -0
  620. package/src/util/graphie.ts +1617 -0
  621. package/src/util/interactive.ts +4191 -0
  622. package/src/util/katex-preprocess.ts +14 -0
  623. package/src/util/math.ts +289 -0
  624. package/src/util/mobile-native-utils.test.ts +22 -0
  625. package/src/util/mobile-native-utils.ts +12 -0
  626. package/src/util/react-render.tsx +27 -0
  627. package/src/util/register-all-widgets-for-testing.ts +11 -0
  628. package/src/util/scroll-utils.ts +155 -0
  629. package/src/util/sizing-utils.ts +46 -0
  630. package/src/util/tex.ts +208 -0
  631. package/src/util/url-utils.ts +34 -0
  632. package/src/util.ts +1003 -0
  633. package/src/version.ts +11 -0
  634. package/src/widget-container.tsx +175 -0
  635. package/src/widgets/__stories__/categorizer.stories.tsx +14 -0
  636. package/src/widgets/__stories__/cs-program.stories.tsx +14 -0
  637. package/src/widgets/__stories__/definition.stories.tsx +111 -0
  638. package/src/widgets/__stories__/dropdown.stories.tsx +14 -0
  639. package/src/widgets/__stories__/explanation.stories.tsx +18 -0
  640. package/src/widgets/__stories__/expression.stories.tsx +29 -0
  641. package/src/widgets/__stories__/graded-group-set.stories.tsx +31 -0
  642. package/src/widgets/__stories__/graded-group.stories.tsx +31 -0
  643. package/src/widgets/__stories__/grapher.stories.tsx +60 -0
  644. package/src/widgets/__stories__/group.stories.tsx +14 -0
  645. package/src/widgets/__stories__/iframe.stories.tsx +14 -0
  646. package/src/widgets/__stories__/image.stories.tsx +61 -0
  647. package/src/widgets/__stories__/input-number.stories.tsx +110 -0
  648. package/src/widgets/__stories__/interaction.stories.tsx +14 -0
  649. package/src/widgets/__stories__/interactive-graph.stories.tsx +60 -0
  650. package/src/widgets/__stories__/matcher.stories.tsx +14 -0
  651. package/src/widgets/__stories__/matrix.stories.tsx +14 -0
  652. package/src/widgets/__stories__/number-line.stories.tsx +14 -0
  653. package/src/widgets/__stories__/numeric-input.stories.tsx +107 -0
  654. package/src/widgets/__stories__/on-screen-keypad.stories.tsx +120 -0
  655. package/src/widgets/__stories__/orderer.stories.tsx +14 -0
  656. package/src/widgets/__stories__/passage-ref.stories.tsx +22 -0
  657. package/src/widgets/__stories__/passage.stories.tsx +32 -0
  658. package/src/widgets/__stories__/radio.stories.tsx +88 -0
  659. package/src/widgets/__stories__/sorter.stories.tsx +14 -0
  660. package/src/widgets/__stories__/transformer.stories.tsx +14 -0
  661. package/src/widgets/__stories__/unit.stories.tsx +30 -0
  662. package/src/widgets/__stories__/video-transcript-link.stories.tsx +27 -0
  663. package/src/widgets/__stories__/video.stories.tsx +14 -0
  664. package/src/widgets/__testdata__/base-radio.testdata.ts +21 -0
  665. package/src/widgets/__testdata__/categorizer.testdata.ts +49 -0
  666. package/src/widgets/__testdata__/cs-program.testdata.ts +27 -0
  667. package/src/widgets/__testdata__/dropdown.testdata.ts +33 -0
  668. package/src/widgets/__testdata__/explanation.testdata.ts +55 -0
  669. package/src/widgets/__testdata__/expression.testdata.ts +94 -0
  670. package/src/widgets/__testdata__/graded-group-set.testdata.ts +356 -0
  671. package/src/widgets/__testdata__/graded-group.testdata.ts +77 -0
  672. package/src/widgets/__testdata__/grapher.testdata.ts +374 -0
  673. package/src/widgets/__testdata__/group.testdata.ts +155 -0
  674. package/src/widgets/__testdata__/iframe.testdata.ts +35 -0
  675. package/src/widgets/__testdata__/image.testdata.ts +36 -0
  676. package/src/widgets/__testdata__/input-number.testdata.ts +72 -0
  677. package/src/widgets/__testdata__/interaction.testdata.ts +262 -0
  678. package/src/widgets/__testdata__/interactive-graph.testdata.ts +644 -0
  679. package/src/widgets/__testdata__/matcher.testdata.ts +33 -0
  680. package/src/widgets/__testdata__/matrix.testdata.ts +28 -0
  681. package/src/widgets/__testdata__/number-line.testdata.ts +30 -0
  682. package/src/widgets/__testdata__/numeric-input.testdata.ts +266 -0
  683. package/src/widgets/__testdata__/orderer.testdata.ts +57 -0
  684. package/src/widgets/__testdata__/passage-ref.testdata.ts +101 -0
  685. package/src/widgets/__testdata__/passage.testdata.ts +95 -0
  686. package/src/widgets/__testdata__/radio.testdata.ts +261 -0
  687. package/src/widgets/__testdata__/sorter.testdata.ts +19 -0
  688. package/src/widgets/__testdata__/transformer.testdata.ts +108 -0
  689. package/src/widgets/__testdata__/video.testdata.ts +21 -0
  690. package/src/widgets/__tests__/__snapshots__/input-number.test.ts.snap +497 -0
  691. package/src/widgets/__tests__/__snapshots__/unit.test.tsx.snap +20 -0
  692. package/src/widgets/__tests__/categorizer.test.ts +203 -0
  693. package/src/widgets/__tests__/cs-program.test.ts +43 -0
  694. package/src/widgets/__tests__/definition.test.ts +136 -0
  695. package/src/widgets/__tests__/dropdown.test.ts +105 -0
  696. package/src/widgets/__tests__/explanation.test.ts +198 -0
  697. package/src/widgets/__tests__/expression.test.ts +431 -0
  698. package/src/widgets/__tests__/graded-group-set-jipt.test.ts +30 -0
  699. package/src/widgets/__tests__/graded-group-set.test.ts +288 -0
  700. package/src/widgets/__tests__/graded-group.test.ts +232 -0
  701. package/src/widgets/__tests__/grapher.cypress.ts +576 -0
  702. package/src/widgets/__tests__/grapher.test.ts +33 -0
  703. package/src/widgets/__tests__/group.test.tsx +512 -0
  704. package/src/widgets/__tests__/iframe.test.ts +45 -0
  705. package/src/widgets/__tests__/image.test.ts +62 -0
  706. package/src/widgets/__tests__/input-number.test.ts +354 -0
  707. package/src/widgets/__tests__/interaction.test.ts +31 -0
  708. package/src/widgets/__tests__/interactive-graph.cypress.ts +815 -0
  709. package/src/widgets/__tests__/interactive-graph.test.ts +106 -0
  710. package/src/widgets/__tests__/matcher.test.tsx +152 -0
  711. package/src/widgets/__tests__/matrix.test.ts +82 -0
  712. package/src/widgets/__tests__/number-line.test.ts +79 -0
  713. package/src/widgets/__tests__/numeric-input.test.ts +502 -0
  714. package/src/widgets/__tests__/orderer.test.ts +73 -0
  715. package/src/widgets/__tests__/passage-ref.test.ts +168 -0
  716. package/src/widgets/__tests__/passage.test.tsx +224 -0
  717. package/src/widgets/__tests__/radio/base-radio.test.tsx +470 -0
  718. package/src/widgets/__tests__/radio/choice-icon.test.tsx +90 -0
  719. package/src/widgets/__tests__/radio/choice.test.tsx +310 -0
  720. package/src/widgets/__tests__/radio/focus-ring.test.tsx +23 -0
  721. package/src/widgets/__tests__/radio/option-status.test.tsx +77 -0
  722. package/src/widgets/__tests__/radio.test.ts +807 -0
  723. package/src/widgets/__tests__/renderQuestion.tsx +77 -0
  724. package/src/widgets/__tests__/unit.test.tsx +168 -0
  725. package/src/widgets/__tests__/video.test.ts +53 -0
  726. package/src/widgets/categorizer.tsx +343 -0
  727. package/src/widgets/cs-program.tsx +227 -0
  728. package/src/widgets/definition.tsx +121 -0
  729. package/src/widgets/dropdown.tsx +142 -0
  730. package/src/widgets/explanation.tsx +317 -0
  731. package/src/widgets/expression.tsx +647 -0
  732. package/src/widgets/graded-group-answer-bar.tsx +142 -0
  733. package/src/widgets/graded-group-set.tsx +296 -0
  734. package/src/widgets/graded-group.tsx +466 -0
  735. package/src/widgets/grapher/util.tsx +803 -0
  736. package/src/widgets/grapher.tsx +674 -0
  737. package/src/widgets/group.tsx +196 -0
  738. package/src/widgets/iframe.tsx +195 -0
  739. package/src/widgets/image.tsx +309 -0
  740. package/src/widgets/input-number.tsx +413 -0
  741. package/src/widgets/interaction/arrow-picker.tsx +38 -0
  742. package/src/widgets/interaction/color-picker.tsx +69 -0
  743. package/src/widgets/interaction/dash-picker.tsx +45 -0
  744. package/src/widgets/interaction/element-container.tsx +101 -0
  745. package/src/widgets/interaction.tsx +826 -0
  746. package/src/widgets/interactive-graph.tsx +2921 -0
  747. package/src/widgets/label-image/__stories__/answer-choices.stories.tsx +98 -0
  748. package/src/widgets/label-image/__stories__/marker.stories.tsx +181 -0
  749. package/src/widgets/label-image/answer-choices.tsx +300 -0
  750. package/src/widgets/label-image/marker.tsx +364 -0
  751. package/src/widgets/label-image/types.ts +26 -0
  752. package/src/widgets/label-image.test.ts +840 -0
  753. package/src/widgets/label-image.tsx +849 -0
  754. package/src/widgets/lights-puzzle.tsx +359 -0
  755. package/src/widgets/matcher.tsx +278 -0
  756. package/src/widgets/matrix.tsx +637 -0
  757. package/src/widgets/measurer.tsx +217 -0
  758. package/src/widgets/molecule/__tests__/layout.test.ts +162 -0
  759. package/src/widgets/molecule/__tests__/parser.test.ts +155 -0
  760. package/src/widgets/molecule/molecule-drawing.ts +173 -0
  761. package/src/widgets/molecule/molecule-layout.ts +362 -0
  762. package/src/widgets/molecule/smiles-parser.ts +296 -0
  763. package/src/widgets/molecule.tsx +172 -0
  764. package/src/widgets/number-line.tsx +864 -0
  765. package/src/widgets/numeric-input.tsx +563 -0
  766. package/src/widgets/orderer.tsx +788 -0
  767. package/src/widgets/passage/__tests__/passage-markdown.test.ts +376 -0
  768. package/src/widgets/passage/passage-markdown.tsx +475 -0
  769. package/src/widgets/passage-ref-target.tsx +73 -0
  770. package/src/widgets/passage-ref.tsx +215 -0
  771. package/src/widgets/passage.tsx +584 -0
  772. package/src/widgets/plotter.tsx +1204 -0
  773. package/src/widgets/radio/__stories__/base-radio.stories.tsx +204 -0
  774. package/src/widgets/radio/__stories__/choice-icon.stories.tsx +168 -0
  775. package/src/widgets/radio/__stories__/choice-none-above.stories.tsx +40 -0
  776. package/src/widgets/radio/__stories__/choice.stories.tsx +137 -0
  777. package/src/widgets/radio/__stories__/focus-ring.stories.tsx +43 -0
  778. package/src/widgets/radio/__stories__/option-status.stories.tsx +105 -0
  779. package/src/widgets/radio/base-radio.tsx +509 -0
  780. package/src/widgets/radio/choice-a11y-text.ts +61 -0
  781. package/src/widgets/radio/choice-icon/choice-icon.tsx +217 -0
  782. package/src/widgets/radio/choice-icon/cross-out-line.tsx +53 -0
  783. package/src/widgets/radio/choice-icon/shared-styles.ts +15 -0
  784. package/src/widgets/radio/choice-none-above.tsx +56 -0
  785. package/src/widgets/radio/choice.jsx.fixture.ts +45 -0
  786. package/src/widgets/radio/choice.tsx +358 -0
  787. package/src/widgets/radio/focus-ring.tsx +57 -0
  788. package/src/widgets/radio/option-status.tsx +100 -0
  789. package/src/widgets/radio/util.test.ts +18 -0
  790. package/src/widgets/radio/util.ts +31 -0
  791. package/src/widgets/radio/widget.tsx +521 -0
  792. package/src/widgets/radio.ts +157 -0
  793. package/src/widgets/reaction-diagram.tsx +144 -0
  794. package/src/widgets/sequence.tsx +137 -0
  795. package/src/widgets/simulator.tsx +831 -0
  796. package/src/widgets/sorter.tsx +154 -0
  797. package/src/widgets/table.tsx +393 -0
  798. package/src/widgets/transformer.tsx +2951 -0
  799. package/src/widgets/unit.tsx +374 -0
  800. package/src/widgets/video-transcript-link.test.tsx +213 -0
  801. package/src/widgets/video-transcript-link.tsx +84 -0
  802. package/src/widgets/video.tsx +124 -0
  803. package/src/widgets.ts +444 -0
  804. package/src/zoom.ts +558 -0
  805. package/tsconfig.json +25 -0
  806. package/tsconfig.tsbuildinfo +1 -0
  807. package/src/__stories__/hints-renderer.stories.jsx +0 -60
  808. package/src/__stories__/item-renderer.stories.jsx +0 -27
  809. package/src/__stories__/server-item-renderer.stories.jsx +0 -27
  810. package/src/__testdata__/graphie_testdata.js +0 -53
  811. package/src/__testdata__/item-renderer_testdata.js +0 -161
  812. package/src/__testdata__/renderer_testdata.js +0 -123
  813. package/src/__tests__/a11y_test.jsx +0 -304
  814. package/src/__tests__/item-renderer_test.jsx +0 -892
  815. package/src/__tests__/jipt-paragraphs_test.js +0 -34
  816. package/src/__tests__/mock-asset-loading-widget.jsx +0 -57
  817. package/src/__tests__/mock-widget.jsx +0 -35
  818. package/src/__tests__/perseus-markdown_test.jsx +0 -747
  819. package/src/__tests__/renderability_test.jsx +0 -481
  820. package/src/__tests__/renderer-api_test.jsx +0 -309
  821. package/src/__tests__/renderer_test.jsx +0 -1792
  822. package/src/__tests__/server-item-renderer_test.jsx +0 -516
  823. package/src/__tests__/test-items/image-item.js +0 -25
  824. package/src/__tests__/test-items/input-number-1-item.js +0 -27
  825. package/src/__tests__/test-items/input-number-2-item.js +0 -39
  826. package/src/__tests__/test-items/table-item.js +0 -30
  827. package/src/__tests__/tex-wrangler_test.js +0 -83
  828. package/src/__tests__/util_test.js +0 -41
  829. package/src/a11y.js +0 -46
  830. package/src/all-widgets.js +0 -9
  831. package/src/article-renderer.jsx +0 -214
  832. package/src/asset-context.js +0 -30
  833. package/src/basic-widgets.js +0 -13
  834. package/src/components/__stories__/button-group.stories.jsx +0 -61
  835. package/src/components/__stories__/fixed-to-responsive.stories.jsx +0 -132
  836. package/src/components/__stories__/graph.stories.jsx +0 -27
  837. package/src/components/__stories__/graphie.stories.jsx +0 -32
  838. package/src/components/__stories__/hud.stories.jsx +0 -36
  839. package/src/components/__stories__/icon.stories.jsx +0 -56
  840. package/src/components/__stories__/image-loader.stories.jsx +0 -60
  841. package/src/components/__stories__/info-tip.stories.jsx +0 -33
  842. package/src/components/__stories__/inline-icon.stories.jsx +0 -39
  843. package/src/components/__stories__/input-with-examples.stories.jsx +0 -52
  844. package/src/components/__stories__/lint.stories.jsx +0 -40
  845. package/src/components/__stories__/math-input.stories.jsx +0 -29
  846. package/src/components/__stories__/math-output.stories.jsx +0 -24
  847. package/src/components/__stories__/multi-button-group.stories.jsx +0 -69
  848. package/src/components/__stories__/number-input.stories.jsx +0 -42
  849. package/src/components/__stories__/prop-check-box.stories.jsx +0 -62
  850. package/src/components/__stories__/range-input.stories.jsx +0 -28
  851. package/src/components/__stories__/simple-keypad-input.stories.jsx +0 -28
  852. package/src/components/__stories__/sortable.stories.jsx +0 -73
  853. package/src/components/__stories__/stub-tag-editor.stories.jsx +0 -44
  854. package/src/components/__stories__/svg-image.stories.jsx +0 -71
  855. package/src/components/__stories__/tex.stories.jsx +0 -24
  856. package/src/components/__stories__/text-buttons.stories.jsx +0 -51
  857. package/src/components/__stories__/text-input.stories.jsx +0 -34
  858. package/src/components/__stories__/text-list-editor.stories.jsx +0 -33
  859. package/src/components/__stories__/zoomable-tex.stories.jsx +0 -39
  860. package/src/components/__stories__/zoomable.stories.jsx +0 -40
  861. package/src/components/__testdata__/sorter_testdata.js +0 -20
  862. package/src/components/__tests__/number-input_test.jsx +0 -112
  863. package/src/components/__tests__/sortable_test.jsx +0 -234
  864. package/src/components/__tests__/sorter_test.jsx +0 -111
  865. package/src/components/__tests__/zoomable_test.jsx +0 -388
  866. package/src/components/button-group.jsx +0 -131
  867. package/src/components/constants.js +0 -15
  868. package/src/components/fixed-to-responsive.jsx +0 -165
  869. package/src/components/graph.jsx +0 -413
  870. package/src/components/graphie-classes.jsx +0 -148
  871. package/src/components/graphie-movables.jsx +0 -342
  872. package/src/components/graphie.jsx +0 -353
  873. package/src/components/highlighting/highlightable-content.jsx +0 -245
  874. package/src/components/highlighting/highlights.js +0 -208
  875. package/src/components/highlighting/ranges.js +0 -299
  876. package/src/components/highlighting/types.js +0 -73
  877. package/src/components/highlighting/ui/highlight-renderer.jsx +0 -179
  878. package/src/components/highlighting/ui/highlight-set-renderer.jsx +0 -190
  879. package/src/components/highlighting/ui/highlight-tooltip.jsx +0 -143
  880. package/src/components/highlighting/ui/highlighting-ui.jsx +0 -163
  881. package/src/components/highlighting/ui/selection-tracker.jsx +0 -184
  882. package/src/components/highlighting/ui/types.js +0 -21
  883. package/src/components/highlighting/ui/util.js +0 -166
  884. package/src/components/highlighting/word-indexer.jsx +0 -155
  885. package/src/components/hud.jsx +0 -179
  886. package/src/components/icon.jsx +0 -149
  887. package/src/components/image-loader.jsx +0 -175
  888. package/src/components/info-tip/info-tip.jsx +0 -146
  889. package/src/components/info-tip.jsx +0 -38
  890. package/src/components/inline-icon.jsx +0 -81
  891. package/src/components/input-with-examples.jsx +0 -248
  892. package/src/components/lint.jsx +0 -455
  893. package/src/components/math-input.jsx +0 -293
  894. package/src/components/math-output.jsx +0 -128
  895. package/src/components/multi-button-group.jsx +0 -137
  896. package/src/components/number-input.jsx +0 -253
  897. package/src/components/prop-check-box.jsx +0 -80
  898. package/src/components/range-input.jsx +0 -61
  899. package/src/components/simple-keypad-input.jsx +0 -88
  900. package/src/components/sortable.jsx +0 -872
  901. package/src/components/stub-tag-editor.jsx +0 -54
  902. package/src/components/svg-image.jsx +0 -765
  903. package/src/components/tex-buttons.jsx +0 -320
  904. package/src/components/tex.jsx +0 -70
  905. package/src/components/text-input.jsx +0 -133
  906. package/src/components/text-list-editor.jsx +0 -157
  907. package/src/components/tooltip.jsx +0 -392
  908. package/src/components/visibility-observer/__stories__/visibility-observer.stories.jsx +0 -140
  909. package/src/components/visibility-observer/get-scroll-parent.js +0 -45
  910. package/src/components/visibility-observer/visibility-observer.js +0 -109
  911. package/src/components/visibility-observer/visibility-observer.jsx +0 -123
  912. package/src/components/zoomable-tex.jsx +0 -70
  913. package/src/components/zoomable.jsx +0 -294
  914. package/src/components.js +0 -17
  915. package/src/definition-context.js +0 -59
  916. package/src/dependencies.js +0 -22
  917. package/src/error-boundary.jsx +0 -48
  918. package/src/extra-widgets.js +0 -75
  919. package/src/hint-renderer.jsx +0 -155
  920. package/src/hints-renderer.jsx +0 -367
  921. package/src/icon-paths.js +0 -120
  922. package/src/index.js +0 -159
  923. package/src/init.js +0 -49
  924. package/src/interaction-tracker.js +0 -60
  925. package/src/interactive2/__tests__/movable-mock.js +0 -48
  926. package/src/interactive2/__tests__/movable-point_test.js +0 -146
  927. package/src/interactive2/interactive-util.js +0 -152
  928. package/src/interactive2/movable-helper-methods.js +0 -160
  929. package/src/interactive2/movable-line-options.js +0 -392
  930. package/src/interactive2/movable-line.js +0 -322
  931. package/src/interactive2/movable-point-options.js +0 -190
  932. package/src/interactive2/movable-point.jsx +0 -597
  933. package/src/interactive2/movable-polygon-options.js +0 -351
  934. package/src/interactive2/movable-polygon.js +0 -368
  935. package/src/interactive2/movable.js +0 -273
  936. package/src/interactive2/objective_.js +0 -79
  937. package/src/interactive2/types.js +0 -3
  938. package/src/interactive2/wrapped-defaults.js +0 -86
  939. package/src/interactive2/wrapped-ellipse.js +0 -86
  940. package/src/interactive2/wrapped-line.js +0 -109
  941. package/src/interactive2/wrapped-path.js +0 -47
  942. package/src/interactive2.js +0 -91
  943. package/src/item-renderer.jsx +0 -514
  944. package/src/item-version.js +0 -12
  945. package/src/jipt-paragraphs.js +0 -52
  946. package/src/keypad-context.js +0 -33
  947. package/src/loading-context.js +0 -20
  948. package/src/logging/log.js +0 -107
  949. package/src/logging/log_test.js +0 -41
  950. package/src/mixins/__tests__/widget-prop-denylist_test.js +0 -23
  951. package/src/mixins/changeable.jsx +0 -115
  952. package/src/mixins/editor-jsonify.jsx +0 -13
  953. package/src/mixins/provide-keypad.jsx +0 -131
  954. package/src/mixins/widget-jsonify-deprecated.jsx +0 -23
  955. package/src/mixins/widget-prop-denylist.js +0 -49
  956. package/src/multi-items/__stories__/multi-renderer.stories.js +0 -88
  957. package/src/multi-items/__testdata__/multi-renderer_testdata.js +0 -174
  958. package/src/multi-items/__tests__/items_test.js +0 -245
  959. package/src/multi-items/__tests__/multi-renderer_test.js +0 -903
  960. package/src/multi-items/__tests__/prop-type-builders_test.js +0 -134
  961. package/src/multi-items/__tests__/shapes_test.js +0 -44
  962. package/src/multi-items/__tests__/trees_test.js +0 -316
  963. package/src/multi-items/items.js +0 -176
  964. package/src/multi-items/multi-renderer.jsx +0 -543
  965. package/src/multi-items/prop-type-builders.js +0 -105
  966. package/src/multi-items/shape-types.js +0 -47
  967. package/src/multi-items/shapes.js +0 -53
  968. package/src/multi-items/tree-types.js +0 -42
  969. package/src/multi-items/trees.js +0 -345
  970. package/src/multi-items.js +0 -76
  971. package/src/perseus-api.jsx +0 -221
  972. package/src/perseus-env.js +0 -26
  973. package/src/perseus-error.js +0 -19
  974. package/src/perseus-markdown.jsx +0 -408
  975. package/src/perseus-types.js +0 -1471
  976. package/src/question-paragraph.jsx +0 -30
  977. package/src/renderability.jsx +0 -108
  978. package/src/renderer.jsx +0 -1937
  979. package/src/server-item-renderer.jsx +0 -467
  980. package/src/sigfigs.jsx +0 -492
  981. package/src/styles/constants.js +0 -96
  982. package/src/styles/global-constants.js +0 -344
  983. package/src/styles/global-styles.js +0 -307
  984. package/src/styles/media-queries.js +0 -48
  985. package/src/styles/shared.js +0 -132
  986. package/src/tex-wrangler.js +0 -176
  987. package/src/translation-linter.js +0 -125
  988. package/src/traversal.jsx +0 -141
  989. package/src/types.js +0 -488
  990. package/src/util/a11y.js +0 -24
  991. package/src/util/answer-types.js +0 -861
  992. package/src/util/answer-types_test.js +0 -115
  993. package/src/util/colors.js +0 -94
  994. package/src/util/fix-passage-refs.jsx +0 -122
  995. package/src/util/graph-utils.js +0 -16
  996. package/src/util/graphie.js +0 -1586
  997. package/src/util/interactive.js +0 -4078
  998. package/src/util/katex-preprocess.js +0 -16
  999. package/src/util/math.js +0 -289
  1000. package/src/util/mobile-native-utils.js +0 -14
  1001. package/src/util/mobile-native-utils_test.js +0 -24
  1002. package/src/util/react-render.js +0 -27
  1003. package/src/util/register-all-widgets-for-testing.js +0 -12
  1004. package/src/util/scroll-utils.js +0 -162
  1005. package/src/util/sizing-utils.js +0 -46
  1006. package/src/util/tex.js +0 -203
  1007. package/src/util/url-utils.js +0 -36
  1008. package/src/util.js +0 -984
  1009. package/src/version.js +0 -11
  1010. package/src/widget-container.jsx +0 -178
  1011. package/src/widgets/__stories__/categorizer.stories.jsx +0 -15
  1012. package/src/widgets/__stories__/cs-program.stories.jsx +0 -15
  1013. package/src/widgets/__stories__/definition.stories.jsx +0 -108
  1014. package/src/widgets/__stories__/dropdown.stories.jsx +0 -15
  1015. package/src/widgets/__stories__/explanation.stories.jsx +0 -19
  1016. package/src/widgets/__stories__/expression.stories.jsx +0 -26
  1017. package/src/widgets/__stories__/graded-group-set.stories.jsx +0 -32
  1018. package/src/widgets/__stories__/graded-group.stories.jsx +0 -32
  1019. package/src/widgets/__stories__/grapher.stories.jsx +0 -47
  1020. package/src/widgets/__stories__/group.stories.jsx +0 -15
  1021. package/src/widgets/__stories__/iframe.stories.jsx +0 -15
  1022. package/src/widgets/__stories__/image.stories.jsx +0 -62
  1023. package/src/widgets/__stories__/input-number.stories.jsx +0 -102
  1024. package/src/widgets/__stories__/interaction.stories.jsx +0 -15
  1025. package/src/widgets/__stories__/interactive-graph.stories.jsx +0 -61
  1026. package/src/widgets/__stories__/matcher.stories.jsx +0 -15
  1027. package/src/widgets/__stories__/matrix.stories.jsx +0 -15
  1028. package/src/widgets/__stories__/number-line.stories.jsx +0 -15
  1029. package/src/widgets/__stories__/numeric-input.stories.jsx +0 -106
  1030. package/src/widgets/__stories__/on-screen-keypad.stories.jsx +0 -118
  1031. package/src/widgets/__stories__/orderer.stories.jsx +0 -15
  1032. package/src/widgets/__stories__/passage-ref.stories.jsx +0 -23
  1033. package/src/widgets/__stories__/passage.stories.jsx +0 -27
  1034. package/src/widgets/__stories__/radio.stories.jsx +0 -90
  1035. package/src/widgets/__stories__/sorter.stories.jsx +0 -15
  1036. package/src/widgets/__stories__/transformer.stories.jsx +0 -15
  1037. package/src/widgets/__stories__/unit.stories.jsx +0 -45
  1038. package/src/widgets/__stories__/video-transcript-link.stories.jsx +0 -24
  1039. package/src/widgets/__stories__/video.stories.jsx +0 -15
  1040. package/src/widgets/__testdata__/base-radio_testdata.js +0 -22
  1041. package/src/widgets/__testdata__/categorizer_testdata.js +0 -50
  1042. package/src/widgets/__testdata__/cs-program_testdata.js +0 -28
  1043. package/src/widgets/__testdata__/dropdown_testdata.js +0 -34
  1044. package/src/widgets/__testdata__/explanation_testdata.js +0 -56
  1045. package/src/widgets/__testdata__/expression_testdata.js +0 -95
  1046. package/src/widgets/__testdata__/graded-group-set_testdata.js +0 -358
  1047. package/src/widgets/__testdata__/graded-group_testdata.js +0 -79
  1048. package/src/widgets/__testdata__/grapher_testdata.js +0 -376
  1049. package/src/widgets/__testdata__/group_testdata.js +0 -156
  1050. package/src/widgets/__testdata__/iframe_testdata.js +0 -36
  1051. package/src/widgets/__testdata__/image_testdata.js +0 -38
  1052. package/src/widgets/__testdata__/input-number_testdata.js +0 -73
  1053. package/src/widgets/__testdata__/interaction_testdata.js +0 -264
  1054. package/src/widgets/__testdata__/interactive-graph_testdata.js +0 -646
  1055. package/src/widgets/__testdata__/matcher_testdata.js +0 -34
  1056. package/src/widgets/__testdata__/matrix_testdata.js +0 -29
  1057. package/src/widgets/__testdata__/number-line_testdata.js +0 -31
  1058. package/src/widgets/__testdata__/numeric-input_testdata.js +0 -266
  1059. package/src/widgets/__testdata__/orderer_testdata.js +0 -58
  1060. package/src/widgets/__testdata__/passage-ref_testdata.js +0 -103
  1061. package/src/widgets/__testdata__/passage_testdata.js +0 -96
  1062. package/src/widgets/__testdata__/radio_testdata.js +0 -262
  1063. package/src/widgets/__testdata__/sorter_testdata.js +0 -20
  1064. package/src/widgets/__testdata__/transformer_testdata.js +0 -109
  1065. package/src/widgets/__testdata__/video_testdata.js +0 -22
  1066. package/src/widgets/__tests__/__snapshots__/expression_test.jsx.snap +0 -38
  1067. package/src/widgets/__tests__/__snapshots__/input-number_test.jsx.snap +0 -978
  1068. package/src/widgets/__tests__/categorizer_test.jsx +0 -205
  1069. package/src/widgets/__tests__/cs-program_test.jsx +0 -45
  1070. package/src/widgets/__tests__/definition_test.js +0 -137
  1071. package/src/widgets/__tests__/dropdown_test.jsx +0 -106
  1072. package/src/widgets/__tests__/explanation_test.js +0 -199
  1073. package/src/widgets/__tests__/expression_test.jsx +0 -439
  1074. package/src/widgets/__tests__/graded-group-set-jipt_test.jsx +0 -32
  1075. package/src/widgets/__tests__/graded-group-set_test.jsx +0 -290
  1076. package/src/widgets/__tests__/graded-group_test.jsx +0 -233
  1077. package/src/widgets/__tests__/grapher.cypress.jsx +0 -546
  1078. package/src/widgets/__tests__/grapher_test.jsx +0 -35
  1079. package/src/widgets/__tests__/group_test.jsx +0 -512
  1080. package/src/widgets/__tests__/iframe_test.jsx +0 -47
  1081. package/src/widgets/__tests__/image_test.jsx +0 -65
  1082. package/src/widgets/__tests__/input-number_test.jsx +0 -360
  1083. package/src/widgets/__tests__/interaction_test.jsx +0 -33
  1084. package/src/widgets/__tests__/interactive-graph.cypress.jsx +0 -777
  1085. package/src/widgets/__tests__/interactive-graph_test.jsx +0 -105
  1086. package/src/widgets/__tests__/matcher_test.jsx +0 -157
  1087. package/src/widgets/__tests__/matrix_test.jsx +0 -84
  1088. package/src/widgets/__tests__/number-line_test.jsx +0 -81
  1089. package/src/widgets/__tests__/numeric-input_test.jsx +0 -514
  1090. package/src/widgets/__tests__/orderer_test.jsx +0 -75
  1091. package/src/widgets/__tests__/passage-ref_test.jsx +0 -166
  1092. package/src/widgets/__tests__/passage_test.jsx +0 -212
  1093. package/src/widgets/__tests__/radio/base-radio_test.jsx +0 -443
  1094. package/src/widgets/__tests__/radio/choice-icon_test.jsx +0 -91
  1095. package/src/widgets/__tests__/radio/choice_test.jsx +0 -311
  1096. package/src/widgets/__tests__/radio/focus-ring_test.jsx +0 -23
  1097. package/src/widgets/__tests__/radio/option-status_test.jsx +0 -65
  1098. package/src/widgets/__tests__/radio_test.jsx +0 -810
  1099. package/src/widgets/__tests__/renderQuestion.jsx +0 -66
  1100. package/src/widgets/__tests__/unit_test.jsx +0 -144
  1101. package/src/widgets/__tests__/video_test.jsx +0 -55
  1102. package/src/widgets/categorizer.jsx +0 -343
  1103. package/src/widgets/cs-program.jsx +0 -227
  1104. package/src/widgets/definition.jsx +0 -121
  1105. package/src/widgets/dropdown.jsx +0 -141
  1106. package/src/widgets/explanation.jsx +0 -315
  1107. package/src/widgets/expression.jsx +0 -652
  1108. package/src/widgets/graded-group-answer-bar.jsx +0 -152
  1109. package/src/widgets/graded-group-set.jsx +0 -296
  1110. package/src/widgets/graded-group.jsx +0 -453
  1111. package/src/widgets/grapher/util.jsx +0 -806
  1112. package/src/widgets/grapher.jsx +0 -663
  1113. package/src/widgets/group.jsx +0 -195
  1114. package/src/widgets/iframe.jsx +0 -198
  1115. package/src/widgets/image.jsx +0 -310
  1116. package/src/widgets/input-number.jsx +0 -412
  1117. package/src/widgets/interaction/arrow-picker.jsx +0 -39
  1118. package/src/widgets/interaction/color-picker.jsx +0 -69
  1119. package/src/widgets/interaction/dash-picker.jsx +0 -46
  1120. package/src/widgets/interaction/element-container.jsx +0 -97
  1121. package/src/widgets/interaction.jsx +0 -742
  1122. package/src/widgets/interactive-graph.jsx +0 -2820
  1123. package/src/widgets/label-image/__stories__/answer-choices.stories.jsx +0 -93
  1124. package/src/widgets/label-image/__stories__/marker.stories.jsx +0 -172
  1125. package/src/widgets/label-image/answer-choices.jsx +0 -311
  1126. package/src/widgets/label-image/marker.jsx +0 -365
  1127. package/src/widgets/label-image/types.js +0 -33
  1128. package/src/widgets/label-image.jsx +0 -841
  1129. package/src/widgets/label-image_test.js +0 -840
  1130. package/src/widgets/lights-puzzle.jsx +0 -356
  1131. package/src/widgets/matcher.jsx +0 -274
  1132. package/src/widgets/matrix.jsx +0 -632
  1133. package/src/widgets/measurer.jsx +0 -215
  1134. package/src/widgets/molecule/__tests__/layout_test.jsx +0 -159
  1135. package/src/widgets/molecule/__tests__/parser_test.jsx +0 -142
  1136. package/src/widgets/molecule/molecule-drawing.jsx +0 -175
  1137. package/src/widgets/molecule/molecule-layout.jsx +0 -367
  1138. package/src/widgets/molecule/smiles-parser.jsx +0 -300
  1139. package/src/widgets/molecule.jsx +0 -172
  1140. package/src/widgets/number-line.jsx +0 -858
  1141. package/src/widgets/numeric-input.jsx +0 -553
  1142. package/src/widgets/orderer.jsx +0 -756
  1143. package/src/widgets/passage/__tests__/passage-markdown_test.jsx +0 -373
  1144. package/src/widgets/passage/passage-markdown.jsx +0 -472
  1145. package/src/widgets/passage-ref-target.jsx +0 -73
  1146. package/src/widgets/passage-ref.jsx +0 -214
  1147. package/src/widgets/passage.jsx +0 -576
  1148. package/src/widgets/plotter.jsx +0 -1198
  1149. package/src/widgets/radio/__stories__/base-radio.stories.jsx +0 -189
  1150. package/src/widgets/radio/__stories__/choice-icon.stories.jsx +0 -166
  1151. package/src/widgets/radio/__stories__/choice-none-above.stories.jsx +0 -41
  1152. package/src/widgets/radio/__stories__/choice.stories.jsx +0 -138
  1153. package/src/widgets/radio/__stories__/focus-ring.stories.jsx +0 -44
  1154. package/src/widgets/radio/__stories__/option-status.stories.jsx +0 -104
  1155. package/src/widgets/radio/base-radio.jsx +0 -494
  1156. package/src/widgets/radio/choice-a11y-text.js +0 -62
  1157. package/src/widgets/radio/choice-icon/choice-icon.jsx +0 -214
  1158. package/src/widgets/radio/choice-icon/cross-out-line.jsx +0 -52
  1159. package/src/widgets/radio/choice-icon/shared-styles.js +0 -16
  1160. package/src/widgets/radio/choice-none-above.jsx +0 -63
  1161. package/src/widgets/radio/choice.jsx +0 -382
  1162. package/src/widgets/radio/choice.jsx.fixture.js +0 -46
  1163. package/src/widgets/radio/focus-ring.jsx +0 -58
  1164. package/src/widgets/radio/option-status.jsx +0 -98
  1165. package/src/widgets/radio/util.js +0 -32
  1166. package/src/widgets/radio/util_test.js +0 -19
  1167. package/src/widgets/radio/widget.jsx +0 -515
  1168. package/src/widgets/radio.jsx +0 -155
  1169. package/src/widgets/reaction-diagram.jsx +0 -137
  1170. package/src/widgets/sequence.jsx +0 -143
  1171. package/src/widgets/simulator.jsx +0 -806
  1172. package/src/widgets/sorter.jsx +0 -149
  1173. package/src/widgets/table.jsx +0 -397
  1174. package/src/widgets/transformer.jsx +0 -2876
  1175. package/src/widgets/unit.jsx +0 -372
  1176. package/src/widgets/video-transcript-link.jsx +0 -84
  1177. package/src/widgets/video-transcript-link_test.js +0 -211
  1178. package/src/widgets/video.jsx +0 -126
  1179. package/src/widgets.js +0 -431
  1180. package/src/zoom.js +0 -539
  1181. /package/{src/multi-items/item-types.js → dist/multi-items/item-types.js.flow} +0 -0
  1182. /package/dist/{strings.js → strings} +0 -0
  1183. /package/src/__tests__/__snapshots__/{item-renderer_test.jsx.snap → item-renderer.test.tsx.snap} +0 -0
  1184. /package/src/__tests__/__snapshots__/{perseus-markdown_test.jsx.snap → perseus-markdown.test.ts.snap} +0 -0
  1185. /package/src/__tests__/__snapshots__/{renderer_test.jsx.snap → renderer.test.tsx.snap} +0 -0
  1186. /package/src/__tests__/__snapshots__/{server-item-renderer_test.jsx.snap → server-item-renderer.test.tsx.snap} +0 -0
  1187. /package/src/components/__tests__/__snapshots__/{sortable_test.jsx.snap → sortable.test.tsx.snap} +0 -0
  1188. /package/src/components/__tests__/__snapshots__/{sorter_test.jsx.snap → sorter.test.tsx.snap} +0 -0
  1189. /package/src/multi-items/__tests__/__snapshots__/{multi-renderer_test.js.snap → multi-renderer.test.tsx.snap} +0 -0
  1190. /package/src/widgets/__tests__/__snapshots__/{categorizer_test.jsx.snap → categorizer.test.ts.snap} +0 -0
  1191. /package/src/widgets/__tests__/__snapshots__/{cs-program_test.jsx.snap → cs-program.test.ts.snap} +0 -0
  1192. /package/src/widgets/__tests__/__snapshots__/{definition_test.js.snap → definition.test.ts.snap} +0 -0
  1193. /package/src/widgets/__tests__/__snapshots__/{dropdown_test.jsx.snap → dropdown.test.ts.snap} +0 -0
  1194. /package/src/widgets/__tests__/__snapshots__/{explanation_test.js.snap → explanation.test.ts.snap} +0 -0
  1195. /package/src/widgets/__tests__/__snapshots__/{graded-group-set-jipt_test.jsx.snap → graded-group-set-jipt.test.ts.snap} +0 -0
  1196. /package/src/widgets/__tests__/__snapshots__/{graded-group-set_test.jsx.snap → graded-group-set.test.ts.snap} +0 -0
  1197. /package/src/widgets/__tests__/__snapshots__/{graded-group_test.jsx.snap → graded-group.test.ts.snap} +0 -0
  1198. /package/src/widgets/__tests__/__snapshots__/{grapher_test.jsx.snap → grapher.test.ts.snap} +0 -0
  1199. /package/src/widgets/__tests__/__snapshots__/{group_test.jsx.snap → group.test.tsx.snap} +0 -0
  1200. /package/src/widgets/__tests__/__snapshots__/{iframe_test.jsx.snap → iframe.test.ts.snap} +0 -0
  1201. /package/src/widgets/__tests__/__snapshots__/{image_test.jsx.snap → image.test.ts.snap} +0 -0
  1202. /package/src/widgets/__tests__/__snapshots__/{interaction_test.jsx.snap → interaction.test.ts.snap} +0 -0
  1203. /package/src/widgets/__tests__/__snapshots__/{interactive-graph_test.jsx.snap → interactive-graph.test.ts.snap} +0 -0
  1204. /package/src/widgets/__tests__/__snapshots__/{matcher_test.jsx.snap → matcher.test.tsx.snap} +0 -0
  1205. /package/src/widgets/__tests__/__snapshots__/{matrix_test.jsx.snap → matrix.test.ts.snap} +0 -0
  1206. /package/src/widgets/__tests__/__snapshots__/{number-line_test.jsx.snap → number-line.test.ts.snap} +0 -0
  1207. /package/src/widgets/__tests__/__snapshots__/{numeric-input_test.jsx.snap → numeric-input.test.ts.snap} +0 -0
  1208. /package/src/widgets/__tests__/__snapshots__/{orderer_test.jsx.snap → orderer.test.ts.snap} +0 -0
  1209. /package/src/widgets/__tests__/__snapshots__/{passage-ref_test.jsx.snap → passage-ref.test.ts.snap} +0 -0
  1210. /package/src/widgets/__tests__/__snapshots__/{passage_test.jsx.snap → passage.test.tsx.snap} +0 -0
  1211. /package/src/widgets/__tests__/__snapshots__/{radio_test.jsx.snap → radio.test.ts.snap} +0 -0
  1212. /package/src/widgets/__tests__/__snapshots__/{video_test.jsx.snap → video.test.ts.snap} +0 -0
@@ -0,0 +1,1992 @@
1
+ /* eslint-disable react/no-unsafe */
2
+ import * as PerseusLinter from "@khanacademy/perseus-linter";
3
+ import classNames from "classnames";
4
+ import $ from "jquery";
5
+ import * as React from "react";
6
+ import ReactDOM from "react-dom";
7
+ import _ from "underscore";
8
+
9
+ import AssetContext from "./asset-context";
10
+ import SvgImage from "./components/svg-image";
11
+ import TeX from "./components/tex";
12
+ import Zoomable from "./components/zoomable";
13
+ import ZoomableTeX from "./components/zoomable-tex";
14
+ import {DefinitionProvider} from "./definition-context";
15
+ import {getDependencies} from "./dependencies";
16
+ import ErrorBoundary from "./error-boundary";
17
+ import InteractionTracker from "./interaction-tracker";
18
+ import Objective from "./interactive2/objective_";
19
+ import JiptParagraphs from "./jipt-paragraphs";
20
+ import {Errors, Log} from "./logging/log";
21
+ import {ClassNames as ApiClassNames, ApiOptions} from "./perseus-api";
22
+ import {PerseusError} from "./perseus-error";
23
+ import PerseusMarkdown from "./perseus-markdown";
24
+ import QuestionParagraph from "./question-paragraph";
25
+ import TranslationLinter from "./translation-linter";
26
+ import Util from "./util";
27
+ import preprocessTex from "./util/katex-preprocess";
28
+ import WidgetContainer from "./widget-container";
29
+ import * as Widgets from "./widgets";
30
+
31
+ import type {PerseusRenderer, PerseusWidgetOptions} from "./perseus-types";
32
+ import type {
33
+ APIOptions,
34
+ APIOptionsWithDefaults,
35
+ FilterCriterion,
36
+ FocusPath,
37
+ LinterContextProps,
38
+ PerseusDependencies,
39
+ PerseusScore,
40
+ WidgetInfo,
41
+ WidgetProps,
42
+ } from "./types";
43
+
44
+ import "./styles/perseus-renderer.less";
45
+
46
+ const {mapObject, mapObjectFromArray} = Objective;
47
+
48
+ const rContainsNonWhitespace = /\S/;
49
+ const rImageURL = /(web\+graphie|https):\/\/[^\s]*/;
50
+
51
+ const noopOnRender = () => {};
52
+
53
+ const SHOULD_CLEAR_WIDGETS_PROP_LIST = ["content", "problemNum", "widgets"];
54
+
55
+ // Check if one focus path / id path is a prefix of another
56
+ // The focus path null will never be a prefix of any non-null
57
+ // path, since it represents no focus.
58
+ // Otherwise, prefix is calculated by whether every array
59
+ // element in the prefix is present in the same position in the
60
+ // wholeArray path.
61
+ const isIdPathPrefix = function (
62
+ prefixArray: FocusPath,
63
+ wholeArray: FocusPath,
64
+ ) {
65
+ if (prefixArray === null || wholeArray === null) {
66
+ return prefixArray === wholeArray;
67
+ }
68
+
69
+ // @ts-expect-error [FEI-5003] - TS2345 - Argument of type 'readonly string[] | undefined' is not assignable to parameter of type 'Collection<any>'.
70
+ return _.every(prefixArray, (elem: string, i: number) => {
71
+ if (wholeArray != null) {
72
+ return _.isEqual(elem, wholeArray[i]);
73
+ }
74
+ });
75
+ };
76
+
77
+ type WidgetState = {
78
+ isMobile?: boolean;
79
+ inTable?: boolean;
80
+ key?: number;
81
+ paragraphIndex?: number;
82
+ foundFullWidth?: boolean;
83
+ baseElements?: any;
84
+ };
85
+
86
+ type SetWidgetPropsFn = (
87
+ id: string,
88
+ newProps: any,
89
+ cb: () => boolean,
90
+ // Widgets can call `onChange` with `silent` set to `true` to prevent
91
+ // interaction events from being triggered in listeners.
92
+ silent?: boolean,
93
+ ) => void;
94
+
95
+ // The return type for getUserInput. Widgets have full control of what is
96
+ // returned so it's not easily typed (some widgets return a scalar (string),
97
+ // some return a custom-built object, some delegate to
98
+ // `WidgetJsonifyDeprecated` which returns an object containing widget props
99
+ // (filtered by deprecated keys)).
100
+ type WidgetUserInput = any;
101
+
102
+ type SerializedState = {
103
+ [id: string]: any;
104
+ };
105
+
106
+ export type Widget = {
107
+ focus: () =>
108
+ | {
109
+ id: string;
110
+ path: FocusPath;
111
+ }
112
+ | boolean;
113
+ getDOMNodeForPath: (path: FocusPath) => Element | Text | null;
114
+ deselectIncorrectSelectedChoices?: () => void;
115
+ restoreSerializedState: (props: any, callback: () => void) => any;
116
+ // TODO(jeremy): I think this return value is wrong. The widget
117
+ // getSerializedState should just return _its_ serialized state, not a
118
+ // key/value list of all widget states (i think!)
119
+ // Returns widget state that can be passed back to `restoreSerializedState`
120
+ // to put the widget back into exactly the same state. If the widget does
121
+ // not implement this function, the renderer simply returns all of the
122
+ // widget's props.
123
+ getSerializedState?: () => SerializedState; // SUSPECT,
124
+ getGrammarTypeForPath: (path: FocusPath) => string;
125
+ blurInputPath?: (path: FocusPath) => null;
126
+ focusInputPath?: (path: FocusPath) => null;
127
+ getInputPaths?: () => ReadonlyArray<FocusPath>;
128
+ setInputValue?: (
129
+ path: FocusPath,
130
+ newValue: string,
131
+ // TODO(jeremy): I think this is actually a callback
132
+ focus: () => unknown,
133
+ ) => void;
134
+ getUserInput?: () => WidgetUserInput | null | undefined;
135
+ simpleValidate?: (
136
+ options?: any,
137
+ onOutputError?: (
138
+ widgetId: any,
139
+ value: string,
140
+ message?: string | null | undefined,
141
+ ) => unknown | null | undefined,
142
+ ) => PerseusScore;
143
+ showRationalesForCurrentlySelectedChoices?: (options?: any) => void;
144
+ examples?: () => ReadonlyArray<string>;
145
+ };
146
+
147
+ type Props = {
148
+ apiOptions?: APIOptions;
149
+ alwaysUpdate?: boolean;
150
+ findExternalWidgets: any;
151
+ highlightedWidgets?: ReadonlyArray<any>;
152
+ images: PerseusRenderer["images"];
153
+ keypadElement?: any; // TODO(kevinb): add proper flow types,
154
+ onInteractWithWidget: (id: string) => void;
155
+ onRender: (node?: any) => void;
156
+ problemNum?: number;
157
+ questionCompleted?: boolean;
158
+ reviewMode?: boolean | null | undefined;
159
+ content: PerseusRenderer["content"];
160
+ serializedState?: any;
161
+ // Callback which is called when serialized state changes with the new
162
+ // serialized state.
163
+ onSerializedStateUpdated: (serializedState: {
164
+ [key: string]: any;
165
+ }) => unknown;
166
+ // If linterContext.highlightLint is true, then content will be passed
167
+ // to the linter and any warnings will be highlighted in the rendered
168
+ // output.
169
+ linterContext: LinterContextProps;
170
+ legacyPerseusLint?: ReadonlyArray<string>;
171
+ widgets: PerseusRenderer["widgets"];
172
+ };
173
+
174
+ type State = {
175
+ translationLintErrors: ReadonlyArray<string>;
176
+ widgetInfo: Readonly<{
177
+ [id: string]: WidgetInfo | null | undefined;
178
+ }>;
179
+ widgetProps: Readonly<{
180
+ [id: string]: any | null | undefined;
181
+ }>;
182
+ jiptContent: any;
183
+ lastUsedWidgetId: string | null | undefined;
184
+ };
185
+
186
+ type Context = LinterContextProps & {
187
+ content: string;
188
+ widgets: {
189
+ [id: string]: any;
190
+ };
191
+ // This is inexact because LinterContextProps is inexact
192
+ };
193
+
194
+ type DefaultProps = {
195
+ alwaysUpdate: Props["alwaysUpdate"];
196
+ content: Props["content"];
197
+ findExternalWidgets: Props["findExternalWidgets"];
198
+ highlightedWidgets: Props["highlightedWidgets"];
199
+ images: Props["images"];
200
+ linterContext: Props["linterContext"];
201
+ onInteractWithWidget: Props["onInteractWithWidget"];
202
+ onRender: Props["onRender"];
203
+ onSerializedStateUpdated: Props["onSerializedStateUpdated"];
204
+ questionCompleted: Props["questionCompleted"];
205
+ reviewMode: Props["reviewMode"];
206
+ serializedState: Props["serializedState"];
207
+ widgets: Props["widgets"];
208
+ };
209
+
210
+ class Renderer extends React.Component<Props, State> {
211
+ _currentFocus: FocusPath | null | undefined;
212
+ // @ts-expect-error [FEI-5003] - TS2564 - Property '_foundTextNodes' has no initializer and is not definitely assigned in the constructor.
213
+ _foundTextNodes: boolean;
214
+ // @ts-expect-error [FEI-5003] - TS2564 - Property '_interactionTrackers' has no initializer and is not definitely assigned in the constructor.
215
+ _interactionTrackers: {
216
+ [id: string]: InteractionTracker;
217
+ };
218
+ // @ts-expect-error [FEI-5003] - TS2564 - Property '_isMounted' has no initializer and is not definitely assigned in the constructor.
219
+ _isMounted: boolean;
220
+ // @ts-expect-error [FEI-5003] - TS2564 - Property '_isTwoColumn' has no initializer and is not definitely assigned in the constructor.
221
+ _isTwoColumn: boolean;
222
+
223
+ // The i18n linter.
224
+ _translationLinter: TranslationLinter;
225
+
226
+ // @ts-expect-error [FEI-5003] - TS2564 - Property 'lastRenderedMarkdown' has no initializer and is not definitely assigned in the constructor.
227
+ lastRenderedMarkdown: React.ReactElement<
228
+ React.ComponentProps<PerseusDependencies["KatexProvider"]>
229
+ >;
230
+ // @ts-expect-error [FEI-5003] - TS2564 - Property 'reuseMarkdown' has no initializer and is not definitely assigned in the constructor.
231
+ reuseMarkdown: boolean;
232
+ // @ts-expect-error [FEI-5003] - TS2564 - Property 'translationIndex' has no initializer and is not definitely assigned in the constructor.
233
+ translationIndex: number;
234
+ // @ts-expect-error [FEI-5003] - TS2564 - Property 'widgetIds' has no initializer and is not definitely assigned in the constructor.
235
+ widgetIds: Array<string>;
236
+
237
+ static defaultProps: DefaultProps = {
238
+ content: "",
239
+ widgets: {},
240
+ images: {},
241
+ highlightedWidgets: [],
242
+ questionCompleted: false,
243
+ // onRender may be called multiple times per render, for example
244
+ // if there are multiple images or TeX pieces within `content`.
245
+ // It is a good idea to debounce any functions passed here.
246
+ onRender: noopOnRender,
247
+ onInteractWithWidget: function () {},
248
+ findExternalWidgets: () => [],
249
+ alwaysUpdate: false,
250
+ reviewMode: false,
251
+ serializedState: null,
252
+ onSerializedStateUpdated: () => {},
253
+ linterContext: PerseusLinter.linterContextDefault,
254
+ };
255
+
256
+ constructor(props: Props, context: Context) {
257
+ super(props, context);
258
+ this._translationLinter = new TranslationLinter();
259
+
260
+ this.state = {
261
+ jiptContent: null,
262
+
263
+ // TranslationLinter is async and currently does not contain a
264
+ // location. This is a list of error strings TranslationLinter
265
+ // detected on its last run.
266
+ translationLintErrors: [],
267
+
268
+ // The ID of the last widget the user interacted with. We'll
269
+ // use this to set the `isLastUsedWidget` flag on the
270
+ // corresponding widget.
271
+ lastUsedWidgetId: null,
272
+
273
+ ...this._getInitialWidgetState(props),
274
+ };
275
+ }
276
+
277
+ componentDidMount() {
278
+ this._isMounted = true;
279
+
280
+ // figure out why we're passing an empty object
281
+ // @ts-expect-error [FEI-5003] - TS2345 - Argument of type '{}' is not assignable to parameter of type 'Props'.
282
+ this.handleRender({});
283
+ this._currentFocus = null;
284
+
285
+ // TODO(emily): actually make the serializedState prop work like a
286
+ // controlled prop, instead of manually calling .restoreSerializedState
287
+ // at the right times.
288
+ if (this.props.serializedState) {
289
+ this.restoreSerializedState(this.props.serializedState);
290
+ }
291
+
292
+ if (this.props.linterContext.highlightLint) {
293
+ // Get i18n lint errors asynchronously. If there are lint errors,
294
+ // this component will be rerendered.
295
+ this._translationLinter.runLinter(
296
+ this.props.content,
297
+ this.handletranslationLintErrors,
298
+ );
299
+ }
300
+ }
301
+
302
+ UNSAFE_componentWillReceiveProps(nextProps: Props) {
303
+ if (
304
+ !_.isEqual(
305
+ _.pick(this.props, SHOULD_CLEAR_WIDGETS_PROP_LIST),
306
+ _.pick(nextProps, SHOULD_CLEAR_WIDGETS_PROP_LIST),
307
+ )
308
+ ) {
309
+ this.setState(this._getInitialWidgetState(nextProps));
310
+ }
311
+ }
312
+
313
+ shouldComponentUpdate(nextProps: Props, nextState: State): any | boolean {
314
+ // QUESTION(jeremy): Shouldn't we look at `nextProps` here? Otherwise
315
+ // we're always looking "one render behind".
316
+ if (this.props.alwaysUpdate) {
317
+ // TOTAL hacks so that findWidgets doesn't break
318
+ // when one widget updates without the other.
319
+ // See passage-refs inside radios, which was why
320
+ // this was introduced.
321
+ // I'm sorry!
322
+ // TODO(aria): cry
323
+ //
324
+ // HACK(djf): I've also set this alwaysUpdate property from
325
+ // async-renderer.jsx in the manticore-package. I'm doing this
326
+ // to work around an infinite loop of some sort that started
327
+ // happening in manticore after the React 16 update. After
328
+ // clicking around in the manticore exercise editor (for example)
329
+ // the UI would freeze up, and the debugger would show that
330
+ // we were always deep in a recursion on the propsChanged line
331
+ // below. There is some kind of timing issue causing some kind
332
+ // of infinite loop, but by avoiding the time-consuming deep
333
+ // equal comparisons on our props (which are often huge) I can
334
+ // no longer reproduce the bug.
335
+ // TODO(djf): Remove this comment
336
+ // https://khanacademy.atlassian.net/browse/CP-834 is resolved.
337
+ return true;
338
+ }
339
+ const stateChanged = !_.isEqual(this.state, nextState);
340
+ const propsChanged = !_.isEqual(this.props, nextProps);
341
+ return propsChanged || stateChanged;
342
+ }
343
+
344
+ UNSAFE_componentWillUpdate(nextProps: Props, nextState: State) {
345
+ const oldJipt = this.shouldRenderJiptPlaceholder(
346
+ this.props,
347
+ this.state,
348
+ );
349
+ const newJipt = this.shouldRenderJiptPlaceholder(nextProps, nextState);
350
+ const oldContent = this.getContent(this.props, this.state);
351
+ const newContent = this.getContent(nextProps, nextState);
352
+ const oldHighlightedWidgets = this.props.highlightedWidgets;
353
+ const newHighlightedWidgets = nextProps.highlightedWidgets;
354
+
355
+ // TODO(jared): This seems to be a perfect overlap with
356
+ // "shouldComponentUpdate" -- can we just remove this
357
+ // componentWillUpdate and the reuseMarkdown attr?
358
+ this.reuseMarkdown =
359
+ !oldJipt &&
360
+ !newJipt &&
361
+ oldContent === newContent &&
362
+ _.isEqual(
363
+ this.state.translationLintErrors,
364
+ nextState.translationLintErrors,
365
+ ) &&
366
+ // If we are running the linter then we need to know when
367
+ // widgets have changed because we need for force the linter to
368
+ // run when that happens. Note: don't do identity comparison here:
369
+ // it can cause frequent re-renders that break MathJax somehow
370
+ (!this.props.linterContext.highlightLint ||
371
+ _.isEqual(this.props.widgets, nextProps.widgets)) &&
372
+ // If the linter is turned on or off, we have to rerender
373
+ this.props.linterContext.highlightLint ===
374
+ nextProps.linterContext.highlightLint &&
375
+ // yes, this is identity array comparison, but these are passed
376
+ // in from state in the item-renderer, so they should be
377
+ // identity equal unless something changed, and it's expensive
378
+ // to loop through them to look for differences.
379
+ // Technically, we could reuse the markdown when this changes,
380
+ // but to do that we'd have to do more expensive checking of
381
+ // whether a widget should be highlighted in the common case
382
+ // where this array hasn't changed, so we just redo the whole
383
+ // render if this changed
384
+ oldHighlightedWidgets === newHighlightedWidgets;
385
+ }
386
+
387
+ componentDidUpdate(prevProps: Props, prevState: State) {
388
+ this.handleRender(prevProps);
389
+ // We even do this if we did reuse the markdown because
390
+ // we might need to update the widget props on this render,
391
+ // even though we have the same widgets.
392
+ // WidgetContainers don't update their widgets' props when
393
+ // they are re-rendered, so even if they've been
394
+ // re-rendered we need to call these methods on them.
395
+ _.each(this.widgetIds, (id) => {
396
+ // eslint-disable-next-line react/no-string-refs
397
+ const container = this.refs["container:" + id];
398
+ // @ts-expect-error [FEI-5003] - TS2339 - Property 'replaceWidgetProps' does not exist on type 'ReactInstance'.
399
+ container && container.replaceWidgetProps(this.getWidgetProps(id));
400
+ });
401
+
402
+ if (
403
+ this.props.serializedState &&
404
+ !_.isEqual(this.props.serializedState, this.getSerializedState())
405
+ ) {
406
+ this.restoreSerializedState(this.props.serializedState);
407
+ }
408
+
409
+ if (this.props.linterContext.highlightLint) {
410
+ // Get i18n lint errors asynchronously. If lint errors have changed
411
+ // since the last run, this component will be rerendered.
412
+ this._translationLinter.runLinter(
413
+ this.props.content,
414
+ this.handletranslationLintErrors,
415
+ );
416
+ }
417
+ }
418
+
419
+ componentWillUnmount() {
420
+ // Clean out the list of widgetIds when unmounting, as this list is
421
+ // meant to be consistent with the refs controlled by the renderer, and
422
+ // refs are also cleared out during unmounting.
423
+ // (This may not be totally necessary, but mobile clients have been
424
+ // seeing JS errors due to an inconsistency between the list of
425
+ // widgetIds and the child refs of the renderer.
426
+ // See: https://phabricator.khanacademy.org/D32420.)
427
+ this.widgetIds = [];
428
+
429
+ if (this.translationIndex != null) {
430
+ // NOTE(jeremy): Since the translationIndex is simply the array
431
+ // index of each renderer, we can't remove Renderers from this
432
+ // list, rather, we simply null out the entry (which means that
433
+ // this array's growth is unbounded until a page reload).
434
+ getDependencies().rendererTranslationComponents.removeComponentAtIndex(
435
+ this.translationIndex,
436
+ );
437
+ }
438
+
439
+ this._isMounted = false;
440
+ }
441
+
442
+ getApiOptions: () => APIOptionsWithDefaults = () => {
443
+ return {
444
+ ...ApiOptions.defaults,
445
+ ...this.props.apiOptions,
446
+ };
447
+ };
448
+
449
+ _getInitialWidgetState: (props: Props) => {
450
+ widgetInfo: State["widgetInfo"];
451
+ widgetProps: State["widgetProps"];
452
+ } = (props: Props) => {
453
+ const allWidgetInfo = this._getAllWidgetsInfo(props);
454
+ return {
455
+ widgetInfo: allWidgetInfo,
456
+ widgetProps: this._getAllWidgetsStartProps(allWidgetInfo, props),
457
+ };
458
+ };
459
+
460
+ // @ts-expect-error [FEI-5003] - TS2322 - Type '(props: Props) => Partial<Record<string, CategorizerWidget | CSProgramWidget | DefinitionWidget | DropdownWidget | ... 35 more ... | VideoWidget>>' is not assignable to type '(props: Props) => { [key: string]: PerseusWidget; }'.
461
+ _getAllWidgetsInfo: (props: Props) => {
462
+ [key: string]: WidgetInfo;
463
+ } = (props: Props) => {
464
+ // @ts-expect-error [FEI-5003] - TS2345 - Argument of type '{ [key: string]: PerseusWidget; }' is not assignable to parameter of type 'Partial<Record<string, CategorizerWidget>>'.
465
+ return mapObject(props.widgets, (widgetInfo, widgetId) => {
466
+ if (!widgetInfo.type || !widgetInfo.alignment) {
467
+ const newValues: Record<string, any> = {};
468
+
469
+ if (!widgetInfo.type) {
470
+ newValues.type = widgetId.split(" ")[0];
471
+ }
472
+ if (!widgetInfo.alignment) {
473
+ newValues.alignment = "default";
474
+ }
475
+
476
+ widgetInfo = _.extend({}, widgetInfo, newValues);
477
+ }
478
+ return Widgets.upgradeWidgetInfoToLatestVersion(widgetInfo);
479
+ });
480
+ };
481
+
482
+ _getAllWidgetsStartProps: (
483
+ allWidgetInfo: {
484
+ [key: string]: WidgetInfo;
485
+ },
486
+ props: Props,
487
+ ) => any = (allWidgetInfo, props) => {
488
+ // @ts-expect-error [FEI-5003] - TS2345 - Argument of type '{ [key: string]: PerseusWidget; }' is not assignable to parameter of type 'Partial<Record<string, CategorizerWidget>>'.
489
+ return mapObject(allWidgetInfo, (widgetInfo) => {
490
+ return Widgets.getRendererPropsForWidgetInfo(
491
+ widgetInfo,
492
+ props.problemNum,
493
+ );
494
+ });
495
+ };
496
+
497
+ // This is only used in _getWidgetInfo as a fallback if widgetId
498
+ // doesn't exist in this.state.widgetInfo. It doesn't get run as
499
+ // part of the happy path.
500
+ // TODO(LP-10713): Refactor how we handle widgetIds that don't exist
501
+ // in this.state.widgetInfo.
502
+ _getDefaultWidgetInfo: (widgetId: string) => any = (widgetId: string) => {
503
+ const widgetIdParts = Util.rTypeFromWidgetId.exec(widgetId);
504
+ if (widgetIdParts == null) {
505
+ // We should probably return null here since there's no
506
+ // widget with the given id.
507
+ // NOTE(jeremy): Further, the widgetId we were given does not even
508
+ // look like a widget ID (ie. `widget-type \d+`). I can't figure
509
+ // out how to trigger this line of code though.
510
+ /* c8 ignore next line */
511
+ return {};
512
+ }
513
+ return {
514
+ type: widgetIdParts[1],
515
+ graded: true,
516
+ options: {},
517
+ };
518
+ };
519
+
520
+ _getWidgetInfo: (widgetId: string) => WidgetInfo = (
521
+ widgetId: string,
522
+ ): WidgetInfo => {
523
+ return (
524
+ this.state.widgetInfo[widgetId] ||
525
+ this._getDefaultWidgetInfo(widgetId)
526
+ );
527
+ };
528
+
529
+ renderWidget: (
530
+ impliedType: string,
531
+ id: string,
532
+ state: WidgetState,
533
+ ) => null | React.ReactNode = (
534
+ impliedType: string,
535
+ id: string,
536
+ state: WidgetState,
537
+ ) => {
538
+ const widgetInfo = this.state.widgetInfo[id];
539
+
540
+ if (widgetInfo && widgetInfo.alignment === "full-width") {
541
+ state.foundFullWidth = true;
542
+ }
543
+
544
+ if (widgetInfo) {
545
+ const type = (widgetInfo && widgetInfo.type) || impliedType;
546
+ const shouldHighlight = _.contains(
547
+ // @ts-expect-error [FEI-5003] - TS2345 - Argument of type 'readonly any[] | undefined' is not assignable to parameter of type 'Collection<any>'.
548
+ this.props.highlightedWidgets,
549
+ id,
550
+ );
551
+
552
+ // By this point we should have no duplicates, which are
553
+ // filtered out in this.render(), so we shouldn't have to
554
+ // worry about using this widget key and ref:
555
+ return (
556
+ <ErrorBoundary key={"container:" + id}>
557
+ <WidgetContainer
558
+ ref={"container:" + id}
559
+ type={type}
560
+ initialProps={this.getWidgetProps(id)}
561
+ shouldHighlight={shouldHighlight}
562
+ linterContext={PerseusLinter.pushContextStack(
563
+ this.props.linterContext,
564
+ "widget",
565
+ )}
566
+ />
567
+ </ErrorBoundary>
568
+ );
569
+ }
570
+ return null;
571
+ };
572
+
573
+ getWidgetProps: (id: string) => WidgetProps<any, PerseusWidgetOptions> = (
574
+ id,
575
+ ) => {
576
+ const apiOptions = this.getApiOptions();
577
+ const widgetProps = this.state.widgetProps[id] || {};
578
+
579
+ // The widget needs access to its "rubric" at all times when in review
580
+ // mode (which is really just part of its widget info).
581
+ let reviewModeRubric = null;
582
+ const widgetInfo = this.state.widgetInfo[id];
583
+ if (this.props.reviewMode && widgetInfo) {
584
+ reviewModeRubric = widgetInfo.options;
585
+ }
586
+
587
+ if (!this._interactionTrackers) {
588
+ this._interactionTrackers = {};
589
+ }
590
+
591
+ let interactionTracker = this._interactionTrackers[id];
592
+ if (!interactionTracker) {
593
+ interactionTracker = this._interactionTrackers[id] =
594
+ new InteractionTracker(
595
+ apiOptions.trackInteraction,
596
+ // $FlowFixMe[incompatible-call]
597
+ // @ts-expect-error [FEI-5003] - TS2345 - Argument of type 'string | null | undefined' is not assignable to parameter of type 'string'.
598
+ widgetInfo && widgetInfo.type,
599
+ id,
600
+ // $FlowFixMe[incompatible-call]
601
+ // @ts-expect-error [FEI-5003] - TS2345 - Argument of type 'string | null | undefined' is not assignable to parameter of type 'string'.
602
+ Widgets.getTracking(widgetInfo && widgetInfo.type),
603
+ );
604
+ }
605
+
606
+ return {
607
+ ...widgetProps,
608
+ ref: id,
609
+ widgetId: id,
610
+ alignment: widgetInfo && widgetInfo.alignment,
611
+ // When determining if a widget is static, we verify that the widget is not an
612
+ // exercise question by verifying that it has no problem number.
613
+ static: widgetInfo && widgetInfo.static && !this.props.problemNum,
614
+ problemNum: this.props.problemNum,
615
+ apiOptions: this.getApiOptions(),
616
+ keypadElement: this.props.keypadElement,
617
+ questionCompleted: this.props.questionCompleted,
618
+ onFocus: _.partial(this._onWidgetFocus, id),
619
+ onBlur: _.partial(this._onWidgetBlur, id),
620
+ findWidgets: this.findWidgets,
621
+ reviewModeRubric: reviewModeRubric,
622
+ onChange: (newProps, cb, silent = false) => {
623
+ this._setWidgetProps(id, newProps, cb, silent);
624
+ },
625
+ trackInteraction: interactionTracker.track,
626
+ isLastUsedWidget: id === this.state.lastUsedWidgetId,
627
+ };
628
+ };
629
+
630
+ /**
631
+ * Serializes the questions state so it can be recovered.
632
+ *
633
+ * The return value of this function can be sent to the
634
+ * `restoreSerializedState` method to restore this state.
635
+ *
636
+ * If an instance of widgetProps is passed in, it generates the serialized
637
+ * state from that instead of the current widget props.
638
+ */
639
+ getSerializedState: (widgetProps?: any) => {
640
+ [id: string]: any;
641
+ } = (
642
+ widgetProps: any,
643
+ ): {
644
+ [id: string]: any;
645
+ } => {
646
+ return mapObject(
647
+ widgetProps || this.state.widgetProps,
648
+ (props, widgetId) => {
649
+ const widget = this.getWidgetInstance(widgetId);
650
+ if (widget && widget.getSerializedState) {
651
+ return widget.getSerializedState();
652
+ }
653
+ return props;
654
+ },
655
+ );
656
+ };
657
+
658
+ restoreSerializedState: (
659
+ serializedState: SerializedState,
660
+ callback?: () => void,
661
+ ) => void = (serializedState: SerializedState, callback?: () => void) => {
662
+ // Do some basic validation on the serialized state (just make sure the
663
+ // widget IDs are what we expect).
664
+ const serializedWidgetIds = _.keys(serializedState);
665
+ const widgetPropIds = _.keys(this.state.widgetProps);
666
+
667
+ // If the two lists of IDs match (ignoring order)
668
+ if (
669
+ serializedWidgetIds.length !== widgetPropIds.length ||
670
+ _.intersection(serializedWidgetIds, widgetPropIds).length !==
671
+ serializedWidgetIds.length
672
+ ) {
673
+ Log.error(
674
+ "Refusing to restore bad serialized state:",
675
+ Errors.Internal,
676
+ {
677
+ loggedMetadata: {
678
+ serializedState: JSON.stringify(serializedState),
679
+ currentProps: JSON.stringify(this.state.widgetProps),
680
+ },
681
+ },
682
+ );
683
+ return;
684
+ }
685
+
686
+ // We want to wait until any children widgets who have a
687
+ // restoreSerializedState function also call their own callbacks before
688
+ // we declare that the operation is finished.
689
+ let numCallbacks = 1;
690
+ const fireCallback = () => {
691
+ --numCallbacks;
692
+ if (callback && numCallbacks === 0) {
693
+ callback();
694
+ }
695
+ };
696
+
697
+ this.setState(
698
+ {
699
+ widgetProps: mapObject(serializedState, (props, widgetId) => {
700
+ const widget = this.getWidgetInstance(widgetId);
701
+ if (widget && widget.restoreSerializedState) {
702
+ // Note that we probably can't call
703
+ // `this.change()/this.props.onChange()` in this
704
+ // function, so we take the return value and use
705
+ // that as props if necessary so that
706
+ // `restoreSerializedState` in a widget can
707
+ // change the props as well as state.
708
+ // If a widget has no props to change, it can
709
+ // safely return null.
710
+ ++numCallbacks;
711
+ const restoreResult = widget.restoreSerializedState(
712
+ props,
713
+ fireCallback,
714
+ );
715
+ return _.extend(
716
+ {},
717
+ this.state.widgetProps[widgetId],
718
+ restoreResult,
719
+ );
720
+ }
721
+ return props;
722
+ }),
723
+ },
724
+ () => {
725
+ // Wait until all components have rendered. In React 16 setState
726
+ // callback fires immediately after this componentDidUpdate, and
727
+ // there is no guarantee that parent/siblings components have
728
+ // finished rendering.
729
+ // TODO(jeff, CP-3128): Use Wonder Blocks Timing API
730
+ // eslint-disable-next-line no-restricted-syntax
731
+ setTimeout(fireCallback, 0);
732
+ },
733
+ );
734
+ };
735
+
736
+ /**
737
+ * Tell each of the radio widgets to show rationales for each of the
738
+ * currently selected choices inside of them. If the widget is correct, it
739
+ * shows rationales for all of the choices. This also disables interaction
740
+ * with the choices that we show rationales for.
741
+ */
742
+ showRationalesForCurrentlySelectedChoices: () => void = () => {
743
+ Object.keys(this.props.widgets).forEach((widgetId) => {
744
+ const widget = this.getWidgetInstance(widgetId);
745
+ if (widget && widget.showRationalesForCurrentlySelectedChoices) {
746
+ widget.showRationalesForCurrentlySelectedChoices(
747
+ this._getWidgetInfo(widgetId).options,
748
+ );
749
+ }
750
+ });
751
+ };
752
+
753
+ /**
754
+ * Tells each of the radio widgets to deselect any of the incorrect choices
755
+ * that are currently selected (leaving correct choices still selected).
756
+ */
757
+ deselectIncorrectSelectedChoices: () => void = () => {
758
+ // TODO(emily): this has the exact same structure as
759
+ // showRationalesForCurrentlySelectedChoices above. Maybe DRY this up.
760
+ Object.keys(this.props.widgets).forEach((widgetId) => {
761
+ const widget = this.getWidgetInstance(widgetId);
762
+ if (widget && widget.deselectIncorrectSelectedChoices) {
763
+ widget.deselectIncorrectSelectedChoices();
764
+ }
765
+ });
766
+ };
767
+
768
+ /**
769
+ * Allows inter-widget communication.
770
+ *
771
+ * This function yields this Renderer's own internal widgets, and it's used
772
+ * in two places.
773
+ *
774
+ * First, we expose our own internal widgets to each other by giving them
775
+ * a `findWidgets` function that, in turn, calls this function.
776
+ *
777
+ * Second, we expose our own internal widgets to this Renderer's parent,
778
+ * by allowing it to call this function directly. That way, it can hook us
779
+ * up to other Renderers on the page, by writing a `findExternalWidgets`
780
+ * prop that calls each other Renderer's `findInternalWidgets` function.
781
+ *
782
+ * Takes a `filterCriterion` on which widgets to return.
783
+ * `filterCriterion` can be one of:
784
+ * * A string widget id
785
+ * * A string widget type
786
+ * * a function from (id, widgetInfo, widgetComponent) to true or false
787
+ *
788
+ * Returns an array of the matching widget components.
789
+ *
790
+ * If you need to do logic with more than the components, it is possible
791
+ * to do such logic inside the filter, rather than on the result array.
792
+ *
793
+ * See the passage-ref widget for an example.
794
+ *
795
+ * "Remember: abilities are not inherently good or evil, it's how you use
796
+ * them." ~ Kyle Katarn
797
+ * Please use this one with caution.
798
+ */
799
+ findInternalWidgets: (
800
+ filterCriterion: FilterCriterion,
801
+ ) => ReadonlyArray<Widget | null | undefined> = (
802
+ filterCriterion: FilterCriterion,
803
+ ) => {
804
+ let filterFunc;
805
+ // Convenience filters:
806
+ // "interactive-graph 3" will give you [[interactive-graph 3]]
807
+ // "interactive-graph" will give you all interactive-graphs
808
+ if (typeof filterCriterion === "string") {
809
+ if (filterCriterion.indexOf(" ") !== -1) {
810
+ const widgetId = filterCriterion;
811
+ filterFunc = (
812
+ id: string,
813
+ widgetInfo: WidgetInfo,
814
+ widget?: Widget | null,
815
+ ) => id === widgetId;
816
+ } else {
817
+ const widgetType = filterCriterion;
818
+ filterFunc = (
819
+ id: string,
820
+ widgetInfo: WidgetInfo,
821
+ widget?: Widget | null,
822
+ ) => {
823
+ return widgetInfo.type === widgetType;
824
+ };
825
+ }
826
+ } else {
827
+ filterFunc = filterCriterion;
828
+ }
829
+
830
+ const results: ReadonlyArray<Widget | null | undefined> = this.widgetIds
831
+ .filter((id: string) => {
832
+ const widgetInfo = this._getWidgetInfo(id);
833
+ const widget = this.getWidgetInstance(id);
834
+ return filterFunc(id, widgetInfo, widget);
835
+ })
836
+ .map(this.getWidgetInstance);
837
+
838
+ return results;
839
+ };
840
+
841
+ /**
842
+ * Allows inter-widget communication.
843
+ *
844
+ * Includes both widgets internal to this Renderer, and external widgets
845
+ * exposed by the `findExternalWidgets` prop.
846
+ *
847
+ * See `findInteralWidgets` for more information.
848
+ */
849
+ findWidgets: (filterCriterion: FilterCriterion) => any = (
850
+ filterCriterion: FilterCriterion,
851
+ ) => {
852
+ return [
853
+ ...this.findInternalWidgets(filterCriterion),
854
+ ...this.props.findExternalWidgets(filterCriterion),
855
+ ];
856
+ };
857
+
858
+ getWidgetInstance: (id: string) => Widget | null | undefined = (
859
+ id: string,
860
+ ): Widget | null | undefined => {
861
+ // eslint-disable-next-line react/no-string-refs
862
+ const ref = this.refs["container:" + id];
863
+ if (!ref) {
864
+ return null;
865
+ }
866
+ // @ts-expect-error [FEI-5003] - TS2339 - Property 'getWidget' does not exist on type 'ReactInstance'.
867
+ return ref.getWidget();
868
+ };
869
+
870
+ _onWidgetFocus: (id: string, focusPath?: ReadonlyArray<string>) => void = (
871
+ id: string,
872
+ focusPath: ReadonlyArray<string> = [],
873
+ ) => {
874
+ if (!_.isArray(focusPath)) {
875
+ throw new PerseusError(
876
+ "widget props.onFocus focusPath must be an Array, " +
877
+ "but was" +
878
+ JSON.stringify(focusPath),
879
+ Errors.Internal,
880
+ );
881
+ }
882
+ this._setCurrentFocus([id].concat(focusPath));
883
+ };
884
+
885
+ _onWidgetBlur: (id: string, blurPath: FocusPath) => void = (
886
+ id: string,
887
+ blurPath: FocusPath,
888
+ ) => {
889
+ const blurringFocusPath = this._currentFocus;
890
+
891
+ // Failsafe: abort if ID is different, because focus probably happened
892
+ // before blur
893
+ // @ts-expect-error [FEI-5003] - TS2769 - No overload matches this call.
894
+ const fullPath = [id].concat(blurPath);
895
+ if (!_.isEqual(fullPath, blurringFocusPath)) {
896
+ return;
897
+ }
898
+
899
+ // Wait until after any new focus events fire this tick before
900
+ // declaring that nothing is focused.
901
+ // If a different widget was focused, we'll see an onBlur event
902
+ // now, but then an onFocus event on a different element before
903
+ // this callback is executed
904
+ _.defer(() => {
905
+ if (_.isEqual(this._currentFocus, blurringFocusPath)) {
906
+ this._setCurrentFocus(null);
907
+ }
908
+ });
909
+ };
910
+
911
+ getContent: (props: Props, state: State) => any = (
912
+ props: Props,
913
+ state: State,
914
+ ) => {
915
+ return state.jiptContent || props.content;
916
+ };
917
+
918
+ shouldRenderJiptPlaceholder: (props: Props, state: State) => boolean = (
919
+ props: Props,
920
+ state: State,
921
+ ): boolean => {
922
+ // TODO(aria): Pass this in via webapp as an apiOption
923
+ return (
924
+ getDependencies().JIPT.useJIPT &&
925
+ state.jiptContent == null &&
926
+ props.content.indexOf("crwdns") !== -1
927
+ );
928
+ };
929
+
930
+ replaceJiptContent: (content: string, paragraphIndex: number) => void = (
931
+ content: string,
932
+ paragraphIndex: number,
933
+ ) => {
934
+ if (paragraphIndex == null) {
935
+ // we're not translating paragraph-wise; replace the whole content
936
+ // (we could also theoretically check for apiOptions.isArticle
937
+ // here, which is what causes paragraphIndex to not be null)
938
+ this.setState({
939
+ jiptContent: content,
940
+ });
941
+ } else {
942
+ // This is the same regex we use in perseus/translate.py to find
943
+ // code blocks. We use it to count entire code blocks as
944
+ // paragraphs.
945
+ const codeFenceRegex =
946
+ /^\s*(`{3,}|~{3,})\s*(\S+)?\s*\n([\s\S]+?)\s*\1\s*$/;
947
+
948
+ if (codeFenceRegex.test(content)) {
949
+ // If a paragraph is a code block, we're going to treat it as a
950
+ // single paragraph even if it has double-newlines in it, so
951
+ // skip the next two checks.
952
+ } else if (/\S\n\s*\n\S/.test(content)) {
953
+ // Our "render the exact same QuestionParagraphs each time"
954
+ // strategy will fail if we allow translating a paragraph
955
+ // to more than one paragraph. This hack renders as a single
956
+ // paragraph and lets the translator know to not use \n\n,
957
+ // hopefully. We can't wait for linting because we can't
958
+ // safely render the node.
959
+ // TODO(aria): Check for the max number of backticks or tildes
960
+ // in the content, and just render a red code block of the
961
+ // content here instead?
962
+ content =
963
+ "$\\large{\\red{\\text{Please translate each " +
964
+ "paragraph to a single paragraph.}}}$";
965
+ } else if (/^\s*$/.test(content)) {
966
+ // We similarly can't have an all-whitespace paragraph, or
967
+ // we will parse it as the closing of the previous paragraph
968
+ content =
969
+ "$\\large{\\red{\\text{Translated paragraph is " +
970
+ "currently empty}}}$";
971
+ }
972
+ // Split the paragraphs; we have to use getContent() in case
973
+ // nothing has been translated yet (in which case we just have
974
+ // this.props.content)
975
+ const allContent = this.getContent(this.props, this.state);
976
+ const paragraphs = JiptParagraphs.parseToArray(allContent);
977
+ paragraphs[paragraphIndex] = content;
978
+ this.setState({
979
+ jiptContent: JiptParagraphs.joinFromArray(paragraphs),
980
+ });
981
+ }
982
+ };
983
+
984
+ // wrap top-level elements in a QuestionParagraph, mostly
985
+ // for appropriate spacing and other css
986
+ // @ts-expect-error [FEI-5003] - TS2322 - Type '(ast: any, state: WidgetState) => never[] | JSX.Element' is not assignable to type '(ast: any, state: WidgetState) => ReactElement<any, string | JSXElementConstructor<any>>'.
987
+ outputMarkdown: (ast: any, state: WidgetState) => React.ReactElement = (
988
+ ast: any,
989
+ state: WidgetState,
990
+ ) => {
991
+ if (_.isArray(ast)) {
992
+ // This is duplicated from simple-markdown
993
+ // TODO(aria): Don't duplicate this logic
994
+ const oldKey = state.key;
995
+ const result = [];
996
+
997
+ // map nestedOutput over the ast, except group any text
998
+ // nodes together into a single string output.
999
+ // NOTE(aria): These are never strings--always QuestionParagraphs
1000
+ // TODO(aria): We probably don't need this string logic here.
1001
+ let lastWasString = false;
1002
+ for (let i = 0; i < ast.length; i++) {
1003
+ state.key = i;
1004
+ state.paragraphIndex = i;
1005
+ const nodeOut = this.outputMarkdown(ast[i], state);
1006
+ const isString = typeof nodeOut === "string";
1007
+ // NOTE(jeremy): As far as I can tell, this if is _never_
1008
+ // reached. As noted above, these are always QuestionParagraphs
1009
+ // now.
1010
+ /* c8 ignore if */
1011
+ if (typeof nodeOut === "string" && lastWasString) {
1012
+ // @ts-expect-error [FEI-5003] - TS2322 - Type 'number' is not assignable to type 'never'.
1013
+ result[result.length - 1] += nodeOut;
1014
+ } else {
1015
+ // @ts-expect-error [FEI-5003] - TS2345 - Argument of type 'ReactElement<any, string | JSXElementConstructor<any>>' is not assignable to parameter of type 'never'.
1016
+ result.push(nodeOut);
1017
+ }
1018
+ lastWasString = isString;
1019
+ }
1020
+
1021
+ state.key = oldKey;
1022
+ return result;
1023
+ }
1024
+ // !!! WARNING: Mutative hacks! mutates `this._foundTextNodes`:
1025
+ // because I wrote a bad interface to simple-markdown.js' `output`
1026
+ this._foundTextNodes = false;
1027
+ state.foundFullWidth = false;
1028
+ const output = this.outputNested(ast, state);
1029
+
1030
+ // In Jipt-land, we need to render the exact same outer
1031
+ // QuestionParagraph nodes always. This means the number of
1032
+ // paragraphs needs to stay the same, and we can't modify
1033
+ // the classnames on the QuestionParagraphs or we'll destroy
1034
+ // the crowdin classnames. So we just only use the
1035
+ // 'paragraph' classname from the QuestionParagraph.
1036
+ // If this becomes a problem it would be easy to fix by wrapping
1037
+ // the nodes in an extra layer (hopefully only for jipt) that
1038
+ // handles the jipt classnames, and let this layer handle the
1039
+ // dynamic classnames.
1040
+ // We can't render the classes the first time and leave them
1041
+ // the same because we don't know at the time of the first
1042
+ // render whether they are full-bleed or centered, since they
1043
+ // only contain crowdin IDs like `crwdns:972384209:0...`
1044
+ let className;
1045
+ if (this.translationIndex != null) {
1046
+ className = null;
1047
+ } else {
1048
+ className = classNames({
1049
+ "perseus-paragraph-centered": !this._foundTextNodes,
1050
+ // There is only one node being rendered,
1051
+ // and it's a full-width widget.
1052
+ "perseus-paragraph-full-width":
1053
+ state.foundFullWidth && ast.content.length === 1,
1054
+ });
1055
+ }
1056
+
1057
+ return (
1058
+ <QuestionParagraph
1059
+ key={state.key}
1060
+ className={className}
1061
+ translationIndex={this.translationIndex}
1062
+ paragraphIndex={state.paragraphIndex}
1063
+ >
1064
+ <ErrorBoundary>{output}</ErrorBoundary>
1065
+ </QuestionParagraph>
1066
+ );
1067
+ };
1068
+
1069
+ // output non-top-level nodes or arrays
1070
+ outputNested: (ast: any, state: WidgetState) => React.ReactElement = (
1071
+ ast: any,
1072
+ state: WidgetState,
1073
+ ) => {
1074
+ if (_.isArray(ast)) {
1075
+ // This is duplicated from simple-markdown
1076
+ // TODO(aria): Don't duplicate this logic
1077
+ const oldKey = state.key;
1078
+ const result: Array<string | React.ReactNode> = [];
1079
+
1080
+ // map nestedOutput over the ast, except group any text
1081
+ // nodes together into a single string output.
1082
+ let lastWasString = false;
1083
+ for (let i = 0; i < ast.length; i++) {
1084
+ state.key = i;
1085
+ const nodeOut = this.outputNested(ast[i], state);
1086
+ const isString = typeof nodeOut === "string";
1087
+ if (typeof nodeOut === "string" && lastWasString) {
1088
+ /**
1089
+ * We know that last was string, but Flow can't see this
1090
+ * refinement.
1091
+ */
1092
+ result[result.length - 1] += nodeOut;
1093
+ } else {
1094
+ result.push(nodeOut);
1095
+ }
1096
+ lastWasString = isString;
1097
+ }
1098
+
1099
+ state.key = oldKey;
1100
+ return result;
1101
+ }
1102
+ return this.outputNode(ast, this.outputNested, state);
1103
+ };
1104
+
1105
+ // output individual AST nodes [not arrays]
1106
+ outputNode: (
1107
+ node: any,
1108
+ nestedOutput: any,
1109
+ state: WidgetState,
1110
+ ) =>
1111
+ | any
1112
+ | null
1113
+ | React.ReactElement<React.ComponentProps<"div">>
1114
+ | React.ReactElement<React.ComponentProps<"span">>
1115
+ | React.ReactNode = (
1116
+ node: any,
1117
+ nestedOutput: any,
1118
+ state: WidgetState,
1119
+ ) => {
1120
+ const apiOptions = this.getApiOptions();
1121
+ const imagePlaceholder = apiOptions.imagePlaceholder;
1122
+
1123
+ if (node.type === "widget") {
1124
+ const widgetPlaceholder = apiOptions.widgetPlaceholder;
1125
+
1126
+ if (widgetPlaceholder) {
1127
+ return widgetPlaceholder;
1128
+ }
1129
+ // Widgets can contain text nodes, so we don't center them with
1130
+ // markdown magic here.
1131
+ // Instead, we center them with css magic in articles.less
1132
+ // /cry(aria)
1133
+ this._foundTextNodes = true;
1134
+
1135
+ if (_.contains(this.widgetIds, node.id)) {
1136
+ // We don't want to render a duplicate widget key/ref,
1137
+ // as this causes problems with react (for obvious
1138
+ // reasons). Instead we just notify the
1139
+ // hopefully-content-creator that they need to change the
1140
+ // widget id.
1141
+ return (
1142
+ <span key={state.key} className="renderer-widget-error">
1143
+ {[
1144
+ "Widget [[",
1145
+ "☃",
1146
+ " ",
1147
+ node.id,
1148
+ "]] already exists.",
1149
+ ].join("")}
1150
+ </span>
1151
+ );
1152
+ }
1153
+ this.widgetIds.push(node.id);
1154
+ return this.renderWidget(node.widgetType, node.id, state);
1155
+ }
1156
+ if (node.type === "blockMath") {
1157
+ // We render math here instead of in perseus-markdown.jsx
1158
+ // because we need to pass it our onRender callback.
1159
+ const content = preprocessTex(node.content);
1160
+
1161
+ const innerStyle = {
1162
+ // HACK(benkomalo): we only want horizontal scrolling, but
1163
+ // overflowX: 'auto' causes a vertical scrolling scrollbar
1164
+ // as well, despite the parent and child elements having
1165
+ // the exact same height. Force it to not scroll by
1166
+ // applying overflowY: 'hidden'
1167
+ overflowX: "auto",
1168
+ overflowY: "hidden",
1169
+
1170
+ // HACK(kevinb): overflowY: 'hidden' inadvertently clips the
1171
+ // top and bottom of some fractions. We add padding to the
1172
+ // top and bottom to avoid the clipping and then correct for
1173
+ // the padding by adding equal but opposite margins.
1174
+ paddingTop: 10,
1175
+ paddingBottom: 10,
1176
+ marginTop: -10,
1177
+ marginBottom: -10,
1178
+ } as const;
1179
+
1180
+ if (apiOptions.isMobile) {
1181
+ // The style for the body of articles and exercises on mobile is
1182
+ // to have a 16px margin. When a user taps to zoom math we'd
1183
+ // like the math to extend all the way to the edge of the page/
1184
+ // To achieve this affect we nest the Zoomable component in two
1185
+ // nested divs. The outer div has a negative margin to
1186
+ // counteract the margin on main perseus container. The inner
1187
+ // div adds the margin back as padding so that when the math is
1188
+ // scaled out it's inset from the edge of the page. When the
1189
+ // TeX component is full size it will extend to the edge of the
1190
+ // page if it's larger than the page.
1191
+ //
1192
+ // TODO(kevinb) automatically determine the margin size
1193
+ const margin = 16;
1194
+ const outerStyle = {
1195
+ marginLeft: -margin,
1196
+ marginRight: -margin,
1197
+ } as const;
1198
+ const horizontalPadding = {
1199
+ paddingLeft: margin,
1200
+ paddingRight: margin,
1201
+ } as const;
1202
+
1203
+ const mobileInnerStyle = {
1204
+ ...innerStyle,
1205
+ ...styles.mobileZoomableParentFix,
1206
+ } as const;
1207
+
1208
+ return (
1209
+ <div
1210
+ key={state.key}
1211
+ className="perseus-block-math"
1212
+ style={outerStyle}
1213
+ >
1214
+ <ErrorBoundary>
1215
+ <div
1216
+ className="perseus-block-math-inner"
1217
+ style={{
1218
+ ...mobileInnerStyle,
1219
+ ...horizontalPadding,
1220
+ }}
1221
+ >
1222
+ <ZoomableTeX>{content}</ZoomableTeX>
1223
+ </div>
1224
+ </ErrorBoundary>
1225
+ </div>
1226
+ );
1227
+ }
1228
+ return (
1229
+ <div key={state.key} className="perseus-block-math">
1230
+ <ErrorBoundary>
1231
+ <div
1232
+ className="perseus-block-math-inner"
1233
+ style={innerStyle}
1234
+ >
1235
+ <AssetContext.Consumer>
1236
+ {({setAssetStatus}) => (
1237
+ <TeX setAssetStatus={setAssetStatus}>
1238
+ {content}
1239
+ </TeX>
1240
+ )}
1241
+ </AssetContext.Consumer>
1242
+ </div>
1243
+ </ErrorBoundary>
1244
+ </div>
1245
+ );
1246
+ }
1247
+ if (node.type === "math") {
1248
+ // Replace uses of \begin{align}...\end{align} which KaTeX doesn't
1249
+ // support (yet) with \begin{aligned}...\end{aligned} which renders
1250
+ // the same is supported by KaTeX. It does the same for align*.
1251
+ // TODO(kevinb) update content to use aligned instead of align.
1252
+ const tex = node.content.replace(/\{align[*]?\}/g, "{aligned}");
1253
+
1254
+ // We render math here instead of in perseus-markdown.jsx
1255
+ // because we need to pass it our onRender callback.
1256
+ return (
1257
+ <span
1258
+ key={state.key}
1259
+ style={{
1260
+ // If math is directly next to text, don't let it
1261
+ // wrap to the next line
1262
+ whiteSpace: "nowrap",
1263
+ }}
1264
+ >
1265
+ <ErrorBoundary>
1266
+ {/* We add extra empty spans around the math to make it not
1267
+ wrap (I don't know why this works, but it does) */}
1268
+ <span />
1269
+ <AssetContext.Consumer>
1270
+ {({setAssetStatus}) => (
1271
+ <TeX
1272
+ onRender={this.props.onRender}
1273
+ setAssetStatus={setAssetStatus}
1274
+ >
1275
+ {tex}
1276
+ </TeX>
1277
+ )}
1278
+ </AssetContext.Consumer>
1279
+ <span />
1280
+ </ErrorBoundary>
1281
+ </span>
1282
+ );
1283
+ }
1284
+ if (node.type === "image") {
1285
+ if (imagePlaceholder) {
1286
+ return imagePlaceholder;
1287
+ }
1288
+
1289
+ // We need to add width and height to images from our
1290
+ // props.images mapping.
1291
+
1292
+ // We do a _.has check here to avoid weird things like
1293
+ // 'toString' or '__proto__' as a url.
1294
+ const extraAttrs = _.has(this.props.images, node.target)
1295
+ ? this.props.images[node.target]
1296
+ : null;
1297
+
1298
+ // The width of a table column is determined by the widest table
1299
+ // cell within that column, but responsive images constrain
1300
+ // themselves to the width of their parent containers. Thus,
1301
+ // responsive images don't do very well within tables. To avoid
1302
+ // haphazard sizing, simply make images within tables unresponsive.
1303
+ // TODO(alex): Make tables themselves responsive.
1304
+ const responsive = !state.inTable;
1305
+ return (
1306
+ <ErrorBoundary key={state.key}>
1307
+ <AssetContext.Consumer>
1308
+ {({setAssetStatus}) => (
1309
+ <SvgImage
1310
+ setAssetStatus={setAssetStatus}
1311
+ /* $FlowFixMe[incompatible-type]: sanitizeUrl() can return null */
1312
+ // @ts-expect-error [FEI-5003] - TS2322 - Type 'string | null | undefined' is not assignable to type 'string | undefined'.
1313
+ src={PerseusMarkdown.sanitizeUrl(node.target)}
1314
+ alt={node.alt}
1315
+ title={node.title}
1316
+ responsive={responsive}
1317
+ onUpdate={this.props.onRender}
1318
+ zoomToFullSizeOnMobile={
1319
+ apiOptions.isMobile && apiOptions.isArticle
1320
+ }
1321
+ {...extraAttrs}
1322
+ />
1323
+ )}
1324
+ </AssetContext.Consumer>
1325
+ </ErrorBoundary>
1326
+ );
1327
+ }
1328
+ if (node.type === "columns") {
1329
+ // Note that we have two columns. This is so we can put
1330
+ // a className on the outer renderer content for SAT.
1331
+ // TODO(aria): See if there is a better way we can do
1332
+ // things like this
1333
+ this._isTwoColumn = true;
1334
+ // but then render normally:
1335
+ return (
1336
+ <ErrorBoundary key={state.key}>
1337
+ {PerseusMarkdown.ruleOutput(node, nestedOutput, state)}
1338
+ </ErrorBoundary>
1339
+ );
1340
+ }
1341
+ if (node.type === "text") {
1342
+ if (rContainsNonWhitespace.test(node.content)) {
1343
+ this._foundTextNodes = true;
1344
+ }
1345
+
1346
+ // Used by the translator portal to replace image URLs with
1347
+ // placeholders, see preprocessWidgets in manticore-utils.js
1348
+ // for more details.
1349
+ if (imagePlaceholder && rImageURL.test(node.content)) {
1350
+ return imagePlaceholder;
1351
+ }
1352
+ return node.content;
1353
+ }
1354
+ if (node.type === "table" || node.type === "titledTable") {
1355
+ const output = PerseusMarkdown.ruleOutput(node, nestedOutput, {
1356
+ ...state,
1357
+ isMobile: apiOptions.isMobile,
1358
+ inTable: true,
1359
+ });
1360
+
1361
+ if (!apiOptions.isMobile) {
1362
+ return output;
1363
+ }
1364
+
1365
+ const margin = 16;
1366
+ const outerStyle = {
1367
+ marginLeft: -margin,
1368
+ marginRight: -margin,
1369
+ } as const;
1370
+ const innerStyle = {
1371
+ paddingLeft: 0,
1372
+ paddingRight: 0,
1373
+ } as const;
1374
+ const mobileInnerStyle = {
1375
+ ...innerStyle,
1376
+ ...styles.mobileZoomableParentFix,
1377
+ } as const;
1378
+
1379
+ const wrappedOutput = (
1380
+ <div style={{...mobileInnerStyle, overflowX: "auto"}}>
1381
+ <ErrorBoundary>
1382
+ <Zoomable animateHeight={true}>{output}</Zoomable>
1383
+ </ErrorBoundary>
1384
+ </div>
1385
+ );
1386
+
1387
+ // TODO(benkomalo): how should we deal with tappable items inside
1388
+ // of tables?
1389
+ return <div style={outerStyle}>{wrappedOutput}</div>;
1390
+ }
1391
+ // If it's a "normal" or "simple" markdown node, just
1392
+ // output it using its output rule.
1393
+ return (
1394
+ <ErrorBoundary key={state.key}>
1395
+ {PerseusMarkdown.ruleOutput(node, nestedOutput, state)}
1396
+ </ErrorBoundary>
1397
+ );
1398
+ };
1399
+
1400
+ handleRender: (prevProps: Props) => void = (prevProps: Props) => {
1401
+ const onRender = this.props.onRender;
1402
+ const oldOnRender = prevProps.onRender;
1403
+
1404
+ // In the common case of no callback specified, avoid this work.
1405
+ if (onRender !== noopOnRender || oldOnRender !== noopOnRender) {
1406
+ // @ts-expect-error [FEI-5003] - TS2769 - No overload matches this call. | TS2339 - Property 'find' does not exist on type 'JQueryStatic'.
1407
+ const $images = $(ReactDOM.findDOMNode(this)).find("img");
1408
+
1409
+ // Fire callback on image load...
1410
+ if (oldOnRender !== noopOnRender) {
1411
+ $images.off("load", oldOnRender);
1412
+ }
1413
+
1414
+ if (onRender !== noopOnRender) {
1415
+ $images.on("load", onRender);
1416
+ }
1417
+ }
1418
+
1419
+ // ...as well as right now (non-image, non-TeX or image from cache)
1420
+ onRender();
1421
+ };
1422
+
1423
+ // Sets the current focus path
1424
+ // If the new focus path is not a prefix of the old focus path,
1425
+ // we send an onChangeFocus event back to our parent.
1426
+ _setCurrentFocus: (path: FocusPath) => void = (path: FocusPath) => {
1427
+ const apiOptions = this.getApiOptions();
1428
+
1429
+ // We don't do this when the new path is a prefix because
1430
+ // that prefix is already focused (we're just in a more specific
1431
+ // area of it). This makes it safe to call _setCurrentFocus
1432
+ // whenever a widget is interacted with--we won't wipe out
1433
+ // our focus state if we are already focused on a subpart
1434
+ // of that widget (i.e. a transformation NumberInput inside
1435
+ // of a transformer widget).
1436
+ if (!isIdPathPrefix(path, this._currentFocus)) {
1437
+ const prevFocus = this._currentFocus;
1438
+
1439
+ if (prevFocus) {
1440
+ this.blurPath(prevFocus);
1441
+ }
1442
+
1443
+ this._currentFocus = path;
1444
+ apiOptions.onFocusChange(this._currentFocus, prevFocus);
1445
+ }
1446
+ };
1447
+
1448
+ focus: () => boolean | null | undefined = () => {
1449
+ let id;
1450
+ let focusResult;
1451
+ for (let i = 0; i < this.widgetIds.length; i++) {
1452
+ const widgetId = this.widgetIds[i];
1453
+ const widget = this.getWidgetInstance(widgetId);
1454
+ const widgetFocusResult = widget && widget.focus && widget.focus();
1455
+ if (widgetFocusResult) {
1456
+ id = widgetId;
1457
+ focusResult = widgetFocusResult;
1458
+ break;
1459
+ }
1460
+ }
1461
+
1462
+ if (id) {
1463
+ // reconstruct a {path, element} focus object
1464
+ let path;
1465
+ if (typeof focusResult === "object") {
1466
+ // TODO(jeremy): I am 99% sure this path is no longer possible.
1467
+ // In D10274, focus management sometimes returned an object
1468
+ // with a `path` and `element` key. But later in D11387 and
1469
+ // D13664 things have been changed and seemingly removed the
1470
+ // object return value.
1471
+ // The result of focus was a {path, id} object itself
1472
+ path = [id].concat(focusResult.path || []);
1473
+ Log.error(
1474
+ "Renderer received a focus result of type 'object' " +
1475
+ "instead of the expected type 'boolean'",
1476
+ Errors.Internal,
1477
+ {
1478
+ loggedMetadata: {
1479
+ focusResult: JSON.stringify(focusResult),
1480
+ currentProps: JSON.stringify(
1481
+ this.state.widgetProps,
1482
+ ),
1483
+ },
1484
+ },
1485
+ );
1486
+ } else {
1487
+ // The result of focus was true or the like; just
1488
+ // construct a root focus object
1489
+ path = [id];
1490
+ }
1491
+
1492
+ this._setCurrentFocus(path);
1493
+ return true;
1494
+ }
1495
+ };
1496
+
1497
+ getDOMNodeForPath: (path: FocusPath) => Element | Text | null | undefined =
1498
+ (path: FocusPath) => {
1499
+ // @ts-expect-error [FEI-5003] - TS2345 - Argument of type 'FocusPath' is not assignable to parameter of type 'List<any>'.
1500
+ const widgetId = _.first(path);
1501
+ // @ts-expect-error [FEI-5003] - TS2345 - Argument of type 'FocusPath' is not assignable to parameter of type 'List<any>'.
1502
+ const interWidgetPath = _.rest(path);
1503
+
1504
+ // Widget handles parsing of the interWidgetPath. If the path is empty
1505
+ // beyond the widgetID, as a special case we just return the widget's
1506
+ // DOM node.
1507
+ const widget = this.getWidgetInstance(widgetId);
1508
+ const getNode = widget && widget.getDOMNodeForPath;
1509
+ if (getNode) {
1510
+ return getNode(interWidgetPath);
1511
+ }
1512
+ if (interWidgetPath.length === 0) {
1513
+ // @ts-expect-error [FEI-5003] - TS2345 - Argument of type 'Widget | null | undefined' is not assignable to parameter of type 'ReactInstance | null | undefined'.
1514
+ return ReactDOM.findDOMNode(widget);
1515
+ }
1516
+ };
1517
+
1518
+ getGrammarTypeForPath: (path: FocusPath) => string | null | undefined = (
1519
+ path: FocusPath,
1520
+ ) => {
1521
+ // @ts-expect-error [FEI-5003] - TS2345 - Argument of type 'FocusPath' is not assignable to parameter of type 'List<any>'.
1522
+ const widgetId = _.first(path);
1523
+ // @ts-expect-error [FEI-5003] - TS2345 - Argument of type 'FocusPath' is not assignable to parameter of type 'List<any>'.
1524
+ const interWidgetPath = _.rest(path);
1525
+
1526
+ const widget = this.getWidgetInstance(widgetId);
1527
+ if (widget && widget.getGrammarTypeForPath) {
1528
+ return widget.getGrammarTypeForPath(interWidgetPath);
1529
+ }
1530
+ };
1531
+
1532
+ getInputPaths: () => ReadonlyArray<FocusPath> = () => {
1533
+ const inputPaths: Array<FocusPath> = [];
1534
+ _.each(this.widgetIds, (widgetId: string) => {
1535
+ const widget = this.getWidgetInstance(widgetId);
1536
+ if (widget && widget.getInputPaths) {
1537
+ // Grab all input paths and add widgetID to the front
1538
+ const widgetInputPaths = widget.getInputPaths();
1539
+ // Prefix paths with their widgetID and add to collective
1540
+ // list of paths.
1541
+ // @ts-expect-error [FEI-5003] - TS2345 - Argument of type '(inputPath: string) => void' is not assignable to parameter of type 'CollectionIterator<FocusPath, void, readonly FocusPath[]>'.
1542
+ _.each(widgetInputPaths, (inputPath: string) => {
1543
+ const relativeInputPath = [widgetId].concat(inputPath);
1544
+ inputPaths.push(relativeInputPath);
1545
+ });
1546
+ }
1547
+ });
1548
+
1549
+ return inputPaths;
1550
+ };
1551
+
1552
+ focusPath: (path: FocusPath) => void = (path: FocusPath) => {
1553
+ // No need to focus if it's already focused
1554
+ if (_.isEqual(this._currentFocus, path)) {
1555
+ return;
1556
+ }
1557
+ if (this._currentFocus) {
1558
+ // Unfocus old path, if exists
1559
+ this.blurPath(this._currentFocus);
1560
+ }
1561
+
1562
+ // @ts-expect-error [FEI-5003] - TS2345 - Argument of type 'FocusPath' is not assignable to parameter of type 'List<any>'.
1563
+ const widgetId = _.first(path);
1564
+ // @ts-expect-error [FEI-5003] - TS2345 - Argument of type 'FocusPath' is not assignable to parameter of type 'List<any>'.
1565
+ const interWidgetPath = _.rest(path);
1566
+
1567
+ // Widget handles parsing of the interWidgetPath
1568
+ const focusWidget = this.getWidgetInstance(widgetId);
1569
+ if (focusWidget && focusWidget.focusInputPath) {
1570
+ focusWidget.focusInputPath(interWidgetPath);
1571
+ }
1572
+ };
1573
+
1574
+ blurPath: (path: FocusPath) => void = (path: FocusPath) => {
1575
+ // No need to blur if it's not focused
1576
+ if (!_.isEqual(this._currentFocus, path)) {
1577
+ return;
1578
+ }
1579
+
1580
+ // @ts-expect-error [FEI-5003] - TS2345 - Argument of type 'FocusPath' is not assignable to parameter of type 'List<any>'.
1581
+ const widgetId = _.first(path);
1582
+ // @ts-expect-error [FEI-5003] - TS2345 - Argument of type 'FocusPath' is not assignable to parameter of type 'List<any>'.
1583
+ const interWidgetPath = _.rest(path);
1584
+ const widget = this.getWidgetInstance(widgetId);
1585
+ // We might be in the editor and blurring a widget that no
1586
+ // longer exists, so only blur if we actually found the widget
1587
+ if (widget) {
1588
+ const blurWidget = this.getWidgetInstance(widgetId);
1589
+ if (blurWidget && blurWidget.blurInputPath) {
1590
+ // Widget handles parsing of the interWidgetPath
1591
+ blurWidget.blurInputPath(interWidgetPath);
1592
+ }
1593
+ }
1594
+ };
1595
+
1596
+ blur: () => void = () => {
1597
+ if (this._currentFocus) {
1598
+ this.blurPath(this._currentFocus);
1599
+ }
1600
+ };
1601
+
1602
+ // Serializes widget state. Seems to be used only by editors though.
1603
+ serialize: () => Record<any, any> = () => {
1604
+ const state: Record<string, any> = {};
1605
+ _.each(
1606
+ this.state.widgetInfo,
1607
+ function (info, id) {
1608
+ // eslint-disable-next-line @babel/no-invalid-this
1609
+ // @ts-expect-error [FEI-5003] - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
1610
+ // eslint-disable-next-line @babel/no-invalid-this
1611
+ const widget = this.getWidgetInstance(id);
1612
+ const s = widget.serialize();
1613
+ if (!_.isEmpty(s)) {
1614
+ state[id] = s;
1615
+ }
1616
+ },
1617
+ this,
1618
+ );
1619
+ return state;
1620
+ };
1621
+
1622
+ emptyWidgets: () => any = () => {
1623
+ // @ts-expect-error [FEI-5003] - TS2345 - Argument of type '(id: string) => boolean | undefined' is not assignable to parameter of type 'Iteratee<string[], boolean, string>'.
1624
+ return _.filter(this.widgetIds, (id) => {
1625
+ const widgetInfo = this._getWidgetInfo(id);
1626
+ if (widgetInfo.static) {
1627
+ // Static widgets shouldn't count as empty
1628
+ return false;
1629
+ }
1630
+ const widget = this.getWidgetInstance(id);
1631
+ if (widget && widget.simpleValidate) {
1632
+ const score: PerseusScore = widget.simpleValidate(
1633
+ widgetInfo.options,
1634
+ // @ts-expect-error [FEI-5003] - TS2345 - Argument of type 'null' is not assignable to parameter of type '((widgetId: any, value: string, message?: string | null | undefined) => unknown) | undefined'.
1635
+ null,
1636
+ );
1637
+ return Util.scoreIsEmpty(score);
1638
+ }
1639
+ });
1640
+ };
1641
+
1642
+ _setWidgetProps: SetWidgetPropsFn = (id, newProps, cb, silent) => {
1643
+ this.setState(
1644
+ (prevState) => {
1645
+ const widgetProps = {
1646
+ ...prevState.widgetProps,
1647
+ [id]: {
1648
+ ...prevState.widgetProps[id],
1649
+ ...newProps,
1650
+ },
1651
+ } as const;
1652
+
1653
+ // Update the `lastUsedWidgetId` to this widget - unless we're
1654
+ // in silent mode. We only want to track the last widget that
1655
+ // was actually _used_, and silent updates generally don't come
1656
+ // from _usage_.
1657
+ const lastUsedWidgetId = silent
1658
+ ? prevState.lastUsedWidgetId
1659
+ : id;
1660
+
1661
+ if (!silent) {
1662
+ this.props.onSerializedStateUpdated(
1663
+ this.getSerializedState(widgetProps),
1664
+ );
1665
+ }
1666
+
1667
+ return {
1668
+ lastUsedWidgetId,
1669
+ widgetProps,
1670
+ };
1671
+ },
1672
+ () => {
1673
+ // Wait until all components have rendered. In React 16 setState
1674
+ // callback fires immediately after this componentDidUpdate, and
1675
+ // there is no guarantee that parent/siblings components have
1676
+ // finished rendering.
1677
+ // TODO(jeff, CP-3128): Use Wonder Blocks Timing API
1678
+ // eslint-disable-next-line no-restricted-syntax
1679
+ setTimeout(() => {
1680
+ const cbResult = cb && cb();
1681
+ if (!silent) {
1682
+ this.props.onInteractWithWidget(id);
1683
+ }
1684
+ if (cbResult !== false) {
1685
+ // TODO(jack): For some reason, some widgets don't always
1686
+ // end up in refs here, which is repro-able if you make an
1687
+ // [[ orderer 1 ]] and copy-paste this, then change it to
1688
+ // be an [[ orderer 2 ]]. The resulting Renderer ends up
1689
+ // with an "orderer 2" ref but not an "orderer 1" ref.
1690
+ // @_@??
1691
+ // TODO(jack): Figure out why this is happening and fix it
1692
+ // As far as I can tell, this is only an issue in the
1693
+ // editor-page, so doing this shouldn't break clients
1694
+ // hopefully
1695
+ this._setCurrentFocus([id]);
1696
+ }
1697
+ }, 0);
1698
+ },
1699
+ );
1700
+ };
1701
+
1702
+ setInputValue: (
1703
+ path: FocusPath,
1704
+ newValue: string,
1705
+ focus: () => unknown,
1706
+ ) => void = (path, newValue, focus) => {
1707
+ // @ts-expect-error [FEI-5003] - TS2345 - Argument of type 'FocusPath' is not assignable to parameter of type 'List<any>'.
1708
+ const widgetId = _.first(path);
1709
+ // @ts-expect-error [FEI-5003] - TS2345 - Argument of type 'FocusPath' is not assignable to parameter of type 'List<any>'.
1710
+ const interWidgetPath = _.rest(path);
1711
+ const widget = this.getWidgetInstance(widgetId);
1712
+
1713
+ // Widget handles parsing of the interWidgetPath.
1714
+ widget?.setInputValue?.(interWidgetPath, newValue, focus);
1715
+ };
1716
+
1717
+ /**
1718
+ * Returns an array of the widget `.getUserInput()` results
1719
+ */
1720
+ getUserInput: () => ReadonlyArray<WidgetUserInput | null | undefined> =
1721
+ () => {
1722
+ return this.widgetIds.map((id: string) => {
1723
+ const widget = this.getWidgetInstance(id);
1724
+ if (widget && widget.getUserInput) {
1725
+ // TODO(Jeremy): Add the widget ID in here so we can more
1726
+ // easily correlate it to the widget state.
1727
+ return widget.getUserInput();
1728
+ }
1729
+ });
1730
+ };
1731
+
1732
+ /**
1733
+ * Returns an array of all widget IDs in the order they occur in
1734
+ * the content.
1735
+ */
1736
+ getWidgetIds: () => ReadonlyArray<string> = () => {
1737
+ return this.widgetIds;
1738
+ };
1739
+
1740
+ /**
1741
+ * Returns the result of `.getUserInput()` for each widget, in
1742
+ * a map from widgetId to userInput.
1743
+ * NOTE(jeremy): This function is hauntingly similar to `getUserInput` with
1744
+ * the major difference being that this function returns a map of
1745
+ * `widgetID` => UserInput and `getUserInput` simply returns an array. It
1746
+ * would be trivial to map between the results of each of these functions,
1747
+ * so we should aim to remove one of these functions.
1748
+ */
1749
+ getUserInputForWidgets: () => {
1750
+ [widgetId: string]: WidgetUserInput | null | undefined;
1751
+ } = () => {
1752
+ return mapObjectFromArray(this.widgetIds, (id) => {
1753
+ const widget = this.getWidgetInstance(id);
1754
+ if (widget && widget.getUserInput) {
1755
+ return widget.getUserInput();
1756
+ }
1757
+ });
1758
+ };
1759
+
1760
+ /**
1761
+ * Returns an object mapping from widget ID to perseus-style score.
1762
+ * The keys of this object are the values of the array returned
1763
+ * from `getWidgetIds`.
1764
+ */
1765
+ scoreWidgets: () => {
1766
+ [widgetId: string]: PerseusScore;
1767
+ } = () => {
1768
+ const widgetProps = this.state.widgetInfo;
1769
+ const onInputError = this.getApiOptions().onInputError;
1770
+
1771
+ const gradedWidgetIds = _.filter(this.widgetIds, (id) => {
1772
+ const props = widgetProps[id];
1773
+ const widgetIsGraded: boolean =
1774
+ props?.graded == null || props.graded;
1775
+ const widgetIsStatic = !!props?.static;
1776
+ // Ungraded widgets or widgets set to static shouldn't be graded.
1777
+ return widgetIsGraded && !widgetIsStatic;
1778
+ });
1779
+
1780
+ const widgetScores: Record<string, any> = {};
1781
+ _.each(gradedWidgetIds, (id) => {
1782
+ const props = widgetProps[id];
1783
+ const widget = this.getWidgetInstance(id);
1784
+ // widget can be undefined if it hasn't yet been rendered
1785
+ if (widget && widget.simpleValidate) {
1786
+ widgetScores[id] = widget.simpleValidate(
1787
+ props?.options,
1788
+ onInputError,
1789
+ );
1790
+ }
1791
+ });
1792
+
1793
+ return widgetScores;
1794
+ };
1795
+
1796
+ /**
1797
+ * Grades the content.
1798
+ */
1799
+ score: () => PerseusScore = () => {
1800
+ return _.reduce(this.scoreWidgets(), Util.combineScores, Util.noScore);
1801
+ };
1802
+
1803
+ guessAndScore: () => [any, PerseusScore] = () => {
1804
+ const totalGuess = this.getUserInput();
1805
+ const totalScore = this.score();
1806
+
1807
+ return [totalGuess, totalScore];
1808
+ };
1809
+
1810
+ examples: () => ReadonlyArray<string> | null | undefined = () => {
1811
+ const widgetIds = this.widgetIds;
1812
+ const examples = _.compact(
1813
+ _.map(widgetIds, (widgetId) => {
1814
+ const widget = this.getWidgetInstance(widgetId);
1815
+ return widget != null && widget.examples
1816
+ ? widget.examples()
1817
+ : null;
1818
+ }),
1819
+ );
1820
+
1821
+ // no widgets with examples
1822
+ if (!examples.length) {
1823
+ return null;
1824
+ }
1825
+
1826
+ const allEqual = _.all(examples, function (example) {
1827
+ return _.isEqual(examples[0], example);
1828
+ });
1829
+
1830
+ // some widgets have different examples
1831
+ // TODO(alex): handle this better
1832
+ if (!allEqual) {
1833
+ return null;
1834
+ }
1835
+
1836
+ return examples[0];
1837
+ };
1838
+
1839
+ // TranslationLinter callback
1840
+ handletranslationLintErrors: (lintErrors: ReadonlyArray<string>) => void = (
1841
+ lintErrors: ReadonlyArray<string>,
1842
+ ) => {
1843
+ if (!this._isMounted) {
1844
+ return;
1845
+ }
1846
+
1847
+ this.setState({
1848
+ translationLintErrors: lintErrors,
1849
+ });
1850
+ };
1851
+
1852
+ render(): React.ReactNode {
1853
+ const apiOptions = this.getApiOptions();
1854
+ const {KatexProvider} = getDependencies();
1855
+
1856
+ if (this.reuseMarkdown) {
1857
+ return this.lastRenderedMarkdown;
1858
+ }
1859
+
1860
+ const content = this.getContent(this.props, this.state);
1861
+ // `this.widgetIds` is appended to in `this.outputMarkdown`:
1862
+ this.widgetIds = [];
1863
+
1864
+ if (this.shouldRenderJiptPlaceholder(this.props, this.state)) {
1865
+ // Crowdin's JIPT (Just in place translation) uses a fake language
1866
+ // with language tag "en-pt" where the value of the translations
1867
+ // look like: {crwdns2657085:0}{crwdne2657085:0} where it keeps the
1868
+ // {crowdinId:ngettext variant}. We detect whether the current
1869
+ // content matches this, so we can take over rendering of
1870
+ // the perseus content as the translators interact with jipt.
1871
+ // We search for only part of the tag that crowdin uses to guard
1872
+ // against them changing the format on us. The full tag it looks
1873
+ // for can be found in https://cdn.crowdin.net/jipt/jipt.js
1874
+ // globalPhrase var.
1875
+
1876
+ // If we haven't already added this component to the registry do so
1877
+ // now. showHints() may cause this component to be rerendered
1878
+ // before jipt has a chance to replace its contents, so this check
1879
+ // will keep us from adding the component to the registry a second
1880
+ // time.
1881
+ if (!this.translationIndex) {
1882
+ this.translationIndex =
1883
+ getDependencies().rendererTranslationComponents.addComponent(
1884
+ this,
1885
+ );
1886
+ }
1887
+
1888
+ // For articles, we add jipt data to individual paragraphs. For
1889
+ // exercises, we add it to the renderer and let translators
1890
+ // translate the entire thing. For the article equivalent of
1891
+ // this if block, search this file for where we render a
1892
+ // QuestionParagraph, and see the `isJipt:` parameter sent to
1893
+ // PerseusMarkdown.parse()
1894
+ if (!apiOptions.isArticle) {
1895
+ // We now need to output this tag, as jipt looks for it to be
1896
+ // able to replace it with a translation that it runs an ajax
1897
+ // call to get. We add a data attribute with the index to the
1898
+ // Persues.TranslationComponent registry so that when jipt
1899
+ // calls its before_dom_insert we can lookup this component by
1900
+ // this attribute and render the text with markdown.
1901
+ return (
1902
+ <KatexProvider>
1903
+ <DefinitionProvider>
1904
+ <div
1905
+ data-perseus-component-index={
1906
+ this.translationIndex
1907
+ }
1908
+ >
1909
+ {content}
1910
+ </div>
1911
+ </DefinitionProvider>
1912
+ </KatexProvider>
1913
+ );
1914
+ }
1915
+ }
1916
+
1917
+ // Hacks:
1918
+ // We use mutable state here to figure out whether the output
1919
+ // had two columns.
1920
+ // It is updated to true by `this.outputMarkdown` if a
1921
+ // column break is found
1922
+ // TODO(aria): We now have a state variable threaded through
1923
+ // simple-markdown output. We should mutate it instead of
1924
+ // state on this component to do this in a less hacky way.
1925
+ this._isTwoColumn = false;
1926
+
1927
+ // Parse the string of markdown to a parse tree
1928
+ const parsedMarkdown = PerseusMarkdown.parse(content, {
1929
+ // Recognize crowdin IDs while translating articles
1930
+ // (This should never be hit by exercises, though if you
1931
+ // decide you want to add a check that this is an article,
1932
+ // go for it.)
1933
+ isJipt: this.translationIndex != null,
1934
+ });
1935
+
1936
+ // Optionally apply the linter to the parse tree
1937
+ if (this.props.linterContext.highlightLint) {
1938
+ // If highlightLint is true and lint is detected, this call
1939
+ // will modify the parse tree by adding lint nodes that will
1940
+ // serve to highlight the lint when rendered
1941
+ const context: Context = {
1942
+ content: this.props.content,
1943
+ widgets: this.props.widgets,
1944
+ ...this.props.linterContext,
1945
+ };
1946
+
1947
+ PerseusLinter.runLinter(parsedMarkdown, context, true);
1948
+
1949
+ // Apply the lint errors from the last TranslationLinter run.
1950
+ // TODO(joshuan): Support overlapping dots.
1951
+ this._translationLinter.applyLintErrors(parsedMarkdown, [
1952
+ ...this.state.translationLintErrors,
1953
+ ...(this.props.legacyPerseusLint || []),
1954
+ ]);
1955
+ }
1956
+
1957
+ // Render the linted markdown parse tree with React components
1958
+ const markdownContents = this.outputMarkdown(parsedMarkdown, {
1959
+ baseElements: apiOptions.baseElements,
1960
+ });
1961
+
1962
+ const className = classNames({
1963
+ [ApiClassNames.RENDERER]: true,
1964
+ [ApiClassNames.RESPONSIVE_RENDERER]: true,
1965
+ [ApiClassNames.TWO_COLUMN_RENDERER]: this._isTwoColumn,
1966
+ });
1967
+
1968
+ this.lastRenderedMarkdown = (
1969
+ <KatexProvider>
1970
+ <DefinitionProvider>
1971
+ <div className={className}>{markdownContents}</div>
1972
+ </DefinitionProvider>
1973
+ </KatexProvider>
1974
+ );
1975
+ return this.lastRenderedMarkdown;
1976
+ }
1977
+ }
1978
+
1979
+ const styles = {
1980
+ mobileZoomableParentFix: {
1981
+ // NOTE(abdul): There is an issue where transform animations will
1982
+ // cause the Zoomable component to disappear when running on the
1983
+ // native app on iPad (iOS 13). I found some answers online that recommend
1984
+ // transforming the parent in 3D space. Doing this forces hardware
1985
+ // acceleration, which causes the process to run on the GPU. It's not
1986
+ // clear to me why this fixes the issue, but it's suggested online
1987
+ // to people dealing with similar disappearance and flickering issues.
1988
+ transform: "translate3d(0,0,0)",
1989
+ },
1990
+ } as const;
1991
+
1992
+ export default Renderer;