@khanacademy/perseus 23.3.0 → 23.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/es/index.js CHANGED
@@ -2,7 +2,7 @@ import * as KAS from '@khanacademy/kas';
2
2
  import { MathInputI18nContext, CursorContext, getKeyTranslator, createMathField, getCursorContext, DesktopKeypad, convertDotToTimesByLocale, mathQuillInstance, KeypadInput, KeypadType, KeyArray, keypadElementPropType } from '@khanacademy/math-input';
3
3
  import * as PerseusLinter from '@khanacademy/perseus-linter';
4
4
  import { linterContextDefault, Rule, linterContextProps } from '@khanacademy/perseus-linter';
5
- import { View, RenderStateRoot, UniqueIDProvider, useUniqueIdWithMock, Text as Text$2 } from '@khanacademy/wonder-blocks-core';
5
+ import { View, RenderStateRoot, UniqueIDProvider, useLatestRef, useUniqueIdWithMock, Text as Text$2 } from '@khanacademy/wonder-blocks-core';
6
6
  import Tooltip$1 from '@khanacademy/wonder-blocks-tooltip';
7
7
  import classNames from 'classnames';
8
8
  import * as React from 'react';
@@ -29,7 +29,7 @@ import { OptionItem, SingleSelect, MultiSelect } from '@khanacademy/wonder-block
29
29
  import caretDown from '@phosphor-icons/core/regular/caret-down.svg';
30
30
  import caretUp from '@phosphor-icons/core/regular/caret-up.svg';
31
31
  import debounce$2 from 'lodash.debounce';
32
- import { vec, Ellipse, Line as Line$4, Point as Point$2, useMovable, Text as Text$1, Polygon, Plot as Plot$3, Coordinates, Mafs } from 'mafs';
32
+ import { vec, Ellipse, Line as Line$4, Point as Point$2, Polygon, useMovable, Text as Text$1, Plot as Plot$3, Coordinates, Mafs } from 'mafs';
33
33
  import { PhosphorIcon } from '@khanacademy/wonder-blocks-icon';
34
34
  import { Body, LabelSmall, LabelMedium } from '@khanacademy/wonder-blocks-typography';
35
35
  import questionIcon from '@phosphor-icons/core/regular/question.svg';
