@khanacademy/perseus 36.1.2 → 37.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/dist/all-widgets.d.ts +1 -0
  2. package/dist/basic-widgets.d.ts +1 -0
  3. package/dist/components/math-input.d.ts +1 -1
  4. package/dist/es/index.js +447 -612
  5. package/dist/es/index.js.map +1 -1
  6. package/dist/extra-widgets.d.ts +1 -0
  7. package/dist/index.js +412 -588
  8. package/dist/index.js.map +1 -1
  9. package/dist/renderer-util.d.ts +8 -0
  10. package/dist/renderer.d.ts +19 -20
  11. package/dist/server-item-renderer.d.ts +3 -1
  12. package/dist/types.d.ts +11 -3
  13. package/dist/util.d.ts +4 -0
  14. package/dist/validation.types.d.ts +14 -6
  15. package/dist/widgets/categorizer/categorizer.d.ts +3 -5
  16. package/dist/widgets/cs-program/cs-program.d.ts +2 -3
  17. package/dist/widgets/definition/definition.d.ts +3 -4
  18. package/dist/widgets/deprecated-standin/deprecated-standin.d.ts +35 -0
  19. package/dist/widgets/deprecated-standin/index.d.ts +1 -37
  20. package/dist/widgets/dropdown/dropdown.d.ts +2 -3
  21. package/dist/widgets/explanation/explanation.d.ts +4 -5
  22. package/dist/widgets/expression/expression.d.ts +4 -8
  23. package/dist/widgets/graded-group/graded-group.d.ts +1 -0
  24. package/dist/widgets/graded-group-set/graded-group-set.d.ts +1 -0
  25. package/dist/widgets/grapher/grapher.d.ts +2 -3
  26. package/dist/widgets/group/group.d.ts +8 -5
  27. package/dist/widgets/iframe/iframe.d.ts +2 -3
  28. package/dist/widgets/image/image.d.ts +3 -4
  29. package/dist/widgets/input-number/input-number.d.ts +2 -5
  30. package/dist/widgets/interaction/interaction.d.ts +3 -4
  31. package/dist/widgets/interactive-graph.d.ts +2 -4
  32. package/dist/widgets/label-image/label-image.d.ts +3 -4
  33. package/dist/widgets/matcher/matcher.d.ts +2 -3
  34. package/dist/widgets/matrix/matrix.d.ts +3 -5
  35. package/dist/widgets/measurer/measurer.d.ts +2 -4
  36. package/dist/widgets/molecule/molecule.d.ts +2 -4
  37. package/dist/widgets/number-line/number-line.d.ts +3 -4
  38. package/dist/widgets/numeric-input/numeric-input.d.ts +2 -3
  39. package/dist/widgets/orderer/orderer.d.ts +2 -3
  40. package/dist/widgets/passage/passage.d.ts +3 -9
  41. package/dist/widgets/passage-ref/passage-ref.d.ts +3 -4
  42. package/dist/widgets/passage-ref-target/passage-ref-target.d.ts +2 -4
  43. package/dist/widgets/phet-simulation/phet-simulation.d.ts +2 -2
  44. package/dist/widgets/plotter/plotter.d.ts +2 -3
  45. package/dist/widgets/python-program/python-program.d.ts +1 -0
  46. package/dist/widgets/radio/radio-component.d.ts +2 -5
  47. package/dist/widgets/radio/radio.d.ts +1 -0
  48. package/dist/widgets/sorter/sorter.d.ts +2 -3
  49. package/dist/widgets/table/table.d.ts +3 -5
  50. package/dist/widgets/video/video.d.ts +2 -8
  51. package/dist/widgets.d.ts +2 -1
  52. package/package.json +2 -2
  53. 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,10 +2373,6 @@ 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
  }
@@ -2465,7 +2422,7 @@ class Expression extends React.Component {
2465
2422
  }, cb);
2466
2423
  }
2467
2424
  render() {
2468
- var _this$props$keypadCon, _this$props$analytics3;
2425
+ var _this$props$keypadCon, _this$props$analytics2;
2469
2426
  if (this.props.apiOptions.customKeypad) {
2470
2427
  return /*#__PURE__*/React.createElement(View, {
2471
2428
  className: css(styles$D.mobileLabelInputWrapper)
@@ -2538,7 +2495,7 @@ class Expression extends React.Component {
2538
2495
  hasError: this.state.showErrorStyle,
2539
2496
  ariaLabel: this.props.ariaLabel || this.context.strings.mathInputBox,
2540
2497
  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 : {
2498
+ analytics: (_this$props$analytics2 = this.props.analytics) != null ? _this$props$analytics2 : {
2542
2499
  onAnalyticsEvent: async function () {}
2543
2500
  }
2544
2501
  }))));
@@ -2691,7 +2648,8 @@ var Expression$1 = {
2691
2648
  },
2692
2649
  propUpgrades: propUpgrades$2,
2693
2650
  // For use by the editor
2694
- isLintable: true
2651
+ isLintable: true,
2652
+ validator: expressionValidator
2695
2653
  };
2696
2654
 
2697
2655
  /**
@@ -2920,6 +2878,9 @@ function combineScores(scoreA, scoreB) {
2920
2878
  }
2921
2879
  });
2922
2880
  }
2881
+ function flattenScores(widgetScoreMap) {
2882
+ return Object.values(widgetScoreMap).reduce(combineScores, noScore);
2883
+ }
2923
2884
  function isCorrect(score) {
2924
2885
  return score.type === "points" && score.earned >= score.total;
2925
2886
  }
@@ -3452,6 +3413,7 @@ const Util = {
3452
3413
  shuffle: shuffle$3,
3453
3414
  split,
3454
3415
  combineScores,
3416
+ flattenScores,
3455
3417
  keScoreFromPerseusScore,
3456
3418
  firstNumericalParse: firstNumericalParse$1,
3457
3419
  stringArrayOfSize: stringArrayOfSize$1,
@@ -9988,6 +9950,8 @@ const setLabelMargins = function setLabelMargins(span, size) {
9988
9950
  });
9989
9951
  } else {
9990
9952
  var _$container$css, _$container$width, _$span$css;
9953
+ const currentHeightMatchesProps = span.scrollHeight === height;
9954
+
9991
9955
  // We are using jQuery to collect information and calculate a scale
9992
9956
  // since we don't have a way to pass it to this function.
9993
9957
  // We need the width of the container in order to calculate the scale to apply to the label.
@@ -10009,6 +9973,17 @@ const setLabelMargins = function setLabelMargins(span, size) {
10009
9973
  // Inherited line-height values can really mess up placement.
10010
9974
  $container.css("line-height", "normal");
10011
9975
 
9976
+ // TODO: Verify that this change doesn't impact other graphies
9977
+ // Check the full list of consuming widgets.
9978
+
9979
+ // If the change in line-height affected the height of the element,
9980
+ // then the height used for calculations should be updated.
9981
+ // This can happen when the first label in the container calls this method,
9982
+ // and the line-height was different when the height measurement was originally referenced.
9983
+ if (currentHeightMatchesProps && span.scrollHeight !== height) {
9984
+ height = span.scrollHeight;
9985
+ }
9986
+
10012
9987
  // The expected width of the graphie is found in the "max-width" property on ".svg-image" containers,
10013
9988
  // and is found in the "width" property on ".graphie" containers.
10014
9989
  const widthValues = (_$container$css = $container.css(["max-width", "width"])) != null ? _$container$css : {
@@ -14645,7 +14620,7 @@ const mapObject$1 = function mapObject(obj, lambda) {
14645
14620
  * });
14646
14621
  * {a: 'aa', b: 'bb'}
14647
14622
  */
