@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/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/index.d.ts CHANGED
@@ -87,7 +87,7 @@ export type { ILogger, LogErrorOptions, ErrorKind } from "./logging/log";
87
87
  export type { ServerItemRenderer as ServerItemRendererComponent } from "./server-item-renderer";
88
88
  export type { Alignment, APIOptions, APIOptionsWithDefaults, ChangeHandler, DeviceType, DomInsertCheckFn, EditorMode, FocusPath, Hint, ImageDict, ImageUploader, JiptLabelStore, JiptRenderer, PerseusDependencies, PerseusScore, Version, VideoData, VideoKind, WidgetDict, WidgetExports, } from "./types";
89
89
  export type { ParsedValue } from "./util";
90
- export type { LockedFigure, LockedFigureColor, LockedFigureFillType, LockedFigureType, LockedPointType, LockedLineType, LockedVectorType, LockedEllipseType, LockedPolygonType, PerseusGraphType, PerseusAnswerArea, PerseusExpressionWidgetOptions, Range, Size, MathFormat, InputNumberWidget, // TODO(jeremy): remove?
90
+ export type { LockedFigure, LockedFigureColor, LockedFigureFillType, LockedFigureType, LockedPointType, LockedLineType, LockedVectorType, LockedEllipseType, LockedPolygonType, PerseusGraphType, PerseusAnswerArea, PerseusExpressionWidgetOptions, Range, Size, CollinearTuple, MathFormat, InputNumberWidget, // TODO(jeremy): remove?
91
91
  PerseusImageBackground, PerseusInputNumberWidgetOptions, PerseusInteractiveGraphWidgetOptions, PerseusItem, PerseusPlotterWidgetOptions, PerseusPythonProgramWidgetOptions, PerseusRadioWidgetOptions, PerseusExampleWidgetOptions, PerseusExampleGraphieWidgetOptions, PerseusSimpleMarkdownTesterWidgetOptions, PerseusRenderer, PerseusWidget, PerseusWidgetsMap, } from "./perseus-types";
92
92
  export type { Coord } from "./interactive2/types";
93
93
  export type { MarkerType } from "./widgets/label-image/types";
package/dist/index.js CHANGED
@@ -31,6 +31,7 @@ var caretDown = require('@phosphor-icons/core/regular/caret-down.svg');
31
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
+ var react = require('@use-gesture/react');
34
35
  var wonderBlocksIcon = require('@khanacademy/wonder-blocks-icon');
35
36
  var wonderBlocksTypography = require('@khanacademy/wonder-blocks-typography');
36
37
  var questionIcon = require('@phosphor-icons/core/regular/question.svg');
@@ -634,8 +635,7 @@ class InnerMathInput extends React__namespace.Component {
634
635
  active: false
635
636
  }) : /*#__PURE__*/React__namespace.createElement(Clickable__default["default"], {
636
637
  "aria-label": this.state.keypadOpen ? this.context.strings.closeKeypad : this.context.strings.openKeypad,
637
- "aria-checked": this.state.keypadOpen,
638
- role: "switch",
638
+ role: "button",
639
639
  onClick: () => this.state.keypadOpen ? this.closeKeypad() : this.openKeypad()
640
640
  }, props => /*#__PURE__*/React__namespace.createElement(MathInputIcon, _extends({
641
641
  active: this.state.keypadOpen
@@ -13188,7 +13188,7 @@ ___default["default"].extend(GraphUtils.Graphie.prototype, {
13188
13188
  }(),
13189
13189
  protractor: function (center) {
13190
13190
  // @ts-expect-error - TS2350 - Only a void function can be called with the 'new' keyword.
13191
- return new Protractor(this, center);
13191
+ return new Protractor$1(this, center);
13192
13192
  },
13193
13193
  ruler: function (options) {
13194
13194
  // @ts-expect-error - TS2350 - Only a void function can be called with the 'new' keyword.
@@ -13196,7 +13196,7 @@ ___default["default"].extend(GraphUtils.Graphie.prototype, {
13196
13196
  },
13197
13197
  addPoints: kmath.vector.add
13198
13198
  });
13199
- function Protractor(graph, center) {
13199
+ function Protractor$1(graph, center) {
13200
13200
  // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
13201
13201
  this.set = graph.raphael.set();
13202
13202
 
@@ -28850,7 +28850,25 @@ class PathBuilder {
28850
28850
  move(x, y) {
28851
28851
  this.path.push({
28852
28852
  action: "M",
28853
- coords: [x, y]
28853
+ args: [x, y]
28854
+ });
28855
+ return this;
28856
+ }
28857
+ line(x, y) {
28858
+ this.path.push({
28859
+ action: "L",
28860
+ args: [x, y]
28861
+ });
28862
+ return this;
28863
+ }
28864
+ circularArc(radius, toX, toY) {
28865
+ let {
28866
+ sweep = false,
28867
+ largeArc = false
28868
+ } = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
28869
+ this.path.push({
28870
+ action: "A",
28871
+ args: [radius, radius, 0 /* rotation */, largeArc ? 1 : 0, sweep ? 1 : 0, toX, toY]
28854
28872
  });
28855
28873
  return this;
28856
28874
  }
@@ -28859,7 +28877,7 @@ class PathBuilder {
28859
28877
  curve(control1X, control1Y, control2X, control2Y, endX, endY) {
28860
28878
  this.path.push({
28861
28879
  action: "C",
28862
- coords: [control1X, control1Y, control2X, control2Y, endX, endY]
28880
+ args: [control1X, control1Y, control2X, control2Y, endX, endY]
28863
28881
  });
28864
28882
  return this;
28865
28883
  }
@@ -28871,13 +28889,40 @@ class PathBuilder {
28871
28889
  }
28872
28890
  }
28873
28891
  function commandToString(command) {
28874
- return `${command.action}${command.coords.join(" ")}`;
28892
+ return `${command.action}${command.args.join(" ")}`;
28875
28893
  }
28876
28894
  function scaleCommandBy(scaleFactor) {
28877
- return command => ({
28878
- ...command,
28879
- coords: command.coords.map(c => c * scaleFactor)
28880
- });
28895
+ return command => {
28896
+ switch (command.action) {
28897
+ case "A":
28898
+ // TODO(benchristel): do we want to refactor this switch to
28899
+ // use polymorphism?
28900
+ // Arc command
28901
+ return {
28902
+ ...command,
28903
+ args: [command.args[0] * scaleFactor,
28904
+ // x radius
28905
+ command.args[1] * scaleFactor,
28906
+ // y radius
28907
+ command.args[2],
28908
+ // rotation
28909
+ command.args[3],
28910
+ // largeArc flag
28911
+ command.args[4],
28912
+ // sweep flag
28913
+ command.args[5] * scaleFactor,
28914
+ // end x
28915
+ command.args[6] * scaleFactor // end y
28916
+ ]
28917
+ };
28918
+
28919
+ default:
28920
+ return {
28921
+ ...command,
28922
+ args: command.args.map(c => c * scaleFactor)
28923
+ };
28924
+ }
28925
+ };
28881
28926
  }
28882
28927
 
28883
28928
  // We use the pathBuilder here to scale up the SVG path coordinates used
@@ -29265,8 +29310,171 @@ function snap(snapStep, point) {
29265
29310
  return [Math.round(requestedX / snapX) * snapX, Math.round(requestedY / snapY) * snapY];
29266
29311
  }
29267
29312
 
29268
- function useDraggable(params) {
29269
- return mafs.useMovable(params);
29313
+ // Returns the closest point to the given `point` that is within the graph
29314
+ // bounds given in `state`.
29315
+ function bound$1(_ref) {
29316
+ let {
29317
+ snapStep,
29318
+ range,
29319
+ point
29320
+ } = _ref;
29321
+ const [requestedX, requestedY] = point;
29322
+ const [snapX, snapY] = snapStep;
29323
+ const [[minX, maxX], [minY, maxY]] = range;
29324
+ return [clamp(requestedX, minX + snapX, maxX - snapX), clamp(requestedY, minY + snapY, maxY - snapY)];
29325
+ }
29326
+ function clamp(value, min, max) {
29327
+ if (value < min) {
29328
+ return min;
29329
+ }
29330
+ if (value > max) {
29331
+ return max;
29332
+ }
29333
+ return value;
29334
+ }
29335
+
29336
+ var isProduction = process.env.NODE_ENV === 'production';
29337
+ var prefix = 'Invariant failed';
29338
+ function invariant(condition, message) {
29339
+ if (condition) {
29340
+ return;
29341
+ }
29342
+ if (isProduction) {
29343
+ throw new Error(prefix);
29344
+ }
29345
+ var provided = typeof message === 'function' ? message() : message;
29346
+ var value = provided ? "".concat(prefix, ": ").concat(provided) : prefix;
29347
+ throw new Error(value);
29348
+ }
29349
+
29350
+ /**
29351
+ * Code in this file is derived from
29352
+ * https://github.com/stevenpetryk/mafs/blob/4520319379a2cc2df8148d8baaef1f85db117103/src/interaction/useMovable.tsx#L20-L83
29353
+ * and copied here under the terms of the MIT license.
29354
+ *
29355
+ * Copyright 2021 Steven Petryk
29356
+ *
29357
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
29358
+ * of this software and associated documentation files (the "Software"), to deal
29359
+ * in the Software without restriction, including without limitation the rights
29360
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
29361
+ * copies of the Software, and to permit persons to whom the Software is
29362
+ * furnished to do so, subject to the following conditions:
29363
+ *
29364
+ * The above copyright notice and this permission notice shall be included in
29365
+ * all copies or substantial portions of the Software.
29366
+ */
29367
+
29368
+ function useDraggable(args) {
29369
+ const {
29370
+ gestureTarget: target,
29371
+ onMove,
29372
+ point,
29373
+ constrain
29374
+ } = args;
29375
+ const [dragging, setDragging] = React__namespace.useState(false);
29376
+ const {
29377
+ xSpan,
29378
+ ySpan
29379
+ } = useSpanContext();
29380
+ const {
29381
+ viewTransform,
29382
+ userTransform
29383
+ } = mafs.useTransformContext();
29384
+ const inverseViewTransform = mafs.vec.matrixInvert(viewTransform);
29385
+ invariant(inverseViewTransform, "The view transform must be invertible.");
29386
+ const inverseTransform = React__namespace.useMemo(() => getInverseTransform(userTransform), [userTransform]);
29387
+ const pickup = React__namespace.useRef([0, 0]);
29388
+ react.useDrag(state => {
29389
+ const {
29390
+ type,
29391
+ event
29392
+ } = state;
29393
+ event?.stopPropagation();
29394
+ const isKeyboard = type.includes("key");
29395
+ if (isKeyboard) {
29396
+ event?.preventDefault();
29397
+ const {
29398
+ direction: yDownDirection,
29399
+ altKey,
29400
+ metaKey,
29401
+ shiftKey
29402
+ } = state;
29403
+ const direction = [yDownDirection[0], -yDownDirection[1]];
29404
+ const span = Math.abs(direction[0]) ? xSpan : ySpan;
29405
+ let divisions = 50;
29406
+ if (altKey || metaKey) {
29407
+ divisions = 200;
29408
+ }
29409
+ if (shiftKey) {
29410
+ divisions = 10;
29411
+ }
29412
+ const min = span / (divisions * 2);
29413
+ const tests = range(span / divisions, span / 2, span / divisions);
29414
+ for (const dx of tests) {
29415
+ // Transform the test back into the point's coordinate system
29416
+ const testMovement = mafs.vec.scale(direction, dx);
29417
+ const testPoint = constrain(mafs.vec.transform(mafs.vec.add(mafs.vec.transform(point, userTransform), testMovement), inverseTransform));
29418
+ if (mafs.vec.dist(testPoint, point) > min) {
29419
+ onMove(testPoint);
29420
+ break;
29421
+ }
29422
+ }
29423
+ } else {
29424
+ const {
29425
+ last,
29426
+ movement: pixelMovement,
29427
+ first
29428
+ } = state;
29429
+ setDragging(!last);
29430
+ if (first) {
29431
+ pickup.current = mafs.vec.transform(point, userTransform);
29432
+ }
29433
+ if (mafs.vec.mag(pixelMovement) === 0) {
29434
+ return;
29435
+ }
29436
+ const movement = mafs.vec.transform(pixelMovement, inverseViewTransform);
29437
+ onMove(constrain(mafs.vec.transform(mafs.vec.add(pickup.current, movement), inverseTransform)));
29438
+ }
29439
+ }, {
29440
+ target,
29441
+ eventOptions: {
29442
+ passive: false
29443
+ }
29444
+ });
29445
+ return {
29446
+ dragging
29447
+ };
29448
+ }
29449
+ function getInverseTransform(transform) {
29450
+ const invert = mafs.vec.matrixInvert(transform);
29451
+ invariant(invert !== null, "Could not invert transform matrix. A parent transformation matrix might be degenerative (mapping 2D space to a line).");
29452
+ return invert;
29453
+ }
29454
+ function useSpanContext() {
29455
+ const {
29456
+ range: [[xMin, xMax], [yMin, yMax]]
29457
+ } = useGraphConfig();
29458
+ const xSpan = xMax - xMin;
29459
+ const ySpan = yMax - yMin;
29460
+ return {
29461
+ xSpan,
29462
+ ySpan
29463
+ };
29464
+ }
29465
+ function range(min, max) {
29466
+ let step = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 1;
29467
+ const result = [];
29468
+ for (let i = min; i < max - step / 2; i += step) {
29469
+ result.push(i);
29470
+ }
29471
+ const computedMax = result[result.length - 1] + step;
29472
+ if (Math.abs(max - computedMax) < step / 1e-6) {
29473
+ result.push(max);
29474
+ } else {
29475
+ result.push(computedMax);
29476
+ }
29477
+ return result;
29270
29478
  }
29271
29479
 
29272
29480
  // The hitbox size of 48px by 48px is preserved from the legacy interactive
@@ -31317,6 +31525,154 @@ const LegacyGrid = _ref => {
31317
31525
  return null;
31318
31526
  };
31319
31527
 
31528
+ const protractorImage = "https://ka-perseus-graphie.s3.amazonaws.com/e9d032f2ab8b95979f674fbfa67056442ba1ff6a.png";
31529
+
31530
+ // The vector from the center of the protractor to the top left corner of the
31531
+ // protractor image, in pixels. Used for positioning.
31532
+ const centerToTopLeft = [-180, -170];
31533
+
31534
+ // The vector from the center of the protractor to the center of the rotation
31535
+ // handle.
31536
+ const centerToRotationHandle = [-176, -15];
31537
+ function Protractor() {
31538
+ const {
31539
+ range,
31540
+ snapStep
31541
+ } = useGraphConfig();
31542
+ const [[xMin, xMax], [yMin, yMax]] = range;
31543
+ // Position the protractor in the center of the graph (horizontally), and
31544
+ // 95% of the way to the bottom of the graph (vertically).
31545
+ const initialCenter = [lerp(xMin, xMax, 0.5), lerp(yMin, yMax, 0.05)];
31546
+ const [center, setCenter] = React.useState(initialCenter);
31547
+ const [rotationHandleOffset, setRotationHandleOffset] = React.useState(centerToRotationHandle);
31548
+ const draggableRef = React.useRef(null);
31549
+ useDraggable({
31550
+ gestureTarget: draggableRef,
31551
+ onMove: setCenter,
31552
+ point: center,
31553
+ constrain: point => bound$1({
31554
+ snapStep,
31555
+ range,
31556
+ point
31557
+ })
31558
+ });
31559
+ const rotationHandleRef = React.useRef(null);
31560
+ useDraggablePx({
31561
+ gestureTarget: rotationHandleRef,
31562
+ onMove: setRotationHandleOffset,
31563
+ point: rotationHandleOffset,
31564
+ constrain: constrainToCircle
31565
+ });
31566
+ const [centerPx] = useTransformVectorsToPixels(center);
31567
+ const topLeftPx = mafs.vec.add(centerPx, centerToTopLeft);
31568
+ const angle = calculateAngleInDegrees(rotationHandleOffset) - calculateAngleInDegrees(centerToRotationHandle);
31569
+ return /*#__PURE__*/React__namespace.createElement("g", {
31570
+ ref: draggableRef,
31571
+ transform: `translate(${topLeftPx[0]}, ${topLeftPx[1]}), rotate(${angle})`,
31572
+ style: {
31573
+ transformOrigin: `${-centerToTopLeft[0]}px ${-centerToTopLeft[1]}px`
31574
+ }
31575
+ }, /*#__PURE__*/React__namespace.createElement("image", {
31576
+ href: protractorImage
31577
+ }), /*#__PURE__*/React__namespace.createElement("g", {
31578
+ transform: `translate(5, ${-centerToTopLeft[1]})`,
31579
+ ref: rotationHandleRef
31580
+ }, /*#__PURE__*/React__namespace.createElement(RotationArrow, null)));
31581
+ }
31582
+ function RotationArrow() {
31583
+ const radius = 175;
31584
+ const angleDeg = 10;
31585
+ const angleRad = degreesToRadians(angleDeg);
31586
+ const endX = radius * (1 - Math.cos(angleRad));
31587
+ const endY = radius * -Math.sin(angleRad);
31588
+ const rotationArrow = pathBuilder().move(0, 0).circularArc(radius, endX, endY, {
31589
+ sweep: true
31590
+ }).build();
31591
+ const arrowhead = pathBuilder().move(-8, 0).line(0, 10).line(8, 0).build();
31592
+ const targetRadius = TARGET_SIZE / 2;
31593
+ return /*#__PURE__*/React__namespace.createElement("g", {
31594
+ className: "protractor-rotation-handle"
31595
+ }, /*#__PURE__*/React__namespace.createElement("path", {
31596
+ className: "protractor-rotation-handle-arrow-arc",
31597
+ d: rotationArrow
31598
+ }), /*#__PURE__*/React__namespace.createElement("path", {
31599
+ className: "protractor-rotation-handle-arrowhead",
31600
+ d: arrowhead
31601
+ }), /*#__PURE__*/React__namespace.createElement("path", {
31602
+ className: "protractor-rotation-handle-arrowhead",
31603
+ d: arrowhead,
31604
+ transform: `translate(${endX}, ${endY}), rotate(${180 + angleDeg})`
31605
+ }), /*#__PURE__*/React__namespace.createElement("ellipse", {
31606
+ cx: "0px",
31607
+ cy: "-15px",
31608
+ rx: targetRadius,
31609
+ ry: targetRadius,
31610
+ fill: "none"
31611
+ }));
31612
+ }
31613
+ const protractorRadius = mafs.vec.mag(centerToRotationHandle);
31614
+ function constrainToCircle(edgePoint) {
31615
+ return mafs.vec.withMag(edgePoint, protractorRadius);
31616
+ }
31617
+
31618
+ /* The implementation of useDraggablePx is derived from
31619
+ * https://github.com/stevenpetryk/mafs/blob/4520319379a2cc2df8148d8baaef1f85db117103/src/interaction/useMovable.tsx#L20-L83
31620
+ * and copied here under the terms of the MIT license.
31621
+ *
31622
+ * Copyright 2021 Steven Petryk
31623
+ *
31624
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
31625
+ * of this software and associated documentation files (the "Software"), to deal
31626
+ * in the Software without restriction, including without limitation the rights
31627
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
31628
+ * copies of the Software, and to permit persons to whom the Software is
31629
+ * furnished to do so, subject to the following conditions:
31630
+ *
31631
+ * The above copyright notice and this permission notice shall be included in
31632
+ * all copies or substantial portions of the Software.
31633
+ */
31634
+ function useDraggablePx(args) {
31635
+ const {
31636
+ gestureTarget: target,
31637
+ onMove,
31638
+ point,
31639
+ constrain = p => p
31640
+ } = args;
31641
+ const pickupPx = React__namespace.useRef([0, 0]);
31642
+ react.useDrag(state => {
31643
+ const {
31644
+ event,
31645
+ first,
31646
+ movement: pixelMovement
31647
+ } = state;
31648
+ event?.stopPropagation();
31649
+ if (first) {
31650
+ pickupPx.current = point;
31651
+ }
31652
+ if (mafs.vec.mag(pixelMovement) === 0) {
31653
+ return;
31654
+ }
31655
+ onMove?.(constrain(mafs.vec.add(pickupPx.current, pixelMovement)));
31656
+ }, {
31657
+ target,
31658
+ eventOptions: {
31659
+ passive: false
31660
+ }
31661
+ });
31662
+ }
31663
+
31664
+ // [L]inear Int[erp]olation: gets the weighted average of two values `a` and
31665
+ // `b`, with the given `fraction` specifying how much weight `a` and `b` get:
31666
+ // - if `fraction` is 0, `lerp` returns `a`.
31667
+ // - if `fraction` is 0.5, `lerp` returns the average of `a` and `b`.
31668
+ // - if `fraction` is 1, `lerp` returns `b`.
31669
+ function lerp(a, b, fraction) {
31670
+ return (b - a) * fraction + a;
31671
+ }
31672
+ function degreesToRadians(degrees) {
31673
+ return degrees / 180 * Math.PI;
31674
+ }
31675
+
31320
31676
  function initializeGraphState(params) {
31321
31677
  const {
31322
31678
  graph,
@@ -32024,27 +32380,13 @@ function boundAndSnapToSides(destinationPoint, _ref3, index) {
32024
32380
  return kmath.vector.add(coordsCopy[rel(-1)], offset);
32025
32381
  }
32026
32382
 
32027
- // Returns the closest point to the given `point` that is within the graph
32028
- // bounds given in `state`.
32029
- function bound$1(_ref4) {
32030
- let {
32031
- snapStep,
32032
- range,
32033
- point
32034
- } = _ref4;
32035
- const [requestedX, requestedY] = point;
32036
- const [snapX, snapY] = snapStep;
32037
- const [[minX, maxX], [minY, maxY]] = range;
32038
- return [clamp(requestedX, minX + snapX, maxX - snapX), clamp(requestedY, minY + snapY, maxY - snapY)];
32039
- }
32040
-
32041
32383
  // Returns the vector from the given point to the top-right corner of the graph when snapped to the grid
32042
- function maxMove(_ref5) {
32384
+ function maxMove(_ref4) {
32043
32385
  let {
32044
32386
  snapStep,
32045
32387
  range,
32046
32388
  point
32047
- } = _ref5;
32389
+ } = _ref4;
32048
32390
  const topRight = bound$1({
32049
32391
  snapStep,
32050
32392
  range,
@@ -32054,12 +32396,12 @@ function maxMove(_ref5) {
32054
32396
  }
32055
32397
 
32056
32398
  // Returns the vector from the given point to the bottom-left corner of the graph when snapped to the grid
32057
- function minMove(_ref6) {
32399
+ function minMove(_ref5) {
32058
32400
  let {
32059
32401
  snapStep,
32060
32402
  range,
32061
32403
  point
32062
- } = _ref6;
32404
+ } = _ref5;
32063
32405
  const bottomLeft = bound$1({
32064
32406
  snapStep,
32065
32407
  range,
@@ -32067,15 +32409,6 @@ function minMove(_ref6) {
32067
32409
  });
32068
32410
  return mafs.vec.sub(bottomLeft, point);
32069
32411
  }
32070
- function clamp(value, min, max) {
32071
- if (value < min) {
32072
- return min;
32073
- }
32074
- if (value > max) {
32075
- return max;
32076
- }
32077
- return value;
32078
- }
32079
32412
  const coordsOverlap = coords => coords.some((coord, i) => coords.some((c, j) => i !== j && kmath.vector.equal(coord, c)));
32080
32413
  function updateAtIndex(args) {
32081
32414
  const {
@@ -32209,6 +32542,10 @@ const StatefulMafsGraph = /*#__PURE__*/React__namespace.forwardRef((props, ref)
32209
32542
  dispatch(changeRange([[xMinRange, xMaxRange], [yMinRange, yMaxRange]]));
32210
32543
  }, [dispatch, xMinRange, xMaxRange, yMinRange, yMaxRange]);
32211
32544
  const numSegments = graph.type === "segment" ? graph.numSegments : null;
32545
+ const numSides = graph.type === "polygon" ? graph.numSides : null;
32546
+ const snapTo = graph.type === "polygon" ? graph.snapTo : null;
32547
+ const showAngles = graph.type === "polygon" ? graph.showAngles : null;
32548
+ const showSides = graph.type === "polygon" ? graph.showSides : null;
32212
32549
  const originalPropsRef = React.useRef(props);
32213
32550
  const latestPropsRef = wonderBlocksCore.useLatestRef(props);
32214
32551
  React.useEffect(() => {
@@ -32219,7 +32556,7 @@ const StatefulMafsGraph = /*#__PURE__*/React__namespace.forwardRef((props, ref)
32219
32556
  if (latestPropsRef.current !== originalPropsRef.current) {
32220
32557
  dispatch(reinitialize(latestPropsRef.current));
32221
32558
  }
32222
- }, [graph.type, numSegments, latestPropsRef]);
32559
+ }, [graph.type, numSegments, numSides, snapTo, showAngles, showSides, latestPropsRef]);
32223
32560
  return /*#__PURE__*/React__namespace.createElement(MafsGraph, _extends({}, props, {
32224
32561
  state: state,
32225
32562
  dispatch: dispatch
@@ -32286,7 +32623,7 @@ const MafsGraph = props => {
32286
32623
  }), props.lockedFigures && /*#__PURE__*/React__namespace.createElement(GraphLockedLayer, {
32287
32624
  lockedFigures: props.lockedFigures,
32288
32625
  range: state.range
32289
- }), renderGraph({
32626
+ }), "/* Protractor */", props.showProtractor && /*#__PURE__*/React__namespace.createElement(Protractor, null), renderGraph({
32290
32627
  state,
32291
32628
  dispatch
32292
32629
  })))));
@@ -33582,14 +33919,9 @@ class InteractiveGraph extends React__namespace.Component {
33582
33919
  return InteractiveGraph.validate(this.getUserInput?.(), rubric, this);
33583
33920
  }
33584
33921
  render() {
33585
- if (this.props.graph.type === "polygon" && this.props.graph.numSides === "unlimited") {
33586
- return /*#__PURE__*/React__namespace.createElement(LegacyInteractiveGraph, _extends({
33587
- ref: this.legacyGraphRef
33588
- }, this.props));
33589
- }
33590
-
33591
33922
  // Mafs shim
33592
- if (this.props.apiOptions?.flags?.["mafs"]?.[this.props.graph.type]) {
33923
+ const mafsFlags = this.props.apiOptions?.flags?.["mafs"];
33924
+ if (shouldUseMafs(mafsFlags, this.props.graph)) {
33593
33925
  const box = getInteractiveBoxFromSizeClass(this.props.containerSizeClass);
33594
33926
  const gridStep = this.props.gridStep || Util.getGridStep(this.props.range, this.props.step, box[0]);
33595
33927
  const snapStep = this.props.snapStep || Util.snapStepFromGridStep(gridStep);
@@ -34148,6 +34480,33 @@ class InteractiveGraph extends React__namespace.Component {
34148
34480
  return props.graph;
34149
34481
  }
34150
34482
  }
34483
+
34484
+ // exported for testing
34485
+ function shouldUseMafs(mafsFlags, graph) {
34486
+ if (typeof mafsFlags === "boolean" || typeof mafsFlags === "undefined") {
34487
+ return false;
34488
+ }
34489
+ switch (graph.type) {
34490
+ case "point":
34491
+ if (graph.numPoints === UNLIMITED) {
34492
+ // TODO(benchristel): add a feature flag for the "unlimited"
34493
+ // case once we've implemented point graphs with unlimited
34494
+ // points
34495
+ return false;
34496
+ }
34497
+ return Boolean(mafsFlags["point"]);
34498
+ case "polygon":
34499
+ if (graph.numSides === UNLIMITED) {
34500
+ // TODO(benchristel): add a feature flag for the "unlimited"
34501
+ // case once we've implemented polygon graphs with unlimited
34502
+ // sides
34503
+ return false;
34504
+ }
34505
+ return Boolean(mafsFlags["polygon"]);
34506
+ default:
34507
+ return Boolean(mafsFlags[graph.type]);
34508
+ }
34509
+ }
34151
34510
  var InteractiveGraph$1 = {
34152
34511
  name: "interactive-graph",
34153
34512
  displayName: "Interactive graph",
@@ -44387,7 +44746,7 @@ const init = function (options) {
44387
44746
 
44388
44747
  // This file is processed by a Rollup plugin (replace) to inject the production
44389
44748
  const libName = "@khanacademy/perseus";
44390
- const libVersion = "23.6.0";
44749
+ const libVersion = "24.1.0";
44391
44750
  perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
44392
44751
 
44393
44752
  const apiVersion = {