@khanacademy/perseus 52.0.1 → 53.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 (60) hide show
  1. package/dist/components/number-input.d.ts +2 -0
  2. package/dist/components/range-input.d.ts +1 -0
  3. package/dist/components/svg-image.d.ts +2 -2
  4. package/dist/es/index.css +0 -30
  5. package/dist/es/index.js +385 -865
  6. package/dist/es/index.js.map +1 -1
  7. package/dist/index.css +0 -30
  8. package/dist/index.d.ts +1 -2
  9. package/dist/index.js +383 -868
  10. package/dist/index.js.map +1 -1
  11. package/dist/perseus-api.d.ts +0 -1
  12. package/dist/renderer.d.ts +1 -1
  13. package/dist/styles/constants.d.ts +0 -2
  14. package/dist/types.d.ts +3 -20
  15. package/dist/util/math-utils.d.ts +1 -0
  16. package/dist/util/scoring.d.ts +0 -15
  17. package/dist/util/test-utils.d.ts +1 -1
  18. package/dist/widget-ai-utils/mock-widget/prompt-utils.d.ts +1 -1
  19. package/dist/widget-container.d.ts +1 -1
  20. package/dist/widgets/categorizer/categorizer.d.ts +0 -3
  21. package/dist/widgets/cs-program/cs-program.d.ts +2 -3
  22. package/dist/widgets/definition/definition.d.ts +0 -2
  23. package/dist/widgets/deprecated-standin/deprecated-standin.d.ts +0 -1
  24. package/dist/widgets/dropdown/dropdown.d.ts +2 -4
  25. package/dist/widgets/explanation/explanation.d.ts +0 -2
  26. package/dist/widgets/expression/expression.d.ts +1 -5
  27. package/dist/widgets/grapher/grapher.d.ts +0 -2
  28. package/dist/widgets/group/group.d.ts +0 -4
  29. package/dist/widgets/iframe/iframe.d.ts +0 -2
  30. package/dist/widgets/image/image.d.ts +0 -3
  31. package/dist/widgets/input-number/input-number.d.ts +0 -3
  32. package/dist/widgets/interaction/interaction.d.ts +0 -1
  33. package/dist/widgets/interactive-graph.d.ts +0 -2
  34. package/dist/widgets/interactive-graphs/backgrounds/axis-ticks.d.ts +1 -0
  35. package/dist/widgets/label-image/label-image.d.ts +2 -2
  36. package/dist/widgets/matcher/matcher.d.ts +0 -2
  37. package/dist/widgets/matrix/matrix.d.ts +0 -3
  38. package/dist/widgets/measurer/measurer.d.ts +0 -1
  39. package/dist/widgets/mock-widgets/mock-widget-types.d.ts +0 -6
  40. package/dist/widgets/mock-widgets/mock-widget.d.ts +2 -5
  41. package/dist/widgets/molecule/molecule.d.ts +0 -1
  42. package/dist/widgets/number-line/number-line.d.ts +2 -3
  43. package/dist/widgets/numeric-input/numeric-input.d.ts +2 -3
  44. package/dist/widgets/orderer/orderer.d.ts +0 -3
  45. package/dist/widgets/passage/passage.d.ts +0 -1
  46. package/dist/widgets/passage-ref/passage-ref.d.ts +0 -2
  47. package/dist/widgets/passage-ref-target/passage-ref-target.d.ts +0 -2
  48. package/dist/widgets/plotter/plotter.d.ts +0 -3
  49. package/dist/widgets/radio/radio.d.ts +0 -3
  50. package/dist/widgets/sorter/sorter.d.ts +2 -4
  51. package/dist/widgets/table/table.d.ts +0 -3
  52. package/dist/widgets/video/video.d.ts +0 -3
  53. package/dist/widgets.d.ts +1 -32
  54. package/package.json +10 -10
  55. package/dist/renderer-util.d.ts +0 -14
  56. package/dist/widgets/__shared__/score-noop.d.ts +0 -11
  57. package/dist/widgets/group/score-group.d.ts +0 -4
  58. package/dist/widgets/group/validate-group.d.ts +0 -4
  59. package/dist/widgets/mock-widgets/score-mock-widget.d.ts +0 -5
  60. package/dist/widgets/mock-widgets/validate-mock-widget.d.ts +0 -4
package/dist/index.js CHANGED
@@ -6,7 +6,6 @@ var KAS = require('@khanacademy/kas');
6
6
  var mathInput = require('@khanacademy/math-input');
7
7
  var perseusCore = require('@khanacademy/perseus-core');
8
8
  var PerseusLinter = require('@khanacademy/perseus-linter');
9
- var perseusScore = require('@khanacademy/perseus-score');
10
9
  var wonderBlocksCore = require('@khanacademy/wonder-blocks-core');
11
10
  var Tooltip$1 = require('@khanacademy/wonder-blocks-tooltip');
12
11
  var wonderBlocksTypography = require('@khanacademy/wonder-blocks-typography');
@@ -21,6 +20,7 @@ var wonderBlocksTokens = require('@khanacademy/wonder-blocks-tokens');
21
20
  var $ = require('jquery');
22
21
  var uuid = require('uuid');
23
22
  var PropTypes = require('prop-types');
23
+ var perseusScore = require('@khanacademy/perseus-score');
24
24
  var wonderStuffCore = require('@khanacademy/wonder-stuff-core');
25
25
  var wonderBlocksProgressSpinner = require('@khanacademy/wonder-blocks-progress-spinner');
26
26
  var kmath = require('@khanacademy/kmath');
@@ -1612,7 +1612,6 @@ const ClassNames = {
1612
1612
  SELECTED: "perseus-radio-selected",
1613
1613
  OPTION_CONTENT: "perseus-radio-option-content"
1614
1614
  },
1615
- INTERACTIVE: "perseus-interactive",
1616
1615
  CORRECT: "perseus-correct",
1617
1616
  INCORRECT: "perseus-incorrect",
1618
1617
  UNANSWERED: "perseus-unanswered",
