@khanacademy/perseus 22.4.0 → 22.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.css CHANGED
@@ -4419,7 +4419,9 @@ html body .mq-editable-field .mq-selection .mq-nthroot-container * {
4419
4419
  );
4420
4420
  }
4421
4421
 
4422
- .MafsView .movable-point:focus-visible .movable-point-focus-outline {
4422
+ .MafsView
4423
+ .movable-point:is(:focus-visible, .movable-point--focus)
4424
+ .movable-point-focus-outline {
4423
4425
  visibility: visible;
4424
4426
  }
4425
4427
 
package/dist/index.js CHANGED
@@ -477,7 +477,7 @@ class InnerMathInput extends React__namespace.Component {
477
477
  locale
478
478
  } = this.context;
479
479
  // Initialize MathQuill.MathField instance
480
- this.__mathField = mathInput.createMathField(this.__mathFieldWrapperRef, this.props.mathInputStrings, baseConfig => ({
480
+ this.__mathField = mathInput.createMathField(this.__mathFieldWrapperRef, locale, this.props.mathInputStrings, baseConfig => ({
481
481
  ...baseConfig,
482
482
  handlers: {
483
483
  edit: debounce(mathField => {
@@ -23621,8 +23621,6 @@ const Choice = function (props) {
23621
23621
  const showDimmed = !reviewMode && apiOptions.readOnly || crossedOut;
23622
23622
  const letter = getChoiceLetter(pos, strings);
23623
23623
  const a11yText = getA11yText(letter, checked, correct, crossedOut, showCorrectness, strings);
23624
- const idFactory = wonderBlocksCore.useUniqueIdWithMock(`choice`);
23625
- const choiceId = idFactory.get(letter);
23626
23624
  return /*#__PURE__*/React__namespace.createElement("div", {
23627
23625
  style: {
23628
23626
  // @ts-expect-error - TS2322 - Type '{ dispay: string; flexDirection: "column"; color: string; }' is not assignable to type 'Properties<string | number, string & {}>'.
@@ -23641,9 +23639,8 @@ const Choice = function (props) {
23641
23639
  }
23642
23640
  }, /*#__PURE__*/React__namespace.createElement("div", {
23643
23641
  className: "perseus-sr-only"
23644
- }, /*#__PURE__*/React__namespace.createElement("input", {
23642
+ }, /*#__PURE__*/React__namespace.createElement("label", null, a11yText, " \xA0 ", content, /*#__PURE__*/React__namespace.createElement("input", {
23645
23643
  type: multipleSelect ? "checkbox" : "radio",
23646
- id: choiceId,
23647
23644
  checked: checked,
23648
23645
  onClick: () => {
23649
23646
  // If we're checking a crossed-out option, let's
@@ -23657,9 +23654,7 @@ const Choice = function (props) {
23657
23654
  disabled: disabled || apiOptions.readOnly,
23658
23655
  tabIndex: -1,
23659
23656
  className: ClassNames.RADIO.OPTION_CONTENT
23660
- }), /*#__PURE__*/React__namespace.createElement("label", {
23661
- htmlFor: choiceId
23662
- }, a11yText, " \xA0 ", content)), /*#__PURE__*/React__namespace.createElement(Clickable__default["default"], {
23657
+ }))), /*#__PURE__*/React__namespace.createElement(Clickable__default["default"], {
23663
23658
  onClick: () => {
23664
23659
  // If we're checking a crossed-out option, let's
23665
23660
  // also uncross it.
@@ -29333,6 +29328,194 @@ function snap(snapStep, point) {
29333
29328
  return [Math.round(requestedX / snapX) * snapX, Math.round(requestedY / snapY) * snapY];
29334
29329
  }
29335
29330
 
29331
+ // The hitbox size of 48px by 48px is preserved from the legacy interactive
29332
+ // graph.
29333
+ const hitboxSizePx = 48;
29334
+
29335
+ // MovablePointView is a purely presentational component (i.e. it is a pure
29336
+ // function with no state or effects) that renders the SVG for a movable point
29337
+ // on an interactive graph.
29338
+ //
29339
+ // It has two modes for managing tabbing / focus: "controlled" (where the caller
29340
+ // manages the display of the focus ring, and the point itself cannot be
29341
+ // focused) and "uncontrolled" (where the point is focusable, and the browser
29342
+ // manages the focus state and styling). For context on why we did this, see
29343
+ // the description of https://github.com/Khan/perseus/pull/1240
29344
+ const MovablePointView = /*#__PURE__*/React.forwardRef((props, hitboxRef) => {
29345
+ const {
29346
+ range,
29347
+ markings,
29348
+ showTooltips
29349
+ } = useGraphConfig();
29350
+ const {
29351
+ point,
29352
+ color = wonderBlocksTokens.color.blue,
29353
+ dragging,
29354
+ focusBehavior
29355
+ } = props;
29356
+
29357
+ // WB Tooltip requires a color name for the background color.
29358
+ // Since the color in props is a hex value, a reverse lookup is needed.
29359
+ const wbColorName = Object.entries(wonderBlocksTokens.color).find(_ref => {
29360
+ let [_, value] = _ref;
29361
+ return value === color;
29362
+ })?.[0] ?? "blue";
29363
+ const pointClasses = `movable-point ${dragging ? "movable-point--dragging" : ""} ${focusClass(focusBehavior)}`;
29364
+ const [[x, y]] = useTransformVectorsToPixels(point);
29365
+ const [xMin, xMax] = range[0];
29366
+ const [yMin, yMax] = range[1];
29367
+ const [[verticalStartX]] = useTransformVectorsToPixels([xMin, 0]);
29368
+ const [[verticalEndX]] = useTransformVectorsToPixels([xMax, 0]);
29369
+ const [[_, horizontalStartY]] = useTransformVectorsToPixels([0, yMin]);
29370
+ const [[__, horizontalEndY]] = useTransformVectorsToPixels([0, yMax]);
29371
+ const showHairlines = dragging && markings !== "none";
29372
+ const hairlines = /*#__PURE__*/React__namespace.createElement("g", null, /*#__PURE__*/React__namespace.createElement("line", {
29373
+ x1: verticalStartX,
29374
+ y1: y,
29375
+ x2: verticalEndX,
29376
+ y2: y,
29377
+ stroke: color
29378
+ }), /*#__PURE__*/React__namespace.createElement("line", {
29379
+ x1: x,
29380
+ y1: horizontalStartY,
29381
+ x2: x,
29382
+ y2: horizontalEndY,
29383
+ stroke: color
29384
+ }));
29385
+ const svgForPoint = /*#__PURE__*/React__namespace.createElement("g", {
29386
+ ref: hitboxRef,
29387
+ className: pointClasses,
29388
+ style: {
29389
+ "--movable-point-color": color
29390
+ },
29391
+ "data-testid": "movable-point",
29392
+ tabIndex: tabIndex(focusBehavior)
29393
+ }, /*#__PURE__*/React__namespace.createElement("circle", {
29394
+ className: "movable-point-hitbox",
29395
+ r: hitboxSizePx / 2,
29396
+ cx: x,
29397
+ cy: y
29398
+ }), /*#__PURE__*/React__namespace.createElement("circle", {
29399
+ className: "movable-point-halo",
29400
+ cx: x,
29401
+ cy: y
29402
+ }), /*#__PURE__*/React__namespace.createElement("circle", {
29403
+ className: "movable-point-ring",
29404
+ cx: x,
29405
+ cy: y
29406
+ }), /*#__PURE__*/React__namespace.createElement("circle", {
29407
+ className: "movable-point-focus-outline",
29408
+ cx: x,
29409
+ cy: y
29410
+ }), /*#__PURE__*/React__namespace.createElement("circle", {
29411
+ className: "movable-point-center",
29412
+ cx: x,
29413
+ cy: y,
29414
+ style: {
29415
+ fill: color
29416
+ },
29417
+ "data-testid": "movable-point__center"
29418
+ }));
29419
+ return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, showHairlines && hairlines, showTooltips ? /*#__PURE__*/React__namespace.createElement(Tooltip__default["default"], {
29420
+ autoUpdate: true,
29421
+ backgroundColor: wbColorName,
29422
+ content: `(${point[0]}, ${point[1]})`,
29423
+ contentStyle: {
29424
+ color: "white"
29425
+ }
29426
+ }, svgForPoint) : svgForPoint);
29427
+ });
29428
+ function focusClass(config) {
29429
+ if (config.type === "controlled" && config.showFocusRing) {
29430
+ return "movable-point--focus";
29431
+ }
29432
+ return "";
29433
+ }
29434
+ function tabIndex(config) {
29435
+ if (config.type === "uncontrolled") {
29436
+ return config.tabIndex;
29437
+ }
29438
+ return undefined;
29439
+ }
29440
+
29441
+ const Segment = props => {
29442
+ const {
29443
+ onMoveLine,
29444
+ color,
29445
+ points: [start, end],
29446
+ extend
29447
+ } = props;
29448
+
29449
+ // We use separate focusableHandle elements, instead of letting the movable
29450
+ // points themselves be focusable, to allow the tab order of the points to
29451
+ // be different from the rendering order. We had to solve for the following
29452
+ // constraints:
29453
+ // - SVG has no equivalent of z-index, so the order of elements in the
29454
+ // document determines the order in which they're painted. We want the
29455
+ // movable line segment to render behind its endpoints (it looks weird
29456
+ // otherwise) so we have to render the line first.
29457
+ // - There isn't a browser-native way to customize tab order, other than
29458
+ // setting tabindex > 0. But that bumps elements to the front of the
29459
+ // tab order for the entire page, which is not what we want.
29460
+ const {
29461
+ visiblePoint: visiblePoint1,
29462
+ focusableHandle: focusableHandle1
29463
+ } = useControlPoint(start, color, p => props.onMovePoint(0, p));
29464
+ const {
29465
+ visiblePoint: visiblePoint2,
29466
+ 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, {
29469
+ start: start,
29470
+ end: end,
29471
+ stroke: color,
29472
+ extend: extend,
29473
+ onMove: onMoveLine
29474
+ }), focusableHandle2, visiblePoint1, visiblePoint2);
29475
+ };
29476
+ function useControlPoint(point, color, onMovePoint) {
29477
+ const {
29478
+ snapStep
29479
+ } = useGraphConfig();
29480
+ const [focused, setFocused] = React.useState(false);
29481
+ const keyboardHandleRef = React.useRef(null);
29482
+ mafs.useMovable({
29483
+ gestureTarget: keyboardHandleRef,
29484
+ point,
29485
+ onMove: onMovePoint,
29486
+ constrain: p => snap(snapStep, p)
29487
+ });
29488
+ const visiblePointRef = React.useRef(null);
29489
+ const {
29490
+ dragging
29491
+ } = mafs.useMovable({
29492
+ gestureTarget: visiblePointRef,
29493
+ point,
29494
+ onMove: onMovePoint,
29495
+ constrain: p => snap(snapStep, p)
29496
+ });
29497
+ const focusableHandle = /*#__PURE__*/React__namespace.createElement("g", {
29498
+ "data-testid": "movable-point__focusable-handle",
29499
+ tabIndex: 0,
29500
+ ref: keyboardHandleRef,
29501
+ onFocus: () => setFocused(true),
29502
+ onBlur: () => setFocused(false)
29503
+ });
29504
+ const visiblePoint = /*#__PURE__*/React__namespace.createElement(MovablePointView, {
29505
+ point: point,
29506
+ dragging: dragging,
29507
+ color: color,
29508
+ ref: visiblePointRef,
29509
+ focusBehavior: {
29510
+ type: "controlled",
29511
+ showFocusRing: focused
29512
+ }
29513
+ });
29514
+ return {
29515
+ focusableHandle,
29516
+ visiblePoint
29517
+ };
29518
+ }
29336
29519
  const defaultStroke = "var(--movable-line-stroke-color)";
29337
29520
  const MovableLine = props => {
29338
29521
  const {
@@ -29345,12 +29528,13 @@ const MovableLine = props => {
29345
29528
  const midpoint = mafs.vec.midpoint(start, end);
29346
29529
  const [startPtPx, endPtPx] = useTransformVectorsToPixels(start, end);
29347
29530
  const {
29531
+ range,
29348
29532
  graphDimensionsInPixels
29349
29533
  } = useGraphConfig();
29350
29534
  let startExtend = undefined;
29351
29535
  let endExtend = undefined;
29352
29536
  if (extend) {
29353
- const trimmedRange = trimRange(extend.range, graphDimensionsInPixels);
29537
+ const trimmedRange = trimRange(range, graphDimensionsInPixels);
29354
29538
  startExtend = extend.start ? getIntersectionOfRayWithBox(start, end, trimmedRange) : undefined;
29355
29539
  endExtend = extend.end ? getIntersectionOfRayWithBox(end, start, trimmedRange) : undefined;
29356
29540
  }
@@ -29429,103 +29613,6 @@ function size(interval) {
29429
29613
  return interval[1] - interval[0];
29430
29614
  }
29431
29615
 
29432
- // The hitbox size of 48px by 48px is preserved from the legacy interactive
29433
- // graph.
29434
- const hitboxSizePx = 48;
29435
- const StyledMovablePoint = props => {
29436
- const {
29437
- range,
29438
- snapStep,
29439
- markings,
29440
- showTooltips
29441
- } = useGraphConfig();
29442
- const hitboxRef = React.useRef(null);
29443
- const {
29444
- point,
29445
- onMove,
29446
- color = wonderBlocksTokens.color.blue
29447
- } = props;
29448
-
29449
- // WB Tooltip requires a color name for the background color.
29450
- // Since the color in props is a hex value, a reverse lookup is needed.
29451
- const wbColorName = Object.entries(wonderBlocksTokens.color).find(_ref => {
29452
- let [_, value] = _ref;
29453
- return value === color;
29454
- })?.[0] ?? "blue";
29455
- const {
29456
- dragging
29457
- } = mafs.useMovable({
29458
- gestureTarget: hitboxRef,
29459
- point,
29460
- onMove,
29461
- constrain: p => snap(snapStep, p)
29462
- });
29463
- const pointClasses = `movable-point ${dragging ? "movable-point--dragging" : ""}`;
29464
- const [[x, y]] = useTransformVectorsToPixels(point);
29465
- const [xMin, xMax] = range[0];
29466
- const [yMin, yMax] = range[1];
29467
- const [[verticalStartX]] = useTransformVectorsToPixels([xMin, 0]);
29468
- const [[verticalEndX]] = useTransformVectorsToPixels([xMax, 0]);
29469
- const [[_, horizontalStartY]] = useTransformVectorsToPixels([0, yMin]);
29470
- const [[__, horizontalEndY]] = useTransformVectorsToPixels([0, yMax]);
29471
- const showHairlines = dragging && markings !== "none";
29472
- const hairlines = /*#__PURE__*/React__namespace.createElement("g", null, /*#__PURE__*/React__namespace.createElement("line", {
29473
- x1: verticalStartX,
29474
- y1: y,
29475
- x2: verticalEndX,
29476
- y2: y,
29477
- stroke: color
29478
- }), /*#__PURE__*/React__namespace.createElement("line", {
29479
- x1: x,
29480
- y1: horizontalStartY,
29481
- x2: x,
29482
- y2: horizontalEndY,
29483
- stroke: color
29484
- }));
29485
- const svgForPoint = /*#__PURE__*/React__namespace.createElement("g", {
29486
- ref: hitboxRef,
29487
- className: pointClasses,
29488
- tabIndex: 0,
29489
- style: {
29490
- "--movable-point-color": color
29491
- },
29492
- "data-testid": "movable-point"
29493
- }, /*#__PURE__*/React__namespace.createElement("circle", {
29494
- className: "movable-point-hitbox",
29495
- r: hitboxSizePx / 2,
29496
- cx: x,
29497
- cy: y
29498
- }), /*#__PURE__*/React__namespace.createElement("circle", {
29499
- className: "movable-point-halo",
29500
- cx: x,
29501
- cy: y
29502
- }), /*#__PURE__*/React__namespace.createElement("circle", {
29503
- className: "movable-point-ring",
29504
- cx: x,
29505
- cy: y
29506
- }), /*#__PURE__*/React__namespace.createElement("circle", {
29507
- className: "movable-point-focus-outline",
29508
- cx: x,
29509
- cy: y
29510
- }), /*#__PURE__*/React__namespace.createElement("circle", {
29511
- className: "movable-point-center",
29512
- cx: x,
29513
- cy: y,
29514
- style: {
29515
- fill: color
29516
- },
29517
- "data-testid": "movable-point__center"
29518
- }));
29519
- return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, showHairlines && hairlines, showTooltips ? /*#__PURE__*/React__namespace.createElement(Tooltip__default["default"], {
29520
- autoUpdate: true,
29521
- backgroundColor: wbColorName,
29522
- content: `(${point[0]}, ${point[1]})`,
29523
- contentStyle: {
29524
- color: "white"
29525
- }
29526
- }, svgForPoint) : svgForPoint);
29527
- };
29528
-
29529
29616
  const SegmentGraph = props => {
29530
29617
  const {
29531
29618
  dispatch
@@ -29533,7 +29620,7 @@ const SegmentGraph = props => {
29533
29620
  const {
29534
29621
  coords: segments
29535
29622
  } = props.graphState;
29536
- return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, segments?.map((segment, i) => /*#__PURE__*/React__namespace.createElement(SegmentView, {
29623
+ return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, segments?.map((segment, i) => /*#__PURE__*/React__namespace.createElement(Segment, {
29537
29624
  key: i,
29538
29625
  points: segment,
29539
29626
  onMoveLine: delta => {
@@ -29544,27 +29631,6 @@ const SegmentGraph = props => {
29544
29631
  }
29545
29632
  })));
29546
29633
  };
29547
- const SegmentView = props => {
29548
- const {
29549
- onMoveLine: onMoveSegment,
29550
- points: [start, end]
29551
- } = props;
29552
- return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(MovableLine, {
29553
- start: start,
29554
- end: end,
29555
- onMove: onMoveSegment
29556
- }), /*#__PURE__*/React__namespace.createElement(StyledMovablePoint, {
29557
- point: start,
29558
- onMove: newPoint => {
29559
- props.onMovePoint(0, newPoint);
29560
- }
29561
- }), /*#__PURE__*/React__namespace.createElement(StyledMovablePoint, {
29562
- point: end,
29563
- onMove: newPoint => {
29564
- props.onMovePoint(1, newPoint);
29565
- }
29566
- }));
29567
- };
29568
29634
 
29569
29635
  const LinearGraph = props => {
29570
29636
  const {
@@ -29574,49 +29640,19 @@ const LinearGraph = props => {
29574
29640
  coords: lines
29575
29641
  } = props.graphState;
29576
29642
  const colors = ["var(--movable-line-stroke-color)", "var(--mafs-violet)"];
29577
- return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, lines?.map((line, i) => /*#__PURE__*/React__namespace.createElement(LineView, {
29643
+ return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, lines?.map((line, i) => /*#__PURE__*/React__namespace.createElement(Segment, {
29578
29644
  key: i,
29579
29645
  points: line,
29580
29646
  onMoveLine: delta => {
29581
29647
  dispatch(moveLine(i, delta));
29582
29648
  },
29583
- onMovePoint: (endpointIndex, destination) => dispatch(moveControlPoint(endpointIndex, destination, i)),
29584
- stroke: colors[i]
29585
- })));
29586
- };
29587
- const LineView = props => {
29588
- const {
29589
- onMoveLine,
29590
- onMovePoint,
29591
- points: [start, end],
29592
- stroke
29593
- } = props;
29594
- const {
29595
- range
29596
- } = useGraphConfig();
29597
- return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(MovableLine, {
29598
- start: start,
29599
- end: end,
29600
- onMove: onMoveLine,
29601
29649
  extend: {
29602
29650
  start: true,
29603
- end: true,
29604
- range
29605
- },
29606
- stroke: stroke
29607
- }), /*#__PURE__*/React__namespace.createElement(StyledMovablePoint, {
29608
- point: start,
29609
- onMove: newPoint => {
29610
- onMovePoint(0, newPoint);
29611
- },
29612
- color: stroke
29613
- }), /*#__PURE__*/React__namespace.createElement(StyledMovablePoint, {
29614
- point: end,
29615
- onMove: newPoint => {
29616
- onMovePoint(1, newPoint);
29651
+ end: true
29617
29652
  },
29618
- color: stroke
29619
- }));
29653
+ onMovePoint: (endpointIndex, destination) => dispatch(moveControlPoint(endpointIndex, destination, i)),
29654
+ color: colors[i]
29655
+ })));
29620
29656
  };
29621
29657
 
29622
29658
  const RayGraph = props => {
@@ -29624,30 +29660,21 @@ const RayGraph = props => {
29624
29660
  dispatch
29625
29661
  } = props;
29626
29662
  const {
29627
- coords: lines,
29628
- range
29663
+ coords: lines
29629
29664
  } = props.graphState;
29630
-
29631
- // a ray only has one line
29632
- const [start, end] = lines[0];
29633
29665
  const handleMoveLine = delta => dispatch(moveLine(0, delta));
29634
- const handleMovePoint = (newPoint, pointIndex) => dispatch(moveControlPoint(pointIndex, newPoint, 0));
29635
- return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(MovableLine, {
29636
- start: start,
29637
- end: end,
29638
- onMove: handleMoveLine,
29666
+ const handleMovePoint = (pointIndex, newPoint) => dispatch(moveControlPoint(pointIndex, newPoint, 0));
29667
+ return /*#__PURE__*/React__namespace.createElement(Segment
29668
+ // a ray only has one line
29669
+ , {
29670
+ points: lines[0],
29671
+ onMoveLine: handleMoveLine,
29672
+ onMovePoint: handleMovePoint,
29639
29673
  extend: {
29640
- end: true,
29641
29674
  start: false,
29642
- range
29675
+ end: true
29643
29676
  }
29644
- }), /*#__PURE__*/React__namespace.createElement(StyledMovablePoint, {
29645
- point: start,
29646
- onMove: newPoint => handleMovePoint(newPoint, 0)
29647
- }), /*#__PURE__*/React__namespace.createElement(StyledMovablePoint, {
29648
- point: end,
29649
- onMove: newPoint => handleMovePoint(newPoint, 1)
29650
- }));
29677
+ });
29651
29678
  };
29652
29679
 
29653
29680
  /**
@@ -29819,6 +29846,36 @@ const linesIntersect = (_ref3, _ref4) => {
29819
29846
  };
29820
29847
  const isEven = n => n % 2 === 0;
29821
29848
 
29849
+ const StyledMovablePoint = props => {
29850
+ const {
29851
+ snapStep
29852
+ } = useGraphConfig();
29853
+ const elementRef = React.useRef(null);
29854
+ const {
29855
+ point,
29856
+ onMove,
29857
+ color = wonderBlocksTokens.color.blue
29858
+ } = props;
29859
+ const {
29860
+ dragging
29861
+ } = mafs.useMovable({
29862
+ gestureTarget: elementRef,
29863
+ point,
29864
+ onMove,
29865
+ constrain: p => snap(snapStep, p)
29866
+ });
29867
+ return /*#__PURE__*/React__namespace.createElement(MovablePointView, {
29868
+ ref: elementRef,
29869
+ point: point,
29870
+ color: color,
29871
+ dragging: dragging,
29872
+ focusBehavior: {
29873
+ type: "uncontrolled",
29874
+ tabIndex: 0
29875
+ }
29876
+ });
29877
+ };
29878
+
29822
29879
  const PolygonGraph = props => {
29823
29880
  const [focused, setFocused] = React__namespace.useState(false);
29824
29881
  const [hovered, setHovered] = React__namespace.useState(false);
@@ -29832,17 +29889,19 @@ const PolygonGraph = props => {
29832
29889
  range,
29833
29890
  snapStep
29834
29891
  } = props.graphState;
29892
+
29893
+ // TODO(benchristel): can the default set of points be removed here? I don't
29894
+ // think coords can be null.
29835
29895
  const points = coords ?? [[0, 0]];
29836
- const pointsSum = points.reduce((acc, point) => mafs.vec.add(acc, point), [0, 0]);
29837
- const midpoint = pointsSum && mafs.vec.scale(pointsSum, 1 / (points.length ?? 1));
29838
29896
  const ref = React__namespace.useRef(null);
29897
+ const dragReferencePoint = points[0];
29839
29898
  const {
29840
29899
  dragging
29841
29900
  } = mafs.useMovable({
29842
29901
  gestureTarget: ref,
29843
- point: midpoint,
29902
+ point: dragReferencePoint,
29844
29903
  onMove: newPoint => {
29845
- const delta = mafs.vec.sub(newPoint, midpoint);
29904
+ const delta = mafs.vec.sub(newPoint, dragReferencePoint);
29846
29905
  dispatch(moveAll(delta));
29847
29906
  },
29848
29907
  constrain: p => snap(snapStep, p)
@@ -43559,7 +43618,7 @@ const init = function (options) {
43559
43618
 
43560
43619
  // This file is processed by a Rollup plugin (replace) to inject the production
43561
43620
  const libName = "@khanacademy/perseus";
43562
- const libVersion = "22.4.0";
43621
+ const libVersion = "22.4.2";
43563
43622
  perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
43564
43623
 
43565
43624
  const apiVersion = {