@khanacademy/perseus 23.0.0 → 23.3.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.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, Plot as Plot$3, Coordinates, Mafs } from 'mafs';
32
+ import { vec, Ellipse, 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';
@@ -10827,6 +10827,33 @@ function intersects(ab, cd) {
10827
10827
  }
10828
10828
  return false;
10829
10829
  }
10830
+
10831
+ // Whether any two sides of a polygon intersect each other
10832
+ function polygonSidesIntersect(vertices) {
10833
+ for (let i = 0; i < vertices.length; i++) {
10834
+ for (let k = i + 1; k < vertices.length; k++) {
10835
+ // If any two vertices are the same point, sides overlap
10836
+ if (point.equal(vertices[i], vertices[k])) {
10837
+ return true;
10838
+ }
10839
+
10840
+ // Find the other end of the sides starting at vertices i and k
10841
+ const iNext = (i + 1) % vertices.length;
10842
+ const kNext = (k + 1) % vertices.length;
10843
+
10844
+ // Adjacent sides always intersect (at the vertex); skip those
10845
+ if (iNext === k || kNext === i) {
10846
+ continue;
10847
+ }
10848
+ const side1 = [vertices[i], vertices[iNext]];
10849
+ const side2 = [vertices[k], vertices[kNext]];
10850
+ if (intersects(side1, side2)) {
10851
+ return true;
10852
+ }
10853
+ }
10854
+ }
10855
+ return false;
10856
+ }
10830
10857
  function vector(a, b) {
10831
10858
  return _.map(_.zip(a, b), function (pair) {
10832
10859
  return pair[0] - pair[1];
@@ -17430,16 +17457,8 @@ class SvgImage extends React.Component {
17430
17457
  // Props to send to all images
17431
17458
  const imageProps = {
17432
17459
  alt: this.props.alt,
17433
- title: this.props.title,
17434
- tabIndex: "0"
17460
+ title: this.props.title
17435
17461
  };
17436
- // If alt text is present, AND overrideAriaHidden is true,
17437
- // include aria-hidden to avoid screen reader from crawling texts
17438
- // both from the div meant for screen readers (see svg-image.jsx)
17439
- // and alt attribute.
17440
- if (this.props.alt && this.props.overrideAriaHidden) {
17441
- imageProps["aria-hidden"] = true;
17442
- }
17443
17462
  const width = this.props.width && this.props.width * this.props.scale;
17444
17463
  const height = this.props.height && this.props.height * this.props.scale;
17445
17464
  const dimensions = {
@@ -27361,7 +27380,7 @@ class ImageWidget extends React.Component {
27361
27380
 
27362
27381
  render() {
27363
27382
  let image;
27364
- let alt;
27383
+ const alt = this.props.caption === this.props.alt ? "" : this.props.alt;
27365
27384
  const {
27366
27385
  apiOptions
27367
27386
  } = this.props;
@@ -27372,29 +27391,7 @@ class ImageWidget extends React.Component {
27372
27391
  setAssetStatus
27373
27392
  }) => /*#__PURE__*/React.createElement(SvgImage, {
27374
27393
  src: url,
27375
- alt:
27376
- /* alt text is formatted in a sr-only
27377
- div next to the image in addition to
27378
- the alt attribute.
27379
- If there is no alt text at all,
27380
- we don't put an alt attribute on
27381
- the image, so that screen readers
27382
- know there's something they can't
27383
- read there :(.
27384
- NOTE: React <=0.13 (maybe later)
27385
- has a bug where it won't ever
27386
- remove an attribute, so if this
27387
- alt node is ever defined it's
27388
- not removed. This is sort of
27389
- dangerous, but we usually re-key
27390
- new renderers so that they're
27391
- rendered from scratch anyways,
27392
- so this shouldn't be a problem
27393
- in practice right now, although
27394
- it will exhibit weird behaviour
27395
- while editing. */
27396
- this.props.alt,
27397
- overrideAriaHidden: true,
27394
+ alt: alt,
27398
27395
  width: backgroundImage.width,
27399
27396
  height: backgroundImage.height,
27400
27397
  preloader: apiOptions.imagePreloader,
@@ -27410,16 +27407,6 @@ class ImageWidget extends React.Component {
27410
27407
  setAssetStatus: setAssetStatus
27411
27408
  }));
27412
27409
  }
27413
- if (this.props.alt) {
27414
- alt = /*#__PURE__*/React.createElement("span", {
27415
- className: "perseus-sr-only"
27416
- }, /*#__PURE__*/React.createElement(Renderer, {
27417
- content: this.props.alt,
27418
- apiOptions: apiOptions,
27419
- linterContext: this.props.linterContext,
27420
- strings: this.context.strings
27421
- }));
27422
- }
27423
27410
 
27424
27411
  // For mobile we combine an image's title and caption.
27425
27412
  if (apiOptions.isMobile) {
@@ -27486,7 +27473,7 @@ class ImageWidget extends React.Component {
27486
27473
  style: {
27487
27474
  maxWidth: backgroundImage.width
27488
27475
  }
27489
- }, image, alt, titleAndCaption);
27476
+ }, image, titleAndCaption);
27490
27477
  }
27491
27478
  let title;
27492
27479
  let caption;
@@ -27518,7 +27505,7 @@ class ImageWidget extends React.Component {
27518
27505
  style: {
27519
27506
  maxWidth: backgroundImage.width
27520
27507
  }
27521
- }, title, image, alt, caption);
27508
+ }, title, image, caption);
27522
27509
  }
