@khanacademy/perseus 0.4.3 → 1.0.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.
Files changed (50) hide show
  1. package/.eslintrc.js +6 -1
  2. package/CHANGELOG.md +34 -0
  3. package/dist/es/index.js +1078 -1299
  4. package/dist/es/index.js.map +1 -1
  5. package/dist/index.js +1077 -1304
  6. package/dist/index.js.map +1 -1
  7. package/dist/strings.js +0 -4
  8. package/package.json +9 -8
  9. package/src/multi-items/__tests__/__snapshots__/multi-renderer_test.js.snap +110 -110
  10. package/src/perseus-api.jsx +10 -36
  11. package/src/perseus-types.js +0 -138
  12. package/src/styles/constants.js +0 -2
  13. package/src/styles/variables.less +0 -2
  14. package/src/types.js +18 -35
  15. package/src/util.js +0 -6
  16. package/src/version.js +1 -1
  17. package/src/widgets/__testdata__/base-radio_testdata.js +22 -0
  18. package/src/widgets/__tests__/__snapshots__/group_test.jsx.snap +110 -110
  19. package/src/widgets/__tests__/__snapshots__/radio_test.jsx.snap +1136 -1160
  20. package/src/widgets/__tests__/radio/base-radio_test.jsx +243 -41
  21. package/src/widgets/__tests__/radio/choice-icon_test.jsx +67 -0
  22. package/src/widgets/__tests__/radio/choice_test.jsx +201 -0
  23. package/src/widgets/__tests__/radio/focus-ring_test.jsx +23 -0
  24. package/src/widgets/__tests__/radio/option-status_test.jsx +65 -0
  25. package/src/widgets/passage/__tests__/passage-markdown_test.jsx +0 -9
  26. package/src/widgets/passage/get-line-height-for-node.js +4 -5
  27. package/src/widgets/passage/passage-markdown.jsx +176 -120
  28. package/src/widgets/passage.jsx +48 -46
  29. package/src/widgets/radio/__stories__/base-radio.stories.jsx +164 -17
  30. package/src/widgets/radio/__stories__/choice-icon.stories.jsx +202 -111
  31. package/src/widgets/radio/__stories__/choice-none-above.stories.jsx +21 -6
  32. package/src/widgets/radio/__stories__/choice.stories.jsx +138 -0
  33. package/src/widgets/radio/__stories__/focus-ring.stories.jsx +29 -23
  34. package/src/widgets/radio/__stories__/option-status.stories.jsx +93 -99
  35. package/src/widgets/radio/base-radio.jsx +483 -556
  36. package/src/widgets/radio/choice-icon/choice-icon.jsx +155 -0
  37. package/src/widgets/radio/choice-icon/cross-out-line.jsx +55 -0
  38. package/src/widgets/radio/choice-icon/library-choice-icon.jsx +219 -0
  39. package/src/widgets/radio/choice-icon/sat-choice-icon.jsx +129 -0
  40. package/src/widgets/radio/choice-icon/shared-styles.js +17 -0
  41. package/src/widgets/radio/choice-none-above.jsx +49 -40
  42. package/src/widgets/radio/choice.jsx +282 -353
  43. package/src/widgets/radio/choice.jsx.fixture.js +0 -18
  44. package/src/widgets/radio/focus-ring.jsx +29 -22
  45. package/src/widgets/radio/option-status.jsx +58 -36
  46. package/src/widgets/radio/widget.jsx +181 -136
  47. package/src/widgets/radio.jsx +16 -17
  48. package/src/widgets/radio/__stories__/cross-out-button.stories.jsx +0 -64
  49. package/src/widgets/radio/choice-icon.jsx +0 -469
  50. package/src/widgets/radio/cross-out-button.jsx +0 -134
package/dist/es/index.js CHANGED
@@ -5,6 +5,7 @@ import { Rule, linterContextDefault, linterContextProps } from '@khanacademy/per
5
5
  import * as i18n from '@khanacademy/wonder-blocks-i18n';
6
6
  import classNames from 'classnames';
7
7
  import * as React from 'react';
8
+ import { useState, useEffect, useRef } from 'react';
8
9
  import _, { debounce } from 'underscore';
9
10
  import PropTypes from 'prop-types';
10
11
  import $ from 'jquery';
@@ -31,7 +32,7 @@ import Link from '@khanacademy/wonder-blocks-link';
31
32
 
32
33
  var version = {
33
34
  apiVersion: {
34
- major: 11,
35
+ major: 12,
35
36
  minor: 0
36
37
  },
37
38
  itemDataVersion: {
@@ -493,38 +494,9 @@ var ApiOptions = {
493
494
  // values. For now, though, you must either specify all fields
494
495
  // of `styling`, or omit the `styling` option entirely.
495
496
  styling: PropTypes.shape({
496
- // Which version of radio widget styles to use in non-SAT
497
- // contexts.
498
- //
499
- // "legacy" was the version of the widget display after XOM but
500
- // before we started adding MCR styles. It doesn't have support
501
- // for rationales. It has since been removed.
502
- //
503
- // "intermediate" is a design which adds several new additions
504
- // to the "legacy" styles such as:
505
- // 1. Using the XOM "desktop" styles (with a visible check icon
506
- // and lines in between choices) on mobile devices.
507
- // 2. Designs for rationales
508
- // 3. Using the single-select styles for multi-select styles
509
- //
510
- // "final" is a design which will build off of the
511
- // "intermediate" designs and adds some improved designs as well
512
- // as:
513
- // 1. a/b/c/d/etc. letters inside of the prompt check box
514
- // 2. New iconography and styles to indicate choice correctness
515
- //
516
- // The "legacy" and "intermediate" designs will be A/B tested
517
- // against each other to ensure that its changes don't cause
518
- // problems due to the new designs. Once the "intermediate"
519
- // designs are finished, they will be A/B tested against the
520
- // "final" designs.
521
- //
522
- // If no flag is provided, "legacy" styles will be shown.
523
- //
524
- // TODO(emily): Remove this by Aug 1, 2017, at which point all
525
- // callsites should have been switched to using the "final"
526
- // designs.
527
- radioStyleVersion: PropTypes.oneOf(["intermediate", "final"])
497
+ // TODO(Nicole): Remove this after landing wonderblock-ifying
498
+ // the radio widget
499
+ primaryProductColor: PropTypes.string
528
500
  }),
529
501
  // The color used for the hint progress indicator (eg. 1 / 3)
530
502
  hintProgressColor: PropTypes.string,
@@ -575,9 +547,7 @@ var ApiOptions = {
575
547
  },
576
548
  setDrawingAreaAvailable: function setDrawingAreaAvailable() {},
577
549
  useDraftEditor: false,
578
- styling: {
579
- radioStyleVersion: "final"
580
- },
550
+ styling: {},
581
551
  canScrollPage: false,
582
552
  inModal: false,
583
553
  crossOutEnabled: false,
@@ -1701,16 +1671,10 @@ function inputPathsEqual(a, b) {
1701
1671
  return a.length === b.length && a.every((item, index) => {
1702
1672
  return b[index] === item;
1703
1673
  });
1704
- } // Keep `rWidgetRule` in sync with `WIDGET_RE`
1705
- // in content_internal/perseus_traversal.py
1706
- // sync-start:widget-id-regex 1271666319 services/content-editing/perseus/translation/content_traversal.go
1707
- // sync-start:widget-id-regex 680240484 services/static/javascript/perseus-merged-editor-package/editor.jsx
1708
- // sync-start:widget-id-regex 1400368396 content_internal/perseus_traversal.py
1709
-
1674
+ }
1710
1675
 
1711
1676
  var rWidgetRule = /^\[\[\u2603 (([a-z-]+) ([0-9]+))\]\]/;
1712
- var rTypeFromWidgetId = /^([a-z-]+) ([0-9]+)$/; // sync-end:widget-id-regex
1713
-
1677
+ var rTypeFromWidgetId = /^([a-z-]+) ([0-9]+)$/;
1714
1678
  var rWidgetParts = new RegExp(rWidgetRule.source + "$");
1715
1679
  var snowman = "\u2603";
1716
1680
  var noScore = {
@@ -3143,11 +3107,10 @@ var warningColor = "#f86700";
3143
3107
  var warningColorHover = "#df5c00";
3144
3108
  var warningColorActive = "#c75300";
3145
3109
  var publishBlockingErrorColor = "#be2612";
3146
- var radioBorderColor$2 = "#BABEC2";
3147
- var checkedColor$1 = "#71B307"; // sync-start:article-max-width 986452807 services/static/javascript/perseus-all-package/styles/variables.less
3148
-
3110
+ var radioBorderColor$1 = "#BABEC2";
3111
+ var checkedColor$1 = "#71B307";
3149
3112
  var articleMaxWidthInPx = 688;
3150
- var articleMaxWidthTableInPx = 512; // sync-end:article-max-width
3113
+ var articleMaxWidthTableInPx = 512;
3151
3114
 
3152
3115
  var constants$1 = /*#__PURE__*/Object.freeze({
3153
3116
  __proto__: null,
@@ -3212,7 +3175,7 @@ var constants$1 = /*#__PURE__*/Object.freeze({
3212
3175
  warningColorHover: warningColorHover,
3213
3176
  warningColorActive: warningColorActive,
3214
3177
  publishBlockingErrorColor: publishBlockingErrorColor,
3215
- radioBorderColor: radioBorderColor$2,
3178
+ radioBorderColor: radioBorderColor$1,
3216
3179
  checkedColor: checkedColor$1,
3217
3180
  articleMaxWidthInPx: articleMaxWidthInPx,
3218
3181
  articleMaxWidthTableInPx: articleMaxWidthTableInPx
@@ -17996,17 +17959,17 @@ class Lint extends React.Component {
17996
17959
  var warningTextStyle;
17997
17960
 
17998
17961
  if (this.props.severity === 1) {
17999
- severityStyle = styles$w.indicatorError;
17962
+ severityStyle = styles$A.indicatorError;
18000
17963
  warningText = "Error";
18001
- warningTextStyle = styles$w.publishBlockingError;
17964
+ warningTextStyle = styles$A.publishBlockingError;
18002
17965
  } else if (this.props.severity === 2) {
18003
- severityStyle = styles$w.indicatorWarning;
17966
+ severityStyle = styles$A.indicatorWarning;
18004
17967
  warningText = "Warning";
18005
- warningTextStyle = styles$w.warning;
17968
+ warningTextStyle = styles$A.warning;
18006
17969
  } else {
18007
- severityStyle = styles$w.indicatorGuideline;
17970
+ severityStyle = styles$A.indicatorGuideline;
18008
17971
  warningText = "Recommendation";
18009
- warningTextStyle = styles$w.warning;
17972
+ warningTextStyle = styles$A.warning;
18010
17973
  }
18011
17974
 
18012
17975
  return /*#__PURE__*/React.createElement("a", {
@@ -18014,16 +17977,16 @@ class Lint extends React.Component {
18014
17977
  target: "lint-help-window",
18015
17978
  className: css(style)
18016
17979
  }, /*#__PURE__*/React.createElement("span", {
18017
- className: css(styles$w.indicator, severityStyle)
17980
+ className: css(styles$A.indicator, severityStyle)
18018
17981
  }, this.props.severity === 1 && /*#__PURE__*/React.createElement(InlineIcon, exclamationIcon)), /*#__PURE__*/React.createElement("div", {
18019
- className: css(styles$w.tooltip, tooltipAbove && styles$w.tooltipAbove)
17982
+ className: css(styles$A.tooltip, tooltipAbove && styles$A.tooltipAbove)
18020
17983
  }, this.props.message.split("\n\n").map((m, i) => /*#__PURE__*/React.createElement("p", {
18021
17984
  key: i,
18022
- className: css(styles$w.tooltipParagraph)
17985
+ className: css(styles$A.tooltipParagraph)
18023
17986
  }, /*#__PURE__*/React.createElement("span", {
18024
17987
  className: css(warningTextStyle)
18025
17988
  }, warningText, ":", " "), m)), /*#__PURE__*/React.createElement("div", {
18026
- className: css(styles$w.tail, tooltipAbove && styles$w.tailAbove)
17989
+ className: css(styles$A.tail, tooltipAbove && styles$A.tailAbove)
18027
17990
  })));
18028
17991
  });
18029
17992
  }
@@ -18071,19 +18034,19 @@ class Lint extends React.Component {
18071
18034
 
18072
18035
  if (blockHighlight) {
18073
18036
  return /*#__PURE__*/React.createElement("span", {
18074
- className: css(styles$w.lintContainer, styles$w.lintContainerBlock)
18075
- }, this.renderLink(styles$w.radioWidgetHoverTarget), /*#__PURE__*/React.createElement("span", null, children));
18037
+ className: css(styles$A.lintContainer, styles$A.lintContainerBlock)
18038
+ }, this.renderLink(styles$A.radioWidgetHoverTarget), /*#__PURE__*/React.createElement("span", null, children));
18076
18039
  }
18077
18040
 
18078
18041
  if (inline) {
18079
18042
  return /*#__PURE__*/React.createElement("span", {
18080
- className: css(styles$w.lintContainer)
18081
- }, this.renderLink(styles$w.inlineHoverTarget), /*#__PURE__*/React.createElement("span", null, children));
18043
+ className: css(styles$A.lintContainer)
18044
+ }, this.renderLink(styles$A.inlineHoverTarget), /*#__PURE__*/React.createElement("span", null, children));
18082
18045
  }
18083
18046
 
18084
18047
  return /*#__PURE__*/React.createElement("div", {
18085
- className: css(styles$w.lintContainer)
18086
- }, this.renderLink(styles$w.hoverTarget), /*#__PURE__*/React.createElement("div", null, children));
18048
+ className: css(styles$A.lintContainer)
18049
+ }, this.renderLink(styles$A.hoverTarget), /*#__PURE__*/React.createElement("div", null, children));
18087
18050
  }
18088
18051
 
18089
18052
  } // $FlowFixMe[prop-missing]
@@ -18108,7 +18071,7 @@ _defineProperty(Lint, "propTypes", {
18108
18071
  severity: PropTypes.number
18109
18072
  });
18110
18073
 
