@khanacademy/perseus 34.1.0 → 35.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 (65) hide show
  1. package/dist/es/index.js +374 -380
  2. package/dist/es/index.js.map +1 -1
  3. package/dist/es/strings.js +8 -2
  4. package/dist/es/strings.js.map +1 -1
  5. package/dist/index.d.ts +1 -1
  6. package/dist/index.js +286 -302
  7. package/dist/index.js.map +1 -1
  8. package/dist/mixins/widget-prop-denylist.d.ts +0 -1
  9. package/dist/perseus-api.d.ts +3 -5
  10. package/dist/perseus-types.d.ts +6 -19
  11. package/dist/renderer.d.ts +4 -4
  12. package/dist/server-item-renderer.d.ts +3 -9
  13. package/dist/strings.d.ts +3 -0
  14. package/dist/strings.js +8 -2
  15. package/dist/strings.js.map +1 -1
  16. package/dist/types.d.ts +1 -3
  17. package/dist/validation.types.d.ts +111 -0
  18. package/dist/widgets/categorizer/categorizer-validator.d.ts +2 -2
  19. package/dist/widgets/categorizer/categorizer.d.ts +6 -6
  20. package/dist/widgets/cs-program/cs-program.d.ts +4 -9
  21. package/dist/widgets/definition/definition.d.ts +3 -4
  22. package/dist/widgets/dropdown/dropdown-validator.d.ts +2 -2
  23. package/dist/widgets/dropdown/dropdown.d.ts +5 -5
  24. package/dist/widgets/explanation/explanation.d.ts +3 -2
  25. package/dist/widgets/expression/expression-validator.d.ts +2 -2
  26. package/dist/widgets/expression/expression.d.ts +32 -26
  27. package/dist/widgets/expression/expression.types.d.ts +0 -1
  28. package/dist/widgets/graded-group/graded-group.d.ts +2 -2
  29. package/dist/widgets/graded-group-set/graded-group-set.d.ts +2 -2
  30. package/dist/widgets/grapher/grapher.d.ts +6 -7
  31. package/dist/widgets/group/group.d.ts +3 -2
  32. package/dist/widgets/iframe/iframe.d.ts +4 -9
  33. package/dist/widgets/image/image.d.ts +3 -4
  34. package/dist/widgets/input-number/input-number-validator.d.ts +3 -3
  35. package/dist/widgets/input-number/input-number.d.ts +7 -10
  36. package/dist/widgets/interaction/interaction.d.ts +3 -3
  37. package/dist/widgets/interactive-graph.d.ts +9 -12
  38. package/dist/widgets/interactive-graphs/interactive-graph-question-builder.d.ts +8 -2
  39. package/dist/widgets/interactive-graphs/stateful-mafs-graph.d.ts +5 -2
  40. package/dist/widgets/label-image/label-image-validator.d.ts +10 -0
  41. package/dist/widgets/label-image/label-image.d.ts +11 -10
  42. package/dist/widgets/label-image/types.d.ts +0 -4
  43. package/dist/widgets/matcher/matcher.d.ts +4 -4
  44. package/dist/widgets/matrix/matrix.d.ts +5 -5
  45. package/dist/widgets/molecule/molecule.d.ts +2 -1
  46. package/dist/widgets/number-line/number-line.d.ts +5 -3
  47. package/dist/widgets/numeric-input/numeric-input-validator.d.ts +2 -2
  48. package/dist/widgets/numeric-input/numeric-input.d.ts +7 -7
  49. package/dist/widgets/orderer/orderer.d.ts +4 -4
  50. package/dist/widgets/passage/passage.d.ts +3 -4
  51. package/dist/widgets/passage-ref/passage-ref.d.ts +3 -11
  52. package/dist/widgets/passage-ref-target/passage-ref-target.d.ts +2 -1
  53. package/dist/widgets/phet-simulation/phet-simulation.d.ts +2 -2
  54. package/dist/widgets/plotter/plotter.d.ts +5 -5
  55. package/dist/widgets/radio/radio-component.d.ts +6 -13
  56. package/dist/widgets/radio/radio-validator.d.ts +5 -0
  57. package/dist/widgets/sorter/sorter-validator.d.ts +4 -0
  58. package/dist/widgets/sorter/sorter.d.ts +7 -9
  59. package/dist/widgets/table/table.d.ts +5 -5
  60. package/dist/widgets/video/video.d.ts +2 -2
  61. package/package.json +1 -1
  62. package/dist/widgets/categorizer/categorizer.types.d.ts +0 -5
  63. package/dist/widgets/dropdown/dropdown.types.d.ts +0 -5
  64. package/dist/widgets/input-number/input-number.types.d.ts +0 -2
  65. package/dist/widgets/numeric-input/numeric-input.types.d.ts +0 -5
package/dist/es/index.js CHANGED
@@ -246,7 +246,10 @@ const mockStrings = {
246
246
  cos: "cos",
247
247
  tan: "tan",
248
248
  simulationLoadFail: "Sorry, this simulation cannot load.",
249
- simulationLocaleWarning: "Sorry, this simulation isn't available in your language."
249
+ simulationLocaleWarning: "Sorry, this simulation isn't available in your language.",
250
+ addPoint: "Add Point",
251
+ removePoint: "Remove Point",
252
+ graphKeyboardPrompt: "Press Shift + Enter to interact with the graph"
250
253
  };
251
254
 
252
255
  /**
@@ -749,7 +752,7 @@ const denylist = [
749
752
  // (technically the renderer still adds them)
750
753
  "key", "ref",
751
754
  // added by src/renderer.jsx
752
- "containerSizeClass", "widgetId", "onChange", "problemNum", "apiOptions", "questionCompleted", "findWidgets",
755
+ "containerSizeClass", "widgetId", "onChange", "problemNum", "apiOptions", "widgetIsOpen", "questionCompleted", "findWidgets",
753
756
  // added by src/editor.jsx, for widgets removing themselves
754
757
  // this is soooo not the right place for this, but alas.
755
758
  "onRemove",
@@ -757,15 +760,6 @@ const denylist = [
757
760
  "id",
758
761
  // Callbacks and items for interaction handling
759
762
  "onBlur", "onFocus", "trackInteraction", "keypadElement"];
760
- const removeDenylistProps = props => {
761
- const newProps = _extends({}, props);
762
- for (const prop of denylist) {
763
- if (prop in newProps) {
764
- delete newProps[prop];
765
- }
766
- }
767
- return newProps;
768
- };
769
763
 
770
764
  /* eslint-disable @babel/no-invalid-this */
771
765
  const USAGE = "Usage:\n" + " this.change({propName: 5}, callback);\n" + ' this.change("propName", 5, callback);\n' + ' this.change("propName")';