27523
27510
  static validate(userInput, rubric) {
27524
27511
  return {
@@ -28671,8 +28658,32 @@ const lockedFigureColors = {
28671
28658
  pink: "#B25071",
28672
28659
  red: "#D92916"
28673
28660
  };
28661
+ const lockedEllipseFillStyles = {
28662
+ none: 0,
28663
+ solid: 1,
28664
+ translucent: 0.4
28665
+ };
28674
28666
  const plotterPlotTypes = ["bar", "line", "pic", "histogram", "dotplot"];
28675
28667
 
28668
+ const LockedEllipse = props => {
28669
+ const {
28670
+ center,
28671
+ radius,
28672
+ angle,
28673
+ color,
28674
+ fillStyle,
28675
+ strokeStyle
28676
+ } = props;
28677
+ return /*#__PURE__*/React.createElement(Ellipse, {
28678
+ center: center,
28679
+ radius: radius,
28680
+ angle: angle,
28681
+ fillOpacity: lockedEllipseFillStyles[fillStyle],
28682
+ strokeStyle: strokeStyle,
28683
+ color: lockedFigureColors[color]
28684
+ });
28685
+ };
28686
+
28676
28687
  function pathBuilder() {
28677
28688
  return new PathBuilder();
28678
28689
  }
@@ -28927,6 +28938,21 @@ const LockedPoint = props => {
28927
28938
  });
28928
28939
  };
28929
28940
 
28941
+ const LockedVector = props => {
28942
+ const {
28943
+ color,
28944
+ points
28945
+ } = props;
28946
+ const [tail, tip] = points;
28947
+ return /*#__PURE__*/React.createElement("g", {
28948
+ className: "locked-vector"
28949
+ }, /*#__PURE__*/React.createElement(Vector, {
28950
+ tail: tail,
28951
+ tip: tip,
28952
+ color: lockedFigureColors[color]
28953
+ }));
28954
+ };
28955
+
28930
28956
  const GraphLockedLayer = props => {
28931
28957
  const {
28932
28958
  lockedFigures
@@ -28942,6 +28968,14 @@ const GraphLockedLayer = props => {
28942
28968
  key: `line-${index}`,
28943
28969
  range: props.range
28944
28970
  }, figure));
28971
+ case "ellipse":
28972
+ return /*#__PURE__*/React.createElement(LockedEllipse, _extends({
28973
+ key: `ellipse-${index}`
28974
+ }, figure));
28975
+ case "vector":
28976
+ return /*#__PURE__*/React.createElement(LockedVector, _extends({
28977
+ key: `vector-${index}`
28978
+ }, figure));
28945
28979
  default:
28946
28980
  /**
28947
28981
  * Devlopment-time future-proofing: This should
@@ -29070,7 +29104,8 @@ const MovablePointView = /*#__PURE__*/forwardRef((props, hitboxRef) => {
29070
29104
  point,
29071
29105
  color: color$1 = color.blue,
29072
29106
  dragging,
29073
- focusBehavior
29107
+ focusBehavior,
29108
+ cursor
29074
29109
  } = props;
29075
29110
 
29076
29111
  // WB Tooltip requires a color name for the background color.
@@ -29102,7 +29137,8 @@ const MovablePointView = /*#__PURE__*/forwardRef((props, hitboxRef) => {
29102
29137
  ref: hitboxRef,
29103
29138
  className: pointClasses,
29104
29139
  style: {
29105
- "--movable-point-color": color$1
29140
+ "--movable-point-color": color$1,
29141
+ cursor
29106
29142
  },
29107
29143
  "data-testid": "movable-point",
29108
29144
  tabIndex: tabIndex(focusBehavior)
@@ -29350,7 +29386,33 @@ const SegmentGraph = props => {
29350
29386
  })));
