@khanacademy/perseus 36.1.2 → 37.0.1

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 (56) hide show
  1. package/dist/all-widgets.d.ts +2 -0
  2. package/dist/basic-widgets.d.ts +2 -0
  3. package/dist/components/math-input.d.ts +1 -1
  4. package/dist/es/index.js +598 -766
  5. package/dist/es/index.js.map +1 -1
  6. package/dist/extra-widgets.d.ts +2 -0
  7. package/dist/index.js +463 -643
  8. package/dist/index.js.map +1 -1
  9. package/dist/perseus-api.d.ts +1 -0
  10. package/dist/renderer-util.d.ts +8 -0
  11. package/dist/renderer.d.ts +19 -20
  12. package/dist/server-item-renderer.d.ts +1 -0
  13. package/dist/types.d.ts +13 -4
  14. package/dist/util/extract-perseus-data.d.ts +1 -1
  15. package/dist/util.d.ts +4 -0
  16. package/dist/validation.types.d.ts +14 -6
  17. package/dist/widgets/categorizer/categorizer.d.ts +4 -5
  18. package/dist/widgets/cs-program/cs-program.d.ts +3 -3
  19. package/dist/widgets/definition/definition.d.ts +4 -4
  20. package/dist/widgets/deprecated-standin/deprecated-standin.d.ts +36 -0
  21. package/dist/widgets/deprecated-standin/index.d.ts +1 -37
  22. package/dist/widgets/dropdown/dropdown.d.ts +3 -3
  23. package/dist/widgets/explanation/explanation.d.ts +5 -5
  24. package/dist/widgets/expression/expression.d.ts +11 -9
  25. package/dist/widgets/graded-group/graded-group.d.ts +2 -0
  26. package/dist/widgets/graded-group-set/graded-group-set.d.ts +2 -0
  27. package/dist/widgets/grapher/grapher.d.ts +3 -3
  28. package/dist/widgets/group/group.d.ts +9 -5
  29. package/dist/widgets/iframe/iframe.d.ts +3 -3
  30. package/dist/widgets/image/image.d.ts +4 -4
  31. package/dist/widgets/input-number/input-number.d.ts +3 -6
  32. package/dist/widgets/interaction/interaction.d.ts +4 -4
  33. package/dist/widgets/interactive-graph.d.ts +3 -4
  34. package/dist/widgets/interactive-graphs/mafs-supported-graph-types.d.ts +1 -1
  35. package/dist/widgets/label-image/label-image.d.ts +6 -4
  36. package/dist/widgets/matcher/matcher.d.ts +3 -3
  37. package/dist/widgets/matrix/matrix.d.ts +4 -5
  38. package/dist/widgets/measurer/measurer.d.ts +3 -4
  39. package/dist/widgets/molecule/molecule.d.ts +3 -4
  40. package/dist/widgets/number-line/number-line.d.ts +4 -4
  41. package/dist/widgets/numeric-input/numeric-input.d.ts +3 -4
  42. package/dist/widgets/orderer/orderer.d.ts +3 -3
  43. package/dist/widgets/passage/passage.d.ts +4 -9
  44. package/dist/widgets/passage-ref/passage-ref.d.ts +4 -4
  45. package/dist/widgets/passage-ref-target/passage-ref-target.d.ts +3 -4
  46. package/dist/widgets/phet-simulation/phet-simulation.d.ts +3 -2
  47. package/dist/widgets/plotter/plotter.d.ts +3 -3
  48. package/dist/widgets/python-program/python-program.d.ts +2 -0
  49. package/dist/widgets/radio/radio-component.d.ts +2 -5
  50. package/dist/widgets/radio/radio.d.ts +2 -0
  51. package/dist/widgets/sorter/sorter.d.ts +3 -3
  52. package/dist/widgets/table/table.d.ts +4 -5
  53. package/dist/widgets/video/video.d.ts +3 -8
  54. package/dist/widgets.d.ts +3 -1
  55. package/package.json +2 -2
  56. package/dist/widgets/expression/expression.types.d.ts +0 -2
package/dist/es/index.js CHANGED
@@ -53,16 +53,6 @@ function _extends() {
53
53
  }, _extends.apply(null, arguments);
54
54
  }
55
55
 
56
- function _objectWithoutPropertiesLoose(r, e) {
57
- if (null == r) return {};
58
- var t = {};
59
- for (var n in r) if ({}.hasOwnProperty.call(r, n)) {
60
- if (e.includes(n)) continue;
61
- t[n] = r[n];
62
- }
63
- return t;
64
- }
65
-
66
56
  /**
67
57
  * The translated strings that are used to render Perseus.
68
58
  */
@@ -2258,7 +2248,6 @@ function expressionValidator(userInput, rubric, strings, locale) {
2258
2248
  };
2259
2249
  }
2260
2250
 
2261
- const _excluded$5 = ["scoring"];
2262
2251
  // Map of international operator names to their English equivalents