14648
- const mapObjectFromArray$1 = function mapObjectFromArray(arr, lambda) {
14623
+ const mapObjectFromArray = function mapObjectFromArray(arr, lambda) {
14649
14624
  const result = {};
14650
14625
  _.each(arr, function (elem) {
14651
14626
  result[elem] = lambda(elem);
@@ -14655,7 +14630,7 @@ const mapObjectFromArray$1 = function mapObjectFromArray(arr, lambda) {
14655
14630
  var objective_ = {
14656
14631
  pluck: pluck,
14657
14632
  mapObject: mapObject$1,
14658
- mapObjectFromArray: mapObjectFromArray$1
14633
+ mapObjectFromArray: mapObjectFromArray
14659
14634
  };
14660
14635
 
14661
14636
  /**
@@ -18818,169 +18793,6 @@ class QuestionParagraph extends React.Component {
18818
18793
  }
18819
18794
  }
18820
18795
 
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
18796
  const DEFAULT_ALIGNMENT$1 = "block";
18985
18797
  // NOTE(kevinb): "default" is not one in `validAlignments`.
18986
18798
  const DEFAULT_SUPPORTED_ALIGNMENTS = ["default"];
@@ -19081,6 +18893,10 @@ const getWidget = name => {
19081
18893
  }
19082
18894
  return widgets[name].widget;
19083
18895
  };
18896
+ const getWidgetValidator = name => {
18897
+ var _widgets$name$validat, _widgets$name3;
18898
+ return (_widgets$name$validat = (_widgets$name3 = widgets[name]) == null ? void 0 : _widgets$name3.validator) != null ? _widgets$name$validat : null;
18899
+ };
19084
18900
  const getEditor = name => {
19085
18901
  return _.has(editors, name) ? editors[name] : null;
19086
18902
  };
@@ -19383,6 +19199,7 @@ var widgets$1 = /*#__PURE__*/Object.freeze({
19383
19199
  replaceEditor: replaceEditor,
19384
19200
  replaceDeprecatedEditors: replaceDeprecatedEditors,
19385
19201
  getWidget: getWidget,
19202
+ getWidgetValidator: getWidgetValidator,
19386
19203
  getEditor: getEditor,
19387
19204
  getTransform: getTransform,
19388
19205
  getVersion: getVersion,
@@ -19402,6 +19219,222 @@ var widgets$1 = /*#__PURE__*/Object.freeze({
19402
19219
  isLintable: isLintable
19403
19220
  });
19404
19221
 
19222
+ function emptyWidgetsFunctional(widgets,
19223
+ // This is a port of old code, I'm not sure why
19224
+ // we need widgetIds vs the keys of the widgets object
19225
+ widgetIds, userInputMap, strings, locale) {
19226
+ return widgetIds.filter(id => {
19227
+ const widget = widgets[id];
19228
+ if (!widget || widget.static) {
19229
+ // Static widgets shouldn't count as empty
19230
+ return false;
19231
+ }
19232
+ let score = null;
19233
+ const userInput = userInputMap[id];
19234
+ const validator = getWidgetValidator(widget.type);
19235
+ if (widget.type === "group") {
19236
+ const scores = scoreWidgetsFunctional(widget.options.widgets, Object.keys(widget.options.widgets), userInputMap[id], strings, locale);
19237
+ score = Util.flattenScores(scores);
19238
+ } else if (validator) {
19239
+ score = validator(userInput, widget.options, strings, locale);
19240
+ }
19241
+ if (score) {
19242
+ return Util.scoreIsEmpty(score);
19243
+ }
19244
+ });
19245
+ }
19246
+ function scoreWidgetsFunctional(widgets,
19247
+ // This is a port of old code, I'm not sure why
19248
+ // we need widgetIds vs the keys of the widgets object
19249
+ widgetIds, userInputMap, strings, locale) {
19250
+ const gradedWidgetIds = widgetIds.filter(id => {
19251
+ const props = widgets[id];
19252
+ const widgetIsGraded = (props == null ? void 0 : props.graded) == null || props.graded;
19253
+ const widgetIsStatic = !!(props != null && props.static);
19254
+ // Ungraded widgets or widgets set to static shouldn't be graded.
19255
+ return widgetIsGraded && !widgetIsStatic;
19256
+ });
19257
+ const widgetScores = {};
19258
+ gradedWidgetIds.forEach(id => {
19259
+ const widget = widgets[id];
19260
+ if (!widget) {
19261
+ return;
19262
+ }
19263
+ const userInput = userInputMap[id];
19264
+ const validator = getWidgetValidator(widget.type);
19265
+ if (widget.type === "group") {
19266
+ const scores = scoreWidgetsFunctional(widget.options.widgets, Object.keys(widget.options.widgets), userInputMap[id], strings, locale);
19267
+ widgetScores[id] = Util.flattenScores(scores);
19268
+ } else if (validator) {
19269
+ widgetScores[id] = validator(userInput, widget.options, strings, locale);
19270
+ }
19271
+ });
19272
+ return widgetScores;
19273
+ }
19274
+
19275
+ /**
19276
+ * TranslationLinter asynchronously calls Khan Academy's poentry linter.
19277
+ *
19278
+ * The poentry linter checks for issues that prevent content from being
19279
+ * translated. This linter is KA-specific and so TranslationLinter does not do
19280
+ * anything in non-KA environments.
19281
+ */
19282
+
19283
+ // A LintCB is called once the linter has been run.
19284
+
19285
+ // msec to wait before we actually call the linter after receiving the
19286
+ // last call. The timer is reset if the lint is called during the wait.
19287
+ const DEBOUNCE_TIMEOUT = 1000;
19288
+ class TranslationLinter {
19289
+ constructor() {
19290
+ // To avoid linting when nothing has changed.
19291
+ this.previousContent = null;
19292
+ /**
19293
+ * Runs the poentry linter on the given perseus string asynchronously.
19294
+ *
19295
+ * It doesn't do anything unless we're in a KA environment.
19296
+ *
19297
+ * onLintErrorsGenerated is called even if there are no errors. It is not
19298
+ * called if perseusStr is unchanged from the last call.
19299
+ *
19300
+ * Linting is relatively expensive -- so we debounce linting each instance
19301
+ * for some amount of time.
19302
+ */
19303
+ this.runLinter = debounce$1((perseusStr, onLintErrorsGenerated) => {
19304
+ // TODO(FEI-5003): Do we still need to check for 'KA'?
19305
+ if (typeof KA === "undefined") {
19306
+ return;
19307
+ }
19308
+ if (perseusStr === this.previousContent) {
19309
+ return;
19310
+ }
19311
+ this.previousContent = perseusStr;
19312
+ if (perseusStr === "") {
19313
+ onLintErrorsGenerated([]);
19314
+ return;
19315
+ }
19316
+ fetch("/api/internal/translations/lint_poentry?preview=1&lang=en", {
19317
+ headers: {
19318
+ "Content-Type": "application/json"
19319
+ },
19320
+ body: JSON.stringify({
19321
+ // The poentry linter verifies that the translation from
19322
+ // the original text to the translated text is sane. We
19323
+ // want to ensure that the translation from English to
19324
+ // English is sane, so msgid === mstr.
19325
+ msgid: perseusStr,
19326
+ // msgid is the original text
19327
+ msgstr: perseusStr,
19328
+ // msgstr is the translated text
19329
+ format: "perseus_text",
19330
+ filename: ""
19331
+ }),
19332
+ method: "POST"
19333
+ }).then(response => {
19334
+ if (response.status >= 400) {
19335
+ return {
19336
+ status: "error",
19337
+ message: "Could not run i18n linter."
19338
+ };
19339
+ }
19340
+ return response.json();
19341
+ }, rejection => {
19342
+ return {
19343
+ status: "error",
19344
+ message: "Could not run i18n linter."
19345
+ };
19346
+ }).then(json => {
19347
+ if (json.status === "error") {
19348
+ onLintErrorsGenerated(["Some part of this text makes it untranslatable. " + "The specific message from the i18n linter was: " + json.message.replace(/\n/g, " ")]);
19349
+ } else {
19350
+ onLintErrorsGenerated([]);
19351
+ }
19352
+ });
19353
+ }, DEBOUNCE_TIMEOUT);
19354
+ }
19355
+ /**
19356
+ * Applies an array of errors generated by linters without position
19357
+ * information (TranslationLinter and the legacy getSaveWarnings()) to the
19358
+ * top of a Perseus tree.
19359
+ */
19360
+ applyLintErrors(parsedMarkdown, translationLintErrors) {
19361
+ // These lint errors do not have position data associated with
19362
+ // them, so we just plop them at the top.
19363
+ if (translationLintErrors.length) {
19364
+ const errorText = translationLintErrors.join("\n\n");
19365
+ parsedMarkdown.unshift({
19366
+ content: {
19367
+ type: "text",
19368
+ content: ""
19369
+ },
19370
+ insideTable: false,
19371
+ message: errorText,
19372
+ ruleName: "legacy-error",
19373
+ severity: Rule.Severity.ERROR,
19374
+ type: "lint"
19375
+ });
19376
+ }
19377
+ }
19378
+ }
19379
+
19380
+ /**
19381
+ * Preprocess TeX code to convert things that MathJax doesn't know how to handle
19382
+ * to things is does.
19383
+ */
19384
+ var preprocessTex = (texCode => texCode
19385
+ // Replace uses of \begin{align}...\end{align} which KaTeX doesn't
19386
+ // support (yet) with \begin{aligned}...\end{aligned} which renders
19387
+ // the same is supported by KaTeX. It does the same for align*.
19388
+ // TODO(kevinb) update content to use aligned instead of align.
19389
+ // TODO(LEMS-1608) Remove this replacement as MathJax supports the
19390
+ // "align" macro correctly (and, in fact, it is not synonymous with
19391
+ // "aligned").
19392
+ .replace(/\{align[*]?\}/g, "{aligned}")
19393
+ // Replace non-breaking spaces with regular spaces.
19394
+ .replace(/[\u00a0]/g, " "));
19395
+
19396
+ const {
19397
+ interactiveSizes
19398
+ } = constants$1;
19399
+
19400
+ // Note: these size cutoffs represent content-width cutoffs as specified in
19401
+ // http://zpl.io/1mVmvU (broken link - we no longer use Zeplin)
19402
+ // TODO(benkomalo): these values aren't used in JS outside of this file, but
19403
+ // are coupled to the values in styles/styles.less - DRY it up at some point
19404
+ const smMax = articleMaxWidthTableInPx;
19405
+ const mdMax = articleMaxWidthInPx;
19406
+ const containerSizeClass = {
19407
+ SMALL: "small",
19408
+ MEDIUM: "medium",
19409
+ LARGE: "large",
19410
+ XLARGE: "xlarge"
19411
+ };
19412
+ const getClassFromWidth = width => {
19413
+ if (!width) {
19414
+ return containerSizeClass.MEDIUM;
19415
+ }
19416
+ if (width <= smMax) {
19417
+ return containerSizeClass.SMALL;
19418
+ }
19419
+ if (width <= mdMax) {
19420
+ return containerSizeClass.MEDIUM;
19421
+ }
19422
+ return containerSizeClass.LARGE;
19423
+ };
19424
+ const getInteractiveBoxFromSizeClass = sizeClass => {
19425
+ if (sizeClass === containerSizeClass.SMALL) {
19426
+ return [interactiveSizes.defaultBoxSizeSmall, interactiveSizes.defaultBoxSizeSmall];
19427
+ }
19428
+ return [interactiveSizes.defaultBoxSize, interactiveSizes.defaultBoxSize];
19429
+ };
19430
+
19431
+ var sizingUtils = /*#__PURE__*/Object.freeze({
19432
+ __proto__: null,
19433
+ containerSizeClass: containerSizeClass,
19434
+ getClassFromWidth: getClassFromWidth,
19435
+ getInteractiveBoxFromSizeClass: getInteractiveBoxFromSizeClass
19436
+ });
19437
+
19405
19438
  class WidgetContainer extends React.Component {
19406
19439
  constructor(...args) {
19407
19440
  super(...args);
@@ -19547,8 +19580,7 @@ WidgetContainer.defaultProps = {
19547
19580
  };
19548
19581
 
19549
19582
  const {
19550
- mapObject,
19551
- mapObjectFromArray
19583
+ mapObject
19552
19584
  } = objective_;
19553
19585
  const rContainsNonWhitespace = /\S/;
19554
19586
  const rImageURL = /(web\+graphie|https):\/\/[^\s]*/;
@@ -19713,11 +19745,7 @@ class Renderer extends React.Component {
19713
19745
  }
19714
19746
  let interactionTracker = this._interactionTrackers[id];
19715
19747
  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));
19748
+ interactionTracker = this._interactionTrackers[id] = new InteractionTracker(apiOptions.trackInteraction, widgetInfo && widgetInfo.type, id, getTracking(widgetInfo && widgetInfo.type));
19721
19749
  }
19722
19750
  return _extends({}, widgetProps, {
19723
19751
  widgetId: id,
@@ -20495,23 +20523,6 @@ class Renderer extends React.Component {
20495
20523
  }, this);
20496
20524
  return state;
20497
20525
  };
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
20526
  this._setWidgetProps = (id, newProps, cb, silent) => {
20516
20527
  this.setState(prevState => {
20517
20528
  const widgetProps = _extends({}, prevState.widgetProps, {
@@ -20568,19 +20579,6 @@ class Renderer extends React.Component {
20568
20579
  // Widget handles parsing of the interWidgetPath.
20569
20580
  widget == null || widget.setInputValue == null || widget.setInputValue(interWidgetPath, newValue, focus);
20570
20581
  };
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
20582
  /**
20585
20583
  * Returns an array of all widget IDs in the order they occur in
20586
20584
  * the content.
@@ -20588,56 +20586,6 @@ class Renderer extends React.Component {
20588
20586
  this.getWidgetIds = () => {
20589
20587
  return this.widgetIds;
20590
20588
  };
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
20589
  this.guessAndScore = () => {
20642
20590
  const totalGuess = this.getUserInput();
20643
20591
  const totalScore = this.score();
@@ -20811,6 +20759,59 @@ class Renderer extends React.Component {
20811
20759
  }
20812
20760
  this._isMounted = false;
20813
20761
  }
20762
+ emptyWidgets() {
20763
+ return emptyWidgetsFunctional(this.state.widgetInfo, this.widgetIds, this.getUserInputMap(), this.props.strings, this.context.locale);
20764
+ }
20765
+ /**
20766
+ * Returns an array of the widget `.getUserInput()` results
20767
+ *
20768
+ * TODO: can we remove this?
20769
+ * @deprecated use getUserInputMap
20770
+ */
20771
+ getUserInput() {
20772
+ return this.widgetIds.map(id => {
20773
+ const widget = this.getWidgetInstance(id);
20774
+ if (widget && widget.getUserInput) {
20775
+ // TODO(Jeremy): Add the widget ID in here so we can more
20776
+ // easily correlate it to the widget state.
20777
+ return widget.getUserInput();
20778
+ }
20779
+ });
20780
+ }
20781
+
20782
+ /**
20783
+ * Returns an object of the widget `.getUserInput()` results
20784
+ */
20785
+ getUserInputMap() {
20786
+ const userInputMap = {};
20787
+ this.widgetIds.forEach(id => {
20788
+ const widget = this.getWidgetInstance(id);
20789
+ // Handle Groups, which have their own sets of widgets
20790
+ if (widget != null && widget.getUserInputMap) {
20791
+ userInputMap[id] = widget.getUserInputMap();
20792
+ } else if (widget != null && widget.getUserInput) {
20793
+ userInputMap[id] = widget.getUserInput();
20794
+ }
20795
+ });
20796
+ return userInputMap;
20797
+ }
20798
+ /**
20799
+ * Returns an object mapping from widget ID to perseus-style score.
20800
+ * The keys of this object are the values of the array returned
20801
+ * from `getWidgetIds`.
20802
+ */
20803
+ scoreWidgets() {
20804
+ return scoreWidgetsFunctional(this.state.widgetInfo, this.widgetIds, this.getUserInputMap(), this.props.strings, this.context.locale);
20805
+ }
20806
+
20807
+ /**
20808
+ * Grades the content.
20809
+ */
20810
+ score() {
20811
+ const scores = this.scoreWidgets();
20812
+ const combinedScore = Util.flattenScores(scores);
20813
+ return combinedScore;
20814
+ }
20814
20815
  render() {
20815
20816
  const apiOptions = this.getApiOptions();
20816
20817
  if (this.reuseMarkdown) {
@@ -20885,11 +20886,11 @@ class Renderer extends React.Component {
20885
20886
  // If highlightLint is true and lint is detected, this call
20886
20887
  // will modify the parse tree by adding lint nodes that will
20887
20888
  // serve to highlight the lint when rendered
20888
- const context = _extends({
20889
+ const fullLinterContext = _extends({
20889
20890
  content: this.props.content,
20890
20891
  widgets: this.props.widgets
20891
20892
  }, this.props.linterContext);
20892
- PerseusLinter.runLinter(parsedMarkdown, context, true);
20893
+ PerseusLinter.runLinter(parsedMarkdown, fullLinterContext, true);
20893
20894
 
20894
20895
  // Apply the lint errors from the last TranslationLinter run.
20895
20896
  // TODO(joshuan): Support overlapping dots.
@@ -20911,6 +20912,7 @@ class Renderer extends React.Component {
20911
20912
  return this.lastRenderedMarkdown;
20912
20913
  }
20913
20914
  }
20915
+ Renderer.contextType = PerseusI18nContext;
20914
20916
  Renderer.defaultProps = {
20915
20917
  content: "",
20916
20918
  widgets: {},
@@ -21398,6 +21400,16 @@ InputWithExamples.defaultProps = {
21398
21400
  className: ""
21399
21401
  };
21400
21402
 
21403
+ function _objectWithoutPropertiesLoose(r, e) {
21404
+ if (null == r) return {};
21405
+ var t = {};
21406
+ for (var n in r) if ({}.hasOwnProperty.call(r, n)) {
21407
+ if (e.includes(n)) continue;
21408
+ t[n] = r[n];
21409
+ }
21410
+ return t;
21411
+ }
21412
+
21401
21413
  const _excluded$4 = ["keypadElement", "onFocus", "value"];
21402
21414
  class SimpleKeypadInput extends React.Component {
21403
21415
  constructor(...args) {
@@ -21800,9 +21812,6 @@ class InputNumber extends React.Component {
21800
21812
  return [strings.yourAnswer].concat(examples);
21801
21813
  };
21802
21814
  }
21803
- static validate(state, rubric, strings) {
21804
- return inputNumberValidator(state, rubric, strings);
21805
- }
21806
21815
  static getUserInputFromProps(props) {
21807
21816
  return {
21808
21817
  currentValue: props.currentValue
@@ -21821,9 +21830,6 @@ class InputNumber extends React.Component {
21821
21830
  getUserInput() {
21822
21831
  return InputNumber.getUserInputFromProps(this.props);
21823
21832
  }
21824
- simpleValidate(rubric) {
21825
- return inputNumberValidator(this.getUserInput(), rubric, this.context.strings);
21826
- }
21827
21833
  render() {
21828
21834
  if (this.props.apiOptions.customKeypad) {
21829
21835
  // TODO(charlie): Support "Review Mode".
@@ -21928,7 +21934,8 @@ var InputNumber$1 = {
21928
21934
  hidden: true,
21929
21935
  widget: InputNumber,
21930
21936
  transform: propTransform$3,
21931
- isLintable: true
21937
+ isLintable: true,
21938
+ validator: inputNumberValidator
21932
21939
  };
21933
21940
 
21934
21941
  const ParseTex = TexWrangler.parseTex;
@@ -22202,12 +22209,6 @@ class NumericInput extends React.Component {
22202
22209
  }
22203
22210
  return answerStrings[0];
22204
22211
  }
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
22212
  getUserInput() {
22212
22213
  return NumericInput.getUserInputFromProps(this.props);
22213
22214
  }
@@ -22334,7 +22335,8 @@ var NumericInput$1 = {
22334
22335
  accessible: true,
22335
22336
  widget: NumericInput,
22336
22337
  transform: propsTransform,
22337
- isLintable: true
22338
+ isLintable: true,
22339
+ validator: numericInputValidator
22338
22340
  };
22339
22341
 
22340
22342
  /**
@@ -24142,6 +24144,9 @@ class Passage extends React.Component {
24142
24144
  this._onResize = void 0;
24143
24145
  // @ts-expect-error - TS2564 - Property '_stylesAppiedTimer' has no initializer and is not definitely assigned in the constructor.
24144
24146
  this._stylesAppiedTimer = void 0;
24147
+ // this just helps with TS weak typing when a Widget
24148
+ // doesn't implement any Widget methods
24149
+ this.isWidget = true;
24145
24150
  this.state = {
24146
24151
  nLines: null,
24147
24152
  startLineNumbersAfter: 0,
@@ -24153,10 +24158,6 @@ class Passage extends React.Component {
24153
24158
  });
24154
24159
  };
24155
24160
  }
24156
- // TODO (LEMS-2396): remove validation logic from widgets that don't validate
24157
- static validate() {
24158
- return noopValidator();
24159
- }
24160
24161
  componentDidMount() {
24161
24162
  this._updateState();
24162
24163
 
@@ -24361,21 +24362,6 @@ class Passage extends React.Component {
24361
24362
  };
24362
24363
  }
24363
24364
 
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
24365
  /**
24380
24366
  * Rendering
24381
24367
  *
@@ -24528,7 +24514,8 @@ var Passage$1 = {
24528
24514
  transform: editorProps => {
24529
24515
  return _.pick(editorProps, "passageTitle", "passageText", "footnotes", "showLineNumbers");
24530
24516
  },
24531
- isLintable: true
24517
+ isLintable: true,
24518
+ validator: () => noopValidator()
24532
24519
  };
24533
24520
 
24534
24521
  /**
@@ -24554,6 +24541,9 @@ class PassageRef extends React.Component {
24554
24541
  this._isMounted = void 0;
24555
24542
  // @ts-expect-error - TS2564 - Property '_throttledUpdateRange' has no initializer and is not definitely assigned in the constructor.
24556
24543
  this._throttledUpdateRange = void 0;
24544
+ // this just helps with TS weak typing when a Widget
24545
+ // doesn't implement any Widget methods
24546
+ this.isWidget = true;
24557
24547
  this.state = {
24558
24548
  lineRange: null,
24559
24549
  content: null
@@ -24582,10 +24572,6 @@ class PassageRef extends React.Component {
24582
24572
  }
24583
24573
  };
24584
24574
  }
24585
- // TODO (LEMS-2396): remove validation logic from widgets that don't validate
24586
- static validate() {
24587
- return noopValidator();
24588
- }
24589
24575
  componentDidMount() {
24590
24576
  // TODO(scottgrant): This is a hack to remove the deprecated call to
24591
24577
  // this.isMounted() but is still considered an anti-pattern.
@@ -24604,15 +24590,6 @@ class PassageRef extends React.Component {
24604
24590
  window.removeEventListener("resize", this._throttledUpdateRange);
24605
24591
  this._isMounted = false;
24606
24592
  }
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
24593
  render() {
24617
24594
  const {
24618
24595
  strings
@@ -24667,7 +24644,9 @@ var PassageRef$1 = {
24667
24644
  version: {
24668
24645
  major: 0,
24669
24646
  minor: 1
24670
- }
24647
+ },
24648
+ // TODO: things that aren't interactive shouldn't need validators
24649
+ validator: () => noopValidator()
24671
24650
  };
24672
24651
 
24673
24652
  /**
@@ -26169,7 +26148,7 @@ class Radio$1 extends React.Component {
26169
26148
  choiceStates
26170
26149
  } = this.props;
26171
26150
  if (choiceStates) {
26172
- const score = this.simpleValidate(rubric);
26151
+ const score = radioValidator(this.getUserInput(), rubric, this.context.strings);
26173
26152
  const widgetCorrect = score.type === "points" && score.total === score.earned;
26174
26153
  const newStates = choiceStates.map(state => _extends({}, state, {
26175
26154
  highlighted: state.selected,
@@ -26217,9 +26196,6 @@ class Radio$1 extends React.Component {
26217
26196
  }
26218
26197
  };
26219
26198
  }
26220
- static validate(userInput, rubric, strings) {
26221
- return radioValidator(userInput, rubric, strings);
26222
- }
26223
26199
  static getUserInputFromProps(props) {
26224
26200
  // Return checked inputs in the form {choicesSelected: [bool]}. (Dear
26225
26201
  // future timeline implementers: this used to be {value: i} before
@@ -26310,9 +26286,6 @@ class Radio$1 extends React.Component {
26310
26286
  getUserInput() {
26311
26287
  return Radio$1.getUserInputFromProps(this.props);
26312
26288
  }
26313
- simpleValidate(rubric) {
26314
- return radioValidator(this.getUserInput(), rubric, this.context.strings);
26315
- }
26316
26289
  render() {
26317
26290
  const {
26318
26291
  choices
@@ -26547,7 +26520,8 @@ var Radio = {
26547
26520
  minor: 0
26548
26521
  },
26549
26522
  propUpgrades: propUpgrades$1,
26550
- isLintable: true
26523
+ isLintable: true,
26524
+ validator: radioValidator
26551
26525
  };
26552
26526
 
26553
26527
  /* eslint-disable import/no-named-as-default */
@@ -26590,9 +26564,6 @@ class Categorizer extends React.Component {
26590
26564
  return change.apply(this, args);
26591
26565
  };
26592
26566
  }
26593
- static validate(userInput, rubric, strings) {
26594
- return categorizerValidator(userInput, rubric, strings);
26595
- }
26596
26567
  static getUserInputFromProps(props) {
26597
26568
  return {
26598
26569
  values: props.values
@@ -26608,9 +26579,6 @@ class Categorizer extends React.Component {
26608
26579
  this.change("values", values);
26609
26580
  this.props.trackInteraction();
26610
26581
  }
26611
- simpleValidate(rubric) {
26612
- return categorizerValidator(this.getUserInput(), rubric, this.context.strings);
26613
- }
26614
26582
  render() {
26615
26583
  const self = this;
26616
26584
 
@@ -26756,7 +26724,8 @@ var Categorizer$1 = {
26756
26724
  staticTransform: editorProps => {
26757
26725
  return _.pick(editorProps, "items", "categories", "values", "randomizeItems");
26758
26726
  },
26759
- isLintable: true
26727
+ isLintable: true,
26728
+ validator: categorizerValidator
26760
26729
  };
26761
26730
 
26762
26731
  /**
@@ -26880,10 +26849,6 @@ class CSProgram extends React.Component {
26880
26849
  return change.apply(this, args);
26881
26850
  };
26882
26851
  }
26883
- // The widget's grading function
26884
- static validate(state) {
26885
- return csProgramValidator(state);
26886
- }
26887
26852
  componentDidMount() {
26888
26853
  $(window).on("message", this.handleMessageEvent);
26889
26854
  }
@@ -26896,9 +26861,6 @@ class CSProgram extends React.Component {
26896
26861
  message: this.props.message
26897
26862
  };
26898
26863
  }
26899
- simpleValidate() {
26900
- return csProgramValidator(this.getUserInput());
26901
- }
26902
26864
  render() {
26903
26865
  if (!this.props.programID) {
26904
26866
  return /*#__PURE__*/React.createElement("div", null);
@@ -26980,21 +26942,16 @@ var CSProgram$1 = {
26980
26942
  displayName: "CS Program",
26981
26943
  supportedAlignments: ["block", "full-width"],
26982
26944
  widget: CSProgram,
26983
- hidden: true
26945
+ hidden: true,
26946
+ validator: csProgramValidator
26984
26947
  };
26985
26948
 
26986
26949
  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();
26950
+ constructor(...args) {
26951
+ super(...args);
26952
+ // this just helps with TS weak typing when a Widget
26953
+ // doesn't implement any Widget methods
26954
+ this.isWidget = true;
26998
26955
  }
26999
26956
  render() {
27000
26957
  return /*#__PURE__*/React.createElement(DefinitionConsumer, null, ({
@@ -27050,22 +27007,19 @@ var Definition$1 = {
27050
27007
  accessible: true,
27051
27008
  defaultAlignment: "inline",
27052
27009
  widget: Definition,
27053
- transform: x => x
27010
+ transform: x => x,
27011
+ // TODO: things that aren't interactive shouldn't need validators
27012
+ validator: () => noopValidator()
27054
27013
  };
27055
27014
 
27056
27015
  // The props are type `any` on purpose so that this can receive props
27057
27016
  // from any deprecated widget
27058
27017
  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);
27018
+ constructor(...args) {
27019
+ super(...args);
27020
+ // this just helps with TS weak typing when a Widget
27021
+ // doesn't implement any Widget methods
27022
+ this.isWidget = true;
27069
27023
  }
27070
27024
  render() {
27071
27025
  return /*#__PURE__*/React__default.createElement("div", {
@@ -27085,7 +27039,9 @@ var DeprecatedStandin$1 = {
27085
27039
  name: "deprecated-standin",
27086
27040
  displayName: "Deprecated Standin",
27087
27041
  widget: DeprecatedStandin,
27088
- hidden: true
27042
+ hidden: true,
27043
+ // TODO: things that aren't interactive shouldn't need validators
27044
+ validator: () => noopValidator(1)
27089
27045
  };
27090
27046
 
27091
27047
  function dropdownValidator(userInput, rubric) {
@@ -27126,17 +27082,11 @@ class Dropdown extends React.Component {
27126
27082
  });
27127
27083
  };
27128
27084
  }
27129
- static validate(userInput, rubric) {
27130
- return dropdownValidator(userInput, rubric);
27131
- }
27132
27085
  getUserInput() {
27133
27086
  return {
27134
27087
  value: this.props.selected
27135
27088
  };
27136
27089
  }
27137
- simpleValidate(rubric) {
27138
- return dropdownValidator(this.getUserInput(), rubric);
27139
- }
27140
27090
  render() {
27141
27091
  const children = [/*#__PURE__*/React.createElement(OptionItem, {
27142
27092
  key: "placeholder",
@@ -27183,7 +27133,8 @@ var Dropdown$1 = {
27183
27133
  defaultAlignment: "inline-block",
27184
27134
  accessible: true,
27185
27135
  widget: Dropdown,
27186
- transform: optionsTransform
27136
+ transform: optionsTransform,
27137
+ validator: dropdownValidator
27187
27138
  };
27188
27139
 
27189
27140
  // transform = _.identity
@@ -27197,6 +27148,9 @@ function mediaQueryIsMatched(mediaQuery) {
27197
27148
  class Explanation extends React.Component {
27198
27149
  constructor(..._args) {
27199
27150
  super(..._args);
27151
+ // this just helps with TS weak typing when a Widget
27152
+ // doesn't implement any Widget methods
27153
+ this.isWidget = true;
27200
27154
  this.state = {
27201
27155
  expanded: false
27202
27156
  };
@@ -27211,24 +27165,12 @@ class Explanation extends React.Component {
27211
27165
  this.props.trackInteraction();
27212
27166
  };
27213
27167
  }
27214
- // TODO (LEMS-2396): remove validation logic from widgets that don't validate
27215
- static validate() {
27216
- return noopValidator();
27217
- }
27218
27168
  componentDidMount() {
27219
27169
  this._mounted = true;
27220
27170
  }
27221
27171
  componentWillUnmount() {
27222
27172
  this._mounted = false;
27223
27173
  }
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
27174
  render() {
27233
27175
  const promptText = this.state.expanded ? this.props.hidePrompt : this.props.showPrompt;
27234
27176
  const caretIcon = this.state.expanded ? caretUp : caretDown;
@@ -27342,7 +27284,9 @@ var Explanation$1 = {
27342
27284
  defaultAlignment: "inline",
27343
27285
  widget: Explanation,
27344
27286
  transform: _.identity,
27345
- isLintable: true
27287
+ isLintable: true,
27288
+ // TODO: things that aren't interactive shouldn't need validators
27289
+ validator: () => noopValidator()
27346
27290
  };
27347
27291
 
27348
27292
  class GradedGroupAnswerBar extends React.Component {
@@ -28942,9 +28886,6 @@ class Grapher extends React.Component {
28942
28886
  }
28943
28887
  };
28944
28888
  }
28945
- static validate(state, rubric) {
28946
- return grapherValidator(state, rubric);
28947
- }
28948
28889
  static getUserInputFromProps(props) {
28949
28890
  return props.plot;
28950
28891
  }
@@ -28963,9 +28904,6 @@ class Grapher extends React.Component {
28963
28904
  tickStep[1] = tickStep[1] / gridStep[1];
28964
28905
  return tickStep;
28965
28906
  }
28966
- simpleValidate(rubric) {
28967
- return grapherValidator(this.getUserInput(), rubric);
28968
- }
28969
28907
  getUserInput() {
28970
28908
  return Grapher.getUserInputFromProps(this.props);
28971
28909
  }
@@ -29053,7 +28991,8 @@ var Grapher$1 = {
29053
28991
  hidden: true,
29054
28992
  widget: Grapher,
29055
28993
  transform: propTransform$2,
29056
- staticTransform: staticTransform$4
28994
+ staticTransform: staticTransform$4,
28995
+ validator: grapherValidator
29057
28996
  };
29058
28997
 
29059
28998
  // exports has no 'transform'
@@ -29099,17 +29038,18 @@ class Group extends React.Component {
29099
29038
  // the group with the correct number.
29100
29039
  this.forceUpdate();
29101
29040
  }
29102
- getUserInput() {
29041
+ getUserInputMap() {
29103
29042
  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
- };
29043
+ return (_this$rendererRef7 = this.rendererRef) == null ? void 0 : _this$rendererRef7.getUserInputMap();
29111
29044
  }
29112
29045
 
29046
+ /**
29047
+ * @deprecated getUserInputMap should be used for Groups
29048
+ */
29049
+ getUserInput() {
29050
+ var _this$rendererRef8;
29051
+ return (_this$rendererRef8 = this.rendererRef) == null ? void 0 : _this$rendererRef8.getUserInput();
29052
+ }
29113
29053
  // Mobile API:
29114
29054
  getInputPaths() {
29115
29055
  var _this$rendererRef$get, _this$rendererRef9;
@@ -29270,9 +29210,6 @@ class Iframe extends React.Component {
29270
29210
  return change.apply(this, args);
29271
29211
  };
29272
29212
  }
29273
- static validate(state) {
29274
- return iframeValidator(state);
29275
- }
29276
29213
  componentDidMount() {
29277
29214
  $(window).on("message", this.handleMessageEvent);
29278
29215
  }
@@ -29285,9 +29222,6 @@ class Iframe extends React.Component {
29285
29222
  message: this.props.message
29286
29223
  };
29287
29224
  }
29288
- simpleValidate() {
29289
- return iframeValidator(this.getUserInput());
29290
- }
29291
29225
  render() {
29292
29226
  const style = {
29293
29227
  width: String(this.props.width),
@@ -29361,7 +29295,8 @@ var Iframe$1 = {
29361
29295
  displayName: "Iframe (deprecated)",
29362
29296
  widget: Iframe,
29363
29297
  // Let's not expose it to all content creators yet
29364
- hidden: true
29298
+ hidden: true,
29299
+ validator: iframeValidator
29365
29300
  };
29366
29301
 
29367
29302
  const defaultBoxSize = 400;
@@ -29379,22 +29314,13 @@ const DEFAULT_ALIGNMENT = "block";
29379
29314
  class ImageWidget extends React.Component {
29380
29315
  constructor(..._args) {
29381
29316
  super(..._args);
29317
+ // this just helps with TS weak typing when a Widget
29318
+ // doesn't implement any Widget methods
29319
+ this.isWidget = true;
29382
29320
  this.change = (...args) => {
29383
29321
  return change.apply(this, args);
29384
29322
  };
29385
29323
  }
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
29324
  render() {
29399
29325
  let image;
29400
29326
  const alt = this.props.caption === this.props.alt ? "" : this.props.alt;
@@ -29549,7 +29475,9 @@ var Image$1 = {
29549
29475
  supportedAlignments: editorAlignments,
29550
29476
  displayName: "Image",
29551
29477
  widget: ImageWidget,
29552
- isLintable: true
29478
+ isLintable: true,
29479
+ // TODO: things that aren't interactive shouldn't need validators
29480
+ validator: () => noopValidator()
29553
29481
  };
29554
29482
 
29555
29483
  /* eslint-disable @khanacademy/ts-no-error-suppressions */
@@ -29618,6 +29546,9 @@ const KAScompile = (expr, options) => {
29618
29546
  class Interaction extends React.Component {
29619
29547
  constructor(..._args) {
29620
29548
  super(..._args);
29549
+ // this just helps with TS weak typing when a Widget
29550
+ // doesn't implement any Widget methods
29551
+ this.isWidget = true;
29621
29552
  this.state = {
29622
29553
  variables: _getInitialVariables(this.props.elements),
29623
29554
  functions: _getInitialFunctions(this.props.elements)
@@ -29701,10 +29632,6 @@ class Interaction extends React.Component {
29701
29632
  return change.apply(this, args);
29702
29633
  };
29703
29634
  }
29704
- // TODO (LEMS-2396): remove validation logic from widgets that don't validate
29705
- static validate() {
29706
- return noopValidator();
29707
- }
29708
29635
  UNSAFE_componentWillReceiveProps(nextProps) {
29709
29636
  // HACK(michaelpolyak): This widget is not wired to use the Perseus
29710
29637
  // `onChange` callback to save user changes. Perseus re-rendering of the
@@ -29723,18 +29650,6 @@ class Interaction extends React.Component {
29723
29650
  });
29724
29651
  }
29725
29652
  }
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
29653
  render() {
29739
29654
  const range = this.props.graph.range;
29740
29655
  let labels = this.props.graph.labels;
@@ -30153,7 +30068,9 @@ var Interactive = {
30153
30068
  displayName: "Interaction",
30154
30069
  widget: Interaction,
30155
30070
  transform: _.identity,
30156
- hidden: true
30071
+ hidden: true,
30072
+ // TODO: things that aren't interactive shouldn't need validators
30073
+ validator: () => noopValidator()
30157
30074
  };
30158
30075
 
30159
30076
  /* eslint-disable @khanacademy/ts-no-error-suppressions */
@@ -37239,9 +37156,6 @@ class LegacyInteractiveGraph extends React.Component {
37239
37156
  getUserInput() {
37240
37157
  return InteractiveGraph.getUserInputFromProps(this.props);
37241
37158
  }
37242
- simpleValidate(rubric) {
37243
- return interactiveGraphValidator(this.getUserInput(), rubric);
37244
- }
37245
37159
  render() {
37246
37160
  const box = getInteractiveBoxFromSizeClass(this.props.containerSizeClass);
37247
37161
  const gridStep = this.props.gridStep || Util.getGridStep(this.props.range, this.props.step, box[0]);
@@ -37318,9 +37232,6 @@ class InteractiveGraph extends React.Component {
37318
37232
  }
37319
37233
  throw new PerseusError("Cannot getUserInput from a graph that has never rendered", Errors.NotAllowed);
37320
37234
  }
37321
- simpleValidate(rubric) {
37322
- return interactiveGraphValidator(this.getUserInput(), rubric);
37323
- }
37324
37235
  render() {
37325
37236
  var _this$props$apiOption;
37326
37237
  // Mafs shim
@@ -37653,9 +37564,6 @@ class InteractiveGraph extends React.Component {
37653
37564
  const angle = getClockwiseAngle(coords, allowReflexAngles);
37654
37565
  return angle.toFixed(0) + "\u00B0 angle" + " at (" + coords[1].join(", ") + ")";
37655
37566
  }
37656
- static validate(userInput, rubric) {
37657
- return interactiveGraphValidator(userInput, rubric);
37658
- }
37659
37567
  static getUserInputFromProps(props) {
37660
37568
  return props.graph;
37661
37569
  }
@@ -37704,7 +37612,8 @@ var InteractiveGraph$1 = {
37704
37612
  name: "interactive-graph",
37705
37613
  displayName: "Interactive graph (Assessments only)",
37706
37614
  widget: InteractiveGraph,
37707
- staticTransform: staticTransform$3
37615
+ staticTransform: staticTransform$3,
37616
+ validator: interactiveGraphValidator
37708
37617
  };
37709
37618
 
37710
37619
  const black = "#000000";
@@ -39069,10 +38978,6 @@ const styles$b = StyleSheet.create({
39069
38978
  // TODO: should this be using WidgetProps / PerseusLabelImageWidgetOptions?
39070
38979
 
39071
38980
  class LabelImage extends React.Component {
39072
- static validate(state, rubric) {
39073
- return labelImageValidator(state);
39074
- }
39075
-
39076
38981
  /**
39077
38982
  * Test whether point is contained within triangle.
39078
38983
  *
@@ -39267,9 +39172,6 @@ class LabelImage extends React.Component {
39267
39172
  componentWillUnmount() {
39268
39173
  this._mounted = false;
39269
39174
  }
39270
- simpleValidate(rubric) {
39271
- return labelImageValidator(this.getUserInput());
39272
- }
39273
39175
  getUserInput() {
39274
39176
  const {
39275
39177
  markers
@@ -39626,19 +39528,13 @@ const LabelImageWithDependencies = /*#__PURE__*/React.forwardRef((props, ref) =>
39626
39528
  analytics: deps.analytics
39627
39529
  }, props));
39628
39530
  });
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
39531
  var LabelImage$1 = {
39637
39532
  name: "label-image",
39638
39533
  displayName: "Label Image",
39639
39534
  widget: LabelImageWithDependencies,
39640
39535
  accessible: true,
39641
- isLintable: true
39536
+ isLintable: true,
39537
+ validator: labelImageValidator
39642
39538
  };
39643
39539
 
39644
39540
  var ItemState = /*#__PURE__*/function (ItemState) {
@@ -40423,12 +40319,6 @@ class Matcher extends React.Component {
40423
40319
  this.refs.right.moveOptionToIndex(option, index);
40424
40320
  };
40425
40321
  }
40426
- static validate(state, rubric) {
40427
- return matcherValidator(state, rubric);
40428
- }
40429
- simpleValidate(rubric) {
40430
- return matcherValidator(this.getUserInput(), rubric);
40431
- }
40432
40322
  render() {
40433
40323
  // To minimize layout shift, we display a spinner until our math
40434
40324
  // renderer is ready to render the math inside the matcher. To
@@ -40566,7 +40456,8 @@ var Matcher$1 = {
40566
40456
  name: "matcher",
40567
40457
  displayName: "Matcher (two column)",
40568
40458
  widget: Matcher,
40569
- isLintable: true
40459
+ isLintable: true,
40460
+ validator: matcherValidator
40570
40461
  };
40571
40462
 
40572
40463
  function matrixValidator(state, rubric, strings) {
@@ -40828,9 +40719,6 @@ class Matrix extends React.Component {
40828
40719
  this.props.trackInteraction();
40829
40720
  };
40830
40721
  }
40831
- static validate(state, rubric, strings) {
40832
- return matrixValidator(state, rubric, strings);
40833
- }
40834
40722
  componentDidMount() {
40835
40723
  // Used in the `onBlur` and `onFocus` handlers
40836
40724
  this.cursorPosition = [0, 0];
@@ -40845,9 +40733,6 @@ class Matrix extends React.Component {
40845
40733
  answers: this.props.answers
40846
40734
  };
40847
40735
  }
40848
- simpleValidate(rubric) {
40849
- return matrixValidator(this.getUserInput(), rubric, this.context.strings);
40850
- }
40851
40736
  render() {
40852
40737
  // Set the input sizes through JS so we can control the size of the
40853
40738
  // brackets. (If we set them in CSS we won't know values until the
@@ -41040,7 +40925,8 @@ var Matrix$1 = {
41040
40925
  widget: Matrix,
41041
40926
  transform: propTransform$1,
41042
40927
  staticTransform: staticTransform$2,
41043
- isLintable: true
40928
+ isLintable: true,
40929
+ validator: matrixValidator
41044
40930
  };
41045
40931
 
41046
40932
  const defaultImage = {
@@ -41051,6 +40937,9 @@ const defaultImage = {
41051
40937
  class Measurer extends React.Component {
41052
40938
  constructor(...args) {
41053
40939
  super(...args);
40940
+ // this just helps with TS weak typing when a Widget
40941
+ // doesn't implement any Widget methods
40942
+ this.isWidget = true;
41054
40943
  this.state = {};
41055
40944
  this.ruler = void 0;
41056
40945
  this.protractor = void 0;
@@ -41104,12 +40993,6 @@ class Measurer extends React.Component {
41104
40993
  });
41105
40994
  }
41106
40995
  }
41107
- getUserInput() {
41108
- return {};
41109
- }
41110
- simpleValidate() {
41111
- return noopValidator(1);
41112
- }
41113
40996
  render() {
41114
40997
  const image = _.extend({}, defaultImage, this.props.image);
41115
40998
 
@@ -41135,7 +41018,6 @@ class Measurer extends React.Component {
41135
41018
  }));
41136
41019
  }
41137
41020
  }
41138
- Measurer.validate = noopValidator;
41139
41021
  Measurer.defaultProps = {
41140
41022
  box: [480, 480],
41141
41023
  image: {},
@@ -41169,7 +41051,9 @@ var Measurer$1 = {
41169
41051
  major: 1,
41170
41052
  minor: 0
41171
41053
  },
41172
- propUpgrades: propUpgrades
41054
+ propUpgrades: propUpgrades,
41055
+ // TODO: things that aren't interactive shouldn't need validators
41056
+ validator: () => noopValidator(1)
41173
41057
  };
41174
41058
 
41175
41059
  /**
@@ -42036,17 +41920,11 @@ class Molecule extends React.Component {
42036
41920
  }
42037
41921
  Molecule.contextType = PerseusI18nContext;
42038
41922
  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();
41923
+ constructor(...args) {
41924
+ super(...args);
41925
+ // this just helps with TS weak typing when a Widget
41926
+ // doesn't implement any Widget methods
41927
+ this.isWidget = true;
42050
41928
  }
42051
41929
  render() {
42052
41930
  return /*#__PURE__*/React.createElement(Molecule, {
@@ -42063,7 +41941,8 @@ var Molecule$1 = {
42063
41941
  name: "molecule-renderer",
42064
41942
  displayName: "Molecule renderer",
42065
41943
  hidden: true,
42066
- widget: MoleculeWidget
41944
+ widget: MoleculeWidget,
41945
+ validator: () => noopValidator()
42067
41946
  };
42068
41947
 
42069
41948
  function numberLineValidator(state, rubric) {
@@ -42208,18 +42087,16 @@ const TickMarks = Graphie.createSimpleClass((graphie, props) => {
42208
42087
  results.push(graphie.line([x, -0.2], [x, 0.2]));
42209
42088
  const labelTicks = props.labelTicks;
42210
42089
  if (labelTicks || props.labelStyle === "decimal ticks") {
42211
- results.push(_label(graphie, props.labelStyle, x, x, base));
42090
+ if (x === leftLabel || x === rightLabel) {
42091
+ results.push(graphie.style({
42092
+ color: KhanColors.BLUE
42093
+ }, () => _label(graphie, props.labelStyle, x, x, base)));
42094
+ } else {
42095
+ results.push(_label(graphie, props.labelStyle, x, x, base));
42096
+ }
42212
42097
  }
42213
42098
  }
42214
42099
 
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
42100
  // Render the labels' lines
42224
42101
  graphie.style({
42225
42102
  stroke: KhanColors.BLUE,
@@ -42494,9 +42371,6 @@ class NumberLine extends React.Component {
42494
42371
  });
42495
42372
  };
42496
42373
  }
42497
- static validate(state, rubric) {
42498
- return numberLineValidator(state, rubric);
42499
- }
42500
42374
  focus() {
42501
42375
  if (this.props.isTickCtrl) {
42502
42376
  // eslint-disable-next-line react/no-string-refs
@@ -42526,9 +42400,6 @@ class NumberLine extends React.Component {
42526
42400
  divisionRange: this.props.divisionRange
42527
42401
  };
42528
42402
  }
42529
- simpleValidate(rubric) {
42530
- return numberLineValidator(this.getUserInput(), rubric);
42531
- }
42532
42403
  render() {
42533
42404
  const {
42534
42405
  strings
@@ -42647,7 +42518,8 @@ var NumberLine$1 = {
42647
42518
  displayName: "Number line",
42648
42519
  widget: NumberLine,
42649
42520
  transform: numberLineTransform,
42650
- staticTransform: staticTransform$1
42521
+ staticTransform: staticTransform$1,
42522
+ validator: numberLineValidator
42651
42523
  };
42652
42524
 
42653
42525
  function ordererValidator(userInput, rubric) {
@@ -43080,9 +42952,6 @@ class Orderer extends React.Component {
43080
42952
  });
43081
42953
  };
43082
42954
  }
43083
- static validate(userInput, rubric) {
43084
- return ordererValidator(userInput, rubric);
43085
- }
43086
42955
  UNSAFE_componentWillReceiveProps(nextProps) {
43087
42956
  if (!_.isEqual(this.props.current, nextProps.current)) {
43088
42957
  this.setState({
@@ -43097,9 +42966,6 @@ class Orderer extends React.Component {
43097
42966
  })
43098
42967
  };
43099
42968
  }
43100
- simpleValidate(rubric) {
43101
- return ordererValidator(this.getUserInput(), rubric);
43102
- }
43103
42969
  render() {
43104
42970
  // This is the card we are currently dragging
43105
42971
  const dragging = this.state.dragging && /*#__PURE__*/React.createElement(Card
@@ -43217,31 +43083,22 @@ var Orderer$1 = {
43217
43083
  displayName: "Orderer",
43218
43084
  hidden: true,
43219
43085
  widget: Orderer,
43220
- isLintable: true
43086
+ isLintable: true,
43087
+ validator: ordererValidator
43221
43088
  };
43222
43089
 
43223
43090
  /* eslint-disable @khanacademy/ts-no-error-suppressions */
43224
43091
  class PassageRefTarget extends React.Component {
43225
43092
  constructor(..._args) {
43226
43093
  super(..._args);
43094
+ // this just helps with TS weak typing when a Widget
43095
+ // doesn't implement any Widget methods
43096
+ this.isWidget = true;
43227
43097
  // TODO passage-ref-target isn't interactive; remove
43228
43098
  this.change = (...args) => {
43229
43099
  return change.apply(this, args);
43230
43100
  };
43231
43101
  }
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
43102
  render() {
43246
43103
  return /*#__PURE__*/React.createElement(Renderer, {
43247
43104
  content: this.props.content
@@ -43275,7 +43132,9 @@ var PassageRefTarget$1 = {
43275
43132
  major: 0,
43276
43133
  minor: 0
43277
43134
  },
43278
- isLintable: true
43135
+ isLintable: true,
43136
+ // TODO: things that aren't interactive shouldn't need validators
43137
+ validator: () => noopValidator()
43279
43138
  };
43280
43139
 
43281
43140
  /**
@@ -43288,6 +43147,9 @@ class PhetSimulation extends React.Component {
43288
43147
  super(props);
43289
43148
  this.iframeRef = /*#__PURE__*/React.createRef();
43290
43149
  this.locale = void 0;
43150
+ // this just helps with TS weak typing when a Widget
43151
+ // doesn't implement any Widget methods
43152
+ this.isWidget = true;
43291
43153
  this.state = {
43292
43154
  url: null,
43293
43155
  banner: null
@@ -43330,11 +43192,6 @@ class PhetSimulation extends React.Component {
43330
43192
  await this.updateSimState(this.props.url);
43331
43193
  }
43332
43194
  }
43333
-
43334
- // TODO (LEMS-2396): remove validation logic from widgets that don't validate
43335
- getUserInput() {
43336
- return null;
43337
- }
43338
43195
  async updateSimState(urlString) {
43339
43196
  const url = makeSafeUrl(urlString, this.locale);
43340
43197
  if (url === null) {
@@ -44233,9 +44090,6 @@ class Plotter extends React.Component {
44233
44090
  });
44234
44091
  };
44235
44092
  }
44236
- static validate(userInput, rubric) {
44237
- return plotterValidator(userInput, rubric);
44238
- }
44239
44093
  componentDidMount() {
44240
44094
  this._isMounted = true;
44241
44095
  this.setupGraphie(this.state);
@@ -44262,9 +44116,6 @@ class Plotter extends React.Component {
44262
44116
  getUserInput() {
44263
44117
  return this.state.values;
44264
44118
  }
44265
- simpleValidate(rubric) {
44266
- return plotterValidator(this.getUserInput(), rubric);
44267
- }
44268
44119
  render() {
44269
44120
  // TODO(kevinb) actually compute the size of the graphie correctly and
44270
44121
  // make it that size so we don't have to add extra padding. The value
@@ -44304,7 +44155,8 @@ var Plotter$1 = {
44304
44155
  displayName: "Plotter",
44305
44156
  hidden: true,
44306
44157
  widget: Plotter,
44307
- staticTransform: staticTransform
44158
+ staticTransform: staticTransform,
44159
+ validator: plotterValidator
44308
44160
  };
44309
44161
 
44310
44162
  /**
@@ -44420,9 +44272,6 @@ class Sorter extends React.Component {
44420
44272
  this.refs.sortable.moveOptionToIndex(option, index);
44421
44273
  };
44422
44274
  }
44423
- static validate(userInput, rubric) {
44424
- return sorterValidator(userInput, rubric);
44425
- }
44426
44275
  componentDidMount() {
44427
44276
  this._isMounted = true;
44428
44277
  }
@@ -44438,9 +44287,6 @@ class Sorter extends React.Component {
44438
44287
  changed: this.state.changed
44439
44288
  };
44440
44289
  }
44441
- simpleValidate(rubric) {
44442
- return sorterValidator(this.getUserInput(), rubric);
44443
- }
44444
44290
  render() {
44445
44291
  const options = shuffle(this.props.correct, this.props.problemNum, /* ensurePermuted */true);
44446
44292
  const {
@@ -44474,7 +44320,8 @@ var Sorter$1 = {
44474
44320
  name: "sorter",
44475
44321
  displayName: "Sorter",
44476
44322
  widget: Sorter,
44477
- isLintable: true
44323
+ isLintable: true,
44324
+ validator: sorterValidator
44478
44325
  };
44479
44326
 
44480
44327
  function tableValidator(state, rubric, strings) {
@@ -44653,15 +44500,9 @@ class Table extends React.Component {
44653
44500
  }, cb);
44654
44501
  };
44655
44502
  }
44656
- static validate(state, rubric, strings) {
44657
- return tableValidator(state, rubric, strings);
44658
- }
44659
44503
  getUserInput() {
44660
44504
  return _.map(this.props.answers, _.clone);
44661
44505
  }
44662
- simpleValidate(rubric) {
44663
- return tableValidator(this.getUserInput(), rubric, this.context.strings);
44664
- }
44665
44506
  getDOMNodeForPath(path) {
44666
44507
  const inputID = getRefForPath(path);
44667
44508
  // eslint-disable-next-line react/no-string-refs
@@ -44762,7 +44603,8 @@ var Table$1 = {
44762
44603
  widget: Table,
44763
44604
  transform: propTransform,
44764
44605
  hidden: true,
44765
- isLintable: true
44606
+ isLintable: true,
44607
+ validator: tableValidator
44766
44608
  };
44767
44609
 
44768
44610
  /**
@@ -44850,26 +44692,14 @@ const IS_VIMEO = /(vimeo\.com)/;
44850
44692
  class Video extends React.Component {
44851
44693
  constructor(..._args) {
44852
44694
  super(..._args);
44853
- this.getUserInput = () => {
44854
- return null;
44855
- };
44695
+ // this just helps with TS weak typing when a Widget
44696
+ // doesn't implement any Widget methods
44697
+ this.isWidget = true;
44856
44698
  this.change = (...args) => {
44857
44699
  // @ts-expect-error - TS2345 - Argument of type 'readonly unknown[]' is not assignable to parameter of type 'any[]'.
44858
44700
  return change.apply(this, args);
44859
44701
  };
44860
44702
  }
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
44703
  render() {
44874
44704
  const {
44875
44705
  InitialRequestUrl
@@ -44929,7 +44759,9 @@ var Video$1 = {
44929
44759
  displayName: "Video",
44930
44760
  defaultAlignment: "block",
44931
44761
  supportedAlignments: ["block", "float-left", "float-right", "full-width"],
44932
- widget: Video
44762
+ widget: Video,
44763
+ // TODO: things that aren't interactive shouldn't need validators
44764
+ validator: () => noopValidator()
44933
44765
  };
44934
44766
 
44935
44767
  /* eslint-disable import/no-named-as-default */
@@ -44973,7 +44805,7 @@ const init = function init(options) {
44973
44805
 
44974
44806
  // This file is processed by a Rollup plugin (replace) to inject the production
44975
44807
  const libName = "@khanacademy/perseus";
44976
- const libVersion = "36.1.2";
44808
+ const libVersion = "37.0.0";
44977
44809
  addLibraryVersionToPerseusDebug(libName, libVersion);
44978
44810
 
44979
44811
  const apiVersion = {
@@ -45549,7 +45381,7 @@ class ServerItemRenderer extends React.Component {
45549
45381
  });
45550
45382
 
45551
45383
  // 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());
45384
+ (_this$props$apiOption = this.props.apiOptions) == null || _this$props$apiOption.interactionCallback == null || _this$props$apiOption.interactionCallback(this.questionRenderer.getUserInputMap());
45553
45385
  };
45554
45386
  // This must be pre-bound otherwise SvgImage's shouldComponentUpdate
45555
45387
  // won't behave correctly and we'll get an infinite loop.
@@ -48313,12 +48145,12 @@ function parsePerseusItem(json) {
48313
48145
  }
48314
48146
  throw new Error("Something went wrong.");
48315
48147
  }
48316
- function buildRandomString() {
48148
+ function buildRandomString(capitalize = false) {
48317
48149
  let randomString = "";
48318
48150
  const randomLength = Math.floor(Math.random() * 8) + 3;
48319
48151
  for (let i = 0; i < randomLength; i++) {
48320
48152
  const randomLetter = String.fromCharCode(97 + Math.floor(Math.random() * 26));
48321
- randomString += randomLetter;
48153
+ randomString += capitalize && i === 0 ? randomLetter.toUpperCase() : randomLetter;
48322
48154
  }
48323
48155
  return randomString;
48324
48156
  }
@@ -48326,9 +48158,12 @@ function buildRandomPhrase() {
48326
48158
  const phrases = [];
48327
48159
  const randomLength = Math.floor(Math.random() * 10) + 5;
48328
48160
  for (let i = 0; i < randomLength; i++) {
48329
- phrases.push(buildRandomString());
48161
+ phrases.push(buildRandomString(i === 0));
48330
48162
  }
48331
- return phrases.join(" ");
48163
+ const modifierStart = ["**", "$"];
48164
+ const modifierEnd = ["**", "$"];
48165
+ const modifierIndex = Math.floor(Math.random() * modifierStart.length);
48166
+ return `${modifierStart[modifierIndex]}${phrases.join(" ")}${modifierEnd[modifierIndex]}`;
48332
48167
  }
48333
48168
  function buildTestData(testObject) {
48334
48169
  return `{"data":{"assessmentItem":{"__typename":"AssessmentItemOrError","error":null,"item":{"__typename":"AssessmentItem","id":"x890b3c70f3e8f4a6","itemData":"${testObject}","problemType":"Type 1","sha":"c7284a3ad65214b4e62bccce236d92f7f5d35941"}}}}`;