@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/es/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/es/index.js CHANGED
@@ -29,7 +29,7 @@ import { OptionItem, SingleSelect, MultiSelect } from '@khanacademy/wonder-block
29
29
  import caretDown from '@phosphor-icons/core/regular/caret-down.svg';
30
30
  import caretUp from '@phosphor-icons/core/regular/caret-up.svg';
31
31
  import debounce$2 from 'lodash.debounce';
32
- import { vec, Line as Line$4, Point as Point$2, useMovable, Text as Text$1, Polygon, Coordinates, Mafs } from 'mafs';
32
+ import { vec, Circle as Circle$1, Line as Line$4, Point as Point$2, useMovable, Text as Text$1, Polygon, Plot as Plot$3, Coordinates, Mafs } from 'mafs';
33
33
  import { PhosphorIcon } from '@khanacademy/wonder-blocks-icon';
34
34
  import { Body, LabelSmall, LabelMedium } from '@khanacademy/wonder-blocks-typography';
35
35
  import questionIcon from '@phosphor-icons/core/regular/question.svg';
@@ -1142,8 +1142,6 @@ const ApiOptions = {
1142
1142
  // drawing area (scratchpad) should be allowed/disallowed.
1143
1143
  // Previously handled by `Khan.scratchpad.enable/disable`
1144
1144
  setDrawingAreaAvailable: PropTypes.func,
1145
- // Whether to use the Draft.js editor or the legacy textarea
1146
- useDraftEditor: PropTypes.bool,
1147
1145
  // The color used for the hint progress indicator (eg. 1 / 3)
1148
1146
  hintProgressColor: PropTypes.string,
1149
1147
  // Whether this Renderer is allowed to auto-scroll the rest of the
@@ -1153,8 +1151,6 @@ const ApiOptions = {
1153
1151
  //
1154
1152
  // Defaults to `false`.
1155
1153
  canScrollPage: PropTypes.bool,
1156
- // Whether or not we are rendering content inside of a modal.
1157
- inModal: PropTypes.bool,
1158
1154
  // Whether to enable the cross-out feature on multiple-choice radio
1159
1155
  // widgets. This allows users to note which answers they believe to
1160
1156
  // be incorrect, to find the answer by process of elimination.
@@ -1190,9 +1186,7 @@ const ApiOptions = {
1190
1186
  }
1191
1187
  },
1192
1188
  setDrawingAreaAvailable: function () {},
1193
- useDraftEditor: false,
1194
1189
  canScrollPage: false,
1195
- inModal: false,
1196
1190
  crossOutEnabled: false,
1197
1191
  editorChangeDelay: 0
1198
1192
  }
