@khanacademy/perseus 57.0.0 → 57.1.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.js CHANGED
@@ -234,8 +234,8 @@ const mockStrings = {
234
234
  simulationLocaleWarning: "Sorry, this simulation isn't available in your language.",
235
235
  selectAnAnswer: "Select an answer",
236
236
  // The following strings are used for interactive graph SR descriptions.
237
- srGraphInstructions: "Use the Tab key to move through the interactive elements in the graph. When an interactive element has focus, use the Arrow keys to move it.",
238
- srUnlimitedGraphInstructions: "Press Shift + Enter to interact with the graph. Use the Tab key to move through the interactive elements in the graph and access the graph Action Bar. When an interactive element has focus, use the Arrow keys to move it or use the Delete key to remove it from the graph. Use the buttons in the Action Bar to add or adjust elements within the graph.",
237
+ srGraphInstructions: "Use the Tab key to move through the interactive elements in the graph. When an interactive element has focus, use Control + Shift + Arrows to move it.",
238
+ srUnlimitedGraphInstructions: "Press Shift + Enter to interact with the graph. Use the Tab key to move through the interactive elements in the graph and access the graph Action Bar. When an interactive element has focus, use Control + Shift + Arrows to move it or use the Delete key to remove it from the graph. Use the buttons in the Action Bar to add or adjust elements within the graph.",
239
239
  xAxis: "X-axis",
240
240
  yAxis: "Y-axis",
241
241
  graphKeyboardPrompt: "Press Shift + Enter to interact with the graph",
@@ -24998,7 +24998,7 @@ class GradedGroupAnswerBar extends React.Component {
24998
24998
  }
24999
24999
  }
25000
25000
  GradedGroupAnswerBar.contextType = PerseusI18nContext;
25001
- const fontSize$1 = 17;
25001
+ const fontSize = 17;
25002
25002
  const styles$j = {
25003
25003
  answerBar: {
25004
25004
  display: "flex",
@@ -25023,7 +25023,7 @@ const styles$j = {
25023
25023
  flexDirection: "row",
25024
25024
  alignItems: "center",
25025
25025
  fontWeight: "bold",
25026
- fontSize: fontSize$1
25026
+ fontSize: fontSize
25027
25027
  }
25028
25028
  };
25029
25029
 
@@ -27679,7 +27679,6 @@ const defaultGraphConfig = {
27679
27679
  width: 0,
27680
27680
  height: 0,
27681
27681
  labels: [],
27682
- labelLocation: "onAxis",
27683
27682
  disableKeyboardInteraction: false,
27684
27683
  interactiveColor: "var(--mafs-blue)"
27685
27684
  };
@@ -27895,8 +27894,6 @@ function escapeIfUnescaped(character) {
27895
27894
  }
27896
27895
  }
27897
27896
 
