@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/index.d.ts CHANGED
@@ -82,7 +82,7 @@ export { default as WIDGET_PROP_DENYLIST } from "./mixins/widget-prop-denylist";
82
82
  export type { PerseusOptions } from "./init";
83
83
  export type { ILogger, LogErrorOptions } from "./logging/log";
84
84
  export type { ServerItemRenderer as ServerItemRendererComponent } from "./server-item-renderer";
85
- export type { Alignment, APIOptions, APIOptionsWithDefaults, ChangeHandler, DeviceType, DomInsertCheckFn, EditorMode, FocusPath, ImageDict, ImageUploader, JiptLabelStore, JiptRenderer, PerseusDependencies, PerseusScore, Version, VideoData, VideoKind, WidgetDict, WidgetExports, SharedRendererProps, } from "./types";
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
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?
88
88
  PerseusImageBackground, PerseusInputNumberWidgetOptions, PerseusInteractiveGraphWidgetOptions, PerseusItem, PerseusPhetSimulationWidgetOptions, PerseusPlotterWidgetOptions, PerseusPythonProgramWidgetOptions, PerseusRadioWidgetOptions, PerseusExampleWidgetOptions, PerseusSimpleMarkdownTesterWidgetOptions, PerseusRenderer, PerseusWidget, PerseusWidgetsMap, MultiItem, } from "./perseus-types";
package/dist/index.js CHANGED
@@ -10084,9 +10084,6 @@ const setLabelMargins = function (span, size) {
10084
10084
  // Inherited line-height values can really mess up placement.
10085
10085
  $container.css("line-height", "normal");
10086
10086
 
10087
- // TODO: Verify that this change doesn't impact other graphies
10088
- // Check the full list of consuming widgets.
10089
-
10090
10087
  // If the change in line-height affected the height of the element,
10091
10088
  // then the height used for calculations should be updated.
10092
10089
  // This can happen when the first label in the container calls this method,
@@ -21903,7 +21900,7 @@ const answerTypes = {
21903
21900
  forms: "pi"
21904
21901
  }
21905
21902
  };