@@ -17436,16 +17430,8 @@ class SvgImage extends React.Component {
17436
17430
  // Props to send to all images
17437
17431
  const imageProps = {
17438
17432
  alt: this.props.alt,
17439
- title: this.props.title,
17440
- tabIndex: "0"
17433
+ title: this.props.title
17441
17434
  };
17442
- // If alt text is present, AND overrideAriaHidden is true,
17443
- // include aria-hidden to avoid screen reader from crawling texts
17444
- // both from the div meant for screen readers (see svg-image.jsx)
17445
- // and alt attribute.
17446
- if (this.props.alt && this.props.overrideAriaHidden) {
17447
- imageProps["aria-hidden"] = true;
17448
- }
17449
17435
  const width = this.props.width && this.props.width * this.props.scale;
17450
17436
  const height = this.props.height && this.props.height * this.props.scale;
17451
17437
  const dimensions = {
@@ -24950,8 +24936,6 @@ var CSProgram$1 = {
24950
24936
  hidden: true
24951
24937
  };
24952
24938
 
24953
- // TODO(FEI-5054): Figure out how to get global .d.ts files working with monorepos
24954
-
24955
24939
  class Definition extends React.Component {
24956
24940
  constructor(...args) {
24957
24941
  super(...args);
@@ -25166,8 +25150,6 @@ var Dropdown$1 = {
25166
25150
 
25167
25151
  /* eslint-disable react/sort-comp */
25168
25152
 
25169
- // TODO(FEI-5054): Figure out how to get global .d.ts files working with monorepos
25170
-
25171
25153
  // transform = _.identity
25172
25154
 
25173
25155
  function mediaQueryIsMatched(mediaQuery) {
@@ -27371,7 +27353,7 @@ class ImageWidget extends React.Component {
27371
27353
 
27372
27354
  render() {
27373
27355
  let image;
27374
- let alt;
27356
+ const alt = this.props.caption === this.props.alt ? "" : this.props.alt;
27375
27357
  const {
27376
27358
  apiOptions
27377
27359
  } = this.props;
@@ -27382,29 +27364,7 @@ class ImageWidget extends React.Component {
27382
27364
  setAssetStatus
27383
27365
  }) => /*#__PURE__*/React.createElement(SvgImage, {
27384
27366
  src: url,
27385
- alt:
27386
- /* alt text is formatted in a sr-only
27387
- div next to the image in addition to
27388
- the alt attribute.
27389
- If there is no alt text at all,
27390
- we don't put an alt attribute on
27391
- the image, so that screen readers
27392
- know there's something they can't
27393
- read there :(.
27394
- NOTE: React <=0.13 (maybe later)
27395
- has a bug where it won't ever
27396
- remove an attribute, so if this
27397
- alt node is ever defined it's
27398
- not removed. This is sort of
27399
- dangerous, but we usually re-key
27400
- new renderers so that they're
27401
- rendered from scratch anyways,
27402
- so this shouldn't be a problem
27403
- in practice right now, although
27404
- it will exhibit weird behaviour
27405
- while editing. */
27406
- this.props.alt,
27407
- overrideAriaHidden: true,
27367
+ alt: alt,
27408
27368
  width: backgroundImage.width,
27409
27369
  height: backgroundImage.height,
27410
27370
  preloader: apiOptions.imagePreloader,
@@ -27420,16 +27380,6 @@ class ImageWidget extends React.Component {
27420
27380
  setAssetStatus: setAssetStatus
27421
27381
  }));
27422
27382
  }
27423
- if (this.props.alt) {
27424
- alt = /*#__PURE__*/React.createElement("span", {
27425
- className: "perseus-sr-only"
27426
- }, /*#__PURE__*/React.createElement(Renderer, {
27427
- content: this.props.alt,
27428
- apiOptions: apiOptions,
27429
- linterContext: this.props.linterContext,
27430
- strings: this.context.strings
27431
- }));
27432
- }
27433
27383
 
27434
27384
  // For mobile we combine an image's title and caption.
27435
27385
  if (apiOptions.isMobile) {
@@ -27496,7 +27446,7 @@ class ImageWidget extends React.Component {
27496
27446
  style: {
27497
27447
  maxWidth: backgroundImage.width
27498
27448
  }
27499
- }, image, alt, titleAndCaption);
27449
+ }, image, titleAndCaption);
27500
27450
  }
27501
27451
  let title;
27502
27452
  let caption;
@@ -27528,7 +27478,7 @@ class ImageWidget extends React.Component {
27528
27478
  style: {
27529
27479
  maxWidth: backgroundImage.width
27530
27480
  }
27531
- }, title, image, alt, caption);
27481
+ }, title, image, caption);
27532
27482
  }