27898
- // Exported for testing purposes
27899
- const fontSize = 14;
27900
27897
  function AxisLabels({
27901
27898
  i18n
27902
27899
  }) {
@@ -27904,26 +27901,24 @@ function AxisLabels({
27904
27901
  range,
27905
27902
  labels,
27906
27903
  width,
27907
- height,
27908
- labelLocation
27904
+ height
27909
27905
  } = useGraphConfig();
27910
27906
  const {
27911
27907
  strings
27912
27908
  } = i18n;
27909
+ const yAxisLabelLocation = [0, range[Y][MAX]];
27910
+ const xAxisLabelLocation = [range[X][MAX], 0];
27911
+ const [xAxisLabelText, yAxisLabelText] = labels;
27913
27912
  const graphInfo = {
27914
27913
  range,
27915
27914
  width,
27916
27915
  height
27917
27916
  };
27918
-
27919
- // Get the position of the main axis labels
27920
- const [xAxisLabelLocation, yAxisLabelLocation] = getLabelPosition(graphInfo, labelLocation);
27921
- const [xAxisLabelText, yAxisLabelText] = labels;
27922
-
27923
- // If the labels are rotated, we need to center the
27924
- // x-axis label along the bottom of the graph.
27925
- const xLabelTransform = labelLocation === "onAxis" ? "translate(7px, -50%)" : "translate(-50%, -50%)";
27926
- const yLabelTransform = labelLocation === "onAxis" ? "translate(-50%, 0px)" : "translate(-50%, 0px) rotate(-90deg)";
27917
+ const [x1, y1] = pointToPixel(xAxisLabelLocation, graphInfo);
27918
+ // The default location for the y-axis-label is at the maximum y point
27919
+ // This is just underneath the tick line for that point. The -24 moves the
27920
+ // label up one grid square, so it sits on top of the graph
27921
+ const [x2, y2] = vec.add(pointToPixel(yAxisLabelLocation, graphInfo), [0, -24]);
27927
27922
  const {
27928
27923
  TeX
27929
27924
  } = getDependencies();
@@ -27931,53 +27926,22 @@ function AxisLabels({
27931
27926
  "aria-label": strings.xAxis,
27932
27927
  style: {
27933
27928
  position: "absolute",
27934
- left: xAxisLabelLocation[X],
27935
- top: xAxisLabelLocation[Y],
27936
- fontSize: fontSize + "px",
27937
- transform: xLabelTransform
27929
+ left: x1,
27930
+ top: y1,
27931
+ fontSize: "14px",
27932
+ transform: "translate(7px, -50%)"
27938
27933
  }
27939
27934
  }, /*#__PURE__*/React__default.createElement(TeX, null, replaceOutsideTeX(xAxisLabelText))), /*#__PURE__*/React__default.createElement("span", {
27940
27935
  "aria-label": strings.yAxis,
27941
27936
  style: {
27942
27937
  position: "absolute",
27943
- left: yAxisLabelLocation[X],
27944
- top: yAxisLabelLocation[Y],
27945
- fontSize: fontSize + "px",
27946
- transform: yLabelTransform
27938
+ left: x2,
27939
+ top: y2,
27940
+ fontSize: "14px",
27941
+ transform: "translate(-50%, 0px)"
27947
27942
  }
27948
27943
  }, /*#__PURE__*/React__default.createElement(TeX, null, replaceOutsideTeX(yAxisLabelText))));
27949
27944
  }
27950
- const getLabelPosition = (graphInfo, labelLocation) => {
27951
- // If the labels are placed along the edge of the graph, we need to place them at the
27952
- // center of the graph, which is the average of the min and max values of the axes.
27953
- if (labelLocation === "alongEdge") {
27954
- // Offset the labels by a certain amount based on the range of the graph, to ensure that
27955
- // the labels do not overlap with the axis tick if they are out of the graph bounds.
27956
- const xAxisLabelOffset = graphInfo.range[Y][MIN] >= 0 ? [0, fontSize * 3] // Move the label down by 3 font sizes if the y-axis min is positive
27957
- : [0, fontSize]; // Move the label down by 1 font size if the y-axis min is negative
27958
- const yAxisLabelOffset = graphInfo.range[X][MIN] >= 0 ? [-fontSize * 3, -fontSize] // Move the label left by 3 font sizes if the x-axis min is positive
27959
- : [-fontSize, -fontSize]; // Move the label left by 1 font size if the x-axis min is negative
27960
-
27961
- // Calculate the location of the labels to be halfway between the min and max values of the axes
27962
- const xAxisLabelLocation = [(graphInfo.range[X][MIN] + graphInfo.range[X][MAX]) / 2, graphInfo.range[Y][MIN]];
27963
- const yAxisLabelLocation = [graphInfo.range[X][MIN], (graphInfo.range[Y][MIN] + graphInfo.range[Y][MAX]) / 2];
27964
-
27965
- // Convert the Vector2 coordinates to pixel coordinates and add the offsets
27966
- const _xLabel = vec.add(pointToPixel(xAxisLabelLocation, graphInfo), xAxisLabelOffset);
27967
- const _yLabel = vec.add(pointToPixel(yAxisLabelLocation, graphInfo), yAxisLabelOffset);
27968
- return [_xLabel, _yLabel];
27969
- }
27970
-
27971
- // Otherwise, the labels are placed on the axes (default), and we need to
27972
- // place them at the end of the axis, which is the maximum value of the axis.
27973
- const xLabelInitial = [graphInfo.range[X][MAX], 0];
27974
- const yLabelInitial = [0, graphInfo.range[Y][MAX]];
27975
- const yLabelOffset = [0, -fontSize * 2]; // Move the y-axis label up by 2 font sizes
27976
-
27977
- const xLabel = pointToPixel(xLabelInitial, graphInfo);
27978
- const yLabel = vec.add(pointToPixel(yLabelInitial, graphInfo), yLabelOffset);
27979
- return [xLabel, yLabel];
27980
- };
27981
27945
 
