@khanacademy/perseus 0.4.3 → 1.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/.eslintrc.js +6 -1
- package/CHANGELOG.md +45 -0
- package/dist/es/index.css +15 -8
- package/dist/es/index.js +1214 -1404
- package/dist/es/index.js.map +1 -1
- package/dist/index.css +15 -8
- package/dist/index.js +1217 -1411
- package/dist/index.js.map +1 -1
- package/dist/strings.js +0 -4
- package/package.json +9 -8
- package/src/components/number-input.jsx +2 -2
- package/src/components/simple-keypad-input.jsx +18 -21
- package/src/index.js +1 -1
- package/src/multi-items/__tests__/__snapshots__/multi-renderer_test.js.snap +426 -135
- package/src/multi-items/__tests__/multi-renderer_test.js +9 -9
- package/src/perseus-api.jsx +10 -36
- package/src/perseus-types.js +18 -145
- package/src/styles/constants.js +0 -2
- package/src/styles/perseus-renderer.less +8 -2
- package/src/styles/variables.less +0 -2
- package/src/styles/widgets/passage.less +9 -5
- package/src/types.js +18 -35
- package/src/util/answer-types.js +2 -3
- package/src/util/math.js +3 -10
- package/src/util.js +0 -6
- package/src/version.js +1 -1
- package/src/widgets/__stories__/numeric-input.stories.jsx +43 -2
- package/src/widgets/__testdata__/base-radio_testdata.js +22 -0
- package/src/widgets/__tests__/__snapshots__/definition_test.js.snap +4 -4
- package/src/widgets/__tests__/__snapshots__/group_test.jsx.snap +291 -130
- package/src/widgets/__tests__/__snapshots__/radio_test.jsx.snap +3293 -1415
- package/src/widgets/__tests__/group_test.jsx +4 -4
- package/src/widgets/__tests__/numeric-input_test.jsx +12 -8
- package/src/widgets/__tests__/passage_test.jsx +81 -0
- package/src/widgets/__tests__/radio/base-radio_test.jsx +291 -41
- package/src/widgets/__tests__/radio/choice-icon_test.jsx +67 -0
- package/src/widgets/__tests__/radio/choice_test.jsx +302 -0
- package/src/widgets/__tests__/radio/focus-ring_test.jsx +23 -0
- package/src/widgets/__tests__/radio/option-status_test.jsx +65 -0
- package/src/widgets/__tests__/radio_test.jsx +39 -15
- package/src/widgets/definition.jsx +1 -1
- package/src/widgets/group.jsx +84 -102
- package/src/widgets/input-number.jsx +6 -1
- package/src/widgets/numeric-input.jsx +87 -80
- package/src/widgets/passage/__tests__/passage-markdown_test.jsx +0 -9
- package/src/widgets/passage/get-line-height-for-node.js +4 -5
- package/src/widgets/passage/passage-markdown.jsx +176 -120
- package/src/widgets/passage.jsx +48 -46
- package/src/widgets/radio/__stories__/base-radio.stories.jsx +164 -17
- package/src/widgets/radio/__stories__/choice-icon.stories.jsx +202 -111
- package/src/widgets/radio/__stories__/choice-none-above.stories.jsx +21 -6
- package/src/widgets/radio/__stories__/choice.stories.jsx +138 -0
- package/src/widgets/radio/__stories__/focus-ring.stories.jsx +29 -23
- package/src/widgets/radio/__stories__/option-status.stories.jsx +93 -99
- package/src/widgets/radio/base-radio.jsx +485 -556
- package/src/widgets/radio/choice-a11y-text.js +62 -0
- package/src/widgets/radio/choice-icon/choice-icon.jsx +87 -0
- package/src/widgets/radio/choice-icon/cross-out-line.jsx +55 -0
- package/src/widgets/radio/choice-icon/library-choice-icon.jsx +214 -0
- package/src/widgets/radio/choice-icon/sat-choice-icon.jsx +120 -0
- package/src/widgets/radio/choice-icon/shared-styles.js +17 -0
- package/src/widgets/radio/choice-none-above.jsx +49 -40
- package/src/widgets/radio/choice.jsx +319 -347
- package/src/widgets/radio/choice.jsx.fixture.js +0 -18
- package/src/widgets/radio/focus-ring.jsx +29 -22
- package/src/widgets/radio/option-status.jsx +58 -36
- package/src/widgets/radio/widget.jsx +181 -136
- package/src/widgets/radio.jsx +16 -17
- package/src/widgets/radio/__stories__/cross-out-button.stories.jsx +0 -64
- package/src/widgets/radio/choice-icon.jsx +0 -469
- package/src/widgets/radio/cross-out-button.jsx +0 -134
package/dist/index.js
CHANGED
|
@@ -73,7 +73,7 @@ var Link__default = /*#__PURE__*/_interopDefaultLegacy(Link);
|
|
|
73
73
|
|
|
74
74
|
var version = {
|
|
75
75
|
apiVersion: {
|
|
76
|
-
major:
|
|
76
|
+
major: 12,
|
|
77
77
|
minor: 0
|
|
78
78
|
},
|
|
79
79
|
itemDataVersion: {
|
|
@@ -438,38 +438,9 @@ const ApiOptions = {
|
|
|
438
438
|
// values. For now, though, you must either specify all fields
|
|
439
439
|
// of `styling`, or omit the `styling` option entirely.
|
|
440
440
|
styling: PropTypes__default["default"].shape({
|
|
441
|
-
//
|
|
442
|
-
//
|
|
443
|
-
|
|
444
|
-
// "legacy" was the version of the widget display after XOM but
|
|
445
|
-
// before we started adding MCR styles. It doesn't have support
|
|
446
|
-
// for rationales. It has since been removed.
|
|
447
|
-
//
|
|
448
|
-
// "intermediate" is a design which adds several new additions
|
|
449
|
-
// to the "legacy" styles such as:
|
|
450
|
-
// 1. Using the XOM "desktop" styles (with a visible check icon
|
|
451
|
-
// and lines in between choices) on mobile devices.
|
|
452
|
-
// 2. Designs for rationales
|
|
453
|
-
// 3. Using the single-select styles for multi-select styles
|
|
454
|
-
//
|
|
455
|
-
// "final" is a design which will build off of the
|
|
456
|
-
// "intermediate" designs and adds some improved designs as well
|
|
457
|
-
// as:
|
|
458
|
-
// 1. a/b/c/d/etc. letters inside of the prompt check box
|
|
459
|
-
// 2. New iconography and styles to indicate choice correctness
|
|
460
|
-
//
|
|
461
|
-
// The "legacy" and "intermediate" designs will be A/B tested
|
|
462
|
-
// against each other to ensure that its changes don't cause
|
|
463
|
-
// problems due to the new designs. Once the "intermediate"
|
|
464
|
-
// designs are finished, they will be A/B tested against the
|
|
465
|
-
// "final" designs.
|
|
466
|
-
//
|
|
467
|
-
// If no flag is provided, "legacy" styles will be shown.
|
|
468
|
-
//
|
|
469
|
-
// TODO(emily): Remove this by Aug 1, 2017, at which point all
|
|
470
|
-
// callsites should have been switched to using the "final"
|
|
471
|
-
// designs.
|
|
472
|
-
radioStyleVersion: PropTypes__default["default"].oneOf(["intermediate", "final"])
|
|
441
|
+
// TODO(Nicole): Remove this after landing wonderblock-ifying
|
|
442
|
+
// the radio widget
|
|
443
|
+
primaryProductColor: PropTypes__default["default"].string
|
|
473
444
|
}),
|
|
474
445
|
// The color used for the hint progress indicator (eg. 1 / 3)
|
|
475
446
|
hintProgressColor: PropTypes__default["default"].string,
|
|
@@ -520,9 +491,7 @@ const ApiOptions = {
|
|
|
520
491
|
},
|
|
521
492
|
setDrawingAreaAvailable: function () {},
|
|
522
493
|
useDraftEditor: false,
|
|
523
|
-
styling: {
|
|
524
|
-
radioStyleVersion: "final"
|
|
525
|
-
},
|
|
494
|
+
styling: {},
|
|
526
495
|
canScrollPage: false,
|
|
527
496
|
inModal: false,
|
|
528
497
|
crossOutEnabled: false,
|
|
@@ -1448,8 +1417,7 @@ const KhanAnswerTypes = {
|
|
|
1448
1417
|
*/
|
|
1449
1418
|
number: {
|
|
1450
1419
|
convertToPredicate: function (correctAnswer, options) {
|
|
1451
|
-
|
|
1452
|
-
const correctFloat = parseFloat($__default["default"].trim(correctAnswer));
|
|
1420
|
+
const correctFloat = parseFloat(correctAnswer);
|
|
1453
1421
|
return [function (guess, maxError) {
|
|
1454
1422
|
return Math.abs(guess - correctFloat) < maxError;
|
|
1455
1423
|
}, $__default["default"].extend({}, options, {
|
|
@@ -1643,16 +1611,10 @@ function inputPathsEqual(a, b) {
|
|
|
1643
1611
|
return a.length === b.length && a.every((item, index) => {
|
|
1644
1612
|
return b[index] === item;
|
|
1645
1613
|
});
|
|
1646
|
-
}
|
|
1647
|
-
// in content_internal/perseus_traversal.py
|
|
1648
|
-
// sync-start:widget-id-regex 1271666319 services/content-editing/perseus/translation/content_traversal.go
|
|
1649
|
-
// sync-start:widget-id-regex 680240484 services/static/javascript/perseus-merged-editor-package/editor.jsx
|
|
1650
|
-
// sync-start:widget-id-regex 1400368396 content_internal/perseus_traversal.py
|
|
1651
|
-
|
|
1614
|
+
}
|
|
1652
1615
|
|
|
1653
1616
|
const rWidgetRule = /^\[\[\u2603 (([a-z-]+) ([0-9]+))\]\]/;
|
|
1654
|
-
const rTypeFromWidgetId = /^([a-z-]+) ([0-9]+)$/;
|
|
1655
|
-
|
|
1617
|
+
const rTypeFromWidgetId = /^([a-z-]+) ([0-9]+)$/;
|
|
1656
1618
|
const rWidgetParts = new RegExp(rWidgetRule.source + "$");
|
|
1657
1619
|
const snowman = "\u2603";
|
|
1658
1620
|
const noScore = {
|
|
@@ -3085,11 +3047,10 @@ const warningColor = "#f86700";
|
|
|
3085
3047
|
const warningColorHover = "#df5c00";
|
|
3086
3048
|
const warningColorActive = "#c75300";
|
|
3087
3049
|
const publishBlockingErrorColor = "#be2612";
|
|
3088
|
-
const radioBorderColor$
|
|
3089
|
-
const checkedColor$1 = "#71B307";
|
|
3090
|
-
|
|
3050
|
+
const radioBorderColor$1 = "#BABEC2";
|
|
3051
|
+
const checkedColor$1 = "#71B307";
|
|
3091
3052
|
const articleMaxWidthInPx = 688;
|
|
3092
|
-
const articleMaxWidthTableInPx = 512;
|
|
3053
|
+
const articleMaxWidthTableInPx = 512;
|
|
3093
3054
|
|
|
3094
3055
|
var constants$1 = /*#__PURE__*/Object.freeze({
|
|
3095
3056
|
__proto__: null,
|
|
@@ -3154,7 +3115,7 @@ var constants$1 = /*#__PURE__*/Object.freeze({
|
|
|
3154
3115
|
warningColorHover: warningColorHover,
|
|
3155
3116
|
warningColorActive: warningColorActive,
|
|
3156
3117
|
publishBlockingErrorColor: publishBlockingErrorColor,
|
|
3157
|
-
radioBorderColor: radioBorderColor$
|
|
3118
|
+
radioBorderColor: radioBorderColor$1,
|
|
3158
3119
|
checkedColor: checkedColor$1,
|
|
3159
3120
|
articleMaxWidthInPx: articleMaxWidthInPx,
|
|
3160
3121
|
articleMaxWidthTableInPx: articleMaxWidthTableInPx
|
|
@@ -17888,17 +17849,17 @@ class Lint extends React__namespace.Component {
|
|
|
17888
17849
|
let warningTextStyle;
|
|
17889
17850
|
|
|
17890
17851
|
if (this.props.severity === 1) {
|
|
17891
|
-
severityStyle = styles$
|
|
17852
|
+
severityStyle = styles$A.indicatorError;
|
|
17892
17853
|
warningText = "Error";
|
|
17893
|
-
warningTextStyle = styles$
|
|
17854
|
+
warningTextStyle = styles$A.publishBlockingError;
|
|
17894
17855
|
} else if (this.props.severity === 2) {
|
|
17895
|
-
severityStyle = styles$
|
|
17856
|
+
severityStyle = styles$A.indicatorWarning;
|
|
17896
17857
|
warningText = "Warning";
|
|
17897
|
-
warningTextStyle = styles$
|
|
17858
|
+
warningTextStyle = styles$A.warning;
|
|
17898
17859
|
} else {
|
|
17899
|
-
severityStyle = styles$
|
|
17860
|
+
severityStyle = styles$A.indicatorGuideline;
|
|
17900
17861
|
warningText = "Recommendation";
|
|
17901
|
-
warningTextStyle = styles$
|
|
17862
|
+
warningTextStyle = styles$A.warning;
|
|
17902
17863
|
}
|
|
17903
17864
|
|
|
17904
17865
|
return /*#__PURE__*/React__namespace.createElement("a", {
|
|
@@ -17906,16 +17867,16 @@ class Lint extends React__namespace.Component {
|
|
|
17906
17867
|
target: "lint-help-window",
|
|
17907
17868
|
className: aphrodite.css(style)
|
|
17908
17869
|
}, /*#__PURE__*/React__namespace.createElement("span", {
|
|
17909
|
-
className: aphrodite.css(styles$
|
|
17870
|
+
className: aphrodite.css(styles$A.indicator, severityStyle)
|
|
17910
17871
|
}, this.props.severity === 1 && /*#__PURE__*/React__namespace.createElement(InlineIcon, exclamationIcon)), /*#__PURE__*/React__namespace.createElement("div", {
|
|
17911
|
-
className: aphrodite.css(styles$
|
|
17872
|
+
className: aphrodite.css(styles$A.tooltip, tooltipAbove && styles$A.tooltipAbove)
|
|
17912
17873
|
}, this.props.message.split("\n\n").map((m, i) => /*#__PURE__*/React__namespace.createElement("p", {
|
|
17913
17874
|
key: i,
|
|
17914
|
-
className: aphrodite.css(styles$
|
|
17875
|
+
className: aphrodite.css(styles$A.tooltipParagraph)
|
|
17915
17876
|
}, /*#__PURE__*/React__namespace.createElement("span", {
|
|
17916
17877
|
className: aphrodite.css(warningTextStyle)
|
|
17917
17878
|
}, warningText, ":", " "), m)), /*#__PURE__*/React__namespace.createElement("div", {
|
|
17918
|
-
className: aphrodite.css(styles$
|
|
17879
|
+
className: aphrodite.css(styles$A.tail, tooltipAbove && styles$A.tailAbove)
|
|
17919
17880
|
})));
|
|
17920
17881
|
});
|
|
17921
17882
|
}
|
|
@@ -17963,19 +17924,19 @@ class Lint extends React__namespace.Component {
|
|
|
17963
17924
|
|
|
17964
17925
|
if (blockHighlight) {
|
|
17965
17926
|
return /*#__PURE__*/React__namespace.createElement("span", {
|
|
17966
|
-
className: aphrodite.css(styles$
|
|
17967
|
-
}, this.renderLink(styles$
|
|
17927
|
+
className: aphrodite.css(styles$A.lintContainer, styles$A.lintContainerBlock)
|
|
17928
|
+
}, this.renderLink(styles$A.radioWidgetHoverTarget), /*#__PURE__*/React__namespace.createElement("span", null, children));
|
|
17968
17929
|
}
|
|
17969
17930
|
|
|
17970
17931
|
if (inline) {
|
|
17971
17932
|
return /*#__PURE__*/React__namespace.createElement("span", {
|
|
17972
|
-
className: aphrodite.css(styles$
|
|
17973
|
-
}, this.renderLink(styles$
|
|
17933
|
+
className: aphrodite.css(styles$A.lintContainer)
|
|
17934
|
+
}, this.renderLink(styles$A.inlineHoverTarget), /*#__PURE__*/React__namespace.createElement("span", null, children));
|
|
17974
17935
|
}
|
|
17975
17936
|
|
|
17976
17937
|
return /*#__PURE__*/React__namespace.createElement("div", {
|
|
17977
|
-
className: aphrodite.css(styles$
|
|
17978
|
-
}, this.renderLink(styles$
|
|
17938
|
+
className: aphrodite.css(styles$A.lintContainer)
|
|
17939
|
+
}, this.renderLink(styles$A.hoverTarget), /*#__PURE__*/React__namespace.createElement("div", null, children));
|
|
17979
17940
|
}
|
|
17980
17941
|
|
|
17981
17942
|
} // $FlowFixMe[prop-missing]
|
|
@@ -18000,7 +17961,7 @@ _defineProperty(Lint, "propTypes", {
|
|
|
18000
17961
|
severity: PropTypes__default["default"].number
|
|
18001
17962
|
});
|
|
18002
17963
|
|
|
18003
|
-
const styles$
|
|
17964
|
+
const styles$A = aphrodite.StyleSheet.create({
|
|
18004
17965
|
// This is the class of the outermost element.
|
|
18005
17966
|
// We use relative positioning so that the lint indicator can be
|
|
18006
17967
|
// positioned absolutely relative to the position of the linty container.
|
|
@@ -19893,7 +19854,7 @@ class Renderer extends React__namespace.Component {
|
|
|
19893
19854
|
paddingRight: margin
|
|
19894
19855
|
};
|
|
19895
19856
|
const mobileInnerStyle = { ...innerStyle,
|
|
19896
|
-
...styles$
|
|
19857
|
+
...styles$z.mobileZoomableParentFix
|
|
19897
19858
|
};
|
|
19898
19859
|
return /*#__PURE__*/React__namespace.createElement("div", {
|
|
19899
19860
|
key: state.key,
|
|
@@ -20033,7 +19994,7 @@ class Renderer extends React__namespace.Component {
|
|
|
20033
19994
|
paddingRight: 0
|
|
20034
19995
|
};
|
|
20035
19996
|
const mobileInnerStyle = { ...innerStyle,
|
|
20036
|
-
...styles$
|
|
19997
|
+
...styles$z.mobileZoomableParentFix
|
|
20037
19998
|
};
|
|
20038
19999
|
const wrappedOutput = /*#__PURE__*/React__namespace.createElement("div", {
|
|
20039
20000
|
style: { ...mobileInnerStyle,
|
|
@@ -20714,7 +20675,7 @@ _defineProperty(Renderer, "defaultProps", {
|
|
|
20714
20675
|
linterContext: PerseusLinter__namespace.linterContextDefault
|
|
20715
20676
|
});
|
|
20716
20677
|
|
|
20717
|
-
const styles$
|
|
20678
|
+
const styles$z = {
|
|
20718
20679
|
mobileZoomableParentFix: {
|
|
20719
20680
|
// NOTE(abdul): There is an issue where transform animations will
|
|
20720
20681
|
// cause the Zoomable component to disappear when running on the
|
|
@@ -22888,35 +22849,36 @@ _defineProperty(PossibleAnswers, "propTypes", {
|
|
|
22888
22849
|
answers: PropTypes__default["default"].arrayOf(PropTypes__default["default"].string)
|
|
22889
22850
|
});
|
|
22890
22851
|
|
|
22891
|
-
|
|
22892
|
-
|
|
22893
|
-
|
|
22894
|
-
|
|
22895
|
-
|
|
22896
|
-
|
|
22897
|
-
|
|
22898
|
-
componentDidMount
|
|
22852
|
+
class SimpleKeypadInput extends React__namespace.Component {
|
|
22853
|
+
constructor() {
|
|
22854
|
+
super(...arguments);
|
|
22855
|
+
|
|
22856
|
+
_defineProperty(this, "_isMounted", false);
|
|
22857
|
+
}
|
|
22858
|
+
|
|
22859
|
+
componentDidMount() {
|
|
22899
22860
|
// TODO(scottgrant): This is a hack to remove the deprecated call to
|
|
22900
22861
|
// this.isMounted() but is still considered an anti-pattern.
|
|
22901
22862
|
this._isMounted = true;
|
|
22902
|
-
}
|
|
22903
|
-
|
|
22863
|
+
}
|
|
22864
|
+
|
|
22865
|
+
componentWillUnmount() {
|
|
22904
22866
|
this._isMounted = false;
|
|
22905
|
-
}
|
|
22867
|
+
}
|
|
22906
22868
|
|
|
22907
22869
|
focus() {
|
|
22908
22870
|
// $FlowFixMe[object-this-reference]
|
|
22909
22871
|
this.refs.input.focus(); // eslint-disable-line react/no-string-refs
|
|
22910
|
-
}
|
|
22872
|
+
}
|
|
22911
22873
|
|
|
22912
22874
|
blur() {
|
|
22913
22875
|
// $FlowFixMe[object-this-reference]
|
|
22914
22876
|
this.refs.input.blur(); // eslint-disable-line react/no-string-refs
|
|
22915
|
-
}
|
|
22877
|
+
}
|
|
22916
22878
|
|
|
22917
|
-
getValue
|
|
22879
|
+
getValue() {
|
|
22918
22880
|
return this.props.value;
|
|
22919
|
-
}
|
|
22881
|
+
}
|
|
22920
22882
|
|
|
22921
22883
|
render() {
|
|
22922
22884
|
// $FlowFixMe[object-this-reference]
|
|
@@ -22953,7 +22915,12 @@ const SimpleKeypadInput = createReactClass__default["default"]({
|
|
|
22953
22915
|
}, rest));
|
|
22954
22916
|
}
|
|
22955
22917
|
|
|
22956
|
-
}
|
|
22918
|
+
}
|
|
22919
|
+
SimpleKeypadInput.propTypes = {
|
|
22920
|
+
keypadElement: mathInput.keypadElementPropType,
|
|
22921
|
+
onFocus: PropTypes__default["default"].func,
|
|
22922
|
+
value: PropTypes__default["default"].oneOfType([PropTypes__default["default"].string, PropTypes__default["default"].number])
|
|
22923
|
+
};
|
|
22957
22924
|
|
|
22958
22925
|
const ParseTex$1 = TexWrangler.parseTex;
|
|
22959
22926
|
const answerTypes = {
|
|
@@ -23208,9 +23175,13 @@ class InputNumber extends React__namespace.Component {
|
|
|
23208
23175
|
|
|
23209
23176
|
if (rubric.answerType == null) {
|
|
23210
23177
|
rubric.answerType = "number";
|
|
23211
|
-
}
|
|
23178
|
+
} // note(matthewc): this will get immediately parsed again by
|
|
23179
|
+
// `KhanAnswerTypes.number.convertToPredicate`, but a string is
|
|
23180
|
+
// expected here
|
|
23181
|
+
|
|
23212
23182
|
|
|
23213
|
-
const
|
|
23183
|
+
const stringValue = "".concat(rubric.value);
|
|
23184
|
+
const val = KhanAnswerTypes.number.createValidatorFunctional(stringValue, {
|
|
23214
23185
|
simplify: rubric.simplify,
|
|
23215
23186
|
inexact: rubric.inexact || undefined,
|
|
23216
23187
|
maxError: rubric.maxError,
|
|
@@ -23336,6 +23307,8 @@ class NumericInput extends React__namespace.Component {
|
|
|
23336
23307
|
constructor() {
|
|
23337
23308
|
super(...arguments);
|
|
23338
23309
|
|
|
23310
|
+
_defineProperty(this, "inputRef", void 0);
|
|
23311
|
+
|
|
23339
23312
|
_defineProperty(this, "state", {
|
|
23340
23313
|
// keeps track of the other set of values when switching
|
|
23341
23314
|
// between 0 and finite solutions
|
|
@@ -23354,9 +23327,8 @@ class NumericInput extends React__namespace.Component {
|
|
|
23354
23327
|
correct = score.type === "points" && score.earned === score.total;
|
|
23355
23328
|
|
|
23356
23329
|
if (!correct) {
|
|
23357
|
-
const correctAnswers =
|
|
23358
|
-
|
|
23359
|
-
const answerStrings = ___default["default"].map(correctAnswers, answer => {
|
|
23330
|
+
const correctAnswers = rubric.answers.filter(answer => answer.status === "correct");
|
|
23331
|
+
const answerStrings = correctAnswers.map(answer => {
|
|
23360
23332
|
// Figure out how this answer is supposed to be
|
|
23361
23333
|
// displayed
|
|
23362
23334
|
let format = "decimal";
|
|
@@ -23377,7 +23349,6 @@ class NumericInput extends React__namespace.Component {
|
|
|
23377
23349
|
|
|
23378
23350
|
return answerString;
|
|
23379
23351
|
});
|
|
23380
|
-
|
|
23381
23352
|
answerBlurb = /*#__PURE__*/React__namespace.createElement(PossibleAnswers, {
|
|
23382
23353
|
answers: answerStrings
|
|
23383
23354
|
});
|
|
@@ -23391,7 +23362,7 @@ class NumericInput extends React__namespace.Component {
|
|
|
23391
23362
|
var _this$props$answerFor;
|
|
23392
23363
|
|
|
23393
23364
|
// if the set of specified forms are empty, allow all forms
|
|
23394
|
-
const forms = ((_this$props$answerFor = this.props.answerForms) === null || _this$props$answerFor === void 0 ? void 0 : _this$props$answerFor.length) !== 0 ? this.props.answerForms :
|
|
23365
|
+
const forms = ((_this$props$answerFor = this.props.answerForms) === null || _this$props$answerFor === void 0 ? void 0 : _this$props$answerFor.length) !== 0 ? this.props.answerForms : Object.keys(formExamples).map(name => {
|
|
23395
23366
|
return {
|
|
23396
23367
|
name: name,
|
|
23397
23368
|
simplify: "required"
|
|
@@ -23417,8 +23388,7 @@ class NumericInput extends React__namespace.Component {
|
|
|
23417
23388
|
|
|
23418
23389
|
const answerFormNames = ___default["default"].uniq((_this$props$answerFor3 = this.props.answerForms) === null || _this$props$answerFor3 === void 0 ? void 0 : _this$props$answerFor3.map(form => form.name));
|
|
23419
23390
|
|
|
23420
|
-
const allFormsAccepted = answerFormNames.length >=
|
|
23421
|
-
|
|
23391
|
+
const allFormsAccepted = answerFormNames.length >= Object.keys(formExamples).length;
|
|
23422
23392
|
return !noFormsAccepted && !allFormsAccepted;
|
|
23423
23393
|
});
|
|
23424
23394
|
|
|
@@ -23427,20 +23397,22 @@ class NumericInput extends React__namespace.Component {
|
|
|
23427
23397
|
});
|
|
23428
23398
|
|
|
23429
23399
|
_defineProperty(this, "focus", () => {
|
|
23430
|
-
|
|
23431
|
-
|
|
23400
|
+
var _this$inputRef;
|
|
23401
|
+
|
|
23402
|
+
(_this$inputRef = this.inputRef) === null || _this$inputRef === void 0 ? void 0 : _this$inputRef.focus();
|
|
23432
23403
|
return true;
|
|
23433
23404
|
});
|
|
23434
23405
|
|
|
23435
|
-
_defineProperty(this, "focusInputPath",
|
|
23436
|
-
|
|
23437
|
-
|
|
23438
|
-
this.
|
|
23406
|
+
_defineProperty(this, "focusInputPath", () => {
|
|
23407
|
+
var _this$inputRef2;
|
|
23408
|
+
|
|
23409
|
+
(_this$inputRef2 = this.inputRef) === null || _this$inputRef2 === void 0 ? void 0 : _this$inputRef2.focus();
|
|
23439
23410
|
});
|
|
23440
23411
|
|
|
23441
|
-
_defineProperty(this, "blurInputPath",
|
|
23442
|
-
|
|
23443
|
-
|
|
23412
|
+
_defineProperty(this, "blurInputPath", () => {
|
|
23413
|
+
var _this$inputRef3;
|
|
23414
|
+
|
|
23415
|
+
(_this$inputRef3 = this.inputRef) === null || _this$inputRef3 === void 0 ? void 0 : _this$inputRef3.blur();
|
|
23444
23416
|
});
|
|
23445
23417
|
|
|
23446
23418
|
_defineProperty(this, "getInputPaths", () => {
|
|
@@ -23508,9 +23480,8 @@ class NumericInput extends React__namespace.Component {
|
|
|
23508
23480
|
}
|
|
23509
23481
|
|
|
23510
23482
|
static getOneCorrectAnswerFromRubric(rubric) {
|
|
23511
|
-
const correctAnswers =
|
|
23512
|
-
|
|
23513
|
-
const answerStrings = ___default["default"].map(correctAnswers, answer => {
|
|
23483
|
+
const correctAnswers = rubric.answers.filter(answer => answer.status === "correct");
|
|
23484
|
+
const answerStrings = correctAnswers.map(answer => {
|
|
23514
23485
|
// Figure out how this answer is supposed to be
|
|
23515
23486
|
// displayed
|
|
23516
23487
|
let format = "decimal";
|
|
@@ -23540,31 +23511,30 @@ class NumericInput extends React__namespace.Component {
|
|
|
23540
23511
|
}
|
|
23541
23512
|
|
|
23542
23513
|
static validate(useInput, rubric) {
|
|
23543
|
-
const allAnswerForms =
|
|
23544
|
-
|
|
23545
|
-
const createValidator = answer =>
|
|
23546
|
-
|
|
23547
|
-
|
|
23548
|
-
|
|
23549
|
-
|
|
23550
|
-
|
|
23551
|
-
|
|
23552
|
-
|
|
23514
|
+
const allAnswerForms = answerFormButtons.map(e => e["value"]);
|
|
23515
|
+
|
|
23516
|
+
const createValidator = answer => {
|
|
23517
|
+
const stringAnswer = "".concat(answer.value);
|
|
23518
|
+
return KhanAnswerTypes.number.createValidatorFunctional(stringAnswer, {
|
|
23519
|
+
message: answer.message,
|
|
23520
|
+
simplify: answer.status === "correct" ? answer.simplify : "optional",
|
|
23521
|
+
inexact: true,
|
|
23522
|
+
// TODO(merlob) backfill / delete
|
|
23523
|
+
maxError: answer.maxError,
|
|
23524
|
+
forms: answer.strict && answer.answerForms && answer.answerForms.length !== 0 ? answer.answerForms : allAnswerForms
|
|
23525
|
+
});
|
|
23526
|
+
}; // We may have received TeX; try to parse it before grading.
|
|
23553
23527
|
// If `currentValue` is not TeX, this should be a no-op.
|
|
23554
23528
|
|
|
23555
23529
|
|
|
23556
23530
|
const currentValue = ParseTex(useInput.currentValue);
|
|
23557
|
-
|
|
23558
|
-
const correctAnswers = ___default["default"].where(rubric.answers, {
|
|
23559
|
-
status: "correct"
|
|
23560
|
-
});
|
|
23561
|
-
|
|
23531
|
+
const correctAnswers = rubric.answers.filter(answer => answer.status === "correct");
|
|
23562
23532
|
const normalizedAnswerExpected = correctAnswers.every(answer => Math.abs(answer.value) <= 1); // Look through all correct answers for one that matches either
|
|
23563
23533
|
// precisely or approximately and return the appropriate message:
|
|
23564
23534
|
// - if precise, return the message that the answer came with
|
|
23565
23535
|
// - if it needs to be simplified, etc., show that message
|
|
23566
23536
|
|
|
23567
|
-
let result =
|
|
23537
|
+
let result = correctAnswers.map(answer => {
|
|
23568
23538
|
// The coefficient is an attribute of the widget
|
|
23569
23539
|
let localValue = currentValue;
|
|
23570
23540
|
|
|
@@ -23578,22 +23548,17 @@ class NumericInput extends React__namespace.Component {
|
|
|
23578
23548
|
|
|
23579
23549
|
const validate = createValidator(answer);
|
|
23580
23550
|
return validate(maybeParsePercentInput(localValue, normalizedAnswerExpected));
|
|
23581
|
-
})
|
|
23551
|
+
}).find(match => match.correct || match.empty);
|
|
23582
23552
|
|
|
23583
23553
|
if (!result) {
|
|
23584
23554
|
// Otherwise, if the guess is not correct
|
|
23585
|
-
const otherAnswers = [].concat(
|
|
23586
|
-
status: "ungraded"
|
|
23587
|
-
}), ___default["default"].where(rubric.answers, {
|
|
23588
|
-
status: "wrong"
|
|
23589
|
-
})); // Look through all other answers and if one matches either
|
|
23555
|
+
const otherAnswers = [].concat(rubric.answers.filter(answer => answer.status === "ungraded"), rubric.answers.filter(answer => answer.status === "wrong")); // Look through all other answers and if one matches either
|
|
23590
23556
|
// precisely or approximately return the answer's message
|
|
23591
23557
|
|
|
23592
|
-
const match =
|
|
23558
|
+
const match = otherAnswers.find(answer => {
|
|
23593
23559
|
const validate = createValidator(answer);
|
|
23594
23560
|
return validate(maybeParsePercentInput(currentValue, normalizedAnswerExpected)).correct;
|
|
23595
23561
|
});
|
|
23596
|
-
|
|
23597
23562
|
result = {
|
|
23598
23563
|
empty: match ? match.status === "ungraded" : false,
|
|
23599
23564
|
correct: match ? match.status === "correct" : false,
|
|
@@ -23640,9 +23605,8 @@ class NumericInput extends React__namespace.Component {
|
|
|
23640
23605
|
|
|
23641
23606
|
if (this.props.apiOptions.customKeypad) {
|
|
23642
23607
|
// TODO(charlie): Support "Review Mode".
|
|
23643
|
-
return maybeRightAlignKeypadInput( /*#__PURE__*/React__namespace.createElement(SimpleKeypadInput
|
|
23644
|
-
|
|
23645
|
-
ref: "input",
|
|
23608
|
+
return maybeRightAlignKeypadInput( /*#__PURE__*/React__namespace.createElement(SimpleKeypadInput, {
|
|
23609
|
+
ref: ref => this.inputRef = ref,
|
|
23646
23610
|
value: this.props.currentValue,
|
|
23647
23611
|
keypadElement: this.props.keypadElement,
|
|
23648
23612
|
onChange: this.handleChange,
|
|
@@ -23651,9 +23615,8 @@ class NumericInput extends React__namespace.Component {
|
|
|
23651
23615
|
}));
|
|
23652
23616
|
}
|
|
23653
23617
|
|
|
23654
|
-
const input = /*#__PURE__*/React__namespace.createElement(InputWithExamples
|
|
23655
|
-
|
|
23656
|
-
ref: "input",
|
|
23618
|
+
const input = /*#__PURE__*/React__namespace.createElement(InputWithExamples, {
|
|
23619
|
+
ref: ref => this.inputRef = ref,
|
|
23657
23620
|
value: this.props.currentValue,
|
|
23658
23621
|
onChange: this.handleChange,
|
|
23659
23622
|
className: classNames__default["default"](classes),
|
|
@@ -23720,7 +23683,7 @@ const unionAnswerForms = function (answerFormsList) {
|
|
|
23720
23683
|
// two elements are equal, and returns a list of unique elements. This is
|
|
23721
23684
|
// just a helper function here, but works generally.
|
|
23722
23685
|
const uniqueBy = function (list, iteratee) {
|
|
23723
|
-
return
|
|
23686
|
+
return list.reduce((uniqueList, element) => {
|
|
23724
23687
|
// For each element, decide whether it's already in the list of
|
|
23725
23688
|
// unique items.
|
|
23726
23689
|
const inList = ___default["default"].find(uniqueList, iteratee.bind(null, element));
|
|
@@ -23734,13 +23697,13 @@ const unionAnswerForms = function (answerFormsList) {
|
|
|
23734
23697
|
}; // Pull out all of the forms from the different lists.
|
|
23735
23698
|
|
|
23736
23699
|
|
|
23737
|
-
const allForms =
|
|
23738
|
-
|
|
23700
|
+
const allForms = answerFormsList.flat(); // Pull out the unique forms using uniqueBy.
|
|
23739
23701
|
|
|
23740
23702
|
const uniqueForms = uniqueBy(allForms, ___default["default"].isEqual); // Sort them by the order they appear in the `formExamples` list.
|
|
23741
23703
|
|
|
23704
|
+
const formExampleKeys = Object.keys(formExamples);
|
|
23742
23705
|
return ___default["default"].sortBy(uniqueForms, form => {
|
|
23743
|
-
return
|
|
23706
|
+
return formExampleKeys.indexOf(form.name);
|
|
23744
23707
|
});
|
|
23745
23708
|
};
|
|
23746
23709
|
// This function checks if the user inputted a percent value, parsing
|
|
@@ -23749,7 +23712,7 @@ const unionAnswerForms = function (answerFormsList) {
|
|
|
23749
23712
|
// can accept several input forms with or without "%", the decision
|
|
23750
23713
|
// to parse based on the presence of "%" in the input, is so that we
|
|
23751
23714
|
// don't accidently scale the user typed value before grading, CP-930.
|
|
23752
|
-
|
|
23715
|
+
function maybeParsePercentInput(inputValue, normalizedAnswerExpected) {
|
|
23753
23716
|
// If the input value is not a string ending with "%", then there's
|
|
23754
23717
|
// nothing more to do. The value will be graded as inputted by user.
|
|
23755
23718
|
if (!(typeof inputValue === "string" && inputValue.endsWith("%"))) {
|
|
@@ -23774,13 +23737,13 @@ const maybeParsePercentInput = (inputValue, normalizedAnswerExpected) => {
|
|
|
23774
23737
|
|
|
23775
23738
|
|
|
23776
23739
|
return value;
|
|
23777
|
-
}
|
|
23740
|
+
}
|
|
23778
23741
|
|
|
23779
23742
|
const propsTransform = function (widgetOptions) {
|
|
23780
23743
|
const rendererProps = ___default["default"].extend(___default["default"].omit(widgetOptions, "answers"), {
|
|
23781
23744
|
answerForms: unionAnswerForms( // Pull out the name of each form and whether that form has
|
|
23782
23745
|
// required simplification.
|
|
23783
|
-
|
|
23746
|
+
widgetOptions.answers.map(answer => {
|
|
23784
23747
|
return ___default["default"].map(answer.answerForms, form => {
|
|
23785
23748
|
return {
|
|
23786
23749
|
simplify: answer.simplify,
|
|
@@ -24016,7 +23979,7 @@ var mediaQueries = {
|
|
|
24016
23979
|
const {
|
|
24017
23980
|
zIndexAboveScratchpad,
|
|
24018
23981
|
zIndexInteractiveComponent,
|
|
24019
|
-
radioBorderColor
|
|
23982
|
+
radioBorderColor,
|
|
24020
23983
|
checkedColor,
|
|
24021
23984
|
circleSize,
|
|
24022
23985
|
radioMarginWidth
|
|
@@ -24071,7 +24034,7 @@ var sharedStyles = aphrodite.StyleSheet.create({
|
|
|
24071
24034
|
},
|
|
24072
24035
|
backgroundColor: "#fff",
|
|
24073
24036
|
border: "2px solid #fff",
|
|
24074
|
-
boxShadow: "0 0px 0px 1px ".concat(radioBorderColor
|
|
24037
|
+
boxShadow: "0 0px 0px 1px ".concat(radioBorderColor),
|
|
24075
24038
|
outline: "none",
|
|
24076
24039
|
boxSizing: "border-box",
|
|
24077
24040
|
flexShrink: 0,
|
|
@@ -24323,35 +24286,65 @@ _defineProperty(Icon, "defaultProps", {
|
|
|
24323
24286
|
color: "currentColor"
|
|
24324
24287
|
});
|
|
24325
24288
|
|
|
24326
|
-
|
|
24327
|
-
|
|
24328
|
-
|
|
24329
|
-
|
|
24330
|
-
|
|
24331
|
-
|
|
24332
|
-
|
|
24333
|
-
|
|
24334
|
-
|
|
24335
|
-
|
|
24289
|
+
function getA11yText(letter, checked, correct, crossedOut, showCorrectness) {
|
|
24290
|
+
// There are two pieces of metadata we want to add to each a11yText:
|
|
24291
|
+
// whether the answer was checked/crossed-out/neither, and whether the
|
|
24292
|
+
// answer is correct/incorrect/not-yet-revealed.
|
|
24293
|
+
//
|
|
24294
|
+
// Translation is tricky for cross-product situations like this, so
|
|
24295
|
+
// we've just enumerated all 9 possibilities as separate strings.
|
|
24296
|
+
if (showCorrectness && correct) {
|
|
24297
|
+
if (checked) {
|
|
24298
|
+
return i18n__namespace._("(Choice %(letter)s, Checked, Correct)", {
|
|
24299
|
+
letter
|
|
24300
|
+
});
|
|
24301
|
+
}
|
|
24302
|
+
|
|
24303
|
+
if (crossedOut) {
|
|
24304
|
+
return i18n__namespace._("(Choice %(letter)s, Crossed out, Correct)", {
|
|
24305
|
+
letter
|
|
24306
|
+
});
|
|
24307
|
+
}
|
|
24308
|
+
|
|
24309
|
+
return i18n__namespace._("(Choice %(letter)s, Correct)", {
|
|
24310
|
+
letter
|
|
24311
|
+
});
|
|
24336
24312
|
}
|
|
24337
24313
|
|
|
24338
|
-
|
|
24314
|
+
if (showCorrectness && !correct) {
|
|
24315
|
+
if (checked) {
|
|
24316
|
+
return i18n__namespace._("(Choice %(letter)s, Checked, Incorrect)", {
|
|
24317
|
+
letter
|
|
24318
|
+
});
|
|
24319
|
+
}
|
|
24339
24320
|
|
|
24340
|
-
|
|
24341
|
-
|
|
24342
|
-
|
|
24343
|
-
});
|
|
24321
|
+
if (crossedOut) {
|
|
24322
|
+
return i18n__namespace._("(Choice %(letter)s, Crossed out, Incorrect)", {
|
|
24323
|
+
letter
|
|
24324
|
+
});
|
|
24325
|
+
}
|
|
24344
24326
|
|
|
24345
|
-
|
|
24346
|
-
|
|
24347
|
-
|
|
24348
|
-
display: "inline-block",
|
|
24349
|
-
borderRadius: "50%",
|
|
24350
|
-
borderWidth: 2,
|
|
24351
|
-
padding: 2,
|
|
24352
|
-
borderStyle: "solid"
|
|
24327
|
+
return i18n__namespace._("(Choice %(letter)s, Incorrect)", {
|
|
24328
|
+
letter
|
|
24329
|
+
});
|
|
24353
24330
|
}
|
|
24354
|
-
|
|
24331
|
+
|
|
24332
|
+
if (checked) {
|
|
24333
|
+
return i18n__namespace._("(Choice %(letter)s, Checked)", {
|
|
24334
|
+
letter
|
|
24335
|
+
});
|
|
24336
|
+
}
|
|
24337
|
+
|
|
24338
|
+
if (crossedOut) {
|
|
24339
|
+
return i18n__namespace._("(Choice %(letter)s, Crossed out)", {
|
|
24340
|
+
letter
|
|
24341
|
+
});
|
|
24342
|
+
}
|
|
24343
|
+
|
|
24344
|
+
return i18n__namespace._("(Choice %(letter)s)", {
|
|
24345
|
+
letter
|
|
24346
|
+
});
|
|
24347
|
+
}
|
|
24355
24348
|
|
|
24356
24349
|
/**
|
|
24357
24350
|
* Given a choice's position in the radio widget, return the corresponding
|
|
@@ -24383,179 +24376,57 @@ function getChoiceLetter(pos) {
|
|
|
24383
24376
|
return " ";
|
|
24384
24377
|
}
|
|
24385
24378
|
|
|
24386
|
-
|
|
24387
|
-
|
|
24388
|
-
|
|
24389
|
-
// TODO(amy): figure out a better scheme for specifying these
|
|
24390
|
-
// styles that isn't such a pain to grok. See some neat ideas
|
|
24391
|
-
// from MDR in https://phabricator.khanacademy.org/D35249.
|
|
24392
|
-
constructStyles(reviewMode, correct, checked) {
|
|
24393
|
-
let backgroundColor;
|
|
24394
|
-
let borderColor = satBlue;
|
|
24395
|
-
let color = satBlue;
|
|
24396
|
-
|
|
24397
|
-
if (reviewMode) {
|
|
24398
|
-
if (correct) {
|
|
24399
|
-
borderColor = satCorrectColor;
|
|
24400
|
-
color = checked ? white$1 : satCorrectColor;
|
|
24401
|
-
backgroundColor = checked ? satCorrectColor : white$1;
|
|
24402
|
-
} else if (checked) {
|
|
24403
|
-
borderColor = satIncorrectColor;
|
|
24404
|
-
color = white$1;
|
|
24405
|
-
backgroundColor = satIncorrectColor;
|
|
24406
|
-
}
|
|
24407
|
-
} else if (checked) {
|
|
24408
|
-
color = white$1;
|
|
24409
|
-
backgroundColor = satBlue;
|
|
24410
|
-
}
|
|
24411
|
-
|
|
24412
|
-
return {
|
|
24413
|
-
color,
|
|
24414
|
-
backgroundColor,
|
|
24415
|
-
borderColor
|
|
24416
|
-
};
|
|
24417
|
-
}
|
|
24418
|
-
|
|
24419
|
-
render() {
|
|
24420
|
-
const {
|
|
24421
|
-
letter,
|
|
24422
|
-
a11yText,
|
|
24423
|
-
reviewMode,
|
|
24424
|
-
checked,
|
|
24425
|
-
correct,
|
|
24426
|
-
crossedOut
|
|
24427
|
-
} = this.props;
|
|
24428
|
-
const {
|
|
24429
|
-
color,
|
|
24430
|
-
backgroundColor,
|
|
24431
|
-
borderColor
|
|
24432
|
-
} = this.constructStyles(reviewMode, correct, checked);
|
|
24433
|
-
return /*#__PURE__*/React__namespace.createElement("div", {
|
|
24434
|
-
className: aphrodite.css(styles$t.iconWrapper)
|
|
24435
|
-
}, /*#__PURE__*/React__namespace.createElement("div", {
|
|
24436
|
-
className: aphrodite.css(styles$t.satCircle),
|
|
24437
|
-
style: {
|
|
24438
|
-
backgroundColor,
|
|
24439
|
-
borderColor
|
|
24440
|
-
}
|
|
24441
|
-
}), /*#__PURE__*/React__namespace.createElement("div", {
|
|
24442
|
-
style: {
|
|
24443
|
-
color
|
|
24444
|
-
},
|
|
24445
|
-
className: aphrodite.css(styles$t.letter)
|
|
24446
|
-
}, /*#__PURE__*/React__namespace.createElement("span", {
|
|
24447
|
-
className: "perseus-sr-only"
|
|
24448
|
-
}, a11yText), /*#__PURE__*/React__namespace.createElement("span", {
|
|
24449
|
-
"aria-hidden": "true"
|
|
24450
|
-
}, letter)), crossedOut && /*#__PURE__*/React__namespace.createElement(CrossOutLine, {
|
|
24451
|
-
color: borderColor,
|
|
24452
|
-
sat: true
|
|
24453
|
-
}));
|
|
24454
|
-
}
|
|
24379
|
+
/**
|
|
24380
|
+
* Renders a circular selection ring around the child.
|
|
24381
|
+
*/
|
|
24455
24382
|
|
|
24383
|
+
function FocusRing(props) {
|
|
24384
|
+
const {
|
|
24385
|
+
visible,
|
|
24386
|
+
color,
|
|
24387
|
+
children,
|
|
24388
|
+
multipleSelect
|
|
24389
|
+
} = props;
|
|
24390
|
+
const borderColor = visible ? color : "transparent";
|
|
24391
|
+
const borderRadius = multipleSelect ? 5 : "50%";
|
|
24392
|
+
const style = {
|
|
24393
|
+
borderColor,
|
|
24394
|
+
borderRadius
|
|
24395
|
+
};
|
|
24396
|
+
return /*#__PURE__*/React__namespace.createElement("span", {
|
|
24397
|
+
"data-test-id": "focus-ring",
|
|
24398
|
+
className: aphrodite.css(styles$y.ring),
|
|
24399
|
+
style: style
|
|
24400
|
+
}, children);
|
|
24456
24401
|
}
|
|
24457
24402
|
|
|
24458
|
-
|
|
24459
|
-
|
|
24460
|
-
|
|
24461
|
-
|
|
24462
|
-
|
|
24463
|
-
|
|
24464
|
-
|
|
24465
|
-
|
|
24466
|
-
|
|
24467
|
-
|
|
24468
|
-
|
|
24469
|
-
|
|
24470
|
-
}
|
|
24471
|
-
|
|
24472
|
-
if (correct) {
|
|
24473
|
-
return /*#__PURE__*/React__namespace.createElement(InlineIcon, _extends({}, iconCheck, {
|
|
24474
|
-
style: {
|
|
24475
|
-
position: "relative",
|
|
24476
|
-
top: -1
|
|
24477
|
-
}
|
|
24478
|
-
}));
|
|
24479
|
-
}
|
|
24480
|
-
|
|
24481
|
-
return /*#__PURE__*/React__namespace.createElement(InlineIcon, iconMinus);
|
|
24482
|
-
} // Handle dynamic styling of the multiple choice icon. Most
|
|
24483
|
-
// MC icon styles are constant, but we do allow the caller
|
|
24484
|
-
// to specify the selected color, and thus must control styles
|
|
24485
|
-
// related to the selected state dynamically.
|
|
24486
|
-
|
|
24487
|
-
|
|
24488
|
-
getDynamicStyles() {
|
|
24489
|
-
const {
|
|
24490
|
-
checked,
|
|
24491
|
-
showCorrectness,
|
|
24492
|
-
pressed,
|
|
24493
|
-
primaryProductColor,
|
|
24494
|
-
correct,
|
|
24495
|
-
transparentBackground
|
|
24496
|
-
} = this.props;
|
|
24497
|
-
|
|
24498
|
-
if (!showCorrectness && pressed) {
|
|
24499
|
-
return {
|
|
24500
|
-
borderColor: primaryProductColor,
|
|
24501
|
-
color: primaryProductColor,
|
|
24502
|
-
backgroundColor: transparentBackground ? "transparent" : white$1
|
|
24503
|
-
};
|
|
24504
|
-
}
|
|
24505
|
-
|
|
24506
|
-
if (checked) {
|
|
24507
|
-
// Note: kaGreen is not only the default product color,
|
|
24508
|
-
// but also the "correctness" color
|
|
24509
|
-
const bg = showCorrectness && correct ? Color__default["default"].green : primaryProductColor;
|
|
24510
|
-
return {
|
|
24511
|
-
color: white$1,
|
|
24512
|
-
backgroundColor: bg,
|
|
24513
|
-
borderColor: bg
|
|
24514
|
-
};
|
|
24515
|
-
}
|
|
24516
|
-
|
|
24517
|
-
return {
|
|
24518
|
-
borderColor: Color__default["default"].offBlack64,
|
|
24519
|
-
color: Color__default["default"].offBlack64
|
|
24520
|
-
};
|
|
24403
|
+
FocusRing.defaultProps = {
|
|
24404
|
+
visible: true,
|
|
24405
|
+
color: kaGreen$1,
|
|
24406
|
+
multipleSelect: false
|
|
24407
|
+
};
|
|
24408
|
+
const styles$y = aphrodite.StyleSheet.create({
|
|
24409
|
+
ring: {
|
|
24410
|
+
margin: "auto",
|
|
24411
|
+
display: "inline-block",
|
|
24412
|
+
borderWidth: 2,
|
|
24413
|
+
padding: 2,
|
|
24414
|
+
borderStyle: "solid"
|
|
24521
24415
|
}
|
|
24416
|
+
});
|
|
24522
24417
|
|
|
24523
|
-
|
|
24524
|
-
|
|
24525
|
-
|
|
24526
|
-
|
|
24527
|
-
|
|
24528
|
-
|
|
24529
|
-
correct,
|
|
24530
|
-
focused,
|
|
24531
|
-
primaryProductColor,
|
|
24532
|
-
previouslyAnswered
|
|
24533
|
-
} = this.props;
|
|
24534
|
-
const dynamicStyles = this.getDynamicStyles();
|
|
24535
|
-
return /*#__PURE__*/React__namespace.createElement("div", {
|
|
24536
|
-
className: aphrodite.css(styles$t.iconWrapper)
|
|
24537
|
-
}, /*#__PURE__*/React__namespace.createElement(FocusRing, {
|
|
24538
|
-
color: primaryProductColor,
|
|
24539
|
-
visible: focused
|
|
24540
|
-
}, /*#__PURE__*/React__namespace.createElement("div", {
|
|
24541
|
-
style: dynamicStyles,
|
|
24542
|
-
className: aphrodite.css(styles$t.libraryCircle, showCorrectness && correct && styles$t.libraryCircleCorrect, showCorrectness && !correct && styles$t.libraryCircleIncorrect, showCorrectness && !correct && (checked || previouslyAnswered) && styles$t.libraryCircleIncorrectAnswered) // used in BaseRadio to check if we actually clicked on the
|
|
24543
|
-
// radio icon
|
|
24544
|
-
,
|
|
24545
|
-
"data-is-radio-icon": true
|
|
24546
|
-
}, /*#__PURE__*/React__namespace.createElement("div", {
|
|
24547
|
-
className: "perseus-sr-only"
|
|
24548
|
-
}, a11yText), /*#__PURE__*/React__namespace.createElement("div", {
|
|
24549
|
-
"aria-hidden": true
|
|
24550
|
-
}, this.getChoiceInner()))), crossedOut && /*#__PURE__*/React__namespace.createElement(CrossOutLine, {
|
|
24551
|
-
color: dynamicStyles.borderColor
|
|
24552
|
-
}));
|
|
24418
|
+
const SAT_ICON_SIZE = 25;
|
|
24419
|
+
const LIBRARY_ICON_SIZE = 24;
|
|
24420
|
+
const styles$x = aphrodite.StyleSheet.create({
|
|
24421
|
+
iconWrapper: {
|
|
24422
|
+
display: "inline-block",
|
|
24423
|
+
position: "relative"
|
|
24553
24424
|
}
|
|
24425
|
+
});
|
|
24554
24426
|
|
|
24555
|
-
|
|
24427
|
+
/* Component for rendering a letter icon in radio choice */
|
|
24556
24428
|
// extra space for the cute rounded line cap.
|
|
24557
24429
|
|
|
24558
|
-
|
|
24559
24430
|
const CROSS_OUT_LINE_PADDING = 2;
|
|
24560
24431
|
const CROSS_OUT_LINE_SIZE = LIBRARY_ICON_SIZE + CROSS_OUT_LINE_PADDING * 2;
|
|
24561
24432
|
const SAT_CROSS_OUT_LINE_SIZE = SAT_ICON_SIZE + CROSS_OUT_LINE_PADDING * 2;
|
|
@@ -24570,7 +24441,7 @@ function CrossOutLine(props) {
|
|
|
24570
24441
|
width: crossOutLineSize,
|
|
24571
24442
|
height: crossOutLineSize,
|
|
24572
24443
|
viewBox: "0 0 ".concat(crossOutLineSize, " ").concat(crossOutLineSize),
|
|
24573
|
-
className: aphrodite.css(styles$
|
|
24444
|
+
className: aphrodite.css(styles$w.crossOutLine)
|
|
24574
24445
|
}, /*#__PURE__*/React__namespace.createElement("line", {
|
|
24575
24446
|
// The line stretches from the bottom-left to top-right.
|
|
24576
24447
|
// We don't quite go to the _very_ corner, because the cute
|
|
@@ -24585,151 +24456,124 @@ function CrossOutLine(props) {
|
|
|
24585
24456
|
}));
|
|
24586
24457
|
}
|
|
24587
24458
|
|
|
24588
|
-
|
|
24589
|
-
|
|
24590
|
-
|
|
24591
|
-
|
|
24592
|
-
|
|
24593
|
-
|
|
24594
|
-
|
|
24595
|
-
|
|
24596
|
-
// whether the answer was checked/crossed-out/neither, and whether the
|
|
24597
|
-
// answer is correct/incorrect/not-yet-revealed.
|
|
24598
|
-
//
|
|
24599
|
-
// Translation is tricky for cross-product situations like this, so
|
|
24600
|
-
// we've just enumerated all 9 possibilities as separate strings.
|
|
24601
|
-
|
|
24602
|
-
if (showCorrectness && correct) {
|
|
24603
|
-
if (checked) {
|
|
24604
|
-
return i18n__namespace._("(Choice %(letter)s, Checked, Correct)", {
|
|
24605
|
-
letter
|
|
24606
|
-
});
|
|
24607
|
-
}
|
|
24608
|
-
|
|
24609
|
-
if (crossedOut) {
|
|
24610
|
-
return i18n__namespace._("(Choice %(letter)s, Crossed out, Correct)", {
|
|
24611
|
-
letter
|
|
24612
|
-
});
|
|
24613
|
-
}
|
|
24459
|
+
const styles$w = aphrodite.StyleSheet.create({
|
|
24460
|
+
crossOutLine: {
|
|
24461
|
+
// Center the icon within the container.
|
|
24462
|
+
position: "absolute",
|
|
24463
|
+
top: "calc(50% - ".concat(CROSS_OUT_LINE_SIZE / 2, "px)"),
|
|
24464
|
+
left: "calc(50% - ".concat(CROSS_OUT_LINE_SIZE / 2, "px)")
|
|
24465
|
+
}
|
|
24466
|
+
});
|
|
24614
24467
|
|
|
24615
|
-
|
|
24616
|
-
|
|
24617
|
-
|
|
24618
|
-
|
|
24468
|
+
function ChoiceInner(props) {
|
|
24469
|
+
const {
|
|
24470
|
+
letter,
|
|
24471
|
+
showCorrectness,
|
|
24472
|
+
correct
|
|
24473
|
+
} = props;
|
|
24619
24474
|
|
|
24620
|
-
|
|
24621
|
-
|
|
24622
|
-
|
|
24623
|
-
letter
|
|
24624
|
-
});
|
|
24625
|
-
}
|
|
24475
|
+
if (!showCorrectness) {
|
|
24476
|
+
return letter;
|
|
24477
|
+
}
|
|
24626
24478
|
|
|
24627
|
-
|
|
24628
|
-
|
|
24629
|
-
|
|
24630
|
-
|
|
24479
|
+
if (correct) {
|
|
24480
|
+
return /*#__PURE__*/React__namespace.createElement(InlineIcon, _extends({}, iconCheck, {
|
|
24481
|
+
style: {
|
|
24482
|
+
position: "relative",
|
|
24483
|
+
top: -1
|
|
24631
24484
|
}
|
|
24632
|
-
|
|
24633
|
-
return i18n__namespace._("(Choice %(letter)s, Incorrect)", {
|
|
24634
|
-
letter
|
|
24635
|
-
});
|
|
24636
|
-
}
|
|
24637
|
-
|
|
24638
|
-
if (checked) {
|
|
24639
|
-
return i18n__namespace._("(Choice %(letter)s, Checked)", {
|
|
24640
|
-
letter
|
|
24641
|
-
});
|
|
24642
|
-
}
|
|
24643
|
-
|
|
24644
|
-
if (crossedOut) {
|
|
24645
|
-
return i18n__namespace._("(Choice %(letter)s, Crossed out)", {
|
|
24646
|
-
letter
|
|
24647
|
-
});
|
|
24648
|
-
}
|
|
24649
|
-
|
|
24650
|
-
return i18n__namespace._("(Choice %(letter)s)", {
|
|
24651
|
-
letter
|
|
24652
|
-
});
|
|
24485
|
+
}));
|
|
24653
24486
|
}
|
|
24654
24487
|
|
|
24655
|
-
|
|
24656
|
-
|
|
24657
|
-
|
|
24658
|
-
|
|
24659
|
-
|
|
24660
|
-
|
|
24661
|
-
|
|
24662
|
-
|
|
24663
|
-
|
|
24664
|
-
|
|
24665
|
-
|
|
24666
|
-
|
|
24667
|
-
|
|
24668
|
-
|
|
24669
|
-
|
|
24670
|
-
|
|
24488
|
+
return /*#__PURE__*/React__namespace.createElement(InlineIcon, iconMinus);
|
|
24489
|
+
} // Handle dynamic styling of the multiple choice icon. Most
|
|
24490
|
+
// MC icon styles are constant, but we do allow the caller
|
|
24491
|
+
// to specify the selected color, and thus must control styles
|
|
24492
|
+
// related to the selected state dynamically.
|
|
24493
|
+
|
|
24494
|
+
|
|
24495
|
+
function getDynamicStyles(checked, showCorrectness, pressed, multipleSelect, primaryProductColor, correct, transparentBackground) {
|
|
24496
|
+
let backgroundColor;
|
|
24497
|
+
let borderColor;
|
|
24498
|
+
let color;
|
|
24499
|
+
|
|
24500
|
+
if (!showCorrectness && pressed) {
|
|
24501
|
+
borderColor = primaryProductColor;
|
|
24502
|
+
color = primaryProductColor;
|
|
24503
|
+
backgroundColor = transparentBackground ? "transparent" : white$1;
|
|
24504
|
+
} else if (checked) {
|
|
24505
|
+
// Note: kaGreen is not only the default product color,
|
|
24506
|
+
// but also the "correctness" color
|
|
24507
|
+
const bg = showCorrectness && correct ? Color__default["default"].green : primaryProductColor;
|
|
24508
|
+
color = white$1;
|
|
24509
|
+
backgroundColor = bg;
|
|
24510
|
+
borderColor = bg;
|
|
24511
|
+
} else {
|
|
24512
|
+
borderColor = Color__default["default"].offBlack64;
|
|
24513
|
+
color = Color__default["default"].offBlack64;
|
|
24514
|
+
} // define shape
|
|
24671
24515
|
|
|
24672
|
-
if (product === "sat") {
|
|
24673
|
-
return /*#__PURE__*/React__namespace.createElement(SATChoiceIcon, {
|
|
24674
|
-
letter: letter,
|
|
24675
|
-
a11yText: this.a11yText(letter),
|
|
24676
|
-
reviewMode: reviewMode,
|
|
24677
|
-
checked: checked,
|
|
24678
|
-
correct: correct,
|
|
24679
|
-
crossedOut: crossedOut
|
|
24680
|
-
});
|
|
24681
|
-
}
|
|
24682
24516
|
|
|
24683
|
-
|
|
24684
|
-
|
|
24685
|
-
|
|
24686
|
-
|
|
24687
|
-
|
|
24688
|
-
|
|
24689
|
-
pressed: pressed,
|
|
24690
|
-
focused: focused,
|
|
24691
|
-
correct: correct,
|
|
24692
|
-
showCorrectness: showCorrectness,
|
|
24693
|
-
primaryProductColor: primaryProductColor,
|
|
24694
|
-
previouslyAnswered: previouslyAnswered,
|
|
24695
|
-
transparentBackground: transparentBackground
|
|
24696
|
-
});
|
|
24517
|
+
let borderRadius;
|
|
24518
|
+
|
|
24519
|
+
if (multipleSelect) {
|
|
24520
|
+
borderRadius = 3;
|
|
24521
|
+
} else {
|
|
24522
|
+
borderRadius = LIBRARY_ICON_SIZE;
|
|
24697
24523
|
}
|
|
24698
24524
|
|
|
24525
|
+
return {
|
|
24526
|
+
backgroundColor,
|
|
24527
|
+
borderColor,
|
|
24528
|
+
color,
|
|
24529
|
+
borderRadius
|
|
24530
|
+
};
|
|
24699
24531
|
}
|
|
24700
24532
|
|
|
24701
|
-
|
|
24702
|
-
|
|
24703
|
-
|
|
24533
|
+
function LibraryChoiceIcon(props) {
|
|
24534
|
+
const {
|
|
24535
|
+
checked,
|
|
24536
|
+
crossedOut,
|
|
24537
|
+
showCorrectness,
|
|
24538
|
+
correct,
|
|
24539
|
+
focused,
|
|
24540
|
+
hovered,
|
|
24541
|
+
multipleSelect,
|
|
24542
|
+
primaryProductColor,
|
|
24543
|
+
previouslyAnswered,
|
|
24544
|
+
letter,
|
|
24545
|
+
pressed,
|
|
24546
|
+
transparentBackground
|
|
24547
|
+
} = props;
|
|
24548
|
+
const dynamicStyles = getDynamicStyles(checked, showCorrectness, pressed, multipleSelect, primaryProductColor, correct, transparentBackground);
|
|
24549
|
+
return /*#__PURE__*/React__namespace.createElement("div", {
|
|
24550
|
+
className: aphrodite.css(styles$x.iconWrapper)
|
|
24551
|
+
}, /*#__PURE__*/React__namespace.createElement(FocusRing, {
|
|
24552
|
+
color: primaryProductColor,
|
|
24553
|
+
visible: focused || hovered,
|
|
24554
|
+
multipleSelect: multipleSelect
|
|
24555
|
+
}, /*#__PURE__*/React__namespace.createElement("div", {
|
|
24556
|
+
style: dynamicStyles,
|
|
24557
|
+
"data-test-id": "choice-icon__library-choice-icon",
|
|
24558
|
+
className: aphrodite.css(styles$v.circle, showCorrectness && correct && styles$v.circleCorrect, showCorrectness && !correct && styles$v.circleIncorrect, showCorrectness && !correct && (checked || previouslyAnswered) && styles$v.circleIncorrectAnswered) // used in BaseRadio to check if we actually clicked on the
|
|
24559
|
+
// radio icon
|
|
24560
|
+
,
|
|
24561
|
+
"data-is-radio-icon": true
|
|
24562
|
+
}, /*#__PURE__*/React__namespace.createElement(ChoiceInner, {
|
|
24563
|
+
letter: letter,
|
|
24564
|
+
showCorrectness: showCorrectness,
|
|
24565
|
+
correct: correct
|
|
24566
|
+
}))), crossedOut && /*#__PURE__*/React__namespace.createElement(CrossOutLine, {
|
|
24567
|
+
color: dynamicStyles.borderColor
|
|
24568
|
+
}));
|
|
24569
|
+
}
|
|
24704
24570
|
|
|
24705
|
-
const styles$
|
|
24706
|
-
|
|
24707
|
-
display: "block",
|
|
24708
|
-
borderRadius: SAT_ICON_SIZE,
|
|
24709
|
-
borderStyle: "solid",
|
|
24710
|
-
borderWidth: 2,
|
|
24711
|
-
content: "''",
|
|
24712
|
-
height: SAT_ICON_SIZE,
|
|
24713
|
-
width: SAT_ICON_SIZE,
|
|
24714
|
-
top: 1,
|
|
24715
|
-
left: 1
|
|
24716
|
-
},
|
|
24717
|
-
iconWrapper: {
|
|
24718
|
-
display: "inline-block",
|
|
24719
|
-
position: "relative"
|
|
24720
|
-
},
|
|
24721
|
-
crossOutLine: {
|
|
24722
|
-
// Center the icon within the container.
|
|
24723
|
-
position: "absolute",
|
|
24724
|
-
top: "calc(50% - ".concat(CROSS_OUT_LINE_SIZE / 2, "px)"),
|
|
24725
|
-
left: "calc(50% - ".concat(CROSS_OUT_LINE_SIZE / 2, "px)")
|
|
24726
|
-
},
|
|
24727
|
-
libraryCircle: {
|
|
24571
|
+
const styles$v = aphrodite.StyleSheet.create({
|
|
24572
|
+
circle: {
|
|
24728
24573
|
// Make the circle
|
|
24729
24574
|
width: LIBRARY_ICON_SIZE,
|
|
24730
24575
|
height: LIBRARY_ICON_SIZE,
|
|
24731
24576
|
boxSizing: "border-box",
|
|
24732
|
-
borderRadius: LIBRARY_ICON_SIZE,
|
|
24733
24577
|
borderStyle: "solid",
|
|
24734
24578
|
borderWidth: 2,
|
|
24735
24579
|
// The default icons have letters in them. Style those letters.
|
|
@@ -24750,18 +24594,108 @@ const styles$t = aphrodite.StyleSheet.create({
|
|
|
24750
24594
|
// pixel so it is much better centered.
|
|
24751
24595
|
lineHeight: "1px"
|
|
24752
24596
|
},
|
|
24753
|
-
|
|
24597
|
+
circleCorrect: {
|
|
24754
24598
|
fontSize: LIBRARY_ICON_SIZE
|
|
24755
24599
|
},
|
|
24756
|
-
|
|
24600
|
+
circleIncorrect: {
|
|
24757
24601
|
fontSize: LIBRARY_ICON_SIZE,
|
|
24758
24602
|
borderColor: gray68,
|
|
24759
24603
|
color: gray68
|
|
24760
24604
|
},
|
|
24761
|
-
|
|
24605
|
+
circleIncorrectAnswered: {
|
|
24762
24606
|
backgroundColor: Color__default["default"].red,
|
|
24763
24607
|
borderColor: Color__default["default"].red,
|
|
24764
24608
|
color: Color__default["default"].white
|
|
24609
|
+
}
|
|
24610
|
+
});
|
|
24611
|
+
|
|
24612
|
+
/* Component for rendering a letter icon in a SAT radio choice */
|
|
24613
|
+
|
|
24614
|
+
// TODO(amy): figure out a better scheme for specifying these
|
|
24615
|
+
// styles that isn't such a pain to grok. See some neat ideas
|
|
24616
|
+
// from MDR in https://phabricator.khanacademy.org/D35249.
|
|
24617
|
+
function constructSATStyles(reviewMode, correct, checked, multipleSelect) {
|
|
24618
|
+
let backgroundColor;
|
|
24619
|
+
let borderColor = satBlue;
|
|
24620
|
+
let color = satBlue;
|
|
24621
|
+
|
|
24622
|
+
if (reviewMode) {
|
|
24623
|
+
if (correct) {
|
|
24624
|
+
borderColor = satCorrectColor;
|
|
24625
|
+
color = checked ? white$1 : satCorrectColor;
|
|
24626
|
+
backgroundColor = checked ? satCorrectColor : white$1;
|
|
24627
|
+
} else if (checked) {
|
|
24628
|
+
borderColor = satIncorrectColor;
|
|
24629
|
+
color = white$1;
|
|
24630
|
+
backgroundColor = satIncorrectColor;
|
|
24631
|
+
}
|
|
24632
|
+
} else if (checked) {
|
|
24633
|
+
color = white$1;
|
|
24634
|
+
backgroundColor = satBlue;
|
|
24635
|
+
}
|
|
24636
|
+
|
|
24637
|
+
let borderRadius;
|
|
24638
|
+
|
|
24639
|
+
if (multipleSelect) {
|
|
24640
|
+
borderRadius = 3;
|
|
24641
|
+
} else {
|
|
24642
|
+
borderRadius = SAT_ICON_SIZE;
|
|
24643
|
+
}
|
|
24644
|
+
|
|
24645
|
+
return {
|
|
24646
|
+
color,
|
|
24647
|
+
backgroundColor,
|
|
24648
|
+
borderColor,
|
|
24649
|
+
borderRadius
|
|
24650
|
+
};
|
|
24651
|
+
}
|
|
24652
|
+
|
|
24653
|
+
function SATChoiceIcon(props) {
|
|
24654
|
+
const {
|
|
24655
|
+
letter,
|
|
24656
|
+
crossedOut,
|
|
24657
|
+
reviewMode,
|
|
24658
|
+
correct,
|
|
24659
|
+
checked,
|
|
24660
|
+
multipleSelect
|
|
24661
|
+
} = props;
|
|
24662
|
+
const {
|
|
24663
|
+
color,
|
|
24664
|
+
backgroundColor,
|
|
24665
|
+
borderColor,
|
|
24666
|
+
borderRadius
|
|
24667
|
+
} = constructSATStyles(reviewMode, correct, checked, multipleSelect);
|
|
24668
|
+
return /*#__PURE__*/React__namespace.createElement("div", {
|
|
24669
|
+
className: aphrodite.css(styles$x.iconWrapper)
|
|
24670
|
+
}, /*#__PURE__*/React__namespace.createElement("div", {
|
|
24671
|
+
className: aphrodite.css(styles$u.satCircle),
|
|
24672
|
+
"data-test-id": "choice-icon__sat-choice-icon",
|
|
24673
|
+
style: {
|
|
24674
|
+
backgroundColor,
|
|
24675
|
+
borderColor,
|
|
24676
|
+
borderRadius
|
|
24677
|
+
}
|
|
24678
|
+
}), /*#__PURE__*/React__namespace.createElement("div", {
|
|
24679
|
+
style: {
|
|
24680
|
+
color
|
|
24681
|
+
},
|
|
24682
|
+
className: aphrodite.css(styles$u.letter)
|
|
24683
|
+
}, /*#__PURE__*/React__namespace.createElement("span", null, letter)), crossedOut && /*#__PURE__*/React__namespace.createElement(CrossOutLine, {
|
|
24684
|
+
color: borderColor,
|
|
24685
|
+
sat: true
|
|
24686
|
+
}));
|
|
24687
|
+
}
|
|
24688
|
+
|
|
24689
|
+
const styles$u = aphrodite.StyleSheet.create({
|
|
24690
|
+
satCircle: {
|
|
24691
|
+
display: "block",
|
|
24692
|
+
borderStyle: "solid",
|
|
24693
|
+
borderWidth: 2,
|
|
24694
|
+
content: "''",
|
|
24695
|
+
height: SAT_ICON_SIZE,
|
|
24696
|
+
width: SAT_ICON_SIZE,
|
|
24697
|
+
top: 1,
|
|
24698
|
+
left: 1
|
|
24765
24699
|
},
|
|
24766
24700
|
letter: {
|
|
24767
24701
|
// These properties make sure that this element has the exact
|
|
@@ -24781,6 +24715,55 @@ const styles$t = aphrodite.StyleSheet.create({
|
|
|
24781
24715
|
}
|
|
24782
24716
|
});
|
|
24783
24717
|
|
|
24718
|
+
/* Component for rendering a letter icon in radio choice */
|
|
24719
|
+
|
|
24720
|
+
function ChoiceIcon(props) {
|
|
24721
|
+
const {
|
|
24722
|
+
pos,
|
|
24723
|
+
reviewMode,
|
|
24724
|
+
checked,
|
|
24725
|
+
crossedOut,
|
|
24726
|
+
correct,
|
|
24727
|
+
product,
|
|
24728
|
+
multipleSelect,
|
|
24729
|
+
showCorrectness,
|
|
24730
|
+
focused,
|
|
24731
|
+
hovered,
|
|
24732
|
+
pressed,
|
|
24733
|
+
primaryProductColor = Color__default["default"].blue,
|
|
24734
|
+
previouslyAnswered,
|
|
24735
|
+
transparentBackground
|
|
24736
|
+
} = props;
|
|
24737
|
+
const letter = getChoiceLetter(pos);
|
|
24738
|
+
|
|
24739
|
+
if (product === "sat") {
|
|
24740
|
+
return /*#__PURE__*/React__namespace.createElement(SATChoiceIcon, {
|
|
24741
|
+
letter: letter,
|
|
24742
|
+
reviewMode: reviewMode,
|
|
24743
|
+
checked: checked,
|
|
24744
|
+
correct: correct,
|
|
24745
|
+
crossedOut: crossedOut,
|
|
24746
|
+
multipleSelect: multipleSelect
|
|
24747
|
+
});
|
|
24748
|
+
}
|
|
24749
|
+
|
|
24750
|
+
return /*#__PURE__*/React__namespace.createElement(LibraryChoiceIcon, {
|
|
24751
|
+
letter: letter,
|
|
24752
|
+
reviewMode: reviewMode,
|
|
24753
|
+
checked: checked,
|
|
24754
|
+
crossedOut: crossedOut,
|
|
24755
|
+
focused: focused,
|
|
24756
|
+
hovered: hovered,
|
|
24757
|
+
pressed: pressed,
|
|
24758
|
+
correct: correct,
|
|
24759
|
+
showCorrectness: showCorrectness,
|
|
24760
|
+
primaryProductColor: primaryProductColor,
|
|
24761
|
+
previouslyAnswered: previouslyAnswered,
|
|
24762
|
+
transparentBackground: transparentBackground,
|
|
24763
|
+
multipleSelect: multipleSelect
|
|
24764
|
+
});
|
|
24765
|
+
}
|
|
24766
|
+
|
|
24784
24767
|
/**
|
|
24785
24768
|
* Renders text indicating whether the choice was correct or
|
|
24786
24769
|
* not and whether the choice was selected or not.
|
|
@@ -24789,63 +24772,68 @@ const styles$t = aphrodite.StyleSheet.create({
|
|
|
24789
24772
|
* between the states more immediately clear to users.
|
|
24790
24773
|
*/
|
|
24791
24774
|
|
|
24792
|
-
|
|
24793
|
-
|
|
24794
|
-
|
|
24795
|
-
|
|
24796
|
-
|
|
24797
|
-
|
|
24798
|
-
}
|
|
24775
|
+
function renderText(checked, correct, crossedOut) {
|
|
24776
|
+
if (correct) {
|
|
24777
|
+
// For correct answers, we surface checked _or_ crossedOut state,
|
|
24778
|
+
// because any interaction with the correct answer is noteworthy!
|
|
24779
|
+
if (checked) {
|
|
24780
|
+
return i18n__namespace._("Correct (selected)");
|
|
24781
|
+
}
|
|
24799
24782
|
|
|
24800
|
-
if (
|
|
24801
|
-
|
|
24802
|
-
|
|
24803
|
-
if (checked) {
|
|
24804
|
-
return i18n__namespace._("Correct (selected)");
|
|
24805
|
-
}
|
|
24783
|
+
if (crossedOut) {
|
|
24784
|
+
return i18n__namespace._("Correct (but you crossed it out)");
|
|
24785
|
+
}
|
|
24806
24786
|
|
|
24807
|
-
|
|
24808
|
-
|
|
24809
|
-
|
|
24787
|
+
return i18n__namespace._("Correct");
|
|
24788
|
+
} // But, for incorrect answers, we only surface checked state,
|
|
24789
|
+
// because crossing out an incorrect answer is not noteworthy.
|
|
24810
24790
|
|
|
24811
|
-
return i18n__namespace._("Correct");
|
|
24812
|
-
} // But, for incorrect answers, we only surface checked state,
|
|
24813
|
-
// because crossing out an incorrect answer is not noteworthy.
|
|
24814
24791
|
|
|
24792
|
+
if (checked) {
|
|
24793
|
+
return i18n__namespace._("Incorrect (selected)");
|
|
24794
|
+
}
|
|
24815
24795
|
|
|
24816
|
-
|
|
24817
|
-
|
|
24818
|
-
|
|
24796
|
+
return i18n__namespace._("Incorrect");
|
|
24797
|
+
}
|
|
24798
|
+
|
|
24799
|
+
function OptionStatus(props) {
|
|
24800
|
+
const {
|
|
24801
|
+
checked,
|
|
24802
|
+
correct,
|
|
24803
|
+
crossedOut,
|
|
24804
|
+
previouslyAnswered,
|
|
24805
|
+
reviewMode,
|
|
24806
|
+
satStyling
|
|
24807
|
+
} = props; // Option status is exclued for SAT
|
|
24808
|
+
|
|
24809
|
+
if (satStyling) {
|
|
24810
|
+
return null;
|
|
24811
|
+
} // Option status is shown only in review mode, or for incorrectly
|
|
24812
|
+
// answered items.
|
|
24819
24813
|
|
|
24820
|
-
|
|
24814
|
+
|
|
24815
|
+
if (!reviewMode && !previouslyAnswered) {
|
|
24816
|
+
return null;
|
|
24821
24817
|
}
|
|
24822
24818
|
|
|
24823
|
-
|
|
24824
|
-
const {
|
|
24825
|
-
checked,
|
|
24826
|
-
correct,
|
|
24827
|
-
previouslyAnswered
|
|
24828
|
-
} = this.props;
|
|
24829
|
-
let textStyle;
|
|
24819
|
+
let textStyle;
|
|
24830
24820
|
|
|
24831
|
-
|
|
24832
|
-
|
|
24821
|
+
if (correct) {
|
|
24822
|
+
textStyle = styles$t.correct;
|
|
24823
|
+
} else {
|
|
24824
|
+
if (checked || previouslyAnswered) {
|
|
24825
|
+
textStyle = styles$t.incorrectAnswered;
|
|
24833
24826
|
} else {
|
|
24834
|
-
|
|
24835
|
-
textStyle = styles$s.incorrectAnswered;
|
|
24836
|
-
} else {
|
|
24837
|
-
textStyle = styles$s.incorrect;
|
|
24838
|
-
}
|
|
24827
|
+
textStyle = styles$t.incorrect;
|
|
24839
24828
|
}
|
|
24840
|
-
|
|
24841
|
-
return /*#__PURE__*/React__namespace.createElement("div", {
|
|
24842
|
-
className: aphrodite.css(styles$s.text, textStyle)
|
|
24843
|
-
}, this._renderText());
|
|
24844
24829
|
}
|
|
24845
24830
|
|
|
24831
|
+
return /*#__PURE__*/React__namespace.createElement("div", {
|
|
24832
|
+
className: aphrodite.css(styles$t.text, textStyle)
|
|
24833
|
+
}, renderText(checked, correct, crossedOut));
|
|
24846
24834
|
}
|
|
24847
24835
|
|
|
24848
|
-
const styles$
|
|
24836
|
+
const styles$t = aphrodite.StyleSheet.create({
|
|
24849
24837
|
text: {
|
|
24850
24838
|
alignItems: "center",
|
|
24851
24839
|
display: "flex",
|
|
@@ -24871,317 +24859,236 @@ const ellipsisHorizontalIcon = {
|
|
|
24871
24859
|
width: 100,
|
|
24872
24860
|
height: 27.284
|
|
24873
24861
|
};
|
|
24862
|
+
// Note(TB): Received errors when using useUniqueIdWithMock
|
|
24863
|
+
// so created this workaround function. Will update when
|
|
24864
|
+
// useUniqueIdWithMock is available.
|
|
24865
|
+
// https://khanacademy.atlassian.net/browse/FEI-4861?atlOrigin=eyJpIjoiNDJlZWMwNjM1NWJhNDBkMWFjY2FmN2I0ZjcxZmQxOGUiLCJwIjoiaiJ9
|
|
24866
|
+
let id = 0;
|
|
24874
24867
|
|
|
24875
|
-
|
|
24876
|
-
|
|
24877
|
-
|
|
24878
|
-
|
|
24879
|
-
_defineProperty(this, "justFinishedTouch", void 0);
|
|
24880
|
-
|
|
24881
|
-
_defineProperty(this, "justFinishedTimeoutID", void 0);
|
|
24882
|
-
|
|
24883
|
-
_defineProperty(this, "_input", void 0);
|
|
24884
|
-
|
|
24885
|
-
_defineProperty(this, "state", {
|
|
24886
|
-
isInputFocused: false,
|
|
24887
|
-
isInputActive: false
|
|
24888
|
-
});
|
|
24889
|
-
|
|
24890
|
-
_defineProperty(this, "inputRef", ref => {
|
|
24891
|
-
this._input = ref;
|
|
24892
|
-
});
|
|
24893
|
-
|
|
24894
|
-
_defineProperty(this, "renderOptionStatus", () => {
|
|
24895
|
-
const {
|
|
24896
|
-
correct,
|
|
24897
|
-
checked,
|
|
24898
|
-
crossedOut,
|
|
24899
|
-
reviewMode,
|
|
24900
|
-
previouslyAnswered
|
|
24901
|
-
} = this.props; // Option status is exclued for SAT
|
|
24902
|
-
|
|
24903
|
-
if (this.props.apiOptions.satStyling) {
|
|
24904
|
-
return null;
|
|
24905
|
-
} // Option status is shown only in review mode, or for incorrectly
|
|
24906
|
-
// answered items.
|
|
24868
|
+
function uniqueId() {
|
|
24869
|
+
return "choice-".concat(id++);
|
|
24870
|
+
}
|
|
24907
24871
|
|
|
24872
|
+
function Choice(props) {
|
|
24873
|
+
const {
|
|
24874
|
+
disabled,
|
|
24875
|
+
checked,
|
|
24876
|
+
content,
|
|
24877
|
+
crossedOut,
|
|
24878
|
+
showCorrectness,
|
|
24879
|
+
multipleSelect,
|
|
24880
|
+
onChange,
|
|
24881
|
+
reviewMode,
|
|
24882
|
+
correct,
|
|
24883
|
+
apiOptions,
|
|
24884
|
+
previouslyAnswered,
|
|
24885
|
+
pos,
|
|
24886
|
+
showRationale,
|
|
24887
|
+
rationale,
|
|
24888
|
+
forwardedRef
|
|
24889
|
+
} = props;
|
|
24890
|
+
const [isInputFocused, setIsInputFocused] = React.useState(false);
|
|
24891
|
+
React.useEffect(() => {
|
|
24892
|
+
if (isInputFocused && disabled) {
|
|
24893
|
+
setIsInputFocused(false);
|
|
24894
|
+
}
|
|
24895
|
+
}, [disabled, isInputFocused, setIsInputFocused]); // Call `this.props.onChange` with the given values. Any keys that are not
|
|
24896
|
+
// specified will be filled in with the current value. (For example, if
|
|
24897
|
+
// `checked` is specified but `crossedOut` is not, then `crossedOut` will
|
|
24898
|
+
// be filled in with `this.props.crossedOut`.)
|
|
24899
|
+
//
|
|
24900
|
+
// This enables us to use shorthand inside this component, while
|
|
24901
|
+
// maintaining a consistent API for the parent.
|
|
24908
24902
|
|
|
24909
|
-
|
|
24910
|
-
|
|
24911
|
-
}
|
|
24903
|
+
function sendChange(newValues) {
|
|
24904
|
+
var _newValues$checked, _newValues$crossedOut;
|
|
24912
24905
|
|
|
24913
|
-
|
|
24914
|
-
|
|
24915
|
-
|
|
24916
|
-
|
|
24917
|
-
|
|
24918
|
-
});
|
|
24906
|
+
const updatedChecked = (_newValues$checked = newValues.checked) !== null && _newValues$checked !== void 0 ? _newValues$checked : checked;
|
|
24907
|
+
const updatedCrossedOut = (_newValues$crossedOut = newValues.crossedOut) !== null && _newValues$crossedOut !== void 0 ? _newValues$crossedOut : crossedOut;
|
|
24908
|
+
onChange({
|
|
24909
|
+
checked: updatedChecked,
|
|
24910
|
+
crossedOut: updatedCrossedOut
|
|
24919
24911
|
});
|
|
24912
|
+
} // HACK: while most of the styling for rendering SAT items is handled
|
|
24913
|
+
// via aphrodite, we also need to assign normal CSS classnames here to
|
|
24914
|
+
// special-case the coloring of MathJax formulas (see .MathJax .math in
|
|
24915
|
+
// stylesheets/task-package/tasks.less)
|
|
24920
24916
|
|
|
24921
|
-
_defineProperty(this, "renderChoiceIcon", _ref => {
|
|
24922
|
-
let {
|
|
24923
|
-
isFocused,
|
|
24924
|
-
isPressed
|
|
24925
|
-
} = _ref;
|
|
24926
|
-
const {
|
|
24927
|
-
radioStyleVersion,
|
|
24928
|
-
primaryProductColor
|
|
24929
|
-
} = this.props.apiOptions.styling;
|
|
24930
|
-
const finalStyles = typeof radioStyleVersion === "undefined" ? false : radioStyleVersion === "final";
|
|
24931
|
-
|
|
24932
|
-
if (!finalStyles && !this.props.apiOptions.satStyling) {
|
|
24933
|
-
return null;
|
|
24934
|
-
}
|
|
24935
|
-
|
|
24936
|
-
return /*#__PURE__*/React__namespace.createElement(ChoiceIcon, {
|
|
24937
|
-
pos: this.props.pos,
|
|
24938
|
-
correct: this.props.correct,
|
|
24939
|
-
crossedOut: this.props.crossedOut,
|
|
24940
|
-
pressed: isPressed,
|
|
24941
|
-
focused: isFocused,
|
|
24942
|
-
checked: this.props.checked,
|
|
24943
|
-
showCorrectness: this.props.showCorrectness,
|
|
24944
|
-
reviewMode: this.props.reviewMode,
|
|
24945
|
-
product: this.props.apiOptions.satStyling ? "sat" : "library",
|
|
24946
|
-
primaryProductColor: primaryProductColor,
|
|
24947
|
-
previouslyAnswered: this.props.previouslyAnswered
|
|
24948
|
-
});
|
|
24949
|
-
});
|
|
24950
24917
|
|
|
24951
|
-
|
|
24952
|
-
|
|
24918
|
+
const satCorrectChoice = apiOptions.satStyling && reviewMode && correct;
|
|
24919
|
+
const satIncorrectChecked = apiOptions.satStyling && reviewMode && !correct && checked;
|
|
24920
|
+
const descriptionClassName = classNames__default["default"]("description", satCorrectChoice && "sat-correct", satIncorrectChecked && "sat-incorrect", aphrodite.css(!apiOptions.satStyling && styles$s.description, apiOptions.satStyling && styles$s.satDescription, satCorrectChoice && styles$s.satDescriptionCorrect, satCorrectChoice && checked && styles$s.satDescriptionCorrectChecked, satIncorrectChecked && styles$s.satDescriptionIncorrectChecked));
|
|
24921
|
+
const rationaleClassName = classNames__default["default"]("perseus-radio-rationale-content", aphrodite.css(styles$s.rationale, !apiOptions.satStyling && styles$s.nonSatRationale, apiOptions.satStyling && styles$s.satReviewRationale)); // We want to show the choices as dimmed out when the choices are
|
|
24922
|
+
// disabled. However, we don't want to do this in the SAT product and
|
|
24923
|
+
// we also don't want to do this when we're in review mode in the
|
|
24924
|
+
// content library.
|
|
24953
24925
|
|
|
24954
|
-
|
|
24955
|
-
|
|
24956
|
-
|
|
24957
|
-
|
|
24958
|
-
|
|
24926
|
+
const showDimmed = !apiOptions.satStyling && !reviewMode && apiOptions.readOnly || crossedOut;
|
|
24927
|
+
const letter = getChoiceLetter(pos);
|
|
24928
|
+
const a11yText = getA11yText(letter, checked, correct, crossedOut, showCorrectness);
|
|
24929
|
+
const choiceId = uniqueId();
|
|
24930
|
+
return /*#__PURE__*/React__namespace.createElement("div", {
|
|
24931
|
+
style: {
|
|
24932
|
+
dispay: "flex",
|
|
24933
|
+
flexDirection: "column"
|
|
24934
|
+
}
|
|
24935
|
+
}, /*#__PURE__*/React__namespace.createElement("div", {
|
|
24936
|
+
style: {
|
|
24937
|
+
display: "flex",
|
|
24938
|
+
flexDirection: "row",
|
|
24939
|
+
width: "100%",
|
|
24940
|
+
opacity: showDimmed ? 0.5 : 1.0
|
|
24941
|
+
}
|
|
24942
|
+
}, /*#__PURE__*/React__namespace.createElement("div", {
|
|
24943
|
+
className: "perseus-sr-only"
|
|
24944
|
+
}, /*#__PURE__*/React__namespace.createElement("input", {
|
|
24945
|
+
type: multipleSelect ? "checkbox" : "radio",
|
|
24946
|
+
id: choiceId,
|
|
24947
|
+
checked: checked,
|
|
24948
|
+
onClick: () => {
|
|
24949
|
+
// If we're checking a crossed-out option, let's
|
|
24950
|
+
// also uncross it.
|
|
24951
|
+
sendChange({
|
|
24952
|
+
checked: !checked,
|
|
24953
|
+
crossedOut: false
|
|
24959
24954
|
});
|
|
24960
|
-
}
|
|
24961
|
-
|
|
24962
|
-
|
|
24963
|
-
|
|
24964
|
-
|
|
24965
|
-
|
|
24966
|
-
|
|
24967
|
-
|
|
24968
|
-
|
|
24969
|
-
|
|
24970
|
-
|
|
24971
|
-
|
|
24972
|
-
|
|
24955
|
+
},
|
|
24956
|
+
onChange: () => {},
|
|
24957
|
+
disabled: disabled || apiOptions.staticRender || apiOptions.readOnly,
|
|
24958
|
+
tabIndex: -1
|
|
24959
|
+
}), /*#__PURE__*/React__namespace.createElement("label", {
|
|
24960
|
+
htmlFor: choiceId
|
|
24961
|
+
}, a11yText, " \xA0 ", content)), /*#__PURE__*/React__namespace.createElement(Clickable__default["default"], {
|
|
24962
|
+
onClick: () => {
|
|
24963
|
+
// If we're checking a crossed-out option, let's
|
|
24964
|
+
// also uncross it.
|
|
24965
|
+
sendChange({
|
|
24966
|
+
checked: !checked,
|
|
24967
|
+
crossedOut: false
|
|
24973
24968
|
});
|
|
24974
|
-
}
|
|
24975
|
-
|
|
24969
|
+
},
|
|
24970
|
+
className: descriptionClassName,
|
|
24971
|
+
disabled: disabled || apiOptions.staticRender || apiOptions.readOnly,
|
|
24972
|
+
style: {
|
|
24973
|
+
flex: 1
|
|
24974
|
+
},
|
|
24975
|
+
ref: forwardedRef,
|
|
24976
|
+
"aria-hidden": "true"
|
|
24977
|
+
}, _ref => {
|
|
24978
|
+
var _apiOptions$styling;
|
|
24976
24979
|
|
|
24977
|
-
|
|
24978
|
-
|
|
24979
|
-
|
|
24980
|
-
|
|
24981
|
-
|
|
24982
|
-
checked
|
|
24983
|
-
} = this.props; // HACK: while most of the styling for rendering SAT items is handled
|
|
24984
|
-
// via aphrodite, we also need to assign normal CSS classnames here to
|
|
24985
|
-
// special-case the coloring of MathJax formulas (see .MathJax .math in
|
|
24986
|
-
// stylesheets/task-package/tasks.less)
|
|
24987
|
-
|
|
24988
|
-
const satCorrectChoice = sat && reviewMode && correct;
|
|
24989
|
-
const satIncorrectChecked = sat && reviewMode && !correct && checked;
|
|
24990
|
-
const descriptionClassName = classNames__default["default"]("description", satCorrectChoice && "sat-correct", satIncorrectChecked && "sat-incorrect", aphrodite.css(!sat && styles$r.description, sat && styles$r.satDescription, satCorrectChoice && styles$r.satDescriptionCorrect, satCorrectChoice && checked && styles$r.satDescriptionCorrectChecked, satIncorrectChecked && styles$r.satDescriptionIncorrectChecked));
|
|
24991
|
-
const rationaleClassName = classNames__default["default"]("perseus-radio-rationale-content", aphrodite.css(styles$r.rationale, !sat && styles$r.nonSatRationale, sat && styles$r.satReviewRationale)); // We want to show the choices as dimmed out when the choices are
|
|
24992
|
-
// disabled. However, we don't want to do this in the SAT product and
|
|
24993
|
-
// we also don't want to do this when we're in review mode in the
|
|
24994
|
-
// content library.
|
|
24995
|
-
|
|
24996
|
-
const showDimmed = !sat && !reviewMode && this.props.apiOptions.readOnly || this.props.crossedOut;
|
|
24980
|
+
let {
|
|
24981
|
+
hovered,
|
|
24982
|
+
focused,
|
|
24983
|
+
pressed
|
|
24984
|
+
} = _ref;
|
|
24997
24985
|
return /*#__PURE__*/React__namespace.createElement("div", {
|
|
24998
|
-
style: {
|
|
24999
|
-
dispay: "flex",
|
|
25000
|
-
flexDirection: "column"
|
|
25001
|
-
}
|
|
25002
|
-
}, /*#__PURE__*/React__namespace.createElement("div", {
|
|
25003
24986
|
style: {
|
|
25004
24987
|
display: "flex",
|
|
25005
24988
|
flexDirection: "row",
|
|
25006
|
-
|
|
25007
|
-
|
|
24989
|
+
justifyContent: "center",
|
|
24990
|
+
alignContent: "center",
|
|
24991
|
+
paddingTop: Spacing__default["default"].xSmall_8,
|
|
24992
|
+
paddingBottom: Spacing__default["default"].xSmall_8
|
|
25008
24993
|
}
|
|
25009
|
-
}, /*#__PURE__*/React__namespace.createElement(
|
|
25010
|
-
|
|
25011
|
-
|
|
25012
|
-
|
|
25013
|
-
|
|
25014
|
-
|
|
25015
|
-
|
|
25016
|
-
|
|
25017
|
-
|
|
25018
|
-
|
|
25019
|
-
|
|
25020
|
-
|
|
25021
|
-
|
|
25022
|
-
|
|
24994
|
+
}, /*#__PURE__*/React__namespace.createElement(ChoiceIcon, {
|
|
24995
|
+
pos: pos,
|
|
24996
|
+
correct: correct,
|
|
24997
|
+
crossedOut: crossedOut,
|
|
24998
|
+
pressed: pressed,
|
|
24999
|
+
focused: focused,
|
|
25000
|
+
checked: checked,
|
|
25001
|
+
hovered: hovered,
|
|
25002
|
+
showCorrectness: showCorrectness,
|
|
25003
|
+
multipleSelect: multipleSelect,
|
|
25004
|
+
reviewMode: reviewMode,
|
|
25005
|
+
product: apiOptions.satStyling ? "sat" : "library",
|
|
25006
|
+
primaryProductColor: (_apiOptions$styling = apiOptions.styling) === null || _apiOptions$styling === void 0 ? void 0 : _apiOptions$styling.primaryProductColor,
|
|
25007
|
+
previouslyAnswered: previouslyAnswered
|
|
25008
|
+
}), /*#__PURE__*/React__namespace.createElement("span", {
|
|
25023
25009
|
style: {
|
|
25024
|
-
|
|
25010
|
+
paddingLeft: Spacing__default["default"].small_12,
|
|
25011
|
+
textAlign: "left",
|
|
25012
|
+
flex: 1,
|
|
25013
|
+
paddingTop: 4
|
|
25025
25014
|
}
|
|
25026
|
-
},
|
|
25015
|
+
}, /*#__PURE__*/React__namespace.createElement("div", null, /*#__PURE__*/React__namespace.createElement(OptionStatus, {
|
|
25016
|
+
checked: checked,
|
|
25017
|
+
correct: correct,
|
|
25018
|
+
crossedOut: crossedOut,
|
|
25019
|
+
previouslyAnswered: previouslyAnswered,
|
|
25020
|
+
reviewMode: reviewMode,
|
|
25021
|
+
satStyling: apiOptions.satStyling
|
|
25022
|
+
})), /*#__PURE__*/React__namespace.createElement("div", null, content)));
|
|
25023
|
+
}), apiOptions.crossOutEnabled && !reviewMode && /*#__PURE__*/React__namespace.createElement(wonderBlocksPopover.Popover, {
|
|
25024
|
+
dismissEnabled: true,
|
|
25025
|
+
content: _ref2 => {
|
|
25027
25026
|
let {
|
|
25028
|
-
|
|
25029
|
-
focused,
|
|
25030
|
-
pressed
|
|
25027
|
+
close
|
|
25031
25028
|
} = _ref2;
|
|
25032
|
-
return /*#__PURE__*/React__namespace.createElement(
|
|
25033
|
-
|
|
25034
|
-
|
|
25035
|
-
|
|
25036
|
-
|
|
25037
|
-
|
|
25038
|
-
|
|
25039
|
-
|
|
25040
|
-
|
|
25041
|
-
|
|
25042
|
-
|
|
25043
|
-
|
|
25044
|
-
|
|
25045
|
-
|
|
25046
|
-
|
|
25047
|
-
|
|
25048
|
-
|
|
25049
|
-
|
|
25050
|
-
|
|
25051
|
-
|
|
25052
|
-
|
|
25053
|
-
|
|
25054
|
-
|
|
25055
|
-
|
|
25056
|
-
close
|
|
25057
|
-
} = _ref3;
|
|
25058
|
-
return /*#__PURE__*/React__namespace.createElement(wonderBlocksPopover.PopoverContent, {
|
|
25059
|
-
title: "Cross out",
|
|
25060
|
-
content: "Cross out option",
|
|
25061
|
-
closeButtonVisible: true,
|
|
25062
|
-
actions: /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, null, /*#__PURE__*/React__namespace.createElement(wonderBlocksLayout.Strut, {
|
|
25063
|
-
size: Spacing__default["default"].medium_16
|
|
25064
|
-
}), /*#__PURE__*/React__namespace.createElement(Button__default["default"], {
|
|
25065
|
-
kind: "primary",
|
|
25066
|
-
"aria-label": "Cross out Choice ".concat(getChoiceLetter(this.props.pos)),
|
|
25067
|
-
disabled: this.props.apiOptions.readOnly || reviewMode,
|
|
25068
|
-
onClick: () => {
|
|
25069
|
-
if (!this.props.crossedOut) {
|
|
25070
|
-
// If we're crossing
|
|
25071
|
-
// out a checked
|
|
25072
|
-
// option, let's also
|
|
25073
|
-
// uncheck it.
|
|
25074
|
-
this._sendChange({
|
|
25075
|
-
checked: false,
|
|
25076
|
-
crossedOut: true
|
|
25077
|
-
});
|
|
25078
|
-
} else {
|
|
25079
|
-
this._sendChange({
|
|
25080
|
-
crossedOut: false
|
|
25081
|
-
});
|
|
25082
|
-
}
|
|
25083
|
-
|
|
25084
|
-
close();
|
|
25029
|
+
return /*#__PURE__*/React__namespace.createElement(wonderBlocksPopover.PopoverContent, {
|
|
25030
|
+
title: "Cross out",
|
|
25031
|
+
content: "Cross out option",
|
|
25032
|
+
closeButtonVisible: true,
|
|
25033
|
+
actions: /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, null, /*#__PURE__*/React__namespace.createElement(wonderBlocksLayout.Strut, {
|
|
25034
|
+
size: Spacing__default["default"].medium_16
|
|
25035
|
+
}), /*#__PURE__*/React__namespace.createElement(Button__default["default"], {
|
|
25036
|
+
kind: "primary",
|
|
25037
|
+
"aria-label": "Cross out Choice ".concat(getChoiceLetter(pos)),
|
|
25038
|
+
disabled: apiOptions.readOnly || reviewMode,
|
|
25039
|
+
onClick: () => {
|
|
25040
|
+
if (!crossedOut) {
|
|
25041
|
+
// If we're crossing
|
|
25042
|
+
// out a checked
|
|
25043
|
+
// option, let's also
|
|
25044
|
+
// uncheck it.
|
|
25045
|
+
sendChange({
|
|
25046
|
+
checked: false,
|
|
25047
|
+
crossedOut: true
|
|
25048
|
+
});
|
|
25049
|
+
} else {
|
|
25050
|
+
sendChange({
|
|
25051
|
+
crossedOut: false
|
|
25052
|
+
});
|
|
25085
25053
|
}
|
|
25086
|
-
|
|
25087
|
-
|
|
25088
|
-
|
|
25054
|
+
|
|
25055
|
+
close();
|
|
25056
|
+
}
|
|
25057
|
+
}, crossedOut ? "Bring back" : "Cross out"))
|
|
25058
|
+
});
|
|
25059
|
+
}
|
|
25060
|
+
}, _ref3 => {
|
|
25061
|
+
let {
|
|
25062
|
+
open
|
|
25063
|
+
} = _ref3;
|
|
25064
|
+
return /*#__PURE__*/React__namespace.createElement(Clickable__default["default"], {
|
|
25065
|
+
onClick: open,
|
|
25066
|
+
"aria-label": "Open menu for Choice ".concat(getChoiceLetter(pos)),
|
|
25067
|
+
disabled: apiOptions.staticRender
|
|
25089
25068
|
}, _ref4 => {
|
|
25090
|
-
|
|
25091
|
-
|
|
25092
|
-
|
|
25093
|
-
|
|
25094
|
-
onClick: open,
|
|
25095
|
-
"aria-label": "Open menu for Choice ".concat(getChoiceLetter(this.props.pos)),
|
|
25096
|
-
disabled: this.props.apiOptions.staticRender
|
|
25097
|
-
}, _ref5 => {
|
|
25098
|
-
return /*#__PURE__*/React__namespace.createElement(Icon, {
|
|
25099
|
-
icon: ellipsisHorizontalIcon,
|
|
25100
|
-
size: 3,
|
|
25101
|
-
color: Color__default["default"].offBlack64
|
|
25102
|
-
});
|
|
25069
|
+
return /*#__PURE__*/React__namespace.createElement(Icon, {
|
|
25070
|
+
icon: ellipsisHorizontalIcon,
|
|
25071
|
+
size: 3,
|
|
25072
|
+
color: Color__default["default"].offBlack64
|
|
25103
25073
|
});
|
|
25104
|
-
})
|
|
25105
|
-
|
|
25106
|
-
|
|
25107
|
-
|
|
25108
|
-
}
|
|
25109
|
-
|
|
25074
|
+
});
|
|
25075
|
+
})), showRationale && /*#__PURE__*/React__namespace.createElement("div", {
|
|
25076
|
+
className: rationaleClassName,
|
|
25077
|
+
"data-test-id": "perseus-radio-rationale-content-".concat(pos)
|
|
25078
|
+
}, rationale));
|
|
25110
25079
|
}
|
|
25111
25080
|
|
|
25112
|
-
|
|
25113
|
-
|
|
25114
|
-
// This change will also require make sure that item-renderer.jsx and
|
|
25115
|
-
// server-item-renderer.jsx have appropriate defaults for apiOptions
|
|
25116
|
-
// because many of the properties on Options.propTypes are required.
|
|
25117
|
-
apiOptions: PropTypes__default["default"].shape({
|
|
25118
|
-
satStyling: PropTypes__default["default"].bool,
|
|
25119
|
-
isMobile: PropTypes__default["default"].bool,
|
|
25120
|
-
styling: PropTypes__default["default"].shape({
|
|
25121
|
-
radioStyleVersion: PropTypes__default["default"].oneOf(["intermediate", "final"]),
|
|
25122
|
-
primaryProductColor: PropTypes__default["default"].string
|
|
25123
|
-
}),
|
|
25124
|
-
readOnly: PropTypes__default["default"].bool
|
|
25125
|
-
}),
|
|
25126
|
-
checked: PropTypes__default["default"].bool,
|
|
25127
|
-
className: PropTypes__default["default"].string,
|
|
25128
|
-
rationale: PropTypes__default["default"].node,
|
|
25129
|
-
content: PropTypes__default["default"].node,
|
|
25130
|
-
correct: PropTypes__default["default"].bool,
|
|
25131
|
-
deselectEnabled: PropTypes__default["default"].bool,
|
|
25132
|
-
disabled: PropTypes__default["default"].bool,
|
|
25133
|
-
editMode: PropTypes__default["default"].bool,
|
|
25134
|
-
groupName: PropTypes__default["default"].string,
|
|
25135
|
-
isLastChoice: PropTypes__default["default"].bool,
|
|
25136
|
-
// Needed for border styling
|
|
25137
|
-
// This indicates the position of the choice relative to others
|
|
25138
|
-
// (so that we can display a nice little (A), (B), etc. next to it)
|
|
25139
|
-
// Also used to generate an id for each input.
|
|
25140
|
-
pos: PropTypes__default["default"].number,
|
|
25141
|
-
reviewMode: PropTypes__default["default"].bool,
|
|
25142
|
-
showRationale: PropTypes__default["default"].bool,
|
|
25143
|
-
showCorrectness: PropTypes__default["default"].bool,
|
|
25144
|
-
type: PropTypes__default["default"].string,
|
|
25145
|
-
// Indicates whether the user has "crossed out" this choice, meaning
|
|
25146
|
-
// that they don't think it's correct. This value does not affect
|
|
25147
|
-
// scoring or other behavior; it's just a note for the user's
|
|
25148
|
-
// reference.
|
|
25149
|
-
crossedOut: PropTypes__default["default"].bool,
|
|
25150
|
-
// Indicates that the user has previously selected this answer. These
|
|
25151
|
-
// answers may be rendered orange in review, rather than grey if
|
|
25152
|
-
// incorrect.
|
|
25153
|
-
previouslyAnswered: PropTypes__default["default"].bool,
|
|
25154
|
-
// A callback indicating that this choice has changed. Its argument is
|
|
25155
|
-
// an object with two keys: `checked` and `crossedOut`. Each contains a
|
|
25156
|
-
// boolean value specifying the new checked and crossed-out value of
|
|
25157
|
-
// this choice.
|
|
25158
|
-
onChange: PropTypes__default["default"].func,
|
|
25159
|
-
// Callbacks for when the user presses the up arrow or down arrow
|
|
25160
|
-
// (respectively) to navigate between choices. When called, the parent
|
|
25161
|
-
// finds the prev/next choice, focuses the input, and checks the radio
|
|
25162
|
-
// button if appropriate. (The reason we have custom behavior is that
|
|
25163
|
-
// we actually _don't_ want to auto-select crossed-out choices!)
|
|
25164
|
-
goToPrevChoice: PropTypes__default["default"].func.isRequired,
|
|
25165
|
-
goToNextChoice: PropTypes__default["default"].func.isRequired
|
|
25166
|
-
});
|
|
25167
|
-
|
|
25168
|
-
_defineProperty(Choice, "defaultProps", {
|
|
25169
|
-
apiOptions: {
|
|
25170
|
-
styling: {}
|
|
25171
|
-
},
|
|
25081
|
+
Choice.defaultProps = {
|
|
25082
|
+
disabled: false,
|
|
25172
25083
|
checked: false,
|
|
25173
|
-
|
|
25084
|
+
onChange: newValues => {},
|
|
25174
25085
|
correct: false,
|
|
25175
|
-
|
|
25176
|
-
|
|
25177
|
-
onChange: function () {},
|
|
25178
|
-
showRationale: false,
|
|
25179
|
-
type: "radio",
|
|
25086
|
+
apiOptions: ApiOptions.defaults,
|
|
25087
|
+
previouslyAnswered: false,
|
|
25180
25088
|
pos: 0,
|
|
25181
|
-
|
|
25182
|
-
}
|
|
25183
|
-
|
|
25184
|
-
const styles$r = aphrodite.StyleSheet.create({
|
|
25089
|
+
showRationale: false
|
|
25090
|
+
};
|
|
25091
|
+
const styles$s = aphrodite.StyleSheet.create({
|
|
25185
25092
|
description: {
|
|
25186
25093
|
display: "inline-block",
|
|
25187
25094
|
width: "100%"
|
|
@@ -25221,184 +25128,83 @@ const styles$r = aphrodite.StyleSheet.create({
|
|
|
25221
25128
|
marginLeft: 45
|
|
25222
25129
|
}
|
|
25223
25130
|
});
|
|
25131
|
+
var Choice$1 = /*#__PURE__*/React__namespace.forwardRef((props, ref) => /*#__PURE__*/React__namespace.createElement(Choice, _extends({}, props, {
|
|
25132
|
+
forwardedRef: ref
|
|
25133
|
+
})));
|
|
25224
25134
|
|
|
25225
|
-
|
|
25226
|
-
|
|
25227
|
-
|
|
25228
|
-
|
|
25229
|
-
|
|
25230
|
-
|
|
25231
|
-
|
|
25232
|
-
|
|
25233
|
-
|
|
25234
|
-
|
|
25235
|
-
|
|
25236
|
-
|
|
25237
|
-
|
|
25238
|
-
|
|
25239
|
-
|
|
25240
|
-
|
|
25241
|
-
|
|
25242
|
-
|
|
25243
|
-
|
|
25244
|
-
|
|
25245
|
-
|
|
25246
|
-
|
|
25247
|
-
|
|
25248
|
-
|
|
25249
|
-
|
|
25250
|
-
|
|
25251
|
-
|
|
25252
|
-
|
|
25253
|
-
};
|
|
25254
|
-
return /*#__PURE__*/React__namespace.createElement(Choice, choiceProps);
|
|
25255
|
-
}
|
|
25256
|
-
|
|
25135
|
+
function ChoiceNoneAbove(props) {
|
|
25136
|
+
const {
|
|
25137
|
+
showContent,
|
|
25138
|
+
content,
|
|
25139
|
+
forwardedRef,
|
|
25140
|
+
...rest
|
|
25141
|
+
} = props;
|
|
25142
|
+
const choiceProps = { ...rest,
|
|
25143
|
+
content: showContent ? content :
|
|
25144
|
+
/*#__PURE__*/
|
|
25145
|
+
// We use a Renderer here because that is how
|
|
25146
|
+
// `this.props.content` is wrapped otherwise.
|
|
25147
|
+
// We pass in a key here so that we avoid a semi-spurious
|
|
25148
|
+
// react warning when we render this in the same place
|
|
25149
|
+
// as the previous choice content renderer.
|
|
25150
|
+
// Note this destroys state, but since all we're doing
|
|
25151
|
+
// is outputting "None of the above", that is okay.
|
|
25152
|
+
//
|
|
25153
|
+
// todo(matthewc): this seems like way overkill
|
|
25154
|
+
// just to render a string
|
|
25155
|
+
React__namespace.createElement(Renderer, {
|
|
25156
|
+
key: "noneOfTheAboveRenderer",
|
|
25157
|
+
content: i18n__namespace._("None of the above")
|
|
25158
|
+
})
|
|
25159
|
+
};
|
|
25160
|
+
return /*#__PURE__*/React__namespace.createElement(Choice$1, _extends({}, choiceProps, {
|
|
25161
|
+
ref: forwardedRef
|
|
25162
|
+
}));
|
|
25257
25163
|
}
|
|
25258
25164
|
|
|
25259
|
-
|
|
25260
|
-
className: PropTypes__default["default"].string,
|
|
25261
|
-
content: PropTypes__default["default"].node,
|
|
25262
|
-
showContent: PropTypes__default["default"].bool
|
|
25263
|
-
});
|
|
25264
|
-
|
|
25265
|
-
_defineProperty(ChoiceNoneAbove, "defaultProps", {
|
|
25165
|
+
ChoiceNoneAbove.defaultProps = {
|
|
25266
25166
|
showContent: true
|
|
25267
|
-
}
|
|
25167
|
+
};
|
|
25168
|
+
var ChoiceNoneAbove$1 = /*#__PURE__*/React__namespace.forwardRef((props, ref) => /*#__PURE__*/React__namespace.createElement(ChoiceNoneAbove, _extends({}, props, {
|
|
25169
|
+
forwardedRef: ref
|
|
25170
|
+
})));
|
|
25268
25171
|
|
|
25269
25172
|
const {
|
|
25270
25173
|
captureScratchpadTouchStart: captureScratchpadTouchStart$2
|
|
25271
|
-
} = Util;
|
|
25272
|
-
const ChoicesType = PropTypes__default["default"].arrayOf(PropTypes__default["default"].shape({
|
|
25273
|
-
// Indicates whether this choice is checked.
|
|
25274
|
-
checked: PropTypes__default["default"].bool,
|
|
25275
|
-
// Indicates whether the user has "crossed out" this choice, meaning
|
|
25276
|
-
// that they don't think it's correct. This value does not affect
|
|
25277
|
-
// scoring or other behavior; it's just a note for the user's
|
|
25278
|
-
// reference.
|
|
25279
|
-
crossedOut: PropTypes__default["default"].bool,
|
|
25280
|
-
content: PropTypes__default["default"].node,
|
|
25281
|
-
rationale: PropTypes__default["default"].node,
|
|
25282
|
-
hasRationale: PropTypes__default["default"].bool,
|
|
25283
|
-
showRationale: PropTypes__default["default"].bool,
|
|
25284
|
-
showCorrectness: PropTypes__default["default"].bool,
|
|
25285
|
-
correct: PropTypes__default["default"].bool,
|
|
25286
|
-
originalIndex: PropTypes__default["default"].number,
|
|
25287
|
-
isNoneOfTheAbove: PropTypes__default["default"].bool
|
|
25288
|
-
}));
|
|
25289
|
-
const radioBorderColor = radioBorderColor$2;
|
|
25290
|
-
|
|
25291
|
-
class BaseRadio extends React__namespace.Component {
|
|
25292
|
-
constructor() {
|
|
25293
|
-
super(...arguments);
|
|
25294
|
-
|
|
25295
|
-
_defineProperty(this, "state", {
|
|
25296
|
-
// TODO(mdr): This keeps the ID stable across re-renders on the
|
|
25297
|
-
// same machine, but, at time of writing, the server's state
|
|
25298
|
-
// isn't rehydrated to the client during SSR, so the server and
|
|
25299
|
-
// client will generate different IDs and cause a mismatch
|
|
25300
|
-
// during SSR :(
|
|
25301
|
-
radioGroupName: ___default["default"].uniqueId("perseus_radio_")
|
|
25302
|
-
});
|
|
25303
|
-
|
|
25304
|
-
_defineProperty(this, "updateChoice", (choiceIndex, newValues) => {
|
|
25305
|
-
// Get the baseline `checked` values. If we're checking a new answer
|
|
25306
|
-
// and multiple-select is not on, we should clear all choices to be
|
|
25307
|
-
// unchecked. Otherwise, we should copy the old checked values.
|
|
25308
|
-
let newCheckedList;
|
|
25309
|
-
|
|
25310
|
-
if (newValues.checked && !this.props.multipleSelect) {
|
|
25311
|
-
newCheckedList = this.props.choices.map(_ => false);
|
|
25312
|
-
} else {
|
|
25313
|
-
newCheckedList = this.props.choices.map(c => c.checked);
|
|
25314
|
-
} // Get the baseline `crossedOut` values.
|
|
25315
|
-
|
|
25174
|
+
} = Util; // exported for tests
|
|
25316
25175
|
|
|
25317
|
-
|
|
25318
|
-
|
|
25319
|
-
|
|
25320
|
-
|
|
25321
|
-
|
|
25322
|
-
checked: newCheckedList,
|
|
25323
|
-
crossedOut: newCrossedOutList
|
|
25176
|
+
function getInstructionsText(multipleSelect, countChoices, numCorrect) {
|
|
25177
|
+
if (multipleSelect) {
|
|
25178
|
+
if (countChoices) {
|
|
25179
|
+
return i18n__namespace._("Choose %(numCorrect)s answers:", {
|
|
25180
|
+
numCorrect: numCorrect
|
|
25324
25181
|
});
|
|
25325
|
-
}
|
|
25326
|
-
|
|
25327
|
-
_defineProperty(this, "focus", i => {
|
|
25328
|
-
// $FlowFixMe[incompatible-use]
|
|
25329
|
-
// $FlowFixMe[prop-missing]
|
|
25330
|
-
ReactDOM__default["default"].findDOMNode(this.refs["radio" + (i || 0)]).focus(); // eslint-disable-line react/no-string-refs
|
|
25331
|
-
|
|
25332
|
-
return true;
|
|
25333
|
-
});
|
|
25334
|
-
|
|
25335
|
-
_defineProperty(this, "getInstructionsText", () => {
|
|
25336
|
-
if (this.props.multipleSelect) {
|
|
25337
|
-
if (this.props.countChoices) {
|
|
25338
|
-
return i18n__namespace._("Choose %(numCorrect)s answers:", {
|
|
25339
|
-
numCorrect: this.props.numCorrect
|
|
25340
|
-
});
|
|
25341
|
-
}
|
|
25342
|
-
|
|
25343
|
-
return i18n__namespace._("Choose all answers that apply:");
|
|
25344
|
-
}
|
|
25345
|
-
|
|
25346
|
-
return i18n__namespace._("Choose 1 answer:");
|
|
25347
|
-
});
|
|
25348
|
-
|
|
25349
|
-
_defineProperty(this, "deselectEnabled", () => {
|
|
25350
|
-
// We want to force enable deselect on mobile.
|
|
25351
|
-
return this.props.apiOptions.isMobile || this.props.deselectEnabled;
|
|
25352
|
-
});
|
|
25353
|
-
|
|
25354
|
-
_defineProperty(this, "goToChoice", newChoiceIndex => {
|
|
25355
|
-
const numChoices = this.props.choices.length; // Wrap the newChoiceIndex around the start/end, if necessary.
|
|
25356
|
-
|
|
25357
|
-
if (newChoiceIndex < 0) {
|
|
25358
|
-
newChoiceIndex += numChoices;
|
|
25359
|
-
}
|
|
25360
|
-
|
|
25361
|
-
if (newChoiceIndex >= numChoices) {
|
|
25362
|
-
newChoiceIndex -= numChoices;
|
|
25363
|
-
} // Focus the new choice's input element.
|
|
25364
|
-
// eslint-disable-next-line react/no-string-refs
|
|
25365
|
-
|
|
25366
|
-
|
|
25367
|
-
const choiceInstance = this.refs["radio".concat(newChoiceIndex)];
|
|
25368
|
-
|
|
25369
|
-
if (!choiceInstance) {
|
|
25370
|
-
Log.error("found no choice at index ".concat(newChoiceIndex, ", even after ") + "wrapping it to be within bounds", Errors.Internal, {
|
|
25371
|
-
loggedMetadata: {
|
|
25372
|
-
choices: JSON.stringify(this.props.choices)
|
|
25373
|
-
}
|
|
25374
|
-
});
|
|
25375
|
-
return;
|
|
25376
|
-
}
|
|
25377
|
-
|
|
25378
|
-
choiceInstance.focusInput(); // We aren't going to change any choices' crossed-out state, but we
|
|
25379
|
-
// _are_ going to deselect all choices.
|
|
25380
|
-
|
|
25381
|
-
const newCrossedOutList = this.props.choices.map(c => c.crossedOut);
|
|
25382
|
-
const newCheckedList = this.props.choices.map(c => false); // And, if the new choice isn't crossed out, we'll select it.
|
|
25383
|
-
|
|
25384
|
-
if (!this.props.choices[newChoiceIndex].crossedOut) {
|
|
25385
|
-
newCheckedList[newChoiceIndex] = true;
|
|
25386
|
-
}
|
|
25182
|
+
}
|
|
25387
25183
|
|
|
25388
|
-
|
|
25389
|
-
checked: newCheckedList,
|
|
25390
|
-
crossedOut: newCrossedOutList
|
|
25391
|
-
});
|
|
25392
|
-
});
|
|
25184
|
+
return i18n__namespace._("Choose all answers that apply:");
|
|
25393
25185
|
}
|
|
25394
25186
|
|
|
25395
|
-
|
|
25396
|
-
|
|
25397
|
-
|
|
25398
|
-
|
|
25399
|
-
|
|
25400
|
-
|
|
25401
|
-
|
|
25187
|
+
return i18n__namespace._("Choose 1 answer:");
|
|
25188
|
+
}
|
|
25189
|
+
|
|
25190
|
+
function BaseRadio(props) {
|
|
25191
|
+
const {
|
|
25192
|
+
apiOptions,
|
|
25193
|
+
reviewModeRubric,
|
|
25194
|
+
choices,
|
|
25195
|
+
editMode,
|
|
25196
|
+
multipleSelect,
|
|
25197
|
+
labelWrap,
|
|
25198
|
+
countChoices,
|
|
25199
|
+
numCorrect,
|
|
25200
|
+
isLastUsedWidget,
|
|
25201
|
+
registerFocusFunction
|
|
25202
|
+
} = props; // useEffect doesn't have previous props
|
|
25203
|
+
|
|
25204
|
+
const prevReviewModeRubric = React.useRef();
|
|
25205
|
+
const choiceRefs = React.useRef([]);
|
|
25206
|
+
React.useEffect(() => {
|
|
25207
|
+
// Switching into review mode can sometimes cause the selected answer
|
|
25402
25208
|
// to scroll out of view - for example, when we reveal all those tall
|
|
25403
25209
|
// rationales. This can be disorienting for the user.
|
|
25404
25210
|
//
|
|
@@ -25411,17 +25217,25 @@ class BaseRadio extends React__namespace.Component {
|
|
|
25411
25217
|
// the most recently used widget (because, if the user has since
|
|
25412
25218
|
// touched something else, they're probably not trying to keep their
|
|
25413
25219
|
// eye on this widget anymore).
|
|
25414
|
-
|
|
25415
|
-
if (apiOptions.canScrollPage && isLastUsedWidget && reviewModeRubric && !prevProps.reviewModeRubric) {
|
|
25220
|
+
if (apiOptions.canScrollPage && isLastUsedWidget && reviewModeRubric && !prevReviewModeRubric.current) {
|
|
25416
25221
|
const checkedIndex = choices.findIndex(c => c.checked);
|
|
25417
25222
|
|
|
25418
25223
|
if (checkedIndex >= 0) {
|
|
25419
|
-
const
|
|
25420
|
-
|
|
25421
|
-
|
|
25224
|
+
const ref = choiceRefs.current[checkedIndex]; // note(matthew): we know this is only getting passed
|
|
25225
|
+
// to a WB Clickable button, so we force it to be of
|
|
25226
|
+
// type HTMLButtonElement
|
|
25227
|
+
|
|
25228
|
+
const anyNode = ReactDOM__default["default"].findDOMNode(ref.current);
|
|
25229
|
+
const buttonNode = anyNode;
|
|
25230
|
+
|
|
25231
|
+
if (buttonNode) {
|
|
25232
|
+
scrollElementIntoView(buttonNode);
|
|
25233
|
+
}
|
|
25422
25234
|
}
|
|
25423
25235
|
}
|
|
25424
|
-
|
|
25236
|
+
|
|
25237
|
+
prevReviewModeRubric.current = reviewModeRubric;
|
|
25238
|
+
}, [apiOptions, choices, isLastUsedWidget, reviewModeRubric]); // When a particular choice's `onChange` handler is called, indicating a
|
|
25425
25239
|
// change in a single choice's values, we need to call our `onChange`
|
|
25426
25240
|
// handler in order to notify our parent. However, our API with our parent
|
|
25427
25241
|
// is that we always provide *all* values for *all* choices, even if just
|
|
@@ -25430,181 +25244,197 @@ class BaseRadio extends React__namespace.Component {
|
|
|
25430
25244
|
// will usually cause the old answer to become unchecked.)
|
|
25431
25245
|
//
|
|
25432
25246
|
// So, given the new values for a particular choice, compute the new values
|
|
25433
|
-
// for all choices, and pass them to `
|
|
25247
|
+
// for all choices, and pass them to `onChange`.
|
|
25434
25248
|
//
|
|
25435
25249
|
// `newValues` is an object with two keys: `checked` and `crossedOut`. Each
|
|
25436
25250
|
// contains a boolean value specifying the new checked and crossed-out
|
|
25437
25251
|
// value of this choice.
|
|
25438
25252
|
|
|
25253
|
+
function updateChoice(choiceIndex, newValues) {
|
|
25254
|
+
const {
|
|
25255
|
+
multipleSelect,
|
|
25256
|
+
choices,
|
|
25257
|
+
onChange
|
|
25258
|
+
} = props; // Get the baseline `checked` values. If we're checking a new answer
|
|
25259
|
+
// and multiple-select is not on, we should clear all choices to be
|
|
25260
|
+
// unchecked. Otherwise, we should copy the old checked values.
|
|
25439
25261
|
|
|
25440
|
-
|
|
25441
|
-
const inputType = this.props.multipleSelect ? "checkbox" : "radio";
|
|
25442
|
-
const rubric = this.props.reviewModeRubric;
|
|
25443
|
-
const reviewMode = !!rubric;
|
|
25444
|
-
const styles = BaseRadio.styles;
|
|
25445
|
-
const sat = this.props.apiOptions.satStyling;
|
|
25446
|
-
const isMobile = this.props.apiOptions.isMobile;
|
|
25447
|
-
const choices = this.props.choices;
|
|
25448
|
-
const firstChoiceHighlighted = choices[0].highlighted;
|
|
25449
|
-
const lastChoiceHighlighted = choices[choices.length - 1].highlighted;
|
|
25450
|
-
const className = classNames__default["default"]("perseus-widget-radio", !this.props.editMode && "perseus-rendered-radio", aphrodite.css(styles.radio, // SAT doesn't use the "responsive styling" as it conflicts
|
|
25451
|
-
// with their custom theming.
|
|
25452
|
-
!sat && styles.responsiveRadioContainer, !sat && firstChoiceHighlighted && isMobile && styles.radioContainerFirstHighlighted, !sat && lastChoiceHighlighted && isMobile && styles.radioContainerLastHighlighted, sat && styles.satRadio));
|
|
25453
|
-
const instructionsClassName = classNames__default["default"]("instructions", aphrodite.css(styles.instructions, isMobile && styles.instructionsMobile));
|
|
25454
|
-
const instructions = this.getInstructionsText();
|
|
25455
|
-
const shouldShowInstructions = !sat;
|
|
25456
|
-
const responsiveClassName = aphrodite.css(styles.responsiveFieldset);
|
|
25457
|
-
const fieldset = /*#__PURE__*/React__namespace.createElement("fieldset", {
|
|
25458
|
-
className: "perseus-widget-radio-fieldset ".concat(responsiveClassName)
|
|
25459
|
-
}, /*#__PURE__*/React__namespace.createElement("legend", {
|
|
25460
|
-
className: "perseus-sr-only"
|
|
25461
|
-
}, instructions), shouldShowInstructions && /*#__PURE__*/React__namespace.createElement("div", {
|
|
25462
|
-
className: instructionsClassName
|
|
25463
|
-
}, instructions), /*#__PURE__*/React__namespace.createElement("ul", {
|
|
25464
|
-
className: className,
|
|
25465
|
-
style: {
|
|
25466
|
-
listStyle: "none"
|
|
25467
|
-
}
|
|
25468
|
-
}, this.props.choices.map(function (choice, i) {
|
|
25469
|
-
let Element = Choice;
|
|
25470
|
-
const elementProps = {
|
|
25471
|
-
ref: "radio".concat(i),
|
|
25472
|
-
apiOptions: this.props.apiOptions,
|
|
25473
|
-
checked: choice.checked,
|
|
25474
|
-
crossedOut: choice.crossedOut,
|
|
25475
|
-
previouslyAnswered: choice.previouslyAnswered,
|
|
25476
|
-
reviewMode,
|
|
25477
|
-
correct: choice.correct,
|
|
25478
|
-
rationale: choice.rationale,
|
|
25479
|
-
content: choice.content,
|
|
25480
|
-
disabled: this.props.apiOptions.readOnly || choice.disabled,
|
|
25481
|
-
editMode: this.props.editMode,
|
|
25482
|
-
groupName: this.state.radioGroupName,
|
|
25483
|
-
isLastChoice: i === this.props.choices.length - 1,
|
|
25484
|
-
showCorrectness: reviewMode || !!choice.showCorrectness,
|
|
25485
|
-
showRationale: choice.hasRationale && (reviewMode || choice.showRationale),
|
|
25486
|
-
type: inputType,
|
|
25487
|
-
pos: i,
|
|
25488
|
-
deselectEnabled: this.deselectEnabled(),
|
|
25489
|
-
onChange: newValues => {
|
|
25490
|
-
this.updateChoice(i, newValues);
|
|
25491
|
-
},
|
|
25492
|
-
goToPrevChoice: () => this.goToChoice(i - 1),
|
|
25493
|
-
goToNextChoice: () => this.goToChoice(i + 1)
|
|
25494
|
-
};
|
|
25262
|
+
let newCheckedList;
|
|
25495
25263
|
|
|
25496
|
-
|
|
25497
|
-
|
|
25264
|
+
if (newValues.checked && !multipleSelect) {
|
|
25265
|
+
newCheckedList = choices.map(_ => false);
|
|
25266
|
+
} else {
|
|
25267
|
+
newCheckedList = choices.map(c => c.checked);
|
|
25268
|
+
} // Get the baseline `crossedOut` values.
|
|
25498
25269
|
|
|
25499
|
-
___default["default"].extend(elementProps, {
|
|
25500
|
-
showContent: choice.revealNoneOfTheAbove
|
|
25501
|
-
});
|
|
25502
|
-
}
|
|
25503
25270
|
|
|
25504
|
-
|
|
25505
|
-
|
|
25506
|
-
|
|
25507
|
-
|
|
25508
|
-
|
|
25509
|
-
|
|
25510
|
-
|
|
25511
|
-
|
|
25512
|
-
|
|
25513
|
-
|
|
25514
|
-
|
|
25515
|
-
|
|
25516
|
-
|
|
25517
|
-
|
|
25518
|
-
|
|
25519
|
-
|
|
25520
|
-
|
|
25521
|
-
|
|
25522
|
-
|
|
25523
|
-
|
|
25524
|
-
|
|
25525
|
-
|
|
25526
|
-
|
|
25527
|
-
|
|
25528
|
-
|
|
25529
|
-
|
|
25530
|
-
|
|
25531
|
-
|
|
25532
|
-
|
|
25533
|
-
|
|
25534
|
-
|
|
25535
|
-
|
|
25536
|
-
|
|
25537
|
-
|
|
25538
|
-
|
|
25539
|
-
|
|
25540
|
-
|
|
25541
|
-
|
|
25542
|
-
|
|
25543
|
-
|
|
25544
|
-
|
|
25545
|
-
|
|
25271
|
+
const newCrossedOutList = choices.map(c => c.crossedOut); // Update this choice's `checked` and `crossedOut` values.
|
|
25272
|
+
|
|
25273
|
+
newCheckedList[choiceIndex] = newValues.checked;
|
|
25274
|
+
newCrossedOutList[choiceIndex] = newValues.crossedOut;
|
|
25275
|
+
onChange({
|
|
25276
|
+
checked: newCheckedList,
|
|
25277
|
+
crossedOut: newCrossedOutList
|
|
25278
|
+
});
|
|
25279
|
+
} // register a callback with the parent that allows
|
|
25280
|
+
// the parent to focus an individual choice
|
|
25281
|
+
|
|
25282
|
+
|
|
25283
|
+
registerFocusFunction === null || registerFocusFunction === void 0 ? void 0 : registerFocusFunction(choiceIndex => {
|
|
25284
|
+
const ref = choiceRefs.current[choiceIndex || 0]; // note(matthew): we know this is only getting passed
|
|
25285
|
+
// to a WB Clickable button, so we force it to be of
|
|
25286
|
+
// type HTMLButtonElement
|
|
25287
|
+
|
|
25288
|
+
const anyNode = ReactDOM__default["default"].findDOMNode(ref.current);
|
|
25289
|
+
const buttonNode = anyNode;
|
|
25290
|
+
|
|
25291
|
+
if (buttonNode) {
|
|
25292
|
+
buttonNode.focus();
|
|
25293
|
+
} else {
|
|
25294
|
+
return false;
|
|
25295
|
+
}
|
|
25296
|
+
|
|
25297
|
+
return true;
|
|
25298
|
+
}); // some commonly used shorthands
|
|
25299
|
+
|
|
25300
|
+
const reviewMode = !!reviewModeRubric;
|
|
25301
|
+
const sat = apiOptions.satStyling;
|
|
25302
|
+
const isMobile = apiOptions.isMobile;
|
|
25303
|
+
const firstChoiceHighlighted = choices[0].highlighted;
|
|
25304
|
+
const lastChoiceHighlighted = choices[choices.length - 1].highlighted;
|
|
25305
|
+
const className = classNames__default["default"]("perseus-widget-radio", !editMode && "perseus-rendered-radio", aphrodite.css(styles$r.radio, // SAT doesn't use the "responsive styling" as it conflicts
|
|
25306
|
+
// with their custom theming.
|
|
25307
|
+
!sat && styles$r.responsiveRadioContainer, !sat && firstChoiceHighlighted && isMobile && styles$r.radioContainerFirstHighlighted, !sat && lastChoiceHighlighted && isMobile && styles$r.radioContainerLastHighlighted, sat && styles$r.satRadio));
|
|
25308
|
+
const instructionsClassName = classNames__default["default"]("instructions", aphrodite.css(styles$r.instructions, isMobile && styles$r.instructionsMobile));
|
|
25309
|
+
const instructions = getInstructionsText(multipleSelect, countChoices, numCorrect);
|
|
25310
|
+
const shouldShowInstructions = !sat;
|
|
25311
|
+
const responsiveClassName = aphrodite.css(styles$r.responsiveFieldset);
|
|
25312
|
+
const fieldset = /*#__PURE__*/React__namespace.createElement("fieldset", {
|
|
25313
|
+
className: "perseus-widget-radio-fieldset ".concat(responsiveClassName)
|
|
25314
|
+
}, /*#__PURE__*/React__namespace.createElement("legend", {
|
|
25315
|
+
className: "perseus-sr-only"
|
|
25316
|
+
}, instructions), shouldShowInstructions && /*#__PURE__*/React__namespace.createElement("div", {
|
|
25317
|
+
className: instructionsClassName,
|
|
25318
|
+
"aria-hidden": "true"
|
|
25319
|
+
}, instructions), /*#__PURE__*/React__namespace.createElement("ul", {
|
|
25320
|
+
className: className,
|
|
25321
|
+
style: {
|
|
25322
|
+
listStyle: "none"
|
|
25323
|
+
}
|
|
25324
|
+
}, choices.map(function (choice, i) {
|
|
25325
|
+
let Element = Choice$1;
|
|
25326
|
+
const ref = /*#__PURE__*/React__namespace.createRef();
|
|
25327
|
+
choiceRefs.current[i] = ref;
|
|
25328
|
+
const elementProps = {
|
|
25329
|
+
apiOptions: apiOptions,
|
|
25330
|
+
multipleSelect: multipleSelect,
|
|
25331
|
+
checked: choice.checked,
|
|
25332
|
+
crossedOut: choice.crossedOut,
|
|
25333
|
+
previouslyAnswered: choice.previouslyAnswered,
|
|
25334
|
+
reviewMode,
|
|
25335
|
+
correct: choice.correct,
|
|
25336
|
+
rationale: choice.rationale,
|
|
25337
|
+
content: choice.content,
|
|
25338
|
+
disabled: apiOptions.readOnly || choice.disabled,
|
|
25339
|
+
showCorrectness: reviewMode || !!choice.showCorrectness,
|
|
25340
|
+
showRationale: choice.hasRationale && (reviewMode || choice.showRationale),
|
|
25341
|
+
pos: i,
|
|
25342
|
+
onChange: newValues => {
|
|
25343
|
+
updateChoice(i, newValues);
|
|
25344
|
+
},
|
|
25345
|
+
ref
|
|
25346
|
+
};
|
|
25347
|
+
|
|
25348
|
+
if (choice.isNoneOfTheAbove) {
|
|
25349
|
+
Element = ChoiceNoneAbove$1;
|
|
25546
25350
|
|
|
25547
|
-
|
|
25351
|
+
___default["default"].extend(elementProps, {
|
|
25352
|
+
showContent: choice.revealNoneOfTheAbove
|
|
25353
|
+
});
|
|
25354
|
+
}
|
|
25355
|
+
|
|
25356
|
+
const nextChoice = choices[i + 1];
|
|
25357
|
+
const nextChoiceHighlighted = !!nextChoice && nextChoice.highlighted;
|
|
25358
|
+
|
|
25359
|
+
const aphroditeClassName = checked => {
|
|
25360
|
+
// Whether or not to show correctness borders
|
|
25361
|
+
// for this choice and the next choice.
|
|
25362
|
+
const satShowCorrectness = sat && reviewMode && checked;
|
|
25363
|
+
const satShowCorrectnessNext = sat && reviewMode && nextChoice && nextChoice.checked;
|
|
25364
|
+
return aphrodite.css(sharedStyles.aboveScratchpad, styles$r.item, !sat && styles$r.responsiveItem, !sat && checked && styles$r.selectedItem, !sat && checked && choice.highlighted && styles$r.aboveBackdrop, !sat && checked && choice.highlighted && apiOptions.isMobile && styles$r.aboveBackdropMobile, !sat && nextChoiceHighlighted && apiOptions.isMobile && styles$r.nextHighlighted, sat && styles$r.satRadioOption, satShowCorrectness && !choice.correct && styles$r.satRadioOptionIncorrect, satShowCorrectness && choice.correct && styles$r.satRadioOptionCorrect, satShowCorrectnessNext && !nextChoice.correct && styles$r.satRadioOptionNextIncorrect, satShowCorrectnessNext && nextChoice.correct && styles$r.satRadioOptionNextCorrect, sat && reviewModeRubric && styles$r.satReviewRadioOption);
|
|
25365
|
+
}; // HACK(abdulrahman): Preloads the selection-state
|
|
25366
|
+
// css because of a bug that causes iOS to lag
|
|
25367
|
+
// when selecting the button for the first time.
|
|
25368
|
+
|
|
25369
|
+
|
|
25370
|
+
aphroditeClassName(true);
|
|
25371
|
+
let correctnessClass; // reviewMode is only true if there's a rubric
|
|
25372
|
+
// but Flow doesn't understand that
|
|
25373
|
+
|
|
25374
|
+
if (reviewMode && reviewModeRubric) {
|
|
25375
|
+
correctnessClass = reviewModeRubric.choices[i].correct ? ClassNames.CORRECT : ClassNames.INCORRECT;
|
|
25376
|
+
}
|
|
25377
|
+
|
|
25378
|
+
const className = classNames__default["default"](aphroditeClassName(choice.checked), // TODO(aria): Make test case for these API
|
|
25379
|
+
// classNames
|
|
25380
|
+
ClassNames.RADIO.OPTION, choice.checked && ClassNames.RADIO.SELECTED, correctnessClass); // In edit mode, the Choice renders a Div in order to
|
|
25381
|
+
// allow for the contentEditable area to be selected
|
|
25382
|
+
// (label forces any clicks inside to select the input
|
|
25383
|
+
// element) We have to add some extra behavior to make
|
|
25384
|
+
// sure that we can still check the choice.
|
|
25385
|
+
|
|
25386
|
+
let listElem = null;
|
|
25387
|
+
let clickHandler = null;
|
|
25388
|
+
|
|
25389
|
+
if (editMode) {
|
|
25390
|
+
clickHandler = e => {
|
|
25391
|
+
// Traverse the parent nodes of the clicked
|
|
25392
|
+
// element.
|
|
25393
|
+
let elem = e.target;
|
|
25394
|
+
|
|
25395
|
+
while (elem && elem !== listElem) {
|
|
25396
|
+
// If the clicked element is inside of the
|
|
25397
|
+
// radio icon, then we want to trigger the
|
|
25398
|
+
// check by flipping the choice of the icon.
|
|
25399
|
+
if (elem.getAttribute("data-is-radio-icon")) {
|
|
25400
|
+
this.updateChoice(i, {
|
|
25401
|
+
checked: !choice.checked,
|
|
25402
|
+
crossedOut: choice.crossedOut
|
|
25403
|
+
});
|
|
25404
|
+
return;
|
|
25548
25405
|
}
|
|
25549
|
-
};
|
|
25550
|
-
} // TODO(mattdr): Index isn't a *good* choice of key
|
|
25551
|
-
// here; is there a better one? Can we use choice
|
|
25552
|
-
// content somehow? Would changing our choice of key
|
|
25553
|
-
// somehow break something happening inside a choice's
|
|
25554
|
-
// child Renderers, by changing when we mount/unmount?
|
|
25555
25406
|
|
|
25407
|
+
elem = elem.parentNode;
|
|
25408
|
+
}
|
|
25409
|
+
};
|
|
25410
|
+
} // TODO(mattdr): Index isn't a *good* choice of key
|
|
25411
|
+
// here; is there a better one? Can we use choice
|
|
25412
|
+
// content somehow? Would changing our choice of key
|
|
25413
|
+
// somehow break something happening inside a choice's
|
|
25414
|
+
// child Renderers, by changing when we mount/unmount?
|
|
25556
25415
|
|
|
25557
|
-
return /*#__PURE__*/React__namespace.createElement("li", {
|
|
25558
|
-
key: i,
|
|
25559
|
-
ref: e => listElem = e,
|
|
25560
|
-
className: className,
|
|
25561
|
-
onClick: clickHandler,
|
|
25562
|
-
onTouchStart: !this.props.labelWrap ? null : captureScratchpadTouchStart$2
|
|
25563
|
-
}, /*#__PURE__*/React__namespace.createElement(Element, elementProps));
|
|
25564
|
-
}, this))); // Allow for horizontal scrolling if content is too wide, which may be
|
|
25565
|
-
// an issue especially on phones.
|
|
25566
|
-
// This is disabled in SAT, since it conflicts with their theming.
|
|
25567
25416
|
|
|
25568
|
-
return /*#__PURE__*/React__namespace.createElement("
|
|
25569
|
-
|
|
25570
|
-
|
|
25571
|
-
|
|
25417
|
+
return /*#__PURE__*/React__namespace.createElement("li", {
|
|
25418
|
+
key: i,
|
|
25419
|
+
ref: e => listElem = e,
|
|
25420
|
+
className: className,
|
|
25421
|
+
onClick: clickHandler,
|
|
25422
|
+
onTouchStart: !labelWrap ? null : captureScratchpadTouchStart$2
|
|
25423
|
+
}, /*#__PURE__*/React__namespace.createElement(Element, elementProps));
|
|
25424
|
+
}, this))); // Allow for horizontal scrolling if content is too wide, which may be
|
|
25425
|
+
// an issue especially on phones.
|
|
25426
|
+
// This is disabled in SAT, since it conflicts with their theming.
|
|
25572
25427
|
|
|
25428
|
+
return /*#__PURE__*/React__namespace.createElement("div", {
|
|
25429
|
+
className: aphrodite.css(!sat && styles$r.responsiveContainer)
|
|
25430
|
+
}, fieldset);
|
|
25573
25431
|
}
|
|
25574
25432
|
|
|
25575
|
-
|
|
25576
|
-
|
|
25577
|
-
|
|
25578
|
-
|
|
25579
|
-
|
|
25580
|
-
styling: PropTypes__default["default"].shape({
|
|
25581
|
-
radioStyleVersion: PropTypes__default["default"].oneOf(["intermediate", "final"])
|
|
25582
|
-
}),
|
|
25583
|
-
canScrollPage: PropTypes__default["default"].bool
|
|
25584
|
-
}),
|
|
25585
|
-
choices: ChoicesType,
|
|
25586
|
-
deselectEnabled: PropTypes__default["default"].bool,
|
|
25587
|
-
editMode: PropTypes__default["default"].bool,
|
|
25588
|
-
labelWrap: PropTypes__default["default"].bool,
|
|
25589
|
-
countChoices: PropTypes__default["default"].bool,
|
|
25590
|
-
numCorrect: PropTypes__default["default"].number,
|
|
25591
|
-
multipleSelect: PropTypes__default["default"].bool,
|
|
25592
|
-
reviewModeRubric: PropTypes__default["default"].shape({
|
|
25593
|
-
choices: ChoicesType
|
|
25594
|
-
}),
|
|
25595
|
-
// A callback indicating that this choice has changed. Its argument is
|
|
25596
|
-
// an object with two keys: `checked` and `crossedOut`. Each contains
|
|
25597
|
-
// an array of boolean values, specifying the new checked and
|
|
25598
|
-
// crossed-out value of each choice.
|
|
25599
|
-
onChange: PropTypes__default["default"].func,
|
|
25600
|
-
// Whether this widget was the most recently used widget in this
|
|
25601
|
-
// Renderer. Determines whether we'll auto-scroll the page upon
|
|
25602
|
-
// entering review mode.
|
|
25603
|
-
isLastUsedWidget: PropTypes__default["default"].bool
|
|
25604
|
-
});
|
|
25605
|
-
|
|
25606
|
-
_defineProperty(BaseRadio, "styles", aphrodite.StyleSheet.create({
|
|
25607
|
-
// eslint-disable-next-line react-native/no-unused-styles
|
|
25433
|
+
BaseRadio.defaultProps = {
|
|
25434
|
+
editMode: false,
|
|
25435
|
+
multipleSelect: false
|
|
25436
|
+
};
|
|
25437
|
+
const styles$r = aphrodite.StyleSheet.create({
|
|
25608
25438
|
instructions: {
|
|
25609
25439
|
display: "block",
|
|
25610
25440
|
color: gray17,
|
|
@@ -25614,7 +25444,6 @@ _defineProperty(BaseRadio, "styles", aphrodite.StyleSheet.create({
|
|
|
25614
25444
|
fontWeight: "bold",
|
|
25615
25445
|
marginBottom: 16
|
|
25616
25446
|
},
|
|
25617
|
-
// eslint-disable-next-line react-native/no-unused-styles
|
|
25618
25447
|
instructionsMobile: {
|
|
25619
25448
|
fontSize: 18,
|
|
25620
25449
|
[mediaQueries.smOrSmaller]: {
|
|
@@ -25628,36 +25457,30 @@ _defineProperty(BaseRadio, "styles", aphrodite.StyleSheet.create({
|
|
|
25628
25457
|
fontSize: 20
|
|
25629
25458
|
}
|
|
25630
25459
|
},
|
|
25631
|
-
// eslint-disable-next-line react-native/no-unused-styles
|
|
25632
25460
|
radio: {
|
|
25633
25461
|
// Avoid centering
|
|
25634
25462
|
width: "100%"
|
|
25635
25463
|
},
|
|
25636
|
-
// eslint-disable-next-line react-native/no-unused-styles
|
|
25637
25464
|
responsiveRadioContainer: {
|
|
25638
|
-
borderBottom: "1px solid ".concat(radioBorderColor),
|
|
25639
|
-
borderTop: "1px solid ".concat(radioBorderColor),
|
|
25465
|
+
borderBottom: "1px solid ".concat(radioBorderColor$1),
|
|
25466
|
+
borderTop: "1px solid ".concat(radioBorderColor$1),
|
|
25640
25467
|
width: "auto",
|
|
25641
25468
|
[mediaQueries.smOrSmaller]: {
|
|
25642
25469
|
marginLeft: negativePhoneMargin,
|
|
25643
25470
|
marginRight: negativePhoneMargin
|
|
25644
25471
|
}
|
|
25645
25472
|
},
|
|
25646
|
-
// eslint-disable-next-line react-native/no-unused-styles
|
|
25647
25473
|
radioContainerFirstHighlighted: {
|
|
25648
25474
|
borderTop: "1px solid rgba(0, 0, 0, 0)"
|
|
25649
25475
|
},
|
|
25650
|
-
// eslint-disable-next-line react-native/no-unused-styles
|
|
25651
25476
|
radioContainerLastHighlighted: {
|
|
25652
25477
|
borderBottom: "1px solid rgba(0, 0, 0, 0)"
|
|
25653
25478
|
},
|
|
25654
|
-
// eslint-disable-next-line react-native/no-unused-styles
|
|
25655
25479
|
satRadio: {
|
|
25656
25480
|
background: "none",
|
|
25657
25481
|
marginLeft: 0,
|
|
25658
25482
|
userSelect: "none"
|
|
25659
25483
|
},
|
|
25660
|
-
// eslint-disable-next-line react-native/no-unused-styles
|
|
25661
25484
|
satRadioOption: {
|
|
25662
25485
|
margin: 0,
|
|
25663
25486
|
padding: 0,
|
|
@@ -25666,57 +25489,40 @@ _defineProperty(BaseRadio, "styles", aphrodite.StyleSheet.create({
|
|
|
25666
25489
|
borderTop: "1px solid #ccc"
|
|
25667
25490
|
}
|
|
25668
25491
|
},
|
|
25669
|
-
// eslint-disable-next-line react-native/no-unused-styles
|
|
25670
25492
|
satRadioOptionCorrect: {
|
|
25671
25493
|
borderBottomColor: satCorrectBorderColor,
|
|
25672
25494
|
":first-child": {
|
|
25673
25495
|
borderTopColor: satCorrectBorderColor
|
|
25674
25496
|
}
|
|
25675
25497
|
},
|
|
25676
|
-
// eslint-disable-next-line react-native/no-unused-styles
|
|
25677
25498
|
satRadioOptionIncorrect: {
|
|
25678
25499
|
borderBottomColor: satIncorrectBorderColor,
|
|
25679
25500
|
":first-child": {
|
|
25680
25501
|
borderTopColor: satIncorrectBorderColor
|
|
25681
25502
|
}
|
|
25682
25503
|
},
|
|
25683
|
-
// eslint-disable-next-line react-native/no-unused-styles
|
|
25684
25504
|
satRadioOptionNextCorrect: {
|
|
25685
25505
|
borderBottomColor: satCorrectBorderColor
|
|
25686
25506
|
},
|
|
25687
|
-
// eslint-disable-next-line react-native/no-unused-styles
|
|
25688
25507
|
satRadioOptionNextIncorrect: {
|
|
25689
25508
|
borderBottomColor: satIncorrectBorderColor
|
|
25690
25509
|
},
|
|
25691
|
-
// eslint-disable-next-line react-native/no-unused-styles
|
|
25692
25510
|
satReviewRadioOption: {
|
|
25693
25511
|
pointerEvents: "none"
|
|
25694
25512
|
},
|
|
25695
|
-
// eslint-disable-next-line react-native/no-unused-styles
|
|
25696
25513
|
item: {
|
|
25697
25514
|
marginLeft: 20
|
|
25698
25515
|
},
|
|
25699
|
-
// eslint-disable-next-line react-native/no-unused-styles
|
|
25700
|
-
inlineItem: {
|
|
25701
|
-
display: "inline-block",
|
|
25702
|
-
paddingLeft: 20,
|
|
25703
|
-
verticalAlign: "middle" // See http://stackoverflow.com/q/8120466 for explanation of
|
|
25704
|
-
// why vertical align property is needed
|
|
25705
|
-
|
|
25706
|
-
},
|
|
25707
|
-
// eslint-disable-next-line react-native/no-unused-styles
|
|
25708
25516
|
responsiveItem: {
|
|
25709
25517
|
marginLeft: 0,
|
|
25710
25518
|
padding: 0,
|
|
25711
25519
|
":not(:last-child)": {
|
|
25712
|
-
borderBottom: "1px solid ".concat(radioBorderColor)
|
|
25520
|
+
borderBottom: "1px solid ".concat(radioBorderColor$1)
|
|
25713
25521
|
}
|
|
25714
25522
|
},
|
|
25715
|
-
// eslint-disable-next-line react-native/no-unused-styles
|
|
25716
25523
|
selectedItem: {
|
|
25717
25524
|
background: "white"
|
|
25718
25525
|
},
|
|
25719
|
-
// eslint-disable-next-line react-native/no-unused-styles
|
|
25720
25526
|
aboveBackdrop: {
|
|
25721
25527
|
position: "relative",
|
|
25722
25528
|
// HACK(emily): We want selected choices to show up above our
|
|
@@ -25725,20 +25531,17 @@ _defineProperty(BaseRadio, "styles", aphrodite.StyleSheet.create({
|
|
|
25725
25531
|
// coordinated between here and webapp. :(
|
|
25726
25532
|
zIndex: 1062
|
|
25727
25533
|
},
|
|
25728
|
-
// eslint-disable-next-line react-native/no-unused-styles
|
|
25729
25534
|
aboveBackdropMobile: {
|
|
25730
25535
|
boxShadow: "0 0 4px 0 rgba(0, 0, 0, 0.2)," + "0 0 2px 0 rgba(0, 0, 0, 0.1)",
|
|
25731
25536
|
":not(:last-child)": {
|
|
25732
25537
|
borderBottom: "1px solid rgba(0, 0, 0, 0)"
|
|
25733
25538
|
}
|
|
25734
25539
|
},
|
|
25735
|
-
// eslint-disable-next-line react-native/no-unused-styles
|
|
25736
25540
|
nextHighlighted: {
|
|
25737
25541
|
":not(:last-child)": {
|
|
25738
25542
|
borderBottom: "1px solid rgba(0, 0, 0, 0)"
|
|
25739
25543
|
}
|
|
25740
25544
|
},
|
|
25741
|
-
// eslint-disable-next-line react-native/no-unused-styles
|
|
25742
25545
|
responsiveContainer: {
|
|
25743
25546
|
overflow: "auto",
|
|
25744
25547
|
marginLeft: negativePhoneMargin,
|
|
@@ -25746,22 +25549,22 @@ _defineProperty(BaseRadio, "styles", aphrodite.StyleSheet.create({
|
|
|
25746
25549
|
paddingLeft: phoneMargin // paddingRight is handled by responsiveFieldset
|
|
25747
25550
|
|
|
25748
25551
|
},
|
|
25749
|
-
// eslint-disable-next-line react-native/no-unused-styles
|
|
25750
25552
|
responsiveFieldset: {
|
|
25751
25553
|
paddingRight: phoneMargin
|
|
25752
25554
|
}
|
|
25753
|
-
}));
|
|
25754
|
-
|
|
25755
|
-
_defineProperty(BaseRadio, "defaultProps", {
|
|
25756
|
-
editMode: false
|
|
25757
25555
|
});
|
|
25758
25556
|
|
|
25759
25557
|
class Radio$1 extends React__namespace.Component {
|
|
25760
25558
|
constructor() {
|
|
25559
|
+
var _this;
|
|
25560
|
+
|
|
25761
25561
|
super(...arguments);
|
|
25562
|
+
_this = this;
|
|
25563
|
+
|
|
25564
|
+
_defineProperty(this, "focusFunction", void 0);
|
|
25762
25565
|
|
|
25763
|
-
_defineProperty(this, "_renderRenderer",
|
|
25764
|
-
content =
|
|
25566
|
+
_defineProperty(this, "_renderRenderer", function () {
|
|
25567
|
+
let content = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : "";
|
|
25765
25568
|
let nextPassageRefId = 1;
|
|
25766
25569
|
const widgets = {};
|
|
25767
25570
|
const modContent = content.replace(/\{\{passage-ref (\d+) (\d+)(?: "([^"]*)")?\}\}/g, (match, passageNum, refNum, summaryText) => {
|
|
@@ -25793,19 +25596,14 @@ class Radio$1 extends React__namespace.Component {
|
|
|
25793
25596
|
key: "choiceContentRenderer",
|
|
25794
25597
|
content: modContent,
|
|
25795
25598
|
widgets: widgets,
|
|
25796
|
-
findExternalWidgets:
|
|
25599
|
+
findExternalWidgets: _this.props.findWidgets,
|
|
25797
25600
|
alwaysUpdate: true,
|
|
25798
|
-
linterContext: { ...
|
|
25601
|
+
linterContext: { ..._this.props.linterContext,
|
|
25799
25602
|
blockHighlight: true
|
|
25800
25603
|
}
|
|
25801
25604
|
});
|
|
25802
25605
|
});
|
|
25803
25606
|
|
|
25804
|
-
_defineProperty(this, "focus", i => {
|
|
25805
|
-
// eslint-disable-next-line react/no-string-refs
|
|
25806
|
-
return this.refs.baseRadio.focus(i);
|
|
25807
|
-
});
|
|
25808
|
-
|
|
25809
25607
|
_defineProperty(this, "updateChoices", newValueLists => {
|
|
25810
25608
|
const {
|
|
25811
25609
|
choiceStates,
|
|
@@ -25816,25 +25614,18 @@ class Radio$1 extends React__namespace.Component {
|
|
|
25816
25614
|
// should clone the old `choiceStates` objects, in preparation to
|
|
25817
25615
|
// mutate them.
|
|
25818
25616
|
|
|
25819
|
-
|
|
25820
|
-
|
|
25821
|
-
|
|
25822
|
-
|
|
25823
|
-
|
|
25824
|
-
|
|
25825
|
-
|
|
25826
|
-
|
|
25827
|
-
|
|
25828
|
-
|
|
25829
|
-
rationaleShown: false,
|
|
25830
|
-
correctnessShown: false,
|
|
25831
|
-
previouslyAnswered: false,
|
|
25832
|
-
readOnly: false
|
|
25833
|
-
}));
|
|
25834
|
-
} // Mutate the new `choiceState` objects, according to the new `checked`
|
|
25617
|
+
const newChoiceStates = choiceStates ? choiceStates.map(state => ({ ...state
|
|
25618
|
+
})) : choices.map(() => ({
|
|
25619
|
+
selected: false,
|
|
25620
|
+
crossedOut: false,
|
|
25621
|
+
highlighted: false,
|
|
25622
|
+
rationaleShown: false,
|
|
25623
|
+
correctnessShown: false,
|
|
25624
|
+
previouslyAnswered: false,
|
|
25625
|
+
readOnly: false
|
|
25626
|
+
})); // Mutate the new `choiceState` objects, according to the new `checked`
|
|
25835
25627
|
// and `crossedOut` values provided in `newValueLists`.
|
|
25836
25628
|
|
|
25837
|
-
|
|
25838
25629
|
newChoiceStates.forEach((choiceState, i) => {
|
|
25839
25630
|
choiceState.selected = newValueLists.checked[i];
|
|
25840
25631
|
choiceState.crossedOut = newValueLists.crossedOut[i];
|
|
@@ -25898,14 +25689,47 @@ class Radio$1 extends React__namespace.Component {
|
|
|
25898
25689
|
});
|
|
25899
25690
|
}
|
|
25900
25691
|
|
|
25692
|
+
// TODO(LP-10672): I think this might be unused right now. I can't find anywhere
|
|
25693
|
+
// that we pass a value to `.focus()` and it seems to have been used for
|
|
25694
|
+
// adding hints when editing.
|
|
25695
|
+
// See: https://github.com/Khan/perseus/blame/e18582b4b69959270b90e237ef1813899711ddfa/src/widgets/radio.js#L169
|
|
25696
|
+
focus(choiceIndex) {
|
|
25697
|
+
if (this.focusFunction) {
|
|
25698
|
+
return this.focusFunction(choiceIndex);
|
|
25699
|
+
}
|
|
25700
|
+
|
|
25701
|
+
return false;
|
|
25702
|
+
} // lets BaseRadio regiser a focus callback so widget
|
|
25703
|
+
// can focus an individual choice
|
|
25704
|
+
|
|
25705
|
+
|
|
25706
|
+
registerFocusFunction(fun) {
|
|
25707
|
+
this.focusFunction = fun;
|
|
25708
|
+
} // When `BaseRadio`'s `onChange` handler is called, indicating a change in
|
|
25709
|
+
// our choices' state, we need to call our `onChange` handler in order to
|
|
25710
|
+
// persist those changes in the item's Perseus state.
|
|
25711
|
+
//
|
|
25712
|
+
// So, given the new values for each choice, construct the new
|
|
25713
|
+
// `choiceStates` objects, and pass them to `this.props.onChange`.
|
|
25714
|
+
//
|
|
25715
|
+
// `newValueLists` is an object with two keys: `checked` and `crossedOut`.
|
|
25716
|
+
// Each contains an array of boolean values, specifying the new checked and
|
|
25717
|
+
// crossed-out value of each choice.
|
|
25718
|
+
//
|
|
25719
|
+
// NOTE(mdr): This method expects to be auto-bound. If this component is
|
|
25720
|
+
// converted to an ES6 class, take care to auto-bind this method!
|
|
25721
|
+
|
|
25722
|
+
|
|
25901
25723
|
render() {
|
|
25902
|
-
|
|
25724
|
+
const {
|
|
25725
|
+
choices
|
|
25726
|
+
} = this.props;
|
|
25903
25727
|
let choiceStates;
|
|
25904
25728
|
|
|
25905
25729
|
if (this.props.static) {
|
|
25906
|
-
choiceStates =
|
|
25907
|
-
selected:
|
|
25908
|
-
crossedOut:
|
|
25730
|
+
choiceStates = choices.map(choice => ({
|
|
25731
|
+
selected: !!choice.correct,
|
|
25732
|
+
crossedOut: false,
|
|
25909
25733
|
readOnly: true,
|
|
25910
25734
|
highlighted: false,
|
|
25911
25735
|
rationaleShown: true,
|
|
@@ -25913,12 +25737,12 @@ class Radio$1 extends React__namespace.Component {
|
|
|
25913
25737
|
previouslyAnswered: false
|
|
25914
25738
|
}));
|
|
25915
25739
|
} else if (this.props.choiceStates) {
|
|
25916
|
-
choiceStates = this.props.choiceStates;
|
|
25740
|
+
choiceStates = this.props.choiceStates;
|
|
25917
25741
|
} else if (this.props.values) {
|
|
25918
25742
|
// Support legacy choiceStates implementation
|
|
25919
25743
|
|
|
25920
25744
|
/* istanbul ignore next - props.values is deprecated */
|
|
25921
|
-
choiceStates =
|
|
25745
|
+
choiceStates = this.props.values.map(val => ({
|
|
25922
25746
|
selected: val,
|
|
25923
25747
|
crossedOut: false,
|
|
25924
25748
|
readOnly: false,
|
|
@@ -25928,7 +25752,7 @@ class Radio$1 extends React__namespace.Component {
|
|
|
25928
25752
|
previouslyAnswered: false
|
|
25929
25753
|
}));
|
|
25930
25754
|
} else {
|
|
25931
|
-
choiceStates =
|
|
25755
|
+
choiceStates = choices.map(() => ({
|
|
25932
25756
|
selected: false,
|
|
25933
25757
|
crossedOut: false,
|
|
25934
25758
|
readOnly: false,
|
|
@@ -25939,7 +25763,7 @@ class Radio$1 extends React__namespace.Component {
|
|
|
25939
25763
|
}));
|
|
25940
25764
|
}
|
|
25941
25765
|
|
|
25942
|
-
|
|
25766
|
+
const choicesProp = choices.map((choice, i) => {
|
|
25943
25767
|
const content = choice.isNoneOfTheAbove && !choice.content ? // we use i18n._ instead of $_ here because the content
|
|
25944
25768
|
// sent to a renderer needs to be a string, not a react
|
|
25945
25769
|
// node (/renderable/fragment).
|
|
@@ -25967,37 +25791,36 @@ class Radio$1 extends React__namespace.Component {
|
|
|
25967
25791
|
// TODO(emily): Come up with a more comprehensive way to solve
|
|
25968
25792
|
// this sort of "serialized state breaks when internal
|
|
25969
25793
|
// structure changes" problem.
|
|
25970
|
-
correct:
|
|
25794
|
+
correct: choice.correct === undefined ? !!reviewChoice && !!reviewChoice.correct : choice.correct,
|
|
25971
25795
|
disabled: readOnly,
|
|
25972
25796
|
hasRationale: !!choice.clue,
|
|
25973
25797
|
rationale: this._renderRenderer(choice.clue),
|
|
25974
25798
|
showRationale: rationaleShown,
|
|
25975
25799
|
showCorrectness: correctnessShown,
|
|
25976
|
-
isNoneOfTheAbove: choice.isNoneOfTheAbove,
|
|
25977
|
-
revealNoneOfTheAbove: this.props.questionCompleted && selected,
|
|
25800
|
+
isNoneOfTheAbove: !!choice.isNoneOfTheAbove,
|
|
25801
|
+
revealNoneOfTheAbove: !!(this.props.questionCompleted && selected),
|
|
25978
25802
|
crossedOut,
|
|
25979
25803
|
highlighted,
|
|
25980
25804
|
previouslyAnswered: previouslyAnswered
|
|
25981
25805
|
};
|
|
25982
25806
|
});
|
|
25983
|
-
return /*#__PURE__*/React__namespace.createElement(BaseRadio
|
|
25984
|
-
, {
|
|
25985
|
-
ref: "baseRadio",
|
|
25807
|
+
return /*#__PURE__*/React__namespace.createElement(BaseRadio, {
|
|
25986
25808
|
labelWrap: true,
|
|
25987
25809
|
multipleSelect: this.props.multipleSelect,
|
|
25988
25810
|
countChoices: this.props.countChoices,
|
|
25989
25811
|
numCorrect: this.props.numCorrect,
|
|
25990
|
-
choices:
|
|
25812
|
+
choices: choicesProp,
|
|
25991
25813
|
onChange: this.updateChoices,
|
|
25992
25814
|
reviewModeRubric: this.props.reviewModeRubric,
|
|
25993
25815
|
deselectEnabled: this.props.deselectEnabled,
|
|
25994
25816
|
apiOptions: this.props.apiOptions,
|
|
25995
|
-
isLastUsedWidget: this.props.isLastUsedWidget
|
|
25817
|
+
isLastUsedWidget: this.props.isLastUsedWidget,
|
|
25818
|
+
registerFocusFunction: i => this.registerFocusFunction(i)
|
|
25996
25819
|
});
|
|
25997
25820
|
}
|
|
25998
25821
|
|
|
25999
25822
|
static validate(userInput, rubric) {
|
|
26000
|
-
const numSelected =
|
|
25823
|
+
const numSelected = userInput.choicesSelected.reduce((sum, selected) => {
|
|
26001
25824
|
return sum + (selected ? 1 : 0);
|
|
26002
25825
|
}, 0);
|
|
26003
25826
|
|
|
@@ -26006,10 +25829,9 @@ class Radio$1 extends React__namespace.Component {
|
|
|
26006
25829
|
type: "invalid",
|
|
26007
25830
|
message: null
|
|
26008
25831
|
};
|
|
26009
|
-
}
|
|
26010
|
-
|
|
25832
|
+
}
|
|
26011
25833
|
|
|
26012
|
-
if (userInput.numCorrect > 1 && numSelected !== userInput.numCorrect) {
|
|
25834
|
+
if (userInput.numCorrect && userInput.numCorrect > 1 && numSelected !== userInput.numCorrect) {
|
|
26013
25835
|
return {
|
|
26014
25836
|
type: "invalid",
|
|
26015
25837
|
message: i18n__namespace._("Please choose the correct number of answers.")
|
|
@@ -26022,14 +25844,12 @@ class Radio$1 extends React__namespace.Component {
|
|
|
26022
25844
|
message: i18n__namespace._("'None of the above' may not be selected " + "when other answers are selected.")
|
|
26023
25845
|
};
|
|
26024
25846
|
}
|
|
26025
|
-
/* jshint -W018 */
|
|
26026
25847
|
|
|
26027
|
-
|
|
26028
|
-
const correct = ___default["default"].all(userInput.choicesSelected, (selected, i) => {
|
|
25848
|
+
const correct = userInput.choicesSelected.every((selected, i) => {
|
|
26029
25849
|
let isCorrect;
|
|
26030
25850
|
|
|
26031
25851
|
if (userInput.noneOfTheAboveIndex === i) {
|
|
26032
|
-
isCorrect =
|
|
25852
|
+
isCorrect = rubric.choices.every((choice, j) => {
|
|
26033
25853
|
return i === j || !choice.correct;
|
|
26034
25854
|
});
|
|
26035
25855
|
} else {
|
|
@@ -26038,9 +25858,6 @@ class Radio$1 extends React__namespace.Component {
|
|
|
26038
25858
|
|
|
26039
25859
|
return isCorrect === selected;
|
|
26040
25860
|
});
|
|
26041
|
-
/* jshint +W018 */
|
|
26042
|
-
|
|
26043
|
-
|
|
26044
25861
|
return {
|
|
26045
25862
|
type: "points",
|
|
26046
25863
|
earned: correct ? 1 : 0,
|
|
@@ -26062,9 +25879,7 @@ class Radio$1 extends React__namespace.Component {
|
|
|
26062
25879
|
const numCorrect = props.numCorrect;
|
|
26063
25880
|
|
|
26064
25881
|
for (let i = 0; i < choicesSelected.length; i++) {
|
|
26065
|
-
|
|
26066
|
-
const index = props.choices[i].originalIndex; // $FlowFixMe[incompatible-use]
|
|
26067
|
-
|
|
25882
|
+
const index = props.choices[i].originalIndex;
|
|
26068
25883
|
choicesSelected[index] = choiceStates[i].selected;
|
|
26069
25884
|
|
|
26070
25885
|
if (props.choices[i].isNoneOfTheAbove) {
|
|
@@ -26085,31 +25900,35 @@ class Radio$1 extends React__namespace.Component {
|
|
|
26085
25900
|
}; // Support legacy choiceState implementation
|
|
26086
25901
|
}
|
|
26087
25902
|
/* istanbul ignore if - props.values is deprecated */
|
|
26088
|
-
// $FlowFixMe[prop-missing]
|
|
26089
25903
|
|
|
26090
25904
|
|
|
26091
|
-
|
|
25905
|
+
const {
|
|
25906
|
+
values
|
|
25907
|
+
} = props;
|
|
25908
|
+
|
|
25909
|
+
if (values) {
|
|
26092
25910
|
let noneOfTheAboveIndex = null;
|
|
26093
25911
|
let noneOfTheAboveSelected = false;
|
|
26094
|
-
const
|
|
25912
|
+
const choicesSelected = [...values];
|
|
26095
25913
|
const countChoices = props.countChoices;
|
|
26096
25914
|
const numCorrect = props.numCorrect;
|
|
25915
|
+
const valuesLength = values.length;
|
|
26097
25916
|
|
|
26098
|
-
for (let i = 0; i <
|
|
25917
|
+
for (let i = 0; i < valuesLength; i++) {
|
|
26099
25918
|
const index = props.choices[i].originalIndex;
|
|
26100
|
-
|
|
25919
|
+
choicesSelected[index] = values[i];
|
|
26101
25920
|
|
|
26102
25921
|
if (props.choices[i].isNoneOfTheAbove) {
|
|
26103
25922
|
noneOfTheAboveIndex = index;
|
|
26104
25923
|
|
|
26105
|
-
if (
|
|
25924
|
+
if (choicesSelected[i]) {
|
|
26106
25925
|
noneOfTheAboveSelected = true;
|
|
26107
25926
|
}
|
|
26108
25927
|
}
|
|
26109
25928
|
}
|
|
26110
25929
|
|
|
26111
25930
|
return {
|
|
26112
|
-
choicesSelected
|
|
25931
|
+
choicesSelected,
|
|
26113
25932
|
noneOfTheAboveIndex,
|
|
26114
25933
|
noneOfTheAboveSelected,
|
|
26115
25934
|
countChoices,
|
|
@@ -26119,15 +25938,14 @@ class Radio$1 extends React__namespace.Component {
|
|
|
26119
25938
|
|
|
26120
25939
|
|
|
26121
25940
|
return {
|
|
26122
|
-
choicesSelected:
|
|
25941
|
+
choicesSelected: props.choices.map(() => false)
|
|
26123
25942
|
};
|
|
26124
25943
|
}
|
|
26125
25944
|
|
|
26126
25945
|
}
|
|
26127
25946
|
|
|
26128
25947
|
_defineProperty(Radio$1, "defaultProps", {
|
|
26129
|
-
choices: [
|
|
26130
|
-
displayCount: null,
|
|
25948
|
+
choices: [],
|
|
26131
25949
|
multipleSelect: false,
|
|
26132
25950
|
countChoices: false,
|
|
26133
25951
|
deselectEnabled: false,
|
|
@@ -26158,14 +25976,14 @@ const _choiceTransform = (widgetOptions, problemNum) => {
|
|
|
26158
25976
|
|
|
26159
25977
|
const _addNoneOfAbove = function (choices) {
|
|
26160
25978
|
let noneOfTheAbove = null;
|
|
26161
|
-
|
|
26162
|
-
const newChoices = ___default["default"].reject(choices, function (choice, index) {
|
|
25979
|
+
const newChoices = choices.filter((choice, index) => {
|
|
26163
25980
|
if (choice.isNoneOfTheAbove) {
|
|
26164
25981
|
noneOfTheAbove = choice;
|
|
26165
|
-
return
|
|
25982
|
+
return false;
|
|
26166
25983
|
}
|
|
26167
|
-
}); // Place the "None of the above" options last
|
|
26168
25984
|
|
|
25985
|
+
return true;
|
|
25986
|
+
}); // Place the "None of the above" options last
|
|
26169
25987
|
|
|
26170
25988
|
if (noneOfTheAbove) {
|
|
26171
25989
|
newChoices.push(noneOfTheAbove);
|
|
@@ -26185,12 +26003,11 @@ const _choiceTransform = (widgetOptions, problemNum) => {
|
|
|
26185
26003
|
}; // Add meta-information to choices
|
|
26186
26004
|
|
|
26187
26005
|
|
|
26188
|
-
|
|
26189
|
-
|
|
26190
|
-
return ___default["default"].extend({}, choice, {
|
|
26006
|
+
const choices = widgetOptions.choices.map((choice, i) => {
|
|
26007
|
+
return { ...choice,
|
|
26191
26008
|
originalIndex: i,
|
|
26192
26009
|
correct: Boolean(choice.correct)
|
|
26193
|
-
}
|
|
26010
|
+
};
|
|
26194
26011
|
}); // Apply all the transforms. Note that the order we call these is
|
|
26195
26012
|
// important!
|
|
26196
26013
|
// 3) finally add "None of the above" to the bottom
|
|
@@ -26775,7 +26592,7 @@ class Definition extends React__namespace.Component {
|
|
|
26775
26592
|
}, /*#__PURE__*/React__namespace.createElement("span", {
|
|
26776
26593
|
className: "perseus-widget-definition"
|
|
26777
26594
|
}, /*#__PURE__*/React__namespace.createElement(Button__default["default"], {
|
|
26778
|
-
size: "
|
|
26595
|
+
size: "medium",
|
|
26779
26596
|
kind: "tertiary",
|
|
26780
26597
|
onClick: () => {
|
|
26781
26598
|
this.props.trackInteraction();
|
|
@@ -28984,6 +28801,8 @@ class Group extends React__namespace.Component {
|
|
|
28984
28801
|
super(...arguments);
|
|
28985
28802
|
_this = this;
|
|
28986
28803
|
|
|
28804
|
+
_defineProperty(this, "rendererRef", void 0);
|
|
28805
|
+
|
|
28987
28806
|
_defineProperty(this, "change", function () {
|
|
28988
28807
|
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
|
|
28989
28808
|
args[_key] = arguments[_key];
|
|
@@ -28993,56 +28812,66 @@ class Group extends React__namespace.Component {
|
|
|
28993
28812
|
});
|
|
28994
28813
|
|
|
28995
28814
|
_defineProperty(this, "getUserInput", () => {
|
|
28996
|
-
|
|
28997
|
-
|
|
28815
|
+
var _this$rendererRef;
|
|
28816
|
+
|
|
28817
|
+
return (_this$rendererRef = this.rendererRef) === null || _this$rendererRef === void 0 ? void 0 : _this$rendererRef.getUserInput();
|
|
28998
28818
|
});
|
|
28999
28819
|
|
|
29000
28820
|
_defineProperty(this, "getSerializedState", () => {
|
|
29001
|
-
|
|
29002
|
-
|
|
28821
|
+
var _this$rendererRef2;
|
|
28822
|
+
|
|
28823
|
+
return (_this$rendererRef2 = this.rendererRef) === null || _this$rendererRef2 === void 0 ? void 0 : _this$rendererRef2.getSerializedState();
|
|
29003
28824
|
});
|
|
29004
28825
|
|
|
29005
28826
|
_defineProperty(this, "restoreSerializedState", (state, callback) => {
|
|
29006
|
-
|
|
29007
|
-
|
|
28827
|
+
var _this$rendererRef3;
|
|
28828
|
+
|
|
28829
|
+
(_this$rendererRef3 = this.rendererRef) === null || _this$rendererRef3 === void 0 ? void 0 : _this$rendererRef3.restoreSerializedState(state, callback); // Tell our renderer that we have no props to change
|
|
29008
28830
|
// (all our changes were in state):
|
|
29009
28831
|
|
|
29010
28832
|
return null;
|
|
29011
28833
|
});
|
|
29012
28834
|
|
|
29013
28835
|
_defineProperty(this, "simpleValidate", rubric => {
|
|
29014
|
-
|
|
29015
|
-
|
|
28836
|
+
var _this$rendererRef4;
|
|
28837
|
+
|
|
28838
|
+
return (_this$rendererRef4 = this.rendererRef) === null || _this$rendererRef4 === void 0 ? void 0 : _this$rendererRef4.score();
|
|
29016
28839
|
});
|
|
29017
28840
|
|
|
29018
28841
|
_defineProperty(this, "getInputPaths", () => {
|
|
29019
|
-
|
|
29020
|
-
|
|
28842
|
+
var _this$rendererRef5;
|
|
28843
|
+
|
|
28844
|
+
return (_this$rendererRef5 = this.rendererRef) === null || _this$rendererRef5 === void 0 ? void 0 : _this$rendererRef5.getInputPaths();
|
|
29021
28845
|
});
|
|
29022
28846
|
|
|
29023
|
-
_defineProperty(this, "setInputValue", (path, newValue,
|
|
29024
|
-
|
|
29025
|
-
|
|
28847
|
+
_defineProperty(this, "setInputValue", (path, newValue, callback) => {
|
|
28848
|
+
var _this$rendererRef6;
|
|
28849
|
+
|
|
28850
|
+
return (_this$rendererRef6 = this.rendererRef) === null || _this$rendererRef6 === void 0 ? void 0 : _this$rendererRef6.setInputValue(path, newValue, callback);
|
|
29026
28851
|
});
|
|
29027
28852
|
|
|
29028
28853
|
_defineProperty(this, "focus", () => {
|
|
29029
|
-
|
|
29030
|
-
|
|
28854
|
+
var _this$rendererRef7;
|
|
28855
|
+
|
|
28856
|
+
return (_this$rendererRef7 = this.rendererRef) === null || _this$rendererRef7 === void 0 ? void 0 : _this$rendererRef7.focus();
|
|
29031
28857
|
});
|
|
29032
28858
|
|
|
29033
28859
|
_defineProperty(this, "focusInputPath", path => {
|
|
29034
|
-
|
|
29035
|
-
|
|
28860
|
+
var _this$rendererRef8;
|
|
28861
|
+
|
|
28862
|
+
(_this$rendererRef8 = this.rendererRef) === null || _this$rendererRef8 === void 0 ? void 0 : _this$rendererRef8.focusPath(path);
|
|
29036
28863
|
});
|
|
29037
28864
|
|
|
29038
28865
|
_defineProperty(this, "blurInputPath", path => {
|
|
29039
|
-
|
|
29040
|
-
|
|
28866
|
+
var _this$rendererRef9;
|
|
28867
|
+
|
|
28868
|
+
(_this$rendererRef9 = this.rendererRef) === null || _this$rendererRef9 === void 0 ? void 0 : _this$rendererRef9.blurPath(path);
|
|
29041
28869
|
});
|
|
29042
28870
|
|
|
29043
28871
|
_defineProperty(this, "showRationalesForCurrentlySelectedChoices", () => {
|
|
29044
|
-
|
|
29045
|
-
|
|
28872
|
+
var _this$rendererRef10;
|
|
28873
|
+
|
|
28874
|
+
(_this$rendererRef10 = this.rendererRef) === null || _this$rendererRef10 === void 0 ? void 0 : _this$rendererRef10.showRationalesForCurrentlySelectedChoices();
|
|
29046
28875
|
});
|
|
29047
28876
|
}
|
|
29048
28877
|
|
|
@@ -29054,7 +28883,10 @@ class Group extends React__namespace.Component {
|
|
|
29054
28883
|
}
|
|
29055
28884
|
|
|
29056
28885
|
render() {
|
|
29057
|
-
|
|
28886
|
+
var _this$rendererRef11;
|
|
28887
|
+
|
|
28888
|
+
const apiOptions = { ...ApiOptions.defaults,
|
|
28889
|
+
...this.props.apiOptions,
|
|
29058
28890
|
// Api Rewriting to support correct onFocus/onBlur
|
|
29059
28891
|
// events for the mobile API
|
|
29060
28892
|
onFocusChange: (newFocus, oldFocus) => {
|
|
@@ -29066,7 +28898,7 @@ class Group extends React__namespace.Component {
|
|
|
29066
28898
|
this.props.onFocus(newFocus);
|
|
29067
28899
|
}
|
|
29068
28900
|
}
|
|
29069
|
-
}
|
|
28901
|
+
}; // Allow a problem number annotation to be added.
|
|
29070
28902
|
// This is cyclical and should probably be reconsidered. In order to
|
|
29071
28903
|
// render the annotation ("Question 3 of 10"), we call findWidgets to
|
|
29072
28904
|
// figure out our index in the list of all fellow group widgets. On
|
|
@@ -29075,9 +28907,8 @@ class Group extends React__namespace.Component {
|
|
|
29075
28907
|
// componentDidMount so that we can number ourselves properly. But,
|
|
29076
28908
|
// really we should have a more unidirectional flow. TODO(marcia): fix.
|
|
29077
28909
|
|
|
29078
|
-
|
|
29079
|
-
const number =
|
|
29080
|
-
|
|
28910
|
+
const groupWidgets = this.props.findWidgets("group");
|
|
28911
|
+
const number = groupWidgets.indexOf(this);
|
|
29081
28912
|
const problemNumComponent = this.props.apiOptions.groupAnnotator(number, this.props.widgetId); // This is a little strange because the id of the widget that actually
|
|
29082
28913
|
// changed is going to be lost in favor of the group widget's id. The
|
|
29083
28914
|
// widgets prop also wasn't actually changed, and this only serves to
|
|
@@ -29085,15 +28916,12 @@ class Group extends React__namespace.Component {
|
|
|
29085
28916
|
// has occurred.
|
|
29086
28917
|
|
|
29087
28918
|
const onInteractWithWidget = id => {
|
|
29088
|
-
|
|
29089
|
-
|
|
29090
|
-
// eslint-disable-next-line react/no-string-refs
|
|
29091
|
-
this.change("widgets", this.refs.renderer.props.widgets);
|
|
28919
|
+
if (this.rendererRef) {
|
|
28920
|
+
this.change("widgets", this.rendererRef.props.widgets);
|
|
29092
28921
|
}
|
|
29093
|
-
};
|
|
29094
|
-
|
|
28922
|
+
};
|
|
29095
28923
|
|
|
29096
|
-
const score = this.
|
|
28924
|
+
const score = (_this$rendererRef11 = this.rendererRef) === null || _this$rendererRef11 === void 0 ? void 0 : _this$rendererRef11.score();
|
|
29097
28925
|
const isValid = score && score.type !== "invalid";
|
|
29098
28926
|
const isInvalid = score && score.type === "invalid"; // TODO(mdr): Widgets inside this Renderer are not discoverable through
|
|
29099
28927
|
// the parent Renderer's `findWidgets` function.
|
|
@@ -29104,15 +28932,17 @@ class Group extends React__namespace.Component {
|
|
|
29104
28932
|
"perseus-group-valid-answer": isValid,
|
|
29105
28933
|
"perseus-group-invalid-answer": isInvalid
|
|
29106
28934
|
})
|
|
29107
|
-
}, problemNumComponent, /*#__PURE__*/React__namespace.createElement(Renderer,
|
|
29108
|
-
|
|
29109
|
-
|
|
28935
|
+
}, problemNumComponent, /*#__PURE__*/React__namespace.createElement(Renderer, {
|
|
28936
|
+
content: this.props.content,
|
|
28937
|
+
widgets: this.props.widgets,
|
|
28938
|
+
images: this.props.images,
|
|
28939
|
+
ref: ref => this.rendererRef = ref,
|
|
29110
28940
|
apiOptions: apiOptions,
|
|
29111
28941
|
findExternalWidgets: this.props.findWidgets,
|
|
29112
28942
|
reviewMode: !!this.props.reviewModeRubric,
|
|
29113
28943
|
onInteractWithWidget: onInteractWithWidget,
|
|
29114
28944
|
linterContext: this.props.linterContext
|
|
29115
|
-
})
|
|
28945
|
+
}), this.props.icon && /*#__PURE__*/React__namespace.createElement("div", {
|
|
29116
28946
|
className: "group-icon"
|
|
29117
28947
|
}, this.props.icon));
|
|
29118
28948
|
}
|
|
@@ -29127,7 +28957,9 @@ _defineProperty(Group, "defaultProps", {
|
|
|
29127
28957
|
});
|
|
29128
28958
|
|
|
29129
28959
|
const traverseChildWidgets$1 = function (props, traverseRenderer) {
|
|
29130
|
-
return
|
|
28960
|
+
return { ...props,
|
|
28961
|
+
...traverseRenderer(props)
|
|
28962
|
+
};
|
|
29131
28963
|
};
|
|
29132
28964
|
|
|
29133
28965
|
var Group$1 = {
|
|
@@ -40994,43 +40826,23 @@ const styles$9 = aphrodite.StyleSheet.create({
|
|
|
40994
40826
|
}
|
|
40995
40827
|
});
|
|
40996
40828
|
|
|
40997
|
-
const getLineHeightForNode = (
|
|
40998
|
-
return $line2.offset().top - $line1.offset().top;
|
|
40829
|
+
const getLineHeightForNode = (line1, line2) => {
|
|
40830
|
+
return $__default["default"](line2).offset().top - $__default["default"](line1).offset().top;
|
|
40999
40831
|
};
|
|
41000
40832
|
|
|
41001
|
-
|
|
41002
|
-
|
|
41003
|
-
|
|
41004
|
-
|
|
41005
|
-
|
|
41006
|
-
|
|
41007
|
-
|
|
41008
|
-
|
|
41009
|
-
|
|
41010
|
-
|
|
41011
|
-
|
|
41012
|
-
|
|
41013
|
-
}
|
|
41014
|
-
const SQUARE_LABEL_STYLE = {
|
|
41015
|
-
display: "inline-block",
|
|
41016
|
-
color: "rgb(255, 255, 255)",
|
|
41017
|
-
backgroundColor: "rgb(90, 90, 90)",
|
|
41018
|
-
paddingLeft: 10,
|
|
41019
|
-
paddingRight: 10,
|
|
41020
|
-
userSelect: "none",
|
|
41021
|
-
WebkitUserSelect: "none"
|
|
41022
|
-
};
|
|
41023
|
-
const CIRCLE_LABEL_STYLE = {
|
|
41024
|
-
display: "inline-block",
|
|
41025
|
-
color: "rgb(255, 255, 255)",
|
|
41026
|
-
backgroundColor: "rgb(90, 90, 90)",
|
|
41027
|
-
userSelect: "none",
|
|
41028
|
-
WebkitUserSelect: "none",
|
|
41029
|
-
width: 22,
|
|
41030
|
-
height: 22,
|
|
41031
|
-
borderRadius: "50%",
|
|
41032
|
-
textAlign: "center"
|
|
41033
|
-
};
|
|
40833
|
+
function getInitialParseState() {
|
|
40834
|
+
return {
|
|
40835
|
+
currentRef: [],
|
|
40836
|
+
useRefs: true,
|
|
40837
|
+
lastRef: 0,
|
|
40838
|
+
firstSentenceRef: null,
|
|
40839
|
+
firstQuestionRef: null,
|
|
40840
|
+
lastFootnote: {
|
|
40841
|
+
id: 0,
|
|
40842
|
+
text: ""
|
|
40843
|
+
}
|
|
40844
|
+
};
|
|
40845
|
+
}
|
|
41034
40846
|
|
|
41035
40847
|
class RefStart extends React__namespace.Component {
|
|
41036
40848
|
constructor() {
|
|
@@ -41049,10 +40861,6 @@ class RefStart extends React__namespace.Component {
|
|
|
41049
40861
|
|
|
41050
40862
|
}
|
|
41051
40863
|
|
|
41052
|
-
_defineProperty(RefStart, "propTypes", {
|
|
41053
|
-
refContent: PropTypes__default["default"].node.isRequired
|
|
41054
|
-
});
|
|
41055
|
-
|
|
41056
40864
|
class RefEnd extends React__namespace.Component {
|
|
41057
40865
|
render() {
|
|
41058
40866
|
return /*#__PURE__*/React__namespace.createElement("span", {
|
|
@@ -41063,20 +40871,11 @@ class RefEnd extends React__namespace.Component {
|
|
|
41063
40871
|
}
|
|
41064
40872
|
|
|
41065
40873
|
const rules = {
|
|
41066
|
-
// $FlowFixMe[prop-missing]
|
|
41067
|
-
// $FlowFixMe[incompatible-use]
|
|
41068
40874
|
newline: SimpleMarkdown__default["default"].defaultRules.newline,
|
|
41069
|
-
// $FlowFixMe[prop-missing]
|
|
41070
|
-
// $FlowFixMe[incompatible-use]
|
|
41071
40875
|
paragraph: SimpleMarkdown__default["default"].defaultRules.paragraph,
|
|
41072
|
-
// $FlowFixMe[prop-missing]
|
|
41073
|
-
// $FlowFixMe[incompatible-use]
|
|
41074
40876
|
escape: SimpleMarkdown__default["default"].defaultRules.escape,
|
|
41075
40877
|
passageFootnote: {
|
|
41076
|
-
// $FlowFixMe[prop-missing]
|
|
41077
|
-
// $FlowFixMe[incompatible-use]
|
|
41078
40878
|
order: SimpleMarkdown__default["default"].defaultRules.escape.order + 0.1,
|
|
41079
|
-
// $FlowFixMe[prop-missing]
|
|
41080
40879
|
match: SimpleMarkdown__default["default"].inlineRegex(/^\^/),
|
|
41081
40880
|
parse: (capture, parse, state) => {
|
|
41082
40881
|
// if no footnotes have been seen, we're id 1. otherwise,
|
|
@@ -41105,10 +40904,8 @@ const rules = {
|
|
|
41105
40904
|
}
|
|
41106
40905
|
},
|
|
41107
40906
|
refStart: {
|
|
41108
|
-
// $FlowFixMe[prop-missing]
|
|
41109
|
-
// $FlowFixMe[incompatible-use]
|
|
41110
40907
|
order: SimpleMarkdown__default["default"].defaultRules.escape.order + 0.2,
|
|
41111
|
-
match: function (source
|
|
40908
|
+
match: function (source) {
|
|
41112
40909
|
const capture = /^\{\{/.exec(source);
|
|
41113
40910
|
|
|
41114
40911
|
if (capture) {
|
|
@@ -41139,12 +40936,14 @@ const rules = {
|
|
|
41139
40936
|
}
|
|
41140
40937
|
|
|
41141
40938
|
closeIndex++;
|
|
41142
|
-
}
|
|
41143
|
-
|
|
41144
|
-
const refText = source.slice(2, closeIndex); // A "magic" capture that matches the opening {{
|
|
40939
|
+
} // A "magic" capture that matches the opening {{
|
|
41145
40940
|
// but captures the full ref text internally :D
|
|
40941
|
+
// unfortunately the "magic" makes Flow upset,
|
|
40942
|
+
// so there's this questionable workaround
|
|
40943
|
+
|
|
41146
40944
|
|
|
41147
|
-
|
|
40945
|
+
capture[1] = source.slice(2, closeIndex);
|
|
40946
|
+
return capture;
|
|
41148
40947
|
}
|
|
41149
40948
|
|
|
41150
40949
|
return null;
|
|
@@ -41167,33 +40966,34 @@ const rules = {
|
|
|
41167
40966
|
// to make those refs into react refs on the
|
|
41168
40967
|
// passage, for instance!
|
|
41169
40968
|
useRefs: false
|
|
41170
|
-
},
|
|
40969
|
+
}, getInitialParseState()));
|
|
41171
40970
|
return {
|
|
41172
40971
|
ref: ref,
|
|
41173
40972
|
refContent: refContent
|
|
41174
40973
|
};
|
|
41175
40974
|
},
|
|
41176
40975
|
react: (node, output, state) => {
|
|
41177
|
-
|
|
40976
|
+
const ref = node.ref;
|
|
40977
|
+
|
|
40978
|
+
if (ref == null) {
|
|
41178
40979
|
return null;
|
|
41179
40980
|
} // We don't pass state here because this is parsed
|
|
41180
40981
|
// and output out-of-band. We don't want to affect
|
|
41181
40982
|
// our state by the double-output here :).
|
|
41182
40983
|
|
|
41183
40984
|
|
|
41184
|
-
const refContent = output(node.refContent, {});
|
|
40985
|
+
const refContent = output(node.refContent, {}); // note(matthewc) the refs created here become the refs
|
|
40986
|
+
// pulled from `this.refs` in passage.jsx
|
|
40987
|
+
|
|
41185
40988
|
return /*#__PURE__*/React__namespace.createElement(RefStart, {
|
|
41186
|
-
ref: START_REF_PREFIX +
|
|
41187
|
-
key: START_REF_PREFIX +
|
|
40989
|
+
ref: START_REF_PREFIX + ref,
|
|
40990
|
+
key: START_REF_PREFIX + ref,
|
|
41188
40991
|
refContent: refContent
|
|
41189
40992
|
});
|
|
41190
40993
|
}
|
|
41191
40994
|
},
|
|
41192
40995
|
refEnd: {
|
|
41193
|
-
// $FlowFixMe[prop-missing]
|
|
41194
|
-
// $FlowFixMe[incompatible-use]
|
|
41195
40996
|
order: SimpleMarkdown__default["default"].defaultRules.escape.order + 0.3,
|
|
41196
|
-
// $FlowFixMe[prop-missing]
|
|
41197
40997
|
match: SimpleMarkdown__default["default"].inlineRegex(/^\}\}/),
|
|
41198
40998
|
parse: (capture, parse, state) => {
|
|
41199
40999
|
if (!state.useRefs) {
|
|
@@ -41209,6 +41009,8 @@ const rules = {
|
|
|
41209
41009
|
},
|
|
41210
41010
|
react: (node, output, state) => {
|
|
41211
41011
|
if (node.ref != null) {
|
|
41012
|
+
// note(matthewc) the refs created here become the refs
|
|
41013
|
+
// pulled from `this.refs` in passage.jsx
|
|
41212
41014
|
return /*#__PURE__*/React__namespace.createElement(RefEnd, {
|
|
41213
41015
|
ref: END_REF_PREFIX + node.ref,
|
|
41214
41016
|
key: END_REF_PREFIX + node.ref
|
|
@@ -41222,10 +41024,7 @@ const rules = {
|
|
|
41222
41024
|
}
|
|
41223
41025
|
},
|
|
41224
41026
|
squareLabel: {
|
|
41225
|
-
// $FlowFixMe[prop-missing]
|
|
41226
|
-
// $FlowFixMe[incompatible-use]
|
|
41227
41027
|
order: SimpleMarkdown__default["default"].defaultRules.escape.order + 0.4,
|
|
41228
|
-
// $FlowFixMe[prop-missing]
|
|
41229
41028
|
match: SimpleMarkdown__default["default"].inlineRegex(/^\[\[(\w+)\]\]( *)/),
|
|
41230
41029
|
parse: (capture, parse, state) => {
|
|
41231
41030
|
if (!state.firstQuestionRef) {
|
|
@@ -41254,10 +41053,7 @@ const rules = {
|
|
|
41254
41053
|
}
|
|
41255
41054
|
},
|
|
41256
41055
|
circleLabel: {
|
|
41257
|
-
// $FlowFixMe[prop-missing]
|
|
41258
|
-
// $FlowFixMe[incompatible-use]
|
|
41259
41056
|
order: SimpleMarkdown__default["default"].defaultRules.escape.order + 0.5,
|
|
41260
|
-
// $FlowFixMe[prop-missing]
|
|
41261
41057
|
match: SimpleMarkdown__default["default"].inlineRegex(/^\(\((\w+)\)\)( *)/),
|
|
41262
41058
|
parse: (capture, parse, state) => {
|
|
41263
41059
|
return {
|
|
@@ -41282,10 +41078,7 @@ const rules = {
|
|
|
41282
41078
|
}
|
|
41283
41079
|
},
|
|
41284
41080
|
squareBracketRef: {
|
|
41285
|
-
// $FlowFixMe[prop-missing]
|
|
41286
|
-
// $FlowFixMe[incompatible-use]
|
|
41287
41081
|
order: SimpleMarkdown__default["default"].defaultRules.escape.order + 0.6,
|
|
41288
|
-
// $FlowFixMe[prop-missing]
|
|
41289
41082
|
match: SimpleMarkdown__default["default"].inlineRegex(/^\[(\d+)\]( *)/),
|
|
41290
41083
|
parse: (capture, parse, state) => {
|
|
41291
41084
|
if (!state.firstSentenceRef) {
|
|
@@ -41311,10 +41104,7 @@ const rules = {
|
|
|
41311
41104
|
}
|
|
41312
41105
|
},
|
|
41313
41106
|
highlight: {
|
|
41314
|
-
// $FlowFixMe[prop-missing]
|
|
41315
|
-
// $FlowFixMe[incompatible-use]
|
|
41316
41107
|
order: SimpleMarkdown__default["default"].defaultRules.escape.order + 0.7,
|
|
41317
|
-
// $FlowFixMe[prop-missing]
|
|
41318
41108
|
match: SimpleMarkdown__default["default"].inlineRegex(/^{highlighting.start}(.+?){highlighting.end}/),
|
|
41319
41109
|
parse: (capture, parse, state) => {
|
|
41320
41110
|
return {
|
|
@@ -41329,10 +41119,7 @@ const rules = {
|
|
|
41329
41119
|
}
|
|
41330
41120
|
},
|
|
41331
41121
|
reviewHighlight: {
|
|
41332
|
-
// $FlowFixMe[prop-missing]
|
|
41333
|
-
// $FlowFixMe[incompatible-use]
|
|
41334
41122
|
order: SimpleMarkdown__default["default"].defaultRules.escape.order + 0.7,
|
|
41335
|
-
// $FlowFixMe[prop-missing]
|
|
41336
41123
|
match: SimpleMarkdown__default["default"].inlineRegex(/^{review-highlighting.start}(.+?){review-highlighting.end}/),
|
|
41337
41124
|
parse: (capture, parse, state) => {
|
|
41338
41125
|
return {
|
|
@@ -41346,21 +41133,11 @@ const rules = {
|
|
|
41346
41133
|
}, node.content)];
|
|
41347
41134
|
}
|
|
41348
41135
|
},
|
|
41349
|
-
// $FlowFixMe[prop-missing]
|
|
41350
|
-
// $FlowFixMe[incompatible-use]
|
|
41351
41136
|
strong: SimpleMarkdown__default["default"].defaultRules.strong,
|
|
41352
|
-
// $FlowFixMe[prop-missing]
|
|
41353
|
-
// $FlowFixMe[incompatible-use]
|
|
41354
41137
|
u: SimpleMarkdown__default["default"].defaultRules.u,
|
|
41355
|
-
// $FlowFixMe[prop-missing]
|
|
41356
|
-
// $FlowFixMe[incompatible-use]
|
|
41357
41138
|
em: SimpleMarkdown__default["default"].defaultRules.em,
|
|
41358
|
-
// $FlowFixMe[prop-missing]
|
|
41359
|
-
// $FlowFixMe[incompatible-use]
|
|
41360
41139
|
del: SimpleMarkdown__default["default"].defaultRules.del,
|
|
41361
|
-
text: {
|
|
41362
|
-
// $FlowFixMe[incompatible-use]
|
|
41363
|
-
...SimpleMarkdown__default["default"].defaultRules.text,
|
|
41140
|
+
text: { ...SimpleMarkdown__default["default"].defaultRules.text,
|
|
41364
41141
|
|
|
41365
41142
|
react(node, output, state) {
|
|
41366
41143
|
return /*#__PURE__*/React__namespace.createElement("span", {
|
|
@@ -41370,32 +41147,54 @@ const rules = {
|
|
|
41370
41147
|
|
|
41371
41148
|
}
|
|
41372
41149
|
};
|
|
41373
|
-
const
|
|
41374
|
-
|
|
41375
|
-
|
|
41376
|
-
|
|
41377
|
-
|
|
41378
|
-
|
|
41379
|
-
|
|
41380
|
-
|
|
41381
|
-
|
|
41382
|
-
|
|
41150
|
+
const START_REF_PREFIX = "start-ref-";
|
|
41151
|
+
const END_REF_PREFIX = "end-ref-";
|
|
41152
|
+
const REF_STYLE = {
|
|
41153
|
+
display: "inline-block",
|
|
41154
|
+
width: 0,
|
|
41155
|
+
visibility: "hidden"
|
|
41156
|
+
};
|
|
41157
|
+
const LABEL_OUTER_STYLE = {
|
|
41158
|
+
// for some reason we need these to keep the nbsp from wrapping when the
|
|
41159
|
+
// inner circle/square is display: inline-block
|
|
41160
|
+
display: "inline",
|
|
41161
|
+
whiteSpace: "nowrap"
|
|
41162
|
+
};
|
|
41163
|
+
const SQUARE_LABEL_STYLE = {
|
|
41164
|
+
display: "inline-block",
|
|
41165
|
+
color: "rgb(255, 255, 255)",
|
|
41166
|
+
backgroundColor: "rgb(90, 90, 90)",
|
|
41167
|
+
paddingLeft: 10,
|
|
41168
|
+
paddingRight: 10,
|
|
41169
|
+
userSelect: "none",
|
|
41170
|
+
WebkitUserSelect: "none"
|
|
41171
|
+
};
|
|
41172
|
+
const CIRCLE_LABEL_STYLE = {
|
|
41173
|
+
display: "inline-block",
|
|
41174
|
+
color: "rgb(255, 255, 255)",
|
|
41175
|
+
backgroundColor: "rgb(90, 90, 90)",
|
|
41176
|
+
userSelect: "none",
|
|
41177
|
+
WebkitUserSelect: "none",
|
|
41178
|
+
width: 22,
|
|
41179
|
+
height: 22,
|
|
41180
|
+
borderRadius: "50%",
|
|
41181
|
+
textAlign: "center"
|
|
41182
|
+
};
|
|
41383
41183
|
const builtParser = SimpleMarkdown__default["default"].parserFor(rules);
|
|
41384
41184
|
|
|
41385
41185
|
const parse = (source, state) => {
|
|
41386
41186
|
state = state || {};
|
|
41387
41187
|
const paragraphedSource = source + "\n\n";
|
|
41388
|
-
return builtParser(paragraphedSource, ___default["default"].extend(state,
|
|
41188
|
+
return builtParser(paragraphedSource, ___default["default"].extend(state, getInitialParseState()));
|
|
41389
41189
|
};
|
|
41390
41190
|
|
|
41191
|
+
const output = SimpleMarkdown__default["default"].reactFor(SimpleMarkdown__default["default"].ruleOutput(rules, "react"));
|
|
41391
41192
|
var PassageMarkdown = {
|
|
41392
|
-
parse
|
|
41393
|
-
|
|
41394
|
-
|
|
41395
|
-
|
|
41396
|
-
|
|
41397
|
-
END_REF_PREFIX: END_REF_PREFIX,
|
|
41398
|
-
_rulesForTesting: rules
|
|
41193
|
+
parse,
|
|
41194
|
+
output,
|
|
41195
|
+
START_REF_PREFIX,
|
|
41196
|
+
END_REF_PREFIX,
|
|
41197
|
+
getInitialParseState
|
|
41399
41198
|
};
|
|
41400
41199
|
|
|
41401
41200
|
// A fake paragraph to measure the line height of the passage. In CSS we always
|
|
@@ -41408,9 +41207,9 @@ class LineHeightMeasurer extends React__namespace.Component {
|
|
|
41408
41207
|
|
|
41409
41208
|
_defineProperty(this, "_cachedLineHeight", void 0);
|
|
41410
41209
|
|
|
41411
|
-
_defineProperty(this, "
|
|
41210
|
+
_defineProperty(this, "body", void 0);
|
|
41412
41211
|
|
|
41413
|
-
_defineProperty(this, "
|
|
41212
|
+
_defineProperty(this, "end", void 0);
|
|
41414
41213
|
}
|
|
41415
41214
|
|
|
41416
41215
|
measureLineHeight() {
|
|
@@ -41422,23 +41221,30 @@ class LineHeightMeasurer extends React__namespace.Component {
|
|
|
41422
41221
|
}
|
|
41423
41222
|
|
|
41424
41223
|
forceMeasureLineHeight() {
|
|
41425
|
-
|
|
41426
|
-
this
|
|
41224
|
+
const body = this.body;
|
|
41225
|
+
const end = this.end;
|
|
41226
|
+
|
|
41227
|
+
if (!body || !end) {
|
|
41228
|
+
return;
|
|
41229
|
+
} // Add some text which magically fills an entire line.
|
|
41230
|
+
|
|
41231
|
+
|
|
41232
|
+
body.textContent = " \u0080"; // Now, the line height is the difference between the top of the
|
|
41427
41233
|
// second line and the top of the first line.
|
|
41428
41234
|
|
|
41429
|
-
this._cachedLineHeight = getLineHeightForNode(
|
|
41235
|
+
this._cachedLineHeight = getLineHeightForNode(body, end); // Clear out the first line so it doesn't overlap the passage.
|
|
41430
41236
|
|
|
41431
|
-
|
|
41237
|
+
body.textContent = "";
|
|
41432
41238
|
}
|
|
41433
41239
|
|
|
41434
41240
|
render() {
|
|
41435
41241
|
return /*#__PURE__*/React__namespace.createElement("div", {
|
|
41436
41242
|
className: aphrodite.css(styles$8.measurer)
|
|
41437
41243
|
}, /*#__PURE__*/React__namespace.createElement("div", null, /*#__PURE__*/React__namespace.createElement("div", {
|
|
41438
|
-
ref:
|
|
41244
|
+
ref: ref => this.body = ref,
|
|
41439
41245
|
className: "paragraph"
|
|
41440
41246
|
}), /*#__PURE__*/React__namespace.createElement("div", {
|
|
41441
|
-
ref:
|
|
41247
|
+
ref: ref => this.end = ref
|
|
41442
41248
|
})));
|
|
41443
41249
|
}
|
|
41444
41250
|
|
|
@@ -41457,9 +41263,11 @@ class Passage extends React__namespace.Component {
|
|
|
41457
41263
|
constructor() {
|
|
41458
41264
|
super(...arguments);
|
|
41459
41265
|
|
|
41460
|
-
_defineProperty(this, "
|
|
41266
|
+
_defineProperty(this, "_contentRef", void 0);
|
|
41461
41267
|
|
|
41462
|
-
_defineProperty(this, "
|
|
41268
|
+
_defineProperty(this, "_lineHeightMeasurerRef", void 0);
|
|
41269
|
+
|
|
41270
|
+
_defineProperty(this, "_onResize", void 0);
|
|
41463
41271
|
|
|
41464
41272
|
_defineProperty(this, "_stylesAppiedTimer", void 0);
|
|
41465
41273
|
|
|
@@ -41480,6 +41288,8 @@ class Passage extends React__namespace.Component {
|
|
|
41480
41288
|
this._updateState();
|
|
41481
41289
|
|
|
41482
41290
|
this._onResize = ___default["default"].throttle(() => {
|
|
41291
|
+
var _this$_lineHeightMeas;
|
|
41292
|
+
|
|
41483
41293
|
// If we're rendering JIPT text, we won't have line numbers or a
|
|
41484
41294
|
// line height measurer, so skip handling this resize.
|
|
41485
41295
|
if (this.shouldRenderJipt()) {
|
|
@@ -41490,7 +41300,7 @@ class Passage extends React__namespace.Component {
|
|
|
41490
41300
|
// listen for resize events.
|
|
41491
41301
|
|
|
41492
41302
|
|
|
41493
|
-
this.
|
|
41303
|
+
(_this$_lineHeightMeas = this._lineHeightMeasurerRef) === null || _this$_lineHeightMeas === void 0 ? void 0 : _this$_lineHeightMeas.forceMeasureLineHeight();
|
|
41494
41304
|
|
|
41495
41305
|
this._updateState();
|
|
41496
41306
|
}, 500);
|
|
@@ -41553,9 +41363,8 @@ class Passage extends React__namespace.Component {
|
|
|
41553
41363
|
}
|
|
41554
41364
|
|
|
41555
41365
|
_measureLines() {
|
|
41556
|
-
|
|
41557
|
-
const
|
|
41558
|
-
const contentsHeight = $renderer.height();
|
|
41366
|
+
const renderer = ReactDOM__default["default"].findDOMNode(this._contentRef);
|
|
41367
|
+
const contentsHeight = $__default["default"](renderer).height();
|
|
41559
41368
|
|
|
41560
41369
|
const lineHeight = this._getLineHeight();
|
|
41561
41370
|
|
|
@@ -41582,7 +41391,9 @@ class Passage extends React__namespace.Component {
|
|
|
41582
41391
|
}
|
|
41583
41392
|
|
|
41584
41393
|
_getLineHeight() {
|
|
41585
|
-
|
|
41394
|
+
var _this$_lineHeightMeas2;
|
|
41395
|
+
|
|
41396
|
+
return ((_this$_lineHeightMeas2 = this._lineHeightMeasurerRef) === null || _this$_lineHeightMeas2 === void 0 ? void 0 : _this$_lineHeightMeas2.measureLineHeight()) || 0;
|
|
41586
41397
|
}
|
|
41587
41398
|
|
|
41588
41399
|
getLineCount() {
|
|
@@ -41598,12 +41409,16 @@ class Passage extends React__namespace.Component {
|
|
|
41598
41409
|
* These are functions to support the passage refs inter-widgets feature
|
|
41599
41410
|
* where other widgets can fetch the line numbers of a reference inside of
|
|
41600
41411
|
* a passage.
|
|
41412
|
+
*
|
|
41413
|
+
* todo(matthewc): The refs are created by PassageMarkdown's refStart and refEnd,
|
|
41414
|
+
* somehow bubbling up to Passage's `this.refs`. This runs against
|
|
41415
|
+
* current best practices for refs by using string refs, but also
|
|
41416
|
+
* by breaking our expectation of explicit data flow.
|
|
41601
41417
|
*/
|
|
41602
41418
|
|
|
41603
41419
|
|
|
41604
41420
|
_getStartRefLineNumber(referenceNumber) {
|
|
41605
|
-
const refRef = PassageMarkdown.START_REF_PREFIX + referenceNumber;
|
|
41606
|
-
|
|
41421
|
+
const refRef = PassageMarkdown.START_REF_PREFIX + referenceNumber;
|
|
41607
41422
|
const ref = this.refs[refRef];
|
|
41608
41423
|
|
|
41609
41424
|
if (!ref) {
|
|
@@ -41626,8 +41441,7 @@ class Passage extends React__namespace.Component {
|
|
|
41626
41441
|
}
|
|
41627
41442
|
|
|
41628
41443
|
_getEndRefLineNumber(referenceNumber) {
|
|
41629
|
-
const refRef = PassageMarkdown.END_REF_PREFIX + referenceNumber;
|
|
41630
|
-
|
|
41444
|
+
const refRef = PassageMarkdown.END_REF_PREFIX + referenceNumber;
|
|
41631
41445
|
const ref = this.refs[refRef];
|
|
41632
41446
|
|
|
41633
41447
|
if (!ref) {
|
|
@@ -41664,9 +41478,8 @@ class Passage extends React__namespace.Component {
|
|
|
41664
41478
|
}
|
|
41665
41479
|
|
|
41666
41480
|
_convertPosToLineNumber(absoluteVPos) {
|
|
41667
|
-
|
|
41668
|
-
const
|
|
41669
|
-
const relativeVPos = absoluteVPos - $content.offset().top;
|
|
41481
|
+
const content = ReactDOM__default["default"].findDOMNode(this._contentRef);
|
|
41482
|
+
const relativeVPos = absoluteVPos - $__default["default"](content).offset().top;
|
|
41670
41483
|
|
|
41671
41484
|
const lineHeight = this._getLineHeight();
|
|
41672
41485
|
|
|
@@ -41675,8 +41488,7 @@ class Passage extends React__namespace.Component {
|
|
|
41675
41488
|
}
|
|
41676
41489
|
|
|
41677
41490
|
_getRefContent(referenceNumber) {
|
|
41678
|
-
const refRef = PassageMarkdown.START_REF_PREFIX + referenceNumber;
|
|
41679
|
-
|
|
41491
|
+
const refRef = PassageMarkdown.START_REF_PREFIX + referenceNumber;
|
|
41680
41492
|
const ref = this.refs[refRef];
|
|
41681
41493
|
|
|
41682
41494
|
if (!ref) {
|
|
@@ -41782,12 +41594,9 @@ class Passage extends React__namespace.Component {
|
|
|
41782
41594
|
onSerializedHighlightsUpdate: this._handleSerializedHighlightsUpdate,
|
|
41783
41595
|
serializedHighlights: this.props.highlights
|
|
41784
41596
|
}, /*#__PURE__*/React__namespace.createElement("div", {
|
|
41785
|
-
ref:
|
|
41786
|
-
}, /*#__PURE__*/React__namespace.createElement(LineHeightMeasurer
|
|
41787
|
-
|
|
41788
|
-
// $FlowFixMe[incompatible-type](0.52.0->0.53.0)
|
|
41789
|
-
, {
|
|
41790
|
-
ref: e => this._lineHeightMeasurer = e
|
|
41597
|
+
ref: ref => this._contentRef = ref
|
|
41598
|
+
}, /*#__PURE__*/React__namespace.createElement(LineHeightMeasurer, {
|
|
41599
|
+
ref: ref => this._lineHeightMeasurerRef = ref
|
|
41791
41600
|
}), PassageMarkdown.output(parsed)));
|
|
41792
41601
|
}
|
|
41793
41602
|
|
|
@@ -41830,10 +41639,7 @@ class Passage extends React__namespace.Component {
|
|
|
41830
41639
|
});
|
|
41831
41640
|
}
|
|
41832
41641
|
|
|
41833
|
-
const parseState =
|
|
41834
|
-
firstSentenceRef: null,
|
|
41835
|
-
firstQuestionRef: null
|
|
41836
|
-
}; // Replace the vertical double quote characters quoting text with
|
|
41642
|
+
const parseState = PassageMarkdown.getInitialParseState(); // Replace the vertical double quote characters quoting text with
|
|
41837
41643
|
// an unicode left and right double quote characters. This would
|
|
41838
41644
|
// avoid rendering right or left unicode double quotes on both
|
|
41839
41645
|
// ends of a sentence wrapped by double quotes.
|