@khanacademy/perseus 20.1.0 → 20.3.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.js CHANGED
@@ -2,7 +2,7 @@ import * as KAS from '@khanacademy/kas';
2
2
  import { keyTranslator, CursorContext, createMathField, getCursorContext, DesktopKeypad, convertDotToTimesByLocale, mathQuillInstance, KeypadInput, KeypadType, KeyArray, keypadElementPropType } from '@khanacademy/math-input';
3
3
  import * as PerseusLinter from '@khanacademy/perseus-linter';
4
4
  import { linterContextDefault, Rule, linterContextProps } from '@khanacademy/perseus-linter';
5
- import { View, RenderStateRoot, useUniqueIdWithMock, Text as Text$1 } from '@khanacademy/wonder-blocks-core';
5
+ import { View, RenderStateRoot, UniqueIDProvider, useUniqueIdWithMock, Text as Text$1 } from '@khanacademy/wonder-blocks-core';
6
6
  import * as i18n from '@khanacademy/wonder-blocks-i18n';
7
7
  import Tooltip$1 from '@khanacademy/wonder-blocks-tooltip';
8
8
  import classNames from 'classnames';
@@ -28,8 +28,10 @@ import Button from '@khanacademy/wonder-blocks-button';
28
28
  import { Strut } from '@khanacademy/wonder-blocks-layout';
29
29
  import Banner from '@khanacademy/wonder-blocks-banner';
30
30
  import { OptionItem, SingleSelect, MultiSelect } from '@khanacademy/wonder-blocks-dropdown';
31
+ import caretDown from '@phosphor-icons/core/assets/regular/caret-down.svg';
32
+ import caretUp from '@phosphor-icons/core/assets/regular/caret-up.svg';
31
33
  import debounce$2 from 'lodash.debounce';
32
- import { vec, useMovable, useTransformContext, MovablePoint as MovablePoint$5, Coordinates, Mafs } from 'mafs';
34
+ import { Point as Point$2, useMovable, useTransformContext, vec, Coordinates, Mafs } from 'mafs';
33
35
  import { PhosphorIcon } from '@khanacademy/wonder-blocks-icon';
34
36
  import questionIcon from '@phosphor-icons/core/regular/question.svg';
35
37
  import { LabelSmall, LabelMedium } from '@khanacademy/wonder-blocks-typography';
@@ -2680,11 +2682,14 @@ function combineScores(scoreA, scoreB) {
2680
2682
  }
2681
2683
  });
2682
2684
  }
