@khanacademy/perseus-editor 14.1.3 → 14.2.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 (48) hide show
  1. package/dist/components/util.d.ts +0 -14
  2. package/dist/es/index.js +1399 -1373
  3. package/dist/es/index.js.map +1 -1
  4. package/dist/index.js +1457 -1427
  5. package/dist/index.js.map +1 -1
  6. package/dist/{components → widgets/interactive-graph-editor/components}/graph-points-count-selector.d.ts +1 -1
  7. package/dist/{components → widgets/interactive-graph-editor/components}/graph-type-selector.d.ts +1 -0
  8. package/dist/{components → widgets/interactive-graph-editor/components}/interactive-graph-description.d.ts +1 -1
  9. package/dist/widgets/interactive-graph-editor/interactive-graph-editor.d.ts +220 -128
  10. package/dist/{components/graph-locked-figures → widgets/interactive-graph-editor/locked-figures}/locked-figures-section.d.ts +1 -1
  11. package/dist/widgets/interactive-graph-editor/locked-figures/util.d.ts +9 -0
  12. package/dist/widgets/interactive-graph-editor/start-coords/start-coords-angle.d.ts +9 -0
  13. package/dist/widgets/interactive-graph-editor/start-coords/start-coords-circle.d.ts +12 -0
  14. package/dist/{components → widgets/interactive-graph-editor/start-coords}/start-coords-line.d.ts +2 -2
  15. package/dist/{components → widgets/interactive-graph-editor/start-coords}/start-coords-multiline.d.ts +2 -2
  16. package/dist/{components → widgets/interactive-graph-editor/start-coords}/start-coords-point.d.ts +2 -2
  17. package/dist/{components → widgets/interactive-graph-editor/start-coords}/start-coords-quadratic.d.ts +2 -2
  18. package/dist/{components → widgets/interactive-graph-editor/start-coords}/start-coords-settings.d.ts +2 -1
  19. package/dist/widgets/interactive-graph-editor/start-coords/start-coords-sinusoid.d.ts +9 -0
  20. package/dist/widgets/interactive-graph-editor/start-coords/types.d.ts +24 -0
  21. package/dist/widgets/interactive-graph-editor/start-coords/util.d.ts +8 -0
  22. package/package.json +2 -2
  23. package/dist/components/start-coords-angle.d.ts +0 -8
  24. package/dist/components/start-coords-circle.d.ts +0 -11
  25. package/dist/components/start-coords-sinusoid.d.ts +0 -8
  26. /package/dist/{components → widgets/interactive-graph-editor/components}/interactive-graph-correct-answer.d.ts +0 -0
  27. /package/dist/{components → widgets/interactive-graph-editor/components}/interactive-graph-settings.d.ts +0 -0
  28. /package/dist/{components → widgets/interactive-graph-editor/components}/segment-count-selector.d.ts +0 -0
  29. /package/dist/{components/graph-locked-figures → widgets/interactive-graph-editor/locked-figures}/color-select.d.ts +0 -0
  30. /package/dist/{components/graph-locked-figures → widgets/interactive-graph-editor/locked-figures}/color-swatch.d.ts +0 -0
  31. /package/dist/{components/graph-locked-figures → widgets/interactive-graph-editor/locked-figures}/defining-point-settings.d.ts +0 -0
  32. /package/dist/{components/graph-locked-figures → widgets/interactive-graph-editor/locked-figures}/ellipse-swatch.d.ts +0 -0
  33. /package/dist/{components/graph-locked-figures → widgets/interactive-graph-editor/locked-figures}/labeled-row.d.ts +0 -0
  34. /package/dist/{components/graph-locked-figures → widgets/interactive-graph-editor/locked-figures}/labeled-switch.d.ts +0 -0
  35. /package/dist/{components/graph-locked-figures → widgets/interactive-graph-editor/locked-figures}/line-stroke-select.d.ts +0 -0
  36. /package/dist/{components/graph-locked-figures → widgets/interactive-graph-editor/locked-figures}/line-swatch.d.ts +0 -0
  37. /package/dist/{components/graph-locked-figures → widgets/interactive-graph-editor/locked-figures}/locked-ellipse-settings.d.ts +0 -0
  38. /package/dist/{components/graph-locked-figures → widgets/interactive-graph-editor/locked-figures}/locked-figure-select.d.ts +0 -0
  39. /package/dist/{components/graph-locked-figures → widgets/interactive-graph-editor/locked-figures}/locked-figure-settings-actions.d.ts +0 -0
  40. /package/dist/{components/graph-locked-figures → widgets/interactive-graph-editor/locked-figures}/locked-figure-settings.d.ts +0 -0
  41. /package/dist/{components/graph-locked-figures → widgets/interactive-graph-editor/locked-figures}/locked-function-examples.d.ts +0 -0
  42. /package/dist/{components/graph-locked-figures → widgets/interactive-graph-editor/locked-figures}/locked-function-settings.d.ts +0 -0
  43. /package/dist/{components/graph-locked-figures → widgets/interactive-graph-editor/locked-figures}/locked-label-settings.d.ts +0 -0
  44. /package/dist/{components/graph-locked-figures → widgets/interactive-graph-editor/locked-figures}/locked-line-settings.d.ts +0 -0
  45. /package/dist/{components/graph-locked-figures → widgets/interactive-graph-editor/locked-figures}/locked-point-settings.d.ts +0 -0
  46. /package/dist/{components/graph-locked-figures → widgets/interactive-graph-editor/locked-figures}/locked-polygon-settings.d.ts +0 -0
  47. /package/dist/{components/graph-locked-figures → widgets/interactive-graph-editor/locked-figures}/locked-vector-settings.d.ts +0 -0
  48. /package/dist/{components/graph-locked-figures → widgets/interactive-graph-editor/locked-figures}/polygon-swatch.d.ts +0 -0
package/dist/index.js CHANGED
@@ -26,6 +26,7 @@ var wonderStuffCore = require('@khanacademy/wonder-stuff-core');
26
26
  var mathInput = require('@khanacademy/math-input');
27
27
  var kmath = require('@khanacademy/kmath');
28
28
  var wonderBlocksDropdown = require('@khanacademy/wonder-blocks-dropdown');
29
+ var Banner = require('@khanacademy/wonder-blocks-banner');
29
30
  var wonderBlocksAccordion = require('@khanacademy/wonder-blocks-accordion');
30
31
  var IconButton = require('@khanacademy/wonder-blocks-icon-button');
31
32
  var caretDoubleDownIcon = require('@phosphor-icons/core/bold/caret-double-down-bold.svg');
@@ -41,7 +42,6 @@ var arrowFatRight = require('@phosphor-icons/core/regular/arrow-fat-right.svg');
41
42
  var arrowFatUp = require('@phosphor-icons/core/regular/arrow-fat-up.svg');
42
43
  var minusCircle = require('@phosphor-icons/core/regular/minus-circle.svg');
43
44
  var plusCircle = require('@phosphor-icons/core/regular/plus-circle.svg');
44
- var Banner = require('@khanacademy/wonder-blocks-banner');
45
45
  var arrowCounterClockwise = require('@phosphor-icons/core/bold/arrow-counter-clockwise-bold.svg');
46
46
 
47
47
  function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
@@ -76,6 +76,7 @@ var classNames__default = /*#__PURE__*/_interopDefaultLegacy(classNames);
76
76
  var ReactDOM__default = /*#__PURE__*/_interopDefaultLegacy(ReactDOM);
77
77
  var KAS__namespace = /*#__PURE__*/_interopNamespace(KAS);
78
78
  var Button__default = /*#__PURE__*/_interopDefaultLegacy(Button);
79
+ var Banner__default = /*#__PURE__*/_interopDefaultLegacy(Banner);
79
80
  var IconButton__default = /*#__PURE__*/_interopDefaultLegacy(IconButton);
80
81
  var caretDoubleDownIcon__default = /*#__PURE__*/_interopDefaultLegacy(caretDoubleDownIcon);
81
82
  var caretDoubleUpIcon__default = /*#__PURE__*/_interopDefaultLegacy(caretDoubleUpIcon);
@@ -90,12 +91,11 @@ var arrowFatRight__default = /*#__PURE__*/_interopDefaultLegacy(arrowFatRight);
90
91
  var arrowFatUp__default = /*#__PURE__*/_interopDefaultLegacy(arrowFatUp);
91
92
  var minusCircle__default = /*#__PURE__*/_interopDefaultLegacy(minusCircle);
92
93
  var plusCircle__default = /*#__PURE__*/_interopDefaultLegacy(plusCircle);
93
- var Banner__default = /*#__PURE__*/_interopDefaultLegacy(Banner);
94
94
  var arrowCounterClockwise__default = /*#__PURE__*/_interopDefaultLegacy(arrowCounterClockwise);
95
95
 
96
96
  // This file is processed by a Rollup plugin (replace) to inject the production
97
97
  const libName = "@khanacademy/perseus-editor";
98
- const libVersion = "14.1.3";
98
+ const libVersion = "14.2.0";
99
99
  perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
100
100
 
101
101
  function _extends() {
@@ -29585,39 +29585,96 @@ function invariant(condition, message) {
29585
29585
  throw new Error(value);
29586
29586
  }
29587
29587
 
29588
- // Renders the given children in a View laid out horizontally.
29589
- const LabeledRow = props => {
29590
- const {
29591
- children,
29592
- label,
29593
- labelSide = "left",
29594
- style
29595
- } = props;
29596
- return /*#__PURE__*/React__namespace.createElement("label", {
29597
- className: aphrodite.css(styles$J.label)
29598
- }, /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
29599
- style: [styles$J.row, style]
29600
- }, labelSide === "start" || /*#__PURE__*/React__namespace.createElement(wonderBlocksTypography.LabelSmall, {
29601
- style: styles$J.spaceEnd
29602
- }, label), children, labelSide === "end" && /*#__PURE__*/React__namespace.createElement(wonderBlocksTypography.LabelSmall, {
29603
- style: styles$J.spaceStart
29604
- }, label)));
29588
+ const UNLIMITED = "unlimited";
29589
+ // Parses a string (ostensibly from some UI element that only supports string
29590
+ // types, such as a <select />) into a numeric point count or the special sigil
29591
+ // "unlimited". Note that 0 is a special case, and means "unlimited".
29592
+ const parsePointCount = points => {
29593
+ const parsed = parseInt(points, 10);
29594
+ if (isNaN(parsed)) {
29595
+ return UNLIMITED;
29596
+ }
29597
+ return parsed === 0 ? UNLIMITED : parsed;
29598
+ };
29599
+
29600
+ const GraphPointsCountSelector = _ref => {
29601
+ let {
29602
+ numPoints = 1,
29603
+ onChange
29604
+ } = _ref;
29605
+ return /*#__PURE__*/React__namespace.createElement(wonderBlocksDropdown.SingleSelect, {
29606
+ selectedValue: `${numPoints}`,
29607
+ onChange: newValue => {
29608
+ onChange(parsePointCount(newValue));
29609
+ }
29610
+ // Never uses placeholder, always has value
29611
+ ,
29612
+ placeholder: "",
29613
+ style: styles$J.singleSelectShort
29614
+ }, [...[...Array(7).keys()].map(n => /*#__PURE__*/React__namespace.createElement(wonderBlocksDropdown.OptionItem, {
29615
+ key: n,
29616
+ value: `${n}`,
29617
+ label: `${n} point${n > 1 ? "s" : ""}`
29618
+ })), /*#__PURE__*/React__namespace.createElement(wonderBlocksDropdown.OptionItem, {
29619
+ key: "unlimited",
29620
+ value: UNLIMITED,
29621
+ label: "unlimited"
29622
+ })]);
29605
29623
  };
29606
29624
  const styles$J = aphrodite.StyleSheet.create({
29607
- label: {
29608
- width: "fit-content"
29609
- },
29610
- row: {
29611
- flexDirection: "row",
29612
- marginTop: wonderBlocksTokens.spacing.xSmall_8,
29613
- alignItems: "center",
29614
- width: "fit-content"
29615
- },
29616
- spaceStart: {
29617
- marginInlineStart: wonderBlocksTokens.spacing.xSmall_8
29618
- },
29619
- spaceEnd: {
29620
- marginInlineEnd: wonderBlocksTokens.spacing.xSmall_8
29625
+ singleSelectShort: {
29626
+ // Non-standard spacing, but it's the smallest we can go
29627
+ // without running into styling issues with the dropdown.
29628
+ height: 26
29629
+ }
29630
+ });
29631
+
29632
+ const GraphTypeSelector = props => {
29633
+ return /*#__PURE__*/React__namespace.createElement(wonderBlocksDropdown.SingleSelect, {
29634
+ selectedValue: props.graphType,
29635
+ onChange: props.onChange,
29636
+ placeholder: "Select an answer type",
29637
+ style: styles$I.singleSelectShort
29638
+ }, props.showNoneOption && /*#__PURE__*/React__namespace.createElement(wonderBlocksDropdown.OptionItem, {
29639
+ value: "none",
29640
+ label: "None"
29641
+ }), /*#__PURE__*/React__namespace.createElement(wonderBlocksDropdown.OptionItem, {
29642
+ value: "linear",
29643
+ label: "Linear function"
29644
+ }), /*#__PURE__*/React__namespace.createElement(wonderBlocksDropdown.OptionItem, {
29645
+ value: "quadratic",
29646
+ label: "Quadratic function"
29647
+ }), /*#__PURE__*/React__namespace.createElement(wonderBlocksDropdown.OptionItem, {
29648
+ value: "sinusoid",
29649
+ label: "Sinusoid function"
29650
+ }), /*#__PURE__*/React__namespace.createElement(wonderBlocksDropdown.OptionItem, {
29651
+ value: "circle",
29652
+ label: "Circle"
29653
+ }), /*#__PURE__*/React__namespace.createElement(wonderBlocksDropdown.OptionItem, {
29654
+ value: "point",
29655
+ label: "Point(s)"
29656
+ }), /*#__PURE__*/React__namespace.createElement(wonderBlocksDropdown.OptionItem, {
29657
+ value: "linear-system",
29658
+ label: "Linear System"
29659
+ }), /*#__PURE__*/React__namespace.createElement(wonderBlocksDropdown.OptionItem, {
29660
+ value: "polygon",
29661
+ label: "Polygon"
29662
+ }), /*#__PURE__*/React__namespace.createElement(wonderBlocksDropdown.OptionItem, {
29663
+ value: "segment",
29664
+ label: "Line Segment(s)"
29665
+ }), /*#__PURE__*/React__namespace.createElement(wonderBlocksDropdown.OptionItem, {
29666
+ value: "ray",
29667
+ label: "Ray"
29668
+ }), /*#__PURE__*/React__namespace.createElement(wonderBlocksDropdown.OptionItem, {
29669
+ value: "angle",
29670
+ label: "Angle"
29671
+ }));
29672
+ };
29673
+ const styles$I = aphrodite.StyleSheet.create({
29674
+ singleSelectShort: {
29675
+ // Non-standard spacing, but it's the smallest we can go
29676
+ // without running into styling issues with the dropdown.
29677
+ height: 26
29621
29678
  }
29622
29679
  });
29623
29680
 
@@ -29629,11 +29686,11 @@ function Heading(_ref) {
29629
29686
  onToggle
29630
29687
  } = _ref;
29631
29688
  return /*#__PURE__*/React__namespace.createElement(Clickable__default["default"], {
29632
- style: [styles$I.container, !isCollapsible && styles$I.notClickable],
29689
+ style: [styles$H.container, !isCollapsible && styles$H.notClickable],
29633
29690
  disabled: !isCollapsible,
29634
29691
  onClick: () => isCollapsible && onToggle?.(!isOpen)
29635
29692
  }, () => /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
29636
- style: styles$I.heading
29693
+ style: styles$H.heading
29637
29694
  }, /*#__PURE__*/React__namespace.createElement(wonderBlocksTypography.LabelLarge, {
29638
29695
  style: {
29639
29696
  fontSize: 14,
@@ -29643,7 +29700,7 @@ function Heading(_ref) {
29643
29700
  isExpanded: isOpen
29644
29701
  })));
29645
29702
  }
