@khanacademy/perseus-editor 14.1.3 → 14.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/dist/components/util.d.ts +0 -14
  2. package/dist/es/index.js +1542 -1450
  3. package/dist/es/index.js.map +1 -1
  4. package/dist/index.js +1375 -1274
  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 +281 -128
  10. package/dist/{components/graph-locked-figures → widgets/interactive-graph-editor/locked-figures}/locked-figure-settings-actions.d.ts +1 -1
  11. package/dist/{components/graph-locked-figures → widgets/interactive-graph-editor/locked-figures}/locked-figure-settings.d.ts +2 -0
  12. package/dist/{components/graph-locked-figures → widgets/interactive-graph-editor/locked-figures}/locked-figures-section.d.ts +3 -2
  13. package/dist/widgets/interactive-graph-editor/locked-figures/locked-label-settings.d.ts +35 -0
  14. package/dist/widgets/interactive-graph-editor/locked-figures/util.d.ts +9 -0
  15. package/dist/widgets/interactive-graph-editor/start-coords/start-coords-angle.d.ts +9 -0
  16. package/dist/widgets/interactive-graph-editor/start-coords/start-coords-circle.d.ts +12 -0
  17. package/dist/{components → widgets/interactive-graph-editor/start-coords}/start-coords-line.d.ts +2 -2
  18. package/dist/{components → widgets/interactive-graph-editor/start-coords}/start-coords-multiline.d.ts +2 -2
  19. package/dist/{components → widgets/interactive-graph-editor/start-coords}/start-coords-point.d.ts +2 -2
  20. package/dist/{components → widgets/interactive-graph-editor/start-coords}/start-coords-quadratic.d.ts +2 -2
  21. package/dist/{components → widgets/interactive-graph-editor/start-coords}/start-coords-settings.d.ts +2 -1
  22. package/dist/widgets/interactive-graph-editor/start-coords/start-coords-sinusoid.d.ts +9 -0
  23. package/dist/widgets/interactive-graph-editor/start-coords/types.d.ts +24 -0
  24. package/dist/widgets/interactive-graph-editor/start-coords/util.d.ts +8 -0
  25. package/package.json +2 -2
  26. package/dist/components/graph-locked-figures/locked-label-settings.d.ts +0 -17
  27. package/dist/components/start-coords-angle.d.ts +0 -8
  28. package/dist/components/start-coords-circle.d.ts +0 -11
  29. package/dist/components/start-coords-sinusoid.d.ts +0 -8
  30. /package/dist/{components → widgets/interactive-graph-editor/components}/interactive-graph-correct-answer.d.ts +0 -0
  31. /package/dist/{components → widgets/interactive-graph-editor/components}/interactive-graph-settings.d.ts +0 -0
  32. /package/dist/{components → widgets/interactive-graph-editor/components}/segment-count-selector.d.ts +0 -0
  33. /package/dist/{components/graph-locked-figures → widgets/interactive-graph-editor/locked-figures}/color-select.d.ts +0 -0
  34. /package/dist/{components/graph-locked-figures → widgets/interactive-graph-editor/locked-figures}/color-swatch.d.ts +0 -0
  35. /package/dist/{components/graph-locked-figures → widgets/interactive-graph-editor/locked-figures}/defining-point-settings.d.ts +0 -0
  36. /package/dist/{components/graph-locked-figures → widgets/interactive-graph-editor/locked-figures}/ellipse-swatch.d.ts +0 -0
  37. /package/dist/{components/graph-locked-figures → widgets/interactive-graph-editor/locked-figures}/labeled-row.d.ts +0 -0
  38. /package/dist/{components/graph-locked-figures → widgets/interactive-graph-editor/locked-figures}/labeled-switch.d.ts +0 -0
  39. /package/dist/{components/graph-locked-figures → widgets/interactive-graph-editor/locked-figures}/line-stroke-select.d.ts +0 -0
  40. /package/dist/{components/graph-locked-figures → widgets/interactive-graph-editor/locked-figures}/line-swatch.d.ts +0 -0
  41. /package/dist/{components/graph-locked-figures → widgets/interactive-graph-editor/locked-figures}/locked-ellipse-settings.d.ts +0 -0
  42. /package/dist/{components/graph-locked-figures → widgets/interactive-graph-editor/locked-figures}/locked-figure-select.d.ts +0 -0
  43. /package/dist/{components/graph-locked-figures → widgets/interactive-graph-editor/locked-figures}/locked-function-examples.d.ts +0 -0
  44. /package/dist/{components/graph-locked-figures → widgets/interactive-graph-editor/locked-figures}/locked-function-settings.d.ts +0 -0
  45. /package/dist/{components/graph-locked-figures → widgets/interactive-graph-editor/locked-figures}/locked-line-settings.d.ts +0 -0
  46. /package/dist/{components/graph-locked-figures → widgets/interactive-graph-editor/locked-figures}/locked-point-settings.d.ts +0 -0
  47. /package/dist/{components/graph-locked-figures → widgets/interactive-graph-editor/locked-figures}/locked-polygon-settings.d.ts +0 -0
  48. /package/dist/{components/graph-locked-figures → widgets/interactive-graph-editor/locked-figures}/locked-vector-settings.d.ts +0 -0
  49. /package/dist/{components/graph-locked-figures → widgets/interactive-graph-editor/locked-figures}/polygon-swatch.d.ts +0 -0
package/dist/es/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { addLibraryVersionToPerseusDebug, PerseusError, Errors } from '@khanacademy/perseus-core';
2
- import { constants, components, Widgets, icons, WIDGET_PROP_DENYLIST, Util, Log, PerseusMarkdown, preprocessTex, ApiOptions, buildMapper, itemToTree, buildEmptyItemTreeForShape, shapes, ItemExtras, itemDataVersion, ClassNames, MultiItems, traverse, Categorizer as Categorizer$1, Changeable, EditorJsonify, Dependencies, PerseusExpressionAnswerFormConsidered, Expression, interactiveSizes, KhanMath, GrapherWidget, GrapherUtil, SizingUtils, PerseusI18nContext, KhanColors, getAngleCoords, getPolygonCoords, getPointCoords, getQuadraticCoords, getSinusoidCoords, getCircleCoords, getLinearSystemCoords, getSegmentCoords, getLineCoords, lockedFigureColors, lockedFigureFillStyles, InteractiveGraphWidget, globalStyles, globalConstants, MatrixWidget, makeSafeUrl, PlotterWidget, plotterPlotTypes, BaseRadio, TableWidget, widgets } from '@khanacademy/perseus';
2
+ import { constants, components, Widgets, icons, WIDGET_PROP_DENYLIST, Util, Log, PerseusMarkdown, preprocessTex, ApiOptions, buildMapper, itemToTree, buildEmptyItemTreeForShape, shapes, ItemExtras, itemDataVersion, ClassNames, MultiItems, traverse, Categorizer as Categorizer$1, Changeable, EditorJsonify, Dependencies, PerseusExpressionAnswerFormConsidered, Expression, interactiveSizes, KhanMath, GrapherWidget, GrapherUtil, SizingUtils, PerseusI18nContext, KhanColors, lockedFigureColors, lockedFigureFillStyles, getAngleCoords, getPolygonCoords, getPointCoords, getQuadraticCoords, getSinusoidCoords, getCircleCoords, getLinearSystemCoords, getSegmentCoords, getLineCoords, InteractiveGraphWidget, globalStyles, globalConstants, MatrixWidget, makeSafeUrl, PlotterWidget, plotterPlotTypes, BaseRadio, TableWidget, widgets } from '@khanacademy/perseus';
3
3
  export { widgets } from '@khanacademy/perseus';
4
4
  import * as React from 'react';
5
5
  import { useRef, useEffect, useState, useId } from 'react';
@@ -19,11 +19,12 @@ import { Checkbox as Checkbox$1, LabeledTextField, TextField, TextArea } from '@
19
19
  import ReactDOM from 'react-dom';
20
20
  import * as KAS from '@khanacademy/kas';
21
21
  import Button from '@khanacademy/wonder-blocks-button';
22
- import { HeadingSmall, HeadingXSmall, LabelSmall, LabelLarge, LabelMedium, LabelXSmall, BodyMonospace } from '@khanacademy/wonder-blocks-typography';
22
+ import { HeadingSmall, HeadingXSmall, LabelLarge, LabelXSmall, BodyMonospace, LabelSmall, LabelMedium } from '@khanacademy/wonder-blocks-typography';
23
23
  import { isTruthy, UnreachableCaseError } from '@khanacademy/wonder-stuff-core';
24
24
  import { useMathInputI18n, createMathField } from '@khanacademy/math-input';
25
25
  import { vector, number } from '@khanacademy/kmath';
26
- import { ActionMenu, ActionItem, SingleSelect, OptionItem } from '@khanacademy/wonder-blocks-dropdown';
26
+ import { SingleSelect, OptionItem, ActionMenu, ActionItem } from '@khanacademy/wonder-blocks-dropdown';
27
+ import Banner from '@khanacademy/wonder-blocks-banner';
27
28
  import { AccordionSection } from '@khanacademy/wonder-blocks-accordion';
28
29
  import IconButton from '@khanacademy/wonder-blocks-icon-button';
29
30
  import caretDoubleDownIcon from '@phosphor-icons/core/bold/caret-double-down-bold.svg';
@@ -33,18 +34,17 @@ import caretUpIcon from '@phosphor-icons/core/bold/caret-up-bold.svg';
33
34
  import trashIcon from '@phosphor-icons/core/bold/trash-bold.svg';
34
35
  import copyIcon from '@phosphor-icons/core/assets/regular/copy.svg';
35
36
  import autoPasteIcon from '@phosphor-icons/core/assets/regular/note-pencil.svg';
37
+ import plusCircle from '@phosphor-icons/core/regular/plus-circle.svg';
36
38
  import arrowFatDown from '@phosphor-icons/core/regular/arrow-fat-down.svg';
37
39
  import arrowFatLeft from '@phosphor-icons/core/regular/arrow-fat-left.svg';
38
40
  import arrowFatRight from '@phosphor-icons/core/regular/arrow-fat-right.svg';
39
41
  import arrowFatUp from '@phosphor-icons/core/regular/arrow-fat-up.svg';
40
42
  import minusCircle from '@phosphor-icons/core/regular/minus-circle.svg';
41
- import plusCircle from '@phosphor-icons/core/regular/plus-circle.svg';
42
- import Banner from '@khanacademy/wonder-blocks-banner';
43
43
  import arrowCounterClockwise from '@phosphor-icons/core/bold/arrow-counter-clockwise-bold.svg';
44
44
 
45
45
  // This file is processed by a Rollup plugin (replace) to inject the production
46
46
  const libName = "@khanacademy/perseus-editor";
47
- const libVersion = "14.1.3";
47
+ const libVersion = "14.3.0";
48
48
  addLibraryVersionToPerseusDebug(libName, libVersion);
49
49
 
