@khanacademy/perseus 37.0.1 → 38.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.
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,
@@ -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",
@@ -42409,17 +42416,23 @@ const TickMarks = Graphie.createSimpleClass((graphie, props) => {
42409
42416
  const results = [];
42410
42417
 
42411
42418
  // For convenience, extract some props into separate variables
42412
- const range = props.range;
42413
- const labelRange = props.labelRange;
42419
+ const {
42420
+ range,
42421
+ labelRange,
42422
+ labelStyle,
42423
+ labelTicks,
42424
+ tickStep,
42425
+ numDivisions
42426
+ } = props;
42414
42427
  const leftLabel = labelRange[0] == null ? range[0] : labelRange[0];
42415
42428
  const rightLabel = labelRange[1] == null ? range[1] : labelRange[1];
42416
42429
 
42417
42430
  // Find base via GCD for non-reduced fractions
42418
42431
  let base;
42419
- if (props.labelStyle === "non-reduced") {
42432
+ if (labelStyle === "non-reduced") {
42420
42433
  const fractions = [leftLabel, rightLabel];
42421
- for (let i = 0; i <= props.numDivisions; i++) {
42422
- const x = range[0] + i * props.tickStep;
42434
+ for (let i = 0; i <= numDivisions; i++) {
42435
+ const x = range[0] + i * tickStep;
42423
42436
  fractions.push(x);
42424
42437
  }
42425
42438
  const getDenom = x => kmath.number.toFraction(x)[1];
@@ -42428,30 +42441,40 @@ const TickMarks = Graphie.createSimpleClass((graphie, props) => {
42428
42441
  } else {
42429
42442
  base = undefined;
42430
42443
  }
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({
42444
+ const highlightedLineStyle = {
42450
42445
  stroke: KhanColors.BLUE,
42451
42446
  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]));
42447
+ };
42448
+ const highlightedTextStyle = {
42449
+ color: KhanColors.BLUE
42450
+ };
42451
+
42452
+ // Generate an array of tick numbers:
42453
+ // `Array(props.numDivisions)` makes an array of null values - one for every division marker
42454
+ // `.keys()` gets the index values for each marker placeholder
42455
+ // `.map()` converts the index values into actual tick numbers
42456
+ const initialTicks = [...Array(numDivisions).keys()].map(index => range[0] + index * tickStep);
42457
+
42458
+ // .sort() comparator
42459
+ const byNumericAscending = (a, b) => a - b;
42460
+
42461
+ // Ensure that any label markers and range endpoints are included in the array
42462
+ // Using `Set()` prevents duplication of tick numbers (and is quite performant)
42463
+ const allTicks = [...new Set([...initialTicks, leftLabel, rightLabel, ...range])].sort(byNumericAscending);
42464
+
42465
+ // Cycle through each tick number and add a tick line, and a label (if needed)
42466
+ allTicks.forEach(tick => {
42467
+ const tickIsHighlighted = tick === leftLabel || tick === rightLabel;
42468
+ const lineStyle = tickIsHighlighted ? highlightedLineStyle : null;
42469
+ const textStyle = tickIsHighlighted ? highlightedTextStyle : null;
42470
+ graphie.style(lineStyle, () => {
42471
+ results.push(graphie.line([tick, -0.2], [tick, 0.2]));
42472
+ });
42473
+ if (labelTicks || tickIsHighlighted || labelStyle === "decimal ticks") {
42474
+ graphie.style(textStyle, () => {
42475
+ results.push(_label(graphie, labelStyle, tick, tick, base));
42476
+ });
42477
+ }
42455
42478
  });
42456
42479
  return results;
42457
42480
  });
@@ -43145,7 +43168,7 @@ class Orderer extends React__namespace.Component {
43145
43168
  list.splice(index, 0, newCard);
43146
43169
  }
43147
43170
  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; }'.
43171
+ // @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
43172
  current: list
43150
43173
  });
43151
43174
  this.setState({
@@ -43308,7 +43331,7 @@ class Orderer extends React__namespace.Component {
43308
43331
  };
43309
43332
  });
43310
43333
  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; }'.
43334
+ // @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
43335
  current: list
43313
43336
  });
43314
43337
  this.setState({
@@ -45156,7 +45179,7 @@ const init = function (options) {
45156
45179
 
45157
45180
  // This file is processed by a Rollup plugin (replace) to inject the production
45158
45181
  const libName = "@khanacademy/perseus";
45159
- const libVersion = "37.0.1";
45182
+ const libVersion = "38.0.0";
45160
45183
  perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
45161
45184
 
45162
45185
  const apiVersion = {