@khanacademy/perseus 1.0.0 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/es/index.css +17 -8
  3. package/dist/es/index.js +258 -223
  4. package/dist/es/index.js.map +1 -1
  5. package/dist/index.css +17 -8
  6. package/dist/index.js +261 -225
  7. package/dist/index.js.map +1 -1
  8. package/package.json +1 -1
  9. package/src/components/__stories__/input-with-examples.stories.jsx +1 -0
  10. package/src/components/input-with-examples.jsx +41 -24
  11. package/src/components/number-input.jsx +2 -2
  12. package/src/components/simple-keypad-input.jsx +18 -21
  13. package/src/components/text-input.jsx +5 -3
  14. package/src/index.js +1 -1
  15. package/src/multi-items/__tests__/__snapshots__/multi-renderer_test.js.snap +356 -65
  16. package/src/multi-items/__tests__/multi-renderer_test.js +9 -9
  17. package/src/perseus-types.js +18 -7
  18. package/src/styles/perseus-renderer.less +8 -2
  19. package/src/styles/widgets/matrix.less +2 -0
  20. package/src/styles/widgets/passage.less +9 -5
  21. package/src/util/answer-types.js +2 -3
  22. package/src/util/math.js +3 -10
  23. package/src/widgets/__stories__/numeric-input.stories.jsx +94 -7
  24. package/src/widgets/__tests__/__snapshots__/definition_test.js.snap +4 -4
  25. package/src/widgets/__tests__/__snapshots__/graded-group-set-jipt_test.jsx.snap +15 -6
  26. package/src/widgets/__tests__/__snapshots__/graded-group-set_test.jsx.snap +5 -2
  27. package/src/widgets/__tests__/__snapshots__/graded-group_test.jsx.snap +4 -1
  28. package/src/widgets/__tests__/__snapshots__/group_test.jsx.snap +228 -67
  29. package/src/widgets/__tests__/__snapshots__/matrix_test.jsx.snap +24 -24
  30. package/src/widgets/__tests__/__snapshots__/numeric-input_test.jsx.snap +4 -4
  31. package/src/widgets/__tests__/__snapshots__/radio_test.jsx.snap +2581 -679
  32. package/src/widgets/__tests__/group_test.jsx +4 -4
  33. package/src/widgets/__tests__/numeric-input_test.jsx +12 -8
  34. package/src/widgets/__tests__/passage_test.jsx +81 -0
  35. package/src/widgets/__tests__/radio/base-radio_test.jsx +55 -7
  36. package/src/widgets/__tests__/radio/choice_test.jsx +164 -63
  37. package/src/widgets/__tests__/radio_test.jsx +39 -15
  38. package/src/widgets/definition.jsx +1 -1
  39. package/src/widgets/graded-group.jsx +5 -1
  40. package/src/widgets/group.jsx +84 -102
  41. package/src/widgets/input-number.jsx +6 -1
  42. package/src/widgets/matrix.jsx +17 -1
  43. package/src/widgets/numeric-input.jsx +99 -86
  44. package/src/widgets/radio/base-radio.jsx +3 -1
  45. package/src/widgets/radio/choice-a11y-text.js +62 -0
  46. package/src/widgets/radio/choice-icon/choice-icon.jsx +0 -68
  47. package/src/widgets/radio/choice-icon/library-choice-icon.jsx +5 -10
  48. package/src/widgets/radio/choice-icon/sat-choice-icon.jsx +3 -12
  49. 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, {
@@ -21699,13 +21698,15 @@ class TextInput extends React.Component {
21699
21698
  onBlur,
21700
21699
  disabled,
21701
21700
  placeholder,
21702
- onKeyDown
21701
+ onKeyDown,
21702
+ style
21703
21703
  } = this.props;
21704
21704
  var formattedValue = value === null ? "" : value.toString();
21705
21705
  return (
21706
21706
  /*#__PURE__*/
21707
21707
  // $FlowIgnore
21708
21708
  React.createElement(TextField, {
21709
+ style: style,
21709
21710
  disabled: disabled,
21710
21711
  id: this.id,
21711
21712
  value: formattedValue,
@@ -22103,7 +22104,8 @@ class InputWithExamples extends React.Component {
22103
22104
  value: this.props.value,
22104
22105
  onFocus: this._handleFocus,
22105
22106
  onBlur: this._handleBlur,
22106
- disabled: this.props.disabled
22107
+ disabled: this.props.disabled,
22108
+ style: this.props.style
22107
22109
  };
22108
22110
 
22109
22111
  if (this.props.type === TEX) {
@@ -22237,32 +22239,14 @@ class InputWithExamples extends React.Component {
22237
22239
 
22238
22240
  }
22239
22241
 
22240
- _defineProperty(InputWithExamples, "propTypes", {
22241
- type: PropTypes.oneOf([MATH, TEXT, TEX]),
22242
- value: PropTypes.string,
22243
- onChange: PropTypes.func.isRequired,
22244
- className: PropTypes.string,
22245
- examples: PropTypes.arrayOf(PropTypes.string).isRequired,
22246
- shouldShowExamples: PropTypes.bool,
22247
- convertDotToTimes: PropTypes.bool,
22248
- buttonSet: PropTypes.string,
22249
- buttonsVisible: PropTypes.oneOf(["always", "never", "focused"]),
22250
- labelText: PropTypes.string,
22251
- onFocus: PropTypes.func,
22252
- onBlur: PropTypes.func,
22253
- disabled: PropTypes.bool,
22254
- // A unique string identifying this InputWithExamples
22255
- id: PropTypes.string.isRequired,
22256
- linterContext: PerseusLinter.linterContextProps
22257
- });
22258
-
22259
22242
  _defineProperty(InputWithExamples, "defaultProps", {
22260
22243
  type: TEXT,
22261
22244
  shouldShowExamples: true,
22262
22245
  onFocus: function onFocus() {},
22263
22246
  onBlur: function onBlur() {},
22264
22247
  disabled: false,
22265
- linterContext: PerseusLinter.linterContextDefault
22248
+ linterContext: PerseusLinter.linterContextDefault,
22249
+ className: ""
22266
22250
  });
22267
22251
 
22268
22252
  /**
@@ -22955,35 +22939,36 @@ _defineProperty(PossibleAnswers, "propTypes", {
22955
22939
  });
22956
22940
 
22957
22941
  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() {
22942
+ class SimpleKeypadInput extends React.Component {
22943
+ constructor() {
22944
+ super(...arguments);
22945
+
22946
+ _defineProperty(this, "_isMounted", false);
22947
+ }
22948
+
22949
+ componentDidMount() {
22966
22950
  // TODO(scottgrant): This is a hack to remove the deprecated call to
22967
22951
  // this.isMounted() but is still considered an anti-pattern.
22968
22952
  this._isMounted = true;
22969
- },
22970
- componentWillUnmount: function componentWillUnmount() {
22953
+ }
22954
+
22955
+ componentWillUnmount() {
22971
22956
  this._isMounted = false;
22972
- },
22957
+ }
22973
22958
 
22974
22959
  focus() {
22975
22960
  // $FlowFixMe[object-this-reference]
22976
22961
  this.refs.input.focus(); // eslint-disable-line react/no-string-refs
22977
- },
22962
+ }
22978
22963
 
22979
22964
  blur() {
22980
22965
  // $FlowFixMe[object-this-reference]
22981
22966
  this.refs.input.blur(); // eslint-disable-line react/no-string-refs
22982
- },
22967
+ }
22983
22968
 
22984
- getValue: function getValue() {
22969
+ getValue() {
22985
22970
  return this.props.value;
22986
- },
22971
+ }
22987
22972
 
22988
22973
  render() {
22989
22974
  // $FlowFixMe[object-this-reference]
@@ -23022,7 +23007,12 @@ var SimpleKeypadInput = createReactClass({
23022
23007
  }, rest));
23023
23008
  }
23024
23009
 
23025
- });
23010
+ }
23011
+ SimpleKeypadInput.propTypes = {
23012
+ keypadElement: keypadElementPropType,
23013
+ onFocus: PropTypes.func,
23014
+ value: PropTypes.oneOfType([PropTypes.string, PropTypes.number])
23015
+ };
23026
23016
 
23027
23017
  var ParseTex$1 = TexWrangler.parseTex;
23028
23018
  var answerTypes = {
@@ -23277,9 +23267,13 @@ class InputNumber extends React.Component {
23277
23267
 
23278
23268
  if (rubric.answerType == null) {
23279
23269
  rubric.answerType = "number";
23280
- }
23270
+ } // note(matthewc): this will get immediately parsed again by
23271
+ // `KhanAnswerTypes.number.convertToPredicate`, but a string is
23272
+ // expected here
23281
23273
 
23282
- var val = KhanAnswerTypes.number.createValidatorFunctional(rubric.value, {
23274
+
23275
+ var stringValue = "".concat(rubric.value);
23276
+ var val = KhanAnswerTypes.number.createValidatorFunctional(stringValue, {
23283
23277
  simplify: rubric.simplify,
23284
23278
  inexact: rubric.inexact || undefined,
23285
23279
  maxError: rubric.maxError,
@@ -23405,6 +23399,8 @@ class NumericInput extends React.Component {
23405
23399
  constructor() {
23406
23400
  super(...arguments);
23407
23401
 
23402
+ _defineProperty(this, "inputRef", void 0);
23403
+
23408
23404
  _defineProperty(this, "state", {
23409
23405
  // keeps track of the other set of values when switching
23410
23406
  // between 0 and finite solutions
@@ -23423,9 +23419,8 @@ class NumericInput extends React.Component {
23423
23419
  correct = score.type === "points" && score.earned === score.total;
23424
23420
 
23425
23421
  if (!correct) {
23426
- var correctAnswers = _.filter(rubric.answers, answer => answer.status === "correct");
23427
-
23428
- var answerStrings = _.map(correctAnswers, answer => {
23422
+ var correctAnswers = rubric.answers.filter(answer => answer.status === "correct");
23423
+ var answerStrings = correctAnswers.map(answer => {
23429
23424
  // Figure out how this answer is supposed to be
23430
23425
  // displayed
23431
23426
  var format = "decimal";
@@ -23446,7 +23441,6 @@ class NumericInput extends React.Component {
23446
23441
 
23447
23442
  return answerString;
23448
23443
  });
23449
-
23450
23444
  answerBlurb = /*#__PURE__*/React.createElement(PossibleAnswers, {
23451
23445
  answers: answerStrings
23452
23446
  });
@@ -23460,7 +23454,7 @@ class NumericInput extends React.Component {
23460
23454
  var _this$props$answerFor;
23461
23455
 
23462
23456
  // 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 => {
23457
+ 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
23458
  return {
23465
23459
  name: name,
23466
23460
  simplify: "required"
@@ -23486,8 +23480,7 @@ class NumericInput extends React.Component {
23486
23480
 
23487
23481
  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
23482
 
23489
- var allFormsAccepted = answerFormNames.length >= _.size(formExamples);
23490
-
23483
+ var allFormsAccepted = answerFormNames.length >= Object.keys(formExamples).length;
23491
23484
  return !noFormsAccepted && !allFormsAccepted;
23492
23485
  });
23493
23486
 
@@ -23496,20 +23489,22 @@ class NumericInput extends React.Component {
23496
23489
  });
23497
23490
 
23498
23491
  _defineProperty(this, "focus", () => {
23499
- // eslint-disable-next-line react/no-string-refs
23500
- this.refs.input.focus();
23492
+ var _this$inputRef;
23493
+
23494
+ (_this$inputRef = this.inputRef) === null || _this$inputRef === void 0 ? void 0 : _this$inputRef.focus();
23501
23495
  return true;
23502
23496
  });
23503
23497
 
23504
- _defineProperty(this, "focusInputPath", inputPath => {
23505
- /* istanbul ignore next */
23506
- // eslint-disable-next-line react/no-string-refs
23507
- this.refs.input.focus();
23498
+ _defineProperty(this, "focusInputPath", () => {
23499
+ var _this$inputRef2;
23500
+
23501
+ (_this$inputRef2 = this.inputRef) === null || _this$inputRef2 === void 0 ? void 0 : _this$inputRef2.focus();
23508
23502
  });
23509
23503
 
23510
- _defineProperty(this, "blurInputPath", inputPath => {
23511
- // eslint-disable-next-line react/no-string-refs
23512
- this.refs.input.blur();
23504
+ _defineProperty(this, "blurInputPath", () => {
23505
+ var _this$inputRef3;
23506
+
23507
+ (_this$inputRef3 = this.inputRef) === null || _this$inputRef3 === void 0 ? void 0 : _this$inputRef3.blur();
23513
23508
  });
23514
23509
 
23515
23510
  _defineProperty(this, "getInputPaths", () => {
@@ -23577,9 +23572,8 @@ class NumericInput extends React.Component {
23577
23572
  }
23578
23573
 
23579
23574
  static getOneCorrectAnswerFromRubric(rubric) {
23580
- var correctAnswers = _.filter(rubric.answers, answer => answer.status === "correct");
23581
-
23582
- var answerStrings = _.map(correctAnswers, answer => {
23575
+ var correctAnswers = rubric.answers.filter(answer => answer.status === "correct");
23576
+ var answerStrings = correctAnswers.map(answer => {
23583
23577
  // Figure out how this answer is supposed to be
23584
23578
  // displayed
23585
23579
  var format = "decimal";
@@ -23609,31 +23603,30 @@ class NumericInput extends React.Component {
23609
23603
  }
23610
23604
 
23611
23605
  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.
23606
+ var allAnswerForms = answerFormButtons.map(e => e["value"]);
23607
+
23608
+ var createValidator = answer => {
23609
+ var stringAnswer = "".concat(answer.value);
23610
+ return KhanAnswerTypes.number.createValidatorFunctional(stringAnswer, {
23611
+ message: answer.message,
23612
+ simplify: answer.status === "correct" ? answer.simplify : "optional",
23613
+ inexact: true,
23614
+ // TODO(merlob) backfill / delete
23615
+ maxError: answer.maxError,
23616
+ forms: answer.strict && answer.answerForms && answer.answerForms.length !== 0 ? answer.answerForms : allAnswerForms
23617
+ });
23618
+ }; // We may have received TeX; try to parse it before grading.
23622
23619
  // If `currentValue` is not TeX, this should be a no-op.
23623
23620
 
23624
23621
 
23625
23622
  var currentValue = ParseTex(useInput.currentValue);
23626
-
23627
- var correctAnswers = _.where(rubric.answers, {
23628
- status: "correct"
23629
- });
23630
-
23623
+ var correctAnswers = rubric.answers.filter(answer => answer.status === "correct");
23631
23624
  var normalizedAnswerExpected = correctAnswers.every(answer => Math.abs(answer.value) <= 1); // Look through all correct answers for one that matches either
23632
23625
  // precisely or approximately and return the appropriate message:
23633
23626
  // - if precise, return the message that the answer came with
23634
23627
  // - if it needs to be simplified, etc., show that message
23635
23628
 
23636
- var result = _.find(_.map(correctAnswers, answer => {
23629
+ var result = correctAnswers.map(answer => {
23637
23630
  // The coefficient is an attribute of the widget
23638
23631
  var localValue = currentValue;
23639
23632
 
@@ -23647,22 +23640,17 @@ class NumericInput extends React.Component {
23647
23640
 
23648
23641
  var validate = createValidator(answer);
23649
23642
  return validate(maybeParsePercentInput(localValue, normalizedAnswerExpected));
23650
- }), match => match.correct || match.empty);
23643
+ }).find(match => match.correct || match.empty);
23651
23644
 
23652
23645
  if (!result) {
23653
23646
  // 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
23647
+ 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
23648
  // precisely or approximately return the answer's message
23660
23649
 
23661
- var match = _.find(otherAnswers, answer => {
23650
+ var match = otherAnswers.find(answer => {
23662
23651
  var validate = createValidator(answer);
23663
23652
  return validate(maybeParsePercentInput(currentValue, normalizedAnswerExpected)).correct;
23664
23653
  });
23665
-
23666
23654
  result = {
23667
23655
  empty: match ? match.status === "ungraded" : false,
23668
23656
  correct: match ? match.status === "correct" : false,
@@ -23693,7 +23681,6 @@ class NumericInput extends React.Component {
23693
23681
  var rubric = this.props.reviewModeRubric;
23694
23682
  var answers = this.getAnswerBlurb(rubric);
23695
23683
  var answerBlurb = answers[0];
23696
- var classes = [];
23697
23684
  var labelText = this.props.labelText;
23698
23685
 
23699
23686
  if (labelText == null || labelText === "") {
@@ -23709,9 +23696,8 @@ class NumericInput extends React.Component {
23709
23696
 
23710
23697
  if (this.props.apiOptions.customKeypad) {
23711
23698
  // TODO(charlie): Support "Review Mode".
23712
- return maybeRightAlignKeypadInput( /*#__PURE__*/React.createElement(SimpleKeypadInput // eslint-disable-next-line react/no-string-refs
23713
- , {
23714
- ref: "input",
23699
+ return maybeRightAlignKeypadInput( /*#__PURE__*/React.createElement(SimpleKeypadInput, {
23700
+ ref: _ref => this.inputRef = _ref,
23715
23701
  value: this.props.currentValue,
23716
23702
  keypadElement: this.props.keypadElement,
23717
23703
  onChange: this.handleChange,
@@ -23720,12 +23706,18 @@ class NumericInput extends React.Component {
23720
23706
  }));
23721
23707
  }
23722
23708
 
23723
- var input = /*#__PURE__*/React.createElement(InputWithExamples // eslint-disable-next-line react/no-string-refs
23724
- , {
23725
- ref: "input",
23709
+ var styles = StyleSheet.create({
23710
+ input: {
23711
+ textAlign: this.props.rightAlign ? "right" : "left",
23712
+ width: this.props.size === "small" ? 40 : 80,
23713
+ padding: 0,
23714
+ height: "auto"
23715
+ }
23716
+ });
23717
+ var input = /*#__PURE__*/React.createElement(InputWithExamples, {
23718
+ ref: _ref2 => this.inputRef = _ref2,
23726
23719
  value: this.props.currentValue,
23727
23720
  onChange: this.handleChange,
23728
- className: classNames(classes),
23729
23721
  labelText: labelText,
23730
23722
  type: this._getInputType(),
23731
23723
  examples: this.examples(),
@@ -23733,9 +23725,8 @@ class NumericInput extends React.Component {
23733
23725
  onFocus: this._handleFocus,
23734
23726
  onBlur: this._handleBlur,
23735
23727
  id: this.props.widgetId,
23736
- disabled: this.props.apiOptions.readOnly // $FlowFixMe[prop-missing]
23737
- ,
23738
- highlightLint: this.props.highlightLint
23728
+ disabled: this.props.apiOptions.readOnly,
23729
+ style: styles.input
23739
23730
  });
23740
23731
  /* This only gets used with SAT Content */
23741
23732
 
@@ -23789,7 +23780,7 @@ var unionAnswerForms = function unionAnswerForms(answerFormsList) {
23789
23780
  // two elements are equal, and returns a list of unique elements. This is
23790
23781
  // just a helper function here, but works generally.
23791
23782
  var uniqueBy = function uniqueBy(list, iteratee) {
23792
- return _.reduce(list, (uniqueList, element) => {
23783
+ return list.reduce((uniqueList, element) => {
23793
23784
  // For each element, decide whether it's already in the list of
23794
23785
  // unique items.
23795
23786
  var inList = _.find(uniqueList, iteratee.bind(null, element));
@@ -23803,13 +23794,13 @@ var unionAnswerForms = function unionAnswerForms(answerFormsList) {
23803
23794
  }; // Pull out all of the forms from the different lists.
23804
23795
 
23805
23796
 
23806
- var allForms = _.flatten(answerFormsList); // Pull out the unique forms using uniqueBy.
23807
-
23797
+ var allForms = answerFormsList.flat(); // Pull out the unique forms using uniqueBy.
23808
23798
 
23809
23799
  var uniqueForms = uniqueBy(allForms, _.isEqual); // Sort them by the order they appear in the `formExamples` list.
23810
23800
 
23801
+ var formExampleKeys = Object.keys(formExamples);
23811
23802
  return _.sortBy(uniqueForms, form => {
23812
- return _.keys(formExamples).indexOf(form.name);
23803
+ return formExampleKeys.indexOf(form.name);
23813
23804
  });
23814
23805
  };
23815
23806
  // This function checks if the user inputted a percent value, parsing
@@ -23818,7 +23809,7 @@ var unionAnswerForms = function unionAnswerForms(answerFormsList) {
23818
23809
  // can accept several input forms with or without "%", the decision
23819
23810
  // to parse based on the presence of "%" in the input, is so that we
23820
23811
  // don't accidently scale the user typed value before grading, CP-930.
23821
- var maybeParsePercentInput = (inputValue, normalizedAnswerExpected) => {
23812
+ function maybeParsePercentInput(inputValue, normalizedAnswerExpected) {
23822
23813
  // If the input value is not a string ending with "%", then there's
23823
23814
  // nothing more to do. The value will be graded as inputted by user.
23824
23815
  if (!(typeof inputValue === "string" && inputValue.endsWith("%"))) {
@@ -23843,13 +23834,13 @@ var maybeParsePercentInput = (inputValue, normalizedAnswerExpected) => {
23843
23834
 
23844
23835
 
23845
23836
  return value;
23846
- };
23837
+ }
23847
23838
 
23848
23839
  var propsTransform = function propsTransform(widgetOptions) {
23849
23840
  var rendererProps = _.extend(_.omit(widgetOptions, "answers"), {
23850
23841
  answerForms: unionAnswerForms( // Pull out the name of each form and whether that form has
23851
23842
  // required simplification.
23852
- _.map(widgetOptions.answers, answer => {
23843
+ widgetOptions.answers.map(answer => {
23853
23844
  return _.map(answer.answerForms, form => {
23854
23845
  return {
23855
23846
  simplify: answer.simplify,
@@ -24393,6 +24384,66 @@ _defineProperty(Icon, "defaultProps", {
24393
24384
  color: "currentColor"
24394
24385
  });
24395
24386
 
24387
+ function getA11yText(letter, checked, correct, crossedOut, showCorrectness) {
24388
+ // There are two pieces of metadata we want to add to each a11yText:
24389
+ // whether the answer was checked/crossed-out/neither, and whether the
24390
+ // answer is correct/incorrect/not-yet-revealed.
24391
+ //
24392
+ // Translation is tricky for cross-product situations like this, so
24393
+ // we've just enumerated all 9 possibilities as separate strings.
24394
+ if (showCorrectness && correct) {
24395
+ if (checked) {
24396
+ return i18n._("(Choice %(letter)s, Checked, Correct)", {
24397
+ letter
24398
+ });
24399
+ }
24400
+
24401
+ if (crossedOut) {
24402
+ return i18n._("(Choice %(letter)s, Crossed out, Correct)", {
24403
+ letter
24404
+ });
24405
+ }
24406
+
24407
+ return i18n._("(Choice %(letter)s, Correct)", {
24408
+ letter
24409
+ });
24410
+ }
24411
+
24412
+ if (showCorrectness && !correct) {
24413
+ if (checked) {
24414
+ return i18n._("(Choice %(letter)s, Checked, Incorrect)", {
24415
+ letter
24416
+ });
24417
+ }
24418
+
24419
+ if (crossedOut) {
24420
+ return i18n._("(Choice %(letter)s, Crossed out, Incorrect)", {
24421
+ letter
24422
+ });
24423
+ }
24424
+
24425
+ return i18n._("(Choice %(letter)s, Incorrect)", {
24426
+ letter
24427
+ });
24428
+ }
24429
+
24430
+ if (checked) {
24431
+ return i18n._("(Choice %(letter)s, Checked)", {
24432
+ letter
24433
+ });
24434
+ }
24435
+
24436
+ if (crossedOut) {
24437
+ return i18n._("(Choice %(letter)s, Crossed out)", {
24438
+ letter
24439
+ });
24440
+ }
24441
+
24442
+ return i18n._("(Choice %(letter)s)", {
24443
+ letter
24444
+ });
24445
+ }
24446
+
24396
24447
  /**
24397
24448
  * Given a choice's position in the radio widget, return the corresponding
24398
24449
  * "choice letter". (For example, in English, `getChoiceLetter(0)` is "A",
@@ -24579,7 +24630,6 @@ function getDynamicStyles(checked, showCorrectness, pressed, multipleSelect, pri
24579
24630
 
24580
24631
  function LibraryChoiceIcon(props) {
24581
24632
  var {
24582
- a11yText,
24583
24633
  checked,
24584
24634
  crossedOut,
24585
24635
  showCorrectness,
@@ -24607,15 +24657,11 @@ function LibraryChoiceIcon(props) {
24607
24657
  // radio icon
24608
24658
  ,
24609
24659
  "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
24660
  }, /*#__PURE__*/React.createElement(ChoiceInner, {
24615
24661
  letter: letter,
24616
24662
  showCorrectness: showCorrectness,
24617
24663
  correct: correct
24618
- })))), crossedOut && /*#__PURE__*/React.createElement(CrossOutLine, {
24664
+ }))), crossedOut && /*#__PURE__*/React.createElement(CrossOutLine, {
24619
24665
  color: dynamicStyles.borderColor
24620
24666
  }));
24621
24667
  }
@@ -24705,7 +24751,6 @@ function constructSATStyles(reviewMode, correct, checked, multipleSelect) {
24705
24751
  function SATChoiceIcon(props) {
24706
24752
  var {
24707
24753
  letter,
24708
- a11yText,
24709
24754
  crossedOut,
24710
24755
  reviewMode,
24711
24756
  correct,
@@ -24733,11 +24778,7 @@ function SATChoiceIcon(props) {
24733
24778
  color
24734
24779
  },
24735
24780
  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, {
24781
+ }, /*#__PURE__*/React.createElement("span", null, letter)), crossedOut && /*#__PURE__*/React.createElement(CrossOutLine, {
24741
24782
  color: borderColor,
24742
24783
  sat: true
24743
24784
  }));
@@ -24774,66 +24815,6 @@ var styles$u = StyleSheet.create({
24774
24815
 
24775
24816
  /* Component for rendering a letter icon in radio choice */
24776
24817
 
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
24818
  function ChoiceIcon(props) {
24838
24819
  var {
24839
24820
  pos,
@@ -24852,12 +24833,10 @@ function ChoiceIcon(props) {
24852
24833
  transparentBackground
24853
24834
  } = props;
24854
24835
  var letter = getChoiceLetter(pos);
24855
- var a11yText = getA11yText(letter, checked, correct, crossedOut, showCorrectness);
24856
24836
 
24857
24837
  if (product === "sat") {
24858
24838
  return /*#__PURE__*/React.createElement(SATChoiceIcon, {
24859
24839
  letter: letter,
24860
- a11yText: a11yText,
24861
24840
  reviewMode: reviewMode,
24862
24841
  checked: checked,
24863
24842
  correct: correct,
@@ -24868,7 +24847,6 @@ function ChoiceIcon(props) {
24868
24847
 
24869
24848
  return /*#__PURE__*/React.createElement(LibraryChoiceIcon, {
24870
24849
  letter: letter,
24871
- a11yText: a11yText,
24872
24850
  reviewMode: reviewMode,
24873
24851
  checked: checked,
24874
24852
  crossedOut: crossedOut,
@@ -24979,6 +24957,15 @@ var ellipsisHorizontalIcon = {
24979
24957
  width: 100,
24980
24958
  height: 27.284
24981
24959
  };
24960
+ // Note(TB): Received errors when using useUniqueIdWithMock
24961
+ // so created this workaround function. Will update when
24962
+ // useUniqueIdWithMock is available.
24963
+ // https://khanacademy.atlassian.net/browse/FEI-4861?atlOrigin=eyJpIjoiNDJlZWMwNjM1NWJhNDBkMWFjY2FmN2I0ZjcxZmQxOGUiLCJwIjoiaiJ9
24964
+ var id = 0;
24965
+
24966
+ function uniqueId() {
24967
+ return "choice-".concat(id++);
24968
+ }
24982
24969
 
24983
24970
  function Choice(props) {
24984
24971
  var {
@@ -25035,6 +25022,9 @@ function Choice(props) {
25035
25022
  // content library.
25036
25023
 
25037
25024
  var showDimmed = !apiOptions.satStyling && !reviewMode && apiOptions.readOnly || crossedOut;
25025
+ var letter = getChoiceLetter(pos);
25026
+ var a11yText = getA11yText(letter, checked, correct, crossedOut, showCorrectness);
25027
+ var choiceId = uniqueId();
25038
25028
  return /*#__PURE__*/React.createElement("div", {
25039
25029
  style: {
25040
25030
  dispay: "flex",
@@ -25047,7 +25037,26 @@ function Choice(props) {
25047
25037
  width: "100%",
25048
25038
  opacity: showDimmed ? 0.5 : 1.0
25049
25039
  }
25050
- }, /*#__PURE__*/React.createElement(Clickable, {
25040
+ }, /*#__PURE__*/React.createElement("div", {
25041
+ className: "perseus-sr-only"
25042
+ }, /*#__PURE__*/React.createElement("input", {
25043
+ type: multipleSelect ? "checkbox" : "radio",
25044
+ id: choiceId,
25045
+ checked: checked,
25046
+ onClick: () => {
25047
+ // If we're checking a crossed-out option, let's
25048
+ // also uncross it.
25049
+ sendChange({
25050
+ checked: !checked,
25051
+ crossedOut: false
25052
+ });
25053
+ },
25054
+ onChange: () => {},
25055
+ disabled: disabled || apiOptions.staticRender || apiOptions.readOnly,
25056
+ tabIndex: -1
25057
+ }), /*#__PURE__*/React.createElement("label", {
25058
+ htmlFor: choiceId
25059
+ }, a11yText, " \xA0 ", content)), /*#__PURE__*/React.createElement(Clickable, {
25051
25060
  onClick: () => {
25052
25061
  // If we're checking a crossed-out option, let's
25053
25062
  // also uncross it.
@@ -25058,13 +25067,11 @@ function Choice(props) {
25058
25067
  },
25059
25068
  className: descriptionClassName,
25060
25069
  disabled: disabled || apiOptions.staticRender || apiOptions.readOnly,
25061
- "aria-label": "Select Choice ".concat(getChoiceLetter(pos)),
25062
- "aria-checked": checked ? "true" : "false",
25063
- role: "checkbox",
25064
25070
  style: {
25065
25071
  flex: 1
25066
25072
  },
25067
- ref: forwardedRef
25073
+ ref: forwardedRef,
25074
+ "aria-hidden": "true"
25068
25075
  }, _ref => {
25069
25076
  var _apiOptions$styling;
25070
25077
 
@@ -25409,7 +25416,8 @@ function BaseRadio(props) {
25409
25416
  }, /*#__PURE__*/React.createElement("legend", {
25410
25417
  className: "perseus-sr-only"
25411
25418
  }, instructions), shouldShowInstructions && /*#__PURE__*/React.createElement("div", {
25412
- className: instructionsClassName
25419
+ className: instructionsClassName,
25420
+ "aria-hidden": "true"
25413
25421
  }, instructions), /*#__PURE__*/React.createElement("ul", {
25414
25422
  className: className,
25415
25423
  style: {
@@ -26685,7 +26693,7 @@ class Definition extends React.Component {
26685
26693
  }, /*#__PURE__*/React.createElement("span", {
26686
26694
  className: "perseus-widget-definition"
26687
26695
  }, /*#__PURE__*/React.createElement(Button, {
26688
- size: "small",
26696
+ size: "medium",
26689
26697
  kind: "tertiary",
26690
26698
  onClick: () => {
26691
26699
  this.props.trackInteraction();
@@ -27368,7 +27376,10 @@ class GradedGroup$1 extends React.Component {
27368
27376
  className: css(a11y.srOnly),
27369
27377
  role: "alert",
27370
27378
  "aria-label": gradeStatus
27371
- }, gradeStatus), !apiOptions.isMobile && /*#__PURE__*/React.createElement("p", null, this.state.message), !apiOptions.isMobile && /*#__PURE__*/React.createElement(Button, {
27379
+ }, gradeStatus), !apiOptions.isMobile && /*#__PURE__*/React.createElement("p", {
27380
+ role: "status",
27381
+ "aria-live": "polite"
27382
+ }, this.state.message), !apiOptions.isMobile && /*#__PURE__*/React.createElement(Button, {
27372
27383
  kind: "secondary",
27373
27384
  disabled: this.props.apiOptions.readOnly,
27374
27385
  onClick: this._checkAnswer
@@ -28902,6 +28913,8 @@ class Group extends React.Component {
28902
28913
  super(...arguments);
28903
28914
  _this = this;
28904
28915
 
28916
+ _defineProperty(this, "rendererRef", void 0);
28917
+
28905
28918
  _defineProperty(this, "change", function () {
28906
28919
  for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
28907
28920
  args[_key] = arguments[_key];
@@ -28911,56 +28924,66 @@ class Group extends React.Component {
28911
28924
  });
28912
28925
 
28913
28926
  _defineProperty(this, "getUserInput", () => {
28914
- // eslint-disable-next-line react/no-string-refs
28915
- return this.refs.renderer.getUserInput();
28927
+ var _this$rendererRef;
28928
+
28929
+ return (_this$rendererRef = this.rendererRef) === null || _this$rendererRef === void 0 ? void 0 : _this$rendererRef.getUserInput();
28916
28930
  });
28917
28931
 
28918
28932
  _defineProperty(this, "getSerializedState", () => {
28919
- // eslint-disable-next-line react/no-string-refs
28920
- return this.refs.renderer.getSerializedState();
28933
+ var _this$rendererRef2;
28934
+
28935
+ return (_this$rendererRef2 = this.rendererRef) === null || _this$rendererRef2 === void 0 ? void 0 : _this$rendererRef2.getSerializedState();
28921
28936
  });
28922
28937
 
28923
28938
  _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
28939
+ var _this$rendererRef3;
28940
+
28941
+ (_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
28942
  // (all our changes were in state):
28927
28943
 
28928
28944
  return null;
28929
28945
  });
28930
28946
 
28931
28947
  _defineProperty(this, "simpleValidate", rubric => {
28932
- // eslint-disable-next-line react/no-string-refs
28933
- return this.refs.renderer.score();
28948
+ var _this$rendererRef4;
28949
+
28950
+ return (_this$rendererRef4 = this.rendererRef) === null || _this$rendererRef4 === void 0 ? void 0 : _this$rendererRef4.score();
28934
28951
  });
28935
28952
 
28936
28953
  _defineProperty(this, "getInputPaths", () => {
28937
- // eslint-disable-next-line react/no-string-refs
28938
- return this.refs.renderer.getInputPaths();
28954
+ var _this$rendererRef5;
28955
+
28956
+ return (_this$rendererRef5 = this.rendererRef) === null || _this$rendererRef5 === void 0 ? void 0 : _this$rendererRef5.getInputPaths();
28939
28957
  });
28940
28958
 
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);
28959
+ _defineProperty(this, "setInputValue", (path, newValue, callback) => {
28960
+ var _this$rendererRef6;
28961
+
28962
+ return (_this$rendererRef6 = this.rendererRef) === null || _this$rendererRef6 === void 0 ? void 0 : _this$rendererRef6.setInputValue(path, newValue, callback);
28944
28963
  });
28945
28964
 
28946
28965
  _defineProperty(this, "focus", () => {
28947
- // eslint-disable-next-line react/no-string-refs
28948
- return this.refs.renderer.focus();
28966
+ var _this$rendererRef7;
28967
+
28968
+ return (_this$rendererRef7 = this.rendererRef) === null || _this$rendererRef7 === void 0 ? void 0 : _this$rendererRef7.focus();
28949
28969
  });
28950
28970
 
28951
28971
  _defineProperty(this, "focusInputPath", path => {
28952
- // eslint-disable-next-line react/no-string-refs
28953
- this.refs.renderer.focusPath(path);
28972
+ var _this$rendererRef8;
28973
+
28974
+ (_this$rendererRef8 = this.rendererRef) === null || _this$rendererRef8 === void 0 ? void 0 : _this$rendererRef8.focusPath(path);
28954
28975
  });
28955
28976
 
28956
28977
  _defineProperty(this, "blurInputPath", path => {
28957
- // eslint-disable-next-line react/no-string-refs
28958
- this.refs.renderer.blurPath(path);
28978
+ var _this$rendererRef9;
28979
+
28980
+ (_this$rendererRef9 = this.rendererRef) === null || _this$rendererRef9 === void 0 ? void 0 : _this$rendererRef9.blurPath(path);
28959
28981
  });
28960
28982
 
28961
28983
  _defineProperty(this, "showRationalesForCurrentlySelectedChoices", () => {
28962
- // eslint-disable-next-line react/no-string-refs
28963
- this.refs.renderer.showRationalesForCurrentlySelectedChoices();
28984
+ var _this$rendererRef10;
28985
+
28986
+ (_this$rendererRef10 = this.rendererRef) === null || _this$rendererRef10 === void 0 ? void 0 : _this$rendererRef10.showRationalesForCurrentlySelectedChoices();
28964
28987
  });
28965
28988
  }
28966
28989
 
@@ -28972,7 +28995,9 @@ class Group extends React.Component {
28972
28995
  }
28973
28996
 
28974
28997
  render() {
28975
- var apiOptions = _.extend({}, ApiOptions.defaults, this.props.apiOptions, {
28998
+ var _this$rendererRef11;
28999
+
29000
+ var apiOptions = _objectSpread2(_objectSpread2(_objectSpread2({}, ApiOptions.defaults), this.props.apiOptions), {}, {
28976
29001
  // Api Rewriting to support correct onFocus/onBlur
28977
29002
  // events for the mobile API
28978
29003
  onFocusChange: (newFocus, oldFocus) => {
@@ -28994,8 +29019,8 @@ class Group extends React.Component {
28994
29019
  // really we should have a more unidirectional flow. TODO(marcia): fix.
28995
29020
 
28996
29021
 
28997
- var number = _.indexOf(this.props.findWidgets("group"), this);
28998
-
29022
+ var groupWidgets = this.props.findWidgets("group");
29023
+ var number = groupWidgets.indexOf(this);
28999
29024
  var problemNumComponent = this.props.apiOptions.groupAnnotator(number, this.props.widgetId); // This is a little strange because the id of the widget that actually
29000
29025
  // changed is going to be lost in favor of the group widget's id. The
29001
29026
  // widgets prop also wasn't actually changed, and this only serves to
@@ -29003,15 +29028,12 @@ class Group extends React.Component {
29003
29028
  // has occurred.
29004
29029
 
29005
29030
  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);
29031
+ if (this.rendererRef) {
29032
+ this.change("widgets", this.rendererRef.props.widgets);
29010
29033
  }
29011
- }; // eslint-disable-next-line react/no-string-refs
29012
-
29034
+ };
29013
29035
 
29014
- var score = this.refs.renderer && this.refs.renderer.score();
29036
+ var score = (_this$rendererRef11 = this.rendererRef) === null || _this$rendererRef11 === void 0 ? void 0 : _this$rendererRef11.score();
29015
29037
  var isValid = score && score.type !== "invalid";
29016
29038
  var isInvalid = score && score.type === "invalid"; // TODO(mdr): Widgets inside this Renderer are not discoverable through
29017
29039
  // the parent Renderer's `findWidgets` function.
@@ -29022,15 +29044,17 @@ class Group extends React.Component {
29022
29044
  "perseus-group-valid-answer": isValid,
29023
29045
  "perseus-group-invalid-answer": isInvalid
29024
29046
  })
29025
- }, problemNumComponent, /*#__PURE__*/React.createElement(Renderer, _extends({}, this.props, {
29026
- // eslint-disable-next-line react/no-string-refs
29027
- ref: "renderer",
29047
+ }, problemNumComponent, /*#__PURE__*/React.createElement(Renderer, {
29048
+ content: this.props.content,
29049
+ widgets: this.props.widgets,
29050
+ images: this.props.images,
29051
+ ref: _ref => this.rendererRef = _ref,
29028
29052
  apiOptions: apiOptions,
29029
29053
  findExternalWidgets: this.props.findWidgets,
29030
29054
  reviewMode: !!this.props.reviewModeRubric,
29031
29055
  onInteractWithWidget: onInteractWithWidget,
29032
29056
  linterContext: this.props.linterContext
29033
- })), this.props.icon && /*#__PURE__*/React.createElement("div", {
29057
+ }), this.props.icon && /*#__PURE__*/React.createElement("div", {
29034
29058
  className: "group-icon"
29035
29059
  }, this.props.icon));
29036
29060
  }
@@ -29045,7 +29069,7 @@ _defineProperty(Group, "defaultProps", {
29045
29069
  });
29046
29070
 
29047
29071
  var traverseChildWidgets$1 = function traverseChildWidgets(props, traverseRenderer) {
29048
- return _.extend({}, props, traverseRenderer(props));
29072
+ return _objectSpread2(_objectSpread2({}, props), traverseRenderer(props));
29049
29073
  };
29050
29074
 
29051
29075
  var Group$1 = {
@@ -36745,7 +36769,18 @@ class Matrix extends React.Component {
36745
36769
  } else if (this.props.numericInput) {
36746
36770
  MatrixInput = /*#__PURE__*/React.createElement(NumberInput, inputProps);
36747
36771
  } else {
36748
- MatrixInput = /*#__PURE__*/React.createElement(TextInput, inputProps);
36772
+ var updatedProps = _objectSpread2(_objectSpread2({}, inputProps), {}, {
36773
+ style: StyleSheet.create({
36774
+ // eslint-disable-next-line react-native/no-unused-styles
36775
+ input: _objectSpread2(_objectSpread2({}, inputProps.style), {}, {
36776
+ display: "inline-block",
36777
+ padding: 0,
36778
+ backgroundColor: outside ? "#f3f3f3" : "#fff"
36779
+ })
36780
+ }).input
36781
+ });
36782
+
36783
+ MatrixInput = /*#__PURE__*/React.createElement(TextInput, updatedProps);
36749
36784
  }
36750
36785
 
36751
36786
  return /*#__PURE__*/React.createElement("span", {