@khanacademy/perseus 56.0.0 → 56.2.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.js CHANGED
@@ -18350,17 +18350,6 @@ class WidgetContainer extends React__namespace.Component {
18350
18350
  widget_id: this.props.id
18351
18351
  },
18352
18352
  onError: error => {
18353
- // TODO(LEMS-2826): Remove analytics event in LEMS-2826 in favor of ti below.
18354
- analytics.onAnalyticsEvent({
18355
- type: "perseus:widget-rendering-error",
18356
- payload: {
18357
- widgetSubType: subType,
18358
- widgetType: type,
18359
- widgetId: this.props.id,
18360
- message: error.message,
18361
- userAgent: userAgent
18362
- }
18363
- });
18364
18353
  analytics.onAnalyticsEvent({
18365
18354
  type: "perseus:widget-rendering-error:ti",
18366
18355
  payload: {
@@ -28143,7 +28132,8 @@ const defaultGraphConfig = {
28143
28132
  width: 0,
28144
28133
  height: 0,
28145
28134
  labels: [],
28146
- disableKeyboardInteraction: false
28135
+ disableKeyboardInteraction: false,
28136
+ interactiveColor: "var(--mafs-blue)"
28147
28137
  };
28148
28138
  const GraphConfigContext = /*#__PURE__*/React.createContext(defaultGraphConfig);
28149
28139
  function useGraphConfig() {
@@ -28220,6 +28210,11 @@ const arrowPath = pathBuilder().move(-3, 4).curve(-2.75, 2.5, 0, 0.25, 0.75, 0).
28220
28210
  function Arrowhead(props) {
28221
28211
  const [point] = useTransformVectorsToPixels(props.tip);
28222
28212
  return /*#__PURE__*/React__namespace.createElement("g", {
28213
+ // Use aria-hidden to hide the line from screen readers
28214
+ // so it doesn't read as "image" with no context.
28215
+ // The elements using this should have their own aria-labels,
28216
+ // so this is okay.
28217
+ "aria-hidden": true,
28223
28218
  className: "interactive-graph-arrowhead",
28224
28219
  transform: `translate(${point[X]} ${point[Y]}) rotate(${props.angle})`
28225
28220
  }, /*#__PURE__*/React__namespace.createElement("g", {
@@ -29389,6 +29384,11 @@ function SVGLine(props) {
29389
29384
  testId
29390
29385
  } = props;
29391
29386
  return /*#__PURE__*/React__namespace.createElement("line", {
29387
+ // Use aria-hidden to hide the line from screen readers
29388
+ // so it doesn't read as "image" with no context.
29389
+ // The elements using this should have their own aria-labels,
29390
+ // so this is okay.
29391
+ "aria-hidden": true,
29392
29392
  x1: start[X],
29393
29393
  y1: start[Y],
29394
29394
  x2: end[X],
@@ -29399,15 +29399,23 @@ function SVGLine(props) {
29399
29399
  });
29400
29400
  }
29401
29401
 
29402
+ /**
29403
+ * Used to visualize vectors for both interative elements
29404
+ * as well as locked figures within an Interactive Graph widget.
29405
+ */
29402
29406
  const {
29403
29407
  calculateAngleInDegrees: calculateAngleInDegrees$3
29404
29408
  } = kmath.angles;
29405
29409
  function Vector(props) {
29410
+ const {
29411
+ interactiveColor
29412
+ } = useGraphConfig();
29406
29413
  const {
29407
29414
  tail,
29408
29415
  tip,
29409
- color,
29410
- style
29416
+ color = interactiveColor,
29417
+ style,
29418
+ testId
29411
29419
  } = props;
29412
29420
  const [tailPx, tipPx] = useTransformVectorsToPixels(tail, tip);
29413
29421
  const direction = mafs.vec.sub(tipPx, tailPx);
@@ -29416,7 +29424,8 @@ function Vector(props) {
29416
29424
  style: {
29417
29425
  stroke: color,
29418
29426
  strokeWidth: 2
29419
- }
29427
+ },
29428
+ "data-testid": testId
29420
29429
  }, /*#__PURE__*/React__namespace.createElement(SVGLine, {
29421
29430
  start: tailPx,
29422
29431
  end: tipPx,
@@ -29714,9 +29723,17 @@ function getAngleFromPoints(points, i) {
29714
29723
  const a = mafs.vec.dist(point, pt1);
29715
29724
  const b = mafs.vec.dist(point, pt2);
29716
29725
  const c = mafs.vec.dist(pt1, pt2);
29726
+ let lawOfCosinesRadicand = (a ** 2 + b ** 2 - c ** 2) / (2 * a * b);
29727
+
29728
+ // If the equation results in a number greater than 1 or less than -1.
29729
+ // Correct to ensure a valid angle.
29730
+ // This ensures we are not producing NaN results from Math.acos.
29731
+ if (lawOfCosinesRadicand < -1 || lawOfCosinesRadicand > 1) {
29732
+ lawOfCosinesRadicand = Math.round(lawOfCosinesRadicand);
29733
+ }
29717
29734
 
29718
29735
  // Law of cosines
29719
- const angle = Math.acos((a ** 2 + b ** 2 - c ** 2) / (2 * a * b));
29736
+ const angle = Math.acos(lawOfCosinesRadicand);
29720
29737
  return angle;
29721
29738
  }
29722
29739
  function getSideLengthsFromPoints(points, i, isPolygonOpen) {
@@ -30243,9 +30260,17 @@ const PolygonAngle = _ref => {
30243
30260
  const a = mafs.vec.dist(centerPoint, endPoints[0]);
30244
30261
  const b = mafs.vec.dist(centerPoint, endPoints[1]);
30245
30262
  const c = mafs.vec.dist(endPoints[0], endPoints[1]);
30263
+ let lawOfCosinesRadicand = (a ** 2 + b ** 2 - c ** 2) / (2 * a * b);
30264
+
30265
+ // If the equation results in a number greater than 1 or less than -1.
30266
+ // Correct to ensure a valid angle.
30267
+ // This ensures we are not producing NaN results from Math.acos.
30268
+ if (lawOfCosinesRadicand < -1 || lawOfCosinesRadicand > 1) {
30269
+ lawOfCosinesRadicand = Math.round(lawOfCosinesRadicand);
30270
+ }
30246
30271
 
30247
30272
  // Law of cosines
30248
- const angle = Math.acos((a ** 2 + b ** 2 - c ** 2) / (2 * a * b));
30273
+ const angle = Math.acos(lawOfCosinesRadicand);
30249
30274
  const y1 = centerY + (startY - centerY) / a * radius;
30250
30275
  const x2 = centerX + (endX - centerX) / b * radius;
30251
30276
  const x1 = centerX + (startX - centerX) / a * radius;
@@ -30288,7 +30313,7 @@ const PolygonAngle = _ref => {
30288
30313
  }), /*#__PURE__*/React__namespace.createElement("feComposite", {
30289
30314
  operator: "over",
30290
30315
  in: "SourceGraphic"
30291
- }))), !isOutside && isRightAngle(angle) ? /*#__PURE__*/React__namespace.createElement(RightAngleSquare, {
30316
+ }))), !isOutside && isRightPolygonAngle(angle) ? /*#__PURE__*/React__namespace.createElement(RightAngleSquare, {
30292
30317
  start: [x1, y1],
30293
30318
  vertex: [x2, y2],
30294
30319
  end: [x3, y3]
@@ -30362,6 +30387,12 @@ const Angle = _ref2 => {
30362
30387
  // Calculate the text position based on the angle and whether we allow reflex angles
30363
30388
  // Let's try the angle bisector method to find the midpoint of the arc
30364
30389
  const [textX, textY] = calculateBisectorPoint(point1, point2, vertex, isReflexive, allowReflexAngles, radius);
30390
+
30391
+ // Determine the color style for the arc when interactive vs disabled
30392
+ const {
30393
+ disableKeyboardInteraction
30394
+ } = useGraphConfig();
30395
+ const arcClassName = disableKeyboardInteraction ? "angle-arc-static" : "angle-arc-interactive";
30365
30396
  return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement("defs", null, /*#__PURE__*/React__namespace.createElement("filter", {
30366
30397
  id: "background",
30367
30398
  x: "-5%",
@@ -30378,10 +30409,10 @@ const Angle = _ref2 => {
30378
30409
  start: [x1, y1],
30379
30410
  vertex: [x2, y2],
30380
30411
  end: [x3, y3],
30381
- className: "arc-right-angle"
30412
+ className: arcClassName
30382
30413
  }) : /*#__PURE__*/React__namespace.createElement(Arc, {
30383
30414
  arc: arc,
30384
- className: "angle-arc"
30415
+ className: arcClassName
30385
30416
  }), showAngles && /*#__PURE__*/React__namespace.createElement(TextLabel, {
30386
30417
  x: textX,
30387
30418
  y: textY,
@@ -30401,10 +30432,16 @@ const RightAngleSquare = _ref3 => {
30401
30432
  className
30402
30433
  } = _ref3;
30403
30434
  return /*#__PURE__*/React__namespace.createElement(MafsCssTransformWrapper, null, /*#__PURE__*/React__namespace.createElement("path", {
30435
+ // Use aria-hidden to hide the line from screen readers
30436
+ // so it doesn't read as "image" with no context.
30437
+ // The elements using this should have their own aria-labels,
30438
+ // so this is okay.
30439
+ "aria-hidden": true,
30404
30440
  d: `M ${x1} ${y1} L ${x3} ${y3} M ${x3} ${y3} L ${x2} ${y2}`,
30405
30441
  strokeWidth: 0.02,
30406
30442
  fill: "none",
30407
- className: className
30443
+ className: className,
30444
+ "data-testid": "angle-indicators__right-angle"
30408
30445
  }));
30409
30446
  };
30410
30447
 
@@ -30416,10 +30453,16 @@ const Arc = _ref4 => {
30416
30453
  className
30417
30454
  } = _ref4;
30418
30455
  return /*#__PURE__*/React__namespace.createElement(MafsCssTransformWrapper, null, /*#__PURE__*/React__namespace.createElement("path", {
30456
+ // Use aria-hidden to hide the line from screen readers
30457
+ // so it doesn't read as "image" with no context.
30458
+ // The elements using this should have their own aria-labels,
30459
+ // so this is okay.
30460
+ "aria-hidden": true,
30419
30461
  d: arc,
30420
30462
  strokeWidth: 0.02,
30421
30463
  fill: "none",
30422
- className: className
30464
+ className: className,
30465
+ "data-testid": "angle-indicators__arc"
30423
30466
  }));
30424
30467
  };
30425
30468
  const isRightPolygonAngle = angle => Math.abs(angle - Math.PI / 2) < 0.01;
@@ -30692,7 +30735,13 @@ function Hairlines(props) {
30692
30735
  const [[verticalEndX]] = useTransformVectorsToPixels([xMax, 0]);
30693
30736
  const [[_, horizontalStartY]] = useTransformVectorsToPixels([0, yMin]);
30694
30737
  const [[__, horizontalEndY]] = useTransformVectorsToPixels([0, yMax]);
30695
- return /*#__PURE__*/React__namespace.createElement("g", null, /*#__PURE__*/React__namespace.createElement("line", {
30738
+ return /*#__PURE__*/React__namespace.createElement("g", {
30739
+ // Use aria-hidden to hide the line from screen readers
30740
+ // so it doesn't read as "image" with no context.
30741
+ // The elements using this should have their own aria-labels,
30742
+ // so this is okay.
30743
+ "aria-hidden": true
30744
+ }, /*#__PURE__*/React__namespace.createElement("line", {
30696
30745
  x1: verticalStartX,
30697
30746
  y1: y,
30698
30747
  x2: verticalEndX,
@@ -30717,11 +30766,12 @@ const hitboxSizePx = 48;
30717
30766
  const MovablePointView = /*#__PURE__*/React.forwardRef((props, hitboxRef) => {
30718
30767
  const {
30719
30768
  markings,
30720
- showTooltips
30769
+ showTooltips,
30770
+ interactiveColor
30721
30771
  } = useGraphConfig();
30722
30772
  const {
30723
30773
  point,
30724
- color = wonderBlocksTokens.color.blue,
30774
+ color = interactiveColor,
30725
30775
  dragging,
30726
30776
  focused,
30727
30777
  cursor,
@@ -30739,6 +30789,11 @@ const MovablePointView = /*#__PURE__*/React.forwardRef((props, hitboxRef) => {
30739
30789
  const [[x, y]] = useTransformVectorsToPixels(point);
30740
30790
  const showHairlines = (dragging || focused) && markings !== "none";
30741
30791
  const svgForPoint = /*#__PURE__*/React__namespace.createElement("g", {
30792
+ // Use aria-hidden to hide the line from screen readers
30793
+ // so it doesn't read as "image" with no context.
30794
+ // The elements using this should have their own aria-labels,
30795
+ // so this is okay.
30796
+ "aria-hidden": true,
30742
30797
  ref: hitboxRef,
30743
30798
  className: pointClasses,
30744
30799
  style: {
@@ -30842,9 +30897,21 @@ function useControlPoint(params) {
30842
30897
  x: srFormatNumber(point[X], locale),
30843
30898
  y: srFormatNumber(point[Y], locale)
30844
30899
  });
30900
+
30901
+ // Set the forwarded ref to the focusable handle element when it changes.
30845
30902
  React.useLayoutEffect(() => {
30846
30903
  setForwardedRef(forwardedRef, focusableHandleRef.current);
30847
30904
  }, [forwardedRef]);
30905
+
30906
+ // If the point is being dragged and is not focused, focus the focusable handle.
30907
+ React.useLayoutEffect(() => {
30908
+ if (dragging && !focused) {
30909
+ // If the point is being dragged, focus the focusable handle so that
30910
+ // users can continue to interact with the point using the keyboard or buttons.
30911
+ // This particular focus call ensures that the focus ring and hairlines are visible.
30912
+ focusableHandleRef.current?.focus();
30913
+ }
30914
+ }, [dragging, focused]);
30848
30915
  const focusableHandle = /*#__PURE__*/React__namespace.createElement("g", {
30849
30916
  "data-testid": "movable-point__focusable-handle",
30850
30917
  className: "movable-point__focusable-handle",
@@ -30905,7 +30972,6 @@ const MovableLine = props => {
30905
30972
  points: [start, end],
30906
30973
  ariaLabels,
30907
30974
  ariaDescribedBy,
30908
- color,
30909
30975
  extend,
30910
30976
  onMoveLine = () => {},
30911
30977
  onMovePoint = () => {}
@@ -30940,7 +31006,6 @@ const MovableLine = props => {
30940
31006
  ariaLive: ariaLives[0],
30941
31007
  point: start,
30942
31008
  sequenceNumber: 1,
30943
- color,
30944
31009
  onMove: p => {
30945
31010
  setAriaLives(["polite", "off", "off"]);
30946
31011
  onMovePoint(0, p);
@@ -30956,7 +31021,6 @@ const MovableLine = props => {
30956
31021
  ariaLive: ariaLives[1],
30957
31022
  point: end,
30958
31023
  sequenceNumber: 2,
30959
- color,
30960
31024
  onMove: p => {
30961
31025
  setAriaLives(["off", "polite", "off"]);
30962
31026
  onMovePoint(1, p);
@@ -30969,7 +31033,6 @@ const MovableLine = props => {
30969
31033
  ariaLive: ariaLives[2],
30970
31034
  start: start,
30971
31035
  end: end,
30972
- stroke: color,
30973
31036
  extend: extend,
30974
31037
  onMove: delta => {
30975
31038
  setAriaLives(["off", "off", "polite"]);
@@ -30978,7 +31041,6 @@ const MovableLine = props => {
30978
31041
  });
30979
31042
  return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, focusableHandle1, line, focusableHandle2, visiblePoint1, visiblePoint2);
30980
31043
  };
30981
- const defaultStroke = "var(--movable-line-stroke-color)";
30982
31044
  const Line$1 = props => {
30983
31045
  const {
30984
31046
  start,
@@ -30987,7 +31049,6 @@ const Line$1 = props => {
30987
31049
  ariaDescribedBy,
30988
31050
  ariaLive,
30989
31051
  extend,
30990
- stroke = defaultStroke,
30991
31052
  onMove
30992
31053
  } = props;
30993
31054
  const [startPtPx, endPtPx] = useTransformVectorsToPixels(start, end);
@@ -30995,7 +31056,8 @@ const Line$1 = props => {
30995
31056
  range,
30996
31057
  graphDimensionsInPixels,
30997
31058
  snapStep,
30998
- disableKeyboardInteraction
31059
+ disableKeyboardInteraction,
31060
+ interactiveColor
30999
31061
  } = useGraphConfig();
31000
31062
  let startExtend = undefined;
31001
31063
  let endExtend = undefined;
@@ -31052,7 +31114,7 @@ const Line$1 = props => {
31052
31114
  start: startPtPx,
31053
31115
  end: endPtPx,
31054
31116
  style: {
31055
- stroke,
31117
+ stroke: interactiveColor,
31056
31118
  strokeWidth: "var(--movable-line-stroke-weight)"
31057
31119
  },
31058
31120
  className: dragging ? "movable-dragging" : "",
@@ -31060,11 +31122,11 @@ const Line$1 = props => {
31060
31122
  })), startExtend && /*#__PURE__*/React__namespace.createElement(Vector, {
31061
31123
  tail: start,
31062
31124
  tip: startExtend,
31063
- color: stroke
31125
+ testId: "movable-line__vector"
31064
31126
  }), endExtend && /*#__PURE__*/React__namespace.createElement(Vector, {
31065
31127
  tail: end,
31066
31128
  tip: endExtend,
31067
- color: stroke
31129
+ testId: "movable-line__vector"
31068
31130
  }));
31069
31131
  };
31070
31132
  const getMovableLineKeyboardConstraint = (line, snapStep, pointIndex) => {
@@ -31137,7 +31199,8 @@ function AngleGraph(props) {
31137
31199
  graphState
31138
31200
  } = props;
31139
31201
  const {
31140
- graphDimensionsInPixels
31202
+ graphDimensionsInPixels,
31203
+ interactiveColor
31141
31204
  } = useGraphConfig();
31142
31205
  const i18n = usePerseusI18n();
31143
31206
  const id = React__namespace.useId();
@@ -31169,13 +31232,14 @@ function AngleGraph(props) {
31169
31232
  start: startPtPx,
31170
31233
  end: endPtPx,
31171
31234
  style: {
31172
- stroke: "var(--movable-line-stroke-color)",
31235
+ stroke: interactiveColor,
31173
31236
  strokeWidth: "var(--movable-line-stroke-weight)"
31174
- }
31237
+ },
31238
+ testId: "angle-graph__line"
31175
31239
  }), /*#__PURE__*/React__namespace.createElement(Vector, {
31176
31240
  tail: angleLines[i][1],
31177
31241
  tip: endExtend,
31178
- color: "var(--movable-line-stroke-color)"
31242
+ testId: "angle-graph__vector"
31179
31243
  }));
31180
31244
  });
31181
31245
 
@@ -31532,7 +31596,8 @@ function MovableCircle(props) {
31532
31596
  } = props;
31533
31597
  const {
31534
31598
  snapStep,
31535
- disableKeyboardInteraction
31599
+ disableKeyboardInteraction,
31600
+ interactiveColor
31536
31601
  } = useGraphConfig();
31537
31602
  const [focused, setFocused] = React__namespace.useState(false);
31538
31603
  const draggableRef = React.useRef(null);
@@ -31568,7 +31633,9 @@ function MovableCircle(props) {
31568
31633
  cx: centerPx[X],
31569
31634
  cy: centerPx[Y],
31570
31635
  rx: radiiPx[X],
31571
- ry: radiiPx[Y]
31636
+ ry: radiiPx[Y],
31637
+ stroke: interactiveColor,
31638
+ "data-testid": "movable-circle__circle"
31572
31639
  }), /*#__PURE__*/React__namespace.createElement(DragHandle, {
31573
31640
  center: center,
31574
31641
  dragging: dragging,
@@ -31585,7 +31652,8 @@ function DragHandle(props) {
31585
31652
  } = props;
31586
31653
  const [centerPx] = useTransformVectorsToPixels(center);
31587
31654
  const {
31588
- markings
31655
+ markings,
31656
+ interactiveColor
31589
31657
  } = useGraphConfig();
31590
31658
  const cornerRadius = Math.min(...dragHandleDimensions) / 2;
31591
31659
  const topLeft = mafs.vec.sub(centerPx, mafs.vec.scale(dragHandleDimensions, 0.5));
@@ -31599,7 +31667,9 @@ function DragHandle(props) {
31599
31667
  width: dragHandleDimensions[X],
31600
31668
  height: dragHandleDimensions[Y],
31601
31669
  rx: cornerRadius,
31602
- ry: cornerRadius
31670
+ ry: cornerRadius,
31671
+ fill: interactiveColor,
31672
+ "data-testid": "movable-circle__handle"
31603
31673
  }), dragHandleDotPositions.map(offsetPx => {
31604
31674
  const [xPx, yPx] = mafs.vec.add(offsetPx, centerPx);
31605
31675
  return /*#__PURE__*/React__namespace.createElement("circle", {
@@ -31758,8 +31828,7 @@ const LinearGraph = (props, key) => {
31758
31828
  start: true,
31759
31829
  end: true
31760
31830
  },
31761
- onMovePoint: (endpointIndex, destination) => dispatch(actions.linear.movePoint(endpointIndex, destination)),
31762
- color: "var(--movable-line-stroke-color)"
31831
+ onMovePoint: (endpointIndex, destination) => dispatch(actions.linear.movePoint(endpointIndex, destination))
31763
31832
  }), /*#__PURE__*/React__namespace.createElement("g", {
31764
31833
  id: pointsDescriptionId,
31765
31834
  style: a11y.srOnly
@@ -31901,8 +31970,7 @@ const LinearSystemGraph = props => {
31901
31970
  start: true,
31902
31971
  end: true
31903
31972
  },
31904
- onMovePoint: (endpointIndex, destination) => dispatch(actions.linearSystem.movePointInFigure(i, endpointIndex, destination)),
31905
- color: "var(--movable-line-stroke-color)"
31973
+ onMovePoint: (endpointIndex, destination) => dispatch(actions.linearSystem.movePointInFigure(i, endpointIndex, destination))
31906
31974
  })), linesAriaInfo.map(_ref2 => {
31907
31975
  let {
31908
31976
  pointsDescriptionId,
@@ -32748,6 +32816,7 @@ function doMovePoint(state, action) {
32748
32816
  return {
32749
32817
  ...state,
32750
32818
  hasBeenInteractedWith: true,
32819
+ focusedPointIndex: action.index,
32751
32820
  coords: setAtIndex({
32752
32821
  array: state.coords,
32753
32822
  index: action.index,
@@ -33083,7 +33152,9 @@ function boundAndSnapToPolygonAngle(destinationPoint, _ref6, index) {
33083
33152
  coords
33084
33153
  } = _ref6;
33085
33154
  const startingPoint = coords[index];
33086
-
33155
+ return calculateAngleSnap(destinationPoint, range, coords, index, startingPoint);
33156
+ }
33157
+ function calculateAngleSnap(destinationPoint, range, coords, index, startingPoint) {
33087
33158
  // Needed to prevent updating the original coords before the checks for
33088
33159
  // degenerate triangles and overlapping sides
33089
33160
  const coordsCopy = [...coords];
@@ -33375,7 +33446,8 @@ const LimitedPolygonGraph = statefulProps => {
33375
33446
  snapStep
33376
33447
  } = statefulProps.graphState;
33377
33448
  const {
33378
- disableKeyboardInteraction
33449
+ disableKeyboardInteraction,
33450
+ interactiveColor
33379
33451
  } = graphConfig;
33380
33452
  const {
33381
33453
  strings,
@@ -33411,12 +33483,16 @@ const LimitedPolygonGraph = statefulProps => {
33411
33483
  "aria-describedby": `${polygonPointsNumId} ${polygonPointsId}`
33412
33484
  }, /*#__PURE__*/React__namespace.createElement(mafs.Polygon, {
33413
33485
  points: [...points],
33414
- color: "var(--movable-line-stroke-color)",
33486
+ color: interactiveColor,
33415
33487
  svgPolygonProps: {
33416
33488
  strokeWidth: focusVisible ? "var(--movable-line-stroke-weight-active)" : "var(--movable-line-stroke-weight)",
33417
33489
  style: {
33418
33490
  fill: "transparent"
33419
- }
33491
+ },
33492
+ // Use aria-hidden to hide the line from screen readers
33493
+ // so it doesn't read as "image" with no context.
33494
+ // This is okay because the graph has its own aria-label.
33495
+ "aria-hidden": true
33420
33496
  }
33421
33497
  }), points.map((point, i) => {
33422
33498
  const pt1 = points.at(i - 1);
@@ -33557,6 +33633,9 @@ const UnlimitedPolygonGraph = statefulProps => {
33557
33633
  strings,
33558
33634
  locale
33559
33635
  } = usePerseusI18n();
33636
+ const {
33637
+ interactiveColor
33638
+ } = useGraphConfig();
33560
33639
  const id = React__namespace.useId();
33561
33640
  const polygonPointsNumId = id + "-points-num";
33562
33641
  const polygonPointsId = id + "-points";
@@ -33597,14 +33676,22 @@ const UnlimitedPolygonGraph = statefulProps => {
33597
33676
  "aria-describedby": `${polygonPointsNumId} ${polygonPointsId}`
33598
33677
  }, /*#__PURE__*/React__namespace.createElement(mafs.Polyline, {
33599
33678
  points: [...points],
33600
- color: "var(--movable-line-stroke-color)",
33679
+ color: interactiveColor,
33601
33680
  svgPolylineProps: {
33602
33681
  strokeWidth: "var(--movable-line-stroke-weight)",
33603
33682
  style: {
33604
33683
  fill: "transparent"
33605
- }
33684
+ },
33685
+ // Use aria-hidden to hide the line from screen readers
33686
+ // so it doesn't read as "image" with no context.
33687
+ // This is okay because the graph has its own aria-label.
33688
+ "aria-hidden": true
33606
33689
  }
33607
33690
  }), /*#__PURE__*/React__namespace.createElement("rect", {
33691
+ // Use aria-hidden to hide the line from screen readers
33692
+ // so it doesn't read as "image" with no context.
33693
+ // This is okay because the graph has its own aria-label.
33694
+ "aria-hidden": true,
33608
33695
  style: {
33609
33696
  fill: "rgba(0,0,0,0)",
33610
33697
  cursor: "crosshair"
@@ -33758,7 +33845,7 @@ function getKeyboardMovementConstraintForPoint(points, index, range, snapStep, s
33758
33845
  case "sides":
33759
33846
  return getSideSnapConstraint(points, index, range);
33760
33847
  case "angles":
33761
- return p => p;
33848
+ return getAngleSnapConstraint(points, index, range);
33762
33849
  default:
33763
33850
  throw new wonderStuffCore.UnreachableCaseError(snapTo);
33764
33851
  }
@@ -33812,6 +33899,49 @@ function getSideSnapConstraint(points, index, range) {
33812
33899
  right: movePointWithConstraint(coord => mafs.vec.add(coord, [1, 0]))
33813
33900
  };
33814
33901
  }
33902
+ function getAngleSnapConstraint(points, index, range) {
33903
+ // Make newPoints mutable.
33904
+ const newPoints = [...points];
33905
+
33906
+ // Get the point that is being moved.
33907
+ const pointToBeMoved = newPoints[index];
33908
+
33909
+ // Create a helper function that moves the point to a valid location
33910
+ // for angle snapping.
33911
+ const movePointWithConstraint = moveFunc => {
33912
+ // The direction the user is attempting to move the point in.
33913
+ let destinationAttempt = bound$1({
33914
+ snapStep: [0, 0],
33915
+ range,
33916
+ point: moveFunc(pointToBeMoved)
33917
+ });
33918
+ // The new point we're moving to.
33919
+ let newPoint = pointToBeMoved;
33920
+
33921
+ // Move the point and keep trying until we are at the boarder
33922
+ // of the graph.
33923
+ while (newPoint[0] === pointToBeMoved[0] && newPoint[1] === pointToBeMoved[1] && isInBound({
33924
+ range,
33925
+ point: destinationAttempt
33926
+ })) {
33927
+ newPoint = calculateAngleSnap(destinationAttempt, range, newPoints, index, pointToBeMoved);
33928
+
33929
+ // Increment the destinationAttempt.
33930
+ // For every time it does not work increment the direction for x and y.
33931
+ destinationAttempt = moveFunc(destinationAttempt);
33932
+ }
33933
+ return newPoint;
33934
+ };
33935
+
33936
+ // For each direction look for the next movable point by a small step to increase changes
33937
+ // of finding the next angle value.
33938
+ return {
33939
+ up: movePointWithConstraint(coord => mafs.vec.add(coord, [0, 0.1])),
33940
+ down: movePointWithConstraint(coord => mafs.vec.sub(coord, [0, 0.1])),
33941
+ left: movePointWithConstraint(coord => mafs.vec.sub(coord, [0.1, 0])),
33942
+ right: movePointWithConstraint(coord => mafs.vec.add(coord, [0.1, 0]))
33943
+ };
33944
+ }
33815
33945
 
33816
33946
  function renderQuadraticGraph(state, dispatch, i18n) {
33817
33947
  return {
@@ -33831,6 +33961,9 @@ function QuadraticGraph(props) {
33831
33961
  coords,
33832
33962
  snapStep
33833
33963
  } = graphState;
33964
+ const {
33965
+ interactiveColor
33966
+ } = useGraphConfig();
33834
33967
  const {
33835
33968
  strings,
33836
33969
  locale
@@ -33872,7 +34005,13 @@ function QuadraticGraph(props) {
33872
34005
  "aria-describedby": `${quadraticDirectionId} ${quadraticVertexId} ${quadraticInterceptsId}`
33873
34006
  }, /*#__PURE__*/React__namespace.createElement(mafs.Plot.OfX, {
33874
34007
  y: y,
33875
- color: wonderBlocksTokens.color.blue
34008
+ color: interactiveColor,
34009
+ svgPathProps: {
34010
+ // Use aria-hidden to hide the line from screen readers
34011
+ // so it doesn't read as "image" with no context.
34012
+ // This is okay because the graph has its own aria-label.
34013
+ "aria-hidden": true
34014
+ }
33876
34015
  }), coords.map((coord, i) => {
33877
34016
  const srQuadraticPoint = getQuadraticPointString(i + 1, coord, strings, locale);
33878
34017
  // Vertex might not exist if the quadratic graph is a line
@@ -34272,6 +34411,9 @@ function SinusoidGraph(props) {
34272
34411
  dispatch,
34273
34412
  graphState
34274
34413
  } = props;
34414
+ const {
34415
+ interactiveColor
34416
+ } = useGraphConfig();
34275
34417
  const i18n = usePerseusI18n();
34276
34418
  const id = React__namespace.useId();
34277
34419
  const descriptionId = id + "-description";
@@ -34314,7 +34456,13 @@ function SinusoidGraph(props) {
34314
34456
  "aria-describedby": descriptionId
34315
34457
  }, /*#__PURE__*/React__namespace.createElement(mafs.Plot.OfX, {
34316
34458
  y: x => computeSine(x, coeffRef.current),
34317
- color: wonderBlocksTokens.color.blue
34459
+ color: interactiveColor,
34460
+ svgPathProps: {
34461
+ // Use aria-hidden to hide the line from screen readers
34462
+ // so it doesn't read as "image" with no context.
34463
+ // This is okay because the graph has its own aria-label.
34464
+ "aria-hidden": true
34465
+ }
34318
34466
  }), coords.map((coord, i) => /*#__PURE__*/React__namespace.createElement(MovablePoint$1, {
34319
34467
  ariaLabel: i === 0 ? srSinusoidRootPoint : srSinusoidPeakPoint,
34320
34468
  key: "point-" + i,
@@ -34663,6 +34811,7 @@ const MafsGraph = props => {
34663
34811
  i18n,
34664
34812
  markings: props.markings
34665
34813
  });
34814
+ const disableInteraction = readOnly || !!props.static;
34666
34815
  return /*#__PURE__*/React__namespace.createElement(GraphConfigContext.Provider, {
34667
34816
  value: {
34668
34817
  range: state.range,
@@ -34675,7 +34824,12 @@ const MafsGraph = props => {
34675
34824
  width,
34676
34825
  height,
34677
34826
  labels,
34678
- disableKeyboardInteraction: readOnly || !!props.static
34827
+ disableKeyboardInteraction: disableInteraction,
34828
+ // If the graph is read-only or static, we want to make it
34829
+ // visually clear that the graph is no longer interactive.
34830
+ // We do this by changing the color of the interactive elements
34831
+ // to a static gray color rather than our standard blue.
34832
+ interactiveColor: disableInteraction ? "var(--static-gray)" : "var(--mafs-blue)"
34679
34833
  }
34680
34834
  }, /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
34681
34835
  className: "mafs-graph-container"
@@ -35701,7 +35855,8 @@ class InteractiveGraph extends React__namespace.Component {
35701
35855
  const staticTransform$3 = ___default["default"].identity;
35702
35856
  var InteractiveGraph$1 = {
35703
35857
  name: "interactive-graph",
35704
- displayName: "Interactive graph (Assessments only)",
35858
+ displayName: "Interactive graph",
35859
+ accessible: true,
35705
35860
  widget: InteractiveGraph,
35706
35861
  staticTransform: staticTransform$3
35707
35862
  };
@@ -41796,7 +41951,7 @@ const addLibraryVersionToPerseusDebug = (libraryName, libraryVersion) => {
41796
41951
 
41797
41952
  // This file is processed by a Rollup plugin (replace) to inject the production
41798
41953
  const libName = "@khanacademy/perseus";
41799
- const libVersion = "56.0.0";
41954
+ const libVersion = "56.2.0";
41800
41955
  addLibraryVersionToPerseusDebug(libName, libVersion);
41801
41956
 
41802
41957
  const apiVersion = {