@khanacademy/perseus 41.5.0 → 42.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.
Files changed (71) hide show
  1. package/dist/es/index.js +325 -268
  2. package/dist/es/index.js.map +1 -1
  3. package/dist/es/strings.js +3 -2
  4. package/dist/es/strings.js.map +1 -1
  5. package/dist/index.d.ts +3 -1
  6. package/dist/index.js +322 -263
  7. package/dist/index.js.map +1 -1
  8. package/dist/renderer-util.d.ts +3 -2
  9. package/dist/renderer.d.ts +7 -10
  10. package/dist/server-item-renderer.d.ts +33 -2
  11. package/dist/strings.d.ts +2 -1
  12. package/dist/strings.js +3 -2
  13. package/dist/strings.js.map +1 -1
  14. package/dist/types.d.ts +6 -1
  15. package/dist/util/parse-perseus-json/exhaustive-test-tool/index.d.ts +2 -0
  16. package/dist/widget-ai-utils/graded-group/{prompt-utils.d.ts → graded-group-ai-utils.d.ts} +1 -1
  17. package/dist/widget-ai-utils/graded-group-set/{prompt-utils.d.ts → graded-group-set-ai-utils.d.ts} +1 -1
  18. package/dist/widget-ai-utils/group/{prompt-utils.d.ts → group-ai-utils.d.ts} +1 -1
  19. package/dist/widget-ai-utils/prompt-types.d.ts +33 -0
  20. package/dist/widget-ai-utils/unsupported-widget.d.ts +1 -1
  21. package/dist/widgets/categorizer/categorizer.d.ts +1 -1
  22. package/dist/widgets/definition/definition.d.ts +1 -1
  23. package/dist/widgets/dropdown/dropdown.d.ts +1 -1
  24. package/dist/widgets/explanation/explanation.d.ts +1 -1
  25. package/dist/widgets/expression/expression.d.ts +1 -1
  26. package/dist/widgets/graded-group/graded-group.d.ts +1 -1
  27. package/dist/widgets/graded-group-set/graded-group-set.d.ts +1 -1
  28. package/dist/widgets/grapher/grapher.d.ts +1 -1
  29. package/dist/widgets/group/group.d.ts +1 -1
  30. package/dist/widgets/image/image.d.ts +1 -1
  31. package/dist/widgets/input-number/input-number.d.ts +1 -1
  32. package/dist/widgets/interactive-graph.d.ts +1 -1
  33. package/dist/widgets/interactive-graphs/graphs/components/movable-point.d.ts +9 -0
  34. package/dist/widgets/interactive-graphs/graphs/components/use-control-point.d.ts +9 -0
  35. package/dist/widgets/label-image/label-image.d.ts +1 -1
  36. package/dist/widgets/matcher/matcher.d.ts +1 -1
  37. package/dist/widgets/matrix/matrix.d.ts +1 -1
  38. package/dist/widgets/number-line/number-line.d.ts +1 -1
  39. package/dist/widgets/orderer/orderer.d.ts +1 -1
  40. package/dist/widgets/passage/passage.d.ts +1 -1
  41. package/dist/widgets/passage-ref/passage-ref.d.ts +1 -1
  42. package/dist/widgets/radio/radio-component.d.ts +1 -1
  43. package/dist/widgets/sorter/sorter.d.ts +1 -1
  44. package/package.json +1 -1
  45. package/dist/prompt-types.d.ts +0 -33
  46. /package/dist/widget-ai-utils/categorizer/{prompt-utils.d.ts → categorizer-ai-utils.d.ts} +0 -0
  47. /package/dist/widget-ai-utils/cs-program/{prompt-utils.d.ts → cs-program-ai-utils.d.ts} +0 -0
  48. /package/dist/widget-ai-utils/definition/{prompt-utils.d.ts → definition-ai-utils.d.ts} +0 -0
  49. /package/dist/widget-ai-utils/dropdown/{prompt-utils.d.ts → dropdown-ai-utils.d.ts} +0 -0
  50. /package/dist/widget-ai-utils/explanation/{prompt-utils.d.ts → explanation-ai-utils.d.ts} +0 -0
  51. /package/dist/widget-ai-utils/expression/{prompt-utils.d.ts → expression-ai-utils.d.ts} +0 -0
  52. /package/dist/widget-ai-utils/grapher/{prompt-utils.d.ts → grapher-ai-utils.d.ts} +0 -0
  53. /package/dist/widget-ai-utils/iframe/{prompt-utils.d.ts → iframe-ai-utils.d.ts} +0 -0
  54. /package/dist/widget-ai-utils/image/{prompt-utils.d.ts → image-ai-utils.d.ts} +0 -0
  55. /package/dist/widget-ai-utils/input-number/{prompt-utils.d.ts → input-number-ai-utils.d.ts} +0 -0
  56. /package/dist/widget-ai-utils/interaction/{prompt-utils.d.ts → interaction-ai-utils.d.ts} +0 -0
  57. /package/dist/widget-ai-utils/interactive-graph/{prompt-utils.d.ts → interactive-graph-ai-utils.d.ts} +0 -0
  58. /package/dist/widget-ai-utils/label-image/{prompt-utils.d.ts → label-image-ai-utils.d.ts} +0 -0
  59. /package/dist/widget-ai-utils/matcher/{prompt-utils.d.ts → matcher-ai-utils.d.ts} +0 -0
  60. /package/dist/widget-ai-utils/matrix/{prompt-utils.d.ts → matrix-ai-utils.d.ts} +0 -0
  61. /package/dist/widget-ai-utils/measurer/{prompt-utils.d.ts → measurer-ai-utils.d.ts} +0 -0
  62. /package/dist/widget-ai-utils/number-line/{prompt-utils.d.ts → number-line-ai-utils.d.ts} +0 -0
  63. /package/dist/widget-ai-utils/orderer/{prompt-utils.d.ts → orderer-ai-utils.d.ts} +0 -0
  64. /package/dist/widget-ai-utils/passage/{prompt-utils.d.ts → passage-ai-utils.d.ts} +0 -0
  65. /package/dist/widget-ai-utils/passage-ref/{prompt-utils.d.ts → passage-ref-ai-utils.d.ts} +0 -0
  66. /package/dist/widget-ai-utils/phet-simulation/{prompt-utils.d.ts → phet-simulation-ai-utils.d.ts} +0 -0
  67. /package/dist/widget-ai-utils/plotter/{prompt-utils.d.ts → plotter-ai-utils.d.ts} +0 -0
  68. /package/dist/widget-ai-utils/python-program/{prompt-utils.d.ts → python-ai-utils.d.ts} +0 -0
  69. /package/dist/widget-ai-utils/radio/{prompt-utils.d.ts → radio-ai-utils.d.ts} +0 -0
  70. /package/dist/widget-ai-utils/sorter/{prompt-utils.d.ts → sorter-ai-utils.d.ts} +0 -0
  71. /package/dist/widget-ai-utils/video/{prompt-utils.d.ts → video-ai-utils.d.ts} +0 -0
