@khanacademy/perseus 22.4.2 → 22.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/es/index.css CHANGED
@@ -4425,6 +4425,41 @@ html body .mq-editable-field .mq-selection .mq-nthroot-container * {
4425
4425
  visibility: visible;
4426
4426
  }
4427
4427
 
4428
+ .MafsView .movable-circle {
4429
+ cursor: grab;
4430
+ }
4431
+
4432
+ .MafsView .movable-circle.movable-circle--dragging {
4433
+ cursor: grabbing;
4434
+ outline: none;
4435
+ }
4436
+
4437
+ .MafsView .movable-circle .focus-ring {
4438
+ visibility: hidden; /* overridden when the circle is focused */
4439
+ stroke: var(--mafs-blue);
4440
+ stroke-width: 2px;
4441
+ fill: transparent;
4442
+ }
4443
+
4444
+ .MafsView .movable-circle:focus {
4445
+ outline: none;
4446
+ }
4447
+
4448
+ .MafsView .movable-circle:focus-visible .focus-ring {
4449
+ visibility: visible;
4450
+ }
4451
+
4452
+ .MafsView .movable-circle .movable-circle-handle {
4453
+ fill: var(--mafs-blue); /* WB fadedOffBlack64 */
4454
+ stroke: #fff;
4455
+ stroke-width: 2px;
4456
+ }
4457
+
4458
+ .MafsView .movable-circle .movable-circle-handle-dot {
4459
+ fill: #fff;
4460
+ r: 1.25px;
4461
+ }
4462
+
4428
4463
  @font-face {
4429
4464
  font-family: Mafs-MJXTEX;
4430
4465
  src: url("https://cdn.kastatic.org/fonts/mathjax/MathJax_Main-Regular.woff")
package/dist/es/index.js CHANGED
@@ -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, Line as Line$3, Point as Point$2, useMovable, Text as Text$1, Polygon, Circle as Circle$1, Coordinates, Mafs } from 'mafs';
32
+ import { vec, Line as Line$4, Point as Point$2, useMovable, Text as Text$1, Polygon, Circle as Circle$1, 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';
@@ -16523,7 +16523,7 @@ const Label$1 = GraphieClasses.createSimpleClass((graphie, props) => {
16523
16523
  }
16524
16524
  return elem;
16525
16525
  });