29351
29387
  };
29352
29388
 
29353
- const LinearGraph = props => {
29389
+ const LinearGraph = (props, key) => {
29390
+ const {
29391
+ dispatch
29392
+ } = props;
29393
+ const {
29394
+ coords: line
29395
+ } = props.graphState;
29396
+
29397
+ // Linear graphs only have one line
29398
+ // (LEMS-2050): Update the reducer so that we have a separate action for moving one line
29399
+ // and another action for moving multiple lines
29400
+ return /*#__PURE__*/React.createElement(MovableLine, {
29401
+ key: 0,
29402
+ points: line,
29403
+ onMoveLine: delta => {
29404
+ dispatch(moveLine(0, delta));
29405
+ },
29406
+ extend: {
29407
+ start: true,
29408
+ end: true
29409
+ },
29410
+ onMovePoint: (endpointIndex, destination) => dispatch(moveControlPoint(endpointIndex, destination, 0)),
29411
+ color: "var(--movable-line-stroke-color)"
29412
+ });
29413
+ };
29414
+
29415
+ const LinearSystemGraph = props => {
29354
29416
  const {
29355
29417
  dispatch
29356
29418
  } = props;
@@ -29370,7 +29432,7 @@ const LinearGraph = props => {
29370
29432
  },
29371
29433
  onMovePoint: (endpointIndex, destination) => dispatch(moveControlPoint(endpointIndex, destination, i)),
29372
29434
  color: colors[i]
29373
- })));
29435
+ })), ";");
29374
29436
  };
29375
29437
 
