@khanacademy/perseus 20.3.1 → 20.4.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
@@ -31,7 +31,7 @@ import { OptionItem, SingleSelect, MultiSelect } from '@khanacademy/wonder-block
31
31
  import caretDown from '@phosphor-icons/core/assets/regular/caret-down.svg';
32
32
  import caretUp from '@phosphor-icons/core/assets/regular/caret-up.svg';
33
33
  import debounce$2 from 'lodash.debounce';
34
- import { Point as Point$2, useMovable, useTransformContext, vec, Coordinates, Mafs } from 'mafs';
34
+ import { Point as Point$2, vec, useTransformContext, useMovable, Vector, Coordinates, Mafs } from 'mafs';
35
35
  import { PhosphorIcon } from '@khanacademy/wonder-blocks-icon';
36
36
  import questionIcon from '@phosphor-icons/core/regular/question.svg';
37
37
  import { LabelSmall, LabelMedium } from '@khanacademy/wonder-blocks-typography';
@@ -14388,7 +14388,7 @@ const DEFAULT_STATE$2 = {
14388
14388
  visibleShape: null,
14389
14389
  mouseTarget: null
14390
14390
  };
14391
- const MovableLine$3 = function MovableLine(graphie, movable, options) {
14391
+ const MovableLine$4 = function MovableLine(graphie, movable, options) {
14392
14392
  assert$6(graphie != null);
14393
14393
  assert$6(options != null);
14394
14394
 
@@ -14406,10 +14406,10 @@ const MovableLine$3 = function MovableLine(graphie, movable, options) {
14406
14406
  // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
14407
14407
  this.modify(_.extend({}, DEFAULT_STATE$2, options));
14408
14408
  };
14409
- _.extend(MovableLine$3, MovableLineOptions);
14410
- InteractiveUtil.createGettersFor(MovableLine$3, _.extend({}, DEFAULT_PROPS$2, DEFAULT_STATE$2));
14411
- InteractiveUtil.addMovableHelperMethodsTo(MovableLine$3);
14412
- _.extend(MovableLine$3.prototype, {
14409
+ _.extend(MovableLine$4, MovableLineOptions);
14410
+ InteractiveUtil.createGettersFor(MovableLine$4, _.extend({}, DEFAULT_PROPS$2, DEFAULT_STATE$2));
14411
+ InteractiveUtil.addMovableHelperMethodsTo(MovableLine$4);
14412
+ _.extend(MovableLine$4.prototype, {
14413
14413
  cloneState: function () {
14414
14414
  return _.extend(this.movable.cloneState(), {
14415
14415
  coords: this.coords()
@@ -14986,7 +14986,7 @@ const DEFAULT_STATE$1 = {
14986
14986
  // TODO(benchristel): this is a memory leak. We add items to
14987
14987
  // tooltipResetFunctions, but never remove them.
14988
14988
  const tooltipResetFunctions = [];
14989
- class MovablePoint$5 {
14989
+ class MovablePoint$4 {
14990
14990
  constructor(graphie, movable, options) {
14991
14991
  this.graphie = void 0;
14992
14992
  this.movable = void 0;
@@ -15503,15 +15503,15 @@ class MovablePoint$5 {
15503
15503
  this.movable.toFront();
15504
15504
  }
15505
15505
  }
15506
- MovablePoint$5.add = MovablePointOptions.add;
15507
- MovablePoint$5.modify = MovablePointOptions.modify;
15508
- MovablePoint$5.draw = MovablePointOptions.draw;
15509
- MovablePoint$5.remove = MovablePointOptions.remove;
15510
- MovablePoint$5.onMoveStart = MovablePointOptions.onMoveStart;
15511
- MovablePoint$5.constraints = MovablePointOptions.constraints;
15512
- MovablePoint$5.onMove = MovablePointOptions.onMove;
15513
- MovablePoint$5.onMoveEnd = MovablePointOptions.onMoveEnd;
15514
- MovablePoint$5.onClick = MovablePointOptions.onClick;
15506
+ MovablePoint$4.add = MovablePointOptions.add;
15507
+ MovablePoint$4.modify = MovablePointOptions.modify;
15508
+ MovablePoint$4.draw = MovablePointOptions.draw;
15509
+ MovablePoint$4.remove = MovablePointOptions.remove;
15510
+ MovablePoint$4.onMoveStart = MovablePointOptions.onMoveStart;
15511
+ MovablePoint$4.constraints = MovablePointOptions.constraints;
15512
+ MovablePoint$4.onMove = MovablePointOptions.onMove;
15513
+ MovablePoint$4.onMoveEnd = MovablePointOptions.onMoveEnd;
15514
+ MovablePoint$4.onClick = MovablePointOptions.onClick;
15515
15515
  function getKey(eventName, id) {
15516
15516
  return eventName + ":" + id;
15517
15517
  }
@@ -16105,15 +16105,15 @@ _.extend(MovablePolygon.prototype, {
16105
16105
  });
16106
16106
 
16107
16107
  const Interactive2 = {
16108
- MovablePoint: MovablePoint$5,
16108
+ MovablePoint: MovablePoint$4,
16109
16109
  addMovablePoint: function (graphie, options) {
16110
16110
  const movable = new Movable(graphie, {});
16111
- return new MovablePoint$5(graphie, movable, options);
16111
+ return new MovablePoint$4(graphie, movable, options);
16112
16112
  },
16113
- MovableLine: MovableLine$3,
16113
+ MovableLine: MovableLine$4,
16114
16114
  addMovableLine: function (graphie, options) {
16115
16115
  const movable = new Movable(graphie, {});
16116
- return new MovableLine$3(graphie, movable, options);
16116
+ return new MovableLine$4(graphie, movable, options);
16117
16117
  },
16118
16118
  MovablePolygon: MovablePolygon,
16119
16119
  addMovablePolygon: function (graphie, options) {
@@ -16149,7 +16149,7 @@ const Interactive2 = {
16149
16149
  }
16150
16150
  };
16151
16151
 
16152
- const MovablePoint$4 = GraphieClasses.createClass({
16152
+ const MovablePoint$3 = GraphieClasses.createClass({
16153
16153
  displayName: "MovablePoint",
16154
16154
  movableProps: ["children"],
16155
16155
  _getProps: function () {
@@ -16206,8 +16206,8 @@ const MovablePoint$4 = GraphieClasses.createClass({
16206
16206
  });
16207
16207
 
16208
16208
  // Include helper methods, such as MovablePoint.constrain.snap()
16209
- _.extend(MovablePoint$4, Interactive2.MovablePoint);
16210
- const MovableLine$2 = GraphieClasses.createClass({
16209
+ _.extend(MovablePoint$3, Interactive2.MovablePoint);
16210
+ const MovableLine$3 = GraphieClasses.createClass({
16211
16211
  displayName: "MovableLine",
16212
16212
  movableProps: ["children"],
16213
16213
  add: function (graphie) {
@@ -16235,7 +16235,7 @@ const MovableLine$2 = GraphieClasses.createClass({
16235
16235
  });
16236
16236
 
16237
16237
  // Include helper methods, such as MovableLine.constrain.snap()
16238
- _.extend(MovableLine$2, Interactive2.MovableLine);
16238
+ _.extend(MovableLine$3, Interactive2.MovableLine);
16239
16239
  const Label$1 = GraphieClasses.createSimpleClass((graphie, props) => {
16240
16240
  let coord = props.coord;
16241
16241
  if (props.unscaled) {
@@ -16375,8 +16375,8 @@ var Movables = {
16375
16375
  Circle: Circle,
16376
16376
  Label: Label$1,
16377
16377
  Line: Line$2,
16378
- MovableLine: MovableLine$2,
16379
- MovablePoint: MovablePoint$4,
16378
+ MovableLine: MovableLine$3,
16379
+ MovablePoint: MovablePoint$3,
16380
16380
  Parabola: Parabola,
16381
16381
  Path: Path,
16382
16382
  Plot: Plot$2,
@@ -24856,6 +24856,12 @@ var Dropdown$1 = {
24856
24856
 
24857
24857
  // transform = _.identity
24858
24858
 
24859
+ function mediaQueryIsMatched(mediaQuery) {
24860
+ if (typeof window.matchMedia !== "function") {
24861
+ return false;
24862
+ }
24863
+ return window.matchMedia(mediaQuery).matches;
24864
+ }
24859
24865
  class Explanation extends React.Component {
24860
24866
  constructor(..._args) {
24861
24867
  super(..._args);
@@ -24889,7 +24895,7 @@ class Explanation extends React.Component {
24889
24895
  render() {
24890
24896
  const promptText = this.state.expanded ? this.props.hidePrompt : this.props.showPrompt;
24891
24897
  const caretIcon = this.state.expanded ? caretUp : caretDown;
24892
- const allowTransition = window.matchMedia("(prefers-reduced-motion: no-preference)").matches;
24898
+ const allowTransition = mediaQueryIsMatched("(prefers-reduced-motion: no-preference)");
24893
24899
 
24894
24900
  // Special styling is needed to fit the button in a block of text without throwing off the line spacing.
24895
24901
  // While the button is not normally included in a block of text, it needs to be able to accommodate such a case.
@@ -24900,7 +24906,7 @@ class Explanation extends React.Component {
24900
24906
  };
24901
24907
  const contentStyling = [styles$p.content, this.state.expanded ? styles$p.contentExpanded : styles$p.contentCollapsed, allowTransition && (this.state.expanded ? styles$p.transitionExpanded : styles$p.transitionCollapsed)];
24902
24908
  return /*#__PURE__*/React.createElement(UniqueIDProvider, {
24903
- mockOnFirstRender: false,
24909
+ mockOnFirstRender: true,
24904
24910
  scope: "explanation-widget"
24905
24911
  }, ids => /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Button, {
24906
24912
  "aria-expanded": this.state.expanded,
@@ -26288,9 +26294,9 @@ var util = /*#__PURE__*/Object.freeze({
26288
26294
  });
26289
26295
 
26290
26296
  // @ts-expect-error - TS2339 - Property 'MovablePoint' does not exist on type 'typeof Graphie'.
26291
- const MovablePoint$3 = Graphie.MovablePoint;
26297
+ const MovablePoint$2 = Graphie.MovablePoint;
26292
26298
  // @ts-expect-error - TS2339 - Property 'MovableLine' does not exist on type 'typeof Graphie'.
26293
- const MovableLine$1 = Graphie.MovableLine;
26299
+ const MovableLine$2 = Graphie.MovableLine;
26294
26300
  function isFlipped(newCoord, oldCoord, line) {
26295
26301
  const CCW = (a, b, c) => {
26296
26302
  return (b[0] - a[0]) * (c[1] - a[1]) - (c[0] - a[0]) * (b[1] - a[1]);
@@ -26345,7 +26351,7 @@ class FunctionGrapher extends React.Component {
26345
26351
  const dashed = {
26346
26352
  strokeDasharray: "- "
26347
26353
  };
26348
- return asymptote && /*#__PURE__*/React.createElement(MovableLine$1, {
26354
+ return asymptote && /*#__PURE__*/React.createElement(MovableLine$2, {
26349
26355
  onMove: (newCoord, oldCoord) => {
26350
26356
  // Calculate and apply displacement
26351
26357
  const delta = vector$1.subtract(newCoord, oldCoord);
@@ -26370,7 +26376,7 @@ class FunctionGrapher extends React.Component {
26370
26376
  }],
26371
26377
  normalStyle: dashed,
26372
26378
  highlightStyle: dashed
26373
- }, _.map(asymptote, (coord, i) => /*#__PURE__*/React.createElement(MovablePoint$3, {
26379
+ }, _.map(asymptote, (coord, i) => /*#__PURE__*/React.createElement(MovablePoint$2, {
26374
26380
  key: `asymptoteCoord-${i}`,
26375
26381
  coord: coord,
26376
26382
  static: true,
@@ -26385,7 +26391,7 @@ class FunctionGrapher extends React.Component {
26385
26391
  }
26386
26392
  render() {
26387
26393
  const pointForCoord = (coord, i) => {
26388
- return /*#__PURE__*/React.createElement(MovablePoint$3, {
26394
+ return /*#__PURE__*/React.createElement(MovablePoint$2, {
26389
26395
  key: i,
26390
26396
  coord: coord,
26391
26397
  static: this.props.static,
@@ -27219,9 +27225,9 @@ const Label = Graphie.Label;
27219
27225
  // @ts-expect-error - TS2339 - Property 'Line' does not exist on type 'typeof Graphie'.
27220
27226
  const Line$1 = Graphie.Line;
27221
27227
  // @ts-expect-error - TS2339 - Property 'MovablePoint' does not exist on type 'typeof Graphie'.
27222
- const MovablePoint$2 = Graphie.MovablePoint;
27228
+ const MovablePoint$1 = Graphie.MovablePoint;
27223
27229
  // @ts-expect-error - TS2339 - Property 'MovableLine' does not exist on type 'typeof Graphie'.
27224
- const MovableLine = Graphie.MovableLine;
27230
+ const MovableLine$1 = Graphie.MovableLine;
27225
27231
  // @ts-expect-error - TS2339 - Property 'Plot' does not exist on type 'typeof Graphie'.
27226
27232
  const Plot = Graphie.Plot;
27227
27233
  // @ts-expect-error - TS2339 - Property 'PlotParametric' does not exist on type 'typeof Graphie'.
@@ -27468,7 +27474,7 @@ class Interaction extends React.Component {
27468
27474
  return [coordX, coordY];
27469
27475
  }];
27470
27476
  if (element.options.constraint === "snap") {
27471
- constraints.push(MovablePoint$2.constraints.snap(element.options.snap));
27477
+ constraints.push(MovablePoint$1.constraints.snap(element.options.snap));
27472
27478
  } else if (element.options.constraint === "x") {
27473
27479
  constraints.push(coord => {
27474
27480
  return [
@@ -27490,7 +27496,7 @@ class Interaction extends React.Component {
27490
27496
  // TODO(eater): foo_[xyz] are hacky non-props to
27491
27497
  // get the component to update when constraints
27492
27498
  // change
27493
- return /*#__PURE__*/React.createElement(MovablePoint$2, {
27499
+ return /*#__PURE__*/React.createElement(MovablePoint$1, {
27494
27500
  key: element.key,
27495
27501
  coord: [
27496
27502
  // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
@@ -27525,7 +27531,7 @@ class Interaction extends React.Component {
27525
27531
  return [coordX, coordY];
27526
27532
  }];
27527
27533
  if (element.options.constraint === "snap") {
27528
- constraints.push(MovablePoint$2.constraints.snap(element.options.snap));
27534
+ constraints.push(MovablePoint$1.constraints.snap(element.options.snap));
27529
27535
  } else if (element.options.constraint === "x") {
27530
27536
  constraints.push(coord => {
27531
27537
  return [
@@ -27553,7 +27559,7 @@ class Interaction extends React.Component {
27553
27559
  this.state.variables["x_" + element.options.endSubscript],
27554
27560
  // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
27555
27561
  this.state.variables["y_" + element.options.endSubscript]];
27556
- return /*#__PURE__*/React.createElement(MovableLine, {
27562
+ return /*#__PURE__*/React.createElement(MovableLine$1, {
27557
27563
  key: element.key,
27558
27564
  constraints: constraints
27559
27565
  // eslint-disable-next-line react/jsx-no-bind
@@ -27566,14 +27572,14 @@ class Interaction extends React.Component {
27566
27572
  foo_x: element.options.constraint,
27567
27573
  foo_y: element.options.constraintFn,
27568
27574
  foo_z: element.options.snap
27569
- }, /*#__PURE__*/React.createElement(MovablePoint$2, {
27575
+ }, /*#__PURE__*/React.createElement(MovablePoint$1, {
27570
27576
  coord: start,
27571
27577
  static: true,
27572
27578
  normalStyle: {
27573
27579
  stroke: "none",
27574
27580
  fill: "none"
27575
27581
  }
27576
- }), /*#__PURE__*/React.createElement(MovablePoint$2, {
27582
+ }), /*#__PURE__*/React.createElement(MovablePoint$1, {
27577
27583
  coord: end,
27578
27584
  static: true,
27579
27585
  normalStyle: {
@@ -28157,20 +28163,142 @@ function moveControlPoint(objectIndex, pointIndex, destination) {
28157
28163
  destination
28158
28164
  };
28159
28165
  }
28160
- const MOVE_SEGMENT = "move-segment";
28161
- function moveSegment(segmentIndex, delta) {
28166
+ const MOVE_LINE = "move-line";
28167
+ function moveSegment(lineIndex, delta) {
28162
28168
  return {
28163
- type: MOVE_SEGMENT,
28164
- segmentIndex,
28169
+ type: MOVE_LINE,
28170
+ lineIndex,
28165
28171
  delta
28166
28172
  };
28167
28173
  }
28168
28174
 
28169
- const MovablePoint$1 = props => {
28175
+ const defaultStroke = "var(--movable-line-stroke-color)";
28176
+ const MovableLine = props => {
28177
+ const {
28178
+ start,
28179
+ end,
28180
+ onMove,
28181
+ extend,
28182
+ stroke = defaultStroke
28183
+ } = props;
28184
+ const midpoint = vec.midpoint(start, end);
28185
+ const {
28186
+ viewTransform,
28187
+ userTransform
28188
+ } = useTransformContext();
28189
+ const transformToPx = vec.matrixMult(viewTransform, userTransform);
28190
+ const startPtPx = vec.transform(start, transformToPx);
28191
+ const endPtPx = vec.transform(end, transformToPx);
28192
+ let startExtend = undefined;
28193
+ let endExtend = undefined;
28194
+ if (extend) {
28195
+ startExtend = extend.start ? getExtensionCoords(end, start, extend.range) : undefined;
28196
+ endExtend = extend.end ? getExtensionCoords(start, end, extend.range) : undefined;
28197
+ }
28198
+ const line = useRef(null);
28199
+ const {
28200
+ dragging
28201
+ } = useMovable({
28202
+ gestureTarget: line,
28203
+ point: midpoint,
28204
+ onMove: newPoint => {
28205
+ onMove(vec.sub(newPoint, midpoint));
28206
+ },
28207
+ constrain: p => p
28208
+ });
28209
+ return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("g", {
28210
+ ref: line,
28211
+ tabIndex: 0,
28212
+ className: "movable-line",
28213
+ style: {
28214
+ cursor: dragging ? "grabbing" : "grab"
28215
+ }
28216
+ }, /*#__PURE__*/React.createElement(SVGLine, {
28217
+ start: startPtPx,
28218
+ end: endPtPx,
28219
+ style: {
28220
+ stroke: "transparent",
28221
+ strokeWidth: 44
28222
+ }
28223
+ }), /*#__PURE__*/React.createElement(SVGLine, {
28224
+ start: startPtPx,
28225
+ end: endPtPx,
28226
+ style: {
28227
+ stroke,
28228
+ strokeWidth: "var(--movable-line-stroke-weight)"
28229
+ },
28230
+ dragging: dragging
28231
+ })), startExtend && /*#__PURE__*/React.createElement(Vector, {
28232
+ tail: start,
28233
+ tip: startExtend,
28234
+ color: stroke
28235
+ }), endExtend && /*#__PURE__*/React.createElement(Vector, {
28236
+ tail: end,
28237
+ tip: endExtend,
28238
+ color: stroke
28239
+ }));
28240
+ };
28241
+ function SVGLine(props) {
28242
+ const {
28243
+ start,
28244
+ end,
28245
+ style,
28246
+ dragging
28247
+ } = props;
28248
+ return /*#__PURE__*/React.createElement("line", {
28249
+ x1: start[0],
28250
+ y1: start[1],
28251
+ x2: end[0],
28252
+ y2: end[1],
28253
+ style: style,
28254
+ className: dragging ? "movable-line-dragging" : undefined
28255
+ });
28256
+ }
28257
+
28258
+ /**
28259
+ * Given two points, find the tips that extends through the points to the edge of the range.
28260
+ * @param collinearPoint - The point that the line passes through. Needed to establish slope.
28261
+ * @param extendFrom - The point that the line extends from to the edge of the graph.
28262
+ */
28263
+ const getExtensionCoords = (collinearPoint, extendFrom, range) => {
28264
+ // edges of the graph
28265
+ const [[xMin, xMax], [yMin, yMax]] = range;
28266
+ const [aX, aY] = collinearPoint;
28267
+ const [bX, bY] = extendFrom;
28268
+ const yDiff = bY - aY;
28269
+ const xDiff = bX - aX;
28270
+ const slope = yDiff / xDiff;
28271
+ const yAtXMin = slope * (xMin - aX) + aY;
28272
+ const yAtXMax = slope * (xMax - aX) + aY;
28273
+ const xAtYMin = (yMin - aY) / slope + aX;
28274
+ const xAtYMax = (yMax - aY) / slope + aX;
28275
+
28276
+ // clock analogy to describe quadrants
28277
+ switch (true) {
28278
+ // 12 o'clock to 2:59
28279
+ case yDiff > 0 && xDiff >= 0:
28280
+ return xAtYMax > xMax ? [xMax, yAtXMax] : [xAtYMax, yMax];
28281
+ // 3 o'clock to 5:59
28282
+ case yDiff <= 0 && xDiff > 0:
28283
+ // xAtYMin evaluates to -Infinity here, so we use absolute value
28284
+ return Math.abs(xAtYMin) > xMax ? [xMax, yAtXMax] : [xAtYMin, yMin];
28285
+ // 9 o'clock to 11:59
28286
+ case yDiff >= 0 && xDiff < 0:
28287
+ return xAtYMax < xMin ? [xMin, yAtXMin] : [xAtYMax, yMax];
28288
+ // 6 o'clock to 8:59
28289
+ case yDiff < 0 && xDiff <= 0:
28290
+ return xAtYMin < xMin ? [xMin, yAtXMin] : [xAtYMin, yMin];
28291
+ default:
28292
+ return [xMax, yAtXMax];
28293
+ }
28294
+ };
28295
+
28296
+ const StyledMovablePoint = props => {
28170
28297
  const hitboxRef = useRef(null);
28171
28298
  const {
28172
28299
  point,
28173
- onMove
28300
+ onMove,
28301
+ color = Color.blue
28174
28302
  } = props;
28175
28303
  const {
28176
28304
  dragging
@@ -28194,11 +28322,11 @@ const MovablePoint$1 = props => {
28194
28322
  cursor: dragging ? "grabbing" : "grab",
28195
28323
  touchAction: "none",
28196
28324
  outline: "none",
28197
- "--movable-point-color": Color.blue
28325
+ "--movable-point-color": color
28198
28326
  }
28199
28327
  }, /*#__PURE__*/React.createElement("circle", {
28200
28328
  className: "movable-point-hitbox",
28201
- r: 30,
28329
+ r: 22,
28202
28330
  cx: x,
28203
28331
  cy: y
28204
28332
  }), /*#__PURE__*/React.createElement("circle", {
@@ -28214,7 +28342,7 @@ const MovablePoint$1 = props => {
28214
28342
  cx: x,
28215
28343
  cy: y,
28216
28344
  style: {
28217
- fill: Color.blue
28345
+ fill: color
28218
28346
  }
28219
28347
  }));
28220
28348
  };
@@ -28224,16 +28352,16 @@ const SegmentGraph = props => {
28224
28352
  dispatch
28225
28353
  } = props;
28226
28354
  const {
28227
- segments,
28355
+ coords: segments,
28228
28356
  snapStep,
28229
28357
  range
28230
28358
  } = props.graphState;
28231
- return /*#__PURE__*/React.createElement(React.Fragment, null, segments.map((segment, i) => /*#__PURE__*/React.createElement(SegmentView, {
28359
+ return /*#__PURE__*/React.createElement(React.Fragment, null, segments == null ? void 0 : segments.map((segment, i) => /*#__PURE__*/React.createElement(SegmentView, {
28232
28360
  key: i,
28233
- segment: segment,
28361
+ collinearPair: segment,
28234
28362
  snaps: snapStep,
28235
28363
  range: range,
28236
- onMoveSegment: delta => {
28364
+ onMoveLine: delta => {
28237
28365
  dispatch(moveSegment(i, delta));
28238
28366
  },
28239
28367
  onMovePoint: (endpointIndex, destination) => dispatch(moveControlPoint(i, endpointIndex, destination)),
@@ -28242,75 +28370,83 @@ const SegmentGraph = props => {
28242
28370
  };
28243
28371
  const SegmentView = props => {
28244
28372
  const {
28245
- onMoveSegment,
28246
- segment: [pt1, pt2]
28373
+ onMoveLine: onMoveSegment,
28374
+ collinearPair: [start, end]
28247
28375
  } = props;
28248
- const midpoint = vec.midpoint(pt1, pt2);
28249
- const segment = useRef(null);
28250
- const {
28251
- dragging: isDraggingSegment
28252
- } = useMovable({
28253
- gestureTarget: segment,
28254
- point: midpoint,
28255
- onMove: newPoint => {
28256
- onMoveSegment(vec.sub(newPoint, midpoint));
28257
- },
28258
- constrain: p => p
28259
- });
28260
- const {
28261
- viewTransform,
28262
- userTransform
28263
- } = useTransformContext();
28264
- const transformToPx = vec.matrixMult(viewTransform, userTransform);
28265
- const pt1Px = vec.transform(pt1, transformToPx);
28266
- const pt2Px = vec.transform(pt2, transformToPx);
28267
- return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("g", {
28268
- ref: segment,
28269
- tabIndex: 0,
28270
- className: "movable-segment",
28271
- style: {
28272
- cursor: isDraggingSegment ? "grabbing" : "grab"
28273
- }
28274
- }, /*#__PURE__*/React.createElement(SVGLine, {
28275
- start: pt1Px,
28276
- end: pt2Px,
28277
- style: {
28278
- stroke: "transparent",
28279
- strokeWidth: 30
28280
- }
28281
- }), /*#__PURE__*/React.createElement(SVGLine, {
28282
- start: pt1Px,
28283
- end: pt2Px,
28284
- style: {
28285
- stroke: "var(--mafs-segment-stroke-color)",
28286
- strokeWidth: "var(--mafs-segment-stroke-weight)"
28287
- }
28288
- })), /*#__PURE__*/React.createElement(MovablePoint$1, {
28289
- point: pt1,
28376
+ return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(MovableLine, {
28377
+ start: start,
28378
+ end: end,
28379
+ onMove: onMoveSegment
28380
+ }), /*#__PURE__*/React.createElement(StyledMovablePoint, {
28381
+ point: start,
28290
28382
  onMove: newPoint => {
28291
28383
  props.onMovePoint(0, newPoint);
28292
28384
  }
28293
- }), /*#__PURE__*/React.createElement(MovablePoint$1, {
28294
- point: pt2,
28385
+ }), /*#__PURE__*/React.createElement(StyledMovablePoint, {
28386
+ point: end,
28295
28387
  onMove: newPoint => {
28296
28388
  props.onMovePoint(1, newPoint);
28297
28389
  }
28298
28390
  }));
28299
28391
  };
28300
- function SVGLine(props) {
28392
+
28393
+ const LinearGraph = props => {
28301
28394
  const {
28302
- start,
28303
- end,
28304
- style
28395
+ dispatch
28305
28396
  } = props;
28306
- return /*#__PURE__*/React.createElement("line", {
28307
- x1: start[0],
28308
- y1: start[1],
28309
- x2: end[0],
28310
- y2: end[1],
28311
- style: style
28312
- });
28313
- }
28397
+ const {
28398
+ coords: lines,
28399
+ snapStep,
28400
+ range,
28401
+ type
28402
+ } = props.graphState;
28403
+ const colors = ["var(--movable-line-stroke-color)", "var(--mafs-violet)"];
28404
+ return /*#__PURE__*/React.createElement(React.Fragment, null, lines == null ? void 0 : lines.map((line, i) => /*#__PURE__*/React.createElement(LineView, {
28405
+ key: i,
28406
+ collinearPair: line,
28407
+ snaps: snapStep,
28408
+ range: range,
28409
+ onMoveLine: delta => {
28410
+ dispatch(moveSegment(i, delta));
28411
+ },
28412
+ onMovePoint: (endpointIndex, destination) => dispatch(moveControlPoint(i, endpointIndex, destination))
28413
+ // "linear" or "linear-system" + index
28414
+ ,
28415
+ "data-testid": type + i,
28416
+ stroke: colors[i]
28417
+ })));
28418
+ };
28419
+ const LineView = props => {
28420
+ const {
28421
+ onMoveLine: onMoveSegment,
28422
+ collinearPair: [start, end],
28423
+ range,
28424
+ stroke
28425
+ } = props;
28426
+ return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(MovableLine, {
28427
+ start: start,
28428
+ end: end,
28429
+ onMove: onMoveSegment,
28430
+ extend: {
28431
+ start: true,
28432
+ end: true,
28433
+ range
28434
+ },
28435
+ stroke: stroke
28436
+ }), /*#__PURE__*/React.createElement(StyledMovablePoint, {
28437
+ point: start,
28438
+ onMove: newPoint => {
28439
+ props.onMovePoint(0, newPoint);
28440
+ },
28441
+ color: stroke
28442
+ }), /*#__PURE__*/React.createElement(StyledMovablePoint, {
28443
+ point: end,
28444
+ onMove: newPoint => {
28445
+ props.onMovePoint(1, newPoint);
28446
+ },
28447
+ color: stroke
28448
+ }));
28449
+ };
28314
28450
 
28315
28451
  const renderLineLabel = (n, [min, max]) => n !== -1 && n !== min && n !== max;
28316
28452
  const axisOptions = (props, index) => ({
@@ -28326,33 +28462,49 @@ const Grid = props => props.markings === "none" ? null : /*#__PURE__*/React.crea
28326
28462
 
28327
28463
  function interactiveGraphReducer(state, action) {
28328
28464
  switch (action.type) {
28329
- case "move-control-point":
28465
+ case MOVE_CONTROL_POINT:
28330
28466
  {
28331
- const newSegments = updateAtIndex(state.segments, action.objectIndex, segment => setAtIndex(segment, action.pointIndex, snap(state, bound$1(state, action.destination))));
28332
- if (!validSegments(newSegments)) {
28467
+ const newCoords = updateAtIndex({
28468
+ array: state.coords,
28469
+ index: action.objectIndex,
28470
+ update: tuple => setAtIndex({
28471
+ array: tuple,
28472
+ index: action.pointIndex,
28473
+ newValue: snap(state, bound$1(state, action.destination))
28474
+ })
28475
+ });
28476
+ if (!validSegments(newCoords)) {
28333
28477
  return state;
28334
28478
  }
28335
28479
  return _extends({}, state, {
28336
28480
  hasBeenInteractedWith: true,
28337
- segments: newSegments
28481
+ coords: newCoords
28338
28482
  });
28339
28483
  }
28340
- case "move-segment":
28484
+ case MOVE_LINE:
28341
28485
  {
28342
- const oldSegment = state.segments[action.segmentIndex];
28343
- const maxMoves = oldSegment.map(point => maxMove(state, point));
28344
- const minMoves = oldSegment.map(point => minMove(state, point));
28486
+ var _state$coords;
28487
+ const currentLine = (_state$coords = state.coords) == null ? void 0 : _state$coords[action.lineIndex];
28488
+ if (!currentLine) {
28489
+ throw new Error("No line to move");
28490
+ }
28491
+ const maxMoves = currentLine.map(point => maxMove(state, point));
28492
+ const minMoves = currentLine.map(point => minMove(state, point));
28345
28493
  const maxXMove = Math.min(...maxMoves.map(move => move[0]));
28346
28494
  const maxYMove = Math.min(...maxMoves.map(move => move[1]));
28347
28495
  const minXMove = Math.max(...minMoves.map(move => move[0]));
28348
28496
  const minYMove = Math.max(...minMoves.map(move => move[1]));
28349
28497
  const dx = clamp(action.delta[0], minXMove, maxXMove);
28350
28498
  const dy = clamp(action.delta[1], minYMove, maxYMove);
28351
- const newSegment = oldSegment.map(point => snap(state, vector$1.add(point, [dx, dy])));
28352
- const newSegments = setAtIndex(state.segments, action.segmentIndex, newSegment);
28499
+ const newValue = [snap(state, vec.add(currentLine[0], [dx, dy])), snap(state, vec.add(currentLine[1], [dx, dy]))];
28500
+ const newLine = setAtIndex({
28501
+ array: state.coords,
28502
+ index: action.lineIndex,
28503
+ newValue
28504
+ });
28353
28505
  return _extends({}, state, {
28354
28506
  hasBeenInteractedWith: true,
28355
- segments: newSegments
28507
+ coords: newLine
28356
28508
  });
28357
28509
  }
28358
28510
  default:
@@ -28398,12 +28550,28 @@ function clamp(value, min, max) {
28398
28550
  function validSegments(segments) {
28399
28551
  return segments.every(([start, end]) => !vector$1.equal(start, end));
28400
28552
  }
28401
- function updateAtIndex(array, index, update) {
28402
- return setAtIndex(array, index, update(array[index]));
28553
+ function updateAtIndex(args) {
28554
+ const {
28555
+ array = [],
28556
+ index,
28557
+ update
28558
+ } = args;
28559
+ const newValue = update(array[index]);
28560
+ return setAtIndex({
28561
+ array,
28562
+ index,
28563
+ newValue
28564
+ });
28403
28565
  }
28404
- function setAtIndex(array, index, newValue) {
28405
- const copy = [...array];
28566
+ function setAtIndex(args) {
28567
+ const {
28568
+ array,
28569
+ index,
28570
+ newValue
28571
+ } = args;
28572
+ const copy = [...(array || [])];
28406
28573
  copy[index] = newValue;
28574
+ // restoring readonly to array
28407
28575
  return copy;
28408
28576
  }
28409
28577
 
@@ -28441,12 +28609,24 @@ function initializeGraphState(params) {
28441
28609
  hasBeenInteractedWith: false,
28442
28610
  range,
28443
28611
  snapStep,
28444
- segments: getDefaultSegments({
28612
+ coords: getDefaultSegments({
28445
28613
  graph,
28446
28614
  step,
28447
28615
  range
28448
28616
  })
28449
28617
  };
28618
+ // Ray can also fall through into this case-- same type, same default coords
28619
+ case "linear":
28620
+ case "linear-system":
28621
+ return {
28622
+ type: graph.type,
28623
+ hasBeenInteractedWith: false,
28624
+ range,
28625
+ snapStep,
28626
+ // A linear graph has a single tuple of points, while a linear
28627
+ // system has two tuples of points.
28628
+ coords: getLineCoords(graph, range, step)
28629
+ };
28450
28630
  }
28451
28631
  throw new Error("Mafs not yet implemented for graph type: " + graph.type);
28452
28632
  }
@@ -28475,6 +28655,38 @@ const getDefaultSegments = props => {
28475
28655
  });
28476
28656
  };
28477
28657
 
28658
+ // TS v4 doesn't narrow return types, while v5 does.
28659
+ // Instead of updating to v5, using generic type to relate input and output types.
28660
+ function getGradableGraph(state, initialGraph) {
28661
+ var _state$coords;
28662
+ if (!state.hasBeenInteractedWith) {
28663
+ return initialGraph;
28664
+ }
28665
+ switch (true) {
28666
+ // coords: Array of CollinearTuple
28667
+ case state.type === "linear-system" && initialGraph.type === "linear-system":
28668
+ case state.type === "segment" && initialGraph.type === "segment":
28669
+ return _extends({}, initialGraph, {
28670
+ coords: state.coords
28671
+ });
28672
+ // RAY has the same type as below; can fall through
28673
+ // coords: CollinearTuple
28674
+ case state.type === "linear" && initialGraph.type === "linear":
28675
+ return _extends({}, initialGraph, {
28676
+ coords: (_state$coords = state.coords) == null ? void 0 : _state$coords[0]
28677
+ });
28678
+ }
28679
+ throw new Error("Mafs is not yet implemented for graph type: " + initialGraph.type);
28680
+ }
28681
+ const defaultLinearCoords = [[[0.25, 0.75], [0.75, 0.75]], [[0.25, 0.25], [0.75, 0.25]]];
28682
+ const getLineCoords = (graph, range, step) => {
28683
+ var _graph$coords;
28684
+ return (
28685
+ // Return two lines for a linear system, one for a ray or linear
28686
+ ((_graph$coords = graph.coords) != null ? _graph$coords : graph.type === "linear-system") ? defaultLinearCoords.map(collinear => normalizePoints(range, step, collinear)) : [normalizePoints(range, step, defaultLinearCoords[0])]
28687
+ );
28688
+ };
28689
+
28478
28690
  // Displays a stylized open eye: lint warnings are visible
28479
28691
  const VisibleIcon = () => /*#__PURE__*/React.createElement("svg", {
28480
28692
  width: "24",
@@ -29172,27 +29384,25 @@ const renderGraph = props => {
29172
29384
  state,
29173
29385
  dispatch
29174
29386
  } = props;
29175
- switch (state.type) {
29387
+ const {
29388
+ type
29389
+ } = state;
29390
+ switch (type) {
29176
29391
  case "segment":
29177
29392
  return /*#__PURE__*/React.createElement(SegmentGraph, {
29178
29393
  graphState: state,
29179
29394
  dispatch: dispatch
29180
29395
  });
29181
- }
29182
- throw new Error("Mafs is not yet implemented for graph type: " + state.type);
29183
- };
29184
- function getGradableGraph(state, initialGraph) {
29185
- if (!state.hasBeenInteractedWith) {
29186
- return initialGraph;
29187
- }
29188
- switch (initialGraph.type) {
29189
- case "segment":
29190
- return _extends({}, initialGraph, {
29191
- coords: state.segments
29396
+ case "linear":
29397
+ case "linear-system":
29398
+ return /*#__PURE__*/React.createElement(LinearGraph, {
29399
+ graphState: state,
29400
+ dispatch: dispatch
29192
29401
  });
29402
+ default:
29403
+ return new UnreachableCaseError(type);
29193
29404
  }
29194
- throw new Error("Mafs is not yet implemented for graph type: " + initialGraph.type);
29195
- }
29405
+ };
29196
29406
  const MafsGraph = /*#__PURE__*/React.forwardRef((props, ref) => {
29197
29407
  var _props$backgroundImag, _props$backgroundImag2, _props$backgroundImag3, _props$backgroundImag4;
29198
29408
  const [width, height] = props.box;
@@ -41234,7 +41444,7 @@ const init = function init(options) {
41234
41444
 
41235
41445
  // This file is processed by a Rollup plugin (replace) to inject the production
41236
41446
  const libName = "@khanacademy/perseus";
41237
- const libVersion = "20.3.1";
41447
+ const libVersion = "20.4.0";
41238
41448
  addLibraryVersionToPerseusDebug(libName, libVersion);
41239
41449
 
41240
41450
  const apiVersion = {
@@ -42259,8 +42469,9 @@ var constants = /*#__PURE__*/Object.freeze({
42259
42469
  // TODO(FEI-4010): Remove `Perseus` prefix for all types here
42260
42470
  // TODO(FEI-4011): Use types generated by https://github.com/jaredly/generate-perseus-flowtypes
42261
42471
 
42472
+ // Range is replaced within this file with Interval, but it is used elsewhere
42473
+ // and exported from the package, so we need to keep it around.
42262
42474
  // TODO(FEI-5054): Figure out how to get global .d.ts files working with monorepos
42263
-
42264
42475
  const ItemExtras = [
42265
42476
  // The user might benefit from using a Scientific Calculator. Provided on Khan Academy when true
42266
42477
  "calculator",