@khanacademy/perseus 53.0.0 → 53.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
@@ -1,6 +1,6 @@
1
1
  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
- import { PerseusError, Errors, expressionLogic, getExpressionPublicWidgetOptions, getDecimalSeparator, approximateDeepEqual, pluck, CoreWidgetRegistry, getUpgradedWidgetOptions, mapObject, getNumericInputPublicWidgetOptions, passageRefLogic, radioLogic, getCategorizerPublicWidgetOptions, getCSProgramPublicWidgetOptions, getDropdownPublicWidgetOptions, GrapherUtil as GrapherUtil$1, lockedFigureColors, lockedFigureFillStyles, approximateEqual, getLabelImagePublicWidgetOptions, getMatrixSize, measurerLogic, getNumberLinePublicWidgetOptions, getOrdererPublicWidgetOptions, getSorterPublicWidgetOptions, addLibraryVersionToPerseusDebug, upgradeWidgetInfoToLatestVersion, getWidgetIdsFromContentByType } from '@khanacademy/perseus-core';
3
+ import { PerseusError, Errors, expressionLogic, getExpressionPublicWidgetOptions, getDecimalSeparator, approximateDeepEqual, pluck, CoreWidgetRegistry, getUpgradedWidgetOptions, mapObject, getNumericInputPublicWidgetOptions, passageRefLogic, radioLogic, getRadioPublicWidgetOptions, getCategorizerPublicWidgetOptions, getCSProgramPublicWidgetOptions, getDropdownPublicWidgetOptions, GrapherUtil as GrapherUtil$1, getGrapherPublicWidgetOptions, getIFramePublicWidgetOptions, lockedFigureColors, lockedFigureFillStyles, approximateEqual, getInteractiveGraphPublicWidgetOptions, getLabelImagePublicWidgetOptions, getMatrixPublicWidgetOptions, getMatrixSize, measurerLogic, getNumberLinePublicWidgetOptions, getOrdererPublicWidgetOptions, getPlotterPublicWidgetOptions, getSorterPublicWidgetOptions, getTablePublicWidgetOptions, addLibraryVersionToPerseusDebug, upgradeWidgetInfoToLatestVersion, getWidgetIdsFromContentByType } from '@khanacademy/perseus-core';
4
4
  export { isFailure, isSuccess, parseAndMigratePerseusArticle, parseAndMigratePerseusItem, parsePerseusItem } from '@khanacademy/perseus-core';
5
5
  import * as PerseusLinter from '@khanacademy/perseus-linter';
6
6
  import { linterContextDefault, Rule } from '@khanacademy/perseus-linter';
@@ -249,7 +249,7 @@ const mockStrings = {
249
249
  num,
250
250
  x,
251
251
  y
252
- }) => `Point ${num} at ${x} comma ${y}`,
252
+ }) => `Point ${num} at ${x} comma ${y}.`,
253
253
  srInteractiveElements: ({
254
254
  elements
255
255
  }) => `Interactive elements: ${elements}`,
@@ -437,7 +437,32 @@ const mockStrings = {
437
437
  point2Y,
438
438
  point3X,
439
439
  point3Y
440
- }) => `Parabola with points at ${point1X} comma ${point1Y}, ${point2X} comma ${point2Y}, and ${point3X} comma ${point3Y}.`
440
+ }) => `Parabola with points at ${point1X} comma ${point1Y}, ${point2X} comma ${point2Y}, and ${point3X} comma ${point3Y}.`,
441
+ srPolygonGraph: "A polygon.",
442
+ srPolygonGraphCoordinatePlane: "A polygon on a coordinate plane.",
443
+ srPolygonGraphPointsNum: ({
444
+ num
445
+ }) => `The polygon has ${num} points.`,
446
+ srPolygonGraphPointsOne: "The polygon has 1 point.",
447
+ srPolygonElementsNum: ({
448
+ num
449
+ }) => `A polygon with ${num} points.`,
450
+ srPolygonElementsOne: "A polygon with 1 point.",
451
+ srPolygonPointAngleApprox: ({
452
+ angle
453
+ }) => `Angle approximately equal to ${angle} degrees.`,
454
+ srPolygonPointAngle: ({
455
+ angle
456
+ }) => `Angle equal to ${angle} degrees.`,
457
+ srPolygonSideLength: ({
458
+ pointNum,
459
+ length
460
+ }) => `A line segment, length equal to ${length} units, connects to point ${pointNum}.`,
461
+ srPolygonSideLengthApprox: ({
462
+ pointNum,
463
+ length
464
+ }) => `A line segment, length approximately equal to ${length} units, connects to point ${pointNum}.`,
465
+ srUnlimitedPolygonEmpty: "An empty coordinate plane."
441
466
  // The above strings are used for interactive graph SR descriptions.
442
467
  };
443
468
 
