@khanacademy/perseus-editor 6.11.0 → 7.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. package/dist/components/{coordinate-pair-input.d.ts → graph-locked-figures/coordinate-pair-input.d.ts} +2 -0
  2. package/dist/components/{defining-point-settings.d.ts → graph-locked-figures/defining-point-settings.d.ts} +9 -2
  3. package/dist/components/{locked-ellipse-settings.d.ts → graph-locked-figures/locked-ellipse-settings.d.ts} +2 -6
  4. package/dist/components/{locked-figure-select.d.ts → graph-locked-figures/locked-figure-select.d.ts} +1 -0
  5. package/dist/components/graph-locked-figures/locked-figure-settings-actions.d.ts +11 -0
  6. package/dist/components/{locked-figure-settings.d.ts → graph-locked-figures/locked-figure-settings.d.ts} +13 -2
  7. package/dist/components/{locked-figures-section.d.ts → graph-locked-figures/locked-figures-section.d.ts} +2 -1
  8. package/dist/components/{locked-line-settings.d.ts → graph-locked-figures/locked-line-settings.d.ts} +2 -6
  9. package/dist/components/{locked-point-settings.d.ts → graph-locked-figures/locked-point-settings.d.ts} +2 -6
  10. package/dist/components/graph-locked-figures/locked-polygon-settings.d.ts +11 -0
  11. package/dist/components/{locked-vector-settings.d.ts → graph-locked-figures/locked-vector-settings.d.ts} +2 -6
  12. package/dist/components/graph-locked-figures/polygon-swatch.d.ts +9 -0
  13. package/dist/es/index.js +2082 -1784
  14. package/dist/es/index.js.map +1 -1
  15. package/dist/index.js +2004 -1699
  16. package/dist/index.js.map +1 -1
  17. package/dist/widgets/interactive-graph-editor.d.ts +202 -162
  18. package/package.json +2 -2
  19. package/dist/components/locked-figure-settings-actions.d.ts +0 -7
  20. /package/dist/components/{color-select.d.ts → graph-locked-figures/color-select.d.ts} +0 -0
  21. /package/dist/components/{color-swatch.d.ts → graph-locked-figures/color-swatch.d.ts} +0 -0
  22. /package/dist/components/{ellipse-swatch.d.ts → graph-locked-figures/ellipse-swatch.d.ts} +0 -0
  23. /package/dist/components/{labeled-row.d.ts → graph-locked-figures/labeled-row.d.ts} +0 -0
  24. /package/dist/components/{labeled-switch.d.ts → graph-locked-figures/labeled-switch.d.ts} +0 -0
  25. /package/dist/components/{line-swatch.d.ts → graph-locked-figures/line-swatch.d.ts} +0 -0
  26. /package/dist/components/{locked-figure-settings-accordion.d.ts → graph-locked-figures/locked-figure-settings-accordion.d.ts} +0 -0
package/dist/es/index.js CHANGED
@@ -18,18 +18,25 @@ import { point, vector, number } from '@khanacademy/kmath';
18
18
  import * as KAS from '@khanacademy/kas';
19
19
  import { isTruthy, UnreachableCaseError } from '@khanacademy/wonder-stuff-core';
20
20
  import { useMathInputI18n, createMathField } from '@khanacademy/math-input';
21
- import { SingleSelect, OptionItem, ActionMenu, ActionItem } from '@khanacademy/wonder-blocks-dropdown';
21
+ import { ActionMenu, ActionItem, SingleSelect, OptionItem } from '@khanacademy/wonder-blocks-dropdown';
22
22
  import { TextField, Checkbox as Checkbox$1 } from '@khanacademy/wonder-blocks-form';
23
23
  import { LabelSmall, LabelMedium, LabelLarge, BodyMonospace } from '@khanacademy/wonder-blocks-typography';
24
- import Banner from '@khanacademy/wonder-blocks-banner';
25
24
  import Button from '@khanacademy/wonder-blocks-button';
26
25
  import { AccordionSection } from '@khanacademy/wonder-blocks-accordion';
26
+ import IconButton from '@khanacademy/wonder-blocks-icon-button';
27
+ import caretDoubleDownIcon from '@phosphor-icons/core/bold/caret-double-down-bold.svg';
28
+ import caretDoubleUpIcon from '@phosphor-icons/core/bold/caret-double-up-bold.svg';
29
+ import caretDownIcon from '@phosphor-icons/core/bold/caret-down-bold.svg';
30
+ import caretUpIcon from '@phosphor-icons/core/bold/caret-up-bold.svg';
27
31
  import trashIcon from '@phosphor-icons/core/bold/trash-bold.svg';
28
32
  import Switch from '@khanacademy/wonder-blocks-switch';
33
+ import minusCircle from '@phosphor-icons/core/regular/minus-circle.svg';
34
+ import plusCircle from '@phosphor-icons/core/regular/plus-circle.svg';
35
+ import Banner from '@khanacademy/wonder-blocks-banner';
29
36
 
30
37
  // This file is processed by a Rollup plugin (replace) to inject the production
31
38
  const libName = "@khanacademy/perseus-editor";
32
- const libVersion = "6.11.0";
39
+ const libVersion = "7.0.0";
33
40
  addLibraryVersionToPerseusDebug(libName, libVersion);
34
41
 
35
42
  function _extends() {
@@ -219,7 +226,7 @@ class SectionControlButton extends React.Component {
219
226
  role: "button",
220
227
  hideDefaultFocusRing: true,
221
228
  "aria-label": title,
222
- style: styles$D.button
229
+ style: styles$G.button
223
230
  }, ({
224
231
  hovered,
225
232
  focused,
@@ -227,7 +234,7 @@ class SectionControlButton extends React.Component {
227
234
  }) => /*#__PURE__*/React.createElement(InlineIcon$b, icon));
228
235
  }
229
236
  }
