@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,399 @@
1
+ /* eslint-disable react/no-unsafe */
2
+ // @flow
3
+ /**
4
+ * This component has been obsoleted by the Wonder Blocks tooltip (https://wonder-blocks.netlify.app/#tooltip-1).
5
+ * It is no longer a WIP. The Wonder Blocks tooltip should be used in future development.
6
+ *
7
+ * This top-level component primarily manages high-level visibility state. It
8
+ * renders the tooltip's target element inline, and renders a TooltipPortal
9
+ * to get the tooltip content outside of any `overflow: hidden` containers.
10
+ *
11
+ * For usage information and help navigating this package, see `README.md`.
12
+ * For prop type information, see `types.js`.
13
+ *
14
+ * Public methods:
15
+ * - `remeasure()`: Re-measure the target element's position, and update the
16
+ * tooltip's position. This is helpful to call when the parent is aware of
17
+ * a change in the target element's position, which might not be clear from
18
+ * the events that the tooltip is watching (e.g., scrolls).
19
+ */
20
+ import {StyleSheet, css} from "aphrodite";
21
+ import PropTypes from "prop-types";
22
+ import * as React from "react";
23
+
24
+ import {getDependencies} from "../../dependencies.js";
25
+ import {Errors, Log} from "../../logging/log.js";
26
+ import {PerseusError} from "../../perseus-error.js";
27
+ import {colors} from "../../styles/global-styles.js";
28
+ import {createVisibilityObserver} from "../visibility-observer/visibility-observer.js";
29
+
30
+ import TooltipPortal from "./tooltip-portal.jsx";
31
+
32
+ import type {VisibilityObserver} from "../visibility-observer/visibility-observer.js";
33
+ import type {NewTooltipProps} from "./types.js";
34
+
35
+ type NewTooltipState = {|
36
+ // Whether the tooltip has been dismissed.
37
+ dismissed: boolean,
38
+ // Whether the target element is currently being hovered.
39
+ hovered: boolean,
40
+ // Whether the target element is currently visible, relative to the "root
41
+ // element" where we're mounting the tooltip.
42
+ //
43
+ // NOTE(mdr): This can be true (element is visible), false (element is not
44
+ // visible), or null (unknown).
45
+ targetElementIsVisible: ?boolean,
46
+ |};
47
+
48
+ type Props = {|
49
+ ...NewTooltipProps,
50
+
51
+ // The wrapped content that serves as the target of the tooltip.
52
+ //
53
+ // This must be a single React element, with block-like styles. (e.g.,
54
+ // `display: block`, `display: inline-block`, `display: flex`, etc.)
55
+ //
56
+ // Unfortunately, we can't reliably track the position of inline elements,
57
+ // and we can't wrap your children in a block-like element without
58
+ // potentially disrupting your call site's layout.
59
+ //
60
+ // So, your element will need to be block-like. Sorry for the extra
61
+ // constraint! (For details, see the `render` method of `NewTooltip`).
62
+ children: React$Element<any>,
63
+ |};
64
+
65
+ type DefaultProps = {|
66
+ color: Props["color"],
67
+ dismissOnClickClose: Props["dismissOnClickClose"],
68
+ inverted: Props["inverted"],
69
+ noPadding: Props["noPadding"],
70
+ offset: Props["offset"],
71
+ offsetFrom: Props["offsetFrom"],
72
+ showOnMount: Props["showOnMount"],
73
+ side: Props["side"],
74
+ toggleOnHover: Props["toggleOnHover"],
75
+ tooltipMargin: Props["tooltipMargin"],
76
+ |};
77
+
78
+ export const DefaultPropValues: DefaultProps = Object.freeze({
79
+ side: "top",
80
+ offset: 0,
81
+ offsetFrom: "bubble",
82
+ tooltipMargin: 0,
83
+ noPadding: false,
84
+ color: colors.gray17,
85
+ inverted: false,
86
+ dismissOnClickClose: false,
87
+ toggleOnHover: true,
88
+ showOnMount: false,
89
+ });
90
+
91
+ class NewTooltip extends React.PureComponent<Props, NewTooltipState> {
92
+ // The root element, where we'll mount tooltips.
93
+ _rootElement: HTMLElement;
94
+
95
+ // The wrapper element that we created, to listen to mouse events.
96
+ _wrapper: ?HTMLElement;
97
+
98
+ // The target element itself: the child of the wrapper, provided by the
99
+ // caller.
100
+ _targetElement: ?HTMLElement;
101
+
102
+ // Our child TooltipPortal instance. Used to pass down `remeasure` calls.
103
+ _tooltipPortal: ?TooltipPortal;
104
+
105
+ // The VisibilityObserver, notifying us when the element enters or exits
106
+ // total invisibility. Will be null iff the browser doesn't support the
107
+ // necessary APIs.
108
+ _visibilityObserver: VisibilityObserver;
109
+
110
+ // Whether this component is mounted.
111
+ _isMounted: boolean;
112
+
113
+ // A timeout used in `componentDidMount`.
114
+ _targetElementMountTimeout: ?TimeoutID;
115
+
116
+ static defaultProps: DefaultProps = DefaultPropValues;
117
+
118
+ constructor(props: Props) {
119
+ super(props);
120
+
121
+ this._isMounted = false;
122
+
123
+ this.state = {
124
+ dismissed: props.dismissed != null ? props.dismissed : false,
125
+ hovered: false,
126
+ targetElementIsVisible: false,
127
+ };
128
+ }
129
+
130
+ componentDidMount() {
131
+ this._isMounted = true;
132
+
133
+ this._registerVisibilityObserver();
134
+ }
135
+
136
+ UNSAFE_componentWillReceiveProps(nextProps: Props) {
137
+ if (nextProps.dismissed != null) {
138
+ // We're serving as a "controlled component": our dismissed state
139
+ // always matches the incoming props.
140
+ this.setState({dismissed: nextProps.dismissed});
141
+ }
142
+ }
143
+
144
+ UNSAFE_componentWillUpdate() {
145
+ // The component may receive a new root element via the context.
146
+ // For example, we may get a modal root element that has become
147
+ // available.
148
+
149
+ this._registerVisibilityObserver();
150
+ }
151
+
152
+ componentWillUnmount() {
153
+ this._isMounted = false;
154
+ if (this._targetElementMountTimeout) {
155
+ // TODO(jeff, WEB-1378): Use Wonder Blocks Timing API.
156
+ // eslint-disable-next-line no-restricted-syntax
157
+ clearTimeout(this._targetElementMountTimeout);
158
+ }
159
+ this._visibilityObserver.disconnect();
160
+ }
161
+
162
+ _findRootElement(): any {
163
+ // In the Khan Academy webapp, the best place to mount is the
164
+ // #outer-wrapper element, because it's our outermost scroll container.
165
+ // But, if we're for some reason in a page without an #outer-wrapper,
166
+ // then mounting at the body element is probably a good guess.
167
+ // Also, if there is a modal anywhere in the context, it should be the
168
+ // root because it obscures the visibility of the higher containers.
169
+ //
170
+ // HACK(davidflanagan):
171
+ // Missions use a modal-like ../../tasks-package/task-container.jsx
172
+ // component that does not work like our standard modal, so we handle
173
+ // that with a special hardcoded case element id. When this component
174
+ // is converted to a wonder block, we should upgrade Modal to use
175
+ // the new context mechanism instead of the legacy mechanism and
176
+ // replace this mission-task-container id with context.
177
+ return (
178
+ this.context.modalContainerElement ||
179
+ document.getElementById("mission-task-container") ||
180
+ document.getElementById("outer-wrapper") ||
181
+ document.body
182
+ );
183
+ }
184
+
185
+ // When this component mounts inside a modal, we don't have the modal
186
+ // container in the context yet. That comes during the next cycle. If the
187
+ // tooltip is in the modal, it will be observing with respect to the app
188
+ // root for a moment (and won't display because it is obscured) and then
189
+ // it will get called again on the next update because the context changes.
190
+ // When it sees that the root is different, it will unregister the first
191
+ // and register the new one.
192
+ _registerVisibilityObserver() {
193
+ const rootElement = this._findRootElement();
194
+
195
+ if (!rootElement) {
196
+ throw new PerseusError(
197
+ "Failed to mount NewTooltip: " +
198
+ "we need a #outer-wrapper or document.body element.",
199
+ Errors.Internal,
200
+ );
201
+ }
202
+
203
+ if (rootElement === this._rootElement) {
204
+ return;
205
+ }
206
+
207
+ this._rootElement = rootElement;
208
+
209
+ if (this._visibilityObserver) {
210
+ this._visibilityObserver.disconnect();
211
+ }
212
+
213
+ // Create a VisibilityObserver, and start by assuming the target
214
+ // element is invisible. On mount, we'll start observing the
215
+ // target, and check whether it's visible.
216
+ this._visibilityObserver = createVisibilityObserver(
217
+ rootElement,
218
+ this._handleVisibilityChange,
219
+ );
220
+
221
+ // Find our target element. We do this by walking down the DOM tree,
222
+ // until we find a node that doesn't have `data-tooltip-wrapper` on it.
223
+ //
224
+ // (This also enables us to compose tooltips: if you wrap an element
225
+ // in multiple tooltips, we'll skip *all* the wrapper elements, and
226
+ // find the correct element.)
227
+ //
228
+ // HACK(mdr): I'm not entirely satisfied with this :/ The whole tooltip
229
+ // composition "API" is a bit of a hack to support different
230
+ // tooltip props based on media query, and a *robust* way of
231
+ // specifying that would probably be preferable... but, in the
232
+ // meantime, I'd rather not invent new styling patterns (there's
233
+ // already enough fragmentation!), and instead write some
234
+ // well-encapsulated hacks that keep the public-facing API simple
235
+ // and low on new concepts.
236
+ const wrapper = this._wrapper;
237
+ if (!wrapper) {
238
+ throw new PerseusError(
239
+ "NewTooltip expected a wrapper element after mount",
240
+ Errors.Internal,
241
+ );
242
+ }
243
+ let targetElement: HTMLElement = wrapper;
244
+ while (targetElement.hasAttribute("data-tooltip-wrapper")) {
245
+ targetElement = (targetElement.childNodes[0]: any);
246
+ if (!targetElement) {
247
+ throw new PerseusError(
248
+ "NewTooltip expected wrapper element to have a child",
249
+ Errors.Internal,
250
+ );
251
+ }
252
+ }
253
+ this._targetElement = targetElement;
254
+
255
+ // Now that we've mounted, update our TooltipPortal (by re-rendering)
256
+ // and our VisibilityObserver, so they can be aware of our new target
257
+ // element.
258
+ // TODO(jeff, WEB-1378): Use Wonder Blocks Timing API.
259
+ // eslint-disable-next-line no-restricted-syntax
260
+ this._targetElementMountTimeout = setTimeout(() => {
261
+ const targetElement = this._targetElement;
262
+ if (!targetElement) {
263
+ throw new PerseusError(
264
+ "NewTooltip should have a target element after mounting.",
265
+ Errors.Internal,
266
+ );
267
+ }
268
+
269
+ // While we're here, let's also check whether the element is
270
+ // block-like, as is required for position tracking to work. If
271
+ // not, warn the developer - but in prod, skip this, because
272
+ // it's slow.
273
+ if (getDependencies().isDevServer) {
274
+ if (getComputedStyle(targetElement).display === "inline") {
275
+ Log.error(
276
+ "Warning: The children of a NewTooltip element " +
277
+ "must be block-like, but this element seems " +
278
+ "to have `display: inline`. This could break " +
279
+ "position tracking.",
280
+ Errors.Internal,
281
+ );
282
+ Log.log("NewTooltip Target Element:", {
283
+ targetElement: targetElement.outerHTML,
284
+ });
285
+ }
286
+ }
287
+
288
+ this._visibilityObserver.setTargetElement(targetElement);
289
+
290
+ this.forceUpdate();
291
+ }, 0);
292
+ }
293
+
294
+ _shouldShowTooltip(): boolean {
295
+ const {toggleOnHover, showOnMount} = this.props;
296
+ const {dismissed, targetElementIsVisible} = this.state;
297
+
298
+ // If the target element isn't visible, then we shouldn't show the
299
+ // tooltip, regardless of configuration.
300
+ if (targetElementIsVisible === false) {
301
+ return false;
302
+ }
303
+
304
+ // If the tooltip was dismissed, or we're not sure yet, don't show it.
305
+ if (dismissed) {
306
+ return false;
307
+ }
308
+
309
+ // If the tooltip should show on mount, and nothing has changed yet
310
+ // that would cause it to hide (i.e., the `dismissed` case above fell
311
+ // through), show the tooltip.
312
+ if (showOnMount) {
313
+ return true;
314
+ }
315
+
316
+ // If the target is hovered, and our visibility is hover-based, show
317
+ // the tooltip.
318
+ if (toggleOnHover && this.state.hovered) {
319
+ return true;
320
+ }
321
+
322
+ return false;
323
+ }
324
+
325
+ _handleVisibilityChange: (targetElementIsVisible: boolean) => void = (
326
+ targetElementIsVisible: boolean,
327
+ ) => {
328
+ this.setState({targetElementIsVisible});
329
+ };
330
+
331
+ _handleMouseEnter: () => void = () => {
332
+ this.setState({hovered: true});
333
+ };
334
+
335
+ _handleMouseLeave: () => void = () => {
336
+ this.setState({hovered: false});
337
+ };
338
+
339
+ _handleDismiss: () => void = () => {
340
+ if (this.props.dismissed == null) {
341
+ // We're serving as an "uncontrolled component": our dismissed
342
+ // state is managed by us, not by incoming props.
343
+ this.setState({dismissed: true});
344
+ }
345
+ this.props.onDismiss && this.props.onDismiss();
346
+ };
347
+
348
+ remeasure() {
349
+ this._tooltipPortal && this._tooltipPortal.remeasure();
350
+ }
351
+
352
+ render(): React.Element<"div"> {
353
+ const {children, toggleOnHover} = this.props;
354
+ const {children: _, ...portalProps} = this.props;
355
+
356
+ return (
357
+ <div
358
+ className={css(styles.targetElement)}
359
+ onMouseEnter={
360
+ toggleOnHover ? this._handleMouseEnter : undefined
361
+ }
362
+ onMouseLeave={
363
+ toggleOnHover ? this._handleMouseLeave : undefined
364
+ }
365
+ ref={(node) => (this._wrapper = node)}
366
+ data-tooltip-wrapper
367
+ data-test-id="question-id"
368
+ >
369
+ {children}
370
+ {this._targetElement && this._shouldShowTooltip() && (
371
+ <TooltipPortal
372
+ tooltipProps={portalProps}
373
+ dismiss={this._handleDismiss}
374
+ rootElement={this._rootElement}
375
+ targetElement={this._targetElement}
376
+ isAboveModal={!!this.context.modalContainerElement}
377
+ ref={(node) => (this._tooltipPortal = node)}
378
+ />
379
+ )}
380
+ </div>
381
+ );
382
+ }
383
+ }
384
+
385
+ NewTooltip.contextTypes = {
386
+ modalContainerElement: PropTypes.instanceOf(HTMLElement),
387
+ };
388
+
389
+ const styles = StyleSheet.create({
390
+ targetElement: {
391
+ // NOTE(mdr): By setting this container to `display: inline`, we avoid
392
+ // disrupting the call site's layout! Yay! Unfortunately, this
393
+ // requires other hacks in order to do position tracking; see the
394
+ // `cloneElement` call in `render`.
395
+ display: "inline",
396
+ },
397
+ });
398
+
399
+ export default NewTooltip;
@@ -0,0 +1,13 @@
1
+ // @flow
2
+ /**
3
+ * Constant style values used across the tooltip components.
4
+ */
5
+ import * as constants from "../../styles/global-constants.js";
6
+
7
+ export const arrowWidth = 18;
8
+ export const arrowHeight = 9;
9
+ export const bubbleBorderRadius: number = parseInt(constants.borderRadiusLarge);
10
+ export const dropShadowXOffset = 0;
11
+ export const dropShadowYOffset = 1;
12
+ export const dropShadowOpacity = 0.1;
13
+ export const dropShadowRadius = 4;
@@ -0,0 +1,184 @@
1
+ // @flow
2
+ /**
3
+ * This is TooltipArrow, part of NewTooltip.
4
+ *
5
+ * This component manages the visuals for the tooltip "arrow" UI element, which
6
+ * is visually attached to the TooltipBubble, and points to the target element.
7
+ *
8
+ * This component and TooltipBubble both count as part of the tooltip, and
9
+ * trigger mouse events like `onClick`, `onMouseEnter`, and `onMouseLeave`.
10
+ */
11
+ import {StyleSheet, css} from "aphrodite";
12
+ import * as React from "react";
13
+
14
+ import {Errors} from "../../logging/log.js";
15
+ import {PerseusError} from "../../perseus-error.js";
16
+
17
+ import {
18
+ arrowHeight,
19
+ arrowWidth,
20
+ dropShadowOpacity,
21
+ dropShadowRadius,
22
+ dropShadowXOffset,
23
+ dropShadowYOffset,
24
+ } from "./style-constants.js";
25
+ import {getTooltipColors} from "./util.js";
26
+
27
+ import type {NewTooltipProps} from "./types.js";
28
+
29
+ type TooltipArrowProps = {|
30
+ tooltipProps: {
31
+ side: NewTooltipProps["side"],
32
+ color: NewTooltipProps["color"],
33
+ inverted: NewTooltipProps["inverted"],
34
+
35
+ onClick?: NewTooltipProps["onClick"],
36
+ onMouseEnter?: NewTooltipProps["onMouseEnter"],
37
+ onMouseLeave?: NewTooltipProps["onMouseLeave"],
38
+ ...
39
+ },
40
+ |};
41
+
42
+ export default class TooltipArrow extends React.PureComponent<TooltipArrowProps> {
43
+ render(): React.Element<"svg"> {
44
+ const {onClick, onMouseEnter, onMouseLeave, side} =
45
+ this.props.tooltipProps;
46
+ const {backgroundColor} = getTooltipColors(this.props.tooltipProps);
47
+
48
+ // Draw the three points of the arrow. Depending on the arrow's
49
+ // direction (i.e., the tooltip's "side"), we choose different points,
50
+ // and set our SVG's bounds differently.
51
+ //
52
+ // Note that `arrowWidth` and `arrowHeight` refer to the
53
+ // downward-pointing arrow (i.e. side="top"). When the arrow points to
54
+ // the left or right instead, the width/height are inverted.
55
+ let points;
56
+ let svgWidth;
57
+ let svgHeight;
58
+ if (side === "top") {
59
+ points = [
60
+ "0,0",
61
+ `${arrowWidth / 2},${arrowHeight}`,
62
+ `${arrowWidth},0`,
63
+ ];
64
+ svgWidth = arrowWidth;
65
+ svgHeight = arrowHeight;
66
+ } else if (side === "right") {
67
+ points = [
68
+ `${arrowHeight},0`,
69
+ `0,${arrowWidth / 2}`,
70
+ `${arrowHeight},${arrowWidth}`,
71
+ ];
72
+ svgWidth = arrowHeight;
73
+ svgHeight = arrowWidth;
74
+ } else if (side === "bottom") {
75
+ points = [
76
+ `0,${arrowHeight}`,
77
+ `${arrowWidth / 2},0`,
78
+ `${arrowWidth},${arrowHeight}`,
79
+ ];
80
+ svgWidth = arrowWidth;
81
+ svgHeight = arrowHeight;
82
+ } else if (side === "left") {
83
+ points = [
84
+ "0,0",
85
+ `${arrowHeight},${arrowWidth / 2}`,
86
+ `0,${arrowWidth}`,
87
+ ];
88
+ svgWidth = arrowHeight;
89
+ svgHeight = arrowWidth;
90
+ } else {
91
+ throw new PerseusError(
92
+ `unexpected side ${side}`,
93
+ Errors.InvalidInput,
94
+ );
95
+ }
96
+
97
+ // This SVG is copied from tooltip-package/tooltip.jsx - with some minor
98
+ // modifications, because styling is handled differently.
99
+ return (
100
+ <svg
101
+ width={svgWidth}
102
+ height={svgHeight}
103
+ className={css(styles.tooltipArrow)}
104
+ >
105
+ {/* Create an SVG filter that applies a blur to an element.
106
+ * We'll apply it to a dark shape outlining the tooltip, which
107
+ * will produce the overall effect of a drop-shadow.
108
+ *
109
+ * Also, scope its ID by side, so that tooltips with other
110
+ * "side" values don't end up using the wrong filter from
111
+ * elsewhere in the document. (The `height` value depends on
112
+ * which way the arrow is turned!)
113
+ *
114
+ * In general, it's not *great* that multiple tooltips of the
115
+ * same side will yield ID conflicts... but at least they'll
116
+ * be the same filter, so it doesn't matter which one we
117
+ * reference, so long as it's for the correct side. */}
118
+ <filter id={`tooltip-dropshadow-${side}`} height="150%">
119
+ <feOffset
120
+ dx={dropShadowXOffset}
121
+ dy={dropShadowYOffset}
122
+ result="offsetblur"
123
+ />
124
+ <feGaussianBlur
125
+ in="SourceAlpha"
126
+ stdDeviation={dropShadowRadius / 2}
127
+ />
128
+ <feComponentTransfer>
129
+ <feFuncA type="linear" slope={dropShadowOpacity} />
130
+ </feComponentTransfer>
131
+ <feMerge>
132
+ <feMergeNode />
133
+ <feMergeNode in="SourceGraphic" />
134
+ </feMerge>
135
+ </filter>
136
+
137
+ {/* Draw the background of the tooltip arrow. */}
138
+ <polyline
139
+ fill={backgroundColor}
140
+ stroke={backgroundColor}
141
+ points={points.join(" ")}
142
+ // We attach our mouse event styles and handlers to this
143
+ // element, instead of the root SVG element. That way, our
144
+ // target area will only include the actual _arrow_ shape,
145
+ // rather than the entire bounding rectangle.
146
+ className={css(
147
+ onClick && styles.tooltipArrowShapeWithOnClick,
148
+ )}
149
+ onClick={onClick}
150
+ onMouseEnter={onMouseEnter}
151
+ onMouseLeave={onMouseLeave}
152
+ />
153
+
154
+ {/* Draw an outline around the tooltip arrow, and apply the
155
+ * blur filter we created above, to produce a drop shadow
156
+ * effect. */}
157
+ <polyline
158
+ // Redraw the stroke on top of the background color,
159
+ // so that the ends aren't extra dark where they meet
160
+ // the border of the tooltip.
161
+ fill={backgroundColor}
162
+ points={points.join(" ")}
163
+ stroke="rgba(0, 0, 0, 0.1)"
164
+ filter={`url(#tooltip-dropshadow-${side})`}
165
+ />
166
+ </svg>
167
+ );
168
+ }
169
+ }
170
+
171
+ const styles = StyleSheet.create({
172
+ tooltipArrow: {
173
+ display: "block",
174
+
175
+ // Ensure that clicking on the tooltip's empty space doesn't clear the
176
+ // user's text selection elsewhere in the page.
177
+ userSelect: "none",
178
+ },
179
+
180
+ tooltipArrowShapeWithOnClick: {
181
+ pointerEvents: "auto",
182
+ cursor: "pointer",
183
+ },
184
+ });