@@ -3202,7 +3202,7 @@ const DeprecationMixin$1 = {
3202
3202
  /**
3203
3203
  * Approximate equality on numbers and primitives.
3204
3204
  */
3205
- function eq$2(x, y) {
3205
+ function eq$3(x, y) {
3206
3206
  if (typeof x === "number" && typeof y === "number") {
3207
3207
  return Math.abs(x - y) < 1e-9;
3208
3208
  }
@@ -3229,7 +3229,7 @@ function deepEq$5(x, y) {
3229
3229
  return false;
3230
3230
  }
3231
3231
  if (typeof x === "function" && typeof y === "function") {
3232
- return eq$2(x, y);
3232
+ return eq$3(x, y);
3233
3233
  }
3234
3234
  if (typeof x === "function" || typeof y === "function") {
3235
3235
  return false;
@@ -3246,7 +3246,7 @@ function deepEq$5(x, y) {
3246
3246
  if (typeof x === "object" && !!x || typeof y === "object" && !!y) {
3247
3247
  return false;
3248
3248
  }
3249
- return eq$2(x, y);
3249
+ return eq$3(x, y);
3250
3250
  }
3251
3251
 
3252
3252
  /**
@@ -3547,7 +3547,7 @@ const Util = {
3547
3547
  tickStepFromNumTicks,
3548
3548
  constrainedTickStepsFromTickSteps,
3549
3549
  DeprecationMixin: DeprecationMixin$1,
3550
- eq: eq$2,
3550
+ eq: eq$3,
3551
3551
  deepEq: deepEq$5,
3552
3552
  parseQueryString,
3553
3553
  updateQueryString: updateQueryString$2,
@@ -10784,7 +10784,7 @@ class WrappedLine extends WrappedDrawing {
10784
10784
  * A collection of geomtry-related utility functions
10785
10785
  */
10786
10786
  const {
10787
- eq: eq$1,
10787
+ eq: eq$2,
10788
10788
  deepEq: deepEq$4
10789
10789
  } = Util;
10790
10790
 
@@ -10792,17 +10792,21 @@ const {
10792
10792
 
10793
10793
  // a, b, c
10794
10794
 
10795
+ // Given a number, return whether it is positive (1), negative (-1), or zero (0)
10795
10796
  function sign(val) {
10796
- if (eq$1(val, 0)) {
10797
+ if (eq$2(val, 0)) {
10797
10798
  return 0;
10798
10799
  }
10799
10800
  return val > 0 ? 1 : -1;
10800
10801
  }
10802
+
10803
+ // Determine whether three points are collinear (0), for a clockwise turn (negative),
10804
+ // or counterclockwise turn (positive)
10801
10805
  function ccw(a, b, c) {
10802
10806
  return (b[0] - a[0]) * (c[1] - a[1]) - (c[0] - a[0]) * (b[1] - a[1]);
10803
10807
  }
10804
10808
  function collinear(a, b, c) {
10805
- return eq$1(ccw(a, b, c), 0);
10809
+ return eq$2(ccw(a, b, c), 0);
10806
10810
  }
10807
10811
 
10808
10812
  // Given rect bounding points A and B, whether point C is inside the rect
@@ -10947,7 +10951,7 @@ function similar(coords1, coords2, tolerance) {
10947
10951
  return pair[0] / pair[1];
10948
10952
  });
10949
10953
  const same = _.all(factors, function (factor) {
10950
- return eq$1(factors[0], factor);
10954
+ return eq$2(factors[0], factor);
10951
10955
  });
10952
10956
  const congruentEnough = _.all(sidePairs, function (pair) {
10953
10957
  return number.equal(pair[0], pair[1], tolerance);
@@ -10999,7 +11003,7 @@ function rotate(array, n) {
10999
11003
  return array.slice(n).concat(array.slice(0, n));
11000
11004
  }
11001
11005
  function getLineEquation(first, second) {
11002
- if (eq$1(first[0], second[0])) {
11006
+ if (eq$2(first[0], second[0])) {
11003
11007
  return "x = " + first[0].toFixed(3);
11004
11008
  }
11005
11009
  const m = (second[1] - first[1]) / (second[0] - first[0]);
@@ -28652,13 +28656,15 @@ const ItemExtras = [
28652
28656
 
28653
28657
  const PerseusExpressionAnswerFormConsidered = ["correct", "wrong", "ungraded"];
28654
28658
  const lockedFigureColors = {
28659
+ blue: "#3D7586",
28655
28660
  green: "#447A53",
28656
28661
  grayH: "#3B3D45",
28657
28662
  purple: "#594094",
28658
28663
  pink: "#B25071",
28659
- red: "#D92916"
28664
+ red: "#D92916",
28665
+ orange: "#946700"
28660
28666
  };
28661
- const lockedEllipseFillStyles = {
28667
+ const lockedFigureFillStyles = {
28662
28668
  none: 0,
28663
28669
  solid: 1,
28664
28670
  translucent: 0.4
@@ -28678,7 +28684,7 @@ const LockedEllipse = props => {
28678
28684
  center: center,
28679
28685
  radius: radius,
28680
28686
  angle: angle,
28681
- fillOpacity: lockedEllipseFillStyles[fillStyle],
28687
+ fillOpacity: lockedFigureFillStyles[fillStyle],
28682
28688
  strokeStyle: strokeStyle,
28683
28689
  color: lockedFigureColors[color]
28684
28690
  });
@@ -28938,6 +28944,29 @@ const LockedPoint = props => {
28938
28944
  });
28939
28945
  };
28940
28946
 
28947
+ const LockedPolygon = props => {
28948
+ const {
28949
+ points,
28950
+ color,
28951
+ showVertices,
28952
+ fillStyle,
28953
+ strokeStyle
28954
+ } = props;
28955
+ return /*#__PURE__*/React.createElement("g", {
28956
+ className: "locked-polygon"
28957
+ }, /*#__PURE__*/React.createElement(Polygon, {
28958
+ points: [...points],
28959
+ fillOpacity: lockedFigureFillStyles[fillStyle],
28960
+ strokeStyle: strokeStyle,
28961
+ color: lockedFigureColors[color]
28962
+ }), showVertices && points.map((point, index) => /*#__PURE__*/React.createElement(Point$2, {
28963
+ key: `locked-polygon-point-${index}`,
28964
+ x: point[0],
28965
+ y: point[1],
28966
+ color: lockedFigureColors[color]
28967
+ })));
28968
+ };
28969
+
28941
28970
  const LockedVector = props => {
28942
28971
  const {
28943
28972
  color,
@@ -28968,13 +28997,17 @@ const GraphLockedLayer = props => {
28968
28997
  key: `line-${index}`,
28969
28998
  range: props.range
28970
28999
  }, figure));
29000
+ case "vector":
29001
+ return /*#__PURE__*/React.createElement(LockedVector, _extends({
29002
+ key: `vector-${index}`
29003
+ }, figure));
28971
29004
  case "ellipse":
28972
29005
  return /*#__PURE__*/React.createElement(LockedEllipse, _extends({
28973
29006
  key: `ellipse-${index}`
28974
29007
  }, figure));
28975
- case "vector":
28976
- return /*#__PURE__*/React.createElement(LockedVector, _extends({
28977
- key: `vector-${index}`
29008
+ case "polygon":
29009
+ return /*#__PURE__*/React.createElement(LockedPolygon, _extends({
29010
+ key: `polygon-${index}`
28978
29011
  }, figure));
28979
29012
  default:
28980
29013
  /**
@@ -28988,6 +29021,13 @@ const GraphLockedLayer = props => {
28988
29021
  }));
28989
29022
  };
28990
29023
 
29024
+ const REINITIALIZE = "reinitialize";
29025
+ function reinitialize(params) {
29026
+ return {
29027
+ type: REINITIALIZE,
29028
+ params
29029
+ };
29030
+ }
28991
29031
  const MOVE_CONTROL_POINT = "move-control-point";
28992
29032
  function moveControlPoint(pointIndex, destination, itemIndex) {
28993
29033
  return {
@@ -29250,6 +29290,7 @@ function useControlPoint(point, color, onMovePoint) {
29250
29290
  });
29251
29291
  const focusableHandle = /*#__PURE__*/React.createElement("g", {
29252
29292
  "data-testid": "movable-point__focusable-handle",
29293
+ className: "movable-point__focusable-handle",
29253
29294
  tabIndex: 0,
29254
29295
  ref: keyboardHandleRef,
29255
29296
  onFocus: () => setFocused(true),
@@ -29419,7 +29460,6 @@ const LinearSystemGraph = props => {
29419
29460
  const {
29420
29461
  coords: lines
29421
29462
  } = props.graphState;
29422
- const colors = ["var(--movable-line-stroke-color)", "var(--mafs-violet)"];
29423
29463
  return /*#__PURE__*/React.createElement(React.Fragment, null, lines == null ? void 0 : lines.map((line, i) => /*#__PURE__*/React.createElement(MovableLine, {
29424
29464
  key: i,
29425
29465
  points: line,
@@ -29431,7 +29471,7 @@ const LinearSystemGraph = props => {
29431
29471
  end: true
29432
29472
  },
29433
29473
  onMovePoint: (endpointIndex, destination) => dispatch(moveControlPoint(endpointIndex, destination, i)),
29434
- color: colors[i]
29474
+ color: "var(--movable-line-stroke-color)"
29435
29475
  })), ";");
29436
29476
  };
29437
29477
 
@@ -29503,7 +29543,8 @@ const Angle = ({
29503
29543
  endPoints,
29504
29544
  range,
29505
29545
  polygonLines,
29506
- showAngles
29546
+ showAngles,
29547
+ snapTo
29507
29548
  }) => {
29508
29549
  const [centerX, centerY] = centerPoint;
29509
29550
  const areClockwise = clockwise$1([centerPoint, ...endPoints]);
@@ -29541,7 +29582,9 @@ const Angle = ({
29541
29582
  if (isOutside) {
29542
29583
  angleInDegrees = 360 - angleInDegrees;
29543
29584
  }
29544
- const angleLabelNumber = parseFloat(angleInDegrees.toFixed(1));
29585
+
29586
+ // Only want to show whole numbers when in angles mode
29587
+ const angleLabelNumber = parseFloat(angleInDegrees.toFixed(snapTo === "angles" ? 0 : 1));
29545
29588
  const angleLabel = Number.isInteger(angleLabelNumber) ? angleLabelNumber : "≈ " + angleLabelNumber;
29546
29589
  return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("defs", null, /*#__PURE__*/React.createElement("filter", {
29547
29590
  id: "background",
@@ -29627,15 +29670,17 @@ const StyledMovablePoint = props => {
29627
29670
  point,
29628
29671
  onMove,
29629
29672
  cursor,
29630
- color: color$1 = color.blue
29673
+ color: color$1 = color.blue,
29674
+ snapTo
29631
29675
  } = props;
29676
+ const snapToValue = snapTo != null ? snapTo : "grid";
29632
29677
  const {
29633
29678
  dragging
29634
29679
  } = useMovable({
29635
29680
  gestureTarget: elementRef,
29636
29681
  point,
29637
29682
  onMove,
29638
- constrain: p => snap(snapStep, p)
29683
+ constrain: p => ["angles", "sides"].includes(snapToValue) ? p : snap(snapStep, p)
29639
29684
  });
29640
29685
  return /*#__PURE__*/React.createElement(MovablePointView, {
29641
29686
  ref: elementRef,
@@ -29661,7 +29706,8 @@ const PolygonGraph = props => {
29661
29706
  showAngles,
29662
29707
  showSides,
29663
29708
  range,
29664
- snapStep
29709
+ snapStep,
29710
+ snapTo
29665
29711
  } = props.graphState;
29666
29712
 
29667
29713
  // TODO(benchristel): can the default set of points be removed here? I don't
@@ -29669,6 +29715,7 @@ const PolygonGraph = props => {
29669
29715
  const points = coords != null ? coords : [[0, 0]];
29670
29716
  const ref = React.useRef(null);
29671
29717
  const dragReferencePoint = points[0];
29718
+ const snapToValue = snapTo != null ? snapTo : "grid";
29672
29719
  const {
29673
29720
  dragging
29674
29721
  } = useMovable({
@@ -29678,7 +29725,7 @@ const PolygonGraph = props => {
29678
29725
  const delta = vec.sub(newPoint, dragReferencePoint);
29679
29726
  dispatch(moveAll(delta));
29680
29727
  },
29681
- constrain: p => snap(snapStep, p)
29728
+ constrain: p => ["angles", "sides"].includes(snapToValue) ? p : snap(snapStep, p)
29682
29729
  });
29683
29730
  const active = hovered || focused || dragging;
29684
29731
  const lines = getLines(points);
@@ -29701,11 +29748,12 @@ const PolygonGraph = props => {
29701
29748
  active: active,
29702
29749
  range: range,
29703
29750
  polygonLines: lines,
29704
- showAngles: !!showAngles
29751
+ showAngles: !!showAngles,
29752
+ snapTo: snapToValue
29705
29753
  });
29706
29754
  }), showSides && lines.map(([start, end], i) => {
29707
29755
  const [x, y] = vec.midpoint(start, end);
29708
- const length = parseFloat(vec.dist(start, end).toFixed(1));
29756
+ const length = parseFloat(vec.dist(start, end).toFixed(snapTo === "sides" ? 0 : 1));
29709
29757
  return /*#__PURE__*/React.createElement(TextLabel, {
29710
29758
  key: "side-" + i,
29711
29759
  x: x,
@@ -29729,143 +29777,12 @@ const PolygonGraph = props => {
29729
29777
  }
29730
29778
  }), points.map((point, i) => /*#__PURE__*/React.createElement(StyledMovablePoint, {
29731
29779
  key: "point-" + i,
29780
+ snapTo: snapTo,
29732
29781
  point: point,
29733
29782
  onMove: destination => dispatch(movePoint(i, destination))
29734
29783
  })));
29735
29784
  };
29736
29785
 
29737
- function initializeGraphState(params) {
29738
- const {
29739
- graph,
29740
- step,
29741
- snapStep,
29742
- range
29743
- } = params;
29744
- const shared = {
29745
- hasBeenInteractedWith: false,
29746
- range,
29747
- snapStep
29748
- };
29749
- switch (graph.type) {
29750
- case "segment":
29751
- return _extends({}, shared, {
29752
- type: "segment",
29753
- coords: getSegmentCoords({
29754
- graph,
29755
- step,
29756
- range
29757
- })
29758
- });
29759
- case "linear":
29760
- return _extends({}, shared, {
29761
- type: graph.type,
29762
- coords: getLineCoords({
29763
- graph,
29764
- range,
29765
- step
29766
- })
29767
- });
29768
- case "ray":
29769
- return _extends({}, shared, {
29770
- type: graph.type,
29771
- coords: getLineCoords({
29772
- graph,
29773
- range,
29774
- step
29775
- })
29776
- });
29777
- case "linear-system":
29778
- return _extends({}, shared, {
29779
- type: graph.type,
29780
- coords: getLinearSystemCoords({
29781
- graph,
29782
- range,
29783
- step
29784
- })
29785
- });
29786
- case "polygon":
29787
- return _extends({}, shared, {
29788
- type: "polygon",
29789
- showAngles: Boolean(graph.showAngles),
29790
- showSides: Boolean(graph.showSides),
29791
- coords: getPolygonCoords({
29792
- graph,
29793
- range,
29794
- step
29795
- })
29796
- });
29797
- case "point":
29798
- return _extends({}, shared, {
29799
- type: graph.type,
29800
- coords: getDefaultPoints({
29801
- graph,
29802
- step,
29803
- range
29804
- })
29805
- });
29806
- case "circle":
29807
- return _extends({}, shared, {
29808
- type: graph.type
29809
- }, getCircleCoords(graph));
29810
- case "quadratic":
29811
- return _extends({}, shared, {
29812
- type: graph.type,
29813
- coords: getQuadraticCoords(graph, range, step)
29814
- });
29815
- case "sinusoid":
29816
- return _extends({}, shared, {
29817
- type: graph.type,
29818
- coords: getSinusoidCoords(graph, range, step)
29819
- });
29820
- case "angle":
29821
- throw new Error("Mafs not yet implemented for graph type: " + graph.type);
29822
- default:
29823
- throw new UnreachableCaseError(graph);
29824
- }
29825
- }
29826
- const getDefaultPoints = ({
29827
- graph,
29828
- range,
29829
- step
29830
- }) => {
29831
- var _graph$coords;
29832
- const numPoints = graph.numPoints || 1;
29833
- let coords = (_graph$coords = graph.coords) == null ? void 0 : _graph$coords.slice();
29834
- if (coords) {
29835
- return coords;
29836
- }
29837
- switch (numPoints) {
29838
- case 1:
29839
- // Back in the day, one point's coords were in graph.coord
29840
- coords = [graph.coord || [0, 0]];
29841
- break;
29842
- case 2:
29843
- coords = [[-5, 0], [5, 0]];
29844
- break;
29845
- case 3:
29846
- coords = [[-5, 0], [0, 0], [5, 0]];
29847
- break;
29848
- case 4:
29849
- coords = [[-6, 0], [-2, 0], [2, 0], [6, 0]];
29850
- break;
29851
- case 5:
29852
- coords = [[-6, 0], [-3, 0], [0, 0], [3, 0], [6, 0]];
29853
- break;
29854
- case 6:
29855
- coords = [[-5, 0], [-3, 0], [-1, 0], [1, 0], [3, 0], [5, 0]];
29856
- break;
29857
- default:
29858
- coords = [];
29859
- break;
29860
- }
29861
- // Transform coords from their -10 to 10 space to 0 to 1
29862
- // because of the old graph.coord, and also it's easier.
29863
- const newCoords = normalizeCoords(coords, [[-10, 10], [-10, 10]]);
29864
- return normalizePoints(range, step, newCoords);
29865
- };
29866
-
29867
- // TS v4 doesn't narrow return types, while v5 does.
29868
- // Instead of updating to v5, using generic type to relate input and output types.
29869
29786
  function getGradableGraph(state, initialGraph) {
29870
29787
  if (!state.hasBeenInteractedWith) {
29871
29788
  return _extends({}, initialGraph);
@@ -29918,115 +29835,6 @@ function getGradableGraph(state, initialGraph) {
29918
29835
  }
29919
29836
  throw new Error("Mafs is not yet implemented for graph type: " + initialGraph.type);
29920
29837
  }
29921
- const getSegmentCoords = ({
29922
- graph,
29923
- range,
29924
- step
29925
- }) => {
29926
- if (graph.coords) {
29927
- return graph.coords;
29928
- }
29929
- const ys = n => {
29930
- switch (n) {
29931
- case 2:
29932
- return [5, -5];
29933
- case 3:
29934
- return [5, 0, -5];
29935
- case 4:
29936
- return [6, 2, -2, -6];
29937
- case 5:
29938
- return [6, 3, 0, -3, -6];
29939
- case 6:
29940
- return [5, 3, 1, -1, -3, -5];
29941
- default:
29942
- return [5];
29943
- }
29944
- };
29945
- const defaultRange = [[-10, 10], [-10, 10]];
29946
- return ys(graph.numSegments).map(y => {
29947
- let endpoints = [[-5, y], [5, y]];
29948
- endpoints = normalizeCoords(endpoints, defaultRange);
29949
- endpoints = normalizePoints(range, step, endpoints);
29950
- return endpoints;
29951
- });
29952
- };
29953
- const defaultLinearCoords = [[[0.25, 0.75], [0.75, 0.75]], [[0.25, 0.25], [0.75, 0.25]]];
29954
- const getLineCoords = ({
29955
- graph,
29956
- range,
29957
- step
29958
- }) => {
29959
- if (graph.coords) {
29960
- return graph.coords;
29961
- }
29962
- return normalizePoints(range, step, defaultLinearCoords[0]);
29963
- };
29964
- const getLinearSystemCoords = ({
29965
- graph,
29966
- range,
29967
- step
29968
- }) => {
29969
- if (graph.coords) {
29970
- return graph.coords;
29971
- }
29972
- return defaultLinearCoords.map(points => normalizePoints(range, step, points));
29973
- };
29974
- const getPolygonCoords = ({
29975
- graph,
29976
- range,
29977
- step
29978
- }) => {
29979
- var _graph$coords2;
29980
- let coords = (_graph$coords2 = graph.coords) == null ? void 0 : _graph$coords2.slice();
29981
- if (coords) {
29982
- return coords;
29983
- }
29984
- const n = graph.numSides || 3;
29985
- if (n === "unlimited") {
29986
- coords = [];
29987
- } else {
29988
- const angle = 2 * Math.PI / n;
29989
- const offset = (1 / n - 1 / 2) * Math.PI;
29990
-
29991
- // TODO(alex): Generalize this to more than just triangles so that
29992
- // all polygons have whole number side lengths if snapping to sides
29993
- const radius = graph.snapTo === "sides" ? Math.sqrt(3) / 3 * 7 : 4;
29994
-
29995
- // Generate coords of a regular polygon with n sides
29996
- coords = [...Array(n).keys()].map(i => [radius * Math.cos(i * angle + offset), radius * Math.sin(i * angle + offset)]);
29997
- }
29998
- coords = normalizeCoords(coords, [[-10, 10], [-10, 10]]);
29999
- const snapToGrid = !["angles", "sides"].includes(graph.snapTo || "");
30000
- coords = normalizePoints(range, step, coords, /* noSnap */!snapToGrid);
30001
- return coords;
30002
- };
30003
- const getSinusoidCoords = (graph, range, step) => {
30004
- if (graph.coords) {
30005
- return [graph.coords[0], graph.coords[1]];
30006
- }
30007
- let coords = [[0.5, 0.5], [0.65, 0.6]];
30008
- coords = normalizePoints(range, step, coords, true);
30009
- return coords;
30010
- };
30011
- const getQuadraticCoords = (graph, range, step) => {
30012
- if (graph.coords) {
30013
- return graph.coords;
30014
- }
30015
- const defaultCoords = [[0.25, 0.75], [0.5, 0.25], [0.75, 0.75]];
30016
- return normalizePoints(range, step, defaultCoords, true);
30017
- };
30018
- const getCircleCoords = (graph, range, step) => {
30019
- if (graph.center != null && graph.radius != null) {
30020
- return {
30021
- center: graph.center,
30022
- radiusPoint: vec.add(graph.center, [graph.radius, 0])
30023
- };
30024
- }
30025
- return {
30026
- center: [0, 0],
30027
- radiusPoint: [1, 0]
30028
- };
30029
- };
30030
29838
 
30031
29839
  /**
30032
29840
  * determine radius of a circle graph
@@ -30160,38 +29968,16 @@ function QuadraticGraph(props) {
30160
29968
 
30161
29969
  // Calculate the y value based on the current x value
30162
29970
  const y = x => (a * x + b) * x + c;
30163
- const handleOnMove = (destination, elementId) => {
30164
- // If the destination is invalid, we want do not want to move the point
30165
- const validDestination = isValidDestination(destination, elementId, coords);
30166
- if (validDestination === false) {
30167
- return;
30168
- }
30169
- dispatch(movePoint(elementId, destination));
30170
- };
30171
29971
  return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Plot$3.OfX, {
30172
29972
  y: y,
30173
29973
  color: color.blue
30174
29974
  }), coords.map((coord, i) => /*#__PURE__*/React.createElement(StyledMovablePoint, {
30175
29975
  key: "point-" + i,
30176
29976
  point: coord,
30177
- onMove: destination => handleOnMove(destination, i)
29977
+ onMove: destination => dispatch(movePoint(i, destination))
30178
29978
  })));
30179
29979
  }
30180
29980
 
30181
- // Ensure that we are only snapping to coordinates that result in a valid quadratic equation
30182
- const isValidDestination = (destination, elementId, coords) => {
30183
- // Set up the new coords and check if the quadratic coefficients are valid
30184
- const newCoords = [...coords];
30185
- newCoords[elementId] = destination;
30186
- const QuadraticCoefficients = getQuadraticCoefficients(newCoords);
30187
-
30188
- // If the new destination results in an invalid quadratic equation, we don't want to move the point
30189
- if (QuadraticCoefficients === undefined) {
30190
- return false;
30191
- }
30192
- return true;
30193
- };
30194
-
30195
29981
  // Get the quadratic coefficients from the 3 control points
30196
29982
  // These equations were originally set up in 2013 and may require some
30197
29983
  // additional comments to help clarify the quadratic formula manipulations
@@ -31335,8 +31121,207 @@ const LegacyGrid = ({
31335
31121
  return null;
31336
31122
  };
31337
31123
 
31124
+ function initializeGraphState(params) {
31125
+ var _graph$snapTo;
31126
+ const {
31127
+ graph,
31128
+ step,
31129
+ snapStep,
31130
+ range
31131
+ } = params;
31132
+ const shared = {
31133
+ hasBeenInteractedWith: false,
31134
+ range,
31135
+ snapStep
31136
+ };
31137
+ switch (graph.type) {
31138
+ case "segment":
31139
+ return _extends({}, shared, {
31140
+ type: "segment",
31141
+ coords: getSegmentCoords(graph, range, step)
31142
+ });
31143
+ case "linear":
31144
+ return _extends({}, shared, {
31145
+ type: graph.type,
31146
+ coords: getLineCoords(graph, range, step)
31147
+ });
31148
+ case "ray":
31149
+ return _extends({}, shared, {
31150
+ type: graph.type,
31151
+ coords: getLineCoords(graph, range, step)
31152
+ });
31153
+ case "linear-system":
31154
+ return _extends({}, shared, {
31155
+ type: graph.type,
31156
+ coords: getLinearSystemCoords(graph, range, step)
31157
+ });
31158
+ case "polygon":
31159
+ return _extends({}, shared, {
31160
+ type: "polygon",
31161
+ showAngles: Boolean(graph.showAngles),
31162
+ showSides: Boolean(graph.showSides),
31163
+ coords: getPolygonCoords(graph, range, step),
31164
+ snapTo: (_graph$snapTo = graph.snapTo) != null ? _graph$snapTo : "grid"
31165
+ });
31166
+ case "point":
31167
+ return _extends({}, shared, {
31168
+ type: graph.type,
31169
+ coords: getPointCoords(graph, range, step)
31170
+ });
31171
+ case "circle":
31172
+ return _extends({}, shared, {
31173
+ type: graph.type
31174
+ }, getCircleCoords(graph));
31175
+ case "quadratic":
31176
+ return _extends({}, shared, {
31177
+ type: graph.type,
31178
+ coords: getQuadraticCoords(graph, range, step)
31179
+ });
31180
+ case "sinusoid":
31181
+ return _extends({}, shared, {
31182
+ type: graph.type,
31183
+ coords: getSinusoidCoords(graph, range, step)
31184
+ });
31185
+ case "angle":
31186
+ throw new Error("Mafs not yet implemented for graph type: " + graph.type);
31187
+ default:
31188
+ throw new UnreachableCaseError(graph);
31189
+ }
31190
+ }
31191
+ function getPointCoords(graph, range, step) {
31192
+ var _graph$coords;
31193
+ const numPoints = graph.numPoints || 1;
31194
+ let coords = (_graph$coords = graph.coords) == null ? void 0 : _graph$coords.slice();
31195
+ if (coords) {
31196
+ return coords;
31197
+ }
31198
+ switch (numPoints) {
31199
+ case 1:
31200
+ // Back in the day, one point's coords were in graph.coord
31201
+ coords = [graph.coord || [0, 0]];
31202
+ break;
31203
+ case 2:
31204
+ coords = [[-5, 0], [5, 0]];
31205
+ break;
31206
+ case 3:
31207
+ coords = [[-5, 0], [0, 0], [5, 0]];
31208
+ break;
31209
+ case 4:
31210
+ coords = [[-6, 0], [-2, 0], [2, 0], [6, 0]];
31211
+ break;
31212
+ case 5:
31213
+ coords = [[-6, 0], [-3, 0], [0, 0], [3, 0], [6, 0]];
31214
+ break;
31215
+ case 6:
31216
+ coords = [[-5, 0], [-3, 0], [-1, 0], [1, 0], [3, 0], [5, 0]];
31217
+ break;
31218
+ default:
31219
+ coords = [];
31220
+ break;
31221
+ }
31222
+ // Transform coords from their -10 to 10 space to 0 to 1
31223
+ // because of the old graph.coord, and also it's easier.
31224
+ const newCoords = normalizeCoords(coords, [[-10, 10], [-10, 10]]);
31225
+ return normalizePoints(range, step, newCoords);
31226
+ }
31227
+ function getSegmentCoords(graph, range, step) {
31228
+ if (graph.coords) {
31229
+ return graph.coords;
31230
+ }
31231
+ const ys = n => {
31232
+ switch (n) {
31233
+ case 2:
31234
+ return [5, -5];
31235
+ case 3:
31236
+ return [5, 0, -5];
31237
+ case 4:
31238
+ return [6, 2, -2, -6];
31239
+ case 5:
31240
+ return [6, 3, 0, -3, -6];
31241
+ case 6:
31242
+ return [5, 3, 1, -1, -3, -5];
31243
+ default:
31244
+ return [5];
31245
+ }
31246
+ };
31247
+ const defaultRange = [[-10, 10], [-10, 10]];
31248
+ return ys(graph.numSegments).map(y => {
31249
+ let endpoints = [[-5, y], [5, y]];
31250
+ endpoints = normalizeCoords(endpoints, defaultRange);
31251
+ endpoints = normalizePoints(range, step, endpoints);
31252
+ return endpoints;
31253
+ });
31254
+ }
31255
+ const defaultLinearCoords = [[[0.25, 0.75], [0.75, 0.75]], [[0.25, 0.25], [0.75, 0.25]]];
31256
+ function getLineCoords(graph, range, step) {
31257
+ if (graph.coords) {
31258
+ return graph.coords;
31259
+ }
31260
+ return normalizePoints(range, step, defaultLinearCoords[0]);
31261
+ }
31262
+ function getLinearSystemCoords(graph, range, step) {
31263
+ if (graph.coords) {
31264
+ return graph.coords;
31265
+ }
31266
+ return defaultLinearCoords.map(points => normalizePoints(range, step, points));
31267
+ }
31268
+ function getPolygonCoords(graph, range, step) {
31269
+ var _graph$coords2;
31270
+ let coords = (_graph$coords2 = graph.coords) == null ? void 0 : _graph$coords2.slice();
31271
+ if (coords) {
31272
+ return coords;
31273
+ }
31274
+ const n = graph.numSides || 3;
31275
+ if (n === "unlimited") {
31276
+ coords = [];
31277
+ } else {
31278
+ const angle = 2 * Math.PI / n;
31279
+ const offset = (1 / n - 1 / 2) * Math.PI;
31280
+
31281
+ // TODO(alex): Generalize this to more than just triangles so that
31282
+ // all polygons have whole number side lengths if snapping to sides
31283
+ const radius = graph.snapTo === "sides" ? Math.sqrt(3) / 3 * 7 : 4;
31284
+
31285
+ // Generate coords of a regular polygon with n sides
31286
+ coords = [...Array(n).keys()].map(i => [radius * Math.cos(i * angle + offset), radius * Math.sin(i * angle + offset)]);
31287
+ }
31288
+ coords = normalizeCoords(coords, [[-10, 10], [-10, 10]]);
31289
+ const snapToGrid = !["angles", "sides"].includes(graph.snapTo || "");
31290
+ coords = normalizePoints(range, step, coords, /* noSnap */!snapToGrid);
31291
+ return coords;
31292
+ }
31293
+ function getSinusoidCoords(graph, range, step) {
31294
+ if (graph.coords) {
31295
+ return [graph.coords[0], graph.coords[1]];
31296
+ }
31297
+ let coords = [[0.5, 0.5], [0.65, 0.6]];
31298
+ coords = normalizePoints(range, step, coords, true);
31299
+ return coords;
31300
+ }
31301
+ function getQuadraticCoords(graph, range, step) {
31302
+ if (graph.coords) {
31303
+ return graph.coords;
31304
+ }
31305
+ const defaultCoords = [[0.25, 0.75], [0.5, 0.25], [0.75, 0.75]];
31306
+ return normalizePoints(range, step, defaultCoords, true);
31307
+ }
31308
+ function getCircleCoords(graph) {
31309
+ if (graph.center != null && graph.radius != null) {
31310
+ return {
31311
+ center: graph.center,
31312
+ radiusPoint: vec.add(graph.center, [graph.radius, 0])
31313
+ };
31314
+ }
31315
+ return {
31316
+ center: [0, 0],
31317
+ radiusPoint: [2, 0]
31318
+ };
31319
+ }
31320
+
31338
31321
  function interactiveGraphReducer(state, action) {
31339
31322
  switch (action.type) {
31323
+ case REINITIALIZE:
31324
+ return initializeGraphState(action.params);
31340
31325
  case MOVE_CONTROL_POINT:
31341
31326
  return doMoveControlPoint(state, action);
31342
31327
  case MOVE_LINE:
@@ -31468,11 +31453,20 @@ function doMoveAll(state, action) {
31468
31453
  switch (state.type) {
31469
31454
  case "polygon":
31470
31455
  {
31471
- const change = getChange(state.coords, action.delta, {
31472
- snapStep,
31473
- range
31474
- });
31475
- const newCoords = state.coords.map(point => snap(snapStep, vec.add(point, change)));
31456
+ let newCoords;
31457
+ if (state.snapTo === "sides" || state.snapTo === "angles") {
31458
+ const change = getChange(state.coords, action.delta, {
31459
+ snapStep: [0, 0],
31460
+ range
31461
+ });
31462
+ newCoords = state.coords.map(point => vec.add(point, change));
31463
+ } else {
31464
+ const change = getChange(state.coords, action.delta, {
31465
+ snapStep,
31466
+ range
31467
+ });
31468
+ newCoords = state.coords.map(point => snap(snapStep, vec.add(point, change)));
31469
+ }
31476
31470
  return _extends({}, state, {
31477
31471
  hasBeenInteractedWith: true,
31478
31472
  coords: newCoords
@@ -31486,10 +31480,18 @@ function doMoveAll(state, action) {
31486
31480
  function doMovePoint(state, action) {
31487
31481
  switch (state.type) {
31488
31482
  case "polygon":
31483
+ let newValue;
31484
+ if (state.snapTo === "sides") {
31485
+ newValue = boundAndSnapToSides(action.destination, state, action.index);
31486
+ } else if (state.snapTo === "angles") {
31487
+ newValue = boundAndSnapToAngle(action.destination, state, action.index);
31488
+ } else {
31489
+ newValue = boundAndSnapToGrid(action.destination, state);
31490
+ }
31489
31491
  const newCoords = setAtIndex({
31490
31492
  array: state.coords,
31491
31493
  index: action.index,
31492
- newValue: boundAndSnapToGrid(action.destination, state)
31494
+ newValue: newValue
31493
31495
  });
31494
31496
 
31495
31497
  // Reject the move if it would cause the sides of the polygon to cross
@@ -31532,6 +31534,15 @@ function doMovePoint(state, action) {
31532
31534
  }
31533
31535
  case "quadratic":
31534
31536
  {
31537
+ // Set up the new coords and check if the quadratic coefficients are valid
31538
+ const _newCoords2 = [...state.coords];
31539
+ _newCoords2[action.index] = action.destination;
31540
+ const QuadraticCoefficients = getQuadraticCoefficients(_newCoords2);
31541
+
31542
+ // If the new destination results in an invalid quadratic equation, we don't want to move the point
31543
+ if (QuadraticCoefficients === undefined) {
31544
+ return state;
31545
+ }
31535
31546
  return _extends({}, state, {
31536
31547
  hasBeenInteractedWith: true,
31537
31548
  coords: setAtIndex({
@@ -31624,14 +31635,8 @@ function doChangeRange(state, action) {
31624
31635
  range: action.range
31625
31636
  });
31626
31637
  }
31627
- const getChange = (coords, delta, constraintOpts) => {
31638
+ const getDeltaVertex = (maxMoves, minMoves, delta) => {
31628
31639
  const [deltaX, deltaY] = delta;
31629
- const maxMoves = coords.map(point => maxMove(_extends({}, constraintOpts, {
31630
- point
31631
- })));
31632
- const minMoves = coords.map(point => minMove(_extends({}, constraintOpts, {
31633
- point
31634
- })));
31635
31640
  const maxXMove = Math.min(...maxMoves.map(move => move[0]));
31636
31641
  const maxYMove = Math.min(...maxMoves.map(move => move[1]));
31637
31642
  const minXMove = Math.max(...minMoves.map(move => move[0]));
@@ -31640,6 +31645,22 @@ const getChange = (coords, delta, constraintOpts) => {
31640
31645
  const dy = clamp(deltaY, minYMove, maxYMove);
31641
31646
  return [dx, dy];
31642
31647
  };
31648
+ const getChange = (coords, delta, constraintOpts) => {
31649
+ const maxMoves = coords.map(point => maxMove(_extends({}, constraintOpts, {
31650
+ point
31651
+ })));
31652
+ const minMoves = coords.map(point => minMove(_extends({}, constraintOpts, {
31653
+ point
31654
+ })));
31655
+ const [dx, dy] = getDeltaVertex(maxMoves, minMoves, delta);
31656
+ return [dx, dy];
31657
+ };
31658
+ const eq$1 = Util.eq;
31659
+
31660
+ // Less than or approximately equal
31661
+ function leq$1(a, b) {
31662
+ return a < b || eq$1(a, b);
31663
+ }
31643
31664
  function boundAndSnapToGrid(point, {
31644
31665
  snapStep,
31645
31666
  range
@@ -31650,6 +31671,131 @@ function boundAndSnapToGrid(point, {
31650
31671
  point
31651
31672
  }));
31652
31673
  }
31674
+ function boundAndSnapToAngle(destinationPoint, {
31675
+ range,
31676
+ coords
31677
+ }, index) {
31678
+ const startingPoint = coords[index];
31679
+
31680
+ // Needed to prevent updating the original coords before the checks for
31681
+ // degenerate triangles and overlapping sides
31682
+ const coordsCopy = [...coords];
31683
+
31684
+ // Takes the destination point and makes sure it is within the bounds of the graph
31685
+ // SnapStep is [0, 0] because we don't want to snap to the grid
31686
+ coordsCopy[index] = bound$1({
31687
+ snapStep: [0, 0],
31688
+ range,
31689
+ point: destinationPoint
31690
+ });
31691
+
31692
+ // Gets the radian angles between the coords and maps them to degrees
31693
+ const angles = angleMeasures(coordsCopy).map(angle => angle * 180 / Math.PI);
31694
+
31695
+ // Gets the relative index of a point
31696
+ const rel = j => {
31697
+ return (index + j + coordsCopy.length) % coordsCopy.length;
31698
+ };
31699
+
31700
+ // Round the angles to left and right of the current point
31701
+ _.each([-1, 1], function (j) {
31702
+ angles[rel(j)] = Math.round(angles[rel(j)]);
31703
+ });
31704
+ const getAngle = function getAngle(a, vertex, b) {
31705
+ const angle = GraphUtils.findAngle(coordsCopy[rel(a)], coordsCopy[rel(b)], coordsCopy[rel(vertex)]);
31706
+ return (angle + 360) % 360;
31707
+ };
31708
+ const innerAngles = [angles[rel(-1)] - getAngle(-2, -1, 1), angles[rel(1)] - getAngle(-1, 1, 2)];
31709
+ innerAngles[2] = 180 - (innerAngles[0] + innerAngles[1]);
31710
+
31711
+ // Avoid degenerate triangles
31712
+ if (innerAngles.some(function (angle) {
31713
+ return leq$1(angle, 1);
31714
+ })) {
31715
+ return startingPoint;
31716
+ }
31717
+ const knownSide = magnitude(
31718
+ // @ts-expect-error - TS2345 - Argument of type 'number[]' is not assignable to parameter of type 'readonly Coord[]'.
31719
+ vector(coordsCopy[rel(-1)], coordsCopy[rel(1)]));
31720
+
31721
+ // Returns true if the points form a counter-clockwise turn;
31722
+ // a.k.a if the point is on the left or right of the polygon.
31723
+ // This is used to determine how to adjust the point: if the point is on the left,
31724
+ // we want to add the inner angle to the outer angle, and if it's on the right,
31725
+ // we want to subtract the inner angle from the outer angle. The angle solved
31726
+ // for is then used in the polar function to determine the new point.
31727
+ const onLeft = sign(ccw(coordsCopy[rel(-1)], coordsCopy[rel(1)], coordsCopy[index])) === 1;
31728
+
31729
+ // Solve for side by using the law of sines
31730
+ const side = Math.sin(innerAngles[1] * Math.PI / 180) / Math.sin(innerAngles[2] * Math.PI / 180) * knownSide;
31731
+
31732
+ // Angle at the second vertex of the polygon
31733
+ const outerAngle = GraphUtils.findAngle(coordsCopy[rel(1)], coordsCopy[rel(-1)]);
31734
+
31735
+ // Uses the length of the side of the polygon (radial coordinate)
31736
+ // and the angle between the first and second sides of the
31737
+ // polygon (angular coordinate) to determine how to adjust the point
31738
+ const offset = polar(side, outerAngle + (onLeft ? 1 : -1) * innerAngles[0]);
31739
+ return vector$1.add(coordsCopy[rel(-1)], offset);
31740
+ }
31741
+ function boundAndSnapToSides(destinationPoint, {
31742
+ range,
31743
+ coords
31744
+ }, index) {
31745
+ const startingPoint = coords[index];
31746
+
31747
+ // Needed to prevent updating the original coords before the checks for
31748
+ // degenerate triangles and overlapping sides
31749
+ const coordsCopy = [...coords];
31750
+
31751
+ // Takes the destination point and makes sure it is within the bounds of the graph
31752
+ // SnapStep is [0, 0] because we don't want to snap to the grid
31753
+ coordsCopy[index] = bound$1({
31754
+ snapStep: [0, 0],
31755
+ range,
31756
+ point: destinationPoint
31757
+ });
31758
+
31759
+ // Gets the relative index of a point
31760
+ const rel = j => {
31761
+ return (index + j + coordsCopy.length) % coordsCopy.length;
31762
+ };
31763
+ const sides = _.map([[coordsCopy[rel(-1)], coordsCopy[index]], [coordsCopy[index], coordsCopy[rel(1)]], [coordsCopy[rel(-1)], coordsCopy[rel(1)]]], function (coordsCopy) {
31764
+ // @ts-expect-error - TS2345 - Argument of type 'number[]' is not assignable to parameter of type 'readonly Coord[]'. | TS2556 - A spread argument must either have a tuple type or be passed to a rest parameter.
31765
+ return magnitude(vector(...coordsCopy));
31766
+ });
31767
+
31768
+ // Round the sides to left and right of the current point
31769
+ _.each([0, 1], function (j) {
31770
+ sides[j] = Math.round(sides[j]);
31771
+ });
31772
+
31773
+ // Avoid degenerate triangles
31774
+ if (leq$1(sides[1] + sides[2], sides[0]) || leq$1(sides[0] + sides[2], sides[1]) || leq$1(sides[0] + sides[1], sides[2])) {
31775
+ return startingPoint;
31776
+ }
31777
+
31778
+ // Solve for angle by using the law of cosines
31779
+ // Angle at the first vertex of the polygon
31780
+ const innerAngle = lawOfCosines(sides[0], sides[2], sides[1]);
31781
+
31782
+ // Angle at the second vertex of the polygon
31783
+ const outerAngle = GraphUtils.findAngle(coordsCopy[rel(1)], coordsCopy[rel(-1)]);
31784
+
31785
+ // Returns true if the points form a counter-clockwise turn;
31786
+ // a.k.a if the point is on the left or right of the polygon.
31787
+ // This is used to determine how to adjust the point: if the point is on the left,
31788
+ // we want to add the inner angle to the outer angle, and if it's on the right,
31789
+ // we want to subtract the inner angle from the outer angle. The angle solved
31790
+ // for is then used in the polar function to determine the new point.
31791
+ const onLeft = sign(ccw(coordsCopy[rel(-1)], coordsCopy[rel(1)], coordsCopy[index])) === 1;
31792
+
31793
+ // Uses the length of the first side of the polygon (radial coordinate)
31794
+ // and the angle between the first and second sides of the
31795
+ // polygon (angular coordinate) to determine how to adjust the point
31796
+ const offset = polar(sides[0], outerAngle + (onLeft ? 1 : -1) * innerAngle);
31797
+ return vector$1.add(coordsCopy[rel(-1)], offset);
31798
+ }
31653
31799
 
31654
31800
  // Returns the closest point to the given `point` that is within the graph
31655
31801
  // bounds given in `state`.
@@ -31664,7 +31810,7 @@ function bound$1({
31664
31810
  return [clamp(requestedX, minX + snapX, maxX - snapX), clamp(requestedY, minY + snapY, maxY - snapY)];
31665
31811
  }
31666
31812
 
31667
- // Returns the vector from the given point to the top-right corner of the graph
31813
+ // Returns the vector from the given point to the top-right corner of the graph when snapped to the grid
31668
31814
  function maxMove({
31669
31815
  snapStep,
31670
31816
  range,
@@ -31678,8 +31824,7 @@ function maxMove({
31678
31824
  return vec.sub(topRight, point);
31679
31825
  }
31680
31826
 
31681
- // Returns the vector from the given point to the bottom-left corner of the
31682
- // graph
31827
+ // Returns the vector from the given point to the bottom-left corner of the graph when snapped to the grid
31683
31828
  function minMove({
31684
31829
  snapStep,
31685
31830
  range,
@@ -31801,11 +31946,12 @@ function mafsStateToInteractiveGraph(state) {
31801
31946
  }
31802
31947
  const StatefulMafsGraph = /*#__PURE__*/React.forwardRef((props, ref) => {
31803
31948
  const {
31804
- onChange
31949
+ onChange,
31950
+ graph
31805
31951
  } = props;
31806
31952
  const [state, dispatch] = React.useReducer(interactiveGraphReducer, props, initializeGraphState);
31807
31953
  useImperativeHandle(ref, () => ({
31808
- getUserInput: () => getGradableGraph(state, props.graph)
31954
+ getUserInput: () => getGradableGraph(state, graph)
31809
31955
  }));
31810
31956
  const prevState = useRef(state);
31811
31957
  useEffect(() => {
@@ -31828,6 +31974,18 @@ const StatefulMafsGraph = /*#__PURE__*/React.forwardRef((props, ref) => {
31828
31974
  useEffect(() => {
31829
31975
  dispatch(changeRange([[xMinRange, xMaxRange], [yMinRange, yMaxRange]]));
31830
31976
  }, [dispatch, xMinRange, xMaxRange, yMinRange, yMaxRange]);
31977
+ const numSegments = graph.type === "segment" ? graph.numSegments : null;
31978
+ const originalPropsRef = useRef(props);
31979
+ const latestPropsRef = useLatestRef(props);
31980
+ useEffect(() => {
31981
+ // This conditional prevents the state from being "reinitialized" right
31982
+ // after the first render. This is an optimization, but also prevents
31983
+ // a bug where the graph would be marked "incorrect" during grading
31984
+ // even if the user never interacted with it.
31985
+ if (latestPropsRef.current !== originalPropsRef.current) {
31986
+ dispatch(reinitialize(latestPropsRef.current));
31987
+ }
31988
+ }, [graph.type, numSegments, latestPropsRef]);
31831
31989
  return /*#__PURE__*/React.createElement(MafsGraph, _extends({}, props, {
31832
31990
  state: state,
31833
31991
  dispatch: dispatch
@@ -43994,7 +44152,7 @@ const init = function init(options) {
43994
44152
 
43995
44153
  // This file is processed by a Rollup plugin (replace) to inject the production
43996
44154
  const libName = "@khanacademy/perseus";
43997
- const libVersion = "23.3.0";
44155
+ const libVersion = "23.5.0";
43998
44156
  addLibraryVersionToPerseusDebug(libName, libVersion);
43999
44157
 
44000
44158
  const apiVersion = {
@@ -46995,5 +47153,5 @@ const EditorJsonify = {
46995
47153
  }
46996
47154
  };
46997
47155
 
46998
- export { ApiOptions, ArrowPicker, ArticleRenderer, BaseRadio, Categorizer$1 as Categorizer, changeable as Changeable, ClassNames, ColorPicker, DashPicker, dependencies as Dependencies, EditorJsonify, ElementContainer, Errors, 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, PerseusError, 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, WidgetJsonifyDeprecated, widgets$1 as Widgets, addWidget, apiVersion, buildEmptyItemTreeForShape, buildMapper, components, constants, contentHasWidgetType, convertWidgetNameToEnum, displaySigFigs, generateTestCategorizerWidget, generateTestExpressionWidget, generateTestInteractiveGraphWidget, generateTestNumericInputWidget, generateTestPerseusItem, generateTestRadioWidget, genericPerseusItemData, getAnswerFromUserInput, getAnswersFromWidgets, getCorrectAnswerForWidgetId, 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, lockedEllipseFillStyles, lockedFigureColors, mediaQueries, parsePerseusItem, plotterPlotTypes, preprocessTex, registerAllWidgetsForTesting, shapes, shouldHaveIndividualAnswer, traverse, usePerseusI18n, violatingWidgets, allWidgets as widgets };
47156
+ export { ApiOptions, ArrowPicker, ArticleRenderer, BaseRadio, Categorizer$1 as Categorizer, changeable as Changeable, ClassNames, ColorPicker, DashPicker, dependencies as Dependencies, EditorJsonify, ElementContainer, Errors, 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, PerseusError, 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, WidgetJsonifyDeprecated, widgets$1 as Widgets, addWidget, apiVersion, buildEmptyItemTreeForShape, buildMapper, components, constants, contentHasWidgetType, convertWidgetNameToEnum, displaySigFigs, generateTestCategorizerWidget, generateTestExpressionWidget, generateTestInteractiveGraphWidget, generateTestNumericInputWidget, generateTestPerseusItem, generateTestRadioWidget, genericPerseusItemData, getAnswerFromUserInput, getAnswersFromWidgets, getCorrectAnswerForWidgetId, 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, mediaQueries, parsePerseusItem, plotterPlotTypes, preprocessTex, registerAllWidgetsForTesting, shapes, shouldHaveIndividualAnswer, traverse, usePerseusI18n, violatingWidgets, allWidgets as widgets };
46999
47157
  //# sourceMappingURL=index.js.map