@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/es/index.js CHANGED
@@ -6,7 +6,7 @@ import { View, RenderStateRoot, useUniqueIdWithMock, UniqueIDProvider, Text as T
6
6
  import Tooltip$1 from '@khanacademy/wonder-blocks-tooltip';
7
7
  import classNames from 'classnames';
8
8
  import * as React from 'react';
9
- import React__default, { useContext, useState, useEffect, useRef, createContext, useImperativeHandle } from 'react';
9
+ import React__default, { useContext, useState, useEffect, useRef, createContext, forwardRef, useImperativeHandle } from 'react';
10
10
  import _, { debounce as debounce$1 } from 'underscore';
11
11
  import Clickable from '@khanacademy/wonder-blocks-clickable';
12
12
  import { Popover, PopoverContentCore, PopoverContent } from '@khanacademy/wonder-blocks-popover';
@@ -26,10 +26,10 @@ import Button from '@khanacademy/wonder-blocks-button';
26
26
  import { Strut } from '@khanacademy/wonder-blocks-layout';
27
27
  import Banner from '@khanacademy/wonder-blocks-banner';
28
28
  import { OptionItem, SingleSelect, MultiSelect } from '@khanacademy/wonder-blocks-dropdown';
29
- import caretDown from '@phosphor-icons/core/assets/regular/caret-down.svg';
30
- import caretUp from '@phosphor-icons/core/assets/regular/caret-up.svg';
29
+ import caretDown from '@phosphor-icons/core/regular/caret-down.svg';
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$3, Point as Point$2, useMovable, Text as Text$1, Polygon, Coordinates, Mafs } from 'mafs';
32
+ import { vec, Line as Line$3, Point as Point$2, useMovable, Text as Text$1, Polygon, Circle as Circle$1, 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';
@@ -341,7 +341,7 @@ class InnerMathInput extends React.Component {
341
341
  locale
342
342
  } = this.context;
343
343
  // Initialize MathQuill.MathField instance
344
- this.__mathField = createMathField(this.__mathFieldWrapperRef, this.props.mathInputStrings, baseConfig => _extends({}, baseConfig, {
344
+ this.__mathField = createMathField(this.__mathFieldWrapperRef, locale, this.props.mathInputStrings, baseConfig => _extends({}, baseConfig, {
345
345
  handlers: {
346
346
  edit: debounce(mathField => {
347
347
  // This handler is guaranteed to be called on change, but
@@ -28726,6 +28726,7 @@ function Arrowhead(props) {
28726
28726
  var _props$color;
28727
28727
  const [point] = useTransformVectorsToPixels(props.tip);
28728
28728
  return /*#__PURE__*/React.createElement("g", {
28729
+ className: "interactive-graph-arrowhead",
28729
28730
  transform: `translate(${point[0]} ${point[1]}) rotate(${-props.angle})`
28730
28731
  }, /*#__PURE__*/React.createElement("g", {
28731
28732
  transform: "translate(-1.5)"
@@ -28740,49 +28741,7 @@ function Arrowhead(props) {
28740
28741
  })));
28741
28742
  }
28742
28743
 
28743
- function SVGLine(props) {
28744
- const {
28745
- start,
28746
- end,
28747
- style,
28748
- className,
28749
- testId
28750
- } = props;
28751
- return /*#__PURE__*/React.createElement("line", {
28752
- x1: start[0],
28753
- y1: start[1],
28754
- x2: end[0],
28755
- y2: end[1],
28756
- style: style,
28757
- className: className,
28758
- "data-testid": testId
28759
- });
28760
- }
28761
-
28762
- function Vector(props) {
28763
- const {
28764
- tail,
28765
- tip,
28766
- color,
28767
- style
28768
- } = props;
28769
- const [tailPx, tipPx] = useTransformVectorsToPixels(tail, tip);
28770
- const direction = vec.sub(tip, tail);
28771
- return /*#__PURE__*/React.createElement("g", {
28772
- style: {
28773
- stroke: color,
28774
- strokeWidth: 2
28775
- }
28776
- }, /*#__PURE__*/React.createElement(SVGLine, {
28777
- start: tailPx,
28778
- end: tipPx,
28779
- style: style
28780
- }), /*#__PURE__*/React.createElement(Arrowhead, {
28781
- tip: tip,
28782
- angle: angleDegrees(direction)
28783
- }));
28784
- }
28785
- function angleDegrees([x, y]) {
28744
+ function calculateAngleInDegrees([x, y]) {
28786
28745
  return Math.atan2(y, x) * 180 / Math.PI;
28787
28746
  }
28788
28747
 
@@ -28824,10 +28783,57 @@ const getIntersectionOfRayWithBox = (initialPoint, throughPoint, box) => {
28824
28783
  return [xMax, yAtXMax];
28825
28784
  }
28826
28785
  };
28827
- const getLines = points => points.map((point, i) => {
28828
- const next = points[(i + 1) % points.length];
28829
- return [point, next];
28830
- });
28786
+ const getLines = points => {
28787
+ return points.map((point, i) => {
28788
+ const next = points[(i + 1) % points.length];
28789
+ return [point, next];
28790
+ });
28791
+ };
28792
+
28793
+ function SVGLine(props) {
28794
+ const {
28795
+ start,
28796
+ end,
28797
+ style,
28798
+ className,
28799
+ testId
28800
+ } = props;
28801
+ return /*#__PURE__*/React.createElement("line", {
28802
+ x1: start[0],
28803
+ y1: start[1],
28804
+ x2: end[0],
28805
+ y2: end[1],
28806
+ style: style,
28807
+ className: className,
28808
+ "data-testid": testId
28809
+ });
28810
+ }
28811
+
28812
+ function Vector(props) {
28813
+ const {
28814
+ tail,
28815
+ tip,
28816
+ color,
28817
+ style
28818
+ } = props;
28819
+ const [tailPx, tipPx] = useTransformVectorsToPixels(tail, tip);
28820
+ const direction = vec.sub(tip, tail);
28821
+ const angle = calculateAngleInDegrees(direction);
28822
+ return /*#__PURE__*/React.createElement("g", {
28823
+ style: {
28824
+ stroke: color,
28825
+ strokeWidth: 2
28826
+ }
28827
+ }, /*#__PURE__*/React.createElement(SVGLine, {
28828
+ start: tailPx,
28829
+ end: tipPx,
28830
+ style: style
28831
+ }), /*#__PURE__*/React.createElement(Arrowhead, {
28832
+ angle: angle,
28833
+ tip: tip,
28834
+ color: color
28835
+ }));
28836
+ }
28831
28837
 
28832
28838
  const LockedLine = props => {
28833
28839
  const {
@@ -28843,27 +28849,38 @@ const LockedLine = props => {
28843
28849
  let line;
28844
28850
  if (kind === "ray") {
28845
28851
  // Rays extend to the end of the graph in one direction.
28846
- const endExtend = getIntersectionOfRayWithBox(point2.coord, point1.coord, range);
28852
+ const extendedPoint = getIntersectionOfRayWithBox(point2.coord, point1.coord, range);
28847
28853
  line = /*#__PURE__*/React.createElement(Vector, {
28848
28854
  tail: point1.coord,
28849
- tip: endExtend,
28855
+ tip: extendedPoint,
28850
28856
  color: lockedFigureColors[color$1],
28851
28857
  style: {
28852
- strokeDasharray: lineStyle === "dashed" ?
28853
- // TODO(lems-1930): Uncomment this line when the
28854
- // dashed style is updated in Mafs.
28855
- // ? "var(--mafs-line-stroke-dash-style)"
28856
- "4, 3" : undefined
28858
+ strokeDasharray: lineStyle === "dashed" ? "var(--mafs-line-stroke-dash-style)" : undefined
28857
28859
  }
28858
28860
  });
28859
28861
  } else {
28860
28862
  const LineType = kind === "segment" ? Line$3.Segment : Line$3.ThroughPoints;
28861
- line = /*#__PURE__*/React.createElement(LineType, {
28863
+ let arrowTip = kind === "segment" ? point2.coord : getIntersectionOfRayWithBox(point2.coord, point1.coord, range);
28864
+ const direction = vec.sub(point2.coord, point1.coord);
28865
+ let angle = calculateAngleInDegrees(direction);
28866
+ const startArrowHead = kind !== "segment" && /*#__PURE__*/React.createElement(Arrowhead, {
28867
+ angle: angle,
28868
+ tip: arrowTip,
28869
+ color: lockedFigureColors[color$1]
28870
+ });
28871
+ arrowTip = kind === "segment" ? point1.coord : getIntersectionOfRayWithBox(point1.coord, point2.coord, range);
28872
+ angle = angle > 180 ? angle - 180 : angle + 180;
28873
+ const endArrowHead = kind !== "segment" && /*#__PURE__*/React.createElement(Arrowhead, {
28874
+ angle: angle,
28875
+ tip: arrowTip,
28876
+ color: lockedFigureColors[color$1]
28877
+ });
28878
+ line = /*#__PURE__*/React.createElement(React.Fragment, null, startArrowHead, /*#__PURE__*/React.createElement(LineType, {
28862
28879
  point1: point1.coord,
28863
28880
  point2: point2.coord,
28864
28881
  color: lockedFigureColors[color$1],
28865
28882
  style: lineStyle
28866
- });
28883
+ }), endArrowHead);
28867
28884
  }
28868
28885
  return /*#__PURE__*/React.createElement("g", {
28869
28886
  className: kind === "ray" ? "locked-ray" : "locked-line"
@@ -28969,6 +28986,20 @@ function movePoint(index, destination) {
28969
28986
  destination
28970
28987
  };
28971
28988
  }
28989
+ const MOVE_CENTER = "move-center";
28990
+ function moveCenter(destination) {
28991
+ return {
28992
+ type: MOVE_CENTER,
28993
+ destination
28994
+ };
28995
+ }
28996
+ const MOVE_RADIUS_POINT = "MOVE_RADIUS_POINT";
28997
+ function moveRadiusPoint(destination) {
28998
+ return {
28999
+ type: MOVE_RADIUS_POINT,
29000
+ destination
29001
+ };
29002
+ }
28972
29003
  const CHANGE_SNAP_STEP = "change-snap-step";
28973
29004
  function changeSnapStep(snapStep) {
28974
29005
  return {
@@ -29015,6 +29046,192 @@ function snap(snapStep, point) {
29015
29046
  return [Math.round(requestedX / snapX) * snapX, Math.round(requestedY / snapY) * snapY];
29016
29047
  }
29017
29048
 
29049
+ // The hitbox size of 48px by 48px is preserved from the legacy interactive
29050
+ // graph.
29051
+ const hitboxSizePx = 48;
29052
+
29053
+ // MovablePointView is a purely presentational component (i.e. it is a pure
29054
+ // function with no state or effects) that renders the SVG for a movable point
29055
+ // on an interactive graph.
29056
+ //
29057
+ // It has two modes for managing tabbing / focus: "controlled" (where the caller
29058
+ // manages the display of the focus ring, and the point itself cannot be
29059
+ // focused) and "uncontrolled" (where the point is focusable, and the browser
29060
+ // manages the focus state and styling). For context on why we did this, see
29061
+ // the description of https://github.com/Khan/perseus/pull/1240
29062
+ const MovablePointView = /*#__PURE__*/forwardRef((props, hitboxRef) => {
29063
+ var _Object$entries$find$, _Object$entries$find;
29064
+ const {
29065
+ range,
29066
+ markings,
29067
+ showTooltips
29068
+ } = useGraphConfig();
29069
+ const {
29070
+ point,
29071
+ color: color$1 = color.blue,
29072
+ dragging,
29073
+ focusBehavior
29074
+ } = props;
29075
+
29076
+ // WB Tooltip requires a color name for the background color.
29077
+ // Since the color in props is a hex value, a reverse lookup is needed.
29078
+ const wbColorName = (_Object$entries$find$ = (_Object$entries$find = Object.entries(color).find(([_, value]) => value === color$1)) == null ? void 0 : _Object$entries$find[0]) != null ? _Object$entries$find$ : "blue";
29079
+ const pointClasses = `movable-point ${dragging ? "movable-point--dragging" : ""} ${focusClass(focusBehavior)}`;
29080
+ const [[x, y]] = useTransformVectorsToPixels(point);
29081
+ const [xMin, xMax] = range[0];
29082
+ const [yMin, yMax] = range[1];
29083
+ const [[verticalStartX]] = useTransformVectorsToPixels([xMin, 0]);
29084
+ const [[verticalEndX]] = useTransformVectorsToPixels([xMax, 0]);
29085
+ const [[_, horizontalStartY]] = useTransformVectorsToPixels([0, yMin]);
29086
+ const [[__, horizontalEndY]] = useTransformVectorsToPixels([0, yMax]);
29087
+ const showHairlines = dragging && markings !== "none";
29088
+ const hairlines = /*#__PURE__*/React.createElement("g", null, /*#__PURE__*/React.createElement("line", {
29089
+ x1: verticalStartX,
29090
+ y1: y,
29091
+ x2: verticalEndX,
29092
+ y2: y,
29093
+ stroke: color$1
29094
+ }), /*#__PURE__*/React.createElement("line", {
29095
+ x1: x,
29096
+ y1: horizontalStartY,
29097
+ x2: x,
29098
+ y2: horizontalEndY,
29099
+ stroke: color$1
29100
+ }));
29101
+ const svgForPoint = /*#__PURE__*/React.createElement("g", {
29102
+ ref: hitboxRef,
29103
+ className: pointClasses,
29104
+ style: {
29105
+ "--movable-point-color": color$1
29106
+ },
29107
+ "data-testid": "movable-point",
29108
+ tabIndex: tabIndex(focusBehavior)
29109
+ }, /*#__PURE__*/React.createElement("circle", {
29110
+ className: "movable-point-hitbox",
29111
+ r: hitboxSizePx / 2,
29112
+ cx: x,
29113
+ cy: y
29114
+ }), /*#__PURE__*/React.createElement("circle", {
29115
+ className: "movable-point-halo",
29116
+ cx: x,
29117
+ cy: y
29118
+ }), /*#__PURE__*/React.createElement("circle", {
29119
+ className: "movable-point-ring",
29120
+ cx: x,
29121
+ cy: y
29122
+ }), /*#__PURE__*/React.createElement("circle", {
29123
+ className: "movable-point-focus-outline",
29124
+ cx: x,
29125
+ cy: y
29126
+ }), /*#__PURE__*/React.createElement("circle", {
29127
+ className: "movable-point-center",
29128
+ cx: x,
29129
+ cy: y,
29130
+ style: {
29131
+ fill: color$1
29132
+ },
29133
+ "data-testid": "movable-point__center"
29134
+ }));
29135
+ return /*#__PURE__*/React.createElement(React.Fragment, null, showHairlines && hairlines, showTooltips ? /*#__PURE__*/React.createElement(Tooltip$1, {
29136
+ autoUpdate: true,
29137
+ backgroundColor: wbColorName,
29138
+ content: `(${point[0]}, ${point[1]})`,
29139
+ contentStyle: {
29140
+ color: "white"
29141
+ }
29142
+ }, svgForPoint) : svgForPoint);
29143
+ });
29144
+ function focusClass(config) {
29145
+ if (config.type === "controlled" && config.showFocusRing) {
29146
+ return "movable-point--focus";
29147
+ }
29148
+ return "";
29149
+ }
29150
+ function tabIndex(config) {
29151
+ if (config.type === "uncontrolled") {
29152
+ return config.tabIndex;
29153
+ }
29154
+ return undefined;
29155
+ }
29156
+
29157
+ const Segment = props => {
29158
+ const {
29159
+ onMoveLine,
29160
+ color,
29161
+ points: [start, end],
29162
+ extend
29163
+ } = props;
29164
+
29165
+ // We use separate focusableHandle elements, instead of letting the movable
29166
+ // points themselves be focusable, to allow the tab order of the points to
29167
+ // be different from the rendering order. We had to solve for the following
29168
+ // constraints:
29169
+ // - SVG has no equivalent of z-index, so the order of elements in the
29170
+ // document determines the order in which they're painted. We want the
29171
+ // movable line segment to render behind its endpoints (it looks weird
29172
+ // otherwise) so we have to render the line first.
29173
+ // - There isn't a browser-native way to customize tab order, other than
29174
+ // setting tabindex > 0. But that bumps elements to the front of the
29175
+ // tab order for the entire page, which is not what we want.
29176
+ const {
29177
+ visiblePoint: visiblePoint1,
29178
+ focusableHandle: focusableHandle1
29179
+ } = useControlPoint(start, color, p => props.onMovePoint(0, p));
29180
+ const {
29181
+ visiblePoint: visiblePoint2,
29182
+ focusableHandle: focusableHandle2
29183
+ } = useControlPoint(end, color, p => props.onMovePoint(1, p));
29184
+ return /*#__PURE__*/React.createElement(React.Fragment, null, focusableHandle1, /*#__PURE__*/React.createElement(MovableLine, {
29185
+ start: start,
29186
+ end: end,
29187
+ stroke: color,
29188
+ extend: extend,
29189
+ onMove: onMoveLine
29190
+ }), focusableHandle2, visiblePoint1, visiblePoint2);
29191
+ };
29192
+ function useControlPoint(point, color, onMovePoint) {
29193
+ const {
29194
+ snapStep
29195
+ } = useGraphConfig();
29196
+ const [focused, setFocused] = useState(false);
29197
+ const keyboardHandleRef = useRef(null);
29198
+ useMovable({
29199
+ gestureTarget: keyboardHandleRef,
29200
+ point,
29201
+ onMove: onMovePoint,
29202
+ constrain: p => snap(snapStep, p)
29203
+ });
29204
+ const visiblePointRef = useRef(null);
29205
+ const {
29206
+ dragging
29207
+ } = useMovable({
29208
+ gestureTarget: visiblePointRef,
29209
+ point,
29210
+ onMove: onMovePoint,
29211
+ constrain: p => snap(snapStep, p)
29212
+ });
29213
+ const focusableHandle = /*#__PURE__*/React.createElement("g", {
29214
+ "data-testid": "movable-point__focusable-handle",
29215
+ tabIndex: 0,
29216
+ ref: keyboardHandleRef,
29217
+ onFocus: () => setFocused(true),
29218
+ onBlur: () => setFocused(false)
29219
+ });
29220
+ const visiblePoint = /*#__PURE__*/React.createElement(MovablePointView, {
29221
+ point: point,
29222
+ dragging: dragging,
29223
+ color: color,
29224
+ ref: visiblePointRef,
29225
+ focusBehavior: {
29226
+ type: "controlled",
29227
+ showFocusRing: focused
29228
+ }
29229
+ });
29230
+ return {
29231
+ focusableHandle,
29232
+ visiblePoint
29233
+ };
29234
+ }
29018
29235
  const defaultStroke = "var(--movable-line-stroke-color)";
29019
29236
  const MovableLine = props => {
29020
29237
  const {
@@ -29027,12 +29244,13 @@ const MovableLine = props => {
29027
29244
  const midpoint = vec.midpoint(start, end);
29028
29245
  const [startPtPx, endPtPx] = useTransformVectorsToPixels(start, end);
29029
29246
  const {
29247
+ range,
29030
29248
  graphDimensionsInPixels
29031
29249
  } = useGraphConfig();
29032
29250
  let startExtend = undefined;
29033
29251
  let endExtend = undefined;
29034
29252
  if (extend) {
29035
- const trimmedRange = trimRange(extend.range, graphDimensionsInPixels);
29253
+ const trimmedRange = trimRange(range, graphDimensionsInPixels);
29036
29254
  startExtend = extend.start ? getIntersectionOfRayWithBox(start, end, trimmedRange) : undefined;
29037
29255
  endExtend = extend.end ? getIntersectionOfRayWithBox(end, start, trimmedRange) : undefined;
29038
29256
  }
@@ -29111,101 +29329,6 @@ function size(interval) {
29111
29329
  return interval[1] - interval[0];
29112
29330
  }
29113
29331
 
29114
- // The hitbox size of 48px by 48px is preserved from the legacy interactive
29115
- // graph.
29116
- const hitboxSizePx = 48;
29117
- const StyledMovablePoint = props => {
29118
- var _Object$entries$find$, _Object$entries$find;
29119
- const {
29120
- range,
29121
- snapStep,
29122
- markings,
29123
- showTooltips
29124
- } = useGraphConfig();
29125
- const hitboxRef = useRef(null);
29126
- const {
29127
- point,
29128
- onMove,
29129
- color: color$1 = color.blue
29130
- } = props;
29131
-
29132
- // WB Tooltip requires a color name for the background color.
29133
- // Since the color in props is a hex value, a reverse lookup is needed.
29134
- const wbColorName = (_Object$entries$find$ = (_Object$entries$find = Object.entries(color).find(([_, value]) => value === color$1)) == null ? void 0 : _Object$entries$find[0]) != null ? _Object$entries$find$ : "blue";
29135
- const {
29136
- dragging
29137
- } = useMovable({
29138
- gestureTarget: hitboxRef,
29139
- point,
29140
- onMove,
29141
- constrain: p => snap(snapStep, p)
29142
- });
29143
- const pointClasses = `movable-point ${dragging ? "movable-point--dragging" : ""}`;
29144
- const [[x, y]] = useTransformVectorsToPixels(point);
29145
- const [xMin, xMax] = range[0];
29146
- const [yMin, yMax] = range[1];
29147
- const [[verticalStartX]] = useTransformVectorsToPixels([xMin, 0]);
29148
- const [[verticalEndX]] = useTransformVectorsToPixels([xMax, 0]);
29149
- const [[_, horizontalStartY]] = useTransformVectorsToPixels([0, yMin]);
29150
- const [[__, horizontalEndY]] = useTransformVectorsToPixels([0, yMax]);
29151
- const showHairlines = dragging && markings !== "none";
29152
- const hairlines = /*#__PURE__*/React.createElement("g", null, /*#__PURE__*/React.createElement("line", {
29153
- x1: verticalStartX,
29154
- y1: y,
29155
- x2: verticalEndX,
29156
- y2: y,
29157
- stroke: color$1
29158
- }), /*#__PURE__*/React.createElement("line", {
29159
- x1: x,
29160
- y1: horizontalStartY,
29161
- x2: x,
29162
- y2: horizontalEndY,
29163
- stroke: color$1
29164
- }));
29165
- const svgForPoint = /*#__PURE__*/React.createElement("g", {
29166
- ref: hitboxRef,
29167
- className: pointClasses,
29168
- tabIndex: 0,
29169
- style: {
29170
- "--movable-point-color": color$1
29171
- },
29172
- "data-testid": "movable-point"
29173
- }, /*#__PURE__*/React.createElement("circle", {
29174
- className: "movable-point-hitbox",
29175
- r: hitboxSizePx / 2,
29176
- cx: x,
29177
- cy: y
29178
- }), /*#__PURE__*/React.createElement("circle", {
29179
- className: "movable-point-halo",
29180
- cx: x,
29181
- cy: y
29182
- }), /*#__PURE__*/React.createElement("circle", {
29183
- className: "movable-point-ring",
29184
- cx: x,
29185
- cy: y
29186
- }), /*#__PURE__*/React.createElement("circle", {
29187
- className: "movable-point-focus-outline",
29188
- cx: x,
29189
- cy: y
29190
- }), /*#__PURE__*/React.createElement("circle", {
29191
- className: "movable-point-center",
29192
- cx: x,
29193
- cy: y,
29194
- style: {
29195
- fill: color$1
29196
- },
29197
- "data-testid": "movable-point__center"
29198
- }));
29199
- return /*#__PURE__*/React.createElement(React.Fragment, null, showHairlines && hairlines, showTooltips ? /*#__PURE__*/React.createElement(Tooltip$1, {
29200
- autoUpdate: true,
29201
- backgroundColor: wbColorName,
29202
- content: `(${point[0]}, ${point[1]})`,
29203
- contentStyle: {
29204
- color: "white"
29205
- }
29206
- }, svgForPoint) : svgForPoint);
29207
- };
29208
-
29209
29332
  const SegmentGraph = props => {
29210
29333
  const {
29211
29334
  dispatch
@@ -29213,7 +29336,7 @@ const SegmentGraph = props => {
29213
29336
  const {
29214
29337
  coords: segments
29215
29338
  } = props.graphState;
29216
- return /*#__PURE__*/React.createElement(React.Fragment, null, segments == null ? void 0 : segments.map((segment, i) => /*#__PURE__*/React.createElement(SegmentView, {
29339
+ return /*#__PURE__*/React.createElement(React.Fragment, null, segments == null ? void 0 : segments.map((segment, i) => /*#__PURE__*/React.createElement(Segment, {
29217
29340
  key: i,
29218
29341
  points: segment,
29219
29342
  onMoveLine: delta => {
@@ -29224,27 +29347,6 @@ const SegmentGraph = props => {
29224
29347
  }
29225
29348
  })));
29226
29349
  };
29227
- const SegmentView = props => {
29228
- const {
29229
- onMoveLine: onMoveSegment,
29230
- points: [start, end]
29231
- } = props;
29232
- return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(MovableLine, {
29233
- start: start,
29234
- end: end,
29235
- onMove: onMoveSegment
29236
- }), /*#__PURE__*/React.createElement(StyledMovablePoint, {
29237
- point: start,
29238
- onMove: newPoint => {
29239
- props.onMovePoint(0, newPoint);
29240
- }
29241
- }), /*#__PURE__*/React.createElement(StyledMovablePoint, {
29242
- point: end,
29243
- onMove: newPoint => {
29244
- props.onMovePoint(1, newPoint);
29245
- }
29246
- }));
29247
- };
29248
29350
 
29249
29351
  const LinearGraph = props => {
29250
29352
  const {
@@ -29254,49 +29356,19 @@ const LinearGraph = props => {
29254
29356
  coords: lines
29255
29357
  } = props.graphState;
29256
29358
  const colors = ["var(--movable-line-stroke-color)", "var(--mafs-violet)"];
29257
- return /*#__PURE__*/React.createElement(React.Fragment, null, lines == null ? void 0 : lines.map((line, i) => /*#__PURE__*/React.createElement(LineView, {
29359
+ return /*#__PURE__*/React.createElement(React.Fragment, null, lines == null ? void 0 : lines.map((line, i) => /*#__PURE__*/React.createElement(Segment, {
29258
29360
  key: i,
29259
29361
  points: line,
29260
29362
  onMoveLine: delta => {
29261
29363
  dispatch(moveLine(i, delta));
29262
29364
  },
29263
- onMovePoint: (endpointIndex, destination) => dispatch(moveControlPoint(endpointIndex, destination, i)),
29264
- stroke: colors[i]
29265
- })));
29266
- };
29267
- const LineView = props => {
29268
- const {
29269
- onMoveLine,
29270
- onMovePoint,
29271
- points: [start, end],
29272
- stroke
29273
- } = props;
29274
- const {
29275
- range
29276
- } = useGraphConfig();
29277
- return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(MovableLine, {
29278
- start: start,
29279
- end: end,
29280
- onMove: onMoveLine,
29281
29365
  extend: {
29282
29366
  start: true,
29283
- end: true,
29284
- range
29367
+ end: true
29285
29368
  },
29286
- stroke: stroke
29287
- }), /*#__PURE__*/React.createElement(StyledMovablePoint, {
29288
- point: start,
29289
- onMove: newPoint => {
29290
- onMovePoint(0, newPoint);
29291
- },
29292
- color: stroke
29293
- }), /*#__PURE__*/React.createElement(StyledMovablePoint, {
29294
- point: end,
29295
- onMove: newPoint => {
29296
- onMovePoint(1, newPoint);
29297
- },
29298
- color: stroke
29299
- }));
29369
+ onMovePoint: (endpointIndex, destination) => dispatch(moveControlPoint(endpointIndex, destination, i)),
29370
+ color: colors[i]
29371
+ })));
29300
29372
  };
29301
29373
 
29302
29374
  const RayGraph = props => {
@@ -29304,30 +29376,21 @@ const RayGraph = props => {
29304
29376
  dispatch
29305
29377
  } = props;
29306
29378
  const {
29307
- coords: lines,
29308
- range
29379
+ coords: lines
29309
29380
  } = props.graphState;
29310
-
29311
- // a ray only has one line
29312
- const [start, end] = lines[0];
29313
29381
  const handleMoveLine = delta => dispatch(moveLine(0, delta));
29314
- const handleMovePoint = (newPoint, pointIndex) => dispatch(moveControlPoint(pointIndex, newPoint, 0));
29315
- return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(MovableLine, {
29316
- start: start,
29317
- end: end,
29318
- onMove: handleMoveLine,
29382
+ const handleMovePoint = (pointIndex, newPoint) => dispatch(moveControlPoint(pointIndex, newPoint, 0));
29383
+ return /*#__PURE__*/React.createElement(Segment
29384
+ // a ray only has one line
29385
+ , {
29386
+ points: lines[0],
29387
+ onMoveLine: handleMoveLine,
29388
+ onMovePoint: handleMovePoint,
29319
29389
  extend: {
29320
- end: true,
29321
29390
  start: false,
29322
- range
29391
+ end: true
29323
29392
  }
29324
- }), /*#__PURE__*/React.createElement(StyledMovablePoint, {
29325
- point: start,
29326
- onMove: newPoint => handleMovePoint(newPoint, 0)
29327
- }), /*#__PURE__*/React.createElement(StyledMovablePoint, {
29328
- point: end,
29329
- onMove: newPoint => handleMovePoint(newPoint, 1)
29330
- }));
29393
+ });
29331
29394
  };
29332
29395
 
29333
29396
  /**
@@ -29491,8 +29554,37 @@ const linesIntersect = ([[a, b], [c, d]], [[p, q], [r, s]]) => {
29491
29554
  };
29492
29555
  const isEven = n => n % 2 === 0;
29493
29556
 
29557
+ const StyledMovablePoint = props => {
29558
+ const {
29559
+ snapStep
29560
+ } = useGraphConfig();
29561
+ const elementRef = useRef(null);
29562
+ const {
29563
+ point,
29564
+ onMove,
29565
+ color: color$1 = color.blue
29566
+ } = props;
29567
+ const {
29568
+ dragging
29569
+ } = useMovable({
29570
+ gestureTarget: elementRef,
29571
+ point,
29572
+ onMove,
29573
+ constrain: p => snap(snapStep, p)
29574
+ });
29575
+ return /*#__PURE__*/React.createElement(MovablePointView, {
29576
+ ref: elementRef,
29577
+ point: point,
29578
+ color: color$1,
29579
+ dragging: dragging,
29580
+ focusBehavior: {
29581
+ type: "uncontrolled",
29582
+ tabIndex: 0
29583
+ }
29584
+ });
29585
+ };
29586
+
29494
29587
  const PolygonGraph = props => {
29495
- var _points$length;
29496
29588
  const [focused, setFocused] = React.useState(false);
29497
29589
  const [hovered, setHovered] = React.useState(false);
29498
29590
  const {
@@ -29505,17 +29597,19 @@ const PolygonGraph = props => {
29505
29597
  range,
29506
29598
  snapStep
29507
29599
  } = props.graphState;
29600
+
29601
+ // TODO(benchristel): can the default set of points be removed here? I don't
29602
+ // think coords can be null.
29508
29603
  const points = coords != null ? coords : [[0, 0]];
29509
- const pointsSum = points.reduce((acc, point) => vec.add(acc, point), [0, 0]);
29510
- const midpoint = pointsSum && vec.scale(pointsSum, 1 / ((_points$length = points.length) != null ? _points$length : 1));
29511
29604
  const ref = React.useRef(null);
29605
+ const dragReferencePoint = points[0];
29512
29606
  const {
29513
29607
  dragging
29514
29608
  } = useMovable({
29515
29609
  gestureTarget: ref,
29516
- point: midpoint,
29610
+ point: dragReferencePoint,
29517
29611
  onMove: newPoint => {
29518
- const delta = vec.sub(newPoint, midpoint);
29612
+ const delta = vec.sub(newPoint, dragReferencePoint);
29519
29613
  dispatch(moveAll(delta));
29520
29614
  },
29521
29615
  constrain: p => snap(snapStep, p)
@@ -29574,6 +29668,272 @@ const PolygonGraph = props => {
29574
29668
  })));
29575
29669
  };
29576
29670
 
29671
+ function initializeGraphState(params) {
29672
+ const {
29673
+ graph,
29674
+ step,
29675
+ snapStep,
29676
+ range,
29677
+ markings
29678
+ } = params;
29679
+ const shared = {
29680
+ hasBeenInteractedWith: false,
29681
+ range,
29682
+ snapStep,
29683
+ markings
29684
+ };
29685
+ switch (graph.type) {
29686
+ case "segment":
29687
+ return _extends({}, shared, {
29688
+ type: "segment",
29689
+ coords: getDefaultSegments({
29690
+ graph,
29691
+ step,
29692
+ range
29693
+ })
29694
+ });
29695
+ case "linear":
29696
+ case "linear-system":
29697
+ case "ray":
29698
+ return _extends({}, shared, {
29699
+ type: graph.type,
29700
+ // Linear and ray graphs have a single tuple of points, while a
29701
+ // linear system has two tuples of points.
29702
+ coords: getLineCoords({
29703
+ graph,
29704
+ range,
29705
+ step
29706
+ })
29707
+ });
29708
+ case "polygon":
29709
+ return _extends({}, shared, {
29710
+ type: "polygon",
29711
+ showAngles: Boolean(graph.showAngles),
29712
+ showSides: Boolean(graph.showSides),
29713
+ coords: getPolygonCoords({
29714
+ graph,
29715
+ range,
29716
+ step
29717
+ })
29718
+ });
29719
+ case "point":
29720
+ return _extends({}, shared, {
29721
+ type: graph.type,
29722
+ coords: getDefaultPoints({
29723
+ graph,
29724
+ step,
29725
+ range
29726
+ })
29727
+ });
29728
+ case "circle":
29729
+ return _extends({}, shared, {
29730
+ type: graph.type,
29731
+ center: [0, 0],
29732
+ radiusPoint: [1, 0]
29733
+ });
29734
+ case "angle":
29735
+ case "sinusoid":
29736
+ case "quadratic":
29737
+ throw new Error("Mafs not yet implemented for graph type: " + graph.type);
29738
+ default:
29739
+ throw new UnreachableCaseError(graph);
29740
+ }
29741
+ }
29742
+ const getDefaultPoints = ({
29743
+ graph,
29744
+ range,
29745
+ step
29746
+ }) => {
29747
+ var _graph$coords;
29748
+ const numPoints = graph.numPoints || 1;
29749
+ let coords = (_graph$coords = graph.coords) == null ? void 0 : _graph$coords.slice();
29750
+ if (coords) {
29751
+ return coords;
29752
+ }
29753
+ switch (numPoints) {
29754
+ case 1:
29755
+ // Back in the day, one point's coords were in graph.coord
29756
+ coords = [graph.coord || [0, 0]];
29757
+ break;
29758
+ case 2:
29759
+ coords = [[-5, 0], [5, 0]];
29760
+ break;
29761
+ case 3:
29762
+ coords = [[-5, 0], [0, 0], [5, 0]];
29763
+ break;
29764
+ case 4:
29765
+ coords = [[-6, 0], [-2, 0], [2, 0], [6, 0]];
29766
+ break;
29767
+ case 5:
29768
+ coords = [[-6, 0], [-3, 0], [0, 0], [3, 0], [6, 0]];
29769
+ break;
29770
+ case 6:
29771
+ coords = [[-5, 0], [-3, 0], [-1, 0], [1, 0], [3, 0], [5, 0]];
29772
+ break;
29773
+ default:
29774
+ coords = [];
29775
+ break;
29776
+ }
29777
+ // Transform coords from their -10 to 10 space to 0 to 1
29778
+ // because of the old graph.coord, and also it's easier.
29779
+ const newCoords = normalizeCoords(coords, [[-10, 10], [-10, 10]]);
29780
+ return normalizePoints(range, step, newCoords);
29781
+ };
29782
+
29783
+ // TS v4 doesn't narrow return types, while v5 does.
29784
+ // Instead of updating to v5, using generic type to relate input and output types.
29785
+ function getGradableGraph(state, initialGraph) {
29786
+ if (!state.hasBeenInteractedWith) {
29787
+ return _extends({}, initialGraph);
29788
+ }
29789
+ if (initialGraph.type === "linear-system" && state.type === "linear-system") {
29790
+ return _extends({}, initialGraph, {
29791
+ coords: state.coords
29792
+ });
29793
+ }
29794
+ if (state.type === "segment" && initialGraph.type === "segment") {
29795
+ return _extends({}, initialGraph, {
29796
+ coords: state.coords
29797
+ });
29798
+ }
29799
+ if (state.type === "linear" && initialGraph.type === "linear") {
29800
+ return _extends({}, initialGraph, {
29801
+ coords: state.coords[0]
29802
+ });
29803
+ }
29804
+ if (state.type === "ray" && initialGraph.type === "ray") {
29805
+ return _extends({}, initialGraph, {
29806
+ coords: state.coords[0]
29807
+ });
29808
+ }
29809
+ if (state.type === "polygon" && initialGraph.type === "polygon") {
29810
+ return _extends({}, initialGraph, {
29811
+ coords: state.coords
29812
+ });
29813
+ }
29814
+ if (state.type === "point" && initialGraph.type === "point") {
29815
+ return _extends({}, initialGraph, {
29816
+ coords: state.coords
29817
+ });
29818
+ }
29819
+ if (state.type === "circle" && initialGraph.type === "circle") {
29820
+ return _extends({}, initialGraph, {
29821
+ center: state.center,
29822
+ radius: getRadius(state)
29823
+ });
29824
+ }
29825
+ throw new Error("Mafs is not yet implemented for graph type: " + initialGraph.type);
29826
+ }
29827
+ const getDefaultSegments = ({
29828
+ graph,
29829
+ range,
29830
+ step
29831
+ }) => {
29832
+ const ys = n => {
29833
+ switch (n) {
29834
+ case 2:
29835
+ return [5, -5];
29836
+ case 3:
29837
+ return [5, 0, -5];
29838
+ case 4:
29839
+ return [6, 2, -2, -6];
29840
+ case 5:
29841
+ return [6, 3, 0, -3, -6];
29842
+ case 6:
29843
+ return [5, 3, 1, -1, -3, -5];
29844
+ default:
29845
+ return [5];
29846
+ }
29847
+ };
29848
+ const defaultRange = [[-10, 10], [-10, 10]];
29849
+ return ys(graph.numSegments).map(y => {
29850
+ let endpoints = [[-5, y], [5, y]];
29851
+ endpoints = normalizeCoords(endpoints, defaultRange);
29852
+ endpoints = normalizePoints(range, step, endpoints);
29853
+ return endpoints;
29854
+ });
29855
+ };
29856
+ const defaultLinearCoords = [[[0.25, 0.75], [0.75, 0.75]], [[0.25, 0.25], [0.75, 0.25]]];
29857
+ const getLineCoords = ({
29858
+ graph,
29859
+ range,
29860
+ step
29861
+ }) => {
29862
+ var _graph$coords2;
29863
+ return (
29864
+ // Return two lines for a linear system, one for a ray or linear
29865
+ ((_graph$coords2 = graph.coords) != null ? _graph$coords2 : graph.type === "linear-system") ? defaultLinearCoords.map(collinear => normalizePoints(range, step, collinear)) : [normalizePoints(range, step, defaultLinearCoords[0])]
29866
+ );
29867
+ };
29868
+ const getPolygonCoords = ({
29869
+ graph,
29870
+ range,
29871
+ step
29872
+ }) => {
29873
+ var _graph$coords3;
29874
+ let coords = (_graph$coords3 = graph.coords) == null ? void 0 : _graph$coords3.slice();
29875
+ if (coords) {
29876
+ return coords;
29877
+ }
29878
+ const n = graph.numSides || 3;
29879
+ if (n === "unlimited") {
29880
+ coords = [];
29881
+ } else {
29882
+ const angle = 2 * Math.PI / n;
29883
+ const offset = (1 / n - 1 / 2) * Math.PI;
29884
+
29885
+ // TODO(alex): Generalize this to more than just triangles so that
29886
+ // all polygons have whole number side lengths if snapping to sides
29887
+ const radius = graph.snapTo === "sides" ? Math.sqrt(3) / 3 * 7 : 4;
29888
+
29889
+ // Generate coords of a regular polygon with n sides
29890
+ coords = [...Array(n).keys()].map(i => [radius * Math.cos(i * angle + offset), radius * Math.sin(i * angle + offset)]);
29891
+ }
29892
+ coords = normalizeCoords(coords, [[-10, 10], [-10, 10]]);
29893
+ const snapToGrid = !["angles", "sides"].includes(graph.snapTo || "");
29894
+ coords = normalizePoints(range, step, coords, /* noSnap */!snapToGrid);
29895
+ return coords;
29896
+ };
29897
+
29898
+ /**
29899
+ * determine radius of a circle graph
29900
+ *
29901
+ * @param graph - the graph object containing the circle
29902
+ * @returns the radius of the circle
29903
+ */
29904
+ function getRadius(graph) {
29905
+ const [centerX, centerY] = graph.center;
29906
+ const [edgeX, edgeY] = graph.radiusPoint;
29907
+ return Math.sqrt(Math.pow(edgeX - centerX, 2) + Math.pow(edgeY - centerY, 2));
29908
+ }
29909
+
29910
+ function CircleGraph(props) {
29911
+ const {
29912
+ dispatch,
29913
+ graphState
29914
+ } = props;
29915
+ const {
29916
+ center,
29917
+ radiusPoint
29918
+ } = graphState;
29919
+ return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Circle$1, {
29920
+ center: center,
29921
+ radius: getRadius(graphState),
29922
+ fillOpacity: 0,
29923
+ color: color.blue
29924
+ }), /*#__PURE__*/React.createElement(StyledMovablePoint, {
29925
+ point: center,
29926
+ onMove: newCenter => {
29927
+ dispatch(moveCenter(newCenter));
29928
+ }
29929
+ }), /*#__PURE__*/React.createElement(StyledMovablePoint, {
29930
+ point: radiusPoint,
29931
+ onMove: newRadiusPoint => {
29932
+ dispatch(moveRadiusPoint(newRadiusPoint));
29933
+ }
29934
+ }));
29935
+ }
29936
+
29577
29937
  function PointGraph(props) {
29578
29938
  const {
29579
29939
  dispatch
@@ -30375,6 +30735,10 @@ function interactiveGraphReducer(state, action) {
30375
30735
  return doMoveAll(state, action);
30376
30736
  case MOVE_POINT:
30377
30737
  return doMovePoint(state, action);
30738
+ case MOVE_CENTER:
30739
+ return doMoveCenter(state, action);
30740
+ case MOVE_RADIUS_POINT:
30741
+ return doMoveRadiusPoint(state, action);
30378
30742
  case CHANGE_SNAP_STEP:
30379
30743
  return doChangeSnapStep(state, action);
30380
30744
  case CHANGE_RANGE:
@@ -30510,6 +30874,65 @@ function doMovePoint(state, action) {
30510
30874
  throw new Error("The movePoint action is only for point and polygon graphs");
30511
30875
  }
30512
30876
  }
30877
+ function doMoveCenter(state, action) {
30878
+ switch (state.type) {
30879
+ case "circle":
30880
+ {
30881
+ // Constrain the center of the circle to the chart range
30882
+ const constrainedCenter = bound$1({
30883
+ snapStep: state.snapStep,
30884
+ range: state.range,
30885
+ point: action.destination
30886
+ });
30887
+
30888
+ // Reposition the radius point based on the new center
30889
+ // (spread to make sure we're not going to mutate anything)
30890
+ const newRadiusPoint = [...vec.add(state.radiusPoint, vec.sub(constrainedCenter, state.center))];
30891
+
30892
+ // Try to position the radius handle in a visible spot
30893
+ // if it otherwise would be off the chart
30894
+ // ex: if the handle is on the right and we move the center
30895
+ // to the rightmost position, move the handle to the left
30896
+ const [xMin, xMax] = state.range[0];
30897
+ const [radX] = newRadiusPoint;
30898
+ if (radX < xMin || radX > xMax) {
30899
+ const xJumpDist = (radX - constrainedCenter[0]) * 2;
30900
+ const possibleNewX = radX - xJumpDist;
30901
+ if (possibleNewX >= xMin && possibleNewX <= xMax) {
30902
+ newRadiusPoint[0] = possibleNewX;
30903
+ }
30904
+ }
30905
+ return _extends({}, state, {
30906
+ hasBeenInteractedWith: true,
30907
+ center: constrainedCenter,
30908
+ radiusPoint: newRadiusPoint
30909
+ });
30910
+ }
30911
+ default:
30912
+ throw new Error("The doMoveCenter action is only for circle graphs");
30913
+ }
30914
+ }
30915
+ function doMoveRadiusPoint(state, action) {
30916
+ switch (state.type) {
30917
+ case "circle":
30918
+ {
30919
+ const [xMin, xMax] = state.range[0];
30920
+ const nextRadiusPoint = [
30921
+ // Constrain to graph range
30922
+ // The +0 is to convert -0 to +0
30923
+ Math.min(Math.max(xMin, action.destination[0] + 0), xMax), state.center[1]];
30924
+ if (_.isEqual(nextRadiusPoint, state.center)) {
30925
+ return state;
30926
+ }
30927
+ return _extends({}, state, {
30928
+ hasBeenInteractedWith: true,
30929
+ radiusPoint: nextRadiusPoint
30930
+ });
30931
+ }
30932
+ default:
30933
+ throw new Error("The doMoveRadiusPoint action is only for circle graphs");
30934
+ }
30935
+ }
30513
30936
  function doChangeSnapStep(state, action) {
30514
30937
  if (
30515
30938
  // Deep equality check since these are arrays
@@ -30621,222 +31044,6 @@ function setAtIndex(args) {
30621
31044
  return copy;
30622
31045
  }
30623
31046
 
30624
- function initializeGraphState(params) {
30625
- const {
30626
- graph,
30627
- step,
30628
- snapStep,
30629
- range,
30630
- markings
30631
- } = params;
30632
- const shared = {
30633
- hasBeenInteractedWith: false,
30634
- range,
30635
- snapStep,
30636
- markings
30637
- };
30638
- switch (graph.type) {
30639
- case "segment":
30640
- return _extends({}, shared, {
30641
- type: "segment",
30642
- coords: getDefaultSegments({
30643
- graph,
30644
- step,
30645
- range
30646
- })
30647
- });
30648
- case "linear":
30649
- case "linear-system":
30650
- case "ray":
30651
- return _extends({}, shared, {
30652
- type: graph.type,
30653
- // Linear and ray graphs have a single tuple of points, while a
30654
- // linear system has two tuples of points.
30655
- coords: getLineCoords({
30656
- graph,
30657
- range,
30658
- step
30659
- })
30660
- });
30661
- case "polygon":
30662
- return _extends({}, shared, {
30663
- type: "polygon",
30664
- showAngles: Boolean(graph.showAngles),
30665
- showSides: Boolean(graph.showSides),
30666
- coords: getPolygonCoords({
30667
- graph,
30668
- range,
30669
- step
30670
- })
30671
- });
30672
- case "point":
30673
- return _extends({}, shared, {
30674
- type: graph.type,
30675
- coords: getDefaultPoints({
30676
- graph,
30677
- step,
30678
- range
30679
- })
30680
- });
30681
- case "angle":
30682
- case "circle":
30683
- case "sinusoid":
30684
- case "quadratic":
30685
- throw new Error("Mafs not yet implemented for graph type: " + graph.type);
30686
- default:
30687
- throw new UnreachableCaseError(graph);
30688
- }
30689
- }
30690
- const getDefaultPoints = ({
30691
- graph,
30692
- range,
30693
- step
30694
- }) => {
30695
- var _graph$coords;
30696
- const numPoints = graph.numPoints || 1;
30697
- let coords = (_graph$coords = graph.coords) == null ? void 0 : _graph$coords.slice();
30698
- if (coords) {
30699
- return coords;
30700
- }
30701
- switch (numPoints) {
30702
- case 1:
30703
- // Back in the day, one point's coords were in graph.coord
30704
- coords = [graph.coord || [0, 0]];
30705
- break;
30706
- case 2:
30707
- coords = [[-5, 0], [5, 0]];
30708
- break;
30709
- case 3:
30710
- coords = [[-5, 0], [0, 0], [5, 0]];
30711
- break;
30712
- case 4:
30713
- coords = [[-6, 0], [-2, 0], [2, 0], [6, 0]];
30714
- break;
30715
- case 5:
30716
- coords = [[-6, 0], [-3, 0], [0, 0], [3, 0], [6, 0]];
30717
- break;
30718
- case 6:
30719
- coords = [[-5, 0], [-3, 0], [-1, 0], [1, 0], [3, 0], [5, 0]];
30720
- break;
30721
- default:
30722
- coords = [];
30723
- break;
30724
- }
30725
- // Transform coords from their -10 to 10 space to 0 to 1
30726
- // because of the old graph.coord, and also it's easier.
30727
- const newCoords = normalizeCoords(coords, [[-10, 10], [-10, 10]]);
30728
- return normalizePoints(range, step, newCoords);
30729
- };
30730
-
30731
- // TS v4 doesn't narrow return types, while v5 does.
30732
- // Instead of updating to v5, using generic type to relate input and output types.
30733
- function getGradableGraph(state, initialGraph) {
30734
- if (!state.hasBeenInteractedWith) {
30735
- return _extends({}, initialGraph);
30736
- }
30737
- if (initialGraph.type === "linear-system" && state.type === "linear-system") {
30738
- return _extends({}, initialGraph, {
30739
- coords: state.coords
30740
- });
30741
- }
30742
- if (state.type === "segment" && initialGraph.type === "segment") {
30743
- return _extends({}, initialGraph, {
30744
- coords: state.coords
30745
- });
30746
- }
30747
- if (state.type === "linear" && initialGraph.type === "linear") {
30748
- return _extends({}, initialGraph, {
30749
- coords: state.coords[0]
30750
- });
30751
- }
30752
- if (state.type === "ray" && initialGraph.type === "ray") {
30753
- return _extends({}, initialGraph, {
30754
- coords: state.coords[0]
30755
- });
30756
- }
30757
- if (state.type === "polygon" && initialGraph.type === "polygon") {
30758
- return _extends({}, initialGraph, {
30759
- coords: state.coords
30760
- });
30761
- }
30762
- if (state.type === "point" && initialGraph.type === "point") {
30763
- return _extends({}, initialGraph, {
30764
- coords: state.coords
30765
- });
30766
- }
30767
- throw new Error("Mafs is not yet implemented for graph type: " + initialGraph.type);
30768
- }
30769
- const getDefaultSegments = ({
30770
- graph,
30771
- range,
30772
- step
30773
- }) => {
30774
- const ys = n => {
30775
- switch (n) {
30776
- case 2:
30777
- return [5, -5];
30778
- case 3:
30779
- return [5, 0, -5];
30780
- case 4:
30781
- return [6, 2, -2, -6];
30782
- case 5:
30783
- return [6, 3, 0, -3, -6];
30784
- case 6:
30785
- return [5, 3, 1, -1, -3, -5];
30786
- default:
30787
- return [5];
30788
- }
30789
- };
30790
- const defaultRange = [[-10, 10], [-10, 10]];
30791
- return ys(graph.numSegments).map(y => {
30792
- let endpoints = [[-5, y], [5, y]];
30793
- endpoints = normalizeCoords(endpoints, defaultRange);
30794
- endpoints = normalizePoints(range, step, endpoints);
30795
- return endpoints;
30796
- });
30797
- };
30798
- const defaultLinearCoords = [[[0.25, 0.75], [0.75, 0.75]], [[0.25, 0.25], [0.75, 0.25]]];
30799
- const getLineCoords = ({
30800
- graph,
30801
- range,
30802
- step
30803
- }) => {
30804
- var _graph$coords2;
30805
- return (
30806
- // Return two lines for a linear system, one for a ray or linear
30807
- ((_graph$coords2 = graph.coords) != null ? _graph$coords2 : graph.type === "linear-system") ? defaultLinearCoords.map(collinear => normalizePoints(range, step, collinear)) : [normalizePoints(range, step, defaultLinearCoords[0])]
30808
- );
30809
- };
30810
- const getPolygonCoords = ({
30811
- graph,
30812
- range,
30813
- step
30814
- }) => {
30815
- var _graph$coords3;
30816
- let coords = (_graph$coords3 = graph.coords) == null ? void 0 : _graph$coords3.slice();
30817
- if (coords) {
30818
- return coords;
30819
- }
30820
- const n = graph.numSides || 3;
30821
- if (n === "unlimited") {
30822
- coords = [];
30823
- } else {
30824
- const angle = 2 * Math.PI / n;
30825
- const offset = (1 / n - 1 / 2) * Math.PI;
30826
-
30827
- // TODO(alex): Generalize this to more than just triangles so that
30828
- // all polygons have whole number side lengths if snapping to sides
30829
- const radius = graph.snapTo === "sides" ? Math.sqrt(3) / 3 * 7 : 4;
30830
-
30831
- // Generate coords of a regular polygon with n sides
30832
- coords = [...Array(n).keys()].map(i => [radius * Math.cos(i * angle + offset), radius * Math.sin(i * angle + offset)]);
30833
- }
30834
- coords = normalizeCoords(coords, [[-10, 10], [-10, 10]]);
30835
- const snapToGrid = !["angles", "sides"].includes(graph.snapTo || "");
30836
- coords = normalizePoints(range, step, coords, /* noSnap */!snapToGrid);
30837
- return coords;
30838
- };
30839
-
30840
31047
  const renderGraph = props => {
30841
31048
  const {
30842
31049
  state,
@@ -30873,20 +31080,42 @@ const renderGraph = props => {
30873
31080
  dispatch: dispatch
30874
31081
  });
30875
31082
  case "circle":
30876
- throw new Error("the circle graph type is not yet implemented");
31083
+ return /*#__PURE__*/React.createElement(CircleGraph, {
31084
+ graphState: state,
31085
+ dispatch: dispatch
31086
+ });
30877
31087
  default:
30878
31088
  return new UnreachableCaseError(type);
30879
31089
  }
30880
31090
  };
31091
+
31092
+ // Rather than be tightly bound to how data was structured in
31093
+ // the legacy interactive graph, this lets us store state
31094
+ // however we want and we just transform it before handing it off
31095
+ // the the parent InteractiveGraph
31096
+ function mafsStateToInteractiveGraph(state) {
31097
+ if (state.graph.type === "circle") {
31098
+ return _extends({}, state, {
31099
+ graph: _extends({}, state.graph, {
31100
+ radius: getRadius(state.graph)
31101
+ })
31102
+ });
31103
+ }
31104
+ return _extends({}, state);
31105
+ }
30881
31106
  const StatefulMafsGraph = /*#__PURE__*/React.forwardRef((props, ref) => {
30882
31107
  const [state, dispatch] = React.useReducer(interactiveGraphReducer, props, initializeGraphState);
30883
31108
  useImperativeHandle(ref, () => ({
30884
31109
  getUserInput: () => getGradableGraph(state, props.graph)
30885
31110
  }));
30886
- return /*#__PURE__*/React.createElement(MafsGraph, _extends({
31111
+ function onChange(next) {
31112
+ props.onChange(mafsStateToInteractiveGraph(next));
31113
+ }
31114
+ return /*#__PURE__*/React.createElement(MafsGraph, _extends({}, props, {
30887
31115
  state: state,
30888
- dispatch: dispatch
30889
- }, props));
31116
+ dispatch: dispatch,
31117
+ onChange: onChange
31118
+ }));
30890
31119
  });
30891
31120
  const MafsGraph = props => {
30892
31121
  const {
@@ -43065,7 +43294,7 @@ const init = function init(options) {
43065
43294
 
43066
43295
  // This file is processed by a Rollup plugin (replace) to inject the production
43067
43296
  const libName = "@khanacademy/perseus";
43068
- const libVersion = "22.3.0";
43297
+ const libVersion = "22.4.1";
43069
43298
  addLibraryVersionToPerseusDebug(libName, libVersion);
43070
43299
 
43071
43300
  const apiVersion = {