@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/index.js CHANGED
@@ -18890,7 +18890,7 @@ const isAccessible = widgetInfo => {
18890
18890
  }
18891
18891
  return !!accessible;
18892
18892
  };
18893
- const getAllWidgetTypes$1 = () => {
18893
+ const getAllWidgetTypes = () => {
18894
18894
  return ___default["default"].keys(widgets);
18895
18895
  };
18896
18896
  const upgradeWidgetInfoToLatestVersion = oldWidgetInfo => {
@@ -19162,7 +19162,7 @@ var widgets$1 = /*#__PURE__*/Object.freeze({
19162
19162
  getVersionVector: getVersionVector,
19163
19163
  getPublicWidgets: getPublicWidgets,
19164
19164
  isAccessible: isAccessible,
19165
- getAllWidgetTypes: getAllWidgetTypes$1,
19165
+ getAllWidgetTypes: getAllWidgetTypes,
19166
19166
  upgradeWidgetInfoToLatestVersion: upgradeWidgetInfoToLatestVersion,
19167
19167
  getRendererPropsForWidgetInfo: getRendererPropsForWidgetInfo,
19168
19168
  traverseChildWidgets: traverseChildWidgets$3,
@@ -19560,6 +19560,9 @@ class Renderer extends React__namespace.Component {
19560
19560
  if (!widgetInfo.type || !widgetInfo.alignment) {
19561
19561
  const newValues = {};
19562
19562
  if (!widgetInfo.type) {
19563
+ // TODO: why does widget have no type?
19564
+ // We don't want to derive type from widget ID
19565
+ // see: LEMS-1845
19563
19566
  newValues.type = widgetId.split(" ")[0];
19564
19567
  }
19565
19568
  if (!widgetInfo.alignment) {
@@ -28416,6 +28419,10 @@ const getRayIntersectionCoords = (collinearPoint, extendFrom, range) => {
28416
28419
  return [xMax, yAtXMax];
28417
28420
  }
28418
28421
  };
28422
+ const getLines = points => points.map((point, i) => {
28423
+ const next = points[(i + 1) % points.length];
28424
+ return [point, next];
28425
+ });
28419
28426
 
28420
28427
  const defaultStroke = "var(--movable-line-stroke-color)";
28421
28428
  const MovableLine = props => {
@@ -28676,12 +28683,55 @@ const RayGraph = props => {
28676
28683
  }));
28677
28684
  };
28678
28685
 
28686
+ /**
28687
+ * Wrapper with Mafs transform styles for SVG elements.
28688
+ * Scales and translates the SVG element to match the Mafs view.
28689
+ * Use if you do not need/want to use the hook in `use-transform.ts`.
28690
+ */
28691
+ const MafsCssTransformWrapper = _ref => {
28692
+ let {
28693
+ children
28694
+ } = _ref;
28695
+ return /*#__PURE__*/React__namespace.createElement("g", {
28696
+ style: {
28697
+ transform: `var(--mafs-view-transform) var(--mafs-user-transform)`
28698
+ }
28699
+ }, children);
28700
+ };
28701
+
28702
+ const TextLabel = _ref => {
28703
+ let {
28704
+ children,
28705
+ ...rest
28706
+ } = _ref;
28707
+ return /*#__PURE__*/React__default["default"].createElement(mafs.Text, _extends({
28708
+ size: 16,
28709
+ svgTextProps: {
28710
+ filter: "url(#background)",
28711
+ fontWeight: "bold"
28712
+ }
28713
+ }, rest), children);
28714
+ };
28715
+ const SvgDefs = () => /*#__PURE__*/React__default["default"].createElement("defs", null, /*#__PURE__*/React__default["default"].createElement("filter", {
28716
+ id: "background",
28717
+ x: "-5%",
28718
+ width: "110%",
28719
+ y: "0%",
28720
+ height: "100%"
28721
+ }, /*#__PURE__*/React__default["default"].createElement("feFlood", {
28722
+ floodColor: "#FFF",
28723
+ floodOpacity: "0.64"
28724
+ }), /*#__PURE__*/React__default["default"].createElement("feComposite", {
28725
+ operator: "over",
28726
+ in: "SourceGraphic"
28727
+ })));
28728
+
28679
28729
  const Angle = _ref => {
28680
28730
  let {
28681
28731
  centerPoint,
28682
28732
  endPoints,
28683
28733
  range,
28684
- polygonPoints,
28734
+ polygonLines,
28685
28735
  showAngles
28686
28736
  } = _ref;
28687
28737
  const [centerX, centerY] = centerPoint;
@@ -28703,7 +28753,7 @@ const Angle = _ref => {
28703
28753
  // Used to calculate the inside of the angle and to create square
28704
28754
  const [x3, y3] = mafs.vec.add(centerPoint, mafs.vec.add(mafs.vec.sub([x1, y1], centerPoint), mafs.vec.sub([x2, y2], centerPoint)));
28705
28755
  if (!showAngles) {
28706
- return isRightAngle(angle) ? /*#__PURE__*/React__namespace.createElement(RightAngleArc, {
28756
+ return isRightAngle(angle) ? /*#__PURE__*/React__namespace.createElement(RightAngleSquare, {
28707
28757
  start: [x1, y1],
28708
28758
  vertex: [x2, y2],
28709
28759
  end: [x3, y3]
@@ -28711,13 +28761,13 @@ const Angle = _ref => {
28711
28761
  }
28712
28762
 
28713
28763
  // Midpoint betwen ends of arc
28714
- const isInside = shouldDrawArcInside([x3, y3], centerPoint, range, polygonPoints);
28715
- const largeArcFlag = isInside ? 1 : 0;
28716
- const sweepFlag = isInside ? 1 : 0;
28764
+ const isOutside = shouldDrawArcOutside([x3, y3], centerPoint, range, polygonLines);
28765
+ const largeArcFlag = isOutside ? 1 : 0;
28766
+ const sweepFlag = isOutside ? 1 : 0;
28717
28767
  const arc = `M ${x1} ${y1} A ${radius} ${radius} 0 ${largeArcFlag} ${sweepFlag} ${x2} ${y2}`;
28718
28768
  let angleInDegrees = angle * (180 / Math.PI);
28719
28769
  // If we have triggered "largArcFlag", the angle should be greater than 180
28720
- if (isInside) {
28770
+ if (isOutside) {
28721
28771
  angleInDegrees = 360 - angleInDegrees;
28722
28772
  }
28723
28773
  const angleLabelNumber = parseFloat(angleInDegrees.toFixed(1));
@@ -28734,92 +28784,63 @@ const Angle = _ref => {
28734
28784
  }), /*#__PURE__*/React__namespace.createElement("feComposite", {
28735
28785
  operator: "over",
28736
28786
  in: "SourceGraphic"
28737
- }))), !isInside && isRightAngle(angle) ? /*#__PURE__*/React__namespace.createElement(RightAngleArc, {
28787
+ }))), !isOutside && isRightAngle(angle) ? /*#__PURE__*/React__namespace.createElement(RightAngleSquare, {
28738
28788
  start: [x1, y1],
28739
28789
  vertex: [x2, y2],
28740
28790
  end: [x3, y3]
28741
- }) : /*#__PURE__*/React__namespace.createElement("g", {
28742
- style: {
28743
- transform: `var(--mafs-view-transform) var(--mafs-user-transform)`
28744
- }
28745
- }, /*#__PURE__*/React__namespace.createElement("path", {
28791
+ }) : /*#__PURE__*/React__namespace.createElement(MafsCssTransformWrapper, null, /*#__PURE__*/React__namespace.createElement("path", {
28746
28792
  d: arc,
28747
28793
  strokeWidth: 0.02,
28748
28794
  fill: "none"
28749
- })), /*#__PURE__*/React__namespace.createElement(mafs.Text, {
28795
+ })), /*#__PURE__*/React__namespace.createElement(TextLabel, {
28750
28796
  x: x3,
28751
28797
  y: y3,
28752
- size: 15,
28753
- svgTextProps: {
28754
- filter: "url(#background)",
28755
- fontWeight: "bold"
28756
- }
28757
- // Shift position if text is too close to movable point
28758
- ,
28759
28798
  attach: y3 - centerY > 0 ? "s" : "n",
28760
28799
  attachDistance: Math.abs(y3 - centerY) < 0.2 || mafs.vec.dist([x3, y3], centerPoint) < 0.3 ? 20 : 10
28761
28800
  }, angleLabel, "\xB0"));
28762
28801
  };
28763
- const RightAngleArc = _ref2 => {
28802
+
28803
+ /**
28804
+ * This is broken out into its own component so that it can be used for an early return
28805
+ * (see line 55 or https://github.com/Khan/perseus/blob/84bbd882b11d16871e1b813a0b901f3b903d5479/packages/perseus/src/widgets/interactive-graphs/graphs/components/angle.tsx#L53-L57)
28806
+ */
28807
+ const RightAngleSquare = _ref2 => {
28764
28808
  let {
28765
28809
  start: [x1, y1],
28766
28810
  vertex: [x2, y2],
28767
28811
  end: [x3, y3]
28768
28812
  } = _ref2;
28769
- return /*#__PURE__*/React__namespace.createElement("g", {
28770
- style: {
28771
- transform: `var(--mafs-view-transform) var(--mafs-user-transform)`
28772
- }
28773
- }, /*#__PURE__*/React__namespace.createElement("path", {
28813
+ return /*#__PURE__*/React__namespace.createElement(MafsCssTransformWrapper, null, /*#__PURE__*/React__namespace.createElement("path", {
28774
28814
  d: `M ${x1} ${y1} L ${x3} ${y3} M ${x3} ${y3} L ${x2} ${y2}`,
28775
28815
  strokeWidth: 0.02,
28776
28816
  fill: "none"
28777
28817
  }));
28778
28818
  };
28779
28819
  const isRightAngle = angle => Math.abs(angle - Math.PI / 2) < 0.01;
28780
- const shouldDrawArcInside = (midPoint, vertex, range, polygonPoints) => {
28781
- const rangeIntersectionPoint = getRayIntersectionCoords(vertex, midPoint, range);
28782
- const lines = getLines(polygonPoints);
28783
- let lineIntersections = 0;
28784
- lines.forEach(line => linesIntersect([vertex, rangeIntersectionPoint], line) && lineIntersections++);
28785
-
28786
- // The intersection check will sometimes return false if it intersects with
28787
- // another vertex, so in the case we get 0 intersections, we check for points.
28788
- if (lineIntersections === 0) {
28789
- // find point in array
28790
- const midpointIndex = polygonPoints.findIndex(_ref3 => {
28791
- let [x, y] = _ref3;
28792
- return x === midPoint[0] && y === midPoint[1];
28793
- });
28794
- // get array sans point and adjacent points
28795
- const [prev, next] = [(midpointIndex - 1 + polygonPoints.length) % polygonPoints.length, (midpointIndex + 1) % polygonPoints.length];
28796
- const [[lineAx, lineAy], [lineBx, lineBy]] = [midPoint, rangeIntersectionPoint];
28797
- for (let i = 0; i < polygonPoints.length; i++) {
28798
- if ([prev, midpointIndex, next].includes(i)) {
28799
- continue;
28800
- }
28801
- const [x, y] = polygonPoints[i];
28802
- const intersectsPoint = (y - lineAy) * (lineBx - lineAx) === (lineBy - lineAy) * (x - lineAx);
28803
- if (intersectsPoint) {
28804
- return false;
28805
- }
28806
- }
28807
- return true;
28808
- }
28820
+
28821
+ /**
28822
+ * Determines if an angle is an inside (false) or outside (true) angle.
28823
+ * They way, we know to flip the `largeArc` and `sweepArc` flags.
28824
+ * Uses the priciple that a ray from a point inside a polygon will intersect
28825
+ * with an odd number of lines, while a ray from a point outside the polygon
28826
+ * will intersect with an even number of lines.
28827
+ * https://stackoverflow.com/questions/217578/how-can-i-determine-whether-a-2d-point-is-within-a-polygon
28828
+ */
28829
+ const shouldDrawArcOutside = (midpoint, vertex, range, polygonLines) => {
28830
+ // Create a ray from the midpoint (inside angle) to the edge of the range
28831
+ const rangeIntersectionPoint = getRayIntersectionCoords(midpoint, vertex, range);
28832
+ let lineIntersectionCount = 0;
28833
+ polygonLines.forEach(line => linesIntersect([vertex, rangeIntersectionPoint], line) && lineIntersectionCount++);
28809
28834
 
28810
28835
  // If the number of intersections is even, the angle is inside the polygon
28811
- return isEven(lineIntersections);
28836
+ return !isEven(lineIntersectionCount);
28812
28837
  };
28813
- const getLines = points => points.map((point, i) => {
28814
- const next = points[(i + 1) % points.length];
28815
- return [point, next];
28816
- });
28817
28838
 
28818
28839
  // https://stackoverflow.com/a/24392281/7347484
28819
28840
  // The "intersects" function in geometry doesn't seem to work for this use case
28820
- const linesIntersect = (_ref4, _ref5) => {
28821
- let [[a, b], [c, d]] = _ref4;
28822
- let [[p, q], [r, s]] = _ref5;
28841
+ const linesIntersect = (_ref3, _ref4) => {
28842
+ let [[a, b], [c, d]] = _ref3;
28843
+ let [[p, q], [r, s]] = _ref4;
28823
28844
  const determinant = (c - a) * (s - q) - (r - p) * (d - b);
28824
28845
  if (determinant === 0) {
28825
28846
  return false;
@@ -28841,6 +28862,7 @@ const PolygonGraph = props => {
28841
28862
  coords,
28842
28863
  type,
28843
28864
  showAngles,
28865
+ showSides,
28844
28866
  range
28845
28867
  } = props.graphState;
28846
28868
  const points = coords ?? [[0, 0]];
@@ -28859,6 +28881,7 @@ const PolygonGraph = props => {
28859
28881
  constrain: p => p
28860
28882
  });
28861
28883
  const active = hovered || focused || dragging;
28884
+ const lines = getLines(points);
28862
28885
  return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(mafs.Polygon, {
28863
28886
  points: [...points],
28864
28887
  color: "var(--movable-line-stroke-color)",
@@ -28877,9 +28900,18 @@ const PolygonGraph = props => {
28877
28900
  endPoints: [pt1, pt2],
28878
28901
  active: active,
28879
28902
  range: range,
28880
- polygonPoints: points,
28903
+ polygonLines: lines,
28881
28904
  showAngles: !!showAngles
28882
28905
  });
28906
+ }), showSides && lines.map((_ref, i) => {
28907
+ let [start, end] = _ref;
28908
+ const [x, y] = mafs.vec.midpoint(start, end);
28909
+ const length = parseFloat(mafs.vec.dist(start, end).toFixed(1));
28910
+ return /*#__PURE__*/React__namespace.createElement(TextLabel, {
28911
+ key: "side-" + i,
28912
+ x: x,
28913
+ y: y
28914
+ }, !Number.isInteger(length) && "≈ ", length);
28883
28915
  }), /*#__PURE__*/React__namespace.createElement(mafs.Polygon, {
28884
28916
  points: [...points],
28885
28917
  color: "transparent",
@@ -29897,31 +29929,41 @@ function initializeGraphState(params) {
29897
29929
  // Instead of updating to v5, using generic type to relate input and output types.
29898
29930
  function getGradableGraph(state, initialGraph) {
29899
29931
  if (!state.hasBeenInteractedWith) {
29900
- return initialGraph;
29932
+ return {
29933
+ type: initialGraph.type
29934
+ };
29901
29935
  }
29902
- switch (true) {
29903
- // coords: Array of CollinearTuple
29904
- case state.type === "linear-system" && initialGraph.type === "linear-system":
29905
- case state.type === "segment" && initialGraph.type === "segment":
29906
- return {
29907
- ...initialGraph,
29908
- coords: state.coords
29909
- };
29910
- // coords: CollinearTuple
29911
- case state.type === "linear" && initialGraph.type === "linear":
29912
- case state.type === "ray" && initialGraph.type === "ray":
29913
- return {
29914
- ...initialGraph,
29915
- coords: state.coords?.[0]
29916
- };
29917
- case state.type === "polygon" && initialGraph.type === "polygon":
29918
- return {
29919
- ...initialGraph,
29920
- coords: state.coords
29921
- };
29922
- default:
29923
- throw new Error("Mafs is not yet implemented for graph type: " + initialGraph.type);
29936
+ if (initialGraph.type === "linear-system" && state.type === "linear-system") {
29937
+ return {
29938
+ ...initialGraph,
29939
+ coords: state.coords
29940
+ };
29941
+ }
29942
+ if (state.type === "segment" && initialGraph.type === "segment") {
29943
+ return {
29944
+ ...initialGraph,
29945
+ coords: state.coords
29946
+ };
29947
+ }
29948
+ if (state.type === "linear" && initialGraph.type === "linear") {
29949
+ return {
29950
+ ...initialGraph,
29951
+ coords: state.coords?.[0]
29952
+ };
29953
+ }
29954
+ if (state.type === "ray" && initialGraph.type === "ray") {
29955
+ return {
29956
+ ...initialGraph,
29957
+ coords: state.coords?.[0]
29958
+ };
29959
+ }
29960
+ if (state.type === "polygon" && initialGraph.type === "polygon") {
29961
+ return {
29962
+ ...initialGraph,
29963
+ coords: state.coords
29964
+ };
29924
29965
  }
29966
+ throw new Error("Mafs is not yet implemented for graph type: " + initialGraph.type);
29925
29967
  }
29926
29968
  const getDefaultSegments = _ref => {
29927
29969
  let {
@@ -30061,7 +30103,7 @@ const MafsGraph = /*#__PURE__*/React__namespace.forwardRef((props, ref) => {
30061
30103
  zoom: false,
30062
30104
  width: width,
30063
30105
  height: height
30064
- }, /*#__PURE__*/React__namespace.createElement(Grid, props), props.lockedFigures && /*#__PURE__*/React__namespace.createElement(GraphLockedLayer, {
30106
+ }, /*#__PURE__*/React__namespace.createElement(SvgDefs, null), /*#__PURE__*/React__namespace.createElement(Grid, props), props.lockedFigures && /*#__PURE__*/React__namespace.createElement(GraphLockedLayer, {
30065
30107
  lockedFigures: props.lockedFigures
30066
30108
  }), renderGraph({
30067
30109
  state,
@@ -31687,18 +31729,18 @@ class InteractiveGraph extends React__namespace.Component {
31687
31729
  const angle = GraphUtils.findAngle(coords[2], coords[0], coords[1]);
31688
31730
  return angle.toFixed(0) + "\u00B0 angle" + " at (" + coords[1].join(", ") + ")";
31689
31731
  }
31690
- static validate(
31691
- // TODO(kevinb): rename state to userInput
31692
- // state === userInput
31693
- state, rubric, component) {
31732
+ static validate(userInput, rubric, component) {
31694
31733
  // When nothing has moved, there will neither be coords nor the
31695
31734
  // circle's center/radius fields. When those fields are absent, skip
31696
31735
  // all these checks; just go mark the answer as empty.
31697
- // @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'.
31698
- const hasValue = !!(state.coords || state.center && state.radius);
31699
- if (state.type === rubric.correct.type && hasValue) {
31700
- if (state.type === "linear" && rubric.correct.type === "linear" && state.coords != null) {
31701
- const guess = state.coords;
31736
+ const hasValue = Boolean(
31737
+ // @ts-expect-error - TS2339 - Property 'coords' does not exist on type 'PerseusGraphType'.
31738
+ userInput.coords ||
31739
+ // @ts-expect-error - TS2339 - Property 'center' does not exist on type 'PerseusGraphType'. | TS2339 - Property 'radius' does not exist on type 'PerseusGraphType'.
31740
+ userInput.center && userInput.radius);
31741
+ if (userInput.type === rubric.correct.type && hasValue) {
31742
+ if (userInput.type === "linear" && rubric.correct.type === "linear" && userInput.coords != null) {
31743
+ const guess = userInput.coords;
31702
31744
  const correct = rubric.correct.coords;
31703
31745
 
31704
31746
  // If both of the guess points are on the correct line, it's
@@ -31715,8 +31757,8 @@ class InteractiveGraph extends React__namespace.Component {
31715
31757
  message: null
31716
31758
  };
31717
31759
  }
31718
- } else if (state.type === "linear-system" && rubric.correct.type === "linear-system" && state.coords != null) {
31719
- const guess = state.coords;
31760
+ } else if (userInput.type === "linear-system" && rubric.correct.type === "linear-system" && userInput.coords != null) {
31761
+ const guess = userInput.coords;
31720
31762
  const correct = rubric.correct.coords;
31721
31763
  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])) {
31722
31764
  return {
@@ -31726,9 +31768,9 @@ class InteractiveGraph extends React__namespace.Component {
31726
31768
  message: null
31727
31769
  };
31728
31770
  }
31729
- } else if (state.type === "quadratic" && rubric.correct.type === "quadratic" && state.coords != null) {
31771
+ } else if (userInput.type === "quadratic" && rubric.correct.type === "quadratic" && userInput.coords != null) {
31730
31772
  // If the parabola coefficients match, it's correct.
31731
- const guessCoeffs = this.getQuadraticCoefficients(state.coords);
31773
+ const guessCoeffs = this.getQuadraticCoefficients(userInput.coords);
31732
31774
  const correctCoeffs = this.getQuadraticCoefficients(
31733
31775
  // @ts-expect-error - TS2345 - Argument of type 'readonly Coord[] | undefined' is not assignable to parameter of type 'readonly Coord[]'.
31734
31776
  rubric.correct.coords);
@@ -31740,8 +31782,8 @@ class InteractiveGraph extends React__namespace.Component {
31740
31782
  message: null
31741
31783
  };
31742
31784
  }
31743
- } else if (state.type === "sinusoid" && rubric.correct.type === "sinusoid" && state.coords != null) {
31744
- const guessCoeffs = this.getSinusoidCoefficients(state.coords);
31785
+ } else if (userInput.type === "sinusoid" && rubric.correct.type === "sinusoid" && userInput.coords != null) {
31786
+ const guessCoeffs = this.getSinusoidCoefficients(userInput.coords);
31745
31787
  const correctCoeffs = this.getSinusoidCoefficients(
31746
31788
  // @ts-expect-error - TS2345 - Argument of type 'readonly Coord[] | undefined' is not assignable to parameter of type 'readonly Coord[]'.
31747
31789
  rubric.correct.coords);
@@ -31756,8 +31798,8 @@ class InteractiveGraph extends React__namespace.Component {
31756
31798
  message: null
31757
31799
  };
31758
31800
  }
31759
- } else if (state.type === "circle" && rubric.correct.type === "circle") {
31760
- if (deepEq$1(state.center, rubric.correct.center) && eq(state.radius, rubric.correct.radius)) {
31801
+ } else if (userInput.type === "circle" && rubric.correct.type === "circle") {
31802
+ if (deepEq$1(userInput.center, rubric.correct.center) && eq(userInput.radius, rubric.correct.radius)) {
31761
31803
  return {
31762
31804
  type: "points",
31763
31805
  earned: 1,
@@ -31765,9 +31807,9 @@ class InteractiveGraph extends React__namespace.Component {
31765
31807
  message: null
31766
31808
  };
31767
31809
  }
31768
- } else if (state.type === "point" && rubric.correct.type === "point" && state.coords != null) {
31810
+ } else if (userInput.type === "point" && rubric.correct.type === "point" && userInput.coords != null) {
31769
31811
  let correct = InteractiveGraph.getPointCoords(rubric.correct, component);
31770
- const guess = state.coords?.slice();
31812
+ const guess = userInput.coords?.slice();
31771
31813
  correct = correct.slice();
31772
31814
  // Everything's already rounded so we shouldn't need to do an
31773
31815
  // eq() comparison but _.isEqual(0, -0) is false, so we'll use
@@ -31784,8 +31826,8 @@ class InteractiveGraph extends React__namespace.Component {
31784
31826
  message: null
31785
31827
  };
31786
31828
  }
31787
- } else if (state.type === "polygon" && rubric.correct.type === "polygon" && state.coords != null) {
31788
- const guess = state.coords?.slice();
31829
+ } else if (userInput.type === "polygon" && rubric.correct.type === "polygon" && userInput.coords != null) {
31830
+ const guess = userInput.coords?.slice();
31789
31831
  // @ts-expect-error - TS2322 - Type 'Coord[] | undefined' is not assignable to type 'Coord[]'.
31790
31832
  const correct = rubric.correct.coords?.slice();
31791
31833
  let match;
@@ -31809,8 +31851,8 @@ class InteractiveGraph extends React__namespace.Component {
31809
31851
  message: null
31810
31852
  };
31811
31853
  }
31812
- } else if (state.type === "segment" && rubric.correct.type === "segment" && state.coords != null) {
31813
- let guess = state.coords.slice();
31854
+ } else if (userInput.type === "segment" && rubric.correct.type === "segment" && userInput.coords != null) {
31855
+ let guess = userInput.coords.slice();
31814
31856
  let correct = rubric.correct.coords?.slice();
31815
31857
  guess = ___default["default"].invoke(guess, "sort").sort();
31816
31858
  // @ts-expect-error - TS2345 - Argument of type '(readonly Coord[])[] | undefined' is not assignable to parameter of type 'Collection<any>'.
@@ -31823,8 +31865,8 @@ class InteractiveGraph extends React__namespace.Component {
31823
31865
  message: null
31824
31866
  };
31825
31867
  }
31826
- } else if (state.type === "ray" && rubric.correct.type === "ray" && state.coords != null) {
31827
- const guess = state.coords;
31868
+ } else if (userInput.type === "ray" && rubric.correct.type === "ray" && userInput.coords != null) {
31869
+ const guess = userInput.coords;
31828
31870
  const correct = rubric.correct.coords;
31829
31871
  if (
31830
31872
  // @ts-expect-error - TS2532 - Object is possibly 'undefined'.
@@ -31838,8 +31880,8 @@ class InteractiveGraph extends React__namespace.Component {
31838
31880
  message: null
31839
31881
  };
31840
31882
  }
31841
- } else if (state.type === "angle" && rubric.correct.type === "angle") {
31842
- const guess = state.coords;
31883
+ } else if (userInput.type === "angle" && rubric.correct.type === "angle") {
31884
+ const guess = userInput.coords;
31843
31885
  const correct = rubric.correct.coords;
31844
31886
  let match;
31845
31887
  if (rubric.correct.match === "congruent") {
@@ -31878,7 +31920,7 @@ class InteractiveGraph extends React__namespace.Component {
31878
31920
 
31879
31921
  // The input wasn't correct, so check if it's a blank input or if it's
31880
31922
  // actually just wrong
31881
- if (!hasValue || ___default["default"].isEqual(state, rubric.graph)) {
31923
+ if (!hasValue || ___default["default"].isEqual(userInput, rubric.graph)) {
31882
31924
  // We're where we started.
31883
31925
  return {
31884
31926
  type: "invalid",
@@ -42073,7 +42115,7 @@ const init = function (options) {
42073
42115
 
42074
42116
  // This file is processed by a Rollup plugin (replace) to inject the production
42075
42117
  const libName = "@khanacademy/perseus";
42076
- const libVersion = "20.8.1";
42118
+ const libVersion = "21.0.0";
42077
42119
  perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
42078
42120
 
42079
42121
  const apiVersion = {
@@ -44322,6 +44364,138 @@ const registerAllWidgetsForTesting = () => {
44322
44364
  replaceDeprecatedWidgets();
44323
44365
  };
44324
44366
 
44367
+ // These are all the current widgets that require user-input and are supported for automatic scoring
44368
+ const QUESTION_WIDGETS = ["numeric-input", "input-number", "radio", "interactive-graph", "interactive-number-line", "categorizer", "plotter", "orderer", "protractor", "matcher", "sorter"];
44369
+
44370
+ /**
44371
+ * Regex for widget placeholders in a string.
44372
+ *
44373
+ * First capture group is the widget ID (ex. 'radio 1')
44374
+ * Second capture group is the widget type (ex. "radio)
44375
+ * exec return will look like: ['[[☃ radio 1]]', 'radio 1', 'radio']
44376
+ */
44377
+ function getWidgetRegex() {
44378
+ return /\[\[☃ ([A-Za-z0-9- ]+)\]\]/g;
44379
+ }
44380
+
44381
+ /**
44382
+ * Add a widget placeholder using the widget ID.
44383
+ * ex. addWidget("radio 1") => "[[☃ radio 1]]"
44384
+ *
44385
+ * @param {string} id
44386
+ * @returns {string}
44387
+ */
44388
+ function addWidget(id) {
44389
+ return `[[☃ ${id}]]`;
44390
+ }
44391
+
44392
+ /**
44393
+ * Get a widget type by a widget's ID
44394
+ * (please don't derive type from ID)
44395
+ *
44396
+ * @param {string} widgetId the ID of the widget
44397
+ * @param {PerseusWidgetsMap} widgetMap widget ID to widget map
44398
+ * @returns {string} the widget type (ie "radio")
44399
+ */
44400
+ function getWidgetTypeByWidgetId(WidgetId, widgetMap) {
44401
+ const widget = widgetMap[WidgetId];
44402
+ return widget?.type ?? null;
44403
+ }
44404
+
44405
+ /**
44406
+ * Does the content have a specific type of widget?
44407
+ *
44408
+ * @param {string} type the type of the widget in question (ie "radio")
44409
+ * @param {string} content the string to search through
44410
+ * @param {PerseusWidgetsMap} widgetMap widget ID to widget map
44411
+ * @returns {boolean} if the content includes the widget type
44412
+ */
44413
+ function contentHasWidgetType(type, content, widgetMap) {
44414
+ return getWidgetIdsFromContentByType(type, content, widgetMap).length > 0;
44415
+ }
44416
+
44417
+ /**
44418
+ * Extract all widget IDs, which includes the widget type and instance number.
44419
+ * example output: ['radio 1', 'categorizer 1', 'categorizor 2']
44420
+ *
44421
+ * Content should contain Perseus widget placeholders,
44422
+ * which look like: '[[☃ radio 1]]'.
44423
+ *
44424
+ * @param {string} content
44425
+ * @returns {ReadonlyArray<string>} widgetIds
44426
+ */
44427
+ function getWidgetIdsFromContent(content) {
44428
+ const widgets = [];
44429
+ const localWidgetRegex = getWidgetRegex();
44430
+ let match = localWidgetRegex.exec(content);
44431
+ while (match !== null) {
44432
+ widgets.push(match[1]);
44433
+ match = localWidgetRegex.exec(content);
44434
+ }
44435
+ return widgets;
44436
+ }
44437
+
44438
+ /**
44439
+ * Get a list of widget IDs from content,
44440
+ * but only for specific widget types
44441
+ *
44442
+ * @param {string} type the type of widget (ie "radio")
44443
+ * @param {string} content the string to parse
44444
+ * @param {PerseusWidgetsMap} widgetMap widget ID to widget map
44445
+ * @returns {ReadonlyArray<string>} the widget type (ie "radio")
44446
+ */
44447
+ function getWidgetIdsFromContentByType(type, content, widgetMap) {
44448
+ const rv = [];
44449
+ const widgetIdsInContent = getWidgetIdsFromContent(content);
44450
+ widgetIdsInContent.forEach(widgetId => {
44451
+ const widget = widgetMap[widgetId];
44452
+ if (widget?.type === type) {
44453
+ rv.push(widgetId);
44454
+ }
44455
+ });
44456
+ return rv;
44457
+ }
44458
+
44459
+ /**
44460
+ * Pull the widget map out of ItemData
44461
+ *
44462
+ * @param {PerseusItem} PerseusItem containing a widgetMap
44463
+ * @returns {WidgetMap} the widget map in the PerseusItem
44464
+ */
44465
+ function getWidgetsMapFromItemData(itemData) {
44466
+ return itemData.question.widgets;
44467
+ }
44468
+
44469
+ /**
44470
+ * Get the widget information from a WidgetMap
44471
+ *
44472
+ * @param {string} widgetId the ID of the widget
44473
+ * @returns {PerseusWidget | null} the widget data if it exists, otherwise null
44474
+ */
44475
+ function getWidgetFromWidgetMap(widgetId, widgetMap) {
44476
+ return widgetMap[widgetId] ?? null;
44477
+ }
44478
+
44479
+ /**
44480
+ * Get select widgets from a widget map.
44481
+ * Useful for multi-items that needs to split
44482
+ * one PerseusItem into several
44483
+ *
44484
+ * @param {ReadonlyArray<string>} widgetIds to extract
44485
+ * @param {PerseusWidgetsMap} widgetMap to extract from
44486
+ * @return {PerseusWidgetsMap} a new widget map with requested widgets
44487
+ */
44488
+ function getWidgetsFromWidgetMap(widgetIds, widgetMap) {
44489
+ const widgets = {};
44490
+ widgetIds.forEach(widgetId => {
44491
+ const widget = getWidgetFromWidgetMap(widgetId, widgetMap);
44492
+ if (widget) {
44493
+ widgets[widgetId] = widget;
44494
+ }
44495
+ });
44496
+ return widgets;
44497
+ }
44498
+
44325
44499
  /**
44326
44500
  * This function extracts the answers from the widgets.
44327
44501
  *
@@ -44726,18 +44900,15 @@ const INDIVIDUAL_ANSWER_WIDGETS = ["interactive-graph", "categorizer", "grapher"
44726
44900
 
44727
44901
  /* Widgets that are supported for automatic scoring */
44728
44902
  const SUPPORTED_WIDGETS = ["radio", "numeric-input", "input-number", "expression", ...INDIVIDUAL_ANSWER_WIDGETS];
44729
- const getWidgetTypeFromWidgetKey = widgetKey => {
44730
- return widgetKey.split(" ")[0];
44731
- };
44732
44903
 
44733
44904
  /* Verify if the perseus item has supported widgets for automatic scoring */
44734
- const isWrongAnswerSupported = widgetKeys => {
44735
- return widgetKeys.length !== 0 && widgetKeys.every(widgetKey => SUPPORTED_WIDGETS.includes(getWidgetTypeFromWidgetKey(widgetKey)));
44905
+ const isWrongAnswerSupported = (widgetIds, widgetMap) => {
44906
+ return widgetIds.length !== 0 && widgetIds.every(widgetId => SUPPORTED_WIDGETS.includes(getWidgetTypeByWidgetId(widgetId, widgetMap)));
44736
44907
  };
44737
44908
 
44738
- /* Verify if the widget key has an individual answer for the coach report view */
44739
- const shouldHaveIndividualAnswer = widgetKey => {
44740
- return INDIVIDUAL_ANSWER_WIDGETS.includes(getWidgetTypeFromWidgetKey(widgetKey));
44909
+ /* Verify if the widget ID has an individual answer for the coach report view */
44910
+ const shouldHaveIndividualAnswer = (widgetId, widgetMap) => {
44911
+ return INDIVIDUAL_ANSWER_WIDGETS.includes(getWidgetTypeByWidgetId(widgetId, widgetMap));
44741
44912
  };
44742
44913
 
44743
44914
  /* Returns the answer userInput submission for currently supported widgets */
@@ -44756,29 +44927,30 @@ const getAnswerFromUserInput = (widgetType, userInput) => {
44756
44927
  return userInput;
44757
44928
  };
44758
44929
 
44759
- /* Returns the correct answer for a given widget key and Perseus Item */
44930
+ /* Returns the correct answer for a given widget ID and Perseus Item */
44760
44931
  // TODO (LEMS-1835): We should fix the resonse type from getWidget to be specific.
44761
44932
  // TODO (LEMS-1836): We should also consider adding the getOneCorrectAnswerFromRubric method to all widgets.
44762
- const getCorrectAnswerForWidgetKey = (widgetKey, itemData) => {
44763
- const rubric = itemData.question.widgets[widgetKey].options;
44764
- const widgetType = getWidgetTypeFromWidgetKey(widgetKey);
44933
+ const getCorrectAnswerForWidgetId = (widgetId, itemData) => {
44934
+ const rubric = itemData.question.widgets[widgetId].options;
44935
+ const widgetMap = getWidgetsMapFromItemData(itemData);
44936
+ const widgetType = getWidgetTypeByWidgetId(widgetId, widgetMap);
44765
44937
  const widget = getWidget(widgetType);
44766
44938
 
44767
44939
  // @ts-expect-error - TS2339 - Property 'getOneCorrectAnswerFromRubric' does not exist on type 'ComponentType<any>'.
44768
44940
  return widget?.getOneCorrectAnswerFromRubric?.(rubric);
44769
44941
  };
44770
44942
 
44771
- /* Verify if the widget key exists in the content string of the Perseus Item */
44772
- const isWidgetKeyInContent = (perseusItem, widgetKey) => {
44773
- return perseusItem.question.content.indexOf(widgetKey) !== -1;
44943
+ /* Verify if the widget ID exists in the content string of the Perseus Item */
44944
+ const isWidgetIdInContent = (perseusItem, widgetId) => {
44945
+ return perseusItem.question.content.indexOf(widgetId) !== -1;
44774
44946
  };
44775
44947
 
44776
- /* Return an array of all the widget keys that exist in the content string of a Perseus Item */
44777
- const getValidWidgetKeys = perseusItem => {
44948
+ /* Return an array of all the widget IDs that exist in the content string of a Perseus Item */
44949
+ const getValidWidgetIds = perseusItem => {
44778
44950
  const {
44779
44951
  widgets
44780
44952
  } = perseusItem.question;
44781
- return wonderStuffCore.keys(widgets).filter(key => isWidgetKeyInContent(perseusItem, key));
44953
+ return wonderStuffCore.keys(widgets).filter(id => isWidgetIdInContent(perseusItem, id));
44782
44954
  };
44783
44955
 
44784
44956
  /**
@@ -44826,7 +44998,7 @@ const genericPerseusItemData = {
44826
44998
  * uses a basic Perseus object and updates it with custom values as needed.
44827
44999
  *
44828
45000
  * @param {Partial<PerseusItem>} customFields
44829
- * @returns PerseusItem
45001
+ * @returns {PerseusItem}
44830
45002
  */
44831
45003
  function generateTestPerseusItem() {
44832
45004
  let customFields = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
@@ -44836,6 +45008,107 @@ function generateTestPerseusItem() {
44836
45008
  };
44837
45009
  }
44838
45010
 
45011
+ /**
45012
+ * Creates an object with the bare amount
45013
+ * of data to be a properly typed RadioWidget
45014
+ *
45015
+ * @returns {RadioWidget} skeleton RadioWidget for testing
45016
+ */
45017
+ function generateTestRadioWidget() {
45018
+ return {
45019
+ type: "radio",
45020
+ options: {
45021
+ choices: []
45022
+ }
45023
+ };
45024
+ }
45025
+
45026
+ /**
45027
+ * Creates an object with the bare amount
45028
+ * of data to be a properly typed InteractiveGraphWidget
45029
+ *
45030
+ * @returns {InteractiveGraphWidget} skeleton InteractiveGraphWidget for testing
45031
+ */
45032
+ function generateTestInteractiveGraphWidget() {
45033
+ return {
45034
+ type: "interactive-graph",
45035
+ options: {
45036
+ step: [1, 1],
45037
+ gridStep: [1, 1],
45038
+ snapStep: [1, 1],
45039
+ markings: "graph",
45040
+ labels: ["x", "y"],
45041
+ showProtractor: false,
45042
+ showRuler: false,
45043
+ rulerLabel: "mm",
45044
+ rulerTicks: 2,
45045
+ range: [[-10, 10], [-10, 10]],
45046
+ graph: {
45047
+ type: "angle"
45048
+ },
45049
+ correct: {
45050
+ type: "angle"
45051
+ }
45052
+ }
45053
+ };
45054
+ }
45055
+
45056
+ /**
45057
+ * Creates an object with the bare amount
45058
+ * of data to be a properly typed InteractiveGraphWidget
45059
+ *
45060
+ * @returns {InteractiveGraphWidget} skeleton InteractiveGraphWidget for testing
45061
+ */
45062
+ function generateTestCategorizerWidget() {
45063
+ return {
45064
+ type: "categorizer",
45065
+ options: {
45066
+ items: [],
45067
+ categories: [],
45068
+ randomizeItems: false,
45069
+ static: false,
45070
+ values: []
45071
+ }
45072
+ };
45073
+ }
45074
+
45075
+ /**
45076
+ * Creates an object with the bare amount
45077
+ * of data to be a properly typed ExpressionWidget
45078
+ *
45079
+ * @returns {ExpressionWidget} skeleton ExpressionWidget for testing
45080
+ */
45081
+ function generateTestExpressionWidget() {
45082
+ return {
45083
+ type: "expression",
45084
+ options: {
45085
+ answerForms: [],
45086
+ buttonSets: ["basic"],
45087
+ functions: [],
45088
+ times: false
45089
+ }
45090
+ };
45091
+ }
45092
+
45093
+ /**
45094
+ * Creates an object with the bare amount
45095
+ * of data to be a properly typed NumericInputWidget
45096
+ *
45097
+ * @returns {NumericInputWidget} skeleton NumericInputWidget for testing
45098
+ */
45099
+ function generateTestNumericInputWidget() {
45100
+ return {
45101
+ type: "numeric-input",
45102
+ options: {
45103
+ answers: [],
45104
+ labelText: "",
45105
+ size: "normal",
45106
+ coefficient: false,
45107
+ static: false
45108
+ }
45109
+ };
45110
+ }
45111
+
44839
45112
  /**
44840
45113
  * Some helpers for CEDAR logging. CEDAR convention dictates
44841
45114
  * that enum values should be upper case. Ex: for the
@@ -44890,162 +45163,6 @@ function convertWidgetNameToEnum(name) {
44890
45163
  }
44891
45164
  return widgetEnum;
44892
45165
  }
44893
- function getWidgetTypeFromWidgetId(widgetId) {
44894
- const [widgetType, widgetNumber] = widgetId.split(" ");
44895
- if (!widgetNumber) {
44896
- throw new Error(`Incorrect format for widget ID: ${widgetId}`);
44897
- }
44898
- if (!widgetNameToEnum[widgetType]) {
44899
- throw new Error(`Unknown widget type (${widgetType}) in widget ID: ${widgetId}`);
44900
- }
44901
- return widgetType;
44902
- }
44903
-
44904
- let WidgetType = /*#__PURE__*/function (WidgetType) {
44905
- WidgetType["Categorizer"] = "categorizer";
44906
- WidgetType["CSProgram"] = "cs-program";
44907
- WidgetType["Definition"] = "definition";
44908
- WidgetType["Dropdown"] = "dropdown";
44909
- WidgetType["Explanation"] = "explanation";
44910
- WidgetType["Expression"] = "expression";
44911
- WidgetType["GradedGroup"] = "graded-group";
44912
- WidgetType["GradedGroupAnswerBar"] = "graded-group-answer-bar";
44913
- WidgetType["GradedGroupSet"] = "graded-group-set";
44914
- WidgetType["Grapher"] = "grapher";
44915
- WidgetType["Group"] = "group";
44916
- WidgetType["Iframe"] = "iframe";
44917
- WidgetType["Image"] = "image";
44918
- WidgetType["InputNumber"] = "input-number";
44919
- WidgetType["Interaction"] = "interaction";
44920
- WidgetType["InteractiveGraph"] = "interactive-graph";
44921
- WidgetType["InteractiveNumberLine"] = "interactive-number-line";
44922
- WidgetType["LabelImage"] = "label-image";
44923
- WidgetType["Matcher"] = "matcher";
44924
- WidgetType["Matrix"] = "matrix";
44925
- WidgetType["Measurer"] = "measurer";
44926
- WidgetType["Molecule"] = "molecule";
44927
- WidgetType["NumberLine"] = "number-line";
44928
- WidgetType["NumericInput"] = "numeric-input";
44929
- WidgetType["Orderer"] = "orderer";
44930
- WidgetType["Passage"] = "passage";
44931
- WidgetType["PassageRef"] = "passage-ref";
44932
- WidgetType["PassageRefTarget"] = "passage-ref-target";
44933
- WidgetType["Plotter"] = "plotter";
44934
- WidgetType["Protractor"] = "protractor";
44935
- WidgetType["PythonProgram"] = "python-program";
44936
- WidgetType["Radio"] = "radio";
44937
- WidgetType["Sorter"] = "sorter";
44938
- WidgetType["Table"] = "table";
44939
- WidgetType["Unit"] = "unit";
44940
- WidgetType["Video"] = "video";
44941
- WidgetType["VideoTranscriptLink"] = "video-transcript-link";
44942
- return WidgetType;
44943
- }({});
44944
- // These are all the current widgets that require user-input and are supported for automatic scoring
44945
- const QUESTION_WIDGETS = ["numeric-input", "input-number", "radio", "interactive-graph", "interactive-number-line", "categorizer", "plotter", "orderer", "protractor", "matcher", "sorter"];
44946
-
44947
- /**
44948
- * Regex for widget placeholders in a string.
44949
- *
44950
- * First capture group is the widget ID (ex. 'radio 1')
44951
- * Second capture group is the widget type (ex. "radio)
44952
- * exec return will look like: ['[[☃ radio 1]]', 'radio 1', 'radio']
44953
- */
44954
- function getWidgetRegex() {
44955
- return /\[\[☃ (([a-z-]+) \d+)\]\]/g;
44956
- }
44957
-
44958
- /**
44959
- * Add a widget placeholder using the widget ID.
44960
- * ex. addWidget("radio 1") => "[[☃ radio 1]]"
44961
- *
44962
- * @param {string} id
44963
- * @returns {string}
44964
- */
44965
- function addWidget(id) {
44966
- return `[[☃ ${id}]]`;
44967
- }
44968
-
44969
- /**
44970
- * Extract all widget IDs, which includes the widget type and instance number.
44971
- * example output: ['radio 1', 'categorizer 1', 'categorizor 2']
44972
- *
44973
- * Content should contain Perseus widget placeholders,
44974
- * which look like: '[[☃ radio 1]]'.
44975
- *
44976
- * @param {string} content
44977
- * @returns {Array<WidgetId>} widgets
44978
- */
44979
- function getAllWidgetIds(content) {
44980
- const widgets = [];
44981
- const localWidgetRegex = getWidgetRegex();
44982
- let match = localWidgetRegex.exec(content);
44983
- while (match !== null) {
44984
- widgets.push(match[1]);
44985
- match = localWidgetRegex.exec(content);
44986
- }
44987
- return widgets;
44988
- }
44989
-
44990
- /**
44991
- * Check if a specific widget is an actual widget type using its name.
44992
- * The widget name should include a widget type, without an instance number,
44993
- * as a string. ex. 'radio'
44994
- *
44995
- * @param {string} widgetName
44996
- * @returns {boolean}
44997
- */
44998
- function isWidgetType(widgetName) {
44999
- return Object.values(WidgetType).includes(widgetName);
45000
- }
45001
-
45002
- /**
45003
- * Extract all widget types from a Perseus JSON content string.
45004
- * This does not include the instance number and prevents duplicates.
45005
- * example output: ['radio', 'categorizer']
45006
- *
45007
- * @param {string} content
45008
- * @returns {Array<WidgetId>}
45009
- */
45010
- function getAllWidgetTypes(content) {
45011
- const widgetTypes = [];
45012
- const localWidgetRegex = getWidgetRegex();
45013
- let match = localWidgetRegex.exec(content);
45014
- while (match !== null) {
45015
- const matchType = match[2];
45016
- if (!widgetTypes.includes(matchType) && isWidgetType(matchType)) {
45017
- widgetTypes.push(matchType);
45018
- }
45019
- match = localWidgetRegex.exec(content);
45020
- }
45021
- return widgetTypes;
45022
- }
45023
-
45024
- /**
45025
- * Check if a specific widget is a question widget type using its ID.
45026
- * The widget ID includes the widget type and the instance number.
45027
- *
45028
- * @param {WidgetId} widgetId
45029
- * @returns {boolean}
45030
- */
45031
- function isQuestionWidgetType(widgetId) {
45032
- return QUESTION_WIDGETS.includes(widgetId.split(" ")[0]);
45033
- }
45034
-
45035
- /**
45036
- * Extract the widget IDs of all widgets considered question widgets. Widget IDs
45037
- * include widget type and instance number.
45038
- * example output: ['radio 1', 'categorizer 1']
45039
- *
45040
- * Content should contain Perseus widget placeholders,
45041
- * which look like: '[[☃ radio 1]]'.
45042
- *
45043
- * @param {string} content
45044
- * @returns Array<WidgetId>
45045
- */
45046
- function getQuestionWidgetIds(content) {
45047
- return getAllWidgetIds(content).filter(isQuestionWidgetType);
45048
- }
45049
45166
 
45050
45167
  const EditorJsonify = {
45051
45168
  serialize: function () {
@@ -45097,7 +45214,6 @@ exports.TableWidget = Table$1;
45097
45214
  exports.Util = Util;
45098
45215
  exports.WIDGET_PROP_DENYLIST = denylist;
45099
45216
  exports.WidgetJsonifyDeprecated = WidgetJsonifyDeprecated;
45100
- exports.WidgetType = WidgetType;
45101
45217
  exports.Widgets = widgets$1;
45102
45218
  exports.addWidget = addWidget;
45103
45219
  exports.apiVersion = apiVersion;
@@ -45105,19 +45221,26 @@ exports.buildEmptyItemTreeForShape = buildEmptyItemTreeForShape;
45105
45221
  exports.buildMapper = buildMapper;
45106
45222
  exports.components = components;
45107
45223
  exports.constants = constants;
45224
+ exports.contentHasWidgetType = contentHasWidgetType;
45108
45225
  exports.convertWidgetNameToEnum = convertWidgetNameToEnum;
45109
45226
  exports.displaySigFigs = displaySigFigs;
45227
+ exports.generateTestCategorizerWidget = generateTestCategorizerWidget;
45228
+ exports.generateTestExpressionWidget = generateTestExpressionWidget;
45229
+ exports.generateTestInteractiveGraphWidget = generateTestInteractiveGraphWidget;
45230
+ exports.generateTestNumericInputWidget = generateTestNumericInputWidget;
45110
45231
  exports.generateTestPerseusItem = generateTestPerseusItem;
45232
+ exports.generateTestRadioWidget = generateTestRadioWidget;
45111
45233
  exports.genericPerseusItemData = genericPerseusItemData;
45112
- exports.getAllWidgetIds = getAllWidgetIds;
45113
- exports.getAllWidgetTypes = getAllWidgetTypes;
45114
45234
  exports.getAnswerFromUserInput = getAnswerFromUserInput;
45115
45235
  exports.getAnswersFromWidgets = getAnswersFromWidgets;
45116
- exports.getCorrectAnswerForWidgetKey = getCorrectAnswerForWidgetKey;
45117
- exports.getQuestionWidgetIds = getQuestionWidgetIds;
45118
- exports.getValidWidgetKeys = getValidWidgetKeys;
45119
- exports.getWidgetTypeFromWidgetId = getWidgetTypeFromWidgetId;
45120
- exports.getWidgetTypeFromWidgetKey = getWidgetTypeFromWidgetKey;
45236
+ exports.getCorrectAnswerForWidgetId = getCorrectAnswerForWidgetId;
45237
+ exports.getValidWidgetIds = getValidWidgetIds;
45238
+ exports.getWidgetFromWidgetMap = getWidgetFromWidgetMap;
45239
+ exports.getWidgetIdsFromContent = getWidgetIdsFromContent;
45240
+ exports.getWidgetIdsFromContentByType = getWidgetIdsFromContentByType;
45241
+ exports.getWidgetTypeByWidgetId = getWidgetTypeByWidgetId;
45242
+ exports.getWidgetsFromWidgetMap = getWidgetsFromWidgetMap;
45243
+ exports.getWidgetsMapFromItemData = getWidgetsMapFromItemData;
45121
45244
  exports.globalConstants = globalConstants;
45122
45245
  exports.globalStyles = globalStyles;
45123
45246
  exports.icons = iconPaths;
@@ -45125,7 +45248,7 @@ exports.init = init;
45125
45248
  exports.injectWidgets = injectWidgets;
45126
45249
  exports.interactiveSizes = interactiveSizes$1;
45127
45250
  exports.isItemRenderableByVersion = isItemRenderableByVersion;
45128
- exports.isWidgetKeyInContent = isWidgetKeyInContent;
45251
+ exports.isWidgetIdInContent = isWidgetIdInContent;
45129
45252
  exports.isWrongAnswerSupported = isWrongAnswerSupported;
45130
45253
  exports.itemDataVersion = itemDataVersion;
45131
45254
  exports.itemToTree = itemToTree;