27533
27483
  static validate(userInput, rubric) {
27534
27484
  return {
@@ -28577,7 +28527,6 @@ function AxisLabels() {
28577
28527
 
28578
28528
  // Range is replaced within this file with Interval, but it is used elsewhere
28579
28529
  // and exported from the package, so we need to keep it around.
28580
- // TODO(FEI-5054): Figure out how to get global .d.ts files working with monorepos
28581
28530
  const ItemExtras = [
28582
28531
  // The user might benefit from using a Scientific Calculator. Provided on Khan Academy when true
28583
28532
  "calculator",
@@ -28684,6 +28633,28 @@ const lockedFigureColors = {
28684
28633
  };
28685
28634
  const plotterPlotTypes = ["bar", "line", "pic", "histogram", "dotplot"];
28686
28635
 
28636
+ const LockedCircle = props => {
28637
+ const {
28638
+ center,
28639
+ radius,
28640
+ color,
28641
+ fillStyle,
28642
+ strokeStyle
28643
+ } = props;
28644
+ const fill = {
28645
+ none: 0,
28646
+ solid: 1,
28647
+ translucent: 0.4
28648
+ };
28649
+ return /*#__PURE__*/React.createElement(Circle$1, {
28650
+ center: center,
28651
+ radius: radius,
28652
+ fillOpacity: fill[fillStyle],
28653
+ strokeStyle: strokeStyle,
28654
+ color: lockedFigureColors[color]
28655
+ });
28656
+ };
28657
+
28687
28658
  function pathBuilder() {
28688
28659
  return new PathBuilder();
28689
28660
  }
@@ -28953,6 +28924,10 @@ const GraphLockedLayer = props => {
28953
28924
  key: `line-${index}`,
28954
28925
  range: props.range
28955
28926
  }, figure));
28927
+ case "circle":
28928
+ return /*#__PURE__*/React.createElement(LockedCircle, _extends({
28929
+ key: `circle-${index}`
28930
+ }, figure));
28956
28931
  default:
28957
28932
  /**
28958
28933
  * Devlopment-time future-proofing: This should
@@ -29742,9 +29717,17 @@ function initializeGraphState(params) {
29742
29717
  center: [0, 0],
29743
29718
  radiusPoint: [1, 0]
29744
29719
  });
29745
- case "angle":
29746
- case "sinusoid":
29747
29720
  case "quadratic":
29721
+ return _extends({}, shared, {
29722
+ type: graph.type,
29723
+ coords: getQuadraticCoords(graph, range, step)
29724
+ });
29725
+ case "sinusoid":
29726
+ return _extends({}, shared, {
29727
+ type: graph.type,
29728
+ coords: getSinusoidCoords(graph, range, step)
29729
+ });
29730
+ case "angle":
29748
29731
  throw new Error("Mafs not yet implemented for graph type: " + graph.type);
29749
29732
  default:
29750
29733
  throw new UnreachableCaseError(graph);
@@ -29833,6 +29816,16 @@ function getGradableGraph(state, initialGraph) {
29833
29816
  radius: getRadius(state)
29834
29817
  });
29835
29818
  }
29819
+ if (state.type === "quadratic" && initialGraph.type === "quadratic") {
29820
+ return _extends({}, initialGraph, {
29821
+ coords: state.coords
29822
+ });
29823
+ }
29824
+ if (state.type === "sinusoid" && initialGraph.type === "sinusoid") {
29825
+ return _extends({}, initialGraph, {
29826
+ coords: state.coords
29827
+ });
29828
+ }
29836
29829
  throw new Error("Mafs is not yet implemented for graph type: " + initialGraph.type);
29837
29830
  }
29838
29831
  const getDefaultSegments = ({
@@ -29910,6 +29903,16 @@ const getPolygonCoords = ({
29910
29903
  coords = normalizePoints(range, step, coords, /* noSnap */!snapToGrid);
29911
29904
  return coords;
29912
29905
  };
29906
+ const getSinusoidCoords = (graph, range, step) => {
29907
+ let coords = [[0.5, 0.5], [0.65, 0.6]];
29908
+ coords = normalizePoints(range, step, coords, true);
29909
+ return coords;
29910
+ };
29911
+ const getQuadraticCoords = (graph, range, step) => {
29912
+ let coords = [[0.25, 0.75], [0.5, 0.25], [0.75, 0.75]];
29913
+ coords = normalizePoints(range, step, coords, true);
29914
+ return coords;
29915
+ };
29913
29916
 