@@ -1030,8 +1024,6 @@ StubTagEditor.defaultProps = {
1030
1024
  * * nothing if it is purely a bug fix.
1031
1025
  *
1032
1026
  * Callbacks passed to Renderer/ItemRenderer:
1033
- * * onInputError:
1034
- * Called when there is an error grading a widget
1035
1027
  * * onFocusChange: (newFocusPath, oldFocusPath, keypadDOMNode)
1036
1028
  * Called when the user focus changes. The first two parameters are `path`
1037
1029
  * arrays uniquely identifying the respect inputs. The third parameter,
@@ -1054,7 +1046,6 @@ StubTagEditor.defaultProps = {
1054
1046
  const ApiOptions = {
1055
1047
  propTypes: PropTypes.shape({
1056
1048
  isArticle: PropTypes.bool.isRequired,
1057
- onInputError: PropTypes.func.isRequired,
1058
1049
  onFocusChange: PropTypes.func.isRequired,
1059
1050
  GroupMetadataEditor: PropTypes.func.isRequired,
1060
1051
  showAlignmentOptions: PropTypes.bool.isRequired,
@@ -1138,7 +1129,6 @@ const ApiOptions = {
1138
1129
  defaults: {
1139
1130
  isArticle: false,
1140
1131
  isMobile: false,
1141
- onInputError: function () {},
1142
1132
  onFocusChange: function () {},
1143
1133
  GroupMetadataEditor: StubTagEditor,
1144
1134
  showAlignmentOptions: false,
@@ -2150,9 +2140,7 @@ const getDecimalSeparator = locale => {
2150
2140
  * show the user an error. TODO(joel) - what error?
2151
2141
  * - Otherwise, pass through the resulting points and message.
2152
2142
  */
2153
- function expressionValidator(userInput, rubric,
2154
- // @ts-expect-error - TS2322 - Type '() => void' is not assignable to type 'OnInputErrorFunctionType'.
2155
- onInputError = function () {}, strings, locale) {
2143
+ function expressionValidator(userInput, rubric, strings, locale) {
2156
2144
  const options = _.clone(rubric);
2157
2145
  _.extend(options, {
2158
2146
  decimal_separator: getDecimalSeparator(locale)
@@ -2252,14 +2240,9 @@ onInputError = function () {}, strings, locale) {
2252
2240
  };
2253
2241
  }
2254
2242
  if (matchingAnswerForm.considered === "ungraded") {
2255
- // We matched an ungraded answer form - return "invalid", which
2256
- // will let the user try again with no penalty
2257
- const apiResult = onInputError(null,
2258
- // Reserved for some widget identifier
2259
- userInput, matchMessage);
2260
2243
  return {
2261
2244
  type: "invalid",
2262
- message: apiResult === false ? null : matchMessage
2245
+ message: matchMessage
2263
2246
  };
2264
2247
  }
2265
2248
  // We matched a graded answer form, so we can now tell the user
@@ -2348,14 +2331,9 @@ class Expression extends React.Component {
2348
2331
  showErrorStyle: false
2349
2332
  });
2350
2333
  if (!this.parse(this.props.value, this.props).parsed) {
2351
- const apiResult = this.props.apiOptions.onInputError(null,
2352
- // reserved for some widget identifier
2353
- this.props.value, this.context.strings.ERROR_TITLE);
2354
- if (apiResult !== false) {
2355
- this.setState({
2356
- invalid: true
2357
- });
2358
- }
2334
+ this.setState({
2335
+ invalid: true
2336
+ });
2359
2337
  }
2360
2338
  }
2361
2339
  };
@@ -2369,12 +2347,12 @@ class Expression extends React.Component {
2369
2347
  });
2370
2348
  }
2371
2349
  };
2372
- this.simpleValidate = (_ref, onInputError) => {
2350
+ this.simpleValidate = _ref => {
2373
2351
  let {
2374
2352
  scoring
2375
2353
  } = _ref,
2376
2354
  rubric = _objectWithoutPropertiesLoose(_ref, _excluded$5);
2377
- const score = expressionValidator(this.getUserInput(), rubric, onInputError || function () {}, this.context.strings, this.context.locale);
2355
+ const score = expressionValidator(this.getUserInput(), rubric, this.context.strings, this.context.locale);
2378
2356
 
2379
2357
  // "scoring" is a flag that indicates when we are checking answers.
2380
2358
  // otherwise, we may just be checking validity after changes.
@@ -2390,9 +2368,6 @@ class Expression extends React.Component {
2390
2368
  }
2391
2369
  return score;
2392
2370
  };
2393
- this.getUserInput = () => {
2394
- return Expression.getUserInputFromProps(this.props);
2395
- };
2396
2371
  this.change = (...args) => {
2397
2372
  return change.apply(this, args);
2398
2373
  };
@@ -2465,10 +2440,8 @@ class Expression extends React.Component {
2465
2440
  };
2466
2441
  }
2467
2442
  // TODO remove this in favor of just using expressionValidator
2468
- static validate(userInput, rubric,
2469
- // @ts-expect-error - TS2322 - Type '() => void' is not assignable to type 'OnInputErrorFunctionType'.
2470
- onInputError = function () {}, strings, locale) {
2471
- return expressionValidator(userInput, rubric, onInputError, strings, locale);
2443
+ static validate(userInput, rubric, strings, locale) {
2444
+ return expressionValidator(userInput, rubric, strings, locale);
2472
2445
  }
2473
2446
  static getUserInputFromProps(props) {
2474
2447
  return normalizeTex(props.value);
@@ -2482,6 +2455,9 @@ class Expression extends React.Component {
2482
2455
  }
2483
2456
  //#endregion
2484
2457
 
2458
+ getUserInput() {
2459
+ return Expression.getUserInputFromProps(this.props);
2460
+ }
2485
2461
  // HACK(joel)
2486
2462
  insert(keyPressed) {
2487
2463
  // eslint-disable-next-line react/no-string-refs
@@ -14677,7 +14653,7 @@ const pluck = function pluck(table, subKey) {
14677
14653
  * });
14678
14654
  * {a: 2, b: 3}
14679
14655
  */
14680
- const mapObject$2 = function mapObject(obj, lambda) {
14656
+ const mapObject$1 = function mapObject(obj, lambda) {
14681
14657
  const result = {};
14682
14658
  _.each(_.keys(obj), function (key) {
14683
14659
  // @ts-expect-error - TS2345 - Argument of type 'string' is not assignable to parameter of type 'K'.
@@ -14703,7 +14679,7 @@ const mapObjectFromArray$1 = function mapObjectFromArray(arr, lambda) {
14703
14679
  };
14704
14680
  var objective_ = {
14705
14681
  pluck: pluck,
14706
- mapObject: mapObject$2,
14682
+ mapObject: mapObject$1,
14707
14683
  mapObjectFromArray: mapObjectFromArray$1
14708
14684
  };
14709
14685
 
@@ -19596,7 +19572,7 @@ WidgetContainer.defaultProps = {
19596
19572
  };
19597
19573
 
19598
19574
  const {
19599
- mapObject: mapObject$1,
19575
+ mapObject,
19600
19576
  mapObjectFromArray
19601
19577
  } = objective_;
19602
19578
  const rContainsNonWhitespace = /\S/;
@@ -19624,9 +19600,6 @@ const isIdPathPrefix = function isIdPathPrefix(prefixArray, wholeArray) {
19624
19600
  });
19625
19601
  };
19626
19602
 
19627
- // The return type for getUserInput. Widgets have full control of what is
19628
- // returned so it's not easily typed (some widgets return a scalar (string),
19629
- // some return a custom-built object
19630
19603
  /**
19631
19604
  * The Widget type represents the common API that the Renderer uses to interact
19632
19605
  * with all widgets. All widgets must implement the methods in this API, unless
@@ -19636,6 +19609,7 @@ const isIdPathPrefix = function isIdPathPrefix(prefixArray, wholeArray) {
19636
19609
  * communicate with the individual widgets to coordinate actions such as
19637
19610
  * scoring, state serialization/deserialization, and focus management.
19638
19611
  */
19612
+
19639
19613
  class Renderer extends React.Component {
19640
19614
  constructor(_props) {
19641
19615
  super(_props);
@@ -19670,7 +19644,7 @@ class Renderer extends React.Component {
19670
19644
  };
19671
19645
  // @ts-expect-error - TS2322 - Type '(props: Props) => Partial<Record<string, CategorizerWidget | CSProgramWidget | DefinitionWidget | DropdownWidget | ... 35 more ... | VideoWidget>>' is not assignable to type '(props: Props) => { [key: string]: PerseusWidget; }'.
19672
19646
  this._getAllWidgetsInfo = props => {
19673
- return mapObject$1(props.widgets, (widgetInfo, widgetId) => {
19647
+ return mapObject(props.widgets, (widgetInfo, widgetId) => {
19674
19648
  if (!widgetInfo.type || !widgetInfo.alignment) {
19675
19649
  const newValues = {};
19676
19650
  if (!widgetInfo.type) {
@@ -19811,7 +19785,7 @@ class Renderer extends React.Component {
19811
19785
  * state from that instead of the current widget props.
19812
19786
  */
19813
19787
  this.getSerializedState = widgetProps => {
19814
- return mapObject$1(widgetProps || this.state.widgetProps, (props, widgetId) => {
19788
+ return mapObject(widgetProps || this.state.widgetProps, (props, widgetId) => {
19815
19789
  const widget = this.getWidgetInstance(widgetId);
19816
19790
  if (widget && widget.getSerializedState) {
19817
19791
  return widget.getSerializedState();
@@ -19847,7 +19821,7 @@ class Renderer extends React.Component {
19847
19821
  }
19848
19822
  };
19849
19823
  this.setState({
19850
- widgetProps: mapObject$1(serializedState, (props, widgetId) => {
19824
+ widgetProps: mapObject(serializedState, (props, widgetId) => {
19851
19825
  const widget = this.getWidgetInstance(widgetId);
19852
19826
  if (widget && widget.restoreSerializedState) {
19853
19827
  // Note that we probably can't call
@@ -20681,7 +20655,6 @@ class Renderer extends React.Component {
20681
20655
  */
20682
20656
  this.scoreWidgets = () => {
20683
20657
  const widgetProps = this.state.widgetInfo;
20684
- const onInputError = this.getApiOptions().onInputError;
20685
20658
  const gradedWidgetIds = _.filter(this.widgetIds, id => {
20686
20659
  const props = widgetProps[id];
20687
20660
  const widgetIsGraded = (props == null ? void 0 : props.graded) == null || props.graded;
@@ -20697,7 +20670,7 @@ class Renderer extends React.Component {
20697
20670
  if (widget && widget.simpleValidate) {
20698
20671
  widgetScores[id] = widget.simpleValidate(_extends({}, props == null ? void 0 : props.options, {
20699
20672
  scoring: true
20700
- }), onInputError);
20673
+ }));
20701
20674
  }
20702
20675
  });
20703
20676
  return widgetScores;
@@ -21738,7 +21711,7 @@ const answerTypes = {
21738
21711
  forms: "pi"
21739
21712
  }
21740
21713
  };
21741
- function inputNumberValidator(state, rubric, strings, onInputError = () => {}) {
21714
+ function inputNumberValidator(state, rubric, strings) {
21742
21715
  if (rubric.answerType == null) {
21743
21716
  rubric.answerType = "number";
21744
21717
  }
@@ -21762,13 +21735,9 @@ function inputNumberValidator(state, rubric, strings, onInputError = () => {}) {
21762
21735
  // TODO(eater): Seems silly to translate result to this invalid/points
21763
21736
  // thing and immediately translate it back in ItemRenderer.scoreInput()
21764
21737
  if (result.empty) {
21765
- // TODO(FEI-3867): remove null-check once we have APIOptionsInternal
21766
- const apiResult = onInputError == null ? void 0 : onInputError(null,
21767
- // reserved for some widget identifier
21768
- state.currentValue, result.message);
21769
21738
  return {
21770
21739
  type: "invalid",
21771
- message: apiResult === false ? null : result.message
21740
+ message: result.message
21772
21741
  };
21773
21742
  }
21774
21743
  return {
@@ -21864,13 +21833,6 @@ class InputNumber extends React.Component {
21864
21833
  currentValue: newValue
21865
21834
  }, cb);
21866
21835
  };
21867
- this.getUserInput = () => {
21868
- return InputNumber.getUserInputFromProps(this.props);
21869
- };
21870
- this.simpleValidate = (rubric, onInputError) => {
21871
- onInputError = onInputError || function () {};
21872
- return inputNumberValidator(this.getUserInput(), rubric, this.context.strings, onInputError);
21873
- };
21874
21836
  this.examples = () => {
21875
21837
  const {
21876
21838
  strings
@@ -21881,8 +21843,8 @@ class InputNumber extends React.Component {
21881
21843
  return [strings.yourAnswer].concat(examples);
21882
21844
  };
21883
21845
  }
21884
- static validate(state, rubric, strings, onInputError = () => {}) {
21885
- return inputNumberValidator(state, rubric, strings, onInputError);
21846
+ static validate(state, rubric, strings) {
21847
+ return inputNumberValidator(state, rubric, strings);
21886
21848
  }
21887
21849
  static getUserInputFromProps(props) {
21888
21850
  return {
@@ -21899,6 +21861,12 @@ class InputNumber extends React.Component {
21899
21861
  }
21900
21862
  return answerString;
21901
21863
  }
21864
+ getUserInput() {
21865
+ return InputNumber.getUserInputFromProps(this.props);
21866
+ }
21867
+ simpleValidate(rubric) {
21868
+ return inputNumberValidator(this.getUserInput(), rubric, this.context.strings);
21869
+ }
21902
21870
  render() {
21903
21871
  if (this.props.apiOptions.customKeypad) {
21904
21872
  // TODO(charlie): Support "Review Mode".
@@ -22202,9 +22170,6 @@ class NumericInput extends React.Component {
22202
22170
  const allFormsAccepted = answerFormNames.length >= Object.keys(formExamples).length;
22203
22171
  return !noFormsAccepted && !allFormsAccepted;
22204
22172
  };
22205
- this.simpleValidate = rubric => {
22206
- return numericInputValidator(this.getUserInput(), rubric, this.context.strings);
22207
- };
22208
22173
  this.focus = () => {
22209
22174
  var _this$inputRef;
22210
22175
  (_this$inputRef = this.inputRef) == null || _this$inputRef.focus();
@@ -22234,9 +22199,6 @@ class NumericInput extends React.Component {
22234
22199
  currentValue: newValue
22235
22200
  }, cb);
22236
22201
  };
22237
- this.getUserInput = () => {
22238
- return NumericInput.getUserInputFromProps(this.props);
22239
- };
22240
22202
  this.handleChange = (newValue, cb) => {
22241
22203
  this.props.onChange({
22242
22204
  currentValue: newValue
@@ -22286,6 +22248,12 @@ class NumericInput extends React.Component {
22286
22248
  static validate(userInput, rubric, strings) {
22287
22249
  return numericInputValidator(userInput, rubric, strings);
22288
22250
  }
22251
+ simpleValidate(rubric) {
22252
+ return numericInputValidator(this.getUserInput(), rubric, this.context.strings);
22253
+ }
22254
+ getUserInput() {
22255
+ return NumericInput.getUserInputFromProps(this.props);
22256
+ }
22289
22257
  render() {
22290
22258
  let labelText = this.props.labelText;
22291
22259
  if (labelText == null || labelText === "") {
@@ -22442,11 +22410,6 @@ class PassageRef extends React.Component {
22442
22410
  lineRange: null,
22443
22411
  content: null
22444
22412
  };
22445
- // TODO(tamarab): getUserInput needs to be updated to only return
22446
- // props input by the user. Currently returns all the widget's props.
22447
- this.getUserInput = () => {
22448
- return removeDenylistProps(this.props);
22449
- };
22450
22413
  this.change = (...args) => {
22451
22414
  return change.apply(this, args);
22452
22415
  };
@@ -22499,6 +22462,11 @@ class PassageRef extends React.Component {
22499
22462
  window.removeEventListener("resize", this._throttledUpdateRange);
22500
22463
  this._isMounted = false;
22501
22464
  }
22465
+
22466
+ // TODO (LEMS-2396): remove validation logic from widgets that don't validate
22467
+ getUserInput() {
22468
+ return null;
22469
+ }
22502
22470
  // TODO (LEMS-2396): remove validation logic from widgets that don't validate
22503
22471
  simpleValidate() {
22504
22472
  return noopValidator();
@@ -23904,6 +23872,56 @@ const styles$t = StyleSheet.create({
23904
23872
  }
23905
23873
  });
23906
23874
 
23875
+ function radioValidator(userInput, rubric, strings) {
23876
+ const numSelected = userInput.choicesSelected.reduce((sum, selected) => {
23877
+ return sum + (selected ? 1 : 0);
23878
+ }, 0);
23879
+ if (numSelected === 0) {
23880
+ return {
23881
+ type: "invalid",
23882
+ message: null
23883
+ };
23884
+ }
23885
+
23886
+ // TODO: should numCorrect actually be on the rubric
23887
+ // instead of the userInput?
23888
+ if (userInput.numCorrect && userInput.numCorrect > 1 && numSelected !== userInput.numCorrect) {
23889
+ return {
23890
+ type: "invalid",
23891
+ message: strings.chooseCorrectNum
23892
+ };
23893
+ // If NOTA and some other answer are checked, ...
23894
+ }
23895
+
23896
+ // TODO: should noneOfTheAboveSelected be replaced with a
23897
+ // combination of choicesSelected and noneOfTheAboveIndex?
23898
+ if (userInput.noneOfTheAboveSelected && numSelected > 1) {
23899
+ return {
23900
+ type: "invalid",
23901
+ message: strings.notNoneOfTheAbove
23902
+ };
23903
+ }
23904
+ const correct = userInput.choicesSelected.every((selected, i) => {
23905
+ let isCorrect;
23906
+ // TODO: should noneOfTheAboveIndex actually be on the rubric
23907
+ // instead of the userInput?
23908
+ if (userInput.noneOfTheAboveIndex === i) {
23909
+ isCorrect = rubric.choices.every((choice, j) => {
23910
+ return i === j || !choice.correct;
23911
+ });
23912
+ } else {
23913
+ isCorrect = !!rubric.choices[i].correct;
23914
+ }
23915
+ return isCorrect === selected;
23916
+ });
23917
+ return {
23918
+ type: "points",
23919
+ earned: correct ? 1 : 0,
23920
+ total: 1,
23921
+ message: null
23922
+ };
23923
+ }
23924
+
23907
23925
  // RenderProps is the return type for radio.jsx#transform
23908
23926
 
23909
23927
  class Radio$1 extends React.Component {
@@ -23998,12 +24016,6 @@ class Radio$1 extends React.Component {
23998
24016
  });
23999
24017
  this.props.trackInteraction();
24000
24018
  };
24001
- this.getUserInput = () => {
24002
- return Radio$1.getUserInputFromProps(this.props);
24003
- };
24004
- this.simpleValidate = rubric => {
24005
- return Radio$1.validate(this.getUserInput(), rubric, this.context.strings);
24006
- };
24007
24019
  /**
24008
24020
  * Turn on rationale display for the currently selected choices. Note that
24009
24021
  * this leaves rationales on for choices that are already showing
@@ -24064,46 +24076,7 @@ class Radio$1 extends React.Component {
24064
24076
  };
24065
24077
  }
24066
24078
  static validate(userInput, rubric, strings) {
24067
- const numSelected = userInput.choicesSelected.reduce((sum, selected) => {
24068
- return sum + (selected ? 1 : 0);
24069
- }, 0);
24070
- if (numSelected === 0) {
24071
- return {
24072
- type: "invalid",
24073
- message: null
24074
- };
24075
- }
24076
- if (userInput.numCorrect && userInput.numCorrect > 1 && numSelected !== userInput.numCorrect) {
24077
- return {
24078
- type: "invalid",
24079
- message: strings.chooseCorrectNum
24080
- };
24081
- // If NOTA and some other answer are checked, ...
24082
- }
24083
-
24084
- if (userInput.noneOfTheAboveSelected && numSelected > 1) {
24085
- return {
24086
- type: "invalid",
24087
- message: strings.notNoneOfTheAbove
24088
- };
24089
- }
24090
- const correct = userInput.choicesSelected.every((selected, i) => {
24091
- let isCorrect;
24092
- if (userInput.noneOfTheAboveIndex === i) {
24093
- isCorrect = rubric.choices.every((choice, j) => {
24094
- return i === j || !choice.correct;
24095
- });
24096
- } else {
24097
- isCorrect = !!rubric.choices[i].correct;
24098
- }
24099
- return isCorrect === selected;
24100
- });
24101
- return {
24102
- type: "points",
24103
- earned: correct ? 1 : 0,
24104
- total: 1,
24105
- message: null
24106
- };
24079
+ return radioValidator(userInput, rubric, strings);
24107
24080
  }
24108
24081
  static getUserInputFromProps(props) {
24109
24082
  // Return checked inputs in the form {choicesSelected: [bool]}. (Dear
@@ -24192,6 +24165,12 @@ class Radio$1 extends React.Component {
24192
24165
  registerFocusFunction(fun) {
24193
24166
  this.focusFunction = fun;
24194
24167
  }
24168
+ getUserInput() {
24169
+ return Radio$1.getUserInputFromProps(this.props);
24170
+ }
24171
+ simpleValidate(rubric) {
24172
+ return radioValidator(this.getUserInput(), rubric, this.context.strings);
24173
+ }
24195
24174
  render() {
24196
24175
  const {
24197
24176
  choices
@@ -24468,12 +24447,6 @@ class Categorizer extends React.Component {
24468
24447
  // @ts-expect-error - TS2345 - Argument of type 'readonly unknown[]' is not assignable to parameter of type 'any[]'.
24469
24448
  return change.apply(this, args);
24470
24449
  };
24471
- this.getUserInput = () => {
24472
- return Categorizer.getUserInputFromProps(this.props);
24473
- };
24474
- this.simpleValidate = rubric => {
24475
- return categorizerValidator(this.getUserInput(), rubric, this.context.strings);
24476
- };
24477
24450
  }
24478
24451
  static validate(userInput, rubric, strings) {
24479
24452
  return categorizerValidator(userInput, rubric, strings);
@@ -24483,6 +24456,9 @@ class Categorizer extends React.Component {
24483
24456
  values: props.values
24484
24457
  };
24485
24458
  }
24459
+ getUserInput() {
24460
+ return Categorizer.getUserInputFromProps(this.props);
24461
+ }
24486
24462
  onChange(itemNum, catNum) {
24487
24463
  const values = [...this.props.values];
24488
24464
  // @ts-expect-error - TS2322 - Type 'number' is not assignable to type 'never'.
@@ -24490,6 +24466,9 @@ class Categorizer extends React.Component {
24490
24466
  this.change("values", values);
24491
24467
  this.props.trackInteraction();
24492
24468
  }
24469
+ simpleValidate(rubric) {
24470
+ return categorizerValidator(this.getUserInput(), rubric, this.context.strings);
24471
+ }
24493
24472
  render() {
24494
24473
  const self = this;
24495
24474
 
@@ -24856,16 +24835,14 @@ var CSProgram$1 = {
24856
24835
  };
24857
24836
 
24858
24837
  class Definition extends React.Component {
24859
- constructor(...args) {
24860
- super(...args);
24861
- this.getUserInput = () => {
24862
- return {};
24863
- };
24864
- }
24865
24838
  // TODO (LEMS-2396): remove validation logic from widgets that don't validate
24866
24839
  static validate() {
24867
24840
  return noopValidator();
24868
24841
  }
24842
+ getUserInput() {
24843
+ return {};
24844
+ }
24845
+
24869
24846
  // TODO (LEMS-2396): remove validation logic from widgets that don't validate
24870
24847
  simpleValidate() {
24871
24848
  return noopValidator();
@@ -25002,18 +24979,18 @@ class Dropdown extends React.Component {
25002
24979
  selected: selected
25003
24980
  });
25004
24981
  };
25005
- this.getUserInput = () => {
25006
- return {
25007
- value: this.props.selected
25008
- };
25009
- };
25010
- this.simpleValidate = rubric => {
25011
- return dropdownValidator(this.getUserInput(), rubric);
25012
- };
25013
24982
  }
25014
24983
  static validate(userInput, rubric) {
25015
24984
  return dropdownValidator(userInput, rubric);
25016
24985
  }
24986
+ getUserInput() {
24987
+ return {
24988
+ value: this.props.selected
24989
+ };
24990
+ }
24991
+ simpleValidate(rubric) {
24992
+ return dropdownValidator(this.getUserInput(), rubric);
24993
+ }
25017
24994
  render() {
25018
24995
  const children = [/*#__PURE__*/React.createElement(OptionItem, {
25019
24996
  key: "placeholder",
@@ -25087,9 +25064,6 @@ class Explanation extends React.Component {
25087
25064
  });
25088
25065
  this.props.trackInteraction();
25089
25066
  };
25090
- this.getUserInput = () => {
25091
- return {};
25092
- };
25093
25067
  }
25094
25068
  // TODO (LEMS-2396): remove validation logic from widgets that don't validate
25095
25069
  static validate() {
@@ -25101,6 +25075,10 @@ class Explanation extends React.Component {
25101
25075
  componentWillUnmount() {
25102
25076
  this._mounted = false;
25103
25077
  }
25078
+ getUserInput() {
25079
+ return {};
25080
+ }
25081
+
25104
25082
  // TODO (LEMS-2396): remove validation logic from widgets that don't validate
25105
25083
  simpleValidate() {
25106
25084
  return noopValidator();
@@ -26724,7 +26702,7 @@ FunctionGrapher.defaultProps = {
26724
26702
  coords: null,
26725
26703
  asymptote: null,
26726
26704
  isMobile: false
26727
- }; // Really this is props.plot, I think
26705
+ };
26728
26706
  /* Widget and editor. */
26729
26707
  class Grapher extends React.Component {
26730
26708
  constructor(...args) {
@@ -26940,49 +26918,41 @@ class Group extends React.Component {
26940
26918
  this.change = (...args) => {
26941
26919
  return change.apply(this, args);
26942
26920
  };
26943
- this.getUserInput = () => {
26944
- var _this$rendererRef;
26945
- return (_this$rendererRef = this.rendererRef) == null ? void 0 : _this$rendererRef.getUserInput();
26946
- };
26947
26921
  this.getSerializedState = () => {
26948
- var _this$rendererRef2;
26949
- return (_this$rendererRef2 = this.rendererRef) == null ? void 0 : _this$rendererRef2.getSerializedState();
26922
+ var _this$rendererRef;
26923
+ return (_this$rendererRef = this.rendererRef) == null ? void 0 : _this$rendererRef.getSerializedState();
26950
26924
  };
26951
26925
  this.restoreSerializedState = (state, callback) => {
26952
- var _this$rendererRef3;
26953
- (_this$rendererRef3 = this.rendererRef) == null || _this$rendererRef3.restoreSerializedState(state, callback);
26926
+ var _this$rendererRef2;
26927
+ (_this$rendererRef2 = this.rendererRef) == null || _this$rendererRef2.restoreSerializedState(state, callback);
26954
26928
  // Tell our renderer that we have no props to change
26955
26929
  // (all our changes were in state):
26956
26930
  return null;
26957
26931
  };
26958
- this.simpleValidate = rubric => {
26959
- var _this$rendererRef4;
26960
- return (_this$rendererRef4 = this.rendererRef) == null ? void 0 : _this$rendererRef4.score();
26961
- };
26962
26932
  // Mobile API:
26963
26933
  this.getInputPaths = () => {
26964
- var _this$rendererRef5;
26965
- return (_this$rendererRef5 = this.rendererRef) == null ? void 0 : _this$rendererRef5.getInputPaths();
26934
+ var _this$rendererRef3;
26935
+ return (_this$rendererRef3 = this.rendererRef) == null ? void 0 : _this$rendererRef3.getInputPaths();
26966
26936
  };
26967
26937
  this.setInputValue = (path, newValue, callback) => {
26968
- var _this$rendererRef6;
26969
- return (_this$rendererRef6 = this.rendererRef) == null ? void 0 : _this$rendererRef6.setInputValue(path, newValue, callback);
26938
+ var _this$rendererRef4;
26939
+ return (_this$rendererRef4 = this.rendererRef) == null ? void 0 : _this$rendererRef4.setInputValue(path, newValue, callback);
26970
26940
  };
26971
26941
  this.focus = () => {
26972
- var _this$rendererRef7;
26973
- return (_this$rendererRef7 = this.rendererRef) == null ? void 0 : _this$rendererRef7.focus();
26942
+ var _this$rendererRef5;
26943
+ return (_this$rendererRef5 = this.rendererRef) == null ? void 0 : _this$rendererRef5.focus();
26974
26944
  };
26975
26945
  this.focusInputPath = path => {
26976
- var _this$rendererRef8;
26977
- (_this$rendererRef8 = this.rendererRef) == null || _this$rendererRef8.focusPath(path);
26946
+ var _this$rendererRef6;
26947
+ (_this$rendererRef6 = this.rendererRef) == null || _this$rendererRef6.focusPath(path);
26978
26948
  };
26979
26949
  this.blurInputPath = path => {
26980
- var _this$rendererRef9;
26981
- (_this$rendererRef9 = this.rendererRef) == null || _this$rendererRef9.blurPath(path);
26950
+ var _this$rendererRef7;
26951
+ (_this$rendererRef7 = this.rendererRef) == null || _this$rendererRef7.blurPath(path);
26982
26952
  };
26983
26953
  this.showRationalesForCurrentlySelectedChoices = () => {
26984
- var _this$rendererRef10;
26985
- (_this$rendererRef10 = this.rendererRef) == null || _this$rendererRef10.showRationalesForCurrentlySelectedChoices();
26954
+ var _this$rendererRef8;
26955
+ (_this$rendererRef8 = this.rendererRef) == null || _this$rendererRef8.showRationalesForCurrentlySelectedChoices();
26986
26956
  };
26987
26957
  }
26988
26958
  componentDidMount() {
@@ -26991,6 +26961,14 @@ class Group extends React.Component {
26991
26961
  // the group with the correct number.
26992
26962
  this.forceUpdate();
26993
26963
  }
26964
+ getUserInput() {
26965
+ var _this$rendererRef9;
26966
+ return (_this$rendererRef9 = this.rendererRef) == null ? void 0 : _this$rendererRef9.getUserInput();
26967
+ }
26968
+ simpleValidate() {
26969
+ var _this$rendererRef10;
26970
+ return (_this$rendererRef10 = this.rendererRef) == null ? void 0 : _this$rendererRef10.score();
26971
+ }
26994
26972
  render() {
26995
26973
  var _this$rendererRef11;
26996
26974
  const apiOptions = _extends({}, ApiOptions.defaults, this.props.apiOptions, {
@@ -27258,15 +27236,16 @@ class ImageWidget extends React.Component {
27258
27236
  this.change = (...args) => {
27259
27237
  return change.apply(this, args);
27260
27238
  };
27261
- this.getUserInput = () => {
27262
- return null;
27263
- };
27264
27239
  this.focus = () => {};
27265
27240
  }
27266
27241
  // TODO (LEMS-2396): remove validation logic from widgets that don't validate
27267
27242
  static validate() {
27268
27243
  return noopValidator();
27269
27244
  }
27245
+ getUserInput() {
27246
+ return null;
27247
+ }
27248
+
27270
27249
  // TODO (LEMS-2396): remove validation logic from widgets that don't validate
27271
27250
  simpleValidate() {
27272
27251
  return noopValidator();
@@ -27578,13 +27557,6 @@ class Interaction extends React.Component {
27578
27557
  this.change = (...args) => {
27579
27558
  return change.apply(this, args);
27580
27559
  };
27581
- this.getUserInput = () => {
27582
- // TODO(eater): Perhaps we want to be able to record the state of the
27583
- // user's interaction. Unfortunately sending all the props will
27584
- // probably make the attempt payload too large. So for now, don't send
27585
- // anything.
27586
- return {};
27587
- };
27588
27560
  }
27589
27561
  // TODO (LEMS-2396): remove validation logic from widgets that don't validate
27590
27562
  static validate() {
@@ -27608,6 +27580,14 @@ class Interaction extends React.Component {
27608
27580
  });
27609
27581
  }
27610
27582
  }
27583
+ getUserInput() {
27584
+ // TODO(eater): Perhaps we want to be able to record the state of the
27585
+ // user's interaction. Unfortunately sending all the props will
27586
+ // probably make the attempt payload too large. So for now, don't send
27587
+ // anything.
27588
+ return {};
27589
+ }
27590
+
27611
27591
  // TODO (LEMS-2396): remove validation logic from widgets that don't validate
27612
27592
  simpleValidate() {
27613
27593
  return noopValidator();
@@ -29662,7 +29642,7 @@ function GraphLockedLabelsLayer(props) {
29662
29642
  lockedFigures
29663
29643
  } = props;
29664
29644
  return lockedFigures.map((figure, i) => {
29665
- var _flags$mafs, _flags$mafs2;
29645
+ var _flags$mafs, _flags$mafs2, _flags$mafs3, _flags$mafs4, _flags$mafs5;
29666
29646
  if (figure.type === "label") {
29667
29647
  return /*#__PURE__*/React.createElement(LockedLabel, _extends({
29668
29648
  key: `label-${i}`
@@ -29672,14 +29652,21 @@ function GraphLockedLabelsLayer(props) {
29672
29652
  // Point flag + point type
29673
29653
  flags != null && (_flags$mafs = flags["mafs"]) != null && _flags$mafs["locked-point-labels"] && figure.type === "point" ||
29674
29654
  // Line flag + line type
29675
- flags != null && (_flags$mafs2 = flags["mafs"]) != null && _flags$mafs2["locked-line-labels"] && figure.type === "line") {
29655
+ flags != null && (_flags$mafs2 = flags["mafs"]) != null && _flags$mafs2["locked-line-labels"] && figure.type === "line" ||
29656
+ // Vector flag + vector type
29657
+ flags != null && (_flags$mafs3 = flags["mafs"]) != null && _flags$mafs3["locked-vector-labels"] && figure.type === "vector" ||
29658
+ // Ellipse flag + ellipse type
29659
+ flags != null && (_flags$mafs4 = flags["mafs"]) != null && _flags$mafs4["locked-ellipse-labels"] && figure.type === "ellipse" ||
29660
+ // Function flag + function type
29661
+ flags != null && (_flags$mafs5 = flags["mafs"]) != null && _flags$mafs5["locked-function-labels"] && figure.type === "function") {
29662
+ var _figure$labels, _figure$points$0$labe, _figure$points$1$labe;
29676
29663
  return /*#__PURE__*/React.createElement(React.Fragment, {
29677
29664
  key: i
29678
- }, figure.labels.map((label, j) => /*#__PURE__*/React.createElement(LockedLabel, _extends({
29665
+ }, (_figure$labels = figure.labels) == null ? void 0 : _figure$labels.map((label, j) => /*#__PURE__*/React.createElement(LockedLabel, _extends({
29679
29666
  key: `${i}-label-${j}`
29680
- }, label))), figure.type === "line" && /*#__PURE__*/React.createElement(React.Fragment, null, figure.points[0].labels.map((label, k) => /*#__PURE__*/React.createElement(LockedLabel, _extends({
29667
+ }, label))), figure.type === "line" && /*#__PURE__*/React.createElement(React.Fragment, null, (_figure$points$0$labe = figure.points[0].labels) == null ? void 0 : _figure$points$0$labe.map((label, k) => /*#__PURE__*/React.createElement(LockedLabel, _extends({
29681
29668
  key: `locked-figure-${i}-point-1-label-${k}`
29682
- }, label))), figure.points[1].labels.map((label, k) => /*#__PURE__*/React.createElement(LockedLabel, _extends({
29669
+ }, label))), (_figure$points$1$labe = figure.points[1].labels) == null ? void 0 : _figure$points$1$labe.map((label, k) => /*#__PURE__*/React.createElement(LockedLabel, _extends({
29683
29670
  key: `locked-figure-${i}-point-2-label-${k}`
29684
29671
  }, label)))));
29685
29672
  }
@@ -31777,7 +31764,7 @@ function Protractor() {
31777
31764
  const [[xMin, xMax], [yMin, yMax]] = range;
31778
31765
  // Position the protractor in the center of the graph (horizontally), and
31779
31766
  // 95% of the way to the bottom of the graph (vertically).
31780
- const initialCenter = [lerp(xMin, xMax, 0.5), lerp(yMin, yMax, 0.05)];
31767
+ const initialCenter = [lerp(xMin, xMax, 0.5), lerp(yMin, yMax, 0.25)];
31781
31768
  const [center, setCenter] = useState(initialCenter);
31782
31769
  const [rotationHandleOffset, setRotationHandleOffset] = useState(centerToRotationHandle);
31783
31770
  const draggableRef = useRef(null);
@@ -31939,6 +31926,9 @@ const MafsGraph = props => {
31939
31926
  x: viewboxX,
31940
31927
  y: viewboxY
31941
31928
  };
31929
+ const {
31930
+ strings
31931
+ } = usePerseusI18n();
31942
31932
  return /*#__PURE__*/React.createElement(GraphConfigContext.Provider, {
31943
31933
  value: {
31944
31934
  range: state.range,
@@ -32053,10 +32043,11 @@ const MafsGraph = props => {
32053
32043
  top: "50%",
32054
32044
  transform: "translateY(-50%)"
32055
32045
  }
32056
- }, /*#__PURE__*/React.createElement(LabelMedium, null, "Press ", /*#__PURE__*/React.createElement("strong", null, "Shift + Enter"), " to interact with the graph"))), renderGraphControls({
32046
+ }, /*#__PURE__*/React.createElement(LabelMedium, null, strings.graphKeyboardPrompt))), renderGraphControls({
32057
32047
  state,
32058
32048
  dispatch,
32059
- width
32049
+ width,
32050
+ strings
32060
32051
  })));
32061
32052
  };
32062
32053
  const renderPointGraphControls = props => {
@@ -32065,6 +32056,10 @@ const renderPointGraphControls = props => {
32065
32056
  showRemovePointButton,
32066
32057
  focusedPointIndex
32067
32058
  } = props.state;
32059
+ const {
32060
+ strings
32061
+ } = props;
32062
+ const shouldShowRemoveButton = showRemovePointButton && focusedPointIndex !== null;
32068
32063
  return /*#__PURE__*/React.createElement(View, {
32069
32064
  style: {
32070
32065
  flexDirection: "row",
@@ -32080,7 +32075,7 @@ const renderPointGraphControls = props => {
32080
32075
  onClick: () => {
32081
32076
  props.dispatch(actions.pointGraph.addPoint([0, 0]));
32082
32077
  }
32083
- }, "Add Point"), interactionMode === "mouse" && showRemovePointButton && focusedPointIndex !== null && /*#__PURE__*/React.createElement(Button, {
32078
+ }, strings.addPoint), interactionMode === "mouse" && /*#__PURE__*/React.createElement(Button, {
32084
32079
  id: REMOVE_BUTTON_ID,
32085
32080
  kind: "secondary",
32086
32081
  color: "destructive"
@@ -32090,18 +32085,20 @@ const renderPointGraphControls = props => {
32090
32085
  tabIndex: -1,
32091
32086
  style: {
32092
32087
  width: "100%",
32093
- marginLeft: "20px"
32088
+ marginLeft: "20px",
32089
+ visibility: shouldShowRemoveButton ? "visible" : "hidden"
32094
32090
  },
32095
32091
  onClick: event => {
32096
32092
  props.dispatch(actions.pointGraph.removePoint(props.state.focusedPointIndex));
32097
32093
  }
32098
- }, "Remove Point"));
32094
+ }, strings.removePoint));
32099
32095
  };
32100
32096
  const renderGraphControls = props => {
32101
32097
  const {
32102
32098
  state,
32103
32099
  dispatch,
32104
- width
32100
+ width,
32101
+ strings
32105
32102
  } = props;
32106
32103
  const {
32107
32104
  type
@@ -32112,7 +32109,8 @@ const renderGraphControls = props => {
32112
32109
  return renderPointGraphControls({
32113
32110
  state,
32114
32111
  dispatch,
32115
- width
32112
+ width,
32113
+ strings
32116
32114
  });
32117
32115
  }
32118
32116
  return null;
@@ -34696,20 +34694,19 @@ class InteractiveGraph extends React.Component {
34696
34694
  super(...args);
34697
34695
  this.legacyGraphRef = /*#__PURE__*/React.createRef();
34698
34696
  this.mafsRef = /*#__PURE__*/React.createRef();
34699
- this.getUserInput = () => {
34700
- if (this.mafsRef.current) {
34701
- var _this$mafsRef$current, _this$mafsRef$current2;
34702
- return (_this$mafsRef$current = (_this$mafsRef$current2 = this.mafsRef.current).getUserInput) == null ? void 0 : _this$mafsRef$current.call(_this$mafsRef$current2);
34703
- }
34704
- if (this.legacyGraphRef.current) {
34705
- return this.legacyGraphRef.current.getUserInput();
34706
- }
34707
- throw new PerseusError("Cannot getUserInput from a graph that has never rendered", Errors.NotAllowed);
34708
- };
34697
+ }
34698
+ getUserInput() {
34699
+ var _this$mafsRef$current, _this$legacyGraphRef$;
34700
+ if ((_this$mafsRef$current = this.mafsRef.current) != null && _this$mafsRef$current.getUserInput) {
34701
+ return this.mafsRef.current.getUserInput();
34702
+ }
34703
+ if ((_this$legacyGraphRef$ = this.legacyGraphRef.current) != null && _this$legacyGraphRef$.getUserInput) {
34704
+ return this.legacyGraphRef.current.getUserInput();
34705
+ }
34706
+ throw new PerseusError("Cannot getUserInput from a graph that has never rendered", Errors.NotAllowed);
34709
34707
  }
34710
34708
  simpleValidate(rubric) {
34711
- var _this$getUserInput;
34712
- return InteractiveGraph.validate((_this$getUserInput = this.getUserInput) == null ? void 0 : _this$getUserInput.call(this), rubric, this);
34709
+ return InteractiveGraph.validate(this.getUserInput(), rubric, this);
34713
34710
  }
34714
34711
  render() {
34715
34712
  var _this$props$apiOption;
@@ -36387,6 +36384,57 @@ const styles$h = StyleSheet.create({
36387
36384
  }
36388
36385
  });
36389
36386
 
36387
+ // Question state for marker as result of user selected answers.
36388
+
36389
+ function scoreMarker(marker) {
36390
+ const score = {
36391
+ hasAnswers: false,
36392
+ isCorrect: false
36393
+ };
36394
+ if (marker.selected && marker.selected.length > 0) {
36395
+ score.hasAnswers = true;
36396
+ }
36397
+ if (marker.answers.length > 0) {
36398
+ if (marker.selected && marker.selected.length === marker.answers.length) {
36399
+ // All correct answers are selected by the user.
36400
+ score.isCorrect = marker.selected.every(choice => marker.answers.includes(choice));
36401
+ }
36402
+ } else if (!marker.selected || marker.selected.length === 0) {
36403
+ // Correct as no answers should be selected by the user.
36404
+ score.isCorrect = true;
36405
+ }
36406
+ return score;
36407
+ }
36408
+ function labelImageValidator(state, rubric) {
36409
+ let numAnswered = 0;
36410
+ let numCorrect = 0;
36411
+ for (const marker of state.markers) {
36412
+ const score = scoreMarker(marker);
36413
+ if (score.hasAnswers) {
36414
+ numAnswered++;
36415
+ }
36416
+ if (score.isCorrect) {
36417
+ numCorrect++;
36418
+ }
36419
+ }
36420
+
36421
+ // We expect all question markers to be answered before grading.
36422
+ if (numAnswered !== state.markers.length) {
36423
+ return {
36424
+ type: "invalid",
36425
+ message: null
36426
+ };
36427
+ }
36428
+ return {
36429
+ type: "points",
36430
+ // Markers with no expected answers are graded as correct if user
36431
+ // makes no answer selection.
36432
+ earned: numCorrect === state.markers.length ? 1 : 0,
36433
+ total: 1,
36434
+ message: null
36435
+ };
36436
+ }
36437
+
36390
36438
  const BringToFront = {
36391
36439
  boxShadow: `0 8px 8px ${color.offBlack64}`,
36392
36440
  zIndex: 1000
@@ -36645,57 +36693,11 @@ const styles$f = StyleSheet.create({
36645
36693
  * No diagonals allowed.
36646
36694
  */
36647
36695
 
36696
+ // TODO: should this be using WidgetProps / PerseusLabelImageWidgetOptions?
36697
+
36648
36698
  class LabelImage extends React.Component {
36649
- static gradeMarker(marker) {
36650
- const score = {
36651
- hasAnswers: false,
36652
- isCorrect: false
36653
- };
36654
- if (marker.selected && marker.selected.length > 0) {
36655
- // @ts-expect-error - TS2540 - Cannot assign to 'hasAnswers' because it is a read-only property.
36656
- score.hasAnswers = true;
36657
- }
36658
- if (marker.answers.length > 0) {
36659
- if (marker.selected && marker.selected.length === marker.answers.length) {
36660
- // All correct answers are selected by the user.
36661
- // @ts-expect-error - TS2540 - Cannot assign to 'isCorrect' because it is a read-only property.
36662
- score.isCorrect = marker.selected.every(choice => marker.answers.includes(choice));
36663
- }
36664
- } else if (!marker.selected || marker.selected.length === 0) {
36665
- // Correct as no answers should be selected by the user.
36666
- // @ts-expect-error - TS2540 - Cannot assign to 'isCorrect' because it is a read-only property.
36667
- score.isCorrect = true;
36668
- }
36669
- return score;
36670
- }
36671
36699
  static validate(state, rubric) {
36672
- let numAnswered = 0;
36673
- let numCorrect = 0;
36674
- for (const marker of state.markers) {
36675
- const score = LabelImage.gradeMarker(marker);
36676
- if (score.hasAnswers) {
36677
- numAnswered++;
36678
- }
36679
- if (score.isCorrect) {
36680
- numCorrect++;
36681
- }
36682
- }
36683
-
36684
- // We expect all question markers to be answered before grading.
36685
- if (numAnswered !== state.markers.length) {
36686
- return {
36687
- type: "invalid",
36688
- message: null
36689
- };
36690
- }
36691
- return {
36692
- type: "points",
36693
- // Markers with no expected answers are graded as correct if user
36694
- // makes no answer selection.
36695
- earned: numCorrect === state.markers.length ? 1 : 0,
36696
- total: 1,
36697
- message: null
36698
- };
36700
+ return labelImageValidator(state);
36699
36701
  }
36700
36702
 
36701
36703
  /**
@@ -36893,7 +36895,7 @@ class LabelImage extends React.Component {
36893
36895
  this._mounted = false;
36894
36896
  }
36895
36897
  simpleValidate(rubric) {
36896
- return LabelImage.validate(this.getUserInput(), rubric);
36898
+ return labelImageValidator(this.getUserInput());
36897
36899
  }
36898
36900
  getUserInput() {
36899
36901
  const {
@@ -36911,7 +36913,7 @@ class LabelImage extends React.Component {
36911
36913
  onChange
36912
36914
  } = this.props;
36913
36915
  const updatedMarkers = markers.map(marker => {
36914
- const score = LabelImage.gradeMarker(marker);
36916
+ const score = scoreMarker(marker);
36915
36917
  return _extends({}, marker, {
36916
36918
  // Reveal correctness state for markers with answers.
36917
36919
  showCorrectness: score.hasAnswers ? score.isCorrect ? "correct" : "incorrect" : undefined
@@ -37058,7 +37060,7 @@ class LabelImage extends React.Component {
37058
37060
  bottom: "top"
37059
37061
  }[markerPosition];
37060
37062
  }
37061
- const score = LabelImage.gradeMarker(marker);
37063
+ const score = scoreMarker(marker);
37062
37064
  // Once the question is answered, show markers
37063
37065
  // with correct answers, otherwise passthrough
37064
37066
  // the correctness state.
@@ -37257,9 +37259,7 @@ const LabelImageWithDependencies = /*#__PURE__*/React.forwardRef((props, ref) =>
37257
37259
  // methods and instead adjust Peresus to provide these facilities through
37258
37260
  // instance methods on our Renderers.
37259
37261
  // @ts-expect-error - TS2339 - Property 'validate' does not exist on type
37260
- LabelImageWithDependencies.validate = LabelImage.validate;
37261
- // @ts-expect-error - TS2339 - Property 'gradeMarker' does not exist on type
37262
- LabelImageWithDependencies.gradeMarker = LabelImage.gradeMarker;
37262
+ LabelImageWithDependencies.validate = labelImageValidator;
37263
37263
  var LabelImage$1 = {
37264
37264
  name: "label-image",
37265
37265
  displayName: "Label Image",
@@ -38039,10 +38039,10 @@ class Matcher extends React.Component {
38039
38039
  // @ts-expect-error - TS2339 - Property 'moveOptionToIndex' does not exist on type 'ReactInstance'.
38040
38040
  this.refs.right.moveOptionToIndex(option, index);
38041
38041
  };
38042
- this.simpleValidate = rubric => {
38043
- // @ts-expect-error - TS2339 - Property 'validate' does not exist on type 'typeof Matcher'.
38044
- return Matcher.validate(this.getUserInput(), rubric);
38045
- };
38042
+ }
38043
+ simpleValidate(rubric) {
38044
+ // @ts-expect-error - TS2339 - Property 'validate' does not exist on type 'typeof Matcher'.
38045
+ return Matcher.validate(this.getUserInput(), rubric);
38046
38046
  }
38047
38047
  render() {
38048
38048
  // To minimize layout shift, we display a spinner until our math
@@ -38406,14 +38406,6 @@ class Matrix extends React.Component {
38406
38406
  }, cb);
38407
38407
  this.props.trackInteraction();
38408
38408
  };
38409
- this.getUserInput = () => {
38410
- return {
38411
- answers: this.props.answers
38412
- };
38413
- };
38414
- this.simpleValidate = rubric => {
38415
- return Matrix.validate(this.getUserInput(), rubric, this.context.strings);
38416
- };
38417
38409
  }
38418
38410
  static validate(state, rubric, strings) {
38419
38411
  const solution = rubric.answers;
@@ -38431,7 +38423,9 @@ class Matrix extends React.Component {
38431
38423
  hasEmptyCell = true;
38432
38424
  }
38433
38425
  if (!incorrectSize) {
38434
- const validator = createValidator(solution[row][col], {
38426
+ const validator = createValidator(
38427
+ // @ts-expect-error - TS2345 - Argument of type 'number' is not assignable to parameter of type 'string'.
38428
+ solution[row][col], {
38435
38429
  simplify: true
38436
38430
  }, strings);
38437
38431
  const result = validator(supplied[row][col]);
@@ -38470,6 +38464,14 @@ class Matrix extends React.Component {
38470
38464
  // Used in the `onBlur` and `onFocus` handlers
38471
38465
  this.cursorPosition = [0, 0];
38472
38466
  }
38467
+ getUserInput() {
38468
+ return {
38469
+ answers: this.props.answers
38470
+ };
38471
+ }
38472
+ simpleValidate(rubric) {
38473
+ return Matrix.validate(this.getUserInput(), rubric, this.context.strings);
38474
+ }
38473
38475
  render() {
38474
38476
  // Set the input sizes through JS so we can control the size of the
38475
38477
  // brackets. (If we set them in CSS we won't know values until the
@@ -39680,16 +39682,14 @@ class Molecule extends React.Component {
39680
39682
  }
39681
39683
  Molecule.contextType = PerseusI18nContext;
39682
39684
  class MoleculeWidget extends React.Component {
39683
- constructor(...args) {
39684
- super(...args);
39685
- this.getUserInput = () => {
39686
- return [];
39687
- };
39688
- }
39689
39685
  // TODO (LEMS-2396): remove validation logic from widgets that don't validate
39690
39686
  simpleValidate() {
39691
39687
  return noopValidator();
39692
39688
  }
39689
+ getUserInput() {
39690
+ return [];
39691
+ }
39692
+
39693
39693
  // TODO (LEMS-2396): remove validation logic from widgets that don't validate
39694
39694
  validate() {
39695
39695
  return noopValidator();
@@ -39837,6 +39837,9 @@ const TickMarks = Graphie.createSimpleClass((graphie, props) => {
39837
39837
  });
39838
39838
  return results;
39839
39839
  });
39840
+
39841
+ // TODO: most widgets use some like Widget<Something, PerseusNumberLineWidgetOptions>
39842
+ // should this one?
39840
39843
  class NumberLine extends React.Component {
39841
39844
  constructor(..._args) {
39842
39845
  super(..._args);
@@ -40116,19 +40119,19 @@ class NumberLine extends React.Component {
40116
40119
  arrows: "->"
40117
40120
  });
40118
40121
  };
40119
- this.getUserInput = () => {
40120
- return {
40121
- numLinePosition: this.props.numLinePosition,
40122
- rel: this.props.isInequality ? this.props.rel : "eq",
40123
- numDivisions: this.props.numDivisions,
40124
- divisionRange: this.props.divisionRange
40125
- };
40126
- };
40127
- this.simpleValidate = rubric => {
40128
- // @ts-expect-error - TS2339 - Property 'validate' does not exist on type 'typeof NumberLine'.
40129
- return NumberLine.validate(this.getUserInput(), rubric);
40122
+ }
40123
+ getUserInput() {
40124
+ return {
40125
+ numLinePosition: this.props.numLinePosition,
40126
+ rel: this.props.isInequality ? this.props.rel : "eq",
40127
+ numDivisions: this.props.numDivisions,
40128
+ divisionRange: this.props.divisionRange
40130
40129
  };
40131
40130
  }
40131
+ simpleValidate(rubric) {
40132
+ // @ts-expect-error - TS2339 - Property 'validate' does not exist on type 'typeof NumberLine'.
40133
+ return NumberLine.validate(this.getUserInput(), rubric);
40134
+ }
40132
40135
  render() {
40133
40136
  const {
40134
40137
  strings
@@ -40203,6 +40206,8 @@ _.extend(NumberLine, {
40203
40206
  const correctRel = rubric.correctRel || "eq";
40204
40207
  const correctPos = number.equal(state.numLinePosition, rubric.correctX || 0);
40205
40208
  const outsideAllowedRange = state.numDivisions > divisionRange[1] || state.numDivisions < divisionRange[0];
40209
+
40210
+ // TODO: I don't think isTickCrtl is a thing anymore
40206
40211
  if (state.isTickCrtl && outsideAllowedRange) {
40207
40212
  return {
40208
40213
  type: "invalid",
@@ -40459,8 +40464,8 @@ Card.defaultProps = {
40459
40464
  animating: false,
40460
40465
  linterContext: linterContextDefault
40461
40466
  };
40462
- const NORMAL = "normal",
40463
- HORIZONTAL = "horizontal";
40467
+ const NORMAL = "normal";
40468
+ const HORIZONTAL = "horizontal";
40464
40469
  class Orderer extends React.Component {
40465
40470
  constructor(...args) {
40466
40471
  super(...args);
@@ -40701,17 +40706,6 @@ class Orderer extends React.Component {
40701
40706
  current: list
40702
40707
  });
40703
40708
  };
40704
- this.getUserInput = () => {
40705
- return {
40706
- current: _.map(this.props.current, function (v) {
40707
- return v.content;
40708
- })
40709
- };
40710
- };
40711
- this.simpleValidate = rubric => {
40712
- // @ts-expect-error - TS2339 - Property 'validate' does not exist on type 'typeof Orderer'.
40713
- return Orderer.validate(this.getUserInput(), rubric);
40714
- };
40715
40709
  }
40716
40710
  UNSAFE_componentWillReceiveProps(nextProps) {
40717
40711
  if (!_.isEqual(this.props.current, nextProps.current)) {
@@ -40720,6 +40714,17 @@ class Orderer extends React.Component {
40720
40714
  });
40721
40715
  }
40722
40716
  }
40717
+ getUserInput() {
40718
+ return {
40719
+ current: _.map(this.props.current, function (v) {
40720
+ return v.content;
40721
+ })
40722
+ };
40723
+ }
40724
+ simpleValidate(rubric) {
40725
+ // @ts-expect-error - TS2339 - Property 'validate' does not exist on type 'typeof Orderer'.
40726
+ return Orderer.validate(this.getUserInput(), rubric);
40727
+ }
40723
40728
  render() {
40724
40729
  // This is the card we are currently dragging
40725
40730
  const dragging = this.state.dragging && /*#__PURE__*/React.createElement(Card
@@ -40833,14 +40838,14 @@ Orderer.defaultProps = {
40833
40838
  linterContext: linterContextDefault
40834
40839
  };
40835
40840
  _.extend(Orderer, {
40836
- validate: function (state, rubric) {
40837
- if (state.current.length === 0) {
40841
+ validate: function (userInput, rubric) {
40842
+ if (userInput.current.length === 0) {
40838
40843
  return {
40839
40844
  type: "invalid",
40840
40845
  message: null
40841
40846
  };
40842
40847
  }
40843
- const correct = _.isEqual(state.current, _.pluck(rubric.correctOptions, "content"));
40848
+ const correct = _.isEqual(userInput.current, _.pluck(rubric.correctOptions, "content"));
40844
40849
  return {
40845
40850
  type: "points",
40846
40851
  earned: correct ? 1 : 0,
@@ -43039,10 +43044,6 @@ class PassageRefTarget extends React.Component {
43039
43044
  constructor(..._args) {
43040
43045
  super(..._args);
43041
43046
  // TODO passage-ref-target isn't interactive; remove
43042
- this.getUserInput = () => {
43043
- return;
43044
- };
43045
- // TODO passage-ref-target isn't interactive; remove
43046
43047
  this.change = (...args) => {
43047
43048
  return change.apply(this, args);
43048
43049
  };
@@ -43051,6 +43052,11 @@ class PassageRefTarget extends React.Component {
43051
43052
  static validate() {
43052
43053
  return noopValidator();
43053
43054
  }
43055
+
43056
+ // TODO passage-ref-target isn't interactive; remove
43057
+ getUserInput() {
43058
+ return null;
43059
+ }
43054
43060
  // TODO (LEMS-2396): remove validation logic from widgets that don't validate
43055
43061
  simpleValidate() {
43056
43062
  return noopValidator();
@@ -43095,9 +43101,6 @@ var PassageRefTarget$1 = {
43095
43101
  * This is a PhET simulation widget. It is used for rendering simulations
43096
43102
  * from https://phet.colorado.edu/.
43097
43103
  */
43098
-
43099
- // For returning user input, but currently the PhET widget
43100
- // does not support accessing user input
43101
43104
  // This renders the PhET sim
43102
43105
  class PhetSimulation extends React.Component {
43103
43106
  constructor(props) {
@@ -43146,6 +43149,8 @@ class PhetSimulation extends React.Component {
43146
43149
  await this.updateSimState(this.props.url);
43147
43150
  }
43148
43151
  }
43152
+
43153
+ // TODO (LEMS-2396): remove validation logic from widgets that don't validate
43149
43154
  getUserInput() {
43150
43155
  return null;
43151
43156
  }
@@ -44031,13 +44036,6 @@ class Plotter extends React.Component {
44031
44036
  });
44032
44037
  });
44033
44038
  };
44034
- this.getUserInput = () => {
44035
- return this.state.values;
44036
- };
44037
- this.simpleValidate = rubric => {
44038
- // @ts-expect-error - TS2339 - Property 'validate' does not exist on type 'typeof Plotter'.
44039
- return Plotter.validate(this.getUserInput(), rubric);
44040
- };
44041
44039
  }
44042
44040
  componentDidMount() {
44043
44041
  this._isMounted = true;
@@ -44062,6 +44060,13 @@ class Plotter extends React.Component {
44062
44060
  componentWillUnmount() {
44063
44061
  this._isMounted = false;
44064
44062
  }
44063
+ getUserInput() {
44064
+ return this.state.values;
44065
+ }
44066
+ simpleValidate(rubric) {
44067
+ // @ts-expect-error - TS2339 - Property 'validate' does not exist on type 'typeof Plotter'.
44068
+ return Plotter.validate(this.getUserInput(), rubric);
44069
+ }
44065
44070
  render() {
44066
44071
  // TODO(kevinb) actually compute the size of the graphie correctly and
44067
44072
  // make it that size so we don't have to add extra padding. The value
@@ -44094,8 +44099,8 @@ Plotter.defaultProps = {
44094
44099
  labelInterval: 1
44095
44100
  };
44096
44101
  _.extend(Plotter, {
44097
- validate: function (guess, rubric) {
44098
- if (deepEq(guess, rubric.starting)) {
44102
+ validate: function (userInput, rubric) {
44103
+ if (deepEq(userInput, rubric.starting)) {
44099
44104
  return {
44100
44105
  type: "invalid",
44101
44106
  message: null
@@ -44103,7 +44108,7 @@ _.extend(Plotter, {
44103
44108
  }
44104
44109
  return {
44105
44110
  type: "points",
44106
- earned: deepEq(guess, rubric.correct) ? 1 : 0,
44111
+ earned: deepEq(userInput, rubric.correct) ? 1 : 0,
44107
44112
  total: 1,
44108
44113
  message: null
44109
44114
  };
@@ -44166,6 +44171,28 @@ var PythonProgram$1 = {
44166
44171
  widget: PythonProgram
44167
44172
  };
44168
44173
 
44174
+ function sorterValidator(userInput, rubric) {
44175
+ // If the sorter widget hasn't been changed yet, we treat it as "empty" which
44176
+ // prevents the "Check" button from becoming active. We want the user
44177
+ // to make a change before trying to move forward. This makes an
44178
+ // assumption that the initial order isn't the correct order! However,
44179
+ // this should be rare if it happens, and interacting with the list
44180
+ // will enable the button, so they won't be locked out of progressing.
44181
+ if (!userInput.changed) {
44182
+ return {
44183
+ type: "invalid",
44184
+ message: null
44185
+ };
44186
+ }
44187
+ const correct = Util.deepEq(userInput.options, rubric.correct);
44188
+ return {
44189
+ type: "points",
44190
+ earned: correct ? 1 : 0,
44191
+ total: 1,
44192
+ message: null
44193
+ };
44194
+ }
44195
+
44169
44196
  const {
44170
44197
  shuffle
44171
44198
  } = Util;
@@ -44195,42 +44222,14 @@ class Sorter extends React.Component {
44195
44222
  }, 0);
44196
44223
  });
44197
44224
  };
44198
- this.getUserInput = () => {
44199
- // eslint-disable-next-line react/no-string-refs
44200
- // @ts-expect-error - TS2339 - Property 'getOptions' does not exist on type 'ReactInstance'.
44201
- return {
44202
- options: this.refs.sortable.getOptions()
44203
- };
44204
- };
44205
44225
  this.moveOptionToIndex = (option, index) => {
44206
44226
  // eslint-disable-next-line react/no-string-refs
44207
44227
  // @ts-expect-error - TS2339 - Property 'moveOptionToIndex' does not exist on type 'ReactInstance'.
44208
44228
  this.refs.sortable.moveOptionToIndex(option, index);
44209
44229
  };
44210
- this.simpleValidate = rubric => {
44211
- // If this widget hasn't been changed yet, we treat it as "empty" which
44212
- // prevents the "Check" button from becoming active. We want the user
44213
- // to make a change before trying to move forward. This makes an
44214
- // assumption that the initial order isn't the correct order! However,
44215
- // this should be rare if it happens, and interacting with the list
44216
- // will enable the button, so they won't be locked out of progressing.
44217
- if (!this.state.changed) {
44218
- return {
44219
- type: "invalid",
44220
- message: null
44221
- };
44222
- }
44223
- return Sorter.validate(this.getUserInput(), rubric);
44224
- };
44225
44230
  }
44226
- static validate(state, rubric) {
44227
- const correct = _.isEqual(state.options, rubric.correct);
44228
- return {
44229
- type: "points",
44230
- earned: correct ? 1 : 0,
44231
- total: 1,
44232
- message: null
44233
- };
44231
+ static validate(userInput, rubric) {
44232
+ return sorterValidator(userInput, rubric);
44234
44233
  }
44235
44234
  componentDidMount() {
44236
44235
  this._isMounted = true;
@@ -44238,6 +44237,18 @@ class Sorter extends React.Component {
44238
44237
  componentWillUnmount() {
44239
44238
  this._isMounted = false;
44240
44239
  }
44240
+ getUserInput() {
44241
+ // eslint-disable-next-line react/no-string-refs
44242
+ // @ts-expect-error - TS2339 - Property 'getOptions' does not exist on type 'ReactInstance'.
44243
+ const options = this.refs.sortable.getOptions();
44244
+ return {
44245
+ options,
44246
+ changed: this.state.changed
44247
+ };
44248
+ }
44249
+ simpleValidate(rubric) {
44250
+ return sorterValidator(this.getUserInput(), rubric);
44251
+ }
44241
44252
  render() {
44242
44253
  const options = shuffle(this.props.correct, this.props.problemNum, /* ensurePermuted */true);
44243
44254
  const {
@@ -44310,9 +44321,6 @@ class Table extends React.Component {
44310
44321
  this._getColumns = () => {
44311
44322
  return this.props.answers[0].length;
44312
44323
  };
44313
- this.getUserInput = () => {
44314
- return _.map(this.props.answers, _.clone);
44315
- };
44316
44324
  this.onValueChange = (row, column, eventOrValue) => {
44317
44325
  const answers = _.map(this.props.answers, _.clone);
44318
44326
 
@@ -44332,10 +44340,6 @@ class Table extends React.Component {
44332
44340
  headers: headers
44333
44341
  });
44334
44342
  };
44335
- this.simpleValidate = rubric => {
44336
- // @ts-expect-error - TS2339 - Property 'validate' does not exist on type 'typeof Table'.
44337
- return Table.validate(this.getUserInput(), rubric, this.context.strings);
44338
- };
44339
44343
  this._handleFocus = inputPath => {
44340
44344
  this.props.onFocus(inputPath);
44341
44345
  };
@@ -44402,6 +44406,13 @@ class Table extends React.Component {
44402
44406
  }, cb);
44403
44407
  };
44404
44408
  }
44409
+ getUserInput() {
44410
+ return _.map(this.props.answers, _.clone);
44411
+ }
44412
+ simpleValidate(rubric) {
44413
+ // @ts-expect-error - TS2339 - Property 'validate' does not exist on type 'typeof Table'.
44414
+ return Table.validate(this.getUserInput(), rubric, this.context.strings);
44415
+ }
44405
44416
  render() {
44406
44417
  const rows = this._getRows();
44407
44418
  const columns = this._getColumns();
@@ -44770,7 +44781,7 @@ const init = function init(options) {
44770
44781
 
44771
44782
  // This file is processed by a Rollup plugin (replace) to inject the production
44772
44783
  const libName = "@khanacademy/perseus";
44773
- const libVersion = "34.1.0";
44784
+ const libVersion = "35.0.0";
44774
44785
  addLibraryVersionToPerseusDebug(libName, libVersion);
44775
44786
 
44776
44787
  const apiVersion = {
@@ -45321,9 +45332,6 @@ const defaultContext = {
45321
45332
  };
45322
45333
  const context = /*#__PURE__*/React.createContext(defaultContext);
45323
45334
 
45324
- const {
45325
- mapObject
45326
- } = objective_;
45327
45335
  class ServerItemRenderer extends React.Component {
45328
45336
  constructor(props) {
45329
45337
  super(props);
@@ -45543,20 +45551,6 @@ class ServerItemRenderer extends React.Component {
45543
45551
  return this.questionRenderer.getWidgetIds();
45544
45552
  }
45545
45553
 
45546
- /**
45547
- * Returns an object mapping from widget ID to KE-style score.
45548
- * The keys of this object are the values of the array returned
45549
- * from `getWidgetIds`.
45550
- */
45551
- scoreWidgets() {
45552
- const qScore = this.questionRenderer.scoreWidgets();
45553
- const qGuess = this.questionRenderer.getUserInputForWidgets();
45554
- const state = this.questionRenderer.getSerializedState();
45555
- return mapObject(qScore, (score, id) => {
45556
- return Util.keScoreFromPerseusScore(score, qGuess[id], state);
45557
- });
45558
- }
45559
-
45560
45554
  /**
45561
45555
  * Get a representation of the current state of the item.
45562
45556
  */