@khanacademy/perseus 41.4.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 (99) hide show
  1. package/dist/es/index.css +3 -0
  2. package/dist/es/index.js +879 -343
  3. package/dist/es/index.js.map +1 -1
  4. package/dist/es/strings.js +3 -2
  5. package/dist/es/strings.js.map +1 -1
  6. package/dist/index.css +3 -0
  7. package/dist/index.d.ts +6 -3
  8. package/dist/index.js +876 -338
  9. package/dist/index.js.map +1 -1
  10. package/dist/renderer-util.d.ts +3 -2
  11. package/dist/renderer.d.ts +7 -10
  12. package/dist/server-item-renderer.d.ts +34 -3
  13. package/dist/strings.d.ts +2 -1
  14. package/dist/strings.js +3 -2
  15. package/dist/strings.js.map +1 -1
  16. package/dist/types.d.ts +6 -1
  17. package/dist/util/parse-perseus-json/exhaustive-test-tool/index.d.ts +2 -0
  18. package/dist/util/parse-perseus-json/perseus-parsers/graded-group-set-widget.d.ts +3 -0
  19. package/dist/util/parse-perseus-json/perseus-parsers/graded-group-widget.d.ts +16 -0
  20. package/dist/util/parse-perseus-json/perseus-parsers/grapher-widget.d.ts +3 -0
  21. package/dist/util/parse-perseus-json/perseus-parsers/group-widget.d.ts +3 -0
  22. package/dist/util/parse-perseus-json/perseus-parsers/iframe-widget.d.ts +3 -0
  23. package/dist/util/parse-perseus-json/perseus-parsers/image-widget.d.ts +3 -0
  24. package/dist/util/parse-perseus-json/perseus-parsers/input-number-widget.d.ts +3 -0
  25. package/dist/util/parse-perseus-json/perseus-parsers/interaction-widget.d.ts +3 -0
  26. package/dist/util/parse-perseus-json/perseus-parsers/label-image-widget.d.ts +3 -0
  27. package/dist/util/parse-perseus-json/perseus-parsers/matcher-widget.d.ts +3 -0
  28. package/dist/util/parse-perseus-json/perseus-parsers/matrix-widget.d.ts +3 -0
  29. package/dist/util/parse-perseus-json/perseus-parsers/measurer-widget.d.ts +3 -0
  30. package/dist/util/parse-perseus-json/perseus-parsers/molecule-renderer-widget.d.ts +3 -0
  31. package/dist/util/parse-perseus-json/perseus-parsers/number-line-widget.d.ts +3 -0
  32. package/dist/util/parse-perseus-json/perseus-parsers/numeric-input-widget.d.ts +3 -0
  33. package/dist/util/parse-perseus-json/perseus-parsers/orderer-widget.d.ts +3 -0
  34. package/dist/util/parse-perseus-json/perseus-parsers/passage-ref-widget.d.ts +3 -0
  35. package/dist/util/parse-perseus-json/perseus-parsers/passage-widget.d.ts +3 -0
  36. package/dist/util/parse-perseus-json/perseus-parsers/phet-simulation-widget.d.ts +3 -0
  37. package/dist/util/parse-perseus-json/perseus-parsers/plotter-widget.d.ts +3 -0
  38. package/dist/util/parse-perseus-json/perseus-parsers/python-program-widget.d.ts +3 -0
  39. package/dist/util/parse-perseus-json/perseus-parsers/radio-widget.d.ts +3 -0
  40. package/dist/util/parse-perseus-json/perseus-parsers/sorter-widget.d.ts +3 -0
  41. package/dist/util/parse-perseus-json/perseus-parsers/table-widget.d.ts +3 -0
  42. package/dist/util/parse-perseus-json/perseus-parsers/video-widget.d.ts +3 -0
  43. package/dist/widget-ai-utils/graded-group/{prompt-utils.d.ts → graded-group-ai-utils.d.ts} +1 -1
  44. package/dist/widget-ai-utils/graded-group-set/{prompt-utils.d.ts → graded-group-set-ai-utils.d.ts} +1 -1
  45. package/dist/widget-ai-utils/group/{prompt-utils.d.ts → group-ai-utils.d.ts} +1 -1
  46. package/dist/widget-ai-utils/prompt-types.d.ts +33 -0
  47. package/dist/widget-ai-utils/unsupported-widget.d.ts +1 -1
  48. package/dist/widgets/categorizer/categorizer.d.ts +1 -1
  49. package/dist/widgets/definition/definition.d.ts +1 -1
  50. package/dist/widgets/dropdown/dropdown.d.ts +1 -1
  51. package/dist/widgets/explanation/explanation.d.ts +1 -1
  52. package/dist/widgets/expression/expression.d.ts +1 -1
  53. package/dist/widgets/graded-group/graded-group.d.ts +1 -1
  54. package/dist/widgets/graded-group-set/graded-group-set.d.ts +1 -1
  55. package/dist/widgets/grapher/grapher.d.ts +1 -1
  56. package/dist/widgets/group/group.d.ts +1 -1
  57. package/dist/widgets/image/image.d.ts +1 -1
  58. package/dist/widgets/input-number/input-number.d.ts +1 -1
  59. package/dist/widgets/interactive-graph.d.ts +1 -1
  60. package/dist/widgets/interactive-graphs/graphs/components/movable-point.d.ts +9 -0
  61. package/dist/widgets/interactive-graphs/graphs/components/use-control-point.d.ts +9 -0
  62. package/dist/widgets/label-image/label-image.d.ts +1 -1
  63. package/dist/widgets/matcher/matcher.d.ts +1 -1
  64. package/dist/widgets/matrix/matrix.d.ts +1 -1
  65. package/dist/widgets/number-line/number-line.d.ts +1 -1
  66. package/dist/widgets/numeric-input/numeric-input.d.ts +7 -0
  67. package/dist/widgets/orderer/orderer.d.ts +1 -1
  68. package/dist/widgets/passage/passage.d.ts +1 -1
  69. package/dist/widgets/passage-ref/passage-ref.d.ts +1 -1
  70. package/dist/widgets/radio/radio-component.d.ts +1 -1
  71. package/dist/widgets/sorter/sorter.d.ts +1 -1
  72. package/package.json +1 -1
  73. package/dist/prompt-types.d.ts +0 -33
  74. /package/dist/widget-ai-utils/categorizer/{prompt-utils.d.ts → categorizer-ai-utils.d.ts} +0 -0
  75. /package/dist/widget-ai-utils/cs-program/{prompt-utils.d.ts → cs-program-ai-utils.d.ts} +0 -0
  76. /package/dist/widget-ai-utils/definition/{prompt-utils.d.ts → definition-ai-utils.d.ts} +0 -0
  77. /package/dist/widget-ai-utils/dropdown/{prompt-utils.d.ts → dropdown-ai-utils.d.ts} +0 -0
  78. /package/dist/widget-ai-utils/explanation/{prompt-utils.d.ts → explanation-ai-utils.d.ts} +0 -0
  79. /package/dist/widget-ai-utils/expression/{prompt-utils.d.ts → expression-ai-utils.d.ts} +0 -0
  80. /package/dist/widget-ai-utils/grapher/{prompt-utils.d.ts → grapher-ai-utils.d.ts} +0 -0
  81. /package/dist/widget-ai-utils/iframe/{prompt-utils.d.ts → iframe-ai-utils.d.ts} +0 -0
  82. /package/dist/widget-ai-utils/image/{prompt-utils.d.ts → image-ai-utils.d.ts} +0 -0
  83. /package/dist/widget-ai-utils/input-number/{prompt-utils.d.ts → input-number-ai-utils.d.ts} +0 -0
  84. /package/dist/widget-ai-utils/interaction/{prompt-utils.d.ts → interaction-ai-utils.d.ts} +0 -0
  85. /package/dist/widget-ai-utils/interactive-graph/{prompt-utils.d.ts → interactive-graph-ai-utils.d.ts} +0 -0
  86. /package/dist/widget-ai-utils/label-image/{prompt-utils.d.ts → label-image-ai-utils.d.ts} +0 -0
  87. /package/dist/widget-ai-utils/matcher/{prompt-utils.d.ts → matcher-ai-utils.d.ts} +0 -0
  88. /package/dist/widget-ai-utils/matrix/{prompt-utils.d.ts → matrix-ai-utils.d.ts} +0 -0
  89. /package/dist/widget-ai-utils/measurer/{prompt-utils.d.ts → measurer-ai-utils.d.ts} +0 -0
  90. /package/dist/widget-ai-utils/number-line/{prompt-utils.d.ts → number-line-ai-utils.d.ts} +0 -0
  91. /package/dist/widget-ai-utils/orderer/{prompt-utils.d.ts → orderer-ai-utils.d.ts} +0 -0
  92. /package/dist/widget-ai-utils/passage/{prompt-utils.d.ts → passage-ai-utils.d.ts} +0 -0
  93. /package/dist/widget-ai-utils/passage-ref/{prompt-utils.d.ts → passage-ref-ai-utils.d.ts} +0 -0
  94. /package/dist/widget-ai-utils/phet-simulation/{prompt-utils.d.ts → phet-simulation-ai-utils.d.ts} +0 -0
  95. /package/dist/widget-ai-utils/plotter/{prompt-utils.d.ts → plotter-ai-utils.d.ts} +0 -0
  96. /package/dist/widget-ai-utils/python-program/{prompt-utils.d.ts → python-ai-utils.d.ts} +0 -0
  97. /package/dist/widget-ai-utils/radio/{prompt-utils.d.ts → radio-ai-utils.d.ts} +0 -0
  98. /package/dist/widget-ai-utils/sorter/{prompt-utils.d.ts → sorter-ai-utils.d.ts} +0 -0
  99. /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}`,
@@ -2591,7 +2592,7 @@ const keypadConfigurationForProps = widgetOptions => {
2591
2592
  times: widgetOptions.times
2592
2593
  };
2593
2594
  };
2594
- const propUpgrades$2 = {
2595
+ const propUpgrades$3 = {
2595
2596
  /* c8 ignore next */
2596
2597
  "1": v0props => ({
2597
2598
  times: v0props.times,
@@ -2651,7 +2652,7 @@ var Expression$1 = {
2651
2652
  major: 1,
2652
2653
  minor: 0
2653
2654
  },
2654
- propUpgrades: propUpgrades$2,
2655
+ propUpgrades: propUpgrades$3,
2655
2656
  // For use by the editor
2656
2657
  isLintable: true,
2657
2658
  scorer: scoreExpression,
@@ -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"];
@@ -18723,6 +18858,9 @@ const replaceDeprecatedWidgets = () => {
18723
18858
  replaceWidget("sequence", "deprecated-standin");
18724
18859
  replaceWidget("simulator", "deprecated-standin");
18725
18860
  replaceWidget("unit-input", "deprecated-standin");
18861
+
18862
+ // Input-Number is a special case as it is being replaced by Numeric-Input
18863
+ replaceWidget("input-number", "numeric-input");
18726
18864
  };
18727
18865
  const registerEditors = editorsToRegister => {
18728
18866
  editorsToRegister.forEach(editor => {
@@ -18757,6 +18895,11 @@ const replaceDeprecatedEditors = () => {
18757
18895
  replaceEditor("sequence", "deprecated-standin");
18758
18896
  replaceEditor("simulator", "deprecated-standin");
18759
18897
  replaceEditor("unit-input", "deprecated-standin");
18898
+
18899
+ // We're replacing the input-number editor with the numeric-input editor here,
18900
+ // but we're also modifying the JSON content to convert input-number into the
18901
+ // numeric-input format over in editor-page.tsx on line 92.
18902
+ replaceEditor("input-number", "numeric-input");
18760
18903
  };
18761
18904
  const getWidget = name => {
18762
18905
  var _widgets$name;
@@ -18807,10 +18950,8 @@ const getVersionVector = () => {
18807
18950
  const getPublicWidgets = () => {
18808
18951
  // TODO(alex): Update underscore.js so that _.pick can take a function.
18809
18952
  // @ts-expect-error - TS2740 - Type 'Pick<{ [key: string]: Readonly<{ name: string; displayName: string; getWidget?: (() => ComponentType<any>) | undefined; accessible?: boolean | ((props: any) => boolean) | undefined; hidden?: boolean | undefined; ... 10 more ...; widget: ComponentType<...>; }>; }, string>' is missing the following properties from type 'readonly Readonly<{ name: string; displayName: string; getWidget?: (() => ComponentType<any>) | undefined; accessible?: boolean | ((props: any) => boolean) | undefined; hidden?: boolean | undefined; ... 10 more ...; widget: ComponentType<...>; }>[]': length, concat, join, slice, and 18 more.
18810
- return _.pick(widgets,
18811
- // @ts-expect-error - TS2345 - Argument of type '(name: string) => boolean | undefined' is not assignable to parameter of type 'Iteratee<string[], boolean, string>'.
18812
- _.reject(_.keys(widgets), function (name) {
18813
- return widgets[name].hidden;
18953
+ return _.pick(widgets, _.reject(_.keys(widgets), function (name) {
18954
+ return widgets[name].hidden || name === "input-number";
18814
18955
  }));
18815
18956
  };
18816
18957
  const isAccessible = widgetInfo => {
@@ -19128,6 +19269,20 @@ widgetIds, userInputMap, strings, locale) {
19128
19269
  }
19129
19270
  });
19130
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
+ }
19131
19286
  function scoreWidgetsFunctional(widgets,
19132
19287
  // This is a port of old code, I'm not sure why
19133
19288
  // we need widgetIds vs the keys of the widgets object
@@ -19148,7 +19303,7 @@ widgetIds, userInputMap, strings, locale) {
19148
19303
  const userInput = userInputMap[id];
19149
19304
  const scorer = getWidgetScorer(widget.type);
19150
19305
  if (widget.type === "group") {
19151
- 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);
19152
19307
  widgetScores[id] = Util.flattenScores(scores);
19153
19308
  } else if (scorer) {
19154
19309
  widgetScores[id] = scorer(userInput, widget.options, strings, locale);
@@ -19980,7 +20135,7 @@ class Renderer extends React.Component {
19980
20135
  // Instead, we center them with css magic in styles.less
19981
20136
  // /cry(aria)
19982
20137
  this._foundTextNodes = true;
19983
- if (_.contains(this.widgetIds, node.id)) {
20138
+ if (this.widgetIds.includes(node.id)) {
19984
20139
  // We don't want to render a duplicate widget key/ref,
19985
20140
  // as this causes problems with react (for obvious
19986
20141
  // reasons). Instead we just notify the
@@ -20284,7 +20439,7 @@ class Renderer extends React.Component {
20284
20439
  };
20285
20440
  this.getInputPaths = () => {
20286
20441
  const inputPaths = [];
20287
- _.each(this.widgetIds, widgetId => {
20442
+ this.widgetIds.forEach(widgetId => {
20288
20443
  const widget = this.getWidgetInstance(widgetId);
20289
20444
  if (widget && widget.getInputPaths) {
20290
20445
  // Grab all input paths and add widgetID to the front
@@ -20292,7 +20447,7 @@ class Renderer extends React.Component {
20292
20447
  // Prefix paths with their widgetID and add to collective
20293
20448
  // list of paths.
20294
20449
  // @ts-expect-error - TS2345 - Argument of type '(inputPath: string) => void' is not assignable to parameter of type 'CollectionIterator<FocusPath, void, readonly FocusPath[]>'.
20295
- _.each(widgetInputPaths, inputPath => {
20450
+ widgetInputPaths.forEach(inputPath => {
20296
20451
  const relativeInputPath = [widgetId].concat(inputPath);
20297
20452
  inputPaths.push(relativeInputPath);
20298
20453
  });
@@ -20421,12 +20576,20 @@ class Renderer extends React.Component {
20421
20576
  this.getWidgetIds = () => {
20422
20577
  return this.widgetIds;
20423
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
+ };
20424
20587
  this.examples = () => {
20425
20588
  const widgetIds = this.widgetIds;
20426
- const examples = _.compact(_.map(widgetIds, widgetId => {
20589
+ const examples = widgetIds.map(widgetId => {
20427
20590
  const widget = this.getWidgetInstance(widgetId);
20428
20591
  return widget != null && widget.examples ? widget.examples() : null;
20429
- }));
20592
+ }).filter(Boolean);
20430
20593
 
20431
20594
  // no widgets with examples
20432
20595
  if (!examples.length) {
@@ -20681,27 +20844,15 @@ class Renderer extends React.Component {
20681
20844
  }
20682
20845
 
20683
20846
  /**
20684
- * Returns an object mapping from widget ID to perseus-style score.
20685
- * The keys of this object are the values of the array returned
20686
- * from `getWidgetIds`.
20687
- */
20688
- scoreWidgets() {
20689
- return scoreWidgetsFunctional(this.state.widgetInfo, this.widgetIds, this.getUserInputMap(), this.props.strings, this.context.locale);
20690
- }
20691
-
20692
- /**
20693
- * Grades the content.
20847
+ * Scores the content.
20848
+ *
20849
+ * @deprecated use scorePerseusItem
20694
20850
  */
20695
20851
  score() {
20696
- const scores = this.scoreWidgets();
20852
+ const scores = scoreWidgetsFunctional(this.state.widgetInfo, this.widgetIds, this.getUserInputMap(), this.props.strings, this.context.locale);
20697
20853
  const combinedScore = Util.flattenScores(scores);
20698
20854
  return combinedScore;
20699
20855
  }
20700
- guessAndScore() {
20701
- const totalGuess = this.getUserInput();
20702
- const totalScore = this.score();
20703
- return [totalGuess, totalScore];
20704
- }
20705
20856
  render() {
20706
20857
  const apiOptions = this.getApiOptions();
20707
20858
  if (this.reuseMarkdown) {
@@ -22150,7 +22301,7 @@ class NumericInput extends React.Component {
22150
22301
  height: "auto"
22151
22302
  }
22152
22303
  });
22153
- return /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement(InputWithExamples, {
22304
+ return /*#__PURE__*/React.createElement(InputWithExamples, {
22154
22305
  ref: ref => this.inputRef = ref,
22155
22306
  value: this.props.currentValue,
22156
22307
  onChange: this.handleChange,
@@ -22162,7 +22313,7 @@ class NumericInput extends React.Component {
22162
22313
  id: this.props.widgetId,
22163
22314
  disabled: this.props.apiOptions.readOnly,
22164
22315
  style: styles.input
22165
- }));
22316
+ });
22166
22317
  }
22167
22318
  }
22168
22319
 
@@ -22231,13 +22382,67 @@ const propsTransform = function propsTransform(widgetOptions) {
22231
22382
  });
22232
22383
  return rendererProps;
22233
22384
  };
22385
+
22386
+ // This function is being used to replace the input-number widget
22387
+ // with the numeric-input widget
22388
+ const propUpgrades$2 = {
22389
+ /* c8 ignore next */
22390
+ "1": initialProps => {
22391
+ // If the initialProps has a value, it means we're upgrading from
22392
+ // input-number to numeric-input. In this case, we need to upgrade
22393
+ // the widget options accordingly.
22394
+ if (initialProps.value) {
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";
22398
+
22399
+ // We need to determine the mathFormat for the numeric-input widget
22400
+ const mathFormat = initialProps.answerType === "rational" ? "proper" // input-number uses "rational" for proper fractions
22401
+ : initialProps.answerType; // Otherwise, we can use the answerType directly
22402
+
22403
+ // If adjusting this logic, also adjust the logic in the convertInputNumberWidgetOptions
22404
+ // function in input-number.ts in the Perseus Editor package's util folder
22405
+ const answers = [{
22406
+ value: initialProps.value,
22407
+ simplify: initialProps.simplify,
22408
+ answerForms: provideAnswerForm ? [mathFormat] : undefined,
22409
+ strict: initialProps.inexact,
22410
+ // We only want to set maxError if the inexact prop is true
22411
+ maxError: initialProps.inexact ? initialProps.maxError : 0,
22412
+ status: "correct",
22413
+ // Input-number only allows correct answers
22414
+ message: ""
22415
+ }];
22416
+ return {
22417
+ answers,
22418
+ size: initialProps.size,
22419
+ coefficient: false,
22420
+ // input-number doesn't have a coefficient prop
22421
+ labelText: "",
22422
+ // input-number doesn't have a labelText prop
22423
+ static: false,
22424
+ // static is always false for numeric-input
22425
+ rightAlign: initialProps.rightAlign || false
22426
+ };
22427
+ } else {
22428
+ // Otherwise simply return the initialProps as there's no differences
22429
+ // between v0 and v1 for numeric-input
22430
+ return initialProps;
22431
+ }
22432
+ }
22433
+ };
22234
22434
  var NumericInput$1 = {
22235
22435
  name: "numeric-input",
22236
22436
  displayName: "Numeric input",
22237
22437
  defaultAlignment: "inline-block",
22238
22438
  accessible: true,
22239
22439
  widget: NumericInput,
22440
+ version: {
22441
+ major: 1,
22442
+ minor: 0
22443
+ },
22240
22444
  transform: propsTransform,
22445
+ propUpgrades: propUpgrades$2,
22241
22446
  isLintable: true,
22242
22447
  scorer: scoreNumericInput,
22243
22448
  getOneCorrectAnswerFromRubric(rubric) {
@@ -29197,6 +29402,9 @@ class Group extends React.Component {
29197
29402
  this.change("widgets", this.rendererRef.props.widgets);
29198
29403
  }
29199
29404
  };
29405
+
29406
+ // TODO(LEMS-2391): replace this when there's a separate check
29407
+ // for valid/invalid state
29200
29408
  const score = (_this$rendererRef12 = this.rendererRef) == null ? void 0 : _this$rendererRef12.score();
29201
29409
  const isValid = score && score.type !== "invalid";
29202
29410
  const isInvalid = score && score.type === "invalid";
@@ -31015,6 +31223,107 @@ function AxisArrows() {
31015
31223
  })));
31016
31224
  }
31017
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
+
31018
31327
  function AxisLabels() {
31019
31328
  const {
31020
31329
  range,
@@ -31046,7 +31355,7 @@ function AxisLabels() {
31046
31355
  fontSize: "14px",
31047
31356
  transform: "translate(7px, -50%)"
31048
31357
  }
31049
- }, /*#__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", {
31050
31359
  style: {
31051
31360
  position: "absolute",
31052
31361
  left: x2,
@@ -31054,7 +31363,7 @@ function AxisLabels() {
31054
31363
  fontSize: "14px",
31055
31364
  transform: "translate(-50%, 0px)"
31056
31365
  }
31057
- }, /*#__PURE__*/React__default.createElement(TeX, null, yAxisLabelText)));
31366
+ }, /*#__PURE__*/React__default.createElement(TeX, null, replaceOutsideTeX(yAxisLabelText))));
31058
31367
  }
31059
31368
 
31060
31369
  // The size of the ticks and labels in pixels
@@ -31957,17 +32266,12 @@ const ItemExtras = [
31957
32266
  // prettier-ignore
31958
32267
 
31959
32268
  // prettier-ignore
31960
-
32269
+ // While this widget is deprecated, we still need the type for conversion purposes
31961
32270
  // prettier-ignore
31962
-
31963
32271
  // prettier-ignore
31964
-
31965
32272
  // prettier-ignore
31966
-
31967
32273
  //prettier-ignore
31968
-
31969
32274
  // A background image applied to various widgets.
31970
-
31971
32275
  const PerseusExpressionAnswerFormConsidered = ["correct", "wrong", "ungraded"];
31972
32276
 
31973
32277
  // 2D range: xMin, xMax, yMin, yMax
@@ -31993,107 +32297,6 @@ const lockedFigureFillStyles = {
31993
32297
 
31994
32298
  const plotterPlotTypes = ["bar", "line", "pic", "histogram", "dotplot"];
31995
32299
 
31996
- /**
31997
- * 44 is touch best practice and AAA compliant for WCAG
31998
- * https://www.w3.org/WAI/WCAG21/Understanding/target-size.html
31999
- */
32000
- const TARGET_SIZE = 44;
32001
- const REMOVE_BUTTON_ID = "perseus_mafs_remove_button";
32002
-
32003
- // same as pointsFromNormalized in interactive-graph.tsx
32004
- const normalizePoints = (range, step, coordsList, noSnap) => coordsList.map(coords =>
32005
- // maps over [x, y]
32006
- coords.map((coord, i) => {
32007
- const axisRange = range[i];
32008
- if (noSnap) {
32009
- return axisRange[MIN] + size(axisRange) * coord;
32010
- }
32011
- const axisStep = step[i];
32012
- const nSteps = Math.floor(size(axisRange) / axisStep);
32013
- const tick = Math.round(coord * nSteps);
32014
- return axisRange[MIN] + axisStep * tick;
32015
- }));
32016
-
32017
- // same as normalizeCoords in interactive-graph.tsx
32018
- const normalizeCoords = (coordsList, ranges) => coordsList.map(coords => coords.map((coord, i) => {
32019
- return (coord + ranges[i][1]) / size(ranges[i]);
32020
- }));
32021
-
32022
- // Returns the closest point to the given `point` that is within the graph
32023
- // bounds given in `state`.
32024
- function bound$1({
32025
- snapStep,
32026
- range,
32027
- point
32028
- }) {
32029
- const boundingBox = inset(snapStep, range);
32030
- return clampToBox(boundingBox, point);
32031
- }
32032
- function isUnlimitedGraphState(state) {
32033
- return state.type === "point" && state.numPoints === "unlimited" || state.type === "polygon" && state.numSides === "unlimited";
32034
- }
32035
-
32036
- /**
32037
- * Replace all text outside of the $ TeX blocks with `\\text{...}`
32038
- * This way, the entire resulting string can be rendered within <TeX>
32039
- * and the text outside of the $ blocks will be non-TeX text.
32040
- */
32041
- function replaceOutsideTeX(mathString) {
32042
- // All the information we need is in the first section,
32043
- // whether it's typed as "blockmath" or "paragraph"
32044
- const firstSection = SimpleMarkdown.parse(mathString)[0];
32045
-
32046
- // If it's blockMath, the outer level has the full math content.
32047
- if (firstSection.type === "blockMath") {
32048
- return firstSection.content;
32049
- }
32050
-
32051
- // If it's a paragraph, we need to iterate through the sections
32052
- // to look for individual math blocks.
32053
- const condensedNodes = condenseTextNodes(firstSection.content);
32054
- let result = "";
32055
- for (const piece of condensedNodes) {
32056
- piece.type === "math" ? result += piece.content : result += `\\text{${escapeSpecialChars(piece.content)}}`;
32057
- }
32058
- return result;
32059
- }
32060
- // Helper function for replaceOutsideTeX()
32061
- // Condense adjacent text nodes into a single text node
32062
- function condenseTextNodes(nodes) {
32063
- const result = [];
32064
- if (!nodes) {
32065
- return result;
32066
- }
32067
- let currentText = "";
32068
- for (const node of nodes) {
32069
- if (node.type === "math") {
32070
- if (currentText) {
32071
- result.push({
32072
- type: "text",
32073
- content: currentText
32074
- });
32075
- currentText = "";
32076
- }
32077
- result.push(node);
32078
- } else {
32079
- currentText += node.content;
32080
- }
32081
- }
32082
- if (currentText) {
32083
- result.push({
32084
- type: "text",
32085
- content: currentText
32086
- });
32087
- }
32088
- return result;
32089
- }
32090
-
32091
- // Helper function for replaceOutsideTeX()
32092
- function escapeSpecialChars(str) {
32093
- // Escape $, \, {, and } characters
32094
- return str.replace(/([$\\{}])/g, "\\$1");
32095
- }
32096
-
32097
32300
  function LockedLabel(props) {
32098
32301
  const {
32099
32302
  coord,
@@ -33316,6 +33519,7 @@ function useControlPoint(params) {
33316
33519
  } = useGraphConfig();
33317
33520
  const {
33318
33521
  point,
33522
+ sequenceNumber,
33319
33523
  color,
33320
33524
  cursor,
33321
33525
  constrain = p => snap(snapStep, p),
@@ -33356,6 +33560,7 @@ function useControlPoint(params) {
33356
33560
  ref: focusableHandleRef,
33357
33561
  role: "button",
33358
33562
  "aria-label": strings.srPointAtCoordinates({
33563
+ num: sequenceNumber,
33359
33564
  x: srFormatNumber(point[X], locale),
33360
33565
  y: srFormatNumber(point[Y], locale)
33361
33566
  })
@@ -33434,6 +33639,7 @@ const MovableLine = props => {
33434
33639
  focusableHandle: focusableHandle1
33435
33640
  } = useControlPoint({
33436
33641
  point: start,
33642
+ sequenceNumber: 1,
33437
33643
  color,
33438
33644
  onMove: p => onMovePoint(0, p)
33439
33645
  });
@@ -33442,6 +33648,7 @@ const MovableLine = props => {
33442
33648
  focusableHandle: focusableHandle2
33443
33649
  } = useControlPoint({
33444
33650
  point: end,
33651
+ sequenceNumber: 2,
33445
33652
  color,
33446
33653
  onMove: p => onMovePoint(1, p)
33447
33654
  });
@@ -33621,14 +33828,17 @@ function AngleGraph(props) {
33621
33828
  // Render the lines, angle, and then movable points
33622
33829
  return /*#__PURE__*/React.createElement(React.Fragment, null, svgLines, /*#__PURE__*/React.createElement(Angle, angleParams), /*#__PURE__*/React.createElement(MovablePoint$1, {
33623
33830
  point: coords[1],
33831
+ sequenceNumber: 1,
33624
33832
  constrain: p => p,
33625
33833
  onMove: destination => dispatch(actions.angle.movePoint(1, destination))
33626
33834
  }), /*#__PURE__*/React.createElement(MovablePoint$1, {
33627
33835
  point: coords[0],
33836
+ sequenceNumber: 2,
33628
33837
  constrain: getAngleSideConstraint(coords[0], coords[1], snapDegrees || 1),
33629
33838
  onMove: destination => dispatch(actions.angle.movePoint(0, destination))
33630
33839
  }), /*#__PURE__*/React.createElement(MovablePoint$1, {
33631
33840
  point: coords[2],
33841
+ sequenceNumber: 3,
33632
33842
  constrain: getAngleSideConstraint(coords[2], coords[1], snapDegrees || 1),
33633
33843
  onMove: destination => dispatch(actions.angle.movePoint(2, destination))
33634
33844
  }));
@@ -33782,6 +33992,7 @@ function CircleGraph(props) {
33782
33992
  onMove: c => dispatch(actions.circle.moveCenter(c))
33783
33993
  }), /*#__PURE__*/React.createElement(MovablePoint$1, {
33784
33994
  point: radiusPoint,
33995
+ sequenceNumber: 1,
33785
33996
  cursor: "ew-resize",
33786
33997
  onMove: newRadiusPoint => {
33787
33998
  dispatch(actions.circle.moveRadiusPoint(newRadiusPoint));
@@ -33957,6 +34168,7 @@ function LimitedPointGraph(props) {
33957
34168
  return /*#__PURE__*/React.createElement(React.Fragment, null, props.graphState.coords.map((point, i) => /*#__PURE__*/React.createElement(MovablePoint$1, {
33958
34169
  key: i,
33959
34170
  point: point,
34171
+ sequenceNumber: i + 1,
33960
34172
  onMove: destination => dispatch(actions.pointGraph.movePoint(i, destination))
33961
34173
  })));
33962
34174
  }
@@ -33999,6 +34211,7 @@ function UnlimitedPointGraph(props) {
33999
34211
  }), props.graphState.coords.map((point, i) => /*#__PURE__*/React.createElement(MovablePoint$1, {
34000
34212
  key: i,
34001
34213
  point: point,
34214
+ sequenceNumber: i + 1,
34002
34215
  onMove: destination => dispatch(actions.pointGraph.movePoint(i, destination)),
34003
34216
  ref: ref => {
34004
34217
  itemsRef.current[i] = ref;
@@ -34029,7 +34242,8 @@ function describePointGraph(state, i18n) {
34029
34242
  if (state.coords.length === 0) {
34030
34243
  return strings.srNoInteractiveElements;
34031
34244
  }
34032
- const pointDescriptions = state.coords.map(([x, y]) => strings.srPointAtCoordinates({
34245
+ const pointDescriptions = state.coords.map(([x, y], index) => strings.srPointAtCoordinates({
34246
+ num: index + 1,
34033
34247
  x: srFormatNumber(x, locale),
34034
34248
  y: srFormatNumber(y, locale)
34035
34249
  }));
@@ -34147,6 +34361,7 @@ const LimitedPolygonGraph = props => {
34147
34361
  key: "point-" + i,
34148
34362
  constrain: constrain,
34149
34363
  point: point,
34364
+ sequenceNumber: i + 1,
34150
34365
  onMove: destination => {
34151
34366
  const now = Date.now();
34152
34367
  const targetFPS = 40;
@@ -34293,6 +34508,7 @@ const UnlimitedPolygonGraph = props => {
34293
34508
  }), props.graphState.coords.map((point, i) => /*#__PURE__*/React.createElement(MovablePoint$1, {
34294
34509
  key: i,
34295
34510
  point: point,
34511
+ sequenceNumber: i + 1,
34296
34512
  onMove: destination => dispatch(actions.pointGraph.movePoint(i, destination)),
34297
34513
  ref: ref => {
34298
34514
  pointRef.current[i] = ref;
@@ -34368,6 +34584,7 @@ function QuadraticGraph(props) {
34368
34584
  }), coords.map((coord, i) => /*#__PURE__*/React.createElement(MovablePoint$1, {
34369
34585
  key: "point-" + i,
34370
34586
  point: coord,
34587
+ sequenceNumber: i + 1,
34371
34588
  onMove: destination => dispatch(actions.quadratic.movePoint(i, destination))
34372
34589
  })));
34373
34590
  }
@@ -34496,6 +34713,7 @@ function SinusoidGraph(props) {
34496
34713
  }), coords.map((coord, i) => /*#__PURE__*/React.createElement(MovablePoint$1, {
34497
34714
  key: "point-" + i,
34498
34715
  point: coord,
34716
+ sequenceNumber: i + 1,
34499
34717
  onMove: destination => dispatch(actions.sinusoid.movePoint(i, destination))
34500
34718
  })));
34501
34719
  }
@@ -44624,7 +44842,7 @@ const init = function init() {
44624
44842
 
44625
44843
  // This file is processed by a Rollup plugin (replace) to inject the production
44626
44844
  const libName = "@khanacademy/perseus";
44627
- const libVersion = "41.4.0";
44845
+ const libVersion = "42.0.0";
44628
44846
  addLibraryVersionToPerseusDebug(libName, libVersion);
44629
44847
 
44630
44848
  const apiVersion = {
@@ -45248,6 +45466,13 @@ class ServerItemRenderer extends React.Component {
45248
45466
  this.props.onRendered(true);
45249
45467
  }
45250
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
+ }
45251
45476
  }
45252
45477
  componentWillUnmount() {
45253
45478
  if (this.blurTimeoutID != null) {
@@ -45328,7 +45553,7 @@ class ServerItemRenderer extends React.Component {
45328
45553
 
45329
45554
  /**
45330
45555
  * Accepts a question area widgetId, or an answer area widgetId of
45331
- * the form "answer-input-number 1", or the string "answer-area"
45556
+ * the form "answer-numeric-input 1", or the string "answer-area"
45332
45557
  * for the whole answer area (if the answer area is a single widget).
45333
45558
  */
45334
45559
  _setWidgetProps(widgetId, newProps, callback) {
@@ -45365,18 +45590,37 @@ class ServerItemRenderer extends React.Component {
45365
45590
  return this.questionRenderer.getPromptJSON();
45366
45591
  }
45367
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
+
45368
45611
  /**
45369
45612
  * Grades the item.
45613
+ *
45614
+ * @deprecated use scorePerseusItem
45370
45615
  */
45371
45616
  scoreInput() {
45372
- const guessAndScore = this.questionRenderer.guessAndScore();
45373
- const guess = guessAndScore[0];
45374
- const score = guessAndScore[1];
45617
+ const guess = this.getUserInput();
45618
+ const score = scorePerseusItem(this.props.item.question, guess, this.context.strings, this.context.locale);
45375
45619
 
45376
45620
  // Continue to include an empty guess for the now defunct answer area.
45377
45621
  // TODO(alex): Check whether we rely on the format here for
45378
45622
  // analyzing ProblemLogs. If not, remove this layer.
45379
- const maxCompatGuess = [guess, []];
45623
+ const maxCompatGuess = [this.questionRenderer.getUserInput(), []];
45380
45624
  const keScore = Util.keScoreFromPerseusScore(score, maxCompatGuess, this.questionRenderer.getSerializedState());
45381
45625
  const emptyQuestionAreaWidgets = this.questionRenderer.emptyWidgets();
45382
45626
  this.setState({
@@ -47224,140 +47468,6 @@ const registerAllWidgetsForTesting = () => {
47224
47468
  replaceDeprecatedWidgets();
47225
47469
  };
47226
47470
 
47227
- // These are all the current widgets that require user-input and are supported for automatic scoring
47228
- const QUESTION_WIDGETS = ["numeric-input", "input-number", "radio", "interactive-graph", "interactive-number-line", "categorizer", "plotter", "orderer", "protractor", "matcher", "sorter"];
47229
-
47230
- /**
47231
- * Regex for widget placeholders in a string.
47232
- *
47233
- * First capture group is the widget ID (ex. 'radio 1')
47234
- * Second capture group is the widget type (ex. "radio)
47235
- * exec return will look like: ['[[☃ radio 1]]', 'radio 1', 'radio']
47236
- */
47237
- function getWidgetRegex() {
47238
- return /\[\[☃ ([A-Za-z0-9- ]+)\]\]/g;
47239
- }
47240
-
47241
- /**
47242
- * Add a widget placeholder using the widget ID.
47243
- * ex. addWidget("radio 1") => "[[☃ radio 1]]"
47244
- *
47245
- * @param {string} id
47246
- * @returns {string}
47247
- */
47248
- function addWidget(id) {
47249
- return `[[☃ ${id}]]`;
47250
- }
47251
-
47252
- /**
47253
- * Get a widget type by a widget's ID
47254
- * (please don't derive type from ID)
47255
- *
47256
- * @param {string} widgetId the ID of the widget
47257
- * @param {PerseusWidgetsMap} widgetMap widget ID to widget map
47258
- * @returns {string} the widget type (ie "radio")
47259
- */
47260
- function getWidgetTypeByWidgetId(WidgetId, widgetMap) {
47261
- var _widget$type;
47262
- const widget = widgetMap[WidgetId];
47263
- return (_widget$type = widget == null ? void 0 : widget.type) != null ? _widget$type : null;
47264
- }
47265
-
47266
- /**
47267
- * Does the content have a specific type of widget?
47268
- *
47269
- * @param {string} type the type of the widget in question (ie "radio")
47270
- * @param {string} content the string to search through
47271
- * @param {PerseusWidgetsMap} widgetMap widget ID to widget map
47272
- * @returns {boolean} if the content includes the widget type
47273
- */
47274
- function contentHasWidgetType(type, content, widgetMap) {
47275
- return getWidgetIdsFromContentByType(type, content, widgetMap).length > 0;
47276
- }
47277
-
47278
- /**
47279
- * Extract all widget IDs, which includes the widget type and instance number.
47280
- * example output: ['radio 1', 'categorizer 1', 'categorizor 2']
47281
- *
47282
- * Content should contain Perseus widget placeholders,
47283
- * which look like: '[[☃ radio 1]]'.
47284
- *
47285
- * @param {string} content
47286
- * @returns {ReadonlyArray<string>} widgetIds
47287
- */
47288
- function getWidgetIdsFromContent(content) {
47289
- const widgets = [];
47290
- const localWidgetRegex = getWidgetRegex();
47291
- let match = localWidgetRegex.exec(content);
47292
- while (match !== null) {
47293
- widgets.push(match[1]);
47294
- match = localWidgetRegex.exec(content);
47295
- }
47296
- return widgets;
47297
- }
47298
-
47299
- /**
47300
- * Get a list of widget IDs from content,
47301
- * but only for specific widget types
47302
- *
47303
- * @param {string} type the type of widget (ie "radio")
47304
- * @param {string} content the string to parse
47305
- * @param {PerseusWidgetsMap} widgetMap widget ID to widget map
47306
- * @returns {ReadonlyArray<string>} the widget type (ie "radio")
47307
- */
47308
- function getWidgetIdsFromContentByType(type, content, widgetMap) {
47309
- const rv = [];
47310
- const widgetIdsInContent = getWidgetIdsFromContent(content);
47311
- widgetIdsInContent.forEach(widgetId => {
47312
- const widget = widgetMap[widgetId];
47313
- if ((widget == null ? void 0 : widget.type) === type) {
47314
- rv.push(widgetId);
47315
- }
47316
- });
47317
- return rv;
47318
- }
47319
-
47320
- /**
47321
- * Pull the widget map out of ItemData
47322
- *
47323
- * @param {PerseusItem} PerseusItem containing a widgetMap
47324
- * @returns {WidgetMap} the widget map in the PerseusItem
47325
- */
47326
- function getWidgetsMapFromItemData(itemData) {
47327
- return itemData.question.widgets;
47328
- }
47329
-
47330
- /**
47331
- * Get the widget information from a WidgetMap
47332
- *
47333
- * @param {string} widgetId the ID of the widget
47334
- * @returns {PerseusWidget | null} the widget data if it exists, otherwise null
47335
- */
47336
- function getWidgetFromWidgetMap(widgetId, widgetMap) {
47337
- var _widgetMap$widgetId;
47338
- return (_widgetMap$widgetId = widgetMap[widgetId]) != null ? _widgetMap$widgetId : null;
47339
- }
47340
-
47341
- /**
47342
- * Get select widgets from a widget map.
47343
- * Useful for multi-items that needs to split
47344
- * one PerseusItem into several
47345
- *
47346
- * @param {ReadonlyArray<string>} widgetIds to extract
47347
- * @param {PerseusWidgetsMap} widgetMap to extract from
47348
- * @return {PerseusWidgetsMap} a new widget map with requested widgets
47349
- */
47350
- function getWidgetsFromWidgetMap(widgetIds, widgetMap) {
47351
- const widgets = {};
47352
- widgetIds.forEach(widgetId => {
47353
- const widget = getWidgetFromWidgetMap(widgetId, widgetMap);
47354
- if (widget) {
47355
- widgets[widgetId] = widget;
47356
- }
47357
- });
47358
- return widgets;
47359
- }
47360
-
47361
47471
  /**
47362
47472
  * This function extracts the answers from the widgets.
47363
47473
  *
@@ -48283,6 +48393,118 @@ const parseExpressionWidget = parseWidget(constant("expression"), object({
48283
48393
  buttonsVisible: optional(enumeration("always", "never", "focused"))
48284
48394
  }));
48285
48395
 
48396
+ const parsePerseusRenderer = object({
48397
+ content: string,
48398
+ // This module has an import cycle with parseWidgetsMap, because the
48399
+ // `group` widget can contain another renderer.
48400
+ // The anonymous function below ensures that we don't try to access
48401
+ // parseWidgetsMap before it's defined.
48402
+ widgets: (rawVal, ctx) => parseWidgetsMap(rawVal, ctx),
48403
+ metadata: optional(array(string)),
48404
+ images: record(string, object({
48405
+ width: number,
48406
+ height: number
48407
+ }))
48408
+ });
48409
+
48410
+ const parseGradedGroupWidgetOptions = object({
48411
+ title: string,
48412
+ hasHint: optional(nullable(boolean)),
48413
+ // This module has an import cycle with parsePerseusRenderer.
48414
+ // The anonymous function below ensures that we don't try to access
48415
+ // parsePerseusRenderer before it's defined.
48416
+ hint: optional(nullable((rawVal, ctx) => parsePerseusRenderer(rawVal, ctx))),
48417
+ content: string,
48418
+ // This module has an import cycle with parseWidgetsMap.
48419
+ // The anonymous function below ensures that we don't try to access
48420
+ // parseWidgetsMap before it's defined.
48421
+ widgets: (rawVal, ctx) => parseWidgetsMap(rawVal, ctx),
48422
+ widgetEnabled: optional(nullable(boolean)),
48423
+ immutableWidgets: optional(nullable(boolean)),
48424
+ images: record(string, object({
48425
+ width: number,
48426
+ height: number
48427
+ }))
48428
+ });
48429
+ const parseGradedGroupWidget = parseWidget(constant("graded-group"), parseGradedGroupWidgetOptions);
48430
+
48431
+ const parseGradedGroupSetWidget = parseWidget(constant("graded-group-set"), object({
48432
+ gradedGroups: array(parseGradedGroupWidgetOptions)
48433
+ }));
48434
+
48435
+ const pairOfNumbers$3 = pair(number, number);
48436
+ const pairOfPoints = pair(pairOfNumbers$3, pairOfNumbers$3);
48437
+ const parseGrapherWidget = parseWidget(constant("grapher"), object({
48438
+ availableTypes: array(enumeration("absolute_value", "exponential", "linear", "logarithm", "quadratic", "sinusoid", "tangent")),
48439
+ correct: union(object({
48440
+ type: constant("absolute_value"),
48441
+ coords: pairOfPoints
48442
+ })).or(object({
48443
+ type: constant("exponential"),
48444
+ asymptote: pairOfPoints,
48445
+ coords: pairOfPoints
48446
+ })).or(object({
48447
+ type: constant("linear"),
48448
+ coords: pairOfPoints
48449
+ })).or(object({
48450
+ type: constant("logarithm"),
48451
+ asymptote: pairOfPoints,
48452
+ coords: pairOfPoints
48453
+ })).or(object({
48454
+ type: constant("quadratic"),
48455
+ coords: pairOfPoints
48456
+ })).or(object({
48457
+ type: constant("sinusoid"),
48458
+ coords: pairOfPoints
48459
+ })).or(object({
48460
+ type: constant("tangent"),
48461
+ coords: pairOfPoints
48462
+ })).parser,
48463
+ graph: object({
48464
+ backgroundImage: object({
48465
+ bottom: optional(number),
48466
+ height: optional(number),
48467
+ left: optional(number),
48468
+ scale: optional(number),
48469
+ url: optional(nullable(string)),
48470
+ width: optional(number)
48471
+ }),
48472
+ box: optional(pairOfNumbers$3),
48473
+ editableSettings: optional(array(enumeration("graph", "snap", "image", "measure"))),
48474
+ gridStep: optional(pairOfNumbers$3),
48475
+ labels: pair(string, string),
48476
+ markings: enumeration("graph", "none", "grid"),
48477
+ range: pair(pairOfNumbers$3, pairOfNumbers$3),
48478
+ rulerLabel: constant(""),
48479
+ rulerTicks: number,
48480
+ showProtractor: optional(boolean),
48481
+ showRuler: optional(boolean),
48482
+ showTooltips: optional(boolean),
48483
+ snapStep: optional(pairOfNumbers$3),
48484
+ step: pairOfNumbers$3,
48485
+ valid: optional(union(boolean).or(string).parser)
48486
+ })
48487
+ }));
48488
+
48489
+ const parseGroupWidget = parseWidget(constant("group"),
48490
+ // This module has an import cycle with parsePerseusRenderer.
48491
+ // The anonymous function below ensures that we don't try to access
48492
+ // parsePerseusRenderer before it's defined.
48493
+ (rawVal, ctx) => parsePerseusRenderer(rawVal, ctx));
48494
+
48495
+ const parseIframeWidget = parseWidget(constant("iframe"), object({
48496
+ url: string,
48497
+ settings: array(object({
48498
+ name: string,
48499
+ value: string
48500
+ })),
48501
+ width: union(number).or(string).parser,
48502
+ height: union(number).or(string).parser,
48503
+ allowFullScreen: boolean,
48504
+ allowTopNavigation: optional(boolean),
48505
+ static: boolean
48506
+ }));
48507
+
48286
48508
  const parsePerseusImageBackground = object({
48287
48509
  url: optional(nullable(string)),
48288
48510
  width: optional(number),
@@ -48294,6 +48516,160 @@ const parsePerseusImageBackground = object({
48294
48516
  scale: optional(union(number).or(string).parser)
48295
48517
  });
48296
48518
 
48519
+ const pairOfNumbers$2 = pair(number, number);
48520
+ const parseImageWidget = parseWidget(constant("image"), object({
48521
+ title: optional(string),
48522
+ caption: optional(string),
48523
+ alt: optional(string),
48524
+ backgroundImage: parsePerseusImageBackground,
48525
+ static: optional(boolean),
48526
+ labels: optional(array(object({
48527
+ content: string,
48528
+ alignment: string,
48529
+ coordinates: array(number)
48530
+ }))),
48531
+ range: optional(pair(pairOfNumbers$2, pairOfNumbers$2)),
48532
+ box: optional(pairOfNumbers$2)
48533
+ }));
48534
+
48535
+ const parseInputNumberWidget = parseWidget(constant("input-number"), object({
48536
+ answerType: optional(enumeration("number", "decimal", "integer", "rational", "improper", "mixed", "percent", "pi")),
48537
+ inexact: optional(boolean),
48538
+ maxError: optional(union(number).or(string).parser),
48539
+ rightAlign: optional(boolean),
48540
+ simplify: enumeration("required", "optional", "enforced"),
48541
+ size: enumeration("normal", "small"),
48542
+ value: union(number).or(string).parser,
48543
+ customKeypad: optional(boolean)
48544
+ }));
48545
+
48546
+ const pairOfNumbers$1 = pair(number, number);
48547
+ const parseFunctionElement = object({
48548
+ type: constant("function"),
48549
+ key: string,
48550
+ options: object({
48551
+ value: string,
48552
+ funcName: string,
48553
+ rangeMin: string,
48554
+ rangeMax: string,
48555
+ color: string,
48556
+ strokeDasharray: string,
48557
+ strokeWidth: number
48558
+ })
48559
+ });
48560
+ const parseLabelElement = object({
48561
+ type: constant("label"),
48562
+ key: string,
48563
+ options: object({
48564
+ label: string,
48565
+ color: string,
48566
+ coordX: string,
48567
+ coordY: string
48568
+ })
48569
+ });
48570
+ const parseLineElement = object({
48571
+ type: constant("line"),
48572
+ key: string,
48573
+ options: object({
48574
+ color: string,
48575
+ startX: string,
48576
+ startY: string,
48577
+ endX: string,
48578
+ endY: string,
48579
+ strokeDasharray: string,
48580
+ strokeWidth: number,
48581
+ arrows: string
48582
+ })
48583
+ });
48584
+ const parseMovableLineElement = object({
48585
+ type: constant("movable-line"),
48586
+ key: string,
48587
+ options: object({
48588
+ startX: string,
48589
+ startY: string,
48590
+ startSubscript: number,
48591
+ endX: string,
48592
+ endY: string,
48593
+ endSubscript: number,
48594
+ constraint: string,
48595
+ snap: number,
48596
+ constraintFn: string,
48597
+ constraintXMin: string,
48598
+ constraintXMax: string,
48599
+ constraintYMin: string,
48600
+ constraintYMax: string
48601
+ })
48602
+ });
48603
+ const parseMovablePointElement = object({
48604
+ type: constant("movable-point"),
48605
+ key: string,
48606
+ options: object({
48607
+ startX: string,
48608
+ startY: string,
48609
+ varSubscript: number,
48610
+ constraint: string,
48611
+ snap: number,
48612
+ constraintFn: string,
48613
+ constraintXMin: string,
48614
+ constraintXMax: string,
48615
+ constraintYMin: string,
48616
+ constraintYMax: string
48617
+ })
48618
+ });
48619
+ const parseParametricElement = object({
48620
+ type: constant("parametric"),
48621
+ key: string,
48622
+ options: object({
48623
+ x: string,
48624
+ y: string,
48625
+ rangeMin: string,
48626
+ rangeMax: string,
48627
+ color: string,
48628
+ strokeDasharray: string,
48629
+ strokeWidth: number
48630
+ })
48631
+ });
48632
+ const parsePointElement = object({
48633
+ type: constant("point"),
48634
+ key: string,
48635
+ options: object({
48636
+ color: string,
48637
+ coordX: string,
48638
+ coordY: string
48639
+ })
48640
+ });
48641
+ const parseRectangleElement = object({
48642
+ type: constant("rectangle"),
48643
+ key: string,
48644
+ options: object({
48645
+ color: string,
48646
+ coordX: string,
48647
+ coordY: string,
48648
+ width: string,
48649
+ height: string
48650
+ })
48651
+ });
48652
+ const parseInteractionWidget = parseWidget(constant("interaction"), object({
48653
+ static: boolean,
48654
+ graph: object({
48655
+ editableSettings: optional(array(enumeration("canvas", "graph"))),
48656
+ box: pairOfNumbers$1,
48657
+ labels: array(string),
48658
+ range: pair(pairOfNumbers$1, pairOfNumbers$1),
48659
+ gridStep: pairOfNumbers$1,
48660
+ markings: enumeration("graph", "grid", "none"),
48661
+ snapStep: optional(pairOfNumbers$1),
48662
+ valid: optional(union(boolean).or(string).parser),
48663
+ backgroundImage: optional(parsePerseusImageBackground),
48664
+ showProtractor: optional(boolean),
48665
+ showRuler: optional(boolean),
48666
+ rulerLabel: optional(string),
48667
+ rulerTicks: optional(number),
48668
+ tickStep: pairOfNumbers$1
48669
+ }),
48670
+ elements: array(union(parseFunctionElement).or(parseLabelElement).or(parseLineElement).or(parseMovableLineElement).or(parseMovablePointElement).or(parseParametricElement).or(parsePointElement).or(parseRectangleElement).parser)
48671
+ }));
48672
+
48297
48673
  // Used to represent 2-D points and ranges
48298
48674
  const pairOfNumbers = pair(number, number);
48299
48675
  const parsePerseusGraphTypeAngle = object({
@@ -48480,6 +48856,195 @@ const parseInteractiveGraphWidget = parseWidget(constant("interactive-graph"), o
48480
48856
  fullGraphAriaDescription: optional(string)
48481
48857
  }));
48482
48858
 
48859
+ const parseLabelImageWidget = parseWidget(constant("label-image"), object({
48860
+ choices: array(string),
48861
+ imageUrl: string,
48862
+ imageAlt: string,
48863
+ imageHeight: number,
48864
+ imageWidth: number,
48865
+ markers: array(object({
48866
+ answers: array(string),
48867
+ label: string,
48868
+ x: number,
48869
+ y: number
48870
+ })),
48871
+ hideChoicesFromInstructions: boolean,
48872
+ multipleAnswers: boolean,
48873
+ static: boolean
48874
+ }));
48875
+
48876
+ const parseMatcherWidget = parseWidget(constant("matcher"), object({
48877
+ labels: array(string),
48878
+ left: array(string),
48879
+ right: array(string),
48880
+ orderMatters: boolean,
48881
+ padding: boolean
48882
+ }));
48883
+
48884
+ const parseMatrixWidget = parseWidget(constant("matrix"), object({
48885
+ prefix: string,
48886
+ suffix: string,
48887
+ answers: array(array(number)),
48888
+ cursorPosition: array(number),
48889
+ matrixBoardSize: array(number),
48890
+ static: boolean
48891
+ }));
48892
+
48893
+ const parseMeasurerWidget = parseWidget(constant("measurer"), object({
48894
+ image: parsePerseusImageBackground,
48895
+ showProtractor: boolean,
48896
+ showRuler: boolean,
48897
+ rulerLabel: string,
48898
+ rulerTicks: number,
48899
+ rulerPixels: number,
48900
+ rulerLength: number,
48901
+ box: pair(number, number),
48902
+ static: boolean
48903
+ }));
48904
+
48905
+ const parseMoleculeRendererWidget = parseWidget(constant("molecule-renderer"), object({
48906
+ widgetId: string,
48907
+ rotationAngle: optional(number),
48908
+ smiles: optional(string)
48909
+ }));
48910
+
48911
+ const parseNumberLineWidget = parseWidget(constant("number-line"), object({
48912
+ range: array(number),
48913
+ labelRange: array(nullable(number)),
48914
+ labelStyle: string,
48915
+ labelTicks: boolean,
48916
+ isTickCtrl: optional(nullable(boolean)),
48917
+ divisionRange: array(number),
48918
+ numDivisions: optional(nullable(number)),
48919
+ snapDivisions: number,
48920
+ tickStep: optional(nullable(number)),
48921
+ correctRel: optional(nullable(string)),
48922
+ correctX: number,
48923
+ initialX: optional(nullable(number)),
48924
+ showTooltip: optional(boolean),
48925
+ static: boolean
48926
+ }));
48927
+
48928
+ const parseMathFormat = enumeration("integer", "mixed", "improper", "proper", "decimal", "percent", "pi");
48929
+ const parseNumericInputWidget = parseWidget(constant("numeric-input"), object({
48930
+ answers: array(object({
48931
+ message: string,
48932
+ value: number,
48933
+ status: string,
48934
+ answerForms: optional(array(parseMathFormat)),
48935
+ strict: boolean,
48936
+ maxError: optional(nullable(number)),
48937
+ simplify: optional(nullable(string))
48938
+ })),
48939
+ labelText: string,
48940
+ size: string,
48941
+ coefficient: boolean,
48942
+ rightAlign: optional(boolean),
48943
+ static: boolean,
48944
+ answerForms: optional(array(object({
48945
+ name: parseMathFormat,
48946
+ simplify: optional(nullable(enumeration("required", "correct", "enforced", "optional")))
48947
+ })))
48948
+ }));
48949
+
48950
+ // There is an import cycle between orderer-widget.ts and perseus-renderer.ts.
48951
+ // This wrapper ensures that we don't refer to parsePerseusRenderer before
48952
+ // it's defined.
48953
+ function parseRenderer(rawValue, ctx) {
48954
+ return parsePerseusRenderer(rawValue, ctx);
48955
+ }
48956
+ const parseOrdererWidget = parseWidget(constant("orderer"), object({
48957
+ options: array(parseRenderer),
48958
+ correctOptions: array(parseRenderer),
48959
+ otherOptions: array(parseRenderer),
48960
+ height: string,
48961
+ layout: string
48962
+ }));
48963
+
48964
+ const parsePassageRefWidget = parseWidget(constant("passage-ref"), object({
48965
+ passageNumber: number,
48966
+ referenceNumber: number,
48967
+ summaryText: string
48968
+ }));
48969
+
48970
+ const parsePassageWidget = parseWidget(constant("passage"), object({
48971
+ footnotes: string,
48972
+ passageText: string,
48973
+ passageTitle: string,
48974
+ showLineNumbers: boolean,
48975
+ static: boolean
48976
+ }));
48977
+
48978
+ const parsePhetSimulationWidget = parseWidget(constant("phet-simulation"), object({
48979
+ url: string,
48980
+ description: string
48981
+ }));
48982
+
48983
+ const parsePlotterWidget = parseWidget(constant("plotter"), object({
48984
+ labels: array(string),
48985
+ categories: array(string),
48986
+ type: enumeration(...plotterPlotTypes),
48987
+ maxY: number,
48988
+ scaleY: number,
48989
+ labelInterval: optional(nullable(number)),
48990
+ snapsPerLine: number,
48991
+ starting: array(number),
48992
+ correct: array(number),
48993
+ picUrl: optional(nullable(string)),
48994
+ picSize: optional(nullable(number)),
48995
+ picBoxHeight: optional(nullable(number)),
48996
+ plotDimensions: array(number)
48997
+ }));
48998
+
48999
+ const parsePythonProgramWidget = parseWidget(constant("python-program"), object({
49000
+ programID: string,
49001
+ height: number
49002
+ }));
49003
+
49004
+ const parseRadioWidget = parseWidget(constant("radio"), object({
49005
+ choices: array(object({
49006
+ content: string,
49007
+ clue: optional(string),
49008
+ correct: optional(boolean),
49009
+ isNoneOfTheAbove: optional(boolean),
49010
+ // deprecated
49011
+ // There is an import cycle between radio-widget.ts and
49012
+ // widgets-map.ts. The anonymous function below ensures that we
49013
+ // don't refer to parseWidgetsMap before it's defined.
49014
+ widgets: optional((rawVal, ctx) => parseWidgetsMap(rawVal, ctx))
49015
+ })),
49016
+ hasNoneOfTheAbove: optional(boolean),
49017
+ countChoices: optional(boolean),
49018
+ randomize: optional(boolean),
49019
+ multipleSelect: optional(boolean),
49020
+ deselectEnabled: optional(boolean),
49021
+ // deprecated
49022
+ onePerLine: optional(boolean),
49023
+ // deprecated
49024
+ displayCount: optional(any),
49025
+ // v0 props
49026
+ // `noneOfTheAbove` is still in use (but only set to `false`).
49027
+ noneOfTheAbove: constant(false)
49028
+ }));
49029
+
49030
+ const parseSorterWidget = parseWidget(constant("sorter"), object({
49031
+ correct: array(string),
49032
+ padding: boolean,
49033
+ layout: enumeration("horizontal", "vertical")
49034
+ }));
49035
+
49036
+ const parseTableWidget = parseWidget(constant("table"), object({
49037
+ headers: array(string),
49038
+ rows: number,
49039
+ columns: number,
49040
+ answers: array(array(string))
49041
+ }));
49042
+
49043
+ const parseVideoWidget = parseWidget(constant("video"), object({
49044
+ location: string,
49045
+ static: optional(boolean)
49046
+ }));
49047
+
48483
49048
  const parseWidgetsMap = (rawValue, ctx) => {
48484
49049
  if (!isObject(rawValue)) {
48485
49050
  return ctx.failure("PerseusWidgetsMap", rawValue);
@@ -48524,86 +49089,67 @@ const parseWidgetsMapEntry = ([key, widget], widgetMap, ctx) => {
48524
49089
  case "expression":
48525
49090
  return parseAndAssign(`expression ${id}`, parseExpressionWidget);
48526
49091
  case "grapher":
48527
- // TODO(LEMS-2585): implement a real parser for this widget
48528
- return parseAndAssign(`grapher ${id}`, any);
49092
+ return parseAndAssign(`grapher ${id}`, parseGrapherWidget);
48529
49093
  case "group":
48530
- // TODO(LEMS-2585): implement a real parser for this widget
48531
- return parseAndAssign(`group ${id}`, any);
49094
+ return parseAndAssign(`group ${id}`, parseGroupWidget);
48532
49095
  case "graded-group":
48533
- // TODO(LEMS-2585): implement a real parser for this widget
48534
- return parseAndAssign(`graded-group ${id}`, any);
49096
+ return parseAndAssign(`graded-group ${id}`, parseGradedGroupWidget);
48535
49097
  case "graded-group-set":
48536
- // TODO(LEMS-2585): implement a real parser for this widget
48537
- return parseAndAssign(`graded-group-set ${id}`, any);
49098
+ return parseAndAssign(`graded-group-set ${id}`, parseGradedGroupSetWidget);
48538
49099
  case "iframe":
48539
- // TODO(LEMS-2585): implement a real parser for this widget
48540
- return parseAndAssign(`iframe ${id}`, any);
49100
+ return parseAndAssign(`iframe ${id}`, parseIframeWidget);
48541
49101
  case "image":
48542
- // TODO(LEMS-2585): implement a real parser for this widget
48543
- return parseAndAssign(`image ${id}`, any);
49102
+ return parseAndAssign(`image ${id}`, parseImageWidget);
48544
49103
  case "input-number":
48545
- // TODO(LEMS-2585): implement a real parser for this widget
48546
- return parseAndAssign(`input-number ${id}`, any);
49104
+ return parseAndAssign(`input-number ${id}`, parseInputNumberWidget);
48547
49105
  case "interaction":
48548
- // TODO(LEMS-2585): implement a real parser for this widget
48549
- return parseAndAssign(`interaction ${id}`, any);
49106
+ return parseAndAssign(`interaction ${id}`, parseInteractionWidget);
48550
49107
  case "interactive-graph":
48551
49108
  return parseAndAssign(`interactive-graph ${id}`, parseInteractiveGraphWidget);
48552
49109
  case "label-image":
48553
- // TODO(LEMS-2585): implement a real parser for this widget
48554
- return parseAndAssign(`label-image ${id}`, any);
49110
+ return parseAndAssign(`label-image ${id}`, parseLabelImageWidget);
48555
49111
  case "matcher":
48556
- // TODO(LEMS-2585): implement a real parser for this widget
48557
- return parseAndAssign(`matcher ${id}`, any);
49112
+ return parseAndAssign(`matcher ${id}`, parseMatcherWidget);
48558
49113
  case "matrix":
48559
- // TODO(LEMS-2585): implement a real parser for this widget
48560
- return parseAndAssign(`matrix ${id}`, any);
49114
+ return parseAndAssign(`matrix ${id}`, parseMatrixWidget);
48561
49115
  case "measurer":
48562
- // TODO(LEMS-2585): implement a real parser for this widget
48563
- return parseAndAssign(`measurer ${id}`, any);
49116
+ return parseAndAssign(`measurer ${id}`, parseMeasurerWidget);
48564
49117
  case "molecule-renderer":
48565
- // TODO(LEMS-2585): implement a real parser for this widget
48566
- return parseAndAssign(`molecule-renderer ${id}`, any);
49118
+ return parseAndAssign(`molecule-renderer ${id}`, parseMoleculeRendererWidget);
48567
49119
  case "number-line":
48568
- // TODO(LEMS-2585): implement a real parser for this widget
48569
- return parseAndAssign(`number-line ${id}`, any);
49120
+ return parseAndAssign(`number-line ${id}`, parseNumberLineWidget);
48570
49121
  case "numeric-input":
48571
- // TODO(LEMS-2585): implement a real parser for this widget
48572
- return parseAndAssign(`numeric-input ${id}`, any);
49122
+ return parseAndAssign(`numeric-input ${id}`, parseNumericInputWidget);
48573
49123
  case "orderer":
48574
- // TODO(LEMS-2585): implement a real parser for this widget
48575
- return parseAndAssign(`orderer ${id}`, any);
49124
+ return parseAndAssign(`orderer ${id}`, parseOrdererWidget);
48576
49125
  case "passage":
48577
- // TODO(LEMS-2585): implement a real parser for this widget
48578
- return parseAndAssign(`passage ${id}`, any);
49126
+ return parseAndAssign(`passage ${id}`, parsePassageWidget);
48579
49127
  case "passage-ref":
48580
- // TODO(LEMS-2585): implement a real parser for this widget
48581
- return parseAndAssign(`passage-ref ${id}`, any);
49128
+ return parseAndAssign(`passage-ref ${id}`, parsePassageRefWidget);
48582
49129
  case "passage-ref-target":
48583
- // TODO(LEMS-2585): implement a real parser for this widget
49130
+ // NOTE(benchristel): as of 2024-11-12, passage-ref-target is only
49131
+ // used in test content. See:
49132
+ // https://www.khanacademy.org/devadmin/content/search?query=widget:passage-ref-target
48584
49133
  return parseAndAssign(`passage-ref-target ${id}`, any);
48585
49134
  case "phet-simulation":
48586
- // TODO(LEMS-2585): implement a real parser for this widget
48587
- return parseAndAssign(`phet-simulation ${id}`, any);
49135
+ return parseAndAssign(`phet-simulation ${id}`, parsePhetSimulationWidget);
48588
49136
  case "plotter":
48589
- // TODO(LEMS-2585): implement a real parser for this widget
48590
- return parseAndAssign(`plotter ${id}`, any);
49137
+ return parseAndAssign(`plotter ${id}`, parsePlotterWidget);
48591
49138
  case "python-program":
48592
- // TODO(LEMS-2585): implement a real parser for this widget
48593
- return parseAndAssign(`python-program ${id}`, any);
49139
+ return parseAndAssign(`python-program ${id}`, parsePythonProgramWidget);
48594
49140
  case "radio":
48595
- // TODO(LEMS-2585): implement a real parser for this widget
48596
- return parseAndAssign(`radio ${id}`, any);
49141
+ return parseAndAssign(`radio ${id}`, parseRadioWidget);
48597
49142
  case "sorter":
48598
- // TODO(LEMS-2585): implement a real parser for this widget
48599
- return parseAndAssign(`sorter ${id}`, any);
49143
+ return parseAndAssign(`sorter ${id}`, parseSorterWidget);
48600
49144
  case "table":
48601
- // TODO(LEMS-2585): implement a real parser for this widget
48602
- return parseAndAssign(`table ${id}`, any);
49145
+ return parseAndAssign(`table ${id}`, parseTableWidget);
48603
49146
  case "video":
48604
- // TODO(LEMS-2585): implement a real parser for this widget
48605
- return parseAndAssign(`video ${id}`, any);
49147
+ return parseAndAssign(`video ${id}`, parseVideoWidget);
48606
49148
  default:
49149
+ if (getWidget(type)) {
49150
+ // @ts-expect-error - 'type' is not a valid widget type
49151
+ return parseAndAssign(`${type} ${id}`, any);
49152
+ }
48607
49153
  return ctx.failure("a valid widget type", type);
48608
49154
  }
48609
49155
  };
@@ -48626,16 +49172,6 @@ const parseHint = object({
48626
49172
  }))
48627
49173
  });
48628
49174
 
48629
- const parsePerseusRenderer = object({
48630
- content: string,
48631
- widgets: parseWidgetsMap,
48632
- metadata: optional(array(string)),
48633
- images: record(string, object({
48634
- width: number,
48635
- height: number
48636
- }))
48637
- });
48638
-
48639
49175
  object({
48640
49176
  question: parsePerseusRenderer,
48641
49177
  hints: array(parseHint),
@@ -48876,5 +49412,5 @@ const GrapherUtil = {
48876
49412
  typeToButton
48877
49413
  };
48878
49414
 
48879
- 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 };
48880
49416
  //# sourceMappingURL=index.js.map