27982
27946
  // The size of the ticks and labels in pixels
27983
27947
  const tickSize = 10;
@@ -30197,6 +30161,7 @@ function useDraggable(args) {
30197
30161
  const {
30198
30162
  direction: yDownDirection,
30199
30163
  altKey,
30164
+ ctrlKey,
30200
30165
  metaKey,
30201
30166
  shiftKey
30202
30167
  } = state;
@@ -30206,7 +30171,7 @@ function useDraggable(args) {
30206
30171
  if (altKey || metaKey) {
30207
30172
  divisions = 200;
30208
30173
  }
30209
- if (shiftKey) {
30174
+ if (shiftKey && !ctrlKey) {
30210
30175
  divisions = 10;
30211
30176
  }
30212
30177
  const min = span / (divisions * 2);
@@ -30735,6 +30700,35 @@ const MovablePoint$1 = /*#__PURE__*/React.forwardRef((props, pointRef) => {
30735
30700
  return /*#__PURE__*/React.createElement(React.Fragment, null, focusableHandle, visiblePoint);
30736
30701
  });
30737
30702
 
30703
+ /**
30704
+ * If an element has an `aria-describedby` attribute, it needs to point to
30705
+ * a separate description element that contains the description for that
30706
+ * element.
30707
+ *
30708
+ * If the element is within an SVG, the description element must be a
30709
+ * `<span>` within a `<foreignObject>` in order for some browser/SR
30710
+ * combos to be able to access it.
30711
+ *
30712
+ * Use this SRDescInSVG component to create the <foreignObject>
30713
+ * and styling for this description element.
30714
+ */
30715
+ function SRDescInSVG(props) {
30716
+ const {
30717
+ id,
30718
+ children
30719
+ } = props;
30720
+ return /*#__PURE__*/React.createElement("foreignObject", null, /*#__PURE__*/React.createElement("span", {
30721
+ id: id
30722
+ // Hidden visually
30723
+ ,
30724
+ style: a11y.srOnly
30725
+ // Hidden from screen readers so they don't read this
30726
+ // again outside of the element that it describes.
30727
+ ,
30728
+ "aria-hidden": true
30729
+ }, children));
30730
+ }
30731
+
30738
30732
  const {
30739
30733
  calculateAngleInDegrees: calculateAngleInDegrees$1,
30740
30734
  getClockwiseAngle: getClockwiseAngle$2,
@@ -30841,9 +30835,8 @@ function AngleGraph(props) {
30841
30835
  constrain: getAngleSideConstraint(coords[2], coords[1], snapDegrees || 1),
30842
30836
  onMove: destination => dispatch(actions.angle.movePoint(2, destination)),
30843
30837
  ariaLabel: srAngleStartingSide
30844
- }), /*#__PURE__*/React.createElement("g", {
30845
- id: descriptionId,
30846
- style: a11y.srOnly
30838
+ }), /*#__PURE__*/React.createElement(SRDescInSVG, {
30839
+ id: descriptionId
30847
30840
  }, srAngleGraphAriaDescription));
30848
30841
  }
30849
30842
  function getAngleGraphDescription(state, i18n) {
@@ -31119,12 +31112,10 @@ function CircleGraph(props) {
31119
31112
  dispatch(actions.circle.moveRadiusPoint(newRadiusPoint));
31120
31113
  },
31121
31114
  constrain: getCircleKeyboardConstraint(center, radiusPoint, snapStep)
31122
- }), /*#__PURE__*/React.createElement("g", {
31123
- id: radiusId,
31124
- style: a11y.srOnly
31125
- }, srCircleRadius), /*#__PURE__*/React.createElement("g", {
31126
- id: outerPointsId,
31127
- style: a11y.srOnly
31115
+ }), /*#__PURE__*/React.createElement(SRDescInSVG, {
31116
+ id: radiusId
31117
+ }, srCircleRadius), /*#__PURE__*/React.createElement(SRDescInSVG, {
31118
+ id: outerPointsId
31128
31119
  }, srCircleOuterPoints));
