@khanacademy/perseus 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (503) hide show
  1. package/.babelrc.js +1 -1
  2. package/CHANGELOG.md +7 -0
  3. package/dist/es/index.js +15 -1
  4. package/dist/es/index.js.map +1 -1
  5. package/dist/index.css +11934 -0
  6. package/dist/index.js +15 -1
  7. package/dist/index.js.map +1 -1
  8. package/dist/strings.js +148 -1
  9. package/package.json +40 -4
  10. package/src/README.md +7 -0
  11. package/src/__stories__/item-renderer.stories.jsx +27 -0
  12. package/src/__stories__/server-item-renderer.stories.jsx +27 -0
  13. package/src/__testdata__/item-renderer_testdata.js +161 -0
  14. package/src/__testdata__/renderer_testdata.js +123 -0
  15. package/src/__tests__/__snapshots__/item-renderer_test.jsx.snap +233 -0
  16. package/src/__tests__/__snapshots__/perseus-markdown_test.jsx.snap +516 -0
  17. package/src/__tests__/__snapshots__/pure-markdown_test.js.snap +113 -0
  18. package/src/__tests__/__snapshots__/renderer_test.jsx.snap +576 -0
  19. package/src/__tests__/__snapshots__/server-item-renderer_test.jsx.snap +306 -0
  20. package/src/__tests__/a11y_test.jsx +304 -0
  21. package/src/__tests__/item-renderer_test.jsx +892 -0
  22. package/src/__tests__/mock-asset-loading-widget.jsx +57 -0
  23. package/src/__tests__/mock-widget.jsx +35 -0
  24. package/src/__tests__/perseus-markdown_test.jsx +747 -0
  25. package/src/__tests__/pure-markdown_test.js +552 -0
  26. package/src/__tests__/renderability_test.jsx +481 -0
  27. package/src/__tests__/renderer-api_test.jsx +333 -0
  28. package/src/__tests__/renderer_test.jsx +1792 -0
  29. package/src/__tests__/server-item-renderer_test.jsx +516 -0
  30. package/src/__tests__/test-items/image-item.js +25 -0
  31. package/src/__tests__/test-items/input-number-1-item.js +27 -0
  32. package/src/__tests__/test-items/input-number-2-item.js +39 -0
  33. package/src/__tests__/test-items/table-item.js +30 -0
  34. package/src/__tests__/tex-wrangler_test.js +83 -0
  35. package/src/__tests__/util_test.js +41 -0
  36. package/src/a11y.js +46 -0
  37. package/src/all-widgets.js +9 -0
  38. package/src/article-renderer.jsx +215 -0
  39. package/src/asset-context.js +30 -0
  40. package/src/basic-widgets.js +13 -0
  41. package/src/components/__stories__/button-group.stories.jsx +61 -0
  42. package/src/components/__stories__/fixed-to-responsive.stories.jsx +132 -0
  43. package/src/components/__stories__/graph.stories.jsx +27 -0
  44. package/src/components/__stories__/graphie.stories.jsx +28 -0
  45. package/src/components/__stories__/hud.stories.jsx +36 -0
  46. package/src/components/__stories__/icon.stories.jsx +56 -0
  47. package/src/components/__stories__/image-loader.stories.jsx +60 -0
  48. package/src/components/__stories__/info-tip.stories.jsx +33 -0
  49. package/src/components/__stories__/inline-icon.stories.jsx +39 -0
  50. package/src/components/__stories__/input-with-examples.stories.jsx +51 -0
  51. package/src/components/__stories__/lint.stories.jsx +40 -0
  52. package/src/components/__stories__/math-input.stories.jsx +29 -0
  53. package/src/components/__stories__/math-output.stories.jsx +24 -0
  54. package/src/components/__stories__/multi-button-group.stories.jsx +69 -0
  55. package/src/components/__stories__/number-input.stories.jsx +42 -0
  56. package/src/components/__stories__/possible-answers.stories.jsx +22 -0
  57. package/src/components/__stories__/prop-check-box.stories.jsx +62 -0
  58. package/src/components/__stories__/range-input.stories.jsx +28 -0
  59. package/src/components/__stories__/simple-keypad-input.stories.jsx +28 -0
  60. package/src/components/__stories__/sortable.stories.jsx +73 -0
  61. package/src/components/__stories__/stub-tag-editor.stories.jsx +44 -0
  62. package/src/components/__stories__/svg-image.stories.jsx +60 -0
  63. package/src/components/__stories__/tex.stories.jsx +18 -0
  64. package/src/components/__stories__/text-buttons.stories.jsx +51 -0
  65. package/src/components/__stories__/text-input.stories.jsx +34 -0
  66. package/src/components/__stories__/text-list-editor.stories.jsx +33 -0
  67. package/src/components/__stories__/zoomable-tex.stories.jsx +39 -0
  68. package/src/components/__stories__/zoomable.stories.jsx +40 -0
  69. package/src/components/__testdata__/sorter_testdata.js +20 -0
  70. package/src/components/__tests__/__snapshots__/sortable_test.jsx.snap +79 -0
  71. package/src/components/__tests__/__snapshots__/sorter_test.jsx.snap +297 -0
  72. package/src/components/__tests__/number-input_test.jsx +112 -0
  73. package/src/components/__tests__/sortable_test.jsx +234 -0
  74. package/src/components/__tests__/sorter_test.jsx +111 -0
  75. package/src/components/__tests__/zoomable_test.jsx +388 -0
  76. package/src/components/button-group.jsx +131 -0
  77. package/src/components/constants.js +15 -0
  78. package/src/components/fixed-to-responsive.jsx +163 -0
  79. package/src/components/graph.jsx +414 -0
  80. package/src/components/graphie-classes.jsx +148 -0
  81. package/src/components/graphie-movables.jsx +342 -0
  82. package/src/components/graphie.jsx +353 -0
  83. package/src/components/highlighting/highlightable-content.jsx +245 -0
  84. package/src/components/highlighting/highlights.js +208 -0
  85. package/src/components/highlighting/ranges.js +299 -0
  86. package/src/components/highlighting/types.js +73 -0
  87. package/src/components/highlighting/ui/highlight-renderer.jsx +179 -0
  88. package/src/components/highlighting/ui/highlight-set-renderer.jsx +190 -0
  89. package/src/components/highlighting/ui/highlight-tooltip.jsx +153 -0
  90. package/src/components/highlighting/ui/highlighting-ui.jsx +163 -0
  91. package/src/components/highlighting/ui/selection-tracker.jsx +195 -0
  92. package/src/components/highlighting/ui/types.js +21 -0
  93. package/src/components/highlighting/ui/util.js +166 -0
  94. package/src/components/highlighting/word-indexer.jsx +155 -0
  95. package/src/components/hud.jsx +179 -0
  96. package/src/components/icon.jsx +149 -0
  97. package/src/components/image-loader.jsx +175 -0
  98. package/src/components/info-tip/info-tip.jsx +149 -0
  99. package/src/components/info-tip.jsx +38 -0
  100. package/src/components/inline-icon.jsx +81 -0
  101. package/src/components/input-with-examples.jsx +231 -0
  102. package/src/components/lint.jsx +456 -0
  103. package/src/components/math-input.jsx +282 -0
  104. package/src/components/math-output.jsx +128 -0
  105. package/src/components/multi-button-group.jsx +137 -0
  106. package/src/components/new-tooltip/README.md +110 -0
  107. package/src/components/new-tooltip/__stories__/flexible-position.stories.jsx +437 -0
  108. package/src/components/new-tooltip/__stories__/new-tooltip.stories.jsx +665 -0
  109. package/src/components/new-tooltip/__stories__/tooltip-arrow.stories.jsx +70 -0
  110. package/src/components/new-tooltip/__stories__/tooltip-bubble.stories.jsx +98 -0
  111. package/src/components/new-tooltip/__tests__/style-constants_test.js +8 -0
  112. package/src/components/new-tooltip/__tests__/util_test.js +47 -0
  113. package/src/components/new-tooltip/flexible-position.jsx +182 -0
  114. package/src/components/new-tooltip/new-tooltip.jsx +399 -0
  115. package/src/components/new-tooltip/style-constants.js +13 -0
  116. package/src/components/new-tooltip/tooltip-arrow.jsx +184 -0
  117. package/src/components/new-tooltip/tooltip-bubble.jsx +223 -0
  118. package/src/components/new-tooltip/tooltip-portal.jsx +101 -0
  119. package/src/components/new-tooltip/tooltip-positioner.jsx +529 -0
  120. package/src/components/new-tooltip/types.js +183 -0
  121. package/src/components/new-tooltip/util.js +27 -0
  122. package/src/components/number-input.jsx +253 -0
  123. package/src/components/possible-answers.jsx +36 -0
  124. package/src/components/prop-check-box.jsx +78 -0
  125. package/src/components/range-input.jsx +61 -0
  126. package/src/components/simple-keypad-input.jsx +91 -0
  127. package/src/components/sortable.jsx +872 -0
  128. package/src/components/stub-tag-editor.jsx +53 -0
  129. package/src/components/svg-image.jsx +762 -0
  130. package/src/components/tex-buttons.jsx +322 -0
  131. package/src/components/tex.jsx +70 -0
  132. package/src/components/text-input.jsx +82 -0
  133. package/src/components/text-list-editor.jsx +157 -0
  134. package/src/components/tooltip.jsx +395 -0
  135. package/src/components/visibility-observer/__stories__/visibility-observer.stories.jsx +140 -0
  136. package/src/components/visibility-observer/get-scroll-parent.js +45 -0
  137. package/src/components/visibility-observer/visibility-observer.js +109 -0
  138. package/src/components/visibility-observer/visibility-observer.jsx +123 -0
  139. package/src/components/zoomable-tex.jsx +70 -0
  140. package/src/components/zoomable.jsx +304 -0
  141. package/src/components.js +17 -0
  142. package/src/dependencies.js +22 -0
  143. package/src/error-boundary.jsx +48 -0
  144. package/src/extra-widgets.js +75 -0
  145. package/src/gorgon/README +40 -0
  146. package/src/gorgon/__tests__/matcher_test.js +497 -0
  147. package/src/gorgon/__tests__/rule_test.js +101 -0
  148. package/src/gorgon/__tests__/rules_test.js +487 -0
  149. package/src/gorgon/__tests__/selector-parser_test.js +52 -0
  150. package/src/gorgon/__tests__/tree-transformer_test.js +446 -0
  151. package/src/gorgon/gorgon.js +290 -0
  152. package/src/gorgon/proptypes.js +29 -0
  153. package/src/gorgon/rule.js +413 -0
  154. package/src/gorgon/rules/absolute-url.js +24 -0
  155. package/src/gorgon/rules/all-rules.js +72 -0
  156. package/src/gorgon/rules/blockquoted-math.js +10 -0
  157. package/src/gorgon/rules/blockquoted-widget.js +10 -0
  158. package/src/gorgon/rules/double-spacing-after-terminal.js +12 -0
  159. package/src/gorgon/rules/extra-content-spacing.js +12 -0
  160. package/src/gorgon/rules/heading-level-1.js +14 -0
  161. package/src/gorgon/rules/heading-level-skip.js +20 -0
  162. package/src/gorgon/rules/heading-sentence-case.js +11 -0
  163. package/src/gorgon/rules/heading-title-case.js +63 -0
  164. package/src/gorgon/rules/image-alt-text.js +21 -0
  165. package/src/gorgon/rules/image-in-table.js +10 -0
  166. package/src/gorgon/rules/image-spaces-around-urls.js +35 -0
  167. package/src/gorgon/rules/image-widget.js +50 -0
  168. package/src/gorgon/rules/link-click-here.js +11 -0
  169. package/src/gorgon/rules/lint-utils.js +48 -0
  170. package/src/gorgon/rules/long-paragraph.js +14 -0
  171. package/src/gorgon/rules/math-adjacent.js +10 -0
  172. package/src/gorgon/rules/math-align-extra-break.js +11 -0
  173. package/src/gorgon/rules/math-align-linebreaks.js +43 -0
  174. package/src/gorgon/rules/math-empty.js +10 -0
  175. package/src/gorgon/rules/math-font-size.js +12 -0
  176. package/src/gorgon/rules/math-frac.js +10 -0
  177. package/src/gorgon/rules/math-nested.js +11 -0
  178. package/src/gorgon/rules/math-starts-with-space.js +12 -0
  179. package/src/gorgon/rules/math-text-empty.js +10 -0
  180. package/src/gorgon/rules/math-without-dollars.js +14 -0
  181. package/src/gorgon/rules/nested-lists.js +11 -0
  182. package/src/gorgon/rules/profanity.js +10 -0
  183. package/src/gorgon/rules/table-missing-cells.js +20 -0
  184. package/src/gorgon/rules/unbalanced-code-delimiters.js +14 -0
  185. package/src/gorgon/rules/unescaped-dollar.js +10 -0
  186. package/src/gorgon/rules/widget-in-table.js +10 -0
  187. package/src/gorgon/selector.js +506 -0
  188. package/src/gorgon/tree-transformer.js +588 -0
  189. package/src/hint-renderer.jsx +159 -0
  190. package/src/hints-renderer.jsx +359 -0
  191. package/src/icon-paths.js +120 -0
  192. package/src/index.js +149 -2
  193. package/src/init.js +49 -0
  194. package/src/interaction-tracker.js +60 -0
  195. package/src/interactive2/__tests__/movable-mock.js +48 -0
  196. package/src/interactive2/__tests__/movable-point_test.js +146 -0
  197. package/src/interactive2/interactive-util.js +152 -0
  198. package/src/interactive2/movable-helper-methods.js +160 -0
  199. package/src/interactive2/movable-line-options.js +392 -0
  200. package/src/interactive2/movable-line.js +322 -0
  201. package/src/interactive2/movable-point-options.js +190 -0
  202. package/src/interactive2/movable-point.jsx +604 -0
  203. package/src/interactive2/movable-polygon-options.js +351 -0
  204. package/src/interactive2/movable-polygon.js +367 -0
  205. package/src/interactive2/movable.js +273 -0
  206. package/src/interactive2/objective_.js +79 -0
  207. package/src/interactive2/types.js +3 -0
  208. package/src/interactive2/wrapped-defaults.js +86 -0
  209. package/src/interactive2/wrapped-ellipse.js +87 -0
  210. package/src/interactive2/wrapped-line.js +109 -0
  211. package/src/interactive2/wrapped-path.js +47 -0
  212. package/src/interactive2.js +91 -0
  213. package/src/item-renderer.jsx +518 -0
  214. package/src/item-version.js +12 -0
  215. package/src/jipt-paragraphs.js +44 -0
  216. package/src/jquery.mobile.vmouse.js +545 -0
  217. package/src/keypad-context.js +33 -0
  218. package/src/loading-context.js +20 -0
  219. package/src/logging/log.js +107 -0
  220. package/src/logging/log_test.js +41 -0
  221. package/src/mixins/changeable.jsx +115 -0
  222. package/src/mixins/editor-jsonify.jsx +13 -0
  223. package/src/mixins/provide-keypad.jsx +131 -0
  224. package/src/mixins/widget-jsonify-deprecated.jsx +23 -0
  225. package/src/mixins/widget-prop-blacklist.js +36 -0
  226. package/src/multi-items/__stories__/multi-renderer.stories.js +88 -0
  227. package/src/multi-items/__testdata__/multi-renderer_testdata.js +174 -0
  228. package/src/multi-items/__tests__/__snapshots__/multi-renderer_test.js.snap +994 -0
  229. package/src/multi-items/__tests__/items_test.js +245 -0
  230. package/src/multi-items/__tests__/multi-renderer_test.js +903 -0
  231. package/src/multi-items/__tests__/prop-type-builders_test.js +134 -0
  232. package/src/multi-items/__tests__/shapes_test.js +44 -0
  233. package/src/multi-items/__tests__/trees_test.js +316 -0
  234. package/src/multi-items/item-types.js +55 -0
  235. package/src/multi-items/items.js +176 -0
  236. package/src/multi-items/multi-renderer.jsx +542 -0
  237. package/src/multi-items/prop-type-builders.js +105 -0
  238. package/src/multi-items/shape-types.js +47 -0
  239. package/src/multi-items/shapes.js +53 -0
  240. package/src/multi-items/tree-types.js +42 -0
  241. package/src/multi-items/trees.js +345 -0
  242. package/src/multi-items.js +76 -0
  243. package/src/not-gorgon.js +129 -0
  244. package/src/perseus-api.jsx +270 -0
  245. package/src/perseus-env.js +26 -0
  246. package/src/perseus-error.js +19 -0
  247. package/src/perseus-markdown.jsx +434 -0
  248. package/src/perseus-types.js +1597 -0
  249. package/src/pure-markdown.js +346 -0
  250. package/src/question-paragraph.jsx +30 -0
  251. package/src/renderability.jsx +108 -0
  252. package/src/renderer.jsx +1931 -0
  253. package/src/server-item-renderer.jsx +467 -0
  254. package/src/sigfigs.jsx +492 -0
  255. package/src/styles/articles.less +748 -0
  256. package/src/styles/constants.js +119 -0
  257. package/src/styles/global-constants.js +350 -0
  258. package/src/styles/global-styles.js +307 -0
  259. package/src/styles/khan-exercise.css +1304 -0
  260. package/src/styles/media-queries.js +48 -0
  261. package/src/styles/perseus-renderer.less +1008 -0
  262. package/src/styles/shared.js +132 -0
  263. package/src/styles/util.less +47 -0
  264. package/src/styles/variables.boostrap.less +301 -0
  265. package/src/styles/variables.less +68 -0
  266. package/src/styles/variables.shared.less +356 -0
  267. package/src/styles/widgets/categorizer.less +39 -0
  268. package/src/styles/widgets/definition.less +86 -0
  269. package/src/styles/widgets/dropdown.less +3 -0
  270. package/src/styles/widgets/expression.less +147 -0
  271. package/src/styles/widgets/graded-group.less +36 -0
  272. package/src/styles/widgets/grapher.less +11 -0
  273. package/src/styles/widgets/group.less +19 -0
  274. package/src/styles/widgets/image.less +29 -0
  275. package/src/styles/widgets/interactive-graph.less +61 -0
  276. package/src/styles/widgets/label-image.less +41 -0
  277. package/src/styles/widgets/matcher.less +8 -0
  278. package/src/styles/widgets/matrix.less +120 -0
  279. package/src/styles/widgets/measurer.less +20 -0
  280. package/src/styles/widgets/orderer.less +97 -0
  281. package/src/styles/widgets/passage.less +151 -0
  282. package/src/styles/widgets/plotter.less +33 -0
  283. package/src/styles/widgets/radio.less +52 -0
  284. package/src/styles/widgets/reaction-diagram.less +35 -0
  285. package/src/styles/widgets/sequence.less +5 -0
  286. package/src/styles/widgets/sortable.less +127 -0
  287. package/src/styles/widgets/table.less +56 -0
  288. package/src/styles/widgets/transformer.less +28 -0
  289. package/src/styles/widgets/unit.less +14 -0
  290. package/src/tex-wrangler.js +176 -0
  291. package/src/traversal.jsx +141 -0
  292. package/src/types.js +538 -0
  293. package/src/util/a11y.js +24 -0
  294. package/src/util/answer-types.js +862 -0
  295. package/src/util/answer-types_test.js +115 -0
  296. package/src/util/colors.js +94 -0
  297. package/src/util/fix-passage-refs.jsx +122 -0
  298. package/src/util/graph-utils.js +16 -0
  299. package/src/util/graphie.js +1585 -0
  300. package/src/util/interactive.js +4071 -0
  301. package/src/util/katex-preprocess.js +16 -0
  302. package/src/util/math.js +296 -0
  303. package/src/util/mobile-native-utils.js +14 -0
  304. package/src/util/mobile-native-utils_test.js +24 -0
  305. package/src/util/register-all-widgets-for-testing.js +12 -0
  306. package/src/util/scroll-utils.js +162 -0
  307. package/src/util/sizing-utils.js +46 -0
  308. package/src/util/tex.js +203 -0
  309. package/src/util/url-utils.js +36 -0
  310. package/src/util.js +990 -0
  311. package/src/version.js +11 -0
  312. package/src/widget-container.jsx +174 -0
  313. package/src/widgets/__stories__/categorizer.stories.jsx +15 -0
  314. package/src/widgets/__stories__/cs-program.stories.jsx +15 -0
  315. package/src/widgets/__stories__/definition.stories.jsx +38 -0
  316. package/src/widgets/__stories__/dropdown.stories.jsx +15 -0
  317. package/src/widgets/__stories__/explanation.stories.jsx +19 -0
  318. package/src/widgets/__stories__/expression.stories.jsx +26 -0
  319. package/src/widgets/__stories__/graded-group-set.stories.jsx +32 -0
  320. package/src/widgets/__stories__/graded-group.stories.jsx +32 -0
  321. package/src/widgets/__stories__/grapher.stories.jsx +47 -0
  322. package/src/widgets/__stories__/group.stories.jsx +15 -0
  323. package/src/widgets/__stories__/iframe.stories.jsx +15 -0
  324. package/src/widgets/__stories__/image.stories.jsx +62 -0
  325. package/src/widgets/__stories__/input-number.stories.jsx +90 -0
  326. package/src/widgets/__stories__/interaction.stories.jsx +15 -0
  327. package/src/widgets/__stories__/interactive-graph.stories.jsx +61 -0
  328. package/src/widgets/__stories__/matcher.stories.jsx +15 -0
  329. package/src/widgets/__stories__/matrix.stories.jsx +15 -0
  330. package/src/widgets/__stories__/number-line.stories.jsx +15 -0
  331. package/src/widgets/__stories__/numeric-input.stories.jsx +19 -0
  332. package/src/widgets/__stories__/orderer.stories.jsx +15 -0
  333. package/src/widgets/__stories__/passage-ref.stories.jsx +19 -0
  334. package/src/widgets/__stories__/passage.stories.jsx +19 -0
  335. package/src/widgets/__stories__/radio.stories.jsx +82 -0
  336. package/src/widgets/__stories__/sorter.stories.jsx +15 -0
  337. package/src/widgets/__stories__/transformer.stories.jsx +15 -0
  338. package/src/widgets/__stories__/video.stories.jsx +15 -0
  339. package/src/widgets/__testdata__/categorizer_testdata.js +50 -0
  340. package/src/widgets/__testdata__/cs-program_testdata.js +28 -0
  341. package/src/widgets/__testdata__/dropdown_testdata.js +34 -0
  342. package/src/widgets/__testdata__/explanation_testdata.js +56 -0
  343. package/src/widgets/__testdata__/expression_testdata.js +95 -0
  344. package/src/widgets/__testdata__/graded-group-set_testdata.js +358 -0
  345. package/src/widgets/__testdata__/graded-group_testdata.js +79 -0
  346. package/src/widgets/__testdata__/grapher_testdata.js +376 -0
  347. package/src/widgets/__testdata__/group_testdata.js +156 -0
  348. package/src/widgets/__testdata__/iframe_testdata.js +36 -0
  349. package/src/widgets/__testdata__/image_testdata.js +38 -0
  350. package/src/widgets/__testdata__/input-number_testdata.js +73 -0
  351. package/src/widgets/__testdata__/interaction_testdata.js +264 -0
  352. package/src/widgets/__testdata__/interactive-graph_testdata.js +646 -0
  353. package/src/widgets/__testdata__/matcher_testdata.js +34 -0
  354. package/src/widgets/__testdata__/matrix_testdata.js +29 -0
  355. package/src/widgets/__testdata__/number-line_testdata.js +31 -0
  356. package/src/widgets/__testdata__/numeric-input_testdata.js +266 -0
  357. package/src/widgets/__testdata__/orderer_testdata.js +58 -0
  358. package/src/widgets/__testdata__/passage-ref_testdata.js +41 -0
  359. package/src/widgets/__testdata__/passage_testdata.js +71 -0
  360. package/src/widgets/__testdata__/radio_testdata.js +210 -0
  361. package/src/widgets/__testdata__/sorter_testdata.js +20 -0
  362. package/src/widgets/__testdata__/transformer_testdata.js +109 -0
  363. package/src/widgets/__testdata__/video_testdata.js +22 -0
  364. package/src/widgets/__tests__/__snapshots__/categorizer_test.jsx.snap +928 -0
  365. package/src/widgets/__tests__/__snapshots__/cs-program_test.jsx.snap +75 -0
  366. package/src/widgets/__tests__/__snapshots__/definition_test.js.snap +99 -0
  367. package/src/widgets/__tests__/__snapshots__/dropdown_test.jsx.snap +143 -0
  368. package/src/widgets/__tests__/__snapshots__/explanation_test.js.snap +473 -0
  369. package/src/widgets/__tests__/__snapshots__/expression_test.jsx.snap +38 -0
  370. package/src/widgets/__tests__/__snapshots__/graded-group-set-jipt_test.jsx.snap +754 -0
  371. package/src/widgets/__tests__/__snapshots__/graded-group-set_test.jsx.snap +310 -0
  372. package/src/widgets/__tests__/__snapshots__/graded-group_test.jsx.snap +863 -0
  373. package/src/widgets/__tests__/__snapshots__/grapher_test.jsx.snap +2880 -0
  374. package/src/widgets/__tests__/__snapshots__/group_test.jsx.snap +1154 -0
  375. package/src/widgets/__tests__/__snapshots__/iframe_test.jsx.snap +69 -0
  376. package/src/widgets/__tests__/__snapshots__/image_test.jsx.snap +388 -0
  377. package/src/widgets/__tests__/__snapshots__/input-number_test.jsx.snap +978 -0
  378. package/src/widgets/__tests__/__snapshots__/interaction_test.jsx.snap +311 -0
  379. package/src/widgets/__tests__/__snapshots__/interactive-graph_test.jsx.snap +2915 -0
  380. package/src/widgets/__tests__/__snapshots__/matcher_test.jsx.snap +1039 -0
  381. package/src/widgets/__tests__/__snapshots__/matrix_test.jsx.snap +450 -0
  382. package/src/widgets/__tests__/__snapshots__/number-line_test.jsx.snap +888 -0
  383. package/src/widgets/__tests__/__snapshots__/numeric-input_test.jsx.snap +930 -0
  384. package/src/widgets/__tests__/__snapshots__/orderer_test.jsx.snap +275 -0
  385. package/src/widgets/__tests__/__snapshots__/passage-ref_test.jsx.snap +446 -0
  386. package/src/widgets/__tests__/__snapshots__/passage_test.jsx.snap +1988 -0
  387. package/src/widgets/__tests__/__snapshots__/radio_test.jsx.snap +7107 -0
  388. package/src/widgets/__tests__/__snapshots__/video_test.jsx.snap +163 -0
  389. package/src/widgets/__tests__/categorizer_test.jsx +205 -0
  390. package/src/widgets/__tests__/cs-program_test.jsx +45 -0
  391. package/src/widgets/__tests__/definition_test.js +152 -0
  392. package/src/widgets/__tests__/dropdown_test.jsx +106 -0
  393. package/src/widgets/__tests__/explanation_test.js +199 -0
  394. package/src/widgets/__tests__/expression_test.jsx +439 -0
  395. package/src/widgets/__tests__/graded-group-set-jipt_test.jsx +33 -0
  396. package/src/widgets/__tests__/graded-group-set_test.jsx +270 -0
  397. package/src/widgets/__tests__/graded-group_test.jsx +233 -0
  398. package/src/widgets/__tests__/grapher.cypress.jsx +486 -0
  399. package/src/widgets/__tests__/grapher_test.jsx +35 -0
  400. package/src/widgets/__tests__/group_test.jsx +512 -0
  401. package/src/widgets/__tests__/iframe_test.jsx +47 -0
  402. package/src/widgets/__tests__/image_test.jsx +65 -0
  403. package/src/widgets/__tests__/input-number_test.jsx +360 -0
  404. package/src/widgets/__tests__/interaction_test.jsx +33 -0
  405. package/src/widgets/__tests__/interactive-graph.cypress.jsx +697 -0
  406. package/src/widgets/__tests__/interactive-graph_test.jsx +105 -0
  407. package/src/widgets/__tests__/matcher_test.jsx +157 -0
  408. package/src/widgets/__tests__/matrix_test.jsx +84 -0
  409. package/src/widgets/__tests__/number-line_test.jsx +81 -0
  410. package/src/widgets/__tests__/numeric-input_test.jsx +574 -0
  411. package/src/widgets/__tests__/orderer_test.jsx +75 -0
  412. package/src/widgets/__tests__/passage-ref_test.jsx +166 -0
  413. package/src/widgets/__tests__/passage_test.jsx +133 -0
  414. package/src/widgets/__tests__/radio/base-radio_test.jsx +82 -0
  415. package/src/widgets/__tests__/radio_test.jsx +651 -0
  416. package/src/widgets/__tests__/renderQuestion.jsx +65 -0
  417. package/src/widgets/__tests__/unit_test.jsx +144 -0
  418. package/src/widgets/__tests__/video_test.jsx +55 -0
  419. package/src/widgets/categorizer.jsx +338 -0
  420. package/src/widgets/cs-program.jsx +228 -0
  421. package/src/widgets/definition.jsx +141 -0
  422. package/src/widgets/dropdown.jsx +143 -0
  423. package/src/widgets/explanation.jsx +315 -0
  424. package/src/widgets/expression.jsx +654 -0
  425. package/src/widgets/graded-group-answer-bar.jsx +152 -0
  426. package/src/widgets/graded-group-set.jsx +285 -0
  427. package/src/widgets/graded-group.jsx +448 -0
  428. package/src/widgets/grapher/util.jsx +819 -0
  429. package/src/widgets/grapher.jsx +658 -0
  430. package/src/widgets/group.jsx +213 -0
  431. package/src/widgets/iframe.jsx +199 -0
  432. package/src/widgets/image.jsx +309 -0
  433. package/src/widgets/input-number.jsx +410 -0
  434. package/src/widgets/interaction/arrow-picker.jsx +39 -0
  435. package/src/widgets/interaction/color-picker.jsx +69 -0
  436. package/src/widgets/interaction/dash-picker.jsx +46 -0
  437. package/src/widgets/interaction/element-container.jsx +97 -0
  438. package/src/widgets/interaction.jsx +742 -0
  439. package/src/widgets/interactive-graph.jsx +2821 -0
  440. package/src/widgets/label-image/__stories__/answer-choices.stories.jsx +93 -0
  441. package/src/widgets/label-image/__stories__/marker.stories.jsx +172 -0
  442. package/src/widgets/label-image/answer-choices.jsx +311 -0
  443. package/src/widgets/label-image/marker.jsx +365 -0
  444. package/src/widgets/label-image/types.js +33 -0
  445. package/src/widgets/label-image.jsx +841 -0
  446. package/src/widgets/label-image_test.js +840 -0
  447. package/src/widgets/lights-puzzle.jsx +356 -0
  448. package/src/widgets/matcher.jsx +271 -0
  449. package/src/widgets/matrix.jsx +615 -0
  450. package/src/widgets/measurer.jsx +215 -0
  451. package/src/widgets/molecule/__tests__/layout_test.jsx +159 -0
  452. package/src/widgets/molecule/__tests__/parser_test.jsx +142 -0
  453. package/src/widgets/molecule/molecule-drawing.jsx +175 -0
  454. package/src/widgets/molecule/molecule-layout.jsx +367 -0
  455. package/src/widgets/molecule/smiles-parser.jsx +300 -0
  456. package/src/widgets/molecule.jsx +172 -0
  457. package/src/widgets/number-line.jsx +858 -0
  458. package/src/widgets/numeric-input.jsx +622 -0
  459. package/src/widgets/orderer.jsx +753 -0
  460. package/src/widgets/passage/__tests__/passage-markdown_test.jsx +382 -0
  461. package/src/widgets/passage/get-line-height-for-node.js +10 -0
  462. package/src/widgets/passage/passage-markdown.jsx +416 -0
  463. package/src/widgets/passage-ref-target.jsx +70 -0
  464. package/src/widgets/passage-ref.jsx +198 -0
  465. package/src/widgets/passage.jsx +601 -0
  466. package/src/widgets/plotter.jsx +1198 -0
  467. package/src/widgets/radio/__stories__/base-radio.stories.jsx +40 -0
  468. package/src/widgets/radio/__stories__/choice-icon.stories.jsx +157 -0
  469. package/src/widgets/radio/__stories__/choice-none-above.stories.jsx +26 -0
  470. package/src/widgets/radio/__stories__/cross-out-button.stories.jsx +64 -0
  471. package/src/widgets/radio/__stories__/cross-out-menu.stories.jsx +59 -0
  472. package/src/widgets/radio/__stories__/cross-out-tooltip.stories.jsx +59 -0
  473. package/src/widgets/radio/__stories__/focus-ring.stories.jsx +38 -0
  474. package/src/widgets/radio/__stories__/option-status.stories.jsx +118 -0
  475. package/src/widgets/radio/__stories__/toggleable-radio-button.stories.jsx +28 -0
  476. package/src/widgets/radio/base-radio.jsx +642 -0
  477. package/src/widgets/radio/choice-icon.jsx +468 -0
  478. package/src/widgets/radio/choice-none-above.jsx +54 -0
  479. package/src/widgets/radio/choice.jsx +692 -0
  480. package/src/widgets/radio/choice.jsx.fixture.js +64 -0
  481. package/src/widgets/radio/cross-out-button.jsx +136 -0
  482. package/src/widgets/radio/cross-out-menu-wrapper.jsx +492 -0
  483. package/src/widgets/radio/cross-out-tooltip.jsx +129 -0
  484. package/src/widgets/radio/focus-ring.jsx +51 -0
  485. package/src/widgets/radio/option-status.jsx +89 -0
  486. package/src/widgets/radio/toggleable-radio-button.jsx +133 -0
  487. package/src/widgets/radio/util.js +32 -0
  488. package/src/widgets/radio/util_test.js +19 -0
  489. package/src/widgets/radio/widget.jsx +470 -0
  490. package/src/widgets/radio.jsx +156 -0
  491. package/src/widgets/reaction-diagram.jsx +136 -0
  492. package/src/widgets/sequence.jsx +140 -0
  493. package/src/widgets/simulator.jsx +808 -0
  494. package/src/widgets/sorter.jsx +146 -0
  495. package/src/widgets/table.jsx +395 -0
  496. package/src/widgets/transformer.jsx +2870 -0
  497. package/src/widgets/unit.jsx +371 -0
  498. package/src/widgets/video-transcript-link.jsx +84 -0
  499. package/src/widgets/video-transcript-link_test.js +211 -0
  500. package/src/widgets/video.jsx +131 -0
  501. package/src/widgets.js +431 -0
  502. package/src/zoom.js +539 -0
  503. package/src/__tests__/index.test.js +0 -14