50
50
  function _extends() {
@@ -29496,39 +29496,95 @@ function invariant(condition, message) {
29496
29496
  throw new Error(value);
29497
29497
  }
29498
29498
 
29499
- // Renders the given children in a View laid out horizontally.
29500
- const LabeledRow = props => {
29501
- const {
29502
- children,
29503
- label,
29504
- labelSide = "left",
29505
- style
29506
- } = props;
29507
- return /*#__PURE__*/React.createElement("label", {
29508
- className: css(styles$J.label)
29509
- }, /*#__PURE__*/React.createElement(View, {
29510
- style: [styles$J.row, style]
29511
- }, labelSide === "start" || /*#__PURE__*/React.createElement(LabelSmall, {
29512
- style: styles$J.spaceEnd
29513
- }, label), children, labelSide === "end" && /*#__PURE__*/React.createElement(LabelSmall, {
29514
- style: styles$J.spaceStart
29515
- }, label)));
29499
+ const UNLIMITED = "unlimited";
29500
+ // Parses a string (ostensibly from some UI element that only supports string
29501
+ // types, such as a <select />) into a numeric point count or the special sigil
29502
+ // "unlimited". Note that 0 is a special case, and means "unlimited".
29503
+ const parsePointCount = points => {
29504
+ const parsed = parseInt(points, 10);
29505
+ if (isNaN(parsed)) {
29506
+ return UNLIMITED;
29507
+ }
29508
+ return parsed === 0 ? UNLIMITED : parsed;
29509
+ };
29510
+
29511
+ const GraphPointsCountSelector = ({
29512
+ numPoints: _numPoints = 1,
29513
+ onChange
29514
+ }) => {
29515
+ return /*#__PURE__*/React.createElement(SingleSelect, {
29516
+ selectedValue: `${_numPoints}`,
29517
+ onChange: newValue => {
29518
+ onChange(parsePointCount(newValue));
29519
+ }
29520
+ // Never uses placeholder, always has value
29521
+ ,
29522
+ placeholder: "",
29523
+ style: styles$J.singleSelectShort
29524
+ }, [...[...Array(7).keys()].map(n => /*#__PURE__*/React.createElement(OptionItem, {
29525
+ key: n,
29526
+ value: `${n}`,
29527
+ label: `${n} point${n > 1 ? "s" : ""}`
29528
+ })), /*#__PURE__*/React.createElement(OptionItem, {
29529
+ key: "unlimited",
29530
+ value: UNLIMITED,
29531
+ label: "unlimited"
29532
+ })]);
29516
29533
  };
29517
29534
  const styles$J = StyleSheet.create({
29518
- label: {
29519
- width: "fit-content"
29520
- },
29521
- row: {
29522
- flexDirection: "row",
29523
- marginTop: spacing.xSmall_8,
29524
- alignItems: "center",
29525
- width: "fit-content"
29526
- },
29527
- spaceStart: {
29528
- marginInlineStart: spacing.xSmall_8
29529
- },
29530
- spaceEnd: {
29531
- marginInlineEnd: spacing.xSmall_8
29535
+ singleSelectShort: {
29536
+ // Non-standard spacing, but it's the smallest we can go
29537
+ // without running into styling issues with the dropdown.
29538
+ height: 26
29539
+ }
29540
+ });
29541
+
29542
+ const GraphTypeSelector = props => {
29543
+ return /*#__PURE__*/React.createElement(SingleSelect, {
29544
+ selectedValue: props.graphType,
29545
+ onChange: props.onChange,
29546
+ placeholder: "Select an answer type",
29547
+ style: styles$I.singleSelectShort
29548
+ }, props.showNoneOption && /*#__PURE__*/React.createElement(OptionItem, {
29549
+ value: "none",
29550
+ label: "None"
29551
+ }), /*#__PURE__*/React.createElement(OptionItem, {
29552
+ value: "linear",
29553
+ label: "Linear function"
29554
+ }), /*#__PURE__*/React.createElement(OptionItem, {
29555
+ value: "quadratic",
29556
+ label: "Quadratic function"
29557
+ }), /*#__PURE__*/React.createElement(OptionItem, {
29558
+ value: "sinusoid",
29559
+ label: "Sinusoid function"
29560
+ }), /*#__PURE__*/React.createElement(OptionItem, {
29561
+ value: "circle",
29562
+ label: "Circle"
29563
+ }), /*#__PURE__*/React.createElement(OptionItem, {
29564
+ value: "point",
29565
+ label: "Point(s)"
29566
+ }), /*#__PURE__*/React.createElement(OptionItem, {
29567
+ value: "linear-system",
29568
+ label: "Linear System"
29569
+ }), /*#__PURE__*/React.createElement(OptionItem, {
29570
+ value: "polygon",
29571
+ label: "Polygon"
29572
+ }), /*#__PURE__*/React.createElement(OptionItem, {
29573
+ value: "segment",
29574
+ label: "Line Segment(s)"
29575
+ }), /*#__PURE__*/React.createElement(OptionItem, {
29576
+ value: "ray",
29577
+ label: "Ray"
29578
+ }), /*#__PURE__*/React.createElement(OptionItem, {
29579
+ value: "angle",
29580
+ label: "Angle"
29581
+ }));
29582
+ };
29583
+ const styles$I = StyleSheet.create({
29584
+ singleSelectShort: {
29585
+ // Non-standard spacing, but it's the smallest we can go
29586
+ // without running into styling issues with the dropdown.
29587
+ height: 26
29532
29588
  }
29533
29589
  });
29534
29590
 
@@ -29539,11 +29595,11 @@ function Heading({
29539
29595
  onToggle
29540
29596
  }) {
29541
29597
  return /*#__PURE__*/React.createElement(Clickable, {
29542
- style: [styles$I.container, !isCollapsible && styles$I.notClickable],
29598
+ style: [styles$H.container, !isCollapsible && styles$H.notClickable],
29543
29599
  disabled: !isCollapsible,
29544
29600
  onClick: () => isCollapsible && (onToggle == null ? void 0 : onToggle(!isOpen))
29545
29601
  }, () => /*#__PURE__*/React.createElement(View, {
29546
- style: styles$I.heading
29602
+ style: styles$H.heading
29547
29603
  }, /*#__PURE__*/React.createElement(LabelLarge, {
29548
29604
  style: {
29549
29605
  fontSize: 14,
@@ -29553,7 +29609,7 @@ function Heading({
29553
29609
  isExpanded: isOpen
29554
29610
  })));
29555
29611
  }
29556
- const styles$I = StyleSheet.create({
29612
+ const styles$H = StyleSheet.create({
29557
29613
  container: {
29558
29614
  marginTop: spacing.small_12,
29559
29615
  // NOTE(jeremy): This is the inverse of the @editorPadding CSS
@@ -29577,349 +29633,759 @@ const styles$I = StyleSheet.create({
29577
29633
  }
29578
29634
  });
29579
29635
 
29580
- function focusWithChromeStickyFocusBugWorkaround(element) {
29581
- // NOTE(marksandstrom) Chrome sticky focus bug.
29582
- //
29583
- // There is a bug in Chrome related to sticky positioning and
29584
- // focus: When a child of an element with sticky positioning is
29585
- // focused with the focus() method, the scrollable area
29586
- // containing that element will scroll to the top (even though
29587
- // the element is already visble on the page). For a dropdown
29588
- // this means that the area will be scrolled to the top when
29589
- // the user interacts with the dropdown (since we actively
29590
- // manage dropdown header and option focus for keyboard users).
29591
- //
29592
- // To work around this bug the `preventScroll` focus option is
29593
- // used.
29594
- //
29595
- // Using `focusOptions` is an experimental browser feature
29596
- // "that should not be used in production code". However, since
29597
- // Chrome is the only browser we support that exhibits this
29598
- // buggy behavior and using `focusOptions` works around the
29599
- // bug, it is perfectly reasonable to use the feature in this
29600
- // case. We are not relying on the effect of the
29601
- // `preventScroll` option otherwise.
29602
- //
29603
- // focusOptions documentation:
29604
- //
29605
- // https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus
29606
- //
29607
- // Note that there could be cases where we want default focus() behavior.
29608
- // One that comes to mind is a dropdown with a long list of options that
29609
- // might be rendered off screen when opened. With this workaround the
29610
- // selected option would not be scrolled into view if the option is near
29611
- // the end of the list. We're not aware of any uses of the dropdown where
29612
- // this is an issue, however, so there isn't currently a way to out of the
29613
- // `preventScroll` behavior.
29614
-
29615
- // @ts-expect-error - TS2339 - Property 'focus' does not exist on type 'Element'.
29616
- element.focus({
29617
- preventScroll: true
29618
- });
29619
- }
29620
- const DEFAULT_COLOR = "grayH";
29621
- function getDefaultFigureForType(type) {
29622
- switch (type) {
29623
- case "point":
29624
- return {
29625
- type: "point",
29626
- coord: [0, 0],
29627
- color: DEFAULT_COLOR,
29628
- filled: true
29629
- };
29630
- case "line":
29631
- return {
29632
- type: "line",
29633
- kind: "line",
29634
- points: [getDefaultFigureForType("point"), _extends({}, getDefaultFigureForType("point"), {
29635
- coord: [2, 2]
29636
- })],
29637
- color: DEFAULT_COLOR,
29638
- lineStyle: "solid",
29639
- showPoint1: false,
29640
- showPoint2: false
29641
- };
29642
- case "vector":
29643
- return {
29644
- type: "vector",
29645
- points: [[0, 0], [2, 2]],
29646
- color: DEFAULT_COLOR
29647
- };
29648
- case "ellipse":
29649
- return {
29650
- type: "ellipse",
29651
- center: [0, 0],
29652
- radius: [1, 1],
29653
- angle: 0,
29654
- color: DEFAULT_COLOR,
29655
- fillStyle: "none",
29656
- strokeStyle: "solid"
29657
- };
29658
- case "polygon":
29659
- return {
29660
- type: "polygon",
29661
- points: [[0, 2], [-1, 0], [1, 0]],
29662
- color: DEFAULT_COLOR,
29663
- showVertices: false,
29664
- fillStyle: "none",
29665
- strokeStyle: "solid"
29666
- };
29667
- case "function":
29668
- return {
29669
- type: "function",
29670
- color: DEFAULT_COLOR,
29671
- strokeStyle: "solid",
29672
- equation: "x^2",
29673
- directionalAxis: "x"
29674
- };
29675
- case "label":
29676
- return {
29677
- type: "label",
29678
- coord: [0, 0],
29679
- text: "",
29680
- color: DEFAULT_COLOR,
29681
- size: "medium"
29682
- };
29683
- default:
29684
- throw new UnreachableCaseError(type);
29685
- }
29686
- }
29687
- function degreeToRadian(degrees) {
29688
- return degrees / 180 * Math.PI;
29689
- }
29690
- function radianToDegree(radians) {
29691
- return radians / Math.PI * 180;
29692
- }
29693
- function getDefaultGraphStartCoords(graph, range, step) {
29694
- switch (graph.type) {
29695
- case "linear":
29696
- case "ray":
29697
- return getLineCoords(_extends({}, graph, {
29698
- startCoords: undefined
29699
- }), range, step);
29700
- case "segment":
29701
- return getSegmentCoords(_extends({}, graph, {
29702
- startCoords: undefined
29703
- }), range, step);
29704
- case "linear-system":
29705
- return getLinearSystemCoords(_extends({}, graph, {
29706
- startCoords: undefined
29707
- }), range, step);
29708
- case "circle":
29709
- const startCoords = getCircleCoords(_extends({}, graph, {
29710
- startCoords: undefined
29711
- }));
29712
- const radius = vector.length(vector.subtract(startCoords.radiusPoint, startCoords.center));
29713
- return {
29714
- center: startCoords.center,
29715
- radius
29716
- };
29717
- case "sinusoid":
29718
- return getSinusoidCoords(_extends({}, graph, {
29719
- startCoords: undefined
29720
- }), range, step);
29721
- case "quadratic":
29722
- return getQuadraticCoords(_extends({}, graph, {
29723
- startCoords: undefined
29724
- }), range, step);
29725
- case "point":
29726
- return getPointCoords(_extends({}, graph, {
29727
- startCoords: undefined
29728
- }), range, step);
29729
- case "polygon":
29730
- return getPolygonCoords(_extends({}, graph, {
29731
- startCoords: undefined
29732
- }), range, step);
29733
- case "angle":
29734
- return getAngleCoords({
29735
- graph: _extends({}, graph, {
29736
- startCoords: undefined
29737
- }),
29738
- range,
29739
- step
29740
- });
29741
- default:
29742
- return undefined;
29743
- }
29636
+ function InteractiveGraphCorrectAnswer(props) {
29637
+ return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Heading, {
29638
+ title: "Correct Answer",
29639
+ isOpen: true,
29640
+ isCollapsible: false
29641
+ }), /*#__PURE__*/React.createElement(View, null, /*#__PURE__*/React.createElement(View, null, /*#__PURE__*/React.createElement(LabelXSmall, {
29642
+ style: {
29643
+ paddingTop: spacing.xxSmall_6,
29644
+ paddingBottom: spacing.xxSmall_6,
29645
+ color: color.offBlack64
29646
+ }
29647
+ }, "Graph the correct answer in the graph below and ensure the equation or point coordinates displayed represent the correct answer."), /*#__PURE__*/React.createElement(BodyMonospace, {
29648
+ style: {
29649
+ fontSize: 12,
29650
+ backgroundColor: "#eee",
29651
+ paddingInline: spacing.xxSmall_6,
29652
+ borderColor: "#ccc",
29653
+ borderStyle: "solid",
29654
+ borderWidth: 1
29655
+ }
29656
+ }, props.equationString)), props.children));
29744
29657
  }
29745
- const getSinusoidEquation = startCoords => {
29746
- // Get coefficients
29747
- // It's assumed that p1 is the root and p2 is the first peak
29748
- const p1 = startCoords[0];
29749
- const p2 = startCoords[1];
29750
-
29751
- // Resulting coefficients are canonical for this sine curve
29752
- const amplitude = p2[1] - p1[1];
29753
- const angularFrequency = Math.PI / (2 * (p2[0] - p1[0]));
29754
- const phase = p1[0] * angularFrequency;
29755
- const verticalOffset = p1[1];
29756
- return "y = " + amplitude.toFixed(3) + "sin(" + angularFrequency.toFixed(3) + "x - " + phase.toFixed(3) + ") + " + verticalOffset.toFixed(3);
29757
- };
29758
- const getQuadraticEquation = startCoords => {
29759
- const p1 = startCoords[0];
29760
- const p2 = startCoords[1];
29761
- const p3 = startCoords[2];
29762
- const denom = (p1[0] - p2[0]) * (p1[0] - p3[0]) * (p2[0] - p3[0]);
29763
- if (denom === 0) {
29764
- // Many of the callers assume that the return value is always defined.
29765
- return "Division by zero error";
29766
- }
29767
- const a = (p3[0] * (p2[1] - p1[1]) + p2[0] * (p1[1] - p3[1]) + p1[0] * (p3[1] - p2[1])) / denom;
29768
- 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;
29769
- 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;
29770
- return "y = " + a.toFixed(3) + "x^2 + " + b.toFixed(3) + "x + " + c.toFixed(3);
29771
- };
29772
- const findAngle = (point1, point2) => {
29773
- const x = point1[0] - point2[0];
29774
- const y = point1[1] - point2[1];
29775
- return (180 + Math.atan2(-y, -x) * 180 / Math.PI + 360) % 360;
29776
- };
29777
- const getAngleEquation = startCoords => {
29778
- const [point1, vertex, point2] = startCoords;
29779
- const angle = findAngle(point2, vertex) - findAngle(point1, vertex);
29780
- const roundedAngle = angle.toFixed(0);
29781
- return `${roundedAngle}\u00B0 angle at (${vertex[0]}, ${vertex[1]})`;
29782
- };
29783
- const shouldShowStartCoordsUI = (graph, isStatic) => {
29784
- // We don't show the start coords UI for static graphs, since
29785
- // the coords shown for this are determined by the coords on the
29786
- // "correct" graph. The start coords would not be used here.
29787
- if (isStatic) {
29788
- return false;
29789
- }
29790
- if (graph.type === "point" && graph.numPoints === "unlimited") {
29791
- return false;
29792
- }
29793
- if (graph.type === "polygon" && (graph.numSides === "unlimited" || graph.snapTo === "angles" || graph.snapTo === "sides")) {
29794
- return false;
29795
- }
29796
- return true;
29797
- };
29798
29658
 
29799
- /**
29800
- * Dropdown for selecting a locked figure to add to an interactive graph.
29801
- * Locked figures are elements (points, segmeents, etc.) that are not
29802
- * interactive, just present in the graph's background.
29803
- *
29804
- * Used in the interactive graph editor's locked figures section.
29805
- */
29806
- const LockedFigureSelect = props => {
29659
+ function InteractiveGraphDescription(props) {
29807
29660
  const {
29808
- id,
29661
+ ariaLabelValue,
29662
+ ariaDescriptionValue,
29809
29663
  onChange
29810
29664
  } = props;
29811
- const figureTypes = ["point", "line", "vector", "ellipse", "polygon", "function"];
29812
- if (props.showLabelsFlag) {
29813
- figureTypes.push("label");
29814
- }
29815
- return /*#__PURE__*/React.createElement(View, {
29816
- style: styles$H.container
29817
- }, /*#__PURE__*/React.createElement(ActionMenu, {
29818
- menuText: "Add locked figure",
29819
- style: styles$H.addElementSelect
29820
- }, figureTypes.map(figureType => /*#__PURE__*/React.createElement(ActionItem, {
29821
- key: `${id}-${figureType}`,
29822
- label: figureType,
29823
- onClick: () => onChange(figureType)
29665
+ const [isOpen, setIsOpen] = React.useState(true);
29666
+ return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Heading, {
29667
+ title: "Description",
29668
+ isCollapsible: true,
29669
+ isOpen: isOpen,
29670
+ onToggle: setIsOpen
29671
+ }), isOpen && /*#__PURE__*/React.createElement(View, null, /*#__PURE__*/React.createElement(LabelXSmall, {
29672
+ style: styles$G.caption
29673
+ }, "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.createElement(LabelLarge, {
29674
+ tag: "label"
29675
+ }, "Title", /*#__PURE__*/React.createElement(TextField, {
29676
+ value: ariaLabelValue,
29677
+ onChange: newValue => onChange({
29678
+ fullGraphAriaLabel: newValue
29679
+ }),
29680
+ style: styles$G.spaceAbove
29681
+ })), /*#__PURE__*/React.createElement(Strut, {
29682
+ size: spacing.small_12
29683
+ }), /*#__PURE__*/React.createElement(LabelLarge, {
29684
+ tag: "label"
29685
+ }, "Description", /*#__PURE__*/React.createElement(TextArea, {
29686
+ rows: 8,
29687
+ resizeType: "vertical",
29688
+ value: ariaDescriptionValue,
29689
+ onChange: newValue => onChange({
29690
+ fullGraphAriaDescription: newValue
29691
+ }),
29692
+ style: styles$G.spaceAbove
29824
29693
  }))));
29825
- };
29826
- const styles$H = StyleSheet.create({
29827
- container: {
29828
- marginTop: spacing.xSmall_8
29694
+ }
29695
+ const styles$G = StyleSheet.create({
29696
+ caption: {
29697
+ color: color.offBlack64,
29698
+ paddingTop: spacing.xxSmall_6,
29699
+ paddingBottom: spacing.xxSmall_6
29829
29700
  },
29830
- addElementSelect: {
29831
- backgroundColor: color.fadedBlue8,
29832
- borderRadius: spacing.xxxSmall_4
29701
+ spaceAbove: {
29702
+ marginTop: spacing.xxxSmall_4
29833
29703
  }
29834
29704
  });
29835
29705
 
29836
- /**
29837
- * This is a custom text field of type="number" for use in Perseus Editors.
29838
- *
29839
- * This makes it so that the text field's input number updates on scroll
29840
- * without scrolling the page.
29841
- *
29842
- * NOTE 1: Native HTML number inputs do not update the number value on scroll,
29843
- * they only scroll the page. Inputs in React do NOT work this way (explanation
29844
- * here: https://stackoverflow.com/a/68266494). By default, scrolling on a
29845
- * focused number input in React causes BOTH the input value to change AND
29846
- * the page to scroll. The behavior in this component is an improvement on
29847
- * the React behavior, but it's the opposite of the native HTML behavior.
29848
- *
29849
- * NOTE 2: Firefox seems to have a custom override for this. Even with this
29850
- * stopPropogation, Firefox matches the native HTML behavior.
29851
- */
29852
- const ScrolllessNumberTextField = props => {
29853
- const inputRef = React.useRef(null);
29854
- React.useEffect(() => {
29855
- const ref = inputRef.current;
29856
-
29857
- // stopPropogation makes it so that the page scroll event is not
29858
- // triggered when the input is focused and the user scrolls.
29859
- // The input value will still change on scroll.
29860
- const ignoreScroll = e => {
29861
- e.stopPropagation();
29862
- };
29863
- ref == null || ref.addEventListener("wheel", ignoreScroll);
29864
- return () => {
29865
- ref == null || ref.removeEventListener("wheel", ignoreScroll);
29866
- };
29867
- }, [inputRef]);
29868
- return /*#__PURE__*/React.createElement(TextField, _extends({
29869
- type: "number"
29870
- }, props, {
29871
- ref: inputRef
29872
- }));
29873
- };
29874
-
29875
- const AngleInput = props => {
29706
+ // Renders the given children in a View laid out horizontally.
29707
+ const LabeledRow = props => {
29876
29708
  const {
29877
- angle,
29878
- onChange
29709
+ children,
29710
+ label,
29711
+ labelSide = "left",
29712
+ style
29879
29713
  } = props;
29880
- const [angleInput, setAngleInput] = React.useState(radianToDegree(angle).toString());
29881
- function handleAngleChange(newValue) {
29882
- // Update the local state (update the input field value).
29883
- setAngleInput(newValue);
29884
-
29885
- // If the new value is not a number, don't update the props.
29886
- // If it's empty, keep the props the same value instead of setting to 0.
29887
- if (isNaN(+newValue) || newValue === "") {
29888
- return;
29889
- }
29890
-
29891
- // Update the graph.
29892
- onChange(degreeToRadian(newValue));
29893
- }
29894
- return /*#__PURE__*/React.createElement(LabelMedium, {
29895
- tag: "label",
29896
- style: styles$G.row
29897
- }, "angle (degrees)", /*#__PURE__*/React.createElement(Strut, {
29898
- size: spacing.xxSmall_6
29899
- }), /*#__PURE__*/React.createElement(ScrolllessNumberTextField, {
29900
- value: angleInput,
29901
- onChange: handleAngleChange,
29902
- style: styles$G.textField
29903
- }), /*#__PURE__*/React.createElement(Strut, {
29904
- size: spacing.xxSmall_6
29905
- }));
29714
+ return /*#__PURE__*/React.createElement("label", {
29715
+ className: css(styles$F.label)
29716
+ }, /*#__PURE__*/React.createElement(View, {
29717
+ style: [styles$F.row, style]
29718
+ }, labelSide === "start" || /*#__PURE__*/React.createElement(LabelSmall, {
29719
+ style: styles$F.spaceEnd
29720
+ }, label), children, labelSide === "end" && /*#__PURE__*/React.createElement(LabelSmall, {
29721
+ style: styles$F.spaceStart
29722
+ }, label)));
29906
29723
  };
29907
- const styles$G = StyleSheet.create({
29724
+ const styles$F = StyleSheet.create({
29725
+ label: {
29726
+ width: "fit-content"
29727
+ },
29908
29728
  row: {
29909
- display: "flex",
29910
29729
  flexDirection: "row",
29911
- alignItems: "center"
29730
+ marginTop: spacing.xSmall_8,
29731
+ alignItems: "center",
29732
+ width: "fit-content"
29912
29733
  },
29913
- textField: {
29914
- width: spacing.xxxLarge_64
29734
+ spaceStart: {
29735
+ marginInlineStart: spacing.xSmall_8
29736
+ },
29737
+ spaceEnd: {
29738
+ marginInlineEnd: spacing.xSmall_8
29915
29739
  }
29916
29740
  });
29917
29741
 
29918
- const CoordinatePairInput = props => {
29919
- const {
29920
- coord,
29921
- labels,
29922
- error,
29742
+ const {
29743
+ ButtonGroup: ButtonGroup$2,
29744
+ InfoTip: InfoTip$d,
29745
+ RangeInput: RangeInput$4
29746
+ } = components;
29747
+ const defaultBackgroundImage$1 = {
29748
+ url: null,
29749
+ width: 0,
29750
+ height: 0
29751
+ };
29752
+ function numSteps(range, step) {
29753
+ return Math.floor((range[1] - range[0]) / step);
29754
+ }
29755
+ class InteractiveGraphSettings extends React.Component {
29756
+ static stateFromProps(props) {
29757
+ return {
29758
+ labelsTextbox: props.labels,
29759
+ gridStepTextbox: props.gridStep,
29760
+ snapStepTextbox: props.snapStep,
29761
+ stepTextbox: props.step,
29762
+ rangeTextbox: props.range,
29763
+ // Shallow-copied clone
29764
+ backgroundImage: _extends({}, props.backgroundImage)
29765
+ };
29766
+ }
29767
+ constructor(props) {
29768
+ super(props);
29769
+ this._isMounted = false;
29770
+ this.bgUrlRef = /*#__PURE__*/React.createRef();
29771
+ this.labelXRef = /*#__PURE__*/React.createRef();
29772
+ this.labelYRef = /*#__PURE__*/React.createRef();
29773
+ this.change = (...args) => {
29774
+ return Changeable.change.apply(this, args);
29775
+ };
29776
+ this.changeBackgroundUrl = e => {
29777
+ var _this$bgUrlRef$curren;
29778
+ // Only continue on blur or "enter"
29779
+ if (e.type === "keypress" && e.key !== "Enter") {
29780
+ return;
29781
+ }
29782
+ const setUrl = (url, width, height) => {
29783
+ // Shallow-copied clone
29784
+ const image = _extends({}, this.props.backgroundImage);
29785
+ image.url = url;
29786
+ image.width = width;
29787
+ image.height = height;
29788
+ this.setState({
29789
+ backgroundImage: image
29790
+ }, this.changeGraph);
29791
+ };
29792
+ const url = (_this$bgUrlRef$curren = this.bgUrlRef.current) == null ? void 0 : _this$bgUrlRef$curren.value;
29793
+ if (url) {
29794
+ Util.getImageSize(url, (width, height) => {
29795
+ if (this._isMounted) {
29796
+ setUrl(url, width, height);
29797
+ }
29798
+ });
29799
+ } else {
29800
+ setUrl(null, 0, 0);
29801
+ }
29802
+ };
29803
+ this.renderLabelChoices = choices => {
29804
+ return choices.map(nameAndValue => /*#__PURE__*/React.createElement("option", {
29805
+ key: nameAndValue[1],
29806
+ value: nameAndValue[1]
29807
+ }, nameAndValue[0]));
29808
+ };
29809
+ this.validRange = range => {
29810
+ const numbers = _.every(range, function (num) {
29811
+ return _.isFinite(num);
29812
+ });
29813
+ if (!numbers) {
29814
+ return "Range must be a valid number";
29815
+ }
29816
+ if (range[0] >= range[1]) {
29817
+ return "Range must have a higher number on the right";
29818
+ }
29819
+ return true;
29820
+ };
29821
+ this.validateStepValue = settings => {
29822
+ const {
29823
+ step,
29824
+ range,
29825
+ name,
29826
+ minTicks,
29827
+ maxTicks
29828
+ } = settings;
29829
+ const nSteps = numSteps(range, step);
29830
+ if (nSteps < minTicks) {
29831
+ return name + " is too large, there must be at least " + minTicks + " ticks.";
29832
+ }
29833
+ if (nSteps > maxTicks) {
29834
+ return name + " is too small, there can be at most " + maxTicks + " ticks.";
29835
+ }
29836
+ return true;
29837
+ };
29838
+ this.validSnapStep = (step, range) => {
29839
+ return this.validateStepValue({
29840
+ step: step,
29841
+ range: range,
29842
+ name: "Snap step",
29843
+ minTicks: 5,
29844
+ maxTicks: 60
29845
+ });
29846
+ };
29847
+ this.validGridStep = (step, range) => {
29848
+ return this.validateStepValue({
29849
+ step: step,
29850
+ range: range,
29851
+ name: "Grid step",
29852
+ minTicks: 3,
29853
+ maxTicks: 60
29854
+ });
29855
+ };
29856
+ this.validStep = (step, range) => {
29857
+ return this.validateStepValue({
29858
+ step: step,
29859
+ range: range,
29860
+ name: "Step",
29861
+ minTicks: 3,
29862
+ maxTicks: 20
29863
+ });
29864
+ };
29865
+ this.validBackgroundImageSize = image => {
29866
+ // Ignore empty images
29867
+ if (!image.url) {
29868
+ return true;
29869
+ }
29870
+ const validSize = image.width <= 450 && image.height <= 450;
29871
+ if (!validSize) {
29872
+ return "Image must be smaller than 450px x 450px.";
29873
+ }
29874
+ return true;
29875
+ };
29876
+ this.validateGraphSettings = (range, step, gridStep, snapStep, image) => {
29877
+ const self = this;
29878
+ let msg;
29879
+ const goodRange = _.every(range, function (range) {
29880
+ msg = self.validRange(range);
29881
+ return msg === true;
29882
+ });
29883
+ if (!goodRange) {
29884
+ return msg;
29885
+ }
29886
+ const goodStep = _.every(step, function (step, i) {
29887
+ msg = self.validStep(step, range[i]);
29888
+ return msg === true;
29889
+ });
29890
+ if (!goodStep) {
29891
+ return msg;
29892
+ }
29893
+ const goodGridStep = _.every(gridStep, function (gridStep, i) {
29894
+ msg = self.validGridStep(gridStep, range[i]);
29895
+ return msg === true;
29896
+ });
29897
+ if (!goodGridStep) {
29898
+ return msg;
29899
+ }
29900
+ const goodSnapStep = _.every(snapStep, function (snapStep, i) {
29901
+ msg = self.validSnapStep(snapStep, range[i]);
29902
+ return msg === true;
29903
+ });
29904
+ if (!goodSnapStep) {
29905
+ return msg;
29906
+ }
29907
+ const goodImageSize = this.validBackgroundImageSize(image);
29908
+ if (goodImageSize !== true) {
29909
+ msg = goodImageSize;
29910
+ return msg;
29911
+ }
29912
+ return true;
29913
+ };
29914
+ this.changeLabel = (i, e) => {
29915
+ const val = e.target.value;
29916
+ const labels = this.state.labelsTextbox.slice();
29917
+ labels[i] = val;
29918
+ this.setState({
29919
+ labelsTextbox: labels
29920
+ }, this.changeGraph);
29921
+ };
29922
+ this.changeRange = (i, values) => {
29923
+ const ranges = this.state.rangeTextbox.slice();
29924
+ ranges[i] = values;
29925
+ const step = this.state.stepTextbox.slice();
29926
+ const gridStep = this.state.gridStepTextbox.slice();
29927
+ const snapStep = this.state.snapStepTextbox.slice();
29928
+ const scale = Util.scaleFromExtent(ranges[i], this.props.box[i]);
29929
+ if (this.validRange(ranges[i]) === true) {
29930
+ step[i] = Util.tickStepFromExtent(ranges[i], this.props.box[i]);
29931
+ const gridStepValue = Util.gridStepFromTickStep(step[i], scale);
29932
+ if (gridStepValue) {
29933
+ gridStep[i] = gridStepValue;
29934
+ }
29935
+ snapStep[i] = gridStep[i] / 2;
29936
+ }
29937
+ this.setState({
29938
+ stepTextbox: step,
29939
+ gridStepTextbox: gridStep,
29940
+ snapStepTextbox: snapStep,
29941
+ rangeTextbox: ranges
29942
+ }, this.changeGraph);
29943
+ };
29944
+ this.changeStep = step => {
29945
+ this.setState({
29946
+ stepTextbox: step
29947
+ }, this.changeGraph);
29948
+ };
29949
+ this.changeSnapStep = snapStep => {
29950
+ this.setState({
29951
+ snapStepTextbox: snapStep
29952
+ }, this.changeGraph);
29953
+ };
29954
+ this.changeGridStep = gridStep => {
29955
+ this.setState({
29956
+ gridStepTextbox: gridStep,
29957
+ snapStepTextbox: _.map(gridStep, function (step) {
29958
+ return step / 2;
29959
+ })
29960
+ }, this.changeGraph);
29961
+ };
29962
+ this.changeGraph = () => {
29963
+ const labels = this.state.labelsTextbox;
29964
+ const range = _.map(this.state.rangeTextbox, function (range) {
29965
+ return _.map(range, Number);
29966
+ });
29967
+ const step = _.map(this.state.stepTextbox, Number);
29968
+ const gridStep = this.state.gridStepTextbox;
29969
+ const snapStep = this.state.snapStepTextbox;
29970
+ const image = this.state.backgroundImage;
29971
+
29972
+ // validationResult is either:
29973
+ // true -> the settings are valid
29974
+ // a string -> the settings are invalid, and the explanation
29975
+ // is contained in the string
29976
+ // TODO(aria): Refactor this to not be confusing
29977
+ const validationResult = this.validateGraphSettings(range, step, gridStep, snapStep, image);
29978
+ if (validationResult === true) {
29979
+ // either true or a string
29980
+ this.change({
29981
+ valid: true,
29982
+ labels: labels,
29983
+ range: range,
29984
+ step: step,
29985
+ gridStep: gridStep,
29986
+ snapStep: snapStep,
29987
+ backgroundImage: image
29988
+ });
29989
+ } else {
29990
+ this.change({
29991
+ valid: validationResult // a string message, not false
29992
+ });
29993
+ }
29994
+ };
29995
+ this.state = _extends({
29996
+ isExpanded: true
29997
+ }, InteractiveGraphSettings.stateFromProps(props));
29998
+ }
29999
+ componentDidMount() {
30000
+ // TODO(scottgrant): This is a hack to remove the deprecated call to
30001
+ // this.isMounted() but is still considered an anti-pattern.
30002
+ this._isMounted = true;
30003
+ this.changeGraph = _.debounce(this.changeGraph, 300);
30004
+ }
30005
+ UNSAFE_componentWillReceiveProps(nextProps) {
30006
+ // Make sure that state updates when switching
30007
+ // between different items in a multi-item editor.
30008
+ if (!_.isEqual(this.props.labels, nextProps.labels) || !_.isEqual(this.props.gridStep, nextProps.gridStep) || !_.isEqual(this.props.snapStep, nextProps.snapStep) || !_.isEqual(this.props.step, nextProps.step) || !_.isEqual(this.props.range, nextProps.range) || !_.isEqual(this.props.backgroundImage, nextProps.backgroundImage)) {
30009
+ this.setState(InteractiveGraphSettings.stateFromProps(nextProps));
30010
+ }
30011
+ }
30012
+ componentWillUnmount() {
30013
+ this._isMounted = false;
30014
+ }
30015
+ render() {
30016
+ return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Heading, {
30017
+ title: "Common Graph Settings",
30018
+ isOpen: this.state.isExpanded,
30019
+ isCollapsible: true,
30020
+ onToggle: () => this.setState({
30021
+ isExpanded: !this.state.isExpanded
30022
+ })
30023
+ }), this.state.isExpanded && /*#__PURE__*/React.createElement(View, null, /*#__PURE__*/React.createElement("div", {
30024
+ className: "graph-settings"
30025
+ }, /*#__PURE__*/React.createElement("div", {
30026
+ className: "perseus-widget-row"
30027
+ }, /*#__PURE__*/React.createElement("div", {
30028
+ className: "perseus-widget-left-col"
30029
+ }, /*#__PURE__*/React.createElement(LabeledRow, {
30030
+ label: "x Label"
30031
+ }, /*#__PURE__*/React.createElement("input", {
30032
+ type: "text",
30033
+ className: "graph-settings-axis-label",
30034
+ ref: this.labelXRef,
30035
+ onChange: e => this.changeLabel(0, e),
30036
+ value: this.state.labelsTextbox[0] || ""
30037
+ }))), /*#__PURE__*/React.createElement("div", {
30038
+ className: "perseus-widget-right-col"
30039
+ }, /*#__PURE__*/React.createElement(LabeledRow, {
30040
+ label: "y Label"
30041
+ }, /*#__PURE__*/React.createElement("input", {
30042
+ type: "text",
30043
+ className: "graph-settings-axis-label",
30044
+ ref: this.labelYRef,
30045
+ onChange: e => this.changeLabel(1, e),
30046
+ value: this.state.labelsTextbox[1] || ""
30047
+ })))), /*#__PURE__*/React.createElement("div", {
30048
+ className: "perseus-widget-row"
30049
+ }, /*#__PURE__*/React.createElement("div", {
30050
+ className: "perseus-widget-left-col"
30051
+ }, /*#__PURE__*/React.createElement(LabeledRow, {
30052
+ label: "x Range"
30053
+ }, /*#__PURE__*/React.createElement(RangeInput$4, {
30054
+ value: this.state.rangeTextbox[0],
30055
+ onChange: vals => this.changeRange(0, vals)
30056
+ }))), /*#__PURE__*/React.createElement("div", {
30057
+ className: "perseus-widget-right-col"
30058
+ }, /*#__PURE__*/React.createElement(LabeledRow, {
30059
+ label: "y Range"
30060
+ }, /*#__PURE__*/React.createElement(RangeInput$4, {
30061
+ value: this.state.rangeTextbox[1],
30062
+ onChange: vals => this.changeRange(1, vals)
30063
+ })))), /*#__PURE__*/React.createElement("div", {
30064
+ className: "perseus-widget-row"
30065
+ }, /*#__PURE__*/React.createElement("div", {
30066
+ className: "perseus-widget-left-col"
30067
+ }, /*#__PURE__*/React.createElement(LabeledRow, {
30068
+ label: "Tick Step"
30069
+ }, /*#__PURE__*/React.createElement(RangeInput$4, {
30070
+ value: this.state.stepTextbox,
30071
+ onChange: this.changeStep
30072
+ }))), /*#__PURE__*/React.createElement("div", {
30073
+ className: "perseus-widget-right-col"
30074
+ }, /*#__PURE__*/React.createElement(LabeledRow, {
30075
+ label: "Grid Step"
30076
+ }, /*#__PURE__*/React.createElement(RangeInput$4, {
30077
+ value: this.state.gridStepTextbox,
30078
+ onChange: this.changeGridStep
30079
+ })))), /*#__PURE__*/React.createElement("div", {
30080
+ className: "perseus-widget-row"
30081
+ }, /*#__PURE__*/React.createElement("div", {
30082
+ className: "perseus-widget-left-col"
30083
+ }, /*#__PURE__*/React.createElement(LabeledRow, {
30084
+ label: "Snap Step"
30085
+ }, /*#__PURE__*/React.createElement(RangeInput$4, {
30086
+ value: this.state.snapStepTextbox,
30087
+ onChange: this.changeSnapStep
30088
+ })))), /*#__PURE__*/React.createElement("div", {
30089
+ className: "perseus-widget-row"
30090
+ }, /*#__PURE__*/React.createElement(LabeledRow, {
30091
+ label: "Markings:"
30092
+ }, /*#__PURE__*/React.createElement(ButtonGroup$2, {
30093
+ value: this.props.markings,
30094
+ allowEmpty: false,
30095
+ buttons: [{
30096
+ value: "graph",
30097
+ content: "Graph"
30098
+ }, {
30099
+ value: "grid",
30100
+ content: "Grid"
30101
+ }, {
30102
+ value: "none",
30103
+ content: "None"
30104
+ }],
30105
+ onChange: this.change("markings")
30106
+ }))), /*#__PURE__*/React.createElement("div", {
30107
+ className: "perseus-widget-left-col"
30108
+ }, /*#__PURE__*/React.createElement(Checkbox$1, {
30109
+ label: "Show tooltips",
30110
+ checked: this.props.showTooltips,
30111
+ onChange: value => {
30112
+ this.change({
30113
+ showTooltips: value
30114
+ });
30115
+ }
30116
+ }))), /*#__PURE__*/React.createElement(LabeledRow, {
30117
+ label: "Background image URL:",
30118
+ style: styles$E.resetSpaceTop
30119
+ }, /*#__PURE__*/React.createElement("input", {
30120
+ type: "text",
30121
+ className: css(styles$E.backgroundUrlInput),
30122
+ ref: this.bgUrlRef,
30123
+ value: this.state.backgroundImage.url || "",
30124
+ onChange: e => {
30125
+ // Shallow-copied clone
30126
+ const image = _extends({}, this.props.backgroundImage);
30127
+ image.url = e.target.value;
30128
+ this.setState({
30129
+ backgroundImage: image
30130
+ });
30131
+ },
30132
+ onKeyPress: this.changeBackgroundUrl,
30133
+ onBlur: this.changeBackgroundUrl
30134
+ }), /*#__PURE__*/React.createElement(InfoTip$d, null, /*#__PURE__*/React.createElement("p", null, "Create an image in graphie, or use the \"Add image\" function to create a background."))), /*#__PURE__*/React.createElement(View, {
30135
+ style: styles$E.protractorSection
30136
+ }, /*#__PURE__*/React.createElement(View, {
30137
+ style: styles$E.checkboxRow
30138
+ }, /*#__PURE__*/React.createElement(Checkbox$1, {
30139
+ label: "Show protractor",
30140
+ checked: this.props.showProtractor,
30141
+ onChange: value => {
30142
+ this.change({
30143
+ showProtractor: value
30144
+ });
30145
+ },
30146
+ style: styles$E.resetSpaceTop
30147
+ })), this.props.showProtractor && /*#__PURE__*/React.createElement(Banner, {
30148
+ layout: "floating",
30149
+ text: "The protractor is not accessible. Please consider an alternate approach.",
30150
+ kind: "warning"
30151
+ }))));
30152
+ }
30153
+ }
30154
+ InteractiveGraphSettings.defaultProps = {
30155
+ box: [interactiveSizes.defaultBoxSizeSmall, interactiveSizes.defaultBoxSizeSmall],
30156
+ labels: ["x", "y"],
30157
+ range: [[-10, 10], [-10, 10]],
30158
+ step: [1, 1],
30159
+ gridStep: [1, 1],
30160
+ snapStep: [1, 1],
30161
+ valid: true,
30162
+ backgroundImage: defaultBackgroundImage$1,
30163
+ markings: "graph",
30164
+ showProtractor: false,
30165
+ showTooltips: false
30166
+ };
30167
+ const styles$E = StyleSheet.create({
30168
+ resetSpaceTop: {
30169
+ marginTop: 0
30170
+ },
30171
+ backgroundUrlInput: {
30172
+ border: `1px solid ${color.offBlack32}`,
30173
+ borderRadius: spacing.xxxSmall_4,
30174
+ padding: spacing.xxxSmall_4
30175
+ },
30176
+ checkboxRow: {
30177
+ flexDirection: "row",
30178
+ alignItems: "center",
30179
+ justifyContent: "space-between",
30180
+ marginBottom: spacing.xSmall_8
30181
+ },
30182
+ protractorSection: {
30183
+ marginTop: spacing.xSmall_8,
30184
+ borderTop: `1px solid ${color.offBlack16}`,
30185
+ paddingTop: spacing.xSmall_8,
30186
+ paddingBottom: spacing.xSmall_8,
30187
+ borderBottom: `1px solid ${color.offBlack16}`
30188
+ }
30189
+ });
30190
+
30191
+ const SegmentCountSelector = ({
30192
+ numSegments: _numSegments = 1,
30193
+ onChange
30194
+ }) => /*#__PURE__*/React.createElement(SingleSelect, {
30195
+ key: "segment-select",
30196
+ selectedValue: `${_numSegments}`
30197
+ // Never uses placeholder, always has value
30198
+ ,
30199
+ placeholder: "",
30200
+ onChange: newValue => {
30201
+ const num = +newValue;
30202
+ onChange(num);
30203
+ },
30204
+ style: styles$D.singleSelectShort
30205
+ }, _.range(1, 7).map(n => /*#__PURE__*/React.createElement(OptionItem, {
30206
+ key: n,
30207
+ value: `${n}`,
30208
+ label: `${n} segment${n > 1 ? "s" : ""}`
30209
+ })));
30210
+ const styles$D = StyleSheet.create({
30211
+ singleSelectShort: {
30212
+ // Non-standard spacing, but it's the smallest we can go
30213
+ // without running into styling issues with the dropdown.
30214
+ height: 26
30215
+ }
30216
+ });
30217
+
30218
+ /**
30219
+ * Dropdown for selecting a locked figure to add to an interactive graph.
30220
+ * Locked figures are elements (points, segmeents, etc.) that are not
30221
+ * interactive, just present in the graph's background.
30222
+ *
30223
+ * Used in the interactive graph editor's locked figures section.
30224
+ */
30225
+ const LockedFigureSelect = props => {
30226
+ const {
30227
+ id,
30228
+ onChange
30229
+ } = props;
30230
+ const figureTypes = ["point", "line", "vector", "ellipse", "polygon", "function"];
30231
+ if (props.showLabelsFlag) {
30232
+ figureTypes.push("label");
30233
+ }
30234
+ return /*#__PURE__*/React.createElement(View, {
30235
+ style: styles$C.container
30236
+ }, /*#__PURE__*/React.createElement(ActionMenu, {
30237
+ menuText: "Add locked figure",
30238
+ style: styles$C.addElementSelect
30239
+ }, figureTypes.map(figureType => /*#__PURE__*/React.createElement(ActionItem, {
30240
+ key: `${id}-${figureType}`,
30241
+ label: figureType,
30242
+ onClick: () => onChange(figureType)
30243
+ }))));
30244
+ };
30245
+ const styles$C = StyleSheet.create({
30246
+ container: {
30247
+ marginTop: spacing.xSmall_8
30248
+ },
30249
+ addElementSelect: {
30250
+ backgroundColor: color.fadedBlue8,
30251
+ borderRadius: spacing.xxxSmall_4
30252
+ }
30253
+ });
30254
+
30255
+ /**
30256
+ * This is a custom text field of type="number" for use in Perseus Editors.
30257
+ *
30258
+ * This makes it so that the text field's input number updates on scroll
30259
+ * without scrolling the page.
30260
+ *
30261
+ * NOTE 1: Native HTML number inputs do not update the number value on scroll,
30262
+ * they only scroll the page. Inputs in React do NOT work this way (explanation
30263
+ * here: https://stackoverflow.com/a/68266494). By default, scrolling on a
30264
+ * focused number input in React causes BOTH the input value to change AND
30265
+ * the page to scroll. The behavior in this component is an improvement on
30266
+ * the React behavior, but it's the opposite of the native HTML behavior.
30267
+ *
30268
+ * NOTE 2: Firefox seems to have a custom override for this. Even with this
30269
+ * stopPropogation, Firefox matches the native HTML behavior.
30270
+ */
30271
+ const ScrolllessNumberTextField = props => {
30272
+ const inputRef = React.useRef(null);
30273
+ React.useEffect(() => {
30274
+ const ref = inputRef.current;
30275
+
30276
+ // stopPropogation makes it so that the page scroll event is not
30277
+ // triggered when the input is focused and the user scrolls.
30278
+ // The input value will still change on scroll.
30279
+ const ignoreScroll = e => {
30280
+ e.stopPropagation();
30281
+ };
30282
+ ref == null || ref.addEventListener("wheel", ignoreScroll);
30283
+ return () => {
30284
+ ref == null || ref.removeEventListener("wheel", ignoreScroll);
30285
+ };
30286
+ }, [inputRef]);
30287
+ return /*#__PURE__*/React.createElement(TextField, _extends({
30288
+ type: "number"
30289
+ }, props, {
30290
+ ref: inputRef
30291
+ }));
30292
+ };
30293
+
30294
+ function focusWithChromeStickyFocusBugWorkaround(element) {
30295
+ // NOTE(marksandstrom) Chrome sticky focus bug.
30296
+ //
30297
+ // There is a bug in Chrome related to sticky positioning and
30298
+ // focus: When a child of an element with sticky positioning is
30299
+ // focused with the focus() method, the scrollable area
30300
+ // containing that element will scroll to the top (even though
30301
+ // the element is already visble on the page). For a dropdown
30302
+ // this means that the area will be scrolled to the top when
30303
+ // the user interacts with the dropdown (since we actively
30304
+ // manage dropdown header and option focus for keyboard users).
30305
+ //
30306
+ // To work around this bug the `preventScroll` focus option is
30307
+ // used.
30308
+ //
30309
+ // Using `focusOptions` is an experimental browser feature
30310
+ // "that should not be used in production code". However, since
30311
+ // Chrome is the only browser we support that exhibits this
30312
+ // buggy behavior and using `focusOptions` works around the
30313
+ // bug, it is perfectly reasonable to use the feature in this
30314
+ // case. We are not relying on the effect of the
30315
+ // `preventScroll` option otherwise.
30316
+ //
30317
+ // focusOptions documentation:
30318
+ //
30319
+ // https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus
30320
+ //
30321
+ // Note that there could be cases where we want default focus() behavior.
30322
+ // One that comes to mind is a dropdown with a long list of options that
30323
+ // might be rendered off screen when opened. With this workaround the
30324
+ // selected option would not be scrolled into view if the option is near
30325
+ // the end of the list. We're not aware of any uses of the dropdown where
30326
+ // this is an issue, however, so there isn't currently a way to out of the
30327
+ // `preventScroll` behavior.
30328
+
30329
+ // @ts-expect-error - TS2339 - Property 'focus' does not exist on type 'Element'.
30330
+ element.focus({
30331
+ preventScroll: true
30332
+ });
30333
+ }
30334
+ function degreeToRadian(degrees) {
30335
+ return degrees / 180 * Math.PI;
30336
+ }
30337
+ function radianToDegree(radians) {
30338
+ return radians / Math.PI * 180;
30339
+ }
30340
+
30341
+ const AngleInput = props => {
30342
+ const {
30343
+ angle,
30344
+ onChange
30345
+ } = props;
30346
+ const [angleInput, setAngleInput] = React.useState(radianToDegree(angle).toString());
30347
+ function handleAngleChange(newValue) {
30348
+ // Update the local state (update the input field value).
30349
+ setAngleInput(newValue);
30350
+
30351
+ // If the new value is not a number, don't update the props.
30352
+ // If it's empty, keep the props the same value instead of setting to 0.
30353
+ if (isNaN(+newValue) || newValue === "") {
30354
+ return;
30355
+ }
30356
+
30357
+ // Update the graph.
30358
+ onChange(degreeToRadian(newValue));
30359
+ }
30360
+ return /*#__PURE__*/React.createElement(LabelMedium, {
30361
+ tag: "label",
30362
+ style: styles$B.row
30363
+ }, "angle (degrees)", /*#__PURE__*/React.createElement(Strut, {
30364
+ size: spacing.xxSmall_6
30365
+ }), /*#__PURE__*/React.createElement(ScrolllessNumberTextField, {
30366
+ value: angleInput,
30367
+ onChange: handleAngleChange,
30368
+ style: styles$B.textField
30369
+ }), /*#__PURE__*/React.createElement(Strut, {
30370
+ size: spacing.xxSmall_6
30371
+ }));
30372
+ };
30373
+ const styles$B = StyleSheet.create({
30374
+ row: {
30375
+ display: "flex",
30376
+ flexDirection: "row",
30377
+ alignItems: "center"
30378
+ },
30379
+ textField: {
30380
+ width: spacing.xxxLarge_64
30381
+ }
30382
+ });
30383
+
30384
+ const CoordinatePairInput = props => {
30385
+ const {
30386
+ coord,
30387
+ labels,
30388
+ error,
29923
30389
  style,
29924
30390
  onChange
29925
30391
  } = props;
@@ -29953,30 +30419,30 @@ const CoordinatePairInput = props => {
29953
30419
  onChange(newCoords);
29954
30420
  }
29955
30421
  return /*#__PURE__*/React.createElement(View, {
29956
- style: [styles$F.row, style]
30422
+ style: [styles$A.row, style]
29957
30423
  }, /*#__PURE__*/React.createElement(LabelMedium, {
29958
30424
  tag: "label",
29959
- style: styles$F.row
30425
+ style: styles$A.row
29960
30426
  }, labels ? labels[0] : "x coord", /*#__PURE__*/React.createElement(Strut, {
29961
30427
  size: spacing.xxSmall_6
29962
30428
  }), /*#__PURE__*/React.createElement(ScrolllessNumberTextField, {
29963
30429
  value: coordState[0],
29964
30430
  onChange: newValue => handleCoordChange(newValue, 0),
29965
- style: [styles$F.textField, error ? styles$F.errorField : undefined]
30431
+ style: [styles$A.textField, error ? styles$A.errorField : undefined]
29966
30432
  })), /*#__PURE__*/React.createElement(Strut, {
29967
30433
  size: spacing.medium_16
29968
30434
  }), /*#__PURE__*/React.createElement(LabelMedium, {
29969
30435
  tag: "label",
29970
- style: styles$F.row
30436
+ style: styles$A.row
29971
30437
  }, labels ? labels[1] : "y coord", /*#__PURE__*/React.createElement(Strut, {
29972
30438
  size: spacing.xxSmall_6
29973
30439
  }), /*#__PURE__*/React.createElement(ScrolllessNumberTextField, {
29974
30440
  value: coordState[1],
29975
30441
  onChange: newValue => handleCoordChange(newValue, 1),
29976
- style: [styles$F.textField, error ? styles$F.errorField : undefined]
30442
+ style: [styles$A.textField, error ? styles$A.errorField : undefined]
29977
30443
  })));
29978
30444
  };
29979
- const styles$F = StyleSheet.create({
30445
+ const styles$A = StyleSheet.create({
29980
30446
  row: {
29981
30447
  display: "flex",
29982
30448
  flexDirection: "row",
@@ -30009,14 +30475,14 @@ const PerseusEditorAccordion = props => {
30009
30475
  }, /*#__PURE__*/React.createElement(AccordionSection, {
30010
30476
  expanded: expanded,
30011
30477
  onToggle: onToggle,
30012
- style: [styles$E.container, containerStyle],
30013
- headerStyle: styles$E.accordionHeader,
30478
+ style: [styles$z.container, containerStyle],
30479
+ headerStyle: styles$z.accordionHeader,
30014
30480
  header: header
30015
30481
  }, /*#__PURE__*/React.createElement(View, {
30016
- style: [styles$E.accordionPanel, panelStyle]
30482
+ style: [styles$z.accordionPanel, panelStyle]
30017
30483
  }, children)));
30018
30484
  };
30019
- const styles$E = StyleSheet.create({
30485
+ const styles$z = StyleSheet.create({
30020
30486
  container: {
30021
30487
  backgroundColor: color.fadedBlue8,
30022
30488
  marginTop: spacing.xSmall_8
@@ -30047,13 +30513,13 @@ const ColorSwatch = props => {
30047
30513
  "aria-label":
30048
30514
  // Don't include an aria label for decorative circles.
30049
30515
  !decorative ? `${color$1}, ${filled ? "filled" : "open"}` : undefined,
30050
- style: [styles$D.colorSwatch, {
30516
+ style: [styles$y.colorSwatch, {
30051
30517
  border: `4px solid ${lockedFigureColors[color$1]}`,
30052
30518
  backgroundColor: filled ? lockedFigureColors[color$1] : color.white
30053
30519
  }]
30054
30520
  });
30055
30521
  };
30056
- const styles$D = StyleSheet.create({
30522
+ const styles$y = StyleSheet.create({
30057
30523
  colorSwatch: {
30058
30524
  // Add a white outline so that the color swatch is visible when
30059
30525
  // the dropdown option is highlighted with its blue background.
@@ -30072,10 +30538,10 @@ const ColorSelect = props => {
30072
30538
  onChange
30073
30539
  } = props;
30074
30540
  return /*#__PURE__*/React.createElement(View, {
30075
- style: [styles$C.row, style]
30541
+ style: [styles$x.row, style]
30076
30542
  }, /*#__PURE__*/React.createElement(LabelMedium, {
30077
30543
  tag: "label",
30078
- style: styles$C.row
30544
+ style: styles$x.row
30079
30545
  }, "color", /*#__PURE__*/React.createElement(Strut, {
30080
30546
  size: spacing.xxSmall_6
30081
30547
  }), /*#__PURE__*/React.createElement(SingleSelect, {
@@ -30094,7 +30560,7 @@ const ColorSelect = props => {
30094
30560
  })
30095
30561
  })))));
30096
30562
  };
30097
- const styles$C = StyleSheet.create({
30563
+ const styles$x = StyleSheet.create({
30098
30564
  row: {
30099
30565
  display: "flex",
30100
30566
  flexDirection: "row",
@@ -30113,17 +30579,17 @@ const EllipseSwatch = props => {
30113
30579
  } = props;
30114
30580
  return /*#__PURE__*/React.createElement(View, {
30115
30581
  "aria-label": `${color}, stroke ${strokeStyle}, fill ${fillStyle}`,
30116
- style: [styles$B.container, {
30582
+ style: [styles$w.container, {
30117
30583
  border: `4px ${strokeStyle} ${lockedFigureColors[color]}`
30118
30584
  }]
30119
30585
  }, /*#__PURE__*/React.createElement(View, {
30120
- style: [styles$B.innerCircle, {
30586
+ style: [styles$w.innerCircle, {
30121
30587
  backgroundColor: lockedFigureColors[color],
30122
30588
  opacity: fillStyle === "white" ? 0 : lockedFigureFillStyles[fillStyle]
30123
30589
  }]
30124
30590
  }));
30125
30591
  };
30126
- const styles$B = StyleSheet.create({
30592
+ const styles$w = StyleSheet.create({
30127
30593
  container: {
30128
30594
  // Add a white outline so that the color swatch is visible when
30129
30595
  // the dropdown option is highlighted with its blue background.
@@ -30149,7 +30615,7 @@ const LineStrokeSelect = props => {
30149
30615
  } = props;
30150
30616
  return /*#__PURE__*/React.createElement(LabelMedium, {
30151
30617
  tag: "label",
30152
- style: styles$A.lineStrokeSelect
30618
+ style: styles$v.lineStrokeSelect
30153
30619
  }, "stroke", /*#__PURE__*/React.createElement(Strut, {
30154
30620
  size: spacing.xxxSmall_4
30155
30621
  }), /*#__PURE__*/React.createElement(SingleSelect, {
@@ -30166,7 +30632,7 @@ const LineStrokeSelect = props => {
30166
30632
  label: "dashed"
30167
30633
  })));
30168
30634
  };
30169
- const styles$A = StyleSheet.create({
30635
+ const styles$v = StyleSheet.create({
30170
30636
  lineStrokeSelect: {
30171
30637
  display: "flex",
30172
30638
  flexDirection: "row",
@@ -30188,40 +30654,40 @@ const LockedFigureSettingsActions = props => {
30188
30654
  onRemove
30189
30655
  } = props;
30190
30656
  return /*#__PURE__*/React.createElement(View, {
30191
- style: styles$z.container
30657
+ style: styles$u.container
30192
30658
  }, /*#__PURE__*/React.createElement(Button, {
30193
30659
  startIcon: trashIcon,
30194
30660
  "aria-label": `Delete locked ${figureType}`,
30195
30661
  onClick: onRemove,
30196
30662
  kind: "tertiary",
30197
- style: styles$z.deleteButton
30198
- }, "Delete"), /*#__PURE__*/React.createElement(Spring, null), /*#__PURE__*/React.createElement(IconButton, {
30663
+ style: styles$u.deleteButton
30664
+ }, "Delete"), onMove && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Spring, null), /*#__PURE__*/React.createElement(IconButton, {
30199
30665
  icon: caretDoubleUpIcon,
30200
30666
  size: "small",
30201
30667
  "aria-label": `Move locked ${figureType} to the back`,
30202
30668
  onClick: () => onMove("back"),
30203
- style: styles$z.iconButton
30669
+ style: styles$u.iconButton
30204
30670
  }), /*#__PURE__*/React.createElement(IconButton, {
30205
30671
  icon: caretUpIcon,
30206
30672
  size: "small",
30207
30673
  "aria-label": `Move locked ${figureType} backward`,
30208
30674
  onClick: () => onMove("backward"),
30209
- style: styles$z.iconButton
30675
+ style: styles$u.iconButton
30210
30676
  }), /*#__PURE__*/React.createElement(IconButton, {
30211
30677
  icon: caretDownIcon,
30212
30678
  size: "small",
30213
30679
  "aria-label": `Move locked ${figureType} forward`,
30214
30680
  onClick: () => onMove("forward"),
30215
- style: styles$z.iconButton
30681
+ style: styles$u.iconButton
30216
30682
  }), /*#__PURE__*/React.createElement(IconButton, {
30217
30683
  icon: caretDoubleDownIcon,
30218
30684
  size: "small",
30219
30685
  "aria-label": `Move locked ${figureType} to the front`,
30220
30686
  onClick: () => onMove("front"),
30221
- style: styles$z.iconButton
30222
- }));
30687
+ style: styles$u.iconButton
30688
+ })));
30223
30689
  };
30224
- const styles$z = StyleSheet.create({
30690
+ const styles$u = StyleSheet.create({
30225
30691
  container: {
30226
30692
  width: "100%",
30227
30693
  flexDirection: "row",
@@ -30240,7 +30706,7 @@ const styles$z = StyleSheet.create({
30240
30706
  });
30241
30707
 
30242
30708
  const {
30243
- InfoTip: InfoTip$d
30709
+ InfoTip: InfoTip$c
30244
30710
  } = components;
30245
30711
  const LockedEllipseSettings = props => {
30246
30712
  const {
@@ -30268,7 +30734,7 @@ const LockedEllipseSettings = props => {
30268
30734
  /*#__PURE__*/
30269
30735
  // Summary: Ellipse, center, radius, color (opacity, dashed)
30270
30736
  React.createElement(View, {
30271
- style: styles$y.row
30737
+ style: styles$t.row
30272
30738
  }, /*#__PURE__*/React.createElement(LabelLarge, null, `Ellipse (${center[0]}, ${center[1]}), radius ${radius[0]}, ${radius[1]}`), /*#__PURE__*/React.createElement(Strut, {
30273
30739
  size: spacing.xSmall_8
30274
30740
  }), /*#__PURE__*/React.createElement(EllipseSwatch, {
@@ -30277,19 +30743,19 @@ const LockedEllipseSettings = props => {
30277
30743
  strokeStyle: strokeStyle
30278
30744
  }))
30279
30745
  }, /*#__PURE__*/React.createElement(View, {
30280
- style: styles$y.row
30746
+ style: styles$t.row
30281
30747
  }, /*#__PURE__*/React.createElement(CoordinatePairInput, {
30282
30748
  coord: center,
30283
- style: styles$y.spaceUnder,
30749
+ style: styles$t.spaceUnder,
30284
30750
  onChange: newCoords => onChangeProps({
30285
30751
  center: newCoords
30286
30752
  })
30287
30753
  }), /*#__PURE__*/React.createElement(View, {
30288
- style: styles$y.spaceUnder
30289
- }, /*#__PURE__*/React.createElement(InfoTip$d, null, "The coordinates for the center of the ellipse."))), /*#__PURE__*/React.createElement(CoordinatePairInput, {
30754
+ style: styles$t.spaceUnder
30755
+ }, /*#__PURE__*/React.createElement(InfoTip$c, null, "The coordinates for the center of the ellipse."))), /*#__PURE__*/React.createElement(CoordinatePairInput, {
30290
30756
  coord: radius,
30291
30757
  labels: ["x radius", "y radius"],
30292
- style: styles$y.spaceUnder,
30758
+ style: styles$t.spaceUnder,
30293
30759
  onChange: newCoords => onChangeProps({
30294
30760
  radius: newCoords
30295
30761
  })
@@ -30301,7 +30767,7 @@ const LockedEllipseSettings = props => {
30301
30767
  }), /*#__PURE__*/React.createElement(Strut, {
30302
30768
  size: spacing.xSmall_8
30303
30769
  }), /*#__PURE__*/React.createElement(View, {
30304
- style: [styles$y.row, styles$y.spaceUnder]
30770
+ style: [styles$t.row, styles$t.spaceUnder]
30305
30771
  }, /*#__PURE__*/React.createElement(ColorSelect, {
30306
30772
  selectedValue: color,
30307
30773
  onChange: handleColorChange
@@ -30309,7 +30775,7 @@ const LockedEllipseSettings = props => {
30309
30775
  size: spacing.medium_16
30310
30776
  }), /*#__PURE__*/React.createElement(LabelMedium, {
30311
30777
  tag: "label",
30312
- style: [styles$y.row, styles$y.truncatedWidth]
30778
+ style: [styles$t.row, styles$t.truncatedWidth]
30313
30779
  }, "fill", /*#__PURE__*/React.createElement(Strut, {
30314
30780
  size: spacing.xxSmall_6
30315
30781
  }), /*#__PURE__*/React.createElement(SingleSelect, {
@@ -30335,7 +30801,7 @@ const LockedEllipseSettings = props => {
30335
30801
  onRemove: onRemove
30336
30802
  }));
30337
30803
  };
30338
- const styles$y = StyleSheet.create({
30804
+ const styles$t = StyleSheet.create({
30339
30805
  row: {
30340
30806
  display: "flex",
30341
30807
  flexDirection: "row",
@@ -30356,15 +30822,15 @@ const LineSwatch = props => {
30356
30822
  lineStyle
30357
30823
  } = props;
30358
30824
  return /*#__PURE__*/React.createElement(View, {
30359
- style: styles$x.container
30825
+ style: styles$s.container
30360
30826
  }, /*#__PURE__*/React.createElement(View, {
30361
30827
  "aria-label": `${color}, ${lineStyle}`,
30362
- style: [styles$x.lineSwatch, {
30828
+ style: [styles$s.lineSwatch, {
30363
30829
  border: `5px ${lineStyle} ${lockedFigureColors[color]}`
30364
30830
  }]
30365
30831
  }));
30366
30832
  };
30367
- const styles$x = StyleSheet.create({
30833
+ const styles$s = StyleSheet.create({
30368
30834
  container: {
30369
30835
  backgroundColor: color.white,
30370
30836
  justifyContent: "center",
@@ -30449,9 +30915,9 @@ const LockedFunctionSettings = props => {
30449
30915
  expanded: props.expanded,
30450
30916
  onToggle: props.onToggle,
30451
30917
  header: /*#__PURE__*/React.createElement(View, {
30452
- style: styles$w.row
30918
+ style: styles$r.row
30453
30919
  }, /*#__PURE__*/React.createElement(LabelLarge, {
30454
- style: styles$w.accordionHeader
30920
+ style: styles$r.accordionHeader
30455
30921
  }, lineLabel), /*#__PURE__*/React.createElement(Strut, {
30456
30922
  size: spacing.xSmall_8
30457
30923
  }), /*#__PURE__*/React.createElement(LineSwatch, {
@@ -30459,7 +30925,7 @@ const LockedFunctionSettings = props => {
30459
30925
  lineStyle: strokeStyle
30460
30926
  }))
30461
30927
  }, /*#__PURE__*/React.createElement(View, {
30462
- style: [styles$w.row, styles$w.spaceUnder]
30928
+ style: [styles$r.row, styles$r.spaceUnder]
30463
30929
  }, /*#__PURE__*/React.createElement(ColorSelect, {
30464
30930
  selectedValue: lineColor,
30465
30931
  onChange: newValue => {
@@ -30473,14 +30939,14 @@ const LockedFunctionSettings = props => {
30473
30939
  handlePropChange("strokeStyle", newValue);
30474
30940
  }
30475
30941
  })), /*#__PURE__*/React.createElement(View, {
30476
- style: [styles$w.row, styles$w.rowSpace]
30942
+ style: [styles$r.row, styles$r.rowSpace]
30477
30943
  }, /*#__PURE__*/React.createElement(SingleSelect, {
30478
30944
  selectedValue: directionalAxis,
30479
30945
  onChange: newValue => {
30480
30946
  handlePropChange("directionalAxis", newValue);
30481
30947
  },
30482
30948
  "aria-label": "equation prefix",
30483
- style: [styles$w.dropdownLabel, styles$w.axisMenu]
30949
+ style: [styles$r.dropdownLabel, styles$r.axisMenu]
30484
30950
  // Placeholder is required, but never gets used.
30485
30951
  ,
30486
30952
  placeholder: ""
@@ -30499,17 +30965,17 @@ const LockedFunctionSettings = props => {
30499
30965
  onChange: newValue => {
30500
30966
  handlePropChange("equation", newValue);
30501
30967
  },
30502
- style: [styles$w.textField]
30968
+ style: [styles$r.textField]
30503
30969
  })), /*#__PURE__*/React.createElement(View, {
30504
- style: [styles$w.row, styles$w.rowSpace]
30970
+ style: [styles$r.row, styles$r.rowSpace]
30505
30971
  }, /*#__PURE__*/React.createElement(LabelMedium, {
30506
30972
  tag: "label",
30507
- style: [styles$w.dropdownLabel, styles$w.domainMin]
30973
+ style: [styles$r.dropdownLabel, styles$r.domainMin]
30508
30974
  }, `${domainRangeText} min`, /*#__PURE__*/React.createElement(Strut, {
30509
30975
  size: spacing.xxSmall_6
30510
30976
  }), /*#__PURE__*/React.createElement(TextField, {
30511
30977
  type: "number",
30512
- style: styles$w.domainMinField,
30978
+ style: styles$r.domainMinField,
30513
30979
  value: domainEntries[0],
30514
30980
  onChange: newValue => {
30515
30981
  handleDomainChange(0, newValue);
@@ -30519,12 +30985,12 @@ const LockedFunctionSettings = props => {
30519
30985
  }), /*#__PURE__*/React.createElement(LabelMedium, {
30520
30986
  tag: "label",
30521
30987
  "aria-label": `${domainRangeText} max`,
30522
- style: [styles$w.dropdownLabel, styles$w.domainMax]
30988
+ style: [styles$r.dropdownLabel, styles$r.domainMax]
30523
30989
  }, "max", /*#__PURE__*/React.createElement(Strut, {
30524
30990
  size: spacing.xxSmall_6
30525
30991
  }), /*#__PURE__*/React.createElement(TextField, {
30526
30992
  type: "number",
30527
- style: styles$w.domainMaxField,
30993
+ style: styles$r.domainMaxField,
30528
30994
  value: domainEntries[1],
30529
30995
  onChange: newValue => {
30530
30996
  handleDomainChange(1, newValue);
@@ -30532,11 +30998,11 @@ const LockedFunctionSettings = props => {
30532
30998
  }))), /*#__PURE__*/React.createElement(PerseusEditorAccordion, {
30533
30999
  header: /*#__PURE__*/React.createElement(LabelLarge, null, "Example Functions"),
30534
31000
  expanded: false,
30535
- containerStyle: styles$w.exampleWorkspace,
30536
- panelStyle: styles$w.exampleAccordionPanel
31001
+ containerStyle: styles$r.exampleWorkspace,
31002
+ panelStyle: styles$r.exampleAccordionPanel
30537
31003
  }, /*#__PURE__*/React.createElement(LabelMedium, {
30538
31004
  tag: "label",
30539
- style: styles$w.dropdownLabel
31005
+ style: styles$r.dropdownLabel
30540
31006
  }, "Choose a category", /*#__PURE__*/React.createElement(Strut, {
30541
31007
  size: spacing.xxSmall_6
30542
31008
  }), /*#__PURE__*/React.createElement(SingleSelect, {
@@ -30550,7 +31016,7 @@ const LockedFunctionSettings = props => {
30550
31016
  label: category
30551
31017
  });
30552
31018
  }))), exampleCategorySelected && /*#__PURE__*/React.createElement("ul", {
30553
- className: css(styles$w.exampleContainer)
31019
+ className: css(styles$r.exampleContainer)
30554
31020
  }, exampleContent.map((example, index) => /*#__PURE__*/React.createElement(ExampleItem, {
30555
31021
  category: exampleCategory,
30556
31022
  example: example,
@@ -30572,29 +31038,29 @@ const ExampleItem = props => {
30572
31038
  const exampleId = useId();
30573
31039
  return /*#__PURE__*/React.createElement("li", {
30574
31040
  key: `${category}-${index}`,
30575
- className: css(styles$w.exampleRow)
31041
+ className: css(styles$r.exampleRow)
30576
31042
  }, /*#__PURE__*/React.createElement(IconButton, {
30577
31043
  icon: autoPasteIcon,
30578
31044
  "aria-label": "paste example",
30579
31045
  "aria-describedby": exampleId,
30580
31046
  onClick: () => pasteEquationFn("equation", example),
30581
31047
  size: "medium",
30582
- style: styles$w.copyPasteButton
31048
+ style: styles$r.copyPasteButton
30583
31049
  }), /*#__PURE__*/React.createElement(IconButton, {
30584
31050
  icon: copyIcon,
30585
31051
  "aria-label": "copy example",
30586
31052
  "aria-describedby": exampleId,
30587
31053
  onClick: () => navigator.clipboard.writeText(example),
30588
31054
  size: "medium",
30589
- style: styles$w.copyPasteButton
31055
+ style: styles$r.copyPasteButton
30590
31056
  }), /*#__PURE__*/React.createElement(Strut, {
30591
31057
  size: spacing.xxxSmall_4
30592
31058
  }), /*#__PURE__*/React.createElement(View, {
30593
- style: styles$w.exampleContent,
31059
+ style: styles$r.exampleContent,
30594
31060
  id: exampleId
30595
31061
  }, example));
30596
31062
  };
30597
- const styles$w = StyleSheet.create({
31063
+ const styles$r = StyleSheet.create({
30598
31064
  accordionHeader: {
30599
31065
  textOverflow: "ellipsis",
30600
31066
  // A maximum width needs to be specified in order for the ellipsis to work.
@@ -30712,15 +31178,16 @@ function LockedLabelSettings(props) {
30712
31178
  expanded: expanded,
30713
31179
  onToggle: onToggle,
30714
31180
  header: /*#__PURE__*/React.createElement(View, {
30715
- style: [styles$v.row, styles$v.accordionHeaderContainer]
31181
+ style: [styles$q.row, styles$q.accordionHeaderContainer]
30716
31182
  }, /*#__PURE__*/React.createElement(LabelLarge, null, "Label (", coord[0], ", ", coord[1], ")"), /*#__PURE__*/React.createElement(Strut, {
30717
31183
  size: spacing.xSmall_8
30718
31184
  }), text !== "" && /*#__PURE__*/React.createElement(LabelLarge, {
30719
31185
  style: [{
30720
31186
  backgroundColor: color.white,
30721
31187
  color: lockedFigureColors[color$1]
30722
- }, styles$v.accordionHeader]
30723
- }, text))
31188
+ }, styles$q.accordionHeader]
31189
+ }, text)),
31190
+ containerStyle: !onMove && styles$q.innerLabelAccordionPanel
30724
31191
  }, /*#__PURE__*/React.createElement(CoordinatePairInput, {
30725
31192
  coord: coord,
30726
31193
  onChange: newCoords => {
@@ -30728,10 +31195,10 @@ function LockedLabelSettings(props) {
30728
31195
  coord: newCoords
30729
31196
  });
30730
31197
  },
30731
- style: styles$v.spaceUnder
31198
+ style: styles$q.spaceUnder
30732
31199
  }), /*#__PURE__*/React.createElement(LabelMedium, {
30733
31200
  tag: "label",
30734
- style: [styles$v.row, styles$v.spaceUnder]
31201
+ style: [styles$q.row, styles$q.spaceUnder]
30735
31202
  }, "TeX", /*#__PURE__*/React.createElement(Strut, {
30736
31203
  size: spacing.xSmall_8
30737
31204
  }), /*#__PURE__*/React.createElement(TextField, {
@@ -30741,20 +31208,20 @@ function LockedLabelSettings(props) {
30741
31208
  text: newValue
30742
31209
  })
30743
31210
  })), /*#__PURE__*/React.createElement(View, {
30744
- style: styles$v.row
30745
- }, /*#__PURE__*/React.createElement(ColorSelect, {
31211
+ style: styles$q.row
31212
+ }, onMove && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(ColorSelect, {
30746
31213
  selectedValue: color$1,
30747
31214
  onChange: newColor => {
30748
31215
  onChangeProps({
30749
31216
  color: newColor
30750
31217
  });
30751
31218
  },
30752
- style: styles$v.spaceUnder
31219
+ style: styles$q.spaceUnder
30753
31220
  }), /*#__PURE__*/React.createElement(Strut, {
30754
31221
  size: spacing.medium_16
30755
- }), /*#__PURE__*/React.createElement(LabelMedium, {
31222
+ })), /*#__PURE__*/React.createElement(LabelMedium, {
30756
31223
  tag: "label",
30757
- style: styles$v.row
31224
+ style: styles$q.row
30758
31225
  }, "size", /*#__PURE__*/React.createElement(Strut, {
30759
31226
  size: spacing.xSmall_8
30760
31227
  }), /*#__PURE__*/React.createElement(SingleSelect, {
@@ -30781,7 +31248,7 @@ function LockedLabelSettings(props) {
30781
31248
  onRemove: onRemove
30782
31249
  }));
30783
31250
  }
30784
- const styles$v = StyleSheet.create({
31251
+ const styles$q = StyleSheet.create({
30785
31252
  accordionHeaderContainer: {
30786
31253
  // Stop the label summary from wrapping.
30787
31254
  whiteSpace: "nowrap"
@@ -30802,6 +31269,9 @@ const styles$v = StyleSheet.create({
30802
31269
  },
30803
31270
  spaceUnder: {
30804
31271
  marginBottom: spacing.xSmall_8
31272
+ },
31273
+ innerLabelAccordionPanel: {
31274
+ backgroundColor: color.white
30805
31275
  }
30806
31276
  });
30807
31277
 
@@ -30815,7 +31285,7 @@ const LabeledSwitch = props => {
30815
31285
  const ids = useUniqueIdWithMock();
30816
31286
  const switchId = ids.get("switch");
30817
31287
  return /*#__PURE__*/React.createElement(View, {
30818
- style: [styles$u.row, style]
31288
+ style: [styles$p.row, style]
30819
31289
  }, /*#__PURE__*/React.createElement(Switch, {
30820
31290
  id: switchId,
30821
31291
  checked: checked,
@@ -30827,7 +31297,7 @@ const LabeledSwitch = props => {
30827
31297
  htmlFor: switchId
30828
31298
  }, label));
30829
31299
  };
30830
- const styles$u = StyleSheet.create({
31300
+ const styles$p = StyleSheet.create({
30831
31301
  row: {
30832
31302
  flexDirection: "row",
30833
31303
  alignItems: "center"
@@ -30861,13 +31331,13 @@ const DefiningPointSettings = props => {
30861
31331
  return /*#__PURE__*/React.createElement(PerseusEditorAccordion, {
30862
31332
  expanded: expanded,
30863
31333
  onToggle: onToggle,
30864
- containerStyle: styles$t.container,
30865
- panelStyle: styles$t.accordionPanel,
31334
+ containerStyle: styles$o.container,
31335
+ panelStyle: styles$o.accordionPanel,
30866
31336
  header:
30867
31337
  /*#__PURE__*/
30868
31338
  // Summary: Point, coords, color (filled/open)
30869
31339
  React.createElement(View, {
30870
- style: styles$t.row
31340
+ style: styles$o.row
30871
31341
  }, /*#__PURE__*/React.createElement(LabelLarge, null, `${label || "Point"} (${coord[0]}, ${coord[1]})`), /*#__PURE__*/React.createElement(Strut, {
30872
31342
  size: spacing.xSmall_8
30873
31343
  }), showPoint && /*#__PURE__*/React.createElement(ColorSwatch, {
@@ -30877,7 +31347,7 @@ const DefiningPointSettings = props => {
30877
31347
  }, /*#__PURE__*/React.createElement(CoordinatePairInput, {
30878
31348
  coord: coord,
30879
31349
  error: !!error,
30880
- style: styles$t.spaceUnder,
31350
+ style: styles$o.spaceUnder,
30881
31351
  onChange: newCoords => {
30882
31352
  onChangeProps({
30883
31353
  coord: newCoords
@@ -30886,12 +31356,12 @@ const DefiningPointSettings = props => {
30886
31356
  }), onTogglePoint && /*#__PURE__*/React.createElement(LabeledSwitch, {
30887
31357
  label: "show point on graph",
30888
31358
  checked: !!showPoint,
30889
- style: showPoint && styles$t.spaceUnder,
31359
+ style: showPoint && styles$o.spaceUnder,
30890
31360
  onChange: onTogglePoint
30891
31361
  }), showPoint && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(ColorSelect, {
30892
31362
  selectedValue: pointColor,
30893
31363
  onChange: handleColorChange,
30894
- style: styles$t.spaceUnder
31364
+ style: styles$o.spaceUnder
30895
31365
  }), /*#__PURE__*/React.createElement(LabeledSwitch, {
30896
31366
  label: "open point",
30897
31367
  checked: !filled,
@@ -30902,7 +31372,7 @@ const DefiningPointSettings = props => {
30902
31372
  }
30903
31373
  })));
30904
31374
  };
30905
- const styles$t = StyleSheet.create({
31375
+ const styles$o = StyleSheet.create({
30906
31376
  container: {
30907
31377
  marginTop: spacing.xSmall_8,
30908
31378
  marginBottom: 0,
@@ -30965,7 +31435,7 @@ const LockedLineSettings = props => {
30965
31435
  expanded: props.expanded,
30966
31436
  onToggle: props.onToggle,
30967
31437
  header: /*#__PURE__*/React.createElement(View, {
30968
- style: styles$s.row
31438
+ style: styles$n.row
30969
31439
  }, /*#__PURE__*/React.createElement(LabelLarge, null, lineLabel), /*#__PURE__*/React.createElement(Strut, {
30970
31440
  size: spacing.xSmall_8
30971
31441
  }), /*#__PURE__*/React.createElement(LineSwatch, {
@@ -30974,7 +31444,7 @@ const LockedLineSettings = props => {
30974
31444
  }))
30975
31445
  }, /*#__PURE__*/React.createElement(LabelMedium, {
30976
31446
  tag: "label",
30977
- style: [styles$s.row, styles$s.spaceUnder]
31447
+ style: [styles$n.row, styles$n.spaceUnder]
30978
31448
  }, "kind", /*#__PURE__*/React.createElement(Strut, {
30979
31449
  size: spacing.xxxSmall_4
30980
31450
  }), /*#__PURE__*/React.createElement(SingleSelect, {
@@ -30995,7 +31465,7 @@ const LockedLineSettings = props => {
30995
31465
  value: "segment",
30996
31466
  label: "segment"
30997
31467
  }))), /*#__PURE__*/React.createElement(View, {
30998
- style: styles$s.row
31468
+ style: styles$n.row
30999
31469
  }, /*#__PURE__*/React.createElement(ColorSelect, {
31000
31470
  selectedValue: lineColor,
31001
31471
  onChange: handleColorChange
@@ -31007,7 +31477,7 @@ const LockedLineSettings = props => {
31007
31477
  lineStyle: value
31008
31478
  })
31009
31479
  })), isInvalid && /*#__PURE__*/React.createElement(LabelMedium, {
31010
- style: styles$s.errorText
31480
+ style: styles$n.errorText
31011
31481
  }, lengthZeroStr), /*#__PURE__*/React.createElement(DefiningPointSettings, _extends({
31012
31482
  label: "Point 1",
31013
31483
  showPoint: showPoint1,
@@ -31032,7 +31502,7 @@ const LockedLineSettings = props => {
31032
31502
  onRemove: onRemove
31033
31503
  }));
31034
31504
  };
31035
- const styles$s = StyleSheet.create({
31505
+ const styles$n = StyleSheet.create({
31036
31506
  row: {
31037
31507
  display: "flex",
31038
31508
  flexDirection: "row",
@@ -31046,24 +31516,122 @@ const styles$s = StyleSheet.create({
31046
31516
  }
31047
31517
  });
31048
31518
 
31049
- /**
31050
- * LockedPointSettings is a component that allows the user to edit the
31051
- * settings of specifically a locked point on the graph.
31052
- *
31053
- * Used in the interactive graph editor's locked figures section.
31054
- */
31519
+ const DEFAULT_COLOR = "grayH";
31520
+ function getDefaultFigureForType(type) {
31521
+ switch (type) {
31522
+ case "point":
31523
+ return {
31524
+ type: "point",
31525
+ coord: [0, 0],
31526
+ color: DEFAULT_COLOR,
31527
+ filled: true,
31528
+ labels: []
31529
+ };
31530
+ case "line":
31531
+ return {
31532
+ type: "line",
31533
+ kind: "line",
31534
+ points: [getDefaultFigureForType("point"), _extends({}, getDefaultFigureForType("point"), {
31535
+ coord: [2, 2]
31536
+ })],
31537
+ color: DEFAULT_COLOR,
31538
+ lineStyle: "solid",
31539
+ showPoint1: false,
31540
+ showPoint2: false
31541
+ };
31542
+ case "vector":
31543
+ return {
31544
+ type: "vector",
31545
+ points: [[0, 0], [2, 2]],
31546
+ color: DEFAULT_COLOR
31547
+ };
31548
+ case "ellipse":
31549
+ return {
31550
+ type: "ellipse",
31551
+ center: [0, 0],
31552
+ radius: [1, 1],
31553
+ angle: 0,
31554
+ color: DEFAULT_COLOR,
31555
+ fillStyle: "none",
31556
+ strokeStyle: "solid"
31557
+ };
31558
+ case "polygon":
31559
+ return {
31560
+ type: "polygon",
31561
+ points: [[0, 2], [-1, 0], [1, 0]],
31562
+ color: DEFAULT_COLOR,
31563
+ showVertices: false,
31564
+ fillStyle: "none",
31565
+ strokeStyle: "solid"
31566
+ };
31567
+ case "function":
31568
+ return {
31569
+ type: "function",
31570
+ color: DEFAULT_COLOR,
31571
+ strokeStyle: "solid",
31572
+ equation: "x^2",
31573
+ directionalAxis: "x"
31574
+ };
31575
+ case "label":
31576
+ return {
31577
+ type: "label",
31578
+ coord: [0, 0],
31579
+ text: "",
31580
+ color: DEFAULT_COLOR,
31581
+ size: "medium"
31582
+ };
31583
+ default:
31584
+ throw new UnreachableCaseError(type);
31585
+ }
31586
+ }
31587
+
31055
31588
  const LockedPointSettings = props => {
31589
+ var _flags$mafs;
31056
31590
  const {
31591
+ flags,
31057
31592
  coord,
31058
31593
  color: pointColor,
31059
31594
  filled = true,
31595
+ labels,
31060
31596
  onChangeProps,
31061
31597
  onMove,
31062
31598
  onRemove
31063
31599
  } = props;
31064
31600
  function handleColorChange(newValue) {
31065
- onChangeProps({
31601
+ const newProps = {
31602
+ color: newValue
31603
+ };
31604
+
31605
+ // Update the color of the all labels to match the point
31606
+ newProps.labels = labels.map(label => _extends({}, label, {
31066
31607
  color: newValue
31608
+ }));
31609
+ onChangeProps(newProps);
31610
+ }
31611
+ function handleCoordChange(newCoord) {
31612
+ const xOffset = newCoord[0] - coord[0];
31613
+ const yOffset = newCoord[1] - coord[1];
31614
+ const newProps = {
31615
+ coord: newCoord
31616
+ };
31617
+
31618
+ // Update the coord by the same amount as the point for all labels
31619
+ newProps.labels = labels.map(label => _extends({}, label, {
31620
+ coord: [label.coord[0] + xOffset, label.coord[1] + yOffset]
31621
+ }));
31622
+ onChangeProps(newProps);
31623
+ }
31624
+ function handleLabelChange(updatedLabel, labelIndex) {
31625
+ const updatedLabels = [...labels];
31626
+ updatedLabels[labelIndex] = _extends({}, labels[labelIndex], updatedLabel);
31627
+ onChangeProps({
31628
+ labels: updatedLabels
31629
+ });
31630
+ }
31631
+ function handleLabelRemove(labelIndex) {
31632
+ const updatedLabels = labels.filter((_, index) => index !== labelIndex);
31633
+ onChangeProps({
31634
+ labels: updatedLabels
31067
31635
  });
31068
31636
  }
31069
31637
  return /*#__PURE__*/React.createElement(PerseusEditorAccordion, {
@@ -31073,7 +31641,7 @@ const LockedPointSettings = props => {
31073
31641
  /*#__PURE__*/
31074
31642
  // Summary: Point, coords, color (filled/open)
31075
31643
  React.createElement(View, {
31076
- style: styles$r.row
31644
+ style: styles$m.row
31077
31645
  }, /*#__PURE__*/React.createElement(LabelLarge, null, `Point (${coord[0]}, ${coord[1]})`), /*#__PURE__*/React.createElement(Strut, {
31078
31646
  size: spacing.xSmall_8
31079
31647
  }), /*#__PURE__*/React.createElement(ColorSwatch, {
@@ -31082,16 +31650,12 @@ const LockedPointSettings = props => {
31082
31650
  }))
31083
31651
  }, /*#__PURE__*/React.createElement(CoordinatePairInput, {
31084
31652
  coord: coord,
31085
- style: styles$r.spaceUnder,
31086
- onChange: newCoord => {
31087
- onChangeProps({
31088
- coord: newCoord
31089
- });
31090
- }
31653
+ style: styles$m.spaceUnder,
31654
+ onChange: handleCoordChange
31091
31655
  }), /*#__PURE__*/React.createElement(ColorSelect, {
31092
31656
  selectedValue: pointColor,
31093
31657
  onChange: handleColorChange,
31094
- style: styles$r.spaceUnder
31658
+ style: styles$m.spaceUnder
31095
31659
  }), /*#__PURE__*/React.createElement(LabeledSwitch, {
31096
31660
  label: "open point",
31097
31661
  checked: !filled,
@@ -31099,20 +31663,52 @@ const LockedPointSettings = props => {
31099
31663
  onChangeProps({
31100
31664
  filled: !newValue
31101
31665
  });
31666
+ },
31667
+ style: styles$m.spaceUnder
31668
+ }), (flags == null || (_flags$mafs = flags["mafs"]) == null ? void 0 : _flags$mafs["locked-point-labels"]) && /*#__PURE__*/React.createElement(React.Fragment, null, labels.map((label, labelIndex) => /*#__PURE__*/React.createElement(LockedLabelSettings, _extends({}, label, {
31669
+ expanded: true,
31670
+ onChangeProps: newLabel => {
31671
+ handleLabelChange(newLabel, labelIndex);
31672
+ },
31673
+ onRemove: () => {
31674
+ handleLabelRemove(labelIndex);
31102
31675
  }
31103
- }), /*#__PURE__*/React.createElement(LockedFigureSettingsActions, {
31676
+ }))), /*#__PURE__*/React.createElement(Button, {
31677
+ kind: "tertiary",
31678
+ startIcon: plusCircle,
31679
+ onClick: () => {
31680
+ const newLabel = _extends({}, getDefaultFigureForType("label"), {
31681
+ // Place the label at the same position
31682
+ // as the point. Add a small offset to
31683
+ // avoid crowding.
31684
+ coord: [coord[0] + 0.5,
31685
+ // Additional offset for each label so
31686
+ // they don't overlap.
31687
+ coord[1] - 1 * (labels == null ? void 0 : labels.length)],
31688
+ // Default to the same color as the point
31689
+ color: pointColor
31690
+ });
31691
+ onChangeProps({
31692
+ labels: [...labels, newLabel]
31693
+ });
31694
+ },
31695
+ style: styles$m.addButton
31696
+ }, "Add visible label")), /*#__PURE__*/React.createElement(LockedFigureSettingsActions, {
31104
31697
  figureType: props.type,
31105
31698
  onMove: onMove,
31106
31699
  onRemove: onRemove
31107
31700
  }));
31108
31701
  };
31109
- const styles$r = StyleSheet.create({
31702
+ const styles$m = StyleSheet.create({
31110
31703
  row: {
31111
31704
  flexDirection: "row",
31112
31705
  alignItems: "center"
31113
31706
  },
31114
31707
  spaceUnder: {
31115
31708
  marginBottom: spacing.xSmall_8
31709
+ },
31710
+ addButton: {
31711
+ alignSelf: "start"
31116
31712
  }
31117
31713
  });
31118
31714
 
@@ -31124,17 +31720,17 @@ const PolygonSwatch = props => {
31124
31720
  } = props;
31125
31721
  return /*#__PURE__*/React.createElement(View, {
31126
31722
  "aria-label": `${color}, stroke ${strokeStyle}, fill ${fillStyle}`,
31127
- style: [styles$q.container, {
31723
+ style: [styles$l.container, {
31128
31724
  border: `4px ${strokeStyle} ${lockedFigureColors[color]}`
31129
31725
  }]
31130
31726
  }, /*#__PURE__*/React.createElement(View, {
31131
- style: [styles$q.innerSquare, {
31727
+ style: [styles$l.innerSquare, {
31132
31728
  backgroundColor: lockedFigureColors[color],
31133
31729
  opacity: fillStyle === "white" ? 0 : lockedFigureFillStyles[fillStyle]
31134
31730
  }]
31135
31731
  }));
31136
31732
  };
31137
- const styles$q = StyleSheet.create({
31733
+ const styles$l = StyleSheet.create({
31138
31734
  container: {
31139
31735
  // Add a white outline so that the color swatch is visible when
31140
31736
  // the dropdown option is highlighted with its blue background.
@@ -31200,7 +31796,7 @@ const LockedPolygonSettings = props => {
31200
31796
  /*#__PURE__*/
31201
31797
  // Summary: Polygon, number of sides, style swatch
31202
31798
  React.createElement(View, {
31203
- style: styles$p.row
31799
+ style: styles$k.row
31204
31800
  }, /*#__PURE__*/React.createElement(LabelLarge, null, `Polygon, ${points.length} sides`), /*#__PURE__*/React.createElement(Strut, {
31205
31801
  size: spacing.xSmall_8
31206
31802
  }), /*#__PURE__*/React.createElement(PolygonSwatch, {
@@ -31209,7 +31805,7 @@ const LockedPolygonSettings = props => {
31209
31805
  strokeStyle: strokeStyle
31210
31806
  }))
31211
31807
  }, /*#__PURE__*/React.createElement(View, {
31212
- style: [styles$p.row, styles$p.spaceUnder]
31808
+ style: [styles$k.row, styles$k.spaceUnder]
31213
31809
  }, /*#__PURE__*/React.createElement(ColorSelect, {
31214
31810
  selectedValue: color,
31215
31811
  onChange: handleColorChange
@@ -31217,7 +31813,7 @@ const LockedPolygonSettings = props => {
31217
31813
  size: spacing.medium_16
31218
31814
  }), /*#__PURE__*/React.createElement(LabelMedium, {
31219
31815
  tag: "label",
31220
- style: [styles$p.row, styles$p.truncatedWidth]
31816
+ style: [styles$k.row, styles$k.truncatedWidth]
31221
31817
  }, "fill", /*#__PURE__*/React.createElement(Strut, {
31222
31818
  size: spacing.xxSmall_6
31223
31819
  }), /*#__PURE__*/React.createElement(SingleSelect, {
@@ -31243,17 +31839,17 @@ const LockedPolygonSettings = props => {
31243
31839
  onChange: newValue => onChangeProps({
31244
31840
  showVertices: newValue
31245
31841
  }),
31246
- style: styles$p.spaceUnder
31842
+ style: styles$k.spaceUnder
31247
31843
  }), /*#__PURE__*/React.createElement(PerseusEditorAccordion, {
31248
31844
  header: /*#__PURE__*/React.createElement(LabelLarge, null, "Points"),
31249
31845
  expanded: true,
31250
- containerStyle: styles$p.pointAccordionContainer,
31251
- panelStyle: styles$p.pointAccordionPanel
31846
+ containerStyle: styles$k.pointAccordionContainer,
31847
+ panelStyle: styles$k.pointAccordionPanel
31252
31848
  }, points.map((point, index) => {
31253
31849
  const pointLabel = String.fromCharCode(65 + index);
31254
31850
  return /*#__PURE__*/React.createElement(View, {
31255
31851
  key: `locked-polygon-point-index-${index}`,
31256
- style: [styles$p.row, styles$p.spaceUnder]
31852
+ style: [styles$k.row, styles$k.spaceUnder]
31257
31853
  }, /*#__PURE__*/React.createElement(LabelLarge, null, `${pointLabel}:`), /*#__PURE__*/React.createElement(Strut, {
31258
31854
  size: spacing.medium_16
31259
31855
  }), /*#__PURE__*/React.createElement(CoordinatePairInput, {
@@ -31281,10 +31877,10 @@ const LockedPolygonSettings = props => {
31281
31877
  points: newPoints
31282
31878
  });
31283
31879
  },
31284
- style: styles$p.icon
31880
+ style: styles$k.icon
31285
31881
  }));
31286
31882
  }), /*#__PURE__*/React.createElement(View, {
31287
- style: [styles$p.row, styles$p.polygonActionsContainer]
31883
+ style: [styles$k.row, styles$k.polygonActionsContainer]
31288
31884
  }, /*#__PURE__*/React.createElement(Button, {
31289
31885
  kind: "tertiary",
31290
31886
  startIcon: plusCircle,
@@ -31294,986 +31890,476 @@ const LockedPolygonSettings = props => {
31294
31890
  });
31295
31891
  }
31296
31892
  }, "Add point"), /*#__PURE__*/React.createElement(Spring, null), /*#__PURE__*/React.createElement(View, {
31297
- style: styles$p.movementButtonsContainer
31893
+ style: styles$k.movementButtonsContainer
31298
31894
  }, /*#__PURE__*/React.createElement(IconButton, {
31299
31895
  "aria-label": "Move polygon up",
31300
- style: styles$p.iconButton,
31896
+ style: styles$k.iconButton,
31301
31897
  size: "small",
31302
31898
  icon: arrowFatUp,
31303
31899
  onClick: () => handlePolygonMove("up")
31304
31900
  }), /*#__PURE__*/React.createElement(View, {
31305
- style: styles$p.row
31306
- }, /*#__PURE__*/React.createElement(IconButton, {
31307
- "aria-label": "Move polygon left",
31308
- style: styles$p.iconButton,
31309
- size: "small",
31310
- icon: arrowFatLeft,
31311
- onClick: () => handlePolygonMove("left")
31312
- }), /*#__PURE__*/React.createElement(IconButton, {
31313
- "aria-label": "Move polygon down",
31314
- style: styles$p.iconButton,
31315
- size: "small",
31316
- icon: arrowFatDown,
31317
- onClick: () => handlePolygonMove("down")
31318
- }), /*#__PURE__*/React.createElement(IconButton, {
31319
- "aria-label": "Move polygon right",
31320
- style: styles$p.iconButton,
31321
- size: "small",
31322
- icon: arrowFatRight,
31323
- onClick: () => handlePolygonMove("right")
31324
- }))))), /*#__PURE__*/React.createElement(LockedFigureSettingsActions, {
31325
- figureType: props.type,
31326
- onMove: onMove,
31327
- onRemove: onRemove
31328
- }));
31329
- };
31330
- const styles$p = StyleSheet.create({
31331
- row: {
31332
- display: "flex",
31333
- flexDirection: "row",
31334
- alignItems: "center"
31335
- },
31336
- pointAccordionContainer: {
31337
- backgroundColor: color.white
31338
- },
31339
- pointAccordionPanel: {
31340
- alignItems: "start"
31341
- },
31342
- icon: {
31343
- marginInlineStart: spacing.xxxSmall_4
31344
- },
31345
- polygonActionsContainer: {
31346
- width: "100%"
31347
- },
31348
- iconButton: {
31349
- margin: 0
31350
- },
31351
- movementButtonsContainer: {
31352
- display: "flex",
31353
- flexDirection: "column",
31354
- alignItems: "center",
31355
- minWidth: "fit-content"
31356
- },
31357
- spaceUnder: {
31358
- marginBottom: spacing.xSmall_8
31359
- },
31360
- truncatedWidth: {
31361
- // Allow truncation, stop bleeding over the edge.
31362
- minWidth: 0
31363
- }
31364
- });
31365
-
31366
- /**
31367
- * LockedVectorSettings is a component that allows the user to edit the
31368
- * settings of specifically a locked vector on the graph.
31369
- *
31370
- * Used in the interactive graph editor's locked figures section.
31371
- */
31372
- const lengthErrorMessage = "The vector cannot have length 0.";
31373
- const LockedVectorSettings = props => {
31374
- const {
31375
- points,
31376
- color: lineColor,
31377
- onChangeProps,
31378
- onMove,
31379
- onRemove
31380
- } = props;
31381
- const [tail, tip] = points;
31382
- const lineLabel = `Vector (${tail[0]}, ${tail[1]}), (${tip[0]}, ${tip[1]})`;
31383
-
31384
- // Check if the line has length 0.
31385
- const isInvalid = vector.equal(tail, tip);
31386
- function handleChangePoint(newCoord, index) {
31387
- if (typeof newCoord !== "undefined") {
31388
- const newPoints = [...points];
31389
- newPoints[index] = [...newCoord];
31390
- onChangeProps({
31391
- points: newPoints
31392
- });
31393
- }
31394
- }
31395
- function handleColorChange(newColor) {
31396
- onChangeProps({
31397
- color: newColor
31398
- });
31399
- }
31400
- return /*#__PURE__*/React.createElement(PerseusEditorAccordion, {
31401
- expanded: props.expanded,
31402
- onToggle: props.onToggle,
31403
- header: /*#__PURE__*/React.createElement(View, {
31404
- style: styles$o.row
31405
- }, /*#__PURE__*/React.createElement(LabelLarge, null, lineLabel), /*#__PURE__*/React.createElement(Strut, {
31406
- size: spacing.xSmall_8
31407
- }), /*#__PURE__*/React.createElement(LineSwatch, {
31408
- color: lineColor,
31409
- lineStyle: "solid"
31410
- }))
31411
- }, /*#__PURE__*/React.createElement(View, {
31412
- style: [styles$o.row, styles$o.spaceUnder]
31413
- }, /*#__PURE__*/React.createElement(ColorSelect, {
31414
- selectedValue: lineColor,
31415
- onChange: handleColorChange
31416
- })), isInvalid && /*#__PURE__*/React.createElement(LabelMedium, {
31417
- style: styles$o.errorText
31418
- }, lengthErrorMessage), /*#__PURE__*/React.createElement(PerseusEditorAccordion, {
31419
- expanded: true // Initial state is expanded
31420
- ,
31421
- containerStyle: styles$o.container,
31422
- panelStyle: styles$o.accordionPanel,
31423
- header: /*#__PURE__*/React.createElement(View, {
31424
- style: styles$o.row
31425
- }, /*#__PURE__*/React.createElement(LabelLarge, null, `Tail (${tail[0]}, ${tail[1]})`))
31426
- }, /*#__PURE__*/React.createElement(CoordinatePairInput, {
31427
- coord: tail,
31428
- error: isInvalid,
31429
- onChange: newProps => {
31430
- handleChangePoint(newProps, 0);
31431
- }
31432
- })), /*#__PURE__*/React.createElement(PerseusEditorAccordion, {
31433
- expanded: true // Initial state is expanded
31434
- ,
31435
- containerStyle: styles$o.container,
31436
- panelStyle: styles$o.accordionPanel,
31437
- header: /*#__PURE__*/React.createElement(View, {
31438
- style: styles$o.row
31439
- }, /*#__PURE__*/React.createElement(LabelLarge, null, `Tip (${tip[0]}, ${tip[1]})`))
31440
- }, /*#__PURE__*/React.createElement(CoordinatePairInput, {
31441
- coord: tip,
31442
- error: isInvalid,
31443
- onChange: newProps => {
31444
- handleChangePoint(newProps, 1);
31445
- }
31446
- })), /*#__PURE__*/React.createElement(LockedFigureSettingsActions, {
31447
- figureType: props.type,
31448
- onMove: onMove,
31449
- onRemove: onRemove
31450
- }));
31451
- };
31452
- const styles$o = StyleSheet.create({
31453
- accordionPanel: {
31454
- paddingBottom: spacing.medium_16
31455
- },
31456
- container: {
31457
- marginTop: spacing.xSmall_8,
31458
- marginBottom: 0,
31459
- marginLeft: -spacing.xxxSmall_4,
31460
- marginRight: -spacing.xxxSmall_4,
31461
- backgroundColor: color.white
31462
- },
31463
- errorText: {
31464
- color: color.red,
31465
- marginTop: spacing.xSmall_8
31466
- },
31467
- row: {
31468
- flexDirection: "row",
31469
- alignItems: "center"
31470
- }
31471
- });
31472
-
31473
- /**
31474
- * LockedFigureSettings takes in a figure and renders the
31475
- * respective settings for that figure type.
31476
- *
31477
- * Used in the interactive graph editor's locked figures section.
31478
- */
31479
-
31480
- // Union this type with other locked figure types when they are added.
31481
-
31482
- const LockedFigureSettings = props => {
31483
- switch (props.type) {
31484
- case "point":
31485
- return /*#__PURE__*/React.createElement(LockedPointSettings, props);
31486
- case "line":
31487
- return /*#__PURE__*/React.createElement(LockedLineSettings, props);
31488
- case "vector":
31489
- return /*#__PURE__*/React.createElement(LockedVectorSettings, props);
31490
- case "ellipse":
31491
- return /*#__PURE__*/React.createElement(LockedEllipseSettings, props);
31492
- case "polygon":
31493
- return /*#__PURE__*/React.createElement(LockedPolygonSettings, props);
31494
- case "function":
31495
- return /*#__PURE__*/React.createElement(LockedFunctionSettings, props);
31496
- case "label":
31497
- if (props.showLabelsFlag) {
31498
- return /*#__PURE__*/React.createElement(LockedLabelSettings, props);
31499
- }
31500
- break;
31501
- }
31502
- return null;
31503
- };
31504
-
31505
- const LockedFiguresSection = props => {
31506
- var _props$figures;
31507
- // Keep track of all figures' accordions' expanded states for the
31508
- // expand/collapse all button. Set the whole array to false initially.
31509
- const collapsedStateArray = Array(((_props$figures = props.figures) != null ? _props$figures : []).length).fill(false);
31510
- const [expandedStates, setExpandedStates] = React.useState(collapsedStateArray);
31511
- const [isExpanded, setIsExpanded] = React.useState(true);
31512
- const uniqueId = useUniqueIdWithMock().get("locked-figures-section");
31513
- const {
31514
- figures,
31515
- onChange
31516
- } = props;
31517
- function addLockedFigure(newFigure) {
31518
- const lockedFigures = figures || [];
31519
- const newProps = {
31520
- lockedFigures: [...lockedFigures, getDefaultFigureForType(newFigure)]
31521
- };
31522
- onChange(newProps);
31523
- setExpandedStates([...expandedStates, true]);
31524
- }
31525
- function moveLockedFigure(index, movement) {
31526
- // Don't allow moving the first figure up or the last figure down.
31527
- if (index === 0 && (movement === "back" || movement === "backward")) {
31528
- return;
31529
- }
31530
- if (figures && index === figures.length - 1 && (movement === "front" || movement === "forward")) {
31531
- return;
31532
- }
31533
- const lockedFigures = figures || [];
31534
- const newFigures = [...lockedFigures];
31535
- const newExpandedStates = [...expandedStates];
31536
-
31537
- // First, remove the figure from its current position
31538
- // in the figures array and the expanded states array.
31539
- const [removedFigure] = newFigures.splice(index, 1);
31540
- newExpandedStates.splice(index, 1);
31541
-
31542
- // Then, add it back in the new position. Add "true" to the
31543
- // expanded states array for the new position (it must already
31544
- // be open since the button to move it is being pressed from there).
31545
- switch (movement) {
31546
- case "back":
31547
- newFigures.unshift(removedFigure);
31548
- newExpandedStates.unshift(true);
31549
- break;
31550
- case "backward":
31551
- newFigures.splice(index - 1, 0, removedFigure);
31552
- newExpandedStates.splice(index - 1, 0, true);
31553
- break;
31554
- case "forward":
31555
- newFigures.splice(index + 1, 0, removedFigure);
31556
- newExpandedStates.splice(index + 1, 0, true);
31557
- break;
31558
- case "front":
31559
- newFigures.push(removedFigure);
31560
- newExpandedStates.push(true);
31561
- break;
31562
- }
31563
- onChange({
31564
- lockedFigures: newFigures
31565
- });
31566
- setExpandedStates(newExpandedStates);
31567
- }
31568
- function removeLockedFigure(index) {
31569
- // eslint-disable-next-line no-alert
31570
- if (window.confirm("Are you sure you want to delete this figure?")) {
31571
- const lockedFigures = figures || [];
31572
- onChange({
31573
- lockedFigures: [...lockedFigures.slice(0, index), ...lockedFigures.slice(index + 1)]
31574
- });
31575
-
31576
- // Update expanded states
31577
- const newExpandedStates = [...expandedStates];
31578
- newExpandedStates.splice(index, 1);
31579
- setExpandedStates(newExpandedStates);
31580
- }
31581
- }
31582
- function changeProps(index,
31583
- // Omit the type from the figure props so it doesn't think
31584
- // we're trying to pass in the props for the wrong type.
31585
- figureProps) {
31586
- const lockedFigures = figures || [];
31587
- const newFigures = {
31588
- lockedFigures: [...lockedFigures.slice(0, index), _extends({}, lockedFigures[index], figureProps), ...lockedFigures.slice(index + 1)]
31589
- };
31590
- onChange(newFigures);
31591
- }
31592
- function toggleExpanded(newValue) {
31593
- setExpandedStates(Array(figures == null ? void 0 : figures.length).fill(newValue));
31594
- }
31595
- const allCollapsed = expandedStates.every(value => !value);
31596
- const buttonLabel = allCollapsed ? "Expand all" : "Collapse all";
31597
- const showExpandButton = !!(figures != null && figures.length);
31598
- return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Heading, {
31599
- title: "Locked Figures",
31600
- isOpen: isExpanded,
31601
- onToggle: () => setIsExpanded(!isExpanded),
31602
- isCollapsible: true
31603
- }), isExpanded && /*#__PURE__*/React.createElement(View, null, figures == null ? void 0 : figures.map((figure, index) => {
31604
- return /*#__PURE__*/React.createElement(LockedFigureSettings, _extends({
31605
- key: `${uniqueId}-locked-${figure}-${index}`,
31606
- showLabelsFlag: props.showLabelsFlag,
31607
- expanded: expandedStates[index],
31608
- onToggle: newValue => {
31609
- const newExpanded = [...expandedStates];
31610
- newExpanded[index] = newValue;
31611
- setExpandedStates(newExpanded);
31612
- }
31613
- }, figure, {
31614
- onChangeProps: newProps => changeProps(index, newProps),
31615
- onMove: movement => moveLockedFigure(index, movement),
31616
- onRemove: () => removeLockedFigure(index)
31617
- }));
31618
- }), /*#__PURE__*/React.createElement(View, {
31619
- style: styles$n.buttonContainer
31620
- }, /*#__PURE__*/React.createElement(LockedFigureSelect, {
31621
- showLabelsFlag: props.showLabelsFlag,
31622
- id: `${uniqueId}-select`,
31623
- onChange: addLockedFigure
31624
- }), /*#__PURE__*/React.createElement(Strut, {
31625
- size: spacing.small_12
31626
- }), showExpandButton && /*#__PURE__*/React.createElement(Button, {
31627
- kind: "secondary",
31628
- onClick: () => toggleExpanded(allCollapsed),
31629
- style: styles$n.button
31630
- }, buttonLabel))));
31901
+ style: styles$k.row
31902
+ }, /*#__PURE__*/React.createElement(IconButton, {
31903
+ "aria-label": "Move polygon left",
31904
+ style: styles$k.iconButton,
31905
+ size: "small",
31906
+ icon: arrowFatLeft,
31907
+ onClick: () => handlePolygonMove("left")
31908
+ }), /*#__PURE__*/React.createElement(IconButton, {
31909
+ "aria-label": "Move polygon down",
31910
+ style: styles$k.iconButton,
31911
+ size: "small",
31912
+ icon: arrowFatDown,
31913
+ onClick: () => handlePolygonMove("down")
31914
+ }), /*#__PURE__*/React.createElement(IconButton, {
31915
+ "aria-label": "Move polygon right",
31916
+ style: styles$k.iconButton,
31917
+ size: "small",
31918
+ icon: arrowFatRight,
31919
+ onClick: () => handlePolygonMove("right")
31920
+ }))))), /*#__PURE__*/React.createElement(LockedFigureSettingsActions, {
31921
+ figureType: props.type,
31922
+ onMove: onMove,
31923
+ onRemove: onRemove
31924
+ }));
31631
31925
  };
31632
- const styles$n = StyleSheet.create({
31633
- buttonContainer: {
31926
+ const styles$k = StyleSheet.create({
31927
+ row: {
31928
+ display: "flex",
31634
31929
  flexDirection: "row",
31635
31930
  alignItems: "center"
31636
31931
  },
31637
- button: {
31638
- marginTop: spacing.xSmall_8,
31639
- flexGrow: 1
31932
+ pointAccordionContainer: {
31933
+ backgroundColor: color.white
31934
+ },
31935
+ pointAccordionPanel: {
31936
+ alignItems: "start"
31937
+ },
31938
+ icon: {
31939
+ marginInlineStart: spacing.xxxSmall_4
31940
+ },
31941
+ polygonActionsContainer: {
31942
+ width: "100%"
31943
+ },
31944
+ iconButton: {
31945
+ margin: 0
31946
+ },
31947
+ movementButtonsContainer: {
31948
+ display: "flex",
31949
+ flexDirection: "column",
31950
+ alignItems: "center",
31951
+ minWidth: "fit-content"
31952
+ },
31953
+ spaceUnder: {
31954
+ marginBottom: spacing.xSmall_8
31955
+ },
31956
+ truncatedWidth: {
31957
+ // Allow truncation, stop bleeding over the edge.
31958
+ minWidth: 0
31640
31959
  }
31641
31960
  });
31642
31961
 
31643
- const UNLIMITED = "unlimited";
31644
- // Parses a string (ostensibly from some UI element that only supports string
31645
- // types, such as a <select />) into a numeric point count or the special sigil
31646
- // "unlimited". Note that 0 is a special case, and means "unlimited".
31647
- const parsePointCount = points => {
31648
- const parsed = parseInt(points, 10);
31649
- if (isNaN(parsed)) {
31650
- return UNLIMITED;
31651
- }
31652
- return parsed === 0 ? UNLIMITED : parsed;
31653
- };
31962
+ /**
31963
+ * LockedVectorSettings is a component that allows the user to edit the
31964
+ * settings of specifically a locked vector on the graph.
31965
+ *
31966
+ * Used in the interactive graph editor's locked figures section.
31967
+ */
31968
+ const lengthErrorMessage = "The vector cannot have length 0.";
31969
+ const LockedVectorSettings = props => {
31970
+ const {
31971
+ points,
31972
+ color: lineColor,
31973
+ onChangeProps,
31974
+ onMove,
31975
+ onRemove
31976
+ } = props;
31977
+ const [tail, tip] = points;
31978
+ const lineLabel = `Vector (${tail[0]}, ${tail[1]}), (${tip[0]}, ${tip[1]})`;
31654
31979
 
31655
- const GraphPointsCountSelector = ({
31656
- numPoints: _numPoints = 1,
31657
- onChange
31658
- }) => {
31659
- return /*#__PURE__*/React.createElement(SingleSelect, {
31660
- selectedValue: `${_numPoints}`,
31661
- onChange: newValue => {
31662
- onChange(parsePointCount(newValue));
31980
+ // Check if the line has length 0.
31981
+ const isInvalid = vector.equal(tail, tip);
31982
+ function handleChangePoint(newCoord, index) {
31983
+ if (typeof newCoord !== "undefined") {
31984
+ const newPoints = [...points];
31985
+ newPoints[index] = [...newCoord];
31986
+ onChangeProps({
31987
+ points: newPoints
31988
+ });
31663
31989
  }
31664
- // Never uses placeholder, always has value
31665
- ,
31666
- placeholder: "",
31667
- style: styles$m.singleSelectShort
31668
- }, [...[...Array(7).keys()].map(n => /*#__PURE__*/React.createElement(OptionItem, {
31669
- key: n,
31670
- value: `${n}`,
31671
- label: `${n} point${n > 1 ? "s" : ""}`
31672
- })), /*#__PURE__*/React.createElement(OptionItem, {
31673
- key: "unlimited",
31674
- value: UNLIMITED,
31675
- label: "unlimited"
31676
- })]);
31677
- };
31678
- const styles$m = StyleSheet.create({
31679
- singleSelectShort: {
31680
- // Non-standard spacing, but it's the smallest we can go
31681
- // without running into styling issues with the dropdown.
31682
- height: 26
31683
31990
  }
31684
- });
31685
-
31686
- const GraphTypeSelector = props => {
31687
- return /*#__PURE__*/React.createElement(SingleSelect, {
31688
- selectedValue: props.graphType,
31689
- onChange: props.onChange,
31690
- placeholder: "Select a graph type",
31691
- style: styles$l.singleSelectShort
31692
- }, /*#__PURE__*/React.createElement(OptionItem, {
31693
- value: "linear",
31694
- label: "Linear function"
31695
- }), /*#__PURE__*/React.createElement(OptionItem, {
31696
- value: "quadratic",
31697
- label: "Quadratic function"
31698
- }), /*#__PURE__*/React.createElement(OptionItem, {
31699
- value: "sinusoid",
31700
- label: "Sinusoid function"
31701
- }), /*#__PURE__*/React.createElement(OptionItem, {
31702
- value: "circle",
31703
- label: "Circle"
31704
- }), /*#__PURE__*/React.createElement(OptionItem, {
31705
- value: "point",
31706
- label: "Point(s)"
31707
- }), /*#__PURE__*/React.createElement(OptionItem, {
31708
- value: "linear-system",
31709
- label: "Linear System"
31710
- }), /*#__PURE__*/React.createElement(OptionItem, {
31711
- value: "polygon",
31712
- label: "Polygon"
31713
- }), /*#__PURE__*/React.createElement(OptionItem, {
31714
- value: "segment",
31715
- label: "Line Segment(s)"
31716
- }), /*#__PURE__*/React.createElement(OptionItem, {
31717
- value: "ray",
31718
- label: "Ray"
31719
- }), /*#__PURE__*/React.createElement(OptionItem, {
31720
- value: "angle",
31721
- label: "Angle"
31722
- }));
31723
- };
31724
- const styles$l = StyleSheet.create({
31725
- singleSelectShort: {
31726
- // Non-standard spacing, but it's the smallest we can go
31727
- // without running into styling issues with the dropdown.
31728
- height: 26
31991
+ function handleColorChange(newColor) {
31992
+ onChangeProps({
31993
+ color: newColor
31994
+ });
31729
31995
  }
31730
- });
31731
-
31732
- function InteractiveGraphCorrectAnswer(props) {
31733
- return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Heading, {
31734
- title: "Correct Answer",
31735
- isOpen: true,
31736
- isCollapsible: false
31737
- }), /*#__PURE__*/React.createElement(View, null, /*#__PURE__*/React.createElement(View, null, /*#__PURE__*/React.createElement(LabelXSmall, {
31738
- style: {
31739
- paddingTop: spacing.xxSmall_6,
31740
- paddingBottom: spacing.xxSmall_6,
31741
- color: color.offBlack64
31996
+ return /*#__PURE__*/React.createElement(PerseusEditorAccordion, {
31997
+ expanded: props.expanded,
31998
+ onToggle: props.onToggle,
31999
+ header: /*#__PURE__*/React.createElement(View, {
32000
+ style: styles$j.row
32001
+ }, /*#__PURE__*/React.createElement(LabelLarge, null, lineLabel), /*#__PURE__*/React.createElement(Strut, {
32002
+ size: spacing.xSmall_8
32003
+ }), /*#__PURE__*/React.createElement(LineSwatch, {
32004
+ color: lineColor,
32005
+ lineStyle: "solid"
32006
+ }))
32007
+ }, /*#__PURE__*/React.createElement(View, {
32008
+ style: [styles$j.row, styles$j.spaceUnder]
32009
+ }, /*#__PURE__*/React.createElement(ColorSelect, {
32010
+ selectedValue: lineColor,
32011
+ onChange: handleColorChange
32012
+ })), isInvalid && /*#__PURE__*/React.createElement(LabelMedium, {
32013
+ style: styles$j.errorText
32014
+ }, lengthErrorMessage), /*#__PURE__*/React.createElement(PerseusEditorAccordion, {
32015
+ expanded: true // Initial state is expanded
32016
+ ,
32017
+ containerStyle: styles$j.container,
32018
+ panelStyle: styles$j.accordionPanel,
32019
+ header: /*#__PURE__*/React.createElement(View, {
32020
+ style: styles$j.row
32021
+ }, /*#__PURE__*/React.createElement(LabelLarge, null, `Tail (${tail[0]}, ${tail[1]})`))
32022
+ }, /*#__PURE__*/React.createElement(CoordinatePairInput, {
32023
+ coord: tail,
32024
+ error: isInvalid,
32025
+ onChange: newProps => {
32026
+ handleChangePoint(newProps, 0);
31742
32027
  }
31743
- }, "Graph the correct answer in the graph below and ensure the equation or point coordinates displayed represent the correct answer."), /*#__PURE__*/React.createElement(BodyMonospace, {
31744
- style: {
31745
- fontSize: 12,
31746
- backgroundColor: "#eee",
31747
- paddingInline: spacing.xxSmall_6,
31748
- borderColor: "#ccc",
31749
- borderStyle: "solid",
31750
- borderWidth: 1
32028
+ })), /*#__PURE__*/React.createElement(PerseusEditorAccordion, {
32029
+ expanded: true // Initial state is expanded
32030
+ ,
32031
+ containerStyle: styles$j.container,
32032
+ panelStyle: styles$j.accordionPanel,
32033
+ header: /*#__PURE__*/React.createElement(View, {
32034
+ style: styles$j.row
32035
+ }, /*#__PURE__*/React.createElement(LabelLarge, null, `Tip (${tip[0]}, ${tip[1]})`))
32036
+ }, /*#__PURE__*/React.createElement(CoordinatePairInput, {
32037
+ coord: tip,
32038
+ error: isInvalid,
32039
+ onChange: newProps => {
32040
+ handleChangePoint(newProps, 1);
31751
32041
  }
31752
- }, props.equationString)), props.children));
31753
- }
31754
-
31755
- function InteractiveGraphDescription(props) {
31756
- const {
31757
- ariaLabelValue,
31758
- ariaDescriptionValue,
31759
- onChange
31760
- } = props;
31761
- const [isOpen, setIsOpen] = React.useState(true);
31762
- return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Heading, {
31763
- title: "Description",
31764
- isCollapsible: true,
31765
- isOpen: isOpen,
31766
- onToggle: setIsOpen
31767
- }), isOpen && /*#__PURE__*/React.createElement(View, null, /*#__PURE__*/React.createElement(LabelXSmall, {
31768
- style: styles$k.caption
31769
- }, "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.createElement(LabelLarge, {
31770
- tag: "label"
31771
- }, "Title", /*#__PURE__*/React.createElement(TextField, {
31772
- value: ariaLabelValue,
31773
- onChange: newValue => onChange({
31774
- fullGraphAriaLabel: newValue
31775
- }),
31776
- style: styles$k.spaceAbove
31777
- })), /*#__PURE__*/React.createElement(Strut, {
31778
- size: spacing.small_12
31779
- }), /*#__PURE__*/React.createElement(LabelLarge, {
31780
- tag: "label"
31781
- }, "Description", /*#__PURE__*/React.createElement(TextArea, {
31782
- rows: 8,
31783
- resizeType: "vertical",
31784
- value: ariaDescriptionValue,
31785
- onChange: newValue => onChange({
31786
- fullGraphAriaDescription: newValue
31787
- }),
31788
- style: styles$k.spaceAbove
31789
- }))));
31790
- }
31791
- const styles$k = StyleSheet.create({
31792
- caption: {
31793
- color: color.offBlack64,
31794
- paddingTop: spacing.xxSmall_6,
31795
- paddingBottom: spacing.xxSmall_6
31796
- },
31797
- spaceAbove: {
31798
- marginTop: spacing.xxxSmall_4
31799
- }
31800
- });
31801
-
31802
- const {
31803
- ButtonGroup: ButtonGroup$2,
31804
- InfoTip: InfoTip$c,
31805
- RangeInput: RangeInput$4
31806
- } = components;
31807
- const defaultBackgroundImage$1 = {
31808
- url: null,
31809
- width: 0,
31810
- height: 0
32042
+ })), /*#__PURE__*/React.createElement(LockedFigureSettingsActions, {
32043
+ figureType: props.type,
32044
+ onMove: onMove,
32045
+ onRemove: onRemove
32046
+ }));
31811
32047
  };
31812
- function numSteps(range, step) {
31813
- return Math.floor((range[1] - range[0]) / step);
31814
- }
31815
- class InteractiveGraphSettings extends React.Component {
31816
- static stateFromProps(props) {
31817
- return {
31818
- labelsTextbox: props.labels,
31819
- gridStepTextbox: props.gridStep,
31820
- snapStepTextbox: props.snapStep,
31821
- stepTextbox: props.step,
31822
- rangeTextbox: props.range,
31823
- // Shallow-copied clone
31824
- backgroundImage: _extends({}, props.backgroundImage)
31825
- };
31826
- }
31827
- constructor(props) {
31828
- super(props);
31829
- this._isMounted = false;
31830
- this.bgUrlRef = /*#__PURE__*/React.createRef();
31831
- this.labelXRef = /*#__PURE__*/React.createRef();
31832
- this.labelYRef = /*#__PURE__*/React.createRef();
31833
- this.change = (...args) => {
31834
- return Changeable.change.apply(this, args);
31835
- };
31836
- this.changeBackgroundUrl = e => {
31837
- var _this$bgUrlRef$curren;
31838
- // Only continue on blur or "enter"
31839
- if (e.type === "keypress" && e.key !== "Enter") {
31840
- return;
31841
- }
31842
- const setUrl = (url, width, height) => {
31843
- // Shallow-copied clone
31844
- const image = _extends({}, this.props.backgroundImage);
31845
- image.url = url;
31846
- image.width = width;
31847
- image.height = height;
31848
- this.setState({
31849
- backgroundImage: image
31850
- }, this.changeGraph);
31851
- };
31852
- const url = (_this$bgUrlRef$curren = this.bgUrlRef.current) == null ? void 0 : _this$bgUrlRef$curren.value;
31853
- if (url) {
31854
- Util.getImageSize(url, (width, height) => {
31855
- if (this._isMounted) {
31856
- setUrl(url, width, height);
31857
- }
31858
- });
31859
- } else {
31860
- setUrl(null, 0, 0);
31861
- }
31862
- };
31863
- this.renderLabelChoices = choices => {
31864
- return choices.map(nameAndValue => /*#__PURE__*/React.createElement("option", {
31865
- key: nameAndValue[1],
31866
- value: nameAndValue[1]
31867
- }, nameAndValue[0]));
31868
- };
31869
- this.validRange = range => {
31870
- const numbers = _.every(range, function (num) {
31871
- return _.isFinite(num);
31872
- });
31873
- if (!numbers) {
31874
- return "Range must be a valid number";
31875
- }
31876
- if (range[0] >= range[1]) {
31877
- return "Range must have a higher number on the right";
31878
- }
31879
- return true;
31880
- };
31881
- this.validateStepValue = settings => {
31882
- const {
31883
- step,
31884
- range,
31885
- name,
31886
- minTicks,
31887
- maxTicks
31888
- } = settings;
31889
- const nSteps = numSteps(range, step);
31890
- if (nSteps < minTicks) {
31891
- return name + " is too large, there must be at least " + minTicks + " ticks.";
31892
- }
31893
- if (nSteps > maxTicks) {
31894
- return name + " is too small, there can be at most " + maxTicks + " ticks.";
31895
- }
31896
- return true;
31897
- };
31898
- this.validSnapStep = (step, range) => {
31899
- return this.validateStepValue({
31900
- step: step,
31901
- range: range,
31902
- name: "Snap step",
31903
- minTicks: 5,
31904
- maxTicks: 60
31905
- });
31906
- };
31907
- this.validGridStep = (step, range) => {
31908
- return this.validateStepValue({
31909
- step: step,
31910
- range: range,
31911
- name: "Grid step",
31912
- minTicks: 3,
31913
- maxTicks: 60
31914
- });
31915
- };
31916
- this.validStep = (step, range) => {
31917
- return this.validateStepValue({
31918
- step: step,
31919
- range: range,
31920
- name: "Step",
31921
- minTicks: 3,
31922
- maxTicks: 20
31923
- });
31924
- };
31925
- this.validBackgroundImageSize = image => {
31926
- // Ignore empty images
31927
- if (!image.url) {
31928
- return true;
31929
- }
31930
- const validSize = image.width <= 450 && image.height <= 450;
31931
- if (!validSize) {
31932
- return "Image must be smaller than 450px x 450px.";
31933
- }
31934
- return true;
31935
- };
31936
- this.validateGraphSettings = (range, step, gridStep, snapStep, image) => {
31937
- const self = this;
31938
- let msg;
31939
- const goodRange = _.every(range, function (range) {
31940
- msg = self.validRange(range);
31941
- return msg === true;
31942
- });
31943
- if (!goodRange) {
31944
- return msg;
31945
- }
31946
- const goodStep = _.every(step, function (step, i) {
31947
- msg = self.validStep(step, range[i]);
31948
- return msg === true;
31949
- });
31950
- if (!goodStep) {
31951
- return msg;
31952
- }
31953
- const goodGridStep = _.every(gridStep, function (gridStep, i) {
31954
- msg = self.validGridStep(gridStep, range[i]);
31955
- return msg === true;
31956
- });
31957
- if (!goodGridStep) {
31958
- return msg;
31959
- }
31960
- const goodSnapStep = _.every(snapStep, function (snapStep, i) {
31961
- msg = self.validSnapStep(snapStep, range[i]);
31962
- return msg === true;
31963
- });
31964
- if (!goodSnapStep) {
31965
- return msg;
31966
- }
31967
- const goodImageSize = this.validBackgroundImageSize(image);
31968
- if (goodImageSize !== true) {
31969
- msg = goodImageSize;
31970
- return msg;
31971
- }
31972
- return true;
31973
- };
31974
- this.changeLabel = (i, e) => {
31975
- const val = e.target.value;
31976
- const labels = this.state.labelsTextbox.slice();
31977
- labels[i] = val;
31978
- this.setState({
31979
- labelsTextbox: labels
31980
- }, this.changeGraph);
31981
- };
31982
- this.changeRange = (i, values) => {
31983
- const ranges = this.state.rangeTextbox.slice();
31984
- ranges[i] = values;
31985
- const step = this.state.stepTextbox.slice();
31986
- const gridStep = this.state.gridStepTextbox.slice();
31987
- const snapStep = this.state.snapStepTextbox.slice();
31988
- const scale = Util.scaleFromExtent(ranges[i], this.props.box[i]);
31989
- if (this.validRange(ranges[i]) === true) {
31990
- step[i] = Util.tickStepFromExtent(ranges[i], this.props.box[i]);
31991
- const gridStepValue = Util.gridStepFromTickStep(step[i], scale);
31992
- if (gridStepValue) {
31993
- gridStep[i] = gridStepValue;
31994
- }
31995
- snapStep[i] = gridStep[i] / 2;
31996
- }
31997
- this.setState({
31998
- stepTextbox: step,
31999
- gridStepTextbox: gridStep,
32000
- snapStepTextbox: snapStep,
32001
- rangeTextbox: ranges
32002
- }, this.changeGraph);
32003
- };
32004
- this.changeStep = step => {
32005
- this.setState({
32006
- stepTextbox: step
32007
- }, this.changeGraph);
32008
- };
32009
- this.changeSnapStep = snapStep => {
32010
- this.setState({
32011
- snapStepTextbox: snapStep
32012
- }, this.changeGraph);
32013
- };
32014
- this.changeGridStep = gridStep => {
32015
- this.setState({
32016
- gridStepTextbox: gridStep,
32017
- snapStepTextbox: _.map(gridStep, function (step) {
32018
- return step / 2;
32019
- })
32020
- }, this.changeGraph);
32021
- };
32022
- this.changeGraph = () => {
32023
- const labels = this.state.labelsTextbox;
32024
- const range = _.map(this.state.rangeTextbox, function (range) {
32025
- return _.map(range, Number);
32026
- });
32027
- const step = _.map(this.state.stepTextbox, Number);
32028
- const gridStep = this.state.gridStepTextbox;
32029
- const snapStep = this.state.snapStepTextbox;
32030
- const image = this.state.backgroundImage;
32048
+ const styles$j = StyleSheet.create({
32049
+ accordionPanel: {
32050
+ paddingBottom: spacing.medium_16
32051
+ },
32052
+ container: {
32053
+ marginTop: spacing.xSmall_8,
32054
+ marginBottom: 0,
32055
+ marginLeft: -spacing.xxxSmall_4,
32056
+ marginRight: -spacing.xxxSmall_4,
32057
+ backgroundColor: color.white
32058
+ },
32059
+ errorText: {
32060
+ color: color.red,
32061
+ marginTop: spacing.xSmall_8
32062
+ },
32063
+ row: {
32064
+ flexDirection: "row",
32065
+ alignItems: "center"
32066
+ }
32067
+ });
32031
32068
 
32032
- // validationResult is either:
32033
- // true -> the settings are valid
32034
- // a string -> the settings are invalid, and the explanation
32035
- // is contained in the string
32036
- // TODO(aria): Refactor this to not be confusing
32037
- const validationResult = this.validateGraphSettings(range, step, gridStep, snapStep, image);
32038
- if (validationResult === true) {
32039
- // either true or a string
32040
- this.change({
32041
- valid: true,
32042
- labels: labels,
32043
- range: range,
32044
- step: step,
32045
- gridStep: gridStep,
32046
- snapStep: snapStep,
32047
- backgroundImage: image
32048
- });
32049
- } else {
32050
- this.change({
32051
- valid: validationResult // a string message, not false
32052
- });
32069
+ /**
32070
+ * LockedFigureSettings takes in a figure and renders the
32071
+ * respective settings for that figure type.
32072
+ *
32073
+ * Used in the interactive graph editor's locked figures section.
32074
+ */
32075
+
32076
+ // Union this type with other locked figure types when they are added.
32077
+
32078
+ const LockedFigureSettings = props => {
32079
+ switch (props.type) {
32080
+ case "point":
32081
+ return /*#__PURE__*/React.createElement(LockedPointSettings, props);
32082
+ case "line":
32083
+ return /*#__PURE__*/React.createElement(LockedLineSettings, props);
32084
+ case "vector":
32085
+ return /*#__PURE__*/React.createElement(LockedVectorSettings, props);
32086
+ case "ellipse":
32087
+ return /*#__PURE__*/React.createElement(LockedEllipseSettings, props);
32088
+ case "polygon":
32089
+ return /*#__PURE__*/React.createElement(LockedPolygonSettings, props);
32090
+ case "function":
32091
+ return /*#__PURE__*/React.createElement(LockedFunctionSettings, props);
32092
+ case "label":
32093
+ if (props.showLabelsFlag) {
32094
+ return /*#__PURE__*/React.createElement(LockedLabelSettings, props);
32053
32095
  }
32096
+ break;
32097
+ }
32098
+ return null;
32099
+ };
32100
+
32101
+ const LockedFiguresSection = props => {
32102
+ var _props$figures;
32103
+ // Keep track of all figures' accordions' expanded states for the
32104
+ // expand/collapse all button. Set the whole array to false initially.
32105
+ const collapsedStateArray = Array(((_props$figures = props.figures) != null ? _props$figures : []).length).fill(false);
32106
+ const [expandedStates, setExpandedStates] = React.useState(collapsedStateArray);
32107
+ const [isExpanded, setIsExpanded] = React.useState(true);
32108
+ const uniqueId = useUniqueIdWithMock().get("locked-figures-section");
32109
+ const {
32110
+ figures,
32111
+ onChange
32112
+ } = props;
32113
+ function addLockedFigure(newFigure) {
32114
+ const lockedFigures = figures || [];
32115
+ const newProps = {
32116
+ lockedFigures: [...lockedFigures, getDefaultFigureForType(newFigure)]
32054
32117
  };
32055
- this.state = _extends({
32056
- isExpanded: true
32057
- }, InteractiveGraphSettings.stateFromProps(props));
32118
+ onChange(newProps);
32119
+ setExpandedStates([...expandedStates, true]);
32058
32120
  }
32059
- componentDidMount() {
32060
- // TODO(scottgrant): This is a hack to remove the deprecated call to
32061
- // this.isMounted() but is still considered an anti-pattern.
32062
- this._isMounted = true;
32063
- this.changeGraph = _.debounce(this.changeGraph, 300);
32121
+ function moveLockedFigure(index, movement) {
32122
+ // Don't allow moving the first figure up or the last figure down.
32123
+ if (index === 0 && (movement === "back" || movement === "backward")) {
32124
+ return;
32125
+ }
32126
+ if (figures && index === figures.length - 1 && (movement === "front" || movement === "forward")) {
32127
+ return;
32128
+ }
32129
+ const lockedFigures = figures || [];
32130
+ const newFigures = [...lockedFigures];
32131
+ const newExpandedStates = [...expandedStates];
32132
+
32133
+ // First, remove the figure from its current position
32134
+ // in the figures array and the expanded states array.
32135
+ const [removedFigure] = newFigures.splice(index, 1);
32136
+ newExpandedStates.splice(index, 1);
32137
+
32138
+ // Then, add it back in the new position. Add "true" to the
32139
+ // expanded states array for the new position (it must already
32140
+ // be open since the button to move it is being pressed from there).
32141
+ switch (movement) {
32142
+ case "back":
32143
+ newFigures.unshift(removedFigure);
32144
+ newExpandedStates.unshift(true);
32145
+ break;
32146
+ case "backward":
32147
+ newFigures.splice(index - 1, 0, removedFigure);
32148
+ newExpandedStates.splice(index - 1, 0, true);
32149
+ break;
32150
+ case "forward":
32151
+ newFigures.splice(index + 1, 0, removedFigure);
32152
+ newExpandedStates.splice(index + 1, 0, true);
32153
+ break;
32154
+ case "front":
32155
+ newFigures.push(removedFigure);
32156
+ newExpandedStates.push(true);
32157
+ break;
32158
+ }
32159
+ onChange({
32160
+ lockedFigures: newFigures
32161
+ });
32162
+ setExpandedStates(newExpandedStates);
32064
32163
  }
32065
- UNSAFE_componentWillReceiveProps(nextProps) {
32066
- // Make sure that state updates when switching
32067
- // between different items in a multi-item editor.
32068
- if (!_.isEqual(this.props.labels, nextProps.labels) || !_.isEqual(this.props.gridStep, nextProps.gridStep) || !_.isEqual(this.props.snapStep, nextProps.snapStep) || !_.isEqual(this.props.step, nextProps.step) || !_.isEqual(this.props.range, nextProps.range) || !_.isEqual(this.props.backgroundImage, nextProps.backgroundImage)) {
32069
- this.setState(InteractiveGraphSettings.stateFromProps(nextProps));
32164
+ function removeLockedFigure(index) {
32165
+ // eslint-disable-next-line no-alert
32166
+ if (window.confirm("Are you sure you want to delete this figure?")) {
32167
+ const lockedFigures = figures || [];
32168
+ onChange({
32169
+ lockedFigures: [...lockedFigures.slice(0, index), ...lockedFigures.slice(index + 1)]
32170
+ });
32171
+
32172
+ // Update expanded states
32173
+ const newExpandedStates = [...expandedStates];
32174
+ newExpandedStates.splice(index, 1);
32175
+ setExpandedStates(newExpandedStates);
32070
32176
  }
32071
32177
  }
32072
- componentWillUnmount() {
32073
- this._isMounted = false;
32178
+ function changeProps(index,
32179
+ // Omit the type from the figure props so it doesn't think
32180
+ // we're trying to pass in the props for the wrong type.
32181
+ figureProps) {
32182
+ const lockedFigures = figures || [];
32183
+ const newFigures = {
32184
+ lockedFigures: [...lockedFigures.slice(0, index), _extends({}, lockedFigures[index], figureProps), ...lockedFigures.slice(index + 1)]
32185
+ };
32186
+ onChange(newFigures);
32074
32187
  }
32075
- render() {
32076
- return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Heading, {
32077
- title: "Common Graph Settings",
32078
- isOpen: this.state.isExpanded,
32079
- isCollapsible: true,
32080
- onToggle: () => this.setState({
32081
- isExpanded: !this.state.isExpanded
32082
- })
32083
- }), this.state.isExpanded && /*#__PURE__*/React.createElement(View, null, /*#__PURE__*/React.createElement("div", {
32084
- className: "graph-settings"
32085
- }, /*#__PURE__*/React.createElement("div", {
32086
- className: "perseus-widget-row"
32087
- }, /*#__PURE__*/React.createElement("div", {
32088
- className: "perseus-widget-left-col"
32089
- }, /*#__PURE__*/React.createElement(LabeledRow, {
32090
- label: "x Label"
32091
- }, /*#__PURE__*/React.createElement("input", {
32092
- type: "text",
32093
- className: "graph-settings-axis-label",
32094
- ref: this.labelXRef,
32095
- onChange: e => this.changeLabel(0, e),
32096
- value: this.state.labelsTextbox[0] || ""
32097
- }))), /*#__PURE__*/React.createElement("div", {
32098
- className: "perseus-widget-right-col"
32099
- }, /*#__PURE__*/React.createElement(LabeledRow, {
32100
- label: "y Label"
32101
- }, /*#__PURE__*/React.createElement("input", {
32102
- type: "text",
32103
- className: "graph-settings-axis-label",
32104
- ref: this.labelYRef,
32105
- onChange: e => this.changeLabel(1, e),
32106
- value: this.state.labelsTextbox[1] || ""
32107
- })))), /*#__PURE__*/React.createElement("div", {
32108
- className: "perseus-widget-row"
32109
- }, /*#__PURE__*/React.createElement("div", {
32110
- className: "perseus-widget-left-col"
32111
- }, /*#__PURE__*/React.createElement(LabeledRow, {
32112
- label: "x Range"
32113
- }, /*#__PURE__*/React.createElement(RangeInput$4, {
32114
- value: this.state.rangeTextbox[0],
32115
- onChange: vals => this.changeRange(0, vals)
32116
- }))), /*#__PURE__*/React.createElement("div", {
32117
- className: "perseus-widget-right-col"
32118
- }, /*#__PURE__*/React.createElement(LabeledRow, {
32119
- label: "y Range"
32120
- }, /*#__PURE__*/React.createElement(RangeInput$4, {
32121
- value: this.state.rangeTextbox[1],
32122
- onChange: vals => this.changeRange(1, vals)
32123
- })))), /*#__PURE__*/React.createElement("div", {
32124
- className: "perseus-widget-row"
32125
- }, /*#__PURE__*/React.createElement("div", {
32126
- className: "perseus-widget-left-col"
32127
- }, /*#__PURE__*/React.createElement(LabeledRow, {
32128
- label: "Tick Step"
32129
- }, /*#__PURE__*/React.createElement(RangeInput$4, {
32130
- value: this.state.stepTextbox,
32131
- onChange: this.changeStep
32132
- }))), /*#__PURE__*/React.createElement("div", {
32133
- className: "perseus-widget-right-col"
32134
- }, /*#__PURE__*/React.createElement(LabeledRow, {
32135
- label: "Grid Step"
32136
- }, /*#__PURE__*/React.createElement(RangeInput$4, {
32137
- value: this.state.gridStepTextbox,
32138
- onChange: this.changeGridStep
32139
- })))), /*#__PURE__*/React.createElement("div", {
32140
- className: "perseus-widget-row"
32141
- }, /*#__PURE__*/React.createElement("div", {
32142
- className: "perseus-widget-left-col"
32143
- }, /*#__PURE__*/React.createElement(LabeledRow, {
32144
- label: "Snap Step"
32145
- }, /*#__PURE__*/React.createElement(RangeInput$4, {
32146
- value: this.state.snapStepTextbox,
32147
- onChange: this.changeSnapStep
32148
- })))), /*#__PURE__*/React.createElement("div", {
32149
- className: "perseus-widget-row"
32150
- }, /*#__PURE__*/React.createElement(LabeledRow, {
32151
- label: "Markings:"
32152
- }, /*#__PURE__*/React.createElement(ButtonGroup$2, {
32153
- value: this.props.markings,
32154
- allowEmpty: false,
32155
- buttons: [{
32156
- value: "graph",
32157
- content: "Graph"
32158
- }, {
32159
- value: "grid",
32160
- content: "Grid"
32161
- }, {
32162
- value: "none",
32163
- content: "None"
32164
- }],
32165
- onChange: this.change("markings")
32166
- }))), /*#__PURE__*/React.createElement("div", {
32167
- className: "perseus-widget-left-col"
32168
- }, /*#__PURE__*/React.createElement(Checkbox$1, {
32169
- label: "Show tooltips",
32170
- checked: this.props.showTooltips,
32171
- onChange: value => {
32172
- this.change({
32173
- showTooltips: value
32174
- });
32175
- }
32176
- }))), /*#__PURE__*/React.createElement(LabeledRow, {
32177
- label: "Background image URL:",
32178
- style: styles$j.resetSpaceTop
32179
- }, /*#__PURE__*/React.createElement("input", {
32180
- type: "text",
32181
- className: css(styles$j.backgroundUrlInput),
32182
- ref: this.bgUrlRef,
32183
- value: this.state.backgroundImage.url || "",
32184
- onChange: e => {
32185
- // Shallow-copied clone
32186
- const image = _extends({}, this.props.backgroundImage);
32187
- image.url = e.target.value;
32188
- this.setState({
32189
- backgroundImage: image
32190
- });
32191
- },
32192
- onKeyPress: this.changeBackgroundUrl,
32193
- onBlur: this.changeBackgroundUrl
32194
- }), /*#__PURE__*/React.createElement(InfoTip$c, null, /*#__PURE__*/React.createElement("p", null, "Create an image in graphie, or use the \"Add image\" function to create a background."))), /*#__PURE__*/React.createElement(View, {
32195
- style: styles$j.protractorSection
32196
- }, /*#__PURE__*/React.createElement(View, {
32197
- style: styles$j.checkboxRow
32198
- }, /*#__PURE__*/React.createElement(Checkbox$1, {
32199
- label: "Show protractor",
32200
- checked: this.props.showProtractor,
32201
- onChange: value => {
32202
- this.change({
32203
- showProtractor: value
32204
- });
32205
- },
32206
- style: styles$j.resetSpaceTop
32207
- })), this.props.showProtractor && /*#__PURE__*/React.createElement(Banner, {
32208
- layout: "floating",
32209
- text: "The protractor is not accessible. Please consider an alternate approach.",
32210
- kind: "warning"
32211
- }))));
32188
+ function toggleExpanded(newValue) {
32189
+ setExpandedStates(Array(figures == null ? void 0 : figures.length).fill(newValue));
32212
32190
  }
32213
- }
32214
- InteractiveGraphSettings.defaultProps = {
32215
- box: [interactiveSizes.defaultBoxSizeSmall, interactiveSizes.defaultBoxSizeSmall],
32216
- labels: ["x", "y"],
32217
- range: [[-10, 10], [-10, 10]],
32218
- step: [1, 1],
32219
- gridStep: [1, 1],
32220
- snapStep: [1, 1],
32221
- valid: true,
32222
- backgroundImage: defaultBackgroundImage$1,
32223
- markings: "graph",
32224
- showProtractor: false,
32225
- showTooltips: false
32191
+ const allCollapsed = expandedStates.every(value => !value);
32192
+ const buttonLabel = allCollapsed ? "Expand all" : "Collapse all";
32193
+ const showExpandButton = !!(figures != null && figures.length);
32194
+ return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Heading, {
32195
+ title: "Locked Figures",
32196
+ isOpen: isExpanded,
32197
+ onToggle: () => setIsExpanded(!isExpanded),
32198
+ isCollapsible: true
32199
+ }), isExpanded && /*#__PURE__*/React.createElement(View, null, figures == null ? void 0 : figures.map((figure, index) => {
32200
+ return /*#__PURE__*/React.createElement(LockedFigureSettings, _extends({
32201
+ key: `${uniqueId}-locked-${figure}-${index}`,
32202
+ flags: props.flags,
32203
+ showLabelsFlag: props.showLabelsFlag,
32204
+ expanded: expandedStates[index],
32205
+ onToggle: newValue => {
32206
+ const newExpanded = [...expandedStates];
32207
+ newExpanded[index] = newValue;
32208
+ setExpandedStates(newExpanded);
32209
+ }
32210
+ }, figure, {
32211
+ onChangeProps: newProps => changeProps(index, newProps),
32212
+ onMove: movement => moveLockedFigure(index, movement),
32213
+ onRemove: () => removeLockedFigure(index)
32214
+ }));
32215
+ }), /*#__PURE__*/React.createElement(View, {
32216
+ style: styles$i.buttonContainer
32217
+ }, /*#__PURE__*/React.createElement(LockedFigureSelect, {
32218
+ showLabelsFlag: props.showLabelsFlag,
32219
+ id: `${uniqueId}-select`,
32220
+ onChange: addLockedFigure
32221
+ }), /*#__PURE__*/React.createElement(Strut, {
32222
+ size: spacing.small_12
32223
+ }), showExpandButton && /*#__PURE__*/React.createElement(Button, {
32224
+ kind: "secondary",
32225
+ onClick: () => toggleExpanded(allCollapsed),
32226
+ style: styles$i.button
32227
+ }, buttonLabel))));
32226
32228
  };
32227
- const styles$j = StyleSheet.create({
32228
- resetSpaceTop: {
32229
- marginTop: 0
32230
- },
32231
- backgroundUrlInput: {
32232
- border: `1px solid ${color.offBlack32}`,
32233
- borderRadius: spacing.xxxSmall_4,
32234
- padding: spacing.xxxSmall_4
32235
- },
32236
- checkboxRow: {
32229
+ const styles$i = StyleSheet.create({
32230
+ buttonContainer: {
32237
32231
  flexDirection: "row",
32238
- alignItems: "center",
32239
- justifyContent: "space-between",
32240
- marginBottom: spacing.xSmall_8
32232
+ alignItems: "center"
32241
32233
  },
32242
- protractorSection: {
32234
+ button: {
32243
32235
  marginTop: spacing.xSmall_8,
32244
- borderTop: `1px solid ${color.offBlack16}`,
32245
- paddingTop: spacing.xSmall_8,
32246
- paddingBottom: spacing.xSmall_8,
32247
- borderBottom: `1px solid ${color.offBlack16}`
32236
+ flexGrow: 1
32248
32237
  }
32249
32238
  });
32250
32239
 
32251
- const SegmentCountSelector = ({
32252
- numSegments: _numSegments = 1,
32253
- onChange
32254
- }) => /*#__PURE__*/React.createElement(SingleSelect, {
32255
- key: "segment-select",
32256
- selectedValue: `${_numSegments}`
32257
- // Never uses placeholder, always has value
32258
- ,
32259
- placeholder: "",
32260
- onChange: newValue => {
32261
- const num = +newValue;
32262
- onChange(num);
32263
- },
32264
- style: styles$i.singleSelectShort
32265
- }, _.range(1, 7).map(n => /*#__PURE__*/React.createElement(OptionItem, {
32266
- key: n,
32267
- value: `${n}`,
32268
- label: `${n} segment${n > 1 ? "s" : ""}`
32269
- })));
32270
- const styles$i = StyleSheet.create({
32271
- singleSelectShort: {
32272
- // Non-standard spacing, but it's the smallest we can go
32273
- // without running into styling issues with the dropdown.
32274
- height: 26
32240
+ function getStartCoords(graph) {
32241
+ if ("startCoords" in graph) {
32242
+ return graph.startCoords;
32275
32243
  }
32276
- });
32244
+ return undefined;
32245
+ }
32246
+ function getDefaultGraphStartCoords(graph, range, step) {
32247
+ switch (graph.type) {
32248
+ case "linear":
32249
+ case "ray":
32250
+ return getLineCoords(_extends({}, graph, {
32251
+ startCoords: undefined
32252
+ }), range, step);
32253
+ case "segment":
32254
+ return getSegmentCoords(_extends({}, graph, {
32255
+ startCoords: undefined
32256
+ }), range, step);
32257
+ case "linear-system":
32258
+ return getLinearSystemCoords(_extends({}, graph, {
32259
+ startCoords: undefined
32260
+ }), range, step);
32261
+ case "circle":
32262
+ const startCoords = getCircleCoords(_extends({}, graph, {
32263
+ startCoords: undefined
32264
+ }));
32265
+ const radius = vector.length(vector.subtract(startCoords.radiusPoint, startCoords.center));
32266
+ return {
32267
+ center: startCoords.center,
32268
+ radius
32269
+ };
32270
+ case "sinusoid":
32271
+ return getSinusoidCoords(_extends({}, graph, {
32272
+ startCoords: undefined
32273
+ }), range, step);
32274
+ case "quadratic":
32275
+ return getQuadraticCoords(_extends({}, graph, {
32276
+ startCoords: undefined
32277
+ }), range, step);
32278
+ case "point":
32279
+ return getPointCoords(_extends({}, graph, {
32280
+ startCoords: undefined
32281
+ }), range, step);
32282
+ case "polygon":
32283
+ return getPolygonCoords(_extends({}, graph, {
32284
+ startCoords: undefined
32285
+ }), range, step);
32286
+ case "angle":
32287
+ return getAngleCoords({
32288
+ graph: _extends({}, graph, {
32289
+ startCoords: undefined
32290
+ }),
32291
+ range,
32292
+ step
32293
+ });
32294
+ default:
32295
+ return undefined;
32296
+ }
32297
+ }
32298
+ const getSinusoidEquation = startCoords => {
32299
+ // Get coefficients
32300
+ // It's assumed that p1 is the root and p2 is the first peak
32301
+ const p1 = startCoords[0];
32302
+ const p2 = startCoords[1];
32303
+
32304
+ // Resulting coefficients are canonical for this sine curve
32305
+ const amplitude = p2[1] - p1[1];
32306
+ const angularFrequency = Math.PI / (2 * (p2[0] - p1[0]));
32307
+ const phase = p1[0] * angularFrequency;
32308
+ const verticalOffset = p1[1];
32309
+ return "y = " + amplitude.toFixed(3) + "sin(" + angularFrequency.toFixed(3) + "x - " + phase.toFixed(3) + ") + " + verticalOffset.toFixed(3);
32310
+ };
32311
+ const getQuadraticEquation = startCoords => {
32312
+ const p1 = startCoords[0];
32313
+ const p2 = startCoords[1];
32314
+ const p3 = startCoords[2];
32315
+ const denom = (p1[0] - p2[0]) * (p1[0] - p3[0]) * (p2[0] - p3[0]);
32316
+ if (denom === 0) {
32317
+ // Many of the callers assume that the return value is always defined.
32318
+ return "Division by zero error";
32319
+ }
32320
+ const a = (p3[0] * (p2[1] - p1[1]) + p2[0] * (p1[1] - p3[1]) + p1[0] * (p3[1] - p2[1])) / denom;
32321
+ 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;
32322
+ 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;
32323
+ return "y = " + a.toFixed(3) + "x^2 + " + b.toFixed(3) + "x + " + c.toFixed(3);
32324
+ };
32325
+ const findAngle = (point1, point2) => {
32326
+ const x = point1[0] - point2[0];
32327
+ const y = point1[1] - point2[1];
32328
+ return (180 + Math.atan2(-y, -x) * 180 / Math.PI + 360) % 360;
32329
+ };
32330
+ const getAngleEquation = startCoords => {
32331
+ const [point1, vertex, point2] = startCoords;
32332
+ const angle = findAngle(point2, vertex) - findAngle(point1, vertex);
32333
+ const roundedAngle = angle.toFixed(0);
32334
+ return `${roundedAngle}\u00B0 angle at (${vertex[0]}, ${vertex[1]})`;
32335
+ };
32336
+ const shouldShowStartCoordsUI = (graph, isStatic) => {
32337
+ // We don't show the start coords UI for static graphs, since
32338
+ // the coords shown for this are determined by the coords on the
32339
+ // "correct" graph. The start coords would not be used here.
32340
+ if (isStatic) {
32341
+ return false;
32342
+ }
32343
+ switch (graph.type) {
32344
+ case "point":
32345
+ return graph.numPoints !== "unlimited";
32346
+ case "polygon":
32347
+ return graph.numSides !== "unlimited" && graph.snapTo !== "angles" && graph.snapTo !== "sides";
32348
+ case "none":
32349
+ return false;
32350
+ case "angle":
32351
+ case "circle":
32352
+ case "linear":
32353
+ case "linear-system":
32354
+ case "quadratic":
32355
+ case "ray":
32356
+ case "segment":
32357
+ case "sinusoid":
32358
+ return true;
32359
+ default:
32360
+ throw new UnreachableCaseError(graph);
32361
+ }
32362
+ };
32277
32363
 
32278
32364
  const StartCoordsAngle = props => {
32279
32365
  const {
@@ -32783,13 +32869,12 @@ class InteractiveGraphEditor extends React.Component {
32783
32869
  // eslint-disable-next-line react/no-string-refs
32784
32870
  const graph = this.refs.graph;
32785
32871
  if (graph) {
32786
- var _this$props$graph2;
32787
32872
  // @ts-expect-error TS2339 Property 'getUserInput' does not exist on type 'ReactInstance'. Property 'getUserInput' does not exist on type 'Component<any, {}, any>'.
32788
32873
  const correct = graph && graph.getUserInput();
32789
32874
  _.extend(json, {
32790
32875
  graph: {
32791
32876
  type: correct.type,
32792
- startCoords: (_this$props$graph2 = this.props.graph) == null ? void 0 : _this$props$graph2.startCoords
32877
+ startCoords: this.props.graph && getStartCoords(this.props.graph)
32793
32878
  },
32794
32879
  correct: correct
32795
32880
  });
@@ -32804,7 +32889,7 @@ class InteractiveGraphEditor extends React.Component {
32804
32889
  return json;
32805
32890
  }
32806
32891
  render() {
32807
- var _this$props$graph$typ, _this$props$graph3, _this$props$apiOption, _this$props$fullGraph, _this$props$fullGraph2, _this$props$correct, _this$props$correct2, _this$props$correct3, _this$props$correct4, _this$props$correct5, _this$props$correct6, _this$props$correct7, _this$props$correct8, _this$props$correct9, _this$props$correct10, _this$props$correct11, _this$props$graph9, _this$props$apiOption2, _this$props$apiOption3;
32892
+ var _this$props$graph$typ, _this$props$graph2, _this$props$apiOption, _this$props$apiOption2, _this$props$fullGraph, _this$props$fullGraph2, _this$props$correct, _this$props$correct2, _this$props$correct3, _this$props$correct4, _this$props$correct5, _this$props$correct6, _this$props$correct7, _this$props$correct8, _this$props$correct9, _this$props$correct10, _this$props$correct11, _this$props$graph8, _this$props$apiOption3, _this$props$apiOption4;
32808
32893
  let graph;
32809
32894
  let equationString;
32810
32895
  const gridStep = this.props.gridStep || Util.getGridStep(this.props.range, this.props.step, interactiveSizes.defaultBoxSize);
@@ -32872,9 +32957,9 @@ class InteractiveGraphEditor extends React.Component {
32872
32957
  }, this.props.valid);
32873
32958
  }
32874
32959
  return /*#__PURE__*/React.createElement(View, null, /*#__PURE__*/React.createElement(LabeledRow, {
32875
- label: "Type of Graph:"
32960
+ label: "Answer type:"
32876
32961
  }, /*#__PURE__*/React.createElement(GraphTypeSelector, {
32877
- graphType: (_this$props$graph$typ = (_this$props$graph3 = this.props.graph) == null ? void 0 : _this$props$graph3.type) != null ? _this$props$graph$typ : InteractiveGraph.defaultProps.graph.type,
32962
+ graphType: (_this$props$graph$typ = (_this$props$graph2 = this.props.graph) == null ? void 0 : _this$props$graph2.type) != null ? _this$props$graph$typ : InteractiveGraph.defaultProps.graph.type,
32878
32963
  onChange: type => {
32879
32964
  this.props.onChange({
32880
32965
  graph: {
@@ -32884,8 +32969,9 @@ class InteractiveGraphEditor extends React.Component {
32884
32969
  type
32885
32970
  }
32886
32971
  });
32887
- }
32888
- })), this.props.graph && ((_this$props$apiOption = this.props.apiOptions) == null || (_this$props$apiOption = _this$props$apiOption.flags) == null || (_this$props$apiOption = _this$props$apiOption.mafs) == null ? void 0 : _this$props$apiOption[this.props.graph.type]) && /*#__PURE__*/React.createElement(InteractiveGraphDescription, {
32972
+ },
32973
+ showNoneOption: (_this$props$apiOption = this.props.apiOptions) == null || (_this$props$apiOption = _this$props$apiOption.flags) == null || (_this$props$apiOption = _this$props$apiOption.mafs) == null ? void 0 : _this$props$apiOption["none"]
32974
+ })), this.props.graph && ((_this$props$apiOption2 = this.props.apiOptions) == null || (_this$props$apiOption2 = _this$props$apiOption2.flags) == null || (_this$props$apiOption2 = _this$props$apiOption2.mafs) == null ? void 0 : _this$props$apiOption2[this.props.graph.type]) && /*#__PURE__*/React.createElement(InteractiveGraphDescription, {
32889
32975
  ariaLabelValue: (_this$props$fullGraph = this.props.fullGraphAriaLabel) != null ? _this$props$fullGraph : "",
32890
32976
  ariaDescriptionValue: (_this$props$fullGraph2 = this.props.fullGraphAriaDescription) != null ? _this$props$fullGraph2 : "",
32891
32977
  onChange: this.props.onChange
@@ -32914,8 +33000,8 @@ class InteractiveGraphEditor extends React.Component {
32914
33000
  selectedValue: (_this$props$correct4 = this.props.correct) != null && _this$props$correct4.numSides ? `${this.props.correct.numSides}` : "3",
32915
33001
  placeholder: "",
32916
33002
  onChange: newValue => {
32917
- var _this$props$graph4;
32918
- invariant(((_this$props$graph4 = this.props.graph) == null ? void 0 : _this$props$graph4.type) === "polygon");
33003
+ var _this$props$graph3;
33004
+ invariant(((_this$props$graph3 = this.props.graph) == null ? void 0 : _this$props$graph3.type) === "polygon");
32919
33005
  const updates = {
32920
33006
  numSides: parsePointCount(newValue),
32921
33007
  coords: null,
@@ -32942,9 +33028,9 @@ class InteractiveGraphEditor extends React.Component {
32942
33028
  ,
32943
33029
  placeholder: "",
32944
33030
  onChange: newValue => {
32945
- var _this$props$graph5, _this$props$graph6;
33031
+ var _this$props$graph4, _this$props$graph5;
32946
33032
  invariant(this.props.correct.type === "polygon", `Expected correct answer type to be polygon, but got ${this.props.correct.type}`);
32947
- invariant(((_this$props$graph5 = this.props.graph) == null ? void 0 : _this$props$graph5.type) === "polygon", `Expected graph type to be polygon, but got ${(_this$props$graph6 = this.props.graph) == null ? void 0 : _this$props$graph6.type}`);
33033
+ invariant(((_this$props$graph4 = this.props.graph) == null ? void 0 : _this$props$graph4.type) === "polygon", `Expected graph type to be polygon, but got ${(_this$props$graph5 = this.props.graph) == null ? void 0 : _this$props$graph5.type}`);
32948
33034
  const updates = {
32949
33035
  snapTo: newValue,
32950
33036
  coords: null
@@ -32972,8 +33058,8 @@ class InteractiveGraphEditor extends React.Component {
32972
33058
  // Don't show indeterminate checkbox state
32973
33059
  !!((_this$props$correct8 = this.props.correct) != null && _this$props$correct8.showAngles),
32974
33060
  onChange: () => {
32975
- var _this$props$graph7;
32976
- if (((_this$props$graph7 = this.props.graph) == null ? void 0 : _this$props$graph7.type) === "polygon") {
33061
+ var _this$props$graph6;
33062
+ if (((_this$props$graph6 = this.props.graph) == null ? void 0 : _this$props$graph6.type) === "polygon") {
32977
33063
  invariant(this.props.correct.type === "polygon", `Expected graph type to be polygon, but got ${this.props.correct.type}`);
32978
33064
  this.props.onChange({
32979
33065
  correct: _extends({}, this.props.correct, {
@@ -32993,8 +33079,8 @@ class InteractiveGraphEditor extends React.Component {
32993
33079
  // Don't show indeterminate checkbox state
32994
33080
  !!((_this$props$correct9 = this.props.correct) != null && _this$props$correct9.showSides),
32995
33081
  onChange: () => {
32996
- var _this$props$graph8;
32997
- if (((_this$props$graph8 = this.props.graph) == null ? void 0 : _this$props$graph8.type) === "polygon" && this.props.correct.type === "polygon") {
33082
+ var _this$props$graph7;
33083
+ if (((_this$props$graph7 = this.props.graph) == null ? void 0 : _this$props$graph7.type) === "polygon" && this.props.correct.type === "polygon") {
32998
33084
  this.props.onChange({
32999
33085
  correct: _extends({}, this.props.correct, {
33000
33086
  showSides: !this.props.correct.showSides
@@ -33022,7 +33108,7 @@ class InteractiveGraphEditor extends React.Component {
33022
33108
  }
33023
33109
  });
33024
33110
  }
33025
- })), ((_this$props$graph9 = this.props.graph) == null ? void 0 : _this$props$graph9.type) && shouldShowStartCoordsUI(this.props.graph, this.props.static) && /*#__PURE__*/React.createElement(StartCoordsSettings, _extends({}, this.props.graph, {
33111
+ })), ((_this$props$graph8 = this.props.graph) == null ? void 0 : _this$props$graph8.type) && shouldShowStartCoordsUI(this.props.graph, this.props.static) && /*#__PURE__*/React.createElement(StartCoordsSettings, _extends({}, this.props.graph, {
33026
33112
  range: this.props.range,
33027
33113
  step: this.props.step,
33028
33114
  onChange: this.changeStartCoords
@@ -33106,8 +33192,9 @@ class InteractiveGraphEditor extends React.Component {
33106
33192
  })), /*#__PURE__*/React.createElement(InfoTip$b, null, /*#__PURE__*/React.createElement("p", null, "Congruency requires only that the angle measures are the same. An exact match implies congruency, but also requires that the angles have the same orientation and that the vertices are in the same position."))),
33107
33193
  // Only show the "Add locked figure" dropdown if the graph
33108
33194
  // is using Mafs.
33109
- this.props.graph && ((_this$props$apiOption2 = this.props.apiOptions) == null || (_this$props$apiOption2 = _this$props$apiOption2.flags) == null || (_this$props$apiOption2 = _this$props$apiOption2.mafs) == null ? void 0 : _this$props$apiOption2[this.props.graph.type]) && /*#__PURE__*/React.createElement(LockedFiguresSection, {
33110
- showLabelsFlag: (_this$props$apiOption3 = this.props.apiOptions) == null || (_this$props$apiOption3 = _this$props$apiOption3.flags) == null || (_this$props$apiOption3 = _this$props$apiOption3.mafs) == null ? void 0 : _this$props$apiOption3["interactive-graph-locked-features-labels"],
33195
+ this.props.graph && ((_this$props$apiOption3 = this.props.apiOptions) == null || (_this$props$apiOption3 = _this$props$apiOption3.flags) == null || (_this$props$apiOption3 = _this$props$apiOption3.mafs) == null ? void 0 : _this$props$apiOption3[this.props.graph.type]) && /*#__PURE__*/React.createElement(LockedFiguresSection, {
33196
+ flags: this.props.apiOptions.flags,
33197
+ showLabelsFlag: (_this$props$apiOption4 = this.props.apiOptions) == null || (_this$props$apiOption4 = _this$props$apiOption4.flags) == null || (_this$props$apiOption4 = _this$props$apiOption4.mafs) == null ? void 0 : _this$props$apiOption4["interactive-graph-locked-features-labels"],
33111
33198
  figures: this.props.lockedFigures,
33112
33199
  onChange: this.props.onChange
33113
33200
  }));
@@ -33144,6 +33231,9 @@ function mergeGraphs(a, b) {
33144
33231
  case "linear-system":
33145
33232
  invariant(b.type === "linear-system");
33146
33233
  return _extends({}, a, b);
33234
+ case "none":
33235
+ invariant(b.type === "none");
33236
+ return _extends({}, a, b);
33147
33237
  case "point":
33148
33238
  invariant(b.type === "point");
33149
33239
  return _extends({}, a, b);
@@ -36901,14 +36991,6 @@ class TableEditor extends React.Component {
36901
36991
  };
36902
36992
  }
36903
36993
  render() {
36904
- const tableProps = _.pick(this.props, "headers", "answers", "onChange", "apiOptions");
36905
- _.extend(tableProps, {
36906
- editableHeaders: true,
36907
- Editor,
36908
- onFocus: () => {},
36909
- onBlur: () => {},
36910
- trackInteraction: () => {}
36911
- });
36912
36994
  return /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("div", {
36913
36995
  className: "perseus-widget-row"
36914
36996
  }, /*#__PURE__*/React.createElement("label", null, "Number of columns:", " ", /*#__PURE__*/React.createElement(NumberInput, {
@@ -36933,7 +37015,17 @@ class TableEditor extends React.Component {
36933
37015
  }
36934
37016
  },
36935
37017
  useArrowKeys: true
36936
- }))), /*#__PURE__*/React.createElement("div", null, " ", "Table of answers:", " ", /*#__PURE__*/React.createElement(InfoTip$1, null, /*#__PURE__*/React.createElement("p", null, "The student has to fill out all cells in the table. For partially filled tables create a table using the template, and insert text input boxes as desired."))), /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement(Table, tableProps)));
37018
+ }))), /*#__PURE__*/React.createElement("div", null, " ", "Table of answers:", " ", /*#__PURE__*/React.createElement(InfoTip$1, null, /*#__PURE__*/React.createElement("p", null, "The student has to fill out all cells in the table. For partially filled tables create a table using the template, and insert text input boxes as desired."))), /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement(Table, {
37019
+ headers: this.props.headers,
37020
+ answers: this.props.answers,
37021
+ onChange: this.props.onChange,
37022
+ apiOptions: this.props.apiOptions,
37023
+ editableHeaders: true,
37024
+ onFocus: () => {},
37025
+ onBlur: () => {},
37026
+ trackInteraction: () => {},
37027
+ Editor: Editor
37028
+ })));
36937
37029
  }
36938
37030
  }
36939
37031
  TableEditor.propTypes = {