16526
- const Line$2 = GraphieClasses.createClass({
16526
+ const Line$3 = GraphieClasses.createClass({
16527
16527
  displayName: "Line",
16528
16528
  movableProps: ["children"],
16529
16529
  add: function (graphie) {
@@ -16642,7 +16642,7 @@ var Movables = {
16642
16642
  Arc: Arc,
16643
16643
  Circle: Circle,
16644
16644
  Label: Label$1,
16645
- Line: Line$2,
16645
+ Line: Line$3,
16646
16646
  MovableLine: MovableLine$3,
16647
16647
  MovablePoint: MovablePoint$3,
16648
16648
  Parabola: Parabola,
@@ -27570,7 +27570,7 @@ var Image$1 = {
27570
27570
  // @ts-expect-error - TS2339 - Property 'Label' does not exist on type 'typeof Graphie'.
27571
27571
  const Label = Graphie.Label;
27572
27572
  // @ts-expect-error - TS2339 - Property 'Line' does not exist on type 'typeof Graphie'.
27573
- const Line$1 = Graphie.Line;
27573
+ const Line$2 = Graphie.Line;
27574
27574
  // @ts-expect-error - TS2339 - Property 'MovablePoint' does not exist on type 'typeof Graphie'.
27575
27575
  const MovablePoint$1 = Graphie.MovablePoint;
27576
27576
  // @ts-expect-error - TS2339 - Property 'MovableLine' does not exist on type 'typeof Graphie'.
@@ -27792,7 +27792,7 @@ class Interaction extends React.Component {
27792
27792
  this._eval(element.options.endX),
27793
27793
  // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
27794
27794
  this._eval(element.options.endY)];
27795
- return /*#__PURE__*/React.createElement(Line$1, {
27795
+ return /*#__PURE__*/React.createElement(Line$2, {
27796
27796
  key: element.key,
27797
27797
  start: start,
27798
27798
  end: end,
@@ -28500,6 +28500,16 @@ function vectorsToPixels(vectors, graphState, translation = [0, 0]) {
28500
28500
  const transformToPx = matrixBuilder().translate(...translation).scale(width / (xMax - xMin), -height / (yMax - yMin)).get();
28501
28501
  return vectors.map(p => vec.transform(p, transformToPx));
28502
28502
  }
28503
+ function dimensionsToPixels(dimens, graphState) {
28504
+ const {
28505
+ range,
28506
+ width,
28507
+ height
28508
+ } = graphState;
28509
+ const [[xMin, xMax], [yMin, yMax]] = range;
28510
+ const transformToPx = matrixBuilder().scale(width / (xMax - xMin), height / (yMax - yMin)).get();
28511
+ return dimens.map(d => vec.transform(d, transformToPx));
28512
+ }
28503
28513
 
28504
28514
  // When converting points, we translate so the pixel point ends up in the right location.
28505
28515
  // This is because points are specific locations on graphs, unlike vectors.
@@ -28512,6 +28522,10 @@ const useTransformVectorsToPixels = (...vectors) => {
28512
28522
  const graphState = useGraphConfig();
28513
28523
  return vectorsToPixels(vectors, graphState);
28514
28524
  };
28525
+ const useTransformDimensionsToPixels = (...dimens) => {
28526
+ const graphState = useGraphConfig();
28527
+ return dimensionsToPixels(dimens, graphState);
28528
+ };
28515
28529
 
28516
28530
  function AxisLabels() {
28517
28531
  const {
@@ -28749,33 +28763,29 @@ function calculateAngleInDegrees([x, y]) {
28749
28763
  */
28750
28764
  const getIntersectionOfRayWithBox = (initialPoint, throughPoint, box) => {
28751
28765
  const [[xMin, xMax], [yMin, yMax]] = box;
28752
- const [aX, aY] = throughPoint;
28753
- const [bX, bY] = initialPoint;
28766
+ const [aX, aY] = initialPoint;
28767
+ const [bX, bY] = throughPoint;
28754
28768
  const yDiff = bY - aY;
28755
28769
  const xDiff = bX - aX;
28756
28770
  const slope = yDiff / xDiff;
28757
- const yAtXMin = slope * (xMin - aX) + aY;
28758
- const yAtXMax = slope * (xMax - aX) + aY;
28759
- const xAtYMin = (yMin - aY) / slope + aX;
28760
- const xAtYMax = (yMax - aY) / slope + aX;
28761
-
28762
- // clock analogy to describe quadrants
28771
+ const inverseSlope = 1 / slope;
28772
+ const xExtreme = xDiff < 0 ? xMin : xMax;
28773
+ const yExtreme = yDiff < 0 ? yMin : yMax;
28774
+ const yAtXExtreme = aY + (xExtreme - aX) * slope;
28775
+ const xAtYExtreme = aX + (yExtreme - aY) * inverseSlope;
28763
28776
  switch (true) {
28764
- // 12 o'clock to 2:59
28765
- case yDiff > 0 && xDiff >= 0:
28766
- return xAtYMax > xMax ? [xMax, yAtXMax] : [xAtYMax, yMax];
28767
- // 3 o'clock to 5:59
28768
- case yDiff <= 0 && xDiff > 0:
28769
- // xAtYMin evaluates to -Infinity here, so we use absolute value
28770
- return Math.abs(xAtYMin) > xMax ? [xMax, yAtXMax] : [xAtYMin, yMin];
28771
- // 9 o'clock to 11:59
28772
- case yDiff >= 0 && xDiff < 0:
28773
- return xAtYMax < xMin ? [xMin, yAtXMin] : [xAtYMax, yMax];
28774
- // 6 o'clock to 8:59
28775
- case yDiff < 0 && xDiff <= 0:
28776
- return xAtYMin < xMin ? [xMin, yAtXMin] : [xAtYMin, yMin];
28777
+ // does the ray exit the graph bounding box via the left or right edge?
28778
+ case isBetween(yAtXExtreme, yMin, yMax):
28779
+ return [xExtreme, yAtXExtreme];
28780
+
28781
+ // does the ray exit the graph bounding box via the top or bottom edge?
28782
+ case isBetween(xAtYExtreme, xMin, xMax):
28783
+ return [xAtYExtreme, yExtreme];
28777
28784
  default:
28778
- return [xMax, yAtXMax];
28785
+ // This default case is only reachable if the input is invalid
28786
+ // (initialPoint is outside the graph bounds, or initialPoint and
28787
+ // throughPoint are the same).
28788
+ return [0, 0];
28779
28789
  }
28780
28790
  };
28781
28791
  const getLines = points => {
@@ -28784,6 +28794,9 @@ const getLines = points => {
28784
28794
  return [point, next];
28785
28795
  });
28786
28796
  };
28797
+ function isBetween(x, low, high) {
28798
+ return x >= low && x <= high;
28799
+ }
28787
28800
 
28788
28801
  function SVGLine(props) {
28789
28802
  const {
@@ -28836,15 +28849,15 @@ const LockedLine = props => {
28836
28849
  lineStyle,
28837
28850
  kind,
28838
28851
  points,
28839
- showStartPoint,
28840
- showEndPoint,
28852
+ showPoint1,
28853
+ showPoint2,
28841
28854
  range
28842
28855
  } = props;
28843
28856
  const [point1, point2] = points;
28844
28857
  let line;
28845
28858
  if (kind === "ray") {
28846
28859
  // Rays extend to the end of the graph in one direction.
28847
- const extendedPoint = getIntersectionOfRayWithBox(point2.coord, point1.coord, range);
28860
+ const extendedPoint = getIntersectionOfRayWithBox(point1.coord, point2.coord, range);
28848
28861
  line = /*#__PURE__*/React.createElement(Vector, {
28849
28862
  tail: point1.coord,
28850
28863
  tip: extendedPoint,
@@ -28854,8 +28867,8 @@ const LockedLine = props => {
28854
28867
  }
28855
28868
  });
28856
28869
  } else {
28857
- const LineType = kind === "segment" ? Line$3.Segment : Line$3.ThroughPoints;
28858
- let arrowTip = kind === "segment" ? point2.coord : getIntersectionOfRayWithBox(point2.coord, point1.coord, range);
28870
+ const LineType = kind === "segment" ? Line$4.Segment : Line$4.ThroughPoints;
28871
+ let arrowTip = kind === "segment" ? point2.coord : getIntersectionOfRayWithBox(point1.coord, point2.coord, range);
28859
28872
  const direction = vec.sub(point2.coord, point1.coord);
28860
28873
  let angle = calculateAngleInDegrees(direction);
28861
28874
  const startArrowHead = kind !== "segment" && /*#__PURE__*/React.createElement(Arrowhead, {
@@ -28863,7 +28876,7 @@ const LockedLine = props => {
28863
28876
  tip: arrowTip,
28864
28877
  color: lockedFigureColors[color$1]
28865
28878
  });
28866
- arrowTip = kind === "segment" ? point1.coord : getIntersectionOfRayWithBox(point1.coord, point2.coord, range);
28879
+ arrowTip = kind === "segment" ? point1.coord : getIntersectionOfRayWithBox(point2.coord, point1.coord, range);
28867
28880
  angle = angle > 180 ? angle - 180 : angle + 180;
28868
28881
  const endArrowHead = kind !== "segment" && /*#__PURE__*/React.createElement(Arrowhead, {
28869
28882
  angle: angle,
@@ -28879,7 +28892,7 @@ const LockedLine = props => {
28879
28892
  }
28880
28893
  return /*#__PURE__*/React.createElement("g", {
28881
28894
  className: kind === "ray" ? "locked-ray" : "locked-line"
28882
- }, line, showStartPoint && /*#__PURE__*/React.createElement(Point$2, {
28895
+ }, line, showPoint1 && /*#__PURE__*/React.createElement(Point$2, {
28883
28896
  x: point1.coord[0],
28884
28897
  y: point1.coord[1],
28885
28898
  svgCircleProps: {
@@ -28889,7 +28902,7 @@ const LockedLine = props => {
28889
28902
  strokeWidth: spacing.xxxxSmall_2
28890
28903
  }
28891
28904
  }
28892
- }), showEndPoint && /*#__PURE__*/React.createElement(Point$2, {
28905
+ }), showPoint2 && /*#__PURE__*/React.createElement(Point$2, {
28893
28906
  x: point2.coord[0],
28894
28907
  y: point2.coord[1],
28895
28908
  svgCircleProps: {
@@ -29149,9 +29162,10 @@ function tabIndex(config) {
29149
29162
  return undefined;
29150
29163
  }
29151
29164
 
29152
- const Segment = props => {
29165
+ const MovableLine = props => {
29153
29166
  const {
29154
- onMoveLine,
29167
+ onMoveLine = () => {},
29168
+ onMovePoint = () => {},
29155
29169
  color,
29156
29170
  points: [start, end],
29157
29171
  extend
@@ -29171,18 +29185,19 @@ const Segment = props => {
29171
29185
  const {
29172
29186
  visiblePoint: visiblePoint1,
29173
29187
  focusableHandle: focusableHandle1
29174
- } = useControlPoint(start, color, p => props.onMovePoint(0, p));
29188
+ } = useControlPoint(start, color, p => onMovePoint(0, p));
29175
29189
  const {
29176
29190
  visiblePoint: visiblePoint2,
29177
29191
  focusableHandle: focusableHandle2
29178
- } = useControlPoint(end, color, p => props.onMovePoint(1, p));
29179
- return /*#__PURE__*/React.createElement(React.Fragment, null, focusableHandle1, /*#__PURE__*/React.createElement(MovableLine, {
29192
+ } = useControlPoint(end, color, p => onMovePoint(1, p));
29193
+ const line = /*#__PURE__*/React.createElement(Line$1, {
29180
29194
  start: start,
29181
29195
  end: end,
29182
29196
  stroke: color,
29183
29197
  extend: extend,
29184
29198
  onMove: onMoveLine
29185
- }), focusableHandle2, visiblePoint1, visiblePoint2);
29199
+ });
29200
+ return /*#__PURE__*/React.createElement(React.Fragment, null, focusableHandle1, line, focusableHandle2, visiblePoint1, visiblePoint2);
29186
29201
  };
29187
29202
  function useControlPoint(point, color, onMovePoint) {
29188
29203
  const {
@@ -29228,7 +29243,7 @@ function useControlPoint(point, color, onMovePoint) {
29228
29243
  };
29229
29244
  }
29230
29245
  const defaultStroke = "var(--movable-line-stroke-color)";
29231
- const MovableLine = props => {
29246
+ const Line$1 = props => {
29232
29247
  const {
29233
29248
  start,
29234
29249
  end,
@@ -29236,29 +29251,29 @@ const MovableLine = props => {
29236
29251
  extend,
29237
29252
  stroke = defaultStroke
29238
29253
  } = props;
29239
- const midpoint = vec.midpoint(start, end);
29240
29254
  const [startPtPx, endPtPx] = useTransformVectorsToPixels(start, end);
29241
29255
  const {
29242
29256
  range,
29243
- graphDimensionsInPixels
29257
+ graphDimensionsInPixels,
29258
+ snapStep
29244
29259
  } = useGraphConfig();
29245
29260
  let startExtend = undefined;
29246
29261
  let endExtend = undefined;
29247
29262
  if (extend) {
29248
29263
  const trimmedRange = trimRange(range, graphDimensionsInPixels);
29249
- startExtend = extend.start ? getIntersectionOfRayWithBox(start, end, trimmedRange) : undefined;
29250
- endExtend = extend.end ? getIntersectionOfRayWithBox(end, start, trimmedRange) : undefined;
29264
+ startExtend = extend.start ? getIntersectionOfRayWithBox(end, start, trimmedRange) : undefined;
29265
+ endExtend = extend.end ? getIntersectionOfRayWithBox(start, end, trimmedRange) : undefined;
29251
29266
  }
29252
29267
  const line = useRef(null);
29253
29268
  const {
29254
29269
  dragging
29255
29270
  } = useMovable({
29256
29271
  gestureTarget: line,
29257
- point: midpoint,
29272
+ point: start,
29258
29273
  onMove: newPoint => {
29259
- onMove(vec.sub(newPoint, midpoint));
29274
+ onMove(vec.sub(newPoint, start));
29260
29275
  },
29261
- constrain: p => p
29276
+ constrain: p => snap(snapStep, p)
29262
29277
  });
29263
29278
  return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("g", {
29264
29279
  ref: line,
@@ -29331,7 +29346,7 @@ const SegmentGraph = props => {
29331
29346
  const {
29332
29347
  coords: segments
29333
29348
  } = props.graphState;
29334
- return /*#__PURE__*/React.createElement(React.Fragment, null, segments == null ? void 0 : segments.map((segment, i) => /*#__PURE__*/React.createElement(Segment, {
29349
+ return /*#__PURE__*/React.createElement(React.Fragment, null, segments == null ? void 0 : segments.map((segment, i) => /*#__PURE__*/React.createElement(MovableLine, {
29335
29350
  key: i,
29336
29351
  points: segment,
29337
29352
  onMoveLine: delta => {
@@ -29351,7 +29366,7 @@ const LinearGraph = props => {
29351
29366
  coords: lines
29352
29367
  } = props.graphState;
29353
29368
  const colors = ["var(--movable-line-stroke-color)", "var(--mafs-violet)"];
29354
- return /*#__PURE__*/React.createElement(React.Fragment, null, lines == null ? void 0 : lines.map((line, i) => /*#__PURE__*/React.createElement(Segment, {
29369
+ return /*#__PURE__*/React.createElement(React.Fragment, null, lines == null ? void 0 : lines.map((line, i) => /*#__PURE__*/React.createElement(MovableLine, {
29355
29370
  key: i,
29356
29371
  points: line,
29357
29372
  onMoveLine: delta => {
@@ -29375,7 +29390,7 @@ const RayGraph = props => {
29375
29390
  } = props.graphState;
29376
29391
  const handleMoveLine = delta => dispatch(moveLine(0, delta));
29377
29392
  const handleMovePoint = (pointIndex, newPoint) => dispatch(moveControlPoint(pointIndex, newPoint, 0));
29378
- return /*#__PURE__*/React.createElement(Segment
29393
+ return /*#__PURE__*/React.createElement(MovableLine
29379
29394
  // a ray only has one line
29380
29395
  , {
29381
29396
  points: lines[0],
@@ -29527,7 +29542,7 @@ const isRightAngle = angle => Math.abs(angle - Math.PI / 2) < 0.01;
29527
29542
  */
29528
29543
  const shouldDrawArcOutside = (midpoint, vertex, range, polygonLines) => {
29529
29544
  // Create a ray from the midpoint (inside angle) to the edge of the range
29530
- const rangeIntersectionPoint = getIntersectionOfRayWithBox(vertex, midpoint, range);
29545
+ const rangeIntersectionPoint = getIntersectionOfRayWithBox(midpoint, vertex, range);
29531
29546
  let lineIntersectionCount = 0;
29532
29547
  polygonLines.forEach(line => linesIntersect([vertex, rangeIntersectionPoint], line) && lineIntersectionCount++);
29533
29548
 
@@ -29668,14 +29683,12 @@ function initializeGraphState(params) {
29668
29683
  graph,
29669
29684
  step,
29670
29685
  snapStep,
29671
- range,
29672
- markings
29686
+ range
29673
29687
  } = params;
29674
29688
  const shared = {
29675
29689
  hasBeenInteractedWith: false,
29676
29690
  range,
29677
- snapStep,
29678
- markings
29691
+ snapStep
29679
29692
  };
29680
29693
  switch (graph.type) {
29681
29694
  case "segment":
@@ -29911,16 +29924,10 @@ function CircleGraph(props) {
29911
29924
  center,
29912
29925
  radiusPoint
29913
29926
  } = graphState;
29914
- return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Circle$1, {
29927
+ return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(MovableCircle, {
29915
29928
  center: center,
29916
29929
  radius: getRadius(graphState),
29917
- fillOpacity: 0,
29918
- color: color.blue
29919
- }), /*#__PURE__*/React.createElement(StyledMovablePoint, {
29920
- point: center,
29921
- onMove: newCenter => {
29922
- dispatch(moveCenter(newCenter));
29923
- }
29930
+ onMove: newCenter => dispatch(moveCenter(newCenter))
29924
29931
  }), /*#__PURE__*/React.createElement(StyledMovablePoint, {
29925
29932
  point: radiusPoint,
29926
29933
  onMove: newRadiusPoint => {
@@ -29928,6 +29935,80 @@ function CircleGraph(props) {
29928
29935
  }
29929
29936
  }));
29930
29937
  }
29938
+ function MovableCircle(props) {
29939
+ const {
29940
+ center,
29941
+ radius,
29942
+ onMove
29943
+ } = props;
29944
+ const {
29945
+ snapStep
29946
+ } = useGraphConfig();
29947
+ const draggableRef = useRef(null);
29948
+ const {
29949
+ dragging
29950
+ } = useMovable({
29951
+ gestureTarget: draggableRef,
29952
+ point: center,
29953
+ onMove,
29954
+ constrain: p => snap(snapStep, p)
29955
+ });
29956
+ const [centerPx] = useTransformVectorsToPixels(center);
29957
+ const [radiiPx] = useTransformDimensionsToPixels([radius, radius]);
29958
+ return /*#__PURE__*/React.createElement("g", {
29959
+ ref: draggableRef,
29960
+ tabIndex: 0,
29961
+ className: `movable-circle ${dragging ? "movable-circle--dragging" : ""}`
29962
+ }, /*#__PURE__*/React.createElement("ellipse", {
29963
+ className: "focus-ring",
29964
+ cx: centerPx[0],
29965
+ cy: centerPx[1],
29966
+ rx: radiiPx[0] + 3,
29967
+ ry: radiiPx[1] + 3
29968
+ }), /*#__PURE__*/React.createElement(Circle$1, {
29969
+ center: center,
29970
+ radius: radius,
29971
+ fillOpacity: 0,
29972
+ color: color.blue
29973
+ }), /*#__PURE__*/React.createElement(DragHandle, {
29974
+ center: center
29975
+ }));
29976
+ }
29977
+ const dragHandleDimensions = [24, 14];
29978
+ const dragHandleDotPositions = crossProduct([-4.4, 0, 4.4], [-2.1, 2.1]);
29979
+ function DragHandle(props) {
29980
+ const {
29981
+ center
29982
+ } = props;
29983
+ const cornerRadius = Math.min(...dragHandleDimensions) / 2;
29984
+ const [centerPx] = useTransformVectorsToPixels(center);
29985
+ const topLeft = vec.sub(centerPx, vec.scale(dragHandleDimensions, 0.5));
29986
+ return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("rect", {
29987
+ className: "movable-circle-handle",
29988
+ x: topLeft[0],
29989
+ y: topLeft[1],
29990
+ width: dragHandleDimensions[0],
29991
+ height: dragHandleDimensions[1],
29992
+ rx: cornerRadius,
29993
+ ry: cornerRadius
29994
+ }), dragHandleDotPositions.map(offsetPx => {
29995
+ const [xPx, yPx] = vec.add(offsetPx, centerPx);
29996
+ return /*#__PURE__*/React.createElement("circle", {
29997
+ className: "movable-circle-handle-dot",
29998
+ cx: xPx,
29999
+ cy: yPx
30000
+ });
30001
+ }));
30002
+ }
30003
+ function crossProduct(as, bs) {
30004
+ const result = [];
30005
+ for (const a of as) {
30006
+ for (const b of bs) {
30007
+ result.push([a, b]);
30008
+ }
30009
+ }
30010
+ return result;
30011
+ }
29931
30012
 
29932
30013
  function PointGraph(props) {
29933
30014
  const {
@@ -31099,35 +31180,22 @@ function mafsStateToInteractiveGraph(state) {
31099
31180
  return _extends({}, state);
31100
31181
  }
31101
31182
  const StatefulMafsGraph = /*#__PURE__*/React.forwardRef((props, ref) => {
31183
+ const {
31184
+ onChange
31185
+ } = props;
31102
31186
  const [state, dispatch] = React.useReducer(interactiveGraphReducer, props, initializeGraphState);
31103
31187
  useImperativeHandle(ref, () => ({
31104
31188
  getUserInput: () => getGradableGraph(state, props.graph)
31105
31189
  }));
31106
- function onChange(next) {
31107
- props.onChange(mafsStateToInteractiveGraph(next));
31108
- }
31109
- return /*#__PURE__*/React.createElement(MafsGraph, _extends({}, props, {
31110
- state: state,
31111
- dispatch: dispatch,
31112
- onChange: onChange
31113
- }));
31114
- });
31115
- const MafsGraph = props => {
31116
- const {
31117
- state,
31118
- dispatch,
31119
- labels
31120
- } = props;
31121
- const [width, height] = props.box;
31122
31190
  const prevState = useRef(state);
31123
31191
  useEffect(() => {
31124
31192
  if (prevState.current !== state) {
31125
- props.onChange({
31193
+ onChange(mafsStateToInteractiveGraph({
31126
31194
  graph: state
31127
- });
31195
+ }));
31128
31196
  }
31129
31197
  prevState.current = state;
31130
- }, [props, state]);
31198
+ }, [onChange, state]);
31131
31199
 
31132
31200
  // Destructuring first to keep useEffect from making excess calls
31133
31201
  const [xSnap, ySnap] = props.snapStep;
@@ -31140,6 +31208,18 @@ const MafsGraph = props => {
31140
31208
  useEffect(() => {
31141
31209
  dispatch(changeRange([[xMinRange, xMaxRange], [yMinRange, yMaxRange]]));
31142
31210
  }, [dispatch, xMinRange, xMaxRange, yMinRange, yMaxRange]);
31211
+ return /*#__PURE__*/React.createElement(MafsGraph, _extends({}, props, {
31212
+ state: state,
31213
+ dispatch: dispatch
31214
+ }));
31215
+ });
31216
+ const MafsGraph = props => {
31217
+ const {
31218
+ state,
31219
+ dispatch,
31220
+ labels
31221
+ } = props;
31222
+ const [width, height] = props.box;
31143
31223
  return /*#__PURE__*/React.createElement(GraphConfigContext.Provider, {
31144
31224
  value: {
31145
31225
  range: state.range,
@@ -31172,8 +31252,8 @@ const MafsGraph = props => {
31172
31252
  }, props.markings === "graph" && /*#__PURE__*/React.createElement(AxisLabels, null), /*#__PURE__*/React.createElement(Mafs, {
31173
31253
  preserveAspectRatio: false,
31174
31254
  viewBox: {
31175
- x: props.range[0],
31176
- y: props.range[1],
31255
+ x: state.range[0],
31256
+ y: state.range[1],
31177
31257
  padding: 0
31178
31258
  },
31179
31259
  pan: false,
@@ -31183,12 +31263,12 @@ const MafsGraph = props => {
31183
31263
  }, /*#__PURE__*/React.createElement(SvgDefs, null), /*#__PURE__*/React.createElement(Grid, {
31184
31264
  tickStep: props.step,
31185
31265
  gridStep: props.gridStep,
31186
- range: props.range,
31266
+ range: state.range,
31187
31267
  containerSizeClass: props.containerSizeClass,
31188
31268
  markings: props.markings
31189
31269
  }), props.lockedFigures && /*#__PURE__*/React.createElement(GraphLockedLayer, {
31190
31270
  lockedFigures: props.lockedFigures,
31191
- range: props.range
31271
+ range: state.range
31192
31272
  }), renderGraph({
31193
31273
  state,
31194
31274
  dispatch
@@ -43289,7 +43369,7 @@ const init = function init(options) {
43289
43369
 
43290
43370
  // This file is processed by a Rollup plugin (replace) to inject the production
43291
43371
  const libName = "@khanacademy/perseus";
43292
- const libVersion = "22.4.2";
43372
+ const libVersion = "22.5.1";
43293
43373
  addLibraryVersionToPerseusDebug(libName, libVersion);
43294
43374
 
43295
43375
  const apiVersion = {