@khanacademy/perseus 22.7.0 → 23.1.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.css CHANGED
@@ -4177,6 +4177,7 @@ html body .mq-editable-field .mq-selection .mq-nthroot-container * {
4177
4177
  --mafs-origin-color: var(--mafs-fg);
4178
4178
  --mafs-line-color: #555;
4179
4179
  --mafs-line-stroke-dash-style: 4, 3;
4180
+ --mafs-axis-stroke-width: 1px;
4180
4181
  --grid-line-subdivision-color: #222;
4181
4182
 
4182
4183
  --mafs-red: #f11d0e;
@@ -4488,7 +4489,7 @@ html body .mq-editable-field .mq-selection .mq-nthroot-container * {
4488
4489
  }
4489
4490
 
4490
4491
  .MafsView pattern g {
4491
- stroke: #909296;
4492
+ stroke: rgba(33, 36, 44, 0.32);
4492
4493
  }
4493
4494
  .axis-tick-labels {
4494
4495
  font-size: 14px;
@@ -4524,19 +4525,6 @@ html body .mq-editable-field .mq-selection .mq-nthroot-container * {
4524
4525
  transform: translateY(calc(-100% + 0.25em));
4525
4526
  }
4526
4527
 
4527
- .x-axis-tick-labels span,
4528
- .y-axis-tick-labels span {
4529
- /* The negative sign is rendered as a pseudo-element to
4530
- ensure that we are positioning all labels correctly */
4531
- &::before {
4532
- content: attr(data-content);
4533
- position: absolute;
4534
- /* We're translating the symbol up by 1px to ensure it's legible around gridlines. */
4535
- transform: translate(-100%, -1px);
4536
- font-weight: 600; /* This helps get the symbol to render crisply */
4537
- }
4538
- }
4539
-
4540
4528
  .y-axis-tick-labels span {
4541
4529
  display: inline-block;
4542
4530
  height: var(--y-axis-label-height, 20px);
package/dist/index.d.ts CHANGED
@@ -87,7 +87,7 @@ export type { ILogger, LogErrorOptions, ErrorKind } from "./logging/log";
87
87
  export type { ServerItemRenderer as ServerItemRendererComponent } from "./server-item-renderer";
88
88
  export type { Alignment, APIOptions, APIOptionsWithDefaults, ChangeHandler, DeviceType, DomInsertCheckFn, EditorMode, FocusPath, Hint, ImageDict, ImageUploader, JiptLabelStore, JiptRenderer, PerseusDependencies, PerseusScore, Version, VideoData, VideoKind, WidgetDict, WidgetExports, } from "./types";
89
89
  export type { ParsedValue } from "./util";
90
- export type { LockedFigure, LockedFigureColor, LockedFigureType, LockedPointType, LockedLineType, PerseusGraphType, PerseusAnswerArea, PerseusExpressionWidgetOptions, Range, Size, MathFormat, InputNumberWidget, // TODO(jeremy): remove?
90
+ export type { LockedFigure, LockedFigureColor, LockedFigureType, LockedPointType, LockedLineType, LockedCircleType, PerseusGraphType, PerseusAnswerArea, PerseusExpressionWidgetOptions, Range, Size, MathFormat, InputNumberWidget, // TODO(jeremy): remove?
91
91
  PerseusImageBackground, PerseusInputNumberWidgetOptions, PerseusInteractiveGraphWidgetOptions, PerseusItem, PerseusPlotterWidgetOptions, PerseusPythonProgramWidgetOptions, PerseusRadioWidgetOptions, PerseusExampleWidgetOptions, PerseusExampleGraphieWidgetOptions, PerseusSimpleMarkdownTesterWidgetOptions, PerseusRenderer, PerseusWidget, PerseusWidgetsMap, } from "./perseus-types";
92
92
  export type { Coord } from "./interactive2/types";
93
93
  export type { MarkerType } from "./widgets/label-image/types";
package/dist/index.js CHANGED
@@ -1245,8 +1245,6 @@ const ApiOptions = {
1245
1245
  // drawing area (scratchpad) should be allowed/disallowed.
1246
1246
  // Previously handled by `Khan.scratchpad.enable/disable`
1247
1247
  setDrawingAreaAvailable: PropTypes__default["default"].func,
1248
- // Whether to use the Draft.js editor or the legacy textarea
1249
- useDraftEditor: PropTypes__default["default"].bool,
1250
1248
  // The color used for the hint progress indicator (eg. 1 / 3)
1251
1249
  hintProgressColor: PropTypes__default["default"].string,
1252
1250
  // Whether this Renderer is allowed to auto-scroll the rest of the
@@ -1256,8 +1254,6 @@ const ApiOptions = {
1256
1254
  //
1257
1255
  // Defaults to `false`.
1258
1256
  canScrollPage: PropTypes__default["default"].bool,
1259
- // Whether or not we are rendering content inside of a modal.
1260
- inModal: PropTypes__default["default"].bool,
1261
1257
  // Whether to enable the cross-out feature on multiple-choice radio
1262
1258
  // widgets. This allows users to note which answers they believe to
1263
1259
  // be incorrect, to find the answer by process of elimination.
@@ -1293,9 +1289,7 @@ const ApiOptions = {
1293
1289
  }
1294
1290
  },
1295
1291
  setDrawingAreaAvailable: function () {},
1296
- useDraftEditor: false,
1297
1292
  canScrollPage: false,
1298
- inModal: false,
1299
1293
  crossOutEnabled: false,
1300
1294
  editorChangeDelay: 0
1301
1295
  }
@@ -17604,16 +17598,8 @@ class SvgImage extends React__namespace.Component {
17604
17598
  // Props to send to all images
17605
17599
  const imageProps = {
17606
17600
  alt: this.props.alt,
17607
- title: this.props.title,
17608
- tabIndex: "0"
17601
+ title: this.props.title
17609
17602
  };
17610
- // If alt text is present, AND overrideAriaHidden is true,
17611
- // include aria-hidden to avoid screen reader from crawling texts
17612
- // both from the div meant for screen readers (see svg-image.jsx)
17613
- // and alt attribute.
17614
- if (this.props.alt && this.props.overrideAriaHidden) {
17615
- imageProps["aria-hidden"] = true;
17616
- }
17617
17603
  const width = this.props.width && this.props.width * this.props.scale;
17618
17604
  const height = this.props.height && this.props.height * this.props.scale;
17619
17605
  const dimensions = {
@@ -25197,8 +25183,6 @@ var CSProgram$1 = {
25197
25183
  hidden: true
25198
25184
  };
25199
25185
 
25200
- // TODO(FEI-5054): Figure out how to get global .d.ts files working with monorepos
25201
-
25202
25186
  class Definition extends React__namespace.Component {
25203
25187
  static contextType = PerseusI18nContext;
25204
25188
  static defaultProps = {
@@ -25410,8 +25394,6 @@ var Dropdown$1 = {
25410
25394
 
25411
25395
  /* eslint-disable react/sort-comp */
25412
25396
 
25413
- // TODO(FEI-5054): Figure out how to get global .d.ts files working with monorepos
25414
-
25415
25397
  // transform = _.identity
25416
25398
 
25417
25399
  function mediaQueryIsMatched(mediaQuery) {
@@ -27659,7 +27641,7 @@ class ImageWidget extends React__namespace.Component {
27659
27641
 
27660
27642
  render() {
27661
27643
  let image;
27662
- let alt;
27644
+ const alt = this.props.caption === this.props.alt ? "" : this.props.alt;
27663
27645
  const {
27664
27646
  apiOptions
27665
27647
  } = this.props;
@@ -27672,29 +27654,7 @@ class ImageWidget extends React__namespace.Component {
27672
27654
  } = _ref;
27673
27655
  return /*#__PURE__*/React__namespace.createElement(SvgImage, {
27674
27656
  src: url,
27675
- alt:
27676
- /* alt text is formatted in a sr-only
27677
- div next to the image in addition to
27678
- the alt attribute.
27679
- If there is no alt text at all,
27680
- we don't put an alt attribute on
27681
- the image, so that screen readers
27682
- know there's something they can't
27683
- read there :(.
27684
- NOTE: React <=0.13 (maybe later)
27685
- has a bug where it won't ever
27686
- remove an attribute, so if this
27687
- alt node is ever defined it's
27688
- not removed. This is sort of
27689
- dangerous, but we usually re-key
27690
- new renderers so that they're
27691
- rendered from scratch anyways,
27692
- so this shouldn't be a problem
27693
- in practice right now, although
27694
- it will exhibit weird behaviour
27695
- while editing. */
27696
- this.props.alt,
27697
- overrideAriaHidden: true,
27657
+ alt: alt,
27698
27658
  width: backgroundImage.width,
27699
27659
  height: backgroundImage.height,
27700
27660
  preloader: apiOptions.imagePreloader,
@@ -27711,16 +27671,6 @@ class ImageWidget extends React__namespace.Component {
27711
27671
  });
27712
27672
  });
27713
27673
  }
27714
- if (this.props.alt) {
27715
- alt = /*#__PURE__*/React__namespace.createElement("span", {
27716
- className: "perseus-sr-only"
27717
- }, /*#__PURE__*/React__namespace.createElement(Renderer, {
27718
- content: this.props.alt,
27719
- apiOptions: apiOptions,
27720
- linterContext: this.props.linterContext,
27721
- strings: this.context.strings
27722
- }));
27723
- }
27724
27674
 
27725
27675
  // For mobile we combine an image's title and caption.
27726
27676
  if (apiOptions.isMobile) {
@@ -27787,7 +27737,7 @@ class ImageWidget extends React__namespace.Component {
27787
27737
  style: {
27788
27738
  maxWidth: backgroundImage.width
27789
27739
  }
27790
- }, image, alt, titleAndCaption);
27740
+ }, image, titleAndCaption);
27791
27741
  }
27792
27742
  let title;
27793
27743
  let caption;
@@ -27819,7 +27769,7 @@ class ImageWidget extends React__namespace.Component {
27819
27769
  style: {
27820
27770
  maxWidth: backgroundImage.width
27821
27771
  }
27822
- }, title, image, alt, caption);
27772
+ }, title, image, caption);
27823
27773
  }
27824
27774
  static validate(userInput, rubric) {
27825
27775
  return {
@@ -28868,7 +28818,6 @@ function AxisLabels() {
28868
28818
 
28869
28819
  // Range is replaced within this file with Interval, but it is used elsewhere
28870
28820
  // and exported from the package, so we need to keep it around.
28871
- // TODO(FEI-5054): Figure out how to get global .d.ts files working with monorepos
28872
28821
  const ItemExtras = [
28873
28822
  // The user might benefit from using a Scientific Calculator. Provided on Khan Academy when true
28874
28823
  "calculator",
@@ -28975,6 +28924,28 @@ const lockedFigureColors = {
28975
28924
  };
28976
28925
  const plotterPlotTypes = ["bar", "line", "pic", "histogram", "dotplot"];
28977
28926
 
28927
+ const LockedCircle = props => {
28928
+ const {
28929
+ center,
28930
+ radius,
28931
+ color,
28932
+ fillStyle,
28933
+ strokeStyle
28934
+ } = props;
28935
+ const fill = {
28936
+ none: 0,
28937
+ solid: 1,
28938
+ translucent: 0.4
28939
+ };
28940
+ return /*#__PURE__*/React__namespace.createElement(mafs.Circle, {
28941
+ center: center,
28942
+ radius: radius,
28943
+ fillOpacity: fill[fillStyle],
28944
+ strokeStyle: strokeStyle,
28945
+ color: lockedFigureColors[color]
28946
+ });
28947
+ };
28948
+
28978
28949
  function pathBuilder() {
28979
28950
  return new PathBuilder();
28980
28951
  }
@@ -29243,6 +29214,10 @@ const GraphLockedLayer = props => {
29243
29214
  key: `line-${index}`,
29244
29215
  range: props.range
29245
29216
  }, figure));
29217
+ case "circle":
29218
+ return /*#__PURE__*/React__namespace.createElement(LockedCircle, _extends({
29219
+ key: `circle-${index}`
29220
+ }, figure));
29246
29221
  default:
29247
29222
  /**
29248
29223
  * Devlopment-time future-proofing: This should
@@ -30048,9 +30023,19 @@ function initializeGraphState(params) {
30048
30023
  center: [0, 0],
30049
30024
  radiusPoint: [1, 0]
30050
30025
  };
30051
- case "angle":
30052
- case "sinusoid":
30053
30026
  case "quadratic":
30027
+ return {
30028
+ ...shared,
30029
+ type: graph.type,
30030
+ coords: getQuadraticCoords(graph, range, step)
30031
+ };
30032
+ case "sinusoid":
30033
+ return {
30034
+ ...shared,
30035
+ type: graph.type,
30036
+ coords: getSinusoidCoords(graph, range, step)
30037
+ };
30038
+ case "angle":
30054
30039
  throw new Error("Mafs not yet implemented for graph type: " + graph.type);
30055
30040
  default:
30056
30041
  throw new wonderStuffCore.UnreachableCaseError(graph);
@@ -30148,6 +30133,18 @@ function getGradableGraph(state, initialGraph) {
30148
30133
  radius: getRadius(state)
30149
30134
  };
30150
30135
  }
30136
+ if (state.type === "quadratic" && initialGraph.type === "quadratic") {
30137
+ return {
30138
+ ...initialGraph,
30139
+ coords: state.coords
30140
+ };
30141
+ }
30142
+ if (state.type === "sinusoid" && initialGraph.type === "sinusoid") {
30143
+ return {
30144
+ ...initialGraph,
30145
+ coords: state.coords
30146
+ };
30147
+ }
30151
30148
  throw new Error("Mafs is not yet implemented for graph type: " + initialGraph.type);
30152
30149
  }
30153
30150
  const getDefaultSegments = _ref2 => {
@@ -30227,6 +30224,16 @@ const getPolygonCoords = _ref4 => {
30227
30224
  coords = normalizePoints(range, step, coords, /* noSnap */!snapToGrid);
30228
30225
  return coords;
30229
30226
  };
30227
+ const getSinusoidCoords = (graph, range, step) => {
30228
+ let coords = [[0.5, 0.5], [0.65, 0.6]];
30229
+ coords = normalizePoints(range, step, coords, true);
30230
+ return coords;
30231
+ };
30232
+ const getQuadraticCoords = (graph, range, step) => {
30233
+ let coords = [[0.25, 0.75], [0.5, 0.25], [0.75, 0.75]];
30234
+ coords = normalizePoints(range, step, coords, true);
30235
+ return coords;
30236
+ };
30230
30237
 
30231
30238
  /**
30232
30239
  * determine radius of a circle graph
@@ -30336,6 +30343,138 @@ function crossProduct(as, bs) {
30336
30343
  return result;
30337
30344
  }
30338
30345
 
30346
+ function QuadraticGraph(props) {
30347
+ const {
30348
+ dispatch,
30349
+ graphState
30350
+ } = props;
30351
+ const {
30352
+ coords
30353
+ } = graphState;
30354
+
30355
+ // The coefficients are used to calculate the quadratic equation, plot the graph, and to indicate
30356
+ // to content creators the currently selected "correct answer". ex: y = 0.200x^2 + 0.000x + 0.000
30357
+ // While we should technically never have invalid coordinates, we want to ensure that we have a fallback
30358
+ const coeffRef = React__namespace.useRef([0, 0, 0]);
30359
+ const coeffs = getQuadraticCoefficients(coords);
30360
+ if (coeffs !== undefined) {
30361
+ coeffRef.current = coeffs;
30362
+ }
30363
+
30364
+ // Destructure the coefficients for calculating the quadratic equation
30365
+ const [a, b, c] = coeffRef.current;
30366
+
30367
+ // Calculate the y value based on the current x value
30368
+ const y = x => (a * x + b) * x + c;
30369
+ const handleOnMove = (destination, elementId) => {
30370
+ // If the destination is invalid, we want do not want to move the point
30371
+ const validDestination = isValidDestination(destination, elementId, coords);
30372
+ if (validDestination === false) {
30373
+ return;
30374
+ }
30375
+ dispatch(movePoint(elementId, destination));
30376
+ };
30377
+ return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(mafs.Plot.OfX, {
30378
+ y: y,
30379
+ color: wonderBlocksTokens.color.blue
30380
+ }), coords.map((coord, i) => /*#__PURE__*/React__namespace.createElement(StyledMovablePoint, {
30381
+ key: "point-" + i,
30382
+ point: coord,
30383
+ onMove: destination => handleOnMove(destination, i)
30384
+ })));
30385
+ }
30386
+
30387
+ // Ensure that we are only snapping to coordinates that result in a valid quadratic equation
30388
+ const isValidDestination = (destination, elementId, coords) => {
30389
+ // Set up the new coords and check if the quadratic coefficients are valid
30390
+ const newCoords = [...coords];
30391
+ newCoords[elementId] = destination;
30392
+ const QuadraticCoefficients = getQuadraticCoefficients(newCoords);
30393
+
30394
+ // If the new destination results in an invalid quadratic equation, we don't want to move the point
30395
+ if (QuadraticCoefficients === undefined) {
30396
+ return false;
30397
+ }
30398
+ return true;
30399
+ };
30400
+
30401
+ // Get the quadratic coefficients from the 3 control points
30402
+ // These equations were originally set up in 2013 and may require some
30403
+ // additional comments to help clarify the quadratic formula manipulations
30404
+ // Origin: https://phabricator.khanacademy.org/D2413
30405
+ const getQuadraticCoefficients = coords => {
30406
+ const p1 = coords[0];
30407
+ const p2 = coords[1];
30408
+ const p3 = coords[2];
30409
+
30410
+ // If the denominator is 0, we are going to return undefined as we are
30411
+ // unable to calculate the quadratic coefficients when they hit infinity
30412
+ const denom = (p1[0] - p2[0]) * (p1[0] - p3[0]) * (p2[0] - p3[0]);
30413
+ if (denom === 0) {
30414
+ return;
30415
+ }
30416
+ const a = (p3[0] * (p2[1] - p1[1]) + p2[0] * (p1[1] - p3[1]) + p1[0] * (p3[1] - p2[1])) / denom;
30417
+ const b = (p3[0] * p3[0] * (p1[1] - p2[1]) + p2[0] * p2[0] * (p3[1] - p1[1]) + p1[0] * p1[0] * (p2[1] - p3[1])) / denom;
30418
+ const c = (p2[0] * p3[0] * (p2[0] - p3[0]) * p1[1] + p3[0] * p1[0] * (p3[0] - p1[0]) * p2[1] + p1[0] * p2[0] * (p1[0] - p2[0]) * p3[1]) / denom;
30419
+ return [a, b, c];
30420
+ };
30421
+
30422
+ function SinusoidGraph(props) {
30423
+ const {
30424
+ dispatch,
30425
+ graphState
30426
+ } = props;
30427
+
30428
+ // Destructure the coordinates from the graph state
30429
+ // Note: The order of the coordinates is important:
30430
+ // The coords[0] is the root and the coords[1] is the first peak
30431
+ const {
30432
+ coords
30433
+ } = graphState;
30434
+
30435
+ // Get the coefficients for calculating the quadratic equation
30436
+ const coeffs = getSinusoidCoefficients(coords);
30437
+ return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(mafs.Plot.OfX, {
30438
+ y: x => computeSine(x, coeffs),
30439
+ color: wonderBlocksTokens.color.blue
30440
+ }), coords.map((coord, i) => /*#__PURE__*/React__namespace.createElement(StyledMovablePoint, {
30441
+ key: "point-" + i,
30442
+ point: coord,
30443
+ onMove: destination => dispatch(movePoint(i, destination))
30444
+ })));
30445
+ }
30446
+
30447
+ // Plot a sinusoid of the form: f(x) = a * sin(b * x - c) + d
30448
+ const computeSine = function (x,
30449
+ // x-coordinate
30450
+ sinusoidCoefficients) {
30451
+ // Break down the coefficients for the sine function to improve readability
30452
+ const {
30453
+ amplitude: a,
30454
+ angularFrequency: b,
30455
+ phase: c,
30456
+ verticalOffset: d
30457
+ } = sinusoidCoefficients;
30458
+ return a * Math.sin(b * x - c) + d;
30459
+ };
30460
+ const getSinusoidCoefficients = coords => {
30461
+ // It's assumed that p1 is the root and p2 is the first peak
30462
+ const p1 = coords[0];
30463
+ const p2 = coords[1];
30464
+
30465
+ // Resulting coefficients are canonical for this sine curve
30466
+ const amplitude = p2[1] - p1[1];
30467
+ const angularFrequency = Math.PI / (2 * (p2[0] - p1[0]));
30468
+ const phase = p1[0] * angularFrequency;
30469
+ const verticalOffset = p1[1];
30470
+ return {
30471
+ amplitude,
30472
+ angularFrequency,
30473
+ phase,
30474
+ verticalOffset
30475
+ };
30476
+ };
30477
+
30339
30478
  // Universal padding for the axis ticks to ensure that the labels