@@ -1868,7 +1867,6 @@ class Expression extends React__namespace.Component {
1868
1867
  // eslint-disable-next-line react/no-string-refs
1869
1868
  , {
1870
1869
  ref: "input",
1871
- className: ClassNames.INTERACTIVE,
1872
1870
  value: this.props.value,
1873
1871
  onChange: this.changeAndTrack,
1874
1872
  convertDotToTimes: this.props.times,
@@ -1970,7 +1968,6 @@ var Expression$1 = {
1970
1968
  name: "expression",
1971
1969
  displayName: "Expression / Equation",
1972
1970
  accessible: true,
1973
- defaultAlignment: "inline-block",
1974
1971
  widget: ExpressionWithDependencies,
1975
1972
  transform: widgetOptions => {
1976
1973
  const {
@@ -1995,12 +1992,6 @@ var Expression$1 = {
1995
1992
  propUpgrades: perseusCore.expressionLogic.widgetOptionsUpgrades,
1996
1993
  // For use by the editor
1997
1994
  isLintable: true,
1998
- // TODO(LEMS-2656): remove TS suppression
1999
- // @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusExpressionUserInput'.
2000
- scorer: perseusScore.scoreExpression,
2001
- // TODO(LEMS-2656): remove TS suppression
2002
- // @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusExpressionUserInput'.
2003
- validator: perseusScore.validateExpression,
2004
1995
  getPublicWidgetOptions: perseusCore.getExpressionPublicWidgetOptions,
2005
1996
  // TODO(LEMS-2656): remove TS suppression
2006
1997
  // @ts-expect-error: Type 'Rubric' is not assignable to type 'PerseusExpressionRubric'.
@@ -3280,10 +3271,6 @@ const pureMdMax$1 = "1023px";
3280
3271
  const pureLgMax$1 = "1279px";
3281
3272
  // @tableBackgroundAccent: #f9f9f9; // for striping
3282
3273
  const tableBackgroundAccent = "#F9F9F9";
3283
- // @zIndexAboveScratchpad: @zIndexScratchPad + 1;
3284
- const zIndexAboveScratchpad$1 = 2;
3285
- // @zIndexInteractiveComponent: @zIndexAboveScratchpad + 1;
3286
- const zIndexInteractiveComponent$1 = 3;
3287
3274
  // @phoneMargin: 16px;
3288
3275
  const phoneMargin = 16;
3289
3276
  const negativePhoneMargin = -16;
@@ -3327,8 +3314,6 @@ var constants = /*#__PURE__*/Object.freeze({
3327
3314
  pureMdMax: pureMdMax$1,
3328
3315
  pureLgMax: pureLgMax$1,
3329
3316
  tableBackgroundAccent: tableBackgroundAccent,
3330
- zIndexAboveScratchpad: zIndexAboveScratchpad$1,
3331
- zIndexInteractiveComponent: zIndexInteractiveComponent$1,
3332
3317
  phoneMargin: phoneMargin,
3333
3318
  negativePhoneMargin: negativePhoneMargin,
3334
3319
  hintBorderWidth: hintBorderWidth,
@@ -17623,130 +17608,162 @@ class QuestionParagraph extends React__namespace.Component {
17623
17608
  }
17624
17609
  }
17625
17610
 
17626
- const noScore = {
17627
- type: "points",
17628
- earned: 0,
17629
- total: 0,
17630
- message: null
17631
- };
17632
-
17633
- /**
17634
- * If a widget says that it is empty once it is graded.
17635
- * Trying to encapsulate references to the score format.
17636
- */
17637
- function scoreIsEmpty(score) {
17638
- // HACK(benkomalo): ugh. this isn't great; the Perseus score objects
17639
- // overload the type "invalid" for what should probably be three
17640
- // distinct cases:
17641
- // - truly empty or not fully filled out
17642
- // - invalid or malformed inputs
17643
- // - "almost correct" like inputs where the widget wants to give
17644
- // feedback (e.g. a fraction needs to be reduced, or `pi` should
17645
- // be used instead of 3.14)
17646
- //
17647
- // Unfortunately the coercion happens all over the place, as these
17648
- // Perseus style score objects are created *everywhere* (basically
17649
- // in every widget), so it's hard to change now. We assume that
17650
- // anything with a "message" is not truly empty, and one of the
17651
- // latter two cases for now.
17652
- return score.type === "invalid" && (!score.message || score.message.length === 0);
17653
- }
17654
-
17655
17611
  /**
17656
- * Combine two score objects.
17612
+ * TranslationLinter asynchronously calls Khan Academy's poentry linter.
17657
17613
  *
17658
- * Given two score objects for two different widgets, combine them so that
17659
- * if one is wrong, the total score is wrong, etc.
17614
+ * The poentry linter checks for issues that prevent content from being
17615
+ * translated. This linter is KA-specific and so TranslationLinter does not do
17616
+ * anything in non-KA environments.
17660
17617
  */
17661
- function combineScores(scoreA, scoreB) {
17662
- let message;
17663
- if (scoreA.type === "points" && scoreB.type === "points") {
17664
- if (scoreA.message && scoreB.message && scoreA.message !== scoreB.message) {
17665
- // TODO(alpert): Figure out how to combine messages usefully
17666
- message = null;
17667
- } else {
17668
- message = scoreA.message || scoreB.message;
17618
+
17619
+ // A LintCB is called once the linter has been run.
17620
+
17621
+ // msec to wait before we actually call the linter after receiving the
17622
+ // last call. The timer is reset if the lint is called during the wait.
17623
+ const DEBOUNCE_TIMEOUT = 1000;
17624
+ class TranslationLinter {
17625
+ // To avoid linting when nothing has changed.
17626
+ previousContent = null;
17627
+
17628
+ /**
17629
+ * Runs the poentry linter on the given perseus string asynchronously.
17630
+ *
17631
+ * It doesn't do anything unless we're in a KA environment.
17632
+ *
17633
+ * onLintErrorsGenerated is called even if there are no errors. It is not
17634
+ * called if perseusStr is unchanged from the last call.
17635
+ *
17636
+ * Linting is relatively expensive -- so we debounce linting each instance
17637
+ * for some amount of time.
17638
+ */
17639
+ runLinter = _.debounce((perseusStr, onLintErrorsGenerated) => {
17640
+ // TODO(FEI-5003): Do we still need to check for 'KA'?
17641
+ if (typeof KA === "undefined") {
17642
+ return;
17669
17643
  }
17670
- return {
17671
- type: "points",
17672
- earned: scoreA.earned + scoreB.earned,
17673
- total: scoreA.total + scoreB.total,
17674
- message: message
17675
- };
17676
- }
17677
- if (scoreA.type === "points" && scoreB.type === "invalid") {
17678
- return scoreB;
17679
- }
17680
- if (scoreA.type === "invalid" && scoreB.type === "points") {
17681
- return scoreA;
17682
- }
17683
- if (scoreA.type === "invalid" && scoreB.type === "invalid") {
17684
- if (scoreA.message && scoreB.message && scoreA.message !== scoreB.message) {
17685
- // TODO(alpert): Figure out how to combine messages usefully
17686
- message = null;
17687
- } else {
17688
- message = scoreA.message || scoreB.message;
17644
+ if (perseusStr === this.previousContent) {
17645
+ return;
17689
17646
  }
17690
- return {
17691
- type: "invalid",
17692
- message: message
17693
- };
17694
- }
17647
+ this.previousContent = perseusStr;
17648
+ if (perseusStr === "") {
17649
+ onLintErrorsGenerated([]);
17650
+ return;
17651
+ }
17652
+ fetch("/api/internal/translations/lint_poentry?preview=1&lang=en", {
17653
+ headers: {
17654
+ "Content-Type": "application/json"
17655
+ },
17656
+ body: JSON.stringify({
17657
+ // The poentry linter verifies that the translation from
17658
+ // the original text to the translated text is sane. We
17659
+ // want to ensure that the translation from English to
17660
+ // English is sane, so msgid === mstr.
17661
+ msgid: perseusStr,
17662
+ // msgid is the original text
17663
+ msgstr: perseusStr,
17664
+ // msgstr is the translated text
17665
+ format: "perseus_text",
17666
+ filename: ""
17667
+ }),
17668
+ method: "POST"
17669
+ }).then(response => {
17670
+ if (response.status >= 400) {
17671
+ return {
17672
+ status: "error",
17673
+ message: "Could not run i18n linter."
17674
+ };
17675
+ }
17676
+ return response.json();
17677
+ }, rejection => {
17678
+ return {
17679
+ status: "error",
17680
+ message: "Could not run i18n linter."
17681
+ };
17682
+ }).then(json => {
17683
+ if (json.status === "error") {
17684
+ onLintErrorsGenerated(["Some part of this text makes it untranslatable. " + "The specific message from the i18n linter was: " + json.message.replace(/\n/g, " ")]);
17685
+ } else {
17686
+ onLintErrorsGenerated([]);
17687
+ }
17688
+ });
17689
+ }, DEBOUNCE_TIMEOUT);
17695
17690
 
17696
17691
  /**
17697
- * The above checks cover all combinations of score type, so if we get here
17698
- * then something is amiss with our inputs.
17692
+ * Applies an array of errors generated by linters without position
17693
+ * information (TranslationLinter and the legacy getSaveWarnings()) to the
17694
+ * top of a Perseus tree.
17699
17695
  */
17700
- throw new perseusCore.PerseusError("PerseusScore with unknown type encountered", perseusCore.Errors.InvalidInput, {
17701
- metadata: {
17702
- scoreA: JSON.stringify(scoreA),
17703
- scoreB: JSON.stringify(scoreB)
17696
+ applyLintErrors(parsedMarkdown, translationLintErrors) {
17697
+ // These lint errors do not have position data associated with
17698
+ // them, so we just plop them at the top.
17699
+ if (translationLintErrors.length) {
17700
+ const errorText = translationLintErrors.join("\n\n");
17701
+ parsedMarkdown.unshift({
17702
+ content: {
17703
+ type: "text",
17704
+ content: ""
17705
+ },
17706
+ insideTable: false,
17707
+ message: errorText,
17708
+ ruleName: "legacy-error",
17709
+ severity: PerseusLinter.Rule.Severity.ERROR,
17710
+ type: "lint"
17711
+ });
17704
17712
  }
17705
- });
17706
- }
17707
- function flattenScores(widgetScoreMap) {
17708
- return Object.values(widgetScoreMap).reduce(combineScores, noScore);
17709
- }
17710
- function isCorrect(score) {
17711
- return score.type === "points" && score.earned >= score.total;
17713
+ }
17712
17714
  }
17713
- function keScoreFromPerseusScore(score,
17714
- // It's weird, but this is what we're passing it
17715
- guess, state) {
17716
- if (score.type === "points") {
17717
- return {
17718
- empty: false,
17719
- correct: isCorrect(score),
17720
- message: score.message,
17721
- guess: guess,
17722
- state: state
17723
- };
17715
+
17716
+ /**
17717
+ * Preprocess TeX code to convert things that MathJax doesn't know how to handle
17718
+ * to things is does.
17719
+ */
17720
+ var preprocessTex = (texCode => texCode
17721
+ // Replace uses of \begin{align}...\end{align} which KaTeX doesn't
17722
+ // support (yet) with \begin{aligned}...\end{aligned} which renders
17723
+ // the same is supported by KaTeX. It does the same for align*.
17724
+ // TODO(kevinb) update content to use aligned instead of align.
17725
+ // TODO(LEMS-1608) Remove this replacement as MathJax supports the
17726
+ // "align" macro correctly (and, in fact, it is not synonymous with
17727
+ // "aligned").
17728
+ .replace(/\{align[*]?\}/g, "{aligned}")
17729
+ // Replace non-breaking spaces with regular spaces.
17730
+ .replace(/[\u00a0]/g, " "));
17731
+
17732
+ const {
17733
+ interactiveSizes
17734
+ } = constants;
17735
+
17736
+ // Note: these size cutoffs represent content-width cutoffs as specified in
17737
+ // http://zpl.io/1mVmvU (broken link - we no longer use Zeplin)
17738
+ // TODO(benkomalo): these values aren't used in JS outside of this file, but
17739
+ // are coupled to the values in styles/styles.less - DRY it up at some point
17740
+ const smMax = articleMaxWidthTableInPx;
17741
+ const mdMax = articleMaxWidthInPx;
17742
+ const containerSizeClass = {
17743
+ SMALL: "small",
17744
+ MEDIUM: "medium",
17745
+ LARGE: "large",
17746
+ XLARGE: "xlarge"
17747
+ };
17748
+ const getClassFromWidth = width => {
17749
+ if (!width) {
17750
+ return containerSizeClass.MEDIUM;
17724
17751
  }
17725
- if (score.type === "invalid") {
17726
- return {
17727
- empty: true,
17728
- correct: false,
17729
- message: score.message,
17730
- suppressAlmostThere: score.suppressAlmostThere,
17731
- guess: guess,
17732
- state: state
17733
- };
17752
+ if (width <= smMax) {
17753
+ return containerSizeClass.SMALL;
17734
17754
  }
17735
- throw new perseusCore.PerseusError(
17736
- // @ts-expect-error - TS2339 - Property 'type' does not exist on type 'never'.
17737
- "Invalid score type: " + score.type, perseusCore.Errors.InvalidInput, {
17738
- metadata: {
17739
- score: JSON.stringify(score),
17740
- guess: JSON.stringify(guess),
17741
- state: JSON.stringify(state)
17742
- }
17743
- });
17744
- }
17755
+ if (width <= mdMax) {
17756
+ return containerSizeClass.MEDIUM;
17757
+ }
17758
+ return containerSizeClass.LARGE;
17759
+ };
17760
+ const getInteractiveBoxFromSizeClass = sizeClass => {
17761
+ if (sizeClass === containerSizeClass.SMALL) {
17762
+ return [interactiveSizes.defaultBoxSizeSmall, interactiveSizes.defaultBoxSizeSmall];
17763
+ }
17764
+ return [interactiveSizes.defaultBoxSize, interactiveSizes.defaultBoxSize];
17765
+ };
17745
17766
 
17746
- const DEFAULT_ALIGNMENT$1 = "block";
17747
- // NOTE(kevinb): "default" is not one in `validAlignments`.
17748
- const DEFAULT_SUPPORTED_ALIGNMENTS = ["default"];
17749
- const DEFAULT_STATIC = false;
17750
17767
  const DEFAULT_TRACKING = "";
17751
17768
  const DEFAULT_LINTABLE = false;
17752
17769
  const widgets = {};
@@ -17763,7 +17780,6 @@ const registerWidgets = widgets => {
17763
17780
  widgets.forEach(widget => {
17764
17781
  registerWidget(widget.name, widget);
17765
17782
  });
17766
- validateAlignments();
17767
17783
  };
17768
17784
 
17769
17785
  /**
@@ -17844,12 +17860,6 @@ const getWidget = name => {
17844
17860
  const getWidgetExport = name => {
17845
17861
  return widgets[name] ?? null;
17846
17862
  };
17847
- const getWidgetValidator = name => {
17848
- return widgets[name]?.validator ?? null;
17849
- };
17850
- const getWidgetScorer = name => {
17851
- return widgets[name]?.scorer ?? null;
17852
- };
17853
17863
  const getPublicWidgetOptionsFunction = name => {
17854
17864
  return widgets[name]?.getPublicWidgetOptions ?? (i => i);
17855
17865
  };
@@ -17895,112 +17905,6 @@ const isAccessible = widgetInfo => {
17895
17905
  const getAllWidgetTypes = () => {
17896
17906
  return ___default["default"].keys(widgets);
17897
17907
  };
17898
- const upgradeWidgetInfoToLatestVersion = oldWidgetInfo => {
17899
- const type = oldWidgetInfo.type;
17900
- // NOTE(jeremy): This looks like it could be replaced by fixing types so
17901
- // that `type` is non-optional. But we're seeing this in Sentry today so I
17902
- // suspect we have legacy data (potentially unpublished) and we should
17903
- // figure that out before depending solely on types.
17904
- if (!___default["default"].isString(type)) {
17905
- throw new perseusCore.PerseusError("widget type must be a string, but was: " + type, perseusCore.Errors.Internal);
17906
- }
17907
- const widgetExports = widgets[type];
17908
- if (widgetExports == null) {
17909
- // If we have a widget that isn't registered, we can't upgrade it
17910
- // TODO(aria): Figure out what the best thing to do here would be
17911
- return oldWidgetInfo;
17912
- }
17913
-
17914
- // Unversioned widgets (pre-July 2014) are all implicitly 0.0
17915
- const initialVersion = oldWidgetInfo.version || {
17916
- major: 0,
17917
- minor: 0
17918
- };
17919
- const latestVersion = widgetExports.version || {
17920
- major: 0,
17921
- minor: 0
17922
- };
17923
-
17924
- // If the widget version is later than what we understand (major
17925
- // version is higher than latest, or major versions are equal and minor
17926
- // version is higher than latest), don't perform any upgrades.
17927
- if (initialVersion.major > latestVersion.major || initialVersion.major === latestVersion.major && initialVersion.minor > latestVersion.minor) {
17928
- return oldWidgetInfo;
17929
- }
17930
-
17931
- // We do a clone here so that it's safe to mutate the input parameter
17932
- // in propUpgrades functions (which I will probably accidentally do at
17933
- // some point, and we would like to not break when that happens).
17934
- let newEditorProps = ___default["default"].clone(oldWidgetInfo.options) || {};
17935
- const upgradePropsMap = widgetExports.propUpgrades || {};
17936
-
17937
- // Empty props usually mean a newly created widget by the editor,
17938
- // and are always considerered up-to-date.
17939
- // Mostly, we'd rather not run upgrade functions on props that are
17940
- // not complete.
17941
- if (___default["default"].keys(newEditorProps).length !== 0) {
17942
- // We loop through all the versions after the current version of
17943
- // the loaded widget, up to and including the latest version of the
17944
- // loaded widget, and run the upgrade function to bring our loaded
17945
- // widget's props up to that version.
17946
- // There is a little subtlety here in that we call
17947
- // upgradePropsMap[1] to upgrade *to* version 1,
17948
- // (not from version 1).
17949
- for (let nextVersion = initialVersion.major + 1; nextVersion <= latestVersion.major; nextVersion++) {
17950
- if (upgradePropsMap[String(nextVersion)]) {
17951
- newEditorProps = upgradePropsMap[String(nextVersion)](newEditorProps);
17952
- } else {
17953
- // This is a Log.error because it is unlikely to be hit in
17954
- // local testing, and a Log.error is slightly less scary in
17955
- // prod than a `throw new Error`
17956
- Log.error("No upgrade found for widget. Cannot render.", perseusCore.Errors.Internal, {
17957
- loggedMetadata: {
17958
- type,
17959
- fromMajorVersion: nextVersion - 1,
17960
- toMajorVersion: nextVersion
17961
- }
17962
- });
17963
- // But try to keep going anyways (yolo!)
17964
- // (Throwing an error here would just break the page
17965
- // silently anyways, so that doesn't seem much better
17966
- // than a halfhearted attempt to continue, however
17967
- // shallow...)
17968
- }
17969
- }
17970
- }
17971
-
17972
- // Minor version upgrades (eg. new optional props) don't have
17973
- // transform functions. Instead, we fill in the new props with their
17974
- // defaults.
17975
- const defaultProps = type in editors ? editors[type].defaultProps : {};
17976
- newEditorProps = {
17977
- ...defaultProps,
17978
- ...newEditorProps
17979
- };
17980
- let alignment = oldWidgetInfo.alignment;
17981
-
17982
- // Widgets that support multiple alignments will "lock in" the
17983
- // alignment to the alignment that would be listed first in the
17984
- // select box. If the widget only supports one alignment, the
17985
- // alignment value will likely just end up as "default".
17986
- if (alignment == null || alignment === "default") {
17987
- alignment = getSupportedAlignments(type)[0];
17988
- }
17989
- let widgetStatic = oldWidgetInfo.static;
17990
- if (widgetStatic == null) {
17991
- widgetStatic = DEFAULT_STATIC;
17992
- }
17993
- return ___default["default"].extend({}, oldWidgetInfo, {
17994
- // maintain other info, like type
17995
- // After upgrading we guarantee that the version is up-to-date
17996
- version: latestVersion,
17997
- // Default graded to true (so null/undefined becomes true):
17998
- graded: oldWidgetInfo.graded != null ? oldWidgetInfo.graded : true,
17999
- alignment: alignment,
18000
- static: widgetStatic,
18001
- options: newEditorProps
18002
- });
18003
- };
18004
17908
  const getRendererPropsForWidgetInfo = (widgetInfo, strings, problemNum) => {
18005
17909
  const type = widgetInfo.type;
18006
17910
  const widgetExports = widgets[type];
@@ -18042,70 +17946,6 @@ const traverseChildWidgets$3 = (widgetInfo, traverseRenderer) => {
18042
17946
  return widgetInfo;
18043
17947
  };
18044
17948
 
18045
- /**
18046
- * Handling for the optional alignments for widgets
18047
- * See widget-container.jsx for details on how alignments are implemented.
18048
- */
18049
-
18050
- /**
18051
- * Returns the list of supported alignments for the given (string) widget
18052
- * type. This is used primarily at editing time to display the choices
18053
- * for the user.
18054
- *
18055
- * Supported alignments are given as an array of strings in the exports of
18056
- * a widget's module.
18057
- */
18058
- const getSupportedAlignments = type => {
18059
- const widgetExport = widgets[type];
18060
- // @ts-expect-error - TS2322 - Type 'string[] | readonly Alignment[]' is not assignable to type 'readonly Alignment[]'.
18061
- return widgetExport && widgetExport.supportedAlignments || DEFAULT_SUPPORTED_ALIGNMENTS;
18062
- };
18063
-
18064
- /**
18065
- * For the given (string) widget type, determine the default alignment for
18066
- * the widget. This is used at rendering time to go from "default" alignment
18067
- * to the actual alignment displayed on the screen.
18068
- *
18069
- * The default alignment is given either as a string (called
18070
- * `defaultAlignment`) or a function (called `getDefaultAlignment`) on
18071
- * the exports of a widget's module.
18072
- */
18073
- const getDefaultAlignment = type => {
18074
- const widgetExports = widgets[type];
18075
- let alignment;
18076
- if (!widgetExports) {
18077
- return DEFAULT_ALIGNMENT$1;
18078
- }
18079
- if (widgetExports.getDefaultAlignment) {
18080
- alignment = widgetExports.getDefaultAlignment();
18081
- } else {
18082
- alignment = widgetExports.defaultAlignment;
18083
- }
18084
- return alignment || DEFAULT_ALIGNMENT$1;
18085
- };
18086
- const validAlignments = ["block", "inline-block", "inline", "float-left", "float-right", "full-width"];
18087
-
18088
- /**
18089
- * Used at startup to fail fast if an alignment given by a widget is
18090
- * invalid.
18091
- */
18092
- // TODO(alex): Change this to run as a testcase (vs. being run at runtime)
18093
- // TODO(LP-10707): I think this can be completely removed because our TypeScript types
18094
- // enforce this!
18095
- const validateAlignments = () => {
18096
- ___default["default"].each(widgets, function (widgetInfo) {
18097
- if (widgetInfo.defaultAlignment && !___default["default"].contains(validAlignments, widgetInfo.defaultAlignment)) {
18098
- throw new perseusCore.PerseusError("Widget '" + widgetInfo.displayName + "' has an invalid defaultAlignment value: " + widgetInfo.defaultAlignment, perseusCore.Errors.InvalidInput);
18099
- }
18100
- if (widgetInfo.supportedAlignments) {
18101
- const unknownAlignments = ___default["default"].difference(widgetInfo.supportedAlignments, validAlignments);
18102
- if (unknownAlignments.length) {
18103
- throw new perseusCore.PerseusError("Widget '" + widgetInfo.displayName + "' has an invalid value for supportedAlignments: " + unknownAlignments.join(" "), perseusCore.Errors.InvalidInput);
18104
- }
18105
- }
18106
- });
18107
- };
18108
-
18109
17949
  /**
18110
17950
  * Handling for static mode for widgets that support it.
18111
17951
  */
@@ -18159,8 +17999,6 @@ var widgets$1 = /*#__PURE__*/Object.freeze({
18159
17999
  replaceDeprecatedEditors: replaceDeprecatedEditors,
18160
18000
  getWidget: getWidget,
18161
18001
  getWidgetExport: getWidgetExport,
18162
- getWidgetValidator: getWidgetValidator,
18163
- getWidgetScorer: getWidgetScorer,
18164
18002
  getPublicWidgetOptionsFunction: getPublicWidgetOptionsFunction,
18165
18003
  getEditor: getEditor,
18166
18004
  getTransform: getTransform,
@@ -18169,267 +18007,14 @@ var widgets$1 = /*#__PURE__*/Object.freeze({
18169
18007
  getPublicWidgets: getPublicWidgets,
18170
18008
  isAccessible: isAccessible,
18171
18009
  getAllWidgetTypes: getAllWidgetTypes,
18172
- upgradeWidgetInfoToLatestVersion: upgradeWidgetInfoToLatestVersion,
18173
18010
  getRendererPropsForWidgetInfo: getRendererPropsForWidgetInfo,
18174
18011
  traverseChildWidgets: traverseChildWidgets$3,
18175
- getSupportedAlignments: getSupportedAlignments,
18176
- getDefaultAlignment: getDefaultAlignment,
18177
- validateAlignments: validateAlignments,
18178
18012
  supportsStaticMode: supportsStaticMode,
18179
18013
  getStaticTransform: getStaticTransform,
18180
18014
  getTracking: getTracking,
18181
18015
  isLintable: isLintable
18182
18016
  });
18183
18017
 
18184
- function getUpgradedWidgetOptions(oldWidgetOptions) {
18185
- return perseusCore.mapObject(oldWidgetOptions, (widgetInfo, widgetId) => {
18186
- if (!widgetInfo.type || !widgetInfo.alignment) {
18187
- const newValues = {};
18188
- if (!widgetInfo.type) {
18189
- // TODO: why does widget have no type?
18190
- // We don't want to derive type from widget ID
18191
- // see: LEMS-1845
18192
- newValues.type = widgetId.split(" ")[0];
18193
- }
18194
- if (!widgetInfo.alignment) {
18195
- newValues.alignment = "default";
18196
- }
18197
- widgetInfo = {
18198
- ...widgetInfo,
18199
- ...newValues
18200
- };
18201
- }
18202
- // TODO(LEMS-2656): remove TS suppression
18203
- return upgradeWidgetInfoToLatestVersion(widgetInfo);
18204
- });
18205
- }
18206
-
18207
- /**
18208
- * Checks the given user input to see if any answerable widgets have not been
18209
- * "filled in" (ie. if they're empty). Another way to think about this
18210
- * function is that its a check to see if we can score the provided input.
18211
- */
18212
- function emptyWidgetsFunctional(widgets,
18213
- // This is a port of old code, I'm not sure why
18214
- // we need widgetIds vs the keys of the widgets object
18215
- widgetIds, userInputMap, strings, locale) {
18216
- return widgetIds.filter(id => {
18217
- const widget = widgets[id];
18218
- if (!widget || widget.static === true) {
18219
- // Static widgets shouldn't count as empty
18220
- return false;
18221
- }
18222
- const validator = getWidgetValidator(widget.type);
18223
- const userInput = userInputMap[id];
18224
- const validationData = widget.options;
18225
- const score = validator?.(userInput, validationData, strings, locale);
18226
- if (score) {
18227
- return scoreIsEmpty(score);
18228
- }
18229
- });
18230
- }
18231
-
18232
- // TODO: combine scorePerseusItem with scoreWidgetsFunctional
18233
- // once scorePerseusItem is the only one calling scoreWidgetsFunctional
18234
- function scorePerseusItem(perseusRenderData, userInputMap,
18235
- // TODO(LEMS-2461,LEMS-2391): these probably
18236
- // need to be removed before we move this to the server
18237
- strings, locale) {
18238
- // There seems to be a chance that PerseusRenderer.widgets might include
18239
- // widget data for widgets that are not in PerseusRenderer.content,
18240
- // so this checks that the widgets are being used before scoring them
18241
- const usedWidgetIds = perseusCore.getWidgetIdsFromContent(perseusRenderData.content);
18242
- const scores = scoreWidgetsFunctional(perseusRenderData.widgets, usedWidgetIds, userInputMap, strings, locale);
18243
- return flattenScores(scores);
18244
- }
18245
- function scoreWidgetsFunctional(widgets,
18246
- // This is a port of old code, I'm not sure why
18247
- // we need widgetIds vs the keys of the widgets object
18248
- widgetIds, userInputMap, strings, locale) {
18249
- const upgradedWidgets = getUpgradedWidgetOptions(widgets);
18250
- const gradedWidgetIds = widgetIds.filter(id => {
18251
- const props = upgradedWidgets[id];
18252
- const widgetIsGraded = props?.graded == null || props.graded;
18253
- const widgetIsStatic = !!props?.static;
18254
- // Ungraded widgets or widgets set to static shouldn't be graded.
18255
- return widgetIsGraded && !widgetIsStatic;
18256
- });
18257
- const widgetScores = {};
18258
- gradedWidgetIds.forEach(id => {
18259
- const widget = upgradedWidgets[id];
18260
- if (!widget) {
18261
- return;
18262
- }
18263
- const userInput = userInputMap[id];
18264
- const validator = getWidgetValidator(widget.type);
18265
- const scorer = getWidgetScorer(widget.type);
18266
-
18267
- // We do validation (empty checks) first and then scoring. If
18268
- // validation fails, it's result is itself a PerseusScore.
18269
- const score = validator?.(userInput, widget.options, strings, locale) ?? scorer?.(userInput, widget.options, strings, locale);
18270
- if (score != null) {
18271
- widgetScores[id] = score;
18272
- }
18273
- });
18274
- return widgetScores;
18275
- }
18276
-
18277
- /**
18278
- * TranslationLinter asynchronously calls Khan Academy's poentry linter.
18279
- *
18280
- * The poentry linter checks for issues that prevent content from being
18281
- * translated. This linter is KA-specific and so TranslationLinter does not do
18282
- * anything in non-KA environments.
18283
- */
18284
-
18285
- // A LintCB is called once the linter has been run.
18286
-
18287
- // msec to wait before we actually call the linter after receiving the
18288
- // last call. The timer is reset if the lint is called during the wait.
18289
- const DEBOUNCE_TIMEOUT = 1000;
18290
- class TranslationLinter {
18291
- // To avoid linting when nothing has changed.
18292
- previousContent = null;
18293
-
18294
- /**
18295
- * Runs the poentry linter on the given perseus string asynchronously.
18296
- *
18297
- * It doesn't do anything unless we're in a KA environment.
18298
- *
18299
- * onLintErrorsGenerated is called even if there are no errors. It is not
18300
- * called if perseusStr is unchanged from the last call.
18301
- *
18302
- * Linting is relatively expensive -- so we debounce linting each instance
18303
- * for some amount of time.
18304
- */
18305
- runLinter = _.debounce((perseusStr, onLintErrorsGenerated) => {
18306
- // TODO(FEI-5003): Do we still need to check for 'KA'?
18307
- if (typeof KA === "undefined") {
18308
- return;
18309
- }
18310
- if (perseusStr === this.previousContent) {
18311
- return;
18312
- }
18313
- this.previousContent = perseusStr;
18314
- if (perseusStr === "") {
18315
- onLintErrorsGenerated([]);
18316
- return;
18317
- }
18318
- fetch("/api/internal/translations/lint_poentry?preview=1&lang=en", {
18319
- headers: {
18320
- "Content-Type": "application/json"
18321
- },
18322
- body: JSON.stringify({
18323
- // The poentry linter verifies that the translation from
18324
- // the original text to the translated text is sane. We
18325
- // want to ensure that the translation from English to
18326
- // English is sane, so msgid === mstr.
18327
- msgid: perseusStr,
18328
- // msgid is the original text
18329
- msgstr: perseusStr,
18330
- // msgstr is the translated text
18331
- format: "perseus_text",
18332
- filename: ""
18333
- }),
18334
- method: "POST"
18335
- }).then(response => {
18336
- if (response.status >= 400) {
18337
- return {
18338
- status: "error",
18339
- message: "Could not run i18n linter."
18340
- };
18341
- }
18342
- return response.json();
18343
- }, rejection => {
18344
- return {
18345
- status: "error",
18346
- message: "Could not run i18n linter."
18347
- };
18348
- }).then(json => {
18349
- if (json.status === "error") {
18350
- onLintErrorsGenerated(["Some part of this text makes it untranslatable. " + "The specific message from the i18n linter was: " + json.message.replace(/\n/g, " ")]);
18351
- } else {
18352
- onLintErrorsGenerated([]);
18353
- }
18354
- });
18355
- }, DEBOUNCE_TIMEOUT);
18356
-
18357
- /**
18358
- * Applies an array of errors generated by linters without position
18359
- * information (TranslationLinter and the legacy getSaveWarnings()) to the
18360
- * top of a Perseus tree.
18361
- */
18362
- applyLintErrors(parsedMarkdown, translationLintErrors) {
18363
- // These lint errors do not have position data associated with
18364
- // them, so we just plop them at the top.
18365
- if (translationLintErrors.length) {
18366
- const errorText = translationLintErrors.join("\n\n");
18367
- parsedMarkdown.unshift({
18368
- content: {
18369
- type: "text",
18370
- content: ""
18371
- },
18372
- insideTable: false,
18373
- message: errorText,
18374
- ruleName: "legacy-error",
18375
- severity: PerseusLinter.Rule.Severity.ERROR,
18376
- type: "lint"
18377
- });
18378
- }
18379
- }
18380
- }
18381
-
18382
- /**
18383
- * Preprocess TeX code to convert things that MathJax doesn't know how to handle
18384
- * to things is does.
18385
- */
18386
- var preprocessTex = (texCode => texCode
18387
- // Replace uses of \begin{align}...\end{align} which KaTeX doesn't
18388
- // support (yet) with \begin{aligned}...\end{aligned} which renders
18389
- // the same is supported by KaTeX. It does the same for align*.
18390
- // TODO(kevinb) update content to use aligned instead of align.
18391
- // TODO(LEMS-1608) Remove this replacement as MathJax supports the
18392
- // "align" macro correctly (and, in fact, it is not synonymous with
18393
- // "aligned").
18394
- .replace(/\{align[*]?\}/g, "{aligned}")
18395
- // Replace non-breaking spaces with regular spaces.
18396
- .replace(/[\u00a0]/g, " "));
18397
-
18398
- const {
18399
- interactiveSizes
18400
- } = constants;
18401
-
18402
- // Note: these size cutoffs represent content-width cutoffs as specified in
18403
- // http://zpl.io/1mVmvU (broken link - we no longer use Zeplin)
18404
- // TODO(benkomalo): these values aren't used in JS outside of this file, but
18405
- // are coupled to the values in styles/styles.less - DRY it up at some point
18406
- const smMax = articleMaxWidthTableInPx;
18407
- const mdMax = articleMaxWidthInPx;
18408
- const containerSizeClass = {
18409
- SMALL: "small",
18410
- MEDIUM: "medium",
18411
- LARGE: "large",
18412
- XLARGE: "xlarge"
18413
- };
18414
- const getClassFromWidth = width => {
18415
- if (!width) {
18416
- return containerSizeClass.MEDIUM;
18417
- }
18418
- if (width <= smMax) {
18419
- return containerSizeClass.SMALL;
18420
- }
18421
- if (width <= mdMax) {
18422
- return containerSizeClass.MEDIUM;
18423
- }
18424
- return containerSizeClass.LARGE;
18425
- };
18426
- const getInteractiveBoxFromSizeClass = sizeClass => {
18427
- if (sizeClass === containerSizeClass.SMALL) {
18428
- return [interactiveSizes.defaultBoxSizeSmall, interactiveSizes.defaultBoxSizeSmall];
18429
- }
18430
- return [interactiveSizes.defaultBoxSize, interactiveSizes.defaultBoxSize];
18431
- };
18432
-
18433
18018
  class WidgetContainer extends React__namespace.Component {
18434
18019
  widgetRef = /*#__PURE__*/React__namespace.createRef();
18435
18020
  static defaultProps = {
@@ -18502,7 +18087,7 @@ class WidgetContainer extends React__namespace.Component {
18502
18087
  }
18503
18088
  let alignment = this.state.widgetProps.alignment;
18504
18089
  if (alignment === "default") {
18505
- alignment = getDefaultAlignment(type);
18090
+ alignment = perseusCore.CoreWidgetRegistry.getDefaultAlignment(type);
18506
18091
  }
18507
18092
  className += " widget-" + alignment;
18508
18093
  const apiOptions = this.state.widgetProps.apiOptions;
@@ -18510,6 +18095,7 @@ class WidgetContainer extends React__namespace.Component {
18510
18095
  // Hack to prevent interaction with static widgets: we overlay a big
18511
18096
  // div on top of the widget and overflow: hidden the container.
18512
18097
  // Ideally widgets themselves should know how to prevent interaction.
18098
+ // UPDATE HTML5: `inert` on the underlying div would be better
18513
18099
  const isStatic = this.state.widgetProps.static || apiOptions.readOnly;
18514
18100
  const staticContainerStyles = {
18515
18101
  position: "relative",
@@ -18521,7 +18107,7 @@ class WidgetContainer extends React__namespace.Component {
18521
18107
  position: "absolute",
18522
18108
  top: 0,
18523
18109
  left: 0,
18524
- zIndex: zIndexInteractiveComponent$1
18110
+ zIndex: 3
18525
18111
  };
18526
18112
 
18527
18113
  // Some widgets may include strings of markdown that we may
@@ -18787,7 +18373,7 @@ class Renderer extends React__namespace.Component {
18787
18373
  };
18788
18374
  };
18789
18375
  _getInitialWidgetState = props => {
18790
- const allWidgetInfo = getUpgradedWidgetOptions(props.widgets);
18376
+ const allWidgetInfo = perseusCore.getUpgradedWidgetOptions(props.widgets);
18791
18377
  return {
18792
18378
  widgetInfo: allWidgetInfo,
18793
18379
  widgetProps: this._getAllWidgetsStartProps(allWidgetInfo, props)
@@ -19699,7 +19285,7 @@ class Renderer extends React__namespace.Component {
19699
19285
  * empty if the graph is in the starting state.
19700
19286
  */
19701
19287
  emptyWidgets() {
19702
- return emptyWidgetsFunctional(this.state.widgetInfo, this.widgetIds, this.getUserInputMap(), this.props.strings, this.context.locale);
19288
+ return perseusScore.emptyWidgetsFunctional(this.state.widgetInfo, this.widgetIds, this.getUserInputMap(), this.context.locale);
19703
19289
  }
19704
19290
  _setWidgetProps = (id, newProps, cb, silent) => {
19705
19291
  this.setState(prevState => {
@@ -19829,8 +19415,8 @@ class Renderer extends React__namespace.Component {
19829
19415
  * @deprecated use scorePerseusItem
19830
19416
  */
19831
19417
  score() {
19832
- const scores = scoreWidgetsFunctional(this.state.widgetInfo, this.widgetIds, this.getUserInputMap(), this.props.strings, this.context.locale);
19833
- const combinedScore = flattenScores(scores);
19418
+ const scores = perseusScore.scoreWidgetsFunctional(this.state.widgetInfo, this.widgetIds, this.getUserInputMap(), this.context.locale);
19419
+ const combinedScore = perseusScore.flattenScores(scores);
19834
19420
  return combinedScore;
19835
19421
  }
19836
19422
 
@@ -20351,7 +19937,7 @@ class InputWithExamples extends React__namespace.Component {
20351
19937
  };
20352
19938
  _getInputClassName = () => {
20353
19939
  // Otherwise, we need to add these INPUT and FOCUSED tags here.
20354
- let className = ClassNames.INPUT + " " + ClassNames.INTERACTIVE;
19940
+ let className = ClassNames.INPUT;
20355
19941
  if (this.state.focused) {
20356
19942
  className += " " + ClassNames.FOCUSED;
20357
19943
  }
@@ -20744,14 +20330,10 @@ const propTransform$3 = widgetOptions => {
20744
20330
  var InputNumber$1 = {
20745
20331
  name: "input-number",
20746
20332
  displayName: "Input number (deprecated - use numeric input instead)",
20747
- defaultAlignment: "inline-block",
20748
20333
  hidden: true,
20749
20334
  widget: InputNumber,
20750
20335
  transform: propTransform$3,
20751
20336
  isLintable: true,
20752
- // TODO(LEMS-2656): remove TS suppression
20753
- // @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusInputNumberUserInput'.
20754
- scorer: perseusScore.scoreInputNumber,
20755
20337
  getOneCorrectAnswerFromRubric(rubric) {
20756
20338
  if (rubric.value == null) {
20757
20339
  return;
@@ -21003,14 +20585,11 @@ const propsTransform = function (widgetOptions) {
21003
20585
  var NumericInput$1 = {
21004
20586
  name: "numeric-input",
21005
20587
  displayName: "Numeric input",
21006
- defaultAlignment: "inline-block",
21007
20588
  accessible: true,
21008
20589
  widget: NumericInput,
21009
20590
  transform: propsTransform,
21010
20591
  isLintable: true,
21011
- // TODO(LEMS-2656): remove TS suppression
21012
- // @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusNumericInputUserInput'.
21013
- scorer: perseusScore.scoreNumericInput,
20592
+ getPublicWidgetOptions: perseusCore.getNumericInputPublicWidgetOptions,
21014
20593
  // TODO(LEMS-2656): remove TS suppression
21015
20594
  // @ts-expect-error: Type 'Rubric' is not assignable to type 'PerseusNumericInputRubric'
21016
20595
  getOneCorrectAnswerFromRubric(rubric) {
@@ -21071,24 +20650,6 @@ const getPromptJSON$p = renderProps => {
21071
20650
  };
21072
20651
  };
21073
20652
 
21074
- /**
21075
- * Several widgets don't have "right"/"wrong" scoring logic,
21076
- * so this just says to move on past those widgets
21077
- *
21078
- * TODO(LEMS-2543) widgets that use this probably shouldn't have any
21079
- * scoring logic and the thing scoring an exercise
21080
- * should just know to skip these
21081
- */
21082
- function scoreNoop() {
21083
- let points = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
21084
- return {
21085
- type: "points",
21086
- earned: points,
21087
- total: points,
21088
- message: null
21089
- };
21090
- }
21091
-
21092
20653
  /**
21093
20654
  * Utility functions for manipulating ranges of highlightable content.
21094
20655
  */
@@ -23266,8 +22827,7 @@ var Passage$1 = {
23266
22827
  transform: editorProps => {
23267
22828
  return ___default["default"].pick(editorProps, "passageTitle", "passageText", "footnotes", "showLineNumbers");
23268
22829
  },
23269
- isLintable: true,
23270
- scorer: () => scoreNoop()
22830
+ isLintable: true
23271
22831
  };
23272
22832
 
23273
22833
  /**
@@ -23393,16 +22953,13 @@ var PassageRef$1 = {
23393
22953
  name: "passage-ref",
23394
22954
  displayName: "PassageRef (SAT only)",
23395
22955
  hidden: true,
23396
- defaultAlignment: "inline",
23397
22956
  widget: PassageRef,
23398
22957
  transform: widgetOptions => ({
23399
22958
  passageNumber: widgetOptions.passageNumber,
23400
22959
  referenceNumber: widgetOptions.referenceNumber,
23401
22960
  summaryText: widgetOptions.summaryText
23402
22961
  }),
23403
- version: perseusCore.passageRefLogic.version,
23404
- // TODO: things that aren't interactive shouldn't need scoring functions
23405
- scorer: () => scoreNoop()
22962
+ version: perseusCore.passageRefLogic.version
23406
22963
  };
23407
22964
 
23408
22965
  /**
@@ -23444,109 +23001,6 @@ var mediaQueries = {
23444
23001
  xlOrLarger: `@media screen and (min-width: ${pureXlMin})`
23445
23002
  };
23446
23003
 
23447
- const {
23448
- zIndexAboveScratchpad,
23449
- zIndexInteractiveComponent,
23450
- radioBorderColor,
23451
- checkedColor,
23452
- circleSize,
23453
- radioMarginWidth
23454
- } = constants;
23455
- var sharedStyles = aphrodite.StyleSheet.create({
23456
- perseusInteractive: {
23457
- zIndex: zIndexInteractiveComponent,
23458
- position: "relative"
23459
- },
23460
- aboveScratchpad: {
23461
- position: "relative",
23462
- zIndex: zIndexAboveScratchpad
23463
- },
23464
- blankBackground: {
23465
- // TODO(emily): Use WB colors?
23466
- backgroundColor: "#FDFDFD"
23467
- },
23468
- perseusSrOnly: {
23469
- border: 0,
23470
- clip: "rect(0,0,0,0)",
23471
- height: 1,
23472
- margin: -1,
23473
- overflow: "hidden",
23474
- padding: 0,
23475
- position: "absolute",
23476
- width: 1
23477
- },
23478
- responsiveLabel: {
23479
- // NOTE(charlie): The values used here should be kept in sync with the
23480
- // caption text sizing in Perseus' styles.less.
23481
- // TODO(charlie): Migrate the captions over to using this style.
23482
- [mediaQueries.smOrSmaller]: {
23483
- fontSize: 14,
23484
- lineHeight: 1.3
23485
- },
23486
- [mediaQueries.md]: {
23487
- fontSize: 17,
23488
- lineHeight: 1.4
23489
- },
23490
- [mediaQueries.lgOrLarger]: {
23491
- fontSize: 20,
23492
- lineHeight: 1.4
23493
- }
23494
- },
23495
- responsiveInput: {
23496
- display: "inline-block",
23497
- WebkitAppearance: "none",
23498
- appearance: "none",
23499
- "::-ms-check": {
23500
- display: "none"
23501
- },
23502
- backgroundColor: "#fff",
23503
- border: "2px solid #fff",
23504
- boxShadow: `0 0px 0px 1px ${radioBorderColor}`,
23505
- outline: "none",
23506
- boxSizing: "border-box",
23507
- flexShrink: 0,
23508
- marginBottom: 1,
23509
- marginLeft: 1,
23510
- marginRight: 1,
23511
- marginTop: 1,
23512
- height: circleSize - 2,
23513
- width: circleSize - 2
23514
- },
23515
- responsiveRadioInput: {
23516
- borderRadius: "50%",
23517
- ":checked": {
23518
- backgroundColor: checkedColor,
23519
- border: "none",
23520
- borderRadius: "50%",
23521
- boxShadow: `inset 0px 0px 0px 2px white, ` + `0 0px 0px 2px ${checkedColor}`,
23522
- marginTop: radioMarginWidth,
23523
- marginBottom: radioMarginWidth,
23524
- marginLeft: radioMarginWidth,
23525
- marginRight: radioMarginWidth,
23526
- height: circleSize - 2 * radioMarginWidth,
23527
- width: circleSize - 2 * radioMarginWidth
23528
- }
23529
- },
23530
- responsiveRadioInputActive: {
23531
- backgroundColor: "#fff",
23532
- border: "2px solid #fff",
23533
- borderRadius: "50%",
23534
- boxShadow: `0 0px 0px 2px ${checkedColor}`,
23535
- marginTop: radioMarginWidth,
23536
- marginBottom: radioMarginWidth,
23537
- marginLeft: radioMarginWidth,
23538
- marginRight: radioMarginWidth,
23539
- height: circleSize - 2 * radioMarginWidth,
23540
- width: circleSize - 2 * radioMarginWidth,
23541
- ":checked": {
23542
- backgroundColor: "#fff"
23543
- }
23544
- },
23545
- disableTextSelection: {
23546
- userSelect: "none"
23547
- }
23548
- });
23549
-
23550
23004
  /**
23551
23005
  * A small collection of useful scrolling utility functions.
23552
23006
  */
@@ -24615,7 +24069,7 @@ const BaseRadio = function (_ref) {
24615
24069
  const aphroditeClassName = checked => {
24616
24070
  // Whether or not to show correctness borders
24617
24071
  // for this choice and the next choice.
24618
- return aphrodite.css(sharedStyles.aboveScratchpad, styles$o.item, styles$o.responsiveItem, checked && styles$o.selectedItem, checked && choice.highlighted && styles$o.aboveBackdrop, checked && choice.highlighted && apiOptions.isMobile && styles$o.aboveBackdropMobile, nextChoiceHighlighted && apiOptions.isMobile && styles$o.nextHighlighted);
24072
+ return aphrodite.css(styles$o.item, styles$o.responsiveItem, checked && styles$o.selectedItem, checked && choice.highlighted && styles$o.aboveBackdrop, checked && choice.highlighted && apiOptions.isMobile && styles$o.aboveBackdropMobile, nextChoiceHighlighted && apiOptions.isMobile && styles$o.nextHighlighted);
24619
24073
  };
24620
24074
 
24621
24075
  // HACK(abdulrahman): Preloads the selection-state
@@ -25219,18 +24673,105 @@ var Radio = {
25219
24673
  staticTransform: transform,
25220
24674
  version: perseusCore.radioLogic.version,
25221
24675
  propUpgrades: perseusCore.radioLogic.widgetOptionsUpgrades,
25222
- isLintable: true,
25223
- // TODO(LEMS-2656): remove TS suppression
25224
- // @ts-expect-error: Type UserInput is not assignable to type PerseusRadioUserInput
25225
- scorer: perseusScore.scoreRadio,
25226
- // TODO(LEMS-2656): remove TS suppression
25227
- // @ts-expect-error: Type UserInput is not assignable to type PerseusRadioUserInput
25228
- validator: perseusScore.validateRadio
24676
+ isLintable: true
25229
24677
  };
25230
24678
 
25231
24679
  /* eslint-disable import/no-named-as-default */
25232
24680
  var basicWidgets = [Radio, InputNumber$1, NumericInput$1, Expression$1];
25233
24681
 
24682
+ const {
24683
+ radioBorderColor,
24684
+ checkedColor,
24685
+ circleSize,
24686
+ radioMarginWidth
24687
+ } = constants;
24688
+ var sharedStyles = aphrodite.StyleSheet.create({
24689
+ blankBackground: {
24690
+ // TODO(emily): Use WB colors?
24691
+ backgroundColor: "#FDFDFD"
24692
+ },
24693
+ perseusSrOnly: {
24694
+ border: 0,
24695
+ clip: "rect(0,0,0,0)",
24696
+ height: 1,
24697
+ margin: -1,
24698
+ overflow: "hidden",
24699
+ padding: 0,
24700
+ position: "absolute",
24701
+ width: 1
24702
+ },
24703
+ responsiveLabel: {
24704
+ // NOTE(charlie): The values used here should be kept in sync with the
24705
+ // caption text sizing in Perseus' styles.less.
24706
+ // TODO(charlie): Migrate the captions over to using this style.
24707
+ [mediaQueries.smOrSmaller]: {
24708
+ fontSize: 14,
24709
+ lineHeight: 1.3
24710
+ },
24711
+ [mediaQueries.md]: {
24712
+ fontSize: 17,
24713
+ lineHeight: 1.4
24714
+ },
24715
+ [mediaQueries.lgOrLarger]: {
24716
+ fontSize: 20,
24717
+ lineHeight: 1.4
24718
+ }
24719
+ },
24720
+ responsiveInput: {
24721
+ display: "inline-block",
24722
+ WebkitAppearance: "none",
24723
+ appearance: "none",
24724
+ "::-ms-check": {
24725
+ display: "none"
24726
+ },
24727
+ backgroundColor: "#fff",
24728
+ border: "2px solid #fff",
24729
+ boxShadow: `0 0px 0px 1px ${radioBorderColor}`,
24730
+ outline: "none",
24731
+ boxSizing: "border-box",
24732
+ flexShrink: 0,
24733
+ marginBottom: 1,
24734
+ marginLeft: 1,
24735
+ marginRight: 1,
24736
+ marginTop: 1,
24737
+ height: circleSize - 2,
24738
+ width: circleSize - 2
24739
+ },
24740
+ responsiveRadioInput: {
24741
+ borderRadius: "50%",
24742
+ ":checked": {
24743
+ backgroundColor: checkedColor,
24744
+ border: "none",
24745
+ borderRadius: "50%",
24746
+ boxShadow: `inset 0px 0px 0px 2px white, ` + `0 0px 0px 2px ${checkedColor}`,
24747
+ marginTop: radioMarginWidth,
24748
+ marginBottom: radioMarginWidth,
24749
+ marginLeft: radioMarginWidth,
24750
+ marginRight: radioMarginWidth,
24751
+ height: circleSize - 2 * radioMarginWidth,
24752
+ width: circleSize - 2 * radioMarginWidth
24753
+ }
24754
+ },
24755
+ responsiveRadioInputActive: {
24756
+ backgroundColor: "#fff",
24757
+ border: "2px solid #fff",
24758
+ borderRadius: "50%",
24759
+ boxShadow: `0 0px 0px 2px ${checkedColor}`,
24760
+ marginTop: radioMarginWidth,
24761
+ marginBottom: radioMarginWidth,
24762
+ marginLeft: radioMarginWidth,
24763
+ marginRight: radioMarginWidth,
24764
+ height: circleSize - 2 * radioMarginWidth,
24765
+ width: circleSize - 2 * radioMarginWidth,
24766
+ ":checked": {
24767
+ backgroundColor: "#fff"
24768
+ }
24769
+ },
24770
+ disableTextSelection: {
24771
+ userSelect: "none"
24772
+ }
24773
+ });
24774
+
25234
24775
  const getPromptJSON$n = (renderProps, userInput) => {
25235
24776
  return {
25236
24777
  type: "categorizer",
@@ -25331,7 +24872,6 @@ class Categorizer extends React__namespace.Component {
25331
24872
  className: "category " + aphrodite.css(styles$n.cell),
25332
24873
  key: catNum
25333
24874
  }, /*#__PURE__*/React__namespace.createElement("div", {
25334
- className: ClassNames.INTERACTIVE,
25335
24875
  role: "button",
25336
24876
  "aria-label": catName,
25337
24877
  onClick: () => this.onChange(itemNum, catNum)
@@ -25423,12 +24963,6 @@ var Categorizer$1 = {
25423
24963
  return ___default["default"].pick(editorProps, "items", "categories", "values", "randomizeItems");
25424
24964
  },
25425
24965
  isLintable: true,
25426
- // TODO(LEMS-2656): remove TS suppression
25427
- // @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusCSProgramUserInput'.
25428
- scorer: perseusScore.scoreCategorizer,
25429
- // TODO(LEMS-2656): remove TS suppression
25430
- // @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusCSProgramUserInput'.
25431
- validator: perseusScore.validateCategorizer,
25432
24966
  getPublicWidgetOptions: perseusCore.getCategorizerPublicWidgetOptions
25433
24967
  };
25434
24968
 
@@ -25638,12 +25172,9 @@ const styles$m = aphrodite.StyleSheet.create({
25638
25172
  var CSProgram$1 = {
25639
25173
  name: "cs-program",
25640
25174
  displayName: "CS Program",
25641
- supportedAlignments: ["block", "full-width"],
25642
25175
  widget: CSProgram,
25643
25176
  hidden: true,
25644
- // TODO(LEMS-2656): remove TS suppression
25645
- // @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusCSProgramUserInput'.
25646
- scorer: perseusScore.scoreCSProgram
25177
+ getPublicWidgetOptions: perseusCore.getCSProgramPublicWidgetOptions
25647
25178
  };
25648
25179
 
25649
25180
  const getPromptJSON$l = renderProps => {
@@ -25720,11 +25251,8 @@ var Definition$1 = {
25720
25251
  name: "definition",
25721
25252
  displayName: "Definition",
25722
25253
  accessible: true,
25723
- defaultAlignment: "inline",
25724
25254
  widget: Definition,
25725
- transform: x => x,
25726
- // TODO: things that aren't interactive shouldn't need scoring functions
25727
- scorer: () => scoreNoop()
25255
+ transform: x => x
25728
25256
  };
25729
25257
 
25730
25258
  // There are no required props, but this component can receive props from any
@@ -25751,9 +25279,7 @@ var DeprecatedStandin$1 = {
25751
25279
  name: "deprecated-standin",
25752
25280
  displayName: "Deprecated Standin",
25753
25281
  widget: DeprecatedStandin,
25754
- hidden: true,
25755
- // TODO: things that aren't interactive shouldn't need scoring functions
25756
- scorer: () => scoreNoop(1)
25282
+ hidden: true
25757
25283
  };
25758
25284
 
25759
25285
  const getPromptJSON$k = (renderProps, userInput) => {
@@ -25862,16 +25388,10 @@ const optionsTransform = widgetOptions => {
25862
25388
  var Dropdown$1 = {
25863
25389
  name: "dropdown",
25864
25390
  displayName: "Drop down",
25865
- defaultAlignment: "inline-block",
25866
25391
  accessible: true,
25867
25392
  widget: Dropdown,
25868
25393
  transform: optionsTransform,
25869
- // TODO(LEMS-2656): remove TS suppression
25870
- // @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusDropdownUserInput'.
25871
- scorer: perseusScore.scoreDropdown,
25872
- // TODO(LEMS-2656): remove TS suppression
25873
- // @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusDropdownUserInput'.
25874
- validator: perseusScore.validateDropdown
25394
+ getPublicWidgetOptions: perseusCore.getDropdownPublicWidgetOptions
25875
25395
  };
25876
25396
 
25877
25397
  const getPromptJSON$j = renderProps => {
@@ -26030,12 +25550,9 @@ var Explanation$1 = {
26030
25550
  name: "explanation",
26031
25551
  displayName: "Explanation",
26032
25552
  accessible: true,
26033
- defaultAlignment: "inline",
26034
25553
  widget: Explanation,
26035
25554
  transform: ___default["default"].identity,
26036
- isLintable: true,
26037
- // TODO: things that aren't interactive shouldn't need scoring functions
26038
- scorer: () => scoreNoop()
25555
+ isLintable: true
26039
25556
  };
26040
25557
 
26041
25558
  const getPromptJSON$i = (title, rendererJSON, hintRendererJSON) => {
@@ -27156,7 +26673,7 @@ class FunctionGrapher extends React__namespace.Component {
27156
26673
  boxSizing: "initial"
27157
26674
  }
27158
26675
  }, /*#__PURE__*/React__namespace.createElement("div", {
27159
- className: "graphie-container above-scratchpad",
26676
+ className: "graphie-container blank-background",
27160
26677
  style: {
27161
26678
  width: box[0],
27162
26679
  height: box[1]
@@ -27284,8 +26801,7 @@ class Grapher extends React__namespace.Component {
27284
26801
  const coords = this.props.plot.coords;
27285
26802
  const asymptote = this.props.plot.asymptote;
27286
26803
  const typeSelector = /*#__PURE__*/React__namespace.createElement("div", {
27287
- style: typeSelectorStyle,
27288
- className: "above-scratchpad"
26804
+ style: typeSelectorStyle
27289
26805
  }, /*#__PURE__*/React__namespace.createElement(ButtonGroup, {
27290
26806
  value: type,
27291
26807
  allowEmpty: true,
@@ -27367,10 +26883,7 @@ var Grapher$1 = {
27367
26883
  hidden: true,
27368
26884
  widget: Grapher,
27369
26885
  transform: propTransform$2,
27370
- staticTransform: staticTransform$4,
27371
- // TODO(LEMS-2656): remove TS suppression
27372
- // @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusGrapherUserInput'.
27373
- scorer: perseusScore.scoreGrapher
26886
+ staticTransform: staticTransform$4
27374
26887
  };
27375
26888
 
27376
26889
  const getPromptJSON$f = rendererJSON => {
@@ -27387,24 +26900,6 @@ const getPromptJSON$f = rendererJSON => {
27387
26900
  };
27388
26901
  };
27389
26902
 
27390
- // The `group` widget is basically a widget hosting a full Perseus system in
27391
- // it. As such, scoring a group means scoring all widgets it contains.
27392
- function scoreGroup(userInput, rubric, strings, locale) {
27393
- const scores = scoreWidgetsFunctional(rubric.widgets, Object.keys(rubric.widgets), userInput, strings, locale);
27394
- return flattenScores(scores);
27395
- }
27396
-
27397
- function validateGroup(userInput, validationData, strings, locale) {
27398
- const emptyWidgets = emptyWidgetsFunctional(validationData.widgets, Object.keys(validationData.widgets), userInput, strings, locale);
27399
- if (emptyWidgets.length === 0) {
27400
- return null;
27401
- }
27402
- return {
27403
- type: "invalid",
27404
- message: null
27405
- };
27406
- }
27407
-
27408
26903
  // exports has no 'transform'
27409
26904
 
27410
26905
  class Group extends React__namespace.Component {
@@ -27556,12 +27051,6 @@ var Group$1 = {
27556
27051
  displayName: "Group (SAT only)",
27557
27052
  widget: Group,
27558
27053
  traverseChildWidgets: traverseChildWidgets,
27559
- // TODO(LEMS-2656): remove TS suppression
27560
- // @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusGroupUserInput'.
27561
- scorer: scoreGroup,
27562
- // TODO(LEMS-2656): remove TS suppression
27563
- // @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusGroupUserInput'.
27564
- validator: validateGroup,
27565
27054
  hidden: true,
27566
27055
  isLintable: true
27567
27056
  };
@@ -27701,10 +27190,7 @@ var Iframe$1 = {
27701
27190
  displayName: "Iframe (deprecated)",
27702
27191
  widget: Iframe,
27703
27192
  // Let's not expose it to all content creators yet
27704
- hidden: true,
27705
- // TODO(LEMS-2656): remove TS suppression
27706
- // @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusIframeUserInput'.
27707
- scorer: perseusScore.scoreIframe
27193
+ hidden: true
27708
27194
  };
27709
27195
 
27710
27196
  const getPromptJSON$d = renderProps => {
@@ -27726,15 +27212,13 @@ const defaultBackgroundImage$2 = {
27726
27212
  width: 0,
27727
27213
  height: 0
27728
27214
  };
27729
- const editorAlignments = ["block", "full-width"];
27730
- const DEFAULT_ALIGNMENT = "block";
27731
27215
 
27732
27216
  // there is no transform as part of exports
27733
27217
 
27734
27218
  class ImageWidget extends React__namespace.Component {
27735
27219
  static contextType = PerseusI18nContext;
27736
27220
  static defaultProps = {
27737
- alignment: DEFAULT_ALIGNMENT,
27221
+ alignment: "block",
27738
27222
  title: "",
27739
27223
  range: [defaultRange, defaultRange],
27740
27224
  box: [defaultBoxSize, defaultBoxSize],
@@ -27901,13 +27385,9 @@ var Image$1 = {
27901
27385
  const bgImage = widgetOptions.backgroundImage;
27902
27386
  return !(bgImage && bgImage.url && !widgetOptions.alt);
27903
27387
  },
27904
- defaultAlignment: DEFAULT_ALIGNMENT,
27905
- supportedAlignments: editorAlignments,
27906
27388
  displayName: "Image",
27907
27389
  widget: ImageWidget,
27908
- isLintable: true,
27909
- // TODO: things that aren't interactive shouldn't need scoring functions
27910
- scorer: () => scoreNoop()
27390
+ isLintable: true
27911
27391
  };
27912
27392
 
27913
27393
  const getPromptJSON$c = () => {
@@ -28510,9 +27990,7 @@ var Interactive = {
28510
27990
  displayName: "Interaction",
28511
27991
  widget: Interaction,
28512
27992
  transform: ___default["default"].identity,
28513
- hidden: true,
28514
- // TODO: things that aren't interactive shouldn't need scoring functions
28515
- scorer: () => scoreNoop()
27993
+ hidden: true
28516
27994
  };
28517
27995
 
28518
27996
  /* eslint-disable @khanacademy/ts-no-error-suppressions */
@@ -28794,7 +28272,7 @@ class Graph extends React__namespace.Component {
28794
28272
  image = null;
28795
28273
  }
28796
28274
  return /*#__PURE__*/React__namespace.createElement("div", {
28797
- className: "graphie-container above-scratchpad",
28275
+ className: "graphie-container blank-background",
28798
28276
  style: {
28799
28277
  width: this.props.box[0],
28800
28278
  height: this.props.box[1]
@@ -29428,7 +28906,8 @@ const YGridTick = _ref => {
29428
28906
  let {
29429
28907
  y,
29430
28908
  range,
29431
- tickStep
28909
+ tickStep,
28910
+ showPi
29432
28911
  } = _ref;
29433
28912
  // If the graph requires out-of-bounds labels, we want to make sure to set the
29434
28913
  // coordinates to the edge of the visible range of the graph. Otherwise,
@@ -29464,6 +28943,7 @@ const YGridTick = _ref => {
29464
28943
  // If the graph displays both the y and x axis lines within the graph, we want
29465
28944
  // to hide the label at -1 on the y-axis to prevent overlap with the x-axis label
29466
28945
  const showLabel = shouldShowLabel(y, range, tickStep);
28946
+ const yLabel = showPi ? divideByAndShowPi(y) : y.toString();
29467
28947
  return /*#__PURE__*/React__namespace.createElement("g", {
29468
28948
  className: "tick",
29469
28949
  "aria-hidden": true
@@ -29481,12 +28961,13 @@ const YGridTick = _ref => {
29481
28961
  textAnchor: "end",
29482
28962
  x: xPositionText,
29483
28963
  y: yPositionText
29484
- }, y.toString()));
28964
+ }, yLabel));
29485
28965
  };
29486
28966
  const XGridTick = _ref2 => {
29487
28967
  let {
29488
28968
  x,
29489
- range
28969
+ range,
28970
+ showPi
29490
28971
  } = _ref2;
29491
28972
  // If the graph requires out-of-bounds labels, we want to make sure to set the
29492
28973
  // coordinates to the edge of the visible range of the graph. Otherwise,
@@ -29525,6 +29006,7 @@ const XGridTick = _ref2 => {
29525
29006
  // Apply the adjustments to the x and y positions for the text
29526
29007
  const xPositionText = xPosition + xAdjustment;
29527
29008
  const yPositionText = yPosition + yAdjustment;
29009
+ const xLabel = showPi ? divideByAndShowPi(x) : x.toString();
29528
29010
  return /*#__PURE__*/React__namespace.createElement("g", {
29529
29011
  className: "tick",
29530
29012
  "aria-hidden": true
@@ -29542,7 +29024,7 @@ const XGridTick = _ref2 => {
29542
29024
  textAnchor: "middle",
29543
29025
  x: xPositionText,
29544
29026
  y: yPositionText
29545
- }, x.toString()));
29027
+ }, xLabel));
29546
29028
  };
29547
29029
 
29548
29030
  // Determines whether to show the label for the given tick
@@ -29589,6 +29071,22 @@ const countSignificantDecimals = number => {
29589
29071
  }
29590
29072
  return numStr.split(".")[1].length;
29591
29073
  };
29074
+
29075
+ // Show the given value as a multiple of pi (already assumed to be
29076
+ // a multiple of pi). Exported for testing
29077
+ function divideByAndShowPi(value) {
29078
+ const dividedValue = value / Math.PI;
29079
+ switch (dividedValue) {
29080
+ case 1:
29081
+ return "π";
29082
+ case -1:
29083
+ return "-π";
29084
+ case 0:
29085
+ return "0";
29086
+ default:
29087
+ return dividedValue + "π";
29088
+ }
29089
+ }
29592
29090
  const AxisTicks = () => {
29593
29091
  const {
29594
29092
  tickStep,
@@ -29611,6 +29109,10 @@ const AxisTicks = () => {
29611
29109
  key: `y-grid-tick-${y}`,
29612
29110
  range: range,
29613
29111
  tickStep: tickStep[Y]
29112
+ // Show the tick labels as multiples of pi
29113
+ // if the tick step is a multiple of pi.
29114
+ ,
29115
+ showPi: tickStep[Y] % Math.PI === 0
29614
29116
  });
29615
29117
  })), /*#__PURE__*/React__namespace.createElement("g", {
29616
29118
  className: "x-axis-ticks"
@@ -29619,6 +29121,10 @@ const AxisTicks = () => {
29619
29121
  x: x,
29620
29122
  key: `x-grid-tick-${x}`,
29621
29123
  range: range
29124
+ // Show the tick labels as multiples of pi
29125
+ // if the tick step is a multiple of pi.
29126
+ ,
29127
+ showPi: tickStep[X] % Math.PI === 0
29622
29128
  });
29623
29129
  })));
29624
29130
  };
@@ -29920,6 +29426,15 @@ const styles$d = aphrodite.StyleSheet.create({
29920
29426
  }
29921
29427
  });
29922
29428
 
29429
+ function isPiMultiple(value) {
29430
+ // Early return for integers that clearly aren't multiples
29431
+ // of pi to save some computation.
29432
+ if (Number.isInteger(value)) {
29433
+ return false;
29434
+ }
29435
+ return value % Math.PI === 0 || value % (Math.PI / 2) === 0 || value % (Math.PI / 3) === 0 || value % (Math.PI / 4) === 0 || value % (Math.PI / 6) === 0;
29436
+ }
29437
+
29923
29438
  const {
29924
29439
  firstNumericalParse,
29925
29440
  captureScratchpadTouchStart
@@ -29957,7 +29472,8 @@ class NumberInput extends React__namespace.Component {
29957
29472
  onFormatChange: PropTypes__default["default"].func,
29958
29473
  checkValidity: PropTypes__default["default"].func,
29959
29474
  size: PropTypes__default["default"].oneOf(["mini", "small", "normal"]),
29960
- label: PropTypes__default["default"].oneOf(["put your labels outside your inputs!"])
29475
+ label: PropTypes__default["default"].oneOf(["put your labels outside your inputs!"]),
29476
+ allowPiTruncation: PropTypes__default["default"].bool
29961
29477
  };
29962
29478
  static defaultProps = {
29963
29479
  value: null,
@@ -29970,6 +29486,19 @@ class NumberInput extends React__namespace.Component {
29970
29486
  state = {
29971
29487
  format: this.props.format
29972
29488
  };
29489
+ componentDidMount() {
29490
+ // If the value is a multiple of pi, but it is not in the pi format,
29491
+ // then convert it to the pi format and show it as a multiple of pi.
29492
+ const value = this.getValue();
29493
+ if (this.props.allowPiTruncation && value !== null && value !== 0) {
29494
+ if (this.state.format !== "pi" && isPiMultiple(value)) {
29495
+ this._setValue(value / Math.PI, "pi");
29496
+ this.setState({
29497
+ format: "pi"
29498
+ });
29499
+ }
29500
+ }
29501
+ }
29973
29502
  componentDidUpdate(prevProps) {
29974
29503
  if (!kmath.number.equal(this.getValue(), this.props.value)) {
29975
29504
  this._setValue(this.props.value, this.state.format);
@@ -30126,7 +29655,8 @@ class RangeInput extends React__namespace.Component {
30126
29655
  value: PropTypes__default["default"].array.isRequired,
30127
29656
  onChange: PropTypes__default["default"].func.isRequired,
30128
29657
  placeholder: PropTypes__default["default"].array,
30129
- checkValidity: PropTypes__default["default"].func
29658
+ checkValidity: PropTypes__default["default"].func,
29659
+ allowPiTruncation: PropTypes__default["default"].bool
30130
29660
  };
30131
29661
  static defaultProps = {
30132
29662
  placeholder: [null, null]
@@ -30150,14 +29680,16 @@ class RangeInput extends React__namespace.Component {
30150
29680
  // eslint-disable-next-line react/jsx-no-bind
30151
29681
  ,
30152
29682
  onChange: this.onChange.bind(this, 0),
30153
- placeholder: this.props.placeholder[0]
29683
+ placeholder: this.props.placeholder[0],
29684
+ allowPiTruncation: this.props.allowPiTruncation
30154
29685
  })), /*#__PURE__*/React__namespace.createElement(NumberInput, _extends({}, this.props, {
30155
29686
  value: value[1],
30156
29687
  checkValidity: val => checkValidity([value[0], val])
30157
29688
  // eslint-disable-next-line react/jsx-no-bind
30158
29689
  ,
30159
29690
  onChange: this.onChange.bind(this, 1),
30160
- placeholder: this.props.placeholder[1]
29691
+ placeholder: this.props.placeholder[1],
29692
+ allowPiTruncation: this.props.allowPiTruncation
30161
29693
  })));
30162
29694
  }
30163
29695
  }
@@ -37226,10 +36758,7 @@ var InteractiveGraph$1 = {
37226
36758
  name: "interactive-graph",
37227
36759
  displayName: "Interactive graph (Assessments only)",
37228
36760
  widget: InteractiveGraph,
37229
- staticTransform: staticTransform$3,
37230
- // TODO(LEMS-2656): remove TS suppression
37231
- // @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusInteractiveGraphUserInput'.
37232
- scorer: perseusScore.scoreInteractiveGraph
36761
+ staticTransform: staticTransform$3
37233
36762
  };
37234
36763
 
37235
36764
  // TODO: can we use WB typography?
@@ -38187,9 +37716,7 @@ var LabelImage$1 = {
38187
37716
  widget: LabelImageWithDependencies,
38188
37717
  accessible: true,
38189
37718
  isLintable: true,
38190
- // TODO(LEMS-2656): remove TS suppression
38191
- // @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusLabelImageUserInput'.
38192
- scorer: perseusScore.scoreLabelImage
37719
+ getPublicWidgetOptions: perseusCore.getLabelImagePublicWidgetOptions
38193
37720
  };
38194
37721
 
38195
37722
  /* eslint-disable @khanacademy/ts-no-error-suppressions */
@@ -38426,7 +37953,7 @@ class Draggable extends React__namespace.Component {
38426
37953
  // We need to keep backwards compatbility with rules specified directly
38427
37954
  // in CSS. Hence the hacky tacking on of manual classNames.
38428
37955
  // See sortable.less for details.
38429
- let className = aphrodite.css(styles$8.card, styles$8.draggable, layout === "horizontal" && styles$8.horizontalCard, layout === "vertical" && styles$8.verticalCard, type === ItemState.DRAGGING && styles$8.dragging, type === ItemState.DISABLED && styles$8.disabled, !includePadding && styles$8.unpaddedCard) + " " + ClassNames.INTERACTIVE + " perseus-sortable-draggable";
37956
+ let className = aphrodite.css(styles$8.card, styles$8.draggable, layout === "horizontal" && styles$8.horizontalCard, layout === "vertical" && styles$8.verticalCard, type === ItemState.DRAGGING && styles$8.dragging, type === ItemState.DISABLED && styles$8.disabled, !includePadding && styles$8.unpaddedCard) + " " + "perseus-sortable-draggable";
38430
37957
  if (!includePadding) {
38431
37958
  className += " perseus-sortable-draggable-unpadded";
38432
37959
  }
@@ -39117,10 +38644,7 @@ var Matcher$1 = {
39117
38644
  name: "matcher",
39118
38645
  displayName: "Matcher (two column)",
39119
38646
  widget: Matcher,
39120
- isLintable: true,
39121
- // TODO(LEMS-2656): remove TS suppression
39122
- // @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusMatcherUserInput'.
39123
- scorer: perseusScore.scoreMatcher
38647
+ isLintable: true
39124
38648
  };
39125
38649
 
39126
38650
  const getPromptJSON$8 = (renderProps, userInput) => {
@@ -39523,13 +39047,7 @@ var Matrix$1 = {
39523
39047
  widget: Matrix,
39524
39048
  transform: propTransform$1,
39525
39049
  staticTransform: staticTransform$2,
39526
- isLintable: true,
39527
- // TODO(LEMS-2656): remove TS suppression
39528
- // @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusMatrixUserInput'.
39529
- scorer: perseusScore.scoreMatrix,
39530
- // TODO(LEMS-2656): remove TS suppression
39531
- // @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusMatrixUserInput'.
39532
- validator: perseusScore.validateMatrix
39050
+ isLintable: true
39533
39051
  };
39534
39052
 
39535
39053
  const getPromptJSON$7 = () => {
@@ -39619,7 +39137,7 @@ class Measurer extends React__namespace.Component {
39619
39137
  // TODO(scottgrant): This isn't a11y-friendly! We should insist on
39620
39138
  // finding some valid alt text when this widget is used.
39621
39139
  return /*#__PURE__*/React__namespace.createElement("div", {
39622
- className: "perseus-widget perseus-widget-measurer " + "graphie-container above-scratchpad",
39140
+ className: "perseus-widget perseus-widget-measurer " + "graphie-container blank-background",
39623
39141
  style: {
39624
39142
  width: this.props.box[0],
39625
39143
  height: this.props.box[1]
@@ -39644,9 +39162,7 @@ var Measurer$1 = {
39644
39162
  hidden: true,
39645
39163
  widget: Measurer,
39646
39164
  version: perseusCore.measurerLogic.version,
39647
- propUpgrades: perseusCore.measurerLogic.widgetOptionsUpgrades,
39648
- // TODO: things that aren't interactive shouldn't need scoring functions
39649
- scorer: () => scoreNoop(1)
39165
+ propUpgrades: perseusCore.measurerLogic.widgetOptionsUpgrades
39650
39166
  };
39651
39167
 
39652
39168
  /**
@@ -40534,8 +40050,7 @@ var Molecule$1 = {
40534
40050
  name: "molecule-renderer",
40535
40051
  displayName: "Molecule renderer",
40536
40052
  hidden: true,
40537
- widget: MoleculeWidget,
40538
- scorer: () => scoreNoop()
40053
+ widget: MoleculeWidget
40539
40054
  };
40540
40055
 
40541
40056
  const getPromptJSON$6 = (renderProps, userInput) => {
@@ -41110,12 +40625,7 @@ var NumberLine$1 = {
41110
40625
  widget: NumberLine,
41111
40626
  transform: numberLineTransform,
41112
40627
  staticTransform: staticTransform$1,
41113
- // TODO(LEMS-2656): remove TS suppression
41114
- // @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusNumberLineUserInput'.
41115
- scorer: perseusScore.scoreNumberLine,
41116
- // TODO(LEMS-2656): remove TS suppression
41117
- // @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusNumberLineUserInput'.
41118
- validator: perseusScore.validateNumberLine
40628
+ getPublicWidgetOptions: perseusCore.getNumberLinePublicWidgetOptions
41119
40629
  };
41120
40630
 
41121
40631
  const getPromptJSON$5 = (renderProps, userInput) => {
@@ -41133,7 +40643,7 @@ const getPromptJSON$5 = (renderProps, userInput) => {
41133
40643
  class PlaceholderCard extends React__namespace.Component {
41134
40644
  render() {
41135
40645
  return /*#__PURE__*/React__namespace.createElement("div", {
41136
- className: "card-wrap " + ClassNames.INTERACTIVE,
40646
+ className: "card-wrap",
41137
40647
  style: {
41138
40648
  width: this.props.width
41139
40649
  }
@@ -41148,7 +40658,7 @@ class PlaceholderCard extends React__namespace.Component {
41148
40658
  class DragHintCard extends React__namespace.Component {
41149
40659
  render() {
41150
40660
  return /*#__PURE__*/React__namespace.createElement("div", {
41151
- className: "card-wrap " + ClassNames.INTERACTIVE
40661
+ className: "card-wrap"
41152
40662
  }, /*#__PURE__*/React__namespace.createElement("div", {
41153
40663
  className: "card drag-hint"
41154
40664
  }));
@@ -41283,7 +40793,7 @@ class Card extends React__namespace.Component {
41283
40793
  const rendererProps = ___default["default"].pick(this.props, "content");
41284
40794
  const onMouseDown = this.props.animating ? $__default["default"].noop : this.onMouseDown;
41285
40795
  return /*#__PURE__*/React__namespace.createElement("div", {
41286
- className: "card-wrap " + ClassNames.INTERACTIVE,
40796
+ className: "card-wrap",
41287
40797
  style: style,
41288
40798
  onMouseDown: onMouseDown,
41289
40799
  onTouchStart: onMouseDown,
@@ -41654,7 +41164,7 @@ class Orderer extends React__namespace.Component {
41654
41164
  });
41655
41165
  }, this));
41656
41166
  return /*#__PURE__*/React__namespace.createElement("div", {
41657
- className: "draggy-boxy-thing orderer " + "height-" + this.props.height + " " + "layout-" + this.props.layout + " " + "above-scratchpad blank-background " + "perseus-clearfix " + ClassNames.INTERACTIVE
41167
+ className: "draggy-boxy-thing orderer " + "height-" + this.props.height + " " + "layout-" + this.props.layout + " " + "blank-background " + "perseus-clearfix "
41658
41168
  // eslint-disable-next-line react/no-string-refs
41659
41169
  ,
41660
41170
  ref: "orderer"
@@ -41667,12 +41177,6 @@ var Orderer$1 = {
41667
41177
  hidden: true,
41668
41178
  widget: Orderer,
41669
41179
  isLintable: true,
41670
- // TODO(LEMS-2656): remove TS suppression
41671
- // @ts-expect-error: Type UserInput is not assignable to type PerseusOrdererUserInput
41672
- scorer: perseusScore.scoreOrderer,
41673
- // TODO(LEMS-2656): remove TS suppression
41674
- // @ts-expect-error: Type UserInput is not assignable to type PerseusOrdererUserInput
41675
- validator: perseusScore.validateOrderer,
41676
41180
  getPublicWidgetOptions: perseusCore.getOrdererPublicWidgetOptions
41677
41181
  };
41678
41182
 
@@ -41716,15 +41220,12 @@ class PassageRefTarget extends React__namespace.Component {
41716
41220
  var PassageRefTarget$1 = {
41717
41221
  name: "passage-ref-target",
41718
41222
  displayName: "PassageRefTarget",
41719
- defaultAlignment: "inline",
41720
41223
  widget: PassageRefTarget,
41721
41224
  hidden: true,
41722
41225
  transform: editorProps => {
41723
41226
  return ___default["default"].pick(editorProps, "content");
41724
41227
  },
41725
- isLintable: true,
41726
- // TODO: things that aren't interactive shouldn't need scoring functions
41727
- scorer: () => scoreNoop()
41228
+ isLintable: true
41728
41229
  };
41729
41230
 
41730
41231
  const getPromptJSON$4 = () => {
@@ -42726,7 +42227,7 @@ class Plotter extends React__namespace.Component {
42726
42227
  marginBottom: this.props.labels[0] ? paddingForBottomLabel : 0
42727
42228
  };
42728
42229
  return /*#__PURE__*/React__namespace.createElement("div", {
42729
- className: "perseus-widget-plotter graphie " + ClassNames.INTERACTIVE
42230
+ className: "perseus-widget-plotter graphie"
42730
42231
  // eslint-disable-next-line react/no-string-refs
42731
42232
  ,
42732
42233
  ref: "graphieDiv",
@@ -42742,13 +42243,7 @@ var Plotter$1 = {
42742
42243
  displayName: "Plotter",
42743
42244
  hidden: true,
42744
42245
  widget: Plotter,
42745
- staticTransform: staticTransform,
42746
- // TODO(LEMS-2656): remove TS suppression
42747
- // @ts-expect-error: Type UserInput is not assignable to type PerseusPlotterUserInput
42748
- scorer: perseusScore.scorePlotter,
42749
- // TODO(LEMS-2656): remove TS suppression
42750
- // @ts-expect-error: Type UserInput is not assignable to type PerseusPlotterUserInput
42751
- validator: perseusScore.validatePlotter
42246
+ staticTransform: staticTransform
42752
42247
  };
42753
42248
 
42754
42249
  const getPromptJSON$2 = () => {
@@ -42909,12 +42404,7 @@ var Sorter$1 = {
42909
42404
  displayName: "Sorter",
42910
42405
  widget: Sorter,
42911
42406
  isLintable: true,
42912
- // TODO(LEMS-2656): remove TS suppression
42913
- // @ts-expect-error: Type UserInput is not assignable to type PerseusSorterUserInput
42914
- scorer: perseusScore.scoreSorter,
42915
- // TODO(LEMS-2656): remove TS suppression
42916
- // @ts-expect-error: Type UserInput is not assignable to type PerseusSorterUserInput
42917
- validator: perseusScore.validateSorter
42407
+ getPublicWidgetOptions: perseusCore.getSorterPublicWidgetOptions
42918
42408
  };
42919
42409
 
42920
42410
  const {
@@ -43130,13 +42620,7 @@ var Table$1 = {
43130
42620
  widget: Table,
43131
42621
  transform: propTransform,
43132
42622
  hidden: true,
43133
- isLintable: true,
43134
- // TODO(LEMS-2656): remove TS suppression
43135
- // @ts-expect-error: Type UserInput is not assignable to type PerseusTableUserInput
43136
- scorer: perseusScore.scoreTable,
43137
- // TODO(LEMS-2656): remove TS suppression
43138
- // @ts-expect-error: Type UserInput is not assignable to type PerseusTableUserInput
43139
- validator: perseusScore.validateTable
42623
+ isLintable: true
43140
42624
  };
43141
42625
 
43142
42626
  const getPromptJSON = () => {
@@ -43298,11 +42782,7 @@ class Video extends React__namespace.Component {
43298
42782
  var Video$1 = {
43299
42783
  name: "video",
43300
42784
  displayName: "Video",
43301
- defaultAlignment: "block",
43302
- supportedAlignments: ["block", "float-left", "float-right", "full-width"],
43303
- widget: Video,
43304
- // TODO: things that aren't interactive shouldn't need scoring functions
43305
- scorer: () => scoreNoop()
42785
+ widget: Video
43306
42786
  };
43307
42787
 
43308
42788
  /* eslint-disable import/no-named-as-default */
@@ -43321,7 +42801,7 @@ const init = function () {
43321
42801
 
43322
42802
  // This file is processed by a Rollup plugin (replace) to inject the production
43323
42803
  const libName = "@khanacademy/perseus";
43324
- const libVersion = "52.0.1";
42804
+ const libVersion = "53.0.0";
43325
42805
  perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
43326
42806
 
43327
42807
  const apiVersion = {
@@ -44197,6 +43677,42 @@ const ref = /*#__PURE__*/React__namespace.forwardRef((props, ref) => /*#__PURE__
44197
43677
  }));
44198
43678
  }));
44199
43679
 
43680
+ function isCorrect(score) {
43681
+ return score.type === "points" && score.earned >= score.total;
43682
+ }
43683
+ function keScoreFromPerseusScore(score,
43684
+ // It's weird, but this is what we're passing it
43685
+ guess, state) {
43686
+ if (score.type === "points") {
43687
+ return {
43688
+ empty: false,
43689
+ correct: isCorrect(score),
43690
+ message: score.message,
43691
+ guess: guess,
43692
+ state: state
43693
+ };
43694
+ }
43695
+ if (score.type === "invalid") {
43696
+ return {
43697
+ empty: true,
43698
+ correct: false,
43699
+ message: score.message,
43700
+ suppressAlmostThere: score.suppressAlmostThere,
43701
+ guess: guess,
43702
+ state: state
43703
+ };
43704
+ }
43705
+ throw new perseusCore.PerseusError(
43706
+ // @ts-expect-error - TS2339 - Property 'type' does not exist on type 'never'.
43707
+ "Invalid score type: " + score.type, perseusCore.Errors.InvalidInput, {
43708
+ metadata: {
43709
+ score: JSON.stringify(score),
43710
+ guess: JSON.stringify(guess),
43711
+ state: JSON.stringify(state)
43712
+ }
43713
+ });
43714
+ }
43715
+
44200
43716
  /**
44201
43717
  * Traverses a {content, widgets, images} renderer props object,
44202
43718
  * such as `itemData.question`
@@ -44217,7 +43733,7 @@ const deepCallbackFor = function (contentCallback, widgetCallback, optionsCallba
44217
43733
  // This doesn't modify the widget info if the widget info
44218
43734
  // is at a later version than is supported, which is important
44219
43735
  // for our latestVersion test below.
44220
- const upgradedWidgetInfo = upgradeWidgetInfoToLatestVersion(widgetInfo);
43736
+ const upgradedWidgetInfo = perseusCore.upgradeWidgetInfoToLatestVersion(widgetInfo);
44221
43737
  const latestVersion = getVersion(upgradedWidgetInfo.type);
44222
43738
 
44223
43739
  // Only traverse our children if we can understand this version
@@ -44329,7 +43845,7 @@ const isRawWidgetInfoRenderableBy = function (widgetInfo, rendererContentVersion
44329
43845
 
44330
43846
  // NOTE: This doesn't modify the widget info if the widget info
44331
43847
  // is at a later version than is supported.
44332
- const upgradedWidgetInfo = upgradeWidgetInfoToLatestVersion(widgetInfo);
43848
+ const upgradedWidgetInfo = perseusCore.upgradeWidgetInfoToLatestVersion(widgetInfo);
44333
43849
  return isUpgradedWidgetInfoRenderableBy(upgradedWidgetInfo, rendererContentVersion[upgradedWidgetInfo.type]);
44334
43850
  };
44335
43851
  const isRendererContentRenderableBy = function (rendererOptions, rendererContentVersion) {
@@ -45677,7 +45193,6 @@ exports.mediaQueries = mediaQueries;
45677
45193
  exports.parseDataFromJSONP = parseDataFromJSONP;
45678
45194
  exports.preprocessTex = preprocessTex;
45679
45195
  exports.registerAllWidgetsForTesting = registerAllWidgetsForTesting;
45680
- exports.scorePerseusItem = scorePerseusItem;
45681
45196
  exports.shouldHaveIndividualAnswer = shouldHaveIndividualAnswer;
45682
45197
  exports.traverse = traverse;
45683
45198
  exports.usePerseusI18n = usePerseusI18n;