@khanacademy/perseus 23.0.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.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, 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';
@@ -17430,16 +17430,8 @@ class SvgImage extends React.Component {
17430
17430
  // Props to send to all images
17431
17431
  const imageProps = {
17432
17432
  alt: this.props.alt,
17433
- title: this.props.title,
17434
- tabIndex: "0"
17433
+ title: this.props.title
17435
17434
  };
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
17435
  const width = this.props.width && this.props.width * this.props.scale;
17444
17436
  const height = this.props.height && this.props.height * this.props.scale;
17445
17437
  const dimensions = {
@@ -27361,7 +27353,7 @@ class ImageWidget extends React.Component {
27361
27353
 
27362
27354
  render() {
27363
27355
  let image;
27364
- let alt;
27356
+ const alt = this.props.caption === this.props.alt ? "" : this.props.alt;
27365
27357
  const {
27366
27358
  apiOptions
27367
27359
  } = this.props;
@@ -27372,29 +27364,7 @@ class ImageWidget extends React.Component {
27372
27364
  setAssetStatus
27373
27365
  }) => /*#__PURE__*/React.createElement(SvgImage, {
27374
27366
  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,
27367
+ alt: alt,
27398
27368
  width: backgroundImage.width,
27399
27369
  height: backgroundImage.height,
27400
27370
  preloader: apiOptions.imagePreloader,
@@ -27410,16 +27380,6 @@ class ImageWidget extends React.Component {
27410
27380
  setAssetStatus: setAssetStatus
27411
27381
  }));
27412
27382
  }
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
27383
 
27424
27384
  // For mobile we combine an image's title and caption.
27425
27385
  if (apiOptions.isMobile) {
@@ -27486,7 +27446,7 @@ class ImageWidget extends React.Component {
27486
27446
  style: {
27487
27447
  maxWidth: backgroundImage.width
27488
27448
  }
27489
- }, image, alt, titleAndCaption);
27449
+ }, image, titleAndCaption);
27490
27450
  }
27491
27451
  let title;
27492
27452
  let caption;
@@ -27518,7 +27478,7 @@ class ImageWidget extends React.Component {
27518
27478
  style: {
27519
27479
  maxWidth: backgroundImage.width
27520
27480
  }
27521
- }, title, image, alt, caption);
27481
+ }, title, image, caption);
27522
27482
  }
27523
27483
  static validate(userInput, rubric) {
27524
27484
  return {
@@ -28673,6 +28633,28 @@ const lockedFigureColors = {
28673
28633
  };
28674
28634
  const plotterPlotTypes = ["bar", "line", "pic", "histogram", "dotplot"];
28675
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
+
28676
28658
  function pathBuilder() {
28677
28659
  return new PathBuilder();
28678
28660
  }
@@ -28942,6 +28924,10 @@ const GraphLockedLayer = props => {
28942
28924
  key: `line-${index}`,
28943
28925
  range: props.range
28944
28926
  }, figure));
28927
+ case "circle":
28928
+ return /*#__PURE__*/React.createElement(LockedCircle, _extends({
28929
+ key: `circle-${index}`
28930
+ }, figure));
28945
28931
  default:
28946
28932
  /**
28947
28933
  * Devlopment-time future-proofing: This should
@@ -29736,8 +29722,12 @@ function initializeGraphState(params) {
29736
29722
  type: graph.type,
29737
29723
  coords: getQuadraticCoords(graph, range, step)
29738
29724
  });
29739
- case "angle":
29740
29725
  case "sinusoid":
29726
+ return _extends({}, shared, {
29727
+ type: graph.type,
29728
+ coords: getSinusoidCoords(graph, range, step)
29729
+ });
29730
+ case "angle":
29741
29731
  throw new Error("Mafs not yet implemented for graph type: " + graph.type);
29742
29732
  default:
29743
29733
  throw new UnreachableCaseError(graph);
@@ -29831,6 +29821,11 @@ function getGradableGraph(state, initialGraph) {
29831
29821
  coords: state.coords
29832
29822
  });
29833
29823
  }
29824
+ if (state.type === "sinusoid" && initialGraph.type === "sinusoid") {
29825
+ return _extends({}, initialGraph, {
29826
+ coords: state.coords
29827
+ });
29828
+ }
29834
29829
  throw new Error("Mafs is not yet implemented for graph type: " + initialGraph.type);
29835
29830
  }
29836
29831
  const getDefaultSegments = ({
@@ -29908,6 +29903,11 @@ const getPolygonCoords = ({
29908
29903
  coords = normalizePoints(range, step, coords, /* noSnap */!snapToGrid);
29909
29904
  return coords;
29910
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
29911
  const getQuadraticCoords = (graph, range, step) => {
29912
29912
  let coords = [[0.25, 0.75], [0.5, 0.25], [0.75, 0.75]];
29913
29913
  coords = normalizePoints(range, step, coords, true);
@@ -30098,6 +30098,62 @@ const getQuadraticCoefficients = coords => {
30098
30098
  return [a, b, c];
30099
30099
  };
30100
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
+
30101
30157
  // Universal padding for the axis ticks to ensure that the labels
30102
30158
  // have enough space to render without overlapping the tick lines
30103
30159
  const tickPadding = 5;
@@ -31224,6 +31280,7 @@ function doMoveControlPoint(state, action) {
31224
31280
  case "point":
31225
31281
  case "polygon":
31226
31282
  case "quadratic":
31283
+ case "sinusoid":
31227
31284
  throw new Error(`Don't use moveControlPoint for ${state.type} graphs. Use movePoint instead!`);
31228
31285
  default:
31229
31286
  throw new UnreachableCaseError(state);
@@ -31310,6 +31367,29 @@ function doMovePoint(state, action) {
31310
31367
  })
31311
31368
  });
31312
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
+ }
31313
31393
  case "quadratic":
31314
31394
  {
31315
31395
  return _extends({}, state, {
@@ -31544,6 +31624,11 @@ const renderGraph = props => {
31544
31624
  graphState: state,
31545
31625
  dispatch: dispatch
31546
31626
  });
31627
+ case "sinusoid":
31628
+ return /*#__PURE__*/React.createElement(SinusoidGraph, {
31629
+ graphState: state,
31630
+ dispatch: dispatch
31631
+ });
31547
31632
  default:
31548
31633
  return new UnreachableCaseError(type);
31549
31634
  }
@@ -43758,7 +43843,7 @@ const init = function init(options) {
43758
43843
 
43759
43844
  // This file is processed by a Rollup plugin (replace) to inject the production
43760
43845
  const libName = "@khanacademy/perseus";
43761
- const libVersion = "23.0.0";
43846
+ const libVersion = "23.1.0";
43762
43847
  addLibraryVersionToPerseusDebug(libName, libVersion);
43763
43848
 
43764
43849
  const apiVersion = {