@khanacademy/perseus 21.0.0 → 21.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
@@ -7,7 +7,7 @@ import * as i18n from '@khanacademy/wonder-blocks-i18n';
7
7
  import Tooltip$1 from '@khanacademy/wonder-blocks-tooltip';
8
8
  import classNames from 'classnames';
9
9
  import * as React from 'react';
10
- import React__default, { useState, useEffect, useRef } from 'react';
10
+ import React__default, { useState, useEffect, useRef, createContext } from 'react';
11
11
  import _, { debounce as debounce$1 } from 'underscore';
12
12
  import Clickable from '@khanacademy/wonder-blocks-clickable';
13
13
  import { Popover, PopoverContentCore, PopoverContent } from '@khanacademy/wonder-blocks-popover';
@@ -24901,9 +24901,22 @@ class Explanation extends React.Component {
24901
24901
  // Special styling is needed to fit the button in a block of text without throwing off the line spacing.
24902
24902
  // While the button is not normally included in a block of text, it needs to be able to accommodate such a case.
24903
24903
  const buttonStyleOverrides = {
24904
- height: "22px",
24904
+ height: "auto",
24905
24905
  marginLeft: "-2px",
24906
- padding: "0 2px"
24906
+ // transfer space on the left side to the padding
24907
+ marginRight: "2px",
24908
+ // adds space between the right border and any text that follows
24909
+ paddingLeft: "2px" // adds space between the left border and the button text
24910
+ };
24911
+
24912
+ const labelStyle = {
24913
+ fontSize: "18px",
24914
+ lineHeight: "20px",
24915
+ // The following property adjusts the large space between the button text and the caret icon.
24916
+ // Since we are unable to adjust the styling of the icon (where the extra space exists),
24917
+ // we are adjusting it on the text label by using a negative margin.
24918
+ marginRight: "-6px",
24919
+ "white-space": "normal"
24907
24920
  };
24908
24921
  const contentStyling = [styles$p.content, this.state.expanded ? styles$p.contentExpanded : styles$p.contentCollapsed, allowTransition && (this.state.expanded ? styles$p.transitionExpanded : styles$p.transitionCollapsed)];
24909
24922
  return /*#__PURE__*/React.createElement(UniqueIDProvider, {
@@ -24914,8 +24927,9 @@ class Explanation extends React.Component {
24914
24927
  "aria-controls": ids.get("content"),
24915
24928
  endIcon: caretIcon,
24916
24929
  kind: "tertiary",
24930
+ labelStyle: labelStyle,
24917
24931
  onClick: this._onClick,
24918
- size: "large",
24932
+ size: "small",
24919
24933
  style: buttonStyleOverrides
24920
24934
  }, promptText), /*#__PURE__*/React.createElement(View, {
24921
24935
  id: ids.get("content"),
@@ -28190,6 +28204,14 @@ const moveAll = delta => ({
28190
28204
  type: MOVE_ALL,
28191
28205
  delta
28192
28206
  });
28207
+ const MOVE_POINT = "move-point";
28208
+ function movePoint(index, destination) {
28209
+ return {
28210
+ type: MOVE_POINT,
28211
+ index,
28212
+ destination
28213
+ };
28214
+ }
28193
28215
 
28194
28216
  /**
28195
28217
  * 44 is touch best practice and AAA compliant for WCAG
@@ -28412,7 +28434,9 @@ const SegmentGraph = props => {
28412
28434
  onMoveLine: delta => {
28413
28435
  dispatch(moveLine(i, delta));
28414
28436
  },
28415
- onMovePoint: (endpointIndex, destination) => dispatch(moveControlPoint(endpointIndex, destination, i)),
28437
+ onMovePoint: (endpointIndex, destination) => {
28438
+ dispatch(moveControlPoint(endpointIndex, destination, i));
28439
+ },
28416
28440
  "data-testid": "segment" + i
28417
28441
  })));
28418
28442
  };
@@ -28768,11 +28792,73 @@ const PolygonGraph = props => {
28768
28792
  }), points.map((point, i) => /*#__PURE__*/React.createElement(StyledMovablePoint, {
28769
28793
  key: "point-" + i,
28770
28794
  point: point,
28771
- onMove: destination => dispatch(moveControlPoint(i, destination)),
28795
+ onMove: destination => dispatch(movePoint(i, destination)),
28772
28796
  "data-testid": type + i
28773
28797
  })));
28774
28798
  };
28775
28799
 
28800
+ function PointGraph(props) {
28801
+ const {
28802
+ dispatch
28803
+ } = props;
28804
+ return /*#__PURE__*/React.createElement(React.Fragment, null, props.graphState.coords.map((point, i) => /*#__PURE__*/React.createElement(StyledMovablePoint, {
28805
+ key: i,
28806
+ point: point,
28807
+ onMove: destination => dispatch(movePoint(i, destination))
28808
+ })));
28809
+ }
28810
+
28811
+ const GraphStateContext = /*#__PURE__*/createContext({
28812
+ state: {},
28813
+ dispatch: () => {}
28814
+ });
28815
+ function useGraphState() {
28816
+ return React__default.useContext(GraphStateContext);
28817
+ }
28818
+
28819
+ function Arrow(props) {
28820
+ const {
28821
+ userTransform,
28822
+ viewTransform
28823
+ } = useTransformContext();
28824
+ const point = [props.x, props.y];
28825
+ const userTransformedPoint = vec.transform(point, userTransform);
28826
+ const viewTransformedPoint = vec.transform(userTransformedPoint, viewTransform);
28827
+ return /*#__PURE__*/React__default.createElement("g", {
28828
+ transform: `translate(${viewTransformedPoint[0]} ${viewTransformedPoint[1]}) rotate(${props.rotate}) scale(1.25)`
28829
+ }, /*#__PURE__*/React__default.createElement("g", {
28830
+ transform: "translate(-1)"
28831
+ }, /*#__PURE__*/React__default.createElement("path", {
28832
+ d: "M-3 4 C-2.75 2.5 0 0.25 0.75 0C0 -0.25 -2.75 -2.5 -3 -4",
28833
+ fill: "none"
28834
+ })));
28835
+ }
28836
+ function AxisArrows() {
28837
+ const {
28838
+ state
28839
+ } = useGraphState();
28840
+ const range = state.range;
28841
+ const [xMin, xMax] = range[0];
28842
+ const [yMin, yMax] = range[1];
28843
+ return /*#__PURE__*/React__default.createElement(React__default.Fragment, null, /*#__PURE__*/React__default.createElement(Arrow, {
28844
+ x: xMax,
28845
+ y: 0,
28846
+ rotate: 0
28847
+ }), /*#__PURE__*/React__default.createElement(Arrow, {
28848
+ x: 0,
28849
+ y: yMin,
28850
+ rotate: 90
28851
+ }), /*#__PURE__*/React__default.createElement(Arrow, {
28852
+ x: xMin,
28853
+ y: 0,
28854
+ rotate: 180
28855
+ }), /*#__PURE__*/React__default.createElement(Arrow, {
28856
+ x: 0,
28857
+ y: yMax,
28858
+ rotate: 270
28859
+ }));
28860
+ }
28861
+
28776
28862
  const renderLineLabel = (n, [min, max]) => n !== -1 && n !== min && n !== max;
28777
28863
  const axisOptions = (props, index) => ({
28778
28864
  axis: props.markings === "graph",
@@ -28780,10 +28866,10 @@ const axisOptions = (props, index) => ({
28780
28866
  subdivisions: props.step[index] / props.gridStep[index],
28781
28867
  labels: n => renderLineLabel(n, props.range[index]) ? n : ""
28782
28868
  });
28783
- const Grid = props => props.markings === "none" ? null : /*#__PURE__*/React.createElement(Coordinates.Cartesian, {
28869
+ const Grid = props => props.markings === "none" ? null : /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Coordinates.Cartesian, {
28784
28870
  xAxis: axisOptions(props, 0),
28785
28871
  yAxis: axisOptions(props, 1)
28786
- });
28872
+ }), props.markings === "graph" && /*#__PURE__*/React.createElement(AxisArrows, null));
28787
28873
 
28788
28874
  // Displays a stylized open eye: lint warnings are visible
28789
28875
  const VisibleIcon = () => /*#__PURE__*/React.createElement("svg", {
@@ -29480,22 +29566,32 @@ const LegacyGrid = ({
29480
29566
  return null;
29481
29567
  };
29482
29568
 
29483
- /** Determine if coords is type CollinearTuple[] */
29484
- const isCollinearTuples = coords => Array.isArray(coords[0][0]);
29485
-
29486
- // Generic type makes returned state match input state
29487
29569
  function interactiveGraphReducer(state, action) {
29570
+ switch (action.type) {
29571
+ case MOVE_CONTROL_POINT:
29572
+ return doMoveControlPoint(state, action);
29573
+ case MOVE_LINE:
29574
+ return doMoveLine(state, action);
29575
+ case MOVE_ALL:
29576
+ return doMoveAll(state, action);
29577
+ case MOVE_POINT:
29578
+ return doMovePoint(state, action);
29579
+ default:
29580
+ throw new UnreachableCaseError(action);
29581
+ }
29582
+ }
29583
+ function doMoveControlPoint(state, action) {
29488
29584
  const {
29489
29585
  snapStep,
29490
29586
  range
29491
29587
  } = state;
29492
- if (!state.coords) {
29493
- throw new Error("Graph state must have been initialized with coords");
29494
- }
29495
- switch (action.type) {
29496
- case MOVE_CONTROL_POINT:
29588
+ switch (state.type) {
29589
+ case "segment":
29590
+ case "linear":
29591
+ case "linear-system":
29592
+ case "ray":
29497
29593
  {
29498
- const newCoords = action.itemIndex !== undefined && state.coords && isCollinearTuples(state.coords) ? updateAtIndex({
29594
+ const newCoords = updateAtIndex({
29499
29595
  array: state.coords,
29500
29596
  index: action.itemIndex,
29501
29597
  update: tuple => setAtIndex({
@@ -29510,23 +29606,9 @@ function interactiveGraphReducer(state, action) {
29510
29606
  })
29511
29607
  })
29512
29608
  })
29513
- }) : setAtIndex({
29514
- array: state.coords,
29515
- index: action.pointIndex,
29516
- newValue: snap({
29517
- snapStep,
29518
- point: bound$1({
29519
- snapStep,
29520
- range,
29521
- point: action.destination
29522
- })
29523
- })
29524
29609
  });
29525
-
29526
- // Cannot type narrow both conditions within function parameters,
29527
- // so this may seem redundant, but it's necessary for type safety.
29528
- const coordsToCheck = isCollinearTuples(newCoords) && action.itemIndex !== undefined ? newCoords[action.itemIndex] : newCoords;
29529
- if (!isCollinearTuples(coordsToCheck) && coordsOverlap(coordsToCheck)) {
29610
+ const coordsToCheck = newCoords[action.itemIndex];
29611
+ if (coordsOverlap(coordsToCheck)) {
29530
29612
  return state;
29531
29613
  }
29532
29614
  return _extends({}, state, {
@@ -29534,16 +29616,30 @@ function interactiveGraphReducer(state, action) {
29534
29616
  coords: newCoords
29535
29617
  });
29536
29618
  }
29537
- case MOVE_LINE:
29619
+ case "circle":
29620
+ throw new Error("FIXME implement circle reducer");
29621
+ case "point":
29622
+ case "polygon":
29623
+ throw new Error(`Don't use moveControlPoint for ${state.type} graphs. Use movePoint instead!`);
29624
+ default:
29625
+ throw new UnreachableCaseError(state);
29626
+ }
29627
+ }
29628
+ function doMoveLine(state, action) {
29629
+ const {
29630
+ snapStep,
29631
+ range
29632
+ } = state;
29633
+ switch (state.type) {
29634
+ case "segment":
29635
+ case "linear":
29636
+ case "linear-system":
29637
+ case "ray":
29538
29638
  {
29539
- var _state$coords;
29540
- if (!isCollinearTuples(state.coords)) {
29541
- throw new Error("Cannot call this action unless coords is array of CollinearTuple.");
29542
- }
29543
29639
  if (action.itemIndex === undefined) {
29544
29640
  throw new Error("Please provide index of line to move");
29545
29641
  }
29546
- const currentLine = (_state$coords = state.coords) == null ? void 0 : _state$coords[action.itemIndex];
29642
+ const currentLine = state.coords[action.itemIndex];
29547
29643
  if (!currentLine) {
29548
29644
  throw new Error("No line to move");
29549
29645
  }
@@ -29564,15 +29660,25 @@ function interactiveGraphReducer(state, action) {
29564
29660
  newValue: newLine
29565
29661
  });
29566
29662
  return _extends({}, state, {
29663
+ type: state.type,
29567
29664
  hasBeenInteractedWith: true,
29568
29665
  coords: newCoords
29569
29666
  });
29570
29667
  }
29571
- case MOVE_ALL:
29668
+ default:
29669
+ // The MoveLine action doesn't make sense for other graph types;
29670
+ // ignore it if it somehow happens
29671
+ return state;
29672
+ }
29673
+ }
29674
+ function doMoveAll(state, action) {
29675
+ const {
29676
+ snapStep,
29677
+ range
29678
+ } = state;
29679
+ switch (state.type) {
29680
+ case "polygon":
29572
29681
  {
29573
- if (isCollinearTuples(state.coords)) {
29574
- throw new Error("Cannot call this action unless coords is array of vectors.");
29575
- }
29576
29682
  const change = getChange(state.coords, action.delta, {
29577
29683
  snapStep,
29578
29684
  range
@@ -29587,7 +29693,33 @@ function interactiveGraphReducer(state, action) {
29587
29693
  });
29588
29694
  }
29589
29695
  default:
29590
- throw new UnreachableCaseError(action);
29696
+ // MoveAll is not supported for other state types; just ignore it.
29697
+ return state;
29698
+ }
29699
+ }
29700
+ function doMovePoint(state, action) {
29701
+ switch (state.type) {
29702
+ case "point":
29703
+ case "polygon":
29704
+ {
29705
+ return _extends({}, state, {
29706
+ hasBeenInteractedWith: true,
29707
+ coords: setAtIndex({
29708
+ array: state.coords,
29709
+ index: action.index,
29710
+ newValue: snap({
29711
+ snapStep: state.snapStep,
29712
+ point: bound$1({
29713
+ snapStep: state.snapStep,
29714
+ range: state.range,
29715
+ point: action.destination
29716
+ })
29717
+ })
29718
+ })
29719
+ });
29720
+ }
29721
+ default:
29722
+ throw new Error("The movePoint action is only for point and polygon graphs");
29591
29723
  }
29592
29724
  }
29593
29725
  const getChange = (coords, delta, constraintOpts) => {
@@ -29668,7 +29800,7 @@ function clamp(value, min, max) {
29668
29800
  const coordsOverlap = coords => coords.some((coord, i) => coords.some((c, j) => i !== j && vector$1.equal(coord, c)));
29669
29801
  function updateAtIndex(args) {
29670
29802
  const {
29671
- array = [],
29803
+ array,
29672
29804
  index,
29673
29805
  update
29674
29806
  } = args;
@@ -29681,13 +29813,12 @@ function updateAtIndex(args) {
29681
29813
  }
29682
29814
  function setAtIndex(args) {
29683
29815
  const {
29684
- array = [],
29816
+ array,
29685
29817
  index,
29686
29818
  newValue
29687
29819
  } = args;
29688
29820
  const copy = [...array];
29689
29821
  copy[index] = newValue;
29690
- // restoring readonly to array
29691
29822
  return copy;
29692
29823
  }
29693
29824
 
@@ -29728,19 +29859,32 @@ function initializeGraphState(params) {
29728
29859
  })
29729
29860
  };
29730
29861
  case "polygon":
29731
- return _extends({
29862
+ return {
29863
+ type: "polygon",
29732
29864
  hasBeenInteractedWith: false,
29733
29865
  range,
29734
- snapStep
29735
- }, graph, {
29866
+ snapStep,
29867
+ showAngles: Boolean(graph.showAngles),
29868
+ showSides: Boolean(graph.showSides),
29736
29869
  coords: getPolygonCoords({
29737
29870
  graph,
29738
29871
  range,
29739
29872
  step
29740
29873
  })
29741
- });
29742
- case "angle":
29874
+ };
29743
29875
  case "point":
29876
+ return {
29877
+ type: graph.type,
29878
+ hasBeenInteractedWith: false,
29879
+ range,
29880
+ snapStep,
29881
+ coords: getDefaultPoints({
29882
+ graph,
29883
+ step,
29884
+ range
29885
+ })
29886
+ };
29887
+ case "angle":
29744
29888
  case "circle":
29745
29889
  case "sinusoid":
29746
29890
  case "quadratic":
@@ -29749,6 +29893,46 @@ function initializeGraphState(params) {
29749
29893
  throw new UnreachableCaseError(graph);
29750
29894
  }
29751
29895
  }
29896
+ const getDefaultPoints = ({
29897
+ graph,
29898
+ range,
29899
+ step
29900
+ }) => {
29901
+ var _graph$coords;
29902
+ const numPoints = graph.numPoints || 1;
29903
+ let coords = (_graph$coords = graph.coords) == null ? void 0 : _graph$coords.slice();
29904
+ if (coords) {
29905
+ return coords;
29906
+ }
29907
+ switch (numPoints) {
29908
+ case 1:
29909
+ // Back in the day, one point's coords were in graph.coord
29910
+ coords = [graph.coord || [0, 0]];
29911
+ break;
29912
+ case 2:
29913
+ coords = [[-5, 0], [5, 0]];
29914
+ break;
29915
+ case 3:
29916
+ coords = [[-5, 0], [0, 0], [5, 0]];
29917
+ break;
29918
+ case 4:
29919
+ coords = [[-6, 0], [-2, 0], [2, 0], [6, 0]];
29920
+ break;
29921
+ case 5:
29922
+ coords = [[-6, 0], [-3, 0], [0, 0], [3, 0], [6, 0]];
29923
+ break;
29924
+ case 6:
29925
+ coords = [[-5, 0], [-3, 0], [-1, 0], [1, 0], [3, 0], [5, 0]];
29926
+ break;
29927
+ default:
29928
+ coords = [];
29929
+ break;
29930
+ }
29931
+ // Transform coords from their -10 to 10 space to 0 to 1
29932
+ // because of the old graph.coord, and also it's easier.
29933
+ const newCoords = normalizeCoords(coords, [[-10, 10], [-10, 10]]);
29934
+ return normalizePoints(range, step, newCoords);
29935
+ };
29752
29936
 
29753
29937
  // TS v4 doesn't narrow return types, while v5 does.
29754
29938
  // Instead of updating to v5, using generic type to relate input and output types.
@@ -29769,15 +29953,13 @@ function getGradableGraph(state, initialGraph) {
29769
29953
  });
29770
29954
  }
29771
29955
  if (state.type === "linear" && initialGraph.type === "linear") {
29772
- var _state$coords;
29773
29956
  return _extends({}, initialGraph, {
29774
- coords: (_state$coords = state.coords) == null ? void 0 : _state$coords[0]
29957
+ coords: state.coords[0]
29775
29958
  });
29776
29959
  }
29777
29960
  if (state.type === "ray" && initialGraph.type === "ray") {
29778
- var _state$coords2;
29779
29961
  return _extends({}, initialGraph, {
29780
- coords: (_state$coords2 = state.coords) == null ? void 0 : _state$coords2[0]
29962
+ coords: state.coords[0]
29781
29963
  });
29782
29964
  }
29783
29965
  if (state.type === "polygon" && initialGraph.type === "polygon") {
@@ -29785,6 +29967,11 @@ function getGradableGraph(state, initialGraph) {
29785
29967
  coords: state.coords
29786
29968
  });
29787
29969
  }
29970
+ if (state.type === "point" && initialGraph.type === "point") {
29971
+ return _extends({}, initialGraph, {
29972
+ coords: state.coords
29973
+ });
29974
+ }
29788
29975
  throw new Error("Mafs is not yet implemented for graph type: " + initialGraph.type);
29789
29976
  }
29790
29977
  const getDefaultSegments = ({
@@ -29822,10 +30009,10 @@ const getLineCoords = ({
29822
30009
  range,
29823
30010
  step
29824
30011
  }) => {
29825
- var _graph$coords;
30012
+ var _graph$coords2;
29826
30013
  return (
29827
30014
  // Return two lines for a linear system, one for a ray or linear
29828
- ((_graph$coords = graph.coords) != null ? _graph$coords : graph.type === "linear-system") ? defaultLinearCoords.map(collinear => normalizePoints(range, step, collinear)) : [normalizePoints(range, step, defaultLinearCoords[0])]
30015
+ ((_graph$coords2 = graph.coords) != null ? _graph$coords2 : graph.type === "linear-system") ? defaultLinearCoords.map(collinear => normalizePoints(range, step, collinear)) : [normalizePoints(range, step, defaultLinearCoords[0])]
29829
30016
  );
29830
30017
  };
29831
30018
  const getPolygonCoords = ({
@@ -29833,7 +30020,8 @@ const getPolygonCoords = ({
29833
30020
  range,
29834
30021
  step
29835
30022
  }) => {
29836
- let coords = graph.coords;
30023
+ var _graph$coords3;
30024
+ let coords = (_graph$coords3 = graph.coords) == null ? void 0 : _graph$coords3.slice();
29837
30025
  if (coords) {
29838
30026
  return coords;
29839
30027
  }
@@ -29887,6 +30075,13 @@ const renderGraph = props => {
29887
30075
  graphState: state,
29888
30076
  dispatch: dispatch
29889
30077
  });
30078
+ case "point":
30079
+ return /*#__PURE__*/React.createElement(PointGraph, {
30080
+ graphState: state,
30081
+ dispatch: dispatch
30082
+ });
30083
+ case "circle":
30084
+ throw new Error("the circle graph type is not yet implemented");
29890
30085
  default:
29891
30086
  return new UnreachableCaseError(type);
29892
30087
  }
@@ -29894,10 +30089,24 @@ const renderGraph = props => {
29894
30089
  const MafsGraph = /*#__PURE__*/React.forwardRef((props, ref) => {
29895
30090
  const [width, height] = props.box;
29896
30091
  const [state, dispatch] = React.useReducer(interactiveGraphReducer, props, initializeGraphState);
30092
+ const prevState = useRef(state);
30093
+ useEffect(() => {
30094
+ if (prevState.current !== state) {
30095
+ props.onChange({
30096
+ graph: state
30097
+ });
30098
+ }
30099
+ prevState.current = state;
30100
+ }, [props, state]);
29897
30101
  React.useImperativeHandle(ref, () => ({
29898
30102
  getUserInput: () => getGradableGraph(state, props.graph)
29899
30103
  }));
29900
- return /*#__PURE__*/React.createElement(View, {
30104
+ return /*#__PURE__*/React.createElement(GraphStateContext.Provider, {
30105
+ value: {
30106
+ state,
30107
+ dispatch
30108
+ }
30109
+ }, /*#__PURE__*/React.createElement(View, {
29901
30110
  style: {
29902
30111
  width,
29903
30112
  height,
@@ -29928,7 +30137,7 @@ const MafsGraph = /*#__PURE__*/React.forwardRef((props, ref) => {
29928
30137
  }), renderGraph({
29929
30138
  state,
29930
30139
  dispatch
29931
- }))));
30140
+ })))));
29932
30141
  });
29933
30142
 
29934
30143
  const {
@@ -31065,6 +31274,7 @@ class LegacyInteractiveGraph extends React.Component {
31065
31274
  $(this.angle).on("move", () => {
31066
31275
  var _this$angle;
31067
31276
  this.onChange({
31277
+ // @ts-expect-error Type '{ coords: any; type: "angle"; showAngles?: boolean | undefined; allowReflexAngles?: boolean | undefined; angleOffsetDeg?: number | undefined; snapDegrees?: number | undefined; match?: "congruent" | undefined; }' is not assignable to type 'InteractiveGraphState | undefined'.
31068
31278
  graph: _extends({}, graph, {
31069
31279
  coords: (_this$angle = this.angle) == null ? void 0 : _this$angle.getClockwiseCoords()
31070
31280
  })
@@ -31656,9 +31866,8 @@ class InteractiveGraph extends React.Component {
31656
31866
  };
31657
31867
  }
31658
31868
  } else if (userInput.type === "point" && rubric.correct.type === "point" && userInput.coords != null) {
31659
- var _userInput$coords;
31660
31869
  let correct = InteractiveGraph.getPointCoords(rubric.correct, component);
31661
- const guess = (_userInput$coords = userInput.coords) == null ? void 0 : _userInput$coords.slice();
31870
+ const guess = userInput.coords.slice();
31662
31871
  correct = correct.slice();
31663
31872
  // Everything's already rounded so we shouldn't need to do an
31664
31873
  // eq() comparison but _.isEqual(0, -0) is false, so we'll use
@@ -31676,8 +31885,8 @@ class InteractiveGraph extends React.Component {
31676
31885
  };
31677
31886
  }
31678
31887
  } else if (userInput.type === "polygon" && rubric.correct.type === "polygon" && userInput.coords != null) {
31679
- var _userInput$coords2, _rubric$correct$coord;
31680
- const guess = (_userInput$coords2 = userInput.coords) == null ? void 0 : _userInput$coords2.slice();
31888
+ var _userInput$coords, _rubric$correct$coord;
31889
+ const guess = (_userInput$coords = userInput.coords) == null ? void 0 : _userInput$coords.slice();
31681
31890
  // @ts-expect-error - TS2322 - Type 'Coord[] | undefined' is not assignable to type 'Coord[]'.
31682
31891
  const correct = (_rubric$correct$coord = rubric.correct.coords) == null ? void 0 : _rubric$correct$coord.slice();
31683
31892
  let match;
@@ -41972,7 +42181,7 @@ const init = function init(options) {
41972
42181
 
41973
42182
  // This file is processed by a Rollup plugin (replace) to inject the production
41974
42183
  const libName = "@khanacademy/perseus";
41975
- const libVersion = "21.0.0";
42184
+ const libVersion = "21.1.0";
41976
42185
  addLibraryVersionToPerseusDebug(libName, libVersion);
41977
42186
 
41978
42187
  const apiVersion = {
@@ -44482,7 +44691,7 @@ function getAnswersFromWidgets(widgets) {
44482
44691
  // E.g. 'item 1\nitem 2\nitem 3'
44483
44692
  const orderer = widget;
44484
44693
  if ((_orderer$options = orderer.options) != null && _orderer$options.correctOptions) {
44485
- answers.push(orderer.options.correctOptions.map(option => option.content).join("\n"));
44694
+ answers.push(joinOptionContents(orderer.options.correctOptions));
44486
44695
  }
44487
44696
  break;
44488
44697
  case "sorter":
@@ -44541,6 +44750,28 @@ function getAnswersFromWidgets(widgets) {
44541
44750
  return answers;
44542
44751
  }
44543
44752
 
44753
+ /**
44754
+ * Join the content of the options together.
44755
+ * @param {Array<{content: string}>} options
44756
+ * @returns {string}
44757
+ */
44758
+ const joinOptionContents = options => options.map(({
44759
+ content
44760
+ }) => content).join("\n");
44761
+
44762
+ /**
44763
+ * Convert an index to an option letter.
44764
+ * @param {number} index
44765
+ * @returns {string}
44766
+ * @example
44767
+ * toOptionLetter(0) // 'A'
44768
+ * toOptionLetter(1) // 'B'
44769
+ * toOptionLetter(25) // 'Z'
44770
+ * // Once the index goes past 25, it will start returning special characters
44771
+ * toOptionLetter(26) // '['
44772
+ */
44773
+ const toOptionLetter = index => String.fromCharCode("A".charCodeAt(0) + index);
44774
+
44544
44775
  /**
44545
44776
  * Inject a string equivalent of the widgets into the content.
44546
44777
  *
@@ -44551,7 +44782,7 @@ function getAnswersFromWidgets(widgets) {
44551
44782
  * @param {PerseusRenderer["widgets"]} widgets
44552
44783
  * @returns
44553
44784
  */
44554
- function injectWidgets(content, widgets) {
44785
+ function injectWidgets(content, widgets, widgetProps) {
44555
44786
  // The types for taskProgress.itemData are not well defined,
44556
44787
  // so there is a chance that widgets or content could be undefined.
44557
44788
  // In that scenario, simply exit early and return the content as is.
@@ -44571,12 +44802,20 @@ function injectWidgets(content, widgets) {
44571
44802
  switch (widget.type) {
44572
44803
  case "radio":
44573
44804
  // Replace radio with the radio options
44574
- // '[[☃ Radio 1]]' -> 'option 1\noption 2\noption 3'
44805
+ // '[[☃ Radio 1]]' -> 'choice 1\nchoice 2\nchoice 3'
44806
+ // or if the current widget state is available with proper order,
44807
+ // '[[☃ Radio 1]]' ->
44808
+ // 'Option A: choice 1\nOption B: choice 2\nOption C: choice 3'
44575
44809
  const radio = widget;
44810
+ const radioProps = widgetProps == null ? void 0 : widgetProps[widgetID];
44576
44811
  if ((_radio$options = radio.options) != null && (_radio$options = _radio$options.choices) != null && _radio$options.length) {
44577
44812
  var _radio$options2;
44578
- let radioContext = radio.options.choices.map(choice => choice.content).join("\n");
44579
- if ((_radio$options2 = radio.options) != null && _radio$options2.randomize) {
44813
+ let radioContext = joinOptionContents(radioProps ? radioProps.choices.map(({
44814
+ content
44815
+ }, i) => ({
44816
+ content: `Option ${toOptionLetter(i)}: ${content}`
44817
+ })) : radio.options.choices);
44818
+ if (!radioProps && (_radio$options2 = radio.options) != null && _radio$options2.randomize) {
44580
44819
  radioContext += "\nThose options are displayed in a different order to the user. If the user says the letter, number, or ordinal number, always ask them clarify which option they are referring to.\n";
44581
44820
  }
44582
44821
  context = context.replace(`[[☃ ${widgetID}]]`, radioContext);
@@ -44685,7 +44924,7 @@ function injectWidgets(content, widgets) {
44685
44924
  // '[[☃ Orderer 1]]' -> 'option 1\noption 2\noption 3'
44686
44925
  const orderer = widget;
44687
44926
  if ((_orderer$options2 = orderer.options) != null && _orderer$options2.options) {
44688
- context = context.replace(`[[☃ ${widgetID}]]`, orderer.options.options.map(option => option.content).join("\n"));
44927
+ context = context.replace(`[[☃ ${widgetID}]]`, joinOptionContents(orderer.options.options));
44689
44928
  }
44690
44929
  break;
44691
44930
  case "sorter":