@khanacademy/perseus 23.6.0 → 24.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/es/index.css CHANGED
@@ -3518,7 +3518,7 @@ body.mobile .framework-perseus table.perseus-widget-table-of-values.non-markdown
3518
3518
  font-size: 18px;
3519
3519
  }
3520
3520
  }
3521
- .perseus-widget-editor-content .locked-figure-accordion h2 {
3521
+ .perseus-widget-editor-content .perseus-editor-accordion h2 {
3522
3522
  padding-top: 0;
3523
3523
  }
3524
3524
  .keypad-input {
@@ -4143,6 +4143,34 @@ html body .mq-editable-field .mq-selection .mq-nthroot-container * {
4143
4143
  }
4144
4144
 
4145
4145
 
4146
+ .MafsView .protractor-rotation-handle {
4147
+ cursor: grab;
4148
+ /* `pointer-events: all` is needed to make the hit target the size of the
4149
+ * entire <g> element and not just its visible subelements */
4150
+ pointer-events: all;
4151
+ transition: transform 0.1s ease-out;
4152
+ }
4153
+
4154
+ .MafsView .protractor-rotation-handle:hover {
4155
+ transform: scale(1.3);
4156
+ }
4157
+
4158
+ .MafsView .protractor-rotation-handle-arrow-arc {
4159
+ fill: none;
4160
+ stroke: var(--mafs-blue);
4161
+ stroke-width: 8;
4162
+ /* `strokeLinecap: square` prevents a hairline crack from appearing
4163
+ * between the arc and the arrowheads, by making the arc extend a bit
4164
+ * beyond its start and end coordinates.
4165
+ */
4166
+ stroke-linecap: square;
4167
+ }
4168
+
4169
+ .MafsView .protractor-rotation-handle-arrowhead {
4170
+ fill: var(--mafs-blue);
4171
+ stroke: none;
4172
+ }
4173
+
4146
4174
  .MafsView {
4147
4175
  display: block;
4148
4176
  background: var(--mafs-bg);
package/dist/es/index.js CHANGED
@@ -29,7 +29,8 @@ import { OptionItem, SingleSelect, MultiSelect } from '@khanacademy/wonder-block
29
29
  import caretDown from '@phosphor-icons/core/regular/caret-down.svg';
30
30
  import caretUp from '@phosphor-icons/core/regular/caret-up.svg';
31
31
  import debounce$2 from 'lodash.debounce';
32
- import { vec, Ellipse, Line as Line$4, Point as Point$2, Polygon, useMovable, Text as Text$1, Plot as Plot$3, Coordinates, Mafs } from 'mafs';
32
+ import { vec, Ellipse, Line as Line$4, Point as Point$2, Polygon, useTransformContext, Text as Text$1, Plot as Plot$3, Coordinates, Mafs } from 'mafs';
33
+ import { useDrag } from '@use-gesture/react';
33
34
  import { PhosphorIcon } from '@khanacademy/wonder-blocks-icon';
34
35
  import { Body, LabelSmall, LabelMedium } from '@khanacademy/wonder-blocks-typography';
35
36
  import questionIcon from '@phosphor-icons/core/regular/question.svg';
@@ -520,8 +521,7 @@ class InnerMathInput extends React.Component {
520
521
  active: false
521
522
  }) : /*#__PURE__*/React.createElement(Clickable, {
522
523
  "aria-label": this.state.keypadOpen ? this.context.strings.closeKeypad : this.context.strings.openKeypad,
523
- "aria-checked": this.state.keypadOpen,
524
- role: "switch",
524
+ role: "button",
525
525
  onClick: () => this.state.keypadOpen ? this.closeKeypad() : this.openKeypad()
526
526
  }, props => /*#__PURE__*/React.createElement(MathInputIcon, _extends({
527
527
  active: this.state.keypadOpen
@@ -13086,7 +13086,7 @@ _.extend(GraphUtils.Graphie.prototype, {
13086
13086
  }(),
13087
13087
  protractor: function (center) {
13088
13088
  // @ts-expect-error - TS2350 - Only a void function can be called with the 'new' keyword.
13089
- return new Protractor(this, center);
13089
+ return new Protractor$1(this, center);
13090
13090
  },
13091
13091
  ruler: function (options) {
13092
13092
  // @ts-expect-error - TS2350 - Only a void function can be called with the 'new' keyword.
@@ -13094,7 +13094,7 @@ _.extend(GraphUtils.Graphie.prototype, {
13094
13094
  },
13095
13095
  addPoints: vector$1.add
13096
13096
  });
13097
- function Protractor(graph, center) {
13097
+ function Protractor$1(graph, center) {
13098
13098
  // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
13099
13099
  this.set = graph.raphael.set();
13100
13100
 
@@ -28558,7 +28558,24 @@ class PathBuilder {
28558
28558
  move(x, y) {
28559
28559
  this.path.push({
28560
28560
  action: "M",
28561
- coords: [x, y]
28561
+ args: [x, y]
28562
+ });
28563
+ return this;
28564
+ }
28565
+ line(x, y) {
28566
+ this.path.push({
28567
+ action: "L",
28568
+ args: [x, y]
28569
+ });
28570
+ return this;
28571
+ }
28572
+ circularArc(radius, toX, toY, {
28573
+ sweep = false,
28574
+ largeArc = false
28575
+ } = {}) {
28576
+ this.path.push({
28577
+ action: "A",
28578
+ args: [radius, radius, 0 /* rotation */, largeArc ? 1 : 0, sweep ? 1 : 0, toX, toY]
28562
28579
  });
28563
28580
  return this;
28564
28581
  }
@@ -28567,7 +28584,7 @@ class PathBuilder {
28567
28584
  curve(control1X, control1Y, control2X, control2Y, endX, endY) {
28568
28585
  this.path.push({
28569
28586
  action: "C",
28570
- coords: [control1X, control1Y, control2X, control2Y, endX, endY]
28587
+ args: [control1X, control1Y, control2X, control2Y, endX, endY]
28571
28588
  });
28572
28589
  return this;
28573
28590
  }
@@ -28579,12 +28596,38 @@ class PathBuilder {
28579
28596
  }
28580
28597
  }
28581
28598
  function commandToString(command) {
28582
- return `${command.action}${command.coords.join(" ")}`;
28599
+ return `${command.action}${command.args.join(" ")}`;
28583
28600
  }
28584
28601
  function scaleCommandBy(scaleFactor) {
28585
- return command => _extends({}, command, {
28586
- coords: command.coords.map(c => c * scaleFactor)
28587
- });
28602
+ return command => {
28603
+ switch (command.action) {
28604
+ case "A":
28605
+ // TODO(benchristel): do we want to refactor this switch to
28606
+ // use polymorphism?
28607
+ // Arc command
28608
+ return _extends({}, command, {
28609
+ args: [command.args[0] * scaleFactor,
28610
+ // x radius
28611
+ command.args[1] * scaleFactor,
28612
+ // y radius
28613
+ command.args[2],
28614
+ // rotation
28615
+ command.args[3],
28616
+ // largeArc flag
28617
+ command.args[4],
28618
+ // sweep flag
28619
+ command.args[5] * scaleFactor,
28620
+ // end x
28621
+ command.args[6] * scaleFactor // end y
28622
+ ]
28623
+ });
28624
+
28625
+ default:
28626
+ return _extends({}, command, {
28627
+ args: command.args.map(c => c * scaleFactor)
28628
+ });
28629
+ }
28630
+ };
28588
28631
  }
28589
28632
 
28590
28633
  // We use the pathBuilder here to scale up the SVG path coordinates used
@@ -28972,8 +29015,169 @@ function snap(snapStep, point) {
28972
29015
  return [Math.round(requestedX / snapX) * snapX, Math.round(requestedY / snapY) * snapY];
28973
29016
  }
28974
29017
 
28975
- function useDraggable(params) {
28976
- return useMovable(params);
29018
+ // Returns the closest point to the given `point` that is within the graph
29019
+ // bounds given in `state`.
29020
+ function bound$1({
29021
+ snapStep,
29022
+ range,
29023
+ point
29024
+ }) {
29025
+ const [requestedX, requestedY] = point;
29026
+ const [snapX, snapY] = snapStep;
29027
+ const [[minX, maxX], [minY, maxY]] = range;
29028
+ return [clamp(requestedX, minX + snapX, maxX - snapX), clamp(requestedY, minY + snapY, maxY - snapY)];
29029
+ }
29030
+ function clamp(value, min, max) {
29031
+ if (value < min) {
29032
+ return min;
29033
+ }
29034
+ if (value > max) {
29035
+ return max;
29036
+ }
29037
+ return value;
29038
+ }
29039
+
29040
+ var isProduction = process.env.NODE_ENV === 'production';
29041
+ var prefix = 'Invariant failed';
29042
+ function invariant(condition, message) {
29043
+ if (condition) {
29044
+ return;
29045
+ }
29046
+ if (isProduction) {
29047
+ throw new Error(prefix);
29048
+ }
29049
+ var provided = typeof message === 'function' ? message() : message;
29050
+ var value = provided ? "".concat(prefix, ": ").concat(provided) : prefix;
29051
+ throw new Error(value);
29052
+ }
29053
+
29054
+ /**
29055
+ * Code in this file is derived from
29056
+ * https://github.com/stevenpetryk/mafs/blob/4520319379a2cc2df8148d8baaef1f85db117103/src/interaction/useMovable.tsx#L20-L83
29057
+ * and copied here under the terms of the MIT license.
29058
+ *
29059
+ * Copyright 2021 Steven Petryk
29060
+ *
29061
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
29062
+ * of this software and associated documentation files (the "Software"), to deal
29063
+ * in the Software without restriction, including without limitation the rights
29064
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
29065
+ * copies of the Software, and to permit persons to whom the Software is
29066
+ * furnished to do so, subject to the following conditions:
29067
+ *
29068
+ * The above copyright notice and this permission notice shall be included in
29069
+ * all copies or substantial portions of the Software.
29070
+ */
29071
+
29072
+ function useDraggable(args) {
29073
+ const {
29074
+ gestureTarget: target,
29075
+ onMove,
29076
+ point,
29077
+ constrain
29078
+ } = args;
29079
+ const [dragging, setDragging] = React.useState(false);
29080
+ const {
29081
+ xSpan,
29082
+ ySpan
29083
+ } = useSpanContext();
29084
+ const {
29085
+ viewTransform,
29086
+ userTransform
29087
+ } = useTransformContext();
29088
+ const inverseViewTransform = vec.matrixInvert(viewTransform);
29089
+ invariant(inverseViewTransform, "The view transform must be invertible.");
29090
+ const inverseTransform = React.useMemo(() => getInverseTransform(userTransform), [userTransform]);
29091
+ const pickup = React.useRef([0, 0]);
29092
+ useDrag(state => {
29093
+ const {
29094
+ type,
29095
+ event
29096
+ } = state;
29097
+ event == null || event.stopPropagation();
29098
+ const isKeyboard = type.includes("key");
29099
+ if (isKeyboard) {
29100
+ event == null || event.preventDefault();
29101
+ const {
29102
+ direction: yDownDirection,
29103
+ altKey,
29104
+ metaKey,
29105
+ shiftKey
29106
+ } = state;
29107
+ const direction = [yDownDirection[0], -yDownDirection[1]];
29108
+ const span = Math.abs(direction[0]) ? xSpan : ySpan;
29109
+ let divisions = 50;
29110
+ if (altKey || metaKey) {
29111
+ divisions = 200;
29112
+ }
29113
+ if (shiftKey) {
29114
+ divisions = 10;
29115
+ }
29116
+ const min = span / (divisions * 2);
29117
+ const tests = range(span / divisions, span / 2, span / divisions);
29118
+ for (const dx of tests) {
29119
+ // Transform the test back into the point's coordinate system
29120
+ const testMovement = vec.scale(direction, dx);
29121
+ const testPoint = constrain(vec.transform(vec.add(vec.transform(point, userTransform), testMovement), inverseTransform));
29122
+ if (vec.dist(testPoint, point) > min) {
29123
+ onMove(testPoint);
29124
+ break;
29125
+ }
29126
+ }
29127
+ } else {
29128
+ const {
29129
+ last,
29130
+ movement: pixelMovement,
29131
+ first
29132
+ } = state;
29133
+ setDragging(!last);
29134
+ if (first) {
29135
+ pickup.current = vec.transform(point, userTransform);
29136
+ }
29137
+ if (vec.mag(pixelMovement) === 0) {
29138
+ return;
29139
+ }
29140
+ const movement = vec.transform(pixelMovement, inverseViewTransform);
29141
+ onMove(constrain(vec.transform(vec.add(pickup.current, movement), inverseTransform)));
29142
+ }
29143
+ }, {
29144
+ target,
29145
+ eventOptions: {
29146
+ passive: false
29147
+ }
29148
+ });
29149
+ return {
29150
+ dragging
29151
+ };
29152
+ }
29153
+ function getInverseTransform(transform) {
29154
+ const invert = vec.matrixInvert(transform);
29155
+ invariant(invert !== null, "Could not invert transform matrix. A parent transformation matrix might be degenerative (mapping 2D space to a line).");
29156
+ return invert;
29157
+ }
29158
+ function useSpanContext() {
29159
+ const {
29160
+ range: [[xMin, xMax], [yMin, yMax]]
29161
+ } = useGraphConfig();
29162
+ const xSpan = xMax - xMin;
29163
+ const ySpan = yMax - yMin;
29164
+ return {
29165
+ xSpan,
29166
+ ySpan
29167
+ };
29168
+ }
29169
+ function range(min, max, step = 1) {
29170
+ const result = [];
29171
+ for (let i = min; i < max - step / 2; i += step) {
29172
+ result.push(i);
29173
+ }
29174
+ const computedMax = result[result.length - 1] + step;
29175
+ if (Math.abs(max - computedMax) < step / 1e-6) {
29176
+ result.push(max);
29177
+ } else {
29178
+ result.push(computedMax);
29179
+ }
29180
+ return result;
28977
29181
  }
28978
29182
 
28979
29183
  // The hitbox size of 48px by 48px is preserved from the legacy interactive
@@ -30996,6 +31200,154 @@ const LegacyGrid = ({
30996
31200
  return null;
30997
31201
  };
30998
31202
 
31203
+ const protractorImage = "https://ka-perseus-graphie.s3.amazonaws.com/e9d032f2ab8b95979f674fbfa67056442ba1ff6a.png";
31204
+
31205
+ // The vector from the center of the protractor to the top left corner of the
31206
+ // protractor image, in pixels. Used for positioning.
31207
+ const centerToTopLeft = [-180, -170];
31208
+
31209
+ // The vector from the center of the protractor to the center of the rotation
31210
+ // handle.
31211
+ const centerToRotationHandle = [-176, -15];
31212
+ function Protractor() {
31213
+ const {
31214
+ range,
31215
+ snapStep
31216
+ } = useGraphConfig();
31217
+ const [[xMin, xMax], [yMin, yMax]] = range;
31218
+ // Position the protractor in the center of the graph (horizontally), and
31219
+ // 95% of the way to the bottom of the graph (vertically).
31220
+ const initialCenter = [lerp(xMin, xMax, 0.5), lerp(yMin, yMax, 0.05)];
31221
+ const [center, setCenter] = useState(initialCenter);
31222
+ const [rotationHandleOffset, setRotationHandleOffset] = useState(centerToRotationHandle);
31223
+ const draggableRef = useRef(null);
31224
+ useDraggable({
31225
+ gestureTarget: draggableRef,
31226
+ onMove: setCenter,
31227
+ point: center,
31228
+ constrain: point => bound$1({
31229
+ snapStep,
31230
+ range,
31231
+ point
31232
+ })
31233
+ });
31234
+ const rotationHandleRef = useRef(null);
31235
+ useDraggablePx({
31236
+ gestureTarget: rotationHandleRef,
31237
+ onMove: setRotationHandleOffset,
31238
+ point: rotationHandleOffset,
31239
+ constrain: constrainToCircle
31240
+ });
31241
+ const [centerPx] = useTransformVectorsToPixels(center);
31242
+ const topLeftPx = vec.add(centerPx, centerToTopLeft);
31243
+ const angle = calculateAngleInDegrees(rotationHandleOffset) - calculateAngleInDegrees(centerToRotationHandle);
31244
+ return /*#__PURE__*/React.createElement("g", {
31245
+ ref: draggableRef,
31246
+ transform: `translate(${topLeftPx[0]}, ${topLeftPx[1]}), rotate(${angle})`,
31247
+ style: {
31248
+ transformOrigin: `${-centerToTopLeft[0]}px ${-centerToTopLeft[1]}px`
31249
+ }
31250
+ }, /*#__PURE__*/React.createElement("image", {
31251
+ href: protractorImage
31252
+ }), /*#__PURE__*/React.createElement("g", {
31253
+ transform: `translate(5, ${-centerToTopLeft[1]})`,
31254
+ ref: rotationHandleRef
31255
+ }, /*#__PURE__*/React.createElement(RotationArrow, null)));
31256
+ }
31257
+ function RotationArrow() {
31258
+ const radius = 175;
31259
+ const angleDeg = 10;
31260
+ const angleRad = degreesToRadians(angleDeg);
31261
+ const endX = radius * (1 - Math.cos(angleRad));
31262
+ const endY = radius * -Math.sin(angleRad);
31263
+ const rotationArrow = pathBuilder().move(0, 0).circularArc(radius, endX, endY, {
31264
+ sweep: true
31265
+ }).build();
31266
+ const arrowhead = pathBuilder().move(-8, 0).line(0, 10).line(8, 0).build();
31267
+ const targetRadius = TARGET_SIZE / 2;
31268
+ return /*#__PURE__*/React.createElement("g", {
31269
+ className: "protractor-rotation-handle"
31270
+ }, /*#__PURE__*/React.createElement("path", {
31271
+ className: "protractor-rotation-handle-arrow-arc",
31272
+ d: rotationArrow
31273
+ }), /*#__PURE__*/React.createElement("path", {
31274
+ className: "protractor-rotation-handle-arrowhead",
31275
+ d: arrowhead
31276
+ }), /*#__PURE__*/React.createElement("path", {
31277
+ className: "protractor-rotation-handle-arrowhead",
31278
+ d: arrowhead,
31279
+ transform: `translate(${endX}, ${endY}), rotate(${180 + angleDeg})`
31280
+ }), /*#__PURE__*/React.createElement("ellipse", {
31281
+ cx: "0px",
31282
+ cy: "-15px",
31283
+ rx: targetRadius,
31284
+ ry: targetRadius,
31285
+ fill: "none"
31286
+ }));
31287
+ }
31288
+ const protractorRadius = vec.mag(centerToRotationHandle);
31289
+ function constrainToCircle(edgePoint) {
31290
+ return vec.withMag(edgePoint, protractorRadius);
31291
+ }
31292
+
31293
+ /* The implementation of useDraggablePx is derived from
31294
+ * https://github.com/stevenpetryk/mafs/blob/4520319379a2cc2df8148d8baaef1f85db117103/src/interaction/useMovable.tsx#L20-L83
31295
+ * and copied here under the terms of the MIT license.
31296
+ *
31297
+ * Copyright 2021 Steven Petryk
31298
+ *
31299
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
31300
+ * of this software and associated documentation files (the "Software"), to deal
31301
+ * in the Software without restriction, including without limitation the rights
31302
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
31303
+ * copies of the Software, and to permit persons to whom the Software is
31304
+ * furnished to do so, subject to the following conditions:
31305
+ *
31306
+ * The above copyright notice and this permission notice shall be included in
31307
+ * all copies or substantial portions of the Software.
31308
+ */
31309
+ function useDraggablePx(args) {
31310
+ const {
31311
+ gestureTarget: target,
31312
+ onMove,
31313
+ point,
31314
+ constrain = p => p
31315
+ } = args;
31316
+ const pickupPx = React.useRef([0, 0]);
31317
+ useDrag(state => {
31318
+ const {
31319
+ event,
31320
+ first,
31321
+ movement: pixelMovement
31322
+ } = state;
31323
+ event == null || event.stopPropagation();
31324
+ if (first) {
31325
+ pickupPx.current = point;
31326
+ }
31327
+ if (vec.mag(pixelMovement) === 0) {
31328
+ return;
31329
+ }
31330
+ onMove == null || onMove(constrain(vec.add(pickupPx.current, pixelMovement)));
31331
+ }, {
31332
+ target,
31333
+ eventOptions: {
31334
+ passive: false
31335
+ }
31336
+ });
31337
+ }
31338
+
31339
+ // [L]inear Int[erp]olation: gets the weighted average of two values `a` and
31340
+ // `b`, with the given `fraction` specifying how much weight `a` and `b` get:
31341
+ // - if `fraction` is 0, `lerp` returns `a`.
31342
+ // - if `fraction` is 0.5, `lerp` returns the average of `a` and `b`.
31343
+ // - if `fraction` is 1, `lerp` returns `b`.
31344
+ function lerp(a, b, fraction) {
31345
+ return (b - a) * fraction + a;
31346
+ }
31347
+ function degreesToRadians(degrees) {
31348
+ return degrees / 180 * Math.PI;
31349
+ }
31350
+
30999
31351
  function initializeGraphState(params) {
31000
31352
  var _graph$snapTo;
31001
31353
  const {
@@ -31678,19 +32030,6 @@ function boundAndSnapToSides(destinationPoint, {
31678
32030
  return vector$1.add(coordsCopy[rel(-1)], offset);
31679
32031
  }
31680
32032
 
31681
- // Returns the closest point to the given `point` that is within the graph
31682
- // bounds given in `state`.
31683
- function bound$1({
31684
- snapStep,
31685
- range,
31686
- point
31687
- }) {
31688
- const [requestedX, requestedY] = point;
31689
- const [snapX, snapY] = snapStep;
31690
- const [[minX, maxX], [minY, maxY]] = range;
31691
- return [clamp(requestedX, minX + snapX, maxX - snapX), clamp(requestedY, minY + snapY, maxY - snapY)];
31692
- }
31693
-
31694
32033
  // Returns the vector from the given point to the top-right corner of the graph when snapped to the grid
31695
32034
  function maxMove({
31696
32035
  snapStep,
@@ -31718,15 +32057,6 @@ function minMove({
31718
32057
  });
31719
32058
  return vec.sub(bottomLeft, point);
31720
32059
  }
31721
- function clamp(value, min, max) {
31722
- if (value < min) {
31723
- return min;
31724
- }
31725
- if (value > max) {
31726
- return max;
31727
- }
31728
- return value;
31729
- }
31730
32060
  const coordsOverlap = coords => coords.some((coord, i) => coords.some((c, j) => i !== j && vector$1.equal(coord, c)));
31731
32061
  function updateAtIndex(args) {
31732
32062
  const {
@@ -31856,6 +32186,10 @@ const StatefulMafsGraph = /*#__PURE__*/React.forwardRef((props, ref) => {
31856
32186
  dispatch(changeRange([[xMinRange, xMaxRange], [yMinRange, yMaxRange]]));
31857
32187
  }, [dispatch, xMinRange, xMaxRange, yMinRange, yMaxRange]);
31858
32188
  const numSegments = graph.type === "segment" ? graph.numSegments : null;
32189
+ const numSides = graph.type === "polygon" ? graph.numSides : null;
32190
+ const snapTo = graph.type === "polygon" ? graph.snapTo : null;
32191
+ const showAngles = graph.type === "polygon" ? graph.showAngles : null;
32192
+ const showSides = graph.type === "polygon" ? graph.showSides : null;
31859
32193
  const originalPropsRef = useRef(props);
31860
32194
  const latestPropsRef = useLatestRef(props);
31861
32195
  useEffect(() => {
@@ -31866,7 +32200,7 @@ const StatefulMafsGraph = /*#__PURE__*/React.forwardRef((props, ref) => {
31866
32200
  if (latestPropsRef.current !== originalPropsRef.current) {
31867
32201
  dispatch(reinitialize(latestPropsRef.current));
31868
32202
  }
31869
- }, [graph.type, numSegments, latestPropsRef]);
32203
+ }, [graph.type, numSegments, numSides, snapTo, showAngles, showSides, latestPropsRef]);
31870
32204
  return /*#__PURE__*/React.createElement(MafsGraph, _extends({}, props, {
31871
32205
  state: state,
31872
32206
  dispatch: dispatch
@@ -31933,7 +32267,7 @@ const MafsGraph = props => {
31933
32267
  }), props.lockedFigures && /*#__PURE__*/React.createElement(GraphLockedLayer, {
31934
32268
  lockedFigures: props.lockedFigures,
31935
32269
  range: state.range
31936
- }), renderGraph({
32270
+ }), "/* Protractor */", props.showProtractor && /*#__PURE__*/React.createElement(Protractor, null), renderGraph({
31937
32271
  state,
31938
32272
  dispatch
31939
32273
  })))));
@@ -33241,14 +33575,9 @@ class InteractiveGraph extends React.Component {
33241
33575
  }
33242
33576
  render() {
33243
33577
  var _this$props$apiOption;
33244
- if (this.props.graph.type === "polygon" && this.props.graph.numSides === "unlimited") {
33245
- return /*#__PURE__*/React.createElement(LegacyInteractiveGraph, _extends({
33246
- ref: this.legacyGraphRef
33247
- }, this.props));
33248
- }
33249
-
33250
33578
  // Mafs shim
33251
- if ((_this$props$apiOption = this.props.apiOptions) != null && (_this$props$apiOption = _this$props$apiOption.flags) != null && (_this$props$apiOption = _this$props$apiOption["mafs"]) != null && _this$props$apiOption[this.props.graph.type]) {
33579
+ const mafsFlags = (_this$props$apiOption = this.props.apiOptions) == null || (_this$props$apiOption = _this$props$apiOption.flags) == null ? void 0 : _this$props$apiOption["mafs"];
33580
+ if (shouldUseMafs(mafsFlags, this.props.graph)) {
33252
33581
  const box = getInteractiveBoxFromSizeClass(this.props.containerSizeClass);
33253
33582
  const gridStep = this.props.gridStep || Util.getGridStep(this.props.range, this.props.step, box[0]);
33254
33583
  const snapStep = this.props.snapStep || Util.snapStepFromGridStep(gridStep);
@@ -33809,6 +34138,8 @@ class InteractiveGraph extends React.Component {
33809
34138
  return props.graph;
33810
34139
  }
33811
34140
  }
34141
+
34142
+ // exported for testing
33812
34143
  InteractiveGraph.defaultProps = {
33813
34144
  labels: ["x", "y"],
33814
34145
  range: [[-10, 10], [-10, 10]],
@@ -33824,6 +34155,31 @@ InteractiveGraph.defaultProps = {
33824
34155
  type: "linear"
33825
34156
  }
33826
34157
  };
34158
+ function shouldUseMafs(mafsFlags, graph) {
34159
+ if (typeof mafsFlags === "boolean" || typeof mafsFlags === "undefined") {
34160
+ return false;
34161
+ }
34162
+ switch (graph.type) {
34163
+ case "point":
34164
+ if (graph.numPoints === UNLIMITED) {
34165
+ // TODO(benchristel): add a feature flag for the "unlimited"
34166
+ // case once we've implemented point graphs with unlimited
34167
+ // points
34168
+ return false;
34169
+ }
34170
+ return Boolean(mafsFlags["point"]);
34171
+ case "polygon":
34172
+ if (graph.numSides === UNLIMITED) {
34173
+ // TODO(benchristel): add a feature flag for the "unlimited"
34174
+ // case once we've implemented polygon graphs with unlimited
34175
+ // sides
34176
+ return false;
34177
+ }
34178
+ return Boolean(mafsFlags["polygon"]);
34179
+ default:
34180
+ return Boolean(mafsFlags[graph.type]);
34181
+ }
34182
+ }
33827
34183
  var InteractiveGraph$1 = {
33828
34184
  name: "interactive-graph",
33829
34185
  displayName: "Interactive graph",
@@ -44039,7 +44395,7 @@ const init = function init(options) {
44039
44395
 
44040
44396
  // This file is processed by a Rollup plugin (replace) to inject the production
44041
44397
  const libName = "@khanacademy/perseus";
44042
- const libVersion = "23.6.0";
44398
+ const libVersion = "24.1.0";
44043
44399
  addLibraryVersionToPerseusDebug(libName, libVersion);
44044
44400
 
44045
44401
  const apiVersion = {