29376
29438
  const RayGraph = props => {
@@ -29378,14 +29440,14 @@ const RayGraph = props => {
29378
29440
  dispatch
29379
29441
  } = props;
29380
29442
  const {
29381
- coords: lines
29443
+ coords: line
29382
29444
  } = props.graphState;
29383
29445
  const handleMoveLine = delta => dispatch(moveLine(0, delta));
29384
29446
  const handleMovePoint = (pointIndex, newPoint) => dispatch(moveControlPoint(pointIndex, newPoint, 0));
29385
- return /*#__PURE__*/React.createElement(MovableLine
29386
- // a ray only has one line
29387
- , {
29388
- points: lines[0],
29447
+
29448
+ // Ray graphs only have one line
29449
+ return /*#__PURE__*/React.createElement(MovableLine, {
29450
+ points: line,
29389
29451
  onMoveLine: handleMoveLine,
29390
29452
  onMovePoint: handleMovePoint,
29391
29453
  extend: {
@@ -29564,6 +29626,7 @@ const StyledMovablePoint = props => {
29564
29626
  const {
29565
29627
  point,
29566
29628
  onMove,
29629
+ cursor,
29567
29630
  color: color$1 = color.blue
29568
29631
  } = props;
29569
29632
  const {
@@ -29582,7 +29645,8 @@ const StyledMovablePoint = props => {
29582
29645
  focusBehavior: {
29583
29646
  type: "uncontrolled",
29584
29647
  tabIndex: 0
29585
- }
29648
+ },
29649
+ cursor: cursor
29586
29650
  });
29587
29651
  };
29588
29652
 
@@ -29686,25 +29750,39 @@ function initializeGraphState(params) {
29686
29750
  case "segment":
29687
29751
  return _extends({}, shared, {
29688
29752
  type: "segment",
29689
- coords: getDefaultSegments({
29753
+ coords: getSegmentCoords({
29690
29754
  graph,
29691
29755
  step,
29692
29756
  range
29693
29757
  })
29694
29758
  });
29695
29759
  case "linear":
29696
- case "linear-system":
29760
+ return _extends({}, shared, {
29761
+ type: graph.type,
29762
+ coords: getLineCoords({
29763
+ graph,
29764
+ range,
29765
+ step
29766
+ })
29767
+ });
29697
29768
  case "ray":
29698
29769
  return _extends({}, shared, {
29699
29770
  type: graph.type,
29700
- // Linear and ray graphs have a single tuple of points, while a
29701
- // linear system has two tuples of points.
29702
29771
  coords: getLineCoords({
29703
29772
  graph,
29704
29773
  range,
29705
29774
  step
29706
29775
  })
29707
29776
  });
29777
+ case "linear-system":
29778
+ return _extends({}, shared, {
29779
+ type: graph.type,
29780
+ coords: getLinearSystemCoords({
29781
+ graph,
29782
+ range,
29783
+ step
29784
+ })
29785
+ });
29708
29786
  case "polygon":
29709
29787
  return _extends({}, shared, {
29710
29788
  type: "polygon",
@@ -29727,17 +29805,19 @@ function initializeGraphState(params) {
29727
29805
  });
29728
29806
  case "circle":
29729
29807
  return _extends({}, shared, {
29730
- type: graph.type,
29731
- center: [0, 0],
29732
- radiusPoint: [1, 0]
29733
- });
29808
+ type: graph.type
29809
+ }, getCircleCoords(graph));
29734
29810
  case "quadratic":
29735
29811
  return _extends({}, shared, {
29736
29812
  type: graph.type,
29737
29813
  coords: getQuadraticCoords(graph, range, step)
29738
29814
  });
29739
- case "angle":
29740
29815
  case "sinusoid":
29816
+ return _extends({}, shared, {
29817
+ type: graph.type,
29818
+ coords: getSinusoidCoords(graph, range, step)
29819
+ });
29820
+ case "angle":
29741
29821
  throw new Error("Mafs not yet implemented for graph type: " + graph.type);
29742
29822
  default:
29743
29823
  throw new UnreachableCaseError(graph);
@@ -29802,12 +29882,12 @@ function getGradableGraph(state, initialGraph) {
29802
29882
  }
29803
29883
  if (state.type === "linear" && initialGraph.type === "linear") {
29804
29884
  return _extends({}, initialGraph, {
29805
- coords: state.coords[0]
29885
+ coords: state.coords
29806
29886
  });
29807
29887
  }
29808
29888
  if (state.type === "ray" && initialGraph.type === "ray") {
29809
29889
  return _extends({}, initialGraph, {
29810
- coords: state.coords[0]
29890
+ coords: state.coords
29811
29891
  });
29812
29892
  }
29813
29893
  if (state.type === "polygon" && initialGraph.type === "polygon") {
@@ -29831,13 +29911,21 @@ function getGradableGraph(state, initialGraph) {
29831
29911
  coords: state.coords
29832
29912
  });
29833
29913
  }
29914
+ if (state.type === "sinusoid" && initialGraph.type === "sinusoid") {
29915
+ return _extends({}, initialGraph, {
29916
+ coords: state.coords
29917
+ });
29918
+ }
29834
29919
  throw new Error("Mafs is not yet implemented for graph type: " + initialGraph.type);
29835
29920
  }
29836
- const getDefaultSegments = ({
29921
+ const getSegmentCoords = ({
29837
29922
  graph,
29838
29923
  range,
29839
29924
  step
29840
29925
  }) => {
29926
+ if (graph.coords) {
29927
+ return graph.coords;
29928
+ }
29841
29929
  const ys = n => {
29842
29930
  switch (n) {
29843
29931
  case 2:
@@ -29868,16 +29956,20 @@ const getLineCoords = ({
29868
29956
  range,
29869
29957
  step
29870
29958
  }) => {
29871
- // Return two lines for a linear system, one for a ray or linear
29872
- switch (graph.type) {
29873
- case "linear-system":
29874
- return defaultLinearCoords.map(points => normalizePoints(range, step, points));
29875
- case "linear":
29876
- case "ray":
29877
- return [normalizePoints(range, step, defaultLinearCoords[0])];
29878
- default:
29879
- throw new UnreachableCaseError(graph);
29959
+ if (graph.coords) {
29960
+ return graph.coords;
29961
+ }
29962
+ return normalizePoints(range, step, defaultLinearCoords[0]);
29963
+ };
29964
+ const getLinearSystemCoords = ({
29965
+ graph,
29966
+ range,
29967
+ step
29968
+ }) => {
29969
+ if (graph.coords) {
29970
+ return graph.coords;
29880
29971
  }
29972
+ return defaultLinearCoords.map(points => normalizePoints(range, step, points));
29881
29973
  };
29882
29974
  const getPolygonCoords = ({
29883
29975
  graph,
@@ -29908,11 +30000,33 @@ const getPolygonCoords = ({
29908
30000
  coords = normalizePoints(range, step, coords, /* noSnap */!snapToGrid);
29909
30001
  return coords;
29910
30002
  };
29911
- const getQuadraticCoords = (graph, range, step) => {
29912
- let coords = [[0.25, 0.75], [0.5, 0.25], [0.75, 0.75]];
30003
+ const getSinusoidCoords = (graph, range, step) => {
30004
+ if (graph.coords) {
30005
+ return [graph.coords[0], graph.coords[1]];
30006
+ }
30007
+ let coords = [[0.5, 0.5], [0.65, 0.6]];
29913
30008
  coords = normalizePoints(range, step, coords, true);
29914
30009
  return coords;
29915
30010
  };
30011
+ const getQuadraticCoords = (graph, range, step) => {
30012
+ if (graph.coords) {
30013
+ return graph.coords;
30014
+ }
30015
+ const defaultCoords = [[0.25, 0.75], [0.5, 0.25], [0.75, 0.75]];
30016
+ return normalizePoints(range, step, defaultCoords, true);
30017
+ };
30018
+ const getCircleCoords = (graph, range, step) => {
30019
+ if (graph.center != null && graph.radius != null) {
30020
+ return {
30021
+ center: graph.center,
30022
+ radiusPoint: vec.add(graph.center, [graph.radius, 0])
30023
+ };
30024
+ }
30025
+ return {
30026
+ center: [0, 0],
30027
+ radiusPoint: [1, 0]
30028
+ };
30029
+ };
29916
30030
 
29917
30031
  /**
29918
30032
  * determine radius of a circle graph
@@ -29941,6 +30055,7 @@ function CircleGraph(props) {
29941
30055
  onMove: newCenter => dispatch(moveCenter(newCenter))
29942
30056
  }), /*#__PURE__*/React.createElement(StyledMovablePoint, {
29943
30057
  point: radiusPoint,
30058
+ cursor: "ew-resize",
29944
30059
  onMove: newRadiusPoint => {
29945
30060
  dispatch(moveRadiusPoint(newRadiusPoint));
29946
30061
  }
@@ -30098,6 +30213,62 @@ const getQuadraticCoefficients = coords => {
30098
30213
  return [a, b, c];
30099
30214
  };
30100
30215
 
30216
+ function SinusoidGraph(props) {
30217
+ const {
30218
+ dispatch,
30219
+ graphState
30220
+ } = props;
30221
+
30222
+ // Destructure the coordinates from the graph state
30223
+ // Note: The order of the coordinates is important:
30224
+ // The coords[0] is the root and the coords[1] is the first peak
30225
+ const {
30226
+ coords
30227
+ } = graphState;
30228
+
30229
+ // Get the coefficients for calculating the quadratic equation
30230
+ const coeffs = getSinusoidCoefficients(coords);
30231
+ return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Plot$3.OfX, {
30232
+ y: x => computeSine(x, coeffs),
30233
+ color: color.blue
30234
+ }), coords.map((coord, i) => /*#__PURE__*/React.createElement(StyledMovablePoint, {
30235
+ key: "point-" + i,
30236
+ point: coord,
30237
+ onMove: destination => dispatch(movePoint(i, destination))
30238
+ })));
30239
+ }
30240
+
30241
+ // Plot a sinusoid of the form: f(x) = a * sin(b * x - c) + d
30242
+ const computeSine = function computeSine(x,
30243
+ // x-coordinate
30244
+ sinusoidCoefficients) {
30245
+ // Break down the coefficients for the sine function to improve readability
30246
+ const {
30247
+ amplitude: a,
30248
+ angularFrequency: b,
30249
+ phase: c,
30250
+ verticalOffset: d
30251
+ } = sinusoidCoefficients;
30252
+ return a * Math.sin(b * x - c) + d;
30253
+ };
30254
+ const getSinusoidCoefficients = coords => {
30255
+ // It's assumed that p1 is the root and p2 is the first peak
30256
+ const p1 = coords[0];
30257
+ const p2 = coords[1];
30258
+
30259
+ // Resulting coefficients are canonical for this sine curve
30260
+ const amplitude = p2[1] - p1[1];
30261
+ const angularFrequency = Math.PI / (2 * (p2[0] - p1[0]));
30262
+ const phase = p1[0] * angularFrequency;
30263
+ const verticalOffset = p1[1];
30264
+ return {
30265
+ amplitude,
30266
+ angularFrequency,
30267
+ phase,
30268
+ verticalOffset
30269
+ };
30270
+ };
30271
+
30101
30272
  // Universal padding for the axis ticks to ensure that the labels
30102
30273
  // have enough space to render without overlapping the tick lines
30103
30274
  const tickPadding = 5;
@@ -31187,15 +31358,9 @@ function interactiveGraphReducer(state, action) {
31187
31358
  }
31188
31359
  }
31189
31360
  function doMoveControlPoint(state, action) {
31190
- const {
31191
- snapStep,
31192
- range
31193
- } = state;
31194
31361
  switch (state.type) {
31195
31362
  case "segment":
31196
- case "linear":
31197
31363
  case "linear-system":
31198
- case "ray":
31199
31364
  {
31200
31365
  const newCoords = updateAtIndex({
31201
31366
  array: state.coords,
@@ -31203,11 +31368,7 @@ function doMoveControlPoint(state, action) {
31203
31368
  update: tuple => setAtIndex({
31204
31369
  array: tuple,
31205
31370
  index: action.pointIndex,
31206
- newValue: snap(snapStep, bound$1({
31207
- snapStep,
31208
- range,
31209
- point: action.destination
31210
- }))
31371
+ newValue: boundAndSnapToGrid(action.destination, state)
31211
31372
  })
31212
31373
  });
31213
31374
  const coordsToCheck = newCoords[action.itemIndex];
@@ -31219,16 +31380,33 @@ function doMoveControlPoint(state, action) {
31219
31380
  coords: newCoords
31220
31381
  });
31221
31382
  }
31383
+ case "linear":
31384
+ case "ray":
31385
+ {
31386
+ const newCoords = setAtIndex({
31387
+ array: state.coords,
31388
+ index: action.pointIndex,
31389
+ newValue: boundAndSnapToGrid(action.destination, state)
31390
+ });
31391
+ return _extends({}, state, {
31392
+ hasBeenInteractedWith: true,
31393
+ coords: newCoords
31394
+ });
31395
+ }
31222
31396
  case "circle":
31223
31397
  throw new Error("FIXME implement circle reducer");
31224
31398
  case "point":
31225
31399
  case "polygon":
31226
31400
  case "quadratic":
31401
+ case "sinusoid":
31227
31402
  throw new Error(`Don't use moveControlPoint for ${state.type} graphs. Use movePoint instead!`);
31228
31403
  default:
31229
31404
  throw new UnreachableCaseError(state);
31230
31405
  }
31231
31406
  }
31407
+
31408
+ // (LEMS-2050): Update the reducer so that we have a separate action for moving one line
31409
+ // and another action for moving multiple lines
31232
31410
  function doMoveLine(state, action) {
31233
31411
  const {
31234
31412
  snapStep,
@@ -31236,9 +31414,7 @@ function doMoveLine(state, action) {
31236
31414
  } = state;
31237
31415
  switch (state.type) {
31238
31416
  case "segment":
31239
- case "linear":
31240
31417
  case "linear-system":
31241
- case "ray":
31242
31418
  {
31243
31419
  if (action.itemIndex === undefined) {
31244
31420
  throw new Error("Please provide index of line to move");
@@ -31263,6 +31439,21 @@ function doMoveLine(state, action) {
31263
31439
  coords: newCoords
31264
31440
  });
31265
31441
  }
31442
+ case "linear":
31443
+ case "ray":
31444
+ {
31445
+ const currentLine = state.coords;
31446
+ const change = getChange(currentLine, action.delta, {
31447
+ snapStep,
31448
+ range
31449
+ });
31450
+ const newLine = [snap(snapStep, vec.add(currentLine[0], change)), snap(snapStep, vec.add(currentLine[1], change))];
31451
+ return _extends({}, state, {
31452
+ type: state.type,
31453
+ hasBeenInteractedWith: true,
31454
+ coords: newLine
31455
+ });
31456
+ }
31266
31457
  default:
31267
31458
  // The MoveLine action doesn't make sense for other graph types;
31268
31459
  // ignore it if it somehow happens
@@ -31295,6 +31486,20 @@ function doMoveAll(state, action) {
31295
31486
  function doMovePoint(state, action) {
31296
31487
  switch (state.type) {
31297
31488
  case "polygon":
31489
+ const newCoords = setAtIndex({
31490
+ array: state.coords,
31491
+ index: action.index,
31492
+ newValue: boundAndSnapToGrid(action.destination, state)
31493
+ });
31494
+
31495
+ // Reject the move if it would cause the sides of the polygon to cross
31496
+ if (polygonSidesIntersect(newCoords)) {
31497
+ return state;
31498
+ }
31499
+ return _extends({}, state, {
31500
+ hasBeenInteractedWith: true,
31501
+ coords: newCoords
31502
+ });
31298
31503
  case "point":
31299
31504
  {
31300
31505
  return _extends({}, state, {
@@ -31302,11 +31507,26 @@ function doMovePoint(state, action) {
31302
31507
  coords: setAtIndex({
31303
31508
  array: state.coords,
31304
31509
  index: action.index,
31305
- newValue: snap(state.snapStep, bound$1({
31306
- snapStep: state.snapStep,
31307
- range: state.range,
31308
- point: action.destination
31309
- }))
31510
+ newValue: boundAndSnapToGrid(action.destination, state)
31511
+ })
31512
+ });
31513
+ }
31514
+ case "sinusoid":
31515
+ {
31516
+ // First, we need to verify that the new coordinates are not on the same vertical line
31517
+ // If they are, we don't want to move the point
31518
+ const destination = action.destination;
31519
+ const _newCoords = [...state.coords];
31520
+ _newCoords[action.index] = action.destination;
31521
+ if (_newCoords[0][0] === _newCoords[1][0]) {
31522
+ return state;
31523
+ }
31524
+ return _extends({}, state, {
31525
+ hasBeenInteractedWith: true,
31526
+ coords: setAtIndex({
31527
+ array: state.coords,
31528
+ index: action.index,
31529
+ newValue: boundAndSnapToGrid(destination, state)
31310
31530
  })
31311
31531
  });
31312
31532
  }
@@ -31317,11 +31537,7 @@ function doMovePoint(state, action) {
31317
31537
  coords: setAtIndex({
31318
31538
  array: state.coords,
31319
31539
  index: action.index,
31320
- newValue: snap(state.snapStep, bound$1({
31321
- snapStep: state.snapStep,
31322
- range: state.range,
31323
- point: action.destination
31324
- }))
31540
+ newValue: boundAndSnapToGrid(action.destination, state)
31325
31541
  })
31326
31542
  });
31327
31543
  }
@@ -31424,6 +31640,17 @@ const getChange = (coords, delta, constraintOpts) => {
31424
31640
  const dy = clamp(deltaY, minYMove, maxYMove);
31425
31641
  return [dx, dy];
31426
31642
  };
31643
+ function boundAndSnapToGrid(point, {
31644
+ snapStep,
31645
+ range
31646
+ }) {
31647
+ return snap(snapStep, bound$1({
31648
+ snapStep,
31649
+ range,
31650
+ point
31651
+ }));
31652
+ }
31653
+
31427
31654
  // Returns the closest point to the given `point` that is within the graph
31428
31655
  // bounds given in `state`.
31429
31656
  function bound$1({
@@ -31513,8 +31740,12 @@ const renderGraph = props => {
31513
31740
  graphState: state,
31514
31741
  dispatch: dispatch
31515
31742
  });
31516
- case "linear":
31517
31743
  case "linear-system":
31744
+ return /*#__PURE__*/React.createElement(LinearSystemGraph, {
31745
+ graphState: state,
31746
+ dispatch: dispatch
31747
+ });
31748
+ case "linear":
31518
31749
  return /*#__PURE__*/React.createElement(LinearGraph, {
31519
31750
  graphState: state,
31520
31751
  dispatch: dispatch
@@ -31544,6 +31775,11 @@ const renderGraph = props => {
31544
31775
  graphState: state,
31545
31776
  dispatch: dispatch
31546
31777
  });
31778
+ case "sinusoid":
31779
+ return /*#__PURE__*/React.createElement(SinusoidGraph, {
31780
+ graphState: state,
31781
+ dispatch: dispatch
31782
+ });
31547
31783
  default:
31548
31784
  return new UnreachableCaseError(type);
31549
31785
  }
@@ -43758,7 +43994,7 @@ const init = function init(options) {
43758
43994
 
43759
43995
  // This file is processed by a Rollup plugin (replace) to inject the production
43760
43996
  const libName = "@khanacademy/perseus";
43761
- const libVersion = "23.0.0";
43997
+ const libVersion = "23.3.0";
43762
43998
  addLibraryVersionToPerseusDebug(libName, libVersion);
43763
43999
 
43764
44000
  const apiVersion = {
@@ -46759,5 +46995,5 @@ const EditorJsonify = {
46759
46995
  }
46760
46996
  };
46761
46997
 
46762
- export { ApiOptions, ArrowPicker, ArticleRenderer, BaseRadio, Categorizer$1 as Categorizer, changeable as Changeable, ClassNames, ColorPicker, DashPicker, dependencies as Dependencies, EditorJsonify, ElementContainer, Errors, Expression, util as GrapherUtil, Grapher$1 as GrapherWidget, HintRenderer, HintsRenderer, InputNumber$1 as InputNumber, InteractiveGraph$1 as InteractiveGraphWidget, ItemExtras, JiptParagraphs, KhanColors, KhanMath, context as LoadingContext, Log, Matrix$1 as MatrixWidget, MultiItems, NumericInput$1 as NumericInput, PerseusError, PerseusExpressionAnswerFormConsidered, PerseusI18nContext, PerseusI18nContextProvider, PerseusMarkdown, Plotter$1 as PlotterWidget, QUESTION_WIDGETS, Radio, Renderer, ref as ServerItemRenderer, sizingUtils as SizingUtils, Table$1 as TableWidget, Util, denylist as WIDGET_PROP_DENYLIST, WidgetJsonifyDeprecated, widgets$1 as Widgets, addWidget, apiVersion, buildEmptyItemTreeForShape, buildMapper, components, constants, contentHasWidgetType, convertWidgetNameToEnum, displaySigFigs, generateTestCategorizerWidget, generateTestExpressionWidget, generateTestInteractiveGraphWidget, generateTestNumericInputWidget, generateTestPerseusItem, generateTestRadioWidget, genericPerseusItemData, getAnswerFromUserInput, getAnswersFromWidgets, getCorrectAnswerForWidgetId, getValidWidgetIds, getWidgetFromWidgetMap, getWidgetIdsFromContent, getWidgetIdsFromContentByType, getWidgetTypeByWidgetId, getWidgetsFromWidgetMap, getWidgetsMapFromItemData, globalConstants, globalStyles, iconPaths as icons, init, injectWidgets, interactiveSizes$1 as interactiveSizes, isItemRenderableByVersion, isWidgetIdInContent, isWrongAnswerSupported, itemDataVersion, itemToTree, ItemVersion as itemVersion, libVersion, lockedFigureColors, mediaQueries, parsePerseusItem, plotterPlotTypes, preprocessTex, registerAllWidgetsForTesting, shapes, shouldHaveIndividualAnswer, traverse, usePerseusI18n, violatingWidgets, allWidgets as widgets };
46998
+ export { ApiOptions, ArrowPicker, ArticleRenderer, BaseRadio, Categorizer$1 as Categorizer, changeable as Changeable, ClassNames, ColorPicker, DashPicker, dependencies as Dependencies, EditorJsonify, ElementContainer, Errors, Expression, util as GrapherUtil, Grapher$1 as GrapherWidget, HintRenderer, HintsRenderer, InputNumber$1 as InputNumber, InteractiveGraph$1 as InteractiveGraphWidget, ItemExtras, JiptParagraphs, KhanColors, KhanMath, context as LoadingContext, Log, Matrix$1 as MatrixWidget, MultiItems, NumericInput$1 as NumericInput, PerseusError, PerseusExpressionAnswerFormConsidered, PerseusI18nContext, PerseusI18nContextProvider, PerseusMarkdown, Plotter$1 as PlotterWidget, QUESTION_WIDGETS, Radio, Renderer, ref as ServerItemRenderer, sizingUtils as SizingUtils, Table$1 as TableWidget, Util, denylist as WIDGET_PROP_DENYLIST, WidgetJsonifyDeprecated, widgets$1 as Widgets, addWidget, apiVersion, buildEmptyItemTreeForShape, buildMapper, components, constants, contentHasWidgetType, convertWidgetNameToEnum, displaySigFigs, generateTestCategorizerWidget, generateTestExpressionWidget, generateTestInteractiveGraphWidget, generateTestNumericInputWidget, generateTestPerseusItem, generateTestRadioWidget, genericPerseusItemData, getAnswerFromUserInput, getAnswersFromWidgets, getCorrectAnswerForWidgetId, getValidWidgetIds, getWidgetFromWidgetMap, getWidgetIdsFromContent, getWidgetIdsFromContentByType, getWidgetTypeByWidgetId, getWidgetsFromWidgetMap, getWidgetsMapFromItemData, globalConstants, globalStyles, iconPaths as icons, init, injectWidgets, interactiveSizes$1 as interactiveSizes, isItemRenderableByVersion, isWidgetIdInContent, isWrongAnswerSupported, itemDataVersion, itemToTree, ItemVersion as itemVersion, libVersion, lockedEllipseFillStyles, lockedFigureColors, mediaQueries, parsePerseusItem, plotterPlotTypes, preprocessTex, registerAllWidgetsForTesting, shapes, shouldHaveIndividualAnswer, traverse, usePerseusI18n, violatingWidgets, allWidgets as widgets };
46763
46999
  //# sourceMappingURL=index.js.map