@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/index.d.ts CHANGED
@@ -68,6 +68,7 @@ export { getWidgetTypeByWidgetId, contentHasWidgetType, getWidgetIdsFromContent,
68
68
  export { convertWidgetNameToEnum } from "./util/widget-enum-utils";
69
69
  export { addWidget, QUESTION_WIDGETS } from "./util/snowman-utils";
70
70
  export { getCircleCoords, getLineCoords, getLinearSystemCoords, getPointCoords, getPolygonCoords, getSegmentCoords, getSinusoidCoords, getQuadraticCoords, getAngleCoords, } from "./widgets/interactive-graphs/reducer/initialize-graph-state";
71
+ export { getClockwiseAngle } from "./widgets/interactive-graphs/math";
71
72
  export { makeSafeUrl } from "./widgets/phet-simulation";
72
73
  /**
73
74
  * Mixins
package/dist/index.js CHANGED
@@ -3263,13 +3263,13 @@ function eq$4(x, y) {
3263
3263
  * Deep approximate equality on primitives, numbers, arrays, and objects.
3264
3264
  * Recursive.
3265
3265
  */
3266
- function deepEq$5(x, y) {
3266
+ function deepEq$6(x, y) {
3267
3267
  if (Array.isArray(x) && Array.isArray(y)) {
3268
3268
  if (x.length !== y.length) {
3269
3269
  return false;
3270
3270
  }
3271
3271
  for (let i = 0; i < x.length; i++) {
3272
- if (!deepEq$5(x[i], y[i])) {
3272
+ if (!deepEq$6(x[i], y[i])) {
3273
3273
  return false;
3274
3274
  }
3275
3275
  }
@@ -3287,10 +3287,10 @@ function deepEq$5(x, y) {
3287
3287
  if (typeof x === "object" && typeof y === "object" && !!x && !!y) {
3288
3288
  return x === y || ___default["default"].all(x, function (v, k) {
3289
3289
  // @ts-expect-error - TS2536 - Type 'CollectionKey<T>' cannot be used to index type 'T'.
3290
- return deepEq$5(y[k], v);
3290
+ return deepEq$6(y[k], v);
3291
3291
  }) && ___default["default"].all(y, function (v, k) {
3292
3292
  // @ts-expect-error - TS2536 - Type 'CollectionKey<T>' cannot be used to index type 'T'.
3293
- return deepEq$5(x[k], v);
3293
+ return deepEq$6(x[k], v);
3294
3294
  });
3295
3295
  }
3296
3296
  if (typeof x === "object" && !!x || typeof y === "object" && !!y) {
@@ -3568,7 +3568,7 @@ const Util = {
3568
3568
  tickStepFromNumTicks,
3569
3569
  constrainedTickStepsFromTickSteps,
3570
3570
  eq: eq$4,
3571
- deepEq: deepEq$5,
3571
+ deepEq: deepEq$6,
3572
3572
  parseQueryString,
3573
3573
  updateQueryString: updateQueryString$2,
3574
3574
  strongEncodeURIComponent,
@@ -10176,7 +10176,10 @@ const GraphUtils = {
10176
10176
  return kmath.point.distanceToPoint(point1, point2);
10177
10177
  },
10178
10178
  // Find the angle in degrees between two or three points
10179
- findAngle: function (point1, point2, vertex) {
10179
+ // This function is deprecated as it has several issues calculating
10180
+ // correctly when dealing with reflex angles or the position of point2
10181
+ // (LEMS-2202) Remove this function while removing the Legacy Interactive Graph.
10182
+ findAngleDeprecated: function (point1, point2, vertex) {
10180
10183
  if (vertex === undefined) {
10181
10184
  const x = point1[0] - point2[0];
10182
10185
  const y = point1[1] - point2[1];
@@ -10185,7 +10188,7 @@ const GraphUtils = {
10185
10188
  }
10186
10189
  return (180 + Math.atan2(-y, -x) * 180 / Math.PI + 360) % 360;
10187
10190
  }
10188
- return GraphUtils.findAngle(point1, vertex) - GraphUtils.findAngle(point2, vertex);
10191
+ return GraphUtils.findAngleDeprecated(point1, vertex) - GraphUtils.findAngleDeprecated(point2, vertex);
10189
10192
  },
10190
10193
  graphs: {}
10191
10194
  };
@@ -10892,7 +10895,7 @@ class WrappedLine extends WrappedDrawing {
10892
10895
  */
10893
10896
  const {
10894
10897
  eq: eq$3,
10895
- deepEq: deepEq$4
10898
+ deepEq: deepEq$5
10896
10899
  } = Util;
10897
10900
 
10898
10901
  // This should really be a readonly tuple of [number, number]
@@ -11052,7 +11055,7 @@ function similar(coords1, coords2, tolerance) {
11052
11055
  angles = rotate(angles, i);
11053
11056
  // @ts-expect-error - TS4104 - The type 'readonly number[]' is 'readonly' and cannot be assigned to the mutable type 'number[]'.
11054
11057
  sides = rotate(sides, i);
11055
- if (deepEq$4(angles1, angles)) {
11058
+ if (deepEq$5(angles1, angles)) {
11056
11059
  const sidePairs = ___default["default"].zip(sides1, sides);
11057
11060
  const factors = ___default["default"].map(sidePairs, function (pair) {
11058
11061
  return pair[0] / pair[1];
@@ -11202,8 +11205,8 @@ ___default["default"].extend(GraphUtils.Graphie.prototype, {
11202
11205
  p1 = options.point3;
11203
11206
  p3 = options.point1;
11204
11207
  }
11205
- const startAngle = GraphUtils.findAngle(p1, vertex);
11206
- const endAngle = GraphUtils.findAngle(p3, vertex);
11208
+ const startAngle = GraphUtils.findAngleDeprecated(p1, vertex);
11209
+ const endAngle = GraphUtils.findAngleDeprecated(p3, vertex);
11207
11210
  const angle = (endAngle + 360 - startAngle) % 360;
11208
11211
  const halfAngle = (startAngle + angle / 2) % 360;
11209
11212
  const sPadding = 5 * options.pushOut;
@@ -11370,15 +11373,15 @@ ___default["default"].extend(GraphUtils.Graphie.prototype, {
11370
11373
  let angle = 135;
11371
11374
  let halfAngle;
11372
11375
  if (p1 && p3) {
11373
- const startAngle = GraphUtils.findAngle(p1, vertex);
11374
- const endAngle = GraphUtils.findAngle(p3, vertex);
11376
+ const startAngle = GraphUtils.findAngleDeprecated(p1, vertex);
11377
+ const endAngle = GraphUtils.findAngleDeprecated(p3, vertex);
11375
11378
  angle = (endAngle + 360 - startAngle) % 360;
11376
11379
  halfAngle = (startAngle + angle / 2 + 180) % 360;
11377
11380
  } else if (p1) {
11378
- const parallelAngle = GraphUtils.findAngle(vertex, p1);
11381
+ const parallelAngle = GraphUtils.findAngleDeprecated(vertex, p1);
11379
11382
  halfAngle = parallelAngle + 90;
11380
11383
  } else if (p3) {
11381
- const parallelAngle = GraphUtils.findAngle(p3, vertex);
11384
+ const parallelAngle = GraphUtils.findAngleDeprecated(p3, vertex);
11382
11385
  halfAngle = parallelAngle + 90;
11383
11386
  } else {
11384
11387
  // Standalone point
@@ -11584,7 +11587,7 @@ ___default["default"].extend(GraphUtils.Graphie.prototype, {
11584
11587
  const vertex = constraints.fixedAngle.vertex.coord || constraints.fixedAngle.vertex;
11585
11588
  const ref = constraints.fixedAngle.ref.coord || constraints.fixedAngle.ref;
11586
11589
  const distPoint = constraints.fixedDistance.point.coord || constraints.fixedDistance.point;
11587
- const constrainedAngle = (constraints.fixedAngle.angle + GraphUtils.findAngle(ref, vertex)) * Math.PI / 180;
11590
+ const constrainedAngle = (constraints.fixedAngle.angle + GraphUtils.findAngleDeprecated(ref, vertex)) * Math.PI / 180;
11588
11591
  const length = constraints.fixedDistance.dist;
11589
11592
  newCoord[0] = length * Math.cos(constrainedAngle) + distPoint[0];
11590
11593
  newCoord[1] = length * Math.sin(constrainedAngle) + distPoint[1];
@@ -11593,8 +11596,8 @@ ___default["default"].extend(GraphUtils.Graphie.prototype, {
11593
11596
  } else if (typeof constraints.fixedAngle.angle === "number") {
11594
11597
  const vertex = constraints.fixedAngle.vertex.coord || constraints.fixedAngle.vertex;
11595
11598
  const ref = constraints.fixedAngle.ref.coord || constraints.fixedAngle.ref;
11596
- const constrainedAngle = (constraints.fixedAngle.angle + GraphUtils.findAngle(ref, vertex)) * Math.PI / 180;
11597
- const angle = GraphUtils.findAngle(coord, vertex) * Math.PI / 180;
11599
+ const constrainedAngle = (constraints.fixedAngle.angle + GraphUtils.findAngleDeprecated(ref, vertex)) * Math.PI / 180;
11600
+ const angle = GraphUtils.findAngleDeprecated(coord, vertex) * Math.PI / 180;
11598
11601
  const distance = GraphUtils.getDistance(coord, vertex);
11599
11602
  let length = distance * Math.cos(constrainedAngle - angle);
11600
11603
  length = length < 1.0 ? 1.0 : length;
@@ -11604,7 +11607,7 @@ ___default["default"].extend(GraphUtils.Graphie.prototype, {
11604
11607
  // distance is constrained
11605
11608
  } else if (typeof constraints.fixedDistance.dist === "number") {
11606
11609
  const distPoint = constraints.fixedDistance.point.coord || constraints.fixedDistance.point;
11607
- let angle = GraphUtils.findAngle(coord, distPoint);
11610
+ let angle = GraphUtils.findAngleDeprecated(coord, distPoint);
11608
11611
  const length = constraints.fixedDistance.dist;
11609
11612
  angle = angle * Math.PI / 180;
11610
11613
  newCoord[0] = length * Math.cos(angle) + distPoint[0];
@@ -13804,7 +13807,7 @@ ___default["default"].extend(MovableAngle.prototype, {
13804
13807
  ___default["default"].each([0, 2], function (i) {
13805
13808
  const oldPoint = points[i].coord;
13806
13809
  let newPoint = kmath.vector.add(oldPoint, delta);
13807
- let angle = GraphUtils.findAngle(newVertex, newPoint);
13810
+ let angle = GraphUtils.findAngleDeprecated(newVertex, newPoint);
13808
13811
  angle *= Math.PI / 180;
13809
13812
  newPoint = graphie.constrainToBoundsOnAngle(newPoint, 10, angle);
13810
13813
  newPoints[i] = newPoint;
@@ -13837,7 +13840,7 @@ ___default["default"].extend(MovableAngle.prototype, {
13837
13840
  return false;
13838
13841
  }
13839
13842
  if (snap) {
13840
- let angle = GraphUtils.findAngle(newPoint, vertex);
13843
+ let angle = GraphUtils.findAngleDeprecated(newPoint, vertex);
13841
13844
  angle = Math.round((angle - snapOffset) / snap) * snap + snapOffset;
13842
13845
  const distance = GraphUtils.getDistance(newPoint, vertex);
13843
13846
  return kmath.vector.add(vertex, polar$1(distance, angle));
@@ -13925,7 +13928,7 @@ ___default["default"].extend(MovableAngle.prototype, {
13925
13928
  * coords when interpreted in a clockwise direction.
13926
13929
  */
13927
13930
  _getClockwiseAngle: function (coords) {
13928
- const rawAngle = GraphUtils.findAngle(
13931
+ const rawAngle = GraphUtils.findAngleDeprecated(
13929
13932
  // The order of these is "weird" to match what a clockwise
13930
13933
  // order is in graphie.labelAngle
13931
13934
  coords[2],
@@ -13991,7 +13994,7 @@ ___default["default"].extend(MovableAngle.prototype, {
13991
13994
 
13992
13995
  /* eslint-disable @babel/no-invalid-this */
13993
13996
  const nestedMap$1 = Util.nestedMap;
13994
- const deepEq$3 = Util.deepEq;
13997
+ const deepEq$4 = Util.deepEq;
13995
13998
 
13996
13999
  /**
13997
14000
  * A base class for all Graphie Movables
@@ -14088,7 +14091,7 @@ const createSimpleClass = function (addFunction) {
14088
14091
  this._prevProps = this.props;
14089
14092
  },
14090
14093
  modify: function (graphie) {
14091
- if (!deepEq$3(this.props, this._prevProps)) {
14094
+ if (!deepEq$4(this.props, this._prevProps)) {
14092
14095
  this.remove();
14093
14096
  this.add(graphie);
14094
14097
  this._prevProps = this.props;
@@ -16835,7 +16838,7 @@ var Movables = {
16835
16838
  const GraphieMovable = GraphieClasses.GraphieMovable;
16836
16839
  const createGraphie = GraphUtils.createGraphie;
16837
16840
  const {
16838
- deepEq: deepEq$2,
16841
+ deepEq: deepEq$3,
16839
16842
  nestedMap
16840
16843
  } = Util;
16841
16844
  const {
@@ -16873,7 +16876,7 @@ class Graphie extends React__namespace.Component {
16873
16876
  if (this.props.setup !== prevProps.setup) {
16874
16877
  Log.error("<Graphie> was given a new setup function. " + "This is a bad idea; please refactor your code to give " + "the same setup function reference to <Graphie> on " + "every render.", perseusCore.Errors.Internal);
16875
16878
  }
16876
- if (!deepEq$2(this.props.options, prevProps.options) || !deepEq$2(this.props.box, prevProps.box) || !deepEq$2(this.props.range, prevProps.range)) {
16879
+ if (!deepEq$3(this.props.options, prevProps.options) || !deepEq$3(this.props.box, prevProps.box) || !deepEq$3(this.props.range, prevProps.range)) {
16877
16880
  this._setupGraphie();
16878
16881
  }
16879
16882
  this._updateMovables();
@@ -30975,16 +30978,40 @@ function polar(r, th) {
30975
30978
  th = th * Math.PI / 180;
30976
30979
  return [r[0] * Math.cos(th), r[1] * Math.sin(th)];
30977
30980
  }
30978
- const findAngle = (point1, point2, vertex) => {
30979
- if (vertex === undefined) {
30980
- const x = point1[0] - point2[0];
30981
- const y = point1[1] - point2[1];
30982
- if (!x && !y) {
30983
- return 0;
30984
- }
30985
- return (180 + Math.atan2(-y, -x) * 180 / Math.PI + 360) % 360;
30981
+ // This function calculates the angle between two points and an optional vertex.
30982
+ // If the vertex is not provided, the angle is measured between the two points.
30983
+ // This does not account for reflex angles or clockwise position.
30984
+ const getAngleFromVertex = (point, vertex) => {
30985
+ const x = point[0] - vertex[0];
30986
+ const y = point[1] - vertex[1];
30987
+ if (!x && !y) {
30988
+ return 0;
30986
30989
  }
30987
- return findAngle(point1, vertex) - findAngle(point2, vertex);
30990
+ return (180 + Math.atan2(-y, -x) * 180 / Math.PI + 360) % 360;
30991
+ };
30992
+
30993
+ // This function calculates the clockwise angle between three points,
30994
+ // and is used to generate the labels and equation strings of the
30995
+ // current angle for the interactive graph.
30996
+ const getClockwiseAngle = function (coords) {
30997
+ let allowReflexAngles = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
30998
+ const coordsCopy = [...coords];
30999
+ // The coords are saved as [point1, vertex, point2] in the interactive graph
31000
+ const areClockwise = clockwise$1([coordsCopy[0], coordsCopy[2], coordsCopy[1]]);
31001
+
31002
+ // We may need to reverse the coordinates if we allow
31003
+ // reflex angles and the points are not in clockwise order.
31004
+ const shouldReverseCoords = !areClockwise || allowReflexAngles;
31005
+
31006
+ // Reverse the coordinates accordingly to ensure the angle is calculated correctly
31007
+ const clockwiseCoords = shouldReverseCoords ? coordsCopy.reverse() : coordsCopy;
31008
+
31009
+ // Calculate the angles between the two points and get the difference
31010
+ // between the two angles to get the clockwise angle.
31011
+ const startAngle = getAngleFromVertex(clockwiseCoords[0], clockwiseCoords[1]);
31012
+ const endAngle = getAngleFromVertex(clockwiseCoords[2], clockwiseCoords[1]);
31013
+ const angle = (startAngle + 360 - endAngle) % 360;
31014
+ return angle;
30988
31015
  };
30989
31016
 
30990
31017
  const defaultGraphConfig = {
@@ -31299,10 +31326,16 @@ const shouldShowLabel = (currentTick, range, tickStep) => {
31299
31326
  function generateTickLocations(tickStep, min, max) {
31300
31327
  const ticks = [];
31301
31328
 
31329
+ // Calculate the number of significant decimals in the tick step so
31330
+ // that we can match the desired precision when generating ticks.
31331
+ const decimalSigFigs = countSignificantDecimals(tickStep);
31332
+
31302
31333
  // Add ticks in the positive direction
31303
31334
  const start = Math.max(min, 0);
31304
31335
  for (let i = start + tickStep; i < max; i += tickStep) {
31305
- ticks.push(i);
31336
+ // Match to the same number of decimal places as the tick step
31337
+ // to avoid floating point errors when working with small numbers
31338
+ ticks.push(parseFloat(i.toFixed(decimalSigFigs)));
31306
31339
  }
31307
31340
 
31308
31341
  // Add ticks in the negative direction
@@ -31313,6 +31346,15 @@ function generateTickLocations(tickStep, min, max) {
31313
31346
  }
31314
31347
  return ticks;
31315
31348
  }
31349
+
31350
+ // Count the number of significant digits after the decimal point
31351
+ const countSignificantDecimals = number => {
31352
+ const numStr = number.toString();
31353
+ if (!numStr.includes(".")) {
31354
+ return 0;
31355
+ }
31356
+ return numStr.split(".")[1].length;
31357
+ };
31316
31358
  const AxisTicks = () => {
31317
31359
  const {
31318
31360
  tickStep,
@@ -33401,8 +33443,8 @@ const Angle = _ref2 => {
33401
33443
  const clockwiseCoords = shouldReverseCoords ? coords : coords.reverse();
33402
33444
 
33403
33445
  // Calculate the angles between the two points
33404
- const startAngle = findAngle(clockwiseCoords[0], vertex);
33405
- const endAngle = findAngle(clockwiseCoords[1], vertex);
33446
+ const startAngle = getAngleFromVertex(clockwiseCoords[0], vertex);
33447
+ const endAngle = getAngleFromVertex(clockwiseCoords[1], vertex);
33406
33448
  const angle = (startAngle + 360 - endAngle) % 360;
33407
33449
 
33408
33450
  // Check if the angle is reflexive
@@ -34491,7 +34533,9 @@ const MafsGraph = props => {
34491
34533
  analytics.onAnalyticsEvent({
34492
34534
  type: "perseus:interactive-graph-widget:rendered",
34493
34535
  payload: {
34494
- type
34536
+ type,
34537
+ widgetType: "INTERACTIVE_GRAPH",
34538
+ widgetId: "interactive-graph"
34495
34539
  }
34496
34540
  });
34497
34541
  });
@@ -35778,7 +35822,7 @@ function boundAndSnapAngleVertex(_ref2, _ref3) {
35778
35822
  for (const i of [0, 2]) {
35779
35823
  const oldPoint = coordsCopy[i];
35780
35824
  let newPoint = mafs.vec.add(oldPoint, delta);
35781
- let angle = findAngle(newVertex, newPoint);
35825
+ let angle = getAngleFromVertex(newVertex, newPoint);
35782
35826
  angle *= Math.PI / 180;
35783
35827
  newPoint = constrainToBoundsOnAngle(newPoint, angle, range, snapStep);
35784
35828
  newPoints[i] = newPoint;
@@ -35851,7 +35895,7 @@ function boundAndSnapAngleEndPoints(destinationPoint, _ref5, index) {
35851
35895
  const vertex = coords[1];
35852
35896
 
35853
35897
  // Gets the angle between the coords and the vertex
35854
- let angle = findAngle(coordsCopy[index], vertex);
35898
+ let angle = getAngleFromVertex(coordsCopy[index], vertex);
35855
35899
 
35856
35900
  // Snap the angle to the nearest multiple of snapDegrees (if provided)
35857
35901
  angle = Math.round((angle - offsetDegrees) / snap) * snap + offsetDegrees;
@@ -35898,8 +35942,8 @@ function boundAndSnapToPolygonAngle(destinationPoint, _ref6, index) {
35898
35942
  angles[rel(j)] = Math.round(angles[rel(j)]);
35899
35943
  });
35900
35944
  const getAngle = function (a, vertex, b) {
35901
- const angle = findAngle(coordsCopy[rel(a)], coordsCopy[rel(b)], coordsCopy[rel(vertex)]);
35902
- return (angle + 360) % 360;
35945
+ const angle = getClockwiseAngle([coordsCopy[rel(a)], coordsCopy[rel(vertex)], coordsCopy[rel(b)]]);
35946
+ return angle;
35903
35947
  };
35904
35948
  const innerAngles = [angles[rel(-1)] - getAngle(-2, -1, 1), angles[rel(1)] - getAngle(-1, 1, 2)];
35905
35949
  innerAngles[2] = 180 - (innerAngles[0] + innerAngles[1]);
@@ -35926,7 +35970,7 @@ function boundAndSnapToPolygonAngle(destinationPoint, _ref6, index) {
35926
35970
  const side = Math.sin(innerAngles[1] * Math.PI / 180) / Math.sin(innerAngles[2] * Math.PI / 180) * knownSide;
35927
35971
 
35928
35972
  // Angle at the second vertex of the polygon
35929
- const outerAngle = findAngle(coordsCopy[rel(1)], coordsCopy[rel(-1)]);
35973
+ const outerAngle = getAngleFromVertex(coordsCopy[rel(1)], coordsCopy[rel(-1)]);
35930
35974
 
35931
35975
  // Uses the length of the side of the polygon (radial coordinate)
35932
35976
  // and the angle between the first and second sides of the
@@ -35977,7 +36021,7 @@ function boundAndSnapToSides(destinationPoint, _ref7, index) {
35977
36021
  const innerAngle = lawOfCosines(sides[0], sides[2], sides[1]);
35978
36022
 
35979
36023
  // Angle at the second vertex of the polygon
35980
- const outerAngle = findAngle(coordsCopy[rel(1)], coordsCopy[rel(-1)]);
36024
+ const outerAngle = getAngleFromVertex(coordsCopy[rel(1)], coordsCopy[rel(-1)]);
35981
36025
 
35982
36026
  // Returns true if the points form a counter-clockwise turn;
35983
36027
  // a.k.a if the point is on the left or right of the polygon.
@@ -36086,7 +36130,8 @@ const StatefulMafsGraph = /*#__PURE__*/React__namespace.forwardRef((props, ref)
36086
36130
  const numPoints = graph.type === "point" ? graph.numPoints : null;
36087
36131
  const numSides = graph.type === "polygon" ? graph.numSides : null;
36088
36132
  const snapTo = graph.type === "polygon" ? graph.snapTo : null;
36089
- const showAngles = graph.type === "polygon" ? graph.showAngles : null;
36133
+ const showAngles = graph.type === "polygon" || graph.type === "angle" ? graph.showAngles : null;
36134
+ const allowReflexAngles = graph.type === "angle" ? graph.allowReflexAngles : null;
36090
36135
  const showSides = graph.type === "polygon" ? graph.showSides : null;
36091
36136
  const startCoords = "startCoords" in graph ? graph.startCoords : undefined;
36092
36137
  const originalPropsRef = React.useRef(props);
@@ -36099,7 +36144,7 @@ const StatefulMafsGraph = /*#__PURE__*/React__namespace.forwardRef((props, ref)
36099
36144
  if (latestPropsRef.current !== originalPropsRef.current) {
36100
36145
  dispatch(reinitialize(latestPropsRef.current));
36101
36146
  }
36102
- }, [graph.type, numPoints, numSegments, numSides, snapTo, showAngles, showSides, latestPropsRef, startCoords]);
36147
+ }, [graph.type, numPoints, numSegments, numSides, snapTo, showAngles, showSides, latestPropsRef, startCoords, allowReflexAngles]);
36103
36148
 
36104
36149
  // If the graph is static, it always displays the correct answer. This is
36105
36150
  // standard behavior for Perseus widgets (e.g. compare the Radio widget).
@@ -36119,7 +36164,7 @@ const StatefulMafsGraph = /*#__PURE__*/React__namespace.forwardRef((props, ref)
36119
36164
  });
36120
36165
 
36121
36166
  const eq$1 = Util.eq;
36122
- const deepEq$1 = Util.deepEq;
36167
+ const deepEq$2 = Util.deepEq;
36123
36168
  function interactiveGraphValidator(userInput, rubric) {
36124
36169
  // None-type graphs are not graded
36125
36170
  if (userInput.type === "none" && rubric.correct.type === "none") {
@@ -36175,7 +36220,7 @@ function interactiveGraphValidator(userInput, rubric) {
36175
36220
  const correctCoeffs = getQuadraticCoefficients(
36176
36221
  // @ts-expect-error - TS2345 - Argument of type 'readonly Coord[] | undefined' is not assignable to parameter of type 'readonly Coord[]'.
36177
36222
  rubric.correct.coords);
36178
- if (deepEq$1(guessCoeffs, correctCoeffs)) {
36223
+ if (deepEq$2(guessCoeffs, correctCoeffs)) {
36179
36224
  return {
36180
36225
  type: "points",
36181
36226
  earned: 1,
@@ -36191,7 +36236,7 @@ function interactiveGraphValidator(userInput, rubric) {
36191
36236
  const canonicalGuessCoeffs = canonicalSineCoefficients$1(guessCoeffs);
36192
36237
  const canonicalCorrectCoeffs = canonicalSineCoefficients$1(correctCoeffs);
36193
36238
  // If the canonical coefficients match, it's correct.
36194
- if (deepEq$1(canonicalGuessCoeffs, canonicalCorrectCoeffs)) {
36239
+ if (deepEq$2(canonicalGuessCoeffs, canonicalCorrectCoeffs)) {
36195
36240
  return {
36196
36241
  type: "points",
36197
36242
  earned: 1,
@@ -36200,7 +36245,7 @@ function interactiveGraphValidator(userInput, rubric) {
36200
36245
  };
36201
36246
  }
36202
36247
  } else if (userInput.type === "circle" && rubric.correct.type === "circle") {
36203
- if (deepEq$1(userInput.center, rubric.correct.center) && eq$1(userInput.radius, rubric.correct.radius)) {
36248
+ if (deepEq$2(userInput.center, rubric.correct.center) && eq$1(userInput.radius, rubric.correct.radius)) {
36204
36249
  return {
36205
36250
  type: "points",
36206
36251
  earned: 1,
@@ -36222,7 +36267,7 @@ function interactiveGraphValidator(userInput, rubric) {
36222
36267
  guess?.sort();
36223
36268
  // @ts-expect-error - TS2339 - Property 'sort' does not exist on type 'readonly Coord[]'.
36224
36269
  correct.sort();
36225
- if (deepEq$1(guess, correct)) {
36270
+ if (deepEq$2(guess, correct)) {
36226
36271
  return {
36227
36272
  type: "points",
36228
36273
  earned: 1,
@@ -36245,7 +36290,7 @@ function interactiveGraphValidator(userInput, rubric) {
36245
36290
  /* exact */
36246
36291
  guess.sort();
36247
36292
  correct.sort();
36248
- match = deepEq$1(guess, correct);
36293
+ match = deepEq$2(guess, correct);
36249
36294
  }
36250
36295
  if (match) {
36251
36296
  return {
@@ -36261,7 +36306,7 @@ function interactiveGraphValidator(userInput, rubric) {
36261
36306
  guess = ___default["default"].invoke(guess, "sort").sort();
36262
36307
  // @ts-expect-error - TS2345 - Argument of type '(readonly Coord[])[] | undefined' is not assignable to parameter of type 'Collection<any>'.
36263
36308
  correct = ___default["default"].invoke(correct, "sort").sort();
36264
- if (deepEq$1(guess, correct)) {
36309
+ if (deepEq$2(guess, correct)) {
36265
36310
  return {
36266
36311
  type: "points",
36267
36312
  earned: 1,
@@ -36274,7 +36319,7 @@ function interactiveGraphValidator(userInput, rubric) {
36274
36319
  const correct = rubric.correct.coords;
36275
36320
  if (
36276
36321
  // @ts-expect-error - TS2532 - Object is possibly 'undefined'.
36277
- deepEq$1(guess[0], correct[0]) &&
36322
+ deepEq$2(guess[0], correct[0]) &&
36278
36323
  // @ts-expect-error - TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'.
36279
36324
  collinear(correct[0], correct[1], guess[1])) {
36280
36325
  return {
@@ -36287,17 +36332,15 @@ function interactiveGraphValidator(userInput, rubric) {
36287
36332
  } else if (userInput.type === "angle" && rubric.correct.type === "angle") {
36288
36333
  const guess = userInput.coords;
36289
36334
  const correct = rubric.correct.coords;
36335
+ const allowReflexAngles = rubric.correct.allowReflexAngles;
36290
36336
  let match;
36291
36337
  if (rubric.correct.match === "congruent") {
36292
36338
  const angles = ___default["default"].map([guess, correct], function (coords) {
36293
- const angle = GraphUtils.findAngle(
36294
- // @ts-expect-error - TS2532 - Object is possibly 'undefined'.
36295
- coords[2],
36296
- // @ts-expect-error - TS2532 - Object is possibly 'undefined'.
36297
- coords[0],
36298
- // @ts-expect-error - TS2532 - Object is possibly 'undefined'.
36299
- coords[1]);
36300
- return (angle + 360) % 360;
36339
+ if (!coords) {
36340
+ return false;
36341
+ }
36342
+ const angle = getClockwiseAngle(coords, allowReflexAngles);
36343
+ return angle;
36301
36344
  });
36302
36345
  // @ts-expect-error - TS2556 - A spread argument must either have a tuple type or be passed to a rest parameter.
36303
36346
  match = eq$1(...angles);
@@ -36305,7 +36348,7 @@ function interactiveGraphValidator(userInput, rubric) {
36305
36348
  /* exact */
36306
36349
  match =
36307
36350
  // @ts-expect-error - TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'.
36308
- deepEq$1(guess[1], correct[1]) &&
36351
+ deepEq$2(guess[1], correct[1]) &&
36309
36352
  // @ts-expect-error - TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'.
36310
36353
  collinear(correct[1], correct[0], guess[0]) &&
36311
36354
  // @ts-expect-error - TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'.
@@ -37200,7 +37243,7 @@ class LegacyInteractiveGraph extends React__namespace.Component {
37200
37243
  angles[rel(j)] = Math.round(angles[rel(j)]);
37201
37244
  });
37202
37245
  const getAngle = function (a, vertex, b) {
37203
- const angle = GraphUtils.findAngle(coords[rel(a)], coords[rel(b)], coords[rel(vertex)]);
37246
+ const angle = GraphUtils.findAngleDeprecated(coords[rel(a)], coords[rel(b)], coords[rel(vertex)]);
37204
37247
  return (angle + 360) % 360;
37205
37248
  };
37206
37249
  const innerAngles = [angles[rel(-1)] - getAngle(-2, -1, 1), angles[rel(1)] - getAngle(-1, 1, 2)];
@@ -37219,7 +37262,7 @@ class LegacyInteractiveGraph extends React__namespace.Component {
37219
37262
 
37220
37263
  // Solve for side by using the law of sines
37221
37264
  const side = Math.sin(innerAngles[1] * Math.PI / 180) / Math.sin(innerAngles[2] * Math.PI / 180) * knownSide;
37222
- const outerAngle = GraphUtils.findAngle(coords[rel(1)], coords[rel(-1)]);
37265
+ const outerAngle = GraphUtils.findAngleDeprecated(coords[rel(1)], coords[rel(-1)]);
37223
37266
  const offset = polar$1(side, outerAngle + (onLeft ? 1 : -1) * innerAngles[0]);
37224
37267
  return this.graphie?.addPoints(coords[rel(-1)], offset);
37225
37268
  }
@@ -37242,7 +37285,7 @@ class LegacyInteractiveGraph extends React__namespace.Component {
37242
37285
 
37243
37286
  // Solve for angle by using the law of cosines
37244
37287
  const innerAngle = lawOfCosines(sides[0], sides[2], sides[1]);
37245
- const outerAngle = GraphUtils.findAngle(coords[rel(1)], coords[rel(-1)]);
37288
+ const outerAngle = GraphUtils.findAngleDeprecated(coords[rel(1)], coords[rel(-1)]);
37246
37289
  const onLeft = sign(ccw(coords[rel(-1)], coords[rel(1)], coords[i])) === 1;
37247
37290
  const offset = polar$1(sides[0], outerAngle + (onLeft ? 1 : -1) * innerAngle);
37248
37291
  return this.graphie?.addPoints(coords[rel(-1)], offset);
@@ -37966,7 +38009,8 @@ class InteractiveGraph extends React__namespace.Component {
37966
38009
  throw makeInvalidTypeError("getAngleEquationString", "angle");
37967
38010
  }
37968
38011
  const coords = InteractiveGraph.getAngleCoords(props.graph, props);
37969
- const angle = GraphUtils.findAngle(coords[2], coords[0], coords[1]);
38012
+ const allowReflexAngles = props.graph.allowReflexAngles;
38013
+ const angle = getClockwiseAngle(coords, allowReflexAngles);
37970
38014
  return angle.toFixed(0) + "\u00B0 angle" + " at (" + coords[1].join(", ") + ")";
37971
38015
  }
37972
38016
  static validate(userInput, rubric) {
@@ -43806,10 +43850,10 @@ var PhetSimulation$1 = {
43806
43850
  };
43807
43851
 
43808
43852
  const {
43809
- deepEq
43853
+ deepEq: deepEq$1
43810
43854
  } = Util;
43811
43855
  function plotterValidator(userInput, rubric) {
43812
- if (deepEq(userInput, rubric.starting)) {
43856
+ if (deepEq$1(userInput, rubric.starting)) {
43813
43857
  return {
43814
43858
  type: "invalid",
43815
43859
  message: null
@@ -43817,7 +43861,7 @@ function plotterValidator(userInput, rubric) {
43817
43861
  }
43818
43862
  return {
43819
43863
  type: "points",
43820
- earned: deepEq(userInput, rubric.correct) ? 1 : 0,
43864
+ earned: deepEq$1(userInput, rubric.correct) ? 1 : 0,
43821
43865
  total: 1,
43822
43866
  message: null
43823
43867
  };
@@ -45294,7 +45338,7 @@ const init = function (options) {
45294
45338
 
45295
45339
  // This file is processed by a Rollup plugin (replace) to inject the production
45296
45340
  const libName = "@khanacademy/perseus";
45297
- const libVersion = "36.1.0";
45341
+ const libVersion = "36.1.2";
45298
45342
  perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
45299
45343
 
45300
45344
  const apiVersion = {
@@ -48572,6 +48616,8 @@ const getValidWidgetIds = perseusItem => {
48572
48616
  return wonderStuffCore.keys(widgets).filter(id => isWidgetIdInContent(perseusItem, id));
48573
48617
  };
48574
48618
 
48619
+ const deepEq = Util.deepEq;
48620
+
48575
48621
  /**
48576
48622
  * Helper to parse PerseusItem JSON
48577
48623
  * Why not just use JSON.parse? We want:
@@ -48581,7 +48627,87 @@ const getValidWidgetIds = perseusItem => {
48581
48627
  * @returns {PerseusItem} the parsed PerseusItem object
48582
48628
  */
48583
48629
  function parsePerseusItem(json) {
48584
- return JSON.parse(json);
48630
+ const randomPhrase = buildRandomPhrase();
48631
+ const randomHintPhrase = buildRandomPhrase();
48632
+ const randomString = buildRandomString();
48633
+ const testingObject = JSON.stringify({
48634
+ answerArea: {
48635
+ calculator: false,
48636
+ chi2Table: false,
48637
+ financialCalculatorMonthlyPayment: false,
48638
+ financialCalculatorTimeToPayOff: false,
48639
+ financialCalculatorTotalAmount: false,
48640
+ periodicTable: false,
48641
+ periodicTableWithKey: false,
48642
+ tTable: false,
48643
+ zTable: false
48644
+ },
48645
+ hints: [randomHintPhrase, `=${Math.floor(Math.random() * 50) + 1}`],
48646
+ itemDataVersion: {
48647
+ major: 0,
48648
+ minor: 1
48649
+ },
48650
+ question: {
48651
+ content: `${randomPhrase}`,
48652
+ images: {},
48653
+ widgets: {
48654
+ expression1: {
48655
+ alignment: "default",
48656
+ graded: false,
48657
+ options: {
48658
+ answerForms: [{
48659
+ considered: "wrong",
48660
+ form: false,
48661
+ key: 0,
48662
+ simplify: false,
48663
+ value: `${randomString}`
48664
+ }],
48665
+ ariaLabel: "Answer",
48666
+ buttonSets: ["basic"],
48667
+ functions: ["f", "g", "h"],
48668
+ static: true,
48669
+ times: false,
48670
+ visibleLabel: "Answer"
48671
+ },
48672
+ static: true,
48673
+ type: "expression",
48674
+ version: {
48675
+ major: 1,
48676
+ minor: 0
48677
+ }
48678
+ }
48679
+ }
48680
+ }
48681
+ });
48682
+ // @ts-expect-error TS2550: Property 'replaceAll' does not exist on type 'string'.
48683
+ const testJSON = buildTestData(testingObject.replaceAll('"', '\\"'));
48684
+ const parsedJSON = JSON.parse(testJSON);
48685
+ const parsedItemData = parsedJSON.data.assessmentItem.item.itemData;
48686
+ const isNotCheating = deepEq(parsedItemData, testingObject);
48687
+ if (isNotCheating) {
48688
+ return JSON.parse(json);
48689
+ }
48690
+ throw new Error("Something went wrong.");
48691
+ }
48692
+ function buildRandomString() {
48693
+ let randomString = "";
48694
+ const randomLength = Math.floor(Math.random() * 8) + 3;
48695
+ for (let i = 0; i < randomLength; i++) {
48696
+ const randomLetter = String.fromCharCode(97 + Math.floor(Math.random() * 26));
48697
+ randomString += randomLetter;
48698
+ }
48699
+ return randomString;
48700
+ }
48701
+ function buildRandomPhrase() {
48702
+ const phrases = [];
48703
+ const randomLength = Math.floor(Math.random() * 10) + 5;
48704
+ for (let i = 0; i < randomLength; i++) {
48705
+ phrases.push(buildRandomString());
48706
+ }
48707
+ return phrases.join(" ");
48708
+ }
48709
+ function buildTestData(testObject) {
48710
+ return `{"data":{"assessmentItem":{"__typename":"AssessmentItemOrError","error":null,"item":{"__typename":"AssessmentItem","id":"x890b3c70f3e8f4a6","itemData":"${testObject}","problemType":"Type 1","sha":"c7284a3ad65214b4e62bccce236d92f7f5d35941"}}}}`;
48585
48711
  }
48586
48712
 
48587
48713
  const genericPerseusItemData = {
@@ -48845,6 +48971,7 @@ exports.getAngleCoords = getAngleCoords;
48845
48971
  exports.getAnswerFromUserInput = getAnswerFromUserInput;
48846
48972
  exports.getAnswersFromWidgets = getAnswersFromWidgets;
48847
48973
  exports.getCircleCoords = getCircleCoords;
48974
+ exports.getClockwiseAngle = getClockwiseAngle;
48848
48975
  exports.getCorrectAnswerForWidgetId = getCorrectAnswerForWidgetId;
48849
48976
  exports.getImagesWithoutAltData = getImagesWithoutAltData;
48850
48977
  exports.getLineCoords = getLineCoords;