@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/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/index.js CHANGED
@@ -16669,7 +16669,7 @@ const Label$1 = GraphieClasses.createSimpleClass((graphie, props) => {
16669
16669
  }
16670
16670
  return elem;
16671
16671
  });
16672
- const Line$2 = GraphieClasses.createClass({
16672
+ const Line$3 = GraphieClasses.createClass({
16673
16673
  displayName: "Line",
16674
16674
  movableProps: ["children"],
16675
16675
  add: function (graphie) {
@@ -16788,7 +16788,7 @@ var Movables = {
16788
16788
  Arc: Arc,
16789
16789
  Circle: Circle,
16790
16790
  Label: Label$1,
16791
- Line: Line$2,
16791
+ Line: Line$3,
16792
16792
  MovableLine: MovableLine$3,
16793
16793
  MovablePoint: MovablePoint$3,
16794
16794
  Parabola: Parabola,
@@ -27849,7 +27849,7 @@ var Image$1 = {
27849
27849
  // @ts-expect-error - TS2339 - Property 'Label' does not exist on type 'typeof Graphie'.
27850
27850
  const Label = Graphie.Label;
27851
27851
  // @ts-expect-error - TS2339 - Property 'Line' does not exist on type 'typeof Graphie'.
27852
- const Line$1 = Graphie.Line;
27852
+ const Line$2 = Graphie.Line;
27853
27853
  // @ts-expect-error - TS2339 - Property 'MovablePoint' does not exist on type 'typeof Graphie'.
27854
27854
  const MovablePoint$1 = Graphie.MovablePoint;
27855
27855
  // @ts-expect-error - TS2339 - Property 'MovableLine' does not exist on type 'typeof Graphie'.
@@ -28076,7 +28076,7 @@ class Interaction extends React__namespace.Component {
28076
28076
  this._eval(element.options.endX),
28077
28077
  // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
28078
28078
  this._eval(element.options.endY)];
28079
- return /*#__PURE__*/React__namespace.createElement(Line$1, {
28079
+ return /*#__PURE__*/React__namespace.createElement(Line$2, {
28080
28080
  key: element.key,
28081
28081
  start: start,
28082
28082
  end: end,
@@ -28785,6 +28785,16 @@ function vectorsToPixels(vectors, graphState) {
28785
28785
  const transformToPx = matrixBuilder().translate(...translation).scale(width / (xMax - xMin), -height / (yMax - yMin)).get();
28786
28786
  return vectors.map(p => mafs.vec.transform(p, transformToPx));
28787
28787
  }
28788
+ function dimensionsToPixels(dimens, graphState) {
28789
+ const {
28790
+ range,
28791
+ width,
28792
+ height
28793
+ } = graphState;
28794
+ const [[xMin, xMax], [yMin, yMax]] = range;
28795
+ const transformToPx = matrixBuilder().scale(width / (xMax - xMin), height / (yMax - yMin)).get();
28796
+ return dimens.map(d => mafs.vec.transform(d, transformToPx));
28797
+ }
28788
28798
 
28789
28799
  // When converting points, we translate so the pixel point ends up in the right location.
28790
28800
  // This is because points are specific locations on graphs, unlike vectors.
@@ -28800,6 +28810,13 @@ const useTransformVectorsToPixels = function () {
28800
28810
  }
28801
28811
  return vectorsToPixels(vectors, graphState);
28802
28812
  };
28813
+ const useTransformDimensionsToPixels = function () {
28814
+ const graphState = useGraphConfig();
28815
+ for (var _len2 = arguments.length, dimens = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
28816
+ dimens[_key2] = arguments[_key2];
28817
+ }
28818
+ return dimensionsToPixels(dimens, graphState);
28819
+ };
28803
28820
 
28804
28821
  function AxisLabels() {
28805
28822
  const {
@@ -29036,33 +29053,29 @@ function calculateAngleInDegrees(_ref) {
29036
29053
  */
29037
29054
  const getIntersectionOfRayWithBox = (initialPoint, throughPoint, box) => {
29038
29055
  const [[xMin, xMax], [yMin, yMax]] = box;
29039
- const [aX, aY] = throughPoint;
29040
- const [bX, bY] = initialPoint;
29056
+ const [aX, aY] = initialPoint;
29057
+ const [bX, bY] = throughPoint;
29041
29058
  const yDiff = bY - aY;
29042
29059
  const xDiff = bX - aX;
29043
29060
  const slope = yDiff / xDiff;
29044
- const yAtXMin = slope * (xMin - aX) + aY;
29045
- const yAtXMax = slope * (xMax - aX) + aY;
29046
- const xAtYMin = (yMin - aY) / slope + aX;
29047
- const xAtYMax = (yMax - aY) / slope + aX;
29048
-
29049
- // clock analogy to describe quadrants
29061
+ const inverseSlope = 1 / slope;
29062
+ const xExtreme = xDiff < 0 ? xMin : xMax;
29063
+ const yExtreme = yDiff < 0 ? yMin : yMax;
29064
+ const yAtXExtreme = aY + (xExtreme - aX) * slope;
29065
+ const xAtYExtreme = aX + (yExtreme - aY) * inverseSlope;
29050
29066
  switch (true) {
29051
- // 12 o'clock to 2:59
29052
- case yDiff > 0 && xDiff >= 0:
29053
- return xAtYMax > xMax ? [xMax, yAtXMax] : [xAtYMax, yMax];
29054
- // 3 o'clock to 5:59
29055
- case yDiff <= 0 && xDiff > 0:
29056
- // xAtYMin evaluates to -Infinity here, so we use absolute value
29057
- return Math.abs(xAtYMin) > xMax ? [xMax, yAtXMax] : [xAtYMin, yMin];
29058
- // 9 o'clock to 11:59
29059
- case yDiff >= 0 && xDiff < 0:
29060
- return xAtYMax < xMin ? [xMin, yAtXMin] : [xAtYMax, yMax];
29061
- // 6 o'clock to 8:59
29062
- case yDiff < 0 && xDiff <= 0:
29063
- return xAtYMin < xMin ? [xMin, yAtXMin] : [xAtYMin, yMin];
29067
+ // does the ray exit the graph bounding box via the left or right edge?
29068
+ case isBetween(yAtXExtreme, yMin, yMax):
29069
+ return [xExtreme, yAtXExtreme];
29070
+
29071
+ // does the ray exit the graph bounding box via the top or bottom edge?
29072
+ case isBetween(xAtYExtreme, xMin, xMax):
29073
+ return [xAtYExtreme, yExtreme];
29064
29074
  default:
29065
- return [xMax, yAtXMax];
29075
+ // This default case is only reachable if the input is invalid
29076
+ // (initialPoint is outside the graph bounds, or initialPoint and
29077
+ // throughPoint are the same).
29078
+ return [0, 0];
29066
29079
  }
29067
29080
  };
29068
29081
  const getLines = points => {
@@ -29071,6 +29084,9 @@ const getLines = points => {
29071
29084
  return [point, next];
29072
29085
  });
29073
29086
  };
29087
+ function isBetween(x, low, high) {
29088
+ return x >= low && x <= high;
29089
+ }
29074
29090
 
29075
29091
  function SVGLine(props) {
29076
29092
  const {
@@ -29123,15 +29139,15 @@ const LockedLine = props => {
29123
29139
  lineStyle,
29124
29140
  kind,
29125
29141
  points,
29126
- showStartPoint,
29127
- showEndPoint,
29142
+ showPoint1,
29143
+ showPoint2,
29128
29144
  range
29129
29145
  } = props;
29130
29146
  const [point1, point2] = points;
29131
29147
  let line;
29132
29148
  if (kind === "ray") {
29133
29149
  // Rays extend to the end of the graph in one direction.
29134
- const extendedPoint = getIntersectionOfRayWithBox(point2.coord, point1.coord, range);
29150
+ const extendedPoint = getIntersectionOfRayWithBox(point1.coord, point2.coord, range);
29135
29151
  line = /*#__PURE__*/React__namespace.createElement(Vector, {
29136
29152
  tail: point1.coord,
29137
29153
  tip: extendedPoint,
@@ -29142,7 +29158,7 @@ const LockedLine = props => {
29142
29158
  });
29143
29159
  } else {
29144
29160
  const LineType = kind === "segment" ? mafs.Line.Segment : mafs.Line.ThroughPoints;
29145
- let arrowTip = kind === "segment" ? point2.coord : getIntersectionOfRayWithBox(point2.coord, point1.coord, range);
29161
+ let arrowTip = kind === "segment" ? point2.coord : getIntersectionOfRayWithBox(point1.coord, point2.coord, range);
29146
29162
  const direction = mafs.vec.sub(point2.coord, point1.coord);
29147
29163
  let angle = calculateAngleInDegrees(direction);
29148
29164
  const startArrowHead = kind !== "segment" && /*#__PURE__*/React__namespace.createElement(Arrowhead, {
@@ -29150,7 +29166,7 @@ const LockedLine = props => {
29150
29166
  tip: arrowTip,
29151
29167
  color: lockedFigureColors[color]
29152
29168
  });
29153
- arrowTip = kind === "segment" ? point1.coord : getIntersectionOfRayWithBox(point1.coord, point2.coord, range);
29169
+ arrowTip = kind === "segment" ? point1.coord : getIntersectionOfRayWithBox(point2.coord, point1.coord, range);
29154
29170
  angle = angle > 180 ? angle - 180 : angle + 180;
29155
29171
  const endArrowHead = kind !== "segment" && /*#__PURE__*/React__namespace.createElement(Arrowhead, {
29156
29172
  angle: angle,
@@ -29166,7 +29182,7 @@ const LockedLine = props => {
29166
29182
  }
29167
29183
  return /*#__PURE__*/React__namespace.createElement("g", {
29168
29184
  className: kind === "ray" ? "locked-ray" : "locked-line"
29169
- }, line, showStartPoint && /*#__PURE__*/React__namespace.createElement(mafs.Point, {
29185
+ }, line, showPoint1 && /*#__PURE__*/React__namespace.createElement(mafs.Point, {
29170
29186
  x: point1.coord[0],
29171
29187
  y: point1.coord[1],
29172
29188
  svgCircleProps: {
@@ -29176,7 +29192,7 @@ const LockedLine = props => {
29176
29192
  strokeWidth: wonderBlocksTokens.spacing.xxxxSmall_2
29177
29193
  }
29178
29194
  }
29179
- }), showEndPoint && /*#__PURE__*/React__namespace.createElement(mafs.Point, {
29195
+ }), showPoint2 && /*#__PURE__*/React__namespace.createElement(mafs.Point, {
29180
29196
  x: point2.coord[0],
29181
29197
  y: point2.coord[1],
29182
29198
  svgCircleProps: {
@@ -29438,9 +29454,10 @@ function tabIndex(config) {
29438
29454
  return undefined;
29439
29455
  }
29440
29456
 
29441
- const Segment = props => {
29457
+ const MovableLine = props => {
29442
29458
  const {
29443
- onMoveLine,
29459
+ onMoveLine = () => {},
29460
+ onMovePoint = () => {},
29444
29461
  color,
29445
29462
  points: [start, end],
29446
29463
  extend
@@ -29460,18 +29477,19 @@ const Segment = props => {
29460
29477
  const {
29461
29478
  visiblePoint: visiblePoint1,
29462
29479
  focusableHandle: focusableHandle1
29463
- } = useControlPoint(start, color, p => props.onMovePoint(0, p));
29480
+ } = useControlPoint(start, color, p => onMovePoint(0, p));
29464
29481
  const {
29465
29482
  visiblePoint: visiblePoint2,
29466
29483
  focusableHandle: focusableHandle2
29467
- } = useControlPoint(end, color, p => props.onMovePoint(1, p));
29468
- return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, focusableHandle1, /*#__PURE__*/React__namespace.createElement(MovableLine, {
29484
+ } = useControlPoint(end, color, p => onMovePoint(1, p));
29485
+ const line = /*#__PURE__*/React__namespace.createElement(Line$1, {
29469
29486
  start: start,
29470
29487
  end: end,
29471
29488
  stroke: color,
29472
29489
  extend: extend,
29473
29490
  onMove: onMoveLine
29474
- }), focusableHandle2, visiblePoint1, visiblePoint2);
29491
+ });
29492
+ return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, focusableHandle1, line, focusableHandle2, visiblePoint1, visiblePoint2);
29475
29493
  };
29476
29494
  function useControlPoint(point, color, onMovePoint) {
29477
29495
  const {
@@ -29517,7 +29535,7 @@ function useControlPoint(point, color, onMovePoint) {
29517
29535
  };
29518
29536
  }
29519
29537
  const defaultStroke = "var(--movable-line-stroke-color)";
29520
- const MovableLine = props => {
29538
+ const Line$1 = props => {
29521
29539
  const {
29522
29540
  start,
29523
29541
  end,
@@ -29525,29 +29543,29 @@ const MovableLine = props => {
29525
29543
  extend,
29526
29544
  stroke = defaultStroke
29527
29545
  } = props;
29528
- const midpoint = mafs.vec.midpoint(start, end);
29529
29546
  const [startPtPx, endPtPx] = useTransformVectorsToPixels(start, end);
29530
29547
  const {
29531
29548
  range,
29532
- graphDimensionsInPixels
29549
+ graphDimensionsInPixels,
29550
+ snapStep
29533
29551
  } = useGraphConfig();
29534
29552
  let startExtend = undefined;
29535
29553
  let endExtend = undefined;
29536
29554
  if (extend) {
29537
29555
  const trimmedRange = trimRange(range, graphDimensionsInPixels);
29538
- startExtend = extend.start ? getIntersectionOfRayWithBox(start, end, trimmedRange) : undefined;
29539
- endExtend = extend.end ? getIntersectionOfRayWithBox(end, start, trimmedRange) : undefined;
29556
+ startExtend = extend.start ? getIntersectionOfRayWithBox(end, start, trimmedRange) : undefined;
29557
+ endExtend = extend.end ? getIntersectionOfRayWithBox(start, end, trimmedRange) : undefined;
29540
29558
  }
29541
29559
  const line = React.useRef(null);
29542
29560
  const {
29543
29561
  dragging
29544
29562
  } = mafs.useMovable({
29545
29563
  gestureTarget: line,
29546
- point: midpoint,
29564
+ point: start,
29547
29565
  onMove: newPoint => {
29548
- onMove(mafs.vec.sub(newPoint, midpoint));
29566
+ onMove(mafs.vec.sub(newPoint, start));
29549
29567
  },
29550
- constrain: p => p
29568
+ constrain: p => snap(snapStep, p)
29551
29569
  });
29552
29570
  return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement("g", {
29553
29571
  ref: line,
@@ -29620,7 +29638,7 @@ const SegmentGraph = props => {
29620
29638
  const {
29621
29639
  coords: segments
29622
29640
  } = props.graphState;
29623
- return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, segments?.map((segment, i) => /*#__PURE__*/React__namespace.createElement(Segment, {
29641
+ return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, segments?.map((segment, i) => /*#__PURE__*/React__namespace.createElement(MovableLine, {
29624
29642
  key: i,
29625
29643
  points: segment,
29626
29644
  onMoveLine: delta => {
@@ -29640,7 +29658,7 @@ const LinearGraph = props => {
29640
29658
  coords: lines
29641
29659
  } = props.graphState;
29642
29660
  const colors = ["var(--movable-line-stroke-color)", "var(--mafs-violet)"];
29643
- return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, lines?.map((line, i) => /*#__PURE__*/React__namespace.createElement(Segment, {
29661
+ return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, lines?.map((line, i) => /*#__PURE__*/React__namespace.createElement(MovableLine, {
29644
29662
  key: i,
29645
29663
  points: line,
29646
29664
  onMoveLine: delta => {
@@ -29664,7 +29682,7 @@ const RayGraph = props => {
29664
29682
  } = props.graphState;
29665
29683
  const handleMoveLine = delta => dispatch(moveLine(0, delta));
29666
29684
  const handleMovePoint = (pointIndex, newPoint) => dispatch(moveControlPoint(pointIndex, newPoint, 0));
29667
- return /*#__PURE__*/React__namespace.createElement(Segment
29685
+ return /*#__PURE__*/React__namespace.createElement(MovableLine
29668
29686
  // a ray only has one line
29669
29687
  , {
29670
29688
  points: lines[0],
@@ -29822,7 +29840,7 @@ const isRightAngle = angle => Math.abs(angle - Math.PI / 2) < 0.01;
29822
29840
  */
29823
29841
  const shouldDrawArcOutside = (midpoint, vertex, range, polygonLines) => {
29824
29842
  // Create a ray from the midpoint (inside angle) to the edge of the range
29825
- const rangeIntersectionPoint = getIntersectionOfRayWithBox(vertex, midpoint, range);
29843
+ const rangeIntersectionPoint = getIntersectionOfRayWithBox(midpoint, vertex, range);
29826
29844
  let lineIntersectionCount = 0;
29827
29845
  polygonLines.forEach(line => linesIntersect([vertex, rangeIntersectionPoint], line) && lineIntersectionCount++);
29828
29846
 
@@ -29966,14 +29984,12 @@ function initializeGraphState(params) {
29966
29984
  graph,
29967
29985
  step,
29968
29986
  snapStep,
29969
- range,
29970
- markings
29987
+ range
29971
29988
  } = params;
29972
29989
  const shared = {
29973
29990
  hasBeenInteractedWith: false,
29974
29991
  range,
29975
- snapStep,
29976
- markings
29992
+ snapStep
29977
29993
  };
29978
29994
  switch (graph.type) {
29979
29995
  case "segment":
@@ -30224,16 +30240,10 @@ function CircleGraph(props) {
30224
30240
  center,
30225
30241
  radiusPoint
30226
30242
  } = graphState;
30227
- return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(mafs.Circle, {
30243
+ return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(MovableCircle, {
30228
30244
  center: center,
30229
30245
  radius: getRadius(graphState),
30230
- fillOpacity: 0,
30231
- color: wonderBlocksTokens.color.blue
30232
- }), /*#__PURE__*/React__namespace.createElement(StyledMovablePoint, {
30233
- point: center,
30234
- onMove: newCenter => {
30235
- dispatch(moveCenter(newCenter));
30236
- }
30246
+ onMove: newCenter => dispatch(moveCenter(newCenter))
30237
30247
  }), /*#__PURE__*/React__namespace.createElement(StyledMovablePoint, {
30238
30248
  point: radiusPoint,
30239
30249
  onMove: newRadiusPoint => {
@@ -30241,6 +30251,80 @@ function CircleGraph(props) {
30241
30251
  }
30242
30252
  }));
30243
30253
  }
30254
+ function MovableCircle(props) {
30255
+ const {
30256
+ center,
30257
+ radius,
30258
+ onMove
30259
+ } = props;
30260
+ const {
30261
+ snapStep
30262
+ } = useGraphConfig();
30263
+ const draggableRef = React.useRef(null);
30264
+ const {
30265
+ dragging
30266
+ } = mafs.useMovable({
30267
+ gestureTarget: draggableRef,
30268
+ point: center,
30269
+ onMove,
30270
+ constrain: p => snap(snapStep, p)
30271
+ });
30272
+ const [centerPx] = useTransformVectorsToPixels(center);
30273
+ const [radiiPx] = useTransformDimensionsToPixels([radius, radius]);
30274
+ return /*#__PURE__*/React__namespace.createElement("g", {
30275
+ ref: draggableRef,
30276
+ tabIndex: 0,
30277
+ className: `movable-circle ${dragging ? "movable-circle--dragging" : ""}`
30278
+ }, /*#__PURE__*/React__namespace.createElement("ellipse", {
30279
+ className: "focus-ring",
30280
+ cx: centerPx[0],
30281
+ cy: centerPx[1],
30282
+ rx: radiiPx[0] + 3,
30283
+ ry: radiiPx[1] + 3
30284
+ }), /*#__PURE__*/React__namespace.createElement(mafs.Circle, {
30285
+ center: center,
30286
+ radius: radius,
30287
+ fillOpacity: 0,
30288
+ color: wonderBlocksTokens.color.blue
30289
+ }), /*#__PURE__*/React__namespace.createElement(DragHandle, {
30290
+ center: center
30291
+ }));
30292
+ }
30293
+ const dragHandleDimensions = [24, 14];
30294
+ const dragHandleDotPositions = crossProduct([-4.4, 0, 4.4], [-2.1, 2.1]);
30295
+ function DragHandle(props) {
30296
+ const {
30297
+ center
30298
+ } = props;
30299
+ const cornerRadius = Math.min(...dragHandleDimensions) / 2;
30300
+ const [centerPx] = useTransformVectorsToPixels(center);
30301
+ const topLeft = mafs.vec.sub(centerPx, mafs.vec.scale(dragHandleDimensions, 0.5));
30302
+ return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement("rect", {
30303
+ className: "movable-circle-handle",
30304
+ x: topLeft[0],
30305
+ y: topLeft[1],
30306
+ width: dragHandleDimensions[0],
30307
+ height: dragHandleDimensions[1],
30308
+ rx: cornerRadius,
30309
+ ry: cornerRadius
30310
+ }), dragHandleDotPositions.map(offsetPx => {
30311
+ const [xPx, yPx] = mafs.vec.add(offsetPx, centerPx);
30312
+ return /*#__PURE__*/React__namespace.createElement("circle", {
30313
+ className: "movable-circle-handle-dot",
30314
+ cx: xPx,
30315
+ cy: yPx
30316
+ });
30317
+ }));
30318
+ }
30319
+ function crossProduct(as, bs) {
30320
+ const result = [];
30321
+ for (const a of as) {
30322
+ for (const b of bs) {
30323
+ result.push([a, b]);
30324
+ }
30325
+ }
30326
+ return result;
30327
+ }
30244
30328
 
30245
30329
  function PointGraph(props) {
30246
30330
  const {
@@ -31433,35 +31517,22 @@ function mafsStateToInteractiveGraph(state) {
31433
31517
  };
31434
31518
  }
31435
31519
  const StatefulMafsGraph = /*#__PURE__*/React__namespace.forwardRef((props, ref) => {
31520
+ const {
31521
+ onChange
31522
+ } = props;
31436
31523
  const [state, dispatch] = React__namespace.useReducer(interactiveGraphReducer, props, initializeGraphState);
31437
31524
  React.useImperativeHandle(ref, () => ({
31438
31525
  getUserInput: () => getGradableGraph(state, props.graph)
31439
31526
  }));
31440
- function onChange(next) {
31441
- props.onChange(mafsStateToInteractiveGraph(next));
31442
- }
31443
- return /*#__PURE__*/React__namespace.createElement(MafsGraph, _extends({}, props, {
31444
- state: state,
31445
- dispatch: dispatch,
31446
- onChange: onChange
31447
- }));
31448
- });
31449
- const MafsGraph = props => {
31450
- const {
31451
- state,
31452
- dispatch,
31453
- labels
31454
- } = props;
31455
- const [width, height] = props.box;
31456
31527
  const prevState = React.useRef(state);
31457
31528
  React.useEffect(() => {
31458
31529
  if (prevState.current !== state) {
31459
- props.onChange({
31530
+ onChange(mafsStateToInteractiveGraph({
31460
31531
  graph: state
31461
- });
31532
+ }));
31462
31533
  }
31463
31534
  prevState.current = state;
31464
- }, [props, state]);
31535
+ }, [onChange, state]);
31465
31536
 
31466
31537
  // Destructuring first to keep useEffect from making excess calls
31467
31538
  const [xSnap, ySnap] = props.snapStep;
@@ -31474,6 +31545,18 @@ const MafsGraph = props => {
31474
31545
  React.useEffect(() => {
31475
31546
  dispatch(changeRange([[xMinRange, xMaxRange], [yMinRange, yMaxRange]]));
31476
31547
  }, [dispatch, xMinRange, xMaxRange, yMinRange, yMaxRange]);
31548
+ return /*#__PURE__*/React__namespace.createElement(MafsGraph, _extends({}, props, {
31549
+ state: state,
31550
+ dispatch: dispatch
31551
+ }));
31552
+ });
31553
+ const MafsGraph = props => {
31554
+ const {
31555
+ state,
31556
+ dispatch,
31557
+ labels
31558
+ } = props;
31559
+ const [width, height] = props.box;
31477
31560
  return /*#__PURE__*/React__namespace.createElement(GraphConfigContext.Provider, {
31478
31561
  value: {
31479
31562
  range: state.range,
@@ -31506,8 +31589,8 @@ const MafsGraph = props => {
31506
31589
  }, props.markings === "graph" && /*#__PURE__*/React__namespace.createElement(AxisLabels, null), /*#__PURE__*/React__namespace.createElement(mafs.Mafs, {
31507
31590
  preserveAspectRatio: false,
31508
31591
  viewBox: {
31509
- x: props.range[0],
31510
- y: props.range[1],
31592
+ x: state.range[0],
31593
+ y: state.range[1],
31511
31594
  padding: 0
31512
31595
  },
31513
31596
  pan: false,
@@ -31517,12 +31600,12 @@ const MafsGraph = props => {
31517
31600
  }, /*#__PURE__*/React__namespace.createElement(SvgDefs, null), /*#__PURE__*/React__namespace.createElement(Grid, {
31518
31601
  tickStep: props.step,
31519
31602
  gridStep: props.gridStep,
31520
- range: props.range,
31603
+ range: state.range,
31521
31604
  containerSizeClass: props.containerSizeClass,
31522
31605
  markings: props.markings
31523
31606
  }), props.lockedFigures && /*#__PURE__*/React__namespace.createElement(GraphLockedLayer, {
31524
31607
  lockedFigures: props.lockedFigures,
31525
- range: props.range
31608
+ range: state.range
31526
31609
  }), renderGraph({
31527
31610
  state,
31528
31611
  dispatch
@@ -43618,7 +43701,7 @@ const init = function (options) {
43618
43701
 
43619
43702
  // This file is processed by a Rollup plugin (replace) to inject the production
43620
43703
  const libName = "@khanacademy/perseus";
43621
- const libVersion = "22.4.2";
43704
+ const libVersion = "22.5.1";
43622
43705
  perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
43623
43706
 
43624
43707
  const apiVersion = {