30340
30479
  // have enough space to render without overlapping the tick lines
30341
30480
  const tickPadding = 5;
@@ -30496,9 +30635,6 @@ const AxisTickLabel = _ref => {
30496
30635
  const pointOnAxis = axis === "x" ? [label, 0] : [0, label];
30497
30636
  const pixelPoint = pointToPixel(pointOnAxis, graphConfig);
30498
30637
 
30499
- // Remove the negative sign from the label string as it is handled in the data-content attribute
30500
- const labelString = Math.abs(label).toString();
30501
-
30502
30638
  // Determine the relevant edge of the graph based on the axis type
30503
30639
  const graphEdge = axis === "x" ? graphConfig.width : graphConfig.height;
30504
30640
 
@@ -30514,15 +30650,9 @@ const AxisTickLabel = _ref => {
30514
30650
  gridStep: gridStep[vectorIndex],
30515
30651
  tickStep: tickStep[vectorIndex]
30516
30652
  });
30517
-
30518
- // Determine whether or not to render a negative symbol for the tick label
30519
- // as the negative sign is handled in the data-content attribute in order to
30520
- // maintain the correct spacing between the labels
30521
- const dataContent = label < 0 && shouldShowLabel ? "−" : null;
30522
30653
  return /*#__PURE__*/React__namespace.createElement("span", {
30523
- className: `${axis}-axis-tick-label`,
30524
- "data-content": dataContent
30525
- }, shouldShowLabel && labelString);
30654
+ className: `${axis}-axis-tick-label`
30655
+ }, shouldShowLabel && label);
30526
30656
  };
