@khanacademy/perseus 22.3.0 → 22.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -27,8 +27,8 @@ var Button = require('@khanacademy/wonder-blocks-button');
27
27
  var wonderBlocksLayout = require('@khanacademy/wonder-blocks-layout');
28
28
  var Banner = require('@khanacademy/wonder-blocks-banner');
29
29
  var wonderBlocksDropdown = require('@khanacademy/wonder-blocks-dropdown');
30
- var caretDown = require('@phosphor-icons/core/assets/regular/caret-down.svg');
31
- var caretUp = require('@phosphor-icons/core/assets/regular/caret-up.svg');
30
+ var caretDown = require('@phosphor-icons/core/regular/caret-down.svg');
31
+ var caretUp = require('@phosphor-icons/core/regular/caret-up.svg');
32
32
  var debounce$1 = require('lodash.debounce');
33
33
  var mafs = require('mafs');
34
34
  var wonderBlocksIcon = require('@khanacademy/wonder-blocks-icon');
@@ -477,7 +477,7 @@ class InnerMathInput extends React__namespace.Component {
477
477
  locale
478
478
  } = this.context;
479
479
  // Initialize MathQuill.MathField instance
480
- this.__mathField = mathInput.createMathField(this.__mathFieldWrapperRef, this.props.mathInputStrings, baseConfig => ({
480
+ this.__mathField = mathInput.createMathField(this.__mathFieldWrapperRef, locale, this.props.mathInputStrings, baseConfig => ({
481
481
  ...baseConfig,
482
482
  handlers: {
483
483
  edit: debounce(mathField => {
@@ -29012,6 +29012,7 @@ const arrowPath = pathBuilder().move(-3, 4).curve(-2.75, 2.5, 0, 0.25, 0.75, 0).
29012
29012
  function Arrowhead(props) {
29013
29013
  const [point] = useTransformVectorsToPixels(props.tip);
29014
29014
  return /*#__PURE__*/React__namespace.createElement("g", {
29015
+ className: "interactive-graph-arrowhead",
29015
29016
  transform: `translate(${point[0]} ${point[1]}) rotate(${-props.angle})`
29016
29017
  }, /*#__PURE__*/React__namespace.createElement("g", {
29017
29018
  transform: "translate(-1.5)"
@@ -29026,49 +29027,7 @@ function Arrowhead(props) {
29026
29027
  })));
29027
29028
  }
29028
29029
 
29029
- function SVGLine(props) {
29030
- const {
29031
- start,
29032
- end,
29033
- style,
29034
- className,
29035
- testId
29036
- } = props;
29037
- return /*#__PURE__*/React__namespace.createElement("line", {
29038
- x1: start[0],
29039
- y1: start[1],
29040
- x2: end[0],
29041
- y2: end[1],
29042
- style: style,
29043
- className: className,
29044
- "data-testid": testId
29045
- });
29046
- }
29047
-
29048
- function Vector(props) {
29049
- const {
29050
- tail,
29051
- tip,
29052
- color,
29053
- style
29054
- } = props;
29055
- const [tailPx, tipPx] = useTransformVectorsToPixels(tail, tip);
29056
- const direction = mafs.vec.sub(tip, tail);
29057
- return /*#__PURE__*/React__namespace.createElement("g", {
29058
- style: {
29059
- stroke: color,
29060
- strokeWidth: 2
29061
- }
29062
- }, /*#__PURE__*/React__namespace.createElement(SVGLine, {
29063
- start: tailPx,
29064
- end: tipPx,
29065
- style: style
29066
- }), /*#__PURE__*/React__namespace.createElement(Arrowhead, {
29067
- tip: tip,
29068
- angle: angleDegrees(direction)
29069
- }));
29070
- }
29071
- function angleDegrees(_ref) {
29030
+ function calculateAngleInDegrees(_ref) {
29072
29031
  let [x, y] = _ref;
29073
29032
  return Math.atan2(y, x) * 180 / Math.PI;
29074
29033
  }
@@ -29111,10 +29070,57 @@ const getIntersectionOfRayWithBox = (initialPoint, throughPoint, box) => {
29111
29070
  return [xMax, yAtXMax];
29112
29071
  }
29113
29072
  };
29114
- const getLines = points => points.map((point, i) => {
29115
- const next = points[(i + 1) % points.length];
29116
- return [point, next];
29117
- });
29073
+ const getLines = points => {
29074
+ return points.map((point, i) => {
29075
+ const next = points[(i + 1) % points.length];
29076
+ return [point, next];
29077
+ });
29078
+ };
29079
+
29080
+ function SVGLine(props) {
29081
+ const {
29082
+ start,
29083
+ end,
29084
+ style,
29085
+ className,
29086
+ testId
29087
+ } = props;
29088
+ return /*#__PURE__*/React__namespace.createElement("line", {
29089
+ x1: start[0],
29090
+ y1: start[1],
29091
+ x2: end[0],
29092
+ y2: end[1],
29093
+ style: style,
29094
+ className: className,
29095
+ "data-testid": testId
29096
+ });
29097
+ }
29098
+
29099
+ function Vector(props) {
29100
+ const {
29101
+ tail,
29102
+ tip,
29103
+ color,
29104
+ style
29105
+ } = props;
29106
+ const [tailPx, tipPx] = useTransformVectorsToPixels(tail, tip);
29107
+ const direction = mafs.vec.sub(tip, tail);
29108
+ const angle = calculateAngleInDegrees(direction);
29109
+ return /*#__PURE__*/React__namespace.createElement("g", {
29110
+ style: {
29111
+ stroke: color,
29112
+ strokeWidth: 2
29113
+ }
29114
+ }, /*#__PURE__*/React__namespace.createElement(SVGLine, {
29115
+ start: tailPx,
29116
+ end: tipPx,
29117
+ style: style
29118
+ }), /*#__PURE__*/React__namespace.createElement(Arrowhead, {
29119
+ angle: angle,
29120
+ tip: tip,
29121
+ color: color
29122
+ }));
29123
+ }
29118
29124
 
29119
29125
  const LockedLine = props => {
29120
29126
  const {
@@ -29130,27 +29136,38 @@ const LockedLine = props => {
29130
29136
  let line;
29131
29137
  if (kind === "ray") {
29132
29138
  // Rays extend to the end of the graph in one direction.
29133
- const endExtend = getIntersectionOfRayWithBox(point2.coord, point1.coord, range);
29139
+ const extendedPoint = getIntersectionOfRayWithBox(point2.coord, point1.coord, range);
29134
29140
  line = /*#__PURE__*/React__namespace.createElement(Vector, {
29135
29141
  tail: point1.coord,
29136
- tip: endExtend,
29142
+ tip: extendedPoint,
29137
29143
  color: lockedFigureColors[color],
29138
29144
  style: {
29139
- strokeDasharray: lineStyle === "dashed" ?
29140
- // TODO(lems-1930): Uncomment this line when the
29141
- // dashed style is updated in Mafs.
29142
- // ? "var(--mafs-line-stroke-dash-style)"
29143
- "4, 3" : undefined
29145
+ strokeDasharray: lineStyle === "dashed" ? "var(--mafs-line-stroke-dash-style)" : undefined
29144
29146
  }
29145
29147
  });
29146
29148
  } else {
29147
29149
  const LineType = kind === "segment" ? mafs.Line.Segment : mafs.Line.ThroughPoints;
29148
- line = /*#__PURE__*/React__namespace.createElement(LineType, {
29150
+ let arrowTip = kind === "segment" ? point2.coord : getIntersectionOfRayWithBox(point2.coord, point1.coord, range);
29151
+ const direction = mafs.vec.sub(point2.coord, point1.coord);
29152
+ let angle = calculateAngleInDegrees(direction);
29153
+ const startArrowHead = kind !== "segment" && /*#__PURE__*/React__namespace.createElement(Arrowhead, {
29154
+ angle: angle,
29155
+ tip: arrowTip,
29156
+ color: lockedFigureColors[color]
29157
+ });
29158
+ arrowTip = kind === "segment" ? point1.coord : getIntersectionOfRayWithBox(point1.coord, point2.coord, range);
29159
+ angle = angle > 180 ? angle - 180 : angle + 180;
29160
+ const endArrowHead = kind !== "segment" && /*#__PURE__*/React__namespace.createElement(Arrowhead, {
29161
+ angle: angle,
29162
+ tip: arrowTip,
29163
+ color: lockedFigureColors[color]
29164
+ });
29165
+ line = /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, startArrowHead, /*#__PURE__*/React__namespace.createElement(LineType, {
29149
29166
  point1: point1.coord,
29150
29167
  point2: point2.coord,
29151
29168
  color: lockedFigureColors[color],
29152
29169
  style: lineStyle
29153
- });
29170
+ }), endArrowHead);
29154
29171
  }
29155
29172
  return /*#__PURE__*/React__namespace.createElement("g", {
29156
29173
  className: kind === "ray" ? "locked-ray" : "locked-line"
@@ -29256,6 +29273,20 @@ function movePoint(index, destination) {
29256
29273
  destination
29257
29274
  };
29258
29275
  }
29276
+ const MOVE_CENTER = "move-center";
29277
+ function moveCenter(destination) {
29278
+ return {
29279
+ type: MOVE_CENTER,
29280
+ destination
29281
+ };
29282
+ }
29283
+ const MOVE_RADIUS_POINT = "MOVE_RADIUS_POINT";
29284
+ function moveRadiusPoint(destination) {
29285
+ return {
29286
+ type: MOVE_RADIUS_POINT,
29287
+ destination
29288
+ };
29289
+ }
29259
29290
  const CHANGE_SNAP_STEP = "change-snap-step";
29260
29291
  function changeSnapStep(snapStep) {
29261
29292
  return {
@@ -29302,6 +29333,194 @@ function snap(snapStep, point) {
29302
29333
  return [Math.round(requestedX / snapX) * snapX, Math.round(requestedY / snapY) * snapY];
29303
29334
  }
29304
29335
 
29336
+ // The hitbox size of 48px by 48px is preserved from the legacy interactive
29337
+ // graph.
29338
+ const hitboxSizePx = 48;
29339
+
29340
+ // MovablePointView is a purely presentational component (i.e. it is a pure
29341
+ // function with no state or effects) that renders the SVG for a movable point
29342
+ // on an interactive graph.
29343
+ //
29344
+ // It has two modes for managing tabbing / focus: "controlled" (where the caller
29345
+ // manages the display of the focus ring, and the point itself cannot be
29346
+ // focused) and "uncontrolled" (where the point is focusable, and the browser
29347
+ // manages the focus state and styling). For context on why we did this, see
29348
+ // the description of https://github.com/Khan/perseus/pull/1240
29349
+ const MovablePointView = /*#__PURE__*/React.forwardRef((props, hitboxRef) => {
29350
+ const {
29351
+ range,
29352
+ markings,
29353
+ showTooltips
29354
+ } = useGraphConfig();
29355
+ const {
29356
+ point,
29357
+ color = wonderBlocksTokens.color.blue,
29358
+ dragging,
29359
+ focusBehavior
29360
+ } = props;
29361
+
29362
+ // WB Tooltip requires a color name for the background color.
29363
+ // Since the color in props is a hex value, a reverse lookup is needed.
29364
+ const wbColorName = Object.entries(wonderBlocksTokens.color).find(_ref => {
29365
+ let [_, value] = _ref;
29366
+ return value === color;
29367
+ })?.[0] ?? "blue";
29368
+ const pointClasses = `movable-point ${dragging ? "movable-point--dragging" : ""} ${focusClass(focusBehavior)}`;
29369
+ const [[x, y]] = useTransformVectorsToPixels(point);
29370
+ const [xMin, xMax] = range[0];
29371
+ const [yMin, yMax] = range[1];
29372
+ const [[verticalStartX]] = useTransformVectorsToPixels([xMin, 0]);
29373
+ const [[verticalEndX]] = useTransformVectorsToPixels([xMax, 0]);
29374
+ const [[_, horizontalStartY]] = useTransformVectorsToPixels([0, yMin]);
29375
+ const [[__, horizontalEndY]] = useTransformVectorsToPixels([0, yMax]);
29376
+ const showHairlines = dragging && markings !== "none";
29377
+ const hairlines = /*#__PURE__*/React__namespace.createElement("g", null, /*#__PURE__*/React__namespace.createElement("line", {
29378
+ x1: verticalStartX,
29379
+ y1: y,
29380
+ x2: verticalEndX,
29381
+ y2: y,
29382
+ stroke: color
29383
+ }), /*#__PURE__*/React__namespace.createElement("line", {
29384
+ x1: x,
29385
+ y1: horizontalStartY,
29386
+ x2: x,
29387
+ y2: horizontalEndY,
29388
+ stroke: color
29389
+ }));
29390
+ const svgForPoint = /*#__PURE__*/React__namespace.createElement("g", {
29391
+ ref: hitboxRef,
29392
+ className: pointClasses,
29393
+ style: {
29394
+ "--movable-point-color": color
29395
+ },
29396
+ "data-testid": "movable-point",
29397
+ tabIndex: tabIndex(focusBehavior)
29398
+ }, /*#__PURE__*/React__namespace.createElement("circle", {
29399
+ className: "movable-point-hitbox",
29400
+ r: hitboxSizePx / 2,
29401
+ cx: x,
29402
+ cy: y
29403
+ }), /*#__PURE__*/React__namespace.createElement("circle", {
29404
+ className: "movable-point-halo",
29405
+ cx: x,
29406
+ cy: y
29407
+ }), /*#__PURE__*/React__namespace.createElement("circle", {
29408
+ className: "movable-point-ring",
29409
+ cx: x,
29410
+ cy: y
29411
+ }), /*#__PURE__*/React__namespace.createElement("circle", {
29412
+ className: "movable-point-focus-outline",
29413
+ cx: x,
29414
+ cy: y
29415
+ }), /*#__PURE__*/React__namespace.createElement("circle", {
29416
+ className: "movable-point-center",
29417
+ cx: x,
29418
+ cy: y,
29419
+ style: {
29420
+ fill: color
29421
+ },
29422
+ "data-testid": "movable-point__center"
29423
+ }));
29424
+ return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, showHairlines && hairlines, showTooltips ? /*#__PURE__*/React__namespace.createElement(Tooltip__default["default"], {
29425
+ autoUpdate: true,
29426
+ backgroundColor: wbColorName,
29427
+ content: `(${point[0]}, ${point[1]})`,
29428
+ contentStyle: {
29429
+ color: "white"
29430
+ }
29431
+ }, svgForPoint) : svgForPoint);
29432
+ });
29433
+ function focusClass(config) {
29434
+ if (config.type === "controlled" && config.showFocusRing) {
29435
+ return "movable-point--focus";
29436
+ }
29437
+ return "";
29438
+ }
29439
+ function tabIndex(config) {
29440
+ if (config.type === "uncontrolled") {
29441
+ return config.tabIndex;
29442
+ }
29443
+ return undefined;
29444
+ }
29445
+
29446
+ const Segment = props => {
29447
+ const {
29448
+ onMoveLine,
29449
+ color,
29450
+ points: [start, end],
29451
+ extend
29452
+ } = props;
29453
+
29454
+ // We use separate focusableHandle elements, instead of letting the movable
29455
+ // points themselves be focusable, to allow the tab order of the points to
29456
+ // be different from the rendering order. We had to solve for the following
29457
+ // constraints:
29458
+ // - SVG has no equivalent of z-index, so the order of elements in the
29459
+ // document determines the order in which they're painted. We want the
29460
+ // movable line segment to render behind its endpoints (it looks weird
29461
+ // otherwise) so we have to render the line first.
29462
+ // - There isn't a browser-native way to customize tab order, other than
29463
+ // setting tabindex > 0. But that bumps elements to the front of the
29464
+ // tab order for the entire page, which is not what we want.
29465
+ const {
29466
+ visiblePoint: visiblePoint1,
29467
+ focusableHandle: focusableHandle1
29468
+ } = useControlPoint(start, color, p => props.onMovePoint(0, p));
29469
+ const {
29470
+ visiblePoint: visiblePoint2,
29471
+ focusableHandle: focusableHandle2
29472
+ } = useControlPoint(end, color, p => props.onMovePoint(1, p));
29473
+ return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, focusableHandle1, /*#__PURE__*/React__namespace.createElement(MovableLine, {
29474
+ start: start,
29475
+ end: end,
29476
+ stroke: color,
29477
+ extend: extend,
29478
+ onMove: onMoveLine
29479
+ }), focusableHandle2, visiblePoint1, visiblePoint2);
29480
+ };
29481
+ function useControlPoint(point, color, onMovePoint) {
29482
+ const {
29483
+ snapStep
29484
+ } = useGraphConfig();
29485
+ const [focused, setFocused] = React.useState(false);
29486
+ const keyboardHandleRef = React.useRef(null);
29487
+ mafs.useMovable({
29488
+ gestureTarget: keyboardHandleRef,
29489
+ point,
29490
+ onMove: onMovePoint,
29491
+ constrain: p => snap(snapStep, p)
29492
+ });
29493
+ const visiblePointRef = React.useRef(null);
29494
+ const {
29495
+ dragging
29496
+ } = mafs.useMovable({
29497
+ gestureTarget: visiblePointRef,
29498
+ point,
29499
+ onMove: onMovePoint,
29500
+ constrain: p => snap(snapStep, p)
29501
+ });
29502
+ const focusableHandle = /*#__PURE__*/React__namespace.createElement("g", {
29503
+ "data-testid": "movable-point__focusable-handle",
29504
+ tabIndex: 0,
29505
+ ref: keyboardHandleRef,
29506
+ onFocus: () => setFocused(true),
29507
+ onBlur: () => setFocused(false)
29508
+ });
29509
+ const visiblePoint = /*#__PURE__*/React__namespace.createElement(MovablePointView, {
29510
+ point: point,
29511
+ dragging: dragging,
29512
+ color: color,
29513
+ ref: visiblePointRef,
29514
+ focusBehavior: {
29515
+ type: "controlled",
29516
+ showFocusRing: focused
29517
+ }
29518
+ });
29519
+ return {
29520
+ focusableHandle,
29521
+ visiblePoint
29522
+ };
29523
+ }
29305
29524
  const defaultStroke = "var(--movable-line-stroke-color)";
29306
29525
  const MovableLine = props => {
29307
29526
  const {
@@ -29314,12 +29533,13 @@ const MovableLine = props => {
29314
29533
  const midpoint = mafs.vec.midpoint(start, end);
29315
29534
  const [startPtPx, endPtPx] = useTransformVectorsToPixels(start, end);
29316
29535
  const {
29536
+ range,
29317
29537
  graphDimensionsInPixels
29318
29538
  } = useGraphConfig();
29319
29539
  let startExtend = undefined;
29320
29540
  let endExtend = undefined;
29321
29541
  if (extend) {
29322
- const trimmedRange = trimRange(extend.range, graphDimensionsInPixels);
29542
+ const trimmedRange = trimRange(range, graphDimensionsInPixels);
29323
29543
  startExtend = extend.start ? getIntersectionOfRayWithBox(start, end, trimmedRange) : undefined;
29324
29544
  endExtend = extend.end ? getIntersectionOfRayWithBox(end, start, trimmedRange) : undefined;
29325
29545
  }
@@ -29398,103 +29618,6 @@ function size(interval) {
29398
29618
  return interval[1] - interval[0];
29399
29619
  }
29400
29620
 
29401
- // The hitbox size of 48px by 48px is preserved from the legacy interactive
29402
- // graph.
29403
- const hitboxSizePx = 48;
29404
- const StyledMovablePoint = props => {
29405
- const {
29406
- range,
29407
- snapStep,
29408
- markings,
29409
- showTooltips
29410
- } = useGraphConfig();
29411
- const hitboxRef = React.useRef(null);
29412
- const {
29413
- point,
29414
- onMove,
29415
- color = wonderBlocksTokens.color.blue
29416
- } = props;
29417
-
29418
- // WB Tooltip requires a color name for the background color.
29419
- // Since the color in props is a hex value, a reverse lookup is needed.
29420
- const wbColorName = Object.entries(wonderBlocksTokens.color).find(_ref => {
29421
- let [_, value] = _ref;
29422
- return value === color;
29423
- })?.[0] ?? "blue";
29424
- const {
29425
- dragging
29426
- } = mafs.useMovable({
29427
- gestureTarget: hitboxRef,
29428
- point,
29429
- onMove,
29430
- constrain: p => snap(snapStep, p)
29431
- });
29432
- const pointClasses = `movable-point ${dragging ? "movable-point--dragging" : ""}`;
29433
- const [[x, y]] = useTransformVectorsToPixels(point);
29434
- const [xMin, xMax] = range[0];
29435
- const [yMin, yMax] = range[1];
29436
- const [[verticalStartX]] = useTransformVectorsToPixels([xMin, 0]);
29437
- const [[verticalEndX]] = useTransformVectorsToPixels([xMax, 0]);
29438
- const [[_, horizontalStartY]] = useTransformVectorsToPixels([0, yMin]);
29439
- const [[__, horizontalEndY]] = useTransformVectorsToPixels([0, yMax]);
29440
- const showHairlines = dragging && markings !== "none";
29441
- const hairlines = /*#__PURE__*/React__namespace.createElement("g", null, /*#__PURE__*/React__namespace.createElement("line", {
29442
- x1: verticalStartX,
29443
- y1: y,
29444
- x2: verticalEndX,
29445
- y2: y,
29446
- stroke: color
29447
- }), /*#__PURE__*/React__namespace.createElement("line", {
29448
- x1: x,
29449
- y1: horizontalStartY,
29450
- x2: x,
29451
- y2: horizontalEndY,
29452
- stroke: color
29453
- }));
29454
- const svgForPoint = /*#__PURE__*/React__namespace.createElement("g", {
29455
- ref: hitboxRef,
29456
- className: pointClasses,
29457
- tabIndex: 0,
29458
- style: {
29459
- "--movable-point-color": color
29460
- },
29461
- "data-testid": "movable-point"
29462
- }, /*#__PURE__*/React__namespace.createElement("circle", {
29463
- className: "movable-point-hitbox",
29464
- r: hitboxSizePx / 2,
29465
- cx: x,
29466
- cy: y
29467
- }), /*#__PURE__*/React__namespace.createElement("circle", {
29468
- className: "movable-point-halo",
29469
- cx: x,
29470
- cy: y
29471
- }), /*#__PURE__*/React__namespace.createElement("circle", {
29472
- className: "movable-point-ring",
29473
- cx: x,
29474
- cy: y
29475
- }), /*#__PURE__*/React__namespace.createElement("circle", {
29476
- className: "movable-point-focus-outline",
29477
- cx: x,
29478
- cy: y
29479
- }), /*#__PURE__*/React__namespace.createElement("circle", {
29480
- className: "movable-point-center",
29481
- cx: x,
29482
- cy: y,
29483
- style: {
29484
- fill: color
29485
- },
29486
- "data-testid": "movable-point__center"
29487
- }));
29488
- return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, showHairlines && hairlines, showTooltips ? /*#__PURE__*/React__namespace.createElement(Tooltip__default["default"], {
29489
- autoUpdate: true,
29490
- backgroundColor: wbColorName,
29491
- content: `(${point[0]}, ${point[1]})`,
29492
- contentStyle: {
29493
- color: "white"
29494
- }
29495
- }, svgForPoint) : svgForPoint);
29496
- };
29497
-
29498
29621
  const SegmentGraph = props => {
29499
29622
  const {
29500
29623
  dispatch
@@ -29502,7 +29625,7 @@ const SegmentGraph = props => {
29502
29625
  const {
29503
29626
  coords: segments
29504
29627
  } = props.graphState;
29505
- return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, segments?.map((segment, i) => /*#__PURE__*/React__namespace.createElement(SegmentView, {
29628
+ return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, segments?.map((segment, i) => /*#__PURE__*/React__namespace.createElement(Segment, {
29506
29629
  key: i,
29507
29630
  points: segment,
29508
29631
  onMoveLine: delta => {
@@ -29513,27 +29636,6 @@ const SegmentGraph = props => {
29513
29636
  }
29514
29637
  })));
29515
29638
  };
29516
- const SegmentView = props => {
29517
- const {
29518
- onMoveLine: onMoveSegment,
29519
- points: [start, end]
29520
- } = props;
29521
- return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(MovableLine, {
29522
- start: start,
29523
- end: end,
29524
- onMove: onMoveSegment
29525
- }), /*#__PURE__*/React__namespace.createElement(StyledMovablePoint, {
29526
- point: start,
29527
- onMove: newPoint => {
29528
- props.onMovePoint(0, newPoint);
29529
- }
29530
- }), /*#__PURE__*/React__namespace.createElement(StyledMovablePoint, {
29531
- point: end,
29532
- onMove: newPoint => {
29533
- props.onMovePoint(1, newPoint);
29534
- }
29535
- }));
29536
- };
29537
29639
 
29538
29640
  const LinearGraph = props => {
29539
29641
  const {
@@ -29543,49 +29645,19 @@ const LinearGraph = props => {
29543
29645
  coords: lines
29544
29646
  } = props.graphState;
29545
29647
  const colors = ["var(--movable-line-stroke-color)", "var(--mafs-violet)"];
29546
- return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, lines?.map((line, i) => /*#__PURE__*/React__namespace.createElement(LineView, {
29648
+ return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, lines?.map((line, i) => /*#__PURE__*/React__namespace.createElement(Segment, {
29547
29649
  key: i,
29548
29650
  points: line,
29549
29651
  onMoveLine: delta => {
29550
29652
  dispatch(moveLine(i, delta));
29551
29653
  },
29552
- onMovePoint: (endpointIndex, destination) => dispatch(moveControlPoint(endpointIndex, destination, i)),
29553
- stroke: colors[i]
29554
- })));
29555
- };
29556
- const LineView = props => {
29557
- const {
29558
- onMoveLine,
29559
- onMovePoint,
29560
- points: [start, end],
29561
- stroke
29562
- } = props;
29563
- const {
29564
- range
29565
- } = useGraphConfig();
29566
- return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(MovableLine, {
29567
- start: start,
29568
- end: end,
29569
- onMove: onMoveLine,
29570
29654
  extend: {
29571
29655
  start: true,
29572
- end: true,
29573
- range
29574
- },
29575
- stroke: stroke
29576
- }), /*#__PURE__*/React__namespace.createElement(StyledMovablePoint, {
29577
- point: start,
29578
- onMove: newPoint => {
29579
- onMovePoint(0, newPoint);
29580
- },
29581
- color: stroke
29582
- }), /*#__PURE__*/React__namespace.createElement(StyledMovablePoint, {
29583
- point: end,
29584
- onMove: newPoint => {
29585
- onMovePoint(1, newPoint);
29656
+ end: true
29586
29657
  },
29587
- color: stroke
29588
- }));
29658
+ onMovePoint: (endpointIndex, destination) => dispatch(moveControlPoint(endpointIndex, destination, i)),
29659
+ color: colors[i]
29660
+ })));
29589
29661
  };
29590
29662
 
29591
29663
  const RayGraph = props => {
@@ -29593,30 +29665,21 @@ const RayGraph = props => {
29593
29665
  dispatch
29594
29666
  } = props;
29595
29667
  const {
29596
- coords: lines,
29597
- range
29668
+ coords: lines
29598
29669
  } = props.graphState;
29599
-
29600
- // a ray only has one line
29601
- const [start, end] = lines[0];
29602
29670
  const handleMoveLine = delta => dispatch(moveLine(0, delta));
29603
- const handleMovePoint = (newPoint, pointIndex) => dispatch(moveControlPoint(pointIndex, newPoint, 0));
29604
- return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(MovableLine, {
29605
- start: start,
29606
- end: end,
29607
- onMove: handleMoveLine,
29671
+ const handleMovePoint = (pointIndex, newPoint) => dispatch(moveControlPoint(pointIndex, newPoint, 0));
29672
+ return /*#__PURE__*/React__namespace.createElement(Segment
29673
+ // a ray only has one line
29674
+ , {
29675
+ points: lines[0],
29676
+ onMoveLine: handleMoveLine,
29677
+ onMovePoint: handleMovePoint,
29608
29678
  extend: {
29609
- end: true,
29610
29679
  start: false,
29611
- range
29680
+ end: true
29612
29681
  }
29613
- }), /*#__PURE__*/React__namespace.createElement(StyledMovablePoint, {
29614
- point: start,
29615
- onMove: newPoint => handleMovePoint(newPoint, 0)
29616
- }), /*#__PURE__*/React__namespace.createElement(StyledMovablePoint, {
29617
- point: end,
29618
- onMove: newPoint => handleMovePoint(newPoint, 1)
29619
- }));
29682
+ });
29620
29683
  };
29621
29684
 
29622
29685
  /**
@@ -29788,6 +29851,36 @@ const linesIntersect = (_ref3, _ref4) => {
29788
29851
  };
29789
29852
  const isEven = n => n % 2 === 0;
29790
29853
 
29854
+ const StyledMovablePoint = props => {
29855
+ const {
29856
+ snapStep
29857
+ } = useGraphConfig();
29858
+ const elementRef = React.useRef(null);
29859
+ const {
29860
+ point,
29861
+ onMove,
29862
+ color = wonderBlocksTokens.color.blue
29863
+ } = props;
29864
+ const {
29865
+ dragging
29866
+ } = mafs.useMovable({
29867
+ gestureTarget: elementRef,
29868
+ point,
29869
+ onMove,
29870
+ constrain: p => snap(snapStep, p)
29871
+ });
29872
+ return /*#__PURE__*/React__namespace.createElement(MovablePointView, {
29873
+ ref: elementRef,
29874
+ point: point,
29875
+ color: color,
29876
+ dragging: dragging,
29877
+ focusBehavior: {
29878
+ type: "uncontrolled",
29879
+ tabIndex: 0
29880
+ }
29881
+ });
29882
+ };
29883
+
29791
29884
  const PolygonGraph = props => {
29792
29885
  const [focused, setFocused] = React__namespace.useState(false);
29793
29886
  const [hovered, setHovered] = React__namespace.useState(false);
@@ -29801,17 +29894,19 @@ const PolygonGraph = props => {
29801
29894
  range,
29802
29895
  snapStep
29803
29896
  } = props.graphState;
29897
+
29898
+ // TODO(benchristel): can the default set of points be removed here? I don't
29899
+ // think coords can be null.
29804
29900
  const points = coords ?? [[0, 0]];
29805
- const pointsSum = points.reduce((acc, point) => mafs.vec.add(acc, point), [0, 0]);
29806
- const midpoint = pointsSum && mafs.vec.scale(pointsSum, 1 / (points.length ?? 1));
29807
29901
  const ref = React__namespace.useRef(null);
29902
+ const dragReferencePoint = points[0];
29808
29903
  const {
29809
29904
  dragging
29810
29905
  } = mafs.useMovable({
29811
29906
  gestureTarget: ref,
29812
- point: midpoint,
29907
+ point: dragReferencePoint,
29813
29908
  onMove: newPoint => {
29814
- const delta = mafs.vec.sub(newPoint, midpoint);
29909
+ const delta = mafs.vec.sub(newPoint, dragReferencePoint);
29815
29910
  dispatch(moveAll(delta));
29816
29911
  },
29817
29912
  constrain: p => snap(snapStep, p)
@@ -29871,6 +29966,287 @@ const PolygonGraph = props => {
29871
29966
  })));
29872
29967
  };
29873
29968
 
29969
+ function initializeGraphState(params) {
29970
+ const {
29971
+ graph,
29972
+ step,
29973
+ snapStep,
29974
+ range,
29975
+ markings
29976
+ } = params;
29977
+ const shared = {
29978
+ hasBeenInteractedWith: false,
29979
+ range,
29980
+ snapStep,
29981
+ markings
29982
+ };
29983
+ switch (graph.type) {
29984
+ case "segment":
29985
+ return {
29986
+ ...shared,
29987
+ type: "segment",
29988
+ coords: getDefaultSegments({
29989
+ graph,
29990
+ step,
29991
+ range
29992
+ })
29993
+ };
29994
+ case "linear":
29995
+ case "linear-system":
29996
+ case "ray":
29997
+ return {
29998
+ ...shared,
29999
+ type: graph.type,
30000
+ // Linear and ray graphs have a single tuple of points, while a
30001
+ // linear system has two tuples of points.
30002
+ coords: getLineCoords({
30003
+ graph,
30004
+ range,
30005
+ step
30006
+ })
30007
+ };
30008
+ case "polygon":
30009
+ return {
30010
+ ...shared,
30011
+ type: "polygon",
30012
+ showAngles: Boolean(graph.showAngles),
30013
+ showSides: Boolean(graph.showSides),
30014
+ coords: getPolygonCoords({
30015
+ graph,
30016
+ range,
30017
+ step
30018
+ })
30019
+ };
30020
+ case "point":
30021
+ return {
30022
+ ...shared,
30023
+ type: graph.type,
30024
+ coords: getDefaultPoints({
30025
+ graph,
30026
+ step,
30027
+ range
30028
+ })
30029
+ };
30030
+ case "circle":
30031
+ return {
30032
+ ...shared,
30033
+ type: graph.type,
30034
+ center: [0, 0],
30035
+ radiusPoint: [1, 0]
30036
+ };
30037
+ case "angle":
30038
+ case "sinusoid":
30039
+ case "quadratic":
30040
+ throw new Error("Mafs not yet implemented for graph type: " + graph.type);
30041
+ default:
30042
+ throw new wonderStuffCore.UnreachableCaseError(graph);
30043
+ }
30044
+ }
30045
+ const getDefaultPoints = _ref => {
30046
+ let {
30047
+ graph,
30048
+ range,
30049
+ step
30050
+ } = _ref;
30051
+ const numPoints = graph.numPoints || 1;
30052
+ let coords = graph.coords?.slice();
30053
+ if (coords) {
30054
+ return coords;
30055
+ }
30056
+ switch (numPoints) {
30057
+ case 1:
30058
+ // Back in the day, one point's coords were in graph.coord
30059
+ coords = [graph.coord || [0, 0]];
30060
+ break;
30061
+ case 2:
30062
+ coords = [[-5, 0], [5, 0]];
30063
+ break;
30064
+ case 3:
30065
+ coords = [[-5, 0], [0, 0], [5, 0]];
30066
+ break;
30067
+ case 4:
30068
+ coords = [[-6, 0], [-2, 0], [2, 0], [6, 0]];
30069
+ break;
30070
+ case 5:
30071
+ coords = [[-6, 0], [-3, 0], [0, 0], [3, 0], [6, 0]];
30072
+ break;
30073
+ case 6:
30074
+ coords = [[-5, 0], [-3, 0], [-1, 0], [1, 0], [3, 0], [5, 0]];
30075
+ break;
30076
+ default:
30077
+ coords = [];
30078
+ break;
30079
+ }
30080
+ // Transform coords from their -10 to 10 space to 0 to 1
30081
+ // because of the old graph.coord, and also it's easier.
30082
+ const newCoords = normalizeCoords(coords, [[-10, 10], [-10, 10]]);
30083
+ return normalizePoints(range, step, newCoords);
30084
+ };
30085
+
30086
+ // TS v4 doesn't narrow return types, while v5 does.
30087
+ // Instead of updating to v5, using generic type to relate input and output types.
30088
+ function getGradableGraph(state, initialGraph) {
30089
+ if (!state.hasBeenInteractedWith) {
30090
+ return {
30091
+ ...initialGraph
30092
+ };
30093
+ }
30094
+ if (initialGraph.type === "linear-system" && state.type === "linear-system") {
30095
+ return {
30096
+ ...initialGraph,
30097
+ coords: state.coords
30098
+ };
30099
+ }
30100
+ if (state.type === "segment" && initialGraph.type === "segment") {
30101
+ return {
30102
+ ...initialGraph,
30103
+ coords: state.coords
30104
+ };
30105
+ }
30106
+ if (state.type === "linear" && initialGraph.type === "linear") {
30107
+ return {
30108
+ ...initialGraph,
30109
+ coords: state.coords[0]
30110
+ };
30111
+ }
30112
+ if (state.type === "ray" && initialGraph.type === "ray") {
30113
+ return {
30114
+ ...initialGraph,
30115
+ coords: state.coords[0]
30116
+ };
30117
+ }
30118
+ if (state.type === "polygon" && initialGraph.type === "polygon") {
30119
+ return {
30120
+ ...initialGraph,
30121
+ coords: state.coords
30122
+ };
30123
+ }
30124
+ if (state.type === "point" && initialGraph.type === "point") {
30125
+ return {
30126
+ ...initialGraph,
30127
+ coords: state.coords
30128
+ };
30129
+ }
30130
+ if (state.type === "circle" && initialGraph.type === "circle") {
30131
+ return {
30132
+ ...initialGraph,
30133
+ center: state.center,
30134
+ radius: getRadius(state)
30135
+ };
30136
+ }
30137
+ throw new Error("Mafs is not yet implemented for graph type: " + initialGraph.type);
30138
+ }
30139
+ const getDefaultSegments = _ref2 => {
30140
+ let {
30141
+ graph,
30142
+ range,
30143
+ step
30144
+ } = _ref2;
30145
+ const ys = n => {
30146
+ switch (n) {
30147
+ case 2:
30148
+ return [5, -5];
30149
+ case 3:
30150
+ return [5, 0, -5];
30151
+ case 4:
30152
+ return [6, 2, -2, -6];
30153
+ case 5:
30154
+ return [6, 3, 0, -3, -6];
30155
+ case 6:
30156
+ return [5, 3, 1, -1, -3, -5];
30157
+ default:
30158
+ return [5];
30159
+ }
30160
+ };
30161
+ const defaultRange = [[-10, 10], [-10, 10]];
30162
+ return ys(graph.numSegments).map(y => {
30163
+ let endpoints = [[-5, y], [5, y]];
30164
+ endpoints = normalizeCoords(endpoints, defaultRange);
30165
+ endpoints = normalizePoints(range, step, endpoints);
30166
+ return endpoints;
30167
+ });
30168
+ };
30169
+ const defaultLinearCoords = [[[0.25, 0.75], [0.75, 0.75]], [[0.25, 0.25], [0.75, 0.25]]];
30170
+ const getLineCoords = _ref3 => {
30171
+ let {
30172
+ graph,
30173
+ range,
30174
+ step
30175
+ } = _ref3;
30176
+ return (
30177
+ // Return two lines for a linear system, one for a ray or linear
30178
+ graph.coords ?? graph.type === "linear-system" ? defaultLinearCoords.map(collinear => normalizePoints(range, step, collinear)) : [normalizePoints(range, step, defaultLinearCoords[0])]
30179
+ );
30180
+ };
30181
+ const getPolygonCoords = _ref4 => {
30182
+ let {
30183
+ graph,
30184
+ range,
30185
+ step
30186
+ } = _ref4;
30187
+ let coords = graph.coords?.slice();
30188
+ if (coords) {
30189
+ return coords;
30190
+ }
30191
+ const n = graph.numSides || 3;
30192
+ if (n === "unlimited") {
30193
+ coords = [];
30194
+ } else {
30195
+ const angle = 2 * Math.PI / n;
30196
+ const offset = (1 / n - 1 / 2) * Math.PI;
30197
+
30198
+ // TODO(alex): Generalize this to more than just triangles so that
30199
+ // all polygons have whole number side lengths if snapping to sides
30200
+ const radius = graph.snapTo === "sides" ? Math.sqrt(3) / 3 * 7 : 4;
30201
+
30202
+ // Generate coords of a regular polygon with n sides
30203
+ coords = [...Array(n).keys()].map(i => [radius * Math.cos(i * angle + offset), radius * Math.sin(i * angle + offset)]);
30204
+ }
30205
+ coords = normalizeCoords(coords, [[-10, 10], [-10, 10]]);
30206
+ const snapToGrid = !["angles", "sides"].includes(graph.snapTo || "");
30207
+ coords = normalizePoints(range, step, coords, /* noSnap */!snapToGrid);
30208
+ return coords;
30209
+ };
30210
+
30211
+ /**
30212
+ * determine radius of a circle graph
30213
+ *
30214
+ * @param graph - the graph object containing the circle
30215
+ * @returns the radius of the circle
30216
+ */
30217
+ function getRadius(graph) {
30218
+ const [centerX, centerY] = graph.center;
30219
+ const [edgeX, edgeY] = graph.radiusPoint;
30220
+ return Math.sqrt(Math.pow(edgeX - centerX, 2) + Math.pow(edgeY - centerY, 2));
30221
+ }
30222
+
30223
+ function CircleGraph(props) {
30224
+ const {
30225
+ dispatch,
30226
+ graphState
30227
+ } = props;
30228
+ const {
30229
+ center,
30230
+ radiusPoint
30231
+ } = graphState;
30232
+ return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(mafs.Circle, {
30233
+ center: center,
30234
+ radius: getRadius(graphState),
30235
+ fillOpacity: 0,
30236
+ color: wonderBlocksTokens.color.blue
30237
+ }), /*#__PURE__*/React__namespace.createElement(StyledMovablePoint, {
30238
+ point: center,
30239
+ onMove: newCenter => {
30240
+ dispatch(moveCenter(newCenter));
30241
+ }
30242
+ }), /*#__PURE__*/React__namespace.createElement(StyledMovablePoint, {
30243
+ point: radiusPoint,
30244
+ onMove: newRadiusPoint => {
30245
+ dispatch(moveRadiusPoint(newRadiusPoint));
30246
+ }
30247
+ }));
30248
+ }
30249
+
29874
30250
  function PointGraph(props) {
29875
30251
  const {
29876
30252
  dispatch
@@ -30676,6 +31052,10 @@ function interactiveGraphReducer(state, action) {
30676
31052
  return doMoveAll(state, action);
30677
31053
  case MOVE_POINT:
30678
31054
  return doMovePoint(state, action);
31055
+ case MOVE_CENTER:
31056
+ return doMoveCenter(state, action);
31057
+ case MOVE_RADIUS_POINT:
31058
+ return doMoveRadiusPoint(state, action);
30679
31059
  case CHANGE_SNAP_STEP:
30680
31060
  return doChangeSnapStep(state, action);
30681
31061
  case CHANGE_RANGE:
@@ -30815,6 +31195,67 @@ function doMovePoint(state, action) {
30815
31195
  throw new Error("The movePoint action is only for point and polygon graphs");
30816
31196
  }
30817
31197
  }
31198
+ function doMoveCenter(state, action) {
31199
+ switch (state.type) {
31200
+ case "circle":
31201
+ {
31202
+ // Constrain the center of the circle to the chart range
31203
+ const constrainedCenter = bound$1({
31204
+ snapStep: state.snapStep,
31205
+ range: state.range,
31206
+ point: action.destination
31207
+ });
31208
+
31209
+ // Reposition the radius point based on the new center
31210
+ // (spread to make sure we're not going to mutate anything)
31211
+ const newRadiusPoint = [...mafs.vec.add(state.radiusPoint, mafs.vec.sub(constrainedCenter, state.center))];
31212
+
31213
+ // Try to position the radius handle in a visible spot
31214
+ // if it otherwise would be off the chart
31215
+ // ex: if the handle is on the right and we move the center
31216
+ // to the rightmost position, move the handle to the left
31217
+ const [xMin, xMax] = state.range[0];
31218
+ const [radX] = newRadiusPoint;
31219
+ if (radX < xMin || radX > xMax) {
31220
+ const xJumpDist = (radX - constrainedCenter[0]) * 2;
31221
+ const possibleNewX = radX - xJumpDist;
31222
+ if (possibleNewX >= xMin && possibleNewX <= xMax) {
31223
+ newRadiusPoint[0] = possibleNewX;
31224
+ }
31225
+ }
31226
+ return {
31227
+ ...state,
31228
+ hasBeenInteractedWith: true,
31229
+ center: constrainedCenter,
31230
+ radiusPoint: newRadiusPoint
31231
+ };
31232
+ }
31233
+ default:
31234
+ throw new Error("The doMoveCenter action is only for circle graphs");
31235
+ }
31236
+ }
31237
+ function doMoveRadiusPoint(state, action) {
31238
+ switch (state.type) {
31239
+ case "circle":
31240
+ {
31241
+ const [xMin, xMax] = state.range[0];
31242
+ const nextRadiusPoint = [
31243
+ // Constrain to graph range
31244
+ // The +0 is to convert -0 to +0
31245
+ Math.min(Math.max(xMin, action.destination[0] + 0), xMax), state.center[1]];
31246
+ if (___default["default"].isEqual(nextRadiusPoint, state.center)) {
31247
+ return state;
31248
+ }
31249
+ return {
31250
+ ...state,
31251
+ hasBeenInteractedWith: true,
31252
+ radiusPoint: nextRadiusPoint
31253
+ };
31254
+ }
31255
+ default:
31256
+ throw new Error("The doMoveRadiusPoint action is only for circle graphs");
31257
+ }
31258
+ }
30818
31259
  function doChangeSnapStep(state, action) {
30819
31260
  if (
30820
31261
  // Deep equality check since these are arrays
@@ -30933,235 +31374,6 @@ function setAtIndex(args) {
30933
31374
  return copy;
30934
31375
  }
30935
31376
 
30936
- function initializeGraphState(params) {
30937
- const {
30938
- graph,
30939
- step,
30940
- snapStep,
30941
- range,
30942
- markings
30943
- } = params;
30944
- const shared = {
30945
- hasBeenInteractedWith: false,
30946
- range,
30947
- snapStep,
30948
- markings
30949
- };
30950
- switch (graph.type) {
30951
- case "segment":
30952
- return {
30953
- ...shared,
30954
- type: "segment",
30955
- coords: getDefaultSegments({
30956
- graph,
30957
- step,
30958
- range
30959
- })
30960
- };
30961
- case "linear":
30962
- case "linear-system":
30963
- case "ray":
30964
- return {
30965
- ...shared,
30966
- type: graph.type,
30967
- // Linear and ray graphs have a single tuple of points, while a
30968
- // linear system has two tuples of points.
30969
- coords: getLineCoords({
30970
- graph,
30971
- range,
30972
- step
30973
- })
30974
- };
30975
- case "polygon":
30976
- return {
30977
- ...shared,
30978
- type: "polygon",
30979
- showAngles: Boolean(graph.showAngles),
30980
- showSides: Boolean(graph.showSides),
30981
- coords: getPolygonCoords({
30982
- graph,
30983
- range,
30984
- step
30985
- })
30986
- };
30987
- case "point":
30988
- return {
30989
- ...shared,
30990
- type: graph.type,
30991
- coords: getDefaultPoints({
30992
- graph,
30993
- step,
30994
- range
30995
- })
30996
- };
30997
- case "angle":
30998
- case "circle":
30999
- case "sinusoid":
31000
- case "quadratic":
31001
- throw new Error("Mafs not yet implemented for graph type: " + graph.type);
31002
- default:
31003
- throw new wonderStuffCore.UnreachableCaseError(graph);
31004
- }
31005
- }
31006
- const getDefaultPoints = _ref => {
31007
- let {
31008
- graph,
31009
- range,
31010
- step
31011
- } = _ref;
31012
- const numPoints = graph.numPoints || 1;
31013
- let coords = graph.coords?.slice();
31014
- if (coords) {
31015
- return coords;
31016
- }
31017
- switch (numPoints) {
31018
- case 1:
31019
- // Back in the day, one point's coords were in graph.coord
31020
- coords = [graph.coord || [0, 0]];
31021
- break;
31022
- case 2:
31023
- coords = [[-5, 0], [5, 0]];
31024
- break;
31025
- case 3:
31026
- coords = [[-5, 0], [0, 0], [5, 0]];
31027
- break;
31028
- case 4:
31029
- coords = [[-6, 0], [-2, 0], [2, 0], [6, 0]];
31030
- break;
31031
- case 5:
31032
- coords = [[-6, 0], [-3, 0], [0, 0], [3, 0], [6, 0]];
31033
- break;
31034
- case 6:
31035
- coords = [[-5, 0], [-3, 0], [-1, 0], [1, 0], [3, 0], [5, 0]];
31036
- break;
31037
- default:
31038
- coords = [];
31039
- break;
31040
- }
31041
- // Transform coords from their -10 to 10 space to 0 to 1
31042
- // because of the old graph.coord, and also it's easier.
31043
- const newCoords = normalizeCoords(coords, [[-10, 10], [-10, 10]]);
31044
- return normalizePoints(range, step, newCoords);
31045
- };
31046
-
31047
- // TS v4 doesn't narrow return types, while v5 does.
31048
- // Instead of updating to v5, using generic type to relate input and output types.
31049
- function getGradableGraph(state, initialGraph) {
31050
- if (!state.hasBeenInteractedWith) {
31051
- return {
31052
- ...initialGraph
31053
- };
31054
- }
31055
- if (initialGraph.type === "linear-system" && state.type === "linear-system") {
31056
- return {
31057
- ...initialGraph,
31058
- coords: state.coords
31059
- };
31060
- }
31061
- if (state.type === "segment" && initialGraph.type === "segment") {
31062
- return {
31063
- ...initialGraph,
31064
- coords: state.coords
31065
- };
31066
- }
31067
- if (state.type === "linear" && initialGraph.type === "linear") {
31068
- return {
31069
- ...initialGraph,
31070
- coords: state.coords[0]
31071
- };
31072
- }
31073
- if (state.type === "ray" && initialGraph.type === "ray") {
31074
- return {
31075
- ...initialGraph,
31076
- coords: state.coords[0]
31077
- };
31078
- }
31079
- if (state.type === "polygon" && initialGraph.type === "polygon") {
31080
- return {
31081
- ...initialGraph,
31082
- coords: state.coords
31083
- };
31084
- }
31085
- if (state.type === "point" && initialGraph.type === "point") {
31086
- return {
31087
- ...initialGraph,
31088
- coords: state.coords
31089
- };
31090
- }
31091
- throw new Error("Mafs is not yet implemented for graph type: " + initialGraph.type);
31092
- }
31093
- const getDefaultSegments = _ref2 => {
31094
- let {
31095
- graph,
31096
- range,
31097
- step
31098
- } = _ref2;
31099
- const ys = n => {
31100
- switch (n) {
31101
- case 2:
31102
- return [5, -5];
31103
- case 3:
31104
- return [5, 0, -5];
31105
- case 4:
31106
- return [6, 2, -2, -6];
31107
- case 5:
31108
- return [6, 3, 0, -3, -6];
31109
- case 6:
31110
- return [5, 3, 1, -1, -3, -5];
31111
- default:
31112
- return [5];
31113
- }
31114
- };
31115
- const defaultRange = [[-10, 10], [-10, 10]];
31116
- return ys(graph.numSegments).map(y => {
31117
- let endpoints = [[-5, y], [5, y]];
31118
- endpoints = normalizeCoords(endpoints, defaultRange);
31119
- endpoints = normalizePoints(range, step, endpoints);
31120
- return endpoints;
31121
- });
31122
- };
31123
- const defaultLinearCoords = [[[0.25, 0.75], [0.75, 0.75]], [[0.25, 0.25], [0.75, 0.25]]];
31124
- const getLineCoords = _ref3 => {
31125
- let {
31126
- graph,
31127
- range,
31128
- step
31129
- } = _ref3;
31130
- return (
31131
- // Return two lines for a linear system, one for a ray or linear
31132
- graph.coords ?? graph.type === "linear-system" ? defaultLinearCoords.map(collinear => normalizePoints(range, step, collinear)) : [normalizePoints(range, step, defaultLinearCoords[0])]
31133
- );
31134
- };
31135
- const getPolygonCoords = _ref4 => {
31136
- let {
31137
- graph,
31138
- range,
31139
- step
31140
- } = _ref4;
31141
- let coords = graph.coords?.slice();
31142
- if (coords) {
31143
- return coords;
31144
- }
31145
- const n = graph.numSides || 3;
31146
- if (n === "unlimited") {
31147
- coords = [];
31148
- } else {
31149
- const angle = 2 * Math.PI / n;
31150
- const offset = (1 / n - 1 / 2) * Math.PI;
31151
-
31152
- // TODO(alex): Generalize this to more than just triangles so that
31153
- // all polygons have whole number side lengths if snapping to sides
31154
- const radius = graph.snapTo === "sides" ? Math.sqrt(3) / 3 * 7 : 4;
31155
-
31156
- // Generate coords of a regular polygon with n sides
31157
- coords = [...Array(n).keys()].map(i => [radius * Math.cos(i * angle + offset), radius * Math.sin(i * angle + offset)]);
31158
- }
31159
- coords = normalizeCoords(coords, [[-10, 10], [-10, 10]]);
31160
- const snapToGrid = !["angles", "sides"].includes(graph.snapTo || "");
31161
- coords = normalizePoints(range, step, coords, /* noSnap */!snapToGrid);
31162
- return coords;
31163
- };
31164
-
31165
31377
  const renderGraph = props => {
31166
31378
  const {
31167
31379
  state,
@@ -31198,20 +31410,46 @@ const renderGraph = props => {
31198
31410
  dispatch: dispatch
31199
31411
  });
31200
31412
  case "circle":
31201
- throw new Error("the circle graph type is not yet implemented");
31413
+ return /*#__PURE__*/React__namespace.createElement(CircleGraph, {
31414
+ graphState: state,
31415
+ dispatch: dispatch
31416
+ });
31202
31417
  default:
31203
31418
  return new wonderStuffCore.UnreachableCaseError(type);
31204
31419
  }
31205
31420
  };
31421
+
31422
+ // Rather than be tightly bound to how data was structured in
31423
+ // the legacy interactive graph, this lets us store state
31424
+ // however we want and we just transform it before handing it off
31425
+ // the the parent InteractiveGraph
31426
+ function mafsStateToInteractiveGraph(state) {
31427
+ if (state.graph.type === "circle") {
31428
+ return {
31429
+ ...state,
31430
+ graph: {
31431
+ ...state.graph,
31432
+ radius: getRadius(state.graph)
31433
+ }
31434
+ };
31435
+ }
31436
+ return {
31437
+ ...state
31438
+ };
31439
+ }
31206
31440
  const StatefulMafsGraph = /*#__PURE__*/React__namespace.forwardRef((props, ref) => {
31207
31441
  const [state, dispatch] = React__namespace.useReducer(interactiveGraphReducer, props, initializeGraphState);
31208
31442
  React.useImperativeHandle(ref, () => ({
31209
31443
  getUserInput: () => getGradableGraph(state, props.graph)
31210
31444
  }));
31211
- return /*#__PURE__*/React__namespace.createElement(MafsGraph, _extends({
31445
+ function onChange(next) {
31446
+ props.onChange(mafsStateToInteractiveGraph(next));
31447
+ }
31448
+ return /*#__PURE__*/React__namespace.createElement(MafsGraph, _extends({}, props, {
31212
31449
  state: state,
31213
- dispatch: dispatch
31214
- }, props));
31450
+ dispatch: dispatch,
31451
+ onChange: onChange
31452
+ }));
31215
31453
  });
31216
31454
  const MafsGraph = props => {
31217
31455
  const {
@@ -43385,7 +43623,7 @@ const init = function (options) {
43385
43623
 
43386
43624
  // This file is processed by a Rollup plugin (replace) to inject the production
43387
43625
  const libName = "@khanacademy/perseus";
43388
- const libVersion = "22.3.0";
43626
+ const libVersion = "22.4.1";
43389
43627
  perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
43390
43628
 
43391
43629
  const apiVersion = {