2263
2252
  const englishOperators = {
2264
2253
  arctg: "arctan",
@@ -2281,13 +2270,6 @@ const anglicizeOperators = tex => {
2281
2270
  const normalizeTex = tex => {
2282
2271
  return anglicizeOperators(tex);
2283
2272
  };
2284
- const deriveKeypadVersion = apiOptions => {
2285
- // We can derive which version of the keypad is in use. This is
2286
- // a bit tricky, but this code will be relatively short-lived
2287
- // as we coalesce onto the new, v2 Keypad, at which point we
2288
- // can remove this `virtualKeypadVersion` field entirely.
2289
- return apiOptions.nativeKeypadProxy != null ? "REACT_NATIVE_KEYPAD" : "MATH_INPUT_KEYPAD_V2";
2290
- };
2291
2273
  // The new, MathQuill input expression widget
2292
2274
  class Expression extends React.Component {
2293
2275
  constructor(..._args) {
@@ -2346,27 +2328,6 @@ class Expression extends React.Component {
2346
2328
  });
2347
2329
  }
2348
2330
  };
2349
- this.simpleValidate = _ref => {
2350
- let {
2351
- scoring
2352
- } = _ref,
2353
- rubric = _objectWithoutPropertiesLoose(_ref, _excluded$5);
2354
- const score = expressionValidator(this.getUserInput(), rubric, this.context.strings, this.context.locale);
2355
-
2356
- // "scoring" is a flag that indicates when we are checking answers.
2357
- // otherwise, we may just be checking validity after changes.
2358
- if (scoring && score.type !== "invalid") {
2359
- var _this$props$analytics;
2360
- (_this$props$analytics = this.props.analytics) == null || _this$props$analytics.onAnalyticsEvent({
2361
- type: "perseus:expression-evaluated",
2362
- payload: {
2363
- result: score.earned === score.total ? "correct" : "incorrect",
2364
- virtualKeypadVersion: deriveKeypadVersion(this.props.apiOptions)
2365
- }
2366
- });
2367
- }
2368
- return score;
2369
- };
2370
2331
  this.change = (...args) => {
2371
2332
  return change.apply(this, args);
2372
2333
  };
@@ -2384,8 +2345,8 @@ class Expression extends React.Component {
2384
2345
  this.props.trackInteraction();
2385
2346
  };
2386
2347
  this._handleFocus = () => {
2387
- var _this$props$analytics2;
2388
- (_this$props$analytics2 = this.props.analytics) == null || _this$props$analytics2.onAnalyticsEvent({
2348
+ var _this$props$analytics;
2349
+ (_this$props$analytics = this.props.analytics) == null || _this$props$analytics.onAnalyticsEvent({
2389
2350
  type: "perseus:expression-focused",
2390
2351
  payload: null
2391
2352
  });
@@ -2412,22 +2373,9 @@ class Expression extends React.Component {
2412
2373
  return [[]];
2413
2374
  };
2414
2375
  }
2415
- // TODO remove this in favor of just using expressionValidator
2416
- static validate(userInput, rubric, strings, locale) {
2417
- return expressionValidator(userInput, rubric, strings, locale);
2418
- }
2419
2376
  static getUserInputFromProps(props) {
2420
2377
  return normalizeTex(props.value);
2421
2378
  }
2422
- static getOneCorrectAnswerFromRubric(rubric) {
2423
- const correctAnswers = (rubric.answerForms || []).filter(answerForm => answerForm.considered === "correct");
2424
- if (correctAnswers.length === 0) {
2425
- return;
2426
- }
2427
- return correctAnswers[0].value;
2428
- }
2429
- //#endregion
2430
-
2431
2379
  getUserInput() {
2432
2380
  return Expression.getUserInputFromProps(this.props);
2433
2381
  }
@@ -2465,7 +2413,7 @@ class Expression extends React.Component {
2465
2413
  }, cb);
2466
2414
  }
2467
2415
  render() {
2468
- var _this$props$keypadCon, _this$props$analytics3;
2416
+ var _this$props$keypadCon, _this$props$analytics2;
2469
2417
  if (this.props.apiOptions.customKeypad) {
2470
2418
  return /*#__PURE__*/React.createElement(View, {
2471
2419
  className: css(styles$D.mobileLabelInputWrapper)
@@ -2538,7 +2486,7 @@ class Expression extends React.Component {
2538
2486
  hasError: this.state.showErrorStyle,
2539
2487
  ariaLabel: this.props.ariaLabel || this.context.strings.mathInputBox,
2540
2488
  extraKeys: (_this$props$keypadCon = this.props.keypadConfiguration) == null ? void 0 : _this$props$keypadCon.extraKeys,
2541
- analytics: (_this$props$analytics3 = this.props.analytics) != null ? _this$props$analytics3 : {
2489
+ analytics: (_this$props$analytics2 = this.props.analytics) != null ? _this$props$analytics2 : {
2542
2490
  onAnalyticsEvent: async function () {}
2543
2491
  }
2544
2492
  }))));
@@ -2655,11 +2603,7 @@ const ExpressionWithDependencies = /*#__PURE__*/React.forwardRef((props, ref) =>
2655
2603
  // methods and instead adjust Perseus to provide these facilities through
2656
2604
  // instance methods on our Renderers.
2657
2605
  // @ts-expect-error - TS2339 - Property 'validate' does not exist on type
2658
- ExpressionWithDependencies.validate = expressionValidator;
2659
- // @ts-expect-error - TS2339 - Property 'validate' does not exist on type
2660
2606
  ExpressionWithDependencies.getUserInputFromProps = Expression.getUserInputFromProps;
2661
- // @ts-expect-error - TS2339 - Property 'validate' does not exist on type
2662
- ExpressionWithDependencies.getOneCorrectAnswerFromRubric = Expression.getOneCorrectAnswerFromRubric;
2663
2607
  var Expression$1 = {
2664
2608
  name: "expression",
2665
2609
  displayName: "Expression / Equation",
@@ -2691,7 +2635,15 @@ var Expression$1 = {
2691
2635
  },
2692
2636
  propUpgrades: propUpgrades$2,
2693
2637
  // For use by the editor
2694
- isLintable: true
2638
+ isLintable: true,
2639
+ validator: expressionValidator,
2640
+ getOneCorrectAnswerFromRubric(rubric) {
2641
+ const correctAnswers = (rubric.answerForms || []).filter(answerForm => answerForm.considered === "correct");
2642
+ if (correctAnswers.length === 0) {
2643
+ return;
2644
+ }
2645
+ return correctAnswers[0].value;
2646
+ }
2695
2647
  };
2696
2648
 
2697
2649
  /**
@@ -2920,6 +2872,9 @@ function combineScores(scoreA, scoreB) {
2920
2872
  }
2921
2873
  });
2922
2874
  }
2875
+ function flattenScores(widgetScoreMap) {
2876
+ return Object.values(widgetScoreMap).reduce(combineScores, noScore);
2877
+ }
2923
2878
  function isCorrect(score) {
2924
2879
  return score.type === "points" && score.earned >= score.total;
2925
2880
  }
@@ -3452,6 +3407,7 @@ const Util = {
3452
3407
  shuffle: shuffle$3,
3453
3408
  split,
3454
3409
  combineScores,
3410
+ flattenScores,
3455
3411
  keScoreFromPerseusScore,
3456
3412
  firstNumericalParse: firstNumericalParse$1,
3457
3413
  stringArrayOfSize: stringArrayOfSize$1,
@@ -9988,6 +9944,8 @@ const setLabelMargins = function setLabelMargins(span, size) {
9988
9944
  });
9989
9945
  } else {
9990
9946
  var _$container$css, _$container$width, _$span$css;
9947
+ const currentHeightMatchesProps = span.scrollHeight === height;
9948
+
9991
9949
  // We are using jQuery to collect information and calculate a scale
9992
9950
  // since we don't have a way to pass it to this function.
9993
9951
  // We need the width of the container in order to calculate the scale to apply to the label.
@@ -10009,6 +9967,17 @@ const setLabelMargins = function setLabelMargins(span, size) {
10009
9967
  // Inherited line-height values can really mess up placement.
10010
9968
  $container.css("line-height", "normal");
10011
9969
 
9970
+ // TODO: Verify that this change doesn't impact other graphies
9971
+ // Check the full list of consuming widgets.
9972
+
9973
+ // If the change in line-height affected the height of the element,
9974
+ // then the height used for calculations should be updated.
9975
+ // This can happen when the first label in the container calls this method,
9976
+ // and the line-height was different when the height measurement was originally referenced.
9977
+ if (currentHeightMatchesProps && span.scrollHeight !== height) {
9978
+ height = span.scrollHeight;
9979
+ }
9980
+
10012
9981
  // The expected width of the graphie is found in the "max-width" property on ".svg-image" containers,
10013
9982
  // and is found in the "width" property on ".graphie" containers.
10014
9983
  const widthValues = (_$container$css = $container.css(["max-width", "width"])) != null ? _$container$css : {
@@ -14645,7 +14614,7 @@ const mapObject$1 = function mapObject(obj, lambda) {
14645
14614
  * });
14646
14615
  * {a: 'aa', b: 'bb'}
14647
14616
  */
14648
- const mapObjectFromArray$1 = function mapObjectFromArray(arr, lambda) {
14617
+ const mapObjectFromArray = function mapObjectFromArray(arr, lambda) {
14649
14618
  const result = {};
14650
14619
  _.each(arr, function (elem) {
14651
14620
  result[elem] = lambda(elem);
@@ -14655,7 +14624,7 @@ const mapObjectFromArray$1 = function mapObjectFromArray(arr, lambda) {
14655
14624
  var objective_ = {
14656
14625
  pluck: pluck,
14657
14626
  mapObject: mapObject$1,
14658
- mapObjectFromArray: mapObjectFromArray$1
14627
+ mapObjectFromArray: mapObjectFromArray
14659
14628
  };
14660
14629
 
14661
14630
  /**
@@ -18818,169 +18787,6 @@ class QuestionParagraph extends React.Component {
18818
18787
  }
18819
18788
  }
18820
18789
 
18821
- /**
18822
- * TranslationLinter asynchronously calls Khan Academy's poentry linter.
18823
- *
18824
- * The poentry linter checks for issues that prevent content from being
18825
- * translated. This linter is KA-specific and so TranslationLinter does not do
18826
- * anything in non-KA environments.
18827
- */
18828
-
18829
- // A LintCB is called once the linter has been run.
18830
-
18831
- // msec to wait before we actually call the linter after receiving the
18832
- // last call. The timer is reset if the lint is called during the wait.
18833
- const DEBOUNCE_TIMEOUT = 1000;
18834
- class TranslationLinter {
18835
- constructor() {
18836
- // To avoid linting when nothing has changed.
18837
- this.previousContent = null;
18838
- /**
18839
- * Runs the poentry linter on the given perseus string asynchronously.
18840
- *
18841
- * It doesn't do anything unless we're in a KA environment.
18842
- *
18843
- * onLintErrorsGenerated is called even if there are no errors. It is not
18844
- * called if perseusStr is unchanged from the last call.
18845
- *
18846
- * Linting is relatively expensive -- so we debounce linting each instance
18847
- * for some amount of time.
18848
- */
18849
- this.runLinter = debounce$1((perseusStr, onLintErrorsGenerated) => {
18850
- // TODO(FEI-5003): Do we still need to check for 'KA'?
18851
- if (typeof KA === "undefined") {
18852
- return;
18853
- }
18854
- if (perseusStr === this.previousContent) {
18855
- return;
18856
- }
18857
- this.previousContent = perseusStr;
18858
- if (perseusStr === "") {
18859
- onLintErrorsGenerated([]);
18860
- return;
18861
- }
18862
- fetch("/api/internal/translations/lint_poentry?preview=1&lang=en", {
18863
- headers: {
18864
- "Content-Type": "application/json"
18865
- },
18866
- body: JSON.stringify({
18867
- // The poentry linter verifies that the translation from
18868
- // the original text to the translated text is sane. We
18869
- // want to ensure that the translation from English to
18870
- // English is sane, so msgid === mstr.
18871
- msgid: perseusStr,
18872
- // msgid is the original text
18873
- msgstr: perseusStr,
18874
- // msgstr is the translated text
18875
- format: "perseus_text",
18876
- filename: ""
18877
- }),
18878
- method: "POST"
18879
- }).then(response => {
18880
- if (response.status >= 400) {
18881
- return {
18882
- status: "error",
18883
- message: "Could not run i18n linter."
18884
- };
18885
- }
18886
- return response.json();
18887
- }, rejection => {
18888
- return {
18889
- status: "error",
18890
- message: "Could not run i18n linter."
18891
- };
18892
- }).then(json => {
18893
- if (json.status === "error") {
18894
- onLintErrorsGenerated(["Some part of this text makes it untranslatable. " + "The specific message from the i18n linter was: " + json.message.replace(/\n/g, " ")]);
18895
- } else {
18896
- onLintErrorsGenerated([]);
18897
- }
18898
- });
18899
- }, DEBOUNCE_TIMEOUT);
18900
- }
18901
- /**
18902
- * Applies an array of errors generated by linters without position
18903
- * information (TranslationLinter and the legacy getSaveWarnings()) to the
18904
- * top of a Perseus tree.
18905
- */
18906
- applyLintErrors(parsedMarkdown, translationLintErrors) {
18907
- // These lint errors do not have position data associated with
18908
- // them, so we just plop them at the top.
18909
- if (translationLintErrors.length) {
18910
- const errorText = translationLintErrors.join("\n\n");
18911
- parsedMarkdown.unshift({
18912
- content: {
18913
- type: "text",
18914
- content: ""
18915
- },
18916
- insideTable: false,
18917
- message: errorText,
18918
- ruleName: "legacy-error",
18919
- severity: Rule.Severity.ERROR,
18920
- type: "lint"
18921
- });
18922
- }
18923
- }
18924
- }
18925
-
18926
- /**
18927
- * Preprocess TeX code to convert things that MathJax doesn't know how to handle
18928
- * to things is does.
18929
- */
18930
- var preprocessTex = (texCode => texCode
18931
- // Replace uses of \begin{align}...\end{align} which KaTeX doesn't
18932
- // support (yet) with \begin{aligned}...\end{aligned} which renders
18933
- // the same is supported by KaTeX. It does the same for align*.
18934
- // TODO(kevinb) update content to use aligned instead of align.
18935
- // TODO(LEMS-1608) Remove this replacement as MathJax supports the
18936
- // "align" macro correctly (and, in fact, it is not synonymous with
18937
- // "aligned").
18938
- .replace(/\{align[*]?\}/g, "{aligned}")
18939
- // Replace non-breaking spaces with regular spaces.
18940
- .replace(/[\u00a0]/g, " "));
18941
-
18942
- const {
18943
- interactiveSizes
18944
- } = constants$1;
18945
-
18946
- // Note: these size cutoffs represent content-width cutoffs as specified in
18947
- // http://zpl.io/1mVmvU (broken link - we no longer use Zeplin)
18948
- // TODO(benkomalo): these values aren't used in JS outside of this file, but
18949
- // are coupled to the values in styles/styles.less - DRY it up at some point
18950
- const smMax = articleMaxWidthTableInPx;
18951
- const mdMax = articleMaxWidthInPx;
18952
- const containerSizeClass = {
18953
- SMALL: "small",
18954
- MEDIUM: "medium",
18955
- LARGE: "large",
18956
- XLARGE: "xlarge"
18957
- };
18958
- const getClassFromWidth = width => {
18959
- if (!width) {
18960
- return containerSizeClass.MEDIUM;
18961
- }
18962
- if (width <= smMax) {
18963
- return containerSizeClass.SMALL;
18964
- }
18965
- if (width <= mdMax) {
18966
- return containerSizeClass.MEDIUM;
18967
- }
18968
- return containerSizeClass.LARGE;
18969
- };
18970
- const getInteractiveBoxFromSizeClass = sizeClass => {
18971
- if (sizeClass === containerSizeClass.SMALL) {
18972
- return [interactiveSizes.defaultBoxSizeSmall, interactiveSizes.defaultBoxSizeSmall];
18973
- }
18974
- return [interactiveSizes.defaultBoxSize, interactiveSizes.defaultBoxSize];
18975
- };
18976
-
18977
- var sizingUtils = /*#__PURE__*/Object.freeze({
18978
- __proto__: null,
18979
- containerSizeClass: containerSizeClass,
18980
- getClassFromWidth: getClassFromWidth,
18981
- getInteractiveBoxFromSizeClass: getInteractiveBoxFromSizeClass
18982
- });
18983
-
18984
18790
  const DEFAULT_ALIGNMENT$1 = "block";
18985
18791
  // NOTE(kevinb): "default" is not one in `validAlignments`.
18986
18792
  const DEFAULT_SUPPORTED_ALIGNMENTS = ["default"];
@@ -19081,6 +18887,14 @@ const getWidget = name => {
19081
18887
  }
19082
18888
  return widgets[name].widget;
19083
18889
  };
18890
+ const getWidgetExport = name => {
18891
+ var _widgets$name3;
18892
+ return (_widgets$name3 = widgets[name]) != null ? _widgets$name3 : null;
18893
+ };
18894
+ const getWidgetValidator = name => {
18895
+ var _widgets$name$validat, _widgets$name4;
18896
+ return (_widgets$name$validat = (_widgets$name4 = widgets[name]) == null ? void 0 : _widgets$name4.validator) != null ? _widgets$name$validat : null;
18897
+ };
19084
18898
  const getEditor = name => {
19085
18899
  return _.has(editors, name) ? editors[name] : null;
19086
18900
  };
@@ -19383,6 +19197,8 @@ var widgets$1 = /*#__PURE__*/Object.freeze({
19383
19197
  replaceEditor: replaceEditor,
19384
19198
  replaceDeprecatedEditors: replaceDeprecatedEditors,
19385
19199
  getWidget: getWidget,
19200
+ getWidgetExport: getWidgetExport,
19201
+ getWidgetValidator: getWidgetValidator,
19386
19202
  getEditor: getEditor,
19387
19203
  getTransform: getTransform,
19388
19204
  getVersion: getVersion,
@@ -19402,6 +19218,222 @@ var widgets$1 = /*#__PURE__*/Object.freeze({
19402
19218
  isLintable: isLintable
19403
19219
  });
19404
19220
 
19221
+ function emptyWidgetsFunctional(widgets,
19222
+ // This is a port of old code, I'm not sure why
19223
+ // we need widgetIds vs the keys of the widgets object
19224
+ widgetIds, userInputMap, strings, locale) {
19225
+ return widgetIds.filter(id => {
19226
+ const widget = widgets[id];
19227
+ if (!widget || widget.static) {
19228
+ // Static widgets shouldn't count as empty
19229
+ return false;
19230
+ }
19231
+ let score = null;
19232
+ const userInput = userInputMap[id];
19233
+ const validator = getWidgetValidator(widget.type);
19234
+ if (widget.type === "group") {
19235
+ const scores = scoreWidgetsFunctional(widget.options.widgets, Object.keys(widget.options.widgets), userInputMap[id], strings, locale);
19236
+ score = Util.flattenScores(scores);
19237
+ } else if (validator) {
19238
+ score = validator(userInput, widget.options, strings, locale);
19239
+ }
19240
+ if (score) {
19241
+ return Util.scoreIsEmpty(score);
19242
+ }
19243
+ });
19244
+ }
19245
+ function scoreWidgetsFunctional(widgets,
19246
+ // This is a port of old code, I'm not sure why
19247
+ // we need widgetIds vs the keys of the widgets object
19248
+ widgetIds, userInputMap, strings, locale) {
19249
+ const gradedWidgetIds = widgetIds.filter(id => {
19250
+ const props = widgets[id];
19251
+ const widgetIsGraded = (props == null ? void 0 : props.graded) == null || props.graded;
19252
+ const widgetIsStatic = !!(props != null && props.static);
19253
+ // Ungraded widgets or widgets set to static shouldn't be graded.
19254
+ return widgetIsGraded && !widgetIsStatic;
19255
+ });
19256
+ const widgetScores = {};
19257
+ gradedWidgetIds.forEach(id => {
19258
+ const widget = widgets[id];
19259
+ if (!widget) {
19260
+ return;
19261
+ }
19262
+ const userInput = userInputMap[id];
19263
+ const validator = getWidgetValidator(widget.type);
19264
+ if (widget.type === "group") {
19265
+ const scores = scoreWidgetsFunctional(widget.options.widgets, Object.keys(widget.options.widgets), userInputMap[id], strings, locale);
19266
+ widgetScores[id] = Util.flattenScores(scores);
19267
+ } else if (validator) {
19268
+ widgetScores[id] = validator(userInput, widget.options, strings, locale);
19269
+ }
19270
+ });
19271
+ return widgetScores;
19272
+ }
19273
+
19274
+ /**
19275
+ * TranslationLinter asynchronously calls Khan Academy's poentry linter.
19276
+ *
19277
+ * The poentry linter checks for issues that prevent content from being
19278
+ * translated. This linter is KA-specific and so TranslationLinter does not do
19279
+ * anything in non-KA environments.
19280
+ */
19281
+
19282
+ // A LintCB is called once the linter has been run.
19283
+
19284
+ // msec to wait before we actually call the linter after receiving the
19285
+ // last call. The timer is reset if the lint is called during the wait.
19286
+ const DEBOUNCE_TIMEOUT = 1000;
19287
+ class TranslationLinter {
19288
+ constructor() {
19289
+ // To avoid linting when nothing has changed.
19290
+ this.previousContent = null;
19291
+ /**
19292
+ * Runs the poentry linter on the given perseus string asynchronously.
19293
+ *
19294
+ * It doesn't do anything unless we're in a KA environment.
19295
+ *
19296
+ * onLintErrorsGenerated is called even if there are no errors. It is not
19297
+ * called if perseusStr is unchanged from the last call.
19298
+ *
19299
+ * Linting is relatively expensive -- so we debounce linting each instance
19300
+ * for some amount of time.
19301
+ */
19302
+ this.runLinter = debounce$1((perseusStr, onLintErrorsGenerated) => {
19303
+ // TODO(FEI-5003): Do we still need to check for 'KA'?
19304
+ if (typeof KA === "undefined") {
19305
+ return;
19306
+ }
19307
+ if (perseusStr === this.previousContent) {
19308
+ return;
19309
+ }
19310
+ this.previousContent = perseusStr;
19311
+ if (perseusStr === "") {
19312
+ onLintErrorsGenerated([]);
19313
+ return;
19314
+ }
19315
+ fetch("/api/internal/translations/lint_poentry?preview=1&lang=en", {
19316
+ headers: {
19317
+ "Content-Type": "application/json"
19318
+ },
19319
+ body: JSON.stringify({
19320
+ // The poentry linter verifies that the translation from
19321
+ // the original text to the translated text is sane. We
19322
+ // want to ensure that the translation from English to
19323
+ // English is sane, so msgid === mstr.
19324
+ msgid: perseusStr,
19325
+ // msgid is the original text
19326
+ msgstr: perseusStr,
19327
+ // msgstr is the translated text
19328
+ format: "perseus_text",
19329
+ filename: ""
19330
+ }),
19331
+ method: "POST"
19332
+ }).then(response => {
19333
+ if (response.status >= 400) {
19334
+ return {
19335
+ status: "error",
19336
+ message: "Could not run i18n linter."
19337
+ };
19338
+ }
19339
+ return response.json();
19340
+ }, rejection => {
19341
+ return {
19342
+ status: "error",
19343
+ message: "Could not run i18n linter."
19344
+ };
19345
+ }).then(json => {
19346
+ if (json.status === "error") {
19347
+ onLintErrorsGenerated(["Some part of this text makes it untranslatable. " + "The specific message from the i18n linter was: " + json.message.replace(/\n/g, " ")]);
19348
+ } else {
19349
+ onLintErrorsGenerated([]);
19350
+ }
19351
+ });
19352
+ }, DEBOUNCE_TIMEOUT);
19353
+ }
19354
+ /**
19355
+ * Applies an array of errors generated by linters without position
19356
+ * information (TranslationLinter and the legacy getSaveWarnings()) to the
19357
+ * top of a Perseus tree.
19358
+ */
19359
+ applyLintErrors(parsedMarkdown, translationLintErrors) {
19360
+ // These lint errors do not have position data associated with
19361
+ // them, so we just plop them at the top.
19362
+ if (translationLintErrors.length) {
19363
+ const errorText = translationLintErrors.join("\n\n");
19364
+ parsedMarkdown.unshift({
19365
+ content: {
19366
+ type: "text",
19367
+ content: ""
19368
+ },
19369
+ insideTable: false,
19370
+ message: errorText,
19371
+ ruleName: "legacy-error",
19372
+ severity: Rule.Severity.ERROR,
19373
+ type: "lint"
19374
+ });
19375
+ }
19376
+ }
19377
+ }
19378
+
19379
+ /**
19380
+ * Preprocess TeX code to convert things that MathJax doesn't know how to handle
19381
+ * to things is does.
19382
+ */
19383
+ var preprocessTex = (texCode => texCode
19384
+ // Replace uses of \begin{align}...\end{align} which KaTeX doesn't
19385
+ // support (yet) with \begin{aligned}...\end{aligned} which renders
19386
+ // the same is supported by KaTeX. It does the same for align*.
19387
+ // TODO(kevinb) update content to use aligned instead of align.
19388
+ // TODO(LEMS-1608) Remove this replacement as MathJax supports the
19389
+ // "align" macro correctly (and, in fact, it is not synonymous with
19390
+ // "aligned").
19391
+ .replace(/\{align[*]?\}/g, "{aligned}")
19392
+ // Replace non-breaking spaces with regular spaces.
19393
+ .replace(/[\u00a0]/g, " "));
19394
+
19395
+ const {
19396
+ interactiveSizes
19397
+ } = constants$1;
19398
+
19399
+ // Note: these size cutoffs represent content-width cutoffs as specified in
19400
+ // http://zpl.io/1mVmvU (broken link - we no longer use Zeplin)
19401
+ // TODO(benkomalo): these values aren't used in JS outside of this file, but
19402
+ // are coupled to the values in styles/styles.less - DRY it up at some point
19403
+ const smMax = articleMaxWidthTableInPx;
19404
+ const mdMax = articleMaxWidthInPx;
19405
+ const containerSizeClass = {
19406
+ SMALL: "small",
19407
+ MEDIUM: "medium",
19408
+ LARGE: "large",
19409
+ XLARGE: "xlarge"
19410
+ };
19411
+ const getClassFromWidth = width => {
19412
+ if (!width) {
19413
+ return containerSizeClass.MEDIUM;
19414
+ }
19415
+ if (width <= smMax) {
19416
+ return containerSizeClass.SMALL;
19417
+ }
19418
+ if (width <= mdMax) {
19419
+ return containerSizeClass.MEDIUM;
19420
+ }
19421
+ return containerSizeClass.LARGE;
19422
+ };
19423
+ const getInteractiveBoxFromSizeClass = sizeClass => {
19424
+ if (sizeClass === containerSizeClass.SMALL) {
19425
+ return [interactiveSizes.defaultBoxSizeSmall, interactiveSizes.defaultBoxSizeSmall];
19426
+ }
19427
+ return [interactiveSizes.defaultBoxSize, interactiveSizes.defaultBoxSize];
19428
+ };
19429
+
19430
+ var sizingUtils = /*#__PURE__*/Object.freeze({
19431
+ __proto__: null,
19432
+ containerSizeClass: containerSizeClass,
19433
+ getClassFromWidth: getClassFromWidth,
19434
+ getInteractiveBoxFromSizeClass: getInteractiveBoxFromSizeClass
19435
+ });
19436
+
19405
19437
  class WidgetContainer extends React.Component {
19406
19438
  constructor(...args) {
19407
19439
  super(...args);
@@ -19547,8 +19579,7 @@ WidgetContainer.defaultProps = {
19547
19579
  };
19548
19580
 
19549
19581
  const {
19550
- mapObject,
19551
- mapObjectFromArray
19582
+ mapObject
19552
19583
  } = objective_;
19553
19584
  const rContainsNonWhitespace = /\S/;
19554
19585
  const rImageURL = /(web\+graphie|https):\/\/[^\s]*/;
@@ -19713,11 +19744,7 @@ class Renderer extends React.Component {
19713
19744
  }
19714
19745
  let interactionTracker = this._interactionTrackers[id];
19715
19746
  if (!interactionTracker) {
19716
- interactionTracker = this._interactionTrackers[id] = new InteractionTracker(apiOptions.trackInteraction,
19717
- // @ts-expect-error - TS2345 - Argument of type 'string | null | undefined' is not assignable to parameter of type 'string'.
19718
- widgetInfo && widgetInfo.type, id,
19719
- // @ts-expect-error - TS2345 - Argument of type 'string | null | undefined' is not assignable to parameter of type 'string'.
19720
- getTracking(widgetInfo && widgetInfo.type));
19747
+ interactionTracker = this._interactionTrackers[id] = new InteractionTracker(apiOptions.trackInteraction, widgetInfo && widgetInfo.type, id, getTracking(widgetInfo && widgetInfo.type));
19721
19748
  }
19722
19749
  return _extends({}, widgetProps, {
19723
19750
  widgetId: id,
@@ -20495,23 +20522,6 @@ class Renderer extends React.Component {
20495
20522
  }, this);
20496
20523
  return state;
20497
20524
  };
20498
- this.emptyWidgets = () => {
20499
- // @ts-expect-error - TS2345 - Argument of type '(id: string) => boolean | undefined' is not assignable to parameter of type 'Iteratee<string[], boolean, string>'.
20500
- return _.filter(this.widgetIds, id => {
20501
- const widgetInfo = this._getWidgetInfo(id);
20502
- if (widgetInfo.static) {
20503
- // Static widgets shouldn't count as empty
20504
- return false;
20505
- }
20506
- const widget = this.getWidgetInstance(id);
20507
- if (widget && widget.simpleValidate) {
20508
- const score = widget.simpleValidate(widgetInfo.options,
20509
- // @ts-expect-error - TS2345 - Argument of type 'null' is not assignable to parameter of type '((widgetId: any, value: string, message?: string | null | undefined) => unknown) | undefined'.
20510
- null);
20511
- return Util.scoreIsEmpty(score);
20512
- }
20513
- });
20514
- };
20515
20525
  this._setWidgetProps = (id, newProps, cb, silent) => {
20516
20526
  this.setState(prevState => {
20517
20527
  const widgetProps = _extends({}, prevState.widgetProps, {
@@ -20568,19 +20578,6 @@ class Renderer extends React.Component {
20568
20578
  // Widget handles parsing of the interWidgetPath.
20569
20579
  widget == null || widget.setInputValue == null || widget.setInputValue(interWidgetPath, newValue, focus);
20570
20580
  };
20571
- /**
20572
- * Returns an array of the widget `.getUserInput()` results
20573
- */
20574
- this.getUserInput = () => {
20575
- return this.widgetIds.map(id => {
20576
- const widget = this.getWidgetInstance(id);
20577
- if (widget && widget.getUserInput) {
20578
- // TODO(Jeremy): Add the widget ID in here so we can more
20579
- // easily correlate it to the widget state.
20580
- return widget.getUserInput();
20581
- }
20582
- });
20583
- };
20584
20581
  /**
20585
20582
  * Returns an array of all widget IDs in the order they occur in
20586
20583
  * the content.
@@ -20588,56 +20585,6 @@ class Renderer extends React.Component {
20588
20585
  this.getWidgetIds = () => {
20589
20586
  return this.widgetIds;
20590
20587
  };
20591
- /**
20592
- * Returns the result of `.getUserInput()` for each widget, in
20593
- * a map from widgetId to userInput.
20594
- * NOTE(jeremy): This function is hauntingly similar to `getUserInput` with
20595
- * the major difference being that this function returns a map of
20596
- * `widgetID` => UserInput and `getUserInput` simply returns an array. It
20597
- * would be trivial to map between the results of each of these functions,
20598
- * so we should aim to remove one of these functions.
20599
- */
20600
- this.getUserInputForWidgets = () => {
20601
- return mapObjectFromArray(this.widgetIds, id => {
20602
- const widget = this.getWidgetInstance(id);
20603
- if (widget && widget.getUserInput) {
20604
- return widget.getUserInput();
20605
- }
20606
- });
20607
- };
20608
- /**
20609
- * Returns an object mapping from widget ID to perseus-style score.
20610
- * The keys of this object are the values of the array returned
20611
- * from `getWidgetIds`.
20612
- */
20613
- this.scoreWidgets = () => {
20614
- const widgetProps = this.state.widgetInfo;
20615
- const gradedWidgetIds = _.filter(this.widgetIds, id => {
20616
- const props = widgetProps[id];
20617
- const widgetIsGraded = (props == null ? void 0 : props.graded) == null || props.graded;
20618
- const widgetIsStatic = !!(props != null && props.static);
20619
- // Ungraded widgets or widgets set to static shouldn't be graded.
20620
- return widgetIsGraded && !widgetIsStatic;
20621
- });
20622
- const widgetScores = {};
20623
- _.each(gradedWidgetIds, id => {
20624
- const props = widgetProps[id];
20625
- const widget = this.getWidgetInstance(id);
20626
- // widget can be undefined if it hasn't yet been rendered
20627
- if (widget && widget.simpleValidate) {
20628
- widgetScores[id] = widget.simpleValidate(_extends({}, props == null ? void 0 : props.options, {
20629
- scoring: true
20630
- }));
20631
- }
20632
- });
20633
- return widgetScores;
20634
- };
20635
- /**
20636
- * Grades the content.
20637
- */
20638
- this.score = () => {
20639
- return _.reduce(this.scoreWidgets(), Util.combineScores, Util.noScore);
20640
- };
20641
20588
  this.guessAndScore = () => {
20642
20589
  const totalGuess = this.getUserInput();
20643
20590
  const totalScore = this.score();
@@ -20811,6 +20758,59 @@ class Renderer extends React.Component {
20811
20758
  }
20812
20759
  this._isMounted = false;
20813
20760
  }
20761
+ emptyWidgets() {
20762
+ return emptyWidgetsFunctional(this.state.widgetInfo, this.widgetIds, this.getUserInputMap(), this.props.strings, this.context.locale);
20763
+ }
20764
+ /**
20765
+ * Returns an array of the widget `.getUserInput()` results
20766
+ *
20767
+ * TODO: can we remove this?
20768
+ * @deprecated use getUserInputMap
20769
+ */
20770
+ getUserInput() {
20771
+ return this.widgetIds.map(id => {
20772
+ const widget = this.getWidgetInstance(id);
20773
+ if (widget && widget.getUserInput) {
20774
+ // TODO(Jeremy): Add the widget ID in here so we can more
20775
+ // easily correlate it to the widget state.
20776
+ return widget.getUserInput();
20777
+ }
20778
+ });
20779
+ }
20780
+
20781
+ /**
20782
+ * Returns an object of the widget `.getUserInput()` results
20783
+ */
20784
+ getUserInputMap() {
20785
+ const userInputMap = {};
20786
+ this.widgetIds.forEach(id => {
20787
+ const widget = this.getWidgetInstance(id);
20788
+ // Handle Groups, which have their own sets of widgets
20789
+ if (widget != null && widget.getUserInputMap) {
20790
+ userInputMap[id] = widget.getUserInputMap();
20791
+ } else if (widget != null && widget.getUserInput) {
20792
+ userInputMap[id] = widget.getUserInput();
20793
+ }
20794
+ });
20795
+ return userInputMap;
20796
+ }
20797
+ /**
20798
+ * Returns an object mapping from widget ID to perseus-style score.
20799
+ * The keys of this object are the values of the array returned
20800
+ * from `getWidgetIds`.
20801
+ */
20802
+ scoreWidgets() {
20803
+ return scoreWidgetsFunctional(this.state.widgetInfo, this.widgetIds, this.getUserInputMap(), this.props.strings, this.context.locale);
20804
+ }
20805
+
20806
+ /**
20807
+ * Grades the content.
20808
+ */
20809
+ score() {
20810
+ const scores = this.scoreWidgets();
20811
+ const combinedScore = Util.flattenScores(scores);
20812
+ return combinedScore;
20813
+ }
20814
20814
  render() {
20815
20815
  const apiOptions = this.getApiOptions();
20816
20816
  if (this.reuseMarkdown) {
@@ -20885,11 +20885,11 @@ class Renderer extends React.Component {
20885
20885
  // If highlightLint is true and lint is detected, this call
20886
20886
  // will modify the parse tree by adding lint nodes that will
20887
20887
  // serve to highlight the lint when rendered
20888
- const context = _extends({
20888
+ const fullLinterContext = _extends({
20889
20889
  content: this.props.content,
20890
20890
  widgets: this.props.widgets
20891
20891
  }, this.props.linterContext);
20892
- PerseusLinter.runLinter(parsedMarkdown, context, true);
20892
+ PerseusLinter.runLinter(parsedMarkdown, fullLinterContext, true);
20893
20893
 
20894
20894
  // Apply the lint errors from the last TranslationLinter run.
20895
20895
  // TODO(joshuan): Support overlapping dots.
@@ -20911,6 +20911,7 @@ class Renderer extends React.Component {
20911
20911
  return this.lastRenderedMarkdown;
20912
20912
  }
20913
20913
  }
20914
+ Renderer.contextType = PerseusI18nContext;
20914
20915
  Renderer.defaultProps = {
20915
20916
  content: "",
20916
20917
  widgets: {},
@@ -21398,6 +21399,16 @@ InputWithExamples.defaultProps = {
21398
21399
  className: ""
21399
21400
  };
21400
21401
 
21402
+ function _objectWithoutPropertiesLoose(r, e) {
21403
+ if (null == r) return {};
21404
+ var t = {};
21405
+ for (var n in r) if ({}.hasOwnProperty.call(r, n)) {
21406
+ if (e.includes(n)) continue;
21407
+ t[n] = r[n];
21408
+ }
21409
+ return t;
21410
+ }
21411
+
21401
21412
  const _excluded$4 = ["keypadElement", "onFocus", "value"];
21402
21413
  class SimpleKeypadInput extends React.Component {
21403
21414
  constructor(...args) {
@@ -21800,30 +21811,14 @@ class InputNumber extends React.Component {
21800
21811
  return [strings.yourAnswer].concat(examples);
21801
21812
  };
21802
21813
  }
21803
- static validate(state, rubric, strings) {
21804
- return inputNumberValidator(state, rubric, strings);
21805
- }
21806
21814
  static getUserInputFromProps(props) {
21807
21815
  return {
21808
21816
  currentValue: props.currentValue
21809
21817
  };
21810
21818
  }
21811
- static getOneCorrectAnswerFromRubric(rubric) {
21812
- if (rubric.value == null) {
21813
- return;
21814
- }
21815
- let answerString = String(rubric.value);
21816
- if (rubric.inexact && rubric.maxError) {
21817
- answerString += " \u00B1 " + rubric.maxError;
21818
- }
21819
- return answerString;
21820
- }
21821
21819
  getUserInput() {
21822
21820
  return InputNumber.getUserInputFromProps(this.props);
21823
21821
  }
21824
- simpleValidate(rubric) {
21825
- return inputNumberValidator(this.getUserInput(), rubric, this.context.strings);
21826
- }
21827
21822
  render() {
21828
21823
  if (this.props.apiOptions.customKeypad) {
21829
21824
  // TODO(charlie): Support "Review Mode".
@@ -21928,7 +21923,18 @@ var InputNumber$1 = {
21928
21923
  hidden: true,
21929
21924
  widget: InputNumber,
21930
21925
  transform: propTransform$3,
21931
- isLintable: true
21926
+ isLintable: true,
21927
+ validator: inputNumberValidator,
21928
+ getOneCorrectAnswerFromRubric(rubric) {
21929
+ if (rubric.value == null) {
21930
+ return;
21931
+ }
21932
+ let answerString = String(rubric.value);
21933
+ if (rubric.inexact && rubric.maxError) {
21934
+ answerString += " \u00B1 " + rubric.maxError;
21935
+ }
21936
+ return answerString;
21937
+ }
21932
21938
  };
21933
21939
 
21934
21940
  const ParseTex = TexWrangler.parseTex;
@@ -22174,7 +22180,135 @@ class NumericInput extends React.Component {
22174
22180
  currentValue: props.currentValue
22175
22181
  };
22176
22182
  }
22177
- static getOneCorrectAnswerFromRubric(rubric) {
22183
+ getUserInput() {
22184
+ return NumericInput.getUserInputFromProps(this.props);
22185
+ }
22186
+ render() {
22187
+ let labelText = this.props.labelText;
22188
+ if (labelText == null || labelText === "") {
22189
+ labelText = this.context.strings.yourAnswerLabel;
22190
+ }
22191
+
22192
+ // To right align a custom keypad we need to wrap it.
22193
+ const maybeRightAlignKeypadInput = keypadInput => {
22194
+ return this.props.rightAlign ? /*#__PURE__*/React.createElement("div", {
22195
+ className: "perseus-input-right-align"
22196
+ }, keypadInput) : keypadInput;
22197
+ };
22198
+ if (this.props.apiOptions.customKeypad) {
22199
+ // TODO(charlie): Support "Review Mode".
22200
+ return maybeRightAlignKeypadInput( /*#__PURE__*/React.createElement(SimpleKeypadInput, {
22201
+ ref: ref => this.inputRef = ref,
22202
+ value: this.props.currentValue,
22203
+ keypadElement: this.props.keypadElement,
22204
+ onChange: this.handleChange,
22205
+ onFocus: this._handleFocus,
22206
+ onBlur: this._handleBlur
22207
+ }));
22208
+ }
22209
+
22210
+ // Note: This is _very_ similar to what `input-number.jsx` does. If
22211
+ // you modify this, double-check if you also need to modify that
22212
+ // component.
22213
+ const styles = StyleSheet.create({
22214
+ input: {
22215
+ textAlign: this.props.rightAlign ? "right" : "left",
22216
+ width: this.props.size === "small" ? 40 : 80,
22217
+ padding: 0,
22218
+ height: "auto"
22219
+ }
22220
+ });
22221
+ return /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement(InputWithExamples, {
22222
+ ref: ref => this.inputRef = ref,
22223
+ value: this.props.currentValue,
22224
+ onChange: this.handleChange,
22225
+ labelText: labelText,
22226
+ examples: this.examples(),
22227
+ shouldShowExamples: this.shouldShowExamples(),
22228
+ onFocus: this._handleFocus,
22229
+ onBlur: this._handleBlur,
22230
+ id: this.props.widgetId,
22231
+ disabled: this.props.apiOptions.readOnly,
22232
+ style: styles.input
22233
+ }));
22234
+ }
22235
+ }
22236
+
22237
+ // TODO(thomas): Currently we receive a list of lists of acceptable answer types
22238
+ // and union them down into a single set. It's worth considering whether it
22239
+ // wouldn't make more sense to have a single set of acceptable answer types for
22240
+ // a given *problem* rather than for each possible [correct/wrong] *answer*.
22241
+ // When should two answers to a problem take different answer types?
22242
+ // See D27790 for more discussion.
22243
+ NumericInput.contextType = PerseusI18nContext;
22244
+ NumericInput.defaultProps = {
22245
+ currentValue: "",
22246
+ size: "normal",
22247
+ rightAlign: false,
22248
+ apiOptions: ApiOptions.defaults,
22249
+ coefficient: false,
22250
+ answerForms: [],
22251
+ labelText: "",
22252
+ linterContext: linterContextDefault
22253
+ };
22254
+ const unionAnswerForms = function unionAnswerForms(answerFormsList) {
22255
+ // Takes a list of lists of answer forms, and returns a list of the forms
22256
+ // in each of these lists in the same order that they're listed in the
22257
+ // `formExamples` forms from above.
22258
+
22259
+ // uniqueBy takes a list of elements and a function which compares whether
22260
+ // two elements are equal, and returns a list of unique elements. This is
22261
+ // just a helper function here, but works generally.
22262
+ const uniqueBy = function uniqueBy(list, iteratee) {
22263
+ // @ts-expect-error - TS2347 - Untyped function calls may not accept type arguments.
22264
+ return list.reduce((uniqueList, element) => {
22265
+ // For each element, decide whether it's already in the list of
22266
+ // unique items.
22267
+ const inList = _.find(uniqueList, iteratee.bind(null, element));
22268
+ if (inList) {
22269
+ return uniqueList;
22270
+ }
22271
+ return uniqueList.concat([element]);
22272
+ }, []);
22273
+ };
22274
+
22275
+ // Pull out all of the forms from the different lists.
22276
+ const allForms = answerFormsList.flat();
22277
+ // Pull out the unique forms using uniqueBy.
22278
+ const uniqueForms = uniqueBy(allForms, _.isEqual);
22279
+ // Sort them by the order they appear in the `formExamples` list.
22280
+ const formExampleKeys = Object.keys(formExamples);
22281
+ return _.sortBy(uniqueForms, form => {
22282
+ return formExampleKeys.indexOf(form.name);
22283
+ });
22284
+ };
22285
+ const propsTransform = function propsTransform(widgetOptions) {
22286
+ const rendererProps = _.extend(_.omit(widgetOptions, "answers"), {
22287
+ answerForms: unionAnswerForms(
22288
+ // Pull out the name of each form and whether that form has
22289
+ // required simplification.
22290
+ widgetOptions.answers.map(answer => {
22291
+ // @ts-expect-error - TS2345 - Argument of type 'readonly MathFormat[] | undefined' is not assignable to parameter of type 'Collection<any>'.
22292
+ return _.map(answer.answerForms, form => {
22293
+ return {
22294
+ simplify: answer.simplify,
22295
+ name: form
22296
+ };
22297
+ });
22298
+ }))
22299
+ });
22300
+ return rendererProps;
22301
+ };
22302
+ var NumericInput$1 = {
22303
+ name: "numeric-input",
22304
+ displayName: "Numeric input",
22305
+ defaultAlignment: "inline-block",
22306
+ accessible: true,
22307
+ widget: NumericInput,
22308
+ transform: propsTransform,
22309
+ isLintable: true,
22310
+ validator: numericInputValidator,
22311
+ getOneCorrectAnswerFromRubric(rubric) {
22178
22312
  const correctAnswers = rubric.answers.filter(answer => answer.status === "correct");
22179
22313
  const answerStrings = correctAnswers.map(answer => {
22180
22314
  // Figure out how this answer is supposed to be
@@ -22202,139 +22336,6 @@ class NumericInput extends React.Component {
22202
22336
  }
22203
22337
  return answerStrings[0];
22204
22338
  }
22205
- static validate(userInput, rubric, strings) {
22206
- return numericInputValidator(userInput, rubric, strings);
22207
- }
22208
- simpleValidate(rubric) {
22209
- return numericInputValidator(this.getUserInput(), rubric, this.context.strings);
22210
- }
22211
- getUserInput() {
22212
- return NumericInput.getUserInputFromProps(this.props);
22213
- }
22214
- render() {
22215
- let labelText = this.props.labelText;
22216
- if (labelText == null || labelText === "") {
22217
- labelText = this.context.strings.yourAnswerLabel;
22218
- }
22219
-
22220
- // To right align a custom keypad we need to wrap it.
22221
- const maybeRightAlignKeypadInput = keypadInput => {
22222
- return this.props.rightAlign ? /*#__PURE__*/React.createElement("div", {
22223
- className: "perseus-input-right-align"
22224
- }, keypadInput) : keypadInput;
22225
- };
22226
- if (this.props.apiOptions.customKeypad) {
22227
- // TODO(charlie): Support "Review Mode".
22228
- return maybeRightAlignKeypadInput( /*#__PURE__*/React.createElement(SimpleKeypadInput, {
22229
- ref: ref => this.inputRef = ref,
22230
- value: this.props.currentValue,
22231
- keypadElement: this.props.keypadElement,
22232
- onChange: this.handleChange,
22233
- onFocus: this._handleFocus,
22234
- onBlur: this._handleBlur
22235
- }));
22236
- }
22237
-
22238
- // Note: This is _very_ similar to what `input-number.jsx` does. If
22239
- // you modify this, double-check if you also need to modify that
22240
- // component.
22241
- const styles = StyleSheet.create({
22242
- input: {
22243
- textAlign: this.props.rightAlign ? "right" : "left",
22244
- width: this.props.size === "small" ? 40 : 80,
22245
- padding: 0,
22246
- height: "auto"
22247
- }
22248
- });
22249
- return /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement(InputWithExamples, {
22250
- ref: ref => this.inputRef = ref,
22251
- value: this.props.currentValue,
22252
- onChange: this.handleChange,
22253
- labelText: labelText,
22254
- examples: this.examples(),
22255
- shouldShowExamples: this.shouldShowExamples(),
22256
- onFocus: this._handleFocus,
22257
- onBlur: this._handleBlur,
22258
- id: this.props.widgetId,
22259
- disabled: this.props.apiOptions.readOnly,
22260
- style: styles.input
22261
- }));
22262
- }
22263
- }
22264
-
22265
- // TODO(thomas): Currently we receive a list of lists of acceptable answer types
22266
- // and union them down into a single set. It's worth considering whether it
22267
- // wouldn't make more sense to have a single set of acceptable answer types for
22268
- // a given *problem* rather than for each possible [correct/wrong] *answer*.
22269
- // When should two answers to a problem take different answer types?
22270
- // See D27790 for more discussion.
22271
- NumericInput.contextType = PerseusI18nContext;
22272
- NumericInput.defaultProps = {
22273
- currentValue: "",
22274
- size: "normal",
22275
- rightAlign: false,
22276
- apiOptions: ApiOptions.defaults,
22277
- coefficient: false,
22278
- answerForms: [],
22279
- labelText: "",
22280
- linterContext: linterContextDefault
22281
- };
22282
- const unionAnswerForms = function unionAnswerForms(answerFormsList) {
22283
- // Takes a list of lists of answer forms, and returns a list of the forms
22284
- // in each of these lists in the same order that they're listed in the
22285
- // `formExamples` forms from above.
22286
-
22287
- // uniqueBy takes a list of elements and a function which compares whether
22288
- // two elements are equal, and returns a list of unique elements. This is
22289
- // just a helper function here, but works generally.
22290
- const uniqueBy = function uniqueBy(list, iteratee) {
22291
- // @ts-expect-error - TS2347 - Untyped function calls may not accept type arguments.
22292
- return list.reduce((uniqueList, element) => {
22293
- // For each element, decide whether it's already in the list of
22294
- // unique items.
22295
- const inList = _.find(uniqueList, iteratee.bind(null, element));
22296
- if (inList) {
22297
- return uniqueList;
22298
- }
22299
- return uniqueList.concat([element]);
22300
- }, []);
22301
- };
22302
-
22303
- // Pull out all of the forms from the different lists.
22304
- const allForms = answerFormsList.flat();
22305
- // Pull out the unique forms using uniqueBy.
22306
- const uniqueForms = uniqueBy(allForms, _.isEqual);
22307
- // Sort them by the order they appear in the `formExamples` list.
22308
- const formExampleKeys = Object.keys(formExamples);
22309
- return _.sortBy(uniqueForms, form => {
22310
- return formExampleKeys.indexOf(form.name);
22311
- });
22312
- };
22313
- const propsTransform = function propsTransform(widgetOptions) {
22314
- const rendererProps = _.extend(_.omit(widgetOptions, "answers"), {
22315
- answerForms: unionAnswerForms(
22316
- // Pull out the name of each form and whether that form has
22317
- // required simplification.
22318
- widgetOptions.answers.map(answer => {
22319
- // @ts-expect-error - TS2345 - Argument of type 'readonly MathFormat[] | undefined' is not assignable to parameter of type 'Collection<any>'.
22320
- return _.map(answer.answerForms, form => {
22321
- return {
22322
- simplify: answer.simplify,
22323
- name: form
22324
- };
22325
- });
22326
- }))
22327
- });
22328
- return rendererProps;
22329
- };
22330
- var NumericInput$1 = {
22331
- name: "numeric-input",
22332
- displayName: "Numeric input",
22333
- defaultAlignment: "inline-block",
22334
- accessible: true,
22335
- widget: NumericInput,
22336
- transform: propsTransform,
22337
- isLintable: true
22338
22339
  };
22339
22340
 
22340
22341
  /**
@@ -24142,6 +24143,9 @@ class Passage extends React.Component {
24142
24143
  this._onResize = void 0;
24143
24144
  // @ts-expect-error - TS2564 - Property '_stylesAppiedTimer' has no initializer and is not definitely assigned in the constructor.
24144
24145
  this._stylesAppiedTimer = void 0;
24146
+ // this just helps with TS weak typing when a Widget
24147
+ // doesn't implement any Widget methods
24148
+ this.isWidget = true;
24145
24149
  this.state = {
24146
24150
  nLines: null,
24147
24151
  startLineNumbersAfter: 0,
@@ -24153,10 +24157,6 @@ class Passage extends React.Component {
24153
24157
  });
24154
24158
  };
24155
24159
  }
24156
- // TODO (LEMS-2396): remove validation logic from widgets that don't validate
24157
- static validate() {
24158
- return noopValidator();
24159
- }
24160
24160
  componentDidMount() {
24161
24161
  this._updateState();
24162
24162
 
@@ -24361,21 +24361,6 @@ class Passage extends React.Component {
24361
24361
  };
24362
24362
  }
24363
24363
 
24364
- /**
24365
- * Misc functions
24366
- *
24367
- * These are misc widget functions used for the widget API
24368
- */
24369
-
24370
- getUserInput() {
24371
- return null;
24372
- }
24373
-
24374
- // TODO (LEMS-2396): remove validation logic from widgets that don't validate
24375
- simpleValidate() {
24376
- return noopValidator();
24377
- }
24378
-
24379
24364
  /**
24380
24365
  * Rendering
24381
24366
  *
@@ -24528,7 +24513,8 @@ var Passage$1 = {
24528
24513
  transform: editorProps => {
24529
24514
  return _.pick(editorProps, "passageTitle", "passageText", "footnotes", "showLineNumbers");
24530
24515
  },
24531
- isLintable: true
24516
+ isLintable: true,
24517
+ validator: () => noopValidator()
24532
24518
  };
24533
24519
 
24534
24520
  /**
@@ -24554,6 +24540,9 @@ class PassageRef extends React.Component {
24554
24540
  this._isMounted = void 0;
24555
24541
  // @ts-expect-error - TS2564 - Property '_throttledUpdateRange' has no initializer and is not definitely assigned in the constructor.
24556
24542
  this._throttledUpdateRange = void 0;
24543
+ // this just helps with TS weak typing when a Widget
24544
+ // doesn't implement any Widget methods
24545
+ this.isWidget = true;
24557
24546
  this.state = {
24558
24547
  lineRange: null,
24559
24548
  content: null
@@ -24582,10 +24571,6 @@ class PassageRef extends React.Component {
24582
24571
  }
24583
24572
  };
24584
24573
  }
24585
- // TODO (LEMS-2396): remove validation logic from widgets that don't validate
24586
- static validate() {
24587
- return noopValidator();
24588
- }
24589
24574
  componentDidMount() {
24590
24575
  // TODO(scottgrant): This is a hack to remove the deprecated call to
24591
24576
  // this.isMounted() but is still considered an anti-pattern.
@@ -24604,15 +24589,6 @@ class PassageRef extends React.Component {
24604
24589
  window.removeEventListener("resize", this._throttledUpdateRange);
24605
24590
  this._isMounted = false;
24606
24591
  }
24607
-
24608
- // TODO (LEMS-2396): remove validation logic from widgets that don't validate
24609
- getUserInput() {
24610
- return null;
24611
- }
24612
- // TODO (LEMS-2396): remove validation logic from widgets that don't validate
24613
- simpleValidate() {
24614
- return noopValidator();
24615
- }
24616
24592
  render() {
24617
24593
  const {
24618
24594
  strings
@@ -24667,7 +24643,9 @@ var PassageRef$1 = {
24667
24643
  version: {
24668
24644
  major: 0,
24669
24645
  minor: 1
24670
- }
24646
+ },
24647
+ // TODO: things that aren't interactive shouldn't need validators
24648
+ validator: () => noopValidator()
24671
24649
  };
24672
24650
 
24673
24651
  /**
@@ -26169,7 +26147,7 @@ class Radio$1 extends React.Component {
26169
26147
  choiceStates
26170
26148
  } = this.props;
26171
26149
  if (choiceStates) {
26172
- const score = this.simpleValidate(rubric);
26150
+ const score = radioValidator(this.getUserInput(), rubric, this.context.strings);
26173
26151
  const widgetCorrect = score.type === "points" && score.total === score.earned;
26174
26152
  const newStates = choiceStates.map(state => _extends({}, state, {
26175
26153
  highlighted: state.selected,
@@ -26217,9 +26195,6 @@ class Radio$1 extends React.Component {
26217
26195
  }
26218
26196
  };
26219
26197
  }
26220
- static validate(userInput, rubric, strings) {
26221
- return radioValidator(userInput, rubric, strings);
26222
- }
26223
26198
  static getUserInputFromProps(props) {
26224
26199
  // Return checked inputs in the form {choicesSelected: [bool]}. (Dear
26225
26200
  // future timeline implementers: this used to be {value: i} before
@@ -26310,9 +26285,6 @@ class Radio$1 extends React.Component {
26310
26285
  getUserInput() {
26311
26286
  return Radio$1.getUserInputFromProps(this.props);
26312
26287
  }
26313
- simpleValidate(rubric) {
26314
- return radioValidator(this.getUserInput(), rubric, this.context.strings);
26315
- }
26316
26288
  render() {
26317
26289
  const {
26318
26290
  choices
@@ -26547,7 +26519,8 @@ var Radio = {
26547
26519
  minor: 0
26548
26520
  },
26549
26521
  propUpgrades: propUpgrades$1,
26550
- isLintable: true
26522
+ isLintable: true,
26523
+ validator: radioValidator
26551
26524
  };
26552
26525
 
26553
26526
  /* eslint-disable import/no-named-as-default */
@@ -26590,9 +26563,6 @@ class Categorizer extends React.Component {
26590
26563
  return change.apply(this, args);
26591
26564
  };
26592
26565
  }
26593
- static validate(userInput, rubric, strings) {
26594
- return categorizerValidator(userInput, rubric, strings);
26595
- }
26596
26566
  static getUserInputFromProps(props) {
26597
26567
  return {
26598
26568
  values: props.values
@@ -26608,9 +26578,6 @@ class Categorizer extends React.Component {
26608
26578
  this.change("values", values);
26609
26579
  this.props.trackInteraction();
26610
26580
  }
26611
- simpleValidate(rubric) {
26612
- return categorizerValidator(this.getUserInput(), rubric, this.context.strings);
26613
- }
26614
26581
  render() {
26615
26582
  const self = this;
26616
26583
 
@@ -26756,7 +26723,8 @@ var Categorizer$1 = {
26756
26723
  staticTransform: editorProps => {
26757
26724
  return _.pick(editorProps, "items", "categories", "values", "randomizeItems");
26758
26725
  },
26759
- isLintable: true
26726
+ isLintable: true,
26727
+ validator: categorizerValidator
26760
26728
  };
26761
26729
 
26762
26730
  /**
@@ -26880,10 +26848,6 @@ class CSProgram extends React.Component {
26880
26848
  return change.apply(this, args);
26881
26849
  };
26882
26850
  }
26883
- // The widget's grading function
26884
- static validate(state) {
26885
- return csProgramValidator(state);
26886
- }
26887
26851
  componentDidMount() {
26888
26852
  $(window).on("message", this.handleMessageEvent);
26889
26853
  }
@@ -26896,9 +26860,6 @@ class CSProgram extends React.Component {
26896
26860
  message: this.props.message
26897
26861
  };
26898
26862
  }
26899
- simpleValidate() {
26900
- return csProgramValidator(this.getUserInput());
26901
- }
26902
26863
  render() {
26903
26864
  if (!this.props.programID) {
26904
26865
  return /*#__PURE__*/React.createElement("div", null);
@@ -26980,21 +26941,16 @@ var CSProgram$1 = {
26980
26941
  displayName: "CS Program",
26981
26942
  supportedAlignments: ["block", "full-width"],
26982
26943
  widget: CSProgram,
26983
- hidden: true
26944
+ hidden: true,
26945
+ validator: csProgramValidator
26984
26946
  };
26985
26947
 
26986
26948
  class Definition extends React.Component {
26987
- // TODO (LEMS-2396): remove validation logic from widgets that don't validate
26988
- static validate() {
26989
- return noopValidator();
26990
- }
26991
- getUserInput() {
26992
- return {};
26993
- }
26994
-
26995
- // TODO (LEMS-2396): remove validation logic from widgets that don't validate
26996
- simpleValidate() {
26997
- return noopValidator();
26949
+ constructor(...args) {
26950
+ super(...args);
26951
+ // this just helps with TS weak typing when a Widget
26952
+ // doesn't implement any Widget methods
26953
+ this.isWidget = true;
26998
26954
  }
26999
26955
  render() {
27000
26956
  return /*#__PURE__*/React.createElement(DefinitionConsumer, null, ({
@@ -27050,22 +27006,19 @@ var Definition$1 = {
27050
27006
  accessible: true,
27051
27007
  defaultAlignment: "inline",
27052
27008
  widget: Definition,
27053
- transform: x => x
27009
+ transform: x => x,
27010
+ // TODO: things that aren't interactive shouldn't need validators
27011
+ validator: () => noopValidator()
27054
27012
  };
27055
27013
 
27056
27014
  // The props are type `any` on purpose so that this can receive props
27057
27015
  // from any deprecated widget
27058
27016
  class DeprecatedStandin extends React__default.Component {
27059
- static validate() {
27060
- // Since this mean to replace an existing widget the learner
27061
- // WILL earn points for this widget
27062
- return noopValidator(1);
27063
- }
27064
- getUserInput() {
27065
- return {};
27066
- }
27067
- simpleValidate() {
27068
- return noopValidator(1);
27017
+ constructor(...args) {
27018
+ super(...args);
27019
+ // this just helps with TS weak typing when a Widget
27020
+ // doesn't implement any Widget methods
27021
+ this.isWidget = true;
27069
27022
  }
27070
27023
  render() {
27071
27024
  return /*#__PURE__*/React__default.createElement("div", {
@@ -27085,7 +27038,9 @@ var DeprecatedStandin$1 = {
27085
27038
  name: "deprecated-standin",
27086
27039
  displayName: "Deprecated Standin",
27087
27040
  widget: DeprecatedStandin,
27088
- hidden: true
27041
+ hidden: true,
27042
+ // TODO: things that aren't interactive shouldn't need validators
27043
+ validator: () => noopValidator(1)
27089
27044
  };
27090
27045
 
27091
27046
  function dropdownValidator(userInput, rubric) {
@@ -27126,17 +27081,11 @@ class Dropdown extends React.Component {
27126
27081
  });
27127
27082
  };
27128
27083
  }
27129
- static validate(userInput, rubric) {
27130
- return dropdownValidator(userInput, rubric);
27131
- }
27132
27084
  getUserInput() {
27133
27085
  return {
27134
27086
  value: this.props.selected
27135
27087
  };
27136
27088
  }
27137
- simpleValidate(rubric) {
27138
- return dropdownValidator(this.getUserInput(), rubric);
27139
- }
27140
27089
  render() {
27141
27090
  const children = [/*#__PURE__*/React.createElement(OptionItem, {
27142
27091
  key: "placeholder",
@@ -27183,7 +27132,8 @@ var Dropdown$1 = {
27183
27132
  defaultAlignment: "inline-block",
27184
27133
  accessible: true,
27185
27134
  widget: Dropdown,
27186
- transform: optionsTransform
27135
+ transform: optionsTransform,
27136
+ validator: dropdownValidator
27187
27137
  };
27188
27138
 
27189
27139
  // transform = _.identity
@@ -27197,6 +27147,9 @@ function mediaQueryIsMatched(mediaQuery) {
27197
27147
  class Explanation extends React.Component {
27198
27148
  constructor(..._args) {
27199
27149
  super(..._args);
27150
+ // this just helps with TS weak typing when a Widget
27151
+ // doesn't implement any Widget methods
27152
+ this.isWidget = true;
27200
27153
  this.state = {
27201
27154
  expanded: false
27202
27155
  };
@@ -27211,24 +27164,12 @@ class Explanation extends React.Component {
27211
27164
  this.props.trackInteraction();
27212
27165
  };
27213
27166
  }
27214
- // TODO (LEMS-2396): remove validation logic from widgets that don't validate
27215
- static validate() {
27216
- return noopValidator();
27217
- }
27218
27167
  componentDidMount() {
27219
27168
  this._mounted = true;
27220
27169
  }
27221
27170
  componentWillUnmount() {
27222
27171
  this._mounted = false;
27223
27172
  }
27224
- getUserInput() {
27225
- return {};
27226
- }
27227
-
27228
- // TODO (LEMS-2396): remove validation logic from widgets that don't validate
27229
- simpleValidate() {
27230
- return noopValidator();
27231
- }
27232
27173
  render() {
27233
27174
  const promptText = this.state.expanded ? this.props.hidePrompt : this.props.showPrompt;
27234
27175
  const caretIcon = this.state.expanded ? caretUp : caretDown;
@@ -27342,7 +27283,9 @@ var Explanation$1 = {
27342
27283
  defaultAlignment: "inline",
27343
27284
  widget: Explanation,
27344
27285
  transform: _.identity,
27345
- isLintable: true
27286
+ isLintable: true,
27287
+ // TODO: things that aren't interactive shouldn't need validators
27288
+ validator: () => noopValidator()
27346
27289
  };
27347
27290
 
27348
27291
  class GradedGroupAnswerBar extends React.Component {
@@ -28942,9 +28885,6 @@ class Grapher extends React.Component {
28942
28885
  }
28943
28886
  };
28944
28887
  }
28945
- static validate(state, rubric) {
28946
- return grapherValidator(state, rubric);
28947
- }
28948
28888
  static getUserInputFromProps(props) {
28949
28889
  return props.plot;
28950
28890
  }
@@ -28963,9 +28903,6 @@ class Grapher extends React.Component {
28963
28903
  tickStep[1] = tickStep[1] / gridStep[1];
28964
28904
  return tickStep;
28965
28905
  }
28966
- simpleValidate(rubric) {
28967
- return grapherValidator(this.getUserInput(), rubric);
28968
- }
28969
28906
  getUserInput() {
28970
28907
  return Grapher.getUserInputFromProps(this.props);
28971
28908
  }
@@ -29053,7 +28990,8 @@ var Grapher$1 = {
29053
28990
  hidden: true,
29054
28991
  widget: Grapher,
29055
28992
  transform: propTransform$2,
29056
- staticTransform: staticTransform$4
28993
+ staticTransform: staticTransform$4,
28994
+ validator: grapherValidator
29057
28995
  };
29058
28996
 
29059
28997
  // exports has no 'transform'
@@ -29099,17 +29037,18 @@ class Group extends React.Component {
29099
29037
  // the group with the correct number.
29100
29038
  this.forceUpdate();
29101
29039
  }
29102
- getUserInput() {
29040
+ getUserInputMap() {
29103
29041
  var _this$rendererRef7;
29104
- return (_this$rendererRef7 = this.rendererRef) == null ? void 0 : _this$rendererRef7.getUserInput();
29105
- }
29106
- simpleValidate() {
29107
- var _this$rendererRef$sco, _this$rendererRef8;
29108
- return (_this$rendererRef$sco = (_this$rendererRef8 = this.rendererRef) == null ? void 0 : _this$rendererRef8.score()) != null ? _this$rendererRef$sco : {
29109
- type: "invalid"
29110
- };
29042
+ return (_this$rendererRef7 = this.rendererRef) == null ? void 0 : _this$rendererRef7.getUserInputMap();
29111
29043
  }
29112
29044
 
29045
+ /**
29046
+ * @deprecated getUserInputMap should be used for Groups
29047
+ */
29048
+ getUserInput() {
29049
+ var _this$rendererRef8;
29050
+ return (_this$rendererRef8 = this.rendererRef) == null ? void 0 : _this$rendererRef8.getUserInput();
29051
+ }
29113
29052
  // Mobile API:
29114
29053
  getInputPaths() {
29115
29054
  var _this$rendererRef$get, _this$rendererRef9;
@@ -29270,9 +29209,6 @@ class Iframe extends React.Component {
29270
29209
  return change.apply(this, args);
29271
29210
  };
29272
29211
  }
29273
- static validate(state) {
29274
- return iframeValidator(state);
29275
- }
29276
29212
  componentDidMount() {
29277
29213
  $(window).on("message", this.handleMessageEvent);
29278
29214
  }
@@ -29285,9 +29221,6 @@ class Iframe extends React.Component {
29285
29221
  message: this.props.message
29286
29222
  };
29287
29223
  }
29288
- simpleValidate() {
29289
- return iframeValidator(this.getUserInput());
29290
- }
29291
29224
  render() {
29292
29225
  const style = {
29293
29226
  width: String(this.props.width),
@@ -29361,7 +29294,8 @@ var Iframe$1 = {
29361
29294
  displayName: "Iframe (deprecated)",
29362
29295
  widget: Iframe,
29363
29296
  // Let's not expose it to all content creators yet
29364
- hidden: true
29297
+ hidden: true,
29298
+ validator: iframeValidator
29365
29299
  };
29366
29300
 
29367
29301
  const defaultBoxSize = 400;
@@ -29379,22 +29313,13 @@ const DEFAULT_ALIGNMENT = "block";
29379
29313
  class ImageWidget extends React.Component {
29380
29314
  constructor(..._args) {
29381
29315
  super(..._args);
29316
+ // this just helps with TS weak typing when a Widget
29317
+ // doesn't implement any Widget methods
29318
+ this.isWidget = true;
29382
29319
  this.change = (...args) => {
29383
29320
  return change.apply(this, args);
29384
29321
  };
29385
29322
  }
29386
- // TODO (LEMS-2396): remove validation logic from widgets that don't validate
29387
- static validate() {
29388
- return noopValidator();
29389
- }
29390
- getUserInput() {
29391
- return null;
29392
- }
29393
-
29394
- // TODO (LEMS-2396): remove validation logic from widgets that don't validate
29395
- simpleValidate() {
29396
- return noopValidator();
29397
- }
29398
29323
  render() {
29399
29324
  let image;
29400
29325
  const alt = this.props.caption === this.props.alt ? "" : this.props.alt;
@@ -29549,7 +29474,9 @@ var Image$1 = {
29549
29474
  supportedAlignments: editorAlignments,
29550
29475
  displayName: "Image",
29551
29476
  widget: ImageWidget,
29552
- isLintable: true
29477
+ isLintable: true,
29478
+ // TODO: things that aren't interactive shouldn't need validators
29479
+ validator: () => noopValidator()
29553
29480
  };
29554
29481
 
29555
29482
  /* eslint-disable @khanacademy/ts-no-error-suppressions */
@@ -29618,6 +29545,9 @@ const KAScompile = (expr, options) => {
29618
29545
  class Interaction extends React.Component {
29619
29546
  constructor(..._args) {
29620
29547
  super(..._args);
29548
+ // this just helps with TS weak typing when a Widget
29549
+ // doesn't implement any Widget methods
29550
+ this.isWidget = true;
29621
29551
  this.state = {
29622
29552
  variables: _getInitialVariables(this.props.elements),
29623
29553
  functions: _getInitialFunctions(this.props.elements)
@@ -29701,10 +29631,6 @@ class Interaction extends React.Component {
29701
29631
  return change.apply(this, args);
29702
29632
  };
29703
29633
  }
29704
- // TODO (LEMS-2396): remove validation logic from widgets that don't validate
29705
- static validate() {
29706
- return noopValidator();
29707
- }
29708
29634
  UNSAFE_componentWillReceiveProps(nextProps) {
29709
29635
  // HACK(michaelpolyak): This widget is not wired to use the Perseus
29710
29636
  // `onChange` callback to save user changes. Perseus re-rendering of the
@@ -29723,18 +29649,6 @@ class Interaction extends React.Component {
29723
29649
  });
29724
29650
  }
29725
29651
  }
29726
- getUserInput() {
29727
- // TODO(eater): Perhaps we want to be able to record the state of the
29728
- // user's interaction. Unfortunately sending all the props will
29729
- // probably make the attempt payload too large. So for now, don't send
29730
- // anything.
29731
- return {};
29732
- }
29733
-
29734
- // TODO (LEMS-2396): remove validation logic from widgets that don't validate
29735
- simpleValidate() {
29736
- return noopValidator();
29737
- }
29738
29652
  render() {
29739
29653
  const range = this.props.graph.range;
29740
29654
  let labels = this.props.graph.labels;
@@ -30153,7 +30067,9 @@ var Interactive = {
30153
30067
  displayName: "Interaction",
30154
30068
  widget: Interaction,
30155
30069
  transform: _.identity,
30156
- hidden: true
30070
+ hidden: true,
30071
+ // TODO: things that aren't interactive shouldn't need validators
30072
+ validator: () => noopValidator()
30157
30073
  };
30158
30074
 
30159
30075
  /* eslint-disable @khanacademy/ts-no-error-suppressions */
@@ -37239,9 +37155,6 @@ class LegacyInteractiveGraph extends React.Component {
37239
37155
  getUserInput() {
37240
37156
  return InteractiveGraph.getUserInputFromProps(this.props);
37241
37157
  }
37242
- simpleValidate(rubric) {
37243
- return interactiveGraphValidator(this.getUserInput(), rubric);
37244
- }
37245
37158
  render() {
37246
37159
  const box = getInteractiveBoxFromSizeClass(this.props.containerSizeClass);
37247
37160
  const gridStep = this.props.gridStep || Util.getGridStep(this.props.range, this.props.step, box[0]);
@@ -37318,9 +37231,6 @@ class InteractiveGraph extends React.Component {
37318
37231
  }
37319
37232
  throw new PerseusError("Cannot getUserInput from a graph that has never rendered", Errors.NotAllowed);
37320
37233
  }
37321
- simpleValidate(rubric) {
37322
- return interactiveGraphValidator(this.getUserInput(), rubric);
37323
- }
37324
37234
  render() {
37325
37235
  var _this$props$apiOption;
37326
37236
  // Mafs shim
@@ -37653,9 +37563,6 @@ class InteractiveGraph extends React.Component {
37653
37563
  const angle = getClockwiseAngle(coords, allowReflexAngles);
37654
37564
  return angle.toFixed(0) + "\u00B0 angle" + " at (" + coords[1].join(", ") + ")";
37655
37565
  }
37656
- static validate(userInput, rubric) {
37657
- return interactiveGraphValidator(userInput, rubric);
37658
- }
37659
37566
  static getUserInputFromProps(props) {
37660
37567
  return props.graph;
37661
37568
  }
@@ -37691,7 +37598,7 @@ function shouldUseMafs(mafsFlags, graph) {
37691
37598
  // TODO(benchristel): add a feature flag for the "unlimited"
37692
37599
  // case once we've implemented polygon graphs with unlimited
37693
37600
  // sides
37694
- return false;
37601
+ return Boolean(mafsFlags["unlimited-polygon"]);
37695
37602
  }
37696
37603
  return Boolean(mafsFlags["polygon"]);
37697
37604
  default:
@@ -37704,7 +37611,8 @@ var InteractiveGraph$1 = {
37704
37611
  name: "interactive-graph",
37705
37612
  displayName: "Interactive graph (Assessments only)",
37706
37613
  widget: InteractiveGraph,
37707
- staticTransform: staticTransform$3
37614
+ staticTransform: staticTransform$3,
37615
+ validator: interactiveGraphValidator
37708
37616
  };
37709
37617
 
37710
37618
  const black = "#000000";
@@ -39069,10 +38977,6 @@ const styles$b = StyleSheet.create({
39069
38977
  // TODO: should this be using WidgetProps / PerseusLabelImageWidgetOptions?
39070
38978
 
39071
38979
  class LabelImage extends React.Component {
39072
- static validate(state, rubric) {
39073
- return labelImageValidator(state);
39074
- }
39075
-
39076
38980
  /**
39077
38981
  * Test whether point is contained within triangle.
39078
38982
  *
@@ -39267,9 +39171,6 @@ class LabelImage extends React.Component {
39267
39171
  componentWillUnmount() {
39268
39172
  this._mounted = false;
39269
39173
  }
39270
- simpleValidate(rubric) {
39271
- return labelImageValidator(this.getUserInput());
39272
- }
39273
39174
  getUserInput() {
39274
39175
  const {
39275
39176
  markers
@@ -39626,19 +39527,13 @@ const LabelImageWithDependencies = /*#__PURE__*/React.forwardRef((props, ref) =>
39626
39527
  analytics: deps.analytics
39627
39528
  }, props));
39628
39529
  });
39629
-
39630
- // HACK: Propogate "static" methods onto our wrapper component.
39631
- // In the future we should adjust client apps to not depend on these static
39632
- // methods and instead adjust Peresus to provide these facilities through
39633
- // instance methods on our Renderers.
39634
- // @ts-expect-error - TS2339 - Property 'validate' does not exist on type
39635
- LabelImageWithDependencies.validate = labelImageValidator;
39636
39530
  var LabelImage$1 = {
39637
39531
  name: "label-image",
39638
39532
  displayName: "Label Image",
39639
39533
  widget: LabelImageWithDependencies,
39640
39534
  accessible: true,
39641
- isLintable: true
39535
+ isLintable: true,
39536
+ validator: labelImageValidator
39642
39537
  };
39643
39538
 
39644
39539
  var ItemState = /*#__PURE__*/function (ItemState) {
@@ -40423,12 +40318,6 @@ class Matcher extends React.Component {
40423
40318
  this.refs.right.moveOptionToIndex(option, index);
40424
40319
  };
40425
40320
  }
40426
- static validate(state, rubric) {
40427
- return matcherValidator(state, rubric);
40428
- }
40429
- simpleValidate(rubric) {
40430
- return matcherValidator(this.getUserInput(), rubric);
40431
- }
40432
40321
  render() {
40433
40322
  // To minimize layout shift, we display a spinner until our math
40434
40323
  // renderer is ready to render the math inside the matcher. To
@@ -40566,7 +40455,8 @@ var Matcher$1 = {
40566
40455
  name: "matcher",
40567
40456
  displayName: "Matcher (two column)",
40568
40457
  widget: Matcher,
40569
- isLintable: true
40458
+ isLintable: true,
40459
+ validator: matcherValidator
40570
40460
  };
40571
40461
 
40572
40462
  function matrixValidator(state, rubric, strings) {
@@ -40828,9 +40718,6 @@ class Matrix extends React.Component {
40828
40718
  this.props.trackInteraction();
40829
40719
  };
40830
40720
  }
40831
- static validate(state, rubric, strings) {
40832
- return matrixValidator(state, rubric, strings);
40833
- }
40834
40721
  componentDidMount() {
40835
40722
  // Used in the `onBlur` and `onFocus` handlers
40836
40723
  this.cursorPosition = [0, 0];
@@ -40845,9 +40732,6 @@ class Matrix extends React.Component {
40845
40732
  answers: this.props.answers
40846
40733
  };
40847
40734
  }
40848
- simpleValidate(rubric) {
40849
- return matrixValidator(this.getUserInput(), rubric, this.context.strings);
40850
- }
40851
40735
  render() {
40852
40736
  // Set the input sizes through JS so we can control the size of the
40853
40737
  // brackets. (If we set them in CSS we won't know values until the
@@ -41040,7 +40924,8 @@ var Matrix$1 = {
41040
40924
  widget: Matrix,
41041
40925
  transform: propTransform$1,
41042
40926
  staticTransform: staticTransform$2,
41043
- isLintable: true
40927
+ isLintable: true,
40928
+ validator: matrixValidator
41044
40929
  };
41045
40930
 
41046
40931
  const defaultImage = {
@@ -41051,6 +40936,9 @@ const defaultImage = {
41051
40936
  class Measurer extends React.Component {
41052
40937
  constructor(...args) {
41053
40938
  super(...args);
40939
+ // this just helps with TS weak typing when a Widget
40940
+ // doesn't implement any Widget methods
40941
+ this.isWidget = true;
41054
40942
  this.state = {};
41055
40943
  this.ruler = void 0;
41056
40944
  this.protractor = void 0;
@@ -41104,12 +40992,6 @@ class Measurer extends React.Component {
41104
40992
  });
41105
40993
  }
41106
40994
  }
41107
- getUserInput() {
41108
- return {};
41109
- }
41110
- simpleValidate() {
41111
- return noopValidator(1);
41112
- }
41113
40995
  render() {
41114
40996
  const image = _.extend({}, defaultImage, this.props.image);
41115
40997
 
@@ -41135,7 +41017,6 @@ class Measurer extends React.Component {
41135
41017
  }));
41136
41018
  }
41137
41019
  }
41138
- Measurer.validate = noopValidator;
41139
41020
  Measurer.defaultProps = {
41140
41021
  box: [480, 480],
41141
41022
  image: {},
@@ -41169,7 +41050,9 @@ var Measurer$1 = {
41169
41050
  major: 1,
41170
41051
  minor: 0
41171
41052
  },
41172
- propUpgrades: propUpgrades
41053
+ propUpgrades: propUpgrades,
41054
+ // TODO: things that aren't interactive shouldn't need validators
41055
+ validator: () => noopValidator(1)
41173
41056
  };
41174
41057
 
41175
41058
  /**
@@ -42036,17 +41919,11 @@ class Molecule extends React.Component {
42036
41919
  }
42037
41920
  Molecule.contextType = PerseusI18nContext;
42038
41921
  class MoleculeWidget extends React.Component {
42039
- // TODO (LEMS-2396): remove validation logic from widgets that don't validate
42040
- simpleValidate() {
42041
- return noopValidator();
42042
- }
42043
- getUserInput() {
42044
- return [];
42045
- }
42046
-
42047
- // TODO (LEMS-2396): remove validation logic from widgets that don't validate
42048
- validate() {
42049
- return noopValidator();
41922
+ constructor(...args) {
41923
+ super(...args);
41924
+ // this just helps with TS weak typing when a Widget
41925
+ // doesn't implement any Widget methods
41926
+ this.isWidget = true;
42050
41927
  }
42051
41928
  render() {
42052
41929
  return /*#__PURE__*/React.createElement(Molecule, {
@@ -42063,7 +41940,8 @@ var Molecule$1 = {
42063
41940
  name: "molecule-renderer",
42064
41941
  displayName: "Molecule renderer",
42065
41942
  hidden: true,
42066
- widget: MoleculeWidget
41943
+ widget: MoleculeWidget,
41944
+ validator: () => noopValidator()
42067
41945
  };
42068
41946
 
42069
41947
  function numberLineValidator(state, rubric) {
@@ -42208,18 +42086,16 @@ const TickMarks = Graphie.createSimpleClass((graphie, props) => {
42208
42086
  results.push(graphie.line([x, -0.2], [x, 0.2]));
42209
42087
  const labelTicks = props.labelTicks;
42210
42088
  if (labelTicks || props.labelStyle === "decimal ticks") {
42211
- results.push(_label(graphie, props.labelStyle, x, x, base));
42089
+ if (x === leftLabel || x === rightLabel) {
42090
+ results.push(graphie.style({
42091
+ color: KhanColors.BLUE
42092
+ }, () => _label(graphie, props.labelStyle, x, x, base)));
42093
+ } else {
42094
+ results.push(_label(graphie, props.labelStyle, x, x, base));
42095
+ }
42212
42096
  }
42213
42097
  }
42214
42098
 
42215
- // Render the text labels
42216
- results.push(graphie.style(props.isMobile ? {
42217
- color: KhanColors.BLUE
42218
- } : {}, () => _label(graphie, props.labelStyle, leftLabel, leftLabel, base)));
42219
- results.push(graphie.style(props.isMobile ? {
42220
- color: KhanColors.BLUE
42221
- } : {}, () => _label(graphie, props.labelStyle, rightLabel, rightLabel, base)));
42222
-
42223
42099
  // Render the labels' lines
42224
42100
  graphie.style({
42225
42101
  stroke: KhanColors.BLUE,
@@ -42494,9 +42370,6 @@ class NumberLine extends React.Component {
42494
42370
  });
42495
42371
  };
42496
42372
  }
42497
- static validate(state, rubric) {
42498
- return numberLineValidator(state, rubric);
42499
- }
42500
42373
  focus() {
42501
42374
  if (this.props.isTickCtrl) {
42502
42375
  // eslint-disable-next-line react/no-string-refs
@@ -42526,9 +42399,6 @@ class NumberLine extends React.Component {
42526
42399
  divisionRange: this.props.divisionRange
42527
42400
  };
42528
42401
  }
42529
- simpleValidate(rubric) {
42530
- return numberLineValidator(this.getUserInput(), rubric);
42531
- }
42532
42402
  render() {
42533
42403
  const {
42534
42404
  strings
@@ -42647,7 +42517,8 @@ var NumberLine$1 = {
42647
42517
  displayName: "Number line",
42648
42518
  widget: NumberLine,
42649
42519
  transform: numberLineTransform,
42650
- staticTransform: staticTransform$1
42520
+ staticTransform: staticTransform$1,
42521
+ validator: numberLineValidator
42651
42522
  };
42652
42523
 
42653
42524
  function ordererValidator(userInput, rubric) {
@@ -43080,9 +42951,6 @@ class Orderer extends React.Component {
43080
42951
  });
43081
42952
  };
43082
42953
  }
43083
- static validate(userInput, rubric) {
43084
- return ordererValidator(userInput, rubric);
43085
- }
43086
42954
  UNSAFE_componentWillReceiveProps(nextProps) {
43087
42955
  if (!_.isEqual(this.props.current, nextProps.current)) {
43088
42956
  this.setState({
@@ -43097,9 +42965,6 @@ class Orderer extends React.Component {
43097
42965
  })
43098
42966
  };
43099
42967
  }
43100
- simpleValidate(rubric) {
43101
- return ordererValidator(this.getUserInput(), rubric);
43102
- }
43103
42968
  render() {
43104
42969
  // This is the card we are currently dragging
43105
42970
  const dragging = this.state.dragging && /*#__PURE__*/React.createElement(Card
@@ -43217,31 +43082,22 @@ var Orderer$1 = {
43217
43082
  displayName: "Orderer",
43218
43083
  hidden: true,
43219
43084
  widget: Orderer,
43220
- isLintable: true
43085
+ isLintable: true,
43086
+ validator: ordererValidator
43221
43087
  };
43222
43088
 
43223
43089
  /* eslint-disable @khanacademy/ts-no-error-suppressions */
43224
43090
  class PassageRefTarget extends React.Component {
43225
43091
  constructor(..._args) {
43226
43092
  super(..._args);
43093
+ // this just helps with TS weak typing when a Widget
43094
+ // doesn't implement any Widget methods
43095
+ this.isWidget = true;
43227
43096
  // TODO passage-ref-target isn't interactive; remove
43228
43097
  this.change = (...args) => {
43229
43098
  return change.apply(this, args);
43230
43099
  };
43231
43100
  }
43232
- // TODO (LEMS-2396): remove validation logic from widgets that don't validate
43233
- static validate() {
43234
- return noopValidator();
43235
- }
43236
-
43237
- // TODO passage-ref-target isn't interactive; remove
43238
- getUserInput() {
43239
- return null;
43240
- }
43241
- // TODO (LEMS-2396): remove validation logic from widgets that don't validate
43242
- simpleValidate() {
43243
- return noopValidator();
43244
- }
43245
43101
  render() {
43246
43102
  return /*#__PURE__*/React.createElement(Renderer, {
43247
43103
  content: this.props.content
@@ -43275,7 +43131,9 @@ var PassageRefTarget$1 = {
43275
43131
  major: 0,
43276
43132
  minor: 0
43277
43133
  },
43278
- isLintable: true
43134
+ isLintable: true,
43135
+ // TODO: things that aren't interactive shouldn't need validators
43136
+ validator: () => noopValidator()
43279
43137
  };
43280
43138
 
43281
43139
  /**
@@ -43288,6 +43146,9 @@ class PhetSimulation extends React.Component {
43288
43146
  super(props);
43289
43147
  this.iframeRef = /*#__PURE__*/React.createRef();
43290
43148
  this.locale = void 0;
43149
+ // this just helps with TS weak typing when a Widget
43150
+ // doesn't implement any Widget methods
43151
+ this.isWidget = true;
43291
43152
  this.state = {
43292
43153
  url: null,
43293
43154
  banner: null
@@ -43330,11 +43191,6 @@ class PhetSimulation extends React.Component {
43330
43191
  await this.updateSimState(this.props.url);
43331
43192
  }
43332
43193
  }
43333
-
43334
- // TODO (LEMS-2396): remove validation logic from widgets that don't validate
43335
- getUserInput() {
43336
- return null;
43337
- }
43338
43194
  async updateSimState(urlString) {
43339
43195
  const url = makeSafeUrl(urlString, this.locale);
43340
43196
  if (url === null) {
@@ -44233,9 +44089,6 @@ class Plotter extends React.Component {
44233
44089
  });
44234
44090
  };
44235
44091
  }
44236
- static validate(userInput, rubric) {
44237
- return plotterValidator(userInput, rubric);
44238
- }
44239
44092
  componentDidMount() {
44240
44093
  this._isMounted = true;
44241
44094
  this.setupGraphie(this.state);
@@ -44262,9 +44115,6 @@ class Plotter extends React.Component {
44262
44115
  getUserInput() {
44263
44116
  return this.state.values;
44264
44117
  }
44265
- simpleValidate(rubric) {
44266
- return plotterValidator(this.getUserInput(), rubric);
44267
- }
44268
44118
  render() {
44269
44119
  // TODO(kevinb) actually compute the size of the graphie correctly and
44270
44120
  // make it that size so we don't have to add extra padding. The value
@@ -44304,7 +44154,8 @@ var Plotter$1 = {
44304
44154
  displayName: "Plotter",
44305
44155
  hidden: true,
44306
44156
  widget: Plotter,
44307
- staticTransform: staticTransform
44157
+ staticTransform: staticTransform,
44158
+ validator: plotterValidator
44308
44159
  };
44309
44160
 
44310
44161
  /**
@@ -44420,9 +44271,6 @@ class Sorter extends React.Component {
44420
44271
  this.refs.sortable.moveOptionToIndex(option, index);
44421
44272
  };
44422
44273
  }
44423
- static validate(userInput, rubric) {
44424
- return sorterValidator(userInput, rubric);
44425
- }
44426
44274
  componentDidMount() {
44427
44275
  this._isMounted = true;
44428
44276
  }
@@ -44438,9 +44286,6 @@ class Sorter extends React.Component {
44438
44286
  changed: this.state.changed
44439
44287
  };
44440
44288
  }
44441
- simpleValidate(rubric) {
44442
- return sorterValidator(this.getUserInput(), rubric);
44443
- }
44444
44289
  render() {
44445
44290
  const options = shuffle(this.props.correct, this.props.problemNum, /* ensurePermuted */true);
44446
44291
  const {
@@ -44474,7 +44319,8 @@ var Sorter$1 = {
44474
44319
  name: "sorter",
44475
44320
  displayName: "Sorter",
44476
44321
  widget: Sorter,
44477
- isLintable: true
44322
+ isLintable: true,
44323
+ validator: sorterValidator
44478
44324
  };
44479
44325
 
44480
44326
  function tableValidator(state, rubric, strings) {
@@ -44653,15 +44499,9 @@ class Table extends React.Component {
44653
44499
  }, cb);
44654
44500
  };
44655
44501
  }
44656
- static validate(state, rubric, strings) {
44657
- return tableValidator(state, rubric, strings);
44658
- }
44659
44502
  getUserInput() {
44660
44503
  return _.map(this.props.answers, _.clone);
44661
44504
  }
44662
- simpleValidate(rubric) {
44663
- return tableValidator(this.getUserInput(), rubric, this.context.strings);
44664
- }
44665
44505
  getDOMNodeForPath(path) {
44666
44506
  const inputID = getRefForPath(path);
44667
44507
  // eslint-disable-next-line react/no-string-refs
@@ -44762,7 +44602,8 @@ var Table$1 = {
44762
44602
  widget: Table,
44763
44603
  transform: propTransform,
44764
44604
  hidden: true,
44765
- isLintable: true
44605
+ isLintable: true,
44606
+ validator: tableValidator
44766
44607
  };
44767
44608
 
44768
44609
  /**
@@ -44850,26 +44691,14 @@ const IS_VIMEO = /(vimeo\.com)/;
44850
44691
  class Video extends React.Component {
44851
44692
  constructor(..._args) {
44852
44693
  super(..._args);
44853
- this.getUserInput = () => {
44854
- return null;
44855
- };
44694
+ // this just helps with TS weak typing when a Widget
44695
+ // doesn't implement any Widget methods
44696
+ this.isWidget = true;
44856
44697
  this.change = (...args) => {
44857
44698
  // @ts-expect-error - TS2345 - Argument of type 'readonly unknown[]' is not assignable to parameter of type 'any[]'.
44858
44699
  return change.apply(this, args);
44859
44700
  };
44860
44701
  }
44861
- /**
44862
- * This is the widget's grading function.
44863
- * Points for videos are tallied by the embedded video itself, in the case
44864
- * of Khan Academy videos.
44865
- */ // TODO (LEMS-2396): remove validation logic from widgets that don't validate
44866
- static validate() {
44867
- return noopValidator();
44868
- }
44869
- // TODO (LEMS-2396): remove validation logic from widgets that don't validate
44870
- simpleValidate() {
44871
- return noopValidator();
44872
- }
44873
44702
  render() {
44874
44703
  const {
44875
44704
  InitialRequestUrl
@@ -44929,7 +44758,9 @@ var Video$1 = {
44929
44758
  displayName: "Video",
44930
44759
  defaultAlignment: "block",
44931
44760
  supportedAlignments: ["block", "float-left", "float-right", "full-width"],
44932
- widget: Video
44761
+ widget: Video,
44762
+ // TODO: things that aren't interactive shouldn't need validators
44763
+ validator: () => noopValidator()
44933
44764
  };
44934
44765
 
44935
44766
  /* eslint-disable import/no-named-as-default */
@@ -44973,7 +44804,7 @@ const init = function init(options) {
44973
44804
 
44974
44805
  // This file is processed by a Rollup plugin (replace) to inject the production
44975
44806
  const libName = "@khanacademy/perseus";
44976
- const libVersion = "36.1.2";
44807
+ const libVersion = "37.0.1";
44977
44808
  addLibraryVersionToPerseusDebug(libName, libVersion);
44978
44809
 
44979
44810
  const apiVersion = {
@@ -45549,7 +45380,7 @@ class ServerItemRenderer extends React.Component {
45549
45380
  });
45550
45381
 
45551
45382
  // Call the interactionCallback, if it exists, with the current user input data
45552
- (_this$props$apiOption = this.props.apiOptions) == null || _this$props$apiOption.interactionCallback == null || _this$props$apiOption.interactionCallback(this.questionRenderer.getUserInputForWidgets());
45383
+ (_this$props$apiOption = this.props.apiOptions) == null || _this$props$apiOption.interactionCallback == null || _this$props$apiOption.interactionCallback(this.questionRenderer.getUserInputMap());
45553
45384
  };
45554
45385
  // This must be pre-bound otherwise SvgImage's shouldComponentUpdate
45555
45386
  // won't behave correctly and we'll get an infinite loop.
@@ -48221,9 +48052,7 @@ const getCorrectAnswerForWidgetId = (widgetId, itemData) => {
48221
48052
  const rubric = itemData.question.widgets[widgetId].options;
48222
48053
  const widgetMap = getWidgetsMapFromItemData(itemData);
48223
48054
  const widgetType = getWidgetTypeByWidgetId(widgetId, widgetMap);
48224
- const widget = getWidget(widgetType);
48225
-
48226
- // @ts-expect-error - TS2339 - Property 'getOneCorrectAnswerFromRubric' does not exist on type 'ComponentType<any>'.
48055
+ const widget = getWidgetExport(widgetType);
48227
48056
  return widget == null || widget.getOneCorrectAnswerFromRubric == null ? void 0 : widget.getOneCorrectAnswerFromRubric(rubric);
48228
48057
  };
48229
48058
 
@@ -48313,12 +48142,12 @@ function parsePerseusItem(json) {
48313
48142
  }
48314
48143
  throw new Error("Something went wrong.");
48315
48144
  }
48316
- function buildRandomString() {
48145
+ function buildRandomString(capitalize = false) {
48317
48146
  let randomString = "";
48318
48147
  const randomLength = Math.floor(Math.random() * 8) + 3;
48319
48148
  for (let i = 0; i < randomLength; i++) {
48320
48149
  const randomLetter = String.fromCharCode(97 + Math.floor(Math.random() * 26));
48321
- randomString += randomLetter;
48150
+ randomString += capitalize && i === 0 ? randomLetter.toUpperCase() : randomLetter;
48322
48151
  }
48323
48152
  return randomString;
48324
48153
  }
@@ -48326,9 +48155,12 @@ function buildRandomPhrase() {
48326
48155
  const phrases = [];
48327
48156
  const randomLength = Math.floor(Math.random() * 10) + 5;
48328
48157
  for (let i = 0; i < randomLength; i++) {
48329
- phrases.push(buildRandomString());
48158
+ phrases.push(buildRandomString(i === 0));
48330
48159
  }
48331
- return phrases.join(" ");
48160
+ const modifierStart = ["**", "$"];
48161
+ const modifierEnd = ["**", "$"];
48162
+ const modifierIndex = Math.floor(Math.random() * modifierStart.length);
48163
+ return `${modifierStart[modifierIndex]}${phrases.join(" ")}${modifierEnd[modifierIndex]}`;
48332
48164
  }
48333
48165
  function buildTestData(testObject) {
48334
48166
  return `{"data":{"assessmentItem":{"__typename":"AssessmentItemOrError","error":null,"item":{"__typename":"AssessmentItem","id":"x890b3c70f3e8f4a6","itemData":"${testObject}","problemType":"Type 1","sha":"c7284a3ad65214b4e62bccce236d92f7f5d35941"}}}}`;