@khanacademy/perseus 35.0.0 → 35.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/dist/es/index.js +756 -615
  2. package/dist/es/index.js.map +1 -1
  3. package/dist/index.js +756 -613
  4. package/dist/index.js.map +1 -1
  5. package/dist/perseus-api.d.ts +2 -0
  6. package/dist/perseus-types.d.ts +7 -0
  7. package/dist/server-item-renderer.d.ts +2 -0
  8. package/dist/types.d.ts +3 -4
  9. package/dist/validation.types.d.ts +5 -2
  10. package/dist/widgets/cs-program/cs-program-validator.d.ts +3 -0
  11. package/dist/widgets/cs-program/cs-program.d.ts +2 -2
  12. package/dist/widgets/expression/expression.d.ts +14 -2
  13. package/dist/widgets/grapher/grapher-validator.d.ts +4 -0
  14. package/dist/widgets/grapher/util.d.ts +0 -2
  15. package/dist/widgets/iframe/iframe-validator.d.ts +3 -0
  16. package/dist/widgets/iframe/iframe.d.ts +3 -2
  17. package/dist/widgets/interactive-graph.d.ts +3 -3
  18. package/dist/widgets/interactive-graphs/backgrounds/axis-ticks.d.ts +1 -1
  19. package/dist/widgets/interactive-graphs/graph-locked-layer.d.ts +2 -0
  20. package/dist/widgets/interactive-graphs/interactive-graph-question-builder.d.ts +7 -0
  21. package/dist/widgets/interactive-graphs/interactive-graph-validator.d.ts +4 -0
  22. package/dist/widgets/interactive-graphs/locked-figures/locked-ellipse.d.ts +5 -1
  23. package/dist/widgets/interactive-graphs/locked-figures/locked-function.d.ts +5 -1
  24. package/dist/widgets/interactive-graphs/locked-figures/locked-line.d.ts +2 -0
  25. package/dist/widgets/interactive-graphs/locked-figures/locked-point.d.ts +5 -1
  26. package/dist/widgets/interactive-graphs/locked-figures/locked-polygon.d.ts +5 -1
  27. package/dist/widgets/interactive-graphs/locked-figures/locked-vector.d.ts +5 -1
  28. package/dist/widgets/interactive-graphs/mafs-state-to-interactive-graph.d.ts +3 -0
  29. package/dist/widgets/label-image/label-image.d.ts +4 -0
  30. package/dist/widgets/matcher/matcher-validator.d.ts +4 -0
  31. package/dist/widgets/matcher/matcher.d.ts +3 -2
  32. package/dist/widgets/matrix/matrix-validator.d.ts +5 -0
  33. package/dist/widgets/matrix/matrix.d.ts +1 -0
  34. package/dist/widgets/number-line/number-line-validator.d.ts +4 -0
  35. package/dist/widgets/number-line/number-line.d.ts +3 -2
  36. package/dist/widgets/plotter/plotter-validator.d.ts +4 -0
  37. package/dist/widgets/plotter/plotter.d.ts +1 -0
  38. package/dist/widgets/table/table-validator.d.ts +5 -0
  39. package/dist/widgets/table/table.d.ts +1 -0
  40. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -3254,7 +3254,7 @@ function constrainedTickStepsFromTickSteps(tickSteps, ranges) {
3254
3254
  /**
3255
3255
  * Approximate equality on numbers and primitives.
3256
3256
  */
3257
- function eq$3(x, y) {
3257
+ function eq$4(x, y) {
3258
3258
  if (typeof x === "number" && typeof y === "number") {
3259
3259
  return Math.abs(x - y) < 1e-9;
3260
3260
  }
@@ -3281,7 +3281,7 @@ function deepEq$5(x, y) {
3281
3281
  return false;
3282
3282
  }
3283
3283
  if (typeof x === "function" && typeof y === "function") {
3284
- return eq$3(x, y);
3284
+ return eq$4(x, y);
3285
3285
  }
3286
3286
  if (typeof x === "function" || typeof y === "function") {
3287
3287
  return false;
@@ -3298,7 +3298,7 @@ function deepEq$5(x, y) {
3298
3298
  if (typeof x === "object" && !!x || typeof y === "object" && !!y) {
3299
3299
  return false;
3300
3300
  }
3301
- return eq$3(x, y);
3301
+ return eq$4(x, y);
3302
3302
  }
3303
3303
 
3304
3304
  /**
@@ -3596,7 +3596,7 @@ const Util = {
3596
3596
  gridStepFromTickStep,
3597
3597
  tickStepFromNumTicks,
3598
3598
  constrainedTickStepsFromTickSteps,
3599
- eq: eq$3,
3599
+ eq: eq$4,
3600
3600
  deepEq: deepEq$5,
3601
3601
  parseQueryString,
3602
3602
  updateQueryString: updateQueryString$2,
@@ -10921,7 +10921,7 @@ class WrappedLine extends WrappedDrawing {
10921
10921
  * A collection of geomtry-related utility functions
10922
10922
  */
10923
10923
  const {
10924
- eq: eq$2,
10924
+ eq: eq$3,
10925
10925
  deepEq: deepEq$4
10926
10926
  } = Util;
10927
10927
 
@@ -10931,7 +10931,7 @@ const {
10931
10931
 
10932
10932
  // Given a number, return whether it is positive (1), negative (-1), or zero (0)
10933
10933
  function sign(val) {
10934
- if (eq$2(val, 0)) {
10934
+ if (eq$3(val, 0)) {
10935
10935
  return 0;
10936
10936
  }
10937
10937
  return val > 0 ? 1 : -1;
@@ -10943,7 +10943,7 @@ function ccw(a, b, c) {
10943
10943
  return (b[0] - a[0]) * (c[1] - a[1]) - (c[0] - a[0]) * (b[1] - a[1]);
10944
10944
  }
10945
10945
  function collinear(a, b, c) {
10946
- return eq$2(ccw(a, b, c), 0);
10946
+ return eq$3(ccw(a, b, c), 0);
10947
10947
  }
10948
10948
 
10949
10949
  // Given rect bounding points A and B, whether point C is inside the rect
@@ -11088,7 +11088,7 @@ function similar(coords1, coords2, tolerance) {
11088
11088
  return pair[0] / pair[1];
11089
11089
  });
11090
11090
  const same = ___default["default"].all(factors, function (factor) {
11091
- return eq$2(factors[0], factor);
11091
+ return eq$3(factors[0], factor);
11092
11092
  });
11093
11093
  const congruentEnough = ___default["default"].all(sidePairs, function (pair) {
11094
11094
  return kmath.number.equal(pair[0], pair[1], tolerance);
@@ -11141,7 +11141,7 @@ function rotate(array, n) {
11141
11141
  return array.slice(n).concat(array.slice(0, n));
11142
11142
  }
11143
11143
  function getLineEquation(first, second) {
11144
- if (eq$2(first[0], second[0])) {
11144
+ if (eq$3(first[0], second[0])) {
11145
11145
  return "x = " + first[0].toFixed(3);
11146
11146
  }
11147
11147
  const m = (second[1] - first[1]) / (second[0] - first[0]);
@@ -24914,6 +24914,31 @@ const parseUrl = url => new URL(url, location().origin);
24914
24914
  */
24915
24915
  const toAbsoluteUrl = url => parseUrl(url).href;
24916
24916
 
24917
+ function csProgramValidator(state) {
24918
+ // The iframe can tell us whether it's correct or incorrect,
24919
+ // and pass an optional message
24920
+ if (state.status === "correct") {
24921
+ return {
24922
+ type: "points",
24923
+ earned: 1,
24924
+ total: 1,
24925
+ message: state.message || null
24926
+ };
24927
+ }
24928
+ if (state.status === "incorrect") {
24929
+ return {
24930
+ type: "points",
24931
+ earned: 0,
24932
+ total: 1,
24933
+ message: state.message || null
24934
+ };
24935
+ }
24936
+ return {
24937
+ type: "invalid",
24938
+ message: "Keep going, you're not there yet!"
24939
+ };
24940
+ }
24941
+
24917
24942
  /**
24918
24943
  * This widget is for embedding Khan Academy CS programs.
24919
24944
  */
@@ -24946,29 +24971,8 @@ class CSProgram extends React__namespace.Component {
24946
24971
  };
24947
24972
 
24948
24973
  // The widget's grading function
24949
- static validate(state, rubric) {
24950
- // The iframe can tell us whether it's correct or incorrect,
24951
- // and pass an optional message
24952
- if (state.status === "correct") {
24953
- return {
24954
- type: "points",
24955
- earned: 1,
24956
- total: 1,
24957
- message: state.message || null
24958
- };
24959
- }
24960
- if (state.status === "incorrect") {
24961
- return {
24962
- type: "points",
24963
- earned: 0,
24964
- total: 1,
24965
- message: state.message || null
24966
- };
24967
- }
24968
- return {
24969
- type: "invalid",
24970
- message: "Keep going, you're not there yet!"
24971
- };
24974
+ static validate(state) {
24975
+ return csProgramValidator(state);
24972
24976
  }
24973
24977
  componentDidMount() {
24974
24978
  $__default["default"](window).on("message", this.handleMessageEvent);
@@ -25005,11 +25009,11 @@ class CSProgram extends React__namespace.Component {
25005
25009
  return change.apply(_this, args);
25006
25010
  };
25007
25011
  })();
25008
- simpleValidate(rubric) {
25009
- return CSProgram.validate({
25012
+ simpleValidate() {
25013
+ return csProgramValidator({
25010
25014
  status: this.props.status,
25011
25015
  message: this.props.message
25012
- }, rubric);
25016
+ });
25013
25017
  }
25014
25018
  render() {
25015
25019
  if (!this.props.programID) {
@@ -26612,49 +26616,6 @@ function functionForType(
26612
26616
  type) {
26613
26617
  return functionTypeMapping[type];
26614
26618
  }
26615
- const validate$1 = (state, rubric) => {
26616
- if (state.type !== rubric.correct.type) {
26617
- return {
26618
- type: "points",
26619
- earned: 0,
26620
- total: 1,
26621
- message: null
26622
- };
26623
- }
26624
-
26625
- // We haven't moved the coords
26626
- if (state.coords == null) {
26627
- return {
26628
- type: "invalid",
26629
- message: null
26630
- };
26631
- }
26632
-
26633
- // Get new function handler for grading
26634
- const grader = functionForType(state.type);
26635
- const guessCoeffs = grader.getCoefficients(state.coords, state.asymptote);
26636
- const correctCoeffs = grader.getCoefficients(rubric.correct.coords, rubric.correct.asymptote);
26637
- if (guessCoeffs == null || correctCoeffs == null) {
26638
- return {
26639
- type: "invalid",
26640
- message: null
26641
- };
26642
- }
26643
- if (grader.areEqual(guessCoeffs, correctCoeffs)) {
26644
- return {
26645
- type: "points",
26646
- earned: 1,
26647
- total: 1,
26648
- message: null
26649
- };
26650
- }
26651
- return {
26652
- type: "points",
26653
- earned: 0,
26654
- total: 1,
26655
- message: null
26656
- };
26657
- };
26658
26619
  const getEquationString = props => {
26659
26620
  const plot = props.plot;
26660
26621
  if (plot.type && plot.coords) {
@@ -26772,7 +26733,6 @@ var util = /*#__PURE__*/Object.freeze({
26772
26733
  DEFAULT_BACKGROUND_IMAGE: DEFAULT_BACKGROUND_IMAGE,
26773
26734
  allTypes: allTypes,
26774
26735
  functionForType: functionForType,
26775
- validate: validate$1,
26776
26736
  getEquationString: getEquationString,
26777
26737
  pointsFromNormalized: pointsFromNormalized,
26778
26738
  maybePointsFromNormalized: maybePointsFromNormalized,
@@ -26783,6 +26743,52 @@ var util = /*#__PURE__*/Object.freeze({
26783
26743
  typeToButton: typeToButton
26784
26744
  });
26785
26745
 
26746
+ function grapherValidator(state, rubric) {
26747
+ if (state.type !== rubric.correct.type) {
26748
+ return {
26749
+ type: "points",
26750
+ earned: 0,
26751
+ total: 1,
26752
+ message: null
26753
+ };
26754
+ }
26755
+
26756
+ // We haven't moved the coords
26757
+ if (state.coords == null) {
26758
+ return {
26759
+ type: "invalid",
26760
+ message: null
26761
+ };
26762
+ }
26763
+
26764
+ // Get new function handler for grading
26765
+ const grader = functionForType(state.type);
26766
+ const guessCoeffs = grader.getCoefficients(state.coords, state.asymptote);
26767
+ const correctCoeffs = grader.getCoefficients(rubric.correct.coords,
26768
+ // @ts-expect-error - TS(2339) - Property 'asymptote' does not exist on type '{ type: "absolute_value"; coords: [Coord, Coord]; }'
26769
+ rubric.correct.asymptote);
26770
+ if (guessCoeffs == null || correctCoeffs == null) {
26771
+ return {
26772
+ type: "invalid",
26773
+ message: null
26774
+ };
26775
+ }
26776
+ if (grader.areEqual(guessCoeffs, correctCoeffs)) {
26777
+ return {
26778
+ type: "points",
26779
+ earned: 1,
26780
+ total: 1,
26781
+ message: null
26782
+ };
26783
+ }
26784
+ return {
26785
+ type: "points",
26786
+ earned: 0,
26787
+ total: 1,
26788
+ message: null
26789
+ };
26790
+ }
26791
+
26786
26792
  // @ts-expect-error - TS2339 - Property 'MovablePoint' does not exist on type 'typeof Graphie'.
26787
26793
  const MovablePoint$3 = Graphie.MovablePoint;
26788
26794
  // @ts-expect-error - TS2339 - Property 'MovableLine' does not exist on type 'typeof Graphie'.
@@ -26986,7 +26992,7 @@ class Grapher extends React__namespace.Component {
26986
26992
  vertHairline;
26987
26993
  static defaultProps = DEFAULT_GRAPHER_PROPS;
26988
26994
  static validate(state, rubric) {
26989
- return validate$1(state, rubric);
26995
+ return grapherValidator(state, rubric);
26990
26996
  }
26991
26997
  static getUserInputFromProps(props) {
26992
26998
  return props.plot;
@@ -27091,7 +27097,7 @@ class Grapher extends React__namespace.Component {
27091
27097
  }
27092
27098
  };
27093
27099
  simpleValidate(rubric) {
27094
- return validate$1(this.getUserInput(), rubric);
27100
+ return grapherValidator(this.getUserInput(), rubric);
27095
27101
  }
27096
27102
  getUserInput() {
27097
27103
  return Grapher.getUserInputFromProps(this.props);
@@ -27334,6 +27340,31 @@ var Group$1 = {
27334
27340
  isLintable: true
27335
27341
  };
27336
27342
 
27343
+ function iframeValidator(state) {
27344
+ // The iframe can tell us whether it's correct or incorrect,
27345
+ // and pass an optional message
27346
+ if (state.status === "correct") {
27347
+ return {
27348
+ type: "points",
27349
+ earned: 1,
27350
+ total: 1,
27351
+ message: state.message || null
27352
+ };
27353
+ }
27354
+ if (state.status === "incorrect") {
27355
+ return {
27356
+ type: "points",
27357
+ earned: 0,
27358
+ total: 1,
27359
+ message: state.message || null
27360
+ };
27361
+ }
27362
+ return {
27363
+ type: "invalid",
27364
+ message: "Keep going, you're not there yet!"
27365
+ };
27366
+ }
27367
+
27337
27368
  /**
27338
27369
  * This is an iframe widget. It is used for rendering an iframe that
27339
27370
  * then communicates its state via window.postMessage
@@ -27354,6 +27385,9 @@ class Iframe extends React__namespace.Component {
27354
27385
  allowFullScreen: false,
27355
27386
  allowTopNavigation: false
27356
27387
  };
27388
+ static validate(state) {
27389
+ return iframeValidator(state);
27390
+ }
27357
27391
  componentDidMount() {
27358
27392
  $__default["default"](window).on("message", this.handleMessageEvent);
27359
27393
  }
@@ -27395,10 +27429,9 @@ class Iframe extends React__namespace.Component {
27395
27429
  return change.apply(_this, args);
27396
27430
  };
27397
27431
  })();
27398
- simpleValidate = rubric => {
27399
- // @ts-expect-error - TS2339 - Property 'validate' does not exist on type 'typeof Iframe'.
27400
- return Iframe.validate(this.getUserInput(), rubric);
27401
- };
27432
+ simpleValidate() {
27433
+ return iframeValidator(this.getUserInput());
27434
+ }
27402
27435
  render() {
27403
27436
  const style = {
27404
27437
  width: String(this.props.width),
@@ -27461,36 +27494,6 @@ class Iframe extends React__namespace.Component {
27461
27494
  });
27462
27495
  }
27463
27496
  }
27464
-
27465
- /**
27466
- * This is the widget's grading function
27467
- */
27468
- ___default["default"].extend(Iframe, {
27469
- validate: function (state, rubric) {
27470
- // The iframe can tell us whether it's correct or incorrect,
27471
- // and pass an optional message
27472
- if (state.status === "correct") {
27473
- return {
27474
- type: "points",
27475
- earned: 1,
27476
- total: 1,
27477
- message: state.message || null
27478
- };
27479
- }
27480
- if (state.status === "incorrect") {
27481
- return {
27482
- type: "points",
27483
- earned: 0,
27484
- total: 1,
27485
- message: state.message || null
27486
- };
27487
- }
27488
- return {
27489
- type: "invalid",
27490
- message: "Keep going, you're not there yet!"
27491
- };
27492
- }
27493
- });
27494
27497
  var Iframe$1 = {
27495
27498
  name: "iframe",
27496
27499
  displayName: "Iframe (deprecated)",
@@ -29023,7 +29026,8 @@ const tickLabelSize = 14;
29023
29026
  const YGridTick = _ref => {
29024
29027
  let {
29025
29028
  y,
29026
- range
29029
+ range,
29030
+ tickStep
29027
29031
  } = _ref;
29028
29032
  // If the graph requires out-of-bounds labels, we want to make sure to set the
29029
29033
  // coordinates to the edge of the visible range of the graph. Otherwise,
@@ -29058,9 +29062,10 @@ const YGridTick = _ref => {
29058
29062
 
29059
29063
  // If the graph displays both the y and x axis lines within the graph, we want
29060
29064
  // to hide the label at -1 on the y-axis to prevent overlap with the x-axis label
29061
- const showLabel = shouldShowLabel(y, range);
29065
+ const showLabel = shouldShowLabel(y, range, tickStep);
29062
29066
  return /*#__PURE__*/React__namespace.createElement("g", {
29063
- className: "tick"
29067
+ className: "tick",
29068
+ "aria-hidden": true
29064
29069
  }, /*#__PURE__*/React__namespace.createElement("line", {
29065
29070
  x1: x1,
29066
29071
  y1: y1,
@@ -29120,7 +29125,8 @@ const XGridTick = _ref2 => {
29120
29125
  const xPositionText = xPosition + xAdjustment;
29121
29126
  const yPositionText = yPosition + yAdjustment;
29122
29127
  return /*#__PURE__*/React__namespace.createElement("g", {
29123
- className: "tick"
29128
+ className: "tick",
29129
+ "aria-hidden": true
29124
29130
  }, /*#__PURE__*/React__namespace.createElement("line", {
29125
29131
  x1: x1,
29126
29132
  y1: y1,
@@ -29139,13 +29145,13 @@ const XGridTick = _ref2 => {
29139
29145
  };
29140
29146
 
29141
29147
  // Determines whether to show the label for the given tick
29142
- // Currently, the only condition is to hide the label at -1
29143
- // on the y-axis when the x-axis is within the graph
29144
- const shouldShowLabel = (number, range) => {
29148
+ // Currently, the only condition is to hide the label at -tickStep
29149
+ // on the y-axis when the y-axis is within the graph bounds
29150
+ const shouldShowLabel = (currentTick, range, tickStep) => {
29145
29151
  let showLabel = true;
29146
29152
 
29147
- // If the x-axis is within the graph and the y-axis is at -1, hide the label
29148
- if (range[X][MIN] < -1 && range[X][MAX] > 0 && number === -1) {
29153
+ // If the y-axis is within the graph and currentTick equals -tickStep, hide the label
29154
+ if (range[X][MIN] < -tickStep && range[X][MAX] > 0 && currentTick === -tickStep) {
29149
29155
  showLabel = false;
29150
29156
  }
29151
29157
  return showLabel;
@@ -29187,7 +29193,8 @@ const AxisTicks = () => {
29187
29193
  return /*#__PURE__*/React__namespace.createElement(YGridTick, {
29188
29194
  y: y,
29189
29195
  key: `y-grid-tick-${y}`,
29190
- range: range
29196
+ range: range,
29197
+ tickStep: tickStep[Y]
29191
29198
  });
29192
29199
  })), /*#__PURE__*/React__namespace.createElement("g", {
29193
29200
  className: "x-axis-ticks"
@@ -29942,7 +29949,8 @@ function LockedLabel(props) {
29942
29949
  color: lockedFigureColors[color],
29943
29950
  fontSize: wonderBlocksTokens.font.size[size],
29944
29951
  backgroundColor: "rgba(255, 255, 255, 0.8)"
29945
- }
29952
+ },
29953
+ "aria-hidden": true
29946
29954
  }, /*#__PURE__*/React__namespace.createElement(TeX, null, text));
29947
29955
  }
29948
29956
 
@@ -29966,6 +29974,8 @@ function GraphLockedLabelsLayer(props) {
29966
29974
  flags?.["mafs"]?.["locked-vector-labels"] && figure.type === "vector" ||
29967
29975
  // Ellipse flag + ellipse type
29968
29976
  flags?.["mafs"]?.["locked-ellipse-labels"] && figure.type === "ellipse" ||
29977
+ // Polygon flag + polygon type
29978
+ flags?.["mafs"]?.["locked-polygon-labels"] && figure.type === "polygon" ||
29969
29979
  // Function flag + function type
29970
29980
  flags?.["mafs"]?.["locked-function-labels"] && figure.type === "function") {
29971
29981
  return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, {
@@ -29989,9 +29999,16 @@ const LockedEllipse = props => {
29989
29999
  angle,
29990
30000
  color,
29991
30001
  fillStyle,
29992
- strokeStyle
30002
+ strokeStyle,
30003
+ ariaLabel,
30004
+ flags
29993
30005
  } = props;
29994
- return /*#__PURE__*/React__namespace.createElement(mafs.Ellipse, {
30006
+ const hasAria = ariaLabel && flags?.["mafs"]?.["locked-figures-aria"];
30007
+ return /*#__PURE__*/React__namespace.createElement("g", {
30008
+ className: "locked-ellipse",
30009
+ "aria-label": hasAria ? ariaLabel : undefined,
30010
+ "aria-hidden": !hasAria
30011
+ }, /*#__PURE__*/React__namespace.createElement(mafs.Ellipse, {
29995
30012
  center: center,
29996
30013
  radius: radius,
29997
30014
  angle: angle,
@@ -30007,7 +30024,7 @@ const LockedEllipse = props => {
30007
30024
  fill: fillStyle === "white" ? wonderBlocksTokens.color.white : lockedFigureColors[color]
30008
30025
  }
30009
30026
  }
30010
- });
30027
+ }));
30011
30028
  };
30012
30029
 
30013
30030
  const LockedFunction = props => {
@@ -30023,6 +30040,7 @@ const LockedFunction = props => {
30023
30040
  style: strokeStyle,
30024
30041
  domain
30025
30042
  };
30043
+ const hasAria = props.ariaLabel && props.flags?.["mafs"]?.["locked-figures-aria"];
30026
30044
  React.useEffect(() => {
30027
30045
  // Parsing the equation in a "useEffect" hook saves about 2ms each frame
30028
30046
  // when the learner is interacting with the graph (i.e. moving points).
@@ -30032,7 +30050,9 @@ const LockedFunction = props => {
30032
30050
  return null;
30033
30051
  }
30034
30052
  return /*#__PURE__*/React__namespace.createElement("g", {
30035
- className: "locked-function"
30053
+ className: "locked-function",
30054
+ "aria-label": hasAria ? props.ariaLabel : undefined,
30055
+ "aria-hidden": !hasAria
30036
30056
  }, directionalAxis === "x" && /*#__PURE__*/React__namespace.createElement(mafs.Plot.OfX, _extends({
30037
30057
  y: x => equation.eval({
30038
30058
  x
@@ -30132,9 +30152,12 @@ const LockedLine = props => {
30132
30152
  points,
30133
30153
  showPoint1,
30134
30154
  showPoint2,
30155
+ ariaLabel,
30156
+ flags,
30135
30157
  range
30136
30158
  } = props;
30137
30159
  const [point1, point2] = points;
30160
+ const hasAria = ariaLabel && flags?.["mafs"]?.["locked-figures-aria"];
30138
30161
  let line;
30139
30162
  if (kind === "ray") {
30140
30163
  // Rays extend to the end of the graph in one direction.
@@ -30174,7 +30197,9 @@ const LockedLine = props => {
30174
30197
  }), endArrowHead);
30175
30198
  }
30176
30199
  return /*#__PURE__*/React__namespace.createElement("g", {
30177
- className: kind === "ray" ? "locked-ray" : "locked-line"
30200
+ className: kind === "ray" ? "locked-ray" : "locked-line",
30201
+ "aria-label": hasAria ? ariaLabel : undefined,
30202
+ "aria-hidden": !hasAria
30178
30203
  }, line, showPoint1 && /*#__PURE__*/React__namespace.createElement(mafs.Point, {
30179
30204
  x: point1.coord[X],
30180
30205
  y: point1.coord[Y],
@@ -30200,12 +30225,19 @@ const LockedLine = props => {
30200
30225
 
30201
30226
  const LockedPoint = props => {
30202
30227
  const {
30228
+ flags,
30203
30229
  color,
30204
30230
  coord,
30205
- filled
30231
+ filled,
30232
+ ariaLabel
30206
30233
  } = props;
30207
30234
  const [x, y] = coord;
30208
- return /*#__PURE__*/React__namespace.createElement(mafs.Point, {
30235
+ const hasAria = ariaLabel && flags?.["mafs"]?.["locked-figures-aria"];
30236
+ return /*#__PURE__*/React__namespace.createElement("g", {
30237
+ className: "locked-point",
30238
+ "aria-label": hasAria ? ariaLabel : undefined,
30239
+ "aria-hidden": !hasAria
30240
+ }, /*#__PURE__*/React__namespace.createElement(mafs.Point, {
30209
30241
  x: x,
30210
30242
  y: y,
30211
30243
  svgCircleProps: {
@@ -30215,7 +30247,7 @@ const LockedPoint = props => {
30215
30247
  strokeWidth: wonderBlocksTokens.spacing.xxxxSmall_2
30216
30248
  }
30217
30249
  }
30218
- });
30250
+ }));
30219
30251
  };
30220
30252
 
30221
30253
  const LockedPolygon = props => {
@@ -30226,8 +30258,11 @@ const LockedPolygon = props => {
30226
30258
  fillStyle,
30227
30259
  strokeStyle
30228
30260
  } = props;
30261
+ const hasAria = props.ariaLabel && props.flags?.["mafs"]?.["locked-figures-aria"];
30229
30262
  return /*#__PURE__*/React__namespace.createElement("g", {
30230
- className: "locked-polygon"
30263
+ className: "locked-polygon",
30264
+ "aria-label": hasAria ? props.ariaLabel : undefined,
30265
+ "aria-hidden": !hasAria
30231
30266
  }, /*#__PURE__*/React__namespace.createElement(mafs.Polygon, {
30232
30267
  points: [...points],
30233
30268
  fillOpacity: lockedFigureFillStyles[fillStyle],
@@ -30253,11 +30288,16 @@ const LockedPolygon = props => {
30253
30288
  const LockedVector = props => {
30254
30289
  const {
30255
30290
  color,
30256
- points
30291
+ points,
30292
+ ariaLabel,
30293
+ flags
30257
30294
  } = props;
30258
30295
  const [tail, tip] = points;
30296
+ const hasAria = ariaLabel && flags?.["mafs"]?.["locked-figures-aria"];
30259
30297
  return /*#__PURE__*/React__namespace.createElement("g", {
30260
- className: "locked-vector"
30298
+ className: "locked-vector",
30299
+ "aria-label": hasAria ? ariaLabel : undefined,
30300
+ "aria-hidden": !hasAria
30261
30301
  }, /*#__PURE__*/React__namespace.createElement(Vector, {
30262
30302
  tail: tail,
30263
30303
  tip: tip,
@@ -30267,6 +30307,7 @@ const LockedVector = props => {
30267
30307
 
30268
30308
  const GraphLockedLayer = props => {
30269
30309
  const {
30310
+ flags,
30270
30311
  lockedFigures
30271
30312
  } = props;
30272
30313
  return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, lockedFigures.map((figure, index) => {
@@ -30274,28 +30315,40 @@ const GraphLockedLayer = props => {
30274
30315
  case "point":
30275
30316
  return /*#__PURE__*/React__namespace.createElement(LockedPoint, _extends({
30276
30317
  key: `point-${index}`
30277
- }, figure));
30318
+ }, figure, {
30319
+ flags: flags
30320
+ }));
30278
30321
  case "line":
30279
30322
  return /*#__PURE__*/React__namespace.createElement(LockedLine, _extends({
30280
30323
  key: `line-${index}`,
30281
30324
  range: props.range
30282
- }, figure));
30325
+ }, figure, {
30326
+ flags: flags
30327
+ }));
30283
30328
  case "vector":
30284
30329
  return /*#__PURE__*/React__namespace.createElement(LockedVector, _extends({
30285
30330
  key: `vector-${index}`
30286
- }, figure));
30331
+ }, figure, {
30332
+ flags: flags
30333
+ }));
30287
30334
  case "ellipse":
30288
30335
  return /*#__PURE__*/React__namespace.createElement(LockedEllipse, _extends({
30289
30336
  key: `ellipse-${index}`
30290
- }, figure));
30337
+ }, figure, {
30338
+ flags: flags
30339
+ }));
30291
30340
  case "polygon":
30292
30341
  return /*#__PURE__*/React__namespace.createElement(LockedPolygon, _extends({
30293
30342
  key: `polygon-${index}`
30294
- }, figure));
30343
+ }, figure, {
30344
+ flags: flags
30345
+ }));
30295
30346
  case "function":
30296
30347
  return /*#__PURE__*/React__namespace.createElement(LockedFunction, _extends({
30297
30348
  key: `function-${index}`
30298
- }, figure));
30349
+ }, figure, {
30350
+ flags: flags
30351
+ }));
30299
30352
  case "label":
30300
30353
  // This is rendered outside the SVG element, since
30301
30354
  // TeX cannot be rendered inside an SVG.
@@ -31782,7 +31835,7 @@ function QuadraticGraph(props) {
31782
31835
  // to content creators the currently selected "correct answer". ex: y = 0.200x^2 + 0.000x + 0.000
31783
31836
  // While we should technically never have invalid coordinates, we want to ensure that we have a fallback
31784
31837
  const coeffRef = React__namespace.useRef([0, 0, 0]);
31785
- const coeffs = getQuadraticCoefficients(coords);
31838
+ const coeffs = getQuadraticCoefficients$1(coords);
31786
31839
  if (coeffs !== undefined) {
31787
31840
  coeffRef.current = coeffs;
31788
31841
  }
@@ -31806,7 +31859,7 @@ function QuadraticGraph(props) {
31806
31859
  // These equations were originally set up in 2013 and may require some
31807
31860
  // additional comments to help clarify the quadratic formula manipulations
31808
31861
  // Origin: https://phabricator.khanacademy.org/D2413
31809
- const getQuadraticCoefficients = coords => {
31862
+ const getQuadraticCoefficients$1 = coords => {
31810
31863
  const p1 = coords[0];
31811
31864
  const p2 = coords[1];
31812
31865
  const p3 = coords[2];
@@ -31846,7 +31899,7 @@ function SinusoidGraph(props) {
31846
31899
  phase: 1,
31847
31900
  verticalOffset: 0
31848
31901
  });
31849
- const coeffs = getSinusoidCoefficients(coords);
31902
+ const coeffs = getSinusoidCoefficients$1(coords);
31850
31903
 
31851
31904
  // If the coefficients are valid, update the reference
31852
31905
  if (coeffs !== undefined) {
@@ -31875,7 +31928,7 @@ sinusoidCoefficients) {
31875
31928
  } = sinusoidCoefficients;
31876
31929
  return a * Math.sin(b * x - c) + d;
31877
31930
  };
31878
- const getSinusoidCoefficients = coords => {
31931
+ const getSinusoidCoefficients$1 = coords => {
31879
31932
  // It's assumed that p1 is the root and p2 is the first peak
31880
31933
  const p1 = coords[0];
31881
31934
  const p2 = coords[1];
@@ -32334,6 +32387,7 @@ const MafsGraph = props => {
32334
32387
  })),
32335
32388
  // Only render the axis ticks and arrows if the markings are set to a full "graph"
32336
32389
  props.markings === "graph" && /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(AxisTicks, null), /*#__PURE__*/React__namespace.createElement(AxisArrows, null)), /*#__PURE__*/React__namespace.createElement("svg", nestedSVGAttributes, props.lockedFigures && /*#__PURE__*/React__namespace.createElement(GraphLockedLayer, {
32390
+ flags: props.flags,
32337
32391
  lockedFigures: props.lockedFigures,
32338
32392
  range: state.range
32339
32393
  }))), props.flags?.["mafs"]?.["interactive-graph-locked-features-labels"] && props.lockedFigures && /*#__PURE__*/React__namespace.createElement(GraphLockedLabelsLayer, {
@@ -32589,6 +32643,95 @@ const renderGraph = props => {
32589
32643
  }
32590
32644
  };
32591
32645
 
32646
+ // Converts the state of a StatefulMafsGraph back to the format used to
32647
+ // represent graph state in the widget JSON.
32648
+ //
32649
+ // Rather than be tightly bound to how data was structured in
32650
+ // the legacy interactive graph, this lets us store state
32651
+ // however we want and we just transform it before handing it off
32652
+ // the the parent InteractiveGraph.
32653
+ //
32654
+ // The transformed data is used in the interactive graph widget editor, to
32655
+ // set the `correct` field of the graph options. In the learner-facing UI, the
32656
+ // data is also stored by the Renderer, and passed back down to the graph
32657
+ // widget via the `graph` prop. Because the data returned by this function
32658
+ // completely replaces the Renderer's representation of the widget, rather than
32659
+ // being merged into it, we take it upon ourselves to merge in the original
32660
+ // widget data here. If we didn't do this merging, the graph's configuration
32661
+ // would reset to the defaults when the learner interacted with it.
32662
+ function mafsStateToInteractiveGraph(state, originalGraph) {
32663
+ switch (state.type) {
32664
+ case "angle":
32665
+ invariant(originalGraph.type === "angle");
32666
+ return {
32667
+ ...originalGraph,
32668
+ coords: state.coords
32669
+ };
32670
+ case "quadratic":
32671
+ invariant(originalGraph.type === "quadratic");
32672
+ return {
32673
+ ...originalGraph,
32674
+ coords: state.coords
32675
+ };
32676
+ case "circle":
32677
+ invariant(originalGraph.type === "circle");
32678
+ return {
32679
+ ...originalGraph,
32680
+ center: state.center,
32681
+ radius: getRadius(state)
32682
+ };
32683
+ case "linear":
32684
+ invariant(originalGraph.type === "linear");
32685
+ return {
32686
+ ...originalGraph,
32687
+ coords: state.coords
32688
+ };
32689
+ case "ray":
32690
+ invariant(originalGraph.type === "ray");
32691
+ return {
32692
+ ...originalGraph,
32693
+ coords: state.coords
32694
+ };
32695
+ case "sinusoid":
32696
+ invariant(originalGraph.type === "sinusoid");
32697
+ return {
32698
+ ...originalGraph,
32699
+ coords: state.coords
32700
+ };
32701
+ case "segment":
32702
+ invariant(originalGraph.type === "segment");
32703
+ return {
32704
+ ...originalGraph,
32705
+ coords: state.coords
32706
+ };
32707
+ case "linear-system":
32708
+ invariant(originalGraph.type === "linear-system");
32709
+ return {
32710
+ ...originalGraph,
32711
+ coords: state.coords
32712
+ };
32713
+ case "polygon":
32714
+ invariant(originalGraph.type === "polygon");
32715
+ return {
32716
+ ...originalGraph,
32717
+ coords: state.coords
32718
+ };
32719
+ case "point":
32720
+ invariant(originalGraph.type === "point");
32721
+ return {
32722
+ ...originalGraph,
32723
+ coords: state.coords
32724
+ };
32725
+ case "none":
32726
+ invariant(originalGraph.type === "none");
32727
+ return {
32728
+ ...originalGraph
32729
+ };
32730
+ default:
32731
+ throw new wonderStuffCore.UnreachableCaseError(state);
32732
+ }
32733
+ }
32734
+
32592
32735
  function initializeGraphState(params) {
32593
32736
  const {
32594
32737
  graph,
@@ -33237,7 +33380,7 @@ function doMovePoint(state, action) {
33237
33380
  // Bind the new destination to the graph range/snapStep and then get the quadratic coefficients
33238
33381
  const boundDestination = boundAndSnapToGrid(action.destination, state);
33239
33382
  newCoords[action.index] = boundDestination;
33240
- const QuadraticCoefficients = getQuadraticCoefficients(newCoords);
33383
+ const QuadraticCoefficients = getQuadraticCoefficients$1(newCoords);
33241
33384
 
33242
33385
  // If the new destination results in an invalid quadratic equation, we don't want to move the point
33243
33386
  if (QuadraticCoefficients === undefined) {
@@ -33393,11 +33536,11 @@ const getChange = (coords, delta, constraintOpts) => {
33393
33536
  const [dx, dy] = getDeltaVertex(maxMoves, minMoves, delta);
33394
33537
  return [dx, dy];
33395
33538
  };
33396
- const eq$1 = Util.eq;
33539
+ const eq$2 = Util.eq;
33397
33540
 
33398
33541
  // Less than or approximately equal
33399
33542
  function leq$1(a, b) {
33400
- return a < b || eq$1(a, b);
33543
+ return a < b || eq$2(a, b);
33401
33544
  }
33402
33545
  function boundAndSnapToGrid(point, _ref) {
33403
33546
  let {
@@ -33709,24 +33852,6 @@ function setAtIndex(args) {
33709
33852
  return copy;
33710
33853
  }
33711
33854
 
33712
- // Rather than be tightly bound to how data was structured in
33713
- // the legacy interactive graph, this lets us store state
33714
- // however we want and we just transform it before handing it off
33715
- // the the parent InteractiveGraph
33716
- function mafsStateToInteractiveGraph(state) {
33717
- if (state.graph.type === "circle") {
33718
- return {
33719
- ...state,
33720
- graph: {
33721
- ...state.graph,
33722
- radius: getRadius(state.graph)
33723
- }
33724
- };
33725
- }
33726
- return {
33727
- ...state
33728
- };
33729
- }
33730
33855
  const StatefulMafsGraph = /*#__PURE__*/React__namespace.forwardRef((props, ref) => {
33731
33856
  const {
33732
33857
  onChange,
@@ -33739,12 +33864,12 @@ const StatefulMafsGraph = /*#__PURE__*/React__namespace.forwardRef((props, ref)
33739
33864
  const prevState = React.useRef(state);
33740
33865
  React.useEffect(() => {
33741
33866
  if (prevState.current !== state) {
33742
- onChange(mafsStateToInteractiveGraph({
33743
- graph: state
33744
- }));
33867
+ onChange({
33868
+ graph: mafsStateToInteractiveGraph(state, graph)
33869
+ });
33745
33870
  }
33746
33871
  prevState.current = state;
33747
- }, [onChange, state]);
33872
+ }, [onChange, state, graph]);
33748
33873
 
33749
33874
  // Destructuring first to keep useEffect from making excess calls
33750
33875
  const [xSnap, ySnap] = props.snapStep;
@@ -33797,12 +33922,232 @@ const StatefulMafsGraph = /*#__PURE__*/React__namespace.forwardRef((props, ref)
33797
33922
  }));
33798
33923
  });
33799
33924
 
33925
+ const eq$1 = Util.eq;
33926
+ const deepEq$1 = Util.deepEq;
33927
+ function interactiveGraphValidator(userInput, rubric) {
33928
+ // None-type graphs are not graded
33929
+ if (userInput.type === "none" && rubric.correct.type === "none") {
33930
+ return {
33931
+ type: "points",
33932
+ earned: 0,
33933
+ total: 0,
33934
+ message: null
33935
+ };
33936
+ }
33937
+
33938
+ // When nothing has moved, there will neither be coords nor the
33939
+ // circle's center/radius fields. When those fields are absent, skip
33940
+ // all these checks; just go mark the answer as empty.
33941
+ const hasValue = Boolean(
33942
+ // @ts-expect-error - TS2339 - Property 'coords' does not exist on type 'PerseusGraphType'.
33943
+ userInput.coords ||
33944
+ // @ts-expect-error - TS2339 - Property 'center' does not exist on type 'PerseusGraphType'. | TS2339 - Property 'radius' does not exist on type 'PerseusGraphType'.
33945
+ userInput.center && userInput.radius);
33946
+ if (userInput.type === rubric.correct.type && hasValue) {
33947
+ if (userInput.type === "linear" && rubric.correct.type === "linear" && userInput.coords != null) {
33948
+ const guess = userInput.coords;
33949
+ const correct = rubric.correct.coords;
33950
+
33951
+ // If both of the guess points are on the correct line, it's
33952
+ // correct.
33953
+ if (
33954
+ // @ts-expect-error - TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'.
33955
+ collinear(correct[0], correct[1], guess[0]) &&
33956
+ // @ts-expect-error - TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'.
33957
+ collinear(correct[0], correct[1], guess[1])) {
33958
+ return {
33959
+ type: "points",
33960
+ earned: 1,
33961
+ total: 1,
33962
+ message: null
33963
+ };
33964
+ }
33965
+ } else if (userInput.type === "linear-system" && rubric.correct.type === "linear-system" && userInput.coords != null) {
33966
+ const guess = userInput.coords;
33967
+ const correct = rubric.correct.coords;
33968
+ if (collinear(correct[0][0], correct[0][1], guess[0][0]) && collinear(correct[0][0], correct[0][1], guess[0][1]) && collinear(correct[1][0], correct[1][1], guess[1][0]) && collinear(correct[1][0], correct[1][1], guess[1][1]) || collinear(correct[0][0], correct[0][1], guess[1][0]) && collinear(correct[0][0], correct[0][1], guess[1][1]) && collinear(correct[1][0], correct[1][1], guess[0][0]) && collinear(correct[1][0], correct[1][1], guess[0][1])) {
33969
+ return {
33970
+ type: "points",
33971
+ earned: 1,
33972
+ total: 1,
33973
+ message: null
33974
+ };
33975
+ }
33976
+ } else if (userInput.type === "quadratic" && rubric.correct.type === "quadratic" && userInput.coords != null) {
33977
+ // If the parabola coefficients match, it's correct.
33978
+ const guessCoeffs = getQuadraticCoefficients(userInput.coords);
33979
+ const correctCoeffs = getQuadraticCoefficients(
33980
+ // @ts-expect-error - TS2345 - Argument of type 'readonly Coord[] | undefined' is not assignable to parameter of type 'readonly Coord[]'.
33981
+ rubric.correct.coords);
33982
+ if (deepEq$1(guessCoeffs, correctCoeffs)) {
33983
+ return {
33984
+ type: "points",
33985
+ earned: 1,
33986
+ total: 1,
33987
+ message: null
33988
+ };
33989
+ }
33990
+ } else if (userInput.type === "sinusoid" && rubric.correct.type === "sinusoid" && userInput.coords != null) {
33991
+ const guessCoeffs = getSinusoidCoefficients(userInput.coords);
33992
+ const correctCoeffs = getSinusoidCoefficients(
33993
+ // @ts-expect-error - TS2345 - Argument of type 'readonly Coord[] | undefined' is not assignable to parameter of type 'readonly Coord[]'.
33994
+ rubric.correct.coords);
33995
+ const canonicalGuessCoeffs = canonicalSineCoefficients$1(guessCoeffs);
33996
+ const canonicalCorrectCoeffs = canonicalSineCoefficients$1(correctCoeffs);
33997
+ // If the canonical coefficients match, it's correct.
33998
+ if (deepEq$1(canonicalGuessCoeffs, canonicalCorrectCoeffs)) {
33999
+ return {
34000
+ type: "points",
34001
+ earned: 1,
34002
+ total: 1,
34003
+ message: null
34004
+ };
34005
+ }
34006
+ } else if (userInput.type === "circle" && rubric.correct.type === "circle") {
34007
+ if (deepEq$1(userInput.center, rubric.correct.center) && eq$1(userInput.radius, rubric.correct.radius)) {
34008
+ return {
34009
+ type: "points",
34010
+ earned: 1,
34011
+ total: 1,
34012
+ message: null
34013
+ };
34014
+ }
34015
+ } else if (userInput.type === "point" && rubric.correct.type === "point" && userInput.coords != null) {
34016
+ let correct = rubric.correct.coords;
34017
+ if (correct == null) {
34018
+ throw new Error("Point graph rubric has null coords");
34019
+ }
34020
+ const guess = userInput.coords.slice();
34021
+ correct = correct.slice();
34022
+ // Everything's already rounded so we shouldn't need to do an
34023
+ // eq() comparison but _.isEqual(0, -0) is false, so we'll use
34024
+ // eq() anyway. The sort should be fine because it'll stringify
34025
+ // it and -0 converted to a string is "0"
34026
+ guess?.sort();
34027
+ // @ts-expect-error - TS2339 - Property 'sort' does not exist on type 'readonly Coord[]'.
34028
+ correct.sort();
34029
+ if (deepEq$1(guess, correct)) {
34030
+ return {
34031
+ type: "points",
34032
+ earned: 1,
34033
+ total: 1,
34034
+ message: null
34035
+ };
34036
+ }
34037
+ } else if (userInput.type === "polygon" && rubric.correct.type === "polygon" && userInput.coords != null) {
34038
+ const guess = userInput.coords?.slice();
34039
+ // @ts-expect-error - TS2322 - Type 'Coord[] | undefined' is not assignable to type 'Coord[]'.
34040
+ const correct = rubric.correct.coords?.slice();
34041
+ let match;
34042
+ if (rubric.correct.match === "similar") {
34043
+ match = similar(guess, correct, Number.POSITIVE_INFINITY);
34044
+ } else if (rubric.correct.match === "congruent") {
34045
+ match = similar(guess, correct, kmath.number.DEFAULT_TOLERANCE);
34046
+ } else if (rubric.correct.match === "approx") {
34047
+ match = similar(guess, correct, 0.1);
34048
+ } else {
34049
+ /* exact */
34050
+ guess.sort();
34051
+ correct.sort();
34052
+ match = deepEq$1(guess, correct);
34053
+ }
34054
+ if (match) {
34055
+ return {
34056
+ type: "points",
34057
+ earned: 1,
34058
+ total: 1,
34059
+ message: null
34060
+ };
34061
+ }
34062
+ } else if (userInput.type === "segment" && rubric.correct.type === "segment" && userInput.coords != null) {
34063
+ let guess = Util.deepClone(userInput.coords);
34064
+ let correct = Util.deepClone(rubric.correct?.coords);
34065
+ guess = ___default["default"].invoke(guess, "sort").sort();
34066
+ // @ts-expect-error - TS2345 - Argument of type '(readonly Coord[])[] | undefined' is not assignable to parameter of type 'Collection<any>'.
34067
+ correct = ___default["default"].invoke(correct, "sort").sort();
34068
+ if (deepEq$1(guess, correct)) {
34069
+ return {
34070
+ type: "points",
34071
+ earned: 1,
34072
+ total: 1,
34073
+ message: null
34074
+ };
34075
+ }
34076
+ } else if (userInput.type === "ray" && rubric.correct.type === "ray" && userInput.coords != null) {
34077
+ const guess = userInput.coords;
34078
+ const correct = rubric.correct.coords;
34079
+ if (
34080
+ // @ts-expect-error - TS2532 - Object is possibly 'undefined'.
34081
+ deepEq$1(guess[0], correct[0]) &&
34082
+ // @ts-expect-error - TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'.
34083
+ collinear(correct[0], correct[1], guess[1])) {
34084
+ return {
34085
+ type: "points",
34086
+ earned: 1,
34087
+ total: 1,
34088
+ message: null
34089
+ };
34090
+ }
34091
+ } else if (userInput.type === "angle" && rubric.correct.type === "angle") {
34092
+ const guess = userInput.coords;
34093
+ const correct = rubric.correct.coords;
34094
+ let match;
34095
+ if (rubric.correct.match === "congruent") {
34096
+ const angles = ___default["default"].map([guess, correct], function (coords) {
34097
+ const angle = GraphUtils.findAngle(
34098
+ // @ts-expect-error - TS2532 - Object is possibly 'undefined'.
34099
+ coords[2],
34100
+ // @ts-expect-error - TS2532 - Object is possibly 'undefined'.
34101
+ coords[0],
34102
+ // @ts-expect-error - TS2532 - Object is possibly 'undefined'.
34103
+ coords[1]);
34104
+ return (angle + 360) % 360;
34105
+ });
34106
+ // @ts-expect-error - TS2556 - A spread argument must either have a tuple type or be passed to a rest parameter.
34107
+ match = eq$1(...angles);
34108
+ } else {
34109
+ /* exact */
34110
+ match =
34111
+ // @ts-expect-error - TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'.
34112
+ deepEq$1(guess[1], correct[1]) &&
34113
+ // @ts-expect-error - TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'.
34114
+ collinear(correct[1], correct[0], guess[0]) &&
34115
+ // @ts-expect-error - TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'.
34116
+ collinear(correct[1], correct[2], guess[2]);
34117
+ }
34118
+ if (match) {
34119
+ return {
34120
+ type: "points",
34121
+ earned: 1,
34122
+ total: 1,
34123
+ message: null
34124
+ };
34125
+ }
34126
+ }
34127
+ }
34128
+
34129
+ // The input wasn't correct, so check if it's a blank input or if it's
34130
+ // actually just wrong
34131
+ if (!hasValue || ___default["default"].isEqual(userInput, rubric.graph)) {
34132
+ // We're where we started.
34133
+ return {
34134
+ type: "invalid",
34135
+ message: null
34136
+ };
34137
+ }
34138
+ return {
34139
+ type: "points",
34140
+ earned: 0,
34141
+ total: 1,
34142
+ message: null
34143
+ };
34144
+ }
34145
+
33800
34146
  const TRASH_ICON_URI = "https://ka-perseus-graphie.s3.amazonaws.com/b1452c0d79fd0f7ff4c3af9488474a0a0decb361.png";
33801
34147
  const defaultBackgroundImage = {
33802
34148
  url: null
33803
34149
  };
33804
34150
  const eq = Util.eq;
33805
- const deepEq$1 = Util.deepEq;
33806
34151
  const UNLIMITED = "unlimited";
33807
34152
 
33808
34153
  // Sample background image:
@@ -33843,6 +34188,39 @@ const makeInvalidTypeError = (functionName, graphType) => {
33843
34188
 
33844
34189
  // There's no transform function in exports
33845
34190
 
34191
+ // TODO: there's another, very similar getSinusoidCoefficients function
34192
+ // they should probably be merged
34193
+ function getSinusoidCoefficients(coords) {
34194
+ // It's assumed that p1 is the root and p2 is the first peak
34195
+ const p1 = coords[0];
34196
+ const p2 = coords[1];
34197
+
34198
+ // Resulting coefficients are canonical for this sine curve
34199
+ const amplitude = p2[1] - p1[1];
34200
+ const angularFrequency = Math.PI / (2 * (p2[0] - p1[0]));
34201
+ const phase = p1[0] * angularFrequency;
34202
+ const verticalOffset = p1[1];
34203
+ return [amplitude, angularFrequency, phase, verticalOffset];
34204
+ }
34205
+
34206
+ // TODO: there's another, very similar getQuadraticCoefficients function
34207
+ // they should probably be merged
34208
+ function getQuadraticCoefficients(coords) {
34209
+ const p1 = coords[0];
34210
+ const p2 = coords[1];
34211
+ const p3 = coords[2];
34212
+ const denom = (p1[0] - p2[0]) * (p1[0] - p3[0]) * (p2[0] - p3[0]);
34213
+ if (denom === 0) {
34214
+ // Many of the callers assume that the return value is always defined.
34215
+ // @ts-expect-error - TS2322 - Type 'undefined' is not assignable to type 'QuadraticCoefficient'.
34216
+ return;
34217
+ }
34218
+ const a = (p3[0] * (p2[1] - p1[1]) + p2[0] * (p1[1] - p3[1]) + p1[0] * (p3[1] - p2[1])) / denom;
34219
+ const b = (p3[0] * p3[0] * (p1[1] - p2[1]) + p2[0] * p2[0] * (p3[1] - p1[1]) + p1[0] * p1[0] * (p2[1] - p3[1])) / denom;
34220
+ const c = (p2[0] * p3[0] * (p2[0] - p3[0]) * p1[1] + p3[0] * p1[0] * (p3[0] - p1[0]) * p2[1] + p1[0] * p2[0] * (p1[0] - p2[0]) * p3[1]) / denom;
34221
+ return [a, b, c];
34222
+ }
34223
+
33846
34224
  // (LEMS-2190): Move the Mafs Angle Graph coordinate reversal logic in interactive-graph-state.ts
33847
34225
  // to this file when we remove the legacy graph. This logic allows us to support bi-directional angles
33848
34226
  // for the new (non-reflexive) Mafs graphs, while maintaining the same scoring behaviour as the legacy graph.
@@ -34963,7 +35341,6 @@ class LegacyInteractiveGraph extends React__namespace.Component {
34963
35341
  });
34964
35342
  $__default["default"](this.angle).on("move", () => {
34965
35343
  this.onChange({
34966
- // @ts-expect-error Type '{ coords: any; type: "angle"; showAngles?: boolean | undefined; allowReflexAngles?: boolean | undefined; angleOffsetDeg?: number | undefined; snapDegrees?: number | undefined; match?: "congruent" | undefined; }' is not assignable to type 'InteractiveGraphState | undefined'.
34967
35344
  graph: {
34968
35345
  ...graph,
34969
35346
  coords: this.angle?.getClockwiseCoords()
@@ -34990,7 +35367,7 @@ class LegacyInteractiveGraph extends React__namespace.Component {
34990
35367
  return InteractiveGraph.getUserInputFromProps(this.props);
34991
35368
  }
34992
35369
  simpleValidate(rubric) {
34993
- return InteractiveGraph.validate(this.getUserInput(), rubric, this);
35370
+ return interactiveGraphValidator(this.getUserInput(), rubric);
34994
35371
  }
34995
35372
  focus = $__default["default"].noop;
34996
35373
  render() {
@@ -35065,7 +35442,7 @@ class InteractiveGraph extends React__namespace.Component {
35065
35442
  throw new perseusCore.PerseusError("Cannot getUserInput from a graph that has never rendered", perseusCore.Errors.NotAllowed);
35066
35443
  }
35067
35444
  simpleValidate(rubric) {
35068
- return InteractiveGraph.validate(this.getUserInput(), rubric, this);
35445
+ return interactiveGraphValidator(this.getUserInput(), rubric);
35069
35446
  }
35070
35447
  render() {
35071
35448
  // Mafs shim
@@ -35088,33 +35465,6 @@ class InteractiveGraph extends React__namespace.Component {
35088
35465
  ref: this.legacyGraphRef
35089
35466
  }, this.props));
35090
35467
  }
35091
- static getQuadraticCoefficients(coords) {
35092
- const p1 = coords[0];
35093
- const p2 = coords[1];
35094
- const p3 = coords[2];
35095
- const denom = (p1[0] - p2[0]) * (p1[0] - p3[0]) * (p2[0] - p3[0]);
35096
- if (denom === 0) {
35097
- // Many of the callers assume that the return value is always defined.
35098
- // @ts-expect-error - TS2322 - Type 'undefined' is not assignable to type 'QuadraticCoefficient'.
35099
- return;
35100
- }
35101
- const a = (p3[0] * (p2[1] - p1[1]) + p2[0] * (p1[1] - p3[1]) + p1[0] * (p3[1] - p2[1])) / denom;
35102
- const b = (p3[0] * p3[0] * (p1[1] - p2[1]) + p2[0] * p2[0] * (p3[1] - p1[1]) + p1[0] * p1[0] * (p2[1] - p3[1])) / denom;
35103
- const c = (p2[0] * p3[0] * (p2[0] - p3[0]) * p1[1] + p3[0] * p1[0] * (p3[0] - p1[0]) * p2[1] + p1[0] * p2[0] * (p1[0] - p2[0]) * p3[1]) / denom;
35104
- return [a, b, c];
35105
- }
35106
- static getSinusoidCoefficients(coords) {
35107
- // It's assumed that p1 is the root and p2 is the first peak
35108
- const p1 = coords[0];
35109
- const p2 = coords[1];
35110
-
35111
- // Resulting coefficients are canonical for this sine curve
35112
- const amplitude = p2[1] - p1[1];
35113
- const angularFrequency = Math.PI / (2 * (p2[0] - p1[0]));
35114
- const phase = p1[0] * angularFrequency;
35115
- const verticalOffset = p1[1];
35116
- return [amplitude, angularFrequency, phase, verticalOffset];
35117
- }
35118
35468
 
35119
35469
  /**
35120
35470
  * @param {object} graph Like props.graph or props.correct
@@ -35328,7 +35678,7 @@ class InteractiveGraph extends React__namespace.Component {
35328
35678
  const coords =
35329
35679
  // @ts-expect-error - TS2339 - Property 'coords' does not exist on type 'PerseusGraphType'.
35330
35680
  props.graph.coords || InteractiveGraph.defaultQuadraticCoords(props);
35331
- return InteractiveGraph.getQuadraticCoefficients(coords);
35681
+ return getQuadraticCoefficients(coords);
35332
35682
  }
35333
35683
  static defaultQuadraticCoords(props) {
35334
35684
  const coords = [[0.25, 0.75], [0.5, 0.25], [0.75, 0.75]];
@@ -35343,7 +35693,7 @@ class InteractiveGraph extends React__namespace.Component {
35343
35693
  const coords =
35344
35694
  // @ts-expect-error - TS2339 - Property 'coords' does not exist on type 'PerseusGraphType'.
35345
35695
  props.graph.coords || InteractiveGraph.defaultSinusoidCoords(props);
35346
- return InteractiveGraph.getSinusoidCoefficients(coords);
35696
+ return getSinusoidCoefficients(coords);
35347
35697
  }
35348
35698
  static defaultSinusoidCoords(props) {
35349
35699
  const coords = [[0.5, 0.5], [0.65, 0.6]];
@@ -35423,220 +35773,8 @@ class InteractiveGraph extends React__namespace.Component {
35423
35773
  const angle = GraphUtils.findAngle(coords[2], coords[0], coords[1]);
35424
35774
  return angle.toFixed(0) + "\u00B0 angle" + " at (" + coords[1].join(", ") + ")";
35425
35775
  }
35426
- static validate(userInput, rubric, component) {
35427
- // None-type graphs are not graded
35428
- if (userInput.type === "none" && rubric.correct.type === "none") {
35429
- return {
35430
- type: "points",
35431
- earned: 0,
35432
- total: 0,
35433
- message: null
35434
- };
35435
- }
35436
-
35437
- // When nothing has moved, there will neither be coords nor the
35438
- // circle's center/radius fields. When those fields are absent, skip
35439
- // all these checks; just go mark the answer as empty.
35440
- const hasValue = Boolean(
35441
- // @ts-expect-error - TS2339 - Property 'coords' does not exist on type 'PerseusGraphType'.
35442
- userInput.coords ||
35443
- // @ts-expect-error - TS2339 - Property 'center' does not exist on type 'PerseusGraphType'. | TS2339 - Property 'radius' does not exist on type 'PerseusGraphType'.
35444
- userInput.center && userInput.radius);
35445
- if (userInput.type === rubric.correct.type && hasValue) {
35446
- if (userInput.type === "linear" && rubric.correct.type === "linear" && userInput.coords != null) {
35447
- const guess = userInput.coords;
35448
- const correct = rubric.correct.coords;
35449
-
35450
- // If both of the guess points are on the correct line, it's
35451
- // correct.
35452
- if (
35453
- // @ts-expect-error - TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'.
35454
- collinear(correct[0], correct[1], guess[0]) &&
35455
- // @ts-expect-error - TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'.
35456
- collinear(correct[0], correct[1], guess[1])) {
35457
- return {
35458
- type: "points",
35459
- earned: 1,
35460
- total: 1,
35461
- message: null
35462
- };
35463
- }
35464
- } else if (userInput.type === "linear-system" && rubric.correct.type === "linear-system" && userInput.coords != null) {
35465
- const guess = userInput.coords;
35466
- const correct = rubric.correct.coords;
35467
- if (collinear(correct[0][0], correct[0][1], guess[0][0]) && collinear(correct[0][0], correct[0][1], guess[0][1]) && collinear(correct[1][0], correct[1][1], guess[1][0]) && collinear(correct[1][0], correct[1][1], guess[1][1]) || collinear(correct[0][0], correct[0][1], guess[1][0]) && collinear(correct[0][0], correct[0][1], guess[1][1]) && collinear(correct[1][0], correct[1][1], guess[0][0]) && collinear(correct[1][0], correct[1][1], guess[0][1])) {
35468
- return {
35469
- type: "points",
35470
- earned: 1,
35471
- total: 1,
35472
- message: null
35473
- };
35474
- }
35475
- } else if (userInput.type === "quadratic" && rubric.correct.type === "quadratic" && userInput.coords != null) {
35476
- // If the parabola coefficients match, it's correct.
35477
- const guessCoeffs = this.getQuadraticCoefficients(userInput.coords);
35478
- const correctCoeffs = this.getQuadraticCoefficients(
35479
- // @ts-expect-error - TS2345 - Argument of type 'readonly Coord[] | undefined' is not assignable to parameter of type 'readonly Coord[]'.
35480
- rubric.correct.coords);
35481
- if (deepEq$1(guessCoeffs, correctCoeffs)) {
35482
- return {
35483
- type: "points",
35484
- earned: 1,
35485
- total: 1,
35486
- message: null
35487
- };
35488
- }
35489
- } else if (userInput.type === "sinusoid" && rubric.correct.type === "sinusoid" && userInput.coords != null) {
35490
- const guessCoeffs = this.getSinusoidCoefficients(userInput.coords);
35491
- const correctCoeffs = this.getSinusoidCoefficients(
35492
- // @ts-expect-error - TS2345 - Argument of type 'readonly Coord[] | undefined' is not assignable to parameter of type 'readonly Coord[]'.
35493
- rubric.correct.coords);
35494
- const canonicalGuessCoeffs = canonicalSineCoefficients$1(guessCoeffs);
35495
- const canonicalCorrectCoeffs = canonicalSineCoefficients$1(correctCoeffs);
35496
- // If the canonical coefficients match, it's correct.
35497
- if (deepEq$1(canonicalGuessCoeffs, canonicalCorrectCoeffs)) {
35498
- return {
35499
- type: "points",
35500
- earned: 1,
35501
- total: 1,
35502
- message: null
35503
- };
35504
- }
35505
- } else if (userInput.type === "circle" && rubric.correct.type === "circle") {
35506
- if (deepEq$1(userInput.center, rubric.correct.center) && eq(userInput.radius, rubric.correct.radius)) {
35507
- return {
35508
- type: "points",
35509
- earned: 1,
35510
- total: 1,
35511
- message: null
35512
- };
35513
- }
35514
- } else if (userInput.type === "point" && rubric.correct.type === "point" && userInput.coords != null) {
35515
- let correct = InteractiveGraph.getPointCoords(rubric.correct, component);
35516
- const guess = userInput.coords.slice();
35517
- correct = correct.slice();
35518
- // Everything's already rounded so we shouldn't need to do an
35519
- // eq() comparison but _.isEqual(0, -0) is false, so we'll use
35520
- // eq() anyway. The sort should be fine because it'll stringify
35521
- // it and -0 converted to a string is "0"
35522
- guess?.sort();
35523
- // @ts-expect-error - TS2339 - Property 'sort' does not exist on type 'readonly Coord[]'.
35524
- correct.sort();
35525
- if (deepEq$1(guess, correct)) {
35526
- return {
35527
- type: "points",
35528
- earned: 1,
35529
- total: 1,
35530
- message: null
35531
- };
35532
- }
35533
- } else if (userInput.type === "polygon" && rubric.correct.type === "polygon" && userInput.coords != null) {
35534
- const guess = userInput.coords?.slice();
35535
- // @ts-expect-error - TS2322 - Type 'Coord[] | undefined' is not assignable to type 'Coord[]'.
35536
- const correct = rubric.correct.coords?.slice();
35537
- let match;
35538
- if (rubric.correct.match === "similar") {
35539
- match = similar(guess, correct, Number.POSITIVE_INFINITY);
35540
- } else if (rubric.correct.match === "congruent") {
35541
- match = similar(guess, correct, kmath.number.DEFAULT_TOLERANCE);
35542
- } else if (rubric.correct.match === "approx") {
35543
- match = similar(guess, correct, 0.1);
35544
- } else {
35545
- /* exact */
35546
- guess.sort();
35547
- correct.sort();
35548
- match = deepEq$1(guess, correct);
35549
- }
35550
- if (match) {
35551
- return {
35552
- type: "points",
35553
- earned: 1,
35554
- total: 1,
35555
- message: null
35556
- };
35557
- }
35558
- } else if (userInput.type === "segment" && rubric.correct.type === "segment" && userInput.coords != null) {
35559
- let guess = Util.deepClone(userInput.coords);
35560
- let correct = Util.deepClone(rubric.correct?.coords);
35561
- guess = ___default["default"].invoke(guess, "sort").sort();
35562
- // @ts-expect-error - TS2345 - Argument of type '(readonly Coord[])[] | undefined' is not assignable to parameter of type 'Collection<any>'.
35563
- correct = ___default["default"].invoke(correct, "sort").sort();
35564
- if (deepEq$1(guess, correct)) {
35565
- return {
35566
- type: "points",
35567
- earned: 1,
35568
- total: 1,
35569
- message: null
35570
- };
35571
- }
35572
- } else if (userInput.type === "ray" && rubric.correct.type === "ray" && userInput.coords != null) {
35573
- const guess = userInput.coords;
35574
- const correct = rubric.correct.coords;
35575
- if (
35576
- // @ts-expect-error - TS2532 - Object is possibly 'undefined'.
35577
- deepEq$1(guess[0], correct[0]) &&
35578
- // @ts-expect-error - TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'.
35579
- collinear(correct[0], correct[1], guess[1])) {
35580
- return {
35581
- type: "points",
35582
- earned: 1,
35583
- total: 1,
35584
- message: null
35585
- };
35586
- }
35587
- } else if (userInput.type === "angle" && rubric.correct.type === "angle") {
35588
- const guess = userInput.coords;
35589
- const correct = rubric.correct.coords;
35590
- let match;
35591
- if (rubric.correct.match === "congruent") {
35592
- const angles = ___default["default"].map([guess, correct], function (coords) {
35593
- const angle = GraphUtils.findAngle(
35594
- // @ts-expect-error - TS2532 - Object is possibly 'undefined'.
35595
- coords[2],
35596
- // @ts-expect-error - TS2532 - Object is possibly 'undefined'.
35597
- coords[0],
35598
- // @ts-expect-error - TS2532 - Object is possibly 'undefined'.
35599
- coords[1]);
35600
- return (angle + 360) % 360;
35601
- });
35602
- // @ts-expect-error - TS2556 - A spread argument must either have a tuple type or be passed to a rest parameter.
35603
- match = eq(...angles);
35604
- } else {
35605
- /* exact */
35606
- match =
35607
- // @ts-expect-error - TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'.
35608
- deepEq$1(guess[1], correct[1]) &&
35609
- // @ts-expect-error - TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'.
35610
- collinear(correct[1], correct[0], guess[0]) &&
35611
- // @ts-expect-error - TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'.
35612
- collinear(correct[1], correct[2], guess[2]);
35613
- }
35614
- if (match) {
35615
- return {
35616
- type: "points",
35617
- earned: 1,
35618
- total: 1,
35619
- message: null
35620
- };
35621
- }
35622
- }
35623
- }
35624
-
35625
- // The input wasn't correct, so check if it's a blank input or if it's
35626
- // actually just wrong
35627
- if (!hasValue || ___default["default"].isEqual(userInput, rubric.graph)) {
35628
- // We're where we started.
35629
- return {
35630
- type: "invalid",
35631
- message: null
35632
- };
35633
- }
35634
- return {
35635
- type: "points",
35636
- earned: 0,
35637
- total: 1,
35638
- message: null
35639
- };
35776
+ static validate(userInput, rubric) {
35777
+ return interactiveGraphValidator(userInput, rubric);
35640
35778
  }
35641
35779
  static getUserInputFromProps(props) {
35642
35780
  return props.graph;
@@ -38335,6 +38473,16 @@ const styles$d = aphrodite.StyleSheet.create({
38335
38473
  }
38336
38474
  });
38337
38475
 
38476
+ function matcherValidator(state, rubric) {
38477
+ const correct = ___default["default"].isEqual(state.left, rubric.left) && ___default["default"].isEqual(state.right, rubric.right);
38478
+ return {
38479
+ type: "points",
38480
+ earned: correct ? 1 : 0,
38481
+ total: 1,
38482
+ message: null
38483
+ };
38484
+ }
38485
+
38338
38486
  const {
38339
38487
  shuffle: shuffle$1,
38340
38488
  seededRNG
@@ -38357,6 +38505,9 @@ class Matcher extends React__namespace.Component {
38357
38505
  rightHeight: 0,
38358
38506
  texRendererLoaded: false
38359
38507
  };
38508
+ static validate(state, rubric) {
38509
+ return matcherValidator(state, rubric);
38510
+ }
38360
38511
  changeAndTrack = e => {
38361
38512
  this.props.onChange(e);
38362
38513
  this.props.trackInteraction();
@@ -38409,8 +38560,7 @@ class Matcher extends React__namespace.Component {
38409
38560
  this.refs.right.moveOptionToIndex(option, index);
38410
38561
  };
38411
38562
  simpleValidate(rubric) {
38412
- // @ts-expect-error - TS2339 - Property 'validate' does not exist on type 'typeof Matcher'.
38413
- return Matcher.validate(this.getUserInput(), rubric);
38563
+ return matcherValidator(this.getUserInput(), rubric);
38414
38564
  }
38415
38565
  render() {
38416
38566
  // To minimize layout shift, we display a spinner until our math
@@ -38506,17 +38656,6 @@ class Matcher extends React__namespace.Component {
38506
38656
  })))));
38507
38657
  }
38508
38658
  }
38509
- ___default["default"].extend(Matcher, {
38510
- validate: function (state, rubric) {
38511
- const correct = ___default["default"].isEqual(state.left, rubric.left) && ___default["default"].isEqual(state.right, rubric.right);
38512
- return {
38513
- type: "points",
38514
- earned: correct ? 1 : 0,
38515
- total: 1,
38516
- message: null
38517
- };
38518
- }
38519
- });
38520
38659
  const padding = 5;
38521
38660
  const border = "1px solid #444";
38522
38661
  const styles$c = aphrodite.StyleSheet.create({
@@ -38552,7 +38691,59 @@ var Matcher$1 = {
38552
38691
  isLintable: true
38553
38692
  };
38554
38693
 
38555
- // Type imports
38694
+ function matrixValidator(state, rubric, strings) {
38695
+ const solution = rubric.answers;
38696
+ const supplied = state.answers;
38697
+ const solutionSize = getMatrixSize(solution);
38698
+ const suppliedSize = getMatrixSize(supplied);
38699
+ const incorrectSize = solutionSize[0] !== suppliedSize[0] || solutionSize[1] !== suppliedSize[1];
38700
+ const createValidator = KhanAnswerTypes.number.createValidatorFunctional;
38701
+ let message = null;
38702
+ let hasEmptyCell = false;
38703
+ let incorrect = false;
38704
+ ___default["default"](suppliedSize[0]).times(row => {
38705
+ ___default["default"](suppliedSize[1]).times(col => {
38706
+ if (supplied[row][col] == null || supplied[row][col].toString().length === 0) {
38707
+ hasEmptyCell = true;
38708
+ }
38709
+ if (!incorrectSize) {
38710
+ const validator = createValidator(
38711
+ // @ts-expect-error - TS2345 - Argument of type 'number' is not assignable to parameter of type 'string'.
38712
+ solution[row][col], {
38713
+ simplify: true
38714
+ }, strings);
38715
+ const result = validator(supplied[row][col]);
38716
+ if (result.message) {
38717
+ // @ts-expect-error - TS2322 - Type 'string' is not assignable to type 'null'.
38718
+ message = result.message;
38719
+ }
38720
+ if (!result.correct) {
38721
+ incorrect = true;
38722
+ }
38723
+ }
38724
+ });
38725
+ });
38726
+ if (hasEmptyCell) {
38727
+ return {
38728
+ type: "invalid",
38729
+ message: strings.fillAllCells
38730
+ };
38731
+ }
38732
+ if (incorrectSize) {
38733
+ return {
38734
+ type: "points",
38735
+ earned: 0,
38736
+ total: 1,
38737
+ message: null
38738
+ };
38739
+ }
38740
+ return {
38741
+ type: "points",
38742
+ earned: incorrect ? 0 : 1,
38743
+ total: 1,
38744
+ message: message
38745
+ };
38746
+ }
38556
38747
 
38557
38748
  const {
38558
38749
  assert: assert$2
@@ -38602,7 +38793,7 @@ const getRefForPath$1 = function (path) {
38602
38793
  const column = getColumnFromPath$1(path);
38603
38794
  return "answer" + row + "," + column;
38604
38795
  };
38605
- const getMatrixSize = function (matrix) {
38796
+ function getMatrixSize(matrix) {
38606
38797
  const matrixSize = [1, 1];
38607
38798
 
38608
38799
  // We need to find the widest row and tallest column to get the correct
@@ -38624,7 +38815,7 @@ const getMatrixSize = function (matrix) {
38624
38815
  }
38625
38816
  });
38626
38817
  return matrixSize;
38627
- };
38818
+ }
38628
38819
  class Matrix extends React__namespace.Component {
38629
38820
  static contextType = PerseusI18nContext;
38630
38821
  // @ts-expect-error - TS2564 - Property 'cursorPosition' has no initializer and is not definitely assigned in the constructor.
@@ -38639,57 +38830,7 @@ class Matrix extends React__namespace.Component {
38639
38830
  linterContext: PerseusLinter.linterContextDefault
38640
38831
  };
38641
38832
  static validate(state, rubric, strings) {
38642
- const solution = rubric.answers;
38643
- const supplied = state.answers;
38644
- const solutionSize = getMatrixSize(solution);
38645
- const suppliedSize = getMatrixSize(supplied);
38646
- const incorrectSize = solutionSize[0] !== suppliedSize[0] || solutionSize[1] !== suppliedSize[1];
38647
- const createValidator = KhanAnswerTypes.number.createValidatorFunctional;
38648
- let message = null;
38649
- let hasEmptyCell = false;
38650
- let incorrect = false;
38651
- ___default["default"](suppliedSize[0]).times(row => {
38652
- ___default["default"](suppliedSize[1]).times(col => {
38653
- if (supplied[row][col] == null || supplied[row][col].toString().length === 0) {
38654
- hasEmptyCell = true;
38655
- }
38656
- if (!incorrectSize) {
38657
- const validator = createValidator(
38658
- // @ts-expect-error - TS2345 - Argument of type 'number' is not assignable to parameter of type 'string'.
38659
- solution[row][col], {
38660
- simplify: true
38661
- }, strings);
38662
- const result = validator(supplied[row][col]);
38663
- if (result.message) {
38664
- // @ts-expect-error - TS2322 - Type 'string' is not assignable to type 'null'.
38665
- message = result.message;
38666
- }
38667
- if (!result.correct) {
38668
- incorrect = true;
38669
- }
38670
- }
38671
- });
38672
- });
38673
- if (hasEmptyCell) {
38674
- return {
38675
- type: "invalid",
38676
- message: strings.fillAllCells
38677
- };
38678
- }
38679
- if (incorrectSize) {
38680
- return {
38681
- type: "points",
38682
- earned: 0,
38683
- total: 1,
38684
- message: null
38685
- };
38686
- }
38687
- return {
38688
- type: "points",
38689
- earned: incorrect ? 0 : 1,
38690
- total: 1,
38691
- message: message
38692
- };
38833
+ return matrixValidator(state, rubric, strings);
38693
38834
  }
38694
38835
  state = {
38695
38836
  enterTheMatrix: 0
@@ -38834,7 +38975,7 @@ class Matrix extends React__namespace.Component {
38834
38975
  };
38835
38976
  }
38836
38977
  simpleValidate(rubric) {
38837
- return Matrix.validate(this.getUserInput(), rubric, this.context.strings);
38978
+ return matrixValidator(this.getUserInput(), rubric, this.context.strings);
38838
38979
  }
38839
38980
  render() {
38840
38981
  // Set the input sizes through JS so we can control the size of the
@@ -40071,6 +40212,45 @@ var Molecule$1 = {
40071
40212
  widget: MoleculeWidget
40072
40213
  };
40073
40214
 
40215
+ function numberLineValidator(state, rubric) {
40216
+ const range = rubric.range;
40217
+ const divisionRange = state.divisionRange;
40218
+ const start = rubric.initialX != null ? rubric.initialX : range[0];
40219
+ const startRel = rubric.isInequality ? "ge" : "eq";
40220
+ const correctRel = rubric.correctRel || "eq";
40221
+ const correctPos = kmath.number.equal(state.numLinePosition, rubric.correctX || 0);
40222
+ const outsideAllowedRange = state.numDivisions > divisionRange[1] || state.numDivisions < divisionRange[0];
40223
+
40224
+ // TODO: I don't think isTickCrtl is a thing anymore
40225
+ if (state.isTickCrtl && outsideAllowedRange) {
40226
+ return {
40227
+ type: "invalid",
40228
+ message: "Number of divisions is outside the allowed range."
40229
+ };
40230
+ }
40231
+ if (correctPos && correctRel === state.rel) {
40232
+ return {
40233
+ type: "points",
40234
+ earned: 1,
40235
+ total: 1,
40236
+ message: null
40237
+ };
40238
+ }
40239
+ if (state.numLinePosition === start && state.rel === startRel) {
40240
+ // We're where we started.
40241
+ return {
40242
+ type: "invalid",
40243
+ message: null
40244
+ };
40245
+ }
40246
+ return {
40247
+ type: "points",
40248
+ earned: 0,
40249
+ total: 1,
40250
+ message: null
40251
+ };
40252
+ }
40253
+
40074
40254
  // @ts-expect-error - TS2339 - Property 'MovablePoint' does not exist on type 'typeof Graphie'.
40075
40255
  const MovablePoint = Graphie.MovablePoint;
40076
40256
  // @ts-expect-error - TS2339 - Property 'Line' does not exist on type 'typeof Graphie'.
@@ -40218,6 +40398,9 @@ class NumberLine extends React__namespace.Component {
40218
40398
  state = {
40219
40399
  numDivisionsEmpty: false
40220
40400
  };
40401
+ static validate(state, rubric) {
40402
+ return numberLineValidator(state, rubric);
40403
+ }
40221
40404
  change = (() => {
40222
40405
  var _this = this;
40223
40406
  return function () {
@@ -40509,8 +40692,7 @@ class NumberLine extends React__namespace.Component {
40509
40692
  };
40510
40693
  }
40511
40694
  simpleValidate(rubric) {
40512
- // @ts-expect-error - TS2339 - Property 'validate' does not exist on type 'typeof NumberLine'.
40513
- return NumberLine.validate(this.getUserInput(), rubric);
40695
+ return numberLineValidator(this.getUserInput(), rubric);
40514
40696
  }
40515
40697
  render() {
40516
40698
  const {
@@ -40562,46 +40744,6 @@ class NumberLine extends React__namespace.Component {
40562
40744
  })) : this._renderGraphie(), !this.props.static && this.props.isInequality && inequalityControls);
40563
40745
  }
40564
40746
  }
40565
- ___default["default"].extend(NumberLine, {
40566
- validate: function (state, rubric) {
40567
- const range = rubric.range;
40568
- const divisionRange = state.divisionRange;
40569
- const start = rubric.initialX != null ? rubric.initialX : range[0];
40570
- const startRel = rubric.isInequality ? "ge" : "eq";
40571
- const correctRel = rubric.correctRel || "eq";
40572
- const correctPos = kmath.number.equal(state.numLinePosition, rubric.correctX || 0);
40573
- const outsideAllowedRange = state.numDivisions > divisionRange[1] || state.numDivisions < divisionRange[0];
40574
-
40575
- // TODO: I don't think isTickCrtl is a thing anymore
40576
- if (state.isTickCrtl && outsideAllowedRange) {
40577
- return {
40578
- type: "invalid",
40579
- message: "Number of divisions is outside the allowed range."
40580
- };
40581
- }
40582
- if (correctPos && correctRel === state.rel) {
40583
- return {
40584
- type: "points",
40585
- earned: 1,
40586
- total: 1,
40587
- message: null
40588
- };
40589
- }
40590
- if (state.numLinePosition === start && state.rel === startRel) {
40591
- // We're where we started.
40592
- return {
40593
- type: "invalid",
40594
- message: null
40595
- };
40596
- }
40597
- return {
40598
- type: "points",
40599
- earned: 0,
40600
- total: 1,
40601
- message: null
40602
- };
40603
- }
40604
- });
40605
40747
  const numberLineTransform = editorProps => {
40606
40748
  const props = ___default["default"].pick(editorProps, ["range", "labelRange", "labelStyle", "labelTicks", "divisionRange", "snapDivisions", "isTickCtrl", "isInequality", "showTooltips"]);
40607
40749
  const numLinePosition = editorProps.initialX != null ? editorProps.initialX : editorProps.range[0];
@@ -43668,10 +43810,25 @@ var PhetSimulation$1 = {
43668
43810
  isLintable: true
43669
43811
  };
43670
43812
 
43671
- /* eslint-disable react/no-unsafe */
43672
43813
  const {
43673
43814
  deepEq
43674
43815
  } = Util;
43816
+ function plotterValidator(userInput, rubric) {
43817
+ if (deepEq(userInput, rubric.starting)) {
43818
+ return {
43819
+ type: "invalid",
43820
+ message: null
43821
+ };
43822
+ }
43823
+ return {
43824
+ type: "points",
43825
+ earned: deepEq(userInput, rubric.correct) ? 1 : 0,
43826
+ total: 1,
43827
+ message: null
43828
+ };
43829
+ }
43830
+
43831
+ /* eslint-disable react/no-unsafe */
43675
43832
  class Plotter extends React__namespace.Component {
43676
43833
  static contextType = PerseusI18nContext;
43677
43834
  // @ts-expect-error - TS2564 - Property 'shouldSetupGraphie' has no initializer and is not definitely assigned in the constructor.
@@ -43700,6 +43857,9 @@ class Plotter extends React__namespace.Component {
43700
43857
  // bottom label.
43701
43858
  categoryHeights: {}
43702
43859
  };
43860
+ static validate(userInput, rubric) {
43861
+ return plotterValidator(userInput, rubric);
43862
+ }
43703
43863
  componentDidMount() {
43704
43864
  this._isMounted = true;
43705
43865
  this.setupGraphie(this.state);
@@ -44445,8 +44605,7 @@ class Plotter extends React__namespace.Component {
44445
44605
  return this.state.values;
44446
44606
  }
44447
44607
  simpleValidate(rubric) {
44448
- // @ts-expect-error - TS2339 - Property 'validate' does not exist on type 'typeof Plotter'.
44449
- return Plotter.validate(this.getUserInput(), rubric);
44608
+ return plotterValidator(this.getUserInput(), rubric);
44450
44609
  }
44451
44610
  render() {
44452
44611
  // TODO(kevinb) actually compute the size of the graphie correctly and
@@ -44465,22 +44624,6 @@ class Plotter extends React__namespace.Component {
44465
44624
  });
44466
44625
  }
44467
44626
  }
44468
- ___default["default"].extend(Plotter, {
44469
- validate: function (userInput, rubric) {
44470
- if (deepEq(userInput, rubric.starting)) {
44471
- return {
44472
- type: "invalid",
44473
- message: null
44474
- };
44475
- }
44476
- return {
44477
- type: "points",
44478
- earned: deepEq(userInput, rubric.correct) ? 1 : 0,
44479
- total: 1,
44480
- message: null
44481
- };
44482
- }
44483
- });
44484
44627
 
44485
44628
  // We don't need to change any of the original props for static mode
44486
44629
  const staticTransform = ___default["default"].identity;
@@ -44649,6 +44792,66 @@ var Sorter$1 = {
44649
44792
  isLintable: true
44650
44793
  };
44651
44794
 
44795
+ function tableValidator(state, rubric, strings) {
44796
+ const filterNonEmpty = function (table) {
44797
+ return ___default["default"].filter(table, function (row) {
44798
+ // Check if row has a cell that is nonempty
44799
+ return ___default["default"].some(row, ___default["default"].identity);
44800
+ });
44801
+ };
44802
+ const solution = filterNonEmpty(rubric.answers);
44803
+ const supplied = filterNonEmpty(state);
44804
+ const hasEmptyCell = ___default["default"].some(supplied, function (row) {
44805
+ return ___default["default"].some(row, function (cell) {
44806
+ return cell === "";
44807
+ });
44808
+ });
44809
+ if (hasEmptyCell || !supplied.length) {
44810
+ return {
44811
+ type: "invalid",
44812
+ message: null
44813
+ };
44814
+ }
44815
+ if (supplied.length !== solution.length) {
44816
+ return {
44817
+ type: "points",
44818
+ earned: 0,
44819
+ total: 1,
44820
+ message: null
44821
+ };
44822
+ }
44823
+ const createValidator = KhanAnswerTypes.number.createValidatorFunctional;
44824
+ let message = null;
44825
+ const allCorrect = ___default["default"].every(solution, function (rowSolution) {
44826
+ for (let i = 0; i < supplied.length; i++) {
44827
+ const rowSupplied = supplied[i];
44828
+ const correct = ___default["default"].every(rowSupplied, function (cellSupplied, i) {
44829
+ const cellSolution = rowSolution[i];
44830
+ const validator = createValidator(cellSolution, {
44831
+ simplify: true
44832
+ }, strings);
44833
+ const result = validator(cellSupplied);
44834
+ if (result.message) {
44835
+ // @ts-expect-error - TS2322 - Type 'string' is not assignable to type 'null'.
44836
+ message = result.message;
44837
+ }
44838
+ return result.correct;
44839
+ });
44840
+ if (correct) {
44841
+ supplied.splice(i, 1);
44842
+ return true;
44843
+ }
44844
+ }
44845
+ return false;
44846
+ });
44847
+ return {
44848
+ type: "points",
44849
+ earned: allCorrect ? 1 : 0,
44850
+ total: 1,
44851
+ message: message
44852
+ };
44853
+ }
44854
+
44652
44855
  const {
44653
44856
  assert
44654
44857
  } = InteractiveUtil;
@@ -44694,6 +44897,9 @@ class Table extends React__namespace.Component {
44694
44897
  linterContext: PerseusLinter.linterContextDefault
44695
44898
  };
44696
44899
  }();
44900
+ static validate(state, rubric, strings) {
44901
+ return tableValidator(state, rubric, strings);
44902
+ }
44697
44903
  _getRows = () => {
44698
44904
  return this.props.answers.length;
44699
44905
  };
@@ -44723,8 +44929,7 @@ class Table extends React__namespace.Component {
44723
44929
  });
44724
44930
  };
44725
44931
  simpleValidate(rubric) {
44726
- // @ts-expect-error - TS2339 - Property 'validate' does not exist on type 'typeof Table'.
44727
- return Table.validate(this.getUserInput(), rubric, this.context.strings);
44932
+ return tableValidator(this.getUserInput(), rubric, this.context.strings);
44728
44933
  }
44729
44934
  _handleFocus = inputPath => {
44730
44935
  this.props.onFocus(inputPath);
@@ -44851,68 +45056,6 @@ class Table extends React__namespace.Component {
44851
45056
  })));
44852
45057
  }
44853
45058
  }
44854
- ___default["default"].extend(Table, {
44855
- validate: function (state, rubric, strings) {
44856
- const filterNonEmpty = function (table) {
44857
- return ___default["default"].filter(table, function (row) {
44858
- // Check if row has a cell that is nonempty
44859
- return ___default["default"].some(row, ___default["default"].identity);
44860
- });
44861
- };
44862
- const solution = filterNonEmpty(rubric.answers);
44863
- const supplied = filterNonEmpty(state);
44864
- const hasEmptyCell = ___default["default"].some(supplied, function (row) {
44865
- return ___default["default"].some(row, function (cell) {
44866
- return cell === "";
44867
- });
44868
- });
44869
- if (hasEmptyCell || !supplied.length) {
44870
- return {
44871
- type: "invalid",
44872
- message: null
44873
- };
44874
- }
44875
- if (supplied.length !== solution.length) {
44876
- return {
44877
- type: "points",
44878
- earned: 0,
44879
- total: 1,
44880
- message: null
44881
- };
44882
- }
44883
- const createValidator = KhanAnswerTypes.number.createValidatorFunctional;
44884
- let message = null;
44885
- const allCorrect = ___default["default"].every(solution, function (rowSolution) {
44886
- let i;
44887
- for (i = 0; i < supplied.length; i++) {
44888
- const rowSupplied = supplied[i];
44889
- const correct = ___default["default"].every(rowSupplied, function (cellSupplied, i) {
44890
- const cellSolution = rowSolution[i];
44891
- const validator = createValidator(cellSolution, {
44892
- simplify: true
44893
- }, strings);
44894
- const result = validator(cellSupplied);
44895
- if (result.message) {
44896
- // @ts-expect-error - TS2322 - Type 'string' is not assignable to type 'null'.
44897
- message = result.message;
44898
- }
44899
- return result.correct;
44900
- });
44901
- if (correct) {
44902
- supplied.splice(i, 1);
44903
- return true;
44904
- }
44905
- }
44906
- return false;
44907
- });
44908
- return {
44909
- type: "points",
44910
- earned: allCorrect ? 1 : 0,
44911
- total: 1,
44912
- message: message
44913
- };
44914
- }
44915
- });
44916
45059
  const propTransform = editorProps => {
44917
45060
  // Remove answers before passing to widget
44918
45061
  const rows = editorProps.answers.length;
@@ -45145,7 +45288,7 @@ const init = function (options) {
45145
45288
 
45146
45289
  // This file is processed by a Rollup plugin (replace) to inject the production
45147
45290
  const libName = "@khanacademy/perseus";
45148
- const libVersion = "35.0.0";
45291
+ const libVersion = "35.1.1";
45149
45292
  perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
45150
45293
 
45151
45294
  const apiVersion = {