@@ -0,0 +1,872 @@
1
+ /* eslint-disable @babel/no-invalid-this, react/forbid-prop-types, react/no-unsafe, react/sort-comp */
2
+ // @flow
3
+ import {CircularSpinner} from "@khanacademy/wonder-blocks-progress-spinner";
4
+ import {StyleSheet, css} from "aphrodite";
5
+ import $ from "jquery";
6
+ import PropTypes from "prop-types";
7
+ import * as React from "react";
8
+ import ReactDOM from "react-dom";
9
+ import _ from "underscore";
10
+
11
+ import {getDependencies} from "../dependencies.js";
12
+ import Gorgon from "../gorgon/gorgon.js";
13
+ import {linterContextProps, linterContextDefault} from "../gorgon/proptypes.js";
14
+ import {ClassNames as ApiClassNames} from "../perseus-api.jsx";
15
+ import Renderer from "../renderer.jsx";
16
+ import Util from "../util.js";
17
+
18
+ import type {LinterContextProps} from "../types";
19
+ const HORIZONTAL = "horizontal";
20
+ const VERTICAL = "vertical";
21
+
22
+ const STATIC = "static";
23
+ const DRAGGING = "dragging";
24
+ const ANIMATING = "animating";
25
+ const DISABLED = "disabled";
26
+
27
+ // Augment the given position with the scroll position of the offset parent.
28
+ const addOffsetParentScroll = ($el, position) => {
29
+ // NOTE(slim): $(el).offsetParent() is NOT equivalent to $el[0].offsetParent.
30
+ // Specifically, when `el` is not positioned, then the native HTMLElement.offsetParent
31
+ // returns the closest table element, which in this case is the <td> rendered by the
32
+ // Perseus Matcher widget.
33
+ // Conversely, jQuery's offsetParent() always returns the closest positioned ancestor,
34
+ // even when the element is not positioned.
35
+ const $offsetParent = $el.offsetParent();
36
+ return {
37
+ top: position.top + $offsetParent.scrollTop(),
38
+ left: position.left + $offsetParent.scrollLeft(),
39
+ };
40
+ };
41
+
42
+ // A placeholder that appears in the sortable whenever an item is dragged.
43
+ class Placeholder extends React.Component<$FlowFixMe> {
44
+ static propTypes = {
45
+ layout: PropTypes.oneOf([HORIZONTAL, VERTICAL]),
46
+ width: PropTypes.number.isRequired,
47
+ height: PropTypes.number.isRequired,
48
+ };
49
+
50
+ render(): React.Node {
51
+ const {layout} = this.props;
52
+ const className = css(
53
+ styles.card,
54
+ styles.placeholder,
55
+ layout === HORIZONTAL && styles.horizontalCard,
56
+ );
57
+ const style: $FlowFixMe = {
58
+ width: this.props.width,
59
+ height: this.props.height,
60
+ };
61
+
62
+ if (this.props.margin != null) {
63
+ style.margin = this.props.margin;
64
+ }
65
+
66
+ return <li className={className} style={style} />;
67
+ }
68
+ }
69
+
70
+ type DraggableProps = $FlowFixMe;
71
+ type DraggableState = $FlowFixMe;
72
+
73
+ // A draggable item in the sortable. Can be in one of four states:
74
+ // Static: The item is not being interacted with.
75
+ // Dragging: The item is being dragged.
76
+ // Animating: The item has been released, and is moving to its destination.
77
+ // Disabled: The item cannot be interacted with.
78
+ //
79
+ // Usual flow: Static -> Dragging -> Animating -> Static
80
+ // [Dis|en]abling: Static|Dragging|Animating -> Disabled -> Static
81
+ class Draggable extends React.Component<DraggableProps, DraggableState> {
82
+ // Handler returned by requestAnimationFrame.
83
+ animationFrameRequest = null;
84
+ isMouseMoveUpBound: boolean;
85
+ _mounted: boolean;
86
+
87
+ static propTypes = {
88
+ content: PropTypes.string.isRequired,
89
+ endPosition: PropTypes.object.isRequired,
90
+ includePadding: PropTypes.bool,
91
+ layout: PropTypes.oneOf([HORIZONTAL, VERTICAL]),
92
+ onAnimationEnd: PropTypes.func.isRequired,
93
+ onMouseDown: PropTypes.func.isRequired,
94
+ onMouseMove: PropTypes.func.isRequired,
95
+ onMouseUp: PropTypes.func.isRequired,
96
+ onRender: PropTypes.func.isRequired,
97
+ type: PropTypes.oneOf([STATIC, DRAGGING, ANIMATING, DISABLED]),
98
+ linterContext: linterContextProps,
99
+ };
100
+
101
+ static defaultProps = {
102
+ includePadding: true,
103
+ type: STATIC,
104
+ linterContext: linterContextDefault,
105
+ };
106
+
107
+ state: DraggableState = {
108
+ startPosition: {left: 0, top: 0},
109
+ startMouse: {left: 0, top: 0},
110
+ mouse: {left: 0, top: 0},
111
+ };
112
+
113
+ componentDidMount() {
114
+ this._mounted = true;
115
+ this.isMouseMoveUpBound = false;
116
+
117
+ // On touch devices, we set up our own touchmove handler because React
118
+ // all event subscriptions using `OnTouchMove` props are non-passive.
119
+ // See: https://github.com/facebook/react/issues/6436
120
+ // Also, we can't subscribe to `ontouchmove` within the `ontouchstart`
121
+ // handler because of a WebKit bug:
122
+ // https://github.com/atlassian/react-beautiful-dnd/issues/413 and
123
+ // https://bugs.webkit.org/show_bug.cgi?id=184250
124
+ document.addEventListener(
125
+ "touchmove",
126
+ this.onMouseMove,
127
+ // Not all browsers support passive events, and when they don't
128
+ // this third paramter is just a boolean. If we pass the "options"
129
+ // object, it's interpreted as `capture=true` (which we don't want!)
130
+ Util.supportsPassiveEvents() ? {passive: false} : false,
131
+ );
132
+ }
133
+
134
+ componentWillUnmount() {
135
+ this._mounted = false;
136
+ // Event handlers should be unbound before component unmounting, but
137
+ // just in case...
138
+ if (this.isMouseMoveUpBound) {
139
+ this.unbindMouseMoveUp();
140
+ }
141
+
142
+ if (this.animationFrameRequest) {
143
+ // TODO(jeff, CP-3128): Use Wonder Blocks Timing API.
144
+ // eslint-disable-next-line no-restricted-syntax
145
+ cancelAnimationFrame(this.animationFrameRequest);
146
+ }
147
+
148
+ document.removeEventListener("touchmove", this.onMouseMove);
149
+ }
150
+
151
+ getCurrentPosition = () => {
152
+ return {
153
+ left:
154
+ this.state.startPosition.left +
155
+ this.state.mouse.left -
156
+ this.state.startMouse.left,
157
+ top:
158
+ this.state.startPosition.top +
159
+ this.state.mouse.top -
160
+ this.state.startMouse.top,
161
+ };
162
+ };
163
+
164
+ render(): React.Node {
165
+ const {includePadding, layout, type} = this.props;
166
+
167
+ // We need to keep backwards compatbility with rules specified directly
168
+ // in CSS. Hence the hacky tacking on of manual classNames.
169
+ // See sortable.less for details.
170
+ let className =
171
+ css(
172
+ styles.card,
173
+ styles.draggable,
174
+ layout === HORIZONTAL && styles.horizontalCard,
175
+ layout === VERTICAL && styles.verticalCard,
176
+ type === DRAGGING && styles.dragging,
177
+ type === DISABLED && styles.disabled,
178
+ !includePadding && styles.unpaddedCard,
179
+ ) +
180
+ " " +
181
+ ApiClassNames.INTERACTIVE +
182
+ " perseus-sortable-draggable";
183
+
184
+ if (!includePadding) {
185
+ className += " perseus-sortable-draggable-unpadded";
186
+ }
187
+
188
+ const style: $FlowFixMe = {
189
+ position: "static",
190
+ };
191
+
192
+ if (this.props.type === DRAGGING || this.props.type === ANIMATING) {
193
+ _.extend(style, {position: "absolute"}, this.getCurrentPosition());
194
+ }
195
+
196
+ if (this.props.width) {
197
+ style.width = this.props.width + 1; // Fix for non-integer widths
198
+ }
199
+ if (this.props.height) {
200
+ style.height = this.props.height;
201
+ }
202
+ if (this.props.margin != null) {
203
+ style.margin = this.props.margin;
204
+ }
205
+ return (
206
+ <li
207
+ className={className}
208
+ style={style}
209
+ onMouseDown={this.onMouseDown}
210
+ onTouchStart={this.onMouseDown}
211
+ onTouchMove={this.onMouseMove}
212
+ onTouchEnd={this.onMouseUp}
213
+ onTouchCancel={this.onMouseUp}
214
+ >
215
+ <Renderer
216
+ content={this.props.content}
217
+ linterContext={Gorgon.pushContextStack(
218
+ this.props.linterContext,
219
+ "draggable",
220
+ )}
221
+ onRender={this.props.onRender}
222
+ />
223
+ </li>
224
+ );
225
+ }
226
+
227
+ componentDidUpdate(prevProps) {
228
+ if (this.props.type === prevProps.type) {
229
+ return;
230
+ }
231
+
232
+ if (this.props.type === ANIMATING) {
233
+ // Start animating
234
+ const current = this.getCurrentPosition();
235
+ const duration =
236
+ 15 *
237
+ Math.sqrt(
238
+ Math.sqrt(
239
+ Math.pow(
240
+ this.props.endPosition.left - current.left,
241
+ 2,
242
+ ) +
243
+ Math.pow(
244
+ this.props.endPosition.top - current.top,
245
+ 2,
246
+ ),
247
+ ),
248
+ );
249
+
250
+ $(ReactDOM.findDOMNode(this)).animate(this.props.endPosition, {
251
+ duration: Math.max(duration, 1),
252
+ // Animating -> Static
253
+ complete: this.props.onAnimationEnd,
254
+ });
255
+ } else if (this.props.type === STATIC) {
256
+ // Ensure that any animations are done
257
+ $(ReactDOM.findDOMNode(this)).finish();
258
+ }
259
+ }
260
+
261
+ bindMouseMoveUp = () => {
262
+ this.isMouseMoveUpBound = true;
263
+ $(document).on("mousemove", this.onMouseMove);
264
+ $(document).on("mouseup", this.onMouseUp);
265
+ };
266
+
267
+ unbindMouseMoveUp = () => {
268
+ this.isMouseMoveUpBound = false;
269
+ $(document).off("mousemove", this.onMouseMove);
270
+ $(document).off("mouseup", this.onMouseUp);
271
+ };
272
+
273
+ onMouseDown = (event) => {
274
+ if (this.props.type !== STATIC) {
275
+ return;
276
+ }
277
+
278
+ if (
279
+ !(
280
+ event.button === 0 ||
281
+ (event.touches != null && event.touches.length === 1)
282
+ )
283
+ ) {
284
+ return;
285
+ }
286
+
287
+ event.preventDefault();
288
+ const loc = Util.extractPointerLocation(event);
289
+
290
+ // TODO(jeff, CP-3128): Use Wonder Blocks Timing API.
291
+ // eslint-disable-next-line no-restricted-syntax
292
+ this.animationFrameRequest = requestAnimationFrame(() => {
293
+ const $el = $(ReactDOM.findDOMNode(this));
294
+
295
+ // jQuery.position() gets the position of the element wrt its offset parent,
296
+ // but subtracts the scroll position of the parent. We need to add that back.
297
+ const position = $el.position();
298
+ const startPosition = addOffsetParentScroll($el, position);
299
+
300
+ if (loc && this._mounted) {
301
+ this.setState(
302
+ {
303
+ startPosition,
304
+ // NOTE(slim): We don't bother adjusting `loc` because we only keep track of (mouse - startMouse) in
305
+ // this.getCurrentPosition(). Adjusting `loc` here requires us to also change how `loc` is used in
306
+ // onMouseMove and onMouseUp, and this requires reading from the DOM each time to measure the parent's scrollTop.
307
+ startMouse: loc,
308
+ mouse: loc,
309
+ dragging: true,
310
+ },
311
+ function () {
312
+ this.bindMouseMoveUp();
313
+
314
+ // Static -> Dragging
315
+ this.props.onMouseDown();
316
+ },
317
+ );
318
+ }
319
+ });
320
+ };
321
+
322
+ onMouseMove: ($FlowFixMe) => void = (event) => {
323
+ const notDragging =
324
+ this.props.type !== DRAGGING || !this.state.dragging;
325
+
326
+ if (notDragging) {
327
+ return;
328
+ }
329
+
330
+ event.preventDefault();
331
+ const loc = Util.extractPointerLocation(event);
332
+
333
+ if (loc) {
334
+ // TODO(jeff, CP-3128): Use Wonder Blocks Timing API.
335
+ // eslint-disable-next-line no-restricted-syntax
336
+ this.animationFrameRequest = requestAnimationFrame(() => {
337
+ this.setState(
338
+ {
339
+ mouse: loc,
340
+ },
341
+ this.props.onMouseMove,
342
+ );
343
+ });
344
+ }
345
+ };
346
+
347
+ onMouseUp: ($FlowFixMe) => void = (event) => {
348
+ const notDragging =
349
+ this.props.type !== DRAGGING || !this.state.dragging;
350
+
351
+ if (notDragging) {
352
+ return;
353
+ }
354
+
355
+ event.preventDefault();
356
+ const loc = Util.extractPointerLocation(event);
357
+ if (loc) {
358
+ this.setState({dragging: false});
359
+ this.unbindMouseMoveUp();
360
+
361
+ // Dragging -> Animating
362
+ this.props.onMouseUp();
363
+ }
364
+ };
365
+ }
366
+ type SortableLayoutKind = "horizontal" | "vertical";
367
+ export type SortableOption = string;
368
+
369
+ type SortableProps = {|
370
+ constraints:
371
+ | {|
372
+ width: number,
373
+ height: number,
374
+ |}
375
+ | {...},
376
+ disabled: boolean,
377
+ layout: SortableLayoutKind,
378
+ margin: number,
379
+ onChange: ($FlowFixMe) => void,
380
+ onMeasure: ({|
381
+ widths: $ReadOnlyArray<number>,
382
+ heights: $ReadOnlyArray<number>,
383
+ |}) => void,
384
+ padding: boolean,
385
+ linterContext: LinterContextProps,
386
+ waitForKatexLoad: boolean,
387
+ options: $ReadOnlyArray<SortableOption>,
388
+ |};
389
+
390
+ type DefaultProps = {|
391
+ constraints: SortableProps["constraints"],
392
+ disabled: SortableProps["disabled"],
393
+ layout: SortableProps["layout"],
394
+ margin: SortableProps["margin"],
395
+ onChange: SortableProps["onChange"],
396
+ onMeasure: SortableProps["onMeasure"],
397
+ padding: SortableProps["padding"],
398
+ linterContext: SortableProps["linterContext"],
399
+ waitForKatexLoad: SortableProps["waitForKatexLoad"],
400
+ |};
401
+
402
+ type ItemState = "disabled" | "static" | "dragging" | "animating";
403
+ type SortableItem = {|
404
+ option: SortableOption,
405
+ key: number,
406
+ type: ItemState,
407
+ endPosition: $FlowFixMe,
408
+ width: number,
409
+ height: number,
410
+ |};
411
+
412
+ type SortableState = {|
413
+ items: $ReadOnlyArray<SortableItem>,
414
+ katex: null | any,
415
+ |};
416
+ class Sortable extends React.Component<SortableProps, SortableState> {
417
+ static defaultProps: DefaultProps = {
418
+ layout: HORIZONTAL,
419
+ padding: true,
420
+ disabled: false,
421
+ constraints: {},
422
+ onMeasure: function () {},
423
+ margin: 5,
424
+ onChange: function () {},
425
+ linterContext: linterContextDefault,
426
+ waitForKatexLoad: true,
427
+ };
428
+
429
+ constructor(props: SortableProps) {
430
+ super(props);
431
+ // Don't call this.setState() here!
432
+ this.state = {
433
+ items: Sortable.itemsFromProps(this.props),
434
+ katex: null,
435
+ };
436
+ }
437
+
438
+ UNSAFE_componentWillReceiveProps(nextProps: SortableProps) {
439
+ const prevProps = this.props;
440
+
441
+ if (!_.isEqual(nextProps.options, prevProps.options)) {
442
+ // Regenerate items
443
+ this.setState({
444
+ items: Sortable.itemsFromProps(nextProps),
445
+ });
446
+ } else if (
447
+ nextProps.layout !== prevProps.layout ||
448
+ nextProps.padding !== prevProps.padding ||
449
+ nextProps.disabled !== prevProps.disabled ||
450
+ !_.isEqual(nextProps.constraints, prevProps.constraints)
451
+ ) {
452
+ // Clear item measurements
453
+ this.setState({
454
+ items: Sortable.clearItemMeasurements(this.state.items),
455
+ });
456
+ }
457
+ }
458
+
459
+ componentDidUpdate() {
460
+ // Measure items if their dimensions have been reset
461
+ if (
462
+ this.state.items.length &&
463
+ !this.state.items[0].width &&
464
+ !this.state.items[0].height
465
+ ) {
466
+ // Measure on the next frame to allow items size to settle.
467
+ // TODO(jeff, CP-3128): Use Wonder Blocks Timing API.
468
+ // eslint-disable-next-line no-restricted-syntax
469
+ setTimeout(() => {
470
+ this.measureItems();
471
+ }, 0);
472
+ }
473
+ }
474
+
475
+ componentDidMount() {
476
+ getDependencies()
477
+ .getKaTeX()
478
+ .then((katex) => {
479
+ this.setState({katex});
480
+ });
481
+ }
482
+
483
+ static itemsFromProps(props: {
484
+ disabled: boolean,
485
+ options: $ReadOnlyArray<SortableOption>,
486
+ ...
487
+ }): $ReadOnlyArray<SortableItem> {
488
+ const type: ItemState = props.disabled ? DISABLED : STATIC;
489
+ return props.options.map((option: SortableOption, i) => {
490
+ return {
491
+ option: option,
492
+ key: i,
493
+ type: type,
494
+ endPosition: {},
495
+ width: 0,
496
+ height: 0,
497
+ };
498
+ });
499
+ }
500
+
501
+ static clearItemMeasurements(
502
+ items: $ReadOnlyArray<SortableItem>,
503
+ ): $ReadOnlyArray<SortableItem> {
504
+ return items.map((item) => {
505
+ return {
506
+ ...item,
507
+ width: 0,
508
+ height: 0,
509
+ };
510
+ });
511
+ }
512
+
513
+ measureItems() {
514
+ // Measure all items and cache what their dimensions should be, taking
515
+ // into account constraints and the current layout. This allows syncing
516
+ // widths and heights for pretty rows/columns. Note that dimensions are
517
+ // explictly set on Draggables - this prevents them from changing size
518
+ // or shape while being dragged.
519
+
520
+ let items: SortableItem = _.clone(this.state.items);
521
+
522
+ // Fetches a jQuery list of elements for each item
523
+ const $items = _.map(
524
+ items,
525
+ function (item) {
526
+ // eslint-disable-next-line react/no-string-refs
527
+ return $(ReactDOM.findDOMNode(this.refs[item.key]));
528
+ },
529
+ this,
530
+ );
531
+
532
+ const widths: $ReadOnlyArray<number> = _.invoke($items, "outerWidth");
533
+ const heights: $ReadOnlyArray<number> = _.invoke($items, "outerHeight");
534
+
535
+ const {constraints, layout} = this.props;
536
+
537
+ let syncWidth: number | null = null;
538
+ if (constraints?.width) {
539
+ // Items must be at least as wide as the specified constraint
540
+ syncWidth = _.max(widths.concat(constraints.width));
541
+ } else if (layout === VERTICAL) {
542
+ // Sync widths to get a clean column
543
+ syncWidth = _.max(widths);
544
+ }
545
+
546
+ let syncHeight: number | null = null;
547
+ if (constraints?.height) {
548
+ // Items must be at least as high as the specified constraint
549
+ syncHeight = _.max(heights.concat(constraints.height));
550
+ } else if (layout === HORIZONTAL) {
551
+ // Sync widths to get a clean row
552
+ syncHeight = _.max(heights);
553
+ }
554
+
555
+ items = _.map(items, function (item, i) {
556
+ item.width = syncWidth || widths[i];
557
+ item.height = syncHeight || heights[i];
558
+ return item;
559
+ });
560
+
561
+ this.setState({items: items}, () => {
562
+ this.props.onMeasure &&
563
+ this.props.onMeasure({widths: widths, heights: heights});
564
+ });
565
+ }
566
+
567
+ // $FlowFixMe[signature-verification-failure]
568
+ remeasureItems = _.debounce(() => {
569
+ this.setState({
570
+ // Clear item measurements
571
+ items: Sortable.clearItemMeasurements(this.state.items),
572
+ });
573
+ }, 20);
574
+
575
+ render(): React.Node {
576
+ // We don't render the sortable until KaTeX has fully loaded, in case
577
+ // the draggables are rendering KaTeX content. This is un-optimal as
578
+ // we end up loading KaTeX even when we may not need it, however it
579
+ // helps to ensure that the dimensions of the draggables (and thus the
580
+ // sortable) will be correct when they render, if their contents are
581
+ // KaTeX-derived.
582
+ if (this.props.waitForKatexLoad && !this.state.katex) {
583
+ return <CircularSpinner />;
584
+ }
585
+ const cards = [];
586
+
587
+ const {layout} = this.props;
588
+ // We need to keep backwards compatbility with rules specified directly
589
+ // in CSS. See sortable.less for details.
590
+ const className = css(styles.sortable) + " perseus-sortable";
591
+
592
+ const syncWidth = this.props.constraints?.width || layout === VERTICAL;
593
+ const syncHeight =
594
+ this.props.constraints?.height || layout === HORIZONTAL;
595
+
596
+ _.each(
597
+ this.state.items,
598
+ function (item, i, items) {
599
+ const isLast = i === items.length - 1;
600
+ const isStatic = item.type === STATIC || item.type === DISABLED;
601
+ let margin;
602
+
603
+ if (this.props.layout === HORIZONTAL) {
604
+ margin = "0 " + this.props.margin + "px 0 0"; // right
605
+ } else if (this.props.layout === VERTICAL) {
606
+ margin = "0 0 " + this.props.margin + "px 0"; // bottom
607
+ }
608
+
609
+ cards.push(
610
+ <Draggable
611
+ content={item.option}
612
+ key={item.key}
613
+ type={item.type}
614
+ ref={item.key}
615
+ width={syncWidth ? item.width : undefined}
616
+ height={syncHeight ? item.height : undefined}
617
+ layout={layout}
618
+ includePadding={this.props.padding}
619
+ margin={isLast && isStatic ? 0 : margin}
620
+ endPosition={item.endPosition}
621
+ linterContext={Gorgon.pushContextStack(
622
+ this.props.linterContext,
623
+ "sortable",
624
+ )}
625
+ onRender={this.remeasureItems}
626
+ // eslint-disable-next-line react/jsx-no-bind
627
+ onMouseDown={this.onMouseDown.bind(this, item.key)}
628
+ // eslint-disable-next-line react/jsx-no-bind
629
+ onMouseMove={this.onMouseMove.bind(this, item.key)}
630
+ // eslint-disable-next-line react/jsx-no-bind
631
+ onMouseUp={this.onMouseUp.bind(this, item.key)}
632
+ // eslint-disable-next-line react/jsx-no-bind
633
+ onTouchMove={this.onMouseMove.bind(this, item.key)}
634
+ // eslint-disable-next-line react/jsx-no-bind
635
+ onTouchEnd={this.onMouseUp.bind(this, item.key)}
636
+ // eslint-disable-next-line react/jsx-no-bind
637
+ onTouchCancel={this.onMouseUp.bind(this, item.key)}
638
+ // eslint-disable-next-line react/jsx-no-bind
639
+ onAnimationEnd={this.onAnimationEnd.bind(
640
+ this,
641
+ item.key,
642
+ )}
643
+ />,
644
+ );
645
+
646
+ if (item.type === DRAGGING || item.type === ANIMATING) {
647
+ cards.push(
648
+ <Placeholder
649
+ key={"placeholder_" + item.key}
650
+ ref={"placeholder_" + item.key}
651
+ width={item.width}
652
+ height={item.height}
653
+ layout={layout}
654
+ margin={isLast ? 0 : margin}
655
+ />,
656
+ );
657
+ }
658
+ },
659
+ this,
660
+ );
661
+
662
+ return <ul className={className}>{cards}</ul>;
663
+ }
664
+
665
+ onMouseDown(key: SortableItem["key"]) {
666
+ // Static -> Dragging
667
+ const items = _.map(this.state.items, function (item) {
668
+ if (item.key === key) {
669
+ item.type = DRAGGING;
670
+ }
671
+ return item;
672
+ });
673
+
674
+ this.setState({items: items});
675
+ }
676
+
677
+ // This is public API and it's ok for components that use
678
+ // this sortable to call this.
679
+ // moveOptionToIndex takes an option and a desired index and
680
+ // will move that item to the desired index. This is useful
681
+ // for testing and other cases where dragging and dropping may
682
+ // not be available
683
+ moveOptionToIndex(option: SortableOption, index: number) {
684
+ const {items} = this.state;
685
+
686
+ if (index < 0 || index > items.length) {
687
+ throw new Error(`index ${index} out of bounds`);
688
+ }
689
+
690
+ const nextItems = _.clone(items);
691
+
692
+ const item = items.filter((item: SortableItem) => {
693
+ return item.option === option;
694
+ })[0];
695
+
696
+ if (item == null) {
697
+ throw new Error(`option ${option} not found`);
698
+ }
699
+
700
+ const currentIndex = items.findIndex((i: SortableItem) => {
701
+ return i.key === item.key;
702
+ });
703
+
704
+ nextItems.splice(currentIndex, 1);
705
+ nextItems.splice(index, 0, item);
706
+
707
+ this.setState({items: nextItems});
708
+ this.props.onChange && this.props.onChange({});
709
+ }
710
+
711
+ onMouseMove(key: SortableItem["key"]) {
712
+ // Dragging: Rearrange items based on draggable's position
713
+ // eslint-disable-next-line react/no-string-refs
714
+ const $draggable = $(ReactDOM.findDOMNode(this.refs[key]));
715
+ const $sortable = $(ReactDOM.findDOMNode(this));
716
+ const items = _.clone(this.state.items);
717
+ const item = _.findWhere(this.state.items, {key: key});
718
+ const margin = this.props.margin || 0;
719
+ const currentIndex = _.indexOf(items, item);
720
+ let newIndex = 0;
721
+
722
+ items.splice(currentIndex, 1);
723
+
724
+ if (this.props.layout === HORIZONTAL) {
725
+ const midWidth = $draggable.offset().left - $sortable.offset().left;
726
+ let sumWidth = 0;
727
+ let cardWidth;
728
+
729
+ _.each(items, function (item) {
730
+ cardWidth = item.width;
731
+ if (midWidth > sumWidth + cardWidth / 2) {
732
+ newIndex += 1;
733
+ }
734
+ sumWidth += cardWidth + margin;
735
+ });
736
+ } else {
737
+ const midHeight = $draggable.offset().top - $sortable.offset().top;
738
+ let sumHeight = 0;
739
+ let cardHeight;
740
+
741
+ _.each(items, function (item) {
742
+ cardHeight = item.height;
743
+ if (midHeight > sumHeight + cardHeight / 2) {
744
+ newIndex += 1;
745
+ }
746
+ sumHeight += cardHeight + margin;
747
+ });
748
+ }
749
+
750
+ if (newIndex !== currentIndex) {
751
+ items.splice(newIndex, 0, item);
752
+ this.setState({items: items});
753
+ }
754
+ }
755
+
756
+ onMouseUp(key: SortableItem["key"]) {
757
+ // Dragging -> Animating
758
+ // TODO(jeff, CP-3128): Use Wonder Blocks Timing API.
759
+ // eslint-disable-next-line no-restricted-syntax
760
+ const nextAnimationFrame = requestAnimationFrame(() => {
761
+ const items = _.map(
762
+ this.state.items,
763
+ function (item) {
764
+ if (item.key === key) {
765
+ item.type = ANIMATING;
766
+ const $placeholder = $(
767
+ ReactDOM.findDOMNode(
768
+ // eslint-disable-next-line react/no-string-refs
769
+ this.refs["placeholder_" + key],
770
+ ),
771
+ );
772
+ const position = $placeholder.position();
773
+ const endPosition = addOffsetParentScroll(
774
+ $placeholder,
775
+ position,
776
+ );
777
+ item.endPosition = endPosition;
778
+ }
779
+ return item;
780
+ },
781
+ this,
782
+ );
783
+
784
+ this.setState({items: items});
785
+ // HACK: We need to know *that* the widget changed, but currently it's
786
+ // not set up in a nice way to tell us *how* it changed, since the
787
+ // permutation of the items is stored in state.
788
+ this.props.onChange && this.props.onChange({});
789
+ });
790
+
791
+ // $FlowFixMe[prop-missing]
792
+ this.animationFrameRequest = nextAnimationFrame;
793
+ }
794
+
795
+ onAnimationEnd(key: SortableItem["key"]) {
796
+ // Animating -> Static
797
+ const items = _.map(this.state.items, function (item) {
798
+ if (item.key === key) {
799
+ item.type = STATIC;
800
+ }
801
+ return item;
802
+ });
803
+
804
+ this.setState({items: items});
805
+ }
806
+
807
+ getOptions(): $ReadOnlyArray<SortableOption> {
808
+ return _.pluck(this.state.items, "option");
809
+ }
810
+ }
811
+
812
+ const styles = StyleSheet.create({
813
+ sortable: {
814
+ boxSizing: "border-box",
815
+ float: "left",
816
+
817
+ padding: 0,
818
+ margin: 0,
819
+ },
820
+
821
+ card: {
822
+ boxSizing: "border-box",
823
+ background: "#fff",
824
+ border: "1px solid #ddd",
825
+ borderRadius: 4,
826
+ cursor: "pointer",
827
+ minWidth: 25,
828
+ minHeight: 44,
829
+ padding: 10,
830
+
831
+ listStyleType: "none",
832
+
833
+ userSelect: "none",
834
+ touchAction: "none",
835
+ },
836
+
837
+ placeholder: {
838
+ background: "#ddd",
839
+ border: "1px solid #ccc",
840
+ },
841
+
842
+ draggable: {
843
+ textAlign: "center",
844
+ },
845
+
846
+ horizontalCard: {
847
+ float: "left",
848
+ cursor: "ew-resize",
849
+ },
850
+
851
+ verticalCard: {
852
+ maxWidth: "100%",
853
+ cursor: "ns-resize",
854
+ },
855
+
856
+ unpaddedCard: {
857
+ padding: 0,
858
+ },
859
+
860
+ dragging: {
861
+ background: "#ffedcd",
862
+ opacity: 0.8,
863
+ },
864
+
865
+ disabled: {
866
+ backgroundColor: "inherit",
867
+ border: "1px solid transparent",
868
+ cursor: "default",
869
+ },
870
+ });
871
+
872
+ export default Sortable;