@khanacademy/perseus 38.0.1 → 39.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/dist/components/math-input.d.ts +1 -1
  2. package/dist/components/sortable.d.ts +1 -1
  3. package/dist/definition-context.d.ts +1 -1
  4. package/dist/es/index.js +121 -226
  5. package/dist/es/index.js.map +1 -1
  6. package/dist/index.d.ts +1 -1
  7. package/dist/index.js +96 -202
  8. package/dist/index.js.map +1 -1
  9. package/dist/interactive2/movable-point.d.ts +1 -1
  10. package/dist/multi-items/prop-type-builders.d.ts +0 -6
  11. package/dist/perseus-api.d.ts +3 -1
  12. package/dist/perseus-types.d.ts +3 -42
  13. package/dist/renderer.d.ts +1 -2
  14. package/dist/server-item-renderer.d.ts +3 -2
  15. package/dist/sigfigs.d.ts +0 -16
  16. package/dist/styles/constants.d.ts +0 -18
  17. package/dist/types.d.ts +4 -6
  18. package/dist/util/math.d.ts +0 -1
  19. package/dist/util.d.ts +2 -1
  20. package/dist/validation.types.d.ts +17 -16
  21. package/dist/widgets/__shared__/noop-validator.d.ts +1 -1
  22. package/dist/widgets/definition/definition.d.ts +1 -2
  23. package/dist/widgets/explanation/explanation.d.ts +1 -2
  24. package/dist/widgets/expression/expression.d.ts +25 -12
  25. package/dist/widgets/iframe/iframe-validator.d.ts +1 -1
  26. package/dist/widgets/image/image.d.ts +1 -2
  27. package/dist/widgets/input-number/input-number.d.ts +0 -1
  28. package/dist/widgets/interaction/interaction.d.ts +1 -2
  29. package/dist/widgets/interactive-graphs/graphs/components/movable-line.d.ts +0 -11
  30. package/dist/widgets/interactive-graphs/graphs/point.d.ts +0 -2
  31. package/dist/widgets/interactive-graphs/graphs/use-transform.d.ts +0 -1
  32. package/dist/widgets/interactive-graphs/mafs-supported-graph-types.d.ts +2 -1
  33. package/dist/widgets/interactive-graphs/protractor.d.ts +0 -2
  34. package/dist/widgets/label-image/answer-choices.d.ts +1 -1
  35. package/dist/widgets/label-image/label-image.d.ts +12 -28
  36. package/dist/widgets/matrix/matrix-validator.d.ts +1 -1
  37. package/dist/widgets/matrix/matrix.d.ts +0 -1
  38. package/dist/widgets/measurer/measurer.d.ts +3 -15
  39. package/dist/widgets/molecule/molecule.d.ts +0 -21
  40. package/dist/widgets/number-line/number-line.d.ts +0 -1
  41. package/dist/widgets/numeric-input/numeric-input.d.ts +0 -1
  42. package/dist/widgets/orderer/orderer.d.ts +1 -1
  43. package/dist/widgets/passage/passage.d.ts +1 -2
  44. package/dist/widgets/passage-ref/passage-ref.d.ts +1 -2
  45. package/dist/widgets/radio/base-radio.d.ts +5 -10
  46. package/dist/widgets/radio/focus-ring.d.ts +4 -11
  47. package/dist/widgets/table/table.d.ts +0 -1
  48. package/dist/widgets/video/video.d.ts +1 -2
  49. package/package.json +3 -3
  50. package/dist/util/fix-passage-refs.d.ts +0 -3
  51. package/dist/widgets/radio/choice.jsx.fixture.d.ts +0 -1
package/dist/index.d.ts CHANGED
@@ -84,7 +84,7 @@ export type { ILogger, LogErrorOptions } from "./logging/log";
84
84
  export type { ServerItemRenderer as ServerItemRendererComponent } from "./server-item-renderer";
85
85
  export type { Alignment, APIOptions, APIOptionsWithDefaults, ChangeHandler, DeviceType, DomInsertCheckFn, EditorMode, FocusPath, ImageDict, ImageUploader, JiptLabelStore, JiptRenderer, PerseusDependencies, PerseusScore, Version, VideoData, VideoKind, WidgetExports, SharedRendererProps, } from "./types";
86
86
  export type { ParsedValue } from "./util";