21906
- function inputNumberValidator(state, rubric, strings) {
21903
+ function inputNumberValidator(userInput, rubric, strings) {
21907
21904
  if (rubric.answerType == null) {
21908
21905
  rubric.answerType = "number";
21909
21906
  }
@@ -21921,7 +21918,7 @@ function inputNumberValidator(state, rubric, strings) {
21921
21918
 
21922
21919
  // We may have received TeX; try to parse it before grading.
21923
21920
  // If `currentValue` is not TeX, this should be a no-op.
21924
- const currentValue = ParseTex$1(state.currentValue);
21921
+ const currentValue = ParseTex$1(userInput.currentValue);
21925
21922
  const result = val(currentValue);
21926
21923
 
21927
21924
  // TODO(eater): Seems silly to translate result to this invalid/points
@@ -28539,7 +28536,7 @@ const Exponential = ___default["default"].extend({}, PlotDefaults, {
28539
28536
  * coordinate, and `false` uses oldCoord as the resulting coordinate.
28540
28537
  */
28541
28538
  extraCoordConstraint: function (newCoord, oldCoord, coords, asymptote, graph) {
28542
- const y = ___default["default"].head(asymptote)[1];
28539
+ const y = asymptote[0][1];
28543
28540
  return ___default["default"].all(coords, coord => coord[1] !== y);
28544
28541
  },
28545
28542
  extraAsymptoteConstraint: function (newCoord, oldCoord, coords, asymptote, graph) {
@@ -28563,7 +28560,7 @@ const Exponential = ___default["default"].extend({}, PlotDefaults, {
28563
28560
  getCoefficients: function (coords, asymptote) {
28564
28561
  const p1 = coords[0];
28565
28562
  const p2 = coords[1];
28566
- const c = ___default["default"].head(asymptote)[1];
28563
+ const c = asymptote[0][1];
28567
28564
  const b = Math.log((p1[1] - c) / (p2[1] - c)) / (p1[0] - p2[0]);
28568
28565
  const a = (p1[1] - c) / Math.exp(b * p1[0]);
28569
28566
  return [a, b, c];
@@ -28590,7 +28587,7 @@ const Logarithm = ___default["default"].extend({}, PlotDefaults, {
28590
28587
  defaultCoords: [[0.55, 0.5], [0.75, 0.75]],
28591
28588
  defaultAsymptote: [[0.5, 0], [0.5, 1.0]],
28592
28589
  extraCoordConstraint: function (newCoord, oldCoord, coords, asymptote, graph) {
28593
- const x = ___default["default"].head(asymptote)[0];
28590
+ const x = asymptote[0][0];
28594
28591
  return ___default["default"].all(coords, coord => coord[0] !== x) && coords[0][1] !== coords[1][1];
28595
28592
  },
28596
28593
  extraAsymptoteConstraint: function (newCoord, oldCoord, coords, asymptote, graph) {
@@ -28618,10 +28615,12 @@ const Logarithm = ___default["default"].extend({}, PlotDefaults, {
28618
28615
  // logic between the two 'models'.
28619
28616
  const flip = coord => [coord[1], coord[0]];
28620
28617
  const inverseCoeffs = Exponential.getCoefficients(___default["default"].map(coords, flip), ___default["default"].map(asymptote, flip));
28621
- const c = -inverseCoeffs[2] / inverseCoeffs[0];
28622
- const b = 1 / inverseCoeffs[0];
28623
- const a = 1 / inverseCoeffs[1];
28624
- return [a, b, c];
28618
+ if (inverseCoeffs) {
28619
+ const c = -inverseCoeffs[2] / inverseCoeffs[0];
28620
+ const b = 1 / inverseCoeffs[0];
28621
+ const a = 1 / inverseCoeffs[1];
28622
+ return [a, b, c];
28623
+ }
28625
28624
  },
28626
28625
  getFunctionForCoeffs: function (coeffs, x, asymptote) {
28627
28626
  const a = coeffs[0],
@@ -28685,11 +28684,9 @@ const functionTypeMapping = {
28685
28684
  absolute_value: AbsoluteValue
28686
28685
  };
28687
28686
  const allTypes = ___default["default"].keys(functionTypeMapping);
28688
- function functionForType(
28689
- // TODO(jeremy): Actually `$Keys<typeof functionTypeMapping>` but that
28690
- // triggers TypeScript to require all of our Plot types to be fully typed which
28691
- // is a big amount of work/change.
28692
- type) {
28687
+ function functionForType(type) {
28688
+ // @ts-expect-error: TypeScript doesn't know how to use deal with generics
28689
+ // and conditional types in this way.
28693
28690
  return functionTypeMapping[type];
28694
28691
  }
28695
28692
  const getEquationString = props => {
@@ -28754,12 +28751,13 @@ const defaultPlotProps = (type, graph) => {
28754
28751
  // widget before even reading the question; you can't lose, but you
28755
28752
  // might get a free win.
28756
28753
  const model = functionForType(type);
28754
+ const defaultAsymptote = "defaultAsymptote" in model ? model.defaultAsymptote : null;
28757
28755
  const gridStep = [1, 1];
28758
28756
  // @ts-expect-error - TS2345 - Argument of type 'number[]' is not assignable to parameter of type '[number, number]'.
28759
28757
  const snapStep = Util.snapStepFromGridStep(gridStep);
28760
28758
  return {
28761
28759
  type,
28762
- asymptote: maybePointsFromNormalized(model.defaultAsymptote, graph.range, graph.step, snapStep),
28760
+ asymptote: maybePointsFromNormalized(defaultAsymptote, graph.range, graph.step, snapStep),
28763
28761
  coords: null
28764
28762
  };
28765
28763
  };
@@ -28819,8 +28817,19 @@ var util = /*#__PURE__*/Object.freeze({
28819
28817
  typeToButton: typeToButton
28820
28818
  });
28821
28819
 
28822
- function grapherValidator(state, rubric) {
28823
- if (state.type !== rubric.correct.type) {
28820
+ function getCoefficientsByType(data) {
28821
+ if (data.type === "exponential" || data.type === "logarithm") {
28822
+ const grader = functionForType(data.type);
28823
+ return grader.getCoefficients(data.coords, data.asymptote);
28824
+ } else if (data.type === "linear" || data.type === "quadratic" || data.type === "absolute_value" || data.type === "sinusoid" || data.type === "tangent") {
28825
+ const grader = functionForType(data.type);
28826
+ return grader.getCoefficients(data.coords);
28827
+ } else {
28828
+ throw new perseusCore.PerseusError("Invalid grapher type", perseusCore.Errors.InvalidInput);
28829
+ }
28830
+ }
28831
+ function grapherValidator(userInput, rubric) {
28832
+ if (userInput.type !== rubric.correct.type) {
28824
28833
  return {
28825
28834
  type: "points",
28826
28835
  earned: 0,
@@ -28830,7 +28839,7 @@ function grapherValidator(state, rubric) {
28830
28839
  }
28831
28840
 
28832
28841
  // We haven't moved the coords
28833
- if (state.coords == null) {
28842
+ if (userInput.coords == null) {
28834
28843
  return {
28835
28844
  type: "invalid",
28836
28845
  message: null
@@ -28838,11 +28847,9 @@ function grapherValidator(state, rubric) {
28838
28847
  }
28839
28848
 
28840
28849
  // Get new function handler for grading
28841
- const grader = functionForType(state.type);
28842
- const guessCoeffs = grader.getCoefficients(state.coords, state.asymptote);
28843
- const correctCoeffs = grader.getCoefficients(rubric.correct.coords,
28844
- // @ts-expect-error - TS(2339) - Property 'asymptote' does not exist on type '{ type: "absolute_value"; coords: [Coord, Coord]; }'
28845
- rubric.correct.asymptote);
28850
+ const grader = functionForType(userInput.type);
28851
+ const guessCoeffs = getCoefficientsByType(userInput);
28852
+ const correctCoeffs = getCoefficientsByType(rubric.correct);
28846
28853
  if (guessCoeffs == null || correctCoeffs == null) {
28847
28854
  return {
28848
28855
  type: "invalid",
@@ -39028,10 +39035,12 @@ function scoreMarker(marker) {
39028
39035
  }
39029
39036
  return score;
39030
39037
  }
39031
- function labelImageValidator(state, rubric) {
39038
+
39039
+ // TODO(LEMS-2440): May need to pull answers out of PerseusLabelImageWidgetOptions[markers] for the rubric
39040
+ function labelImageValidator(userInput, rubric) {
39032
39041
  let numAnswered = 0;
39033
39042
  let numCorrect = 0;
39034
- for (const marker of state.markers) {
39043
+ for (const marker of userInput.markers) {
39035
39044
  const score = scoreMarker(marker);
39036
39045
  if (score.hasAnswers) {
39037
39046
  numAnswered++;
@@ -39042,7 +39051,7 @@ function labelImageValidator(state, rubric) {
39042
39051
  }
39043
39052
 
39044
39053
  // We expect all question markers to be answered before grading.
39045
- if (numAnswered !== state.markers.length) {
39054
+ if (numAnswered !== userInput.markers.length) {
39046
39055
  return {
39047
39056
  type: "invalid",
39048
39057
  message: null
@@ -39052,7 +39061,7 @@ function labelImageValidator(state, rubric) {
39052
39061
  type: "points",
39053
39062
  // Markers with no expected answers are graded as correct if user
39054
39063
  // makes no answer selection.
39055
- earned: numCorrect === state.markers.length ? 1 : 0,
39064
+ earned: numCorrect === userInput.markers.length ? 1 : 0,
39056
39065
  total: 1,
39057
39066
  message: null
39058
39067
  };
@@ -39525,6 +39534,8 @@ class LabelImage extends React__namespace.Component {
39525
39534
  markers
39526
39535
  };
39527
39536
  }
39537
+
39538
+ // TODO(LEMS-2544): Investigate impact on scoring; possibly pull out &/or remove rubric parameter.
39528
39539
  showRationalesForCurrentlySelectedChoices(rubric) {
39529
39540
  const {
39530
39541
  markers
@@ -42409,17 +42420,23 @@ const TickMarks = Graphie.createSimpleClass((graphie, props) => {
42409
42420
  const results = [];
42410
42421
 
42411
42422
  // For convenience, extract some props into separate variables
42412
- const range = props.range;
42413
- const labelRange = props.labelRange;
42423
+ const {
42424
+ range,
42425
+ labelRange,
42426
+ labelStyle,
42427
+ labelTicks,
42428
+ tickStep,
42429
+ numDivisions
42430
+ } = props;
42414
42431
  const leftLabel = labelRange[0] == null ? range[0] : labelRange[0];
42415
42432
  const rightLabel = labelRange[1] == null ? range[1] : labelRange[1];
42416
42433
 
42417
42434
  // Find base via GCD for non-reduced fractions
42418
42435
  let base;
42419
- if (props.labelStyle === "non-reduced") {
42436
+ if (labelStyle === "non-reduced") {
42420
42437
  const fractions = [leftLabel, rightLabel];
42421
- for (let i = 0; i <= props.numDivisions; i++) {
42422
- const x = range[0] + i * props.tickStep;
42438
+ for (let i = 0; i <= numDivisions; i++) {
42439
+ const x = range[0] + i * tickStep;
42423
42440
  fractions.push(x);
42424
42441
  }
42425
42442
  const getDenom = x => kmath.number.toFraction(x)[1];
@@ -42428,30 +42445,42 @@ const TickMarks = Graphie.createSimpleClass((graphie, props) => {
42428
42445
  } else {
42429
42446
  base = undefined;
42430
42447
  }
42431
-
42432
- // Draw and save the tick marks and tick labels
42433
- for (let i = 0; i <= props.numDivisions; i++) {
42434
- const x = range[0] + i * props.tickStep;
42435
- results.push(graphie.line([x, -0.2], [x, 0.2]));
42436
- const labelTicks = props.labelTicks;
42437
- if (labelTicks || props.labelStyle === "decimal ticks") {
42438
- if (x === leftLabel || x === rightLabel) {
42439
- results.push(graphie.style({
42440
- color: KhanColors.BLUE
42441
- }, () => _label(graphie, props.labelStyle, x, x, base)));
42442
- } else {
42443
- results.push(_label(graphie, props.labelStyle, x, x, base));
42444
- }
42445
- }
42446
- }
42447
-
42448
- // Render the labels' lines
42449
- graphie.style({
42448
+ const highlightedLineStyle = {
42450
42449
  stroke: KhanColors.BLUE,
42451
42450
  strokeWidth: 3.5
42452
- }, () => {
42453
- results.push(graphie.line([leftLabel, -0.2], [leftLabel, 0.2]));
42454
- results.push(graphie.line([rightLabel, -0.2], [rightLabel, 0.2]));
42451
+ };
42452
+ const highlightedTextStyle = {
42453
+ color: KhanColors.BLUE
42454
+ };
42455
+
42456
+ // Generate an array of tick numbers:
42457
+ // `Array(Math.round(numDivisions))` makes an array of null values - one for every division marker
42458
+ // `.keys()` gets the index values for each marker placeholder
42459
+ // `.map()` converts the index values into actual tick numbers
42460
+ // NOTE: 'numDivisions' can sometimes be a non-integer (i.e. 7.000001).
42461
+ // Using Math.round() to ensure that an integer is used in the Array setup.
42462
+ const initialTicks = [...Array(Math.round(numDivisions)).keys()].map(index => range[0] + index * tickStep);
42463
+
42464
+ // .sort() comparator
42465
+ const byNumericAscending = (a, b) => a - b;
42466
+
42467
+ // Ensure that any label markers and range endpoints are included in the array
42468
+ // Using `Set()` prevents duplication of tick numbers (and is quite performant)
42469
+ const allTicks = [...new Set([...initialTicks, leftLabel, rightLabel, ...range])].sort(byNumericAscending);
42470
+
42471
+ // Cycle through each tick number and add a tick line, and a label (if needed)
42472
+ allTicks.forEach(tick => {
42473
+ const tickIsHighlighted = tick === leftLabel || tick === rightLabel;
42474
+ const lineStyle = tickIsHighlighted ? highlightedLineStyle : null;
42475
+ const textStyle = tickIsHighlighted ? highlightedTextStyle : null;
42476
+ graphie.style(lineStyle, () => {
42477
+ results.push(graphie.line([tick, -0.2], [tick, 0.2]));
42478
+ });
42479
+ if (labelTicks || tickIsHighlighted || labelStyle === "decimal ticks") {
42480
+ graphie.style(textStyle, () => {
42481
+ results.push(_label(graphie, labelStyle, tick, tick, base));
42482
+ });
42483
+ }
42455
42484
  });
42456
42485
  return results;
42457
42486
  });
@@ -43145,7 +43174,7 @@ class Orderer extends React__namespace.Component {
43145
43174
  list.splice(index, 0, newCard);
43146
43175
  }
43147
43176
  this.props.onChange({
43148
- // @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; }'.
43177
+ // @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; }'.
43149
43178
  current: list
43150
43179
  });
43151
43180
  this.setState({
@@ -43308,7 +43337,7 @@ class Orderer extends React__namespace.Component {
43308
43337
  };
43309
43338
  });
43310
43339
  this.props.onChange({
43311
- // @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; }'.
43340
+ // @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; }'.
43312
43341
  current: list
43313
43342
  });
43314
43343
  this.setState({
@@ -45156,7 +45185,7 @@ const init = function (options) {
45156
45185
 
45157
45186
  // This file is processed by a Rollup plugin (replace) to inject the production
45158
45187
  const libName = "@khanacademy/perseus";
45159
- const libVersion = "37.0.1";
45188
+ const libVersion = "38.0.1";
45160
45189
  perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
45161
45190
 
45162
45191
  const apiVersion = {