31129
31120
  }
31130
31121
  function MovableCircle(props) {
@@ -31371,15 +31362,12 @@ const LinearGraph = (props, key) => {
31371
31362
  end: true
31372
31363
  },
31373
31364
  onMovePoint: (endpointIndex, destination) => dispatch(actions.linear.movePoint(endpointIndex, destination))
31374
- }), /*#__PURE__*/React.createElement("g", {
31375
- id: pointsDescriptionId,
31376
- style: a11y.srOnly
31377
- }, srLinearGraphPoints), /*#__PURE__*/React.createElement("g", {
31378
- id: interceptDescriptionId,
31379
- style: a11y.srOnly
31380
- }, interceptString), /*#__PURE__*/React.createElement("g", {
31381
- id: slopeDescriptionId,
31382
- style: a11y.srOnly
31365
+ }), /*#__PURE__*/React.createElement(SRDescInSVG, {
31366
+ id: pointsDescriptionId
31367
+ }, srLinearGraphPoints), /*#__PURE__*/React.createElement(SRDescInSVG, {
31368
+ id: interceptDescriptionId
31369
+ }, interceptString), /*#__PURE__*/React.createElement(SRDescInSVG, {
31370
+ id: slopeDescriptionId
31383
31371
  }, slopeString));
31384
31372
  };
31385
31373
  function getLinearGraphDescription(state, i18n) {
@@ -31517,21 +31505,16 @@ const LinearSystemGraph = props => {
31517
31505
  pointsDescription,
31518
31506
  interceptDescription,
31519
31507
  slopeDescription
31520
- }) => /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("g", {
31521
- key: pointsDescriptionId,
31522
- id: pointsDescriptionId,
31523
- style: a11y.srOnly
31524
- }, pointsDescription), /*#__PURE__*/React.createElement("g", {
31525
- key: interceptDescriptionId,
31526
- id: interceptDescriptionId,
31527
- style: a11y.srOnly
31528
- }, interceptDescription), /*#__PURE__*/React.createElement("g", {
31529
- key: slopeDescriptionId,
31530
- id: slopeDescriptionId,
31531
- style: a11y.srOnly
31532
- }, slopeDescription))), /*#__PURE__*/React.createElement("g", {
31533
- id: intersectionId,
31534
- style: a11y.srOnly
31508
+ }, i) => /*#__PURE__*/React.createElement("span", {
31509
+ key: `line-descriptions-${i}`
31510
+ }, /*#__PURE__*/React.createElement(SRDescInSVG, {
31511
+ id: pointsDescriptionId
31512
+ }, pointsDescription), /*#__PURE__*/React.createElement(SRDescInSVG, {
31513
+ id: interceptDescriptionId
31514
+ }, interceptDescription), /*#__PURE__*/React.createElement(SRDescInSVG, {
31515
+ id: slopeDescriptionId
31516
+ }, slopeDescription))), /*#__PURE__*/React.createElement(SRDescInSVG, {
31517
+ id: intersectionId
31535
31518
  }, intersectionDescription));
31536
31519
  };
31537
31520
 
@@ -33086,23 +33069,21 @@ const LimitedPolygonGraph = statefulProps => {
33086
33069
  // point is "polite".
33087
33070
  setAriaLives(["off", ...newPointAriaLives]);
33088
33071
  }
