@khanacademy/perseus 43.0.1 → 44.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/dist/all-widgets.d.ts +2 -23
  2. package/dist/basic-widgets.d.ts +2 -23
  3. package/dist/components/graph.d.ts +1 -0
  4. package/dist/components/icon.d.ts +0 -1
  5. package/dist/components/multi-button-group.d.ts +1 -0
  6. package/dist/components/number-input.d.ts +5 -3
  7. package/dist/components/text-input.d.ts +4 -2
  8. package/dist/components/tooltip.d.ts +1 -0
  9. package/dist/es/index.js +476 -261
  10. package/dist/es/index.js.map +1 -1
  11. package/dist/es/strings.js +4 -2
  12. package/dist/es/strings.js.map +1 -1
  13. package/dist/extra-widgets.d.ts +2 -23
  14. package/dist/index.d.ts +2 -2
  15. package/dist/index.js +465 -250
  16. package/dist/index.js.map +1 -1
  17. package/dist/interactive2/interactive-util.d.ts +1 -1
  18. package/dist/interactive2/movable-point-options.d.ts +2 -2
  19. package/dist/interactive2/movable-point.d.ts +7 -7
  20. package/dist/interactive2/objective_.d.ts +1 -1
  21. package/dist/jipt-paragraphs.d.ts +2 -2
  22. package/dist/perseus-api.d.ts +2 -77
  23. package/dist/perseus-types.d.ts +17 -17
  24. package/dist/server-item-renderer.d.ts +26 -49
  25. package/dist/strings.d.ts +1 -0
  26. package/dist/strings.js +4 -2
  27. package/dist/strings.js.map +1 -1
  28. package/dist/styles/global-styles.d.ts +0 -1
  29. package/dist/styles/shared.d.ts +0 -1
  30. package/dist/traversal.d.ts +1 -1
  31. package/dist/util/a11y.d.ts +0 -1
  32. package/dist/util/debounce.d.ts +1 -1
  33. package/dist/util/parse-perseus-json/general-purpose-parsers/defaulted.d.ts +1 -1
  34. package/dist/util/parse-perseus-json/general-purpose-parsers/index.d.ts +2 -0
  35. package/dist/util/parse-perseus-json/general-purpose-parsers/pipe-parsers.d.ts +7 -0
  36. package/dist/util/parse-perseus-json/general-purpose-parsers/pipe-parsers.typetest.d.ts +1 -0
  37. package/dist/util/parse-perseus-json/general-purpose-parsers/unknown.d.ts +2 -0
  38. package/dist/util/parse-perseus-json/perseus-parsers/graded-group-widget.d.ts +2 -2
  39. package/dist/util/parse-perseus-json/perseus-parsers/images-map.d.ts +5 -0
  40. package/dist/util/parse-perseus-json/perseus-parsers/widget.d.ts +4 -0
  41. package/dist/validation.types.d.ts +14 -6
  42. package/dist/widgets/categorizer/categorizer.d.ts +2 -2
  43. package/dist/widgets/categorizer/score-categorizer.d.ts +2 -2
  44. package/dist/widgets/categorizer/validate-categorizer.d.ts +15 -0
  45. package/dist/widgets/definition/definition.d.ts +1 -1
  46. package/dist/widgets/deprecated-standin/deprecated-standin.d.ts +1 -1
  47. package/dist/widgets/dropdown/dropdown.d.ts +9 -1
  48. package/dist/widgets/dropdown/validate-dropdown.d.ts +10 -0
  49. package/dist/widgets/explanation/explanation.d.ts +1 -1
  50. package/dist/widgets/expression/expression.d.ts +92 -217
  51. package/dist/widgets/image/image.d.ts +1 -1
  52. package/dist/widgets/interaction/interaction.d.ts +1 -1
  53. package/dist/widgets/interactive-graphs/graphs/components/movable-point.d.ts +11 -7
  54. package/dist/widgets/interactive-graphs/graphs/components/use-control-point.d.ts +7 -3
  55. package/dist/widgets/interactive-graphs/graphs/quadratic.d.ts +1 -1
  56. package/dist/widgets/interactive-graphs/types.d.ts +1 -0
  57. package/dist/widgets/label-image/answer-pill.d.ts +0 -1
  58. package/dist/widgets/label-image/label-image.d.ts +5 -65
  59. package/dist/widgets/label-image/marker.d.ts +0 -1
  60. package/dist/widgets/matrix/matrix.d.ts +12 -5
  61. package/dist/widgets/measurer/measurer.d.ts +2 -2
  62. package/dist/widgets/molecule/molecule.d.ts +1 -1
  63. package/dist/widgets/orderer/validate-orderer.d.ts +12 -0
  64. package/dist/widgets/passage/passage-markdown.d.ts +2 -2
  65. package/dist/widgets/passage/passage.d.ts +1 -1
  66. package/dist/widgets/passage-ref/passage-ref.d.ts +1 -1
  67. package/dist/widgets/passage-ref-target/passage-ref-target.d.ts +1 -1
  68. package/dist/widgets/plotter/plotter.d.ts +2 -2
  69. package/dist/widgets/plotter/score-plotter.d.ts +2 -2
  70. package/dist/widgets/plotter/validate-plotter.d.ts +12 -0
  71. package/dist/widgets/radio/choice-icon/shared-styles.d.ts +0 -1
  72. package/dist/widgets/radio/choice-none-above.d.ts +1 -1
  73. package/dist/widgets/radio/radio.d.ts +3 -3
  74. package/dist/widgets/radio/validate-radio.d.ts +14 -0
  75. package/dist/widgets/sorter/validate-sorter.d.ts +13 -0
  76. package/dist/widgets/video/video.d.ts +1 -1
  77. package/package.json +5 -5
  78. package/dist/util/parse-perseus-json/general-purpose-parsers/compose-parsers.d.ts +0 -2
package/dist/es/index.js CHANGED
@@ -4,7 +4,7 @@ import * as PerseusLinter from '@khanacademy/perseus-linter';
4
4
  import { linterContextDefault, Rule } from '@khanacademy/perseus-linter';
5
5
  import { View, RenderStateRoot, UniqueIDProvider, useOnMountEffect, useLatestRef, useUniqueIdWithMock, Text as Text$2 } from '@khanacademy/wonder-blocks-core';
6
6
  import Tooltip$1 from '@khanacademy/wonder-blocks-tooltip';
7
- import { HeadingMedium, LabelSmall, Body, LabelMedium } from '@khanacademy/wonder-blocks-typography';
7
+ import { HeadingMedium, LabelSmall, LabelLarge, Body, LabelMedium } from '@khanacademy/wonder-blocks-typography';
8
8
  import { StyleSheet, css } from 'aphrodite';
9
9
  import classNames$1 from 'classnames';
10
10
  import * as React from 'react';
@@ -249,7 +249,8 @@ const mockStrings = {
249
249
  srInteractiveElements: ({
250
250
  elements
251
251
  }) => `Interactive elements: ${elements}`,
252
- srNoInteractiveElements: "No interactive elements"
252
+ srNoInteractiveElements: "No interactive elements",
253
+ selectAnAnswer: "Select an answer"
253
254
  };
254
255
 
255
256
  /**
@@ -2760,8 +2761,6 @@ async function getImageSizeModern(url) {
2760
2761
  });
2761
2762
  }
2762
2763
 
2763
- /* eslint-disable @babel/no-invalid-this, getter-return, one-var */
2764
-
2765
2764
  // TODO: dedupe this with Coord in interactive2/types.js
2766
2765
 
2767
2766
  let supportsPassive = false;