18111
- var styles$w = StyleSheet.create({
18074
+ var styles$A = StyleSheet.create({
18112
18075
  // This is the class of the outermost element.
18113
18076
  // We use relative positioning so that the lint indicator can be
18114
18077
  // positioned absolutely relative to the position of the linty container.
@@ -19998,7 +19961,7 @@ class Renderer extends React.Component {
19998
19961
  paddingRight: margin
19999
19962
  };
20000
19963
 
20001
- var mobileInnerStyle = _objectSpread2(_objectSpread2({}, innerStyle), styles$v.mobileZoomableParentFix);
19964
+ var mobileInnerStyle = _objectSpread2(_objectSpread2({}, innerStyle), styles$z.mobileZoomableParentFix);
20002
19965
 
20003
19966
  return /*#__PURE__*/React.createElement("div", {
20004
19967
  key: state.key,
@@ -20136,7 +20099,7 @@ class Renderer extends React.Component {
20136
20099
  paddingRight: 0
20137
20100
  };
20138
20101
 
20139
- var _mobileInnerStyle = _objectSpread2(_objectSpread2({}, _innerStyle), styles$v.mobileZoomableParentFix);
20102
+ var _mobileInnerStyle = _objectSpread2(_objectSpread2({}, _innerStyle), styles$z.mobileZoomableParentFix);
20140
20103
 
20141
20104
  var wrappedOutput = /*#__PURE__*/React.createElement("div", {
20142
20105
  style: _objectSpread2(_objectSpread2({}, _mobileInnerStyle), {}, {
@@ -20815,7 +20778,7 @@ _defineProperty(Renderer, "defaultProps", {
20815
20778
  linterContext: PerseusLinter.linterContextDefault
20816
20779
  });
20817
20780
 
20818
- var styles$v = {
20781
+ var styles$z = {
20819
20782
  mobileZoomableParentFix: {
20820
20783
  // NOTE(abdul): There is an issue where transform animations will
20821
20784
  // cause the Zoomable component to disappear when running on the
@@ -22991,7 +22954,7 @@ _defineProperty(PossibleAnswers, "propTypes", {
22991
22954
  answers: PropTypes.arrayOf(PropTypes.string)
22992
22955
  });
22993
22956
 
22994
- var _excluded$2 = ["keypadElement", "onFocus", "value"];
22957
+ var _excluded$3 = ["keypadElement", "onFocus", "value"];
22995
22958
  var SimpleKeypadInput = createReactClass({
22996
22959
  displayName: "SimpleKeypadInput",
22997
22960
  propTypes: {
@@ -23036,7 +22999,7 @@ var SimpleKeypadInput = createReactClass({
23036
22999
  onFocus: _onFocus,
23037
23000
  value
23038
23001
  } = _this$props,
23039
- rest = _objectWithoutProperties(_this$props, _excluded$2);
23002
+ rest = _objectWithoutProperties(_this$props, _excluded$3);
23040
23003
 
23041
23004
  return /*#__PURE__*/React.createElement(KeypadInput // eslint-disable-next-line react/no-string-refs
23042
23005
  , _extends({
@@ -24122,7 +24085,7 @@ var mediaQueries = {
24122
24085
  var {
24123
24086
  zIndexAboveScratchpad,
24124
24087
  zIndexInteractiveComponent,
24125
- radioBorderColor: radioBorderColor$1,
24088
+ radioBorderColor,
24126
24089
  checkedColor,
24127
24090
  circleSize,
24128
24091
  radioMarginWidth
@@ -24177,7 +24140,7 @@ var sharedStyles = StyleSheet.create({
24177
24140
  },
24178
24141
  backgroundColor: "#fff",
24179
24142
  border: "2px solid #fff",
24180
- boxShadow: "0 0px 0px 1px ".concat(radioBorderColor$1),
24143
+ boxShadow: "0 0px 0px 1px ".concat(radioBorderColor),
24181
24144
  outline: "none",
24182
24145
  boxSizing: "border-box",
24183
24146
  flexShrink: 0,
@@ -24430,36 +24393,6 @@ _defineProperty(Icon, "defaultProps", {
24430
24393
  color: "currentColor"
24431
24394
  });
24432
24395
 
24433
- class FocusRing extends React.Component {
24434
- render() {
24435
- var borderColor = this.props.visible ? this.props.color : "transparent";
24436
- var style = {
24437
- borderColor: borderColor
24438
- };
24439
- return /*#__PURE__*/React.createElement("span", {
24440
- className: css(styles$u.ring),
24441
- style: style
24442
- }, this.props.children);
24443
- }
24444
-
24445
- }
24446
-
24447
- _defineProperty(FocusRing, "defaultProps", {
24448
- visible: true,
24449
- color: kaGreen$1
24450
- });
24451
-
24452
- var styles$u = StyleSheet.create({
24453
- ring: {
24454
- margin: "auto",
24455
- display: "inline-block",
24456
- borderRadius: "50%",
24457
- borderWidth: 2,
24458
- padding: 2,
24459
- borderStyle: "solid"
24460
- }
24461
- });
24462
-
24463
24396
  /**
24464
24397
  * Given a choice's position in the radio widget, return the corresponding
24465
24398
  * "choice letter". (For example, in English, `getChoiceLetter(0)` is "A",
@@ -24490,179 +24423,57 @@ function getChoiceLetter(pos) {
24490
24423
  return " ";
24491
24424
  }
24492
24425
 
24493
- var SAT_ICON_SIZE = 25;
24494
-
24495
- class SATChoiceIcon extends React.Component {
24496
- // TODO(amy): figure out a better scheme for specifying these
24497
- // styles that isn't such a pain to grok. See some neat ideas
24498
- // from MDR in https://phabricator.khanacademy.org/D35249.
24499
- constructStyles(reviewMode, correct, checked) {
24500
- var backgroundColor;
24501
- var borderColor = satBlue;
24502
- var color = satBlue;
24503
-
24504
- if (reviewMode) {
24505
- if (correct) {
24506
- borderColor = satCorrectColor;
24507
- color = checked ? white$1 : satCorrectColor;
24508
- backgroundColor = checked ? satCorrectColor : white$1;
24509
- } else if (checked) {
24510
- borderColor = satIncorrectColor;
24511
- color = white$1;
24512
- backgroundColor = satIncorrectColor;
24513
- }
24514
- } else if (checked) {
24515
- color = white$1;
24516
- backgroundColor = satBlue;
24517
- }
24518
-
24519
- return {
24520
- color,
24521
- backgroundColor,
24522
- borderColor
24523
- };
24524
- }
24525
-
24526
- render() {
24527
- var {
24528
- letter,
24529
- a11yText,
24530
- reviewMode,
24531
- checked,
24532
- correct,
24533
- crossedOut
24534
- } = this.props;
24535
- var {
24536
- color,
24537
- backgroundColor,
24538
- borderColor
24539
- } = this.constructStyles(reviewMode, correct, checked);
24540
- return /*#__PURE__*/React.createElement("div", {
24541
- className: css(styles$t.iconWrapper)
24542
- }, /*#__PURE__*/React.createElement("div", {
24543
- className: css(styles$t.satCircle),
24544
- style: {
24545
- backgroundColor,
24546
- borderColor
24547
- }
24548
- }), /*#__PURE__*/React.createElement("div", {
24549
- style: {
24550
- color
24551
- },
24552
- className: css(styles$t.letter)
24553
- }, /*#__PURE__*/React.createElement("span", {
24554
- className: "perseus-sr-only"
24555
- }, a11yText), /*#__PURE__*/React.createElement("span", {
24556
- "aria-hidden": "true"
24557
- }, letter)), crossedOut && /*#__PURE__*/React.createElement(CrossOutLine, {
24558
- color: borderColor,
24559
- sat: true
24560
- }));
24561
- }
24426
+ /**
24427
+ * Renders a circular selection ring around the child.
24428
+ */
24562
24429
 
24430
+ function FocusRing(props) {
24431
+ var {
24432
+ visible,
24433
+ color,
24434
+ children,
24435
+ multipleSelect
24436
+ } = props;
24437
+ var borderColor = visible ? color : "transparent";
24438
+ var borderRadius = multipleSelect ? 5 : "50%";
24439
+ var style = {
24440
+ borderColor,
24441
+ borderRadius
24442
+ };
24443
+ return /*#__PURE__*/React.createElement("span", {
24444
+ "data-test-id": "focus-ring",
24445
+ className: css(styles$y.ring),
24446
+ style: style
24447
+ }, children);
24563
24448
  }
24564
24449
 
24565
- var LIBRARY_ICON_SIZE = 24;
24566
-
24567
- class LibraryChoiceIcon extends React.Component {
24568
- getChoiceInner() {
24569
- var {
24570
- letter,
24571
- showCorrectness,
24572
- correct
24573
- } = this.props;
24574
-
24575
- if (!showCorrectness) {
24576
- return letter;
24577
- }
24578
-
24579
- if (correct) {
24580
- return /*#__PURE__*/React.createElement(InlineIcon, _extends({}, iconCheck, {
24581
- style: {
24582
- position: "relative",
24583
- top: -1
24584
- }
24585
- }));
24586
- }
24587
-
24588
- return /*#__PURE__*/React.createElement(InlineIcon, iconMinus);
24589
- } // Handle dynamic styling of the multiple choice icon. Most
24590
- // MC icon styles are constant, but we do allow the caller
24591
- // to specify the selected color, and thus must control styles
24592
- // related to the selected state dynamically.
24593
-
24594
-
24595
- getDynamicStyles() {
24596
- var {
24597
- checked,
24598
- showCorrectness,
24599
- pressed,
24600
- primaryProductColor,
24601
- correct,
24602
- transparentBackground
24603
- } = this.props;
24604
-
24605
- if (!showCorrectness && pressed) {
24606
- return {
24607
- borderColor: primaryProductColor,
24608
- color: primaryProductColor,
24609
- backgroundColor: transparentBackground ? "transparent" : white$1
24610
- };
24611
- }
24612
-
24613
- if (checked) {
24614
- // Note: kaGreen is not only the default product color,
24615
- // but also the "correctness" color
24616
- var bg = showCorrectness && correct ? Color.green : primaryProductColor;
24617
- return {
24618
- color: white$1,
24619
- backgroundColor: bg,
24620
- borderColor: bg
24621
- };
24622
- }
24623
-
24624
- return {
24625
- borderColor: Color.offBlack64,
24626
- color: Color.offBlack64
24627
- };
24450
+ FocusRing.defaultProps = {
24451
+ visible: true,
24452
+ color: kaGreen$1,
24453
+ multipleSelect: false
24454
+ };
24455
+ var styles$y = StyleSheet.create({
24456
+ ring: {
24457
+ margin: "auto",
24458
+ display: "inline-block",
24459
+ borderWidth: 2,
24460
+ padding: 2,
24461
+ borderStyle: "solid"
24628
24462
  }
24463
+ });
24629
24464
 
24630
- render() {
24631
- var {
24632
- a11yText,
24633
- checked,
24634
- crossedOut,
24635
- showCorrectness,
24636
- correct,
24637
- focused,
24638
- primaryProductColor,
24639
- previouslyAnswered
24640
- } = this.props;
24641
- var dynamicStyles = this.getDynamicStyles();
24642
- return /*#__PURE__*/React.createElement("div", {
24643
- className: css(styles$t.iconWrapper)
24644
- }, /*#__PURE__*/React.createElement(FocusRing, {
24645
- color: primaryProductColor,
24646
- visible: focused
24647
- }, /*#__PURE__*/React.createElement("div", {
24648
- style: dynamicStyles,
24649
- className: 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
24650
- // radio icon
24651
- ,
24652
- "data-is-radio-icon": true
24653
- }, /*#__PURE__*/React.createElement("div", {
24654
- className: "perseus-sr-only"
24655
- }, a11yText), /*#__PURE__*/React.createElement("div", {
24656
- "aria-hidden": true
24657
- }, this.getChoiceInner()))), crossedOut && /*#__PURE__*/React.createElement(CrossOutLine, {
24658
- color: dynamicStyles.borderColor
24659
- }));
24465
+ var SAT_ICON_SIZE = 25;
24466
+ var LIBRARY_ICON_SIZE = 24;
24467
+ var styles$x = StyleSheet.create({
24468
+ iconWrapper: {
24469
+ display: "inline-block",
24470
+ position: "relative"
24660
24471
  }
24472
+ });
24661
24473
 
24662
- } // The "cross-out line" SVG is a bit bigger than the library icon, to provide
24474
+ /* Component for rendering a letter icon in radio choice */
24663
24475
  // extra space for the cute rounded line cap.
24664
24476
 
24665
-
24666
24477
  var CROSS_OUT_LINE_PADDING = 2;
24667
24478
  var CROSS_OUT_LINE_SIZE = LIBRARY_ICON_SIZE + CROSS_OUT_LINE_PADDING * 2;
24668
24479
  var SAT_CROSS_OUT_LINE_SIZE = SAT_ICON_SIZE + CROSS_OUT_LINE_PADDING * 2;
@@ -24677,7 +24488,7 @@ function CrossOutLine(props) {
24677
24488
  width: crossOutLineSize,
24678
24489
  height: crossOutLineSize,
24679
24490
  viewBox: "0 0 ".concat(crossOutLineSize, " ").concat(crossOutLineSize),
24680
- className: css(styles$t.crossOutLine)
24491
+ className: css(styles$w.crossOutLine)
24681
24492
  }, /*#__PURE__*/React.createElement("line", {
24682
24493
  // The line stretches from the bottom-left to top-right.
24683
24494
  // We don't quite go to the _very_ corner, because the cute
@@ -24692,151 +24503,129 @@ function CrossOutLine(props) {
24692
24503
  }));
24693
24504
  }
24694
24505
 
24695
- class ChoiceIcon extends React.Component {
24696
- a11yText(letter) {
24697
- var {
24698
- checked,
24699
- correct,
24700
- crossedOut,
24701
- showCorrectness
24702
- } = this.props; // There are two pieces of metadata we want to add to each a11yText:
24703
- // whether the answer was checked/crossed-out/neither, and whether the
24704
- // answer is correct/incorrect/not-yet-revealed.
24705
- //
24706
- // Translation is tricky for cross-product situations like this, so
24707
- // we've just enumerated all 9 possibilities as separate strings.
24708
-
24709
- if (showCorrectness && correct) {
24710
- if (checked) {
24711
- return i18n._("(Choice %(letter)s, Checked, Correct)", {
24712
- letter
24713
- });
24714
- }
24715
-
24716
- if (crossedOut) {
24717
- return i18n._("(Choice %(letter)s, Crossed out, Correct)", {
24718
- letter
24719
- });
24720
- }
24506
+ var styles$w = StyleSheet.create({
24507
+ crossOutLine: {
24508
+ // Center the icon within the container.
24509
+ position: "absolute",
24510
+ top: "calc(50% - ".concat(CROSS_OUT_LINE_SIZE / 2, "px)"),
24511
+ left: "calc(50% - ".concat(CROSS_OUT_LINE_SIZE / 2, "px)")
24512
+ }
24513
+ });
24721
24514
 
24722
- return i18n._("(Choice %(letter)s, Correct)", {
24723
- letter
24724
- });
24725
- }
24515
+ function ChoiceInner(props) {
24516
+ var {
24517
+ letter,
24518
+ showCorrectness,
24519
+ correct
24520
+ } = props;
24726
24521
 
24727
- if (showCorrectness && !correct) {
24728
- if (checked) {
24729
- return i18n._("(Choice %(letter)s, Checked, Incorrect)", {
24730
- letter
24731
- });
24732
- }
24522
+ if (!showCorrectness) {
24523
+ return letter;
24524
+ }
24733
24525
 
24734
- if (crossedOut) {
24735
- return i18n._("(Choice %(letter)s, Crossed out, Incorrect)", {
24736
- letter
24737
- });
24526
+ if (correct) {
24527
+ return /*#__PURE__*/React.createElement(InlineIcon, _extends({}, iconCheck, {
24528
+ style: {
24529
+ position: "relative",
24530
+ top: -1
24738
24531
  }
24739
-
24740
- return i18n._("(Choice %(letter)s, Incorrect)", {
24741
- letter
24742
- });
24743
- }
24744
-
24745
- if (checked) {
24746
- return i18n._("(Choice %(letter)s, Checked)", {
24747
- letter
24748
- });
24749
- }
24750
-
24751
- if (crossedOut) {
24752
- return i18n._("(Choice %(letter)s, Crossed out)", {
24753
- letter
24754
- });
24755
- }
24756
-
24757
- return i18n._("(Choice %(letter)s)", {
24758
- letter
24759
- });
24532
+ }));
24760
24533
  }
24761
24534
 
24762
- render() {
24763
- var {
24764
- pos,
24765
- reviewMode,
24766
- checked,
24767
- crossedOut,
24768
- correct,
24769
- product,
24770
- showCorrectness,
24771
- pressed,
24772
- focused,
24773
- primaryProductColor,
24774
- previouslyAnswered,
24775
- transparentBackground
24776
- } = this.props;
24777
- var letter = getChoiceLetter(pos);
24535
+ return /*#__PURE__*/React.createElement(InlineIcon, iconMinus);
24536
+ } // Handle dynamic styling of the multiple choice icon. Most
24537
+ // MC icon styles are constant, but we do allow the caller
24538
+ // to specify the selected color, and thus must control styles
24539
+ // related to the selected state dynamically.
24540
+
24541
+
24542
+ function getDynamicStyles(checked, showCorrectness, pressed, multipleSelect, primaryProductColor, correct, transparentBackground) {
24543
+ var backgroundColor;
24544
+ var borderColor;
24545
+ var color;
24546
+
24547
+ if (!showCorrectness && pressed) {
24548
+ borderColor = primaryProductColor;
24549
+ color = primaryProductColor;
24550
+ backgroundColor = transparentBackground ? "transparent" : white$1;
24551
+ } else if (checked) {
24552
+ // Note: kaGreen is not only the default product color,
24553
+ // but also the "correctness" color
24554
+ var bg = showCorrectness && correct ? Color.green : primaryProductColor;
24555
+ color = white$1;
24556
+ backgroundColor = bg;
24557
+ borderColor = bg;
24558
+ } else {
24559
+ borderColor = Color.offBlack64;
24560
+ color = Color.offBlack64;
24561
+ } // define shape
24778
24562
 
24779
- if (product === "sat") {
24780
- return /*#__PURE__*/React.createElement(SATChoiceIcon, {
24781
- letter: letter,
24782
- a11yText: this.a11yText(letter),
24783
- reviewMode: reviewMode,
24784
- checked: checked,
24785
- correct: correct,
24786
- crossedOut: crossedOut
24787
- });
24788
- }
24789
24563
 
24790
- return /*#__PURE__*/React.createElement(LibraryChoiceIcon, {
24791
- letter: letter,
24792
- a11yText: this.a11yText(letter),
24793
- reviewMode: reviewMode,
24794
- checked: checked,
24795
- crossedOut: crossedOut,
24796
- pressed: pressed,
24797
- focused: focused,
24798
- correct: correct,
24799
- showCorrectness: showCorrectness,
24800
- primaryProductColor: primaryProductColor,
24801
- previouslyAnswered: previouslyAnswered,
24802
- transparentBackground: transparentBackground
24803
- });
24564
+ var borderRadius;
24565
+
24566
+ if (multipleSelect) {
24567
+ borderRadius = 3;
24568
+ } else {
24569
+ borderRadius = LIBRARY_ICON_SIZE;
24804
24570
  }
24805
24571
 
24572
+ return {
24573
+ backgroundColor,
24574
+ borderColor,
24575
+ color,
24576
+ borderRadius
24577
+ };
24806
24578
  }
24807
24579
 
24808
- _defineProperty(ChoiceIcon, "defaultProps", {
24809
- primaryProductColor: Color.blue
24810
- });
24580
+ function LibraryChoiceIcon(props) {
24581
+ var {
24582
+ a11yText,
24583
+ checked,
24584
+ crossedOut,
24585
+ showCorrectness,
24586
+ correct,
24587
+ focused,
24588
+ hovered,
24589
+ multipleSelect,
24590
+ primaryProductColor,
24591
+ previouslyAnswered,
24592
+ letter,
24593
+ pressed,
24594
+ transparentBackground
24595
+ } = props;
24596
+ var dynamicStyles = getDynamicStyles(checked, showCorrectness, pressed, multipleSelect, primaryProductColor, correct, transparentBackground);
24597
+ return /*#__PURE__*/React.createElement("div", {
24598
+ className: css(styles$x.iconWrapper)
24599
+ }, /*#__PURE__*/React.createElement(FocusRing, {
24600
+ color: primaryProductColor,
24601
+ visible: focused || hovered,
24602
+ multipleSelect: multipleSelect
24603
+ }, /*#__PURE__*/React.createElement("div", {
24604
+ style: dynamicStyles,
24605
+ "data-test-id": "choice-icon__library-choice-icon",
24606
+ className: 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
24607
+ // radio icon
24608
+ ,
24609
+ "data-is-radio-icon": true
24610
+ }, /*#__PURE__*/React.createElement("div", {
24611
+ className: "perseus-sr-only"
24612
+ }, a11yText), /*#__PURE__*/React.createElement("div", {
24613
+ "aria-hidden": true
24614
+ }, /*#__PURE__*/React.createElement(ChoiceInner, {
24615
+ letter: letter,
24616
+ showCorrectness: showCorrectness,
24617
+ correct: correct
24618
+ })))), crossedOut && /*#__PURE__*/React.createElement(CrossOutLine, {
24619
+ color: dynamicStyles.borderColor
24620
+ }));
24621
+ }
24811
24622
 
24812
- var styles$t = StyleSheet.create({
24813
- satCircle: {
24814
- display: "block",
24815
- borderRadius: SAT_ICON_SIZE,
24816
- borderStyle: "solid",
24817
- borderWidth: 2,
24818
- content: "''",
24819
- height: SAT_ICON_SIZE,
24820
- width: SAT_ICON_SIZE,
24821
- top: 1,
24822
- left: 1
24823
- },
24824
- iconWrapper: {
24825
- display: "inline-block",
24826
- position: "relative"
24827
- },
24828
- crossOutLine: {
24829
- // Center the icon within the container.
24830
- position: "absolute",
24831
- top: "calc(50% - ".concat(CROSS_OUT_LINE_SIZE / 2, "px)"),
24832
- left: "calc(50% - ".concat(CROSS_OUT_LINE_SIZE / 2, "px)")
24833
- },
24834
- libraryCircle: {
24623
+ var styles$v = StyleSheet.create({
24624
+ circle: {
24835
24625
  // Make the circle
24836
24626
  width: LIBRARY_ICON_SIZE,
24837
24627
  height: LIBRARY_ICON_SIZE,
24838
24628
  boxSizing: "border-box",
24839
- borderRadius: LIBRARY_ICON_SIZE,
24840
24629
  borderStyle: "solid",
24841
24630
  borderWidth: 2,
24842
24631
  // The default icons have letters in them. Style those letters.
@@ -24857,18 +24646,113 @@ var styles$t = StyleSheet.create({
24857
24646
  // pixel so it is much better centered.
24858
24647
  lineHeight: "1px"
24859
24648
  },
24860
- libraryCircleCorrect: {
24649
+ circleCorrect: {
24861
24650
  fontSize: LIBRARY_ICON_SIZE
24862
24651
  },
24863
- libraryCircleIncorrect: {
24652
+ circleIncorrect: {
24864
24653
  fontSize: LIBRARY_ICON_SIZE,
24865
24654
  borderColor: gray68,
24866
24655
  color: gray68
24867
24656
  },
24868
- libraryCircleIncorrectAnswered: {
24657
+ circleIncorrectAnswered: {
24869
24658
  backgroundColor: Color.red,
24870
24659
  borderColor: Color.red,
24871
24660
  color: Color.white
24661
+ }
24662
+ });
24663
+
24664
+ /* Component for rendering a letter icon in a SAT radio choice */
24665
+
24666
+ // TODO(amy): figure out a better scheme for specifying these
24667
+ // styles that isn't such a pain to grok. See some neat ideas
24668
+ // from MDR in https://phabricator.khanacademy.org/D35249.
24669
+ function constructSATStyles(reviewMode, correct, checked, multipleSelect) {
24670
+ var backgroundColor;
24671
+ var borderColor = satBlue;
24672
+ var color = satBlue;
24673
+
24674
+ if (reviewMode) {
24675
+ if (correct) {
24676
+ borderColor = satCorrectColor;
24677
+ color = checked ? white$1 : satCorrectColor;
24678
+ backgroundColor = checked ? satCorrectColor : white$1;
24679
+ } else if (checked) {
24680
+ borderColor = satIncorrectColor;
24681
+ color = white$1;
24682
+ backgroundColor = satIncorrectColor;
24683
+ }
24684
+ } else if (checked) {
24685
+ color = white$1;
24686
+ backgroundColor = satBlue;
24687
+ }
24688
+
24689
+ var borderRadius;
24690
+
24691
+ if (multipleSelect) {
24692
+ borderRadius = 3;
24693
+ } else {
24694
+ borderRadius = SAT_ICON_SIZE;
24695
+ }
24696
+
24697
+ return {
24698
+ color,
24699
+ backgroundColor,
24700
+ borderColor,
24701
+ borderRadius
24702
+ };
24703
+ }
24704
+
24705
+ function SATChoiceIcon(props) {
24706
+ var {
24707
+ letter,
24708
+ a11yText,
24709
+ crossedOut,
24710
+ reviewMode,
24711
+ correct,
24712
+ checked,
24713
+ multipleSelect
24714
+ } = props;
24715
+ var {
24716
+ color,
24717
+ backgroundColor,
24718
+ borderColor,
24719
+ borderRadius
24720
+ } = constructSATStyles(reviewMode, correct, checked, multipleSelect);
24721
+ return /*#__PURE__*/React.createElement("div", {
24722
+ className: css(styles$x.iconWrapper)
24723
+ }, /*#__PURE__*/React.createElement("div", {
24724
+ className: css(styles$u.satCircle),
24725
+ "data-test-id": "choice-icon__sat-choice-icon",
24726
+ style: {
24727
+ backgroundColor,
24728
+ borderColor,
24729
+ borderRadius
24730
+ }
24731
+ }), /*#__PURE__*/React.createElement("div", {
24732
+ style: {
24733
+ color
24734
+ },
24735
+ className: css(styles$u.letter)
24736
+ }, /*#__PURE__*/React.createElement("span", {
24737
+ className: "perseus-sr-only"
24738
+ }, a11yText), /*#__PURE__*/React.createElement("span", {
24739
+ "aria-hidden": "true"
24740
+ }, letter)), crossedOut && /*#__PURE__*/React.createElement(CrossOutLine, {
24741
+ color: borderColor,
24742
+ sat: true
24743
+ }));
24744
+ }
24745
+
24746
+ var styles$u = StyleSheet.create({
24747
+ satCircle: {
24748
+ display: "block",
24749
+ borderStyle: "solid",
24750
+ borderWidth: 2,
24751
+ content: "''",
24752
+ height: SAT_ICON_SIZE,
24753
+ width: SAT_ICON_SIZE,
24754
+ top: 1,
24755
+ left: 1
24872
24756
  },
24873
24757
  letter: {
24874
24758
  // These properties make sure that this element has the exact
@@ -24888,6 +24772,118 @@ var styles$t = StyleSheet.create({
24888
24772
  }
24889
24773
  });
24890
24774
 
24775
+ /* Component for rendering a letter icon in radio choice */
24776
+
24777
+ function getA11yText(letter, checked, correct, crossedOut, showCorrectness) {
24778
+ // There are two pieces of metadata we want to add to each a11yText:
24779
+ // whether the answer was checked/crossed-out/neither, and whether the
24780
+ // answer is correct/incorrect/not-yet-revealed.
24781
+ //
24782
+ // Translation is tricky for cross-product situations like this, so
24783
+ // we've just enumerated all 9 possibilities as separate strings.
24784
+ if (showCorrectness && correct) {
24785
+ if (checked) {
24786
+ return i18n._("(Choice %(letter)s, Checked, Correct)", {
24787
+ letter
24788
+ });
24789
+ }
24790
+
24791
+ if (crossedOut) {
24792
+ return i18n._("(Choice %(letter)s, Crossed out, Correct)", {
24793
+ letter
24794
+ });
24795
+ }
24796
+
24797
+ return i18n._("(Choice %(letter)s, Correct)", {
24798
+ letter
24799
+ });
24800
+ }
24801
+
24802
+ if (showCorrectness && !correct) {
24803
+ if (checked) {
24804
+ return i18n._("(Choice %(letter)s, Checked, Incorrect)", {
24805
+ letter
24806
+ });
24807
+ }
24808
+
24809
+ if (crossedOut) {
24810
+ return i18n._("(Choice %(letter)s, Crossed out, Incorrect)", {
24811
+ letter
24812
+ });
24813
+ }
24814
+
24815
+ return i18n._("(Choice %(letter)s, Incorrect)", {
24816
+ letter
24817
+ });
24818
+ }
24819
+
24820
+ if (checked) {
24821
+ return i18n._("(Choice %(letter)s, Checked)", {
24822
+ letter
24823
+ });
24824
+ }
24825
+
24826
+ if (crossedOut) {
24827
+ return i18n._("(Choice %(letter)s, Crossed out)", {
24828
+ letter
24829
+ });
24830
+ }
24831
+
24832
+ return i18n._("(Choice %(letter)s)", {
24833
+ letter
24834
+ });
24835
+ }
24836
+
24837
+ function ChoiceIcon(props) {
24838
+ var {
24839
+ pos,
24840
+ reviewMode,
24841
+ checked,
24842
+ crossedOut,
24843
+ correct,
24844
+ product,
24845
+ multipleSelect,
24846
+ showCorrectness,
24847
+ focused,
24848
+ hovered,
24849
+ pressed,
24850
+ primaryProductColor = Color.blue,
24851
+ previouslyAnswered,
24852
+ transparentBackground
24853
+ } = props;
24854
+ var letter = getChoiceLetter(pos);
24855
+ var a11yText = getA11yText(letter, checked, correct, crossedOut, showCorrectness);
24856
+
24857
+ if (product === "sat") {
24858
+ return /*#__PURE__*/React.createElement(SATChoiceIcon, {
24859
+ letter: letter,
24860
+ a11yText: a11yText,
24861
+ reviewMode: reviewMode,
24862
+ checked: checked,
24863
+ correct: correct,
24864
+ crossedOut: crossedOut,
24865
+ multipleSelect: multipleSelect
24866
+ });
24867
+ }
24868
+
24869
+ return /*#__PURE__*/React.createElement(LibraryChoiceIcon, {
24870
+ letter: letter,
24871
+ a11yText: a11yText,
24872
+ reviewMode: reviewMode,
24873
+ checked: checked,
24874
+ crossedOut: crossedOut,
24875
+ focused: focused,
24876
+ hovered: hovered,
24877
+ pressed: pressed,
24878
+ correct: correct,
24879
+ showCorrectness: showCorrectness,
24880
+ primaryProductColor: primaryProductColor,
24881
+ previouslyAnswered: previouslyAnswered,
24882
+ transparentBackground: transparentBackground,
24883
+ multipleSelect: multipleSelect
24884
+ });
24885
+ }
24886
+
24891
24887
  /**
24892
24888
  * Renders text indicating whether the choice was correct or
24893
24889
  * not and whether the choice was selected or not.
@@ -24896,63 +24892,68 @@ var styles$t = StyleSheet.create({
24896
24892
  * between the states more immediately clear to users.
24897
24893
  */
24898
24894
 
24899
- class OptionStatus extends React.Component {
24900
- _renderText() {
24901
- var {
24902
- checked,
24903
- correct,
24904
- crossedOut
24905
- } = this.props;
24895
+ function renderText(checked, correct, crossedOut) {
24896
+ if (correct) {
24897
+ // For correct answers, we surface checked _or_ crossedOut state,
24898
+ // because any interaction with the correct answer is noteworthy!
24899
+ if (checked) {
24900
+ return i18n._("Correct (selected)");
24901
+ }
24906
24902
 
24907
- if (correct) {
24908
- // For correct answers, we surface checked _or_ crossedOut state,
24909
- // because any interaction with the correct answer is noteworthy!
24910
- if (checked) {
24911
- return i18n._("Correct (selected)");
24912
- }
24903
+ if (crossedOut) {
24904
+ return i18n._("Correct (but you crossed it out)");
24905
+ }
24913
24906
 
24914
- if (crossedOut) {
24915
- return i18n._("Correct (but you crossed it out)");
24916
- }
24907
+ return i18n._("Correct");
24908
+ } // But, for incorrect answers, we only surface checked state,
24909
+ // because crossing out an incorrect answer is not noteworthy.
24917
24910
 
24918
- return i18n._("Correct");
24919
- } // But, for incorrect answers, we only surface checked state,
24920
- // because crossing out an incorrect answer is not noteworthy.
24921
24911
 
24912
+ if (checked) {
24913
+ return i18n._("Incorrect (selected)");
24914
+ }
24922
24915
 
24923
- if (checked) {
24924
- return i18n._("Incorrect (selected)");
24925
- }
24916
+ return i18n._("Incorrect");
24917
+ }
24926
24918
 
24927
- return i18n._("Incorrect");
24919
+ function OptionStatus(props) {
24920
+ var {
24921
+ checked,
24922
+ correct,
24923
+ crossedOut,
24924
+ previouslyAnswered,
24925
+ reviewMode,
24926
+ satStyling
24927
+ } = props; // Option status is exclued for SAT
24928
+
24929
+ if (satStyling) {
24930
+ return null;
24931
+ } // Option status is shown only in review mode, or for incorrectly
24932
+ // answered items.
24933
+
24934
+
24935
+ if (!reviewMode && !previouslyAnswered) {
24936
+ return null;
24928
24937
  }
24929
24938
 
24930
- render() {
24931
- var {
24932
- checked,
24933
- correct,
24934
- previouslyAnswered
24935
- } = this.props;
24936
- var textStyle;
24939
+ var textStyle;
24937
24940
 
24938
- if (correct) {
24939
- textStyle = styles$s.correct;
24941
+ if (correct) {
24942
+ textStyle = styles$t.correct;
24943
+ } else {
24944
+ if (checked || previouslyAnswered) {
24945
+ textStyle = styles$t.incorrectAnswered;
24940
24946
  } else {
24941
- if (checked || previouslyAnswered) {
24942
- textStyle = styles$s.incorrectAnswered;
24943
- } else {
24944
- textStyle = styles$s.incorrect;
24945
- }
24947
+ textStyle = styles$t.incorrect;
24946
24948
  }
24947
-
24948
- return /*#__PURE__*/React.createElement("div", {
24949
- className: css(styles$s.text, textStyle)
24950
- }, this._renderText());
24951
24949
  }
24952
24950
 
24951
+ return /*#__PURE__*/React.createElement("div", {
24952
+ className: css(styles$t.text, textStyle)
24953
+ }, renderText(checked, correct, crossedOut));
24953
24954
  }
24954
24955
 
24955
- var styles$s = StyleSheet.create({
24956
+ var styles$t = StyleSheet.create({
24956
24957
  text: {
24957
24958
  alignItems: "center",
24958
24959
  display: "flex",
@@ -24979,316 +24980,206 @@ var ellipsisHorizontalIcon = {
24979
24980
  height: 27.284
24980
24981
  };
24981
24982
 
24982
- class Choice extends React.Component {
24983
- constructor() {
24984
- super(...arguments);
24985
-
24986
- _defineProperty(this, "justFinishedTouch", void 0);
24987
-
24988
- _defineProperty(this, "justFinishedTimeoutID", void 0);
24989
-
24990
- _defineProperty(this, "_input", void 0);
24991
-
24992
- _defineProperty(this, "state", {
24993
- isInputFocused: false,
24994
- isInputActive: false
24995
- });
24996
-
24997
- _defineProperty(this, "inputRef", ref => {
24998
- this._input = ref;
24999
- });
25000
-
25001
- _defineProperty(this, "renderOptionStatus", () => {
25002
- var {
25003
- correct,
25004
- checked,
25005
- crossedOut,
25006
- reviewMode,
25007
- previouslyAnswered
25008
- } = this.props; // Option status is exclued for SAT
25009
-
25010
- if (this.props.apiOptions.satStyling) {
25011
- return null;
25012
- } // Option status is shown only in review mode, or for incorrectly
25013
- // answered items.
25014
-
25015
-
25016
- if (!reviewMode && !previouslyAnswered) {
25017
- return null;
25018
- }
25019
-
25020
- return /*#__PURE__*/React.createElement(OptionStatus, {
25021
- checked: checked,
25022
- correct: correct,
25023
- crossedOut: crossedOut,
25024
- previouslyAnswered: previouslyAnswered
25025
- });
25026
- });
25027
-
25028
- _defineProperty(this, "renderChoiceIcon", _ref => {
25029
- var {
25030
- isFocused,
25031
- isPressed
25032
- } = _ref;
25033
- var {
25034
- radioStyleVersion,
25035
- primaryProductColor
25036
- } = this.props.apiOptions.styling;
25037
- var finalStyles = typeof radioStyleVersion === "undefined" ? false : radioStyleVersion === "final";
25038
-
25039
- if (!finalStyles && !this.props.apiOptions.satStyling) {
25040
- return null;
25041
- }
25042
-
25043
- return /*#__PURE__*/React.createElement(ChoiceIcon, {
25044
- pos: this.props.pos,
25045
- correct: this.props.correct,
25046
- crossedOut: this.props.crossedOut,
25047
- pressed: isPressed,
25048
- focused: isFocused,
25049
- checked: this.props.checked,
25050
- showCorrectness: this.props.showCorrectness,
25051
- reviewMode: this.props.reviewMode,
25052
- product: this.props.apiOptions.satStyling ? "sat" : "library",
25053
- primaryProductColor: primaryProductColor,
25054
- previouslyAnswered: this.props.previouslyAnswered
25055
- });
25056
- });
24983
+ function Choice(props) {
24984
+ var {
24985
+ disabled,
24986
+ checked,
24987
+ content,
24988
+ crossedOut,
24989
+ showCorrectness,
24990
+ multipleSelect,
24991
+ onChange,
24992
+ reviewMode,
24993
+ correct,
24994
+ apiOptions,
24995
+ previouslyAnswered,
24996
+ pos,
24997
+ showRationale,
24998
+ rationale,
24999
+ forwardedRef
25000
+ } = props;
25001
+ var [isInputFocused, setIsInputFocused] = useState(false);
25002
+ useEffect(() => {
25003
+ if (isInputFocused && disabled) {
25004
+ setIsInputFocused(false);
25005
+ }
25006
+ }, [disabled, isInputFocused, setIsInputFocused]); // Call `this.props.onChange` with the given values. Any keys that are not
25007
+ // specified will be filled in with the current value. (For example, if
25008
+ // `checked` is specified but `crossedOut` is not, then `crossedOut` will
25009
+ // be filled in with `this.props.crossedOut`.)
25010
+ //
25011
+ // This enables us to use shorthand inside this component, while
25012
+ // maintaining a consistent API for the parent.
25057
25013
 
25058
- _defineProperty(this, "_sendChange", newValues => {
25059
- var _newValues$checked, _newValues$crossedOut;
25014
+ function sendChange(newValues) {
25015
+ var _newValues$checked, _newValues$crossedOut;
25060
25016
 
25061
- var checked = (_newValues$checked = newValues.checked) !== null && _newValues$checked !== void 0 ? _newValues$checked : this.props.checked;
25062
- var crossedOut = (_newValues$crossedOut = newValues.crossedOut) !== null && _newValues$crossedOut !== void 0 ? _newValues$crossedOut : this.props.crossedOut;
25063
- this.props.onChange({
25064
- checked,
25065
- crossedOut
25066
- });
25017
+ var updatedChecked = (_newValues$checked = newValues.checked) !== null && _newValues$checked !== void 0 ? _newValues$checked : checked;
25018
+ var updatedCrossedOut = (_newValues$crossedOut = newValues.crossedOut) !== null && _newValues$crossedOut !== void 0 ? _newValues$crossedOut : crossedOut;
25019
+ onChange({
25020
+ checked: updatedChecked,
25021
+ crossedOut: updatedCrossedOut
25067
25022
  });
25023
+ } // HACK: while most of the styling for rendering SAT items is handled
25024
+ // via aphrodite, we also need to assign normal CSS classnames here to
25025
+ // special-case the coloring of MathJax formulas (see .MathJax .math in
25026
+ // stylesheets/task-package/tasks.less)
25068
25027
 
25069
- _defineProperty(this, "focusInput", () => {
25070
- var _this$_input;
25071
25028
 
25072
- (_this$_input = this._input) === null || _this$_input === void 0 ? void 0 : _this$_input.focus();
25073
- });
25074
- }
25029
+ var satCorrectChoice = apiOptions.satStyling && reviewMode && correct;
25030
+ var satIncorrectChecked = apiOptions.satStyling && reviewMode && !correct && checked;
25031
+ var descriptionClassName = classNames("description", satCorrectChoice && "sat-correct", satIncorrectChecked && "sat-incorrect", css(!apiOptions.satStyling && styles$s.description, apiOptions.satStyling && styles$s.satDescription, satCorrectChoice && styles$s.satDescriptionCorrect, satCorrectChoice && checked && styles$s.satDescriptionCorrectChecked, satIncorrectChecked && styles$s.satDescriptionIncorrectChecked));
25032
+ var rationaleClassName = classNames("perseus-radio-rationale-content", 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
25033
+ // disabled. However, we don't want to do this in the SAT product and
25034
+ // we also don't want to do this when we're in review mode in the
25035
+ // content library.
25075
25036
 
25076
- UNSAFE_componentWillUpdate(nextProps) {
25077
- if (this.state.isInputFocused && nextProps.disabled) {
25078
- this.setState({
25079
- isInputFocused: false
25080
- });
25037
+ var showDimmed = !apiOptions.satStyling && !reviewMode && apiOptions.readOnly || crossedOut;
25038
+ return /*#__PURE__*/React.createElement("div", {
25039
+ style: {
25040
+ dispay: "flex",
25041
+ flexDirection: "column"
25081
25042
  }
25082
- }
25043
+ }, /*#__PURE__*/React.createElement("div", {
25044
+ style: {
25045
+ display: "flex",
25046
+ flexDirection: "row",
25047
+ width: "100%",
25048
+ opacity: showDimmed ? 0.5 : 1.0
25049
+ }
25050
+ }, /*#__PURE__*/React.createElement(Clickable, {
25051
+ onClick: () => {
25052
+ // If we're checking a crossed-out option, let's
25053
+ // also uncross it.
25054
+ sendChange({
25055
+ checked: !checked,
25056
+ crossedOut: false
25057
+ });
25058
+ },
25059
+ className: descriptionClassName,
25060
+ disabled: disabled || apiOptions.staticRender || apiOptions.readOnly,
25061
+ "aria-label": "Select Choice ".concat(getChoiceLetter(pos)),
25062
+ "aria-checked": checked ? "true" : "false",
25063
+ role: "checkbox",
25064
+ style: {
25065
+ flex: 1
25066
+ },
25067
+ ref: forwardedRef
25068
+ }, _ref => {
25069
+ var _apiOptions$styling;
25083
25070
 
25084
- render() {
25085
- var sat = this.props.apiOptions.satStyling;
25086
25071
  var {
25087
- reviewMode,
25088
- correct,
25089
- checked
25090
- } = this.props; // HACK: while most of the styling for rendering SAT items is handled
25091
- // via aphrodite, we also need to assign normal CSS classnames here to
25092
- // special-case the coloring of MathJax formulas (see .MathJax .math in
25093
- // stylesheets/task-package/tasks.less)
25094
-
25095
- var satCorrectChoice = sat && reviewMode && correct;
25096
- var satIncorrectChecked = sat && reviewMode && !correct && checked;
25097
- var descriptionClassName = classNames("description", satCorrectChoice && "sat-correct", satIncorrectChecked && "sat-incorrect", css(!sat && styles$r.description, sat && styles$r.satDescription, satCorrectChoice && styles$r.satDescriptionCorrect, satCorrectChoice && checked && styles$r.satDescriptionCorrectChecked, satIncorrectChecked && styles$r.satDescriptionIncorrectChecked));
25098
- var rationaleClassName = classNames("perseus-radio-rationale-content", 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
25099
- // disabled. However, we don't want to do this in the SAT product and
25100
- // we also don't want to do this when we're in review mode in the
25101
- // content library.
25102
-
25103
- var showDimmed = !sat && !reviewMode && this.props.apiOptions.readOnly || this.props.crossedOut;
25072
+ hovered,
25073
+ focused,
25074
+ pressed
25075
+ } = _ref;
25104
25076
  return /*#__PURE__*/React.createElement("div", {
25105
- style: {
25106
- dispay: "flex",
25107
- flexDirection: "column"
25108
- }
25109
- }, /*#__PURE__*/React.createElement("div", {
25110
25077
  style: {
25111
25078
  display: "flex",
25112
25079
  flexDirection: "row",
25113
- width: "100%",
25114
- opacity: showDimmed ? 0.5 : 1.0
25080
+ justifyContent: "center",
25081
+ alignContent: "center",
25082
+ paddingTop: Spacing.xSmall_8,
25083
+ paddingBottom: Spacing.xSmall_8
25115
25084
  }
25116
- }, /*#__PURE__*/React.createElement(Clickable, {
25117
- onClick: () => {
25118
- // If we're checking a crossed-out option, let's
25119
- // also uncross it.
25120
- this._sendChange({
25121
- checked: true,
25122
- crossedOut: false
25123
- });
25124
- },
25125
- className: descriptionClassName,
25126
- disabled: this.props.disabled || this.props.apiOptions.staticRender || this.props.apiOptions.readOnly,
25127
- "aria-label": "Select Choice ".concat(getChoiceLetter(this.props.pos)),
25128
- "aria-checked": this.props.checked,
25129
- role: "checkbox",
25085
+ }, /*#__PURE__*/React.createElement(ChoiceIcon, {
25086
+ pos: pos,
25087
+ correct: correct,
25088
+ crossedOut: crossedOut,
25089
+ pressed: pressed,
25090
+ focused: focused,
25091
+ checked: checked,
25092
+ hovered: hovered,
25093
+ showCorrectness: showCorrectness,
25094
+ multipleSelect: multipleSelect,
25095
+ reviewMode: reviewMode,
25096
+ product: apiOptions.satStyling ? "sat" : "library",
25097
+ primaryProductColor: (_apiOptions$styling = apiOptions.styling) === null || _apiOptions$styling === void 0 ? void 0 : _apiOptions$styling.primaryProductColor,
25098
+ previouslyAnswered: previouslyAnswered
25099
+ }), /*#__PURE__*/React.createElement("span", {
25130
25100
  style: {
25131
- flex: 1
25101
+ paddingLeft: Spacing.small_12,
25102
+ textAlign: "left",
25103
+ flex: 1,
25104
+ paddingTop: 4
25132
25105
  }
25133
- }, _ref2 => {
25106
+ }, /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement(OptionStatus, {
25107
+ checked: checked,
25108
+ correct: correct,
25109
+ crossedOut: crossedOut,
25110
+ previouslyAnswered: previouslyAnswered,
25111
+ reviewMode: reviewMode,
25112
+ satStyling: apiOptions.satStyling
25113
+ })), /*#__PURE__*/React.createElement("div", null, content)));
25114
+ }), apiOptions.crossOutEnabled && !reviewMode && /*#__PURE__*/React.createElement(Popover, {
25115
+ dismissEnabled: true,
25116
+ content: _ref2 => {
25134
25117
  var {
25135
- hovered,
25136
- focused,
25137
- pressed
25118
+ close
25138
25119
  } = _ref2;
25139
- return /*#__PURE__*/React.createElement("div", {
25140
- style: {
25141
- display: "flex",
25142
- flexDirection: "row",
25143
- justifyContent: "center",
25144
- alignContent: "center",
25145
- paddingTop: Spacing.xSmall_8,
25146
- paddingBottom: Spacing.xSmall_8
25147
- }
25148
- }, /*#__PURE__*/React.createElement("span", null, this.renderChoiceIcon({
25149
- isFocused: focused,
25150
- isPressed: pressed
25151
- })), /*#__PURE__*/React.createElement("span", {
25152
- style: {
25153
- paddingLeft: Spacing.small_12,
25154
- textAlign: "left",
25155
- flex: 1,
25156
- paddingTop: 4
25157
- }
25158
- }, /*#__PURE__*/React.createElement("div", null, this.renderOptionStatus()), /*#__PURE__*/React.createElement("div", null, this.props.content)));
25159
- }), this.props.apiOptions.crossOutEnabled && !reviewMode && /*#__PURE__*/React.createElement(Popover, {
25160
- dismissEnabled: true,
25161
- content: _ref3 => {
25162
- var {
25163
- close
25164
- } = _ref3;
25165
- return /*#__PURE__*/React.createElement(PopoverContent, {
25166
- title: "Cross out",
25167
- content: "Cross out option",
25168
- closeButtonVisible: true,
25169
- actions: /*#__PURE__*/React.createElement(View, null, /*#__PURE__*/React.createElement(Strut, {
25170
- size: Spacing.medium_16
25171
- }), /*#__PURE__*/React.createElement(Button, {
25172
- kind: "primary",
25173
- "aria-label": "Cross out Choice ".concat(getChoiceLetter(this.props.pos)),
25174
- disabled: this.props.apiOptions.readOnly || reviewMode,
25175
- onClick: () => {
25176
- if (!this.props.crossedOut) {
25177
- // If we're crossing
25178
- // out a checked
25179
- // option, let's also
25180
- // uncheck it.
25181
- this._sendChange({
25182
- checked: false,
25183
- crossedOut: true
25184
- });
25185
- } else {
25186
- this._sendChange({
25187
- crossedOut: false
25188
- });
25189
- }
25190
-
25191
- close();
25120
+ return /*#__PURE__*/React.createElement(PopoverContent, {
25121
+ title: "Cross out",
25122
+ content: "Cross out option",
25123
+ closeButtonVisible: true,
25124
+ actions: /*#__PURE__*/React.createElement(View, null, /*#__PURE__*/React.createElement(Strut, {
25125
+ size: Spacing.medium_16
25126
+ }), /*#__PURE__*/React.createElement(Button, {
25127
+ kind: "primary",
25128
+ "aria-label": "Cross out Choice ".concat(getChoiceLetter(pos)),
25129
+ disabled: apiOptions.readOnly || reviewMode,
25130
+ onClick: () => {
25131
+ if (!crossedOut) {
25132
+ // If we're crossing
25133
+ // out a checked
25134
+ // option, let's also
25135
+ // uncheck it.
25136
+ sendChange({
25137
+ checked: false,
25138
+ crossedOut: true
25139
+ });
25140
+ } else {
25141
+ sendChange({
25142
+ crossedOut: false
25143
+ });
25192
25144
  }
25193
- }, this.props.crossedOut ? "Bring back" : "Cross out"))
25194
- });
25195
- }
25145
+
25146
+ close();
25147
+ }
25148
+ }, crossedOut ? "Bring back" : "Cross out"))
25149
+ });
25150
+ }
25151
+ }, _ref3 => {
25152
+ var {
25153
+ open
25154
+ } = _ref3;
25155
+ return /*#__PURE__*/React.createElement(Clickable, {
25156
+ onClick: open,
25157
+ "aria-label": "Open menu for Choice ".concat(getChoiceLetter(pos)),
25158
+ disabled: apiOptions.staticRender
25196
25159
  }, _ref4 => {
25197
- var {
25198
- open
25199
- } = _ref4;
25200
- return /*#__PURE__*/React.createElement(Clickable, {
25201
- onClick: open,
25202
- "aria-label": "Open menu for Choice ".concat(getChoiceLetter(this.props.pos)),
25203
- disabled: this.props.apiOptions.staticRender
25204
- }, _ref5 => {
25205
- return /*#__PURE__*/React.createElement(Icon, {
25206
- icon: ellipsisHorizontalIcon,
25207
- size: 3,
25208
- color: Color.offBlack64
25209
- });
25160
+ return /*#__PURE__*/React.createElement(Icon, {
25161
+ icon: ellipsisHorizontalIcon,
25162
+ size: 3,
25163
+ color: Color.offBlack64
25210
25164
  });
25211
- })), this.props.showRationale && /*#__PURE__*/React.createElement("div", {
25212
- className: rationaleClassName,
25213
- "data-test-id": "perseus-radio-rationale-content-".concat(this.props.pos)
25214
- }, this.props.rationale));
25215
- }
25216
-
25165
+ });
25166
+ })), showRationale && /*#__PURE__*/React.createElement("div", {
25167
+ className: rationaleClassName,
25168
+ "data-test-id": "perseus-radio-rationale-content-".concat(pos)
25169
+ }, rationale));
25217
25170
  }
25218
25171
 
25219
- _defineProperty(Choice, "propTypes", {
25220
- // TODO(kevinb) use Options.propTypes from perseus-api.jsx
25221
- // This change will also require make sure that item-renderer.jsx and
25222
- // server-item-renderer.jsx have appropriate defaults for apiOptions
25223
- // because many of the properties on Options.propTypes are required.
25224
- apiOptions: PropTypes.shape({
25225
- satStyling: PropTypes.bool,
25226
- isMobile: PropTypes.bool,
25227
- styling: PropTypes.shape({
25228
- radioStyleVersion: PropTypes.oneOf(["intermediate", "final"]),
25229
- primaryProductColor: PropTypes.string
25230
- }),
25231
- readOnly: PropTypes.bool
25232
- }),
25233
- checked: PropTypes.bool,
25234
- className: PropTypes.string,
25235
- rationale: PropTypes.node,
25236
- content: PropTypes.node,
25237
- correct: PropTypes.bool,
25238
- deselectEnabled: PropTypes.bool,
25239
- disabled: PropTypes.bool,
25240
- editMode: PropTypes.bool,
25241
- groupName: PropTypes.string,
25242
- isLastChoice: PropTypes.bool,
25243
- // Needed for border styling
25244
- // This indicates the position of the choice relative to others
25245
- // (so that we can display a nice little (A), (B), etc. next to it)
25246
- // Also used to generate an id for each input.
25247
- pos: PropTypes.number,
25248
- reviewMode: PropTypes.bool,
25249
- showRationale: PropTypes.bool,
25250
- showCorrectness: PropTypes.bool,
25251
- type: PropTypes.string,
25252
- // Indicates whether the user has "crossed out" this choice, meaning
25253
- // that they don't think it's correct. This value does not affect
25254
- // scoring or other behavior; it's just a note for the user's
25255
- // reference.
25256
- crossedOut: PropTypes.bool,
25257
- // Indicates that the user has previously selected this answer. These
25258
- // answers may be rendered orange in review, rather than grey if
25259
- // incorrect.
25260
- previouslyAnswered: PropTypes.bool,
25261
- // A callback indicating that this choice has changed. Its argument is
25262
- // an object with two keys: `checked` and `crossedOut`. Each contains a
25263
- // boolean value specifying the new checked and crossed-out value of
25264
- // this choice.
25265
- onChange: PropTypes.func,
25266
- // Callbacks for when the user presses the up arrow or down arrow
25267
- // (respectively) to navigate between choices. When called, the parent
25268
- // finds the prev/next choice, focuses the input, and checks the radio
25269
- // button if appropriate. (The reason we have custom behavior is that
25270
- // we actually _don't_ want to auto-select crossed-out choices!)
25271
- goToPrevChoice: PropTypes.func.isRequired,
25272
- goToNextChoice: PropTypes.func.isRequired
25273
- });
25274
-
25275
- _defineProperty(Choice, "defaultProps", {
25276
- apiOptions: {
25277
- styling: {}
25278
- },
25172
+ Choice.defaultProps = {
25173
+ disabled: false,
25279
25174
  checked: false,
25280
- classSet: {},
25175
+ onChange: newValues => {},
25281
25176
  correct: false,
25282
- disabled: false,
25283
- editMode: false,
25284
- onChange: function onChange() {},
25285
- showRationale: false,
25286
- type: "radio",
25177
+ apiOptions: ApiOptions.defaults,
25178
+ previouslyAnswered: false,
25287
25179
  pos: 0,
25288
- previouslyAnswered: false
25289
- });
25290
-
25291
- var styles$r = StyleSheet.create({
25180
+ showRationale: false
25181
+ };
25182
+ var styles$s = StyleSheet.create({
25292
25183
  description: {
25293
25184
  display: "inline-block",
25294
25185
  width: "100%"
@@ -25328,185 +25219,87 @@ var styles$r = StyleSheet.create({
25328
25219
  marginLeft: 45
25329
25220
  }
25330
25221
  });
25222
+ var Choice$1 = /*#__PURE__*/React.forwardRef((props, ref) => /*#__PURE__*/React.createElement(Choice, _extends({}, props, {
25223
+ forwardedRef: ref
25224
+ })));
25331
25225
 
25332
- class ChoiceNoneAbove extends React.Component {
25333
- constructor() {
25334
- super(...arguments);
25226
+ var _excluded$2 = ["showContent", "content", "forwardedRef"];
25335
25227
 
25336
- _defineProperty(this, "_choice", void 0);
25337
-
25338
- _defineProperty(this, "focusInput", () => {
25339
- this._choice.focusInput();
25340
- });
25341
- }
25342
-
25343
- render() {
25344
- var choiceProps = _objectSpread2(_objectSpread2({}, this.props), {}, {
25345
- className: classNames(this.props.className, "none-of-above"),
25346
- content: this.props.showContent ? this.props.content :
25347
- /*#__PURE__*/
25348
- // We use a Renderer here because that is how
25349
- // `this.props.content` is wrapped otherwise.
25350
- // We pass in a key here so that we avoid a semi-spurious
25351
- // react warning when we render this in the same place
25352
- // as the previous choice content renderer.
25353
- // Note this destroys state, but since all we're doing
25354
- // is outputting "None of the above", that is okay.
25355
- React.createElement(Renderer, {
25356
- key: "noneOfTheAboveRenderer",
25357
- content: i18n._("None of the above")
25358
- }),
25359
- ref: el => this._choice = el
25360
- });
25361
-
25362
- return /*#__PURE__*/React.createElement(Choice, choiceProps);
25363
- }
25228
+ function ChoiceNoneAbove(props) {
25229
+ var {
25230
+ showContent,
25231
+ content,
25232
+ forwardedRef
25233
+ } = props,
25234
+ rest = _objectWithoutProperties(props, _excluded$2);
25235
+
25236
+ var choiceProps = _objectSpread2(_objectSpread2({}, rest), {}, {
25237
+ content: showContent ? content :
25238
+ /*#__PURE__*/
25239
+ // We use a Renderer here because that is how
25240
+ // `this.props.content` is wrapped otherwise.
25241
+ // We pass in a key here so that we avoid a semi-spurious
25242
+ // react warning when we render this in the same place
25243
+ // as the previous choice content renderer.
25244
+ // Note this destroys state, but since all we're doing
25245
+ // is outputting "None of the above", that is okay.
25246
+ //
25247
+ // todo(matthewc): this seems like way overkill
25248
+ // just to render a string
25249
+ React.createElement(Renderer, {
25250
+ key: "noneOfTheAboveRenderer",
25251
+ content: i18n._("None of the above")
25252
+ })
25253
+ });
25364
25254
 
25255
+ return /*#__PURE__*/React.createElement(Choice$1, _extends({}, choiceProps, {
25256
+ ref: forwardedRef
25257
+ }));
25365
25258
  }
25366
25259
 
25367
- _defineProperty(ChoiceNoneAbove, "propTypes", {
25368
- className: PropTypes.string,
25369
- content: PropTypes.node,
25370
- showContent: PropTypes.bool
25371
- });
25372
-
25373
- _defineProperty(ChoiceNoneAbove, "defaultProps", {
25260
+ ChoiceNoneAbove.defaultProps = {
25374
25261
  showContent: true
25375
- });
25262
+ };
25263
+ var ChoiceNoneAbove$1 = /*#__PURE__*/React.forwardRef((props, ref) => /*#__PURE__*/React.createElement(ChoiceNoneAbove, _extends({}, props, {
25264
+ forwardedRef: ref
25265
+ })));
25376
25266
 
25377
25267
  var {
25378
25268
  captureScratchpadTouchStart: captureScratchpadTouchStart$2
25379
- } = Util;
25380
- var ChoicesType = PropTypes.arrayOf(PropTypes.shape({
25381
- // Indicates whether this choice is checked.
25382
- checked: PropTypes.bool,
25383
- // Indicates whether the user has "crossed out" this choice, meaning
25384
- // that they don't think it's correct. This value does not affect
25385
- // scoring or other behavior; it's just a note for the user's
25386
- // reference.
25387
- crossedOut: PropTypes.bool,
25388
- content: PropTypes.node,
25389
- rationale: PropTypes.node,
25390
- hasRationale: PropTypes.bool,
25391
- showRationale: PropTypes.bool,
25392
- showCorrectness: PropTypes.bool,
25393
- correct: PropTypes.bool,
25394
- originalIndex: PropTypes.number,
25395
- isNoneOfTheAbove: PropTypes.bool
25396
- }));
25397
- var radioBorderColor = radioBorderColor$2;
25398
-
25399
- class BaseRadio extends React.Component {
25400
- constructor() {
25401
- super(...arguments);
25402
-
25403
- _defineProperty(this, "state", {
25404
- // TODO(mdr): This keeps the ID stable across re-renders on the
25405
- // same machine, but, at time of writing, the server's state
25406
- // isn't rehydrated to the client during SSR, so the server and
25407
- // client will generate different IDs and cause a mismatch
25408
- // during SSR :(
25409
- radioGroupName: _.uniqueId("perseus_radio_")
25410
- });
25411
-
25412
- _defineProperty(this, "updateChoice", (choiceIndex, newValues) => {
25413
- // Get the baseline `checked` values. If we're checking a new answer
25414
- // and multiple-select is not on, we should clear all choices to be
25415
- // unchecked. Otherwise, we should copy the old checked values.
25416
- var newCheckedList;
25417
-
25418
- if (newValues.checked && !this.props.multipleSelect) {
25419
- newCheckedList = this.props.choices.map(_ => false);
25420
- } else {
25421
- newCheckedList = this.props.choices.map(c => c.checked);
25422
- } // Get the baseline `crossedOut` values.
25269
+ } = Util; // exported for tests
25423
25270
 
25424
-
25425
- var newCrossedOutList = this.props.choices.map(c => c.crossedOut); // Update this choice's `checked` and `crossedOut` values.
25426
-
25427
- newCheckedList[choiceIndex] = newValues.checked;
25428
- newCrossedOutList[choiceIndex] = newValues.crossedOut;
25429
- this.props.onChange({
25430
- checked: newCheckedList,
25431
- crossedOut: newCrossedOutList
25271
+ function getInstructionsText(multipleSelect, countChoices, numCorrect) {
25272
+ if (multipleSelect) {
25273
+ if (countChoices) {
25274
+ return i18n._("Choose %(numCorrect)s answers:", {
25275
+ numCorrect: numCorrect
25432
25276
  });
25433
- });
25434
-
25435
- _defineProperty(this, "focus", i => {
25436
- // $FlowFixMe[incompatible-use]
25437
- // $FlowFixMe[prop-missing]
25438
- ReactDOM__default.findDOMNode(this.refs["radio" + (i || 0)]).focus(); // eslint-disable-line react/no-string-refs
25439
-
25440
- return true;
25441
- });
25442
-
25443
- _defineProperty(this, "getInstructionsText", () => {
25444
- if (this.props.multipleSelect) {
25445
- if (this.props.countChoices) {
25446
- return i18n._("Choose %(numCorrect)s answers:", {
25447
- numCorrect: this.props.numCorrect
25448
- });
25449
- }
25450
-
25451
- return i18n._("Choose all answers that apply:");
25452
- }
25453
-
25454
- return i18n._("Choose 1 answer:");
25455
- });
25456
-
25457
- _defineProperty(this, "deselectEnabled", () => {
25458
- // We want to force enable deselect on mobile.
25459
- return this.props.apiOptions.isMobile || this.props.deselectEnabled;
25460
- });
25461
-
25462
- _defineProperty(this, "goToChoice", newChoiceIndex => {
25463
- var numChoices = this.props.choices.length; // Wrap the newChoiceIndex around the start/end, if necessary.
25464
-
25465
- if (newChoiceIndex < 0) {
25466
- newChoiceIndex += numChoices;
25467
- }
25468
-
25469
- if (newChoiceIndex >= numChoices) {
25470
- newChoiceIndex -= numChoices;
25471
- } // Focus the new choice's input element.
25472
- // eslint-disable-next-line react/no-string-refs
25473
-
25474
-
25475
- var choiceInstance = this.refs["radio".concat(newChoiceIndex)];
25476
-
25477
- if (!choiceInstance) {
25478
- Log.error("found no choice at index ".concat(newChoiceIndex, ", even after ") + "wrapping it to be within bounds", Errors.Internal, {
25479
- loggedMetadata: {
25480
- choices: JSON.stringify(this.props.choices)
25481
- }
25482
- });
25483
- return;
25484
- }
25485
-
25486
- choiceInstance.focusInput(); // We aren't going to change any choices' crossed-out state, but we
25487
- // _are_ going to deselect all choices.
25488
-
25489
- var newCrossedOutList = this.props.choices.map(c => c.crossedOut);
25490
- var newCheckedList = this.props.choices.map(c => false); // And, if the new choice isn't crossed out, we'll select it.
25491
-
25492
- if (!this.props.choices[newChoiceIndex].crossedOut) {
25493
- newCheckedList[newChoiceIndex] = true;
25494
- }
25277
+ }
25495
25278
 
25496
- this.props.onChange({
25497
- checked: newCheckedList,
25498
- crossedOut: newCrossedOutList
25499
- });
25500
- });
25279
+ return i18n._("Choose all answers that apply:");
25501
25280
  }
25502
25281
 
25503
- componentDidUpdate(prevProps) {
25504
- var {
25505
- apiOptions,
25506
- choices,
25507
- isLastUsedWidget,
25508
- reviewModeRubric
25509
- } = this.props; // Switching into review mode can sometimes cause the selected answer
25282
+ return i18n._("Choose 1 answer:");
25283
+ }
25284
+
25285
+ function BaseRadio(props) {
25286
+ var {
25287
+ apiOptions,
25288
+ reviewModeRubric,
25289
+ choices,
25290
+ editMode,
25291
+ multipleSelect,
25292
+ labelWrap,
25293
+ countChoices,
25294
+ numCorrect,
25295
+ isLastUsedWidget,
25296
+ registerFocusFunction
25297
+ } = props; // useEffect doesn't have previous props
25298
+
25299
+ var prevReviewModeRubric = useRef();
25300
+ var choiceRefs = useRef([]);
25301
+ useEffect(() => {
25302
+ // Switching into review mode can sometimes cause the selected answer
25510
25303
  // to scroll out of view - for example, when we reveal all those tall
25511
25304
  // rationales. This can be disorienting for the user.
25512
25305
  //
@@ -25519,17 +25312,25 @@ class BaseRadio extends React.Component {
25519
25312
  // the most recently used widget (because, if the user has since
25520
25313
  // touched something else, they're probably not trying to keep their
25521
25314
  // eye on this widget anymore).
25522
-
25523
- if (apiOptions.canScrollPage && isLastUsedWidget && reviewModeRubric && !prevProps.reviewModeRubric) {
25315
+ if (apiOptions.canScrollPage && isLastUsedWidget && reviewModeRubric && !prevReviewModeRubric.current) {
25524
25316
  var checkedIndex = choices.findIndex(c => c.checked);
25525
25317
 
25526
25318
  if (checkedIndex >= 0) {
25527
- var checkedNode = ReactDOM__default.findDOMNode( // eslint-disable-next-line react/no-string-refs
25528
- this.refs["radio" + checkedIndex]);
25529
- scrollElementIntoView(checkedNode);
25319
+ var ref = choiceRefs.current[checkedIndex]; // note(matthew): we know this is only getting passed
25320
+ // to a WB Clickable button, so we force it to be of
25321
+ // type HTMLButtonElement
25322
+
25323
+ var anyNode = ReactDOM__default.findDOMNode(ref.current);
25324
+ var buttonNode = anyNode;
25325
+
25326
+ if (buttonNode) {
25327
+ scrollElementIntoView(buttonNode);
25328
+ }
25530
25329
  }
25531
25330
  }
25532
- } // When a particular choice's `onChange` handler is called, indicating a
25331
+
25332
+ prevReviewModeRubric.current = reviewModeRubric;
25333
+ }, [apiOptions, choices, isLastUsedWidget, reviewModeRubric]); // When a particular choice's `onChange` handler is called, indicating a
25533
25334
  // change in a single choice's values, we need to call our `onChange`
25534
25335
  // handler in order to notify our parent. However, our API with our parent
25535
25336
  // is that we always provide *all* values for *all* choices, even if just
@@ -25538,181 +25339,196 @@ class BaseRadio extends React.Component {
25538
25339
  // will usually cause the old answer to become unchecked.)
25539
25340
  //
25540
25341
  // So, given the new values for a particular choice, compute the new values
25541
- // for all choices, and pass them to `this.props.onChange`.
25342
+ // for all choices, and pass them to `onChange`.
25542
25343
  //
25543
25344
  // `newValues` is an object with two keys: `checked` and `crossedOut`. Each
25544
25345
  // contains a boolean value specifying the new checked and crossed-out
25545
25346
  // value of this choice.
25546
25347
 
25348
+ function updateChoice(choiceIndex, newValues) {
25349
+ var {
25350
+ multipleSelect,
25351
+ choices,
25352
+ onChange
25353
+ } = props; // Get the baseline `checked` values. If we're checking a new answer
25354
+ // and multiple-select is not on, we should clear all choices to be
25355
+ // unchecked. Otherwise, we should copy the old checked values.
25547
25356
 
25548
- render() {
25549
- var inputType = this.props.multipleSelect ? "checkbox" : "radio";
25550
- var rubric = this.props.reviewModeRubric;
25551
- var reviewMode = !!rubric;
25552
- var styles = BaseRadio.styles;
25553
- var sat = this.props.apiOptions.satStyling;
25554
- var isMobile = this.props.apiOptions.isMobile;
25555
- var choices = this.props.choices;
25556
- var firstChoiceHighlighted = choices[0].highlighted;
25557
- var lastChoiceHighlighted = choices[choices.length - 1].highlighted;
25558
- var className = classNames("perseus-widget-radio", !this.props.editMode && "perseus-rendered-radio", css(styles.radio, // SAT doesn't use the "responsive styling" as it conflicts
25559
- // with their custom theming.
25560
- !sat && styles.responsiveRadioContainer, !sat && firstChoiceHighlighted && isMobile && styles.radioContainerFirstHighlighted, !sat && lastChoiceHighlighted && isMobile && styles.radioContainerLastHighlighted, sat && styles.satRadio));
25561
- var instructionsClassName = classNames("instructions", css(styles.instructions, isMobile && styles.instructionsMobile));
25562
- var instructions = this.getInstructionsText();
25563
- var shouldShowInstructions = !sat;
25564
- var responsiveClassName = css(styles.responsiveFieldset);
25565
- var fieldset = /*#__PURE__*/React.createElement("fieldset", {
25566
- className: "perseus-widget-radio-fieldset ".concat(responsiveClassName)
25567
- }, /*#__PURE__*/React.createElement("legend", {
25568
- className: "perseus-sr-only"
25569
- }, instructions), shouldShowInstructions && /*#__PURE__*/React.createElement("div", {
25570
- className: instructionsClassName
25571
- }, instructions), /*#__PURE__*/React.createElement("ul", {
25572
- className: className,
25573
- style: {
25574
- listStyle: "none"
25575
- }
25576
- }, this.props.choices.map(function (choice, i) {
25577
- var Element = Choice;
25578
- var elementProps = {
25579
- ref: "radio".concat(i),
25580
- apiOptions: this.props.apiOptions,
25581
- checked: choice.checked,
25582
- crossedOut: choice.crossedOut,
25583
- previouslyAnswered: choice.previouslyAnswered,
25584
- reviewMode,
25585
- correct: choice.correct,
25586
- rationale: choice.rationale,
25587
- content: choice.content,
25588
- disabled: this.props.apiOptions.readOnly || choice.disabled,
25589
- editMode: this.props.editMode,
25590
- groupName: this.state.radioGroupName,
25591
- isLastChoice: i === this.props.choices.length - 1,
25592
- showCorrectness: reviewMode || !!choice.showCorrectness,
25593
- showRationale: choice.hasRationale && (reviewMode || choice.showRationale),
25594
- type: inputType,
25595
- pos: i,
25596
- deselectEnabled: this.deselectEnabled(),
25597
- onChange: newValues => {
25598
- this.updateChoice(i, newValues);
25599
- },
25600
- goToPrevChoice: () => this.goToChoice(i - 1),
25601
- goToNextChoice: () => this.goToChoice(i + 1)
25602
- };
25357
+ var newCheckedList;
25603
25358
 
25604
- if (choice.isNoneOfTheAbove) {
25605
- Element = ChoiceNoneAbove;
25359
+ if (newValues.checked && !multipleSelect) {
25360
+ newCheckedList = choices.map(_ => false);
25361
+ } else {
25362
+ newCheckedList = choices.map(c => c.checked);
25363
+ } // Get the baseline `crossedOut` values.
25606
25364
 
25607
- _.extend(elementProps, {
25608
- showContent: choice.revealNoneOfTheAbove
25609
- });
25610
- }
25611
25365
 
25612
- var nextChoice = this.props.choices[i + 1];
25613
- var nextChoiceHighlighted = !!nextChoice && nextChoice.highlighted;
25614
-
25615
- var aphroditeClassName = checked => {
25616
- // Whether or not to show correctness borders
25617
- // for this choice and the next choice.
25618
- var satShowCorrectness = sat && reviewMode && checked;
25619
- var satShowCorrectnessNext = sat && reviewMode && nextChoice && nextChoice.checked;
25620
- return css(sharedStyles.aboveScratchpad, styles.item, !sat && styles.responsiveItem, !sat && checked && styles.selectedItem, !sat && checked && choice.highlighted && styles.aboveBackdrop, !sat && checked && choice.highlighted && this.props.apiOptions.isMobile && styles.aboveBackdropMobile, !sat && nextChoiceHighlighted && this.props.apiOptions.isMobile && styles.nextHighlighted, sat && styles.satRadioOption, satShowCorrectness && !choice.correct && styles.satRadioOptionIncorrect, satShowCorrectness && choice.correct && styles.satRadioOptionCorrect, satShowCorrectnessNext && !nextChoice.correct && styles.satRadioOptionNextIncorrect, satShowCorrectnessNext && nextChoice.correct && styles.satRadioOptionNextCorrect, sat && rubric && styles.satReviewRadioOption);
25621
- }; // HACK(abdulrahman): Preloads the selection-state
25622
- // css because of a bug that causes iOS to lag
25623
- // when selecting the button for the first time.
25624
-
25625
-
25626
- aphroditeClassName(true);
25627
- var className = classNames(aphroditeClassName(choice.checked), // TODO(aria): Make test case for these API
25628
- // classNames
25629
- ClassNames.RADIO.OPTION, choice.checked && ClassNames.RADIO.SELECTED, reviewMode && rubric.choices[i].correct && ClassNames.CORRECT, reviewMode && !rubric.choices[i].correct && ClassNames.INCORRECT); // In edit mode, the Choice renders a Div in order to
25630
- // allow for the contentEditable area to be selected
25631
- // (label forces any clicks inside to select the input
25632
- // element) We have to add some extra behavior to make
25633
- // sure that we can still check the choice.
25634
-
25635
- var listElem = null;
25636
- var clickHandler = null;
25637
-
25638
- if (this.props.editMode) {
25639
- clickHandler = e => {
25640
- // Traverse the parent nodes of the clicked
25641
- // element.
25642
- var elem = e.target;
25643
-
25644
- while (elem && elem !== listElem) {
25645
- // If the clicked element is inside of the
25646
- // radio icon, then we want to trigger the
25647
- // check by flipping the choice of the icon.
25648
- if (elem.getAttribute("data-is-radio-icon")) {
25649
- this.updateChoice(i, {
25650
- checked: !choice.checked
25651
- });
25652
- return;
25653
- }
25366
+ var newCrossedOutList = choices.map(c => c.crossedOut); // Update this choice's `checked` and `crossedOut` values.
25367
+
25368
+ newCheckedList[choiceIndex] = newValues.checked;
25369
+ newCrossedOutList[choiceIndex] = newValues.crossedOut;
25370
+ onChange({
25371
+ checked: newCheckedList,
25372
+ crossedOut: newCrossedOutList
25373
+ });
25374
+ } // register a callback with the parent that allows
25375
+ // the parent to focus an individual choice
25376
+
25377
+
25378
+ registerFocusFunction === null || registerFocusFunction === void 0 ? void 0 : registerFocusFunction(choiceIndex => {
25379
+ var ref = choiceRefs.current[choiceIndex || 0]; // note(matthew): we know this is only getting passed
25380
+ // to a WB Clickable button, so we force it to be of
25381
+ // type HTMLButtonElement
25382
+
25383
+ var anyNode = ReactDOM__default.findDOMNode(ref.current);
25384
+ var buttonNode = anyNode;
25385
+
25386
+ if (buttonNode) {
25387
+ buttonNode.focus();
25388
+ } else {
25389
+ return false;
25390
+ }
25391
+
25392
+ return true;
25393
+ }); // some commonly used shorthands
25394
+
25395
+ var reviewMode = !!reviewModeRubric;
25396
+ var sat = apiOptions.satStyling;
25397
+ var isMobile = apiOptions.isMobile;
25398
+ var firstChoiceHighlighted = choices[0].highlighted;
25399
+ var lastChoiceHighlighted = choices[choices.length - 1].highlighted;
25400
+ var className = classNames("perseus-widget-radio", !editMode && "perseus-rendered-radio", css(styles$r.radio, // SAT doesn't use the "responsive styling" as it conflicts
25401
+ // with their custom theming.
25402
+ !sat && styles$r.responsiveRadioContainer, !sat && firstChoiceHighlighted && isMobile && styles$r.radioContainerFirstHighlighted, !sat && lastChoiceHighlighted && isMobile && styles$r.radioContainerLastHighlighted, sat && styles$r.satRadio));
25403
+ var instructionsClassName = classNames("instructions", css(styles$r.instructions, isMobile && styles$r.instructionsMobile));
25404
+ var instructions = getInstructionsText(multipleSelect, countChoices, numCorrect);
25405
+ var shouldShowInstructions = !sat;
25406
+ var responsiveClassName = css(styles$r.responsiveFieldset);
25407
+ var fieldset = /*#__PURE__*/React.createElement("fieldset", {
25408
+ className: "perseus-widget-radio-fieldset ".concat(responsiveClassName)
25409
+ }, /*#__PURE__*/React.createElement("legend", {
25410
+ className: "perseus-sr-only"
25411
+ }, instructions), shouldShowInstructions && /*#__PURE__*/React.createElement("div", {
25412
+ className: instructionsClassName
25413
+ }, instructions), /*#__PURE__*/React.createElement("ul", {
25414
+ className: className,
25415
+ style: {
25416
+ listStyle: "none"
25417
+ }
25418
+ }, choices.map(function (choice, i) {
25419
+ var Element = Choice$1;
25420
+ var ref = /*#__PURE__*/React.createRef();
25421
+ choiceRefs.current[i] = ref;
25422
+ var elementProps = {
25423
+ apiOptions: apiOptions,
25424
+ multipleSelect: multipleSelect,
25425
+ checked: choice.checked,
25426
+ crossedOut: choice.crossedOut,
25427
+ previouslyAnswered: choice.previouslyAnswered,
25428
+ reviewMode,
25429
+ correct: choice.correct,
25430
+ rationale: choice.rationale,
25431
+ content: choice.content,
25432
+ disabled: apiOptions.readOnly || choice.disabled,
25433
+ showCorrectness: reviewMode || !!choice.showCorrectness,
25434
+ showRationale: choice.hasRationale && (reviewMode || choice.showRationale),
25435
+ pos: i,
25436
+ onChange: newValues => {
25437
+ updateChoice(i, newValues);
25438
+ },
25439
+ ref
25440
+ };
25441
+
25442
+ if (choice.isNoneOfTheAbove) {
25443
+ Element = ChoiceNoneAbove$1;
25444
+
25445
+ _.extend(elementProps, {
25446
+ showContent: choice.revealNoneOfTheAbove
25447
+ });
25448
+ }
25449
+
25450
+ var nextChoice = choices[i + 1];
25451
+ var nextChoiceHighlighted = !!nextChoice && nextChoice.highlighted;
25452
+
25453
+ var aphroditeClassName = checked => {
25454
+ // Whether or not to show correctness borders
25455
+ // for this choice and the next choice.
25456
+ var satShowCorrectness = sat && reviewMode && checked;
25457
+ var satShowCorrectnessNext = sat && reviewMode && nextChoice && nextChoice.checked;
25458
+ return 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);
25459
+ }; // HACK(abdulrahman): Preloads the selection-state
25460
+ // css because of a bug that causes iOS to lag
25461
+ // when selecting the button for the first time.
25654
25462
 
25655
- elem = elem.parentNode;
25463
+
25464
+ aphroditeClassName(true);
25465
+ var correctnessClass; // reviewMode is only true if there's a rubric
25466
+ // but Flow doesn't understand that
25467
+
25468
+ if (reviewMode && reviewModeRubric) {
25469
+ correctnessClass = reviewModeRubric.choices[i].correct ? ClassNames.CORRECT : ClassNames.INCORRECT;
25470
+ }
25471
+
25472
+ var className = classNames(aphroditeClassName(choice.checked), // TODO(aria): Make test case for these API
25473
+ // classNames
25474
+ ClassNames.RADIO.OPTION, choice.checked && ClassNames.RADIO.SELECTED, correctnessClass); // In edit mode, the Choice renders a Div in order to
25475
+ // allow for the contentEditable area to be selected
25476
+ // (label forces any clicks inside to select the input
25477
+ // element) We have to add some extra behavior to make
25478
+ // sure that we can still check the choice.
25479
+
25480
+ var listElem = null;
25481
+ var clickHandler = null;
25482
+
25483
+ if (editMode) {
25484
+ clickHandler = e => {
25485
+ // Traverse the parent nodes of the clicked
25486
+ // element.
25487
+ var elem = e.target;
25488
+
25489
+ while (elem && elem !== listElem) {
25490
+ // If the clicked element is inside of the
25491
+ // radio icon, then we want to trigger the
25492
+ // check by flipping the choice of the icon.
25493
+ if (elem.getAttribute("data-is-radio-icon")) {
25494
+ this.updateChoice(i, {
25495
+ checked: !choice.checked,
25496
+ crossedOut: choice.crossedOut
25497
+ });
25498
+ return;
25656
25499
  }
25657
- };
25658
- } // TODO(mattdr): Index isn't a *good* choice of key
25659
- // here; is there a better one? Can we use choice
25660
- // content somehow? Would changing our choice of key
25661
- // somehow break something happening inside a choice's
25662
- // child Renderers, by changing when we mount/unmount?
25663
25500
 
25501
+ elem = elem.parentNode;
25502
+ }
25503
+ };
25504
+ } // TODO(mattdr): Index isn't a *good* choice of key
25505
+ // here; is there a better one? Can we use choice
25506
+ // content somehow? Would changing our choice of key
25507
+ // somehow break something happening inside a choice's
25508
+ // child Renderers, by changing when we mount/unmount?
25664
25509
 
25665
- return /*#__PURE__*/React.createElement("li", {
25666
- key: i,
25667
- ref: e => listElem = e,
25668
- className: className,
25669
- onClick: clickHandler,
25670
- onTouchStart: !this.props.labelWrap ? null : captureScratchpadTouchStart$2
25671
- }, /*#__PURE__*/React.createElement(Element, elementProps));
25672
- }, this))); // Allow for horizontal scrolling if content is too wide, which may be
25673
- // an issue especially on phones.
25674
- // This is disabled in SAT, since it conflicts with their theming.
25675
25510
 
25676
- return /*#__PURE__*/React.createElement("div", {
25677
- className: css(!sat && styles.responsiveContainer)
25678
- }, fieldset);
25679
- }
25511
+ return /*#__PURE__*/React.createElement("li", {
25512
+ key: i,
25513
+ ref: e => listElem = e,
25514
+ className: className,
25515
+ onClick: clickHandler,
25516
+ onTouchStart: !labelWrap ? null : captureScratchpadTouchStart$2
25517
+ }, /*#__PURE__*/React.createElement(Element, elementProps));
25518
+ }, this))); // Allow for horizontal scrolling if content is too wide, which may be
25519
+ // an issue especially on phones.
25520
+ // This is disabled in SAT, since it conflicts with their theming.
25680
25521
 
25522
+ return /*#__PURE__*/React.createElement("div", {
25523
+ className: css(!sat && styles$r.responsiveContainer)
25524
+ }, fieldset);
25681
25525
  }
25682
25526
 
25683
- _defineProperty(BaseRadio, "propTypes", {
25684
- apiOptions: PropTypes.shape({
25685
- readOnly: PropTypes.bool,
25686
- satStyling: PropTypes.bool,
25687
- isMobile: PropTypes.bool,
25688
- styling: PropTypes.shape({
25689
- radioStyleVersion: PropTypes.oneOf(["intermediate", "final"])
25690
- }),
25691
- canScrollPage: PropTypes.bool
25692
- }),
25693
- choices: ChoicesType,
25694
- deselectEnabled: PropTypes.bool,
25695
- editMode: PropTypes.bool,
25696
- labelWrap: PropTypes.bool,
25697
- countChoices: PropTypes.bool,
25698
- numCorrect: PropTypes.number,
25699
- multipleSelect: PropTypes.bool,
25700
- reviewModeRubric: PropTypes.shape({
25701
- choices: ChoicesType
25702
- }),
25703
- // A callback indicating that this choice has changed. Its argument is
25704
- // an object with two keys: `checked` and `crossedOut`. Each contains
25705
- // an array of boolean values, specifying the new checked and
25706
- // crossed-out value of each choice.
25707
- onChange: PropTypes.func,
25708
- // Whether this widget was the most recently used widget in this
25709
- // Renderer. Determines whether we'll auto-scroll the page upon
25710
- // entering review mode.
25711
- isLastUsedWidget: PropTypes.bool
25712
- });
25713
-
25714
- _defineProperty(BaseRadio, "styles", StyleSheet.create({
25715
- // eslint-disable-next-line react-native/no-unused-styles
25527
+ BaseRadio.defaultProps = {
25528
+ editMode: false,
25529
+ multipleSelect: false
25530
+ };
25531
+ var styles$r = StyleSheet.create({
25716
25532
  instructions: {
25717
25533
  display: "block",
25718
25534
  color: gray17,
@@ -25722,7 +25538,6 @@ _defineProperty(BaseRadio, "styles", StyleSheet.create({
25722
25538
  fontWeight: "bold",
25723
25539
  marginBottom: 16
25724
25540
  },
25725
- // eslint-disable-next-line react-native/no-unused-styles
25726
25541
  instructionsMobile: {
25727
25542
  fontSize: 18,
25728
25543
  [mediaQueries.smOrSmaller]: {
@@ -25736,36 +25551,30 @@ _defineProperty(BaseRadio, "styles", StyleSheet.create({
25736
25551
  fontSize: 20
25737
25552
  }
25738
25553
  },
25739
- // eslint-disable-next-line react-native/no-unused-styles
25740
25554
  radio: {
25741
25555
  // Avoid centering
25742
25556
  width: "100%"
25743
25557
  },
25744
- // eslint-disable-next-line react-native/no-unused-styles
25745
25558
  responsiveRadioContainer: {
25746
- borderBottom: "1px solid ".concat(radioBorderColor),
25747
- borderTop: "1px solid ".concat(radioBorderColor),
25559
+ borderBottom: "1px solid ".concat(radioBorderColor$1),
25560
+ borderTop: "1px solid ".concat(radioBorderColor$1),
25748
25561
  width: "auto",
25749
25562
  [mediaQueries.smOrSmaller]: {
25750
25563
  marginLeft: negativePhoneMargin,
25751
25564
  marginRight: negativePhoneMargin
25752
25565
  }
25753
25566
  },
25754
- // eslint-disable-next-line react-native/no-unused-styles
25755
25567
  radioContainerFirstHighlighted: {
25756
25568
  borderTop: "1px solid rgba(0, 0, 0, 0)"
25757
25569
  },
25758
- // eslint-disable-next-line react-native/no-unused-styles
25759
25570
  radioContainerLastHighlighted: {
25760
25571
  borderBottom: "1px solid rgba(0, 0, 0, 0)"
25761
25572
  },
25762
- // eslint-disable-next-line react-native/no-unused-styles
25763
25573
  satRadio: {
25764
25574
  background: "none",
25765
25575
  marginLeft: 0,
25766
25576
  userSelect: "none"
25767
25577
  },
25768
- // eslint-disable-next-line react-native/no-unused-styles
25769
25578
  satRadioOption: {
25770
25579
  margin: 0,
25771
25580
  padding: 0,
@@ -25774,57 +25583,40 @@ _defineProperty(BaseRadio, "styles", StyleSheet.create({
25774
25583
  borderTop: "1px solid #ccc"
25775
25584
  }
25776
25585
  },
25777
- // eslint-disable-next-line react-native/no-unused-styles
25778
25586
  satRadioOptionCorrect: {
25779
25587
  borderBottomColor: satCorrectBorderColor,
25780
25588
  ":first-child": {
25781
25589
  borderTopColor: satCorrectBorderColor
25782
25590
  }
25783
25591
  },
25784
- // eslint-disable-next-line react-native/no-unused-styles
25785
25592
  satRadioOptionIncorrect: {
25786
25593
  borderBottomColor: satIncorrectBorderColor,
25787
25594
  ":first-child": {
25788
25595
  borderTopColor: satIncorrectBorderColor
25789
25596
  }
25790
25597
  },
25791
- // eslint-disable-next-line react-native/no-unused-styles
25792
25598
  satRadioOptionNextCorrect: {
25793
25599
  borderBottomColor: satCorrectBorderColor
25794
25600
  },
25795
- // eslint-disable-next-line react-native/no-unused-styles
25796
25601
  satRadioOptionNextIncorrect: {
25797
25602
  borderBottomColor: satIncorrectBorderColor
25798
25603
  },
25799
- // eslint-disable-next-line react-native/no-unused-styles
25800
25604
  satReviewRadioOption: {
25801
25605
  pointerEvents: "none"
25802
25606
  },
25803
- // eslint-disable-next-line react-native/no-unused-styles
25804
25607
  item: {
25805
25608
  marginLeft: 20
25806
25609
  },
25807
- // eslint-disable-next-line react-native/no-unused-styles
25808
- inlineItem: {
25809
- display: "inline-block",
25810
- paddingLeft: 20,
25811
- verticalAlign: "middle" // See http://stackoverflow.com/q/8120466 for explanation of
25812
- // why vertical align property is needed
25813
-
25814
- },
25815
- // eslint-disable-next-line react-native/no-unused-styles
25816
25610
  responsiveItem: {
25817
25611
  marginLeft: 0,
25818
25612
  padding: 0,
25819
25613
  ":not(:last-child)": {
25820
- borderBottom: "1px solid ".concat(radioBorderColor)
25614
+ borderBottom: "1px solid ".concat(radioBorderColor$1)
25821
25615
  }
25822
25616
  },
25823
- // eslint-disable-next-line react-native/no-unused-styles
25824
25617
  selectedItem: {
25825
25618
  background: "white"
25826
25619
  },
25827
- // eslint-disable-next-line react-native/no-unused-styles
25828
25620
  aboveBackdrop: {
25829
25621
  position: "relative",
25830
25622
  // HACK(emily): We want selected choices to show up above our
@@ -25833,20 +25625,17 @@ _defineProperty(BaseRadio, "styles", StyleSheet.create({
25833
25625
  // coordinated between here and webapp. :(
25834
25626
  zIndex: 1062
25835
25627
  },
25836
- // eslint-disable-next-line react-native/no-unused-styles
25837
25628
  aboveBackdropMobile: {
25838
25629
  boxShadow: "0 0 4px 0 rgba(0, 0, 0, 0.2)," + "0 0 2px 0 rgba(0, 0, 0, 0.1)",
25839
25630
  ":not(:last-child)": {
25840
25631
  borderBottom: "1px solid rgba(0, 0, 0, 0)"
25841
25632
  }
25842
25633
  },
25843
- // eslint-disable-next-line react-native/no-unused-styles
25844
25634
  nextHighlighted: {
25845
25635
  ":not(:last-child)": {
25846
25636
  borderBottom: "1px solid rgba(0, 0, 0, 0)"
25847
25637
  }
25848
25638
  },
25849
- // eslint-disable-next-line react-native/no-unused-styles
25850
25639
  responsiveContainer: {
25851
25640
  overflow: "auto",
25852
25641
  marginLeft: negativePhoneMargin,
@@ -25854,22 +25643,22 @@ _defineProperty(BaseRadio, "styles", StyleSheet.create({
25854
25643
  paddingLeft: phoneMargin // paddingRight is handled by responsiveFieldset
25855
25644
 
25856
25645
  },
25857
- // eslint-disable-next-line react-native/no-unused-styles
25858
25646
  responsiveFieldset: {
25859
25647
  paddingRight: phoneMargin
25860
25648
  }
25861
- }));
25862
-
25863
- _defineProperty(BaseRadio, "defaultProps", {
25864
- editMode: false
25865
25649
  });
25866
25650
 
25867
25651
  class Radio$1 extends React.Component {
25868
25652
  constructor() {
25653
+ var _this;
25654
+
25869
25655
  super(...arguments);
25656
+ _this = this;
25870
25657
 
25871
- _defineProperty(this, "_renderRenderer", content => {
25872
- content = content || "";
25658
+ _defineProperty(this, "focusFunction", void 0);
25659
+
25660
+ _defineProperty(this, "_renderRenderer", function () {
25661
+ var content = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : "";
25873
25662
  var nextPassageRefId = 1;
25874
25663
  var widgets = {};
25875
25664
  var modContent = content.replace(/\{\{passage-ref (\d+) (\d+)(?: "([^"]*)")?\}\}/g, (match, passageNum, refNum, summaryText) => {
@@ -25901,19 +25690,14 @@ class Radio$1 extends React.Component {
25901
25690
  key: "choiceContentRenderer",
25902
25691
  content: modContent,
25903
25692
  widgets: widgets,
25904
- findExternalWidgets: this.props.findWidgets,
25693
+ findExternalWidgets: _this.props.findWidgets,
25905
25694
  alwaysUpdate: true,
25906
- linterContext: _objectSpread2(_objectSpread2({}, this.props.linterContext), {}, {
25695
+ linterContext: _objectSpread2(_objectSpread2({}, _this.props.linterContext), {}, {
25907
25696
  blockHighlight: true
25908
25697
  })
25909
25698
  });
25910
25699
  });
25911
25700
 
25912
- _defineProperty(this, "focus", i => {
25913
- // eslint-disable-next-line react/no-string-refs
25914
- return this.refs.baseRadio.focus(i);
25915
- });
25916
-
25917
25701
  _defineProperty(this, "updateChoices", newValueLists => {
25918
25702
  var {
25919
25703
  choiceStates,
@@ -25924,24 +25708,17 @@ class Radio$1 extends React.Component {
25924
25708
  // should clone the old `choiceStates` objects, in preparation to
25925
25709
  // mutate them.
25926
25710
 
25927
- var newChoiceStates;
25928
-
25929
- if (choiceStates) {
25930
- newChoiceStates = choiceStates.map(state => _objectSpread2({}, state));
25931
- } else {
25932
- newChoiceStates = choices.map(() => ({
25933
- selected: false,
25934
- crossedOut: false,
25935
- highlighted: false,
25936
- rationaleShown: false,
25937
- correctnessShown: false,
25938
- previouslyAnswered: false,
25939
- readOnly: false
25940
- }));
25941
- } // Mutate the new `choiceState` objects, according to the new `checked`
25711
+ var newChoiceStates = choiceStates ? choiceStates.map(state => _objectSpread2({}, state)) : choices.map(() => ({
25712
+ selected: false,
25713
+ crossedOut: false,
25714
+ highlighted: false,
25715
+ rationaleShown: false,
25716
+ correctnessShown: false,
25717
+ previouslyAnswered: false,
25718
+ readOnly: false
25719
+ })); // Mutate the new `choiceState` objects, according to the new `checked`
25942
25720
  // and `crossedOut` values provided in `newValueLists`.
25943
25721
 
25944
-
25945
25722
  newChoiceStates.forEach((choiceState, i) => {
25946
25723
  choiceState.selected = newValueLists.checked[i];
25947
25724
  choiceState.crossedOut = newValueLists.crossedOut[i];
@@ -26005,14 +25782,47 @@ class Radio$1 extends React.Component {
26005
25782
  });
26006
25783
  }
26007
25784
 
25785
+ // TODO(LP-10672): I think this might be unused right now. I can't find anywhere
25786
+ // that we pass a value to `.focus()` and it seems to have been used for
25787
+ // adding hints when editing.
25788
+ // See: https://github.com/Khan/perseus/blame/e18582b4b69959270b90e237ef1813899711ddfa/src/widgets/radio.js#L169
25789
+ focus(choiceIndex) {
25790
+ if (this.focusFunction) {
25791
+ return this.focusFunction(choiceIndex);
25792
+ }
25793
+
25794
+ return false;
25795
+ } // lets BaseRadio regiser a focus callback so widget
25796
+ // can focus an individual choice
25797
+
25798
+
25799
+ registerFocusFunction(fun) {
25800
+ this.focusFunction = fun;
25801
+ } // When `BaseRadio`'s `onChange` handler is called, indicating a change in
25802
+ // our choices' state, we need to call our `onChange` handler in order to
25803
+ // persist those changes in the item's Perseus state.
25804
+ //
25805
+ // So, given the new values for each choice, construct the new
25806
+ // `choiceStates` objects, and pass them to `this.props.onChange`.
25807
+ //
25808
+ // `newValueLists` is an object with two keys: `checked` and `crossedOut`.
25809
+ // Each contains an array of boolean values, specifying the new checked and
25810
+ // crossed-out value of each choice.
25811
+ //
25812
+ // NOTE(mdr): This method expects to be auto-bound. If this component is
25813
+ // converted to an ES6 class, take care to auto-bind this method!
25814
+
25815
+
26008
25816
  render() {
26009
- var choices = this.props.choices;
25817
+ var {
25818
+ choices
25819
+ } = this.props;
26010
25820
  var choiceStates;
26011
25821
 
26012
25822
  if (this.props.static) {
26013
- choiceStates = _.map(choices, val => ({
26014
- selected: val.correct,
26015
- crossedOut: val.crossedOut,
25823
+ choiceStates = choices.map(choice => ({
25824
+ selected: !!choice.correct,
25825
+ crossedOut: false,
26016
25826
  readOnly: true,
26017
25827
  highlighted: false,
26018
25828
  rationaleShown: true,
@@ -26020,12 +25830,12 @@ class Radio$1 extends React.Component {
26020
25830
  previouslyAnswered: false
26021
25831
  }));
26022
25832
  } else if (this.props.choiceStates) {
26023
- choiceStates = this.props.choiceStates; // $FlowFixMe[prop-missing]
25833
+ choiceStates = this.props.choiceStates;
26024
25834
  } else if (this.props.values) {
26025
25835
  // Support legacy choiceStates implementation
26026
25836
 
26027
25837
  /* istanbul ignore next - props.values is deprecated */
26028
- choiceStates = _.map(this.props.values, val => ({
25838
+ choiceStates = this.props.values.map(val => ({
26029
25839
  selected: val,
26030
25840
  crossedOut: false,
26031
25841
  readOnly: false,
@@ -26035,7 +25845,7 @@ class Radio$1 extends React.Component {
26035
25845
  previouslyAnswered: false
26036
25846
  }));
26037
25847
  } else {
26038
- choiceStates = _.map(choices, () => ({
25848
+ choiceStates = choices.map(() => ({
26039
25849
  selected: false,
26040
25850
  crossedOut: false,
26041
25851
  readOnly: false,
@@ -26046,7 +25856,7 @@ class Radio$1 extends React.Component {
26046
25856
  }));
26047
25857
  }
26048
25858
 
26049
- choices = _.map(choices, (choice, i) => {
25859
+ var choicesProp = choices.map((choice, i) => {
26050
25860
  var content = choice.isNoneOfTheAbove && !choice.content ? // we use i18n._ instead of $_ here because the content
26051
25861
  // sent to a renderer needs to be a string, not a react
26052
25862
  // node (/renderable/fragment).
@@ -26074,37 +25884,36 @@ class Radio$1 extends React.Component {
26074
25884
  // TODO(emily): Come up with a more comprehensive way to solve
26075
25885
  // this sort of "serialized state breaks when internal
26076
25886
  // structure changes" problem.
26077
- correct: typeof choice.correct === "undefined" ? !!reviewChoice && reviewChoice.correct : choice.correct,
25887
+ correct: choice.correct === undefined ? !!reviewChoice && !!reviewChoice.correct : choice.correct,
26078
25888
  disabled: readOnly,
26079
25889
  hasRationale: !!choice.clue,
26080
25890
  rationale: this._renderRenderer(choice.clue),
26081
25891
  showRationale: rationaleShown,
26082
25892
  showCorrectness: correctnessShown,
26083
- isNoneOfTheAbove: choice.isNoneOfTheAbove,
26084
- revealNoneOfTheAbove: this.props.questionCompleted && selected,
25893
+ isNoneOfTheAbove: !!choice.isNoneOfTheAbove,
25894
+ revealNoneOfTheAbove: !!(this.props.questionCompleted && selected),
26085
25895
  crossedOut,
26086
25896
  highlighted,
26087
25897
  previouslyAnswered: previouslyAnswered
26088
25898
  };
26089
25899
  });
26090
- return /*#__PURE__*/React.createElement(BaseRadio // eslint-disable-next-line react/no-string-refs
26091
- , {
26092
- ref: "baseRadio",
25900
+ return /*#__PURE__*/React.createElement(BaseRadio, {
26093
25901
  labelWrap: true,
26094
25902
  multipleSelect: this.props.multipleSelect,
26095
25903
  countChoices: this.props.countChoices,
26096
25904
  numCorrect: this.props.numCorrect,
26097
- choices: choices,
25905
+ choices: choicesProp,
26098
25906
  onChange: this.updateChoices,
26099
25907
  reviewModeRubric: this.props.reviewModeRubric,
26100
25908
  deselectEnabled: this.props.deselectEnabled,
26101
25909
  apiOptions: this.props.apiOptions,
26102
- isLastUsedWidget: this.props.isLastUsedWidget
25910
+ isLastUsedWidget: this.props.isLastUsedWidget,
25911
+ registerFocusFunction: i => this.registerFocusFunction(i)
26103
25912
  });
26104
25913
  }
26105
25914
 
26106
25915
  static validate(userInput, rubric) {
26107
- var numSelected = _.reduce(userInput.choicesSelected, (sum, selected) => {
25916
+ var numSelected = userInput.choicesSelected.reduce((sum, selected) => {
26108
25917
  return sum + (selected ? 1 : 0);
26109
25918
  }, 0);
26110
25919
 
@@ -26113,10 +25922,9 @@ class Radio$1 extends React.Component {
26113
25922
  type: "invalid",
26114
25923
  message: null
26115
25924
  };
26116
- } // $FlowFixMe[invalid-compare]
26117
-
25925
+ }
26118
25926
 
26119
- if (userInput.numCorrect > 1 && numSelected !== userInput.numCorrect) {
25927
+ if (userInput.numCorrect && userInput.numCorrect > 1 && numSelected !== userInput.numCorrect) {
26120
25928
  return {
26121
25929
  type: "invalid",
26122
25930
  message: i18n._("Please choose the correct number of answers.")
@@ -26129,14 +25937,12 @@ class Radio$1 extends React.Component {
26129
25937
  message: i18n._("'None of the above' may not be selected " + "when other answers are selected.")
26130
25938
  };
26131
25939
  }
26132
- /* jshint -W018 */
26133
-
26134
25940
 
26135
- var correct = _.all(userInput.choicesSelected, (selected, i) => {
25941
+ var correct = userInput.choicesSelected.every((selected, i) => {
26136
25942
  var isCorrect;
26137
25943
 
26138
25944
  if (userInput.noneOfTheAboveIndex === i) {
26139
- isCorrect = _.all(rubric.choices, (choice, j) => {
25945
+ isCorrect = rubric.choices.every((choice, j) => {
26140
25946
  return i === j || !choice.correct;
26141
25947
  });
26142
25948
  } else {
@@ -26145,9 +25951,6 @@ class Radio$1 extends React.Component {
26145
25951
 
26146
25952
  return isCorrect === selected;
26147
25953
  });
26148
- /* jshint +W018 */
26149
-
26150
-
26151
25954
  return {
26152
25955
  type: "points",
26153
25956
  earned: correct ? 1 : 0,
@@ -26169,9 +25972,7 @@ class Radio$1 extends React.Component {
26169
25972
  var numCorrect = props.numCorrect;
26170
25973
 
26171
25974
  for (var i = 0; i < choicesSelected.length; i++) {
26172
- // $FlowFixMe[prop-missing]
26173
- var index = props.choices[i].originalIndex; // $FlowFixMe[incompatible-use]
26174
-
25975
+ var index = props.choices[i].originalIndex;
26175
25976
  choicesSelected[index] = choiceStates[i].selected;
26176
25977
 
26177
25978
  if (props.choices[i].isNoneOfTheAbove) {
@@ -26192,31 +25993,35 @@ class Radio$1 extends React.Component {
26192
25993
  }; // Support legacy choiceState implementation
26193
25994
  }
26194
25995
  /* istanbul ignore if - props.values is deprecated */
26195
- // $FlowFixMe[prop-missing]
26196
25996
 
26197
25997
 
26198
- if (props.values) {
25998
+ var {
25999
+ values
26000
+ } = props;
26001
+
26002
+ if (values) {
26199
26003
  var _noneOfTheAboveIndex = null;
26200
26004
  var _noneOfTheAboveSelected = false;
26201
- var values = props.values.slice();
26005
+ var _choicesSelected = [...values];
26202
26006
  var _countChoices = props.countChoices;
26203
26007
  var _numCorrect = props.numCorrect;
26008
+ var valuesLength = values.length;
26204
26009
 
26205
- for (var _i = 0; _i < props.values.length; _i++) {
26010
+ for (var _i = 0; _i < valuesLength; _i++) {
26206
26011
  var _index = props.choices[_i].originalIndex;
26207
- values[_index] = props.values[_i];
26012
+ _choicesSelected[_index] = values[_i];
26208
26013
 
26209
26014
  if (props.choices[_i].isNoneOfTheAbove) {
26210
26015
  _noneOfTheAboveIndex = _index;
26211
26016
 
26212
- if (values[_i]) {
26017
+ if (_choicesSelected[_i]) {
26213
26018
  _noneOfTheAboveSelected = true;
26214
26019
  }
26215
26020
  }
26216
26021
  }
26217
26022
 
26218
26023
  return {
26219
- choicesSelected: values,
26024
+ choicesSelected: _choicesSelected,
26220
26025
  noneOfTheAboveIndex: _noneOfTheAboveIndex,
26221
26026
  noneOfTheAboveSelected: _noneOfTheAboveSelected,
26222
26027
  countChoices: _countChoices,
@@ -26226,15 +26031,14 @@ class Radio$1 extends React.Component {
26226
26031
 
26227
26032
 
26228
26033
  return {
26229
- choicesSelected: _.map(props.choices, () => false)
26034
+ choicesSelected: props.choices.map(() => false)
26230
26035
  };
26231
26036
  }
26232
26037
 
26233
26038
  }
26234
26039
 
26235
26040
  _defineProperty(Radio$1, "defaultProps", {
26236
- choices: [{}],
26237
- displayCount: null,
26041
+ choices: [],
26238
26042
  multipleSelect: false,
26239
26043
  countChoices: false,
26240
26044
  deselectEnabled: false,
@@ -26265,14 +26069,14 @@ var _choiceTransform = (widgetOptions, problemNum) => {
26265
26069
 
26266
26070
  var _addNoneOfAbove = function _addNoneOfAbove(choices) {
26267
26071
  var noneOfTheAbove = null;
26268
-
26269
- var newChoices = _.reject(choices, function (choice, index) {
26072
+ var newChoices = choices.filter((choice, index) => {
26270
26073
  if (choice.isNoneOfTheAbove) {
26271
26074
  noneOfTheAbove = choice;
26272
- return true;
26075
+ return false;
26273
26076
  }
26274
- }); // Place the "None of the above" options last
26275
26077
 
26078
+ return true;
26079
+ }); // Place the "None of the above" options last
26276
26080
 
26277
26081
  if (noneOfTheAbove) {
26278
26082
  newChoices.push(noneOfTheAbove);
@@ -26292,9 +26096,8 @@ var _choiceTransform = (widgetOptions, problemNum) => {
26292
26096
  }; // Add meta-information to choices
26293
26097
 
26294
26098
 
26295
- var choices = widgetOptions.choices.slice();
26296
- choices = _.map(choices, (choice, i) => {
26297
- return _.extend({}, choice, {
26099
+ var choices = widgetOptions.choices.map((choice, i) => {
26100
+ return _objectSpread2(_objectSpread2({}, choice), {}, {
26298
26101
  originalIndex: i,
26299
26102
  correct: Boolean(choice.correct)
26300
26103
  });
@@ -41132,43 +40935,23 @@ var styles$9 = StyleSheet.create({
41132
40935
  }
41133
40936
  });
41134
40937
 
41135
- var getLineHeightForNode = ($line1, $line2) => {
41136
- return $line2.offset().top - $line1.offset().top;
40938
+ var getLineHeightForNode = (line1, line2) => {
40939
+ return $(line2).offset().top - $(line1).offset().top;
41137
40940
  };
41138
40941
 
41139
- var START_REF_PREFIX = "start-ref-";
41140
- var END_REF_PREFIX = "end-ref-";
41141
- var REF_STYLE = {
41142
- display: "inline-block",
41143
- width: 0,
41144
- visibility: "hidden"
41145
- };
41146
- var LABEL_OUTER_STYLE = {
41147
- // for some reason we need these to keep the nbsp from wrapping when the
41148
- // inner circle/square is display: inline-block
41149
- display: "inline",
41150
- whiteSpace: "nowrap"
41151
- };
41152
- var SQUARE_LABEL_STYLE = {
41153
- display: "inline-block",
41154
- color: "rgb(255, 255, 255)",
41155
- backgroundColor: "rgb(90, 90, 90)",
41156
- paddingLeft: 10,
41157
- paddingRight: 10,
41158
- userSelect: "none",
41159
- WebkitUserSelect: "none"
41160
- };
41161
- var CIRCLE_LABEL_STYLE = {
41162
- display: "inline-block",
41163
- color: "rgb(255, 255, 255)",
41164
- backgroundColor: "rgb(90, 90, 90)",
41165
- userSelect: "none",
41166
- WebkitUserSelect: "none",
41167
- width: 22,
41168
- height: 22,
41169
- borderRadius: "50%",
41170
- textAlign: "center"
41171
- };
40942
+ function getInitialParseState() {
40943
+ return {
40944
+ currentRef: [],
40945
+ useRefs: true,
40946
+ lastRef: 0,
40947
+ firstSentenceRef: null,
40948
+ firstQuestionRef: null,
40949
+ lastFootnote: {
40950
+ id: 0,
40951
+ text: ""
40952
+ }
40953
+ };
40954
+ }
41172
40955
 
41173
40956
  class RefStart extends React.Component {
41174
40957
  constructor() {
@@ -41187,10 +40970,6 @@ class RefStart extends React.Component {
41187
40970
 
41188
40971
  }
41189
40972
 
41190
- _defineProperty(RefStart, "propTypes", {
41191
- refContent: PropTypes.node.isRequired
41192
- });
41193
-
41194
40973
  class RefEnd extends React.Component {
41195
40974
  render() {
41196
40975
  return /*#__PURE__*/React.createElement("span", {
@@ -41201,20 +40980,11 @@ class RefEnd extends React.Component {
41201
40980
  }
41202
40981
 
41203
40982
  var rules = {
41204
- // $FlowFixMe[prop-missing]
41205
- // $FlowFixMe[incompatible-use]
41206
40983
  newline: SimpleMarkdown.defaultRules.newline,
41207
- // $FlowFixMe[prop-missing]
41208
- // $FlowFixMe[incompatible-use]
41209
40984
  paragraph: SimpleMarkdown.defaultRules.paragraph,
41210
- // $FlowFixMe[prop-missing]
41211
- // $FlowFixMe[incompatible-use]
41212
40985
  escape: SimpleMarkdown.defaultRules.escape,
41213
40986
  passageFootnote: {
41214
- // $FlowFixMe[prop-missing]
41215
- // $FlowFixMe[incompatible-use]
41216
40987
  order: SimpleMarkdown.defaultRules.escape.order + 0.1,
41217
- // $FlowFixMe[prop-missing]
41218
40988
  match: SimpleMarkdown.inlineRegex(/^\^/),
41219
40989
  parse: (capture, _parse, state) => {
41220
40990
  // if no footnotes have been seen, we're id 1. otherwise,
@@ -41243,10 +41013,8 @@ var rules = {
41243
41013
  }
41244
41014
  },
41245
41015
  refStart: {
41246
- // $FlowFixMe[prop-missing]
41247
- // $FlowFixMe[incompatible-use]
41248
41016
  order: SimpleMarkdown.defaultRules.escape.order + 0.2,
41249
- match: function match(source, state) {
41017
+ match: function match(source) {
41250
41018
  var capture = /^\{\{/.exec(source);
41251
41019
 
41252
41020
  if (capture) {
@@ -41277,12 +41045,14 @@ var rules = {
41277
41045
  }
41278
41046
 
41279
41047
  closeIndex++;
41280
- }
41281
-
41282
- var refText = source.slice(2, closeIndex); // A "magic" capture that matches the opening {{
41048
+ } // A "magic" capture that matches the opening {{
41283
41049
  // but captures the full ref text internally :D
41050
+ // unfortunately the "magic" makes Flow upset,
41051
+ // so there's this questionable workaround
41052
+
41284
41053
 
41285
- return [capture[0], refText];
41054
+ capture[1] = source.slice(2, closeIndex);
41055
+ return capture;
41286
41056
  }
41287
41057
 
41288
41058
  return null;
@@ -41306,7 +41076,7 @@ var rules = {
41306
41076
  // to make those refs into react refs on the
41307
41077
  // passage, for instance!
41308
41078
  useRefs: false
41309
- }, INITIAL_PARSE_STATE));
41079
+ }, getInitialParseState()));
41310
41080
 
41311
41081
  return {
41312
41082
  ref: ref,
@@ -41314,26 +41084,27 @@ var rules = {
41314
41084
  };
41315
41085
  },
41316
41086
  react: (node, output, state) => {
41317
- if (node.ref == null) {
41087
+ var ref = node.ref;
41088
+
41089
+ if (ref == null) {
41318
41090
  return null;
41319
41091
  } // We don't pass state here because this is parsed
41320
41092
  // and output out-of-band. We don't want to affect
41321
41093
  // our state by the double-output here :).
41322
41094
 
41323
41095
 
41324
- var refContent = output(node.refContent, {});
41096
+ var refContent = output(node.refContent, {}); // note(matthewc) the refs created here become the refs
41097
+ // pulled from `this.refs` in passage.jsx
41098
+
41325
41099
  return /*#__PURE__*/React.createElement(RefStart, {
41326
- ref: START_REF_PREFIX + node.ref,
41327
- key: START_REF_PREFIX + node.ref,
41100
+ ref: START_REF_PREFIX + ref,
41101
+ key: START_REF_PREFIX + ref,
41328
41102
  refContent: refContent
41329
41103
  });
41330
41104
  }
41331
41105
  },
41332
41106
  refEnd: {
41333
- // $FlowFixMe[prop-missing]
41334
- // $FlowFixMe[incompatible-use]
41335
41107
  order: SimpleMarkdown.defaultRules.escape.order + 0.3,
41336
- // $FlowFixMe[prop-missing]
41337
41108
  match: SimpleMarkdown.inlineRegex(/^\}\}/),
41338
41109
  parse: (capture, _parse3, state) => {
41339
41110
  if (!state.useRefs) {
@@ -41349,6 +41120,8 @@ var rules = {
41349
41120
  },
41350
41121
  react: (node, output, state) => {
41351
41122
  if (node.ref != null) {
41123
+ // note(matthewc) the refs created here become the refs
41124
+ // pulled from `this.refs` in passage.jsx
41352
41125
  return /*#__PURE__*/React.createElement(RefEnd, {
41353
41126
  ref: END_REF_PREFIX + node.ref,
41354
41127
  key: END_REF_PREFIX + node.ref
@@ -41362,10 +41135,7 @@ var rules = {
41362
41135
  }
41363
41136
  },
41364
41137
  squareLabel: {
41365
- // $FlowFixMe[prop-missing]
41366
- // $FlowFixMe[incompatible-use]
41367
41138
  order: SimpleMarkdown.defaultRules.escape.order + 0.4,
41368
- // $FlowFixMe[prop-missing]
41369
41139
  match: SimpleMarkdown.inlineRegex(/^\[\[(\w+)\]\]( *)/),
41370
41140
  parse: (capture, _parse4, state) => {
41371
41141
  if (!state.firstQuestionRef) {
@@ -41394,10 +41164,7 @@ var rules = {
41394
41164
  }
41395
41165
  },
41396
41166
  circleLabel: {
41397
- // $FlowFixMe[prop-missing]
41398
- // $FlowFixMe[incompatible-use]
41399
41167
  order: SimpleMarkdown.defaultRules.escape.order + 0.5,
41400
- // $FlowFixMe[prop-missing]
41401
41168
  match: SimpleMarkdown.inlineRegex(/^\(\((\w+)\)\)( *)/),
41402
41169
  parse: (capture, _parse5, state) => {
41403
41170
  return {
@@ -41422,10 +41189,7 @@ var rules = {
41422
41189
  }
41423
41190
  },
41424
41191
  squareBracketRef: {
41425
- // $FlowFixMe[prop-missing]
41426
- // $FlowFixMe[incompatible-use]
41427
41192
  order: SimpleMarkdown.defaultRules.escape.order + 0.6,
41428
- // $FlowFixMe[prop-missing]
41429
41193
  match: SimpleMarkdown.inlineRegex(/^\[(\d+)\]( *)/),
41430
41194
  parse: (capture, _parse6, state) => {
41431
41195
  if (!state.firstSentenceRef) {
@@ -41451,10 +41215,7 @@ var rules = {
41451
41215
  }
41452
41216
  },
41453
41217
  highlight: {
41454
- // $FlowFixMe[prop-missing]
41455
- // $FlowFixMe[incompatible-use]
41456
41218
  order: SimpleMarkdown.defaultRules.escape.order + 0.7,
41457
- // $FlowFixMe[prop-missing]
41458
41219
  match: SimpleMarkdown.inlineRegex(/^{highlighting.start}(.+?){highlighting.end}/),
41459
41220
  parse: (capture, _parse7, state) => {
41460
41221
  return {
@@ -41469,10 +41230,7 @@ var rules = {
41469
41230
  }
41470
41231
  },
41471
41232
  reviewHighlight: {
41472
- // $FlowFixMe[prop-missing]
41473
- // $FlowFixMe[incompatible-use]
41474
41233
  order: SimpleMarkdown.defaultRules.escape.order + 0.7,
41475
- // $FlowFixMe[prop-missing]
41476
41234
  match: SimpleMarkdown.inlineRegex(/^{review-highlighting.start}(.+?){review-highlighting.end}/),
41477
41235
  parse: (capture, _parse8, state) => {
41478
41236
  return {
@@ -41486,17 +41244,9 @@ var rules = {
41486
41244
  }, node.content)];
41487
41245
  }
41488
41246
  },
41489
- // $FlowFixMe[prop-missing]
41490
- // $FlowFixMe[incompatible-use]
41491
41247
  strong: SimpleMarkdown.defaultRules.strong,
41492
- // $FlowFixMe[prop-missing]
41493
- // $FlowFixMe[incompatible-use]
41494
41248
  u: SimpleMarkdown.defaultRules.u,
41495
- // $FlowFixMe[prop-missing]
41496
- // $FlowFixMe[incompatible-use]
41497
41249
  em: SimpleMarkdown.defaultRules.em,
41498
- // $FlowFixMe[prop-missing]
41499
- // $FlowFixMe[incompatible-use]
41500
41250
  del: SimpleMarkdown.defaultRules.del,
41501
41251
  text: _objectSpread2(_objectSpread2({}, SimpleMarkdown.defaultRules.text), {}, {
41502
41252
  react(node, output, state) {
@@ -41507,32 +41257,54 @@ var rules = {
41507
41257
 
41508
41258
  })
41509
41259
  };
41510
- var INITIAL_PARSE_STATE = {
41511
- currentRef: [],
41512
- useRefs: true,
41513
- lastRef: 0,
41514
- lastFootnote: {
41515
- id: 0,
41516
- text: ""
41517
- }
41518
- }; // $FlowFixMe[prop-missing]
41519
-
41260
+ var START_REF_PREFIX = "start-ref-";
41261
+ var END_REF_PREFIX = "end-ref-";
41262
+ var REF_STYLE = {
41263
+ display: "inline-block",
41264
+ width: 0,
41265
+ visibility: "hidden"
41266
+ };
41267
+ var LABEL_OUTER_STYLE = {
41268
+ // for some reason we need these to keep the nbsp from wrapping when the
41269
+ // inner circle/square is display: inline-block
41270
+ display: "inline",
41271
+ whiteSpace: "nowrap"
41272
+ };
41273
+ var SQUARE_LABEL_STYLE = {
41274
+ display: "inline-block",
41275
+ color: "rgb(255, 255, 255)",
41276
+ backgroundColor: "rgb(90, 90, 90)",
41277
+ paddingLeft: 10,
41278
+ paddingRight: 10,
41279
+ userSelect: "none",
41280
+ WebkitUserSelect: "none"
41281
+ };
41282
+ var CIRCLE_LABEL_STYLE = {
41283
+ display: "inline-block",
41284
+ color: "rgb(255, 255, 255)",
41285
+ backgroundColor: "rgb(90, 90, 90)",
41286
+ userSelect: "none",
41287
+ WebkitUserSelect: "none",
41288
+ width: 22,
41289
+ height: 22,
41290
+ borderRadius: "50%",
41291
+ textAlign: "center"
41292
+ };
41520
41293
  var builtParser = SimpleMarkdown.parserFor(rules);
41521
41294
 
41522
41295
  var parse = (source, state) => {
41523
41296
  state = state || {};
41524
41297
  var paragraphedSource = source + "\n\n";
41525
- return builtParser(paragraphedSource, _.extend(state, INITIAL_PARSE_STATE));
41298
+ return builtParser(paragraphedSource, _.extend(state, getInitialParseState()));
41526
41299
  };
41527
41300
 
41301
+ var output = SimpleMarkdown.reactFor(SimpleMarkdown.ruleOutput(rules, "react"));
41528
41302
  var PassageMarkdown = {
41529
- parse: parse,
41530
- // $FlowFixMe[prop-missing]
41531
- output: SimpleMarkdown.reactFor( // $FlowFixMe[prop-missing]
41532
- SimpleMarkdown.ruleOutput(rules, "react")),
41533
- START_REF_PREFIX: START_REF_PREFIX,
41534
- END_REF_PREFIX: END_REF_PREFIX,
41535
- _rulesForTesting: rules
41303
+ parse,
41304
+ output,
41305
+ START_REF_PREFIX,
41306
+ END_REF_PREFIX,
41307
+ getInitialParseState
41536
41308
  };
41537
41309
 
41538
41310
  // A fake paragraph to measure the line height of the passage. In CSS we always
@@ -41545,9 +41317,9 @@ class LineHeightMeasurer extends React.Component {
41545
41317
 
41546
41318
  _defineProperty(this, "_cachedLineHeight", void 0);
41547
41319
 
41548
- _defineProperty(this, "$body", void 0);
41320
+ _defineProperty(this, "body", void 0);
41549
41321
 
41550
- _defineProperty(this, "$end", void 0);
41322
+ _defineProperty(this, "end", void 0);
41551
41323
  }
41552
41324
 
41553
41325
  measureLineHeight() {
@@ -41559,23 +41331,30 @@ class LineHeightMeasurer extends React.Component {
41559
41331
  }
41560
41332
 
41561
41333
  forceMeasureLineHeight() {
41562
- // Add some text which magically fills an entire line.
41563
- this.$body.text(" \u0080"); // Now, the line height is the difference between the top of the
41334
+ var body = this.body;
41335
+ var end = this.end;
41336
+
41337
+ if (!body || !end) {
41338
+ return;
41339
+ } // Add some text which magically fills an entire line.
41340
+
41341
+
41342
+ body.textContent = " \u0080"; // Now, the line height is the difference between the top of the
41564
41343
  // second line and the top of the first line.
41565
41344
 
41566
- this._cachedLineHeight = getLineHeightForNode(this.$body, this.$end); // Clear out the first line so it doesn't overlap the passage.
41345
+ this._cachedLineHeight = getLineHeightForNode(body, end); // Clear out the first line so it doesn't overlap the passage.
41567
41346
 
41568
- this.$body.text("");
41347
+ body.textContent = "";
41569
41348
  }
41570
41349
 
41571
41350
  render() {
41572
41351
  return /*#__PURE__*/React.createElement("div", {
41573
41352
  className: css(styles$8.measurer)
41574
41353
  }, /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("div", {
41575
- ref: e => this.$body = $(e),
41354
+ ref: _ref => this.body = _ref,
41576
41355
  className: "paragraph"
41577
41356
  }), /*#__PURE__*/React.createElement("div", {
41578
- ref: e => this.$end = $(e)
41357
+ ref: _ref2 => this.end = _ref2
41579
41358
  })));
41580
41359
  }
41581
41360
 
@@ -41594,9 +41373,11 @@ class Passage extends React.Component {
41594
41373
  constructor() {
41595
41374
  super(...arguments);
41596
41375
 
41597
- _defineProperty(this, "_onResize", void 0);
41376
+ _defineProperty(this, "_contentRef", void 0);
41377
+
41378
+ _defineProperty(this, "_lineHeightMeasurerRef", void 0);
41598
41379
 
41599
- _defineProperty(this, "_lineHeightMeasurer", void 0);
41380
+ _defineProperty(this, "_onResize", void 0);
41600
41381
 
41601
41382
  _defineProperty(this, "_stylesAppiedTimer", void 0);
41602
41383
 
@@ -41617,6 +41398,8 @@ class Passage extends React.Component {
41617
41398
  this._updateState();
41618
41399
 
41619
41400
  this._onResize = _.throttle(() => {
41401
+ var _this$_lineHeightMeas;
41402
+
41620
41403
  // If we're rendering JIPT text, we won't have line numbers or a
41621
41404
  // line height measurer, so skip handling this resize.
41622
41405
  if (this.shouldRenderJipt()) {
@@ -41627,7 +41410,7 @@ class Passage extends React.Component {
41627
41410
  // listen for resize events.
41628
41411
 
41629
41412
 
41630
- this._lineHeightMeasurer.forceMeasureLineHeight();
41413
+ (_this$_lineHeightMeas = this._lineHeightMeasurerRef) === null || _this$_lineHeightMeas === void 0 ? void 0 : _this$_lineHeightMeas.forceMeasureLineHeight();
41631
41414
 
41632
41415
  this._updateState();
41633
41416
  }, 500);
@@ -41690,9 +41473,8 @@ class Passage extends React.Component {
41690
41473
  }
41691
41474
 
41692
41475
  _measureLines() {
41693
- // eslint-disable-next-line react/no-string-refs
41694
- var $renderer = $(ReactDOM__default.findDOMNode(this.refs.content));
41695
- var contentsHeight = $renderer.height();
41476
+ var renderer = ReactDOM__default.findDOMNode(this._contentRef);
41477
+ var contentsHeight = $(renderer).height();
41696
41478
 
41697
41479
  var lineHeight = this._getLineHeight();
41698
41480
 
@@ -41719,7 +41501,9 @@ class Passage extends React.Component {
41719
41501
  }
41720
41502
 
41721
41503
  _getLineHeight() {
41722
- return this._lineHeightMeasurer.measureLineHeight();
41504
+ var _this$_lineHeightMeas2;
41505
+
41506
+ return ((_this$_lineHeightMeas2 = this._lineHeightMeasurerRef) === null || _this$_lineHeightMeas2 === void 0 ? void 0 : _this$_lineHeightMeas2.measureLineHeight()) || 0;
41723
41507
  }
41724
41508
 
41725
41509
  getLineCount() {
@@ -41735,12 +41519,16 @@ class Passage extends React.Component {
41735
41519
  * These are functions to support the passage refs inter-widgets feature
41736
41520
  * where other widgets can fetch the line numbers of a reference inside of
41737
41521
  * a passage.
41522
+ *
41523
+ * todo(matthewc): The refs are created by PassageMarkdown's refStart and refEnd,
41524
+ * somehow bubbling up to Passage's `this.refs`. This runs against
41525
+ * current best practices for refs by using string refs, but also
41526
+ * by breaking our expectation of explicit data flow.
41738
41527
  */
41739
41528
 
41740
41529
 
41741
41530
  _getStartRefLineNumber(referenceNumber) {
41742
- var refRef = PassageMarkdown.START_REF_PREFIX + referenceNumber; // eslint-disable-next-line react/no-string-refs
41743
-
41531
+ var refRef = PassageMarkdown.START_REF_PREFIX + referenceNumber;
41744
41532
  var ref = this.refs[refRef];
41745
41533
 
41746
41534
  if (!ref) {
@@ -41763,8 +41551,7 @@ class Passage extends React.Component {
41763
41551
  }
41764
41552
 
41765
41553
  _getEndRefLineNumber(referenceNumber) {
41766
- var refRef = PassageMarkdown.END_REF_PREFIX + referenceNumber; // eslint-disable-next-line react/no-string-refs
41767
-
41554
+ var refRef = PassageMarkdown.END_REF_PREFIX + referenceNumber;
41768
41555
  var ref = this.refs[refRef];
41769
41556
 
41770
41557
  if (!ref) {
@@ -41801,9 +41588,8 @@ class Passage extends React.Component {
41801
41588
  }
41802
41589
 
41803
41590
  _convertPosToLineNumber(absoluteVPos) {
41804
- // eslint-disable-next-line react/no-string-refs
41805
- var $content = $(ReactDOM__default.findDOMNode(this.refs.content));
41806
- var relativeVPos = absoluteVPos - $content.offset().top;
41591
+ var content = ReactDOM__default.findDOMNode(this._contentRef);
41592
+ var relativeVPos = absoluteVPos - $(content).offset().top;
41807
41593
 
41808
41594
  var lineHeight = this._getLineHeight();
41809
41595
 
@@ -41812,8 +41598,7 @@ class Passage extends React.Component {
41812
41598
  }
41813
41599
 
41814
41600
  _getRefContent(referenceNumber) {
41815
- var refRef = PassageMarkdown.START_REF_PREFIX + referenceNumber; // eslint-disable-next-line react/no-string-refs
41816
-
41601
+ var refRef = PassageMarkdown.START_REF_PREFIX + referenceNumber;
41817
41602
  var ref = this.refs[refRef];
41818
41603
 
41819
41604
  if (!ref) {
@@ -41919,12 +41704,9 @@ class Passage extends React.Component {
41919
41704
  onSerializedHighlightsUpdate: this._handleSerializedHighlightsUpdate,
41920
41705
  serializedHighlights: this.props.highlights
41921
41706
  }, /*#__PURE__*/React.createElement("div", {
41922
- ref: "content"
41923
- }, /*#__PURE__*/React.createElement(LineHeightMeasurer // TODO(mdr): We found a new Flow error when upgrading:
41924
- // "e (null) This type is incompatible with this._lineHeightMeasurer (LineHeightMeasurer)"
41925
- // $FlowFixMe[incompatible-type](0.52.0->0.53.0)
41926
- , {
41927
- ref: e => this._lineHeightMeasurer = e
41707
+ ref: _ref4 => this._contentRef = _ref4
41708
+ }, /*#__PURE__*/React.createElement(LineHeightMeasurer, {
41709
+ ref: _ref3 => this._lineHeightMeasurerRef = _ref3
41928
41710
  }), PassageMarkdown.output(parsed)));
41929
41711
  }
41930
41712
 
@@ -41967,10 +41749,7 @@ class Passage extends React.Component {
41967
41749
  });
41968
41750
  }
41969
41751
 
41970
- var parseState = {
41971
- firstSentenceRef: null,
41972
- firstQuestionRef: null
41973
- }; // Replace the vertical double quote characters quoting text with
41752
+ var parseState = PassageMarkdown.getInitialParseState(); // Replace the vertical double quote characters quoting text with
41974
41753
  // an unicode left and right double quote characters. This would
41975
41754
  // avoid rendering right or left unicode double quotes on both
41976
41755
  // ends of a sentence wrapped by double quotes.