@@ -9838,7 +9863,7 @@ class WrappedLine extends WrappedDrawing {
9838
9863
 
9839
9864
  /* eslint-disable @babel/no-invalid-this */
9840
9865
  const {
9841
- clockwise: clockwise$3,
9866
+ clockwise: clockwise$2,
9842
9867
  reverseVector: reverseVector$1
9843
9868
  } = geometry;
9844
9869
  function scaledDistanceFromAngle(angle) {
@@ -11076,7 +11101,7 @@ _.extend(GraphUtils.Graphie.prototype, {
11076
11101
  // Temporary objects
11077
11102
  _.invoke(polygon.temp, "remove");
11078
11103
  polygon.temp = [];
11079
- const isClockwise = clockwise$3(polygon.coords);
11104
+ const isClockwise = clockwise$2(polygon.coords);
11080
11105
 
11081
11106
  // Update angle labels
11082
11107
  if (polygon.angleLabels.length || polygon.showRightAngleMarkers.length) {
@@ -14467,7 +14492,7 @@ function sum(array) {
14467
14492
  return memo + arg;
14468
14493
  }, 0);
14469
14494
  }
14470
- function clockwise$2(points) {
14495
+ function clockwise$1(points) {
14471
14496
  const segments = _.zip(points, points.slice(1).concat(points.slice(0, 1)));
14472
14497
  const areas = _.map(segments, function (segment) {
14473
14498
  const p1 = segment[0];
@@ -14533,7 +14558,7 @@ const draw$1 = {
14533
14558
  const graphie = this.graphie;
14534
14559
  const self = this;
14535
14560
  const coords = _.invoke(state.points, "coord");
14536
- const isClockwise = clockwise$2(coords);
14561
+ const isClockwise = clockwise$1(coords);
14537
14562
  const n = coords.length;
14538
14563
 
14539
14564
  // graphie.labelAngle and similar methods attempt to re-use the label
@@ -16270,9 +16295,29 @@ class Zoomable extends React.Component {
16270
16295
  if (!this._isMounted) {
16271
16296
  return;
16272
16297
  }
16298
+
16299
+ // When a zoomable item is contained within a collapsed parent (such as an Explanation widget),
16300
+ // the parent node has zero width, which causes the scale to be miscalculated.
16301
+ // Therefore, we check to see if the node has a valid width, and if it doesn't,
16302
+ // then we traverse up the DOM tree and look for the closest "perseus-renderer"
16303
+ // (which should have a width).
16304
+ // To be cautious, we also account for the possibility that we can't find any parents with a valid width
16305
+ // (not that they don't exist, just that we can't find them).
16306
+ let parentNode = this._node;
16307
+ let currentNode = parentNode;
16308
+ while (currentNode && currentNode.offsetWidth === 0) {
16309
+ var _currentNode$parentEl, _currentNode$parentEl2;
16310
+ // When traversing up the DOM tree, we need to start at the "parentElement"
16311
+ // because "closest()" can match the current element,
16312
+ // and if it does so, then we are stuck in an infinite loop.
16313
+ currentNode = (_currentNode$parentEl = (_currentNode$parentEl2 = currentNode.parentElement) == null ? void 0 : _currentNode$parentEl2.closest(".perseus-renderer")) != null ? _currentNode$parentEl : null;
16314
+ }
16315
+ if (currentNode) {
16316
+ parentNode = currentNode;
16317
+ }
16273
16318
  const parentBounds = {
16274
- width: this._node.offsetWidth,
16275
- height: this._node.offsetHeight
16319
+ width: parentNode.offsetWidth,
16320
+ height: parentNode.offsetHeight
16276
16321
  };
16277
16322
  const childBounds = this.props.computeChildBounds(this._node, parentBounds);
16278
16323
 
@@ -19292,7 +19337,7 @@ function _objectWithoutPropertiesLoose(r, e) {
19292
19337
  if (null == r) return {};
19293
19338
  var t = {};
19294
19339
  for (var n in r) if ({}.hasOwnProperty.call(r, n)) {
19295
- if (e.includes(n)) continue;
19340
+ if (-1 !== e.indexOf(n)) continue;
19296
19341
  t[n] = r[n];
19297
19342
  }
19298
19343
  return t;
@@ -23061,8 +23106,8 @@ const styles$s = StyleSheet.create({
23061
23106
  crossOutLine: {
23062
23107
  // Center the icon within the container.
23063
23108
  position: "absolute",
23064
- top: `0px`,
23065
- left: `0px`
23109
+ top: `2px`,
23110
+ left: `2px`
23066
23111
  }
23067
23112
  });
23068
23113
 
@@ -23089,43 +23134,6 @@ function ChoiceInner(props) {
23089
23134
  }
23090
23135
  return /*#__PURE__*/React.createElement(InlineIcon, iconMinus);
23091
23136
  }
23092
-
23093
- // Handle dynamic styling of the multiple choice icon. Most
23094
- // MC icon styles are constant, but we do allow the caller
23095
- // to specify the selected color, and thus must control styles
23096
- // related to the selected state dynamically.
23097
- function getDynamicStyles(checked, showCorrectness, pressed, multipleSelect, correct) {
23098
- let backgroundColor;
23099
- let borderColor;
23100
- let color$1;
23101
- if (!showCorrectness && pressed) {
23102
- borderColor = color.blue;
23103
- color$1 = color.blue;
23104
- backgroundColor = "transparent";
23105
- } else if (checked) {
23106
- const bg = showCorrectness && correct ? color.green : color.blue;
23107
- color$1 = white;
23108
- backgroundColor = bg;
23109
- borderColor = bg;
23110
- } else {
23111
- borderColor = color.offBlack64;
23112
- color$1 = color.offBlack64;
23113
- }
23114
-
23115
- // define shape
23116
- let borderRadius;
23117
- if (multipleSelect) {
23118
- borderRadius = 3;
23119
- } else {
23120
- borderRadius = CHOICE_ICON_SIZE;
23121
- }
23122
- return {
23123
- backgroundColor,
23124
- borderColor,
23125
- color: color$1,
23126
- borderRadius
23127
- };
23128
- }
23129
23137
  const ChoiceIcon = function ChoiceIcon(props) {
23130
23138
  const {
23131
23139
  checked,
@@ -23137,9 +23145,41 @@ const ChoiceIcon = function ChoiceIcon(props) {
23137
23145
  multipleSelect,
23138
23146
  pos,
23139
23147
  previouslyAnswered,
23148
+ // Used in "review mode"/"show rationale" to show that option was previously chosen
23140
23149
  pressed
23141
23150
  } = props;
23142
- const dynamicStyles = getDynamicStyles(checked, showCorrectness, pressed, multipleSelect, correct);
23151
+
23152
+ // Accounts for incorrect choice still displaying as learner tries again
23153
+ const choiceIsChecked = checked || showCorrectness && !correct && previouslyAnswered;
23154
+
23155
+ // Core styling
23156
+ const choiceStyling = [styles$r.choiceBase, multipleSelect ? styles$r.multiSelectShape : styles$r.singleSelectShape, showCorrectness ? styles$r.choiceHasIcon : styles$r.choiceHasLetter, choiceIsChecked ? styles$r.choiceIsChecked : styles$r.choiceIsUnchecked];
23157
+
23158
+ // Color styling
23159
+ // Handle dynamic styling of the multiple choice icon. Most
23160
+ // MC icon styles are constant, but we do allow the caller
23161
+ // to specify the selected color, and thus must control styles
23162
+ // related to the selected state dynamically.
23163
+ let crossOutColor;
23164
+ if (showCorrectness && correct && checked) {
23165
+ choiceStyling.push(styles$r.choiceCorrect);
23166
+ crossOutColor = color.green;
23167
+ } else if (showCorrectness && !correct && (checked || previouslyAnswered)) {
23168
+ choiceStyling.push(styles$r.choiceIncorrect);
23169
+ crossOutColor = color.red;
23170
+ } else if (checked) {
23171
+ // Show filled neutral blue color (showCorrectness is false)
23172
+ choiceStyling.push(styles$r.choiceNeutral);
23173
+ crossOutColor = color.blue;
23174
+ } else if (pressed) {
23175
+ // Show outlined neutral blue color (showCorrectness is false)
23176
+ choiceStyling.push(styles$r.activeNeutral);
23177
+ crossOutColor = color.blue;
23178
+ } else {
23179
+ // choice is not checked
23180
+ choiceStyling.push(styles$r.uncheckedColors);
23181
+ crossOutColor = color.offBlack64;
23182
+ }
23143
23183
  return /*#__PURE__*/React.createElement("div", {
23144
23184
  className: css(styles$t.iconWrapper)
23145
23185
  }, /*#__PURE__*/React.createElement(FocusRing, {
@@ -23147,9 +23187,8 @@ const ChoiceIcon = function ChoiceIcon(props) {
23147
23187
  visible: focused || hovered,
23148
23188
  multipleSelect: multipleSelect
23149
23189
  }, /*#__PURE__*/React.createElement("div", {
23150
- style: dynamicStyles,
23151
23190
  "data-testid": "choice-icon__library-choice-icon",
23152
- className: css(styles$r.circle, showCorrectness && correct && styles$r.circleCorrect, showCorrectness && !correct && styles$r.circleIncorrect, showCorrectness && !correct && (checked || previouslyAnswered) && styles$r.circleIncorrectAnswered)
23191
+ className: css(...choiceStyling)
23153
23192
  // used in BaseRadio editMode to check
23154
23193
  // if we actually clicked on the radio icon
23155
23194
  ,
@@ -23161,7 +23200,7 @@ const ChoiceIcon = function ChoiceIcon(props) {
23161
23200
  showCorrectness: showCorrectness,
23162
23201
  correct: correct
23163
23202
  })))), crossedOut && /*#__PURE__*/React.createElement(CrossOutLine, {
23164
- color: dynamicStyles.borderColor
23203
+ color: crossOutColor
23165
23204
  }));
23166
23205
  };
23167
23206
  const styles$r = StyleSheet.create({
@@ -23173,8 +23212,7 @@ const styles$r = StyleSheet.create({
23173
23212
  alignItems: "center",
23174
23213
  justifyContent: "center"
23175
23214
  },
23176
- circle: {
23177
- // Make the circle
23215
+ choiceBase: {
23178
23216
  width: CHOICE_ICON_SIZE,
23179
23217
  height: CHOICE_ICON_SIZE,
23180
23218
  boxSizing: "border-box",
@@ -23186,30 +23224,48 @@ const styles$r = StyleSheet.create({
23186
23224
  // "bold font family" so that characters which fall back to the default
23187
23225
  // font get bolded too.
23188
23226
  fontWeight: "bold",
23189
- fontSize: 12,
23190
23227
  // Center the icon wrapper.
23191
23228
  display: "flex",
23192
23229
  alignItems: "center",
23193
- justifyContent: "center",
23194
- // HACK(emily): I don't know why adding this line height makes the text
23195
- // appear centered better than any other value, but it does. In
23196
- // particular, at large zoom levels this line height does almost
23197
- // nothing, but at the default size this shifts the letter down one
23198
- // pixel so it is much better centered.
23199
- lineHeight: "1px"
23200
- },
23201
- circleCorrect: {
23230
+ justifyContent: "center"
23231
+ },
23232
+ choiceHasLetter: {
23233
+ fontSize: 12
23234
+ },
23235
+ choiceHasIcon: {
23202
23236
  fontSize: CHOICE_ICON_SIZE
23203
23237
  },
23204
- circleIncorrect: {
23205
- fontSize: CHOICE_ICON_SIZE,
23206
- borderColor: gray68,
23207
- color: gray68
23238
+ choiceIsChecked: {
23239
+ color: color.white
23240
+ },
23241
+ choiceIsUnchecked: {
23242
+ color: color.offBlack64
23208
23243
  },
23209
- circleIncorrectAnswered: {
23244
+ choiceCorrect: {
23245
+ backgroundColor: color.green,
23246
+ borderColor: color.green
23247
+ },
23248
+ choiceIncorrect: {
23210
23249
  backgroundColor: color.red,
23211
- borderColor: color.red,
23212
- color: color.white
23250
+ borderColor: color.red
23251
+ },
23252
+ choiceNeutral: {
23253
+ backgroundColor: color.blue,
23254
+ borderColor: color.blue
23255
+ },
23256
+ activeNeutral: {
23257
+ color: color.blue,
23258
+ borderColor: color.blue,
23259
+ backgroundColor: "transparent"
23260
+ },
23261
+ multiSelectShape: {
23262
+ borderRadius: 3
23263
+ },
23264
+ singleSelectShape: {
23265
+ borderRadius: CHOICE_ICON_SIZE
23266
+ },
23267
+ uncheckedColors: {
23268
+ borderColor: color.offBlack64
23213
23269
  }
23214
23270
  });
23215
23271
 
@@ -24335,7 +24391,8 @@ var Radio = {
24335
24391
  staticTransform: transform,
24336
24392
  version: radioLogic.version,
24337
24393
  propUpgrades: radioLogic.widgetOptionsUpgrades,
24338
- isLintable: true
24394
+ isLintable: true,
24395
+ getPublicWidgetOptions: getRadioPublicWidgetOptions
24339
24396
  };
24340
24397
 
24341
24398
  /* eslint-disable import/no-named-as-default */
@@ -26522,7 +26579,8 @@ var Grapher$1 = {
26522
26579
  hidden: true,
26523
26580
  widget: Grapher,
26524
26581
  transform: propTransform$2,
26525
- staticTransform: staticTransform$4
26582
+ staticTransform: staticTransform$4,
26583
+ getPublicWidgetOptions: getGrapherPublicWidgetOptions
26526
26584
  };
26527
26585
 
26528
26586
  const getPromptJSON$f = rendererJSON => {
@@ -26827,7 +26885,8 @@ var Iframe$1 = {
26827
26885
  displayName: "Iframe (deprecated)",
26828
26886
  widget: Iframe,
26829
26887
  // Let's not expose it to all content creators yet
26830
- hidden: true
26888
+ hidden: true,
26889
+ getPublicWidgetOptions: getIFramePublicWidgetOptions
26831
26890
  };
26832
26891
 
26833
26892
  const getPromptJSON$d = renderProps => {
@@ -29534,10 +29593,10 @@ function Vector(props) {
29534
29593
  }));
29535
29594
  }
29536
29595
 
29537
- function srFormatNumber(a, locale) {
29596
+ function srFormatNumber(a, locale, maximumFractionDigits) {
29538
29597
  // adding zero here converts negative zero to positive zero.
29539
29598
  return (0 + a).toLocaleString(locale, {
29540
- maximumFractionDigits: 3,
29599
+ maximumFractionDigits: maximumFractionDigits != null ? maximumFractionDigits : 3,
29541
29600
  useGrouping: false // no thousands separators
29542
29601
  });
29543
29602
  }
@@ -29713,6 +29772,95 @@ a, b, c) {
29713
29772
  return [x1, x2];
29714
29773
  }
29715
29774
 
29775
+ /**
29776
+ * Given a list of points, return the angle made by the point at index i
29777
+ * with the points at indices i - 1 and i + 1.
29778
+ */
29779
+ function getAngleFromPoints(points, i) {
29780
+ // Make sure the index is valid.
29781
+ if (i < 0 || i >= points.length || !Number.isInteger(i)) {
29782
+ return null;
29783
+ }
29784
+
29785
+ // If there are fewer than 3 points, there is no angle.
29786
+ if (points.length < 3) {
29787
+ return null;
29788
+ }
29789
+
29790
+ // Get the points at the current index and the two adjacent points.
29791
+ const point = points.at(i);
29792
+ const pt1 = points.at(i - 1);
29793
+ const pt2 = points[(i + 1) % points.length];
29794
+ if (!point || !pt1 || !pt2) {
29795
+ return null;
29796
+ }
29797
+
29798
+ // Get the lengths of adjacent sides, making a triangle
29799
+ // we can use to calculate the angle.
29800
+ const a = vec.dist(point, pt1);
29801
+ const b = vec.dist(point, pt2);
29802
+ const c = vec.dist(pt1, pt2);
29803
+
29804
+ // Law of cosines
29805
+ const angle = Math.acos((a ** 2 + b ** 2 - c ** 2) / (2 * a * b));
29806
+ return angle;
29807
+ }
29808
+ function getSideLengthsFromPoints(points, i, isPolygonOpen) {
29809
+ if (i < 0 || i >= points.length || !Number.isInteger(i)) {
29810
+ return [];
29811
+ }
29812
+ if (points.length < 2) {
29813
+ return [];
29814
+ }
29815
+ const returnArray = [];
29816
+ const point = points[i];
29817
+ // If this point is the first point, then side 1 is the
29818
+ // last point in the list.
29819
+ const point1Index = i === 0 ? points.length - 1 : i - 1;
29820
+ const point1 = points[point1Index];
29821
+ // Make sure the previous point is not the same
29822
+ // as the current point.
29823
+ const side1 = i !== point1Index ? vec.dist(point, point1) : null;
29824
+ // The first side of the first point does not exist if
29825
+ // the polygon is open.
29826
+ if (side1 && !(isPolygonOpen && i === 0)) {
29827
+ returnArray.push({
29828
+ pointIndex: point1Index,
29829
+ sideLength: side1
29830
+ });
29831
+ }
29832
+
29833
+ // See if there is a side 2.
29834
+ const point2Index = (i + 1) % points.length;
29835
+ const point2 = points[point2Index];
29836
+ // Make sure that the next point is not the same as the
29837
+ // current point, and don't repeat the first point.
29838
+ const side2 = i !== point2Index && point2Index !== point1Index ? vec.dist(point, point2) : null;
29839
+ // The second side of the last point does not exist if
29840
+ // the polygon is open.
29841
+ if (side2 && !(isPolygonOpen && i === points.length - 1)) {
29842
+ returnArray.push({
29843
+ pointIndex: point2Index,
29844
+ sideLength: side2
29845
+ });
29846
+ }
29847
+ return returnArray;
29848
+ }
29849
+
29850
+ /**
29851
+ * Determine whether to determine the string with the
29852
+ * exact side length or the approximate side length.
29853
+ */
29854
+ function getPolygonSideString(sideLength, pointIndex, strings, locale) {
29855
+ return Number.isInteger(sideLength) ? strings.srPolygonSideLength({
29856
+ pointNum: pointIndex + 1,
29857
+ length: `${sideLength}`
29858
+ }) : strings.srPolygonSideLengthApprox({
29859
+ pointNum: pointIndex + 1,
29860
+ length: srFormatNumber(sideLength, locale, 1)
29861
+ });
29862
+ }
29863
+
29716
29864
  const {
29717
29865
  calculateAngleInDegrees: calculateAngleInDegrees$2
29718
29866
  } = angles;
@@ -30158,7 +30306,7 @@ const SvgDefs = () => /*#__PURE__*/React__default.createElement("defs", null, /*
30158
30306
  })));
30159
30307
 
30160
30308
  const {
30161
- clockwise: clockwise$1
30309
+ clockwise
30162
30310
  } = geometry;
30163
30311
  const {
30164
30312
  getAngleFromVertex: getAngleFromVertex$1
@@ -30172,7 +30320,7 @@ const PolygonAngle = ({
30172
30320
  snapTo
30173
30321
  }) => {
30174
30322
  const [centerX, centerY] = centerPoint;
30175
- const areClockwise = clockwise$1([centerPoint, ...endPoints]);
30323
+ const areClockwise = clockwise([centerPoint, ...endPoints]);
30176
30324
  const [[startX, startY], [endX, endY]] = areClockwise ? endPoints : endPoints.reverse();
30177
30325
  const radius = 0.3;
30178
30326
  const a = vec.dist(centerPoint, endPoints[0]);
@@ -30244,7 +30392,7 @@ const Angle = ({
30244
30392
  range
30245
30393
  }) => {
30246
30394
  // Check if the points are clockwise or not, depending on whether we allow reflex angles
30247
- const areClockwise = clockwise$1([...coords, vertex]);
30395
+ const areClockwise = clockwise([...coords, vertex]);
30248
30396
  const shouldReverseCoords = areClockwise && !allowReflexAngles;
30249
30397
 
30250
30398
  // Reverse the coordinates accordingly to ensure the angle is calculated correctly
@@ -31177,9 +31325,6 @@ function getAngleSideConstraint(sidePoint, vertex, snapDegrees) {
31177
31325
  };
31178
31326
  }
31179
31327
 
31180
- const {
31181
- clockwise
31182
- } = geometry;
31183
31328
  function getGradableGraph(state, initialGraph) {
31184
31329
  if (!state.hasBeenInteractedWith) {
31185
31330
  return _extends({}, initialGraph);
@@ -31243,16 +31388,8 @@ function getGradableGraph(state, initialGraph) {
31243
31388
  });
31244
31389
  }
31245
31390
  if (state.type === "angle" && initialGraph.type === "angle") {
31246
- // We're going to reverse the coords for scoring if the angle is clockwise and
31247
- // we don't allow reflex angles. This is because the angle is largely defined
31248
- // by the order of the points in the coords array, and we want to maintain
31249
- // the same angle scoring with the legacy graph (which had a bug).
31250
- // (LEMS-2190): When we remove the legacy graph, move this logic to the scoring function.
31251
- const areClockwise = clockwise([state.coords[0], state.coords[2], state.coords[1]]);
31252
- const shouldReverseCoords = areClockwise && !state.allowReflexAngles;
31253
- const coords = shouldReverseCoords ? state.coords.slice().reverse() : state.coords;
31254
31391
  return _extends({}, initialGraph, {
31255
- coords,
31392
+ coords: state.coords,
31256
31393
  allowReflexAngles: state.allowReflexAngles
31257
31394
  });
31258
31395
  }
@@ -31891,17 +32028,20 @@ function describePointGraph(state, i18n) {
31891
32028
  y: srFormatNumber(y, locale)
31892
32029
  }));
31893
32030
  return strings.srInteractiveElements({
31894
- elements: pointDescriptions.join(", ")
32031
+ elements: pointDescriptions.join(" ")
31895
32032
  });
31896
32033
  }
31897
32034
 
31898
- function renderPolygonGraph(state, dispatch) {
32035
+ const {
32036
+ convertRadiansToDegrees
32037
+ } = angles;
32038
+ function renderPolygonGraph(state, dispatch, i18n, markings) {
31899
32039
  return {
31900
32040
  graph: /*#__PURE__*/React.createElement(PolygonGraph, {
31901
32041
  graphState: state,
31902
32042
  dispatch: dispatch
31903
32043
  }),
31904
- interactiveElementsDescription: null
32044
+ interactiveElementsDescription: getPolygonGraphDescription(state, i18n, markings)
31905
32045
  };
31906
32046
  }
31907
32047
  const PolygonGraph = props => {
@@ -31986,7 +32126,7 @@ const PolygonGraph = props => {
31986
32126
  focusVisible,
31987
32127
  setFocusVisible
31988
32128
  });
31989
- return numSides === "unlimited" ? UnlimitedPolygonGraph(statefulProps) : LimitedPolygonGraph(statefulProps);
32129
+ return numSides === "unlimited" ? /*#__PURE__*/React.createElement(UnlimitedPolygonGraph, statefulProps) : /*#__PURE__*/React.createElement(LimitedPolygonGraph, statefulProps);
31990
32130
  };
31991
32131
  const LimitedPolygonGraph = statefulProps => {
31992
32132
  const {
@@ -32011,8 +32151,39 @@ const LimitedPolygonGraph = statefulProps => {
32011
32151
  const {
32012
32152
  disableKeyboardInteraction
32013
32153
  } = graphConfig;
32154
+ const {
32155
+ strings,
32156
+ locale
32157
+ } = usePerseusI18n();
32158
+ const id = React.useId();
32159
+ const pointsOffArray = Array(points.length).fill("off");
32160
+ // When moving an element, set its aria-live to "polite" and the others
32161
+ // to "off". Otherwise, other connected elements that move at the same
32162
+ // time might override the currently focused element's aria live.
32163
+ const [ariaLives, setAriaLives] = React.useState([
32164
+ // First one represents the aria-live value for the polygon itself.
32165
+ "off",
32166
+ // The rest represent the points.
32167
+ ...pointsOffArray]);
32014
32168
  const lines = getLines(points);
32015
- return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Polygon, {
32169
+ const polygonPointsNumId = id + "-points-num";
32170
+ const polygonPointsId = id + "-points";
32171
+
32172
+ // Aria label srings
32173
+ const {
32174
+ srPolygonGraph,
32175
+ srPolygonGraphPointsNum,
32176
+ srPolygonGraphPoints,
32177
+ srPolygonElementsNum
32178
+ } = describePolygonGraph(statefulProps.graphState, {
32179
+ strings,
32180
+ locale
32181
+ }, statefulProps.graphConfig.markings);
32182
+ return /*#__PURE__*/React.createElement("g", {
32183
+ // Outer graph minimal description
32184
+ "aria-label": srPolygonGraph,
32185
+ "aria-describedby": `${polygonPointsNumId} ${polygonPointsId}`
32186
+ }, /*#__PURE__*/React.createElement(Polygon, {
32016
32187
  points: [...points],
32017
32188
  color: "var(--movable-line-stroke-color)",
32018
32189
  svgPolygonProps: {
@@ -32037,13 +32208,17 @@ const LimitedPolygonGraph = statefulProps => {
32037
32208
  snapTo: snapTo
32038
32209
  });
32039
32210
  }), showSides && lines.map(([start, end], i) => {
32211
+ // Use x and y to find the position of the label
32040
32212
  const [x, y] = vec.midpoint(start, end);
32041
- const length = parseFloat(vec.dist(start, end).toFixed(snapTo === "sides" ? 0 : 1));
32213
+ const length = vec.dist(start, end);
32214
+ // Check if the length needs to indicate
32215
+ // that it's an approximation.
32216
+ const isApprox = !Number.isInteger(length);
32042
32217
  return /*#__PURE__*/React.createElement(TextLabel, {
32043
32218
  key: "side-" + i,
32044
32219
  x: x,
32045
32220
  y: y
32046
- }, !Number.isInteger(length) && "≈ ", length);
32221
+ }, isApprox ? `≈ ${length.toFixed(snapTo === "sides" ? 0 : 1)}` : length);
32047
32222
  }), /*#__PURE__*/React.createElement(Polygon, {
32048
32223
  points: [...points],
32049
32224
  color: "transparent",
@@ -32063,27 +32238,80 @@ const LimitedPolygonGraph = statefulProps => {
32063
32238
  setFocusVisible(hasFocusVisible(polygonRef.current));
32064
32239
  },
32065
32240
  // Required for lines to darken on focus
32066
- onFocus: () => setFocusVisible(hasFocusVisible(polygonRef.current)),
32241
+ onFocus: () => {
32242
+ setFocusVisible(hasFocusVisible(polygonRef.current));
32243
+ setAriaLives(() => ["polite", ...pointsOffArray]);
32244
+ },
32067
32245
  // Required for line weighting to update on blur. Without this,
32068
32246
  // the user has to hover over the shape for it to update
32069
32247
  onBlur: () => setFocusVisible(hasFocusVisible(polygonRef.current)),
32070
- className: "movable-polygon"
32071
- }
32072
- }), points.map((point, i) => /*#__PURE__*/React.createElement(MovablePoint$1, {
32073
- key: "point-" + i,
32074
- constrain: constrain,
32075
- point: point,
32076
- sequenceNumber: i + 1,
32077
- onMove: destination => {
32078
- const now = Date.now();
32079
- const targetFPS = 40;
32080
- const moveThresholdTime = 1000 / targetFPS;
32081
- if (now - lastMoveTimeRef.current > moveThresholdTime) {
32082
- dispatch(actions.polygon.movePoint(i, destination));
32083
- lastMoveTimeRef.current = now;
32084
- }
32248
+ className: "movable-polygon",
32249
+ // Accessibility-related fields
32250
+ role: "button",
32251
+ "aria-label": srPolygonGraphPoints ? `${srPolygonElementsNum} ${srPolygonGraphPoints}` : srPolygonElementsNum,
32252
+ "aria-live": ariaLives[0]
32085
32253
  }
32086
- })));
32254
+ }), points.map((point, i) => {
32255
+ const angleId = `${id}-angle-${i}`;
32256
+ const side1Id = `${id}-point-${i}-side-1`;
32257
+ const side2Id = `${id}-point-${i}-side-2`;
32258
+
32259
+ // Limited polygons always have an angle at each vertex.
32260
+ const angle = getAngleFromPoints(points, i);
32261
+ const angleDegree = angle ? convertRadiansToDegrees(angle) : null;
32262
+
32263
+ // Limited polygons always two sides attached to each vertex.
32264
+ const sidesArray = getSideLengthsFromPoints(points, i);
32265
+ const {
32266
+ pointIndex: point1Index,
32267
+ sideLength: side1Length
32268
+ } = sidesArray[0];
32269
+ const {
32270
+ pointIndex: point2Index,
32271
+ sideLength: side2Length
32272
+ } = sidesArray[1];
32273
+ return /*#__PURE__*/React.createElement("g", {
32274
+ key: "point-" + i
32275
+ }, /*#__PURE__*/React.createElement(MovablePoint$1, {
32276
+ ariaDescribedBy: `${angleId} ${side1Id} ${side2Id}`,
32277
+ ariaLive: ariaLives[i + 1],
32278
+ constrain: constrain,
32279
+ point: point,
32280
+ sequenceNumber: i + 1,
32281
+ onMove: destination => {
32282
+ const now = Date.now();
32283
+ const targetFPS = 40;
32284
+ const moveThresholdTime = 1000 / targetFPS;
32285
+ if (now - lastMoveTimeRef.current > moveThresholdTime) {
32286
+ dispatch(actions.polygon.movePoint(i, destination));
32287
+ lastMoveTimeRef.current = now;
32288
+ }
32289
+ },
32290
+ onFocus: () => {
32291
+ const newPointAriaLives = [...pointsOffArray];
32292
+ newPointAriaLives[i] = "polite";
32293
+ // Whole polygon is "off", and the current
32294
+ // point is "polite".
32295
+ setAriaLives(["off", ...newPointAriaLives]);
32296
+ }
32297
+ }), angleDegree && /*#__PURE__*/React.createElement("g", {
32298
+ id: angleId
32299
+ }, Number.isInteger(angleDegree) ? strings.srPolygonPointAngle({
32300
+ angle: angleDegree
32301
+ }) : strings.srPolygonPointAngleApprox({
32302
+ angle: srFormatNumber(angleDegree, locale, 1)
32303
+ })), /*#__PURE__*/React.createElement("g", {
32304
+ id: side1Id
32305
+ }, getPolygonSideString(side1Length, point1Index, strings, locale)), /*#__PURE__*/React.createElement("g", {
32306
+ id: side2Id
32307
+ }, getPolygonSideString(side2Length, point2Index, strings, locale)));
32308
+ }), /*#__PURE__*/React.createElement("g", {
32309
+ id: polygonPointsNumId,
32310
+ style: a11y.srOnly
32311
+ }, srPolygonGraphPointsNum), srPolygonGraphPoints && /*#__PURE__*/React.createElement("g", {
32312
+ id: polygonPointsId,
32313
+ style: a11y.srOnly
32314
+ }, srPolygonGraphPoints));
32087
32315
  };
32088
32316
  const UnlimitedPolygonGraph = statefulProps => {
32089
32317
  const {
@@ -32098,6 +32326,19 @@ const UnlimitedPolygonGraph = statefulProps => {
32098
32326
  coords,
32099
32327
  closedPolygon
32100
32328
  } = statefulProps.graphState;
32329
+ const {
32330
+ strings,
32331
+ locale
32332
+ } = usePerseusI18n();
32333
+ const id = React.useId();
32334
+ const polygonPointsNumId = id + "-points-num";
32335
+ const polygonPointsId = id + "-points";
32336
+
32337
+ // When moving an element, set its aria-live to "polite" and the others
32338
+ // to "off". Otherwise, other connected elements that move at the same
32339
+ // time might override the currently focused element's aria live.
32340
+ const pointsOffArray = Array(points.length).fill("off");
32341
+ const [ariaLives, setAriaLives] = React.useState(pointsOffArray);
32101
32342
 
32102
32343
  // If the polygon is closed, return a LimitedPolygon component.
32103
32344
  if (closedPolygon) {
@@ -32111,7 +32352,22 @@ const UnlimitedPolygonGraph = statefulProps => {
32111
32352
  } = graphConfig;
32112
32353
  const widthPx = graphDimensionsInPixels[0];
32113
32354
  const heightPx = graphDimensionsInPixels[1];
32114
- return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Polyline, {
32355
+
32356
+ // Aria label srings
32357
+ const emptyGraph = coords.length === 0;
32358
+ const {
32359
+ srPolygonGraph,
32360
+ srPolygonGraphPointsNum,
32361
+ srPolygonGraphPoints
32362
+ } = describePolygonGraph(statefulProps.graphState, {
32363
+ strings,
32364
+ locale
32365
+ }, statefulProps.graphConfig.markings);
32366
+ return /*#__PURE__*/React.createElement("g", {
32367
+ // Outer graph minimal description
32368
+ "aria-label": emptyGraph ? strings.srUnlimitedPolygonEmpty : srPolygonGraph,
32369
+ "aria-describedby": `${polygonPointsNumId} ${polygonPointsId}`
32370
+ }, /*#__PURE__*/React.createElement(Polyline, {
32115
32371
  points: [...points],
32116
32372
  color: "var(--movable-line-stroke-color)",
32117
32373
  svgPolylineProps: {
@@ -32136,28 +32392,68 @@ const UnlimitedPolygonGraph = statefulProps => {
32136
32392
  const graphCoordinates = pixelsToVectors([[x, y]], graphConfig);
32137
32393
  dispatch(actions.polygon.addPoint(graphCoordinates[0]));
32138
32394
  }
32139
- }), coords.map((point, i) => /*#__PURE__*/React.createElement(MovablePoint$1, {
32140
- key: i,
32141
- point: point,
32142
- sequenceNumber: i + 1,
32143
- onMove: destination => dispatch(actions.polygon.movePoint(i, destination)),
32144
- ref: ref => {
32145
- pointsRef.current[i] = ref;
32146
- },
32147
- onFocus: () => {
32148
- dispatch(actions.polygon.focusPoint(i));
32149
- },
32150
- onClick: () => {
32151
- // If the point being clicked is the first point and
32152
- // there's enough non-duplicated points to form
32153
- // a polygon (3 or more), close the shape before
32154
- // setting focus.
32155
- if (i === 0 && getArrayWithoutDuplicates(coords).length >= 3) {
32156
- dispatch(actions.polygon.closePolygon());
32157
- }
32158
- dispatch(actions.polygon.clickPoint(i));
32395
+ }), coords.map((point, i) => {
32396
+ const angleId = `${id}-angle-${i}`;
32397
+ let sideIds = "";
32398
+
32399
+ // This point only has an angle if it's not the first
32400
+ // or last point in this open polygon.
32401
+ const hasAngle = i > 0 && i < coords.length - 1;
32402
+ const angle = hasAngle ? getAngleFromPoints(points, i) : null;
32403
+ const angleDegree = angle ? convertRadiansToDegrees(angle) : null;
32404
+ const sidesArray = getSideLengthsFromPoints(points, i, true);
32405
+ for (let sideIndex = 0; sideIndex < sidesArray.length; sideIndex++) {
32406
+ sideIds += `${id}-point-${i}-side-${sideIndex} `;
32159
32407
  }
32160
- })));
32408
+ return /*#__PURE__*/React.createElement("g", {
32409
+ key: "point-" + i
32410
+ }, /*#__PURE__*/React.createElement(MovablePoint$1, {
32411
+ ariaDescribedBy: `${angleId} ${sideIds}`,
32412
+ ariaLive: ariaLives[i],
32413
+ point: point,
32414
+ sequenceNumber: i + 1,
32415
+ onMove: destination => dispatch(actions.polygon.movePoint(i, destination)),
32416
+ ref: ref => {
32417
+ pointsRef.current[i] = ref;
32418
+ },
32419
+ onFocus: () => {
32420
+ dispatch(actions.polygon.focusPoint(i));
32421
+ const newPointAriaLives = [...pointsOffArray];
32422
+ newPointAriaLives[i] = "polite";
32423
+ // Whole polygon is "off", and the current
32424
+ // point is "polite".
32425
+ setAriaLives([...newPointAriaLives]);
32426
+ },
32427
+ onClick: () => {
32428
+ // If the point being clicked is the first point and
32429
+ // there's enough non-duplicated points to form
32430
+ // a polygon (3 or more), close the shape before
32431
+ // setting focus.
32432
+ if (i === 0 && getArrayWithoutDuplicates(coords).length >= 3) {
32433
+ dispatch(actions.polygon.closePolygon());
32434
+ }
32435
+ dispatch(actions.polygon.clickPoint(i));
32436
+ }
32437
+ }), angleDegree && /*#__PURE__*/React.createElement("g", {
32438
+ id: angleId
32439
+ }, Number.isInteger(angleDegree) ? strings.srPolygonPointAngle({
32440
+ angle: angleDegree
32441
+ }) : strings.srPolygonPointAngleApprox({
32442
+ angle: srFormatNumber(angleDegree, locale, 1)
32443
+ })), sidesArray.map(({
32444
+ pointIndex,
32445
+ sideLength
32446
+ }, j) => /*#__PURE__*/React.createElement("g", {
32447
+ key: `${id}-point-${i}-side-${j}`,
32448
+ id: `${id}-point-${i}-side-${j}`
32449
+ }, getPolygonSideString(sideLength, pointIndex, strings, locale))));
32450
+ }), coords.length > 0 && /*#__PURE__*/React.createElement("g", {
32451
+ id: polygonPointsNumId,
32452
+ style: a11y.srOnly
32453
+ }, srPolygonGraphPointsNum), srPolygonGraphPoints && /*#__PURE__*/React.createElement("g", {
32454
+ id: polygonPointsId,
32455
+ style: a11y.srOnly
32456
+ }, srPolygonGraphPoints));
32161
32457
  };
32162
32458
  function getLines(points) {
32163
32459
  return points.map((point, i) => {
@@ -32179,6 +32475,54 @@ const hasFocusVisible = element => {
32179
32475
  return matches(":focus");
32180
32476
  }
32181
32477
  };
32478
+ function getPolygonGraphDescription(state, i18n, markings) {
32479
+ const strings = describePolygonGraph(state, i18n, markings);
32480
+ return strings.srPolygonInteractiveElements;
32481
+ }
32482
+ function describePolygonGraph(state, i18n, markings) {
32483
+ const {
32484
+ strings,
32485
+ locale
32486
+ } = i18n;
32487
+ const {
32488
+ coords
32489
+ } = state;
32490
+ const isCoordinatePlane = markings === "axes" || markings === "graph";
32491
+ const hasOnePoint = coords.length === 1;
32492
+
32493
+ // Figure out graph aria label based on markings.
32494
+ const srPolygonGraph = isCoordinatePlane ? strings.srPolygonGraphCoordinatePlane : strings.srPolygonGraph;
32495
+ const srPolygonGraphPointsNum = hasOnePoint ? strings.srPolygonGraphPointsOne : strings.srPolygonGraphPointsNum({
32496
+ num: coords.length
32497
+ });
32498
+ let srPolygonGraphPoints;
32499
+ // Figure out graph description based on markings.
32500
+ // If the graph is not on a coordinate plane, we should not include
32501
+ // the points' coordinates in the description.
32502
+ if (isCoordinatePlane) {
32503
+ const pointsString = coords.map((coord, i) => {
32504
+ return strings.srPointAtCoordinates({
32505
+ num: i + 1,
32506
+ x: srFormatNumber(coord[0], locale),
32507
+ y: srFormatNumber(coord[1], locale)
32508
+ });
32509
+ });
32510
+ srPolygonGraphPoints = pointsString.join(" ");
32511
+ }
32512
+ const srPolygonElementsNum = hasOnePoint ? strings.srPolygonElementsOne : strings.srPolygonElementsNum({
32513
+ num: coords.length
32514
+ });
32515
+ const srPolygonInteractiveElements = coords.length > 0 ? strings.srInteractiveElements({
32516
+ elements: [srPolygonElementsNum, srPolygonGraphPoints].join(" ")
32517
+ }) : null;
32518
+ return {
32519
+ srPolygonGraph,
32520
+ srPolygonGraphPointsNum,
32521
+ srPolygonGraphPoints,
32522
+ srPolygonElementsNum,
32523
+ srPolygonInteractiveElements
32524
+ };
32525
+ }
32182
32526
 
32183
32527
  function renderQuadraticGraph(state, dispatch, i18n) {
32184
32528
  return {
@@ -32878,7 +33222,8 @@ const MafsGraph = props => {
32878
33222
  } = renderGraphElements({
32879
33223
  state,
32880
33224
  dispatch,
32881
- i18n
33225
+ i18n,
33226
+ markings: props.markings
32882
33227
  });
32883
33228
  return /*#__PURE__*/React.createElement(GraphConfigContext.Provider, {
32884
33229
  value: {
@@ -32913,7 +33258,7 @@ const MafsGraph = props => {
32913
33258
  handleKeyboardEvent(event, state, dispatch);
32914
33259
  },
32915
33260
  "aria-label": fullGraphAriaLabel,
32916
- "aria-describedby": describedByIds(fullGraphAriaDescription && descriptionId, interactiveElementsDescription && interactiveElementsDescriptionId, isUnlimitedGraphState(state) && "unlimited-graph-keyboard-prompt"),
33261
+ "aria-describedby": describedByIds(fullGraphAriaDescription && descriptionId, interactiveElementsDescription && interactiveElementsDescriptionId, isUnlimitedGraphState(state) && unlimitedGraphKeyboardPromptId),
32917
33262
  ref: graphRef,
32918
33263
  tabIndex: 0,
32919
33264
  onFocus: event => {
@@ -32939,7 +33284,19 @@ const MafsGraph = props => {
32939
33284
  bottom: 0,
32940
33285
  left: 0
32941
33286
  }
32942
- }, (props.markings === "graph" || props.markings === "axes") && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(AxisLabels, null)), /*#__PURE__*/React.createElement(Mafs, {
33287
+ }, (props.markings === "graph" || props.markings === "axes") && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(AxisLabels, null)), /*#__PURE__*/React.createElement(View
33288
+ // If we have locked figures, we set aria-hidden
33289
+ // to false so that screen readers can read the
33290
+ // locked figures. If we don't have any locked
33291
+ // figures, we need to set aria-hidden to true so
33292
+ // that screen readers don't read this Mafs element
33293
+ // as an empty image.
33294
+ // Note: Adding this in a wrapping View element
33295
+ // so that `aria-hidden` is a valid property.
33296
+ // It does not work on `Mafs` or svg elements.
33297
+ , {
33298
+ "aria-hidden": props.lockedFigures && props.lockedFigures.length > 0 ? false : true
33299
+ }, /*#__PURE__*/React.createElement(Mafs, {
32943
33300
  preserveAspectRatio: false,
32944
33301
  viewBox: {
32945
33302
  x: state.range[X],
@@ -32959,10 +33316,10 @@ const MafsGraph = props => {
32959
33316
  height: height
32960
33317
  })),
32961
33318
  // Only render the axis ticks and arrows if the markings are set to a full "graph"
32962
- (props.markings === "graph" || props.markings === "axes") && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(AxisTicks, null), /*#__PURE__*/React.createElement(AxisArrows, null)), /*#__PURE__*/React.createElement("svg", nestedSVGAttributes, props.lockedFigures && /*#__PURE__*/React.createElement(GraphLockedLayer, {
33319
+ (props.markings === "graph" || props.markings === "axes") && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(AxisTicks, null), /*#__PURE__*/React.createElement(AxisArrows, null)), props.lockedFigures && props.lockedFigures.length > 0 && /*#__PURE__*/React.createElement("svg", nestedSVGAttributes, /*#__PURE__*/React.createElement(GraphLockedLayer, {
32963
33320
  lockedFigures: props.lockedFigures,
32964
33321
  range: state.range
32965
- }))), props.lockedFigures && /*#__PURE__*/React.createElement(GraphLockedLabelsLayer, {
33322
+ })))), props.lockedFigures && /*#__PURE__*/React.createElement(GraphLockedLabelsLayer, {
32966
33323
  lockedFigures: props.lockedFigures
32967
33324
  }), /*#__PURE__*/React.createElement(View, {
32968
33325
  style: {
@@ -33257,7 +33614,8 @@ const renderGraphElements = props => {
33257
33614
  const {
33258
33615
  state,
33259
33616
  dispatch,
33260
- i18n
33617
+ i18n,
33618
+ markings
33261
33619
  } = props;
33262
33620
  const {
33263
33621
  type
@@ -33274,7 +33632,7 @@ const renderGraphElements = props => {
33274
33632
  case "ray":
33275
33633
  return renderRayGraph(state, dispatch);
33276
33634
  case "polygon":
33277
- return renderPolygonGraph(state, dispatch);
33635
+ return renderPolygonGraph(state, dispatch, i18n, markings);
33278
33636
  case "point":
33279
33637
  return renderPointGraph(state, dispatch);
33280
33638
  case "circle":
@@ -35911,26 +36269,17 @@ class InteractiveGraph extends React.Component {
35911
36269
  }
35912
36270
  render() {
35913
36271
  var _this$props$apiOption;
35914
- // Mafs shim
35915
- const mafsFlags = (_this$props$apiOption = this.props.apiOptions) == null || (_this$props$apiOption = _this$props$apiOption.flags) == null ? void 0 : _this$props$apiOption["mafs"];
35916
- if (shouldUseMafs(mafsFlags, this.props.graph)) {
35917
- var _this$props$apiOption2, _this$props$apiOption3;
35918
- const box = getInteractiveBoxFromSizeClass(this.props.containerSizeClass);
35919
- const gridStep = this.props.gridStep || Util.getGridStep(this.props.range, this.props.step, box[0]);
35920
- const snapStep = this.props.snapStep || Util.snapStepFromGridStep(gridStep);
35921
- return /*#__PURE__*/React.createElement(StatefulMafsGraph, _extends({}, this.props, {
35922
- flags: (_this$props$apiOption2 = this.props.apiOptions) == null ? void 0 : _this$props$apiOption2.flags,
35923
- ref: this.mafsRef,
35924
- gridStep: gridStep,
35925
- snapStep: snapStep,
35926
- box: box,
35927
- showTooltips: !!this.props.showTooltips,
35928
- readOnly: (_this$props$apiOption3 = this.props.apiOptions) == null ? void 0 : _this$props$apiOption3.readOnly
35929
- }));
35930
- }
35931
- return /*#__PURE__*/React.createElement(LegacyInteractiveGraph, _extends({
35932
- ref: this.legacyGraphRef
35933
- }, this.props));
36272
+ const box = getInteractiveBoxFromSizeClass(this.props.containerSizeClass);
36273
+ const gridStep = this.props.gridStep || Util.getGridStep(this.props.range, this.props.step, box[0]);
36274
+ const snapStep = this.props.snapStep || Util.snapStepFromGridStep(gridStep);
36275
+ return /*#__PURE__*/React.createElement(StatefulMafsGraph, _extends({}, this.props, {
36276
+ ref: this.mafsRef,
36277
+ gridStep: gridStep,
36278
+ snapStep: snapStep,
36279
+ box: box,
36280
+ showTooltips: !!this.props.showTooltips,
36281
+ readOnly: (_this$props$apiOption = this.props.apiOptions) == null ? void 0 : _this$props$apiOption.readOnly
36282
+ }));
35934
36283
  }
35935
36284
 
35936
36285
  /**
@@ -36246,7 +36595,7 @@ class InteractiveGraph extends React.Component {
36246
36595
  }
36247
36596
  }
36248
36597
 
36249
- // exported for testing
36598
+ // We don't need to change any of the original props for static mode
36250
36599
  InteractiveGraph.defaultProps = {
36251
36600
  labels: ["x", "y"],
36252
36601
  range: [[-10, 10], [-10, 10]],
@@ -36259,34 +36608,13 @@ InteractiveGraph.defaultProps = {
36259
36608
  type: "linear"
36260
36609
  }
36261
36610
  };
36262
- function shouldUseMafs(mafsFlags, graph) {
36263
- if (typeof mafsFlags === "boolean" || typeof mafsFlags === "undefined") {
36264
- return false;
36265
- }
36266
- switch (graph.type) {
36267
- case "none":
36268
- return true;
36269
- case "point":
36270
- if (graph.numPoints === UNLIMITED) {
36271
- return Boolean(mafsFlags["unlimited-point"]);
36272
- }
36273
- return Boolean(mafsFlags["point"]);
36274
- case "polygon":
36275
- if (graph.numSides === UNLIMITED) {
36276
- return Boolean(mafsFlags["unlimited-polygon"]);
36277
- }
36278
- return Boolean(mafsFlags["polygon"]);
36279
- default:
36280
- return Boolean(mafsFlags[graph.type]);
36281
- }
36282
- }
36283
- // We don't need to change any of the original props for static mode
36284
36611
  const staticTransform$3 = _.identity;
36285
36612
  var InteractiveGraph$1 = {
36286
36613
  name: "interactive-graph",
36287
36614
  displayName: "Interactive graph (Assessments only)",
36288
36615
  widget: InteractiveGraph,
36289
- staticTransform: staticTransform$3
36616
+ staticTransform: staticTransform$3,
36617
+ getPublicWidgetOptions: getInteractiveGraphPublicWidgetOptions
36290
36618
  };
36291
36619
 
36292
36620
  // TODO: can we use WB typography?
@@ -38566,7 +38894,8 @@ var Matrix$1 = {
38566
38894
  widget: Matrix,
38567
38895
  transform: propTransform$1,
38568
38896
  staticTransform: staticTransform$2,
38569
- isLintable: true
38897
+ isLintable: true,
38898
+ getPublicWidgetOptions: getMatrixPublicWidgetOptions
38570
38899
  };
38571
38900
 
38572
38901
  const getPromptJSON$7 = () => {
@@ -41761,7 +42090,8 @@ var Plotter$1 = {
41761
42090
  displayName: "Plotter",
41762
42091
  hidden: true,
41763
42092
  widget: Plotter,
41764
- staticTransform: staticTransform
42093
+ staticTransform: staticTransform,
42094
+ getPublicWidgetOptions: getPlotterPublicWidgetOptions
41765
42095
  };
41766
42096
 
41767
42097
  const getPromptJSON$2 = () => {
@@ -42143,7 +42473,8 @@ var Table$1 = {
42143
42473
  widget: Table,
42144
42474
  transform: propTransform,
42145
42475
  hidden: true,
42146
- isLintable: true
42476
+ isLintable: true,
42477
+ getPublicWidgetOptions: getTablePublicWidgetOptions
42147
42478
  };
42148
42479
 
42149
42480
  const getPromptJSON = () => {
@@ -42322,7 +42653,7 @@ const init = function init() {
42322
42653
 
42323
42654
  // This file is processed by a Rollup plugin (replace) to inject the production
42324
42655
  const libName = "@khanacademy/perseus";
42325
- const libVersion = "53.0.0";
42656
+ const libVersion = "53.1.0";
42326
42657
  addLibraryVersionToPerseusDebug(libName, libVersion);
42327
42658
 
42328
42659
  const apiVersion = {