@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.
- package/.babelrc.js +1 -1
- package/CHANGELOG.md +29 -0
- package/dist/es/index.css +4361 -0
- package/dist/es/index.js +7 -1
- package/dist/es/index.js.map +1 -1
- package/dist/index.css +4361 -0
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/dist/strings.js +148 -46
- package/package.json +50 -4
- package/src/README.md +35 -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 +164 -0
- package/src/components/graph.jsx +415 -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 +146 -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 +454 -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 +80 -0
- package/src/components/range-input.jsx +61 -0
- package/src/components/simple-keypad-input.jsx +91 -0
- package/src/components/sortable.jsx +873 -0
- package/src/components/stub-tag-editor.jsx +54 -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 +603 -0
- package/src/interactive2/movable-polygon-options.js +351 -0
- package/src/interactive2/movable-polygon.js +368 -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 +129 -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 +543 -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 +433 -0
- package/src/perseus-types.js +1597 -0
- package/src/pure-markdown.js +347 -0
- package/src/question-paragraph.jsx +30 -0
- package/src/renderability.jsx +108 -0
- package/src/renderer.jsx +1932 -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 +1007 -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 +1586 -0
- package/src/util/interactive.js +4078 -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__/unit.stories.jsx +45 -0
- package/src/widgets/__stories__/video-transcript-link.stories.jsx +24 -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 +66 -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 +227 -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 +652 -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 +806 -0
- package/src/widgets/grapher.jsx +663 -0
- package/src/widgets/group.jsx +213 -0
- package/src/widgets/iframe.jsx +198 -0
- package/src/widgets/image.jsx +310 -0
- package/src/widgets/input-number.jsx +411 -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 +2820 -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 +616 -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 +137 -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 +394 -0
- package/src/widgets/transformer.jsx +2876 -0
- package/src/widgets/unit.jsx +372 -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 +126 -0
- package/src/widgets.js +431 -0
- package/src/zoom.js +539 -0
- package/README.md +0 -3
- package/src/__tests__/index.test.js +0 -14
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
/* eslint-disable react/sort-comp */
|
|
2
|
+
// @flow
|
|
3
|
+
import classNames from "classnames";
|
|
4
|
+
import $ from "jquery";
|
|
5
|
+
import MathQuill from "mathquill";
|
|
6
|
+
import PropTypes from "prop-types";
|
|
7
|
+
import * as React from "react";
|
|
8
|
+
import ReactDOM from "react-dom";
|
|
9
|
+
import _ from "underscore";
|
|
10
|
+
|
|
11
|
+
import TexButtons from "./tex-buttons.jsx";
|
|
12
|
+
|
|
13
|
+
const PT = PropTypes;
|
|
14
|
+
|
|
15
|
+
// A WYSIWYG math input that calls `onChange(LaTeX-string)`
|
|
16
|
+
class MathInput extends React.Component<$FlowFixMe, $FlowFixMe> {
|
|
17
|
+
mouseDown: boolean;
|
|
18
|
+
|
|
19
|
+
static propTypes = {
|
|
20
|
+
value: PT.string,
|
|
21
|
+
onChange: PT.func.isRequired,
|
|
22
|
+
convertDotToTimes: PT.bool,
|
|
23
|
+
buttonsVisible: PT.oneOf(["always", "never", "focused"]),
|
|
24
|
+
buttonSets: TexButtons.buttonSetsType.isRequired,
|
|
25
|
+
labelText: PropTypes.string,
|
|
26
|
+
onFocus: PT.func,
|
|
27
|
+
onBlur: PT.func,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
static defaultProps: $FlowFixMe = {
|
|
31
|
+
value: "",
|
|
32
|
+
convertDotToTimes: false,
|
|
33
|
+
buttonsVisible: "focused",
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
state: $FlowFixMe = {focused: false};
|
|
37
|
+
|
|
38
|
+
render(): React.Node {
|
|
39
|
+
let className = classNames({
|
|
40
|
+
"perseus-math-input": true,
|
|
41
|
+
|
|
42
|
+
// mathquill usually adds these itself but react removes them when
|
|
43
|
+
// updating the component.
|
|
44
|
+
"mq-editable-field": true,
|
|
45
|
+
"mq-math-mode": true,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
if (this.props.className) {
|
|
49
|
+
className = className + " " + this.props.className;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
let buttons = null;
|
|
53
|
+
if (this._shouldShowButtons()) {
|
|
54
|
+
buttons = (
|
|
55
|
+
<TexButtons
|
|
56
|
+
sets={this.props.buttonSets}
|
|
57
|
+
className="math-input-buttons absolute"
|
|
58
|
+
convertDotToTimes={this.props.convertDotToTimes}
|
|
59
|
+
onInsert={this.insert}
|
|
60
|
+
/>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<div style={{display: "inline-block"}}>
|
|
66
|
+
<div style={{display: "inline-block"}}>
|
|
67
|
+
<span
|
|
68
|
+
className={className}
|
|
69
|
+
// eslint-disable-next-line react/no-string-refs
|
|
70
|
+
ref="mathinput"
|
|
71
|
+
aria-label={this.props.labelText}
|
|
72
|
+
onFocus={this.handleFocus}
|
|
73
|
+
onBlur={this.handleBlur}
|
|
74
|
+
/>
|
|
75
|
+
</div>
|
|
76
|
+
<div style={{position: "relative"}} onBlur={this.handleBlur}>
|
|
77
|
+
{buttons}
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// handlers:
|
|
84
|
+
// keep track of two related bits of state:
|
|
85
|
+
// * this.state.focused - whether the buttons are currently shown
|
|
86
|
+
// * this.mouseDown - whether a mouse click is active that started in the
|
|
87
|
+
// buttons div
|
|
88
|
+
|
|
89
|
+
handleFocus: () => void = () => {
|
|
90
|
+
this.setState({focused: true});
|
|
91
|
+
// TODO(joel) fix properly - we should probably allow onFocus handlers
|
|
92
|
+
// to this property, but we need to work correctly with them.
|
|
93
|
+
// if (this.props.onFocus) {
|
|
94
|
+
// this.props.onFocus();
|
|
95
|
+
// }
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
handleMouseDown: (MouseEvent) => void = (event) => {
|
|
99
|
+
// $FlowFixMe[incompatible-use]
|
|
100
|
+
// $FlowFixMe[incompatible-call]
|
|
101
|
+
const focused = ReactDOM.findDOMNode(this).contains(event.target);
|
|
102
|
+
this.mouseDown = focused;
|
|
103
|
+
if (!focused) {
|
|
104
|
+
this.setState({focused: false});
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
handleMouseUp: (MouseEvent) => void = () => {
|
|
109
|
+
// this mouse click started in the buttons div so we should focus the
|
|
110
|
+
// input
|
|
111
|
+
if (this.mouseDown) {
|
|
112
|
+
this.focus();
|
|
113
|
+
}
|
|
114
|
+
this.mouseDown = false;
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
handleBlur: (SyntheticFocusEvent<>) => void = (e) => {
|
|
118
|
+
// TODO(michaelpolyak): Consider trapping focus within the button group.
|
|
119
|
+
// Focusing back on the input when TAB out of the last button in the
|
|
120
|
+
// group. This will probably require ESCAPE key handling to enable to
|
|
121
|
+
// close (blur) the button group in order to focus on next page element.
|
|
122
|
+
if (
|
|
123
|
+
!this.mouseDown &&
|
|
124
|
+
// $FlowFixMe[incompatible-use]
|
|
125
|
+
// $FlowFixMe[incompatible-call]
|
|
126
|
+
!ReactDOM.findDOMNode(this).contains(e.relatedTarget)
|
|
127
|
+
) {
|
|
128
|
+
this.setState({focused: false});
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
_shouldShowButtons: () => boolean = () => {
|
|
133
|
+
if (this.props.buttonsVisible === "always") {
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
if (this.props.buttonsVisible === "never") {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
return this.state.focused;
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
insert: ($FlowFixMe) => void = (value) => {
|
|
143
|
+
const input = this.mathField();
|
|
144
|
+
if (_(value).isFunction()) {
|
|
145
|
+
value(input);
|
|
146
|
+
} else if (value[0] === "\\") {
|
|
147
|
+
input.cmd(value).focus();
|
|
148
|
+
} else {
|
|
149
|
+
input.write(value).focus();
|
|
150
|
+
}
|
|
151
|
+
input.focus();
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
mathField: ($FlowFixMe) => $FlowFixMe = (options) => {
|
|
155
|
+
// The MathQuill API is now "versioned" through its own "InterVer"
|
|
156
|
+
// system.
|
|
157
|
+
// See: https://github.com/mathquill/mathquill/pull/459
|
|
158
|
+
const MQ = MathQuill.getInterface(2);
|
|
159
|
+
|
|
160
|
+
// MathQuill.MathField takes a DOM node, MathQuill-ifies it if it's
|
|
161
|
+
// seeing that node for the first time, then returns the associated
|
|
162
|
+
// MathQuill object for that node. It is stable - will always return
|
|
163
|
+
// the same object when called on the same DOM node.
|
|
164
|
+
// eslint-disable-next-line react/no-string-refs
|
|
165
|
+
return MQ.MathField(ReactDOM.findDOMNode(this.refs.mathinput), options);
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
componentWillUnmount() {
|
|
169
|
+
window.removeEventListener("mousedown", this.handleMouseDown);
|
|
170
|
+
window.removeEventListener("mouseup", this.handleMouseUp);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
componentDidMount() {
|
|
174
|
+
window.addEventListener("mousedown", this.handleMouseDown);
|
|
175
|
+
window.addEventListener("mouseup", this.handleMouseUp);
|
|
176
|
+
|
|
177
|
+
let initialized = false;
|
|
178
|
+
|
|
179
|
+
// Initialize MathQuill.MathField instance
|
|
180
|
+
this.mathField({
|
|
181
|
+
// LaTeX commands that, when typed, are immediately replaced by the
|
|
182
|
+
// appropriate symbol. This does not include ln, log, or any of the
|
|
183
|
+
// trig functions; those are always interpreted as commands.
|
|
184
|
+
autoCommands: "pi theta phi sqrt nthroot",
|
|
185
|
+
|
|
186
|
+
// Pop the cursor out of super/subscripts on arithmetic operators
|
|
187
|
+
// or (in)equalities.
|
|
188
|
+
charsThatBreakOutOfSupSub: "+-*/=<>β β€β₯",
|
|
189
|
+
|
|
190
|
+
// Prevent excessive super/subscripts or fractions from being
|
|
191
|
+
// created without operands, e.g. when somebody holds down a key
|
|
192
|
+
supSubsRequireOperand: true,
|
|
193
|
+
|
|
194
|
+
// The name of this option is somewhat misleading, as tabbing in
|
|
195
|
+
// MathQuill breaks you out of a nested context (fraction/script)
|
|
196
|
+
// if you're in one, but moves focus to the next input if you're
|
|
197
|
+
// not. Spaces (with this option enabled) are just ignored in the
|
|
198
|
+
// latter case.
|
|
199
|
+
//
|
|
200
|
+
// TODO(alex): In order to allow inputting mixed numbers, we will
|
|
201
|
+
// have to accept spaces in certain cases. The desired behavior is
|
|
202
|
+
// still to escape nested contexts if currently in one, but to
|
|
203
|
+
// insert a space if not (we don't expect mixed numbers in nested
|
|
204
|
+
// contexts). We should also limit to one consecutive space.
|
|
205
|
+
spaceBehavesLikeTab: true,
|
|
206
|
+
|
|
207
|
+
handlers: {
|
|
208
|
+
edited: (mathField) => {
|
|
209
|
+
// This handler is guaranteed to be called on change, but
|
|
210
|
+
// unlike React it sometimes generates false positives.
|
|
211
|
+
// One of these is on initialization (with an empty string
|
|
212
|
+
// value), so we have to guard against that below.
|
|
213
|
+
let value = mathField.latex();
|
|
214
|
+
|
|
215
|
+
// Provide a MathQuill-compatible way to generate the
|
|
216
|
+
// not-equals sign without pasting unicode or typing TeX
|
|
217
|
+
value = value.replace(/<>/g, "\\ne");
|
|
218
|
+
|
|
219
|
+
// Use the specified symbol to represent multiplication
|
|
220
|
+
// TODO(alex): Add an option to disallow variables, in
|
|
221
|
+
// which case 'x' should get converted to '\\times'
|
|
222
|
+
if (this.props.convertDotToTimes) {
|
|
223
|
+
value = value.replace(/\\cdot/g, "\\times");
|
|
224
|
+
|
|
225
|
+
// Preserve cursor position in the common case:
|
|
226
|
+
// typing '*' to insert a multiplication sign.
|
|
227
|
+
// We do this by modifying internal MathQuill state
|
|
228
|
+
// directly, instead of waiting for `.latex()` to be
|
|
229
|
+
// called in `componentDidUpdate()`.
|
|
230
|
+
const left = mathField.__controller.cursor[MathQuill.L];
|
|
231
|
+
if (left && left.ctrlSeq === "\\cdot ") {
|
|
232
|
+
mathField.__controller.backspace();
|
|
233
|
+
mathField.cmd("\\times");
|
|
234
|
+
}
|
|
235
|
+
} else {
|
|
236
|
+
value = value.replace(/\\times/g, "\\cdot");
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (initialized && this.props.value !== value) {
|
|
240
|
+
this.props.onChange(value);
|
|
241
|
+
}
|
|
242
|
+
},
|
|
243
|
+
enter: () => {
|
|
244
|
+
// NOTE(kevinb): This isn't how answers to exercises are
|
|
245
|
+
// submitted. The actual mechanism for this can be found
|
|
246
|
+
// in exercise-problem-template.jsx, see `handleSubmit`.
|
|
247
|
+
|
|
248
|
+
// This handler is called when the user presses the enter
|
|
249
|
+
// key. Since this isn't an actual <input> element, we have
|
|
250
|
+
// to manually trigger the usually automatic form submit.
|
|
251
|
+
// eslint-disable-next-line react/no-string-refs
|
|
252
|
+
$(ReactDOM.findDOMNode(this.refs.mathinput)).submit();
|
|
253
|
+
},
|
|
254
|
+
upOutOf: (mathField) => {
|
|
255
|
+
// This handler is called when the user presses the up
|
|
256
|
+
// arrow key, but there is nowhere in the expression to go
|
|
257
|
+
// up to (no numerator or exponent). For ease of use,
|
|
258
|
+
// interpret this as an attempt to create an exponent.
|
|
259
|
+
mathField.typedText("^");
|
|
260
|
+
},
|
|
261
|
+
},
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
// Ideally, we would be able to pass an initial value directly into
|
|
265
|
+
// the constructor above
|
|
266
|
+
this.mathField().latex(this.props.value);
|
|
267
|
+
|
|
268
|
+
initialized = true;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
focus: () => void = () => {
|
|
272
|
+
this.mathField().focus();
|
|
273
|
+
this.setState({focused: true});
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
blur: () => void = () => {
|
|
277
|
+
this.mathField().blur();
|
|
278
|
+
this.setState({focused: false});
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
export default MathInput;
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/* eslint-disable react/sort-comp */
|
|
2
|
+
// @flow
|
|
3
|
+
import $ from "jquery";
|
|
4
|
+
import PropTypes from "prop-types";
|
|
5
|
+
import * as React from "react";
|
|
6
|
+
import ReactDOM from "react-dom";
|
|
7
|
+
import _ from "underscore";
|
|
8
|
+
|
|
9
|
+
import {getDependencies} from "../dependencies.js";
|
|
10
|
+
import {ClassNames as ApiClassNames} from "../perseus-api.jsx";
|
|
11
|
+
import TexWrangler from "../tex-wrangler.js";
|
|
12
|
+
|
|
13
|
+
const ModifyTex = TexWrangler.modifyTex;
|
|
14
|
+
|
|
15
|
+
class MathOutput extends React.Component<$FlowFixMe, $FlowFixMe> {
|
|
16
|
+
static propTypes = {
|
|
17
|
+
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
|
18
|
+
className: PropTypes.string,
|
|
19
|
+
labelText: PropTypes.string,
|
|
20
|
+
onFocus: PropTypes.func,
|
|
21
|
+
onBlur: PropTypes.func,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
static defaultProps: $FlowFixMe = {
|
|
25
|
+
value: "",
|
|
26
|
+
onFocus: function () {},
|
|
27
|
+
onBlur: function () {},
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
state: $FlowFixMe = {
|
|
31
|
+
focused: false,
|
|
32
|
+
selectorNamespace: _.uniqueId("math-output"),
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
_getInputClassName: () => string = () => {
|
|
36
|
+
let className =
|
|
37
|
+
"math-output " +
|
|
38
|
+
ApiClassNames.INPUT +
|
|
39
|
+
" " +
|
|
40
|
+
ApiClassNames.INTERACTIVE;
|
|
41
|
+
if (this.state.focused) {
|
|
42
|
+
className += " " + ApiClassNames.FOCUSED;
|
|
43
|
+
}
|
|
44
|
+
if (this.props.className) {
|
|
45
|
+
className += " " + this.props.className;
|
|
46
|
+
}
|
|
47
|
+
return className;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
_getDisplayValue: (string | number) => string = (value) => {
|
|
51
|
+
// Cast from (potentially a) number to string
|
|
52
|
+
let displayText;
|
|
53
|
+
if (value != null) {
|
|
54
|
+
displayText = "" + value;
|
|
55
|
+
} else {
|
|
56
|
+
displayText = "";
|
|
57
|
+
}
|
|
58
|
+
return ModifyTex(displayText);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
render(): React.Node {
|
|
62
|
+
const divStyle = {
|
|
63
|
+
textAlign: "center",
|
|
64
|
+
};
|
|
65
|
+
const {TeX} = getDependencies();
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<span
|
|
69
|
+
// eslint-disable-next-line react/no-string-refs
|
|
70
|
+
ref="input"
|
|
71
|
+
className={this._getInputClassName()}
|
|
72
|
+
aria-label={this.props.labelText}
|
|
73
|
+
onMouseDown={this.focus}
|
|
74
|
+
onTouchStart={this.focus}
|
|
75
|
+
>
|
|
76
|
+
<div style={divStyle}>
|
|
77
|
+
<TeX>{this._getDisplayValue(this.props.value)}</TeX>
|
|
78
|
+
</div>
|
|
79
|
+
</span>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
getValue: () => string | number = () => {
|
|
84
|
+
return this.props.value;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
focus: () => void = () => {
|
|
88
|
+
if (!this.state.focused) {
|
|
89
|
+
this.props.onFocus();
|
|
90
|
+
this._bindBlurHandler();
|
|
91
|
+
this.setState({
|
|
92
|
+
focused: true,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
blur: () => void = () => {
|
|
98
|
+
if (this.state.focused) {
|
|
99
|
+
this.props.onBlur();
|
|
100
|
+
this._unbindBlurHandler();
|
|
101
|
+
this.setState({
|
|
102
|
+
focused: false,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
_bindBlurHandler: () => void = () => {
|
|
108
|
+
$(document).bind("vclick." + this.state.selectorNamespace, (e) => {
|
|
109
|
+
// Detect whether the target has our React DOM node as a parent
|
|
110
|
+
const $closestWidget = $(e.target).closest(
|
|
111
|
+
ReactDOM.findDOMNode(this),
|
|
112
|
+
);
|
|
113
|
+
if (!$closestWidget.length) {
|
|
114
|
+
this.blur();
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
_unbindBlurHandler: () => void = () => {
|
|
120
|
+
$(document).unbind("." + this.state.selectorNamespace);
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
componentWillUnmount() {
|
|
124
|
+
this._unbindBlurHandler();
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export default MathOutput;
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
|
|
3
|
+
import {css, StyleSheet} from "aphrodite";
|
|
4
|
+
import * as React from "react";
|
|
5
|
+
import * as ReactDOM from "react-dom";
|
|
6
|
+
|
|
7
|
+
type Props = {|
|
|
8
|
+
// the initial values of the buttons selected, defaults to null (no
|
|
9
|
+
// selection).
|
|
10
|
+
values: ?$ReadOnlyArray<any>,
|
|
11
|
+
buttons: $ReadOnlyArray<{|
|
|
12
|
+
// the value returned when the button is selected
|
|
13
|
+
value: any,
|
|
14
|
+
// the content shown within the button, typically a string that gets
|
|
15
|
+
// rendered as the button's display text
|
|
16
|
+
content: React.Node,
|
|
17
|
+
// the title-text shown on hover
|
|
18
|
+
title?: string,
|
|
19
|
+
|}>,
|
|
20
|
+
// a function that is provided with the updated set of selected value
|
|
21
|
+
// (which it then is responsible for updating)
|
|
22
|
+
onChange: (values: any) => mixed,
|
|
23
|
+
// if false, at least one button must be selected at all times.
|
|
24
|
+
// defaults to true
|
|
25
|
+
allowEmpty?: boolean,
|
|
26
|
+
|};
|
|
27
|
+
|
|
28
|
+
type DefaultProps = {|
|
|
29
|
+
allowEmpty: Props["allowEmpty"],
|
|
30
|
+
values: Props["values"],
|
|
31
|
+
|};
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* MultiButtonGroup is an aesthetically pleasing group of buttons,
|
|
35
|
+
* which allows multiple buttons to be selected at the same time.
|
|
36
|
+
*
|
|
37
|
+
* NOTE: This component is almost identical to ./button-group.jsx except that
|
|
38
|
+
* this component allows multiple selection!
|
|
39
|
+
*/
|
|
40
|
+
class MultiButtonGroup extends React.Component<Props> {
|
|
41
|
+
static defaultProps: DefaultProps = {
|
|
42
|
+
values: [],
|
|
43
|
+
allowEmpty: true,
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
focus(): boolean {
|
|
47
|
+
// $FlowFixMe[prop-missing]
|
|
48
|
+
ReactDOM.findDOMNode(this)?.focus();
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
toggleSelect: (any) => void = (newValue: any) => {
|
|
53
|
+
const values = (this.props.values || []).slice(0);
|
|
54
|
+
const allowEmpty = this.props.allowEmpty;
|
|
55
|
+
|
|
56
|
+
if (
|
|
57
|
+
values.indexOf(newValue) >= 0 &&
|
|
58
|
+
(values.length > 1 || allowEmpty)
|
|
59
|
+
) {
|
|
60
|
+
// If the value is already selected, unselect it
|
|
61
|
+
values.splice(values.indexOf(newValue), 1);
|
|
62
|
+
} else {
|
|
63
|
+
// Otherwise merge with other values and return
|
|
64
|
+
if (values.indexOf(newValue) < 0) {
|
|
65
|
+
values.push(newValue);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
this.props.onChange(values);
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
render(): React.Element<"div"> {
|
|
73
|
+
const values = this.props.values || [];
|
|
74
|
+
const buttons = this.props.buttons.map((button, i) => {
|
|
75
|
+
const selected = values.indexOf(button.value) >= 0;
|
|
76
|
+
return (
|
|
77
|
+
<button
|
|
78
|
+
title={button.title}
|
|
79
|
+
type="button"
|
|
80
|
+
id={"" + i}
|
|
81
|
+
key={"" + i}
|
|
82
|
+
ref={"button" + i}
|
|
83
|
+
className={css(
|
|
84
|
+
styles.buttonStyle,
|
|
85
|
+
selected && styles.selectedStyle,
|
|
86
|
+
)}
|
|
87
|
+
onClick={() => this.toggleSelect(button.value)}
|
|
88
|
+
>
|
|
89
|
+
{button.content || "" + button.value}
|
|
90
|
+
</button>
|
|
91
|
+
);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
const outerStyle = {
|
|
95
|
+
display: "inline-block",
|
|
96
|
+
};
|
|
97
|
+
return <div style={outerStyle}>{buttons}</div>;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const styles = StyleSheet.create({
|
|
102
|
+
buttonStyle: {
|
|
103
|
+
backgroundColor: "white",
|
|
104
|
+
border: "1px solid #ccc",
|
|
105
|
+
borderLeft: "0",
|
|
106
|
+
cursor: "pointer",
|
|
107
|
+
margin: "0",
|
|
108
|
+
padding: "5px 10px",
|
|
109
|
+
position: "relative", // for hover
|
|
110
|
+
|
|
111
|
+
":first-child": {
|
|
112
|
+
borderLeft: "1px solid #ccc",
|
|
113
|
+
borderTopLeftRadius: "3px",
|
|
114
|
+
borderBottomLeftRadius: "3px",
|
|
115
|
+
},
|
|
116
|
+
|
|
117
|
+
":last-child": {
|
|
118
|
+
borderRight: "1px solid #ccc",
|
|
119
|
+
borderTopRightRadius: "3px",
|
|
120
|
+
borderBottomRightRadius: "3px",
|
|
121
|
+
},
|
|
122
|
+
|
|
123
|
+
":hover": {
|
|
124
|
+
backgroundColor: "#ccc",
|
|
125
|
+
},
|
|
126
|
+
|
|
127
|
+
":focus": {
|
|
128
|
+
zIndex: 2,
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
|
|
132
|
+
selectedStyle: {
|
|
133
|
+
backgroundColor: "#ddd",
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
export default MultiButtonGroup;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# `NewTooltip`: KA tooltip component
|
|
2
|
+
|
|
3
|
+
Across the Khan Academy webapp, we use a shared "tooltip" UI element, to visually annotate page elements with additional information. This React component, `NewTooltip`, is designed to satisfy all of our tooltip needs! π
|
|
4
|
+
|
|
5
|
+
**Warning: This component is WIP!** We're in the process of rolling out this tooltip to eventually replace the old `Tooltip` component. `NewTooltip` is currently is only used at a select few call sites. [Scroll down for details.](#warning-this-component-is-wip)
|
|
6
|
+
|
|
7
|
+
## Usage
|
|
8
|
+
|
|
9
|
+
To point a tooltip at an element, wrap it in a `NewTooltip` component:
|
|
10
|
+
|
|
11
|
+
```jsx
|
|
12
|
+
<NewTooltip content={i18n._("You should click on this!")}>
|
|
13
|
+
<button><$_>Assign to students</$_></button>
|
|
14
|
+
</NewTooltip>
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
This will produce a tooltip that says "You should click on this!" when you hover over the wrapped button.
|
|
18
|
+
|
|
19
|
+
You can also provide more advanced content:
|
|
20
|
+
|
|
21
|
+
```jsx
|
|
22
|
+
const content = <div className={css(styles.tooltipContent)}>
|
|
23
|
+
<p><$_>You should click on this!</$_></p>
|
|
24
|
+
<p><$_>It will help you!</$_></p>
|
|
25
|
+
</div>;
|
|
26
|
+
|
|
27
|
+
<NewTooltip content={content}>
|
|
28
|
+
<button>Assign to students</button>
|
|
29
|
+
</NewTooltip>
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
There are also many styling, positioning, and behavioral options for the tooltip! See `types.js` for the list of options, and the [React Sandbox][fixtures] to see them in action.
|
|
33
|
+
|
|
34
|
+
## Accessibility
|
|
35
|
+
|
|
36
|
+
Unlike some other tooltip components on the web, this tooltip is a _visual_ element rather than a semantic element. Its use cases are relatively diverse:
|
|
37
|
+
|
|
38
|
+
* Explaining a UI control
|
|
39
|
+
* Illustrating a relationship between two permanent UI elements
|
|
40
|
+
* Showing a transient UI element without disrupting the flow of the document
|
|
41
|
+
* Prompting the user to take a secondary or blocking action (tooltip) before taking the main action (target)
|
|
42
|
+
|
|
43
|
+
The key commonality is that the tooltip and its target are somehow _related_β¦ but that's about it.
|
|
44
|
+
|
|
45
|
+
**So, be sure to add your own ARIA attributes to tooltip targets and content!** Only you know the semantic relationship between your tooltip and target, so it's up to you to express it.
|
|
46
|
+
|
|
47
|
+
## Package structure
|
|
48
|
+
|
|
49
|
+
The two most important files in this package are:
|
|
50
|
+
|
|
51
|
+
* `new-tooltip.jsx`: The entry point to our tooltip code. Manages high-level visibility state. Renders the tooltip's target element inline, and renders a `TooltipPortal` to get the tooltip content outside of any `overflow: hidden` containers.
|
|
52
|
+
* `types.js`: Defines the props for `NewTooltip`.
|
|
53
|
+
|
|
54
|
+
We also have a number of supporting files, as well:
|
|
55
|
+
|
|
56
|
+
* React components
|
|
57
|
+
* `tooltip-portal.jsx`: Simulates a [React 16 portal][react-portal]. Creates a node near the root of the document, and renders a `TooltipPositioner` there, instead of inline with the target element.
|
|
58
|
+
* `tooltip-positioner.jsx`: Manages the position of the tooltip UI. Computes the position of the target element, and watches for changes. Renders a `TooltipBubble` and `TooltipArrow`, positioned to point at the target element.
|
|
59
|
+
* `tooltip-bubble.jsx`: The main tooltip "bubble" element. Renders the tooltip content.
|
|
60
|
+
* `tooltip-arrow.jsx`: The little triangle that points from the tooltip bubble to the target element.
|
|
61
|
+
* Library files:
|
|
62
|
+
* `util.js`: Miscellaneous utility functions.
|
|
63
|
+
|
|
64
|
+
Note that these components are relatively tightly coupledβfor example, rather than specifying their individual data requirements, each tooltip component just forwards the entire set of `tooltipProps` to its child tooltip components. This enables faster iteration, but means that the child components aren't especially reusable.
|
|
65
|
+
|
|
66
|
+
## Warning: This component is WIP!
|
|
67
|
+
|
|
68
|
+
This is `NewTooltip`, a fork of the old `Tooltip`, in which we've built a brand new layout algorithm to address some key UX issues. Someday, `NewTooltip` will replace `Tooltip`, hooray! [See ADR #33 for details.][adr-33]
|
|
69
|
+
|
|
70
|
+
`NewTooltip` is probably _feature-ready_ for most use cases on Khan Academy, but **general usage is not yet advised**. We're still developing our plan for the "Wonder Blocks" shared component rollout, and we want to keep call sites well-constrained until we know more. But, if you _need_ the features of `NewTooltip` today, maybe we can figure something out! Hit up the [#reusable-components][reusable-components] Slack channel.
|
|
71
|
+
|
|
72
|
+
But, if you need a feature that hasn't been ported yet, maybe you'll want to continue to use `Tooltip`. (Or port the feature into `NewTooltip`? π)
|
|
73
|
+
|
|
74
|
+
Here's the overall behavioral comparison right now. Especially valuable features are marked with a star (βοΈ).
|
|
75
|
+
|
|
76
|
+
| Feature | `NewTooltip` | `Tooltip` |
|
|
77
|
+
|----------------------------------------------|--------------|-----------|
|
|
78
|
+
| βοΈ **Basic tooltip functionality** | β
| β
|
|
|
79
|
+
| βοΈ **Escape `overflow: hidden` containers** | β
| β
|
|
|
80
|
+
| βοΈ **Attach to any side of the target** | β
| β
|
|
|
81
|
+
| βοΈ **Configurable colors** | β
| β
|
|
|
82
|
+
| βοΈ **Dismiss on click** | β
| β
|
|
|
83
|
+
| βοΈ **Only show once per user lifetime** | β
| β
|
|
|
84
|
+
| Tweak position via the `offset` prop | β
| β
|
|
|
85
|
+
| Arrow center-aligned with bubble | β
| β
|
|
|
86
|
+
| βοΈ **Auto-shift position to avoid wrapping** | β
| β |
|
|
87
|
+
| βοΈ **Auto-reposition during scrolling** | β
| β |
|
|
88
|
+
| βοΈ **Update content without remounting** | β
| β |
|
|
89
|
+
| βοΈ **Media queries to show/hide tooltip** | β
| β |
|
|
90
|
+
| Can specify offset from the _arrow_ | β
| β |
|
|
91
|
+
| Can specify margin around tooltip | β
| β |
|
|
92
|
+
| βοΈ **Can be used inside a modal** | β
| β
|
|
|
93
|
+
| Can target `position: fixed` elements | β | β
|
|
|
94
|
+
| Auto-choose top or bottom anchoring | β | β
|
|
|
95
|
+
| Try to left/right align tooltip with target | β | β
|
|
|
96
|
+
| Manual positioning (left/top) | β | β
|
|
|
97
|
+
| Dismiss on scroll/interaction/timer | β | β
|
|
|
98
|
+
| Show tooltip when target gains focus | β | β
|
|
|
99
|
+
|
|
100
|
+
> NOTE(mdr): It's possible that some of the old `Tooltip` features are also "especially valuable", and I just don't know about it yet! So far we've only used `NewTooltip` at GTP call sites, and they haven't needed many of these features, so it's not clear to me how broadly they're actually used. (We'll probably have to support most of the missing options eventually, but maybe not all, in case some of their use cases were addressed by the general positioning improvements?)
|
|
101
|
+
|
|
102
|
+
Eventually, we'll roll out `NewTooltip` to all KA call sites, and upgrade it to support everything that the old `Tooltip` call sites need. So, no worries if you end up adding new `Tooltip` call sites; we'll help upgrade them soon! [For more details, see ADR #33.][adr-33]
|
|
103
|
+
|
|
104
|
+
[adr-33]: https://docs.google.com/document/d/1B4pMGWp344e96RmIbnsmUBiTbrizdbOvipe-9I_1iZQ/edit
|
|
105
|
+
[fixtures]: http://localhost:8081/react-sandbox/components/new-tooltip-package/new-tooltip.jsx.fixture.js
|
|
106
|
+
[react-portal]: https://reactjs.org/docs/portals.html
|
|
107
|
+
[reusable-components]: https://khanacademy.slack.com/messages/C4PE1QM5Y/
|
|
108
|
+
|
|
109
|
+
Code-Owner: Frontend Infrastructure
|
|
110
|
+
Crowdin-Category: _other_
|