87
- export type { Hint, LegacyButtonSets, LockedFigure, LockedFigureColor, LockedFigureFillType, LockedFigureType, LockedPointType, LockedLineType, LockedVectorType, LockedEllipseType, LockedPolygonType, LockedFunctionType, LockedLabelType, PerseusGraphType, PerseusAnswerArea, PerseusExpressionWidgetOptions, Range, Size, CollinearTuple, MathFormat, InputNumberWidget, // TODO(jeremy): remove?
87
+ export type { Hint, LegacyButtonSets, LockedFigure, LockedFigureColor, LockedFigureFillType, LockedFigureType, LockedPointType, LockedLineType, LockedVectorType, LockedEllipseType, LockedPolygonType, LockedFunctionType, LockedLabelType, LockedLineStyle, PerseusGraphType, PerseusAnswerArea, PerseusExpressionWidgetOptions, Range, Size, CollinearTuple, MathFormat, InputNumberWidget, // TODO(jeremy): remove?
88
88
  PerseusImageBackground, PerseusInputNumberWidgetOptions, PerseusInteractiveGraphWidgetOptions, PerseusItem, PerseusPhetSimulationWidgetOptions, PerseusPlotterWidgetOptions, PerseusPythonProgramWidgetOptions, PerseusRadioWidgetOptions, PerseusExampleWidgetOptions, PerseusSimpleMarkdownTesterWidgetOptions, PerseusRenderer, PerseusWidget, PerseusWidgetsMap, MultiItem, } from "./perseus-types";
89
89
  export type { Coord } from "./interactive2/types";
90
90
  export type { MarkerType } from "./widgets/label-image/types";
package/dist/index.js CHANGED
@@ -2530,12 +2530,6 @@ class Expression extends React__namespace.Component {
2530
2530
  /* c8 ignore next */
2531
2531
  return [[]];
2532
2532
  };
2533
-
2534
- // TODO(Nicole): I believe this is going away and won't be needed anymore
2535
- getGrammarTypeForPath(inputPath) {
2536
- /* c8 ignore next */
2537
- return "expression";
2538
- }
2539
2533
  setInputValue(path, newValue, cb) {
2540
2534
  this.props.onChange({
2541
2535
  value: newValue
@@ -2995,7 +2989,9 @@ function flattenScores(widgetScoreMap) {
2995
2989
  function isCorrect(score) {
2996
2990
  return score.type === "points" && score.earned >= score.total;
2997
2991
  }
2998
- function keScoreFromPerseusScore(score, guess, state) {
2992
+ function keScoreFromPerseusScore(score,
2993
+ // It's weird, but this is what we're passing it
2994
+ guess, state) {
2999
2995
  if (score.type === "points") {
3000
2996
  return {
3001
2997
  empty: false,
@@ -4006,36 +4002,14 @@ const ZoomService = new ZoomServiceClass();
4006
4002
  const baseFontFamily$1 = "'Lato', sans-serif";
4007
4003
  // @boldFontFamily: "Lato-Bold", "Lato", sans-serif;
4008
4004
  const boldFontFamily = "'Lato-Bold', 'Lato', sans-serif";
4009
- // @green: #76a005;
4010
- const green$1 = "#76A005";
4011
4005
  // @kaGreen: #71b307;
4012
4006
  const kaGreen$1 = "#71B307";
4013
- // @blue: #1c758a;
4014
- const blue$1 = "#1C758A";
4015
- // @gray: #aaa;
4016
- const gray$1 = "#AAAAAA";
4017
- // @red: #ffbaba;
4018
- const red$1 = "#FFBABA";
4019
- // @grayLight: #aaa;
4020
- const grayLight$1 = "#AAAAAA";
4021
- // @grayLighter: #ddd;
4022
- const grayLighter$1 = "#DDDDDD";
4023
- // @learnstormBlue: #4898fc;
4024
- const learnstormBlue$1 = "#4898FC";
4025
4007
  const white$1 = "#FFFFFF";
4026
- const gray98 = "#FAFAFA";
4027
4008
  const gray97 = "#F6F7F7";
4028
- const gray95 = "#F0F1F2";
4029
- const gray90 = "#E3E5E6";
4030
4009
  const gray85 = "#D6D8DA";
4031
4010
  const gray76 = "#BABEC2";
4032
4011
  const gray68 = "#888D93";
4033
- const gray41 = "#626569";
4034
- const gray25 = "#3B3e40";
4035
4012
  const gray17 = "#21242c";
4036
- const black$1 = "#000000";
4037
- const warning1 = "#F86700";
4038
- const warning3 = "#C75300";
4039
4013
 
4040
4014
  // @pure-sm-min: 568px;
4041
4015
  const pureSmMin$2 = "568px";
@@ -4055,16 +4029,10 @@ const pureMdMax$2 = "1023px";
4055
4029
  const pureLgMax$2 = "1279px";
4056
4030
  // @tableBackgroundAccent: #f9f9f9; // for striping
4057
4031
  const tableBackgroundAccent$1 = "#F9F9F9";
4058
- // @zIndexScratchPad: 1;
4059
- const zIndexScratchPad = 1;
4060
4032
  // @zIndexAboveScratchpad: @zIndexScratchPad + 1;
4061
4033
  const zIndexAboveScratchpad$1 = 2;
4062
4034
  // @zIndexInteractiveComponent: @zIndexAboveScratchpad + 1;
4063
4035
  const zIndexInteractiveComponent$1 = 3;
4064
- // @zIndexCurrentlyDragging: @zIndexInteractiveComponent + 1;
4065
- const zIndexCurrentlyDragging = 4;
4066
- // @zIndexCalculator: @zIndexCurrentlyDragging + 1;
4067
- const zIndexCalculator = 5;
4068
4036
  // @phoneMargin: 16px;
4069
4037
  const phoneMargin = 16;
4070
4038
  const negativePhoneMargin = -16;
@@ -4092,28 +4060,13 @@ var constants$1 = /*#__PURE__*/Object.freeze({
4092
4060
  __proto__: null,
4093
4061
  baseFontFamily: baseFontFamily$1,
4094
4062
  boldFontFamily: boldFontFamily,
4095
- green: green$1,
4096
4063
  kaGreen: kaGreen$1,
4097
- blue: blue$1,
4098
- gray: gray$1,
4099
- red: red$1,
4100
- grayLight: grayLight$1,
4101
- grayLighter: grayLighter$1,
4102
- learnstormBlue: learnstormBlue$1,
4103
4064
  white: white$1,
4104
- gray98: gray98,
4105
4065
  gray97: gray97,
4106
- gray95: gray95,
4107
- gray90: gray90,
4108
4066
  gray85: gray85,
4109
4067
  gray76: gray76,
4110
4068
  gray68: gray68,
4111
- gray41: gray41,
4112
- gray25: gray25,
4113
4069
  gray17: gray17,
4114
- black: black$1,
4115
- warning1: warning1,
4116
- warning3: warning3,
4117
4070
  pureSmMin: pureSmMin$2,
4118
4071
  pureMdMin: pureMdMin$2,
4119
4072
  pureLgMin: pureLgMin$2,
@@ -4123,11 +4076,8 @@ var constants$1 = /*#__PURE__*/Object.freeze({
4123
4076
  pureMdMax: pureMdMax$2,
4124
4077
  pureLgMax: pureLgMax$2,
4125
4078
  tableBackgroundAccent: tableBackgroundAccent$1,
4126
- zIndexScratchPad: zIndexScratchPad,
4127
4079
  zIndexAboveScratchpad: zIndexAboveScratchpad$1,
4128
4080
  zIndexInteractiveComponent: zIndexInteractiveComponent$1,
4129
- zIndexCurrentlyDragging: zIndexCurrentlyDragging,
4130
- zIndexCalculator: zIndexCalculator,
4131
4081
  phoneMargin: phoneMargin,
4132
4082
  negativePhoneMargin: negativePhoneMargin,
4133
4083
  hintBorderWidth: hintBorderWidth,
@@ -20119,6 +20069,7 @@ class Renderer extends React__namespace.Component {
20119
20069
  onBlur: ___default["default"].partial(this._onWidgetBlur, id),
20120
20070
  findWidgets: this.findWidgets,
20121
20071
  reviewModeRubric: reviewModeRubric,
20072
+ reviewMode: this.props.reviewMode,
20122
20073
  onChange: function (newProps, cb) {
20123
20074
  let silent = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
20124
20075
  _this._setWidgetProps(id, newProps, cb, silent);
@@ -20832,14 +20783,6 @@ class Renderer extends React__namespace.Component {
20832
20783
  return ReactDOM__default["default"].findDOMNode(widget);
20833
20784
  }
20834
20785
  };
20835
- getGrammarTypeForPath = path => {
20836
- // @ts-expect-error - TS2345 - Argument of type 'FocusPath' is not assignable to parameter of type 'List<any>'.
20837
- const widgetId = ___default["default"].first(path);
20838
- // @ts-expect-error - TS2345 - Argument of type 'FocusPath' is not assignable to parameter of type 'List<any>'.
20839
- const interWidgetPath = ___default["default"].rest(path);
20840
- const widget = this.getWidgetInstance(widgetId);
20841
- return widget?.getGrammarTypeForPath?.(interWidgetPath);
20842
- };
20843
20786
  getInputPaths = () => {
20844
20787
  const inputPaths = [];
20845
20788
  ___default["default"].each(this.widgetIds, widgetId => {
@@ -21040,11 +20983,11 @@ class Renderer extends React__namespace.Component {
21040
20983
  const combinedScore = Util.flattenScores(scores);
21041
20984
  return combinedScore;
21042
20985
  }
21043
- guessAndScore = () => {
20986
+ guessAndScore() {
21044
20987
  const totalGuess = this.getUserInput();
21045
20988
  const totalScore = this.score();
21046
20989
  return [totalGuess, totalScore];
21047
- };
20990
+ }
21048
20991
  examples = () => {
21049
20992
  const widgetIds = this.widgetIds;
21050
20993
  const examples = ___default["default"].compact(___default["default"].map(widgetIds, widgetId => {
@@ -21317,7 +21260,14 @@ const Triangle = props => {
21317
21260
  }
21318
21261
  });
21319
21262
  };
21320
- const TooltipArrow = props => {
21263
+ const TooltipArrow = _ref => {
21264
+ let {
21265
+ position = "relative",
21266
+ visibility = "visible",
21267
+ left = 100,
21268
+ top = 0,
21269
+ ...props
21270
+ } = _ref;
21321
21271
  // TODO(aria): Think about adding a box-shadow to the triangle here
21322
21272
  // See http://css-tricks.com/triangle-with-shadow/
21323
21273
 
@@ -21328,10 +21278,10 @@ const TooltipArrow = props => {
21328
21278
  return /*#__PURE__*/React__namespace.createElement("div", {
21329
21279
  style: {
21330
21280
  display: "block",
21331
- position: props.position,
21332
- visibility: props.visibility,
21333
- left: props.left,
21334
- top: props["top"],
21281
+ position: position,
21282
+ visibility: visibility,
21283
+ left: left,
21284
+ top: top,
21335
21285
  width: props.width + 2,
21336
21286
  height: props.height + 1,
21337
21287
  marginTop: -1,
@@ -21357,12 +21307,6 @@ const TooltipArrow = props => {
21357
21307
  height: props.height
21358
21308
  }));
21359
21309
  };
21360
- TooltipArrow.defaultProps = {
21361
- position: "relative",
21362
- visibility: "visible",
21363
- left: 0,
21364
- top: 0
21365
- };
21366
21310
  const VERTICAL_CORNERS = {
21367
21311
  top: {
21368
21312
  top: "-100%"
@@ -22023,13 +21967,6 @@ class InputNumber extends React__namespace.Component {
22023
21967
  /* c8 ignore next */
22024
21968
  return [[]];
22025
21969
  };
22026
-
22027
- // Note: We believe that this isn't used anywhere but are leaving it during
22028
- // the initial refactor
22029
- getGrammarTypeForPath = path => {
22030
- /* c8 ignore next */
22031
- return "number";
22032
- };
22033
21970
  setInputValue = (path, newValue, cb) => {
22034
21971
  this.props.onChange({
22035
21972
  currentValue: newValue
@@ -22067,17 +22004,13 @@ class InputNumber extends React__namespace.Component {
22067
22004
  }
22068
22005
  return input;
22069
22006
  }
22070
- // HACK(johnsullivan): Create a function with shared logic between
22071
- // this and NumericInput.
22072
- // TODO(jeremy): Deprecate this widget and prefer numeric-input.
22073
- const rubric = this.props.reviewModeRubric;
22074
22007
 
22075
22008
  // Note: This is _very_ similar to what `numeric-input.jsx` does. If
22076
22009
  // you modify this, double-check if you also need to modify that
22077
22010
  // component.
22078
22011
  const inputStyles = [styles$A.default, this.props.size === "small" ? styles$A.small : null, this.props.rightAlign ? styles$A.rightAlign : styles$A.leftAlign];
22079
22012
  // Unanswered
22080
- if (rubric && !this.props.currentValue) {
22013
+ if (this.props.reviewMode && !this.props.currentValue) {
22081
22014
  inputStyles.push(styles$A.answerStateUnanswered);
22082
22015
  }
22083
22016
  return /*#__PURE__*/React__namespace.createElement(InputWithExamples
@@ -22244,48 +22177,35 @@ function numericInputValidator(userInput, rubric, strings) {
22244
22177
  // We may have received TeX; try to parse it before grading.
22245
22178
  // If `currentValue` is not TeX, this should be a no-op.
22246
22179
  const currentValue = ParseTex(userInput.currentValue);
22247
- const correctAnswers = rubric.answers.filter(answer => answer.status === "correct");
22248
- const normalizedAnswerExpected = correctAnswers.every(answer => Math.abs(answer.value) <= 1);
22249
-
22250
- // Look through all correct answers for one that matches either
22251
- // precisely or approximately and return the appropriate message:
22252
- // - if precise, return the message that the answer came with
22253
- // - if it needs to be simplified, etc., show that message
22254
- let result = correctAnswers.map(answer => {
22255
- // The coefficient is an attribute of the widget
22256
- let localValue = currentValue;
22257
- if (rubric.coefficient) {
22258
- if (!localValue) {
22259
- localValue = 1;
22260
- } else if (localValue === "-") {
22261
- localValue = -1;
22262
- }
22263
- }
22264
- const validate = createValidator(answer);
22265
- return validate(maybeParsePercentInput(localValue, normalizedAnswerExpected));
22266
- }).find(match => match.correct || match.empty);
22267
- if (!result) {
22268
- // Otherwise, if the guess is not correct
22269
- const otherAnswers = [].concat(
22270
- // @ts-expect-error - TS2769 - No overload matches this call.
22271
- rubric.answers.filter(answer => answer.status === "ungraded"), rubric.answers.filter(answer => answer.status === "wrong"));
22180
+ const normalizedAnswerExpected = rubric.answers.filter(answer => answer.status === "correct").every(answer => Math.abs(answer.value) <= 1);
22272
22181
 
22273
- // Look through all other answers and if one matches either
22274
- // precisely or approximately return the answer's message
22275
- const match = otherAnswers.find(answer => {
22276
- const validate = createValidator(answer);
22277
- return validate(maybeParsePercentInput(currentValue, normalizedAnswerExpected)).correct;
22278
- });
22279
- result = {
22280
- // @ts-expect-error - TS2339 - Property 'status' does not exist on type 'never'.
22281
- empty: match ? match.status === "ungraded" : false,
22282
- // @ts-expect-error - TS2339 - Property 'status' does not exist on type 'never'.
22283
- correct: match ? match.status === "correct" : false,
22284
- // @ts-expect-error - TS2339 - Property 'message' does not exist on type 'never'.
22285
- message: match ? match.message : null,
22286
- guess: currentValue
22287
- };
22182
+ // The coefficient is an attribute of the widget
22183
+ let localValue = currentValue;
22184
+ if (rubric.coefficient) {
22185
+ if (!localValue) {
22186
+ localValue = 1;
22187
+ } else if (localValue === "-") {
22188
+ localValue = -1;
22189
+ }
22288
22190
  }
22191
+ const matchedAnswer = rubric.answers.map(answer => {
22192
+ const validateFn = createValidator(answer);
22193
+ const score = validateFn(maybeParsePercentInput(localValue, normalizedAnswerExpected));
22194
+ return {
22195
+ ...answer,
22196
+ score
22197
+ };
22198
+ }).find(answer => {
22199
+ // NOTE: "answer.score.correct" indicates a match via the validate function.
22200
+ // It does NOT indicate that the answer itself is correct.
22201
+ return answer.score.correct || answer.status === "correct" && answer.score.empty;
22202
+ });
22203
+ const result = matchedAnswer?.status === "correct" ? matchedAnswer.score : {
22204
+ empty: matchedAnswer?.status === "ungraded",
22205
+ correct: matchedAnswer?.status === "correct",
22206
+ message: matchedAnswer?.message ?? null,
22207
+ guess: localValue
22208
+ };
22289
22209
 
22290
22210
  // TODO(eater): Seems silly to translate result to this
22291
22211
  // invalid/points thing and immediately translate it
@@ -22378,10 +22298,6 @@ class NumericInput extends React__namespace.Component {
22378
22298
  /* c8 ignore next */
22379
22299
  return [[]];
22380
22300
  };
22381
- getGrammarTypeForPath = inputPath => {
22382
- /* c8 ignore next */
22383
- return "number";
22384
- };
22385
22301
  setInputValue = (path, newValue, cb) => {
22386
22302
  /* c8 ignore next */
22387
22303
  this.props.onChange({
@@ -22551,7 +22467,7 @@ var NumericInput$1 = {
22551
22467
  * Several widgets don't have "right"/"wrong" validation logic,
22552
22468
  * so this just says to move on past those widgets
22553
22469
  *
22554
- * TODO(LEMS-2396) widgets that use this probably shouldn't have any
22470
+ * TODO(LEMS-2543) widgets that use this probably shouldn't have any
22555
22471
  * validation logic and the thing validating an exercise
22556
22472
  * should just know to skip these
22557
22473
  */
@@ -24625,7 +24541,7 @@ class Passage extends React__namespace.Component {
24625
24541
  const enabled = this.state.stylesAreApplied;
24626
24542
 
24627
24543
  // Highlights are read-only in review mode.
24628
- const editable = !this.props.reviewModeRubric;
24544
+ const editable = !this.props.reviewMode;
24629
24545
  return /*#__PURE__*/React__namespace.createElement(HighlightableContent, {
24630
24546
  editable: editable,
24631
24547
  enabled: enabled,
@@ -25263,13 +25179,13 @@ function getA11yText(letter, checked, correct, crossedOut, showCorrectness, stri
25263
25179
  /**
25264
25180
  * Renders a circular selection ring around the child.
25265
25181
  */
25266
- const FocusRing = function (props) {
25267
- const {
25268
- visible,
25269
- color,
25270
- children,
25271
- multipleSelect
25272
- } = props;
25182
+ const FocusRing = function (_ref) {
25183
+ let {
25184
+ visible = true,
25185
+ color = kaGreen$1,
25186
+ multipleSelect = false,
25187
+ children
25188
+ } = _ref;
25273
25189
  const borderColor = visible ? color : "transparent";
25274
25190
  const borderRadius = multipleSelect ? 5 : "50%";
25275
25191
  const style = {
@@ -25282,11 +25198,6 @@ const FocusRing = function (props) {
25282
25198
  style: style
25283
25199
  }, children);
25284
25200
  };
25285
- FocusRing.defaultProps = {
25286
- visible: true,
25287
- color: kaGreen$1,
25288
- multipleSelect: false
25289
- };
25290
25201
  const styles$v = aphrodite.StyleSheet.create({
25291
25202
  ring: {
25292
25203
  margin: "auto",
@@ -25825,13 +25736,13 @@ var Choice$1 = /*#__PURE__*/React__namespace.forwardRef((props, ref) => /*#__PUR
25825
25736
  forwardedRef: ref
25826
25737
  })));
25827
25738
 
25828
- const ChoiceNoneAbove = function (props) {
25829
- const {
25830
- showContent,
25739
+ const ChoiceNoneAbove = function (_ref) {
25740
+ let {
25831
25741
  content,
25832
25742
  forwardedRef,
25743
+ showContent = true,
25833
25744
  ...rest
25834
- } = props;
25745
+ } = _ref;
25835
25746
  const {
25836
25747
  strings
25837
25748
  } = usePerseusI18n();
@@ -25859,9 +25770,6 @@ const ChoiceNoneAbove = function (props) {
25859
25770
  ref: forwardedRef
25860
25771
  }));
25861
25772
  };
25862
- ChoiceNoneAbove.defaultProps = {
25863
- showContent: true
25864
- };
25865
25773
  var ChoiceNoneAbove$1 = /*#__PURE__*/React__namespace.forwardRef((props, ref) => /*#__PURE__*/React__namespace.createElement(ChoiceNoneAbove, _extends({}, props, {
25866
25774
  forwardedRef: ref
25867
25775
  })));
@@ -25883,19 +25791,21 @@ function getInstructionsText(multipleSelect, countChoices, numCorrect, strings)
25883
25791
  }
25884
25792
  return strings.chooseOneAnswer;
25885
25793
  }
25886
- const BaseRadio = function (props) {
25887
- const {
25794
+ const BaseRadio = function (_ref) {
25795
+ let {
25888
25796
  apiOptions,
25889
25797
  reviewModeRubric,
25798
+ reviewMode,
25890
25799
  choices,
25891
- editMode,
25892
- multipleSelect,
25800
+ editMode = false,
25801
+ multipleSelect = false,
25893
25802
  labelWrap,
25894
25803
  countChoices,
25895
25804
  numCorrect,
25896
25805
  isLastUsedWidget,
25806
+ onChange,
25897
25807
  registerFocusFunction
25898
- } = props;
25808
+ } = _ref;
25899
25809
  const {
25900
25810
  strings
25901
25811
  } = usePerseusI18n();
@@ -25952,12 +25862,6 @@ const BaseRadio = function (props) {
25952
25862
  // contains a boolean value specifying the new checked and crossed-out
25953
25863
  // value of this choice.
25954
25864
  function updateChoice(choiceIndex, newValues) {
25955
- const {
25956
- multipleSelect,
25957
- choices,
25958
- onChange
25959
- } = props;
25960
-
25961
25865
  // Get the baseline `checked` values. If we're checking a new answer
25962
25866
  // and multiple-select is not on, we should clear all choices to be
25963
25867
  // unchecked. Otherwise, we should copy the old checked values.
@@ -25999,7 +25903,6 @@ const BaseRadio = function (props) {
25999
25903
  });
26000
25904
 
26001
25905
  // some commonly used shorthands
26002
- const reviewMode = !!reviewModeRubric;
26003
25906
  const isMobile = apiOptions.isMobile;
26004
25907
  const firstChoiceHighlighted = choices[0].highlighted;
26005
25908
  const lastChoiceHighlighted = choices[choices.length - 1].highlighted;
@@ -26125,10 +26028,6 @@ const BaseRadio = function (props) {
26125
26028
  className: aphrodite.css(styles$p.responsiveContainer)
26126
26029
  }, fieldset);
26127
26030
  };
26128
- BaseRadio.defaultProps = {
26129
- editMode: false,
26130
- multipleSelect: false
26131
- };
26132
26031
  const styles$p = aphrodite.StyleSheet.create({
26133
26032
  instructions: {
26134
26033
  display: "block",
@@ -26227,7 +26126,7 @@ function radioValidator(userInput, rubric, strings) {
26227
26126
  };
26228
26127
  }
26229
26128
 
26230
- // TODO: should numCorrect actually be on the rubric
26129
+ // TODO(LEMS-2541): should numCorrect actually be on the rubric
26231
26130
  // instead of the userInput?
26232
26131
  if (userInput.numCorrect && userInput.numCorrect > 1 && numSelected !== userInput.numCorrect) {
26233
26132
  return {
@@ -26237,7 +26136,7 @@ function radioValidator(userInput, rubric, strings) {
26237
26136
  // If NOTA and some other answer are checked, ...
26238
26137
  }
26239
26138
 
26240
- // TODO: should noneOfTheAboveSelected be replaced with a
26139
+ // TODO(LEMS-2541): should noneOfTheAboveSelected be replaced with a
26241
26140
  // combination of choicesSelected and noneOfTheAboveIndex?
26242
26141
  if (userInput.noneOfTheAboveSelected && numSelected > 1) {
26243
26142
  return {
@@ -26247,7 +26146,7 @@ function radioValidator(userInput, rubric, strings) {
26247
26146
  }
26248
26147
  const correct = userInput.choicesSelected.every((selected, i) => {
26249
26148
  let isCorrect;
26250
- // TODO: should noneOfTheAboveIndex actually be on the rubric
26149
+ // TODO(LEMS-2541): should noneOfTheAboveIndex actually be on the rubric
26251
26150
  // instead of the userInput?
26252
26151
  if (userInput.noneOfTheAboveIndex === i) {
26253
26152
  isCorrect = rubric.choices.every((choice, j) => {
@@ -26289,7 +26188,6 @@ class Radio$1 extends React__namespace.Component {
26289
26188
  let noneOfTheAboveSelected = false;
26290
26189
  const choiceStates = props.choiceStates;
26291
26190
  const choicesSelected = choiceStates.map(() => false);
26292
- const countChoices = props.countChoices;
26293
26191
  const numCorrect = props.numCorrect;
26294
26192
  for (let i = 0; i < choicesSelected.length; i++) {
26295
26193
  const index = props.choices[i].originalIndex;
@@ -26303,7 +26201,6 @@ class Radio$1 extends React__namespace.Component {
26303
26201
  }
26304
26202
  }
26305
26203
  return {
26306
- countChoices,
26307
26204
  choicesSelected,
26308
26205
  numCorrect,
26309
26206
  noneOfTheAboveIndex,
@@ -26319,7 +26216,6 @@ class Radio$1 extends React__namespace.Component {
26319
26216
  let noneOfTheAboveIndex = null;
26320
26217
  let noneOfTheAboveSelected = false;
26321
26218
  const choicesSelected = [...values];
26322
- const countChoices = props.countChoices;
26323
26219
  const numCorrect = props.numCorrect;
26324
26220
  const valuesLength = values.length;
26325
26221
  for (let i = 0; i < valuesLength; i++) {
@@ -26337,7 +26233,6 @@ class Radio$1 extends React__namespace.Component {
26337
26233
  choicesSelected,
26338
26234
  noneOfTheAboveIndex,
26339
26235
  noneOfTheAboveSelected,
26340
- countChoices,
26341
26236
  numCorrect
26342
26237
  };
26343
26238
  }
@@ -26600,7 +26495,7 @@ class Radio$1 extends React__namespace.Component {
26600
26495
  highlighted,
26601
26496
  previouslyAnswered
26602
26497
  } = choiceStates[i];
26603
- const reviewChoice = this.props.reviewModeRubric && this.props.reviewModeRubric.choices[i];
26498
+ const reviewChoice = this.props.reviewModeRubric?.choices[i];
26604
26499
  return {
26605
26500
  content: this._renderRenderer(content),
26606
26501
  checked: selected,
@@ -26635,6 +26530,7 @@ class Radio$1 extends React__namespace.Component {
26635
26530
  choices: choicesProp,
26636
26531
  onChange: this.updateChoices,
26637
26532
  reviewModeRubric: this.props.reviewModeRubric,
26533
+ reviewMode: this.props.reviewMode,
26638
26534
  deselectEnabled: this.props.deselectEnabled,
26639
26535
  apiOptions: this.props.apiOptions,
26640
26536
  isLastUsedWidget: this.props.isLastUsedWidget,
@@ -29393,7 +29289,7 @@ class Group extends React__namespace.Component {
29393
29289
  ref: ref => this.rendererRef = ref,
29394
29290
  apiOptions: apiOptions,
29395
29291
  findExternalWidgets: this.props.findWidgets,
29396
- reviewMode: !!this.props.reviewModeRubric,
29292
+ reviewMode: this.props.reviewMode,
29397
29293
  onInteractWithWidget: onInteractWithWidget,
29398
29294
  linterContext: this.props.linterContext,
29399
29295
  strings: this.context.strings
@@ -29420,23 +29316,23 @@ var Group$1 = {
29420
29316
  isLintable: true
29421
29317
  };
29422
29318
 
29423
- function iframeValidator(state) {
29319
+ function iframeValidator(userInput) {
29424
29320
  // The iframe can tell us whether it's correct or incorrect,
29425
29321
  // and pass an optional message
29426
- if (state.status === "correct") {
29322
+ if (userInput.status === "correct") {
29427
29323
  return {
29428
29324
  type: "points",
29429
29325
  earned: 1,
29430
29326
  total: 1,
29431
- message: state.message || null
29327
+ message: userInput.message || null
29432
29328
  };
29433
29329
  }
29434
- if (state.status === "incorrect") {
29330
+ if (userInput.status === "incorrect") {
29435
29331
  return {
29436
29332
  type: "points",
29437
29333
  earned: 0,
29438
29334
  total: 1,
29439
- message: state.message || null
29335
+ message: userInput.message || null
29440
29336
  };
29441
29337
  }
29442
29338
  return {
@@ -39327,8 +39223,6 @@ const styles$b = aphrodite.StyleSheet.create({
39327
39223
  * No diagonals allowed.
39328
39224
  */
39329
39225
 
39330
- // TODO: should this be using WidgetProps / PerseusLabelImageWidgetOptions?
39331
-
39332
39226
  class LabelImage extends React__namespace.Component {
39333
39227
  static contextType = PerseusI18nContext;
39334
39228
  // The rendered markers on the question image for labeling.
@@ -40822,9 +40716,9 @@ var Matcher$1 = {
40822
40716
  validator: matcherValidator
40823
40717
  };
40824
40718
 
40825
- function matrixValidator(state, rubric, strings) {
40719
+ function matrixValidator(userInput, rubric, strings) {
40826
40720
  const solution = rubric.answers;
40827
- const supplied = state.answers;
40721
+ const supplied = userInput.answers;
40828
40722
  const solutionSize = getMatrixSize(solution);
40829
40723
  const suppliedSize = getMatrixSize(supplied);
40830
40724
  const incorrectSize = solutionSize[0] !== suppliedSize[0] || solutionSize[1] !== suppliedSize[1];
@@ -40979,9 +40873,6 @@ class Matrix extends React__namespace.Component {
40979
40873
  });
40980
40874
  return inputPaths;
40981
40875
  };
40982
- getGrammarTypeForPath = inputPath => {
40983
- return "number";
40984
- };
40985
40876
  _handleFocus = (row, col) => {
40986
40877
  this.props.onFocus(getInputPath$1(row, col));
40987
40878
  };
@@ -41298,7 +41189,7 @@ const defaultImage = {
41298
41189
  class Measurer extends React__namespace.Component {
41299
41190
  static defaultProps = {
41300
41191
  box: [480, 480],
41301
- image: {},
41192
+ image: defaultImage,
41302
41193
  showProtractor: true,
41303
41194
  protractorX: 7.5,
41304
41195
  protractorY: 0.5,
@@ -42596,11 +42487,6 @@ class NumberLine extends React__namespace.Component {
42596
42487
  }
42597
42488
  return null;
42598
42489
  }
42599
- getGrammarTypeForPath(inputPath) {
42600
- if (inputPath?.length === 1 && inputPath[0] === "tick-ctrl") {
42601
- return "number";
42602
- }
42603
- }
42604
42490
  setInputValue = (inputPath, value, callback) => {
42605
42491
  if (inputPath.length === 1 && inputPath[0] === "tick-ctrl") {
42606
42492
  this.onNumDivisionsChange(value, callback);
@@ -44889,9 +44775,6 @@ class Table extends React__namespace.Component {
44889
44775
  });
44890
44776
  return inputPaths;
44891
44777
  };
44892
- getGrammarTypeForPath = inputPath => {
44893
- return "number";
44894
- };
44895
44778
  setInputValue = (path, newValue, cb) => {
44896
44779
  // Extract row, column information
44897
44780
  const row = getRowFromPath(path);
@@ -45185,7 +45068,7 @@ const init = function (options) {
45185
45068
 
45186
45069
  // This file is processed by a Rollup plugin (replace) to inject the production
45187
45070
  const libName = "@khanacademy/perseus";
45188
- const libVersion = "38.0.1";
45071
+ const libVersion = "39.0.1";
45189
45072
  perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
45190
45073
 
45191
45074
  const apiVersion = {
@@ -45896,9 +45779,6 @@ class ServerItemRenderer extends React__namespace.Component {
45896
45779
  getDOMNodeForPath(path) {
45897
45780
  return this.questionRenderer.getDOMNodeForPath(path);
45898
45781
  }
45899
- getGrammarTypeForPath(path) {
45900
- return this.questionRenderer.getGrammarTypeForPath(path);
45901
- }
45902
45782
  getInputPaths() {
45903
45783
  const questionAreaInputPaths = this.questionRenderer.getInputPaths();
45904
45784
  return questionAreaInputPaths;
@@ -47199,7 +47079,6 @@ const styles = aphrodite.StyleSheet.create({
47199
47079
  * If you're writing new components, though, consider using the Item and Shape
47200
47080
  * types instead.
47201
47081
  */
47202
-
47203
47082
  /**
47204
47083
  * Return a PropType that accepts Items of the given shape, and rejects other
47205
47084
  * objects.
@@ -47431,6 +47310,21 @@ function violatingWidgets(itemData) {
47431
47310
  }
47432
47311
 
47433
47312
  /* eslint-disable eqeqeq, @typescript-eslint/no-unused-vars */
47313
+ /*
47314
+ * A class for formatting number to significant digits.
47315
+ * Copyright (C) 2001 Stephen Ostermiller
47316
+ * http://ostermiller.org/contact.pl?regarding?JavaScript+Significant+Figures
47317
+ *
47318
+ * This program is free software; you can redistribute it and/or modify
47319
+ * it under the terms of the GNU General Public License as published by
47320
+ * the Free Software Foundation; either version 2 of the License, or
47321
+ * (at your option) any later version.
47322
+ *
47323
+ * This program is distributed in the hope that it will be useful,
47324
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
47325
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
47326
+ * GNU General Public License for more details.
47327
+ */
47434
47328
 
47435
47329
  /**
47436
47330
  * Format a floating point for display using the specified