@khanacademy/perseus 27.2.0 → 28.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 +452 -362
  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 +438 -348
  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 +8 -7
  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/index.js CHANGED
@@ -18,6 +18,7 @@ var Clickable = require('@khanacademy/wonder-blocks-clickable');
18
18
  var wonderBlocksPopover = require('@khanacademy/wonder-blocks-popover');
19
19
  var wonderBlocksTokens = require('@khanacademy/wonder-blocks-tokens');
20
20
  var $ = require('jquery');
21
+ var uuid = require('uuid');
21
22
  var PropTypes = require('prop-types');
22
23
  var kmath = require('@khanacademy/kmath');
23
24
  var wonderStuffCore = require('@khanacademy/wonder-stuff-core');
@@ -35,11 +36,11 @@ var debounce$1 = require('lodash.debounce');
35
36
  var mafs = require('mafs');
36
37
  var wonderBlocksIcon = require('@khanacademy/wonder-blocks-icon');
37
38
  var questionIcon = require('@phosphor-icons/core/regular/question.svg');
38
- var createReactClass = require('create-react-class');
39
39
  var react = require('@use-gesture/react');
40
40
  var Switch = require('@khanacademy/wonder-blocks-switch');
41
41
  var Pill = require('@khanacademy/wonder-blocks-pill');
42
42
  var reactPopper = require('react-popper');
43
+ var createReactClass = require('create-react-class');
43
44
  var Link = require('@khanacademy/wonder-blocks-link');
44
45
 
45
46
  function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
@@ -81,9 +82,9 @@ var caretDown__default = /*#__PURE__*/_interopDefaultLegacy(caretDown);
81
82
  var caretUp__default = /*#__PURE__*/_interopDefaultLegacy(caretUp);
82
83
  var debounce__default = /*#__PURE__*/_interopDefaultLegacy(debounce$1);
83
84
  var questionIcon__default = /*#__PURE__*/_interopDefaultLegacy(questionIcon);
84
- var createReactClass__default = /*#__PURE__*/_interopDefaultLegacy(createReactClass);
85
85
  var Switch__default = /*#__PURE__*/_interopDefaultLegacy(Switch);
86
86
  var Pill__default = /*#__PURE__*/_interopDefaultLegacy(Pill);
87
+ var createReactClass__default = /*#__PURE__*/_interopDefaultLegacy(createReactClass);
87
88
  var Link__default = /*#__PURE__*/_interopDefaultLegacy(Link);
88
89
 
