@khanacademy/perseus 27.1.0 → 28.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/dist/components/image-loader.d.ts +2 -2
  2. package/dist/components/{info-tip.d.ts → info-tip/index.d.ts} +2 -2
  3. package/dist/components/info-tip/info-tip-base.d.ts +6 -0
  4. package/dist/components.d.ts +0 -1
  5. package/dist/es/index.js +445 -341
  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 +433 -327
  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 +5 -2
  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 +5 -2
  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 +8 -8
  22. package/dist/util/{katex-preprocess.d.ts → tex-preprocess.d.ts} +1 -1
  23. package/dist/widgets/expression.d.ts +32 -14
  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 +10 -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 +10 -4
  36. package/package.json +6 -6
  37. package/dist/components/info-tip/info-tip.d.ts +0 -6
  38. package/dist/components/prop-check-box.d.ts +0 -4
package/dist/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",
@@ -1263,26 +1292,6 @@ const ClassNames = {
1263
1292
  MOBILE: "perseus-mobile"
1264
1293
  };
1265
1294
 
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
1295
  const KhanMath = {
1287
1296
  // Simplify formulas before display
1288
1297
  cleanMath: function (expr) {
@@ -2587,7 +2596,7 @@ class Expression extends React__namespace.Component {
2587
2596
  render() {
2588
2597
  if (this.props.apiOptions.customKeypad) {
2589
2598
  return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
2590
- className: aphrodite.css(styles$D.mobileLabelInputWrapper)
2599
+ className: aphrodite.css(styles$C.mobileLabelInputWrapper)
2591
2600
  }, !!this.props.visibleLabel && /*#__PURE__*/React__namespace.createElement(wonderBlocksTypography.LabelSmall, {
2592
2601
  htmlFor: this._textareaId,
2593
2602
  tag: "label"
@@ -2621,7 +2630,7 @@ class Expression extends React__namespace.Component {
2621
2630
  ERROR_TITLE
2622
2631
  } = this.context.strings;
2623
2632
  return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
2624
- className: aphrodite.css(styles$D.desktopLabelInputWrapper)
2633
+ className: aphrodite.css(styles$C.desktopLabelInputWrapper)
2625
2634
  }, !!this.props.visibleLabel && /*#__PURE__*/React__namespace.createElement(wonderBlocksTypography.LabelSmall, {
2626
2635
  htmlFor: this._textareaId,
2627
2636
  tag: "label"
@@ -2662,7 +2671,7 @@ class Expression extends React__namespace.Component {
2662
2671
  }))));
2663
2672
  }
2664
2673
  }
2665
- const styles$D = aphrodite.StyleSheet.create({
2674
+ const styles$C = aphrodite.StyleSheet.create({
2666
2675
  mobileLabelInputWrapper: {
2667
2676
  padding: "15px 4px 0"
2668
2677
  },
@@ -9039,8 +9048,8 @@ function findChildOrAdd(elem, className) {
9039
9048
  return $child;
9040
9049
  }
9041
9050
  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.
9051
+ // Process a node and add math inside of it. This uses MathJax to
9052
+ // format the math.
9044
9053
  //
9045
9054
  // elem: The element which the math should be added to.
9046
9055
  //
@@ -15435,7 +15444,7 @@ const DEFAULT_STATE$1 = {
15435
15444
  // TODO(benchristel): this is a memory leak. We add items to
15436
15445
  // tooltipResetFunctions, but never remove them.
15437
15446
  const tooltipResetFunctions = [];
15438
- class MovablePoint$4 {
15447
+ class MovablePoint$5 {
15439
15448
  graphie;
15440
15449
  movable;
15441
15450
  state;
@@ -15743,9 +15752,9 @@ class MovablePoint$4 {
15743
15752
  * Displays a tooltip above the point, replacing any previous contents. If
15744
15753
  * there is no tooltip initialized, adds the tooltip.
15745
15754
  *
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.
15755
+ * If the type of contents is string, the contents will be rendered as TeX
15756
+ * Otherwise, the content will be assumed to be a DOM node and will be
15757
+ * appended inside the tooltip.
15749
15758
  */
15750
15759
  _showTooltip(contents) {
15751
15760
  if (!this._tooltip) {
@@ -16568,10 +16577,10 @@ ___default["default"].extend(MovablePolygon.prototype, {
16568
16577
  });
16569
16578
 
16570
16579
  const Interactive2 = {
16571
- MovablePoint: MovablePoint$4,
16580
+ MovablePoint: MovablePoint$5,
16572
16581
  addMovablePoint: function (graphie, options) {
16573
16582
  const movable = new Movable(graphie, {});
16574
- return new MovablePoint$4(graphie, movable, options);
16583
+ return new MovablePoint$5(graphie, movable, options);
16575
16584
  },
16576
16585
  MovableLine: MovableLine$4,
16577
16586
  addMovableLine: function (graphie, options) {
@@ -16613,7 +16622,7 @@ const Interactive2 = {
16613
16622
  }
16614
16623
  };
16615
16624
 
16616
- const MovablePoint$3 = GraphieClasses.createClass({
16625
+ const MovablePoint$4 = GraphieClasses.createClass({
16617
16626
  displayName: "MovablePoint",
16618
16627
  movableProps: ["children"],
16619
16628
  _getProps: function () {
@@ -16671,7 +16680,7 @@ const MovablePoint$3 = GraphieClasses.createClass({
16671
16680
  });
16672
16681
 
16673
16682
  // Include helper methods, such as MovablePoint.constrain.snap()
16674
- ___default["default"].extend(MovablePoint$3, Interactive2.MovablePoint);
16683
+ ___default["default"].extend(MovablePoint$4, Interactive2.MovablePoint);
16675
16684
  const MovableLine$3 = GraphieClasses.createClass({
16676
16685
  displayName: "MovableLine",
16677
16686
  movableProps: ["children"],
@@ -16841,7 +16850,7 @@ var Movables = {
16841
16850
  Label: Label$1,
16842
16851
  Line: Line$3,
16843
16852
  MovableLine: MovableLine$3,
16844
- MovablePoint: MovablePoint$3,
16853
+ MovablePoint: MovablePoint$4,
16845
16854
  Parabola: Parabola,
16846
16855
  Path: Path,
16847
16856
  Plot: Plot$2,
@@ -17199,17 +17208,15 @@ class ImageLoader extends React__namespace.Component {
17199
17208
  src,
17200
17209
  imgProps
17201
17210
  } = this.props;
17202
- let onKeyUp = null;
17203
- let onKeyDown = null;
17211
+ let onKeyUp;
17212
+ let onKeyDown;
17204
17213
  if (imgProps.onClick != null) {
17205
- // @ts-expect-error - TS2322 - Type '(e: React.KeyboardEvent) => void' is not assignable to type 'null'.
17206
17214
  onKeyUp = e => {
17207
17215
  // 13 is enter key, 32 is space key
17208
17216
  if (e.keyCode === 13 || e.keyCode === 32) {
17209
17217
  imgProps.onClick && imgProps.onClick(e);
17210
17218
  }
17211
17219
  };
17212
- // @ts-expect-error - TS2322 - Type '(e: React.KeyboardEvent) => void' is not assignable to type 'null'.
17213
17220
  onKeyDown = e => {
17214
17221
  // 32 is space key
17215
17222
  if (e.keyCode === 32) {
@@ -17220,12 +17227,8 @@ class ImageLoader extends React__namespace.Component {
17220
17227
  }
17221
17228
  const staticUrl = getDependencies().staticUrl;
17222
17229
  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
- ,
17230
+ src: staticUrl(src),
17231
+ onKeyUp: onKeyUp,
17229
17232
  onKeyDown: onKeyDown
17230
17233
  }, imgProps));
17231
17234
  };
@@ -17716,18 +17719,14 @@ class SvgImage extends React__namespace.Component {
17716
17719
  allowFullBleed: this.props.allowFullBleed && isImageProbablyPhotograph(imageSrc)
17717
17720
  }, /*#__PURE__*/React__namespace.createElement(ImageLoader, {
17718
17721
  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
- ,
17722
+ imgProps: imageProps,
17722
17723
  preloader: preloader,
17723
17724
  onUpdate: this.handleUpdate
17724
17725
  }), extraGraphie);
17725
17726
  }
17726
17727
  imageProps.style = dimensions;
17727
17728
  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
- ,
17729
+ src: imageSrc,
17731
17730
  preloader: preloader,
17732
17731
  imgProps: imageProps,
17733
17732
  onUpdate: this.handleUpdate
@@ -17777,9 +17776,7 @@ class SvgImage extends React__namespace.Component {
17777
17776
  }, /*#__PURE__*/React__namespace.createElement(ImageLoader, {
17778
17777
  src: imageUrl,
17779
17778
  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
- ,
17779
+ onUpdate: this.handleUpdate,
17783
17780
  preloader: preloader,
17784
17781
  imgProps: imageProps
17785
17782
  }), graphie, extraGraphie);
@@ -17791,9 +17788,7 @@ class SvgImage extends React__namespace.Component {
17791
17788
  }, /*#__PURE__*/React__namespace.createElement(ImageLoader, {
17792
17789
  src: imageUrl,
17793
17790
  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
- ,
17791
+ onUpdate: this.handleUpdate,
17797
17792
  preloader: preloader,
17798
17793
  imgProps: imageProps
17799
17794
  }), graphie);
@@ -18371,33 +18366,33 @@ class Lint extends React__namespace.Component {
18371
18366
  let warningText;
18372
18367
  let warningTextStyle;
18373
18368
  if (this.props.severity === Severity.Error) {
18374
- severityStyle = styles$C.indicatorError;
18369
+ severityStyle = styles$B.indicatorError;
18375
18370
  warningText = "Error";
18376
- warningTextStyle = styles$C.publishBlockingError;
18371
+ warningTextStyle = styles$B.publishBlockingError;
18377
18372
  } else if (this.props.severity === Severity.Warning) {
18378
- severityStyle = styles$C.indicatorWarning;
18373
+ severityStyle = styles$B.indicatorWarning;
18379
18374
  warningText = "Warning";
18380
- warningTextStyle = styles$C.warning;
18375
+ warningTextStyle = styles$B.warning;
18381
18376
  } else {
18382
- severityStyle = styles$C.indicatorGuideline;
18377
+ severityStyle = styles$B.indicatorGuideline;
18383
18378
  warningText = "Recommendation";
18384
- warningTextStyle = styles$C.warning;
18379
+ warningTextStyle = styles$B.warning;
18385
18380
  }
18386
18381
  return /*#__PURE__*/React__namespace.createElement("a", {
18387
18382
  href: `https://khanacademy.org/r/linter-rules#${this.props.ruleName}`,
18388
18383
  target: "lint-help-window",
18389
18384
  className: aphrodite.css(style)
18390
18385
  }, /*#__PURE__*/React__namespace.createElement("span", {
18391
- className: aphrodite.css(styles$C.indicator, severityStyle)
18386
+ className: aphrodite.css(styles$B.indicator, severityStyle)
18392
18387
  }, 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)
18388
+ className: aphrodite.css(styles$B.tooltip, tooltipAbove && styles$B.tooltipAbove)
18394
18389
  }, this.props.message.split("\n\n").map((m, i) => /*#__PURE__*/React__namespace.createElement("p", {
18395
18390
  key: i,
18396
- className: aphrodite.css(styles$C.tooltipParagraph)
18391
+ className: aphrodite.css(styles$B.tooltipParagraph)
18397
18392
  }, /*#__PURE__*/React__namespace.createElement("span", {
18398
18393
  className: aphrodite.css(warningTextStyle)
18399
18394
  }, warningText, ":", " "), m)), /*#__PURE__*/React__namespace.createElement("div", {
18400
- className: aphrodite.css(styles$C.tail, tooltipAbove && styles$C.tailAbove)
18395
+ className: aphrodite.css(styles$B.tail, tooltipAbove && styles$B.tailAbove)
18401
18396
  })));
18402
18397
  };
18403
18398
 
@@ -18425,20 +18420,20 @@ class Lint extends React__namespace.Component {
18425
18420
  }
18426
18421
  if (blockHighlight) {
18427
18422
  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));
18423
+ className: aphrodite.css(styles$B.lintContainer, styles$B.lintContainerBlock)
18424
+ }, this.renderLink(styles$B.radioWidgetHoverTarget), /*#__PURE__*/React__namespace.createElement("span", null, children));
18430
18425
  }
18431
18426
  if (inline) {
18432
18427
  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));
18428
+ className: aphrodite.css(styles$B.lintContainer)
18429
+ }, this.renderLink(styles$B.inlineHoverTarget), /*#__PURE__*/React__namespace.createElement("span", null, children));
18435
18430
  }
18436
18431
  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));
18432
+ className: aphrodite.css(styles$B.lintContainer)
18433
+ }, this.renderLink(styles$B.hoverTarget), /*#__PURE__*/React__namespace.createElement("div", null, children));
18439
18434
  }