230
- const styles$D = StyleSheet.create({
237
+ const styles$G = StyleSheet.create({
231
238
  button: {
232
239
  backgroundColor: color.gold,
233
240
  margin: 2,
@@ -20732,13 +20739,13 @@ class WidgetSelect extends React.Component {
20732
20739
  * that collapse a region.
20733
20740
  */
20734
20741
  function ToggleableCaret(props) {
20735
- const iconStyle = props.isExpanded ? styles$C.expanded : styles$C.collapsed;
20742
+ const iconStyle = props.isExpanded ? styles$F.expanded : styles$F.collapsed;
20736
20743
  return /*#__PURE__*/React.createElement(PhosphorIcon, {
20737
20744
  icon: caretRight,
20738
20745
  style: iconStyle
20739
20746
  });
20740
20747
  }
20741
- const styles$C = StyleSheet.create({
20748
+ const styles$F = StyleSheet.create({
20742
20749
  collapsed: {
20743
20750
  transition: ".15s"
20744
20751
  },
@@ -20771,16 +20778,16 @@ class TexErrorView extends React.Component {
20771
20778
  showErrors
20772
20779
  } = this.state;
20773
20780
  return /*#__PURE__*/React.createElement(View, {
20774
- style: styles$B.errorContainer
20781
+ style: styles$E.errorContainer
20775
20782
  }, /*#__PURE__*/React.createElement(View, {
20776
- style: styles$B.title,
20783
+ style: styles$E.title,
20777
20784
  onClick: this.handleToggleTexErrors
20778
20785
  }, /*#__PURE__*/React.createElement(ToggleableCaret, {
20779
20786
  isExpanded: showErrors
20780
20787
  }), "\xA0 TeX Errors (", errorList.length, ")"), showErrors && /*#__PURE__*/React.createElement(View, {
20781
- style: styles$B.errorExplanation
20788
+ style: styles$E.errorExplanation
20782
20789
  }, "If your math doesn't display correctly, these errors might help you troubleshoot. Message #content-kitchen for help."), showErrors && errorList.map((e, index) => /*#__PURE__*/React.createElement(View, {
20783
- style: styles$B.error,
20790
+ style: styles$E.error,
20784
20791
  key: index
20785
20792
  }, /*#__PURE__*/React.createElement(View, {
20786
20793
  style: {
@@ -20789,7 +20796,7 @@ class TexErrorView extends React.Component {
20789
20796
  }, e.math), /*#__PURE__*/React.createElement(View, null, e.message))));
20790
20797
  }
20791
20798
  }
20792
- const styles$B = StyleSheet.create({
20799
+ const styles$E = StyleSheet.create({
20793
20800
  title: {
20794
20801
  backgroundColor: "#eee",
20795
20802
  fontSize: "1.25em",
@@ -24583,7 +24590,7 @@ class SimpleButton extends React.Component {
24583
24590
  } = _this$props,
24584
24591
  otherProps = _objectWithoutPropertiesLoose(_this$props, _excluded$3);
24585
24592
  return /*#__PURE__*/React.createElement("div", _extends({
24586
- className: css(styles$A.baseButton, color === "green" && styles$A.green, color === "orange" && styles$A.orange),
24593
+ className: css(styles$D.baseButton, color === "green" && styles$D.green, color === "orange" && styles$D.orange),
24587
24594
  onClick: onClick
24588
24595
  }, otherProps), children);
24589
24596
  }
@@ -24598,7 +24605,7 @@ SimpleButton.defaultProps = {
24598
24605
  color: "green",
24599
24606
  onClick: () => {}
24600
24607
  };
24601
- const styles$A = StyleSheet.create({
24608
+ const styles$D = StyleSheet.create({
24602
24609
  baseButton: {
24603
24610
  top: "0",
24604
24611
  fontSize: "11px",
@@ -24818,23 +24825,23 @@ const LeafContainer = ({
24818
24825
  }) => {
24819
24826
  const hasPreviewHeading = shape.type === "content" || shape.type === "hint";
24820
24827
  const previewHeading = hasPreviewHeading && /*#__PURE__*/React.createElement("div", {
24821
- className: css(styles$z.containerHeader)
24828
+ className: css(styles$C.containerHeader)
24822
24829
  }, /*#__PURE__*/React.createElement(Header, {
24823
24830
  depth: path.length,
24824
- className: css(styles$z.containerTitle)
24831
+ className: css(styles$C.containerTitle)
24825
24832
  }, capitalize(name)));
24826
24833
  return /*#__PURE__*/React.createElement("div", {
24827
- className: css(styles$z.container)
24834
+ className: css(styles$C.container)
24828
24835
  }, /*#__PURE__*/React.createElement("span", {
24829
- className: css(styles$z.row, styles$z.rowHeading)
24836
+ className: css(styles$C.row, styles$C.rowHeading)
24830
24837
  }, /*#__PURE__*/React.createElement("div", {
24831
- className: css(styles$z.columnLeft)
24838
+ className: css(styles$C.columnLeft)
24832
24839
  }, /*#__PURE__*/React.createElement("div", {
24833
- className: "pod-title " + css(styles$z.containerHeader)
24840
+ className: "pod-title " + css(styles$C.containerHeader)
24834
24841
  }, /*#__PURE__*/React.createElement("div", {
24835
- className: css(styles$z.containerTitle)
24842
+ className: css(styles$C.containerTitle)
24836
24843
  }, capitalize(name)), controls)), /*#__PURE__*/React.createElement("div", {
24837
- className: css(styles$z.columnRight)
24844
+ className: css(styles$C.columnRight)
24838
24845
  }, previewHeading)), children);
24839
24846
  };
24840
24847
  const ArrayContainer = props => {
@@ -24847,11 +24854,11 @@ const ArrayContainer = props => {
24847
24854
  actions
24848
24855
  } = props;
24849
24856
  return /*#__PURE__*/React.createElement("div", {
24850
- className: css(styles$z.container)
24857
+ className: css(styles$C.container)
24851
24858
  }, controls && /*#__PURE__*/React.createElement("div", {
24852
- className: css(styles$z.columnLeft, styles$z.containerHeader)
24859
+ className: css(styles$C.columnLeft, styles$C.containerHeader)
24853
24860
  }, controls), /*#__PURE__*/React.createElement("div", null, children), /*#__PURE__*/React.createElement("div", {
24854
- className: css(styles$z.columnLeft)
24861
+ className: css(styles$C.columnLeft)
24855
24862
  }, /*#__PURE__*/React.createElement("a", {
24856
24863
  href: "javascript:void 0",
24857
24864
  onClick: () => actions.addArrayElement(path, shape.elementShape)
@@ -24864,28 +24871,28 @@ const ObjectContainer = ({
24864
24871
  path
24865
24872
  }) => {
24866
24873
  const headingEditor = /*#__PURE__*/React.createElement("div", {
24867
- className: css(styles$z.containerHeader)
24874
+ className: css(styles$C.containerHeader)
24868
24875
  }, /*#__PURE__*/React.createElement(Header, {
24869
24876
  depth: path.length,
24870
- className: css(styles$z.containerTitle)
24877
+ className: css(styles$C.containerTitle)
24871
24878
  }, capitalize(name)), controls);
24872
24879
  const headingPreview = (name || controls) && /*#__PURE__*/React.createElement("div", {
24873
- className: css(styles$z.containerHeader, styles$z.previewCollectionHeader)
24880
+ className: css(styles$C.containerHeader, styles$C.previewCollectionHeader)
24874
24881
  }, /*#__PURE__*/React.createElement(Header, {
24875
24882
  depth: path.length,
24876
- className: css(styles$z.containerTitle)
24883
+ className: css(styles$C.containerTitle)
24877
24884
  }, capitalize(name)));
24878
24885
  const hasBothHeadings = headingEditor && headingPreview;
24879
24886
  return /*#__PURE__*/React.createElement("div", {
24880
- className: css(styles$z.container)
24887
+ className: css(styles$C.container)
24881
24888
  }, hasBothHeadings && /*#__PURE__*/React.createElement("span", {
24882
- className: css(styles$z.row)
24889
+ className: css(styles$C.row)
24883
24890
  }, /*#__PURE__*/React.createElement("div", {
24884
- className: css(styles$z.columnLeft)
24891
+ className: css(styles$C.columnLeft)
24885
24892
  }, headingEditor), /*#__PURE__*/React.createElement("div", {
24886
- className: css(styles$z.columnRight)
24893
+ className: css(styles$C.columnRight)
24887
24894
  }, headingPreview)), /*#__PURE__*/React.createElement("div", {
24888
- className: css(path.length > 0 && styles$z.contentIndent)
24895
+ className: css(path.length > 0 && styles$C.contentIndent)
24889
24896
  }, children));
24890
24897
  };
24891
24898
 
@@ -25008,11 +25015,11 @@ const ItemNodeContent = withStickiness(props => {
25008
25015
  className: "framework-perseus"
25009
25016
  }, lens(renderers).get(path));
25010
25017
  return /*#__PURE__*/React.createElement("span", null, /*#__PURE__*/React.createElement("div", {
25011
- className: css(styles$z.row)
25018
+ className: css(styles$C.row)
25012
25019
  }, /*#__PURE__*/React.createElement("div", {
25013
- className: css(styles$z.columnLeft)
25020
+ className: css(styles$C.columnLeft)
25014
25021
  }, /*#__PURE__*/React.createElement("div", {
25015
- className: css(sticky && styles$z.sticky)
25022
+ className: css(sticky && styles$C.sticky)
25016
25023
  },
25017
25024
  /*#__PURE__*/
25018
25025
  // @ts-expect-error - TS2769 - No overload matches this call.
@@ -25020,9 +25027,9 @@ const ItemNodeContent = withStickiness(props => {
25020
25027
  onChange: newVal => actions.mergeValueAtPath(path, newVal),
25021
25028
  apiOptions: apiOptions
25022
25029
  })))), /*#__PURE__*/React.createElement("div", {
25023
- className: css(styles$z.columnRight)
25030
+ className: css(styles$C.columnRight)
25024
25031
  }, /*#__PURE__*/React.createElement("div", {
25025
- className: css(sticky && styles$z.sticky)
25032
+ className: css(sticky && styles$C.sticky)
25026
25033
  }, preview))));
25027
25034
  });
25028
25035
  const HintNodeContent = withStickiness(props => {
@@ -25038,13 +25045,13 @@ const HintNodeContent = withStickiness(props => {
25038
25045
  className: "framework-perseus"
25039
25046
  }, lens(renderers).get(path));
25040
25047
  return /*#__PURE__*/React.createElement("div", {
25041
- className: css(styles$z.row)
25048
+ className: css(styles$C.row)
25042
25049
  }, /*#__PURE__*/React.createElement("div", {
25043
- className: css(styles$z.columnLeft)
25050
+ className: css(styles$C.columnLeft)
25044
25051
  }, /*#__PURE__*/React.createElement("div", {
25045
- className: css(sticky && styles$z.sticky)
25052
+ className: css(sticky && styles$C.sticky)
25046
25053
  }, /*#__PURE__*/React.createElement(HintEditor, _extends({}, data, {
25047
- className: css(styles$z.hintEditor),
25054
+ className: css(styles$C.hintEditor),
25048
25055
  onChange: newVal => actions.mergeValueAtPath(path, newVal),
25049
25056
  apiOptions: apiOptions,
25050
25057
  showTitle: false,
@@ -25059,9 +25066,9 @@ const HintNodeContent = withStickiness(props => {
25059
25066
  isFirst: true,
25060
25067
  isLast: true
25061
25068
  })))), /*#__PURE__*/React.createElement("div", {
25062
- className: css(styles$z.columnRight)
25069
+ className: css(styles$C.columnRight)
25063
25070
  }, /*#__PURE__*/React.createElement("div", {
25064
- className: css(sticky && styles$z.sticky)
25071
+ className: css(sticky && styles$C.sticky)
25065
25072
  }, preview)));
25066
25073
  });
25067
25074
  const TagsNodeContent = props => {
@@ -25078,9 +25085,9 @@ const TagsNodeContent = props => {
25078
25085
  return null;
25079
25086
  }
25080
25087
  return /*#__PURE__*/React.createElement("div", {
25081
- className: css(styles$z.columnLeft)
25088
+ className: css(styles$C.columnLeft)
25082
25089
  }, /*#__PURE__*/React.createElement("div", {
25083
- className: css(styles$z.tagsEditor)
25090
+ className: css(styles$C.tagsEditor)
25084
25091
  }, /*#__PURE__*/React.createElement(GroupMetadataEditor, {
25085
25092
  value: data,
25086
25093
  onChange: newVal => actions.setValueAtPath(path, newVal),
@@ -25111,23 +25118,23 @@ const ArrayNodeContent = props => {
25111
25118
  const subpath = path.concat(i);
25112
25119
  const controls = [i > 0 && /*#__PURE__*/React.createElement("div", {
25113
25120
  key: "moveArrayElementUp",
25114
- className: css(styles$z.control)
25121
+ className: css(styles$C.control)
25115
25122
  }, /*#__PURE__*/React.createElement(SimpleButton, {
25116
25123
  color: "orange",
25117
25124
  title: "Move up",
25118
25125
  onClick: () => actions.moveArrayElementUp(subpath)
25119
25126
  }, /*#__PURE__*/React.createElement("div", {
25120
- className: css(styles$z.verticalFlip)
25127
+ className: css(styles$C.verticalFlip)
25121
25128
  }, /*#__PURE__*/React.createElement(InlineIcon$6, iconChevronDown)))), i < data.length - 1 && /*#__PURE__*/React.createElement("div", {
25122
25129
  key: "moveArrayElementDown",
25123
- className: css(styles$z.control)
25130
+ className: css(styles$C.control)
25124
25131
  }, /*#__PURE__*/React.createElement(SimpleButton, {
25125
25132
  color: "orange",
25126
25133
  title: "Move down",
25127
25134
  onClick: () => actions.moveArrayElementDown(subpath)
25128
25135
  }, /*#__PURE__*/React.createElement(InlineIcon$6, iconChevronDown))), /*#__PURE__*/React.createElement("div", {
25129
25136
  key: "removeArrayElement",
25130
- className: css(styles$z.control)
25137
+ className: css(styles$C.control)
25131
25138
  }, /*#__PURE__*/React.createElement(SimpleButton, {
25132
25139
  color: "orange",
25133
25140
  title: "Delete",
@@ -25135,7 +25142,7 @@ const ArrayNodeContent = props => {
25135
25142
  }, /*#__PURE__*/React.createElement(InlineIcon$6, iconTrash$4)))];
25136
25143
  return /*#__PURE__*/React.createElement("div", {
25137
25144
  key: i,
25138
- className: css(styles$z.arrayElement, !elementIsLeaf && styles$z.arrayElementAndNotLeaf)
25145
+ className: css(styles$C.arrayElement, !elementIsLeaf && styles$C.arrayElementAndNotLeaf)
25139
25146
  }, /*#__PURE__*/React.createElement(NodeContainer, _extends({}, otherProps, {
25140
25147
  key: i,
25141
25148
  shape: shape.elementShape,
@@ -25161,7 +25168,7 @@ const ObjectNodeContent = props => {
25161
25168
  // made sense to the shape's author is the order in which we'll iterate :)
25162
25169
  const children = Object.keys(shape.shape).map(subkey => /*#__PURE__*/React.createElement("div", {
25163
25170
  key: subkey,
25164
- className: css(styles$z.objectElement)
25171
+ className: css(styles$C.objectElement)
25165
25172
  }, /*#__PURE__*/React.createElement(NodeContainer, _extends({}, otherProps, {
25166
25173
  shape: shape.shape[subkey],
25167
25174
  data: data[subkey],
@@ -25328,7 +25335,7 @@ class MultiRendererEditor extends React.Component {
25328
25335
  }, this._renderContent());
25329
25336
  }
25330
25337
  }
25331
- const styles$z = StyleSheet.create({
25338
+ const styles$C = StyleSheet.create({
25332
25339
  // This is used in a number of places throughout this file.
25333
25340
  container: {},
25334
25341
  // eslint-disable-next-line react-native/no-unused-styles
@@ -25480,47 +25487,47 @@ const StatefulArticleEditor = createReactClass({
25480
25487
  screen
25481
25488
  } = this.state;
25482
25489
  return /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("div", {
25483
- style: styles$y.controlBar
25490
+ style: styles$B.controlBar
25484
25491
  }, /*#__PURE__*/React.createElement("span", {
25485
- style: styles$y.controls
25492
+ style: styles$B.controls
25486
25493
  }, "Mode:", " ", /*#__PURE__*/React.createElement("span", {
25487
25494
  onClick: () => this.setState({
25488
25495
  mode: "edit"
25489
25496
  }),
25490
- style: mode === "edit" ? styles$y.controlSelected : styles$y.control
25497
+ style: mode === "edit" ? styles$B.controlSelected : styles$B.control
25491
25498
  }, "EDIT"), " | ", /*#__PURE__*/React.createElement("span", {
25492
25499
  onClick: () => this.setState({
25493
25500
  mode: "preview"
25494
25501
  }),
25495
- style: mode === "preview" ? styles$y.controlSelected : styles$y.control
25502
+ style: mode === "preview" ? styles$B.controlSelected : styles$B.control
25496
25503
  }, "PREVIEW"), " | ", /*#__PURE__*/React.createElement("span", {
25497
25504
  onClick: () => this.setState({
25498
25505
  mode: "json"
25499
25506
  }),
25500
- style: mode === "json" ? styles$y.controlSelected : styles$y.control
25507
+ style: mode === "json" ? styles$B.controlSelected : styles$B.control
25501
25508
  }, "JSON")), /*#__PURE__*/React.createElement("span", {
25502
- style: styles$y.controls
25509
+ style: styles$B.controls
25503
25510
  }, "Screen:", " ", /*#__PURE__*/React.createElement("span", {
25504
25511
  onClick: () => this.setState({
25505
25512
  screen: "phone"
25506
25513
  }),
25507
- style: screen === "phone" ? styles$y.controlSelected : styles$y.control
25514
+ style: screen === "phone" ? styles$B.controlSelected : styles$B.control
25508
25515
  }, "PHONE"), " | ", /*#__PURE__*/React.createElement("span", {
25509
25516
  onClick: () => this.setState({
25510
25517
  screen: "tablet"
25511
25518
  }),
25512
- style: screen === "tablet" ? styles$y.controlSelected : styles$y.control
25519
+ style: screen === "tablet" ? styles$B.controlSelected : styles$B.control
25513
25520
  }, "TABLET"), " | ", /*#__PURE__*/React.createElement("span", {
25514
25521
  onClick: () => this.setState({
25515
25522
  screen: "desktop"
25516
25523
  }),
25517
- style: screen === "desktop" ? styles$y.controlSelected : styles$y.control
25524
+ style: screen === "desktop" ? styles$B.controlSelected : styles$B.control
25518
25525
  }, "DESKTOP"))), /*#__PURE__*/React.createElement("div", {
25519
- style: styles$y.editor
25526
+ style: styles$B.editor
25520
25527
  }, /*#__PURE__*/React.createElement(ArticleEditor, this.state)));
25521
25528
  }
25522
25529
  });
25523
- const styles$y = {
25530
+ const styles$B = {
25524
25531
  controlBar: {
25525
25532
  marginBottom: 25,
25526
25533
  marginTop: 5
@@ -27010,10 +27017,10 @@ class GradedGroupEditor extends React.Component {
27010
27017
  }, /*#__PURE__*/React.createElement("div", {
27011
27018
  className: "perseus-widget-row"
27012
27019
  }, /*#__PURE__*/React.createElement("label", {
27013
- className: css(styles$x.title)
27020
+ className: css(styles$A.title)
27014
27021
  }, "Title:", " ", /*#__PURE__*/React.createElement(TextInput$6, {
27015
27022
  value: this.props.title,
27016
- className: css(styles$x.input)
27023
+ className: css(styles$A.input)
27017
27024
  // @ts-expect-error - TS2554 - Expected 3 arguments, but got 1.
27018
27025
  ,
27019
27026
  onChange: this.change("title")
@@ -27038,7 +27045,7 @@ class GradedGroupEditor extends React.Component {
27038
27045
  }, /*#__PURE__*/React.createElement(InlineIcon$4, iconPlus$1), " Add a hint"), this.props.hint && /*#__PURE__*/React.createElement("div", {
27039
27046
  className: "perseus-hint-editor"
27040
27047
  }, /*#__PURE__*/React.createElement("div", {
27041
- className: css(styles$x.hintsTitle)
27048
+ className: css(styles$A.hintsTitle)
27042
27049
  }, "Hint"), /*#__PURE__*/React.createElement(Editor, {
27043
27050
  ref: this.hintEditor,
27044
27051
  content: this.props.hint ? this.props.hint.content : "",
@@ -27075,7 +27082,7 @@ GradedGroupEditor.defaultProps = {
27075
27082
  images: {},
27076
27083
  hint: null
27077
27084
  };
27078
- const styles$x = StyleSheet.create({
27085
+ const styles$A = StyleSheet.create({
27079
27086
  title: {
27080
27087
  fontSize: 18,
27081
27088
  fontWeight: "bold"
@@ -28414,13 +28421,13 @@ function MathquillInput(props) {
28414
28421
  }
28415
28422
  });
28416
28423
  return /*#__PURE__*/React.createElement(View, {
28417
- style: styles$w.outerWrapper
28424
+ style: styles$z.outerWrapper
28418
28425
  }, /*#__PURE__*/React.createElement("span", {
28419
28426
  ref: mathFieldWrapperRef,
28420
28427
  className: "perseus-math-input mq-editable-field mq-math-mode"
28421
28428
  }));
28422
28429
  }
28423
- const styles$w = StyleSheet.create({
28430
+ const styles$z = StyleSheet.create({
28424
28431
  outerWrapper: {
28425
28432
  display: "inline-block",
28426
28433
  borderStyle: "solid",
@@ -29457,95 +29464,6 @@ class InteractionEditor extends React.Component {
29457
29464
  InteractionEditor.widgetName = "interaction";
29458
29465
  InteractionEditor.defaultProps = defaultInteractionProps;
29459
29466
 
29460
- const UNLIMITED = "unlimited";
29461
- // Parses a string (ostensibly from some UI element that only supports string
29462
- // types, such as a <select />) into a numeric point count or the special sigil
29463
- // "unlimited". Note that 0 is a special case, and means "unlimited".
29464
- const parsePointCount = points => {
29465
- const parsed = parseInt(points, 10);
29466
- if (isNaN(parsed)) {
29467
- return UNLIMITED;
29468
- }
29469
- return parsed === 0 ? UNLIMITED : parsed;
29470
- };
29471
-
29472
- const GraphPointsCountSelector = ({
29473
- numPoints: _numPoints = 1,
29474
- onChange
29475
- }) => {
29476
- return /*#__PURE__*/React.createElement(SingleSelect, {
29477
- selectedValue: `${_numPoints}`,
29478
- onChange: newValue => {
29479
- onChange(parsePointCount(newValue));
29480
- }
29481
- // Never uses placeholder, always has value
29482
- ,
29483
- placeholder: "",
29484
- style: styles$v.singleSelectShort
29485
- }, [...[...Array(7).keys()].map(n => /*#__PURE__*/React.createElement(OptionItem, {
29486
- key: n,
29487
- value: `${n}`,
29488
- label: `${n} point${n > 1 ? "s" : ""}`
29489
- })), /*#__PURE__*/React.createElement(OptionItem, {
29490
- key: "unlimited",
29491
- value: UNLIMITED,
29492
- label: "unlimited"
29493
- })]);
29494
- };
29495
- const styles$v = StyleSheet.create({
29496
- singleSelectShort: {
29497
- // Non-standard spacing, but it's the smallest we can go
29498
- // without running into styling issues with the dropdown.
29499
- height: 26
29500
- }
29501
- });
29502
-
29503
- const GraphTypeSelector = props => {
29504
- return /*#__PURE__*/React.createElement(SingleSelect, {
29505
- selectedValue: props.graphType,
29506
- onChange: props.onChange,
29507
- placeholder: "Select a graph type",
29508
- style: styles$u.singleSelectShort
29509
- }, /*#__PURE__*/React.createElement(OptionItem, {
29510
- value: "linear",
29511
- label: "Linear function"
29512
- }), /*#__PURE__*/React.createElement(OptionItem, {
29513
- value: "quadratic",
29514
- label: "Quadratic function"
29515
- }), /*#__PURE__*/React.createElement(OptionItem, {
29516
- value: "sinusoid",
29517
- label: "Sinusoid function"
29518
- }), /*#__PURE__*/React.createElement(OptionItem, {
29519
- value: "circle",
29520
- label: "Circle"
29521
- }), /*#__PURE__*/React.createElement(OptionItem, {
29522
- value: "point",
29523
- label: "Point(s)"
29524
- }), /*#__PURE__*/React.createElement(OptionItem, {
29525
- value: "linear-system",
29526
- label: "Linear System"
29527
- }), /*#__PURE__*/React.createElement(OptionItem, {
29528
- value: "polygon",
29529
- label: "Polygon"
29530
- }), /*#__PURE__*/React.createElement(OptionItem, {
29531
- value: "segment",
29532
- label: "Line Segment(s)"
29533
- }), /*#__PURE__*/React.createElement(OptionItem, {
29534
- value: "ray",
29535
- label: "Ray"
29536
- }), /*#__PURE__*/React.createElement(OptionItem, {
29537
- value: "angle",
29538
- label: "Angle"
29539
- }));
29540
- };
29541
- const styles$u = StyleSheet.create({
29542
- singleSelectShort: {
29543
- // Non-standard spacing, but it's the smallest we can go
29544
- // without running into styling issues with the dropdown.
29545
- height: 26
29546
- }
29547
- });
29548
-
29549
29467
  // Renders the given children in a View laid out horizontally.
29550
29468
  const LabeledRow = props => {
29551
29469
  const {
@@ -29555,16 +29473,16 @@ const LabeledRow = props => {
29555
29473
  style
29556
29474
  } = props;
29557
29475
  return /*#__PURE__*/React.createElement("label", {
29558
- className: css(styles$t.label)
29476
+ className: css(styles$y.label)
29559
29477
  }, /*#__PURE__*/React.createElement(View, {
29560
- style: [styles$t.row, style]
29478
+ style: [styles$y.row, style]
29561
29479
  }, labelSide === "start" || /*#__PURE__*/React.createElement(LabelSmall, {
29562
- style: styles$t.spaceEnd
29480
+ style: styles$y.spaceEnd
29563
29481
  }, label), children, labelSide === "end" && /*#__PURE__*/React.createElement(LabelSmall, {
29564
- style: styles$t.spaceStart
29482
+ style: styles$y.spaceStart
29565
29483
  }, label)));
29566
29484
  };
29567
- const styles$t = StyleSheet.create({
29485
+ const styles$y = StyleSheet.create({
29568
29486
  label: {
29569
29487
  width: "fit-content"
29570
29488
  },
@@ -29582,1764 +29500,2143 @@ const styles$t = StyleSheet.create({
29582
29500
  }
29583
29501
  });
29584
29502
 
29585
- const {
29586
- ButtonGroup: ButtonGroup$2,
29587
- InfoTip: InfoTip$d,
29588
- PropCheckBox: PropCheckBox$7,
29589
- RangeInput: RangeInput$4
29590
- } = components;
29591
- const defaultBackgroundImage$1 = {
29592
- url: null,
29593
- width: 0,
29594
- height: 0
29503
+ const mediaDefaultSpecLargeMarginWidth = spacing.large_24;
29504
+ const MEDIA_DEFAULT_SPEC = {
29505
+ small: {
29506
+ query: "(max-width: 767px)",
29507
+ totalColumns: 4,
29508
+ gutterWidth: spacing.medium_16,
29509
+ marginWidth: spacing.medium_16
29510
+ },
29511
+ medium: {
29512
+ query: "(min-width: 768px) and (max-width: 1023px)",
29513
+ totalColumns: 8,
29514
+ gutterWidth: spacing.xLarge_32,
29515
+ marginWidth: spacing.large_24
29516
+ },
29517
+ large: {
29518
+ query: "(min-width: 1024px)",
29519
+ totalColumns: 12,
29520
+ gutterWidth: spacing.xLarge_32,
29521
+ marginWidth: mediaDefaultSpecLargeMarginWidth,
29522
+ maxWidth: 1120 + mediaDefaultSpecLargeMarginWidth * 2
29523
+ }
29595
29524
  };
29596
- function numSteps(range, step) {
29597
- return Math.floor((range[1] - range[0]) / step);
29598
- }
29599
- function Heading({
29600
- title,
29601
- isOpen,
29602
- onToggle
29603
- }) {
29604
- return /*#__PURE__*/React.createElement(View, {
29605
- style: {
29606
- flexDirection: "row",
29607
- justifyContent: "space-between",
29608
- backgroundColor: color.offBlack8,
29609
- padding: spacing.xSmall_8,
29610
- marginTop: spacing.small_12,
29611
- // NOTE(jeremy): This is the inverse of the @editorPadding CSS
29612
- // variable found in perseus-editor.less. For now, it must
29613
- // match otherwise there's a gap from this header to the edge
29614
- // of the editor borders.
29615
- marginInline: -10,
29616
- cursor: "pointer"
29617
- },
29618
- onClick: () => onToggle == null ? void 0 : onToggle(!isOpen)
29619
- }, /*#__PURE__*/React.createElement(LabelSmall, null, title), /*#__PURE__*/React.createElement(ToggleableCaret, {
29620
- isExpanded: isOpen
29621
- }));
29525
+ ({
29526
+ large: {
29527
+ query: "(min-width: 1px)",
29528
+ totalColumns: 12,
29529
+ gutterWidth: spacing.xLarge_32,
29530
+ marginWidth: spacing.medium_16
29531
+ }
29532
+ });
29533
+ ({
29534
+ small: {
29535
+ query: "(max-width: 767px)",
29536
+ totalColumns: 4,
29537
+ gutterWidth: spacing.medium_16,
29538
+ marginWidth: spacing.medium_16
29539
+ },
29540
+ large: {
29541
+ query: "(min-width: 768px)",
29542
+ totalColumns: 12,
29543
+ gutterWidth: spacing.xLarge_32,
29544
+ marginWidth: spacing.xxLarge_48
29545
+ }
29546
+ });
29547
+
29548
+ const defaultContext = {
29549
+ ssrSize: "large",
29550
+ mediaSpec: MEDIA_DEFAULT_SPEC
29551
+ };
29552
+ const MediaLayoutContext = React.createContext(defaultContext);
29553
+ MediaLayoutContext.displayName = "MediaLayoutContext";
29554
+
29555
+ class Spring extends React.Component {
29556
+ render() {
29557
+ const {
29558
+ style
29559
+ } = this.props;
29560
+ return React.createElement(View, {
29561
+ "aria-hidden": "true",
29562
+ style: [styles$x.grow, style]
29563
+ });
29564
+ }
29622
29565
  }
29623
- class InteractiveGraphSettings extends React.Component {
29624
- static stateFromProps(props) {
29625
- return {
29626
- labelsTextbox: props.labels,
29627
- gridStepTextbox: props.gridStep,
29628
- snapStepTextbox: props.snapStep,
29629
- stepTextbox: props.step,
29630
- rangeTextbox: props.range,
29631
- // Shallow-copied clone
29632
- backgroundImage: _extends({}, props.backgroundImage)
29633
- };
29566
+ const styles$x = StyleSheet.create({
29567
+ grow: {
29568
+ flexGrow: 1
29634
29569
  }
29635
- constructor(props) {
29636
- super(props);
29637
- this._isMounted = false;
29638
- this.bgUrlRef = /*#__PURE__*/React.createRef();
29639
- this.labelXRef = /*#__PURE__*/React.createRef();
29640
- this.labelYRef = /*#__PURE__*/React.createRef();
29641
- this.change = (...args) => {
29642
- return Changeable.change.apply(this, args);
29643
- };
29644
- // TODO(aria): Make either a wrapper for standard events to work
29645
- // with this.change, or make these use some TextInput/NumberInput box
29646
- this.changeRulerLabel = e => {
29647
- this.change({
29648
- rulerLabel: e.target.value
29649
- });
29650
- };
29651
- this.changeRulerTicks = e => {
29652
- this.change({
29653
- rulerTicks: +e.target.value
29654
- });
29655
- };
29656
- this.changeBackgroundUrl = e => {
29657
- var _this$bgUrlRef$curren;
29658
- // Only continue on blur or "enter"
29659
- if (e.type === "keypress" && e.key !== "Enter") {
29660
- return;
29661
- }
29662
- const setUrl = (url, width, height) => {
29663
- // Shallow-copied clone
29664
- const image = _extends({}, this.props.backgroundImage);
29665
- image.url = url;
29666
- image.width = width;
29667
- image.height = height;
29668
- this.setState({
29669
- backgroundImage: image
29670
- }, this.changeGraph);
29671
- };
29672
- const url = (_this$bgUrlRef$curren = this.bgUrlRef.current) == null ? void 0 : _this$bgUrlRef$curren.value;
29673
- if (url) {
29674
- Util.getImageSize(url, (width, height) => {
29675
- if (this._isMounted) {
29676
- setUrl(url, width, height);
29677
- }
29678
- });
29679
- } else {
29680
- setUrl(null, 0, 0);
29681
- }
29682
- };
29683
- this.renderLabelChoices = choices => {
29684
- return choices.map(nameAndValue => /*#__PURE__*/React.createElement("option", {
29685
- key: nameAndValue[1],
29686
- value: nameAndValue[1]
29687
- }, nameAndValue[0]));
29688
- };
29689
- this.validRange = range => {
29690
- const numbers = _.every(range, function (num) {
29691
- return _.isFinite(num);
29692
- });
29693
- if (!numbers) {
29694
- return "Range must be a valid number";
29695
- }
29696
- if (range[0] >= range[1]) {
29697
- return "Range must have a higher number on the right";
29698
- }
29699
- return true;
29700
- };
29701
- this.validateStepValue = settings => {
29702
- const {
29703
- step,
29704
- range,
29705
- name,
29706
- minTicks,
29707
- maxTicks
29708
- } = settings;
29709
- const nSteps = numSteps(range, step);
29710
- if (nSteps < minTicks) {
29711
- return name + " is too large, there must be at least " + minTicks + " ticks.";
29712
- }
29713
- if (nSteps > maxTicks) {
29714
- return name + " is too small, there can be at most " + maxTicks + " ticks.";
29715
- }
29716
- return true;
29717
- };
29718
- this.validSnapStep = (step, range) => {
29719
- return this.validateStepValue({
29720
- step: step,
29721
- range: range,
29722
- name: "Snap step",
29723
- minTicks: 5,
29724
- maxTicks: 60
29725
- });
29726
- };
29727
- this.validGridStep = (step, range) => {
29728
- return this.validateStepValue({
29729
- step: step,
29730
- range: range,
29731
- name: "Grid step",
29732
- minTicks: 3,
29733
- maxTicks: 60
29734
- });
29735
- };
29736
- this.validStep = (step, range) => {
29737
- return this.validateStepValue({
29738
- step: step,
29739
- range: range,
29740
- name: "Step",
29741
- minTicks: 3,
29742
- maxTicks: 20
29743
- });
29744
- };
29745
- this.validBackgroundImageSize = image => {
29746
- // Ignore empty images
29747
- if (!image.url) {
29748
- return true;
29749
- }
29750
- const validSize = image.width <= 450 && image.height <= 450;
29751
- if (!validSize) {
29752
- return "Image must be smaller than 450px x 450px.";
29753
- }
29754
- return true;
29755
- };
29756
- this.validateGraphSettings = (range, step, gridStep, snapStep, image) => {
29757
- const self = this;
29758
- let msg;
29759
- const goodRange = _.every(range, function (range) {
29760
- msg = self.validRange(range);
29761
- return msg === true;
29762
- });
29763
- if (!goodRange) {
29764
- return msg;
29765
- }
29766
- const goodStep = _.every(step, function (step, i) {
29767
- msg = self.validStep(step, range[i]);
29768
- return msg === true;
29769
- });
29770
- if (!goodStep) {
29771
- return msg;
29772
- }
29773
- const goodGridStep = _.every(gridStep, function (gridStep, i) {
29774
- msg = self.validGridStep(gridStep, range[i]);
29775
- return msg === true;
29776
- });
29777
- if (!goodGridStep) {
29778
- return msg;
29779
- }
29780
- const goodSnapStep = _.every(snapStep, function (snapStep, i) {
29781
- msg = self.validSnapStep(snapStep, range[i]);
29782
- return msg === true;
29783
- });
29784
- if (!goodSnapStep) {
29785
- return msg;
29786
- }
29787
- const goodImageSize = this.validBackgroundImageSize(image);
29788
- if (goodImageSize !== true) {
29789
- msg = goodImageSize;
29790
- return msg;
29791
- }
29792
- return true;
29793
- };
29794
- this.changeLabel = (i, e) => {
29795
- const val = e.target.value;
29796
- const labels = this.state.labelsTextbox.slice();
29797
- labels[i] = val;
29798
- this.setState({
29799
- labelsTextbox: labels
29800
- }, this.changeGraph);
29801
- };
29802
- this.changeRange = (i, values) => {
29803
- const ranges = this.state.rangeTextbox.slice();
29804
- ranges[i] = values;
29805
- const step = this.state.stepTextbox.slice();
29806
- const gridStep = this.state.gridStepTextbox.slice();
29807
- const snapStep = this.state.snapStepTextbox.slice();
29808
- const scale = Util.scaleFromExtent(ranges[i], this.props.box[i]);
29809
- if (this.validRange(ranges[i]) === true) {
29810
- step[i] = Util.tickStepFromExtent(ranges[i], this.props.box[i]);
29811
- const gridStepValue = Util.gridStepFromTickStep(step[i], scale);
29812
- if (gridStepValue) {
29813
- gridStep[i] = gridStepValue;
29814
- }
29815
- snapStep[i] = gridStep[i] / 2;
29816
- }
29817
- this.setState({
29818
- stepTextbox: step,
29819
- gridStepTextbox: gridStep,
29820
- snapStepTextbox: snapStep,
29821
- rangeTextbox: ranges
29822
- }, this.changeGraph);
29823
- };
29824
- this.changeStep = step => {
29825
- this.setState({
29826
- stepTextbox: step
29827
- }, this.changeGraph);
29828
- };
29829
- this.changeSnapStep = snapStep => {
29830
- this.setState({
29831
- snapStepTextbox: snapStep
29832
- }, this.changeGraph);
29833
- };
29834
- this.changeGridStep = gridStep => {
29835
- this.setState({
29836
- gridStepTextbox: gridStep,
29837
- snapStepTextbox: _.map(gridStep, function (step) {
29838
- return step / 2;
29839
- })
29840
- }, this.changeGraph);
29841
- };
29842
- this.changeGraph = () => {
29843
- const labels = this.state.labelsTextbox;
29844
- const range = _.map(this.state.rangeTextbox, function (range) {
29845
- return _.map(range, Number);
29846
- });
29847
- const step = _.map(this.state.stepTextbox, Number);
29848
- const gridStep = this.state.gridStepTextbox;
29849
- const snapStep = this.state.snapStepTextbox;
29850
- const image = this.state.backgroundImage;
29570
+ });
29851
29571
 
29852
- // validationResult is either:
29853
- // true -> the settings are valid
29854
- // a string -> the settings are invalid, and the explanation
29855
- // is contained in the string
29856
- // TODO(aria): Refactor this to not be confusing
29857
- const validationResult = this.validateGraphSettings(range, step, gridStep, snapStep, image);
29858
- if (validationResult === true) {
29859
- // either true or a string
29860
- this.change({
29861
- valid: true,
29862
- labels: labels,
29863
- range: range,
29864
- step: step,
29865
- gridStep: gridStep,
29866
- snapStep: snapStep,
29867
- backgroundImage: image
29868
- });
29869
- } else {
29870
- this.change({
29871
- valid: validationResult // a string message, not false
29872
- });
29873
- }
29874
- };
29875
- this.state = _extends({
29876
- isExpanded: true
29877
- }, InteractiveGraphSettings.stateFromProps(props));
29572
+ class Strut extends React.Component {
29573
+ render() {
29574
+ const {
29575
+ size,
29576
+ style
29577
+ } = this.props;
29578
+ return React.createElement(View, {
29579
+ "aria-hidden": "true",
29580
+ style: [strutStyle(size), style]
29581
+ });
29878
29582
  }
29879
- componentDidMount() {
29880
- // TODO(scottgrant): This is a hack to remove the deprecated call to
29881
- // this.isMounted() but is still considered an anti-pattern.
29882
- this._isMounted = true;
29883
- this.changeGraph = _.debounce(this.changeGraph, 300);
29583
+ }
29584
+ const strutStyle = size => {
29585
+ return {
29586
+ width: size,
29587
+ MsFlexBasis: size,
29588
+ MsFlexPreferredSize: size,
29589
+ WebkitFlexBasis: size,
29590
+ flexBasis: size,
29591
+ flexShrink: 0
29592
+ };
29593
+ };
29594
+
29595
+ function focusWithChromeStickyFocusBugWorkaround(element) {
29596
+ // NOTE(marksandstrom) Chrome sticky focus bug.
29597
+ //
29598
+ // There is a bug in Chrome related to sticky positioning and
29599
+ // focus: When a child of an element with sticky positioning is
29600
+ // focused with the focus() method, the scrollable area
29601
+ // containing that element will scroll to the top (even though
29602
+ // the element is already visble on the page). For a dropdown
29603
+ // this means that the area will be scrolled to the top when
29604
+ // the user interacts with the dropdown (since we actively
29605
+ // manage dropdown header and option focus for keyboard users).
29606
+ //
29607
+ // To work around this bug the `preventScroll` focus option is
29608
+ // used.
29609
+ //
29610
+ // Using `focusOptions` is an experimental browser feature
29611
+ // "that should not be used in production code". However, since
29612
+ // Chrome is the only browser we support that exhibits this
29613
+ // buggy behavior and using `focusOptions` works around the
29614
+ // bug, it is perfectly reasonable to use the feature in this
29615
+ // case. We are not relying on the effect of the
29616
+ // `preventScroll` option otherwise.
29617
+ //
29618
+ // focusOptions documentation:
29619
+ //
29620
+ // https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus
29621
+ //
29622
+ // Note that there could be cases where we want default focus() behavior.
29623
+ // One that comes to mind is a dropdown with a long list of options that
29624
+ // might be rendered off screen when opened. With this workaround the
29625
+ // selected option would not be scrolled into view if the option is near
29626
+ // the end of the list. We're not aware of any uses of the dropdown where
29627
+ // this is an issue, however, so there isn't currently a way to out of the
29628
+ // `preventScroll` behavior.
29629
+
29630
+ // @ts-expect-error - TS2339 - Property 'focus' does not exist on type 'Element'.
29631
+ element.focus({
29632
+ preventScroll: true
29633
+ });
29634
+ }
29635
+ const DEFAULT_COLOR = "grayH";
29636
+ function getDefaultFigureForType(type) {
29637
+ switch (type) {
29638
+ case "point":
29639
+ return {
29640
+ type: "point",
29641
+ coord: [0, 0],
29642
+ color: DEFAULT_COLOR,
29643
+ filled: true
29644
+ };
29645
+ case "line":
29646
+ return {
29647
+ type: "line",
29648
+ kind: "line",
29649
+ points: [getDefaultFigureForType("point"), _extends({}, getDefaultFigureForType("point"), {
29650
+ coord: [2, 2]
29651
+ })],
29652
+ color: DEFAULT_COLOR,
29653
+ lineStyle: "solid",
29654
+ showPoint1: false,
29655
+ showPoint2: false
29656
+ };
29657
+ case "vector":
29658
+ return {
29659
+ type: "vector",
29660
+ points: [[0, 0], [2, 2]],
29661
+ color: DEFAULT_COLOR
29662
+ };
29663
+ case "ellipse":
29664
+ return {
29665
+ type: "ellipse",
29666
+ center: [0, 0],
29667
+ radius: [1, 1],
29668
+ angle: 0,
29669
+ color: DEFAULT_COLOR,
29670
+ fillStyle: "none",
29671
+ strokeStyle: "solid"
29672
+ };
29673
+ case "polygon":
29674
+ return {
29675
+ type: "polygon",
29676
+ points: [[0, 2], [-1, 0], [1, 0]],
29677
+ color: DEFAULT_COLOR,
29678
+ showVertices: false,
29679
+ fillStyle: "none",
29680
+ strokeStyle: "solid"
29681
+ };
29682
+ default:
29683
+ throw new UnreachableCaseError(type);
29684
+ }
29685
+ }
29686
+ function degreeToRadian(degrees) {
29687
+ return degrees / 180 * Math.PI;
29688
+ }
29689
+ function radianToDegree(radians) {
29690
+ return radians / Math.PI * 180;
29691
+ }
29692
+
29693
+ /**
29694
+ * Dropdown for selecting a locked figure to add to an interactive graph.
29695
+ * Locked figures are elements (points, segmeents, etc.) that are not
29696
+ * interactive, just present in the graph's background.
29697
+ *
29698
+ * Used in the interactive graph editor's locked figures section.
29699
+ */
29700
+ const LockedFigureSelect = props => {
29701
+ const {
29702
+ id,
29703
+ onChange
29704
+ } = props;
29705
+ const figureTypes = props.showM2Features ? ["point", "line", "vector", "ellipse", "polygon"] : ["point", "line"];
29706
+ return /*#__PURE__*/React.createElement(View, {
29707
+ style: styles$w.container
29708
+ }, /*#__PURE__*/React.createElement(ActionMenu, {
29709
+ menuText: "Add locked figure",
29710
+ style: styles$w.addElementSelect
29711
+ }, figureTypes.map(figureType => /*#__PURE__*/React.createElement(ActionItem, {
29712
+ key: `${id}-${figureType}`,
29713
+ label: figureType,
29714
+ onClick: () => onChange(figureType)
29715
+ }, figureType))));
29716
+ };
29717
+ const styles$w = StyleSheet.create({
29718
+ container: {
29719
+ marginTop: spacing.xSmall_8
29720
+ },
29721
+ addElementSelect: {
29722
+ backgroundColor: color.fadedBlue8,
29723
+ borderRadius: spacing.xxxSmall_4
29884
29724
  }
29885
- UNSAFE_componentWillReceiveProps(nextProps) {
29886
- // Make sure that state updates when switching
29887
- // between different items in a multi-item editor.
29888
- 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)) {
29889
- this.setState(InteractiveGraphSettings.stateFromProps(nextProps));
29725
+ });
29726
+
29727
+ const AngleInput = props => {
29728
+ const {
29729
+ angle,
29730
+ onChange
29731
+ } = props;
29732
+ const [angleInput, setAngleInput] = React.useState(radianToDegree(angle).toString());
29733
+ function handleAngleChange(newValue) {
29734
+ // Update the local state (update the input field value).
29735
+ setAngleInput(newValue);
29736
+
29737
+ // If the new value is not a number, don't update the props.
29738
+ // If it's empty, keep the props the same value instead of setting to 0.
29739
+ if (isNaN(+newValue) || newValue === "") {
29740
+ return;
29890
29741
  }
29742
+
29743
+ // Update the graph.
29744
+ onChange(degreeToRadian(newValue));
29891
29745
  }
29892
- componentWillUnmount() {
29893
- this._isMounted = false;
29746
+ return /*#__PURE__*/React.createElement(LabelMedium, {
29747
+ tag: "label",
29748
+ style: styles$v.row
29749
+ }, "angle (degrees)", /*#__PURE__*/React.createElement(Strut, {
29750
+ size: spacing.xxSmall_6
29751
+ }), /*#__PURE__*/React.createElement(TextField, {
29752
+ type: "number",
29753
+ value: angleInput,
29754
+ onChange: handleAngleChange,
29755
+ style: styles$v.textField
29756
+ }), /*#__PURE__*/React.createElement(Strut, {
29757
+ size: spacing.xxSmall_6
29758
+ }));
29759
+ };
29760
+ const styles$v = StyleSheet.create({
29761
+ row: {
29762
+ display: "flex",
29763
+ flexDirection: "row",
29764
+ alignItems: "center"
29765
+ },
29766
+ textField: {
29767
+ width: spacing.xxxLarge_64
29894
29768
  }
29895
- render() {
29896
- return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Heading, {
29897
- title: "Common Graph Settings",
29898
- isOpen: this.state.isExpanded,
29899
- onToggle: () => this.setState({
29900
- isExpanded: !this.state.isExpanded
29901
- })
29902
- }), this.state.isExpanded && /*#__PURE__*/React.createElement(View, null, /*#__PURE__*/React.createElement("div", {
29903
- className: "graph-settings"
29904
- }, /*#__PURE__*/React.createElement("div", {
29905
- className: "perseus-widget-row"
29906
- }, /*#__PURE__*/React.createElement("div", {
29907
- className: "perseus-widget-left-col"
29908
- }, /*#__PURE__*/React.createElement(LabeledRow, {
29909
- label: "x Label"
29910
- }, /*#__PURE__*/React.createElement("input", {
29911
- type: "text",
29912
- className: "graph-settings-axis-label",
29913
- ref: this.labelXRef,
29914
- onChange: e => this.changeLabel(0, e),
29915
- value: this.state.labelsTextbox[0] || ""
29916
- }))), /*#__PURE__*/React.createElement("div", {
29917
- className: "perseus-widget-right-col"
29918
- }, /*#__PURE__*/React.createElement(LabeledRow, {
29919
- label: "y Label"
29920
- }, /*#__PURE__*/React.createElement("input", {
29921
- type: "text",
29922
- className: "graph-settings-axis-label",
29923
- ref: this.labelYRef,
29924
- onChange: e => this.changeLabel(1, e),
29925
- value: this.state.labelsTextbox[1] || ""
29926
- })))), /*#__PURE__*/React.createElement("div", {
29927
- className: "perseus-widget-row"
29928
- }, /*#__PURE__*/React.createElement("div", {
29929
- className: "perseus-widget-left-col"
29930
- }, /*#__PURE__*/React.createElement(LabeledRow, {
29931
- label: "x Range"
29932
- }, /*#__PURE__*/React.createElement(RangeInput$4, {
29933
- value: this.state.rangeTextbox[0],
29934
- onChange: vals => this.changeRange(0, vals)
29935
- }))), /*#__PURE__*/React.createElement("div", {
29936
- className: "perseus-widget-right-col"
29937
- }, /*#__PURE__*/React.createElement(LabeledRow, {
29938
- label: "y Range"
29939
- }, /*#__PURE__*/React.createElement(RangeInput$4, {
29940
- value: this.state.rangeTextbox[1],
29941
- onChange: vals => this.changeRange(1, vals)
29942
- })))), /*#__PURE__*/React.createElement("div", {
29943
- className: "perseus-widget-row"
29944
- }, /*#__PURE__*/React.createElement("div", {
29945
- className: "perseus-widget-left-col"
29946
- }, /*#__PURE__*/React.createElement(LabeledRow, {
29947
- label: "Tick Step"
29948
- }, /*#__PURE__*/React.createElement(RangeInput$4, {
29949
- value: this.state.stepTextbox,
29950
- onChange: this.changeStep
29951
- }))), /*#__PURE__*/React.createElement("div", {
29952
- className: "perseus-widget-right-col"
29953
- }, /*#__PURE__*/React.createElement(LabeledRow, {
29954
- label: "Grid Step"
29955
- }, /*#__PURE__*/React.createElement(RangeInput$4, {
29956
- value: this.state.gridStepTextbox,
29957
- onChange: this.changeGridStep
29958
- })))), /*#__PURE__*/React.createElement("div", {
29959
- className: "perseus-widget-row"
29960
- }, /*#__PURE__*/React.createElement("div", {
29961
- className: "perseus-widget-left-col"
29962
- }, /*#__PURE__*/React.createElement(LabeledRow, {
29963
- label: "Snap Step"
29964
- }, /*#__PURE__*/React.createElement(RangeInput$4, {
29965
- value: this.state.snapStepTextbox,
29966
- onChange: this.changeSnapStep
29967
- })))), /*#__PURE__*/React.createElement("div", {
29968
- className: "perseus-widget-row"
29969
- }, /*#__PURE__*/React.createElement(LabeledRow, {
29970
- label: "Markings:"
29971
- }, /*#__PURE__*/React.createElement(ButtonGroup$2, {
29972
- value: this.props.markings,
29973
- allowEmpty: false,
29974
- buttons: [{
29975
- value: "graph",
29976
- content: "Graph"
29977
- }, {
29978
- value: "grid",
29979
- content: "Grid"
29980
- }, {
29981
- value: "none",
29982
- content: "None"
29983
- }],
29984
- onChange: this.change("markings")
29985
- }))), /*#__PURE__*/React.createElement("div", {
29986
- className: "perseus-widget-left-col"
29987
- }, /*#__PURE__*/React.createElement(PropCheckBox$7, {
29988
- label: "Show tooltips",
29989
- showTooltips: this.props.showTooltips,
29990
- onChange: this.change
29991
- }))), /*#__PURE__*/React.createElement(LabeledRow, {
29992
- label: "Background image URL:",
29993
- style: styles$s.resetSpaceTop
29994
- }, /*#__PURE__*/React.createElement("input", {
29995
- type: "text",
29996
- className: css(styles$s.backgroundUrlInput),
29997
- ref: this.bgUrlRef,
29998
- value: this.state.backgroundImage.url || "",
29999
- onChange: e => {
30000
- // Shallow-copied clone
30001
- const image = _extends({}, this.props.backgroundImage);
30002
- image.url = e.target.value;
30003
- this.setState({
30004
- backgroundImage: image
30005
- });
30006
- },
30007
- onKeyPress: this.changeBackgroundUrl,
30008
- onBlur: this.changeBackgroundUrl
30009
- }), /*#__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, {
30010
- style: styles$s.rulerSection
30011
- }, /*#__PURE__*/React.createElement(View, {
30012
- style: styles$s.checkboxRow
30013
- }, /*#__PURE__*/React.createElement(PropCheckBox$7, {
30014
- label: "Show ruler",
30015
- showRuler: this.props.showRuler,
30016
- onChange: this.change,
30017
- style: styles$s.resetSpaceTop
30018
- }), /*#__PURE__*/React.createElement(PropCheckBox$7, {
30019
- label: "Show protractor",
30020
- showProtractor: this.props.showProtractor,
30021
- onChange: this.change,
30022
- style: styles$s.resetSpaceTop
30023
- })), (this.props.showRuler || this.props.showProtractor) && /*#__PURE__*/React.createElement(Banner, {
30024
- layout: "floating",
30025
- text: "The ruler and protractor are not accessible. Please consider an alternate approach.",
30026
- kind: "warning"
30027
- }), this.props.showRuler && /*#__PURE__*/React.createElement(View, {
30028
- style: styles$s.spaceTop
30029
- }, /*#__PURE__*/React.createElement(LabeledRow, {
30030
- label: "Ruler label:",
30031
- style: styles$s.resetSpaceTop
30032
- }, /*#__PURE__*/React.createElement(SingleSelect, {
30033
- id: "ruler-label-select",
30034
- selectedValue: this.props.rulerLabel,
30035
- onChange: newValue => {
30036
- this.change({
30037
- rulerLabel: newValue
30038
- });
30039
- },
30040
- placeholder: "None",
30041
- style: styles$s.singleSelectShort
30042
- }, /*#__PURE__*/React.createElement(OptionItem, {
30043
- value: "",
30044
- label: "None",
30045
- horizontalRule: "full-width"
30046
- }), /*#__PURE__*/React.createElement(OptionItem, {
30047
- value: "mm",
30048
- label: "Milimeters"
30049
- }), /*#__PURE__*/React.createElement(OptionItem, {
30050
- value: "cm",
30051
- label: "Centimeters"
30052
- }), /*#__PURE__*/React.createElement(OptionItem, {
30053
- value: "m",
30054
- label: "Meters"
30055
- }), /*#__PURE__*/React.createElement(OptionItem, {
30056
- value: "km",
30057
- label: "Kilometers",
30058
- horizontalRule: "full-width"
30059
- }), /*#__PURE__*/React.createElement(OptionItem, {
30060
- value: "in",
30061
- label: "Inches"
30062
- }), /*#__PURE__*/React.createElement(OptionItem, {
30063
- value: "ft",
30064
- label: "Feet"
30065
- }), /*#__PURE__*/React.createElement(OptionItem, {
30066
- value: "yd",
30067
- label: "Yards"
30068
- }), /*#__PURE__*/React.createElement(OptionItem, {
30069
- value: "mi",
30070
- label: "Miles"
30071
- }))), /*#__PURE__*/React.createElement(LabeledRow, {
30072
- label: "Ruler ticks:"
30073
- }, /*#__PURE__*/React.createElement(SingleSelect, {
30074
- id: "ruler-ticks-select",
30075
- selectedValue: `${this.props.rulerTicks}`,
30076
- onChange: newValue => {
30077
- this.change({
30078
- rulerTicks: newValue
30079
- });
30080
- },
30081
- placeholder: "10",
30082
- style: styles$s.singleSelectShort
30083
- }, [1, 2, 4, 8, 10, 16].map(value => /*#__PURE__*/React.createElement(OptionItem, {
30084
- key: value,
30085
- value: `${value}`,
30086
- label: `${value}`
30087
- }))))))));
29769
+ });
29770
+
29771
+ const ColorSwatch = props => {
29772
+ const {
29773
+ color: color$1,
29774
+ filled = true,
29775
+ decorative = false
29776
+ } = props;
29777
+ return /*#__PURE__*/React.createElement(View, {
29778
+ "aria-label":
29779
+ // Don't include an aria label for decorative circles.
29780
+ !decorative ? `${color$1}, ${filled ? "filled" : "open"}` : undefined,
29781
+ style: [styles$u.colorSwatch, {
29782
+ border: `4px solid ${lockedFigureColors[color$1]}`,
29783
+ backgroundColor: filled ? lockedFigureColors[color$1] : color.white
29784
+ }]
29785
+ });
29786
+ };
29787
+ const styles$u = StyleSheet.create({
29788
+ colorSwatch: {
29789
+ // Add a white outline so that the color swatch is visible when
29790
+ // the dropdown option is highlighted with its blue background.
29791
+ outline: `2px solid ${color.offWhite}`,
29792
+ borderRadius: "50%",
29793
+ width: spacing.large_24,
29794
+ height: spacing.large_24
30088
29795
  }
30089
- }
30090
- InteractiveGraphSettings.defaultProps = {
30091
- box: [interactiveSizes.defaultBoxSizeSmall, interactiveSizes.defaultBoxSizeSmall],
30092
- labels: ["x", "y"],
30093
- range: [[-10, 10], [-10, 10]],
30094
- step: [1, 1],
30095
- gridStep: [1, 1],
30096
- snapStep: [1, 1],
30097
- valid: true,
30098
- backgroundImage: defaultBackgroundImage$1,
30099
- markings: "graph",
30100
- showProtractor: false,
30101
- showRuler: false,
30102
- showTooltips: false,
30103
- rulerLabel: "",
30104
- rulerTicks: 10
29796
+ });
29797
+
29798
+ const possibleColors = Object.keys(lockedFigureColors);
29799
+ const ColorSelect = props => {
29800
+ const {
29801
+ selectedValue,
29802
+ style,
29803
+ onChange
29804
+ } = props;
29805
+ return /*#__PURE__*/React.createElement(View, {
29806
+ style: [styles$t.row, style]
29807
+ }, /*#__PURE__*/React.createElement(LabelMedium, {
29808
+ tag: "label",
29809
+ style: styles$t.row
29810
+ }, "color", /*#__PURE__*/React.createElement(Strut, {
29811
+ size: spacing.xxSmall_6
29812
+ }), /*#__PURE__*/React.createElement(SingleSelect, {
29813
+ selectedValue: selectedValue,
29814
+ onChange: onChange
29815
+ // Placeholder is required, but never gets used.
29816
+ ,
29817
+ placeholder: ""
29818
+ }, possibleColors.map(colorName => /*#__PURE__*/React.createElement(OptionItem, {
29819
+ key: colorName,
29820
+ value: colorName,
29821
+ label: colorName,
29822
+ leftAccessory: /*#__PURE__*/React.createElement(ColorSwatch, {
29823
+ color: colorName,
29824
+ decorative: true
29825
+ })
29826
+ }, colorName)))));
29827
+ };
29828
+ const styles$t = StyleSheet.create({
29829
+ row: {
29830
+ display: "flex",
29831
+ flexDirection: "row",
29832
+ alignItems: "center"
29833
+ }
29834
+ });
29835
+
29836
+ const CoordinatePairInput = props => {
29837
+ const {
29838
+ coord,
29839
+ labels,
29840
+ error,
29841
+ style,
29842
+ onChange
29843
+ } = props;
29844
+
29845
+ // Keep track of the coordinates via state as the user is editing them,
29846
+ // before they are updated in the props as a valid number.
29847
+ const [coordState, setCoordState] = React.useState([
29848
+ // Using strings to make it easier to work with the text fields.
29849
+ coord[0].toString(), coord[1].toString()]);
29850
+ function handleCoordChange(newValue, coordIndex) {
29851
+ // Update the local state (update the input field value).
29852
+ const newCoordState = [...coordState];
29853
+ newCoordState[coordIndex] = newValue;
29854
+ setCoordState(newCoordState);
29855
+
29856
+ // If the new value is not a number, don't update the props.
29857
+ // If it's empty, keep the props the same value instead of setting to 0.
29858
+ if (isNaN(+newValue) || newValue === "") {
29859
+ return;
29860
+ }
29861
+
29862
+ // Update the props (update the graph).
29863
+ const newCoords = [...coord];
29864
+ newCoords[coordIndex] = +newValue;
29865
+ onChange(newCoords);
29866
+ }
29867
+ return /*#__PURE__*/React.createElement(View, {
29868
+ style: [styles$s.row, style]
29869
+ }, /*#__PURE__*/React.createElement(LabelMedium, {
29870
+ tag: "label",
29871
+ style: styles$s.row
29872
+ }, labels ? labels[0] : "x coord", /*#__PURE__*/React.createElement(Strut, {
29873
+ size: spacing.xxSmall_6
29874
+ }), /*#__PURE__*/React.createElement(TextField, {
29875
+ type: "number",
29876
+ value: coordState[0],
29877
+ onChange: newValue => handleCoordChange(newValue, 0),
29878
+ style: [styles$s.textField, error ? styles$s.errorField : undefined]
29879
+ })), /*#__PURE__*/React.createElement(Strut, {
29880
+ size: spacing.medium_16
29881
+ }), /*#__PURE__*/React.createElement(LabelMedium, {
29882
+ tag: "label",
29883
+ style: styles$s.row
29884
+ }, labels ? labels[1] : "y coord", /*#__PURE__*/React.createElement(Strut, {
29885
+ size: spacing.xxSmall_6
29886
+ }), /*#__PURE__*/React.createElement(TextField, {
29887
+ type: "number",
29888
+ value: coordState[1],
29889
+ onChange: newValue => handleCoordChange(newValue, 1),
29890
+ style: [styles$s.textField, error ? styles$s.errorField : undefined]
29891
+ })));
30105
29892
  };
30106
29893
  const styles$s = StyleSheet.create({
30107
- resetSpaceTop: {
30108
- marginTop: 0
30109
- },
30110
- spaceTop: {
30111
- marginTop: spacing.xSmall_8
30112
- },
30113
- singleSelectShort: {
30114
- // Non-standard spacing, but it's the smallest we can go
30115
- // without running into styling issues with the dropdown.
30116
- height: 26
29894
+ row: {
29895
+ display: "flex",
29896
+ flexDirection: "row",
29897
+ alignItems: "center"
30117
29898
  },
30118
- backgroundUrlInput: {
30119
- border: `1px solid ${color.offBlack32}`,
30120
- borderRadius: spacing.xxxSmall_4,
30121
- padding: spacing.xxxSmall_4
29899
+ textField: {
29900
+ width: spacing.xxxLarge_64
30122
29901
  },
30123
- checkboxRow: {
30124
- flexDirection: "row",
29902
+ errorField: {
29903
+ borderColor: color.red,
29904
+ backgroundColor: color.fadedRed8
29905
+ }
29906
+ });
29907
+
29908
+ const EllipseSwatch = props => {
29909
+ const {
29910
+ color,
29911
+ fillStyle,
29912
+ strokeStyle
29913
+ } = props;
29914
+ return /*#__PURE__*/React.createElement(View, {
29915
+ "aria-label": `${color}, stroke ${strokeStyle}, fill ${fillStyle}`,
29916
+ style: [styles$r.container, {
29917
+ border: `4px ${strokeStyle} ${lockedFigureColors[color]}`
29918
+ }]
29919
+ }, /*#__PURE__*/React.createElement(View, {
29920
+ style: [styles$r.innerCircle, {
29921
+ backgroundColor: lockedFigureColors[color],
29922
+ opacity: lockedFigureFillStyles[fillStyle]
29923
+ }]
29924
+ }));
29925
+ };
29926
+ const styles$r = StyleSheet.create({
29927
+ container: {
29928
+ // Add a white outline so that the color swatch is visible when
29929
+ // the dropdown option is highlighted with its blue background.
29930
+ outline: `2px solid ${color.offWhite}`,
29931
+ borderRadius: "50%",
29932
+ width: spacing.xLarge_32,
29933
+ height: spacing.large_24,
29934
+ backgroundColor: color.white,
30125
29935
  alignItems: "center",
30126
- justifyContent: "space-between",
30127
- marginBottom: spacing.xSmall_8
29936
+ justifyContent: "center"
30128
29937
  },
30129
- rulerSection: {
30130
- marginTop: spacing.xSmall_8,
30131
- borderTop: `1px solid ${color.offBlack16}`,
30132
- paddingTop: spacing.xSmall_8,
30133
- paddingBottom: spacing.xSmall_8,
30134
- borderBottom: `1px solid ${color.offBlack16}`
29938
+ innerCircle: {
29939
+ width: 28,
29940
+ height: 20,
29941
+ borderRadius: "50%"
30135
29942
  }
30136
29943
  });
30137
29944
 
30138
- const mediaDefaultSpecLargeMarginWidth = spacing.large_24;
30139
- const MEDIA_DEFAULT_SPEC = {
30140
- small: {
30141
- query: "(max-width: 767px)",
30142
- totalColumns: 4,
30143
- gutterWidth: spacing.medium_16,
30144
- marginWidth: spacing.medium_16
29945
+ const LockedFigureSettingsAccordion = props => {
29946
+ const {
29947
+ children,
29948
+ header,
29949
+ expanded,
29950
+ containerStyle,
29951
+ panelStyle,
29952
+ onToggle
29953
+ } = props;
29954
+ return /*#__PURE__*/React.createElement(View
29955
+ // More specificity so that we can override the default
29956
+ // .heading > h2 > .header styles from the articles.less
29957
+ // file (which is imported in perseus-renderer.less).
29958
+ , {
29959
+ className: "locked-figure-accordion"
29960
+ }, /*#__PURE__*/React.createElement(AccordionSection, {
29961
+ expanded: expanded,
29962
+ onToggle: onToggle,
29963
+ style: [styles$q.container, containerStyle],
29964
+ headerStyle: styles$q.accordionHeader,
29965
+ header: header
29966
+ }, /*#__PURE__*/React.createElement(View, {
29967
+ style: [styles$q.accordionPanel, panelStyle]
29968
+ }, children)));
29969
+ };
29970
+ const styles$q = StyleSheet.create({
29971
+ container: {
29972
+ backgroundColor: color.fadedBlue8,
29973
+ marginTop: spacing.xSmall_8
30145
29974
  },
30146
- medium: {
30147
- query: "(min-width: 768px) and (max-width: 1023px)",
30148
- totalColumns: 8,
30149
- gutterWidth: spacing.xLarge_32,
30150
- marginWidth: spacing.large_24
29975
+ accordionHeader: {
29976
+ padding: spacing.small_12,
29977
+ // Don't move the dropdown caret.
29978
+ paddingInlineEnd: 0,
29979
+ // Fixed height so the addition of the color swatch doesn't
29980
+ // change the height of the header when toggling.
29981
+ height: spacing.xxLarge_48
30151
29982
  },
30152
- large: {
30153
- query: "(min-width: 1024px)",
30154
- totalColumns: 12,
30155
- gutterWidth: spacing.xLarge_32,
30156
- marginWidth: mediaDefaultSpecLargeMarginWidth,
30157
- maxWidth: 1120 + mediaDefaultSpecLargeMarginWidth * 2
30158
- }
30159
- };
30160
- ({
30161
- large: {
30162
- query: "(min-width: 1px)",
30163
- totalColumns: 12,
30164
- gutterWidth: spacing.xLarge_32,
30165
- marginWidth: spacing.medium_16
29983
+ accordionPanel: {
29984
+ paddingTop: spacing.xxSmall_6,
29985
+ paddingBottom: spacing.xxxSmall_4,
29986
+ paddingLeft: spacing.small_12,
29987
+ paddingRight: spacing.small_12
30166
29988
  }
30167
29989
  });
30168
- ({
30169
- small: {
30170
- query: "(max-width: 767px)",
30171
- totalColumns: 4,
30172
- gutterWidth: spacing.medium_16,
30173
- marginWidth: spacing.medium_16
29990
+
29991
+ /**
29992
+ * The part of a locked figure settings panel that contains actions
29993
+ * for this panel and its associated locked figure, including
29994
+ * the delete button.
29995
+ */
29996
+ const LockedFigureSettingsActions = props => {
29997
+ const {
29998
+ figureType,
29999
+ onMove,
30000
+ onRemove
30001
+ } = props;
30002
+ return /*#__PURE__*/React.createElement(View, {
30003
+ style: styles$p.container
30004
+ }, /*#__PURE__*/React.createElement(Button, {
30005
+ startIcon: trashIcon,
30006
+ "aria-label": `Delete locked ${figureType}`,
30007
+ onClick: onRemove,
30008
+ kind: "tertiary",
30009
+ style: styles$p.deleteButton
30010
+ }, "Delete"), /*#__PURE__*/React.createElement(Spring, null), props.showM2Features && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(IconButton, {
30011
+ icon: caretDoubleUpIcon,
30012
+ size: "small",
30013
+ "aria-label": `Move locked ${figureType} to the back`,
30014
+ onClick: () => onMove("back"),
30015
+ style: styles$p.iconButton
30016
+ }), /*#__PURE__*/React.createElement(IconButton, {
30017
+ icon: caretUpIcon,
30018
+ size: "small",
30019
+ "aria-label": `Move locked ${figureType} backward`,
30020
+ onClick: () => onMove("backward"),
30021
+ style: styles$p.iconButton
30022
+ }), /*#__PURE__*/React.createElement(IconButton, {
30023
+ icon: caretDownIcon,
30024
+ size: "small",
30025
+ "aria-label": `Move locked ${figureType} forward`,
30026
+ onClick: () => onMove("forward"),
30027
+ style: styles$p.iconButton
30028
+ }), /*#__PURE__*/React.createElement(IconButton, {
30029
+ icon: caretDoubleDownIcon,
30030
+ size: "small",
30031
+ "aria-label": `Move locked ${figureType} to the front`,
30032
+ onClick: () => onMove("front"),
30033
+ style: styles$p.iconButton
30034
+ })));
30035
+ };
30036
+ const styles$p = StyleSheet.create({
30037
+ container: {
30038
+ width: "100%",
30039
+ flexDirection: "row",
30040
+ alignItems: "center",
30041
+ marginTop: spacing.xxxSmall_4
30174
30042
  },
30175
- large: {
30176
- query: "(min-width: 768px)",
30177
- totalColumns: 12,
30178
- gutterWidth: spacing.xLarge_32,
30179
- marginWidth: spacing.xxLarge_48
30043
+ deleteButton: {
30044
+ // Line up the delete icon with the rest of the content.
30045
+ marginInlineStart: -spacing.xxxSmall_4
30046
+ },
30047
+ iconButton: {
30048
+ // Reset the margin because the icon buttons
30049
+ // overlap each other otherwise.
30050
+ margin: 0
30180
30051
  }
30181
30052
  });
30182
30053
 
30183
- const defaultContext = {
30184
- ssrSize: "large",
30185
- mediaSpec: MEDIA_DEFAULT_SPEC
30054
+ const {
30055
+ InfoTip: InfoTip$d
30056
+ } = components;
30057
+ const LockedEllipseSettings = props => {
30058
+ const {
30059
+ center,
30060
+ radius,
30061
+ angle,
30062
+ color,
30063
+ fillStyle,
30064
+ strokeStyle,
30065
+ expanded,
30066
+ onToggle,
30067
+ onChangeProps,
30068
+ onMove,
30069
+ onRemove
30070
+ } = props;
30071
+ function handleColorChange(newValue) {
30072
+ onChangeProps({
30073
+ color: newValue
30074
+ });
30075
+ }
30076
+ return /*#__PURE__*/React.createElement(LockedFigureSettingsAccordion, {
30077
+ expanded: expanded,
30078
+ onToggle: onToggle,
30079
+ header:
30080
+ /*#__PURE__*/
30081
+ // Summary: Ellipse, center, radius, color (opacity, dashed)
30082
+ React.createElement(View, {
30083
+ style: styles$o.row
30084
+ }, /*#__PURE__*/React.createElement(LabelLarge, null, `Ellipse (${center[0]}, ${center[1]}), radius ${radius[0]}, ${radius[1]}`), /*#__PURE__*/React.createElement(Strut, {
30085
+ size: spacing.xSmall_8
30086
+ }), /*#__PURE__*/React.createElement(EllipseSwatch, {
30087
+ color: props.color,
30088
+ fillStyle: fillStyle,
30089
+ strokeStyle: strokeStyle
30090
+ }))
30091
+ }, /*#__PURE__*/React.createElement(View, {
30092
+ style: styles$o.row
30093
+ }, /*#__PURE__*/React.createElement(CoordinatePairInput, {
30094
+ coord: center,
30095
+ style: styles$o.spaceUnder,
30096
+ onChange: newCoords => onChangeProps({
30097
+ center: newCoords
30098
+ })
30099
+ }), /*#__PURE__*/React.createElement(View, {
30100
+ style: styles$o.spaceUnder
30101
+ }, /*#__PURE__*/React.createElement(InfoTip$d, null, "The coordinates for the center of the ellipse."))), /*#__PURE__*/React.createElement(CoordinatePairInput, {
30102
+ coord: radius,
30103
+ labels: ["x radius", "y radius"],
30104
+ style: styles$o.spaceUnder,
30105
+ onChange: newCoords => onChangeProps({
30106
+ radius: newCoords
30107
+ })
30108
+ }), /*#__PURE__*/React.createElement(AngleInput, {
30109
+ angle: angle,
30110
+ onChange: newAngle => onChangeProps({
30111
+ angle: newAngle
30112
+ })
30113
+ }), /*#__PURE__*/React.createElement(Strut, {
30114
+ size: spacing.xSmall_8
30115
+ }), /*#__PURE__*/React.createElement(View, {
30116
+ style: [styles$o.row, styles$o.spaceUnder]
30117
+ }, /*#__PURE__*/React.createElement(ColorSelect, {
30118
+ selectedValue: color,
30119
+ onChange: handleColorChange
30120
+ }), /*#__PURE__*/React.createElement(Strut, {
30121
+ size: spacing.medium_16
30122
+ }), /*#__PURE__*/React.createElement(LabelMedium, {
30123
+ tag: "label",
30124
+ style: styles$o.row
30125
+ }, "fill", /*#__PURE__*/React.createElement(Strut, {
30126
+ size: spacing.xxSmall_6
30127
+ }), /*#__PURE__*/React.createElement(SingleSelect, {
30128
+ selectedValue: fillStyle,
30129
+ onChange: value => onChangeProps({
30130
+ fillStyle: value
30131
+ })
30132
+ // Placeholder is required, but never gets used.
30133
+ ,
30134
+ placeholder: ""
30135
+ }, Object.keys(lockedFigureFillStyles).map(option => /*#__PURE__*/React.createElement(OptionItem, {
30136
+ key: option,
30137
+ value: option,
30138
+ label: option
30139
+ }, option))))), /*#__PURE__*/React.createElement(LabelMedium, {
30140
+ tag: "label",
30141
+ style: styles$o.row
30142
+ }, "stroke", /*#__PURE__*/React.createElement(Strut, {
30143
+ size: spacing.xxSmall_6
30144
+ }), /*#__PURE__*/React.createElement(SingleSelect, {
30145
+ selectedValue: strokeStyle,
30146
+ onChange: value => onChangeProps({
30147
+ strokeStyle: value
30148
+ })
30149
+ // Placeholder is required, but never gets used.
30150
+ ,
30151
+ placeholder: ""
30152
+ }, /*#__PURE__*/React.createElement(OptionItem, {
30153
+ value: "solid",
30154
+ label: "solid"
30155
+ }, "solid"), /*#__PURE__*/React.createElement(OptionItem, {
30156
+ value: "dashed",
30157
+ label: "dashed"
30158
+ }, "dashed"))), /*#__PURE__*/React.createElement(LockedFigureSettingsActions, {
30159
+ showM2Features: props.showM2Features,
30160
+ figureType: props.type,
30161
+ onMove: onMove,
30162
+ onRemove: onRemove
30163
+ }));
30186
30164
  };
30187
- const MediaLayoutContext = React.createContext(defaultContext);
30188
- MediaLayoutContext.displayName = "MediaLayoutContext";
30189
- StyleSheet.create({
30190
- grow: {
30191
- flexGrow: 1
30165
+ const styles$o = StyleSheet.create({
30166
+ row: {
30167
+ display: "flex",
30168
+ flexDirection: "row",
30169
+ alignItems: "center"
30170
+ },
30171
+ spaceUnder: {
30172
+ marginBottom: spacing.xSmall_8
30192
30173
  }
30193
30174
  });
30194
30175
 
30195
- class Strut extends React.Component {
30196
- render() {
30197
- const {
30198
- size,
30199
- style
30200
- } = this.props;
30201
- return React.createElement(View, {
30202
- "aria-hidden": "true",
30203
- style: [strutStyle(size), style]
30204
- });
30205
- }
30206
- }
30207
- const strutStyle = size => {
30208
- return {
30209
- width: size,
30210
- MsFlexBasis: size,
30211
- MsFlexPreferredSize: size,
30212
- WebkitFlexBasis: size,
30213
- flexBasis: size,
30214
- flexShrink: 0
30215
- };
30176
+ const LabeledSwitch = props => {
30177
+ const {
30178
+ checked,
30179
+ label,
30180
+ style,
30181
+ onChange
30182
+ } = props;
30183
+ const ids = useUniqueIdWithMock();
30184
+ const switchId = ids.get("switch");
30185
+ return /*#__PURE__*/React.createElement(View, {
30186
+ style: [styles$n.row, style]
30187
+ }, /*#__PURE__*/React.createElement(Switch, {
30188
+ id: switchId,
30189
+ checked: checked,
30190
+ onChange: onChange
30191
+ }), /*#__PURE__*/React.createElement(Strut, {
30192
+ size: spacing.xSmall_8
30193
+ }), /*#__PURE__*/React.createElement(LabelMedium, {
30194
+ tag: "label",
30195
+ htmlFor: switchId
30196
+ }, label));
30216
30197
  };
30198
+ const styles$n = StyleSheet.create({
30199
+ row: {
30200
+ flexDirection: "row",
30201
+ alignItems: "center"
30202
+ }
30203
+ });
30217
30204
 
30218
30205
  /**
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.
30206
+ * DefiningPointSettings is a component that allows the user to edit the
30207
+ * settings of a point that defines a locked figure (ex. line) on the graph.
30222
30208
  *
30223
30209
  * Used in the interactive graph editor's locked figures section.
30224
30210
  */
30225
- const LockedFigureSelect = props => {
30211
+ const DefiningPointSettings = props => {
30212
+ const {
30213
+ coord,
30214
+ color: pointColor,
30215
+ filled = true,
30216
+ label,
30217
+ showPoint = "false",
30218
+ error,
30219
+ onChangeProps,
30220
+ onTogglePoint,
30221
+ expanded,
30222
+ onToggle
30223
+ } = props;
30224
+ function handleColorChange(newValue) {
30225
+ onChangeProps({
30226
+ color: newValue
30227
+ });
30228
+ }
30229
+ return /*#__PURE__*/React.createElement(LockedFigureSettingsAccordion, {
30230
+ expanded: expanded,
30231
+ onToggle: onToggle,
30232
+ containerStyle: styles$m.container,
30233
+ panelStyle: styles$m.accordionPanel,
30234
+ header:
30235
+ /*#__PURE__*/
30236
+ // Summary: Point, coords, color (filled/open)
30237
+ React.createElement(View, {
30238
+ style: styles$m.row
30239
+ }, /*#__PURE__*/React.createElement(LabelLarge, null, `${label || "Point"} (${coord[0]}, ${coord[1]})`), /*#__PURE__*/React.createElement(Strut, {
30240
+ size: spacing.xSmall_8
30241
+ }), showPoint && /*#__PURE__*/React.createElement(ColorSwatch, {
30242
+ color: pointColor,
30243
+ filled: filled
30244
+ }))
30245
+ }, /*#__PURE__*/React.createElement(CoordinatePairInput, {
30246
+ coord: coord,
30247
+ error: !!error,
30248
+ style: styles$m.spaceUnder,
30249
+ onChange: newCoords => {
30250
+ onChangeProps({
30251
+ coord: newCoords
30252
+ });
30253
+ }
30254
+ }), onTogglePoint && /*#__PURE__*/React.createElement(LabeledSwitch, {
30255
+ label: "show point on graph",
30256
+ checked: !!showPoint,
30257
+ style: showPoint && styles$m.spaceUnder,
30258
+ onChange: onTogglePoint
30259
+ }), showPoint && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(ColorSelect, {
30260
+ selectedValue: pointColor,
30261
+ onChange: handleColorChange,
30262
+ style: styles$m.spaceUnder
30263
+ }), /*#__PURE__*/React.createElement(LabeledSwitch, {
30264
+ label: "open point",
30265
+ checked: !filled,
30266
+ onChange: newValue => {
30267
+ onChangeProps({
30268
+ filled: !newValue
30269
+ });
30270
+ }
30271
+ })));
30272
+ };
30273
+ const styles$m = StyleSheet.create({
30274
+ container: {
30275
+ marginTop: spacing.xSmall_8,
30276
+ marginBottom: 0,
30277
+ marginLeft: -spacing.xxxSmall_4,
30278
+ marginRight: -spacing.xxxSmall_4,
30279
+ backgroundColor: color.white
30280
+ },
30281
+ accordionPanel: {
30282
+ // Need more space since we don't have the actions' margins.
30283
+ paddingBottom: spacing.medium_16
30284
+ },
30285
+ row: {
30286
+ flexDirection: "row",
30287
+ alignItems: "center"
30288
+ },
30289
+ spaceUnder: {
30290
+ marginBottom: spacing.xSmall_8
30291
+ }
30292
+ });
30293
+
30294
+ const LineSwatch = props => {
30226
30295
  const {
30227
- id,
30228
- onChange
30296
+ color,
30297
+ lineStyle
30229
30298
  } = props;
30230
- const figureTypes = props.showM2Features ? ["point", "line", "ellipse", "vector"] : ["point", "line"];
30231
30299
  return /*#__PURE__*/React.createElement(View, {
30232
- style: styles$r.container
30233
- }, /*#__PURE__*/React.createElement(ActionMenu, {
30234
- menuText: "Add locked figure",
30235
- style: styles$r.addElementSelect
30236
- }, figureTypes.map(figureType => /*#__PURE__*/React.createElement(ActionItem, {
30237
- key: `${id}-${figureType}`,
30238
- label: figureType,
30239
- onClick: () => onChange(figureType)
30240
- }, figureType))));
30300
+ style: styles$l.container
30301
+ }, /*#__PURE__*/React.createElement(View, {
30302
+ "aria-label": `${color}, ${lineStyle}`,
30303
+ style: [styles$l.lineSwatch, {
30304
+ border: `5px ${lineStyle} ${lockedFigureColors[color]}`
30305
+ }]
30306
+ }));
30241
30307
  };
30242
- const styles$r = StyleSheet.create({
30308
+ const styles$l = StyleSheet.create({
30243
30309
  container: {
30244
- marginTop: spacing.xSmall_8
30245
- },
30246
- addElementSelect: {
30247
- backgroundColor: color.fadedBlue8,
30310
+ backgroundColor: color.white,
30311
+ justifyContent: "center",
30312
+ padding: spacing.xSmall_8,
30248
30313
  borderRadius: spacing.xxxSmall_4
30314
+ },
30315
+ lineSwatch: {
30316
+ width: 40
30249
30317
  }
30250
30318
  });
30251
30319
 
30252
- function focusWithChromeStickyFocusBugWorkaround(element) {
30253
- // NOTE(marksandstrom) Chrome sticky focus bug.
30254
- //
30255
- // There is a bug in Chrome related to sticky positioning and
30256
- // focus: When a child of an element with sticky positioning is
30257
- // focused with the focus() method, the scrollable area
30258
- // containing that element will scroll to the top (even though
30259
- // the element is already visble on the page). For a dropdown
30260
- // this means that the area will be scrolled to the top when
30261
- // the user interacts with the dropdown (since we actively
30262
- // manage dropdown header and option focus for keyboard users).
30263
- //
30264
- // To work around this bug the `preventScroll` focus option is
30265
- // used.
30266
- //
30267
- // Using `focusOptions` is an experimental browser feature
30268
- // "that should not be used in production code". However, since
30269
- // Chrome is the only browser we support that exhibits this
30270
- // buggy behavior and using `focusOptions` works around the
30271
- // bug, it is perfectly reasonable to use the feature in this
30272
- // case. We are not relying on the effect of the
30273
- // `preventScroll` option otherwise.
30274
- //
30275
- // focusOptions documentation:
30276
- //
30277
- // https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus
30278
- //
30279
- // Note that there could be cases where we want default focus() behavior.
30280
- // One that comes to mind is a dropdown with a long list of options that
30281
- // might be rendered off screen when opened. With this workaround the
30282
- // selected option would not be scrolled into view if the option is near
30283
- // the end of the list. We're not aware of any uses of the dropdown where
30284
- // this is an issue, however, so there isn't currently a way to out of the
30285
- // `preventScroll` behavior.
30320
+ const lengthZeroStr = "The line cannot have length 0.";
30321
+ const LockedLineSettings = props => {
30322
+ const {
30323
+ kind,
30324
+ points,
30325
+ color: lineColor,
30326
+ lineStyle = "solid",
30327
+ showPoint1,
30328
+ showPoint2,
30329
+ onChangeProps,
30330
+ onMove,
30331
+ onRemove
30332
+ } = props;
30333
+ const [point1, point2] = points;
30334
+ const capitalizeKind = kind.charAt(0).toUpperCase() + kind.slice(1);
30335
+ const lineLabel = `${capitalizeKind} (${point1.coord[0]},
30336
+ ${point1.coord[1]}), (${point2.coord[0]}, ${point2.coord[1]})`;
30286
30337
 
30287
- // @ts-expect-error - TS2339 - Property 'focus' does not exist on type 'Element'.
30288
- element.focus({
30289
- preventScroll: true
30290
- });
30291
- }
30292
- const DEFAULT_COLOR = "grayH";
30293
- function getDefaultFigureForType(type) {
30294
- switch (type) {
30295
- case "point":
30296
- return {
30297
- type: "point",
30298
- coord: [0, 0],
30299
- color: DEFAULT_COLOR,
30300
- filled: true
30301
- };
30302
- case "line":
30303
- return {
30304
- type: "line",
30305
- kind: "line",
30306
- points: [getDefaultFigureForType("point"), _extends({}, getDefaultFigureForType("point"), {
30307
- coord: [2, 2]
30308
- })],
30309
- color: DEFAULT_COLOR,
30310
- lineStyle: "solid",
30311
- showPoint1: false,
30312
- showPoint2: false
30313
- };
30314
- case "vector":
30315
- return {
30316
- type: "vector",
30317
- points: [[0, 0], [2, 2]],
30318
- color: DEFAULT_COLOR
30319
- };
30320
- case "ellipse":
30321
- return {
30322
- type: "ellipse",
30323
- center: [0, 0],
30324
- radius: [1, 1],
30325
- angle: 0,
30326
- color: DEFAULT_COLOR,
30327
- fillStyle: "none",
30328
- strokeStyle: "solid"
30329
- };
30330
- case "polygon":
30331
- return {
30332
- type: "polygon",
30333
- points: [[0, 2], [-1, 0], [1, 0]],
30334
- color: DEFAULT_COLOR,
30335
- showVertices: false,
30336
- fillStyle: "none",
30337
- strokeStyle: "solid"
30338
- };
30339
- default:
30340
- throw new UnreachableCaseError(type);
30338
+ // Check if the line has length 0.
30339
+ const isInvalid = vector.equal(point1.coord, point2.coord);
30340
+ function handleChangePoint(newPointProps, index) {
30341
+ const newPoints = [...points];
30342
+ newPoints[index] = _extends({}, points[index], newPointProps);
30343
+ onChangeProps({
30344
+ points: newPoints
30345
+ });
30341
30346
  }
30342
- }
30343
- function degreeToRadian(degrees) {
30344
- return degrees / 180 * Math.PI;
30345
- }
30346
- function radianToDegree(radians) {
30347
- return radians / Math.PI * 180;
30348
- }
30347
+ function handleColorChange(newColor) {
30348
+ onChangeProps({
30349
+ color: newColor,
30350
+ // Keep the line's points' colors in sync with the line color.
30351
+ points: [_extends({}, point1, {
30352
+ color: newColor
30353
+ }), _extends({}, point2, {
30354
+ color: newColor
30355
+ })]
30356
+ });
30357
+ }
30358
+ return /*#__PURE__*/React.createElement(LockedFigureSettingsAccordion, {
30359
+ expanded: props.expanded,
30360
+ onToggle: props.onToggle,
30361
+ header: /*#__PURE__*/React.createElement(View, {
30362
+ style: styles$k.row
30363
+ }, /*#__PURE__*/React.createElement(LabelLarge, null, lineLabel), /*#__PURE__*/React.createElement(Strut, {
30364
+ size: spacing.xSmall_8
30365
+ }), /*#__PURE__*/React.createElement(LineSwatch, {
30366
+ color: lineColor,
30367
+ lineStyle: lineStyle
30368
+ }))
30369
+ }, /*#__PURE__*/React.createElement(LabelMedium, {
30370
+ tag: "label",
30371
+ style: [styles$k.row, styles$k.spaceUnder]
30372
+ }, "kind", /*#__PURE__*/React.createElement(Strut, {
30373
+ size: spacing.xxxSmall_4
30374
+ }), /*#__PURE__*/React.createElement(SingleSelect, {
30375
+ selectedValue: kind,
30376
+ onChange: value => onChangeProps({
30377
+ kind: value
30378
+ })
30379
+ // Placeholder is required, but never gets used.
30380
+ ,
30381
+ placeholder: ""
30382
+ }, /*#__PURE__*/React.createElement(OptionItem, {
30383
+ value: "line",
30384
+ label: "line"
30385
+ }), /*#__PURE__*/React.createElement(OptionItem, {
30386
+ value: "ray",
30387
+ label: "ray"
30388
+ }), /*#__PURE__*/React.createElement(OptionItem, {
30389
+ value: "segment",
30390
+ label: "segment"
30391
+ }))), /*#__PURE__*/React.createElement(View, {
30392
+ style: styles$k.row
30393
+ }, /*#__PURE__*/React.createElement(ColorSelect, {
30394
+ selectedValue: lineColor,
30395
+ onChange: handleColorChange
30396
+ }), /*#__PURE__*/React.createElement(Strut, {
30397
+ size: spacing.small_12
30398
+ }), /*#__PURE__*/React.createElement(LabelMedium, {
30399
+ tag: "label",
30400
+ style: styles$k.row
30401
+ }, "style", /*#__PURE__*/React.createElement(Strut, {
30402
+ size: spacing.xxxSmall_4
30403
+ }), /*#__PURE__*/React.createElement(SingleSelect, {
30404
+ selectedValue: lineStyle,
30405
+ onChange: value => onChangeProps({
30406
+ lineStyle: value
30407
+ })
30408
+ // Placeholder is required, but never gets used.
30409
+ ,
30410
+ placeholder: "",
30411
+ style: styles$k.selectMarginOffset
30412
+ }, /*#__PURE__*/React.createElement(OptionItem, {
30413
+ value: "solid",
30414
+ label: "solid"
30415
+ }), /*#__PURE__*/React.createElement(OptionItem, {
30416
+ value: "dashed",
30417
+ label: "dashed"
30418
+ })))), isInvalid && /*#__PURE__*/React.createElement(LabelMedium, {
30419
+ style: styles$k.errorText
30420
+ }, lengthZeroStr), /*#__PURE__*/React.createElement(DefiningPointSettings, _extends({
30421
+ label: "Point 1",
30422
+ showPoint: showPoint1,
30423
+ error: isInvalid ? lengthZeroStr : null
30424
+ }, point1, {
30425
+ onTogglePoint: newValue => onChangeProps({
30426
+ showPoint1: newValue
30427
+ }),
30428
+ onChangeProps: newProps => handleChangePoint(newProps, 0)
30429
+ })), /*#__PURE__*/React.createElement(DefiningPointSettings, _extends({
30430
+ label: "Point 2",
30431
+ showPoint: showPoint2,
30432
+ error: isInvalid ? lengthZeroStr : null
30433
+ }, point2, {
30434
+ onTogglePoint: newValue => onChangeProps({
30435
+ showPoint2: newValue
30436
+ }),
30437
+ onChangeProps: newProps => handleChangePoint(newProps, 1)
30438
+ })), /*#__PURE__*/React.createElement(LockedFigureSettingsActions, {
30439
+ showM2Features: props.showM2Features,
30440
+ figureType: props.type,
30441
+ onMove: onMove,
30442
+ onRemove: onRemove
30443
+ }));
30444
+ };
30445
+ const styles$k = StyleSheet.create({
30446
+ row: {
30447
+ display: "flex",
30448
+ flexDirection: "row",
30449
+ alignItems: "center"
30450
+ },
30451
+ spaceUnder: {
30452
+ marginBottom: spacing.xSmall_8
30453
+ },
30454
+ selectMarginOffset: {
30455
+ // Align with the point settings accordions.
30456
+ marginInlineEnd: -spacing.xxxSmall_4
30457
+ },
30458
+ errorText: {
30459
+ color: color.red
30460
+ }
30461
+ });
30349
30462
 
30350
- const AngleInput = props => {
30463
+ /**
30464
+ * LockedPointSettings is a component that allows the user to edit the
30465
+ * settings of specifically a locked point on the graph.
30466
+ *
30467
+ * Used in the interactive graph editor's locked figures section.
30468
+ */
30469
+ const LockedPointSettings = props => {
30351
30470
  const {
30352
- angle,
30353
- onChange
30471
+ coord,
30472
+ color: pointColor,
30473
+ filled = true,
30474
+ onChangeProps,
30475
+ onMove,
30476
+ onRemove
30354
30477
  } = props;
30355
- const [angleInput, setAngleInput] = React.useState(radianToDegree(angle).toString());
30356
- function handleAngleChange(newValue) {
30357
- // Update the local state (update the input field value).
30358
- setAngleInput(newValue);
30359
-
30360
- // If the new value is not a number, don't update the props.
30361
- // If it's empty, keep the props the same value instead of setting to 0.
30362
- if (isNaN(+newValue) || newValue === "") {
30363
- return;
30364
- }
30365
-
30366
- // Update the graph.
30367
- onChange(degreeToRadian(newValue));
30478
+ function handleColorChange(newValue) {
30479
+ onChangeProps({
30480
+ color: newValue
30481
+ });
30368
30482
  }
30369
- return /*#__PURE__*/React.createElement(LabelMedium, {
30370
- tag: "label",
30371
- style: styles$q.row
30372
- }, "angle (degrees)", /*#__PURE__*/React.createElement(Strut, {
30373
- size: spacing.xxSmall_6
30374
- }), /*#__PURE__*/React.createElement(TextField, {
30375
- type: "number",
30376
- value: angleInput,
30377
- onChange: handleAngleChange,
30378
- style: styles$q.textField
30379
- }), /*#__PURE__*/React.createElement(Strut, {
30380
- size: spacing.xxSmall_6
30483
+ return /*#__PURE__*/React.createElement(LockedFigureSettingsAccordion, {
30484
+ expanded: props.expanded,
30485
+ onToggle: props.onToggle,
30486
+ header:
30487
+ /*#__PURE__*/
30488
+ // Summary: Point, coords, color (filled/open)
30489
+ React.createElement(View, {
30490
+ style: styles$j.row
30491
+ }, /*#__PURE__*/React.createElement(LabelLarge, null, `Point (${coord[0]}, ${coord[1]})`), /*#__PURE__*/React.createElement(Strut, {
30492
+ size: spacing.xSmall_8
30493
+ }), /*#__PURE__*/React.createElement(ColorSwatch, {
30494
+ color: pointColor,
30495
+ filled: filled
30496
+ }))
30497
+ }, /*#__PURE__*/React.createElement(CoordinatePairInput, {
30498
+ coord: coord,
30499
+ style: styles$j.spaceUnder,
30500
+ onChange: newCoord => {
30501
+ onChangeProps({
30502
+ coord: newCoord
30503
+ });
30504
+ }
30505
+ }), /*#__PURE__*/React.createElement(ColorSelect, {
30506
+ selectedValue: pointColor,
30507
+ onChange: handleColorChange,
30508
+ style: styles$j.spaceUnder
30509
+ }), /*#__PURE__*/React.createElement(LabeledSwitch, {
30510
+ label: "open point",
30511
+ checked: !filled,
30512
+ onChange: newValue => {
30513
+ onChangeProps({
30514
+ filled: !newValue
30515
+ });
30516
+ }
30517
+ }), /*#__PURE__*/React.createElement(LockedFigureSettingsActions, {
30518
+ showM2Features: props.showM2Features,
30519
+ figureType: props.type,
30520
+ onMove: onMove,
30521
+ onRemove: onRemove
30381
30522
  }));
30382
30523
  };
30383
- const styles$q = StyleSheet.create({
30524
+ const styles$j = StyleSheet.create({
30384
30525
  row: {
30385
- display: "flex",
30386
30526
  flexDirection: "row",
30387
30527
  alignItems: "center"
30388
30528
  },
30389
- textField: {
30390
- width: spacing.xxxLarge_64
30529
+ spaceUnder: {
30530
+ marginBottom: spacing.xSmall_8
30391
30531
  }
30392
30532
  });
30393
30533
 
30394
- const ColorSwatch = props => {
30534
+ const PolygonSwatch = props => {
30395
30535
  const {
30396
- color: color$1,
30397
- filled = true,
30398
- decorative = false
30536
+ color,
30537
+ fillStyle,
30538
+ strokeStyle
30399
30539
  } = props;
30400
30540
  return /*#__PURE__*/React.createElement(View, {
30401
- "aria-label":
30402
- // Don't include an aria label for decorative circles.
30403
- !decorative ? `${color$1}, ${filled ? "filled" : "open"}` : undefined,
30404
- style: [styles$p.colorSwatch, {
30405
- border: `4px solid ${lockedFigureColors[color$1]}`,
30406
- backgroundColor: filled ? lockedFigureColors[color$1] : color.white
30541
+ "aria-label": `${color}, stroke ${strokeStyle}, fill ${fillStyle}`,
30542
+ style: [styles$i.container, {
30543
+ border: `4px ${strokeStyle} ${lockedFigureColors[color]}`
30407
30544
  }]
30408
- });
30545
+ }, /*#__PURE__*/React.createElement(View, {
30546
+ style: [styles$i.innerSquare, {
30547
+ backgroundColor: lockedFigureColors[color],
30548
+ opacity: lockedFigureFillStyles[fillStyle]
30549
+ }]
30550
+ }));
30409
30551
  };
30410
- const styles$p = StyleSheet.create({
30411
- colorSwatch: {
30552
+ const styles$i = StyleSheet.create({
30553
+ container: {
30412
30554
  // Add a white outline so that the color swatch is visible when
30413
30555
  // the dropdown option is highlighted with its blue background.
30414
30556
  outline: `2px solid ${color.offWhite}`,
30415
- borderRadius: "50%",
30416
30557
  width: spacing.large_24,
30417
- height: spacing.large_24
30558
+ height: spacing.large_24,
30559
+ backgroundColor: color.white,
30560
+ alignItems: "center",
30561
+ justifyContent: "center"
30562
+ },
30563
+ innerSquare: {
30564
+ width: 20,
30565
+ height: 20
30418
30566
  }
30419
30567
  });
30420
30568
 
30421
- const possibleColors = Object.keys(lockedFigureColors);
30422
- const ColorSelect = props => {
30569
+ const LockedPolygonSettings = props => {
30423
30570
  const {
30424
- selectedValue,
30425
- style,
30426
- onChange
30571
+ points,
30572
+ color,
30573
+ showVertices,
30574
+ fillStyle,
30575
+ strokeStyle,
30576
+ expanded,
30577
+ onToggle,
30578
+ onChangeProps,
30579
+ onMove,
30580
+ onRemove
30427
30581
  } = props;
30428
- return /*#__PURE__*/React.createElement(View, {
30429
- style: [styles$o.row, style]
30430
- }, /*#__PURE__*/React.createElement(LabelMedium, {
30582
+ function handleColorChange(newValue) {
30583
+ onChangeProps({
30584
+ color: newValue
30585
+ });
30586
+ }
30587
+ return /*#__PURE__*/React.createElement(LockedFigureSettingsAccordion, {
30588
+ expanded: expanded,
30589
+ onToggle: onToggle,
30590
+ header:
30591
+ /*#__PURE__*/
30592
+ // Summary: Polygon, number of sides, style swatch
30593
+ React.createElement(View, {
30594
+ style: styles$h.row
30595
+ }, /*#__PURE__*/React.createElement(LabelLarge, null, `Polygon, ${points.length} sides`), /*#__PURE__*/React.createElement(Strut, {
30596
+ size: spacing.xSmall_8
30597
+ }), /*#__PURE__*/React.createElement(PolygonSwatch, {
30598
+ color: color,
30599
+ fillStyle: fillStyle,
30600
+ strokeStyle: strokeStyle
30601
+ }))
30602
+ }, /*#__PURE__*/React.createElement(View, {
30603
+ style: [styles$h.row, styles$h.spaceUnder]
30604
+ }, /*#__PURE__*/React.createElement(ColorSelect, {
30605
+ selectedValue: color,
30606
+ onChange: handleColorChange
30607
+ }), /*#__PURE__*/React.createElement(Strut, {
30608
+ size: spacing.medium_16
30609
+ }), /*#__PURE__*/React.createElement(LabelMedium, {
30431
30610
  tag: "label",
30432
- style: styles$o.row
30433
- }, "color", /*#__PURE__*/React.createElement(Strut, {
30611
+ style: styles$h.row
30612
+ }, "fill", /*#__PURE__*/React.createElement(Strut, {
30434
30613
  size: spacing.xxSmall_6
30435
30614
  }), /*#__PURE__*/React.createElement(SingleSelect, {
30436
- selectedValue: selectedValue,
30437
- onChange: onChange
30615
+ selectedValue: fillStyle,
30616
+ onChange: value => onChangeProps({
30617
+ fillStyle: value
30618
+ })
30438
30619
  // Placeholder is required, but never gets used.
30439
30620
  ,
30440
30621
  placeholder: ""
30441
- }, possibleColors.map(colorName => /*#__PURE__*/React.createElement(OptionItem, {
30442
- key: colorName,
30443
- value: colorName,
30444
- label: colorName,
30445
- leftAccessory: /*#__PURE__*/React.createElement(ColorSwatch, {
30446
- color: colorName,
30447
- decorative: true
30448
- })
30449
- }, colorName)))));
30450
- };
30451
- const styles$o = StyleSheet.create({
30452
- row: {
30453
- display: "flex",
30454
- flexDirection: "row",
30455
- alignItems: "center"
30456
- }
30457
- });
30458
-
30459
- const CoordinatePairInput = props => {
30460
- const {
30461
- coord,
30462
- labels,
30463
- error,
30464
- onChange
30465
- } = props;
30466
-
30467
- // Keep track of the coordinates via state as the user is editing them,
30468
- // before they are updated in the props as a valid number.
30469
- const [coordState, setCoordState] = React.useState([
30470
- // Using strings to make it easier to work with the text fields.
30471
- coord[0].toString(), coord[1].toString()]);
30472
- function handleCoordChange(newValue, coordIndex) {
30473
- // Update the local state (update the input field value).
30474
- const newCoordState = [...coordState];
30475
- newCoordState[coordIndex] = newValue;
30476
- setCoordState(newCoordState);
30477
-
30478
- // If the new value is not a number, don't update the props.
30479
- // If it's empty, keep the props the same value instead of setting to 0.
30480
- if (isNaN(+newValue) || newValue === "") {
30481
- return;
30482
- }
30483
-
30484
- // Update the props (update the graph).
30485
- const newCoords = [...coord];
30486
- newCoords[coordIndex] = +newValue;
30487
- onChange(newCoords);
30488
- }
30489
- return /*#__PURE__*/React.createElement(View, null, /*#__PURE__*/React.createElement(View, {
30490
- style: [styles$n.row, styles$n.spaceUnder]
30491
- }, /*#__PURE__*/React.createElement(LabelMedium, {
30492
- tag: "label",
30493
- style: styles$n.row
30494
- }, labels ? labels[0] : "x coord", /*#__PURE__*/React.createElement(Strut, {
30495
- size: spacing.xxSmall_6
30496
- }), /*#__PURE__*/React.createElement(TextField, {
30497
- type: "number",
30498
- value: coordState[0],
30499
- onChange: newValue => handleCoordChange(newValue, 0),
30500
- style: [styles$n.textField, error ? styles$n.errorField : undefined]
30501
- })), /*#__PURE__*/React.createElement(Strut, {
30502
- size: spacing.medium_16
30503
- }), /*#__PURE__*/React.createElement(LabelMedium, {
30622
+ }, Object.keys(lockedFigureFillStyles).map(option => /*#__PURE__*/React.createElement(OptionItem, {
30623
+ key: option,
30624
+ value: option,
30625
+ label: option
30626
+ }, option))))), /*#__PURE__*/React.createElement(LabelMedium, {
30504
30627
  tag: "label",
30505
- style: styles$n.row
30506
- }, labels ? labels[1] : "y coord", /*#__PURE__*/React.createElement(Strut, {
30628
+ style: [styles$h.row, styles$h.spaceUnder]
30629
+ }, "stroke", /*#__PURE__*/React.createElement(Strut, {
30507
30630
  size: spacing.xxSmall_6
30508
- }), /*#__PURE__*/React.createElement(TextField, {
30509
- type: "number",
30510
- value: coordState[1],
30511
- onChange: newValue => handleCoordChange(newValue, 1),
30512
- style: [styles$n.textField, error ? styles$n.errorField : undefined]
30513
- }))));
30631
+ }), /*#__PURE__*/React.createElement(SingleSelect, {
30632
+ selectedValue: strokeStyle,
30633
+ onChange: value => onChangeProps({
30634
+ strokeStyle: value
30635
+ })
30636
+ // Placeholder is required, but never gets used.
30637
+ ,
30638
+ placeholder: ""
30639
+ }, /*#__PURE__*/React.createElement(OptionItem, {
30640
+ value: "solid",
30641
+ label: "solid"
30642
+ }, "solid"), /*#__PURE__*/React.createElement(OptionItem, {
30643
+ value: "dashed",
30644
+ label: "dashed"
30645
+ }, "dashed"))), /*#__PURE__*/React.createElement(LabeledSwitch, {
30646
+ label: "show vertices",
30647
+ checked: showVertices,
30648
+ onChange: newValue => onChangeProps({
30649
+ showVertices: newValue
30650
+ }),
30651
+ style: styles$h.spaceUnder
30652
+ }), /*#__PURE__*/React.createElement(LockedFigureSettingsAccordion, {
30653
+ header: /*#__PURE__*/React.createElement(LabelLarge, null, "Points"),
30654
+ expanded: true,
30655
+ containerStyle: styles$h.pointAccordionContainer,
30656
+ panelStyle: styles$h.pointAccordionPanel
30657
+ }, points.map((point, index) => {
30658
+ const pointLabel = String.fromCharCode(65 + index);
30659
+ return /*#__PURE__*/React.createElement(View, {
30660
+ key: `locked-polygon-point-${point[0]}-${point[1]}-index-${index}`,
30661
+ style: [styles$h.row, styles$h.spaceUnder]
30662
+ }, /*#__PURE__*/React.createElement(LabelLarge, null, `${pointLabel}:`), /*#__PURE__*/React.createElement(Strut, {
30663
+ size: spacing.medium_16
30664
+ }), /*#__PURE__*/React.createElement(CoordinatePairInput, {
30665
+ coord: point,
30666
+ labels: ["x", "y"],
30667
+ onChange: newValue => {
30668
+ const newPoints = [...points];
30669
+ newPoints[index] = newValue;
30670
+ props.onChangeProps({
30671
+ points: newPoints
30672
+ });
30673
+ }
30674
+ }),
30675
+ // Only show the minus (delete) buttons if there are
30676
+ // more than 3 points. 3 points is the minimum number
30677
+ // of points for a polygon (triangle).
30678
+ points.length > 3 && /*#__PURE__*/React.createElement(IconButton, {
30679
+ "aria-label": `Delete polygon point ${pointLabel}`,
30680
+ icon: minusCircle,
30681
+ color: "destructive",
30682
+ onClick: () => {
30683
+ const newPoints = [...points];
30684
+ newPoints.splice(index, 1);
30685
+ props.onChangeProps({
30686
+ points: newPoints
30687
+ });
30688
+ },
30689
+ style: styles$h.icon
30690
+ }));
30691
+ }), /*#__PURE__*/React.createElement(Button, {
30692
+ kind: "tertiary",
30693
+ startIcon: plusCircle,
30694
+ onClick: () => {
30695
+ props.onChangeProps({
30696
+ points: [...points, [0, 0]]
30697
+ });
30698
+ }
30699
+ }, "Add point")), /*#__PURE__*/React.createElement(LockedFigureSettingsActions, {
30700
+ showM2Features: props.showM2Features,
30701
+ figureType: props.type,
30702
+ onMove: onMove,
30703
+ onRemove: onRemove
30704
+ }));
30514
30705
  };
30515
- const styles$n = StyleSheet.create({
30706
+ const styles$h = StyleSheet.create({
30516
30707
  row: {
30517
30708
  display: "flex",
30518
30709
  flexDirection: "row",
30519
30710
  alignItems: "center"
30520
30711
  },
30521
- spaceUnder: {
30522
- marginBottom: spacing.xSmall_8
30712
+ pointAccordionContainer: {
30713
+ backgroundColor: color.white
30523
30714
  },
30524
- textField: {
30525
- width: spacing.xxxLarge_64
30715
+ pointAccordionPanel: {
30716
+ alignItems: "start"
30526
30717
  },
30527
- errorField: {
30528
- borderColor: color.red,
30529
- backgroundColor: color.fadedRed8
30718
+ icon: {
30719
+ marginInlineStart: spacing.xxxSmall_4
30720
+ },
30721
+ spaceUnder: {
30722
+ marginBottom: spacing.xSmall_8
30530
30723
  }
30531
30724
  });
30532
30725
 
30533
- const EllipseSwatch = props => {
30726
+ /**
30727
+ * LockedVectorSettings is a component that allows the user to edit the
30728
+ * settings of specifically a locked vector on the graph.
30729
+ *
30730
+ * Used in the interactive graph editor's locked figures section.
30731
+ */
30732
+ const lengthErrorMessage = "The vector cannot have length 0.";
30733
+ const LockedVectorSettings = props => {
30534
30734
  const {
30535
- color,
30536
- fillStyle,
30537
- strokeStyle
30735
+ points,
30736
+ color: lineColor,
30737
+ onChangeProps,
30738
+ onMove,
30739
+ onRemove
30538
30740
  } = props;
30539
- return /*#__PURE__*/React.createElement(View, {
30540
- "aria-label": `${color}, stroke ${strokeStyle}, fill ${fillStyle}`,
30541
- style: [styles$m.container, {
30542
- border: `4px ${strokeStyle} ${lockedFigureColors[color]}`
30543
- }]
30741
+ const [tail, tip] = points;
30742
+ const lineLabel = `Vector (${tail[0]}, ${tail[1]}), (${tip[0]}, ${tip[1]})`;
30743
+
30744
+ // Check if the line has length 0.
30745
+ const isInvalid = vector.equal(tail, tip);
30746
+ function handleChangePoint(newCoord, index) {
30747
+ if (typeof newCoord !== "undefined") {
30748
+ const newPoints = [...points];
30749
+ newPoints[index] = [...newCoord];
30750
+ onChangeProps({
30751
+ points: newPoints
30752
+ });
30753
+ }
30754
+ }
30755
+ function handleColorChange(newColor) {
30756
+ onChangeProps({
30757
+ color: newColor
30758
+ });
30759
+ }
30760
+ return /*#__PURE__*/React.createElement(LockedFigureSettingsAccordion, {
30761
+ expanded: props.expanded,
30762
+ onToggle: props.onToggle,
30763
+ header: /*#__PURE__*/React.createElement(View, {
30764
+ style: styles$g.row
30765
+ }, /*#__PURE__*/React.createElement(LabelLarge, null, lineLabel), /*#__PURE__*/React.createElement(Strut, {
30766
+ size: spacing.xSmall_8
30767
+ }), /*#__PURE__*/React.createElement(LineSwatch, {
30768
+ color: lineColor,
30769
+ lineStyle: "solid"
30770
+ }))
30544
30771
  }, /*#__PURE__*/React.createElement(View, {
30545
- style: [styles$m.innerCircle, {
30546
- backgroundColor: lockedFigureColors[color],
30547
- opacity: lockedFigureFillStyles[fillStyle]
30548
- }]
30772
+ style: [styles$g.row, styles$g.spaceUnder]
30773
+ }, /*#__PURE__*/React.createElement(ColorSelect, {
30774
+ selectedValue: lineColor,
30775
+ onChange: handleColorChange
30776
+ })), isInvalid && /*#__PURE__*/React.createElement(LabelMedium, {
30777
+ style: styles$g.errorText
30778
+ }, lengthErrorMessage), /*#__PURE__*/React.createElement(LockedFigureSettingsAccordion, {
30779
+ expanded: true // Initial state is expanded
30780
+ ,
30781
+ containerStyle: styles$g.container,
30782
+ panelStyle: styles$g.accordionPanel,
30783
+ header: /*#__PURE__*/React.createElement(View, {
30784
+ style: styles$g.row
30785
+ }, /*#__PURE__*/React.createElement(LabelLarge, null, `Tail (${tail[0]}, ${tail[1]})`))
30786
+ }, /*#__PURE__*/React.createElement(CoordinatePairInput, {
30787
+ coord: tail,
30788
+ error: isInvalid,
30789
+ onChange: newProps => {
30790
+ handleChangePoint(newProps, 0);
30791
+ }
30792
+ })), /*#__PURE__*/React.createElement(LockedFigureSettingsAccordion, {
30793
+ expanded: true // Initial state is expanded
30794
+ ,
30795
+ containerStyle: styles$g.container,
30796
+ panelStyle: styles$g.accordionPanel,
30797
+ header: /*#__PURE__*/React.createElement(View, {
30798
+ style: styles$g.row
30799
+ }, /*#__PURE__*/React.createElement(LabelLarge, null, `Tip (${tip[0]}, ${tip[1]})`))
30800
+ }, /*#__PURE__*/React.createElement(CoordinatePairInput, {
30801
+ coord: tip,
30802
+ error: isInvalid,
30803
+ onChange: newProps => {
30804
+ handleChangePoint(newProps, 1);
30805
+ }
30806
+ })), /*#__PURE__*/React.createElement(LockedFigureSettingsActions, {
30807
+ showM2Features: props.showM2Features,
30808
+ figureType: props.type,
30809
+ onMove: onMove,
30810
+ onRemove: onRemove
30549
30811
  }));
30550
30812
  };
30551
- const styles$m = StyleSheet.create({
30813
+ const styles$g = StyleSheet.create({
30814
+ accordionPanel: {
30815
+ paddingBottom: spacing.medium_16
30816
+ },
30552
30817
  container: {
30553
- // Add a white outline so that the color swatch is visible when
30554
- // the dropdown option is highlighted with its blue background.
30555
- outline: `2px solid ${color.offWhite}`,
30556
- borderRadius: "50%",
30557
- width: spacing.xLarge_32,
30558
- height: spacing.large_24,
30559
- backgroundColor: color.white,
30560
- alignItems: "center",
30561
- justifyContent: "center"
30818
+ marginTop: spacing.xSmall_8,
30819
+ marginBottom: 0,
30820
+ marginLeft: -spacing.xxxSmall_4,
30821
+ marginRight: -spacing.xxxSmall_4,
30822
+ backgroundColor: color.white
30562
30823
  },
30563
- innerCircle: {
30564
- width: 28,
30565
- height: 20,
30566
- borderRadius: "50%"
30824
+ errorText: {
30825
+ color: color.red,
30826
+ marginTop: spacing.xSmall_8
30827
+ },
30828
+ row: {
30829
+ flexDirection: "row",
30830
+ alignItems: "center"
30567
30831
  }
30568
30832
  });
30569
30833
 
30570
- const LockedFigureSettingsAccordion = props => {
30834
+ /**
30835
+ * LockedFigureSettings takes in a figure and renders the
30836
+ * respective settings for that figure type.
30837
+ *
30838
+ * Used in the interactive graph editor's locked figures section.
30839
+ */
30840
+
30841
+ // Union this type with other locked figure types when they are added.
30842
+
30843
+ const LockedFigureSettings = props => {
30844
+ switch (props.type) {
30845
+ case "point":
30846
+ return /*#__PURE__*/React.createElement(LockedPointSettings, props);
30847
+ case "line":
30848
+ return /*#__PURE__*/React.createElement(LockedLineSettings, props);
30849
+ case "vector":
30850
+ if (props.showM2Features) {
30851
+ return /*#__PURE__*/React.createElement(LockedVectorSettings, props);
30852
+ }
30853
+ break;
30854
+ case "ellipse":
30855
+ if (props.showM2Features) {
30856
+ return /*#__PURE__*/React.createElement(LockedEllipseSettings, props);
30857
+ }
30858
+ break;
30859
+ case "polygon":
30860
+ if (props.showM2Features) {
30861
+ return /*#__PURE__*/React.createElement(LockedPolygonSettings, props);
30862
+ }
30863
+ }
30864
+ return null;
30865
+ };
30866
+
30867
+ const LockedFiguresSection = props => {
30868
+ var _props$figures;
30869
+ // Keep track of all figures' accordions' expanded states for the
30870
+ // expand/collapse all button. Set the whole array to false initially.
30871
+ const collapsedStateArray = Array(((_props$figures = props.figures) != null ? _props$figures : []).length).fill(false);
30872
+ const [expandedStates, setExpandedStates] = React.useState(collapsedStateArray);
30873
+ const uniqueId = useUniqueIdWithMock().get("locked-figures-section");
30571
30874
  const {
30572
- children,
30573
- header,
30574
- expanded,
30575
- containerStyle,
30576
- panelStyle,
30577
- onToggle
30875
+ figures,
30876
+ onChange
30578
30877
  } = props;
30579
- return /*#__PURE__*/React.createElement(View
30580
- // More specificity so that we can override the default
30581
- // .heading > h2 > .header styles from the articles.less
30582
- // file (which is imported in perseus-renderer.less).
30583
- , {
30584
- className: "locked-figure-accordion"
30585
- }, /*#__PURE__*/React.createElement(AccordionSection, {
30586
- expanded: expanded,
30587
- onToggle: onToggle,
30588
- style: [styles$l.container, containerStyle],
30589
- headerStyle: styles$l.accordionHeader,
30590
- header: header
30591
- }, /*#__PURE__*/React.createElement(View, {
30592
- style: [styles$l.accordionPanel, panelStyle]
30593
- }, children)));
30594
- };
30595
- const styles$l = StyleSheet.create({
30596
- container: {
30597
- backgroundColor: color.fadedBlue8,
30598
- marginTop: spacing.xSmall_8
30599
- },
30600
- accordionHeader: {
30601
- padding: spacing.small_12,
30602
- // Don't move the dropdown caret.
30603
- paddingInlineEnd: 0,
30604
- // Fixed height so the addition of the color swatch doesn't
30605
- // change the height of the header when toggling.
30606
- height: spacing.xxLarge_48
30607
- },
30608
- accordionPanel: {
30609
- paddingTop: spacing.xxSmall_6,
30610
- paddingBottom: spacing.xxxSmall_4,
30611
- paddingLeft: spacing.small_12,
30612
- paddingRight: spacing.small_12
30878
+ function addLockedFigure(newFigure) {
30879
+ const lockedFigures = figures || [];
30880
+ const newProps = {
30881
+ lockedFigures: [...lockedFigures, getDefaultFigureForType(newFigure)]
30882
+ };
30883
+ onChange(newProps);
30884
+ setExpandedStates([...expandedStates, true]);
30885
+ }
30886
+ function moveLockedFigure(index, movement) {
30887
+ // Don't allow moving the first figure up or the last figure down.
30888
+ if (index === 0 && (movement === "back" || movement === "backward")) {
30889
+ return;
30890
+ }
30891
+ if (figures && index === figures.length - 1 && (movement === "front" || movement === "forward")) {
30892
+ return;
30893
+ }
30894
+ const lockedFigures = figures || [];
30895
+ const newFigures = [...lockedFigures];
30896
+ const newExpandedStates = [...expandedStates];
30897
+
30898
+ // First, remove the figure from its current position
30899
+ // in the figures array and the expanded states array.
30900
+ const [removedFigure] = newFigures.splice(index, 1);
30901
+ newExpandedStates.splice(index, 1);
30902
+
30903
+ // Then, add it back in the new position. Add "true" to the
30904
+ // expanded states array for the new position (it must already
30905
+ // be open since the button to move it is being pressed from there).
30906
+ switch (movement) {
30907
+ case "back":
30908
+ newFigures.unshift(removedFigure);
30909
+ newExpandedStates.unshift(true);
30910
+ break;
30911
+ case "backward":
30912
+ newFigures.splice(index - 1, 0, removedFigure);
30913
+ newExpandedStates.splice(index - 1, 0, true);
30914
+ break;
30915
+ case "forward":
30916
+ newFigures.splice(index + 1, 0, removedFigure);
30917
+ newExpandedStates.splice(index + 1, 0, true);
30918
+ break;
30919
+ case "front":
30920
+ newFigures.push(removedFigure);
30921
+ newExpandedStates.push(true);
30922
+ break;
30923
+ }
30924
+ onChange({
30925
+ lockedFigures: newFigures
30926
+ });
30927
+ setExpandedStates(newExpandedStates);
30928
+ }
30929
+ function removeLockedFigure(index) {
30930
+ // eslint-disable-next-line no-alert
30931
+ if (window.confirm("Are you sure you want to delete this figure?")) {
30932
+ const lockedFigures = figures || [];
30933
+ onChange({
30934
+ lockedFigures: [...lockedFigures.slice(0, index), ...lockedFigures.slice(index + 1)]
30935
+ });
30936
+
30937
+ // Update expanded states
30938
+ const newExpandedStates = [...expandedStates];
30939
+ newExpandedStates.splice(index, 1);
30940
+ setExpandedStates(newExpandedStates);
30941
+ }
30942
+ }
30943
+ function changeProps(index,
30944
+ // Omit the type from the figure props so it doesn't think
30945
+ // we're trying to pass in the props for the wrong type.
30946
+ figureProps) {
30947
+ const lockedFigures = figures || [];
30948
+ const newFigures = {
30949
+ lockedFigures: [...lockedFigures.slice(0, index), _extends({}, lockedFigures[index], figureProps), ...lockedFigures.slice(index + 1)]
30950
+ };
30951
+ onChange(newFigures);
30613
30952
  }
30614
- });
30615
-
30616
- /**
30617
- * The part of a locked figure settings panel that contains actions
30618
- * for this panel and its associated locked figure, including
30619
- * the delete button.
30620
- */
30621
- const LockedFigureSettingsActions = props => {
30622
- const {
30623
- onRemove,
30624
- figureAriaLabel
30625
- } = props;
30626
- return /*#__PURE__*/React.createElement(View, {
30627
- style: styles$k.container
30628
- }, /*#__PURE__*/React.createElement(Button, {
30629
- startIcon: trashIcon,
30630
- "aria-label": `Delete ${figureAriaLabel}`,
30631
- onClick: onRemove,
30632
- kind: "tertiary",
30633
- style: styles$k.deleteButton
30634
- }, "Delete"));
30953
+ function toggleExpanded(newValue) {
30954
+ setExpandedStates(Array(figures == null ? void 0 : figures.length).fill(newValue));
30955
+ }
30956
+ const allCollapsed = expandedStates.every(value => !value);
30957
+ const buttonLabel = allCollapsed ? "Expand all" : "Collapse all";
30958
+ const showExpandButton = !!(figures != null && figures.length);
30959
+ return /*#__PURE__*/React.createElement(View, null, figures == null ? void 0 : figures.map((figure, index) => {
30960
+ return /*#__PURE__*/React.createElement(LockedFigureSettings, _extends({
30961
+ key: `${uniqueId}-locked-${figure}-${index}`,
30962
+ showM2Features: props.showM2Features,
30963
+ showM2bFeatures: props.showM2bFeatures,
30964
+ expanded: expandedStates[index],
30965
+ onToggle: newValue => {
30966
+ const newExpanded = [...expandedStates];
30967
+ newExpanded[index] = newValue;
30968
+ setExpandedStates(newExpanded);
30969
+ }
30970
+ }, figure, {
30971
+ onChangeProps: newProps => changeProps(index, newProps),
30972
+ onMove: movement => moveLockedFigure(index, movement),
30973
+ onRemove: () => removeLockedFigure(index)
30974
+ }));
30975
+ }), /*#__PURE__*/React.createElement(View, {
30976
+ style: styles$f.buttonContainer
30977
+ }, /*#__PURE__*/React.createElement(LockedFigureSelect, {
30978
+ showM2Features: props.showM2Features,
30979
+ showM2bFeatures: props.showM2bFeatures,
30980
+ id: `${uniqueId}-select`,
30981
+ onChange: addLockedFigure
30982
+ }), /*#__PURE__*/React.createElement(Strut, {
30983
+ size: spacing.small_12
30984
+ }), showExpandButton && /*#__PURE__*/React.createElement(Button, {
30985
+ kind: "secondary",
30986
+ onClick: () => toggleExpanded(allCollapsed),
30987
+ style: styles$f.button
30988
+ }, buttonLabel)));
30635
30989
  };
30636
- const styles$k = StyleSheet.create({
30637
- container: {
30638
- width: "100%",
30990
+ const styles$f = StyleSheet.create({
30991
+ buttonContainer: {
30639
30992
  flexDirection: "row",
30640
- alignItems: "center",
30641
- marginTop: spacing.xxxSmall_4
30993
+ alignItems: "center"
30642
30994
  },
30643
- deleteButton: {
30644
- // Line up the delete icon with the rest of the content.
30645
- marginInlineStart: -spacing.xxxSmall_4
30995
+ button: {
30996
+ marginTop: spacing.xSmall_8,
30997
+ flexGrow: 1
30646
30998
  }
30647
30999
  });
30648
31000
 
30649
- const {
30650
- InfoTip: InfoTip$c
30651
- } = components;
30652
- const LockedEllipseSettings = props => {
30653
- const {
30654
- center,
30655
- radius,
30656
- angle,
30657
- color,
30658
- fillStyle,
30659
- strokeStyle,
30660
- expanded,
30661
- onToggle,
30662
- onChangeProps,
30663
- onRemove
30664
- } = props;
30665
- function handleColorChange(newValue) {
30666
- onChangeProps({
30667
- color: newValue
30668
- });
31001
+ const UNLIMITED = "unlimited";
31002
+ // Parses a string (ostensibly from some UI element that only supports string
31003
+ // types, such as a <select />) into a numeric point count or the special sigil
31004
+ // "unlimited". Note that 0 is a special case, and means "unlimited".
31005
+ const parsePointCount = points => {
31006
+ const parsed = parseInt(points, 10);
31007
+ if (isNaN(parsed)) {
31008
+ return UNLIMITED;
30669
31009
  }
30670
- return /*#__PURE__*/React.createElement(LockedFigureSettingsAccordion, {
30671
- expanded: expanded,
30672
- onToggle: onToggle,
30673
- header:
30674
- /*#__PURE__*/
30675
- // Summary: Ellipse, center, radius, color (opacity, dashed)
30676
- React.createElement(View, {
30677
- style: styles$j.row
30678
- }, /*#__PURE__*/React.createElement(LabelLarge, null, `Ellipse (${center[0]}, ${center[1]}), radius ${radius[0]}, ${radius[1]}`), /*#__PURE__*/React.createElement(Strut, {
30679
- size: spacing.xSmall_8
30680
- }), /*#__PURE__*/React.createElement(EllipseSwatch, {
30681
- color: props.color,
30682
- fillStyle: fillStyle,
30683
- strokeStyle: strokeStyle
30684
- }))
30685
- }, /*#__PURE__*/React.createElement(View, {
30686
- style: styles$j.row
30687
- }, /*#__PURE__*/React.createElement(CoordinatePairInput, {
30688
- coord: center,
30689
- onChange: newCoords => onChangeProps({
30690
- center: newCoords
30691
- })
30692
- }), /*#__PURE__*/React.createElement(View, {
30693
- style: styles$j.spaceUnder
30694
- }, /*#__PURE__*/React.createElement(InfoTip$c, null, "The coordinates for the center of the ellipse."))), /*#__PURE__*/React.createElement(CoordinatePairInput, {
30695
- coord: radius,
30696
- labels: ["x radius", "y radius"],
30697
- onChange: newCoords => onChangeProps({
30698
- radius: newCoords
30699
- })
30700
- }), /*#__PURE__*/React.createElement(AngleInput, {
30701
- angle: angle,
30702
- onChange: newAngle => onChangeProps({
30703
- angle: newAngle
30704
- })
30705
- }), /*#__PURE__*/React.createElement(Strut, {
30706
- size: spacing.xSmall_8
30707
- }), /*#__PURE__*/React.createElement(View, {
30708
- style: [styles$j.row, styles$j.spaceUnder]
30709
- }, /*#__PURE__*/React.createElement(ColorSelect, {
30710
- selectedValue: color,
30711
- onChange: handleColorChange
30712
- }), /*#__PURE__*/React.createElement(Strut, {
30713
- size: spacing.medium_16
30714
- }), /*#__PURE__*/React.createElement(LabelMedium, {
30715
- tag: "label",
30716
- style: styles$j.row
30717
- }, "fill", /*#__PURE__*/React.createElement(Strut, {
30718
- size: spacing.xxSmall_6
30719
- }), /*#__PURE__*/React.createElement(SingleSelect, {
30720
- selectedValue: fillStyle,
30721
- onChange: value => onChangeProps({
30722
- fillStyle: value
30723
- })
30724
- // Placeholder is required, but never gets used.
30725
- ,
30726
- placeholder: ""
30727
- }, Object.keys(lockedFigureFillStyles).map(option => /*#__PURE__*/React.createElement(OptionItem, {
30728
- key: option,
30729
- value: option,
30730
- label: option
30731
- }, option))))), /*#__PURE__*/React.createElement(LabelMedium, {
30732
- tag: "label",
30733
- style: styles$j.row
30734
- }, "stroke", /*#__PURE__*/React.createElement(Strut, {
30735
- size: spacing.xxSmall_6
30736
- }), /*#__PURE__*/React.createElement(SingleSelect, {
30737
- selectedValue: strokeStyle,
30738
- onChange: value => onChangeProps({
30739
- strokeStyle: value
30740
- })
30741
- // Placeholder is required, but never gets used.
31010
+ return parsed === 0 ? UNLIMITED : parsed;
31011
+ };
31012
+
31013
+ const GraphPointsCountSelector = ({
31014
+ numPoints: _numPoints = 1,
31015
+ onChange
31016
+ }) => {
31017
+ return /*#__PURE__*/React.createElement(SingleSelect, {
31018
+ selectedValue: `${_numPoints}`,
31019
+ onChange: newValue => {
31020
+ onChange(parsePointCount(newValue));
31021
+ }
31022
+ // Never uses placeholder, always has value
30742
31023
  ,
30743
- placeholder: ""
30744
- }, /*#__PURE__*/React.createElement(OptionItem, {
30745
- value: "solid",
30746
- label: "solid"
30747
- }, "solid"), /*#__PURE__*/React.createElement(OptionItem, {
30748
- value: "dashed",
30749
- label: "dashed"
30750
- }, "dashed"))), /*#__PURE__*/React.createElement(LockedFigureSettingsActions, {
30751
- onRemove: onRemove,
30752
- figureAriaLabel: `locked ellipse at ${center[0]}, ${center[1]}`
30753
- }));
31024
+ placeholder: "",
31025
+ style: styles$e.singleSelectShort
31026
+ }, [...[...Array(7).keys()].map(n => /*#__PURE__*/React.createElement(OptionItem, {
31027
+ key: n,
31028
+ value: `${n}`,
31029
+ label: `${n} point${n > 1 ? "s" : ""}`
31030
+ })), /*#__PURE__*/React.createElement(OptionItem, {
31031
+ key: "unlimited",
31032
+ value: UNLIMITED,
31033
+ label: "unlimited"
31034
+ })]);
30754
31035
  };
30755
- const styles$j = StyleSheet.create({
30756
- row: {
30757
- display: "flex",
30758
- flexDirection: "row",
30759
- alignItems: "center"
30760
- },
30761
- spaceUnder: {
30762
- marginBottom: spacing.xSmall_8
31036
+ const styles$e = StyleSheet.create({
31037
+ singleSelectShort: {
31038
+ // Non-standard spacing, but it's the smallest we can go
31039
+ // without running into styling issues with the dropdown.
31040
+ height: 26
30763
31041
  }
30764
31042
  });
30765
31043
 
30766
- const LabeledSwitch = props => {
30767
- const {
30768
- checked,
30769
- label,
30770
- style,
30771
- onChange
30772
- } = props;
30773
- const ids = useUniqueIdWithMock();
30774
- const switchId = ids.get("switch");
30775
- return /*#__PURE__*/React.createElement(View, {
30776
- style: [styles$i.row, style]
30777
- }, /*#__PURE__*/React.createElement(Switch, {
30778
- id: switchId,
30779
- checked: checked,
30780
- onChange: onChange
30781
- }), /*#__PURE__*/React.createElement(Strut, {
30782
- size: spacing.xSmall_8
30783
- }), /*#__PURE__*/React.createElement(LabelMedium, {
30784
- tag: "label",
30785
- htmlFor: switchId
30786
- }, label));
31044
+ const GraphTypeSelector = props => {
31045
+ return /*#__PURE__*/React.createElement(SingleSelect, {
31046
+ selectedValue: props.graphType,
31047
+ onChange: props.onChange,
31048
+ placeholder: "Select a graph type",
31049
+ style: styles$d.singleSelectShort
31050
+ }, /*#__PURE__*/React.createElement(OptionItem, {
31051
+ value: "linear",
31052
+ label: "Linear function"
31053
+ }), /*#__PURE__*/React.createElement(OptionItem, {
31054
+ value: "quadratic",
31055
+ label: "Quadratic function"
31056
+ }), /*#__PURE__*/React.createElement(OptionItem, {
31057
+ value: "sinusoid",
31058
+ label: "Sinusoid function"
31059
+ }), /*#__PURE__*/React.createElement(OptionItem, {
31060
+ value: "circle",
31061
+ label: "Circle"
31062
+ }), /*#__PURE__*/React.createElement(OptionItem, {
31063
+ value: "point",
31064
+ label: "Point(s)"
31065
+ }), /*#__PURE__*/React.createElement(OptionItem, {
31066
+ value: "linear-system",
31067
+ label: "Linear System"
31068
+ }), /*#__PURE__*/React.createElement(OptionItem, {
31069
+ value: "polygon",
31070
+ label: "Polygon"
31071
+ }), /*#__PURE__*/React.createElement(OptionItem, {
31072
+ value: "segment",
31073
+ label: "Line Segment(s)"
31074
+ }), /*#__PURE__*/React.createElement(OptionItem, {
31075
+ value: "ray",
31076
+ label: "Ray"
31077
+ }), /*#__PURE__*/React.createElement(OptionItem, {
31078
+ value: "angle",
31079
+ label: "Angle"
31080
+ }));
30787
31081
  };
30788
- const styles$i = StyleSheet.create({
30789
- row: {
30790
- flexDirection: "row",
30791
- alignItems: "center"
31082
+ const styles$d = StyleSheet.create({
31083
+ singleSelectShort: {
31084
+ // Non-standard spacing, but it's the smallest we can go
31085
+ // without running into styling issues with the dropdown.
31086
+ height: 26
30792
31087
  }
30793
31088
  });
30794
31089
 
30795
- /**
30796
- * DefiningPointSettings is a component that allows the user to edit the
30797
- * settings of a point that defines a locked figure (ex. line) on the graph.
30798
- *
30799
- * Used in the interactive graph editor's locked figures section.
30800
- */
30801
- const DefiningPointSettings = props => {
30802
- const {
30803
- coord,
30804
- color: pointColor,
30805
- filled = true,
30806
- label,
30807
- showPoint = "false",
30808
- error,
30809
- onChangeProps,
30810
- onTogglePoint
30811
- } = props;
30812
- function handleColorChange(newValue) {
30813
- onChangeProps({
30814
- color: newValue
30815
- });
31090
+ const {
31091
+ ButtonGroup: ButtonGroup$2,
31092
+ InfoTip: InfoTip$c,
31093
+ PropCheckBox: PropCheckBox$7,
31094
+ RangeInput: RangeInput$4
31095
+ } = components;
31096
+ const defaultBackgroundImage$1 = {
31097
+ url: null,
31098
+ width: 0,
31099
+ height: 0
31100
+ };
31101
+ function numSteps(range, step) {
31102
+ return Math.floor((range[1] - range[0]) / step);
31103
+ }
31104
+ function Heading({
31105
+ title,
31106
+ isOpen,
31107
+ onToggle
31108
+ }) {
31109
+ return /*#__PURE__*/React.createElement(View, {
31110
+ style: {
31111
+ flexDirection: "row",
31112
+ justifyContent: "space-between",
31113
+ backgroundColor: color.offBlack8,
31114
+ padding: spacing.xSmall_8,
31115
+ marginTop: spacing.small_12,
31116
+ // NOTE(jeremy): This is the inverse of the @editorPadding CSS
31117
+ // variable found in perseus-editor.less. For now, it must
31118
+ // match otherwise there's a gap from this header to the edge
31119
+ // of the editor borders.
31120
+ marginInline: -10,
31121
+ cursor: "pointer"
31122
+ },
31123
+ onClick: () => onToggle == null ? void 0 : onToggle(!isOpen)
31124
+ }, /*#__PURE__*/React.createElement(LabelSmall, null, title), /*#__PURE__*/React.createElement(ToggleableCaret, {
31125
+ isExpanded: isOpen
31126
+ }));
31127
+ }
31128
+ class InteractiveGraphSettings extends React.Component {
31129
+ static stateFromProps(props) {
31130
+ return {
31131
+ labelsTextbox: props.labels,
31132
+ gridStepTextbox: props.gridStep,
31133
+ snapStepTextbox: props.snapStep,
31134
+ stepTextbox: props.step,
31135
+ rangeTextbox: props.range,
31136
+ // Shallow-copied clone
31137
+ backgroundImage: _extends({}, props.backgroundImage)
31138
+ };
30816
31139
  }
30817
- return /*#__PURE__*/React.createElement(LockedFigureSettingsAccordion, {
30818
- expanded: props.expanded,
30819
- onToggle: props.onToggle,
30820
- containerStyle: styles$h.container,
30821
- panelStyle: styles$h.accordionPanel,
30822
- header:
30823
- /*#__PURE__*/
30824
- // Summary: Point, coords, color (filled/open)
30825
- React.createElement(View, {
30826
- style: styles$h.row
30827
- }, /*#__PURE__*/React.createElement(LabelLarge, null, `${label || "Point"} (${coord[0]}, ${coord[1]})`), /*#__PURE__*/React.createElement(Strut, {
30828
- size: spacing.xSmall_8
30829
- }), showPoint && /*#__PURE__*/React.createElement(ColorSwatch, {
30830
- color: pointColor,
30831
- filled: filled
30832
- }))
30833
- }, /*#__PURE__*/React.createElement(CoordinatePairInput, {
30834
- coord: coord,
30835
- error: !!error,
30836
- onChange: newCoords => {
30837
- onChangeProps({
30838
- coord: newCoords
31140
+ constructor(props) {
31141
+ super(props);
31142
+ this._isMounted = false;
31143
+ this.bgUrlRef = /*#__PURE__*/React.createRef();
31144
+ this.labelXRef = /*#__PURE__*/React.createRef();
31145
+ this.labelYRef = /*#__PURE__*/React.createRef();
31146
+ this.change = (...args) => {
31147
+ return Changeable.change.apply(this, args);
31148
+ };
31149
+ // TODO(aria): Make either a wrapper for standard events to work
31150
+ // with this.change, or make these use some TextInput/NumberInput box
31151
+ this.changeRulerLabel = e => {
31152
+ this.change({
31153
+ rulerLabel: e.target.value
30839
31154
  });
30840
- }
30841
- }), onTogglePoint && /*#__PURE__*/React.createElement(LabeledSwitch, {
30842
- label: "show point on graph",
30843
- checked: !!showPoint,
30844
- style: showPoint && styles$h.spaceUnder,
30845
- onChange: onTogglePoint
30846
- }), showPoint && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(ColorSelect, {
30847
- selectedValue: pointColor,
30848
- onChange: handleColorChange,
30849
- style: styles$h.spaceUnder
30850
- }), /*#__PURE__*/React.createElement(LabeledSwitch, {
30851
- label: "open point",
30852
- checked: !filled,
30853
- onChange: newValue => {
30854
- onChangeProps({
30855
- filled: !newValue
31155
+ };
31156
+ this.changeRulerTicks = e => {
31157
+ this.change({
31158
+ rulerTicks: +e.target.value
31159
+ });
31160
+ };
31161
+ this.changeBackgroundUrl = e => {
31162
+ var _this$bgUrlRef$curren;
31163
+ // Only continue on blur or "enter"
31164
+ if (e.type === "keypress" && e.key !== "Enter") {
31165
+ return;
31166
+ }
31167
+ const setUrl = (url, width, height) => {
31168
+ // Shallow-copied clone
31169
+ const image = _extends({}, this.props.backgroundImage);
31170
+ image.url = url;
31171
+ image.width = width;
31172
+ image.height = height;
31173
+ this.setState({
31174
+ backgroundImage: image
31175
+ }, this.changeGraph);
31176
+ };
31177
+ const url = (_this$bgUrlRef$curren = this.bgUrlRef.current) == null ? void 0 : _this$bgUrlRef$curren.value;
31178
+ if (url) {
31179
+ Util.getImageSize(url, (width, height) => {
31180
+ if (this._isMounted) {
31181
+ setUrl(url, width, height);
31182
+ }
31183
+ });
31184
+ } else {
31185
+ setUrl(null, 0, 0);
31186
+ }
31187
+ };
31188
+ this.renderLabelChoices = choices => {
31189
+ return choices.map(nameAndValue => /*#__PURE__*/React.createElement("option", {
31190
+ key: nameAndValue[1],
31191
+ value: nameAndValue[1]
31192
+ }, nameAndValue[0]));
31193
+ };
31194
+ this.validRange = range => {
31195
+ const numbers = _.every(range, function (num) {
31196
+ return _.isFinite(num);
31197
+ });
31198
+ if (!numbers) {
31199
+ return "Range must be a valid number";
31200
+ }
31201
+ if (range[0] >= range[1]) {
31202
+ return "Range must have a higher number on the right";
31203
+ }
31204
+ return true;
31205
+ };
31206
+ this.validateStepValue = settings => {
31207
+ const {
31208
+ step,
31209
+ range,
31210
+ name,
31211
+ minTicks,
31212
+ maxTicks
31213
+ } = settings;
31214
+ const nSteps = numSteps(range, step);
31215
+ if (nSteps < minTicks) {
31216
+ return name + " is too large, there must be at least " + minTicks + " ticks.";
31217
+ }
31218
+ if (nSteps > maxTicks) {
31219
+ return name + " is too small, there can be at most " + maxTicks + " ticks.";
31220
+ }
31221
+ return true;
31222
+ };
31223
+ this.validSnapStep = (step, range) => {
31224
+ return this.validateStepValue({
31225
+ step: step,
31226
+ range: range,
31227
+ name: "Snap step",
31228
+ minTicks: 5,
31229
+ maxTicks: 60
31230
+ });
31231
+ };
31232
+ this.validGridStep = (step, range) => {
31233
+ return this.validateStepValue({
31234
+ step: step,
31235
+ range: range,
31236
+ name: "Grid step",
31237
+ minTicks: 3,
31238
+ maxTicks: 60
31239
+ });
31240
+ };
31241
+ this.validStep = (step, range) => {
31242
+ return this.validateStepValue({
31243
+ step: step,
31244
+ range: range,
31245
+ name: "Step",
31246
+ minTicks: 3,
31247
+ maxTicks: 20
31248
+ });
31249
+ };
31250
+ this.validBackgroundImageSize = image => {
31251
+ // Ignore empty images
31252
+ if (!image.url) {
31253
+ return true;
31254
+ }
31255
+ const validSize = image.width <= 450 && image.height <= 450;
31256
+ if (!validSize) {
31257
+ return "Image must be smaller than 450px x 450px.";
31258
+ }
31259
+ return true;
31260
+ };
31261
+ this.validateGraphSettings = (range, step, gridStep, snapStep, image) => {
31262
+ const self = this;
31263
+ let msg;
31264
+ const goodRange = _.every(range, function (range) {
31265
+ msg = self.validRange(range);
31266
+ return msg === true;
31267
+ });
31268
+ if (!goodRange) {
31269
+ return msg;
31270
+ }
31271
+ const goodStep = _.every(step, function (step, i) {
31272
+ msg = self.validStep(step, range[i]);
31273
+ return msg === true;
31274
+ });
31275
+ if (!goodStep) {
31276
+ return msg;
31277
+ }
31278
+ const goodGridStep = _.every(gridStep, function (gridStep, i) {
31279
+ msg = self.validGridStep(gridStep, range[i]);
31280
+ return msg === true;
31281
+ });
31282
+ if (!goodGridStep) {
31283
+ return msg;
31284
+ }
31285
+ const goodSnapStep = _.every(snapStep, function (snapStep, i) {
31286
+ msg = self.validSnapStep(snapStep, range[i]);
31287
+ return msg === true;
30856
31288
  });
30857
- }
30858
- })));
30859
- };
30860
- const styles$h = StyleSheet.create({
30861
- container: {
30862
- marginTop: spacing.xSmall_8,
30863
- marginBottom: 0,
30864
- marginLeft: -spacing.xxxSmall_4,
30865
- marginRight: -spacing.xxxSmall_4,
30866
- backgroundColor: color.white
30867
- },
30868
- accordionPanel: {
30869
- // Need more space since we don't have the actions' margins.
30870
- paddingBottom: spacing.medium_16
30871
- },
30872
- row: {
30873
- flexDirection: "row",
30874
- alignItems: "center"
30875
- },
30876
- spaceUnder: {
30877
- marginBottom: spacing.xSmall_8
30878
- }
30879
- });
30880
-
30881
- const LineSwatch = props => {
30882
- const {
30883
- color,
30884
- lineStyle
30885
- } = props;
30886
- return /*#__PURE__*/React.createElement(View, {
30887
- style: styles$g.container
30888
- }, /*#__PURE__*/React.createElement(View, {
30889
- "aria-label": `${color}, ${lineStyle}`,
30890
- style: [styles$g.lineSwatch, {
30891
- border: `5px ${lineStyle} ${lockedFigureColors[color]}`
30892
- }]
30893
- }));
30894
- };
30895
- const styles$g = StyleSheet.create({
30896
- container: {
30897
- backgroundColor: color.white,
30898
- justifyContent: "center",
30899
- padding: spacing.xSmall_8,
30900
- borderRadius: spacing.xxxSmall_4
30901
- },
30902
- lineSwatch: {
30903
- width: 40
30904
- }
30905
- });
30906
-
30907
- const lengthZeroStr = "The line cannot have length 0.";
30908
- const LockedLineSettings = props => {
30909
- const {
30910
- kind,
30911
- points,
30912
- color: lineColor,
30913
- lineStyle = "solid",
30914
- showPoint1,
30915
- showPoint2,
30916
- onChangeProps,
30917
- onRemove
30918
- } = props;
30919
- const [point1, point2] = points;
30920
- const capitalizeKind = kind.charAt(0).toUpperCase() + kind.slice(1);
30921
- const lineLabel = `${capitalizeKind} (${point1.coord[0]},
30922
- ${point1.coord[1]}), (${point2.coord[0]}, ${point2.coord[1]})`;
31289
+ if (!goodSnapStep) {
31290
+ return msg;
31291
+ }
31292
+ const goodImageSize = this.validBackgroundImageSize(image);
31293
+ if (goodImageSize !== true) {
31294
+ msg = goodImageSize;
31295
+ return msg;
31296
+ }
31297
+ return true;
31298
+ };
31299
+ this.changeLabel = (i, e) => {
31300
+ const val = e.target.value;
31301
+ const labels = this.state.labelsTextbox.slice();
31302
+ labels[i] = val;
31303
+ this.setState({
31304
+ labelsTextbox: labels
31305
+ }, this.changeGraph);
31306
+ };
31307
+ this.changeRange = (i, values) => {
31308
+ const ranges = this.state.rangeTextbox.slice();
31309
+ ranges[i] = values;
31310
+ const step = this.state.stepTextbox.slice();
31311
+ const gridStep = this.state.gridStepTextbox.slice();
31312
+ const snapStep = this.state.snapStepTextbox.slice();
31313
+ const scale = Util.scaleFromExtent(ranges[i], this.props.box[i]);
31314
+ if (this.validRange(ranges[i]) === true) {
31315
+ step[i] = Util.tickStepFromExtent(ranges[i], this.props.box[i]);
31316
+ const gridStepValue = Util.gridStepFromTickStep(step[i], scale);
31317
+ if (gridStepValue) {
31318
+ gridStep[i] = gridStepValue;
31319
+ }
31320
+ snapStep[i] = gridStep[i] / 2;
31321
+ }
31322
+ this.setState({
31323
+ stepTextbox: step,
31324
+ gridStepTextbox: gridStep,
31325
+ snapStepTextbox: snapStep,
31326
+ rangeTextbox: ranges
31327
+ }, this.changeGraph);
31328
+ };
31329
+ this.changeStep = step => {
31330
+ this.setState({
31331
+ stepTextbox: step
31332
+ }, this.changeGraph);
31333
+ };
31334
+ this.changeSnapStep = snapStep => {
31335
+ this.setState({
31336
+ snapStepTextbox: snapStep
31337
+ }, this.changeGraph);
31338
+ };
31339
+ this.changeGridStep = gridStep => {
31340
+ this.setState({
31341
+ gridStepTextbox: gridStep,
31342
+ snapStepTextbox: _.map(gridStep, function (step) {
31343
+ return step / 2;
31344
+ })
31345
+ }, this.changeGraph);
31346
+ };
31347
+ this.changeGraph = () => {
31348
+ const labels = this.state.labelsTextbox;
31349
+ const range = _.map(this.state.rangeTextbox, function (range) {
31350
+ return _.map(range, Number);
31351
+ });
31352
+ const step = _.map(this.state.stepTextbox, Number);
31353
+ const gridStep = this.state.gridStepTextbox;
31354
+ const snapStep = this.state.snapStepTextbox;
31355
+ const image = this.state.backgroundImage;
30923
31356
 
30924
- // Check if the line has length 0.
30925
- const isInvalid = vector.equal(point1.coord, point2.coord);
30926
- function handleChangePoint(newPointProps, index) {
30927
- const newPoints = [...points];
30928
- newPoints[index] = _extends({}, points[index], newPointProps);
30929
- onChangeProps({
30930
- points: newPoints
30931
- });
30932
- }
30933
- function handleColorChange(newColor) {
30934
- onChangeProps({
30935
- color: newColor,
30936
- // Keep the line's points' colors in sync with the line color.
30937
- points: [_extends({}, point1, {
30938
- color: newColor
30939
- }), _extends({}, point2, {
30940
- color: newColor
30941
- })]
30942
- });
30943
- }
30944
- return /*#__PURE__*/React.createElement(LockedFigureSettingsAccordion, {
30945
- expanded: props.expanded,
30946
- onToggle: props.onToggle,
30947
- header: /*#__PURE__*/React.createElement(View, {
30948
- style: styles$f.row
30949
- }, /*#__PURE__*/React.createElement(LabelLarge, null, lineLabel), /*#__PURE__*/React.createElement(Strut, {
30950
- size: spacing.xSmall_8
30951
- }), /*#__PURE__*/React.createElement(LineSwatch, {
30952
- color: lineColor,
30953
- lineStyle: lineStyle
30954
- }))
30955
- }, /*#__PURE__*/React.createElement(LabelMedium, {
30956
- tag: "label",
30957
- style: [styles$f.row, styles$f.spaceUnder]
30958
- }, "kind", /*#__PURE__*/React.createElement(Strut, {
30959
- size: spacing.xxxSmall_4
30960
- }), /*#__PURE__*/React.createElement(SingleSelect, {
30961
- selectedValue: kind,
30962
- onChange: value => onChangeProps({
30963
- kind: value
30964
- })
30965
- // Placeholder is required, but never gets used.
30966
- ,
30967
- placeholder: ""
30968
- }, /*#__PURE__*/React.createElement(OptionItem, {
30969
- value: "line",
30970
- label: "line"
30971
- }), /*#__PURE__*/React.createElement(OptionItem, {
30972
- value: "ray",
30973
- label: "ray"
30974
- }), /*#__PURE__*/React.createElement(OptionItem, {
30975
- value: "segment",
30976
- label: "segment"
30977
- }))), /*#__PURE__*/React.createElement(View, {
30978
- style: styles$f.row
30979
- }, /*#__PURE__*/React.createElement(ColorSelect, {
30980
- selectedValue: lineColor,
30981
- onChange: handleColorChange
30982
- }), /*#__PURE__*/React.createElement(Strut, {
30983
- size: spacing.small_12
30984
- }), /*#__PURE__*/React.createElement(LabelMedium, {
30985
- tag: "label",
30986
- style: styles$f.row
30987
- }, "style", /*#__PURE__*/React.createElement(Strut, {
30988
- size: spacing.xxxSmall_4
30989
- }), /*#__PURE__*/React.createElement(SingleSelect, {
30990
- selectedValue: lineStyle,
30991
- onChange: value => onChangeProps({
30992
- lineStyle: value
30993
- })
30994
- // Placeholder is required, but never gets used.
30995
- ,
30996
- placeholder: "",
30997
- style: styles$f.selectMarginOffset
30998
- }, /*#__PURE__*/React.createElement(OptionItem, {
30999
- value: "solid",
31000
- label: "solid"
31001
- }), /*#__PURE__*/React.createElement(OptionItem, {
31002
- value: "dashed",
31003
- label: "dashed"
31004
- })))), isInvalid && /*#__PURE__*/React.createElement(LabelMedium, {
31005
- style: styles$f.errorText
31006
- }, lengthZeroStr), /*#__PURE__*/React.createElement(DefiningPointSettings, _extends({
31007
- label: "Point 1",
31008
- showPoint: showPoint1,
31009
- error: isInvalid ? lengthZeroStr : null
31010
- }, point1, {
31011
- onTogglePoint: newValue => onChangeProps({
31012
- showPoint1: newValue
31013
- }),
31014
- onChangeProps: newProps => handleChangePoint(newProps, 0)
31015
- })), /*#__PURE__*/React.createElement(DefiningPointSettings, _extends({
31016
- label: "Point 2",
31017
- showPoint: showPoint2,
31018
- error: isInvalid ? lengthZeroStr : null
31019
- }, point2, {
31020
- onTogglePoint: newValue => onChangeProps({
31021
- showPoint2: newValue
31022
- }),
31023
- onChangeProps: newProps => handleChangePoint(newProps, 1)
31024
- })), /*#__PURE__*/React.createElement(LockedFigureSettingsActions, {
31025
- onRemove: onRemove,
31026
- figureAriaLabel: `locked line defined by
31027
- ${point1.coord[0]}, ${point1.coord[1]} and
31028
- ${point2.coord[0]}, ${point2.coord[1]}.`
31029
- }));
31030
- };
31031
- const styles$f = StyleSheet.create({
31032
- row: {
31033
- display: "flex",
31034
- flexDirection: "row",
31035
- alignItems: "center"
31036
- },
31037
- spaceUnder: {
31038
- marginBottom: spacing.xSmall_8
31039
- },
31040
- selectMarginOffset: {
31041
- // Align with the point settings accordions.
31042
- marginInlineEnd: -spacing.xxxSmall_4
31043
- },
31044
- errorText: {
31045
- color: color.red
31357
+ // validationResult is either:
31358
+ // true -> the settings are valid
31359
+ // a string -> the settings are invalid, and the explanation
31360
+ // is contained in the string
31361
+ // TODO(aria): Refactor this to not be confusing
31362
+ const validationResult = this.validateGraphSettings(range, step, gridStep, snapStep, image);
31363
+ if (validationResult === true) {
31364
+ // either true or a string
31365
+ this.change({
31366
+ valid: true,
31367
+ labels: labels,
31368
+ range: range,
31369
+ step: step,
31370
+ gridStep: gridStep,
31371
+ snapStep: snapStep,
31372
+ backgroundImage: image
31373
+ });
31374
+ } else {
31375
+ this.change({
31376
+ valid: validationResult // a string message, not false
31377
+ });
31378
+ }
31379
+ };
31380
+ this.state = _extends({
31381
+ isExpanded: true
31382
+ }, InteractiveGraphSettings.stateFromProps(props));
31046
31383
  }
31047
- });
31048
-
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
- */
31055
- const LockedPointSettings = props => {
31056
- const {
31057
- coord,
31058
- color: pointColor,
31059
- filled = true,
31060
- onChangeProps,
31061
- onRemove
31062
- } = props;
31063
- function handleColorChange(newValue) {
31064
- onChangeProps({
31065
- color: newValue
31066
- });
31384
+ componentDidMount() {
31385
+ // TODO(scottgrant): This is a hack to remove the deprecated call to
31386
+ // this.isMounted() but is still considered an anti-pattern.
31387
+ this._isMounted = true;
31388
+ this.changeGraph = _.debounce(this.changeGraph, 300);
31067
31389
  }
31068
- return /*#__PURE__*/React.createElement(LockedFigureSettingsAccordion, {
31069
- expanded: props.expanded,
31070
- onToggle: props.onToggle,
31071
- header:
31072
- /*#__PURE__*/
31073
- // Summary: Point, coords, color (filled/open)
31074
- React.createElement(View, {
31075
- style: styles$e.row
31076
- }, /*#__PURE__*/React.createElement(LabelLarge, null, `Point (${coord[0]}, ${coord[1]})`), /*#__PURE__*/React.createElement(Strut, {
31077
- size: spacing.xSmall_8
31078
- }), /*#__PURE__*/React.createElement(ColorSwatch, {
31079
- color: pointColor,
31080
- filled: filled
31081
- }))
31082
- }, /*#__PURE__*/React.createElement(CoordinatePairInput, {
31083
- coord: coord,
31084
- onChange: newCoord => {
31085
- onChangeProps({
31086
- coord: newCoord
31087
- });
31088
- }
31089
- }), /*#__PURE__*/React.createElement(ColorSelect, {
31090
- selectedValue: pointColor,
31091
- onChange: handleColorChange,
31092
- style: styles$e.spaceUnder
31093
- }), /*#__PURE__*/React.createElement(LabeledSwitch, {
31094
- label: "open point",
31095
- checked: !filled,
31096
- onChange: newValue => {
31097
- onChangeProps({
31098
- filled: !newValue
31099
- });
31390
+ UNSAFE_componentWillReceiveProps(nextProps) {
31391
+ // Make sure that state updates when switching
31392
+ // between different items in a multi-item editor.
31393
+ 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)) {
31394
+ this.setState(InteractiveGraphSettings.stateFromProps(nextProps));
31100
31395
  }
31101
- }), /*#__PURE__*/React.createElement(LockedFigureSettingsActions, {
31102
- onRemove: onRemove,
31103
- figureAriaLabel: `locked point at ${coord[0]}, ${coord[1]}`
31104
- }));
31105
- };
31106
- const styles$e = StyleSheet.create({
31107
- row: {
31108
- flexDirection: "row",
31109
- alignItems: "center"
31110
- },
31111
- spaceUnder: {
31112
- marginBottom: spacing.xSmall_8
31113
31396
  }
31114
- });
31115
-
31116
- /**
31117
- * LockedVectorSettings is a component that allows the user to edit the
31118
- * settings of specifically a locked vector on the graph.
31119
- *
31120
- * Used in the interactive graph editor's locked figures section.
31121
- */
31122
- const lengthErrorMessage = "The vector cannot have length 0.";
31123
- const LockedVectorSettings = props => {
31124
- const {
31125
- points,
31126
- color: lineColor,
31127
- onChangeProps,
31128
- onRemove
31129
- } = props;
31130
- const [tail, tip] = points;
31131
- const lineLabel = `Vector (${tail[0]}, ${tail[1]}), (${tip[0]}, ${tip[1]})`;
31132
-
31133
- // Check if the line has length 0.
31134
- const isInvalid = vector.equal(tail, tip);
31135
- function handleChangePoint(newCoord, index) {
31136
- if (typeof newCoord !== "undefined") {
31137
- const newPoints = [...points];
31138
- newPoints[index] = [...newCoord];
31139
- onChangeProps({
31140
- points: newPoints
31141
- });
31142
- }
31397
+ componentWillUnmount() {
31398
+ this._isMounted = false;
31143
31399
  }
31144
- function handleColorChange(newColor) {
31145
- onChangeProps({
31146
- color: newColor
31147
- });
31400
+ render() {
31401
+ return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Heading, {
31402
+ title: "Common Graph Settings",
31403
+ isOpen: this.state.isExpanded,
31404
+ onToggle: () => this.setState({
31405
+ isExpanded: !this.state.isExpanded
31406
+ })
31407
+ }), this.state.isExpanded && /*#__PURE__*/React.createElement(View, null, /*#__PURE__*/React.createElement("div", {
31408
+ className: "graph-settings"
31409
+ }, /*#__PURE__*/React.createElement("div", {
31410
+ className: "perseus-widget-row"
31411
+ }, /*#__PURE__*/React.createElement("div", {
31412
+ className: "perseus-widget-left-col"
31413
+ }, /*#__PURE__*/React.createElement(LabeledRow, {
31414
+ label: "x Label"
31415
+ }, /*#__PURE__*/React.createElement("input", {
31416
+ type: "text",
31417
+ className: "graph-settings-axis-label",
31418
+ ref: this.labelXRef,
31419
+ onChange: e => this.changeLabel(0, e),
31420
+ value: this.state.labelsTextbox[0] || ""
31421
+ }))), /*#__PURE__*/React.createElement("div", {
31422
+ className: "perseus-widget-right-col"
31423
+ }, /*#__PURE__*/React.createElement(LabeledRow, {
31424
+ label: "y Label"
31425
+ }, /*#__PURE__*/React.createElement("input", {
31426
+ type: "text",
31427
+ className: "graph-settings-axis-label",
31428
+ ref: this.labelYRef,
31429
+ onChange: e => this.changeLabel(1, e),
31430
+ value: this.state.labelsTextbox[1] || ""
31431
+ })))), /*#__PURE__*/React.createElement("div", {
31432
+ className: "perseus-widget-row"
31433
+ }, /*#__PURE__*/React.createElement("div", {
31434
+ className: "perseus-widget-left-col"
31435
+ }, /*#__PURE__*/React.createElement(LabeledRow, {
31436
+ label: "x Range"
31437
+ }, /*#__PURE__*/React.createElement(RangeInput$4, {
31438
+ value: this.state.rangeTextbox[0],
31439
+ onChange: vals => this.changeRange(0, vals)
31440
+ }))), /*#__PURE__*/React.createElement("div", {
31441
+ className: "perseus-widget-right-col"
31442
+ }, /*#__PURE__*/React.createElement(LabeledRow, {
31443
+ label: "y Range"
31444
+ }, /*#__PURE__*/React.createElement(RangeInput$4, {
31445
+ value: this.state.rangeTextbox[1],
31446
+ onChange: vals => this.changeRange(1, vals)
31447
+ })))), /*#__PURE__*/React.createElement("div", {
31448
+ className: "perseus-widget-row"
31449
+ }, /*#__PURE__*/React.createElement("div", {
31450
+ className: "perseus-widget-left-col"
31451
+ }, /*#__PURE__*/React.createElement(LabeledRow, {
31452
+ label: "Tick Step"
31453
+ }, /*#__PURE__*/React.createElement(RangeInput$4, {
31454
+ value: this.state.stepTextbox,
31455
+ onChange: this.changeStep
31456
+ }))), /*#__PURE__*/React.createElement("div", {
31457
+ className: "perseus-widget-right-col"
31458
+ }, /*#__PURE__*/React.createElement(LabeledRow, {
31459
+ label: "Grid Step"
31460
+ }, /*#__PURE__*/React.createElement(RangeInput$4, {
31461
+ value: this.state.gridStepTextbox,
31462
+ onChange: this.changeGridStep
31463
+ })))), /*#__PURE__*/React.createElement("div", {
31464
+ className: "perseus-widget-row"
31465
+ }, /*#__PURE__*/React.createElement("div", {
31466
+ className: "perseus-widget-left-col"
31467
+ }, /*#__PURE__*/React.createElement(LabeledRow, {
31468
+ label: "Snap Step"
31469
+ }, /*#__PURE__*/React.createElement(RangeInput$4, {
31470
+ value: this.state.snapStepTextbox,
31471
+ onChange: this.changeSnapStep
31472
+ })))), /*#__PURE__*/React.createElement("div", {
31473
+ className: "perseus-widget-row"
31474
+ }, /*#__PURE__*/React.createElement(LabeledRow, {
31475
+ label: "Markings:"
31476
+ }, /*#__PURE__*/React.createElement(ButtonGroup$2, {
31477
+ value: this.props.markings,
31478
+ allowEmpty: false,
31479
+ buttons: [{
31480
+ value: "graph",
31481
+ content: "Graph"
31482
+ }, {
31483
+ value: "grid",
31484
+ content: "Grid"
31485
+ }, {
31486
+ value: "none",
31487
+ content: "None"
31488
+ }],
31489
+ onChange: this.change("markings")
31490
+ }))), /*#__PURE__*/React.createElement("div", {
31491
+ className: "perseus-widget-left-col"
31492
+ }, /*#__PURE__*/React.createElement(PropCheckBox$7, {
31493
+ label: "Show tooltips",
31494
+ showTooltips: this.props.showTooltips,
31495
+ onChange: this.change
31496
+ }))), /*#__PURE__*/React.createElement(LabeledRow, {
31497
+ label: "Background image URL:",
31498
+ style: styles$c.resetSpaceTop
31499
+ }, /*#__PURE__*/React.createElement("input", {
31500
+ type: "text",
31501
+ className: css(styles$c.backgroundUrlInput),
31502
+ ref: this.bgUrlRef,
31503
+ value: this.state.backgroundImage.url || "",
31504
+ onChange: e => {
31505
+ // Shallow-copied clone
31506
+ const image = _extends({}, this.props.backgroundImage);
31507
+ image.url = e.target.value;
31508
+ this.setState({
31509
+ backgroundImage: image
31510
+ });
31511
+ },
31512
+ onKeyPress: this.changeBackgroundUrl,
31513
+ onBlur: this.changeBackgroundUrl
31514
+ }), /*#__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, {
31515
+ style: styles$c.rulerSection
31516
+ }, /*#__PURE__*/React.createElement(View, {
31517
+ style: styles$c.checkboxRow
31518
+ }, /*#__PURE__*/React.createElement(PropCheckBox$7, {
31519
+ label: "Show ruler",
31520
+ showRuler: this.props.showRuler,
31521
+ onChange: this.change,
31522
+ style: styles$c.resetSpaceTop
31523
+ }), /*#__PURE__*/React.createElement(PropCheckBox$7, {
31524
+ label: "Show protractor",
31525
+ showProtractor: this.props.showProtractor,
31526
+ onChange: this.change,
31527
+ style: styles$c.resetSpaceTop
31528
+ })), (this.props.showRuler || this.props.showProtractor) && /*#__PURE__*/React.createElement(Banner, {
31529
+ layout: "floating",
31530
+ text: "The ruler and protractor are not accessible. Please consider an alternate approach.",
31531
+ kind: "warning"
31532
+ }), this.props.showRuler && /*#__PURE__*/React.createElement(View, {
31533
+ style: styles$c.spaceTop
31534
+ }, /*#__PURE__*/React.createElement(LabeledRow, {
31535
+ label: "Ruler label:",
31536
+ style: styles$c.resetSpaceTop
31537
+ }, /*#__PURE__*/React.createElement(SingleSelect, {
31538
+ id: "ruler-label-select",
31539
+ selectedValue: this.props.rulerLabel,
31540
+ onChange: newValue => {
31541
+ this.change({
31542
+ rulerLabel: newValue
31543
+ });
31544
+ },
31545
+ placeholder: "None",
31546
+ style: styles$c.singleSelectShort
31547
+ }, /*#__PURE__*/React.createElement(OptionItem, {
31548
+ value: "",
31549
+ label: "None",
31550
+ horizontalRule: "full-width"
31551
+ }), /*#__PURE__*/React.createElement(OptionItem, {
31552
+ value: "mm",
31553
+ label: "Milimeters"
31554
+ }), /*#__PURE__*/React.createElement(OptionItem, {
31555
+ value: "cm",
31556
+ label: "Centimeters"
31557
+ }), /*#__PURE__*/React.createElement(OptionItem, {
31558
+ value: "m",
31559
+ label: "Meters"
31560
+ }), /*#__PURE__*/React.createElement(OptionItem, {
31561
+ value: "km",
31562
+ label: "Kilometers",
31563
+ horizontalRule: "full-width"
31564
+ }), /*#__PURE__*/React.createElement(OptionItem, {
31565
+ value: "in",
31566
+ label: "Inches"
31567
+ }), /*#__PURE__*/React.createElement(OptionItem, {
31568
+ value: "ft",
31569
+ label: "Feet"
31570
+ }), /*#__PURE__*/React.createElement(OptionItem, {
31571
+ value: "yd",
31572
+ label: "Yards"
31573
+ }), /*#__PURE__*/React.createElement(OptionItem, {
31574
+ value: "mi",
31575
+ label: "Miles"
31576
+ }))), /*#__PURE__*/React.createElement(LabeledRow, {
31577
+ label: "Ruler ticks:"
31578
+ }, /*#__PURE__*/React.createElement(SingleSelect, {
31579
+ id: "ruler-ticks-select",
31580
+ selectedValue: `${this.props.rulerTicks}`,
31581
+ onChange: newValue => {
31582
+ this.change({
31583
+ rulerTicks: newValue
31584
+ });
31585
+ },
31586
+ placeholder: "10",
31587
+ style: styles$c.singleSelectShort
31588
+ }, [1, 2, 4, 8, 10, 16].map(value => /*#__PURE__*/React.createElement(OptionItem, {
31589
+ key: value,
31590
+ value: `${value}`,
31591
+ label: `${value}`
31592
+ }))))))));
31148
31593
  }
31149
- return /*#__PURE__*/React.createElement(LockedFigureSettingsAccordion, {
31150
- expanded: props.expanded,
31151
- onToggle: props.onToggle,
31152
- header: /*#__PURE__*/React.createElement(View, {
31153
- style: styles$d.row
31154
- }, /*#__PURE__*/React.createElement(LabelLarge, null, lineLabel), /*#__PURE__*/React.createElement(Strut, {
31155
- size: spacing.xSmall_8
31156
- }), /*#__PURE__*/React.createElement(LineSwatch, {
31157
- color: lineColor,
31158
- lineStyle: "solid"
31159
- }))
31160
- }, /*#__PURE__*/React.createElement(View, {
31161
- style: [styles$d.row, styles$d.spaceUnder]
31162
- }, /*#__PURE__*/React.createElement(ColorSelect, {
31163
- selectedValue: lineColor,
31164
- onChange: handleColorChange
31165
- })), isInvalid && /*#__PURE__*/React.createElement(LabelMedium, {
31166
- style: styles$d.errorText
31167
- }, lengthErrorMessage), /*#__PURE__*/React.createElement(LockedFigureSettingsAccordion, {
31168
- expanded: true // Initial state is expanded
31169
- ,
31170
- containerStyle: styles$d.container,
31171
- panelStyle: styles$d.accordionPanel,
31172
- header: /*#__PURE__*/React.createElement(View, {
31173
- style: styles$d.row
31174
- }, /*#__PURE__*/React.createElement(LabelLarge, null, `Tail (${tail[0]}, ${tail[1]})`))
31175
- }, /*#__PURE__*/React.createElement(CoordinatePairInput, {
31176
- coord: tail,
31177
- error: isInvalid,
31178
- onChange: newProps => {
31179
- handleChangePoint(newProps, 0);
31180
- }
31181
- })), /*#__PURE__*/React.createElement(LockedFigureSettingsAccordion, {
31182
- expanded: true // Initial state is expanded
31183
- ,
31184
- containerStyle: styles$d.container,
31185
- panelStyle: styles$d.accordionPanel,
31186
- header: /*#__PURE__*/React.createElement(View, {
31187
- style: styles$d.row
31188
- }, /*#__PURE__*/React.createElement(LabelLarge, null, `Tip (${tip[0]}, ${tip[1]})`))
31189
- }, /*#__PURE__*/React.createElement(CoordinatePairInput, {
31190
- coord: tip,
31191
- error: isInvalid,
31192
- onChange: newProps => {
31193
- handleChangePoint(newProps, 1);
31194
- }
31195
- })), /*#__PURE__*/React.createElement(LockedFigureSettingsActions, {
31196
- onRemove: onRemove,
31197
- figureAriaLabel: `locked vector defined by starting point
31198
- ${tail[0]}, ${tail[1]} and extending to
31199
- ${tip[0]}, ${tip[1]}.`
31200
- }));
31594
+ }
31595
+ InteractiveGraphSettings.defaultProps = {
31596
+ box: [interactiveSizes.defaultBoxSizeSmall, interactiveSizes.defaultBoxSizeSmall],
31597
+ labels: ["x", "y"],
31598
+ range: [[-10, 10], [-10, 10]],
31599
+ step: [1, 1],
31600
+ gridStep: [1, 1],
31601
+ snapStep: [1, 1],
31602
+ valid: true,
31603
+ backgroundImage: defaultBackgroundImage$1,
31604
+ markings: "graph",
31605
+ showProtractor: false,
31606
+ showRuler: false,
31607
+ showTooltips: false,
31608
+ rulerLabel: "",
31609
+ rulerTicks: 10
31201
31610
  };
31202
- const styles$d = StyleSheet.create({
31203
- accordionPanel: {
31204
- paddingBottom: spacing.medium_16
31205
- },
31206
- container: {
31207
- marginTop: spacing.xSmall_8,
31208
- marginBottom: 0,
31209
- marginLeft: -spacing.xxxSmall_4,
31210
- marginRight: -spacing.xxxSmall_4,
31211
- backgroundColor: color.white
31611
+ const styles$c = StyleSheet.create({
31612
+ resetSpaceTop: {
31613
+ marginTop: 0
31212
31614
  },
31213
- errorText: {
31214
- color: color.red,
31615
+ spaceTop: {
31215
31616
  marginTop: spacing.xSmall_8
31216
31617
  },
31217
- row: {
31218
- flexDirection: "row",
31219
- alignItems: "center"
31220
- }
31221
- });
31222
-
31223
- /**
31224
- * LockedFigureSettings takes in a figure and renders the
31225
- * respective settings for that figure type.
31226
- *
31227
- * Used in the interactive graph editor's locked figures section.
31228
- */
31229
-
31230
- // Union this type with other locked figure types when they are added.
31231
-
31232
- const LockedFigureSettings = props => {
31233
- switch (props.type) {
31234
- case "point":
31235
- return /*#__PURE__*/React.createElement(LockedPointSettings, props);
31236
- case "line":
31237
- return /*#__PURE__*/React.createElement(LockedLineSettings, props);
31238
- case "ellipse":
31239
- if (props.showM2Features) {
31240
- return /*#__PURE__*/React.createElement(LockedEllipseSettings, props);
31241
- }
31242
- break;
31243
- case "vector":
31244
- if (props.showM2Features) {
31245
- return /*#__PURE__*/React.createElement(LockedVectorSettings, props);
31246
- }
31247
- break;
31248
- }
31249
- return null;
31250
- };
31251
-
31252
- const LockedFiguresSection = props => {
31253
- var _props$figures;
31254
- // Keep track of all figures' accordions' expanded states for the
31255
- // expand/collapse all button. Set the whole array to false initially.
31256
- const collapsedStateArray = Array(((_props$figures = props.figures) != null ? _props$figures : []).length).fill(false);
31257
- const [expandedStates, setExpandedStates] = React.useState(collapsedStateArray);
31258
- const uniqueId = useUniqueIdWithMock().get("locked-figures-section");
31259
- const {
31260
- figures,
31261
- onChange
31262
- } = props;
31263
- function addLockedFigure(newFigure) {
31264
- const lockedFigures = figures || [];
31265
- const newProps = {
31266
- lockedFigures: [...lockedFigures, getDefaultFigureForType(newFigure)]
31267
- };
31268
- onChange(newProps);
31269
- setExpandedStates([...expandedStates, true]);
31270
- }
31271
- function removeLockedFigure(index) {
31272
- // eslint-disable-next-line no-alert
31273
- if (window.confirm("Are you sure you want to delete this figure?")) {
31274
- const lockedFigures = figures || [];
31275
- onChange({
31276
- lockedFigures: [...lockedFigures.slice(0, index), ...lockedFigures.slice(index + 1)]
31277
- });
31278
-
31279
- // Update expanded states
31280
- const newExpandedStates = [...expandedStates];
31281
- newExpandedStates.splice(index, 1);
31282
- setExpandedStates(newExpandedStates);
31283
- }
31284
- }
31285
- function changeProps(index,
31286
- // Omit the type from the figure props so it doesn't think
31287
- // we're trying to pass in the props for the wrong type.
31288
- figureProps) {
31289
- const lockedFigures = figures || [];
31290
- const newFigures = {
31291
- lockedFigures: [...lockedFigures.slice(0, index), _extends({}, lockedFigures[index], figureProps), ...lockedFigures.slice(index + 1)]
31292
- };
31293
- onChange(newFigures);
31294
- }
31295
- function toggleExpanded(newValue) {
31296
- setExpandedStates(Array(figures == null ? void 0 : figures.length).fill(newValue));
31297
- }
31298
- const allCollapsed = expandedStates.every(value => !value);
31299
- const buttonLabel = allCollapsed ? "Expand all" : "Collapse all";
31300
- const showExpandButton = !!(figures != null && figures.length);
31301
- return /*#__PURE__*/React.createElement(View, null, figures == null ? void 0 : figures.map((figure, index) => {
31302
- if (figure.type === "polygon") {
31303
- // TODO(LEMS-1943): Implement locked polygon settings.
31304
- // Remove this block once locked polygon settings are
31305
- // implemented.
31306
- return;
31307
- }
31308
- return /*#__PURE__*/React.createElement(LockedFigureSettings, _extends({
31309
- key: `${uniqueId}-locked-${figure}-${index}`,
31310
- showM2Features: props.showM2Features,
31311
- expanded: expandedStates[index],
31312
- onToggle: newValue => {
31313
- const newExpanded = [...expandedStates];
31314
- newExpanded[index] = newValue;
31315
- setExpandedStates(newExpanded);
31316
- }
31317
- }, figure, {
31318
- onChangeProps: newProps => changeProps(index, newProps),
31319
- onRemove: () => removeLockedFigure(index)
31320
- }));
31321
- }), /*#__PURE__*/React.createElement(View, {
31322
- style: styles$c.buttonContainer
31323
- }, /*#__PURE__*/React.createElement(LockedFigureSelect, {
31324
- showM2Features: props.showM2Features,
31325
- id: `${uniqueId}-select`,
31326
- onChange: addLockedFigure
31327
- }), /*#__PURE__*/React.createElement(Strut, {
31328
- size: spacing.small_12
31329
- }), showExpandButton && /*#__PURE__*/React.createElement(Button, {
31330
- kind: "secondary",
31331
- onClick: () => toggleExpanded(allCollapsed),
31332
- style: styles$c.button
31333
- }, buttonLabel)));
31334
- };
31335
- const styles$c = StyleSheet.create({
31336
- buttonContainer: {
31618
+ singleSelectShort: {
31619
+ // Non-standard spacing, but it's the smallest we can go
31620
+ // without running into styling issues with the dropdown.
31621
+ height: 26
31622
+ },
31623
+ backgroundUrlInput: {
31624
+ border: `1px solid ${color.offBlack32}`,
31625
+ borderRadius: spacing.xxxSmall_4,
31626
+ padding: spacing.xxxSmall_4
31627
+ },
31628
+ checkboxRow: {
31337
31629
  flexDirection: "row",
31338
- alignItems: "center"
31630
+ alignItems: "center",
31631
+ justifyContent: "space-between",
31632
+ marginBottom: spacing.xSmall_8
31339
31633
  },
31340
- button: {
31634
+ rulerSection: {
31341
31635
  marginTop: spacing.xSmall_8,
31342
- flexGrow: 1
31636
+ borderTop: `1px solid ${color.offBlack16}`,
31637
+ paddingTop: spacing.xSmall_8,
31638
+ paddingBottom: spacing.xSmall_8,
31639
+ borderBottom: `1px solid ${color.offBlack16}`
31343
31640
  }
31344
31641
  });
31345
31642
 
@@ -31444,7 +31741,7 @@ class InteractiveGraphEditor extends React.Component {
31444
31741
  return testGraphKey.join(":");
31445
31742
  }
31446
31743
  render() {
31447
- var _this$props$graph$typ, _this$props$graph, _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$apiOption, _this$props$apiOption2;
31744
+ var _this$props$graph$typ, _this$props$graph, _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$apiOption, _this$props$apiOption2, _this$props$apiOption3;
31448
31745
  let graph;
31449
31746
  let equationString;
31450
31747
  const gridStep = this.props.gridStep || Util.getGridStep(this.props.range, this.props.step, interactiveSizes.defaultBoxSize);
@@ -31696,6 +31993,7 @@ class InteractiveGraphEditor extends React.Component {
31696
31993
  // is using Mafs.
31697
31994
  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(LockedFiguresSection, {
31698
31995
  showM2Features: (_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["interactive-graph-locked-features-m2"],
31996
+ showM2bFeatures: (_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-m2b"],
31699
31997
  figures: this.props.lockedFigures,
31700
31998
  onChange: this.props.onChange
31701
31999
  }));