@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/index.js CHANGED
@@ -366,7 +366,10 @@ const mockStrings = {
366
366
  cos: "cos",
367
367
  tan: "tan",
368
368
  simulationLoadFail: "Sorry, this simulation cannot load.",
369
- simulationLocaleWarning: "Sorry, this simulation isn't available in your language."
369
+ simulationLocaleWarning: "Sorry, this simulation isn't available in your language.",
370
+ addPoint: "Add Point",
371
+ removePoint: "Remove Point",
372
+ graphKeyboardPrompt: "Press Shift + Enter to interact with the graph"
370
373
  };
371
374
 
372
375
  /**
@@ -862,7 +865,7 @@ const denylist = [
862
865
  // (technically the renderer still adds them)
863
866
  "key", "ref",
864
867
  // added by src/renderer.jsx
865
- "containerSizeClass", "widgetId", "onChange", "problemNum", "apiOptions", "questionCompleted", "findWidgets",
868
+ "containerSizeClass", "widgetId", "onChange", "problemNum", "apiOptions", "widgetIsOpen", "questionCompleted", "findWidgets",
866
869
  // added by src/editor.jsx, for widgets removing themselves
867
870
  // this is soooo not the right place for this, but alas.
868
871
  "onRemove",
@@ -870,17 +873,6 @@ const denylist = [
870
873
  "id",
871
874
  // Callbacks and items for interaction handling
872
875
  "onBlur", "onFocus", "trackInteraction", "keypadElement"];
873
- const removeDenylistProps = props => {
874
- const newProps = {
875
- ...props
876
- };
877
- for (const prop of denylist) {
878
- if (prop in newProps) {
879
- delete newProps[prop];
880
- }
881
- }
882
- return newProps;
883
- };
884
876
 
885
877
  /* eslint-disable @babel/no-invalid-this */
886
878
  const USAGE = "Usage:\n" + " this.change({propName: 5}, callback);\n" + ' this.change("propName", 5, callback);\n' + ' this.change("propName")';