2685
+ function isCorrect(score) {
2686
+ return score.type === "points" && score.earned >= score.total;
2687
+ }
2683
2688
  function keScoreFromPerseusScore(score, guess, state) {
2684
2689
  if (score.type === "points") {
2685
2690
  return {
2686
2691
  empty: false,
2687
- correct: score.earned >= score.total,
2692
+ correct: isCorrect(score),
2688
2693
  message: score.message,
2689
2694
  guess: guess,
2690
2695
  state: state
@@ -14981,7 +14986,7 @@ const DEFAULT_STATE$1 = {
14981
14986
  // TODO(benchristel): this is a memory leak. We add items to
14982
14987
  // tooltipResetFunctions, but never remove them.
14983
14988
  const tooltipResetFunctions = [];
14984
- class MovablePoint$4 {
14989
+ class MovablePoint$5 {
14985
14990
  constructor(graphie, movable, options) {
14986
14991
  this.graphie = void 0;
14987
14992
  this.movable = void 0;
@@ -15498,15 +15503,15 @@ class MovablePoint$4 {
15498
15503
  this.movable.toFront();
15499
15504
  }
15500
15505
  }
15501
- MovablePoint$4.add = MovablePointOptions.add;
15502
- MovablePoint$4.modify = MovablePointOptions.modify;
15503
- MovablePoint$4.draw = MovablePointOptions.draw;
15504
- MovablePoint$4.remove = MovablePointOptions.remove;
15505
- MovablePoint$4.onMoveStart = MovablePointOptions.onMoveStart;
15506
- MovablePoint$4.constraints = MovablePointOptions.constraints;
15507
- MovablePoint$4.onMove = MovablePointOptions.onMove;
15508
- MovablePoint$4.onMoveEnd = MovablePointOptions.onMoveEnd;
15509
- MovablePoint$4.onClick = MovablePointOptions.onClick;
15506
+ MovablePoint$5.add = MovablePointOptions.add;
15507
+ MovablePoint$5.modify = MovablePointOptions.modify;
15508
+ MovablePoint$5.draw = MovablePointOptions.draw;
15509
+ MovablePoint$5.remove = MovablePointOptions.remove;
15510
+ MovablePoint$5.onMoveStart = MovablePointOptions.onMoveStart;
15511
+ MovablePoint$5.constraints = MovablePointOptions.constraints;
15512
+ MovablePoint$5.onMove = MovablePointOptions.onMove;
15513
+ MovablePoint$5.onMoveEnd = MovablePointOptions.onMoveEnd;
15514
+ MovablePoint$5.onClick = MovablePointOptions.onClick;
15510
15515
  function getKey(eventName, id) {
15511
15516
  return eventName + ":" + id;
15512
15517
  }
@@ -16100,10 +16105,10 @@ _.extend(MovablePolygon.prototype, {
16100
16105
  });
16101
16106
 
16102
16107
  const Interactive2 = {
16103
- MovablePoint: MovablePoint$4,
16108
+ MovablePoint: MovablePoint$5,
16104
16109
  addMovablePoint: function (graphie, options) {
16105
16110
  const movable = new Movable(graphie, {});
16106
- return new MovablePoint$4(graphie, movable, options);
16111
+ return new MovablePoint$5(graphie, movable, options);
16107
16112
  },
16108
16113
  MovableLine: MovableLine$3,
16109
16114
  addMovableLine: function (graphie, options) {
@@ -16144,7 +16149,7 @@ const Interactive2 = {
16144
16149
  }
16145
16150
  };
16146
16151
 
16147
- const MovablePoint$3 = GraphieClasses.createClass({
16152
+ const MovablePoint$4 = GraphieClasses.createClass({
16148
16153
  displayName: "MovablePoint",
16149
16154
  movableProps: ["children"],
16150
16155
  _getProps: function () {
@@ -16201,7 +16206,7 @@ const MovablePoint$3 = GraphieClasses.createClass({
16201
16206
  });
16202
16207
 
16203
16208
  // Include helper methods, such as MovablePoint.constrain.snap()
16204
- _.extend(MovablePoint$3, Interactive2.MovablePoint);
16209
+ _.extend(MovablePoint$4, Interactive2.MovablePoint);
16205
16210
  const MovableLine$2 = GraphieClasses.createClass({
16206
16211
  displayName: "MovableLine",
16207
16212
  movableProps: ["children"],
@@ -16371,7 +16376,7 @@ var Movables = {
16371
16376
  Label: Label$1,
16372
16377
  Line: Line$2,
16373
16378
  MovableLine: MovableLine$2,
16374
- MovablePoint: MovablePoint$3,
16379
+ MovablePoint: MovablePoint$4,
16375
16380
  Parabola: Parabola,
16376
16381
  Path: Path,
16377
16382
  Plot: Plot$2,
@@ -24882,57 +24887,42 @@ class Explanation extends React.Component {
24882
24887
  };
24883
24888
  }
24884
24889
  render() {
24885
- const {
24886
- isArticle,
24887
- isMobile
24888
- } = this.props.apiOptions;
24889
24890
  const promptText = this.state.expanded ? this.props.hidePrompt : this.props.showPrompt;
24890
- let promptContainer;
24891
-
24892
- // TODO(diedra): This isn't a valid href;
24893
- // change this to a button that looks like a link.
24894
- const href = "javascript:void(0)";
24895
- const onClick = this._onClick;
24896
- if (isMobile) {
24897
- promptContainer = /*#__PURE__*/React.createElement("div", {
24898
- className: css(styles$p.linkContainer)
24899
- }, /*#__PURE__*/React.createElement("a", {
24900
- className: css(styles$p.mobileExplanationLink),
24901
- href: href,
24902
- onClick: onClick,
24903
- role: "button",
24904
- "aria-expanded": this.state.expanded
24905
- }, promptText), this.state.expanded && /*#__PURE__*/React.createElement("svg", {
24906
- className: css(styles$p.disclosureArrow)
24907
- }, /*#__PURE__*/React.createElement("polygon", {
24908
- style: {
24909
- fill: backgroundColor
24910
- },
24911
- points: `0,${arrowHeight} ` + `${arrowWidth},${arrowHeight} ` + `${arrowWidth / 2},0`
24912
- })));
24913
- } else {
24914
- const viewStyling = isArticle ? [styles$p.explanationLink, styles$p.articleLink] : [styles$p.explanationLink, styles$p.exerciseLink];
24915
- promptContainer = /*#__PURE__*/React.createElement(Clickable, {
24916
- onClick: onClick,
24917
- "aria-expanded": this.state.expanded
24918
- }, () => /*#__PURE__*/React.createElement(View, {
24919
- style: viewStyling
24920
- }, `[${promptText}]`));
24921
- }
24922
- const expandedStyle = isMobile ? styles$p.contentExpandedMobile : styles$p.contentExpanded;
24923
- return /*#__PURE__*/React.createElement("div", {
24924
- className: css(styles$p.container)
24925
- }, promptContainer, this.state.expanded && /*#__PURE__*/React.createElement("div", {
24926
- className: css(styles$p.content, isMobile && styles$p.contentMobile, this.state.expanded && expandedStyle)
24927
- // eslint-disable-next-line react/no-string-refs
24928
- ,
24929
- ref: "content"
24891
+ const caretIcon = this.state.expanded ? caretUp : caretDown;
24892
+ const allowTransition = window.matchMedia("(prefers-reduced-motion: no-preference)").matches;
24893
+
24894
+ // Special styling is needed to fit the button in a block of text without throwing off the line spacing.
24895
+ // While the button is not normally included in a block of text, it needs to be able to accommodate such a case.
24896
+ const buttonStyleOverrides = {
24897
+ height: "22px",
24898
+ marginLeft: "-2px",
24899
+ padding: "0 2px"
24900
+ };
24901
+ const contentStyling = [styles$p.content, this.state.expanded ? styles$p.contentExpanded : styles$p.contentCollapsed, allowTransition && (this.state.expanded ? styles$p.transitionExpanded : styles$p.transitionCollapsed)];
24902
+ return /*#__PURE__*/React.createElement(UniqueIDProvider, {
24903
+ mockOnFirstRender: false,
24904
+ scope: "explanation-widget"
24905
+ }, ids => /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Button, {
24906
+ "aria-expanded": this.state.expanded,
24907
+ "aria-controls": ids.get("content"),
24908
+ endIcon: caretIcon,
24909
+ kind: "tertiary",
24910
+ onClick: this._onClick,
24911
+ size: "large",
24912
+ style: buttonStyleOverrides
24913
+ }, promptText), /*#__PURE__*/React.createElement(View, {
24914
+ id: ids.get("content"),
24915
+ style: contentStyling,
24916
+ "aria-hidden": !this.state.expanded,
24917
+ testId: "content-container"
24918
+ }, /*#__PURE__*/React.createElement(View, {
24919
+ style: styles$p.contentWrapper
24930
24920
  }, /*#__PURE__*/React.createElement(Renderer, {
24931
24921
  apiOptions: this.props.apiOptions,
24932
24922
  content: this.props.explanation,
24933
24923
  widgets: this.props.widgets,
24934
24924
  linterContext: this.props.linterContext
24935
- })));
24925
+ })))));
24936
24926
  }
24937
24927
  }
24938
24928
  Explanation.defaultProps = {
@@ -24944,94 +24934,44 @@ Explanation.defaultProps = {
24944
24934
  };
24945
24935
  const leftBorderSpacing = 23;
24946
24936
  const verticalContentPadding = 10;
24947
- const arrowWidth = 30;
24948
24937
  const arrowHeight = 14;
24949
- const backgroundColor = gray95;
24950
24938
  const styles$p = StyleSheet.create({
24951
- container: {
24952
- display: "inline",
24939
+ content: {
24940
+ borderLeft: "0px solid #ccc",
24941
+ display: "inline-grid",
24953
24942
  position: "relative"
24954
24943
  },
24955
- linkContainer: {
24956
- display: "inline-block"
24957
- },
24958
- explanationLink: {
24959
- fontStyle: "italic",
24960
- color: "#007d96"
24961
- },
24962
- articleLink: {
24963
- // Copied from .body-text in articles.less
24964
- fontSize: 20,
24965
- lineHeight: "30px"
24966
- },
24967
- exerciseLink: {
24968
- // Copied from .legacy-typography in util.less
24969
- fontSize: 14,
24970
- lineHeight: "19.6px"
24971
- },
24972
- mobileExplanationLink: {
24973
- color: kaGreen$1,
24974
- borderBottom: `dashed 1px ${kaGreen$1}`,
24975
- textDecoration: "none",
24976
- // TODO(benkomalo): these should be pulled in from common typography
24977
- // shared files so we have a single place where the type hierarchy is
24978
- // defined; one off font sizes for individual components should be
24979
- // avoided.
24980
- [mediaQueries.xl]: {
24981
- fontSize: 22,
24982
- lineHeight: 1.4
24983
- },
24984
- [mediaQueries.lgOrSmaller]: {
24985
- fontSize: 20,
24986
- lineHeight: 1.5
24987
- },
24988
- [mediaQueries.smOrSmaller]: {
24989
- fontSize: 18,
24990
- lineHeight: 1.2
24991
- }
24992
- },
24993
- content: {
24994
- position: "relative",
24995
- transition: "margin-top 0.1s"
24944
+ contentCollapsed: {
24945
+ gridTemplateColumns: "0fr",
24946
+ gridTemplateRows: "0fr",
24947
+ marginBottom: 0,
24948
+ marginTop: 0,
24949
+ minWidth: "0",
24950
+ paddingBottom: 0,
24951
+ visibility: "hidden"
24996
24952
  },
24997
24953
  contentExpanded: {
24998
- borderLeft: "5px solid #ccc",
24954
+ borderLeftWidth: "5px",
24955
+ gridTemplateColumns: "1fr",
24956
+ gridTemplateRows: "1fr",
24999
24957
  marginLeft: -leftBorderSpacing,
24958
+ minWidth: "100%",
25000
24959
  paddingLeft: leftBorderSpacing,
25001
24960
  paddingBottom: verticalContentPadding,
24961
+ visibility: "visible",
25002
24962
  // Note: we still use arrow height as the vertical margin, even on
25003
24963
  // desktop when there is no arrow, but it's good enough.
25004
24964
  marginBottom: arrowHeight,
25005
24965
  marginTop: arrowHeight
25006
24966
  },
25007
- contentExpandedMobile: {
25008
- boxSizing: "content-box",
25009
- paddingTop: 32,
25010
- paddingBottom: 32,
25011
- marginTop: arrowHeight
24967
+ contentWrapper: {
24968
+ overflow: "hidden"
25012
24969
  },
25013
- contentMobile: {
25014
- background: backgroundColor,
25015
- // TODO(benkomalo): this is to "full bleed" the background.
25016
- // The actual content padding differs depending on the host
25017
- // container, so this needs to be fixed eventually.
25018
- marginLeft: negativePhoneMargin,
25019
- marginRight: negativePhoneMargin,
25020
- paddingLeft: phoneMargin,
25021
- paddingRight: phoneMargin
25022
- },
25023
- disclosureArrow: {
25024
- // HACK - positioning at "bottom: 0", doesn't actually position it to
25025
- // the real bottom, because the container is `inline-block`, and it
25026
- // seems to position it to the baseline? We put in a generous
25027
- // fudge factor to position it down to be flush with the content box
25028
- // below it.
25029
- bottom: -(arrowHeight + 5),
25030
- height: arrowHeight,
25031
- left: "50%",
25032
- marginLeft: -(arrowWidth / 2),
25033
- position: "absolute",
25034
- width: arrowWidth
24970
+ transitionCollapsed: {
24971
+ transition: "all 0.25s step-end, grid-template-rows 0.25s, margin-top 0.25s, margin-bottom 0.25s, padding-bottom 0.25s"
24972
+ },
24973
+ transitionExpanded: {
24974
+ transition: "grid-template-rows 0.5s, margin-top 0.5s, margin-bottom 0.5s, padding-bottom 0.5s"
25035
24975
  }
25036
24976
  });
25037
24977
  var Explanation$1 = {
@@ -26348,7 +26288,7 @@ var util = /*#__PURE__*/Object.freeze({
26348
26288
  });
26349
26289
 
26350
26290
  // @ts-expect-error - TS2339 - Property 'MovablePoint' does not exist on type 'typeof Graphie'.
26351
- const MovablePoint$2 = Graphie.MovablePoint;
26291
+ const MovablePoint$3 = Graphie.MovablePoint;
26352
26292
  // @ts-expect-error - TS2339 - Property 'MovableLine' does not exist on type 'typeof Graphie'.
26353
26293
  const MovableLine$1 = Graphie.MovableLine;
26354
26294
  function isFlipped(newCoord, oldCoord, line) {
@@ -26430,7 +26370,7 @@ class FunctionGrapher extends React.Component {
26430
26370
  }],
26431
26371
  normalStyle: dashed,
26432
26372
  highlightStyle: dashed
26433
- }, _.map(asymptote, (coord, i) => /*#__PURE__*/React.createElement(MovablePoint$2, {
26373
+ }, _.map(asymptote, (coord, i) => /*#__PURE__*/React.createElement(MovablePoint$3, {
26434
26374
  key: `asymptoteCoord-${i}`,
26435
26375
  coord: coord,
26436
26376
  static: true,
@@ -26445,7 +26385,7 @@ class FunctionGrapher extends React.Component {
26445
26385
  }
26446
26386
  render() {
26447
26387
  const pointForCoord = (coord, i) => {
26448
- return /*#__PURE__*/React.createElement(MovablePoint$2, {
26388
+ return /*#__PURE__*/React.createElement(MovablePoint$3, {
26449
26389
  key: i,
26450
26390
  coord: coord,
26451
26391
  static: this.props.static,
@@ -27279,7 +27219,7 @@ const Label = Graphie.Label;
27279
27219
  // @ts-expect-error - TS2339 - Property 'Line' does not exist on type 'typeof Graphie'.
27280
27220
  const Line$1 = Graphie.Line;
27281
27221
  // @ts-expect-error - TS2339 - Property 'MovablePoint' does not exist on type 'typeof Graphie'.
27282
- const MovablePoint$1 = Graphie.MovablePoint;
27222
+ const MovablePoint$2 = Graphie.MovablePoint;
27283
27223
  // @ts-expect-error - TS2339 - Property 'MovableLine' does not exist on type 'typeof Graphie'.
27284
27224
  const MovableLine = Graphie.MovableLine;
27285
27225
  // @ts-expect-error - TS2339 - Property 'Plot' does not exist on type 'typeof Graphie'.
@@ -27528,7 +27468,7 @@ class Interaction extends React.Component {
27528
27468
  return [coordX, coordY];
27529
27469
  }];
27530
27470
  if (element.options.constraint === "snap") {
27531
- constraints.push(MovablePoint$1.constraints.snap(element.options.snap));
27471
+ constraints.push(MovablePoint$2.constraints.snap(element.options.snap));
27532
27472
  } else if (element.options.constraint === "x") {
27533
27473
  constraints.push(coord => {
27534
27474
  return [
@@ -27550,7 +27490,7 @@ class Interaction extends React.Component {
27550
27490
  // TODO(eater): foo_[xyz] are hacky non-props to
27551
27491
  // get the component to update when constraints
27552
27492
  // change
27553
- return /*#__PURE__*/React.createElement(MovablePoint$1, {
27493
+ return /*#__PURE__*/React.createElement(MovablePoint$2, {
27554
27494
  key: element.key,
27555
27495
  coord: [
27556
27496
  // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
@@ -27585,7 +27525,7 @@ class Interaction extends React.Component {
27585
27525
  return [coordX, coordY];
27586
27526
  }];
27587
27527
  if (element.options.constraint === "snap") {
27588
- constraints.push(MovablePoint$1.constraints.snap(element.options.snap));
27528
+ constraints.push(MovablePoint$2.constraints.snap(element.options.snap));
27589
27529
  } else if (element.options.constraint === "x") {
27590
27530
  constraints.push(coord => {
27591
27531
  return [
@@ -27626,14 +27566,14 @@ class Interaction extends React.Component {
27626
27566
  foo_x: element.options.constraint,
27627
27567
  foo_y: element.options.constraintFn,
27628
27568
  foo_z: element.options.snap
27629
- }, /*#__PURE__*/React.createElement(MovablePoint$1, {
27569
+ }, /*#__PURE__*/React.createElement(MovablePoint$2, {
27630
27570
  coord: start,
27631
27571
  static: true,
27632
27572
  normalStyle: {
27633
27573
  stroke: "none",
27634
27574
  fill: "none"
27635
27575
  }
27636
- }), /*#__PURE__*/React.createElement(MovablePoint$1, {
27576
+ }), /*#__PURE__*/React.createElement(MovablePoint$2, {
27637
27577
  coord: end,
27638
27578
  static: true,
27639
27579
  normalStyle: {
@@ -28054,33 +27994,6 @@ class Graph extends React.Component {
28054
27994
  allowScratchpad: true,
28055
27995
  setDrawingAreaAvailable: this.props.setDrawingAreaAvailable
28056
27996
  });
28057
-
28058
- // Maybe move this out later
28059
- if (this.props.lockedFigures) {
28060
- for (const lockedFigure of this.props.lockedFigures) {
28061
- switch (lockedFigure.type) {
28062
- case "point":
28063
- graphie.circle(
28064
- // center
28065
- lockedFigure.coord,
28066
- // radius
28067
- 0.2,
28068
- // style
28069
- lockedFigure.style);
28070
- break;
28071
- default:
28072
- /**
28073
- * Devlopment-time future-proofing: This `never` should
28074
- * fail during type-checking if we add a new locked
28075
- * shape type and forget to handle it in any other
28076
- * switch case here.
28077
- */
28078
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
28079
- lockedFigure.type;
28080
- throw new Error(`Unknown locked shape type: ${lockedFigure.type}`);
28081
- }
28082
- }
28083
- }
28084
27997
  this._updateProtractor();
28085
27998
  this._updateRuler();
28086
27999
 
@@ -28206,6 +28119,35 @@ Graph.defaultProps = {
28206
28119
  isMobile: false
28207
28120
  };
28208
28121
 
28122
+ const GraphLockedLayer = props => {
28123
+ const {
28124
+ lockedFigures
28125
+ } = props;
28126
+ return /*#__PURE__*/React.createElement(React.Fragment, null, lockedFigures.map((figure, index) => {
28127
+ switch (figure.type) {
28128
+ case "point":
28129
+ const [x, y] = figure.coord;
28130
+ return /*#__PURE__*/React.createElement(Point$2, {
28131
+ key: `${figure.type}-${index}`,
28132
+ x: x,
28133
+ y: y,
28134
+ svgCircleProps: {
28135
+ style: figure.style
28136
+ }
28137
+ });
28138
+ }
28139
+
28140
+ /**
28141
+ * Devlopment-time future-proofing: This `never` should
28142
+ * fail during type-checking if we add a new locked
28143
+ * shape type and forget to handle it in any other
28144
+ * switch case here.
28145
+ */
28146
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
28147
+ throw new UnreachableCaseError(figure.type);
28148
+ }));
28149
+ };
28150
+
28209
28151
  const MOVE_CONTROL_POINT = "move-control-point";
28210
28152
  function moveControlPoint(objectIndex, pointIndex, destination) {
28211
28153
  return {
@@ -28224,6 +28166,59 @@ function moveSegment(segmentIndex, delta) {
28224
28166
  };
28225
28167
  }
28226
28168
 
28169
+ const MovablePoint$1 = props => {
28170
+ const hitboxRef = useRef(null);
28171
+ const {
28172
+ point,
28173
+ onMove
28174
+ } = props;
28175
+ const {
28176
+ dragging
28177
+ } = useMovable({
28178
+ gestureTarget: hitboxRef,
28179
+ point,
28180
+ onMove,
28181
+ constrain: p => p
28182
+ });
28183
+ const {
28184
+ viewTransform,
28185
+ userTransform
28186
+ } = useTransformContext();
28187
+ const transformToPx = vec.matrixMult(viewTransform, userTransform);
28188
+ const [x, y] = vec.transform(point, transformToPx);
28189
+ return /*#__PURE__*/React.createElement("g", {
28190
+ ref: hitboxRef,
28191
+ className: "movable-point",
28192
+ tabIndex: 0,
28193
+ style: {
28194
+ cursor: dragging ? "grabbing" : "grab",
28195
+ touchAction: "none",
28196
+ outline: "none",
28197
+ "--movable-point-color": Color.blue
28198
+ }
28199
+ }, /*#__PURE__*/React.createElement("circle", {
28200
+ className: "movable-point-hitbox",
28201
+ r: 30,
28202
+ cx: x,
28203
+ cy: y
28204
+ }), /*#__PURE__*/React.createElement("circle", {
28205
+ className: "movable-point-halo",
28206
+ cx: x,
28207
+ cy: y
28208
+ }), /*#__PURE__*/React.createElement("circle", {
28209
+ className: "movable-point-ring",
28210
+ cx: x,
28211
+ cy: y
28212
+ }), /*#__PURE__*/React.createElement("circle", {
28213
+ className: "movable-point-center",
28214
+ cx: x,
28215
+ cy: y,
28216
+ style: {
28217
+ fill: Color.blue
28218
+ }
28219
+ }));
28220
+ };
28221
+
28227
28222
  const SegmentGraph = props => {
28228
28223
  const {
28229
28224
  dispatch
@@ -28266,9 +28261,9 @@ const SegmentView = props => {
28266
28261
  viewTransform,
28267
28262
  userTransform
28268
28263
  } = useTransformContext();
28269
- const transform = vec.matrixMult(viewTransform, userTransform);
28270
- const scaledPoint1 = vec.transform(pt1, transform);
28271
- const scaledPoint2 = vec.transform(pt2, transform);
28264
+ const transformToPx = vec.matrixMult(viewTransform, userTransform);
28265
+ const pt1Px = vec.transform(pt1, transformToPx);
28266
+ const pt2Px = vec.transform(pt2, transformToPx);
28272
28267
  return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("g", {
28273
28268
  ref: segment,
28274
28269
  tabIndex: 0,
@@ -28277,28 +28272,26 @@ const SegmentView = props => {
28277
28272
  cursor: isDraggingSegment ? "grabbing" : "grab"
28278
28273
  }
28279
28274
  }, /*#__PURE__*/React.createElement(SVGLine, {
28280
- start: scaledPoint1,
28281
- end: scaledPoint2,
28275
+ start: pt1Px,
28276
+ end: pt2Px,
28282
28277
  style: {
28283
28278
  stroke: "transparent",
28284
28279
  strokeWidth: 30
28285
28280
  }
28286
28281
  }), /*#__PURE__*/React.createElement(SVGLine, {
28287
- start: scaledPoint1,
28288
- end: scaledPoint2,
28282
+ start: pt1Px,
28283
+ end: pt2Px,
28289
28284
  style: {
28290
28285
  stroke: "var(--mafs-segment-stroke-color)",
28291
28286
  strokeWidth: "var(--mafs-segment-stroke-weight)"
28292
28287
  }
28293
- })), /*#__PURE__*/React.createElement(MovablePoint$5, {
28288
+ })), /*#__PURE__*/React.createElement(MovablePoint$1, {
28294
28289
  point: pt1,
28295
- color: Color.blue,
28296
28290
  onMove: newPoint => {
28297
28291
  props.onMovePoint(0, newPoint);
28298
28292
  }
28299
- }), /*#__PURE__*/React.createElement(MovablePoint$5, {
28293
+ }), /*#__PURE__*/React.createElement(MovablePoint$1, {
28300
28294
  point: pt2,
28301
- color: Color.blue,
28302
28295
  onMove: newPoint => {
28303
28296
  props.onMovePoint(1, newPoint);
28304
28297
  }
@@ -29230,7 +29223,9 @@ const MafsGraph = /*#__PURE__*/React.forwardRef((props, ref) => {
29230
29223
  zoom: false,
29231
29224
  width: width,
29232
29225
  height: height
29233
- }, !legacyGrid && /*#__PURE__*/React.createElement(Grid, props), renderGraph({
29226
+ }, !legacyGrid && /*#__PURE__*/React.createElement(Grid, props), props.lockedFigures && /*#__PURE__*/React.createElement(GraphLockedLayer, {
29227
+ lockedFigures: props.lockedFigures
29228
+ }), renderGraph({
29234
29229
  state,
29235
29230
  dispatch
29236
29231
  }))));
@@ -29369,12 +29364,13 @@ class InteractiveGraph extends React.Component {
29369
29364
  }
29370
29365
  };
29371
29366
  this.resetGraphie = () => {
29367
+ var _this$refs$graph;
29372
29368
  this.shouldResetGraphie = false;
29373
29369
  this.parabola = null;
29374
29370
  this.sinusoid = null;
29375
29371
  // eslint-disable-next-line react/no-string-refs
29376
29372
  // @ts-expect-error - TS2339 - Property 'reset' does not exist on type 'ReactInstance'.
29377
- this.refs.graph.reset();
29373
+ (_this$refs$graph = this.refs.graph) == null || _this$refs$graph.reset();
29378
29374
  };
29379
29375
  this.setupGraphie = () => {
29380
29376
  this.setTrashCanVisibility(0);
@@ -30507,8 +30503,7 @@ class InteractiveGraph extends React.Component {
30507
30503
  onMouseDown: onMouseDown,
30508
30504
  onGraphieUpdated: this.setGraphie,
30509
30505
  setDrawingAreaAvailable: this.props.apiOptions.setDrawingAreaAvailable,
30510
- isMobile: isMobile,
30511
- lockedFigures: this.props.lockedFigures
30506
+ isMobile: isMobile
30512
30507
  }));
30513
30508
  }
30514
30509
  static getQuadraticCoefficients(coords) {
@@ -41239,7 +41234,7 @@ const init = function init(options) {
41239
41234
 
41240
41235
  // This file is processed by a Rollup plugin (replace) to inject the production
41241
41236
  const libName = "@khanacademy/perseus";
41242
- const libVersion = "20.1.0";
41237
+ const libVersion = "20.3.0";
41243
41238
  addLibraryVersionToPerseusDebug(libName, libVersion);
41244
41239
 
41245
41240
  const apiVersion = {