@khanacademy/perseus 43.0.1 → 43.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 (33) hide show
  1. package/dist/components/number-input.d.ts +5 -3
  2. package/dist/components/text-input.d.ts +4 -2
  3. package/dist/es/index.js +433 -238
  4. package/dist/es/index.js.map +1 -1
  5. package/dist/index.js +424 -229
  6. package/dist/index.js.map +1 -1
  7. package/dist/perseus-types.d.ts +14 -7
  8. package/dist/util/parse-perseus-json/general-purpose-parsers/defaulted.d.ts +1 -1
  9. package/dist/util/parse-perseus-json/general-purpose-parsers/index.d.ts +2 -0
  10. package/dist/util/parse-perseus-json/general-purpose-parsers/pipe-parsers.d.ts +7 -0
  11. package/dist/util/parse-perseus-json/general-purpose-parsers/pipe-parsers.typetest.d.ts +1 -0
  12. package/dist/util/parse-perseus-json/general-purpose-parsers/unknown.d.ts +2 -0
  13. package/dist/util/parse-perseus-json/perseus-parsers/images-map.d.ts +5 -0
  14. package/dist/util/parse-perseus-json/perseus-parsers/widget.d.ts +4 -0
  15. package/dist/validation.types.d.ts +14 -6
  16. package/dist/widgets/categorizer/categorizer.d.ts +2 -2
  17. package/dist/widgets/categorizer/score-categorizer.d.ts +2 -2
  18. package/dist/widgets/categorizer/validate-categorizer.d.ts +15 -0
  19. package/dist/widgets/dropdown/validate-dropdown.d.ts +10 -0
  20. package/dist/widgets/interactive-graphs/graphs/components/movable-point.d.ts +11 -7
  21. package/dist/widgets/interactive-graphs/graphs/components/use-control-point.d.ts +7 -3
  22. package/dist/widgets/interactive-graphs/types.d.ts +1 -0
  23. package/dist/widgets/matrix/matrix.d.ts +12 -5
  24. package/dist/widgets/measurer/measurer.d.ts +1 -1
  25. package/dist/widgets/orderer/validate-orderer.d.ts +12 -0
  26. package/dist/widgets/plotter/plotter.d.ts +2 -2
  27. package/dist/widgets/plotter/score-plotter.d.ts +2 -2
  28. package/dist/widgets/plotter/validate-plotter.d.ts +12 -0
  29. package/dist/widgets/radio/radio.d.ts +1 -1
  30. package/dist/widgets/radio/validate-radio.d.ts +14 -0
  31. package/dist/widgets/sorter/validate-sorter.d.ts +13 -0
  32. package/package.json +4 -4
  33. package/dist/util/parse-perseus-json/general-purpose-parsers/compose-parsers.d.ts +0 -2
package/dist/es/index.js CHANGED
@@ -2760,8 +2760,6 @@ async function getImageSizeModern(url) {
2760
2760
  });
2761
2761
  }
2762
2762
 
2763
- /* eslint-disable @babel/no-invalid-this, getter-return, one-var */
2764
-
2765
2763
  // TODO: dedupe this with Coord in interactive2/types.js
2766
2764
 
2767
2765
  let supportsPassive = false;
