@khanacademy/perseus 22.4.1 → 22.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/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,
@@ -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.
@@ -27854,7 +27849,7 @@ var Image$1 = {
27854
27849
  // @ts-expect-error - TS2339 - Property 'Label' does not exist on type 'typeof Graphie'.
27855
27850
  const Label = Graphie.Label;
27856
27851
  // @ts-expect-error - TS2339 - Property 'Line' does not exist on type 'typeof Graphie'.
27857
- const Line$1 = Graphie.Line;
27852
+ const Line$2 = Graphie.Line;
27858
27853
  // @ts-expect-error - TS2339 - Property 'MovablePoint' does not exist on type 'typeof Graphie'.
27859
27854
  const MovablePoint$1 = Graphie.MovablePoint;
27860
27855
  // @ts-expect-error - TS2339 - Property 'MovableLine' does not exist on type 'typeof Graphie'.
@@ -28081,7 +28076,7 @@ class Interaction extends React__namespace.Component {
28081
28076
  this._eval(element.options.endX),
28082
28077
  // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
28083
28078
  this._eval(element.options.endY)];
28084
- return /*#__PURE__*/React__namespace.createElement(Line$1, {
28079
+ return /*#__PURE__*/React__namespace.createElement(Line$2, {
28085
28080
  key: element.key,
28086
28081
  start: start,
28087
28082
  end: end,
@@ -28790,6 +28785,16 @@ function vectorsToPixels(vectors, graphState) {
28790
28785
  const transformToPx = matrixBuilder().translate(...translation).scale(width / (xMax - xMin), -height / (yMax - yMin)).get();
28791
28786
  return vectors.map(p => mafs.vec.transform(p, transformToPx));
28792
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
+ }
28793
28798
 
28794
28799
  // When converting points, we translate so the pixel point ends up in the right location.
28795
28800
  // This is because points are specific locations on graphs, unlike vectors.
@@ -28805,6 +28810,13 @@ const useTransformVectorsToPixels = function () {
28805
28810
  }
28806
28811
  return vectorsToPixels(vectors, graphState);
28807
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
+ };
28808
28820
 
28809
28821
  function AxisLabels() {
28810
28822
  const {
@@ -29041,33 +29053,29 @@ function calculateAngleInDegrees(_ref) {
29041
29053
  */
29042
29054
  const getIntersectionOfRayWithBox = (initialPoint, throughPoint, box) => {
29043
29055
  const [[xMin, xMax], [yMin, yMax]] = box;
29044
- const [aX, aY] = throughPoint;
29045
- const [bX, bY] = initialPoint;
29056
+ const [aX, aY] = initialPoint;
29057
+ const [bX, bY] = throughPoint;
29046
29058
  const yDiff = bY - aY;
29047
29059
  const xDiff = bX - aX;
29048
29060
  const slope = yDiff / xDiff;
29049
- const yAtXMin = slope * (xMin - aX) + aY;
29050
- const yAtXMax = slope * (xMax - aX) + aY;
29051
- const xAtYMin = (yMin - aY) / slope + aX;
29052
- const xAtYMax = (yMax - aY) / slope + aX;
29053
-
29054
- // 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;
29055
29066
  switch (true) {
29056
- // 12 o'clock to 2:59
29057
- case yDiff > 0 && xDiff >= 0:
29058
- return xAtYMax > xMax ? [xMax, yAtXMax] : [xAtYMax, yMax];
29059
- // 3 o'clock to 5:59
29060
- case yDiff <= 0 && xDiff > 0:
29061
- // xAtYMin evaluates to -Infinity here, so we use absolute value
29062
- return Math.abs(xAtYMin) > xMax ? [xMax, yAtXMax] : [xAtYMin, yMin];
29063
- // 9 o'clock to 11:59
29064
- case yDiff >= 0 && xDiff < 0:
29065
- return xAtYMax < xMin ? [xMin, yAtXMin] : [xAtYMax, yMax];
29066
- // 6 o'clock to 8:59
29067
- case yDiff < 0 && xDiff <= 0:
29068
- 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];
29069
29074
  default:
29070
- 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];
29071
29079
  }
29072
29080
  };
29073
29081
  const getLines = points => {
@@ -29076,6 +29084,9 @@ const getLines = points => {
29076
29084
  return [point, next];
29077
29085
  });
29078
29086
  };
29087
+ function isBetween(x, low, high) {
29088
+ return x >= low && x <= high;
29089
+ }
29079
29090
 
29080
29091
  function SVGLine(props) {
29081
29092
  const {
@@ -29128,15 +29139,15 @@ const LockedLine = props => {
29128
29139
  lineStyle,
29129
29140
  kind,
29130
29141
  points,
29131
- showStartPoint,
29132
- showEndPoint,
29142
+ showPoint1,
29143
+ showPoint2,
29133
29144
  range
29134
29145
  } = props;
29135
29146
  const [point1, point2] = points;
29136
29147
  let line;
29137
29148
  if (kind === "ray") {
29138
29149
  // Rays extend to the end of the graph in one direction.
29139
- const extendedPoint = getIntersectionOfRayWithBox(point2.coord, point1.coord, range);
29150
+ const extendedPoint = getIntersectionOfRayWithBox(point1.coord, point2.coord, range);
29140
29151
  line = /*#__PURE__*/React__namespace.createElement(Vector, {
29141
29152
  tail: point1.coord,
29142
29153
  tip: extendedPoint,
@@ -29147,7 +29158,7 @@ const LockedLine = props => {
29147
29158
  });
29148
29159
  } else {
29149
29160
  const LineType = kind === "segment" ? mafs.Line.Segment : mafs.Line.ThroughPoints;
29150
- 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);
29151
29162
  const direction = mafs.vec.sub(point2.coord, point1.coord);
29152
29163
  let angle = calculateAngleInDegrees(direction);
29153
29164
  const startArrowHead = kind !== "segment" && /*#__PURE__*/React__namespace.createElement(Arrowhead, {
@@ -29155,7 +29166,7 @@ const LockedLine = props => {
29155
29166
  tip: arrowTip,
29156
29167
  color: lockedFigureColors[color]
29157
29168
  });
29158
- arrowTip = kind === "segment" ? point1.coord : getIntersectionOfRayWithBox(point1.coord, point2.coord, range);
29169
+ arrowTip = kind === "segment" ? point1.coord : getIntersectionOfRayWithBox(point2.coord, point1.coord, range);
29159
29170
  angle = angle > 180 ? angle - 180 : angle + 180;
29160
29171
  const endArrowHead = kind !== "segment" && /*#__PURE__*/React__namespace.createElement(Arrowhead, {
29161
29172
  angle: angle,
@@ -29171,7 +29182,7 @@ const LockedLine = props => {
29171
29182
  }
29172
29183
  return /*#__PURE__*/React__namespace.createElement("g", {
29173
29184
  className: kind === "ray" ? "locked-ray" : "locked-line"
29174
- }, line, showStartPoint && /*#__PURE__*/React__namespace.createElement(mafs.Point, {
29185
+ }, line, showPoint1 && /*#__PURE__*/React__namespace.createElement(mafs.Point, {
29175
29186
  x: point1.coord[0],
29176
29187
  y: point1.coord[1],
29177
29188
  svgCircleProps: {
@@ -29181,7 +29192,7 @@ const LockedLine = props => {
29181
29192
  strokeWidth: wonderBlocksTokens.spacing.xxxxSmall_2
29182
29193
  }
29183
29194
  }
29184
- }), showEndPoint && /*#__PURE__*/React__namespace.createElement(mafs.Point, {
29195
+ }), showPoint2 && /*#__PURE__*/React__namespace.createElement(mafs.Point, {
29185
29196
  x: point2.coord[0],
29186
29197
  y: point2.coord[1],
29187
29198
  svgCircleProps: {
@@ -29443,9 +29454,10 @@ function tabIndex(config) {
29443
29454
  return undefined;
29444
29455
  }
29445
29456
 
29446
- const Segment = props => {
29457
+ const MovableLine = props => {
29447
29458
  const {
29448
- onMoveLine,
29459
+ onMoveLine = () => {},
29460
+ onMovePoint = () => {},
29449
29461
  color,
29450
29462
  points: [start, end],
29451
29463
  extend
@@ -29465,18 +29477,19 @@ const Segment = props => {
29465
29477
  const {
29466
29478
  visiblePoint: visiblePoint1,
29467
29479
  focusableHandle: focusableHandle1
29468
- } = useControlPoint(start, color, p => props.onMovePoint(0, p));
29480
+ } = useControlPoint(start, color, p => onMovePoint(0, p));
29469
29481
  const {
29470
29482
  visiblePoint: visiblePoint2,
29471
29483
  focusableHandle: focusableHandle2
29472
- } = useControlPoint(end, color, p => props.onMovePoint(1, p));
29473
- 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, {
29474
29486
  start: start,
29475
29487
  end: end,
29476
29488
  stroke: color,
29477
29489
  extend: extend,
29478
29490
  onMove: onMoveLine
29479
- }), focusableHandle2, visiblePoint1, visiblePoint2);
29491
+ });
29492
+ return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, focusableHandle1, line, focusableHandle2, visiblePoint1, visiblePoint2);
29480
29493
  };
29481
29494
  function useControlPoint(point, color, onMovePoint) {
29482
29495
  const {
@@ -29522,7 +29535,7 @@ function useControlPoint(point, color, onMovePoint) {
29522
29535
  };
29523
29536
  }
29524
29537
  const defaultStroke = "var(--movable-line-stroke-color)";
29525
- const MovableLine = props => {
29538
+ const Line$1 = props => {
29526
29539
  const {
29527
29540
  start,
29528
29541
  end,
@@ -29530,29 +29543,29 @@ const MovableLine = props => {
29530
29543
  extend,
29531
29544
  stroke = defaultStroke
29532
29545
  } = props;
29533
- const midpoint = mafs.vec.midpoint(start, end);
29534
29546
  const [startPtPx, endPtPx] = useTransformVectorsToPixels(start, end);
29535
29547
  const {
29536
29548
  range,
29537
- graphDimensionsInPixels
29549
+ graphDimensionsInPixels,
29550
+ snapStep
29538
29551
  } = useGraphConfig();
29539
29552
  let startExtend = undefined;
29540
29553
  let endExtend = undefined;
29541
29554
  if (extend) {
29542
29555
  const trimmedRange = trimRange(range, graphDimensionsInPixels);
29543
- startExtend = extend.start ? getIntersectionOfRayWithBox(start, end, trimmedRange) : undefined;
29544
- 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;
29545
29558
  }
29546
29559
  const line = React.useRef(null);
29547
29560
  const {
29548
29561
  dragging
29549
29562
  } = mafs.useMovable({
29550
29563
  gestureTarget: line,
29551
- point: midpoint,
29564
+ point: start,
29552
29565
  onMove: newPoint => {
29553
- onMove(mafs.vec.sub(newPoint, midpoint));
29566
+ onMove(mafs.vec.sub(newPoint, start));
29554
29567
  },
29555
- constrain: p => p
29568
+ constrain: p => snap(snapStep, p)
29556
29569
  });
29557
29570
  return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement("g", {
29558
29571
  ref: line,
@@ -29625,7 +29638,7 @@ const SegmentGraph = props => {
29625
29638
  const {
29626
29639
  coords: segments
29627
29640
  } = props.graphState;
29628
- 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, {
29629
29642
  key: i,
29630
29643
  points: segment,
29631
29644
  onMoveLine: delta => {
@@ -29645,7 +29658,7 @@ const LinearGraph = props => {
29645
29658
  coords: lines
29646
29659
  } = props.graphState;
29647
29660
  const colors = ["var(--movable-line-stroke-color)", "var(--mafs-violet)"];
29648
- 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, {
29649
29662
  key: i,
29650
29663
  points: line,
29651
29664
  onMoveLine: delta => {
@@ -29669,7 +29682,7 @@ const RayGraph = props => {
29669
29682
  } = props.graphState;
29670
29683
  const handleMoveLine = delta => dispatch(moveLine(0, delta));
29671
29684
  const handleMovePoint = (pointIndex, newPoint) => dispatch(moveControlPoint(pointIndex, newPoint, 0));
29672
- return /*#__PURE__*/React__namespace.createElement(Segment
29685
+ return /*#__PURE__*/React__namespace.createElement(MovableLine
29673
29686
  // a ray only has one line
29674
29687
  , {
29675
29688
  points: lines[0],
@@ -29827,7 +29840,7 @@ const isRightAngle = angle => Math.abs(angle - Math.PI / 2) < 0.01;
29827
29840
  */
29828
29841
  const shouldDrawArcOutside = (midpoint, vertex, range, polygonLines) => {
29829
29842
  // Create a ray from the midpoint (inside angle) to the edge of the range
29830
- const rangeIntersectionPoint = getIntersectionOfRayWithBox(vertex, midpoint, range);
29843
+ const rangeIntersectionPoint = getIntersectionOfRayWithBox(midpoint, vertex, range);
29831
29844
  let lineIntersectionCount = 0;
29832
29845
  polygonLines.forEach(line => linesIntersect([vertex, rangeIntersectionPoint], line) && lineIntersectionCount++);
29833
29846
 
@@ -30229,16 +30242,10 @@ function CircleGraph(props) {
30229
30242
  center,
30230
30243
  radiusPoint
30231
30244
  } = graphState;
30232
- return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(mafs.Circle, {
30245
+ return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(MovableCircle, {
30233
30246
  center: center,
30234
30247
  radius: getRadius(graphState),
30235
- fillOpacity: 0,
30236
- color: wonderBlocksTokens.color.blue
30237
- }), /*#__PURE__*/React__namespace.createElement(StyledMovablePoint, {
30238
- point: center,
30239
- onMove: newCenter => {
30240
- dispatch(moveCenter(newCenter));
30241
- }
30248
+ onMove: newCenter => dispatch(moveCenter(newCenter))
30242
30249
  }), /*#__PURE__*/React__namespace.createElement(StyledMovablePoint, {
30243
30250
  point: radiusPoint,
30244
30251
  onMove: newRadiusPoint => {
@@ -30246,6 +30253,80 @@ function CircleGraph(props) {
30246
30253
  }
30247
30254
  }));
30248
30255
  }
30256
+ function MovableCircle(props) {
30257
+ const {
30258
+ center,
30259
+ radius,
30260
+ onMove
30261
+ } = props;
30262
+ const {
30263
+ snapStep
30264
+ } = useGraphConfig();
30265
+ const draggableRef = React.useRef(null);
30266
+ const {
30267
+ dragging
30268
+ } = mafs.useMovable({
30269
+ gestureTarget: draggableRef,
30270
+ point: center,
30271
+ onMove,
30272
+ constrain: p => snap(snapStep, p)
30273
+ });
30274
+ const [centerPx] = useTransformVectorsToPixels(center);
30275
+ const [radiiPx] = useTransformDimensionsToPixels([radius, radius]);
30276
+ return /*#__PURE__*/React__namespace.createElement("g", {
30277
+ ref: draggableRef,
30278
+ tabIndex: 0,
30279
+ className: `movable-circle ${dragging ? "movable-circle--dragging" : ""}`
30280
+ }, /*#__PURE__*/React__namespace.createElement("ellipse", {
30281
+ className: "focus-ring",
30282
+ cx: centerPx[0],
30283
+ cy: centerPx[1],
30284
+ rx: radiiPx[0] + 3,
30285
+ ry: radiiPx[1] + 3
30286
+ }), /*#__PURE__*/React__namespace.createElement(mafs.Circle, {
30287
+ center: center,
30288
+ radius: radius,
30289
+ fillOpacity: 0,
30290
+ color: wonderBlocksTokens.color.blue
30291
+ }), /*#__PURE__*/React__namespace.createElement(DragHandle, {
30292
+ center: center
30293
+ }));
30294
+ }
30295
+ const dragHandleDimensions = [24, 14];
30296
+ const dragHandleDotPositions = crossProduct([-4.4, 0, 4.4], [-2.1, 2.1]);
30297
+ function DragHandle(props) {
30298
+ const {
30299
+ center
30300
+ } = props;
30301
+ const cornerRadius = Math.min(...dragHandleDimensions) / 2;
30302
+ const [centerPx] = useTransformVectorsToPixels(center);
30303
+ const topLeft = mafs.vec.sub(centerPx, mafs.vec.scale(dragHandleDimensions, 0.5));
30304
+ return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement("rect", {
30305
+ className: "movable-circle-handle",
30306
+ x: topLeft[0],
30307
+ y: topLeft[1],
30308
+ width: dragHandleDimensions[0],
30309
+ height: dragHandleDimensions[1],
30310
+ rx: cornerRadius,
30311
+ ry: cornerRadius
30312
+ }), dragHandleDotPositions.map(offsetPx => {
30313
+ const [xPx, yPx] = mafs.vec.add(offsetPx, centerPx);
30314
+ return /*#__PURE__*/React__namespace.createElement("circle", {
30315
+ className: "movable-circle-handle-dot",
30316
+ cx: xPx,
30317
+ cy: yPx
30318
+ });
30319
+ }));
30320
+ }
30321
+ function crossProduct(as, bs) {
30322
+ const result = [];
30323
+ for (const a of as) {
30324
+ for (const b of bs) {
30325
+ result.push([a, b]);
30326
+ }
30327
+ }
30328
+ return result;
30329
+ }
30249
30330
 
30250
30331
  function PointGraph(props) {
30251
30332
  const {
@@ -43623,7 +43704,7 @@ const init = function (options) {
43623
43704
 
43624
43705
  // This file is processed by a Rollup plugin (replace) to inject the production
43625
43706
  const libName = "@khanacademy/perseus";
43626
- const libVersion = "22.4.1";
43707
+ const libVersion = "22.5.0";
43627
43708
  perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
43628
43709
 
43629
43710
  const apiVersion = {