29914
29917
  /**
29915
29918
  * determine radius of a circle graph
@@ -30019,6 +30022,138 @@ function crossProduct(as, bs) {
30019
30022
  return result;
30020
30023
  }
30021
30024
 
30025
+ function QuadraticGraph(props) {
30026
+ const {
30027
+ dispatch,
30028
+ graphState
30029
+ } = props;
30030
+ const {
30031
+ coords
30032
+ } = graphState;
30033
+
30034
+ // The coefficients are used to calculate the quadratic equation, plot the graph, and to indicate
30035
+ // to content creators the currently selected "correct answer". ex: y = 0.200x^2 + 0.000x + 0.000
30036
+ // While we should technically never have invalid coordinates, we want to ensure that we have a fallback
30037
+ const coeffRef = React.useRef([0, 0, 0]);
30038
+ const coeffs = getQuadraticCoefficients(coords);
30039
+ if (coeffs !== undefined) {
30040
+ coeffRef.current = coeffs;
30041
+ }
30042
+
30043
+ // Destructure the coefficients for calculating the quadratic equation
30044
+ const [a, b, c] = coeffRef.current;
30045
+
30046
+ // Calculate the y value based on the current x value
30047
+ const y = x => (a * x + b) * x + c;
30048
+ const handleOnMove = (destination, elementId) => {
30049
+ // If the destination is invalid, we want do not want to move the point
30050
+ const validDestination = isValidDestination(destination, elementId, coords);
30051
+ if (validDestination === false) {
30052
+ return;
30053
+ }
30054
+ dispatch(movePoint(elementId, destination));
30055
+ };
30056
+ return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Plot$3.OfX, {
30057
+ y: y,
30058
+ color: color.blue
30059
+ }), coords.map((coord, i) => /*#__PURE__*/React.createElement(StyledMovablePoint, {
30060
+ key: "point-" + i,
30061
+ point: coord,
30062
+ onMove: destination => handleOnMove(destination, i)
30063
+ })));
30064
+ }
30065
+
30066
+ // Ensure that we are only snapping to coordinates that result in a valid quadratic equation
30067
+ const isValidDestination = (destination, elementId, coords) => {
30068
+ // Set up the new coords and check if the quadratic coefficients are valid
30069
+ const newCoords = [...coords];
30070
+ newCoords[elementId] = destination;
30071
+ const QuadraticCoefficients = getQuadraticCoefficients(newCoords);
30072
+
30073
+ // If the new destination results in an invalid quadratic equation, we don't want to move the point
30074
+ if (QuadraticCoefficients === undefined) {
30075
+ return false;
30076
+ }
30077
+ return true;
30078
+ };
30079
+
30080
+ // Get the quadratic coefficients from the 3 control points
30081
+ // These equations were originally set up in 2013 and may require some
30082
+ // additional comments to help clarify the quadratic formula manipulations
30083
+ // Origin: https://phabricator.khanacademy.org/D2413
30084
+ const getQuadraticCoefficients = coords => {
30085
+ const p1 = coords[0];
30086
+ const p2 = coords[1];
30087
+ const p3 = coords[2];
30088
+
30089
+ // If the denominator is 0, we are going to return undefined as we are
30090
+ // unable to calculate the quadratic coefficients when they hit infinity
30091
+ const denom = (p1[0] - p2[0]) * (p1[0] - p3[0]) * (p2[0] - p3[0]);
30092
+ if (denom === 0) {
30093
+ return;
30094
+ }
30095
+ const a = (p3[0] * (p2[1] - p1[1]) + p2[0] * (p1[1] - p3[1]) + p1[0] * (p3[1] - p2[1])) / denom;
30096
+ 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;
30097
+ 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;
30098
+ return [a, b, c];
30099
+ };
30100
+
30101
+ function SinusoidGraph(props) {
30102
+ const {
30103
+ dispatch,
30104
+ graphState
30105
+ } = props;
30106
+
30107
+ // Destructure the coordinates from the graph state
30108
+ // Note: The order of the coordinates is important:
30109
+ // The coords[0] is the root and the coords[1] is the first peak
30110
+ const {
30111
+ coords
30112
+ } = graphState;
30113
+
30114
+ // Get the coefficients for calculating the quadratic equation
30115
+ const coeffs = getSinusoidCoefficients(coords);
30116
+ return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Plot$3.OfX, {
30117
+ y: x => computeSine(x, coeffs),
30118
+ color: color.blue
30119
+ }), coords.map((coord, i) => /*#__PURE__*/React.createElement(StyledMovablePoint, {
30120
+ key: "point-" + i,
30121
+ point: coord,
30122
+ onMove: destination => dispatch(movePoint(i, destination))
30123
+ })));
30124
+ }
30125
+
30126
+ // Plot a sinusoid of the form: f(x) = a * sin(b * x - c) + d
30127
+ const computeSine = function computeSine(x,
30128
+ // x-coordinate
30129
+ sinusoidCoefficients) {
30130
+ // Break down the coefficients for the sine function to improve readability
30131
+ const {
30132
+ amplitude: a,
30133
+ angularFrequency: b,
30134
+ phase: c,
30135
+ verticalOffset: d
30136
+ } = sinusoidCoefficients;
30137
+ return a * Math.sin(b * x - c) + d;
30138
+ };
30139
+ const getSinusoidCoefficients = coords => {
30140
+ // It's assumed that p1 is the root and p2 is the first peak
30141
+ const p1 = coords[0];
30142
+ const p2 = coords[1];
30143
+
30144
+ // Resulting coefficients are canonical for this sine curve
30145
+ const amplitude = p2[1] - p1[1];
30146
+ const angularFrequency = Math.PI / (2 * (p2[0] - p1[0]));
30147
+ const phase = p1[0] * angularFrequency;
30148
+ const verticalOffset = p1[1];
30149
+ return {
30150
+ amplitude,
30151
+ angularFrequency,
30152
+ phase,
30153
+ verticalOffset
30154
+ };
30155
+ };
30156
+
30022
30157
  // Universal padding for the axis ticks to ensure that the labels
