@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/index.js CHANGED
@@ -2882,8 +2882,6 @@ async function getImageSizeModern(url) {
2882
2882
  });
2883
2883
  }
2884
2884
 
2885
- /* eslint-disable @babel/no-invalid-this, getter-return, one-var */
2886
-
2887
2885
  // TODO: dedupe this with Coord in interactive2/types.js
2888
2886
 
2889
2887
  let supportsPassive = false;
@@ -2960,8 +2958,8 @@ function shuffle$3(array, randomSeed) {
2960
2958
  do {
2961
2959
  // Fischer-Yates shuffle
2962
2960
  for (let top = shuffled.length; top > 0; top--) {
2963
- const newEnd = Math.floor(random() * top),
2964
- temp = shuffled[newEnd];
2961
+ const newEnd = Math.floor(random() * top);
2962
+ const temp = shuffled[newEnd];
2965
2963
 
2966
2964
  // @ts-expect-error - TS2542 - Index signature in type 'readonly T[]' only permits reading.
2967
2965
  shuffled[newEnd] = shuffled[top - 1];
@@ -3293,13 +3291,13 @@ function eq$4(x, y) {
3293
3291
  * Deep approximate equality on primitives, numbers, arrays, and objects.
3294
3292
  * Recursive.
3295
3293
  */
3296
- function deepEq$6(x, y) {
3294
+ function deepEq$7(x, y) {
3297
3295
  if (Array.isArray(x) && Array.isArray(y)) {
3298
3296
  if (x.length !== y.length) {
3299
3297
  return false;
3300
3298
  }
3301
3299
  for (let i = 0; i < x.length; i++) {
3302
- if (!deepEq$6(x[i], y[i])) {
3300
+ if (!deepEq$7(x[i], y[i])) {
3303
3301
  return false;
3304
3302
  }
3305
3303
  }
@@ -3317,10 +3315,10 @@ function deepEq$6(x, y) {
3317
3315
  if (typeof x === "object" && typeof y === "object" && !!x && !!y) {
3318
3316
  return x === y || ___default["default"].all(x, function (v, k) {
3319
3317
  // @ts-expect-error - TS2536 - Type 'CollectionKey<T>' cannot be used to index type 'T'.
3320
- return deepEq$6(y[k], v);
3318
+ return deepEq$7(y[k], v);
3321
3319
  }) && ___default["default"].all(y, function (v, k) {
3322
3320
  // @ts-expect-error - TS2536 - Type 'CollectionKey<T>' cannot be used to index type 'T'.
3323
- return deepEq$6(x[k], v);
3321
+ return deepEq$7(x[k], v);
3324
3322
  });
3325
3323
  }
3326
3324
  if (typeof x === "object" && !!x || typeof y === "object" && !!y) {
@@ -3599,7 +3597,7 @@ const Util = {
3599
3597
  tickStepFromNumTicks,
3600
3598
  constrainedTickStepsFromTickSteps,
3601
3599
  eq: eq$4,
3602
- deepEq: deepEq$6,
3600
+ deepEq: deepEq$7,
3603
3601
  parseQueryString,
3604
3602
  updateQueryString: updateQueryString$2,
3605
3603
  strongEncodeURIComponent,
@@ -9002,7 +9000,6 @@ var Tex$1 = {
9002
9000
  }
9003
9001
  };
9004
9002
 
9005
- /* eslint-disable @babel/no-invalid-this */
9006
9003
  const {
9007
9004
  processMath: processMath$1
9008
9005
  } = Tex$1;
@@ -10632,7 +10629,9 @@ const getClipPoint = function (graph, coord, angle) {
10632
10629
  return clipPoint;
10633
10630
  };
10634
10631
 
10635
- /* eslint-disable @babel/no-invalid-this */
10632
+ /**
10633
+ * Default methods for a wrapped movable.
10634
+ */
10636
10635
 
10637
10636
  /*
10638
10637
  * attr() and animate() simply pass the
@@ -10890,7 +10889,7 @@ class WrappedLine extends WrappedDrawing {
10890
10889
  */
10891
10890
  const {
10892
10891
  eq: eq$3,
10893
- deepEq: deepEq$5
10892
+ deepEq: deepEq$6
10894
10893
  } = Util;
10895
10894
 
10896
10895
  // This should really be a readonly tuple of [number, number]
@@ -11050,7 +11049,7 @@ function similar(coords1, coords2, tolerance) {
11050
11049
  angles = rotate(angles, i);
11051
11050
  // @ts-expect-error - TS4104 - The type 'readonly number[]' is 'readonly' and cannot be assigned to the mutable type 'number[]'.
11052
11051
  sides = rotate(sides, i);
11053
- if (deepEq$5(angles1, angles)) {
11052
+ if (deepEq$6(angles1, angles)) {
11054
11053
  const sidePairs = ___default["default"].zip(sides1, sides);
11055
11054
  const factors = ___default["default"].map(sidePairs, function (pair) {
11056
11055
  return pair[0] / pair[1];
@@ -13989,7 +13988,7 @@ ___default["default"].extend(MovableAngle.prototype, {
13989
13988
 
13990
13989
  /* eslint-disable @babel/no-invalid-this */
13991
13990
  const nestedMap$1 = Util.nestedMap;
13992
- const deepEq$4 = Util.deepEq;
13991
+ const deepEq$5 = Util.deepEq;
13993
13992
 
13994
13993
  /**
13995
13994
  * A base class for all Graphie Movables
@@ -14086,7 +14085,7 @@ const createSimpleClass = function (addFunction) {
14086
14085
  this._prevProps = this.props;
14087
14086
  },
14088
14087
  modify: function (graphie) {
14089
- if (!deepEq$4(this.props, this._prevProps)) {
14088
+ if (!deepEq$5(this.props, this._prevProps)) {
14090
14089
  this.remove();
14091
14090
  this.add(graphie);
14092
14091
  this._prevProps = this.props;
@@ -16744,7 +16743,7 @@ var Movables = {
16744
16743
  const GraphieMovable = GraphieClasses.GraphieMovable;
16745
16744
  const createGraphie = GraphUtils.createGraphie;
16746
16745
  const {
16747
- deepEq: deepEq$3,
16746
+ deepEq: deepEq$4,
16748
16747
  nestedMap
16749
16748
  } = Util;
16750
16749
  const {
@@ -16782,7 +16781,7 @@ class Graphie extends React__namespace.Component {
16782
16781
  if (this.props.setup !== prevProps.setup) {
16783
16782
  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.", perseusCore.Errors.Internal);
16784
16783
  }
16785
- if (!deepEq$3(this.props.options, prevProps.options) || !deepEq$3(this.props.box, prevProps.box) || !deepEq$3(this.props.range, prevProps.range)) {
16784
+ if (!deepEq$4(this.props.options, prevProps.options) || !deepEq$4(this.props.box, prevProps.box) || !deepEq$4(this.props.range, prevProps.range)) {
16786
16785
  this._setupGraphie();
16787
16786
  }
16788
16787
  this._updateMovables();
@@ -18557,7 +18556,6 @@ const styles$C = aphrodite.StyleSheet.create({
18557
18556
  }
18558
18557
  });
18559
18558
 
18560
- /* eslint-disable no-useless-escape, no-prototype-builtins */
18561
18559
  const rules$1 = {
18562
18560
  ...pureMarkdown.pureMarkdownRules,
18563
18561
  // NOTE: basically ignored by JIPT. wraps everything at the outer layer
@@ -18734,7 +18732,7 @@ const rules$1 = {
18734
18732
  // The href prop should always be here, but just in case somehow the article's
18735
18733
  // link was not put together properly, let's make sure it's there so we
18736
18734
  // don't break the entire page.
18737
- const isKAUrl = href ? href.match(/https?:\/\/[^\/]*khanacademy.org|^\//) : false;
18735
+ const isKAUrl = href ? href.match(/https?:\/\/[^/]*khanacademy.org|^\//) : false;
18738
18736
  if (!isKAUrl) {
18739
18737
  // Prevents "reverse tabnabbing" phishing attacks
18740
18738
  // @ts-expect-error - TS2322 - Type '"noopener noreferrer"' is not assignable to type 'null'.
@@ -21144,6 +21142,7 @@ function uniqueIdForInput() {
21144
21142
  return `${prefix}${lastId}`;
21145
21143
  }
21146
21144
  class TextInput extends React__namespace.Component {
21145
+ inputRef = /*#__PURE__*/React__namespace.createRef();
21147
21146
  static defaultProps = {
21148
21147
  value: "",
21149
21148
  disabled: false
@@ -21157,33 +21156,32 @@ class TextInput extends React__namespace.Component {
21157
21156
  this.id = uniqueIdForInput();
21158
21157
  }
21159
21158
  }
21159
+ _getInput = () => {
21160
+ if (!this.inputRef.current) {
21161
+ throw new perseusCore.PerseusError("Input ref accessed before set", perseusCore.Errors.Internal);
21162
+ }
21163
+ return this.inputRef.current;
21164
+ };
21160
21165
  focus = () => {
21161
- // @ts-expect-error - TS2531 - Object is possibly 'null'. | TS2339 - Property 'focus' does not exist on type 'Element | Text'.
21162
- ReactDOM__default["default"].findDOMNode(this).focus();
21166
+ this._getInput().focus();
21163
21167
  };
21164
21168
  blur = () => {
21165
- // @ts-expect-error - TS2531 - Object is possibly 'null'. | TS2339 - Property 'blur' does not exist on type 'Element | Text'.
21166
- ReactDOM__default["default"].findDOMNode(this).blur();
21169
+ this._getInput().blur();
21167
21170
  };
21168
21171
  getValue = () => {
21169
- // @ts-expect-error - TS2339 - Property 'value' does not exist on type 'Element | Text'.
21170
- return ReactDOM__default["default"].findDOMNode(this)?.value;
21172
+ return this.inputRef.current?.value;
21171
21173
  };
21172
21174
  getStringValue = () => {
21173
- // @ts-expect-error - TS2339 - Property 'value' does not exist on type 'Element | Text'.
21174
- return ReactDOM__default["default"].findDOMNode(this)?.value.toString();
21175
+ return this.inputRef.current?.value.toString();
21175
21176
  };
21176
21177
  setSelectionRange = (selectionStart, selectionEnd) => {
21177
- // @ts-expect-error - TS2531 - Object is possibly 'null'. | TS2339 - Property 'setSelectionRange' does not exist on type 'Element | Text'.
21178
- ReactDOM__default["default"].findDOMNode(this).setSelectionRange(selectionStart, selectionEnd);
21178
+ this._getInput().setSelectionRange(selectionStart, selectionEnd);
21179
21179
  };
21180
21180
  getSelectionStart = () => {
21181
- // @ts-expect-error - TS2531 - Object is possibly 'null'. | TS2339 - Property 'selectionStart' does not exist on type 'Element | Text'.
21182
- return ReactDOM__default["default"].findDOMNode(this).selectionStart;
21181
+ return this._getInput().selectionStart;
21183
21182
  };
21184
21183
  getSelectionEnd = () => {
21185
- // @ts-expect-error - TS2531 - Object is possibly 'null'. | TS2339 - Property 'selectionEnd' does not exist on type 'Element | Text'.
21186
- return ReactDOM__default["default"].findDOMNode(this).selectionEnd;
21184
+ return this._getInput().selectionEnd;
21187
21185
  };
21188
21186
  render() {
21189
21187
  const {
@@ -21198,6 +21196,7 @@ class TextInput extends React__namespace.Component {
21198
21196
  } = this.props;
21199
21197
  const formattedValue = value === null ? "" : value.toString();
21200
21198
  return /*#__PURE__*/React__namespace.createElement(wonderBlocksForm.TextField, {
21199
+ ref: this.inputRef,
21201
21200
  style: style,
21202
21201
  disabled: disabled,
21203
21202
  id: this.id,
@@ -22219,7 +22218,7 @@ function maybeParsePercentInput(inputValue, normalizedAnswerExpected) {
22219
22218
  return value / 100;
22220
22219
  }
22221
22220
 
22222
- // Otherwise, we return input valuåe (number) stripped of the "%".
22221
+ // Otherwise, we return input value (number) stripped of the "%".
22223
22222
  return value;
22224
22223
  }
22225
22224
  function scoreNumericInput(userInput, rubric, strings) {
@@ -26277,7 +26276,15 @@ const styles$p = aphrodite.StyleSheet.create({
26277
26276
  }
26278
26277
  });
26279
26278
 
26280
- function scoreRadio(userInput, rubric, strings) {
26279
+ /**
26280
+ * Checks if the user has selected at least one option. Additional validation
26281
+ * is done in scoreRadio to check if the number of selected options is correct
26282
+ * and if the user has selected both a correct option and the "none of the above"
26283
+ * option.
26284
+ * @param userInput
26285
+ * @see `scoreRadio` for the additional validation logic and the scoring logic.
26286
+ */
26287
+ function validateRadio(userInput) {
26281
26288
  const numSelected = userInput.choicesSelected.reduce((sum, selected) => {
26282
26289
  return sum + (selected ? 1 : 0);
26283
26290
  }, 0);
@@ -26287,6 +26294,17 @@ function scoreRadio(userInput, rubric, strings) {
26287
26294
  message: null
26288
26295
  };
26289
26296
  }
26297
+ return null;
26298
+ }
26299
+
26300
+ function scoreRadio(userInput, rubric, strings) {
26301
+ const validationError = validateRadio(userInput);
26302
+ if (validationError) {
26303
+ return validationError;
26304
+ }
26305
+ const numSelected = userInput.choicesSelected.reduce((sum, selected) => {
26306
+ return sum + (selected ? 1 : 0);
26307
+ }, 0);
26290
26308
  const numCorrect = rubric.choices.reduce((sum, currentChoice) => {
26291
26309
  return currentChoice.correct ? sum + 1 : sum;
26292
26310
  }, 0);
@@ -26766,18 +26784,17 @@ const transform = (widgetOptions, strings, problemNum) => {
26766
26784
  };
26767
26785
  const propUpgrades$1 = {
26768
26786
  "1": v0props => {
26769
- let choices;
26770
- let hasNoneOfTheAbove;
26771
- if (!v0props.noneOfTheAbove) {
26772
- choices = v0props.choices;
26773
- hasNoneOfTheAbove = false;
26774
- } else {
26787
+ const {
26788
+ noneOfTheAbove,
26789
+ ...rest
26790
+ } = v0props;
26791
+ if (noneOfTheAbove) {
26775
26792
  throw new Error("radio widget v0 no longer supports auto noneOfTheAbove");
26776
26793
  }
26777
- return ___default["default"].extend(___default["default"].omit(v0props, "noneOfTheAbove"), {
26778
- choices: choices,
26779
- hasNoneOfTheAbove: hasNoneOfTheAbove
26780
- });
26794
+ return {
26795
+ ...rest,
26796
+ hasNoneOfTheAbove: false
26797
+ };
26781
26798
  }
26782
26799
  };
26783
26800
  var Radio = {
@@ -26814,23 +26831,36 @@ const getPromptJSON$n = (renderProps, userInput) => {
26814
26831
  };
26815
26832
  };
26816
26833
 
26817
- function scoreCategorizer(userInput, rubric, strings) {
26818
- let completed = true;
26819
- let allCorrect = true;
26820
- rubric.values.forEach((value, i) => {
26821
- if (userInput.values[i] == null) {
26822
- completed = false;
26823
- }
26824
- if (userInput.values[i] !== value) {
26825
- allCorrect = false;
26826
- }
26827
- });
26828
- if (!completed) {
26834
+ /**
26835
+ * Checks userInput from the categorizer widget to see if the user has selected
26836
+ * a category for each item.
26837
+ * @param userInput - The user's input corresponding to an array of indices that
26838
+ * represent the selected category for each row/item.
26839
+ * @param validationData - An array of strings corresponding to each row/item
26840
+ * @param strings - Used to provide a validation message
26841
+ */
26842
+ function validateCategorizer(userInput, validationData, strings) {
26843
+ const incomplete = validationData.items.some((_, i) => userInput.values[i] == null);
26844
+ if (incomplete) {
26829
26845
  return {
26830
26846
  type: "invalid",
26831
26847
  message: strings.invalidSelection
26832
26848
  };
26833
26849
  }
26850
+ return null;
26851
+ }
26852
+
26853
+ function scoreCategorizer(userInput, scoringData, strings) {
26854
+ const validationError = validateCategorizer(userInput, scoringData, strings);
26855
+ if (validationError) {
26856
+ return validationError;
26857
+ }
26858
+ let allCorrect = true;
26859
+ scoringData.values.forEach((value, i) => {
26860
+ if (userInput.values[i] !== value) {
26861
+ allCorrect = false;
26862
+ }
26863
+ });
26834
26864
  return {
26835
26865
  type: "points",
26836
26866
  earned: allCorrect ? 1 : 0,
@@ -27384,15 +27414,26 @@ const getPromptJSON$k = (renderProps, userInput) => {
27384
27414
  };
27385
27415
  };
27386
27416
 
27387
- function scoreDropdown(userInput, rubric) {
27388
- const selected = userInput.value;
27389
- if (selected === 0) {
27417
+ /**
27418
+ * Checks if the user has selected an item from the dropdown before scoring.
27419
+ * This is shown with a userInput value / index other than 0.
27420
+ */
27421
+ function validateDropdown(userInput) {
27422
+ if (userInput.value === 0) {
27390
27423
  return {
27391
27424
  type: "invalid",
27392
27425
  message: null
27393
27426
  };
27394
27427
  }
27395
- const correct = rubric.choices[selected - 1].correct;
27428
+ return null;
27429
+ }
27430
+
27431
+ function scoreDropdown(userInput, rubric) {
27432
+ const validationError = validateDropdown(userInput);
27433
+ if (validationError) {
27434
+ return validationError;
27435
+ }
27436
+ const correct = rubric.choices[userInput.value - 1].correct;
27396
27437
  return {
27397
27438
  type: "points",
27398
27439
  earned: correct ? 1 : 0,
@@ -27782,8 +27823,6 @@ const getNextState = (currentState, answerable) => {
27782
27823
  }
27783
27824
  };
27784
27825
 
27785
- // exports has no 'transform'
27786
-
27787
27826
  // A Graded Group is more or less a Group widget that displays a check
27788
27827
  // answer button below the rendered content. When clicked, the widget grades
27789
27828
  // the stuff inside and displays feedback about whether the inputted answer was
@@ -28453,7 +28492,6 @@ const getPromptJSON$g = (renderProps, userInput) => {
28453
28492
  };
28454
28493
  };
28455
28494
 
28456
- /* eslint-disable one-var */
28457
28495
  // @ts-expect-error - TS2339 - Property 'Plot' does not exist on type 'typeof Graphie'.
28458
28496
  const Plot$1 = Graphie.Plot;
28459
28497
  const DEFAULT_BACKGROUND_IMAGE = {
@@ -28554,14 +28592,14 @@ const Linear = ___default["default"].extend({}, PlotDefaults, {
28554
28592
  return [m, b];
28555
28593
  },
28556
28594
  getFunctionForCoeffs: function (coeffs, x) {
28557
- const m = coeffs[0],
28558
- b = coeffs[1];
28595
+ const m = coeffs[0];
28596
+ const b = coeffs[1];
28559
28597
  return m * x + b;
28560
28598
  },
28561
28599
  getEquationString: function (coords) {
28562
28600
  const coeffs = this.getCoefficients(coords);
28563
- const m = coeffs[0],
28564
- b = coeffs[1];
28601
+ const m = coeffs[0];
28602
+ const b = coeffs[1];
28565
28603
  return "y = " + m.toFixed(3) + "x + " + b.toFixed(3);
28566
28604
  }
28567
28605
  });
@@ -28585,9 +28623,9 @@ const Quadratic = ___default["default"].extend({}, PlotDefaults, {
28585
28623
  return [a, b, c];
28586
28624
  },
28587
28625
  getFunctionForCoeffs: function (coeffs, x) {
28588
- const a = coeffs[0],
28589
- b = coeffs[1],
28590
- c = coeffs[2];
28626
+ const a = coeffs[0];
28627
+ const b = coeffs[1];
28628
+ const c = coeffs[2];
28591
28629
  return (a * x + b) * x + c;
28592
28630
  },
28593
28631
  getPropsForCoeffs: function (coeffs) {
@@ -28599,9 +28637,9 @@ const Quadratic = ___default["default"].extend({}, PlotDefaults, {
28599
28637
  },
28600
28638
  getEquationString: function (coords) {
28601
28639
  const coeffs = this.getCoefficients(coords);
28602
- const a = coeffs[0],
28603
- b = coeffs[1],
28604
- c = coeffs[2];
28640
+ const a = coeffs[0];
28641
+ const b = coeffs[1];
28642
+ const c = coeffs[2];
28605
28643
  return "y = " + a.toFixed(3) + "x^2 + " + b.toFixed(3) + "x + " + c.toFixed(3);
28606
28644
  }
28607
28645
  });
@@ -28620,10 +28658,10 @@ const Sinusoid = ___default["default"].extend({}, PlotDefaults, {
28620
28658
  return [a, b, c, d];
28621
28659
  },
28622
28660
  getFunctionForCoeffs: function (coeffs, x) {
28623
- const a = coeffs[0],
28624
- b = coeffs[1],
28625
- c = coeffs[2],
28626
- d = coeffs[3];
28661
+ const a = coeffs[0];
28662
+ const b = coeffs[1];
28663
+ const c = coeffs[2];
28664
+ const d = coeffs[3];
28627
28665
  return a * Math.sin(b * x - c) + d;
28628
28666
  },
28629
28667
  getPropsForCoeffs: function (coeffs) {
@@ -28636,10 +28674,10 @@ const Sinusoid = ___default["default"].extend({}, PlotDefaults, {
28636
28674
  },
28637
28675
  getEquationString: function (coords) {
28638
28676
  const coeffs = this.getCoefficients(coords);
28639
- const a = coeffs[0],
28640
- b = coeffs[1],
28641
- c = coeffs[2],
28642
- d = coeffs[3];
28677
+ const a = coeffs[0];
28678
+ const b = coeffs[1];
28679
+ const c = coeffs[2];
28680
+ const d = coeffs[3];
28643
28681
  return "y = " + a.toFixed(3) + " sin(" + b.toFixed(3) + "x - " + c.toFixed(3) + ") + " + d.toFixed(3);
28644
28682
  },
28645
28683
  areEqual: function (coeffs1, coeffs2) {
@@ -28659,18 +28697,18 @@ const Tangent = ___default["default"].extend({}, PlotDefaults, {
28659
28697
  return [a, b, c, d];
28660
28698
  },
28661
28699
  getFunctionForCoeffs: function (coeffs, x) {
28662
- const a = coeffs[0],
28663
- b = coeffs[1],
28664
- c = coeffs[2],
28665
- d = coeffs[3];
28700
+ const a = coeffs[0];
28701
+ const b = coeffs[1];
28702
+ const c = coeffs[2];
28703
+ const d = coeffs[3];
28666
28704
  return a * Math.tan(b * x - c) + d;
28667
28705
  },
28668
28706
  getEquationString: function (coords) {
28669
28707
  const coeffs = this.getCoefficients(coords);
28670
- const a = coeffs[0],
28671
- b = coeffs[1],
28672
- c = coeffs[2],
28673
- d = coeffs[3];
28708
+ const a = coeffs[0];
28709
+ const b = coeffs[1];
28710
+ const c = coeffs[2];
28711
+ const d = coeffs[3];
28674
28712
  return "y = " + a.toFixed(3) + " sin(" + b.toFixed(3) + "x - " + c.toFixed(3) + ") + " + d.toFixed(3);
28675
28713
  },
28676
28714
  areEqual: function (coeffs1, coeffs2) {
@@ -28729,9 +28767,9 @@ const Exponential = ___default["default"].extend({}, PlotDefaults, {
28729
28767
  return [a, b, c];
28730
28768
  },
28731
28769
  getFunctionForCoeffs: function (coeffs, x) {
28732
- const a = coeffs[0],
28733
- b = coeffs[1],
28734
- c = coeffs[2];
28770
+ const a = coeffs[0];
28771
+ const b = coeffs[1];
28772
+ const c = coeffs[2];
28735
28773
  return a * Math.exp(b * x) + c;
28736
28774
  },
28737
28775
  getEquationString: function (coords, asymptote) {
@@ -28739,9 +28777,9 @@ const Exponential = ___default["default"].extend({}, PlotDefaults, {
28739
28777
  return null;
28740
28778
  }
28741
28779
  const coeffs = this.getCoefficients(coords, asymptote);
28742
- const a = coeffs[0],
28743
- b = coeffs[1],
28744
- c = coeffs[2];
28780
+ const a = coeffs[0];
28781
+ const b = coeffs[1];
28782
+ const c = coeffs[2];
28745
28783
  return "y = " + a.toFixed(3) + "e^(" + b.toFixed(3) + "x) + " + c.toFixed(3);
28746
28784
  }
28747
28785
  });
@@ -28786,9 +28824,9 @@ const Logarithm = ___default["default"].extend({}, PlotDefaults, {
28786
28824
  }
28787
28825
  },
28788
28826
  getFunctionForCoeffs: function (coeffs, x, asymptote) {
28789
- const a = coeffs[0],
28790
- b = coeffs[1],
28791
- c = coeffs[2];
28827
+ const a = coeffs[0];
28828
+ const b = coeffs[1];
28829
+ const c = coeffs[2];
28792
28830
  return a * Math.log(b * x + c);
28793
28831
  },
28794
28832
  getEquationString: function (coords, asymptote) {
@@ -28796,9 +28834,9 @@ const Logarithm = ___default["default"].extend({}, PlotDefaults, {
28796
28834
  return null;
28797
28835
  }
28798
28836
  const coeffs = this.getCoefficients(coords, asymptote);
28799
- const a = coeffs[0],
28800
- b = coeffs[1],
28801
- c = coeffs[2];
28837
+ const a = coeffs[0];
28838
+ const b = coeffs[1];
28839
+ const c = coeffs[2];
28802
28840
  return "y = ln(" + a.toFixed(3) + "x + " + b.toFixed(3) + ") + " + c.toFixed(3);
28803
28841
  }
28804
28842
  });
@@ -28822,16 +28860,16 @@ const AbsoluteValue = ___default["default"].extend({}, PlotDefaults, {
28822
28860
  return [m, horizontalOffset, verticalOffset];
28823
28861
  },
28824
28862
  getFunctionForCoeffs: function (coeffs, x) {
28825
- const m = coeffs[0],
28826
- horizontalOffset = coeffs[1],
28827
- verticalOffset = coeffs[2];
28863
+ const m = coeffs[0];
28864
+ const horizontalOffset = coeffs[1];
28865
+ const verticalOffset = coeffs[2];
28828
28866
  return m * Math.abs(x - horizontalOffset) + verticalOffset;
28829
28867
  },
28830
28868
  getEquationString: function (coords) {
28831
28869
  const coeffs = this.getCoefficients(coords);
28832
- const m = coeffs[0],
28833
- horizontalOffset = coeffs[1],
28834
- verticalOffset = coeffs[2];
28870
+ const m = coeffs[0];
28871
+ const horizontalOffset = coeffs[1];
28872
+ const verticalOffset = coeffs[2];
28835
28873
  return "y = " + m.toFixed(3) + "| x - " + horizontalOffset.toFixed(3) + "| + " + verticalOffset.toFixed(3);
28836
28874
  }
28837
28875
  });
@@ -32054,6 +32092,7 @@ const getNumericFormat = KhanMath.getNumericFormat;
32054
32092
  */
32055
32093
  class NumberInput extends React__namespace.Component {
32056
32094
  static contextType = PerseusI18nContext;
32095
+ inputRef = /*#__PURE__*/React__namespace.createRef();
32057
32096
  static propTypes = {
32058
32097
  value: PropTypes__default["default"].number,
32059
32098
  format: PropTypes__default["default"].string,
@@ -32080,23 +32119,24 @@ class NumberInput extends React__namespace.Component {
32080
32119
  this._setValue(this.props.value, this.state.format);
32081
32120
  }
32082
32121
  }
32122
+ _getInput = () => {
32123
+ if (!this.inputRef.current) {
32124
+ throw new perseusCore.PerseusError("Input ref accessed before set", perseusCore.Errors.Internal);
32125
+ }
32126
+ return this.inputRef.current;
32127
+ };
32083
32128
 
32084
32129
  /* Return the current "value" of this input
32085
32130
  * If empty, it returns the placeholder (if it is a number) or null
32086
32131
  */
32087
32132
  getValue = () => {
32088
- return this.parseInputValue(
32089
- // @ts-expect-error - TS2531 - Object is possibly 'null'. | TS2339 - Property 'value' does not exist on type 'Element | Text'.
32090
- ReactDOM__default["default"].findDOMNode(this.refs.input).value // eslint-disable-line react/no-string-refs
32091
- );
32133
+ return this.parseInputValue(this._getInput().value);
32092
32134
  };
32093
32135
 
32094
32136
  /* Return the current string value of this input */
32095
32137
  getStringValue = () => {
32096
- // @ts-expect-error - TS2531 - Object is possibly 'null'. | TS2339 - Property 'value' does not exist on type 'Element | Text'.
32097
- return ReactDOM__default["default"].findDOMNode(this.refs.input).value.toString(); // eslint-disable-line react/no-string-refs
32138
+ return this._getInput().toString();
32098
32139
  };
32099
-
32100
32140
  parseInputValue = value => {
32101
32141
  if (value === "") {
32102
32142
  const placeholder = this.props.placeholder;
@@ -32108,26 +32148,21 @@ class NumberInput extends React__namespace.Component {
32108
32148
 
32109
32149
  /* Set text input focus to this input */
32110
32150
  focus = () => {
32111
- // @ts-expect-error - TS2531 - Object is possibly 'null'. | TS2339 - Property 'focus' does not exist on type 'Element | Text'.
32112
- ReactDOM__default["default"].findDOMNode(this.refs.input).focus(); // eslint-disable-line react/no-string-refs
32151
+ this._getInput().focus();
32113
32152
  this._handleFocus();
32114
32153
  };
32115
32154
  blur = () => {
32116
- // @ts-expect-error - TS2531 - Object is possibly 'null'. | TS2339 - Property 'blur' does not exist on type 'Element | Text'.
32117
- ReactDOM__default["default"].findDOMNode(this.refs.input).blur(); // eslint-disable-line react/no-string-refs
32155
+ this._getInput().blur();
32118
32156
  this._handleBlur();
32119
32157
  };
32120
32158
  setSelectionRange = (selectionStart, selectionEnd) => {
32121
- // @ts-expect-error - TS2531 - Object is possibly 'null'. | TS2339 - Property 'setSelectionRange' does not exist on type 'Element | Text'.
32122
- ReactDOM__default["default"].findDOMNode(this).setSelectionRange(selectionStart, selectionEnd);
32159
+ this._getInput().setSelectionRange(selectionStart, selectionEnd);
32123
32160
  };
32124
32161
  getSelectionStart = () => {
32125
- // @ts-expect-error - TS2531 - Object is possibly 'null'. | TS2339 - Property 'selectionStart' does not exist on type 'Element | Text'.
32126
- return ReactDOM__default["default"].findDOMNode(this).selectionStart;
32162
+ return this._getInput().selectionStart;
32127
32163
  };
32128
32164
  getSelectionEnd = () => {
32129
- // @ts-expect-error - TS2531 - Object is possibly 'null'. | TS2339 - Property 'selectionEnd' does not exist on type 'Element | Text'.
32130
- return ReactDOM__default["default"].findDOMNode(this).selectionEnd;
32165
+ return this._getInput().selectionEnd;
32131
32166
  };
32132
32167
  _checkValidity = value => {
32133
32168
  if (value == null) {
@@ -32187,9 +32222,7 @@ class NumberInput extends React__namespace.Component {
32187
32222
  }
32188
32223
  };
32189
32224
  _setValue = (val, format) => {
32190
- // eslint-disable-next-line react/no-string-refs
32191
- // @ts-expect-error - TS2769 - No overload matches this call. | TS2339 - Property 'val' does not exist on type 'JQueryStatic'.
32192
- $__default["default"](ReactDOM__default["default"].findDOMNode(this.refs.input)).val(toNumericString(val, format));
32225
+ this._getInput().value = toNumericString(val, format);
32193
32226
  };
32194
32227
  render() {
32195
32228
  let classes = classNames__default["default"]({
@@ -32213,10 +32246,8 @@ class NumberInput extends React__namespace.Component {
32213
32246
  } = this.props;
32214
32247
  return /*#__PURE__*/React__namespace.createElement("input", _extends({}, restProps, {
32215
32248
  className: classes,
32216
- type: "text"
32217
- // eslint-disable-next-line react/no-string-refs
32218
- ,
32219
- ref: "input",
32249
+ type: "text",
32250
+ ref: this.inputRef,
32220
32251
  onChange: this._handleChange,
32221
32252
  onFocus: this._handleFocus,
32222
32253
  onBlur: this._handleBlur,
@@ -33711,15 +33742,18 @@ function useControlPoint(params) {
33711
33742
  } = useGraphConfig();
33712
33743
  const {
33713
33744
  point,
33714
- sequenceNumber,
33745
+ ariaDescribedBy,
33746
+ ariaLabel,
33747
+ ariaLive = "polite",
33715
33748
  color,
33716
- cursor,
33717
33749
  constrain = p => snap(snapStep, p),
33750
+ cursor,
33751
+ forwardedRef = noop$1,
33752
+ sequenceNumber = 1,
33718
33753
  onMove = noop$1,
33719
33754
  onClick = noop$1,
33720
33755
  onFocus = noop$1,
33721
- onBlur = noop$1,
33722
- forwardedRef = noop$1
33756
+ onBlur = noop$1
33723
33757
  } = params;
33724
33758
  const {
33725
33759
  strings,
@@ -33742,6 +33776,13 @@ function useControlPoint(params) {
33742
33776
  onMove,
33743
33777
  constrainKeyboardMovement: constrain
33744
33778
  });
33779
+
33780
+ // if custom aria label is not provided, will use default of sequence number and point coordinates
33781
+ const pointAriaLabel = ariaLabel || strings.srPointAtCoordinates({
33782
+ num: sequenceNumber,
33783
+ x: srFormatNumber(point[X], locale),
33784
+ y: srFormatNumber(point[Y], locale)
33785
+ });
33745
33786
  React.useLayoutEffect(() => {
33746
33787
  setForwardedRef(forwardedRef, focusableHandleRef.current);
33747
33788
  }, [forwardedRef]);
@@ -33751,15 +33792,9 @@ function useControlPoint(params) {
33751
33792
  tabIndex: disableKeyboardInteraction ? -1 : 0,
33752
33793
  ref: focusableHandleRef,
33753
33794
  role: "button",
33754
- "aria-label": strings.srPointAtCoordinates({
33755
- num: sequenceNumber,
33756
- x: srFormatNumber(point[X], locale),
33757
- y: srFormatNumber(point[Y], locale)
33758
- })
33759
- // aria-live="assertive" causes the new location of the point to be
33760
- // announced immediately on move.
33761
- ,
33762
- "aria-live": "assertive",
33795
+ "aria-describedby": ariaDescribedBy,
33796
+ "aria-label": pointAriaLabel,
33797
+ "aria-live": ariaLive,
33763
33798
  onFocus: event => {
33764
33799
  onFocus(event);
33765
33800
  setFocused(true);
@@ -36835,7 +36870,7 @@ const StatefulMafsGraph = /*#__PURE__*/React__namespace.forwardRef((props, ref)
36835
36870
  });
36836
36871
 
36837
36872
  const eq$1 = Util.eq;
36838
- const deepEq$2 = Util.deepEq;
36873
+ const deepEq$3 = Util.deepEq;
36839
36874
  function scoreInteractiveGraph(userInput, rubric) {
36840
36875
  // None-type graphs are not graded
36841
36876
  if (userInput.type === "none" && rubric.correct.type === "none") {
@@ -36885,7 +36920,7 @@ function scoreInteractiveGraph(userInput, rubric) {
36885
36920
  // If the parabola coefficients match, it's correct.
36886
36921
  const guessCoeffs = getQuadraticCoefficients(userInput.coords);
36887
36922
  const correctCoeffs = getQuadraticCoefficients(rubric.correct.coords);
36888
- if (deepEq$2(guessCoeffs, correctCoeffs)) {
36923
+ if (deepEq$3(guessCoeffs, correctCoeffs)) {
36889
36924
  return {
36890
36925
  type: "points",
36891
36926
  earned: 1,
@@ -36899,7 +36934,7 @@ function scoreInteractiveGraph(userInput, rubric) {
36899
36934
  const canonicalGuessCoeffs = canonicalSineCoefficients$1(guessCoeffs);
36900
36935
  const canonicalCorrectCoeffs = canonicalSineCoefficients$1(correctCoeffs);
36901
36936
  // If the canonical coefficients match, it's correct.
36902
- if (deepEq$2(canonicalGuessCoeffs, canonicalCorrectCoeffs)) {
36937
+ if (deepEq$3(canonicalGuessCoeffs, canonicalCorrectCoeffs)) {
36903
36938
  return {
36904
36939
  type: "points",
36905
36940
  earned: 1,
@@ -36908,7 +36943,7 @@ function scoreInteractiveGraph(userInput, rubric) {
36908
36943
  };
36909
36944
  }
36910
36945
  } else if (userInput.type === "circle" && rubric.correct.type === "circle") {
36911
- if (deepEq$2(userInput.center, rubric.correct.center) && eq$1(userInput.radius, rubric.correct.radius)) {
36946
+ if (deepEq$3(userInput.center, rubric.correct.center) && eq$1(userInput.radius, rubric.correct.radius)) {
36912
36947
  return {
36913
36948
  type: "points",
36914
36949
  earned: 1,
@@ -36930,7 +36965,7 @@ function scoreInteractiveGraph(userInput, rubric) {
36930
36965
  guess?.sort();
36931
36966
  // @ts-expect-error - TS2339 - Property 'sort' does not exist on type 'readonly Coord[]'.
36932
36967
  correct.sort();
36933
- if (deepEq$2(guess, correct)) {
36968
+ if (deepEq$3(guess, correct)) {
36934
36969
  return {
36935
36970
  type: "points",
36936
36971
  earned: 1,
@@ -36952,7 +36987,7 @@ function scoreInteractiveGraph(userInput, rubric) {
36952
36987
  /* exact */
36953
36988
  guess.sort();
36954
36989
  correct.sort();
36955
- match = deepEq$2(guess, correct);
36990
+ match = deepEq$3(guess, correct);
36956
36991
  }
36957
36992
  if (match) {
36958
36993
  return {
@@ -36967,7 +37002,7 @@ function scoreInteractiveGraph(userInput, rubric) {
36967
37002
  let correct = Util.deepClone(rubric.correct.coords);
36968
37003
  guess = ___default["default"].invoke(guess, "sort").sort();
36969
37004
  correct = ___default["default"].invoke(correct, "sort").sort();
36970
- if (deepEq$2(guess, correct)) {
37005
+ if (deepEq$3(guess, correct)) {
36971
37006
  return {
36972
37007
  type: "points",
36973
37008
  earned: 1,
@@ -36978,7 +37013,7 @@ function scoreInteractiveGraph(userInput, rubric) {
36978
37013
  } else if (userInput.type === "ray" && rubric.correct.type === "ray" && userInput.coords != null) {
36979
37014
  const guess = userInput.coords;
36980
37015
  const correct = rubric.correct.coords;
36981
- if (deepEq$2(guess[0], correct[0]) && collinear(correct[0], correct[1], guess[1])) {
37016
+ if (deepEq$3(guess[0], correct[0]) && collinear(correct[0], correct[1], guess[1])) {
36982
37017
  return {
36983
37018
  type: "points",
36984
37019
  earned: 1,
@@ -37005,7 +37040,7 @@ function scoreInteractiveGraph(userInput, rubric) {
37005
37040
  /* exact */
37006
37041
  match =
37007
37042
  // @ts-expect-error - TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'.
37008
- deepEq$2(guess[1], correct[1]) &&
37043
+ deepEq$3(guess[1], correct[1]) &&
37009
37044
  // @ts-expect-error - TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'.
37010
37045
  collinear(correct[1], correct[0], guess[0]) &&
37011
37046
  // @ts-expect-error - TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'.
@@ -41267,14 +41302,20 @@ class Measurer extends React__namespace.Component {
41267
41302
  }
41268
41303
  const propUpgrades = {
41269
41304
  "1": v0props => {
41270
- const v1props = ___default["default"](v0props).chain().omit("imageUrl", "imageTop", "imageLeft").extend({
41305
+ const {
41306
+ imageUrl,
41307
+ imageTop,
41308
+ imageLeft,
41309
+ ...rest
41310
+ } = v0props;
41311
+ return {
41312
+ ...rest,
41271
41313
  image: {
41272
- url: v0props.imageUrl,
41273
- top: v0props.imageTop,
41274
- left: v0props.imageLeft
41314
+ url: imageUrl,
41315
+ top: imageTop,
41316
+ left: imageLeft
41275
41317
  }
41276
- }).value();
41277
- return v1props;
41318
+ };
41278
41319
  }
41279
41320
  };
41280
41321
  var Measurer$1 = {
@@ -42808,14 +42849,28 @@ const getPromptJSON$5 = (renderProps, userInput) => {
42808
42849
  };
42809
42850
  };
42810
42851
 
42811
- function scoreOrderer(userInput, rubric) {
42852
+ /**
42853
+ * Checks user input from the orderer widget to see if the user has started
42854
+ * ordering the options, making the widget scorable.
42855
+ * @param userInput
42856
+ * @see `scoreOrderer` for more details.
42857
+ */
42858
+ function validateOrderer(userInput) {
42812
42859
  if (userInput.current.length === 0) {
42813
42860
  return {
42814
42861
  type: "invalid",
42815
42862
  message: null
42816
42863
  };
42817
42864
  }
42818
- const correct = ___default["default"].isEqual(userInput.current, ___default["default"].pluck(rubric.correctOptions, "content"));
42865
+ return null;
42866
+ }
42867
+
42868
+ function scoreOrderer(userInput, rubric) {
42869
+ const validateError = validateOrderer(userInput);
42870
+ if (validateError) {
42871
+ return validateError;
42872
+ }
42873
+ const correct = ___default["default"].isEqual(userInput.current, rubric.correctOptions.map(option => option.content));
42819
42874
  return {
42820
42875
  type: "points",
42821
42876
  earned: correct ? 1 : 0,
@@ -42991,15 +43046,13 @@ class Card extends React__namespace.Component {
42991
43046
  }))));
42992
43047
  }
42993
43048
  }
42994
- const NORMAL = "normal";
42995
- const HORIZONTAL = "horizontal";
42996
43049
  class Orderer extends React__namespace.Component {
42997
43050
  static defaultProps = {
42998
43051
  current: [],
42999
43052
  options: [],
43000
43053
  correctOptions: [],
43001
- height: NORMAL,
43002
- layout: HORIZONTAL,
43054
+ height: "normal",
43055
+ layout: "horizontal",
43003
43056
  linterContext: PerseusLinter.linterContextDefault
43004
43057
  };
43005
43058
  state = {
@@ -43152,7 +43205,7 @@ class Orderer extends React__namespace.Component {
43152
43205
  };
43153
43206
  findCorrectIndex = (draggable, list) => {
43154
43207
  // Find the correct index for a card given the current cards.
43155
- const isHorizontal = this.props.layout === HORIZONTAL;
43208
+ const isHorizontal = this.props.layout === "horizontal";
43156
43209
  // eslint-disable-next-line react/no-string-refs
43157
43210
  // @ts-expect-error - TS2769 - No overload matches this call.
43158
43211
  const $dragList = $__default["default"](ReactDOM__default["default"].findDOMNode(this.refs.dragList));
@@ -43202,28 +43255,24 @@ class Orderer extends React__namespace.Component {
43202
43255
  if (draggable == null) {
43203
43256
  return false;
43204
43257
  }
43205
- const isHorizontal = this.props.layout === HORIZONTAL,
43206
- // @ts-expect-error - TS2769 - No overload matches this call.
43207
- $draggable = $__default["default"](ReactDOM__default["default"].findDOMNode(draggable)),
43208
- // eslint-disable-next-line react/no-string-refs
43209
- // @ts-expect-error - TS2769 - No overload matches this call.
43210
- $bank = $__default["default"](ReactDOM__default["default"].findDOMNode(this.refs.bank)),
43211
- // @ts-expect-error - TS2339 - Property 'offset' does not exist on type 'JQueryStatic'.
43212
- draggableOffset = $draggable.offset(),
43213
- // @ts-expect-error - TS2339 - Property 'offset' does not exist on type 'JQueryStatic'.
43214
- bankOffset = $bank.offset(),
43215
- // @ts-expect-error - TS2339 - Property 'outerHeight' does not exist on type 'JQueryStatic'.
43216
- draggableHeight = $draggable.outerHeight(true),
43217
- // @ts-expect-error - TS2339 - Property 'outerHeight' does not exist on type 'JQueryStatic'.
43218
- bankHeight = $bank.outerHeight(true),
43219
- // @ts-expect-error - TS2339 - Property 'outerWidth' does not exist on type 'JQueryStatic'.
43220
- bankWidth = $bank.outerWidth(true),
43221
- // eslint-disable-next-line react/no-string-refs
43222
- dragList = ReactDOM__default["default"].findDOMNode(this.refs.dragList);
43223
- // @ts-expect-error - TS2769 - No overload matches this call. | TS2339 - Property 'width' does not exist on type 'JQueryStatic'.
43224
- $__default["default"](dragList).width();
43225
- const // @ts-expect-error - TS2339 - Property 'outerWidth' does not exist on type 'JQueryStatic'.
43226
- draggableWidth = $draggable.outerWidth(true);
43258
+ const isHorizontal = this.props.layout === "horizontal";
43259
+ // @ts-expect-error - TS2769 - No overload matches this call.
43260
+ const $draggable = $__default["default"](ReactDOM__default["default"].findDOMNode(draggable));
43261
+ // eslint-disable-next-line react/no-string-refs
43262
+ // @ts-expect-error - TS2769 - No overload matches this call.
43263
+ const $bank = $__default["default"](ReactDOM__default["default"].findDOMNode(this.refs.bank));
43264
+ // @ts-expect-error - TS2339 - Property 'offset' does not exist on type 'JQueryStatic'.
43265
+ const draggableOffset = $draggable.offset();
43266
+ // @ts-expect-error - TS2339 - Property 'offset' does not exist on type 'JQueryStatic'.
43267
+ const bankOffset = $bank.offset();
43268
+ // @ts-expect-error - TS2339 - Property 'outerHeight' does not exist on type 'JQueryStatic'.
43269
+ const draggableHeight = $draggable.outerHeight(true);
43270
+ // @ts-expect-error - TS2339 - Property 'outerHeight' does not exist on type 'JQueryStatic'.
43271
+ const bankHeight = $bank.outerHeight(true);
43272
+ // @ts-expect-error - TS2339 - Property 'outerWidth' does not exist on type 'JQueryStatic'.
43273
+ const bankWidth = $bank.outerWidth(true);
43274
+ // @ts-expect-error - TS2339 - Property 'outerWidth' does not exist on type 'JQueryStatic'.
43275
+ const draggableWidth = $draggable.outerWidth(true);
43227
43276
  if (isHorizontal) {
43228
43277
  return draggableOffset.top + draggableHeight / 2 < bankOffset.top + bankHeight;
43229
43278
  }
@@ -43642,18 +43691,36 @@ const getPromptJSON$3 = () => {
43642
43691
  };
43643
43692
 
43644
43693
  const {
43645
- deepEq: deepEq$1
43694
+ deepEq: deepEq$2
43646
43695
  } = Util;
43647
- function scorePlotter(userInput, rubric) {
43648
- if (deepEq$1(userInput, rubric.starting)) {
43696
+
43697
+ /**
43698
+ * Checks user input to confirm it is not the same as the starting values for the graph.
43699
+ * This means the user has modified the graph, and the question can be scored.
43700
+ *
43701
+ * @see 'scorePlotter' for more details on scoring.
43702
+ */
43703
+ function validatePlotter(userInput, validationData) {
43704
+ if (deepEq$2(userInput, validationData.starting)) {
43649
43705
  return {
43650
43706
  type: "invalid",
43651
43707
  message: null
43652
43708
  };
43653
43709
  }
43710
+ return null;
43711
+ }
43712
+
43713
+ const {
43714
+ deepEq: deepEq$1
43715
+ } = Util;
43716
+ function scorePlotter(userInput, scoringData) {
43717
+ const validationError = validatePlotter(userInput, scoringData);
43718
+ if (validationError) {
43719
+ return validationError;
43720
+ }
43654
43721
  return {
43655
43722
  type: "points",
43656
- earned: deepEq$1(userInput, rubric.correct) ? 1 : 0,
43723
+ earned: deepEq$1(userInput, scoringData.correct) ? 1 : 0,
43657
43724
  total: 1,
43658
43725
  message: null
43659
43726
  };
@@ -44540,7 +44607,14 @@ const getPromptJSON$1 = userInput => {
44540
44607
  };
44541
44608
  };
44542
44609
 
44543
- function scoreSorter(userInput, rubric) {
44610
+ /**
44611
+ * Checks user input for the sorter widget to ensure that the user has made
44612
+ * changes before attempting to score the widget.
44613
+ * @param userInput
44614
+ * @see 'scoreSorter' in 'packages/perseus/src/widgets/sorter/score-sorter.ts'
44615
+ * for more details on how the sorter widget is scored.
44616
+ */
44617
+ function validateSorter(userInput) {
44544
44618
  // If the sorter widget hasn't been changed yet, we treat it as "empty" which
44545
44619
  // prevents the "Check" button from becoming active. We want the user
44546
44620
  // to make a change before trying to move forward. This makes an
@@ -44553,6 +44627,14 @@ function scoreSorter(userInput, rubric) {
44553
44627
  message: null
44554
44628
  };
44555
44629
  }
44630
+ return null;
44631
+ }
44632
+
44633
+ function scoreSorter(userInput, rubric) {
44634
+ const validationError = validateSorter(userInput);
44635
+ if (validationError) {
44636
+ return validationError;
44637
+ }
44556
44638
  const correct = Util.deepEq(userInput.options, rubric.correct);
44557
44639
  return {
44558
44640
  type: "points",
@@ -45127,7 +45209,7 @@ const init = function () {
45127
45209
 
45128
45210
  // This file is processed by a Rollup plugin (replace) to inject the production
45129
45211
  const libName = "@khanacademy/perseus";
45130
- const libVersion = "43.0.1";
45212
+ const libVersion = "43.1.0";
45131
45213
  perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
45132
45214
 
45133
45215
  const apiVersion = {
@@ -45412,7 +45494,6 @@ const styles$3 = aphrodite.StyleSheet.create({
45412
45494
  }
45413
45495
  });
45414
45496
 
45415
- /* eslint-disable @khanacademy/ts-no-error-suppressions */
45416
45497
  // Use of UNSAFE_componentWillReceiveProps:
45417
45498
  // eslint-disable-next-line react/no-unsafe
45418
45499
  class HintsRenderer extends React__namespace.Component {
@@ -48638,6 +48719,27 @@ function pair(parseA, parseB) {
48638
48719
  };
48639
48720
  }
48640
48721
 
48722
+ function pipeParsers(p) {
48723
+ return new ParserPipeline(p);
48724
+ }
48725
+ class ParserPipeline {
48726
+ constructor(parser) {
48727
+ this.parser = parser;
48728
+ }
48729
+ then(nextParser) {
48730
+ return new ParserPipeline(composeParsers(this.parser, nextParser));
48731
+ }
48732
+ }
48733
+ function composeParsers(parserA, parserB) {
48734
+ return (rawValue, ctx) => {
48735
+ const partialResult = parserA(rawValue, ctx);
48736
+ if (isFailure(partialResult)) {
48737
+ return partialResult;
48738
+ }
48739
+ return parserB(partialResult.value, ctx);
48740
+ };
48741
+ }
48742
+
48641
48743
  function record(parseKey, parseValue) {
48642
48744
  return (rawValue, ctx) => {
48643
48745
  if (!isObject(rawValue)) {
@@ -48718,6 +48820,20 @@ function either(parseA, parseB) {
48718
48820
  };
48719
48821
  }
48720
48822
 
48823
+ function defaulted(parser, fallback) {
48824
+ return (rawValue, ctx) => {
48825
+ if (rawValue == null) {
48826
+ return success(fallback(rawValue));
48827
+ }
48828
+ return parser(rawValue, ctx);
48829
+ };
48830
+ }
48831
+
48832
+ const parseImages = defaulted(record(string, object({
48833
+ width: number,
48834
+ height: number
48835
+ })), () => ({}));
48836
+
48721
48837
  function parseWidget(parseType, parseOptions) {
48722
48838
  return object({
48723
48839
  type: parseType,
@@ -48726,10 +48842,21 @@ function parseWidget(parseType, parseOptions) {
48726
48842
  alignment: optional(string),
48727
48843
  options: parseOptions,
48728
48844
  key: optional(number),
48729
- version: object({
48845
+ version: optional(object({
48730
48846
  major: number,
48731
48847
  minor: number
48732
- })
48848
+ }))
48849
+ });
48850
+ }
48851
+ function parseWidgetWithVersion(parseVersion, parseType, parseOptions) {
48852
+ return object({
48853
+ type: parseType,
48854
+ static: optional(boolean),
48855
+ graded: optional(boolean),
48856
+ alignment: optional(string),
48857
+ options: parseOptions,
48858
+ key: optional(number),
48859
+ version: parseVersion
48733
48860
  });
48734
48861
  }
48735
48862
 
@@ -48737,7 +48864,7 @@ const parseCategorizerWidget = parseWidget(constant("categorizer"), object({
48737
48864
  items: array(string),
48738
48865
  categories: array(string),
48739
48866
  randomizeItems: boolean,
48740
- static: boolean,
48867
+ static: defaulted(boolean, () => false),
48741
48868
  values: array(number),
48742
48869
  highlightLint: optional(boolean),
48743
48870
  linterContext: optional(object({
@@ -48769,7 +48896,7 @@ const parseDefinitionWidget = parseWidget(constant("definition"), object({
48769
48896
 
48770
48897
  const parseDropdownWidget = parseWidget(constant("dropdown"), object({
48771
48898
  placeholder: string,
48772
- static: boolean,
48899
+ static: defaulted(boolean, () => false),
48773
48900
  choices: array(object({
48774
48901
  content: string,
48775
48902
  correct: boolean
@@ -48792,9 +48919,12 @@ const parseAnswerForm = object({
48792
48919
  form: boolean,
48793
48920
  simplify: boolean,
48794
48921
  considered: enumeration("correct", "wrong", "ungraded"),
48795
- key: optional(string)
48922
+ key: pipeParsers(optional(union(string).or(number).parser)).then((key, ctx) => ctx.success(String(key))).parser
48796
48923
  });
48797
- const parseExpressionWidget = parseWidget(constant("expression"), object({
48924
+ const parseExpressionWidgetV1 = parseWidgetWithVersion(object({
48925
+ major: constant(1),
48926
+ minor: number
48927
+ }), constant("expression"), object({
48798
48928
  answerForms: array(parseAnswerForm),
48799
48929
  functions: array(string),
48800
48930
  times: boolean,
@@ -48803,23 +48933,69 @@ const parseExpressionWidget = parseWidget(constant("expression"), object({
48803
48933
  buttonSets: array(enumeration("basic", "basic+div", "trig", "prealgebra", "logarithms", "basic relations", "advanced relations")),
48804
48934
  buttonsVisible: optional(enumeration("always", "never", "focused"))
48805
48935
  }));
48936
+ const parseExpressionWidgetV0 = parseWidgetWithVersion(optional(object({
48937
+ major: constant(0),
48938
+ minor: number
48939
+ })), constant("expression"), object({
48940
+ functions: array(string),
48941
+ times: boolean,
48942
+ visibleLabel: optional(string),
48943
+ ariaLabel: optional(string),
48944
+ form: boolean,
48945
+ simplify: boolean,
48946
+ value: string,
48947
+ buttonSets: array(enumeration("basic", "basic+div", "trig", "prealgebra", "logarithms", "basic relations", "advanced relations")),
48948
+ buttonsVisible: optional(enumeration("always", "never", "focused"))
48949
+ }));
48950
+ function migrateV0ToV1(widget, ctx) {
48951
+ const {
48952
+ options
48953
+ } = widget;
48954
+ return ctx.success({
48955
+ ...widget,
48956
+ version: {
48957
+ major: 1,
48958
+ minor: 0
48959
+ },
48960
+ options: {
48961
+ times: options.times,
48962
+ buttonSets: options.buttonSets,
48963
+ functions: options.functions,
48964
+ buttonsVisible: options.buttonsVisible,
48965
+ visibleLabel: options.visibleLabel,
48966
+ ariaLabel: options.ariaLabel,
48967
+ answerForms: [{
48968
+ considered: "correct",
48969
+ form: options.form,
48970
+ simplify: options.simplify,
48971
+ value: options.value
48972
+ }]
48973
+ }
48974
+ });
48975
+ }
48976
+ const parseExpressionWidget = union(parseExpressionWidgetV1).or(pipeParsers(parseExpressionWidgetV0).then(migrateV0ToV1).parser).parser;
48806
48977
 
48807
- const parsePerseusRenderer = object({
48808
- content: string,
48978
+ const parsePerseusRenderer = defaulted(object({
48979
+ // TODO(benchristel): content is also defaulted to empty string in
48980
+ // renderer.tsx. See if we can remove one default or the other.
48981
+ content: defaulted(string, () => ""),
48809
48982
  // This module has an import cycle with parseWidgetsMap, because the
48810
48983
  // `group` widget can contain another renderer.
48811
48984
  // The anonymous function below ensures that we don't try to access
48812
48985
  // parseWidgetsMap before it's defined.
48813
- widgets: (rawVal, ctx) => parseWidgetsMap(rawVal, ctx),
48986
+ widgets: defaulted((rawVal, ctx) => parseWidgetsMap(rawVal, ctx), () => ({})),
48814
48987
  metadata: optional(array(string)),
48815
- images: record(string, object({
48816
- width: number,
48817
- height: number
48818
- }))
48819
- });
48988
+ images: parseImages
48989
+ }),
48990
+ // Default value
48991
+ () => ({
48992
+ content: "",
48993
+ widgets: {},
48994
+ images: {}
48995
+ }));
48820
48996
 
48821
48997
  const parseGradedGroupWidgetOptions = object({
48822
- title: string,
48998
+ title: defaulted(string, () => ""),
48823
48999
  hasHint: optional(nullable(boolean)),
48824
49000
  // This module has an import cycle with parsePerseusRenderer.
48825
49001
  // The anonymous function below ensures that we don't try to access
@@ -48913,7 +49089,7 @@ const parseIframeWidget = parseWidget(constant("iframe"), object({
48913
49089
  height: union(number).or(string).parser,
48914
49090
  allowFullScreen: boolean,
48915
49091
  allowTopNavigation: optional(boolean),
48916
- static: boolean
49092
+ static: defaulted(boolean, () => false)
48917
49093
  }));
48918
49094
 
48919
49095
  const parsePerseusImageBackground = object({
@@ -49292,13 +49468,13 @@ const parseMatcherWidget = parseWidget(constant("matcher"), object({
49292
49468
  padding: boolean
49293
49469
  }));
49294
49470
 
49295
- const parseMatrixWidget = parseWidget(constant("matrix"), object({
49296
- prefix: string,
49297
- suffix: string,
49471
+ const parseMatrixWidget = parseWidget(defaulted(constant("matrix"), () => "matrix"), object({
49472
+ prefix: optional(string),
49473
+ suffix: optional(string),
49298
49474
  answers: array(array(number)),
49299
- cursorPosition: array(number),
49475
+ cursorPosition: optional(array(number)),
49300
49476
  matrixBoardSize: array(number),
49301
- static: boolean
49477
+ static: optional(boolean)
49302
49478
  }));
49303
49479
 
49304
49480
  const parseMeasurerWidget = parseWidget(constant("measurer"), object({
@@ -49368,8 +49544,8 @@ const parseOrdererWidget = parseWidget(constant("orderer"), object({
49368
49544
  options: array(parseRenderer),
49369
49545
  correctOptions: array(parseRenderer),
49370
49546
  otherOptions: array(parseRenderer),
49371
- height: string,
49372
- layout: string
49547
+ height: enumeration("normal", "auto"),
49548
+ layout: enumeration("horizontal", "vertical")
49373
49549
  }));
49374
49550
 
49375
49551
  const parsePassageRefWidget = parseWidget(constant("passage-ref"), object({
@@ -49414,7 +49590,7 @@ const parsePythonProgramWidget = parseWidget(constant("python-program"), object(
49414
49590
 
49415
49591
  const parseRadioWidget = parseWidget(constant("radio"), object({
49416
49592
  choices: array(object({
49417
- content: string,
49593
+ content: defaulted(string, () => ""),
49418
49594
  clue: optional(string),
49419
49595
  correct: optional(boolean),
49420
49596
  isNoneOfTheAbove: optional(boolean),
@@ -49576,26 +49752,45 @@ const parseWidgetMapKeyComponents = pair(string, parseStringToPositiveInt);
49576
49752
  const parseHint = object({
49577
49753
  replace: optional(boolean),
49578
49754
  content: string,
49579
- widgets: parseWidgetsMap,
49755
+ widgets: defaulted(parseWidgetsMap, () => ({})),
49580
49756
  metadata: optional(array(string)),
49581
- images: record(string, object({
49582
- width: number,
49583
- height: number
49584
- }))
49757
+ images: parseImages
49585
49758
  });
49586
49759
 
49587
49760
  object({
49588
49761
  question: parsePerseusRenderer,
49589
- hints: array(parseHint),
49590
- answerArea: record(enumeration(...ItemExtras), boolean),
49591
- itemDataVersion: object({
49762
+ hints: defaulted(array(parseHint), () => []),
49763
+ answerArea: pipeParsers(defaulted(object({}), () => ({}))).then(migrateAnswerArea).then(record(enumeration(...ItemExtras), boolean)).parser,
49764
+ itemDataVersion: optional(object({
49592
49765
  major: number,
49593
49766
  minor: number
49594
- }),
49767
+ })),
49595
49768
  // Deprecated field
49596
49769
  answer: any
49597
49770
  });
49598
49771
 
49772
+ // Some answerAreas have extra fields, like:
49773
+ //
49774
+ // "answerArea": {
49775
+ // "type": "multiple",
49776
+ // "options": {
49777
+ // "content": "",
49778
+ // "images": {},
49779
+ // "widgets": {}
49780
+ // }
49781
+ // }
49782
+ //
49783
+ // The "type" and "options" fields don't seem to be used anywhere. This
49784
+ // migration function removes them.
49785
+ function migrateAnswerArea(rawValue, ctx) {
49786
+ const {
49787
+ type: _,
49788
+ options: __,
49789
+ ...rest
49790
+ } = rawValue;
49791
+ return ctx.success(rest);
49792
+ }
49793
+
49599
49794
  /**
49600
49795
  * Helper to parse PerseusItem JSON
49601
49796
  * Why not just use JSON.parse? We want: