@khanacademy/perseus 49.0.3 → 49.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/es/index.js CHANGED
@@ -303,7 +303,34 @@ const mockStrings = {
303
303
  xIntercept,
304
304
  yIntercept
305
305
  }) => `The line crosses the X-axis at ${xIntercept} comma 0 and the Y-axis at 0 comma ${yIntercept}.`,
306
- srLinearGraphOriginIntercept: "The line crosses the x and y axes at the graph's origin."
306
+ srLinearGraphOriginIntercept: "The line crosses the x and y axes at the graph's origin.",
307
+ srLinearGrabHandle: ({
308
+ point1X,
309
+ point1Y,
310
+ point2X,
311
+ point2Y
312
+ }) => `Line from ${point1X} comma ${point1Y} to ${point2X} comma ${point2Y}.`,
313
+ srAngleSideAtCoordinates: ({
314
+ point,
315
+ side,
316
+ x,
317
+ y
318
+ }) => `Point ${point}, ${side} at ${x} comma ${y}`,
319
+ srAngleVertexAtCoordinatesWithAngleMeasure: ({
320
+ x,
321
+ y,
322
+ angleMeasure
323
+ }) => `Point 2, vertex at ${x} comma ${y}. Angle ${angleMeasure} degrees`,
324
+ srAngleGraphAriaLabel: "An angle on a coordinate plane.",
325
+ srAngleGraphAriaDescription: ({
326
+ angleMeasure,
327
+ vertexX,
328
+ vertexY,
329
+ isX,
330
+ isY,
331
+ tsX,
332
+ tsY
333
+ }) => `The angle measure is ${angleMeasure} degrees with a vertex at ${vertexX} comma ${vertexY}, a point on the initial side at ${isX} comma ${isY} and a point on the terminal side at ${tsX} comma ${tsY}.`
307
334
  // The above strings are used for interactive graph SR descriptions.
308
335
  };
309
336
 
@@ -33850,15 +33877,21 @@ const noop$1 = () => {};
33850
33877
 