18440
18435
  }
18441
- const styles$C = aphrodite.StyleSheet.create({
18436
+ const styles$B = aphrodite.StyleSheet.create({
18442
18437
  // This is the class of the outermost element.
18443
18438
  // We use relative positioning so that the lint indicator can be
18444
18439
  // positioned absolutely relative to the position of the linty container.
@@ -19132,15 +19127,17 @@ class TranslationLinter {
19132
19127
  }
19133
19128
 
19134
19129
  /**
19135
- * Preprocess TeX code to convert things that KaTeX doesn't know how to handle
19130
+ * Preprocess TeX code to convert things that MathJax doesn't know how to handle
19136
19131
  * to things is does.
19137
19132
  */
19138
-
19139
19133
  var preprocessTex = (texCode => texCode
19140
19134
  // Replace uses of \begin{align}...\end{align} which KaTeX doesn't
19141
19135
  // support (yet) with \begin{aligned}...\end{aligned} which renders
19142
19136
  // the same is supported by KaTeX. It does the same for align*.
19143
19137
  // TODO(kevinb) update content to use aligned instead of align.
19138
+ // TODO(LEMS-1608) Remove this replacement as MathJax supports the
19139
+ // "align" macro correctly (and, in fact, it is not synonymous with
19140
+ // "aligned").
19144
19141
  .replace(/\{align[*]?\}/g, "{aligned}")
19145
19142
  // Replace non-breaking spaces with regular spaces.
19146
19143
  .replace(/[\u00a0]/g, " "));
@@ -20574,7 +20571,7 @@ class Renderer extends React__namespace.Component {
20574
20571
  };
20575
20572
  const mobileInnerStyle = {
20576
20573
  ...innerStyle,
20577
- ...styles$B.mobileZoomableParentFix
20574
+ ...styles$A.mobileZoomableParentFix
20578
20575
  };
20579
20576
  return /*#__PURE__*/React__namespace.createElement("div", {
20580
20577
  key: state.key,
@@ -20608,6 +20605,9 @@ class Renderer extends React__namespace.Component {
20608
20605
  // support (yet) with \begin{aligned}...\end{aligned} which renders
20609
20606
  // the same is supported by KaTeX. It does the same for align*.
20610
20607
  // TODO(kevinb) update content to use aligned instead of align.
20608
+ // TODO(LEMS-1608) Remove this replacement as MathJax supports the
20609
+ // "align" macro correctly (and, in fact, it is not synonymous with
20610
+ // "aligned").
20611
20611
  const tex = node.content.replace(/\{align[*]?\}/g, "{aligned}");
20612
20612
 
20613
20613
  // We render math here instead of in perseus-markdown.jsx
@@ -20711,7 +20711,7 @@ class Renderer extends React__namespace.Component {
20711
20711
  };
20712
20712
  const mobileInnerStyle = {
20713
20713
  ...innerStyle,
20714
- ...styles$B.mobileZoomableParentFix
20714
+ ...styles$A.mobileZoomableParentFix
20715
20715
  };
20716
20716
  const wrappedOutput = /*#__PURE__*/React__namespace.createElement("div", {
20717
20717
  style: {
@@ -21219,7 +21219,7 @@ class Renderer extends React__namespace.Component {
21219
21219
  return this.lastRenderedMarkdown;
21220
21220
  }
21221
21221
  }
21222
- const styles$B = {
21222
+ const styles$A = {
21223
21223
  mobileZoomableParentFix: {
21224
21224
  // NOTE(abdul): There is an issue where transform animations will
21225
21225
  // cause the Zoomable component to disappear when running on the
@@ -22019,10 +22019,10 @@ class InputNumber extends React__namespace.Component {
22019
22019
  // Note: This is _very_ similar to what `numeric-input.jsx` does. If
22020
22020
  // you modify this, double-check if you also need to modify that
22021
22021
  // component.
22022
- const inputStyles = [styles$A.default, this.props.size === "small" ? styles$A.small : null, this.props.rightAlign ? styles$A.rightAlign : styles$A.leftAlign];
22022
+ const inputStyles = [styles$z.default, this.props.size === "small" ? styles$z.small : null, this.props.rightAlign ? styles$z.rightAlign : styles$z.leftAlign];
22023
22023
  // Unanswered
22024
22024
  if (rubric && !this.props.currentValue) {
22025
- inputStyles.push(styles$A.answerStateUnanswered);
22025
+ inputStyles.push(styles$z.answerStateUnanswered);
22026
22026
  }
22027
22027
  return /*#__PURE__*/React__namespace.createElement(InputWithExamples
22028
22028
  // eslint-disable-next-line react/no-string-refs
@@ -22162,7 +22162,7 @@ class InputNumber extends React__namespace.Component {
22162
22162
  return answerString;
22163
22163
  }
22164
22164
  }
22165
- const styles$A = aphrodite.StyleSheet.create({
22165
+ const styles$z = aphrodite.StyleSheet.create({
22166
22166
  default: {
22167
22167
  width: 80,
22168
22168
  height: "auto"
@@ -23155,7 +23155,7 @@ const FocusRing = function (props) {
23155
23155
  };
23156
23156
  return /*#__PURE__*/React__namespace.createElement("span", {
23157
23157
  "data-testid": "focus-ring",
23158
- className: aphrodite.css(styles$z.ring),
23158
+ className: aphrodite.css(styles$y.ring),
23159
23159
  style: style
23160
23160
  }, children);
23161
23161
  };
@@ -23164,7 +23164,7 @@ FocusRing.defaultProps = {
23164
23164
  color: kaGreen$1,
23165
23165
  multipleSelect: false
23166
23166
  };
23167
- const styles$z = aphrodite.StyleSheet.create({
23167
+ const styles$y = aphrodite.StyleSheet.create({
23168
23168
  ring: {
23169
23169
  margin: "auto",
23170
23170
  display: "inline-block",
@@ -23191,7 +23191,7 @@ function getChoiceLetter(pos, strings) {
23191
23191
  }
23192
23192
 
23193
23193
  const CHOICE_ICON_SIZE = 24;
23194
- const styles$y = aphrodite.StyleSheet.create({
23194
+ const styles$x = aphrodite.StyleSheet.create({
23195
23195
  iconWrapper: {
23196
23196
  display: "inline-block",
23197
23197
  position: "relative"
@@ -23214,7 +23214,7 @@ const CrossOutLine = function (props) {
23214
23214
  width: CROSS_OUT_LINE_SIZE,
23215
23215
  height: CROSS_OUT_LINE_SIZE,
23216
23216
  viewBox: `0 0 ${CROSS_OUT_LINE_SIZE} ${CROSS_OUT_LINE_SIZE}`,
23217
- className: aphrodite.css(styles$x.crossOutLine),
23217
+ className: aphrodite.css(styles$w.crossOutLine),
23218
23218
  "data-testid": "choice-icon__cross-out-line"
23219
23219
  }, /*#__PURE__*/React__namespace.createElement("line", {
23220
23220
  // The line stretches from the bottom-left to top-right.
@@ -23229,7 +23229,7 @@ const CrossOutLine = function (props) {
23229
23229
  strokeLinecap: "round"
23230
23230
  }));
23231
23231
  };
23232
- const styles$x = aphrodite.StyleSheet.create({
23232
+ const styles$w = aphrodite.StyleSheet.create({
23233
23233
  crossOutLine: {
23234
23234
  // Center the icon within the container.
23235
23235
  position: "absolute",
@@ -23313,7 +23313,7 @@ const ChoiceIcon = function (props) {
23313
23313
  } = props;
23314
23314
  const dynamicStyles = getDynamicStyles(checked, showCorrectness, pressed, multipleSelect, correct);
23315
23315
  return /*#__PURE__*/React__namespace.createElement("div", {
23316
- className: aphrodite.css(styles$y.iconWrapper)
23316
+ className: aphrodite.css(styles$x.iconWrapper)
23317
23317
  }, /*#__PURE__*/React__namespace.createElement(FocusRing, {
23318
23318
  color: wonderBlocksTokens.color.blue,
23319
23319
  visible: focused || hovered,
@@ -23322,13 +23322,13 @@ const ChoiceIcon = function (props) {
23322
23322
  // @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
23323
  style: dynamicStyles,
23324
23324
  "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)
23325
+ className: aphrodite.css(styles$v.circle, showCorrectness && correct && styles$v.circleCorrect, showCorrectness && !correct && styles$v.circleIncorrect, showCorrectness && !correct && (checked || previouslyAnswered) && styles$v.circleIncorrectAnswered)
23326
23326
  // used in BaseRadio editMode to check
23327
23327
  // if we actually clicked on the radio icon
23328
23328
  ,
23329
23329
  "data-is-radio-icon": true
23330
23330
  }, /*#__PURE__*/React__namespace.createElement("div", {
23331
- className: aphrodite.css(styles$w.innerWrapper)
23331
+ className: aphrodite.css(styles$v.innerWrapper)
23332
23332
  }, /*#__PURE__*/React__namespace.createElement(ChoiceInner, {
23333
23333
  pos: pos,
23334
23334
  showCorrectness: showCorrectness,
@@ -23337,7 +23337,7 @@ const ChoiceIcon = function (props) {
23337
23337
  color: dynamicStyles.borderColor
23338
23338
  }));
23339
23339
  };
23340
- const styles$w = aphrodite.StyleSheet.create({
23340
+ const styles$v = aphrodite.StyleSheet.create({
23341
23341
  innerWrapper: {
23342
23342
  width: CHOICE_ICON_SIZE,
23343
23343
  height: CHOICE_ICON_SIZE,
@@ -23432,19 +23432,19 @@ const OptionStatus = function (props) {
23432
23432
  }
23433
23433
  let textStyle;
23434
23434
  if (correct) {
23435
- textStyle = styles$v.correct;
23435
+ textStyle = styles$u.correct;
23436
23436
  } else {
23437
23437
  if (checked || previouslyAnswered) {
23438
- textStyle = styles$v.incorrectAnswered;
23438
+ textStyle = styles$u.incorrectAnswered;
23439
23439
  } else {
23440
- textStyle = styles$v.incorrect;
23440
+ textStyle = styles$u.incorrect;
23441
23441
  }
23442
23442
  }
23443
23443
  return /*#__PURE__*/React__namespace.createElement("div", {
23444
- className: aphrodite.css(styles$v.text, textStyle)
23444
+ className: aphrodite.css(styles$u.text, textStyle)
23445
23445
  }, renderText(checked, correct, crossedOut, strings));
23446
23446
  };
23447
- const styles$v = aphrodite.StyleSheet.create({
23447
+ const styles$u = aphrodite.StyleSheet.create({
23448
23448
  text: {
23449
23449
  alignItems: "center",
23450
23450
  display: "flex",
@@ -23513,8 +23513,8 @@ const Choice = function (props) {
23513
23513
  crossedOut: updatedCrossedOut
23514
23514
  });
23515
23515
  }
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));
23516
+ const descriptionClassName = classNames__default["default"]("description", aphrodite.css(styles$t.description));
23517
+ const rationaleClassName = classNames__default["default"]("perseus-radio-rationale-content", aphrodite.css(styles$t.rationale));
23518
23518
 
23519
23519
  // We want to show the choices as dimmed out when the choices are disabled.
23520
23520
  // However, we don't want to do this when we're in review mode in the
@@ -23681,7 +23681,7 @@ const Choice = function (props) {
23681
23681
  "data-testid": `perseus-radio-rationale-content-${pos}`
23682
23682
  }, rationale));
23683
23683
  };
23684
- const styles$u = aphrodite.StyleSheet.create({
23684
+ const styles$t = aphrodite.StyleSheet.create({
23685
23685
  description: {
23686
23686
  display: "inline-block",
23687
23687
  width: "100%"
@@ -23880,10 +23880,10 @@ const BaseRadio = function (props) {
23880
23880
  const isMobile = apiOptions.isMobile;
23881
23881
  const firstChoiceHighlighted = choices[0].highlighted;
23882
23882
  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));
23883
+ 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));
23884
+ const instructionsClassName = classNames__default["default"]("instructions", aphrodite.css(styles$s.instructions, isMobile && styles$s.instructionsMobile));
23885
23885
  const instructions = getInstructionsText(multipleSelect, countChoices, numCorrect, strings);
23886
- const responsiveClassName = aphrodite.css(styles$t.responsiveFieldset);
23886
+ const responsiveClassName = aphrodite.css(styles$s.responsiveFieldset);
23887
23887
  const fieldset = /*#__PURE__*/React__namespace.createElement("fieldset", {
23888
23888
  className: `perseus-widget-radio-fieldset ${responsiveClassName}`
23889
23889
  }, /*#__PURE__*/React__namespace.createElement("legend", {
@@ -23934,7 +23934,7 @@ const BaseRadio = function (props) {
23934
23934
  const aphroditeClassName = checked => {
23935
23935
  // Whether or not to show correctness borders
23936
23936
  // 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);
23937
+ 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
23938
  };
23939
23939
 
23940
23940
  // HACK(abdulrahman): Preloads the selection-state
@@ -24008,14 +24008,14 @@ const BaseRadio = function (props) {
24008
24008
  // Allow for horizontal scrolling if content is too wide, which may be
24009
24009
  // an issue especially on phones.
24010
24010
  return /*#__PURE__*/React__namespace.createElement("div", {
24011
- className: aphrodite.css(styles$t.responsiveContainer)
24011
+ className: aphrodite.css(styles$s.responsiveContainer)
24012
24012
  }, fieldset);
24013
24013
  };
24014
24014
  BaseRadio.defaultProps = {
24015
24015
  editMode: false,
24016
24016
  multipleSelect: false
24017
24017
  };
24018
- const styles$t = aphrodite.StyleSheet.create({
24018
+ const styles$s = aphrodite.StyleSheet.create({
24019
24019
  instructions: {
24020
24020
  display: "block",
24021
24021
  color: gray17,
@@ -24699,9 +24699,9 @@ class Categorizer extends React__namespace.Component {
24699
24699
  indexedItems = Util.shuffle(indexedItems, this.props.problemNum);
24700
24700
  }
24701
24701
  const table = /*#__PURE__*/React__namespace.createElement("table", {
24702
- className: "categorizer-table " + aphrodite.css(styles$s.mobileTable)
24702
+ className: "categorizer-table " + aphrodite.css(styles$r.mobileTable)
24703
24703
  }, /*#__PURE__*/React__namespace.createElement("thead", null, /*#__PURE__*/React__namespace.createElement("tr", null, /*#__PURE__*/React__namespace.createElement("td", {
24704
- className: aphrodite.css(styles$s.emptyHeaderCell)
24704
+ className: aphrodite.css(styles$r.emptyHeaderCell)
24705
24705
  }), this.props.categories.map((category, i) => {
24706
24706
  // Array index is the correct key here, as that's
24707
24707
  // how category grading actually works -- no way
@@ -24709,7 +24709,7 @@ class Categorizer extends React__namespace.Component {
24709
24709
  // middle. (If we later add that, this should be
24710
24710
  // fixed.)
24711
24711
  return /*#__PURE__*/React__namespace.createElement("th", {
24712
- className: aphrodite.css(styles$s.header),
24712
+ className: aphrodite.css(styles$r.header),
24713
24713
  key: i
24714
24714
  }, /*#__PURE__*/React__namespace.createElement(Renderer, {
24715
24715
  content: category,
@@ -24731,7 +24731,7 @@ class Categorizer extends React__namespace.Component {
24731
24731
  })), self.props.categories.map((catName, catNum) => {
24732
24732
  const selected = self.props.values[itemNum] === catNum;
24733
24733
  return /*#__PURE__*/React__namespace.createElement("td", {
24734
- className: "category " + aphrodite.css(styles$s.cell),
24734
+ className: "category " + aphrodite.css(styles$r.cell),
24735
24735
  key: catNum
24736
24736
  }, /*#__PURE__*/React__namespace.createElement("div", {
24737
24737
  className: ClassNames.INTERACTIVE,
@@ -24746,7 +24746,7 @@ class Categorizer extends React__namespace.Component {
24746
24746
  onChange: () => this.onChange(itemNum, catNum),
24747
24747
  onClick: e => e.stopPropagation()
24748
24748
  }), !isMobile && /*#__PURE__*/React__namespace.createElement("span", {
24749
- className: aphrodite.css(styles$s.radioSpan, selected && styles$s.checkedRadioSpan, this.props.static && selected && styles$s.staticCheckedRadioSpan)
24749
+ className: aphrodite.css(styles$r.radioSpan, selected && styles$r.checkedRadioSpan, this.props.static && selected && styles$r.staticCheckedRadioSpan)
24750
24750
  }, selected ? /*#__PURE__*/React__namespace.createElement(InlineIcon, iconCircle) : /*#__PURE__*/React__namespace.createElement(InlineIcon, iconCircleThin))));
24751
24751
  }));
24752
24752
  /* eslint-enable max-len */
@@ -24758,7 +24758,7 @@ class Categorizer extends React__namespace.Component {
24758
24758
  "categorizer-container": true,
24759
24759
  "static-mode": this.props.static
24760
24760
  });
24761
- const inlineStyles = this.props.apiOptions.isMobile ? [styles$s.fullBleedContainer] : [];
24761
+ const inlineStyles = this.props.apiOptions.isMobile ? [styles$r.fullBleedContainer] : [];
24762
24762
  return /*#__PURE__*/React__namespace.createElement("div", {
24763
24763
  className: extraClassNames + " " + aphrodite.css(...inlineStyles)
24764
24764
  }, table);
@@ -24807,7 +24807,7 @@ const pageMargin = 16;
24807
24807
 
24808
24808
  // Stylesheets aren't directly testable
24809
24809
  /* c8 ignore next */
24810
- const styles$s = aphrodite.StyleSheet.create({
24810
+ const styles$r = aphrodite.StyleSheet.create({
24811
24811
  mobileTable: {
24812
24812
  [mediaQueries.smOrSmaller]: {
24813
24813
  minWidth: "auto"
@@ -25070,7 +25070,7 @@ class CSProgram extends React__namespace.Component {
25070
25070
  // creator "went wild".
25071
25071
  // http://www.html5rocks.com/en/tutorials/security/sandboxed-iframes/
25072
25072
  return /*#__PURE__*/React__namespace.createElement("div", {
25073
- className: aphrodite.css(styles$r.widthOverride, styleContainer && styles$r.container)
25073
+ className: aphrodite.css(styles$q.widthOverride, styleContainer && styles$q.container)
25074
25074
  }, /*#__PURE__*/React__namespace.createElement("iframe", {
25075
25075
  sandbox: sandboxOptions,
25076
25076
  src: url,
@@ -25080,7 +25080,7 @@ class CSProgram extends React__namespace.Component {
25080
25080
  }));
25081
25081
  }
25082
25082
  }
25083
- const styles$r = aphrodite.StyleSheet.create({
25083
+ const styles$q = aphrodite.StyleSheet.create({
25084
25084
  // Override the inherited width from the perseus paragraph class
25085
25085
  // 820 is minimum width for 420px editor and 400px canvas
25086
25086
  widthOverride: {
@@ -25127,7 +25127,7 @@ class Definition extends React__namespace.Component {
25127
25127
  return /*#__PURE__*/React__namespace.createElement(wonderBlocksPopover.Popover, {
25128
25128
  content: /*#__PURE__*/React__namespace.createElement(wonderBlocksPopover.PopoverContentCore, {
25129
25129
  color: "white",
25130
- style: styles$q.tooltipBody,
25130
+ style: styles$p.tooltipBody,
25131
25131
  closeButtonVisible: true
25132
25132
  }, /*#__PURE__*/React__namespace.createElement(Renderer, {
25133
25133
  apiOptions: this.props.apiOptions,
@@ -25159,7 +25159,7 @@ class Definition extends React__namespace.Component {
25159
25159
  });
25160
25160
  }
25161
25161
  }
25162
- const styles$q = {
25162
+ const styles$p = {
25163
25163
  tooltipBody: {
25164
25164
  color: wonderBlocksTokens.color.offBlack,
25165
25165
  fontSize: 20,
@@ -25386,7 +25386,7 @@ class Explanation extends React__namespace.Component {
25386
25386
  marginRight: "-6px",
25387
25387
  "white-space": "normal"
25388
25388
  };
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)];
25389
+ 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
25390
  return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.UniqueIDProvider, {
25391
25391
  mockOnFirstRender: true,
25392
25392
  scope: "explanation-widget"
@@ -25405,7 +25405,7 @@ class Explanation extends React__namespace.Component {
25405
25405
  "aria-hidden": !this.state.expanded,
25406
25406
  testId: "content-container"
25407
25407
  }, /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
25408
- style: styles$p.contentWrapper
25408
+ style: styles$o.contentWrapper
25409
25409
  }, /*#__PURE__*/React__namespace.createElement(Renderer, {
25410
25410
  apiOptions: this.props.apiOptions,
25411
25411
  content: this.props.explanation,
@@ -25424,7 +25424,7 @@ class Explanation extends React__namespace.Component {
25424
25424
  const leftBorderSpacing = 23;
25425
25425
  const verticalContentPadding = 10;
25426
25426
  const arrowHeight = 14;
25427
- const styles$p = aphrodite.StyleSheet.create({
25427
+ const styles$o = aphrodite.StyleSheet.create({
25428
25428
  content: {
25429
25429
  borderLeft: "0px solid #ccc",
25430
25430
  display: "inline-grid",
@@ -25490,15 +25490,15 @@ class GradedGroupAnswerBar extends React__namespace.Component {
25490
25490
  nextQuestion
25491
25491
  } = this.context.strings;
25492
25492
  const answerBarStyle = {
25493
- ...styles$o.answerBar,
25493
+ ...styles$n.answerBar,
25494
25494
  backgroundColor: answerBarState === "CORRECT" ? wonderBlocksTokens.color.offWhite : "white",
25495
25495
  // Center the "Correct!" message only when there's no next question
25496
25496
  justifyContent: answerBarState === "CORRECT" && !onNextQuestion ? "center" : "space-between"
25497
25497
  };
25498
25498
  const message = answerBarState === "INCORRECT" ? /*#__PURE__*/React__namespace.createElement("span", {
25499
- style: styles$o.text
25499
+ style: styles$n.text
25500
25500
  }, /*#__PURE__*/React__namespace.createElement("span", {
25501
- style: styles$o.tryAgainIcon
25501
+ style: styles$n.tryAgainIcon
25502
25502
  }, /*#__PURE__*/React__namespace.createElement(InlineIcon, iconTryAgain)), /*#__PURE__*/React__namespace.createElement("span", {
25503
25503
  style: {
25504
25504
  marginLeft: 8
@@ -25517,7 +25517,7 @@ class GradedGroupAnswerBar extends React__namespace.Component {
25517
25517
  return /*#__PURE__*/React__namespace.createElement("div", {
25518
25518
  style: answerBarStyle
25519
25519
  }, /*#__PURE__*/React__namespace.createElement("span", {
25520
- style: styles$o.text
25520
+ style: styles$n.text
25521
25521
  }, /*#__PURE__*/React__namespace.createElement("span", {
25522
25522
  style: {
25523
25523
  fontSize: 28,
@@ -25539,7 +25539,7 @@ class GradedGroupAnswerBar extends React__namespace.Component {
25539
25539
  }
25540
25540
  }
25541
25541
  const fontSize = 17;
25542
- const styles$o = {
25542
+ const styles$n = {
25543
25543
  answerBar: {
25544
25544
  display: "flex",
25545
25545
  alignItems: "center",
@@ -25739,7 +25739,7 @@ class GradedGroup extends React__namespace.Component {
25739
25739
  }));
25740
25740
  gradeStatus = this.context.strings.incorrect;
25741
25741
  }
25742
- const mobileClass = this.props.inGradedGroupSet ? aphrodite.css(styles$n.gradedGroupInSet) : aphrodite.css(styles$n.gradedGroup);
25742
+ const mobileClass = this.props.inGradedGroupSet ? aphrodite.css(styles$m.gradedGroupInSet) : aphrodite.css(styles$m.gradedGroup);
25743
25743
  const classes = classNames__default["default"]({
25744
25744
  [mobileClass]: apiOptions.isMobile,
25745
25745
  "perseus-graded-group": true,
@@ -25758,7 +25758,7 @@ class GradedGroup extends React__namespace.Component {
25758
25758
  return /*#__PURE__*/React__namespace.createElement("div", {
25759
25759
  className: classes
25760
25760
  }, !!this.props.title && /*#__PURE__*/React__namespace.createElement("div", {
25761
- className: aphrodite.css(styles$n.title)
25761
+ className: aphrodite.css(styles$m.title)
25762
25762
  }, this.props.title), /*#__PURE__*/React__namespace.createElement(Renderer, _extends({}, this.props, {
25763
25763
  // eslint-disable-next-line react/no-string-refs
25764
25764
  ref: "renderer",
@@ -25792,7 +25792,7 @@ class GradedGroup extends React__namespace.Component {
25792
25792
  }, 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
25793
  // @ts-expect-error - TS2322 - Type 'string' is not assignable to type 'number | undefined'.
25794
25794
  tabIndex: "0",
25795
- className: aphrodite.css(styles$n.explanationTitle),
25795
+ className: aphrodite.css(styles$m.explanationTitle),
25796
25796
  onClick: () => this.setState({
25797
25797
  showHint: false
25798
25798
  }),
@@ -25825,7 +25825,7 @@ class GradedGroup extends React__namespace.Component {
25825
25825
  showHint: true
25826
25826
  });
25827
25827
  },
25828
- className: aphrodite.css(styles$n.showHintLink)
25828
+ className: aphrodite.css(styles$m.showHintLink)
25829
25829
  }, this.context.strings.explain)), apiOptions.isMobile && answerBarState !== "HIDDEN" && /*#__PURE__*/React__namespace.createElement(GradedGroupAnswerBar, {
25830
25830
  apiOptions: apiOptions,
25831
25831
  answerBarState: answerBarState,
@@ -25837,7 +25837,7 @@ class GradedGroup extends React__namespace.Component {
25837
25837
  const traverseChildWidgets$2 = function (props, traverseRenderer) {
25838
25838
  return ___default["default"].extend({}, props, traverseRenderer(props));
25839
25839
  };
25840
- const styles$n = aphrodite.StyleSheet.create({
25840
+ const styles$m = aphrodite.StyleSheet.create({
25841
25841
  gradedGroupInSet: {
25842
25842
  // Reset a few desktop-only styles that come from graded-group.less
25843
25843
  marginLeft: 0,
@@ -25908,20 +25908,20 @@ class Indicators extends React__namespace.Component {
25908
25908
  return /*#__PURE__*/React__namespace.createElement("ul", {
25909
25909
  // reduntantly add class name for use in .less files--
25910
25910
  // the styles object key gets hashed
25911
- className: classNames__default["default"](aphrodite.css(styles$m.indicatorContainer), "indicatorContainer")
25911
+ className: classNames__default["default"](aphrodite.css(styles$l.indicatorContainer), "indicatorContainer")
25912
25912
  }, this.props.gradedGroups.map((_ref, i) => {
25913
25913
  let {
25914
25914
  title
25915
25915
  } = _ref;
25916
25916
  return /*#__PURE__*/React__namespace.createElement("li", {
25917
- className: aphrodite.css(styles$m.indicator),
25917
+ className: aphrodite.css(styles$l.indicator),
25918
25918
  key: title
25919
25919
  }, /*#__PURE__*/React__namespace.createElement(Clickable__default["default"], {
25920
25920
  role: "button",
25921
25921
  "aria-label": this.context.strings.skipToTitle({
25922
25922
  title
25923
25923
  }),
25924
- style: styles$m.indicatorButton,
25924
+ style: styles$l.indicatorButton,
25925
25925
  onClick: () => this.props.onChangeCurrentGroup(i),
25926
25926
  onKeyDown: e => this.handleKeyDown(e, i)
25927
25927
  }, _ref2 => {
@@ -25931,9 +25931,9 @@ class Indicators extends React__namespace.Component {
25931
25931
  pressed
25932
25932
  } = _ref2;
25933
25933
  return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
25934
- style: [styles$m.indicatorDot, (hovered || focused || pressed) && styles$m.indicatorDotFocused]
25934
+ style: [styles$l.indicatorDot, (hovered || focused || pressed) && styles$l.indicatorDotFocused]
25935
25935
  }, i === this.props.currentGroup && /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
25936
- style: styles$m.indicatorDotActive
25936
+ style: styles$l.indicatorDotActive
25937
25937
  }, /*#__PURE__*/React__namespace.createElement("span", {
25938
25938
  className: aphrodite.css(a11y.srOnly)
25939
25939
  }, this.context.strings.current)));
@@ -26009,7 +26009,7 @@ class GradedGroupSet extends React__namespace.Component {
26009
26009
  } = getDependencies();
26010
26010
  if (JIPT.useJIPT && this.props.gradedGroups.length > 1) {
26011
26011
  return /*#__PURE__*/React__namespace.createElement("div", {
26012
- className: aphrodite.css(styles$m.container)
26012
+ className: aphrodite.css(styles$l.container)
26013
26013
  }, this.props.gradedGroups.map((gradedGroup, i) => {
26014
26014
  return (
26015
26015
  /*#__PURE__*/
@@ -26032,13 +26032,13 @@ class GradedGroupSet extends React__namespace.Component {
26032
26032
  const numGroups = this.props.gradedGroups.length;
26033
26033
  const handleNextQuestion = this.state.currentGroup < numGroups - 1 ? this.handleNextQuestion : null;
26034
26034
  return /*#__PURE__*/React__namespace.createElement("div", {
26035
- className: aphrodite.css(styles$m.container)
26035
+ className: aphrodite.css(styles$l.container)
26036
26036
  }, /*#__PURE__*/React__namespace.createElement("div", {
26037
- className: aphrodite.css(styles$m.top)
26037
+ className: aphrodite.css(styles$l.top)
26038
26038
  }, /*#__PURE__*/React__namespace.createElement("div", {
26039
- className: aphrodite.css(styles$m.title)
26039
+ className: aphrodite.css(styles$l.title)
26040
26040
  }, currentGroup.title), /*#__PURE__*/React__namespace.createElement("div", {
26041
- className: aphrodite.css(styles$m.spacer)
26041
+ className: aphrodite.css(styles$l.spacer)
26042
26042
  }), /*#__PURE__*/React__namespace.createElement(Indicators, {
26043
26043
  currentGroup: this.state.currentGroup,
26044
26044
  gradedGroups: this.props.gradedGroups,
@@ -26076,7 +26076,7 @@ var GradedGroupSet$1 = {
26076
26076
  tracking: "all",
26077
26077
  isLintable: true
26078
26078
  };
26079
- const styles$m = aphrodite.StyleSheet.create({
26079
+ const styles$l = aphrodite.StyleSheet.create({
26080
26080
  top: {
26081
26081
  display: "flex",
26082
26082
  flexDirection: "row"
@@ -26182,7 +26182,7 @@ class ButtonGroup extends React__namespace.Component {
26182
26182
  id: "" + i,
26183
26183
  ref: "button" + i,
26184
26184
  key: "" + i,
26185
- className: aphrodite.css(styles$l.buttonStyle, button.value === value && styles$l.selectedStyle),
26185
+ className: aphrodite.css(styles$k.buttonStyle, button.value === value && styles$k.selectedStyle),
26186
26186
  onClick: () => this.toggleSelect(button.value)
26187
26187
  }, button.content || "" + button.value);
26188
26188
  });
@@ -26195,7 +26195,7 @@ class ButtonGroup extends React__namespace.Component {
26195
26195
  }, buttons);
26196
26196
  }
26197
26197
  }
26198
- const styles$l = aphrodite.StyleSheet.create({
26198
+ const styles$k = aphrodite.StyleSheet.create({
26199
26199
  buttonStyle: {
26200
26200
  backgroundColor: "white",
26201
26201
  border: "1px solid #ccc",
@@ -26799,7 +26799,7 @@ var util = /*#__PURE__*/Object.freeze({
26799
26799
  });
26800
26800
 
26801
26801
  // @ts-expect-error - TS2339 - Property 'MovablePoint' does not exist on type 'typeof Graphie'.
26802
- const MovablePoint$2 = Graphie.MovablePoint;
26802
+ const MovablePoint$3 = Graphie.MovablePoint;
26803
26803
  // @ts-expect-error - TS2339 - Property 'MovableLine' does not exist on type 'typeof Graphie'.
26804
26804
  const MovableLine$2 = Graphie.MovableLine;
26805
26805
  function isFlipped(newCoord, oldCoord, line) {
@@ -26845,7 +26845,7 @@ class FunctionGrapher extends React__namespace.Component {
26845
26845
  })();
26846
26846
  render() {
26847
26847
  const pointForCoord = (coord, i) => {
26848
- return /*#__PURE__*/React__namespace.createElement(MovablePoint$2, {
26848
+ return /*#__PURE__*/React__namespace.createElement(MovablePoint$3, {
26849
26849
  key: i,
26850
26850
  coord: coord,
26851
26851
  static: this.props.static,
@@ -26982,7 +26982,7 @@ class FunctionGrapher extends React__namespace.Component {
26982
26982
  }],
26983
26983
  normalStyle: dashed,
26984
26984
  highlightStyle: dashed
26985
- }, ___default["default"].map(asymptote, (coord, i) => /*#__PURE__*/React__namespace.createElement(MovablePoint$2, {
26985
+ }, ___default["default"].map(asymptote, (coord, i) => /*#__PURE__*/React__namespace.createElement(MovablePoint$3, {
26986
26986
  key: `asymptoteCoord-${i}`,
26987
26987
  coord: coord,
26988
26988
  static: true,
@@ -27723,7 +27723,7 @@ const Label = Graphie.Label;
27723
27723
  // @ts-expect-error - TS2339 - Property 'Line' does not exist on type 'typeof Graphie'.
27724
27724
  const Line$2 = Graphie.Line;
27725
27725
  // @ts-expect-error - TS2339 - Property 'MovablePoint' does not exist on type 'typeof Graphie'.
27726
- const MovablePoint$1 = Graphie.MovablePoint;
27726
+ const MovablePoint$2 = Graphie.MovablePoint;
27727
27727
  // @ts-expect-error - TS2339 - Property 'MovableLine' does not exist on type 'typeof Graphie'.
27728
27728
  const MovableLine$1 = Graphie.MovableLine;
27729
27729
  // @ts-expect-error - TS2339 - Property 'Plot' does not exist on type 'typeof Graphie'.
@@ -27977,7 +27977,7 @@ class Interaction extends React__namespace.Component {
27977
27977
  return [coordX, coordY];
27978
27978
  }];
27979
27979
  if (element.options.constraint === "snap") {
27980
- constraints.push(MovablePoint$1.constraints.snap(element.options.snap));
27980
+ constraints.push(MovablePoint$2.constraints.snap(element.options.snap));
27981
27981
  } else if (element.options.constraint === "x") {
27982
27982
  constraints.push(coord => {
27983
27983
  return [
@@ -27999,7 +27999,7 @@ class Interaction extends React__namespace.Component {
27999
27999
  // TODO(eater): foo_[xyz] are hacky non-props to
28000
28000
  // get the component to update when constraints
28001
28001
  // change
28002
- return /*#__PURE__*/React__namespace.createElement(MovablePoint$1, {
28002
+ return /*#__PURE__*/React__namespace.createElement(MovablePoint$2, {
28003
28003
  key: element.key,
28004
28004
  coord: [
28005
28005
  // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
@@ -28034,7 +28034,7 @@ class Interaction extends React__namespace.Component {
28034
28034
  return [coordX, coordY];
28035
28035
  }];
28036
28036
  if (element.options.constraint === "snap") {
28037
- constraints.push(MovablePoint$1.constraints.snap(element.options.snap));
28037
+ constraints.push(MovablePoint$2.constraints.snap(element.options.snap));
28038
28038
  } else if (element.options.constraint === "x") {
28039
28039
  constraints.push(coord => {
28040
28040
  return [
@@ -28075,14 +28075,14 @@ class Interaction extends React__namespace.Component {
28075
28075
  foo_x: element.options.constraint,
28076
28076
  foo_y: element.options.constraintFn,
28077
28077
  foo_z: element.options.snap
28078
- }, /*#__PURE__*/React__namespace.createElement(MovablePoint$1, {
28078
+ }, /*#__PURE__*/React__namespace.createElement(MovablePoint$2, {
28079
28079
  coord: start,
28080
28080
  static: true,
28081
28081
  normalStyle: {
28082
28082
  stroke: "none",
28083
28083
  fill: "none"
28084
28084
  }
28085
- }), /*#__PURE__*/React__namespace.createElement(MovablePoint$1, {
28085
+ }), /*#__PURE__*/React__namespace.createElement(MovablePoint$2, {
28086
28086
  coord: end,
28087
28087
  static: true,
28088
28088
  normalStyle: {
@@ -28639,7 +28639,7 @@ const defaultGraphConfig = {
28639
28639
  width: 0,
28640
28640
  height: 0,
28641
28641
  labels: [],
28642
- hintMode: false
28642
+ disableKeyboardInteraction: false
28643
28643
  };
28644
28644
  const GraphConfigContext = /*#__PURE__*/React.createContext(defaultGraphConfig);
28645
28645
  function useGraphConfig() {
@@ -28786,6 +28786,30 @@ const segmentsIntersect = (_ref, _ref2) => {
28786
28786
  }
28787
28787
  };
28788
28788
 
28789
+ // Finds the unique intersection point between two rays. Returns undefined if
28790
+ // there is not exactly one intersection point. The first point in each of the
28791
+ // given ray "segments" should be the initial point of the ray. The second point
28792
+ // can be any arbitrary point on the ray different from the initial point.
28793
+ function findIntersectionOfRays(_ref3, _ref4) {
28794
+ let [[a, b], [c, d]] = _ref3;
28795
+ let [[p, q], [r, s]] = _ref4;
28796
+ // See https://stackoverflow.com/a/24392281/7347484 for an explanation of
28797
+ // the determinant and lambda values.
28798
+ const determinant = (c - a) * (s - q) - (r - p) * (d - b);
28799
+ if (determinant === 0) {
28800
+ return undefined;
28801
+ } else {
28802
+ const lambda = ((s - q) * (r - a) + (p - r) * (s - b)) / determinant;
28803
+ const gamma = ((b - d) * (r - a) + (c - a) * (s - b)) / determinant;
28804
+ if (lambda <= 0 || gamma >= 1) {
28805
+ return undefined;
28806
+ }
28807
+ const initialPoint = [a, b];
28808
+ const direction = mafs.vec.sub([c, d], initialPoint);
28809
+ return mafs.vec.add(initialPoint, mafs.vec.scale(direction, lambda));
28810
+ }
28811
+ }
28812
+
28789
28813
  // This file contains helper functions for working with angles.
28790
28814
 
28791
28815
  function convertDegreesToRadians(degrees) {
@@ -28798,6 +28822,8 @@ function calculateAngleInDegrees(_ref) {
28798
28822
  let [x, y] = _ref;
28799
28823
  return Math.atan2(y, x) * 180 / Math.PI;
28800
28824
  }
28825
+
28826
+ // Converts polar coordinates to cartesian. The th(eta) parameter is in degrees.
28801
28827
  function polar(r, th) {
28802
28828
  if (typeof r === "number") {
28803
28829
  r = [r, r];
@@ -29407,7 +29433,7 @@ const VisibleIcon = () => /*#__PURE__*/React__namespace.createElement("svg", {
29407
29433
  width: "24",
29408
29434
  height: "24",
29409
29435
  viewBox: "0 0 24 24",
29410
- className: aphrodite.css(styles$k.icon)
29436
+ className: aphrodite.css(styles$j.icon)
29411
29437
  }, /*#__PURE__*/React__namespace.createElement("defs", null, /*#__PURE__*/React__namespace.createElement("path", {
29412
29438
  id: "a",
29413
29439
  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 +29464,7 @@ const HiddenIcon = () => /*#__PURE__*/React__namespace.createElement("svg", {
29438
29464
  width: "24",
29439
29465
  height: "24",
29440
29466
  viewBox: "0 0 24 24",
29441
- className: aphrodite.css(styles$k.icon)
29467
+ className: aphrodite.css(styles$j.icon)
29442
29468
  }, /*#__PURE__*/React__namespace.createElement("defs", null, /*#__PURE__*/React__namespace.createElement("path", {
29443
29469
  id: "a",
29444
29470
  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 +29499,20 @@ const HUD = _ref => {
29473
29499
  let state;
29474
29500
  let icon;
29475
29501
  if (enabled) {
29476
- state = styles$k.enabled;
29502
+ state = styles$j.enabled;
29477
29503
  icon = /*#__PURE__*/React__namespace.createElement(VisibleIcon, null);
29478
29504
  } else {
29479
- state = styles$k.disabled;
29505
+ state = styles$j.disabled;
29480
29506
  icon = /*#__PURE__*/React__namespace.createElement(HiddenIcon, null);
29481
29507
  }
29482
29508
  return /*#__PURE__*/React__namespace.createElement("button", {
29483
- className: aphrodite.css(styles$k.hud, fixedPosition && styles$k.hudFixedPosition, state),
29509
+ className: aphrodite.css(styles$j.hud, fixedPosition && styles$j.hudFixedPosition, state),
29484
29510
  onClick: e => {
29485
29511
  onClick();
29486
29512
  }
29487
29513
  }, icon, message);
29488
29514
  };
29489
- const styles$k = aphrodite.StyleSheet.create({
29515
+ const styles$j = aphrodite.StyleSheet.create({
29490
29516
  hud: {
29491
29517
  boxSizing: "border-box",
29492
29518
  height: 36,
@@ -29536,19 +29562,19 @@ const styles$k = aphrodite.StyleSheet.create({
29536
29562
  }
29537
29563
  });
29538
29564
 
29539
- const InfoTip$1 = props => {
29565
+ const InfoTipBase = props => {
29540
29566
  return /*#__PURE__*/React__namespace.createElement(Tooltip__default["default"], {
29541
29567
  content: /*#__PURE__*/React__namespace.createElement(wonderBlocksTypography.Body, {
29542
- style: styles$j.body
29568
+ style: styles$i.body
29543
29569
  }, props.children),
29544
29570
  placement: "right"
29545
29571
  }, /*#__PURE__*/React__namespace.createElement(wonderBlocksIcon.PhosphorIcon, {
29546
29572
  size: "small",
29547
29573
  icon: questionIcon__default["default"],
29548
- style: styles$j.spacingLeft
29574
+ style: styles$i.spacingLeft
29549
29575
  }));
29550
29576
  };
29551
- const styles$j = aphrodite.StyleSheet.create({
29577
+ const styles$i = aphrodite.StyleSheet.create({
29552
29578
  spacingLeft: {
29553
29579
  marginInlineStart: wonderBlocksTokens.spacing.xxxSmall_4
29554
29580
  },
@@ -29577,7 +29603,7 @@ class InfoTip extends React__namespace.Component {
29577
29603
 
29578
29604
  render() {
29579
29605
  if (this.state.didMount) {
29580
- return /*#__PURE__*/React__namespace.createElement(InfoTip$1, this.props);
29606
+ return /*#__PURE__*/React__namespace.createElement(InfoTipBase, this.props);
29581
29607
  }
29582
29608
  return /*#__PURE__*/React__namespace.createElement("div", null);
29583
29609
  }
@@ -29624,7 +29650,7 @@ class MultiButtonGroup extends React__namespace.Component {
29624
29650
  id: "" + i,
29625
29651
  key: "" + i,
29626
29652
  ref: "button" + i,
29627
- className: aphrodite.css(styles$i.buttonStyle, selected && styles$i.selectedStyle),
29653
+ className: aphrodite.css(styles$h.buttonStyle, selected && styles$h.selectedStyle),
29628
29654
  onClick: () => this.toggleSelect(button.value)
29629
29655
  }, button.content || "" + button.value);
29630
29656
  });
@@ -29636,7 +29662,7 @@ class MultiButtonGroup extends React__namespace.Component {
29636
29662
  }, buttons);
29637
29663
  }
29638
29664
  }
29639
- const styles$i = aphrodite.StyleSheet.create({
29665
+ const styles$h = aphrodite.StyleSheet.create({
29640
29666
  buttonStyle: {
29641
29667
  backgroundColor: "white",
29642
29668
  border: "1px solid #ccc",
@@ -29868,62 +29894,6 @@ class NumberInput extends React__namespace.Component {
29868
29894
  };
29869
29895
  }
29870
29896
 
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
29897
  const truth = () => true;
29928
29898
 
29929
29899
  /* A minor abstraction on top of NumberInput for ranges
@@ -29982,7 +29952,6 @@ var components = /*#__PURE__*/Object.freeze({
29982
29952
  MathInput: MathInput,
29983
29953
  MultiButtonGroup: MultiButtonGroup,
29984
29954
  NumberInput: NumberInput,
29985
- PropCheckBox: PropCheckBox,
29986
29955
  RangeInput: RangeInput,
29987
29956
  SvgImage: SvgImage,
29988
29957
  TextInput: TextInput,
@@ -30140,8 +30109,9 @@ const lockedFigureColors = {
30140
30109
  };
30141
30110
  const lockedFigureFillStyles = {
30142
30111
  none: 0,
30143
- solid: 1,
30144
- translucent: 0.4
30112
+ white: 1,
30113
+ translucent: 0.4,
30114
+ solid: 1
30145
30115
  };
30146
30116
  const plotterPlotTypes = ["bar", "line", "pic", "histogram", "dotplot"];
30147
30117
 
@@ -30161,10 +30131,20 @@ const LockedEllipse = props => {
30161
30131
  fillOpacity: lockedFigureFillStyles[fillStyle],
30162
30132
  strokeStyle: strokeStyle,
30163
30133
  color: lockedFigureColors[color]
30134
+ // We need to override the svg props if we want to have a
30135
+ // different fill color than the stroke color (specifically,
30136
+ // in the case where the fillStyle is "white").
30137
+ ,
30138
+ svgEllipseProps: {
30139
+ style: {
30140
+ fill: fillStyle === "white" ? wonderBlocksTokens.color.white : lockedFigureColors[color]
30141
+ }
30142
+ }
30164
30143
  });
30165
30144
  };
30166
30145
 
30167
30146
  const LockedFunction = props => {
30147
+ const [equation, setEquation] = React.useState();
30168
30148
  const {
30169
30149
  color,
30170
30150
  strokeStyle,
@@ -30176,7 +30156,14 @@ const LockedFunction = props => {
30176
30156
  style: strokeStyle,
30177
30157
  domain
30178
30158
  };
30179
- const equation = props.equationParsed || KAS__namespace.parse(props.equation).expr;
30159
+ React.useEffect(() => {
30160
+ // Parsing the equation in a "useEffect" hook saves about 2ms each frame
30161
+ // when the learner is interacting with the graph (i.e. moving points).
30162
+ setEquation(KAS__namespace.parse(props.equation).expr);
30163
+ }, [props.equation]);
30164
+ if (typeof equation === "undefined") {
30165
+ return null;
30166
+ }
30180
30167
  return /*#__PURE__*/React__namespace.createElement("g", {
30181
30168
  className: "locked-function"
30182
30169
  }, directionalAxis === "x" && /*#__PURE__*/React__namespace.createElement(mafs.Plot.OfX, _extends({
@@ -30379,6 +30366,15 @@ const LockedPolygon = props => {
30379
30366
  fillOpacity: lockedFigureFillStyles[fillStyle],
30380
30367
  strokeStyle: strokeStyle,
30381
30368
  color: lockedFigureColors[color]
30369
+ // We need to override the svg props if we want to have a
30370
+ // different fill color than the stroke color (specifically,
30371
+ // in the case where the fillStyle is "white").
30372
+ ,
30373
+ svgPolygonProps: {
30374
+ style: {
30375
+ fill: fillStyle === "white" ? wonderBlocksTokens.color.white : lockedFigureColors[color]
30376
+ }
30377
+ }
30382
30378
  }), showVertices && points.map((point, index) => /*#__PURE__*/React__namespace.createElement(mafs.Point, {
30383
30379
  key: `locked-polygon-point-${index}`,
30384
30380
  x: point[X],
@@ -30625,7 +30621,7 @@ function useDraggable(args) {
30625
30621
  gestureTarget: target,
30626
30622
  onMove,
30627
30623
  point,
30628
- constrain
30624
+ constrainKeyboardMovement
30629
30625
  } = args;
30630
30626
  const [dragging, setDragging] = React__namespace.useState(false);
30631
30627
  const {
@@ -30648,6 +30644,7 @@ function useDraggable(args) {
30648
30644
  event?.stopPropagation();
30649
30645
  const isKeyboard = type.includes("key");
30650
30646
  if (isKeyboard) {
30647
+ invariant(event instanceof KeyboardEvent);
30651
30648
  event?.preventDefault();
30652
30649
 
30653
30650
  // When a key is held down, we see multiple "keydown" events,
@@ -30659,6 +30656,11 @@ function useDraggable(args) {
30659
30656
  if (type === "keyup") {
30660
30657
  return;
30661
30658
  }
30659
+ if (typeof constrainKeyboardMovement === "object") {
30660
+ const destination = constrainKeyboardMovement[directionForKey[event.key]];
30661
+ onMove(destination ?? point);
30662
+ return;
30663
+ }
30662
30664
  const {
30663
30665
  direction: yDownDirection,
30664
30666
  altKey,
@@ -30679,7 +30681,7 @@ function useDraggable(args) {
30679
30681
  for (const dx of tests) {
30680
30682
  // Transform the test back into the point's coordinate system
30681
30683
  const testMovement = mafs.vec.scale(direction, dx);
30682
- const testPoint = constrain(mafs.vec.transform(mafs.vec.add(mafs.vec.transform(point, userTransform), testMovement), inverseTransform));
30684
+ const testPoint = constrainKeyboardMovement(mafs.vec.transform(mafs.vec.add(mafs.vec.transform(point, userTransform), testMovement), inverseTransform));
30683
30685
  if (mafs.vec.dist(testPoint, point) > min) {
30684
30686
  onMove(testPoint);
30685
30687
  break;
@@ -30699,7 +30701,7 @@ function useDraggable(args) {
30699
30701
  return;
30700
30702
  }
30701
30703
  const movement = mafs.vec.transform(pixelMovement, inverseViewTransform);
30702
- onMove(constrain(mafs.vec.transform(mafs.vec.add(pickup.current, movement), inverseTransform)));
30704
+ onMove(mafs.vec.transform(mafs.vec.add(pickup.current, movement), inverseTransform));
30703
30705
  }
30704
30706
  }, {
30705
30707
  target,
@@ -30711,6 +30713,12 @@ function useDraggable(args) {
30711
30713
  dragging
30712
30714
  };
30713
30715
  }
30716
+ const directionForKey = {
30717
+ ArrowLeft: "left",
30718
+ ArrowRight: "right",
30719
+ ArrowUp: "up",
30720
+ ArrowDown: "down"
30721
+ };
30714
30722
  function getInverseTransform(transform) {
30715
30723
  const invert = mafs.vec.matrixInvert(transform);
30716
30724
  invariant(invert !== null, "Could not invert transform matrix. A parent transformation matrix might be degenerative (mapping 2D space to a line).");
@@ -30760,7 +30768,7 @@ const MovablePointView = /*#__PURE__*/React.forwardRef((props, hitboxRef) => {
30760
30768
  range,
30761
30769
  markings,
30762
30770
  showTooltips,
30763
- hintMode
30771
+ disableKeyboardInteraction
30764
30772
  } = useGraphConfig();
30765
30773
  const {
30766
30774
  point,
@@ -30805,7 +30813,7 @@ const MovablePointView = /*#__PURE__*/React.forwardRef((props, hitboxRef) => {
30805
30813
  cursor
30806
30814
  },
30807
30815
  "data-testid": "movable-point",
30808
- tabIndex: hintMode ? -1 : tabIndex(focusBehavior)
30816
+ tabIndex: disableKeyboardInteraction ? -1 : tabIndex(focusBehavior)
30809
30817
  }, /*#__PURE__*/React__namespace.createElement("circle", {
30810
30818
  className: "movable-point-hitbox",
30811
30819
  r: hitboxSizePx / 2,
@@ -30894,7 +30902,7 @@ const MovableLine = props => {
30894
30902
  function useControlPoint(point, color, onMovePoint) {
30895
30903
  const {
30896
30904
  snapStep,
30897
- hintMode
30905
+ disableKeyboardInteraction
30898
30906
  } = useGraphConfig();
30899
30907
  const [focused, setFocused] = React.useState(false);
30900
30908
  const keyboardHandleRef = React.useRef(null);
@@ -30902,7 +30910,7 @@ function useControlPoint(point, color, onMovePoint) {
30902
30910
  gestureTarget: keyboardHandleRef,
30903
30911
  point,
30904
30912
  onMove: onMovePoint,
30905
- constrain: p => snap(snapStep, p)
30913
+ constrainKeyboardMovement: p => snap(snapStep, p)
30906
30914
  });
30907
30915
  const visiblePointRef = React.useRef(null);
30908
30916
  const {
@@ -30911,12 +30919,12 @@ function useControlPoint(point, color, onMovePoint) {
30911
30919
  gestureTarget: visiblePointRef,
30912
30920
  point,
30913
30921
  onMove: onMovePoint,
30914
- constrain: p => snap(snapStep, p)
30922
+ constrainKeyboardMovement: p => snap(snapStep, p)
30915
30923
  });
30916
30924
  const focusableHandle = /*#__PURE__*/React__namespace.createElement("g", {
30917
30925
  "data-testid": "movable-point__focusable-handle",
30918
30926
  className: "movable-point__focusable-handle",
30919
- tabIndex: hintMode ? -1 : 0,
30927
+ tabIndex: disableKeyboardInteraction ? -1 : 0,
30920
30928
  ref: keyboardHandleRef,
30921
30929
  onFocus: () => setFocused(true),
30922
30930
  onBlur: () => setFocused(false)
@@ -30950,7 +30958,7 @@ const Line$1 = props => {
30950
30958
  range,
30951
30959
  graphDimensionsInPixels,
30952
30960
  snapStep,
30953
- hintMode
30961
+ disableKeyboardInteraction
30954
30962
  } = useGraphConfig();
30955
30963
  let startExtend = undefined;
30956
30964
  let endExtend = undefined;
@@ -30968,11 +30976,11 @@ const Line$1 = props => {
30968
30976
  onMove: newPoint => {
30969
30977
  onMove(mafs.vec.sub(newPoint, start));
30970
30978
  },
30971
- constrain: p => snap(snapStep, p)
30979
+ constrainKeyboardMovement: p => snap(snapStep, p)
30972
30980
  });
30973
30981
  return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement("g", {
30974
30982
  ref: line,
30975
- tabIndex: hintMode ? -1 : 0,
30983
+ tabIndex: disableKeyboardInteraction ? -1 : 0,
30976
30984
  className: "movable-line",
30977
30985
  "data-testid": "movable-line",
30978
30986
  style: {
@@ -31443,7 +31451,7 @@ function calculateBisectorPoint(point1, point2, vertex, isReflex, allowReflex, a
31443
31451
  return scaledBisector;
31444
31452
  }
31445
31453
 
31446
- const StyledMovablePoint = props => {
31454
+ const MovablePoint$1 = props => {
31447
31455
  const {
31448
31456
  snapStep
31449
31457
  } = useGraphConfig();
@@ -31453,16 +31461,15 @@ const StyledMovablePoint = props => {
31453
31461
  onMove,
31454
31462
  cursor,
31455
31463
  color = wonderBlocksTokens.color.blue,
31456
- snapTo
31464
+ constrain = p => snap(snapStep, p)
31457
31465
  } = props;
31458
- const snapToValue = snapTo ?? "grid";
31459
31466
  const {
31460
31467
  dragging
31461
31468
  } = useDraggable({
31462
31469
  gestureTarget: elementRef,
31463
31470
  point,
31464
31471
  onMove,
31465
- constrain: p => ["angles", "sides"].includes(snapToValue) ? p : snap(snapStep, p)
31472
+ constrainKeyboardMovement: constrain
31466
31473
  });
31467
31474
  return /*#__PURE__*/React__namespace.createElement(MovablePointView, {
31468
31475
  ref: elementRef,
@@ -31478,8 +31485,10 @@ const StyledMovablePoint = props => {
31478
31485
  };
31479
31486
 
31480
31487
  const PolygonGraph = props => {
31481
- const [focused, setFocused] = React__namespace.useState(false);
31482
31488
  const [hovered, setHovered] = React__namespace.useState(false);
31489
+ // This is more so required for the re-rendering that occurs when state
31490
+ // updates; specifically with regard to line weighting and polygon focus.
31491
+ const [focusVisible, setFocusVisible] = React__namespace.useState(false);
31483
31492
  const {
31484
31493
  dispatch
31485
31494
  } = props;
@@ -31489,10 +31498,10 @@ const PolygonGraph = props => {
31489
31498
  showSides,
31490
31499
  range,
31491
31500
  snapStep,
31492
- snapTo
31501
+ snapTo = "grid"
31493
31502
  } = props.graphState;
31494
31503
  const {
31495
- hintMode
31504
+ disableKeyboardInteraction
31496
31505
  } = useGraphConfig();
31497
31506
 
31498
31507
  // TODO(benchristel): can the default set of points be removed here? I don't
@@ -31500,7 +31509,7 @@ const PolygonGraph = props => {
31500
31509
  const points = coords ?? [[0, 0]];
31501
31510
  const ref = React__namespace.useRef(null);
31502
31511
  const dragReferencePoint = points[0];
31503
- const snapToValue = snapTo ?? "grid";
31512
+ const constrain = ["angles", "sides"].includes(snapTo) ? p => p : p => snap(snapStep, p);
31504
31513
  const {
31505
31514
  dragging
31506
31515
  } = useDraggable({
@@ -31510,16 +31519,15 @@ const PolygonGraph = props => {
31510
31519
  const delta = mafs.vec.sub(newPoint, dragReferencePoint);
31511
31520
  dispatch(actions.polygon.moveAll(delta));
31512
31521
  },
31513
- constrain: p => ["angles", "sides"].includes(snapToValue) ? p : snap(snapStep, p)
31522
+ constrainKeyboardMovement: constrain
31514
31523
  });
31515
- const active = hovered || focused || dragging;
31516
31524
  const lastMoveTime = React__namespace.useRef(0);
31517
31525
  const lines = getLines(points);
31518
31526
  return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(mafs.Polygon, {
31519
31527
  points: [...points],
31520
31528
  color: "var(--movable-line-stroke-color)",
31521
31529
  svgPolygonProps: {
31522
- strokeWidth: active ? "var(--movable-line-stroke-weight-active)" : "var(--movable-line-stroke-weight)",
31530
+ strokeWidth: focusVisible ? "var(--movable-line-stroke-weight-active)" : "var(--movable-line-stroke-weight)",
31523
31531
  style: {
31524
31532
  fill: "transparent"
31525
31533
  }
@@ -31534,11 +31542,10 @@ const PolygonGraph = props => {
31534
31542
  key: "angle-" + i,
31535
31543
  centerPoint: point,
31536
31544
  endPoints: [pt1, pt2],
31537
- active: active,
31538
31545
  range: range,
31539
31546
  polygonLines: lines,
31540
31547
  showAngles: !!showAngles,
31541
- snapTo: snapToValue
31548
+ snapTo: snapTo
31542
31549
  });
31543
31550
  }), showSides && lines.map((_ref, i) => {
31544
31551
  let [start, end] = _ref;
@@ -31554,21 +31561,29 @@ const PolygonGraph = props => {
31554
31561
  color: "transparent",
31555
31562
  svgPolygonProps: {
31556
31563
  ref,
31557
- tabIndex: hintMode ? -1 : 0,
31564
+ tabIndex: disableKeyboardInteraction ? -1 : 0,
31558
31565
  strokeWidth: TARGET_SIZE,
31559
31566
  style: {
31560
31567
  cursor: dragging ? "grabbing" : "grab",
31561
31568
  fill: hovered ? "var(--mafs-blue)" : "transparent"
31562
31569
  },
31563
- onFocus: () => setFocused(true),
31564
- onBlur: () => setFocused(false),
31565
31570
  onMouseEnter: () => setHovered(true),
31566
31571
  onMouseLeave: () => setHovered(false),
31572
+ // Required to remove line weighting when user clicks away
31573
+ // from the focused polygon
31574
+ onKeyDownCapture: () => {
31575
+ setFocusVisible(hasFocusVisible(ref.current));
31576
+ },
31577
+ // Required for lines to darken on focus
31578
+ onFocus: () => setFocusVisible(hasFocusVisible(ref.current)),
31579
+ // Required for line weighting to update on blur. Without this,
31580
+ // the user has to hover over the shape for it to update
31581
+ onBlur: () => setFocusVisible(hasFocusVisible(ref.current)),
31567
31582
  className: "movable-polygon"
31568
31583
  }
31569
- }), points.map((point, i) => /*#__PURE__*/React__namespace.createElement(StyledMovablePoint, {
31584
+ }), points.map((point, i) => /*#__PURE__*/React__namespace.createElement(MovablePoint$1, {
31570
31585
  key: "point-" + i,
31571
- snapTo: snapTo,
31586
+ constrain: constrain,
31572
31587
  point: point,
31573
31588
  onMove: destination => {
31574
31589
  const now = Date.now();
@@ -31587,6 +31602,17 @@ function getLines(points) {
31587
31602
  return [point, next];
31588
31603
  });
31589
31604
  }
31605
+ const hasFocusVisible = element => {
31606
+ const matches = selector => element?.matches(selector) ?? false;
31607
+ try {
31608
+ return matches(":focus-visible");
31609
+ } catch (e) {
31610
+ // jsdom doesn't support :focus-visible
31611
+ // (see https://github.com/jsdom/jsdom/issues/3426),
31612
+ // so the call to matches(":focus-visible") will fail in tests.
31613
+ return matches(":focus");
31614
+ }
31615
+ };
31590
31616
 
31591
31617
  function getGradableGraph(state, initialGraph) {
31592
31618
  if (!state.hasBeenInteractedWith) {
@@ -31650,9 +31676,17 @@ function getGradableGraph(state, initialGraph) {
31650
31676
  };
31651
31677
  }
31652
31678
  if (state.type === "angle" && initialGraph.type === "angle") {
31679
+ // We're going to reverse the coords for scoring if the angle is clockwise and
31680
+ // we don't allow reflex angles. This is because the angle is largely defined
31681
+ // by the order of the points in the coords array, and we want to maintain
31682
+ // the same angle scoring with the legacy graph (which had a bug).
31683
+ // (LEMS-2190): When we remove the legacy graph, move this logic to the scoring function.
31684
+ const areClockwise = clockwise$1([state.coords[0], state.coords[2], state.coords[1]]);
31685
+ const shouldReverseCoords = areClockwise && !state.allowReflexAngles;
31686
+ const coords = shouldReverseCoords ? state.coords.slice().reverse() : state.coords;
31653
31687
  return {
31654
31688
  ...initialGraph,
31655
- coords: state.coords,
31689
+ coords,
31656
31690
  allowReflexAngles: state.allowReflexAngles
31657
31691
  };
31658
31692
  }
@@ -31684,7 +31718,7 @@ function CircleGraph(props) {
31684
31718
  center: center,
31685
31719
  radius: getRadius(graphState),
31686
31720
  onMove: c => dispatch(actions.circle.moveCenter(c))
31687
- }), /*#__PURE__*/React__namespace.createElement(StyledMovablePoint, {
31721
+ }), /*#__PURE__*/React__namespace.createElement(MovablePoint$1, {
31688
31722
  point: radiusPoint,
31689
31723
  cursor: "ew-resize",
31690
31724
  onMove: newRadiusPoint => {
@@ -31700,7 +31734,7 @@ function MovableCircle(props) {
31700
31734
  } = props;
31701
31735
  const {
31702
31736
  snapStep,
31703
- hintMode
31737
+ disableKeyboardInteraction
31704
31738
  } = useGraphConfig();
31705
31739
  const draggableRef = React.useRef(null);
31706
31740
  const {
@@ -31709,13 +31743,13 @@ function MovableCircle(props) {
31709
31743
  gestureTarget: draggableRef,
31710
31744
  point: center,
31711
31745
  onMove,
31712
- constrain: p => snap(snapStep, p)
31746
+ constrainKeyboardMovement: p => snap(snapStep, p)
31713
31747
  });
31714
31748
  const [centerPx] = useTransformVectorsToPixels(center);
31715
31749
  const [radiiPx] = useTransformDimensionsToPixels([radius, radius]);
31716
31750
  return /*#__PURE__*/React__namespace.createElement("g", {
31717
31751
  ref: draggableRef,
31718
- tabIndex: hintMode ? -1 : 0,
31752
+ tabIndex: disableKeyboardInteraction ? -1 : 0,
31719
31753
  className: `movable-circle ${dragging ? "movable-circle--dragging" : ""}`
31720
31754
  }, /*#__PURE__*/React__namespace.createElement("ellipse", {
31721
31755
  className: "focus-ring",
@@ -31796,7 +31830,7 @@ function QuadraticGraph(props) {
31796
31830
  return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(mafs.Plot.OfX, {
31797
31831
  y: y,
31798
31832
  color: wonderBlocksTokens.color.blue
31799
- }), coords.map((coord, i) => /*#__PURE__*/React__namespace.createElement(StyledMovablePoint, {
31833
+ }), coords.map((coord, i) => /*#__PURE__*/React__namespace.createElement(MovablePoint$1, {
31800
31834
  key: "point-" + i,
31801
31835
  point: coord,
31802
31836
  onMove: destination => dispatch(actions.quadratic.movePoint(i, destination))
@@ -31856,7 +31890,7 @@ function SinusoidGraph(props) {
31856
31890
  return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(mafs.Plot.OfX, {
31857
31891
  y: x => computeSine(x, coeffRef.current),
31858
31892
  color: wonderBlocksTokens.color.blue
31859
- }), coords.map((coord, i) => /*#__PURE__*/React__namespace.createElement(StyledMovablePoint, {
31893
+ }), coords.map((coord, i) => /*#__PURE__*/React__namespace.createElement(MovablePoint$1, {
31860
31894
  key: "point-" + i,
31861
31895
  point: coord,
31862
31896
  onMove: destination => dispatch(actions.sinusoid.movePoint(i, destination))
@@ -31912,7 +31946,6 @@ function AngleGraph(props) {
31912
31946
  showAngles,
31913
31947
  range,
31914
31948
  allowReflexAngles,
31915
- angleOffsetDeg,
31916
31949
  snapDegrees
31917
31950
  } = graphState;
31918
31951
 
@@ -31951,8 +31984,6 @@ function AngleGraph(props) {
31951
31984
  coords: endPoints,
31952
31985
  allowReflexAngles: allowReflexAngles || false,
31953
31986
  // Whether to allow reflex angles or not
31954
- angleOffsetDeg: angleOffsetDeg || 0,
31955
- // The angle offset from the x-axis
31956
31987
  snapDegrees: snapDegrees || 1,
31957
31988
  // The multiple of degrees to snap to
31958
31989
  range: range,
@@ -31960,19 +31991,65 @@ function AngleGraph(props) {
31960
31991
  };
31961
31992
 
31962
31993
  // Render the lines, angle, and then movable points
31963
- 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, {
31964
- key: "point-" + i,
31965
- snapTo: "angles",
31966
- point: point,
31967
- onMove: destination => dispatch(actions.angle.movePoint(i, destination))
31968
- })));
31994
+ return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, svgLines, /*#__PURE__*/React__namespace.createElement(Angle, angleParams), /*#__PURE__*/React__namespace.createElement(MovablePoint$1, {
31995
+ point: coords[1],
31996
+ constrain: p => p,
31997
+ onMove: destination => dispatch(actions.angle.movePoint(1, destination))
31998
+ }), /*#__PURE__*/React__namespace.createElement(MovablePoint$1, {
31999
+ point: coords[0],
32000
+ constrain: getAngleSideConstraint(coords[0], coords[1], snapDegrees || 1),
32001
+ onMove: destination => dispatch(actions.angle.movePoint(0, destination))
32002
+ }), /*#__PURE__*/React__namespace.createElement(MovablePoint$1, {
32003
+ point: coords[2],
32004
+ constrain: getAngleSideConstraint(coords[2], coords[1], snapDegrees || 1),
32005
+ onMove: destination => dispatch(actions.angle.movePoint(2, destination))
32006
+ }));
32007
+ }
32008
+ const positiveX = [1, 0];
32009
+ const negativeX = [-1, 0];
32010
+ const positiveY = [0, 1];
32011
+ const negativeY = [0, -1];
32012
+ function getAngleSideConstraint(sidePoint, vertex, snapDegrees) {
32013
+ const currentAngle = calculateAngleInDegrees(mafs.vec.sub(sidePoint, vertex));
32014
+
32015
+ // Find the rays that start at the current point and point up, down, left
32016
+ // and right.
32017
+ const leftRay = [sidePoint, mafs.vec.add(sidePoint, negativeX)];
32018
+ const rightRay = [sidePoint, mafs.vec.add(sidePoint, positiveX)];
32019
+ const upRay = [sidePoint, mafs.vec.add(sidePoint, positiveY)];
32020
+ const downRay = [sidePoint, mafs.vec.add(sidePoint, negativeY)];
32021
+
32022
+ // find the angles that lie one snap step clockwise and counter-clockwise
32023
+ // from the current angle. These are the angles to which the side can be
32024
+ // moved.
32025
+ const oneStepCounterClockwise = currentAngle + snapDegrees;
32026
+ const oneStepClockwise = currentAngle - snapDegrees;
32027
+
32028
+ // find the rays that start from the vertex and point in the direction of
32029
+ // the angles we just computed.
32030
+ const counterClockwiseRay = [vertex, mafs.vec.add(vertex, polar(1, oneStepCounterClockwise))];
32031
+ const clockwiseRay = [vertex, mafs.vec.add(vertex, polar(1, oneStepClockwise))];
32032
+
32033
+ // find the intersections of those rays with the horizontal and vertical
32034
+ // rays extending from the sidePoint. These intersections are the points to
32035
+ // which sidePoint can move that will result in a rotation of `snapDegrees`.
32036
+ const left = findIntersectionOfRays(leftRay, counterClockwiseRay) ?? findIntersectionOfRays(leftRay, clockwiseRay);
32037
+ const right = findIntersectionOfRays(rightRay, counterClockwiseRay) ?? findIntersectionOfRays(rightRay, clockwiseRay);
32038
+ const up = findIntersectionOfRays(upRay, counterClockwiseRay) ?? findIntersectionOfRays(upRay, clockwiseRay);
32039
+ const down = findIntersectionOfRays(downRay, counterClockwiseRay) ?? findIntersectionOfRays(downRay, clockwiseRay);
32040
+ return {
32041
+ up: up ?? sidePoint,
32042
+ down: down ?? sidePoint,
32043
+ left: left ?? sidePoint,
32044
+ right: right ?? sidePoint
32045
+ };
31969
32046
  }
31970
32047
 
31971
32048
  function PointGraph(props) {
31972
32049
  const {
31973
32050
  dispatch
31974
32051
  } = props;
31975
- return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, props.graphState.coords.map((point, i) => /*#__PURE__*/React__namespace.createElement(StyledMovablePoint, {
32052
+ return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, props.graphState.coords.map((point, i) => /*#__PURE__*/React__namespace.createElement(MovablePoint$1, {
31976
32053
  key: i,
31977
32054
  point: point,
31978
32055
  onMove: destination => dispatch(actions.pointGraph.movePoint(i, destination))
@@ -32004,7 +32081,7 @@ function Protractor() {
32004
32081
  gestureTarget: draggableRef,
32005
32082
  onMove: setCenter,
32006
32083
  point: center,
32007
- constrain: point => bound$1({
32084
+ constrainKeyboardMovement: point => bound$1({
32008
32085
  snapStep,
32009
32086
  range,
32010
32087
  point
@@ -32120,7 +32197,8 @@ const MafsGraph = props => {
32120
32197
  state,
32121
32198
  dispatch,
32122
32199
  labels,
32123
- hintMode
32200
+ hintMode,
32201
+ readOnly
32124
32202
  } = props;
32125
32203
  const [width, height] = props.box;
32126
32204
  const tickStep = props.step;
@@ -32136,7 +32214,7 @@ const MafsGraph = props => {
32136
32214
  width,
32137
32215
  height,
32138
32216
  labels,
32139
- hintMode
32217
+ disableKeyboardInteraction: hintMode || readOnly
32140
32218
  }
32141
32219
  }, /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
32142
32220
  className: "mafs-graph",
@@ -32501,6 +32579,9 @@ const getAngleCoords = params => {
32501
32579
  if (graph.coords) {
32502
32580
  return graph.coords;
32503
32581
  }
32582
+ if (graph.startCoords) {
32583
+ return graph.startCoords;
32584
+ }
32504
32585
  const {
32505
32586
  snapDegrees,
32506
32587
  angleOffsetDeg
@@ -32530,6 +32611,7 @@ const getAngleCoords = params => {
32530
32611
  return coords;
32531
32612
  };
32532
32613
 
32614
+ const minDistanceBetweenAngleVertexAndSidePoint = 2;
32533
32615
  function interactiveGraphReducer(state, action) {
32534
32616
  switch (action.type) {
32535
32617
  case REINITIALIZE:
@@ -32700,23 +32782,30 @@ function doMovePoint(state, action) {
32700
32782
  case "angle":
32701
32783
  // If the index is 1, we are moving the vertex of the angle,
32702
32784
  // which will move the other two points as well
32703
- if (action.index === 1) {
32704
- const updatedCoords = boundAndSnapAngleVertex(state, action);
32785
+ const newState = (() => {
32786
+ if (action.index === 1) {
32787
+ const updatedCoords = boundAndSnapAngleVertex(state, action);
32788
+ return {
32789
+ ...state,
32790
+ hasBeenInteractedWith: true,
32791
+ coords: updatedCoords
32792
+ };
32793
+ }
32705
32794
  return {
32706
32795
  ...state,
32707
32796
  hasBeenInteractedWith: true,
32708
- coords: updatedCoords
32797
+ coords: setAtIndex({
32798
+ array: state.coords,
32799
+ index: action.index,
32800
+ newValue: boundAndSnapAngleEndPoints(action.destination, state, action.index)
32801
+ })
32709
32802
  };
32803
+ })();
32804
+ if (angleSidePointsTooCloseToVertex(newState)) {
32805
+ // cancel the move
32806
+ return state;
32710
32807
  }
32711
- return {
32712
- ...state,
32713
- hasBeenInteractedWith: true,
32714
- coords: setAtIndex({
32715
- array: state.coords,
32716
- index: action.index,
32717
- newValue: boundAndSnapAngleEndPoints(action.destination, state, action.index)
32718
- })
32719
- };
32808
+ return newState;
32720
32809
  case "polygon":
32721
32810
  let newValue;
32722
32811
  if (state.snapTo === "sides") {
@@ -32809,11 +32898,7 @@ function doMoveCenter(state, action) {
32809
32898
  case "circle":
32810
32899
  {
32811
32900
  // Constrain the center of the circle to the chart range
32812
- const constrainedCenter = bound$1({
32813
- snapStep: state.snapStep,
32814
- range: state.range,
32815
- point: action.destination
32816
- });
32901
+ const constrainedCenter = boundAndSnapToGrid(action.destination, state);
32817
32902
 
32818
32903
  // Reposition the radius point based on the new center
32819
32904
  // (spread to make sure we're not going to mutate anything)
@@ -32848,10 +32933,10 @@ function doMoveRadiusPoint(state, action) {
32848
32933
  case "circle":
32849
32934
  {
32850
32935
  const [xMin, xMax] = state.range[X];
32851
- const nextRadiusPoint = [
32936
+ const nextRadiusPoint = snap(state.snapStep, [
32852
32937
  // Constrain to graph range
32853
32938
  // The +0 is to convert -0 to +0
32854
- clamp(action.destination[X] + 0, xMin, xMax), state.center[1]];
32939
+ clamp(action.destination[X] + 0, xMin, xMax), state.center[1]]);
32855
32940
  if (___default["default"].isEqual(nextRadiusPoint, state.center)) {
32856
32941
  return state;
32857
32942
  }
@@ -32941,17 +33026,16 @@ function boundAndSnapAngleVertex(_ref2, _ref3) {
32941
33026
 
32942
33027
  // Get the current and upcoming positions of the vertex
32943
33028
  const startingVertex = coordsCopy[1];
32944
- const newVertex = bound$1({
32945
- snapStep,
32946
- range,
32947
- point: snap(snapStep, destination)
32948
- });
33029
+
33030
+ // Prevent the vertex from getting too close to the edge, so that all
33031
+ // points of the angle can fit within the graph bounds.
33032
+ const insetAmount = mafs.vec.add(snapStep, [minDistanceBetweenAngleVertexAndSidePoint, minDistanceBetweenAngleVertexAndSidePoint]);
33033
+ const newVertex = clampToBox(inset(insetAmount, range), snap(snapStep, destination));
32949
33034
 
32950
33035
  // Get the vector from the starting vertex to the new vertex
32951
33036
  const delta = mafs.vec.add(newVertex, reverseVector(startingVertex));
32952
33037
 
32953
33038
  // Apply the delta to each of the other two points so that the angle is maintained
32954
- let valid = true;
32955
33039
  const newPoints = {};
32956
33040
  for (const i of [0, 2]) {
32957
33041
  const oldPoint = coordsCopy[i];
@@ -32960,22 +33044,14 @@ function boundAndSnapAngleVertex(_ref2, _ref3) {
32960
33044
  angle *= Math.PI / 180;
32961
33045
  newPoint = constrainToBoundsOnAngle(newPoint, angle, range, snapStep);
32962
33046
  newPoints[i] = newPoint;
32963
-
32964
- // Check if the new point is too close to the vertex
32965
- if (tooClose(newVertex, newPoint)) {
32966
- valid = false;
32967
- }
32968
33047
  }
32969
33048
 
32970
33049
  // Update the vertex after snapping to the snapStep
32971
33050
  newPoints[1] = newVertex;
32972
- // Only move points if all new positions are valid
32973
- if (valid) {
32974
- Object.entries(newPoints).forEach(_ref4 => {
32975
- let [i, newPoint] = _ref4;
32976
- coordsCopy[i] = newPoint;
32977
- });
32978
- }
33051
+ Object.entries(newPoints).forEach(_ref4 => {
33052
+ let [i, newPoint] = _ref4;
33053
+ coordsCopy[i] = newPoint;
33054
+ });
32979
33055
  return coordsCopy;
32980
33056
  }
32981
33057
 
@@ -33041,10 +33117,17 @@ function boundAndSnapAngleEndPoints(destinationPoint, _ref5, index) {
33041
33117
 
33042
33118
  // Snap the angle to the nearest multiple of snapDegrees (if provided)
33043
33119
  angle = Math.round((angle - offsetDegrees) / snap) * snap + offsetDegrees;
33044
- const distance = mafs.vec.dist(coordsCopy[index], vertex);
33120
+
33121
+ // add 0.01 to prevent rounding errors from causing the point to snap to
33122
+ // a location that is too close to the vertex.
33123
+ const minDistance = minDistanceBetweenAngleVertexAndSidePoint + 0.01;
33124
+ const distance = Math.max(mafs.vec.dist(coordsCopy[index], vertex), minDistance);
33045
33125
  const snappedValue = mafs.vec.add(vertex, polar(distance, angle));
33046
33126
  return snappedValue;
33047
33127
  }
33128
+ function angleSidePointsTooCloseToVertex(state) {
33129
+ return tooClose(state.coords[0], state.coords[1], state.range) || tooClose(state.coords[2], state.coords[1], state.range);
33130
+ }
33048
33131
  function boundAndSnapToPolygonAngle(destinationPoint, _ref6, index) {
33049
33132
  let {
33050
33133
  range,
@@ -33275,7 +33358,12 @@ const StatefulMafsGraph = /*#__PURE__*/React__namespace.forwardRef((props, ref)
33275
33358
  React.useEffect(() => {
33276
33359
  dispatch(changeRange([[xMinRange, xMaxRange], [yMinRange, yMaxRange]]));
33277
33360
  }, [dispatch, xMinRange, xMaxRange, yMinRange, yMaxRange]);
33361
+
33362
+ // Update the graph whenever any of the following values changes.
33363
+ // This is necessary to keep the graph previews in sync with the updated
33364
+ // graph settings within the interative graph editor.
33278
33365
  const numSegments = graph.type === "segment" ? graph.numSegments : null;
33366
+ const numPoints = graph.type === "point" ? graph.numPoints : null;
33279
33367
  const numSides = graph.type === "polygon" ? graph.numSides : null;
33280
33368
  const snapTo = graph.type === "polygon" ? graph.snapTo : null;
33281
33369
  const showAngles = graph.type === "polygon" ? graph.showAngles : null;
@@ -33290,7 +33378,7 @@ const StatefulMafsGraph = /*#__PURE__*/React__namespace.forwardRef((props, ref)
33290
33378
  if (latestPropsRef.current !== originalPropsRef.current) {
33291
33379
  dispatch(reinitialize(latestPropsRef.current));
33292
33380
  }
33293
- }, [graph.type, numSegments, numSides, snapTo, showAngles, showSides, latestPropsRef, graph.startCoords]);
33381
+ }, [graph.type, numPoints, numSegments, numSides, snapTo, showAngles, showSides, latestPropsRef, graph.startCoords]);
33294
33382
  return /*#__PURE__*/React__namespace.createElement(MafsGraph, _extends({}, props, {
33295
33383
  state: state,
33296
33384
  dispatch: dispatch
@@ -33353,6 +33441,10 @@ const makeInvalidTypeError = (functionName, graphType) => {
33353
33441
 
33354
33442
  // There's no transform function in exports
33355
33443
 
33444
+ // (LEMS-2190): Move the Mafs Angle Graph coordinate reversal logic in interactive-graph-state.ts
33445
+ // to this file when we remove the legacy graph. This logic allows us to support bi-directional angles
33446
+ // for the new (non-reflexive) Mafs graphs, while maintaining the same scoring behaviour as the legacy graph.
33447
+ // Once the legacy graph is removed, we should move this logic directly into the validate function below.
33356
33448
  class LegacyInteractiveGraph extends React__namespace.Component {
33357
33449
  static contextType = PerseusI18nContext;
33358
33450
  angle;
@@ -34590,7 +34682,8 @@ class InteractiveGraph extends React__namespace.Component {
34590
34682
  snapStep: snapStep,
34591
34683
  box: box,
34592
34684
  showTooltips: !!this.props.showTooltips,
34593
- hintMode: this.props.hintMode
34685
+ hintMode: this.props.hintMode,
34686
+ readOnly: this.props.apiOptions?.readOnly
34594
34687
  }));
34595
34688
  }
34596
34689
  return /*#__PURE__*/React__namespace.createElement(LegacyInteractiveGraph, _extends({
@@ -44512,7 +44605,7 @@ const init = function (options) {
44512
44605
 
44513
44606
  // This file is processed by a Rollup plugin (replace) to inject the production
44514
44607
  const libName = "@khanacademy/perseus";
44515
- const libVersion = "27.1.0";
44608
+ const libVersion = "28.0.0";
44516
44609
  perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
44517
44610
 
44518
44611
  const apiVersion = {
@@ -45070,7 +45163,6 @@ const context = /*#__PURE__*/React__namespace.createContext(defaultContext);
45070
45163
  const {
45071
45164
  mapObject
45072
45165
  } = objective_;
45073
- /* eslint-disable-next-line react/no-unsafe */
45074
45166
  class ServerItemRenderer extends React__namespace.Component {
45075
45167
  static contextType = PerseusI18nContext;
45076
45168
  // @ts-expect-error - TS2564 - Property 'questionRenderer' has no initializer and is not definitely assigned in the constructor.
@@ -46449,7 +46541,16 @@ class MultiRenderer extends React__namespace.Component {
46449
46541
  // _annotateRendererArray() needs this
46450
46542
  ref: null,
46451
46543
  makeRenderer: () => /*#__PURE__*/React__namespace.createElement(HintsRenderer, _extends({}, this._getRendererProps(), {
46452
- findExternalWidgets: findExternalWidgets,
46544
+ findExternalWidgets: findExternalWidgets
46545
+ // Note(Jeremy): The MultiRenderer codebase types are
46546
+ // slightly different from the rest of the codebase.
46547
+ // Ideally `HintNode` would spread the `Hint` type into it,
46548
+ // but there is a difference in optionality of `widgets`
46549
+ // and `images` and that causes a huge cascade of type
46550
+ // errors (they are truly optional, but most of our code
46551
+ // assumes they're provided/set). Ignoring this for now.
46552
+ // @ts-expect-error - TS2769 - Type 'HintNode' is not assignable to type 'Hint'.
46553
+ ,
46453
46554
  hints: [hint]
46454
46555
  }))
46455
46556
  };
@@ -48166,13 +48267,18 @@ exports.generateTestNumericInputWidget = generateTestNumericInputWidget;
48166
48267
  exports.generateTestPerseusItem = generateTestPerseusItem;
48167
48268
  exports.generateTestRadioWidget = generateTestRadioWidget;
48168
48269
  exports.genericPerseusItemData = genericPerseusItemData;
48270
+ exports.getAngleCoords = getAngleCoords;
48169
48271
  exports.getAnswerFromUserInput = getAnswerFromUserInput;
48170
48272
  exports.getAnswersFromWidgets = getAnswersFromWidgets;
48171
48273
  exports.getCircleCoords = getCircleCoords;
48172
48274
  exports.getCorrectAnswerForWidgetId = getCorrectAnswerForWidgetId;
48173
48275
  exports.getLineCoords = getLineCoords;
48174
48276
  exports.getLinearSystemCoords = getLinearSystemCoords;
48277
+ exports.getPointCoords = getPointCoords;
48278
+ exports.getPolygonCoords = getPolygonCoords;
48279
+ exports.getQuadraticCoords = getQuadraticCoords;
48175
48280
  exports.getSegmentCoords = getSegmentCoords;
48281
+ exports.getSinusoidCoords = getSinusoidCoords;
48176
48282
  exports.getValidWidgetIds = getValidWidgetIds;
48177
48283
  exports.getWidgetFromWidgetMap = getWidgetFromWidgetMap;
48178
48284
  exports.getWidgetIdsFromContent = getWidgetIdsFromContent;