@khanacademy/perseus 37.0.1 → 38.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.
package/dist/es/index.js CHANGED
@@ -9967,9 +9967,6 @@ const setLabelMargins = function setLabelMargins(span, size) {
9967
9967
  // Inherited line-height values can really mess up placement.
9968
9968
  $container.css("line-height", "normal");
9969
9969
 
9970
- // TODO: Verify that this change doesn't impact other graphies
9971
- // Check the full list of consuming widgets.
9972
-
9973
9970
  // If the change in line-height affected the height of the element,
9974
9971
  // then the height used for calculations should be updated.
9975
9972
  // This can happen when the first label in the container calls this method,
@@ -21679,7 +21676,7 @@ const answerTypes = {
21679
21676
  forms: "pi"
21680
21677
  }
21681
21678
  };
21682
- function inputNumberValidator(state, rubric, strings) {
21679
+ function inputNumberValidator(userInput, rubric, strings) {
21683
21680
  if (rubric.answerType == null) {
21684
21681
  rubric.answerType = "number";
21685
21682
  }
@@ -21697,7 +21694,7 @@ function inputNumberValidator(state, rubric, strings) {
21697
21694
 
21698
21695
  // We may have received TeX; try to parse it before grading.
21699
21696
  // If `currentValue` is not TeX, this should be a no-op.
21700
- const currentValue = ParseTex$1(state.currentValue);
21697
+ const currentValue = ParseTex$1(userInput.currentValue);
21701
21698
  const result = val(currentValue);
21702
21699
 
21703
21700
  // TODO(eater): Seems silly to translate result to this invalid/points
@@ -28275,7 +28272,7 @@ const Exponential = _.extend({}, PlotDefaults, {
28275
28272
  * coordinate, and `false` uses oldCoord as the resulting coordinate.
28276
28273
  */
28277
28274
  extraCoordConstraint: function (newCoord, oldCoord, coords, asymptote, graph) {
28278
- const y = _.head(asymptote)[1];
28275
+ const y = asymptote[0][1];
28279
28276
  return _.all(coords, coord => coord[1] !== y);
28280
28277
  },
28281
28278
  extraAsymptoteConstraint: function (newCoord, oldCoord, coords, asymptote, graph) {
@@ -28299,7 +28296,7 @@ const Exponential = _.extend({}, PlotDefaults, {
28299
28296
  getCoefficients: function (coords, asymptote) {
28300
28297
  const p1 = coords[0];
28301
28298
  const p2 = coords[1];
28302
- const c = _.head(asymptote)[1];
28299
+ const c = asymptote[0][1];
28303
28300
  const b = Math.log((p1[1] - c) / (p2[1] - c)) / (p1[0] - p2[0]);
28304
28301
  const a = (p1[1] - c) / Math.exp(b * p1[0]);
28305
28302
  return [a, b, c];
@@ -28326,7 +28323,7 @@ const Logarithm = _.extend({}, PlotDefaults, {
28326
28323
  defaultCoords: [[0.55, 0.5], [0.75, 0.75]],
28327
28324
  defaultAsymptote: [[0.5, 0], [0.5, 1.0]],
28328
28325
  extraCoordConstraint: function (newCoord, oldCoord, coords, asymptote, graph) {
28329
- const x = _.head(asymptote)[0];
28326
+ const x = asymptote[0][0];
28330
28327
  return _.all(coords, coord => coord[0] !== x) && coords[0][1] !== coords[1][1];
28331
28328
  },
28332
28329
  extraAsymptoteConstraint: function (newCoord, oldCoord, coords, asymptote, graph) {
@@ -28354,10 +28351,12 @@ const Logarithm = _.extend({}, PlotDefaults, {
28354
28351
  // logic between the two 'models'.
28355
28352
  const flip = coord => [coord[1], coord[0]];
28356
28353
  const inverseCoeffs = Exponential.getCoefficients(_.map(coords, flip), _.map(asymptote, flip));
28357
- const c = -inverseCoeffs[2] / inverseCoeffs[0];
28358
- const b = 1 / inverseCoeffs[0];
28359
- const a = 1 / inverseCoeffs[1];
28360
- return [a, b, c];
28354
+ if (inverseCoeffs) {
28355
+ const c = -inverseCoeffs[2] / inverseCoeffs[0];
28356
+ const b = 1 / inverseCoeffs[0];
28357
+ const a = 1 / inverseCoeffs[1];
28358
+ return [a, b, c];
28359
+ }
28361
28360
  },
28362
28361
  getFunctionForCoeffs: function (coeffs, x, asymptote) {
28363
28362
  const a = coeffs[0],
@@ -28421,11 +28420,9 @@ const functionTypeMapping = {
28421
28420
  absolute_value: AbsoluteValue
28422
28421
  };
28423
28422
  const allTypes = _.keys(functionTypeMapping);
28424
- function functionForType(
28425
- // TODO(jeremy): Actually `$Keys<typeof functionTypeMapping>` but that
28426
- // triggers TypeScript to require all of our Plot types to be fully typed which
28427
- // is a big amount of work/change.
28428
- type) {
28423
+ function functionForType(type) {
28424
+ // @ts-expect-error: TypeScript doesn't know how to use deal with generics
28425
+ // and conditional types in this way.
28429
28426
  return functionTypeMapping[type];
28430
28427
  }
28431
28428
  const getEquationString = props => {
@@ -28490,12 +28487,13 @@ const defaultPlotProps = (type, graph) => {
28490
28487
  // widget before even reading the question; you can't lose, but you
28491
28488
  // might get a free win.
28492
28489
  const model = functionForType(type);
28490
+ const defaultAsymptote = "defaultAsymptote" in model ? model.defaultAsymptote : null;
28493
28491
  const gridStep = [1, 1];
28494
28492
  // @ts-expect-error - TS2345 - Argument of type 'number[]' is not assignable to parameter of type '[number, number]'.
28495
28493
  const snapStep = Util.snapStepFromGridStep(gridStep);
28496
28494
  return {
28497
28495
  type,
28498
- asymptote: maybePointsFromNormalized(model.defaultAsymptote, graph.range, graph.step, snapStep),
28496
+ asymptote: maybePointsFromNormalized(defaultAsymptote, graph.range, graph.step, snapStep),
28499
28497
  coords: null
28500
28498
  };
28501
28499
  };
@@ -28555,8 +28553,19 @@ var util = /*#__PURE__*/Object.freeze({
28555
28553
  typeToButton: typeToButton
28556
28554
  });
28557
28555
 
28558
- function grapherValidator(state, rubric) {
28559
- if (state.type !== rubric.correct.type) {
28556
+ function getCoefficientsByType(data) {
28557
+ if (data.type === "exponential" || data.type === "logarithm") {
28558
+ const grader = functionForType(data.type);
28559
+ return grader.getCoefficients(data.coords, data.asymptote);
28560
+ } else if (data.type === "linear" || data.type === "quadratic" || data.type === "absolute_value" || data.type === "sinusoid" || data.type === "tangent") {
28561
+ const grader = functionForType(data.type);
28562
+ return grader.getCoefficients(data.coords);
28563
+ } else {
28564
+ throw new PerseusError("Invalid grapher type", Errors.InvalidInput);
28565
+ }
28566
+ }
28567
+ function grapherValidator(userInput, rubric) {
28568
+ if (userInput.type !== rubric.correct.type) {
28560
28569
  return {
28561
28570
  type: "points",
28562
28571
  earned: 0,
@@ -28566,7 +28575,7 @@ function grapherValidator(state, rubric) {
28566
28575
  }
28567
28576
 
28568
28577
  // We haven't moved the coords
28569
- if (state.coords == null) {
28578
+ if (userInput.coords == null) {
28570
28579
  return {
28571
28580
  type: "invalid",
28572
28581
  message: null
@@ -28574,11 +28583,9 @@ function grapherValidator(state, rubric) {
28574
28583
  }
28575
28584
 
28576
28585
  // Get new function handler for grading
28577
- const grader = functionForType(state.type);
28578
- const guessCoeffs = grader.getCoefficients(state.coords, state.asymptote);
28579
- const correctCoeffs = grader.getCoefficients(rubric.correct.coords,
28580
- // @ts-expect-error - TS(2339) - Property 'asymptote' does not exist on type '{ type: "absolute_value"; coords: [Coord, Coord]; }'
28581
- rubric.correct.asymptote);
28586
+ const grader = functionForType(userInput.type);
28587
+ const guessCoeffs = getCoefficientsByType(userInput);
28588
+ const correctCoeffs = getCoefficientsByType(rubric.correct);
28582
28589
  if (guessCoeffs == null || correctCoeffs == null) {
28583
28590
  return {
28584
28591
  type: "invalid",
@@ -38686,10 +38693,12 @@ function scoreMarker(marker) {
38686
38693
  }
38687
38694
  return score;
38688
38695
  }
38689
- function labelImageValidator(state, rubric) {
38696
+
38697
+ // TODO(LEMS-2440): May need to pull answers out of PerseusLabelImageWidgetOptions[markers] for the rubric
38698
+ function labelImageValidator(userInput, rubric) {
38690
38699
  let numAnswered = 0;
38691
38700
  let numCorrect = 0;
38692
- for (const marker of state.markers) {
38701
+ for (const marker of userInput.markers) {
38693
38702
  const score = scoreMarker(marker);
38694
38703
  if (score.hasAnswers) {
38695
38704
  numAnswered++;
@@ -38700,7 +38709,7 @@ function labelImageValidator(state, rubric) {
38700
38709
  }
38701
38710
 
38702
38711
  // We expect all question markers to be answered before grading.
38703
- if (numAnswered !== state.markers.length) {
38712
+ if (numAnswered !== userInput.markers.length) {
38704
38713
  return {
38705
38714
  type: "invalid",
38706
38715
  message: null
@@ -38710,7 +38719,7 @@ function labelImageValidator(state, rubric) {
38710
38719
  type: "points",
38711
38720
  // Markers with no expected answers are graded as correct if user
38712
38721
  // makes no answer selection.
38713
- earned: numCorrect === state.markers.length ? 1 : 0,
38722
+ earned: numCorrect === userInput.markers.length ? 1 : 0,
38714
38723
  total: 1,
38715
38724
  message: null
38716
38725
  };
@@ -39179,6 +39188,8 @@ class LabelImage extends React.Component {
39179
39188
  markers
39180
39189
  };
39181
39190
  }
39191
+
39192
+ // TODO(LEMS-2544): Investigate impact on scoring; possibly pull out &/or remove rubric parameter.
39182
39193
  showRationalesForCurrentlySelectedChoices(rubric) {
39183
39194
  const {
39184
39195
  markers
@@ -42060,17 +42071,23 @@ const TickMarks = Graphie.createSimpleClass((graphie, props) => {
42060
42071
  const results = [];
42061
42072
 
42062
42073
  // For convenience, extract some props into separate variables
42063
- const range = props.range;
42064
- const labelRange = props.labelRange;
42074
+ const {
42075
+ range,
42076
+ labelRange,
42077
+ labelStyle,
42078
+ labelTicks,
42079
+ tickStep,
42080
+ numDivisions
42081
+ } = props;
42065
42082
  const leftLabel = labelRange[0] == null ? range[0] : labelRange[0];
42066
42083
  const rightLabel = labelRange[1] == null ? range[1] : labelRange[1];
42067
42084
 
42068
42085
  // Find base via GCD for non-reduced fractions
42069
42086
  let base;
42070
- if (props.labelStyle === "non-reduced") {
42087
+ if (labelStyle === "non-reduced") {
42071
42088
  const fractions = [leftLabel, rightLabel];
42072
- for (let i = 0; i <= props.numDivisions; i++) {
42073
- const x = range[0] + i * props.tickStep;
42089
+ for (let i = 0; i <= numDivisions; i++) {
42090
+ const x = range[0] + i * tickStep;
42074
42091
  fractions.push(x);
42075
42092
  }
42076
42093
  const getDenom = x => number.toFraction(x)[1];
@@ -42079,30 +42096,42 @@ const TickMarks = Graphie.createSimpleClass((graphie, props) => {
42079
42096
  } else {
42080
42097
  base = undefined;
42081
42098
  }
42082
-
42083
- // Draw and save the tick marks and tick labels
42084
- for (let i = 0; i <= props.numDivisions; i++) {
42085
- const x = range[0] + i * props.tickStep;
42086
- results.push(graphie.line([x, -0.2], [x, 0.2]));
42087
- const labelTicks = props.labelTicks;
42088
- if (labelTicks || props.labelStyle === "decimal ticks") {
42089
- if (x === leftLabel || x === rightLabel) {
42090
- results.push(graphie.style({
42091
- color: KhanColors.BLUE
42092
- }, () => _label(graphie, props.labelStyle, x, x, base)));
42093
- } else {
42094
- results.push(_label(graphie, props.labelStyle, x, x, base));
42095
- }
42096
- }
42097
- }
42098
-
42099
- // Render the labels' lines
42100
- graphie.style({
42099
+ const highlightedLineStyle = {
42101
42100
  stroke: KhanColors.BLUE,
42102
42101
  strokeWidth: 3.5
42103
- }, () => {
42104
- results.push(graphie.line([leftLabel, -0.2], [leftLabel, 0.2]));
42105
- results.push(graphie.line([rightLabel, -0.2], [rightLabel, 0.2]));
42102
+ };
42103
+ const highlightedTextStyle = {
42104
+ color: KhanColors.BLUE
42105
+ };
42106
+
42107
+ // Generate an array of tick numbers:
42108
+ // `Array(Math.round(numDivisions))` makes an array of null values - one for every division marker
42109
+ // `.keys()` gets the index values for each marker placeholder
42110
+ // `.map()` converts the index values into actual tick numbers
42111
+ // NOTE: 'numDivisions' can sometimes be a non-integer (i.e. 7.000001).
42112
+ // Using Math.round() to ensure that an integer is used in the Array setup.
42113
+ const initialTicks = [...Array(Math.round(numDivisions)).keys()].map(index => range[0] + index * tickStep);
42114
+
42115
+ // .sort() comparator
42116
+ const byNumericAscending = (a, b) => a - b;
42117
+
42118
+ // Ensure that any label markers and range endpoints are included in the array
42119
+ // Using `Set()` prevents duplication of tick numbers (and is quite performant)
42120
+ const allTicks = [...new Set([...initialTicks, leftLabel, rightLabel, ...range])].sort(byNumericAscending);
42121
+
42122
+ // Cycle through each tick number and add a tick line, and a label (if needed)
42123
+ allTicks.forEach(tick => {
42124
+ const tickIsHighlighted = tick === leftLabel || tick === rightLabel;
42125
+ const lineStyle = tickIsHighlighted ? highlightedLineStyle : null;
42126
+ const textStyle = tickIsHighlighted ? highlightedTextStyle : null;
42127
+ graphie.style(lineStyle, () => {
42128
+ results.push(graphie.line([tick, -0.2], [tick, 0.2]));
42129
+ });
42130
+ if (labelTicks || tickIsHighlighted || labelStyle === "decimal ticks") {
42131
+ graphie.style(textStyle, () => {
42132
+ results.push(_label(graphie, labelStyle, tick, tick, base));
42133
+ });
42134
+ }
42106
42135
  });
42107
42136
  return results;
42108
42137
  });
@@ -42781,7 +42810,7 @@ class Orderer extends React.Component {
42781
42810
  list.splice(index, 0, newCard);
42782
42811
  }
42783
42812
  this.props.onChange({
42784
- // @ts-expect-error - TS2345 - Argument of type '{ current: any[]; }' is not assignable to parameter of type '{ hints?: readonly Hint[] | undefined; replace?: boolean | undefined; content?: string | undefined; widgets?: WidgetDict | undefined; images?: ImageDict | undefined; ... 13 more ...; plot?: any; }'.
42813
+ // @ts-expect-error - TS2345 - Argument of type '{ current: any[]; }' is not assignable to parameter of type '{ hints?: readonly Hint[] | undefined; replace?: boolean | undefined; content?: string | undefined; widgets?: PerseusWidgetsMap | undefined; images?: ImageDict | undefined; ... 13 more ...; plot?: any; }'.
42785
42814
  current: list
42786
42815
  });
42787
42816
  this.setState({
@@ -42943,7 +42972,7 @@ class Orderer extends React.Component {
42943
42972
  };
42944
42973
  });
42945
42974
  this.props.onChange({
42946
- // @ts-expect-error - TS2345 - Argument of type '{ current: { content: string; }[]; }' is not assignable to parameter of type '{ hints?: readonly Hint[] | undefined; replace?: boolean | undefined; content?: string | undefined; widgets?: WidgetDict | undefined; images?: ImageDict | undefined; ... 13 more ...; plot?: any; }'.
42975
+ // @ts-expect-error - TS2345 - Argument of type '{ current: { content: string; }[]; }' is not assignable to parameter of type '{ hints?: readonly Hint[] | undefined; replace?: boolean | undefined; content?: string | undefined; widgets?: PerseusWidgetsMap | undefined; images?: ImageDict | undefined; ... 13 more ...; plot?: any; }'.
42947
42976
  current: list
42948
42977
  });
42949
42978
  this.setState({
@@ -44804,7 +44833,7 @@ const init = function init(options) {
44804
44833
 
44805
44834
  // This file is processed by a Rollup plugin (replace) to inject the production
44806
44835
  const libName = "@khanacademy/perseus";
44807
- const libVersion = "37.0.1";
44836
+ const libVersion = "38.0.1";
44808
44837
  addLibraryVersionToPerseusDebug(libName, libVersion);
44809
44838
 
44810
44839
  const apiVersion = {