@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,692 @@
|
|
|
1
|
+
/* eslint-disable react/no-unsafe */
|
|
2
|
+
// @flow
|
|
3
|
+
import {StyleSheet, css} from "aphrodite";
|
|
4
|
+
import classNames from "classnames";
|
|
5
|
+
import PropTypes from "prop-types";
|
|
6
|
+
import * as React from "react";
|
|
7
|
+
import _ from "underscore";
|
|
8
|
+
|
|
9
|
+
import {ClassNames} from "../../perseus-api.jsx";
|
|
10
|
+
import * as styleConstants from "../../styles/constants.js";
|
|
11
|
+
import mediaQueries from "../../styles/media-queries.js";
|
|
12
|
+
import sharedStyles from "../../styles/shared.js";
|
|
13
|
+
|
|
14
|
+
import ChoiceIcon from "./choice-icon.jsx";
|
|
15
|
+
import CrossOutMenuWrapper from "./cross-out-menu-wrapper.jsx";
|
|
16
|
+
import OptionStatus from "./option-status.jsx";
|
|
17
|
+
import ToggleableRadioButton from "./toggleable-radio-button.jsx";
|
|
18
|
+
|
|
19
|
+
const focusedStyleMixin = {
|
|
20
|
+
backgroundColor: styleConstants.satSelectedBackgroundColor,
|
|
21
|
+
outline: `2px solid ${styleConstants.satBlue}`,
|
|
22
|
+
// Render the outline higher than the next element's border
|
|
23
|
+
zIndex: 1,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const intermediateCheckboxPadding = `16px 16px`;
|
|
27
|
+
const intermediateCheckboxPaddingPhone = `12px 16px`;
|
|
28
|
+
|
|
29
|
+
export const TouchIgnoreTimeout = 10;
|
|
30
|
+
|
|
31
|
+
type State = {|
|
|
32
|
+
isInputFocused: boolean,
|
|
33
|
+
isInputActive: boolean,
|
|
34
|
+
|};
|
|
35
|
+
|
|
36
|
+
class Choice extends React.Component<$FlowFixMe, State> {
|
|
37
|
+
justFinishedTouch: boolean;
|
|
38
|
+
justFinishedTimeoutID: TimeoutID;
|
|
39
|
+
_input: ?HTMLInputElement;
|
|
40
|
+
|
|
41
|
+
static propTypes = {
|
|
42
|
+
// TODO(kevinb) use Options.propTypes from perseus-api.jsx
|
|
43
|
+
// This change will also require make sure that item-renderer.jsx and
|
|
44
|
+
// server-item-renderer.jsx have appropriate defaults for apiOptions
|
|
45
|
+
// because many of the properties on Options.propTypes are required.
|
|
46
|
+
apiOptions: PropTypes.shape({
|
|
47
|
+
satStyling: PropTypes.bool,
|
|
48
|
+
isMobile: PropTypes.bool,
|
|
49
|
+
styling: PropTypes.shape({
|
|
50
|
+
radioStyleVersion: PropTypes.oneOf(["intermediate", "final"]),
|
|
51
|
+
primaryProductColor: PropTypes.string,
|
|
52
|
+
}),
|
|
53
|
+
readOnly: PropTypes.bool,
|
|
54
|
+
}),
|
|
55
|
+
checked: PropTypes.bool,
|
|
56
|
+
className: PropTypes.string,
|
|
57
|
+
rationale: PropTypes.node,
|
|
58
|
+
content: PropTypes.node,
|
|
59
|
+
correct: PropTypes.bool,
|
|
60
|
+
deselectEnabled: PropTypes.bool,
|
|
61
|
+
disabled: PropTypes.bool,
|
|
62
|
+
editMode: PropTypes.bool,
|
|
63
|
+
groupName: PropTypes.string,
|
|
64
|
+
isLastChoice: PropTypes.bool, // Needed for border styling
|
|
65
|
+
// This indicates the position of the choice relative to others
|
|
66
|
+
// (so that we can display a nice little (A), (B), etc. next to it)
|
|
67
|
+
// Also used to generate an id for each input.
|
|
68
|
+
pos: PropTypes.number,
|
|
69
|
+
reviewMode: PropTypes.bool,
|
|
70
|
+
showRationale: PropTypes.bool,
|
|
71
|
+
showCorrectness: PropTypes.bool,
|
|
72
|
+
type: PropTypes.string,
|
|
73
|
+
|
|
74
|
+
// Indicates whether the user has "crossed out" this choice, meaning
|
|
75
|
+
// that they don't think it's correct. This value does not affect
|
|
76
|
+
// scoring or other behavior; it's just a note for the user's
|
|
77
|
+
// reference.
|
|
78
|
+
crossedOut: PropTypes.bool,
|
|
79
|
+
|
|
80
|
+
// Indicates that the user has previously selected this answer. These
|
|
81
|
+
// answers may be rendered orange in review, rather than grey if
|
|
82
|
+
// incorrect.
|
|
83
|
+
previouslyAnswered: PropTypes.bool,
|
|
84
|
+
|
|
85
|
+
// A callback indicating that this choice has changed. Its argument is
|
|
86
|
+
// an object with two keys: `checked` and `crossedOut`. Each contains a
|
|
87
|
+
// boolean value specifying the new checked and crossed-out value of
|
|
88
|
+
// this choice.
|
|
89
|
+
onChange: PropTypes.func,
|
|
90
|
+
|
|
91
|
+
// Callbacks for when the user presses the up arrow or down arrow
|
|
92
|
+
// (respectively) to navigate between choices. When called, the parent
|
|
93
|
+
// finds the prev/next choice, focuses the input, and checks the radio
|
|
94
|
+
// button if appropriate. (The reason we have custom behavior is that
|
|
95
|
+
// we actually _don't_ want to auto-select crossed-out choices!)
|
|
96
|
+
goToPrevChoice: PropTypes.func.isRequired,
|
|
97
|
+
goToNextChoice: PropTypes.func.isRequired,
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
static defaultProps: $FlowFixMe = {
|
|
101
|
+
apiOptions: {
|
|
102
|
+
styling: {},
|
|
103
|
+
},
|
|
104
|
+
checked: false,
|
|
105
|
+
classSet: {},
|
|
106
|
+
correct: false,
|
|
107
|
+
disabled: false,
|
|
108
|
+
editMode: false,
|
|
109
|
+
onChange: function () {},
|
|
110
|
+
showRationale: false,
|
|
111
|
+
type: "radio",
|
|
112
|
+
pos: 0,
|
|
113
|
+
previouslyAnswered: false,
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
state: $FlowFixMe = {
|
|
117
|
+
isInputFocused: false,
|
|
118
|
+
isInputActive: false,
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
UNSAFE_componentWillUpdate(nextProps: $FlowFixMe) {
|
|
122
|
+
if (this.state.isInputFocused && nextProps.disabled) {
|
|
123
|
+
this.setState({
|
|
124
|
+
isInputFocused: false,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
componentWillUnmount() {
|
|
130
|
+
if (this.justFinishedTimeoutID != null) {
|
|
131
|
+
// TODO(jeff, CP-3128): Use Wonder Blocks Timing API
|
|
132
|
+
// eslint-disable-next-line no-restricted-syntax
|
|
133
|
+
clearTimeout(this.justFinishedTimeoutID);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
onInputFocus: () => void = () => {
|
|
138
|
+
this.setState({isInputFocused: true});
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
onInputBlur: () => void = () => {
|
|
142
|
+
this.setState({isInputFocused: false});
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
onInputMouseDown: (SyntheticMouseEvent<> | SyntheticTouchEvent<>) => void =
|
|
146
|
+
(e) => {
|
|
147
|
+
if (e.type === "mousedown" && this.justFinishedTouch) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
this.setState({isInputActive: true});
|
|
152
|
+
|
|
153
|
+
// Simulate Chrome's radio button behavior in all browsers: when the
|
|
154
|
+
// mouse goes down or up, the radio button should become focused.
|
|
155
|
+
// That way, the newly-selected answer becomes highlighted after click.
|
|
156
|
+
if (this.props.apiOptions.satStyling && this._input) {
|
|
157
|
+
this._input.focus();
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
onInputMouseUp: (SyntheticMouseEvent<> | SyntheticTouchEvent<>) => void = (
|
|
162
|
+
e,
|
|
163
|
+
) => {
|
|
164
|
+
if (e.type === "mouseup" && this.justFinishedTouch) {
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// NOTE(emily): We do some special handling here of touch events to
|
|
169
|
+
// make the "active" effect look better. In particular, when you click
|
|
170
|
+
// using touch events, we get a series of events going
|
|
171
|
+
// touchstart -> (delay) -> touchend -> mousedown -> mouseup -> click
|
|
172
|
+
// In order to make sure that we don't turn the active state off and on
|
|
173
|
+
// and off again during the touchend -> mousedown -> mouseup series, we
|
|
174
|
+
// set a flag (this.justFinishedTouch) after the touchend, and ignore
|
|
175
|
+
// the mousedown and mouseup events. Then, a little while later, we
|
|
176
|
+
// turn the flag off. Instead of turning the active state off right at
|
|
177
|
+
// the beginning, we wait for a little bit to sync it up better with
|
|
178
|
+
// the click event.
|
|
179
|
+
if (e.type === "touchend") {
|
|
180
|
+
this.justFinishedTouch = true;
|
|
181
|
+
|
|
182
|
+
// TODO(jeff, CP-3128): Use Wonder Blocks Timing API
|
|
183
|
+
/* eslint-disable no-restricted-syntax */
|
|
184
|
+
if (this.justFinishedTimeoutID != null) {
|
|
185
|
+
clearTimeout(this.justFinishedTimeoutID);
|
|
186
|
+
}
|
|
187
|
+
this.justFinishedTimeoutID = setTimeout(() => {
|
|
188
|
+
this.setState({isInputActive: false});
|
|
189
|
+
this.justFinishedTouch = false;
|
|
190
|
+
}, TouchIgnoreTimeout);
|
|
191
|
+
/* eslint-enable no-restricted-syntax */
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
this.setState({isInputActive: false});
|
|
196
|
+
|
|
197
|
+
// Simulate Chrome's radio button behavior in all browsers: when the
|
|
198
|
+
// mouse goes down or up, the radio button should become focused.
|
|
199
|
+
// That way, the newly-selected answer becomes highlighted after click.
|
|
200
|
+
if (this.props.apiOptions.satStyling && this._input) {
|
|
201
|
+
this._input.focus();
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
onInputMouseOut: () => void = () => {
|
|
206
|
+
this.setState({isInputActive: false});
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
inputRef: ($FlowFixMe) => void = (ref) => {
|
|
210
|
+
this._input = ref;
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
renderOptionStatus: () => React.Node = () => {
|
|
214
|
+
const {correct, checked, crossedOut, reviewMode, previouslyAnswered} =
|
|
215
|
+
this.props;
|
|
216
|
+
|
|
217
|
+
// Option status is exclued for SAT
|
|
218
|
+
if (this.props.apiOptions.satStyling) {
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Option status is shown only in review mode, or for incorrectly
|
|
223
|
+
// answered items.
|
|
224
|
+
if (!reviewMode && !previouslyAnswered) {
|
|
225
|
+
return null;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return (
|
|
229
|
+
<OptionStatus
|
|
230
|
+
checked={checked}
|
|
231
|
+
correct={correct}
|
|
232
|
+
crossedOut={crossedOut}
|
|
233
|
+
previouslyAnswered={previouslyAnswered}
|
|
234
|
+
/>
|
|
235
|
+
);
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
renderChoiceIcon: () => React.Node = () => {
|
|
239
|
+
const {radioStyleVersion, primaryProductColor} =
|
|
240
|
+
this.props.apiOptions.styling;
|
|
241
|
+
const finalStyles =
|
|
242
|
+
typeof radioStyleVersion === "undefined"
|
|
243
|
+
? false
|
|
244
|
+
: radioStyleVersion === "final";
|
|
245
|
+
|
|
246
|
+
if (!finalStyles && !this.props.apiOptions.satStyling) {
|
|
247
|
+
return null;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return (
|
|
251
|
+
<ChoiceIcon
|
|
252
|
+
pos={this.props.pos}
|
|
253
|
+
correct={this.props.correct}
|
|
254
|
+
crossedOut={this.props.crossedOut}
|
|
255
|
+
pressed={this.state.isInputActive}
|
|
256
|
+
focused={this.state.isInputFocused}
|
|
257
|
+
checked={this.props.checked}
|
|
258
|
+
showCorrectness={this.props.showCorrectness}
|
|
259
|
+
reviewMode={this.props.reviewMode}
|
|
260
|
+
product={this.props.apiOptions.satStyling ? "sat" : "library"}
|
|
261
|
+
primaryProductColor={primaryProductColor}
|
|
262
|
+
previouslyAnswered={this.props.previouslyAnswered}
|
|
263
|
+
/>
|
|
264
|
+
);
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
_setChecked: (boolean) => void = (willBeChecked) => {
|
|
268
|
+
if (willBeChecked) {
|
|
269
|
+
// If we're checking a crossed-out option, let's also uncross it.
|
|
270
|
+
this._sendChange({checked: true, crossedOut: false});
|
|
271
|
+
} else {
|
|
272
|
+
this._sendChange({checked: false});
|
|
273
|
+
}
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
// NOTE(mdr): This method expects to be auto-bound. If this component is
|
|
277
|
+
// converted to an ES6 class, take care to auto-bind this method!
|
|
278
|
+
_setCrossedOut: (boolean) => void = (willBeCrossedOut) => {
|
|
279
|
+
if (willBeCrossedOut) {
|
|
280
|
+
// If we're crossing out a checked option, let's also uncheck it.
|
|
281
|
+
this._sendChange({checked: false, crossedOut: true});
|
|
282
|
+
} else {
|
|
283
|
+
this._sendChange({crossedOut: false});
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
// Call `this.props.onChange` with the given values. Any keys that are not
|
|
288
|
+
// specified will be filled in with the current value. (For example, if
|
|
289
|
+
// `checked` is specified but `crossedOut` is not, then `crossedOut` will
|
|
290
|
+
// be filled in with `this.props.crossedOut`.)
|
|
291
|
+
//
|
|
292
|
+
// This enables us to use shorthand inside this component, while
|
|
293
|
+
// maintaining a consistent API for the parent.
|
|
294
|
+
_sendChange: ($FlowFixMe) => void = (newValues) => {
|
|
295
|
+
const checked =
|
|
296
|
+
newValues.checked != null ? newValues.checked : this.props.checked;
|
|
297
|
+
|
|
298
|
+
const crossedOut =
|
|
299
|
+
newValues.crossedOut != null
|
|
300
|
+
? newValues.crossedOut
|
|
301
|
+
: this.props.crossedOut;
|
|
302
|
+
|
|
303
|
+
this.props.onChange({checked, crossedOut});
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Public method. Focus the choice's <input> element.
|
|
308
|
+
*/
|
|
309
|
+
focusInput: () => void = () => {
|
|
310
|
+
this._input?.focus();
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
render(): React.Node {
|
|
314
|
+
const sat = this.props.apiOptions.satStyling;
|
|
315
|
+
const isMobile = this.props.apiOptions.isMobile;
|
|
316
|
+
|
|
317
|
+
const {radioStyleVersion} = this.props.apiOptions.styling;
|
|
318
|
+
const finalStyles =
|
|
319
|
+
typeof radioStyleVersion === "undefined"
|
|
320
|
+
? false
|
|
321
|
+
: radioStyleVersion === "final";
|
|
322
|
+
|
|
323
|
+
const className = classNames(
|
|
324
|
+
this.props.className,
|
|
325
|
+
"checkbox-label",
|
|
326
|
+
css(
|
|
327
|
+
styles.label,
|
|
328
|
+
isMobile && sharedStyles.disableTextSelection,
|
|
329
|
+
!sat && styles.responsiveLabel,
|
|
330
|
+
sat && styles.satLabel,
|
|
331
|
+
),
|
|
332
|
+
);
|
|
333
|
+
|
|
334
|
+
const satRadioMenuContainer = classNames(
|
|
335
|
+
css(
|
|
336
|
+
this.state.isInputFocused && styles.satDescriptionInputFocused,
|
|
337
|
+
this.state.isInputActive && styles.satDescriptionInputActive,
|
|
338
|
+
),
|
|
339
|
+
);
|
|
340
|
+
|
|
341
|
+
// If we're showing an answer to be incorrect, we render it as
|
|
342
|
+
// unchecked, regardless of the `checked` state we've stored.
|
|
343
|
+
//
|
|
344
|
+
// This is because the existence of a selected radio button makes it
|
|
345
|
+
// impossible to tab-navigate to any other choices (standard radio
|
|
346
|
+
// behavior), but the selected answer is now disabled and not tabbable,
|
|
347
|
+
// which means that non-mouse/non-touch users can't select another
|
|
348
|
+
// answer to try again!
|
|
349
|
+
//
|
|
350
|
+
// (This behavior is only necessary for type "radio", but we also apply
|
|
351
|
+
// it for type "checkbox", for consistency.)
|
|
352
|
+
const showingIncorrectness =
|
|
353
|
+
this.props.showCorrectness && !this.props.correct;
|
|
354
|
+
const inputIsChecked = this.props.checked && !showingIncorrectness;
|
|
355
|
+
|
|
356
|
+
// There's two different input components we could use (the builtin
|
|
357
|
+
// input component, or the ToggleableRadioButton component). These are
|
|
358
|
+
// the props that we will pass to either.
|
|
359
|
+
const commonInputProps = {
|
|
360
|
+
type: this.props.type,
|
|
361
|
+
name: this.props.groupName,
|
|
362
|
+
id: `${this.props.groupName}-choice-${this.props.pos}`,
|
|
363
|
+
checked: inputIsChecked,
|
|
364
|
+
disabled: this.props.disabled,
|
|
365
|
+
onFocus: this.onInputFocus,
|
|
366
|
+
onBlur: this.onInputBlur,
|
|
367
|
+
className: css(
|
|
368
|
+
// intermediate styles are not different for radio and
|
|
369
|
+
// checkbox, and have a separate active state.
|
|
370
|
+
!finalStyles && sharedStyles.perseusInteractive,
|
|
371
|
+
!finalStyles && styles.input,
|
|
372
|
+
!finalStyles && sharedStyles.responsiveInput,
|
|
373
|
+
!finalStyles && !sat && sharedStyles.responsiveRadioInput,
|
|
374
|
+
!finalStyles &&
|
|
375
|
+
!sat &&
|
|
376
|
+
this.state.isInputActive &&
|
|
377
|
+
sharedStyles.responsiveRadioInputActive,
|
|
378
|
+
finalStyles && sharedStyles.perseusSrOnly,
|
|
379
|
+
sat && sharedStyles.perseusSrOnly,
|
|
380
|
+
sat && this.props.reviewMode && styles.satReviewInput,
|
|
381
|
+
),
|
|
382
|
+
};
|
|
383
|
+
|
|
384
|
+
let input = null;
|
|
385
|
+
if (this.props.type === "radio") {
|
|
386
|
+
// This is a special radio button that allows a user to deselect
|
|
387
|
+
// it by merely clicking/selecting it again.
|
|
388
|
+
input = (
|
|
389
|
+
<ToggleableRadioButton
|
|
390
|
+
onChecked={(willBeChecked) =>
|
|
391
|
+
this._setChecked(willBeChecked)
|
|
392
|
+
}
|
|
393
|
+
goToPrevChoice={this.props.goToPrevChoice}
|
|
394
|
+
goToNextChoice={this.props.goToNextChoice}
|
|
395
|
+
inputRef={this.inputRef}
|
|
396
|
+
{...commonInputProps}
|
|
397
|
+
/>
|
|
398
|
+
);
|
|
399
|
+
} else {
|
|
400
|
+
input = (
|
|
401
|
+
<input
|
|
402
|
+
onChange={(event) => this._setChecked(event.target.checked)}
|
|
403
|
+
ref={this.inputRef}
|
|
404
|
+
{...commonInputProps}
|
|
405
|
+
/>
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
const {reviewMode, correct, checked} = this.props;
|
|
410
|
+
// HACK: while most of the styling for rendering SAT items is handled
|
|
411
|
+
// via aphrodite, we also need to assign normal CSS classnames here to
|
|
412
|
+
// special-case the coloring of MathJax formulas (see .MathJax .math in
|
|
413
|
+
// stylesheets/task-package/tasks.less)
|
|
414
|
+
const satCorrectChoice = sat && reviewMode && correct;
|
|
415
|
+
const satIncorrectChecked = sat && reviewMode && !correct && checked;
|
|
416
|
+
const descriptionClassName = classNames(
|
|
417
|
+
"description",
|
|
418
|
+
satCorrectChoice && "sat-correct",
|
|
419
|
+
satIncorrectChecked && "sat-incorrect",
|
|
420
|
+
css(
|
|
421
|
+
!sat && styles.description,
|
|
422
|
+
sat && styles.satDescription,
|
|
423
|
+
satCorrectChoice && styles.satDescriptionCorrect,
|
|
424
|
+
satCorrectChoice &&
|
|
425
|
+
checked &&
|
|
426
|
+
styles.satDescriptionCorrectChecked,
|
|
427
|
+
satIncorrectChecked && styles.satDescriptionIncorrectChecked,
|
|
428
|
+
),
|
|
429
|
+
);
|
|
430
|
+
|
|
431
|
+
const checkboxContentClassName = classNames(
|
|
432
|
+
"checkbox",
|
|
433
|
+
css(
|
|
434
|
+
sharedStyles.perseusInteractive,
|
|
435
|
+
!sat && styles.choiceIconWrapper,
|
|
436
|
+
sat && styles.satCheckboxOptionContent,
|
|
437
|
+
),
|
|
438
|
+
);
|
|
439
|
+
|
|
440
|
+
const checkboxAndOptionClassName = classNames(
|
|
441
|
+
"checkbox-and-option",
|
|
442
|
+
css(
|
|
443
|
+
!sat && styles.intermediateResponsiveCheckbox,
|
|
444
|
+
!sat &&
|
|
445
|
+
reviewMode &&
|
|
446
|
+
styles.intermediateResponsiveCheckboxReview,
|
|
447
|
+
),
|
|
448
|
+
);
|
|
449
|
+
|
|
450
|
+
const rationaleClassName = classNames(
|
|
451
|
+
"perseus-radio-rationale-content",
|
|
452
|
+
css(
|
|
453
|
+
styles.rationale,
|
|
454
|
+
!sat && styles.nonSatRationale,
|
|
455
|
+
sat && styles.satReviewRationale,
|
|
456
|
+
),
|
|
457
|
+
);
|
|
458
|
+
|
|
459
|
+
// In edit mode, we must allow selection of the contentEditable
|
|
460
|
+
// element inside, therefore we cannot use a label, which makes
|
|
461
|
+
// selection of anything inside automatically select the input
|
|
462
|
+
// element instead
|
|
463
|
+
const LabelOrDiv = this.props.editMode ? "div" : "label";
|
|
464
|
+
|
|
465
|
+
// We only show cross-out in certain products, and
|
|
466
|
+
// only when you're still _answering_ the question. We also show this
|
|
467
|
+
// for the SAT product area.
|
|
468
|
+
const crossOutEnabled = this.props.apiOptions.crossOutEnabled;
|
|
469
|
+
const showCrossOutMenu = crossOutEnabled && !this.props.showCorrectness;
|
|
470
|
+
|
|
471
|
+
// We want to show the choices as dimmed out when the choices are
|
|
472
|
+
// disabled. However, we don't want to do this in the SAT product and
|
|
473
|
+
// we also don't want to do this when we're in review mode in the
|
|
474
|
+
// content library.
|
|
475
|
+
const showDimmed =
|
|
476
|
+
(!sat && !reviewMode && this.props.apiOptions.readOnly) ||
|
|
477
|
+
(showCrossOutMenu && this.props.crossedOut);
|
|
478
|
+
|
|
479
|
+
return (
|
|
480
|
+
<div className={sat ? satRadioMenuContainer : undefined}>
|
|
481
|
+
<CrossOutMenuWrapper
|
|
482
|
+
enabled={showCrossOutMenu}
|
|
483
|
+
pos={this.props.pos}
|
|
484
|
+
primaryProductColor={
|
|
485
|
+
this.props.apiOptions.styling.primaryProductColor
|
|
486
|
+
}
|
|
487
|
+
crossedOut={this.props.crossedOut}
|
|
488
|
+
onCrossedOutChange={this._setCrossedOut}
|
|
489
|
+
isSatProduct={sat}
|
|
490
|
+
>
|
|
491
|
+
<LabelOrDiv
|
|
492
|
+
htmlFor={
|
|
493
|
+
!this.props.editMode
|
|
494
|
+
? commonInputProps.id
|
|
495
|
+
: undefined
|
|
496
|
+
}
|
|
497
|
+
className={className}
|
|
498
|
+
style={{opacity: showDimmed ? 0.5 : 1.0}}
|
|
499
|
+
>
|
|
500
|
+
<div
|
|
501
|
+
className={descriptionClassName}
|
|
502
|
+
onMouseDown={this.onInputMouseDown}
|
|
503
|
+
onMouseUp={this.onInputMouseUp}
|
|
504
|
+
onMouseOut={this.onInputMouseOut}
|
|
505
|
+
onTouchStart={this.onInputMouseDown}
|
|
506
|
+
onTouchEnd={this.onInputMouseUp}
|
|
507
|
+
>
|
|
508
|
+
<div className={checkboxAndOptionClassName}>
|
|
509
|
+
<span className={checkboxContentClassName}>
|
|
510
|
+
{input}
|
|
511
|
+
{this.renderChoiceIcon()}
|
|
512
|
+
</span>
|
|
513
|
+
{/* A pseudo-label. <label> is slightly broken on iOS,
|
|
514
|
+
so this works around that. Unfortunately, it is
|
|
515
|
+
simplest to just work around that everywhere. */}
|
|
516
|
+
<span
|
|
517
|
+
className={classNames(
|
|
518
|
+
ClassNames.RADIO.OPTION_CONTENT,
|
|
519
|
+
ClassNames.INTERACTIVE,
|
|
520
|
+
css(
|
|
521
|
+
sat && styles.satRadioOptionContent,
|
|
522
|
+
sat &&
|
|
523
|
+
reviewMode &&
|
|
524
|
+
styles.satReviewRadioOptionContent,
|
|
525
|
+
),
|
|
526
|
+
)}
|
|
527
|
+
style={{cursor: "default"}}
|
|
528
|
+
>
|
|
529
|
+
<div
|
|
530
|
+
className={css(
|
|
531
|
+
styles.optionStatusContainer,
|
|
532
|
+
)}
|
|
533
|
+
>
|
|
534
|
+
{this.renderOptionStatus()}
|
|
535
|
+
</div>
|
|
536
|
+
<div>{this.props.content}</div>
|
|
537
|
+
</span>
|
|
538
|
+
</div>
|
|
539
|
+
{this.props.showRationale && (
|
|
540
|
+
<div
|
|
541
|
+
className={rationaleClassName}
|
|
542
|
+
data-test-id={`perseus-radio-rationale-content-${this.props.pos}`}
|
|
543
|
+
>
|
|
544
|
+
{this.props.rationale}
|
|
545
|
+
</div>
|
|
546
|
+
)}
|
|
547
|
+
</div>
|
|
548
|
+
</LabelOrDiv>
|
|
549
|
+
</CrossOutMenuWrapper>
|
|
550
|
+
</div>
|
|
551
|
+
);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
const styles = StyleSheet.create({
|
|
556
|
+
description: {
|
|
557
|
+
display: "inline-block",
|
|
558
|
+
width: "100%",
|
|
559
|
+
},
|
|
560
|
+
|
|
561
|
+
satDescription: {
|
|
562
|
+
display: "block",
|
|
563
|
+
position: "relative",
|
|
564
|
+
boxSizing: "border-box",
|
|
565
|
+
cursor: "pointer",
|
|
566
|
+
marginLeft: 0,
|
|
567
|
+
padding: "17px 14px",
|
|
568
|
+
},
|
|
569
|
+
|
|
570
|
+
satDescriptionInputFocused: {
|
|
571
|
+
...focusedStyleMixin,
|
|
572
|
+
display: "block",
|
|
573
|
+
},
|
|
574
|
+
|
|
575
|
+
satDescriptionInputActive: {
|
|
576
|
+
...focusedStyleMixin,
|
|
577
|
+
backgroundColor: styleConstants.satActiveBackgroundColor,
|
|
578
|
+
},
|
|
579
|
+
|
|
580
|
+
satDescriptionCorrect: {
|
|
581
|
+
color: styleConstants.satCorrectColor,
|
|
582
|
+
},
|
|
583
|
+
|
|
584
|
+
satDescriptionCorrectChecked: {
|
|
585
|
+
backgroundColor: styleConstants.satCorrectBackgroundColor,
|
|
586
|
+
},
|
|
587
|
+
|
|
588
|
+
satDescriptionIncorrectChecked: {
|
|
589
|
+
color: styleConstants.satIncorrectColor,
|
|
590
|
+
backgroundColor: styleConstants.satIncorrectBackgroundColor,
|
|
591
|
+
},
|
|
592
|
+
|
|
593
|
+
input: {
|
|
594
|
+
display: "inline-block",
|
|
595
|
+
width: 20,
|
|
596
|
+
margin: 3,
|
|
597
|
+
marginLeft: -20,
|
|
598
|
+
marginRight: 0,
|
|
599
|
+
float: "none",
|
|
600
|
+
},
|
|
601
|
+
|
|
602
|
+
satReviewInput: {
|
|
603
|
+
pointerEvents: "none",
|
|
604
|
+
},
|
|
605
|
+
|
|
606
|
+
satRadioOptionContent: {
|
|
607
|
+
userSelect: "text",
|
|
608
|
+
display: "block",
|
|
609
|
+
marginLeft: 45,
|
|
610
|
+
// Overriding here, not sure why typically set
|
|
611
|
+
// to "cursor: default" in js
|
|
612
|
+
cursor: "inherit",
|
|
613
|
+
},
|
|
614
|
+
|
|
615
|
+
satReviewRadioOptionContent: {
|
|
616
|
+
fontWeight: "bold",
|
|
617
|
+
},
|
|
618
|
+
|
|
619
|
+
satCheckboxOptionContent: {
|
|
620
|
+
position: "absolute",
|
|
621
|
+
display: "block",
|
|
622
|
+
top: "50%",
|
|
623
|
+
margin: "-16px 0 0 0",
|
|
624
|
+
width: "auto",
|
|
625
|
+
},
|
|
626
|
+
|
|
627
|
+
choiceIconWrapper: {
|
|
628
|
+
display: "flex",
|
|
629
|
+
marginRight: 12,
|
|
630
|
+
|
|
631
|
+
// NOTE(mdr): Without this style, the bubbles shrink on iOS
|
|
632
|
+
// when answer text gets long.
|
|
633
|
+
flexShrink: 0,
|
|
634
|
+
},
|
|
635
|
+
|
|
636
|
+
optionStatusContainer: {
|
|
637
|
+
display: "block",
|
|
638
|
+
},
|
|
639
|
+
|
|
640
|
+
rationale: {
|
|
641
|
+
display: "block",
|
|
642
|
+
},
|
|
643
|
+
|
|
644
|
+
nonSatRationale: {
|
|
645
|
+
padding: intermediateCheckboxPadding,
|
|
646
|
+
paddingTop: 0,
|
|
647
|
+
marginLeft: 44,
|
|
648
|
+
[mediaQueries.smOrSmaller]: {
|
|
649
|
+
padding: intermediateCheckboxPaddingPhone,
|
|
650
|
+
paddingTop: 0,
|
|
651
|
+
},
|
|
652
|
+
},
|
|
653
|
+
|
|
654
|
+
satReviewRationale: {
|
|
655
|
+
marginTop: 13,
|
|
656
|
+
marginLeft: 45,
|
|
657
|
+
},
|
|
658
|
+
|
|
659
|
+
label: {
|
|
660
|
+
display: "block",
|
|
661
|
+
transition: "opacity 0.2s ease-out",
|
|
662
|
+
},
|
|
663
|
+
|
|
664
|
+
responsiveLabel: {
|
|
665
|
+
WebkitTapHighlightColor: "transparent",
|
|
666
|
+
display: "flex",
|
|
667
|
+
|
|
668
|
+
// Ensure that all items consume the full height of the choice.
|
|
669
|
+
// That way, the cross-out menu click target is full-height,
|
|
670
|
+
// which makes it easier to click!
|
|
671
|
+
alignItems: "stretch",
|
|
672
|
+
},
|
|
673
|
+
|
|
674
|
+
satLabel: {
|
|
675
|
+
cursor: "pointer",
|
|
676
|
+
},
|
|
677
|
+
|
|
678
|
+
intermediateResponsiveCheckbox: {
|
|
679
|
+
display: "flex",
|
|
680
|
+
alignItems: "center",
|
|
681
|
+
|
|
682
|
+
padding: intermediateCheckboxPadding,
|
|
683
|
+
[mediaQueries.smOrSmaller]: {
|
|
684
|
+
padding: intermediateCheckboxPaddingPhone,
|
|
685
|
+
},
|
|
686
|
+
},
|
|
687
|
+
intermediateResponsiveCheckboxReview: {
|
|
688
|
+
alignItems: "flex-start",
|
|
689
|
+
},
|
|
690
|
+
});
|
|
691
|
+
|
|
692
|
+
export default Choice;
|