@@ -2837,8 +2835,8 @@ function shuffle$3(array, randomSeed, ensurePermuted = false) {
2837
2835
  do {
2838
2836
  // Fischer-Yates shuffle
2839
2837
  for (let top = shuffled.length; top > 0; top--) {
2840
- const newEnd = Math.floor(random() * top),
2841
- temp = shuffled[newEnd];
2838
+ const newEnd = Math.floor(random() * top);
2839
+ const temp = shuffled[newEnd];
2842
2840
 
2843
2841
  // @ts-expect-error - TS2542 - Index signature in type 'readonly T[]' only permits reading.
2844
2842
  shuffled[newEnd] = shuffled[top - 1];
@@ -3170,13 +3168,13 @@ function eq$4(x, y) {
3170
3168
  * Deep approximate equality on primitives, numbers, arrays, and objects.
3171
3169
  * Recursive.
3172
3170
  */
3173
- function deepEq$6(x, y) {
3171
+ function deepEq$7(x, y) {
3174
3172
  if (Array.isArray(x) && Array.isArray(y)) {
3175
3173
  if (x.length !== y.length) {
3176
3174
  return false;
3177
3175
  }
3178
3176
  for (let i = 0; i < x.length; i++) {
3179
- if (!deepEq$6(x[i], y[i])) {
3177
+ if (!deepEq$7(x[i], y[i])) {
3180
3178
  return false;
3181
3179
  }
3182
3180
  }
@@ -3194,10 +3192,10 @@ function deepEq$6(x, y) {
3194
3192
  if (typeof x === "object" && typeof y === "object" && !!x && !!y) {
3195
3193
  return x === y || _.all(x, function (v, k) {
3196
3194
  // @ts-expect-error - TS2536 - Type 'CollectionKey<T>' cannot be used to index type 'T'.
3197
- return deepEq$6(y[k], v);
3195
+ return deepEq$7(y[k], v);
3198
3196
  }) && _.all(y, function (v, k) {
3199
3197
  // @ts-expect-error - TS2536 - Type 'CollectionKey<T>' cannot be used to index type 'T'.
3200
- return deepEq$6(x[k], v);
3198
+ return deepEq$7(x[k], v);
3201
3199
  });
3202
3200
  }
3203
3201
  if (typeof x === "object" && !!x || typeof y === "object" && !!y) {
@@ -3478,7 +3476,7 @@ const Util = {
3478
3476
  tickStepFromNumTicks,
3479
3477
  constrainedTickStepsFromTickSteps,
3480
3478
  eq: eq$4,
3481
- deepEq: deepEq$6,
3479
+ deepEq: deepEq$7,
3482
3480
  parseQueryString,
3483
3481
  updateQueryString: updateQueryString$2,
3484
3482
  strongEncodeURIComponent,
@@ -10509,7 +10507,9 @@ const getClipPoint = function getClipPoint(graph, coord, angle) {
10509
10507
  return clipPoint;
10510
10508
  };
10511
10509
 
10512
- /* eslint-disable @babel/no-invalid-this */
10510
+ /**
10511
+ * Default methods for a wrapped movable.
10512
+ */
10513
10513
 
10514
10514
  /*
10515
10515
  * attr() and animate() simply pass the
@@ -10767,7 +10767,7 @@ class WrappedLine extends WrappedDrawing {
10767
10767
  */
10768
10768
  const {
10769
10769
  eq: eq$3,
10770
- deepEq: deepEq$5
10770
+ deepEq: deepEq$6
10771
10771
  } = Util;
10772
10772
 
10773
10773
  // This should really be a readonly tuple of [number, number]
@@ -10927,7 +10927,7 @@ function similar(coords1, coords2, tolerance) {
10927
10927
  angles = rotate(angles, i);
10928
10928
  // @ts-expect-error - TS4104 - The type 'readonly number[]' is 'readonly' and cannot be assigned to the mutable type 'number[]'.
10929
10929
  sides = rotate(sides, i);
10930
- if (deepEq$5(angles1, angles)) {
10930
+ if (deepEq$6(angles1, angles)) {
10931
10931
  const sidePairs = _.zip(sides1, sides);
10932
10932
  const factors = _.map(sidePairs, function (pair) {
10933
10933
  return pair[0] / pair[1];
@@ -13865,7 +13865,7 @@ _.extend(MovableAngle.prototype, {
13865
13865
 
13866
13866
  /* eslint-disable @babel/no-invalid-this */
13867
13867
  const nestedMap$1 = Util.nestedMap;
13868
- const deepEq$4 = Util.deepEq;
13868
+ const deepEq$5 = Util.deepEq;
13869
13869
 
13870
13870
  /**
13871
13871
  * A base class for all Graphie Movables
@@ -13962,7 +13962,7 @@ const createSimpleClass = function createSimpleClass(addFunction) {
13962
13962
  this._prevProps = this.props;
13963
13963
  },
13964
13964
  modify: function (graphie) {
13965
- if (!deepEq$4(this.props, this._prevProps)) {
13965
+ if (!deepEq$5(this.props, this._prevProps)) {
13966
13966
  this.remove();
13967
13967
  this.add(graphie);
13968
13968
  this._prevProps = this.props;
@@ -16576,7 +16576,7 @@ var Movables = {
16576
16576
  const GraphieMovable = GraphieClasses.GraphieMovable;
16577
16577
  const createGraphie = GraphUtils.createGraphie;
16578
16578
  const {
16579
- deepEq: deepEq$3,
16579
+ deepEq: deepEq$4,
16580
16580
  nestedMap
16581
16581
  } = Util;
16582
16582
  const {
@@ -16818,7 +16818,7 @@ class Graphie extends React.Component {
16818
16818
  if (this.props.setup !== prevProps.setup) {
16819
16819
  Log.error("<Graphie> was given a new setup function. " + "This is a bad idea; please refactor your code to give " + "the same setup function reference to <Graphie> on " + "every render.", Errors.Internal);
16820
16820
  }
16821
- if (!deepEq$3(this.props.options, prevProps.options) || !deepEq$3(this.props.box, prevProps.box) || !deepEq$3(this.props.range, prevProps.range)) {
16821
+ if (!deepEq$4(this.props.options, prevProps.options) || !deepEq$4(this.props.box, prevProps.box) || !deepEq$4(this.props.range, prevProps.range)) {
16822
16822
  this._setupGraphie();
16823
16823
  }
16824
16824
  this._updateMovables();
@@ -18536,7 +18536,7 @@ const rules$1 = _extends({}, pureMarkdownRules, {
18536
18536
  // The href prop should always be here, but just in case somehow the article's
18537
18537
  // link was not put together properly, let's make sure it's there so we
18538
18538
  // don't break the entire page.
18539
- const isKAUrl = href ? href.match(/https?:\/\/[^\/]*khanacademy.org|^\//) : false;
18539
+ const isKAUrl = href ? href.match(/https?:\/\/[^/]*khanacademy.org|^\//) : false;
18540
18540
  if (!isKAUrl) {
18541
18541
  // Prevents "reverse tabnabbing" phishing attacks
18542
18542
  // @ts-expect-error - TS2322 - Type '"noopener noreferrer"' is not assignable to type 'null'.
@@ -20889,36 +20889,36 @@ function uniqueIdForInput(prefix = "input-") {
20889
20889
  class TextInput extends React.Component {
20890
20890
  constructor(props) {
20891
20891
  super(props);
20892
+ this.inputRef = /*#__PURE__*/React.createRef();
20892
20893
  this.id = void 0;
20894
+ this._getInput = () => {
20895
+ if (!this.inputRef.current) {
20896
+ throw new PerseusError("Input ref accessed before set", Errors.Internal);
20897
+ }
20898
+ return this.inputRef.current;
20899
+ };
20893
20900
  this.focus = () => {
20894
- // @ts-expect-error - TS2531 - Object is possibly 'null'. | TS2339 - Property 'focus' does not exist on type 'Element | Text'.
20895
- ReactDOM__default.findDOMNode(this).focus();
20901
+ this._getInput().focus();
20896
20902
  };
20897
20903
  this.blur = () => {
20898
- // @ts-expect-error - TS2531 - Object is possibly 'null'. | TS2339 - Property 'blur' does not exist on type 'Element | Text'.
20899
- ReactDOM__default.findDOMNode(this).blur();
20904
+ this._getInput().blur();
20900
20905
  };
20901
20906
  this.getValue = () => {
20902
- var _ReactDOM$findDOMNode;
20903
- // @ts-expect-error - TS2339 - Property 'value' does not exist on type 'Element | Text'.
20904
- return (_ReactDOM$findDOMNode = ReactDOM__default.findDOMNode(this)) == null ? void 0 : _ReactDOM$findDOMNode.value;
20907
+ var _this$inputRef$curren;
20908
+ return (_this$inputRef$curren = this.inputRef.current) == null ? void 0 : _this$inputRef$curren.value;
20905
20909
  };
20906
20910
  this.getStringValue = () => {
20907
- var _ReactDOM$findDOMNode2;
20908
- // @ts-expect-error - TS2339 - Property 'value' does not exist on type 'Element | Text'.
20909
- return (_ReactDOM$findDOMNode2 = ReactDOM__default.findDOMNode(this)) == null ? void 0 : _ReactDOM$findDOMNode2.value.toString();
20911
+ var _this$inputRef$curren2;
20912
+ return (_this$inputRef$curren2 = this.inputRef.current) == null ? void 0 : _this$inputRef$curren2.value.toString();
20910
20913
  };
20911
20914
  this.setSelectionRange = (selectionStart, selectionEnd) => {
20912
- // @ts-expect-error - TS2531 - Object is possibly 'null'. | TS2339 - Property 'setSelectionRange' does not exist on type 'Element | Text'.
20913
- ReactDOM__default.findDOMNode(this).setSelectionRange(selectionStart, selectionEnd);
20915
+ this._getInput().setSelectionRange(selectionStart, selectionEnd);
20914
20916
  };
20915
20917
  this.getSelectionStart = () => {
20916
- // @ts-expect-error - TS2531 - Object is possibly 'null'. | TS2339 - Property 'selectionStart' does not exist on type 'Element | Text'.
20917
- return ReactDOM__default.findDOMNode(this).selectionStart;
20918
+ return this._getInput().selectionStart;
20918
20919
  };
20919
20920
  this.getSelectionEnd = () => {
20920
- // @ts-expect-error - TS2531 - Object is possibly 'null'. | TS2339 - Property 'selectionEnd' does not exist on type 'Element | Text'.
20921
- return ReactDOM__default.findDOMNode(this).selectionEnd;
20921
+ return this._getInput().selectionEnd;
20922
20922
  };
20923
20923
  if (props.id) {
20924
20924
  this.id = props.id;
@@ -20939,6 +20939,7 @@ class TextInput extends React.Component {
20939
20939
  } = this.props;
20940
20940
  const formattedValue = value === null ? "" : value.toString();
20941
20941
  return /*#__PURE__*/React.createElement(TextField, {
20942
+ ref: this.inputRef,
20942
20943
  style: style,
20943
20944
  disabled: disabled,
20944
20945
  id: this.id,
@@ -20973,7 +20974,7 @@ function _objectWithoutPropertiesLoose(r, e) {
20973
20974
  return t;
20974
20975
  }
20975
20976
 
20976
- const _excluded$5 = ["position", "visibility", "left", "top"];
20977
+ const _excluded$8 = ["position", "visibility", "left", "top"];
20977
20978
  const zIndex = 10;
20978
20979
  let HorizontalDirection = /*#__PURE__*/function (HorizontalDirection) {
20979
20980
  HorizontalDirection["Left"] = "left";
@@ -21024,7 +21025,7 @@ const TooltipArrow = _ref => {
21024
21025
  left = 100,
21025
21026
  top = 0
21026
21027
  } = _ref,
21027
- props = _objectWithoutPropertiesLoose(_ref, _excluded$5);
21028
+ props = _objectWithoutPropertiesLoose(_ref, _excluded$8);
21028
21029
  // TODO(aria): Think about adding a box-shadow to the triangle here
21029
21030
  // See http://css-tricks.com/triangle-with-shadow/
21030
21031
 
@@ -21384,7 +21385,7 @@ InputWithExamples.defaultProps = {
21384
21385
  className: ""
21385
21386
  };
21386
21387
 
21387
- const _excluded$4 = ["keypadElement", "onFocus", "value"];
21388
+ const _excluded$7 = ["keypadElement", "onFocus", "value"];
21388
21389
  class SimpleKeypadInput extends React.Component {
21389
21390
  constructor(...args) {
21390
21391
  super(...args);
@@ -21429,7 +21430,7 @@ class SimpleKeypadInput extends React.Component {
21429
21430
  onFocus,
21430
21431
  value
21431
21432
  } = _this$props,
21432
- rest = _objectWithoutPropertiesLoose(_this$props, _excluded$4);
21433
+ rest = _objectWithoutPropertiesLoose(_this$props, _excluded$7);
21433
21434
  return (
21434
21435
  /*#__PURE__*/
21435
21436
  // @ts-expect-error - TS2769 - No overload matches this call.
@@ -21991,7 +21992,7 @@ function maybeParsePercentInput(inputValue, normalizedAnswerExpected) {
21991
21992
  return value / 100;
21992
21993
  }
21993
21994
 
21994
- // Otherwise, we return input valuåe (number) stripped of the "%".
21995
+ // Otherwise, we return input value (number) stripped of the "%".
21995
21996
  return value;
21996
21997
  }
21997
21998
  function scoreNumericInput(userInput, rubric, strings) {
@@ -25662,14 +25663,14 @@ var Choice$1 = /*#__PURE__*/React.forwardRef((props, ref) => /*#__PURE__*/React.
25662
25663
  forwardedRef: ref
25663
25664
  })));
25664
25665
 
25665
- const _excluded$3 = ["content", "forwardedRef", "showContent"];
25666
+ const _excluded$6 = ["content", "forwardedRef", "showContent"];
25666
25667
  const ChoiceNoneAbove = function ChoiceNoneAbove(_ref) {
25667
25668
  let {
25668
25669
  content,
25669
25670
  forwardedRef,
25670
25671
  showContent = true
25671
25672
  } = _ref,
25672
- rest = _objectWithoutPropertiesLoose(_ref, _excluded$3);
25673
+ rest = _objectWithoutPropertiesLoose(_ref, _excluded$6);
25673
25674
  const {
25674
25675
  strings
25675
25676
  } = usePerseusI18n();
@@ -26040,7 +26041,15 @@ const styles$p = StyleSheet.create({
26040
26041
  }
26041
26042
  });
26042
26043
 
26043
- function scoreRadio(userInput, rubric, strings) {
26044
+ /**
26045
+ * Checks if the user has selected at least one option. Additional validation
26046
+ * is done in scoreRadio to check if the number of selected options is correct
26047
+ * and if the user has selected both a correct option and the "none of the above"
26048
+ * option.
26049
+ * @param userInput
26050
+ * @see `scoreRadio` for the additional validation logic and the scoring logic.
26051
+ */
26052
+ function validateRadio(userInput) {
26044
26053
  const numSelected = userInput.choicesSelected.reduce((sum, selected) => {
26045
26054
  return sum + (selected ? 1 : 0);
26046
26055
  }, 0);
@@ -26050,6 +26059,17 @@ function scoreRadio(userInput, rubric, strings) {
26050
26059
  message: null
26051
26060
  };
26052
26061
  }
26062
+ return null;
26063
+ }
26064
+
26065
+ function scoreRadio(userInput, rubric, strings) {
26066
+ const validationError = validateRadio(userInput);
26067
+ if (validationError) {
26068
+ return validationError;
26069
+ }
26070
+ const numSelected = userInput.choicesSelected.reduce((sum, selected) => {
26071
+ return sum + (selected ? 1 : 0);
26072
+ }, 0);
26053
26073
  const numCorrect = rubric.choices.reduce((sum, currentChoice) => {
26054
26074
  return currentChoice.correct ? sum + 1 : sum;
26055
26075
  }, 0);
@@ -26426,6 +26446,7 @@ Radio$1.defaultProps = {
26426
26446
  showSolutions: "none"
26427
26447
  };
26428
26448
 
26449
+ const _excluded$5 = ["noneOfTheAbove"];
26429
26450
  const {
26430
26451
  shuffle: shuffle$2,
26431
26452
  random
@@ -26514,17 +26535,15 @@ const transform = (widgetOptions, strings, problemNum) => {
26514
26535
  };
26515
26536
  const propUpgrades$1 = {
26516
26537
  "1": v0props => {
26517
- let choices;
26518
- let hasNoneOfTheAbove;
26519
- if (!v0props.noneOfTheAbove) {
26520
- choices = v0props.choices;
26521
- hasNoneOfTheAbove = false;
26522
- } else {
26538
+ const {
26539
+ noneOfTheAbove
26540
+ } = v0props,
26541
+ rest = _objectWithoutPropertiesLoose(v0props, _excluded$5);
26542
+ if (noneOfTheAbove) {
26523
26543
  throw new Error("radio widget v0 no longer supports auto noneOfTheAbove");
26524
26544
  }
26525
- return _.extend(_.omit(v0props, "noneOfTheAbove"), {
26526
- choices: choices,
26527
- hasNoneOfTheAbove: hasNoneOfTheAbove
26545
+ return _extends({}, rest, {
26546
+ hasNoneOfTheAbove: false
26528
26547
  });
26529
26548
  }
26530
26549
  };
@@ -26562,23 +26581,36 @@ const getPromptJSON$n = (renderProps, userInput) => {
26562
26581
  };
26563
26582
  };
26564
26583
 
26565
- function scoreCategorizer(userInput, rubric, strings) {
26566
- let completed = true;
26567
- let allCorrect = true;
26568
- rubric.values.forEach((value, i) => {
26569
- if (userInput.values[i] == null) {
26570
- completed = false;
26571
- }
26572
- if (userInput.values[i] !== value) {
26573
- allCorrect = false;
26574
- }
26575
- });
26576
- if (!completed) {
26584
+ /**
26585
+ * Checks userInput from the categorizer widget to see if the user has selected
26586
+ * a category for each item.
26587
+ * @param userInput - The user's input corresponding to an array of indices that
26588
+ * represent the selected category for each row/item.
26589
+ * @param validationData - An array of strings corresponding to each row/item
26590
+ * @param strings - Used to provide a validation message
26591
+ */
26592
+ function validateCategorizer(userInput, validationData, strings) {
26593
+ const incomplete = validationData.items.some((_, i) => userInput.values[i] == null);
26594
+ if (incomplete) {
26577
26595
  return {
26578
26596
  type: "invalid",
26579
26597
  message: strings.invalidSelection
26580
26598
  };
26581
26599
  }
26600
+ return null;
26601
+ }
26602
+
26603
+ function scoreCategorizer(userInput, scoringData, strings) {
26604
+ const validationError = validateCategorizer(userInput, scoringData, strings);
26605
+ if (validationError) {
26606
+ return validationError;
26607
+ }
26608
+ let allCorrect = true;
26609
+ scoringData.values.forEach((value, i) => {
26610
+ if (userInput.values[i] !== value) {
26611
+ allCorrect = false;
26612
+ }
26613
+ });
26582
26614
  return {
26583
26615
  type: "points",
26584
26616
  earned: allCorrect ? 1 : 0,
@@ -27124,15 +27156,26 @@ const getPromptJSON$k = (renderProps, userInput) => {
27124
27156
  };
27125
27157
  };
27126
27158
 
27127
- function scoreDropdown(userInput, rubric) {
27128
- const selected = userInput.value;
27129
- if (selected === 0) {
27159
+ /**
27160
+ * Checks if the user has selected an item from the dropdown before scoring.
27161
+ * This is shown with a userInput value / index other than 0.
27162
+ */
27163
+ function validateDropdown(userInput) {
27164
+ if (userInput.value === 0) {
27130
27165
  return {
27131
27166
  type: "invalid",
27132
27167
  message: null
27133
27168
  };
27134
27169
  }
27135
- const correct = rubric.choices[selected - 1].correct;
27170
+ return null;
27171
+ }
27172
+
27173
+ function scoreDropdown(userInput, rubric) {
27174
+ const validationError = validateDropdown(userInput);
27175
+ if (validationError) {
27176
+ return validationError;
27177
+ }
27178
+ const correct = rubric.choices[userInput.value - 1].correct;
27136
27179
  return {
27137
27180
  type: "points",
27138
27181
  earned: correct ? 1 : 0,
@@ -27519,8 +27562,6 @@ const getNextState = (currentState, answerable) => {
27519
27562
  }
27520
27563
  };
27521
27564
 
27522
- // exports has no 'transform'
27523
-
27524
27565
  // A Graded Group is more or less a Group widget that displays a check
27525
27566
  // answer button below the rendered content. When clicked, the widget grades
27526
27567
  // the stuff inside and displays feedback about whether the inputted answer was
@@ -28184,7 +28225,6 @@ const getPromptJSON$g = (renderProps, userInput) => {
28184
28225
  };
28185
28226
  };
28186
28227
 
28187
- /* eslint-disable one-var */
28188
28228
  // @ts-expect-error - TS2339 - Property 'Plot' does not exist on type 'typeof Graphie'.
28189
28229
  const Plot$1 = Graphie.Plot;
28190
28230
  const DEFAULT_BACKGROUND_IMAGE = {
@@ -28285,14 +28325,14 @@ const Linear = _.extend({}, PlotDefaults, {
28285
28325
  return [m, b];
28286
28326
  },
28287
28327
  getFunctionForCoeffs: function (coeffs, x) {
28288
- const m = coeffs[0],
28289
- b = coeffs[1];
28328
+ const m = coeffs[0];
28329
+ const b = coeffs[1];
28290
28330
  return m * x + b;
28291
28331
  },
28292
28332
  getEquationString: function (coords) {
28293
28333
  const coeffs = this.getCoefficients(coords);
28294
- const m = coeffs[0],
28295
- b = coeffs[1];
28334
+ const m = coeffs[0];
28335
+ const b = coeffs[1];
28296
28336
  return "y = " + m.toFixed(3) + "x + " + b.toFixed(3);
28297
28337
  }
28298
28338
  });
@@ -28316,9 +28356,9 @@ const Quadratic = _.extend({}, PlotDefaults, {
28316
28356
  return [a, b, c];
28317
28357
  },
28318
28358
  getFunctionForCoeffs: function (coeffs, x) {
28319
- const a = coeffs[0],
28320
- b = coeffs[1],
28321
- c = coeffs[2];
28359
+ const a = coeffs[0];
28360
+ const b = coeffs[1];
28361
+ const c = coeffs[2];
28322
28362
  return (a * x + b) * x + c;
28323
28363
  },
28324
28364
  getPropsForCoeffs: function (coeffs) {
@@ -28330,9 +28370,9 @@ const Quadratic = _.extend({}, PlotDefaults, {
28330
28370
  },
28331
28371
  getEquationString: function (coords) {
28332
28372
  const coeffs = this.getCoefficients(coords);
28333
- const a = coeffs[0],
28334
- b = coeffs[1],
28335
- c = coeffs[2];
28373
+ const a = coeffs[0];
28374
+ const b = coeffs[1];
28375
+ const c = coeffs[2];
28336
28376
  return "y = " + a.toFixed(3) + "x^2 + " + b.toFixed(3) + "x + " + c.toFixed(3);
28337
28377
  }
28338
28378
  });
@@ -28351,10 +28391,10 @@ const Sinusoid = _.extend({}, PlotDefaults, {
28351
28391
  return [a, b, c, d];
28352
28392
  },
28353
28393
  getFunctionForCoeffs: function (coeffs, x) {
28354
- const a = coeffs[0],
28355
- b = coeffs[1],
28356
- c = coeffs[2],
28357
- d = coeffs[3];
28394
+ const a = coeffs[0];
28395
+ const b = coeffs[1];
28396
+ const c = coeffs[2];
28397
+ const d = coeffs[3];
28358
28398
  return a * Math.sin(b * x - c) + d;
28359
28399
  },
28360
28400
  getPropsForCoeffs: function (coeffs) {
@@ -28367,10 +28407,10 @@ const Sinusoid = _.extend({}, PlotDefaults, {
28367
28407
  },
28368
28408
  getEquationString: function (coords) {
28369
28409
  const coeffs = this.getCoefficients(coords);
28370
- const a = coeffs[0],
28371
- b = coeffs[1],
28372
- c = coeffs[2],
28373
- d = coeffs[3];
28410
+ const a = coeffs[0];
28411
+ const b = coeffs[1];
28412
+ const c = coeffs[2];
28413
+ const d = coeffs[3];
28374
28414
  return "y = " + a.toFixed(3) + " sin(" + b.toFixed(3) + "x - " + c.toFixed(3) + ") + " + d.toFixed(3);
28375
28415
  },
28376
28416
  areEqual: function (coeffs1, coeffs2) {
@@ -28390,18 +28430,18 @@ const Tangent = _.extend({}, PlotDefaults, {
28390
28430
  return [a, b, c, d];
28391
28431
  },
28392
28432
  getFunctionForCoeffs: function (coeffs, x) {
28393
- const a = coeffs[0],
28394
- b = coeffs[1],
28395
- c = coeffs[2],
28396
- d = coeffs[3];
28433
+ const a = coeffs[0];
28434
+ const b = coeffs[1];
28435
+ const c = coeffs[2];
28436
+ const d = coeffs[3];
28397
28437
  return a * Math.tan(b * x - c) + d;
28398
28438
  },
28399
28439
  getEquationString: function (coords) {
28400
28440
  const coeffs = this.getCoefficients(coords);
28401
- const a = coeffs[0],
28402
- b = coeffs[1],
28403
- c = coeffs[2],
28404
- d = coeffs[3];
28441
+ const a = coeffs[0];
28442
+ const b = coeffs[1];
28443
+ const c = coeffs[2];
28444
+ const d = coeffs[3];
28405
28445
  return "y = " + a.toFixed(3) + " sin(" + b.toFixed(3) + "x - " + c.toFixed(3) + ") + " + d.toFixed(3);
28406
28446
  },
28407
28447
  areEqual: function (coeffs1, coeffs2) {
@@ -28460,9 +28500,9 @@ const Exponential = _.extend({}, PlotDefaults, {
28460
28500
  return [a, b, c];
28461
28501
  },
28462
28502
  getFunctionForCoeffs: function (coeffs, x) {
28463
- const a = coeffs[0],
28464
- b = coeffs[1],
28465
- c = coeffs[2];
28503
+ const a = coeffs[0];
28504
+ const b = coeffs[1];
28505
+ const c = coeffs[2];
28466
28506
  return a * Math.exp(b * x) + c;
28467
28507
  },
28468
28508
  getEquationString: function (coords, asymptote) {
@@ -28470,9 +28510,9 @@ const Exponential = _.extend({}, PlotDefaults, {
28470
28510
  return null;
28471
28511
  }
28472
28512
  const coeffs = this.getCoefficients(coords, asymptote);
28473
- const a = coeffs[0],
28474
- b = coeffs[1],
28475
- c = coeffs[2];
28513
+ const a = coeffs[0];
28514
+ const b = coeffs[1];
28515
+ const c = coeffs[2];
28476
28516
  return "y = " + a.toFixed(3) + "e^(" + b.toFixed(3) + "x) + " + c.toFixed(3);
28477
28517
  }
28478
28518
  });
@@ -28517,9 +28557,9 @@ const Logarithm = _.extend({}, PlotDefaults, {
28517
28557
  }
28518
28558
  },
28519
28559
  getFunctionForCoeffs: function (coeffs, x, asymptote) {
28520
- const a = coeffs[0],
28521
- b = coeffs[1],
28522
- c = coeffs[2];
28560
+ const a = coeffs[0];
28561
+ const b = coeffs[1];
28562
+ const c = coeffs[2];
28523
28563
  return a * Math.log(b * x + c);
28524
28564
  },
28525
28565
  getEquationString: function (coords, asymptote) {
@@ -28527,9 +28567,9 @@ const Logarithm = _.extend({}, PlotDefaults, {
28527
28567
  return null;
28528
28568
  }
28529
28569
  const coeffs = this.getCoefficients(coords, asymptote);
28530
- const a = coeffs[0],
28531
- b = coeffs[1],
28532
- c = coeffs[2];
28570
+ const a = coeffs[0];
28571
+ const b = coeffs[1];
28572
+ const c = coeffs[2];
28533
28573
  return "y = ln(" + a.toFixed(3) + "x + " + b.toFixed(3) + ") + " + c.toFixed(3);
28534
28574
  }
28535
28575
  });
@@ -28553,16 +28593,16 @@ const AbsoluteValue = _.extend({}, PlotDefaults, {
28553
28593
  return [m, horizontalOffset, verticalOffset];
28554
28594
  },
28555
28595
  getFunctionForCoeffs: function (coeffs, x) {
28556
- const m = coeffs[0],
28557
- horizontalOffset = coeffs[1],
28558
- verticalOffset = coeffs[2];
28596
+ const m = coeffs[0];
28597
+ const horizontalOffset = coeffs[1];
28598
+ const verticalOffset = coeffs[2];
28559
28599
  return m * Math.abs(x - horizontalOffset) + verticalOffset;
28560
28600
  },
28561
28601
  getEquationString: function (coords) {
28562
28602
  const coeffs = this.getCoefficients(coords);
28563
- const m = coeffs[0],
28564
- horizontalOffset = coeffs[1],
28565
- verticalOffset = coeffs[2];
28603
+ const m = coeffs[0];
28604
+ const horizontalOffset = coeffs[1];
28605
+ const verticalOffset = coeffs[2];
28566
28606
  return "y = " + m.toFixed(3) + "| x - " + horizontalOffset.toFixed(3) + "| + " + verticalOffset.toFixed(3);
28567
28607
  }
28568
28608
  });
@@ -31726,7 +31766,7 @@ const styles$e = StyleSheet.create({
31726
31766
  }
31727
31767
  });
31728
31768
 
31729
- const _excluded$2 = ["onFormatChange", "checkValidity", "useArrowKeys"];
31769
+ const _excluded$4 = ["onFormatChange", "checkValidity", "useArrowKeys"];
31730
31770
  const {
31731
31771
  firstNumericalParse,
31732
31772
  captureScratchpadTouchStart
@@ -31756,22 +31796,25 @@ const getNumericFormat = KhanMath.getNumericFormat;
31756
31796
  class NumberInput extends React.Component {
31757
31797
  constructor(...args) {
31758
31798
  super(...args);
31799
+ this.inputRef = /*#__PURE__*/React.createRef();
31759
31800
  this.state = {
31760
31801
  format: this.props.format
31761
31802
  };
31803
+ this._getInput = () => {
31804
+ if (!this.inputRef.current) {
31805
+ throw new PerseusError("Input ref accessed before set", Errors.Internal);
31806
+ }
31807
+ return this.inputRef.current;
31808
+ };
31762
31809
  /* Return the current "value" of this input
31763
31810
  * If empty, it returns the placeholder (if it is a number) or null
31764
31811
  */
31765
31812
  this.getValue = () => {
31766
- return this.parseInputValue(
31767
- // @ts-expect-error - TS2531 - Object is possibly 'null'. | TS2339 - Property 'value' does not exist on type 'Element | Text'.
31768
- ReactDOM__default.findDOMNode(this.refs.input).value // eslint-disable-line react/no-string-refs
31769
- );
31813
+ return this.parseInputValue(this._getInput().value);
31770
31814
  };
31771
31815
  /* Return the current string value of this input */
31772
31816
  this.getStringValue = () => {
31773
- // @ts-expect-error - TS2531 - Object is possibly 'null'. | TS2339 - Property 'value' does not exist on type 'Element | Text'.
31774
- return ReactDOM__default.findDOMNode(this.refs.input).value.toString(); // eslint-disable-line react/no-string-refs
31817
+ return this._getInput().toString();
31775
31818
  };
31776
31819
  this.parseInputValue = value => {
31777
31820
  if (value === "") {
@@ -31783,26 +31826,21 @@ class NumberInput extends React.Component {
31783
31826
  };
31784
31827
  /* Set text input focus to this input */
31785
31828
  this.focus = () => {
31786
- // @ts-expect-error - TS2531 - Object is possibly 'null'. | TS2339 - Property 'focus' does not exist on type 'Element | Text'.
31787
- ReactDOM__default.findDOMNode(this.refs.input).focus(); // eslint-disable-line react/no-string-refs
31829
+ this._getInput().focus();
31788
31830
  this._handleFocus();
31789
31831
  };
31790
31832
  this.blur = () => {
31791
- // @ts-expect-error - TS2531 - Object is possibly 'null'. | TS2339 - Property 'blur' does not exist on type 'Element | Text'.
31792
- ReactDOM__default.findDOMNode(this.refs.input).blur(); // eslint-disable-line react/no-string-refs
31833
+ this._getInput().blur();
31793
31834
  this._handleBlur();
31794
31835
  };
31795
31836
  this.setSelectionRange = (selectionStart, selectionEnd) => {
31796
- // @ts-expect-error - TS2531 - Object is possibly 'null'. | TS2339 - Property 'setSelectionRange' does not exist on type 'Element | Text'.
31797
- ReactDOM__default.findDOMNode(this).setSelectionRange(selectionStart, selectionEnd);
31837
+ this._getInput().setSelectionRange(selectionStart, selectionEnd);
31798
31838
  };
31799
31839
  this.getSelectionStart = () => {
31800
- // @ts-expect-error - TS2531 - Object is possibly 'null'. | TS2339 - Property 'selectionStart' does not exist on type 'Element | Text'.
31801
- return ReactDOM__default.findDOMNode(this).selectionStart;
31840
+ return this._getInput().selectionStart;
31802
31841
  };
31803
31842
  this.getSelectionEnd = () => {
31804
- // @ts-expect-error - TS2531 - Object is possibly 'null'. | TS2339 - Property 'selectionEnd' does not exist on type 'Element | Text'.
31805
- return ReactDOM__default.findDOMNode(this).selectionEnd;
31843
+ return this._getInput().selectionEnd;
31806
31844
  };
31807
31845
  this._checkValidity = value => {
31808
31846
  if (value == null) {
@@ -31862,9 +31900,7 @@ class NumberInput extends React.Component {
31862
31900
  }
31863
31901
  };
31864
31902
  this._setValue = (val, format) => {
31865
- // eslint-disable-next-line react/no-string-refs
31866
- // @ts-expect-error - TS2769 - No overload matches this call. | TS2339 - Property 'val' does not exist on type 'JQueryStatic'.
31867
- $(ReactDOM__default.findDOMNode(this.refs.input)).val(toNumericString(val, format));
31903
+ this._getInput().value = toNumericString(val, format);
31868
31904
  };
31869
31905
  }
31870
31906
  componentDidUpdate(prevProps) {
@@ -31884,13 +31920,11 @@ class NumberInput extends React.Component {
31884
31920
  classes = classes + " " + this.props.className;
31885
31921
  }
31886
31922
  const _this$props = this.props,
31887
- restProps = _objectWithoutPropertiesLoose(_this$props, _excluded$2);
31923
+ restProps = _objectWithoutPropertiesLoose(_this$props, _excluded$4);
31888
31924
  return /*#__PURE__*/React.createElement("input", _extends({}, restProps, {
31889
31925
  className: classes,
31890
- type: "text"
31891
- // eslint-disable-next-line react/no-string-refs
31892
- ,
31893
- ref: "input",
31926
+ type: "text",
31927
+ ref: this.inputRef,
31894
31928
  onChange: this._handleChange,
31895
31929
  onFocus: this._handleFocus,
31896
31930
  onBlur: this._handleBlur,
@@ -32811,12 +32845,12 @@ const MafsCssTransformWrapper = ({
32811
32845
  }
32812
32846
  }, children);
32813
32847
 
32814
- const _excluded$1 = ["children"];
32848
+ const _excluded$3 = ["children"];
32815
32849
  const TextLabel = _ref => {
32816
32850
  let {
32817
32851
  children
32818
32852
  } = _ref,
32819
- rest = _objectWithoutPropertiesLoose(_ref, _excluded$1);
32853
+ rest = _objectWithoutPropertiesLoose(_ref, _excluded$3);
32820
32854
  return /*#__PURE__*/React__default.createElement(Text$1, _extends({
32821
32855
  size: 16,
32822
32856
  svgTextProps: {
@@ -33397,15 +33431,18 @@ function useControlPoint(params) {
33397
33431
  } = useGraphConfig();
33398
33432
  const {
33399
33433
  point,
33400
- sequenceNumber,
33434
+ ariaDescribedBy,
33435
+ ariaLabel,
33436
+ ariaLive = "polite",
33401
33437
  color,
33402
- cursor,
33403
33438
  constrain = p => snap(snapStep, p),
33439
+ cursor,
33440
+ forwardedRef = noop$1,
33441
+ sequenceNumber = 1,
33404
33442
  onMove = noop$1,
33405
33443
  onClick = noop$1,
33406
33444
  onFocus = noop$1,
33407
- onBlur = noop$1,
33408
- forwardedRef = noop$1
33445
+ onBlur = noop$1
33409
33446
  } = params;
33410
33447
  const {
33411
33448
  strings,
@@ -33428,6 +33465,13 @@ function useControlPoint(params) {
33428
33465
  onMove,
33429
33466
  constrainKeyboardMovement: constrain
33430
33467
  });
33468
+
33469
+ // if custom aria label is not provided, will use default of sequence number and point coordinates
33470
+ const pointAriaLabel = ariaLabel || strings.srPointAtCoordinates({
33471
+ num: sequenceNumber,
33472
+ x: srFormatNumber(point[X], locale),
33473
+ y: srFormatNumber(point[Y], locale)
33474
+ });
33431
33475
  useLayoutEffect(() => {
33432
33476
  setForwardedRef(forwardedRef, focusableHandleRef.current);
33433
33477
  }, [forwardedRef]);
@@ -33437,15 +33481,9 @@ function useControlPoint(params) {
33437
33481
  tabIndex: disableKeyboardInteraction ? -1 : 0,
33438
33482
  ref: focusableHandleRef,
33439
33483
  role: "button",
33440
- "aria-label": strings.srPointAtCoordinates({
33441
- num: sequenceNumber,
33442
- x: srFormatNumber(point[X], locale),
33443
- y: srFormatNumber(point[Y], locale)
33444
- })
33445
- // aria-live="assertive" causes the new location of the point to be
33446
- // announced immediately on move.
33447
- ,
33448
- "aria-live": "assertive",
33484
+ "aria-describedby": ariaDescribedBy,
33485
+ "aria-label": pointAriaLabel,
33486
+ "aria-live": ariaLive,
33449
33487
  onFocus: event => {
33450
33488
  onFocus(event);
33451
33489
  setFocused(true);
@@ -36451,7 +36489,7 @@ const StatefulMafsGraph = /*#__PURE__*/React.forwardRef((props, ref) => {
36451
36489
  });
36452
36490
 
36453
36491
  const eq$1 = Util.eq;
36454
- const deepEq$2 = Util.deepEq;
36492
+ const deepEq$3 = Util.deepEq;
36455
36493
  function scoreInteractiveGraph(userInput, rubric) {
36456
36494
  // None-type graphs are not graded
36457
36495
  if (userInput.type === "none" && rubric.correct.type === "none") {
@@ -36501,7 +36539,7 @@ function scoreInteractiveGraph(userInput, rubric) {
36501
36539
  // If the parabola coefficients match, it's correct.
36502
36540
  const guessCoeffs = getQuadraticCoefficients(userInput.coords);
36503
36541
  const correctCoeffs = getQuadraticCoefficients(rubric.correct.coords);
36504
- if (deepEq$2(guessCoeffs, correctCoeffs)) {
36542
+ if (deepEq$3(guessCoeffs, correctCoeffs)) {
36505
36543
  return {
36506
36544
  type: "points",
36507
36545
  earned: 1,
@@ -36515,7 +36553,7 @@ function scoreInteractiveGraph(userInput, rubric) {
36515
36553
  const canonicalGuessCoeffs = canonicalSineCoefficients$1(guessCoeffs);
36516
36554
  const canonicalCorrectCoeffs = canonicalSineCoefficients$1(correctCoeffs);
36517
36555
  // If the canonical coefficients match, it's correct.
36518
- if (deepEq$2(canonicalGuessCoeffs, canonicalCorrectCoeffs)) {
36556
+ if (deepEq$3(canonicalGuessCoeffs, canonicalCorrectCoeffs)) {
36519
36557
  return {
36520
36558
  type: "points",
36521
36559
  earned: 1,
@@ -36524,7 +36562,7 @@ function scoreInteractiveGraph(userInput, rubric) {
36524
36562
  };
36525
36563
  }
36526
36564
  } else if (userInput.type === "circle" && rubric.correct.type === "circle") {
36527
- if (deepEq$2(userInput.center, rubric.correct.center) && eq$1(userInput.radius, rubric.correct.radius)) {
36565
+ if (deepEq$3(userInput.center, rubric.correct.center) && eq$1(userInput.radius, rubric.correct.radius)) {
36528
36566
  return {
36529
36567
  type: "points",
36530
36568
  earned: 1,
@@ -36546,7 +36584,7 @@ function scoreInteractiveGraph(userInput, rubric) {
36546
36584
  guess == null || guess.sort();
36547
36585
  // @ts-expect-error - TS2339 - Property 'sort' does not exist on type 'readonly Coord[]'.
36548
36586
  correct.sort();
36549
- if (deepEq$2(guess, correct)) {
36587
+ if (deepEq$3(guess, correct)) {
36550
36588
  return {
36551
36589
  type: "points",
36552
36590
  earned: 1,
@@ -36568,7 +36606,7 @@ function scoreInteractiveGraph(userInput, rubric) {
36568
36606
  /* exact */
36569
36607
  guess.sort();
36570
36608
  correct.sort();
36571
- match = deepEq$2(guess, correct);
36609
+ match = deepEq$3(guess, correct);
36572
36610
  }
36573
36611
  if (match) {
36574
36612
  return {
@@ -36583,7 +36621,7 @@ function scoreInteractiveGraph(userInput, rubric) {
36583
36621
  let correct = Util.deepClone(rubric.correct.coords);
36584
36622
  guess = _.invoke(guess, "sort").sort();
36585
36623
  correct = _.invoke(correct, "sort").sort();
36586
- if (deepEq$2(guess, correct)) {
36624
+ if (deepEq$3(guess, correct)) {
36587
36625
  return {
36588
36626
  type: "points",
36589
36627
  earned: 1,
@@ -36594,7 +36632,7 @@ function scoreInteractiveGraph(userInput, rubric) {
36594
36632
  } else if (userInput.type === "ray" && rubric.correct.type === "ray" && userInput.coords != null) {
36595
36633
  const guess = userInput.coords;
36596
36634
  const correct = rubric.correct.coords;
36597
- if (deepEq$2(guess[0], correct[0]) && collinear(correct[0], correct[1], guess[1])) {
36635
+ if (deepEq$3(guess[0], correct[0]) && collinear(correct[0], correct[1], guess[1])) {
36598
36636
  return {
36599
36637
  type: "points",
36600
36638
  earned: 1,
@@ -36621,7 +36659,7 @@ function scoreInteractiveGraph(userInput, rubric) {
36621
36659
  /* exact */
36622
36660
  match =
36623
36661
  // @ts-expect-error - TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'.
36624
- deepEq$2(guess[1], correct[1]) &&
36662
+ deepEq$3(guess[1], correct[1]) &&
36625
36663
  // @ts-expect-error - TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'.
36626
36664
  collinear(correct[1], correct[0], guess[0]) &&
36627
36665
  // @ts-expect-error - TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'.
@@ -40795,6 +40833,7 @@ const getPromptJSON$7 = () => {
40795
40833
  return getUnsupportedPromptJSON("measurer");
40796
40834
  };
40797
40835
 
40836
+ const _excluded$2 = ["imageUrl", "imageTop", "imageLeft"];
40798
40837
  const defaultImage = {
40799
40838
  url: null,
40800
40839
  top: 0,
@@ -40901,14 +40940,19 @@ Measurer.defaultProps = {
40901
40940
  };
40902
40941
  const propUpgrades = {
40903
40942
  "1": v0props => {
40904
- const v1props = _(v0props).chain().omit("imageUrl", "imageTop", "imageLeft").extend({
40943
+ const {
40944
+ imageUrl,
40945
+ imageTop,
40946
+ imageLeft
40947
+ } = v0props,
40948
+ rest = _objectWithoutPropertiesLoose(v0props, _excluded$2);
40949
+ return _extends({}, rest, {
40905
40950
  image: {
40906
- url: v0props.imageUrl,
40907
- top: v0props.imageTop,
40908
- left: v0props.imageLeft
40951
+ url: imageUrl,
40952
+ top: imageTop,
40953
+ left: imageLeft
40909
40954
  }
40910
- }).value();
40911
- return v1props;
40955
+ });
40912
40956
  }
40913
40957
  };
40914
40958
  var Measurer$1 = {
@@ -42436,14 +42480,28 @@ const getPromptJSON$5 = (renderProps, userInput) => {
42436
42480
  };
42437
42481
  };
42438
42482
 
42439
- function scoreOrderer(userInput, rubric) {
42483
+ /**
42484
+ * Checks user input from the orderer widget to see if the user has started
42485
+ * ordering the options, making the widget scorable.
42486
+ * @param userInput
42487
+ * @see `scoreOrderer` for more details.
42488
+ */
42489
+ function validateOrderer(userInput) {
42440
42490
  if (userInput.current.length === 0) {
42441
42491
  return {
42442
42492
  type: "invalid",
42443
42493
  message: null
42444
42494
  };
42445
42495
  }
42446
- const correct = _.isEqual(userInput.current, _.pluck(rubric.correctOptions, "content"));
42496
+ return null;
42497
+ }
42498
+
42499
+ function scoreOrderer(userInput, rubric) {
42500
+ const validateError = validateOrderer(userInput);
42501
+ if (validateError) {
42502
+ return validateError;
42503
+ }
42504
+ const correct = _.isEqual(userInput.current, rubric.correctOptions.map(option => option.content));
42447
42505
  return {
42448
42506
  type: "points",
42449
42507
  earned: correct ? 1 : 0,
@@ -42623,8 +42681,6 @@ Card.defaultProps = {
42623
42681
  animating: false,
42624
42682
  linterContext: linterContextDefault
42625
42683
  };
42626
- const NORMAL = "normal";
42627
- const HORIZONTAL = "horizontal";
42628
42684
  class Orderer extends React.Component {
42629
42685
  constructor(...args) {
42630
42686
  super(...args);
@@ -42771,7 +42827,7 @@ class Orderer extends React.Component {
42771
42827
  };
42772
42828
  this.findCorrectIndex = (draggable, list) => {
42773
42829
  // Find the correct index for a card given the current cards.
42774
- const isHorizontal = this.props.layout === HORIZONTAL;
42830
+ const isHorizontal = this.props.layout === "horizontal";
42775
42831
  // eslint-disable-next-line react/no-string-refs
42776
42832
  // @ts-expect-error - TS2769 - No overload matches this call.
42777
42833
  const $dragList = $(ReactDOM__default.findDOMNode(this.refs.dragList));
@@ -42821,28 +42877,24 @@ class Orderer extends React.Component {
42821
42877
  if (draggable == null) {
42822
42878
  return false;
42823
42879
  }
42824
- const isHorizontal = this.props.layout === HORIZONTAL,
42825
- // @ts-expect-error - TS2769 - No overload matches this call.
42826
- $draggable = $(ReactDOM__default.findDOMNode(draggable)),
42827
- // eslint-disable-next-line react/no-string-refs
42828
- // @ts-expect-error - TS2769 - No overload matches this call.
42829
- $bank = $(ReactDOM__default.findDOMNode(this.refs.bank)),
42830
- // @ts-expect-error - TS2339 - Property 'offset' does not exist on type 'JQueryStatic'.
42831
- draggableOffset = $draggable.offset(),
42832
- // @ts-expect-error - TS2339 - Property 'offset' does not exist on type 'JQueryStatic'.
42833
- bankOffset = $bank.offset(),
42834
- // @ts-expect-error - TS2339 - Property 'outerHeight' does not exist on type 'JQueryStatic'.
42835
- draggableHeight = $draggable.outerHeight(true),
42836
- // @ts-expect-error - TS2339 - Property 'outerHeight' does not exist on type 'JQueryStatic'.
42837
- bankHeight = $bank.outerHeight(true),
42838
- // @ts-expect-error - TS2339 - Property 'outerWidth' does not exist on type 'JQueryStatic'.
42839
- bankWidth = $bank.outerWidth(true),
42840
- // eslint-disable-next-line react/no-string-refs
42841
- dragList = ReactDOM__default.findDOMNode(this.refs.dragList);
42842
- // @ts-expect-error - TS2769 - No overload matches this call. | TS2339 - Property 'width' does not exist on type 'JQueryStatic'.
42843
- $(dragList).width();
42844
- const // @ts-expect-error - TS2339 - Property 'outerWidth' does not exist on type 'JQueryStatic'.
42845
- draggableWidth = $draggable.outerWidth(true);
42880
+ const isHorizontal = this.props.layout === "horizontal";
42881
+ // @ts-expect-error - TS2769 - No overload matches this call.
42882
+ const $draggable = $(ReactDOM__default.findDOMNode(draggable));
42883
+ // eslint-disable-next-line react/no-string-refs
42884
+ // @ts-expect-error - TS2769 - No overload matches this call.
42885
+ const $bank = $(ReactDOM__default.findDOMNode(this.refs.bank));
42886
+ // @ts-expect-error - TS2339 - Property 'offset' does not exist on type 'JQueryStatic'.
42887
+ const draggableOffset = $draggable.offset();
42888
+ // @ts-expect-error - TS2339 - Property 'offset' does not exist on type 'JQueryStatic'.
42889
+ const bankOffset = $bank.offset();
42890
+ // @ts-expect-error - TS2339 - Property 'outerHeight' does not exist on type 'JQueryStatic'.
42891
+ const draggableHeight = $draggable.outerHeight(true);
42892
+ // @ts-expect-error - TS2339 - Property 'outerHeight' does not exist on type 'JQueryStatic'.
42893
+ const bankHeight = $bank.outerHeight(true);
42894
+ // @ts-expect-error - TS2339 - Property 'outerWidth' does not exist on type 'JQueryStatic'.
42895
+ const bankWidth = $bank.outerWidth(true);
42896
+ // @ts-expect-error - TS2339 - Property 'outerWidth' does not exist on type 'JQueryStatic'.
42897
+ const draggableWidth = $draggable.outerWidth(true);
42846
42898
  if (isHorizontal) {
42847
42899
  return draggableOffset.top + draggableHeight / 2 < bankOffset.top + bankHeight;
42848
42900
  }
@@ -42991,8 +43043,8 @@ Orderer.defaultProps = {
42991
43043
  current: [],
42992
43044
  options: [],
42993
43045
  correctOptions: [],
42994
- height: NORMAL,
42995
- layout: HORIZONTAL,
43046
+ height: "normal",
43047
+ layout: "horizontal",
42996
43048
  linterContext: linterContextDefault
42997
43049
  };
42998
43050
  var Orderer$1 = {
@@ -43271,18 +43323,36 @@ const getPromptJSON$3 = () => {
43271
43323
  };
43272
43324
 
43273
43325
  const {
43274
- deepEq: deepEq$1
43326
+ deepEq: deepEq$2
43275
43327
  } = Util;
43276
- function scorePlotter(userInput, rubric) {
43277
- if (deepEq$1(userInput, rubric.starting)) {
43328
+
43329
+ /**
43330
+ * Checks user input to confirm it is not the same as the starting values for the graph.
43331
+ * This means the user has modified the graph, and the question can be scored.
43332
+ *
43333
+ * @see 'scorePlotter' for more details on scoring.
43334
+ */
43335
+ function validatePlotter(userInput, validationData) {
43336
+ if (deepEq$2(userInput, validationData.starting)) {
43278
43337
  return {
43279
43338
  type: "invalid",
43280
43339
  message: null
43281
43340
  };
43282
43341
  }
43342
+ return null;
43343
+ }
43344
+
43345
+ const {
43346
+ deepEq: deepEq$1
43347
+ } = Util;
43348
+ function scorePlotter(userInput, scoringData) {
43349
+ const validationError = validatePlotter(userInput, scoringData);
43350
+ if (validationError) {
43351
+ return validationError;
43352
+ }
43283
43353
  return {
43284
43354
  type: "points",
43285
- earned: deepEq$1(userInput, rubric.correct) ? 1 : 0,
43355
+ earned: deepEq$1(userInput, scoringData.correct) ? 1 : 0,
43286
43356
  total: 1,
43287
43357
  message: null
43288
43358
  };
@@ -44167,7 +44237,14 @@ const getPromptJSON$1 = userInput => {
44167
44237
  };
44168
44238
  };
44169
44239
 
44170
- function scoreSorter(userInput, rubric) {
44240
+ /**
44241
+ * Checks user input for the sorter widget to ensure that the user has made
44242
+ * changes before attempting to score the widget.
44243
+ * @param userInput
44244
+ * @see 'scoreSorter' in 'packages/perseus/src/widgets/sorter/score-sorter.ts'
44245
+ * for more details on how the sorter widget is scored.
44246
+ */
44247
+ function validateSorter(userInput) {
44171
44248
  // If the sorter widget hasn't been changed yet, we treat it as "empty" which
44172
44249
  // prevents the "Check" button from becoming active. We want the user
44173
44250
  // to make a change before trying to move forward. This makes an
@@ -44180,6 +44257,14 @@ function scoreSorter(userInput, rubric) {
44180
44257
  message: null
44181
44258
  };
44182
44259
  }
44260
+ return null;
44261
+ }
44262
+
44263
+ function scoreSorter(userInput, rubric) {
44264
+ const validationError = validateSorter(userInput);
44265
+ if (validationError) {
44266
+ return validationError;
44267
+ }
44183
44268
  const correct = Util.deepEq(userInput.options, rubric.correct);
44184
44269
  return {
44185
44270
  type: "points",
@@ -44758,7 +44843,7 @@ const init = function init() {
44758
44843
 
44759
44844
  // This file is processed by a Rollup plugin (replace) to inject the production
44760
44845
  const libName = "@khanacademy/perseus";
44761
- const libVersion = "43.0.1";
44846
+ const libVersion = "43.1.0";
44762
44847
  addLibraryVersionToPerseusDebug(libName, libVersion);
44763
44848
 
44764
44849
  const apiVersion = {
@@ -46362,7 +46447,7 @@ const str = {
46362
46447
  }
46363
46448
  };
46364
46449
 
46365
- const _excluded = ["item", "children", "shape", "serializedState", "onSerializedStateUpdated"];
46450
+ const _excluded$1 = ["item", "children", "shape", "serializedState", "onSerializedStateUpdated"];
46366
46451
 
46367
46452
  // TODO(mdr)
46368
46453
  // TODO(mdr)
@@ -46447,7 +46532,7 @@ class MultiRenderer extends React.Component {
46447
46532
  // explicitly pulling them out of `this.props` so I don't pass them to
46448
46533
  // `<Renderer>`. I'm not sure how else to do this.
46449
46534
  const _this$props = this.props,
46450
- otherProps = _objectWithoutPropertiesLoose(_this$props, _excluded);
46535
+ otherProps = _objectWithoutPropertiesLoose(_this$props, _excluded$1);
46451
46536
  return _extends({}, otherProps, {
46452
46537
  strings: this.context.strings
46453
46538
  });
@@ -48251,6 +48336,27 @@ function pair(parseA, parseB) {
48251
48336
  };
48252
48337
  }
48253
48338
 
48339
+ function pipeParsers(p) {
48340
+ return new ParserPipeline(p);
48341
+ }
48342
+ class ParserPipeline {
48343
+ constructor(parser) {
48344
+ this.parser = parser;
48345
+ }
48346
+ then(nextParser) {
48347
+ return new ParserPipeline(composeParsers(this.parser, nextParser));
48348
+ }
48349
+ }
48350
+ function composeParsers(parserA, parserB) {
48351
+ return (rawValue, ctx) => {
48352
+ const partialResult = parserA(rawValue, ctx);
48353
+ if (isFailure(partialResult)) {
48354
+ return partialResult;
48355
+ }
48356
+ return parserB(partialResult.value, ctx);
48357
+ };
48358
+ }
48359
+
48254
48360
  function record(parseKey, parseValue) {
48255
48361
  return (rawValue, ctx) => {
48256
48362
  if (!isObject(rawValue)) {
@@ -48331,6 +48437,20 @@ function either(parseA, parseB) {
48331
48437
  };
48332
48438
  }
48333
48439
 
48440
+ function defaulted(parser, fallback) {
48441
+ return (rawValue, ctx) => {
48442
+ if (rawValue == null) {
48443
+ return success(fallback(rawValue));
48444
+ }
48445
+ return parser(rawValue, ctx);
48446
+ };
48447
+ }
48448
+
48449
+ const parseImages = defaulted(record(string, object({
48450
+ width: number,
48451
+ height: number
48452
+ })), () => ({}));
48453
+
48334
48454
  function parseWidget(parseType, parseOptions) {
48335
48455
  return object({
48336
48456
  type: parseType,
@@ -48339,10 +48459,21 @@ function parseWidget(parseType, parseOptions) {
48339
48459
  alignment: optional(string),
48340
48460
  options: parseOptions,
48341
48461
  key: optional(number),
48342
- version: object({
48462
+ version: optional(object({
48343
48463
  major: number,
48344
48464
  minor: number
48345
- })
48465
+ }))
48466
+ });
48467
+ }
48468
+ function parseWidgetWithVersion(parseVersion, parseType, parseOptions) {
48469
+ return object({
48470
+ type: parseType,
48471
+ static: optional(boolean),
48472
+ graded: optional(boolean),
48473
+ alignment: optional(string),
48474
+ options: parseOptions,
48475
+ key: optional(number),
48476
+ version: parseVersion
48346
48477
  });
48347
48478
  }
48348
48479
 
@@ -48350,7 +48481,7 @@ const parseCategorizerWidget = parseWidget(constant("categorizer"), object({
48350
48481
  items: array(string),
48351
48482
  categories: array(string),
48352
48483
  randomizeItems: boolean,
48353
- static: boolean,
48484
+ static: defaulted(boolean, () => false),
48354
48485
  values: array(number),
48355
48486
  highlightLint: optional(boolean),
48356
48487
  linterContext: optional(object({
@@ -48382,7 +48513,7 @@ const parseDefinitionWidget = parseWidget(constant("definition"), object({
48382
48513
 
48383
48514
  const parseDropdownWidget = parseWidget(constant("dropdown"), object({
48384
48515
  placeholder: string,
48385
- static: boolean,
48516
+ static: defaulted(boolean, () => false),
48386
48517
  choices: array(object({
48387
48518
  content: string,
48388
48519
  correct: boolean
@@ -48405,9 +48536,12 @@ const parseAnswerForm = object({
48405
48536
  form: boolean,
48406
48537
  simplify: boolean,
48407
48538
  considered: enumeration("correct", "wrong", "ungraded"),
48408
- key: optional(string)
48539
+ key: pipeParsers(optional(union(string).or(number).parser)).then((key, ctx) => ctx.success(String(key))).parser
48409
48540
  });
48410
- const parseExpressionWidget = parseWidget(constant("expression"), object({
48541
+ const parseExpressionWidgetV1 = parseWidgetWithVersion(object({
48542
+ major: constant(1),
48543
+ minor: number
48544
+ }), constant("expression"), object({
48411
48545
  answerForms: array(parseAnswerForm),
48412
48546
  functions: array(string),
48413
48547
  times: boolean,
@@ -48416,23 +48550,68 @@ const parseExpressionWidget = parseWidget(constant("expression"), object({
48416
48550
  buttonSets: array(enumeration("basic", "basic+div", "trig", "prealgebra", "logarithms", "basic relations", "advanced relations")),
48417
48551
  buttonsVisible: optional(enumeration("always", "never", "focused"))
48418
48552
  }));
48553
+ const parseExpressionWidgetV0 = parseWidgetWithVersion(optional(object({
48554
+ major: constant(0),
48555
+ minor: number
48556
+ })), constant("expression"), object({
48557
+ functions: array(string),
48558
+ times: boolean,
48559
+ visibleLabel: optional(string),
48560
+ ariaLabel: optional(string),
48561
+ form: boolean,
48562
+ simplify: boolean,
48563
+ value: string,
48564
+ buttonSets: array(enumeration("basic", "basic+div", "trig", "prealgebra", "logarithms", "basic relations", "advanced relations")),
48565
+ buttonsVisible: optional(enumeration("always", "never", "focused"))
48566
+ }));
48567
+ function migrateV0ToV1(widget, ctx) {
48568
+ const {
48569
+ options
48570
+ } = widget;
48571
+ return ctx.success(_extends({}, widget, {
48572
+ version: {
48573
+ major: 1,
48574
+ minor: 0
48575
+ },
48576
+ options: {
48577
+ times: options.times,
48578
+ buttonSets: options.buttonSets,
48579
+ functions: options.functions,
48580
+ buttonsVisible: options.buttonsVisible,
48581
+ visibleLabel: options.visibleLabel,
48582
+ ariaLabel: options.ariaLabel,
48583
+ answerForms: [{
48584
+ considered: "correct",
48585
+ form: options.form,
48586
+ simplify: options.simplify,
48587
+ value: options.value
48588
+ }]
48589
+ }
48590
+ }));
48591
+ }
48592
+ const parseExpressionWidget = union(parseExpressionWidgetV1).or(pipeParsers(parseExpressionWidgetV0).then(migrateV0ToV1).parser).parser;
48419
48593
 
48420
- const parsePerseusRenderer = object({
48421
- content: string,
48594
+ const parsePerseusRenderer = defaulted(object({
48595
+ // TODO(benchristel): content is also defaulted to empty string in
48596
+ // renderer.tsx. See if we can remove one default or the other.
48597
+ content: defaulted(string, () => ""),
48422
48598
  // This module has an import cycle with parseWidgetsMap, because the
48423
48599
  // `group` widget can contain another renderer.
48424
48600
  // The anonymous function below ensures that we don't try to access
48425
48601
  // parseWidgetsMap before it's defined.
48426
- widgets: (rawVal, ctx) => parseWidgetsMap(rawVal, ctx),
48602
+ widgets: defaulted((rawVal, ctx) => parseWidgetsMap(rawVal, ctx), () => ({})),
48427
48603
  metadata: optional(array(string)),
48428
- images: record(string, object({
48429
- width: number,
48430
- height: number
48431
- }))
48432
- });
48604
+ images: parseImages
48605
+ }),
48606
+ // Default value
48607
+ () => ({
48608
+ content: "",
48609
+ widgets: {},
48610
+ images: {}
48611
+ }));
48433
48612
 
48434
48613
  const parseGradedGroupWidgetOptions = object({
48435
- title: string,
48614
+ title: defaulted(string, () => ""),
48436
48615
  hasHint: optional(nullable(boolean)),
48437
48616
  // This module has an import cycle with parsePerseusRenderer.
48438
48617
  // The anonymous function below ensures that we don't try to access
@@ -48526,7 +48705,7 @@ const parseIframeWidget = parseWidget(constant("iframe"), object({
48526
48705
  height: union(number).or(string).parser,
48527
48706
  allowFullScreen: boolean,
48528
48707
  allowTopNavigation: optional(boolean),
48529
- static: boolean
48708
+ static: defaulted(boolean, () => false)
48530
48709
  }));
48531
48710
 
48532
48711
  const parsePerseusImageBackground = object({
@@ -48905,13 +49084,13 @@ const parseMatcherWidget = parseWidget(constant("matcher"), object({
48905
49084
  padding: boolean
48906
49085
  }));
48907
49086
 
48908
- const parseMatrixWidget = parseWidget(constant("matrix"), object({
48909
- prefix: string,
48910
- suffix: string,
49087
+ const parseMatrixWidget = parseWidget(defaulted(constant("matrix"), () => "matrix"), object({
49088
+ prefix: optional(string),
49089
+ suffix: optional(string),
48911
49090
  answers: array(array(number)),
48912
- cursorPosition: array(number),
49091
+ cursorPosition: optional(array(number)),
48913
49092
  matrixBoardSize: array(number),
48914
- static: boolean
49093
+ static: optional(boolean)
48915
49094
  }));
48916
49095
 
48917
49096
  const parseMeasurerWidget = parseWidget(constant("measurer"), object({
@@ -48981,8 +49160,8 @@ const parseOrdererWidget = parseWidget(constant("orderer"), object({
48981
49160
  options: array(parseRenderer),
48982
49161
  correctOptions: array(parseRenderer),
48983
49162
  otherOptions: array(parseRenderer),
48984
- height: string,
48985
- layout: string
49163
+ height: enumeration("normal", "auto"),
49164
+ layout: enumeration("horizontal", "vertical")
48986
49165
  }));
48987
49166
 
48988
49167
  const parsePassageRefWidget = parseWidget(constant("passage-ref"), object({
@@ -49027,7 +49206,7 @@ const parsePythonProgramWidget = parseWidget(constant("python-program"), object(
49027
49206
 
49028
49207
  const parseRadioWidget = parseWidget(constant("radio"), object({
49029
49208
  choices: array(object({
49030
- content: string,
49209
+ content: defaulted(string, () => ""),
49031
49210
  clue: optional(string),
49032
49211
  correct: optional(boolean),
49033
49212
  isNoneOfTheAbove: optional(boolean),
@@ -49188,26 +49367,42 @@ const parseWidgetMapKeyComponents = pair(string, parseStringToPositiveInt);
49188
49367
  const parseHint = object({
49189
49368
  replace: optional(boolean),
49190
49369
  content: string,
49191
- widgets: parseWidgetsMap,
49370
+ widgets: defaulted(parseWidgetsMap, () => ({})),
49192
49371
  metadata: optional(array(string)),
49193
- images: record(string, object({
49194
- width: number,
49195
- height: number
49196
- }))
49372
+ images: parseImages
49197
49373
  });
49198
49374
 
49375
+ const _excluded = ["type", "options"];
49199
49376
  object({
49200
49377
  question: parsePerseusRenderer,
49201
- hints: array(parseHint),
49202
- answerArea: record(enumeration(...ItemExtras), boolean),
49203
- itemDataVersion: object({
49378
+ hints: defaulted(array(parseHint), () => []),
49379
+ answerArea: pipeParsers(defaulted(object({}), () => ({}))).then(migrateAnswerArea).then(record(enumeration(...ItemExtras), boolean)).parser,
49380
+ itemDataVersion: optional(object({
49204
49381
  major: number,
49205
49382
  minor: number
49206
- }),
49383
+ })),
49207
49384
  // Deprecated field
49208
49385
  answer: any
49209
49386
  });
49210
49387
 
49388
+ // Some answerAreas have extra fields, like:
49389
+ //
49390
+ // "answerArea": {
49391
+ // "type": "multiple",
49392
+ // "options": {
49393
+ // "content": "",
49394
+ // "images": {},
49395
+ // "widgets": {}
49396
+ // }
49397
+ // }
49398
+ //
49399
+ // The "type" and "options" fields don't seem to be used anywhere. This
49400
+ // migration function removes them.
49401
+ function migrateAnswerArea(rawValue, ctx) {
49402
+ const rest = _objectWithoutPropertiesLoose(rawValue, _excluded);
49403
+ return ctx.success(rest);
49404
+ }
49405
+
49211
49406
  /**
49212
49407
  * Helper to parse PerseusItem JSON
49213
49408
  * Why not just use JSON.parse? We want: