@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/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
@@ -2,7 +2,7 @@ import * as KAS from '@khanacademy/kas';
2
2
  import { MathInputI18nContext, CursorContext, getKeyTranslator, createMathField, getCursorContext, DesktopKeypad, convertDotToTimesByLocale, mathQuillInstance, KeypadInput, KeypadType, KeyArray, keypadElementPropType } from '@khanacademy/math-input';
3
3
  import * as PerseusLinter from '@khanacademy/perseus-linter';
4
4
  import { linterContextDefault, Rule, linterContextProps } from '@khanacademy/perseus-linter';
5
- import { View, RenderStateRoot, useUniqueIdWithMock, UniqueIDProvider, Text as Text$2 } from '@khanacademy/wonder-blocks-core';
5
+ import { View, RenderStateRoot, UniqueIDProvider, useUniqueIdWithMock, Text as Text$2 } from '@khanacademy/wonder-blocks-core';
6
6
  import Tooltip$1 from '@khanacademy/wonder-blocks-tooltip';
7
7
  import classNames from 'classnames';
8
8
  import * as React from 'react';
@@ -29,7 +29,7 @@ import { OptionItem, SingleSelect, MultiSelect } from '@khanacademy/wonder-block
29
29
  import caretDown from '@phosphor-icons/core/regular/caret-down.svg';
30
30
  import caretUp from '@phosphor-icons/core/regular/caret-up.svg';
31
31
  import debounce$2 from 'lodash.debounce';