30527
30657
 
30528
30658
  // Determine whether or not to show the tick label text
@@ -31477,6 +31607,8 @@ function doMoveControlPoint(state, action) {
31477
31607
  throw new Error("FIXME implement circle reducer");
31478
31608
  case "point":
31479
31609
  case "polygon":
31610
+ case "quadratic":
31611
+ case "sinusoid":
31480
31612
  throw new Error(`Don't use moveControlPoint for ${state.type} graphs. Use movePoint instead!`);
31481
31613
  default:
31482
31614
  throw new wonderStuffCore.UnreachableCaseError(state);
@@ -31549,8 +31681,48 @@ function doMoveAll(state, action) {
31549
31681
  }
31550
31682
  function doMovePoint(state, action) {
31551
31683
  switch (state.type) {
31552
- case "point":
31553
31684
  case "polygon":
31685
+ case "point":
31686
+ {
31687
+ return {
31688
+ ...state,
31689
+ hasBeenInteractedWith: true,
31690
+ coords: setAtIndex({
31691
+ array: state.coords,
31692
+ index: action.index,
31693
+ newValue: snap(state.snapStep, bound$1({
31694
+ snapStep: state.snapStep,
31695
+ range: state.range,
31696
+ point: action.destination
31697
+ }))
31698
+ })
31699
+ };
31700
+ }
31701
+ case "sinusoid":
31702
+ {
31703
+ // First, we need to verify that the new coordinates are not on the same vertical line
31704
+ // If they are, we don't want to move the point
31705
+ const destination = action.destination;
31706
+ const newCoords = [...state.coords];
31707
+ newCoords[action.index] = action.destination;
31708
+ if (newCoords[0][0] === newCoords[1][0]) {
31709
+ return state;
31710
+ }
31711
+ return {
31712
+ ...state,
31713
+ hasBeenInteractedWith: true,
31714
+ coords: setAtIndex({
31715
+ array: state.coords,
31716
+ index: action.index,
31717
+ newValue: snap(state.snapStep, bound$1({
31718
+ snapStep: state.snapStep,
31719
+ range: state.range,
31720
+ point: destination
31721
+ }))
31722
+ })
31723
+ };
31724
+ }
31725
+ case "quadratic":
31554
31726
  {
31555
31727
  return {
31556
31728
  ...state,
@@ -31567,7 +31739,7 @@ function doMovePoint(state, action) {
31567
31739
  };
31568
31740
  }
31569
31741
  default:
31570
- throw new Error("The movePoint action is only for point and polygon graphs");
31742
+ throw new Error("The movePoint action is only for point, quadratic, and polygon graphs");
31571
31743
  }
31572
31744
  }
31573
31745
  function doMoveCenter(state, action) {
@@ -31789,6 +31961,16 @@ const renderGraph = props => {
31789
31961
  graphState: state,
31790
31962
  dispatch: dispatch
31791
31963
  });
31964
+ case "quadratic":
31965
+ return /*#__PURE__*/React__namespace.createElement(QuadraticGraph, {
31966
+ graphState: state,
31967
+ dispatch: dispatch
31968
+ });
31969
+ case "sinusoid":
31970
+ return /*#__PURE__*/React__namespace.createElement(SinusoidGraph, {
31971
+ graphState: state,
31972
+ dispatch: dispatch
31973
+ });
31792
31974
  default:
31793
31975
  return new wonderStuffCore.UnreachableCaseError(type);
31794
31976
  }
@@ -44002,7 +44184,7 @@ const init = function (options) {
44002
44184
 
44003
44185
  // This file is processed by a Rollup plugin (replace) to inject the production
44004
44186
  const libName = "@khanacademy/perseus";
44005
- const libVersion = "22.7.0";
44187
+ const libVersion = "23.1.0";
44006
44188
  perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
44007
44189
 
44008
44190
  const apiVersion = {