29646
- const styles$I = aphrodite.StyleSheet.create({
29703
+ const styles$H = aphrodite.StyleSheet.create({
29647
29704
  container: {
29648
29705
  marginTop: wonderBlocksTokens.spacing.small_12,
29649
29706
  // NOTE(jeremy): This is the inverse of the @editorPadding CSS
@@ -29667,348 +29724,765 @@ const styles$I = aphrodite.StyleSheet.create({
29667
29724
  }
29668
29725
  });
29669
29726
 
29670
- function focusWithChromeStickyFocusBugWorkaround(element) {
29671
- // NOTE(marksandstrom) Chrome sticky focus bug.
29672
- //
29673
- // There is a bug in Chrome related to sticky positioning and
29674
- // focus: When a child of an element with sticky positioning is
29675
- // focused with the focus() method, the scrollable area
29676
- // containing that element will scroll to the top (even though
29677
- // the element is already visble on the page). For a dropdown
29678
- // this means that the area will be scrolled to the top when
29679
- // the user interacts with the dropdown (since we actively
29680
- // manage dropdown header and option focus for keyboard users).
29681
- //
29682
- // To work around this bug the `preventScroll` focus option is
29683
- // used.
29684
- //
29685
- // Using `focusOptions` is an experimental browser feature
29686
- // "that should not be used in production code". However, since
29687
- // Chrome is the only browser we support that exhibits this
29688
- // buggy behavior and using `focusOptions` works around the
29689
- // bug, it is perfectly reasonable to use the feature in this
29690
- // case. We are not relying on the effect of the
29691
- // `preventScroll` option otherwise.
29692
- //
29693
- // focusOptions documentation:
29694
- //
29695
- // https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus
29696
- //
29697
- // Note that there could be cases where we want default focus() behavior.
29698
- // One that comes to mind is a dropdown with a long list of options that
29699
- // might be rendered off screen when opened. With this workaround the
29700
- // selected option would not be scrolled into view if the option is near
29701
- // the end of the list. We're not aware of any uses of the dropdown where
29702
- // this is an issue, however, so there isn't currently a way to out of the
29703
- // `preventScroll` behavior.
29704
-
29705
- // @ts-expect-error - TS2339 - Property 'focus' does not exist on type 'Element'.
29706
- element.focus({
29707
- preventScroll: true
29708
- });
29709
- }
29710
- const DEFAULT_COLOR = "grayH";
29711
- function getDefaultFigureForType(type) {
29712
- switch (type) {
29713
- case "point":
29714
- return {
29715
- type: "point",
29716
- coord: [0, 0],
29717
- color: DEFAULT_COLOR,
29718
- filled: true
29719
- };
29720
- case "line":
29721
- return {
29722
- type: "line",
29723
- kind: "line",
29724
- points: [getDefaultFigureForType("point"), {
29725
- ...getDefaultFigureForType("point"),
29726
- coord: [2, 2]
29727
- }],
29728
- color: DEFAULT_COLOR,
29729
- lineStyle: "solid",
29730
- showPoint1: false,
29731
- showPoint2: false
29732
- };
29733
- case "vector":
29734
- return {
29735
- type: "vector",
29736
- points: [[0, 0], [2, 2]],
29737
- color: DEFAULT_COLOR
29738
- };
29739
- case "ellipse":
29740
- return {
29741
- type: "ellipse",
29742
- center: [0, 0],
29743
- radius: [1, 1],
29744
- angle: 0,
29745
- color: DEFAULT_COLOR,
29746
- fillStyle: "none",
29747
- strokeStyle: "solid"
29748
- };
29749
- case "polygon":
29750
- return {
29751
- type: "polygon",
29752
- points: [[0, 2], [-1, 0], [1, 0]],
29753
- color: DEFAULT_COLOR,
29754
- showVertices: false,
29755
- fillStyle: "none",
29756
- strokeStyle: "solid"
29757
- };
29758
- case "function":
29759
- return {
29760
- type: "function",
29761
- color: DEFAULT_COLOR,
29762
- strokeStyle: "solid",
29763
- equation: "x^2",
29764
- directionalAxis: "x"
29765
- };
29766
- case "label":
29767
- return {
29768
- type: "label",
29769
- coord: [0, 0],
29770
- text: "",
29771
- color: DEFAULT_COLOR,
29772
- size: "medium"
29773
- };
29774
- default:
29775
- throw new wonderStuffCore.UnreachableCaseError(type);
29776
- }
29777
- }
29778
- function degreeToRadian(degrees) {
29779
- return degrees / 180 * Math.PI;
29780
- }
29781
- function radianToDegree(radians) {
29782
- return radians / Math.PI * 180;
29783
- }
29784
- function getDefaultGraphStartCoords(graph, range, step) {
29785
- switch (graph.type) {
29786
- case "linear":
29787
- case "ray":
29788
- return perseus.getLineCoords({
29789
- ...graph,
29790
- startCoords: undefined
29791
- }, range, step);
29792
- case "segment":
29793
- return perseus.getSegmentCoords({
29794
- ...graph,
29795
- startCoords: undefined
29796
- }, range, step);
29797
- case "linear-system":
29798
- return perseus.getLinearSystemCoords({
29799
- ...graph,
29800
- startCoords: undefined
29801
- }, range, step);
29802
- case "circle":
29803
- const startCoords = perseus.getCircleCoords({
29804
- ...graph,
29805
- startCoords: undefined
29806
- });
29807
- const radius = kmath.vector.length(kmath.vector.subtract(startCoords.radiusPoint, startCoords.center));
29808
- return {
29809
- center: startCoords.center,
29810
- radius
29811
- };
29812
- case "sinusoid":
29813
- return perseus.getSinusoidCoords({
29814
- ...graph,
29815
- startCoords: undefined
29816
- }, range, step);
29817
- case "quadratic":
29818
- return perseus.getQuadraticCoords({
29819
- ...graph,
29820
- startCoords: undefined
29821
- }, range, step);
29822
- case "point":
29823
- return perseus.getPointCoords({
29824
- ...graph,
29825
- startCoords: undefined
29826
- }, range, step);
29827
- case "polygon":
29828
- return perseus.getPolygonCoords({
29829
- ...graph,
29830
- startCoords: undefined
29831
- }, range, step);
29832
- case "angle":
29833
- return perseus.getAngleCoords({
29834
- graph: {
29835
- ...graph,
29836
- startCoords: undefined
29837
- },
29838
- range,
29839
- step
29840
- });
29841
- default:
29842
- return undefined;
29843
- }
29727
+ function InteractiveGraphCorrectAnswer(props) {
29728
+ return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(Heading, {
29729
+ title: "Correct Answer",
29730
+ isOpen: true,
29731
+ isCollapsible: false
29732
+ }), /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, null, /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, null, /*#__PURE__*/React__namespace.createElement(wonderBlocksTypography.LabelXSmall, {
29733
+ style: {
29734
+ paddingTop: wonderBlocksTokens.spacing.xxSmall_6,
29735
+ paddingBottom: wonderBlocksTokens.spacing.xxSmall_6,
29736
+ color: wonderBlocksTokens.color.offBlack64
29737
+ }
29738
+ }, "Graph the correct answer in the graph below and ensure the equation or point coordinates displayed represent the correct answer."), /*#__PURE__*/React__namespace.createElement(wonderBlocksTypography.BodyMonospace, {
29739
+ style: {
29740
+ fontSize: 12,
29741
+ backgroundColor: "#eee",
29742
+ paddingInline: wonderBlocksTokens.spacing.xxSmall_6,
29743
+ borderColor: "#ccc",
29744
+ borderStyle: "solid",
29745
+ borderWidth: 1
29746
+ }
29747
+ }, props.equationString)), props.children));
29844
29748
  }
29845
- const getSinusoidEquation = startCoords => {
29846
- // Get coefficients
29847
- // It's assumed that p1 is the root and p2 is the first peak
29848
- const p1 = startCoords[0];
29849
- const p2 = startCoords[1];
29850
-
29851
- // Resulting coefficients are canonical for this sine curve
29852
- const amplitude = p2[1] - p1[1];
29853
- const angularFrequency = Math.PI / (2 * (p2[0] - p1[0]));
29854
- const phase = p1[0] * angularFrequency;
29855
- const verticalOffset = p1[1];
29856
- return "y = " + amplitude.toFixed(3) + "sin(" + angularFrequency.toFixed(3) + "x - " + phase.toFixed(3) + ") + " + verticalOffset.toFixed(3);
29857
- };
29858
- const getQuadraticEquation = startCoords => {
29859
- const p1 = startCoords[0];
29860
- const p2 = startCoords[1];
29861
- const p3 = startCoords[2];
29862
- const denom = (p1[0] - p2[0]) * (p1[0] - p3[0]) * (p2[0] - p3[0]);
29863
- if (denom === 0) {
29864
- // Many of the callers assume that the return value is always defined.
29865
- return "Division by zero error";
29866
- }
29867
- const a = (p3[0] * (p2[1] - p1[1]) + p2[0] * (p1[1] - p3[1]) + p1[0] * (p3[1] - p2[1])) / denom;
29868
- const b = (p3[0] * p3[0] * (p1[1] - p2[1]) + p2[0] * p2[0] * (p3[1] - p1[1]) + p1[0] * p1[0] * (p2[1] - p3[1])) / denom;
29869
- const c = (p2[0] * p3[0] * (p2[0] - p3[0]) * p1[1] + p3[0] * p1[0] * (p3[0] - p1[0]) * p2[1] + p1[0] * p2[0] * (p1[0] - p2[0]) * p3[1]) / denom;
29870
- return "y = " + a.toFixed(3) + "x^2 + " + b.toFixed(3) + "x + " + c.toFixed(3);
29871
- };
29872
- const findAngle = (point1, point2) => {
29873
- const x = point1[0] - point2[0];
29874
- const y = point1[1] - point2[1];
29875
- return (180 + Math.atan2(-y, -x) * 180 / Math.PI + 360) % 360;
29876
- };
29877
- const getAngleEquation = startCoords => {
29878
- const [point1, vertex, point2] = startCoords;
29879
- const angle = findAngle(point2, vertex) - findAngle(point1, vertex);
29880
- const roundedAngle = angle.toFixed(0);
29881
- return `${roundedAngle}\u00B0 angle at (${vertex[0]}, ${vertex[1]})`;
29882
- };
29883
- const shouldShowStartCoordsUI = (graph, isStatic) => {
29884
- // We don't show the start coords UI for static graphs, since
29885
- // the coords shown for this are determined by the coords on the
29886
- // "correct" graph. The start coords would not be used here.
29887
- if (isStatic) {
29888
- return false;
29889
- }
29890
- if (graph.type === "point" && graph.numPoints === "unlimited") {
29891
- return false;
29892
- }
29893
- if (graph.type === "polygon" && (graph.numSides === "unlimited" || graph.snapTo === "angles" || graph.snapTo === "sides")) {
29894
- return false;
29895
- }
29896
- return true;
29897
- };
29898
29749
 
29899
- /**
29900
- * Dropdown for selecting a locked figure to add to an interactive graph.
29901
- * Locked figures are elements (points, segmeents, etc.) that are not
29902
- * interactive, just present in the graph's background.
29903
- *
29904
- * Used in the interactive graph editor's locked figures section.
29905
- */
29906
- const LockedFigureSelect = props => {
29750
+ function InteractiveGraphDescription(props) {
29907
29751
  const {
29908
- id,
29752
+ ariaLabelValue,
29753
+ ariaDescriptionValue,
29909
29754
  onChange
29910
29755
  } = props;
29911
- const figureTypes = ["point", "line", "vector", "ellipse", "polygon", "function"];
29912
- if (props.showLabelsFlag) {
29913
- figureTypes.push("label");
29914
- }
29915
- return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
29916
- style: styles$H.container
29917
- }, /*#__PURE__*/React__namespace.createElement(wonderBlocksDropdown.ActionMenu, {
29918
- menuText: "Add locked figure",
29919
- style: styles$H.addElementSelect
29920
- }, figureTypes.map(figureType => /*#__PURE__*/React__namespace.createElement(wonderBlocksDropdown.ActionItem, {
29921
- key: `${id}-${figureType}`,
29922
- label: figureType,
29923
- onClick: () => onChange(figureType)
29756
+ const [isOpen, setIsOpen] = React__namespace.useState(true);
29757
+ return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(Heading, {
29758
+ title: "Description",
29759
+ isCollapsible: true,
29760
+ isOpen: isOpen,
29761
+ onToggle: setIsOpen
29762
+ }), isOpen && /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, null, /*#__PURE__*/React__namespace.createElement(wonderBlocksTypography.LabelXSmall, {
29763
+ style: styles$G.caption
29764
+ }, "Use these fields to describe the graph as a whole. These are used by screen readers to describe content to users who are visually impaired."), /*#__PURE__*/React__namespace.createElement(wonderBlocksTypography.LabelLarge, {
29765
+ tag: "label"
29766
+ }, "Title", /*#__PURE__*/React__namespace.createElement(wonderBlocksForm.TextField, {
29767
+ value: ariaLabelValue,
29768
+ onChange: newValue => onChange({
29769
+ fullGraphAriaLabel: newValue
29770
+ }),
29771
+ style: styles$G.spaceAbove
29772
+ })), /*#__PURE__*/React__namespace.createElement(Strut, {
29773
+ size: wonderBlocksTokens.spacing.small_12
29774
+ }), /*#__PURE__*/React__namespace.createElement(wonderBlocksTypography.LabelLarge, {
29775
+ tag: "label"
29776
+ }, "Description", /*#__PURE__*/React__namespace.createElement(wonderBlocksForm.TextArea, {
29777
+ rows: 8,
29778
+ resizeType: "vertical",
29779
+ value: ariaDescriptionValue,
29780
+ onChange: newValue => onChange({
29781
+ fullGraphAriaDescription: newValue
29782
+ }),
29783
+ style: styles$G.spaceAbove
29924
29784
  }))));
29925
- };
29926
- const styles$H = aphrodite.StyleSheet.create({
29927
- container: {
29928
- marginTop: wonderBlocksTokens.spacing.xSmall_8
29785
+ }
29786
+ const styles$G = aphrodite.StyleSheet.create({
29787
+ caption: {
29788
+ color: wonderBlocksTokens.color.offBlack64,
29789
+ paddingTop: wonderBlocksTokens.spacing.xxSmall_6,
29790
+ paddingBottom: wonderBlocksTokens.spacing.xxSmall_6
29929
29791
  },
29930
- addElementSelect: {
29931
- backgroundColor: wonderBlocksTokens.color.fadedBlue8,
29932
- borderRadius: wonderBlocksTokens.spacing.xxxSmall_4
29792
+ spaceAbove: {
29793
+ marginTop: wonderBlocksTokens.spacing.xxxSmall_4
29933
29794
  }
29934
29795
  });
29935
29796
 
29936
- /**
29937
- * This is a custom text field of type="number" for use in Perseus Editors.
29938
- *
29939
- * This makes it so that the text field's input number updates on scroll
29940
- * without scrolling the page.
29941
- *
29942
- * NOTE 1: Native HTML number inputs do not update the number value on scroll,
29943
- * they only scroll the page. Inputs in React do NOT work this way (explanation
29944
- * here: https://stackoverflow.com/a/68266494). By default, scrolling on a
29945
- * focused number input in React causes BOTH the input value to change AND
29946
- * the page to scroll. The behavior in this component is an improvement on
29947
- * the React behavior, but it's the opposite of the native HTML behavior.
29948
- *
29949
- * NOTE 2: Firefox seems to have a custom override for this. Even with this
29950
- * stopPropogation, Firefox matches the native HTML behavior.
29951
- */
29952
- const ScrolllessNumberTextField = props => {
29953
- const inputRef = React__namespace.useRef(null);
29954
- React__namespace.useEffect(() => {
29955
- const ref = inputRef.current;
29956
-
29957
- // stopPropogation makes it so that the page scroll event is not
29958
- // triggered when the input is focused and the user scrolls.
29959
- // The input value will still change on scroll.
29960
- const ignoreScroll = e => {
29961
- e.stopPropagation();
29962
- };
29963
- ref?.addEventListener("wheel", ignoreScroll);
29964
- return () => {
29965
- ref?.removeEventListener("wheel", ignoreScroll);
29966
- };
29967
- }, [inputRef]);
29968
- return /*#__PURE__*/React__namespace.createElement(wonderBlocksForm.TextField, _extends({
29969
- type: "number"
29970
- }, props, {
29971
- ref: inputRef
29972
- }));
29973
- };
29974
-
29975
- const AngleInput = props => {
29797
+ // Renders the given children in a View laid out horizontally.
29798
+ const LabeledRow = props => {
29976
29799
  const {
29977
- angle,
29978
- onChange
29800
+ children,
29801
+ label,
29802
+ labelSide = "left",
29803
+ style
29979
29804
  } = props;
29980
- const [angleInput, setAngleInput] = React__namespace.useState(radianToDegree(angle).toString());
29981
- function handleAngleChange(newValue) {
29982
- // Update the local state (update the input field value).
29983
- setAngleInput(newValue);
29984
-
29985
- // If the new value is not a number, don't update the props.
29986
- // If it's empty, keep the props the same value instead of setting to 0.
29987
- if (isNaN(+newValue) || newValue === "") {
29988
- return;
29989
- }
29990
-
29991
- // Update the graph.
29992
- onChange(degreeToRadian(newValue));
29993
- }
29994
- return /*#__PURE__*/React__namespace.createElement(wonderBlocksTypography.LabelMedium, {
29995
- tag: "label",
29996
- style: styles$G.row
29997
- }, "angle (degrees)", /*#__PURE__*/React__namespace.createElement(Strut, {
29998
- size: wonderBlocksTokens.spacing.xxSmall_6
29999
- }), /*#__PURE__*/React__namespace.createElement(ScrolllessNumberTextField, {
30000
- value: angleInput,
30001
- onChange: handleAngleChange,
30002
- style: styles$G.textField
30003
- }), /*#__PURE__*/React__namespace.createElement(Strut, {
30004
- size: wonderBlocksTokens.spacing.xxSmall_6
30005
- }));
29805
+ return /*#__PURE__*/React__namespace.createElement("label", {
29806
+ className: aphrodite.css(styles$F.label)
29807
+ }, /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
29808
+ style: [styles$F.row, style]
29809
+ }, labelSide === "start" || /*#__PURE__*/React__namespace.createElement(wonderBlocksTypography.LabelSmall, {
29810
+ style: styles$F.spaceEnd
29811
+ }, label), children, labelSide === "end" && /*#__PURE__*/React__namespace.createElement(wonderBlocksTypography.LabelSmall, {
29812
+ style: styles$F.spaceStart
29813
+ }, label)));
30006
29814
  };
30007
- const styles$G = aphrodite.StyleSheet.create({
29815
+ const styles$F = aphrodite.StyleSheet.create({
29816
+ label: {
29817
+ width: "fit-content"
29818
+ },
30008
29819
  row: {
30009
- display: "flex",
30010
29820
  flexDirection: "row",
30011
- alignItems: "center"
29821
+ marginTop: wonderBlocksTokens.spacing.xSmall_8,
29822
+ alignItems: "center",
29823
+ width: "fit-content"
29824
+ },
29825
+ spaceStart: {
29826
+ marginInlineStart: wonderBlocksTokens.spacing.xSmall_8
29827
+ },
29828
+ spaceEnd: {
29829
+ marginInlineEnd: wonderBlocksTokens.spacing.xSmall_8
29830
+ }
29831
+ });
29832
+
29833
+ /* eslint-disable react/forbid-prop-types, react/no-unsafe */
29834
+ const {
29835
+ ButtonGroup: ButtonGroup$2,
29836
+ InfoTip: InfoTip$d,
29837
+ RangeInput: RangeInput$4
29838
+ } = perseus.components;
29839
+ const defaultBackgroundImage$1 = {
29840
+ url: null,
29841
+ width: 0,
29842
+ height: 0
29843
+ };
29844
+ function numSteps(range, step) {
29845
+ return Math.floor((range[1] - range[0]) / step);
29846
+ }
29847
+ class InteractiveGraphSettings extends React__namespace.Component {
29848
+ _isMounted = false;
29849
+ bgUrlRef = /*#__PURE__*/React__namespace.createRef();
29850
+ labelXRef = /*#__PURE__*/React__namespace.createRef();
29851
+ labelYRef = /*#__PURE__*/React__namespace.createRef();
29852
+ static stateFromProps(props) {
29853
+ return {
29854
+ labelsTextbox: props.labels,
29855
+ gridStepTextbox: props.gridStep,
29856
+ snapStepTextbox: props.snapStep,
29857
+ stepTextbox: props.step,
29858
+ rangeTextbox: props.range,
29859
+ // Shallow-copied clone
29860
+ backgroundImage: {
29861
+ ...props.backgroundImage
29862
+ }
29863
+ };
29864
+ }
29865
+ constructor(props) {
29866
+ super(props);
29867
+ this.state = {
29868
+ isExpanded: true,
29869
+ ...InteractiveGraphSettings.stateFromProps(props)
29870
+ };
29871
+ }
29872
+ static defaultProps = {
29873
+ box: [perseus.interactiveSizes.defaultBoxSizeSmall, perseus.interactiveSizes.defaultBoxSizeSmall],
29874
+ labels: ["x", "y"],
29875
+ range: [[-10, 10], [-10, 10]],
29876
+ step: [1, 1],
29877
+ gridStep: [1, 1],
29878
+ snapStep: [1, 1],
29879
+ valid: true,
29880
+ backgroundImage: defaultBackgroundImage$1,
29881
+ markings: "graph",
29882
+ showProtractor: false,
29883
+ showTooltips: false
29884
+ };
29885
+ componentDidMount() {
29886
+ // TODO(scottgrant): This is a hack to remove the deprecated call to
29887
+ // this.isMounted() but is still considered an anti-pattern.
29888
+ this._isMounted = true;
29889
+ this.changeGraph = ___default["default"].debounce(this.changeGraph, 300);
29890
+ }
29891
+ UNSAFE_componentWillReceiveProps(nextProps) {
29892
+ // Make sure that state updates when switching
29893
+ // between different items in a multi-item editor.
29894
+ if (!___default["default"].isEqual(this.props.labels, nextProps.labels) || !___default["default"].isEqual(this.props.gridStep, nextProps.gridStep) || !___default["default"].isEqual(this.props.snapStep, nextProps.snapStep) || !___default["default"].isEqual(this.props.step, nextProps.step) || !___default["default"].isEqual(this.props.range, nextProps.range) || !___default["default"].isEqual(this.props.backgroundImage, nextProps.backgroundImage)) {
29895
+ this.setState(InteractiveGraphSettings.stateFromProps(nextProps));
29896
+ }
29897
+ }
29898
+ componentWillUnmount() {
29899
+ this._isMounted = false;
29900
+ }
29901
+ change = (() => {
29902
+ var _this = this;
29903
+ return function () {
29904
+ for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
29905
+ args[_key] = arguments[_key];
29906
+ }
29907
+ return perseus.Changeable.change.apply(_this, args);
29908
+ };
29909
+ })();
29910
+ changeBackgroundUrl = e => {
29911
+ // Only continue on blur or "enter"
29912
+ if (e.type === "keypress" && e.key !== "Enter") {
29913
+ return;
29914
+ }
29915
+ const setUrl = (url, width, height) => {
29916
+ // Shallow-copied clone
29917
+ const image = {
29918
+ ...this.props.backgroundImage
29919
+ };
29920
+ image.url = url;
29921
+ image.width = width;
29922
+ image.height = height;
29923
+ this.setState({
29924
+ backgroundImage: image
29925
+ }, this.changeGraph);
29926
+ };
29927
+ const url = this.bgUrlRef.current?.value;
29928
+ if (url) {
29929
+ perseus.Util.getImageSize(url, (width, height) => {
29930
+ if (this._isMounted) {
29931
+ setUrl(url, width, height);
29932
+ }
29933
+ });
29934
+ } else {
29935
+ setUrl(null, 0, 0);
29936
+ }
29937
+ };
29938
+ renderLabelChoices = choices => {
29939
+ return choices.map(nameAndValue => /*#__PURE__*/React__namespace.createElement("option", {
29940
+ key: nameAndValue[1],
29941
+ value: nameAndValue[1]
29942
+ }, nameAndValue[0]));
29943
+ };
29944
+ validRange = range => {
29945
+ const numbers = ___default["default"].every(range, function (num) {
29946
+ return ___default["default"].isFinite(num);
29947
+ });
29948
+ if (!numbers) {
29949
+ return "Range must be a valid number";
29950
+ }
29951
+ if (range[0] >= range[1]) {
29952
+ return "Range must have a higher number on the right";
29953
+ }
29954
+ return true;
29955
+ };
29956
+ validateStepValue = settings => {
29957
+ const {
29958
+ step,
29959
+ range,
29960
+ name,
29961
+ minTicks,
29962
+ maxTicks
29963
+ } = settings;
29964
+ const nSteps = numSteps(range, step);
29965
+ if (nSteps < minTicks) {
29966
+ return name + " is too large, there must be at least " + minTicks + " ticks.";
29967
+ }
29968
+ if (nSteps > maxTicks) {
29969
+ return name + " is too small, there can be at most " + maxTicks + " ticks.";
29970
+ }
29971
+ return true;
29972
+ };
29973
+ validSnapStep = (step, range) => {
29974
+ return this.validateStepValue({
29975
+ step: step,
29976
+ range: range,
29977
+ name: "Snap step",
29978
+ minTicks: 5,
29979
+ maxTicks: 60
29980
+ });
29981
+ };
29982
+ validGridStep = (step, range) => {
29983
+ return this.validateStepValue({
29984
+ step: step,
29985
+ range: range,
29986
+ name: "Grid step",
29987
+ minTicks: 3,
29988
+ maxTicks: 60
29989
+ });
29990
+ };
29991
+ validStep = (step, range) => {
29992
+ return this.validateStepValue({
29993
+ step: step,
29994
+ range: range,
29995
+ name: "Step",
29996
+ minTicks: 3,
29997
+ maxTicks: 20
29998
+ });
29999
+ };
30000
+ validBackgroundImageSize = image => {
30001
+ // Ignore empty images
30002
+ if (!image.url) {
30003
+ return true;
30004
+ }
30005
+ const validSize = image.width <= 450 && image.height <= 450;
30006
+ if (!validSize) {
30007
+ return "Image must be smaller than 450px x 450px.";
30008
+ }
30009
+ return true;
30010
+ };
30011
+ validateGraphSettings = (range, step, gridStep, snapStep, image) => {
30012
+ const self = this;
30013
+ let msg;
30014
+ const goodRange = ___default["default"].every(range, function (range) {
30015
+ msg = self.validRange(range);
30016
+ return msg === true;
30017
+ });
30018
+ if (!goodRange) {
30019
+ return msg;
30020
+ }
30021
+ const goodStep = ___default["default"].every(step, function (step, i) {
30022
+ msg = self.validStep(step, range[i]);
30023
+ return msg === true;
30024
+ });
30025
+ if (!goodStep) {
30026
+ return msg;
30027
+ }
30028
+ const goodGridStep = ___default["default"].every(gridStep, function (gridStep, i) {
30029
+ msg = self.validGridStep(gridStep, range[i]);
30030
+ return msg === true;
30031
+ });
30032
+ if (!goodGridStep) {
30033
+ return msg;
30034
+ }
30035
+ const goodSnapStep = ___default["default"].every(snapStep, function (snapStep, i) {
30036
+ msg = self.validSnapStep(snapStep, range[i]);
30037
+ return msg === true;
30038
+ });
30039
+ if (!goodSnapStep) {
30040
+ return msg;
30041
+ }
30042
+ const goodImageSize = this.validBackgroundImageSize(image);
30043
+ if (goodImageSize !== true) {
30044
+ msg = goodImageSize;
30045
+ return msg;
30046
+ }
30047
+ return true;
30048
+ };
30049
+ changeLabel = (i, e) => {
30050
+ const val = e.target.value;
30051
+ const labels = this.state.labelsTextbox.slice();
30052
+ labels[i] = val;
30053
+ this.setState({
30054
+ labelsTextbox: labels
30055
+ }, this.changeGraph);
30056
+ };
30057
+ changeRange = (i, values) => {
30058
+ const ranges = this.state.rangeTextbox.slice();
30059
+ ranges[i] = values;
30060
+ const step = this.state.stepTextbox.slice();
30061
+ const gridStep = this.state.gridStepTextbox.slice();
30062
+ const snapStep = this.state.snapStepTextbox.slice();
30063
+ const scale = perseus.Util.scaleFromExtent(ranges[i], this.props.box[i]);
30064
+ if (this.validRange(ranges[i]) === true) {
30065
+ step[i] = perseus.Util.tickStepFromExtent(ranges[i], this.props.box[i]);
30066
+ const gridStepValue = perseus.Util.gridStepFromTickStep(step[i], scale);
30067
+ if (gridStepValue) {
30068
+ gridStep[i] = gridStepValue;
30069
+ }
30070
+ snapStep[i] = gridStep[i] / 2;
30071
+ }
30072
+ this.setState({
30073
+ stepTextbox: step,
30074
+ gridStepTextbox: gridStep,
30075
+ snapStepTextbox: snapStep,
30076
+ rangeTextbox: ranges
30077
+ }, this.changeGraph);
30078
+ };
30079
+ changeStep = step => {
30080
+ this.setState({
30081
+ stepTextbox: step
30082
+ }, this.changeGraph);
30083
+ };
30084
+ changeSnapStep = snapStep => {
30085
+ this.setState({
30086
+ snapStepTextbox: snapStep
30087
+ }, this.changeGraph);
30088
+ };
30089
+ changeGridStep = gridStep => {
30090
+ this.setState({
30091
+ gridStepTextbox: gridStep,
30092
+ snapStepTextbox: ___default["default"].map(gridStep, function (step) {
30093
+ return step / 2;
30094
+ })
30095
+ }, this.changeGraph);
30096
+ };
30097
+ changeGraph = () => {
30098
+ const labels = this.state.labelsTextbox;
30099
+ const range = ___default["default"].map(this.state.rangeTextbox, function (range) {
30100
+ return ___default["default"].map(range, Number);
30101
+ });
30102
+ const step = ___default["default"].map(this.state.stepTextbox, Number);
30103
+ const gridStep = this.state.gridStepTextbox;
30104
+ const snapStep = this.state.snapStepTextbox;
30105
+ const image = this.state.backgroundImage;
30106
+
30107
+ // validationResult is either:
30108
+ // true -> the settings are valid
30109
+ // a string -> the settings are invalid, and the explanation
30110
+ // is contained in the string
30111
+ // TODO(aria): Refactor this to not be confusing
30112
+ const validationResult = this.validateGraphSettings(range, step, gridStep, snapStep, image);
30113
+ if (validationResult === true) {
30114
+ // either true or a string
30115
+ this.change({
30116
+ valid: true,
30117
+ labels: labels,
30118
+ range: range,
30119
+ step: step,
30120
+ gridStep: gridStep,
30121
+ snapStep: snapStep,
30122
+ backgroundImage: image
30123
+ });
30124
+ } else {
30125
+ this.change({
30126
+ valid: validationResult // a string message, not false
30127
+ });
30128
+ }
30129
+ };
30130
+
30131
+ render() {
30132
+ return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(Heading, {
30133
+ title: "Common Graph Settings",
30134
+ isOpen: this.state.isExpanded,
30135
+ isCollapsible: true,
30136
+ onToggle: () => this.setState({
30137
+ isExpanded: !this.state.isExpanded
30138
+ })
30139
+ }), this.state.isExpanded && /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, null, /*#__PURE__*/React__namespace.createElement("div", {
30140
+ className: "graph-settings"
30141
+ }, /*#__PURE__*/React__namespace.createElement("div", {
30142
+ className: "perseus-widget-row"
30143
+ }, /*#__PURE__*/React__namespace.createElement("div", {
30144
+ className: "perseus-widget-left-col"
30145
+ }, /*#__PURE__*/React__namespace.createElement(LabeledRow, {
30146
+ label: "x Label"
30147
+ }, /*#__PURE__*/React__namespace.createElement("input", {
30148
+ type: "text",
30149
+ className: "graph-settings-axis-label",
30150
+ ref: this.labelXRef,
30151
+ onChange: e => this.changeLabel(0, e),
30152
+ value: this.state.labelsTextbox[0] || ""
30153
+ }))), /*#__PURE__*/React__namespace.createElement("div", {
30154
+ className: "perseus-widget-right-col"
30155
+ }, /*#__PURE__*/React__namespace.createElement(LabeledRow, {
30156
+ label: "y Label"
30157
+ }, /*#__PURE__*/React__namespace.createElement("input", {
30158
+ type: "text",
30159
+ className: "graph-settings-axis-label",
30160
+ ref: this.labelYRef,
30161
+ onChange: e => this.changeLabel(1, e),
30162
+ value: this.state.labelsTextbox[1] || ""
30163
+ })))), /*#__PURE__*/React__namespace.createElement("div", {
30164
+ className: "perseus-widget-row"
30165
+ }, /*#__PURE__*/React__namespace.createElement("div", {
30166
+ className: "perseus-widget-left-col"
30167
+ }, /*#__PURE__*/React__namespace.createElement(LabeledRow, {
30168
+ label: "x Range"
30169
+ }, /*#__PURE__*/React__namespace.createElement(RangeInput$4, {
30170
+ value: this.state.rangeTextbox[0],
30171
+ onChange: vals => this.changeRange(0, vals)
30172
+ }))), /*#__PURE__*/React__namespace.createElement("div", {
30173
+ className: "perseus-widget-right-col"
30174
+ }, /*#__PURE__*/React__namespace.createElement(LabeledRow, {
30175
+ label: "y Range"
30176
+ }, /*#__PURE__*/React__namespace.createElement(RangeInput$4, {
30177
+ value: this.state.rangeTextbox[1],
30178
+ onChange: vals => this.changeRange(1, vals)
30179
+ })))), /*#__PURE__*/React__namespace.createElement("div", {
30180
+ className: "perseus-widget-row"
30181
+ }, /*#__PURE__*/React__namespace.createElement("div", {
30182
+ className: "perseus-widget-left-col"
30183
+ }, /*#__PURE__*/React__namespace.createElement(LabeledRow, {
30184
+ label: "Tick Step"
30185
+ }, /*#__PURE__*/React__namespace.createElement(RangeInput$4, {
30186
+ value: this.state.stepTextbox,
30187
+ onChange: this.changeStep
30188
+ }))), /*#__PURE__*/React__namespace.createElement("div", {
30189
+ className: "perseus-widget-right-col"
30190
+ }, /*#__PURE__*/React__namespace.createElement(LabeledRow, {
30191
+ label: "Grid Step"
30192
+ }, /*#__PURE__*/React__namespace.createElement(RangeInput$4, {
30193
+ value: this.state.gridStepTextbox,
30194
+ onChange: this.changeGridStep
30195
+ })))), /*#__PURE__*/React__namespace.createElement("div", {
30196
+ className: "perseus-widget-row"
30197
+ }, /*#__PURE__*/React__namespace.createElement("div", {
30198
+ className: "perseus-widget-left-col"
30199
+ }, /*#__PURE__*/React__namespace.createElement(LabeledRow, {
30200
+ label: "Snap Step"
30201
+ }, /*#__PURE__*/React__namespace.createElement(RangeInput$4, {
30202
+ value: this.state.snapStepTextbox,
30203
+ onChange: this.changeSnapStep
30204
+ })))), /*#__PURE__*/React__namespace.createElement("div", {
30205
+ className: "perseus-widget-row"
30206
+ }, /*#__PURE__*/React__namespace.createElement(LabeledRow, {
30207
+ label: "Markings:"
30208
+ }, /*#__PURE__*/React__namespace.createElement(ButtonGroup$2, {
30209
+ value: this.props.markings,
30210
+ allowEmpty: false,
30211
+ buttons: [{
30212
+ value: "graph",
30213
+ content: "Graph"
30214
+ }, {
30215
+ value: "grid",
30216
+ content: "Grid"
30217
+ }, {
30218
+ value: "none",
30219
+ content: "None"
30220
+ }],
30221
+ onChange: this.change("markings")
30222
+ }))), /*#__PURE__*/React__namespace.createElement("div", {
30223
+ className: "perseus-widget-left-col"
30224
+ }, /*#__PURE__*/React__namespace.createElement(wonderBlocksForm.Checkbox, {
30225
+ label: "Show tooltips",
30226
+ checked: this.props.showTooltips,
30227
+ onChange: value => {
30228
+ this.change({
30229
+ showTooltips: value
30230
+ });
30231
+ }
30232
+ }))), /*#__PURE__*/React__namespace.createElement(LabeledRow, {
30233
+ label: "Background image URL:",
30234
+ style: styles$E.resetSpaceTop
30235
+ }, /*#__PURE__*/React__namespace.createElement("input", {
30236
+ type: "text",
30237
+ className: aphrodite.css(styles$E.backgroundUrlInput),
30238
+ ref: this.bgUrlRef,
30239
+ value: this.state.backgroundImage.url || "",
30240
+ onChange: e => {
30241
+ // Shallow-copied clone
30242
+ const image = {
30243
+ ...this.props.backgroundImage
30244
+ };
30245
+ image.url = e.target.value;
30246
+ this.setState({
30247
+ backgroundImage: image
30248
+ });
30249
+ },
30250
+ onKeyPress: this.changeBackgroundUrl,
30251
+ onBlur: this.changeBackgroundUrl
30252
+ }), /*#__PURE__*/React__namespace.createElement(InfoTip$d, null, /*#__PURE__*/React__namespace.createElement("p", null, "Create an image in graphie, or use the \"Add image\" function to create a background."))), /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
30253
+ style: styles$E.protractorSection
30254
+ }, /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
30255
+ style: styles$E.checkboxRow
30256
+ }, /*#__PURE__*/React__namespace.createElement(wonderBlocksForm.Checkbox, {
30257
+ label: "Show protractor",
30258
+ checked: this.props.showProtractor,
30259
+ onChange: value => {
30260
+ this.change({
30261
+ showProtractor: value
30262
+ });
30263
+ },
30264
+ style: styles$E.resetSpaceTop
30265
+ })), this.props.showProtractor && /*#__PURE__*/React__namespace.createElement(Banner__default["default"], {
30266
+ layout: "floating",
30267
+ text: "The protractor is not accessible. Please consider an alternate approach.",
30268
+ kind: "warning"
30269
+ }))));
30270
+ }
30271
+ }
30272
+ const styles$E = aphrodite.StyleSheet.create({
30273
+ resetSpaceTop: {
30274
+ marginTop: 0
30275
+ },
30276
+ backgroundUrlInput: {
30277
+ border: `1px solid ${wonderBlocksTokens.color.offBlack32}`,
30278
+ borderRadius: wonderBlocksTokens.spacing.xxxSmall_4,
30279
+ padding: wonderBlocksTokens.spacing.xxxSmall_4
30280
+ },
30281
+ checkboxRow: {
30282
+ flexDirection: "row",
30283
+ alignItems: "center",
30284
+ justifyContent: "space-between",
30285
+ marginBottom: wonderBlocksTokens.spacing.xSmall_8
30286
+ },
30287
+ protractorSection: {
30288
+ marginTop: wonderBlocksTokens.spacing.xSmall_8,
30289
+ borderTop: `1px solid ${wonderBlocksTokens.color.offBlack16}`,
30290
+ paddingTop: wonderBlocksTokens.spacing.xSmall_8,
30291
+ paddingBottom: wonderBlocksTokens.spacing.xSmall_8,
30292
+ borderBottom: `1px solid ${wonderBlocksTokens.color.offBlack16}`
30293
+ }
30294
+ });
30295
+
30296
+ const SegmentCountSelector = _ref => {
30297
+ let {
30298
+ numSegments = 1,
30299
+ onChange
30300
+ } = _ref;
30301
+ return /*#__PURE__*/React__namespace.createElement(wonderBlocksDropdown.SingleSelect, {
30302
+ key: "segment-select",
30303
+ selectedValue: `${numSegments}`
30304
+ // Never uses placeholder, always has value
30305
+ ,
30306
+ placeholder: "",
30307
+ onChange: newValue => {
30308
+ const num = +newValue;
30309
+ onChange(num);
30310
+ },
30311
+ style: styles$D.singleSelectShort
30312
+ }, ___default["default"].range(1, 7).map(n => /*#__PURE__*/React__namespace.createElement(wonderBlocksDropdown.OptionItem, {
30313
+ key: n,
30314
+ value: `${n}`,
30315
+ label: `${n} segment${n > 1 ? "s" : ""}`
30316
+ })));
30317
+ };
30318
+ const styles$D = aphrodite.StyleSheet.create({
30319
+ singleSelectShort: {
30320
+ // Non-standard spacing, but it's the smallest we can go
30321
+ // without running into styling issues with the dropdown.
30322
+ height: 26
30323
+ }
30324
+ });
30325
+
30326
+ /**
30327
+ * Dropdown for selecting a locked figure to add to an interactive graph.
30328
+ * Locked figures are elements (points, segmeents, etc.) that are not
30329
+ * interactive, just present in the graph's background.
30330
+ *
30331
+ * Used in the interactive graph editor's locked figures section.
30332
+ */
30333
+ const LockedFigureSelect = props => {
30334
+ const {
30335
+ id,
30336
+ onChange
30337
+ } = props;
30338
+ const figureTypes = ["point", "line", "vector", "ellipse", "polygon", "function"];
30339
+ if (props.showLabelsFlag) {
30340
+ figureTypes.push("label");
30341
+ }
30342
+ return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
30343
+ style: styles$C.container
30344
+ }, /*#__PURE__*/React__namespace.createElement(wonderBlocksDropdown.ActionMenu, {
30345
+ menuText: "Add locked figure",
30346
+ style: styles$C.addElementSelect
30347
+ }, figureTypes.map(figureType => /*#__PURE__*/React__namespace.createElement(wonderBlocksDropdown.ActionItem, {
30348
+ key: `${id}-${figureType}`,
30349
+ label: figureType,
30350
+ onClick: () => onChange(figureType)
30351
+ }))));
30352
+ };
30353
+ const styles$C = aphrodite.StyleSheet.create({
30354
+ container: {
30355
+ marginTop: wonderBlocksTokens.spacing.xSmall_8
30356
+ },
30357
+ addElementSelect: {
30358
+ backgroundColor: wonderBlocksTokens.color.fadedBlue8,
30359
+ borderRadius: wonderBlocksTokens.spacing.xxxSmall_4
30360
+ }
30361
+ });
30362
+
30363
+ /**
30364
+ * This is a custom text field of type="number" for use in Perseus Editors.
30365
+ *
30366
+ * This makes it so that the text field's input number updates on scroll
30367
+ * without scrolling the page.
30368
+ *
30369
+ * NOTE 1: Native HTML number inputs do not update the number value on scroll,
30370
+ * they only scroll the page. Inputs in React do NOT work this way (explanation
30371
+ * here: https://stackoverflow.com/a/68266494). By default, scrolling on a
30372
+ * focused number input in React causes BOTH the input value to change AND
30373
+ * the page to scroll. The behavior in this component is an improvement on
30374
+ * the React behavior, but it's the opposite of the native HTML behavior.
30375
+ *
30376
+ * NOTE 2: Firefox seems to have a custom override for this. Even with this
30377
+ * stopPropogation, Firefox matches the native HTML behavior.
30378
+ */
30379
+ const ScrolllessNumberTextField = props => {
30380
+ const inputRef = React__namespace.useRef(null);
30381
+ React__namespace.useEffect(() => {
30382
+ const ref = inputRef.current;
30383
+
30384
+ // stopPropogation makes it so that the page scroll event is not
30385
+ // triggered when the input is focused and the user scrolls.
30386
+ // The input value will still change on scroll.
30387
+ const ignoreScroll = e => {
30388
+ e.stopPropagation();
30389
+ };
30390
+ ref?.addEventListener("wheel", ignoreScroll);
30391
+ return () => {
30392
+ ref?.removeEventListener("wheel", ignoreScroll);
30393
+ };
30394
+ }, [inputRef]);
30395
+ return /*#__PURE__*/React__namespace.createElement(wonderBlocksForm.TextField, _extends({
30396
+ type: "number"
30397
+ }, props, {
30398
+ ref: inputRef
30399
+ }));
30400
+ };
30401
+
30402
+ function focusWithChromeStickyFocusBugWorkaround(element) {
30403
+ // NOTE(marksandstrom) Chrome sticky focus bug.
30404
+ //
30405
+ // There is a bug in Chrome related to sticky positioning and
30406
+ // focus: When a child of an element with sticky positioning is
30407
+ // focused with the focus() method, the scrollable area
30408
+ // containing that element will scroll to the top (even though
30409
+ // the element is already visble on the page). For a dropdown
30410
+ // this means that the area will be scrolled to the top when
30411
+ // the user interacts with the dropdown (since we actively
30412
+ // manage dropdown header and option focus for keyboard users).
30413
+ //
30414
+ // To work around this bug the `preventScroll` focus option is
30415
+ // used.
30416
+ //
30417
+ // Using `focusOptions` is an experimental browser feature
30418
+ // "that should not be used in production code". However, since
30419
+ // Chrome is the only browser we support that exhibits this
30420
+ // buggy behavior and using `focusOptions` works around the
30421
+ // bug, it is perfectly reasonable to use the feature in this
30422
+ // case. We are not relying on the effect of the
30423
+ // `preventScroll` option otherwise.
30424
+ //
30425
+ // focusOptions documentation:
30426
+ //
30427
+ // https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus
30428
+ //
30429
+ // Note that there could be cases where we want default focus() behavior.
30430
+ // One that comes to mind is a dropdown with a long list of options that
30431
+ // might be rendered off screen when opened. With this workaround the
30432
+ // selected option would not be scrolled into view if the option is near
30433
+ // the end of the list. We're not aware of any uses of the dropdown where
30434
+ // this is an issue, however, so there isn't currently a way to out of the
30435
+ // `preventScroll` behavior.
30436
+
30437
+ // @ts-expect-error - TS2339 - Property 'focus' does not exist on type 'Element'.
30438
+ element.focus({
30439
+ preventScroll: true
30440
+ });
30441
+ }
30442
+ function degreeToRadian(degrees) {
30443
+ return degrees / 180 * Math.PI;
30444
+ }
30445
+ function radianToDegree(radians) {
30446
+ return radians / Math.PI * 180;
30447
+ }
30448
+
30449
+ const AngleInput = props => {
30450
+ const {
30451
+ angle,
30452
+ onChange
30453
+ } = props;
30454
+ const [angleInput, setAngleInput] = React__namespace.useState(radianToDegree(angle).toString());
30455
+ function handleAngleChange(newValue) {
30456
+ // Update the local state (update the input field value).
30457
+ setAngleInput(newValue);
30458
+
30459
+ // If the new value is not a number, don't update the props.
30460
+ // If it's empty, keep the props the same value instead of setting to 0.
30461
+ if (isNaN(+newValue) || newValue === "") {
30462
+ return;
30463
+ }
30464
+
30465
+ // Update the graph.
30466
+ onChange(degreeToRadian(newValue));
30467
+ }
30468
+ return /*#__PURE__*/React__namespace.createElement(wonderBlocksTypography.LabelMedium, {
30469
+ tag: "label",
30470
+ style: styles$B.row
30471
+ }, "angle (degrees)", /*#__PURE__*/React__namespace.createElement(Strut, {
30472
+ size: wonderBlocksTokens.spacing.xxSmall_6
30473
+ }), /*#__PURE__*/React__namespace.createElement(ScrolllessNumberTextField, {
30474
+ value: angleInput,
30475
+ onChange: handleAngleChange,
30476
+ style: styles$B.textField
30477
+ }), /*#__PURE__*/React__namespace.createElement(Strut, {
30478
+ size: wonderBlocksTokens.spacing.xxSmall_6
30479
+ }));
30480
+ };
30481
+ const styles$B = aphrodite.StyleSheet.create({
30482
+ row: {
30483
+ display: "flex",
30484
+ flexDirection: "row",
30485
+ alignItems: "center"
30012
30486
  },
30013
30487
  textField: {
30014
30488
  width: wonderBlocksTokens.spacing.xxxLarge_64
@@ -30053,30 +30527,30 @@ const CoordinatePairInput = props => {
30053
30527
  onChange(newCoords);
30054
30528
  }
30055
30529
  return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
30056
- style: [styles$F.row, style]
30530
+ style: [styles$A.row, style]
30057
30531
  }, /*#__PURE__*/React__namespace.createElement(wonderBlocksTypography.LabelMedium, {
30058
30532
  tag: "label",
30059
- style: styles$F.row
30533
+ style: styles$A.row
30060
30534
  }, labels ? labels[0] : "x coord", /*#__PURE__*/React__namespace.createElement(Strut, {
30061
30535
  size: wonderBlocksTokens.spacing.xxSmall_6
30062
30536
  }), /*#__PURE__*/React__namespace.createElement(ScrolllessNumberTextField, {
30063
30537
  value: coordState[0],
30064
30538
  onChange: newValue => handleCoordChange(newValue, 0),
30065
- style: [styles$F.textField, error ? styles$F.errorField : undefined]
30539
+ style: [styles$A.textField, error ? styles$A.errorField : undefined]
30066
30540
  })), /*#__PURE__*/React__namespace.createElement(Strut, {
30067
30541
  size: wonderBlocksTokens.spacing.medium_16
30068
30542
  }), /*#__PURE__*/React__namespace.createElement(wonderBlocksTypography.LabelMedium, {
30069
30543
  tag: "label",
30070
- style: styles$F.row
30544
+ style: styles$A.row
30071
30545
  }, labels ? labels[1] : "y coord", /*#__PURE__*/React__namespace.createElement(Strut, {
30072
30546
  size: wonderBlocksTokens.spacing.xxSmall_6
30073
30547
  }), /*#__PURE__*/React__namespace.createElement(ScrolllessNumberTextField, {
30074
30548
  value: coordState[1],
30075
30549
  onChange: newValue => handleCoordChange(newValue, 1),
30076
- style: [styles$F.textField, error ? styles$F.errorField : undefined]
30550
+ style: [styles$A.textField, error ? styles$A.errorField : undefined]
30077
30551
  })));
30078
30552
  };
