@khanacademy/perseus 0.0.1 β†’ 0.2.1

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 (507) hide show
  1. package/.babelrc.js +1 -1
  2. package/CHANGELOG.md +29 -0
  3. package/dist/es/index.css +4361 -0
  4. package/dist/es/index.js +7 -1
  5. package/dist/es/index.js.map +1 -1
  6. package/dist/index.css +4361 -0
  7. package/dist/index.js +7 -1
  8. package/dist/index.js.map +1 -1
  9. package/dist/strings.js +148 -46
  10. package/package.json +50 -4
  11. package/src/README.md +35 -0
  12. package/src/__stories__/item-renderer.stories.jsx +27 -0
  13. package/src/__stories__/server-item-renderer.stories.jsx +27 -0
  14. package/src/__testdata__/item-renderer_testdata.js +161 -0
  15. package/src/__testdata__/renderer_testdata.js +123 -0
  16. package/src/__tests__/__snapshots__/item-renderer_test.jsx.snap +233 -0
  17. package/src/__tests__/__snapshots__/perseus-markdown_test.jsx.snap +516 -0
  18. package/src/__tests__/__snapshots__/pure-markdown_test.js.snap +113 -0
  19. package/src/__tests__/__snapshots__/renderer_test.jsx.snap +576 -0
  20. package/src/__tests__/__snapshots__/server-item-renderer_test.jsx.snap +306 -0
  21. package/src/__tests__/a11y_test.jsx +304 -0
  22. package/src/__tests__/item-renderer_test.jsx +892 -0
  23. package/src/__tests__/mock-asset-loading-widget.jsx +57 -0
  24. package/src/__tests__/mock-widget.jsx +35 -0
  25. package/src/__tests__/perseus-markdown_test.jsx +747 -0
  26. package/src/__tests__/pure-markdown_test.js +552 -0
  27. package/src/__tests__/renderability_test.jsx +481 -0
  28. package/src/__tests__/renderer-api_test.jsx +333 -0
  29. package/src/__tests__/renderer_test.jsx +1792 -0
  30. package/src/__tests__/server-item-renderer_test.jsx +516 -0
  31. package/src/__tests__/test-items/image-item.js +25 -0
  32. package/src/__tests__/test-items/input-number-1-item.js +27 -0
  33. package/src/__tests__/test-items/input-number-2-item.js +39 -0
  34. package/src/__tests__/test-items/table-item.js +30 -0
  35. package/src/__tests__/tex-wrangler_test.js +83 -0
  36. package/src/__tests__/util_test.js +41 -0
  37. package/src/a11y.js +46 -0
  38. package/src/all-widgets.js +9 -0
  39. package/src/article-renderer.jsx +215 -0
  40. package/src/asset-context.js +30 -0
  41. package/src/basic-widgets.js +13 -0
  42. package/src/components/__stories__/button-group.stories.jsx +61 -0
  43. package/src/components/__stories__/fixed-to-responsive.stories.jsx +132 -0
  44. package/src/components/__stories__/graph.stories.jsx +27 -0
  45. package/src/components/__stories__/graphie.stories.jsx +28 -0
  46. package/src/components/__stories__/hud.stories.jsx +36 -0
  47. package/src/components/__stories__/icon.stories.jsx +56 -0
  48. package/src/components/__stories__/image-loader.stories.jsx +60 -0
  49. package/src/components/__stories__/info-tip.stories.jsx +33 -0
  50. package/src/components/__stories__/inline-icon.stories.jsx +39 -0
  51. package/src/components/__stories__/input-with-examples.stories.jsx +51 -0
  52. package/src/components/__stories__/lint.stories.jsx +40 -0
  53. package/src/components/__stories__/math-input.stories.jsx +29 -0
  54. package/src/components/__stories__/math-output.stories.jsx +24 -0
  55. package/src/components/__stories__/multi-button-group.stories.jsx +69 -0
  56. package/src/components/__stories__/number-input.stories.jsx +42 -0
  57. package/src/components/__stories__/possible-answers.stories.jsx +22 -0
  58. package/src/components/__stories__/prop-check-box.stories.jsx +62 -0
  59. package/src/components/__stories__/range-input.stories.jsx +28 -0
  60. package/src/components/__stories__/simple-keypad-input.stories.jsx +28 -0
  61. package/src/components/__stories__/sortable.stories.jsx +73 -0
  62. package/src/components/__stories__/stub-tag-editor.stories.jsx +44 -0
  63. package/src/components/__stories__/svg-image.stories.jsx +60 -0
  64. package/src/components/__stories__/tex.stories.jsx +18 -0
  65. package/src/components/__stories__/text-buttons.stories.jsx +51 -0
  66. package/src/components/__stories__/text-input.stories.jsx +34 -0
  67. package/src/components/__stories__/text-list-editor.stories.jsx +33 -0
  68. package/src/components/__stories__/zoomable-tex.stories.jsx +39 -0
  69. package/src/components/__stories__/zoomable.stories.jsx +40 -0
  70. package/src/components/__testdata__/sorter_testdata.js +20 -0
  71. package/src/components/__tests__/__snapshots__/sortable_test.jsx.snap +79 -0
  72. package/src/components/__tests__/__snapshots__/sorter_test.jsx.snap +297 -0
  73. package/src/components/__tests__/number-input_test.jsx +112 -0
  74. package/src/components/__tests__/sortable_test.jsx +234 -0
  75. package/src/components/__tests__/sorter_test.jsx +111 -0
  76. package/src/components/__tests__/zoomable_test.jsx +388 -0
  77. package/src/components/button-group.jsx +131 -0
  78. package/src/components/constants.js +15 -0
  79. package/src/components/fixed-to-responsive.jsx +164 -0
  80. package/src/components/graph.jsx +415 -0
  81. package/src/components/graphie-classes.jsx +148 -0
  82. package/src/components/graphie-movables.jsx +342 -0
  83. package/src/components/graphie.jsx +353 -0
  84. package/src/components/highlighting/highlightable-content.jsx +245 -0
  85. package/src/components/highlighting/highlights.js +208 -0
  86. package/src/components/highlighting/ranges.js +299 -0
  87. package/src/components/highlighting/types.js +73 -0
  88. package/src/components/highlighting/ui/highlight-renderer.jsx +179 -0
  89. package/src/components/highlighting/ui/highlight-set-renderer.jsx +190 -0
  90. package/src/components/highlighting/ui/highlight-tooltip.jsx +153 -0
  91. package/src/components/highlighting/ui/highlighting-ui.jsx +163 -0
  92. package/src/components/highlighting/ui/selection-tracker.jsx +195 -0
  93. package/src/components/highlighting/ui/types.js +21 -0
  94. package/src/components/highlighting/ui/util.js +166 -0
  95. package/src/components/highlighting/word-indexer.jsx +155 -0
  96. package/src/components/hud.jsx +179 -0
  97. package/src/components/icon.jsx +149 -0
  98. package/src/components/image-loader.jsx +175 -0
  99. package/src/components/info-tip/info-tip.jsx +146 -0
  100. package/src/components/info-tip.jsx +38 -0
  101. package/src/components/inline-icon.jsx +81 -0
  102. package/src/components/input-with-examples.jsx +231 -0
  103. package/src/components/lint.jsx +454 -0
  104. package/src/components/math-input.jsx +282 -0
  105. package/src/components/math-output.jsx +128 -0
  106. package/src/components/multi-button-group.jsx +137 -0
  107. package/src/components/new-tooltip/README.md +110 -0
  108. package/src/components/new-tooltip/__stories__/flexible-position.stories.jsx +437 -0
  109. package/src/components/new-tooltip/__stories__/new-tooltip.stories.jsx +665 -0
  110. package/src/components/new-tooltip/__stories__/tooltip-arrow.stories.jsx +70 -0
  111. package/src/components/new-tooltip/__stories__/tooltip-bubble.stories.jsx +98 -0
  112. package/src/components/new-tooltip/__tests__/style-constants_test.js +8 -0
  113. package/src/components/new-tooltip/__tests__/util_test.js +47 -0
  114. package/src/components/new-tooltip/flexible-position.jsx +182 -0
  115. package/src/components/new-tooltip/new-tooltip.jsx +399 -0
  116. package/src/components/new-tooltip/style-constants.js +13 -0
  117. package/src/components/new-tooltip/tooltip-arrow.jsx +184 -0
  118. package/src/components/new-tooltip/tooltip-bubble.jsx +223 -0
  119. package/src/components/new-tooltip/tooltip-portal.jsx +101 -0
  120. package/src/components/new-tooltip/tooltip-positioner.jsx +529 -0
  121. package/src/components/new-tooltip/types.js +183 -0
  122. package/src/components/new-tooltip/util.js +27 -0
  123. package/src/components/number-input.jsx +253 -0
  124. package/src/components/possible-answers.jsx +36 -0
  125. package/src/components/prop-check-box.jsx +80 -0
  126. package/src/components/range-input.jsx +61 -0
  127. package/src/components/simple-keypad-input.jsx +91 -0
  128. package/src/components/sortable.jsx +873 -0
  129. package/src/components/stub-tag-editor.jsx +54 -0
  130. package/src/components/svg-image.jsx +762 -0
  131. package/src/components/tex-buttons.jsx +322 -0
  132. package/src/components/tex.jsx +70 -0
  133. package/src/components/text-input.jsx +82 -0
  134. package/src/components/text-list-editor.jsx +157 -0
  135. package/src/components/tooltip.jsx +395 -0
  136. package/src/components/visibility-observer/__stories__/visibility-observer.stories.jsx +140 -0
  137. package/src/components/visibility-observer/get-scroll-parent.js +45 -0
  138. package/src/components/visibility-observer/visibility-observer.js +109 -0
  139. package/src/components/visibility-observer/visibility-observer.jsx +123 -0
  140. package/src/components/zoomable-tex.jsx +70 -0
  141. package/src/components/zoomable.jsx +304 -0
  142. package/src/components.js +17 -0
  143. package/src/dependencies.js +22 -0
  144. package/src/error-boundary.jsx +48 -0
  145. package/src/extra-widgets.js +75 -0
  146. package/src/gorgon/README +40 -0
  147. package/src/gorgon/__tests__/matcher_test.js +497 -0
  148. package/src/gorgon/__tests__/rule_test.js +101 -0
  149. package/src/gorgon/__tests__/rules_test.js +487 -0
  150. package/src/gorgon/__tests__/selector-parser_test.js +52 -0
  151. package/src/gorgon/__tests__/tree-transformer_test.js +446 -0
  152. package/src/gorgon/gorgon.js +290 -0
  153. package/src/gorgon/proptypes.js +29 -0
  154. package/src/gorgon/rule.js +413 -0
  155. package/src/gorgon/rules/absolute-url.js +24 -0
  156. package/src/gorgon/rules/all-rules.js +72 -0
  157. package/src/gorgon/rules/blockquoted-math.js +10 -0
  158. package/src/gorgon/rules/blockquoted-widget.js +10 -0
  159. package/src/gorgon/rules/double-spacing-after-terminal.js +12 -0
  160. package/src/gorgon/rules/extra-content-spacing.js +12 -0
  161. package/src/gorgon/rules/heading-level-1.js +14 -0
  162. package/src/gorgon/rules/heading-level-skip.js +20 -0
  163. package/src/gorgon/rules/heading-sentence-case.js +11 -0
  164. package/src/gorgon/rules/heading-title-case.js +63 -0
  165. package/src/gorgon/rules/image-alt-text.js +21 -0
  166. package/src/gorgon/rules/image-in-table.js +10 -0
  167. package/src/gorgon/rules/image-spaces-around-urls.js +35 -0
  168. package/src/gorgon/rules/image-widget.js +50 -0
  169. package/src/gorgon/rules/link-click-here.js +11 -0
  170. package/src/gorgon/rules/lint-utils.js +48 -0
  171. package/src/gorgon/rules/long-paragraph.js +14 -0
  172. package/src/gorgon/rules/math-adjacent.js +10 -0
  173. package/src/gorgon/rules/math-align-extra-break.js +11 -0
  174. package/src/gorgon/rules/math-align-linebreaks.js +43 -0
  175. package/src/gorgon/rules/math-empty.js +10 -0
  176. package/src/gorgon/rules/math-font-size.js +12 -0
  177. package/src/gorgon/rules/math-frac.js +10 -0
  178. package/src/gorgon/rules/math-nested.js +11 -0
  179. package/src/gorgon/rules/math-starts-with-space.js +12 -0
  180. package/src/gorgon/rules/math-text-empty.js +10 -0
  181. package/src/gorgon/rules/math-without-dollars.js +14 -0
  182. package/src/gorgon/rules/nested-lists.js +11 -0
  183. package/src/gorgon/rules/profanity.js +10 -0
  184. package/src/gorgon/rules/table-missing-cells.js +20 -0
  185. package/src/gorgon/rules/unbalanced-code-delimiters.js +14 -0
  186. package/src/gorgon/rules/unescaped-dollar.js +10 -0
  187. package/src/gorgon/rules/widget-in-table.js +10 -0
  188. package/src/gorgon/selector.js +506 -0
  189. package/src/gorgon/tree-transformer.js +588 -0
  190. package/src/hint-renderer.jsx +159 -0
  191. package/src/hints-renderer.jsx +359 -0
  192. package/src/icon-paths.js +120 -0
  193. package/src/index.js +149 -2
  194. package/src/init.js +49 -0
  195. package/src/interaction-tracker.js +60 -0
  196. package/src/interactive2/__tests__/movable-mock.js +48 -0
  197. package/src/interactive2/__tests__/movable-point_test.js +146 -0
  198. package/src/interactive2/interactive-util.js +152 -0
  199. package/src/interactive2/movable-helper-methods.js +160 -0
  200. package/src/interactive2/movable-line-options.js +392 -0
  201. package/src/interactive2/movable-line.js +322 -0
  202. package/src/interactive2/movable-point-options.js +190 -0
  203. package/src/interactive2/movable-point.jsx +603 -0
  204. package/src/interactive2/movable-polygon-options.js +351 -0
  205. package/src/interactive2/movable-polygon.js +368 -0
  206. package/src/interactive2/movable.js +273 -0
  207. package/src/interactive2/objective_.js +79 -0
  208. package/src/interactive2/types.js +3 -0
  209. package/src/interactive2/wrapped-defaults.js +86 -0
  210. package/src/interactive2/wrapped-ellipse.js +87 -0
  211. package/src/interactive2/wrapped-line.js +109 -0
  212. package/src/interactive2/wrapped-path.js +47 -0
  213. package/src/interactive2.js +91 -0
  214. package/src/item-renderer.jsx +518 -0
  215. package/src/item-version.js +12 -0
  216. package/src/jipt-paragraphs.js +44 -0
  217. package/src/jquery.mobile.vmouse.js +545 -0
  218. package/src/keypad-context.js +33 -0
  219. package/src/loading-context.js +20 -0
  220. package/src/logging/log.js +107 -0
  221. package/src/logging/log_test.js +41 -0
  222. package/src/mixins/changeable.jsx +115 -0
  223. package/src/mixins/editor-jsonify.jsx +13 -0
  224. package/src/mixins/provide-keypad.jsx +129 -0
  225. package/src/mixins/widget-jsonify-deprecated.jsx +23 -0
  226. package/src/mixins/widget-prop-blacklist.js +36 -0
  227. package/src/multi-items/__stories__/multi-renderer.stories.js +88 -0
  228. package/src/multi-items/__testdata__/multi-renderer_testdata.js +174 -0
  229. package/src/multi-items/__tests__/__snapshots__/multi-renderer_test.js.snap +994 -0
  230. package/src/multi-items/__tests__/items_test.js +245 -0
  231. package/src/multi-items/__tests__/multi-renderer_test.js +903 -0
  232. package/src/multi-items/__tests__/prop-type-builders_test.js +134 -0
  233. package/src/multi-items/__tests__/shapes_test.js +44 -0
  234. package/src/multi-items/__tests__/trees_test.js +316 -0
  235. package/src/multi-items/item-types.js +55 -0
  236. package/src/multi-items/items.js +176 -0
  237. package/src/multi-items/multi-renderer.jsx +543 -0
  238. package/src/multi-items/prop-type-builders.js +105 -0
  239. package/src/multi-items/shape-types.js +47 -0
  240. package/src/multi-items/shapes.js +53 -0
  241. package/src/multi-items/tree-types.js +42 -0
  242. package/src/multi-items/trees.js +345 -0
  243. package/src/multi-items.js +76 -0
  244. package/src/not-gorgon.js +129 -0
  245. package/src/perseus-api.jsx +270 -0
  246. package/src/perseus-env.js +26 -0
  247. package/src/perseus-error.js +19 -0
  248. package/src/perseus-markdown.jsx +433 -0
  249. package/src/perseus-types.js +1597 -0
  250. package/src/pure-markdown.js +347 -0
  251. package/src/question-paragraph.jsx +30 -0
  252. package/src/renderability.jsx +108 -0
  253. package/src/renderer.jsx +1932 -0
  254. package/src/server-item-renderer.jsx +467 -0
  255. package/src/sigfigs.jsx +492 -0
  256. package/src/styles/articles.less +748 -0
  257. package/src/styles/constants.js +119 -0
  258. package/src/styles/global-constants.js +350 -0
  259. package/src/styles/global-styles.js +307 -0
  260. package/src/styles/khan-exercise.css +1304 -0
  261. package/src/styles/media-queries.js +48 -0
  262. package/src/styles/perseus-renderer.less +1007 -0
  263. package/src/styles/shared.js +132 -0
  264. package/src/styles/util.less +47 -0
  265. package/src/styles/variables.boostrap.less +301 -0
  266. package/src/styles/variables.less +68 -0
  267. package/src/styles/variables.shared.less +356 -0
  268. package/src/styles/widgets/categorizer.less +39 -0
  269. package/src/styles/widgets/definition.less +86 -0
  270. package/src/styles/widgets/dropdown.less +3 -0
  271. package/src/styles/widgets/expression.less +147 -0
  272. package/src/styles/widgets/graded-group.less +36 -0
  273. package/src/styles/widgets/grapher.less +11 -0
  274. package/src/styles/widgets/group.less +19 -0
  275. package/src/styles/widgets/image.less +29 -0
  276. package/src/styles/widgets/interactive-graph.less +61 -0
  277. package/src/styles/widgets/label-image.less +41 -0
  278. package/src/styles/widgets/matcher.less +8 -0
  279. package/src/styles/widgets/matrix.less +120 -0
  280. package/src/styles/widgets/measurer.less +20 -0
  281. package/src/styles/widgets/orderer.less +97 -0
  282. package/src/styles/widgets/passage.less +151 -0
  283. package/src/styles/widgets/plotter.less +33 -0
  284. package/src/styles/widgets/radio.less +52 -0
  285. package/src/styles/widgets/reaction-diagram.less +35 -0
  286. package/src/styles/widgets/sequence.less +5 -0
  287. package/src/styles/widgets/sortable.less +127 -0
  288. package/src/styles/widgets/table.less +56 -0
  289. package/src/styles/widgets/transformer.less +28 -0
  290. package/src/styles/widgets/unit.less +14 -0
  291. package/src/tex-wrangler.js +176 -0
  292. package/src/traversal.jsx +141 -0
  293. package/src/types.js +538 -0
  294. package/src/util/a11y.js +24 -0
  295. package/src/util/answer-types.js +862 -0
  296. package/src/util/answer-types_test.js +115 -0
  297. package/src/util/colors.js +94 -0
  298. package/src/util/fix-passage-refs.jsx +122 -0
  299. package/src/util/graph-utils.js +16 -0
  300. package/src/util/graphie.js +1586 -0
  301. package/src/util/interactive.js +4078 -0
  302. package/src/util/katex-preprocess.js +16 -0
  303. package/src/util/math.js +296 -0
  304. package/src/util/mobile-native-utils.js +14 -0
  305. package/src/util/mobile-native-utils_test.js +24 -0
  306. package/src/util/register-all-widgets-for-testing.js +12 -0
  307. package/src/util/scroll-utils.js +162 -0
  308. package/src/util/sizing-utils.js +46 -0
  309. package/src/util/tex.js +203 -0
  310. package/src/util/url-utils.js +36 -0
  311. package/src/util.js +990 -0
  312. package/src/version.js +11 -0
  313. package/src/widget-container.jsx +174 -0
  314. package/src/widgets/__stories__/categorizer.stories.jsx +15 -0
  315. package/src/widgets/__stories__/cs-program.stories.jsx +15 -0
  316. package/src/widgets/__stories__/definition.stories.jsx +38 -0
  317. package/src/widgets/__stories__/dropdown.stories.jsx +15 -0
  318. package/src/widgets/__stories__/explanation.stories.jsx +19 -0
  319. package/src/widgets/__stories__/expression.stories.jsx +26 -0
  320. package/src/widgets/__stories__/graded-group-set.stories.jsx +32 -0
  321. package/src/widgets/__stories__/graded-group.stories.jsx +32 -0
  322. package/src/widgets/__stories__/grapher.stories.jsx +47 -0
  323. package/src/widgets/__stories__/group.stories.jsx +15 -0
  324. package/src/widgets/__stories__/iframe.stories.jsx +15 -0
  325. package/src/widgets/__stories__/image.stories.jsx +62 -0
  326. package/src/widgets/__stories__/input-number.stories.jsx +90 -0
  327. package/src/widgets/__stories__/interaction.stories.jsx +15 -0
  328. package/src/widgets/__stories__/interactive-graph.stories.jsx +61 -0
  329. package/src/widgets/__stories__/matcher.stories.jsx +15 -0
  330. package/src/widgets/__stories__/matrix.stories.jsx +15 -0
  331. package/src/widgets/__stories__/number-line.stories.jsx +15 -0
  332. package/src/widgets/__stories__/numeric-input.stories.jsx +19 -0
  333. package/src/widgets/__stories__/orderer.stories.jsx +15 -0
  334. package/src/widgets/__stories__/passage-ref.stories.jsx +19 -0
  335. package/src/widgets/__stories__/passage.stories.jsx +19 -0
  336. package/src/widgets/__stories__/radio.stories.jsx +82 -0
  337. package/src/widgets/__stories__/sorter.stories.jsx +15 -0
  338. package/src/widgets/__stories__/transformer.stories.jsx +15 -0
  339. package/src/widgets/__stories__/unit.stories.jsx +45 -0
  340. package/src/widgets/__stories__/video-transcript-link.stories.jsx +24 -0
  341. package/src/widgets/__stories__/video.stories.jsx +15 -0
  342. package/src/widgets/__testdata__/categorizer_testdata.js +50 -0
  343. package/src/widgets/__testdata__/cs-program_testdata.js +28 -0
  344. package/src/widgets/__testdata__/dropdown_testdata.js +34 -0
  345. package/src/widgets/__testdata__/explanation_testdata.js +56 -0
  346. package/src/widgets/__testdata__/expression_testdata.js +95 -0
  347. package/src/widgets/__testdata__/graded-group-set_testdata.js +358 -0
  348. package/src/widgets/__testdata__/graded-group_testdata.js +79 -0
  349. package/src/widgets/__testdata__/grapher_testdata.js +376 -0
  350. package/src/widgets/__testdata__/group_testdata.js +156 -0
  351. package/src/widgets/__testdata__/iframe_testdata.js +36 -0
  352. package/src/widgets/__testdata__/image_testdata.js +38 -0
  353. package/src/widgets/__testdata__/input-number_testdata.js +73 -0
  354. package/src/widgets/__testdata__/interaction_testdata.js +264 -0
  355. package/src/widgets/__testdata__/interactive-graph_testdata.js +646 -0
  356. package/src/widgets/__testdata__/matcher_testdata.js +34 -0
  357. package/src/widgets/__testdata__/matrix_testdata.js +29 -0
  358. package/src/widgets/__testdata__/number-line_testdata.js +31 -0
  359. package/src/widgets/__testdata__/numeric-input_testdata.js +266 -0
  360. package/src/widgets/__testdata__/orderer_testdata.js +58 -0
  361. package/src/widgets/__testdata__/passage-ref_testdata.js +41 -0
  362. package/src/widgets/__testdata__/passage_testdata.js +71 -0
  363. package/src/widgets/__testdata__/radio_testdata.js +210 -0
  364. package/src/widgets/__testdata__/sorter_testdata.js +20 -0
  365. package/src/widgets/__testdata__/transformer_testdata.js +109 -0
  366. package/src/widgets/__testdata__/video_testdata.js +22 -0
  367. package/src/widgets/__tests__/__snapshots__/categorizer_test.jsx.snap +928 -0
  368. package/src/widgets/__tests__/__snapshots__/cs-program_test.jsx.snap +75 -0
  369. package/src/widgets/__tests__/__snapshots__/definition_test.js.snap +99 -0
  370. package/src/widgets/__tests__/__snapshots__/dropdown_test.jsx.snap +143 -0
  371. package/src/widgets/__tests__/__snapshots__/explanation_test.js.snap +473 -0
  372. package/src/widgets/__tests__/__snapshots__/expression_test.jsx.snap +38 -0
  373. package/src/widgets/__tests__/__snapshots__/graded-group-set-jipt_test.jsx.snap +754 -0
  374. package/src/widgets/__tests__/__snapshots__/graded-group-set_test.jsx.snap +310 -0
  375. package/src/widgets/__tests__/__snapshots__/graded-group_test.jsx.snap +863 -0
  376. package/src/widgets/__tests__/__snapshots__/grapher_test.jsx.snap +2880 -0
  377. package/src/widgets/__tests__/__snapshots__/group_test.jsx.snap +1154 -0
  378. package/src/widgets/__tests__/__snapshots__/iframe_test.jsx.snap +69 -0
  379. package/src/widgets/__tests__/__snapshots__/image_test.jsx.snap +388 -0
  380. package/src/widgets/__tests__/__snapshots__/input-number_test.jsx.snap +978 -0
  381. package/src/widgets/__tests__/__snapshots__/interaction_test.jsx.snap +311 -0
  382. package/src/widgets/__tests__/__snapshots__/interactive-graph_test.jsx.snap +2915 -0
  383. package/src/widgets/__tests__/__snapshots__/matcher_test.jsx.snap +1039 -0
  384. package/src/widgets/__tests__/__snapshots__/matrix_test.jsx.snap +450 -0
  385. package/src/widgets/__tests__/__snapshots__/number-line_test.jsx.snap +888 -0
  386. package/src/widgets/__tests__/__snapshots__/numeric-input_test.jsx.snap +930 -0
  387. package/src/widgets/__tests__/__snapshots__/orderer_test.jsx.snap +275 -0
  388. package/src/widgets/__tests__/__snapshots__/passage-ref_test.jsx.snap +446 -0
  389. package/src/widgets/__tests__/__snapshots__/passage_test.jsx.snap +1988 -0
  390. package/src/widgets/__tests__/__snapshots__/radio_test.jsx.snap +7107 -0
  391. package/src/widgets/__tests__/__snapshots__/video_test.jsx.snap +163 -0
  392. package/src/widgets/__tests__/categorizer_test.jsx +205 -0
  393. package/src/widgets/__tests__/cs-program_test.jsx +45 -0
  394. package/src/widgets/__tests__/definition_test.js +152 -0
  395. package/src/widgets/__tests__/dropdown_test.jsx +106 -0
  396. package/src/widgets/__tests__/explanation_test.js +199 -0
  397. package/src/widgets/__tests__/expression_test.jsx +439 -0
  398. package/src/widgets/__tests__/graded-group-set-jipt_test.jsx +33 -0
  399. package/src/widgets/__tests__/graded-group-set_test.jsx +270 -0
  400. package/src/widgets/__tests__/graded-group_test.jsx +233 -0
  401. package/src/widgets/__tests__/grapher.cypress.jsx +486 -0
  402. package/src/widgets/__tests__/grapher_test.jsx +35 -0
  403. package/src/widgets/__tests__/group_test.jsx +512 -0
  404. package/src/widgets/__tests__/iframe_test.jsx +47 -0
  405. package/src/widgets/__tests__/image_test.jsx +65 -0
  406. package/src/widgets/__tests__/input-number_test.jsx +360 -0
  407. package/src/widgets/__tests__/interaction_test.jsx +33 -0
  408. package/src/widgets/__tests__/interactive-graph.cypress.jsx +697 -0
  409. package/src/widgets/__tests__/interactive-graph_test.jsx +105 -0
  410. package/src/widgets/__tests__/matcher_test.jsx +157 -0
  411. package/src/widgets/__tests__/matrix_test.jsx +84 -0
  412. package/src/widgets/__tests__/number-line_test.jsx +81 -0
  413. package/src/widgets/__tests__/numeric-input_test.jsx +574 -0
  414. package/src/widgets/__tests__/orderer_test.jsx +75 -0
  415. package/src/widgets/__tests__/passage-ref_test.jsx +166 -0
  416. package/src/widgets/__tests__/passage_test.jsx +133 -0
  417. package/src/widgets/__tests__/radio/base-radio_test.jsx +82 -0
  418. package/src/widgets/__tests__/radio_test.jsx +651 -0
  419. package/src/widgets/__tests__/renderQuestion.jsx +66 -0
  420. package/src/widgets/__tests__/unit_test.jsx +144 -0
  421. package/src/widgets/__tests__/video_test.jsx +55 -0
  422. package/src/widgets/categorizer.jsx +338 -0
  423. package/src/widgets/cs-program.jsx +227 -0
  424. package/src/widgets/definition.jsx +141 -0
  425. package/src/widgets/dropdown.jsx +143 -0
  426. package/src/widgets/explanation.jsx +315 -0
  427. package/src/widgets/expression.jsx +652 -0
  428. package/src/widgets/graded-group-answer-bar.jsx +152 -0
  429. package/src/widgets/graded-group-set.jsx +285 -0
  430. package/src/widgets/graded-group.jsx +448 -0
  431. package/src/widgets/grapher/util.jsx +806 -0
  432. package/src/widgets/grapher.jsx +663 -0
  433. package/src/widgets/group.jsx +213 -0
  434. package/src/widgets/iframe.jsx +198 -0
  435. package/src/widgets/image.jsx +310 -0
  436. package/src/widgets/input-number.jsx +411 -0
  437. package/src/widgets/interaction/arrow-picker.jsx +39 -0
  438. package/src/widgets/interaction/color-picker.jsx +69 -0
  439. package/src/widgets/interaction/dash-picker.jsx +46 -0
  440. package/src/widgets/interaction/element-container.jsx +97 -0
  441. package/src/widgets/interaction.jsx +742 -0
  442. package/src/widgets/interactive-graph.jsx +2820 -0
  443. package/src/widgets/label-image/__stories__/answer-choices.stories.jsx +93 -0
  444. package/src/widgets/label-image/__stories__/marker.stories.jsx +172 -0
  445. package/src/widgets/label-image/answer-choices.jsx +311 -0
  446. package/src/widgets/label-image/marker.jsx +365 -0
  447. package/src/widgets/label-image/types.js +33 -0
  448. package/src/widgets/label-image.jsx +841 -0
  449. package/src/widgets/label-image_test.js +840 -0
  450. package/src/widgets/lights-puzzle.jsx +356 -0
  451. package/src/widgets/matcher.jsx +271 -0
  452. package/src/widgets/matrix.jsx +616 -0
  453. package/src/widgets/measurer.jsx +215 -0
  454. package/src/widgets/molecule/__tests__/layout_test.jsx +159 -0
  455. package/src/widgets/molecule/__tests__/parser_test.jsx +142 -0
  456. package/src/widgets/molecule/molecule-drawing.jsx +175 -0
  457. package/src/widgets/molecule/molecule-layout.jsx +367 -0
  458. package/src/widgets/molecule/smiles-parser.jsx +300 -0
  459. package/src/widgets/molecule.jsx +172 -0
  460. package/src/widgets/number-line.jsx +858 -0
  461. package/src/widgets/numeric-input.jsx +622 -0
  462. package/src/widgets/orderer.jsx +753 -0
  463. package/src/widgets/passage/__tests__/passage-markdown_test.jsx +382 -0
  464. package/src/widgets/passage/get-line-height-for-node.js +10 -0
  465. package/src/widgets/passage/passage-markdown.jsx +416 -0
  466. package/src/widgets/passage-ref-target.jsx +70 -0
  467. package/src/widgets/passage-ref.jsx +198 -0
  468. package/src/widgets/passage.jsx +601 -0
  469. package/src/widgets/plotter.jsx +1198 -0
  470. package/src/widgets/radio/__stories__/base-radio.stories.jsx +40 -0
  471. package/src/widgets/radio/__stories__/choice-icon.stories.jsx +157 -0
  472. package/src/widgets/radio/__stories__/choice-none-above.stories.jsx +26 -0
  473. package/src/widgets/radio/__stories__/cross-out-button.stories.jsx +64 -0
  474. package/src/widgets/radio/__stories__/cross-out-menu.stories.jsx +59 -0
  475. package/src/widgets/radio/__stories__/cross-out-tooltip.stories.jsx +59 -0
  476. package/src/widgets/radio/__stories__/focus-ring.stories.jsx +38 -0
  477. package/src/widgets/radio/__stories__/option-status.stories.jsx +118 -0
  478. package/src/widgets/radio/__stories__/toggleable-radio-button.stories.jsx +28 -0
  479. package/src/widgets/radio/base-radio.jsx +642 -0
  480. package/src/widgets/radio/choice-icon.jsx +468 -0
  481. package/src/widgets/radio/choice-none-above.jsx +54 -0
  482. package/src/widgets/radio/choice.jsx +692 -0
  483. package/src/widgets/radio/choice.jsx.fixture.js +64 -0
  484. package/src/widgets/radio/cross-out-button.jsx +136 -0
  485. package/src/widgets/radio/cross-out-menu-wrapper.jsx +492 -0
  486. package/src/widgets/radio/cross-out-tooltip.jsx +129 -0
  487. package/src/widgets/radio/focus-ring.jsx +51 -0
  488. package/src/widgets/radio/option-status.jsx +89 -0
  489. package/src/widgets/radio/toggleable-radio-button.jsx +133 -0
  490. package/src/widgets/radio/util.js +32 -0
  491. package/src/widgets/radio/util_test.js +19 -0
  492. package/src/widgets/radio/widget.jsx +470 -0
  493. package/src/widgets/radio.jsx +156 -0
  494. package/src/widgets/reaction-diagram.jsx +137 -0
  495. package/src/widgets/sequence.jsx +140 -0
  496. package/src/widgets/simulator.jsx +808 -0
  497. package/src/widgets/sorter.jsx +146 -0
  498. package/src/widgets/table.jsx +394 -0
  499. package/src/widgets/transformer.jsx +2876 -0
  500. package/src/widgets/unit.jsx +372 -0
  501. package/src/widgets/video-transcript-link.jsx +84 -0
  502. package/src/widgets/video-transcript-link_test.js +211 -0
  503. package/src/widgets/video.jsx +126 -0
  504. package/src/widgets.js +431 -0
  505. package/src/zoom.js +539 -0
  506. package/README.md +0 -3
  507. package/src/__tests__/index.test.js +0 -14