33089
- }), angleDegree && /*#__PURE__*/React.createElement("g", {
33072
+ }), angleDegree && /*#__PURE__*/React.createElement(SRDescInSVG, {
33090
33073
  id: angleId
33091
33074
  }, Number.isInteger(angleDegree) ? strings.srPolygonPointAngle({
33092
33075
  angle: angleDegree
33093
33076
  }) : strings.srPolygonPointAngleApprox({
33094
33077
  angle: srFormatNumber(angleDegree, locale, 1)
33095
- })), /*#__PURE__*/React.createElement("g", {
33078
+ })), /*#__PURE__*/React.createElement(SRDescInSVG, {
33096
33079
  id: side1Id
33097
- }, getPolygonSideString(side1Length, point1Index, strings, locale)), /*#__PURE__*/React.createElement("g", {
33080
+ }, getPolygonSideString(side1Length, point1Index, strings, locale)), /*#__PURE__*/React.createElement(SRDescInSVG, {
33098
33081
  id: side2Id
33099
33082
  }, getPolygonSideString(side2Length, point2Index, strings, locale)));
33100
- }), /*#__PURE__*/React.createElement("g", {
33101
- id: polygonPointsNumId,
33102
- style: a11y.srOnly
33103
- }, srPolygonGraphPointsNum), srPolygonGraphPoints && /*#__PURE__*/React.createElement("g", {
33104
- id: polygonPointsId,
33105
- style: a11y.srOnly
33083
+ }), /*#__PURE__*/React.createElement(SRDescInSVG, {
33084
+ id: polygonPointsNumId
33085
+ }, srPolygonGraphPointsNum), srPolygonGraphPoints && /*#__PURE__*/React.createElement(SRDescInSVG, {
33086
+ id: polygonPointsId
33106
33087
  }, srPolygonGraphPoints));
33107
33088
  };
33108
33089
  const UnlimitedPolygonGraph = statefulProps => {
@@ -33237,7 +33218,7 @@ const UnlimitedPolygonGraph = statefulProps => {
33237
33218
  }
33238
33219
  dispatch(actions.polygon.clickPoint(i));
33239
33220
  }
33240
- }), angleDegree && /*#__PURE__*/React.createElement("g", {
33221
+ }), angleDegree && /*#__PURE__*/React.createElement(SRDescInSVG, {
33241
33222
  id: angleId
33242
33223
  }, Number.isInteger(angleDegree) ? strings.srPolygonPointAngle({
33243
33224
  angle: angleDegree
@@ -33246,16 +33227,14 @@ const UnlimitedPolygonGraph = statefulProps => {
33246
33227
  })), sidesArray.map(({
33247
33228
  pointIndex,
33248
33229
  sideLength
33249
- }, j) => /*#__PURE__*/React.createElement("g", {
33230
+ }, j) => /*#__PURE__*/React.createElement(SRDescInSVG, {
33250
33231
  key: `${id}-point-${i}-side-${j}`,
33251
33232
  id: `${id}-point-${i}-side-${j}`
33252
33233
  }, getPolygonSideString(sideLength, pointIndex, strings, locale))));
33253
- }), coords.length > 0 && /*#__PURE__*/React.createElement("g", {
33254
- id: polygonPointsNumId,
33255
- style: a11y.srOnly
33256
- }, srPolygonGraphPointsNum), srPolygonGraphPoints && /*#__PURE__*/React.createElement("g", {
33257
- id: polygonPointsId,
33258
- style: a11y.srOnly
33234
+ }), coords.length > 0 && /*#__PURE__*/React.createElement(SRDescInSVG, {
33235
+ id: polygonPointsNumId
33236
+ }, srPolygonGraphPointsNum), srPolygonGraphPoints && /*#__PURE__*/React.createElement(SRDescInSVG, {
33237
+ id: polygonPointsId
33259
33238
  }, srPolygonGraphPoints));
33260
33239
  };
33261
33240
  function getLines(points) {
@@ -33512,15 +33491,12 @@ function QuadraticGraph(props) {
33512
33491
  constrain: getQuadraticKeyboardConstraint(coords, snapStep, i),
33513
33492
  onMove: destination => dispatch(actions.quadratic.movePoint(i, destination))
33514
33493
  });
