@khanacademy/perseus 27.2.0 → 28.0.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.
Files changed (38) hide show
  1. package/dist/components/image-loader.d.ts +2 -2
  2. package/dist/components/{info-tip.d.ts → info-tip/index.d.ts} +2 -2
  3. package/dist/components/info-tip/info-tip-base.d.ts +6 -0
  4. package/dist/components.d.ts +0 -1
  5. package/dist/es/index.js +432 -340
  6. package/dist/es/index.js.map +1 -1
  7. package/dist/es/strings.js +6 -2
  8. package/dist/es/strings.js.map +1 -1
  9. package/dist/hints-renderer.d.ts +2 -1
  10. package/dist/index.d.ts +4 -4
  11. package/dist/index.js +418 -326
  12. package/dist/index.js.map +1 -1
  13. package/dist/interactive2/movable-point.d.ts +3 -3
  14. package/dist/perseus-api.d.ts +3 -0
  15. package/dist/perseus-types.d.ts +10 -6
  16. package/dist/renderer.d.ts +2 -1
  17. package/dist/server-item-renderer.d.ts +3 -0
  18. package/dist/strings.d.ts +2 -0
  19. package/dist/strings.js +6 -2
  20. package/dist/strings.js.map +1 -1
  21. package/dist/types.d.ts +7 -7
  22. package/dist/util/{katex-preprocess.d.ts → tex-preprocess.d.ts} +1 -1
  23. package/dist/widgets/expression.d.ts +20 -2
  24. package/dist/widgets/interactive-graphs/graphs/angle.d.ts +7 -0
  25. package/dist/widgets/interactive-graphs/graphs/components/angle-indicators.d.ts +0 -2
  26. package/dist/widgets/interactive-graphs/graphs/components/movable-point.d.ts +3 -2
  27. package/dist/widgets/interactive-graphs/graphs/polygon.d.ts +1 -0
  28. package/dist/widgets/interactive-graphs/graphs/use-draggable.d.ts +7 -1
  29. package/dist/widgets/interactive-graphs/locked-figures/locked-function.d.ts +1 -1
  30. package/dist/widgets/interactive-graphs/mafs-graph.d.ts +1 -0
  31. package/dist/widgets/interactive-graphs/math/geometry.d.ts +2 -1
  32. package/dist/widgets/interactive-graphs/reducer/initialize-graph-state.d.ts +8 -1
  33. package/dist/widgets/interactive-graphs/reducer/use-graph-config.d.ts +1 -1
  34. package/dist/widgets/interactive-graphs/stateful-mafs-graph.d.ts +1 -0
  35. package/dist/widgets/label-image.d.ts +6 -0
  36. package/package.json +6 -6
  37. package/dist/components/info-tip/info-tip.d.ts +0 -6
  38. package/dist/components/prop-check-box.d.ts +0 -4
package/dist/es/index.js CHANGED
@@ -5,7 +5,7 @@ import * as PerseusLinter from '@khanacademy/perseus-linter';
5
5
  import { linterContextDefault, Rule, linterContextProps } from '@khanacademy/perseus-linter';
6
6
  import { View, RenderStateRoot, UniqueIDProvider, useLatestRef, useUniqueIdWithMock, Text as Text$2 } from '@khanacademy/wonder-blocks-core';
7
7
  import Tooltip$1 from '@khanacademy/wonder-blocks-tooltip';
8
- import { LabelSmall, Body, LabelMedium } from '@khanacademy/wonder-blocks-typography';
8
+ import { HeadingMedium, LabelSmall, Body, LabelMedium } from '@khanacademy/wonder-blocks-typography';
9
9
  import { StyleSheet, css } from 'aphrodite';
10
10
  import classNames from 'classnames';
11
11
  import * as React from 'react';
@@ -17,13 +17,14 @@ import Clickable from '@khanacademy/wonder-blocks-clickable';
17
17
  import { Popover, PopoverContentCore, PopoverContent } from '@khanacademy/wonder-blocks-popover';
18
18
  import { color, spacing } from '@khanacademy/wonder-blocks-tokens';
19
19
  import $ from 'jquery';
20
+ import { v4 } from 'uuid';
20
21
  import PropTypes from 'prop-types';
21
22
  import { number, point, vector as vector$1, line } from '@khanacademy/kmath';
22
23
  import { entries, UnreachableCaseError, keys } from '@khanacademy/wonder-stuff-core';
23
24
  import { CircularSpinner } from '@khanacademy/wonder-blocks-progress-spinner';
24
25
  import SimpleMarkdown from '@khanacademy/simple-markdown';
25
26
  import { pureMarkdownRules } from '@khanacademy/pure-markdown';
26
- import { TextField, Checkbox } from '@khanacademy/wonder-blocks-form';
27
+ import { TextField } from '@khanacademy/wonder-blocks-form';
27
28
  import Button from '@khanacademy/wonder-blocks-button';
28
29
  import { Strut } from '@khanacademy/wonder-blocks-layout';
29
30
  import Banner from '@khanacademy/wonder-blocks-banner';
@@ -34,11 +35,11 @@ import debounce$2 from 'lodash.debounce';
34
35
  import { vec, Coordinates, Ellipse, Plot as Plot$3, Line as Line$4, Point as Point$2, Polygon, useTransformContext, Text as Text$1, Mafs } from 'mafs';
35
36
  import { PhosphorIcon } from '@khanacademy/wonder-blocks-icon';
36
37
  import questionIcon from '@phosphor-icons/core/regular/question.svg';
37
- import createReactClass from 'create-react-class';
38
38
  import { useDrag } from '@use-gesture/react';
39
39
  import Switch from '@khanacademy/wonder-blocks-switch';
40
40
  import Pill from '@khanacademy/wonder-blocks-pill';
41
41
  import { Popper } from 'react-popper';
42
+ import createReactClass from 'create-react-class';
42
43
  import Link from '@khanacademy/wonder-blocks-link';
43
44
 
