@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/index.js CHANGED
@@ -2386,35 +2386,14 @@ const anglicizeOperators = tex => {
2386
2386
  const normalizeTex = tex => {
2387
2387
  return anglicizeOperators(tex);
2388
2388
  };
2389
- const deriveKeypadVersion = apiOptions => {
2390
- // We can derive which version of the keypad is in use. This is
2391
- // a bit tricky, but this code will be relatively short-lived
2392
- // as we coalesce onto the new, v2 Keypad, at which point we
2393
- // can remove this `virtualKeypadVersion` field entirely.
2394
- return apiOptions.nativeKeypadProxy != null ? "REACT_NATIVE_KEYPAD" : "MATH_INPUT_KEYPAD_V2";
2395
- };
2396
2389
  // The new, MathQuill input expression widget
2397
2390
  class Expression extends React__namespace.Component {
2398
2391
  static contextType = PerseusI18nContext;
2399
2392
  _textareaId = `expression_textarea_${Date.now()}`;
2400
2393
  _isMounted = false;
2401
-
2402
- // TODO remove this in favor of just using expressionValidator
2403
- static validate(userInput, rubric, strings, locale) {
2404
- return expressionValidator(userInput, rubric, strings, locale);
2405
- }
2406
2394
  static getUserInputFromProps(props) {
2407
2395
  return normalizeTex(props.value);
2408
2396
  }
2409
- static getOneCorrectAnswerFromRubric(rubric) {
2410
- const correctAnswers = (rubric.answerForms || []).filter(answerForm => answerForm.considered === "correct");
2411
- if (correctAnswers.length === 0) {
2412
- return;
2413
- }
2414
- return correctAnswers[0].value;
2415
- }
2416
- //#endregion
2417
-
2418
2397
  static defaultProps = {
2419
2398
  value: "",
2420
2399
  times: false,
@@ -2478,26 +2457,6 @@ class Expression extends React__namespace.Component {
2478
2457
  });
2479
2458
  }
2480
2459
  };
2481
- simpleValidate = _ref => {
2482
- let {
2483
- scoring,
2484
- ...rubric
2485
- } = _ref;
2486
- const score = expressionValidator(this.getUserInput(), rubric, this.context.strings, this.context.locale);
2487
-
2488
- // "scoring" is a flag that indicates when we are checking answers.
2489
- // otherwise, we may just be checking validity after changes.
2490
- if (scoring && score.type !== "invalid") {
2491
- this.props.analytics?.onAnalyticsEvent({
2492
- type: "perseus:expression-evaluated",
2493
- payload: {
2494
- result: score.earned === score.total ? "correct" : "incorrect",
2495
- virtualKeypadVersion: deriveKeypadVersion(this.props.apiOptions)
2496
- }
2497
- });
2498
- }
2499
- return score;
2500
- };
2501
2460
  getUserInput() {
2502
2461
  return Expression.getUserInputFromProps(this.props);
2503
2462
  }
