@khanacademy/perseus 36.1.0 → 36.1.2

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.
package/dist/es/index.js CHANGED
@@ -3157,13 +3157,13 @@ function eq$4(x, y) {
3157
3157
  * Deep approximate equality on primitives, numbers, arrays, and objects.
3158
3158
  * Recursive.
3159
3159
  */
3160
- function deepEq$5(x, y) {
3160
+ function deepEq$6(x, y) {
3161
3161
  if (Array.isArray(x) && Array.isArray(y)) {
3162
3162
  if (x.length !== y.length) {
3163
3163
  return false;
3164
3164
  }
3165
3165
  for (let i = 0; i < x.length; i++) {
3166
- if (!deepEq$5(x[i], y[i])) {
3166
+ if (!deepEq$6(x[i], y[i])) {
3167
3167
  return false;
3168
3168
  }
3169
3169
  }
@@ -3181,10 +3181,10 @@ function deepEq$5(x, y) {
3181
3181
  if (typeof x === "object" && typeof y === "object" && !!x && !!y) {
3182
3182
  return x === y || _.all(x, function (v, k) {
3183
3183
  // @ts-expect-error - TS2536 - Type 'CollectionKey<T>' cannot be used to index type 'T'.
3184
- return deepEq$5(y[k], v);
3184
+ return deepEq$6(y[k], v);
3185
3185
  }) && _.all(y, function (v, k) {
3186
3186
  // @ts-expect-error - TS2536 - Type 'CollectionKey<T>' cannot be used to index type 'T'.
3187
- return deepEq$5(x[k], v);
3187
+ return deepEq$6(x[k], v);
3188
3188
  });
3189
3189
  }
3190
3190
  if (typeof x === "object" && !!x || typeof y === "object" && !!y) {
@@ -3464,7 +3464,7 @@ const Util = {
3464
3464
  tickStepFromNumTicks,
3465
3465
  constrainedTickStepsFromTickSteps,
3466
3466
  eq: eq$4,
3467
- deepEq: deepEq$5,
3467
+ deepEq: deepEq$6,
3468
3468
  parseQueryString,
3469
3469
  updateQueryString: updateQueryString$2,
3470
3470
  strongEncodeURIComponent,
@@ -10070,7 +10070,10 @@ const GraphUtils = {
10070
10070
  return point.distanceToPoint(point1, point2);
10071
10071
  },
10072
10072
  // Find the angle in degrees between two or three points
10073
- findAngle: function (point1, point2, vertex) {
10073
+ // This function is deprecated as it has several issues calculating
10074
+ // correctly when dealing with reflex angles or the position of point2
10075
+ // (LEMS-2202) Remove this function while removing the Legacy Interactive Graph.
10076
+ findAngleDeprecated: function (point1, point2, vertex) {
10074
10077
  if (vertex === undefined) {
10075
10078
  const x = point1[0] - point2[0];
10076
10079
  const y = point1[1] - point2[1];
@@ -10079,7 +10082,7 @@ const GraphUtils = {
10079
10082
  }
10080
10083
  return (180 + Math.atan2(-y, -x) * 180 / Math.PI + 360) % 360;
10081
10084
  }
10082
- return GraphUtils.findAngle(point1, vertex) - GraphUtils.findAngle(point2, vertex);
10085
+ return GraphUtils.findAngleDeprecated(point1, vertex) - GraphUtils.findAngleDeprecated(point2, vertex);
10083
10086
  },
10084
10087
  graphs: {}
10085
10088
  };
@@ -10786,7 +10789,7 @@ class WrappedLine extends WrappedDrawing {
10786
10789
  */
10787
10790
  const {
10788
10791
  eq: eq$3,
10789
- deepEq: deepEq$4
10792
+ deepEq: deepEq$5
10790
10793
  } = Util;
10791
10794
 
10792
10795
  // This should really be a readonly tuple of [number, number]
@@ -10946,7 +10949,7 @@ function similar(coords1, coords2, tolerance) {
10946
10949
  angles = rotate(angles, i);
10947
10950
  // @ts-expect-error - TS4104 - The type 'readonly number[]' is 'readonly' and cannot be assigned to the mutable type 'number[]'.
10948
10951
  sides = rotate(sides, i);
10949
- if (deepEq$4(angles1, angles)) {
10952
+ if (deepEq$5(angles1, angles)) {
10950
10953
  const sidePairs = _.zip(sides1, sides);
10951
10954
  const factors = _.map(sidePairs, function (pair) {
10952
10955
  return pair[0] / pair[1];
@@ -11095,8 +11098,8 @@ _.extend(GraphUtils.Graphie.prototype, {
11095
11098
  p1 = options.point3;
11096
11099
  p3 = options.point1;
11097
11100
  }
11098
- const startAngle = GraphUtils.findAngle(p1, vertex);
11099
- const endAngle = GraphUtils.findAngle(p3, vertex);
11101
+ const startAngle = GraphUtils.findAngleDeprecated(p1, vertex);
11102
+ const endAngle = GraphUtils.findAngleDeprecated(p3, vertex);
11100
11103
  const angle = (endAngle + 360 - startAngle) % 360;
11101
11104
  const halfAngle = (startAngle + angle / 2) % 360;
11102
11105
  const sPadding = 5 * options.pushOut;
@@ -11263,15 +11266,15 @@ _.extend(GraphUtils.Graphie.prototype, {
11263
11266
  let angle = 135;
11264
11267
  let halfAngle;
11265
11268
  if (p1 && p3) {
11266
- const startAngle = GraphUtils.findAngle(p1, vertex);
11267
- const endAngle = GraphUtils.findAngle(p3, vertex);
11269
+ const startAngle = GraphUtils.findAngleDeprecated(p1, vertex);
11270
+ const endAngle = GraphUtils.findAngleDeprecated(p3, vertex);
11268
11271
  angle = (endAngle + 360 - startAngle) % 360;
11269
11272
  halfAngle = (startAngle + angle / 2 + 180) % 360;
11270
11273
  } else if (p1) {
11271
- const parallelAngle = GraphUtils.findAngle(vertex, p1);
11274
+ const parallelAngle = GraphUtils.findAngleDeprecated(vertex, p1);
11272
11275
  halfAngle = parallelAngle + 90;
11273
11276
  } else if (p3) {
11274
- const parallelAngle = GraphUtils.findAngle(p3, vertex);
11277
+ const parallelAngle = GraphUtils.findAngleDeprecated(p3, vertex);
11275
11278
  halfAngle = parallelAngle + 90;
11276
11279
  } else {
11277
11280
  // Standalone point
@@ -11477,7 +11480,7 @@ _.extend(GraphUtils.Graphie.prototype, {
11477
11480
  const vertex = constraints.fixedAngle.vertex.coord || constraints.fixedAngle.vertex;
11478
11481
  const ref = constraints.fixedAngle.ref.coord || constraints.fixedAngle.ref;
11479
11482
  const distPoint = constraints.fixedDistance.point.coord || constraints.fixedDistance.point;
11480
- const constrainedAngle = (constraints.fixedAngle.angle + GraphUtils.findAngle(ref, vertex)) * Math.PI / 180;
11483
+ const constrainedAngle = (constraints.fixedAngle.angle + GraphUtils.findAngleDeprecated(ref, vertex)) * Math.PI / 180;
11481
11484
  const length = constraints.fixedDistance.dist;
11482
11485
  newCoord[0] = length * Math.cos(constrainedAngle) + distPoint[0];
11483
11486
  newCoord[1] = length * Math.sin(constrainedAngle) + distPoint[1];
@@ -11486,8 +11489,8 @@ _.extend(GraphUtils.Graphie.prototype, {
11486
11489
  } else if (typeof constraints.fixedAngle.angle === "number") {
11487
11490
  const vertex = constraints.fixedAngle.vertex.coord || constraints.fixedAngle.vertex;
11488
11491
  const ref = constraints.fixedAngle.ref.coord || constraints.fixedAngle.ref;
11489
- const constrainedAngle = (constraints.fixedAngle.angle + GraphUtils.findAngle(ref, vertex)) * Math.PI / 180;
11490
- const angle = GraphUtils.findAngle(coord, vertex) * Math.PI / 180;
11492
+ const constrainedAngle = (constraints.fixedAngle.angle + GraphUtils.findAngleDeprecated(ref, vertex)) * Math.PI / 180;
11493
+ const angle = GraphUtils.findAngleDeprecated(coord, vertex) * Math.PI / 180;
11491
11494
  const distance = GraphUtils.getDistance(coord, vertex);
11492
11495
  let length = distance * Math.cos(constrainedAngle - angle);
11493
11496
  length = length < 1.0 ? 1.0 : length;
@@ -11497,7 +11500,7 @@ _.extend(GraphUtils.Graphie.prototype, {
11497
11500
  // distance is constrained
11498
11501
  } else if (typeof constraints.fixedDistance.dist === "number") {
11499
11502
  const distPoint = constraints.fixedDistance.point.coord || constraints.fixedDistance.point;
11500
- let angle = GraphUtils.findAngle(coord, distPoint);
11503
+ let angle = GraphUtils.findAngleDeprecated(coord, distPoint);
11501
11504
  const length = constraints.fixedDistance.dist;
11502
11505
  angle = angle * Math.PI / 180;
11503
11506
  newCoord[0] = length * Math.cos(angle) + distPoint[0];
@@ -13697,7 +13700,7 @@ _.extend(MovableAngle.prototype, {
13697
13700
  _.each([0, 2], function (i) {
13698
13701
  const oldPoint = points[i].coord;
13699
13702
  let newPoint = vector$1.add(oldPoint, delta);
13700
- let angle = GraphUtils.findAngle(newVertex, newPoint);
13703
+ let angle = GraphUtils.findAngleDeprecated(newVertex, newPoint);
13701
13704
  angle *= Math.PI / 180;
13702
13705
  newPoint = graphie.constrainToBoundsOnAngle(newPoint, 10, angle);
13703
13706
  newPoints[i] = newPoint;
@@ -13730,7 +13733,7 @@ _.extend(MovableAngle.prototype, {
13730
13733
  return false;
13731
13734
  }
13732
13735
  if (snap) {
13733
- let angle = GraphUtils.findAngle(newPoint, vertex);
13736
+ let angle = GraphUtils.findAngleDeprecated(newPoint, vertex);
13734
13737
  angle = Math.round((angle - snapOffset) / snap) * snap + snapOffset;
13735
13738
  const distance = GraphUtils.getDistance(newPoint, vertex);
13736
13739
  return vector$1.add(vertex, polar$1(distance, angle));
@@ -13818,7 +13821,7 @@ _.extend(MovableAngle.prototype, {
13818
13821
  * coords when interpreted in a clockwise direction.
13819
13822
  */
13820
13823
  _getClockwiseAngle: function (coords) {
13821
- const rawAngle = GraphUtils.findAngle(
13824
+ const rawAngle = GraphUtils.findAngleDeprecated(
13822
13825
  // The order of these is "weird" to match what a clockwise
13823
13826
  // order is in graphie.labelAngle
13824
13827
  coords[2],
@@ -13884,7 +13887,7 @@ _.extend(MovableAngle.prototype, {
13884
13887
 
13885
13888
  /* eslint-disable @babel/no-invalid-this */
13886
13889
  const nestedMap$1 = Util.nestedMap;
13887
- const deepEq$3 = Util.deepEq;
13890
+ const deepEq$4 = Util.deepEq;
13888
13891
 
13889
13892
  /**
13890
13893
  * A base class for all Graphie Movables
@@ -13981,7 +13984,7 @@ const createSimpleClass = function createSimpleClass(addFunction) {
13981
13984
  this._prevProps = this.props;
13982
13985
  },
13983
13986
  modify: function (graphie) {
13984
- if (!deepEq$3(this.props, this._prevProps)) {
13987
+ if (!deepEq$4(this.props, this._prevProps)) {
13985
13988
  this.remove();
13986
13989
  this.add(graphie);
13987
13990
  this._prevProps = this.props;
@@ -16684,7 +16687,7 @@ var Movables = {
16684
16687
  const GraphieMovable = GraphieClasses.GraphieMovable;
16685
16688
  const createGraphie = GraphUtils.createGraphie;
16686
16689
  const {
16687
- deepEq: deepEq$2,
16690
+ deepEq: deepEq$3,
16688
16691
  nestedMap
16689
16692
  } = Util;
16690
16693
  const {
@@ -16926,7 +16929,7 @@ class Graphie extends React.Component {
16926
16929
  if (this.props.setup !== prevProps.setup) {
16927
16930
  Log.error("<Graphie> was given a new setup function. " + "This is a bad idea; please refactor your code to give " + "the same setup function reference to <Graphie> on " + "every render.", Errors.Internal);
16928
16931
  }
16929
- if (!deepEq$2(this.props.options, prevProps.options) || !deepEq$2(this.props.box, prevProps.box) || !deepEq$2(this.props.range, prevProps.range)) {
16932
+ if (!deepEq$3(this.props.options, prevProps.options) || !deepEq$3(this.props.box, prevProps.box) || !deepEq$3(this.props.range, prevProps.range)) {
16930
16933
  this._setupGraphie();
16931
16934
  }
16932
16935
  this._updateMovables();
@@ -30672,16 +30675,39 @@ function polar(r, th) {
30672
30675
  th = th * Math.PI / 180;
30673
30676
  return [r[0] * Math.cos(th), r[1] * Math.sin(th)];
30674
30677
  }
30675
- const findAngle = (point1, point2, vertex) => {
30676
- if (vertex === undefined) {
30677
- const x = point1[0] - point2[0];
30678
- const y = point1[1] - point2[1];
30679
- if (!x && !y) {
30680
- return 0;
30681
- }
30682
- return (180 + Math.atan2(-y, -x) * 180 / Math.PI + 360) % 360;
30678
+ // This function calculates the angle between two points and an optional vertex.
30679
+ // If the vertex is not provided, the angle is measured between the two points.
30680
+ // This does not account for reflex angles or clockwise position.
30681
+ const getAngleFromVertex = (point, vertex) => {
30682
+ const x = point[0] - vertex[0];
30683
+ const y = point[1] - vertex[1];
30684
+ if (!x && !y) {
30685
+ return 0;
30683
30686
  }
30684
- return findAngle(point1, vertex) - findAngle(point2, vertex);
30687
+ return (180 + Math.atan2(-y, -x) * 180 / Math.PI + 360) % 360;
30688
+ };
30689
+
30690
+ // This function calculates the clockwise angle between three points,
30691
+ // and is used to generate the labels and equation strings of the
30692
+ // current angle for the interactive graph.
30693
+ const getClockwiseAngle = (coords, allowReflexAngles = false) => {
30694
+ const coordsCopy = [...coords];
30695
+ // The coords are saved as [point1, vertex, point2] in the interactive graph
30696
+ const areClockwise = clockwise$1([coordsCopy[0], coordsCopy[2], coordsCopy[1]]);
30697
+
30698
+ // We may need to reverse the coordinates if we allow
30699
+ // reflex angles and the points are not in clockwise order.
30700
+ const shouldReverseCoords = !areClockwise || allowReflexAngles;
30701
+
30702
+ // Reverse the coordinates accordingly to ensure the angle is calculated correctly
30703
+ const clockwiseCoords = shouldReverseCoords ? coordsCopy.reverse() : coordsCopy;
30704
+
30705
+ // Calculate the angles between the two points and get the difference
30706
+ // between the two angles to get the clockwise angle.
30707
+ const startAngle = getAngleFromVertex(clockwiseCoords[0], clockwiseCoords[1]);
30708
+ const endAngle = getAngleFromVertex(clockwiseCoords[2], clockwiseCoords[1]);
30709
+ const angle = (startAngle + 360 - endAngle) % 360;
30710
+ return angle;
30685
30711
  };
30686
30712
 
30687
30713
  const defaultGraphConfig = {
@@ -30988,10 +31014,16 @@ const shouldShowLabel = (currentTick, range, tickStep) => {
30988
31014
  function generateTickLocations(tickStep, min, max) {
30989
31015
  const ticks = [];
30990
31016
 
31017
+ // Calculate the number of significant decimals in the tick step so
31018
+ // that we can match the desired precision when generating ticks.
31019
+ const decimalSigFigs = countSignificantDecimals(tickStep);
31020
+
30991
31021
  // Add ticks in the positive direction
30992
31022
  const start = Math.max(min, 0);
30993
31023
  for (let _i = start + tickStep; _i < max; _i += tickStep) {
30994
- ticks.push(_i);
31024
+ // Match to the same number of decimal places as the tick step
31025
+ // to avoid floating point errors when working with small numbers
31026
+ ticks.push(parseFloat(_i.toFixed(decimalSigFigs)));
30995
31027
  }
30996
31028
 
30997
31029
  // Add ticks in the negative direction
@@ -31002,6 +31034,15 @@ function generateTickLocations(tickStep, min, max) {
31002
31034
  }
31003
31035
  return ticks;
31004
31036
  }
31037
+
31038
+ // Count the number of significant digits after the decimal point
31039
+ const countSignificantDecimals = number => {
31040
+ const numStr = number.toString();
31041
+ if (!numStr.includes(".")) {
31042
+ return 0;
31043
+ }
31044
+ return numStr.split(".")[1].length;
31045
+ };
31005
31046
  const AxisTicks = () => {
31006
31047
  const {
31007
31048
  tickStep,
@@ -33088,8 +33129,8 @@ const Angle = ({
33088
33129
  const clockwiseCoords = shouldReverseCoords ? coords : coords.reverse();
33089
33130
 
33090
33131
  // Calculate the angles between the two points
33091
- const startAngle = findAngle(clockwiseCoords[0], vertex);
33092
- const endAngle = findAngle(clockwiseCoords[1], vertex);
33132
+ const startAngle = getAngleFromVertex(clockwiseCoords[0], vertex);
33133
+ const endAngle = getAngleFromVertex(clockwiseCoords[1], vertex);
33093
33134
  const angle = (startAngle + 360 - endAngle) % 360;
33094
33135
 
33095
33136
  // Check if the angle is reflexive
@@ -34168,7 +34209,9 @@ const MafsGraph = props => {
34168
34209
  analytics.onAnalyticsEvent({
34169
34210
  type: "perseus:interactive-graph-widget:rendered",
34170
34211
  payload: {
34171
- type
34212
+ type,
34213
+ widgetType: "INTERACTIVE_GRAPH",
34214
+ widgetId: "interactive-graph"
34172
34215
  }
34173
34216
  });
34174
34217
  });
@@ -35408,7 +35451,7 @@ function boundAndSnapAngleVertex({
35408
35451
  for (const i of [0, 2]) {
35409
35452
  const oldPoint = coordsCopy[i];
35410
35453
  let newPoint = vec.add(oldPoint, delta);
35411
- let angle = findAngle(newVertex, newPoint);
35454
+ let angle = getAngleFromVertex(newVertex, newPoint);
35412
35455
  angle *= Math.PI / 180;
35413
35456
  newPoint = constrainToBoundsOnAngle(newPoint, angle, range, snapStep);
35414
35457
  newPoints[i] = newPoint;
@@ -35479,7 +35522,7 @@ function boundAndSnapAngleEndPoints(destinationPoint, {
35479
35522
  const vertex = coords[1];
35480
35523
 
35481
35524
  // Gets the angle between the coords and the vertex
35482
- let angle = findAngle(coordsCopy[index], vertex);
35525
+ let angle = getAngleFromVertex(coordsCopy[index], vertex);
35483
35526
 
35484
35527
  // Snap the angle to the nearest multiple of snapDegrees (if provided)
35485
35528
  angle = Math.round((angle - offsetDegrees) / snap) * snap + offsetDegrees;
@@ -35525,8 +35568,8 @@ function boundAndSnapToPolygonAngle(destinationPoint, {
35525
35568
  angles[rel(j)] = Math.round(angles[rel(j)]);
35526
35569
  });
35527
35570
  const getAngle = function getAngle(a, vertex, b) {
35528
- const angle = findAngle(coordsCopy[rel(a)], coordsCopy[rel(b)], coordsCopy[rel(vertex)]);
35529
- return (angle + 360) % 360;
35571
+ const angle = getClockwiseAngle([coordsCopy[rel(a)], coordsCopy[rel(vertex)], coordsCopy[rel(b)]]);
35572
+ return angle;
35530
35573
  };
35531
35574
  const innerAngles = [angles[rel(-1)] - getAngle(-2, -1, 1), angles[rel(1)] - getAngle(-1, 1, 2)];
35532
35575
  innerAngles[2] = 180 - (innerAngles[0] + innerAngles[1]);
@@ -35553,7 +35596,7 @@ function boundAndSnapToPolygonAngle(destinationPoint, {
35553
35596
  const side = Math.sin(innerAngles[1] * Math.PI / 180) / Math.sin(innerAngles[2] * Math.PI / 180) * knownSide;
35554
35597
 
35555
35598
  // Angle at the second vertex of the polygon
35556
- const outerAngle = findAngle(coordsCopy[rel(1)], coordsCopy[rel(-1)]);
35599
+ const outerAngle = getAngleFromVertex(coordsCopy[rel(1)], coordsCopy[rel(-1)]);
35557
35600
 
35558
35601
  // Uses the length of the side of the polygon (radial coordinate)
35559
35602
  // and the angle between the first and second sides of the
@@ -35603,7 +35646,7 @@ function boundAndSnapToSides(destinationPoint, {
35603
35646
  const innerAngle = lawOfCosines(sides[0], sides[2], sides[1]);
35604
35647
 
35605
35648
  // Angle at the second vertex of the polygon
35606
- const outerAngle = findAngle(coordsCopy[rel(1)], coordsCopy[rel(-1)]);
35649
+ const outerAngle = getAngleFromVertex(coordsCopy[rel(1)], coordsCopy[rel(-1)]);
35607
35650
 
35608
35651
  // Returns true if the points form a counter-clockwise turn;
35609
35652
  // a.k.a if the point is on the left or right of the polygon.
@@ -35710,7 +35753,8 @@ const StatefulMafsGraph = /*#__PURE__*/React.forwardRef((props, ref) => {
35710
35753
  const numPoints = graph.type === "point" ? graph.numPoints : null;
35711
35754
  const numSides = graph.type === "polygon" ? graph.numSides : null;
35712
35755
  const snapTo = graph.type === "polygon" ? graph.snapTo : null;
35713
- const showAngles = graph.type === "polygon" ? graph.showAngles : null;
35756
+ const showAngles = graph.type === "polygon" || graph.type === "angle" ? graph.showAngles : null;
35757
+ const allowReflexAngles = graph.type === "angle" ? graph.allowReflexAngles : null;
35714
35758
  const showSides = graph.type === "polygon" ? graph.showSides : null;
35715
35759
  const startCoords = "startCoords" in graph ? graph.startCoords : undefined;
35716
35760
  const originalPropsRef = useRef(props);
@@ -35723,7 +35767,7 @@ const StatefulMafsGraph = /*#__PURE__*/React.forwardRef((props, ref) => {
35723
35767
  if (latestPropsRef.current !== originalPropsRef.current) {
35724
35768
  dispatch(reinitialize(latestPropsRef.current));
35725
35769
  }
35726
- }, [graph.type, numPoints, numSegments, numSides, snapTo, showAngles, showSides, latestPropsRef, startCoords]);
35770
+ }, [graph.type, numPoints, numSegments, numSides, snapTo, showAngles, showSides, latestPropsRef, startCoords, allowReflexAngles]);
35727
35771
 
35728
35772
  // If the graph is static, it always displays the correct answer. This is
35729
35773
  // standard behavior for Perseus widgets (e.g. compare the Radio widget).
@@ -35742,7 +35786,7 @@ const StatefulMafsGraph = /*#__PURE__*/React.forwardRef((props, ref) => {
35742
35786
  });
35743
35787
 
35744
35788
  const eq$1 = Util.eq;
35745
- const deepEq$1 = Util.deepEq;
35789
+ const deepEq$2 = Util.deepEq;
35746
35790
  function interactiveGraphValidator(userInput, rubric) {
35747
35791
  // None-type graphs are not graded
35748
35792
  if (userInput.type === "none" && rubric.correct.type === "none") {
@@ -35798,7 +35842,7 @@ function interactiveGraphValidator(userInput, rubric) {
35798
35842
  const correctCoeffs = getQuadraticCoefficients(
35799
35843
  // @ts-expect-error - TS2345 - Argument of type 'readonly Coord[] | undefined' is not assignable to parameter of type 'readonly Coord[]'.
35800
35844
  rubric.correct.coords);
35801
- if (deepEq$1(guessCoeffs, correctCoeffs)) {
35845
+ if (deepEq$2(guessCoeffs, correctCoeffs)) {
35802
35846
  return {
35803
35847
  type: "points",
35804
35848
  earned: 1,
@@ -35814,7 +35858,7 @@ function interactiveGraphValidator(userInput, rubric) {
35814
35858
  const canonicalGuessCoeffs = canonicalSineCoefficients$1(guessCoeffs);
35815
35859
  const canonicalCorrectCoeffs = canonicalSineCoefficients$1(correctCoeffs);
35816
35860
  // If the canonical coefficients match, it's correct.
35817
- if (deepEq$1(canonicalGuessCoeffs, canonicalCorrectCoeffs)) {
35861
+ if (deepEq$2(canonicalGuessCoeffs, canonicalCorrectCoeffs)) {
35818
35862
  return {
35819
35863
  type: "points",
35820
35864
  earned: 1,
@@ -35823,7 +35867,7 @@ function interactiveGraphValidator(userInput, rubric) {
35823
35867
  };
35824
35868
  }
35825
35869
  } else if (userInput.type === "circle" && rubric.correct.type === "circle") {
35826
- if (deepEq$1(userInput.center, rubric.correct.center) && eq$1(userInput.radius, rubric.correct.radius)) {
35870
+ if (deepEq$2(userInput.center, rubric.correct.center) && eq$1(userInput.radius, rubric.correct.radius)) {
35827
35871
  return {
35828
35872
  type: "points",
35829
35873
  earned: 1,
@@ -35845,7 +35889,7 @@ function interactiveGraphValidator(userInput, rubric) {
35845
35889
  guess == null || guess.sort();
35846
35890
  // @ts-expect-error - TS2339 - Property 'sort' does not exist on type 'readonly Coord[]'.
35847
35891
  correct.sort();
35848
- if (deepEq$1(guess, correct)) {
35892
+ if (deepEq$2(guess, correct)) {
35849
35893
  return {
35850
35894
  type: "points",
35851
35895
  earned: 1,
@@ -35869,7 +35913,7 @@ function interactiveGraphValidator(userInput, rubric) {
35869
35913
  /* exact */
35870
35914
  guess.sort();
35871
35915
  correct.sort();
35872
- match = deepEq$1(guess, correct);
35916
+ match = deepEq$2(guess, correct);
35873
35917
  }
35874
35918
  if (match) {
35875
35919
  return {
@@ -35886,7 +35930,7 @@ function interactiveGraphValidator(userInput, rubric) {
35886
35930
  guess = _.invoke(guess, "sort").sort();
35887
35931
  // @ts-expect-error - TS2345 - Argument of type '(readonly Coord[])[] | undefined' is not assignable to parameter of type 'Collection<any>'.
35888
35932
  correct = _.invoke(correct, "sort").sort();
35889
- if (deepEq$1(guess, correct)) {
35933
+ if (deepEq$2(guess, correct)) {
35890
35934
  return {
35891
35935
  type: "points",
35892
35936
  earned: 1,
@@ -35899,7 +35943,7 @@ function interactiveGraphValidator(userInput, rubric) {
35899
35943
  const correct = rubric.correct.coords;
35900
35944
  if (
35901
35945
  // @ts-expect-error - TS2532 - Object is possibly 'undefined'.
35902
- deepEq$1(guess[0], correct[0]) &&
35946
+ deepEq$2(guess[0], correct[0]) &&
35903
35947
  // @ts-expect-error - TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'.
35904
35948
  collinear(correct[0], correct[1], guess[1])) {
35905
35949
  return {
@@ -35912,17 +35956,15 @@ function interactiveGraphValidator(userInput, rubric) {
35912
35956
  } else if (userInput.type === "angle" && rubric.correct.type === "angle") {
35913
35957
  const guess = userInput.coords;
35914
35958
  const correct = rubric.correct.coords;
35959
+ const allowReflexAngles = rubric.correct.allowReflexAngles;
35915
35960
  let match;
35916
35961
  if (rubric.correct.match === "congruent") {
35917
35962
  const angles = _.map([guess, correct], function (coords) {
35918
- const angle = GraphUtils.findAngle(
35919
- // @ts-expect-error - TS2532 - Object is possibly 'undefined'.
35920
- coords[2],
35921
- // @ts-expect-error - TS2532 - Object is possibly 'undefined'.
35922
- coords[0],
35923
- // @ts-expect-error - TS2532 - Object is possibly 'undefined'.
35924
- coords[1]);
35925
- return (angle + 360) % 360;
35963
+ if (!coords) {
35964
+ return false;
35965
+ }
35966
+ const angle = getClockwiseAngle(coords, allowReflexAngles);
35967
+ return angle;
35926
35968
  });
35927
35969
  // @ts-expect-error - TS2556 - A spread argument must either have a tuple type or be passed to a rest parameter.
35928
35970
  match = eq$1(...angles);
@@ -35930,7 +35972,7 @@ function interactiveGraphValidator(userInput, rubric) {
35930
35972
  /* exact */
35931
35973
  match =
35932
35974
  // @ts-expect-error - TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'.
35933
- deepEq$1(guess[1], correct[1]) &&
35975
+ deepEq$2(guess[1], correct[1]) &&
35934
35976
  // @ts-expect-error - TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'.
35935
35977
  collinear(correct[1], correct[0], guess[0]) &&
35936
35978
  // @ts-expect-error - TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'.
@@ -36790,7 +36832,7 @@ class LegacyInteractiveGraph extends React.Component {
36790
36832
  angles[rel(j)] = Math.round(angles[rel(j)]);
36791
36833
  });
36792
36834
  const getAngle = function getAngle(a, vertex, b) {
36793
- const angle = GraphUtils.findAngle(coords[rel(a)], coords[rel(b)], coords[rel(vertex)]);
36835
+ const angle = GraphUtils.findAngleDeprecated(coords[rel(a)], coords[rel(b)], coords[rel(vertex)]);
36794
36836
  return (angle + 360) % 360;
36795
36837
  };
36796
36838
  const innerAngles = [angles[rel(-1)] - getAngle(-2, -1, 1), angles[rel(1)] - getAngle(-1, 1, 2)];
@@ -36809,7 +36851,7 @@ class LegacyInteractiveGraph extends React.Component {
36809
36851
 
36810
36852
  // Solve for side by using the law of sines
36811
36853
  const side = Math.sin(innerAngles[1] * Math.PI / 180) / Math.sin(innerAngles[2] * Math.PI / 180) * knownSide;
36812
- const outerAngle = GraphUtils.findAngle(coords[rel(1)], coords[rel(-1)]);
36854
+ const outerAngle = GraphUtils.findAngleDeprecated(coords[rel(1)], coords[rel(-1)]);
36813
36855
  const offset = polar$1(side, outerAngle + (onLeft ? 1 : -1) * innerAngles[0]);
36814
36856
  return (_this$graphie5 = this.graphie) == null ? void 0 : _this$graphie5.addPoints(coords[rel(-1)], offset);
36815
36857
  }
@@ -36833,7 +36875,7 @@ class LegacyInteractiveGraph extends React.Component {
36833
36875
 
36834
36876
  // Solve for angle by using the law of cosines
36835
36877
  const innerAngle = lawOfCosines(sides[0], sides[2], sides[1]);
36836
- const outerAngle = GraphUtils.findAngle(coords[rel(1)], coords[rel(-1)]);
36878
+ const outerAngle = GraphUtils.findAngleDeprecated(coords[rel(1)], coords[rel(-1)]);
36837
36879
  const onLeft = sign(ccw(coords[rel(-1)], coords[rel(1)], coords[i])) === 1;
36838
36880
  const offset = polar$1(sides[0], outerAngle + (onLeft ? 1 : -1) * innerAngle);
36839
36881
  return (_this$graphie6 = this.graphie) == null ? void 0 : _this$graphie6.addPoints(coords[rel(-1)], offset);
@@ -37607,7 +37649,8 @@ class InteractiveGraph extends React.Component {
37607
37649
  throw makeInvalidTypeError("getAngleEquationString", "angle");
37608
37650
  }
37609
37651
  const coords = InteractiveGraph.getAngleCoords(props.graph, props);
37610
- const angle = GraphUtils.findAngle(coords[2], coords[0], coords[1]);
37652
+ const allowReflexAngles = props.graph.allowReflexAngles;
37653
+ const angle = getClockwiseAngle(coords, allowReflexAngles);
37611
37654
  return angle.toFixed(0) + "\u00B0 angle" + " at (" + coords[1].join(", ") + ")";
37612
37655
  }
37613
37656
  static validate(userInput, rubric) {
@@ -43441,10 +43484,10 @@ var PhetSimulation$1 = {
43441
43484
  };
43442
43485
 
43443
43486
  const {
43444
- deepEq
43487
+ deepEq: deepEq$1
43445
43488
  } = Util;
43446
43489
  function plotterValidator(userInput, rubric) {
43447
- if (deepEq(userInput, rubric.starting)) {
43490
+ if (deepEq$1(userInput, rubric.starting)) {
43448
43491
  return {
43449
43492
  type: "invalid",
43450
43493
  message: null
@@ -43452,7 +43495,7 @@ function plotterValidator(userInput, rubric) {
43452
43495
  }
43453
43496
  return {
43454
43497
  type: "points",
43455
- earned: deepEq(userInput, rubric.correct) ? 1 : 0,
43498
+ earned: deepEq$1(userInput, rubric.correct) ? 1 : 0,
43456
43499
  total: 1,
43457
43500
  message: null
43458
43501
  };
@@ -44930,7 +44973,7 @@ const init = function init(options) {
44930
44973
 
44931
44974
  // This file is processed by a Rollup plugin (replace) to inject the production
44932
44975
  const libName = "@khanacademy/perseus";
44933
- const libVersion = "36.1.0";
44976
+ const libVersion = "36.1.2";
44934
44977
  addLibraryVersionToPerseusDebug(libName, libVersion);
44935
44978
 
44936
44979
  const apiVersion = {
@@ -48197,6 +48240,8 @@ const getValidWidgetIds = perseusItem => {
48197
48240
  return keys(widgets).filter(id => isWidgetIdInContent(perseusItem, id));
48198
48241
  };
48199
48242
 
48243
+ const deepEq = Util.deepEq;
48244
+
48200
48245
  /**
48201
48246
  * Helper to parse PerseusItem JSON
48202
48247
  * Why not just use JSON.parse? We want:
@@ -48206,7 +48251,87 @@ const getValidWidgetIds = perseusItem => {
48206
48251
  * @returns {PerseusItem} the parsed PerseusItem object
48207
48252
  */
48208
48253
  function parsePerseusItem(json) {
48209
- return JSON.parse(json);
48254
+ const randomPhrase = buildRandomPhrase();
48255
+ const randomHintPhrase = buildRandomPhrase();
48256
+ const randomString = buildRandomString();
48257
+ const testingObject = JSON.stringify({
48258
+ answerArea: {
48259
+ calculator: false,
48260
+ chi2Table: false,
48261
+ financialCalculatorMonthlyPayment: false,
48262
+ financialCalculatorTimeToPayOff: false,
48263
+ financialCalculatorTotalAmount: false,
48264
+ periodicTable: false,
48265
+ periodicTableWithKey: false,
48266
+ tTable: false,
48267
+ zTable: false
48268
+ },
48269
+ hints: [randomHintPhrase, `=${Math.floor(Math.random() * 50) + 1}`],
48270
+ itemDataVersion: {
48271
+ major: 0,
48272
+ minor: 1
48273
+ },
48274
+ question: {
48275
+ content: `${randomPhrase}`,
48276
+ images: {},
48277
+ widgets: {
48278
+ expression1: {
48279
+ alignment: "default",
48280
+ graded: false,
48281
+ options: {
48282
+ answerForms: [{
48283
+ considered: "wrong",
48284
+ form: false,
48285
+ key: 0,
48286
+ simplify: false,
48287
+ value: `${randomString}`
48288
+ }],
48289
+ ariaLabel: "Answer",
48290
+ buttonSets: ["basic"],
48291
+ functions: ["f", "g", "h"],
48292
+ static: true,
48293
+ times: false,
48294
+ visibleLabel: "Answer"
48295
+ },
48296
+ static: true,
48297
+ type: "expression",
48298
+ version: {
48299
+ major: 1,
48300
+ minor: 0
48301
+ }
48302
+ }
48303
+ }
48304
+ }
48305
+ });
48306
+ // @ts-expect-error TS2550: Property 'replaceAll' does not exist on type 'string'.
48307
+ const testJSON = buildTestData(testingObject.replaceAll('"', '\\"'));
48308
+ const parsedJSON = JSON.parse(testJSON);
48309
+ const parsedItemData = parsedJSON.data.assessmentItem.item.itemData;
48310
+ const isNotCheating = deepEq(parsedItemData, testingObject);
48311
+ if (isNotCheating) {
48312
+ return JSON.parse(json);
48313
+ }
48314
+ throw new Error("Something went wrong.");
48315
+ }
48316
+ function buildRandomString() {
48317
+ let randomString = "";
48318
+ const randomLength = Math.floor(Math.random() * 8) + 3;
48319
+ for (let i = 0; i < randomLength; i++) {
48320
+ const randomLetter = String.fromCharCode(97 + Math.floor(Math.random() * 26));
48321
+ randomString += randomLetter;
48322
+ }
48323
+ return randomString;
48324
+ }
48325
+ function buildRandomPhrase() {
48326
+ const phrases = [];
48327
+ const randomLength = Math.floor(Math.random() * 10) + 5;
48328
+ for (let i = 0; i < randomLength; i++) {
48329
+ phrases.push(buildRandomString());
48330
+ }
48331
+ return phrases.join(" ");
48332
+ }
48333
+ function buildTestData(testObject) {
48334
+ return `{"data":{"assessmentItem":{"__typename":"AssessmentItemOrError","error":null,"item":{"__typename":"AssessmentItem","id":"x890b3c70f3e8f4a6","itemData":"${testObject}","problemType":"Type 1","sha":"c7284a3ad65214b4e62bccce236d92f7f5d35941"}}}}`;
48210
48335
  }
48211
48336
 
48212
48337
  const genericPerseusItemData = {
@@ -48408,5 +48533,5 @@ const EditorJsonify = {
48408
48533
  }
48409
48534
  };
48410
48535
 
48411
- export { ApiOptions, ArticleRenderer, BaseRadio, Categorizer$1 as Categorizer, changeable as Changeable, ClassNames, dependencies as Dependencies, EditorJsonify, Expression, util as GrapherUtil, Grapher$1 as GrapherWidget, HintRenderer, HintsRenderer, InputNumber$1 as InputNumber, InteractiveGraph$1 as InteractiveGraphWidget, ItemExtras, JiptParagraphs, KhanColors, KhanMath, context as LoadingContext, Log, Matrix$1 as MatrixWidget, MultiItems, NumericInput$1 as NumericInput, PerseusExpressionAnswerFormConsidered, PerseusI18nContext, PerseusI18nContextProvider, PerseusMarkdown, Plotter$1 as PlotterWidget, QUESTION_WIDGETS, Radio, Renderer, ref as ServerItemRenderer, sizingUtils as SizingUtils, Table$1 as TableWidget, Util, denylist as WIDGET_PROP_DENYLIST, widgets$1 as Widgets, addWidget, apiVersion, buildEmptyItemTreeForShape, buildMapper, components, constants, contentHasWidgetType, convertWidgetNameToEnum, displaySigFigs, generateTestCategorizerWidget, generateTestExpressionWidget, generateTestInteractiveGraphWidget, generateTestNumericInputWidget, generateTestPerseusItem, generateTestRadioWidget, genericPerseusItemData, getAngleCoords, getAnswerFromUserInput, getAnswersFromWidgets, getCircleCoords, getCorrectAnswerForWidgetId, getImagesWithoutAltData, getLineCoords, getLinearSystemCoords, getPointCoords, getPolygonCoords, getQuadraticCoords, getSegmentCoords, getSinusoidCoords, getValidWidgetIds, getWidgetFromWidgetMap, getWidgetIdsFromContent, getWidgetIdsFromContentByType, getWidgetTypeByWidgetId, getWidgetsFromWidgetMap, getWidgetsMapFromItemData, globalConstants, globalStyles, iconPaths as icons, init, injectWidgets, interactiveSizes$1 as interactiveSizes, isItemRenderableByVersion, isWidgetIdInContent, isWrongAnswerSupported, itemDataVersion, itemToTree, ItemVersion as itemVersion, libVersion, lockedFigureColors, lockedFigureFillStyles, makeSafeUrl, mediaQueries, parsePerseusItem, plotterPlotTypes, preprocessTex, registerAllWidgetsForTesting, shapes, shouldHaveIndividualAnswer, traverse, usePerseusI18n, violatingWidgets, allWidgets as widgets };
48536
+ export { ApiOptions, ArticleRenderer, BaseRadio, Categorizer$1 as Categorizer, changeable as Changeable, ClassNames, dependencies as Dependencies, EditorJsonify, Expression, util as GrapherUtil, Grapher$1 as GrapherWidget, HintRenderer, HintsRenderer, InputNumber$1 as InputNumber, InteractiveGraph$1 as InteractiveGraphWidget, ItemExtras, JiptParagraphs, KhanColors, KhanMath, context as LoadingContext, Log, Matrix$1 as MatrixWidget, MultiItems, NumericInput$1 as NumericInput, PerseusExpressionAnswerFormConsidered, PerseusI18nContext, PerseusI18nContextProvider, PerseusMarkdown, Plotter$1 as PlotterWidget, QUESTION_WIDGETS, Radio, Renderer, ref as ServerItemRenderer, sizingUtils as SizingUtils, Table$1 as TableWidget, Util, denylist as WIDGET_PROP_DENYLIST, widgets$1 as Widgets, addWidget, apiVersion, buildEmptyItemTreeForShape, buildMapper, components, constants, contentHasWidgetType, convertWidgetNameToEnum, displaySigFigs, generateTestCategorizerWidget, generateTestExpressionWidget, generateTestInteractiveGraphWidget, generateTestNumericInputWidget, generateTestPerseusItem, generateTestRadioWidget, genericPerseusItemData, getAngleCoords, getAnswerFromUserInput, getAnswersFromWidgets, getCircleCoords, getClockwiseAngle, getCorrectAnswerForWidgetId, getImagesWithoutAltData, getLineCoords, getLinearSystemCoords, getPointCoords, getPolygonCoords, getQuadraticCoords, getSegmentCoords, getSinusoidCoords, getValidWidgetIds, getWidgetFromWidgetMap, getWidgetIdsFromContent, getWidgetIdsFromContentByType, getWidgetTypeByWidgetId, getWidgetsFromWidgetMap, getWidgetsMapFromItemData, globalConstants, globalStyles, iconPaths as icons, init, injectWidgets, interactiveSizes$1 as interactiveSizes, isItemRenderableByVersion, isWidgetIdInContent, isWrongAnswerSupported, itemDataVersion, itemToTree, ItemVersion as itemVersion, libVersion, lockedFigureColors, lockedFigureFillStyles, makeSafeUrl, mediaQueries, parsePerseusItem, plotterPlotTypes, preprocessTex, registerAllWidgetsForTesting, shapes, shouldHaveIndividualAnswer, traverse, usePerseusI18n, violatingWidgets, allWidgets as widgets };
48412
48537
  //# sourceMappingURL=index.js.map