@khanacademy/perseus 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/dist/es/index.css +15 -8
  3. package/dist/es/index.js +225 -194
  4. package/dist/es/index.js.map +1 -1
  5. package/dist/index.css +15 -8
  6. package/dist/index.js +229 -196
  7. package/dist/index.js.map +1 -1
  8. package/package.json +1 -1
  9. package/src/components/number-input.jsx +2 -2
  10. package/src/components/simple-keypad-input.jsx +18 -21
  11. package/src/index.js +1 -1
  12. package/src/multi-items/__tests__/__snapshots__/multi-renderer_test.js.snap +356 -65
  13. package/src/multi-items/__tests__/multi-renderer_test.js +9 -9
  14. package/src/perseus-types.js +18 -7
  15. package/src/styles/perseus-renderer.less +8 -2
  16. package/src/styles/widgets/passage.less +9 -5
  17. package/src/util/answer-types.js +2 -3
  18. package/src/util/math.js +3 -10
  19. package/src/widgets/__stories__/numeric-input.stories.jsx +43 -2
  20. package/src/widgets/__tests__/__snapshots__/definition_test.js.snap +4 -4
  21. package/src/widgets/__tests__/__snapshots__/group_test.jsx.snap +226 -65
  22. package/src/widgets/__tests__/__snapshots__/radio_test.jsx.snap +2581 -679
  23. package/src/widgets/__tests__/group_test.jsx +4 -4
  24. package/src/widgets/__tests__/numeric-input_test.jsx +12 -8
  25. package/src/widgets/__tests__/passage_test.jsx +81 -0
  26. package/src/widgets/__tests__/radio/base-radio_test.jsx +55 -7
  27. package/src/widgets/__tests__/radio/choice_test.jsx +164 -63
  28. package/src/widgets/__tests__/radio_test.jsx +39 -15
  29. package/src/widgets/definition.jsx +1 -1
  30. package/src/widgets/group.jsx +84 -102
  31. package/src/widgets/input-number.jsx +6 -1
  32. package/src/widgets/numeric-input.jsx +87 -80
  33. package/src/widgets/radio/base-radio.jsx +3 -1
  34. package/src/widgets/radio/choice-a11y-text.js +62 -0
  35. package/src/widgets/radio/choice-icon/choice-icon.jsx +0 -68
  36. package/src/widgets/radio/choice-icon/library-choice-icon.jsx +5 -10
  37. package/src/widgets/radio/choice-icon/sat-choice-icon.jsx +3 -12
  38. package/src/widgets/radio/choice.jsx +46 -3