32
- import { vec, 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,
@@ -23407,8 +23407,6 @@ const Choice = function Choice(props) {
23407
23407
  const showDimmed = !reviewMode && apiOptions.readOnly || crossedOut;
23408
23408
  const letter = getChoiceLetter(pos, strings);
23409
23409
  const a11yText = getA11yText(letter, checked, correct, crossedOut, showCorrectness, strings);
23410
- const idFactory = useUniqueIdWithMock(`choice`);
23411
- const choiceId = idFactory.get(letter);
23412
23410
  return /*#__PURE__*/React.createElement("div", {
23413
23411
  style: {
23414
23412
  // @ts-expect-error - TS2322 - Type '{ dispay: string; flexDirection: "column"; color: string; }' is not assignable to type 'Properties<string | number, string & {}>'.
@@ -23427,9 +23425,8 @@ const Choice = function Choice(props) {
23427
23425
  }
23428
23426
  }, /*#__PURE__*/React.createElement("div", {
23429
23427
  className: "perseus-sr-only"
23430
- }, /*#__PURE__*/React.createElement("input", {
23428
+ }, /*#__PURE__*/React.createElement("label", null, a11yText, " \xA0 ", content, /*#__PURE__*/React.createElement("input", {
23431
23429
  type: multipleSelect ? "checkbox" : "radio",
23432
- id: choiceId,
23433
23430
  checked: checked,
23434
23431
  onClick: () => {
23435
23432
  // If we're checking a crossed-out option, let's
@@ -23443,9 +23440,7 @@ const Choice = function Choice(props) {
23443
23440
  disabled: disabled || apiOptions.readOnly,
23444
23441
  tabIndex: -1,
23445
23442
  className: ClassNames.RADIO.OPTION_CONTENT
23446
- }), /*#__PURE__*/React.createElement("label", {
23447
- htmlFor: choiceId
23448
- }, a11yText, " \xA0 ", content)), /*#__PURE__*/React.createElement(Clickable, {
23443
+ }))), /*#__PURE__*/React.createElement(Clickable, {
23449
23444
  onClick: () => {
23450
23445
  // If we're checking a crossed-out option, let's
23451
23446
  // also uncross it.
@@ -27575,7 +27570,7 @@ var Image$1 = {
27575
27570
  // @ts-expect-error - TS2339 - Property 'Label' does not exist on type 'typeof Graphie'.
27576
27571
  const Label = Graphie.Label;
27577
27572
  // @ts-expect-error - TS2339 - Property 'Line' does not exist on type 'typeof Graphie'.
27578
- const Line$1 = Graphie.Line;
27573
+ const Line$2 = Graphie.Line;
27579
27574
  // @ts-expect-error - TS2339 - Property 'MovablePoint' does not exist on type 'typeof Graphie'.
27580
27575
  const MovablePoint$1 = Graphie.MovablePoint;
27581
27576
  // @ts-expect-error - TS2339 - Property 'MovableLine' does not exist on type 'typeof Graphie'.
@@ -27797,7 +27792,7 @@ class Interaction extends React.Component {
27797
27792
  this._eval(element.options.endX),
27798
27793
  // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
27799
27794
  this._eval(element.options.endY)];
27800
- return /*#__PURE__*/React.createElement(Line$1, {
27795
+ return /*#__PURE__*/React.createElement(Line$2, {
27801
27796
  key: element.key,
27802
27797
  start: start,
27803
27798
  end: end,
@@ -28505,6 +28500,16 @@ function vectorsToPixels(vectors, graphState, translation = [0, 0]) {
28505
28500
  const transformToPx = matrixBuilder().translate(...translation).scale(width / (xMax - xMin), -height / (yMax - yMin)).get();
28506
28501
  return vectors.map(p => vec.transform(p, transformToPx));
28507
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
+ }
28508
28513
 
28509
28514
  // When converting points, we translate so the pixel point ends up in the right location.
28510
28515
  // This is because points are specific locations on graphs, unlike vectors.
@@ -28517,6 +28522,10 @@ const useTransformVectorsToPixels = (...vectors) => {
28517
28522
  const graphState = useGraphConfig();
28518
28523
  return vectorsToPixels(vectors, graphState);
28519
28524
  };
28525
+ const useTransformDimensionsToPixels = (...dimens) => {
28526
+ const graphState = useGraphConfig();
28527
+ return dimensionsToPixels(dimens, graphState);
28528
+ };
28520
28529
 
28521
28530
  function AxisLabels() {
28522
28531
  const {
@@ -28754,33 +28763,29 @@ function calculateAngleInDegrees([x, y]) {
28754
28763
  */
28755
28764
  const getIntersectionOfRayWithBox = (initialPoint, throughPoint, box) => {
28756
28765
  const [[xMin, xMax], [yMin, yMax]] = box;
28757
- const [aX, aY] = throughPoint;
28758
- const [bX, bY] = initialPoint;
28766
+ const [aX, aY] = initialPoint;
28767
+ const [bX, bY] = throughPoint;
28759
28768
  const yDiff = bY - aY;
28760
28769
  const xDiff = bX - aX;
28761
28770
  const slope = yDiff / xDiff;
28762
- const yAtXMin = slope * (xMin - aX) + aY;
28763
- const yAtXMax = slope * (xMax - aX) + aY;
28764
- const xAtYMin = (yMin - aY) / slope + aX;
28765
- const xAtYMax = (yMax - aY) / slope + aX;
28766
-
28767
- // 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;
28768
28776
  switch (true) {
28769
- // 12 o'clock to 2:59
28770
- case yDiff > 0 && xDiff >= 0:
28771
- return xAtYMax > xMax ? [xMax, yAtXMax] : [xAtYMax, yMax];
28772
- // 3 o'clock to 5:59
28773
- case yDiff <= 0 && xDiff > 0:
28774
- // xAtYMin evaluates to -Infinity here, so we use absolute value
28775
- return Math.abs(xAtYMin) > xMax ? [xMax, yAtXMax] : [xAtYMin, yMin];
28776
- // 9 o'clock to 11:59
28777
- case yDiff >= 0 && xDiff < 0:
28778
- return xAtYMax < xMin ? [xMin, yAtXMin] : [xAtYMax, yMax];
28779
- // 6 o'clock to 8:59
28780
- case yDiff < 0 && xDiff <= 0:
28781
- 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];
28782
28784
  default:
28783
- 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];
28784
28789
  }
28785
28790
  };
28786
28791
  const getLines = points => {
@@ -28789,6 +28794,9 @@ const getLines = points => {
28789
28794
  return [point, next];
28790
28795
  });
28791
28796
  };
28797
+ function isBetween(x, low, high) {
28798
+ return x >= low && x <= high;
28799
+ }
28792
28800
 
28793
28801
  function SVGLine(props) {
28794
28802
  const {
@@ -28841,15 +28849,15 @@ const LockedLine = props => {
28841
28849
  lineStyle,
28842
28850
  kind,
28843
28851
  points,
28844
- showStartPoint,
28845
- showEndPoint,
28852
+ showPoint1,
28853
+ showPoint2,
28846
28854
  range
28847
28855
  } = props;
28848
28856
  const [point1, point2] = points;
28849
28857
  let line;
28850
28858
  if (kind === "ray") {
28851
28859
  // Rays extend to the end of the graph in one direction.
28852
- const extendedPoint = getIntersectionOfRayWithBox(point2.coord, point1.coord, range);
28860
+ const extendedPoint = getIntersectionOfRayWithBox(point1.coord, point2.coord, range);
28853
28861
  line = /*#__PURE__*/React.createElement(Vector, {
28854
28862
  tail: point1.coord,
28855
28863
  tip: extendedPoint,
@@ -28859,8 +28867,8 @@ const LockedLine = props => {
28859
28867
  }
28860
28868
  });
28861
28869
  } else {
28862
- const LineType = kind === "segment" ? Line$3.Segment : Line$3.ThroughPoints;
28863
- 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);
28864
28872
  const direction = vec.sub(point2.coord, point1.coord);
28865
28873
  let angle = calculateAngleInDegrees(direction);
28866
28874
  const startArrowHead = kind !== "segment" && /*#__PURE__*/React.createElement(Arrowhead, {
@@ -28868,7 +28876,7 @@ const LockedLine = props => {
28868
28876
  tip: arrowTip,
28869
28877
  color: lockedFigureColors[color$1]
28870
28878
  });
28871
- arrowTip = kind === "segment" ? point1.coord : getIntersectionOfRayWithBox(point1.coord, point2.coord, range);
28879
+ arrowTip = kind === "segment" ? point1.coord : getIntersectionOfRayWithBox(point2.coord, point1.coord, range);
28872
28880
  angle = angle > 180 ? angle - 180 : angle + 180;
28873
28881
  const endArrowHead = kind !== "segment" && /*#__PURE__*/React.createElement(Arrowhead, {
28874
28882
  angle: angle,
@@ -28884,7 +28892,7 @@ const LockedLine = props => {
28884
28892
  }
28885
28893
  return /*#__PURE__*/React.createElement("g", {
28886
28894
  className: kind === "ray" ? "locked-ray" : "locked-line"
28887
- }, line, showStartPoint && /*#__PURE__*/React.createElement(Point$2, {
28895
+ }, line, showPoint1 && /*#__PURE__*/React.createElement(Point$2, {
28888
28896
  x: point1.coord[0],
28889
28897
  y: point1.coord[1],
28890
28898
  svgCircleProps: {
@@ -28894,7 +28902,7 @@ const LockedLine = props => {
28894
28902
  strokeWidth: spacing.xxxxSmall_2
28895
28903
  }
28896
28904
  }
28897
- }), showEndPoint && /*#__PURE__*/React.createElement(Point$2, {
28905
+ }), showPoint2 && /*#__PURE__*/React.createElement(Point$2, {
28898
28906
  x: point2.coord[0],
28899
28907
  y: point2.coord[1],
28900
28908
  svgCircleProps: {
@@ -29154,9 +29162,10 @@ function tabIndex(config) {
29154
29162
  return undefined;
29155
29163
  }
29156
29164
 
29157
- const Segment = props => {
29165
+ const MovableLine = props => {
29158
29166
  const {
29159
- onMoveLine,
29167
+ onMoveLine = () => {},
29168
+ onMovePoint = () => {},
29160
29169
  color,
29161
29170
  points: [start, end],
29162
29171
  extend
@@ -29176,18 +29185,19 @@ const Segment = props => {
29176
29185
  const {
29177
29186
  visiblePoint: visiblePoint1,
29178
29187
  focusableHandle: focusableHandle1
29179
- } = useControlPoint(start, color, p => props.onMovePoint(0, p));
29188
+ } = useControlPoint(start, color, p => onMovePoint(0, p));
29180
29189
  const {
29181
29190
  visiblePoint: visiblePoint2,
29182
29191
  focusableHandle: focusableHandle2
29183
- } = useControlPoint(end, color, p => props.onMovePoint(1, p));
29184
- 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, {
29185
29194
  start: start,
29186
29195
  end: end,
29187
29196
  stroke: color,
29188
29197
  extend: extend,
29189
29198
  onMove: onMoveLine
29190
- }), focusableHandle2, visiblePoint1, visiblePoint2);
29199
+ });
29200
+ return /*#__PURE__*/React.createElement(React.Fragment, null, focusableHandle1, line, focusableHandle2, visiblePoint1, visiblePoint2);
29191
29201
  };
29192
29202
  function useControlPoint(point, color, onMovePoint) {
29193
29203
  const {
@@ -29233,7 +29243,7 @@ function useControlPoint(point, color, onMovePoint) {
29233
29243
  };
29234
29244
  }
29235
29245
  const defaultStroke = "var(--movable-line-stroke-color)";
29236
- const MovableLine = props => {
29246
+ const Line$1 = props => {
29237
29247
  const {
29238
29248
  start,
29239
29249
  end,
@@ -29241,29 +29251,29 @@ const MovableLine = props => {
29241
29251
  extend,
29242
29252
  stroke = defaultStroke
29243
29253
  } = props;
29244
- const midpoint = vec.midpoint(start, end);
29245
29254
  const [startPtPx, endPtPx] = useTransformVectorsToPixels(start, end);
29246
29255
  const {
29247
29256
  range,
29248
- graphDimensionsInPixels
29257
+ graphDimensionsInPixels,
29258
+ snapStep
29249
29259
  } = useGraphConfig();
29250
29260
  let startExtend = undefined;
29251
29261
  let endExtend = undefined;
29252
29262
  if (extend) {
29253
29263
  const trimmedRange = trimRange(range, graphDimensionsInPixels);
29254
- startExtend = extend.start ? getIntersectionOfRayWithBox(start, end, trimmedRange) : undefined;
29255
- 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;
29256
29266
  }
29257
29267
  const line = useRef(null);
29258
29268
  const {
29259
29269
  dragging
29260
29270
  } = useMovable({
29261
29271
  gestureTarget: line,
29262
- point: midpoint,
29272
+ point: start,
29263
29273
  onMove: newPoint => {
29264
- onMove(vec.sub(newPoint, midpoint));
29274
+ onMove(vec.sub(newPoint, start));
29265
29275
  },
29266
- constrain: p => p
29276
+ constrain: p => snap(snapStep, p)
29267
29277
  });
29268
29278
  return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("g", {
29269
29279
  ref: line,
@@ -29336,7 +29346,7 @@ const SegmentGraph = props => {
29336
29346
  const {
29337
29347
  coords: segments
29338
29348
  } = props.graphState;
29339
- 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, {
29340
29350
  key: i,
29341
29351
  points: segment,
29342
29352
  onMoveLine: delta => {
@@ -29356,7 +29366,7 @@ const LinearGraph = props => {
29356
29366
  coords: lines
29357
29367
  } = props.graphState;
29358
29368
  const colors = ["var(--movable-line-stroke-color)", "var(--mafs-violet)"];
29359
- 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, {
29360
29370
  key: i,
29361
29371
  points: line,
29362
29372
  onMoveLine: delta => {
@@ -29380,7 +29390,7 @@ const RayGraph = props => {
29380
29390
  } = props.graphState;
29381
29391
  const handleMoveLine = delta => dispatch(moveLine(0, delta));
29382
29392
  const handleMovePoint = (pointIndex, newPoint) => dispatch(moveControlPoint(pointIndex, newPoint, 0));
29383
- return /*#__PURE__*/React.createElement(Segment
29393
+ return /*#__PURE__*/React.createElement(MovableLine
29384
29394
  // a ray only has one line
29385
29395
  , {
29386
29396
  points: lines[0],
@@ -29532,7 +29542,7 @@ const isRightAngle = angle => Math.abs(angle - Math.PI / 2) < 0.01;
29532
29542
  */
29533
29543
  const shouldDrawArcOutside = (midpoint, vertex, range, polygonLines) => {
29534
29544
  // Create a ray from the midpoint (inside angle) to the edge of the range
29535
- const rangeIntersectionPoint = getIntersectionOfRayWithBox(vertex, midpoint, range);
29545
+ const rangeIntersectionPoint = getIntersectionOfRayWithBox(midpoint, vertex, range);
29536
29546
  let lineIntersectionCount = 0;
29537
29547
  polygonLines.forEach(line => linesIntersect([vertex, rangeIntersectionPoint], line) && lineIntersectionCount++);
29538
29548
 
@@ -29916,16 +29926,10 @@ function CircleGraph(props) {
29916
29926
  center,
29917
29927
  radiusPoint
29918
29928
  } = graphState;
29919
- return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Circle$1, {
29929
+ return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(MovableCircle, {
29920
29930
  center: center,
29921
29931
  radius: getRadius(graphState),
29922
- fillOpacity: 0,
29923
- color: color.blue
29924
- }), /*#__PURE__*/React.createElement(StyledMovablePoint, {
29925
- point: center,
29926
- onMove: newCenter => {
29927
- dispatch(moveCenter(newCenter));
29928
- }
29932
+ onMove: newCenter => dispatch(moveCenter(newCenter))
29929
29933
  }), /*#__PURE__*/React.createElement(StyledMovablePoint, {
29930
29934
  point: radiusPoint,
29931
29935
  onMove: newRadiusPoint => {
@@ -29933,6 +29937,80 @@ function CircleGraph(props) {
29933
29937
  }
29934
29938
  }));
29935
29939
  }
29940
+ function MovableCircle(props) {
29941
+ const {
29942
+ center,
29943
+ radius,
29944
+ onMove
29945
+ } = props;
29946
+ const {
29947
+ snapStep
29948
+ } = useGraphConfig();
29949
+ const draggableRef = useRef(null);
29950
+ const {
29951
+ dragging
29952
+ } = useMovable({
29953
+ gestureTarget: draggableRef,
29954
+ point: center,
29955
+ onMove,
29956
+ constrain: p => snap(snapStep, p)
29957
+ });
29958
+ const [centerPx] = useTransformVectorsToPixels(center);
29959
+ const [radiiPx] = useTransformDimensionsToPixels([radius, radius]);
29960
+ return /*#__PURE__*/React.createElement("g", {
29961
+ ref: draggableRef,
29962
+ tabIndex: 0,
29963
+ className: `movable-circle ${dragging ? "movable-circle--dragging" : ""}`
29964
+ }, /*#__PURE__*/React.createElement("ellipse", {
29965
+ className: "focus-ring",
29966
+ cx: centerPx[0],
29967
+ cy: centerPx[1],
29968
+ rx: radiiPx[0] + 3,
29969
+ ry: radiiPx[1] + 3
29970
+ }), /*#__PURE__*/React.createElement(Circle$1, {
29971
+ center: center,
29972
+ radius: radius,
29973
+ fillOpacity: 0,
29974
+ color: color.blue
29975
+ }), /*#__PURE__*/React.createElement(DragHandle, {
29976
+ center: center
29977
+ }));
29978
+ }
29979
+ const dragHandleDimensions = [24, 14];
29980
+ const dragHandleDotPositions = crossProduct([-4.4, 0, 4.4], [-2.1, 2.1]);
29981
+ function DragHandle(props) {
29982
+ const {
29983
+ center
29984
+ } = props;
29985
+ const cornerRadius = Math.min(...dragHandleDimensions) / 2;
29986
+ const [centerPx] = useTransformVectorsToPixels(center);
29987
+ const topLeft = vec.sub(centerPx, vec.scale(dragHandleDimensions, 0.5));
29988
+ return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("rect", {
29989
+ className: "movable-circle-handle",
29990
+ x: topLeft[0],
29991
+ y: topLeft[1],
29992
+ width: dragHandleDimensions[0],
29993
+ height: dragHandleDimensions[1],
29994
+ rx: cornerRadius,
29995
+ ry: cornerRadius
29996
+ }), dragHandleDotPositions.map(offsetPx => {
29997
+ const [xPx, yPx] = vec.add(offsetPx, centerPx);
29998
+ return /*#__PURE__*/React.createElement("circle", {
29999
+ className: "movable-circle-handle-dot",
30000
+ cx: xPx,
30001
+ cy: yPx
30002
+ });
30003
+ }));
30004
+ }
30005
+ function crossProduct(as, bs) {
30006
+ const result = [];
30007
+ for (const a of as) {
30008
+ for (const b of bs) {
30009
+ result.push([a, b]);
30010
+ }
30011
+ }
30012
+ return result;
30013
+ }
29936
30014
 
29937
30015
  function PointGraph(props) {
29938
30016
  const {
@@ -43294,7 +43372,7 @@ const init = function init(options) {
43294
43372
 
43295
43373
  // This file is processed by a Rollup plugin (replace) to inject the production
43296
43374
  const libName = "@khanacademy/perseus";
43297
- const libVersion = "22.4.1";
43375
+ const libVersion = "22.5.0";
43298
43376
  addLibraryVersionToPerseusDebug(libName, libVersion);
43299
43377
 
43300
43378
  const apiVersion = {