@@ -0,0 +1,282 @@
1
+ /* eslint-disable react/sort-comp */
2
+ // @flow
3
+ import classNames from "classnames";
4
+ import $ from "jquery";
5
+ import MathQuill from "mathquill";
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 TexButtons from "./tex-buttons.jsx";
12
+
13
+ const PT = PropTypes;
14
+
15
+ // A WYSIWYG math input that calls `onChange(LaTeX-string)`
16
+ class MathInput extends React.Component<$FlowFixMe, $FlowFixMe> {
17
+ mouseDown: boolean;
18
+
19
+ static propTypes = {
20
+ value: PT.string,
21
+ onChange: PT.func.isRequired,
22
+ convertDotToTimes: PT.bool,
23
+ buttonsVisible: PT.oneOf(["always", "never", "focused"]),
24
+ buttonSets: TexButtons.buttonSetsType.isRequired,
25
+ labelText: PropTypes.string,
26
+ onFocus: PT.func,
27
+ onBlur: PT.func,
28
+ };
29
+
30
+ static defaultProps: $FlowFixMe = {
31
+ value: "",
32
+ convertDotToTimes: false,
33
+ buttonsVisible: "focused",
34
+ };
35
+
36
+ state: $FlowFixMe = {focused: false};
37
+
38
+ render(): React.Node {
39
+ let className = classNames({
40
+ "perseus-math-input": true,
41
+
42
+ // mathquill usually adds these itself but react removes them when
43
+ // updating the component.
44
+ "mq-editable-field": true,
45
+ "mq-math-mode": true,
46
+ });
47
+
48
+ if (this.props.className) {
49
+ className = className + " " + this.props.className;
50
+ }
51
+
52
+ let buttons = null;
53
+ if (this._shouldShowButtons()) {
54
+ buttons = (
55
+ <TexButtons
56
+ sets={this.props.buttonSets}
57
+ className="math-input-buttons absolute"
58
+ convertDotToTimes={this.props.convertDotToTimes}
59
+ onInsert={this.insert}
60
+ />
61
+ );
62
+ }
63
+
64
+ return (
65
+ <div style={{display: "inline-block"}}>
66
+ <div style={{display: "inline-block"}}>
67
+ <span
68
+ className={className}
69
+ // eslint-disable-next-line react/no-string-refs
70
+ ref="mathinput"
71
+ aria-label={this.props.labelText}
72
+ onFocus={this.handleFocus}
73
+ onBlur={this.handleBlur}
74
+ />
75
+ </div>
76
+ <div style={{position: "relative"}} onBlur={this.handleBlur}>
77
+ {buttons}
78
+ </div>
79
+ </div>
80
+ );
81
+ }
82
+
83
+ // handlers:
84
+ // keep track of two related bits of state:
85
+ // * this.state.focused - whether the buttons are currently shown
86
+ // * this.mouseDown - whether a mouse click is active that started in the
87
+ // buttons div
88
+
89
+ handleFocus: () => void = () => {
90
+ this.setState({focused: true});
91
+ // TODO(joel) fix properly - we should probably allow onFocus handlers
92
+ // to this property, but we need to work correctly with them.
93
+ // if (this.props.onFocus) {
94
+ // this.props.onFocus();
95
+ // }
96
+ };
97
+
98
+ handleMouseDown: (MouseEvent) => void = (event) => {
99
+ // $FlowFixMe[incompatible-use]
100
+ // $FlowFixMe[incompatible-call]
101
+ const focused = ReactDOM.findDOMNode(this).contains(event.target);
102
+ this.mouseDown = focused;
103
+ if (!focused) {
104
+ this.setState({focused: false});
105
+ }
106
+ };
107
+
108
+ handleMouseUp: (MouseEvent) => void = () => {
109
+ // this mouse click started in the buttons div so we should focus the
110
+ // input
111
+ if (this.mouseDown) {
112
+ this.focus();
113
+ }
114
+ this.mouseDown = false;
115
+ };
116
+
117
+ handleBlur: (SyntheticFocusEvent<>) => void = (e) => {
118
+ // TODO(michaelpolyak): Consider trapping focus within the button group.
119
+ // Focusing back on the input when TAB out of the last button in the
120
+ // group. This will probably require ESCAPE key handling to enable to
121
+ // close (blur) the button group in order to focus on next page element.
122
+ if (
123
+ !this.mouseDown &&
124
+ // $FlowFixMe[incompatible-use]
125
+ // $FlowFixMe[incompatible-call]
126
+ !ReactDOM.findDOMNode(this).contains(e.relatedTarget)
127
+ ) {
128
+ this.setState({focused: false});
129
+ }
130
+ };
131
+
132
+ _shouldShowButtons: () => boolean = () => {
133
+ if (this.props.buttonsVisible === "always") {
134
+ return true;
135
+ }
136
+ if (this.props.buttonsVisible === "never") {
137
+ return false;
138
+ }
139
+ return this.state.focused;
140
+ };
141
+
142
+ insert: ($FlowFixMe) => void = (value) => {
143
+ const input = this.mathField();
144
+ if (_(value).isFunction()) {
145
+ value(input);
146
+ } else if (value[0] === "\\") {
147
+ input.cmd(value).focus();
148
+ } else {
149
+ input.write(value).focus();
150
+ }
151
+ input.focus();
152
+ };
153
+
154
+ mathField: ($FlowFixMe) => $FlowFixMe = (options) => {
155
+ // The MathQuill API is now "versioned" through its own "InterVer"
156
+ // system.
157
+ // See: https://github.com/mathquill/mathquill/pull/459
158
+ const MQ = MathQuill.getInterface(2);
159
+
160
+ // MathQuill.MathField takes a DOM node, MathQuill-ifies it if it's
161
+ // seeing that node for the first time, then returns the associated
162
+ // MathQuill object for that node. It is stable - will always return
163
+ // the same object when called on the same DOM node.
164
+ // eslint-disable-next-line react/no-string-refs
165
+ return MQ.MathField(ReactDOM.findDOMNode(this.refs.mathinput), options);
166
+ };
167
+
168
+ componentWillUnmount() {
169
+ window.removeEventListener("mousedown", this.handleMouseDown);
170
+ window.removeEventListener("mouseup", this.handleMouseUp);
171
+ }
172
+
173
+ componentDidMount() {
174
+ window.addEventListener("mousedown", this.handleMouseDown);
175
+ window.addEventListener("mouseup", this.handleMouseUp);
176
+
177
+ let initialized = false;
178
+
179
+ // Initialize MathQuill.MathField instance
180
+ this.mathField({
181
+ // LaTeX commands that, when typed, are immediately replaced by the
182
+ // appropriate symbol. This does not include ln, log, or any of the
183
+ // trig functions; those are always interpreted as commands.
184
+ autoCommands: "pi theta phi sqrt nthroot",
185
+
186
+ // Pop the cursor out of super/subscripts on arithmetic operators
187
+ // or (in)equalities.
188
+ charsThatBreakOutOfSupSub: "+-*/=<>≠≀β‰₯",
189
+
190
+ // Prevent excessive super/subscripts or fractions from being
191
+ // created without operands, e.g. when somebody holds down a key
192
+ supSubsRequireOperand: true,
193
+
194
+ // The name of this option is somewhat misleading, as tabbing in
195
+ // MathQuill breaks you out of a nested context (fraction/script)
196
+ // if you're in one, but moves focus to the next input if you're
197
+ // not. Spaces (with this option enabled) are just ignored in the
198
+ // latter case.
199
+ //
200
+ // TODO(alex): In order to allow inputting mixed numbers, we will
201
+ // have to accept spaces in certain cases. The desired behavior is
202
+ // still to escape nested contexts if currently in one, but to
203
+ // insert a space if not (we don't expect mixed numbers in nested
204
+ // contexts). We should also limit to one consecutive space.
205
+ spaceBehavesLikeTab: true,
206
+
207
+ handlers: {
208
+ edited: (mathField) => {
209
+ // This handler is guaranteed to be called on change, but
210
+ // unlike React it sometimes generates false positives.
211
+ // One of these is on initialization (with an empty string
212
+ // value), so we have to guard against that below.
213
+ let value = mathField.latex();
214
+
215
+ // Provide a MathQuill-compatible way to generate the
216
+ // not-equals sign without pasting unicode or typing TeX
217
+ value = value.replace(/<>/g, "\\ne");
218
+
219
+ // Use the specified symbol to represent multiplication
220
+ // TODO(alex): Add an option to disallow variables, in
221
+ // which case 'x' should get converted to '\\times'
222
+ if (this.props.convertDotToTimes) {
223
+ value = value.replace(/\\cdot/g, "\\times");
224
+
225
+ // Preserve cursor position in the common case:
226
+ // typing '*' to insert a multiplication sign.
227
+ // We do this by modifying internal MathQuill state
228
+ // directly, instead of waiting for `.latex()` to be
229
+ // called in `componentDidUpdate()`.
230
+ const left = mathField.__controller.cursor[MathQuill.L];
231
+ if (left && left.ctrlSeq === "\\cdot ") {
232
+ mathField.__controller.backspace();
233
+ mathField.cmd("\\times");
234
+ }
235
+ } else {
236
+ value = value.replace(/\\times/g, "\\cdot");
237
+ }
238
+
239
+ if (initialized && this.props.value !== value) {
240
+ this.props.onChange(value);
241
+ }
242
+ },
243
+ enter: () => {
244
+ // NOTE(kevinb): This isn't how answers to exercises are
245
+ // submitted. The actual mechanism for this can be found
246
+ // in exercise-problem-template.jsx, see `handleSubmit`.
247
+
248
+ // This handler is called when the user presses the enter
249
+ // key. Since this isn't an actual <input> element, we have
250
+ // to manually trigger the usually automatic form submit.
251
+ // eslint-disable-next-line react/no-string-refs
252
+ $(ReactDOM.findDOMNode(this.refs.mathinput)).submit();
253
+ },
254
+ upOutOf: (mathField) => {
255
+ // This handler is called when the user presses the up
256
+ // arrow key, but there is nowhere in the expression to go
257
+ // up to (no numerator or exponent). For ease of use,
258
+ // interpret this as an attempt to create an exponent.
259
+ mathField.typedText("^");
260
+ },
261
+ },
262
+ });
263
+
264
+ // Ideally, we would be able to pass an initial value directly into
265
+ // the constructor above
266
+ this.mathField().latex(this.props.value);
267
+
268
+ initialized = true;
269
+ }
270
+
271
+ focus: () => void = () => {
272
+ this.mathField().focus();
273
+ this.setState({focused: true});
274
+ };
275
+
276
+ blur: () => void = () => {
277
+ this.mathField().blur();
278
+ this.setState({focused: false});
279
+ };
280
+ }
281
+
282
+ export default MathInput;
@@ -0,0 +1,128 @@
1
+ /* eslint-disable react/sort-comp */
2
+ // @flow
3
+ import $ from "jquery";
4
+ import PropTypes from "prop-types";
5
+ import * as React from "react";
6
+ import ReactDOM from "react-dom";
7
+ import _ from "underscore";
8
+
9
+ import {getDependencies} from "../dependencies.js";
10
+ import {ClassNames as ApiClassNames} from "../perseus-api.jsx";
11
+ import TexWrangler from "../tex-wrangler.js";
12
+
13
+ const ModifyTex = TexWrangler.modifyTex;
14
+
15
+ class MathOutput extends React.Component<$FlowFixMe, $FlowFixMe> {
16
+ static propTypes = {
17
+ value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
18
+ className: PropTypes.string,
19
+ labelText: PropTypes.string,
20
+ onFocus: PropTypes.func,
21
+ onBlur: PropTypes.func,
22
+ };
23
+
24
+ static defaultProps: $FlowFixMe = {
25
+ value: "",
26
+ onFocus: function () {},
27
+ onBlur: function () {},
28
+ };
29
+
30
+ state: $FlowFixMe = {
31
+ focused: false,
32
+ selectorNamespace: _.uniqueId("math-output"),
33
+ };
34
+
35
+ _getInputClassName: () => string = () => {
36
+ let className =
37
+ "math-output " +
38
+ ApiClassNames.INPUT +
39
+ " " +
40
+ ApiClassNames.INTERACTIVE;
41
+ if (this.state.focused) {
42
+ className += " " + ApiClassNames.FOCUSED;
43
+ }
44
+ if (this.props.className) {
45
+ className += " " + this.props.className;
46
+ }
47
+ return className;
48
+ };
49
+
50
+ _getDisplayValue: (string | number) => string = (value) => {
51
+ // Cast from (potentially a) number to string
52
+ let displayText;
53
+ if (value != null) {
54
+ displayText = "" + value;
55
+ } else {
56
+ displayText = "";
57
+ }
58
+ return ModifyTex(displayText);
59
+ };
60
+
61
+ render(): React.Node {
62
+ const divStyle = {
63
+ textAlign: "center",
64
+ };
65
+ const {TeX} = getDependencies();
66
+
67
+ return (
68
+ <span
69
+ // eslint-disable-next-line react/no-string-refs
70
+ ref="input"
71
+ className={this._getInputClassName()}
72
+ aria-label={this.props.labelText}
73
+ onMouseDown={this.focus}
74
+ onTouchStart={this.focus}
75
+ >
76
+ <div style={divStyle}>
77
+ <TeX>{this._getDisplayValue(this.props.value)}</TeX>
78
+ </div>
79
+ </span>
80
+ );
81
+ }
82
+
83
+ getValue: () => string | number = () => {
84
+ return this.props.value;
85
+ };
86
+
87
+ focus: () => void = () => {
88
+ if (!this.state.focused) {
89
+ this.props.onFocus();
90
+ this._bindBlurHandler();
91
+ this.setState({
92
+ focused: true,
93
+ });
94
+ }
95
+ };
96
+
97
+ blur: () => void = () => {
98
+ if (this.state.focused) {
99
+ this.props.onBlur();
100
+ this._unbindBlurHandler();
101
+ this.setState({
102
+ focused: false,
103
+ });
104
+ }
105
+ };
106
+
107
+ _bindBlurHandler: () => void = () => {
108
+ $(document).bind("vclick." + this.state.selectorNamespace, (e) => {
109
+ // Detect whether the target has our React DOM node as a parent
110
+ const $closestWidget = $(e.target).closest(
111
+ ReactDOM.findDOMNode(this),
112
+ );
113
+ if (!$closestWidget.length) {
114
+ this.blur();
115
+ }
116
+ });
117
+ };
118
+
119
+ _unbindBlurHandler: () => void = () => {
120
+ $(document).unbind("." + this.state.selectorNamespace);
121
+ };
122
+
123
+ componentWillUnmount() {
124
+ this._unbindBlurHandler();
125
+ }
126
+ }
127
+
128
+ export default MathOutput;
@@ -0,0 +1,137 @@
1
+ // @flow
2
+
3
+ import {css, StyleSheet} from "aphrodite";
4
+ import * as React from "react";
5
+ import * as ReactDOM from "react-dom";
6
+
7
+ type Props = {|
8
+ // the initial values of the buttons selected, defaults to null (no
9
+ // selection).
10
+ values: ?$ReadOnlyArray<any>,
11
+ buttons: $ReadOnlyArray<{|
12
+ // the value returned when the button is selected
13
+ value: any,
14
+ // the content shown within the button, typically a string that gets
15
+ // rendered as the button's display text
16
+ content: React.Node,
17
+ // the title-text shown on hover
18
+ title?: string,
19
+ |}>,
20
+ // a function that is provided with the updated set of selected value
21
+ // (which it then is responsible for updating)
22
+ onChange: (values: any) => mixed,
23
+ // if false, at least one button must be selected at all times.
24
+ // defaults to true
25
+ allowEmpty?: boolean,
26
+ |};
27
+
28
+ type DefaultProps = {|
29
+ allowEmpty: Props["allowEmpty"],
30
+ values: Props["values"],
31
+ |};
32
+
33
+ /**
34
+ * MultiButtonGroup is an aesthetically pleasing group of buttons,
35
+ * which allows multiple buttons to be selected at the same time.
36
+ *
37
+ * NOTE: This component is almost identical to ./button-group.jsx except that
38
+ * this component allows multiple selection!
39
+ */
40
+ class MultiButtonGroup extends React.Component<Props> {
41
+ static defaultProps: DefaultProps = {
42
+ values: [],
43
+ allowEmpty: true,
44
+ };
45
+
46
+ focus(): boolean {
47
+ // $FlowFixMe[prop-missing]
48
+ ReactDOM.findDOMNode(this)?.focus();
49
+ return true;
50
+ }
51
+
52
+ toggleSelect: (any) => void = (newValue: any) => {
53
+ const values = (this.props.values || []).slice(0);
54
+ const allowEmpty = this.props.allowEmpty;
55
+
56
+ if (
57
+ values.indexOf(newValue) >= 0 &&
58
+ (values.length > 1 || allowEmpty)
59
+ ) {
60
+ // If the value is already selected, unselect it
61
+ values.splice(values.indexOf(newValue), 1);
62
+ } else {
63
+ // Otherwise merge with other values and return
64
+ if (values.indexOf(newValue) < 0) {
65
+ values.push(newValue);
66
+ }
67
+ }
68
+
69
+ this.props.onChange(values);
70
+ };
71
+
72
+ render(): React.Element<"div"> {
73
+ const values = this.props.values || [];
74
+ const buttons = this.props.buttons.map((button, i) => {
75
+ const selected = values.indexOf(button.value) >= 0;
76
+ return (
77
+ <button
78
+ title={button.title}
79
+ type="button"
80
+ id={"" + i}
81
+ key={"" + i}
82
+ ref={"button" + i}
83
+ className={css(
84
+ styles.buttonStyle,
85
+ selected && styles.selectedStyle,
86
+ )}
87
+ onClick={() => this.toggleSelect(button.value)}
88
+ >
89
+ {button.content || "" + button.value}
90
+ </button>
91
+ );
92
+ });
93
+
94
+ const outerStyle = {
95
+ display: "inline-block",
96
+ };
97
+ return <div style={outerStyle}>{buttons}</div>;
98
+ }
99
+ }
100
+
101
+ const styles = StyleSheet.create({
102
+ buttonStyle: {
103
+ backgroundColor: "white",
104
+ border: "1px solid #ccc",
105
+ borderLeft: "0",
106
+ cursor: "pointer",
107
+ margin: "0",
108
+ padding: "5px 10px",
109
+ position: "relative", // for hover
110
+
111
+ ":first-child": {
112
+ borderLeft: "1px solid #ccc",
113
+ borderTopLeftRadius: "3px",
114
+ borderBottomLeftRadius: "3px",
115
+ },
116
+
117
+ ":last-child": {
118
+ borderRight: "1px solid #ccc",
119
+ borderTopRightRadius: "3px",
120
+ borderBottomRightRadius: "3px",
121
+ },
122
+
123
+ ":hover": {
124
+ backgroundColor: "#ccc",
125
+ },
126
+
127
+ ":focus": {
128
+ zIndex: 2,
129
+ },
130
+ },
131
+
132
+ selectedStyle: {
133
+ backgroundColor: "#ddd",
134
+ },
135
+ });
136
+
137
+ export default MultiButtonGroup;
@@ -0,0 +1,110 @@
1
+ # `NewTooltip`: KA tooltip component
2
+
3
+ Across the Khan Academy webapp, we use a shared "tooltip" UI element, to visually annotate page elements with additional information. This React component, `NewTooltip`, is designed to satisfy all of our tooltip needs! πŸ˜„
4
+
5
+ **Warning: This component is WIP!** We're in the process of rolling out this tooltip to eventually replace the old `Tooltip` component. `NewTooltip` is currently is only used at a select few call sites. [Scroll down for details.](#warning-this-component-is-wip)
6
+
7
+ ## Usage
8
+
9
+ To point a tooltip at an element, wrap it in a `NewTooltip` component:
10
+
11
+ ```jsx
12
+ <NewTooltip content={i18n._("You should click on this!")}>
13
+ <button><$_>Assign to students</$_></button>
14
+ </NewTooltip>
15
+ ```
16
+
17
+ This will produce a tooltip that says "You should click on this!" when you hover over the wrapped button.
18
+
19
+ You can also provide more advanced content:
20
+
21
+ ```jsx
22
+ const content = <div className={css(styles.tooltipContent)}>
23
+ <p><$_>You should click on this!</$_></p>
24
+ <p><$_>It will help you!</$_></p>
25
+ </div>;
26
+
27
+ <NewTooltip content={content}>
28
+ <button>Assign to students</button>
29
+ </NewTooltip>
30
+ ```
31
+
32
+ There are also many styling, positioning, and behavioral options for the tooltip! See `types.js` for the list of options, and the [React Sandbox][fixtures] to see them in action.
33
+
34
+ ## Accessibility
35
+
36
+ Unlike some other tooltip components on the web, this tooltip is a _visual_ element rather than a semantic element. Its use cases are relatively diverse:
37
+
38
+ * Explaining a UI control
39
+ * Illustrating a relationship between two permanent UI elements
40
+ * Showing a transient UI element without disrupting the flow of the document
41
+ * Prompting the user to take a secondary or blocking action (tooltip) before taking the main action (target)
42
+
43
+ The key commonality is that the tooltip and its target are somehow _related_… but that's about it.
44
+
45
+ **So, be sure to add your own ARIA attributes to tooltip targets and content!** Only you know the semantic relationship between your tooltip and target, so it's up to you to express it.
46
+
47
+ ## Package structure
48
+
49
+ The two most important files in this package are:
50
+
51
+ * `new-tooltip.jsx`: The entry point to our tooltip code. Manages high-level visibility state. Renders the tooltip's target element inline, and renders a `TooltipPortal` to get the tooltip content outside of any `overflow: hidden` containers.
52
+ * `types.js`: Defines the props for `NewTooltip`.
53
+
54
+ We also have a number of supporting files, as well:
55
+
56
+ * React components
57
+ * `tooltip-portal.jsx`: Simulates a [React 16 portal][react-portal]. Creates a node near the root of the document, and renders a `TooltipPositioner` there, instead of inline with the target element.
58
+ * `tooltip-positioner.jsx`: Manages the position of the tooltip UI. Computes the position of the target element, and watches for changes. Renders a `TooltipBubble` and `TooltipArrow`, positioned to point at the target element.
59
+ * `tooltip-bubble.jsx`: The main tooltip "bubble" element. Renders the tooltip content.
60
+ * `tooltip-arrow.jsx`: The little triangle that points from the tooltip bubble to the target element.
61
+ * Library files:
62
+ * `util.js`: Miscellaneous utility functions.
63
+
64
+ Note that these components are relatively tightly coupledβ€”for example, rather than specifying their individual data requirements, each tooltip component just forwards the entire set of `tooltipProps` to its child tooltip components. This enables faster iteration, but means that the child components aren't especially reusable.
65
+
66
+ ## Warning: This component is WIP!
67
+
68
+ This is `NewTooltip`, a fork of the old `Tooltip`, in which we've built a brand new layout algorithm to address some key UX issues. Someday, `NewTooltip` will replace `Tooltip`, hooray! [See ADR #33 for details.][adr-33]
69
+
70
+ `NewTooltip` is probably _feature-ready_ for most use cases on Khan Academy, but **general usage is not yet advised**. We're still developing our plan for the "Wonder Blocks" shared component rollout, and we want to keep call sites well-constrained until we know more. But, if you _need_ the features of `NewTooltip` today, maybe we can figure something out! Hit up the [#reusable-components][reusable-components] Slack channel.
71
+
72
+ But, if you need a feature that hasn't been ported yet, maybe you'll want to continue to use `Tooltip`. (Or port the feature into `NewTooltip`? 😍)
73
+
74
+ Here's the overall behavioral comparison right now. Especially valuable features are marked with a star (⭐️).
75
+
76
+ | Feature | `NewTooltip` | `Tooltip` |
77
+ |----------------------------------------------|--------------|-----------|
78
+ | ⭐️ **Basic tooltip functionality** | βœ… | βœ… |
79
+ | ⭐️ **Escape `overflow: hidden` containers** | βœ… | βœ… |
80
+ | ⭐️ **Attach to any side of the target** | βœ… | βœ… |
81
+ | ⭐️ **Configurable colors** | βœ… | βœ… |
82
+ | ⭐️ **Dismiss on click** | βœ… | βœ… |
83
+ | ⭐️ **Only show once per user lifetime** | βœ… | βœ… |
84
+ | Tweak position via the `offset` prop | βœ… | βœ… |
85
+ | Arrow center-aligned with bubble | βœ… | βœ… |
86
+ | ⭐️ **Auto-shift position to avoid wrapping** | βœ… | ❌ |
87
+ | ⭐️ **Auto-reposition during scrolling** | βœ… | ❌ |
88
+ | ⭐️ **Update content without remounting** | βœ… | ❌ |
89
+ | ⭐️ **Media queries to show/hide tooltip** | βœ… | ❌ |
90
+ | Can specify offset from the _arrow_ | βœ… | ❌ |
91
+ | Can specify margin around tooltip | βœ… | ❌ |
92
+ | ⭐️ **Can be used inside a modal** | βœ… | βœ… |
93
+ | Can target `position: fixed` elements | ❌ | βœ… |
94
+ | Auto-choose top or bottom anchoring | ❌ | βœ… |
95
+ | Try to left/right align tooltip with target | ❌ | βœ… |
96
+ | Manual positioning (left/top) | ❌ | βœ… |
97
+ | Dismiss on scroll/interaction/timer | ❌ | βœ… |
98
+ | Show tooltip when target gains focus | ❌ | βœ… |
99
+
100
+ > NOTE(mdr): It's possible that some of the old `Tooltip` features are also "especially valuable", and I just don't know about it yet! So far we've only used `NewTooltip` at GTP call sites, and they haven't needed many of these features, so it's not clear to me how broadly they're actually used. (We'll probably have to support most of the missing options eventually, but maybe not all, in case some of their use cases were addressed by the general positioning improvements?)
101
+
102
+ Eventually, we'll roll out `NewTooltip` to all KA call sites, and upgrade it to support everything that the old `Tooltip` call sites need. So, no worries if you end up adding new `Tooltip` call sites; we'll help upgrade them soon! [For more details, see ADR #33.][adr-33]
103
+
104
+ [adr-33]: https://docs.google.com/document/d/1B4pMGWp344e96RmIbnsmUBiTbrizdbOvipe-9I_1iZQ/edit
105
+ [fixtures]: http://localhost:8081/react-sandbox/components/new-tooltip-package/new-tooltip.jsx.fixture.js
106
+ [react-portal]: https://reactjs.org/docs/portals.html
107
+ [reusable-components]: https://khanacademy.slack.com/messages/C4PE1QM5Y/
108
+
109
+ Code-Owner: Frontend Infrastructure
110
+ Crowdin-Category: _other_