@@ -2760,11 +2719,7 @@ const ExpressionWithDependencies = /*#__PURE__*/React__namespace.forwardRef((pro
2760
2719
  // methods and instead adjust Perseus to provide these facilities through
2761
2720
  // instance methods on our Renderers.
2762
2721
  // @ts-expect-error - TS2339 - Property 'validate' does not exist on type
2763
- ExpressionWithDependencies.validate = expressionValidator;
2764
- // @ts-expect-error - TS2339 - Property 'validate' does not exist on type
2765
2722
  ExpressionWithDependencies.getUserInputFromProps = Expression.getUserInputFromProps;
2766
- // @ts-expect-error - TS2339 - Property 'validate' does not exist on type
2767
- ExpressionWithDependencies.getOneCorrectAnswerFromRubric = Expression.getOneCorrectAnswerFromRubric;
2768
2723
  var Expression$1 = {
2769
2724
  name: "expression",
2770
2725
  displayName: "Expression / Equation",
@@ -2796,7 +2751,15 @@ var Expression$1 = {
2796
2751
  },
2797
2752
  propUpgrades: propUpgrades$2,
2798
2753
  // For use by the editor
2799
- isLintable: true
2754
+ isLintable: true,
2755
+ validator: expressionValidator,
2756
+ getOneCorrectAnswerFromRubric(rubric) {
2757
+ const correctAnswers = (rubric.answerForms || []).filter(answerForm => answerForm.considered === "correct");
2758
+ if (correctAnswers.length === 0) {
2759
+ return;
2760
+ }
2761
+ return correctAnswers[0].value;
2762
+ }
2800
2763
  };
2801
2764
 
2802
2765
  /**
@@ -3026,6 +2989,9 @@ function combineScores(scoreA, scoreB) {
3026
2989
  }
3027
2990
  });
3028
2991
  }
2992
+ function flattenScores(widgetScoreMap) {
2993
+ return Object.values(widgetScoreMap).reduce(combineScores, noScore);
2994
+ }
3029
2995
  function isCorrect(score) {
3030
2996
  return score.type === "points" && score.earned >= score.total;
3031
2997
  }
@@ -3556,6 +3522,7 @@ const Util = {
3556
3522
  shuffle: shuffle$3,
3557
3523
  split,
3558
3524
  combineScores,
3525
+ flattenScores,
3559
3526
  keScoreFromPerseusScore,
3560
3527
  firstNumericalParse: firstNumericalParse$1,
3561
3528
  stringArrayOfSize: stringArrayOfSize$1,
@@ -10094,6 +10061,8 @@ const setLabelMargins = function (span, size) {
10094
10061
  marginTop: -height / 2 - y * scale
10095
10062
  });
10096
10063
  } else {
10064
+ const currentHeightMatchesProps = span.scrollHeight === height;
10065
+
10097
10066
  // We are using jQuery to collect information and calculate a scale
10098
10067
  // since we don't have a way to pass it to this function.
10099
10068
  // We need the width of the container in order to calculate the scale to apply to the label.
@@ -10115,6 +10084,17 @@ const setLabelMargins = function (span, size) {
10115
10084
  // Inherited line-height values can really mess up placement.
10116
10085
  $container.css("line-height", "normal");
10117
10086
 
10087
+ // TODO: Verify that this change doesn't impact other graphies
10088
+ // Check the full list of consuming widgets.
10089
+
10090
+ // If the change in line-height affected the height of the element,
10091
+ // then the height used for calculations should be updated.
10092
+ // This can happen when the first label in the container calls this method,
10093
+ // and the line-height was different when the height measurement was originally referenced.
10094
+ if (currentHeightMatchesProps && span.scrollHeight !== height) {
10095
+ height = span.scrollHeight;
10096
+ }
10097
+
10118
10098
  // The expected width of the graphie is found in the "max-width" property on ".svg-image" containers,
10119
10099
  // and is found in the "width" property on ".graphie" containers.
10120
10100
  const widthValues = $container.css(["max-width", "width"]) ?? {
@@ -14777,7 +14757,7 @@ const mapObject$1 = function (obj, lambda) {
14777
14757
  * });
14778
14758
  * {a: 'aa', b: 'bb'}
14779
14759
  */
14780
- const mapObjectFromArray$1 = function (arr, lambda) {
14760
+ const mapObjectFromArray = function (arr, lambda) {
14781
14761
  const result = {};
14782
14762
  ___default["default"].each(arr, function (elem) {
14783
14763
  result[elem] = lambda(elem);
@@ -14787,7 +14767,7 @@ const mapObjectFromArray$1 = function (arr, lambda) {
14787
14767
  var objective_ = {
14788
14768
  pluck: pluck,
14789
14769
  mapObject: mapObject$1,
14790
- mapObjectFromArray: mapObjectFromArray$1
14770
+ mapObjectFromArray: mapObjectFromArray
14791
14771
  };
14792
14772
 
14793
14773
  /**
@@ -19003,169 +18983,6 @@ class QuestionParagraph extends React__namespace.Component {
19003
18983
  }
19004
18984
  }
19005
18985
 
19006
- /**
19007
- * TranslationLinter asynchronously calls Khan Academy's poentry linter.
19008
- *
19009
- * The poentry linter checks for issues that prevent content from being
19010
- * translated. This linter is KA-specific and so TranslationLinter does not do
19011
- * anything in non-KA environments.
19012
- */
19013
-
19014
- // A LintCB is called once the linter has been run.
19015
-
19016
- // msec to wait before we actually call the linter after receiving the
19017
- // last call. The timer is reset if the lint is called during the wait.
19018
- const DEBOUNCE_TIMEOUT = 1000;
19019
- class TranslationLinter {
19020
- // To avoid linting when nothing has changed.
19021
- previousContent = null;
19022
-
19023
- /**
19024
- * Runs the poentry linter on the given perseus string asynchronously.
19025
- *
19026
- * It doesn't do anything unless we're in a KA environment.
19027
- *
19028
- * onLintErrorsGenerated is called even if there are no errors. It is not
19029
- * called if perseusStr is unchanged from the last call.
19030
- *
19031
- * Linting is relatively expensive -- so we debounce linting each instance
19032
- * for some amount of time.
19033
- */
19034
- runLinter = _.debounce((perseusStr, onLintErrorsGenerated) => {
19035
- // TODO(FEI-5003): Do we still need to check for 'KA'?
19036
- if (typeof KA === "undefined") {
19037
- return;
19038
- }
19039
- if (perseusStr === this.previousContent) {
19040
- return;
19041
- }
19042
- this.previousContent = perseusStr;
19043
- if (perseusStr === "") {
19044
- onLintErrorsGenerated([]);
19045
- return;
19046
- }
19047
- fetch("/api/internal/translations/lint_poentry?preview=1&lang=en", {
19048
- headers: {
19049
- "Content-Type": "application/json"
19050
- },
19051
- body: JSON.stringify({
19052
- // The poentry linter verifies that the translation from
19053
- // the original text to the translated text is sane. We
19054
- // want to ensure that the translation from English to
19055
- // English is sane, so msgid === mstr.
19056
- msgid: perseusStr,
19057
- // msgid is the original text
19058
- msgstr: perseusStr,
19059
- // msgstr is the translated text
19060
- format: "perseus_text",
19061
- filename: ""
19062
- }),
19063
- method: "POST"
19064
- }).then(response => {
19065
- if (response.status >= 400) {
19066
- return {
19067
- status: "error",
19068
- message: "Could not run i18n linter."
19069
- };
19070
- }
19071
- return response.json();
19072
- }, rejection => {
19073
- return {
19074
- status: "error",
19075
- message: "Could not run i18n linter."
19076
- };
19077
- }).then(json => {
19078
- if (json.status === "error") {
19079
- onLintErrorsGenerated(["Some part of this text makes it untranslatable. " + "The specific message from the i18n linter was: " + json.message.replace(/\n/g, " ")]);
19080
- } else {
19081
- onLintErrorsGenerated([]);
19082
- }
19083
- });
19084
- }, DEBOUNCE_TIMEOUT);
19085
-
19086
- /**
19087
- * Applies an array of errors generated by linters without position
19088
- * information (TranslationLinter and the legacy getSaveWarnings()) to the
19089
- * top of a Perseus tree.
19090
- */
19091
- applyLintErrors(parsedMarkdown, translationLintErrors) {
19092
- // These lint errors do not have position data associated with
19093
- // them, so we just plop them at the top.
19094
- if (translationLintErrors.length) {
19095
- const errorText = translationLintErrors.join("\n\n");
19096
- parsedMarkdown.unshift({
19097
- content: {
19098
- type: "text",
19099
- content: ""
19100
- },
19101
- insideTable: false,
19102
- message: errorText,
19103
- ruleName: "legacy-error",
19104
- severity: PerseusLinter.Rule.Severity.ERROR,
19105
- type: "lint"
19106
- });
19107
- }
19108
- }
19109
- }
19110
-
19111
- /**
19112
- * Preprocess TeX code to convert things that MathJax doesn't know how to handle
19113
- * to things is does.
19114
- */
19115
- var preprocessTex = (texCode => texCode
19116
- // Replace uses of \begin{align}...\end{align} which KaTeX doesn't
19117
- // support (yet) with \begin{aligned}...\end{aligned} which renders
19118
- // the same is supported by KaTeX. It does the same for align*.
19119
- // TODO(kevinb) update content to use aligned instead of align.
19120
- // TODO(LEMS-1608) Remove this replacement as MathJax supports the
19121
- // "align" macro correctly (and, in fact, it is not synonymous with
19122
- // "aligned").
19123
- .replace(/\{align[*]?\}/g, "{aligned}")
19124
- // Replace non-breaking spaces with regular spaces.
19125
- .replace(/[\u00a0]/g, " "));
19126
-
19127
- const {
19128
- interactiveSizes
19129
- } = constants$1;
19130
-
19131
- // Note: these size cutoffs represent content-width cutoffs as specified in
19132
- // http://zpl.io/1mVmvU (broken link - we no longer use Zeplin)
19133
- // TODO(benkomalo): these values aren't used in JS outside of this file, but
19134
- // are coupled to the values in styles/styles.less - DRY it up at some point
19135
- const smMax = articleMaxWidthTableInPx;
19136
- const mdMax = articleMaxWidthInPx;
19137
- const containerSizeClass = {
19138
- SMALL: "small",
19139
- MEDIUM: "medium",
19140
- LARGE: "large",
19141
- XLARGE: "xlarge"
19142
- };
19143
- const getClassFromWidth = width => {
19144
- if (!width) {
19145
- return containerSizeClass.MEDIUM;
19146
- }
19147
- if (width <= smMax) {
19148
- return containerSizeClass.SMALL;
19149
- }
19150
- if (width <= mdMax) {
19151
- return containerSizeClass.MEDIUM;
19152
- }
19153
- return containerSizeClass.LARGE;
19154
- };
19155
- const getInteractiveBoxFromSizeClass = sizeClass => {
19156
- if (sizeClass === containerSizeClass.SMALL) {
19157
- return [interactiveSizes.defaultBoxSizeSmall, interactiveSizes.defaultBoxSizeSmall];
19158
- }
19159
- return [interactiveSizes.defaultBoxSize, interactiveSizes.defaultBoxSize];
19160
- };
19161
-
19162
- var sizingUtils = /*#__PURE__*/Object.freeze({
19163
- __proto__: null,
19164
- containerSizeClass: containerSizeClass,
19165
- getClassFromWidth: getClassFromWidth,
19166
- getInteractiveBoxFromSizeClass: getInteractiveBoxFromSizeClass
19167
- });
19168
-
19169
18986
  const DEFAULT_ALIGNMENT$1 = "block";
19170
18987
  // NOTE(kevinb): "default" is not one in `validAlignments`.
19171
18988
  const DEFAULT_SUPPORTED_ALIGNMENTS = ["default"];
@@ -19264,6 +19081,12 @@ const getWidget = name => {
19264
19081
  }
19265
19082
  return widgets[name].widget;
19266
19083
  };
19084
+ const getWidgetExport = name => {
19085
+ return widgets[name] ?? null;
19086
+ };
19087
+ const getWidgetValidator = name => {
19088
+ return widgets[name]?.validator ?? null;
19089
+ };
19267
19090
  const getEditor = name => {
19268
19091
  return ___default["default"].has(editors, name) ? editors[name] : null;
19269
19092
  };
@@ -19569,6 +19392,8 @@ var widgets$1 = /*#__PURE__*/Object.freeze({
19569
19392
  replaceEditor: replaceEditor,
19570
19393
  replaceDeprecatedEditors: replaceDeprecatedEditors,
19571
19394
  getWidget: getWidget,
19395
+ getWidgetExport: getWidgetExport,
19396
+ getWidgetValidator: getWidgetValidator,
19572
19397
  getEditor: getEditor,
19573
19398
  getTransform: getTransform,
19574
19399
  getVersion: getVersion,
@@ -19588,6 +19413,222 @@ var widgets$1 = /*#__PURE__*/Object.freeze({
19588
19413
  isLintable: isLintable
19589
19414
  });
19590
19415
 
19416
+ function emptyWidgetsFunctional(widgets,
19417
+ // This is a port of old code, I'm not sure why
19418
+ // we need widgetIds vs the keys of the widgets object
19419
+ widgetIds, userInputMap, strings, locale) {
19420
+ return widgetIds.filter(id => {
19421
+ const widget = widgets[id];
19422
+ if (!widget || widget.static) {
19423
+ // Static widgets shouldn't count as empty
19424
+ return false;
19425
+ }
19426
+ let score = null;
19427
+ const userInput = userInputMap[id];
19428
+ const validator = getWidgetValidator(widget.type);
19429
+ if (widget.type === "group") {
19430
+ const scores = scoreWidgetsFunctional(widget.options.widgets, Object.keys(widget.options.widgets), userInputMap[id], strings, locale);
19431
+ score = Util.flattenScores(scores);
19432
+ } else if (validator) {
19433
+ score = validator(userInput, widget.options, strings, locale);
19434
+ }
19435
+ if (score) {
19436
+ return Util.scoreIsEmpty(score);
19437
+ }
19438
+ });
19439
+ }
19440
+ function scoreWidgetsFunctional(widgets,
19441
+ // This is a port of old code, I'm not sure why
19442
+ // we need widgetIds vs the keys of the widgets object
19443
+ widgetIds, userInputMap, strings, locale) {
19444
+ const gradedWidgetIds = widgetIds.filter(id => {
19445
+ const props = widgets[id];
19446
+ const widgetIsGraded = props?.graded == null || props.graded;
19447
+ const widgetIsStatic = !!props?.static;
19448
+ // Ungraded widgets or widgets set to static shouldn't be graded.
19449
+ return widgetIsGraded && !widgetIsStatic;
19450
+ });
19451
+ const widgetScores = {};
19452
+ gradedWidgetIds.forEach(id => {
19453
+ const widget = widgets[id];
19454
+ if (!widget) {
19455
+ return;
19456
+ }
19457
+ const userInput = userInputMap[id];
19458
+ const validator = getWidgetValidator(widget.type);
19459
+ if (widget.type === "group") {
19460
+ const scores = scoreWidgetsFunctional(widget.options.widgets, Object.keys(widget.options.widgets), userInputMap[id], strings, locale);
19461
+ widgetScores[id] = Util.flattenScores(scores);
19462
+ } else if (validator) {
19463
+ widgetScores[id] = validator(userInput, widget.options, strings, locale);
19464
+ }
19465
+ });
19466
+ return widgetScores;
19467
+ }
19468
+
19469
+ /**
19470
+ * TranslationLinter asynchronously calls Khan Academy's poentry linter.
19471
+ *
19472
+ * The poentry linter checks for issues that prevent content from being
19473
+ * translated. This linter is KA-specific and so TranslationLinter does not do
19474
+ * anything in non-KA environments.
19475
+ */
19476
+
19477
+ // A LintCB is called once the linter has been run.
19478
+
19479
+ // msec to wait before we actually call the linter after receiving the
19480
+ // last call. The timer is reset if the lint is called during the wait.
19481
+ const DEBOUNCE_TIMEOUT = 1000;
19482
+ class TranslationLinter {
19483
+ // To avoid linting when nothing has changed.
19484
+ previousContent = null;
19485
+
19486
+ /**
19487
+ * Runs the poentry linter on the given perseus string asynchronously.
19488
+ *
19489
+ * It doesn't do anything unless we're in a KA environment.
19490
+ *
19491
+ * onLintErrorsGenerated is called even if there are no errors. It is not
19492
+ * called if perseusStr is unchanged from the last call.
19493
+ *
19494
+ * Linting is relatively expensive -- so we debounce linting each instance
19495
+ * for some amount of time.
19496
+ */
19497
+ runLinter = _.debounce((perseusStr, onLintErrorsGenerated) => {
19498
+ // TODO(FEI-5003): Do we still need to check for 'KA'?
19499
+ if (typeof KA === "undefined") {
19500
+ return;
19501
+ }
19502
+ if (perseusStr === this.previousContent) {
19503
+ return;
19504
+ }
19505
+ this.previousContent = perseusStr;
19506
+ if (perseusStr === "") {
19507
+ onLintErrorsGenerated([]);
19508
+ return;
19509
+ }
19510
+ fetch("/api/internal/translations/lint_poentry?preview=1&lang=en", {
19511
+ headers: {
19512
+ "Content-Type": "application/json"
19513
+ },
19514
+ body: JSON.stringify({
19515
+ // The poentry linter verifies that the translation from
19516
+ // the original text to the translated text is sane. We
19517
+ // want to ensure that the translation from English to
19518
+ // English is sane, so msgid === mstr.
19519
+ msgid: perseusStr,
19520
+ // msgid is the original text
19521
+ msgstr: perseusStr,
19522
+ // msgstr is the translated text
19523
+ format: "perseus_text",
19524
+ filename: ""
19525
+ }),
19526
+ method: "POST"
19527
+ }).then(response => {
19528
+ if (response.status >= 400) {
19529
+ return {
19530
+ status: "error",
19531
+ message: "Could not run i18n linter."
19532
+ };
19533
+ }
19534
+ return response.json();
19535
+ }, rejection => {
19536
+ return {
19537
+ status: "error",
19538
+ message: "Could not run i18n linter."
19539
+ };
19540
+ }).then(json => {
19541
+ if (json.status === "error") {
19542
+ onLintErrorsGenerated(["Some part of this text makes it untranslatable. " + "The specific message from the i18n linter was: " + json.message.replace(/\n/g, " ")]);
19543
+ } else {
19544
+ onLintErrorsGenerated([]);
19545
+ }
19546
+ });
19547
+ }, DEBOUNCE_TIMEOUT);
19548
+
19549
+ /**
19550
+ * Applies an array of errors generated by linters without position
19551
+ * information (TranslationLinter and the legacy getSaveWarnings()) to the
19552
+ * top of a Perseus tree.
19553
+ */
19554
+ applyLintErrors(parsedMarkdown, translationLintErrors) {
19555
+ // These lint errors do not have position data associated with
19556
+ // them, so we just plop them at the top.
19557
+ if (translationLintErrors.length) {
19558
+ const errorText = translationLintErrors.join("\n\n");
19559
+ parsedMarkdown.unshift({
19560
+ content: {
19561
+ type: "text",
19562
+ content: ""
19563
+ },
19564
+ insideTable: false,
19565
+ message: errorText,
19566
+ ruleName: "legacy-error",
19567
+ severity: PerseusLinter.Rule.Severity.ERROR,
19568
+ type: "lint"
19569
+ });
19570
+ }
19571
+ }
19572
+ }
19573
+
19574
+ /**
19575
+ * Preprocess TeX code to convert things that MathJax doesn't know how to handle
19576
+ * to things is does.
19577
+ */
19578
+ var preprocessTex = (texCode => texCode
19579
+ // Replace uses of \begin{align}...\end{align} which KaTeX doesn't
19580
+ // support (yet) with \begin{aligned}...\end{aligned} which renders
19581
+ // the same is supported by KaTeX. It does the same for align*.
19582
+ // TODO(kevinb) update content to use aligned instead of align.
19583
+ // TODO(LEMS-1608) Remove this replacement as MathJax supports the
19584
+ // "align" macro correctly (and, in fact, it is not synonymous with
19585
+ // "aligned").
19586
+ .replace(/\{align[*]?\}/g, "{aligned}")
19587
+ // Replace non-breaking spaces with regular spaces.
19588
+ .replace(/[\u00a0]/g, " "));
19589
+
19590
+ const {
19591
+ interactiveSizes
19592
+ } = constants$1;
19593
+
19594
+ // Note: these size cutoffs represent content-width cutoffs as specified in
19595
+ // http://zpl.io/1mVmvU (broken link - we no longer use Zeplin)
19596
+ // TODO(benkomalo): these values aren't used in JS outside of this file, but
19597
+ // are coupled to the values in styles/styles.less - DRY it up at some point
19598
+ const smMax = articleMaxWidthTableInPx;
19599
+ const mdMax = articleMaxWidthInPx;
19600
+ const containerSizeClass = {
19601
+ SMALL: "small",
19602
+ MEDIUM: "medium",
19603
+ LARGE: "large",
19604
+ XLARGE: "xlarge"
19605
+ };
19606
+ const getClassFromWidth = width => {
19607
+ if (!width) {
19608
+ return containerSizeClass.MEDIUM;
19609
+ }
19610
+ if (width <= smMax) {
19611
+ return containerSizeClass.SMALL;
19612
+ }
19613
+ if (width <= mdMax) {
19614
+ return containerSizeClass.MEDIUM;
19615
+ }
19616
+ return containerSizeClass.LARGE;
19617
+ };
19618
+ const getInteractiveBoxFromSizeClass = sizeClass => {
19619
+ if (sizeClass === containerSizeClass.SMALL) {
19620
+ return [interactiveSizes.defaultBoxSizeSmall, interactiveSizes.defaultBoxSizeSmall];
19621
+ }
19622
+ return [interactiveSizes.defaultBoxSize, interactiveSizes.defaultBoxSize];
19623
+ };
19624
+
19625
+ var sizingUtils = /*#__PURE__*/Object.freeze({
19626
+ __proto__: null,
19627
+ containerSizeClass: containerSizeClass,
19628
+ getClassFromWidth: getClassFromWidth,
19629
+ getInteractiveBoxFromSizeClass: getInteractiveBoxFromSizeClass
19630
+ });
19631
+
19591
19632
  class WidgetContainer extends React__namespace.Component {
19592
19633
  widgetRef = /*#__PURE__*/React__namespace.createRef();
19593
19634
  static defaultProps = {
@@ -19734,8 +19775,7 @@ class WidgetContainer extends React__namespace.Component {
19734
19775
  }
19735
19776
 
19736
19777
  const {
19737
- mapObject,
19738
- mapObjectFromArray
19778
+ mapObject
19739
19779
  } = objective_;
19740
19780
  const rContainsNonWhitespace = /\S/;
19741
19781
  const rImageURL = /(web\+graphie|https):\/\/[^\s]*/;
@@ -19762,6 +19802,7 @@ const isIdPathPrefix = function (prefixArray, wholeArray) {
19762
19802
  });
19763
19803
  };
19764
19804
  class Renderer extends React__namespace.Component {
19805
+ static contextType = PerseusI18nContext;
19765
19806
  _currentFocus;
19766
19807
  // @ts-expect-error - TS2564 - Property '_foundTextNodes' has no initializer and is not definitely assigned in the constructor.
19767
19808
  _foundTextNodes;
@@ -20065,11 +20106,7 @@ class Renderer extends React__namespace.Component {
20065
20106
  }
20066
20107
  let interactionTracker = this._interactionTrackers[id];
20067
20108
  if (!interactionTracker) {
20068
- interactionTracker = this._interactionTrackers[id] = new InteractionTracker(apiOptions.trackInteraction,
20069
- // @ts-expect-error - TS2345 - Argument of type 'string | null | undefined' is not assignable to parameter of type 'string'.
20070
- widgetInfo && widgetInfo.type, id,
20071
- // @ts-expect-error - TS2345 - Argument of type 'string | null | undefined' is not assignable to parameter of type 'string'.
20072
- getTracking(widgetInfo && widgetInfo.type));
20109
+ interactionTracker = this._interactionTrackers[id] = new InteractionTracker(apiOptions.trackInteraction, widgetInfo && widgetInfo.type, id, getTracking(widgetInfo && widgetInfo.type));
20073
20110
  }
20074
20111
  return {
20075
20112
  ...widgetProps,
@@ -20883,23 +20920,9 @@ class Renderer extends React__namespace.Component {
20883
20920
  }, this);
20884
20921
  return state;
20885
20922
  };
20886
- emptyWidgets = () => {
20887
- // @ts-expect-error - TS2345 - Argument of type '(id: string) => boolean | undefined' is not assignable to parameter of type 'Iteratee<string[], boolean, string>'.
20888
- return ___default["default"].filter(this.widgetIds, id => {
20889
- const widgetInfo = this._getWidgetInfo(id);
20890
- if (widgetInfo.static) {
20891
- // Static widgets shouldn't count as empty
20892
- return false;
20893
- }
20894
- const widget = this.getWidgetInstance(id);
20895
- if (widget && widget.simpleValidate) {
20896
- const score = widget.simpleValidate(widgetInfo.options,
20897
- // @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'.
20898
- null);
20899
- return Util.scoreIsEmpty(score);
20900
- }
20901
- });
20902
- };
20923
+ emptyWidgets() {
20924
+ return emptyWidgetsFunctional(this.state.widgetInfo, this.widgetIds, this.getUserInputMap(), this.props.strings, this.context.locale);
20925
+ }
20903
20926
  _setWidgetProps = (id, newProps, cb, silent) => {
20904
20927
  this.setState(prevState => {
20905
20928
  const widgetProps = {
@@ -20963,8 +20986,11 @@ class Renderer extends React__namespace.Component {
20963
20986
 
20964
20987
  /**
20965
20988
  * Returns an array of the widget `.getUserInput()` results
20989
+ *
20990
+ * TODO: can we remove this?
20991
+ * @deprecated use getUserInputMap
20966
20992
  */
20967
- getUserInput = () => {
20993
+ getUserInput() {
20968
20994
  return this.widgetIds.map(id => {
20969
20995
  const widget = this.getWidgetInstance(id);
20970
20996
  if (widget && widget.getUserInput) {
@@ -20973,7 +20999,24 @@ class Renderer extends React__namespace.Component {
20973
20999
  return widget.getUserInput();
20974
21000
  }
20975
21001
  });
20976
- };
21002
+ }
21003
+
21004
+ /**
21005
+ * Returns an object of the widget `.getUserInput()` results
21006
+ */
21007
+ getUserInputMap() {
21008
+ const userInputMap = {};
21009
+ this.widgetIds.forEach(id => {
21010
+ const widget = this.getWidgetInstance(id);
21011
+ // Handle Groups, which have their own sets of widgets
21012
+ if (widget?.getUserInputMap) {
21013
+ userInputMap[id] = widget.getUserInputMap();
21014
+ } else if (widget?.getUserInput) {
21015
+ userInputMap[id] = widget.getUserInput();
21016
+ }
21017
+ });
21018
+ return userInputMap;
21019
+ }
20977
21020
 
20978
21021
  /**
20979
21022
  * Returns an array of all widget IDs in the order they occur in
@@ -20983,59 +21026,23 @@ class Renderer extends React__namespace.Component {
20983
21026
  return this.widgetIds;
20984
21027
  };
20985
21028
 
20986
- /**
20987
- * Returns the result of `.getUserInput()` for each widget, in
20988
- * a map from widgetId to userInput.
20989
- * NOTE(jeremy): This function is hauntingly similar to `getUserInput` with
20990
- * the major difference being that this function returns a map of
20991
- * `widgetID` => UserInput and `getUserInput` simply returns an array. It
20992
- * would be trivial to map between the results of each of these functions,
20993
- * so we should aim to remove one of these functions.
20994
- */
20995
- getUserInputForWidgets = () => {
20996
- return mapObjectFromArray(this.widgetIds, id => {
20997
- const widget = this.getWidgetInstance(id);
20998
- if (widget && widget.getUserInput) {
20999
- return widget.getUserInput();
21000
- }
21001
- });
21002
- };
21003
-
21004
21029
  /**
21005
21030
  * Returns an object mapping from widget ID to perseus-style score.
21006
21031
  * The keys of this object are the values of the array returned
21007
21032
  * from `getWidgetIds`.
21008
21033
  */
21009
- scoreWidgets = () => {
21010
- const widgetProps = this.state.widgetInfo;
21011
- const gradedWidgetIds = ___default["default"].filter(this.widgetIds, id => {
21012
- const props = widgetProps[id];
21013
- const widgetIsGraded = props?.graded == null || props.graded;
21014
- const widgetIsStatic = !!props?.static;
21015
- // Ungraded widgets or widgets set to static shouldn't be graded.
21016
- return widgetIsGraded && !widgetIsStatic;
21017
- });
21018
- const widgetScores = {};
21019
- ___default["default"].each(gradedWidgetIds, id => {
21020
- const props = widgetProps[id];
21021
- const widget = this.getWidgetInstance(id);
21022
- // widget can be undefined if it hasn't yet been rendered
21023
- if (widget && widget.simpleValidate) {
21024
- widgetScores[id] = widget.simpleValidate({
21025
- ...props?.options,
21026
- scoring: true
21027
- });
21028
- }
21029
- });
21030
- return widgetScores;
21031
- };
21034
+ scoreWidgets() {
21035
+ return scoreWidgetsFunctional(this.state.widgetInfo, this.widgetIds, this.getUserInputMap(), this.props.strings, this.context.locale);
21036
+ }
21032
21037
 
21033
21038
  /**
21034
21039
  * Grades the content.
21035
21040
  */
21036
- score = () => {
21037
- return ___default["default"].reduce(this.scoreWidgets(), Util.combineScores, Util.noScore);
21038
- };
21041
+ score() {
21042
+ const scores = this.scoreWidgets();
21043
+ const combinedScore = Util.flattenScores(scores);
21044
+ return combinedScore;
21045
+ }
21039
21046
  guessAndScore = () => {
21040
21047
  const totalGuess = this.getUserInput();
21041
21048
  const totalScore = this.score();
@@ -21147,12 +21154,12 @@ class Renderer extends React__namespace.Component {
21147
21154
  // If highlightLint is true and lint is detected, this call
21148
21155
  // will modify the parse tree by adding lint nodes that will
21149
21156
  // serve to highlight the lint when rendered
21150
- const context = {
21157
+ const fullLinterContext = {
21151
21158
  content: this.props.content,
21152
21159
  widgets: this.props.widgets,
21153
21160
  ...this.props.linterContext
21154
21161
  };
21155
- PerseusLinter__namespace.runLinter(parsedMarkdown, context, true);
21162
+ PerseusLinter__namespace.runLinter(parsedMarkdown, fullLinterContext, true);
21156
21163
 
21157
21164
  // Apply the lint errors from the last TranslationLinter run.
21158
21165
  // TODO(joshuan): Support overlapping dots.
@@ -21974,24 +21981,11 @@ class InputNumber extends React__namespace.Component {
21974
21981
  apiOptions: ApiOptions.defaults,
21975
21982
  linterContext: PerseusLinter.linterContextDefault
21976
21983
  };
21977
- static validate(state, rubric, strings) {
21978
- return inputNumberValidator(state, rubric, strings);
21979
- }
21980
21984
  static getUserInputFromProps(props) {
21981
21985
  return {
21982
21986
  currentValue: props.currentValue
21983
21987
  };
21984
21988
  }
21985
- static getOneCorrectAnswerFromRubric(rubric) {
21986
- if (rubric.value == null) {
21987
- return;
21988
- }
21989
- let answerString = String(rubric.value);
21990
- if (rubric.inexact && rubric.maxError) {
21991
- answerString += " \u00B1 " + rubric.maxError;
21992
- }
21993
- return answerString;
21994
- }
21995
21989
  shouldShowExamples = () => {
21996
21990
  return this.props.answerType !== "number";
21997
21991
  };
@@ -22047,9 +22041,6 @@ class InputNumber extends React__namespace.Component {
22047
22041
  getUserInput() {
22048
22042
  return InputNumber.getUserInputFromProps(this.props);
22049
22043
  }
22050
- simpleValidate(rubric) {
22051
- return inputNumberValidator(this.getUserInput(), rubric, this.context.strings);
22052
- }
22053
22044
  examples = () => {
22054
22045
  const {
22055
22046
  strings
@@ -22152,7 +22143,18 @@ var InputNumber$1 = {
22152
22143
  hidden: true,
22153
22144
  widget: InputNumber,
22154
22145
  transform: propTransform$3,
22155
- isLintable: true
22146
+ isLintable: true,
22147
+ validator: inputNumberValidator,
22148
+ getOneCorrectAnswerFromRubric(rubric) {
22149
+ if (rubric.value == null) {
22150
+ return;
22151
+ }
22152
+ let answerString = String(rubric.value);
22153
+ if (rubric.inexact && rubric.maxError) {
22154
+ answerString += " \u00B1 " + rubric.maxError;
22155
+ }
22156
+ return answerString;
22157
+ }
22156
22158
  };
22157
22159
 
22158
22160
  const ParseTex = TexWrangler.parseTex;
@@ -22331,37 +22333,6 @@ class NumericInput extends React__namespace.Component {
22331
22333
  currentValue: props.currentValue
22332
22334
  };
22333
22335
  }
22334
- static getOneCorrectAnswerFromRubric(rubric) {
22335
- const correctAnswers = rubric.answers.filter(answer => answer.status === "correct");
22336
- const answerStrings = correctAnswers.map(answer => {
22337
- // Figure out how this answer is supposed to be
22338
- // displayed
22339
- let format = "decimal";
22340
- if (answer.answerForms && answer.answerForms[0]) {
22341
- // NOTE(johnsullivan): This isn't exactly ideal, but
22342
- // it does behave well for all the currently known
22343
- // problems. See D14742 for some discussion on
22344
- // alternate strategies.
22345
- format = answer.answerForms[0];
22346
- }
22347
-
22348
- // @ts-expect-error - TS2345 - Argument of type 'string' is not assignable to parameter of type 'MathFormat | undefined'.
22349
- let answerString = KhanMath.toNumericString(answer.value, format);
22350
- if (answer.maxError) {
22351
- answerString += " \u00B1 " +
22352
- // @ts-expect-error - TS2345 - Argument of type 'string' is not assignable to parameter of type 'MathFormat | undefined'.
22353
- KhanMath.toNumericString(answer.maxError, format);
22354
- }
22355
- return answerString;
22356
- });
22357
- if (answerStrings.length === 0) {
22358
- return;
22359
- }
22360
- return answerStrings[0];
22361
- }
22362
- static validate(userInput, rubric, strings) {
22363
- return numericInputValidator(userInput, rubric, strings);
22364
- }
22365
22336
  state = {
22366
22337
  // keeps track of the other set of values when switching
22367
22338
  // between 0 and finite solutions
@@ -22394,9 +22365,6 @@ class NumericInput extends React__namespace.Component {
22394
22365
  const allFormsAccepted = answerFormNames.length >= Object.keys(formExamples).length;
22395
22366
  return !noFormsAccepted && !allFormsAccepted;
22396
22367
  };
22397
- simpleValidate(rubric) {
22398
- return numericInputValidator(this.getUserInput(), rubric, this.context.strings);
22399
- }
22400
22368
  focus = () => {
22401
22369
  this.inputRef?.focus();
22402
22370
  return true;
@@ -22550,7 +22518,36 @@ var NumericInput$1 = {
22550
22518
  accessible: true,
22551
22519
  widget: NumericInput,
22552
22520
  transform: propsTransform,
22553
- isLintable: true
22521
+ isLintable: true,
22522
+ validator: numericInputValidator,
22523
+ getOneCorrectAnswerFromRubric(rubric) {
22524
+ const correctAnswers = rubric.answers.filter(answer => answer.status === "correct");
22525
+ const answerStrings = correctAnswers.map(answer => {
22526
+ // Figure out how this answer is supposed to be
22527
+ // displayed
22528
+ let format = "decimal";
22529
+ if (answer.answerForms && answer.answerForms[0]) {
22530
+ // NOTE(johnsullivan): This isn't exactly ideal, but
22531
+ // it does behave well for all the currently known
22532
+ // problems. See D14742 for some discussion on
22533
+ // alternate strategies.
22534
+ format = answer.answerForms[0];
22535
+ }
22536
+
22537
+ // @ts-expect-error - TS2345 - Argument of type 'string' is not assignable to parameter of type 'MathFormat | undefined'.
22538
+ let answerString = KhanMath.toNumericString(answer.value, format);
22539
+ if (answer.maxError) {
22540
+ answerString += " \u00B1 " +
22541
+ // @ts-expect-error - TS2345 - Argument of type 'string' is not assignable to parameter of type 'MathFormat | undefined'.
22542
+ KhanMath.toNumericString(answer.maxError, format);
22543
+ }
22544
+ return answerString;
22545
+ });
22546
+ if (answerStrings.length === 0) {
22547
+ return;
22548
+ }
22549
+ return answerStrings[0];
22550
+ }
22554
22551
  };
22555
22552
 
22556
22553
  /**
@@ -24371,16 +24368,15 @@ class Passage extends React__namespace.Component {
24371
24368
  highlights: {},
24372
24369
  linterContext: PerseusLinter.linterContextDefault
24373
24370
  };
24371
+
24372
+ // this just helps with TS weak typing when a Widget
24373
+ // doesn't implement any Widget methods
24374
+ isWidget = true;
24374
24375
  state = {
24375
24376
  nLines: null,
24376
24377
  startLineNumbersAfter: 0,
24377
24378
  stylesAreApplied: false
24378
24379
  };
24379
-
24380
- // TODO (LEMS-2396): remove validation logic from widgets that don't validate
24381
- static validate() {
24382
- return noopValidator();
24383
- }
24384
24380
  componentDidMount() {
24385
24381
  this._updateState();
24386
24382
 
@@ -24589,21 +24585,6 @@ class Passage extends React__namespace.Component {
24589
24585
  };
24590
24586
  }
24591
24587
 
24592
- /**
24593
- * Misc functions
24594
- *
24595
- * These are misc widget functions used for the widget API
24596
- */
24597
-
24598
- getUserInput() {
24599
- return null;
24600
- }
24601
-
24602
- // TODO (LEMS-2396): remove validation logic from widgets that don't validate
24603
- simpleValidate() {
24604
- return noopValidator();
24605
- }
24606
-
24607
24588
  /**
24608
24589
  * Rendering
24609
24590
  *
@@ -24747,7 +24728,8 @@ var Passage$1 = {
24747
24728
  transform: editorProps => {
24748
24729
  return ___default["default"].pick(editorProps, "passageTitle", "passageText", "footnotes", "showLineNumbers");
24749
24730
  },
24750
- isLintable: true
24731
+ isLintable: true,
24732
+ validator: () => noopValidator()
24751
24733
  };
24752
24734
 
24753
24735
  /**
@@ -24777,15 +24759,14 @@ class PassageRef extends React__namespace.Component {
24777
24759
  referenceNumber: 1,
24778
24760
  summaryText: ""
24779
24761
  };
24762
+
24763
+ // this just helps with TS weak typing when a Widget
24764
+ // doesn't implement any Widget methods
24765
+ isWidget = true;
24780
24766
  state = {
24781
24767
  lineRange: null,
24782
24768
  content: null
24783
24769
  };
24784
-
24785
- // TODO (LEMS-2396): remove validation logic from widgets that don't validate
24786
- static validate() {
24787
- return noopValidator();
24788
- }
24789
24770
  componentDidMount() {
24790
24771
  // TODO(scottgrant): This is a hack to remove the deprecated call to
24791
24772
  // this.isMounted() but is still considered an anti-pattern.
@@ -24804,11 +24785,6 @@ class PassageRef extends React__namespace.Component {
24804
24785
  window.removeEventListener("resize", this._throttledUpdateRange);
24805
24786
  this._isMounted = false;
24806
24787
  }
24807
-
24808
- // TODO (LEMS-2396): remove validation logic from widgets that don't validate
24809
- getUserInput() {
24810
- return null;
24811
- }
24812
24788
  change = (() => {
24813
24789
  var _this = this;
24814
24790
  return function () {
@@ -24838,11 +24814,6 @@ class PassageRef extends React__namespace.Component {
24838
24814
  }
24839
24815
  }
24840
24816
  };
24841
-
24842
- // TODO (LEMS-2396): remove validation logic from widgets that don't validate
24843
- simpleValidate() {
24844
- return noopValidator();
24845
- }
24846
24817
  render() {
24847
24818
  const {
24848
24819
  strings
@@ -24891,7 +24862,9 @@ var PassageRef$1 = {
24891
24862
  version: {
24892
24863
  major: 0,
24893
24864
  minor: 1
24894
- }
24865
+ },
24866
+ // TODO: things that aren't interactive shouldn't need validators
24867
+ validator: () => noopValidator()
24895
24868
  };
24896
24869
 
24897
24870
  /**
@@ -26310,9 +26283,6 @@ class Radio$1 extends React__namespace.Component {
26310
26283
  linterContext: PerseusLinter.linterContextDefault,
26311
26284
  showSolutions: "none"
26312
26285
  };
26313
- static validate(userInput, rubric, strings) {
26314
- return radioValidator(userInput, rubric, strings);
26315
- }
26316
26286
  static getUserInputFromProps(props) {
26317
26287
  // Return checked inputs in the form {choicesSelected: [bool]}. (Dear
26318
26288
  // future timeline implementers: this used to be {value: i} before
@@ -26499,9 +26469,6 @@ class Radio$1 extends React__namespace.Component {
26499
26469
  getUserInput() {
26500
26470
  return Radio$1.getUserInputFromProps(this.props);
26501
26471
  }
26502
- simpleValidate(rubric) {
26503
- return radioValidator(this.getUserInput(), rubric, this.context.strings);
26504
- }
26505
26472
 
26506
26473
  /**
26507
26474
  * Turn on rationale display for the currently selected choices. Note that
@@ -26514,7 +26481,7 @@ class Radio$1 extends React__namespace.Component {
26514
26481
  choiceStates
26515
26482
  } = this.props;
26516
26483
  if (choiceStates) {
26517
- const score = this.simpleValidate(rubric);
26484
+ const score = radioValidator(this.getUserInput(), rubric, this.context.strings);
26518
26485
  const widgetCorrect = score.type === "points" && score.total === score.earned;
26519
26486
  const newStates = choiceStates.map(state => ({
26520
26487
  ...state,
@@ -26794,7 +26761,8 @@ var Radio = {
26794
26761
  minor: 0
26795
26762
  },
26796
26763
  propUpgrades: propUpgrades$1,
26797
- isLintable: true
26764
+ isLintable: true,
26765
+ validator: radioValidator
26798
26766
  };
26799
26767
 
26800
26768
  /* eslint-disable import/no-named-as-default */
@@ -26837,9 +26805,6 @@ class Categorizer extends React__namespace.Component {
26837
26805
  state = {
26838
26806
  uniqueId: ___default["default"].uniqueId("perseus_radio_")
26839
26807
  };
26840
- static validate(userInput, rubric, strings) {
26841
- return categorizerValidator(userInput, rubric, strings);
26842
- }
26843
26808
  static getUserInputFromProps(props) {
26844
26809
  return {
26845
26810
  values: props.values
@@ -26865,9 +26830,6 @@ class Categorizer extends React__namespace.Component {
26865
26830
  this.change("values", values);
26866
26831
  this.props.trackInteraction();
26867
26832
  }
26868
- simpleValidate(rubric) {
26869
- return categorizerValidator(this.getUserInput(), rubric, this.context.strings);
26870
- }
26871
26833
  render() {
26872
26834
  const self = this;
26873
26835
 
@@ -27006,7 +26968,8 @@ var Categorizer$1 = {
27006
26968
  staticTransform: editorProps => {
27007
26969
  return ___default["default"].pick(editorProps, "items", "categories", "values", "randomizeItems");
27008
26970
  },
27009
- isLintable: true
26971
+ isLintable: true,
26972
+ validator: categorizerValidator
27010
26973
  };
27011
26974
 
27012
26975
  /**
@@ -27111,11 +27074,6 @@ class CSProgram extends React__namespace.Component {
27111
27074
  // optional message
27112
27075
  message: null
27113
27076
  };
27114
-
27115
- // The widget's grading function
27116
- static validate(state) {
27117
- return csProgramValidator(state);
27118
- }
27119
27077
  componentDidMount() {
27120
27078
  $__default["default"](window).on("message", this.handleMessageEvent);
27121
27079
  }
@@ -27157,9 +27115,6 @@ class CSProgram extends React__namespace.Component {
27157
27115
  message: this.props.message
27158
27116
  };
27159
27117
  }
27160
- simpleValidate() {
27161
- return csProgramValidator(this.getUserInput());
27162
- }
27163
27118
  render() {
27164
27119
  if (!this.props.programID) {
27165
27120
  return /*#__PURE__*/React__namespace.createElement("div", null);
@@ -27234,7 +27189,8 @@ var CSProgram$1 = {
27234
27189
  displayName: "CS Program",
27235
27190
  supportedAlignments: ["block", "full-width"],
27236
27191
  widget: CSProgram,
27237
- hidden: true
27192
+ hidden: true,
27193
+ validator: csProgramValidator
27238
27194
  };
27239
27195
 
27240
27196
  class Definition extends React__namespace.Component {
@@ -27244,18 +27200,9 @@ class Definition extends React__namespace.Component {
27244
27200
  definition: "definition goes here"
27245
27201
  };
27246
27202
 
27247
- // TODO (LEMS-2396): remove validation logic from widgets that don't validate
27248
- static validate() {
27249
- return noopValidator();
27250
- }
27251
- getUserInput() {
27252
- return {};
27253
- }
27254
-
27255
- // TODO (LEMS-2396): remove validation logic from widgets that don't validate
27256
- simpleValidate() {
27257
- return noopValidator();
27258
- }
27203
+ // this just helps with TS weak typing when a Widget
27204
+ // doesn't implement any Widget methods
27205
+ isWidget = true;
27259
27206
  render() {
27260
27207
  return /*#__PURE__*/React__namespace.createElement(DefinitionConsumer, null, _ref => {
27261
27208
  let {
@@ -27311,24 +27258,18 @@ var Definition$1 = {
27311
27258
  accessible: true,
27312
27259
  defaultAlignment: "inline",
27313
27260
  widget: Definition,
27314
- transform: x => x
27261
+ transform: x => x,
27262
+ // TODO: things that aren't interactive shouldn't need validators
27263
+ validator: () => noopValidator()
27315
27264
  };
27316
27265
 
27317
27266
  // The props are type `any` on purpose so that this can receive props
27318
27267
  // from any deprecated widget
27319
27268
  class DeprecatedStandin extends React__default["default"].Component {
27320
27269
  static contextType = PerseusI18nContext;
27321
- static validate() {
27322
- // Since this mean to replace an existing widget the learner
27323
- // WILL earn points for this widget
27324
- return noopValidator(1);
27325
- }
27326
- getUserInput() {
27327
- return {};
27328
- }
27329
- simpleValidate() {
27330
- return noopValidator(1);
27331
- }
27270
+ // this just helps with TS weak typing when a Widget
27271
+ // doesn't implement any Widget methods
27272
+ isWidget = true;
27332
27273
  render() {
27333
27274
  return /*#__PURE__*/React__default["default"].createElement("div", {
27334
27275
  style: {
@@ -27346,7 +27287,9 @@ var DeprecatedStandin$1 = {
27346
27287
  name: "deprecated-standin",
27347
27288
  displayName: "Deprecated Standin",
27348
27289
  widget: DeprecatedStandin,
27349
- hidden: true
27290
+ hidden: true,
27291
+ // TODO: things that aren't interactive shouldn't need validators
27292
+ validator: () => noopValidator(1)
27350
27293
  };
27351
27294
 
27352
27295
  function dropdownValidator(userInput, rubric) {
@@ -27373,9 +27316,6 @@ class Dropdown extends React__namespace.Component {
27373
27316
  placeholder: "",
27374
27317
  apiOptions: ApiOptions.defaults
27375
27318
  };
27376
- static validate(userInput, rubric) {
27377
- return dropdownValidator(userInput, rubric);
27378
- }
27379
27319
  focus = () => {
27380
27320
  // TODO(LP-10797): This focus() call doesn't do anything because our
27381
27321
  // root element is a <div> and that cannot be focused without a
@@ -27398,9 +27338,6 @@ class Dropdown extends React__namespace.Component {
27398
27338
  value: this.props.selected
27399
27339
  };
27400
27340
  }
27401
- simpleValidate(rubric) {
27402
- return dropdownValidator(this.getUserInput(), rubric);
27403
- }
27404
27341
  render() {
27405
27342
  const children = [/*#__PURE__*/React__namespace.createElement(wonderBlocksDropdown.OptionItem, {
27406
27343
  key: "placeholder",
@@ -27441,7 +27378,8 @@ var Dropdown$1 = {
27441
27378
  defaultAlignment: "inline-block",
27442
27379
  accessible: true,
27443
27380
  widget: Dropdown,
27444
- transform: optionsTransform
27381
+ transform: optionsTransform,
27382
+ validator: dropdownValidator
27445
27383
  };
27446
27384
 
27447
27385
  // transform = _.identity
@@ -27454,10 +27392,6 @@ function mediaQueryIsMatched(mediaQuery) {
27454
27392
  }
27455
27393
  class Explanation extends React__namespace.Component {
27456
27394
  static contextType = PerseusI18nContext;
27457
- state = {
27458
- expanded: false
27459
- };
27460
- _mounted = false;
27461
27395
  static defaultProps = {
27462
27396
  showPrompt: "Explain",
27463
27397
  hidePrompt: "Hide explanation",
@@ -27466,10 +27400,13 @@ class Explanation extends React__namespace.Component {
27466
27400
  linterContext: PerseusLinter.linterContextDefault
27467
27401
  };
27468
27402
 
27469
- // TODO (LEMS-2396): remove validation logic from widgets that don't validate
27470
- static validate() {
27471
- return noopValidator();
27472
- }
27403
+ // this just helps with TS weak typing when a Widget
27404
+ // doesn't implement any Widget methods
27405
+ isWidget = true;
27406
+ state = {
27407
+ expanded: false
27408
+ };
27409
+ _mounted = false;
27473
27410
  componentDidMount() {
27474
27411
  this._mounted = true;
27475
27412
  }
@@ -27491,14 +27428,6 @@ class Explanation extends React__namespace.Component {
27491
27428
  });
27492
27429
  this.props.trackInteraction();
27493
27430
  };
27494
- getUserInput() {
27495
- return {};
27496
- }
27497
-
27498
- // TODO (LEMS-2396): remove validation logic from widgets that don't validate
27499
- simpleValidate() {
27500
- return noopValidator();
27501
- }
27502
27431
  render() {
27503
27432
  const promptText = this.state.expanded ? this.props.hidePrompt : this.props.showPrompt;
27504
27433
  const caretIcon = this.state.expanded ? caretUp__default["default"] : caretDown__default["default"];
@@ -27604,7 +27533,9 @@ var Explanation$1 = {
27604
27533
  defaultAlignment: "inline",
27605
27534
  widget: Explanation,
27606
27535
  transform: ___default["default"].identity,
27607
- isLintable: true
27536
+ isLintable: true,
27537
+ // TODO: things that aren't interactive shouldn't need validators
27538
+ validator: () => noopValidator()
27608
27539
  };
27609
27540
 
27610
27541
  class GradedGroupAnswerBar extends React__namespace.Component {
@@ -29136,9 +29067,6 @@ class Grapher extends React__namespace.Component {
29136
29067
  horizHairline;
29137
29068
  vertHairline;
29138
29069
  static defaultProps = DEFAULT_GRAPHER_PROPS;
29139
- static validate(state, rubric) {
29140
- return grapherValidator(state, rubric);
29141
- }
29142
29070
  static getUserInputFromProps(props) {
29143
29071
  return props.plot;
29144
29072
  }
@@ -29241,9 +29169,6 @@ class Grapher extends React__namespace.Component {
29241
29169
  this.vertHairline.hide();
29242
29170
  }
29243
29171
  };
29244
- simpleValidate(rubric) {
29245
- return grapherValidator(this.getUserInput(), rubric);
29246
- }
29247
29172
  getUserInput() {
29248
29173
  return Grapher.getUserInputFromProps(this.props);
29249
29174
  }
@@ -29335,7 +29260,8 @@ var Grapher$1 = {
29335
29260
  hidden: true,
29336
29261
  widget: Grapher,
29337
29262
  transform: propTransform$2,
29338
- staticTransform: staticTransform$4
29263
+ staticTransform: staticTransform$4,
29264
+ validator: grapherValidator
29339
29265
  };
29340
29266
 
29341
29267
  // exports has no 'transform'
@@ -29364,6 +29290,13 @@ class Group extends React__namespace.Component {
29364
29290
  return change.apply(_this, args);
29365
29291
  };
29366
29292
  })();
29293
+ getUserInputMap() {
29294
+ return this.rendererRef?.getUserInputMap();
29295
+ }
29296
+
29297
+ /**
29298
+ * @deprecated getUserInputMap should be used for Groups
29299
+ */
29367
29300
  getUserInput() {
29368
29301
  return this.rendererRef?.getUserInput();
29369
29302
  }
@@ -29376,11 +29309,6 @@ class Group extends React__namespace.Component {
29376
29309
  // (all our changes were in state):
29377
29310
  return null;
29378
29311
  };
29379
- simpleValidate() {
29380
- return this.rendererRef?.score() ?? {
29381
- type: "invalid"
29382
- };
29383
- }
29384
29312
 
29385
29313
  // Mobile API:
29386
29314
  getInputPaths() {
@@ -29530,9 +29458,6 @@ class Iframe extends React__namespace.Component {
29530
29458
  allowFullScreen: false,
29531
29459
  allowTopNavigation: false
29532
29460
  };
29533
- static validate(state) {
29534
- return iframeValidator(state);
29535
- }
29536
29461
  componentDidMount() {
29537
29462
  $__default["default"](window).on("message", this.handleMessageEvent);
29538
29463
  }
@@ -29574,9 +29499,6 @@ class Iframe extends React__namespace.Component {
29574
29499
  return change.apply(_this, args);
29575
29500
  };
29576
29501
  })();
29577
- simpleValidate() {
29578
- return iframeValidator(this.getUserInput());
29579
- }
29580
29502
  render() {
29581
29503
  const style = {
29582
29504
  width: String(this.props.width),
@@ -29644,7 +29566,8 @@ var Iframe$1 = {
29644
29566
  displayName: "Iframe (deprecated)",
29645
29567
  widget: Iframe,
29646
29568
  // Let's not expose it to all content creators yet
29647
- hidden: true
29569
+ hidden: true,
29570
+ validator: iframeValidator
29648
29571
  };
29649
29572
 
29650
29573
  const defaultBoxSize = 400;
@@ -29673,10 +29596,9 @@ class ImageWidget extends React__namespace.Component {
29673
29596
  linterContext: PerseusLinter.linterContextDefault
29674
29597
  };
29675
29598
 
29676
- // TODO (LEMS-2396): remove validation logic from widgets that don't validate
29677
- static validate() {
29678
- return noopValidator();
29679
- }
29599
+ // this just helps with TS weak typing when a Widget
29600
+ // doesn't implement any Widget methods
29601
+ isWidget = true;
29680
29602
  change = (() => {
29681
29603
  var _this = this;
29682
29604
  return function () {
@@ -29686,14 +29608,6 @@ class ImageWidget extends React__namespace.Component {
29686
29608
  return change.apply(_this, args);
29687
29609
  };
29688
29610
  })();
29689
- getUserInput() {
29690
- return null;
29691
- }
29692
-
29693
- // TODO (LEMS-2396): remove validation logic from widgets that don't validate
29694
- simpleValidate() {
29695
- return noopValidator();
29696
- }
29697
29611
  render() {
29698
29612
  let image;
29699
29613
  const alt = this.props.caption === this.props.alt ? "" : this.props.alt;
@@ -29839,7 +29753,9 @@ var Image$1 = {
29839
29753
  supportedAlignments: editorAlignments,
29840
29754
  displayName: "Image",
29841
29755
  widget: ImageWidget,
29842
- isLintable: true
29756
+ isLintable: true,
29757
+ // TODO: things that aren't interactive shouldn't need validators
29758
+ validator: () => noopValidator()
29843
29759
  };
29844
29760
 
29845
29761
  /* eslint-disable @khanacademy/ts-no-error-suppressions */
@@ -29918,10 +29834,9 @@ class Interaction extends React__namespace.Component {
29918
29834
  elements: []
29919
29835
  };
29920
29836
 
29921
- // TODO (LEMS-2396): remove validation logic from widgets that don't validate
29922
- static validate() {
29923
- return noopValidator();
29924
- }
29837
+ // this just helps with TS weak typing when a Widget
29838
+ // doesn't implement any Widget methods
29839
+ isWidget = true;
29925
29840
  state = {
29926
29841
  variables: _getInitialVariables(this.props.elements),
29927
29842
  functions: _getInitialFunctions(this.props.elements)
@@ -30029,18 +29944,6 @@ class Interaction extends React__namespace.Component {
30029
29944
  return change.apply(_this, args);
30030
29945
  };
30031
29946
  })();
30032
- getUserInput() {
30033
- // TODO(eater): Perhaps we want to be able to record the state of the
30034
- // user's interaction. Unfortunately sending all the props will
30035
- // probably make the attempt payload too large. So for now, don't send
30036
- // anything.
30037
- return {};
30038
- }
30039
-
30040
- // TODO (LEMS-2396): remove validation logic from widgets that don't validate
30041
- simpleValidate() {
30042
- return noopValidator();
30043
- }
30044
29947
  render() {
30045
29948
  const range = this.props.graph.range;
30046
29949
  let labels = this.props.graph.labels;
@@ -30448,7 +30351,9 @@ var Interactive = {
30448
30351
  displayName: "Interaction",
30449
30352
  widget: Interaction,
30450
30353
  transform: ___default["default"].identity,
30451
- hidden: true
30354
+ hidden: true,
30355
+ // TODO: things that aren't interactive shouldn't need validators
30356
+ validator: () => noopValidator()
30452
30357
  };
30453
30358
 
30454
30359
  /* eslint-disable @khanacademy/ts-no-error-suppressions */
@@ -37605,9 +37510,6 @@ class LegacyInteractiveGraph extends React__namespace.Component {
37605
37510
  getUserInput() {
37606
37511
  return InteractiveGraph.getUserInputFromProps(this.props);
37607
37512
  }
37608
- simpleValidate(rubric) {
37609
- return interactiveGraphValidator(this.getUserInput(), rubric);
37610
- }
37611
37513
  focus = $__default["default"].noop;
37612
37514
  render() {
37613
37515
  const box = getInteractiveBoxFromSizeClass(this.props.containerSizeClass);
@@ -37680,9 +37582,6 @@ class InteractiveGraph extends React__namespace.Component {
37680
37582
  }
37681
37583
  throw new perseusCore.PerseusError("Cannot getUserInput from a graph that has never rendered", perseusCore.Errors.NotAllowed);
37682
37584
  }
37683
- simpleValidate(rubric) {
37684
- return interactiveGraphValidator(this.getUserInput(), rubric);
37685
- }
37686
37585
  render() {
37687
37586
  // Mafs shim
37688
37587
  const mafsFlags = this.props.apiOptions?.flags?.["mafs"];
@@ -38013,9 +37912,6 @@ class InteractiveGraph extends React__namespace.Component {
38013
37912
  const angle = getClockwiseAngle(coords, allowReflexAngles);
38014
37913
  return angle.toFixed(0) + "\u00B0 angle" + " at (" + coords[1].join(", ") + ")";
38015
37914
  }
38016
- static validate(userInput, rubric) {
38017
- return interactiveGraphValidator(userInput, rubric);
38018
- }
38019
37915
  static getUserInputFromProps(props) {
38020
37916
  return props.graph;
38021
37917
  }
@@ -38039,7 +37935,7 @@ function shouldUseMafs(mafsFlags, graph) {
38039
37935
  // TODO(benchristel): add a feature flag for the "unlimited"
38040
37936
  // case once we've implemented polygon graphs with unlimited
38041
37937
  // sides
38042
- return false;
37938
+ return Boolean(mafsFlags["unlimited-polygon"]);
38043
37939
  }
38044
37940
  return Boolean(mafsFlags["polygon"]);
38045
37941
  default:
@@ -38052,7 +37948,8 @@ var InteractiveGraph$1 = {
38052
37948
  name: "interactive-graph",
38053
37949
  displayName: "Interactive graph (Assessments only)",
38054
37950
  widget: InteractiveGraph,
38055
- staticTransform: staticTransform$3
37951
+ staticTransform: staticTransform$3,
37952
+ validator: interactiveGraphValidator
38056
37953
  };
38057
37954
 
38058
37955
  const black = "#000000";
@@ -39428,9 +39325,6 @@ class LabelImage extends React__namespace.Component {
39428
39325
  // The rendered markers on the question image for labeling.
39429
39326
  _markers;
39430
39327
  _mounted = false;
39431
- static validate(state, rubric) {
39432
- return labelImageValidator(state);
39433
- }
39434
39328
 
39435
39329
  /**
39436
39330
  * Test whether point is contained within triangle.
@@ -39623,9 +39517,6 @@ class LabelImage extends React__namespace.Component {
39623
39517
  componentWillUnmount() {
39624
39518
  this._mounted = false;
39625
39519
  }
39626
- simpleValidate(rubric) {
39627
- return labelImageValidator(this.getUserInput());
39628
- }
39629
39520
  getUserInput() {
39630
39521
  const {
39631
39522
  markers
@@ -39991,19 +39882,13 @@ const LabelImageWithDependencies = /*#__PURE__*/React__namespace.forwardRef((pro
39991
39882
  analytics: deps.analytics
39992
39883
  }, props));
39993
39884
  });
39994
-
39995
- // HACK: Propogate "static" methods onto our wrapper component.
39996
- // In the future we should adjust client apps to not depend on these static
39997
- // methods and instead adjust Peresus to provide these facilities through
39998
- // instance methods on our Renderers.
39999
- // @ts-expect-error - TS2339 - Property 'validate' does not exist on type
40000
- LabelImageWithDependencies.validate = labelImageValidator;
40001
39885
  var LabelImage$1 = {
40002
39886
  name: "label-image",
40003
39887
  displayName: "Label Image",
40004
39888
  widget: LabelImageWithDependencies,
40005
39889
  accessible: true,
40006
- isLintable: true
39890
+ isLintable: true,
39891
+ validator: labelImageValidator
40007
39892
  };
40008
39893
 
40009
39894
  /* eslint-disable @khanacademy/ts-no-error-suppressions */
@@ -40745,9 +40630,6 @@ class Matcher extends React__namespace.Component {
40745
40630
  rightHeight: 0,
40746
40631
  texRendererLoaded: false
40747
40632
  };
40748
- static validate(state, rubric) {
40749
- return matcherValidator(state, rubric);
40750
- }
40751
40633
  changeAndTrack = e => {
40752
40634
  this.props.onChange(e);
40753
40635
  this.props.trackInteraction();
@@ -40799,9 +40681,6 @@ class Matcher extends React__namespace.Component {
40799
40681
  // @ts-expect-error - TS2339 - Property 'moveOptionToIndex' does not exist on type 'ReactInstance'.
40800
40682
  this.refs.right.moveOptionToIndex(option, index);
40801
40683
  };
40802
- simpleValidate(rubric) {
40803
- return matcherValidator(this.getUserInput(), rubric);
40804
- }
40805
40684
  render() {
40806
40685
  // To minimize layout shift, we display a spinner until our math
40807
40686
  // renderer is ready to render the math inside the matcher. To
@@ -40928,7 +40807,8 @@ var Matcher$1 = {
40928
40807
  name: "matcher",
40929
40808
  displayName: "Matcher (two column)",
40930
40809
  widget: Matcher,
40931
- isLintable: true
40810
+ isLintable: true,
40811
+ validator: matcherValidator
40932
40812
  };
40933
40813
 
40934
40814
  function matrixValidator(state, rubric, strings) {
@@ -41069,9 +40949,6 @@ class Matrix extends React__namespace.Component {
41069
40949
  apiOptions: ApiOptions.defaults,
41070
40950
  linterContext: PerseusLinter.linterContextDefault
41071
40951
  };
41072
- static validate(state, rubric, strings) {
41073
- return matrixValidator(state, rubric, strings);
41074
- }
41075
40952
  state = {
41076
40953
  enterTheMatrix: 0
41077
40954
  };
@@ -41214,9 +41091,6 @@ class Matrix extends React__namespace.Component {
41214
41091
  answers: this.props.answers
41215
41092
  };
41216
41093
  }
41217
- simpleValidate(rubric) {
41218
- return matrixValidator(this.getUserInput(), rubric, this.context.strings);
41219
- }
41220
41094
  render() {
41221
41095
  // Set the input sizes through JS so we can control the size of the
41222
41096
  // brackets. (If we set them in CSS we won't know values until the
@@ -41401,7 +41275,8 @@ var Matrix$1 = {
41401
41275
  widget: Matrix,
41402
41276
  transform: propTransform$1,
41403
41277
  staticTransform: staticTransform$2,
41404
- isLintable: true
41278
+ isLintable: true,
41279
+ validator: matrixValidator
41405
41280
  };
41406
41281
 
41407
41282
  const defaultImage = {
@@ -41410,7 +41285,6 @@ const defaultImage = {
41410
41285
  left: 0
41411
41286
  };
41412
41287
  class Measurer extends React__namespace.Component {
41413
- static validate = noopValidator;
41414
41288
  static defaultProps = {
41415
41289
  box: [480, 480],
41416
41290
  image: {},
@@ -41423,6 +41297,10 @@ class Measurer extends React__namespace.Component {
41423
41297
  rulerPixels: 40,
41424
41298
  rulerLength: 10
41425
41299
  };
41300
+
41301
+ // this just helps with TS weak typing when a Widget
41302
+ // doesn't implement any Widget methods
41303
+ isWidget = true;
41426
41304
  state = {};
41427
41305
  ruler;
41428
41306
  protractor;
@@ -41475,12 +41353,6 @@ class Measurer extends React__namespace.Component {
41475
41353
  });
41476
41354
  }
41477
41355
  }
41478
- getUserInput() {
41479
- return {};
41480
- }
41481
- simpleValidate() {
41482
- return noopValidator(1);
41483
- }
41484
41356
  render() {
41485
41357
  const image = ___default["default"].extend({}, defaultImage, this.props.image);
41486
41358
 
@@ -41527,7 +41399,9 @@ var Measurer$1 = {
41527
41399
  major: 1,
41528
41400
  minor: 0
41529
41401
  },
41530
- propUpgrades: propUpgrades
41402
+ propUpgrades: propUpgrades,
41403
+ // TODO: things that aren't interactive shouldn't need validators
41404
+ validator: () => noopValidator(1)
41531
41405
  };
41532
41406
 
41533
41407
  /**
@@ -42400,18 +42274,9 @@ class MoleculeWidget extends React__namespace.Component {
42400
42274
  rotationAngle: 0
42401
42275
  };
42402
42276
 
42403
- // TODO (LEMS-2396): remove validation logic from widgets that don't validate
42404
- simpleValidate() {
42405
- return noopValidator();
42406
- }
42407
- getUserInput() {
42408
- return [];
42409
- }
42410
-
42411
- // TODO (LEMS-2396): remove validation logic from widgets that don't validate
42412
- validate() {
42413
- return noopValidator();
42414
- }
42277
+ // this just helps with TS weak typing when a Widget
42278
+ // doesn't implement any Widget methods
42279
+ isWidget = true;
42415
42280
  render() {
42416
42281
  return /*#__PURE__*/React__namespace.createElement(Molecule, {
42417
42282
  widgetId: this.props.widgetId,
@@ -42424,7 +42289,8 @@ var Molecule$1 = {
42424
42289
  name: "molecule-renderer",
42425
42290
  displayName: "Molecule renderer",
42426
42291
  hidden: true,
42427
- widget: MoleculeWidget
42292
+ widget: MoleculeWidget,
42293
+ validator: () => noopValidator()
42428
42294
  };
42429
42295
 
42430
42296
  function numberLineValidator(state, rubric) {
@@ -42569,18 +42435,16 @@ const TickMarks = Graphie.createSimpleClass((graphie, props) => {
42569
42435
  results.push(graphie.line([x, -0.2], [x, 0.2]));
42570
42436
  const labelTicks = props.labelTicks;
42571
42437
  if (labelTicks || props.labelStyle === "decimal ticks") {
42572
- results.push(_label(graphie, props.labelStyle, x, x, base));
42438
+ if (x === leftLabel || x === rightLabel) {
42439
+ results.push(graphie.style({
42440
+ color: KhanColors.BLUE
42441
+ }, () => _label(graphie, props.labelStyle, x, x, base)));
42442
+ } else {
42443
+ results.push(_label(graphie, props.labelStyle, x, x, base));
42444
+ }
42573
42445
  }
42574
42446
  }
42575
42447
 
42576
- // Render the text labels
42577
- results.push(graphie.style(props.isMobile ? {
42578
- color: KhanColors.BLUE
42579
- } : {}, () => _label(graphie, props.labelStyle, leftLabel, leftLabel, base)));
42580
- results.push(graphie.style(props.isMobile ? {
42581
- color: KhanColors.BLUE
42582
- } : {}, () => _label(graphie, props.labelStyle, rightLabel, rightLabel, base)));
42583
-
42584
42448
  // Render the labels' lines
42585
42449
  graphie.style({
42586
42450
  stroke: KhanColors.BLUE,
@@ -42613,9 +42477,6 @@ class NumberLine extends React__namespace.Component {
42613
42477
  state = {
42614
42478
  numDivisionsEmpty: false
42615
42479
  };
42616
- static validate(state, rubric) {
42617
- return numberLineValidator(state, rubric);
42618
- }
42619
42480
  change = (() => {
42620
42481
  var _this = this;
42621
42482
  return function () {
@@ -42908,9 +42769,6 @@ class NumberLine extends React__namespace.Component {
42908
42769
  divisionRange: this.props.divisionRange
42909
42770
  };
42910
42771
  }
42911
- simpleValidate(rubric) {
42912
- return numberLineValidator(this.getUserInput(), rubric);
42913
- }
42914
42772
  render() {
42915
42773
  const {
42916
42774
  strings
@@ -43014,7 +42872,8 @@ var NumberLine$1 = {
43014
42872
  displayName: "Number line",
43015
42873
  widget: NumberLine,
43016
42874
  transform: numberLineTransform,
43017
- staticTransform: staticTransform$1
42875
+ staticTransform: staticTransform$1,
42876
+ validator: numberLineValidator
43018
42877
  };
43019
42878
 
43020
42879
  function ordererValidator(userInput, rubric) {
@@ -43224,9 +43083,6 @@ class Orderer extends React__namespace.Component {
43224
43083
  animateTo: null,
43225
43084
  grabPos: null
43226
43085
  };
43227
- static validate(userInput, rubric) {
43228
- return ordererValidator(userInput, rubric);
43229
- }
43230
43086
  UNSAFE_componentWillReceiveProps(nextProps) {
43231
43087
  if (!___default["default"].isEqual(this.props.current, nextProps.current)) {
43232
43088
  this.setState({
@@ -43466,9 +43322,6 @@ class Orderer extends React__namespace.Component {
43466
43322
  })
43467
43323
  };
43468
43324
  }
43469
- simpleValidate(rubric) {
43470
- return ordererValidator(this.getUserInput(), rubric);
43471
- }
43472
43325
  render() {
43473
43326
  // This is the card we are currently dragging
43474
43327
  const dragging = this.state.dragging && /*#__PURE__*/React__namespace.createElement(Card
@@ -43578,7 +43431,8 @@ var Orderer$1 = {
43578
43431
  displayName: "Orderer",
43579
43432
  hidden: true,
43580
43433
  widget: Orderer,
43581
- isLintable: true
43434
+ isLintable: true,
43435
+ validator: ordererValidator
43582
43436
  };
43583
43437
 
43584
43438
  /* eslint-disable @khanacademy/ts-no-error-suppressions */
@@ -43589,15 +43443,9 @@ class PassageRefTarget extends React__namespace.Component {
43589
43443
  linterContext: PerseusLinter.linterContextDefault
43590
43444
  };
43591
43445
 
43592
- // TODO (LEMS-2396): remove validation logic from widgets that don't validate
43593
- static validate() {
43594
- return noopValidator();
43595
- }
43596
-
43597
- // TODO passage-ref-target isn't interactive; remove
43598
- getUserInput() {
43599
- return null;
43600
- }
43446
+ // this just helps with TS weak typing when a Widget
43447
+ // doesn't implement any Widget methods
43448
+ isWidget = true;
43601
43449
 
43602
43450
  // TODO passage-ref-target isn't interactive; remove
43603
43451
  change = (() => {
@@ -43609,11 +43457,6 @@ class PassageRefTarget extends React__namespace.Component {
43609
43457
  return change.apply(_this, args);
43610
43458
  };
43611
43459
  })();
43612
-
43613
- // TODO (LEMS-2396): remove validation logic from widgets that don't validate
43614
- simpleValidate() {
43615
- return noopValidator();
43616
- }
43617
43460
  render() {
43618
43461
  return /*#__PURE__*/React__namespace.createElement(Renderer, {
43619
43462
  content: this.props.content
@@ -43642,7 +43485,9 @@ var PassageRefTarget$1 = {
43642
43485
  major: 0,
43643
43486
  minor: 0
43644
43487
  },
43645
- isLintable: true
43488
+ isLintable: true,
43489
+ // TODO: things that aren't interactive shouldn't need validators
43490
+ validator: () => noopValidator()
43646
43491
  };
43647
43492
 
43648
43493
  /**
@@ -43654,6 +43499,10 @@ class PhetSimulation extends React__namespace.Component {
43654
43499
  static contextType = PerseusI18nContext;
43655
43500
  iframeRef = /*#__PURE__*/React__namespace.createRef();
43656
43501
  locale;
43502
+
43503
+ // this just helps with TS weak typing when a Widget
43504
+ // doesn't implement any Widget methods
43505
+ isWidget = true;
43657
43506
  state = {
43658
43507
  url: null,
43659
43508
  banner: null
@@ -43672,11 +43521,6 @@ class PhetSimulation extends React__namespace.Component {
43672
43521
  }
43673
43522
  }
43674
43523
 
43675
- // TODO (LEMS-2396): remove validation logic from widgets that don't validate
43676
- getUserInput() {
43677
- return null;
43678
- }
43679
-
43680
43524
  // kaLocales and PhET locales use different formats and abbreviations.
43681
43525
  // PhET accepts different formats, i.e. kaLocale's hyphens, but it does not accept
43682
43526
  // different abbreviations, so in points of divergence of abbreviations, we need to
@@ -43896,9 +43740,6 @@ class Plotter extends React__namespace.Component {
43896
43740
  // bottom label.
43897
43741
  categoryHeights: {}
43898
43742
  };
43899
- static validate(userInput, rubric) {
43900
- return plotterValidator(userInput, rubric);
43901
- }
43902
43743
  componentDidMount() {
43903
43744
  this._isMounted = true;
43904
43745
  this.setupGraphie(this.state);
@@ -44643,9 +44484,6 @@ class Plotter extends React__namespace.Component {
44643
44484
  getUserInput() {
44644
44485
  return this.state.values;
44645
44486
  }
44646
- simpleValidate(rubric) {
44647
- return plotterValidator(this.getUserInput(), rubric);
44648
- }
44649
44487
  render() {
44650
44488
  // TODO(kevinb) actually compute the size of the graphie correctly and
44651
44489
  // make it that size so we don't have to add extra padding. The value
@@ -44671,7 +44509,8 @@ var Plotter$1 = {
44671
44509
  displayName: "Plotter",
44672
44510
  hidden: true,
44673
44511
  widget: Plotter,
44674
- staticTransform: staticTransform
44512
+ staticTransform: staticTransform,
44513
+ validator: plotterValidator
44675
44514
  };
44676
44515
 
44677
44516
  /**
@@ -44766,9 +44605,6 @@ class Sorter extends React__namespace.Component {
44766
44605
  onChange: function () {},
44767
44606
  linterContext: PerseusLinter.linterContextDefault
44768
44607
  };
44769
- static validate(userInput, rubric) {
44770
- return sorterValidator(userInput, rubric);
44771
- }
44772
44608
  state = {
44773
44609
  changed: false
44774
44610
  };
@@ -44811,9 +44647,6 @@ class Sorter extends React__namespace.Component {
44811
44647
  // @ts-expect-error - TS2339 - Property 'moveOptionToIndex' does not exist on type 'ReactInstance'.
44812
44648
  this.refs.sortable.moveOptionToIndex(option, index);
44813
44649
  };
44814
- simpleValidate(rubric) {
44815
- return sorterValidator(this.getUserInput(), rubric);
44816
- }
44817
44650
  render() {
44818
44651
  const options = shuffle(this.props.correct, this.props.problemNum, /* ensurePermuted */true);
44819
44652
  const {
@@ -44839,7 +44672,8 @@ var Sorter$1 = {
44839
44672
  name: "sorter",
44840
44673
  displayName: "Sorter",
44841
44674
  widget: Sorter,
44842
- isLintable: true
44675
+ isLintable: true,
44676
+ validator: sorterValidator
44843
44677
  };
44844
44678
 
44845
44679
  function tableValidator(state, rubric, strings) {
@@ -44947,9 +44781,6 @@ class Table extends React__namespace.Component {
44947
44781
  linterContext: PerseusLinter.linterContextDefault
44948
44782
  };
44949
44783
  }();
44950
- static validate(state, rubric, strings) {
44951
- return tableValidator(state, rubric, strings);
44952
- }
44953
44784
  _getRows = () => {
44954
44785
  return this.props.answers.length;
44955
44786
  };
@@ -44978,9 +44809,6 @@ class Table extends React__namespace.Component {
44978
44809
  headers: headers
44979
44810
  });
44980
44811
  };
44981
- simpleValidate(rubric) {
44982
- return tableValidator(this.getUserInput(), rubric, this.context.strings);
44983
- }
44984
44812
  _handleFocus = inputPath => {
44985
44813
  this.props.onFocus(inputPath);
44986
44814
  };
@@ -45124,7 +44952,8 @@ var Table$1 = {
45124
44952
  widget: Table,
45125
44953
  transform: propTransform,
45126
44954
  hidden: true,
45127
- isLintable: true
44955
+ isLintable: true,
44956
+ validator: tableValidator
45128
44957
  };
45129
44958
 
45130
44959
  /**
@@ -45210,22 +45039,9 @@ const IS_VIMEO = /(vimeo\.com)/;
45210
45039
  */
45211
45040
  class Video extends React__namespace.Component {
45212
45041
  static contextType = PerseusI18nContext;
45213
- /**
45214
- * This is the widget's grading function.
45215
- * Points for videos are tallied by the embedded video itself, in the case
45216
- * of Khan Academy videos.
45217
- */ // TODO (LEMS-2396): remove validation logic from widgets that don't validate
45218
- static validate() {
45219
- return noopValidator();
45220
- }
45221
- getUserInput = () => {
45222
- return null;
45223
- };
45224
-
45225
- // TODO (LEMS-2396): remove validation logic from widgets that don't validate
45226
- simpleValidate() {
45227
- return noopValidator();
45228
- }
45042
+ // this just helps with TS weak typing when a Widget
45043
+ // doesn't implement any Widget methods
45044
+ isWidget = true;
45229
45045
  change = (() => {
45230
45046
  var _this = this;
45231
45047
  return function () {
@@ -45294,7 +45110,9 @@ var Video$1 = {
45294
45110
  displayName: "Video",
45295
45111
  defaultAlignment: "block",
45296
45112
  supportedAlignments: ["block", "float-left", "float-right", "full-width"],
45297
- widget: Video
45113
+ widget: Video,
45114
+ // TODO: things that aren't interactive shouldn't need validators
45115
+ validator: () => noopValidator()
45298
45116
  };
45299
45117
 
45300
45118
  /* eslint-disable import/no-named-as-default */
@@ -45338,7 +45156,7 @@ const init = function (options) {
45338
45156
 
45339
45157
  // This file is processed by a Rollup plugin (replace) to inject the production
45340
45158
  const libName = "@khanacademy/perseus";
45341
- const libVersion = "36.1.2";
45159
+ const libVersion = "37.0.1";
45342
45160
  perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
45343
45161
 
45344
45162
  const apiVersion = {
@@ -46064,7 +45882,7 @@ class ServerItemRenderer extends React__namespace.Component {
46064
45882
  });
46065
45883
 
46066
45884
  // Call the interactionCallback, if it exists, with the current user input data
46067
- this.props.apiOptions?.interactionCallback?.(this.questionRenderer.getUserInputForWidgets());
45885
+ this.props.apiOptions?.interactionCallback?.(this.questionRenderer.getUserInputMap());
46068
45886
  };
46069
45887
  focus() {
46070
45888
  return this.questionRenderer.focus();
@@ -48597,9 +48415,7 @@ const getCorrectAnswerForWidgetId = (widgetId, itemData) => {
48597
48415
  const rubric = itemData.question.widgets[widgetId].options;
48598
48416
  const widgetMap = getWidgetsMapFromItemData(itemData);
48599
48417
  const widgetType = getWidgetTypeByWidgetId(widgetId, widgetMap);
48600
- const widget = getWidget(widgetType);
48601
-
48602
- // @ts-expect-error - TS2339 - Property 'getOneCorrectAnswerFromRubric' does not exist on type 'ComponentType<any>'.
48418
+ const widget = getWidgetExport(widgetType);
48603
48419
  return widget?.getOneCorrectAnswerFromRubric?.(rubric);
48604
48420
  };
48605
48421
 
@@ -48690,11 +48506,12 @@ function parsePerseusItem(json) {
48690
48506
  throw new Error("Something went wrong.");
48691
48507
  }
48692
48508
  function buildRandomString() {
48509
+ let capitalize = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
48693
48510
  let randomString = "";
48694
48511
  const randomLength = Math.floor(Math.random() * 8) + 3;
48695
48512
  for (let i = 0; i < randomLength; i++) {
48696
48513
  const randomLetter = String.fromCharCode(97 + Math.floor(Math.random() * 26));
48697
- randomString += randomLetter;
48514
+ randomString += capitalize && i === 0 ? randomLetter.toUpperCase() : randomLetter;
48698
48515
  }
48699
48516
  return randomString;
48700
48517
  }
@@ -48702,9 +48519,12 @@ function buildRandomPhrase() {
48702
48519
  const phrases = [];
48703
48520
  const randomLength = Math.floor(Math.random() * 10) + 5;
48704
48521
  for (let i = 0; i < randomLength; i++) {
48705
- phrases.push(buildRandomString());
48522
+ phrases.push(buildRandomString(i === 0));
48706
48523
  }
48707
- return phrases.join(" ");
48524
+ const modifierStart = ["**", "$"];
48525
+ const modifierEnd = ["**", "$"];
48526
+ const modifierIndex = Math.floor(Math.random() * modifierStart.length);
48527
+ return `${modifierStart[modifierIndex]}${phrases.join(" ")}${modifierEnd[modifierIndex]}`;
48708
48528
  }
48709
48529
  function buildTestData(testObject) {
48710
48530
  return `{"data":{"assessmentItem":{"__typename":"AssessmentItemOrError","error":null,"item":{"__typename":"AssessmentItem","id":"x890b3c70f3e8f4a6","itemData":"${testObject}","problemType":"Type 1","sha":"c7284a3ad65214b4e62bccce236d92f7f5d35941"}}}}`;