33515
- }), srQuadraticDirection && /*#__PURE__*/React.createElement("g", {
33516
- id: quadraticDirectionId,
33517
- style: a11y.srOnly
33518
- }, srQuadraticDirection), srQuadraticVertex && /*#__PURE__*/React.createElement("g", {
33519
- id: quadraticVertexId,
33520
- style: a11y.srOnly
33521
- }, srQuadraticVertex), /*#__PURE__*/React.createElement("g", {
33522
- id: quadraticInterceptsId,
33523
- style: a11y.srOnly
33494
+ }), srQuadraticDirection && /*#__PURE__*/React.createElement(SRDescInSVG, {
33495
+ id: quadraticDirectionId
33496
+ }, srQuadraticDirection), srQuadraticVertex && /*#__PURE__*/React.createElement(SRDescInSVG, {
33497
+ id: quadraticVertexId
33498
+ }, srQuadraticVertex), /*#__PURE__*/React.createElement(SRDescInSVG, {
33499
+ id: quadraticInterceptsId
33524
33500
  }, srQuadraticXIntercepts ? `${srQuadraticXIntercepts} ${srQuadraticYIntercept}` : `${srQuadraticYIntercept}`));
33525
33501
  }
33526
33502
 
@@ -33708,9 +33684,8 @@ const RayGraph = props => {
33708
33684
  start: false,
33709
33685
  end: true
33710
33686
  }
33711
- }), /*#__PURE__*/React.createElement("g", {
33712
- id: pointsDescriptionId,
33713
- style: a11y.srOnly
33687
+ }), /*#__PURE__*/React.createElement(SRDescInSVG, {
33688
+ id: pointsDescriptionId
33714
33689
  }, srRayPoints));
33715
33690
  };
33716
33691
  function getRayGraphDescription(state, i18n) {
@@ -33848,13 +33823,11 @@ const SegmentGraph = ({
33848
33823
  point2Y: srFormatNumber(segment[1][Y], locale)
33849
33824
  })
33850
33825
  }
33851
- }), /*#__PURE__*/React.createElement("g", {
33852
- id: lengthDescriptionId,
33853
- style: a11y.srOnly
33826
+ }), /*#__PURE__*/React.createElement(SRDescInSVG, {
33827
+ id: lengthDescriptionId
33854
33828
  }, strings.srSegmentLength({
33855
33829
  length: srFormatNumber(getLengthOfSegment(segment), locale)
33856
- })))), /*#__PURE__*/React.createElement("g", {
33857
- style: a11y.srOnly,
33830
+ })))), /*#__PURE__*/React.createElement(SRDescInSVG, {
33858
33831
  id: wholeGraphDescriptionId
33859
33832
  }, getWholeSegmentGraphAriaDescription()));
33860
33833
  };
@@ -33953,7 +33926,7 @@ function SinusoidGraph(props) {
33953
33926
  sequenceNumber: i + 1,
33954
33927
  constrain: getSinusoidKeyboardConstraint(coords, snapStep, i),
33955
33928
  onMove: destination => dispatch(actions.sinusoid.movePoint(i, destination))
33956
- })), /*#__PURE__*/React.createElement("g", {
33929
+ })), /*#__PURE__*/React.createElement(SRDescInSVG, {
33957
33930
  id: descriptionId
33958
33931
  }, srSinusoidDescription));
33959
33932
  }
@@ -34227,7 +34200,6 @@ const MafsGraph = props => {
34227
34200
  state,
34228
34201
  dispatch,
34229
34202
  labels,
34230
- labelLocation,
34231
34203
  readOnly,
34232
34204
  fullGraphAriaLabel,
34233
34205
  fullGraphAriaDescription
@@ -34308,7 +34280,6 @@ const MafsGraph = props => {
34308
34280
  width,
34309
34281
  height,
34310
34282
  labels,
34311
- labelLocation,
34312
34283
  disableKeyboardInteraction: disableInteraction,
34313
34284
  // If the graph is read-only or static, we want to make it
34314
34285
  // visually clear that the graph is no longer interactive.
@@ -41420,7 +41391,7 @@ const addLibraryVersionToPerseusDebug = (libraryName, libraryVersion) => {
41420
41391
 
41421
41392
  // This file is processed by a Rollup plugin (replace) to inject the production
41422
41393
  const libName = "@khanacademy/perseus";
41423
- const libVersion = "57.0.0";
41394
+ const libVersion = "57.1.0";
41424
41395
  addLibraryVersionToPerseusDebug(libName, libVersion);
41425
41396
 
41426
41397
  const apiVersion = {