30023
30158
  // have enough space to render without overlapping the tick lines
30024
30159
  const tickPadding = 5;
@@ -30178,9 +30313,6 @@ const AxisTickLabel = ({
30178
30313
  const pointOnAxis = axis === "x" ? [label, 0] : [0, label];
30179
30314
  const pixelPoint = pointToPixel(pointOnAxis, graphConfig);
30180
30315
 
30181
- // Remove the negative sign from the label string as it is handled in the data-content attribute
30182
- const labelString = Math.abs(label).toString();
30183
-
30184
30316
  // Determine the relevant edge of the graph based on the axis type
30185
30317
  const graphEdge = axis === "x" ? graphConfig.width : graphConfig.height;
30186
30318
 
@@ -30196,15 +30328,9 @@ const AxisTickLabel = ({
30196
30328
  gridStep: gridStep[vectorIndex],
30197
30329
  tickStep: tickStep[vectorIndex]
30198
30330
  });
30199
-
30200
- // Determine whether or not to render a negative symbol for the tick label
30201
- // as the negative sign is handled in the data-content attribute in order to
30202
- // maintain the correct spacing between the labels
30203
- const dataContent = label < 0 && shouldShowLabel ? "−" : null;
30204
30331
  return /*#__PURE__*/React.createElement("span", {
30205
- className: `${axis}-axis-tick-label`,
30206
- "data-content": dataContent
30207
- }, shouldShowLabel && labelString);
30332
+ className: `${axis}-axis-tick-label`
30333
+ }, shouldShowLabel && label);
30208
30334
  };
