@khanacademy/perseus 0.0.1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.babelrc.js +1 -1
- package/CHANGELOG.md +7 -0
- package/dist/es/index.js +15 -1
- package/dist/es/index.js.map +1 -1
- package/dist/index.css +11934 -0
- package/dist/index.js +15 -1
- package/dist/index.js.map +1 -1
- package/dist/strings.js +148 -1
- package/package.json +40 -4
- package/src/README.md +7 -0
- package/src/__stories__/item-renderer.stories.jsx +27 -0
- package/src/__stories__/server-item-renderer.stories.jsx +27 -0
- package/src/__testdata__/item-renderer_testdata.js +161 -0
- package/src/__testdata__/renderer_testdata.js +123 -0
- package/src/__tests__/__snapshots__/item-renderer_test.jsx.snap +233 -0
- package/src/__tests__/__snapshots__/perseus-markdown_test.jsx.snap +516 -0
- package/src/__tests__/__snapshots__/pure-markdown_test.js.snap +113 -0
- package/src/__tests__/__snapshots__/renderer_test.jsx.snap +576 -0
- package/src/__tests__/__snapshots__/server-item-renderer_test.jsx.snap +306 -0
- package/src/__tests__/a11y_test.jsx +304 -0
- package/src/__tests__/item-renderer_test.jsx +892 -0
- package/src/__tests__/mock-asset-loading-widget.jsx +57 -0
- package/src/__tests__/mock-widget.jsx +35 -0
- package/src/__tests__/perseus-markdown_test.jsx +747 -0
- package/src/__tests__/pure-markdown_test.js +552 -0
- package/src/__tests__/renderability_test.jsx +481 -0
- package/src/__tests__/renderer-api_test.jsx +333 -0
- package/src/__tests__/renderer_test.jsx +1792 -0
- package/src/__tests__/server-item-renderer_test.jsx +516 -0
- package/src/__tests__/test-items/image-item.js +25 -0
- package/src/__tests__/test-items/input-number-1-item.js +27 -0
- package/src/__tests__/test-items/input-number-2-item.js +39 -0
- package/src/__tests__/test-items/table-item.js +30 -0
- package/src/__tests__/tex-wrangler_test.js +83 -0
- package/src/__tests__/util_test.js +41 -0
- package/src/a11y.js +46 -0
- package/src/all-widgets.js +9 -0
- package/src/article-renderer.jsx +215 -0
- package/src/asset-context.js +30 -0
- package/src/basic-widgets.js +13 -0
- package/src/components/__stories__/button-group.stories.jsx +61 -0
- package/src/components/__stories__/fixed-to-responsive.stories.jsx +132 -0
- package/src/components/__stories__/graph.stories.jsx +27 -0
- package/src/components/__stories__/graphie.stories.jsx +28 -0
- package/src/components/__stories__/hud.stories.jsx +36 -0
- package/src/components/__stories__/icon.stories.jsx +56 -0
- package/src/components/__stories__/image-loader.stories.jsx +60 -0
- package/src/components/__stories__/info-tip.stories.jsx +33 -0
- package/src/components/__stories__/inline-icon.stories.jsx +39 -0
- package/src/components/__stories__/input-with-examples.stories.jsx +51 -0
- package/src/components/__stories__/lint.stories.jsx +40 -0
- package/src/components/__stories__/math-input.stories.jsx +29 -0
- package/src/components/__stories__/math-output.stories.jsx +24 -0
- package/src/components/__stories__/multi-button-group.stories.jsx +69 -0
- package/src/components/__stories__/number-input.stories.jsx +42 -0
- package/src/components/__stories__/possible-answers.stories.jsx +22 -0
- package/src/components/__stories__/prop-check-box.stories.jsx +62 -0
- package/src/components/__stories__/range-input.stories.jsx +28 -0
- package/src/components/__stories__/simple-keypad-input.stories.jsx +28 -0
- package/src/components/__stories__/sortable.stories.jsx +73 -0
- package/src/components/__stories__/stub-tag-editor.stories.jsx +44 -0
- package/src/components/__stories__/svg-image.stories.jsx +60 -0
- package/src/components/__stories__/tex.stories.jsx +18 -0
- package/src/components/__stories__/text-buttons.stories.jsx +51 -0
- package/src/components/__stories__/text-input.stories.jsx +34 -0
- package/src/components/__stories__/text-list-editor.stories.jsx +33 -0
- package/src/components/__stories__/zoomable-tex.stories.jsx +39 -0
- package/src/components/__stories__/zoomable.stories.jsx +40 -0
- package/src/components/__testdata__/sorter_testdata.js +20 -0
- package/src/components/__tests__/__snapshots__/sortable_test.jsx.snap +79 -0
- package/src/components/__tests__/__snapshots__/sorter_test.jsx.snap +297 -0
- package/src/components/__tests__/number-input_test.jsx +112 -0
- package/src/components/__tests__/sortable_test.jsx +234 -0
- package/src/components/__tests__/sorter_test.jsx +111 -0
- package/src/components/__tests__/zoomable_test.jsx +388 -0
- package/src/components/button-group.jsx +131 -0
- package/src/components/constants.js +15 -0
- package/src/components/fixed-to-responsive.jsx +163 -0
- package/src/components/graph.jsx +414 -0
- package/src/components/graphie-classes.jsx +148 -0
- package/src/components/graphie-movables.jsx +342 -0
- package/src/components/graphie.jsx +353 -0
- package/src/components/highlighting/highlightable-content.jsx +245 -0
- package/src/components/highlighting/highlights.js +208 -0
- package/src/components/highlighting/ranges.js +299 -0
- package/src/components/highlighting/types.js +73 -0
- package/src/components/highlighting/ui/highlight-renderer.jsx +179 -0
- package/src/components/highlighting/ui/highlight-set-renderer.jsx +190 -0
- package/src/components/highlighting/ui/highlight-tooltip.jsx +153 -0
- package/src/components/highlighting/ui/highlighting-ui.jsx +163 -0
- package/src/components/highlighting/ui/selection-tracker.jsx +195 -0
- package/src/components/highlighting/ui/types.js +21 -0
- package/src/components/highlighting/ui/util.js +166 -0
- package/src/components/highlighting/word-indexer.jsx +155 -0
- package/src/components/hud.jsx +179 -0
- package/src/components/icon.jsx +149 -0
- package/src/components/image-loader.jsx +175 -0
- package/src/components/info-tip/info-tip.jsx +149 -0
- package/src/components/info-tip.jsx +38 -0
- package/src/components/inline-icon.jsx +81 -0
- package/src/components/input-with-examples.jsx +231 -0
- package/src/components/lint.jsx +456 -0
- package/src/components/math-input.jsx +282 -0
- package/src/components/math-output.jsx +128 -0
- package/src/components/multi-button-group.jsx +137 -0
- package/src/components/new-tooltip/README.md +110 -0
- package/src/components/new-tooltip/__stories__/flexible-position.stories.jsx +437 -0
- package/src/components/new-tooltip/__stories__/new-tooltip.stories.jsx +665 -0
- package/src/components/new-tooltip/__stories__/tooltip-arrow.stories.jsx +70 -0
- package/src/components/new-tooltip/__stories__/tooltip-bubble.stories.jsx +98 -0
- package/src/components/new-tooltip/__tests__/style-constants_test.js +8 -0
- package/src/components/new-tooltip/__tests__/util_test.js +47 -0
- package/src/components/new-tooltip/flexible-position.jsx +182 -0
- package/src/components/new-tooltip/new-tooltip.jsx +399 -0
- package/src/components/new-tooltip/style-constants.js +13 -0
- package/src/components/new-tooltip/tooltip-arrow.jsx +184 -0
- package/src/components/new-tooltip/tooltip-bubble.jsx +223 -0
- package/src/components/new-tooltip/tooltip-portal.jsx +101 -0
- package/src/components/new-tooltip/tooltip-positioner.jsx +529 -0
- package/src/components/new-tooltip/types.js +183 -0
- package/src/components/new-tooltip/util.js +27 -0
- package/src/components/number-input.jsx +253 -0
- package/src/components/possible-answers.jsx +36 -0
- package/src/components/prop-check-box.jsx +78 -0
- package/src/components/range-input.jsx +61 -0
- package/src/components/simple-keypad-input.jsx +91 -0
- package/src/components/sortable.jsx +872 -0
- package/src/components/stub-tag-editor.jsx +53 -0
- package/src/components/svg-image.jsx +762 -0
- package/src/components/tex-buttons.jsx +322 -0
- package/src/components/tex.jsx +70 -0
- package/src/components/text-input.jsx +82 -0
- package/src/components/text-list-editor.jsx +157 -0
- package/src/components/tooltip.jsx +395 -0
- package/src/components/visibility-observer/__stories__/visibility-observer.stories.jsx +140 -0
- package/src/components/visibility-observer/get-scroll-parent.js +45 -0
- package/src/components/visibility-observer/visibility-observer.js +109 -0
- package/src/components/visibility-observer/visibility-observer.jsx +123 -0
- package/src/components/zoomable-tex.jsx +70 -0
- package/src/components/zoomable.jsx +304 -0
- package/src/components.js +17 -0
- package/src/dependencies.js +22 -0
- package/src/error-boundary.jsx +48 -0
- package/src/extra-widgets.js +75 -0
- package/src/gorgon/README +40 -0
- package/src/gorgon/__tests__/matcher_test.js +497 -0
- package/src/gorgon/__tests__/rule_test.js +101 -0
- package/src/gorgon/__tests__/rules_test.js +487 -0
- package/src/gorgon/__tests__/selector-parser_test.js +52 -0
- package/src/gorgon/__tests__/tree-transformer_test.js +446 -0
- package/src/gorgon/gorgon.js +290 -0
- package/src/gorgon/proptypes.js +29 -0
- package/src/gorgon/rule.js +413 -0
- package/src/gorgon/rules/absolute-url.js +24 -0
- package/src/gorgon/rules/all-rules.js +72 -0
- package/src/gorgon/rules/blockquoted-math.js +10 -0
- package/src/gorgon/rules/blockquoted-widget.js +10 -0
- package/src/gorgon/rules/double-spacing-after-terminal.js +12 -0
- package/src/gorgon/rules/extra-content-spacing.js +12 -0
- package/src/gorgon/rules/heading-level-1.js +14 -0
- package/src/gorgon/rules/heading-level-skip.js +20 -0
- package/src/gorgon/rules/heading-sentence-case.js +11 -0
- package/src/gorgon/rules/heading-title-case.js +63 -0
- package/src/gorgon/rules/image-alt-text.js +21 -0
- package/src/gorgon/rules/image-in-table.js +10 -0
- package/src/gorgon/rules/image-spaces-around-urls.js +35 -0
- package/src/gorgon/rules/image-widget.js +50 -0
- package/src/gorgon/rules/link-click-here.js +11 -0
- package/src/gorgon/rules/lint-utils.js +48 -0
- package/src/gorgon/rules/long-paragraph.js +14 -0
- package/src/gorgon/rules/math-adjacent.js +10 -0
- package/src/gorgon/rules/math-align-extra-break.js +11 -0
- package/src/gorgon/rules/math-align-linebreaks.js +43 -0
- package/src/gorgon/rules/math-empty.js +10 -0
- package/src/gorgon/rules/math-font-size.js +12 -0
- package/src/gorgon/rules/math-frac.js +10 -0
- package/src/gorgon/rules/math-nested.js +11 -0
- package/src/gorgon/rules/math-starts-with-space.js +12 -0
- package/src/gorgon/rules/math-text-empty.js +10 -0
- package/src/gorgon/rules/math-without-dollars.js +14 -0
- package/src/gorgon/rules/nested-lists.js +11 -0
- package/src/gorgon/rules/profanity.js +10 -0
- package/src/gorgon/rules/table-missing-cells.js +20 -0
- package/src/gorgon/rules/unbalanced-code-delimiters.js +14 -0
- package/src/gorgon/rules/unescaped-dollar.js +10 -0
- package/src/gorgon/rules/widget-in-table.js +10 -0
- package/src/gorgon/selector.js +506 -0
- package/src/gorgon/tree-transformer.js +588 -0
- package/src/hint-renderer.jsx +159 -0
- package/src/hints-renderer.jsx +359 -0
- package/src/icon-paths.js +120 -0
- package/src/index.js +149 -2
- package/src/init.js +49 -0
- package/src/interaction-tracker.js +60 -0
- package/src/interactive2/__tests__/movable-mock.js +48 -0
- package/src/interactive2/__tests__/movable-point_test.js +146 -0
- package/src/interactive2/interactive-util.js +152 -0
- package/src/interactive2/movable-helper-methods.js +160 -0
- package/src/interactive2/movable-line-options.js +392 -0
- package/src/interactive2/movable-line.js +322 -0
- package/src/interactive2/movable-point-options.js +190 -0
- package/src/interactive2/movable-point.jsx +604 -0
- package/src/interactive2/movable-polygon-options.js +351 -0
- package/src/interactive2/movable-polygon.js +367 -0
- package/src/interactive2/movable.js +273 -0
- package/src/interactive2/objective_.js +79 -0
- package/src/interactive2/types.js +3 -0
- package/src/interactive2/wrapped-defaults.js +86 -0
- package/src/interactive2/wrapped-ellipse.js +87 -0
- package/src/interactive2/wrapped-line.js +109 -0
- package/src/interactive2/wrapped-path.js +47 -0
- package/src/interactive2.js +91 -0
- package/src/item-renderer.jsx +518 -0
- package/src/item-version.js +12 -0
- package/src/jipt-paragraphs.js +44 -0
- package/src/jquery.mobile.vmouse.js +545 -0
- package/src/keypad-context.js +33 -0
- package/src/loading-context.js +20 -0
- package/src/logging/log.js +107 -0
- package/src/logging/log_test.js +41 -0
- package/src/mixins/changeable.jsx +115 -0
- package/src/mixins/editor-jsonify.jsx +13 -0
- package/src/mixins/provide-keypad.jsx +131 -0
- package/src/mixins/widget-jsonify-deprecated.jsx +23 -0
- package/src/mixins/widget-prop-blacklist.js +36 -0
- package/src/multi-items/__stories__/multi-renderer.stories.js +88 -0
- package/src/multi-items/__testdata__/multi-renderer_testdata.js +174 -0
- package/src/multi-items/__tests__/__snapshots__/multi-renderer_test.js.snap +994 -0
- package/src/multi-items/__tests__/items_test.js +245 -0
- package/src/multi-items/__tests__/multi-renderer_test.js +903 -0
- package/src/multi-items/__tests__/prop-type-builders_test.js +134 -0
- package/src/multi-items/__tests__/shapes_test.js +44 -0
- package/src/multi-items/__tests__/trees_test.js +316 -0
- package/src/multi-items/item-types.js +55 -0
- package/src/multi-items/items.js +176 -0
- package/src/multi-items/multi-renderer.jsx +542 -0
- package/src/multi-items/prop-type-builders.js +105 -0
- package/src/multi-items/shape-types.js +47 -0
- package/src/multi-items/shapes.js +53 -0
- package/src/multi-items/tree-types.js +42 -0
- package/src/multi-items/trees.js +345 -0
- package/src/multi-items.js +76 -0
- package/src/not-gorgon.js +129 -0
- package/src/perseus-api.jsx +270 -0
- package/src/perseus-env.js +26 -0
- package/src/perseus-error.js +19 -0
- package/src/perseus-markdown.jsx +434 -0
- package/src/perseus-types.js +1597 -0
- package/src/pure-markdown.js +346 -0
- package/src/question-paragraph.jsx +30 -0
- package/src/renderability.jsx +108 -0
- package/src/renderer.jsx +1931 -0
- package/src/server-item-renderer.jsx +467 -0
- package/src/sigfigs.jsx +492 -0
- package/src/styles/articles.less +748 -0
- package/src/styles/constants.js +119 -0
- package/src/styles/global-constants.js +350 -0
- package/src/styles/global-styles.js +307 -0
- package/src/styles/khan-exercise.css +1304 -0
- package/src/styles/media-queries.js +48 -0
- package/src/styles/perseus-renderer.less +1008 -0
- package/src/styles/shared.js +132 -0
- package/src/styles/util.less +47 -0
- package/src/styles/variables.boostrap.less +301 -0
- package/src/styles/variables.less +68 -0
- package/src/styles/variables.shared.less +356 -0
- package/src/styles/widgets/categorizer.less +39 -0
- package/src/styles/widgets/definition.less +86 -0
- package/src/styles/widgets/dropdown.less +3 -0
- package/src/styles/widgets/expression.less +147 -0
- package/src/styles/widgets/graded-group.less +36 -0
- package/src/styles/widgets/grapher.less +11 -0
- package/src/styles/widgets/group.less +19 -0
- package/src/styles/widgets/image.less +29 -0
- package/src/styles/widgets/interactive-graph.less +61 -0
- package/src/styles/widgets/label-image.less +41 -0
- package/src/styles/widgets/matcher.less +8 -0
- package/src/styles/widgets/matrix.less +120 -0
- package/src/styles/widgets/measurer.less +20 -0
- package/src/styles/widgets/orderer.less +97 -0
- package/src/styles/widgets/passage.less +151 -0
- package/src/styles/widgets/plotter.less +33 -0
- package/src/styles/widgets/radio.less +52 -0
- package/src/styles/widgets/reaction-diagram.less +35 -0
- package/src/styles/widgets/sequence.less +5 -0
- package/src/styles/widgets/sortable.less +127 -0
- package/src/styles/widgets/table.less +56 -0
- package/src/styles/widgets/transformer.less +28 -0
- package/src/styles/widgets/unit.less +14 -0
- package/src/tex-wrangler.js +176 -0
- package/src/traversal.jsx +141 -0
- package/src/types.js +538 -0
- package/src/util/a11y.js +24 -0
- package/src/util/answer-types.js +862 -0
- package/src/util/answer-types_test.js +115 -0
- package/src/util/colors.js +94 -0
- package/src/util/fix-passage-refs.jsx +122 -0
- package/src/util/graph-utils.js +16 -0
- package/src/util/graphie.js +1585 -0
- package/src/util/interactive.js +4071 -0
- package/src/util/katex-preprocess.js +16 -0
- package/src/util/math.js +296 -0
- package/src/util/mobile-native-utils.js +14 -0
- package/src/util/mobile-native-utils_test.js +24 -0
- package/src/util/register-all-widgets-for-testing.js +12 -0
- package/src/util/scroll-utils.js +162 -0
- package/src/util/sizing-utils.js +46 -0
- package/src/util/tex.js +203 -0
- package/src/util/url-utils.js +36 -0
- package/src/util.js +990 -0
- package/src/version.js +11 -0
- package/src/widget-container.jsx +174 -0
- package/src/widgets/__stories__/categorizer.stories.jsx +15 -0
- package/src/widgets/__stories__/cs-program.stories.jsx +15 -0
- package/src/widgets/__stories__/definition.stories.jsx +38 -0
- package/src/widgets/__stories__/dropdown.stories.jsx +15 -0
- package/src/widgets/__stories__/explanation.stories.jsx +19 -0
- package/src/widgets/__stories__/expression.stories.jsx +26 -0
- package/src/widgets/__stories__/graded-group-set.stories.jsx +32 -0
- package/src/widgets/__stories__/graded-group.stories.jsx +32 -0
- package/src/widgets/__stories__/grapher.stories.jsx +47 -0
- package/src/widgets/__stories__/group.stories.jsx +15 -0
- package/src/widgets/__stories__/iframe.stories.jsx +15 -0
- package/src/widgets/__stories__/image.stories.jsx +62 -0
- package/src/widgets/__stories__/input-number.stories.jsx +90 -0
- package/src/widgets/__stories__/interaction.stories.jsx +15 -0
- package/src/widgets/__stories__/interactive-graph.stories.jsx +61 -0
- package/src/widgets/__stories__/matcher.stories.jsx +15 -0
- package/src/widgets/__stories__/matrix.stories.jsx +15 -0
- package/src/widgets/__stories__/number-line.stories.jsx +15 -0
- package/src/widgets/__stories__/numeric-input.stories.jsx +19 -0
- package/src/widgets/__stories__/orderer.stories.jsx +15 -0
- package/src/widgets/__stories__/passage-ref.stories.jsx +19 -0
- package/src/widgets/__stories__/passage.stories.jsx +19 -0
- package/src/widgets/__stories__/radio.stories.jsx +82 -0
- package/src/widgets/__stories__/sorter.stories.jsx +15 -0
- package/src/widgets/__stories__/transformer.stories.jsx +15 -0
- package/src/widgets/__stories__/video.stories.jsx +15 -0
- package/src/widgets/__testdata__/categorizer_testdata.js +50 -0
- package/src/widgets/__testdata__/cs-program_testdata.js +28 -0
- package/src/widgets/__testdata__/dropdown_testdata.js +34 -0
- package/src/widgets/__testdata__/explanation_testdata.js +56 -0
- package/src/widgets/__testdata__/expression_testdata.js +95 -0
- package/src/widgets/__testdata__/graded-group-set_testdata.js +358 -0
- package/src/widgets/__testdata__/graded-group_testdata.js +79 -0
- package/src/widgets/__testdata__/grapher_testdata.js +376 -0
- package/src/widgets/__testdata__/group_testdata.js +156 -0
- package/src/widgets/__testdata__/iframe_testdata.js +36 -0
- package/src/widgets/__testdata__/image_testdata.js +38 -0
- package/src/widgets/__testdata__/input-number_testdata.js +73 -0
- package/src/widgets/__testdata__/interaction_testdata.js +264 -0
- package/src/widgets/__testdata__/interactive-graph_testdata.js +646 -0
- package/src/widgets/__testdata__/matcher_testdata.js +34 -0
- package/src/widgets/__testdata__/matrix_testdata.js +29 -0
- package/src/widgets/__testdata__/number-line_testdata.js +31 -0
- package/src/widgets/__testdata__/numeric-input_testdata.js +266 -0
- package/src/widgets/__testdata__/orderer_testdata.js +58 -0
- package/src/widgets/__testdata__/passage-ref_testdata.js +41 -0
- package/src/widgets/__testdata__/passage_testdata.js +71 -0
- package/src/widgets/__testdata__/radio_testdata.js +210 -0
- package/src/widgets/__testdata__/sorter_testdata.js +20 -0
- package/src/widgets/__testdata__/transformer_testdata.js +109 -0
- package/src/widgets/__testdata__/video_testdata.js +22 -0
- package/src/widgets/__tests__/__snapshots__/categorizer_test.jsx.snap +928 -0
- package/src/widgets/__tests__/__snapshots__/cs-program_test.jsx.snap +75 -0
- package/src/widgets/__tests__/__snapshots__/definition_test.js.snap +99 -0
- package/src/widgets/__tests__/__snapshots__/dropdown_test.jsx.snap +143 -0
- package/src/widgets/__tests__/__snapshots__/explanation_test.js.snap +473 -0
- package/src/widgets/__tests__/__snapshots__/expression_test.jsx.snap +38 -0
- package/src/widgets/__tests__/__snapshots__/graded-group-set-jipt_test.jsx.snap +754 -0
- package/src/widgets/__tests__/__snapshots__/graded-group-set_test.jsx.snap +310 -0
- package/src/widgets/__tests__/__snapshots__/graded-group_test.jsx.snap +863 -0
- package/src/widgets/__tests__/__snapshots__/grapher_test.jsx.snap +2880 -0
- package/src/widgets/__tests__/__snapshots__/group_test.jsx.snap +1154 -0
- package/src/widgets/__tests__/__snapshots__/iframe_test.jsx.snap +69 -0
- package/src/widgets/__tests__/__snapshots__/image_test.jsx.snap +388 -0
- package/src/widgets/__tests__/__snapshots__/input-number_test.jsx.snap +978 -0
- package/src/widgets/__tests__/__snapshots__/interaction_test.jsx.snap +311 -0
- package/src/widgets/__tests__/__snapshots__/interactive-graph_test.jsx.snap +2915 -0
- package/src/widgets/__tests__/__snapshots__/matcher_test.jsx.snap +1039 -0
- package/src/widgets/__tests__/__snapshots__/matrix_test.jsx.snap +450 -0
- package/src/widgets/__tests__/__snapshots__/number-line_test.jsx.snap +888 -0
- package/src/widgets/__tests__/__snapshots__/numeric-input_test.jsx.snap +930 -0
- package/src/widgets/__tests__/__snapshots__/orderer_test.jsx.snap +275 -0
- package/src/widgets/__tests__/__snapshots__/passage-ref_test.jsx.snap +446 -0
- package/src/widgets/__tests__/__snapshots__/passage_test.jsx.snap +1988 -0
- package/src/widgets/__tests__/__snapshots__/radio_test.jsx.snap +7107 -0
- package/src/widgets/__tests__/__snapshots__/video_test.jsx.snap +163 -0
- package/src/widgets/__tests__/categorizer_test.jsx +205 -0
- package/src/widgets/__tests__/cs-program_test.jsx +45 -0
- package/src/widgets/__tests__/definition_test.js +152 -0
- package/src/widgets/__tests__/dropdown_test.jsx +106 -0
- package/src/widgets/__tests__/explanation_test.js +199 -0
- package/src/widgets/__tests__/expression_test.jsx +439 -0
- package/src/widgets/__tests__/graded-group-set-jipt_test.jsx +33 -0
- package/src/widgets/__tests__/graded-group-set_test.jsx +270 -0
- package/src/widgets/__tests__/graded-group_test.jsx +233 -0
- package/src/widgets/__tests__/grapher.cypress.jsx +486 -0
- package/src/widgets/__tests__/grapher_test.jsx +35 -0
- package/src/widgets/__tests__/group_test.jsx +512 -0
- package/src/widgets/__tests__/iframe_test.jsx +47 -0
- package/src/widgets/__tests__/image_test.jsx +65 -0
- package/src/widgets/__tests__/input-number_test.jsx +360 -0
- package/src/widgets/__tests__/interaction_test.jsx +33 -0
- package/src/widgets/__tests__/interactive-graph.cypress.jsx +697 -0
- package/src/widgets/__tests__/interactive-graph_test.jsx +105 -0
- package/src/widgets/__tests__/matcher_test.jsx +157 -0
- package/src/widgets/__tests__/matrix_test.jsx +84 -0
- package/src/widgets/__tests__/number-line_test.jsx +81 -0
- package/src/widgets/__tests__/numeric-input_test.jsx +574 -0
- package/src/widgets/__tests__/orderer_test.jsx +75 -0
- package/src/widgets/__tests__/passage-ref_test.jsx +166 -0
- package/src/widgets/__tests__/passage_test.jsx +133 -0
- package/src/widgets/__tests__/radio/base-radio_test.jsx +82 -0
- package/src/widgets/__tests__/radio_test.jsx +651 -0
- package/src/widgets/__tests__/renderQuestion.jsx +65 -0
- package/src/widgets/__tests__/unit_test.jsx +144 -0
- package/src/widgets/__tests__/video_test.jsx +55 -0
- package/src/widgets/categorizer.jsx +338 -0
- package/src/widgets/cs-program.jsx +228 -0
- package/src/widgets/definition.jsx +141 -0
- package/src/widgets/dropdown.jsx +143 -0
- package/src/widgets/explanation.jsx +315 -0
- package/src/widgets/expression.jsx +654 -0
- package/src/widgets/graded-group-answer-bar.jsx +152 -0
- package/src/widgets/graded-group-set.jsx +285 -0
- package/src/widgets/graded-group.jsx +448 -0
- package/src/widgets/grapher/util.jsx +819 -0
- package/src/widgets/grapher.jsx +658 -0
- package/src/widgets/group.jsx +213 -0
- package/src/widgets/iframe.jsx +199 -0
- package/src/widgets/image.jsx +309 -0
- package/src/widgets/input-number.jsx +410 -0
- package/src/widgets/interaction/arrow-picker.jsx +39 -0
- package/src/widgets/interaction/color-picker.jsx +69 -0
- package/src/widgets/interaction/dash-picker.jsx +46 -0
- package/src/widgets/interaction/element-container.jsx +97 -0
- package/src/widgets/interaction.jsx +742 -0
- package/src/widgets/interactive-graph.jsx +2821 -0
- package/src/widgets/label-image/__stories__/answer-choices.stories.jsx +93 -0
- package/src/widgets/label-image/__stories__/marker.stories.jsx +172 -0
- package/src/widgets/label-image/answer-choices.jsx +311 -0
- package/src/widgets/label-image/marker.jsx +365 -0
- package/src/widgets/label-image/types.js +33 -0
- package/src/widgets/label-image.jsx +841 -0
- package/src/widgets/label-image_test.js +840 -0
- package/src/widgets/lights-puzzle.jsx +356 -0
- package/src/widgets/matcher.jsx +271 -0
- package/src/widgets/matrix.jsx +615 -0
- package/src/widgets/measurer.jsx +215 -0
- package/src/widgets/molecule/__tests__/layout_test.jsx +159 -0
- package/src/widgets/molecule/__tests__/parser_test.jsx +142 -0
- package/src/widgets/molecule/molecule-drawing.jsx +175 -0
- package/src/widgets/molecule/molecule-layout.jsx +367 -0
- package/src/widgets/molecule/smiles-parser.jsx +300 -0
- package/src/widgets/molecule.jsx +172 -0
- package/src/widgets/number-line.jsx +858 -0
- package/src/widgets/numeric-input.jsx +622 -0
- package/src/widgets/orderer.jsx +753 -0
- package/src/widgets/passage/__tests__/passage-markdown_test.jsx +382 -0
- package/src/widgets/passage/get-line-height-for-node.js +10 -0
- package/src/widgets/passage/passage-markdown.jsx +416 -0
- package/src/widgets/passage-ref-target.jsx +70 -0
- package/src/widgets/passage-ref.jsx +198 -0
- package/src/widgets/passage.jsx +601 -0
- package/src/widgets/plotter.jsx +1198 -0
- package/src/widgets/radio/__stories__/base-radio.stories.jsx +40 -0
- package/src/widgets/radio/__stories__/choice-icon.stories.jsx +157 -0
- package/src/widgets/radio/__stories__/choice-none-above.stories.jsx +26 -0
- package/src/widgets/radio/__stories__/cross-out-button.stories.jsx +64 -0
- package/src/widgets/radio/__stories__/cross-out-menu.stories.jsx +59 -0
- package/src/widgets/radio/__stories__/cross-out-tooltip.stories.jsx +59 -0
- package/src/widgets/radio/__stories__/focus-ring.stories.jsx +38 -0
- package/src/widgets/radio/__stories__/option-status.stories.jsx +118 -0
- package/src/widgets/radio/__stories__/toggleable-radio-button.stories.jsx +28 -0
- package/src/widgets/radio/base-radio.jsx +642 -0
- package/src/widgets/radio/choice-icon.jsx +468 -0
- package/src/widgets/radio/choice-none-above.jsx +54 -0
- package/src/widgets/radio/choice.jsx +692 -0
- package/src/widgets/radio/choice.jsx.fixture.js +64 -0
- package/src/widgets/radio/cross-out-button.jsx +136 -0
- package/src/widgets/radio/cross-out-menu-wrapper.jsx +492 -0
- package/src/widgets/radio/cross-out-tooltip.jsx +129 -0
- package/src/widgets/radio/focus-ring.jsx +51 -0
- package/src/widgets/radio/option-status.jsx +89 -0
- package/src/widgets/radio/toggleable-radio-button.jsx +133 -0
- package/src/widgets/radio/util.js +32 -0
- package/src/widgets/radio/util_test.js +19 -0
- package/src/widgets/radio/widget.jsx +470 -0
- package/src/widgets/radio.jsx +156 -0
- package/src/widgets/reaction-diagram.jsx +136 -0
- package/src/widgets/sequence.jsx +140 -0
- package/src/widgets/simulator.jsx +808 -0
- package/src/widgets/sorter.jsx +146 -0
- package/src/widgets/table.jsx +395 -0
- package/src/widgets/transformer.jsx +2870 -0
- package/src/widgets/unit.jsx +371 -0
- package/src/widgets/video-transcript-link.jsx +84 -0
- package/src/widgets/video-transcript-link_test.js +211 -0
- package/src/widgets/video.jsx +131 -0
- package/src/widgets.js +431 -0
- package/src/zoom.js +539 -0
- 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
|
+
});
|