@khanacademy/perseus-editor 6.3.0 → 6.5.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.
package/dist/es/index.js CHANGED
@@ -22,14 +22,14 @@ import { SingleSelect, OptionItem, ActionMenu, ActionItem } from '@khanacademy/w
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
24
  import Banner from '@khanacademy/wonder-blocks-banner';
25
- import { AccordionSection } from '@khanacademy/wonder-blocks-accordion';
26
25
  import Button from '@khanacademy/wonder-blocks-button';
27
- import trashIcon from '@phosphor-icons/core/bold/trash-bold.svg';
28
26
  import Switch from '@khanacademy/wonder-blocks-switch';
27
+ import { AccordionSection } from '@khanacademy/wonder-blocks-accordion';
28
+ import trashIcon from '@phosphor-icons/core/bold/trash-bold.svg';
29
29
 
30
30
  // This file is processed by a Rollup plugin (replace) to inject the production
31
31
  const libName = "@khanacademy/perseus-editor";
32
- const libVersion = "6.3.0";
32
+ const libVersion = "6.5.0";
33
33
  addLibraryVersionToPerseusDebug(libName, libVersion);
34
34
 
35
35
  function _extends() {
@@ -219,7 +219,7 @@ class SectionControlButton extends React.Component {
219
219
  role: "button",
220
220
  hideDefaultFocusRing: true,
221
221
  "aria-label": title,
222
- style: styles$v.button
222
+ style: styles$z.button
223
223
  }, ({
224
224
  hovered,
225
225
  focused,
@@ -227,7 +227,7 @@ class SectionControlButton extends React.Component {
227
227
  }) => /*#__PURE__*/React.createElement(InlineIcon$b, icon));
228
228
  }
229
229
  }