package/dist/es/index.js CHANGED
@@ -241,9 +241,10 @@ const mockStrings = {
241
241
  removePoint: "Remove Point",
242
242
  graphKeyboardPrompt: "Press Shift + Enter to interact with the graph",
243
243
  srPointAtCoordinates: ({
244
+ num,
244
245
  x,
245
246
  y
246
- }) => `Point at ${x} comma ${y}`,
247
+ }) => `Point ${num} at ${x} comma ${y}`,
247
248
  srInteractiveElements: ({
248
249
  elements
249
250
  }) => `Interactive elements: ${elements}`,
@@ -18673,6 +18674,140 @@ class QuestionParagraph extends React.Component {
18673
18674
  }
18674
18675
  }
18675
18676
 
18677
+ // These are all the current widgets that require user-input and are supported for automatic scoring
18678
+ const QUESTION_WIDGETS = ["numeric-input", "input-number", "radio", "interactive-graph", "interactive-number-line", "categorizer", "plotter", "orderer", "protractor", "matcher", "sorter"];
18679
+
18680
+ /**
18681
+ * Regex for widget placeholders in a string.
18682
+ *
18683
+ * First capture group is the widget ID (ex. 'radio 1')
18684
+ * Second capture group is the widget type (ex. "radio)
18685
+ * exec return will look like: ['[[☃ radio 1]]', 'radio 1', 'radio']
18686
+ */
18687
+ function getWidgetRegex() {
18688
+ return /\[\[☃ ([A-Za-z0-9- ]+)\]\]/g;
18689
+ }
18690
+
18691
+ /**
18692
+ * Add a widget placeholder using the widget ID.
18693
+ * ex. addWidget("radio 1") => "[[☃ radio 1]]"
18694
+ *
18695
+ * @param {string} id
18696
+ * @returns {string}
18697
+ */
18698
+ function addWidget(id) {
18699
+ return `[[☃ ${id}]]`;
18700
+ }
18701
+
18702
+ /**
18703
+ * Get a widget type by a widget's ID
18704
+ * (please don't derive type from ID)
18705
+ *
18706
+ * @param {string} widgetId the ID of the widget
18707
+ * @param {PerseusWidgetsMap} widgetMap widget ID to widget map
18708
+ * @returns {string} the widget type (ie "radio")
18709
+ */
18710
+ function getWidgetTypeByWidgetId(WidgetId, widgetMap) {
18711
+ var _widget$type;
18712
+ const widget = widgetMap[WidgetId];
18713
+ return (_widget$type = widget == null ? void 0 : widget.type) != null ? _widget$type : null;
18714
+ }
18715
+
18716
+ /**
18717
+ * Does the content have a specific type of widget?
18718
+ *
18719
+ * @param {string} type the type of the widget in question (ie "radio")
18720
+ * @param {string} content the string to search through
18721
+ * @param {PerseusWidgetsMap} widgetMap widget ID to widget map
18722
+ * @returns {boolean} if the content includes the widget type
18723
+ */
18724
+ function contentHasWidgetType(type, content, widgetMap) {
18725
+ return getWidgetIdsFromContentByType(type, content, widgetMap).length > 0;
18726
+ }
18727
+
18728
+ /**
18729
+ * Extract all widget IDs, which includes the widget type and instance number.
18730
+ * example output: ['radio 1', 'categorizer 1', 'categorizor 2']
18731
+ *
18732
+ * Content should contain Perseus widget placeholders,
18733
+ * which look like: '[[☃ radio 1]]'.
18734
+ *
18735
+ * @param {string} content
18736
+ * @returns {ReadonlyArray<string>} widgetIds
18737
+ */
18738
+ function getWidgetIdsFromContent(content) {
18739
+ const widgets = [];
18740
+ const localWidgetRegex = getWidgetRegex();
18741
+ let match = localWidgetRegex.exec(content);
18742
+ while (match !== null) {
18743
+ widgets.push(match[1]);
18744
+ match = localWidgetRegex.exec(content);
18745
+ }
18746
+ return widgets;
18747
+ }
18748
+
18749
+ /**
18750
+ * Get a list of widget IDs from content,
18751
+ * but only for specific widget types
18752
+ *
18753
+ * @param {string} type the type of widget (ie "radio")
18754
+ * @param {string} content the string to parse
18755
+ * @param {PerseusWidgetsMap} widgetMap widget ID to widget map
18756
+ * @returns {ReadonlyArray<string>} the widget type (ie "radio")
18757
+ */
18758
+ function getWidgetIdsFromContentByType(type, content, widgetMap) {
18759
+ const rv = [];
18760
+ const widgetIdsInContent = getWidgetIdsFromContent(content);
18761
+ widgetIdsInContent.forEach(widgetId => {
18762
+ const widget = widgetMap[widgetId];
18763
+ if ((widget == null ? void 0 : widget.type) === type) {
18764
+ rv.push(widgetId);
18765
+ }
18766
+ });
18767
+ return rv;
18768
+ }
18769
+
18770
+ /**
18771
+ * Pull the widget map out of ItemData
18772
+ *
18773
+ * @param {PerseusItem} PerseusItem containing a widgetMap
18774
+ * @returns {WidgetMap} the widget map in the PerseusItem
18775
+ */
18776
+ function getWidgetsMapFromItemData(itemData) {
18777
+ return itemData.question.widgets;
18778
+ }
18779
+
18780
+ /**
18781
+ * Get the widget information from a WidgetMap
18782
+ *
18783
+ * @param {string} widgetId the ID of the widget
18784
+ * @returns {PerseusWidget | null} the widget data if it exists, otherwise null
18785
+ */
18786
+ function getWidgetFromWidgetMap(widgetId, widgetMap) {
18787
+ var _widgetMap$widgetId;
18788
+ return (_widgetMap$widgetId = widgetMap[widgetId]) != null ? _widgetMap$widgetId : null;
18789
+ }
18790
+
18791
+ /**
18792
+ * Get select widgets from a widget map.
18793
+ * Useful for multi-items that needs to split
18794
+ * one PerseusItem into several
18795
+ *
18796
+ * @param {ReadonlyArray<string>} widgetIds to extract
18797
+ * @param {PerseusWidgetsMap} widgetMap to extract from
18798
+ * @return {PerseusWidgetsMap} a new widget map with requested widgets
18799
+ */
18800
+ function getWidgetsFromWidgetMap(widgetIds, widgetMap) {
18801
+ const widgets = {};
18802
+ widgetIds.forEach(widgetId => {
18803
+ const widget = getWidgetFromWidgetMap(widgetId, widgetMap);
18804
+ if (widget) {
18805
+ widgets[widgetId] = widget;
18806
+ }
18807
+ });
18808
+ return widgets;
18809
+ }
18810
+
18676
18811
  const DEFAULT_ALIGNMENT$1 = "block";