89
90
  function _extends() {
@@ -354,7 +355,9 @@ const mockStrings = {
354
355
  loading: "Loading...",
355
356
  videoTranscript: "See video transcript",
356
357
  somethingWrong: "Something went wrong.",
357
- videoWrapper: "Khan Academy video wrapper"
358
+ videoWrapper: "Khan Academy video wrapper",
359
+ mathInputTitle: "mathematics keyboard",
360
+ mathInputDescription: "Use keyboard/mouse to interact with math-based input fields"
358
361
  };
359
362
 
360
363
  /**
@@ -386,6 +389,26 @@ function PerseusI18nContextProvider(_ref) {
386
389
  }
387
390
  const usePerseusI18n = () => React.useContext(PerseusI18nContext);
388
391
 
392
+ /*
393
+ * A11y (Accessibility) style rules
394
+ * These are used to mark up areas on the site that should only be readable
395
+ * when using a screen reader.
396
+ */
397
+ var a11y = aphrodite.StyleSheet.create({
398
+ // Make content only visible to screen readers.
399
+ // Both collegeboard.org and Bootstrap 3 use this exact implementation.
400
+ srOnly: {
401
+ border: 0,
402
+ clip: "rect(0,0,0,0)",
403
+ height: 1,
404
+ margin: -1,
405
+ overflow: "hidden",
406
+ padding: 0,
407
+ position: "absolute",
408
+ width: 1
409
+ }
410
+ });
411
+
389
412
  const debounce = (func, delay) => {
390
413
  let timer = null;
391
414
  return function () {
@@ -588,11 +611,12 @@ class InnerMathInput extends React__namespace.Component {
588
611
  "mq-editable-field": true,
589
612
  "mq-math-mode": true
590
613
  });
614
+ const popoverContentUniqueId = uuid.v4().slice(0, 8);
591
615
  if (this.props.className) {
592
616
  className = className + " " + this.props.className;
593
617
  }
594
618
  return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
595
- style: [styles$E.outerWrapper, this.state.focused && styles$E.wrapperFocused, this.props.hasError && styles$E.wrapperError]
619
+ style: [styles$D.outerWrapper, this.state.focused && styles$D.wrapperFocused, this.props.hasError && styles$D.wrapperError]
596
620
  }, /*#__PURE__*/React__namespace.createElement("div", {
597
621
  style: {
598
622
  display: "flex",
@@ -620,16 +644,21 @@ class InnerMathInput extends React__namespace.Component {
620
644
  opened: this.state.keypadOpen,
621
645
  onClose: () => this.closeKeypad(),
622
646
  dismissEnabled: true,
623
- content: () => /*#__PURE__*/React__namespace.createElement(wonderBlocksPopover.PopoverContentCore, {
647
+ "aria-label": this.context.strings.mathInputTitle,
648
+ "aria-describedby": `popover-content-${popoverContentUniqueId}`,
649
+ content: () => /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(wonderBlocksTypography.HeadingMedium, {
650
+ id: `popover-content-${popoverContentUniqueId}`,
651
+ style: a11y.srOnly
652
+ }, this.context.strings.mathInputDescription), /*#__PURE__*/React__namespace.createElement(wonderBlocksPopover.PopoverContentCore, {
624
653
  closeButtonVisible: true,
625
- style: styles$E.popoverContent
654
+ style: styles$D.popoverContent
626
655
  }, /*#__PURE__*/React__namespace.createElement(mathInput.DesktopKeypad, _extends({
627
656
  onAnalyticsEvent: this.props.analytics.onAnalyticsEvent,
628
657
  extraKeys: this.props.extraKeys,
629
658
  onClickKey: this.handleKeypadPress,
630
659
  cursorContext: this.state.cursorContext,
631
660
  convertDotToTimes: this.props.convertDotToTimes
632
- }, this.props.keypadButtonSets ?? mapButtonSets(this.props?.buttonSets))))
661
+ }, this.props.keypadButtonSets ?? mapButtonSets(this.props?.buttonSets)))))
633
662
  }, this.props.buttonsVisible === "never" ? /*#__PURE__*/React__namespace.createElement(MathInputIcon, {
634
663
  hovered: false,
635
664
  focused: false,
@@ -687,9 +716,9 @@ const MathInputIcon = _ref => {
687
716
  fillColor = wonderBlocksTokens.color.offBlack;
688
717
  break;
689
718
  }
690
- const dynamicClass = active || focused ? styles$E.iconActive : styles$E.iconInactive;
719
+ const dynamicClass = active || focused ? styles$D.iconActive : styles$D.iconInactive;
691
720
  return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
692
- style: [styles$E.iconContainer, dynamicClass]
721
+ style: [styles$D.iconContainer, dynamicClass]
693
722
  }, /*#__PURE__*/React__namespace.createElement("svg", {
694
723
  xmlns: "http://www.w3.org/2000/svg",
695
724
  width: "16",
@@ -734,7 +763,7 @@ const inputFocused = {
734
763
  borderColor: wonderBlocksTokens.color.blue,
735
764
  margin: -1
736
765
  };
737
- const styles$E = aphrodite.StyleSheet.create({
766
+ const styles$D = aphrodite.StyleSheet.create({
738
767
  iconContainer: {
739
768
  display: "flex",
740
769
  justifyContent: "center",
@@ -743,9 +772,11 @@ const styles$E = aphrodite.StyleSheet.create({
743
772
  borderRadius: 1
744
773
  },
745
774
  iconInactive: {
775
+ border: "2px solid transparent",
746
776
  backgroundColor: wonderBlocksTokens.color.offBlack8
747
777
  },
748
778
  iconActive: {
779
+ border: `2px solid ${wonderBlocksTokens.color.white}`,
749
780
  backgroundColor: wonderBlocksTokens.color.offBlack64
750
781
  },
751
782
  outerWrapper: {
@@ -1263,26 +1294,6 @@ const ClassNames = {
1263
1294
  MOBILE: "perseus-mobile"
1264
1295
  };
1265
1296
 
1266
- /*
1267
- * A11y (Accessibility) style rules
1268
- * These are used to mark up areas on the site that should only be readable
1269
- * when using a screen reader.
1270
- */
1271
- var a11y = aphrodite.StyleSheet.create({
1272
- // Make content only visible to screen readers.
1273
- // Both collegeboard.org and Bootstrap 3 use this exact implementation.
1274
- srOnly: {
1275
- border: 0,
1276
- clip: "rect(0,0,0,0)",
1277
- height: 1,
1278
- margin: -1,
1279
- overflow: "hidden",
1280
- padding: 0,
1281
- position: "absolute",
1282
- width: 1
1283
- }
1284
- });
1285
-
1286
1297
  const KhanMath = {
1287
1298
  // Simplify formulas before display
1288
1299
  cleanMath: function (expr) {
@@ -2587,7 +2598,7 @@ class Expression extends React__namespace.Component {
2587
2598
  render() {
2588
2599
  if (this.props.apiOptions.customKeypad) {
2589
2600
  return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
2590
- className: aphrodite.css(styles$D.mobileLabelInputWrapper)
2601
+ className: aphrodite.css(styles$C.mobileLabelInputWrapper)
2591
2602
  }, !!this.props.visibleLabel && /*#__PURE__*/React__namespace.createElement(wonderBlocksTypography.LabelSmall, {
2592
2603
  htmlFor: this._textareaId,
2593
2604
  tag: "label"
@@ -2621,7 +2632,7 @@ class Expression extends React__namespace.Component {
2621
2632
  ERROR_TITLE
2622
2633
  } = this.context.strings;
2623
2634
  return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
2624
- className: aphrodite.css(styles$D.desktopLabelInputWrapper)
2635
+ className: aphrodite.css(styles$C.desktopLabelInputWrapper)
2625
2636
  }, !!this.props.visibleLabel && /*#__PURE__*/React__namespace.createElement(wonderBlocksTypography.LabelSmall, {
2626
2637
  htmlFor: this._textareaId,
2627
2638
  tag: "label"
@@ -2662,7 +2673,7 @@ class Expression extends React__namespace.Component {
2662
2673
  }))));
2663
2674
  }
2664
2675
  }
2665
- const styles$D = aphrodite.StyleSheet.create({
2676
+ const styles$C = aphrodite.StyleSheet.create({
2666
2677
  mobileLabelInputWrapper: {
2667
2678
  padding: "15px 4px 0"
2668
2679
  },
@@ -9039,8 +9050,8 @@ function findChildOrAdd(elem, className) {
9039
9050
  return $child;
9040
9051
  }
9041
9052
  var Tex$1 = {
9042
- // Process a node and add math inside of it. This attempts to use KaTeX to
9043
- // format the math, and if that fails it falls back to MathJax.
9053
+ // Process a node and add math inside of it. This uses MathJax to
9054
+ // format the math.
9044
9055
  //
9045
9056
  // elem: The element which the math should be added to.
9046
9057
  //
@@ -15435,7 +15446,7 @@ const DEFAULT_STATE$1 = {
15435
15446
  // TODO(benchristel): this is a memory leak. We add items to
15436
15447
  // tooltipResetFunctions, but never remove them.
15437
15448
  const tooltipResetFunctions = [];
15438
- class MovablePoint$4 {
15449
+ class MovablePoint$5 {
15439
15450
  graphie;
15440
15451
  movable;
15441
15452
  state;
@@ -15743,9 +15754,9 @@ class MovablePoint$4 {
15743
15754
  * Displays a tooltip above the point, replacing any previous contents. If
15744
15755
  * there is no tooltip initialized, adds the tooltip.
15745
15756
  *
15746
- * If the type of contents is string, the contents will be rendered with
15747
- * KaTeX. Otherwise, the content will be assumed to be a DOM node and will
15748
- * be appended inside the tooltip.
15757
+ * If the type of contents is string, the contents will be rendered as TeX
15758
+ * Otherwise, the content will be assumed to be a DOM node and will be
15759
+ * appended inside the tooltip.
15749
15760
  */
15750
15761
  _showTooltip(contents) {
15751
15762
  if (!this._tooltip) {
@@ -16568,10 +16579,10 @@ ___default["default"].extend(MovablePolygon.prototype, {
16568
16579
  });
16569
16580
 
16570
16581
  const Interactive2 = {
16571
- MovablePoint: MovablePoint$4,
16582
+ MovablePoint: MovablePoint$5,
16572
16583
  addMovablePoint: function (graphie, options) {
16573
16584
  const movable = new Movable(graphie, {});
16574
- return new MovablePoint$4(graphie, movable, options);
16585
+ return new MovablePoint$5(graphie, movable, options);
16575
16586
  },
16576
16587
  MovableLine: MovableLine$4,
16577
16588
  addMovableLine: function (graphie, options) {
@@ -16613,7 +16624,7 @@ const Interactive2 = {
16613
16624
  }
16614
16625
  };
16615
16626
 
16616
- const MovablePoint$3 = GraphieClasses.createClass({
16627
+ const MovablePoint$4 = GraphieClasses.createClass({
16617
16628
  displayName: "MovablePoint",
16618
16629
  movableProps: ["children"],
16619
16630
  _getProps: function () {
@@ -16671,7 +16682,7 @@ const MovablePoint$3 = GraphieClasses.createClass({
16671
16682
  });
16672
16683
 
16673
16684
  // Include helper methods, such as MovablePoint.constrain.snap()
16674
- ___default["default"].extend(MovablePoint$3, Interactive2.MovablePoint);
16685
+ ___default["default"].extend(MovablePoint$4, Interactive2.MovablePoint);
16675
16686
  const MovableLine$3 = GraphieClasses.createClass({
16676
16687
  displayName: "MovableLine",
16677
16688
  movableProps: ["children"],
@@ -16841,7 +16852,7 @@ var Movables = {
16841
16852
  Label: Label$1,
16842
16853
  Line: Line$3,
16843
16854
  MovableLine: MovableLine$3,
16844
- MovablePoint: MovablePoint$3,
16855
+ MovablePoint: MovablePoint$4,
16845
16856
  Parabola: Parabola,
16846
16857
  Path: Path,
16847
16858
  Plot: Plot$2,
@@ -17199,17 +17210,15 @@ class ImageLoader extends React__namespace.Component {
17199
17210
  src,
17200
17211
  imgProps
17201
17212
  } = this.props;
17202
- let onKeyUp = null;
17203
- let onKeyDown = null;
17213
+ let onKeyUp;
17214
+ let onKeyDown;
17204
17215
  if (imgProps.onClick != null) {
17205
- // @ts-expect-error - TS2322 - Type '(e: React.KeyboardEvent) => void' is not assignable to type 'null'.
17206
17216
  onKeyUp = e => {
17207
17217
  // 13 is enter key, 32 is space key
17208
17218
  if (e.keyCode === 13 || e.keyCode === 32) {
17209
17219
  imgProps.onClick && imgProps.onClick(e);
17210
17220
  }
17211
17221
  };
17212
- // @ts-expect-error - TS2322 - Type '(e: React.KeyboardEvent) => void' is not assignable to type 'null'.
17213
17222
  onKeyDown = e => {
17214
17223
  // 32 is space key
17215
17224
  if (e.keyCode === 32) {
@@ -17220,12 +17229,8 @@ class ImageLoader extends React__namespace.Component {
17220
17229
  }
17221
17230
  const staticUrl = getDependencies().staticUrl;
17222
17231
  return /*#__PURE__*/React__namespace.createElement("img", _extends({
17223
- src: staticUrl(src)
17224
- // @ts-expect-error - TS2322 - Type 'null' is not assignable to type 'KeyboardEventHandler<HTMLImageElement> | undefined'.
17225
- ,
17226
- onKeyUp: onKeyUp
17227
- // @ts-expect-error - TS2322 - Type 'null' is not assignable to type 'KeyboardEventHandler<HTMLImageElement> | undefined'.
17228
- ,
17232
+ src: staticUrl(src),
17233
+ onKeyUp: onKeyUp,
17229
17234
  onKeyDown: onKeyDown
17230
17235
  }, imgProps));
17231
17236
  };
@@ -17716,18 +17721,14 @@ class SvgImage extends React__namespace.Component {
17716
17721
  allowFullBleed: this.props.allowFullBleed && isImageProbablyPhotograph(imageSrc)
17717
17722
  }, /*#__PURE__*/React__namespace.createElement(ImageLoader, {
17718
17723
  src: imageSrc,
17719
- imgProps: imageProps
17720
- // @ts-expect-error - TS2322 - Type '(() => Element) | null' is not assignable to type '() => ReactElement<any, string | JSXElementConstructor<any>> | null | undefined'.
17721
- ,
17724
+ imgProps: imageProps,
17722
17725
  preloader: preloader,
17723
17726
  onUpdate: this.handleUpdate
17724
17727
  }), extraGraphie);
17725
17728
  }
17726
17729
  imageProps.style = dimensions;
17727
17730
  return /*#__PURE__*/React__namespace.createElement(ImageLoader, {
17728
- src: imageSrc
17729
- // @ts-expect-error - TS2322 - Type '(() => Element) | null' is not assignable to type '() => ReactElement<any, string | JSXElementConstructor<any>> | null | undefined'.
17730
- ,
17731
+ src: imageSrc,
17731
17732
  preloader: preloader,
17732
17733
  imgProps: imageProps,
17733
17734
  onUpdate: this.handleUpdate
@@ -17777,9 +17778,7 @@ class SvgImage extends React__namespace.Component {
17777
17778
  }, /*#__PURE__*/React__namespace.createElement(ImageLoader, {
17778
17779
  src: imageUrl,
17779
17780
  onLoad: this.onImageLoad,
17780
- onUpdate: this.handleUpdate
17781
- // @ts-expect-error - TS2322 - Type '(() => Element) | null' is not assignable to type '() => ReactElement<any, string | JSXElementConstructor<any>> | null | undefined'.
17782
- ,
17781
+ onUpdate: this.handleUpdate,
17783
17782
  preloader: preloader,
17784
17783
  imgProps: imageProps
17785
17784
  }), graphie, extraGraphie);
@@ -17791,9 +17790,7 @@ class SvgImage extends React__namespace.Component {
17791
17790
  }, /*#__PURE__*/React__namespace.createElement(ImageLoader, {
17792
17791
  src: imageUrl,
17793
17792
  onLoad: this.onImageLoad,
17794
- onUpdate: this.handleUpdate
17795
- // @ts-expect-error - TS2322 - Type '(() => Element) | null' is not assignable to type '() => ReactElement<any, string | JSXElementConstructor<any>> | null | undefined'.
17796
- ,
17793
+ onUpdate: this.handleUpdate,
17797
17794
  preloader: preloader,
17798
17795
  imgProps: imageProps
17799
17796
  }), graphie);
@@ -18371,33 +18368,33 @@ class Lint extends React__namespace.Component {
18371
18368
  let warningText;
18372
18369
  let warningTextStyle;
18373
18370
  if (this.props.severity === Severity.Error) {
18374
- severityStyle = styles$C.indicatorError;
18371
+ severityStyle = styles$B.indicatorError;
18375
18372
  warningText = "Error";
18376
- warningTextStyle = styles$C.publishBlockingError;
18373
+ warningTextStyle = styles$B.publishBlockingError;
18377
18374
  } else if (this.props.severity === Severity.Warning) {
18378
- severityStyle = styles$C.indicatorWarning;
18375
+ severityStyle = styles$B.indicatorWarning;
18379
18376
  warningText = "Warning";
18380
- warningTextStyle = styles$C.warning;
18377
+ warningTextStyle = styles$B.warning;
18381
18378
  } else {
18382
- severityStyle = styles$C.indicatorGuideline;
18379
+ severityStyle = styles$B.indicatorGuideline;
18383
18380
  warningText = "Recommendation";
18384
- warningTextStyle = styles$C.warning;
18381
+ warningTextStyle = styles$B.warning;
18385
18382
  }
18386
18383
  return /*#__PURE__*/React__namespace.createElement("a", {
18387
18384
  href: `https://khanacademy.org/r/linter-rules#${this.props.ruleName}`,
18388
18385
  target: "lint-help-window",
18389
18386
  className: aphrodite.css(style)
18390
18387
  }, /*#__PURE__*/React__namespace.createElement("span", {
18391
- className: aphrodite.css(styles$C.indicator, severityStyle)
18388
+ className: aphrodite.css(styles$B.indicator, severityStyle)
18392
18389
  }, this.props.severity === 1 && /*#__PURE__*/React__namespace.createElement(InlineIcon, exclamationIcon)), /*#__PURE__*/React__namespace.createElement("div", {
18393
- className: aphrodite.css(styles$C.tooltip, tooltipAbove && styles$C.tooltipAbove)
18390
+ className: aphrodite.css(styles$B.tooltip, tooltipAbove && styles$B.tooltipAbove)
18394
18391
  }, this.props.message.split("\n\n").map((m, i) => /*#__PURE__*/React__namespace.createElement("p", {
18395
18392
  key: i,
18396
- className: aphrodite.css(styles$C.tooltipParagraph)
18393
+ className: aphrodite.css(styles$B.tooltipParagraph)
18397
18394
  }, /*#__PURE__*/React__namespace.createElement("span", {
18398
18395
  className: aphrodite.css(warningTextStyle)
18399
18396
  }, warningText, ":", " "), m)), /*#__PURE__*/React__namespace.createElement("div", {
18400
- className: aphrodite.css(styles$C.tail, tooltipAbove && styles$C.tailAbove)
18397
+ className: aphrodite.css(styles$B.tail, tooltipAbove && styles$B.tailAbove)
18401
18398
  })));
18402
18399
  };
18403
18400
 
@@ -18425,20 +18422,20 @@ class Lint extends React__namespace.Component {
18425
18422
  }
18426
18423
  if (blockHighlight) {
18427
18424
  return /*#__PURE__*/React__namespace.createElement("span", {
18428
- className: aphrodite.css(styles$C.lintContainer, styles$C.lintContainerBlock)
18429
- }, this.renderLink(styles$C.radioWidgetHoverTarget), /*#__PURE__*/React__namespace.createElement("span", null, children));
18425
+ className: aphrodite.css(styles$B.lintContainer, styles$B.lintContainerBlock)
18426
+ }, this.renderLink(styles$B.radioWidgetHoverTarget), /*#__PURE__*/React__namespace.createElement("span", null, children));
18430
18427
  }
18431
18428
  if (inline) {
18432
18429
  return /*#__PURE__*/React__namespace.createElement("span", {
18433
- className: aphrodite.css(styles$C.lintContainer)
18434
- }, this.renderLink(styles$C.inlineHoverTarget), /*#__PURE__*/React__namespace.createElement("span", null, children));
18430
+ className: aphrodite.css(styles$B.lintContainer)
18431
+ }, this.renderLink(styles$B.inlineHoverTarget), /*#__PURE__*/React__namespace.createElement("span", null, children));
18435
18432
  }
18436
18433
  return /*#__PURE__*/React__namespace.createElement("div", {
18437
- className: aphrodite.css(styles$C.lintContainer)
18438
- }, this.renderLink(styles$C.hoverTarget), /*#__PURE__*/React__namespace.createElement("div", null, children));
18434
+ className: aphrodite.css(styles$B.lintContainer)
18435
+ }, this.renderLink(styles$B.hoverTarget), /*#__PURE__*/React__namespace.createElement("div", null, children));
18439
18436
  }
18440
18437
  }
18441
- const styles$C = aphrodite.StyleSheet.create({
18438
+ const styles$B = aphrodite.StyleSheet.create({
18442
18439
  // This is the class of the outermost element.
18443
18440
  // We use relative positioning so that the lint indicator can be
18444
18441
  // positioned absolutely relative to the position of the linty container.
@@ -19132,15 +19129,17 @@ class TranslationLinter {
19132
19129
  }
19133
19130
 
19134
19131
  /**
19135
- * Preprocess TeX code to convert things that KaTeX doesn't know how to handle
19132
+ * Preprocess TeX code to convert things that MathJax doesn't know how to handle
19136
19133
  * to things is does.
19137
19134
  */
19138
-
19139
19135
  var preprocessTex = (texCode => texCode
19140
19136
  // Replace uses of \begin{align}...\end{align} which KaTeX doesn't
19141
19137
  // support (yet) with \begin{aligned}...\end{aligned} which renders
19142
19138
  // the same is supported by KaTeX. It does the same for align*.
19143
19139
  // TODO(kevinb) update content to use aligned instead of align.
19140
+ // TODO(LEMS-1608) Remove this replacement as MathJax supports the
19141
+ // "align" macro correctly (and, in fact, it is not synonymous with
19142
+ // "aligned").
19144
19143
  .replace(/\{align[*]?\}/g, "{aligned}")
19145
19144
  // Replace non-breaking spaces with regular spaces.
19146
19145
  .replace(/[\u00a0]/g, " "));
@@ -20574,7 +20573,7 @@ class Renderer extends React__namespace.Component {
20574
20573
  };
20575
20574
  const mobileInnerStyle = {
20576
20575
  ...innerStyle,
20577
- ...styles$B.mobileZoomableParentFix
20576
+ ...styles$A.mobileZoomableParentFix
20578
20577
  };
20579
20578
  return /*#__PURE__*/React__namespace.createElement("div", {
20580
20579
  key: state.key,
@@ -20608,6 +20607,9 @@ class Renderer extends React__namespace.Component {
20608
20607
  // support (yet) with \begin{aligned}...\end{aligned} which renders
20609
20608
  // the same is supported by KaTeX. It does the same for align*.
20610
20609
  // TODO(kevinb) update content to use aligned instead of align.
20610
+ // TODO(LEMS-1608) Remove this replacement as MathJax supports the
20611
+ // "align" macro correctly (and, in fact, it is not synonymous with
20612
+ // "aligned").
20611
20613
  const tex = node.content.replace(/\{align[*]?\}/g, "{aligned}");
20612
20614
 
20613
20615
  // We render math here instead of in perseus-markdown.jsx
@@ -20711,7 +20713,7 @@ class Renderer extends React__namespace.Component {
20711
20713
  };
20712
20714
  const mobileInnerStyle = {
20713
20715
  ...innerStyle,
20714
- ...styles$B.mobileZoomableParentFix
20716
+ ...styles$A.mobileZoomableParentFix
20715
20717
  };
20716
20718
  const wrappedOutput = /*#__PURE__*/React__namespace.createElement("div", {
20717
20719
  style: {
@@ -21219,7 +21221,7 @@ class Renderer extends React__namespace.Component {
21219
21221
  return this.lastRenderedMarkdown;
21220
21222
  }
21221
21223
  }
21222
- const styles$B = {
21224
+ const styles$A = {
21223
21225
  mobileZoomableParentFix: {
21224
21226
  // NOTE(abdul): There is an issue where transform animations will
21225
21227
  // cause the Zoomable component to disappear when running on the
@@ -21265,28 +21267,24 @@ class TextInput extends React__namespace.Component {
21265
21267
  style
21266
21268
  } = this.props;
21267
21269
  const formattedValue = value === null ? "" : value.toString();
21268
- return (
21269
- /*#__PURE__*/
21270
- // $FlowIgnore
21271
- React__namespace.createElement(wonderBlocksForm.TextField, {
21272
- style: style,
21273
- disabled: disabled,
21274
- id: this.id,
21275
- value: formattedValue,
21276
- type: "text",
21277
- "aria-label": labelText,
21278
- onChange: value => this.props.onChange(value),
21279
- placeholder: placeholder,
21280
- onFocus: onFocus,
21281
- onBlur: onBlur,
21282
- onKeyDown: onKeyDown
21283
- // @ts-expect-error - TS2322 - Type '{ style: StyleType; disabled: boolean | undefined; id: string; value: string; type: "text"; "aria-label": string | undefined; onChange: (value: string) => void; placeholder: string | undefined; ... 5 more ...; autoComplete: string; }' is not assignable to type 'IntrinsicAttributes & ExportProps & RefAttributes<HTMLInputElement>'.
21284
- ,
21285
- autoCorrect: "off",
21286
- autoCapitalize: "off",
21287
- autoComplete: "off"
21288
- })
21289
- );
21270
+ return /*#__PURE__*/React__namespace.createElement(wonderBlocksForm.TextField, {
21271
+ style: style,
21272
+ disabled: disabled,
21273
+ id: this.id,
21274
+ value: formattedValue,
21275
+ type: "text",
21276
+ "aria-label": labelText,
21277
+ onChange: value => this.props.onChange(value),
21278
+ placeholder: placeholder,
21279
+ onFocus: onFocus,
21280
+ onBlur: onBlur,
21281
+ onKeyDown: onKeyDown
21282
+ // @ts-expect-error - TS2322 - Type '{ style: StyleType; disabled: boolean | undefined; id: string; value: string; type: "text"; "aria-label": string | undefined; onChange: (value: string) => void; placeholder: string | undefined; ... 5 more ...; autoComplete: string; }' is not assignable to type 'IntrinsicAttributes & ExportProps & RefAttributes<HTMLInputElement>'.
21283
+ ,
21284
+ autoCorrect: "off",
21285
+ autoCapitalize: "off",
21286
+ autoComplete: "off"
21287
+ });
21290
21288
  }
21291
21289
  focus = () => {
21292
21290
  // @ts-expect-error - TS2531 - Object is possibly 'null'. | TS2339 - Property 'focus' does not exist on type 'Element | Text'.
@@ -22019,10 +22017,10 @@ class InputNumber extends React__namespace.Component {
22019
22017
  // Note: This is _very_ similar to what `numeric-input.jsx` does. If
22020
22018
  // you modify this, double-check if you also need to modify that
22021
22019
  // component.
22022
- const inputStyles = [styles$A.default, this.props.size === "small" ? styles$A.small : null, this.props.rightAlign ? styles$A.rightAlign : styles$A.leftAlign];
22020
+ const inputStyles = [styles$z.default, this.props.size === "small" ? styles$z.small : null, this.props.rightAlign ? styles$z.rightAlign : styles$z.leftAlign];
22023
22021
  // Unanswered
22024
22022
  if (rubric && !this.props.currentValue) {
22025
- inputStyles.push(styles$A.answerStateUnanswered);
22023
+ inputStyles.push(styles$z.answerStateUnanswered);
22026
22024
  }
22027
22025
  return /*#__PURE__*/React__namespace.createElement(InputWithExamples
22028
22026
  // eslint-disable-next-line react/no-string-refs
@@ -22162,7 +22160,7 @@ class InputNumber extends React__namespace.Component {
22162
22160
  return answerString;
22163
22161
  }
22164
22162
  }
22165
- const styles$A = aphrodite.StyleSheet.create({
22163
+ const styles$z = aphrodite.StyleSheet.create({
22166
22164
  default: {
22167
22165
  width: 80,
22168
22166
  height: "auto"
@@ -23155,7 +23153,7 @@ const FocusRing = function (props) {
23155
23153
  };
23156
23154
  return /*#__PURE__*/React__namespace.createElement("span", {
23157
23155
  "data-testid": "focus-ring",
23158
- className: aphrodite.css(styles$z.ring),
23156
+ className: aphrodite.css(styles$y.ring),
23159
23157
  style: style
23160
23158
  }, children);
23161
23159
  };
@@ -23164,7 +23162,7 @@ FocusRing.defaultProps = {
23164
23162
  color: kaGreen$1,
23165
23163
  multipleSelect: false
23166
23164
  };
23167
- const styles$z = aphrodite.StyleSheet.create({
23165
+ const styles$y = aphrodite.StyleSheet.create({
23168
23166
  ring: {
23169
23167
  margin: "auto",
23170
23168
  display: "inline-block",
@@ -23191,7 +23189,7 @@ function getChoiceLetter(pos, strings) {
23191
23189
  }
23192
23190
 
23193
23191
  const CHOICE_ICON_SIZE = 24;
23194
- const styles$y = aphrodite.StyleSheet.create({
23192
+ const styles$x = aphrodite.StyleSheet.create({
23195
23193
  iconWrapper: {
23196
23194
  display: "inline-block",
23197
23195
  position: "relative"
@@ -23214,7 +23212,7 @@ const CrossOutLine = function (props) {
23214
23212
  width: CROSS_OUT_LINE_SIZE,
23215
23213
  height: CROSS_OUT_LINE_SIZE,
23216
23214
  viewBox: `0 0 ${CROSS_OUT_LINE_SIZE} ${CROSS_OUT_LINE_SIZE}`,
23217
- className: aphrodite.css(styles$x.crossOutLine),
23215
+ className: aphrodite.css(styles$w.crossOutLine),
23218
23216
  "data-testid": "choice-icon__cross-out-line"
23219
23217
  }, /*#__PURE__*/React__namespace.createElement("line", {
23220
23218
  // The line stretches from the bottom-left to top-right.
@@ -23229,7 +23227,7 @@ const CrossOutLine = function (props) {
23229
23227
  strokeLinecap: "round"
23230
23228
  }));
23231
23229
  };
23232
- const styles$x = aphrodite.StyleSheet.create({
23230
+ const styles$w = aphrodite.StyleSheet.create({
23233
23231
  crossOutLine: {
23234
23232
  // Center the icon within the container.
23235
23233
  position: "absolute",
@@ -23313,7 +23311,7 @@ const ChoiceIcon = function (props) {
23313
23311
  } = props;
23314
23312
  const dynamicStyles = getDynamicStyles(checked, showCorrectness, pressed, multipleSelect, correct);
23315
23313
  return /*#__PURE__*/React__namespace.createElement("div", {
23316
- className: aphrodite.css(styles$y.iconWrapper)
23314
+ className: aphrodite.css(styles$x.iconWrapper)
23317
23315
  }, /*#__PURE__*/React__namespace.createElement(FocusRing, {
23318
23316
  color: wonderBlocksTokens.color.blue,
23319
23317
  visible: focused || hovered,
@@ -23322,13 +23320,13 @@ const ChoiceIcon = function (props) {
23322
23320
  // @ts-expect-error - TS2322 - Type '{ backgroundColor: string | null | undefined; borderColor: string; color: string; borderRadius: number; }' is not assignable to type 'Properties<string | number, string & {}>'.
23323
23321
  style: dynamicStyles,
23324
23322
  "data-testid": "choice-icon__library-choice-icon",
23325
- className: aphrodite.css(styles$w.circle, showCorrectness && correct && styles$w.circleCorrect, showCorrectness && !correct && styles$w.circleIncorrect, showCorrectness && !correct && (checked || previouslyAnswered) && styles$w.circleIncorrectAnswered)
23323
+ className: aphrodite.css(styles$v.circle, showCorrectness && correct && styles$v.circleCorrect, showCorrectness && !correct && styles$v.circleIncorrect, showCorrectness && !correct && (checked || previouslyAnswered) && styles$v.circleIncorrectAnswered)
23326
23324
  // used in BaseRadio editMode to check
23327
23325
  // if we actually clicked on the radio icon
23328
23326
  ,
23329
23327
  "data-is-radio-icon": true
23330
23328
  }, /*#__PURE__*/React__namespace.createElement("div", {
23331
- className: aphrodite.css(styles$w.innerWrapper)
23329
+ className: aphrodite.css(styles$v.innerWrapper)
23332
23330
  }, /*#__PURE__*/React__namespace.createElement(ChoiceInner, {
23333
23331
  pos: pos,
23334
23332
  showCorrectness: showCorrectness,
@@ -23337,7 +23335,7 @@ const ChoiceIcon = function (props) {
23337
23335
  color: dynamicStyles.borderColor
23338
23336
  }));
23339
23337
  };
23340
- const styles$w = aphrodite.StyleSheet.create({
23338
+ const styles$v = aphrodite.StyleSheet.create({
23341
23339
  innerWrapper: {
23342
23340
  width: CHOICE_ICON_SIZE,
23343
23341
  height: CHOICE_ICON_SIZE,
@@ -23432,19 +23430,19 @@ const OptionStatus = function (props) {
23432
23430
  }
23433
23431
  let textStyle;
23434
23432
  if (correct) {
23435
- textStyle = styles$v.correct;
23433
+ textStyle = styles$u.correct;
23436
23434
  } else {
23437
23435
  if (checked || previouslyAnswered) {
23438
- textStyle = styles$v.incorrectAnswered;
23436
+ textStyle = styles$u.incorrectAnswered;
23439
23437
  } else {
23440
- textStyle = styles$v.incorrect;
23438
+ textStyle = styles$u.incorrect;
23441
23439
  }
23442
23440
  }
23443
23441
  return /*#__PURE__*/React__namespace.createElement("div", {
23444
- className: aphrodite.css(styles$v.text, textStyle)
23442
+ className: aphrodite.css(styles$u.text, textStyle)
23445
23443
  }, renderText(checked, correct, crossedOut, strings));
23446
23444
  };
23447
- const styles$v = aphrodite.StyleSheet.create({
23445
+ const styles$u = aphrodite.StyleSheet.create({
23448
23446
  text: {
23449
23447
  alignItems: "center",
23450
23448
  display: "flex",
@@ -23513,8 +23511,8 @@ const Choice = function (props) {
23513
23511
  crossedOut: updatedCrossedOut
23514
23512
  });
23515
23513
  }
23516
- const descriptionClassName = classNames__default["default"]("description", aphrodite.css(styles$u.description));
23517
- const rationaleClassName = classNames__default["default"]("perseus-radio-rationale-content", aphrodite.css(styles$u.rationale));
23514
+ const descriptionClassName = classNames__default["default"]("description", aphrodite.css(styles$t.description));
23515
+ const rationaleClassName = classNames__default["default"]("perseus-radio-rationale-content", aphrodite.css(styles$t.rationale));
23518
23516
 
23519
23517
  // We want to show the choices as dimmed out when the choices are disabled.
23520
23518
  // However, we don't want to do this when we're in review mode in the
@@ -23681,7 +23679,7 @@ const Choice = function (props) {
23681
23679
  "data-testid": `perseus-radio-rationale-content-${pos}`
23682
23680
  }, rationale));
23683
23681
  };
23684
- const styles$u = aphrodite.StyleSheet.create({
23682
+ const styles$t = aphrodite.StyleSheet.create({
23685
23683
  description: {
23686
23684
  display: "inline-block",
23687
23685
  width: "100%"
@@ -23880,10 +23878,10 @@ const BaseRadio = function (props) {
23880
23878
  const isMobile = apiOptions.isMobile;
23881
23879
  const firstChoiceHighlighted = choices[0].highlighted;
23882
23880
  const lastChoiceHighlighted = choices[choices.length - 1].highlighted;
23883
- const className = classNames__default["default"]("perseus-widget-radio", !editMode && "perseus-rendered-radio", aphrodite.css(styles$t.radio, styles$t.responsiveRadioContainer, firstChoiceHighlighted && isMobile && styles$t.radioContainerFirstHighlighted, lastChoiceHighlighted && isMobile && styles$t.radioContainerLastHighlighted));
23884
- const instructionsClassName = classNames__default["default"]("instructions", aphrodite.css(styles$t.instructions, isMobile && styles$t.instructionsMobile));
23881
+ const className = classNames__default["default"]("perseus-widget-radio", !editMode && "perseus-rendered-radio", aphrodite.css(styles$s.radio, styles$s.responsiveRadioContainer, firstChoiceHighlighted && isMobile && styles$s.radioContainerFirstHighlighted, lastChoiceHighlighted && isMobile && styles$s.radioContainerLastHighlighted));
23882
+ const instructionsClassName = classNames__default["default"]("instructions", aphrodite.css(styles$s.instructions, isMobile && styles$s.instructionsMobile));
23885
23883
  const instructions = getInstructionsText(multipleSelect, countChoices, numCorrect, strings);
23886
- const responsiveClassName = aphrodite.css(styles$t.responsiveFieldset);
23884
+ const responsiveClassName = aphrodite.css(styles$s.responsiveFieldset);
23887
23885
  const fieldset = /*#__PURE__*/React__namespace.createElement("fieldset", {
23888
23886
  className: `perseus-widget-radio-fieldset ${responsiveClassName}`
23889
23887
  }, /*#__PURE__*/React__namespace.createElement("legend", {
@@ -23934,7 +23932,7 @@ const BaseRadio = function (props) {
23934
23932
  const aphroditeClassName = checked => {
23935
23933
  // Whether or not to show correctness borders
23936
23934
  // for this choice and the next choice.
23937
- return aphrodite.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);
23935
+ return aphrodite.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);
23938
23936
  };
23939
23937
 
23940
23938
  // HACK(abdulrahman): Preloads the selection-state
@@ -24008,14 +24006,14 @@ const BaseRadio = function (props) {
24008
24006
  // Allow for horizontal scrolling if content is too wide, which may be
24009
24007
  // an issue especially on phones.
24010
24008
  return /*#__PURE__*/React__namespace.createElement("div", {
24011
- className: aphrodite.css(styles$t.responsiveContainer)
24009
+ className: aphrodite.css(styles$s.responsiveContainer)
24012
24010
  }, fieldset);
24013
24011
  };
24014
24012
  BaseRadio.defaultProps = {
24015
24013
  editMode: false,
24016
24014
  multipleSelect: false
24017
24015
  };
24018
- const styles$t = aphrodite.StyleSheet.create({
24016
+ const styles$s = aphrodite.StyleSheet.create({
24019
24017
  instructions: {
24020
24018
  display: "block",
24021
24019
  color: gray17,
@@ -24699,9 +24697,9 @@ class Categorizer extends React__namespace.Component {
24699
24697
  indexedItems = Util.shuffle(indexedItems, this.props.problemNum);
24700
24698
  }
24701
24699
  const table = /*#__PURE__*/React__namespace.createElement("table", {
24702
- className: "categorizer-table " + aphrodite.css(styles$s.mobileTable)
24700
+ className: "categorizer-table " + aphrodite.css(styles$r.mobileTable)
24703
24701
  }, /*#__PURE__*/React__namespace.createElement("thead", null, /*#__PURE__*/React__namespace.createElement("tr", null, /*#__PURE__*/React__namespace.createElement("td", {
24704
- className: aphrodite.css(styles$s.emptyHeaderCell)
24702
+ className: aphrodite.css(styles$r.emptyHeaderCell)
24705
24703
  }), this.props.categories.map((category, i) => {
24706
24704
  // Array index is the correct key here, as that's
24707
24705
  // how category grading actually works -- no way
@@ -24709,7 +24707,7 @@ class Categorizer extends React__namespace.Component {
24709
24707
  // middle. (If we later add that, this should be
24710
24708
  // fixed.)
24711
24709
  return /*#__PURE__*/React__namespace.createElement("th", {
24712
- className: aphrodite.css(styles$s.header),
24710
+ className: aphrodite.css(styles$r.header),
24713
24711
  key: i
24714
24712
  }, /*#__PURE__*/React__namespace.createElement(Renderer, {
24715
24713
  content: category,
@@ -24731,7 +24729,7 @@ class Categorizer extends React__namespace.Component {
24731
24729
  })), self.props.categories.map((catName, catNum) => {
24732
24730
  const selected = self.props.values[itemNum] === catNum;
24733
24731
  return /*#__PURE__*/React__namespace.createElement("td", {
24734
- className: "category " + aphrodite.css(styles$s.cell),
24732
+ className: "category " + aphrodite.css(styles$r.cell),
24735
24733
  key: catNum
24736
24734
  }, /*#__PURE__*/React__namespace.createElement("div", {
24737
24735
  className: ClassNames.INTERACTIVE,
@@ -24746,7 +24744,7 @@ class Categorizer extends React__namespace.Component {
24746
24744
  onChange: () => this.onChange(itemNum, catNum),
24747
24745
  onClick: e => e.stopPropagation()
24748
24746
  }), !isMobile && /*#__PURE__*/React__namespace.createElement("span", {
24749
- className: aphrodite.css(styles$s.radioSpan, selected && styles$s.checkedRadioSpan, this.props.static && selected && styles$s.staticCheckedRadioSpan)
24747
+ className: aphrodite.css(styles$r.radioSpan, selected && styles$r.checkedRadioSpan, this.props.static && selected && styles$r.staticCheckedRadioSpan)
24750
24748
  }, selected ? /*#__PURE__*/React__namespace.createElement(InlineIcon, iconCircle) : /*#__PURE__*/React__namespace.createElement(InlineIcon, iconCircleThin))));
24751
24749
  }));
24752
24750
  /* eslint-enable max-len */
@@ -24758,7 +24756,7 @@ class Categorizer extends React__namespace.Component {
24758
24756
  "categorizer-container": true,
24759
24757
  "static-mode": this.props.static
24760
24758
  });
24761
- const inlineStyles = this.props.apiOptions.isMobile ? [styles$s.fullBleedContainer] : [];
24759
+ const inlineStyles = this.props.apiOptions.isMobile ? [styles$r.fullBleedContainer] : [];
24762
24760
  return /*#__PURE__*/React__namespace.createElement("div", {
24763
24761
  className: extraClassNames + " " + aphrodite.css(...inlineStyles)
24764
24762
  }, table);
@@ -24807,7 +24805,7 @@ const pageMargin = 16;
24807
24805
 
24808
24806
  // Stylesheets aren't directly testable
24809
24807
  /* c8 ignore next */
24810
- const styles$s = aphrodite.StyleSheet.create({
24808
+ const styles$r = aphrodite.StyleSheet.create({
24811
24809
  mobileTable: {
24812
24810
  [mediaQueries.smOrSmaller]: {
24813
24811
  minWidth: "auto"
@@ -25070,7 +25068,7 @@ class CSProgram extends React__namespace.Component {
25070
25068
  // creator "went wild".
25071
25069
  // http://www.html5rocks.com/en/tutorials/security/sandboxed-iframes/
25072
25070
  return /*#__PURE__*/React__namespace.createElement("div", {
25073
- className: aphrodite.css(styles$r.widthOverride, styleContainer && styles$r.container)
25071
+ className: aphrodite.css(styles$q.widthOverride, styleContainer && styles$q.container)
25074
25072
  }, /*#__PURE__*/React__namespace.createElement("iframe", {
25075
25073
  sandbox: sandboxOptions,
25076
25074
  src: url,
@@ -25080,7 +25078,7 @@ class CSProgram extends React__namespace.Component {
25080
25078
  }));
25081
25079
  }
25082
25080
  }
25083
- const styles$r = aphrodite.StyleSheet.create({
25081
+ const styles$q = aphrodite.StyleSheet.create({
25084
25082
  // Override the inherited width from the perseus paragraph class
25085
25083
  // 820 is minimum width for 420px editor and 400px canvas
25086
25084
  widthOverride: {
@@ -25127,7 +25125,7 @@ class Definition extends React__namespace.Component {
25127
25125
  return /*#__PURE__*/React__namespace.createElement(wonderBlocksPopover.Popover, {
25128
25126
  content: /*#__PURE__*/React__namespace.createElement(wonderBlocksPopover.PopoverContentCore, {
25129
25127
  color: "white",
25130
- style: styles$q.tooltipBody,
25128
+ style: styles$p.tooltipBody,
25131
25129
  closeButtonVisible: true
25132
25130
  }, /*#__PURE__*/React__namespace.createElement(Renderer, {
25133
25131
  apiOptions: this.props.apiOptions,
@@ -25159,7 +25157,7 @@ class Definition extends React__namespace.Component {
25159
25157
  });
25160
25158
  }
25161
25159
  }
25162
- const styles$q = {
25160
+ const styles$p = {
25163
25161
  tooltipBody: {
25164
25162
  color: wonderBlocksTokens.color.offBlack,
25165
25163
  fontSize: 20,
@@ -25386,7 +25384,7 @@ class Explanation extends React__namespace.Component {
25386
25384
  marginRight: "-6px",
25387
25385
  "white-space": "normal"
25388
25386
  };
25389
- const contentStyling = [styles$p.content, this.state.expanded ? styles$p.contentExpanded : styles$p.contentCollapsed, allowTransition && (this.state.expanded ? styles$p.transitionExpanded : styles$p.transitionCollapsed)];
25387
+ const contentStyling = [styles$o.content, this.state.expanded ? styles$o.contentExpanded : styles$o.contentCollapsed, allowTransition && (this.state.expanded ? styles$o.transitionExpanded : styles$o.transitionCollapsed)];
25390
25388
  return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.UniqueIDProvider, {
25391
25389
  mockOnFirstRender: true,
25392
25390
  scope: "explanation-widget"
@@ -25405,7 +25403,7 @@ class Explanation extends React__namespace.Component {
25405
25403
  "aria-hidden": !this.state.expanded,
25406
25404
  testId: "content-container"
25407
25405
  }, /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
25408
- style: styles$p.contentWrapper
25406
+ style: styles$o.contentWrapper
25409
25407
  }, /*#__PURE__*/React__namespace.createElement(Renderer, {
25410
25408
  apiOptions: this.props.apiOptions,
25411
25409
  content: this.props.explanation,
@@ -25424,7 +25422,7 @@ class Explanation extends React__namespace.Component {
25424
25422
  const leftBorderSpacing = 23;
25425
25423
  const verticalContentPadding = 10;
25426
25424
  const arrowHeight = 14;
25427
- const styles$p = aphrodite.StyleSheet.create({
25425
+ const styles$o = aphrodite.StyleSheet.create({
25428
25426
  content: {
25429
25427
  borderLeft: "0px solid #ccc",
25430
25428
  display: "inline-grid",
@@ -25490,15 +25488,15 @@ class GradedGroupAnswerBar extends React__namespace.Component {
25490
25488
  nextQuestion
25491
25489
  } = this.context.strings;
25492
25490
  const answerBarStyle = {
25493
- ...styles$o.answerBar,
25491
+ ...styles$n.answerBar,
25494
25492
  backgroundColor: answerBarState === "CORRECT" ? wonderBlocksTokens.color.offWhite : "white",
25495
25493
  // Center the "Correct!" message only when there's no next question
25496
25494
  justifyContent: answerBarState === "CORRECT" && !onNextQuestion ? "center" : "space-between"
25497
25495
  };
25498
25496
  const message = answerBarState === "INCORRECT" ? /*#__PURE__*/React__namespace.createElement("span", {
25499
- style: styles$o.text
25497
+ style: styles$n.text
25500
25498
  }, /*#__PURE__*/React__namespace.createElement("span", {
25501
- style: styles$o.tryAgainIcon
25499
+ style: styles$n.tryAgainIcon
25502
25500
  }, /*#__PURE__*/React__namespace.createElement(InlineIcon, iconTryAgain)), /*#__PURE__*/React__namespace.createElement("span", {
25503
25501
  style: {
25504
25502
  marginLeft: 8
@@ -25517,7 +25515,7 @@ class GradedGroupAnswerBar extends React__namespace.Component {
25517
25515
  return /*#__PURE__*/React__namespace.createElement("div", {
25518
25516
  style: answerBarStyle
25519
25517
  }, /*#__PURE__*/React__namespace.createElement("span", {
25520
- style: styles$o.text
25518
+ style: styles$n.text
25521
25519
  }, /*#__PURE__*/React__namespace.createElement("span", {
25522
25520
  style: {
25523
25521
  fontSize: 28,
@@ -25539,7 +25537,7 @@ class GradedGroupAnswerBar extends React__namespace.Component {
25539
25537
  }
25540
25538
  }
25541
25539
  const fontSize = 17;
25542
- const styles$o = {
25540
+ const styles$n = {
25543
25541
  answerBar: {
25544
25542
  display: "flex",
25545
25543
  alignItems: "center",
@@ -25739,7 +25737,7 @@ class GradedGroup extends React__namespace.Component {
25739
25737
  }));
25740
25738
  gradeStatus = this.context.strings.incorrect;
25741
25739
  }
25742
- const mobileClass = this.props.inGradedGroupSet ? aphrodite.css(styles$n.gradedGroupInSet) : aphrodite.css(styles$n.gradedGroup);
25740
+ const mobileClass = this.props.inGradedGroupSet ? aphrodite.css(styles$m.gradedGroupInSet) : aphrodite.css(styles$m.gradedGroup);
25743
25741
  const classes = classNames__default["default"]({
25744
25742
  [mobileClass]: apiOptions.isMobile,
25745
25743
  "perseus-graded-group": true,
@@ -25758,7 +25756,7 @@ class GradedGroup extends React__namespace.Component {
25758
25756
  return /*#__PURE__*/React__namespace.createElement("div", {
25759
25757
  className: classes
25760
25758
  }, !!this.props.title && /*#__PURE__*/React__namespace.createElement("div", {
25761
- className: aphrodite.css(styles$n.title)
25759
+ className: aphrodite.css(styles$m.title)
25762
25760
  }, this.props.title), /*#__PURE__*/React__namespace.createElement(Renderer, _extends({}, this.props, {
25763
25761
  // eslint-disable-next-line react/no-string-refs
25764
25762
  ref: "renderer",
@@ -25792,7 +25790,7 @@ class GradedGroup extends React__namespace.Component {
25792
25790
  }, this.context.strings.nextQuestion), this.props.hint && this.props.hint.content && (this.state.showHint ? /*#__PURE__*/React__namespace.createElement("div", null, /*#__PURE__*/React__namespace.createElement("button", {
25793
25791
  // @ts-expect-error - TS2322 - Type 'string' is not assignable to type 'number | undefined'.
25794
25792
  tabIndex: "0",
25795
- className: aphrodite.css(styles$n.explanationTitle),
25793
+ className: aphrodite.css(styles$m.explanationTitle),
25796
25794
  onClick: () => this.setState({
25797
25795
  showHint: false
25798
25796
  }),
@@ -25825,7 +25823,7 @@ class GradedGroup extends React__namespace.Component {
25825
25823
  showHint: true
25826
25824
  });
25827
25825
  },
25828
- className: aphrodite.css(styles$n.showHintLink)
25826
+ className: aphrodite.css(styles$m.showHintLink)
25829
25827
  }, this.context.strings.explain)), apiOptions.isMobile && answerBarState !== "HIDDEN" && /*#__PURE__*/React__namespace.createElement(GradedGroupAnswerBar, {
25830
25828
  apiOptions: apiOptions,
25831
25829
  answerBarState: answerBarState,
@@ -25837,7 +25835,7 @@ class GradedGroup extends React__namespace.Component {
25837
25835
  const traverseChildWidgets$2 = function (props, traverseRenderer) {
25838
25836
  return ___default["default"].extend({}, props, traverseRenderer(props));
25839
25837
  };
25840
- const styles$n = aphrodite.StyleSheet.create({
25838
+ const styles$m = aphrodite.StyleSheet.create({
25841
25839
  gradedGroupInSet: {
25842
25840
  // Reset a few desktop-only styles that come from graded-group.less
25843
25841
  marginLeft: 0,
@@ -25908,20 +25906,20 @@ class Indicators extends React__namespace.Component {
25908
25906
  return /*#__PURE__*/React__namespace.createElement("ul", {
25909
25907
  // reduntantly add class name for use in .less files--
25910
25908
  // the styles object key gets hashed
25911
- className: classNames__default["default"](aphrodite.css(styles$m.indicatorContainer), "indicatorContainer")
25909
+ className: classNames__default["default"](aphrodite.css(styles$l.indicatorContainer), "indicatorContainer")
25912
25910
  }, this.props.gradedGroups.map((_ref, i) => {
25913
25911
  let {
25914
25912
  title
25915
25913
  } = _ref;
25916
25914
  return /*#__PURE__*/React__namespace.createElement("li", {
25917
- className: aphrodite.css(styles$m.indicator),
25915
+ className: aphrodite.css(styles$l.indicator),
25918
25916
  key: title
25919
25917
  }, /*#__PURE__*/React__namespace.createElement(Clickable__default["default"], {
25920
25918
  role: "button",
25921
25919
  "aria-label": this.context.strings.skipToTitle({
25922
25920
  title
25923
25921
  }),
25924
- style: styles$m.indicatorButton,
25922
+ style: styles$l.indicatorButton,
25925
25923
  onClick: () => this.props.onChangeCurrentGroup(i),
25926
25924
  onKeyDown: e => this.handleKeyDown(e, i)
25927
25925
  }, _ref2 => {
@@ -25931,9 +25929,9 @@ class Indicators extends React__namespace.Component {
25931
25929
  pressed
25932
25930
  } = _ref2;
25933
25931
  return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
25934
- style: [styles$m.indicatorDot, (hovered || focused || pressed) && styles$m.indicatorDotFocused]
25932
+ style: [styles$l.indicatorDot, (hovered || focused || pressed) && styles$l.indicatorDotFocused]
25935
25933
  }, i === this.props.currentGroup && /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
25936
- style: styles$m.indicatorDotActive
25934
+ style: styles$l.indicatorDotActive
25937
25935
  }, /*#__PURE__*/React__namespace.createElement("span", {
25938
25936
  className: aphrodite.css(a11y.srOnly)
25939
25937
  }, this.context.strings.current)));
@@ -26009,7 +26007,7 @@ class GradedGroupSet extends React__namespace.Component {
26009
26007
  } = getDependencies();
26010
26008
  if (JIPT.useJIPT && this.props.gradedGroups.length > 1) {
26011
26009
  return /*#__PURE__*/React__namespace.createElement("div", {
26012
- className: aphrodite.css(styles$m.container)
26010
+ className: aphrodite.css(styles$l.container)
26013
26011
  }, this.props.gradedGroups.map((gradedGroup, i) => {
26014
26012
  return (
26015
26013
  /*#__PURE__*/
@@ -26032,13 +26030,13 @@ class GradedGroupSet extends React__namespace.Component {
26032
26030
  const numGroups = this.props.gradedGroups.length;
26033
26031
  const handleNextQuestion = this.state.currentGroup < numGroups - 1 ? this.handleNextQuestion : null;
26034
26032
  return /*#__PURE__*/React__namespace.createElement("div", {
26035
- className: aphrodite.css(styles$m.container)
26033
+ className: aphrodite.css(styles$l.container)
26036
26034
  }, /*#__PURE__*/React__namespace.createElement("div", {
26037
- className: aphrodite.css(styles$m.top)
26035
+ className: aphrodite.css(styles$l.top)
26038
26036
  }, /*#__PURE__*/React__namespace.createElement("div", {
26039
- className: aphrodite.css(styles$m.title)
26037
+ className: aphrodite.css(styles$l.title)
26040
26038
  }, currentGroup.title), /*#__PURE__*/React__namespace.createElement("div", {
26041
- className: aphrodite.css(styles$m.spacer)
26039
+ className: aphrodite.css(styles$l.spacer)
26042
26040
  }), /*#__PURE__*/React__namespace.createElement(Indicators, {
26043
26041
  currentGroup: this.state.currentGroup,
26044
26042
  gradedGroups: this.props.gradedGroups,
@@ -26076,7 +26074,7 @@ var GradedGroupSet$1 = {
26076
26074
  tracking: "all",
26077
26075
  isLintable: true
26078
26076
  };
26079
- const styles$m = aphrodite.StyleSheet.create({
26077
+ const styles$l = aphrodite.StyleSheet.create({
26080
26078
  top: {
26081
26079
  display: "flex",
26082
26080
  flexDirection: "row"
@@ -26182,7 +26180,7 @@ class ButtonGroup extends React__namespace.Component {
26182
26180
  id: "" + i,
26183
26181
  ref: "button" + i,
26184
26182
  key: "" + i,
26185
- className: aphrodite.css(styles$l.buttonStyle, button.value === value && styles$l.selectedStyle),
26183
+ className: aphrodite.css(styles$k.buttonStyle, button.value === value && styles$k.selectedStyle),
26186
26184
  onClick: () => this.toggleSelect(button.value)
26187
26185
  }, button.content || "" + button.value);
26188
26186
  });
@@ -26195,7 +26193,7 @@ class ButtonGroup extends React__namespace.Component {
26195
26193
  }, buttons);
26196
26194
  }
26197
26195
  }
26198
- const styles$l = aphrodite.StyleSheet.create({
26196
+ const styles$k = aphrodite.StyleSheet.create({
26199
26197
  buttonStyle: {
26200
26198
  backgroundColor: "white",
26201
26199
  border: "1px solid #ccc",
@@ -26799,7 +26797,7 @@ var util = /*#__PURE__*/Object.freeze({
26799
26797
  });
26800
26798
 
26801
26799
  // @ts-expect-error - TS2339 - Property 'MovablePoint' does not exist on type 'typeof Graphie'.
26802
- const MovablePoint$2 = Graphie.MovablePoint;
26800
+ const MovablePoint$3 = Graphie.MovablePoint;
26803
26801
  // @ts-expect-error - TS2339 - Property 'MovableLine' does not exist on type 'typeof Graphie'.
26804
26802
  const MovableLine$2 = Graphie.MovableLine;
26805
26803
  function isFlipped(newCoord, oldCoord, line) {
@@ -26845,7 +26843,7 @@ class FunctionGrapher extends React__namespace.Component {
26845
26843
  })();
26846
26844
  render() {
26847
26845
  const pointForCoord = (coord, i) => {
26848
- return /*#__PURE__*/React__namespace.createElement(MovablePoint$2, {
26846
+ return /*#__PURE__*/React__namespace.createElement(MovablePoint$3, {
26849
26847
  key: i,
26850
26848
  coord: coord,
26851
26849
  static: this.props.static,
@@ -26982,7 +26980,7 @@ class FunctionGrapher extends React__namespace.Component {
26982
26980
  }],
26983
26981
  normalStyle: dashed,
26984
26982
  highlightStyle: dashed
26985
- }, ___default["default"].map(asymptote, (coord, i) => /*#__PURE__*/React__namespace.createElement(MovablePoint$2, {
26983
+ }, ___default["default"].map(asymptote, (coord, i) => /*#__PURE__*/React__namespace.createElement(MovablePoint$3, {
26986
26984
  key: `asymptoteCoord-${i}`,
26987
26985
  coord: coord,
26988
26986
  static: true,
@@ -27723,7 +27721,7 @@ const Label = Graphie.Label;
27723
27721
  // @ts-expect-error - TS2339 - Property 'Line' does not exist on type 'typeof Graphie'.
27724
27722
  const Line$2 = Graphie.Line;
27725
27723
  // @ts-expect-error - TS2339 - Property 'MovablePoint' does not exist on type 'typeof Graphie'.
27726
- const MovablePoint$1 = Graphie.MovablePoint;
27724
+ const MovablePoint$2 = Graphie.MovablePoint;
27727
27725
  // @ts-expect-error - TS2339 - Property 'MovableLine' does not exist on type 'typeof Graphie'.
27728
27726
  const MovableLine$1 = Graphie.MovableLine;
27729
27727
  // @ts-expect-error - TS2339 - Property 'Plot' does not exist on type 'typeof Graphie'.
@@ -27977,7 +27975,7 @@ class Interaction extends React__namespace.Component {
27977
27975
  return [coordX, coordY];
27978
27976
  }];
27979
27977
  if (element.options.constraint === "snap") {
27980
- constraints.push(MovablePoint$1.constraints.snap(element.options.snap));
27978
+ constraints.push(MovablePoint$2.constraints.snap(element.options.snap));
27981
27979
  } else if (element.options.constraint === "x") {
27982
27980
  constraints.push(coord => {
27983
27981
  return [
@@ -27999,7 +27997,7 @@ class Interaction extends React__namespace.Component {
27999
27997
  // TODO(eater): foo_[xyz] are hacky non-props to
28000
27998
  // get the component to update when constraints
28001
27999
  // change
28002
- return /*#__PURE__*/React__namespace.createElement(MovablePoint$1, {
28000
+ return /*#__PURE__*/React__namespace.createElement(MovablePoint$2, {
28003
28001
  key: element.key,
28004
28002
  coord: [
28005
28003
  // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
@@ -28034,7 +28032,7 @@ class Interaction extends React__namespace.Component {
28034
28032
  return [coordX, coordY];
28035
28033
  }];
28036
28034
  if (element.options.constraint === "snap") {
28037
- constraints.push(MovablePoint$1.constraints.snap(element.options.snap));
28035
+ constraints.push(MovablePoint$2.constraints.snap(element.options.snap));
28038
28036
  } else if (element.options.constraint === "x") {
28039
28037
  constraints.push(coord => {
28040
28038
  return [
@@ -28075,14 +28073,14 @@ class Interaction extends React__namespace.Component {
28075
28073
  foo_x: element.options.constraint,
28076
28074
  foo_y: element.options.constraintFn,
28077
28075
  foo_z: element.options.snap
28078
- }, /*#__PURE__*/React__namespace.createElement(MovablePoint$1, {
28076
+ }, /*#__PURE__*/React__namespace.createElement(MovablePoint$2, {
28079
28077
  coord: start,
28080
28078
  static: true,
28081
28079
  normalStyle: {
28082
28080
  stroke: "none",
28083
28081
  fill: "none"
28084
28082
  }
28085
- }), /*#__PURE__*/React__namespace.createElement(MovablePoint$1, {
28083
+ }), /*#__PURE__*/React__namespace.createElement(MovablePoint$2, {
28086
28084
  coord: end,
28087
28085
  static: true,
28088
28086
  normalStyle: {
@@ -28639,7 +28637,7 @@ const defaultGraphConfig = {
28639
28637
  width: 0,
28640
28638
  height: 0,
28641
28639
  labels: [],
28642
- hintMode: false
28640
+ disableKeyboardInteraction: false
28643
28641
  };
28644
28642
  const GraphConfigContext = /*#__PURE__*/React.createContext(defaultGraphConfig);
28645
28643
  function useGraphConfig() {
@@ -28786,6 +28784,30 @@ const segmentsIntersect = (_ref, _ref2) => {
28786
28784
  }
28787
28785
  };
28788
28786
 
28787
+ // Finds the unique intersection point between two rays. Returns undefined if
28788
+ // there is not exactly one intersection point. The first point in each of the
28789
+ // given ray "segments" should be the initial point of the ray. The second point
28790
+ // can be any arbitrary point on the ray different from the initial point.
28791
+ function findIntersectionOfRays(_ref3, _ref4) {
28792
+ let [[a, b], [c, d]] = _ref3;
28793
+ let [[p, q], [r, s]] = _ref4;
28794
+ // See https://stackoverflow.com/a/24392281/7347484 for an explanation of
28795
+ // the determinant and lambda values.
28796
+ const determinant = (c - a) * (s - q) - (r - p) * (d - b);
28797
+ if (determinant === 0) {
28798
+ return undefined;
28799
+ } else {
28800
+ const lambda = ((s - q) * (r - a) + (p - r) * (s - b)) / determinant;
28801
+ const gamma = ((b - d) * (r - a) + (c - a) * (s - b)) / determinant;
28802
+ if (lambda <= 0 || gamma >= 1) {
28803
+ return undefined;
28804
+ }
28805
+ const initialPoint = [a, b];
28806
+ const direction = mafs.vec.sub([c, d], initialPoint);
28807
+ return mafs.vec.add(initialPoint, mafs.vec.scale(direction, lambda));
28808
+ }
28809
+ }
28810
+
28789
28811
  // This file contains helper functions for working with angles.
28790
28812
 
28791
28813
  function convertDegreesToRadians(degrees) {
@@ -28798,6 +28820,8 @@ function calculateAngleInDegrees(_ref) {
28798
28820
  let [x, y] = _ref;
28799
28821
  return Math.atan2(y, x) * 180 / Math.PI;
28800
28822
  }
28823
+
28824
+ // Converts polar coordinates to cartesian. The th(eta) parameter is in degrees.
28801
28825
  function polar(r, th) {
28802
28826
  if (typeof r === "number") {
28803
28827
  r = [r, r];
@@ -29407,7 +29431,7 @@ const VisibleIcon = () => /*#__PURE__*/React__namespace.createElement("svg", {
29407
29431
  width: "24",
29408
29432
  height: "24",
29409
29433
  viewBox: "0 0 24 24",
29410
- className: aphrodite.css(styles$k.icon)
29434
+ className: aphrodite.css(styles$j.icon)
29411
29435
  }, /*#__PURE__*/React__namespace.createElement("defs", null, /*#__PURE__*/React__namespace.createElement("path", {
29412
29436
  id: "a",
29413
29437
  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"
@@ -29438,7 +29462,7 @@ const HiddenIcon = () => /*#__PURE__*/React__namespace.createElement("svg", {
29438
29462
  width: "24",
29439
29463
  height: "24",
29440
29464
  viewBox: "0 0 24 24",
29441
- className: aphrodite.css(styles$k.icon)
29465
+ className: aphrodite.css(styles$j.icon)
29442
29466
  }, /*#__PURE__*/React__namespace.createElement("defs", null, /*#__PURE__*/React__namespace.createElement("path", {
29443
29467
  id: "a",
29444
29468
  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"
@@ -29473,20 +29497,20 @@ const HUD = _ref => {
29473
29497
  let state;
29474
29498
  let icon;
29475
29499
  if (enabled) {
29476
- state = styles$k.enabled;
29500
+ state = styles$j.enabled;
29477
29501
  icon = /*#__PURE__*/React__namespace.createElement(VisibleIcon, null);
29478
29502
  } else {
29479
- state = styles$k.disabled;
29503
+ state = styles$j.disabled;
29480
29504
  icon = /*#__PURE__*/React__namespace.createElement(HiddenIcon, null);
29481
29505
  }
29482
29506
  return /*#__PURE__*/React__namespace.createElement("button", {
29483
- className: aphrodite.css(styles$k.hud, fixedPosition && styles$k.hudFixedPosition, state),
29507
+ className: aphrodite.css(styles$j.hud, fixedPosition && styles$j.hudFixedPosition, state),
29484
29508
  onClick: e => {
29485
29509
  onClick();
29486
29510
  }
29487
29511
  }, icon, message);
29488
29512
  };
29489
- const styles$k = aphrodite.StyleSheet.create({
29513
+ const styles$j = aphrodite.StyleSheet.create({
29490
29514
  hud: {
29491
29515
  boxSizing: "border-box",
29492
29516
  height: 36,
@@ -29536,19 +29560,19 @@ const styles$k = aphrodite.StyleSheet.create({
29536
29560
  }
29537
29561
  });
29538
29562
 
29539
- const InfoTip$1 = props => {
29563
+ const InfoTipBase = props => {
29540
29564
  return /*#__PURE__*/React__namespace.createElement(Tooltip__default["default"], {
29541
29565
  content: /*#__PURE__*/React__namespace.createElement(wonderBlocksTypography.Body, {
29542
- style: styles$j.body
29566
+ style: styles$i.body
29543
29567
  }, props.children),
29544
29568
  placement: "right"
29545
29569
  }, /*#__PURE__*/React__namespace.createElement(wonderBlocksIcon.PhosphorIcon, {
29546
29570
  size: "small",
29547
29571
  icon: questionIcon__default["default"],
29548
- style: styles$j.spacingLeft
29572
+ style: styles$i.spacingLeft
29549
29573
  }));
29550
29574
  };
29551
- const styles$j = aphrodite.StyleSheet.create({
29575
+ const styles$i = aphrodite.StyleSheet.create({
29552
29576
  spacingLeft: {
29553
29577
  marginInlineStart: wonderBlocksTokens.spacing.xxxSmall_4
29554
29578
  },
@@ -29577,7 +29601,7 @@ class InfoTip extends React__namespace.Component {
29577
29601
 
29578
29602
  render() {
29579
29603
  if (this.state.didMount) {
29580
- return /*#__PURE__*/React__namespace.createElement(InfoTip$1, this.props);
29604
+ return /*#__PURE__*/React__namespace.createElement(InfoTipBase, this.props);
29581
29605
  }
29582
29606
  return /*#__PURE__*/React__namespace.createElement("div", null);
29583
29607
  }
@@ -29624,7 +29648,7 @@ class MultiButtonGroup extends React__namespace.Component {
29624
29648
  id: "" + i,
29625
29649
  key: "" + i,
29626
29650
  ref: "button" + i,
29627
- className: aphrodite.css(styles$i.buttonStyle, selected && styles$i.selectedStyle),
29651
+ className: aphrodite.css(styles$h.buttonStyle, selected && styles$h.selectedStyle),
29628
29652
  onClick: () => this.toggleSelect(button.value)
29629
29653
  }, button.content || "" + button.value);
29630
29654
  });
@@ -29636,7 +29660,7 @@ class MultiButtonGroup extends React__namespace.Component {
29636
29660
  }, buttons);
29637
29661
  }
29638
29662
  }
29639
- const styles$i = aphrodite.StyleSheet.create({
29663
+ const styles$h = aphrodite.StyleSheet.create({
29640
29664
  buttonStyle: {
29641
29665
  backgroundColor: "white",
29642
29666
  border: "1px solid #ccc",
@@ -29868,62 +29892,6 @@ class NumberInput extends React__namespace.Component {
29868
29892
  };
29869
29893
  }
29870
29894
 
29871
- /* eslint-disable @babel/no-invalid-this */
29872
-
29873
- /* A checkbox that syncs its value to props using the
29874
- * renderer's onChange method, and gets the prop name
29875
- * dynamically from its props list
29876
- */
29877
- const PropCheckBox = createReactClass__default["default"]({
29878
- displayName: "PropCheckBox",
29879
- propTypes: {
29880
- labelAlignment: PropTypes__default["default"].oneOf(["left", "right"])
29881
- },
29882
- DEFAULT_PROPS: {
29883
- label: null,
29884
- onChange: null,
29885
- labelAlignment: "left"
29886
- },
29887
- getDefaultProps: function () {
29888
- return this.DEFAULT_PROPS;
29889
- },
29890
- propName: function () {
29891
- const propName = ___default["default"].find(___default["default"].keys(this.props), function (localPropName) {
29892
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
29893
- return !___default["default"].has(this.DEFAULT_PROPS, localPropName);
29894
- }, this);
29895
- if (!propName) {
29896
- throw new perseusCore.PerseusError("Attempted to create a PropCheckBox with no prop!", perseusCore.Errors.InvalidInput);
29897
- }
29898
- return propName;
29899
- },
29900
- _labelAlignLeft: function () {
29901
- return this.props.labelAlignment === "left";
29902
- },
29903
- render: function () {
29904
- const propName = this.propName();
29905
- return /*#__PURE__*/React__namespace.createElement("label", {
29906
- className: aphrodite.css(styles$h.labeledCheckbox)
29907
- }, this._labelAlignLeft() && /*#__PURE__*/React__namespace.createElement(wonderBlocksTypography.LabelSmall, null, this.props.label), /*#__PURE__*/React__namespace.createElement(wonderBlocksForm.Checkbox, {
29908
- checked: this.props[propName],
29909
- onChange: this.toggle
29910
- }), !this._labelAlignLeft() && /*#__PURE__*/React__namespace.createElement(wonderBlocksTypography.LabelSmall, null, this.props.label));
29911
- },
29912
- toggle: function () {
29913
- const propName = this.propName();
29914
- const changes = {};
29915
- changes[propName] = !this.props[propName];
29916
- this.props.onChange(changes);
29917
- }
29918
- });
29919
- const styles$h = aphrodite.StyleSheet.create({
29920
- labeledCheckbox: {
29921
- display: "flex",
29922
- flexDirection: "row",
29923
- alignItems: "center"
29924
- }
29925
- });
29926
-
29927
29895
  const truth = () => true;
29928
29896
 
29929
29897
  /* A minor abstraction on top of NumberInput for ranges
@@ -29982,7 +29950,6 @@ var components = /*#__PURE__*/Object.freeze({
29982
29950
  MathInput: MathInput,
29983
29951
  MultiButtonGroup: MultiButtonGroup,
29984
29952
  NumberInput: NumberInput,
29985
- PropCheckBox: PropCheckBox,
29986
29953
  RangeInput: RangeInput,
29987
29954
  SvgImage: SvgImage,
29988
29955
  TextInput: TextInput,
@@ -30140,8 +30107,9 @@ const lockedFigureColors = {
30140
30107
  };
30141
30108
  const lockedFigureFillStyles = {
30142
30109
  none: 0,
30143
- solid: 1,
30144
- translucent: 0.4
30110
+ white: 1,
30111
+ translucent: 0.4,
30112
+ solid: 1
30145
30113
  };
30146
30114
  const plotterPlotTypes = ["bar", "line", "pic", "histogram", "dotplot"];
30147
30115
 
@@ -30161,10 +30129,20 @@ const LockedEllipse = props => {
30161
30129
  fillOpacity: lockedFigureFillStyles[fillStyle],
30162
30130
  strokeStyle: strokeStyle,
30163
30131
  color: lockedFigureColors[color]
30132
+ // We need to override the svg props if we want to have a
30133
+ // different fill color than the stroke color (specifically,
30134
+ // in the case where the fillStyle is "white").
30135
+ ,
30136
+ svgEllipseProps: {
30137
+ style: {
30138
+ fill: fillStyle === "white" ? wonderBlocksTokens.color.white : lockedFigureColors[color]
30139
+ }
30140
+ }
30164
30141
  });
30165
30142
  };
30166
30143
 
30167
30144
  const LockedFunction = props => {
30145
+ const [equation, setEquation] = React.useState();
30168
30146
  const {
30169
30147
  color,
30170
30148
  strokeStyle,
@@ -30176,7 +30154,14 @@ const LockedFunction = props => {
30176
30154
  style: strokeStyle,
30177
30155
  domain
30178
30156
  };
30179
- const equation = props.equationParsed || KAS__namespace.parse(props.equation).expr;
30157
+ React.useEffect(() => {
30158
+ // Parsing the equation in a "useEffect" hook saves about 2ms each frame
30159
+ // when the learner is interacting with the graph (i.e. moving points).
30160
+ setEquation(KAS__namespace.parse(props.equation).expr);
30161
+ }, [props.equation]);
30162
+ if (typeof equation === "undefined") {
30163
+ return null;
30164
+ }
30180
30165
  return /*#__PURE__*/React__namespace.createElement("g", {
30181
30166
  className: "locked-function"
30182
30167
  }, directionalAxis === "x" && /*#__PURE__*/React__namespace.createElement(mafs.Plot.OfX, _extends({
@@ -30379,6 +30364,15 @@ const LockedPolygon = props => {
30379
30364
  fillOpacity: lockedFigureFillStyles[fillStyle],
30380
30365
  strokeStyle: strokeStyle,
30381
30366
  color: lockedFigureColors[color]
30367
+ // We need to override the svg props if we want to have a
30368
+ // different fill color than the stroke color (specifically,
30369
+ // in the case where the fillStyle is "white").
30370
+ ,
30371
+ svgPolygonProps: {
30372
+ style: {
30373
+ fill: fillStyle === "white" ? wonderBlocksTokens.color.white : lockedFigureColors[color]
30374
+ }
30375
+ }
30382
30376
  }), showVertices && points.map((point, index) => /*#__PURE__*/React__namespace.createElement(mafs.Point, {
30383
30377
  key: `locked-polygon-point-${index}`,
30384
30378
  x: point[X],
@@ -30625,7 +30619,7 @@ function useDraggable(args) {
30625
30619
  gestureTarget: target,
30626
30620
  onMove,
30627
30621
  point,
30628
- constrain
30622
+ constrainKeyboardMovement
30629
30623
  } = args;
30630
30624
  const [dragging, setDragging] = React__namespace.useState(false);
30631
30625
  const {
@@ -30648,6 +30642,7 @@ function useDraggable(args) {
30648
30642
  event?.stopPropagation();
30649
30643
  const isKeyboard = type.includes("key");
30650
30644
  if (isKeyboard) {
30645
+ invariant(event instanceof KeyboardEvent);
30651
30646
  event?.preventDefault();
30652
30647
 
30653
30648
  // When a key is held down, we see multiple "keydown" events,
@@ -30659,6 +30654,11 @@ function useDraggable(args) {
30659
30654
  if (type === "keyup") {
30660
30655
  return;
30661
30656
  }
30657
+ if (typeof constrainKeyboardMovement === "object") {
30658
+ const destination = constrainKeyboardMovement[directionForKey[event.key]];
30659
+ onMove(destination ?? point);
30660
+ return;
30661
+ }
30662
30662
  const {
30663
30663
  direction: yDownDirection,
30664
30664
  altKey,
@@ -30679,7 +30679,7 @@ function useDraggable(args) {
30679
30679
  for (const dx of tests) {
30680
30680
  // Transform the test back into the point's coordinate system
30681
30681
  const testMovement = mafs.vec.scale(direction, dx);
30682
- const testPoint = constrain(mafs.vec.transform(mafs.vec.add(mafs.vec.transform(point, userTransform), testMovement), inverseTransform));
30682
+ const testPoint = constrainKeyboardMovement(mafs.vec.transform(mafs.vec.add(mafs.vec.transform(point, userTransform), testMovement), inverseTransform));
30683
30683
  if (mafs.vec.dist(testPoint, point) > min) {
30684
30684
  onMove(testPoint);
30685
30685
  break;
@@ -30699,7 +30699,7 @@ function useDraggable(args) {
30699
30699
  return;
30700
30700
  }
30701
30701
  const movement = mafs.vec.transform(pixelMovement, inverseViewTransform);
30702
- onMove(constrain(mafs.vec.transform(mafs.vec.add(pickup.current, movement), inverseTransform)));
30702
+ onMove(mafs.vec.transform(mafs.vec.add(pickup.current, movement), inverseTransform));
30703
30703
  }
30704
30704
  }, {
30705
30705
  target,
@@ -30711,6 +30711,12 @@ function useDraggable(args) {
30711
30711
  dragging
30712
30712
  };
30713
30713
  }
30714
+ const directionForKey = {
30715
+ ArrowLeft: "left",
30716
+ ArrowRight: "right",
30717
+ ArrowUp: "up",
30718
+ ArrowDown: "down"
30719
+ };
30714
30720
  function getInverseTransform(transform) {
30715
30721
  const invert = mafs.vec.matrixInvert(transform);
30716
30722
  invariant(invert !== null, "Could not invert transform matrix. A parent transformation matrix might be degenerative (mapping 2D space to a line).");
@@ -30760,7 +30766,7 @@ const MovablePointView = /*#__PURE__*/React.forwardRef((props, hitboxRef) => {
30760
30766
  range,
30761
30767
  markings,
30762
30768
  showTooltips,
30763
- hintMode
30769
+ disableKeyboardInteraction
30764
30770
  } = useGraphConfig();
30765
30771
  const {
30766
30772
  point,
@@ -30805,7 +30811,7 @@ const MovablePointView = /*#__PURE__*/React.forwardRef((props, hitboxRef) => {
30805
30811
  cursor
30806
30812
  },
30807
30813
  "data-testid": "movable-point",
30808
- tabIndex: hintMode ? -1 : tabIndex(focusBehavior)
30814
+ tabIndex: disableKeyboardInteraction ? -1 : tabIndex(focusBehavior)
30809
30815
  }, /*#__PURE__*/React__namespace.createElement("circle", {
30810
30816
  className: "movable-point-hitbox",
30811
30817
  r: hitboxSizePx / 2,
@@ -30894,7 +30900,7 @@ const MovableLine = props => {
30894
30900
  function useControlPoint(point, color, onMovePoint) {
30895
30901
  const {
30896
30902
  snapStep,
30897
- hintMode
30903
+ disableKeyboardInteraction
30898
30904
  } = useGraphConfig();
30899
30905
  const [focused, setFocused] = React.useState(false);
30900
30906
  const keyboardHandleRef = React.useRef(null);
@@ -30902,7 +30908,7 @@ function useControlPoint(point, color, onMovePoint) {
30902
30908
  gestureTarget: keyboardHandleRef,
30903
30909
  point,
30904
30910
  onMove: onMovePoint,
30905
- constrain: p => snap(snapStep, p)
30911
+ constrainKeyboardMovement: p => snap(snapStep, p)
30906
30912
  });
30907
30913
  const visiblePointRef = React.useRef(null);
30908
30914
  const {
@@ -30911,12 +30917,12 @@ function useControlPoint(point, color, onMovePoint) {
30911
30917
  gestureTarget: visiblePointRef,
30912
30918
  point,
30913
30919
  onMove: onMovePoint,
30914
- constrain: p => snap(snapStep, p)
30920
+ constrainKeyboardMovement: p => snap(snapStep, p)
30915
30921
  });
30916
30922
  const focusableHandle = /*#__PURE__*/React__namespace.createElement("g", {
30917
30923
  "data-testid": "movable-point__focusable-handle",
30918
30924
  className: "movable-point__focusable-handle",
30919
- tabIndex: hintMode ? -1 : 0,
30925
+ tabIndex: disableKeyboardInteraction ? -1 : 0,
30920
30926
  ref: keyboardHandleRef,
30921
30927
  onFocus: () => setFocused(true),
30922
30928
  onBlur: () => setFocused(false)
@@ -30950,7 +30956,7 @@ const Line$1 = props => {
30950
30956
  range,
30951
30957
  graphDimensionsInPixels,
30952
30958
  snapStep,
30953
- hintMode
30959
+ disableKeyboardInteraction
30954
30960
  } = useGraphConfig();
30955
30961
  let startExtend = undefined;
30956
30962
  let endExtend = undefined;
@@ -30968,11 +30974,11 @@ const Line$1 = props => {
30968
30974
  onMove: newPoint => {
30969
30975
  onMove(mafs.vec.sub(newPoint, start));
30970
30976
  },
30971
- constrain: p => snap(snapStep, p)
30977
+ constrainKeyboardMovement: p => snap(snapStep, p)
30972
30978
  });
30973
30979
  return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement("g", {
30974
30980
  ref: line,
30975
- tabIndex: hintMode ? -1 : 0,
30981
+ tabIndex: disableKeyboardInteraction ? -1 : 0,
30976
30982
  className: "movable-line",
30977
30983
  "data-testid": "movable-line",
30978
30984
  style: {
@@ -31443,7 +31449,7 @@ function calculateBisectorPoint(point1, point2, vertex, isReflex, allowReflex, a
31443
31449
  return scaledBisector;
31444
31450
  }
31445
31451
 
31446
- const StyledMovablePoint = props => {
31452
+ const MovablePoint$1 = props => {
31447
31453
  const {
31448
31454
  snapStep
31449
31455
  } = useGraphConfig();
@@ -31453,16 +31459,15 @@ const StyledMovablePoint = props => {
31453
31459
  onMove,
31454
31460
  cursor,
31455
31461
  color = wonderBlocksTokens.color.blue,
31456
- snapTo
31462
+ constrain = p => snap(snapStep, p)
31457
31463
  } = props;
31458
- const snapToValue = snapTo ?? "grid";
31459
31464
  const {
31460
31465
  dragging
31461
31466
  } = useDraggable({
31462
31467
  gestureTarget: elementRef,
31463
31468
  point,
31464
31469
  onMove,
31465
- constrain: p => ["angles", "sides"].includes(snapToValue) ? p : snap(snapStep, p)
31470
+ constrainKeyboardMovement: constrain
31466
31471
  });
31467
31472
  return /*#__PURE__*/React__namespace.createElement(MovablePointView, {
31468
31473
  ref: elementRef,
@@ -31478,8 +31483,10 @@ const StyledMovablePoint = props => {
31478
31483
  };
31479
31484
 
31480
31485
  const PolygonGraph = props => {
31481
- const [focused, setFocused] = React__namespace.useState(false);
31482
31486
  const [hovered, setHovered] = React__namespace.useState(false);
31487
+ // This is more so required for the re-rendering that occurs when state
31488
+ // updates; specifically with regard to line weighting and polygon focus.
31489
+ const [focusVisible, setFocusVisible] = React__namespace.useState(false);
31483
31490
  const {
31484
31491
  dispatch
31485
31492
  } = props;
@@ -31489,10 +31496,10 @@ const PolygonGraph = props => {
31489
31496
  showSides,
31490
31497
  range,
31491
31498
  snapStep,
31492
- snapTo
31499
+ snapTo = "grid"
31493
31500
  } = props.graphState;
31494
31501
  const {
31495
- hintMode
31502
+ disableKeyboardInteraction
31496
31503
  } = useGraphConfig();
31497
31504
 
31498
31505
  // TODO(benchristel): can the default set of points be removed here? I don't
@@ -31500,7 +31507,7 @@ const PolygonGraph = props => {
31500
31507
  const points = coords ?? [[0, 0]];
31501
31508
  const ref = React__namespace.useRef(null);
31502
31509
  const dragReferencePoint = points[0];
31503
- const snapToValue = snapTo ?? "grid";
31510
+ const constrain = ["angles", "sides"].includes(snapTo) ? p => p : p => snap(snapStep, p);
31504
31511
  const {
31505
31512
  dragging
31506
31513
  } = useDraggable({
@@ -31510,16 +31517,15 @@ const PolygonGraph = props => {
31510
31517
  const delta = mafs.vec.sub(newPoint, dragReferencePoint);
31511
31518
  dispatch(actions.polygon.moveAll(delta));
31512
31519
  },
31513
- constrain: p => ["angles", "sides"].includes(snapToValue) ? p : snap(snapStep, p)
31520
+ constrainKeyboardMovement: constrain
31514
31521
  });
31515
- const active = hovered || focused || dragging;
31516
31522
  const lastMoveTime = React__namespace.useRef(0);
31517
31523
  const lines = getLines(points);
31518
31524
  return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(mafs.Polygon, {
31519
31525
  points: [...points],
31520
31526
  color: "var(--movable-line-stroke-color)",
31521
31527
  svgPolygonProps: {
31522
- strokeWidth: active ? "var(--movable-line-stroke-weight-active)" : "var(--movable-line-stroke-weight)",
31528
+ strokeWidth: focusVisible ? "var(--movable-line-stroke-weight-active)" : "var(--movable-line-stroke-weight)",
31523
31529
  style: {
31524
31530
  fill: "transparent"
31525
31531
  }
@@ -31534,11 +31540,10 @@ const PolygonGraph = props => {
31534
31540
  key: "angle-" + i,
31535
31541
  centerPoint: point,
31536
31542
  endPoints: [pt1, pt2],
31537
- active: active,
31538
31543
  range: range,
31539
31544
  polygonLines: lines,
31540
31545
  showAngles: !!showAngles,
31541
- snapTo: snapToValue
31546
+ snapTo: snapTo
31542
31547
  });
31543
31548
  }), showSides && lines.map((_ref, i) => {
31544
31549
  let [start, end] = _ref;
@@ -31554,21 +31559,29 @@ const PolygonGraph = props => {
31554
31559
  color: "transparent",
31555
31560
  svgPolygonProps: {
31556
31561
  ref,
31557
- tabIndex: hintMode ? -1 : 0,
31562
+ tabIndex: disableKeyboardInteraction ? -1 : 0,
31558
31563
  strokeWidth: TARGET_SIZE,
31559
31564
  style: {
31560
31565
  cursor: dragging ? "grabbing" : "grab",
31561
31566
  fill: hovered ? "var(--mafs-blue)" : "transparent"
31562
31567
  },
31563
- onFocus: () => setFocused(true),
31564
- onBlur: () => setFocused(false),
31565
31568
  onMouseEnter: () => setHovered(true),
31566
31569
  onMouseLeave: () => setHovered(false),
31570
+ // Required to remove line weighting when user clicks away
31571
+ // from the focused polygon
31572
+ onKeyDownCapture: () => {
31573
+ setFocusVisible(hasFocusVisible(ref.current));
31574
+ },
31575
+ // Required for lines to darken on focus
31576
+ onFocus: () => setFocusVisible(hasFocusVisible(ref.current)),
31577
+ // Required for line weighting to update on blur. Without this,
31578
+ // the user has to hover over the shape for it to update
31579
+ onBlur: () => setFocusVisible(hasFocusVisible(ref.current)),
31567
31580
  className: "movable-polygon"
31568
31581
  }
31569
- }), points.map((point, i) => /*#__PURE__*/React__namespace.createElement(StyledMovablePoint, {
31582
+ }), points.map((point, i) => /*#__PURE__*/React__namespace.createElement(MovablePoint$1, {
31570
31583
  key: "point-" + i,
31571
- snapTo: snapTo,
31584
+ constrain: constrain,
31572
31585
  point: point,
31573
31586
  onMove: destination => {
31574
31587
  const now = Date.now();
@@ -31587,6 +31600,17 @@ function getLines(points) {
31587
31600
  return [point, next];
31588
31601
  });
31589
31602
  }
31603
+ const hasFocusVisible = element => {
31604
+ const matches = selector => element?.matches(selector) ?? false;
31605
+ try {
31606
+ return matches(":focus-visible");
31607
+ } catch (e) {
31608
+ // jsdom doesn't support :focus-visible
31609
+ // (see https://github.com/jsdom/jsdom/issues/3426),
31610
+ // so the call to matches(":focus-visible") will fail in tests.
31611
+ return matches(":focus");
31612
+ }
31613
+ };
31590
31614
 
31591
31615
  function getGradableGraph(state, initialGraph) {
31592
31616
  if (!state.hasBeenInteractedWith) {
@@ -31692,7 +31716,7 @@ function CircleGraph(props) {
31692
31716
  center: center,
31693
31717
  radius: getRadius(graphState),
31694
31718
  onMove: c => dispatch(actions.circle.moveCenter(c))
31695
- }), /*#__PURE__*/React__namespace.createElement(StyledMovablePoint, {
31719
+ }), /*#__PURE__*/React__namespace.createElement(MovablePoint$1, {
31696
31720
  point: radiusPoint,
31697
31721
  cursor: "ew-resize",
31698
31722
  onMove: newRadiusPoint => {
@@ -31708,7 +31732,7 @@ function MovableCircle(props) {
31708
31732
  } = props;
31709
31733
  const {
31710
31734
  snapStep,
31711
- hintMode
31735
+ disableKeyboardInteraction
31712
31736
  } = useGraphConfig();
31713
31737
  const draggableRef = React.useRef(null);
31714
31738
  const {
@@ -31717,13 +31741,13 @@ function MovableCircle(props) {
31717
31741
  gestureTarget: draggableRef,
31718
31742
  point: center,
31719
31743
  onMove,
31720
- constrain: p => snap(snapStep, p)
31744
+ constrainKeyboardMovement: p => snap(snapStep, p)
31721
31745
  });
31722
31746
  const [centerPx] = useTransformVectorsToPixels(center);
31723
31747
  const [radiiPx] = useTransformDimensionsToPixels([radius, radius]);
31724
31748
  return /*#__PURE__*/React__namespace.createElement("g", {
31725
31749
  ref: draggableRef,
31726
- tabIndex: hintMode ? -1 : 0,
31750
+ tabIndex: disableKeyboardInteraction ? -1 : 0,
31727
31751
  className: `movable-circle ${dragging ? "movable-circle--dragging" : ""}`
31728
31752
  }, /*#__PURE__*/React__namespace.createElement("ellipse", {
31729
31753
  className: "focus-ring",
@@ -31804,7 +31828,7 @@ function QuadraticGraph(props) {
31804
31828
  return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(mafs.Plot.OfX, {
31805
31829
  y: y,
31806
31830
  color: wonderBlocksTokens.color.blue
31807
- }), coords.map((coord, i) => /*#__PURE__*/React__namespace.createElement(StyledMovablePoint, {
31831
+ }), coords.map((coord, i) => /*#__PURE__*/React__namespace.createElement(MovablePoint$1, {
31808
31832
  key: "point-" + i,
31809
31833
  point: coord,
31810
31834
  onMove: destination => dispatch(actions.quadratic.movePoint(i, destination))
@@ -31864,7 +31888,7 @@ function SinusoidGraph(props) {
31864
31888
  return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(mafs.Plot.OfX, {
31865
31889
  y: x => computeSine(x, coeffRef.current),
31866
31890
  color: wonderBlocksTokens.color.blue
31867
- }), coords.map((coord, i) => /*#__PURE__*/React__namespace.createElement(StyledMovablePoint, {
31891
+ }), coords.map((coord, i) => /*#__PURE__*/React__namespace.createElement(MovablePoint$1, {
31868
31892
  key: "point-" + i,
31869
31893
  point: coord,
31870
31894
  onMove: destination => dispatch(actions.sinusoid.movePoint(i, destination))
@@ -31920,7 +31944,6 @@ function AngleGraph(props) {
31920
31944
  showAngles,
31921
31945
  range,
31922
31946
  allowReflexAngles,
31923
- angleOffsetDeg,
31924
31947
  snapDegrees
31925
31948
  } = graphState;
31926
31949
 
@@ -31959,8 +31982,6 @@ function AngleGraph(props) {
31959
31982
  coords: endPoints,
31960
31983
  allowReflexAngles: allowReflexAngles || false,
31961
31984
  // Whether to allow reflex angles or not
31962
- angleOffsetDeg: angleOffsetDeg || 0,
31963
- // The angle offset from the x-axis
31964
31985
  snapDegrees: snapDegrees || 1,
31965
31986
  // The multiple of degrees to snap to
31966
31987
  range: range,
@@ -31968,19 +31989,65 @@ function AngleGraph(props) {
31968
31989
  };
31969
31990
 
31970
31991
  // Render the lines, angle, and then movable points
31971
- return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, svgLines, /*#__PURE__*/React__namespace.createElement(Angle, angleParams), coords.map((point, i) => /*#__PURE__*/React__namespace.createElement(StyledMovablePoint, {
31972
- key: "point-" + i,
31973
- snapTo: "angles",
31974
- point: point,
31975
- onMove: destination => dispatch(actions.angle.movePoint(i, destination))
31976
- })));
31992
+ return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, svgLines, /*#__PURE__*/React__namespace.createElement(Angle, angleParams), /*#__PURE__*/React__namespace.createElement(MovablePoint$1, {
31993
+ point: coords[1],
31994
+ constrain: p => p,
31995
+ onMove: destination => dispatch(actions.angle.movePoint(1, destination))
31996
+ }), /*#__PURE__*/React__namespace.createElement(MovablePoint$1, {
31997
+ point: coords[0],
31998
+ constrain: getAngleSideConstraint(coords[0], coords[1], snapDegrees || 1),
31999
+ onMove: destination => dispatch(actions.angle.movePoint(0, destination))
32000
+ }), /*#__PURE__*/React__namespace.createElement(MovablePoint$1, {
32001
+ point: coords[2],
32002
+ constrain: getAngleSideConstraint(coords[2], coords[1], snapDegrees || 1),
32003
+ onMove: destination => dispatch(actions.angle.movePoint(2, destination))
32004
+ }));
32005
+ }
32006
+ const positiveX = [1, 0];
32007
+ const negativeX = [-1, 0];
32008
+ const positiveY = [0, 1];
32009
+ const negativeY = [0, -1];
32010
+ function getAngleSideConstraint(sidePoint, vertex, snapDegrees) {
32011
+ const currentAngle = calculateAngleInDegrees(mafs.vec.sub(sidePoint, vertex));
32012
+
32013
+ // Find the rays that start at the current point and point up, down, left
32014
+ // and right.
32015
+ const leftRay = [sidePoint, mafs.vec.add(sidePoint, negativeX)];
32016
+ const rightRay = [sidePoint, mafs.vec.add(sidePoint, positiveX)];
32017
+ const upRay = [sidePoint, mafs.vec.add(sidePoint, positiveY)];
32018
+ const downRay = [sidePoint, mafs.vec.add(sidePoint, negativeY)];
32019
+
32020
+ // find the angles that lie one snap step clockwise and counter-clockwise
32021
+ // from the current angle. These are the angles to which the side can be
32022
+ // moved.
32023
+ const oneStepCounterClockwise = currentAngle + snapDegrees;
32024
+ const oneStepClockwise = currentAngle - snapDegrees;
32025
+
32026
+ // find the rays that start from the vertex and point in the direction of
32027
+ // the angles we just computed.
32028
+ const counterClockwiseRay = [vertex, mafs.vec.add(vertex, polar(1, oneStepCounterClockwise))];
32029
+ const clockwiseRay = [vertex, mafs.vec.add(vertex, polar(1, oneStepClockwise))];
32030
+
32031
+ // find the intersections of those rays with the horizontal and vertical
32032
+ // rays extending from the sidePoint. These intersections are the points to
32033
+ // which sidePoint can move that will result in a rotation of `snapDegrees`.
32034
+ const left = findIntersectionOfRays(leftRay, counterClockwiseRay) ?? findIntersectionOfRays(leftRay, clockwiseRay);
32035
+ const right = findIntersectionOfRays(rightRay, counterClockwiseRay) ?? findIntersectionOfRays(rightRay, clockwiseRay);
32036
+ const up = findIntersectionOfRays(upRay, counterClockwiseRay) ?? findIntersectionOfRays(upRay, clockwiseRay);
32037
+ const down = findIntersectionOfRays(downRay, counterClockwiseRay) ?? findIntersectionOfRays(downRay, clockwiseRay);
32038
+ return {
32039
+ up: up ?? sidePoint,
32040
+ down: down ?? sidePoint,
32041
+ left: left ?? sidePoint,
32042
+ right: right ?? sidePoint
32043
+ };
31977
32044
  }
31978
32045
 
31979
32046
  function PointGraph(props) {
31980
32047
  const {
31981
32048
  dispatch
31982
32049
  } = props;
31983
- return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, props.graphState.coords.map((point, i) => /*#__PURE__*/React__namespace.createElement(StyledMovablePoint, {
32050
+ return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, props.graphState.coords.map((point, i) => /*#__PURE__*/React__namespace.createElement(MovablePoint$1, {
31984
32051
  key: i,
31985
32052
  point: point,
31986
32053
  onMove: destination => dispatch(actions.pointGraph.movePoint(i, destination))
@@ -32012,7 +32079,7 @@ function Protractor() {
32012
32079
  gestureTarget: draggableRef,
32013
32080
  onMove: setCenter,
32014
32081
  point: center,
32015
- constrain: point => bound$1({
32082
+ constrainKeyboardMovement: point => bound$1({
32016
32083
  snapStep,
32017
32084
  range,
32018
32085
  point
@@ -32128,7 +32195,8 @@ const MafsGraph = props => {
32128
32195
  state,
32129
32196
  dispatch,
32130
32197
  labels,
32131
- hintMode
32198
+ hintMode,
32199
+ readOnly
32132
32200
  } = props;
32133
32201
  const [width, height] = props.box;
32134
32202
  const tickStep = props.step;
@@ -32144,7 +32212,7 @@ const MafsGraph = props => {
32144
32212
  width,
32145
32213
  height,
32146
32214
  labels,
32147
- hintMode
32215
+ disableKeyboardInteraction: hintMode || readOnly
32148
32216
  }
32149
32217
  }, /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
32150
32218
  className: "mafs-graph",
@@ -32509,6 +32577,9 @@ const getAngleCoords = params => {
32509
32577
  if (graph.coords) {
32510
32578
  return graph.coords;
32511
32579
  }
32580
+ if (graph.startCoords) {
32581
+ return graph.startCoords;
32582
+ }
32512
32583
  const {
32513
32584
  snapDegrees,
32514
32585
  angleOffsetDeg
@@ -32538,6 +32609,7 @@ const getAngleCoords = params => {
32538
32609
  return coords;
32539
32610
  };
32540
32611
 
32612
+ const minDistanceBetweenAngleVertexAndSidePoint = 2;
32541
32613
  function interactiveGraphReducer(state, action) {
32542
32614
  switch (action.type) {
32543
32615
  case REINITIALIZE:
@@ -32708,23 +32780,30 @@ function doMovePoint(state, action) {
32708
32780
  case "angle":
32709
32781
  // If the index is 1, we are moving the vertex of the angle,
32710
32782
  // which will move the other two points as well
32711
- if (action.index === 1) {
32712
- const updatedCoords = boundAndSnapAngleVertex(state, action);
32783
+ const newState = (() => {
32784
+ if (action.index === 1) {
32785
+ const updatedCoords = boundAndSnapAngleVertex(state, action);
32786
+ return {
32787
+ ...state,
32788
+ hasBeenInteractedWith: true,
32789
+ coords: updatedCoords
32790
+ };
32791
+ }
32713
32792
  return {
32714
32793
  ...state,
32715
32794
  hasBeenInteractedWith: true,
32716
- coords: updatedCoords
32795
+ coords: setAtIndex({
32796
+ array: state.coords,
32797
+ index: action.index,
32798
+ newValue: boundAndSnapAngleEndPoints(action.destination, state, action.index)
32799
+ })
32717
32800
  };
32801
+ })();
32802
+ if (angleSidePointsTooCloseToVertex(newState)) {
32803
+ // cancel the move
32804
+ return state;
32718
32805
  }
32719
- return {
32720
- ...state,
32721
- hasBeenInteractedWith: true,
32722
- coords: setAtIndex({
32723
- array: state.coords,
32724
- index: action.index,
32725
- newValue: boundAndSnapAngleEndPoints(action.destination, state, action.index)
32726
- })
32727
- };
32806
+ return newState;
32728
32807
  case "polygon":
32729
32808
  let newValue;
32730
32809
  if (state.snapTo === "sides") {
@@ -32817,11 +32896,7 @@ function doMoveCenter(state, action) {
32817
32896
  case "circle":
32818
32897
  {
32819
32898
  // Constrain the center of the circle to the chart range
32820
- const constrainedCenter = bound$1({
32821
- snapStep: state.snapStep,
32822
- range: state.range,
32823
- point: action.destination
32824
- });
32899
+ const constrainedCenter = boundAndSnapToGrid(action.destination, state);
32825
32900
 
32826
32901
  // Reposition the radius point based on the new center
32827
32902
  // (spread to make sure we're not going to mutate anything)
@@ -32856,10 +32931,10 @@ function doMoveRadiusPoint(state, action) {
32856
32931
  case "circle":
32857
32932
  {
32858
32933
  const [xMin, xMax] = state.range[X];
32859
- const nextRadiusPoint = [
32934
+ const nextRadiusPoint = snap(state.snapStep, [
32860
32935
  // Constrain to graph range
32861
32936
  // The +0 is to convert -0 to +0
32862
- clamp(action.destination[X] + 0, xMin, xMax), state.center[1]];
32937
+ clamp(action.destination[X] + 0, xMin, xMax), state.center[1]]);
32863
32938
  if (___default["default"].isEqual(nextRadiusPoint, state.center)) {
32864
32939
  return state;
32865
32940
  }
@@ -32949,17 +33024,16 @@ function boundAndSnapAngleVertex(_ref2, _ref3) {
32949
33024
 
32950
33025
  // Get the current and upcoming positions of the vertex
32951
33026
  const startingVertex = coordsCopy[1];
32952
- const newVertex = bound$1({
32953
- snapStep,
32954
- range,
32955
- point: snap(snapStep, destination)
32956
- });
33027
+
33028
+ // Prevent the vertex from getting too close to the edge, so that all
33029
+ // points of the angle can fit within the graph bounds.
33030
+ const insetAmount = mafs.vec.add(snapStep, [minDistanceBetweenAngleVertexAndSidePoint, minDistanceBetweenAngleVertexAndSidePoint]);
33031
+ const newVertex = clampToBox(inset(insetAmount, range), snap(snapStep, destination));
32957
33032
 
32958
33033
  // Get the vector from the starting vertex to the new vertex
32959
33034
  const delta = mafs.vec.add(newVertex, reverseVector(startingVertex));
32960
33035
 
32961
33036
  // Apply the delta to each of the other two points so that the angle is maintained
32962
- let valid = true;
32963
33037
  const newPoints = {};
32964
33038
  for (const i of [0, 2]) {
32965
33039
  const oldPoint = coordsCopy[i];
@@ -32968,22 +33042,14 @@ function boundAndSnapAngleVertex(_ref2, _ref3) {
32968
33042
  angle *= Math.PI / 180;
32969
33043
  newPoint = constrainToBoundsOnAngle(newPoint, angle, range, snapStep);
32970
33044
  newPoints[i] = newPoint;
32971
-
32972
- // Check if the new point is too close to the vertex
32973
- if (tooClose(newVertex, newPoint)) {
32974
- valid = false;
32975
- }
32976
33045
  }
32977
33046
 
32978
33047
  // Update the vertex after snapping to the snapStep
32979
33048
  newPoints[1] = newVertex;
32980
- // Only move points if all new positions are valid
32981
- if (valid) {
32982
- Object.entries(newPoints).forEach(_ref4 => {
32983
- let [i, newPoint] = _ref4;
32984
- coordsCopy[i] = newPoint;
32985
- });
32986
- }
33049
+ Object.entries(newPoints).forEach(_ref4 => {
33050
+ let [i, newPoint] = _ref4;
33051
+ coordsCopy[i] = newPoint;
33052
+ });
32987
33053
  return coordsCopy;
32988
33054
  }
32989
33055
 
@@ -33049,10 +33115,17 @@ function boundAndSnapAngleEndPoints(destinationPoint, _ref5, index) {
33049
33115
 
33050
33116
  // Snap the angle to the nearest multiple of snapDegrees (if provided)
33051
33117
  angle = Math.round((angle - offsetDegrees) / snap) * snap + offsetDegrees;
33052
- const distance = mafs.vec.dist(coordsCopy[index], vertex);
33118
+
33119
+ // add 0.01 to prevent rounding errors from causing the point to snap to
33120
+ // a location that is too close to the vertex.
33121
+ const minDistance = minDistanceBetweenAngleVertexAndSidePoint + 0.01;
33122
+ const distance = Math.max(mafs.vec.dist(coordsCopy[index], vertex), minDistance);
33053
33123
  const snappedValue = mafs.vec.add(vertex, polar(distance, angle));
33054
33124
  return snappedValue;
33055
33125
  }
33126
+ function angleSidePointsTooCloseToVertex(state) {
33127
+ return tooClose(state.coords[0], state.coords[1], state.range) || tooClose(state.coords[2], state.coords[1], state.range);
33128
+ }
33056
33129
  function boundAndSnapToPolygonAngle(destinationPoint, _ref6, index) {
33057
33130
  let {
33058
33131
  range,
@@ -33283,7 +33356,12 @@ const StatefulMafsGraph = /*#__PURE__*/React__namespace.forwardRef((props, ref)
33283
33356
  React.useEffect(() => {
33284
33357
  dispatch(changeRange([[xMinRange, xMaxRange], [yMinRange, yMaxRange]]));
33285
33358
  }, [dispatch, xMinRange, xMaxRange, yMinRange, yMaxRange]);
33359
+
33360
+ // Update the graph whenever any of the following values changes.
33361
+ // This is necessary to keep the graph previews in sync with the updated
33362
+ // graph settings within the interative graph editor.
33286
33363
  const numSegments = graph.type === "segment" ? graph.numSegments : null;
33364
+ const numPoints = graph.type === "point" ? graph.numPoints : null;
33287
33365
  const numSides = graph.type === "polygon" ? graph.numSides : null;
33288
33366
  const snapTo = graph.type === "polygon" ? graph.snapTo : null;
33289
33367
  const showAngles = graph.type === "polygon" ? graph.showAngles : null;
@@ -33298,7 +33376,7 @@ const StatefulMafsGraph = /*#__PURE__*/React__namespace.forwardRef((props, ref)
33298
33376
  if (latestPropsRef.current !== originalPropsRef.current) {
33299
33377
  dispatch(reinitialize(latestPropsRef.current));
33300
33378
  }
33301
- }, [graph.type, numSegments, numSides, snapTo, showAngles, showSides, latestPropsRef, graph.startCoords]);
33379
+ }, [graph.type, numPoints, numSegments, numSides, snapTo, showAngles, showSides, latestPropsRef, graph.startCoords]);
33302
33380
  return /*#__PURE__*/React__namespace.createElement(MafsGraph, _extends({}, props, {
33303
33381
  state: state,
33304
33382
  dispatch: dispatch
@@ -34602,7 +34680,8 @@ class InteractiveGraph extends React__namespace.Component {
34602
34680
  snapStep: snapStep,
34603
34681
  box: box,
34604
34682
  showTooltips: !!this.props.showTooltips,
34605
- hintMode: this.props.hintMode
34683
+ hintMode: this.props.hintMode,
34684
+ readOnly: this.props.apiOptions?.readOnly
34606
34685
  }));
34607
34686
  }
34608
34687
  return /*#__PURE__*/React__namespace.createElement(LegacyInteractiveGraph, _extends({
@@ -44524,7 +44603,7 @@ const init = function (options) {
44524
44603
 
44525
44604
  // This file is processed by a Rollup plugin (replace) to inject the production
44526
44605
  const libName = "@khanacademy/perseus";
44527
- const libVersion = "27.2.0";
44606
+ const libVersion = "28.1.0";
44528
44607
  perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
44529
44608
 
44530
44609
  const apiVersion = {
@@ -45082,7 +45161,6 @@ const context = /*#__PURE__*/React__namespace.createContext(defaultContext);
45082
45161
  const {
45083
45162
  mapObject
45084
45163
  } = objective_;
45085
- /* eslint-disable-next-line react/no-unsafe */
45086
45164
  class ServerItemRenderer extends React__namespace.Component {
45087
45165
  static contextType = PerseusI18nContext;
45088
45166
  // @ts-expect-error - TS2564 - Property 'questionRenderer' has no initializer and is not definitely assigned in the constructor.
@@ -46461,7 +46539,16 @@ class MultiRenderer extends React__namespace.Component {
46461
46539
  // _annotateRendererArray() needs this
46462
46540
  ref: null,
46463
46541
  makeRenderer: () => /*#__PURE__*/React__namespace.createElement(HintsRenderer, _extends({}, this._getRendererProps(), {
46464
- findExternalWidgets: findExternalWidgets,
46542
+ findExternalWidgets: findExternalWidgets
46543
+ // Note(Jeremy): The MultiRenderer codebase types are
46544
+ // slightly different from the rest of the codebase.
46545
+ // Ideally `HintNode` would spread the `Hint` type into it,
46546
+ // but there is a difference in optionality of `widgets`
46547
+ // and `images` and that causes a huge cascade of type
46548
+ // errors (they are truly optional, but most of our code
46549
+ // assumes they're provided/set). Ignoring this for now.
46550
+ // @ts-expect-error - TS2769 - Type 'HintNode' is not assignable to type 'Hint'.
46551
+ ,
46465
46552
  hints: [hint]
46466
46553
  }))
46467
46554
  };
@@ -48178,12 +48265,15 @@ exports.generateTestNumericInputWidget = generateTestNumericInputWidget;
48178
48265
  exports.generateTestPerseusItem = generateTestPerseusItem;
48179
48266
  exports.generateTestRadioWidget = generateTestRadioWidget;
48180
48267
  exports.genericPerseusItemData = genericPerseusItemData;
48268
+ exports.getAngleCoords = getAngleCoords;
48181
48269
  exports.getAnswerFromUserInput = getAnswerFromUserInput;
48182
48270
  exports.getAnswersFromWidgets = getAnswersFromWidgets;
48183
48271
  exports.getCircleCoords = getCircleCoords;
48184
48272
  exports.getCorrectAnswerForWidgetId = getCorrectAnswerForWidgetId;
48185
48273
  exports.getLineCoords = getLineCoords;
48186
48274
  exports.getLinearSystemCoords = getLinearSystemCoords;
48275
+ exports.getPointCoords = getPointCoords;
48276
+ exports.getPolygonCoords = getPolygonCoords;
48187
48277
  exports.getQuadraticCoords = getQuadraticCoords;
48188
48278
  exports.getSegmentCoords = getSegmentCoords;
48189
48279
  exports.getSinusoidCoords = getSinusoidCoords;