@@ -1142,8 +1134,6 @@ class StubTagEditor extends React__namespace.Component {
1142
1134
  * * nothing if it is purely a bug fix.
1143
1135
  *
1144
1136
  * Callbacks passed to Renderer/ItemRenderer:
1145
- * * onInputError:
1146
- * Called when there is an error grading a widget
1147
1137
  * * onFocusChange: (newFocusPath, oldFocusPath, keypadDOMNode)
1148
1138
  * Called when the user focus changes. The first two parameters are `path`
1149
1139
  * arrays uniquely identifying the respect inputs. The third parameter,
@@ -1166,7 +1156,6 @@ class StubTagEditor extends React__namespace.Component {
1166
1156
  const ApiOptions = {
1167
1157
  propTypes: PropTypes__default["default"].shape({
1168
1158
  isArticle: PropTypes__default["default"].bool.isRequired,
1169
- onInputError: PropTypes__default["default"].func.isRequired,
1170
1159
  onFocusChange: PropTypes__default["default"].func.isRequired,
1171
1160
  GroupMetadataEditor: PropTypes__default["default"].func.isRequired,
1172
1161
  showAlignmentOptions: PropTypes__default["default"].bool.isRequired,
@@ -1250,7 +1239,6 @@ const ApiOptions = {
1250
1239
  defaults: {
1251
1240
  isArticle: false,
1252
1241
  isMobile: false,
1253
- onInputError: function () {},
1254
1242
  onFocusChange: function () {},
1255
1243
  GroupMetadataEditor: StubTagEditor,
1256
1244
  showAlignmentOptions: false,
@@ -2262,10 +2250,7 @@ const getDecimalSeparator = locale => {
2262
2250
  * show the user an error. TODO(joel) - what error?
2263
2251
  * - Otherwise, pass through the resulting points and message.
2264
2252
  */
2265
- function expressionValidator(userInput, rubric) {
2266
- let onInputError = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : function () {};
2267
- let strings = arguments.length > 3 ? arguments[3] : undefined;
2268
- let locale = arguments.length > 4 ? arguments[4] : undefined;
2253
+ function expressionValidator(userInput, rubric, strings, locale) {
2269
2254
  const options = ___default["default"].clone(rubric);
2270
2255
  ___default["default"].extend(options, {
2271
2256
  decimal_separator: getDecimalSeparator(locale)
@@ -2365,14 +2350,9 @@ function expressionValidator(userInput, rubric) {
2365
2350
  };
2366
2351
  }
2367
2352
  if (matchingAnswerForm.considered === "ungraded") {
2368
- // We matched an ungraded answer form - return "invalid", which
2369
- // will let the user try again with no penalty
2370
- const apiResult = onInputError(null,
2371
- // Reserved for some widget identifier
2372
- userInput, matchMessage);
2373
2353
  return {
2374
2354
  type: "invalid",
2375
- message: apiResult === false ? null : matchMessage
2355
+ message: matchMessage
2376
2356
  };
2377
2357
  }
2378
2358
  // We matched a graded answer form, so we can now tell the user
@@ -2422,11 +2402,8 @@ class Expression extends React__namespace.Component {
2422
2402
  _isMounted = false;
2423
2403
 
2424
2404
  // TODO remove this in favor of just using expressionValidator
2425
- static validate(userInput, rubric) {
2426
- let onInputError = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : function () {};
2427
- let strings = arguments.length > 3 ? arguments[3] : undefined;
2428
- let locale = arguments.length > 4 ? arguments[4] : undefined;
2429
- return expressionValidator(userInput, rubric, onInputError, strings, locale);
2405
+ static validate(userInput, rubric, strings, locale) {
2406
+ return expressionValidator(userInput, rubric, strings, locale);
2430
2407
  }
2431
2408
  static getUserInputFromProps(props) {
2432
2409
  return normalizeTex(props.value);
@@ -2487,14 +2464,9 @@ class Expression extends React__namespace.Component {
2487
2464
  showErrorStyle: false
2488
2465
  });
2489
2466
  if (!this.parse(this.props.value, this.props).parsed) {
2490
- const apiResult = this.props.apiOptions.onInputError(null,
2491
- // reserved for some widget identifier
2492
- this.props.value, this.context.strings.ERROR_TITLE);
2493
- if (apiResult !== false) {
2494
- this.setState({
2495
- invalid: true
2496
- });
2497
- }
2467
+ this.setState({
2468
+ invalid: true
2469
+ });
2498
2470
  }
2499
2471
  }
2500
2472
  };
@@ -2508,12 +2480,12 @@ class Expression extends React__namespace.Component {
2508
2480
  });
2509
2481
  }
2510
2482
  };
2511
- simpleValidate = (_ref, onInputError) => {
2483
+ simpleValidate = _ref => {
2512
2484
  let {
2513
2485
  scoring,
2514
2486
  ...rubric
2515
2487
  } = _ref;
2516
- const score = expressionValidator(this.getUserInput(), rubric, onInputError || function () {}, this.context.strings, this.context.locale);
2488
+ const score = expressionValidator(this.getUserInput(), rubric, this.context.strings, this.context.locale);
2517
2489
 
2518
2490
  // "scoring" is a flag that indicates when we are checking answers.
2519
2491
  // otherwise, we may just be checking validity after changes.
@@ -2528,9 +2500,9 @@ class Expression extends React__namespace.Component {
2528
2500
  }
2529
2501
  return score;
2530
2502
  };
2531
- getUserInput = () => {
2503
+ getUserInput() {
2532
2504
  return Expression.getUserInputFromProps(this.props);
2533
- };
2505
+ }
2534
2506
  change = (() => {
2535
2507
  var _this = this;
2536
2508
  return function () {
@@ -14815,7 +14787,7 @@ const pluck = function (table, subKey) {
14815
14787
  * });
14816
14788
  * {a: 2, b: 3}
14817
14789
  */
14818
- const mapObject$2 = function (obj, lambda) {
14790
+ const mapObject$1 = function (obj, lambda) {
14819
14791
  const result = {};
14820
14792
  ___default["default"].each(___default["default"].keys(obj), function (key) {
14821
14793
  // @ts-expect-error - TS2345 - Argument of type 'string' is not assignable to parameter of type 'K'.
@@ -14841,7 +14813,7 @@ const mapObjectFromArray$1 = function (arr, lambda) {
14841
14813
  };
14842
14814
  var objective_ = {
14843
14815
  pluck: pluck,
14844
- mapObject: mapObject$2,
14816
+ mapObject: mapObject$1,
14845
14817
  mapObjectFromArray: mapObjectFromArray$1
14846
14818
  };
14847
14819
 
@@ -19789,7 +19761,7 @@ class WidgetContainer extends React__namespace.Component {
19789
19761
  }
19790
19762
 
19791
19763
  const {
19792
- mapObject: mapObject$1,
19764
+ mapObject,
19793
19765
  mapObjectFromArray
19794
19766
  } = objective_;
19795
19767
  const rContainsNonWhitespace = /\S/;
@@ -19817,9 +19789,6 @@ const isIdPathPrefix = function (prefixArray, wholeArray) {
19817
19789
  });
19818
19790
  };
19819
19791
 
19820
- // The return type for getUserInput. Widgets have full control of what is
19821
- // returned so it's not easily typed (some widgets return a scalar (string),
19822
- // some return a custom-built object
19823
19792
  /**
19824
19793
  * The Widget type represents the common API that the Renderer uses to interact
19825
19794
  * with all widgets. All widgets must implement the methods in this API, unless
@@ -19829,6 +19798,7 @@ const isIdPathPrefix = function (prefixArray, wholeArray) {
19829
19798
  * communicate with the individual widgets to coordinate actions such as
19830
19799
  * scoring, state serialization/deserialization, and focus management.
19831
19800
  */
19801
+
19832
19802
  class Renderer extends React__namespace.Component {
19833
19803
  _currentFocus;
19834
19804
  // @ts-expect-error - TS2564 - Property '_foundTextNodes' has no initializer and is not definitely assigned in the constructor.
@@ -20025,7 +19995,7 @@ class Renderer extends React__namespace.Component {
20025
19995
 
20026
19996
  // @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; }'.
20027
19997
  _getAllWidgetsInfo = props => {
20028
- return mapObject$1(props.widgets, (widgetInfo, widgetId) => {
19998
+ return mapObject(props.widgets, (widgetInfo, widgetId) => {
20029
19999
  if (!widgetInfo.type || !widgetInfo.alignment) {
20030
20000
  const newValues = {};
20031
20001
  if (!widgetInfo.type) {
@@ -20172,7 +20142,7 @@ class Renderer extends React__namespace.Component {
20172
20142
  * state from that instead of the current widget props.
20173
20143
  */
20174
20144
  getSerializedState = widgetProps => {
20175
- return mapObject$1(widgetProps || this.state.widgetProps, (props, widgetId) => {
20145
+ return mapObject(widgetProps || this.state.widgetProps, (props, widgetId) => {
20176
20146
  const widget = this.getWidgetInstance(widgetId);
20177
20147
  if (widget && widget.getSerializedState) {
20178
20148
  return widget.getSerializedState();
@@ -20208,7 +20178,7 @@ class Renderer extends React__namespace.Component {
20208
20178
  }
20209
20179
  };
20210
20180
  this.setState({
20211
- widgetProps: mapObject$1(serializedState, (props, widgetId) => {
20181
+ widgetProps: mapObject(serializedState, (props, widgetId) => {
20212
20182
  const widget = this.getWidgetInstance(widgetId);
20213
20183
  if (widget && widget.restoreSerializedState) {
20214
20184
  // Note that we probably can't call
@@ -21083,7 +21053,6 @@ class Renderer extends React__namespace.Component {
21083
21053
  */
21084
21054
  scoreWidgets = () => {
21085
21055
  const widgetProps = this.state.widgetInfo;
21086
- const onInputError = this.getApiOptions().onInputError;
21087
21056
  const gradedWidgetIds = ___default["default"].filter(this.widgetIds, id => {
21088
21057
  const props = widgetProps[id];
21089
21058
  const widgetIsGraded = props?.graded == null || props.graded;
@@ -21100,7 +21069,7 @@ class Renderer extends React__namespace.Component {
21100
21069
  widgetScores[id] = widget.simpleValidate({
21101
21070
  ...props?.options,
21102
21071
  scoring: true
21103
- }, onInputError);
21072
+ });
21104
21073
  }
21105
21074
  });
21106
21075
  return widgetScores;
@@ -21973,7 +21942,6 @@ const answerTypes = {
21973
21942
  }
21974
21943
  };
21975
21944
  function inputNumberValidator(state, rubric, strings) {
21976
- let onInputError = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : () => {};
21977
21945
  if (rubric.answerType == null) {
21978
21946
  rubric.answerType = "number";
21979
21947
  }
@@ -21997,13 +21965,9 @@ function inputNumberValidator(state, rubric, strings) {
21997
21965
  // TODO(eater): Seems silly to translate result to this invalid/points
21998
21966
  // thing and immediately translate it back in ItemRenderer.scoreInput()
21999
21967
  if (result.empty) {
22000
- // TODO(FEI-3867): remove null-check once we have APIOptionsInternal
22001
- const apiResult = onInputError?.(null,
22002
- // reserved for some widget identifier
22003
- state.currentValue, result.message);
22004
21968
  return {
22005
21969
  type: "invalid",
22006
- message: apiResult === false ? null : result.message
21970
+ message: result.message
22007
21971
  };
22008
21972
  }
22009
21973
  return {
@@ -22056,8 +22020,7 @@ class InputNumber extends React__namespace.Component {
22056
22020
  linterContext: PerseusLinter.linterContextDefault
22057
22021
  };
22058
22022
  static validate(state, rubric, strings) {
22059
- let onInputError = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : () => {};
22060
- return inputNumberValidator(state, rubric, strings, onInputError);
22023
+ return inputNumberValidator(state, rubric, strings);
22061
22024
  }
22062
22025
  static getUserInputFromProps(props) {
22063
22026
  return {
@@ -22126,13 +22089,12 @@ class InputNumber extends React__namespace.Component {
22126
22089
  currentValue: newValue
22127
22090
  }, cb);
22128
22091
  };
22129
- getUserInput = () => {
22092
+ getUserInput() {
22130
22093
  return InputNumber.getUserInputFromProps(this.props);
22131
- };
22132
- simpleValidate = (rubric, onInputError) => {
22133
- onInputError = onInputError || function () {};
22134
- return inputNumberValidator(this.getUserInput(), rubric, this.context.strings, onInputError);
22135
- };
22094
+ }
22095
+ simpleValidate(rubric) {
22096
+ return inputNumberValidator(this.getUserInput(), rubric, this.context.strings);
22097
+ }
22136
22098
  examples = () => {
22137
22099
  const {
22138
22100
  strings
@@ -22477,9 +22439,9 @@ class NumericInput extends React__namespace.Component {
22477
22439
  const allFormsAccepted = answerFormNames.length >= Object.keys(formExamples).length;
22478
22440
  return !noFormsAccepted && !allFormsAccepted;
22479
22441
  };
22480
- simpleValidate = rubric => {
22442
+ simpleValidate(rubric) {
22481
22443
  return numericInputValidator(this.getUserInput(), rubric, this.context.strings);
22482
- };
22444
+ }
22483
22445
  focus = () => {
22484
22446
  this.inputRef?.focus();
22485
22447
  return true;
@@ -22506,9 +22468,9 @@ class NumericInput extends React__namespace.Component {
22506
22468
  currentValue: newValue
22507
22469
  }, cb);
22508
22470
  };
22509
- getUserInput = () => {
22471
+ getUserInput() {
22510
22472
  return NumericInput.getUserInputFromProps(this.props);
22511
- };
22473
+ }
22512
22474
  handleChange = (newValue, cb) => {
22513
22475
  this.props.onChange({
22514
22476
  currentValue: newValue
@@ -22695,11 +22657,10 @@ class PassageRef extends React__namespace.Component {
22695
22657
  this._isMounted = false;
22696
22658
  }
22697
22659
 
22698
- // TODO(tamarab): getUserInput needs to be updated to only return
22699
- // props input by the user. Currently returns all the widget's props.
22700
- getUserInput = () => {
22701
- return removeDenylistProps(this.props);
22702
- };
22660
+ // TODO (LEMS-2396): remove validation logic from widgets that don't validate
22661
+ getUserInput() {
22662
+ return null;
22663
+ }
22703
22664
  change = (() => {
22704
22665
  var _this = this;
22705
22666
  return function () {
@@ -24143,6 +24104,56 @@ const styles$t = aphrodite.StyleSheet.create({
24143
24104
  }
24144
24105
  });
24145
24106
 
24107
+ function radioValidator(userInput, rubric, strings) {
24108
+ const numSelected = userInput.choicesSelected.reduce((sum, selected) => {
24109
+ return sum + (selected ? 1 : 0);
24110
+ }, 0);
24111
+ if (numSelected === 0) {
24112
+ return {
24113
+ type: "invalid",
24114
+ message: null
24115
+ };
24116
+ }
24117
+
24118
+ // TODO: should numCorrect actually be on the rubric
24119
+ // instead of the userInput?
24120
+ if (userInput.numCorrect && userInput.numCorrect > 1 && numSelected !== userInput.numCorrect) {
24121
+ return {
24122
+ type: "invalid",
24123
+ message: strings.chooseCorrectNum
24124
+ };
24125
+ // If NOTA and some other answer are checked, ...
24126
+ }
24127
+
24128
+ // TODO: should noneOfTheAboveSelected be replaced with a
24129
+ // combination of choicesSelected and noneOfTheAboveIndex?
24130
+ if (userInput.noneOfTheAboveSelected && numSelected > 1) {
24131
+ return {
24132
+ type: "invalid",
24133
+ message: strings.notNoneOfTheAbove
24134
+ };
24135
+ }
24136
+ const correct = userInput.choicesSelected.every((selected, i) => {
24137
+ let isCorrect;
24138
+ // TODO: should noneOfTheAboveIndex actually be on the rubric
24139
+ // instead of the userInput?
24140
+ if (userInput.noneOfTheAboveIndex === i) {
24141
+ isCorrect = rubric.choices.every((choice, j) => {
24142
+ return i === j || !choice.correct;
24143
+ });
24144
+ } else {
24145
+ isCorrect = !!rubric.choices[i].correct;
24146
+ }
24147
+ return isCorrect === selected;
24148
+ });
24149
+ return {
24150
+ type: "points",
24151
+ earned: correct ? 1 : 0,
24152
+ total: 1,
24153
+ message: null
24154
+ };
24155
+ }
24156
+
24146
24157
  // RenderProps is the return type for radio.jsx#transform
24147
24158
 
24148
24159
  class Radio$1 extends React__namespace.Component {
@@ -24158,46 +24169,7 @@ class Radio$1 extends React__namespace.Component {
24158
24169
  showSolutions: "none"
24159
24170
  };
24160
24171
  static validate(userInput, rubric, strings) {
24161
- const numSelected = userInput.choicesSelected.reduce((sum, selected) => {
24162
- return sum + (selected ? 1 : 0);
24163
- }, 0);
24164
- if (numSelected === 0) {
24165
- return {
24166
- type: "invalid",
24167
- message: null
24168
- };
24169
- }
24170
- if (userInput.numCorrect && userInput.numCorrect > 1 && numSelected !== userInput.numCorrect) {
24171
- return {
24172
- type: "invalid",
24173
- message: strings.chooseCorrectNum
24174
- };
24175
- // If NOTA and some other answer are checked, ...
24176
- }
24177
-
24178
- if (userInput.noneOfTheAboveSelected && numSelected > 1) {
24179
- return {
24180
- type: "invalid",
24181
- message: strings.notNoneOfTheAbove
24182
- };
24183
- }
24184
- const correct = userInput.choicesSelected.every((selected, i) => {
24185
- let isCorrect;
24186
- if (userInput.noneOfTheAboveIndex === i) {
24187
- isCorrect = rubric.choices.every((choice, j) => {
24188
- return i === j || !choice.correct;
24189
- });
24190
- } else {
24191
- isCorrect = !!rubric.choices[i].correct;
24192
- }
24193
- return isCorrect === selected;
24194
- });
24195
- return {
24196
- type: "points",
24197
- earned: correct ? 1 : 0,
24198
- total: 1,
24199
- message: null
24200
- };
24172
+ return radioValidator(userInput, rubric, strings);
24201
24173
  }
24202
24174
  static getUserInputFromProps(props) {
24203
24175
  // Return checked inputs in the form {choicesSelected: [bool]}. (Dear
@@ -24382,12 +24354,12 @@ class Radio$1 extends React__namespace.Component {
24382
24354
  });
24383
24355
  this.props.trackInteraction();
24384
24356
  };
24385
- getUserInput = () => {
24357
+ getUserInput() {
24386
24358
  return Radio$1.getUserInputFromProps(this.props);
24387
- };
24388
- simpleValidate = rubric => {
24389
- return Radio$1.validate(this.getUserInput(), rubric, this.context.strings);
24390
- };
24359
+ }
24360
+ simpleValidate(rubric) {
24361
+ return radioValidator(this.getUserInput(), rubric, this.context.strings);
24362
+ }
24391
24363
 
24392
24364
  /**
24393
24365
  * Turn on rationale display for the currently selected choices. Note that
@@ -24741,9 +24713,9 @@ class Categorizer extends React__namespace.Component {
24741
24713
  return change.apply(_this, args);
24742
24714
  };
24743
24715
  })();
24744
- getUserInput = () => {
24716
+ getUserInput() {
24745
24717
  return Categorizer.getUserInputFromProps(this.props);
24746
- };
24718
+ }
24747
24719
  onChange(itemNum, catNum) {
24748
24720
  const values = [...this.props.values];
24749
24721
  // @ts-expect-error - TS2322 - Type 'number' is not assignable to type 'never'.
@@ -24751,9 +24723,9 @@ class Categorizer extends React__namespace.Component {
24751
24723
  this.change("values", values);
24752
24724
  this.props.trackInteraction();
24753
24725
  }
24754
- simpleValidate = rubric => {
24726
+ simpleValidate(rubric) {
24755
24727
  return categorizerValidator(this.getUserInput(), rubric, this.context.strings);
24756
- };
24728
+ }
24757
24729
  render() {
24758
24730
  const self = this;
24759
24731
 
@@ -25127,9 +25099,9 @@ class Definition extends React__namespace.Component {
25127
25099
  static validate() {
25128
25100
  return noopValidator();
25129
25101
  }
25130
- getUserInput = () => {
25102
+ getUserInput() {
25131
25103
  return {};
25132
- };
25104
+ }
25133
25105
 
25134
25106
  // TODO (LEMS-2396): remove validation logic from widgets that don't validate
25135
25107
  simpleValidate() {
@@ -25272,14 +25244,14 @@ class Dropdown extends React__namespace.Component {
25272
25244
  selected: selected
25273
25245
  });
25274
25246
  };
25275
- getUserInput = () => {
25247
+ getUserInput() {
25276
25248
  return {
25277
25249
  value: this.props.selected
25278
25250
  };
25279
- };
25280
- simpleValidate = rubric => {
25251
+ }
25252
+ simpleValidate(rubric) {
25281
25253
  return dropdownValidator(this.getUserInput(), rubric);
25282
- };
25254
+ }
25283
25255
  render() {
25284
25256
  const children = [/*#__PURE__*/React__namespace.createElement(wonderBlocksDropdown.OptionItem, {
25285
25257
  key: "placeholder",
@@ -25370,9 +25342,9 @@ class Explanation extends React__namespace.Component {
25370
25342
  });
25371
25343
  this.props.trackInteraction();
25372
25344
  };
25373
- getUserInput = () => {
25345
+ getUserInput() {
25374
25346
  return {};
25375
- };
25347
+ }
25376
25348
 
25377
25349
  // TODO (LEMS-2396): remove validation logic from widgets that don't validate
25378
25350
  simpleValidate() {
@@ -27008,9 +26980,6 @@ class FunctionGrapher extends React__namespace.Component {
27008
26980
  }), this.props.model && this.renderPlot(), this.props.model && this.renderAsymptote(), this.props.model && points)));
27009
26981
  }
27010
26982
  }
27011
-
27012
- // Really this is props.plot, I think
27013
-
27014
26983
  /* Widget and editor. */
27015
26984
  class Grapher extends React__namespace.Component {
27016
26985
  horizHairline;
@@ -27245,9 +27214,9 @@ class Group extends React__namespace.Component {
27245
27214
  return change.apply(_this, args);
27246
27215
  };
27247
27216
  })();
27248
- getUserInput = () => {
27217
+ getUserInput() {
27249
27218
  return this.rendererRef?.getUserInput();
27250
- };
27219
+ }
27251
27220
  getSerializedState = () => {
27252
27221
  return this.rendererRef?.getSerializedState();
27253
27222
  };
@@ -27257,9 +27226,9 @@ class Group extends React__namespace.Component {
27257
27226
  // (all our changes were in state):
27258
27227
  return null;
27259
27228
  };
27260
- simpleValidate = rubric => {
27229
+ simpleValidate() {
27261
27230
  return this.rendererRef?.score();
27262
- };
27231
+ }
27263
27232
 
27264
27233
  // Mobile API:
27265
27234
  getInputPaths = () => {
@@ -27569,9 +27538,9 @@ class ImageWidget extends React__namespace.Component {
27569
27538
  return change.apply(_this, args);
27570
27539
  };
27571
27540
  })();
27572
- getUserInput = () => {
27541
+ getUserInput() {
27573
27542
  return null;
27574
- };
27543
+ }
27575
27544
 
27576
27545
  // TODO (LEMS-2396): remove validation logic from widgets that don't validate
27577
27546
  simpleValidate() {
@@ -27914,13 +27883,13 @@ class Interaction extends React__namespace.Component {
27914
27883
  return change.apply(_this, args);
27915
27884
  };
27916
27885
  })();
27917
- getUserInput = () => {
27886
+ getUserInput() {
27918
27887
  // TODO(eater): Perhaps we want to be able to record the state of the
27919
27888
  // user's interaction. Unfortunately sending all the props will
27920
27889
  // probably make the attempt payload too large. So for now, don't send
27921
27890
  // anything.
27922
27891
  return {};
27923
- };
27892
+ }
27924
27893
 
27925
27894
  // TODO (LEMS-2396): remove validation logic from widgets that don't validate
27926
27895
  simpleValidate() {
@@ -29992,14 +29961,20 @@ function GraphLockedLabelsLayer(props) {
29992
29961
  // Point flag + point type
29993
29962
  flags?.["mafs"]?.["locked-point-labels"] && figure.type === "point" ||
29994
29963
  // Line flag + line type
29995
- flags?.["mafs"]?.["locked-line-labels"] && figure.type === "line") {
29964
+ flags?.["mafs"]?.["locked-line-labels"] && figure.type === "line" ||
29965
+ // Vector flag + vector type
29966
+ flags?.["mafs"]?.["locked-vector-labels"] && figure.type === "vector" ||
29967
+ // Ellipse flag + ellipse type
29968
+ flags?.["mafs"]?.["locked-ellipse-labels"] && figure.type === "ellipse" ||
29969
+ // Function flag + function type
29970
+ flags?.["mafs"]?.["locked-function-labels"] && figure.type === "function") {
29996
29971
  return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, {
29997
29972
  key: i
29998
- }, figure.labels.map((label, j) => /*#__PURE__*/React__namespace.createElement(LockedLabel, _extends({
29973
+ }, figure.labels?.map((label, j) => /*#__PURE__*/React__namespace.createElement(LockedLabel, _extends({
29999
29974
  key: `${i}-label-${j}`
30000
- }, label))), figure.type === "line" && /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, figure.points[0].labels.map((label, k) => /*#__PURE__*/React__namespace.createElement(LockedLabel, _extends({
29975
+ }, label))), figure.type === "line" && /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, figure.points[0].labels?.map((label, k) => /*#__PURE__*/React__namespace.createElement(LockedLabel, _extends({
30001
29976
  key: `locked-figure-${i}-point-1-label-${k}`
30002
- }, label))), figure.points[1].labels.map((label, k) => /*#__PURE__*/React__namespace.createElement(LockedLabel, _extends({
29977
+ }, label))), figure.points[1].labels?.map((label, k) => /*#__PURE__*/React__namespace.createElement(LockedLabel, _extends({
30003
29978
  key: `locked-figure-${i}-point-2-label-${k}`
30004
29979
  }, label)))));
30005
29980
  }
@@ -32117,7 +32092,7 @@ function Protractor() {
32117
32092
  const [[xMin, xMax], [yMin, yMax]] = range;
32118
32093
  // Position the protractor in the center of the graph (horizontally), and
32119
32094
  // 95% of the way to the bottom of the graph (vertically).
32120
- const initialCenter = [lerp(xMin, xMax, 0.5), lerp(yMin, yMax, 0.05)];
32095
+ const initialCenter = [lerp(xMin, xMax, 0.5), lerp(yMin, yMax, 0.25)];
32121
32096
  const [center, setCenter] = React.useState(initialCenter);
32122
32097
  const [rotationHandleOffset, setRotationHandleOffset] = React.useState(centerToRotationHandle);
32123
32098
  const draggableRef = React.useRef(null);
@@ -32278,6 +32253,9 @@ const MafsGraph = props => {
32278
32253
  x: viewboxX,
32279
32254
  y: viewboxY
32280
32255
  };
32256
+ const {
32257
+ strings
32258
+ } = usePerseusI18n();
32281
32259
  return /*#__PURE__*/React__namespace.createElement(GraphConfigContext.Provider, {
32282
32260
  value: {
32283
32261
  range: state.range,
@@ -32392,10 +32370,11 @@ const MafsGraph = props => {
32392
32370
  top: "50%",
32393
32371
  transform: "translateY(-50%)"
32394
32372
  }
32395
- }, /*#__PURE__*/React__namespace.createElement(wonderBlocksTypography.LabelMedium, null, "Press ", /*#__PURE__*/React__namespace.createElement("strong", null, "Shift + Enter"), " to interact with the graph"))), renderGraphControls({
32373
+ }, /*#__PURE__*/React__namespace.createElement(wonderBlocksTypography.LabelMedium, null, strings.graphKeyboardPrompt))), renderGraphControls({
32396
32374
  state,
32397
32375
  dispatch,
32398
- width
32376
+ width,
32377
+ strings
32399
32378
  })));
32400
32379
  };
32401
32380
  const renderPointGraphControls = props => {
@@ -32404,6 +32383,10 @@ const renderPointGraphControls = props => {
32404
32383
  showRemovePointButton,
32405
32384
  focusedPointIndex
32406
32385
  } = props.state;
32386
+ const {
32387
+ strings
32388
+ } = props;
32389
+ const shouldShowRemoveButton = showRemovePointButton && focusedPointIndex !== null;
32407
32390
  return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
32408
32391
  style: {
32409
32392
  flexDirection: "row",
@@ -32419,7 +32402,7 @@ const renderPointGraphControls = props => {
32419
32402
  onClick: () => {
32420
32403
  props.dispatch(actions.pointGraph.addPoint([0, 0]));
32421
32404
  }
32422
- }, "Add Point"), interactionMode === "mouse" && showRemovePointButton && focusedPointIndex !== null && /*#__PURE__*/React__namespace.createElement(Button__default["default"], {
32405
+ }, strings.addPoint), interactionMode === "mouse" && /*#__PURE__*/React__namespace.createElement(Button__default["default"], {
32423
32406
  id: REMOVE_BUTTON_ID,
32424
32407
  kind: "secondary",
32425
32408
  color: "destructive"
@@ -32429,18 +32412,20 @@ const renderPointGraphControls = props => {
32429
32412
  tabIndex: -1,
32430
32413
  style: {
32431
32414
  width: "100%",
32432
- marginLeft: "20px"
32415
+ marginLeft: "20px",
32416
+ visibility: shouldShowRemoveButton ? "visible" : "hidden"
32433
32417
  },
32434
32418
  onClick: event => {
32435
32419
  props.dispatch(actions.pointGraph.removePoint(props.state.focusedPointIndex));
32436
32420
  }
32437
- }, "Remove Point"));
32421
+ }, strings.removePoint));
32438
32422
  };
32439
32423
  const renderGraphControls = props => {
32440
32424
  const {
32441
32425
  state,
32442
32426
  dispatch,
32443
- width
32427
+ width,
32428
+ strings
32444
32429
  } = props;
32445
32430
  const {
32446
32431
  type
@@ -32451,7 +32436,8 @@ const renderGraphControls = props => {
32451
32436
  return renderPointGraphControls({
32452
32437
  state,
32453
32438
  dispatch,
32454
- width
32439
+ width,
32440
+ strings
32455
32441
  });
32456
32442
  }
32457
32443
  return null;
@@ -35069,17 +35055,17 @@ class InteractiveGraph extends React__namespace.Component {
35069
35055
  type: "linear"
35070
35056
  }
35071
35057
  };
35072
- getUserInput = () => {
35073
- if (this.mafsRef.current) {
35074
- return this.mafsRef.current.getUserInput?.();
35058
+ getUserInput() {
35059
+ if (this.mafsRef.current?.getUserInput) {
35060
+ return this.mafsRef.current.getUserInput();
35075
35061
  }
35076
- if (this.legacyGraphRef.current) {
35062
+ if (this.legacyGraphRef.current?.getUserInput) {
35077
35063
  return this.legacyGraphRef.current.getUserInput();
35078
35064
  }
35079
35065
  throw new perseusCore.PerseusError("Cannot getUserInput from a graph that has never rendered", perseusCore.Errors.NotAllowed);
35080
- };
35066
+ }
35081
35067
  simpleValidate(rubric) {
35082
- return InteractiveGraph.validate(this.getUserInput?.(), rubric, this);
35068
+ return InteractiveGraph.validate(this.getUserInput(), rubric, this);
35083
35069
  }
35084
35070
  render() {
35085
35071
  // Mafs shim
@@ -36746,6 +36732,57 @@ const styles$h = aphrodite.StyleSheet.create({
36746
36732
  }
36747
36733
  });
36748
36734
 
36735
+ // Question state for marker as result of user selected answers.
36736
+
36737
+ function scoreMarker(marker) {
36738
+ const score = {
36739
+ hasAnswers: false,
36740
+ isCorrect: false
36741
+ };
36742
+ if (marker.selected && marker.selected.length > 0) {
36743
+ score.hasAnswers = true;
36744
+ }
36745
+ if (marker.answers.length > 0) {
36746
+ if (marker.selected && marker.selected.length === marker.answers.length) {
36747
+ // All correct answers are selected by the user.
36748
+ score.isCorrect = marker.selected.every(choice => marker.answers.includes(choice));
36749
+ }
36750
+ } else if (!marker.selected || marker.selected.length === 0) {
36751
+ // Correct as no answers should be selected by the user.
36752
+ score.isCorrect = true;
36753
+ }
36754
+ return score;
36755
+ }
36756
+ function labelImageValidator(state, rubric) {
36757
+ let numAnswered = 0;
36758
+ let numCorrect = 0;
36759
+ for (const marker of state.markers) {
36760
+ const score = scoreMarker(marker);
36761
+ if (score.hasAnswers) {
36762
+ numAnswered++;
36763
+ }
36764
+ if (score.isCorrect) {
36765
+ numCorrect++;
36766
+ }
36767
+ }
36768
+
36769
+ // We expect all question markers to be answered before grading.
36770
+ if (numAnswered !== state.markers.length) {
36771
+ return {
36772
+ type: "invalid",
36773
+ message: null
36774
+ };
36775
+ }
36776
+ return {
36777
+ type: "points",
36778
+ // Markers with no expected answers are graded as correct if user
36779
+ // makes no answer selection.
36780
+ earned: numCorrect === state.markers.length ? 1 : 0,
36781
+ total: 1,
36782
+ message: null
36783
+ };
36784
+ }
36785
+
36749
36786
  const BringToFront = {
36750
36787
  boxShadow: `0 8px 8px ${wonderBlocksTokens.color.offBlack64}`,
36751
36788
  zIndex: 1000
@@ -37006,61 +37043,15 @@ const styles$f = aphrodite.StyleSheet.create({
37006
37043
  * No diagonals allowed.
37007
37044
  */
37008
37045
 
37046
+ // TODO: should this be using WidgetProps / PerseusLabelImageWidgetOptions?
37047
+
37009
37048
  class LabelImage extends React__namespace.Component {
37010
37049
  static contextType = PerseusI18nContext;
37011
37050
  // The rendered markers on the question image for labeling.
37012
37051
  _markers;
37013
37052
  _mounted = false;
37014
- static gradeMarker(marker) {
37015
- const score = {
37016
- hasAnswers: false,
37017
- isCorrect: false
37018
- };
37019
- if (marker.selected && marker.selected.length > 0) {
37020
- // @ts-expect-error - TS2540 - Cannot assign to 'hasAnswers' because it is a read-only property.
37021
- score.hasAnswers = true;
37022
- }
37023
- if (marker.answers.length > 0) {
37024
- if (marker.selected && marker.selected.length === marker.answers.length) {
37025
- // All correct answers are selected by the user.
37026
- // @ts-expect-error - TS2540 - Cannot assign to 'isCorrect' because it is a read-only property.
37027
- score.isCorrect = marker.selected.every(choice => marker.answers.includes(choice));
37028
- }
37029
- } else if (!marker.selected || marker.selected.length === 0) {
37030
- // Correct as no answers should be selected by the user.
37031
- // @ts-expect-error - TS2540 - Cannot assign to 'isCorrect' because it is a read-only property.
37032
- score.isCorrect = true;
37033
- }
37034
- return score;
37035
- }
37036
37053
  static validate(state, rubric) {
37037
- let numAnswered = 0;
37038
- let numCorrect = 0;
37039
- for (const marker of state.markers) {
37040
- const score = LabelImage.gradeMarker(marker);
37041
- if (score.hasAnswers) {
37042
- numAnswered++;
37043
- }
37044
- if (score.isCorrect) {
37045
- numCorrect++;
37046
- }
37047
- }
37048
-
37049
- // We expect all question markers to be answered before grading.
37050
- if (numAnswered !== state.markers.length) {
37051
- return {
37052
- type: "invalid",
37053
- message: null
37054
- };
37055
- }
37056
- return {
37057
- type: "points",
37058
- // Markers with no expected answers are graded as correct if user
37059
- // makes no answer selection.
37060
- earned: numCorrect === state.markers.length ? 1 : 0,
37061
- total: 1,
37062
- message: null
37063
- };
37054
+ return labelImageValidator(state);
37064
37055
  }
37065
37056
 
37066
37057
  /**
@@ -37255,7 +37246,7 @@ class LabelImage extends React__namespace.Component {
37255
37246
  this._mounted = false;
37256
37247
  }
37257
37248
  simpleValidate(rubric) {
37258
- return LabelImage.validate(this.getUserInput(), rubric);
37249
+ return labelImageValidator(this.getUserInput());
37259
37250
  }
37260
37251
  getUserInput() {
37261
37252
  const {
@@ -37273,7 +37264,7 @@ class LabelImage extends React__namespace.Component {
37273
37264
  onChange
37274
37265
  } = this.props;
37275
37266
  const updatedMarkers = markers.map(marker => {
37276
- const score = LabelImage.gradeMarker(marker);
37267
+ const score = scoreMarker(marker);
37277
37268
  return {
37278
37269
  ...marker,
37279
37270
  // Reveal correctness state for markers with answers.
@@ -37422,7 +37413,7 @@ class LabelImage extends React__namespace.Component {
37422
37413
  bottom: "top"
37423
37414
  }[markerPosition];
37424
37415
  }
37425
- const score = LabelImage.gradeMarker(marker);
37416
+ const score = scoreMarker(marker);
37426
37417
  // Once the question is answered, show markers
37427
37418
  // with correct answers, otherwise passthrough
37428
37419
  // the correctness state.
@@ -37628,9 +37619,7 @@ const LabelImageWithDependencies = /*#__PURE__*/React__namespace.forwardRef((pro
37628
37619
  // methods and instead adjust Peresus to provide these facilities through
37629
37620
  // instance methods on our Renderers.
37630
37621
  // @ts-expect-error - TS2339 - Property 'validate' does not exist on type
37631
- LabelImageWithDependencies.validate = LabelImage.validate;
37632
- // @ts-expect-error - TS2339 - Property 'gradeMarker' does not exist on type
37633
- LabelImageWithDependencies.gradeMarker = LabelImage.gradeMarker;
37622
+ LabelImageWithDependencies.validate = labelImageValidator;
37634
37623
  var LabelImage$1 = {
37635
37624
  name: "label-image",
37636
37625
  displayName: "Label Image",
@@ -38419,10 +38408,10 @@ class Matcher extends React__namespace.Component {
38419
38408
  // @ts-expect-error - TS2339 - Property 'moveOptionToIndex' does not exist on type 'ReactInstance'.
38420
38409
  this.refs.right.moveOptionToIndex(option, index);
38421
38410
  };
38422
- simpleValidate = rubric => {
38411
+ simpleValidate(rubric) {
38423
38412
  // @ts-expect-error - TS2339 - Property 'validate' does not exist on type 'typeof Matcher'.
38424
38413
  return Matcher.validate(this.getUserInput(), rubric);
38425
- };
38414
+ }
38426
38415
  render() {
38427
38416
  // To minimize layout shift, we display a spinner until our math
38428
38417
  // renderer is ready to render the math inside the matcher. To
@@ -38665,7 +38654,9 @@ class Matrix extends React__namespace.Component {
38665
38654
  hasEmptyCell = true;
38666
38655
  }
38667
38656
  if (!incorrectSize) {
38668
- const validator = createValidator(solution[row][col], {
38657
+ const validator = createValidator(
38658
+ // @ts-expect-error - TS2345 - Argument of type 'number' is not assignable to parameter of type 'string'.
38659
+ solution[row][col], {
38669
38660
  simplify: true
38670
38661
  }, strings);
38671
38662
  const result = validator(supplied[row][col]);
@@ -38837,14 +38828,14 @@ class Matrix extends React__namespace.Component {
38837
38828
  }, cb);
38838
38829
  this.props.trackInteraction();
38839
38830
  };
38840
- getUserInput = () => {
38831
+ getUserInput() {
38841
38832
  return {
38842
38833
  answers: this.props.answers
38843
38834
  };
38844
- };
38845
- simpleValidate = rubric => {
38835
+ }
38836
+ simpleValidate(rubric) {
38846
38837
  return Matrix.validate(this.getUserInput(), rubric, this.context.strings);
38847
- };
38838
+ }
38848
38839
  render() {
38849
38840
  // Set the input sizes through JS so we can control the size of the
38850
38841
  // brackets. (If we set them in CSS we won't know values until the
@@ -40057,9 +40048,9 @@ class MoleculeWidget extends React__namespace.Component {
40057
40048
  simpleValidate() {
40058
40049
  return noopValidator();
40059
40050
  }
40060
- getUserInput = () => {
40051
+ getUserInput() {
40061
40052
  return [];
40062
- };
40053
+ }
40063
40054
 
40064
40055
  // TODO (LEMS-2396): remove validation logic from widgets that don't validate
40065
40056
  validate() {
@@ -40205,6 +40196,9 @@ const TickMarks = Graphie.createSimpleClass((graphie, props) => {
40205
40196
  });
40206
40197
  return results;
40207
40198
  });
40199
+
40200
+ // TODO: most widgets use some like Widget<Something, PerseusNumberLineWidgetOptions>
40201
+ // should this one?
40208
40202
  class NumberLine extends React__namespace.Component {
40209
40203
  static contextType = PerseusI18nContext;
40210
40204
  static defaultProps = {
@@ -40506,18 +40500,18 @@ class NumberLine extends React__namespace.Component {
40506
40500
  arrows: "->"
40507
40501
  });
40508
40502
  };
40509
- getUserInput = () => {
40503
+ getUserInput() {
40510
40504
  return {
40511
40505
  numLinePosition: this.props.numLinePosition,
40512
40506
  rel: this.props.isInequality ? this.props.rel : "eq",
40513
40507
  numDivisions: this.props.numDivisions,
40514
40508
  divisionRange: this.props.divisionRange
40515
40509
  };
40516
- };
40517
- simpleValidate = rubric => {
40510
+ }
40511
+ simpleValidate(rubric) {
40518
40512
  // @ts-expect-error - TS2339 - Property 'validate' does not exist on type 'typeof NumberLine'.
40519
40513
  return NumberLine.validate(this.getUserInput(), rubric);
40520
- };
40514
+ }
40521
40515
  render() {
40522
40516
  const {
40523
40517
  strings
@@ -40577,6 +40571,8 @@ ___default["default"].extend(NumberLine, {
40577
40571
  const correctRel = rubric.correctRel || "eq";
40578
40572
  const correctPos = kmath.number.equal(state.numLinePosition, rubric.correctX || 0);
40579
40573
  const outsideAllowedRange = state.numDivisions > divisionRange[1] || state.numDivisions < divisionRange[0];
40574
+
40575
+ // TODO: I don't think isTickCrtl is a thing anymore
40580
40576
  if (state.isTickCrtl && outsideAllowedRange) {
40581
40577
  return {
40582
40578
  type: "invalid",
@@ -40829,8 +40825,8 @@ class Card extends React__namespace.Component {
40829
40825
  }))));
40830
40826
  }
40831
40827
  }
40832
- const NORMAL = "normal",
40833
- HORIZONTAL = "horizontal";
40828
+ const NORMAL = "normal";
40829
+ const HORIZONTAL = "horizontal";
40834
40830
  class Orderer extends React__namespace.Component {
40835
40831
  static defaultProps = {
40836
40832
  current: [],
@@ -41085,17 +41081,17 @@ class Orderer extends React__namespace.Component {
41085
41081
  current: list
41086
41082
  });
41087
41083
  };
41088
- getUserInput = () => {
41084
+ getUserInput() {
41089
41085
  return {
41090
41086
  current: ___default["default"].map(this.props.current, function (v) {
41091
41087
  return v.content;
41092
41088
  })
41093
41089
  };
41094
- };
41095
- simpleValidate = rubric => {
41090
+ }
41091
+ simpleValidate(rubric) {
41096
41092
  // @ts-expect-error - TS2339 - Property 'validate' does not exist on type 'typeof Orderer'.
41097
41093
  return Orderer.validate(this.getUserInput(), rubric);
41098
- };
41094
+ }
41099
41095
  render() {
41100
41096
  // This is the card we are currently dragging
41101
41097
  const dragging = this.state.dragging && /*#__PURE__*/React__namespace.createElement(Card
@@ -41201,14 +41197,14 @@ class Orderer extends React__namespace.Component {
41201
41197
  }
41202
41198
  }
41203
41199
  ___default["default"].extend(Orderer, {
41204
- validate: function (state, rubric) {
41205
- if (state.current.length === 0) {
41200
+ validate: function (userInput, rubric) {
41201
+ if (userInput.current.length === 0) {
41206
41202
  return {
41207
41203
  type: "invalid",
41208
41204
  message: null
41209
41205
  };
41210
41206
  }
41211
- const correct = ___default["default"].isEqual(state.current, ___default["default"].pluck(rubric.correctOptions, "content"));
41207
+ const correct = ___default["default"].isEqual(userInput.current, ___default["default"].pluck(rubric.correctOptions, "content"));
41212
41208
  return {
41213
41209
  type: "points",
41214
41210
  earned: correct ? 1 : 0,
@@ -43418,9 +43414,9 @@ class PassageRefTarget extends React__namespace.Component {
43418
43414
  }
43419
43415
 
43420
43416
  // TODO passage-ref-target isn't interactive; remove
43421
- getUserInput = () => {
43422
- return;
43423
- };
43417
+ getUserInput() {
43418
+ return null;
43419
+ }
43424
43420
 
43425
43421
  // TODO passage-ref-target isn't interactive; remove
43426
43422
  change = (() => {
@@ -43472,9 +43468,6 @@ var PassageRefTarget$1 = {
43472
43468
  * This is a PhET simulation widget. It is used for rendering simulations
43473
43469
  * from https://phet.colorado.edu/.
43474
43470
  */
43475
-
43476
- // For returning user input, but currently the PhET widget
43477
- // does not support accessing user input
43478
43471
  // This renders the PhET sim
43479
43472
  class PhetSimulation extends React__namespace.Component {
43480
43473
  static contextType = PerseusI18nContext;
@@ -43497,6 +43490,8 @@ class PhetSimulation extends React__namespace.Component {
43497
43490
  await this.updateSimState(this.props.url);
43498
43491
  }
43499
43492
  }
43493
+
43494
+ // TODO (LEMS-2396): remove validation logic from widgets that don't validate
43500
43495
  getUserInput() {
43501
43496
  return null;
43502
43497
  }
@@ -44446,13 +44441,13 @@ class Plotter extends React__namespace.Component {
44446
44441
  });
44447
44442
  });
44448
44443
  };
44449
- getUserInput = () => {
44444
+ getUserInput() {
44450
44445
  return this.state.values;
44451
- };
44452
- simpleValidate = rubric => {
44446
+ }
44447
+ simpleValidate(rubric) {
44453
44448
  // @ts-expect-error - TS2339 - Property 'validate' does not exist on type 'typeof Plotter'.
44454
44449
  return Plotter.validate(this.getUserInput(), rubric);
44455
- };
44450
+ }
44456
44451
  render() {
44457
44452
  // TODO(kevinb) actually compute the size of the graphie correctly and
44458
44453
  // make it that size so we don't have to add extra padding. The value
@@ -44471,8 +44466,8 @@ class Plotter extends React__namespace.Component {
44471
44466
  }
44472
44467
  }
44473
44468
  ___default["default"].extend(Plotter, {
44474
- validate: function (guess, rubric) {
44475
- if (deepEq(guess, rubric.starting)) {
44469
+ validate: function (userInput, rubric) {
44470
+ if (deepEq(userInput, rubric.starting)) {
44476
44471
  return {
44477
44472
  type: "invalid",
44478
44473
  message: null
@@ -44480,7 +44475,7 @@ ___default["default"].extend(Plotter, {
44480
44475
  }
44481
44476
  return {
44482
44477
  type: "points",
44483
- earned: deepEq(guess, rubric.correct) ? 1 : 0,
44478
+ earned: deepEq(userInput, rubric.correct) ? 1 : 0,
44484
44479
  total: 1,
44485
44480
  message: null
44486
44481
  };
@@ -44543,6 +44538,28 @@ var PythonProgram$1 = {
44543
44538
  widget: PythonProgram
44544
44539
  };
44545
44540
 
44541
+ function sorterValidator(userInput, rubric) {
44542
+ // If the sorter widget hasn't been changed yet, we treat it as "empty" which
44543
+ // prevents the "Check" button from becoming active. We want the user
44544
+ // to make a change before trying to move forward. This makes an
44545
+ // assumption that the initial order isn't the correct order! However,
44546
+ // this should be rare if it happens, and interacting with the list
44547
+ // will enable the button, so they won't be locked out of progressing.
44548
+ if (!userInput.changed) {
44549
+ return {
44550
+ type: "invalid",
44551
+ message: null
44552
+ };
44553
+ }
44554
+ const correct = Util.deepEq(userInput.options, rubric.correct);
44555
+ return {
44556
+ type: "points",
44557
+ earned: correct ? 1 : 0,
44558
+ total: 1,
44559
+ message: null
44560
+ };
44561
+ }
44562
+
44546
44563
  const {
44547
44564
  shuffle
44548
44565
  } = Util;
@@ -44556,14 +44573,8 @@ class Sorter extends React__namespace.Component {
44556
44573
  onChange: function () {},
44557
44574
  linterContext: PerseusLinter.linterContextDefault
44558
44575
  };
44559
- static validate(state, rubric) {
44560
- const correct = ___default["default"].isEqual(state.options, rubric.correct);
44561
- return {
44562
- type: "points",
44563
- earned: correct ? 1 : 0,
44564
- total: 1,
44565
- message: null
44566
- };
44576
+ static validate(userInput, rubric) {
44577
+ return sorterValidator(userInput, rubric);
44567
44578
  }
44568
44579
  state = {
44569
44580
  changed: false
@@ -44593,33 +44604,23 @@ class Sorter extends React__namespace.Component {
44593
44604
  }, 0);
44594
44605
  });
44595
44606
  };
44596
- getUserInput = () => {
44607
+ getUserInput() {
44597
44608
  // eslint-disable-next-line react/no-string-refs
44598
44609
  // @ts-expect-error - TS2339 - Property 'getOptions' does not exist on type 'ReactInstance'.
44610
+ const options = this.refs.sortable.getOptions();
44599
44611
  return {
44600
- options: this.refs.sortable.getOptions()
44612
+ options,
44613
+ changed: this.state.changed
44601
44614
  };
44602
- };
44615
+ }
44603
44616
  moveOptionToIndex = (option, index) => {
44604
44617
  // eslint-disable-next-line react/no-string-refs
44605
44618
  // @ts-expect-error - TS2339 - Property 'moveOptionToIndex' does not exist on type 'ReactInstance'.
44606
44619
  this.refs.sortable.moveOptionToIndex(option, index);
44607
44620
  };
44608
- simpleValidate = rubric => {
44609
- // If this widget hasn't been changed yet, we treat it as "empty" which
44610
- // prevents the "Check" button from becoming active. We want the user
44611
- // to make a change before trying to move forward. This makes an
44612
- // assumption that the initial order isn't the correct order! However,
44613
- // this should be rare if it happens, and interacting with the list
44614
- // will enable the button, so they won't be locked out of progressing.
44615
- if (!this.state.changed) {
44616
- return {
44617
- type: "invalid",
44618
- message: null
44619
- };
44620
- }
44621
- return Sorter.validate(this.getUserInput(), rubric);
44622
- };
44621
+ simpleValidate(rubric) {
44622
+ return sorterValidator(this.getUserInput(), rubric);
44623
+ }
44623
44624
  render() {
44624
44625
  const options = shuffle(this.props.correct, this.props.problemNum, /* ensurePermuted */true);
44625
44626
  const {
@@ -44699,9 +44700,9 @@ class Table extends React__namespace.Component {
44699
44700
  _getColumns = () => {
44700
44701
  return this.props.answers[0].length;
44701
44702
  };
44702
- getUserInput = () => {
44703
+ getUserInput() {
44703
44704
  return ___default["default"].map(this.props.answers, ___default["default"].clone);
44704
- };
44705
+ }
44705
44706
  onValueChange = (row, column, eventOrValue) => {
44706
44707
  const answers = ___default["default"].map(this.props.answers, ___default["default"].clone);
44707
44708
 
@@ -44721,10 +44722,10 @@ class Table extends React__namespace.Component {
44721
44722
  headers: headers
44722
44723
  });
44723
44724
  };
44724
- simpleValidate = rubric => {
44725
+ simpleValidate(rubric) {
44725
44726
  // @ts-expect-error - TS2339 - Property 'validate' does not exist on type 'typeof Table'.
44726
44727
  return Table.validate(this.getUserInput(), rubric, this.context.strings);
44727
- };
44728
+ }
44728
44729
  _handleFocus = inputPath => {
44729
44730
  this.props.onFocus(inputPath);
44730
44731
  };
@@ -45144,7 +45145,7 @@ const init = function (options) {
45144
45145
 
45145
45146
  // This file is processed by a Rollup plugin (replace) to inject the production
45146
45147
  const libName = "@khanacademy/perseus";
45147
- const libVersion = "34.1.0";
45148
+ const libVersion = "35.0.0";
45148
45149
  perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
45149
45150
 
45150
45151
  const apiVersion = {
@@ -45698,9 +45699,6 @@ const defaultContext = {
45698
45699
  };
45699
45700
  const context = /*#__PURE__*/React__namespace.createContext(defaultContext);
45700
45701
 
45701
- const {
45702
- mapObject
45703
- } = objective_;
45704
45702
  class ServerItemRenderer extends React__namespace.Component {
45705
45703
  static contextType = PerseusI18nContext;
45706
45704
  // @ts-expect-error - TS2564 - Property 'questionRenderer' has no initializer and is not definitely assigned in the constructor.
@@ -45916,20 +45914,6 @@ class ServerItemRenderer extends React__namespace.Component {
45916
45914
  return this.questionRenderer.getWidgetIds();
45917
45915
  }
45918
45916
 
45919
- /**
45920
- * Returns an object mapping from widget ID to KE-style score.
45921
- * The keys of this object are the values of the array returned
45922
- * from `getWidgetIds`.
45923
- */
45924
- scoreWidgets() {
45925
- const qScore = this.questionRenderer.scoreWidgets();
45926
- const qGuess = this.questionRenderer.getUserInputForWidgets();
45927
- const state = this.questionRenderer.getSerializedState();
45928
- return mapObject(qScore, (score, id) => {
45929
- return Util.keScoreFromPerseusScore(score, qGuess[id], state);
45930
- });
45931
- }
45932
-
45933
45917
  /**
45934
45918
  * Get a representation of the current state of the item.
45935
45919
  */