package/dist/es/index.js CHANGED
@@ -1476,8 +1476,7 @@ var KhanAnswerTypes = {
1476
1476
  */
1477
1477
  number: {
1478
1478
  convertToPredicate: function convertToPredicate(correctAnswer, options) {
1479
- // TODO(alpert): Don't think this $.trim is necessary
1480
- var correctFloat = parseFloat($.trim(correctAnswer));
1479
+ var correctFloat = parseFloat(correctAnswer);
1481
1480
  return [function (guess, maxError) {
1482
1481
  return Math.abs(guess - correctFloat) < maxError;
1483
1482
  }, $.extend({}, options, {
@@ -22955,35 +22954,36 @@ _defineProperty(PossibleAnswers, "propTypes", {
22955
22954
  });
22956
22955
 
22957
22956
  var _excluded$3 = ["keypadElement", "onFocus", "value"];
22958
- var SimpleKeypadInput = createReactClass({
22959
- displayName: "SimpleKeypadInput",
22960
- propTypes: {
22961
- keypadElement: keypadElementPropType,
22962
- onFocus: PropTypes.func,
22963
- value: PropTypes.oneOfType([PropTypes.string, PropTypes.number])
22964
- },
22965
- componentDidMount: function componentDidMount() {
22957
+ class SimpleKeypadInput extends React.Component {
22958
+ constructor() {
22959
+ super(...arguments);
22960
+
22961
+ _defineProperty(this, "_isMounted", false);
22962
+ }
22963
+
22964
+ componentDidMount() {
22966
22965
  // TODO(scottgrant): This is a hack to remove the deprecated call to
22967
22966
  // this.isMounted() but is still considered an anti-pattern.
22968
22967
  this._isMounted = true;
22969
- },
22970
- componentWillUnmount: function componentWillUnmount() {
22968
+ }
22969
+
22970
+ componentWillUnmount() {
22971
22971
  this._isMounted = false;
22972
- },
22972
+ }
22973
22973
 
22974
22974
  focus() {
22975
22975
  // $FlowFixMe[object-this-reference]
22976
22976
  this.refs.input.focus(); // eslint-disable-line react/no-string-refs
22977
- },
22977
+ }
22978
22978
 
22979
22979
  blur() {
22980
22980
  // $FlowFixMe[object-this-reference]
22981
22981
  this.refs.input.blur(); // eslint-disable-line react/no-string-refs
22982
- },
22982
+ }
22983
22983
 
22984
- getValue: function getValue() {
22984
+ getValue() {
22985
22985
  return this.props.value;
22986
- },
22986
+ }
22987
22987
 
22988
22988
  render() {
22989
22989
  // $FlowFixMe[object-this-reference]
@@ -23022,7 +23022,12 @@ var SimpleKeypadInput = createReactClass({
23022
23022
  }, rest));
23023
23023
  }
23024
23024
 
23025
- });
23025
+ }
23026
+ SimpleKeypadInput.propTypes = {
23027
+ keypadElement: keypadElementPropType,
23028
+ onFocus: PropTypes.func,
23029
+ value: PropTypes.oneOfType([PropTypes.string, PropTypes.number])
23030
+ };
23026
23031
 
23027
23032
  var ParseTex$1 = TexWrangler.parseTex;
23028
23033
  var answerTypes = {
@@ -23277,9 +23282,13 @@ class InputNumber extends React.Component {
23277
23282
 
23278
23283
  if (rubric.answerType == null) {
23279
23284
  rubric.answerType = "number";
23280
- }
23285
+ } // note(matthewc): this will get immediately parsed again by
23286
+ // `KhanAnswerTypes.number.convertToPredicate`, but a string is
23287
+ // expected here
23288
+
23281
23289
 
23282
- var val = KhanAnswerTypes.number.createValidatorFunctional(rubric.value, {
23290
+ var stringValue = "".concat(rubric.value);
23291
+ var val = KhanAnswerTypes.number.createValidatorFunctional(stringValue, {
23283
23292
  simplify: rubric.simplify,
23284
23293
  inexact: rubric.inexact || undefined,
23285
23294
  maxError: rubric.maxError,
@@ -23405,6 +23414,8 @@ class NumericInput extends React.Component {
23405
23414
  constructor() {
23406
23415
  super(...arguments);
23407
23416
 
23417
+ _defineProperty(this, "inputRef", void 0);
23418
+
23408
23419
  _defineProperty(this, "state", {
23409
23420
  // keeps track of the other set of values when switching
23410
23421
  // between 0 and finite solutions
@@ -23423,9 +23434,8 @@ class NumericInput extends React.Component {
23423
23434
  correct = score.type === "points" && score.earned === score.total;
23424
23435
 
23425
23436
  if (!correct) {
23426
- var correctAnswers = _.filter(rubric.answers, answer => answer.status === "correct");
23427
-
23428
- var answerStrings = _.map(correctAnswers, answer => {
23437
+ var correctAnswers = rubric.answers.filter(answer => answer.status === "correct");
23438
+ var answerStrings = correctAnswers.map(answer => {
23429
23439
  // Figure out how this answer is supposed to be
23430
23440
  // displayed
23431
23441
  var format = "decimal";
@@ -23446,7 +23456,6 @@ class NumericInput extends React.Component {
23446
23456
 
23447
23457
  return answerString;
23448
23458
  });
23449
-
23450
23459
  answerBlurb = /*#__PURE__*/React.createElement(PossibleAnswers, {
23451
23460
  answers: answerStrings
23452
23461
  });
@@ -23460,7 +23469,7 @@ class NumericInput extends React.Component {
23460
23469
  var _this$props$answerFor;
23461
23470
 
23462
23471
  // if the set of specified forms are empty, allow all forms
23463
- var forms = ((_this$props$answerFor = this.props.answerForms) === null || _this$props$answerFor === void 0 ? void 0 : _this$props$answerFor.length) !== 0 ? this.props.answerForms : _.map(_.keys(formExamples), name => {
23472
+ var forms = ((_this$props$answerFor = this.props.answerForms) === null || _this$props$answerFor === void 0 ? void 0 : _this$props$answerFor.length) !== 0 ? this.props.answerForms : Object.keys(formExamples).map(name => {
23464
23473
  return {
23465
23474
  name: name,
23466
23475
  simplify: "required"
@@ -23486,8 +23495,7 @@ class NumericInput extends React.Component {
23486
23495
 
23487
23496
  var answerFormNames = _.uniq((_this$props$answerFor3 = this.props.answerForms) === null || _this$props$answerFor3 === void 0 ? void 0 : _this$props$answerFor3.map(form => form.name));
23488
23497
 
23489
- var allFormsAccepted = answerFormNames.length >= _.size(formExamples);
23490
-
23498
+ var allFormsAccepted = answerFormNames.length >= Object.keys(formExamples).length;
23491
23499
  return !noFormsAccepted && !allFormsAccepted;
23492
23500
  });
23493
23501
 
@@ -23496,20 +23504,22 @@ class NumericInput extends React.Component {
23496
23504
  });
23497
23505
 
23498
23506
  _defineProperty(this, "focus", () => {
23499
- // eslint-disable-next-line react/no-string-refs
23500
- this.refs.input.focus();
23507
+ var _this$inputRef;
23508
+
23509
+ (_this$inputRef = this.inputRef) === null || _this$inputRef === void 0 ? void 0 : _this$inputRef.focus();
23501
23510
  return true;
23502
23511
  });
23503
23512
 
23504
- _defineProperty(this, "focusInputPath", inputPath => {
23505
- /* istanbul ignore next */
23506
- // eslint-disable-next-line react/no-string-refs
23507
- this.refs.input.focus();
23513
+ _defineProperty(this, "focusInputPath", () => {
23514
+ var _this$inputRef2;
23515
+
23516
+ (_this$inputRef2 = this.inputRef) === null || _this$inputRef2 === void 0 ? void 0 : _this$inputRef2.focus();
23508
23517
  });
23509
23518
 
23510
- _defineProperty(this, "blurInputPath", inputPath => {
23511
- // eslint-disable-next-line react/no-string-refs
23512
- this.refs.input.blur();
23519
+ _defineProperty(this, "blurInputPath", () => {
23520
+ var _this$inputRef3;
23521
+
23522
+ (_this$inputRef3 = this.inputRef) === null || _this$inputRef3 === void 0 ? void 0 : _this$inputRef3.blur();
23513
23523
  });
23514
23524
 
23515
23525
  _defineProperty(this, "getInputPaths", () => {
@@ -23577,9 +23587,8 @@ class NumericInput extends React.Component {
23577
23587
  }
23578
23588
 
23579
23589
  static getOneCorrectAnswerFromRubric(rubric) {
23580
- var correctAnswers = _.filter(rubric.answers, answer => answer.status === "correct");
23581
-
23582
- var answerStrings = _.map(correctAnswers, answer => {
23590
+ var correctAnswers = rubric.answers.filter(answer => answer.status === "correct");
23591
+ var answerStrings = correctAnswers.map(answer => {
23583
23592
  // Figure out how this answer is supposed to be
23584
23593
  // displayed
23585
23594
  var format = "decimal";
@@ -23609,31 +23618,30 @@ class NumericInput extends React.Component {
23609
23618
  }
23610
23619
 
23611
23620
  static validate(useInput, rubric) {
23612
- var allAnswerForms = _.pluck(answerFormButtons, "value");
23613
-
23614
- var createValidator = answer => KhanAnswerTypes.number.createValidatorFunctional(answer.value, {
23615
- message: answer.message,
23616
- simplify: answer.status === "correct" ? answer.simplify : "optional",
23617
- inexact: true,
23618
- // TODO(merlob) backfill / delete
23619
- maxError: answer.maxError,
23620
- forms: answer.strict && answer.answerForms && answer.answerForms.length !== 0 ? answer.answerForms : allAnswerForms
23621
- }); // We may have received TeX; try to parse it before grading.
23621
+ var allAnswerForms = answerFormButtons.map(e => e["value"]);
23622
+
23623
+ var createValidator = answer => {
23624
+ var stringAnswer = "".concat(answer.value);
23625
+ return KhanAnswerTypes.number.createValidatorFunctional(stringAnswer, {
23626
+ message: answer.message,
23627
+ simplify: answer.status === "correct" ? answer.simplify : "optional",
23628
+ inexact: true,
23629
+ // TODO(merlob) backfill / delete
23630
+ maxError: answer.maxError,
23631
+ forms: answer.strict && answer.answerForms && answer.answerForms.length !== 0 ? answer.answerForms : allAnswerForms
23632
+ });
23633
+ }; // We may have received TeX; try to parse it before grading.
23622
23634
  // If `currentValue` is not TeX, this should be a no-op.
23623
23635
 
23624
23636
 
23625
23637
  var currentValue = ParseTex(useInput.currentValue);
23626
-
23627
- var correctAnswers = _.where(rubric.answers, {
23628
- status: "correct"
23629
- });
23630
-
23638
+ var correctAnswers = rubric.answers.filter(answer => answer.status === "correct");
23631
23639
  var normalizedAnswerExpected = correctAnswers.every(answer => Math.abs(answer.value) <= 1); // Look through all correct answers for one that matches either
23632
23640
  // precisely or approximately and return the appropriate message:
23633
23641
  // - if precise, return the message that the answer came with
23634
23642
  // - if it needs to be simplified, etc., show that message
23635
23643
 
23636
- var result = _.find(_.map(correctAnswers, answer => {
23644
+ var result = correctAnswers.map(answer => {
23637
23645
  // The coefficient is an attribute of the widget
23638
23646
  var localValue = currentValue;
23639
23647
 
@@ -23647,22 +23655,17 @@ class NumericInput extends React.Component {
23647
23655
 
23648
23656
  var validate = createValidator(answer);
23649
23657
  return validate(maybeParsePercentInput(localValue, normalizedAnswerExpected));
23650
- }), match => match.correct || match.empty);
23658
+ }).find(match => match.correct || match.empty);
23651
23659
 
23652
23660
  if (!result) {
23653
23661
  // Otherwise, if the guess is not correct
23654
- var otherAnswers = [].concat(_.where(rubric.answers, {
23655
- status: "ungraded"
23656
- }), _.where(rubric.answers, {
23657
- status: "wrong"
23658
- })); // Look through all other answers and if one matches either
23662
+ var otherAnswers = [].concat(rubric.answers.filter(answer => answer.status === "ungraded"), rubric.answers.filter(answer => answer.status === "wrong")); // Look through all other answers and if one matches either
23659
23663
  // precisely or approximately return the answer's message
23660
23664
 
23661
- var match = _.find(otherAnswers, answer => {
23665
+ var match = otherAnswers.find(answer => {
23662
23666
  var validate = createValidator(answer);
23663
23667
  return validate(maybeParsePercentInput(currentValue, normalizedAnswerExpected)).correct;
23664
23668
  });
23665
-
23666
23669
  result = {
23667
23670
  empty: match ? match.status === "ungraded" : false,
23668
23671
  correct: match ? match.status === "correct" : false,
@@ -23709,9 +23712,8 @@ class NumericInput extends React.Component {
23709
23712
 
23710
23713
  if (this.props.apiOptions.customKeypad) {
23711
23714
  // TODO(charlie): Support "Review Mode".
23712
- return maybeRightAlignKeypadInput( /*#__PURE__*/React.createElement(SimpleKeypadInput // eslint-disable-next-line react/no-string-refs
23713
- , {
23714
- ref: "input",
23715
+ return maybeRightAlignKeypadInput( /*#__PURE__*/React.createElement(SimpleKeypadInput, {
23716
+ ref: _ref => this.inputRef = _ref,
23715
23717
  value: this.props.currentValue,
23716
23718
  keypadElement: this.props.keypadElement,
23717
23719
  onChange: this.handleChange,
@@ -23720,9 +23722,8 @@ class NumericInput extends React.Component {
23720
23722
  }));
23721
23723
  }
23722
23724
 
23723
- var input = /*#__PURE__*/React.createElement(InputWithExamples // eslint-disable-next-line react/no-string-refs
23724
- , {
23725
- ref: "input",
23725
+ var input = /*#__PURE__*/React.createElement(InputWithExamples, {
23726
+ ref: _ref2 => this.inputRef = _ref2,
23726
23727
  value: this.props.currentValue,
23727
23728
  onChange: this.handleChange,
23728
23729
  className: classNames(classes),
@@ -23789,7 +23790,7 @@ var unionAnswerForms = function unionAnswerForms(answerFormsList) {
23789
23790
  // two elements are equal, and returns a list of unique elements. This is
23790
23791
  // just a helper function here, but works generally.
23791
23792
  var uniqueBy = function uniqueBy(list, iteratee) {
23792
- return _.reduce(list, (uniqueList, element) => {
23793
+ return list.reduce((uniqueList, element) => {
23793
23794
  // For each element, decide whether it's already in the list of
23794
23795
  // unique items.
23795
23796
  var inList = _.find(uniqueList, iteratee.bind(null, element));
@@ -23803,13 +23804,13 @@ var unionAnswerForms = function unionAnswerForms(answerFormsList) {
23803
23804
  }; // Pull out all of the forms from the different lists.
23804
23805
 
23805
23806
 
23806
- var allForms = _.flatten(answerFormsList); // Pull out the unique forms using uniqueBy.
23807
-
23807
+ var allForms = answerFormsList.flat(); // Pull out the unique forms using uniqueBy.
23808
23808
 
23809
23809
  var uniqueForms = uniqueBy(allForms, _.isEqual); // Sort them by the order they appear in the `formExamples` list.
23810
23810
 
23811
+ var formExampleKeys = Object.keys(formExamples);
23811
23812
  return _.sortBy(uniqueForms, form => {
23812
- return _.keys(formExamples).indexOf(form.name);
23813
+ return formExampleKeys.indexOf(form.name);
23813
23814
  });
23814
23815
  };
23815
23816
  // This function checks if the user inputted a percent value, parsing
@@ -23818,7 +23819,7 @@ var unionAnswerForms = function unionAnswerForms(answerFormsList) {
23818
23819
  // can accept several input forms with or without "%", the decision
23819
23820
  // to parse based on the presence of "%" in the input, is so that we
23820
23821
  // don't accidently scale the user typed value before grading, CP-930.
23821
- var maybeParsePercentInput = (inputValue, normalizedAnswerExpected) => {
23822
+ function maybeParsePercentInput(inputValue, normalizedAnswerExpected) {
23822
23823
  // If the input value is not a string ending with "%", then there's
23823
23824
  // nothing more to do. The value will be graded as inputted by user.
23824
23825
  if (!(typeof inputValue === "string" && inputValue.endsWith("%"))) {
@@ -23843,13 +23844,13 @@ var maybeParsePercentInput = (inputValue, normalizedAnswerExpected) => {
23843
23844
 
23844
23845
 
23845
23846
  return value;
23846
- };
23847
+ }
23847
23848
 
23848
23849
  var propsTransform = function propsTransform(widgetOptions) {
23849
23850
  var rendererProps = _.extend(_.omit(widgetOptions, "answers"), {
23850
23851
  answerForms: unionAnswerForms( // Pull out the name of each form and whether that form has
23851
23852
  // required simplification.
23852
- _.map(widgetOptions.answers, answer => {
23853
+ widgetOptions.answers.map(answer => {
23853
23854
  return _.map(answer.answerForms, form => {
23854
23855
  return {
23855
23856
  simplify: answer.simplify,
@@ -24393,6 +24394,66 @@ _defineProperty(Icon, "defaultProps", {
24393
24394
  color: "currentColor"
24394
24395
  });
24395
24396
 
24397
+ function getA11yText(letter, checked, correct, crossedOut, showCorrectness) {
24398
+ // There are two pieces of metadata we want to add to each a11yText:
24399
+ // whether the answer was checked/crossed-out/neither, and whether the
24400
+ // answer is correct/incorrect/not-yet-revealed.
24401
+ //
24402
+ // Translation is tricky for cross-product situations like this, so
24403
+ // we've just enumerated all 9 possibilities as separate strings.
24404
+ if (showCorrectness && correct) {
24405
+ if (checked) {
24406
+ return i18n._("(Choice %(letter)s, Checked, Correct)", {
24407
+ letter
24408
+ });
24409
+ }
24410
+
24411
+ if (crossedOut) {
24412
+ return i18n._("(Choice %(letter)s, Crossed out, Correct)", {
24413
+ letter
24414
+ });
24415
+ }
24416
+
24417
+ return i18n._("(Choice %(letter)s, Correct)", {
24418
+ letter
24419
+ });
24420
+ }
24421
+
24422
+ if (showCorrectness && !correct) {
24423
+ if (checked) {
24424
+ return i18n._("(Choice %(letter)s, Checked, Incorrect)", {
24425
+ letter
24426
+ });
24427
+ }
24428
+
24429
+ if (crossedOut) {
24430
+ return i18n._("(Choice %(letter)s, Crossed out, Incorrect)", {
24431
+ letter
24432
+ });
24433
+ }
24434
+
24435
+ return i18n._("(Choice %(letter)s, Incorrect)", {
24436
+ letter
24437
+ });
24438
+ }
24439
+
24440
+ if (checked) {
24441
+ return i18n._("(Choice %(letter)s, Checked)", {
24442
+ letter
24443
+ });
24444
+ }
24445
+
24446
+ if (crossedOut) {
24447
+ return i18n._("(Choice %(letter)s, Crossed out)", {
24448
+ letter
24449
+ });
24450
+ }
24451
+
24452
+ return i18n._("(Choice %(letter)s)", {
24453
+ letter
24454
+ });
24455
+ }
24456
+
24396
24457
  /**
24397
24458
  * Given a choice's position in the radio widget, return the corresponding
24398
24459
  * "choice letter". (For example, in English, `getChoiceLetter(0)` is "A",
@@ -24579,7 +24640,6 @@ function getDynamicStyles(checked, showCorrectness, pressed, multipleSelect, pri
24579
24640
 
24580
24641
  function LibraryChoiceIcon(props) {
24581
24642
  var {
24582
- a11yText,
24583
24643
  checked,
24584
24644
  crossedOut,
24585
24645
  showCorrectness,
@@ -24607,15 +24667,11 @@ function LibraryChoiceIcon(props) {
24607
24667
  // radio icon
24608
24668
  ,
24609
24669
  "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
24670
  }, /*#__PURE__*/React.createElement(ChoiceInner, {
24615
24671
  letter: letter,
24616
24672
  showCorrectness: showCorrectness,
24617
24673
  correct: correct
24618
- })))), crossedOut && /*#__PURE__*/React.createElement(CrossOutLine, {
24674
+ }))), crossedOut && /*#__PURE__*/React.createElement(CrossOutLine, {
24619
24675
  color: dynamicStyles.borderColor
24620
24676
  }));
24621
24677
  }
@@ -24705,7 +24761,6 @@ function constructSATStyles(reviewMode, correct, checked, multipleSelect) {
24705
24761
  function SATChoiceIcon(props) {
24706
24762
  var {
24707
24763
  letter,
24708
- a11yText,
24709
24764
  crossedOut,
24710
24765
  reviewMode,
24711
24766
  correct,
@@ -24733,11 +24788,7 @@ function SATChoiceIcon(props) {
24733
24788
  color
24734
24789
  },
24735
24790
  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, {
24791
+ }, /*#__PURE__*/React.createElement("span", null, letter)), crossedOut && /*#__PURE__*/React.createElement(CrossOutLine, {
24741
24792
  color: borderColor,
24742
24793
  sat: true
24743
24794
  }));
@@ -24774,66 +24825,6 @@ var styles$u = StyleSheet.create({
24774
24825
 
24775
24826
  /* Component for rendering a letter icon in radio choice */
24776
24827
 
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
24828
  function ChoiceIcon(props) {
24838
24829
  var {
24839
24830
  pos,
@@ -24852,12 +24843,10 @@ function ChoiceIcon(props) {
24852
24843
  transparentBackground
24853
24844
  } = props;
24854
24845
  var letter = getChoiceLetter(pos);
24855
- var a11yText = getA11yText(letter, checked, correct, crossedOut, showCorrectness);
24856
24846
 
24857
24847
  if (product === "sat") {
24858
24848
  return /*#__PURE__*/React.createElement(SATChoiceIcon, {
24859
24849
  letter: letter,
24860
- a11yText: a11yText,
24861
24850
  reviewMode: reviewMode,
24862
24851
  checked: checked,
24863
24852
  correct: correct,
@@ -24868,7 +24857,6 @@ function ChoiceIcon(props) {
24868
24857
 
24869
24858
  return /*#__PURE__*/React.createElement(LibraryChoiceIcon, {
24870
24859
  letter: letter,
24871
- a11yText: a11yText,
24872
24860
  reviewMode: reviewMode,
24873
24861
  checked: checked,
24874
24862
  crossedOut: crossedOut,
@@ -24979,6 +24967,15 @@ var ellipsisHorizontalIcon = {
24979
24967
  width: 100,
24980
24968
  height: 27.284
24981
24969
  };
24970
+ // Note(TB): Received errors when using useUniqueIdWithMock
24971
+ // so created this workaround function. Will update when
24972
+ // useUniqueIdWithMock is available.
24973
+ // https://khanacademy.atlassian.net/browse/FEI-4861?atlOrigin=eyJpIjoiNDJlZWMwNjM1NWJhNDBkMWFjY2FmN2I0ZjcxZmQxOGUiLCJwIjoiaiJ9
24974
+ var id = 0;
24975
+
24976
+ function uniqueId() {
24977
+ return "choice-".concat(id++);
24978
+ }
24982
24979
 
24983
24980
  function Choice(props) {
24984
24981
  var {
@@ -25035,6 +25032,9 @@ function Choice(props) {
25035
25032
  // content library.
25036
25033
 
25037
25034
  var showDimmed = !apiOptions.satStyling && !reviewMode && apiOptions.readOnly || crossedOut;
25035
+ var letter = getChoiceLetter(pos);
25036
+ var a11yText = getA11yText(letter, checked, correct, crossedOut, showCorrectness);
25037
+ var choiceId = uniqueId();
25038
25038
  return /*#__PURE__*/React.createElement("div", {
25039
25039
  style: {
25040
25040
  dispay: "flex",
@@ -25047,7 +25047,26 @@ function Choice(props) {
25047
25047
  width: "100%",
25048
25048
  opacity: showDimmed ? 0.5 : 1.0
25049
25049
  }
25050
- }, /*#__PURE__*/React.createElement(Clickable, {
25050
+ }, /*#__PURE__*/React.createElement("div", {
25051
+ className: "perseus-sr-only"
25052
+ }, /*#__PURE__*/React.createElement("input", {
25053
+ type: multipleSelect ? "checkbox" : "radio",
25054
+ id: choiceId,
25055
+ checked: checked,
25056
+ onClick: () => {
25057
+ // If we're checking a crossed-out option, let's
25058
+ // also uncross it.
25059
+ sendChange({
25060
+ checked: !checked,
25061
+ crossedOut: false
25062
+ });
25063
+ },
25064
+ onChange: () => {},
25065
+ disabled: disabled || apiOptions.staticRender || apiOptions.readOnly,
25066
+ tabIndex: -1
25067
+ }), /*#__PURE__*/React.createElement("label", {
25068
+ htmlFor: choiceId
25069
+ }, a11yText, " \xA0 ", content)), /*#__PURE__*/React.createElement(Clickable, {
25051
25070
  onClick: () => {
25052
25071
  // If we're checking a crossed-out option, let's
25053
25072
  // also uncross it.
@@ -25058,13 +25077,11 @@ function Choice(props) {
25058
25077
  },
25059
25078
  className: descriptionClassName,
25060
25079
  disabled: disabled || apiOptions.staticRender || apiOptions.readOnly,
25061
- "aria-label": "Select Choice ".concat(getChoiceLetter(pos)),
25062
- "aria-checked": checked ? "true" : "false",
25063
- role: "checkbox",
25064
25080
  style: {
25065
25081
  flex: 1
25066
25082
  },
25067
- ref: forwardedRef
25083
+ ref: forwardedRef,
25084
+ "aria-hidden": "true"
25068
25085
  }, _ref => {
25069
25086
  var _apiOptions$styling;
25070
25087
 
@@ -25409,7 +25426,8 @@ function BaseRadio(props) {
25409
25426
  }, /*#__PURE__*/React.createElement("legend", {
25410
25427
  className: "perseus-sr-only"
25411
25428
  }, instructions), shouldShowInstructions && /*#__PURE__*/React.createElement("div", {
25412
- className: instructionsClassName
25429
+ className: instructionsClassName,
25430
+ "aria-hidden": "true"
25413
25431
  }, instructions), /*#__PURE__*/React.createElement("ul", {
25414
25432
  className: className,
25415
25433
  style: {
@@ -26685,7 +26703,7 @@ class Definition extends React.Component {
26685
26703
  }, /*#__PURE__*/React.createElement("span", {
26686
26704
  className: "perseus-widget-definition"
26687
26705
  }, /*#__PURE__*/React.createElement(Button, {
26688
- size: "small",
26706
+ size: "medium",
26689
26707
  kind: "tertiary",
26690
26708
  onClick: () => {
26691
26709
  this.props.trackInteraction();
@@ -28902,6 +28920,8 @@ class Group extends React.Component {
28902
28920
  super(...arguments);
28903
28921
  _this = this;
28904
28922
 
28923
+ _defineProperty(this, "rendererRef", void 0);
28924
+
28905
28925
  _defineProperty(this, "change", function () {
28906
28926
  for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
28907
28927
  args[_key] = arguments[_key];
@@ -28911,56 +28931,66 @@ class Group extends React.Component {
28911
28931
  });
28912
28932
 
28913
28933
  _defineProperty(this, "getUserInput", () => {
28914
- // eslint-disable-next-line react/no-string-refs
28915
- return this.refs.renderer.getUserInput();
28934
+ var _this$rendererRef;
28935
+
28936
+ return (_this$rendererRef = this.rendererRef) === null || _this$rendererRef === void 0 ? void 0 : _this$rendererRef.getUserInput();
28916
28937
  });
28917
28938
 
28918
28939
  _defineProperty(this, "getSerializedState", () => {
28919
- // eslint-disable-next-line react/no-string-refs
28920
- return this.refs.renderer.getSerializedState();
28940
+ var _this$rendererRef2;
28941
+
28942
+ return (_this$rendererRef2 = this.rendererRef) === null || _this$rendererRef2 === void 0 ? void 0 : _this$rendererRef2.getSerializedState();
28921
28943
  });
28922
28944
 
28923
28945
  _defineProperty(this, "restoreSerializedState", (state, callback) => {
28924
- // eslint-disable-next-line react/no-string-refs
28925
- this.refs.renderer.restoreSerializedState(state, callback); // Tell our renderer that we have no props to change
28946
+ var _this$rendererRef3;
28947
+
28948
+ (_this$rendererRef3 = this.rendererRef) === null || _this$rendererRef3 === void 0 ? void 0 : _this$rendererRef3.restoreSerializedState(state, callback); // Tell our renderer that we have no props to change
28926
28949
  // (all our changes were in state):
28927
28950
 
28928
28951
  return null;
28929
28952
  });
28930
28953
 
28931
28954
  _defineProperty(this, "simpleValidate", rubric => {
28932
- // eslint-disable-next-line react/no-string-refs
28933
- return this.refs.renderer.score();
28955
+ var _this$rendererRef4;
28956
+
28957
+ return (_this$rendererRef4 = this.rendererRef) === null || _this$rendererRef4 === void 0 ? void 0 : _this$rendererRef4.score();
28934
28958
  });
28935
28959
 
28936
28960
  _defineProperty(this, "getInputPaths", () => {
28937
- // eslint-disable-next-line react/no-string-refs
28938
- return this.refs.renderer.getInputPaths();
28961
+ var _this$rendererRef5;
28962
+
28963
+ return (_this$rendererRef5 = this.rendererRef) === null || _this$rendererRef5 === void 0 ? void 0 : _this$rendererRef5.getInputPaths();
28939
28964
  });
28940
28965
 
28941
- _defineProperty(this, "setInputValue", (path, newValue, cb) => {
28942
- // eslint-disable-next-line react/no-string-refs
28943
- return this.refs.renderer.setInputValue(path, newValue, cb);
28966
+ _defineProperty(this, "setInputValue", (path, newValue, callback) => {
28967
+ var _this$rendererRef6;
28968
+
28969
+ return (_this$rendererRef6 = this.rendererRef) === null || _this$rendererRef6 === void 0 ? void 0 : _this$rendererRef6.setInputValue(path, newValue, callback);
28944
28970
  });
28945
28971
 
28946
28972
  _defineProperty(this, "focus", () => {
28947
- // eslint-disable-next-line react/no-string-refs
28948
- return this.refs.renderer.focus();
28973
+ var _this$rendererRef7;
28974
+
28975
+ return (_this$rendererRef7 = this.rendererRef) === null || _this$rendererRef7 === void 0 ? void 0 : _this$rendererRef7.focus();
28949
28976
  });
28950
28977
 
28951
28978
  _defineProperty(this, "focusInputPath", path => {
28952
- // eslint-disable-next-line react/no-string-refs
28953
- this.refs.renderer.focusPath(path);
28979
+ var _this$rendererRef8;
28980
+
28981
+ (_this$rendererRef8 = this.rendererRef) === null || _this$rendererRef8 === void 0 ? void 0 : _this$rendererRef8.focusPath(path);
28954
28982
  });
28955
28983
 
28956
28984
  _defineProperty(this, "blurInputPath", path => {
28957
- // eslint-disable-next-line react/no-string-refs
28958
- this.refs.renderer.blurPath(path);
28985
+ var _this$rendererRef9;
28986
+
28987
+ (_this$rendererRef9 = this.rendererRef) === null || _this$rendererRef9 === void 0 ? void 0 : _this$rendererRef9.blurPath(path);
28959
28988
  });
28960
28989
 
28961
28990
  _defineProperty(this, "showRationalesForCurrentlySelectedChoices", () => {
28962
- // eslint-disable-next-line react/no-string-refs
28963
- this.refs.renderer.showRationalesForCurrentlySelectedChoices();
28991
+ var _this$rendererRef10;
28992
+
28993
+ (_this$rendererRef10 = this.rendererRef) === null || _this$rendererRef10 === void 0 ? void 0 : _this$rendererRef10.showRationalesForCurrentlySelectedChoices();
28964
28994
  });
28965
28995
  }
28966
28996
 
@@ -28972,7 +29002,9 @@ class Group extends React.Component {
28972
29002
  }
28973
29003
 
28974
29004
  render() {
28975
- var apiOptions = _.extend({}, ApiOptions.defaults, this.props.apiOptions, {
29005
+ var _this$rendererRef11;
29006
+
29007
+ var apiOptions = _objectSpread2(_objectSpread2(_objectSpread2({}, ApiOptions.defaults), this.props.apiOptions), {}, {
28976
29008
  // Api Rewriting to support correct onFocus/onBlur
28977
29009
  // events for the mobile API
28978
29010
  onFocusChange: (newFocus, oldFocus) => {
@@ -28994,8 +29026,8 @@ class Group extends React.Component {
28994
29026
  // really we should have a more unidirectional flow. TODO(marcia): fix.
28995
29027
 
28996
29028
 
28997
- var number = _.indexOf(this.props.findWidgets("group"), this);
28998
-
29029
+ var groupWidgets = this.props.findWidgets("group");
29030
+ var number = groupWidgets.indexOf(this);
28999
29031
  var problemNumComponent = this.props.apiOptions.groupAnnotator(number, this.props.widgetId); // This is a little strange because the id of the widget that actually
29000
29032
  // changed is going to be lost in favor of the group widget's id. The
29001
29033
  // widgets prop also wasn't actually changed, and this only serves to
@@ -29003,15 +29035,12 @@ class Group extends React.Component {
29003
29035
  // has occurred.
29004
29036
 
29005
29037
  var onInteractWithWidget = id => {
29006
- // eslint-disable-next-line react/no-string-refs
29007
- if (this.refs.renderer) {
29008
- // eslint-disable-next-line react/no-string-refs
29009
- this.change("widgets", this.refs.renderer.props.widgets);
29038
+ if (this.rendererRef) {
29039
+ this.change("widgets", this.rendererRef.props.widgets);
29010
29040
  }
29011
- }; // eslint-disable-next-line react/no-string-refs
29012
-
29041
+ };
29013
29042
 
29014
- var score = this.refs.renderer && this.refs.renderer.score();
29043
+ var score = (_this$rendererRef11 = this.rendererRef) === null || _this$rendererRef11 === void 0 ? void 0 : _this$rendererRef11.score();
29015
29044
  var isValid = score && score.type !== "invalid";
29016
29045
  var isInvalid = score && score.type === "invalid"; // TODO(mdr): Widgets inside this Renderer are not discoverable through
29017
29046
  // the parent Renderer's `findWidgets` function.
@@ -29022,15 +29051,17 @@ class Group extends React.Component {
29022
29051
  "perseus-group-valid-answer": isValid,
29023
29052
  "perseus-group-invalid-answer": isInvalid
29024
29053
  })
29025
- }, problemNumComponent, /*#__PURE__*/React.createElement(Renderer, _extends({}, this.props, {
29026
- // eslint-disable-next-line react/no-string-refs
29027
- ref: "renderer",
29054
+ }, problemNumComponent, /*#__PURE__*/React.createElement(Renderer, {
29055
+ content: this.props.content,
29056
+ widgets: this.props.widgets,
29057
+ images: this.props.images,
29058
+ ref: _ref => this.rendererRef = _ref,
29028
29059
  apiOptions: apiOptions,
29029
29060
  findExternalWidgets: this.props.findWidgets,
29030
29061
  reviewMode: !!this.props.reviewModeRubric,
29031
29062
  onInteractWithWidget: onInteractWithWidget,
29032
29063
  linterContext: this.props.linterContext
29033
- })), this.props.icon && /*#__PURE__*/React.createElement("div", {
29064
+ }), this.props.icon && /*#__PURE__*/React.createElement("div", {
29034
29065
  className: "group-icon"
29035
29066
  }, this.props.icon));
29036
29067
  }
@@ -29045,7 +29076,7 @@ _defineProperty(Group, "defaultProps", {
29045
29076
  });
29046
29077
 
29047
29078
  var traverseChildWidgets$1 = function traverseChildWidgets(props, traverseRenderer) {
29048
- return _.extend({}, props, traverseRenderer(props));
29079
+ return _objectSpread2(_objectSpread2({}, props), traverseRenderer(props));
29049
29080
  };
29050
29081
 
29051
29082
  var Group$1 = {