@khanacademy/perseus 22.4.2 → 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,
@@ -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
 
@@ -30224,16 +30242,10 @@ function CircleGraph(props) {
30224
30242
  center,
30225
30243
  radiusPoint
30226
30244
  } = graphState;
30227
- 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, {
30228
30246
  center: center,
30229
30247
  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
- }
30248
+ onMove: newCenter => dispatch(moveCenter(newCenter))
30237
30249
  }), /*#__PURE__*/React__namespace.createElement(StyledMovablePoint, {
30238
30250
  point: radiusPoint,
30239
30251
  onMove: newRadiusPoint => {
@@ -30241,6 +30253,80 @@ function CircleGraph(props) {
30241
30253
  }
30242
30254
  }));
30243
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
+ }
30244
30330
 
30245
30331
  function PointGraph(props) {
30246
30332
  const {
@@ -43618,7 +43704,7 @@ const init = function (options) {
43618
43704
 
43619
43705
  // This file is processed by a Rollup plugin (replace) to inject the production
43620
43706
  const libName = "@khanacademy/perseus";
43621
- const libVersion = "22.4.2";
43707
+ const libVersion = "22.5.0";
43622
43708
  perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
43623
43709
 
43624
43710
  const apiVersion = {