44
45
  function _extends() {
@@ -244,7 +245,9 @@ const mockStrings = {
244
245
  loading: "Loading...",
245
246
  videoTranscript: "See video transcript",
246
247
  somethingWrong: "Something went wrong.",
247
- videoWrapper: "Khan Academy video wrapper"
248
+ videoWrapper: "Khan Academy video wrapper",
249
+ mathInputTitle: "mathematics keyboard",
250
+ mathInputDescription: "Use keyboard/mouse to interact with math-based input fields"
248
251
  };
249
252
 
250
253
  /**
@@ -275,6 +278,26 @@ function PerseusI18nContextProvider({
275
278
  }
276
279
  const usePerseusI18n = () => useContext(PerseusI18nContext);
277
280
 
281
+ /*
282
+ * A11y (Accessibility) style rules
283
+ * These are used to mark up areas on the site that should only be readable
284
+ * when using a screen reader.
285
+ */
286
+ var a11y = StyleSheet.create({
287
+ // Make content only visible to screen readers.
288
+ // Both collegeboard.org and Bootstrap 3 use this exact implementation.
289
+ srOnly: {
290
+ border: 0,
291
+ clip: "rect(0,0,0,0)",
292
+ height: 1,
293
+ margin: -1,
294
+ overflow: "hidden",
295
+ padding: 0,
296
+ position: "absolute",
297
+ width: 1
298
+ }
299
+ });
300
+
278
301
  const debounce = (func, delay) => {
279
302
  let timer = null;
280
303
  return (...args) => {
@@ -472,11 +495,12 @@ class InnerMathInput extends React.Component {
472
495
  "mq-editable-field": true,
473
496
  "mq-math-mode": true
474
497
  });
498
+ const popoverContentUniqueId = v4().slice(0, 8);
475
499
  if (this.props.className) {
476
500
  className = className + " " + this.props.className;
477
501
  }
478
502
  return /*#__PURE__*/React.createElement(View, {
479
- style: [styles$E.outerWrapper, this.state.focused && styles$E.wrapperFocused, this.props.hasError && styles$E.wrapperError]
503
+ style: [styles$D.outerWrapper, this.state.focused && styles$D.wrapperFocused, this.props.hasError && styles$D.wrapperError]
480
504
  }, /*#__PURE__*/React.createElement("div", {
481
505
  style: {
482
506
  display: "flex",
@@ -504,18 +528,23 @@ class InnerMathInput extends React.Component {
504
528
  opened: this.state.keypadOpen,
505
529
  onClose: () => this.closeKeypad(),
506
530
  dismissEnabled: true,
531
+ "aria-label": this.context.strings.mathInputTitle,
532
+ "aria-describedby": `popover-content-${popoverContentUniqueId}`,
507
533
  content: () => {
508
534
  var _this$props$keypadBut, _this$props;
509
- return /*#__PURE__*/React.createElement(PopoverContentCore, {
535
+ return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(HeadingMedium, {
536
+ id: `popover-content-${popoverContentUniqueId}`,
537
+ style: a11y.srOnly
538
+ }, this.context.strings.mathInputDescription), /*#__PURE__*/React.createElement(PopoverContentCore, {
510
539
  closeButtonVisible: true,
511
- style: styles$E.popoverContent
540
+ style: styles$D.popoverContent
512
541
  }, /*#__PURE__*/React.createElement(DesktopKeypad, _extends({
513
542
  onAnalyticsEvent: this.props.analytics.onAnalyticsEvent,
514
543
  extraKeys: this.props.extraKeys,
515
544
  onClickKey: this.handleKeypadPress,
516
545
  cursorContext: this.state.cursorContext,
517
546
  convertDotToTimes: this.props.convertDotToTimes
518
- }, (_this$props$keypadBut = this.props.keypadButtonSets) != null ? _this$props$keypadBut : mapButtonSets((_this$props = this.props) == null ? void 0 : _this$props.buttonSets))));
547
+ }, (_this$props$keypadBut = this.props.keypadButtonSets) != null ? _this$props$keypadBut : mapButtonSets((_this$props = this.props) == null ? void 0 : _this$props.buttonSets)))));
519
548
  }
520
549
  }, this.props.buttonsVisible === "never" ? /*#__PURE__*/React.createElement(MathInputIcon, {
521
550
  hovered: false,
@@ -584,9 +613,9 @@ const MathInputIcon = ({
584
613
  fillColor = color.offBlack;
585
614
  break;
586
615
  }
587
- const dynamicClass = active || focused ? styles$E.iconActive : styles$E.iconInactive;
616
+ const dynamicClass = active || focused ? styles$D.iconActive : styles$D.iconInactive;
588
617
  return /*#__PURE__*/React.createElement(View, {
589
- style: [styles$E.iconContainer, dynamicClass]
618
+ style: [styles$D.iconContainer, dynamicClass]
590
619
  }, /*#__PURE__*/React.createElement("svg", {
591
620
  xmlns: "http://www.w3.org/2000/svg",
592
621
  width: "16",
@@ -631,7 +660,7 @@ const inputFocused = {
631
660
  borderColor: color.blue,
632
661
  margin: -1
633
662
  };
634
- const styles$E = StyleSheet.create({
663
+ const styles$D = StyleSheet.create({
635
664
  iconContainer: {
636
665
  display: "flex",
637
666
  justifyContent: "center",
@@ -1161,26 +1190,6 @@ const ClassNames = {
1161
1190
  MOBILE: "perseus-mobile"
1162
1191
  };
1163
1192
 
1164
- /*
1165
- * A11y (Accessibility) style rules
1166
- * These are used to mark up areas on the site that should only be readable
1167
- * when using a screen reader.
1168
- */
1169
- var a11y = StyleSheet.create({
1170
- // Make content only visible to screen readers.
1171
- // Both collegeboard.org and Bootstrap 3 use this exact implementation.
1172
- srOnly: {
1173
- border: 0,
1174
- clip: "rect(0,0,0,0)",
1175
- height: 1,
1176
- margin: -1,
1177
- overflow: "hidden",
1178
- padding: 0,
1179
- position: "absolute",
1180
- width: 1
1181
- }
1182
- });
1183
-
1184
1193
  const KhanMath = {
1185
1194
  // Simplify formulas before display
1186
1195
  cleanMath: function (expr) {
@@ -2475,7 +2484,7 @@ class Expression extends React.Component {
2475
2484
  var _this$props$keypadCon, _this$props$analytics3;
2476
2485
  if (this.props.apiOptions.customKeypad) {
2477
2486
  return /*#__PURE__*/React.createElement(View, {
2478
- className: css(styles$D.mobileLabelInputWrapper)
2487
+ className: css(styles$C.mobileLabelInputWrapper)
2479
2488
  }, !!this.props.visibleLabel && /*#__PURE__*/React.createElement(LabelSmall, {
2480
2489
  htmlFor: this._textareaId,
2481
2490
  tag: "label"
@@ -2510,7 +2519,7 @@ class Expression extends React.Component {
2510
2519
  ERROR_TITLE
2511
2520
  } = this.context.strings;
2512
2521
  return /*#__PURE__*/React.createElement(View, {
2513
- className: css(styles$D.desktopLabelInputWrapper)
2522
+ className: css(styles$C.desktopLabelInputWrapper)
2514
2523
  }, !!this.props.visibleLabel && /*#__PURE__*/React.createElement(LabelSmall, {
2515
2524
  htmlFor: this._textareaId,
2516
2525
  tag: "label"
@@ -2562,7 +2571,7 @@ Expression.defaultProps = {
2562
2571
  apiOptions: ApiOptions.defaults,
2563
2572
  linterContext: linterContextDefault
2564
2573
  };
2565
- const styles$D = StyleSheet.create({
2574
+ const styles$C = StyleSheet.create({
2566
2575
  mobileLabelInputWrapper: {
2567
2576
  padding: "15px 4px 0"
2568
2577
  },
@@ -8942,8 +8951,8 @@ function findChildOrAdd(elem, className) {
8942
8951
  return $child;
8943
8952
  }
8944
8953
  var Tex$1 = {
8945
- // Process a node and add math inside of it. This attempts to use KaTeX to
8946
- // format the math, and if that fails it falls back to MathJax.
8954
+ // Process a node and add math inside of it. This uses MathJax to
8955
+ // format the math.
8947
8956
  //
8948
8957
  // elem: The element which the math should be added to.
8949
8958
  //
@@ -15305,7 +15314,7 @@ const DEFAULT_STATE$1 = {
15305
15314
  // TODO(benchristel): this is a memory leak. We add items to
15306
15315
  // tooltipResetFunctions, but never remove them.
15307
15316
  const tooltipResetFunctions = [];
15308
- class MovablePoint$4 {
15317
+ class MovablePoint$5 {
15309
15318
  constructor(graphie, movable, options) {
15310
15319
  this.graphie = void 0;
15311
15320
  this.movable = void 0;
@@ -15600,9 +15609,9 @@ class MovablePoint$4 {
15600
15609
  * Displays a tooltip above the point, replacing any previous contents. If
15601
15610
  * there is no tooltip initialized, adds the tooltip.
15602
15611
  *
15603
- * If the type of contents is string, the contents will be rendered with
15604
- * KaTeX. Otherwise, the content will be assumed to be a DOM node and will
15605
- * be appended inside the tooltip.
15612
+ * If the type of contents is string, the contents will be rendered as TeX
15613
+ * Otherwise, the content will be assumed to be a DOM node and will be
15614
+ * appended inside the tooltip.
15606
15615
  */
15607
15616
  _showTooltip(contents) {
15608
15617
  if (!this._tooltip) {
@@ -15823,15 +15832,15 @@ class MovablePoint$4 {
15823
15832
  this.movable.toFront();
15824
15833
  }
15825
15834
  }
15826
- MovablePoint$4.add = MovablePointOptions.add;
15827
- MovablePoint$4.modify = MovablePointOptions.modify;
15828
- MovablePoint$4.draw = MovablePointOptions.draw;
15829
- MovablePoint$4.remove = MovablePointOptions.remove;
15830
- MovablePoint$4.onMoveStart = MovablePointOptions.onMoveStart;
15831
- MovablePoint$4.constraints = MovablePointOptions.constraints;
15832
- MovablePoint$4.onMove = MovablePointOptions.onMove;
15833
- MovablePoint$4.onMoveEnd = MovablePointOptions.onMoveEnd;
15834
- MovablePoint$4.onClick = MovablePointOptions.onClick;
15835
+ MovablePoint$5.add = MovablePointOptions.add;
15836
+ MovablePoint$5.modify = MovablePointOptions.modify;
15837
+ MovablePoint$5.draw = MovablePointOptions.draw;
15838
+ MovablePoint$5.remove = MovablePointOptions.remove;
15839
+ MovablePoint$5.onMoveStart = MovablePointOptions.onMoveStart;
15840
+ MovablePoint$5.constraints = MovablePointOptions.constraints;
15841
+ MovablePoint$5.onMove = MovablePointOptions.onMove;
15842
+ MovablePoint$5.onMoveEnd = MovablePointOptions.onMoveEnd;
15843
+ MovablePoint$5.onClick = MovablePointOptions.onClick;
15835
15844
  function getKey(eventName, id) {
15836
15845
  return eventName + ":" + id;
15837
15846
  }
@@ -16425,10 +16434,10 @@ _.extend(MovablePolygon.prototype, {
16425
16434
  });
16426
16435
 
16427
16436
  const Interactive2 = {
16428
- MovablePoint: MovablePoint$4,
16437
+ MovablePoint: MovablePoint$5,
16429
16438
  addMovablePoint: function (graphie, options) {
16430
16439
  const movable = new Movable(graphie, {});
16431
- return new MovablePoint$4(graphie, movable, options);
16440
+ return new MovablePoint$5(graphie, movable, options);
16432
16441
  },
16433
16442
  MovableLine: MovableLine$4,
16434
16443
  addMovableLine: function (graphie, options) {
@@ -16469,7 +16478,7 @@ const Interactive2 = {
16469
16478
  }
16470
16479
  };
16471
16480
 
16472
- const MovablePoint$3 = GraphieClasses.createClass({
16481
+ const MovablePoint$4 = GraphieClasses.createClass({
16473
16482
  displayName: "MovablePoint",
16474
16483
  movableProps: ["children"],
16475
16484
  _getProps: function () {
@@ -16526,7 +16535,7 @@ const MovablePoint$3 = GraphieClasses.createClass({
16526
16535
  });
16527
16536
 
16528
16537
  // Include helper methods, such as MovablePoint.constrain.snap()
16529
- _.extend(MovablePoint$3, Interactive2.MovablePoint);
16538
+ _.extend(MovablePoint$4, Interactive2.MovablePoint);
16530
16539
  const MovableLine$3 = GraphieClasses.createClass({
16531
16540
  displayName: "MovableLine",
16532
16541
  movableProps: ["children"],
@@ -16696,7 +16705,7 @@ var Movables = {
16696
16705
  Label: Label$1,
16697
16706
  Line: Line$3,
16698
16707
  MovableLine: MovableLine$3,
16699
- MovablePoint: MovablePoint$3,
16708
+ MovablePoint: MovablePoint$4,
16700
16709
  Parabola: Parabola,
16701
16710
  Path: Path,
16702
16711
  Plot: Plot$2,
@@ -17025,17 +17034,15 @@ class ImageLoader extends React.Component {
17025
17034
  src,
17026
17035
  imgProps
17027
17036
  } = this.props;
17028
- let onKeyUp = null;
17029
- let onKeyDown = null;
17037
+ let onKeyUp;
17038
+ let onKeyDown;
17030
17039
  if (imgProps.onClick != null) {
17031
- // @ts-expect-error - TS2322 - Type '(e: React.KeyboardEvent) => void' is not assignable to type 'null'.
17032
17040
  onKeyUp = e => {
17033
17041
  // 13 is enter key, 32 is space key
17034
17042
  if (e.keyCode === 13 || e.keyCode === 32) {
17035
17043
  imgProps.onClick && imgProps.onClick(e);
17036
17044
  }
17037
17045
  };
17038
- // @ts-expect-error - TS2322 - Type '(e: React.KeyboardEvent) => void' is not assignable to type 'null'.
17039
17046
  onKeyDown = e => {
17040
17047
  // 32 is space key
17041
17048
  if (e.keyCode === 32) {
@@ -17046,12 +17053,8 @@ class ImageLoader extends React.Component {
17046
17053
  }
17047
17054
  const staticUrl = getDependencies().staticUrl;
17048
17055
  return /*#__PURE__*/React.createElement("img", _extends({
17049
- src: staticUrl(src)
17050
- // @ts-expect-error - TS2322 - Type 'null' is not assignable to type 'KeyboardEventHandler<HTMLImageElement> | undefined'.
17051
- ,
17052
- onKeyUp: onKeyUp
17053
- // @ts-expect-error - TS2322 - Type 'null' is not assignable to type 'KeyboardEventHandler<HTMLImageElement> | undefined'.
17054
- ,
17056
+ src: staticUrl(src),
17057
+ onKeyUp: onKeyUp,
17055
17058
  onKeyDown: onKeyDown
17056
17059
  }, imgProps));
17057
17060
  };
@@ -17549,18 +17552,14 @@ class SvgImage extends React.Component {
17549
17552
  allowFullBleed: this.props.allowFullBleed && isImageProbablyPhotograph(imageSrc)
17550
17553
  }, /*#__PURE__*/React.createElement(ImageLoader, {
17551
17554
  src: imageSrc,
17552
- imgProps: imageProps
17553
- // @ts-expect-error - TS2322 - Type '(() => Element) | null' is not assignable to type '() => ReactElement<any, string | JSXElementConstructor<any>> | null | undefined'.
17554
- ,
17555
+ imgProps: imageProps,
17555
17556
  preloader: preloader,
17556
17557
  onUpdate: this.handleUpdate
17557
17558
  }), extraGraphie);
17558
17559
  }
17559
17560
  imageProps.style = dimensions;
17560
17561
  return /*#__PURE__*/React.createElement(ImageLoader, {
17561
- src: imageSrc
17562
- // @ts-expect-error - TS2322 - Type '(() => Element) | null' is not assignable to type '() => ReactElement<any, string | JSXElementConstructor<any>> | null | undefined'.
17563
- ,
17562
+ src: imageSrc,
17564
17563
  preloader: preloader,
17565
17564
  imgProps: imageProps,
17566
17565
  onUpdate: this.handleUpdate
@@ -17610,9 +17609,7 @@ class SvgImage extends React.Component {
17610
17609
  }, /*#__PURE__*/React.createElement(ImageLoader, {
17611
17610
  src: imageUrl,
17612
17611
  onLoad: this.onImageLoad,
17613
- onUpdate: this.handleUpdate
17614
- // @ts-expect-error - TS2322 - Type '(() => Element) | null' is not assignable to type '() => ReactElement<any, string | JSXElementConstructor<any>> | null | undefined'.
17615
- ,
17612
+ onUpdate: this.handleUpdate,
17616
17613
  preloader: preloader,
17617
17614
  imgProps: imageProps
17618
17615
  }), graphie, extraGraphie);
@@ -17624,9 +17621,7 @@ class SvgImage extends React.Component {
17624
17621
  }, /*#__PURE__*/React.createElement(ImageLoader, {
17625
17622
  src: imageUrl,
17626
17623
  onLoad: this.onImageLoad,
17627
- onUpdate: this.handleUpdate
17628
- // @ts-expect-error - TS2322 - Type '(() => Element) | null' is not assignable to type '() => ReactElement<any, string | JSXElementConstructor<any>> | null | undefined'.
17629
- ,
17624
+ onUpdate: this.handleUpdate,
17630
17625
  preloader: preloader,
17631
17626
  imgProps: imageProps
17632
17627
  }), graphie);
@@ -18199,33 +18194,33 @@ class Lint extends React.Component {
18199
18194
  let warningText;
18200
18195
  let warningTextStyle;
18201
18196
  if (this.props.severity === Severity.Error) {
18202
- severityStyle = styles$C.indicatorError;
18197
+ severityStyle = styles$B.indicatorError;
18203
18198
  warningText = "Error";
18204
- warningTextStyle = styles$C.publishBlockingError;
18199
+ warningTextStyle = styles$B.publishBlockingError;
18205
18200
  } else if (this.props.severity === Severity.Warning) {
18206
- severityStyle = styles$C.indicatorWarning;
18201
+ severityStyle = styles$B.indicatorWarning;
18207
18202
  warningText = "Warning";
18208
- warningTextStyle = styles$C.warning;
18203
+ warningTextStyle = styles$B.warning;
18209
18204
  } else {
18210
- severityStyle = styles$C.indicatorGuideline;
18205
+ severityStyle = styles$B.indicatorGuideline;
18211
18206
  warningText = "Recommendation";
18212
- warningTextStyle = styles$C.warning;
18207
+ warningTextStyle = styles$B.warning;
18213
18208
  }
18214
18209
  return /*#__PURE__*/React.createElement("a", {
18215
18210
  href: `https://khanacademy.org/r/linter-rules#${this.props.ruleName}`,
18216
18211
  target: "lint-help-window",
18217
18212
  className: css(style)
18218
18213
  }, /*#__PURE__*/React.createElement("span", {
18219
- className: css(styles$C.indicator, severityStyle)
18214
+ className: css(styles$B.indicator, severityStyle)
18220
18215
  }, this.props.severity === 1 && /*#__PURE__*/React.createElement(InlineIcon, exclamationIcon)), /*#__PURE__*/React.createElement("div", {
18221
- className: css(styles$C.tooltip, tooltipAbove && styles$C.tooltipAbove)
18216
+ className: css(styles$B.tooltip, tooltipAbove && styles$B.tooltipAbove)
18222
18217
  }, this.props.message.split("\n\n").map((m, i) => /*#__PURE__*/React.createElement("p", {
18223
18218
  key: i,
18224
- className: css(styles$C.tooltipParagraph)
18219
+ className: css(styles$B.tooltipParagraph)
18225
18220
  }, /*#__PURE__*/React.createElement("span", {
18226
18221
  className: css(warningTextStyle)
18227
18222
  }, warningText, ":", " "), m)), /*#__PURE__*/React.createElement("div", {
18228
- className: css(styles$C.tail, tooltipAbove && styles$C.tailAbove)
18223
+ className: css(styles$B.tail, tooltipAbove && styles$B.tailAbove)
18229
18224
  })));
18230
18225
  };
18231
18226
  }
@@ -18263,20 +18258,20 @@ class Lint extends React.Component {
18263
18258
  }
18264
18259
  if (blockHighlight) {
18265
18260
  return /*#__PURE__*/React.createElement("span", {
18266
- className: css(styles$C.lintContainer, styles$C.lintContainerBlock)
18267
- }, this.renderLink(styles$C.radioWidgetHoverTarget), /*#__PURE__*/React.createElement("span", null, children));
18261
+ className: css(styles$B.lintContainer, styles$B.lintContainerBlock)
18262
+ }, this.renderLink(styles$B.radioWidgetHoverTarget), /*#__PURE__*/React.createElement("span", null, children));
18268
18263
  }
18269
18264
  if (inline) {
18270
18265
  return /*#__PURE__*/React.createElement("span", {
18271
- className: css(styles$C.lintContainer)
18272
- }, this.renderLink(styles$C.inlineHoverTarget), /*#__PURE__*/React.createElement("span", null, children));
18266
+ className: css(styles$B.lintContainer)
18267
+ }, this.renderLink(styles$B.inlineHoverTarget), /*#__PURE__*/React.createElement("span", null, children));
18273
18268
  }
18274
18269
  return /*#__PURE__*/React.createElement("div", {
18275
- className: css(styles$C.lintContainer)
18276
- }, this.renderLink(styles$C.hoverTarget), /*#__PURE__*/React.createElement("div", null, children));
18270
+ className: css(styles$B.lintContainer)
18271
+ }, this.renderLink(styles$B.hoverTarget), /*#__PURE__*/React.createElement("div", null, children));
18277
18272
  }
18278
18273
  }
18279
- const styles$C = StyleSheet.create({
18274
+ const styles$B = StyleSheet.create({
18280
18275
  // This is the class of the outermost element.
18281
18276
  // We use relative positioning so that the lint indicator can be
18282
18277
  // positioned absolutely relative to the position of the linty container.
@@ -18953,15 +18948,17 @@ class TranslationLinter {
18953
18948
  }
18954
18949
 
18955
18950
  /**
18956
- * Preprocess TeX code to convert things that KaTeX doesn't know how to handle
18951
+ * Preprocess TeX code to convert things that MathJax doesn't know how to handle
18957
18952
  * to things is does.
18958
18953
  */
18959
-
18960
18954
  var preprocessTex = (texCode => texCode
18961
18955
  // Replace uses of \begin{align}...\end{align} which KaTeX doesn't
18962
18956
  // support (yet) with \begin{aligned}...\end{aligned} which renders
18963
18957
  // the same is supported by KaTeX. It does the same for align*.
18964
18958
  // TODO(kevinb) update content to use aligned instead of align.
18959
+ // TODO(LEMS-1608) Remove this replacement as MathJax supports the
18960
+ // "align" macro correctly (and, in fact, it is not synonymous with
18961
+ // "aligned").
18965
18962
  .replace(/\{align[*]?\}/g, "{aligned}")
18966
18963
  // Replace non-breaking spaces with regular spaces.
18967
18964
  .replace(/[\u00a0]/g, " "));
@@ -20211,7 +20208,7 @@ class Renderer extends React.Component {
20211
20208
  paddingLeft: margin,
20212
20209
  paddingRight: margin
20213
20210
  };
20214
- const mobileInnerStyle = _extends({}, innerStyle, styles$B.mobileZoomableParentFix);
20211
+ const mobileInnerStyle = _extends({}, innerStyle, styles$A.mobileZoomableParentFix);
20215
20212
  return /*#__PURE__*/React.createElement("div", {
20216
20213
  key: state.key,
20217
20214
  className: "perseus-block-math",
@@ -20238,6 +20235,9 @@ class Renderer extends React.Component {
20238
20235
  // support (yet) with \begin{aligned}...\end{aligned} which renders
20239
20236
  // the same is supported by KaTeX. It does the same for align*.
20240
20237
  // TODO(kevinb) update content to use aligned instead of align.
20238
+ // TODO(LEMS-1608) Remove this replacement as MathJax supports the
20239
+ // "align" macro correctly (and, in fact, it is not synonymous with
20240
+ // "aligned").
20241
20241
  const tex = node.content.replace(/\{align[*]?\}/g, "{aligned}");
20242
20242
 
20243
20243
  // We render math here instead of in perseus-markdown.jsx
@@ -20332,7 +20332,7 @@ class Renderer extends React.Component {
20332
20332
  paddingLeft: 0,
20333
20333
  paddingRight: 0
20334
20334
  };
20335
- const mobileInnerStyle = _extends({}, innerStyle, styles$B.mobileZoomableParentFix);
20335
+ const mobileInnerStyle = _extends({}, innerStyle, styles$A.mobileZoomableParentFix);
20336
20336
  const wrappedOutput = /*#__PURE__*/React.createElement("div", {
20337
20337
  style: _extends({}, mobileInnerStyle, {
20338
20338
  overflowX: "auto"
@@ -20981,7 +20981,7 @@ Renderer.defaultProps = {
20981
20981
  onSerializedStateUpdated: () => {},
20982
20982
  linterContext: PerseusLinter.linterContextDefault
20983
20983
  };
20984
- const styles$B = {
20984
+ const styles$A = {
20985
20985
  mobileZoomableParentFix: {
20986
20986
  // NOTE(abdul): There is an issue where transform animations will
20987
20987
  // cause the Zoomable component to disappear when running on the
@@ -21855,10 +21855,10 @@ class InputNumber extends React.Component {
21855
21855
  // Note: This is _very_ similar to what `numeric-input.jsx` does. If
21856
21856
  // you modify this, double-check if you also need to modify that
21857
21857
  // component.
21858
- const inputStyles = [styles$A.default, this.props.size === "small" ? styles$A.small : null, this.props.rightAlign ? styles$A.rightAlign : styles$A.leftAlign];
21858
+ const inputStyles = [styles$z.default, this.props.size === "small" ? styles$z.small : null, this.props.rightAlign ? styles$z.rightAlign : styles$z.leftAlign];
21859
21859
  // Unanswered
21860
21860
  if (rubric && !this.props.currentValue) {
21861
- inputStyles.push(styles$A.answerStateUnanswered);
21861
+ inputStyles.push(styles$z.answerStateUnanswered);
21862
21862
  }
21863
21863
  return /*#__PURE__*/React.createElement(InputWithExamples
21864
21864
  // eslint-disable-next-line react/no-string-refs
@@ -21943,7 +21943,7 @@ InputNumber.defaultProps = {
21943
21943
  apiOptions: ApiOptions.defaults,
21944
21944
  linterContext: linterContextDefault
21945
21945
  };
21946
- const styles$A = StyleSheet.create({
21946
+ const styles$z = StyleSheet.create({
21947
21947
  default: {
21948
21948
  width: 80,
21949
21949
  height: "auto"
@@ -22938,7 +22938,7 @@ const FocusRing = function FocusRing(props) {
22938
22938
  };
22939
22939
  return /*#__PURE__*/React.createElement("span", {
22940
22940
  "data-testid": "focus-ring",
22941
- className: css(styles$z.ring),
22941
+ className: css(styles$y.ring),
22942
22942
  style: style
22943
22943
  }, children);
22944
22944
  };
@@ -22947,7 +22947,7 @@ FocusRing.defaultProps = {
22947
22947
  color: kaGreen$1,
22948
22948
  multipleSelect: false
22949
22949
  };
22950
- const styles$z = StyleSheet.create({
22950
+ const styles$y = StyleSheet.create({
22951
22951
  ring: {
22952
22952
  margin: "auto",
22953
22953
  display: "inline-block",
@@ -22974,7 +22974,7 @@ function getChoiceLetter(pos, strings) {
22974
22974
  }
22975
22975
 
22976
22976
  const CHOICE_ICON_SIZE = 24;
22977
- const styles$y = StyleSheet.create({
22977
+ const styles$x = StyleSheet.create({
22978
22978
  iconWrapper: {
22979
22979
  display: "inline-block",
22980
22980
  position: "relative"
@@ -22997,7 +22997,7 @@ const CrossOutLine = function CrossOutLine(props) {
22997
22997
  width: CROSS_OUT_LINE_SIZE,
22998
22998
  height: CROSS_OUT_LINE_SIZE,
22999
22999
  viewBox: `0 0 ${CROSS_OUT_LINE_SIZE} ${CROSS_OUT_LINE_SIZE}`,
23000
- className: css(styles$x.crossOutLine),
23000
+ className: css(styles$w.crossOutLine),
23001
23001
  "data-testid": "choice-icon__cross-out-line"
23002
23002
  }, /*#__PURE__*/React.createElement("line", {
23003
23003
  // The line stretches from the bottom-left to top-right.
@@ -23012,7 +23012,7 @@ const CrossOutLine = function CrossOutLine(props) {
23012
23012
  strokeLinecap: "round"
23013
23013
  }));
23014
23014
  };
23015
- const styles$x = StyleSheet.create({
23015
+ const styles$w = StyleSheet.create({
23016
23016
  crossOutLine: {
23017
23017
  // Center the icon within the container.
23018
23018
  position: "absolute",
@@ -23096,7 +23096,7 @@ const ChoiceIcon = function ChoiceIcon(props) {
23096
23096
  } = props;
23097
23097
  const dynamicStyles = getDynamicStyles(checked, showCorrectness, pressed, multipleSelect, correct);
23098
23098
  return /*#__PURE__*/React.createElement("div", {
23099
- className: css(styles$y.iconWrapper)
23099
+ className: css(styles$x.iconWrapper)
23100
23100
  }, /*#__PURE__*/React.createElement(FocusRing, {
23101
23101
  color: color.blue,
23102
23102
  visible: focused || hovered,
@@ -23105,13 +23105,13 @@ const ChoiceIcon = function ChoiceIcon(props) {
23105
23105
  // @ts-expect-error - TS2322 - Type '{ backgroundColor: string | null | undefined; borderColor: string; color: string; borderRadius: number; }' is not assignable to type 'Properties<string | number, string & {}>'.
23106
23106
  style: dynamicStyles,
23107
23107
  "data-testid": "choice-icon__library-choice-icon",
23108
- className: css(styles$w.circle, showCorrectness && correct && styles$w.circleCorrect, showCorrectness && !correct && styles$w.circleIncorrect, showCorrectness && !correct && (checked || previouslyAnswered) && styles$w.circleIncorrectAnswered)
23108
+ className: css(styles$v.circle, showCorrectness && correct && styles$v.circleCorrect, showCorrectness && !correct && styles$v.circleIncorrect, showCorrectness && !correct && (checked || previouslyAnswered) && styles$v.circleIncorrectAnswered)
23109
23109
  // used in BaseRadio editMode to check
23110
23110
  // if we actually clicked on the radio icon
23111
23111
  ,
23112
23112
  "data-is-radio-icon": true
23113
23113
  }, /*#__PURE__*/React.createElement("div", {
23114
- className: css(styles$w.innerWrapper)
23114
+ className: css(styles$v.innerWrapper)
23115
23115
  }, /*#__PURE__*/React.createElement(ChoiceInner, {
23116
23116
  pos: pos,
23117
23117
  showCorrectness: showCorrectness,
@@ -23120,7 +23120,7 @@ const ChoiceIcon = function ChoiceIcon(props) {
23120
23120
  color: dynamicStyles.borderColor
23121
23121
  }));
23122
23122
  };
23123
- const styles$w = StyleSheet.create({
23123
+ const styles$v = StyleSheet.create({
23124
23124
  innerWrapper: {
23125
23125
  width: CHOICE_ICON_SIZE,
23126
23126
  height: CHOICE_ICON_SIZE,
@@ -23215,19 +23215,19 @@ const OptionStatus = function OptionStatus(props) {
23215
23215
  }
23216
23216
  let textStyle;
23217
23217
  if (correct) {
23218
- textStyle = styles$v.correct;
23218
+ textStyle = styles$u.correct;
23219
23219
  } else {
23220
23220
  if (checked || previouslyAnswered) {
23221
- textStyle = styles$v.incorrectAnswered;
23221
+ textStyle = styles$u.incorrectAnswered;
23222
23222
  } else {
23223
- textStyle = styles$v.incorrect;
23223
+ textStyle = styles$u.incorrect;
23224
23224
  }
23225
23225
  }
23226
23226
  return /*#__PURE__*/React.createElement("div", {
23227
- className: css(styles$v.text, textStyle)
23227
+ className: css(styles$u.text, textStyle)
23228
23228
  }, renderText(checked, correct, crossedOut, strings));
23229
23229
  };
23230
- const styles$v = StyleSheet.create({
23230
+ const styles$u = StyleSheet.create({
23231
23231
  text: {
23232
23232
  alignItems: "center",
23233
23233
  display: "flex",
@@ -23297,8 +23297,8 @@ const Choice = function Choice(props) {
23297
23297
  crossedOut: updatedCrossedOut
23298
23298
  });
23299
23299
  }
23300
- const descriptionClassName = classNames("description", css(styles$u.description));
23301
- const rationaleClassName = classNames("perseus-radio-rationale-content", css(styles$u.rationale));
23300
+ const descriptionClassName = classNames("description", css(styles$t.description));
23301
+ const rationaleClassName = classNames("perseus-radio-rationale-content", css(styles$t.rationale));
23302
23302
 
23303
23303
  // We want to show the choices as dimmed out when the choices are disabled.
23304
23304
  // However, we don't want to do this when we're in review mode in the
@@ -23458,7 +23458,7 @@ const Choice = function Choice(props) {
23458
23458
  "data-testid": `perseus-radio-rationale-content-${pos}`
23459
23459
  }, rationale));
23460
23460
  };
23461
- const styles$u = StyleSheet.create({
23461
+ const styles$t = StyleSheet.create({
23462
23462
  description: {
23463
23463
  display: "inline-block",
23464
23464
  width: "100%"
@@ -23657,10 +23657,10 @@ const BaseRadio = function BaseRadio(props) {
23657
23657
  const isMobile = apiOptions.isMobile;
23658
23658
  const firstChoiceHighlighted = choices[0].highlighted;
23659
23659
  const lastChoiceHighlighted = choices[choices.length - 1].highlighted;
23660
- const className = classNames("perseus-widget-radio", !editMode && "perseus-rendered-radio", css(styles$t.radio, styles$t.responsiveRadioContainer, firstChoiceHighlighted && isMobile && styles$t.radioContainerFirstHighlighted, lastChoiceHighlighted && isMobile && styles$t.radioContainerLastHighlighted));
23661
- const instructionsClassName = classNames("instructions", css(styles$t.instructions, isMobile && styles$t.instructionsMobile));
23660
+ const className = classNames("perseus-widget-radio", !editMode && "perseus-rendered-radio", css(styles$s.radio, styles$s.responsiveRadioContainer, firstChoiceHighlighted && isMobile && styles$s.radioContainerFirstHighlighted, lastChoiceHighlighted && isMobile && styles$s.radioContainerLastHighlighted));
23661
+ const instructionsClassName = classNames("instructions", css(styles$s.instructions, isMobile && styles$s.instructionsMobile));
23662
23662
  const instructions = getInstructionsText(multipleSelect, countChoices, numCorrect, strings);
23663
- const responsiveClassName = css(styles$t.responsiveFieldset);
23663
+ const responsiveClassName = css(styles$s.responsiveFieldset);
23664
23664
  const fieldset = /*#__PURE__*/React.createElement("fieldset", {
23665
23665
  className: `perseus-widget-radio-fieldset ${responsiveClassName}`
23666
23666
  }, /*#__PURE__*/React.createElement("legend", {
@@ -23711,7 +23711,7 @@ const BaseRadio = function BaseRadio(props) {
23711
23711
  const aphroditeClassName = checked => {
23712
23712
  // Whether or not to show correctness borders
23713
23713
  // for this choice and the next choice.
23714
- return css(sharedStyles.aboveScratchpad, styles$t.item, styles$t.responsiveItem, checked && styles$t.selectedItem, checked && choice.highlighted && styles$t.aboveBackdrop, checked && choice.highlighted && apiOptions.isMobile && styles$t.aboveBackdropMobile, nextChoiceHighlighted && apiOptions.isMobile && styles$t.nextHighlighted);
23714
+ return css(sharedStyles.aboveScratchpad, styles$s.item, styles$s.responsiveItem, checked && styles$s.selectedItem, checked && choice.highlighted && styles$s.aboveBackdrop, checked && choice.highlighted && apiOptions.isMobile && styles$s.aboveBackdropMobile, nextChoiceHighlighted && apiOptions.isMobile && styles$s.nextHighlighted);
23715
23715
  };
23716
23716
 
23717
23717
  // HACK(abdulrahman): Preloads the selection-state
@@ -23785,14 +23785,14 @@ const BaseRadio = function BaseRadio(props) {
23785
23785
  // Allow for horizontal scrolling if content is too wide, which may be
23786
23786
  // an issue especially on phones.
23787
23787
  return /*#__PURE__*/React.createElement("div", {
23788
- className: css(styles$t.responsiveContainer)
23788
+ className: css(styles$s.responsiveContainer)
23789
23789
  }, fieldset);
23790
23790
  };
23791
23791
  BaseRadio.defaultProps = {
23792
23792
  editMode: false,
23793
23793
  multipleSelect: false
23794
23794
  };
23795
- const styles$t = StyleSheet.create({
23795
+ const styles$s = StyleSheet.create({
23796
23796
  instructions: {
23797
23797
  display: "block",
23798
23798
  color: gray17,
@@ -24454,9 +24454,9 @@ class Categorizer extends React.Component {
24454
24454
  indexedItems = Util.shuffle(indexedItems, this.props.problemNum);
24455
24455
  }
24456
24456
  const table = /*#__PURE__*/React.createElement("table", {
24457
- className: "categorizer-table " + css(styles$s.mobileTable)
24457
+ className: "categorizer-table " + css(styles$r.mobileTable)
24458
24458
  }, /*#__PURE__*/React.createElement("thead", null, /*#__PURE__*/React.createElement("tr", null, /*#__PURE__*/React.createElement("td", {
24459
- className: css(styles$s.emptyHeaderCell)
24459
+ className: css(styles$r.emptyHeaderCell)
24460
24460
  }), this.props.categories.map((category, i) => {
24461
24461
  // Array index is the correct key here, as that's
24462
24462
  // how category grading actually works -- no way
@@ -24464,7 +24464,7 @@ class Categorizer extends React.Component {
24464
24464
  // middle. (If we later add that, this should be
24465
24465
  // fixed.)
24466
24466
  return /*#__PURE__*/React.createElement("th", {
24467
- className: css(styles$s.header),
24467
+ className: css(styles$r.header),
24468
24468
  key: i
24469
24469
  }, /*#__PURE__*/React.createElement(Renderer, {
24470
24470
  content: category,
@@ -24486,7 +24486,7 @@ class Categorizer extends React.Component {
24486
24486
  })), self.props.categories.map((catName, catNum) => {
24487
24487
  const selected = self.props.values[itemNum] === catNum;
24488
24488
  return /*#__PURE__*/React.createElement("td", {
24489
- className: "category " + css(styles$s.cell),
24489
+ className: "category " + css(styles$r.cell),
24490
24490
  key: catNum
24491
24491
  }, /*#__PURE__*/React.createElement("div", {
24492
24492
  className: ClassNames.INTERACTIVE,
@@ -24501,7 +24501,7 @@ class Categorizer extends React.Component {
24501
24501
  onChange: () => this.onChange(itemNum, catNum),
24502
24502
  onClick: e => e.stopPropagation()
24503
24503
  }), !isMobile && /*#__PURE__*/React.createElement("span", {
24504
- className: css(styles$s.radioSpan, selected && styles$s.checkedRadioSpan, this.props.static && selected && styles$s.staticCheckedRadioSpan)
24504
+ className: css(styles$r.radioSpan, selected && styles$r.checkedRadioSpan, this.props.static && selected && styles$r.staticCheckedRadioSpan)
24505
24505
  }, selected ? /*#__PURE__*/React.createElement(InlineIcon, iconCircle) : /*#__PURE__*/React.createElement(InlineIcon, iconCircleThin))));
24506
24506
  }));
24507
24507
  /* eslint-enable max-len */
@@ -24513,7 +24513,7 @@ class Categorizer extends React.Component {
24513
24513
  "categorizer-container": true,
24514
24514
  "static-mode": this.props.static
24515
24515
  });
24516
- const inlineStyles = this.props.apiOptions.isMobile ? [styles$s.fullBleedContainer] : [];
24516
+ const inlineStyles = this.props.apiOptions.isMobile ? [styles$r.fullBleedContainer] : [];
24517
24517
  return /*#__PURE__*/React.createElement("div", {
24518
24518
  className: extraClassNames + " " + css(...inlineStyles)
24519
24519
  }, table);
@@ -24566,7 +24566,7 @@ const pageMargin = 16;
24566
24566
 
24567
24567
  // Stylesheets aren't directly testable
24568
24568
  /* c8 ignore next */
24569
- const styles$s = StyleSheet.create({
24569
+ const styles$r = StyleSheet.create({
24570
24570
  mobileTable: {
24571
24571
  [mediaQueries.smOrSmaller]: {
24572
24572
  minWidth: "auto"
@@ -24802,7 +24802,7 @@ class CSProgram extends React.Component {
24802
24802
  // creator "went wild".
24803
24803
  // http://www.html5rocks.com/en/tutorials/security/sandboxed-iframes/
24804
24804
  return /*#__PURE__*/React.createElement("div", {
24805
- className: css(styles$r.widthOverride, styleContainer && styles$r.container)
24805
+ className: css(styles$q.widthOverride, styleContainer && styles$q.container)
24806
24806
  }, /*#__PURE__*/React.createElement("iframe", {
24807
24807
  sandbox: sandboxOptions,
24808
24808
  src: url,
@@ -24831,7 +24831,7 @@ CSProgram.defaultProps = {
24831
24831
  // optional message
24832
24832
  message: null
24833
24833
  };
24834
- const styles$r = StyleSheet.create({
24834
+ const styles$q = StyleSheet.create({
24835
24835
  // Override the inherited width from the perseus paragraph class
24836
24836
  // 820 is minimum width for 420px editor and 400px canvas
24837
24837
  widthOverride: {
@@ -24874,7 +24874,7 @@ class Definition extends React.Component {
24874
24874
  }) => /*#__PURE__*/React.createElement(Popover, {
24875
24875
  content: /*#__PURE__*/React.createElement(PopoverContentCore, {
24876
24876
  color: "white",
24877
- style: styles$q.tooltipBody,
24877
+ style: styles$p.tooltipBody,
24878
24878
  closeButtonVisible: true
24879
24879
  }, /*#__PURE__*/React.createElement(Renderer, {
24880
24880
  apiOptions: this.props.apiOptions,
@@ -24907,7 +24907,7 @@ Definition.defaultProps = {
24907
24907
  togglePrompt: "define me",
24908
24908
  definition: "definition goes here"
24909
24909
  };
24910
- const styles$q = {
24910
+ const styles$p = {
24911
24911
  tooltipBody: {
24912
24912
  color: color.offBlack,
24913
24913
  fontSize: 20,
@@ -25135,7 +25135,7 @@ class Explanation extends React.Component {
25135
25135
  marginRight: "-6px",
25136
25136
  "white-space": "normal"
25137
25137
  };
25138
- const contentStyling = [styles$p.content, this.state.expanded ? styles$p.contentExpanded : styles$p.contentCollapsed, allowTransition && (this.state.expanded ? styles$p.transitionExpanded : styles$p.transitionCollapsed)];
25138
+ const contentStyling = [styles$o.content, this.state.expanded ? styles$o.contentExpanded : styles$o.contentCollapsed, allowTransition && (this.state.expanded ? styles$o.transitionExpanded : styles$o.transitionCollapsed)];
25139
25139
  return /*#__PURE__*/React.createElement(UniqueIDProvider, {
25140
25140
  mockOnFirstRender: true,
25141
25141
  scope: "explanation-widget"
@@ -25154,7 +25154,7 @@ class Explanation extends React.Component {
25154
25154
  "aria-hidden": !this.state.expanded,
25155
25155
  testId: "content-container"
25156
25156
  }, /*#__PURE__*/React.createElement(View, {
25157
- style: styles$p.contentWrapper
25157
+ style: styles$o.contentWrapper
25158
25158
  }, /*#__PURE__*/React.createElement(Renderer, {
25159
25159
  apiOptions: this.props.apiOptions,
25160
25160
  content: this.props.explanation,
@@ -25175,7 +25175,7 @@ Explanation.defaultProps = {
25175
25175
  const leftBorderSpacing = 23;
25176
25176
  const verticalContentPadding = 10;
25177
25177
  const arrowHeight = 14;
25178
- const styles$p = StyleSheet.create({
25178
+ const styles$o = StyleSheet.create({
25179
25179
  content: {
25180
25180
  borderLeft: "0px solid #ccc",
25181
25181
  display: "inline-grid",
@@ -25239,15 +25239,15 @@ class GradedGroupAnswerBar extends React.Component {
25239
25239
  correctExcited,
25240
25240
  nextQuestion
25241
25241
  } = this.context.strings;
25242
- const answerBarStyle = _extends({}, styles$o.answerBar, {
25242
+ const answerBarStyle = _extends({}, styles$n.answerBar, {
25243
25243
  backgroundColor: answerBarState === "CORRECT" ? color.offWhite : "white",
25244
25244
  // Center the "Correct!" message only when there's no next question
25245
25245
  justifyContent: answerBarState === "CORRECT" && !onNextQuestion ? "center" : "space-between"
25246
25246
  });
25247
25247
  const message = answerBarState === "INCORRECT" ? /*#__PURE__*/React.createElement("span", {
25248
- style: styles$o.text
25248
+ style: styles$n.text
25249
25249
  }, /*#__PURE__*/React.createElement("span", {
25250
- style: styles$o.tryAgainIcon
25250
+ style: styles$n.tryAgainIcon
25251
25251
  }, /*#__PURE__*/React.createElement(InlineIcon, iconTryAgain)), /*#__PURE__*/React.createElement("span", {
25252
25252
  style: {
25253
25253
  marginLeft: 8
@@ -25266,7 +25266,7 @@ class GradedGroupAnswerBar extends React.Component {
25266
25266
  return /*#__PURE__*/React.createElement("div", {
25267
25267
  style: answerBarStyle
25268
25268
  }, /*#__PURE__*/React.createElement("span", {
25269
- style: styles$o.text
25269
+ style: styles$n.text
25270
25270
  }, /*#__PURE__*/React.createElement("span", {
25271
25271
  style: {
25272
25272
  fontSize: 28,
@@ -25289,7 +25289,7 @@ class GradedGroupAnswerBar extends React.Component {
25289
25289
  }
25290
25290
  GradedGroupAnswerBar.contextType = PerseusI18nContext;
25291
25291
  const fontSize = 17;
25292
- const styles$o = {
25292
+ const styles$n = {
25293
25293
  answerBar: {
25294
25294
  display: "flex",
25295
25295
  alignItems: "center",
@@ -25474,7 +25474,7 @@ class GradedGroup extends React.Component {
25474
25474
  }));
25475
25475
  gradeStatus = this.context.strings.incorrect;
25476
25476
  }
25477
- const mobileClass = this.props.inGradedGroupSet ? css(styles$n.gradedGroupInSet) : css(styles$n.gradedGroup);
25477
+ const mobileClass = this.props.inGradedGroupSet ? css(styles$m.gradedGroupInSet) : css(styles$m.gradedGroup);
25478
25478
  const classes = classNames({
25479
25479
  [mobileClass]: apiOptions.isMobile,
25480
25480
  "perseus-graded-group": true,
@@ -25493,7 +25493,7 @@ class GradedGroup extends React.Component {
25493
25493
  return /*#__PURE__*/React.createElement("div", {
25494
25494
  className: classes
25495
25495
  }, !!this.props.title && /*#__PURE__*/React.createElement("div", {
25496
- className: css(styles$n.title)
25496
+ className: css(styles$m.title)
25497
25497
  }, this.props.title), /*#__PURE__*/React.createElement(Renderer, _extends({}, this.props, {
25498
25498
  // eslint-disable-next-line react/no-string-refs
25499
25499
  ref: "renderer",
@@ -25526,7 +25526,7 @@ class GradedGroup extends React.Component {
25526
25526
  }, this.context.strings.nextQuestion), this.props.hint && this.props.hint.content && (this.state.showHint ? /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("button", {
25527
25527
  // @ts-expect-error - TS2322 - Type 'string' is not assignable to type 'number | undefined'.
25528
25528
  tabIndex: "0",
25529
- className: css(styles$n.explanationTitle),
25529
+ className: css(styles$m.explanationTitle),
25530
25530
  onClick: () => this.setState({
25531
25531
  showHint: false
25532
25532
  }),
@@ -25559,7 +25559,7 @@ class GradedGroup extends React.Component {
25559
25559
  showHint: true
25560
25560
  });
25561
25561
  },
25562
- className: css(styles$n.showHintLink)
25562
+ className: css(styles$m.showHintLink)
25563
25563
  }, this.context.strings.explain)), apiOptions.isMobile && answerBarState !== "HIDDEN" && /*#__PURE__*/React.createElement(GradedGroupAnswerBar, {
25564
25564
  apiOptions: apiOptions,
25565
25565
  answerBarState: answerBarState,
@@ -25581,7 +25581,7 @@ GradedGroup.defaultProps = {
25581
25581
  const traverseChildWidgets$2 = function traverseChildWidgets(props, traverseRenderer) {
25582
25582
  return _.extend({}, props, traverseRenderer(props));
25583
25583
  };
25584
- const styles$n = StyleSheet.create({
25584
+ const styles$m = StyleSheet.create({
25585
25585
  gradedGroupInSet: {
25586
25586
  // Reset a few desktop-only styles that come from graded-group.less
25587
25587
  marginLeft: 0,
@@ -25654,18 +25654,18 @@ class Indicators extends React.Component {
25654
25654
  return /*#__PURE__*/React.createElement("ul", {
25655
25655
  // reduntantly add class name for use in .less files--
25656
25656
  // the styles object key gets hashed
25657
- className: classNames(css(styles$m.indicatorContainer), "indicatorContainer")
25657
+ className: classNames(css(styles$l.indicatorContainer), "indicatorContainer")
25658
25658
  }, this.props.gradedGroups.map(({
25659
25659
  title
25660
25660
  }, i) => /*#__PURE__*/React.createElement("li", {
25661
- className: css(styles$m.indicator),
25661
+ className: css(styles$l.indicator),
25662
25662
  key: title
25663
25663
  }, /*#__PURE__*/React.createElement(Clickable, {
25664
25664
  role: "button",
25665
25665
  "aria-label": this.context.strings.skipToTitle({
25666
25666
  title
25667
25667
  }),
25668
- style: styles$m.indicatorButton,
25668
+ style: styles$l.indicatorButton,
25669
25669
  onClick: () => this.props.onChangeCurrentGroup(i),
25670
25670
  onKeyDown: e => this.handleKeyDown(e, i)
25671
25671
  }, ({
@@ -25673,9 +25673,9 @@ class Indicators extends React.Component {
25673
25673
  focused,
25674
25674
  pressed
25675
25675
  }) => /*#__PURE__*/React.createElement(View, {
25676
- style: [styles$m.indicatorDot, (hovered || focused || pressed) && styles$m.indicatorDotFocused]
25676
+ style: [styles$l.indicatorDot, (hovered || focused || pressed) && styles$l.indicatorDotFocused]
25677
25677
  }, i === this.props.currentGroup && /*#__PURE__*/React.createElement(View, {
25678
- style: styles$m.indicatorDotActive
25678
+ style: styles$l.indicatorDotActive
25679
25679
  }, /*#__PURE__*/React.createElement("span", {
25680
25680
  className: css(a11y.srOnly)
25681
25681
  }, this.context.strings.current)))))));
@@ -25739,7 +25739,7 @@ class GradedGroupSet extends React.Component {
25739
25739
  } = getDependencies();
25740
25740
  if (JIPT.useJIPT && this.props.gradedGroups.length > 1) {
25741
25741
  return /*#__PURE__*/React.createElement("div", {
25742
- className: css(styles$m.container)
25742
+ className: css(styles$l.container)
25743
25743
  }, this.props.gradedGroups.map((gradedGroup, i) => {
25744
25744
  return (
25745
25745
  /*#__PURE__*/
@@ -25762,13 +25762,13 @@ class GradedGroupSet extends React.Component {
25762
25762
  const numGroups = this.props.gradedGroups.length;
25763
25763
  const handleNextQuestion = this.state.currentGroup < numGroups - 1 ? this.handleNextQuestion : null;
25764
25764
  return /*#__PURE__*/React.createElement("div", {
25765
- className: css(styles$m.container)
25765
+ className: css(styles$l.container)
25766
25766
  }, /*#__PURE__*/React.createElement("div", {
25767
- className: css(styles$m.top)
25767
+ className: css(styles$l.top)
25768
25768
  }, /*#__PURE__*/React.createElement("div", {
25769
- className: css(styles$m.title)
25769
+ className: css(styles$l.title)
25770
25770
  }, currentGroup.title), /*#__PURE__*/React.createElement("div", {
25771
- className: css(styles$m.spacer)
25771
+ className: css(styles$l.spacer)
25772
25772
  }), /*#__PURE__*/React.createElement(Indicators, {
25773
25773
  currentGroup: this.state.currentGroup,
25774
25774
  gradedGroups: this.props.gradedGroups,
@@ -25810,7 +25810,7 @@ var GradedGroupSet$1 = {
25810
25810
  tracking: "all",
25811
25811
  isLintable: true
25812
25812
  };
25813
- const styles$m = StyleSheet.create({
25813
+ const styles$l = StyleSheet.create({
25814
25814
  top: {
25815
25815
  display: "flex",
25816
25816
  flexDirection: "row"
@@ -25915,7 +25915,7 @@ class ButtonGroup extends React.Component {
25915
25915
  id: "" + i,
25916
25916
  ref: "button" + i,
25917
25917
  key: "" + i,
25918
- className: css(styles$l.buttonStyle, button.value === value && styles$l.selectedStyle),
25918
+ className: css(styles$k.buttonStyle, button.value === value && styles$k.selectedStyle),
25919
25919
  onClick: () => this.toggleSelect(button.value)
25920
25920
  }, button.content || "" + button.value);
25921
25921
  });
@@ -25932,7 +25932,7 @@ ButtonGroup.defaultProps = {
25932
25932
  value: null,
25933
25933
  allowEmpty: true
25934
25934
  };
25935
- const styles$l = StyleSheet.create({
25935
+ const styles$k = StyleSheet.create({
25936
25936
  buttonStyle: {
25937
25937
  backgroundColor: "white",
25938
25938
  border: "1px solid #ccc",
@@ -26536,7 +26536,7 @@ var util = /*#__PURE__*/Object.freeze({
26536
26536
  });
26537
26537
 
26538
26538
  // @ts-expect-error - TS2339 - Property 'MovablePoint' does not exist on type 'typeof Graphie'.
26539
- const MovablePoint$2 = Graphie.MovablePoint;
26539
+ const MovablePoint$3 = Graphie.MovablePoint;
26540
26540
  // @ts-expect-error - TS2339 - Property 'MovableLine' does not exist on type 'typeof Graphie'.
26541
26541
  const MovableLine$2 = Graphie.MovableLine;
26542
26542
  function isFlipped(newCoord, oldCoord, line) {
@@ -26618,7 +26618,7 @@ class FunctionGrapher extends React.Component {
26618
26618
  }],
26619
26619
  normalStyle: dashed,
26620
26620
  highlightStyle: dashed
26621
- }, _.map(asymptote, (coord, i) => /*#__PURE__*/React.createElement(MovablePoint$2, {
26621
+ }, _.map(asymptote, (coord, i) => /*#__PURE__*/React.createElement(MovablePoint$3, {
26622
26622
  key: `asymptoteCoord-${i}`,
26623
26623
  coord: coord,
26624
26624
  static: true,
@@ -26633,7 +26633,7 @@ class FunctionGrapher extends React.Component {
26633
26633
  }
26634
26634
  render() {
26635
26635
  const pointForCoord = (coord, i) => {
26636
- return /*#__PURE__*/React.createElement(MovablePoint$2, {
26636
+ return /*#__PURE__*/React.createElement(MovablePoint$3, {
26637
26637
  key: i,
26638
26638
  coord: coord,
26639
26639
  static: this.props.static,
@@ -27442,7 +27442,7 @@ const Label = Graphie.Label;
27442
27442
  // @ts-expect-error - TS2339 - Property 'Line' does not exist on type 'typeof Graphie'.
27443
27443
  const Line$2 = Graphie.Line;
27444
27444
  // @ts-expect-error - TS2339 - Property 'MovablePoint' does not exist on type 'typeof Graphie'.
27445
- const MovablePoint$1 = Graphie.MovablePoint;
27445
+ const MovablePoint$2 = Graphie.MovablePoint;
27446
27446
  // @ts-expect-error - TS2339 - Property 'MovableLine' does not exist on type 'typeof Graphie'.
27447
27447
  const MovableLine$1 = Graphie.MovableLine;
27448
27448
  // @ts-expect-error - TS2339 - Property 'Plot' does not exist on type 'typeof Graphie'.
@@ -27691,7 +27691,7 @@ class Interaction extends React.Component {
27691
27691
  return [coordX, coordY];
27692
27692
  }];
27693
27693
  if (element.options.constraint === "snap") {
27694
- constraints.push(MovablePoint$1.constraints.snap(element.options.snap));
27694
+ constraints.push(MovablePoint$2.constraints.snap(element.options.snap));
27695
27695
  } else if (element.options.constraint === "x") {
27696
27696
  constraints.push(coord => {
27697
27697
  return [
@@ -27713,7 +27713,7 @@ class Interaction extends React.Component {
27713
27713
  // TODO(eater): foo_[xyz] are hacky non-props to
27714
27714
  // get the component to update when constraints
27715
27715
  // change
27716
- return /*#__PURE__*/React.createElement(MovablePoint$1, {
27716
+ return /*#__PURE__*/React.createElement(MovablePoint$2, {
27717
27717
  key: element.key,
27718
27718
  coord: [
27719
27719
  // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
@@ -27748,7 +27748,7 @@ class Interaction extends React.Component {
27748
27748
  return [coordX, coordY];
27749
27749
  }];
27750
27750
  if (element.options.constraint === "snap") {
27751
- constraints.push(MovablePoint$1.constraints.snap(element.options.snap));
27751
+ constraints.push(MovablePoint$2.constraints.snap(element.options.snap));
27752
27752
  } else if (element.options.constraint === "x") {
27753
27753
  constraints.push(coord => {
27754
27754
  return [
@@ -27789,14 +27789,14 @@ class Interaction extends React.Component {
27789
27789
  foo_x: element.options.constraint,
27790
27790
  foo_y: element.options.constraintFn,
27791
27791
  foo_z: element.options.snap
27792
- }, /*#__PURE__*/React.createElement(MovablePoint$1, {
27792
+ }, /*#__PURE__*/React.createElement(MovablePoint$2, {
27793
27793
  coord: start,
27794
27794
  static: true,
27795
27795
  normalStyle: {
27796
27796
  stroke: "none",
27797
27797
  fill: "none"
27798
27798
  }
27799
- }), /*#__PURE__*/React.createElement(MovablePoint$1, {
27799
+ }), /*#__PURE__*/React.createElement(MovablePoint$2, {
27800
27800
  coord: end,
27801
27801
  static: true,
27802
27802
  normalStyle: {
@@ -28353,7 +28353,7 @@ const defaultGraphConfig = {
28353
28353
  width: 0,
28354
28354
  height: 0,
28355
28355
  labels: [],
28356
- hintMode: false
28356
+ disableKeyboardInteraction: false
28357
28357
  };
28358
28358
  const GraphConfigContext = /*#__PURE__*/createContext(defaultGraphConfig);
28359
28359
  function useGraphConfig() {
@@ -28490,6 +28490,28 @@ const segmentsIntersect = ([[a, b], [c, d]], [[p, q], [r, s]]) => {
28490
28490
  }
28491
28491
  };
28492
28492
 
28493
+ // Finds the unique intersection point between two rays. Returns undefined if
28494
+ // there is not exactly one intersection point. The first point in each of the
28495
+ // given ray "segments" should be the initial point of the ray. The second point
28496
+ // can be any arbitrary point on the ray different from the initial point.
28497
+ function findIntersectionOfRays([[a, b], [c, d]], [[p, q], [r, s]]) {
28498
+ // See https://stackoverflow.com/a/24392281/7347484 for an explanation of
28499
+ // the determinant and lambda values.
28500
+ const determinant = (c - a) * (s - q) - (r - p) * (d - b);
28501
+ if (determinant === 0) {
28502
+ return undefined;
28503
+ } else {
28504
+ const lambda = ((s - q) * (r - a) + (p - r) * (s - b)) / determinant;
28505
+ const gamma = ((b - d) * (r - a) + (c - a) * (s - b)) / determinant;
28506
+ if (lambda <= 0 || gamma >= 1) {
28507
+ return undefined;
28508
+ }
28509
+ const initialPoint = [a, b];
28510
+ const direction = vec.sub([c, d], initialPoint);
28511
+ return vec.add(initialPoint, vec.scale(direction, lambda));
28512
+ }
28513
+ }
28514
+
28493
28515
  // This file contains helper functions for working with angles.
28494
28516
 
28495
28517
  function convertDegreesToRadians(degrees) {
@@ -28501,6 +28523,8 @@ function convertDegreesToRadians(degrees) {
28501
28523
  function calculateAngleInDegrees([x, y]) {
28502
28524
  return Math.atan2(y, x) * 180 / Math.PI;
28503
28525
  }
28526
+
28527
+ // Converts polar coordinates to cartesian. The th(eta) parameter is in degrees.
28504
28528
  function polar(r, th) {
28505
28529
  if (typeof r === "number") {
28506
28530
  r = [r, r];
@@ -29106,7 +29130,7 @@ const VisibleIcon = () => /*#__PURE__*/React.createElement("svg", {
29106
29130
  width: "24",
29107
29131
  height: "24",
29108
29132
  viewBox: "0 0 24 24",
29109
- className: css(styles$k.icon)
29133
+ className: css(styles$j.icon)
29110
29134
  }, /*#__PURE__*/React.createElement("defs", null, /*#__PURE__*/React.createElement("path", {
29111
29135
  id: "a",
29112
29136
  d: "M7.401 10.035c-1.424.748-2.599 1.905-3.544 " + "3.48a1 1 0 0 1-1.714-1.03C4.325 8.849 7.652 7 " + "12 7c4.348 0 7.675 1.848 9.857 5.486a1 1 0 0 " + "1-1.714 1.028c-.945-1.574-2.12-2.73-3.544-" + "3.48a5 5 0 1 1-9.198 0zM12 15a3 3 0 1 0 0-6 3 3 " + "0 0 0 0 6z"
@@ -29137,7 +29161,7 @@ const HiddenIcon = () => /*#__PURE__*/React.createElement("svg", {
29137
29161
  width: "24",
29138
29162
  height: "24",
29139
29163
  viewBox: "0 0 24 24",
29140
- className: css(styles$k.icon)
29164
+ className: css(styles$j.icon)
29141
29165
  }, /*#__PURE__*/React.createElement("defs", null, /*#__PURE__*/React.createElement("path", {
29142
29166
  id: "a",
29143
29167
  d: "M8.794 7.38C9.791 7.127 10.86 7 12 7c4.348 0 " + "7.675 1.848 9.857 5.486a1 1 0 0 1-1.714 " + "1.028c-.945-1.574-2.12-2.73-3.544-3.48.258." + "604.401 1.268.401 1.966 0 1.02-.305 " + "1.967-.828 2.757l2.535 2.536a1 1 0 0 " + "1-1.414 1.414l-12-12a1 1 0 0 1 " + "1.414-1.414L8.794 7.38zm5.914 5.913a3 3 0 0 " + "0-4.001-4.001l4 4.001zM6.072 8.486l2.976 " + "2.976a3 3 0 0 0 3.49 3.49l1.579 1.58A5 5 0 " + "0 1 7.4 10.035c-1.424.747-2.599 1.904-3.544 " + "3.478a1 1 0 0 1-1.714-1.028c1.049-1.75 " + "2.363-3.085 3.929-4z"
@@ -29171,20 +29195,20 @@ const HUD = ({
29171
29195
  let state;
29172
29196
  let icon;
29173
29197
  if (enabled) {
29174
- state = styles$k.enabled;
29198
+ state = styles$j.enabled;
29175
29199
  icon = /*#__PURE__*/React.createElement(VisibleIcon, null);
29176
29200
  } else {
29177
- state = styles$k.disabled;
29201
+ state = styles$j.disabled;
29178
29202
  icon = /*#__PURE__*/React.createElement(HiddenIcon, null);
29179
29203
  }
29180
29204
  return /*#__PURE__*/React.createElement("button", {
29181
- className: css(styles$k.hud, _fixedPosition && styles$k.hudFixedPosition, state),
29205
+ className: css(styles$j.hud, _fixedPosition && styles$j.hudFixedPosition, state),
29182
29206
  onClick: e => {
29183
29207
  onClick();
29184
29208
  }
29185
29209
  }, icon, message);
29186
29210
  };
29187
- const styles$k = StyleSheet.create({
29211
+ const styles$j = StyleSheet.create({
29188
29212
  hud: {
29189
29213
  boxSizing: "border-box",
29190
29214
  height: 36,
@@ -29234,19 +29258,19 @@ const styles$k = StyleSheet.create({
29234
29258
  }
29235
29259
  });
29236
29260
 
29237
- const InfoTip$1 = props => {
29261
+ const InfoTipBase = props => {
29238
29262
  return /*#__PURE__*/React.createElement(Tooltip$1, {
29239
29263
  content: /*#__PURE__*/React.createElement(Body, {
29240
- style: styles$j.body
29264
+ style: styles$i.body
29241
29265
  }, props.children),
29242
29266
  placement: "right"
29243
29267
  }, /*#__PURE__*/React.createElement(PhosphorIcon, {
29244
29268
  size: "small",
29245
29269
  icon: questionIcon,
29246
- style: styles$j.spacingLeft
29270
+ style: styles$i.spacingLeft
29247
29271
  }));
29248
29272
  };
29249
- const styles$j = StyleSheet.create({
29273
+ const styles$i = StyleSheet.create({
29250
29274
  spacingLeft: {
29251
29275
  marginInlineStart: spacing.xxxSmall_4
29252
29276
  },
@@ -29278,7 +29302,7 @@ class InfoTip extends React.Component {
29278
29302
 
29279
29303
  render() {
29280
29304
  if (this.state.didMount) {
29281
- return /*#__PURE__*/React.createElement(InfoTip$1, this.props);
29305
+ return /*#__PURE__*/React.createElement(InfoTipBase, this.props);
29282
29306
  }
29283
29307
  return /*#__PURE__*/React.createElement("div", null);
29284
29308
  }
@@ -29325,7 +29349,7 @@ class MultiButtonGroup extends React.Component {
29325
29349
  id: "" + i,
29326
29350
  key: "" + i,
29327
29351
  ref: "button" + i,
29328
- className: css(styles$i.buttonStyle, selected && styles$i.selectedStyle),
29352
+ className: css(styles$h.buttonStyle, selected && styles$h.selectedStyle),
29329
29353
  onClick: () => this.toggleSelect(button.value)
29330
29354
  }, button.content || "" + button.value);
29331
29355
  });
@@ -29341,7 +29365,7 @@ MultiButtonGroup.defaultProps = {
29341
29365
  values: [],
29342
29366
  allowEmpty: true
29343
29367
  };
29344
- const styles$i = StyleSheet.create({
29368
+ const styles$h = StyleSheet.create({
29345
29369
  buttonStyle: {
29346
29370
  backgroundColor: "white",
29347
29371
  border: "1px solid #ccc",
@@ -29566,62 +29590,6 @@ NumberInput.defaultProps = {
29566
29590
  useArrowKeys: false
29567
29591
  };
29568
29592
 
29569
- /* eslint-disable @babel/no-invalid-this */
29570
-
29571
- /* A checkbox that syncs its value to props using the
29572
- * renderer's onChange method, and gets the prop name
29573
- * dynamically from its props list
29574
- */
29575
- const PropCheckBox = createReactClass({
29576
- displayName: "PropCheckBox",
29577
- propTypes: {
29578
- labelAlignment: PropTypes.oneOf(["left", "right"])
29579
- },
29580
- DEFAULT_PROPS: {
29581
- label: null,
29582
- onChange: null,
29583
- labelAlignment: "left"
29584
- },
29585
- getDefaultProps: function () {
29586
- return this.DEFAULT_PROPS;
29587
- },
29588
- propName: function () {
29589
- const propName = _.find(_.keys(this.props), function (localPropName) {
29590
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
29591
- return !_.has(this.DEFAULT_PROPS, localPropName);
29592
- }, this);
29593
- if (!propName) {
29594
- throw new PerseusError("Attempted to create a PropCheckBox with no prop!", Errors.InvalidInput);
29595
- }
29596
- return propName;
29597
- },
29598
- _labelAlignLeft: function () {
29599
- return this.props.labelAlignment === "left";
29600
- },
29601
- render: function () {
29602
- const propName = this.propName();
29603
- return /*#__PURE__*/React.createElement("label", {
29604
- className: css(styles$h.labeledCheckbox)
29605
- }, this._labelAlignLeft() && /*#__PURE__*/React.createElement(LabelSmall, null, this.props.label), /*#__PURE__*/React.createElement(Checkbox, {
29606
- checked: this.props[propName],
29607
- onChange: this.toggle
29608
- }), !this._labelAlignLeft() && /*#__PURE__*/React.createElement(LabelSmall, null, this.props.label));
29609
- },
29610
- toggle: function () {
29611
- const propName = this.propName();
29612
- const changes = {};
29613
- changes[propName] = !this.props[propName];
29614
- this.props.onChange(changes);
29615
- }
29616
- });
29617
- const styles$h = StyleSheet.create({
29618
- labeledCheckbox: {
29619
- display: "flex",
29620
- flexDirection: "row",
29621
- alignItems: "center"
29622
- }
29623
- });
29624
-
29625
29593
  const truth = () => true;
29626
29594
 
29627
29595
  /* A minor abstraction on top of NumberInput for ranges
@@ -29683,7 +29651,6 @@ var components = /*#__PURE__*/Object.freeze({
29683
29651
  MathInput: MathInput,
29684
29652
  MultiButtonGroup: MultiButtonGroup,
29685
29653
  NumberInput: NumberInput,
29686
- PropCheckBox: PropCheckBox,
29687
29654
  RangeInput: RangeInput,
29688
29655
  SvgImage: SvgImage,
29689
29656
  TextInput: TextInput,
@@ -29837,8 +29804,9 @@ const lockedFigureColors = {
29837
29804
  };
29838
29805
  const lockedFigureFillStyles = {
29839
29806
  none: 0,
29840
- solid: 1,
29841
- translucent: 0.4
29807
+ white: 1,
29808
+ translucent: 0.4,
29809
+ solid: 1
29842
29810
  };
29843
29811
  const plotterPlotTypes = ["bar", "line", "pic", "histogram", "dotplot"];
29844
29812
 
@@ -29847,7 +29815,7 @@ const LockedEllipse = props => {
29847
29815
  center,
29848
29816
  radius,
29849
29817
  angle,
29850
- color,
29818
+ color: color$1,
29851
29819
  fillStyle,
29852
29820
  strokeStyle
29853
29821
  } = props;
@@ -29857,11 +29825,21 @@ const LockedEllipse = props => {
29857
29825
  angle: angle,
29858
29826
  fillOpacity: lockedFigureFillStyles[fillStyle],
29859
29827
  strokeStyle: strokeStyle,
29860
- color: lockedFigureColors[color]
29828
+ color: lockedFigureColors[color$1]
29829
+ // We need to override the svg props if we want to have a
29830
+ // different fill color than the stroke color (specifically,
29831
+ // in the case where the fillStyle is "white").
29832
+ ,
29833
+ svgEllipseProps: {
29834
+ style: {
29835
+ fill: fillStyle === "white" ? color.white : lockedFigureColors[color$1]
29836
+ }
29837
+ }
29861
29838
  });
29862
29839
  };
29863
29840
 
29864
29841
  const LockedFunction = props => {
29842
+ const [equation, setEquation] = useState();
29865
29843
  const {
29866
29844
  color,
29867
29845
  strokeStyle,
@@ -29873,7 +29851,14 @@ const LockedFunction = props => {
29873
29851
  style: strokeStyle,
29874
29852
  domain
29875
29853
  };
29876
- const equation = props.equationParsed || KAS.parse(props.equation).expr;
29854
+ useEffect(() => {
29855
+ // Parsing the equation in a "useEffect" hook saves about 2ms each frame
29856
+ // when the learner is interacting with the graph (i.e. moving points).
29857
+ setEquation(KAS.parse(props.equation).expr);
29858
+ }, [props.equation]);
29859
+ if (typeof equation === "undefined") {
29860
+ return null;
29861
+ }
29877
29862
  return /*#__PURE__*/React.createElement("g", {
29878
29863
  className: "locked-function"
29879
29864
  }, directionalAxis === "x" && /*#__PURE__*/React.createElement(Plot$3.OfX, _extends({
@@ -30064,7 +30049,7 @@ const LockedPoint = props => {
30064
30049
  const LockedPolygon = props => {
30065
30050
  const {
30066
30051
  points,
30067
- color,
30052
+ color: color$1,
30068
30053
  showVertices,
30069
30054
  fillStyle,
30070
30055
  strokeStyle
@@ -30075,12 +30060,21 @@ const LockedPolygon = props => {
30075
30060
  points: [...points],
30076
30061
  fillOpacity: lockedFigureFillStyles[fillStyle],
30077
30062
  strokeStyle: strokeStyle,
30078
- color: lockedFigureColors[color]
30063
+ color: lockedFigureColors[color$1]
30064
+ // We need to override the svg props if we want to have a
30065
+ // different fill color than the stroke color (specifically,
30066
+ // in the case where the fillStyle is "white").
30067
+ ,
30068
+ svgPolygonProps: {
30069
+ style: {
30070
+ fill: fillStyle === "white" ? color.white : lockedFigureColors[color$1]
30071
+ }
30072
+ }
30079
30073
  }), showVertices && points.map((point, index) => /*#__PURE__*/React.createElement(Point$2, {
30080
30074
  key: `locked-polygon-point-${index}`,
30081
30075
  x: point[X],
30082
30076
  y: point[Y],
30083
- color: lockedFigureColors[color]
30077
+ color: lockedFigureColors[color$1]
30084
30078
  })));
30085
30079
  };
30086
30080
 
@@ -30321,7 +30315,7 @@ function useDraggable(args) {
30321
30315
  gestureTarget: target,
30322
30316
  onMove,
30323
30317
  point,
30324
- constrain
30318
+ constrainKeyboardMovement
30325
30319
  } = args;
30326
30320
  const [dragging, setDragging] = React.useState(false);
30327
30321
  const {
@@ -30344,6 +30338,7 @@ function useDraggable(args) {
30344
30338
  event == null || event.stopPropagation();
30345
30339
  const isKeyboard = type.includes("key");
30346
30340
  if (isKeyboard) {
30341
+ invariant(event instanceof KeyboardEvent);
30347
30342
  event == null || event.preventDefault();
30348
30343
 
30349
30344
  // When a key is held down, we see multiple "keydown" events,
@@ -30355,6 +30350,11 @@ function useDraggable(args) {
30355
30350
  if (type === "keyup") {
30356
30351
  return;
30357
30352
  }
30353
+ if (typeof constrainKeyboardMovement === "object") {
30354
+ const destination = constrainKeyboardMovement[directionForKey[event.key]];
30355
+ onMove(destination != null ? destination : point);
30356
+ return;
30357
+ }
30358
30358
  const {
30359
30359
  direction: yDownDirection,
30360
30360
  altKey,
@@ -30375,7 +30375,7 @@ function useDraggable(args) {
30375
30375
  for (const dx of tests) {
30376
30376
  // Transform the test back into the point's coordinate system
30377
30377
  const testMovement = vec.scale(direction, dx);
30378
- const testPoint = constrain(vec.transform(vec.add(vec.transform(point, userTransform), testMovement), inverseTransform));
30378
+ const testPoint = constrainKeyboardMovement(vec.transform(vec.add(vec.transform(point, userTransform), testMovement), inverseTransform));
30379
30379
  if (vec.dist(testPoint, point) > min) {
30380
30380
  onMove(testPoint);
30381
30381
  break;
@@ -30395,7 +30395,7 @@ function useDraggable(args) {
30395
30395
  return;
30396
30396
  }
30397
30397
  const movement = vec.transform(pixelMovement, inverseViewTransform);
30398
- onMove(constrain(vec.transform(vec.add(pickup.current, movement), inverseTransform)));
30398
+ onMove(vec.transform(vec.add(pickup.current, movement), inverseTransform));
30399
30399
  }
30400
30400
  }, {
30401
30401
  target,
@@ -30407,6 +30407,12 @@ function useDraggable(args) {
30407
30407
  dragging
30408
30408
  };
30409
30409
  }
30410
+ const directionForKey = {
30411
+ ArrowLeft: "left",
30412
+ ArrowRight: "right",
30413
+ ArrowUp: "up",
30414
+ ArrowDown: "down"
30415
+ };
30410
30416
  function getInverseTransform(transform) {
30411
30417
  const invert = vec.matrixInvert(transform);
30412
30418
  invariant(invert !== null, "Could not invert transform matrix. A parent transformation matrix might be degenerative (mapping 2D space to a line).");
@@ -30456,7 +30462,7 @@ const MovablePointView = /*#__PURE__*/forwardRef((props, hitboxRef) => {
30456
30462
  range,
30457
30463
  markings,
30458
30464
  showTooltips,
30459
- hintMode
30465
+ disableKeyboardInteraction
30460
30466
  } = useGraphConfig();
30461
30467
  const {
30462
30468
  point,
@@ -30498,7 +30504,7 @@ const MovablePointView = /*#__PURE__*/forwardRef((props, hitboxRef) => {
30498
30504
  cursor
30499
30505
  },
30500
30506
  "data-testid": "movable-point",
30501
- tabIndex: hintMode ? -1 : tabIndex(focusBehavior)
30507
+ tabIndex: disableKeyboardInteraction ? -1 : tabIndex(focusBehavior)
30502
30508
  }, /*#__PURE__*/React.createElement("circle", {
30503
30509
  className: "movable-point-hitbox",
30504
30510
  r: hitboxSizePx / 2,
@@ -30587,7 +30593,7 @@ const MovableLine = props => {
30587
30593
  function useControlPoint(point, color, onMovePoint) {
30588
30594
  const {
30589
30595
  snapStep,
30590
- hintMode
30596
+ disableKeyboardInteraction
30591
30597
  } = useGraphConfig();
30592
30598
  const [focused, setFocused] = useState(false);
30593
30599
  const keyboardHandleRef = useRef(null);
@@ -30595,7 +30601,7 @@ function useControlPoint(point, color, onMovePoint) {
30595
30601
  gestureTarget: keyboardHandleRef,
30596
30602
  point,
30597
30603
  onMove: onMovePoint,
30598
- constrain: p => snap(snapStep, p)
30604
+ constrainKeyboardMovement: p => snap(snapStep, p)
30599
30605
  });
30600
30606
  const visiblePointRef = useRef(null);
30601
30607
  const {
@@ -30604,12 +30610,12 @@ function useControlPoint(point, color, onMovePoint) {
30604
30610
  gestureTarget: visiblePointRef,
30605
30611
  point,
30606
30612
  onMove: onMovePoint,
30607
- constrain: p => snap(snapStep, p)
30613
+ constrainKeyboardMovement: p => snap(snapStep, p)
30608
30614
  });
30609
30615
  const focusableHandle = /*#__PURE__*/React.createElement("g", {
30610
30616
  "data-testid": "movable-point__focusable-handle",
30611
30617
  className: "movable-point__focusable-handle",
30612
- tabIndex: hintMode ? -1 : 0,
30618
+ tabIndex: disableKeyboardInteraction ? -1 : 0,
30613
30619
  ref: keyboardHandleRef,
30614
30620
  onFocus: () => setFocused(true),
30615
30621
  onBlur: () => setFocused(false)
@@ -30643,7 +30649,7 @@ const Line$1 = props => {
30643
30649
  range,
30644
30650
  graphDimensionsInPixels,
30645
30651
  snapStep,
30646
- hintMode
30652
+ disableKeyboardInteraction
30647
30653
  } = useGraphConfig();
30648
30654
  let startExtend = undefined;
30649
30655
  let endExtend = undefined;
@@ -30661,11 +30667,11 @@ const Line$1 = props => {
30661
30667
  onMove: newPoint => {
30662
30668
  onMove(vec.sub(newPoint, start));
30663
30669
  },
30664
- constrain: p => snap(snapStep, p)
30670
+ constrainKeyboardMovement: p => snap(snapStep, p)
30665
30671
  });
30666
30672
  return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("g", {
30667
30673
  ref: line,
30668
- tabIndex: hintMode ? -1 : 0,
30674
+ tabIndex: disableKeyboardInteraction ? -1 : 0,
30669
30675
  className: "movable-line",
30670
30676
  "data-testid": "movable-line",
30671
30677
  style: {
@@ -31128,7 +31134,7 @@ function calculateBisectorPoint(point1, point2, vertex, isReflex, allowReflex, a
31128
31134
  return scaledBisector;
31129
31135
  }
31130
31136
 
31131
- const StyledMovablePoint = props => {
31137
+ const MovablePoint$1 = props => {
31132
31138
  const {
31133
31139
  snapStep
31134
31140
  } = useGraphConfig();
@@ -31138,16 +31144,15 @@ const StyledMovablePoint = props => {
31138
31144
  onMove,
31139
31145
  cursor,
31140
31146
  color: color$1 = color.blue,
31141
- snapTo
31147
+ constrain = p => snap(snapStep, p)
31142
31148
  } = props;
31143
- const snapToValue = snapTo != null ? snapTo : "grid";
31144
31149
  const {
31145
31150
  dragging
31146
31151
  } = useDraggable({
31147
31152
  gestureTarget: elementRef,
31148
31153
  point,
31149
31154
  onMove,
31150
- constrain: p => ["angles", "sides"].includes(snapToValue) ? p : snap(snapStep, p)
31155
+ constrainKeyboardMovement: constrain
31151
31156
  });
31152
31157
  return /*#__PURE__*/React.createElement(MovablePointView, {
31153
31158
  ref: elementRef,
@@ -31163,8 +31168,10 @@ const StyledMovablePoint = props => {
31163
31168
  };
31164
31169
 
31165
31170
  const PolygonGraph = props => {
31166
- const [focused, setFocused] = React.useState(false);
31167
31171
  const [hovered, setHovered] = React.useState(false);
31172
+ // This is more so required for the re-rendering that occurs when state
31173
+ // updates; specifically with regard to line weighting and polygon focus.
31174
+ const [focusVisible, setFocusVisible] = React.useState(false);
31168
31175
  const {
31169
31176
  dispatch
31170
31177
  } = props;
@@ -31174,10 +31181,10 @@ const PolygonGraph = props => {
31174
31181
  showSides,
31175
31182
  range,
31176
31183
  snapStep,
31177
- snapTo
31184
+ snapTo = "grid"
31178
31185
  } = props.graphState;
31179
31186
  const {
31180
- hintMode
31187
+ disableKeyboardInteraction
31181
31188
  } = useGraphConfig();
31182
31189
 
31183
31190
  // TODO(benchristel): can the default set of points be removed here? I don't
@@ -31185,7 +31192,7 @@ const PolygonGraph = props => {
31185
31192
  const points = coords != null ? coords : [[0, 0]];
31186
31193
  const ref = React.useRef(null);
31187
31194
  const dragReferencePoint = points[0];
31188
- const snapToValue = snapTo != null ? snapTo : "grid";
31195
+ const constrain = ["angles", "sides"].includes(snapTo) ? p => p : p => snap(snapStep, p);
31189
31196
  const {
31190
31197
  dragging
31191
31198
  } = useDraggable({
@@ -31195,16 +31202,15 @@ const PolygonGraph = props => {
31195
31202
  const delta = vec.sub(newPoint, dragReferencePoint);
31196
31203
  dispatch(actions.polygon.moveAll(delta));
31197
31204
  },
31198
- constrain: p => ["angles", "sides"].includes(snapToValue) ? p : snap(snapStep, p)
31205
+ constrainKeyboardMovement: constrain
31199
31206
  });
31200
- const active = hovered || focused || dragging;
31201
31207
  const lastMoveTime = React.useRef(0);
31202
31208
  const lines = getLines(points);
31203
31209
  return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Polygon, {
31204
31210
  points: [...points],
31205
31211
  color: "var(--movable-line-stroke-color)",
31206
31212
  svgPolygonProps: {
31207
- strokeWidth: active ? "var(--movable-line-stroke-weight-active)" : "var(--movable-line-stroke-weight)",
31213
+ strokeWidth: focusVisible ? "var(--movable-line-stroke-weight-active)" : "var(--movable-line-stroke-weight)",
31208
31214
  style: {
31209
31215
  fill: "transparent"
31210
31216
  }
@@ -31219,11 +31225,10 @@ const PolygonGraph = props => {
31219
31225
  key: "angle-" + i,
31220
31226
  centerPoint: point,
31221
31227
  endPoints: [pt1, pt2],
31222
- active: active,
31223
31228
  range: range,
31224
31229
  polygonLines: lines,
31225
31230
  showAngles: !!showAngles,
31226
- snapTo: snapToValue
31231
+ snapTo: snapTo
31227
31232
  });
31228
31233
  }), showSides && lines.map(([start, end], i) => {
31229
31234
  const [x, y] = vec.midpoint(start, end);
@@ -31238,21 +31243,29 @@ const PolygonGraph = props => {
31238
31243
  color: "transparent",
31239
31244
  svgPolygonProps: {
31240
31245
  ref,
31241
- tabIndex: hintMode ? -1 : 0,
31246
+ tabIndex: disableKeyboardInteraction ? -1 : 0,
31242
31247
  strokeWidth: TARGET_SIZE,
31243
31248
  style: {
31244
31249
  cursor: dragging ? "grabbing" : "grab",
31245
31250
  fill: hovered ? "var(--mafs-blue)" : "transparent"
31246
31251
  },
31247
- onFocus: () => setFocused(true),
31248
- onBlur: () => setFocused(false),
31249
31252
  onMouseEnter: () => setHovered(true),
31250
31253
  onMouseLeave: () => setHovered(false),
31254
+ // Required to remove line weighting when user clicks away
31255
+ // from the focused polygon
31256
+ onKeyDownCapture: () => {
31257
+ setFocusVisible(hasFocusVisible(ref.current));
31258
+ },
31259
+ // Required for lines to darken on focus
31260
+ onFocus: () => setFocusVisible(hasFocusVisible(ref.current)),
31261
+ // Required for line weighting to update on blur. Without this,
31262
+ // the user has to hover over the shape for it to update
31263
+ onBlur: () => setFocusVisible(hasFocusVisible(ref.current)),
31251
31264
  className: "movable-polygon"
31252
31265
  }
31253
- }), points.map((point, i) => /*#__PURE__*/React.createElement(StyledMovablePoint, {
31266
+ }), points.map((point, i) => /*#__PURE__*/React.createElement(MovablePoint$1, {
31254
31267
  key: "point-" + i,
31255
- snapTo: snapTo,
31268
+ constrain: constrain,
31256
31269
  point: point,
31257
31270
  onMove: destination => {
31258
31271
  const now = Date.now();
@@ -31271,6 +31284,20 @@ function getLines(points) {
31271
31284
  return [point, next];
31272
31285
  });
31273
31286
  }
31287
+ const hasFocusVisible = element => {
31288
+ const matches = selector => {
31289
+ var _element$matches;
31290
+ return (_element$matches = element == null ? void 0 : element.matches(selector)) != null ? _element$matches : false;
31291
+ };
31292
+ try {
31293
+ return matches(":focus-visible");
31294
+ } catch (e) {
31295
+ // jsdom doesn't support :focus-visible
31296
+ // (see https://github.com/jsdom/jsdom/issues/3426),
31297
+ // so the call to matches(":focus-visible") will fail in tests.
31298
+ return matches(":focus");
31299
+ }
31300
+ };
31274
31301
 
31275
31302
  function getGradableGraph(state, initialGraph) {
31276
31303
  if (!state.hasBeenInteractedWith) {
@@ -31364,7 +31391,7 @@ function CircleGraph(props) {
31364
31391
  center: center,
31365
31392
  radius: getRadius(graphState),
31366
31393
  onMove: c => dispatch(actions.circle.moveCenter(c))
31367
- }), /*#__PURE__*/React.createElement(StyledMovablePoint, {
31394
+ }), /*#__PURE__*/React.createElement(MovablePoint$1, {
31368
31395
  point: radiusPoint,
31369
31396
  cursor: "ew-resize",
31370
31397
  onMove: newRadiusPoint => {
@@ -31380,7 +31407,7 @@ function MovableCircle(props) {
31380
31407
  } = props;
31381
31408
  const {
31382
31409
  snapStep,
31383
- hintMode
31410
+ disableKeyboardInteraction
31384
31411
  } = useGraphConfig();
31385
31412
  const draggableRef = useRef(null);
31386
31413
  const {
@@ -31389,13 +31416,13 @@ function MovableCircle(props) {
31389
31416
  gestureTarget: draggableRef,
31390
31417
  point: center,
31391
31418
  onMove,
31392
- constrain: p => snap(snapStep, p)
31419
+ constrainKeyboardMovement: p => snap(snapStep, p)
31393
31420
  });
31394
31421
  const [centerPx] = useTransformVectorsToPixels(center);
31395
31422
  const [radiiPx] = useTransformDimensionsToPixels([radius, radius]);
31396
31423
  return /*#__PURE__*/React.createElement("g", {
31397
31424
  ref: draggableRef,
31398
- tabIndex: hintMode ? -1 : 0,
31425
+ tabIndex: disableKeyboardInteraction ? -1 : 0,
31399
31426
  className: `movable-circle ${dragging ? "movable-circle--dragging" : ""}`
31400
31427
  }, /*#__PURE__*/React.createElement("ellipse", {
31401
31428
  className: "focus-ring",
@@ -31476,7 +31503,7 @@ function QuadraticGraph(props) {
31476
31503
  return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Plot$3.OfX, {
31477
31504
  y: y,
31478
31505
  color: color.blue
31479
- }), coords.map((coord, i) => /*#__PURE__*/React.createElement(StyledMovablePoint, {
31506
+ }), coords.map((coord, i) => /*#__PURE__*/React.createElement(MovablePoint$1, {
31480
31507
  key: "point-" + i,
31481
31508
  point: coord,
31482
31509
  onMove: destination => dispatch(actions.quadratic.movePoint(i, destination))
@@ -31536,7 +31563,7 @@ function SinusoidGraph(props) {
31536
31563
  return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Plot$3.OfX, {
31537
31564
  y: x => computeSine(x, coeffRef.current),
31538
31565
  color: color.blue
31539
- }), coords.map((coord, i) => /*#__PURE__*/React.createElement(StyledMovablePoint, {
31566
+ }), coords.map((coord, i) => /*#__PURE__*/React.createElement(MovablePoint$1, {
31540
31567
  key: "point-" + i,
31541
31568
  point: coord,
31542
31569
  onMove: destination => dispatch(actions.sinusoid.movePoint(i, destination))
@@ -31592,7 +31619,6 @@ function AngleGraph(props) {
31592
31619
  showAngles,
31593
31620
  range,
31594
31621
  allowReflexAngles,
31595
- angleOffsetDeg,
31596
31622
  snapDegrees
31597
31623
  } = graphState;
31598
31624
 
@@ -31630,8 +31656,6 @@ function AngleGraph(props) {
31630
31656
  coords: endPoints,
31631
31657
  allowReflexAngles: allowReflexAngles || false,
31632
31658
  // Whether to allow reflex angles or not
31633
- angleOffsetDeg: angleOffsetDeg || 0,
31634
- // The angle offset from the x-axis
31635
31659
  snapDegrees: snapDegrees || 1,
31636
31660
  // The multiple of degrees to snap to
31637
31661
  range: range,
@@ -31639,19 +31663,66 @@ function AngleGraph(props) {
31639
31663
  };
31640
31664
 
31641
31665
  // Render the lines, angle, and then movable points
31642
- return /*#__PURE__*/React.createElement(React.Fragment, null, svgLines, /*#__PURE__*/React.createElement(Angle, angleParams), coords.map((point, i) => /*#__PURE__*/React.createElement(StyledMovablePoint, {
31643
- key: "point-" + i,
31644
- snapTo: "angles",
31645
- point: point,
31646
- onMove: destination => dispatch(actions.angle.movePoint(i, destination))
31647
- })));
31666
+ return /*#__PURE__*/React.createElement(React.Fragment, null, svgLines, /*#__PURE__*/React.createElement(Angle, angleParams), /*#__PURE__*/React.createElement(MovablePoint$1, {
31667
+ point: coords[1],
31668
+ constrain: p => p,
31669
+ onMove: destination => dispatch(actions.angle.movePoint(1, destination))
31670
+ }), /*#__PURE__*/React.createElement(MovablePoint$1, {
31671
+ point: coords[0],
31672
+ constrain: getAngleSideConstraint(coords[0], coords[1], snapDegrees || 1),
31673
+ onMove: destination => dispatch(actions.angle.movePoint(0, destination))
31674
+ }), /*#__PURE__*/React.createElement(MovablePoint$1, {
31675
+ point: coords[2],
31676
+ constrain: getAngleSideConstraint(coords[2], coords[1], snapDegrees || 1),
31677
+ onMove: destination => dispatch(actions.angle.movePoint(2, destination))
31678
+ }));
31679
+ }
31680
+ const positiveX = [1, 0];
31681
+ const negativeX = [-1, 0];
31682
+ const positiveY = [0, 1];
31683
+ const negativeY = [0, -1];
31684
+ function getAngleSideConstraint(sidePoint, vertex, snapDegrees) {
31685
+ var _findIntersectionOfRa, _findIntersectionOfRa2, _findIntersectionOfRa3, _findIntersectionOfRa4;
31686
+ const currentAngle = calculateAngleInDegrees(vec.sub(sidePoint, vertex));
31687
+
31688
+ // Find the rays that start at the current point and point up, down, left
31689
+ // and right.
31690
+ const leftRay = [sidePoint, vec.add(sidePoint, negativeX)];
31691
+ const rightRay = [sidePoint, vec.add(sidePoint, positiveX)];
31692
+ const upRay = [sidePoint, vec.add(sidePoint, positiveY)];
31693
+ const downRay = [sidePoint, vec.add(sidePoint, negativeY)];
31694
+
31695
+ // find the angles that lie one snap step clockwise and counter-clockwise
31696
+ // from the current angle. These are the angles to which the side can be
31697
+ // moved.
31698
+ const oneStepCounterClockwise = currentAngle + snapDegrees;
31699
+ const oneStepClockwise = currentAngle - snapDegrees;
31700
+
31701
+ // find the rays that start from the vertex and point in the direction of
31702
+ // the angles we just computed.
31703
+ const counterClockwiseRay = [vertex, vec.add(vertex, polar(1, oneStepCounterClockwise))];
31704
+ const clockwiseRay = [vertex, vec.add(vertex, polar(1, oneStepClockwise))];
31705
+
31706
+ // find the intersections of those rays with the horizontal and vertical
31707
+ // rays extending from the sidePoint. These intersections are the points to
31708
+ // which sidePoint can move that will result in a rotation of `snapDegrees`.
31709
+ const left = (_findIntersectionOfRa = findIntersectionOfRays(leftRay, counterClockwiseRay)) != null ? _findIntersectionOfRa : findIntersectionOfRays(leftRay, clockwiseRay);
31710
+ const right = (_findIntersectionOfRa2 = findIntersectionOfRays(rightRay, counterClockwiseRay)) != null ? _findIntersectionOfRa2 : findIntersectionOfRays(rightRay, clockwiseRay);
31711
+ const up = (_findIntersectionOfRa3 = findIntersectionOfRays(upRay, counterClockwiseRay)) != null ? _findIntersectionOfRa3 : findIntersectionOfRays(upRay, clockwiseRay);
31712
+ const down = (_findIntersectionOfRa4 = findIntersectionOfRays(downRay, counterClockwiseRay)) != null ? _findIntersectionOfRa4 : findIntersectionOfRays(downRay, clockwiseRay);
31713
+ return {
31714
+ up: up != null ? up : sidePoint,
31715
+ down: down != null ? down : sidePoint,
31716
+ left: left != null ? left : sidePoint,
31717
+ right: right != null ? right : sidePoint
31718
+ };
31648
31719
  }
31649
31720
 
31650
31721
  function PointGraph(props) {
31651
31722
  const {
31652
31723
  dispatch
31653
31724
  } = props;
31654
- return /*#__PURE__*/React.createElement(React.Fragment, null, props.graphState.coords.map((point, i) => /*#__PURE__*/React.createElement(StyledMovablePoint, {
31725
+ return /*#__PURE__*/React.createElement(React.Fragment, null, props.graphState.coords.map((point, i) => /*#__PURE__*/React.createElement(MovablePoint$1, {
31655
31726
  key: i,
31656
31727
  point: point,
31657
31728
  onMove: destination => dispatch(actions.pointGraph.movePoint(i, destination))
@@ -31683,7 +31754,7 @@ function Protractor() {
31683
31754
  gestureTarget: draggableRef,
31684
31755
  onMove: setCenter,
31685
31756
  point: center,
31686
- constrain: point => bound$1({
31757
+ constrainKeyboardMovement: point => bound$1({
31687
31758
  snapStep,
31688
31759
  range,
31689
31760
  point
@@ -31799,7 +31870,8 @@ const MafsGraph = props => {
31799
31870
  state,
31800
31871
  dispatch,
31801
31872
  labels,
31802
- hintMode
31873
+ hintMode,
31874
+ readOnly
31803
31875
  } = props;
31804
31876
  const [width, height] = props.box;
31805
31877
  const tickStep = props.step;
@@ -31815,7 +31887,7 @@ const MafsGraph = props => {
31815
31887
  width,
31816
31888
  height,
31817
31889
  labels,
31818
- hintMode
31890
+ disableKeyboardInteraction: hintMode || readOnly
31819
31891
  }
31820
31892
  }, /*#__PURE__*/React.createElement(View, {
31821
31893
  className: "mafs-graph",
@@ -32173,6 +32245,9 @@ const getAngleCoords = params => {
32173
32245
  if (graph.coords) {
32174
32246
  return graph.coords;
32175
32247
  }
32248
+ if (graph.startCoords) {
32249
+ return graph.startCoords;
32250
+ }
32176
32251
  const {
32177
32252
  snapDegrees,
32178
32253
  angleOffsetDeg
@@ -32202,6 +32277,7 @@ const getAngleCoords = params => {
32202
32277
  return coords;
32203
32278
  };
32204
32279
 
32280
+ const minDistanceBetweenAngleVertexAndSidePoint = 2;
32205
32281
  function interactiveGraphReducer(state, action) {
32206
32282
  switch (action.type) {
32207
32283
  case REINITIALIZE:
@@ -32367,21 +32443,28 @@ function doMovePoint(state, action) {
32367
32443
  case "angle":
32368
32444
  // If the index is 1, we are moving the vertex of the angle,
32369
32445
  // which will move the other two points as well
32370
- if (action.index === 1) {
32371
- const updatedCoords = boundAndSnapAngleVertex(state, action);
32446
+ const newState = (() => {
32447
+ if (action.index === 1) {
32448
+ const updatedCoords = boundAndSnapAngleVertex(state, action);
32449
+ return _extends({}, state, {
32450
+ hasBeenInteractedWith: true,
32451
+ coords: updatedCoords
32452
+ });
32453
+ }
32372
32454
  return _extends({}, state, {
32373
32455
  hasBeenInteractedWith: true,
32374
- coords: updatedCoords
32456
+ coords: setAtIndex({
32457
+ array: state.coords,
32458
+ index: action.index,
32459
+ newValue: boundAndSnapAngleEndPoints(action.destination, state, action.index)
32460
+ })
32375
32461
  });
32462
+ })();
32463
+ if (angleSidePointsTooCloseToVertex(newState)) {
32464
+ // cancel the move
32465
+ return state;
32376
32466
  }
32377
- return _extends({}, state, {
32378
- hasBeenInteractedWith: true,
32379
- coords: setAtIndex({
32380
- array: state.coords,
32381
- index: action.index,
32382
- newValue: boundAndSnapAngleEndPoints(action.destination, state, action.index)
32383
- })
32384
- });
32467
+ return newState;
32385
32468
  case "polygon":
32386
32469
  let newValue;
32387
32470
  if (state.snapTo === "sides") {
@@ -32470,11 +32553,7 @@ function doMoveCenter(state, action) {
32470
32553
  case "circle":
32471
32554
  {
32472
32555
  // Constrain the center of the circle to the chart range
32473
- const constrainedCenter = bound$1({
32474
- snapStep: state.snapStep,
32475
- range: state.range,
32476
- point: action.destination
32477
- });
32556
+ const constrainedCenter = boundAndSnapToGrid(action.destination, state);
32478
32557
 
32479
32558
  // Reposition the radius point based on the new center
32480
32559
  // (spread to make sure we're not going to mutate anything)
@@ -32508,10 +32587,10 @@ function doMoveRadiusPoint(state, action) {
32508
32587
  case "circle":
32509
32588
  {
32510
32589
  const [xMin, xMax] = state.range[X];
32511
- const nextRadiusPoint = [
32590
+ const nextRadiusPoint = snap(state.snapStep, [
32512
32591
  // Constrain to graph range
32513
32592
  // The +0 is to convert -0 to +0
32514
- clamp(action.destination[X] + 0, xMin, xMax), state.center[1]];
32593
+ clamp(action.destination[X] + 0, xMin, xMax), state.center[1]]);
32515
32594
  if (_.isEqual(nextRadiusPoint, state.center)) {
32516
32595
  return state;
32517
32596
  }
@@ -32593,17 +32672,16 @@ function boundAndSnapAngleVertex({
32593
32672
 
32594
32673
  // Get the current and upcoming positions of the vertex
32595
32674
  const startingVertex = coordsCopy[1];
32596
- const newVertex = bound$1({
32597
- snapStep,
32598
- range,
32599
- point: snap(snapStep, destination)
32600
- });
32675
+
32676
+ // Prevent the vertex from getting too close to the edge, so that all
32677
+ // points of the angle can fit within the graph bounds.
32678
+ const insetAmount = vec.add(snapStep, [minDistanceBetweenAngleVertexAndSidePoint, minDistanceBetweenAngleVertexAndSidePoint]);
32679
+ const newVertex = clampToBox(inset(insetAmount, range), snap(snapStep, destination));
32601
32680
 
32602
32681
  // Get the vector from the starting vertex to the new vertex
32603
32682
  const delta = vec.add(newVertex, reverseVector(startingVertex));
32604
32683
 
32605
32684
  // Apply the delta to each of the other two points so that the angle is maintained
32606
- let valid = true;
32607
32685
  const newPoints = {};
32608
32686
  for (const i of [0, 2]) {
32609
32687
  const oldPoint = coordsCopy[i];
@@ -32612,21 +32690,13 @@ function boundAndSnapAngleVertex({
32612
32690
  angle *= Math.PI / 180;
32613
32691
  newPoint = constrainToBoundsOnAngle(newPoint, angle, range, snapStep);
32614
32692
  newPoints[i] = newPoint;
32615
-
32616
- // Check if the new point is too close to the vertex
32617
- if (tooClose(newVertex, newPoint)) {
32618
- valid = false;
32619
- }
32620
32693
  }
32621
32694
 
32622
32695
  // Update the vertex after snapping to the snapStep
32623
32696
  newPoints[1] = newVertex;
32624
- // Only move points if all new positions are valid
32625
- if (valid) {
32626
- Object.entries(newPoints).forEach(([i, newPoint]) => {
32627
- coordsCopy[i] = newPoint;
32628
- });
32629
- }
32697
+ Object.entries(newPoints).forEach(([i, newPoint]) => {
32698
+ coordsCopy[i] = newPoint;
32699
+ });
32630
32700
  return coordsCopy;
32631
32701
  }
32632
32702
 
@@ -32691,10 +32761,17 @@ function boundAndSnapAngleEndPoints(destinationPoint, {
32691
32761
 
32692
32762
  // Snap the angle to the nearest multiple of snapDegrees (if provided)
32693
32763
  angle = Math.round((angle - offsetDegrees) / snap) * snap + offsetDegrees;
32694
- const distance = vec.dist(coordsCopy[index], vertex);
32764
+
32765
+ // add 0.01 to prevent rounding errors from causing the point to snap to
32766
+ // a location that is too close to the vertex.
32767
+ const minDistance = minDistanceBetweenAngleVertexAndSidePoint + 0.01;
32768
+ const distance = Math.max(vec.dist(coordsCopy[index], vertex), minDistance);
32695
32769
  const snappedValue = vec.add(vertex, polar(distance, angle));
32696
32770
  return snappedValue;
32697
32771
  }
32772
+ function angleSidePointsTooCloseToVertex(state) {
32773
+ return tooClose(state.coords[0], state.coords[1], state.range) || tooClose(state.coords[2], state.coords[1], state.range);
32774
+ }
32698
32775
  function boundAndSnapToPolygonAngle(destinationPoint, {
32699
32776
  range,
32700
32777
  coords
@@ -32917,7 +32994,12 @@ const StatefulMafsGraph = /*#__PURE__*/React.forwardRef((props, ref) => {
32917
32994
  useEffect(() => {
32918
32995
  dispatch(changeRange([[xMinRange, xMaxRange], [yMinRange, yMaxRange]]));
32919
32996
  }, [dispatch, xMinRange, xMaxRange, yMinRange, yMaxRange]);
32997
+
32998
+ // Update the graph whenever any of the following values changes.
32999
+ // This is necessary to keep the graph previews in sync with the updated
33000
+ // graph settings within the interative graph editor.
32920
33001
  const numSegments = graph.type === "segment" ? graph.numSegments : null;
33002
+ const numPoints = graph.type === "point" ? graph.numPoints : null;
32921
33003
  const numSides = graph.type === "polygon" ? graph.numSides : null;
32922
33004
  const snapTo = graph.type === "polygon" ? graph.snapTo : null;
32923
33005
  const showAngles = graph.type === "polygon" ? graph.showAngles : null;
@@ -32932,7 +33014,7 @@ const StatefulMafsGraph = /*#__PURE__*/React.forwardRef((props, ref) => {
32932
33014
  if (latestPropsRef.current !== originalPropsRef.current) {
32933
33015
  dispatch(reinitialize(latestPropsRef.current));
32934
33016
  }
32935
- }, [graph.type, numSegments, numSides, snapTo, showAngles, showSides, latestPropsRef, graph.startCoords]);
33017
+ }, [graph.type, numPoints, numSegments, numSides, snapTo, showAngles, showSides, latestPropsRef, graph.startCoords]);
32936
33018
  return /*#__PURE__*/React.createElement(MafsGraph, _extends({}, props, {
32937
33019
  state: state,
32938
33020
  dispatch: dispatch
@@ -34242,6 +34324,7 @@ class InteractiveGraph extends React.Component {
34242
34324
  // Mafs shim
34243
34325
  const mafsFlags = (_this$props$apiOption = this.props.apiOptions) == null || (_this$props$apiOption = _this$props$apiOption.flags) == null ? void 0 : _this$props$apiOption["mafs"];
34244
34326
  if (shouldUseMafs(mafsFlags, this.props.graph)) {
34327
+ var _this$props$apiOption2;
34245
34328
  const box = getInteractiveBoxFromSizeClass(this.props.containerSizeClass);
34246
34329
  const gridStep = this.props.gridStep || Util.getGridStep(this.props.range, this.props.step, box[0]);
34247
34330
  const snapStep = this.props.snapStep || Util.snapStepFromGridStep(gridStep);
@@ -34251,7 +34334,8 @@ class InteractiveGraph extends React.Component {
34251
34334
  snapStep: snapStep,
34252
34335
  box: box,
34253
34336
  showTooltips: !!this.props.showTooltips,
34254
- hintMode: this.props.hintMode
34337
+ hintMode: this.props.hintMode,
34338
+ readOnly: (_this$props$apiOption2 = this.props.apiOptions) == null ? void 0 : _this$props$apiOption2.readOnly
34255
34339
  }));
34256
34340
  }
34257
34341
  return /*#__PURE__*/React.createElement(LegacyInteractiveGraph, _extends({
@@ -44170,7 +44254,7 @@ const init = function init(options) {
44170
44254
 
44171
44255
  // This file is processed by a Rollup plugin (replace) to inject the production
44172
44256
  const libName = "@khanacademy/perseus";
44173
- const libVersion = "27.2.0";
44257
+ const libVersion = "28.0.0";
44174
44258
  addLibraryVersionToPerseusDebug(libName, libVersion);
44175
44259
 
44176
44260
  const apiVersion = {
@@ -44725,7 +44809,6 @@ const context = /*#__PURE__*/React.createContext(defaultContext);
44725
44809
  const {
44726
44810
  mapObject
44727
44811
  } = objective_;
44728
- /* eslint-disable-next-line react/no-unsafe */
44729
44812
  class ServerItemRenderer extends React.Component {
44730
44813
  constructor(props) {
44731
44814
  super(props);
@@ -46091,7 +46174,16 @@ class MultiRenderer extends React.Component {
46091
46174
  // _annotateRendererArray() needs this
46092
46175
  ref: null,
46093
46176
  makeRenderer: () => /*#__PURE__*/React.createElement(HintsRenderer, _extends({}, this._getRendererProps(), {
46094
- findExternalWidgets: findExternalWidgets,
46177
+ findExternalWidgets: findExternalWidgets
46178
+ // Note(Jeremy): The MultiRenderer codebase types are
46179
+ // slightly different from the rest of the codebase.
46180
+ // Ideally `HintNode` would spread the `Hint` type into it,
46181
+ // but there is a difference in optionality of `widgets`
46182
+ // and `images` and that causes a huge cascade of type
46183
+ // errors (they are truly optional, but most of our code
46184
+ // assumes they're provided/set). Ignoring this for now.
46185
+ // @ts-expect-error - TS2769 - Type 'HintNode' is not assignable to type 'Hint'.
46186
+ ,
46095
46187
  hints: [hint]
46096
46188
  }))
46097
46189
  };
@@ -47750,5 +47842,5 @@ const EditorJsonify = {
47750
47842
  }
47751
47843
  };
47752
47844
 
47753
- export { ApiOptions, ArrowPicker, ArticleRenderer, BaseRadio, Categorizer$1 as Categorizer, changeable as Changeable, ClassNames, ColorPicker, DashPicker, dependencies as Dependencies, EditorJsonify, ElementContainer, Expression, util as GrapherUtil, Grapher$1 as GrapherWidget, HintRenderer, HintsRenderer, InputNumber$1 as InputNumber, InteractiveGraph$1 as InteractiveGraphWidget, ItemExtras, JiptParagraphs, KhanColors, KhanMath, context as LoadingContext, Log, Matrix$1 as MatrixWidget, MultiItems, NumericInput$1 as NumericInput, PerseusExpressionAnswerFormConsidered, PerseusI18nContext, PerseusI18nContextProvider, PerseusMarkdown, Plotter$1 as PlotterWidget, QUESTION_WIDGETS, Radio, Renderer, ref as ServerItemRenderer, sizingUtils as SizingUtils, Table$1 as TableWidget, Util, denylist as WIDGET_PROP_DENYLIST, WidgetJsonifyDeprecated, widgets$1 as Widgets, addWidget, apiVersion, buildEmptyItemTreeForShape, buildMapper, components, constants, contentHasWidgetType, convertWidgetNameToEnum, displaySigFigs, generateTestCategorizerWidget, generateTestExpressionWidget, generateTestInteractiveGraphWidget, generateTestNumericInputWidget, generateTestPerseusItem, generateTestRadioWidget, genericPerseusItemData, getAnswerFromUserInput, getAnswersFromWidgets, getCircleCoords, getCorrectAnswerForWidgetId, getLineCoords, getLinearSystemCoords, getQuadraticCoords, getSegmentCoords, getSinusoidCoords, getValidWidgetIds, getWidgetFromWidgetMap, getWidgetIdsFromContent, getWidgetIdsFromContentByType, getWidgetTypeByWidgetId, getWidgetsFromWidgetMap, getWidgetsMapFromItemData, globalConstants, globalStyles, iconPaths as icons, init, injectWidgets, interactiveSizes$1 as interactiveSizes, isItemRenderableByVersion, isWidgetIdInContent, isWrongAnswerSupported, itemDataVersion, itemToTree, ItemVersion as itemVersion, libVersion, lockedFigureColors, lockedFigureFillStyles, mediaQueries, parsePerseusItem, plotterPlotTypes, preprocessTex, registerAllWidgetsForTesting, shapes, shouldHaveIndividualAnswer, traverse, usePerseusI18n, violatingWidgets, allWidgets as widgets };
47845
+ export { ApiOptions, ArrowPicker, ArticleRenderer, BaseRadio, Categorizer$1 as Categorizer, changeable as Changeable, ClassNames, ColorPicker, DashPicker, dependencies as Dependencies, EditorJsonify, ElementContainer, Expression, util as GrapherUtil, Grapher$1 as GrapherWidget, HintRenderer, HintsRenderer, InputNumber$1 as InputNumber, InteractiveGraph$1 as InteractiveGraphWidget, ItemExtras, JiptParagraphs, KhanColors, KhanMath, context as LoadingContext, Log, Matrix$1 as MatrixWidget, MultiItems, NumericInput$1 as NumericInput, PerseusExpressionAnswerFormConsidered, PerseusI18nContext, PerseusI18nContextProvider, PerseusMarkdown, Plotter$1 as PlotterWidget, QUESTION_WIDGETS, Radio, Renderer, ref as ServerItemRenderer, sizingUtils as SizingUtils, Table$1 as TableWidget, Util, denylist as WIDGET_PROP_DENYLIST, WidgetJsonifyDeprecated, widgets$1 as Widgets, addWidget, apiVersion, buildEmptyItemTreeForShape, buildMapper, components, constants, contentHasWidgetType, convertWidgetNameToEnum, displaySigFigs, generateTestCategorizerWidget, generateTestExpressionWidget, generateTestInteractiveGraphWidget, generateTestNumericInputWidget, generateTestPerseusItem, generateTestRadioWidget, genericPerseusItemData, getAngleCoords, getAnswerFromUserInput, getAnswersFromWidgets, getCircleCoords, getCorrectAnswerForWidgetId, getLineCoords, getLinearSystemCoords, getPointCoords, getPolygonCoords, getQuadraticCoords, getSegmentCoords, getSinusoidCoords, getValidWidgetIds, getWidgetFromWidgetMap, getWidgetIdsFromContent, getWidgetIdsFromContentByType, getWidgetTypeByWidgetId, getWidgetsFromWidgetMap, getWidgetsMapFromItemData, globalConstants, globalStyles, iconPaths as icons, init, injectWidgets, interactiveSizes$1 as interactiveSizes, isItemRenderableByVersion, isWidgetIdInContent, isWrongAnswerSupported, itemDataVersion, itemToTree, ItemVersion as itemVersion, libVersion, lockedFigureColors, lockedFigureFillStyles, mediaQueries, parsePerseusItem, plotterPlotTypes, preprocessTex, registerAllWidgetsForTesting, shapes, shouldHaveIndividualAnswer, traverse, usePerseusI18n, violatingWidgets, allWidgets as widgets };
47754
47846
  //# sourceMappingURL=index.js.map