@khanacademy/perseus 36.1.1 → 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 (57) 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 +523 -660
  5. package/dist/es/index.js.map +1 -1
  6. package/dist/extra-widgets.d.ts +1 -0
  7. package/dist/index.d.ts +1 -0
  8. package/dist/index.js +489 -635
  9. package/dist/index.js.map +1 -1
  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 +3 -1
  13. package/dist/types.d.ts +11 -3
  14. package/dist/util/graphie.d.ts +1 -1
  15. package/dist/util.d.ts +4 -0
  16. package/dist/validation.types.d.ts +19 -9
  17. package/dist/widgets/categorizer/categorizer.d.ts +3 -5
  18. package/dist/widgets/cs-program/cs-program.d.ts +4 -8
  19. package/dist/widgets/definition/definition.d.ts +3 -4
  20. package/dist/widgets/deprecated-standin/deprecated-standin.d.ts +35 -0
  21. package/dist/widgets/deprecated-standin/index.d.ts +1 -37
  22. package/dist/widgets/dropdown/dropdown.d.ts +2 -3
  23. package/dist/widgets/explanation/explanation.d.ts +4 -5
  24. package/dist/widgets/expression/expression.d.ts +4 -8
  25. package/dist/widgets/graded-group/graded-group.d.ts +1 -0
  26. package/dist/widgets/graded-group-set/graded-group-set.d.ts +1 -0
  27. package/dist/widgets/grapher/grapher.d.ts +2 -3
  28. package/dist/widgets/group/group.d.ts +8 -5
  29. package/dist/widgets/iframe/iframe.d.ts +2 -3
  30. package/dist/widgets/image/image.d.ts +3 -4
  31. package/dist/widgets/input-number/input-number.d.ts +2 -5
  32. package/dist/widgets/interaction/interaction.d.ts +3 -4
  33. package/dist/widgets/interactive-graph.d.ts +3 -5
  34. package/dist/widgets/interactive-graphs/math/angles.d.ts +3 -1
  35. package/dist/widgets/interactive-graphs/math/index.d.ts +1 -1
  36. package/dist/widgets/label-image/label-image.d.ts +3 -4
  37. package/dist/widgets/matcher/matcher.d.ts +2 -3
  38. package/dist/widgets/matrix/matrix.d.ts +3 -5
  39. package/dist/widgets/measurer/measurer.d.ts +2 -4
  40. package/dist/widgets/molecule/molecule.d.ts +2 -4
  41. package/dist/widgets/number-line/number-line.d.ts +3 -4
  42. package/dist/widgets/numeric-input/numeric-input.d.ts +2 -3
  43. package/dist/widgets/orderer/orderer.d.ts +2 -3
  44. package/dist/widgets/passage/passage.d.ts +3 -9
  45. package/dist/widgets/passage-ref/passage-ref.d.ts +3 -4
  46. package/dist/widgets/passage-ref-target/passage-ref-target.d.ts +2 -4
  47. package/dist/widgets/phet-simulation/phet-simulation.d.ts +2 -2
  48. package/dist/widgets/plotter/plotter.d.ts +2 -3
  49. package/dist/widgets/python-program/python-program.d.ts +1 -0
  50. package/dist/widgets/radio/radio-component.d.ts +2 -5
  51. package/dist/widgets/radio/radio.d.ts +1 -0
  52. package/dist/widgets/sorter/sorter.d.ts +2 -3
  53. package/dist/widgets/table/table.d.ts +3 -5
  54. package/dist/widgets/video/video.d.ts +2 -8
  55. package/dist/widgets.d.ts +2 -1
  56. package/package.json +9 -9
  57. 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 : {
@@ -10070,7 +10045,10 @@ const GraphUtils = {
10070
10045
  return point.distanceToPoint(point1, point2);
10071
10046
  },
10072
10047
  // Find the angle in degrees between two or three points
10073
- findAngle: function (point1, point2, vertex) {
10048
+ // This function is deprecated as it has several issues calculating
10049
+ // correctly when dealing with reflex angles or the position of point2
10050
+ // (LEMS-2202) Remove this function while removing the Legacy Interactive Graph.
10051
+ findAngleDeprecated: function (point1, point2, vertex) {
10074
10052
  if (vertex === undefined) {
10075
10053
  const x = point1[0] - point2[0];
10076
10054
  const y = point1[1] - point2[1];
@@ -10079,7 +10057,7 @@ const GraphUtils = {
10079
10057
  }
10080
10058
  return (180 + Math.atan2(-y, -x) * 180 / Math.PI + 360) % 360;
10081
10059
  }
10082
- return GraphUtils.findAngle(point1, vertex) - GraphUtils.findAngle(point2, vertex);
10060
+ return GraphUtils.findAngleDeprecated(point1, vertex) - GraphUtils.findAngleDeprecated(point2, vertex);
10083
10061
  },
10084
10062
  graphs: {}
10085
10063
  };
@@ -11095,8 +11073,8 @@ _.extend(GraphUtils.Graphie.prototype, {
11095
11073
  p1 = options.point3;
11096
11074
  p3 = options.point1;
11097
11075
  }
11098
- const startAngle = GraphUtils.findAngle(p1, vertex);
11099
- const endAngle = GraphUtils.findAngle(p3, vertex);
11076
+ const startAngle = GraphUtils.findAngleDeprecated(p1, vertex);
11077
+ const endAngle = GraphUtils.findAngleDeprecated(p3, vertex);
11100
11078
  const angle = (endAngle + 360 - startAngle) % 360;
11101
11079
  const halfAngle = (startAngle + angle / 2) % 360;
11102
11080
  const sPadding = 5 * options.pushOut;
@@ -11263,15 +11241,15 @@ _.extend(GraphUtils.Graphie.prototype, {
11263
11241
  let angle = 135;
11264
11242
  let halfAngle;
11265
11243
  if (p1 && p3) {
11266
- const startAngle = GraphUtils.findAngle(p1, vertex);
11267
- const endAngle = GraphUtils.findAngle(p3, vertex);
11244
+ const startAngle = GraphUtils.findAngleDeprecated(p1, vertex);
11245
+ const endAngle = GraphUtils.findAngleDeprecated(p3, vertex);
11268
11246
  angle = (endAngle + 360 - startAngle) % 360;
11269
11247
  halfAngle = (startAngle + angle / 2 + 180) % 360;
11270
11248
  } else if (p1) {
11271
- const parallelAngle = GraphUtils.findAngle(vertex, p1);
11249
+ const parallelAngle = GraphUtils.findAngleDeprecated(vertex, p1);
11272
11250
  halfAngle = parallelAngle + 90;
11273
11251
  } else if (p3) {
11274
- const parallelAngle = GraphUtils.findAngle(p3, vertex);
11252
+ const parallelAngle = GraphUtils.findAngleDeprecated(p3, vertex);
11275
11253
  halfAngle = parallelAngle + 90;
11276
11254
  } else {
11277
11255
  // Standalone point
@@ -11477,7 +11455,7 @@ _.extend(GraphUtils.Graphie.prototype, {
11477
11455
  const vertex = constraints.fixedAngle.vertex.coord || constraints.fixedAngle.vertex;
11478
11456
  const ref = constraints.fixedAngle.ref.coord || constraints.fixedAngle.ref;
11479
11457
  const distPoint = constraints.fixedDistance.point.coord || constraints.fixedDistance.point;
11480
- const constrainedAngle = (constraints.fixedAngle.angle + GraphUtils.findAngle(ref, vertex)) * Math.PI / 180;
11458
+ const constrainedAngle = (constraints.fixedAngle.angle + GraphUtils.findAngleDeprecated(ref, vertex)) * Math.PI / 180;
11481
11459
  const length = constraints.fixedDistance.dist;
11482
11460
  newCoord[0] = length * Math.cos(constrainedAngle) + distPoint[0];
11483
11461
  newCoord[1] = length * Math.sin(constrainedAngle) + distPoint[1];
@@ -11486,8 +11464,8 @@ _.extend(GraphUtils.Graphie.prototype, {
11486
11464
  } else if (typeof constraints.fixedAngle.angle === "number") {
11487
11465
  const vertex = constraints.fixedAngle.vertex.coord || constraints.fixedAngle.vertex;
11488
11466
  const ref = constraints.fixedAngle.ref.coord || constraints.fixedAngle.ref;
11489
- const constrainedAngle = (constraints.fixedAngle.angle + GraphUtils.findAngle(ref, vertex)) * Math.PI / 180;
11490
- const angle = GraphUtils.findAngle(coord, vertex) * Math.PI / 180;
11467
+ const constrainedAngle = (constraints.fixedAngle.angle + GraphUtils.findAngleDeprecated(ref, vertex)) * Math.PI / 180;
11468
+ const angle = GraphUtils.findAngleDeprecated(coord, vertex) * Math.PI / 180;
11491
11469
  const distance = GraphUtils.getDistance(coord, vertex);
11492
11470
  let length = distance * Math.cos(constrainedAngle - angle);
11493
11471
  length = length < 1.0 ? 1.0 : length;
@@ -11497,7 +11475,7 @@ _.extend(GraphUtils.Graphie.prototype, {
11497
11475
  // distance is constrained
11498
11476
  } else if (typeof constraints.fixedDistance.dist === "number") {
11499
11477
  const distPoint = constraints.fixedDistance.point.coord || constraints.fixedDistance.point;
11500
- let angle = GraphUtils.findAngle(coord, distPoint);
11478
+ let angle = GraphUtils.findAngleDeprecated(coord, distPoint);
11501
11479
  const length = constraints.fixedDistance.dist;
11502
11480
  angle = angle * Math.PI / 180;
11503
11481
  newCoord[0] = length * Math.cos(angle) + distPoint[0];
@@ -13697,7 +13675,7 @@ _.extend(MovableAngle.prototype, {
13697
13675
  _.each([0, 2], function (i) {
13698
13676
  const oldPoint = points[i].coord;
13699
13677
  let newPoint = vector$1.add(oldPoint, delta);
13700
- let angle = GraphUtils.findAngle(newVertex, newPoint);
13678
+ let angle = GraphUtils.findAngleDeprecated(newVertex, newPoint);
13701
13679
  angle *= Math.PI / 180;
13702
13680
  newPoint = graphie.constrainToBoundsOnAngle(newPoint, 10, angle);
13703
13681
  newPoints[i] = newPoint;
@@ -13730,7 +13708,7 @@ _.extend(MovableAngle.prototype, {
13730
13708
  return false;
13731
13709
  }
13732
13710
  if (snap) {
13733
- let angle = GraphUtils.findAngle(newPoint, vertex);
13711
+ let angle = GraphUtils.findAngleDeprecated(newPoint, vertex);
13734
13712
  angle = Math.round((angle - snapOffset) / snap) * snap + snapOffset;
13735
13713
  const distance = GraphUtils.getDistance(newPoint, vertex);
13736
13714
  return vector$1.add(vertex, polar$1(distance, angle));
@@ -13818,7 +13796,7 @@ _.extend(MovableAngle.prototype, {
13818
13796
  * coords when interpreted in a clockwise direction.
13819
13797
  */
13820
13798
  _getClockwiseAngle: function (coords) {
13821
- const rawAngle = GraphUtils.findAngle(
13799
+ const rawAngle = GraphUtils.findAngleDeprecated(
13822
13800
  // The order of these is "weird" to match what a clockwise
13823
13801
  // order is in graphie.labelAngle
13824
13802
  coords[2],
@@ -14642,7 +14620,7 @@ const mapObject$1 = function mapObject(obj, lambda) {
14642
14620
  * });
14643
14621
  * {a: 'aa', b: 'bb'}
14644
14622
  */
14645
- const mapObjectFromArray$1 = function mapObjectFromArray(arr, lambda) {
14623
+ const mapObjectFromArray = function mapObjectFromArray(arr, lambda) {
14646
14624
  const result = {};
14647
14625
  _.each(arr, function (elem) {
14648
14626
  result[elem] = lambda(elem);
@@ -14652,7 +14630,7 @@ const mapObjectFromArray$1 = function mapObjectFromArray(arr, lambda) {
14652
14630
  var objective_ = {
14653
14631
  pluck: pluck,
14654
14632
  mapObject: mapObject$1,
14655
- mapObjectFromArray: mapObjectFromArray$1
14633
+ mapObjectFromArray: mapObjectFromArray
14656
14634
  };
14657
14635
 
14658
14636
  /**
@@ -18815,169 +18793,6 @@ class QuestionParagraph extends React.Component {
18815
18793
  }
18816
18794
  }
18817
18795
 
18818
- /**
18819
- * TranslationLinter asynchronously calls Khan Academy's poentry linter.
18820
- *
18821
- * The poentry linter checks for issues that prevent content from being
18822
- * translated. This linter is KA-specific and so TranslationLinter does not do
18823
- * anything in non-KA environments.
18824
- */
18825
-
18826
- // A LintCB is called once the linter has been run.
18827
-
18828
- // msec to wait before we actually call the linter after receiving the
18829
- // last call. The timer is reset if the lint is called during the wait.
18830
- const DEBOUNCE_TIMEOUT = 1000;
18831
- class TranslationLinter {
18832
- constructor() {
18833
- // To avoid linting when nothing has changed.
18834
- this.previousContent = null;
18835
- /**
18836
- * Runs the poentry linter on the given perseus string asynchronously.
18837
- *
18838
- * It doesn't do anything unless we're in a KA environment.
18839
- *
18840
- * onLintErrorsGenerated is called even if there are no errors. It is not
18841
- * called if perseusStr is unchanged from the last call.
18842
- *
18843
- * Linting is relatively expensive -- so we debounce linting each instance
18844
- * for some amount of time.
18845
- */
18846
- this.runLinter = debounce$1((perseusStr, onLintErrorsGenerated) => {
18847
- // TODO(FEI-5003): Do we still need to check for 'KA'?
18848
- if (typeof KA === "undefined") {
18849
- return;
18850
- }
18851
- if (perseusStr === this.previousContent) {
18852
- return;
18853
- }
18854
- this.previousContent = perseusStr;
18855
- if (perseusStr === "") {
18856
- onLintErrorsGenerated([]);
18857
- return;
18858
- }
18859
- fetch("/api/internal/translations/lint_poentry?preview=1&lang=en", {
18860
- headers: {
18861
- "Content-Type": "application/json"
18862
- },
18863
- body: JSON.stringify({
18864
- // The poentry linter verifies that the translation from
18865
- // the original text to the translated text is sane. We
18866
- // want to ensure that the translation from English to
18867
- // English is sane, so msgid === mstr.
18868
- msgid: perseusStr,
18869
- // msgid is the original text
18870
- msgstr: perseusStr,
18871
- // msgstr is the translated text
18872
- format: "perseus_text",
18873
- filename: ""
18874
- }),
18875
- method: "POST"
18876
- }).then(response => {
18877
- if (response.status >= 400) {
18878
- return {
18879
- status: "error",
18880
- message: "Could not run i18n linter."
18881
- };
18882
- }
18883
- return response.json();
18884
- }, rejection => {
18885
- return {
18886
- status: "error",
18887
- message: "Could not run i18n linter."
18888
- };
18889
- }).then(json => {
18890
- if (json.status === "error") {
18891
- onLintErrorsGenerated(["Some part of this text makes it untranslatable. " + "The specific message from the i18n linter was: " + json.message.replace(/\n/g, " ")]);
18892
- } else {
18893
- onLintErrorsGenerated([]);
18894
- }
18895
- });
18896
- }, DEBOUNCE_TIMEOUT);
18897
- }
18898
- /**
18899
- * Applies an array of errors generated by linters without position
18900
- * information (TranslationLinter and the legacy getSaveWarnings()) to the
18901
- * top of a Perseus tree.
18902
- */
18903
- applyLintErrors(parsedMarkdown, translationLintErrors) {
18904
- // These lint errors do not have position data associated with
18905
- // them, so we just plop them at the top.
18906
- if (translationLintErrors.length) {
18907
- const errorText = translationLintErrors.join("\n\n");
18908
- parsedMarkdown.unshift({
18909
- content: {
18910
- type: "text",
18911
- content: ""
18912
- },
18913
- insideTable: false,
18914
- message: errorText,
18915
- ruleName: "legacy-error",
18916
- severity: Rule.Severity.ERROR,
18917
- type: "lint"
18918
- });
18919
- }
18920
- }
18921
- }
18922
-
18923
- /**
18924
- * Preprocess TeX code to convert things that MathJax doesn't know how to handle
18925
- * to things is does.
18926
- */
18927
- var preprocessTex = (texCode => texCode
18928
- // Replace uses of \begin{align}...\end{align} which KaTeX doesn't
18929
- // support (yet) with \begin{aligned}...\end{aligned} which renders
18930
- // the same is supported by KaTeX. It does the same for align*.
18931
- // TODO(kevinb) update content to use aligned instead of align.
18932
- // TODO(LEMS-1608) Remove this replacement as MathJax supports the
18933
- // "align" macro correctly (and, in fact, it is not synonymous with
18934
- // "aligned").
18935
- .replace(/\{align[*]?\}/g, "{aligned}")
18936
- // Replace non-breaking spaces with regular spaces.
18937
- .replace(/[\u00a0]/g, " "));
18938
-
18939
- const {
18940
- interactiveSizes
18941
- } = constants$1;
18942
-
18943
- // Note: these size cutoffs represent content-width cutoffs as specified in
18944
- // http://zpl.io/1mVmvU (broken link - we no longer use Zeplin)
18945
- // TODO(benkomalo): these values aren't used in JS outside of this file, but
18946
- // are coupled to the values in styles/styles.less - DRY it up at some point
18947
- const smMax = articleMaxWidthTableInPx;
18948
- const mdMax = articleMaxWidthInPx;
18949
- const containerSizeClass = {
18950
- SMALL: "small",
18951
- MEDIUM: "medium",
18952
- LARGE: "large",
18953
- XLARGE: "xlarge"
18954
- };
18955
- const getClassFromWidth = width => {
18956
- if (!width) {
18957
- return containerSizeClass.MEDIUM;
18958
- }
18959
- if (width <= smMax) {
18960
- return containerSizeClass.SMALL;
18961
- }
18962
- if (width <= mdMax) {
18963
- return containerSizeClass.MEDIUM;
18964
- }
18965
- return containerSizeClass.LARGE;
18966
- };
18967
- const getInteractiveBoxFromSizeClass = sizeClass => {
18968
- if (sizeClass === containerSizeClass.SMALL) {
18969
- return [interactiveSizes.defaultBoxSizeSmall, interactiveSizes.defaultBoxSizeSmall];
18970
- }
18971
- return [interactiveSizes.defaultBoxSize, interactiveSizes.defaultBoxSize];
18972
- };
18973
-
18974
- var sizingUtils = /*#__PURE__*/Object.freeze({
18975
- __proto__: null,
18976
- containerSizeClass: containerSizeClass,
18977
- getClassFromWidth: getClassFromWidth,
18978
- getInteractiveBoxFromSizeClass: getInteractiveBoxFromSizeClass
18979
- });
18980
-
18981
18796
  const DEFAULT_ALIGNMENT$1 = "block";
18982
18797
  // NOTE(kevinb): "default" is not one in `validAlignments`.
18983
18798
  const DEFAULT_SUPPORTED_ALIGNMENTS = ["default"];
@@ -19078,6 +18893,10 @@ const getWidget = name => {
19078
18893
  }
19079
18894
  return widgets[name].widget;
19080
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
+ };
19081
18900
  const getEditor = name => {
19082
18901
  return _.has(editors, name) ? editors[name] : null;
19083
18902
  };
@@ -19380,6 +19199,7 @@ var widgets$1 = /*#__PURE__*/Object.freeze({
19380
19199
  replaceEditor: replaceEditor,
19381
19200
  replaceDeprecatedEditors: replaceDeprecatedEditors,
19382
19201
  getWidget: getWidget,
19202
+ getWidgetValidator: getWidgetValidator,
19383
19203
  getEditor: getEditor,
19384
19204
  getTransform: getTransform,
19385
19205
  getVersion: getVersion,
@@ -19399,6 +19219,222 @@ var widgets$1 = /*#__PURE__*/Object.freeze({
19399
19219
  isLintable: isLintable
19400
19220
  });
19401
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
+
19402
19438
  class WidgetContainer extends React.Component {
19403
19439
  constructor(...args) {
19404
19440
  super(...args);
@@ -19544,8 +19580,7 @@ WidgetContainer.defaultProps = {
19544
19580
  };
19545
19581
 
19546
19582
  const {
19547
- mapObject,
19548
- mapObjectFromArray
19583
+ mapObject
19549
19584
  } = objective_;
19550
19585
  const rContainsNonWhitespace = /\S/;
19551
19586
  const rImageURL = /(web\+graphie|https):\/\/[^\s]*/;
@@ -19710,11 +19745,7 @@ class Renderer extends React.Component {
19710
19745
  }
19711
19746
  let interactionTracker = this._interactionTrackers[id];
19712
19747
  if (!interactionTracker) {
19713
- interactionTracker = this._interactionTrackers[id] = new InteractionTracker(apiOptions.trackInteraction,
19714
- // @ts-expect-error - TS2345 - Argument of type 'string | null | undefined' is not assignable to parameter of type 'string'.
19715
- widgetInfo && widgetInfo.type, id,
19716
- // @ts-expect-error - TS2345 - Argument of type 'string | null | undefined' is not assignable to parameter of type 'string'.
19717
- getTracking(widgetInfo && widgetInfo.type));
19748
+ interactionTracker = this._interactionTrackers[id] = new InteractionTracker(apiOptions.trackInteraction, widgetInfo && widgetInfo.type, id, getTracking(widgetInfo && widgetInfo.type));
19718
19749
  }
19719
19750
  return _extends({}, widgetProps, {
19720
19751
  widgetId: id,
@@ -20492,23 +20523,6 @@ class Renderer extends React.Component {
20492
20523
  }, this);
20493
20524
  return state;
20494
20525
  };
20495
- this.emptyWidgets = () => {
20496
- // @ts-expect-error - TS2345 - Argument of type '(id: string) => boolean | undefined' is not assignable to parameter of type 'Iteratee<string[], boolean, string>'.
20497
- return _.filter(this.widgetIds, id => {
20498
- const widgetInfo = this._getWidgetInfo(id);
20499
- if (widgetInfo.static) {
20500
- // Static widgets shouldn't count as empty
20501
- return false;
20502
- }
20503
- const widget = this.getWidgetInstance(id);
20504
- if (widget && widget.simpleValidate) {
20505
- const score = widget.simpleValidate(widgetInfo.options,
20506
- // @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'.
20507
- null);
20508
- return Util.scoreIsEmpty(score);
20509
- }
20510
- });
20511
- };
20512
20526
  this._setWidgetProps = (id, newProps, cb, silent) => {
20513
20527
  this.setState(prevState => {
20514
20528
  const widgetProps = _extends({}, prevState.widgetProps, {
@@ -20565,19 +20579,6 @@ class Renderer extends React.Component {
20565
20579
  // Widget handles parsing of the interWidgetPath.
20566
20580
  widget == null || widget.setInputValue == null || widget.setInputValue(interWidgetPath, newValue, focus);
20567
20581
  };
20568
- /**
20569
- * Returns an array of the widget `.getUserInput()` results
20570
- */
20571
- this.getUserInput = () => {
20572
- return this.widgetIds.map(id => {
20573
- const widget = this.getWidgetInstance(id);
20574
- if (widget && widget.getUserInput) {
20575
- // TODO(Jeremy): Add the widget ID in here so we can more
20576
- // easily correlate it to the widget state.
20577
- return widget.getUserInput();
20578
- }
20579
- });
20580
- };
20581
20582
  /**
20582
20583
  * Returns an array of all widget IDs in the order they occur in
20583
20584
  * the content.
@@ -20585,56 +20586,6 @@ class Renderer extends React.Component {
20585
20586
  this.getWidgetIds = () => {
20586
20587
  return this.widgetIds;
20587
20588
  };
20588
- /**
20589
- * Returns the result of `.getUserInput()` for each widget, in
20590
- * a map from widgetId to userInput.
20591
- * NOTE(jeremy): This function is hauntingly similar to `getUserInput` with
20592
- * the major difference being that this function returns a map of
20593
- * `widgetID` => UserInput and `getUserInput` simply returns an array. It
20594
- * would be trivial to map between the results of each of these functions,
20595
- * so we should aim to remove one of these functions.
20596
- */
20597
- this.getUserInputForWidgets = () => {
20598
- return mapObjectFromArray(this.widgetIds, id => {
20599
- const widget = this.getWidgetInstance(id);
20600
- if (widget && widget.getUserInput) {
20601
- return widget.getUserInput();
20602
- }
20603
- });
20604
- };
20605
- /**
20606
- * Returns an object mapping from widget ID to perseus-style score.
20607
- * The keys of this object are the values of the array returned
20608
- * from `getWidgetIds`.
20609
- */
20610
- this.scoreWidgets = () => {
20611
- const widgetProps = this.state.widgetInfo;
20612
- const gradedWidgetIds = _.filter(this.widgetIds, id => {
20613
- const props = widgetProps[id];
20614
- const widgetIsGraded = (props == null ? void 0 : props.graded) == null || props.graded;
20615
- const widgetIsStatic = !!(props != null && props.static);
20616
- // Ungraded widgets or widgets set to static shouldn't be graded.
20617
- return widgetIsGraded && !widgetIsStatic;
20618
- });
20619
- const widgetScores = {};
20620
- _.each(gradedWidgetIds, id => {
20621
- const props = widgetProps[id];
20622
- const widget = this.getWidgetInstance(id);
20623
- // widget can be undefined if it hasn't yet been rendered
20624
- if (widget && widget.simpleValidate) {
20625
- widgetScores[id] = widget.simpleValidate(_extends({}, props == null ? void 0 : props.options, {
20626
- scoring: true
20627
- }));
20628
- }
20629
- });
20630
- return widgetScores;
20631
- };
20632
- /**
20633
- * Grades the content.
20634
- */
20635
- this.score = () => {
20636
- return _.reduce(this.scoreWidgets(), Util.combineScores, Util.noScore);
20637
- };
20638
20589
  this.guessAndScore = () => {
20639
20590
  const totalGuess = this.getUserInput();
20640
20591
  const totalScore = this.score();
@@ -20808,6 +20759,59 @@ class Renderer extends React.Component {
20808
20759
  }
20809
20760
  this._isMounted = false;
20810
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
+ }
20811
20815
  render() {
20812
20816
  const apiOptions = this.getApiOptions();
20813
20817
  if (this.reuseMarkdown) {
@@ -20882,11 +20886,11 @@ class Renderer extends React.Component {
20882
20886
  // If highlightLint is true and lint is detected, this call
20883
20887
  // will modify the parse tree by adding lint nodes that will
20884
20888
  // serve to highlight the lint when rendered
20885
- const context = _extends({
20889
+ const fullLinterContext = _extends({
20886
20890
  content: this.props.content,
20887
20891
  widgets: this.props.widgets
20888
20892
  }, this.props.linterContext);
20889
- PerseusLinter.runLinter(parsedMarkdown, context, true);
20893
+ PerseusLinter.runLinter(parsedMarkdown, fullLinterContext, true);
20890
20894
 
20891
20895
  // Apply the lint errors from the last TranslationLinter run.
20892
20896
  // TODO(joshuan): Support overlapping dots.
@@ -20908,6 +20912,7 @@ class Renderer extends React.Component {
20908
20912
  return this.lastRenderedMarkdown;
20909
20913
  }
20910
20914
  }
20915
+ Renderer.contextType = PerseusI18nContext;
20911
20916
  Renderer.defaultProps = {
20912
20917
  content: "",
20913
20918
  widgets: {},
@@ -21395,6 +21400,16 @@ InputWithExamples.defaultProps = {
21395
21400
  className: ""
21396
21401
  };
21397
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
+
21398
21413
  const _excluded$4 = ["keypadElement", "onFocus", "value"];
21399
21414
  class SimpleKeypadInput extends React.Component {
21400
21415
  constructor(...args) {
@@ -21797,9 +21812,6 @@ class InputNumber extends React.Component {
21797
21812
  return [strings.yourAnswer].concat(examples);
21798
21813
  };
21799
21814
  }
21800
- static validate(state, rubric, strings) {
21801
- return inputNumberValidator(state, rubric, strings);
21802
- }
21803
21815
  static getUserInputFromProps(props) {
21804
21816
  return {
21805
21817
  currentValue: props.currentValue
@@ -21818,9 +21830,6 @@ class InputNumber extends React.Component {
21818
21830
  getUserInput() {
21819
21831
  return InputNumber.getUserInputFromProps(this.props);
21820
21832
  }
21821
- simpleValidate(rubric) {
21822
- return inputNumberValidator(this.getUserInput(), rubric, this.context.strings);
21823
- }
21824
21833
  render() {
21825
21834
  if (this.props.apiOptions.customKeypad) {
21826
21835
  // TODO(charlie): Support "Review Mode".
@@ -21925,7 +21934,8 @@ var InputNumber$1 = {
21925
21934
  hidden: true,
21926
21935
  widget: InputNumber,
21927
21936
  transform: propTransform$3,
21928
- isLintable: true
21937
+ isLintable: true,
21938
+ validator: inputNumberValidator
21929
21939
  };
21930
21940
 
21931
21941
  const ParseTex = TexWrangler.parseTex;
@@ -22199,12 +22209,6 @@ class NumericInput extends React.Component {
22199
22209
  }
22200
22210
  return answerStrings[0];
22201
22211
  }
22202
- static validate(userInput, rubric, strings) {
22203
- return numericInputValidator(userInput, rubric, strings);
22204
- }
22205
- simpleValidate(rubric) {
22206
- return numericInputValidator(this.getUserInput(), rubric, this.context.strings);
22207
- }
22208
22212
  getUserInput() {
22209
22213
  return NumericInput.getUserInputFromProps(this.props);
22210
22214
  }
@@ -22331,7 +22335,8 @@ var NumericInput$1 = {
22331
22335
  accessible: true,
22332
22336
  widget: NumericInput,
22333
22337
  transform: propsTransform,
22334
- isLintable: true
22338
+ isLintable: true,
22339
+ validator: numericInputValidator
22335
22340
  };
22336
22341
 
22337
22342
  /**
@@ -24139,6 +24144,9 @@ class Passage extends React.Component {
24139
24144
  this._onResize = void 0;
24140
24145
  // @ts-expect-error - TS2564 - Property '_stylesAppiedTimer' has no initializer and is not definitely assigned in the constructor.
24141
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;
24142
24150
  this.state = {
24143
24151
  nLines: null,
24144
24152
  startLineNumbersAfter: 0,
@@ -24150,10 +24158,6 @@ class Passage extends React.Component {
24150
24158
  });
24151
24159
  };
24152
24160
  }
24153
- // TODO (LEMS-2396): remove validation logic from widgets that don't validate
24154
- static validate() {
24155
- return noopValidator();
24156
- }
24157
24161
  componentDidMount() {
24158
24162
  this._updateState();
24159
24163
 
@@ -24358,21 +24362,6 @@ class Passage extends React.Component {
24358
24362
  };
24359
24363
  }
24360
24364
 
24361
- /**
24362
- * Misc functions
24363
- *
24364
- * These are misc widget functions used for the widget API
24365
- */
24366
-
24367
- getUserInput() {
24368
- return null;
24369
- }
24370
-
24371
- // TODO (LEMS-2396): remove validation logic from widgets that don't validate
24372
- simpleValidate() {
24373
- return noopValidator();
24374
- }
24375
-
24376
24365
  /**
24377
24366
  * Rendering
24378
24367
  *
@@ -24525,7 +24514,8 @@ var Passage$1 = {
24525
24514
  transform: editorProps => {
24526
24515
  return _.pick(editorProps, "passageTitle", "passageText", "footnotes", "showLineNumbers");
24527
24516
  },
24528
- isLintable: true
24517
+ isLintable: true,
24518
+ validator: () => noopValidator()
24529
24519
  };
24530
24520
 
24531
24521
  /**
@@ -24551,6 +24541,9 @@ class PassageRef extends React.Component {
24551
24541
  this._isMounted = void 0;
24552
24542
  // @ts-expect-error - TS2564 - Property '_throttledUpdateRange' has no initializer and is not definitely assigned in the constructor.
24553
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;
24554
24547
  this.state = {
24555
24548
  lineRange: null,
24556
24549
  content: null
@@ -24579,10 +24572,6 @@ class PassageRef extends React.Component {
24579
24572
  }
24580
24573
  };
24581
24574
  }
24582
- // TODO (LEMS-2396): remove validation logic from widgets that don't validate
24583
- static validate() {
24584
- return noopValidator();
24585
- }
24586
24575
  componentDidMount() {
24587
24576
  // TODO(scottgrant): This is a hack to remove the deprecated call to
24588
24577
  // this.isMounted() but is still considered an anti-pattern.
@@ -24601,15 +24590,6 @@ class PassageRef extends React.Component {
24601
24590
  window.removeEventListener("resize", this._throttledUpdateRange);
24602
24591
  this._isMounted = false;
24603
24592
  }
24604
-
24605
- // TODO (LEMS-2396): remove validation logic from widgets that don't validate
24606
- getUserInput() {
24607
- return null;
24608
- }
24609
- // TODO (LEMS-2396): remove validation logic from widgets that don't validate
24610
- simpleValidate() {
24611
- return noopValidator();
24612
- }
24613
24593
  render() {
24614
24594
  const {
24615
24595
  strings
@@ -24664,7 +24644,9 @@ var PassageRef$1 = {
24664
24644
  version: {
24665
24645
  major: 0,
24666
24646
  minor: 1
24667
- }
24647
+ },
24648
+ // TODO: things that aren't interactive shouldn't need validators
24649
+ validator: () => noopValidator()
24668
24650
  };
24669
24651
 
24670
24652
  /**
@@ -26166,7 +26148,7 @@ class Radio$1 extends React.Component {
26166
26148
  choiceStates
26167
26149
  } = this.props;
26168
26150
  if (choiceStates) {
26169
- const score = this.simpleValidate(rubric);
26151
+ const score = radioValidator(this.getUserInput(), rubric, this.context.strings);
26170
26152
  const widgetCorrect = score.type === "points" && score.total === score.earned;
26171
26153
  const newStates = choiceStates.map(state => _extends({}, state, {
26172
26154
  highlighted: state.selected,
@@ -26214,9 +26196,6 @@ class Radio$1 extends React.Component {
26214
26196
  }
26215
26197
  };
26216
26198
  }
26217
- static validate(userInput, rubric, strings) {
26218
- return radioValidator(userInput, rubric, strings);
26219
- }
26220
26199
  static getUserInputFromProps(props) {
26221
26200
  // Return checked inputs in the form {choicesSelected: [bool]}. (Dear
26222
26201
  // future timeline implementers: this used to be {value: i} before
@@ -26307,9 +26286,6 @@ class Radio$1 extends React.Component {
26307
26286
  getUserInput() {
26308
26287
  return Radio$1.getUserInputFromProps(this.props);
26309
26288
  }
26310
- simpleValidate(rubric) {
26311
- return radioValidator(this.getUserInput(), rubric, this.context.strings);
26312
- }
26313
26289
  render() {
26314
26290
  const {
26315
26291
  choices
@@ -26544,7 +26520,8 @@ var Radio = {
26544
26520
  minor: 0
26545
26521
  },
26546
26522
  propUpgrades: propUpgrades$1,
26547
- isLintable: true
26523
+ isLintable: true,
26524
+ validator: radioValidator
26548
26525
  };
26549
26526
 
26550
26527
  /* eslint-disable import/no-named-as-default */
@@ -26587,9 +26564,6 @@ class Categorizer extends React.Component {
26587
26564
  return change.apply(this, args);
26588
26565
  };
26589
26566
  }
26590
- static validate(userInput, rubric, strings) {
26591
- return categorizerValidator(userInput, rubric, strings);
26592
- }
26593
26567
  static getUserInputFromProps(props) {
26594
26568
  return {
26595
26569
  values: props.values
@@ -26605,9 +26579,6 @@ class Categorizer extends React.Component {
26605
26579
  this.change("values", values);
26606
26580
  this.props.trackInteraction();
26607
26581
  }
26608
- simpleValidate(rubric) {
26609
- return categorizerValidator(this.getUserInput(), rubric, this.context.strings);
26610
- }
26611
26582
  render() {
26612
26583
  const self = this;
26613
26584
 
@@ -26753,7 +26724,8 @@ var Categorizer$1 = {
26753
26724
  staticTransform: editorProps => {
26754
26725
  return _.pick(editorProps, "items", "categories", "values", "randomizeItems");
26755
26726
  },
26756
- isLintable: true
26727
+ isLintable: true,
26728
+ validator: categorizerValidator
26757
26729
  };
26758
26730
 
26759
26731
  /**
@@ -26877,10 +26849,6 @@ class CSProgram extends React.Component {
26877
26849
  return change.apply(this, args);
26878
26850
  };
26879
26851
  }
26880
- // The widget's grading function
26881
- static validate(state) {
26882
- return csProgramValidator(state);
26883
- }
26884
26852
  componentDidMount() {
26885
26853
  $(window).on("message", this.handleMessageEvent);
26886
26854
  }
@@ -26893,9 +26861,6 @@ class CSProgram extends React.Component {
26893
26861
  message: this.props.message
26894
26862
  };
26895
26863
  }
26896
- simpleValidate() {
26897
- return csProgramValidator(this.getUserInput());
26898
- }
26899
26864
  render() {
26900
26865
  if (!this.props.programID) {
26901
26866
  return /*#__PURE__*/React.createElement("div", null);
@@ -26977,21 +26942,16 @@ var CSProgram$1 = {
26977
26942
  displayName: "CS Program",
26978
26943
  supportedAlignments: ["block", "full-width"],
26979
26944
  widget: CSProgram,
26980
- hidden: true
26945
+ hidden: true,
26946
+ validator: csProgramValidator
26981
26947
  };
26982
26948
 
26983
26949
  class Definition extends React.Component {
26984
- // TODO (LEMS-2396): remove validation logic from widgets that don't validate
26985
- static validate() {
26986
- return noopValidator();
26987
- }
26988
- getUserInput() {
26989
- return {};
26990
- }
26991
-
26992
- // TODO (LEMS-2396): remove validation logic from widgets that don't validate
26993
- simpleValidate() {
26994
- 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;
26995
26955
  }
26996
26956
  render() {
26997
26957
  return /*#__PURE__*/React.createElement(DefinitionConsumer, null, ({
@@ -27047,22 +27007,19 @@ var Definition$1 = {
27047
27007
  accessible: true,
27048
27008
  defaultAlignment: "inline",
27049
27009
  widget: Definition,
27050
- transform: x => x
27010
+ transform: x => x,
27011
+ // TODO: things that aren't interactive shouldn't need validators
27012
+ validator: () => noopValidator()
27051
27013
  };
27052
27014
 
27053
27015
  // The props are type `any` on purpose so that this can receive props
27054
27016
  // from any deprecated widget
27055
27017
  class DeprecatedStandin extends React__default.Component {
27056
- static validate() {
27057
- // Since this mean to replace an existing widget the learner
27058
- // WILL earn points for this widget
27059
- return noopValidator(1);
27060
- }
27061
- getUserInput() {
27062
- return {};
27063
- }
27064
- simpleValidate() {
27065
- 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;
27066
27023
  }
27067
27024
  render() {
27068
27025
  return /*#__PURE__*/React__default.createElement("div", {
@@ -27082,7 +27039,9 @@ var DeprecatedStandin$1 = {
27082
27039
  name: "deprecated-standin",
27083
27040
  displayName: "Deprecated Standin",
27084
27041
  widget: DeprecatedStandin,
27085
- hidden: true
27042
+ hidden: true,
27043
+ // TODO: things that aren't interactive shouldn't need validators
27044
+ validator: () => noopValidator(1)
27086
27045
  };
27087
27046
 
27088
27047
  function dropdownValidator(userInput, rubric) {
@@ -27123,17 +27082,11 @@ class Dropdown extends React.Component {
27123
27082
  });
27124
27083
  };
27125
27084
  }
27126
- static validate(userInput, rubric) {
27127
- return dropdownValidator(userInput, rubric);
27128
- }
27129
27085
  getUserInput() {
27130
27086
  return {
27131
27087
  value: this.props.selected
27132
27088
  };
27133
27089
  }
27134
- simpleValidate(rubric) {
27135
- return dropdownValidator(this.getUserInput(), rubric);
27136
- }
27137
27090
  render() {
27138
27091
  const children = [/*#__PURE__*/React.createElement(OptionItem, {
27139
27092
  key: "placeholder",
@@ -27180,7 +27133,8 @@ var Dropdown$1 = {
27180
27133
  defaultAlignment: "inline-block",
27181
27134
  accessible: true,
27182
27135
  widget: Dropdown,
27183
- transform: optionsTransform
27136
+ transform: optionsTransform,
27137
+ validator: dropdownValidator
27184
27138
  };
27185
27139
 
27186
27140
  // transform = _.identity
@@ -27194,6 +27148,9 @@ function mediaQueryIsMatched(mediaQuery) {
27194
27148
  class Explanation extends React.Component {
27195
27149
  constructor(..._args) {
27196
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;
27197
27154
  this.state = {
27198
27155
  expanded: false
27199
27156
  };
@@ -27208,24 +27165,12 @@ class Explanation extends React.Component {
27208
27165
  this.props.trackInteraction();
27209
27166
  };
27210
27167
  }
27211
- // TODO (LEMS-2396): remove validation logic from widgets that don't validate
27212
- static validate() {
27213
- return noopValidator();
27214
- }
27215
27168
  componentDidMount() {
27216
27169
  this._mounted = true;
27217
27170
  }
27218
27171
  componentWillUnmount() {
27219
27172
  this._mounted = false;
27220
27173
  }
27221
- getUserInput() {
27222
- return {};
27223
- }
27224
-
27225
- // TODO (LEMS-2396): remove validation logic from widgets that don't validate
27226
- simpleValidate() {
27227
- return noopValidator();
27228
- }
27229
27174
  render() {
27230
27175
  const promptText = this.state.expanded ? this.props.hidePrompt : this.props.showPrompt;
27231
27176
  const caretIcon = this.state.expanded ? caretUp : caretDown;
@@ -27339,7 +27284,9 @@ var Explanation$1 = {
27339
27284
  defaultAlignment: "inline",
27340
27285
  widget: Explanation,
27341
27286
  transform: _.identity,
27342
- isLintable: true
27287
+ isLintable: true,
27288
+ // TODO: things that aren't interactive shouldn't need validators
27289
+ validator: () => noopValidator()
27343
27290
  };
27344
27291
 
27345
27292
  class GradedGroupAnswerBar extends React.Component {
@@ -28939,9 +28886,6 @@ class Grapher extends React.Component {
28939
28886
  }
28940
28887
  };
28941
28888
  }
28942
- static validate(state, rubric) {
28943
- return grapherValidator(state, rubric);
28944
- }
28945
28889
  static getUserInputFromProps(props) {
28946
28890
  return props.plot;
28947
28891
  }
@@ -28960,9 +28904,6 @@ class Grapher extends React.Component {
28960
28904
  tickStep[1] = tickStep[1] / gridStep[1];
28961
28905
  return tickStep;
28962
28906
  }
28963
- simpleValidate(rubric) {
28964
- return grapherValidator(this.getUserInput(), rubric);
28965
- }
28966
28907
  getUserInput() {
28967
28908
  return Grapher.getUserInputFromProps(this.props);
28968
28909
  }
@@ -29050,7 +28991,8 @@ var Grapher$1 = {
29050
28991
  hidden: true,
29051
28992
  widget: Grapher,
29052
28993
  transform: propTransform$2,
29053
- staticTransform: staticTransform$4
28994
+ staticTransform: staticTransform$4,
28995
+ validator: grapherValidator
29054
28996
  };
29055
28997
 
29056
28998
  // exports has no 'transform'
@@ -29096,17 +29038,18 @@ class Group extends React.Component {
29096
29038
  // the group with the correct number.
29097
29039
  this.forceUpdate();
29098
29040
  }
29099
- getUserInput() {
29041
+ getUserInputMap() {
29100
29042
  var _this$rendererRef7;
29101
- return (_this$rendererRef7 = this.rendererRef) == null ? void 0 : _this$rendererRef7.getUserInput();
29102
- }
29103
- simpleValidate() {
29104
- var _this$rendererRef$sco, _this$rendererRef8;
29105
- return (_this$rendererRef$sco = (_this$rendererRef8 = this.rendererRef) == null ? void 0 : _this$rendererRef8.score()) != null ? _this$rendererRef$sco : {
29106
- type: "invalid"
29107
- };
29043
+ return (_this$rendererRef7 = this.rendererRef) == null ? void 0 : _this$rendererRef7.getUserInputMap();
29108
29044
  }
29109
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
+ }
29110
29053
  // Mobile API:
29111
29054
  getInputPaths() {
29112
29055
  var _this$rendererRef$get, _this$rendererRef9;
@@ -29267,9 +29210,6 @@ class Iframe extends React.Component {
29267
29210
  return change.apply(this, args);
29268
29211
  };
29269
29212
  }
29270
- static validate(state) {
29271
- return iframeValidator(state);
29272
- }
29273
29213
  componentDidMount() {
29274
29214
  $(window).on("message", this.handleMessageEvent);
29275
29215
  }
@@ -29282,9 +29222,6 @@ class Iframe extends React.Component {
29282
29222
  message: this.props.message
29283
29223
  };
29284
29224
  }
29285
- simpleValidate() {
29286
- return iframeValidator(this.getUserInput());
29287
- }
29288
29225
  render() {
29289
29226
  const style = {
29290
29227
  width: String(this.props.width),
@@ -29358,7 +29295,8 @@ var Iframe$1 = {
29358
29295
  displayName: "Iframe (deprecated)",
29359
29296
  widget: Iframe,
29360
29297
  // Let's not expose it to all content creators yet
29361
- hidden: true
29298
+ hidden: true,
29299
+ validator: iframeValidator
29362
29300
  };
29363
29301
 
29364
29302
  const defaultBoxSize = 400;
@@ -29376,22 +29314,13 @@ const DEFAULT_ALIGNMENT = "block";
29376
29314
  class ImageWidget extends React.Component {
29377
29315
  constructor(..._args) {
29378
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;
29379
29320
  this.change = (...args) => {
29380
29321
  return change.apply(this, args);
29381
29322
  };
29382
29323
  }
29383
- // TODO (LEMS-2396): remove validation logic from widgets that don't validate
29384
- static validate() {
29385
- return noopValidator();
29386
- }
29387
- getUserInput() {
29388
- return null;
29389
- }
29390
-
29391
- // TODO (LEMS-2396): remove validation logic from widgets that don't validate
29392
- simpleValidate() {
29393
- return noopValidator();
29394
- }
29395
29324
  render() {
29396
29325
  let image;
29397
29326
  const alt = this.props.caption === this.props.alt ? "" : this.props.alt;
@@ -29546,7 +29475,9 @@ var Image$1 = {
29546
29475
  supportedAlignments: editorAlignments,
29547
29476
  displayName: "Image",
29548
29477
  widget: ImageWidget,
29549
- isLintable: true
29478
+ isLintable: true,
29479
+ // TODO: things that aren't interactive shouldn't need validators
29480
+ validator: () => noopValidator()
29550
29481
  };
29551
29482
 
29552
29483
  /* eslint-disable @khanacademy/ts-no-error-suppressions */
@@ -29615,6 +29546,9 @@ const KAScompile = (expr, options) => {
29615
29546
  class Interaction extends React.Component {
29616
29547
  constructor(..._args) {
29617
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;
29618
29552
  this.state = {
29619
29553
  variables: _getInitialVariables(this.props.elements),
29620
29554
  functions: _getInitialFunctions(this.props.elements)
@@ -29698,10 +29632,6 @@ class Interaction extends React.Component {
29698
29632
  return change.apply(this, args);
29699
29633
  };
29700
29634
  }
29701
- // TODO (LEMS-2396): remove validation logic from widgets that don't validate
29702
- static validate() {
29703
- return noopValidator();
29704
- }
29705
29635
  UNSAFE_componentWillReceiveProps(nextProps) {
29706
29636
  // HACK(michaelpolyak): This widget is not wired to use the Perseus
29707
29637
  // `onChange` callback to save user changes. Perseus re-rendering of the
@@ -29720,18 +29650,6 @@ class Interaction extends React.Component {
29720
29650
  });
29721
29651
  }
29722
29652
  }
29723
- getUserInput() {
29724
- // TODO(eater): Perhaps we want to be able to record the state of the
29725
- // user's interaction. Unfortunately sending all the props will
29726
- // probably make the attempt payload too large. So for now, don't send
29727
- // anything.
29728
- return {};
29729
- }
29730
-
29731
- // TODO (LEMS-2396): remove validation logic from widgets that don't validate
29732
- simpleValidate() {
29733
- return noopValidator();
29734
- }
29735
29653
  render() {
29736
29654
  const range = this.props.graph.range;
29737
29655
  let labels = this.props.graph.labels;
@@ -30150,7 +30068,9 @@ var Interactive = {
30150
30068
  displayName: "Interaction",
30151
30069
  widget: Interaction,
30152
30070
  transform: _.identity,
30153
- hidden: true
30071
+ hidden: true,
30072
+ // TODO: things that aren't interactive shouldn't need validators
30073
+ validator: () => noopValidator()
30154
30074
  };
30155
30075
 
30156
30076
  /* eslint-disable @khanacademy/ts-no-error-suppressions */
@@ -30672,16 +30592,39 @@ function polar(r, th) {
30672
30592
  th = th * Math.PI / 180;
30673
30593
  return [r[0] * Math.cos(th), r[1] * Math.sin(th)];
30674
30594
  }
30675
- const findAngle = (point1, point2, vertex) => {
30676
- if (vertex === undefined) {
30677
- const x = point1[0] - point2[0];
30678
- const y = point1[1] - point2[1];
30679
- if (!x && !y) {
30680
- return 0;
30681
- }
30682
- return (180 + Math.atan2(-y, -x) * 180 / Math.PI + 360) % 360;
30595
+ // This function calculates the angle between two points and an optional vertex.
30596
+ // If the vertex is not provided, the angle is measured between the two points.
30597
+ // This does not account for reflex angles or clockwise position.
30598
+ const getAngleFromVertex = (point, vertex) => {
30599
+ const x = point[0] - vertex[0];
30600
+ const y = point[1] - vertex[1];
30601
+ if (!x && !y) {
30602
+ return 0;
30683
30603
  }
30684
- return findAngle(point1, vertex) - findAngle(point2, vertex);
30604
+ return (180 + Math.atan2(-y, -x) * 180 / Math.PI + 360) % 360;
30605
+ };
30606
+
30607
+ // This function calculates the clockwise angle between three points,
30608
+ // and is used to generate the labels and equation strings of the
30609
+ // current angle for the interactive graph.
30610
+ const getClockwiseAngle = (coords, allowReflexAngles = false) => {
30611
+ const coordsCopy = [...coords];
30612
+ // The coords are saved as [point1, vertex, point2] in the interactive graph
30613
+ const areClockwise = clockwise$1([coordsCopy[0], coordsCopy[2], coordsCopy[1]]);
30614
+
30615
+ // We may need to reverse the coordinates if we allow
30616
+ // reflex angles and the points are not in clockwise order.
30617
+ const shouldReverseCoords = !areClockwise || allowReflexAngles;
30618
+
30619
+ // Reverse the coordinates accordingly to ensure the angle is calculated correctly
30620
+ const clockwiseCoords = shouldReverseCoords ? coordsCopy.reverse() : coordsCopy;
30621
+
30622
+ // Calculate the angles between the two points and get the difference
30623
+ // between the two angles to get the clockwise angle.
30624
+ const startAngle = getAngleFromVertex(clockwiseCoords[0], clockwiseCoords[1]);
30625
+ const endAngle = getAngleFromVertex(clockwiseCoords[2], clockwiseCoords[1]);
30626
+ const angle = (startAngle + 360 - endAngle) % 360;
30627
+ return angle;
30685
30628
  };
30686
30629
 
30687
30630
  const defaultGraphConfig = {
@@ -33103,8 +33046,8 @@ const Angle = ({
33103
33046
  const clockwiseCoords = shouldReverseCoords ? coords : coords.reverse();
33104
33047
 
33105
33048
  // Calculate the angles between the two points
33106
- const startAngle = findAngle(clockwiseCoords[0], vertex);
33107
- const endAngle = findAngle(clockwiseCoords[1], vertex);
33049
+ const startAngle = getAngleFromVertex(clockwiseCoords[0], vertex);
33050
+ const endAngle = getAngleFromVertex(clockwiseCoords[1], vertex);
33108
33051
  const angle = (startAngle + 360 - endAngle) % 360;
33109
33052
 
33110
33053
  // Check if the angle is reflexive
@@ -34183,7 +34126,9 @@ const MafsGraph = props => {
34183
34126
  analytics.onAnalyticsEvent({
34184
34127
  type: "perseus:interactive-graph-widget:rendered",
34185
34128
  payload: {
34186
- type
34129
+ type,
34130
+ widgetType: "INTERACTIVE_GRAPH",
34131
+ widgetId: "interactive-graph"
34187
34132
  }
34188
34133
  });
34189
34134
  });
@@ -35423,7 +35368,7 @@ function boundAndSnapAngleVertex({
35423
35368
  for (const i of [0, 2]) {
35424
35369
  const oldPoint = coordsCopy[i];
35425
35370
  let newPoint = vec.add(oldPoint, delta);
35426
- let angle = findAngle(newVertex, newPoint);
35371
+ let angle = getAngleFromVertex(newVertex, newPoint);
35427
35372
  angle *= Math.PI / 180;
35428
35373
  newPoint = constrainToBoundsOnAngle(newPoint, angle, range, snapStep);
35429
35374
  newPoints[i] = newPoint;
@@ -35494,7 +35439,7 @@ function boundAndSnapAngleEndPoints(destinationPoint, {
35494
35439
  const vertex = coords[1];
35495
35440
 
35496
35441
  // Gets the angle between the coords and the vertex
35497
- let angle = findAngle(coordsCopy[index], vertex);
35442
+ let angle = getAngleFromVertex(coordsCopy[index], vertex);
35498
35443
 
35499
35444
  // Snap the angle to the nearest multiple of snapDegrees (if provided)
35500
35445
  angle = Math.round((angle - offsetDegrees) / snap) * snap + offsetDegrees;
@@ -35540,8 +35485,8 @@ function boundAndSnapToPolygonAngle(destinationPoint, {
35540
35485
  angles[rel(j)] = Math.round(angles[rel(j)]);
35541
35486
  });
35542
35487
  const getAngle = function getAngle(a, vertex, b) {
35543
- const angle = findAngle(coordsCopy[rel(a)], coordsCopy[rel(b)], coordsCopy[rel(vertex)]);
35544
- return (angle + 360) % 360;
35488
+ const angle = getClockwiseAngle([coordsCopy[rel(a)], coordsCopy[rel(vertex)], coordsCopy[rel(b)]]);
35489
+ return angle;
35545
35490
  };
35546
35491
  const innerAngles = [angles[rel(-1)] - getAngle(-2, -1, 1), angles[rel(1)] - getAngle(-1, 1, 2)];
35547
35492
  innerAngles[2] = 180 - (innerAngles[0] + innerAngles[1]);
@@ -35568,7 +35513,7 @@ function boundAndSnapToPolygonAngle(destinationPoint, {
35568
35513
  const side = Math.sin(innerAngles[1] * Math.PI / 180) / Math.sin(innerAngles[2] * Math.PI / 180) * knownSide;
35569
35514
 
35570
35515
  // Angle at the second vertex of the polygon
35571
- const outerAngle = findAngle(coordsCopy[rel(1)], coordsCopy[rel(-1)]);
35516
+ const outerAngle = getAngleFromVertex(coordsCopy[rel(1)], coordsCopy[rel(-1)]);
35572
35517
 
35573
35518
  // Uses the length of the side of the polygon (radial coordinate)
35574
35519
  // and the angle between the first and second sides of the
@@ -35618,7 +35563,7 @@ function boundAndSnapToSides(destinationPoint, {
35618
35563
  const innerAngle = lawOfCosines(sides[0], sides[2], sides[1]);
35619
35564
 
35620
35565
  // Angle at the second vertex of the polygon
35621
- const outerAngle = findAngle(coordsCopy[rel(1)], coordsCopy[rel(-1)]);
35566
+ const outerAngle = getAngleFromVertex(coordsCopy[rel(1)], coordsCopy[rel(-1)]);
35622
35567
 
35623
35568
  // Returns true if the points form a counter-clockwise turn;
35624
35569
  // a.k.a if the point is on the left or right of the polygon.
@@ -35725,7 +35670,8 @@ const StatefulMafsGraph = /*#__PURE__*/React.forwardRef((props, ref) => {
35725
35670
  const numPoints = graph.type === "point" ? graph.numPoints : null;
35726
35671
  const numSides = graph.type === "polygon" ? graph.numSides : null;
35727
35672
  const snapTo = graph.type === "polygon" ? graph.snapTo : null;
35728
- const showAngles = graph.type === "polygon" ? graph.showAngles : null;
35673
+ const showAngles = graph.type === "polygon" || graph.type === "angle" ? graph.showAngles : null;
35674
+ const allowReflexAngles = graph.type === "angle" ? graph.allowReflexAngles : null;
35729
35675
  const showSides = graph.type === "polygon" ? graph.showSides : null;
35730
35676
  const startCoords = "startCoords" in graph ? graph.startCoords : undefined;
35731
35677
  const originalPropsRef = useRef(props);
@@ -35738,7 +35684,7 @@ const StatefulMafsGraph = /*#__PURE__*/React.forwardRef((props, ref) => {
35738
35684
  if (latestPropsRef.current !== originalPropsRef.current) {
35739
35685
  dispatch(reinitialize(latestPropsRef.current));
35740
35686
  }
35741
- }, [graph.type, numPoints, numSegments, numSides, snapTo, showAngles, showSides, latestPropsRef, startCoords]);
35687
+ }, [graph.type, numPoints, numSegments, numSides, snapTo, showAngles, showSides, latestPropsRef, startCoords, allowReflexAngles]);
35742
35688
 
35743
35689
  // If the graph is static, it always displays the correct answer. This is
35744
35690
  // standard behavior for Perseus widgets (e.g. compare the Radio widget).
@@ -35927,17 +35873,15 @@ function interactiveGraphValidator(userInput, rubric) {
35927
35873
  } else if (userInput.type === "angle" && rubric.correct.type === "angle") {
35928
35874
  const guess = userInput.coords;
35929
35875
  const correct = rubric.correct.coords;
35876
+ const allowReflexAngles = rubric.correct.allowReflexAngles;
35930
35877
  let match;
35931
35878
  if (rubric.correct.match === "congruent") {
35932
35879
  const angles = _.map([guess, correct], function (coords) {
35933
- const angle = GraphUtils.findAngle(
35934
- // @ts-expect-error - TS2532 - Object is possibly 'undefined'.
35935
- coords[2],
35936
- // @ts-expect-error - TS2532 - Object is possibly 'undefined'.
35937
- coords[0],
35938
- // @ts-expect-error - TS2532 - Object is possibly 'undefined'.
35939
- coords[1]);
35940
- return (angle + 360) % 360;
35880
+ if (!coords) {
35881
+ return false;
35882
+ }
35883
+ const angle = getClockwiseAngle(coords, allowReflexAngles);
35884
+ return angle;
35941
35885
  });
35942
35886
  // @ts-expect-error - TS2556 - A spread argument must either have a tuple type or be passed to a rest parameter.
35943
35887
  match = eq$1(...angles);
@@ -36805,7 +36749,7 @@ class LegacyInteractiveGraph extends React.Component {
36805
36749
  angles[rel(j)] = Math.round(angles[rel(j)]);
36806
36750
  });
36807
36751
  const getAngle = function getAngle(a, vertex, b) {
36808
- const angle = GraphUtils.findAngle(coords[rel(a)], coords[rel(b)], coords[rel(vertex)]);
36752
+ const angle = GraphUtils.findAngleDeprecated(coords[rel(a)], coords[rel(b)], coords[rel(vertex)]);
36809
36753
  return (angle + 360) % 360;
36810
36754
  };
36811
36755
  const innerAngles = [angles[rel(-1)] - getAngle(-2, -1, 1), angles[rel(1)] - getAngle(-1, 1, 2)];
@@ -36824,7 +36768,7 @@ class LegacyInteractiveGraph extends React.Component {
36824
36768
 
36825
36769
  // Solve for side by using the law of sines
36826
36770
  const side = Math.sin(innerAngles[1] * Math.PI / 180) / Math.sin(innerAngles[2] * Math.PI / 180) * knownSide;
36827
- const outerAngle = GraphUtils.findAngle(coords[rel(1)], coords[rel(-1)]);
36771
+ const outerAngle = GraphUtils.findAngleDeprecated(coords[rel(1)], coords[rel(-1)]);
36828
36772
  const offset = polar$1(side, outerAngle + (onLeft ? 1 : -1) * innerAngles[0]);
36829
36773
  return (_this$graphie5 = this.graphie) == null ? void 0 : _this$graphie5.addPoints(coords[rel(-1)], offset);
36830
36774
  }
@@ -36848,7 +36792,7 @@ class LegacyInteractiveGraph extends React.Component {
36848
36792
 
36849
36793
  // Solve for angle by using the law of cosines
36850
36794
  const innerAngle = lawOfCosines(sides[0], sides[2], sides[1]);
36851
- const outerAngle = GraphUtils.findAngle(coords[rel(1)], coords[rel(-1)]);
36795
+ const outerAngle = GraphUtils.findAngleDeprecated(coords[rel(1)], coords[rel(-1)]);
36852
36796
  const onLeft = sign(ccw(coords[rel(-1)], coords[rel(1)], coords[i])) === 1;
36853
36797
  const offset = polar$1(sides[0], outerAngle + (onLeft ? 1 : -1) * innerAngle);
36854
36798
  return (_this$graphie6 = this.graphie) == null ? void 0 : _this$graphie6.addPoints(coords[rel(-1)], offset);
@@ -37212,9 +37156,6 @@ class LegacyInteractiveGraph extends React.Component {
37212
37156
  getUserInput() {
37213
37157
  return InteractiveGraph.getUserInputFromProps(this.props);
37214
37158
  }
37215
- simpleValidate(rubric) {
37216
- return interactiveGraphValidator(this.getUserInput(), rubric);
37217
- }
37218
37159
  render() {
37219
37160
  const box = getInteractiveBoxFromSizeClass(this.props.containerSizeClass);
37220
37161
  const gridStep = this.props.gridStep || Util.getGridStep(this.props.range, this.props.step, box[0]);
@@ -37291,9 +37232,6 @@ class InteractiveGraph extends React.Component {
37291
37232
  }
37292
37233
  throw new PerseusError("Cannot getUserInput from a graph that has never rendered", Errors.NotAllowed);
37293
37234
  }
37294
- simpleValidate(rubric) {
37295
- return interactiveGraphValidator(this.getUserInput(), rubric);
37296
- }
37297
37235
  render() {
37298
37236
  var _this$props$apiOption;
37299
37237
  // Mafs shim
@@ -37622,12 +37560,10 @@ class InteractiveGraph extends React.Component {
37622
37560
  throw makeInvalidTypeError("getAngleEquationString", "angle");
37623
37561
  }
37624
37562
  const coords = InteractiveGraph.getAngleCoords(props.graph, props);
37625
- const angle = GraphUtils.findAngle(coords[2], coords[0], coords[1]);
37563
+ const allowReflexAngles = props.graph.allowReflexAngles;
37564
+ const angle = getClockwiseAngle(coords, allowReflexAngles);
37626
37565
  return angle.toFixed(0) + "\u00B0 angle" + " at (" + coords[1].join(", ") + ")";
37627
37566
  }
37628
- static validate(userInput, rubric) {
37629
- return interactiveGraphValidator(userInput, rubric);
37630
- }
37631
37567
  static getUserInputFromProps(props) {
37632
37568
  return props.graph;
37633
37569
  }
@@ -37676,7 +37612,8 @@ var InteractiveGraph$1 = {
37676
37612
  name: "interactive-graph",
37677
37613
  displayName: "Interactive graph (Assessments only)",
37678
37614
  widget: InteractiveGraph,
37679
- staticTransform: staticTransform$3
37615
+ staticTransform: staticTransform$3,
37616
+ validator: interactiveGraphValidator
37680
37617
  };
37681
37618
 
37682
37619
  const black = "#000000";
@@ -39041,10 +38978,6 @@ const styles$b = StyleSheet.create({
39041
38978
  // TODO: should this be using WidgetProps / PerseusLabelImageWidgetOptions?
39042
38979
 
39043
38980
  class LabelImage extends React.Component {
39044
- static validate(state, rubric) {
39045
- return labelImageValidator(state);
39046
- }
39047
-
39048
38981
  /**
39049
38982
  * Test whether point is contained within triangle.
39050
38983
  *
@@ -39239,9 +39172,6 @@ class LabelImage extends React.Component {
39239
39172
  componentWillUnmount() {
39240
39173
  this._mounted = false;
39241
39174
  }
39242
- simpleValidate(rubric) {
39243
- return labelImageValidator(this.getUserInput());
39244
- }
39245
39175
  getUserInput() {
39246
39176
  const {
39247
39177
  markers
@@ -39598,19 +39528,13 @@ const LabelImageWithDependencies = /*#__PURE__*/React.forwardRef((props, ref) =>
39598
39528
  analytics: deps.analytics
39599
39529
  }, props));
39600
39530
  });
39601
-
39602
- // HACK: Propogate "static" methods onto our wrapper component.
39603
- // In the future we should adjust client apps to not depend on these static
39604
- // methods and instead adjust Peresus to provide these facilities through
39605
- // instance methods on our Renderers.
39606
- // @ts-expect-error - TS2339 - Property 'validate' does not exist on type
39607
- LabelImageWithDependencies.validate = labelImageValidator;
39608
39531
  var LabelImage$1 = {
39609
39532
  name: "label-image",
39610
39533
  displayName: "Label Image",
39611
39534
  widget: LabelImageWithDependencies,
39612
39535
  accessible: true,
39613
- isLintable: true
39536
+ isLintable: true,
39537
+ validator: labelImageValidator
39614
39538
  };
39615
39539
 
39616
39540
  var ItemState = /*#__PURE__*/function (ItemState) {
@@ -40395,12 +40319,6 @@ class Matcher extends React.Component {
40395
40319
  this.refs.right.moveOptionToIndex(option, index);
40396
40320
  };
40397
40321
  }
40398
- static validate(state, rubric) {
40399
- return matcherValidator(state, rubric);
40400
- }
40401
- simpleValidate(rubric) {
40402
- return matcherValidator(this.getUserInput(), rubric);
40403
- }
40404
40322
  render() {
40405
40323
  // To minimize layout shift, we display a spinner until our math
40406
40324
  // renderer is ready to render the math inside the matcher. To
@@ -40538,7 +40456,8 @@ var Matcher$1 = {
40538
40456
  name: "matcher",
40539
40457
  displayName: "Matcher (two column)",
40540
40458
  widget: Matcher,
40541
- isLintable: true
40459
+ isLintable: true,
40460
+ validator: matcherValidator
40542
40461
  };
40543
40462
 
40544
40463
  function matrixValidator(state, rubric, strings) {
@@ -40800,9 +40719,6 @@ class Matrix extends React.Component {
40800
40719
  this.props.trackInteraction();
40801
40720
  };
40802
40721
  }
40803
- static validate(state, rubric, strings) {
40804
- return matrixValidator(state, rubric, strings);
40805
- }
40806
40722
  componentDidMount() {
40807
40723
  // Used in the `onBlur` and `onFocus` handlers
40808
40724
  this.cursorPosition = [0, 0];
@@ -40817,9 +40733,6 @@ class Matrix extends React.Component {
40817
40733
  answers: this.props.answers
40818
40734
  };
40819
40735
  }
40820
- simpleValidate(rubric) {
40821
- return matrixValidator(this.getUserInput(), rubric, this.context.strings);
40822
- }
40823
40736
  render() {
40824
40737
  // Set the input sizes through JS so we can control the size of the
40825
40738
  // brackets. (If we set them in CSS we won't know values until the
@@ -41012,7 +40925,8 @@ var Matrix$1 = {
41012
40925
  widget: Matrix,
41013
40926
  transform: propTransform$1,
41014
40927
  staticTransform: staticTransform$2,
41015
- isLintable: true
40928
+ isLintable: true,
40929
+ validator: matrixValidator
41016
40930
  };
41017
40931
 
41018
40932
  const defaultImage = {
@@ -41023,6 +40937,9 @@ const defaultImage = {
41023
40937
  class Measurer extends React.Component {
41024
40938
  constructor(...args) {
41025
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;
41026
40943
  this.state = {};
41027
40944
  this.ruler = void 0;
41028
40945
  this.protractor = void 0;
@@ -41076,12 +40993,6 @@ class Measurer extends React.Component {
41076
40993
  });
41077
40994
  }
41078
40995
  }
41079
- getUserInput() {
41080
- return {};
41081
- }
41082
- simpleValidate() {
41083
- return noopValidator(1);
41084
- }
41085
40996
  render() {
41086
40997
  const image = _.extend({}, defaultImage, this.props.image);
41087
40998
 
@@ -41107,7 +41018,6 @@ class Measurer extends React.Component {
41107
41018
  }));
41108
41019
  }
41109
41020
  }
41110
- Measurer.validate = noopValidator;
41111
41021
  Measurer.defaultProps = {
41112
41022
  box: [480, 480],
41113
41023
  image: {},
@@ -41141,7 +41051,9 @@ var Measurer$1 = {
41141
41051
  major: 1,
41142
41052
  minor: 0
41143
41053
  },
41144
- propUpgrades: propUpgrades
41054
+ propUpgrades: propUpgrades,
41055
+ // TODO: things that aren't interactive shouldn't need validators
41056
+ validator: () => noopValidator(1)
41145
41057
  };
41146
41058
 
41147
41059
  /**
@@ -42008,17 +41920,11 @@ class Molecule extends React.Component {
42008
41920
  }
42009
41921
  Molecule.contextType = PerseusI18nContext;
42010
41922
  class MoleculeWidget extends React.Component {
42011
- // TODO (LEMS-2396): remove validation logic from widgets that don't validate
42012
- simpleValidate() {
42013
- return noopValidator();
42014
- }
42015
- getUserInput() {
42016
- return [];
42017
- }
42018
-
42019
- // TODO (LEMS-2396): remove validation logic from widgets that don't validate
42020
- validate() {
42021
- 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;
42022
41928
  }
42023
41929
  render() {
42024
41930
  return /*#__PURE__*/React.createElement(Molecule, {
@@ -42035,7 +41941,8 @@ var Molecule$1 = {
42035
41941
  name: "molecule-renderer",
42036
41942
  displayName: "Molecule renderer",
42037
41943
  hidden: true,
42038
- widget: MoleculeWidget
41944
+ widget: MoleculeWidget,
41945
+ validator: () => noopValidator()
42039
41946
  };
42040
41947
 
42041
41948
  function numberLineValidator(state, rubric) {
@@ -42180,18 +42087,16 @@ const TickMarks = Graphie.createSimpleClass((graphie, props) => {
42180
42087
  results.push(graphie.line([x, -0.2], [x, 0.2]));
42181
42088
  const labelTicks = props.labelTicks;
42182
42089
  if (labelTicks || props.labelStyle === "decimal ticks") {
42183
- 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
+ }
42184
42097
  }
42185
42098
  }
42186
42099
 
42187
- // Render the text labels
42188
- results.push(graphie.style(props.isMobile ? {
42189
- color: KhanColors.BLUE
42190
- } : {}, () => _label(graphie, props.labelStyle, leftLabel, leftLabel, base)));
42191
- results.push(graphie.style(props.isMobile ? {
42192
- color: KhanColors.BLUE
42193
- } : {}, () => _label(graphie, props.labelStyle, rightLabel, rightLabel, base)));
42194
-
42195
42100
  // Render the labels' lines
42196
42101
  graphie.style({
42197
42102
  stroke: KhanColors.BLUE,
@@ -42466,9 +42371,6 @@ class NumberLine extends React.Component {
42466
42371
  });
42467
42372
  };
42468
42373
  }
42469
- static validate(state, rubric) {
42470
- return numberLineValidator(state, rubric);
42471
- }
42472
42374
  focus() {
42473
42375
  if (this.props.isTickCtrl) {
42474
42376
  // eslint-disable-next-line react/no-string-refs
@@ -42498,9 +42400,6 @@ class NumberLine extends React.Component {
42498
42400
  divisionRange: this.props.divisionRange
42499
42401
  };
42500
42402
  }
42501
- simpleValidate(rubric) {
42502
- return numberLineValidator(this.getUserInput(), rubric);
42503
- }
42504
42403
  render() {
42505
42404
  const {
42506
42405
  strings
@@ -42619,7 +42518,8 @@ var NumberLine$1 = {
42619
42518
  displayName: "Number line",
42620
42519
  widget: NumberLine,
42621
42520
  transform: numberLineTransform,
42622
- staticTransform: staticTransform$1
42521
+ staticTransform: staticTransform$1,
42522
+ validator: numberLineValidator
42623
42523
  };
42624
42524
 
42625
42525
  function ordererValidator(userInput, rubric) {
@@ -43052,9 +42952,6 @@ class Orderer extends React.Component {
43052
42952
  });
43053
42953
  };
43054
42954
  }
43055
- static validate(userInput, rubric) {
43056
- return ordererValidator(userInput, rubric);
43057
- }
43058
42955
  UNSAFE_componentWillReceiveProps(nextProps) {
43059
42956
  if (!_.isEqual(this.props.current, nextProps.current)) {
43060
42957
  this.setState({
@@ -43069,9 +42966,6 @@ class Orderer extends React.Component {
43069
42966
  })
43070
42967
  };
43071
42968
  }
43072
- simpleValidate(rubric) {
43073
- return ordererValidator(this.getUserInput(), rubric);
43074
- }
43075
42969
  render() {
43076
42970
  // This is the card we are currently dragging
43077
42971
  const dragging = this.state.dragging && /*#__PURE__*/React.createElement(Card
@@ -43189,31 +43083,22 @@ var Orderer$1 = {
43189
43083
  displayName: "Orderer",
43190
43084
  hidden: true,
43191
43085
  widget: Orderer,
43192
- isLintable: true
43086
+ isLintable: true,
43087
+ validator: ordererValidator
43193
43088
  };
43194
43089
 
43195
43090
  /* eslint-disable @khanacademy/ts-no-error-suppressions */
43196
43091
  class PassageRefTarget extends React.Component {
43197
43092
  constructor(..._args) {
43198
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;
43199
43097
  // TODO passage-ref-target isn't interactive; remove
43200
43098
  this.change = (...args) => {
43201
43099
  return change.apply(this, args);
43202
43100
  };
43203
43101
  }
43204
- // TODO (LEMS-2396): remove validation logic from widgets that don't validate
43205
- static validate() {
43206
- return noopValidator();
43207
- }
43208
-
43209
- // TODO passage-ref-target isn't interactive; remove
43210
- getUserInput() {
43211
- return null;
43212
- }
43213
- // TODO (LEMS-2396): remove validation logic from widgets that don't validate
43214
- simpleValidate() {
43215
- return noopValidator();
43216
- }
43217
43102
  render() {
43218
43103
  return /*#__PURE__*/React.createElement(Renderer, {
43219
43104
  content: this.props.content
@@ -43247,7 +43132,9 @@ var PassageRefTarget$1 = {
43247
43132
  major: 0,
43248
43133
  minor: 0
43249
43134
  },
43250
- isLintable: true
43135
+ isLintable: true,
43136
+ // TODO: things that aren't interactive shouldn't need validators
43137
+ validator: () => noopValidator()
43251
43138
  };
43252
43139
 
43253
43140
  /**
@@ -43260,6 +43147,9 @@ class PhetSimulation extends React.Component {
43260
43147
  super(props);
43261
43148
  this.iframeRef = /*#__PURE__*/React.createRef();
43262
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;
43263
43153
  this.state = {
43264
43154
  url: null,
43265
43155
  banner: null
@@ -43302,11 +43192,6 @@ class PhetSimulation extends React.Component {
43302
43192
  await this.updateSimState(this.props.url);
43303
43193
  }
43304
43194
  }
43305
-
43306
- // TODO (LEMS-2396): remove validation logic from widgets that don't validate
43307
- getUserInput() {
43308
- return null;
43309
- }
43310
43195
  async updateSimState(urlString) {
43311
43196
  const url = makeSafeUrl(urlString, this.locale);
43312
43197
  if (url === null) {
@@ -44205,9 +44090,6 @@ class Plotter extends React.Component {
44205
44090
  });
44206
44091
  };
44207
44092
  }
44208
- static validate(userInput, rubric) {
44209
- return plotterValidator(userInput, rubric);
44210
- }
44211
44093
  componentDidMount() {
44212
44094
  this._isMounted = true;
44213
44095
  this.setupGraphie(this.state);
@@ -44234,9 +44116,6 @@ class Plotter extends React.Component {
44234
44116
  getUserInput() {
44235
44117
  return this.state.values;
44236
44118
  }
44237
- simpleValidate(rubric) {
44238
- return plotterValidator(this.getUserInput(), rubric);
44239
- }
44240
44119
  render() {
44241
44120
  // TODO(kevinb) actually compute the size of the graphie correctly and
44242
44121
  // make it that size so we don't have to add extra padding. The value
@@ -44276,7 +44155,8 @@ var Plotter$1 = {
44276
44155
  displayName: "Plotter",
44277
44156
  hidden: true,
44278
44157
  widget: Plotter,
44279
- staticTransform: staticTransform
44158
+ staticTransform: staticTransform,
44159
+ validator: plotterValidator
44280
44160
  };
44281
44161
 
44282
44162
  /**
@@ -44392,9 +44272,6 @@ class Sorter extends React.Component {
44392
44272
  this.refs.sortable.moveOptionToIndex(option, index);
44393
44273
  };
44394
44274
  }
44395
- static validate(userInput, rubric) {
44396
- return sorterValidator(userInput, rubric);
44397
- }
44398
44275
  componentDidMount() {
44399
44276
  this._isMounted = true;
44400
44277
  }
@@ -44410,9 +44287,6 @@ class Sorter extends React.Component {
44410
44287
  changed: this.state.changed
44411
44288
  };
44412
44289
  }
44413
- simpleValidate(rubric) {
44414
- return sorterValidator(this.getUserInput(), rubric);
44415
- }
44416
44290
  render() {
44417
44291
  const options = shuffle(this.props.correct, this.props.problemNum, /* ensurePermuted */true);
44418
44292
  const {
@@ -44446,7 +44320,8 @@ var Sorter$1 = {
44446
44320
  name: "sorter",
44447
44321
  displayName: "Sorter",
44448
44322
  widget: Sorter,
44449
- isLintable: true
44323
+ isLintable: true,
44324
+ validator: sorterValidator
44450
44325
  };
44451
44326
 
44452
44327
  function tableValidator(state, rubric, strings) {
@@ -44625,15 +44500,9 @@ class Table extends React.Component {
44625
44500
  }, cb);
44626
44501
  };
44627
44502
  }
44628
- static validate(state, rubric, strings) {
44629
- return tableValidator(state, rubric, strings);
44630
- }
44631
44503
  getUserInput() {
44632
44504
  return _.map(this.props.answers, _.clone);
44633
44505
  }
44634
- simpleValidate(rubric) {
44635
- return tableValidator(this.getUserInput(), rubric, this.context.strings);
44636
- }
44637
44506
  getDOMNodeForPath(path) {
44638
44507
  const inputID = getRefForPath(path);
44639
44508
  // eslint-disable-next-line react/no-string-refs
@@ -44734,7 +44603,8 @@ var Table$1 = {
44734
44603
  widget: Table,
44735
44604
  transform: propTransform,
44736
44605
  hidden: true,
44737
- isLintable: true
44606
+ isLintable: true,
44607
+ validator: tableValidator
44738
44608
  };
44739
44609
 
44740
44610
  /**
@@ -44822,26 +44692,14 @@ const IS_VIMEO = /(vimeo\.com)/;
44822
44692
  class Video extends React.Component {
44823
44693
  constructor(..._args) {
44824
44694
  super(..._args);
44825
- this.getUserInput = () => {
44826
- return null;
44827
- };
44695
+ // this just helps with TS weak typing when a Widget
44696
+ // doesn't implement any Widget methods
44697
+ this.isWidget = true;
44828
44698
  this.change = (...args) => {
44829
44699
  // @ts-expect-error - TS2345 - Argument of type 'readonly unknown[]' is not assignable to parameter of type 'any[]'.
44830
44700
  return change.apply(this, args);
44831
44701
  };
44832
44702
  }
44833
- /**
44834
- * This is the widget's grading function.
44835
- * Points for videos are tallied by the embedded video itself, in the case
44836
- * of Khan Academy videos.
44837
- */ // TODO (LEMS-2396): remove validation logic from widgets that don't validate
44838
- static validate() {
44839
- return noopValidator();
44840
- }
44841
- // TODO (LEMS-2396): remove validation logic from widgets that don't validate
44842
- simpleValidate() {
44843
- return noopValidator();
44844
- }
44845
44703
  render() {
44846
44704
  const {
44847
44705
  InitialRequestUrl
@@ -44901,7 +44759,9 @@ var Video$1 = {
44901
44759
  displayName: "Video",
44902
44760
  defaultAlignment: "block",
44903
44761
  supportedAlignments: ["block", "float-left", "float-right", "full-width"],
44904
- widget: Video
44762
+ widget: Video,
44763
+ // TODO: things that aren't interactive shouldn't need validators
44764
+ validator: () => noopValidator()
44905
44765
  };
44906
44766
 
44907
44767
  /* eslint-disable import/no-named-as-default */
@@ -44945,7 +44805,7 @@ const init = function init(options) {
44945
44805
 
44946
44806
  // This file is processed by a Rollup plugin (replace) to inject the production
44947
44807
  const libName = "@khanacademy/perseus";
44948
- const libVersion = "36.1.1";
44808
+ const libVersion = "37.0.0";
44949
44809
  addLibraryVersionToPerseusDebug(libName, libVersion);
44950
44810
 
44951
44811
  const apiVersion = {
@@ -45521,7 +45381,7 @@ class ServerItemRenderer extends React.Component {
45521
45381
  });
45522
45382
 
45523
45383
  // Call the interactionCallback, if it exists, with the current user input data
45524
- (_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());
45525
45385
  };
45526
45386
  // This must be pre-bound otherwise SvgImage's shouldComponentUpdate
45527
45387
  // won't behave correctly and we'll get an infinite loop.
@@ -48285,12 +48145,12 @@ function parsePerseusItem(json) {
48285
48145
  }
48286
48146
  throw new Error("Something went wrong.");
48287
48147
  }
48288
- function buildRandomString() {
48148
+ function buildRandomString(capitalize = false) {
48289
48149
  let randomString = "";
48290
48150
  const randomLength = Math.floor(Math.random() * 8) + 3;
48291
48151
  for (let i = 0; i < randomLength; i++) {
48292
48152
  const randomLetter = String.fromCharCode(97 + Math.floor(Math.random() * 26));
48293
- randomString += randomLetter;
48153
+ randomString += capitalize && i === 0 ? randomLetter.toUpperCase() : randomLetter;
48294
48154
  }
48295
48155
  return randomString;
48296
48156
  }
@@ -48298,9 +48158,12 @@ function buildRandomPhrase() {
48298
48158
  const phrases = [];
48299
48159
  const randomLength = Math.floor(Math.random() * 10) + 5;
48300
48160
  for (let i = 0; i < randomLength; i++) {
48301
- phrases.push(buildRandomString());
48161
+ phrases.push(buildRandomString(i === 0));
48302
48162
  }
48303
- 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]}`;
48304
48167
  }
48305
48168
  function buildTestData(testObject) {
48306
48169
  return `{"data":{"assessmentItem":{"__typename":"AssessmentItemOrError","error":null,"item":{"__typename":"AssessmentItem","id":"x890b3c70f3e8f4a6","itemData":"${testObject}","problemType":"Type 1","sha":"c7284a3ad65214b4e62bccce236d92f7f5d35941"}}}}`;
@@ -48505,5 +48368,5 @@ const EditorJsonify = {
48505
48368
  }
48506
48369
  };
48507
48370
 
48508
- export { ApiOptions, ArticleRenderer, BaseRadio, Categorizer$1 as Categorizer, changeable as Changeable, ClassNames, dependencies as Dependencies, EditorJsonify, Expression, util as GrapherUtil, Grapher$1 as GrapherWidget, HintRenderer, HintsRenderer, InputNumber$1 as InputNumber, InteractiveGraph$1 as InteractiveGraphWidget, ItemExtras, JiptParagraphs, KhanColors, KhanMath, context as LoadingContext, Log, Matrix$1 as MatrixWidget, MultiItems, NumericInput$1 as NumericInput, PerseusExpressionAnswerFormConsidered, PerseusI18nContext, PerseusI18nContextProvider, PerseusMarkdown, Plotter$1 as PlotterWidget, QUESTION_WIDGETS, Radio, Renderer, ref as ServerItemRenderer, sizingUtils as SizingUtils, Table$1 as TableWidget, Util, denylist as WIDGET_PROP_DENYLIST, widgets$1 as Widgets, addWidget, apiVersion, buildEmptyItemTreeForShape, buildMapper, components, constants, contentHasWidgetType, convertWidgetNameToEnum, displaySigFigs, generateTestCategorizerWidget, generateTestExpressionWidget, generateTestInteractiveGraphWidget, generateTestNumericInputWidget, generateTestPerseusItem, generateTestRadioWidget, genericPerseusItemData, getAngleCoords, getAnswerFromUserInput, getAnswersFromWidgets, getCircleCoords, getCorrectAnswerForWidgetId, getImagesWithoutAltData, getLineCoords, getLinearSystemCoords, getPointCoords, getPolygonCoords, getQuadraticCoords, getSegmentCoords, getSinusoidCoords, getValidWidgetIds, getWidgetFromWidgetMap, getWidgetIdsFromContent, getWidgetIdsFromContentByType, getWidgetTypeByWidgetId, getWidgetsFromWidgetMap, getWidgetsMapFromItemData, globalConstants, globalStyles, iconPaths as icons, init, injectWidgets, interactiveSizes$1 as interactiveSizes, isItemRenderableByVersion, isWidgetIdInContent, isWrongAnswerSupported, itemDataVersion, itemToTree, ItemVersion as itemVersion, libVersion, lockedFigureColors, lockedFigureFillStyles, makeSafeUrl, mediaQueries, parsePerseusItem, plotterPlotTypes, preprocessTex, registerAllWidgetsForTesting, shapes, shouldHaveIndividualAnswer, traverse, usePerseusI18n, violatingWidgets, allWidgets as widgets };
48371
+ export { ApiOptions, ArticleRenderer, BaseRadio, Categorizer$1 as Categorizer, changeable as Changeable, ClassNames, dependencies as Dependencies, EditorJsonify, Expression, util as GrapherUtil, Grapher$1 as GrapherWidget, HintRenderer, HintsRenderer, InputNumber$1 as InputNumber, InteractiveGraph$1 as InteractiveGraphWidget, ItemExtras, JiptParagraphs, KhanColors, KhanMath, context as LoadingContext, Log, Matrix$1 as MatrixWidget, MultiItems, NumericInput$1 as NumericInput, PerseusExpressionAnswerFormConsidered, PerseusI18nContext, PerseusI18nContextProvider, PerseusMarkdown, Plotter$1 as PlotterWidget, QUESTION_WIDGETS, Radio, Renderer, ref as ServerItemRenderer, sizingUtils as SizingUtils, Table$1 as TableWidget, Util, denylist as WIDGET_PROP_DENYLIST, widgets$1 as Widgets, addWidget, apiVersion, buildEmptyItemTreeForShape, buildMapper, components, constants, contentHasWidgetType, convertWidgetNameToEnum, displaySigFigs, generateTestCategorizerWidget, generateTestExpressionWidget, generateTestInteractiveGraphWidget, generateTestNumericInputWidget, generateTestPerseusItem, generateTestRadioWidget, genericPerseusItemData, getAngleCoords, getAnswerFromUserInput, getAnswersFromWidgets, getCircleCoords, getClockwiseAngle, getCorrectAnswerForWidgetId, getImagesWithoutAltData, getLineCoords, getLinearSystemCoords, getPointCoords, getPolygonCoords, getQuadraticCoords, getSegmentCoords, getSinusoidCoords, getValidWidgetIds, getWidgetFromWidgetMap, getWidgetIdsFromContent, getWidgetIdsFromContentByType, getWidgetTypeByWidgetId, getWidgetsFromWidgetMap, getWidgetsMapFromItemData, globalConstants, globalStyles, iconPaths as icons, init, injectWidgets, interactiveSizes$1 as interactiveSizes, isItemRenderableByVersion, isWidgetIdInContent, isWrongAnswerSupported, itemDataVersion, itemToTree, ItemVersion as itemVersion, libVersion, lockedFigureColors, lockedFigureFillStyles, makeSafeUrl, mediaQueries, parsePerseusItem, plotterPlotTypes, preprocessTex, registerAllWidgetsForTesting, shapes, shouldHaveIndividualAnswer, traverse, usePerseusI18n, violatingWidgets, allWidgets as widgets };
48509
48372
  //# sourceMappingURL=index.js.map