@@ -2837,8 +2836,8 @@ function shuffle$3(array, randomSeed, ensurePermuted = false) {
2837
2836
  do {
2838
2837
  // Fischer-Yates shuffle
2839
2838
  for (let top = shuffled.length; top > 0; top--) {
2840
- const newEnd = Math.floor(random() * top),
2841
- temp = shuffled[newEnd];
2839
+ const newEnd = Math.floor(random() * top);
2840
+ const temp = shuffled[newEnd];
2842
2841
 
2843
2842
  // @ts-expect-error - TS2542 - Index signature in type 'readonly T[]' only permits reading.
2844
2843
  shuffled[newEnd] = shuffled[top - 1];
@@ -3170,13 +3169,13 @@ function eq$4(x, y) {
3170
3169
  * Deep approximate equality on primitives, numbers, arrays, and objects.
3171
3170
  * Recursive.
3172
3171
  */
3173
- function deepEq$6(x, y) {
3172
+ function deepEq$7(x, y) {
3174
3173
  if (Array.isArray(x) && Array.isArray(y)) {
3175
3174
  if (x.length !== y.length) {
3176
3175
  return false;
3177
3176
  }
3178
3177
  for (let i = 0; i < x.length; i++) {
3179
- if (!deepEq$6(x[i], y[i])) {
3178
+ if (!deepEq$7(x[i], y[i])) {
3180
3179
  return false;
3181
3180
  }
3182
3181
  }
@@ -3194,10 +3193,10 @@ function deepEq$6(x, y) {
3194
3193
  if (typeof x === "object" && typeof y === "object" && !!x && !!y) {
3195
3194
  return x === y || _.all(x, function (v, k) {
3196
3195
  // @ts-expect-error - TS2536 - Type 'CollectionKey<T>' cannot be used to index type 'T'.
3197
- return deepEq$6(y[k], v);
3196
+ return deepEq$7(y[k], v);
3198
3197
  }) && _.all(y, function (v, k) {
3199
3198
  // @ts-expect-error - TS2536 - Type 'CollectionKey<T>' cannot be used to index type 'T'.
3200
- return deepEq$6(x[k], v);
3199
+ return deepEq$7(x[k], v);
3201
3200
  });
3202
3201
  }
3203
3202
  if (typeof x === "object" && !!x || typeof y === "object" && !!y) {
@@ -3478,7 +3477,7 @@ const Util = {
3478
3477
  tickStepFromNumTicks,
3479
3478
  constrainedTickStepsFromTickSteps,
3480
3479
  eq: eq$4,
3481
- deepEq: deepEq$6,
3480
+ deepEq: deepEq$7,
3482
3481
  parseQueryString,
3483
3482
  updateQueryString: updateQueryString$2,
3484
3483
  strongEncodeURIComponent,
@@ -10509,7 +10508,9 @@ const getClipPoint = function getClipPoint(graph, coord, angle) {
10509
10508
  return clipPoint;
10510
10509
  };
10511
10510
 
10512
- /* eslint-disable @babel/no-invalid-this */
10511
+ /**
10512
+ * Default methods for a wrapped movable.
10513
+ */
10513
10514
 
10514
10515
  /*
10515
10516
  * attr() and animate() simply pass the
@@ -10767,7 +10768,7 @@ class WrappedLine extends WrappedDrawing {
10767
10768
  */
10768
10769
  const {
10769
10770
  eq: eq$3,
10770
- deepEq: deepEq$5
10771
+ deepEq: deepEq$6
10771
10772
  } = Util;
10772
10773
 
10773
10774
  // This should really be a readonly tuple of [number, number]
@@ -10927,7 +10928,7 @@ function similar(coords1, coords2, tolerance) {
10927
10928
  angles = rotate(angles, i);
10928
10929
  // @ts-expect-error - TS4104 - The type 'readonly number[]' is 'readonly' and cannot be assigned to the mutable type 'number[]'.
10929
10930
  sides = rotate(sides, i);
10930
- if (deepEq$5(angles1, angles)) {
10931
+ if (deepEq$6(angles1, angles)) {
10931
10932
  const sidePairs = _.zip(sides1, sides);
10932
10933
  const factors = _.map(sidePairs, function (pair) {
10933
10934
  return pair[0] / pair[1];
@@ -13865,7 +13866,7 @@ _.extend(MovableAngle.prototype, {
13865
13866
 
13866
13867
  /* eslint-disable @babel/no-invalid-this */
13867
13868
  const nestedMap$1 = Util.nestedMap;
13868
- const deepEq$4 = Util.deepEq;
13869
+ const deepEq$5 = Util.deepEq;
13869
13870
 
13870
13871
  /**
13871
13872
  * A base class for all Graphie Movables
@@ -13962,7 +13963,7 @@ const createSimpleClass = function createSimpleClass(addFunction) {
13962
13963
  this._prevProps = this.props;
13963
13964
  },
13964
13965
  modify: function (graphie) {
13965
- if (!deepEq$4(this.props, this._prevProps)) {
13966
+ if (!deepEq$5(this.props, this._prevProps)) {
13966
13967
  this.remove();
13967
13968
  this.add(graphie);
13968
13969
  this._prevProps = this.props;
@@ -16576,7 +16577,7 @@ var Movables = {
16576
16577
  const GraphieMovable = GraphieClasses.GraphieMovable;
16577
16578
  const createGraphie = GraphUtils.createGraphie;
16578
16579
  const {
16579
- deepEq: deepEq$3,
16580
+ deepEq: deepEq$4,
16580
16581
  nestedMap
16581
16582
  } = Util;
16582
16583
  const {
@@ -16629,7 +16630,7 @@ class Graphie extends React.Component {
16629
16630
  };
16630
16631
  this._setupGraphie = () => {
16631
16632
  this._removeMovables();
16632
- const graphieDiv = ReactDOM__default.findDOMNode(this.graphieDivRef.current);
16633
+ const graphieDiv = this.graphieDivRef.current;
16633
16634
  if (graphieDiv == null || graphieDiv instanceof Text) {
16634
16635
  throw new Error("No graphie container div found");
16635
16636
  }
@@ -16818,7 +16819,7 @@ class Graphie extends React.Component {
16818
16819
  if (this.props.setup !== prevProps.setup) {
16819
16820
  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
16821
  }
16821
- if (!deepEq$3(this.props.options, prevProps.options) || !deepEq$3(this.props.box, prevProps.box) || !deepEq$3(this.props.range, prevProps.range)) {
16822
+ if (!deepEq$4(this.props.options, prevProps.options) || !deepEq$4(this.props.box, prevProps.box) || !deepEq$4(this.props.range, prevProps.range)) {
16822
16823
  this._setupGraphie();
16823
16824
  }
16824
16825
  this._updateMovables();
@@ -18536,7 +18537,7 @@ const rules$1 = _extends({}, pureMarkdownRules, {
18536
18537
  // The href prop should always be here, but just in case somehow the article's
18537
18538
  // link was not put together properly, let's make sure it's there so we
18538
18539
  // don't break the entire page.
18539
- const isKAUrl = href ? href.match(/https?:\/\/[^\/]*khanacademy.org|^\//) : false;
18540
+ const isKAUrl = href ? href.match(/https?:\/\/[^/]*khanacademy.org|^\//) : false;
18540
18541
  if (!isKAUrl) {
18541
18542
  // Prevents "reverse tabnabbing" phishing attacks
18542
18543
  // @ts-expect-error - TS2322 - Type '"noopener noreferrer"' is not assignable to type 'null'.
@@ -20889,36 +20890,36 @@ function uniqueIdForInput(prefix = "input-") {
20889
20890
  class TextInput extends React.Component {
20890
20891
  constructor(props) {
20891
20892
  super(props);
20893
+ this.inputRef = /*#__PURE__*/React.createRef();
20892
20894
  this.id = void 0;
20895
+ this._getInput = () => {
20896
+ if (!this.inputRef.current) {
20897
+ throw new PerseusError("Input ref accessed before set", Errors.Internal);
20898
+ }
20899
+ return this.inputRef.current;
20900
+ };
20893
20901
  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();
20902
+ this._getInput().focus();
20896
20903
  };
20897
20904
  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();
20905
+ this._getInput().blur();
20900
20906
  };
20901
20907
  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;
20908
+ var _this$inputRef$curren;
20909
+ return (_this$inputRef$curren = this.inputRef.current) == null ? void 0 : _this$inputRef$curren.value;
20905
20910
  };
20906
20911
  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();
20912
+ var _this$inputRef$curren2;
20913
+ return (_this$inputRef$curren2 = this.inputRef.current) == null ? void 0 : _this$inputRef$curren2.value.toString();
20910
20914
  };
20911
20915
  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);
20916
+ this._getInput().setSelectionRange(selectionStart, selectionEnd);
20914
20917
  };
20915
20918
  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;
20919
+ return this._getInput().selectionStart;
20918
20920
  };
20919
20921
  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;
20922
+ return this._getInput().selectionEnd;
20922
20923
  };
20923
20924
  if (props.id) {
20924
20925
  this.id = props.id;
@@ -20939,6 +20940,7 @@ class TextInput extends React.Component {
20939
20940
  } = this.props;
20940
20941
  const formattedValue = value === null ? "" : value.toString();
20941
20942
  return /*#__PURE__*/React.createElement(TextField, {
20943
+ ref: this.inputRef,
20942
20944
  style: style,
20943
20945
  disabled: disabled,
20944
20946
  id: this.id,
@@ -20973,7 +20975,7 @@ function _objectWithoutPropertiesLoose(r, e) {
20973
20975
  return t;
20974
20976
  }
20975
20977
 
20976
- const _excluded$5 = ["position", "visibility", "left", "top"];
20978
+ const _excluded$8 = ["position", "visibility", "left", "top"];
20977
20979
  const zIndex = 10;
20978
20980
  let HorizontalDirection = /*#__PURE__*/function (HorizontalDirection) {
20979
20981
  HorizontalDirection["Left"] = "left";
@@ -21024,7 +21026,7 @@ const TooltipArrow = _ref => {
21024
21026
  left = 100,
21025
21027
  top = 0
21026
21028
  } = _ref,
21027
- props = _objectWithoutPropertiesLoose(_ref, _excluded$5);
21029
+ props = _objectWithoutPropertiesLoose(_ref, _excluded$8);
21028
21030
  // TODO(aria): Think about adding a box-shadow to the triangle here
21029
21031
  // See http://css-tricks.com/triangle-with-shadow/
21030
21032
 
@@ -21134,6 +21136,7 @@ const HORIZONTAL_ALIGNMNENTS = {
21134
21136
  class Tooltip extends React.Component {
21135
21137
  constructor(...args) {
21136
21138
  super(...args);
21139
+ this.tooltipContainerRef = /*#__PURE__*/React.createRef();
21137
21140
  this.state = {
21138
21141
  height: null // used for offsetting "top" positioned tooltips
21139
21142
  };
@@ -21208,8 +21211,7 @@ class Tooltip extends React.Component {
21208
21211
  display: this.props.show ? "block" : "none"
21209
21212
  }
21210
21213
  }, /*#__PURE__*/React.createElement("div", {
21211
- // eslint-disable-next-line react/no-string-refs
21212
- ref: "tooltipContainer",
21214
+ ref: this.tooltipContainerRef,
21213
21215
  className: "tooltipContainer",
21214
21216
  style: {
21215
21217
  position: "absolute",
@@ -21236,10 +21238,8 @@ class Tooltip extends React.Component {
21236
21238
  }, this.props.children.slice(1)), arrowBelow));
21237
21239
  }
21238
21240
  _updateHeight() {
21239
- const tooltipContainer = ReactDOM__default.findDOMNode(
21240
- // eslint-disable-next-line react/no-string-refs
21241
- this.refs.tooltipContainer);
21242
- const height = tooltipContainer.offsetHeight;
21241
+ const tooltipContainer = this.tooltipContainerRef.current;
21242
+ const height = (tooltipContainer == null ? void 0 : tooltipContainer.offsetHeight) || 0;
21243
21243
  if (height !== this.state.height) {
21244
21244
  this.setState({
21245
21245
  height
@@ -21384,7 +21384,7 @@ InputWithExamples.defaultProps = {
21384
21384
  className: ""
21385
21385
  };
21386
21386
 
21387
- const _excluded$4 = ["keypadElement", "onFocus", "value"];
21387
+ const _excluded$7 = ["keypadElement", "onFocus", "value"];
21388
21388
  class SimpleKeypadInput extends React.Component {
21389
21389
  constructor(...args) {
21390
21390
  super(...args);
@@ -21429,7 +21429,7 @@ class SimpleKeypadInput extends React.Component {
21429
21429
  onFocus,
21430
21430
  value
21431
21431
  } = _this$props,
21432
- rest = _objectWithoutPropertiesLoose(_this$props, _excluded$4);
21432
+ rest = _objectWithoutPropertiesLoose(_this$props, _excluded$7);
21433
21433
  return (
21434
21434
  /*#__PURE__*/
21435
21435
  // @ts-expect-error - TS2769 - No overload matches this call.
@@ -21991,7 +21991,7 @@ function maybeParsePercentInput(inputValue, normalizedAnswerExpected) {
21991
21991
  return value / 100;
21992
21992
  }
21993
21993
 
21994
- // Otherwise, we return input valuåe (number) stripped of the "%".
21994
+ // Otherwise, we return input value (number) stripped of the "%".
21995
21995
  return value;
21996
21996
  }
21997
21997
  function scoreNumericInput(userInput, rubric, strings) {
@@ -25662,14 +25662,14 @@ var Choice$1 = /*#__PURE__*/React.forwardRef((props, ref) => /*#__PURE__*/React.
25662
25662
  forwardedRef: ref
25663
25663
  })));
25664
25664
 
25665
- const _excluded$3 = ["content", "forwardedRef", "showContent"];
25665
+ const _excluded$6 = ["content", "forwardedRef", "showContent"];
25666
25666
  const ChoiceNoneAbove = function ChoiceNoneAbove(_ref) {
25667
25667
  let {
25668
25668
  content,
25669
25669
  forwardedRef,
25670
25670
  showContent = true
25671
25671
  } = _ref,
25672
- rest = _objectWithoutPropertiesLoose(_ref, _excluded$3);
25672
+ rest = _objectWithoutPropertiesLoose(_ref, _excluded$6);
25673
25673
  const {
25674
25674
  strings
25675
25675
  } = usePerseusI18n();
@@ -26040,7 +26040,15 @@ const styles$p = StyleSheet.create({
26040
26040
  }
26041
26041
  });
26042
26042
 
26043
- function scoreRadio(userInput, rubric, strings) {
26043
+ /**
26044
+ * Checks if the user has selected at least one option. Additional validation
26045
+ * is done in scoreRadio to check if the number of selected options is correct
26046
+ * and if the user has selected both a correct option and the "none of the above"
26047
+ * option.
26048
+ * @param userInput
26049
+ * @see `scoreRadio` for the additional validation logic and the scoring logic.
26050
+ */
26051
+ function validateRadio(userInput) {
26044
26052
  const numSelected = userInput.choicesSelected.reduce((sum, selected) => {
26045
26053
  return sum + (selected ? 1 : 0);
26046
26054
  }, 0);
@@ -26050,6 +26058,17 @@ function scoreRadio(userInput, rubric, strings) {
26050
26058
  message: null
26051
26059
  };
26052
26060
  }
26061
+ return null;
26062
+ }
26063
+
26064
+ function scoreRadio(userInput, rubric, strings) {
26065
+ const validationError = validateRadio(userInput);
26066
+ if (validationError) {
26067
+ return validationError;
26068
+ }
26069
+ const numSelected = userInput.choicesSelected.reduce((sum, selected) => {
26070
+ return sum + (selected ? 1 : 0);
26071
+ }, 0);
26053
26072
  const numCorrect = rubric.choices.reduce((sum, currentChoice) => {
26054
26073
  return currentChoice.correct ? sum + 1 : sum;
26055
26074
  }, 0);
@@ -26426,6 +26445,7 @@ Radio$1.defaultProps = {
26426
26445
  showSolutions: "none"
26427
26446
  };
26428
26447
 
26448
+ const _excluded$5 = ["noneOfTheAbove"];
26429
26449
  const {
26430
26450
  shuffle: shuffle$2,
26431
26451
  random
@@ -26514,17 +26534,15 @@ const transform = (widgetOptions, strings, problemNum) => {
26514
26534
  };
26515
26535
  const propUpgrades$1 = {
26516
26536
  "1": v0props => {
26517
- let choices;
26518
- let hasNoneOfTheAbove;
26519
- if (!v0props.noneOfTheAbove) {
26520
- choices = v0props.choices;
26521
- hasNoneOfTheAbove = false;
26522
- } else {
26537
+ const {
26538
+ noneOfTheAbove
26539
+ } = v0props,
26540
+ rest = _objectWithoutPropertiesLoose(v0props, _excluded$5);
26541
+ if (noneOfTheAbove) {
26523
26542
  throw new Error("radio widget v0 no longer supports auto noneOfTheAbove");
26524
26543
  }
26525
- return _.extend(_.omit(v0props, "noneOfTheAbove"), {
26526
- choices: choices,
26527
- hasNoneOfTheAbove: hasNoneOfTheAbove
26544
+ return _extends({}, rest, {
26545
+ hasNoneOfTheAbove: false
26528
26546
  });
26529
26547
  }
26530
26548
  };
@@ -26562,23 +26580,36 @@ const getPromptJSON$n = (renderProps, userInput) => {
26562
26580
  };
26563
26581
  };
26564
26582
 
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) {
26583
+ /**
26584
+ * Checks userInput from the categorizer widget to see if the user has selected
26585
+ * a category for each item.
26586
+ * @param userInput - The user's input corresponding to an array of indices that
26587
+ * represent the selected category for each row/item.
26588
+ * @param validationData - An array of strings corresponding to each row/item
26589
+ * @param strings - Used to provide a validation message
26590
+ */
26591
+ function validateCategorizer(userInput, validationData, strings) {
26592
+ const incomplete = validationData.items.some((_, i) => userInput.values[i] == null);
26593
+ if (incomplete) {
26577
26594
  return {
26578
26595
  type: "invalid",
26579
26596
  message: strings.invalidSelection
26580
26597
  };
26581
26598
  }
26599
+ return null;
26600
+ }
26601
+
26602
+ function scoreCategorizer(userInput, scoringData, strings) {
26603
+ const validationError = validateCategorizer(userInput, scoringData, strings);
26604
+ if (validationError) {
26605
+ return validationError;
26606
+ }
26607
+ let allCorrect = true;
26608
+ scoringData.values.forEach((value, i) => {
26609
+ if (userInput.values[i] !== value) {
26610
+ allCorrect = false;
26611
+ }
26612
+ });
26582
26613
  return {
26583
26614
  type: "points",
26584
26615
  earned: allCorrect ? 1 : 0,
@@ -27124,15 +27155,26 @@ const getPromptJSON$k = (renderProps, userInput) => {
27124
27155
  };
27125
27156
  };
27126
27157
 
27127
- function scoreDropdown(userInput, rubric) {
27128
- const selected = userInput.value;
27129
- if (selected === 0) {
27158
+ /**
27159
+ * Checks if the user has selected an item from the dropdown before scoring.
27160
+ * This is shown with a userInput value / index other than 0.
27161
+ */
27162
+ function validateDropdown(userInput) {
27163
+ if (userInput.value === 0) {
27130
27164
  return {
27131
27165
  type: "invalid",
27132
27166
  message: null
27133
27167
  };
27134
27168
  }
27135
- const correct = rubric.choices[selected - 1].correct;
27169
+ return null;
27170
+ }
27171
+
27172
+ function scoreDropdown(userInput, rubric) {
27173
+ const validationError = validateDropdown(userInput);
27174
+ if (validationError) {
27175
+ return validationError;
27176
+ }
27177
+ const correct = rubric.choices[userInput.value - 1].correct;
27136
27178
  return {
27137
27179
  type: "points",
27138
27180
  earned: correct ? 1 : 0,
@@ -27181,23 +27223,40 @@ class Dropdown extends React.Component {
27181
27223
  value: String(i + 1),
27182
27224
  label: choice
27183
27225
  }))];
27184
- return /*#__PURE__*/React.createElement("div", {
27185
- // NOTE(jared): These are required to prevent weird behavior
27186
- // When there's a dropdown in a zoomable table.
27226
+ return /*#__PURE__*/React.createElement(UniqueIDProvider, {
27227
+ scope: "dropdown-widget",
27228
+ mockOnFirstRender: true
27229
+ }, ids => /*#__PURE__*/React.createElement(View
27230
+ // NOTE(jared): These are required to prevent weird behavior
27231
+ // When there's a dropdown in a zoomable table.
27232
+ , {
27187
27233
  onClick: e => {
27188
27234
  e.stopPropagation();
27189
27235
  },
27190
27236
  onTouchStart: e => {
27191
27237
  e.stopPropagation();
27192
27238
  }
27193
- }, /*#__PURE__*/React.createElement(SingleSelect, {
27239
+ }, this.props.visibleLabel && /*#__PURE__*/React.createElement(LabelLarge, {
27240
+ tag: "label",
27241
+ id: ids.get("dropdown-label")
27242
+ }, this.props.visibleLabel), /*#__PURE__*/React.createElement(SingleSelect, {
27243
+ id: ids.get("dropdown"),
27194
27244
  placeholder: "",
27195
27245
  onChange: value => this._handleChange(parseInt(value)),
27196
27246
  selectedValue: String(this.props.selected),
27197
- disabled: this.props.apiOptions.readOnly
27198
- }, children));
27247
+ disabled: this.props.apiOptions.readOnly,
27248
+ "aria-label": this.props.ariaLabel || this.context.strings.selectAnAnswer,
27249
+ "aria-labelledby": ids.get("dropdown-label")
27250
+ // This is currently necessary for SRs to read the labels properly.
27251
+ // However, WB is working on a change to add the "combobox" role to
27252
+ // all dropdowns.
27253
+ // See https://khanacademy.atlassian.net/browse/WB-1671
27254
+ ,
27255
+ role: "combobox"
27256
+ }, children)));
27199
27257
  }
27200
27258
  }
27259
+ Dropdown.contextType = PerseusI18nContext;
27201
27260
  Dropdown.defaultProps = {
27202
27261
  choices: [],
27203
27262
  selected: 0,
@@ -27207,6 +27266,8 @@ Dropdown.defaultProps = {
27207
27266
  const optionsTransform = widgetOptions => {
27208
27267
  return {
27209
27268
  placeholder: widgetOptions.placeholder,
27269
+ visibleLabel: widgetOptions.visibleLabel,
27270
+ ariaLabel: widgetOptions.ariaLabel,
27210
27271
  choices: widgetOptions.choices.map(choice => choice.content)
27211
27272
  };
27212
27273
  };
@@ -27519,8 +27580,6 @@ const getNextState = (currentState, answerable) => {
27519
27580
  }
27520
27581
  };
27521
27582
 
27522
- // exports has no 'transform'
27523
-
27524
27583
  // A Graded Group is more or less a Group widget that displays a check
27525
27584
  // answer button below the rendered content. When clicked, the widget grades
27526
27585
  // the stuff inside and displays feedback about whether the inputted answer was
@@ -28184,7 +28243,6 @@ const getPromptJSON$g = (renderProps, userInput) => {
28184
28243
  };
28185
28244
  };
28186
28245
 
28187
- /* eslint-disable one-var */
28188
28246
  // @ts-expect-error - TS2339 - Property 'Plot' does not exist on type 'typeof Graphie'.
28189
28247
  const Plot$1 = Graphie.Plot;
28190
28248
  const DEFAULT_BACKGROUND_IMAGE = {
@@ -28285,14 +28343,14 @@ const Linear = _.extend({}, PlotDefaults, {
28285
28343
  return [m, b];
28286
28344
  },
28287
28345
  getFunctionForCoeffs: function (coeffs, x) {
28288
- const m = coeffs[0],
28289
- b = coeffs[1];
28346
+ const m = coeffs[0];
28347
+ const b = coeffs[1];
28290
28348
  return m * x + b;
28291
28349
  },
28292
28350
  getEquationString: function (coords) {
28293
28351
  const coeffs = this.getCoefficients(coords);
28294
- const m = coeffs[0],
28295
- b = coeffs[1];
28352
+ const m = coeffs[0];
28353
+ const b = coeffs[1];
28296
28354
  return "y = " + m.toFixed(3) + "x + " + b.toFixed(3);
28297
28355
  }
28298
28356
  });
@@ -28316,9 +28374,9 @@ const Quadratic = _.extend({}, PlotDefaults, {
28316
28374
  return [a, b, c];
28317
28375
  },
28318
28376
  getFunctionForCoeffs: function (coeffs, x) {
28319
- const a = coeffs[0],
28320
- b = coeffs[1],
28321
- c = coeffs[2];
28377
+ const a = coeffs[0];
28378
+ const b = coeffs[1];
28379
+ const c = coeffs[2];
28322
28380
  return (a * x + b) * x + c;
28323
28381
  },
28324
28382
  getPropsForCoeffs: function (coeffs) {
@@ -28330,9 +28388,9 @@ const Quadratic = _.extend({}, PlotDefaults, {
28330
28388
  },
28331
28389
  getEquationString: function (coords) {
28332
28390
  const coeffs = this.getCoefficients(coords);
28333
- const a = coeffs[0],
28334
- b = coeffs[1],
28335
- c = coeffs[2];
28391
+ const a = coeffs[0];
28392
+ const b = coeffs[1];
28393
+ const c = coeffs[2];
28336
28394
  return "y = " + a.toFixed(3) + "x^2 + " + b.toFixed(3) + "x + " + c.toFixed(3);
28337
28395
  }
28338
28396
  });
@@ -28351,10 +28409,10 @@ const Sinusoid = _.extend({}, PlotDefaults, {
28351
28409
  return [a, b, c, d];
28352
28410
  },
28353
28411
  getFunctionForCoeffs: function (coeffs, x) {
28354
- const a = coeffs[0],
28355
- b = coeffs[1],
28356
- c = coeffs[2],
28357
- d = coeffs[3];
28412
+ const a = coeffs[0];
28413
+ const b = coeffs[1];
28414
+ const c = coeffs[2];
28415
+ const d = coeffs[3];
28358
28416
  return a * Math.sin(b * x - c) + d;
28359
28417
  },
28360
28418
  getPropsForCoeffs: function (coeffs) {
@@ -28367,10 +28425,10 @@ const Sinusoid = _.extend({}, PlotDefaults, {
28367
28425
  },
28368
28426
  getEquationString: function (coords) {
28369
28427
  const coeffs = this.getCoefficients(coords);
28370
- const a = coeffs[0],
28371
- b = coeffs[1],
28372
- c = coeffs[2],
28373
- d = coeffs[3];
28428
+ const a = coeffs[0];
28429
+ const b = coeffs[1];
28430
+ const c = coeffs[2];
28431
+ const d = coeffs[3];
28374
28432
  return "y = " + a.toFixed(3) + " sin(" + b.toFixed(3) + "x - " + c.toFixed(3) + ") + " + d.toFixed(3);
28375
28433
  },
28376
28434
  areEqual: function (coeffs1, coeffs2) {
@@ -28390,18 +28448,18 @@ const Tangent = _.extend({}, PlotDefaults, {
28390
28448
  return [a, b, c, d];
28391
28449
  },
28392
28450
  getFunctionForCoeffs: function (coeffs, x) {
28393
- const a = coeffs[0],
28394
- b = coeffs[1],
28395
- c = coeffs[2],
28396
- d = coeffs[3];
28451
+ const a = coeffs[0];
28452
+ const b = coeffs[1];
28453
+ const c = coeffs[2];
28454
+ const d = coeffs[3];
28397
28455
  return a * Math.tan(b * x - c) + d;
28398
28456
  },
28399
28457
  getEquationString: function (coords) {
28400
28458
  const coeffs = this.getCoefficients(coords);
28401
- const a = coeffs[0],
28402
- b = coeffs[1],
28403
- c = coeffs[2],
28404
- d = coeffs[3];
28459
+ const a = coeffs[0];
28460
+ const b = coeffs[1];
28461
+ const c = coeffs[2];
28462
+ const d = coeffs[3];
28405
28463
  return "y = " + a.toFixed(3) + " sin(" + b.toFixed(3) + "x - " + c.toFixed(3) + ") + " + d.toFixed(3);
28406
28464
  },
28407
28465
  areEqual: function (coeffs1, coeffs2) {
@@ -28460,9 +28518,9 @@ const Exponential = _.extend({}, PlotDefaults, {
28460
28518
  return [a, b, c];
28461
28519
  },
28462
28520
  getFunctionForCoeffs: function (coeffs, x) {
28463
- const a = coeffs[0],
28464
- b = coeffs[1],
28465
- c = coeffs[2];
28521
+ const a = coeffs[0];
28522
+ const b = coeffs[1];
28523
+ const c = coeffs[2];
28466
28524
  return a * Math.exp(b * x) + c;
28467
28525
  },
28468
28526
  getEquationString: function (coords, asymptote) {
@@ -28470,9 +28528,9 @@ const Exponential = _.extend({}, PlotDefaults, {
28470
28528
  return null;
28471
28529
  }
28472
28530
  const coeffs = this.getCoefficients(coords, asymptote);
28473
- const a = coeffs[0],
28474
- b = coeffs[1],
28475
- c = coeffs[2];
28531
+ const a = coeffs[0];
28532
+ const b = coeffs[1];
28533
+ const c = coeffs[2];
28476
28534
  return "y = " + a.toFixed(3) + "e^(" + b.toFixed(3) + "x) + " + c.toFixed(3);
28477
28535
  }
28478
28536
  });
@@ -28517,9 +28575,9 @@ const Logarithm = _.extend({}, PlotDefaults, {
28517
28575
  }
28518
28576
  },
28519
28577
  getFunctionForCoeffs: function (coeffs, x, asymptote) {
28520
- const a = coeffs[0],
28521
- b = coeffs[1],
28522
- c = coeffs[2];
28578
+ const a = coeffs[0];
28579
+ const b = coeffs[1];
28580
+ const c = coeffs[2];
28523
28581
  return a * Math.log(b * x + c);
28524
28582
  },
28525
28583
  getEquationString: function (coords, asymptote) {
@@ -28527,9 +28585,9 @@ const Logarithm = _.extend({}, PlotDefaults, {
28527
28585
  return null;
28528
28586
  }
28529
28587
  const coeffs = this.getCoefficients(coords, asymptote);
28530
- const a = coeffs[0],
28531
- b = coeffs[1],
28532
- c = coeffs[2];
28588
+ const a = coeffs[0];
28589
+ const b = coeffs[1];
28590
+ const c = coeffs[2];
28533
28591
  return "y = ln(" + a.toFixed(3) + "x + " + b.toFixed(3) + ") + " + c.toFixed(3);
28534
28592
  }
28535
28593
  });
@@ -28553,16 +28611,16 @@ const AbsoluteValue = _.extend({}, PlotDefaults, {
28553
28611
  return [m, horizontalOffset, verticalOffset];
28554
28612
  },
28555
28613
  getFunctionForCoeffs: function (coeffs, x) {
28556
- const m = coeffs[0],
28557
- horizontalOffset = coeffs[1],
28558
- verticalOffset = coeffs[2];
28614
+ const m = coeffs[0];
28615
+ const horizontalOffset = coeffs[1];
28616
+ const verticalOffset = coeffs[2];
28559
28617
  return m * Math.abs(x - horizontalOffset) + verticalOffset;
28560
28618
  },
28561
28619
  getEquationString: function (coords) {
28562
28620
  const coeffs = this.getCoefficients(coords);
28563
- const m = coeffs[0],
28564
- horizontalOffset = coeffs[1],
28565
- verticalOffset = coeffs[2];
28621
+ const m = coeffs[0];
28622
+ const horizontalOffset = coeffs[1];
28623
+ const verticalOffset = coeffs[2];
28566
28624
  return "y = " + m.toFixed(3) + "| x - " + horizontalOffset.toFixed(3) + "| + " + verticalOffset.toFixed(3);
28567
28625
  }
28568
28626
  });
@@ -30305,6 +30363,7 @@ function numSteps$1(range, step) {
30305
30363
  class Graph extends React.Component {
30306
30364
  constructor(...args) {
30307
30365
  super(...args);
30366
+ this.graphieDivRef = /*#__PURE__*/React.createRef();
30308
30367
  this.protractor = void 0;
30309
30368
  this.ruler = void 0;
30310
30369
  this._graphie = void 0;
@@ -30348,9 +30407,7 @@ class Graph extends React.Component {
30348
30407
  if (this._hasSetupGraphieThisUpdate) {
30349
30408
  return;
30350
30409
  }
30351
-
30352
- // eslint-disable-next-line react/no-string-refs
30353
- const graphieDiv = ReactDOM__default.findDOMNode(this.refs.graphieDiv);
30410
+ const graphieDiv = this.graphieDivRef.current;
30354
30411
  // @ts-expect-error - TS2769 - No overload matches this call. | TS2339 - Property 'empty' does not exist on type 'JQueryStatic'.
30355
30412
  $(graphieDiv).empty();
30356
30413
 
@@ -30558,7 +30615,7 @@ class Graph extends React.Component {
30558
30615
  onClick: this.onClick
30559
30616
  }, image, /*#__PURE__*/React.createElement("div", {
30560
30617
  className: "graphie",
30561
- ref: "graphieDiv"
30618
+ ref: this.graphieDivRef
30562
30619
  }));
30563
30620
  }
30564
30621
  }
@@ -31646,6 +31703,7 @@ class InfoTip extends React.Component {
31646
31703
  class MultiButtonGroup extends React.Component {
31647
31704
  constructor(...args) {
31648
31705
  super(...args);
31706
+ this.buttonContainerRef = /*#__PURE__*/React.createRef();
31649
31707
  this.toggleSelect = newValue => {
31650
31708
  const values = (this.props.values || []).slice(0);
31651
31709
  const allowEmpty = this.props.allowEmpty;
@@ -31662,9 +31720,8 @@ class MultiButtonGroup extends React.Component {
31662
31720
  };
31663
31721
  }
31664
31722
  focus() {
31665
- var _ReactDOM$findDOMNode;
31666
- // @ts-expect-error - TS2339 - Property 'focus' does not exist on type 'Element | Text'.
31667
- (_ReactDOM$findDOMNode = ReactDOM.findDOMNode(this)) == null || _ReactDOM$findDOMNode.focus();
31723
+ var _this$buttonContainer;
31724
+ (_this$buttonContainer = this.buttonContainerRef.current) == null || _this$buttonContainer.focus();
31668
31725
  return true;
31669
31726
  }
31670
31727
  render() {
@@ -31685,7 +31742,8 @@ class MultiButtonGroup extends React.Component {
31685
31742
  display: "inline-block"
31686
31743
  };
31687
31744
  return /*#__PURE__*/React.createElement("div", {
31688
- style: outerStyle
31745
+ style: outerStyle,
31746
+ ref: this.buttonContainerRef
31689
31747
  }, buttons);
31690
31748
  }
31691
31749
  }
@@ -31726,7 +31784,7 @@ const styles$e = StyleSheet.create({
31726
31784
  }
31727
31785
  });
31728
31786
 
31729
- const _excluded$2 = ["onFormatChange", "checkValidity", "useArrowKeys"];
31787
+ const _excluded$4 = ["onFormatChange", "checkValidity", "useArrowKeys"];
31730
31788
  const {
31731
31789
  firstNumericalParse,
31732
31790
  captureScratchpadTouchStart
@@ -31756,22 +31814,25 @@ const getNumericFormat = KhanMath.getNumericFormat;
31756
31814
  class NumberInput extends React.Component {
31757
31815
  constructor(...args) {
31758
31816
  super(...args);
31817
+ this.inputRef = /*#__PURE__*/React.createRef();
31759
31818
  this.state = {
31760
31819
  format: this.props.format
31761
31820
  };
31821
+ this._getInput = () => {
31822
+ if (!this.inputRef.current) {
31823
+ throw new PerseusError("Input ref accessed before set", Errors.Internal);
31824
+ }
31825
+ return this.inputRef.current;
31826
+ };
31762
31827
  /* Return the current "value" of this input
31763
31828
  * If empty, it returns the placeholder (if it is a number) or null
31764
31829
  */
31765
31830
  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
- );
31831
+ return this.parseInputValue(this._getInput().value);
31770
31832
  };
31771
31833
  /* Return the current string value of this input */
31772
31834
  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
31835
+ return this._getInput().toString();
31775
31836
  };
31776
31837
  this.parseInputValue = value => {
31777
31838
  if (value === "") {
@@ -31783,26 +31844,21 @@ class NumberInput extends React.Component {
31783
31844
  };
31784
31845
  /* Set text input focus to this input */
31785
31846
  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
31847
+ this._getInput().focus();
31788
31848
  this._handleFocus();
31789
31849
  };
31790
31850
  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
31851
+ this._getInput().blur();
31793
31852
  this._handleBlur();
31794
31853
  };
31795
31854
  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);
31855
+ this._getInput().setSelectionRange(selectionStart, selectionEnd);
31798
31856
  };
31799
31857
  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;
31858
+ return this._getInput().selectionStart;
31802
31859
  };
31803
31860
  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;
31861
+ return this._getInput().selectionEnd;
31806
31862
  };
31807
31863
  this._checkValidity = value => {
31808
31864
  if (value == null) {
@@ -31862,9 +31918,7 @@ class NumberInput extends React.Component {
31862
31918
  }
31863
31919
  };
31864
31920
  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));
31921
+ this._getInput().value = toNumericString(val, format);
31868
31922
  };
31869
31923
  }
31870
31924
  componentDidUpdate(prevProps) {
@@ -31884,13 +31938,11 @@ class NumberInput extends React.Component {
31884
31938
  classes = classes + " " + this.props.className;
31885
31939
  }
31886
31940
  const _this$props = this.props,
31887
- restProps = _objectWithoutPropertiesLoose(_this$props, _excluded$2);
31941
+ restProps = _objectWithoutPropertiesLoose(_this$props, _excluded$4);
31888
31942
  return /*#__PURE__*/React.createElement("input", _extends({}, restProps, {
31889
31943
  className: classes,
31890
- type: "text"
31891
- // eslint-disable-next-line react/no-string-refs
31892
- ,
31893
- ref: "input",
31944
+ type: "text",
31945
+ ref: this.inputRef,
31894
31946
  onChange: this._handleChange,
31895
31947
  onFocus: this._handleFocus,
31896
31948
  onBlur: this._handleBlur,
@@ -32811,12 +32863,12 @@ const MafsCssTransformWrapper = ({
32811
32863
  }
32812
32864
  }, children);
32813
32865
 
32814
- const _excluded$1 = ["children"];
32866
+ const _excluded$3 = ["children"];
32815
32867
  const TextLabel = _ref => {
32816
32868
  let {
32817
32869
  children
32818
32870
  } = _ref,
32819
- rest = _objectWithoutPropertiesLoose(_ref, _excluded$1);
32871
+ rest = _objectWithoutPropertiesLoose(_ref, _excluded$3);
32820
32872
  return /*#__PURE__*/React__default.createElement(Text$1, _extends({
32821
32873
  size: 16,
32822
32874
  svgTextProps: {
@@ -33397,15 +33449,18 @@ function useControlPoint(params) {
33397
33449
  } = useGraphConfig();
33398
33450
  const {
33399
33451
  point,
33400
- sequenceNumber,
33452
+ ariaDescribedBy,
33453
+ ariaLabel,
33454
+ ariaLive = "polite",
33401
33455
  color,
33402
- cursor,
33403
33456
  constrain = p => snap(snapStep, p),
33457
+ cursor,
33458
+ forwardedRef = noop$1,
33459
+ sequenceNumber = 1,
33404
33460
  onMove = noop$1,
33405
33461
  onClick = noop$1,
33406
33462
  onFocus = noop$1,
33407
- onBlur = noop$1,
33408
- forwardedRef = noop$1
33463
+ onBlur = noop$1
33409
33464
  } = params;
33410
33465
  const {
33411
33466
  strings,
@@ -33428,6 +33483,13 @@ function useControlPoint(params) {
33428
33483
  onMove,
33429
33484
  constrainKeyboardMovement: constrain
33430
33485
  });
33486
+
33487
+ // if custom aria label is not provided, will use default of sequence number and point coordinates
33488
+ const pointAriaLabel = ariaLabel || strings.srPointAtCoordinates({
33489
+ num: sequenceNumber,
33490
+ x: srFormatNumber(point[X], locale),
33491
+ y: srFormatNumber(point[Y], locale)
33492
+ });
33431
33493
  useLayoutEffect(() => {
33432
33494
  setForwardedRef(forwardedRef, focusableHandleRef.current);
33433
33495
  }, [forwardedRef]);
@@ -33437,15 +33499,9 @@ function useControlPoint(params) {
33437
33499
  tabIndex: disableKeyboardInteraction ? -1 : 0,
33438
33500
  ref: focusableHandleRef,
33439
33501
  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",
33502
+ "aria-describedby": ariaDescribedBy,
33503
+ "aria-label": pointAriaLabel,
33504
+ "aria-live": ariaLive,
33449
33505
  onFocus: event => {
33450
33506
  onFocus(event);
33451
33507
  setFocused(true);
@@ -36451,7 +36507,7 @@ const StatefulMafsGraph = /*#__PURE__*/React.forwardRef((props, ref) => {
36451
36507
  });
36452
36508
 
36453
36509
  const eq$1 = Util.eq;
36454
- const deepEq$2 = Util.deepEq;
36510
+ const deepEq$3 = Util.deepEq;
36455
36511
  function scoreInteractiveGraph(userInput, rubric) {
36456
36512
  // None-type graphs are not graded
36457
36513
  if (userInput.type === "none" && rubric.correct.type === "none") {
@@ -36501,7 +36557,7 @@ function scoreInteractiveGraph(userInput, rubric) {
36501
36557
  // If the parabola coefficients match, it's correct.
36502
36558
  const guessCoeffs = getQuadraticCoefficients(userInput.coords);
36503
36559
  const correctCoeffs = getQuadraticCoefficients(rubric.correct.coords);
36504
- if (deepEq$2(guessCoeffs, correctCoeffs)) {
36560
+ if (deepEq$3(guessCoeffs, correctCoeffs)) {
36505
36561
  return {
36506
36562
  type: "points",
36507
36563
  earned: 1,
@@ -36515,7 +36571,7 @@ function scoreInteractiveGraph(userInput, rubric) {
36515
36571
  const canonicalGuessCoeffs = canonicalSineCoefficients$1(guessCoeffs);
36516
36572
  const canonicalCorrectCoeffs = canonicalSineCoefficients$1(correctCoeffs);
36517
36573
  // If the canonical coefficients match, it's correct.
36518
- if (deepEq$2(canonicalGuessCoeffs, canonicalCorrectCoeffs)) {
36574
+ if (deepEq$3(canonicalGuessCoeffs, canonicalCorrectCoeffs)) {
36519
36575
  return {
36520
36576
  type: "points",
36521
36577
  earned: 1,
@@ -36524,7 +36580,7 @@ function scoreInteractiveGraph(userInput, rubric) {
36524
36580
  };
36525
36581
  }
36526
36582
  } 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)) {
36583
+ if (deepEq$3(userInput.center, rubric.correct.center) && eq$1(userInput.radius, rubric.correct.radius)) {
36528
36584
  return {
36529
36585
  type: "points",
36530
36586
  earned: 1,
@@ -36546,7 +36602,7 @@ function scoreInteractiveGraph(userInput, rubric) {
36546
36602
  guess == null || guess.sort();
36547
36603
  // @ts-expect-error - TS2339 - Property 'sort' does not exist on type 'readonly Coord[]'.
36548
36604
  correct.sort();
36549
- if (deepEq$2(guess, correct)) {
36605
+ if (deepEq$3(guess, correct)) {
36550
36606
  return {
36551
36607
  type: "points",
36552
36608
  earned: 1,
@@ -36568,7 +36624,7 @@ function scoreInteractiveGraph(userInput, rubric) {
36568
36624
  /* exact */
36569
36625
  guess.sort();
36570
36626
  correct.sort();
36571
- match = deepEq$2(guess, correct);
36627
+ match = deepEq$3(guess, correct);
36572
36628
  }
36573
36629
  if (match) {
36574
36630
  return {
@@ -36583,7 +36639,7 @@ function scoreInteractiveGraph(userInput, rubric) {
36583
36639
  let correct = Util.deepClone(rubric.correct.coords);
36584
36640
  guess = _.invoke(guess, "sort").sort();
36585
36641
  correct = _.invoke(correct, "sort").sort();
36586
- if (deepEq$2(guess, correct)) {
36642
+ if (deepEq$3(guess, correct)) {
36587
36643
  return {
36588
36644
  type: "points",
36589
36645
  earned: 1,
@@ -36594,7 +36650,7 @@ function scoreInteractiveGraph(userInput, rubric) {
36594
36650
  } else if (userInput.type === "ray" && rubric.correct.type === "ray" && userInput.coords != null) {
36595
36651
  const guess = userInput.coords;
36596
36652
  const correct = rubric.correct.coords;
36597
- if (deepEq$2(guess[0], correct[0]) && collinear(correct[0], correct[1], guess[1])) {
36653
+ if (deepEq$3(guess[0], correct[0]) && collinear(correct[0], correct[1], guess[1])) {
36598
36654
  return {
36599
36655
  type: "points",
36600
36656
  earned: 1,
@@ -36621,7 +36677,7 @@ function scoreInteractiveGraph(userInput, rubric) {
36621
36677
  /* exact */
36622
36678
  match =
36623
36679
  // @ts-expect-error - TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'.
36624
- deepEq$2(guess[1], correct[1]) &&
36680
+ deepEq$3(guess[1], correct[1]) &&
36625
36681
  // @ts-expect-error - TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'.
36626
36682
  collinear(correct[1], correct[0], guess[0]) &&
36627
36683
  // @ts-expect-error - TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'.
@@ -40795,6 +40851,7 @@ const getPromptJSON$7 = () => {
40795
40851
  return getUnsupportedPromptJSON("measurer");
40796
40852
  };
40797
40853
 
40854
+ const _excluded$2 = ["imageUrl", "imageTop", "imageLeft"];
40798
40855
  const defaultImage = {
40799
40856
  url: null,
40800
40857
  top: 0,
@@ -40901,14 +40958,19 @@ Measurer.defaultProps = {
40901
40958
  };
40902
40959
  const propUpgrades = {
40903
40960
  "1": v0props => {
40904
- const v1props = _(v0props).chain().omit("imageUrl", "imageTop", "imageLeft").extend({
40961
+ const {
40962
+ imageUrl,
40963
+ imageTop,
40964
+ imageLeft
40965
+ } = v0props,
40966
+ rest = _objectWithoutPropertiesLoose(v0props, _excluded$2);
40967
+ return _extends({}, rest, {
40905
40968
  image: {
40906
- url: v0props.imageUrl,
40907
- top: v0props.imageTop,
40908
- left: v0props.imageLeft
40969
+ url: imageUrl,
40970
+ top: imageTop,
40971
+ left: imageLeft
40909
40972
  }
40910
- }).value();
40911
- return v1props;
40973
+ });
40912
40974
  }
40913
40975
  };
40914
40976
  var Measurer$1 = {
@@ -42436,14 +42498,28 @@ const getPromptJSON$5 = (renderProps, userInput) => {
42436
42498
  };
42437
42499
  };
42438
42500
 
42439
- function scoreOrderer(userInput, rubric) {
42501
+ /**
42502
+ * Checks user input from the orderer widget to see if the user has started
42503
+ * ordering the options, making the widget scorable.
42504
+ * @param userInput
42505
+ * @see `scoreOrderer` for more details.
42506
+ */
42507
+ function validateOrderer(userInput) {
42440
42508
  if (userInput.current.length === 0) {
42441
42509
  return {
42442
42510
  type: "invalid",
42443
42511
  message: null
42444
42512
  };
42445
42513
  }
42446
- const correct = _.isEqual(userInput.current, _.pluck(rubric.correctOptions, "content"));
42514
+ return null;
42515
+ }
42516
+
42517
+ function scoreOrderer(userInput, rubric) {
42518
+ const validateError = validateOrderer(userInput);
42519
+ if (validateError) {
42520
+ return validateError;
42521
+ }
42522
+ const correct = _.isEqual(userInput.current, rubric.correctOptions.map(option => option.content));
42447
42523
  return {
42448
42524
  type: "points",
42449
42525
  earned: correct ? 1 : 0,
@@ -42623,8 +42699,6 @@ Card.defaultProps = {
42623
42699
  animating: false,
42624
42700
  linterContext: linterContextDefault
42625
42701
  };
42626
- const NORMAL = "normal";
42627
- const HORIZONTAL = "horizontal";
42628
42702
  class Orderer extends React.Component {
42629
42703
  constructor(...args) {
42630
42704
  super(...args);
@@ -42771,7 +42845,7 @@ class Orderer extends React.Component {
42771
42845
  };
42772
42846
  this.findCorrectIndex = (draggable, list) => {
42773
42847
  // Find the correct index for a card given the current cards.
42774
- const isHorizontal = this.props.layout === HORIZONTAL;
42848
+ const isHorizontal = this.props.layout === "horizontal";
42775
42849
  // eslint-disable-next-line react/no-string-refs
42776
42850
  // @ts-expect-error - TS2769 - No overload matches this call.
42777
42851
  const $dragList = $(ReactDOM__default.findDOMNode(this.refs.dragList));
@@ -42821,28 +42895,24 @@ class Orderer extends React.Component {
42821
42895
  if (draggable == null) {
42822
42896
  return false;
42823
42897
  }
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);
42898
+ const isHorizontal = this.props.layout === "horizontal";
42899
+ // @ts-expect-error - TS2769 - No overload matches this call.
42900
+ const $draggable = $(ReactDOM__default.findDOMNode(draggable));
42901
+ // eslint-disable-next-line react/no-string-refs
42902
+ // @ts-expect-error - TS2769 - No overload matches this call.
42903
+ const $bank = $(ReactDOM__default.findDOMNode(this.refs.bank));
42904
+ // @ts-expect-error - TS2339 - Property 'offset' does not exist on type 'JQueryStatic'.
42905
+ const draggableOffset = $draggable.offset();
42906
+ // @ts-expect-error - TS2339 - Property 'offset' does not exist on type 'JQueryStatic'.
42907
+ const bankOffset = $bank.offset();
42908
+ // @ts-expect-error - TS2339 - Property 'outerHeight' does not exist on type 'JQueryStatic'.
42909
+ const draggableHeight = $draggable.outerHeight(true);
42910
+ // @ts-expect-error - TS2339 - Property 'outerHeight' does not exist on type 'JQueryStatic'.
42911
+ const bankHeight = $bank.outerHeight(true);
42912
+ // @ts-expect-error - TS2339 - Property 'outerWidth' does not exist on type 'JQueryStatic'.
42913
+ const bankWidth = $bank.outerWidth(true);
42914
+ // @ts-expect-error - TS2339 - Property 'outerWidth' does not exist on type 'JQueryStatic'.
42915
+ const draggableWidth = $draggable.outerWidth(true);
42846
42916
  if (isHorizontal) {
42847
42917
  return draggableOffset.top + draggableHeight / 2 < bankOffset.top + bankHeight;
42848
42918
  }
@@ -42991,8 +43061,8 @@ Orderer.defaultProps = {
42991
43061
  current: [],
42992
43062
  options: [],
42993
43063
  correctOptions: [],
42994
- height: NORMAL,
42995
- layout: HORIZONTAL,
43064
+ height: "normal",
43065
+ layout: "horizontal",
42996
43066
  linterContext: linterContextDefault
42997
43067
  };
42998
43068
  var Orderer$1 = {
@@ -43271,18 +43341,36 @@ const getPromptJSON$3 = () => {
43271
43341
  };
43272
43342
 
43273
43343
  const {
43274
- deepEq: deepEq$1
43344
+ deepEq: deepEq$2
43275
43345
  } = Util;
43276
- function scorePlotter(userInput, rubric) {
43277
- if (deepEq$1(userInput, rubric.starting)) {
43346
+
43347
+ /**
43348
+ * Checks user input to confirm it is not the same as the starting values for the graph.
43349
+ * This means the user has modified the graph, and the question can be scored.
43350
+ *
43351
+ * @see 'scorePlotter' for more details on scoring.
43352
+ */
43353
+ function validatePlotter(userInput, validationData) {
43354
+ if (deepEq$2(userInput, validationData.starting)) {
43278
43355
  return {
43279
43356
  type: "invalid",
43280
43357
  message: null
43281
43358
  };
43282
43359
  }
43360
+ return null;
43361
+ }
43362
+
43363
+ const {
43364
+ deepEq: deepEq$1
43365
+ } = Util;
43366
+ function scorePlotter(userInput, scoringData) {
43367
+ const validationError = validatePlotter(userInput, scoringData);
43368
+ if (validationError) {
43369
+ return validationError;
43370
+ }
43283
43371
  return {
43284
43372
  type: "points",
43285
- earned: deepEq$1(userInput, rubric.correct) ? 1 : 0,
43373
+ earned: deepEq$1(userInput, scoringData.correct) ? 1 : 0,
43286
43374
  total: 1,
43287
43375
  message: null
43288
43376
  };
@@ -44167,7 +44255,14 @@ const getPromptJSON$1 = userInput => {
44167
44255
  };
44168
44256
  };
44169
44257
 
44170
- function scoreSorter(userInput, rubric) {
44258
+ /**
44259
+ * Checks user input for the sorter widget to ensure that the user has made
44260
+ * changes before attempting to score the widget.
44261
+ * @param userInput
44262
+ * @see 'scoreSorter' in 'packages/perseus/src/widgets/sorter/score-sorter.ts'
44263
+ * for more details on how the sorter widget is scored.
44264
+ */
44265
+ function validateSorter(userInput) {
44171
44266
  // If the sorter widget hasn't been changed yet, we treat it as "empty" which
44172
44267
  // prevents the "Check" button from becoming active. We want the user
44173
44268
  // to make a change before trying to move forward. This makes an
@@ -44180,6 +44275,14 @@ function scoreSorter(userInput, rubric) {
44180
44275
  message: null
44181
44276
  };
44182
44277
  }
44278
+ return null;
44279
+ }
44280
+
44281
+ function scoreSorter(userInput, rubric) {
44282
+ const validationError = validateSorter(userInput);
44283
+ if (validationError) {
44284
+ return validationError;
44285
+ }
44183
44286
  const correct = Util.deepEq(userInput.options, rubric.correct);
44184
44287
  return {
44185
44288
  type: "points",
@@ -44758,7 +44861,7 @@ const init = function init() {
44758
44861
 
44759
44862
  // This file is processed by a Rollup plugin (replace) to inject the production
44760
44863
  const libName = "@khanacademy/perseus";
44761
- const libVersion = "43.0.1";
44864
+ const libVersion = "44.0.0";
44762
44865
  addLibraryVersionToPerseusDebug(libName, libVersion);
44763
44866
 
44764
44867
  const apiVersion = {
@@ -46362,7 +46465,7 @@ const str = {
46362
46465
  }
46363
46466
  };
46364
46467
 
46365
- const _excluded = ["item", "children", "shape", "serializedState", "onSerializedStateUpdated"];
46468
+ const _excluded$1 = ["item", "children", "shape", "serializedState", "onSerializedStateUpdated"];
46366
46469
 
46367
46470
  // TODO(mdr)
46368
46471
  // TODO(mdr)
@@ -46447,7 +46550,7 @@ class MultiRenderer extends React.Component {
46447
46550
  // explicitly pulling them out of `this.props` so I don't pass them to
46448
46551
  // `<Renderer>`. I'm not sure how else to do this.
46449
46552
  const _this$props = this.props,
46450
- otherProps = _objectWithoutPropertiesLoose(_this$props, _excluded);
46553
+ otherProps = _objectWithoutPropertiesLoose(_this$props, _excluded$1);
46451
46554
  return _extends({}, otherProps, {
46452
46555
  strings: this.context.strings
46453
46556
  });
@@ -48251,6 +48354,27 @@ function pair(parseA, parseB) {
48251
48354
  };
48252
48355
  }
48253
48356
 
48357
+ function pipeParsers(p) {
48358
+ return new ParserPipeline(p);
48359
+ }
48360
+ class ParserPipeline {
48361
+ constructor(parser) {
48362
+ this.parser = parser;
48363
+ }
48364
+ then(nextParser) {
48365
+ return new ParserPipeline(composeParsers(this.parser, nextParser));
48366
+ }
48367
+ }
48368
+ function composeParsers(parserA, parserB) {
48369
+ return (rawValue, ctx) => {
48370
+ const partialResult = parserA(rawValue, ctx);
48371
+ if (isFailure(partialResult)) {
48372
+ return partialResult;
48373
+ }
48374
+ return parserB(partialResult.value, ctx);
48375
+ };
48376
+ }
48377
+
48254
48378
  function record(parseKey, parseValue) {
48255
48379
  return (rawValue, ctx) => {
48256
48380
  if (!isObject(rawValue)) {
@@ -48331,6 +48455,20 @@ function either(parseA, parseB) {
48331
48455
  };
48332
48456
  }
48333
48457
 
48458
+ function defaulted(parser, fallback) {
48459
+ return (rawValue, ctx) => {
48460
+ if (rawValue == null) {
48461
+ return success(fallback(rawValue));
48462
+ }
48463
+ return parser(rawValue, ctx);
48464
+ };
48465
+ }
48466
+
48467
+ const parseImages = defaulted(record(string, object({
48468
+ width: number,
48469
+ height: number
48470
+ })), () => ({}));
48471
+
48334
48472
  function parseWidget(parseType, parseOptions) {
48335
48473
  return object({
48336
48474
  type: parseType,
@@ -48339,10 +48477,21 @@ function parseWidget(parseType, parseOptions) {
48339
48477
  alignment: optional(string),
48340
48478
  options: parseOptions,
48341
48479
  key: optional(number),
48342
- version: object({
48480
+ version: optional(object({
48343
48481
  major: number,
48344
48482
  minor: number
48345
- })
48483
+ }))
48484
+ });
48485
+ }
48486
+ function parseWidgetWithVersion(parseVersion, parseType, parseOptions) {
48487
+ return object({
48488
+ type: parseType,
48489
+ static: optional(boolean),
48490
+ graded: optional(boolean),
48491
+ alignment: optional(string),
48492
+ options: parseOptions,
48493
+ key: optional(number),
48494
+ version: parseVersion
48346
48495
  });
48347
48496
  }
48348
48497
 
@@ -48350,7 +48499,7 @@ const parseCategorizerWidget = parseWidget(constant("categorizer"), object({
48350
48499
  items: array(string),
48351
48500
  categories: array(string),
48352
48501
  randomizeItems: boolean,
48353
- static: boolean,
48502
+ static: defaulted(boolean, () => false),
48354
48503
  values: array(number),
48355
48504
  highlightLint: optional(boolean),
48356
48505
  linterContext: optional(object({
@@ -48382,7 +48531,9 @@ const parseDefinitionWidget = parseWidget(constant("definition"), object({
48382
48531
 
48383
48532
  const parseDropdownWidget = parseWidget(constant("dropdown"), object({
48384
48533
  placeholder: string,
48385
- static: boolean,
48534
+ ariaLabel: optional(string),
48535
+ visibleLabel: optional(string),
48536
+ static: defaulted(boolean, () => false),
48386
48537
  choices: array(object({
48387
48538
  content: string,
48388
48539
  correct: boolean
@@ -48405,9 +48556,12 @@ const parseAnswerForm = object({
48405
48556
  form: boolean,
48406
48557
  simplify: boolean,
48407
48558
  considered: enumeration("correct", "wrong", "ungraded"),
48408
- key: optional(string)
48559
+ key: pipeParsers(optional(union(string).or(number).parser)).then((key, ctx) => ctx.success(String(key))).parser
48409
48560
  });
48410
- const parseExpressionWidget = parseWidget(constant("expression"), object({
48561
+ const parseExpressionWidgetV1 = parseWidgetWithVersion(object({
48562
+ major: constant(1),
48563
+ minor: number
48564
+ }), constant("expression"), object({
48411
48565
  answerForms: array(parseAnswerForm),
48412
48566
  functions: array(string),
48413
48567
  times: boolean,
@@ -48416,23 +48570,68 @@ const parseExpressionWidget = parseWidget(constant("expression"), object({
48416
48570
  buttonSets: array(enumeration("basic", "basic+div", "trig", "prealgebra", "logarithms", "basic relations", "advanced relations")),
48417
48571
  buttonsVisible: optional(enumeration("always", "never", "focused"))
48418
48572
  }));
48573
+ const parseExpressionWidgetV0 = parseWidgetWithVersion(optional(object({
48574
+ major: constant(0),
48575
+ minor: number
48576
+ })), constant("expression"), object({
48577
+ functions: array(string),
48578
+ times: boolean,
48579
+ visibleLabel: optional(string),
48580
+ ariaLabel: optional(string),
48581
+ form: boolean,
48582
+ simplify: boolean,
48583
+ value: string,
48584
+ buttonSets: array(enumeration("basic", "basic+div", "trig", "prealgebra", "logarithms", "basic relations", "advanced relations")),
48585
+ buttonsVisible: optional(enumeration("always", "never", "focused"))
48586
+ }));
48587
+ function migrateV0ToV1(widget, ctx) {
48588
+ const {
48589
+ options
48590
+ } = widget;
48591
+ return ctx.success(_extends({}, widget, {
48592
+ version: {
48593
+ major: 1,
48594
+ minor: 0
48595
+ },
48596
+ options: {
48597
+ times: options.times,
48598
+ buttonSets: options.buttonSets,
48599
+ functions: options.functions,
48600
+ buttonsVisible: options.buttonsVisible,
48601
+ visibleLabel: options.visibleLabel,
48602
+ ariaLabel: options.ariaLabel,
48603
+ answerForms: [{
48604
+ considered: "correct",
48605
+ form: options.form,
48606
+ simplify: options.simplify,
48607
+ value: options.value
48608
+ }]
48609
+ }
48610
+ }));
48611
+ }
48612
+ const parseExpressionWidget = union(parseExpressionWidgetV1).or(pipeParsers(parseExpressionWidgetV0).then(migrateV0ToV1).parser).parser;
48419
48613
 
48420
- const parsePerseusRenderer = object({
48421
- content: string,
48614
+ const parsePerseusRenderer = defaulted(object({
48615
+ // TODO(benchristel): content is also defaulted to empty string in
48616
+ // renderer.tsx. See if we can remove one default or the other.
48617
+ content: defaulted(string, () => ""),
48422
48618
  // This module has an import cycle with parseWidgetsMap, because the
48423
48619
  // `group` widget can contain another renderer.
48424
48620
  // The anonymous function below ensures that we don't try to access
48425
48621
  // parseWidgetsMap before it's defined.
48426
- widgets: (rawVal, ctx) => parseWidgetsMap(rawVal, ctx),
48622
+ widgets: defaulted((rawVal, ctx) => parseWidgetsMap(rawVal, ctx), () => ({})),
48427
48623
  metadata: optional(array(string)),
48428
- images: record(string, object({
48429
- width: number,
48430
- height: number
48431
- }))
48432
- });
48624
+ images: parseImages
48625
+ }),
48626
+ // Default value
48627
+ () => ({
48628
+ content: "",
48629
+ widgets: {},
48630
+ images: {}
48631
+ }));
48433
48632
 
48434
48633
  const parseGradedGroupWidgetOptions = object({
48435
- title: string,
48634
+ title: defaulted(string, () => ""),
48436
48635
  hasHint: optional(nullable(boolean)),
48437
48636
  // This module has an import cycle with parsePerseusRenderer.
48438
48637
  // The anonymous function below ensures that we don't try to access
@@ -48526,7 +48725,7 @@ const parseIframeWidget = parseWidget(constant("iframe"), object({
48526
48725
  height: union(number).or(string).parser,
48527
48726
  allowFullScreen: boolean,
48528
48727
  allowTopNavigation: optional(boolean),
48529
- static: boolean
48728
+ static: defaulted(boolean, () => false)
48530
48729
  }));
48531
48730
 
48532
48731
  const parsePerseusImageBackground = object({
@@ -48905,13 +49104,13 @@ const parseMatcherWidget = parseWidget(constant("matcher"), object({
48905
49104
  padding: boolean
48906
49105
  }));
48907
49106
 
48908
- const parseMatrixWidget = parseWidget(constant("matrix"), object({
48909
- prefix: string,
48910
- suffix: string,
49107
+ const parseMatrixWidget = parseWidget(defaulted(constant("matrix"), () => "matrix"), object({
49108
+ prefix: optional(string),
49109
+ suffix: optional(string),
48911
49110
  answers: array(array(number)),
48912
- cursorPosition: array(number),
49111
+ cursorPosition: optional(array(number)),
48913
49112
  matrixBoardSize: array(number),
48914
- static: boolean
49113
+ static: optional(boolean)
48915
49114
  }));
48916
49115
 
48917
49116
  const parseMeasurerWidget = parseWidget(constant("measurer"), object({
@@ -48981,8 +49180,8 @@ const parseOrdererWidget = parseWidget(constant("orderer"), object({
48981
49180
  options: array(parseRenderer),
48982
49181
  correctOptions: array(parseRenderer),
48983
49182
  otherOptions: array(parseRenderer),
48984
- height: string,
48985
- layout: string
49183
+ height: enumeration("normal", "auto"),
49184
+ layout: enumeration("horizontal", "vertical")
48986
49185
  }));
48987
49186
 
48988
49187
  const parsePassageRefWidget = parseWidget(constant("passage-ref"), object({
@@ -49027,7 +49226,7 @@ const parsePythonProgramWidget = parseWidget(constant("python-program"), object(
49027
49226
 
49028
49227
  const parseRadioWidget = parseWidget(constant("radio"), object({
49029
49228
  choices: array(object({
49030
- content: string,
49229
+ content: defaulted(string, () => ""),
49031
49230
  clue: optional(string),
49032
49231
  correct: optional(boolean),
49033
49232
  isNoneOfTheAbove: optional(boolean),
@@ -49188,26 +49387,42 @@ const parseWidgetMapKeyComponents = pair(string, parseStringToPositiveInt);
49188
49387
  const parseHint = object({
49189
49388
  replace: optional(boolean),
49190
49389
  content: string,
49191
- widgets: parseWidgetsMap,
49390
+ widgets: defaulted(parseWidgetsMap, () => ({})),
49192
49391
  metadata: optional(array(string)),
49193
- images: record(string, object({
49194
- width: number,
49195
- height: number
49196
- }))
49392
+ images: parseImages
49197
49393
  });
49198
49394
 
49395
+ const _excluded = ["type", "options"];
49199
49396
  object({
49200
49397
  question: parsePerseusRenderer,
49201
- hints: array(parseHint),
49202
- answerArea: record(enumeration(...ItemExtras), boolean),
49203
- itemDataVersion: object({
49398
+ hints: defaulted(array(parseHint), () => []),
49399
+ answerArea: pipeParsers(defaulted(object({}), () => ({}))).then(migrateAnswerArea).then(record(enumeration(...ItemExtras), boolean)).parser,
49400
+ itemDataVersion: optional(object({
49204
49401
  major: number,
49205
49402
  minor: number
49206
- }),
49403
+ })),
49207
49404
  // Deprecated field
49208
49405
  answer: any
49209
49406
  });
49210
49407
 
49408
+ // Some answerAreas have extra fields, like:
49409
+ //
49410
+ // "answerArea": {
49411
+ // "type": "multiple",
49412
+ // "options": {
49413
+ // "content": "",
49414
+ // "images": {},
49415
+ // "widgets": {}
49416
+ // }
49417
+ // }
49418
+ //
49419
+ // The "type" and "options" fields don't seem to be used anywhere. This
49420
+ // migration function removes them.
49421
+ function migrateAnswerArea(rawValue, ctx) {
49422
+ const rest = _objectWithoutPropertiesLoose(rawValue, _excluded);
49423
+ return ctx.success(rest);
49424
+ }
49425
+
49211
49426
  /**
49212
49427
  * Helper to parse PerseusItem JSON
49213
49428
  * Why not just use JSON.parse? We want: