@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/index.css CHANGED
@@ -2363,7 +2363,7 @@ img.zoom-img {
2363
2363
  }
2364
2364
  .framework-perseus .perseus-renderer > .paragraph > ul:not(.perseus-widget-radio),
2365
2365
  .framework-perseus .perseus-renderer > .paragraph > ol {
2366
- margin: -11px 0px 22px 0px;
2366
+ margin: 0px 0px 22px 0px;
2367
2367
  }
2368
2368
  .framework-perseus .paragraph ul:not(.perseus-widget-radio, .indicatorContainer) {
2369
2369
  padding-left: 35px;
@@ -4258,18 +4258,24 @@ html body .mq-editable-field .mq-selection .mq-nthroot-container * {
4258
4258
  --mafs-bg: transparent;
4259
4259
  --mafs-fg: rgb(33, 36, 44); /* WB color.offBlack */
4260
4260
 
4261
- --mafs-line-color: rgb(33, 36, 44, 0.64); /* WB color.offBlack64 */
4262
- --grid-line-subdivision-color: rgba(33, 36, 44, 0.16);
4261
+ --mafs-line-color: rgba(33, 36, 44, 0.16); /* WB color.offBlack16*/
4263
4262
 
4264
4263
  --mafs-blue: #1865f2; /* WB color.blue */
4265
4264
  --mafs-red: #d92916; /* WB color.red */
4266
4265
  --mafs-green: #00a60e; /* WB color.green */
4267
4266
  --mafs-violet: #9059ff; /* WB color.purple */
4268
4267
  --mafs-yellow: #ffb100; /* WB color.gold */
4268
+
4269
+ /* overridden on a per-point basis */
4270
+ --movable-point-color: var(--mafs-blue);
4271
+ --movable-point-center-radius: 6px;
4272
+ --movable-point-ring-radius: calc(2px + var(--movable-point-center-radius));
4273
+ --movable-point-halo-radius: calc(3px + var(--movable-point-ring-radius));
4274
+ --movable-point-hover-expansion: 2px;
4269
4275
  }
4270
4276
 
4271
4277
  .MafsView .movable-segment {
4272
- --mafs-segment-stroke-color: var(--mafs-line-color);
4278
+ --mafs-segment-stroke-color: rgba(33, 36, 44, 0.64); /* offBlack64 */
4273
4279
  --mafs-segment-stroke-weight: 2px;
4274
4280
  }
4275
4281
 
@@ -4282,3 +4288,51 @@ html body .mq-editable-field .mq-selection .mq-nthroot-container * {
4282
4288
  .MafsView .movable-segment:focus {
4283
4289
  outline: none;
4284
4290
  }
4291
+
4292
+ .MafsView .movable-point-hitbox {
4293
+ fill: transparent;
4294
+ }
4295
+
4296
+ .MafsView :is(.movable-point-center, .movable-point-ring, .movable-point-halo) {
4297
+ transition: r 0.15s ease-out;
4298
+ }
4299
+
4300
+ .MafsView .movable-point-center {
4301
+ r: var(--movable-point-center-radius);
4302
+ }
4303
+
4304
+ .MafsView .movable-point-halo {
4305
+ r: var(--movable-point-halo-radius);
4306
+ fill: var(--movable-point-color);
4307
+ opacity: 0.25;
4308
+ filter: drop-shadow(0 5px 5px #0008);
4309
+ }
4310
+
4311
+ .MafsView .movable-point-ring {
4312
+ r: var(--movable-point-ring-radius);
4313
+ fill: #fff;
4314
+ }
4315
+
4316
+ .MafsView .movable-point:hover .movable-point-center {
4317
+ r: calc(
4318
+ var(--movable-point-hover-expansion) +
4319
+ var(--movable-point-center-radius)
4320
+ );
4321
+ }
4322
+
4323
+ .MafsView .movable-point:hover .movable-point-ring {
4324
+ r: calc(
4325
+ var(--movable-point-hover-expansion) + var(--movable-point-ring-radius)
4326
+ );
4327
+ }
4328
+
4329
+ .MafsView .movable-point:hover .movable-point-halo {
4330
+ r: calc(
4331
+ var(--movable-point-hover-expansion) + var(--movable-point-halo-radius)
4332
+ );
4333
+ }
4334
+
4335
+ .MafsView .movable-point:focus-visible .movable-point-halo {
4336
+ outline: 2px solid blue;
4337
+ border-radius: 99px;
4338
+ }
package/dist/index.d.ts CHANGED
@@ -83,7 +83,7 @@ export type { ILogger, LogErrorOptions, ErrorKind } from "./logging/log";
83
83
  export type { ServerItemRenderer as ServerItemRendererComponent } from "./server-item-renderer";
84
84
  export type { Alignment, APIOptions, APIOptionsWithDefaults, ChangeHandler, DeviceType, DomInsertCheckFn, EditorMode, FocusPath, Hint, ImageDict, ImageUploader, JiptLabelStore, JiptRenderer, PerseusDependencies, PerseusScore, Version, VideoData, VideoKind, WidgetDict, WidgetExports, } from "./types";
85
85
  export type { ParsedValue } from "./util";
86
- export type { PerseusAnswerArea, PerseusExpressionWidgetOptions, Range, Size, MathFormat, InputNumberWidget, // TODO(jeremy): remove?
86
+ export type { LockedFigure, LockedFigureType, LockedPoint, PerseusGraphType, PerseusAnswerArea, PerseusExpressionWidgetOptions, Range, Size, MathFormat, InputNumberWidget, // TODO(jeremy): remove?
87
87
  PerseusImageBackground, PerseusInputNumberWidgetOptions, PerseusInteractiveGraphWidgetOptions, PerseusItem, PerseusPlotterWidgetOptions, PerseusPythonProgramWidgetOptions, PerseusRadioWidgetOptions, PerseusExampleWidgetOptions, PerseusExampleGraphieWidgetOptions, PerseusSimpleMarkdownTesterWidgetOptions, PerseusRenderer, PerseusWidget, PerseusWidgetsMap, } from "./perseus-types";
88
88
  export type { Coord } from "./interactive2/types";
89
89
  export type { MarkerType } from "./widgets/label-image/types";
package/dist/index.js CHANGED
@@ -29,6 +29,8 @@ var Button = require('@khanacademy/wonder-blocks-button');
29
29
  var wonderBlocksLayout = require('@khanacademy/wonder-blocks-layout');
30
30
  var Banner = require('@khanacademy/wonder-blocks-banner');
31
31
  var wonderBlocksDropdown = require('@khanacademy/wonder-blocks-dropdown');
32
+ var caretDown = require('@phosphor-icons/core/assets/regular/caret-down.svg');
33
+ var caretUp = require('@phosphor-icons/core/assets/regular/caret-up.svg');
32
34
  var debounce$1 = require('lodash.debounce');
33
35
  var mafs = require('mafs');
34
36
  var wonderBlocksIcon = require('@khanacademy/wonder-blocks-icon');
@@ -79,6 +81,8 @@ var ReactDOM__namespace = /*#__PURE__*/_interopNamespace(ReactDOM);
79
81
  var SimpleMarkdown__default = /*#__PURE__*/_interopDefaultLegacy(SimpleMarkdown);
80
82
  var Button__default = /*#__PURE__*/_interopDefaultLegacy(Button);
81
83
  var Banner__default = /*#__PURE__*/_interopDefaultLegacy(Banner);
84
+ var caretDown__default = /*#__PURE__*/_interopDefaultLegacy(caretDown);
85
+ var caretUp__default = /*#__PURE__*/_interopDefaultLegacy(caretUp);
82
86
  var debounce__default = /*#__PURE__*/_interopDefaultLegacy(debounce$1);
83
87
  var questionIcon__default = /*#__PURE__*/_interopDefaultLegacy(questionIcon);
84
88
  var createReactClass__default = /*#__PURE__*/_interopDefaultLegacy(createReactClass);
@@ -2705,11 +2709,14 @@ function combineScores(scoreA, scoreB) {
2705
2709
  }
2706
2710
  });
2707
2711
  }
2712
+ function isCorrect(score) {
2713
+ return score.type === "points" && score.earned >= score.total;
2714
+ }
2708
2715
  function keScoreFromPerseusScore(score, guess, state) {
2709
2716
  if (score.type === "points") {
2710
2717
  return {
2711
2718
  empty: false,
2712
- correct: score.earned >= score.total,
2719
+ correct: isCorrect(score),
2713
2720
  message: score.message,
2714
2721
  guess: guess,
2715
2722
  state: state
@@ -15001,7 +15008,7 @@ const DEFAULT_STATE$1 = {
15001
15008
  // TODO(benchristel): this is a memory leak. We add items to
15002
15009
  // tooltipResetFunctions, but never remove them.
15003
15010
  const tooltipResetFunctions = [];
15004
- class MovablePoint$4 {
15011
+ class MovablePoint$5 {
15005
15012
  // @ts-expect-error - TS2564: Property 'prevState' has no initializer and is not definitely assigned in the constructor.
15006
15013
 
15007
15014
  _listenerMap = {};
@@ -16129,10 +16136,10 @@ ___default["default"].extend(MovablePolygon.prototype, {
16129
16136
  });
16130
16137
 
16131
16138
  const Interactive2 = {
16132
- MovablePoint: MovablePoint$4,
16139
+ MovablePoint: MovablePoint$5,
16133
16140
  addMovablePoint: function (graphie, options) {
16134
16141
  const movable = new Movable(graphie, {});
16135
- return new MovablePoint$4(graphie, movable, options);
16142
+ return new MovablePoint$5(graphie, movable, options);
16136
16143
  },
16137
16144
  MovableLine: MovableLine$3,
16138
16145
  addMovableLine: function (graphie, options) {
@@ -16174,7 +16181,7 @@ const Interactive2 = {
16174
16181
  }
16175
16182
  };
16176
16183
 
16177
- const MovablePoint$3 = GraphieClasses.createClass({
16184
+ const MovablePoint$4 = GraphieClasses.createClass({
16178
16185
  displayName: "MovablePoint",
16179
16186
  movableProps: ["children"],
16180
16187
  _getProps: function () {
@@ -16232,7 +16239,7 @@ const MovablePoint$3 = GraphieClasses.createClass({
16232
16239
  });
16233
16240
 
16234
16241
  // Include helper methods, such as MovablePoint.constrain.snap()
16235
- ___default["default"].extend(MovablePoint$3, Interactive2.MovablePoint);
16242
+ ___default["default"].extend(MovablePoint$4, Interactive2.MovablePoint);
16236
16243
  const MovableLine$2 = GraphieClasses.createClass({
16237
16244
  displayName: "MovableLine",
16238
16245
  movableProps: ["children"],
@@ -16402,7 +16409,7 @@ var Movables = {
16402
16409
  Label: Label$1,
16403
16410
  Line: Line$2,
16404
16411
  MovableLine: MovableLine$2,
16405
- MovablePoint: MovablePoint$3,
16412
+ MovablePoint: MovablePoint$4,
16406
16413
  Parabola: Parabola,
16407
16414
  Path: Path,
16408
16415
  Plot: Plot$2,
@@ -25007,57 +25014,42 @@ class Explanation extends React__namespace.Component {
25007
25014
  this.props.trackInteraction();
25008
25015
  };
25009
25016
  render() {
25010
- const {
25011
- isArticle,
25012
- isMobile
25013
- } = this.props.apiOptions;
25014
25017
  const promptText = this.state.expanded ? this.props.hidePrompt : this.props.showPrompt;
25015
- let promptContainer;
25016
-
25017
- // TODO(diedra): This isn't a valid href;
25018
- // change this to a button that looks like a link.
25019
- const href = "javascript:void(0)";
25020
- const onClick = this._onClick;
25021
- if (isMobile) {
25022
- promptContainer = /*#__PURE__*/React__namespace.createElement("div", {
25023
- className: aphrodite.css(styles$p.linkContainer)
25024
- }, /*#__PURE__*/React__namespace.createElement("a", {
25025
- className: aphrodite.css(styles$p.mobileExplanationLink),
25026
- href: href,
25027
- onClick: onClick,
25028
- role: "button",
25029
- "aria-expanded": this.state.expanded
25030
- }, promptText), this.state.expanded && /*#__PURE__*/React__namespace.createElement("svg", {
25031
- className: aphrodite.css(styles$p.disclosureArrow)
25032
- }, /*#__PURE__*/React__namespace.createElement("polygon", {
25033
- style: {
25034
- fill: backgroundColor
25035
- },
25036
- points: `0,${arrowHeight} ` + `${arrowWidth},${arrowHeight} ` + `${arrowWidth / 2},0`
25037
- })));
25038
- } else {
25039
- const viewStyling = isArticle ? [styles$p.explanationLink, styles$p.articleLink] : [styles$p.explanationLink, styles$p.exerciseLink];
25040
- promptContainer = /*#__PURE__*/React__namespace.createElement(Clickable__default["default"], {
25041
- onClick: onClick,
25042
- "aria-expanded": this.state.expanded
25043
- }, () => /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
25044
- style: viewStyling
25045
- }, `[${promptText}]`));
25046
- }
25047
- const expandedStyle = isMobile ? styles$p.contentExpandedMobile : styles$p.contentExpanded;
25048
- return /*#__PURE__*/React__namespace.createElement("div", {
25049
- className: aphrodite.css(styles$p.container)
25050
- }, promptContainer, this.state.expanded && /*#__PURE__*/React__namespace.createElement("div", {
25051
- className: aphrodite.css(styles$p.content, isMobile && styles$p.contentMobile, this.state.expanded && expandedStyle)
25052
- // eslint-disable-next-line react/no-string-refs
25053
- ,
25054
- ref: "content"
25018
+ const caretIcon = this.state.expanded ? caretUp__default["default"] : caretDown__default["default"];
25019
+ const allowTransition = window.matchMedia("(prefers-reduced-motion: no-preference)").matches;
25020
+
25021
+ // Special styling is needed to fit the button in a block of text without throwing off the line spacing.
25022
+ // While the button is not normally included in a block of text, it needs to be able to accommodate such a case.
25023
+ const buttonStyleOverrides = {
25024
+ height: "22px",
25025
+ marginLeft: "-2px",
25026
+ padding: "0 2px"
25027
+ };
25028
+ const contentStyling = [styles$p.content, this.state.expanded ? styles$p.contentExpanded : styles$p.contentCollapsed, allowTransition && (this.state.expanded ? styles$p.transitionExpanded : styles$p.transitionCollapsed)];
25029
+ return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.UniqueIDProvider, {
25030
+ mockOnFirstRender: false,
25031
+ scope: "explanation-widget"
25032
+ }, ids => /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(Button__default["default"], {
25033
+ "aria-expanded": this.state.expanded,
25034
+ "aria-controls": ids.get("content"),
25035
+ endIcon: caretIcon,
25036
+ kind: "tertiary",
25037
+ onClick: this._onClick,
25038
+ size: "large",
25039
+ style: buttonStyleOverrides
25040
+ }, promptText), /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
25041
+ id: ids.get("content"),
25042
+ style: contentStyling,
25043
+ "aria-hidden": !this.state.expanded,
25044
+ testId: "content-container"
25045
+ }, /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
25046
+ style: styles$p.contentWrapper
25055
25047
  }, /*#__PURE__*/React__namespace.createElement(Renderer, {
25056
25048
  apiOptions: this.props.apiOptions,
25057
25049
  content: this.props.explanation,
25058
25050
  widgets: this.props.widgets,
25059
25051
  linterContext: this.props.linterContext
25060
- })));
25052
+ })))));
25061
25053
  }
25062
25054
  getUserInput = () => {
25063
25055
  return {};
@@ -25068,94 +25060,44 @@ class Explanation extends React__namespace.Component {
25068
25060
  }
25069
25061
  const leftBorderSpacing = 23;
25070
25062
  const verticalContentPadding = 10;
25071
- const arrowWidth = 30;
25072
25063
  const arrowHeight = 14;
25073
- const backgroundColor = gray95;
25074
25064
  const styles$p = aphrodite.StyleSheet.create({
25075
- container: {
25076
- display: "inline",
25065
+ content: {
25066
+ borderLeft: "0px solid #ccc",
25067
+ display: "inline-grid",
25077
25068
  position: "relative"
25078
25069
  },
25079
- linkContainer: {
25080
- display: "inline-block"
25081
- },
25082
- explanationLink: {
25083
- fontStyle: "italic",
25084
- color: "#007d96"
25085
- },
25086
- articleLink: {
25087
- // Copied from .body-text in articles.less
25088
- fontSize: 20,
25089
- lineHeight: "30px"
25090
- },
25091
- exerciseLink: {
25092
- // Copied from .legacy-typography in util.less
25093
- fontSize: 14,
25094
- lineHeight: "19.6px"
25095
- },
25096
- mobileExplanationLink: {
25097
- color: kaGreen$1,
25098
- borderBottom: `dashed 1px ${kaGreen$1}`,
25099
- textDecoration: "none",
25100
- // TODO(benkomalo): these should be pulled in from common typography
25101
- // shared files so we have a single place where the type hierarchy is
25102
- // defined; one off font sizes for individual components should be
25103
- // avoided.
25104
- [mediaQueries.xl]: {
25105
- fontSize: 22,
25106
- lineHeight: 1.4
25107
- },
25108
- [mediaQueries.lgOrSmaller]: {
25109
- fontSize: 20,
25110
- lineHeight: 1.5
25111
- },
25112
- [mediaQueries.smOrSmaller]: {
25113
- fontSize: 18,
25114
- lineHeight: 1.2
25115
- }
25116
- },
25117
- content: {
25118
- position: "relative",
25119
- transition: "margin-top 0.1s"
25070
+ contentCollapsed: {
25071
+ gridTemplateColumns: "0fr",
25072
+ gridTemplateRows: "0fr",
25073
+ marginBottom: 0,
25074
+ marginTop: 0,
25075
+ minWidth: "0",
25076
+ paddingBottom: 0,
25077
+ visibility: "hidden"
25120
25078
  },
25121
25079
  contentExpanded: {
25122
- borderLeft: "5px solid #ccc",
25080
+ borderLeftWidth: "5px",
25081
+ gridTemplateColumns: "1fr",
25082
+ gridTemplateRows: "1fr",
25123
25083
  marginLeft: -leftBorderSpacing,
25084
+ minWidth: "100%",
25124
25085
  paddingLeft: leftBorderSpacing,
25125
25086
  paddingBottom: verticalContentPadding,
25087
+ visibility: "visible",
25126
25088
  // Note: we still use arrow height as the vertical margin, even on
25127
25089
  // desktop when there is no arrow, but it's good enough.
25128
25090
  marginBottom: arrowHeight,
25129
25091
  marginTop: arrowHeight
25130
25092
  },
25131
- contentExpandedMobile: {
25132
- boxSizing: "content-box",
25133
- paddingTop: 32,
25134
- paddingBottom: 32,
25135
- marginTop: arrowHeight
25093
+ contentWrapper: {
25094
+ overflow: "hidden"
25136
25095
  },
25137
- contentMobile: {
25138
- background: backgroundColor,
25139
- // TODO(benkomalo): this is to "full bleed" the background.
25140
- // The actual content padding differs depending on the host
25141
- // container, so this needs to be fixed eventually.
25142
- marginLeft: negativePhoneMargin,
25143
- marginRight: negativePhoneMargin,
25144
- paddingLeft: phoneMargin,
25145
- paddingRight: phoneMargin
25146
- },
25147
- disclosureArrow: {
25148
- // HACK - positioning at "bottom: 0", doesn't actually position it to
25149
- // the real bottom, because the container is `inline-block`, and it
25150
- // seems to position it to the baseline? We put in a generous
25151
- // fudge factor to position it down to be flush with the content box
25152
- // below it.
25153
- bottom: -(arrowHeight + 5),
25154
- height: arrowHeight,
25155
- left: "50%",
25156
- marginLeft: -(arrowWidth / 2),
25157
- position: "absolute",
25158
- width: arrowWidth
25096
+ transitionCollapsed: {
25097
+ transition: "all 0.25s step-end, grid-template-rows 0.25s, margin-top 0.25s, margin-bottom 0.25s, padding-bottom 0.25s"
25098
+ },
25099
+ transitionExpanded: {
25100
+ transition: "grid-template-rows 0.5s, margin-top 0.5s, margin-bottom 0.5s, padding-bottom 0.5s"
25159
25101
  }
25160
25102
  });
25161
25103
  var Explanation$1 = {
@@ -26482,7 +26424,7 @@ var util = /*#__PURE__*/Object.freeze({
26482
26424
  });
26483
26425
 
26484
26426
  // @ts-expect-error - TS2339 - Property 'MovablePoint' does not exist on type 'typeof Graphie'.
26485
- const MovablePoint$2 = Graphie.MovablePoint;
26427
+ const MovablePoint$3 = Graphie.MovablePoint;
26486
26428
  // @ts-expect-error - TS2339 - Property 'MovableLine' does not exist on type 'typeof Graphie'.
26487
26429
  const MovableLine$1 = Graphie.MovableLine;
26488
26430
  function isFlipped(newCoord, oldCoord, line) {
@@ -26528,7 +26470,7 @@ class FunctionGrapher extends React__namespace.Component {
26528
26470
  })();
26529
26471
  render() {
26530
26472
  const pointForCoord = (coord, i) => {
26531
- return /*#__PURE__*/React__namespace.createElement(MovablePoint$2, {
26473
+ return /*#__PURE__*/React__namespace.createElement(MovablePoint$3, {
26532
26474
  key: i,
26533
26475
  coord: coord,
26534
26476
  static: this.props.static,
@@ -26665,7 +26607,7 @@ class FunctionGrapher extends React__namespace.Component {
26665
26607
  }],
26666
26608
  normalStyle: dashed,
26667
26609
  highlightStyle: dashed
26668
- }, ___default["default"].map(asymptote, (coord, i) => /*#__PURE__*/React__namespace.createElement(MovablePoint$2, {
26610
+ }, ___default["default"].map(asymptote, (coord, i) => /*#__PURE__*/React__namespace.createElement(MovablePoint$3, {
26669
26611
  key: `asymptoteCoord-${i}`,
26670
26612
  coord: coord,
26671
26613
  static: true,
@@ -27428,7 +27370,7 @@ const Label = Graphie.Label;
27428
27370
  // @ts-expect-error - TS2339 - Property 'Line' does not exist on type 'typeof Graphie'.
27429
27371
  const Line$1 = Graphie.Line;
27430
27372
  // @ts-expect-error - TS2339 - Property 'MovablePoint' does not exist on type 'typeof Graphie'.
27431
- const MovablePoint$1 = Graphie.MovablePoint;
27373
+ const MovablePoint$2 = Graphie.MovablePoint;
27432
27374
  // @ts-expect-error - TS2339 - Property 'MovableLine' does not exist on type 'typeof Graphie'.
27433
27375
  const MovableLine = Graphie.MovableLine;
27434
27376
  // @ts-expect-error - TS2339 - Property 'Plot' does not exist on type 'typeof Graphie'.
@@ -27682,7 +27624,7 @@ class Interaction extends React__namespace.Component {
27682
27624
  return [coordX, coordY];
27683
27625
  }];
27684
27626
  if (element.options.constraint === "snap") {
27685
- constraints.push(MovablePoint$1.constraints.snap(element.options.snap));
27627
+ constraints.push(MovablePoint$2.constraints.snap(element.options.snap));
27686
27628
  } else if (element.options.constraint === "x") {
27687
27629
  constraints.push(coord => {
27688
27630
  return [
@@ -27704,7 +27646,7 @@ class Interaction extends React__namespace.Component {
27704
27646
  // TODO(eater): foo_[xyz] are hacky non-props to
27705
27647
  // get the component to update when constraints
27706
27648
  // change
27707
- return /*#__PURE__*/React__namespace.createElement(MovablePoint$1, {
27649
+ return /*#__PURE__*/React__namespace.createElement(MovablePoint$2, {
27708
27650
  key: element.key,
27709
27651
  coord: [
27710
27652
  // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
@@ -27739,7 +27681,7 @@ class Interaction extends React__namespace.Component {
27739
27681
  return [coordX, coordY];
27740
27682
  }];
27741
27683
  if (element.options.constraint === "snap") {
27742
- constraints.push(MovablePoint$1.constraints.snap(element.options.snap));
27684
+ constraints.push(MovablePoint$2.constraints.snap(element.options.snap));
27743
27685
  } else if (element.options.constraint === "x") {
27744
27686
  constraints.push(coord => {
27745
27687
  return [
@@ -27780,14 +27722,14 @@ class Interaction extends React__namespace.Component {
27780
27722
  foo_x: element.options.constraint,
27781
27723
  foo_y: element.options.constraintFn,
27782
27724
  foo_z: element.options.snap
27783
- }, /*#__PURE__*/React__namespace.createElement(MovablePoint$1, {
27725
+ }, /*#__PURE__*/React__namespace.createElement(MovablePoint$2, {
27784
27726
  coord: start,
27785
27727
  static: true,
27786
27728
  normalStyle: {
27787
27729
  stroke: "none",
27788
27730
  fill: "none"
27789
27731
  }
27790
- }), /*#__PURE__*/React__namespace.createElement(MovablePoint$1, {
27732
+ }), /*#__PURE__*/React__namespace.createElement(MovablePoint$2, {
27791
27733
  coord: end,
27792
27734
  static: true,
27793
27735
  normalStyle: {
@@ -28238,33 +28180,6 @@ class Graph extends React__namespace.Component {
28238
28180
  allowScratchpad: true,
28239
28181
  setDrawingAreaAvailable: this.props.setDrawingAreaAvailable
28240
28182
  });
28241
-
28242
- // Maybe move this out later
28243
- if (this.props.lockedFigures) {
28244
- for (const lockedFigure of this.props.lockedFigures) {
28245
- switch (lockedFigure.type) {
28246
- case "point":
28247
- graphie.circle(
28248
- // center
28249
- lockedFigure.coord,
28250
- // radius
28251
- 0.2,
28252
- // style
28253
- lockedFigure.style);
28254
- break;
28255
- default:
28256
- /**
28257
- * Devlopment-time future-proofing: This `never` should
28258
- * fail during type-checking if we add a new locked
28259
- * shape type and forget to handle it in any other
28260
- * switch case here.
28261
- */
28262
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
28263
- lockedFigure.type;
28264
- throw new Error(`Unknown locked shape type: ${lockedFigure.type}`);
28265
- }
28266
- }
28267
- }
28268
28183
  this._updateProtractor();
28269
28184
  this._updateRuler();
28270
28185
 
@@ -28357,6 +28272,35 @@ class Graph extends React__namespace.Component {
28357
28272
  }
28358
28273
  }
28359
28274
 
28275
+ const GraphLockedLayer = props => {
28276
+ const {
28277
+ lockedFigures
28278
+ } = props;
28279
+ return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, lockedFigures.map((figure, index) => {
28280
+ switch (figure.type) {
28281
+ case "point":
28282
+ const [x, y] = figure.coord;
28283
+ return /*#__PURE__*/React__namespace.createElement(mafs.Point, {
28284
+ key: `${figure.type}-${index}`,
28285
+ x: x,
28286
+ y: y,
28287
+ svgCircleProps: {
28288
+ style: figure.style
28289
+ }
28290
+ });
28291
+ }
28292
+
28293
+ /**
28294
+ * Devlopment-time future-proofing: This `never` should
28295
+ * fail during type-checking if we add a new locked
28296
+ * shape type and forget to handle it in any other
28297
+ * switch case here.
28298
+ */
28299
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
28300
+ throw new wonderStuffCore.UnreachableCaseError(figure.type);
28301
+ }));
28302
+ };
28303
+
28360
28304
  const MOVE_CONTROL_POINT = "move-control-point";
28361
28305
  function moveControlPoint(objectIndex, pointIndex, destination) {
28362
28306
  return {
@@ -28375,6 +28319,59 @@ function moveSegment(segmentIndex, delta) {
28375
28319
  };
28376
28320
  }
28377
28321
 
28322
+ const MovablePoint$1 = props => {
28323
+ const hitboxRef = React.useRef(null);
28324
+ const {
28325
+ point,
28326
+ onMove
28327
+ } = props;
28328
+ const {
28329
+ dragging
28330
+ } = mafs.useMovable({
28331
+ gestureTarget: hitboxRef,
28332
+ point,
28333
+ onMove,
28334
+ constrain: p => p
28335
+ });
28336
+ const {
28337
+ viewTransform,
28338
+ userTransform
28339
+ } = mafs.useTransformContext();
28340
+ const transformToPx = mafs.vec.matrixMult(viewTransform, userTransform);
28341
+ const [x, y] = mafs.vec.transform(point, transformToPx);
28342
+ return /*#__PURE__*/React__namespace.createElement("g", {
28343
+ ref: hitboxRef,
28344
+ className: "movable-point",
28345
+ tabIndex: 0,
28346
+ style: {
28347
+ cursor: dragging ? "grabbing" : "grab",
28348
+ touchAction: "none",
28349
+ outline: "none",
28350
+ "--movable-point-color": Color__default["default"].blue
28351
+ }
28352
+ }, /*#__PURE__*/React__namespace.createElement("circle", {
28353
+ className: "movable-point-hitbox",
28354
+ r: 30,
28355
+ cx: x,
28356
+ cy: y
28357
+ }), /*#__PURE__*/React__namespace.createElement("circle", {
28358
+ className: "movable-point-halo",
28359
+ cx: x,
28360
+ cy: y
28361
+ }), /*#__PURE__*/React__namespace.createElement("circle", {
28362
+ className: "movable-point-ring",
28363
+ cx: x,
28364
+ cy: y
28365
+ }), /*#__PURE__*/React__namespace.createElement("circle", {
28366
+ className: "movable-point-center",
28367
+ cx: x,
28368
+ cy: y,
28369
+ style: {
28370
+ fill: Color__default["default"].blue
28371
+ }
28372
+ }));
28373
+ };
28374
+
28378
28375
  const SegmentGraph = props => {
28379
28376
  const {
28380
28377
  dispatch
@@ -28417,9 +28414,9 @@ const SegmentView = props => {
28417
28414
  viewTransform,
28418
28415
  userTransform
28419
28416
  } = mafs.useTransformContext();
28420
- const transform = mafs.vec.matrixMult(viewTransform, userTransform);
28421
- const scaledPoint1 = mafs.vec.transform(pt1, transform);
28422
- const scaledPoint2 = mafs.vec.transform(pt2, transform);
28417
+ const transformToPx = mafs.vec.matrixMult(viewTransform, userTransform);
28418
+ const pt1Px = mafs.vec.transform(pt1, transformToPx);
28419
+ const pt2Px = mafs.vec.transform(pt2, transformToPx);
28423
28420
  return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement("g", {
28424
28421
  ref: segment,
28425
28422
  tabIndex: 0,
@@ -28428,28 +28425,26 @@ const SegmentView = props => {
28428
28425
  cursor: isDraggingSegment ? "grabbing" : "grab"
28429
28426
  }
28430
28427
  }, /*#__PURE__*/React__namespace.createElement(SVGLine, {
28431
- start: scaledPoint1,
28432
- end: scaledPoint2,
28428
+ start: pt1Px,
28429
+ end: pt2Px,
28433
28430
  style: {
28434
28431
  stroke: "transparent",
28435
28432
  strokeWidth: 30
28436
28433
  }
28437
28434
  }), /*#__PURE__*/React__namespace.createElement(SVGLine, {
28438
- start: scaledPoint1,
28439
- end: scaledPoint2,
28435
+ start: pt1Px,
28436
+ end: pt2Px,
28440
28437
  style: {
28441
28438
  stroke: "var(--mafs-segment-stroke-color)",
28442
28439
  strokeWidth: "var(--mafs-segment-stroke-weight)"
28443
28440
  }
28444
- })), /*#__PURE__*/React__namespace.createElement(mafs.MovablePoint, {
28441
+ })), /*#__PURE__*/React__namespace.createElement(MovablePoint$1, {
28445
28442
  point: pt1,
28446
- color: Color__default["default"].blue,
28447
28443
  onMove: newPoint => {
28448
28444
  props.onMovePoint(0, newPoint);
28449
28445
  }
28450
- }), /*#__PURE__*/React__namespace.createElement(mafs.MovablePoint, {
28446
+ }), /*#__PURE__*/React__namespace.createElement(MovablePoint$1, {
28451
28447
  point: pt2,
28452
- color: Color__default["default"].blue,
28453
28448
  onMove: newPoint => {
28454
28449
  props.onMovePoint(1, newPoint);
28455
28450
  }
@@ -29389,7 +29384,9 @@ const MafsGraph = /*#__PURE__*/React__namespace.forwardRef((props, ref) => {
29389
29384
  zoom: false,
29390
29385
  width: width,
29391
29386
  height: height
29392
- }, !legacyGrid && /*#__PURE__*/React__namespace.createElement(Grid, props), renderGraph({
29387
+ }, !legacyGrid && /*#__PURE__*/React__namespace.createElement(Grid, props), props.lockedFigures && /*#__PURE__*/React__namespace.createElement(GraphLockedLayer, {
29388
+ lockedFigures: props.lockedFigures
29389
+ }), renderGraph({
29393
29390
  state,
29394
29391
  dispatch
29395
29392
  }))));
@@ -29568,7 +29565,7 @@ class InteractiveGraph extends React__namespace.Component {
29568
29565
  this.sinusoid = null;
29569
29566
  // eslint-disable-next-line react/no-string-refs
29570
29567
  // @ts-expect-error - TS2339 - Property 'reset' does not exist on type 'ReactInstance'.
29571
- this.refs.graph.reset();
29568
+ this.refs.graph?.reset();
29572
29569
  };
29573
29570
  setupGraphie = () => {
29574
29571
  this.setTrashCanVisibility(0);
@@ -30644,8 +30641,7 @@ class InteractiveGraph extends React__namespace.Component {
30644
30641
  onMouseDown: onMouseDown,
30645
30642
  onGraphieUpdated: this.setGraphie,
30646
30643
  setDrawingAreaAvailable: this.props.apiOptions.setDrawingAreaAvailable,
30647
- isMobile: isMobile,
30648
- lockedFigures: this.props.lockedFigures
30644
+ isMobile: isMobile
30649
30645
  }));
30650
30646
  }
30651
30647
  static getQuadraticCoefficients(coords) {
@@ -41367,7 +41363,7 @@ const init = function (options) {
41367
41363
 
41368
41364
  // This file is processed by a Rollup plugin (replace) to inject the production
41369
41365
  const libName = "@khanacademy/perseus";
41370
- const libVersion = "20.1.0";
41366
+ const libVersion = "20.3.0";
41371
41367
  perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
41372
41368
 
41373
41369
  const apiVersion = {