30209
30335
 
30210
30336
  // Determine whether or not to show the tick label text
@@ -31153,6 +31279,8 @@ function doMoveControlPoint(state, action) {
31153
31279
  throw new Error("FIXME implement circle reducer");
31154
31280
  case "point":
31155
31281
  case "polygon":
31282
+ case "quadratic":
31283
+ case "sinusoid":
31156
31284
  throw new Error(`Don't use moveControlPoint for ${state.type} graphs. Use movePoint instead!`);
31157
31285
  default:
31158
31286
  throw new UnreachableCaseError(state);
@@ -31223,8 +31351,46 @@ function doMoveAll(state, action) {
31223
31351
  }
31224
31352
  function doMovePoint(state, action) {
31225
31353
  switch (state.type) {
31226
- case "point":
31227
31354
  case "polygon":
31355
+ case "point":
31356
+ {
31357
+ return _extends({}, state, {
31358
+ hasBeenInteractedWith: true,
31359
+ coords: setAtIndex({
31360
+ array: state.coords,
31361
+ index: action.index,
31362
+ newValue: snap(state.snapStep, bound$1({
31363
+ snapStep: state.snapStep,
31364
+ range: state.range,
31365
+ point: action.destination
31366
+ }))
31367
+ })
31368
+ });
31369
+ }
31370
+ case "sinusoid":
31371
+ {
31372
+ // First, we need to verify that the new coordinates are not on the same vertical line
31373
+ // If they are, we don't want to move the point
31374
+ const destination = action.destination;
31375
+ const newCoords = [...state.coords];
31376
+ newCoords[action.index] = action.destination;
31377
+ if (newCoords[0][0] === newCoords[1][0]) {
31378
+ return state;
31379
+ }
31380
+ return _extends({}, state, {
31381
+ hasBeenInteractedWith: true,
31382
+ coords: setAtIndex({
31383
+ array: state.coords,
31384
+ index: action.index,
31385
+ newValue: snap(state.snapStep, bound$1({
31386
+ snapStep: state.snapStep,
31387
+ range: state.range,
31388
+ point: destination
31389
+ }))
31390
+ })
31391
+ });
31392
+ }
31393
+ case "quadratic":
31228
31394
  {
31229
31395
  return _extends({}, state, {
31230
31396
  hasBeenInteractedWith: true,
@@ -31240,7 +31406,7 @@ function doMovePoint(state, action) {
31240
31406
  });
31241
31407
  }
31242
31408
  default:
31243
- throw new Error("The movePoint action is only for point and polygon graphs");
31409
+ throw new Error("The movePoint action is only for point, quadratic, and polygon graphs");
31244
31410
  }
31245
31411
  }
31246
31412
  function doMoveCenter(state, action) {
@@ -31453,6 +31619,16 @@ const renderGraph = props => {
31453
31619
  graphState: state,
31454
31620
  dispatch: dispatch
31455
31621
  });
31622
+ case "quadratic":
31623
+ return /*#__PURE__*/React.createElement(QuadraticGraph, {
31624
+ graphState: state,
31625
+ dispatch: dispatch
31626
+ });
31627
+ case "sinusoid":
31628
+ return /*#__PURE__*/React.createElement(SinusoidGraph, {
31629
+ graphState: state,
31630
+ dispatch: dispatch
31631
+ });
31456
31632
  default:
31457
31633
  return new UnreachableCaseError(type);
31458
31634
  }
@@ -43667,7 +43843,7 @@ const init = function init(options) {
43667
43843
 
43668
43844
  // This file is processed by a Rollup plugin (replace) to inject the production
43669
43845
  const libName = "@khanacademy/perseus";
43670
- const libVersion = "22.7.0";
43846
+ const libVersion = "23.1.0";
43671
43847
  addLibraryVersionToPerseusDebug(libName, libVersion);
43672
43848
 
43673
43849
  const apiVersion = {