18677
18812
  // NOTE(kevinb): "default" is not one in `validAlignments`.
18678
18813
  const DEFAULT_SUPPORTED_ALIGNMENTS = ["default"];
@@ -19134,6 +19269,20 @@ widgetIds, userInputMap, strings, locale) {
19134
19269
  }
19135
19270
  });
19136
19271
  }
19272
+
19273
+ // TODO: combine scorePerseusItem with scoreWidgetsFunctional
19274
+ // once scorePerseusItem is the only one calling scoreWidgetsFunctional
19275
+ function scorePerseusItem(perseusRenderData, userInputMap,
19276
+ // TODO(LEMS-2461,LEMS-2391): these probably
19277
+ // need to be removed before we move this to the server
19278
+ strings, locale) {
19279
+ // There seems to be a chance that PerseusRenderer.widgets might include
19280
+ // widget data for widgets that are not in PerseusRenderer.content,
19281
+ // so this checks that the widgets are being used before scoring them
19282
+ const usedWidgetIds = getWidgetIdsFromContent(perseusRenderData.content);
19283
+ const scores = scoreWidgetsFunctional(perseusRenderData.widgets, usedWidgetIds, userInputMap, strings, locale);
19284
+ return Util.flattenScores(scores);
19285
+ }
19137
19286
  function scoreWidgetsFunctional(widgets,
19138
19287
  // This is a port of old code, I'm not sure why
19139
19288
  // we need widgetIds vs the keys of the widgets object
@@ -19154,7 +19303,7 @@ widgetIds, userInputMap, strings, locale) {
19154
19303
  const userInput = userInputMap[id];
19155
19304
  const scorer = getWidgetScorer(widget.type);
19156
19305
  if (widget.type === "group") {
19157
- const scores = scoreWidgetsFunctional(widget.options.widgets, Object.keys(widget.options.widgets), userInputMap[id], strings, locale);
19306
+ const scores = scoreWidgetsFunctional(widget.options.widgets, getWidgetIdsFromContent(widget.options.content), userInputMap[id], strings, locale);
19158
19307
  widgetScores[id] = Util.flattenScores(scores);
19159
19308
  } else if (scorer) {
19160
19309
  widgetScores[id] = scorer(userInput, widget.options, strings, locale);
@@ -19986,7 +20135,7 @@ class Renderer extends React.Component {
19986
20135
  // Instead, we center them with css magic in styles.less
19987
20136
  // /cry(aria)
19988
20137
  this._foundTextNodes = true;
19989
- if (_.contains(this.widgetIds, node.id)) {
20138
+ if (this.widgetIds.includes(node.id)) {
19990
20139
  // We don't want to render a duplicate widget key/ref,
19991
20140
  // as this causes problems with react (for obvious
19992
20141
  // reasons). Instead we just notify the
@@ -20290,7 +20439,7 @@ class Renderer extends React.Component {
20290
20439
  };
20291
20440
  this.getInputPaths = () => {
20292
20441
  const inputPaths = [];
20293
- _.each(this.widgetIds, widgetId => {
20442
+ this.widgetIds.forEach(widgetId => {
20294
20443
  const widget = this.getWidgetInstance(widgetId);
20295
20444
  if (widget && widget.getInputPaths) {
20296
20445
  // Grab all input paths and add widgetID to the front
@@ -20298,7 +20447,7 @@ class Renderer extends React.Component {
20298
20447
  // Prefix paths with their widgetID and add to collective
20299
20448
  // list of paths.
20300
20449
  // @ts-expect-error - TS2345 - Argument of type '(inputPath: string) => void' is not assignable to parameter of type 'CollectionIterator<FocusPath, void, readonly FocusPath[]>'.
20301
- _.each(widgetInputPaths, inputPath => {
20450
+ widgetInputPaths.forEach(inputPath => {
20302
20451
  const relativeInputPath = [widgetId].concat(inputPath);
20303
20452
  inputPaths.push(relativeInputPath);
20304
20453
  });
@@ -20427,12 +20576,20 @@ class Renderer extends React.Component {
20427
20576
  this.getWidgetIds = () => {
20428
20577
  return this.widgetIds;
20429
20578
  };
20579
+ /**
20580
+ * @deprecated use scorePerseusItem
20581
+ */
20582
+ this.guessAndScore = () => {
20583
+ const totalGuess = this.getUserInput();
20584
+ const totalScore = this.score();
20585
+ return [totalGuess, totalScore];
20586
+ };
20430
20587
  this.examples = () => {
20431
20588
  const widgetIds = this.widgetIds;
20432
- const examples = _.compact(_.map(widgetIds, widgetId => {
20589
+ const examples = widgetIds.map(widgetId => {
20433
20590
  const widget = this.getWidgetInstance(widgetId);
20434
20591
  return widget != null && widget.examples ? widget.examples() : null;
20435
- }));
20592
+ }).filter(Boolean);
20436
20593
 
20437
20594
  // no widgets with examples
20438
20595
  if (!examples.length) {
@@ -20687,27 +20844,15 @@ class Renderer extends React.Component {
20687
20844
  }
20688
20845
 
20689
20846
  /**
20690
- * Returns an object mapping from widget ID to perseus-style score.
20691
- * The keys of this object are the values of the array returned
20692
- * from `getWidgetIds`.
20693
- */
20694
- scoreWidgets() {
20695
- return scoreWidgetsFunctional(this.state.widgetInfo, this.widgetIds, this.getUserInputMap(), this.props.strings, this.context.locale);
20696
- }
20697
-
20698
- /**
20699
- * Grades the content.
20847
+ * Scores the content.
20848
+ *
20849
+ * @deprecated use scorePerseusItem
20700
20850
  */
20701
20851
  score() {
20702
- const scores = this.scoreWidgets();
20852
+ const scores = scoreWidgetsFunctional(this.state.widgetInfo, this.widgetIds, this.getUserInputMap(), this.props.strings, this.context.locale);
20703
20853
  const combinedScore = Util.flattenScores(scores);
20704
20854
  return combinedScore;
20705
20855
  }
20706
- guessAndScore() {
20707
- const totalGuess = this.getUserInput();
20708
- const totalScore = this.score();
20709
- return [totalGuess, totalScore];
20710
- }
20711
20856
  render() {
20712
20857
  const apiOptions = this.getApiOptions();
20713
20858
  if (this.reuseMarkdown) {
@@ -22247,7 +22392,9 @@ const propUpgrades$2 = {
22247
22392
  // input-number to numeric-input. In this case, we need to upgrade
22248
22393
  // the widget options accordingly.
22249
22394
  if (initialProps.value) {
22250
- const provideAnswerForm = initialProps.answerType !== "number";
22395
+ // If the answerType is not number or percent, we need to provide
22396
+ // the answer form for the numeric-input widget
22397
+ const provideAnswerForm = initialProps.answerType !== "number" && initialProps.answerType !== "percent";
22251
22398
 
22252
22399
  // We need to determine the mathFormat for the numeric-input widget
22253
22400
  const mathFormat = initialProps.answerType === "rational" ? "proper" // input-number uses "rational" for proper fractions
@@ -29255,6 +29402,9 @@ class Group extends React.Component {
29255
29402
  this.change("widgets", this.rendererRef.props.widgets);
29256
29403
  }
29257
29404
  };
29405
+
29406
+ // TODO(LEMS-2391): replace this when there's a separate check
29407
+ // for valid/invalid state
29258
29408
  const score = (_this$rendererRef12 = this.rendererRef) == null ? void 0 : _this$rendererRef12.score();
29259
29409
  const isValid = score && score.type !== "invalid";
29260
29410
  const isInvalid = score && score.type === "invalid";
@@ -31073,6 +31223,107 @@ function AxisArrows() {
31073
31223
  })));
31074
31224
  }
31075
31225
 
31226
+ /**
31227
+ * 44 is touch best practice and AAA compliant for WCAG
31228
+ * https://www.w3.org/WAI/WCAG21/Understanding/target-size.html
31229
+ */
31230
+ const TARGET_SIZE = 44;
31231
+ const REMOVE_BUTTON_ID = "perseus_mafs_remove_button";
31232
+
31233
+ // same as pointsFromNormalized in interactive-graph.tsx
31234
+ const normalizePoints = (range, step, coordsList, noSnap) => coordsList.map(coords =>
31235
+ // maps over [x, y]
31236
+ coords.map((coord, i) => {
31237
+ const axisRange = range[i];
31238
+ if (noSnap) {
31239
+ return axisRange[MIN] + size(axisRange) * coord;
31240
+ }
31241
+ const axisStep = step[i];
31242
+ const nSteps = Math.floor(size(axisRange) / axisStep);
31243
+ const tick = Math.round(coord * nSteps);
31244
+ return axisRange[MIN] + axisStep * tick;
31245
+ }));
31246
+
31247
+ // same as normalizeCoords in interactive-graph.tsx
31248
+ const normalizeCoords = (coordsList, ranges) => coordsList.map(coords => coords.map((coord, i) => {
31249
+ return (coord + ranges[i][1]) / size(ranges[i]);
31250
+ }));
31251
+
31252
+ // Returns the closest point to the given `point` that is within the graph
31253
+ // bounds given in `state`.
31254
+ function bound$1({
31255
+ snapStep,
31256
+ range,
31257
+ point
31258
+ }) {
31259
+ const boundingBox = inset(snapStep, range);
31260
+ return clampToBox(boundingBox, point);
31261
+ }
31262
+ function isUnlimitedGraphState(state) {
31263
+ return state.type === "point" && state.numPoints === "unlimited" || state.type === "polygon" && state.numSides === "unlimited";
31264
+ }
31265
+
31266
+ /**
31267
+ * Replace all text outside of the $ TeX blocks with `\\text{...}`
31268
+ * This way, the entire resulting string can be rendered within <TeX>
31269
+ * and the text outside of the $ blocks will be non-TeX text.
31270
+ */
31271
+ function replaceOutsideTeX(mathString) {
31272
+ // All the information we need is in the first section,
31273
+ // whether it's typed as "blockmath" or "paragraph"
31274
+ const firstSection = SimpleMarkdown.parse(mathString)[0];
31275
+
31276
+ // If it's blockMath, the outer level has the full math content.
31277
+ if (firstSection.type === "blockMath") {
31278
+ return firstSection.content;
31279
+ }
31280
+
31281
+ // If it's a paragraph, we need to iterate through the sections
31282
+ // to look for individual math blocks.
31283
+ const condensedNodes = condenseTextNodes(firstSection.content);
31284
+ let result = "";
31285
+ for (const piece of condensedNodes) {
31286
+ piece.type === "math" ? result += piece.content : result += `\\text{${escapeSpecialChars(piece.content)}}`;
31287
+ }
31288
+ return result;
31289
+ }
31290
+ // Helper function for replaceOutsideTeX()
31291
+ // Condense adjacent text nodes into a single text node
31292
+ function condenseTextNodes(nodes) {
31293
+ const result = [];
31294
+ if (!nodes) {
31295
+ return result;
31296
+ }
31297
+ let currentText = "";
31298
+ for (const node of nodes) {
31299
+ if (node.type === "math") {
31300
+ if (currentText) {
31301
+ result.push({
31302
+ type: "text",
31303
+ content: currentText
31304
+ });
31305
+ currentText = "";
31306
+ }
31307
+ result.push(node);
31308
+ } else {
31309
+ currentText += node.content;
31310
+ }
31311
+ }
31312
+ if (currentText) {
31313
+ result.push({
31314
+ type: "text",
31315
+ content: currentText
31316
+ });
31317
+ }
31318
+ return result;
31319
+ }
31320
+
31321
+ // Helper function for replaceOutsideTeX()
31322
+ function escapeSpecialChars(str) {
31323
+ // Escape $, \, {, and } characters
31324
+ return str.replace(/([$\\{}])/g, "\\$1");
31325
+ }
31326
+
31076
31327
  function AxisLabels() {
31077
31328
  const {
31078
31329
  range,
@@ -31104,7 +31355,7 @@ function AxisLabels() {
31104
31355
  fontSize: "14px",
31105
31356
  transform: "translate(7px, -50%)"
31106
31357
  }
31107
- }, /*#__PURE__*/React__default.createElement(TeX, null, xAxisLabelText)), /*#__PURE__*/React__default.createElement("span", {
31358
+ }, /*#__PURE__*/React__default.createElement(TeX, null, replaceOutsideTeX(xAxisLabelText))), /*#__PURE__*/React__default.createElement("span", {
31108
31359
  style: {
31109
31360
  position: "absolute",
31110
31361
  left: x2,
@@ -31112,7 +31363,7 @@ function AxisLabels() {
31112
31363
  fontSize: "14px",
31113
31364
  transform: "translate(-50%, 0px)"
31114
31365
  }
31115
- }, /*#__PURE__*/React__default.createElement(TeX, null, yAxisLabelText)));
31366
+ }, /*#__PURE__*/React__default.createElement(TeX, null, replaceOutsideTeX(yAxisLabelText))));
31116
31367
  }
31117
31368
 
31118
31369
  // The size of the ticks and labels in pixels
@@ -32046,107 +32297,6 @@ const lockedFigureFillStyles = {
32046
32297
 
32047
32298
  const plotterPlotTypes = ["bar", "line", "pic", "histogram", "dotplot"];
32048
32299
 
32049
- /**
32050
- * 44 is touch best practice and AAA compliant for WCAG
32051
- * https://www.w3.org/WAI/WCAG21/Understanding/target-size.html
32052
- */
32053
- const TARGET_SIZE = 44;
32054
- const REMOVE_BUTTON_ID = "perseus_mafs_remove_button";
32055
-
32056
- // same as pointsFromNormalized in interactive-graph.tsx
32057
- const normalizePoints = (range, step, coordsList, noSnap) => coordsList.map(coords =>
32058
- // maps over [x, y]
32059
- coords.map((coord, i) => {
32060
- const axisRange = range[i];
32061
- if (noSnap) {
32062
- return axisRange[MIN] + size(axisRange) * coord;
32063
- }
32064
- const axisStep = step[i];
32065
- const nSteps = Math.floor(size(axisRange) / axisStep);
32066
- const tick = Math.round(coord * nSteps);
32067
- return axisRange[MIN] + axisStep * tick;
32068
- }));
32069
-
32070
- // same as normalizeCoords in interactive-graph.tsx
32071
- const normalizeCoords = (coordsList, ranges) => coordsList.map(coords => coords.map((coord, i) => {
32072
- return (coord + ranges[i][1]) / size(ranges[i]);
32073
- }));
32074
-
32075
- // Returns the closest point to the given `point` that is within the graph
32076
- // bounds given in `state`.
32077
- function bound$1({
32078
- snapStep,
32079
- range,
32080
- point
32081
- }) {
32082
- const boundingBox = inset(snapStep, range);
32083
- return clampToBox(boundingBox, point);
32084
- }
32085
- function isUnlimitedGraphState(state) {
32086
- return state.type === "point" && state.numPoints === "unlimited" || state.type === "polygon" && state.numSides === "unlimited";
32087
- }
32088
-
32089
- /**
32090
- * Replace all text outside of the $ TeX blocks with `\\text{...}`
32091
- * This way, the entire resulting string can be rendered within <TeX>
32092
- * and the text outside of the $ blocks will be non-TeX text.
32093
- */
32094
- function replaceOutsideTeX(mathString) {
32095
- // All the information we need is in the first section,
32096
- // whether it's typed as "blockmath" or "paragraph"
32097
- const firstSection = SimpleMarkdown.parse(mathString)[0];
32098
-
32099
- // If it's blockMath, the outer level has the full math content.
32100
- if (firstSection.type === "blockMath") {
32101
- return firstSection.content;
32102
- }
32103
-
32104
- // If it's a paragraph, we need to iterate through the sections
32105
- // to look for individual math blocks.
32106
- const condensedNodes = condenseTextNodes(firstSection.content);
32107
- let result = "";
32108
- for (const piece of condensedNodes) {
32109
- piece.type === "math" ? result += piece.content : result += `\\text{${escapeSpecialChars(piece.content)}}`;
32110
- }
32111
- return result;
32112
- }
32113
- // Helper function for replaceOutsideTeX()
32114
- // Condense adjacent text nodes into a single text node
32115
- function condenseTextNodes(nodes) {
32116
- const result = [];
32117
- if (!nodes) {
32118
- return result;
32119
- }
32120
- let currentText = "";
32121
- for (const node of nodes) {
32122
- if (node.type === "math") {
32123
- if (currentText) {
32124
- result.push({
32125
- type: "text",
32126
- content: currentText
32127
- });
32128
- currentText = "";
32129
- }
32130
- result.push(node);
32131
- } else {
32132
- currentText += node.content;
32133
- }
32134
- }
32135
- if (currentText) {
32136
- result.push({
32137
- type: "text",
32138
- content: currentText
32139
- });
32140
- }
32141
- return result;
32142
- }
32143
-
32144
- // Helper function for replaceOutsideTeX()
32145
- function escapeSpecialChars(str) {
32146
- // Escape $, \, {, and } characters
32147
- return str.replace(/([$\\{}])/g, "\\$1");
32148
- }
32149
-
32150
32300
  function LockedLabel(props) {
32151
32301
  const {
32152
32302
  coord,
@@ -33369,6 +33519,7 @@ function useControlPoint(params) {
33369
33519
  } = useGraphConfig();
33370
33520
  const {
33371
33521
  point,
33522
+ sequenceNumber,
33372
33523
  color,
33373
33524
  cursor,
33374
33525
  constrain = p => snap(snapStep, p),
@@ -33409,6 +33560,7 @@ function useControlPoint(params) {
33409
33560
  ref: focusableHandleRef,
33410
33561
  role: "button",
33411
33562
  "aria-label": strings.srPointAtCoordinates({
33563
+ num: sequenceNumber,
33412
33564
  x: srFormatNumber(point[X], locale),
33413
33565
  y: srFormatNumber(point[Y], locale)
33414
33566
  })
@@ -33487,6 +33639,7 @@ const MovableLine = props => {
33487
33639
  focusableHandle: focusableHandle1
33488
33640
  } = useControlPoint({
33489
33641
  point: start,
33642
+ sequenceNumber: 1,
33490
33643
  color,
33491
33644
  onMove: p => onMovePoint(0, p)
33492
33645
  });
@@ -33495,6 +33648,7 @@ const MovableLine = props => {
33495
33648
  focusableHandle: focusableHandle2
33496
33649
  } = useControlPoint({
33497
33650
  point: end,
33651
+ sequenceNumber: 2,
33498
33652
  color,
33499
33653
  onMove: p => onMovePoint(1, p)
33500
33654
  });
@@ -33674,14 +33828,17 @@ function AngleGraph(props) {
33674
33828
  // Render the lines, angle, and then movable points
33675
33829
  return /*#__PURE__*/React.createElement(React.Fragment, null, svgLines, /*#__PURE__*/React.createElement(Angle, angleParams), /*#__PURE__*/React.createElement(MovablePoint$1, {
33676
33830
  point: coords[1],
33831
+ sequenceNumber: 1,
33677
33832
  constrain: p => p,
33678
33833
  onMove: destination => dispatch(actions.angle.movePoint(1, destination))
33679
33834
  }), /*#__PURE__*/React.createElement(MovablePoint$1, {
33680
33835
  point: coords[0],
33836
+ sequenceNumber: 2,
33681
33837
  constrain: getAngleSideConstraint(coords[0], coords[1], snapDegrees || 1),
33682
33838
  onMove: destination => dispatch(actions.angle.movePoint(0, destination))
33683
33839
  }), /*#__PURE__*/React.createElement(MovablePoint$1, {
33684
33840
  point: coords[2],
33841
+ sequenceNumber: 3,
33685
33842
  constrain: getAngleSideConstraint(coords[2], coords[1], snapDegrees || 1),
33686
33843
  onMove: destination => dispatch(actions.angle.movePoint(2, destination))
33687
33844
  }));
@@ -33835,6 +33992,7 @@ function CircleGraph(props) {
33835
33992
  onMove: c => dispatch(actions.circle.moveCenter(c))
33836
33993
  }), /*#__PURE__*/React.createElement(MovablePoint$1, {
33837
33994
  point: radiusPoint,
33995
+ sequenceNumber: 1,
33838
33996
  cursor: "ew-resize",
33839
33997
  onMove: newRadiusPoint => {
33840
33998
  dispatch(actions.circle.moveRadiusPoint(newRadiusPoint));
@@ -34010,6 +34168,7 @@ function LimitedPointGraph(props) {
34010
34168
  return /*#__PURE__*/React.createElement(React.Fragment, null, props.graphState.coords.map((point, i) => /*#__PURE__*/React.createElement(MovablePoint$1, {
34011
34169
  key: i,
34012
34170
  point: point,
34171
+ sequenceNumber: i + 1,
34013
34172
  onMove: destination => dispatch(actions.pointGraph.movePoint(i, destination))
34014
34173
  })));
34015
34174
  }
@@ -34052,6 +34211,7 @@ function UnlimitedPointGraph(props) {
34052
34211
  }), props.graphState.coords.map((point, i) => /*#__PURE__*/React.createElement(MovablePoint$1, {
34053
34212
  key: i,
34054
34213
  point: point,
34214
+ sequenceNumber: i + 1,
34055
34215
  onMove: destination => dispatch(actions.pointGraph.movePoint(i, destination)),
34056
34216
  ref: ref => {
34057
34217
  itemsRef.current[i] = ref;
@@ -34082,7 +34242,8 @@ function describePointGraph(state, i18n) {
34082
34242
  if (state.coords.length === 0) {
34083
34243
  return strings.srNoInteractiveElements;
34084
34244
  }
34085
- const pointDescriptions = state.coords.map(([x, y]) => strings.srPointAtCoordinates({
34245
+ const pointDescriptions = state.coords.map(([x, y], index) => strings.srPointAtCoordinates({
34246
+ num: index + 1,
34086
34247
  x: srFormatNumber(x, locale),
34087
34248
  y: srFormatNumber(y, locale)
34088
34249
  }));
@@ -34200,6 +34361,7 @@ const LimitedPolygonGraph = props => {
34200
34361
  key: "point-" + i,
34201
34362
  constrain: constrain,
34202
34363
  point: point,
34364
+ sequenceNumber: i + 1,
34203
34365
  onMove: destination => {
34204
34366
  const now = Date.now();
34205
34367
  const targetFPS = 40;
@@ -34346,6 +34508,7 @@ const UnlimitedPolygonGraph = props => {
34346
34508
  }), props.graphState.coords.map((point, i) => /*#__PURE__*/React.createElement(MovablePoint$1, {
34347
34509
  key: i,
34348
34510
  point: point,
34511
+ sequenceNumber: i + 1,
34349
34512
  onMove: destination => dispatch(actions.pointGraph.movePoint(i, destination)),
34350
34513
  ref: ref => {
34351
34514
  pointRef.current[i] = ref;
@@ -34421,6 +34584,7 @@ function QuadraticGraph(props) {
34421
34584
  }), coords.map((coord, i) => /*#__PURE__*/React.createElement(MovablePoint$1, {
34422
34585
  key: "point-" + i,
34423
34586
  point: coord,
34587
+ sequenceNumber: i + 1,
34424
34588
  onMove: destination => dispatch(actions.quadratic.movePoint(i, destination))
34425
34589
  })));
34426
34590
  }
@@ -34549,6 +34713,7 @@ function SinusoidGraph(props) {
34549
34713
  }), coords.map((coord, i) => /*#__PURE__*/React.createElement(MovablePoint$1, {
34550
34714
  key: "point-" + i,
34551
34715
  point: coord,
34716
+ sequenceNumber: i + 1,
34552
34717
  onMove: destination => dispatch(actions.sinusoid.movePoint(i, destination))
34553
34718
  })));
34554
34719
  }
@@ -44677,7 +44842,7 @@ const init = function init() {
44677
44842
 
44678
44843
  // This file is processed by a Rollup plugin (replace) to inject the production
44679
44844
  const libName = "@khanacademy/perseus";
44680
- const libVersion = "41.5.0";
44845
+ const libVersion = "42.0.0";
44681
44846
  addLibraryVersionToPerseusDebug(libName, libVersion);
44682
44847
 
44683
44848
  const apiVersion = {
@@ -45301,6 +45466,13 @@ class ServerItemRenderer extends React.Component {
45301
45466
  this.props.onRendered(true);
45302
45467
  }
45303
45468
  }
45469
+ if (this.props.score && this.props.score !== prevProps.score) {
45470
+ const emptyQuestionAreaWidgets = this.questionRenderer.emptyWidgets();
45471
+ this.setState({
45472
+ questionCompleted: this.props.score.correct,
45473
+ questionHighlightedWidgets: emptyQuestionAreaWidgets
45474
+ });
45475
+ }
45304
45476
  }
45305
45477
  componentWillUnmount() {
45306
45478
  if (this.blurTimeoutID != null) {
@@ -45418,18 +45590,37 @@ class ServerItemRenderer extends React.Component {
45418
45590
  return this.questionRenderer.getPromptJSON();
45419
45591
  }
45420
45592
 
45593
+ /**
45594
+ * Returns an array of the widget `.getUserInput()` results
45595
+ *
45596
+ * TODO: can we remove this? Seems to be just for backwards
45597
+ * compatibility with old Perseus Chrome logging
45598
+ * @deprecated use getUserInput
45599
+ */
45600
+ getUserInputLegacy() {
45601
+ return this.questionRenderer.getUserInput();
45602
+ }
45603
+
45604
+ /**
45605
+ * Returns an object of the widget `.getUserInput()` results
45606
+ */
45607
+ getUserInput() {
45608
+ return this.questionRenderer.getUserInputMap();
45609
+ }
45610
+
45421
45611
  /**
45422
45612
  * Grades the item.
45613
+ *
45614
+ * @deprecated use scorePerseusItem
45423
45615
  */
45424
45616
  scoreInput() {
45425
- const guessAndScore = this.questionRenderer.guessAndScore();
45426
- const guess = guessAndScore[0];
45427
- const score = guessAndScore[1];
45617
+ const guess = this.getUserInput();
45618
+ const score = scorePerseusItem(this.props.item.question, guess, this.context.strings, this.context.locale);
45428
45619
 
45429
45620
  // Continue to include an empty guess for the now defunct answer area.
45430
45621
  // TODO(alex): Check whether we rely on the format here for
45431
45622
  // analyzing ProblemLogs. If not, remove this layer.
45432
- const maxCompatGuess = [guess, []];
45623
+ const maxCompatGuess = [this.questionRenderer.getUserInput(), []];
45433
45624
  const keScore = Util.keScoreFromPerseusScore(score, maxCompatGuess, this.questionRenderer.getSerializedState());
45434
45625
  const emptyQuestionAreaWidgets = this.questionRenderer.emptyWidgets();
45435
45626
  this.setState({
@@ -47277,140 +47468,6 @@ const registerAllWidgetsForTesting = () => {
47277
47468
  replaceDeprecatedWidgets();
47278
47469
  };
47279
47470
 
47280
- // These are all the current widgets that require user-input and are supported for automatic scoring
47281
- const QUESTION_WIDGETS = ["numeric-input", "input-number", "radio", "interactive-graph", "interactive-number-line", "categorizer", "plotter", "orderer", "protractor", "matcher", "sorter"];
47282
-
47283
- /**
47284
- * Regex for widget placeholders in a string.
47285
- *
47286
- * First capture group is the widget ID (ex. 'radio 1')
47287
- * Second capture group is the widget type (ex. "radio)
47288
- * exec return will look like: ['[[☃ radio 1]]', 'radio 1', 'radio']
47289
- */
47290
- function getWidgetRegex() {
47291
- return /\[\[☃ ([A-Za-z0-9- ]+)\]\]/g;
47292
- }
47293
-
47294
- /**
47295
- * Add a widget placeholder using the widget ID.
47296
- * ex. addWidget("radio 1") => "[[☃ radio 1]]"
47297
- *
47298
- * @param {string} id
47299
- * @returns {string}
47300
- */
47301
- function addWidget(id) {
47302
- return `[[☃ ${id}]]`;
47303
- }
47304
-
47305
- /**
47306
- * Get a widget type by a widget's ID
47307
- * (please don't derive type from ID)
47308
- *
47309
- * @param {string} widgetId the ID of the widget
47310
- * @param {PerseusWidgetsMap} widgetMap widget ID to widget map
47311
- * @returns {string} the widget type (ie "radio")
47312
- */
47313
- function getWidgetTypeByWidgetId(WidgetId, widgetMap) {
47314
- var _widget$type;
47315
- const widget = widgetMap[WidgetId];
47316
- return (_widget$type = widget == null ? void 0 : widget.type) != null ? _widget$type : null;
47317
- }
47318
-
47319
- /**
47320
- * Does the content have a specific type of widget?
47321
- *
47322
- * @param {string} type the type of the widget in question (ie "radio")
47323
- * @param {string} content the string to search through
47324
- * @param {PerseusWidgetsMap} widgetMap widget ID to widget map
47325
- * @returns {boolean} if the content includes the widget type
47326
- */
47327
- function contentHasWidgetType(type, content, widgetMap) {
47328
- return getWidgetIdsFromContentByType(type, content, widgetMap).length > 0;
47329
- }
47330
-
47331
- /**
47332
- * Extract all widget IDs, which includes the widget type and instance number.
47333
- * example output: ['radio 1', 'categorizer 1', 'categorizor 2']
47334
- *
47335
- * Content should contain Perseus widget placeholders,
47336
- * which look like: '[[☃ radio 1]]'.
47337
- *
47338
- * @param {string} content
47339
- * @returns {ReadonlyArray<string>} widgetIds
47340
- */
47341
- function getWidgetIdsFromContent(content) {
47342
- const widgets = [];
47343
- const localWidgetRegex = getWidgetRegex();
47344
- let match = localWidgetRegex.exec(content);
47345
- while (match !== null) {
47346
- widgets.push(match[1]);
47347
- match = localWidgetRegex.exec(content);
47348
- }
47349
- return widgets;
47350
- }
47351
-
47352
- /**
47353
- * Get a list of widget IDs from content,
47354
- * but only for specific widget types
47355
- *
47356
- * @param {string} type the type of widget (ie "radio")
47357
- * @param {string} content the string to parse
47358
- * @param {PerseusWidgetsMap} widgetMap widget ID to widget map
47359
- * @returns {ReadonlyArray<string>} the widget type (ie "radio")
47360
- */
47361
- function getWidgetIdsFromContentByType(type, content, widgetMap) {
47362
- const rv = [];
47363
- const widgetIdsInContent = getWidgetIdsFromContent(content);
47364
- widgetIdsInContent.forEach(widgetId => {
47365
- const widget = widgetMap[widgetId];
47366
- if ((widget == null ? void 0 : widget.type) === type) {
47367
- rv.push(widgetId);
47368
- }
47369
- });
47370
- return rv;
47371
- }
47372
-
47373
- /**
47374
- * Pull the widget map out of ItemData
47375
- *
47376
- * @param {PerseusItem} PerseusItem containing a widgetMap
47377
- * @returns {WidgetMap} the widget map in the PerseusItem
47378
- */
47379
- function getWidgetsMapFromItemData(itemData) {
47380
- return itemData.question.widgets;
47381
- }
47382
-
47383
- /**
47384
- * Get the widget information from a WidgetMap
47385
- *
47386
- * @param {string} widgetId the ID of the widget
47387
- * @returns {PerseusWidget | null} the widget data if it exists, otherwise null
47388
- */
47389
- function getWidgetFromWidgetMap(widgetId, widgetMap) {
47390
- var _widgetMap$widgetId;
47391
- return (_widgetMap$widgetId = widgetMap[widgetId]) != null ? _widgetMap$widgetId : null;
47392
- }
47393
-
47394
- /**
47395
- * Get select widgets from a widget map.
47396
- * Useful for multi-items that needs to split
47397
- * one PerseusItem into several
47398
- *
47399
- * @param {ReadonlyArray<string>} widgetIds to extract
47400
- * @param {PerseusWidgetsMap} widgetMap to extract from
47401
- * @return {PerseusWidgetsMap} a new widget map with requested widgets
47402
- */
47403
- function getWidgetsFromWidgetMap(widgetIds, widgetMap) {
47404
- const widgets = {};
47405
- widgetIds.forEach(widgetId => {
47406
- const widget = getWidgetFromWidgetMap(widgetId, widgetMap);
47407
- if (widget) {
47408
- widgets[widgetId] = widget;
47409
- }
47410
- });
47411
- return widgets;
47412
- }
47413
-
47414
47471
  /**
47415
47472
  * This function extracts the answers from the widgets.
47416
47473
  *
@@ -49355,5 +49412,5 @@ const GrapherUtil = {
49355
49412
  typeToButton
49356
49413
  };
49357
49414
 
49358
- export { ApiOptions, ArticleRenderer, BaseRadio, Categorizer$1 as Categorizer, changeable as Changeable, ClassNames, dependencies as Dependencies, EditorJsonify, Expression, 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, PerseusExpressionAnswerFormConsidered, PerseusI18nContext, PerseusI18nContextProvider, PerseusMarkdown, Plotter$1 as PlotterWidget, QUESTION_WIDGETS, Radio, Renderer, ref as ServerItemRenderer, Table$1 as TableWidget, Util, denylist as WIDGET_PROP_DENYLIST, widgets$1 as Widgets, addWidget, apiVersion, bodyXsmallBold, buildEmptyItemTreeForShape, buildMapper, components, containerSizeClass, contentHasWidgetType, convertWidgetNameToEnum, displaySigFigs, generateTestCategorizerWidget, generateTestExpressionWidget, generateTestInteractiveGraphWidget, generateTestNumericInputWidget, generateTestPerseusItem, generateTestRadioWidget, genericPerseusItemData, getAngleCoords, getAnswerFromUserInput, getAnswersFromWidgets, getCircleCoords, getClockwiseAngle, getCorrectAnswerForWidgetId, getImagesWithoutAltData, getInteractiveBoxFromSizeClass, getLineCoords, getLinearSystemCoords, getPointCoords, getPolygonCoords, getQuadraticCoords, getSegmentCoords, getSinusoidCoords, getValidWidgetIds, getWidgetFromWidgetMap, getWidgetIdsFromContent, getWidgetIdsFromContentByType, getWidgetTypeByWidgetId, getWidgetsFromWidgetMap, getWidgetsMapFromItemData, iconChevronDown, iconTrash, init, injectWidgets, interactiveSizes$1 as interactiveSizes, isItemRenderableByVersion, isWidgetIdInContent, isWrongAnswerSupported, itemDataVersion, itemToTree, ItemVersion as itemVersion, libVersion, lockedFigureColors, lockedFigureFillStyles, makeSafeUrl, mediaQueries, parsePerseusItem, plotterPlotTypes, preprocessTex, registerAllWidgetsForTesting, shapes, shouldHaveIndividualAnswer, traverse, usePerseusI18n, violatingWidgets, allWidgets as widgets };
49415
+ export { ApiOptions, ArticleRenderer, BaseRadio, Categorizer$1 as Categorizer, changeable as Changeable, ClassNames, dependencies as Dependencies, EditorJsonify, Expression, 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, PerseusExpressionAnswerFormConsidered, PerseusI18nContext, PerseusI18nContextProvider, PerseusMarkdown, Plotter$1 as PlotterWidget, QUESTION_WIDGETS, Radio, Renderer, ref as ServerItemRenderer, Table$1 as TableWidget, Util, denylist as WIDGET_PROP_DENYLIST, widgets$1 as Widgets, addWidget, apiVersion, bodyXsmallBold, buildEmptyItemTreeForShape, buildMapper, components, containerSizeClass, contentHasWidgetType, convertWidgetNameToEnum, displaySigFigs, generateTestCategorizerWidget, generateTestExpressionWidget, generateTestInteractiveGraphWidget, generateTestNumericInputWidget, generateTestPerseusItem, generateTestRadioWidget, genericPerseusItemData, getAngleCoords, getAnswerFromUserInput, getAnswersFromWidgets, getCircleCoords, getClockwiseAngle, getCorrectAnswerForWidgetId, getImagesWithoutAltData, getInteractiveBoxFromSizeClass, getLineCoords, getLinearSystemCoords, getPointCoords, getPolygonCoords, getQuadraticCoords, getSegmentCoords, getSinusoidCoords, getValidWidgetIds, getWidgetFromWidgetMap, getWidgetIdsFromContent, getWidgetIdsFromContentByType, getWidgetTypeByWidgetId, getWidgetsFromWidgetMap, getWidgetsMapFromItemData, iconChevronDown, iconTrash, init, injectWidgets, interactiveSizes$1 as interactiveSizes, isItemRenderableByVersion, isWidgetIdInContent, isWrongAnswerSupported, itemDataVersion, itemToTree, ItemVersion as itemVersion, libVersion, lockedFigureColors, lockedFigureFillStyles, makeSafeUrl, mediaQueries, parsePerseusItem, plotterPlotTypes, preprocessTex, registerAllWidgetsForTesting, scorePerseusItem, shapes, shouldHaveIndividualAnswer, traverse, usePerseusI18n, violatingWidgets, allWidgets as widgets };
49359
49416
  //# sourceMappingURL=index.js.map