@khanacademy/perseus 24.0.0 → 24.2.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');
@@ -13187,7 +13188,7 @@ ___default["default"].extend(GraphUtils.Graphie.prototype, {
13187
13188
  }(),
13188
13189
  protractor: function (center) {
13189
13190
  // @ts-expect-error - TS2350 - Only a void function can be called with the 'new' keyword.
13190
- return new Protractor(this, center);
13191
+ return new Protractor$1(this, center);
13191
13192
  },
13192
13193
  ruler: function (options) {
13193
13194
  // @ts-expect-error - TS2350 - Only a void function can be called with the 'new' keyword.
@@ -13195,7 +13196,7 @@ ___default["default"].extend(GraphUtils.Graphie.prototype, {
13195
13196
  },
13196
13197
  addPoints: kmath.vector.add
13197
13198
  });
13198
- function Protractor(graph, center) {
13199
+ function Protractor$1(graph, center) {
13199
13200
  // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
13200
13201
  this.set = graph.raphael.set();
13201
13202
 
@@ -28849,7 +28850,25 @@ class PathBuilder {
28849
28850
  move(x, y) {
28850
28851
  this.path.push({
28851
28852
  action: "M",
28852
- 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]
28853
28872
  });
28854
28873
  return this;
28855
28874
  }
@@ -28858,7 +28877,7 @@ class PathBuilder {
28858
28877
  curve(control1X, control1Y, control2X, control2Y, endX, endY) {
28859
28878
  this.path.push({
28860
28879
  action: "C",
28861
- coords: [control1X, control1Y, control2X, control2Y, endX, endY]
28880
+ args: [control1X, control1Y, control2X, control2Y, endX, endY]
28862
28881
  });
28863
28882
  return this;
28864
28883
  }
@@ -28870,13 +28889,40 @@ class PathBuilder {
28870
28889
  }
28871
28890
  }
28872
28891
  function commandToString(command) {
28873
- return `${command.action}${command.coords.join(" ")}`;
28892
+ return `${command.action}${command.args.join(" ")}`;
28874
28893
  }
28875
28894
  function scaleCommandBy(scaleFactor) {
28876
- return command => ({
28877
- ...command,
28878
- coords: command.coords.map(c => c * scaleFactor)
28879
- });
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
+ };
28880
28926
  }
28881
28927
 
28882
28928
  // We use the pathBuilder here to scale up the SVG path coordinates used
@@ -29264,8 +29310,171 @@ function snap(snapStep, point) {
29264
29310
  return [Math.round(requestedX / snapX) * snapX, Math.round(requestedY / snapY) * snapY];
29265
29311
  }
29266
29312
 
29267
- function useDraggable(params) {
29268
- 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;
29269
29478
  }
29270
29479
 
29271
29480
  // The hitbox size of 48px by 48px is preserved from the legacy interactive
@@ -30099,6 +30308,7 @@ function DragHandle(props) {
30099
30308
  }), dragHandleDotPositions.map(offsetPx => {
30100
30309
  const [xPx, yPx] = mafs.vec.add(offsetPx, centerPx);
30101
30310
  return /*#__PURE__*/React__namespace.createElement("circle", {
30311
+ key: `circle-${xPx}-${yPx}`,
30102
30312
  className: "movable-circle-handle-dot",
30103
30313
  cx: xPx,
30104
30314
  cy: yPx
@@ -31316,6 +31526,154 @@ const LegacyGrid = _ref => {
31316
31526
  return null;
31317
31527
  };
31318
31528
 
31529
+ const protractorImage = "https://ka-perseus-graphie.s3.amazonaws.com/e9d032f2ab8b95979f674fbfa67056442ba1ff6a.png";
31530
+
31531
+ // The vector from the center of the protractor to the top left corner of the
31532
+ // protractor image, in pixels. Used for positioning.
31533
+ const centerToTopLeft = [-180, -170];
31534
+
31535
+ // The vector from the center of the protractor to the center of the rotation
31536
+ // handle.
31537
+ const centerToRotationHandle = [-176, -15];
31538
+ function Protractor() {
31539
+ const {
31540
+ range,
31541
+ snapStep
31542
+ } = useGraphConfig();
31543
+ const [[xMin, xMax], [yMin, yMax]] = range;
31544
+ // Position the protractor in the center of the graph (horizontally), and
31545
+ // 95% of the way to the bottom of the graph (vertically).
31546
+ const initialCenter = [lerp(xMin, xMax, 0.5), lerp(yMin, yMax, 0.05)];
31547
+ const [center, setCenter] = React.useState(initialCenter);
31548
+ const [rotationHandleOffset, setRotationHandleOffset] = React.useState(centerToRotationHandle);
31549
+ const draggableRef = React.useRef(null);
31550
+ useDraggable({
31551
+ gestureTarget: draggableRef,
31552
+ onMove: setCenter,
31553
+ point: center,
31554
+ constrain: point => bound$1({
31555
+ snapStep,
31556
+ range,
31557
+ point
31558
+ })
31559
+ });
31560
+ const rotationHandleRef = React.useRef(null);
31561
+ useDraggablePx({
31562
+ gestureTarget: rotationHandleRef,
31563
+ onMove: setRotationHandleOffset,
31564
+ point: rotationHandleOffset,
31565
+ constrain: constrainToCircle
31566
+ });
31567
+ const [centerPx] = useTransformVectorsToPixels(center);
31568
+ const topLeftPx = mafs.vec.add(centerPx, centerToTopLeft);
31569
+ const angle = calculateAngleInDegrees(rotationHandleOffset) - calculateAngleInDegrees(centerToRotationHandle);
31570
+ return /*#__PURE__*/React__namespace.createElement("g", {
31571
+ ref: draggableRef,
31572
+ transform: `translate(${topLeftPx[0]}, ${topLeftPx[1]}), rotate(${angle})`,
31573
+ style: {
31574
+ transformOrigin: `${-centerToTopLeft[0]}px ${-centerToTopLeft[1]}px`
31575
+ }
31576
+ }, /*#__PURE__*/React__namespace.createElement("image", {
31577
+ href: protractorImage
31578
+ }), /*#__PURE__*/React__namespace.createElement("g", {
31579
+ transform: `translate(5, ${-centerToTopLeft[1]})`,
31580
+ ref: rotationHandleRef
31581
+ }, /*#__PURE__*/React__namespace.createElement(RotationArrow, null)));
31582
+ }
31583
+ function RotationArrow() {
31584
+ const radius = 175;
31585
+ const angleDeg = 10;
31586
+ const angleRad = degreesToRadians(angleDeg);
31587
+ const endX = radius * (1 - Math.cos(angleRad));
31588
+ const endY = radius * -Math.sin(angleRad);
31589
+ const rotationArrow = pathBuilder().move(0, 0).circularArc(radius, endX, endY, {
31590
+ sweep: true
31591
+ }).build();
31592
+ const arrowhead = pathBuilder().move(-8, 0).line(0, 10).line(8, 0).build();
31593
+ const targetRadius = TARGET_SIZE / 2;
31594
+ return /*#__PURE__*/React__namespace.createElement("g", {
31595
+ className: "protractor-rotation-handle"
31596
+ }, /*#__PURE__*/React__namespace.createElement("path", {
31597
+ className: "protractor-rotation-handle-arrow-arc",
31598
+ d: rotationArrow
31599
+ }), /*#__PURE__*/React__namespace.createElement("path", {
31600
+ className: "protractor-rotation-handle-arrowhead",
31601
+ d: arrowhead
31602
+ }), /*#__PURE__*/React__namespace.createElement("path", {
31603
+ className: "protractor-rotation-handle-arrowhead",
31604
+ d: arrowhead,
31605
+ transform: `translate(${endX}, ${endY}), rotate(${180 + angleDeg})`
31606
+ }), /*#__PURE__*/React__namespace.createElement("ellipse", {
31607
+ cx: "0px",
31608
+ cy: "-15px",
31609
+ rx: targetRadius,
31610
+ ry: targetRadius,
31611
+ fill: "none"
31612
+ }));
31613
+ }
31614
+ const protractorRadius = mafs.vec.mag(centerToRotationHandle);
31615
+ function constrainToCircle(edgePoint) {
31616
+ return mafs.vec.withMag(edgePoint, protractorRadius);
31617
+ }
31618
+
31619
+ /* The implementation of useDraggablePx is derived from
31620
+ * https://github.com/stevenpetryk/mafs/blob/4520319379a2cc2df8148d8baaef1f85db117103/src/interaction/useMovable.tsx#L20-L83
31621
+ * and copied here under the terms of the MIT license.
31622
+ *
31623
+ * Copyright 2021 Steven Petryk
31624
+ *
31625
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
31626
+ * of this software and associated documentation files (the "Software"), to deal
31627
+ * in the Software without restriction, including without limitation the rights
31628
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
31629
+ * copies of the Software, and to permit persons to whom the Software is
31630
+ * furnished to do so, subject to the following conditions:
31631
+ *
31632
+ * The above copyright notice and this permission notice shall be included in
31633
+ * all copies or substantial portions of the Software.
31634
+ */
31635
+ function useDraggablePx(args) {
31636
+ const {
31637
+ gestureTarget: target,
31638
+ onMove,
31639
+ point,
31640
+ constrain = p => p
31641
+ } = args;
31642
+ const pickupPx = React__namespace.useRef([0, 0]);
31643
+ react.useDrag(state => {
31644
+ const {
31645
+ event,
31646
+ first,
31647
+ movement: pixelMovement
31648
+ } = state;
31649
+ event?.stopPropagation();
31650
+ if (first) {
31651
+ pickupPx.current = point;
31652
+ }
31653
+ if (mafs.vec.mag(pixelMovement) === 0) {
31654
+ return;
31655
+ }
31656
+ onMove?.(constrain(mafs.vec.add(pickupPx.current, pixelMovement)));
31657
+ }, {
31658
+ target,
31659
+ eventOptions: {
31660
+ passive: false
31661
+ }
31662
+ });
31663
+ }
31664
+
31665
+ // [L]inear Int[erp]olation: gets the weighted average of two values `a` and
31666
+ // `b`, with the given `fraction` specifying how much weight `a` and `b` get:
31667
+ // - if `fraction` is 0, `lerp` returns `a`.
31668
+ // - if `fraction` is 0.5, `lerp` returns the average of `a` and `b`.
31669
+ // - if `fraction` is 1, `lerp` returns `b`.
31670
+ function lerp(a, b, fraction) {
31671
+ return (b - a) * fraction + a;
31672
+ }
31673
+ function degreesToRadians(degrees) {
31674
+ return degrees / 180 * Math.PI;
31675
+ }
31676
+
31319
31677
  function initializeGraphState(params) {
31320
31678
  const {
31321
31679
  graph,
@@ -32023,27 +32381,13 @@ function boundAndSnapToSides(destinationPoint, _ref3, index) {
32023
32381
  return kmath.vector.add(coordsCopy[rel(-1)], offset);
32024
32382
  }
32025
32383
 
32026
- // Returns the closest point to the given `point` that is within the graph
32027
- // bounds given in `state`.
32028
- function bound$1(_ref4) {
32029
- let {
32030
- snapStep,
32031
- range,
32032
- point
32033
- } = _ref4;
32034
- const [requestedX, requestedY] = point;
32035
- const [snapX, snapY] = snapStep;
32036
- const [[minX, maxX], [minY, maxY]] = range;
32037
- return [clamp(requestedX, minX + snapX, maxX - snapX), clamp(requestedY, minY + snapY, maxY - snapY)];
32038
- }
32039
-
32040
32384
  // Returns the vector from the given point to the top-right corner of the graph when snapped to the grid
32041
- function maxMove(_ref5) {
32385
+ function maxMove(_ref4) {
32042
32386
  let {
32043
32387
  snapStep,
32044
32388
  range,
32045
32389
  point
32046
- } = _ref5;
32390
+ } = _ref4;
32047
32391
  const topRight = bound$1({
32048
32392
  snapStep,
32049
32393
  range,
@@ -32053,12 +32397,12 @@ function maxMove(_ref5) {
32053
32397
  }
32054
32398
 
32055
32399
  // Returns the vector from the given point to the bottom-left corner of the graph when snapped to the grid
32056
- function minMove(_ref6) {
32400
+ function minMove(_ref5) {
32057
32401
  let {
32058
32402
  snapStep,
32059
32403
  range,
32060
32404
  point
32061
- } = _ref6;
32405
+ } = _ref5;
32062
32406
  const bottomLeft = bound$1({
32063
32407
  snapStep,
32064
32408
  range,
@@ -32066,15 +32410,6 @@ function minMove(_ref6) {
32066
32410
  });
32067
32411
  return mafs.vec.sub(bottomLeft, point);
32068
32412
  }
32069
- function clamp(value, min, max) {
32070
- if (value < min) {
32071
- return min;
32072
- }
32073
- if (value > max) {
32074
- return max;
32075
- }
32076
- return value;
32077
- }
32078
32413
  const coordsOverlap = coords => coords.some((coord, i) => coords.some((c, j) => i !== j && kmath.vector.equal(coord, c)));
32079
32414
  function updateAtIndex(args) {
32080
32415
  const {
@@ -32289,7 +32624,7 @@ const MafsGraph = props => {
32289
32624
  }), props.lockedFigures && /*#__PURE__*/React__namespace.createElement(GraphLockedLayer, {
32290
32625
  lockedFigures: props.lockedFigures,
32291
32626
  range: state.range
32292
- }), renderGraph({
32627
+ }), "/* Protractor */", props.showProtractor && /*#__PURE__*/React__namespace.createElement(Protractor, null), renderGraph({
32293
32628
  state,
32294
32629
  dispatch
32295
32630
  })))));
@@ -33585,14 +33920,9 @@ class InteractiveGraph extends React__namespace.Component {
33585
33920
  return InteractiveGraph.validate(this.getUserInput?.(), rubric, this);
33586
33921
  }
33587
33922
  render() {
33588
- if (this.props.graph.type === "polygon" && this.props.graph.numSides === "unlimited") {
33589
- return /*#__PURE__*/React__namespace.createElement(LegacyInteractiveGraph, _extends({
33590
- ref: this.legacyGraphRef
33591
- }, this.props));
33592
- }
33593
-
33594
33923
  // Mafs shim
33595
- if (this.props.apiOptions?.flags?.["mafs"]?.[this.props.graph.type]) {
33924
+ const mafsFlags = this.props.apiOptions?.flags?.["mafs"];
33925
+ if (shouldUseMafs(mafsFlags, this.props.graph)) {
33596
33926
  const box = getInteractiveBoxFromSizeClass(this.props.containerSizeClass);
33597
33927
  const gridStep = this.props.gridStep || Util.getGridStep(this.props.range, this.props.step, box[0]);
33598
33928
  const snapStep = this.props.snapStep || Util.snapStepFromGridStep(gridStep);
@@ -34151,6 +34481,33 @@ class InteractiveGraph extends React__namespace.Component {
34151
34481
  return props.graph;
34152
34482
  }
34153
34483
  }
34484
+
34485
+ // exported for testing
34486
+ function shouldUseMafs(mafsFlags, graph) {
34487
+ if (typeof mafsFlags === "boolean" || typeof mafsFlags === "undefined") {
34488
+ return false;
34489
+ }
34490
+ switch (graph.type) {
34491
+ case "point":
34492
+ if (graph.numPoints === UNLIMITED) {
34493
+ // TODO(benchristel): add a feature flag for the "unlimited"
34494
+ // case once we've implemented point graphs with unlimited
34495
+ // points
34496
+ return false;
34497
+ }
34498
+ return Boolean(mafsFlags["point"]);
34499
+ case "polygon":
34500
+ if (graph.numSides === UNLIMITED) {
34501
+ // TODO(benchristel): add a feature flag for the "unlimited"
34502
+ // case once we've implemented polygon graphs with unlimited
34503
+ // sides
34504
+ return false;
34505
+ }
34506
+ return Boolean(mafsFlags["polygon"]);
34507
+ default:
34508
+ return Boolean(mafsFlags[graph.type]);
34509
+ }
34510
+ }
34154
34511
  var InteractiveGraph$1 = {
34155
34512
  name: "interactive-graph",
34156
34513
  displayName: "Interactive graph",
@@ -44390,7 +44747,7 @@ const init = function (options) {
44390
44747
 
44391
44748
  // This file is processed by a Rollup plugin (replace) to inject the production
44392
44749
  const libName = "@khanacademy/perseus";
44393
- const libVersion = "24.0.0";
44750
+ const libVersion = "24.2.0";
44394
44751
  perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
44395
44752
 
44396
44753
  const apiVersion = {