33851
33878
  const MovableLine = props => {
33852
33879
  const {
33880
+ points: [start, end],
33853
33881
  ariaLabels,
33854
33882
  ariaDescribedBy,
33855
- onMoveLine = () => {},
33856
- onMovePoint = () => {},
33857
33883
  color,
33858
- points: [start, end],
33859
- extend
33884
+ extend,
33885
+ onMoveLine = () => {},
33886
+ onMovePoint = () => {}
33860
33887
  } = props;
33861
33888
 
33889
+ // Aria live states for (0) point 1, (1) point 2, and (2) grab handle.
33890
+ // When moving an element, set its aria live to "polite" and the others
33891
+ // to "off". Otherwise, other connected elements that move at the same
33892
+ // time might override the currently focused element's aria live.
33893
+ const [ariaLives, setAriaLives] = React.useState(["off", "off", "off"]);
33894
+
33862
33895
  // We use separate focusableHandle elements, instead of letting the movable
33863
33896
  // points themselves be focusable, to allow the tab order of the points to
33864
33897
  // be different from the rendering order. We had to solve for the following
@@ -33876,10 +33909,14 @@ const MovableLine = props => {
33876
33909
  } = useControlPoint({
33877
33910
  ariaLabel: ariaLabels == null ? void 0 : ariaLabels.point1AriaLabel,
33878
33911
  ariaDescribedBy: ariaDescribedBy,
33912
+ ariaLive: ariaLives[0],
33879
33913
  point: start,
33880
33914
  sequenceNumber: 1,
33881
33915
  color,
33882
- onMove: p => onMovePoint(0, p)
33916
+ onMove: p => {
33917
+ setAriaLives(["polite", "off", "off"]);
33918
+ onMovePoint(0, p);
33919
+ }
33883
33920
  });
33884
33921
  const {
33885
33922
  visiblePoint: visiblePoint2,
@@ -33887,17 +33924,27 @@ const MovableLine = props => {
33887
33924
  } = useControlPoint({
33888
33925
  ariaLabel: ariaLabels == null ? void 0 : ariaLabels.point2AriaLabel,
33889
33926
  ariaDescribedBy: ariaDescribedBy,
33927
+ ariaLive: ariaLives[1],
33890
33928
  point: end,
33891
33929
  sequenceNumber: 2,
33892
33930
  color,
33893
- onMove: p => onMovePoint(1, p)
33931
+ onMove: p => {
33932
+ setAriaLives(["off", "polite", "off"]);
33933
+ onMovePoint(1, p);
33934
+ }
33894
33935
  });
33895
33936
  const line = /*#__PURE__*/React.createElement(Line$1, {
33937
+ ariaLabel: ariaLabels == null ? void 0 : ariaLabels.grabHandleAriaLabel,
33938
+ ariaDescribedBy: ariaDescribedBy,
33939
+ ariaLive: ariaLives[2],
33896
33940
  start: start,
33897
33941
  end: end,
33898
33942
  stroke: color,
33899
33943
  extend: extend,
33900
- onMove: onMoveLine
33944
+ onMove: delta => {
33945
+ setAriaLives(["off", "off", "polite"]);
33946
+ onMoveLine(delta);
33947
+ }
33901
33948
  });
33902
33949
  return /*#__PURE__*/React.createElement(React.Fragment, null, focusableHandle1, line, focusableHandle2, visiblePoint1, visiblePoint2);
33903
33950
  };
@@ -33906,9 +33953,12 @@ const Line$1 = props => {
33906
33953
  const {
33907
33954
  start,
33908
33955
  end,
33909
- onMove,
33956
+ ariaLabel,
33957
+ ariaDescribedBy,
33958
+ ariaLive,
33910
33959
  extend,
33911
- stroke = defaultStroke
33960
+ stroke = defaultStroke,
33961
+ onMove
33912
33962
  } = props;
33913
33963
  const [startPtPx, endPtPx] = useTransformVectorsToPixels(start, end);
33914
33964
  const {
@@ -33938,11 +33988,19 @@ const Line$1 = props => {
33938
33988
  return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("g", {
33939
33989
  ref: line,
33940
33990
  tabIndex: disableKeyboardInteraction ? -1 : 0,
33991
+ "aria-label": ariaLabel,
33992
+ "aria-describedby": ariaDescribedBy,
33993
+ "aria-live": ariaLive,
33941
33994
  className: "movable-line",
33942
33995
  "data-testid": "movable-line",
33943
33996
  style: {
33944
33997
  cursor: dragging ? "grabbing" : "grab"
33945
33998
  }
33999
+ // Indicate that this element is interactive.
34000
+ // As a bonus, giving this group a non-group role makes
34001
+ // the screen reader skip over its empty children.
34002
+ ,
34003
+ role: "button"
33946
34004
  }, /*#__PURE__*/React.createElement(SVGLine, {
33947
34005
  start: startPtPx,
33948
34006
  end: endPtPx,
@@ -34065,23 +34123,78 @@ function AngleGraph(props) {
34065
34123
  showAngles: showAngles || false // Whether to show the angle or not
34066
34124
  };
34067
34125
 
34126
+ const {
34127
+ strings,
34128
+ locale
34129
+ } = usePerseusI18n();
34130
+ const angleMeasure = srFormatNumber(getClockwiseAngle([endPoints[0], centerPoint, endPoints[1]], allowReflexAngles), locale);
34131
+ const wholeAngleAriaLabel = strings.srAngleGraphAriaLabel;
34132
+ const wholeAngleDescription = strings.srAngleGraphAriaDescription({
34133
+ angleMeasure,
34134
+ vertexX: srFormatNumber(coords[1][X], locale),
34135
+ vertexY: srFormatNumber(coords[1][Y], locale),
34136
+ isX: srFormatNumber(coords[2][X], locale),
34137
+ isY: srFormatNumber(coords[2][Y], locale),
34138
+ tsX: srFormatNumber(coords[0][X], locale),
34139
+ tsY: srFormatNumber(coords[0][Y], locale)
34140
+ });
34141
+ const formatCoordinates = (x, y) => ({
34142
+ x: srFormatNumber(x, locale),
34143
+ y: srFormatNumber(y, locale)
34144
+ });
34145
+ const formatSideAriaLabel = (side, newPoint) => {
34146
+ const formattedCoordinates = formatCoordinates(newPoint[X], newPoint[Y]);
34147
+ const label = side === "terminal" ? strings.srAngleSideAtCoordinates(_extends({
34148
+ point: 1,
34149
+ side: "terminal side"
34150
+ }, formattedCoordinates)) : strings.srAngleSideAtCoordinates(_extends({
34151
+ point: 3,
34152
+ side: "initial side"
34153
+ }, formattedCoordinates));
34154
+ return label;
34155
+ };
34156
+ const formatVertexAriaLabel = newPoint => {
34157
+ const formattedVertexCoordinates = formatCoordinates(newPoint[X], newPoint[Y]);
34158
+ const label = showAngles ? strings.srAngleVertexAtCoordinatesWithAngleMeasure(_extends({}, formattedVertexCoordinates, {
34159
+ angleMeasure: angleMeasure
34160
+ })) : strings.srAngleSideAtCoordinates(_extends({
34161
+ point: 2,
34162
+ side: "vertex"
34163
+ }, formattedVertexCoordinates));
34164
+ return label;
34165
+ };
34166
+ const initialSideAriaLabel = formatSideAriaLabel("initial", coords[2]);
34167
+ const terminalSideAriaLabel = formatSideAriaLabel("terminal", coords[0]);
34168
+ const vertexAriaLabel = formatVertexAriaLabel(coords[1]);
34169
+
34068
34170
  // Render the lines, angle, and then movable points
34069
- return /*#__PURE__*/React.createElement(React.Fragment, null, svgLines, /*#__PURE__*/React.createElement(Angle, angleParams), /*#__PURE__*/React.createElement(MovablePoint$1, {
34171
+ return /*#__PURE__*/React.createElement("g", {
34172
+ "aria-label": wholeAngleAriaLabel,
34173
+ "aria-describedby": "angle-description"
34174
+ }, svgLines, /*#__PURE__*/React.createElement(Angle, angleParams), /*#__PURE__*/React.createElement(MovablePoint$1, {
34070
34175
  point: coords[1],
34071
34176
  sequenceNumber: 1,
34072
34177
  constrain: p => p,
34073
- onMove: destination => dispatch(actions.angle.movePoint(1, destination))
34178
+ onMove: destination => dispatch(actions.angle.movePoint(1, destination)),
34179
+ ariaLabel: vertexAriaLabel
34074
34180
  }), /*#__PURE__*/React.createElement(MovablePoint$1, {
34075
34181
  point: coords[0],
34076
34182
  sequenceNumber: 2,
34077
34183
  constrain: getAngleSideConstraint(coords[0], coords[1], snapDegrees || 1),
34078
- onMove: destination => dispatch(actions.angle.movePoint(0, destination))
34184
+ onMove: destination => dispatch(actions.angle.movePoint(0, destination)),
34185
+ ariaLabel: terminalSideAriaLabel
34079
34186
  }), /*#__PURE__*/React.createElement(MovablePoint$1, {
34080
34187
  point: coords[2],
34081
34188
  sequenceNumber: 3,
34082
34189
  constrain: getAngleSideConstraint(coords[2], coords[1], snapDegrees || 1),
34083
- onMove: destination => dispatch(actions.angle.movePoint(2, destination))
34084
- }));
34190
+ onMove: destination => dispatch(actions.angle.movePoint(2, destination)),
34191
+ ariaLabel: initialSideAriaLabel
34192
+ }), /*#__PURE__*/React.createElement("g", {
34193
+ id: "angle-description",
34194
+ style: {
34195
+ display: "hidden"
34196
+ }
34197
+ }, wholeAngleDescription));
34085
34198
  }
34086
34199
  const positiveX = [1, 0];
34087
34200
  const negativeX = [-1, 0];
@@ -34442,6 +34555,12 @@ const LinearGraph = (props, key) => {
34442
34555
  point2X: srFormatNumber(line[1][0], locale),
34443
34556
  point2Y: srFormatNumber(line[1][1], locale)
34444
34557
  });
34558
+ const grabHandleAriaLabel = strings.srLinearGrabHandle({
34559
+ point1X: srFormatNumber(line[0][0], locale),
34560
+ point1Y: srFormatNumber(line[0][1], locale),
34561
+ point2X: srFormatNumber(line[1][0], locale),
34562
+ point2Y: srFormatNumber(line[1][1], locale)
34563
+ });
34445
34564
 
34446
34565
  // Slope description
34447
34566
  const slope = (line[1][1] - line[0][1]) / (line[1][0] - line[0][0]);
@@ -34483,9 +34602,13 @@ const LinearGraph = (props, key) => {
34483
34602
  // Outer line minimal description
34484
34603
  ,
34485
34604
  "aria-label": strings.srLinearGraph,
34486
- "aria-describedby": `${pointsDescriptionId} ${interceptDescriptionId} ${slopeDescriptionId}`
34605
+ "aria-describedby": `${pointsDescriptionId} ${interceptDescriptionId} ${slopeDescriptionId}`,
34606
+ role: "figure"
34487
34607
  }, /*#__PURE__*/React.createElement(MovableLine, {
34488
34608
  key: 0,
34609
+ ariaLabels: {
34610
+ grabHandleAriaLabel: grabHandleAriaLabel
34611
+ },
34489
34612
  ariaDescribedBy: `${interceptDescriptionId} ${slopeDescriptionId}`,
34490
34613
  points: line,
34491
34614
  onMoveLine: delta => {
@@ -45431,7 +45554,7 @@ const init = function init() {
45431
45554
 
45432
45555
  // This file is processed by a Rollup plugin (replace) to inject the production
45433
45556
  const libName = "@khanacademy/perseus";
45434
- const libVersion = "49.0.3";
45557
+ const libVersion = "49.1.2";
45435
45558
  addLibraryVersionToPerseusDebug(libName, libVersion);
45436
45559
 
45437
45560
  const apiVersion = {