230
- const styles$v = StyleSheet.create({
230
+ const styles$z = StyleSheet.create({
231
231
  button: {
232
232
  backgroundColor: color.gold,
233
233
  margin: 2,
@@ -20727,6 +20727,27 @@ class WidgetSelect extends React.Component {
20727
20727
  }
20728
20728
  }
20729
20729
 
20730
+ /**
20731
+ * Displays a caret in either expanded or collapsed state. For use in headings
20732
+ * that collapse a region.
20733
+ */
20734
+ function ToggleableCaret(props) {
20735
+ const iconStyle = props.isExpanded ? styles$y.expanded : styles$y.collapsed;
20736
+ return /*#__PURE__*/React.createElement(PhosphorIcon, {
20737
+ icon: caretRight,
20738
+ style: iconStyle
20739
+ });
20740
+ }
20741
+ const styles$y = StyleSheet.create({
20742
+ collapsed: {
20743
+ transition: ".15s"
20744
+ },
20745
+ expanded: {
20746
+ transform: "rotate(90deg)",
20747
+ transition: ".15s"
20748
+ }
20749
+ });
20750
+
20730
20751
  /**
20731
20752
  * Displays a collapsable list of TeX rendering errors.
20732
20753
  */
@@ -20749,19 +20770,17 @@ class TexErrorView extends React.Component {
20749
20770
  const {
20750
20771
  showErrors
20751
20772
  } = this.state;
20752
- const iconStyle = showErrors ? styles$u.iconExpanded : styles$u.iconCollapsed;
20753
20773
  return /*#__PURE__*/React.createElement(View, {
20754
- style: styles$u.errorContainer
20774
+ style: styles$x.errorContainer
20755
20775
  }, /*#__PURE__*/React.createElement(View, {
20756
- style: styles$u.title,
20776
+ style: styles$x.title,
20757
20777
  onClick: this.handleToggleTexErrors
20758
- }, /*#__PURE__*/React.createElement(PhosphorIcon, {
20759
- icon: caretRight,
20760
- style: iconStyle
20778
+ }, /*#__PURE__*/React.createElement(ToggleableCaret, {
20779
+ isExpanded: showErrors
20761
20780
  }), "\xA0 TeX Errors (", errorList.length, ")"), showErrors && /*#__PURE__*/React.createElement(View, {
20762
- style: styles$u.errorExplanation
20781
+ style: styles$x.errorExplanation
20763
20782
  }, "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, {
20764
- style: styles$u.error,
20783
+ style: styles$x.error,
20765
20784
  key: index
20766
20785
  }, /*#__PURE__*/React.createElement(View, {
20767
20786
  style: {
@@ -20770,7 +20789,7 @@ class TexErrorView extends React.Component {
20770
20789
  }, e.math), /*#__PURE__*/React.createElement(View, null, e.message))));
20771
20790
  }
20772
20791
  }
20773
- const styles$u = StyleSheet.create({
20792
+ const styles$x = StyleSheet.create({
20774
20793
  title: {
20775
20794
  backgroundColor: "#eee",
20776
20795
  fontSize: "1.25em",
@@ -20790,13 +20809,6 @@ const styles$u = StyleSheet.create({
20790
20809
  },
20791
20810
  error: {
20792
20811
  padding: "4px 10px"
20793
- },
20794
- iconCollapsed: {
20795
- transition: ".15s"
20796
- },
20797
- iconExpanded: {
20798
- transform: "rotate(90deg)",
20799
- transition: ".15s"
20800
20812
  }
20801
20813
  });
20802
20814
 
@@ -24571,7 +24583,7 @@ class SimpleButton extends React.Component {
24571
24583
  } = _this$props,
24572
24584
  otherProps = _objectWithoutPropertiesLoose(_this$props, _excluded$3);
24573
24585
  return /*#__PURE__*/React.createElement("div", _extends({
24574
- className: css(styles$t.baseButton, color === "green" && styles$t.green, color === "orange" && styles$t.orange),
24586
+ className: css(styles$w.baseButton, color === "green" && styles$w.green, color === "orange" && styles$w.orange),
24575
24587
  onClick: onClick
24576
24588
  }, otherProps), children);
24577
24589
  }
@@ -24586,7 +24598,7 @@ SimpleButton.defaultProps = {
24586
24598
  color: "green",
24587
24599
  onClick: () => {}
24588
24600
  };
24589
- const styles$t = StyleSheet.create({
24601
+ const styles$w = StyleSheet.create({
24590
24602
  baseButton: {
24591
24603
  top: "0",
24592
24604
  fontSize: "11px",
@@ -24806,23 +24818,23 @@ const LeafContainer = ({
24806
24818
  }) => {
24807
24819
  const hasPreviewHeading = shape.type === "content" || shape.type === "hint";
24808
24820
  const previewHeading = hasPreviewHeading && /*#__PURE__*/React.createElement("div", {
24809
- className: css(styles$s.containerHeader)
24821
+ className: css(styles$v.containerHeader)
24810
24822
  }, /*#__PURE__*/React.createElement(Header, {
24811
24823
  depth: path.length,
24812
- className: css(styles$s.containerTitle)
24824
+ className: css(styles$v.containerTitle)
24813
24825
  }, capitalize(name)));
24814
24826
  return /*#__PURE__*/React.createElement("div", {
24815
- className: css(styles$s.container)
24827
+ className: css(styles$v.container)
24816
24828
  }, /*#__PURE__*/React.createElement("span", {
24817
- className: css(styles$s.row, styles$s.rowHeading)
24829
+ className: css(styles$v.row, styles$v.rowHeading)
24818
24830
  }, /*#__PURE__*/React.createElement("div", {
24819
- className: css(styles$s.columnLeft)
24831
+ className: css(styles$v.columnLeft)
24820
24832
  }, /*#__PURE__*/React.createElement("div", {
24821
- className: "pod-title " + css(styles$s.containerHeader)
24833
+ className: "pod-title " + css(styles$v.containerHeader)
24822
24834
  }, /*#__PURE__*/React.createElement("div", {
24823
- className: css(styles$s.containerTitle)
24835
+ className: css(styles$v.containerTitle)
24824
24836
  }, capitalize(name)), controls)), /*#__PURE__*/React.createElement("div", {
24825
- className: css(styles$s.columnRight)
24837
+ className: css(styles$v.columnRight)
24826
24838
  }, previewHeading)), children);
24827
24839
  };
24828
24840
  const ArrayContainer = props => {
@@ -24835,11 +24847,11 @@ const ArrayContainer = props => {
24835
24847
  actions
24836
24848
  } = props;
24837
24849
  return /*#__PURE__*/React.createElement("div", {
24838
- className: css(styles$s.container)
24850
+ className: css(styles$v.container)
24839
24851
  }, controls && /*#__PURE__*/React.createElement("div", {
24840
- className: css(styles$s.columnLeft, styles$s.containerHeader)
24852
+ className: css(styles$v.columnLeft, styles$v.containerHeader)
24841
24853
  }, controls), /*#__PURE__*/React.createElement("div", null, children), /*#__PURE__*/React.createElement("div", {
24842
- className: css(styles$s.columnLeft)
24854
+ className: css(styles$v.columnLeft)
24843
24855
  }, /*#__PURE__*/React.createElement("a", {
24844
24856
  href: "javascript:void 0",
24845
24857
  onClick: () => actions.addArrayElement(path, shape.elementShape)
@@ -24852,28 +24864,28 @@ const ObjectContainer = ({
24852
24864
  path
24853
24865
  }) => {
24854
24866
  const headingEditor = /*#__PURE__*/React.createElement("div", {
24855
- className: css(styles$s.containerHeader)
24867
+ className: css(styles$v.containerHeader)
24856
24868
  }, /*#__PURE__*/React.createElement(Header, {
24857
24869
  depth: path.length,
24858
- className: css(styles$s.containerTitle)
24870
+ className: css(styles$v.containerTitle)
24859
24871
  }, capitalize(name)), controls);
24860
24872
  const headingPreview = (name || controls) && /*#__PURE__*/React.createElement("div", {
24861
- className: css(styles$s.containerHeader, styles$s.previewCollectionHeader)
24873
+ className: css(styles$v.containerHeader, styles$v.previewCollectionHeader)
24862
24874
  }, /*#__PURE__*/React.createElement(Header, {
24863
24875
  depth: path.length,
24864
- className: css(styles$s.containerTitle)
24876
+ className: css(styles$v.containerTitle)
24865
24877
  }, capitalize(name)));
24866
24878
  const hasBothHeadings = headingEditor && headingPreview;
24867
24879
  return /*#__PURE__*/React.createElement("div", {
24868
- className: css(styles$s.container)
24880
+ className: css(styles$v.container)
24869
24881
  }, hasBothHeadings && /*#__PURE__*/React.createElement("span", {
24870
- className: css(styles$s.row)
24882
+ className: css(styles$v.row)
24871
24883
  }, /*#__PURE__*/React.createElement("div", {
24872
- className: css(styles$s.columnLeft)
24884
+ className: css(styles$v.columnLeft)
24873
24885
  }, headingEditor), /*#__PURE__*/React.createElement("div", {
24874
- className: css(styles$s.columnRight)
24886
+ className: css(styles$v.columnRight)
24875
24887
  }, headingPreview)), /*#__PURE__*/React.createElement("div", {
24876
- className: css(path.length > 0 && styles$s.contentIndent)
24888
+ className: css(path.length > 0 && styles$v.contentIndent)
24877
24889
  }, children));
24878
24890
  };
24879
24891
 
@@ -24996,11 +25008,11 @@ const ItemNodeContent = withStickiness(props => {
24996
25008
  className: "framework-perseus"
24997
25009
  }, lens(renderers).get(path));
24998
25010
  return /*#__PURE__*/React.createElement("span", null, /*#__PURE__*/React.createElement("div", {
24999
- className: css(styles$s.row)
25011
+ className: css(styles$v.row)
25000
25012
  }, /*#__PURE__*/React.createElement("div", {
25001
- className: css(styles$s.columnLeft)
25013
+ className: css(styles$v.columnLeft)
25002
25014
  }, /*#__PURE__*/React.createElement("div", {
25003
- className: css(sticky && styles$s.sticky)
25015
+ className: css(sticky && styles$v.sticky)
25004
25016
  },
25005
25017
  /*#__PURE__*/
25006
25018
  // @ts-expect-error - TS2769 - No overload matches this call.
@@ -25008,9 +25020,9 @@ const ItemNodeContent = withStickiness(props => {
25008
25020
  onChange: newVal => actions.mergeValueAtPath(path, newVal),
25009
25021
  apiOptions: apiOptions
25010
25022
  })))), /*#__PURE__*/React.createElement("div", {
25011
- className: css(styles$s.columnRight)
25023
+ className: css(styles$v.columnRight)
25012
25024
  }, /*#__PURE__*/React.createElement("div", {
25013
- className: css(sticky && styles$s.sticky)
25025
+ className: css(sticky && styles$v.sticky)
25014
25026
  }, preview))));
25015
25027
  });
25016
25028
  const HintNodeContent = withStickiness(props => {
@@ -25026,13 +25038,13 @@ const HintNodeContent = withStickiness(props => {
25026
25038
  className: "framework-perseus"
25027
25039
  }, lens(renderers).get(path));
25028
25040
  return /*#__PURE__*/React.createElement("div", {
25029
- className: css(styles$s.row)
25041
+ className: css(styles$v.row)
25030
25042
  }, /*#__PURE__*/React.createElement("div", {
25031
- className: css(styles$s.columnLeft)
25043
+ className: css(styles$v.columnLeft)
25032
25044
  }, /*#__PURE__*/React.createElement("div", {
25033
- className: css(sticky && styles$s.sticky)
25045
+ className: css(sticky && styles$v.sticky)
25034
25046
  }, /*#__PURE__*/React.createElement(HintEditor, _extends({}, data, {
25035
- className: css(styles$s.hintEditor),
25047
+ className: css(styles$v.hintEditor),
25036
25048
  onChange: newVal => actions.mergeValueAtPath(path, newVal),
25037
25049
  apiOptions: apiOptions,
25038
25050
  showTitle: false,
@@ -25047,9 +25059,9 @@ const HintNodeContent = withStickiness(props => {
25047
25059
  isFirst: true,
25048
25060
  isLast: true
25049
25061
  })))), /*#__PURE__*/React.createElement("div", {
25050
- className: css(styles$s.columnRight)
25062
+ className: css(styles$v.columnRight)
25051
25063
  }, /*#__PURE__*/React.createElement("div", {
25052
- className: css(sticky && styles$s.sticky)
25064
+ className: css(sticky && styles$v.sticky)
25053
25065
  }, preview)));
25054
25066
  });
25055
25067
  const TagsNodeContent = props => {
@@ -25066,9 +25078,9 @@ const TagsNodeContent = props => {
25066
25078
  return null;
25067
25079
  }
25068
25080
  return /*#__PURE__*/React.createElement("div", {
25069
- className: css(styles$s.columnLeft)
25081
+ className: css(styles$v.columnLeft)
25070
25082
  }, /*#__PURE__*/React.createElement("div", {
25071
- className: css(styles$s.tagsEditor)
25083
+ className: css(styles$v.tagsEditor)
25072
25084
  }, /*#__PURE__*/React.createElement(GroupMetadataEditor, {
25073
25085
  value: data,
25074
25086
  onChange: newVal => actions.setValueAtPath(path, newVal),
@@ -25099,23 +25111,23 @@ const ArrayNodeContent = props => {
25099
25111
  const subpath = path.concat(i);
25100
25112
  const controls = [i > 0 && /*#__PURE__*/React.createElement("div", {
25101
25113
  key: "moveArrayElementUp",
25102
- className: css(styles$s.control)
25114
+ className: css(styles$v.control)
25103
25115
  }, /*#__PURE__*/React.createElement(SimpleButton, {
25104
25116
  color: "orange",
25105
25117
  title: "Move up",
25106
25118
  onClick: () => actions.moveArrayElementUp(subpath)
25107
25119
  }, /*#__PURE__*/React.createElement("div", {
25108
- className: css(styles$s.verticalFlip)
25120
+ className: css(styles$v.verticalFlip)
25109
25121
  }, /*#__PURE__*/React.createElement(InlineIcon$6, iconChevronDown)))), i < data.length - 1 && /*#__PURE__*/React.createElement("div", {
25110
25122
  key: "moveArrayElementDown",
25111
- className: css(styles$s.control)
25123
+ className: css(styles$v.control)
25112
25124
  }, /*#__PURE__*/React.createElement(SimpleButton, {
25113
25125
  color: "orange",
25114
25126
  title: "Move down",
25115
25127
  onClick: () => actions.moveArrayElementDown(subpath)
25116
25128
  }, /*#__PURE__*/React.createElement(InlineIcon$6, iconChevronDown))), /*#__PURE__*/React.createElement("div", {
25117
25129
  key: "removeArrayElement",
25118
- className: css(styles$s.control)
25130
+ className: css(styles$v.control)
25119
25131
  }, /*#__PURE__*/React.createElement(SimpleButton, {
25120
25132
  color: "orange",
25121
25133
  title: "Delete",
@@ -25123,7 +25135,7 @@ const ArrayNodeContent = props => {
25123
25135
  }, /*#__PURE__*/React.createElement(InlineIcon$6, iconTrash$4)))];
25124
25136
  return /*#__PURE__*/React.createElement("div", {
25125
25137
  key: i,
25126
- className: css(styles$s.arrayElement, !elementIsLeaf && styles$s.arrayElementAndNotLeaf)
25138
+ className: css(styles$v.arrayElement, !elementIsLeaf && styles$v.arrayElementAndNotLeaf)
25127
25139
  }, /*#__PURE__*/React.createElement(NodeContainer, _extends({}, otherProps, {
25128
25140
  key: i,
25129
25141
  shape: shape.elementShape,
@@ -25149,7 +25161,7 @@ const ObjectNodeContent = props => {
25149
25161
  // made sense to the shape's author is the order in which we'll iterate :)
25150
25162
  const children = Object.keys(shape.shape).map(subkey => /*#__PURE__*/React.createElement("div", {
25151
25163
  key: subkey,
25152
- className: css(styles$s.objectElement)
25164
+ className: css(styles$v.objectElement)
25153
25165
  }, /*#__PURE__*/React.createElement(NodeContainer, _extends({}, otherProps, {
25154
25166
  shape: shape.shape[subkey],
25155
25167
  data: data[subkey],
@@ -25316,7 +25328,7 @@ class MultiRendererEditor extends React.Component {
25316
25328
  }, this._renderContent());
25317
25329
  }
25318
25330
  }
25319
- const styles$s = StyleSheet.create({
25331
+ const styles$v = StyleSheet.create({
25320
25332
  // This is used in a number of places throughout this file.
25321
25333
  container: {},
25322
25334
  // eslint-disable-next-line react-native/no-unused-styles
@@ -25468,47 +25480,47 @@ const StatefulArticleEditor = createReactClass({
25468
25480
  screen
25469
25481
  } = this.state;
25470
25482
  return /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("div", {
25471
- style: styles$r.controlBar
25483
+ style: styles$u.controlBar
25472
25484
  }, /*#__PURE__*/React.createElement("span", {
25473
- style: styles$r.controls
25485
+ style: styles$u.controls
25474
25486
  }, "Mode:", " ", /*#__PURE__*/React.createElement("span", {
25475
25487
  onClick: () => this.setState({
25476
25488
  mode: "edit"
25477
25489
  }),
25478
- style: mode === "edit" ? styles$r.controlSelected : styles$r.control
25490
+ style: mode === "edit" ? styles$u.controlSelected : styles$u.control
25479
25491
  }, "EDIT"), " | ", /*#__PURE__*/React.createElement("span", {
25480
25492
  onClick: () => this.setState({
25481
25493
  mode: "preview"
25482
25494
  }),
25483
- style: mode === "preview" ? styles$r.controlSelected : styles$r.control
25495
+ style: mode === "preview" ? styles$u.controlSelected : styles$u.control
25484
25496
  }, "PREVIEW"), " | ", /*#__PURE__*/React.createElement("span", {
25485
25497
  onClick: () => this.setState({
25486
25498
  mode: "json"
25487
25499
  }),
25488
- style: mode === "json" ? styles$r.controlSelected : styles$r.control
25500
+ style: mode === "json" ? styles$u.controlSelected : styles$u.control
25489
25501
  }, "JSON")), /*#__PURE__*/React.createElement("span", {
25490
- style: styles$r.controls
25502
+ style: styles$u.controls
25491
25503
  }, "Screen:", " ", /*#__PURE__*/React.createElement("span", {
25492
25504
  onClick: () => this.setState({
25493
25505
  screen: "phone"
25494
25506
  }),
25495
- style: screen === "phone" ? styles$r.controlSelected : styles$r.control
25507
+ style: screen === "phone" ? styles$u.controlSelected : styles$u.control
25496
25508
  }, "PHONE"), " | ", /*#__PURE__*/React.createElement("span", {
25497
25509
  onClick: () => this.setState({
25498
25510
  screen: "tablet"
25499
25511
  }),
25500
- style: screen === "tablet" ? styles$r.controlSelected : styles$r.control
25512
+ style: screen === "tablet" ? styles$u.controlSelected : styles$u.control
25501
25513
  }, "TABLET"), " | ", /*#__PURE__*/React.createElement("span", {
25502
25514
  onClick: () => this.setState({
25503
25515
  screen: "desktop"
25504
25516
  }),
25505
- style: screen === "desktop" ? styles$r.controlSelected : styles$r.control
25517
+ style: screen === "desktop" ? styles$u.controlSelected : styles$u.control
25506
25518
  }, "DESKTOP"))), /*#__PURE__*/React.createElement("div", {
25507
- style: styles$r.editor
25519
+ style: styles$u.editor
25508
25520
  }, /*#__PURE__*/React.createElement(ArticleEditor, this.state)));
25509
25521
  }
25510
25522
  });
25511
- const styles$r = {
25523
+ const styles$u = {
25512
25524
  controlBar: {
25513
25525
  marginBottom: 25,
25514
25526
  marginTop: 5
@@ -26998,10 +27010,10 @@ class GradedGroupEditor extends React.Component {
26998
27010
  }, /*#__PURE__*/React.createElement("div", {
26999
27011
  className: "perseus-widget-row"
27000
27012
  }, /*#__PURE__*/React.createElement("label", {
27001
- className: css(styles$q.title)
27013
+ className: css(styles$t.title)
27002
27014
  }, "Title:", " ", /*#__PURE__*/React.createElement(TextInput$6, {
27003
27015
  value: this.props.title,
27004
- className: css(styles$q.input)
27016
+ className: css(styles$t.input)
27005
27017
  // @ts-expect-error - TS2554 - Expected 3 arguments, but got 1.
27006
27018
  ,
27007
27019
  onChange: this.change("title")
@@ -27026,7 +27038,7 @@ class GradedGroupEditor extends React.Component {
27026
27038
  }, /*#__PURE__*/React.createElement(InlineIcon$4, iconPlus$1), " Add a hint"), this.props.hint && /*#__PURE__*/React.createElement("div", {
27027
27039
  className: "perseus-hint-editor"
27028
27040
  }, /*#__PURE__*/React.createElement("div", {
27029
- className: css(styles$q.hintsTitle)
27041
+ className: css(styles$t.hintsTitle)
27030
27042
  }, "Hint"), /*#__PURE__*/React.createElement(Editor, {
27031
27043
  ref: this.hintEditor,
27032
27044
  content: this.props.hint ? this.props.hint.content : "",
@@ -27063,7 +27075,7 @@ GradedGroupEditor.defaultProps = {
27063
27075
  images: {},
27064
27076
  hint: null
27065
27077
  };
27066
- const styles$q = StyleSheet.create({
27078
+ const styles$t = StyleSheet.create({
27067
27079
  title: {
27068
27080
  fontSize: 18,
27069
27081
  fontWeight: "bold"
@@ -28402,13 +28414,13 @@ function MathquillInput(props) {
28402
28414
  }
28403
28415
  });
28404
28416
  return /*#__PURE__*/React.createElement(View, {
28405
- style: styles$p.outerWrapper
28417
+ style: styles$s.outerWrapper
28406
28418
  }, /*#__PURE__*/React.createElement("span", {
28407
28419
  ref: mathFieldWrapperRef,
28408
28420
  className: "perseus-math-input mq-editable-field mq-math-mode"
28409
28421
  }));
28410
28422
  }
28411
- const styles$p = StyleSheet.create({
28423
+ const styles$s = StyleSheet.create({
28412
28424
  outerWrapper: {
28413
28425
  display: "inline-block",
28414
28426
  borderStyle: "solid",
@@ -29469,7 +29481,7 @@ const GraphPointsCountSelector = ({
29469
29481
  // Never uses placeholder, always has value
29470
29482
  ,
29471
29483
  placeholder: "",
29472
- style: styles$o.singleSelectShort
29484
+ style: styles$r.singleSelectShort
29473
29485
  }, [...[...Array(7).keys()].map(n => /*#__PURE__*/React.createElement(OptionItem, {
29474
29486
  key: n,
29475
29487
  value: `${n}`,
@@ -29480,7 +29492,7 @@ const GraphPointsCountSelector = ({
29480
29492
  label: "unlimited"
29481
29493
  })]);
29482
29494
  };
29483
- const styles$o = StyleSheet.create({
29495
+ const styles$r = StyleSheet.create({
29484
29496
  singleSelectShort: {
29485
29497
  // Non-standard spacing, but it's the smallest we can go
29486
29498
  // without running into styling issues with the dropdown.
@@ -29493,7 +29505,7 @@ const GraphTypeSelector = props => {
29493
29505
  selectedValue: props.graphType,
29494
29506
  onChange: props.onChange,
29495
29507
  placeholder: "Select a graph type",
29496
- style: styles$n.singleSelectShort
29508
+ style: styles$q.singleSelectShort
29497
29509
  }, /*#__PURE__*/React.createElement(OptionItem, {
29498
29510
  value: "linear",
29499
29511
  label: "Linear function"
@@ -29526,7 +29538,7 @@ const GraphTypeSelector = props => {
29526
29538
  label: "Angle"
29527
29539
  }));
29528
29540
  };
29529
- const styles$n = StyleSheet.create({
29541
+ const styles$q = StyleSheet.create({
29530
29542
  singleSelectShort: {
29531
29543
  // Non-standard spacing, but it's the smallest we can go
29532
29544
  // without running into styling issues with the dropdown.
@@ -29543,16 +29555,16 @@ const LabeledRow = props => {
29543
29555
  style
29544
29556
  } = props;
29545
29557
  return /*#__PURE__*/React.createElement("label", {
29546
- className: css(styles$m.label)
29558
+ className: css(styles$p.label)
29547
29559
  }, /*#__PURE__*/React.createElement(View, {
29548
- style: [styles$m.row, style]
29560
+ style: [styles$p.row, style]
29549
29561
  }, labelSide === "start" || /*#__PURE__*/React.createElement(LabelSmall, {
29550
- style: styles$m.spaceEnd
29562
+ style: styles$p.spaceEnd
29551
29563
  }, label), children, labelSide === "end" && /*#__PURE__*/React.createElement(LabelSmall, {
29552
- style: styles$m.spaceStart
29564
+ style: styles$p.spaceStart
29553
29565
  }, label)));
29554
29566
  };
29555
- const styles$m = StyleSheet.create({
29567
+ const styles$p = StyleSheet.create({
29556
29568
  label: {
29557
29569
  width: "fit-content"
29558
29570
  },
@@ -29584,6 +29596,30 @@ const defaultBackgroundImage$1 = {
29584
29596
  function numSteps(range, step) {
29585
29597
  return Math.floor((range[1] - range[0]) / step);
29586
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
+ }));
29622
+ }
29587
29623
  class InteractiveGraphSettings extends React.Component {
29588
29624
  static stateFromProps(props) {
29589
29625
  return {
@@ -29836,7 +29872,9 @@ class InteractiveGraphSettings extends React.Component {
29836
29872
  });
29837
29873
  }
29838
29874
  };
29839
- this.state = InteractiveGraphSettings.stateFromProps(props);
29875
+ this.state = _extends({
29876
+ isExpanded: true
29877
+ }, InteractiveGraphSettings.stateFromProps(props));
29840
29878
  }
29841
29879
  componentDidMount() {
29842
29880
  // TODO(scottgrant): This is a hack to remove the deprecated call to
@@ -29855,7 +29893,13 @@ class InteractiveGraphSettings extends React.Component {
29855
29893
  this._isMounted = false;
29856
29894
  }
29857
29895
  render() {
29858
- return /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("div", {
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", {
29859
29903
  className: "graph-settings"
29860
29904
  }, /*#__PURE__*/React.createElement("div", {
29861
29905
  className: "perseus-widget-row"
@@ -29946,10 +29990,10 @@ class InteractiveGraphSettings extends React.Component {
29946
29990
  onChange: this.change
29947
29991
  }))), /*#__PURE__*/React.createElement(LabeledRow, {
29948
29992
  label: "Background image URL:",
29949
- style: styles$l.resetSpaceTop
29993
+ style: styles$o.resetSpaceTop
29950
29994
  }, /*#__PURE__*/React.createElement("input", {
29951
29995
  type: "text",
29952
- className: css(styles$l.backgroundUrlInput),
29996
+ className: css(styles$o.backgroundUrlInput),
29953
29997
  ref: this.bgUrlRef,
29954
29998
  value: this.state.backgroundImage.url || "",
29955
29999
  onChange: e => {
@@ -29963,28 +30007,28 @@ class InteractiveGraphSettings extends React.Component {
29963
30007
  onKeyPress: this.changeBackgroundUrl,
29964
30008
  onBlur: this.changeBackgroundUrl
29965
30009
  }), /*#__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, {
29966
- style: styles$l.rulerSection
30010
+ style: styles$o.rulerSection
29967
30011
  }, /*#__PURE__*/React.createElement(View, {
29968
- style: styles$l.checkboxRow
30012
+ style: styles$o.checkboxRow
29969
30013
  }, /*#__PURE__*/React.createElement(PropCheckBox$7, {
29970
30014
  label: "Show ruler",
29971
30015
  showRuler: this.props.showRuler,
29972
30016
  onChange: this.change,
29973
- style: styles$l.resetSpaceTop
30017
+ style: styles$o.resetSpaceTop
29974
30018
  }), /*#__PURE__*/React.createElement(PropCheckBox$7, {
29975
30019
  label: "Show protractor",
29976
30020
  showProtractor: this.props.showProtractor,
29977
30021
  onChange: this.change,
29978
- style: styles$l.resetSpaceTop
30022
+ style: styles$o.resetSpaceTop
29979
30023
  })), (this.props.showRuler || this.props.showProtractor) && /*#__PURE__*/React.createElement(Banner, {
29980
30024
  layout: "floating",
29981
30025
  text: "The ruler and protractor are not accessible. Please consider an alternate approach.",
29982
30026
  kind: "warning"
29983
30027
  }), this.props.showRuler && /*#__PURE__*/React.createElement(View, {
29984
- style: styles$l.spaceTop
30028
+ style: styles$o.spaceTop
29985
30029
  }, /*#__PURE__*/React.createElement(LabeledRow, {
29986
30030
  label: "Ruler label:",
29987
- style: styles$l.resetSpaceTop
30031
+ style: styles$o.resetSpaceTop
29988
30032
  }, /*#__PURE__*/React.createElement(SingleSelect, {
29989
30033
  id: "ruler-label-select",
29990
30034
  selectedValue: this.props.rulerLabel,
@@ -29994,7 +30038,7 @@ class InteractiveGraphSettings extends React.Component {
29994
30038
  });
29995
30039
  },
29996
30040
  placeholder: "None",
29997
- style: styles$l.singleSelectShort
30041
+ style: styles$o.singleSelectShort
29998
30042
  }, /*#__PURE__*/React.createElement(OptionItem, {
29999
30043
  value: "",
30000
30044
  label: "None",
@@ -30035,12 +30079,12 @@ class InteractiveGraphSettings extends React.Component {
30035
30079
  });
30036
30080
  },
30037
30081
  placeholder: "10",
30038
- style: styles$l.singleSelectShort
30082
+ style: styles$o.singleSelectShort
30039
30083
  }, [1, 2, 4, 8, 10, 16].map(value => /*#__PURE__*/React.createElement(OptionItem, {
30040
30084
  key: value,
30041
30085
  value: `${value}`,
30042
30086
  label: `${value}`
30043
- })))))));
30087
+ }))))))));
30044
30088
  }
30045
30089
  }
30046
30090
  InteractiveGraphSettings.defaultProps = {
@@ -30059,7 +30103,7 @@ InteractiveGraphSettings.defaultProps = {
30059
30103
  rulerLabel: "",
30060
30104
  rulerTicks: 10
30061
30105
  };
30062
- const styles$l = StyleSheet.create({
30106
+ const styles$o = StyleSheet.create({
30063
30107
  resetSpaceTop: {
30064
30108
  marginTop: 0
30065
30109
  },
@@ -30091,43 +30135,6 @@ const styles$l = StyleSheet.create({
30091
30135
  }
30092
30136
  });
30093
30137
 
30094
- /**
30095
- * Dropdown for selecting a locked figure to add to an interactive graph.
30096
- * Locked figures are elements (points, segmeents, etc.) that are not
30097
- * interactive, just present in the graph's background.
30098
- *
30099
- * Used in the interactive graph editor's locked figures section.
30100
- */
30101
- const LockedFigureSelect = props => {
30102
- const {
30103
- id,
30104
- onChange
30105
- } = props;
30106
- return /*#__PURE__*/React.createElement(View, {
30107
- style: styles$k.container
30108
- }, /*#__PURE__*/React.createElement(ActionMenu, {
30109
- menuText: "Add locked figure",
30110
- style: styles$k.addElementSelect
30111
- }, [/*#__PURE__*/React.createElement(ActionItem, {
30112
- key: `${id}-point`,
30113
- label: "Point",
30114
- onClick: () => onChange("point")
30115
- }, "Point"), /*#__PURE__*/React.createElement(ActionItem, {
30116
- key: `${id}-line`,
30117
- label: "Line",
30118
- onClick: () => onChange("line")
30119
- }, "Line")]));
30120
- };
30121
- const styles$k = StyleSheet.create({
30122
- container: {
30123
- marginTop: spacing.xSmall_8
30124
- },
30125
- addElementSelect: {
30126
- backgroundColor: color.fadedBlue8,
30127
- borderRadius: spacing.xxxSmall_4
30128
- }
30129
- });
30130
-
30131
30138
  const mediaDefaultSpecLargeMarginWidth = spacing.large_24;
30132
30139
  const MEDIA_DEFAULT_SPEC = {
30133
30140
  small: {
@@ -30208,6 +30215,43 @@ const strutStyle = size => {
30208
30215
  };
30209
30216
  };
30210
30217
 
30218
+ /**
30219
+ * Dropdown for selecting a locked figure to add to an interactive graph.
30220
+ * Locked figures are elements (points, segmeents, etc.) that are not
30221
+ * interactive, just present in the graph's background.
30222
+ *
30223
+ * Used in the interactive graph editor's locked figures section.
30224
+ */
30225
+ const LockedFigureSelect = props => {
30226
+ const {
30227
+ id,
30228
+ onChange
30229
+ } = props;
30230
+ return /*#__PURE__*/React.createElement(View, {
30231
+ style: styles$n.container
30232
+ }, /*#__PURE__*/React.createElement(ActionMenu, {
30233
+ menuText: "Add locked figure",
30234
+ style: styles$n.addElementSelect
30235
+ }, [/*#__PURE__*/React.createElement(ActionItem, {
30236
+ key: `${id}-point`,
30237
+ label: "Point",
30238
+ onClick: () => onChange("point")
30239
+ }, "Point"), /*#__PURE__*/React.createElement(ActionItem, {
30240
+ key: `${id}-line`,
30241
+ label: "Line",
30242
+ onClick: () => onChange("line")
30243
+ }, "Line")]));
30244
+ };
30245
+ const styles$n = StyleSheet.create({
30246
+ container: {
30247
+ marginTop: spacing.xSmall_8
30248
+ },
30249
+ addElementSelect: {
30250
+ backgroundColor: color.fadedBlue8,
30251
+ borderRadius: spacing.xxxSmall_4
30252
+ }
30253
+ });
30254
+
30211
30255
  const ColorSwatch = props => {
30212
30256
  const {
30213
30257
  color: color$1,
@@ -30217,14 +30261,14 @@ const ColorSwatch = props => {
30217
30261
  return /*#__PURE__*/React.createElement(View, {
30218
30262
  "aria-label":
30219
30263
  // Don't include an aria label for decorative circles.
30220
- !decorative ? `Color: ${color$1}, ${filled ? "filled" : "open"}` : undefined,
30221
- style: [styles$j.colorSwatch, {
30264
+ !decorative ? `${color$1}, ${filled ? "filled" : "open"}` : undefined,
30265
+ style: [styles$m.colorSwatch, {
30222
30266
  border: `4px solid ${lockedFigureColors[color$1]}`,
30223
30267
  backgroundColor: filled ? lockedFigureColors[color$1] : color.white
30224
30268
  }]
30225
30269
  });
30226
30270
  };
30227
- const styles$j = StyleSheet.create({
30271
+ const styles$m = StyleSheet.create({
30228
30272
  colorSwatch: {
30229
30273
  // Add a white outline so that the color swatch is visible when
30230
30274
  // the dropdown option is highlighted with its blue background.
@@ -30238,16 +30282,17 @@ const styles$j = StyleSheet.create({
30238
30282
  const possibleColors = Object.keys(lockedFigureColors);
30239
30283
  const ColorSelect = props => {
30240
30284
  const {
30241
- id,
30242
30285
  selectedValue,
30243
30286
  style,
30244
30287
  onChange
30245
30288
  } = props;
30289
+ const ids = useUniqueIdWithMock();
30290
+ const id = ids.get("color-select");
30246
30291
  return /*#__PURE__*/React.createElement(View, {
30247
- style: [styles$i.row, style]
30292
+ style: [styles$l.row, style]
30248
30293
  }, /*#__PURE__*/React.createElement(LabelMedium, {
30249
30294
  htmlFor: id,
30250
- style: styles$i.label,
30295
+ style: styles$l.label,
30251
30296
  tag: "label"
30252
30297
  }, "color"), /*#__PURE__*/React.createElement(SingleSelect, {
30253
30298
  id: id,
@@ -30266,7 +30311,7 @@ const ColorSelect = props => {
30266
30311
  })
30267
30312
  }, colorName))));
30268
30313
  };
30269
- const styles$i = StyleSheet.create({
30314
+ const styles$l = StyleSheet.create({
30270
30315
  row: {
30271
30316
  flexDirection: "row",
30272
30317
  alignItems: "center"
@@ -30276,31 +30321,115 @@ const styles$i = StyleSheet.create({
30276
30321
  }
30277
30322
  });
30278
30323
 
30279
- const LineSwatch = props => {
30324
+ const CoordinatePairInput = props => {
30280
30325
  const {
30281
- color,
30282
- lineStyle
30326
+ coord,
30327
+ error,
30328
+ onChangeProps
30283
30329
  } = props;
30284
- return /*#__PURE__*/React.createElement(View, {
30285
- style: styles$h.container
30286
- }, /*#__PURE__*/React.createElement(View, {
30287
- "aria-label": `${color}, ${lineStyle}`,
30288
- style: [styles$h.lineSwatch, {
30289
- border: `5px ${lineStyle} ${lockedFigureColors[color]}`
30290
- }]
30291
- }));
30330
+
30331
+ // Keep track of the coordinates via state as the user is editing them,
30332
+ // before they are updated in the props as a valid number.
30333
+ const [coordState, setCoordState] = React.useState([
30334
+ // Using strings to make it easier to work with the text fields.
30335
+ coord[0].toString(), coord[1].toString()]);
30336
+
30337
+ // Generate unique IDs so that the programmatic labels can be associated
30338
+ // with their respective text fields.
30339
+ const ids = useUniqueIdWithMock();
30340
+ const xCoordId = ids.get("x-coord");
30341
+ const yCoordId = ids.get("y-coord");
30342
+ function handleCoordChange(newValue, coordIndex) {
30343
+ // Update the local state (update the input field value).
30344
+ const newCoordState = [...coordState];
30345
+ newCoordState[coordIndex] = newValue;
30346
+ setCoordState(newCoordState);
30347
+
30348
+ // If the new value is not a number, don't update the props.
30349
+ // If it's empty, keep the props the same value instead of setting to 0.
30350
+ if (isNaN(+newValue) || newValue === "") {
30351
+ return;
30352
+ }
30353
+
30354
+ // Update the props (update the graph).
30355
+ const newCoords = [...coord];
30356
+ newCoords[coordIndex] = +newValue;
30357
+ onChangeProps({
30358
+ coord: newCoords
30359
+ });
30360
+ }
30361
+ return /*#__PURE__*/React.createElement(View, null, /*#__PURE__*/React.createElement(View, {
30362
+ style: [styles$k.row, styles$k.spaceUnder]
30363
+ }, /*#__PURE__*/React.createElement(LabelMedium, {
30364
+ htmlFor: xCoordId,
30365
+ style: styles$k.label,
30366
+ tag: "label"
30367
+ }, "x coord"), /*#__PURE__*/React.createElement(TextField, {
30368
+ id: xCoordId,
30369
+ type: "number",
30370
+ value: coordState[0],
30371
+ onChange: newValue => handleCoordChange(newValue, 0),
30372
+ style: [styles$k.textField, error ? styles$k.errorField : undefined]
30373
+ }), /*#__PURE__*/React.createElement(Strut, {
30374
+ size: spacing.medium_16
30375
+ }), /*#__PURE__*/React.createElement(LabelMedium, {
30376
+ htmlFor: yCoordId,
30377
+ style: styles$k.label,
30378
+ tag: "label"
30379
+ }, "y coord"), /*#__PURE__*/React.createElement(TextField, {
30380
+ id: yCoordId,
30381
+ type: "number",
30382
+ value: coordState[1],
30383
+ onChange: newValue => handleCoordChange(newValue, 1),
30384
+ style: [styles$k.textField, error ? styles$k.errorField : undefined]
30385
+ })));
30292
30386
  };
30293
- const styles$h = StyleSheet.create({
30294
- container: {
30295
- backgroundColor: color.white,
30296
- justifyContent: "center",
30297
- padding: spacing.xSmall_8,
30298
- borderRadius: spacing.xxxSmall_4
30387
+ const styles$k = StyleSheet.create({
30388
+ row: {
30389
+ flexDirection: "row",
30390
+ alignItems: "center"
30299
30391
  },
30300
- lineSwatch: {
30301
- // Add a white outline so that the color swatch is visible when
30302
- // the dropdown option is highlighted with its blue background.
30303
- width: 40
30392
+ spaceUnder: {
30393
+ marginBottom: spacing.xSmall_8
30394
+ },
30395
+ label: {
30396
+ marginInlineEnd: spacing.xxSmall_6
30397
+ },
30398
+ textField: {
30399
+ width: spacing.xxxLarge_64
30400
+ },
30401
+ errorField: {
30402
+ borderColor: color.red,
30403
+ backgroundColor: color.fadedRed8
30404
+ }
30405
+ });
30406
+
30407
+ const LabeledSwitch = props => {
30408
+ const {
30409
+ checked,
30410
+ label,
30411
+ style,
30412
+ onChange
30413
+ } = props;
30414
+ const ids = useUniqueIdWithMock();
30415
+ const switchId = ids.get("switch");
30416
+ return /*#__PURE__*/React.createElement(View, {
30417
+ style: [styles$j.row, style]
30418
+ }, /*#__PURE__*/React.createElement(Switch, {
30419
+ id: switchId,
30420
+ checked: checked,
30421
+ onChange: onChange
30422
+ }), /*#__PURE__*/React.createElement(Strut, {
30423
+ size: spacing.xSmall_8
30424
+ }), /*#__PURE__*/React.createElement(LabelMedium, {
30425
+ tag: "label",
30426
+ htmlFor: switchId
30427
+ }, label));
30428
+ };
30429
+ const styles$j = StyleSheet.create({
30430
+ row: {
30431
+ flexDirection: "row",
30432
+ alignItems: "center"
30304
30433
  }
30305
30434
  });
30306
30435
 
@@ -30308,8 +30437,10 @@ const LockedFigureSettingsAccordion = props => {
30308
30437
  const {
30309
30438
  children,
30310
30439
  header,
30440
+ expanded,
30311
30441
  containerStyle,
30312
- panelStyle
30442
+ panelStyle,
30443
+ onToggle
30313
30444
  } = props;
30314
30445
  return /*#__PURE__*/React.createElement(View
30315
30446
  // More specificity so that we can override the default
@@ -30318,14 +30449,16 @@ const LockedFigureSettingsAccordion = props => {
30318
30449
  , {
30319
30450
  className: "locked-figure-accordion"
30320
30451
  }, /*#__PURE__*/React.createElement(AccordionSection, {
30321
- style: [styles$g.container, containerStyle],
30322
- headerStyle: styles$g.accordionHeader,
30452
+ expanded: expanded,
30453
+ onToggle: onToggle,
30454
+ style: [styles$i.container, containerStyle],
30455
+ headerStyle: styles$i.accordionHeader,
30323
30456
  header: header
30324
30457
  }, /*#__PURE__*/React.createElement(View, {
30325
- style: [styles$g.accordionPanel, panelStyle]
30458
+ style: [styles$i.accordionPanel, panelStyle]
30326
30459
  }, children)));
30327
30460
  };
30328
- const styles$g = StyleSheet.create({
30461
+ const styles$i = StyleSheet.create({
30329
30462
  container: {
30330
30463
  backgroundColor: color.fadedBlue8,
30331
30464
  marginTop: spacing.xSmall_8
@@ -30347,170 +30480,56 @@ const styles$g = StyleSheet.create({
30347
30480
  });
30348
30481
 
30349
30482
  /**
30350
- * The part of a locked figure settings panel that contains actions
30351
- * for this panel and its associated locked figure, including
30352
- * the delete button.
30353
- */
30354
- const LockedFigureSettingsActions = props => {
30355
- const {
30356
- onRemove,
30357
- figureAriaLabel
30358
- } = props;
30359
- return /*#__PURE__*/React.createElement(View, {
30360
- style: styles$f.container
30361
- }, /*#__PURE__*/React.createElement(Button, {
30362
- startIcon: trashIcon,
30363
- "aria-label": `Delete ${figureAriaLabel}`,
30364
- onClick: onRemove,
30365
- kind: "tertiary",
30366
- style: styles$f.deleteButton
30367
- }, "Delete"));
30368
- };
30369
- const styles$f = StyleSheet.create({
30370
- container: {
30371
- width: "100%",
30372
- flexDirection: "row",
30373
- alignItems: "center",
30374
- marginTop: spacing.xxxSmall_4
30375
- },
30376
- deleteButton: {
30377
- // Line up the delete icon with the rest of the content.
30378
- marginInlineStart: -spacing.xxxSmall_4
30379
- }
30380
- });
30381
-
30382
- const LabeledSwitch = props => {
30383
- const {
30384
- checked,
30385
- label,
30386
- style,
30387
- onChange
30388
- } = props;
30389
- const ids = useUniqueIdWithMock();
30390
- const switchId = ids.get("switch");
30391
- return /*#__PURE__*/React.createElement(View, {
30392
- style: [styles$e.row, style]
30393
- }, /*#__PURE__*/React.createElement(Switch, {
30394
- id: switchId,
30395
- checked: checked,
30396
- onChange: onChange
30397
- }), /*#__PURE__*/React.createElement(Strut, {
30398
- size: spacing.xSmall_8
30399
- }), /*#__PURE__*/React.createElement(LabelMedium, {
30400
- tag: "label",
30401
- htmlFor: switchId
30402
- }, label));
30403
- };
30404
- const styles$e = StyleSheet.create({
30405
- row: {
30406
- flexDirection: "row",
30407
- alignItems: "center"
30408
- }
30409
- });
30410
-
30411
- /**
30412
- * LockedPointSettings is a component that allows the user to edit the
30413
- * settings of specifically a locked point on the graph.
30483
+ * DefiningPointSettings is a component that allows the user to edit the
30484
+ * settings of a point that defines a locked figure (ex. line) on the graph.
30414
30485
  *
30415
30486
  * Used in the interactive graph editor's locked figures section.
30416
30487
  */
30417
- const LockedPointSettings = props => {
30488
+ const DefiningPointSettings = props => {
30418
30489
  const {
30419
30490
  coord,
30420
30491
  color: pointColor,
30421
30492
  filled = true,
30422
30493
  label,
30423
- toggled = "true",
30424
- style,
30494
+ showPoint = "false",
30495
+ error,
30425
30496
  onChangeProps,
30426
- onToggle,
30427
- onRemove
30497
+ onTogglePoint
30428
30498
  } = props;
30429
-
30430
- // Keep track of the coordinates via state as the user is editing them,
30431
- // before they are updated in the props as a valid number.
30432
- const [coordState, setCoordState] = React.useState([
30433
- // Using strings to make it easier to work with the text fields.
30434
- coord[0].toString(), coord[1].toString()]);
30435
-
30436
- // Generate unique IDs so that the programmatic labels can be associated
30437
- // with their respective text fields.
30438
- const ids = useUniqueIdWithMock();
30439
- const xCoordId = ids.get("x-coord");
30440
- const yCoordId = ids.get("y-coord");
30441
- const colorSelectId = ids.get("point-color-select");
30442
- function handleCoordChange(newValue, coordIndex) {
30443
- // Update the local state (update the input field value).
30444
- const newCoordState = [...coordState];
30445
- newCoordState[coordIndex] = newValue;
30446
- setCoordState(newCoordState);
30447
-
30448
- // If the new value is not a number, don't update the props.
30449
- // If it's empty, keep the props the same value instead of setting to 0.
30450
- if (isNaN(+newValue) || newValue === "") {
30451
- return;
30452
- }
30453
-
30454
- // Update the props (update the graph).
30455
- const newCoords = [...coord];
30456
- newCoords[coordIndex] = +newValue;
30457
- onChangeProps({
30458
- coord: newCoords
30459
- });
30460
- }
30461
30499
  function handleColorChange(newValue) {
30462
30500
  onChangeProps({
30463
30501
  color: newValue
30464
30502
  });
30465
30503
  }
30466
30504
  return /*#__PURE__*/React.createElement(LockedFigureSettingsAccordion, {
30467
- containerStyle: style,
30468
- panelStyle: !onRemove && styles$d.accordionPanelWithoutActions,
30505
+ expanded: props.expanded,
30506
+ onToggle: props.onToggle,
30507
+ containerStyle: styles$h.container,
30508
+ panelStyle: styles$h.accordionPanel,
30469
30509
  header:
30470
30510
  /*#__PURE__*/
30471
30511
  // Summary: Point, coords, color (filled/open)
30472
30512
  React.createElement(View, {
30473
- style: styles$d.row
30513
+ style: styles$h.row
30474
30514
  }, /*#__PURE__*/React.createElement(LabelLarge, null, `${label || "Point"} (${coord[0]}, ${coord[1]})`), /*#__PURE__*/React.createElement(Strut, {
30475
30515
  size: spacing.xSmall_8
30476
- }), toggled && /*#__PURE__*/React.createElement(ColorSwatch, {
30516
+ }), showPoint && /*#__PURE__*/React.createElement(ColorSwatch, {
30477
30517
  color: pointColor,
30478
30518
  filled: filled
30479
30519
  }))
30480
- }, /*#__PURE__*/React.createElement(View, {
30481
- style: [styles$d.row, styles$d.spaceUnder]
30482
- }, /*#__PURE__*/React.createElement(LabelMedium, {
30483
- htmlFor: xCoordId,
30484
- style: styles$d.label,
30485
- tag: "label"
30486
- }, "x coord"), /*#__PURE__*/React.createElement(TextField, {
30487
- id: xCoordId,
30488
- type: "number",
30489
- value: coordState[0],
30490
- onChange: newValue => handleCoordChange(newValue, 0),
30491
- style: styles$d.textField
30492
- }), /*#__PURE__*/React.createElement(Strut, {
30493
- size: spacing.medium_16
30494
- }), /*#__PURE__*/React.createElement(LabelMedium, {
30495
- htmlFor: yCoordId,
30496
- style: styles$d.label,
30497
- tag: "label"
30498
- }, "y coord"), /*#__PURE__*/React.createElement(TextField, {
30499
- id: yCoordId,
30500
- type: "number",
30501
- value: coordState[1],
30502
- onChange: newValue => handleCoordChange(newValue, 1),
30503
- style: styles$d.textField
30504
- })), onToggle && /*#__PURE__*/React.createElement(LabeledSwitch, {
30520
+ }, /*#__PURE__*/React.createElement(CoordinatePairInput, {
30521
+ coord: coord,
30522
+ error: !!error,
30523
+ onChangeProps: onChangeProps
30524
+ }), onTogglePoint && /*#__PURE__*/React.createElement(LabeledSwitch, {
30505
30525
  label: "show point on graph",
30506
- checked: !!toggled,
30507
- style: toggled && styles$d.spaceUnder,
30508
- onChange: onToggle
30509
- }), toggled && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(ColorSelect, {
30510
- id: colorSelectId,
30526
+ checked: !!showPoint,
30527
+ style: showPoint && styles$h.spaceUnder,
30528
+ onChange: onTogglePoint
30529
+ }), showPoint && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(ColorSelect, {
30511
30530
  selectedValue: pointColor,
30512
30531
  onChange: handleColorChange,
30513
- style: styles$d.spaceUnder
30532
+ style: styles$h.spaceUnder
30514
30533
  }), /*#__PURE__*/React.createElement(LabeledSwitch, {
30515
30534
  label: "open point",
30516
30535
  checked: !filled,
@@ -30519,13 +30538,17 @@ const LockedPointSettings = props => {
30519
30538
  filled: !newValue
30520
30539
  });
30521
30540
  }
30522
- })), onRemove && /*#__PURE__*/React.createElement(LockedFigureSettingsActions, {
30523
- onRemove: onRemove,
30524
- figureAriaLabel: `locked point at ${coord[0]}, ${coord[1]}`
30525
- }));
30541
+ })));
30526
30542
  };
30527
- const styles$d = StyleSheet.create({
30528
- accordionPanelWithoutActions: {
30543
+ const styles$h = StyleSheet.create({
30544
+ container: {
30545
+ marginTop: spacing.xSmall_8,
30546
+ marginBottom: 0,
30547
+ marginLeft: -spacing.xxxSmall_4,
30548
+ marginRight: -spacing.xxxSmall_4,
30549
+ backgroundColor: color.white
30550
+ },
30551
+ accordionPanel: {
30529
30552
  // Need more space since we don't have the actions' margins.
30530
30553
  paddingBottom: spacing.medium_16
30531
30554
  },
@@ -30535,15 +30558,69 @@ const styles$d = StyleSheet.create({
30535
30558
  },
30536
30559
  spaceUnder: {
30537
30560
  marginBottom: spacing.xSmall_8
30561
+ }
30562
+ });
30563
+
30564
+ const LineSwatch = props => {
30565
+ const {
30566
+ color,
30567
+ lineStyle
30568
+ } = props;
30569
+ return /*#__PURE__*/React.createElement(View, {
30570
+ style: styles$g.container
30571
+ }, /*#__PURE__*/React.createElement(View, {
30572
+ "aria-label": `${color}, ${lineStyle}`,
30573
+ style: [styles$g.lineSwatch, {
30574
+ border: `5px ${lineStyle} ${lockedFigureColors[color]}`
30575
+ }]
30576
+ }));
30577
+ };
30578
+ const styles$g = StyleSheet.create({
30579
+ container: {
30580
+ backgroundColor: color.white,
30581
+ justifyContent: "center",
30582
+ padding: spacing.xSmall_8,
30583
+ borderRadius: spacing.xxxSmall_4
30538
30584
  },
30539
- label: {
30540
- marginInlineEnd: spacing.xxSmall_6
30585
+ lineSwatch: {
30586
+ width: 40
30587
+ }
30588
+ });
30589
+
30590
+ /**
30591
+ * The part of a locked figure settings panel that contains actions
30592
+ * for this panel and its associated locked figure, including
30593
+ * the delete button.
30594
+ */
30595
+ const LockedFigureSettingsActions = props => {
30596
+ const {
30597
+ onRemove,
30598
+ figureAriaLabel
30599
+ } = props;
30600
+ return /*#__PURE__*/React.createElement(View, {
30601
+ style: styles$f.container
30602
+ }, /*#__PURE__*/React.createElement(Button, {
30603
+ startIcon: trashIcon,
30604
+ "aria-label": `Delete ${figureAriaLabel}`,
30605
+ onClick: onRemove,
30606
+ kind: "tertiary",
30607
+ style: styles$f.deleteButton
30608
+ }, "Delete"));
30609
+ };
30610
+ const styles$f = StyleSheet.create({
30611
+ container: {
30612
+ width: "100%",
30613
+ flexDirection: "row",
30614
+ alignItems: "center",
30615
+ marginTop: spacing.xxxSmall_4
30541
30616
  },
30542
- textField: {
30543
- width: spacing.xxxLarge_64
30617
+ deleteButton: {
30618
+ // Line up the delete icon with the rest of the content.
30619
+ marginInlineStart: -spacing.xxxSmall_4
30544
30620
  }
30545
30621
  });
30546
30622
 
30623
+ const lengthZeroStr = "The line cannot have length 0.";
30547
30624
  const LockedLineSettings = props => {
30548
30625
  const {
30549
30626
  kind,
@@ -30561,11 +30638,11 @@ const LockedLineSettings = props => {
30561
30638
  // with their respective text fields.
30562
30639
  const ids = useUniqueIdWithMock();
30563
30640
  const kindSelectId = ids.get("line-kind-select");
30564
- const colorSelectId = ids.get("line-color-select");
30565
30641
  const styleSelectId = ids.get("line-style-select");
30566
30642
  const capitalizeKind = kind.charAt(0).toUpperCase() + kind.slice(1);
30567
30643
  const lineLabel = `${capitalizeKind} (${point1.coord[0]},
30568
30644
  ${point1.coord[1]}), (${point2.coord[0]}, ${point2.coord[1]})`;
30645
+ const isInvalid = point1.coord[0] === point2.coord[0] && point1.coord[1] === point2.coord[1];
30569
30646
  function handleChangePoint(newPointProps, index) {
30570
30647
  const newPoints = [...points];
30571
30648
  newPoints[index] = _extends({}, points[index], newPointProps);
@@ -30585,8 +30662,10 @@ const LockedLineSettings = props => {
30585
30662
  });
30586
30663
  }
30587
30664
  return /*#__PURE__*/React.createElement(LockedFigureSettingsAccordion, {
30665
+ expanded: props.expanded,
30666
+ onToggle: props.onToggle,
30588
30667
  header: /*#__PURE__*/React.createElement(View, {
30589
- style: styles$c.row
30668
+ style: styles$e.row
30590
30669
  }, /*#__PURE__*/React.createElement(LabelLarge, null, lineLabel), /*#__PURE__*/React.createElement(Strut, {
30591
30670
  size: spacing.xSmall_8
30592
30671
  }), /*#__PURE__*/React.createElement(LineSwatch, {
@@ -30594,10 +30673,10 @@ const LockedLineSettings = props => {
30594
30673
  lineStyle: lineStyle
30595
30674
  }))
30596
30675
  }, /*#__PURE__*/React.createElement(View, {
30597
- style: [styles$c.row, styles$c.spaceUnder]
30676
+ style: [styles$e.row, styles$e.spaceUnder]
30598
30677
  }, /*#__PURE__*/React.createElement(LabelMedium, {
30599
30678
  htmlFor: kindSelectId,
30600
- style: styles$c.label,
30679
+ style: styles$e.label,
30601
30680
  tag: "label"
30602
30681
  }, "kind"), /*#__PURE__*/React.createElement(SingleSelect, {
30603
30682
  id: kindSelectId,
@@ -30611,25 +30690,24 @@ const LockedLineSettings = props => {
30611
30690
  }, /*#__PURE__*/React.createElement(OptionItem, {
30612
30691
  value: "line",
30613
30692
  label: "line"
30614
- }), /*#__PURE__*/React.createElement(OptionItem, {
30615
- value: "segment",
30616
- label: "segment"
30617
30693
  }), /*#__PURE__*/React.createElement(OptionItem, {
30618
30694
  value: "ray",
30619
30695
  label: "ray"
30696
+ }), /*#__PURE__*/React.createElement(OptionItem, {
30697
+ value: "segment",
30698
+ label: "segment"
30620
30699
  }))), /*#__PURE__*/React.createElement(View, {
30621
- style: [styles$c.row, styles$c.spaceUnder]
30700
+ style: [styles$e.row, styles$e.spaceUnder]
30622
30701
  }, /*#__PURE__*/React.createElement(ColorSelect, {
30623
- id: colorSelectId,
30624
30702
  selectedValue: lineColor,
30625
30703
  onChange: handleColorChange
30626
30704
  }), /*#__PURE__*/React.createElement(Strut, {
30627
30705
  size: spacing.small_12
30628
30706
  }), /*#__PURE__*/React.createElement(View, {
30629
- style: styles$c.row
30707
+ style: styles$e.row
30630
30708
  }, /*#__PURE__*/React.createElement(LabelMedium, {
30631
30709
  htmlFor: styleSelectId,
30632
- style: styles$c.label,
30710
+ style: styles$e.label,
30633
30711
  tag: "label"
30634
30712
  }, "style"), /*#__PURE__*/React.createElement(SingleSelect, {
30635
30713
  id: styleSelectId,
@@ -30640,31 +30718,33 @@ const LockedLineSettings = props => {
30640
30718
  // Placeholder is required, but never gets used.
30641
30719
  ,
30642
30720
  placeholder: "",
30643
- style: styles$c.selectMarginOffset
30721
+ style: styles$e.selectMarginOffset
30644
30722
  }, /*#__PURE__*/React.createElement(OptionItem, {
30645
30723
  value: "solid",
30646
30724
  label: "solid"
30647
30725
  }), /*#__PURE__*/React.createElement(OptionItem, {
30648
30726
  value: "dashed",
30649
30727
  label: "dashed"
30650
- })))), /*#__PURE__*/React.createElement(LockedPointSettings, _extends({
30728
+ })))), isInvalid && /*#__PURE__*/React.createElement(LabelMedium, {
30729
+ style: styles$e.errorText
30730
+ }, lengthZeroStr), /*#__PURE__*/React.createElement(DefiningPointSettings, _extends({
30651
30731
  label: "Point 1",
30652
- toggled: showPoint1
30732
+ showPoint: showPoint1,
30733
+ error: isInvalid ? lengthZeroStr : null
30653
30734
  }, point1, {
30654
- onToggle: newValue => onChangeProps({
30735
+ onTogglePoint: newValue => onChangeProps({
30655
30736
  showPoint1: newValue
30656
30737
  }),
30657
- onChangeProps: newProps => handleChangePoint(newProps, 0),
30658
- style: styles$c.lockedPointSettingsContainer
30659
- })), /*#__PURE__*/React.createElement(LockedPointSettings, _extends({
30738
+ onChangeProps: newProps => handleChangePoint(newProps, 0)
30739
+ })), /*#__PURE__*/React.createElement(DefiningPointSettings, _extends({
30660
30740
  label: "Point 2",
30661
- toggled: showPoint2
30741
+ showPoint: showPoint2,
30742
+ error: isInvalid ? lengthZeroStr : null
30662
30743
  }, point2, {
30663
- onToggle: newValue => onChangeProps({
30744
+ onTogglePoint: newValue => onChangeProps({
30664
30745
  showPoint2: newValue
30665
30746
  }),
30666
- onChangeProps: newProps => handleChangePoint(newProps, 1),
30667
- style: styles$c.lockedPointSettingsContainer
30747
+ onChangeProps: newProps => handleChangePoint(newProps, 1)
30668
30748
  })), /*#__PURE__*/React.createElement(LockedFigureSettingsActions, {
30669
30749
  onRemove: onRemove,
30670
30750
  figureAriaLabel: `locked line defined by
@@ -30672,14 +30752,7 @@ const LockedLineSettings = props => {
30672
30752
  ${point2.coord[0]}, ${point2.coord[1]}.`
30673
30753
  }));
30674
30754
  };
30675
- const styles$c = StyleSheet.create({
30676
- lockedPointSettingsContainer: {
30677
- marginTop: spacing.xSmall_8,
30678
- marginBottom: 0,
30679
- marginLeft: -spacing.xxxSmall_4,
30680
- marginRight: -spacing.xxxSmall_4,
30681
- backgroundColor: color.white
30682
- },
30755
+ const styles$e = StyleSheet.create({
30683
30756
  row: {
30684
30757
  flexDirection: "row",
30685
30758
  alignItems: "center"
@@ -30693,6 +30766,72 @@ const styles$c = StyleSheet.create({
30693
30766
  selectMarginOffset: {
30694
30767
  // Align with the point settings accordions.
30695
30768
  marginInlineEnd: -spacing.xxxSmall_4
30769
+ },
30770
+ errorText: {
30771
+ color: color.red
30772
+ }
30773
+ });
30774
+
30775
+ /**
30776
+ * LockedPointSettings is a component that allows the user to edit the
30777
+ * settings of specifically a locked point on the graph.
30778
+ *
30779
+ * Used in the interactive graph editor's locked figures section.
30780
+ */
30781
+ const LockedPointSettings = props => {
30782
+ const {
30783
+ coord,
30784
+ color: pointColor,
30785
+ filled = true,
30786
+ onChangeProps,
30787
+ onRemove
30788
+ } = props;
30789
+ function handleColorChange(newValue) {
30790
+ onChangeProps({
30791
+ color: newValue
30792
+ });
30793
+ }
30794
+ return /*#__PURE__*/React.createElement(LockedFigureSettingsAccordion, {
30795
+ expanded: props.expanded,
30796
+ onToggle: props.onToggle,
30797
+ header:
30798
+ /*#__PURE__*/
30799
+ // Summary: Point, coords, color (filled/open)
30800
+ React.createElement(View, {
30801
+ style: styles$d.row
30802
+ }, /*#__PURE__*/React.createElement(LabelLarge, null, `Point (${coord[0]}, ${coord[1]})`), /*#__PURE__*/React.createElement(Strut, {
30803
+ size: spacing.xSmall_8
30804
+ }), /*#__PURE__*/React.createElement(ColorSwatch, {
30805
+ color: pointColor,
30806
+ filled: filled
30807
+ }))
30808
+ }, /*#__PURE__*/React.createElement(CoordinatePairInput, {
30809
+ coord: coord,
30810
+ onChangeProps: onChangeProps
30811
+ }), /*#__PURE__*/React.createElement(ColorSelect, {
30812
+ selectedValue: pointColor,
30813
+ onChange: handleColorChange,
30814
+ style: styles$d.spaceUnder
30815
+ }), /*#__PURE__*/React.createElement(LabeledSwitch, {
30816
+ label: "open point",
30817
+ checked: !filled,
30818
+ onChange: newValue => {
30819
+ onChangeProps({
30820
+ filled: !newValue
30821
+ });
30822
+ }
30823
+ }), /*#__PURE__*/React.createElement(LockedFigureSettingsActions, {
30824
+ onRemove: onRemove,
30825
+ figureAriaLabel: `locked point at ${coord[0]}, ${coord[1]}`
30826
+ }));
30827
+ };
30828
+ const styles$d = StyleSheet.create({
30829
+ row: {
30830
+ flexDirection: "row",
30831
+ alignItems: "center"
30832
+ },
30833
+ spaceUnder: {
30834
+ marginBottom: spacing.xSmall_8
30696
30835
  }
30697
30836
  });
30698
30837
 
@@ -30783,6 +30922,11 @@ function getDefaultFigureForType(type) {
30783
30922
  }
30784
30923
 
30785
30924
  const LockedFiguresSection = props => {
30925
+ var _props$figures;
30926
+ // Keep track of all figures' accordions' expanded states for the
30927
+ // expand/collapse all button. Set the whole array to false initially.
30928
+ const collapsedStateArray = Array(((_props$figures = props.figures) != null ? _props$figures : []).length).fill(false);
30929
+ const [expandedStates, setExpandedStates] = React.useState(collapsedStateArray);
30786
30930
  const uniqueId = useUniqueIdWithMock().get("locked-figures-section");
30787
30931
  const {
30788
30932
  figures,
@@ -30794,6 +30938,7 @@ const LockedFiguresSection = props => {
30794
30938
  lockedFigures: [...lockedFigures, getDefaultFigureForType(newFigure)]
30795
30939
  };
30796
30940
  onChange(newProps);
30941
+ setExpandedStates([...expandedStates, true]);
30797
30942
  }
30798
30943
  function removeLockedFigure(index) {
30799
30944
  // eslint-disable-next-line no-alert
@@ -30802,6 +30947,11 @@ const LockedFiguresSection = props => {
30802
30947
  onChange({
30803
30948
  lockedFigures: [...lockedFigures.slice(0, index), ...lockedFigures.slice(index + 1)]
30804
30949
  });
30950
+
30951
+ // Update expanded states
30952
+ const newExpandedStates = [...expandedStates];
30953
+ newExpandedStates.splice(index, 1);
30954
+ setExpandedStates(newExpandedStates);
30805
30955
  }
30806
30956
  }
30807
30957
  function changeProps(index,
@@ -30814,16 +30964,46 @@ const LockedFiguresSection = props => {
30814
30964
  };
30815
30965
  onChange(newFigures);
30816
30966
  }
30967
+ function toggleExpanded(newValue) {
30968
+ setExpandedStates(Array(figures == null ? void 0 : figures.length).fill(newValue));
30969
+ }
30970
+ const allCollapsed = expandedStates.every(value => !value);
30971
+ const buttonLabel = allCollapsed ? "Expand all" : "Collapse all";
30972
+ const showExpandButton = !!(figures != null && figures.length);
30817
30973
  return /*#__PURE__*/React.createElement(View, null, figures == null ? void 0 : figures.map((figure, index) => /*#__PURE__*/React.createElement(LockedFigureSettings, _extends({
30974
+ expanded: expandedStates[index],
30975
+ onToggle: newValue => {
30976
+ const newExpanded = [...expandedStates];
30977
+ newExpanded[index] = newValue;
30978
+ setExpandedStates(newExpanded);
30979
+ },
30818
30980
  key: `${uniqueId}-locked-${figure}-${index}`
30819
30981
  }, figure, {
30820
30982
  onChangeProps: newProps => changeProps(index, newProps),
30821
30983
  onRemove: () => removeLockedFigure(index)
30822
- }))), /*#__PURE__*/React.createElement(LockedFigureSelect, {
30984
+ }))), /*#__PURE__*/React.createElement(View, {
30985
+ style: styles$c.buttonContainer
30986
+ }, /*#__PURE__*/React.createElement(LockedFigureSelect, {
30823
30987
  id: `${uniqueId}-select`,
30824
30988
  onChange: addLockedFigure
30825
- }));
30989
+ }), /*#__PURE__*/React.createElement(Strut, {
30990
+ size: spacing.small_12
30991
+ }), showExpandButton && /*#__PURE__*/React.createElement(Button, {
30992
+ kind: "secondary",
30993
+ onClick: () => toggleExpanded(allCollapsed),
30994
+ style: styles$c.button
30995
+ }, buttonLabel)));
30826
30996
  };
30997
+ const styles$c = StyleSheet.create({
30998
+ buttonContainer: {
30999
+ flexDirection: "row",
31000
+ alignItems: "center"
31001
+ },
31002
+ button: {
31003
+ marginTop: spacing.xSmall_8,
31004
+ flexGrow: 1
31005
+ }
31006
+ });
30827
31007
 
30828
31008
  const SegmentCountSelector = ({
30829
31009
  numSegments: _numSegments = 1,