30079
- const styles$F = aphrodite.StyleSheet.create({
30553
+ const styles$A = aphrodite.StyleSheet.create({
30080
30554
  row: {
30081
30555
  display: "flex",
30082
30556
  flexDirection: "row",
@@ -30109,14 +30583,14 @@ const PerseusEditorAccordion = props => {
30109
30583
  }, /*#__PURE__*/React__namespace.createElement(wonderBlocksAccordion.AccordionSection, {
30110
30584
  expanded: expanded,
30111
30585
  onToggle: onToggle,
30112
- style: [styles$E.container, containerStyle],
30113
- headerStyle: styles$E.accordionHeader,
30586
+ style: [styles$z.container, containerStyle],
30587
+ headerStyle: styles$z.accordionHeader,
30114
30588
  header: header
30115
30589
  }, /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
30116
- style: [styles$E.accordionPanel, panelStyle]
30590
+ style: [styles$z.accordionPanel, panelStyle]
30117
30591
  }, children)));
30118
30592
  };
30119
- const styles$E = aphrodite.StyleSheet.create({
30593
+ const styles$z = aphrodite.StyleSheet.create({
30120
30594
  container: {
30121
30595
  backgroundColor: wonderBlocksTokens.color.fadedBlue8,
30122
30596
  marginTop: wonderBlocksTokens.spacing.xSmall_8
@@ -30147,13 +30621,13 @@ const ColorSwatch = props => {
30147
30621
  "aria-label":
30148
30622
  // Don't include an aria label for decorative circles.
30149
30623
  !decorative ? `${color}, ${filled ? "filled" : "open"}` : undefined,
30150
- style: [styles$D.colorSwatch, {
30624
+ style: [styles$y.colorSwatch, {
30151
30625
  border: `4px solid ${perseus.lockedFigureColors[color]}`,
30152
30626
  backgroundColor: filled ? perseus.lockedFigureColors[color] : wonderBlocksTokens.color.white
30153
30627
  }]
30154
30628
  });
30155
30629
  };
30156
- const styles$D = aphrodite.StyleSheet.create({
30630
+ const styles$y = aphrodite.StyleSheet.create({
30157
30631
  colorSwatch: {
30158
30632
  // Add a white outline so that the color swatch is visible when
30159
30633
  // the dropdown option is highlighted with its blue background.
@@ -30172,10 +30646,10 @@ const ColorSelect = props => {
30172
30646
  onChange
30173
30647
  } = props;
30174
30648
  return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
30175
- style: [styles$C.row, style]
30649
+ style: [styles$x.row, style]
30176
30650
  }, /*#__PURE__*/React__namespace.createElement(wonderBlocksTypography.LabelMedium, {
30177
30651
  tag: "label",
30178
- style: styles$C.row
30652
+ style: styles$x.row
30179
30653
  }, "color", /*#__PURE__*/React__namespace.createElement(Strut, {
30180
30654
  size: wonderBlocksTokens.spacing.xxSmall_6
30181
30655
  }), /*#__PURE__*/React__namespace.createElement(wonderBlocksDropdown.SingleSelect, {
@@ -30194,7 +30668,7 @@ const ColorSelect = props => {
30194
30668
  })
30195
30669
  })))));
30196
30670
  };
30197
- const styles$C = aphrodite.StyleSheet.create({
30671
+ const styles$x = aphrodite.StyleSheet.create({
30198
30672
  row: {
30199
30673
  display: "flex",
30200
30674
  flexDirection: "row",
@@ -30213,17 +30687,17 @@ const EllipseSwatch = props => {
30213
30687
  } = props;
30214
30688
  return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
30215
30689
  "aria-label": `${color}, stroke ${strokeStyle}, fill ${fillStyle}`,
30216
- style: [styles$B.container, {
30690
+ style: [styles$w.container, {
30217
30691
  border: `4px ${strokeStyle} ${perseus.lockedFigureColors[color]}`
30218
30692
  }]
30219
30693
  }, /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
30220
- style: [styles$B.innerCircle, {
30694
+ style: [styles$w.innerCircle, {
30221
30695
  backgroundColor: perseus.lockedFigureColors[color],
30222
30696
  opacity: fillStyle === "white" ? 0 : perseus.lockedFigureFillStyles[fillStyle]
30223
30697
  }]
30224
30698
  }));
30225
30699
  };
30226
- const styles$B = aphrodite.StyleSheet.create({
30700
+ const styles$w = aphrodite.StyleSheet.create({
30227
30701
  container: {
30228
30702
  // Add a white outline so that the color swatch is visible when
30229
30703
  // the dropdown option is highlighted with its blue background.
@@ -30249,7 +30723,7 @@ const LineStrokeSelect = props => {
30249
30723
  } = props;
30250
30724
  return /*#__PURE__*/React__namespace.createElement(wonderBlocksTypography.LabelMedium, {
30251
30725
  tag: "label",
30252
- style: styles$A.lineStrokeSelect
30726
+ style: styles$v.lineStrokeSelect
30253
30727
  }, "stroke", /*#__PURE__*/React__namespace.createElement(Strut, {
30254
30728
  size: wonderBlocksTokens.spacing.xxxSmall_4
30255
30729
  }), /*#__PURE__*/React__namespace.createElement(wonderBlocksDropdown.SingleSelect, {
@@ -30266,7 +30740,7 @@ const LineStrokeSelect = props => {
30266
30740
  label: "dashed"
30267
30741
  })));
30268
30742
  };
30269
- const styles$A = aphrodite.StyleSheet.create({
30743
+ const styles$v = aphrodite.StyleSheet.create({
30270
30744
  lineStrokeSelect: {
30271
30745
  display: "flex",
30272
30746
  flexDirection: "row",
@@ -30288,40 +30762,40 @@ const LockedFigureSettingsActions = props => {
30288
30762
  onRemove
30289
30763
  } = props;
30290
30764
  return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
30291
- style: styles$z.container
30765
+ style: styles$u.container
30292
30766
  }, /*#__PURE__*/React__namespace.createElement(Button__default["default"], {
30293
30767
  startIcon: trashIcon__default["default"],
30294
30768
  "aria-label": `Delete locked ${figureType}`,
30295
30769
  onClick: onRemove,
30296
30770
  kind: "tertiary",
30297
- style: styles$z.deleteButton
30771
+ style: styles$u.deleteButton
30298
30772
  }, "Delete"), /*#__PURE__*/React__namespace.createElement(Spring, null), /*#__PURE__*/React__namespace.createElement(IconButton__default["default"], {
30299
30773
  icon: caretDoubleUpIcon__default["default"],
30300
30774
  size: "small",
30301
30775
  "aria-label": `Move locked ${figureType} to the back`,
30302
30776
  onClick: () => onMove("back"),
30303
- style: styles$z.iconButton
30777
+ style: styles$u.iconButton
30304
30778
  }), /*#__PURE__*/React__namespace.createElement(IconButton__default["default"], {
30305
30779
  icon: caretUpIcon__default["default"],
30306
30780
  size: "small",
30307
30781
  "aria-label": `Move locked ${figureType} backward`,
30308
30782
  onClick: () => onMove("backward"),
30309
- style: styles$z.iconButton
30783
+ style: styles$u.iconButton
30310
30784
  }), /*#__PURE__*/React__namespace.createElement(IconButton__default["default"], {
30311
30785
  icon: caretDownIcon__default["default"],
30312
30786
  size: "small",
30313
30787
  "aria-label": `Move locked ${figureType} forward`,
30314
30788
  onClick: () => onMove("forward"),
30315
- style: styles$z.iconButton
30789
+ style: styles$u.iconButton
30316
30790
  }), /*#__PURE__*/React__namespace.createElement(IconButton__default["default"], {
30317
30791
  icon: caretDoubleDownIcon__default["default"],
30318
30792
  size: "small",
30319
30793
  "aria-label": `Move locked ${figureType} to the front`,
30320
30794
  onClick: () => onMove("front"),
30321
- style: styles$z.iconButton
30795
+ style: styles$u.iconButton
30322
30796
  }));
30323
30797
  };
30324
- const styles$z = aphrodite.StyleSheet.create({
30798
+ const styles$u = aphrodite.StyleSheet.create({
30325
30799
  container: {
30326
30800
  width: "100%",
30327
30801
  flexDirection: "row",
@@ -30340,7 +30814,7 @@ const styles$z = aphrodite.StyleSheet.create({
30340
30814
  });
30341
30815
 
30342
30816
  const {
30343
- InfoTip: InfoTip$d
30817
+ InfoTip: InfoTip$c
30344
30818
  } = perseus.components;
30345
30819
  const LockedEllipseSettings = props => {
30346
30820
  const {
@@ -30368,7 +30842,7 @@ const LockedEllipseSettings = props => {
30368
30842
  /*#__PURE__*/
30369
30843
  // Summary: Ellipse, center, radius, color (opacity, dashed)
30370
30844
  React__namespace.createElement(wonderBlocksCore.View, {
30371
- style: styles$y.row
30845
+ style: styles$t.row
30372
30846
  }, /*#__PURE__*/React__namespace.createElement(wonderBlocksTypography.LabelLarge, null, `Ellipse (${center[0]}, ${center[1]}), radius ${radius[0]}, ${radius[1]}`), /*#__PURE__*/React__namespace.createElement(Strut, {
30373
30847
  size: wonderBlocksTokens.spacing.xSmall_8
30374
30848
  }), /*#__PURE__*/React__namespace.createElement(EllipseSwatch, {
@@ -30377,19 +30851,19 @@ const LockedEllipseSettings = props => {
30377
30851
  strokeStyle: strokeStyle
30378
30852
  }))
30379
30853
  }, /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
30380
- style: styles$y.row
30854
+ style: styles$t.row
30381
30855
  }, /*#__PURE__*/React__namespace.createElement(CoordinatePairInput, {
30382
30856
  coord: center,
30383
- style: styles$y.spaceUnder,
30857
+ style: styles$t.spaceUnder,
30384
30858
  onChange: newCoords => onChangeProps({
30385
30859
  center: newCoords
30386
30860
  })
30387
30861
  }), /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
30388
- style: styles$y.spaceUnder
30389
- }, /*#__PURE__*/React__namespace.createElement(InfoTip$d, null, "The coordinates for the center of the ellipse."))), /*#__PURE__*/React__namespace.createElement(CoordinatePairInput, {
30862
+ style: styles$t.spaceUnder
30863
+ }, /*#__PURE__*/React__namespace.createElement(InfoTip$c, null, "The coordinates for the center of the ellipse."))), /*#__PURE__*/React__namespace.createElement(CoordinatePairInput, {
30390
30864
  coord: radius,
30391
30865
  labels: ["x radius", "y radius"],
30392
- style: styles$y.spaceUnder,
30866
+ style: styles$t.spaceUnder,
30393
30867
  onChange: newCoords => onChangeProps({
30394
30868
  radius: newCoords
30395
30869
  })
@@ -30401,7 +30875,7 @@ const LockedEllipseSettings = props => {
30401
30875
  }), /*#__PURE__*/React__namespace.createElement(Strut, {
30402
30876
  size: wonderBlocksTokens.spacing.xSmall_8
30403
30877
  }), /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
30404
- style: [styles$y.row, styles$y.spaceUnder]
30878
+ style: [styles$t.row, styles$t.spaceUnder]
30405
30879
  }, /*#__PURE__*/React__namespace.createElement(ColorSelect, {
30406
30880
  selectedValue: color,
30407
30881
  onChange: handleColorChange
@@ -30409,7 +30883,7 @@ const LockedEllipseSettings = props => {
30409
30883
  size: wonderBlocksTokens.spacing.medium_16
30410
30884
  }), /*#__PURE__*/React__namespace.createElement(wonderBlocksTypography.LabelMedium, {
30411
30885
  tag: "label",
30412
- style: [styles$y.row, styles$y.truncatedWidth]
30886
+ style: [styles$t.row, styles$t.truncatedWidth]
30413
30887
  }, "fill", /*#__PURE__*/React__namespace.createElement(Strut, {
30414
30888
  size: wonderBlocksTokens.spacing.xxSmall_6
30415
30889
  }), /*#__PURE__*/React__namespace.createElement(wonderBlocksDropdown.SingleSelect, {
@@ -30435,7 +30909,7 @@ const LockedEllipseSettings = props => {
30435
30909
  onRemove: onRemove
30436
30910
  }));
30437
30911
  };
30438
- const styles$y = aphrodite.StyleSheet.create({
30912
+ const styles$t = aphrodite.StyleSheet.create({
30439
30913
  row: {
30440
30914
  display: "flex",
30441
30915
  flexDirection: "row",
@@ -30456,15 +30930,15 @@ const LineSwatch = props => {
30456
30930
  lineStyle
30457
30931
  } = props;
30458
30932
  return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
30459
- style: styles$x.container
30933
+ style: styles$s.container
30460
30934
  }, /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
30461
30935
  "aria-label": `${color}, ${lineStyle}`,
30462
- style: [styles$x.lineSwatch, {
30936
+ style: [styles$s.lineSwatch, {
30463
30937
  border: `5px ${lineStyle} ${perseus.lockedFigureColors[color]}`
30464
30938
  }]
30465
30939
  }));
30466
30940
  };
30467
- const styles$x = aphrodite.StyleSheet.create({
30941
+ const styles$s = aphrodite.StyleSheet.create({
30468
30942
  container: {
30469
30943
  backgroundColor: wonderBlocksTokens.color.white,
30470
30944
  justifyContent: "center",
@@ -30549,9 +31023,9 @@ const LockedFunctionSettings = props => {
30549
31023
  expanded: props.expanded,
30550
31024
  onToggle: props.onToggle,
30551
31025
  header: /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
30552
- style: styles$w.row
31026
+ style: styles$r.row
30553
31027
  }, /*#__PURE__*/React__namespace.createElement(wonderBlocksTypography.LabelLarge, {
30554
- style: styles$w.accordionHeader
31028
+ style: styles$r.accordionHeader
30555
31029
  }, lineLabel), /*#__PURE__*/React__namespace.createElement(Strut, {
30556
31030
  size: wonderBlocksTokens.spacing.xSmall_8
30557
31031
  }), /*#__PURE__*/React__namespace.createElement(LineSwatch, {
@@ -30559,7 +31033,7 @@ const LockedFunctionSettings = props => {
30559
31033
  lineStyle: strokeStyle
30560
31034
  }))
30561
31035
  }, /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
30562
- style: [styles$w.row, styles$w.spaceUnder]
31036
+ style: [styles$r.row, styles$r.spaceUnder]
30563
31037
  }, /*#__PURE__*/React__namespace.createElement(ColorSelect, {
30564
31038
  selectedValue: lineColor,
30565
31039
  onChange: newValue => {
@@ -30573,14 +31047,14 @@ const LockedFunctionSettings = props => {
30573
31047
  handlePropChange("strokeStyle", newValue);
30574
31048
  }
30575
31049
  })), /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
30576
- style: [styles$w.row, styles$w.rowSpace]
31050
+ style: [styles$r.row, styles$r.rowSpace]
30577
31051
  }, /*#__PURE__*/React__namespace.createElement(wonderBlocksDropdown.SingleSelect, {
30578
31052
  selectedValue: directionalAxis,
30579
31053
  onChange: newValue => {
30580
31054
  handlePropChange("directionalAxis", newValue);
30581
31055
  },
30582
31056
  "aria-label": "equation prefix",
30583
- style: [styles$w.dropdownLabel, styles$w.axisMenu]
31057
+ style: [styles$r.dropdownLabel, styles$r.axisMenu]
30584
31058
  // Placeholder is required, but never gets used.
30585
31059
  ,
30586
31060
  placeholder: ""
@@ -30599,17 +31073,17 @@ const LockedFunctionSettings = props => {
30599
31073
  onChange: newValue => {
30600
31074
  handlePropChange("equation", newValue);
30601
31075
  },
30602
- style: [styles$w.textField]
31076
+ style: [styles$r.textField]
30603
31077
  })), /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
30604
- style: [styles$w.row, styles$w.rowSpace]
31078
+ style: [styles$r.row, styles$r.rowSpace]
30605
31079
  }, /*#__PURE__*/React__namespace.createElement(wonderBlocksTypography.LabelMedium, {
30606
31080
  tag: "label",
30607
- style: [styles$w.dropdownLabel, styles$w.domainMin]
31081
+ style: [styles$r.dropdownLabel, styles$r.domainMin]
30608
31082
  }, `${domainRangeText} min`, /*#__PURE__*/React__namespace.createElement(Strut, {
30609
31083
  size: wonderBlocksTokens.spacing.xxSmall_6
30610
31084
  }), /*#__PURE__*/React__namespace.createElement(wonderBlocksForm.TextField, {
30611
31085
  type: "number",
30612
- style: styles$w.domainMinField,
31086
+ style: styles$r.domainMinField,
30613
31087
  value: domainEntries[0],
30614
31088
  onChange: newValue => {
30615
31089
  handleDomainChange(0, newValue);
@@ -30619,12 +31093,12 @@ const LockedFunctionSettings = props => {
30619
31093
  }), /*#__PURE__*/React__namespace.createElement(wonderBlocksTypography.LabelMedium, {
30620
31094
  tag: "label",
30621
31095
  "aria-label": `${domainRangeText} max`,
30622
- style: [styles$w.dropdownLabel, styles$w.domainMax]
31096
+ style: [styles$r.dropdownLabel, styles$r.domainMax]
30623
31097
  }, "max", /*#__PURE__*/React__namespace.createElement(Strut, {
30624
31098
  size: wonderBlocksTokens.spacing.xxSmall_6
30625
31099
  }), /*#__PURE__*/React__namespace.createElement(wonderBlocksForm.TextField, {
30626
31100
  type: "number",
30627
- style: styles$w.domainMaxField,
31101
+ style: styles$r.domainMaxField,
30628
31102
  value: domainEntries[1],
30629
31103
  onChange: newValue => {
30630
31104
  handleDomainChange(1, newValue);
@@ -30632,11 +31106,11 @@ const LockedFunctionSettings = props => {
30632
31106
  }))), /*#__PURE__*/React__namespace.createElement(PerseusEditorAccordion, {
30633
31107
  header: /*#__PURE__*/React__namespace.createElement(wonderBlocksTypography.LabelLarge, null, "Example Functions"),
30634
31108
  expanded: false,
30635
- containerStyle: styles$w.exampleWorkspace,
30636
- panelStyle: styles$w.exampleAccordionPanel
31109
+ containerStyle: styles$r.exampleWorkspace,
31110
+ panelStyle: styles$r.exampleAccordionPanel
30637
31111
  }, /*#__PURE__*/React__namespace.createElement(wonderBlocksTypography.LabelMedium, {
30638
31112
  tag: "label",
30639
- style: styles$w.dropdownLabel
31113
+ style: styles$r.dropdownLabel
30640
31114
  }, "Choose a category", /*#__PURE__*/React__namespace.createElement(Strut, {
30641
31115
  size: wonderBlocksTokens.spacing.xxSmall_6
30642
31116
  }), /*#__PURE__*/React__namespace.createElement(wonderBlocksDropdown.SingleSelect, {
@@ -30650,7 +31124,7 @@ const LockedFunctionSettings = props => {
30650
31124
  label: category
30651
31125
  });
30652
31126
  }))), exampleCategorySelected && /*#__PURE__*/React__namespace.createElement("ul", {
30653
- className: aphrodite.css(styles$w.exampleContainer)
31127
+ className: aphrodite.css(styles$r.exampleContainer)
30654
31128
  }, exampleContent.map((example, index) => /*#__PURE__*/React__namespace.createElement(ExampleItem, {
30655
31129
  category: exampleCategory,
30656
31130
  example: example,
@@ -30672,29 +31146,29 @@ const ExampleItem = props => {
30672
31146
  const exampleId = React.useId();
30673
31147
  return /*#__PURE__*/React__namespace.createElement("li", {
30674
31148
  key: `${category}-${index}`,
30675
- className: aphrodite.css(styles$w.exampleRow)
31149
+ className: aphrodite.css(styles$r.exampleRow)
30676
31150
  }, /*#__PURE__*/React__namespace.createElement(IconButton__default["default"], {
30677
31151
  icon: autoPasteIcon__default["default"],
30678
31152
  "aria-label": "paste example",
30679
31153
  "aria-describedby": exampleId,
30680
31154
  onClick: () => pasteEquationFn("equation", example),
30681
31155
  size: "medium",
30682
- style: styles$w.copyPasteButton
31156
+ style: styles$r.copyPasteButton
30683
31157
  }), /*#__PURE__*/React__namespace.createElement(IconButton__default["default"], {
30684
31158
  icon: copyIcon__default["default"],
30685
31159
  "aria-label": "copy example",
30686
31160
  "aria-describedby": exampleId,
30687
31161
  onClick: () => navigator.clipboard.writeText(example),
30688
31162
  size: "medium",
30689
- style: styles$w.copyPasteButton
31163
+ style: styles$r.copyPasteButton
30690
31164
  }), /*#__PURE__*/React__namespace.createElement(Strut, {
30691
31165
  size: wonderBlocksTokens.spacing.xxxSmall_4
30692
31166
  }), /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
30693
- style: styles$w.exampleContent,
31167
+ style: styles$r.exampleContent,
30694
31168
  id: exampleId
30695
31169
  }, example));
30696
31170
  };
30697
- const styles$w = aphrodite.StyleSheet.create({
31171
+ const styles$r = aphrodite.StyleSheet.create({
30698
31172
  accordionHeader: {
30699
31173
  textOverflow: "ellipsis",
30700
31174
  // A maximum width needs to be specified in order for the ellipsis to work.
@@ -30812,14 +31286,14 @@ function LockedLabelSettings(props) {
30812
31286
  expanded: expanded,
30813
31287
  onToggle: onToggle,
30814
31288
  header: /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
30815
- style: [styles$v.row, styles$v.accordionHeaderContainer]
31289
+ style: [styles$q.row, styles$q.accordionHeaderContainer]
30816
31290
  }, /*#__PURE__*/React__namespace.createElement(wonderBlocksTypography.LabelLarge, null, "Label (", coord[0], ", ", coord[1], ")"), /*#__PURE__*/React__namespace.createElement(Strut, {
30817
31291
  size: wonderBlocksTokens.spacing.xSmall_8
30818
31292
  }), text !== "" && /*#__PURE__*/React__namespace.createElement(wonderBlocksTypography.LabelLarge, {
30819
31293
  style: [{
30820
31294
  backgroundColor: wonderBlocksTokens.color.white,
30821
31295
  color: perseus.lockedFigureColors[color]
30822
- }, styles$v.accordionHeader]
31296
+ }, styles$q.accordionHeader]
30823
31297
  }, text))
30824
31298
  }, /*#__PURE__*/React__namespace.createElement(CoordinatePairInput, {
30825
31299
  coord: coord,
@@ -30828,10 +31302,10 @@ function LockedLabelSettings(props) {
30828
31302
  coord: newCoords
30829
31303
  });
30830
31304
  },
30831
- style: styles$v.spaceUnder
31305
+ style: styles$q.spaceUnder
30832
31306
  }), /*#__PURE__*/React__namespace.createElement(wonderBlocksTypography.LabelMedium, {
30833
31307
  tag: "label",
30834
- style: [styles$v.row, styles$v.spaceUnder]
31308
+ style: [styles$q.row, styles$q.spaceUnder]
30835
31309
  }, "TeX", /*#__PURE__*/React__namespace.createElement(Strut, {
30836
31310
  size: wonderBlocksTokens.spacing.xSmall_8
30837
31311
  }), /*#__PURE__*/React__namespace.createElement(wonderBlocksForm.TextField, {
@@ -30841,7 +31315,7 @@ function LockedLabelSettings(props) {
30841
31315
  text: newValue
30842
31316
  })
30843
31317
  })), /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
30844
- style: styles$v.row
31318
+ style: styles$q.row
30845
31319
  }, /*#__PURE__*/React__namespace.createElement(ColorSelect, {
30846
31320
  selectedValue: color,
30847
31321
  onChange: newColor => {
@@ -30849,12 +31323,12 @@ function LockedLabelSettings(props) {
30849
31323
  color: newColor
30850
31324
  });
30851
31325
  },
30852
- style: styles$v.spaceUnder
31326
+ style: styles$q.spaceUnder
30853
31327
  }), /*#__PURE__*/React__namespace.createElement(Strut, {
30854
31328
  size: wonderBlocksTokens.spacing.medium_16
30855
31329
  }), /*#__PURE__*/React__namespace.createElement(wonderBlocksTypography.LabelMedium, {
30856
31330
  tag: "label",
30857
- style: styles$v.row
31331
+ style: styles$q.row
30858
31332
  }, "size", /*#__PURE__*/React__namespace.createElement(Strut, {
30859
31333
  size: wonderBlocksTokens.spacing.xSmall_8
30860
31334
  }), /*#__PURE__*/React__namespace.createElement(wonderBlocksDropdown.SingleSelect, {
@@ -30881,7 +31355,7 @@ function LockedLabelSettings(props) {
30881
31355
  onRemove: onRemove
30882
31356
  }));
30883
31357
  }
30884
- const styles$v = aphrodite.StyleSheet.create({
31358
+ const styles$q = aphrodite.StyleSheet.create({
30885
31359
  accordionHeaderContainer: {
30886
31360
  // Stop the label summary from wrapping.
30887
31361
  whiteSpace: "nowrap"
@@ -30915,7 +31389,7 @@ const LabeledSwitch = props => {
30915
31389
  const ids = wonderBlocksCore.useUniqueIdWithMock();
30916
31390
  const switchId = ids.get("switch");
30917
31391
  return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
30918
- style: [styles$u.row, style]
31392
+ style: [styles$p.row, style]
30919
31393
  }, /*#__PURE__*/React__namespace.createElement(Switch__default["default"], {
30920
31394
  id: switchId,
30921
31395
  checked: checked,
@@ -30927,7 +31401,7 @@ const LabeledSwitch = props => {
30927
31401
  htmlFor: switchId
30928
31402
  }, label));
30929
31403
  };
30930
- const styles$u = aphrodite.StyleSheet.create({
31404
+ const styles$p = aphrodite.StyleSheet.create({
30931
31405
  row: {
30932
31406
  flexDirection: "row",
30933
31407
  alignItems: "center"
@@ -30961,13 +31435,13 @@ const DefiningPointSettings = props => {
30961
31435
  return /*#__PURE__*/React__namespace.createElement(PerseusEditorAccordion, {
30962
31436
  expanded: expanded,
30963
31437
  onToggle: onToggle,
30964
- containerStyle: styles$t.container,
30965
- panelStyle: styles$t.accordionPanel,
31438
+ containerStyle: styles$o.container,
31439
+ panelStyle: styles$o.accordionPanel,
30966
31440
  header:
30967
31441
  /*#__PURE__*/
30968
31442
  // Summary: Point, coords, color (filled/open)
30969
31443
  React__namespace.createElement(wonderBlocksCore.View, {
30970
- style: styles$t.row
31444
+ style: styles$o.row
30971
31445
  }, /*#__PURE__*/React__namespace.createElement(wonderBlocksTypography.LabelLarge, null, `${label || "Point"} (${coord[0]}, ${coord[1]})`), /*#__PURE__*/React__namespace.createElement(Strut, {
30972
31446
  size: wonderBlocksTokens.spacing.xSmall_8
30973
31447
  }), showPoint && /*#__PURE__*/React__namespace.createElement(ColorSwatch, {
@@ -30977,7 +31451,7 @@ const DefiningPointSettings = props => {
30977
31451
  }, /*#__PURE__*/React__namespace.createElement(CoordinatePairInput, {
30978
31452
  coord: coord,
30979
31453
  error: !!error,
30980
- style: styles$t.spaceUnder,
31454
+ style: styles$o.spaceUnder,
30981
31455
  onChange: newCoords => {
30982
31456
  onChangeProps({
30983
31457
  coord: newCoords
@@ -30986,12 +31460,12 @@ const DefiningPointSettings = props => {
30986
31460
  }), onTogglePoint && /*#__PURE__*/React__namespace.createElement(LabeledSwitch, {
30987
31461
  label: "show point on graph",
30988
31462
  checked: !!showPoint,
30989
- style: showPoint && styles$t.spaceUnder,
31463
+ style: showPoint && styles$o.spaceUnder,
30990
31464
  onChange: onTogglePoint
30991
31465
  }), showPoint && /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(ColorSelect, {
30992
31466
  selectedValue: pointColor,
30993
31467
  onChange: handleColorChange,
30994
- style: styles$t.spaceUnder
31468
+ style: styles$o.spaceUnder
30995
31469
  }), /*#__PURE__*/React__namespace.createElement(LabeledSwitch, {
30996
31470
  label: "open point",
30997
31471
  checked: !filled,
@@ -31002,7 +31476,7 @@ const DefiningPointSettings = props => {
31002
31476
  }
31003
31477
  })));
31004
31478
  };
31005
- const styles$t = aphrodite.StyleSheet.create({
31479
+ const styles$o = aphrodite.StyleSheet.create({
31006
31480
  container: {
31007
31481
  marginTop: wonderBlocksTokens.spacing.xSmall_8,
31008
31482
  marginBottom: 0,
@@ -31070,7 +31544,7 @@ const LockedLineSettings = props => {
31070
31544
  expanded: props.expanded,
31071
31545
  onToggle: props.onToggle,
31072
31546
  header: /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
31073
- style: styles$s.row
31547
+ style: styles$n.row
31074
31548
  }, /*#__PURE__*/React__namespace.createElement(wonderBlocksTypography.LabelLarge, null, lineLabel), /*#__PURE__*/React__namespace.createElement(Strut, {
31075
31549
  size: wonderBlocksTokens.spacing.xSmall_8
31076
31550
  }), /*#__PURE__*/React__namespace.createElement(LineSwatch, {
@@ -31079,7 +31553,7 @@ const LockedLineSettings = props => {
31079
31553
  }))
31080
31554
  }, /*#__PURE__*/React__namespace.createElement(wonderBlocksTypography.LabelMedium, {
31081
31555
  tag: "label",
31082
- style: [styles$s.row, styles$s.spaceUnder]
31556
+ style: [styles$n.row, styles$n.spaceUnder]
31083
31557
  }, "kind", /*#__PURE__*/React__namespace.createElement(Strut, {
31084
31558
  size: wonderBlocksTokens.spacing.xxxSmall_4
31085
31559
  }), /*#__PURE__*/React__namespace.createElement(wonderBlocksDropdown.SingleSelect, {
@@ -31100,7 +31574,7 @@ const LockedLineSettings = props => {
31100
31574
  value: "segment",
31101
31575
  label: "segment"
31102
31576
  }))), /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
31103
- style: styles$s.row
31577
+ style: styles$n.row
31104
31578
  }, /*#__PURE__*/React__namespace.createElement(ColorSelect, {
31105
31579
  selectedValue: lineColor,
31106
31580
  onChange: handleColorChange
@@ -31112,7 +31586,7 @@ const LockedLineSettings = props => {
31112
31586
  lineStyle: value
31113
31587
  })
31114
31588
  })), isInvalid && /*#__PURE__*/React__namespace.createElement(wonderBlocksTypography.LabelMedium, {
31115
- style: styles$s.errorText
31589
+ style: styles$n.errorText
31116
31590
  }, lengthZeroStr), /*#__PURE__*/React__namespace.createElement(DefiningPointSettings, _extends({
31117
31591
  label: "Point 1",
31118
31592
  showPoint: showPoint1,
@@ -31137,7 +31611,7 @@ const LockedLineSettings = props => {
31137
31611
  onRemove: onRemove
31138
31612
  }));
31139
31613
  };
31140
- const styles$s = aphrodite.StyleSheet.create({
31614
+ const styles$n = aphrodite.StyleSheet.create({
31141
31615
  row: {
31142
31616
  display: "flex",
31143
31617
  flexDirection: "row",
@@ -31178,7 +31652,7 @@ const LockedPointSettings = props => {
31178
31652
  /*#__PURE__*/
31179
31653
  // Summary: Point, coords, color (filled/open)
31180
31654
  React__namespace.createElement(wonderBlocksCore.View, {
31181
- style: styles$r.row
31655
+ style: styles$m.row
31182
31656
  }, /*#__PURE__*/React__namespace.createElement(wonderBlocksTypography.LabelLarge, null, `Point (${coord[0]}, ${coord[1]})`), /*#__PURE__*/React__namespace.createElement(Strut, {
31183
31657
  size: wonderBlocksTokens.spacing.xSmall_8
31184
31658
  }), /*#__PURE__*/React__namespace.createElement(ColorSwatch, {
@@ -31187,7 +31661,7 @@ const LockedPointSettings = props => {
31187
31661
  }))
31188
31662
  }, /*#__PURE__*/React__namespace.createElement(CoordinatePairInput, {
31189
31663
  coord: coord,
31190
- style: styles$r.spaceUnder,
31664
+ style: styles$m.spaceUnder,
31191
31665
  onChange: newCoord => {
31192
31666
  onChangeProps({
31193
31667
  coord: newCoord
@@ -31196,7 +31670,7 @@ const LockedPointSettings = props => {
31196
31670
  }), /*#__PURE__*/React__namespace.createElement(ColorSelect, {
31197
31671
  selectedValue: pointColor,
31198
31672
  onChange: handleColorChange,
31199
- style: styles$r.spaceUnder
31673
+ style: styles$m.spaceUnder
31200
31674
  }), /*#__PURE__*/React__namespace.createElement(LabeledSwitch, {
31201
31675
  label: "open point",
31202
31676
  checked: !filled,
@@ -31211,7 +31685,7 @@ const LockedPointSettings = props => {
31211
31685
  onRemove: onRemove
31212
31686
  }));
31213
31687
  };
31214
- const styles$r = aphrodite.StyleSheet.create({
31688
+ const styles$m = aphrodite.StyleSheet.create({
31215
31689
  row: {
31216
31690
  flexDirection: "row",
31217
31691
  alignItems: "center"
@@ -31229,17 +31703,17 @@ const PolygonSwatch = props => {
31229
31703
  } = props;
31230
31704
  return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
31231
31705
  "aria-label": `${color}, stroke ${strokeStyle}, fill ${fillStyle}`,
31232
- style: [styles$q.container, {
31706
+ style: [styles$l.container, {
31233
31707
  border: `4px ${strokeStyle} ${perseus.lockedFigureColors[color]}`
31234
31708
  }]
31235
31709
  }, /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
31236
- style: [styles$q.innerSquare, {
31710
+ style: [styles$l.innerSquare, {
31237
31711
  backgroundColor: perseus.lockedFigureColors[color],
31238
31712
  opacity: fillStyle === "white" ? 0 : perseus.lockedFigureFillStyles[fillStyle]
31239
31713
  }]
31240
31714
  }));
31241
31715
  };
31242
- const styles$q = aphrodite.StyleSheet.create({
31716
+ const styles$l = aphrodite.StyleSheet.create({
31243
31717
  container: {
31244
31718
  // Add a white outline so that the color swatch is visible when
31245
31719
  // the dropdown option is highlighted with its blue background.
@@ -31317,7 +31791,7 @@ const LockedPolygonSettings = props => {
31317
31791
  /*#__PURE__*/
31318
31792
  // Summary: Polygon, number of sides, style swatch
31319
31793
  React__namespace.createElement(wonderBlocksCore.View, {
31320
- style: styles$p.row
31794
+ style: styles$k.row
31321
31795
  }, /*#__PURE__*/React__namespace.createElement(wonderBlocksTypography.LabelLarge, null, `Polygon, ${points.length} sides`), /*#__PURE__*/React__namespace.createElement(Strut, {
31322
31796
  size: wonderBlocksTokens.spacing.xSmall_8
31323
31797
  }), /*#__PURE__*/React__namespace.createElement(PolygonSwatch, {
@@ -31326,7 +31800,7 @@ const LockedPolygonSettings = props => {
31326
31800
  strokeStyle: strokeStyle
31327
31801
  }))
31328
31802
  }, /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
31329
- style: [styles$p.row, styles$p.spaceUnder]
31803
+ style: [styles$k.row, styles$k.spaceUnder]
31330
31804
  }, /*#__PURE__*/React__namespace.createElement(ColorSelect, {
31331
31805
  selectedValue: color,
31332
31806
  onChange: handleColorChange
@@ -31334,7 +31808,7 @@ const LockedPolygonSettings = props => {
31334
31808
  size: wonderBlocksTokens.spacing.medium_16
31335
31809
  }), /*#__PURE__*/React__namespace.createElement(wonderBlocksTypography.LabelMedium, {
31336
31810
  tag: "label",
31337
- style: [styles$p.row, styles$p.truncatedWidth]
31811
+ style: [styles$k.row, styles$k.truncatedWidth]
31338
31812
  }, "fill", /*#__PURE__*/React__namespace.createElement(Strut, {
31339
31813
  size: wonderBlocksTokens.spacing.xxSmall_6
31340
31814
  }), /*#__PURE__*/React__namespace.createElement(wonderBlocksDropdown.SingleSelect, {
@@ -31360,17 +31834,17 @@ const LockedPolygonSettings = props => {
31360
31834
  onChange: newValue => onChangeProps({
31361
31835
  showVertices: newValue
31362
31836
  }),
31363
- style: styles$p.spaceUnder
31837
+ style: styles$k.spaceUnder
31364
31838
  }), /*#__PURE__*/React__namespace.createElement(PerseusEditorAccordion, {
31365
31839
  header: /*#__PURE__*/React__namespace.createElement(wonderBlocksTypography.LabelLarge, null, "Points"),
31366
31840
  expanded: true,
31367
- containerStyle: styles$p.pointAccordionContainer,
31368
- panelStyle: styles$p.pointAccordionPanel
31841
+ containerStyle: styles$k.pointAccordionContainer,
31842
+ panelStyle: styles$k.pointAccordionPanel
31369
31843
  }, points.map((point, index) => {
31370
31844
  const pointLabel = String.fromCharCode(65 + index);
31371
31845
  return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
31372
31846
  key: `locked-polygon-point-index-${index}`,
31373
- style: [styles$p.row, styles$p.spaceUnder]
31847
+ style: [styles$k.row, styles$k.spaceUnder]
31374
31848
  }, /*#__PURE__*/React__namespace.createElement(wonderBlocksTypography.LabelLarge, null, `${pointLabel}:`), /*#__PURE__*/React__namespace.createElement(Strut, {
31375
31849
  size: wonderBlocksTokens.spacing.medium_16
31376
31850
  }), /*#__PURE__*/React__namespace.createElement(CoordinatePairInput, {
@@ -31398,10 +31872,10 @@ const LockedPolygonSettings = props => {
31398
31872
  points: newPoints
31399
31873
  });
31400
31874
  },
31401
- style: styles$p.icon
31875
+ style: styles$k.icon
31402
31876
  }));
31403
31877
  }), /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
31404
- style: [styles$p.row, styles$p.polygonActionsContainer]
31878
+ style: [styles$k.row, styles$k.polygonActionsContainer]
31405
31879
  }, /*#__PURE__*/React__namespace.createElement(Button__default["default"], {
31406
31880
  kind: "tertiary",
31407
31881
  startIcon: plusCircle__default["default"],
@@ -31411,1006 +31885,555 @@ const LockedPolygonSettings = props => {
31411
31885
  });
31412
31886
  }
31413
31887
  }, "Add point"), /*#__PURE__*/React__namespace.createElement(Spring, null), /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
31414
- style: styles$p.movementButtonsContainer
31888
+ style: styles$k.movementButtonsContainer
31415
31889
  }, /*#__PURE__*/React__namespace.createElement(IconButton__default["default"], {
31416
31890
  "aria-label": "Move polygon up",
31417
- style: styles$p.iconButton,
31891
+ style: styles$k.iconButton,
31418
31892
  size: "small",
31419
31893
  icon: arrowFatUp__default["default"],
31420
31894
  onClick: () => handlePolygonMove("up")
31421
31895
  }), /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
31422
- style: styles$p.row
31896
+ style: styles$k.row
31423
31897
  }, /*#__PURE__*/React__namespace.createElement(IconButton__default["default"], {
31424
31898
  "aria-label": "Move polygon left",
31425
- style: styles$p.iconButton,
31426
- size: "small",
31427
- icon: arrowFatLeft__default["default"],
31428
- onClick: () => handlePolygonMove("left")
31429
- }), /*#__PURE__*/React__namespace.createElement(IconButton__default["default"], {
31430
- "aria-label": "Move polygon down",
31431
- style: styles$p.iconButton,
31899
+ style: styles$k.iconButton,
31432
31900
  size: "small",
31433
- icon: arrowFatDown__default["default"],
31434
- onClick: () => handlePolygonMove("down")
31435
- }), /*#__PURE__*/React__namespace.createElement(IconButton__default["default"], {
31436
- "aria-label": "Move polygon right",
31437
- style: styles$p.iconButton,
31438
- size: "small",
31439
- icon: arrowFatRight__default["default"],
31440
- onClick: () => handlePolygonMove("right")
31441
- }))))), /*#__PURE__*/React__namespace.createElement(LockedFigureSettingsActions, {
31442
- figureType: props.type,
31443
- onMove: onMove,
31444
- onRemove: onRemove
31445
- }));
31446
- };
31447
- const styles$p = aphrodite.StyleSheet.create({
31448
- row: {
31449
- display: "flex",
31450
- flexDirection: "row",
31451
- alignItems: "center"
31452
- },
31453
- pointAccordionContainer: {
31454
- backgroundColor: wonderBlocksTokens.color.white
31455
- },
31456
- pointAccordionPanel: {
31457
- alignItems: "start"
31458
- },
31459
- icon: {
31460
- marginInlineStart: wonderBlocksTokens.spacing.xxxSmall_4
31461
- },
31462
- polygonActionsContainer: {
31463
- width: "100%"
31464
- },
31465
- iconButton: {
31466
- margin: 0
31467
- },
31468
- movementButtonsContainer: {
31469
- display: "flex",
31470
- flexDirection: "column",
31471
- alignItems: "center",
31472
- minWidth: "fit-content"
31473
- },
31474
- spaceUnder: {
31475
- marginBottom: wonderBlocksTokens.spacing.xSmall_8
31476
- },
31477
- truncatedWidth: {
31478
- // Allow truncation, stop bleeding over the edge.
31479
- minWidth: 0
31480
- }
31481
- });
31482
-
31483
- /**
31484
- * LockedVectorSettings is a component that allows the user to edit the
31485
- * settings of specifically a locked vector on the graph.
31486
- *
31487
- * Used in the interactive graph editor's locked figures section.
31488
- */
31489
- const lengthErrorMessage = "The vector cannot have length 0.";
31490
- const LockedVectorSettings = props => {
31491
- const {
31492
- points,
31493
- color: lineColor,
31494
- onChangeProps,
31495
- onMove,
31496
- onRemove
31497
- } = props;
31498
- const [tail, tip] = points;
31499
- const lineLabel = `Vector (${tail[0]}, ${tail[1]}), (${tip[0]}, ${tip[1]})`;
31500
-
31501
- // Check if the line has length 0.
31502
- const isInvalid = kmath.vector.equal(tail, tip);
31503
- function handleChangePoint(newCoord, index) {
31504
- if (typeof newCoord !== "undefined") {
31505
- const newPoints = [...points];
31506
- newPoints[index] = [...newCoord];
31507
- onChangeProps({
31508
- points: newPoints
31509
- });
31510
- }
31511
- }
31512
- function handleColorChange(newColor) {
31513
- onChangeProps({
31514
- color: newColor
31515
- });
31516
- }
31517
- return /*#__PURE__*/React__namespace.createElement(PerseusEditorAccordion, {
31518
- expanded: props.expanded,
31519
- onToggle: props.onToggle,
31520
- header: /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
31521
- style: styles$o.row
31522
- }, /*#__PURE__*/React__namespace.createElement(wonderBlocksTypography.LabelLarge, null, lineLabel), /*#__PURE__*/React__namespace.createElement(Strut, {
31523
- size: wonderBlocksTokens.spacing.xSmall_8
31524
- }), /*#__PURE__*/React__namespace.createElement(LineSwatch, {
31525
- color: lineColor,
31526
- lineStyle: "solid"
31527
- }))
31528
- }, /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
31529
- style: [styles$o.row, styles$o.spaceUnder]
31530
- }, /*#__PURE__*/React__namespace.createElement(ColorSelect, {
31531
- selectedValue: lineColor,
31532
- onChange: handleColorChange
31533
- })), isInvalid && /*#__PURE__*/React__namespace.createElement(wonderBlocksTypography.LabelMedium, {
31534
- style: styles$o.errorText
31535
- }, lengthErrorMessage), /*#__PURE__*/React__namespace.createElement(PerseusEditorAccordion, {
31536
- expanded: true // Initial state is expanded
31537
- ,
31538
- containerStyle: styles$o.container,
31539
- panelStyle: styles$o.accordionPanel,
31540
- header: /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
31541
- style: styles$o.row
31542
- }, /*#__PURE__*/React__namespace.createElement(wonderBlocksTypography.LabelLarge, null, `Tail (${tail[0]}, ${tail[1]})`))
31543
- }, /*#__PURE__*/React__namespace.createElement(CoordinatePairInput, {
31544
- coord: tail,
31545
- error: isInvalid,
31546
- onChange: newProps => {
31547
- handleChangePoint(newProps, 0);
31548
- }
31549
- })), /*#__PURE__*/React__namespace.createElement(PerseusEditorAccordion, {
31550
- expanded: true // Initial state is expanded
31551
- ,
31552
- containerStyle: styles$o.container,
31553
- panelStyle: styles$o.accordionPanel,
31554
- header: /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
31555
- style: styles$o.row
31556
- }, /*#__PURE__*/React__namespace.createElement(wonderBlocksTypography.LabelLarge, null, `Tip (${tip[0]}, ${tip[1]})`))
31557
- }, /*#__PURE__*/React__namespace.createElement(CoordinatePairInput, {
31558
- coord: tip,
31559
- error: isInvalid,
31560
- onChange: newProps => {
31561
- handleChangePoint(newProps, 1);
31562
- }
31563
- })), /*#__PURE__*/React__namespace.createElement(LockedFigureSettingsActions, {
31564
- figureType: props.type,
31565
- onMove: onMove,
31566
- onRemove: onRemove
31567
- }));
31568
- };
31569
- const styles$o = aphrodite.StyleSheet.create({
31570
- accordionPanel: {
31571
- paddingBottom: wonderBlocksTokens.spacing.medium_16
31572
- },
31573
- container: {
31574
- marginTop: wonderBlocksTokens.spacing.xSmall_8,
31575
- marginBottom: 0,
31576
- marginLeft: -wonderBlocksTokens.spacing.xxxSmall_4,
31577
- marginRight: -wonderBlocksTokens.spacing.xxxSmall_4,
31578
- backgroundColor: wonderBlocksTokens.color.white
31579
- },
31580
- errorText: {
31581
- color: wonderBlocksTokens.color.red,
31582
- marginTop: wonderBlocksTokens.spacing.xSmall_8
31583
- },
31584
- row: {
31585
- flexDirection: "row",
31586
- alignItems: "center"
31587
- }
31588
- });
31589
-
31590
- /**
31591
- * LockedFigureSettings takes in a figure and renders the
31592
- * respective settings for that figure type.
31593
- *
31594
- * Used in the interactive graph editor's locked figures section.
31595
- */
31596
-
31597
- // Union this type with other locked figure types when they are added.
31598
-
31599
- const LockedFigureSettings = props => {
31600
- switch (props.type) {
31601
- case "point":
31602
- return /*#__PURE__*/React__namespace.createElement(LockedPointSettings, props);
31603
- case "line":
31604
- return /*#__PURE__*/React__namespace.createElement(LockedLineSettings, props);
31605
- case "vector":
31606
- return /*#__PURE__*/React__namespace.createElement(LockedVectorSettings, props);
31607
- case "ellipse":
31608
- return /*#__PURE__*/React__namespace.createElement(LockedEllipseSettings, props);
31609
- case "polygon":
31610
- return /*#__PURE__*/React__namespace.createElement(LockedPolygonSettings, props);
31611
- case "function":
31612
- return /*#__PURE__*/React__namespace.createElement(LockedFunctionSettings, props);
31613
- case "label":
31614
- if (props.showLabelsFlag) {
31615
- return /*#__PURE__*/React__namespace.createElement(LockedLabelSettings, props);
31616
- }
31617
- break;
31618
- }
31619
- return null;
31620
- };
31621
-
31622
- const LockedFiguresSection = props => {
31623
- // Keep track of all figures' accordions' expanded states for the
31624
- // expand/collapse all button. Set the whole array to false initially.
31625
- const collapsedStateArray = Array((props.figures ?? []).length).fill(false);
31626
- const [expandedStates, setExpandedStates] = React__namespace.useState(collapsedStateArray);
31627
- const [isExpanded, setIsExpanded] = React__namespace.useState(true);
31628
- const uniqueId = wonderBlocksCore.useUniqueIdWithMock().get("locked-figures-section");
31629
- const {
31630
- figures,
31631
- onChange
31632
- } = props;
31633
- function addLockedFigure(newFigure) {
31634
- const lockedFigures = figures || [];
31635
- const newProps = {
31636
- lockedFigures: [...lockedFigures, getDefaultFigureForType(newFigure)]
31637
- };
31638
- onChange(newProps);
31639
- setExpandedStates([...expandedStates, true]);
31640
- }
31641
- function moveLockedFigure(index, movement) {
31642
- // Don't allow moving the first figure up or the last figure down.
31643
- if (index === 0 && (movement === "back" || movement === "backward")) {
31644
- return;
31645
- }
31646
- if (figures && index === figures.length - 1 && (movement === "front" || movement === "forward")) {
31647
- return;
31648
- }
31649
- const lockedFigures = figures || [];
31650
- const newFigures = [...lockedFigures];
31651
- const newExpandedStates = [...expandedStates];
31652
-
31653
- // First, remove the figure from its current position
31654
- // in the figures array and the expanded states array.
31655
- const [removedFigure] = newFigures.splice(index, 1);
31656
- newExpandedStates.splice(index, 1);
31657
-
31658
- // Then, add it back in the new position. Add "true" to the
31659
- // expanded states array for the new position (it must already
31660
- // be open since the button to move it is being pressed from there).
31661
- switch (movement) {
31662
- case "back":
31663
- newFigures.unshift(removedFigure);
31664
- newExpandedStates.unshift(true);
31665
- break;
31666
- case "backward":
31667
- newFigures.splice(index - 1, 0, removedFigure);
31668
- newExpandedStates.splice(index - 1, 0, true);
31669
- break;
31670
- case "forward":
31671
- newFigures.splice(index + 1, 0, removedFigure);
31672
- newExpandedStates.splice(index + 1, 0, true);
31673
- break;
31674
- case "front":
31675
- newFigures.push(removedFigure);
31676
- newExpandedStates.push(true);
31677
- break;
31678
- }
31679
- onChange({
31680
- lockedFigures: newFigures
31681
- });
31682
- setExpandedStates(newExpandedStates);
31683
- }
31684
- function removeLockedFigure(index) {
31685
- // eslint-disable-next-line no-alert
31686
- if (window.confirm("Are you sure you want to delete this figure?")) {
31687
- const lockedFigures = figures || [];
31688
- onChange({
31689
- lockedFigures: [...lockedFigures.slice(0, index), ...lockedFigures.slice(index + 1)]
31690
- });
31691
-
31692
- // Update expanded states
31693
- const newExpandedStates = [...expandedStates];
31694
- newExpandedStates.splice(index, 1);
31695
- setExpandedStates(newExpandedStates);
31696
- }
31697
- }
31698
- function changeProps(index,
31699
- // Omit the type from the figure props so it doesn't think
31700
- // we're trying to pass in the props for the wrong type.
31701
- figureProps) {
31702
- const lockedFigures = figures || [];
31703
- const newFigures = {
31704
- lockedFigures: [...lockedFigures.slice(0, index), {
31705
- ...lockedFigures[index],
31706
- ...figureProps
31707
- }, ...lockedFigures.slice(index + 1)]
31708
- };
31709
- onChange(newFigures);
31710
- }
31711
- function toggleExpanded(newValue) {
31712
- setExpandedStates(Array(figures?.length).fill(newValue));
31713
- }
31714
- const allCollapsed = expandedStates.every(value => !value);
31715
- const buttonLabel = allCollapsed ? "Expand all" : "Collapse all";
31716
- const showExpandButton = !!figures?.length;
31717
- return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(Heading, {
31718
- title: "Locked Figures",
31719
- isOpen: isExpanded,
31720
- onToggle: () => setIsExpanded(!isExpanded),
31721
- isCollapsible: true
31722
- }), isExpanded && /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, null, figures?.map((figure, index) => {
31723
- return /*#__PURE__*/React__namespace.createElement(LockedFigureSettings, _extends({
31724
- key: `${uniqueId}-locked-${figure}-${index}`,
31725
- showLabelsFlag: props.showLabelsFlag,
31726
- expanded: expandedStates[index],
31727
- onToggle: newValue => {
31728
- const newExpanded = [...expandedStates];
31729
- newExpanded[index] = newValue;
31730
- setExpandedStates(newExpanded);
31731
- }
31732
- }, figure, {
31733
- onChangeProps: newProps => changeProps(index, newProps),
31734
- onMove: movement => moveLockedFigure(index, movement),
31735
- onRemove: () => removeLockedFigure(index)
31736
- }));
31737
- }), /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
31738
- style: styles$n.buttonContainer
31739
- }, /*#__PURE__*/React__namespace.createElement(LockedFigureSelect, {
31740
- showLabelsFlag: props.showLabelsFlag,
31741
- id: `${uniqueId}-select`,
31742
- onChange: addLockedFigure
31743
- }), /*#__PURE__*/React__namespace.createElement(Strut, {
31744
- size: wonderBlocksTokens.spacing.small_12
31745
- }), showExpandButton && /*#__PURE__*/React__namespace.createElement(Button__default["default"], {
31746
- kind: "secondary",
31747
- onClick: () => toggleExpanded(allCollapsed),
31748
- style: styles$n.button
31749
- }, buttonLabel))));
31750
- };
31751
- const styles$n = aphrodite.StyleSheet.create({
31752
- buttonContainer: {
31753
- flexDirection: "row",
31754
- alignItems: "center"
31755
- },
31756
- button: {
31757
- marginTop: wonderBlocksTokens.spacing.xSmall_8,
31758
- flexGrow: 1
31759
- }
31760
- });
31761
-
31762
- const UNLIMITED = "unlimited";
31763
- // Parses a string (ostensibly from some UI element that only supports string
31764
- // types, such as a <select />) into a numeric point count or the special sigil
31765
- // "unlimited". Note that 0 is a special case, and means "unlimited".
31766
- const parsePointCount = points => {
31767
- const parsed = parseInt(points, 10);
31768
- if (isNaN(parsed)) {
31769
- return UNLIMITED;
31770
- }
31771
- return parsed === 0 ? UNLIMITED : parsed;
31772
- };
31773
-
31774
- const GraphPointsCountSelector = _ref => {
31775
- let {
31776
- numPoints = 1,
31777
- onChange
31778
- } = _ref;
31779
- return /*#__PURE__*/React__namespace.createElement(wonderBlocksDropdown.SingleSelect, {
31780
- selectedValue: `${numPoints}`,
31781
- onChange: newValue => {
31782
- onChange(parsePointCount(newValue));
31783
- }
31784
- // Never uses placeholder, always has value
31785
- ,
31786
- placeholder: "",
31787
- style: styles$m.singleSelectShort
31788
- }, [...[...Array(7).keys()].map(n => /*#__PURE__*/React__namespace.createElement(wonderBlocksDropdown.OptionItem, {
31789
- key: n,
31790
- value: `${n}`,
31791
- label: `${n} point${n > 1 ? "s" : ""}`
31792
- })), /*#__PURE__*/React__namespace.createElement(wonderBlocksDropdown.OptionItem, {
31793
- key: "unlimited",
31794
- value: UNLIMITED,
31795
- label: "unlimited"
31796
- })]);
31797
- };
31798
- const styles$m = aphrodite.StyleSheet.create({
31799
- singleSelectShort: {
31800
- // Non-standard spacing, but it's the smallest we can go
31801
- // without running into styling issues with the dropdown.
31802
- height: 26
31803
- }
31804
- });
31805
-
31806
- const GraphTypeSelector = props => {
31807
- return /*#__PURE__*/React__namespace.createElement(wonderBlocksDropdown.SingleSelect, {
31808
- selectedValue: props.graphType,
31809
- onChange: props.onChange,
31810
- placeholder: "Select a graph type",
31811
- style: styles$l.singleSelectShort
31812
- }, /*#__PURE__*/React__namespace.createElement(wonderBlocksDropdown.OptionItem, {
31813
- value: "linear",
31814
- label: "Linear function"
31815
- }), /*#__PURE__*/React__namespace.createElement(wonderBlocksDropdown.OptionItem, {
31816
- value: "quadratic",
31817
- label: "Quadratic function"
31818
- }), /*#__PURE__*/React__namespace.createElement(wonderBlocksDropdown.OptionItem, {
31819
- value: "sinusoid",
31820
- label: "Sinusoid function"
31821
- }), /*#__PURE__*/React__namespace.createElement(wonderBlocksDropdown.OptionItem, {
31822
- value: "circle",
31823
- label: "Circle"
31824
- }), /*#__PURE__*/React__namespace.createElement(wonderBlocksDropdown.OptionItem, {
31825
- value: "point",
31826
- label: "Point(s)"
31827
- }), /*#__PURE__*/React__namespace.createElement(wonderBlocksDropdown.OptionItem, {
31828
- value: "linear-system",
31829
- label: "Linear System"
31830
- }), /*#__PURE__*/React__namespace.createElement(wonderBlocksDropdown.OptionItem, {
31831
- value: "polygon",
31832
- label: "Polygon"
31833
- }), /*#__PURE__*/React__namespace.createElement(wonderBlocksDropdown.OptionItem, {
31834
- value: "segment",
31835
- label: "Line Segment(s)"
31836
- }), /*#__PURE__*/React__namespace.createElement(wonderBlocksDropdown.OptionItem, {
31837
- value: "ray",
31838
- label: "Ray"
31839
- }), /*#__PURE__*/React__namespace.createElement(wonderBlocksDropdown.OptionItem, {
31840
- value: "angle",
31841
- label: "Angle"
31901
+ icon: arrowFatLeft__default["default"],
31902
+ onClick: () => handlePolygonMove("left")
31903
+ }), /*#__PURE__*/React__namespace.createElement(IconButton__default["default"], {
31904
+ "aria-label": "Move polygon down",
31905
+ style: styles$k.iconButton,
31906
+ size: "small",
31907
+ icon: arrowFatDown__default["default"],
31908
+ onClick: () => handlePolygonMove("down")
31909
+ }), /*#__PURE__*/React__namespace.createElement(IconButton__default["default"], {
31910
+ "aria-label": "Move polygon right",
31911
+ style: styles$k.iconButton,
31912
+ size: "small",
31913
+ icon: arrowFatRight__default["default"],
31914
+ onClick: () => handlePolygonMove("right")
31915
+ }))))), /*#__PURE__*/React__namespace.createElement(LockedFigureSettingsActions, {
31916
+ figureType: props.type,
31917
+ onMove: onMove,
31918
+ onRemove: onRemove
31842
31919
  }));
31843
31920
  };
31844
- const styles$l = aphrodite.StyleSheet.create({
31845
- singleSelectShort: {
31846
- // Non-standard spacing, but it's the smallest we can go
31847
- // without running into styling issues with the dropdown.
31848
- height: 26
31921
+ const styles$k = aphrodite.StyleSheet.create({
31922
+ row: {
31923
+ display: "flex",
31924
+ flexDirection: "row",
31925
+ alignItems: "center"
31926
+ },
31927
+ pointAccordionContainer: {
31928
+ backgroundColor: wonderBlocksTokens.color.white
31929
+ },
31930
+ pointAccordionPanel: {
31931
+ alignItems: "start"
31932
+ },
31933
+ icon: {
31934
+ marginInlineStart: wonderBlocksTokens.spacing.xxxSmall_4
31935
+ },
31936
+ polygonActionsContainer: {
31937
+ width: "100%"
31938
+ },
31939
+ iconButton: {
31940
+ margin: 0
31941
+ },
31942
+ movementButtonsContainer: {
31943
+ display: "flex",
31944
+ flexDirection: "column",
31945
+ alignItems: "center",
31946
+ minWidth: "fit-content"
31947
+ },
31948
+ spaceUnder: {
31949
+ marginBottom: wonderBlocksTokens.spacing.xSmall_8
31950
+ },
31951
+ truncatedWidth: {
31952
+ // Allow truncation, stop bleeding over the edge.
31953
+ minWidth: 0
31849
31954
  }
31850
31955
  });
31851
31956
 
31852
- function InteractiveGraphCorrectAnswer(props) {
31853
- return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(Heading, {
31854
- title: "Correct Answer",
31855
- isOpen: true,
31856
- isCollapsible: false
31857
- }), /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, null, /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, null, /*#__PURE__*/React__namespace.createElement(wonderBlocksTypography.LabelXSmall, {
31858
- style: {
31859
- paddingTop: wonderBlocksTokens.spacing.xxSmall_6,
31860
- paddingBottom: wonderBlocksTokens.spacing.xxSmall_6,
31861
- color: wonderBlocksTokens.color.offBlack64
31862
- }
31863
- }, "Graph the correct answer in the graph below and ensure the equation or point coordinates displayed represent the correct answer."), /*#__PURE__*/React__namespace.createElement(wonderBlocksTypography.BodyMonospace, {
31864
- style: {
31865
- fontSize: 12,
31866
- backgroundColor: "#eee",
31867
- paddingInline: wonderBlocksTokens.spacing.xxSmall_6,
31868
- borderColor: "#ccc",
31869
- borderStyle: "solid",
31870
- borderWidth: 1
31871
- }
31872
- }, props.equationString)), props.children));
31873
- }
31874
-
31875
- function InteractiveGraphDescription(props) {
31957
+ /**
31958
+ * LockedVectorSettings is a component that allows the user to edit the
31959
+ * settings of specifically a locked vector on the graph.
31960
+ *
31961
+ * Used in the interactive graph editor's locked figures section.
31962
+ */
31963
+ const lengthErrorMessage = "The vector cannot have length 0.";
31964
+ const LockedVectorSettings = props => {
31876
31965
  const {
31877
- ariaLabelValue,
31878
- ariaDescriptionValue,
31879
- onChange
31966
+ points,
31967
+ color: lineColor,
31968
+ onChangeProps,
31969
+ onMove,
31970
+ onRemove
31880
31971
  } = props;
31881
- const [isOpen, setIsOpen] = React__namespace.useState(true);
31882
- return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(Heading, {
31883
- title: "Description",
31884
- isCollapsible: true,
31885
- isOpen: isOpen,
31886
- onToggle: setIsOpen
31887
- }), isOpen && /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, null, /*#__PURE__*/React__namespace.createElement(wonderBlocksTypography.LabelXSmall, {
31888
- style: styles$k.caption
31889
- }, "Use these fields to describe the graph as a whole. These are used by screen readers to describe content to users who are visually impaired."), /*#__PURE__*/React__namespace.createElement(wonderBlocksTypography.LabelLarge, {
31890
- tag: "label"
31891
- }, "Title", /*#__PURE__*/React__namespace.createElement(wonderBlocksForm.TextField, {
31892
- value: ariaLabelValue,
31893
- onChange: newValue => onChange({
31894
- fullGraphAriaLabel: newValue
31895
- }),
31896
- style: styles$k.spaceAbove
31897
- })), /*#__PURE__*/React__namespace.createElement(Strut, {
31898
- size: wonderBlocksTokens.spacing.small_12
31899
- }), /*#__PURE__*/React__namespace.createElement(wonderBlocksTypography.LabelLarge, {
31900
- tag: "label"
31901
- }, "Description", /*#__PURE__*/React__namespace.createElement(wonderBlocksForm.TextArea, {
31902
- rows: 8,
31903
- resizeType: "vertical",
31904
- value: ariaDescriptionValue,
31905
- onChange: newValue => onChange({
31906
- fullGraphAriaDescription: newValue
31907
- }),
31908
- style: styles$k.spaceAbove
31909
- }))));
31910
- }
31911
- const styles$k = aphrodite.StyleSheet.create({
31912
- caption: {
31913
- color: wonderBlocksTokens.color.offBlack64,
31914
- paddingTop: wonderBlocksTokens.spacing.xxSmall_6,
31915
- paddingBottom: wonderBlocksTokens.spacing.xxSmall_6
31916
- },
31917
- spaceAbove: {
31918
- marginTop: wonderBlocksTokens.spacing.xxxSmall_4
31919
- }
31920
- });
31972
+ const [tail, tip] = points;
31973
+ const lineLabel = `Vector (${tail[0]}, ${tail[1]}), (${tip[0]}, ${tip[1]})`;
31921
31974
 
31922
- /* eslint-disable react/forbid-prop-types, react/no-unsafe */
31923
- const {
31924
- ButtonGroup: ButtonGroup$2,
31925
- InfoTip: InfoTip$c,
31926
- RangeInput: RangeInput$4
31927
- } = perseus.components;
31928
- const defaultBackgroundImage$1 = {
31929
- url: null,
31930
- width: 0,
31931
- height: 0
31932
- };
31933
- function numSteps(range, step) {
31934
- return Math.floor((range[1] - range[0]) / step);
31935
- }
31936
- class InteractiveGraphSettings extends React__namespace.Component {
31937
- _isMounted = false;
31938
- bgUrlRef = /*#__PURE__*/React__namespace.createRef();
31939
- labelXRef = /*#__PURE__*/React__namespace.createRef();
31940
- labelYRef = /*#__PURE__*/React__namespace.createRef();
31941
- static stateFromProps(props) {
31942
- return {
31943
- labelsTextbox: props.labels,
31944
- gridStepTextbox: props.gridStep,
31945
- snapStepTextbox: props.snapStep,
31946
- stepTextbox: props.step,
31947
- rangeTextbox: props.range,
31948
- // Shallow-copied clone
31949
- backgroundImage: {
31950
- ...props.backgroundImage
31951
- }
31952
- };
31953
- }
31954
- constructor(props) {
31955
- super(props);
31956
- this.state = {
31957
- isExpanded: true,
31958
- ...InteractiveGraphSettings.stateFromProps(props)
31959
- };
31975
+ // Check if the line has length 0.
31976
+ const isInvalid = kmath.vector.equal(tail, tip);
31977
+ function handleChangePoint(newCoord, index) {
31978
+ if (typeof newCoord !== "undefined") {
31979
+ const newPoints = [...points];
31980
+ newPoints[index] = [...newCoord];
31981
+ onChangeProps({
31982
+ points: newPoints
31983
+ });
31984
+ }
31960
31985
  }
31961
- static defaultProps = {
31962
- box: [perseus.interactiveSizes.defaultBoxSizeSmall, perseus.interactiveSizes.defaultBoxSizeSmall],
31963
- labels: ["x", "y"],
31964
- range: [[-10, 10], [-10, 10]],
31965
- step: [1, 1],
31966
- gridStep: [1, 1],
31967
- snapStep: [1, 1],
31968
- valid: true,
31969
- backgroundImage: defaultBackgroundImage$1,
31970
- markings: "graph",
31971
- showProtractor: false,
31972
- showTooltips: false
31973
- };
31974
- componentDidMount() {
31975
- // TODO(scottgrant): This is a hack to remove the deprecated call to
31976
- // this.isMounted() but is still considered an anti-pattern.
31977
- this._isMounted = true;
31978
- this.changeGraph = ___default["default"].debounce(this.changeGraph, 300);
31986
+ function handleColorChange(newColor) {
31987
+ onChangeProps({
31988
+ color: newColor
31989
+ });
31979
31990
  }
31980
- UNSAFE_componentWillReceiveProps(nextProps) {
31981
- // Make sure that state updates when switching
31982
- // between different items in a multi-item editor.
31983
- if (!___default["default"].isEqual(this.props.labels, nextProps.labels) || !___default["default"].isEqual(this.props.gridStep, nextProps.gridStep) || !___default["default"].isEqual(this.props.snapStep, nextProps.snapStep) || !___default["default"].isEqual(this.props.step, nextProps.step) || !___default["default"].isEqual(this.props.range, nextProps.range) || !___default["default"].isEqual(this.props.backgroundImage, nextProps.backgroundImage)) {
31984
- this.setState(InteractiveGraphSettings.stateFromProps(nextProps));
31991
+ return /*#__PURE__*/React__namespace.createElement(PerseusEditorAccordion, {
31992
+ expanded: props.expanded,
31993
+ onToggle: props.onToggle,
31994
+ header: /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
31995
+ style: styles$j.row
31996
+ }, /*#__PURE__*/React__namespace.createElement(wonderBlocksTypography.LabelLarge, null, lineLabel), /*#__PURE__*/React__namespace.createElement(Strut, {
31997
+ size: wonderBlocksTokens.spacing.xSmall_8
31998
+ }), /*#__PURE__*/React__namespace.createElement(LineSwatch, {
31999
+ color: lineColor,
32000
+ lineStyle: "solid"
32001
+ }))
32002
+ }, /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
32003
+ style: [styles$j.row, styles$j.spaceUnder]
32004
+ }, /*#__PURE__*/React__namespace.createElement(ColorSelect, {
32005
+ selectedValue: lineColor,
32006
+ onChange: handleColorChange
32007
+ })), isInvalid && /*#__PURE__*/React__namespace.createElement(wonderBlocksTypography.LabelMedium, {
32008
+ style: styles$j.errorText
32009
+ }, lengthErrorMessage), /*#__PURE__*/React__namespace.createElement(PerseusEditorAccordion, {
32010
+ expanded: true // Initial state is expanded
32011
+ ,
32012
+ containerStyle: styles$j.container,
32013
+ panelStyle: styles$j.accordionPanel,
32014
+ header: /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
32015
+ style: styles$j.row
32016
+ }, /*#__PURE__*/React__namespace.createElement(wonderBlocksTypography.LabelLarge, null, `Tail (${tail[0]}, ${tail[1]})`))
32017
+ }, /*#__PURE__*/React__namespace.createElement(CoordinatePairInput, {
32018
+ coord: tail,
32019
+ error: isInvalid,
32020
+ onChange: newProps => {
32021
+ handleChangePoint(newProps, 0);
31985
32022
  }
32023
+ })), /*#__PURE__*/React__namespace.createElement(PerseusEditorAccordion, {
32024
+ expanded: true // Initial state is expanded
32025
+ ,
32026
+ containerStyle: styles$j.container,
32027
+ panelStyle: styles$j.accordionPanel,
32028
+ header: /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
32029
+ style: styles$j.row
32030
+ }, /*#__PURE__*/React__namespace.createElement(wonderBlocksTypography.LabelLarge, null, `Tip (${tip[0]}, ${tip[1]})`))
32031
+ }, /*#__PURE__*/React__namespace.createElement(CoordinatePairInput, {
32032
+ coord: tip,
32033
+ error: isInvalid,
32034
+ onChange: newProps => {
32035
+ handleChangePoint(newProps, 1);
32036
+ }
32037
+ })), /*#__PURE__*/React__namespace.createElement(LockedFigureSettingsActions, {
32038
+ figureType: props.type,
32039
+ onMove: onMove,
32040
+ onRemove: onRemove
32041
+ }));
32042
+ };
32043
+ const styles$j = aphrodite.StyleSheet.create({
32044
+ accordionPanel: {
32045
+ paddingBottom: wonderBlocksTokens.spacing.medium_16
32046
+ },
32047
+ container: {
32048
+ marginTop: wonderBlocksTokens.spacing.xSmall_8,
32049
+ marginBottom: 0,
32050
+ marginLeft: -wonderBlocksTokens.spacing.xxxSmall_4,
32051
+ marginRight: -wonderBlocksTokens.spacing.xxxSmall_4,
32052
+ backgroundColor: wonderBlocksTokens.color.white
32053
+ },
32054
+ errorText: {
32055
+ color: wonderBlocksTokens.color.red,
32056
+ marginTop: wonderBlocksTokens.spacing.xSmall_8
32057
+ },
32058
+ row: {
32059
+ flexDirection: "row",
32060
+ alignItems: "center"
31986
32061
  }
31987
- componentWillUnmount() {
31988
- this._isMounted = false;
31989
- }
31990
- change = (() => {
31991
- var _this = this;
31992
- return function () {
31993
- for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
31994
- args[_key] = arguments[_key];
32062
+ });
32063
+
32064
+ /**
32065
+ * LockedFigureSettings takes in a figure and renders the
32066
+ * respective settings for that figure type.
32067
+ *
32068
+ * Used in the interactive graph editor's locked figures section.
32069
+ */
32070
+
32071
+ // Union this type with other locked figure types when they are added.
32072
+
32073
+ const LockedFigureSettings = props => {
32074
+ switch (props.type) {
32075
+ case "point":
32076
+ return /*#__PURE__*/React__namespace.createElement(LockedPointSettings, props);
32077
+ case "line":
32078
+ return /*#__PURE__*/React__namespace.createElement(LockedLineSettings, props);
32079
+ case "vector":
32080
+ return /*#__PURE__*/React__namespace.createElement(LockedVectorSettings, props);
32081
+ case "ellipse":
32082
+ return /*#__PURE__*/React__namespace.createElement(LockedEllipseSettings, props);
32083
+ case "polygon":
32084
+ return /*#__PURE__*/React__namespace.createElement(LockedPolygonSettings, props);
32085
+ case "function":
32086
+ return /*#__PURE__*/React__namespace.createElement(LockedFunctionSettings, props);
32087
+ case "label":
32088
+ if (props.showLabelsFlag) {
32089
+ return /*#__PURE__*/React__namespace.createElement(LockedLabelSettings, props);
31995
32090
  }
31996
- return perseus.Changeable.change.apply(_this, args);
31997
- };
31998
- })();
31999
- changeBackgroundUrl = e => {
32000
- // Only continue on blur or "enter"
32001
- if (e.type === "keypress" && e.key !== "Enter") {
32002
- return;
32003
- }
32004
- const setUrl = (url, width, height) => {
32005
- // Shallow-copied clone
32006
- const image = {
32007
- ...this.props.backgroundImage
32091
+ break;
32092
+ }
32093
+ return null;
32094
+ };
32095
+
32096
+ const DEFAULT_COLOR = "grayH";
32097
+ function getDefaultFigureForType(type) {
32098
+ switch (type) {
32099
+ case "point":
32100
+ return {
32101
+ type: "point",
32102
+ coord: [0, 0],
32103
+ color: DEFAULT_COLOR,
32104
+ filled: true
32008
32105
  };
32009
- image.url = url;
32010
- image.width = width;
32011
- image.height = height;
32012
- this.setState({
32013
- backgroundImage: image
32014
- }, this.changeGraph);
32015
- };
32016
- const url = this.bgUrlRef.current?.value;
32017
- if (url) {
32018
- perseus.Util.getImageSize(url, (width, height) => {
32019
- if (this._isMounted) {
32020
- setUrl(url, width, height);
32021
- }
32022
- });
32023
- } else {
32024
- setUrl(null, 0, 0);
32025
- }
32026
- };
32027
- renderLabelChoices = choices => {
32028
- return choices.map(nameAndValue => /*#__PURE__*/React__namespace.createElement("option", {
32029
- key: nameAndValue[1],
32030
- value: nameAndValue[1]
32031
- }, nameAndValue[0]));
32032
- };
32033
- validRange = range => {
32034
- const numbers = ___default["default"].every(range, function (num) {
32035
- return ___default["default"].isFinite(num);
32036
- });
32037
- if (!numbers) {
32038
- return "Range must be a valid number";
32039
- }
32040
- if (range[0] >= range[1]) {
32041
- return "Range must have a higher number on the right";
32042
- }
32043
- return true;
32044
- };
32045
- validateStepValue = settings => {
32046
- const {
32047
- step,
32048
- range,
32049
- name,
32050
- minTicks,
32051
- maxTicks
32052
- } = settings;
32053
- const nSteps = numSteps(range, step);
32054
- if (nSteps < minTicks) {
32055
- return name + " is too large, there must be at least " + minTicks + " ticks.";
32056
- }
32057
- if (nSteps > maxTicks) {
32058
- return name + " is too small, there can be at most " + maxTicks + " ticks.";
32059
- }
32060
- return true;
32061
- };
32062
- validSnapStep = (step, range) => {
32063
- return this.validateStepValue({
32064
- step: step,
32065
- range: range,
32066
- name: "Snap step",
32067
- minTicks: 5,
32068
- maxTicks: 60
32069
- });
32070
- };
32071
- validGridStep = (step, range) => {
32072
- return this.validateStepValue({
32073
- step: step,
32074
- range: range,
32075
- name: "Grid step",
32076
- minTicks: 3,
32077
- maxTicks: 60
32078
- });
32079
- };
32080
- validStep = (step, range) => {
32081
- return this.validateStepValue({
32082
- step: step,
32083
- range: range,
32084
- name: "Step",
32085
- minTicks: 3,
32086
- maxTicks: 20
32087
- });
32088
- };
32089
- validBackgroundImageSize = image => {
32090
- // Ignore empty images
32091
- if (!image.url) {
32092
- return true;
32093
- }
32094
- const validSize = image.width <= 450 && image.height <= 450;
32095
- if (!validSize) {
32096
- return "Image must be smaller than 450px x 450px.";
32097
- }
32098
- return true;
32099
- };
32100
- validateGraphSettings = (range, step, gridStep, snapStep, image) => {
32101
- const self = this;
32102
- let msg;
32103
- const goodRange = ___default["default"].every(range, function (range) {
32104
- msg = self.validRange(range);
32105
- return msg === true;
32106
- });
32107
- if (!goodRange) {
32108
- return msg;
32109
- }
32110
- const goodStep = ___default["default"].every(step, function (step, i) {
32111
- msg = self.validStep(step, range[i]);
32112
- return msg === true;
32113
- });
32114
- if (!goodStep) {
32115
- return msg;
32116
- }
32117
- const goodGridStep = ___default["default"].every(gridStep, function (gridStep, i) {
32118
- msg = self.validGridStep(gridStep, range[i]);
32119
- return msg === true;
32120
- });
32121
- if (!goodGridStep) {
32122
- return msg;
32123
- }
32124
- const goodSnapStep = ___default["default"].every(snapStep, function (snapStep, i) {
32125
- msg = self.validSnapStep(snapStep, range[i]);
32126
- return msg === true;
32127
- });
32128
- if (!goodSnapStep) {
32129
- return msg;
32106
+ case "line":
32107
+ return {
32108
+ type: "line",
32109
+ kind: "line",
32110
+ points: [getDefaultFigureForType("point"), {
32111
+ ...getDefaultFigureForType("point"),
32112
+ coord: [2, 2]
32113
+ }],
32114
+ color: DEFAULT_COLOR,
32115
+ lineStyle: "solid",
32116
+ showPoint1: false,
32117
+ showPoint2: false
32118
+ };
32119
+ case "vector":
32120
+ return {
32121
+ type: "vector",
32122
+ points: [[0, 0], [2, 2]],
32123
+ color: DEFAULT_COLOR
32124
+ };
32125
+ case "ellipse":
32126
+ return {
32127
+ type: "ellipse",
32128
+ center: [0, 0],
32129
+ radius: [1, 1],
32130
+ angle: 0,
32131
+ color: DEFAULT_COLOR,
32132
+ fillStyle: "none",
32133
+ strokeStyle: "solid"
32134
+ };
32135
+ case "polygon":
32136
+ return {
32137
+ type: "polygon",
32138
+ points: [[0, 2], [-1, 0], [1, 0]],
32139
+ color: DEFAULT_COLOR,
32140
+ showVertices: false,
32141
+ fillStyle: "none",
32142
+ strokeStyle: "solid"
32143
+ };
32144
+ case "function":
32145
+ return {
32146
+ type: "function",
32147
+ color: DEFAULT_COLOR,
32148
+ strokeStyle: "solid",
32149
+ equation: "x^2",
32150
+ directionalAxis: "x"
32151
+ };
32152
+ case "label":
32153
+ return {
32154
+ type: "label",
32155
+ coord: [0, 0],
32156
+ text: "",
32157
+ color: DEFAULT_COLOR,
32158
+ size: "medium"
32159
+ };
32160
+ default:
32161
+ throw new wonderStuffCore.UnreachableCaseError(type);
32162
+ }
32163
+ }
32164
+
32165
+ const LockedFiguresSection = props => {
32166
+ // Keep track of all figures' accordions' expanded states for the
32167
+ // expand/collapse all button. Set the whole array to false initially.
32168
+ const collapsedStateArray = Array((props.figures ?? []).length).fill(false);
32169
+ const [expandedStates, setExpandedStates] = React__namespace.useState(collapsedStateArray);
32170
+ const [isExpanded, setIsExpanded] = React__namespace.useState(true);
32171
+ const uniqueId = wonderBlocksCore.useUniqueIdWithMock().get("locked-figures-section");
32172
+ const {
32173
+ figures,
32174
+ onChange
32175
+ } = props;
32176
+ function addLockedFigure(newFigure) {
32177
+ const lockedFigures = figures || [];
32178
+ const newProps = {
32179
+ lockedFigures: [...lockedFigures, getDefaultFigureForType(newFigure)]
32180
+ };
32181
+ onChange(newProps);
32182
+ setExpandedStates([...expandedStates, true]);
32183
+ }
32184
+ function moveLockedFigure(index, movement) {
32185
+ // Don't allow moving the first figure up or the last figure down.
32186
+ if (index === 0 && (movement === "back" || movement === "backward")) {
32187
+ return;
32130
32188
  }
32131
- const goodImageSize = this.validBackgroundImageSize(image);
32132
- if (goodImageSize !== true) {
32133
- msg = goodImageSize;
32134
- return msg;
32189
+ if (figures && index === figures.length - 1 && (movement === "front" || movement === "forward")) {
32190
+ return;
32135
32191
  }
32136
- return true;
32137
- };
32138
- changeLabel = (i, e) => {
32139
- const val = e.target.value;
32140
- const labels = this.state.labelsTextbox.slice();
32141
- labels[i] = val;
32142
- this.setState({
32143
- labelsTextbox: labels
32144
- }, this.changeGraph);
32145
- };
32146
- changeRange = (i, values) => {
32147
- const ranges = this.state.rangeTextbox.slice();
32148
- ranges[i] = values;
32149
- const step = this.state.stepTextbox.slice();
32150
- const gridStep = this.state.gridStepTextbox.slice();
32151
- const snapStep = this.state.snapStepTextbox.slice();
32152
- const scale = perseus.Util.scaleFromExtent(ranges[i], this.props.box[i]);
32153
- if (this.validRange(ranges[i]) === true) {
32154
- step[i] = perseus.Util.tickStepFromExtent(ranges[i], this.props.box[i]);
32155
- const gridStepValue = perseus.Util.gridStepFromTickStep(step[i], scale);
32156
- if (gridStepValue) {
32157
- gridStep[i] = gridStepValue;
32158
- }
32159
- snapStep[i] = gridStep[i] / 2;
32192
+ const lockedFigures = figures || [];
32193
+ const newFigures = [...lockedFigures];
32194
+ const newExpandedStates = [...expandedStates];
32195
+
32196
+ // First, remove the figure from its current position
32197
+ // in the figures array and the expanded states array.
32198
+ const [removedFigure] = newFigures.splice(index, 1);
32199
+ newExpandedStates.splice(index, 1);
32200
+
32201
+ // Then, add it back in the new position. Add "true" to the
32202
+ // expanded states array for the new position (it must already
32203
+ // be open since the button to move it is being pressed from there).
32204
+ switch (movement) {
32205
+ case "back":
32206
+ newFigures.unshift(removedFigure);
32207
+ newExpandedStates.unshift(true);
32208
+ break;
32209
+ case "backward":
32210
+ newFigures.splice(index - 1, 0, removedFigure);
32211
+ newExpandedStates.splice(index - 1, 0, true);
32212
+ break;
32213
+ case "forward":
32214
+ newFigures.splice(index + 1, 0, removedFigure);
32215
+ newExpandedStates.splice(index + 1, 0, true);
32216
+ break;
32217
+ case "front":
32218
+ newFigures.push(removedFigure);
32219
+ newExpandedStates.push(true);
32220
+ break;
32160
32221
  }
32161
- this.setState({
32162
- stepTextbox: step,
32163
- gridStepTextbox: gridStep,
32164
- snapStepTextbox: snapStep,
32165
- rangeTextbox: ranges
32166
- }, this.changeGraph);
32167
- };
32168
- changeStep = step => {
32169
- this.setState({
32170
- stepTextbox: step
32171
- }, this.changeGraph);
32172
- };
32173
- changeSnapStep = snapStep => {
32174
- this.setState({
32175
- snapStepTextbox: snapStep
32176
- }, this.changeGraph);
32177
- };
32178
- changeGridStep = gridStep => {
32179
- this.setState({
32180
- gridStepTextbox: gridStep,
32181
- snapStepTextbox: ___default["default"].map(gridStep, function (step) {
32182
- return step / 2;
32183
- })
32184
- }, this.changeGraph);
32185
- };
32186
- changeGraph = () => {
32187
- const labels = this.state.labelsTextbox;
32188
- const range = ___default["default"].map(this.state.rangeTextbox, function (range) {
32189
- return ___default["default"].map(range, Number);
32222
+ onChange({
32223
+ lockedFigures: newFigures
32190
32224
  });
32191
- const step = ___default["default"].map(this.state.stepTextbox, Number);
32192
- const gridStep = this.state.gridStepTextbox;
32193
- const snapStep = this.state.snapStepTextbox;
32194
- const image = this.state.backgroundImage;
32195
-
32196
- // validationResult is either:
32197
- // true -> the settings are valid
32198
- // a string -> the settings are invalid, and the explanation
32199
- // is contained in the string
32200
- // TODO(aria): Refactor this to not be confusing
32201
- const validationResult = this.validateGraphSettings(range, step, gridStep, snapStep, image);
32202
- if (validationResult === true) {
32203
- // either true or a string
32204
- this.change({
32205
- valid: true,
32206
- labels: labels,
32207
- range: range,
32208
- step: step,
32209
- gridStep: gridStep,
32210
- snapStep: snapStep,
32211
- backgroundImage: image
32212
- });
32213
- } else {
32214
- this.change({
32215
- valid: validationResult // a string message, not false
32225
+ setExpandedStates(newExpandedStates);
32226
+ }
32227
+ function removeLockedFigure(index) {
32228
+ // eslint-disable-next-line no-alert
32229
+ if (window.confirm("Are you sure you want to delete this figure?")) {
32230
+ const lockedFigures = figures || [];
32231
+ onChange({
32232
+ lockedFigures: [...lockedFigures.slice(0, index), ...lockedFigures.slice(index + 1)]
32216
32233
  });
32217
- }
32218
- };
32219
32234
 
32220
- render() {
32221
- return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(Heading, {
32222
- title: "Common Graph Settings",
32223
- isOpen: this.state.isExpanded,
32224
- isCollapsible: true,
32225
- onToggle: () => this.setState({
32226
- isExpanded: !this.state.isExpanded
32227
- })
32228
- }), this.state.isExpanded && /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, null, /*#__PURE__*/React__namespace.createElement("div", {
32229
- className: "graph-settings"
32230
- }, /*#__PURE__*/React__namespace.createElement("div", {
32231
- className: "perseus-widget-row"
32232
- }, /*#__PURE__*/React__namespace.createElement("div", {
32233
- className: "perseus-widget-left-col"
32234
- }, /*#__PURE__*/React__namespace.createElement(LabeledRow, {
32235
- label: "x Label"
32236
- }, /*#__PURE__*/React__namespace.createElement("input", {
32237
- type: "text",
32238
- className: "graph-settings-axis-label",
32239
- ref: this.labelXRef,
32240
- onChange: e => this.changeLabel(0, e),
32241
- value: this.state.labelsTextbox[0] || ""
32242
- }))), /*#__PURE__*/React__namespace.createElement("div", {
32243
- className: "perseus-widget-right-col"
32244
- }, /*#__PURE__*/React__namespace.createElement(LabeledRow, {
32245
- label: "y Label"
32246
- }, /*#__PURE__*/React__namespace.createElement("input", {
32247
- type: "text",
32248
- className: "graph-settings-axis-label",
32249
- ref: this.labelYRef,
32250
- onChange: e => this.changeLabel(1, e),
32251
- value: this.state.labelsTextbox[1] || ""
32252
- })))), /*#__PURE__*/React__namespace.createElement("div", {
32253
- className: "perseus-widget-row"
32254
- }, /*#__PURE__*/React__namespace.createElement("div", {
32255
- className: "perseus-widget-left-col"
32256
- }, /*#__PURE__*/React__namespace.createElement(LabeledRow, {
32257
- label: "x Range"
32258
- }, /*#__PURE__*/React__namespace.createElement(RangeInput$4, {
32259
- value: this.state.rangeTextbox[0],
32260
- onChange: vals => this.changeRange(0, vals)
32261
- }))), /*#__PURE__*/React__namespace.createElement("div", {
32262
- className: "perseus-widget-right-col"
32263
- }, /*#__PURE__*/React__namespace.createElement(LabeledRow, {
32264
- label: "y Range"
32265
- }, /*#__PURE__*/React__namespace.createElement(RangeInput$4, {
32266
- value: this.state.rangeTextbox[1],
32267
- onChange: vals => this.changeRange(1, vals)
32268
- })))), /*#__PURE__*/React__namespace.createElement("div", {
32269
- className: "perseus-widget-row"
32270
- }, /*#__PURE__*/React__namespace.createElement("div", {
32271
- className: "perseus-widget-left-col"
32272
- }, /*#__PURE__*/React__namespace.createElement(LabeledRow, {
32273
- label: "Tick Step"
32274
- }, /*#__PURE__*/React__namespace.createElement(RangeInput$4, {
32275
- value: this.state.stepTextbox,
32276
- onChange: this.changeStep
32277
- }))), /*#__PURE__*/React__namespace.createElement("div", {
32278
- className: "perseus-widget-right-col"
32279
- }, /*#__PURE__*/React__namespace.createElement(LabeledRow, {
32280
- label: "Grid Step"
32281
- }, /*#__PURE__*/React__namespace.createElement(RangeInput$4, {
32282
- value: this.state.gridStepTextbox,
32283
- onChange: this.changeGridStep
32284
- })))), /*#__PURE__*/React__namespace.createElement("div", {
32285
- className: "perseus-widget-row"
32286
- }, /*#__PURE__*/React__namespace.createElement("div", {
32287
- className: "perseus-widget-left-col"
32288
- }, /*#__PURE__*/React__namespace.createElement(LabeledRow, {
32289
- label: "Snap Step"
32290
- }, /*#__PURE__*/React__namespace.createElement(RangeInput$4, {
32291
- value: this.state.snapStepTextbox,
32292
- onChange: this.changeSnapStep
32293
- })))), /*#__PURE__*/React__namespace.createElement("div", {
32294
- className: "perseus-widget-row"
32295
- }, /*#__PURE__*/React__namespace.createElement(LabeledRow, {
32296
- label: "Markings:"
32297
- }, /*#__PURE__*/React__namespace.createElement(ButtonGroup$2, {
32298
- value: this.props.markings,
32299
- allowEmpty: false,
32300
- buttons: [{
32301
- value: "graph",
32302
- content: "Graph"
32303
- }, {
32304
- value: "grid",
32305
- content: "Grid"
32306
- }, {
32307
- value: "none",
32308
- content: "None"
32309
- }],
32310
- onChange: this.change("markings")
32311
- }))), /*#__PURE__*/React__namespace.createElement("div", {
32312
- className: "perseus-widget-left-col"
32313
- }, /*#__PURE__*/React__namespace.createElement(wonderBlocksForm.Checkbox, {
32314
- label: "Show tooltips",
32315
- checked: this.props.showTooltips,
32316
- onChange: value => {
32317
- this.change({
32318
- showTooltips: value
32319
- });
32320
- }
32321
- }))), /*#__PURE__*/React__namespace.createElement(LabeledRow, {
32322
- label: "Background image URL:",
32323
- style: styles$j.resetSpaceTop
32324
- }, /*#__PURE__*/React__namespace.createElement("input", {
32325
- type: "text",
32326
- className: aphrodite.css(styles$j.backgroundUrlInput),
32327
- ref: this.bgUrlRef,
32328
- value: this.state.backgroundImage.url || "",
32329
- onChange: e => {
32330
- // Shallow-copied clone
32331
- const image = {
32332
- ...this.props.backgroundImage
32333
- };
32334
- image.url = e.target.value;
32335
- this.setState({
32336
- backgroundImage: image
32337
- });
32338
- },
32339
- onKeyPress: this.changeBackgroundUrl,
32340
- onBlur: this.changeBackgroundUrl
32341
- }), /*#__PURE__*/React__namespace.createElement(InfoTip$c, null, /*#__PURE__*/React__namespace.createElement("p", null, "Create an image in graphie, or use the \"Add image\" function to create a background."))), /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
32342
- style: styles$j.protractorSection
32343
- }, /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
32344
- style: styles$j.checkboxRow
32345
- }, /*#__PURE__*/React__namespace.createElement(wonderBlocksForm.Checkbox, {
32346
- label: "Show protractor",
32347
- checked: this.props.showProtractor,
32348
- onChange: value => {
32349
- this.change({
32350
- showProtractor: value
32351
- });
32352
- },
32353
- style: styles$j.resetSpaceTop
32354
- })), this.props.showProtractor && /*#__PURE__*/React__namespace.createElement(Banner__default["default"], {
32355
- layout: "floating",
32356
- text: "The protractor is not accessible. Please consider an alternate approach.",
32357
- kind: "warning"
32358
- }))));
32235
+ // Update expanded states
32236
+ const newExpandedStates = [...expandedStates];
32237
+ newExpandedStates.splice(index, 1);
32238
+ setExpandedStates(newExpandedStates);
32239
+ }
32359
32240
  }
32360
- }
32361
- const styles$j = aphrodite.StyleSheet.create({
32362
- resetSpaceTop: {
32363
- marginTop: 0
32364
- },
32365
- backgroundUrlInput: {
32366
- border: `1px solid ${wonderBlocksTokens.color.offBlack32}`,
32367
- borderRadius: wonderBlocksTokens.spacing.xxxSmall_4,
32368
- padding: wonderBlocksTokens.spacing.xxxSmall_4
32369
- },
32370
- checkboxRow: {
32241
+ function changeProps(index,
32242
+ // Omit the type from the figure props so it doesn't think
32243
+ // we're trying to pass in the props for the wrong type.
32244
+ figureProps) {
32245
+ const lockedFigures = figures || [];
32246
+ const newFigures = {
32247
+ lockedFigures: [...lockedFigures.slice(0, index), {
32248
+ ...lockedFigures[index],
32249
+ ...figureProps
32250
+ }, ...lockedFigures.slice(index + 1)]
32251
+ };
32252
+ onChange(newFigures);
32253
+ }
32254
+ function toggleExpanded(newValue) {
32255
+ setExpandedStates(Array(figures?.length).fill(newValue));
32256
+ }
32257
+ const allCollapsed = expandedStates.every(value => !value);
32258
+ const buttonLabel = allCollapsed ? "Expand all" : "Collapse all";
32259
+ const showExpandButton = !!figures?.length;
32260
+ return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(Heading, {
32261
+ title: "Locked Figures",
32262
+ isOpen: isExpanded,
32263
+ onToggle: () => setIsExpanded(!isExpanded),
32264
+ isCollapsible: true
32265
+ }), isExpanded && /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, null, figures?.map((figure, index) => {
32266
+ return /*#__PURE__*/React__namespace.createElement(LockedFigureSettings, _extends({
32267
+ key: `${uniqueId}-locked-${figure}-${index}`,
32268
+ showLabelsFlag: props.showLabelsFlag,
32269
+ expanded: expandedStates[index],
32270
+ onToggle: newValue => {
32271
+ const newExpanded = [...expandedStates];
32272
+ newExpanded[index] = newValue;
32273
+ setExpandedStates(newExpanded);
32274
+ }
32275
+ }, figure, {
32276
+ onChangeProps: newProps => changeProps(index, newProps),
32277
+ onMove: movement => moveLockedFigure(index, movement),
32278
+ onRemove: () => removeLockedFigure(index)
32279
+ }));
32280
+ }), /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
32281
+ style: styles$i.buttonContainer
32282
+ }, /*#__PURE__*/React__namespace.createElement(LockedFigureSelect, {
32283
+ showLabelsFlag: props.showLabelsFlag,
32284
+ id: `${uniqueId}-select`,
32285
+ onChange: addLockedFigure
32286
+ }), /*#__PURE__*/React__namespace.createElement(Strut, {
32287
+ size: wonderBlocksTokens.spacing.small_12
32288
+ }), showExpandButton && /*#__PURE__*/React__namespace.createElement(Button__default["default"], {
32289
+ kind: "secondary",
32290
+ onClick: () => toggleExpanded(allCollapsed),
32291
+ style: styles$i.button
32292
+ }, buttonLabel))));
32293
+ };
32294
+ const styles$i = aphrodite.StyleSheet.create({
32295
+ buttonContainer: {
32371
32296
  flexDirection: "row",
32372
- alignItems: "center",
32373
- justifyContent: "space-between",
32374
- marginBottom: wonderBlocksTokens.spacing.xSmall_8
32297
+ alignItems: "center"
32375
32298
  },
32376
- protractorSection: {
32299
+ button: {
32377
32300
  marginTop: wonderBlocksTokens.spacing.xSmall_8,
32378
- borderTop: `1px solid ${wonderBlocksTokens.color.offBlack16}`,
32379
- paddingTop: wonderBlocksTokens.spacing.xSmall_8,
32380
- paddingBottom: wonderBlocksTokens.spacing.xSmall_8,
32381
- borderBottom: `1px solid ${wonderBlocksTokens.color.offBlack16}`
32301
+ flexGrow: 1
32382
32302
  }
32383
32303
  });
32384
32304
 
32385
- const SegmentCountSelector = _ref => {
32386
- let {
32387
- numSegments = 1,
32388
- onChange
32389
- } = _ref;
32390
- return /*#__PURE__*/React__namespace.createElement(wonderBlocksDropdown.SingleSelect, {
32391
- key: "segment-select",
32392
- selectedValue: `${numSegments}`
32393
- // Never uses placeholder, always has value
32394
- ,
32395
- placeholder: "",
32396
- onChange: newValue => {
32397
- const num = +newValue;
32398
- onChange(num);
32399
- },
32400
- style: styles$i.singleSelectShort
32401
- }, ___default["default"].range(1, 7).map(n => /*#__PURE__*/React__namespace.createElement(wonderBlocksDropdown.OptionItem, {
32402
- key: n,
32403
- value: `${n}`,
32404
- label: `${n} segment${n > 1 ? "s" : ""}`
32405
- })));
32305
+ function getStartCoords(graph) {
32306
+ if ("startCoords" in graph) {
32307
+ return graph.startCoords;
32308
+ }
32309
+ return undefined;
32310
+ }
32311
+ function getDefaultGraphStartCoords(graph, range, step) {
32312
+ switch (graph.type) {
32313
+ case "linear":
32314
+ case "ray":
32315
+ return perseus.getLineCoords({
32316
+ ...graph,
32317
+ startCoords: undefined
32318
+ }, range, step);
32319
+ case "segment":
32320
+ return perseus.getSegmentCoords({
32321
+ ...graph,
32322
+ startCoords: undefined
32323
+ }, range, step);
32324
+ case "linear-system":
32325
+ return perseus.getLinearSystemCoords({
32326
+ ...graph,
32327
+ startCoords: undefined
32328
+ }, range, step);
32329
+ case "circle":
32330
+ const startCoords = perseus.getCircleCoords({
32331
+ ...graph,
32332
+ startCoords: undefined
32333
+ });
32334
+ const radius = kmath.vector.length(kmath.vector.subtract(startCoords.radiusPoint, startCoords.center));
32335
+ return {
32336
+ center: startCoords.center,
32337
+ radius
32338
+ };
32339
+ case "sinusoid":
32340
+ return perseus.getSinusoidCoords({
32341
+ ...graph,
32342
+ startCoords: undefined
32343
+ }, range, step);
32344
+ case "quadratic":
32345
+ return perseus.getQuadraticCoords({
32346
+ ...graph,
32347
+ startCoords: undefined
32348
+ }, range, step);
32349
+ case "point":
32350
+ return perseus.getPointCoords({
32351
+ ...graph,
32352
+ startCoords: undefined
32353
+ }, range, step);
32354
+ case "polygon":
32355
+ return perseus.getPolygonCoords({
32356
+ ...graph,
32357
+ startCoords: undefined
32358
+ }, range, step);
32359
+ case "angle":
32360
+ return perseus.getAngleCoords({
32361
+ graph: {
32362
+ ...graph,
32363
+ startCoords: undefined
32364
+ },
32365
+ range,
32366
+ step
32367
+ });
32368
+ default:
32369
+ return undefined;
32370
+ }
32371
+ }
32372
+ const getSinusoidEquation = startCoords => {
32373
+ // Get coefficients
32374
+ // It's assumed that p1 is the root and p2 is the first peak
32375
+ const p1 = startCoords[0];
32376
+ const p2 = startCoords[1];
32377
+
32378
+ // Resulting coefficients are canonical for this sine curve
32379
+ const amplitude = p2[1] - p1[1];
32380
+ const angularFrequency = Math.PI / (2 * (p2[0] - p1[0]));
32381
+ const phase = p1[0] * angularFrequency;
32382
+ const verticalOffset = p1[1];
32383
+ return "y = " + amplitude.toFixed(3) + "sin(" + angularFrequency.toFixed(3) + "x - " + phase.toFixed(3) + ") + " + verticalOffset.toFixed(3);
32406
32384
  };
32407
- const styles$i = aphrodite.StyleSheet.create({
32408
- singleSelectShort: {
32409
- // Non-standard spacing, but it's the smallest we can go
32410
- // without running into styling issues with the dropdown.
32411
- height: 26
32385
+ const getQuadraticEquation = startCoords => {
32386
+ const p1 = startCoords[0];
32387
+ const p2 = startCoords[1];
32388
+ const p3 = startCoords[2];
32389
+ const denom = (p1[0] - p2[0]) * (p1[0] - p3[0]) * (p2[0] - p3[0]);
32390
+ if (denom === 0) {
32391
+ // Many of the callers assume that the return value is always defined.
32392
+ return "Division by zero error";
32412
32393
  }
32413
- });
32394
+ const a = (p3[0] * (p2[1] - p1[1]) + p2[0] * (p1[1] - p3[1]) + p1[0] * (p3[1] - p2[1])) / denom;
32395
+ const b = (p3[0] * p3[0] * (p1[1] - p2[1]) + p2[0] * p2[0] * (p3[1] - p1[1]) + p1[0] * p1[0] * (p2[1] - p3[1])) / denom;
32396
+ const c = (p2[0] * p3[0] * (p2[0] - p3[0]) * p1[1] + p3[0] * p1[0] * (p3[0] - p1[0]) * p2[1] + p1[0] * p2[0] * (p1[0] - p2[0]) * p3[1]) / denom;
32397
+ return "y = " + a.toFixed(3) + "x^2 + " + b.toFixed(3) + "x + " + c.toFixed(3);
32398
+ };
32399
+ const findAngle = (point1, point2) => {
32400
+ const x = point1[0] - point2[0];
32401
+ const y = point1[1] - point2[1];
32402
+ return (180 + Math.atan2(-y, -x) * 180 / Math.PI + 360) % 360;
32403
+ };
32404
+ const getAngleEquation = startCoords => {
32405
+ const [point1, vertex, point2] = startCoords;
32406
+ const angle = findAngle(point2, vertex) - findAngle(point1, vertex);
32407
+ const roundedAngle = angle.toFixed(0);
32408
+ return `${roundedAngle}\u00B0 angle at (${vertex[0]}, ${vertex[1]})`;
32409
+ };
32410
+ const shouldShowStartCoordsUI = (graph, isStatic) => {
32411
+ // We don't show the start coords UI for static graphs, since
32412
+ // the coords shown for this are determined by the coords on the
32413
+ // "correct" graph. The start coords would not be used here.
32414
+ if (isStatic) {
32415
+ return false;
32416
+ }
32417
+ switch (graph.type) {
32418
+ case "point":
32419
+ return graph.numPoints !== "unlimited";
32420
+ case "polygon":
32421
+ return graph.numSides !== "unlimited" && graph.snapTo !== "angles" && graph.snapTo !== "sides";
32422
+ case "none":
32423
+ return false;
32424
+ case "angle":
32425
+ case "circle":
32426
+ case "linear":
32427
+ case "linear-system":
32428
+ case "quadratic":
32429
+ case "ray":
32430
+ case "segment":
32431
+ case "sinusoid":
32432
+ return true;
32433
+ default:
32434
+ throw new wonderStuffCore.UnreachableCaseError(graph);
32435
+ }
32436
+ };
32414
32437
 
32415
32438
  const StartCoordsAngle = props => {
32416
32439
  const {
@@ -32922,7 +32945,7 @@ class InteractiveGraphEditor extends React__namespace.Component {
32922
32945
  ___default["default"].extend(json, {
32923
32946
  graph: {
32924
32947
  type: correct.type,
32925
- startCoords: this.props.graph?.startCoords
32948
+ startCoords: this.props.graph && getStartCoords(this.props.graph)
32926
32949
  },
32927
32950
  correct: correct
32928
32951
  });
@@ -33017,7 +33040,7 @@ class InteractiveGraphEditor extends React__namespace.Component {
33017
33040
  }, this.props.valid);
33018
33041
  }
33019
33042
  return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, null, /*#__PURE__*/React__namespace.createElement(LabeledRow, {
33020
- label: "Type of Graph:"
33043
+ label: "Answer type:"
33021
33044
  }, /*#__PURE__*/React__namespace.createElement(GraphTypeSelector, {
33022
33045
  graphType: this.props.graph?.type ?? InteractiveGraph.defaultProps.graph.type,
33023
33046
  onChange: type => {
@@ -33029,7 +33052,8 @@ class InteractiveGraphEditor extends React__namespace.Component {
33029
33052
  type
33030
33053
  }
33031
33054
  });
33032
- }
33055
+ },
33056
+ showNoneOption: this.props.apiOptions?.flags?.mafs?.["none"]
33033
33057
  })), this.props.graph && this.props.apiOptions?.flags?.mafs?.[this.props.graph.type] && /*#__PURE__*/React__namespace.createElement(InteractiveGraphDescription, {
33034
33058
  ariaLabelValue: this.props.fullGraphAriaLabel ?? "",
33035
33059
  ariaDescriptionValue: this.props.fullGraphAriaDescription ?? "",
@@ -33305,6 +33329,12 @@ function mergeGraphs(a, b) {
33305
33329
  ...a,
33306
33330
  ...b
33307
33331
  };
33332
+ case "none":
33333
+ invariant(b.type === "none");
33334
+ return {
33335
+ ...a,
33336
+ ...b
33337
+ };
33308
33338
  case "point":
33309
33339
  invariant(b.type === "point");
33310
33340
  return {