@khanacademy/perseus 20.8.1 → 21.0.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
@@ -1921,7 +1921,7 @@ const KhanAnswerTypes = {
1921
1921
  }
1922
1922
  };
1923
1923
 
1924
- const _excluded$4 = ["scoring"];
1924
+ const _excluded$5 = ["scoring"];
1925
1925
  const ERROR_TITLE = i18n._("Oops!");
1926
1926
  const ERROR_MESSAGE = i18n._("Sorry, I don't understand that!");
1927
1927
 
@@ -2009,7 +2009,7 @@ class Expression extends React.Component {
2009
2009
  let {
2010
2010
  scoring
2011
2011
  } = _ref,
2012
- rubric = _objectWithoutPropertiesLoose(_ref, _excluded$4);
2012
+ rubric = _objectWithoutPropertiesLoose(_ref, _excluded$5);
2013
2013
  const score = Expression.validate(this.getUserInput(), rubric, onInputError || function () {});
2014
2014
 
2015
2015
  // "scoring" is a flag that indicates when we are checking answers.
@@ -18832,7 +18832,7 @@ const isAccessible = widgetInfo => {
18832
18832
  }
18833
18833
  return !!accessible;
18834
18834
  };
18835
- const getAllWidgetTypes$1 = () => {
18835
+ const getAllWidgetTypes = () => {
18836
18836
  return _.keys(widgets);
18837
18837
  };
18838
18838
  const upgradeWidgetInfoToLatestVersion = oldWidgetInfo => {
@@ -19101,7 +19101,7 @@ var widgets$1 = /*#__PURE__*/Object.freeze({
19101
19101
  getVersionVector: getVersionVector,
19102
19102
  getPublicWidgets: getPublicWidgets,
19103
19103
  isAccessible: isAccessible,
19104
- getAllWidgetTypes: getAllWidgetTypes$1,
19104
+ getAllWidgetTypes: getAllWidgetTypes,
19105
19105
  upgradeWidgetInfoToLatestVersion: upgradeWidgetInfoToLatestVersion,
19106
19106
  getRendererPropsForWidgetInfo: getRendererPropsForWidgetInfo,
19107
19107
  traverseChildWidgets: traverseChildWidgets$3,
@@ -19339,6 +19339,9 @@ class Renderer extends React.Component {
19339
19339
  if (!widgetInfo.type || !widgetInfo.alignment) {
19340
19340
  const newValues = {};
19341
19341
  if (!widgetInfo.type) {
19342
+ // TODO: why does widget have no type?
19343
+ // We don't want to derive type from widget ID
19344
+ // see: LEMS-1845
19342
19345
  newValues.type = widgetId.split(" ")[0];
19343
19346
  }
19344
19347
  if (!widgetInfo.alignment) {
@@ -21428,7 +21431,7 @@ InputWithExamples.defaultProps = {
21428
21431
  className: ""
21429
21432
  };
21430
21433
 
21431
- const _excluded$3 = ["keypadElement", "onFocus", "value"];
21434
+ const _excluded$4 = ["keypadElement", "onFocus", "value"];
21432
21435
  class SimpleKeypadInput extends React.Component {
21433
21436
  constructor(...args) {
21434
21437
  super(...args);
@@ -21467,7 +21470,7 @@ class SimpleKeypadInput extends React.Component {
21467
21470
  onFocus,
21468
21471
  value
21469
21472
  } = _this$props,
21470
- rest = _objectWithoutPropertiesLoose(_this$props, _excluded$3);
21473
+ rest = _objectWithoutPropertiesLoose(_this$props, _excluded$4);
21471
21474
  return (
21472
21475
  /*#__PURE__*/
21473
21476
  // @ts-expect-error - TS2769 - No overload matches this call.
@@ -23298,14 +23301,14 @@ var Choice$1 = /*#__PURE__*/React.forwardRef((props, ref) => /*#__PURE__*/React.
23298
23301
  forwardedRef: ref
23299
23302
  })));
23300
23303
 
23301
- const _excluded$2 = ["showContent", "content", "forwardedRef"];
23304
+ const _excluded$3 = ["showContent", "content", "forwardedRef"];
23302
23305
  const ChoiceNoneAbove = function ChoiceNoneAbove(props) {
23303
23306
  const {
23304
23307
  showContent,
23305
23308
  content,
23306
23309
  forwardedRef
23307
23310
  } = props,
23308
- rest = _objectWithoutPropertiesLoose(props, _excluded$2);
23311
+ rest = _objectWithoutPropertiesLoose(props, _excluded$3);
23309
23312
  const choiceProps = _extends({}, rest, {
23310
23313
  content: showContent ? content :
23311
23314
  /*#__PURE__*/
@@ -28261,6 +28264,10 @@ const getRayIntersectionCoords = (collinearPoint, extendFrom, range) => {
28261
28264
  return [xMax, yAtXMax];
28262
28265
  }
28263
28266
  };
28267
+ const getLines = points => points.map((point, i) => {
28268
+ const next = points[(i + 1) % points.length];
28269
+ return [point, next];
28270
+ });
28264
28271
 
28265
28272
  const defaultStroke = "var(--movable-line-stroke-color)";
28266
28273
  const MovableLine = props => {
@@ -28521,11 +28528,52 @@ const RayGraph = props => {
28521
28528
  }));
28522
28529
  };
28523
28530
 
28531
+ /**
28532
+ * Wrapper with Mafs transform styles for SVG elements.
28533
+ * Scales and translates the SVG element to match the Mafs view.
28534
+ * Use if you do not need/want to use the hook in `use-transform.ts`.
28535
+ */
28536
+ const MafsCssTransformWrapper = ({
28537
+ children
28538
+ }) => /*#__PURE__*/React.createElement("g", {
28539
+ style: {
28540
+ transform: `var(--mafs-view-transform) var(--mafs-user-transform)`
28541
+ }
28542
+ }, children);
28543
+
28544
+ const _excluded$2 = ["children"];
28545
+ const TextLabel = _ref => {
28546
+ let {
28547
+ children
28548
+ } = _ref,
28549
+ rest = _objectWithoutPropertiesLoose(_ref, _excluded$2);
28550
+ return /*#__PURE__*/React__default.createElement(Text$1, _extends({
28551
+ size: 16,
28552
+ svgTextProps: {
28553
+ filter: "url(#background)",
28554
+ fontWeight: "bold"
28555
+ }
28556
+ }, rest), children);
28557
+ };
28558
+ const SvgDefs = () => /*#__PURE__*/React__default.createElement("defs", null, /*#__PURE__*/React__default.createElement("filter", {
28559
+ id: "background",
28560
+ x: "-5%",
28561
+ width: "110%",
28562
+ y: "0%",
28563
+ height: "100%"
28564
+ }, /*#__PURE__*/React__default.createElement("feFlood", {
28565
+ floodColor: "#FFF",
28566
+ floodOpacity: "0.64"
28567
+ }), /*#__PURE__*/React__default.createElement("feComposite", {
28568
+ operator: "over",
28569
+ in: "SourceGraphic"
28570
+ })));
28571
+
28524
28572
  const Angle = ({
28525
28573
  centerPoint,
28526
28574
  endPoints,
28527
28575
  range,
28528
- polygonPoints,
28576
+ polygonLines,
28529
28577
  showAngles
28530
28578
  }) => {
28531
28579
  const [centerX, centerY] = centerPoint;
@@ -28547,7 +28595,7 @@ const Angle = ({
28547
28595
  // Used to calculate the inside of the angle and to create square
28548
28596
  const [x3, y3] = vec.add(centerPoint, vec.add(vec.sub([x1, y1], centerPoint), vec.sub([x2, y2], centerPoint)));
28549
28597
  if (!showAngles) {
28550
- return isRightAngle(angle) ? /*#__PURE__*/React.createElement(RightAngleArc, {
28598
+ return isRightAngle(angle) ? /*#__PURE__*/React.createElement(RightAngleSquare, {
28551
28599
  start: [x1, y1],
28552
28600
  vertex: [x2, y2],
28553
28601
  end: [x3, y3]
@@ -28555,13 +28603,13 @@ const Angle = ({
28555
28603
  }
28556
28604
 
28557
28605
  // Midpoint betwen ends of arc
28558
- const isInside = shouldDrawArcInside([x3, y3], centerPoint, range, polygonPoints);
28559
- const largeArcFlag = isInside ? 1 : 0;
28560
- const sweepFlag = isInside ? 1 : 0;
28606
+ const isOutside = shouldDrawArcOutside([x3, y3], centerPoint, range, polygonLines);
28607
+ const largeArcFlag = isOutside ? 1 : 0;
28608
+ const sweepFlag = isOutside ? 1 : 0;
28561
28609
  const arc = `M ${x1} ${y1} A ${radius} ${radius} 0 ${largeArcFlag} ${sweepFlag} ${x2} ${y2}`;
28562
28610
  let angleInDegrees = angle * (180 / Math.PI);
28563
28611
  // If we have triggered "largArcFlag", the angle should be greater than 180
28564
- if (isInside) {
28612
+ if (isOutside) {
28565
28613
  angleInDegrees = 360 - angleInDegrees;
28566
28614
  }
28567
28615
  const angleLabelNumber = parseFloat(angleInDegrees.toFixed(1));
@@ -28578,80 +28626,54 @@ const Angle = ({
28578
28626
  }), /*#__PURE__*/React.createElement("feComposite", {
28579
28627
  operator: "over",
28580
28628
  in: "SourceGraphic"
28581
- }))), !isInside && isRightAngle(angle) ? /*#__PURE__*/React.createElement(RightAngleArc, {
28629
+ }))), !isOutside && isRightAngle(angle) ? /*#__PURE__*/React.createElement(RightAngleSquare, {
28582
28630
  start: [x1, y1],
28583
28631
  vertex: [x2, y2],
28584
28632
  end: [x3, y3]
28585
- }) : /*#__PURE__*/React.createElement("g", {
28586
- style: {
28587
- transform: `var(--mafs-view-transform) var(--mafs-user-transform)`
28588
- }
28589
- }, /*#__PURE__*/React.createElement("path", {
28633
+ }) : /*#__PURE__*/React.createElement(MafsCssTransformWrapper, null, /*#__PURE__*/React.createElement("path", {
28590
28634
  d: arc,
28591
28635
  strokeWidth: 0.02,
28592
28636
  fill: "none"
28593
- })), /*#__PURE__*/React.createElement(Text$1, {
28637
+ })), /*#__PURE__*/React.createElement(TextLabel, {
28594
28638
  x: x3,
28595
28639
  y: y3,
28596
- size: 15,
28597
- svgTextProps: {
28598
- filter: "url(#background)",
28599
- fontWeight: "bold"
28600
- }
28601
- // Shift position if text is too close to movable point
28602
- ,
28603
28640
  attach: y3 - centerY > 0 ? "s" : "n",
28604
28641
  attachDistance: Math.abs(y3 - centerY) < 0.2 || vec.dist([x3, y3], centerPoint) < 0.3 ? 20 : 10
28605
28642
  }, angleLabel, "\xB0"));
28606
28643
  };
28607
- const RightAngleArc = ({
28644
+
28645
+ /**
28646
+ * This is broken out into its own component so that it can be used for an early return
28647
+ * (see line 55 or https://github.com/Khan/perseus/blob/84bbd882b11d16871e1b813a0b901f3b903d5479/packages/perseus/src/widgets/interactive-graphs/graphs/components/angle.tsx#L53-L57)
28648
+ */
28649
+ const RightAngleSquare = ({
28608
28650
  start: [x1, y1],
28609
28651
  vertex: [x2, y2],
28610
28652
  end: [x3, y3]
28611
- }) => /*#__PURE__*/React.createElement("g", {
28612
- style: {
28613
- transform: `var(--mafs-view-transform) var(--mafs-user-transform)`
28614
- }
28615
- }, /*#__PURE__*/React.createElement("path", {
28653
+ }) => /*#__PURE__*/React.createElement(MafsCssTransformWrapper, null, /*#__PURE__*/React.createElement("path", {
28616
28654
  d: `M ${x1} ${y1} L ${x3} ${y3} M ${x3} ${y3} L ${x2} ${y2}`,
28617
28655
  strokeWidth: 0.02,
28618
28656
  fill: "none"
28619
28657
  }));
28620
28658
  const isRightAngle = angle => Math.abs(angle - Math.PI / 2) < 0.01;
28621
- const shouldDrawArcInside = (midPoint, vertex, range, polygonPoints) => {
28622
- const rangeIntersectionPoint = getRayIntersectionCoords(vertex, midPoint, range);
28623
- const lines = getLines(polygonPoints);
28624
- let lineIntersections = 0;
28625
- lines.forEach(line => linesIntersect([vertex, rangeIntersectionPoint], line) && lineIntersections++);
28626
-
28627
- // The intersection check will sometimes return false if it intersects with
28628
- // another vertex, so in the case we get 0 intersections, we check for points.
28629
- if (lineIntersections === 0) {
28630
- // find point in array
28631
- const midpointIndex = polygonPoints.findIndex(([x, y]) => x === midPoint[0] && y === midPoint[1]);
28632
- // get array sans point and adjacent points
28633
- const [prev, next] = [(midpointIndex - 1 + polygonPoints.length) % polygonPoints.length, (midpointIndex + 1) % polygonPoints.length];
28634
- const [[lineAx, lineAy], [lineBx, lineBy]] = [midPoint, rangeIntersectionPoint];
28635
- for (let i = 0; i < polygonPoints.length; i++) {
28636
- if ([prev, midpointIndex, next].includes(i)) {
28637
- continue;
28638
- }
28639
- const [x, y] = polygonPoints[i];
28640
- const intersectsPoint = (y - lineAy) * (lineBx - lineAx) === (lineBy - lineAy) * (x - lineAx);
28641
- if (intersectsPoint) {
28642
- return false;
28643
- }
28644
- }
28645
- return true;
28646
- }
28659
+
28660
+ /**
28661
+ * Determines if an angle is an inside (false) or outside (true) angle.
28662
+ * They way, we know to flip the `largeArc` and `sweepArc` flags.
28663
+ * Uses the priciple that a ray from a point inside a polygon will intersect
28664
+ * with an odd number of lines, while a ray from a point outside the polygon
28665
+ * will intersect with an even number of lines.
28666
+ * https://stackoverflow.com/questions/217578/how-can-i-determine-whether-a-2d-point-is-within-a-polygon
28667
+ */
28668
+ const shouldDrawArcOutside = (midpoint, vertex, range, polygonLines) => {
28669
+ // Create a ray from the midpoint (inside angle) to the edge of the range
28670
+ const rangeIntersectionPoint = getRayIntersectionCoords(midpoint, vertex, range);
28671
+ let lineIntersectionCount = 0;
28672
+ polygonLines.forEach(line => linesIntersect([vertex, rangeIntersectionPoint], line) && lineIntersectionCount++);
28647
28673
 
28648
28674
  // If the number of intersections is even, the angle is inside the polygon
28649
- return isEven(lineIntersections);
28675
+ return !isEven(lineIntersectionCount);
28650
28676
  };
28651
- const getLines = points => points.map((point, i) => {
28652
- const next = points[(i + 1) % points.length];
28653
- return [point, next];
28654
- });
28655
28677
 
28656
28678
  // https://stackoverflow.com/a/24392281/7347484
28657
28679
  // The "intersects" function in geometry doesn't seem to work for this use case
@@ -28678,6 +28700,7 @@ const PolygonGraph = props => {
28678
28700
  coords,
28679
28701
  type,
28680
28702
  showAngles,
28703
+ showSides,
28681
28704
  range
28682
28705
  } = props.graphState;
28683
28706
  const points = coords != null ? coords : [[0, 0]];
@@ -28696,6 +28719,7 @@ const PolygonGraph = props => {
28696
28719
  constrain: p => p
28697
28720
  });
28698
28721
  const active = hovered || focused || dragging;
28722
+ const lines = getLines(points);
28699
28723
  return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Polygon, {
28700
28724
  points: [...points],
28701
28725
  color: "var(--movable-line-stroke-color)",
@@ -28714,9 +28738,17 @@ const PolygonGraph = props => {
28714
28738
  endPoints: [pt1, pt2],
28715
28739
  active: active,
28716
28740
  range: range,
28717
- polygonPoints: points,
28741
+ polygonLines: lines,
28718
28742
  showAngles: !!showAngles
28719
28743
  });
28744
+ }), showSides && lines.map(([start, end], i) => {
28745
+ const [x, y] = vec.midpoint(start, end);
28746
+ const length = parseFloat(vec.dist(start, end).toFixed(1));
28747
+ return /*#__PURE__*/React.createElement(TextLabel, {
28748
+ key: "side-" + i,
28749
+ x: x,
28750
+ y: y
28751
+ }, !Number.isInteger(length) && "≈ ", length);
28720
28752
  }), /*#__PURE__*/React.createElement(Polygon, {
28721
28753
  points: [...points],
28722
28754
  color: "transparent",
@@ -29721,30 +29753,39 @@ function initializeGraphState(params) {
29721
29753
  // TS v4 doesn't narrow return types, while v5 does.
29722
29754
  // Instead of updating to v5, using generic type to relate input and output types.
29723
29755
  function getGradableGraph(state, initialGraph) {
29724
- var _state$coords;
29725
29756
  if (!state.hasBeenInteractedWith) {
29726
- return initialGraph;
29757
+ return {
29758
+ type: initialGraph.type
29759
+ };
29727
29760
  }
29728
- switch (true) {
29729
- // coords: Array of CollinearTuple
29730
- case state.type === "linear-system" && initialGraph.type === "linear-system":
29731
- case state.type === "segment" && initialGraph.type === "segment":
29732
- return _extends({}, initialGraph, {
29733
- coords: state.coords
29734
- });
29735
- // coords: CollinearTuple
29736
- case state.type === "linear" && initialGraph.type === "linear":
29737
- case state.type === "ray" && initialGraph.type === "ray":
29738
- return _extends({}, initialGraph, {
29739
- coords: (_state$coords = state.coords) == null ? void 0 : _state$coords[0]
29740
- });
29741
- case state.type === "polygon" && initialGraph.type === "polygon":
29742
- return _extends({}, initialGraph, {
29743
- coords: state.coords
29744
- });
29745
- default:
29746
- throw new Error("Mafs is not yet implemented for graph type: " + initialGraph.type);
29761
+ if (initialGraph.type === "linear-system" && state.type === "linear-system") {
29762
+ return _extends({}, initialGraph, {
29763
+ coords: state.coords
29764
+ });
29765
+ }
29766
+ if (state.type === "segment" && initialGraph.type === "segment") {
29767
+ return _extends({}, initialGraph, {
29768
+ coords: state.coords
29769
+ });
29770
+ }
29771
+ if (state.type === "linear" && initialGraph.type === "linear") {
29772
+ var _state$coords;
29773
+ return _extends({}, initialGraph, {
29774
+ coords: (_state$coords = state.coords) == null ? void 0 : _state$coords[0]
29775
+ });
29776
+ }
29777
+ if (state.type === "ray" && initialGraph.type === "ray") {
29778
+ var _state$coords2;
29779
+ return _extends({}, initialGraph, {
29780
+ coords: (_state$coords2 = state.coords) == null ? void 0 : _state$coords2[0]
29781
+ });
29782
+ }
29783
+ if (state.type === "polygon" && initialGraph.type === "polygon") {
29784
+ return _extends({}, initialGraph, {
29785
+ coords: state.coords
29786
+ });
29747
29787
  }
29788
+ throw new Error("Mafs is not yet implemented for graph type: " + initialGraph.type);
29748
29789
  }
29749
29790
  const getDefaultSegments = ({
29750
29791
  graph,
@@ -29882,7 +29923,7 @@ const MafsGraph = /*#__PURE__*/React.forwardRef((props, ref) => {
29882
29923
  zoom: false,
29883
29924
  width: width,
29884
29925
  height: height
29885
- }, /*#__PURE__*/React.createElement(Grid, props), props.lockedFigures && /*#__PURE__*/React.createElement(GraphLockedLayer, {
29926
+ }, /*#__PURE__*/React.createElement(SvgDefs, null), /*#__PURE__*/React.createElement(Grid, props), props.lockedFigures && /*#__PURE__*/React.createElement(GraphLockedLayer, {
29886
29927
  lockedFigures: props.lockedFigures
29887
29928
  }), renderGraph({
29888
29929
  state,
@@ -31536,18 +31577,18 @@ class InteractiveGraph extends React.Component {
31536
31577
  const angle = GraphUtils.findAngle(coords[2], coords[0], coords[1]);
31537
31578
  return angle.toFixed(0) + "\u00B0 angle" + " at (" + coords[1].join(", ") + ")";
31538
31579
  }
31539
- static validate(
31540
- // TODO(kevinb): rename state to userInput
31541
- // state === userInput
31542
- state, rubric, component) {
31580
+ static validate(userInput, rubric, component) {
31543
31581
  // When nothing has moved, there will neither be coords nor the
31544
31582
  // circle's center/radius fields. When those fields are absent, skip
31545
31583
  // all these checks; just go mark the answer as empty.
31546
- // @ts-expect-error - TS2339 - Property 'coords' does not exist on type 'PerseusGraphType'. | TS2339 - Property 'center' does not exist on type 'PerseusGraphType'. | TS2339 - Property 'radius' does not exist on type 'PerseusGraphType'.
31547
- const hasValue = !!(state.coords || state.center && state.radius);
31548
- if (state.type === rubric.correct.type && hasValue) {
31549
- if (state.type === "linear" && rubric.correct.type === "linear" && state.coords != null) {
31550
- const guess = state.coords;
31584
+ const hasValue = Boolean(
31585
+ // @ts-expect-error - TS2339 - Property 'coords' does not exist on type 'PerseusGraphType'.
31586
+ userInput.coords ||
31587
+ // @ts-expect-error - TS2339 - Property 'center' does not exist on type 'PerseusGraphType'. | TS2339 - Property 'radius' does not exist on type 'PerseusGraphType'.
31588
+ userInput.center && userInput.radius);
31589
+ if (userInput.type === rubric.correct.type && hasValue) {
31590
+ if (userInput.type === "linear" && rubric.correct.type === "linear" && userInput.coords != null) {
31591
+ const guess = userInput.coords;
31551
31592
  const correct = rubric.correct.coords;
31552
31593
 
31553
31594
  // If both of the guess points are on the correct line, it's
@@ -31564,8 +31605,8 @@ class InteractiveGraph extends React.Component {
31564
31605
  message: null
31565
31606
  };
31566
31607
  }
31567
- } else if (state.type === "linear-system" && rubric.correct.type === "linear-system" && state.coords != null) {
31568
- const guess = state.coords;
31608
+ } else if (userInput.type === "linear-system" && rubric.correct.type === "linear-system" && userInput.coords != null) {
31609
+ const guess = userInput.coords;
31569
31610
  const correct = rubric.correct.coords;
31570
31611
  if (collinear(correct[0][0], correct[0][1], guess[0][0]) && collinear(correct[0][0], correct[0][1], guess[0][1]) && collinear(correct[1][0], correct[1][1], guess[1][0]) && collinear(correct[1][0], correct[1][1], guess[1][1]) || collinear(correct[0][0], correct[0][1], guess[1][0]) && collinear(correct[0][0], correct[0][1], guess[1][1]) && collinear(correct[1][0], correct[1][1], guess[0][0]) && collinear(correct[1][0], correct[1][1], guess[0][1])) {
31571
31612
  return {
@@ -31575,9 +31616,9 @@ class InteractiveGraph extends React.Component {
31575
31616
  message: null
31576
31617
  };
31577
31618
  }
31578
- } else if (state.type === "quadratic" && rubric.correct.type === "quadratic" && state.coords != null) {
31619
+ } else if (userInput.type === "quadratic" && rubric.correct.type === "quadratic" && userInput.coords != null) {
31579
31620
  // If the parabola coefficients match, it's correct.
31580
- const guessCoeffs = this.getQuadraticCoefficients(state.coords);
31621
+ const guessCoeffs = this.getQuadraticCoefficients(userInput.coords);
31581
31622
  const correctCoeffs = this.getQuadraticCoefficients(
31582
31623
  // @ts-expect-error - TS2345 - Argument of type 'readonly Coord[] | undefined' is not assignable to parameter of type 'readonly Coord[]'.
31583
31624
  rubric.correct.coords);
@@ -31589,8 +31630,8 @@ class InteractiveGraph extends React.Component {
31589
31630
  message: null
31590
31631
  };
31591
31632
  }
31592
- } else if (state.type === "sinusoid" && rubric.correct.type === "sinusoid" && state.coords != null) {
31593
- const guessCoeffs = this.getSinusoidCoefficients(state.coords);
31633
+ } else if (userInput.type === "sinusoid" && rubric.correct.type === "sinusoid" && userInput.coords != null) {
31634
+ const guessCoeffs = this.getSinusoidCoefficients(userInput.coords);
31594
31635
  const correctCoeffs = this.getSinusoidCoefficients(
31595
31636
  // @ts-expect-error - TS2345 - Argument of type 'readonly Coord[] | undefined' is not assignable to parameter of type 'readonly Coord[]'.
31596
31637
  rubric.correct.coords);
@@ -31605,8 +31646,8 @@ class InteractiveGraph extends React.Component {
31605
31646
  message: null
31606
31647
  };
31607
31648
  }
31608
- } else if (state.type === "circle" && rubric.correct.type === "circle") {
31609
- if (deepEq$1(state.center, rubric.correct.center) && eq(state.radius, rubric.correct.radius)) {
31649
+ } else if (userInput.type === "circle" && rubric.correct.type === "circle") {
31650
+ if (deepEq$1(userInput.center, rubric.correct.center) && eq(userInput.radius, rubric.correct.radius)) {
31610
31651
  return {
31611
31652
  type: "points",
31612
31653
  earned: 1,
@@ -31614,10 +31655,10 @@ class InteractiveGraph extends React.Component {
31614
31655
  message: null
31615
31656
  };
31616
31657
  }
31617
- } else if (state.type === "point" && rubric.correct.type === "point" && state.coords != null) {
31618
- var _state$coords;
31658
+ } else if (userInput.type === "point" && rubric.correct.type === "point" && userInput.coords != null) {
31659
+ var _userInput$coords;
31619
31660
  let correct = InteractiveGraph.getPointCoords(rubric.correct, component);
31620
- const guess = (_state$coords = state.coords) == null ? void 0 : _state$coords.slice();
31661
+ const guess = (_userInput$coords = userInput.coords) == null ? void 0 : _userInput$coords.slice();
31621
31662
  correct = correct.slice();
31622
31663
  // Everything's already rounded so we shouldn't need to do an
31623
31664
  // eq() comparison but _.isEqual(0, -0) is false, so we'll use
@@ -31634,9 +31675,9 @@ class InteractiveGraph extends React.Component {
31634
31675
  message: null
31635
31676
  };
31636
31677
  }
31637
- } else if (state.type === "polygon" && rubric.correct.type === "polygon" && state.coords != null) {
31638
- var _state$coords2, _rubric$correct$coord;
31639
- const guess = (_state$coords2 = state.coords) == null ? void 0 : _state$coords2.slice();
31678
+ } 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();
31640
31681
  // @ts-expect-error - TS2322 - Type 'Coord[] | undefined' is not assignable to type 'Coord[]'.
31641
31682
  const correct = (_rubric$correct$coord = rubric.correct.coords) == null ? void 0 : _rubric$correct$coord.slice();
31642
31683
  let match;
@@ -31660,9 +31701,9 @@ class InteractiveGraph extends React.Component {
31660
31701
  message: null
31661
31702
  };
31662
31703
  }
31663
- } else if (state.type === "segment" && rubric.correct.type === "segment" && state.coords != null) {
31704
+ } else if (userInput.type === "segment" && rubric.correct.type === "segment" && userInput.coords != null) {
31664
31705
  var _rubric$correct$coord2;
31665
- let guess = state.coords.slice();
31706
+ let guess = userInput.coords.slice();
31666
31707
  let correct = (_rubric$correct$coord2 = rubric.correct.coords) == null ? void 0 : _rubric$correct$coord2.slice();
31667
31708
  guess = _.invoke(guess, "sort").sort();
31668
31709
  // @ts-expect-error - TS2345 - Argument of type '(readonly Coord[])[] | undefined' is not assignable to parameter of type 'Collection<any>'.
@@ -31675,8 +31716,8 @@ class InteractiveGraph extends React.Component {
31675
31716
  message: null
31676
31717
  };
31677
31718
  }
31678
- } else if (state.type === "ray" && rubric.correct.type === "ray" && state.coords != null) {
31679
- const guess = state.coords;
31719
+ } else if (userInput.type === "ray" && rubric.correct.type === "ray" && userInput.coords != null) {
31720
+ const guess = userInput.coords;
31680
31721
  const correct = rubric.correct.coords;
31681
31722
  if (
31682
31723
  // @ts-expect-error - TS2532 - Object is possibly 'undefined'.
@@ -31690,8 +31731,8 @@ class InteractiveGraph extends React.Component {
31690
31731
  message: null
31691
31732
  };
31692
31733
  }
31693
- } else if (state.type === "angle" && rubric.correct.type === "angle") {
31694
- const guess = state.coords;
31734
+ } else if (userInput.type === "angle" && rubric.correct.type === "angle") {
31735
+ const guess = userInput.coords;
31695
31736
  const correct = rubric.correct.coords;
31696
31737
  let match;
31697
31738
  if (rubric.correct.match === "congruent") {
@@ -31730,7 +31771,7 @@ class InteractiveGraph extends React.Component {
31730
31771
 
31731
31772
  // The input wasn't correct, so check if it's a blank input or if it's
31732
31773
  // actually just wrong
31733
- if (!hasValue || _.isEqual(state, rubric.graph)) {
31774
+ if (!hasValue || _.isEqual(userInput, rubric.graph)) {
31734
31775
  // We're where we started.
31735
31776
  return {
31736
31777
  type: "invalid",
@@ -41931,7 +41972,7 @@ const init = function init(options) {
41931
41972
 
41932
41973
  // This file is processed by a Rollup plugin (replace) to inject the production
41933
41974
  const libName = "@khanacademy/perseus";
41934
- const libVersion = "20.8.1";
41975
+ const libVersion = "21.0.0";
41935
41976
  addLibraryVersionToPerseusDebug(libName, libVersion);
41936
41977
 
41937
41978
  const apiVersion = {
@@ -44183,6 +44224,140 @@ const registerAllWidgetsForTesting = () => {
44183
44224
  replaceDeprecatedWidgets();
44184
44225
  };
44185
44226
 
44227
+ // These are all the current widgets that require user-input and are supported for automatic scoring
44228
+ const QUESTION_WIDGETS = ["numeric-input", "input-number", "radio", "interactive-graph", "interactive-number-line", "categorizer", "plotter", "orderer", "protractor", "matcher", "sorter"];
44229
+
44230
+ /**
44231
+ * Regex for widget placeholders in a string.
44232
+ *
44233
+ * First capture group is the widget ID (ex. 'radio 1')
44234
+ * Second capture group is the widget type (ex. "radio)
44235
+ * exec return will look like: ['[[☃ radio 1]]', 'radio 1', 'radio']
44236
+ */
44237
+ function getWidgetRegex() {
44238
+ return /\[\[☃ ([A-Za-z0-9- ]+)\]\]/g;
44239
+ }
44240
+
44241
+ /**
44242
+ * Add a widget placeholder using the widget ID.
44243
+ * ex. addWidget("radio 1") => "[[☃ radio 1]]"
44244
+ *
44245
+ * @param {string} id
44246
+ * @returns {string}
44247
+ */
44248
+ function addWidget(id) {
44249
+ return `[[☃ ${id}]]`;
44250
+ }
44251
+
44252
+ /**
44253
+ * Get a widget type by a widget's ID
44254
+ * (please don't derive type from ID)
44255
+ *
44256
+ * @param {string} widgetId the ID of the widget
44257
+ * @param {PerseusWidgetsMap} widgetMap widget ID to widget map
44258
+ * @returns {string} the widget type (ie "radio")
44259
+ */
44260
+ function getWidgetTypeByWidgetId(WidgetId, widgetMap) {
44261
+ var _widget$type;
44262
+ const widget = widgetMap[WidgetId];
44263
+ return (_widget$type = widget == null ? void 0 : widget.type) != null ? _widget$type : null;
44264
+ }
44265
+
44266
+ /**
44267
+ * Does the content have a specific type of widget?
44268
+ *
44269
+ * @param {string} type the type of the widget in question (ie "radio")
44270
+ * @param {string} content the string to search through
44271
+ * @param {PerseusWidgetsMap} widgetMap widget ID to widget map
44272
+ * @returns {boolean} if the content includes the widget type
44273
+ */
44274
+ function contentHasWidgetType(type, content, widgetMap) {
44275
+ return getWidgetIdsFromContentByType(type, content, widgetMap).length > 0;
44276
+ }
44277
+
44278
+ /**
44279
+ * Extract all widget IDs, which includes the widget type and instance number.
44280
+ * example output: ['radio 1', 'categorizer 1', 'categorizor 2']
44281
+ *
44282
+ * Content should contain Perseus widget placeholders,
44283
+ * which look like: '[[☃ radio 1]]'.
44284
+ *
44285
+ * @param {string} content
44286
+ * @returns {ReadonlyArray<string>} widgetIds
44287
+ */
44288
+ function getWidgetIdsFromContent(content) {
44289
+ const widgets = [];
44290
+ const localWidgetRegex = getWidgetRegex();
44291
+ let match = localWidgetRegex.exec(content);
44292
+ while (match !== null) {
44293
+ widgets.push(match[1]);
44294
+ match = localWidgetRegex.exec(content);
44295
+ }
44296
+ return widgets;
44297
+ }
44298
+
44299
+ /**
44300
+ * Get a list of widget IDs from content,
44301
+ * but only for specific widget types
44302
+ *
44303
+ * @param {string} type the type of widget (ie "radio")
44304
+ * @param {string} content the string to parse
44305
+ * @param {PerseusWidgetsMap} widgetMap widget ID to widget map
44306
+ * @returns {ReadonlyArray<string>} the widget type (ie "radio")
44307
+ */
44308
+ function getWidgetIdsFromContentByType(type, content, widgetMap) {
44309
+ const rv = [];
44310
+ const widgetIdsInContent = getWidgetIdsFromContent(content);
44311
+ widgetIdsInContent.forEach(widgetId => {
44312
+ const widget = widgetMap[widgetId];
44313
+ if ((widget == null ? void 0 : widget.type) === type) {
44314
+ rv.push(widgetId);
44315
+ }
44316
+ });
44317
+ return rv;
44318
+ }
44319
+
44320
+ /**
44321
+ * Pull the widget map out of ItemData
44322
+ *
44323
+ * @param {PerseusItem} PerseusItem containing a widgetMap
44324
+ * @returns {WidgetMap} the widget map in the PerseusItem
44325
+ */
44326
+ function getWidgetsMapFromItemData(itemData) {
44327
+ return itemData.question.widgets;
44328
+ }
44329
+
44330
+ /**
44331
+ * Get the widget information from a WidgetMap
44332
+ *
44333
+ * @param {string} widgetId the ID of the widget
44334
+ * @returns {PerseusWidget | null} the widget data if it exists, otherwise null
44335
+ */
44336
+ function getWidgetFromWidgetMap(widgetId, widgetMap) {
44337
+ var _widgetMap$widgetId;
44338
+ return (_widgetMap$widgetId = widgetMap[widgetId]) != null ? _widgetMap$widgetId : null;
44339
+ }
44340
+
44341
+ /**
44342
+ * Get select widgets from a widget map.
44343
+ * Useful for multi-items that needs to split
44344
+ * one PerseusItem into several
44345
+ *
44346
+ * @param {ReadonlyArray<string>} widgetIds to extract
44347
+ * @param {PerseusWidgetsMap} widgetMap to extract from
44348
+ * @return {PerseusWidgetsMap} a new widget map with requested widgets
44349
+ */
44350
+ function getWidgetsFromWidgetMap(widgetIds, widgetMap) {
44351
+ const widgets = {};
44352
+ widgetIds.forEach(widgetId => {
44353
+ const widget = getWidgetFromWidgetMap(widgetId, widgetMap);
44354
+ if (widget) {
44355
+ widgets[widgetId] = widget;
44356
+ }
44357
+ });
44358
+ return widgets;
44359
+ }
44360
+
44186
44361
  /**
44187
44362
  * This function extracts the answers from the widgets.
44188
44363
  *
@@ -44593,18 +44768,15 @@ const INDIVIDUAL_ANSWER_WIDGETS = ["interactive-graph", "categorizer", "grapher"
44593
44768
 
44594
44769
  /* Widgets that are supported for automatic scoring */
44595
44770
  const SUPPORTED_WIDGETS = ["radio", "numeric-input", "input-number", "expression", ...INDIVIDUAL_ANSWER_WIDGETS];
44596
- const getWidgetTypeFromWidgetKey = widgetKey => {
44597
- return widgetKey.split(" ")[0];
44598
- };
44599
44771
 
44600
44772
  /* Verify if the perseus item has supported widgets for automatic scoring */
44601
- const isWrongAnswerSupported = widgetKeys => {
44602
- return widgetKeys.length !== 0 && widgetKeys.every(widgetKey => SUPPORTED_WIDGETS.includes(getWidgetTypeFromWidgetKey(widgetKey)));
44773
+ const isWrongAnswerSupported = (widgetIds, widgetMap) => {
44774
+ return widgetIds.length !== 0 && widgetIds.every(widgetId => SUPPORTED_WIDGETS.includes(getWidgetTypeByWidgetId(widgetId, widgetMap)));
44603
44775
  };
44604
44776
 
44605
- /* Verify if the widget key has an individual answer for the coach report view */
44606
- const shouldHaveIndividualAnswer = widgetKey => {
44607
- return INDIVIDUAL_ANSWER_WIDGETS.includes(getWidgetTypeFromWidgetKey(widgetKey));
44777
+ /* Verify if the widget ID has an individual answer for the coach report view */
44778
+ const shouldHaveIndividualAnswer = (widgetId, widgetMap) => {
44779
+ return INDIVIDUAL_ANSWER_WIDGETS.includes(getWidgetTypeByWidgetId(widgetId, widgetMap));
44608
44780
  };
44609
44781
 
44610
44782
  /* Returns the answer userInput submission for currently supported widgets */
@@ -44623,29 +44795,30 @@ const getAnswerFromUserInput = (widgetType, userInput) => {
44623
44795
  return userInput;
44624
44796
  };
44625
44797
 
44626
- /* Returns the correct answer for a given widget key and Perseus Item */
44798
+ /* Returns the correct answer for a given widget ID and Perseus Item */
44627
44799
  // TODO (LEMS-1835): We should fix the resonse type from getWidget to be specific.
44628
44800
  // TODO (LEMS-1836): We should also consider adding the getOneCorrectAnswerFromRubric method to all widgets.
44629
- const getCorrectAnswerForWidgetKey = (widgetKey, itemData) => {
44630
- const rubric = itemData.question.widgets[widgetKey].options;
44631
- const widgetType = getWidgetTypeFromWidgetKey(widgetKey);
44801
+ const getCorrectAnswerForWidgetId = (widgetId, itemData) => {
44802
+ const rubric = itemData.question.widgets[widgetId].options;
44803
+ const widgetMap = getWidgetsMapFromItemData(itemData);
44804
+ const widgetType = getWidgetTypeByWidgetId(widgetId, widgetMap);
44632
44805
  const widget = getWidget(widgetType);
44633
44806
 
44634
44807
  // @ts-expect-error - TS2339 - Property 'getOneCorrectAnswerFromRubric' does not exist on type 'ComponentType<any>'.
44635
44808
  return widget == null || widget.getOneCorrectAnswerFromRubric == null ? void 0 : widget.getOneCorrectAnswerFromRubric(rubric);
44636
44809
  };
44637
44810
 
44638
- /* Verify if the widget key exists in the content string of the Perseus Item */
44639
- const isWidgetKeyInContent = (perseusItem, widgetKey) => {
44640
- return perseusItem.question.content.indexOf(widgetKey) !== -1;
44811
+ /* Verify if the widget ID exists in the content string of the Perseus Item */
44812
+ const isWidgetIdInContent = (perseusItem, widgetId) => {
44813
+ return perseusItem.question.content.indexOf(widgetId) !== -1;
44641
44814
  };
44642
44815
 
44643
- /* Return an array of all the widget keys that exist in the content string of a Perseus Item */
44644
- const getValidWidgetKeys = perseusItem => {
44816
+ /* Return an array of all the widget IDs that exist in the content string of a Perseus Item */
44817
+ const getValidWidgetIds = perseusItem => {
44645
44818
  const {
44646
44819
  widgets
44647
44820
  } = perseusItem.question;
44648
- return keys(widgets).filter(key => isWidgetKeyInContent(perseusItem, key));
44821
+ return keys(widgets).filter(id => isWidgetIdInContent(perseusItem, id));
44649
44822
  };
44650
44823
 
44651
44824
  /**
@@ -44693,12 +44866,113 @@ const genericPerseusItemData = {
44693
44866
  * uses a basic Perseus object and updates it with custom values as needed.
44694
44867
  *
44695
44868
  * @param {Partial<PerseusItem>} customFields
44696
- * @returns PerseusItem
44869
+ * @returns {PerseusItem}
44697
44870
  */
44698
44871
  function generateTestPerseusItem(customFields = {}) {
44699
44872
  return _extends({}, genericPerseusItemData, customFields);
44700
44873
  }
44701
44874
 
44875
+ /**
44876
+ * Creates an object with the bare amount
44877
+ * of data to be a properly typed RadioWidget
44878
+ *
44879
+ * @returns {RadioWidget} skeleton RadioWidget for testing
44880
+ */
44881
+ function generateTestRadioWidget() {
44882
+ return {
44883
+ type: "radio",
44884
+ options: {
44885
+ choices: []
44886
+ }
44887
+ };
44888
+ }
44889
+
44890
+ /**
44891
+ * Creates an object with the bare amount
44892
+ * of data to be a properly typed InteractiveGraphWidget
44893
+ *
44894
+ * @returns {InteractiveGraphWidget} skeleton InteractiveGraphWidget for testing
44895
+ */
44896
+ function generateTestInteractiveGraphWidget() {
44897
+ return {
44898
+ type: "interactive-graph",
44899
+ options: {
44900
+ step: [1, 1],
44901
+ gridStep: [1, 1],
44902
+ snapStep: [1, 1],
44903
+ markings: "graph",
44904
+ labels: ["x", "y"],
44905
+ showProtractor: false,
44906
+ showRuler: false,
44907
+ rulerLabel: "mm",
44908
+ rulerTicks: 2,
44909
+ range: [[-10, 10], [-10, 10]],
44910
+ graph: {
44911
+ type: "angle"
44912
+ },
44913
+ correct: {
44914
+ type: "angle"
44915
+ }
44916
+ }
44917
+ };
44918
+ }
44919
+
44920
+ /**
44921
+ * Creates an object with the bare amount
44922
+ * of data to be a properly typed InteractiveGraphWidget
44923
+ *
44924
+ * @returns {InteractiveGraphWidget} skeleton InteractiveGraphWidget for testing
44925
+ */
44926
+ function generateTestCategorizerWidget() {
44927
+ return {
44928
+ type: "categorizer",
44929
+ options: {
44930
+ items: [],
44931
+ categories: [],
44932
+ randomizeItems: false,
44933
+ static: false,
44934
+ values: []
44935
+ }
44936
+ };
44937
+ }
44938
+
44939
+ /**
44940
+ * Creates an object with the bare amount
44941
+ * of data to be a properly typed ExpressionWidget
44942
+ *
44943
+ * @returns {ExpressionWidget} skeleton ExpressionWidget for testing
44944
+ */
44945
+ function generateTestExpressionWidget() {
44946
+ return {
44947
+ type: "expression",
44948
+ options: {
44949
+ answerForms: [],
44950
+ buttonSets: ["basic"],
44951
+ functions: [],
44952
+ times: false
44953
+ }
44954
+ };
44955
+ }
44956
+
44957
+ /**
44958
+ * Creates an object with the bare amount
44959
+ * of data to be a properly typed NumericInputWidget
44960
+ *
44961
+ * @returns {NumericInputWidget} skeleton NumericInputWidget for testing
44962
+ */
44963
+ function generateTestNumericInputWidget() {
44964
+ return {
44965
+ type: "numeric-input",
44966
+ options: {
44967
+ answers: [],
44968
+ labelText: "",
44969
+ size: "normal",
44970
+ coefficient: false,
44971
+ static: false
44972
+ }
44973
+ };
44974
+ }
44975
+
44702
44976
  /**
44703
44977
  * Some helpers for CEDAR logging. CEDAR convention dictates
44704
44978
  * that enum values should be upper case. Ex: for the
@@ -44753,162 +45027,6 @@ function convertWidgetNameToEnum(name) {
44753
45027
  }
44754
45028
  return widgetEnum;
44755
45029
  }
44756
- function getWidgetTypeFromWidgetId(widgetId) {
44757
- const [widgetType, widgetNumber] = widgetId.split(" ");
44758
- if (!widgetNumber) {
44759
- throw new Error(`Incorrect format for widget ID: ${widgetId}`);
44760
- }
44761
- if (!widgetNameToEnum[widgetType]) {
44762
- throw new Error(`Unknown widget type (${widgetType}) in widget ID: ${widgetId}`);
44763
- }
44764
- return widgetType;
44765
- }
44766
-
44767
- let WidgetType = /*#__PURE__*/function (WidgetType) {
44768
- WidgetType["Categorizer"] = "categorizer";
44769
- WidgetType["CSProgram"] = "cs-program";
44770
- WidgetType["Definition"] = "definition";
44771
- WidgetType["Dropdown"] = "dropdown";
44772
- WidgetType["Explanation"] = "explanation";
44773
- WidgetType["Expression"] = "expression";
44774
- WidgetType["GradedGroup"] = "graded-group";
44775
- WidgetType["GradedGroupAnswerBar"] = "graded-group-answer-bar";
44776
- WidgetType["GradedGroupSet"] = "graded-group-set";
44777
- WidgetType["Grapher"] = "grapher";
44778
- WidgetType["Group"] = "group";
44779
- WidgetType["Iframe"] = "iframe";
44780
- WidgetType["Image"] = "image";
44781
- WidgetType["InputNumber"] = "input-number";
44782
- WidgetType["Interaction"] = "interaction";
44783
- WidgetType["InteractiveGraph"] = "interactive-graph";
44784
- WidgetType["InteractiveNumberLine"] = "interactive-number-line";
44785
- WidgetType["LabelImage"] = "label-image";
44786
- WidgetType["Matcher"] = "matcher";
44787
- WidgetType["Matrix"] = "matrix";
44788
- WidgetType["Measurer"] = "measurer";
44789
- WidgetType["Molecule"] = "molecule";
44790
- WidgetType["NumberLine"] = "number-line";
44791
- WidgetType["NumericInput"] = "numeric-input";
44792
- WidgetType["Orderer"] = "orderer";
44793
- WidgetType["Passage"] = "passage";
44794
- WidgetType["PassageRef"] = "passage-ref";
44795
- WidgetType["PassageRefTarget"] = "passage-ref-target";
44796
- WidgetType["Plotter"] = "plotter";
44797
- WidgetType["Protractor"] = "protractor";
44798
- WidgetType["PythonProgram"] = "python-program";
44799
- WidgetType["Radio"] = "radio";
44800
- WidgetType["Sorter"] = "sorter";
44801
- WidgetType["Table"] = "table";
44802
- WidgetType["Unit"] = "unit";
44803
- WidgetType["Video"] = "video";
44804
- WidgetType["VideoTranscriptLink"] = "video-transcript-link";
44805
- return WidgetType;
44806
- }({});
44807
- // These are all the current widgets that require user-input and are supported for automatic scoring
44808
- const QUESTION_WIDGETS = ["numeric-input", "input-number", "radio", "interactive-graph", "interactive-number-line", "categorizer", "plotter", "orderer", "protractor", "matcher", "sorter"];
44809
-
44810
- /**
44811
- * Regex for widget placeholders in a string.
44812
- *
44813
- * First capture group is the widget ID (ex. 'radio 1')
44814
- * Second capture group is the widget type (ex. "radio)
44815
- * exec return will look like: ['[[☃ radio 1]]', 'radio 1', 'radio']
44816
- */
44817
- function getWidgetRegex() {
44818
- return /\[\[☃ (([a-z-]+) \d+)\]\]/g;
44819
- }
44820
-
44821
- /**
44822
- * Add a widget placeholder using the widget ID.
44823
- * ex. addWidget("radio 1") => "[[☃ radio 1]]"
44824
- *
44825
- * @param {string} id
44826
- * @returns {string}
44827
- */
44828
- function addWidget(id) {
44829
- return `[[☃ ${id}]]`;
44830
- }
44831
-
44832
- /**
44833
- * Extract all widget IDs, which includes the widget type and instance number.
44834
- * example output: ['radio 1', 'categorizer 1', 'categorizor 2']
44835
- *
44836
- * Content should contain Perseus widget placeholders,
44837
- * which look like: '[[☃ radio 1]]'.
44838
- *
44839
- * @param {string} content
44840
- * @returns {Array<WidgetId>} widgets
44841
- */
44842
- function getAllWidgetIds(content) {
44843
- const widgets = [];
44844
- const localWidgetRegex = getWidgetRegex();
44845
- let match = localWidgetRegex.exec(content);
44846
- while (match !== null) {
44847
- widgets.push(match[1]);
44848
- match = localWidgetRegex.exec(content);
44849
- }
44850
- return widgets;
44851
- }
44852
-
44853
- /**
44854
- * Check if a specific widget is an actual widget type using its name.
44855
- * The widget name should include a widget type, without an instance number,
44856
- * as a string. ex. 'radio'
44857
- *
44858
- * @param {string} widgetName
44859
- * @returns {boolean}
44860
- */
44861
- function isWidgetType(widgetName) {
44862
- return Object.values(WidgetType).includes(widgetName);
44863
- }
44864
-
44865
- /**
44866
- * Extract all widget types from a Perseus JSON content string.
44867
- * This does not include the instance number and prevents duplicates.
44868
- * example output: ['radio', 'categorizer']
44869
- *
44870
- * @param {string} content
44871
- * @returns {Array<WidgetId>}
44872
- */
44873
- function getAllWidgetTypes(content) {
44874
- const widgetTypes = [];
44875
- const localWidgetRegex = getWidgetRegex();
44876
- let match = localWidgetRegex.exec(content);
44877
- while (match !== null) {
44878
- const matchType = match[2];
44879
- if (!widgetTypes.includes(matchType) && isWidgetType(matchType)) {
44880
- widgetTypes.push(matchType);
44881
- }
44882
- match = localWidgetRegex.exec(content);
44883
- }
44884
- return widgetTypes;
44885
- }
44886
-
44887
- /**
44888
- * Check if a specific widget is a question widget type using its ID.
44889
- * The widget ID includes the widget type and the instance number.
44890
- *
44891
- * @param {WidgetId} widgetId
44892
- * @returns {boolean}
44893
- */
44894
- function isQuestionWidgetType(widgetId) {
44895
- return QUESTION_WIDGETS.includes(widgetId.split(" ")[0]);
44896
- }
44897
-
44898
- /**
44899
- * Extract the widget IDs of all widgets considered question widgets. Widget IDs
44900
- * include widget type and instance number.
44901
- * example output: ['radio 1', 'categorizer 1']
44902
- *
44903
- * Content should contain Perseus widget placeholders,
44904
- * which look like: '[[☃ radio 1]]'.
44905
- *
44906
- * @param {string} content
44907
- * @returns Array<WidgetId>
44908
- */
44909
- function getQuestionWidgetIds(content) {
44910
- return getAllWidgetIds(content).filter(isQuestionWidgetType);
44911
- }
44912
45030
 
44913
45031
  const EditorJsonify = {
44914
45032
  serialize: function () {
@@ -44918,5 +45036,5 @@ const EditorJsonify = {
44918
45036
  }
44919
45037
  };
44920
45038
 
44921
- export { ApiOptions, ArrowPicker, ArticleRenderer, BaseRadio, Categorizer$1 as Categorizer, changeable as Changeable, ClassNames, ColorPicker, DashPicker, dependencies as Dependencies, EditorJsonify, ElementContainer, Errors, Expression, util as GrapherUtil, Grapher$1 as GrapherWidget, HintRenderer, HintsRenderer, InputNumber$1 as InputNumber, InteractiveGraph$1 as InteractiveGraphWidget, ItemExtras, JiptParagraphs, KhanColors, KhanMath, context as LoadingContext, Log, Matrix$1 as MatrixWidget, MultiItems, NumericInput$1 as NumericInput, PerseusError, PerseusExpressionAnswerFormConsidered, PerseusMarkdown, Plotter$1 as PlotterWidget, QUESTION_WIDGETS, Radio, Renderer, ref as ServerItemRenderer, sizingUtils as SizingUtils, Table$1 as TableWidget, Util, denylist as WIDGET_PROP_DENYLIST, WidgetJsonifyDeprecated, WidgetType, widgets$1 as Widgets, addWidget, apiVersion, buildEmptyItemTreeForShape, buildMapper, components, constants, convertWidgetNameToEnum, displaySigFigs, generateTestPerseusItem, genericPerseusItemData, getAllWidgetIds, getAllWidgetTypes, getAnswerFromUserInput, getAnswersFromWidgets, getCorrectAnswerForWidgetKey, getQuestionWidgetIds, getValidWidgetKeys, getWidgetTypeFromWidgetId, getWidgetTypeFromWidgetKey, globalConstants, globalStyles, iconPaths as icons, init, injectWidgets, interactiveSizes$1 as interactiveSizes, isItemRenderableByVersion, isWidgetKeyInContent, isWrongAnswerSupported, itemDataVersion, itemToTree, ItemVersion as itemVersion, libVersion, mediaQueries, parsePerseusItem, plotterPlotTypes, preprocessTex, registerAllWidgetsForTesting, shapes, shouldHaveIndividualAnswer, traverse, violatingWidgets, allWidgets as widgets };
45039
+ export { ApiOptions, ArrowPicker, ArticleRenderer, BaseRadio, Categorizer$1 as Categorizer, changeable as Changeable, ClassNames, ColorPicker, DashPicker, dependencies as Dependencies, EditorJsonify, ElementContainer, Errors, Expression, util as GrapherUtil, Grapher$1 as GrapherWidget, HintRenderer, HintsRenderer, InputNumber$1 as InputNumber, InteractiveGraph$1 as InteractiveGraphWidget, ItemExtras, JiptParagraphs, KhanColors, KhanMath, context as LoadingContext, Log, Matrix$1 as MatrixWidget, MultiItems, NumericInput$1 as NumericInput, PerseusError, PerseusExpressionAnswerFormConsidered, PerseusMarkdown, Plotter$1 as PlotterWidget, QUESTION_WIDGETS, Radio, Renderer, ref as ServerItemRenderer, sizingUtils as SizingUtils, Table$1 as TableWidget, Util, denylist as WIDGET_PROP_DENYLIST, WidgetJsonifyDeprecated, widgets$1 as Widgets, addWidget, apiVersion, buildEmptyItemTreeForShape, buildMapper, components, constants, contentHasWidgetType, convertWidgetNameToEnum, displaySigFigs, generateTestCategorizerWidget, generateTestExpressionWidget, generateTestInteractiveGraphWidget, generateTestNumericInputWidget, generateTestPerseusItem, generateTestRadioWidget, genericPerseusItemData, getAnswerFromUserInput, getAnswersFromWidgets, getCorrectAnswerForWidgetId, getValidWidgetIds, getWidgetFromWidgetMap, getWidgetIdsFromContent, getWidgetIdsFromContentByType, getWidgetTypeByWidgetId, getWidgetsFromWidgetMap, getWidgetsMapFromItemData, globalConstants, globalStyles, iconPaths as icons, init, injectWidgets, interactiveSizes$1 as interactiveSizes, isItemRenderableByVersion, isWidgetIdInContent, isWrongAnswerSupported, itemDataVersion, itemToTree, ItemVersion as itemVersion, libVersion, mediaQueries, parsePerseusItem, plotterPlotTypes, preprocessTex, registerAllWidgetsForTesting, shapes, shouldHaveIndividualAnswer, traverse, violatingWidgets, allWidgets as widgets };
44922
45040
  //# sourceMappingURL=index.js.map