@khanacademy/perseus-editor 14.3.0 → 14.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
@@ -25,6 +25,7 @@ import { useMathInputI18n, createMathField } from '@khanacademy/math-input';
25
25
  import { vector, number } from '@khanacademy/kmath';
26
26
  import { SingleSelect, OptionItem, ActionMenu, ActionItem } from '@khanacademy/wonder-blocks-dropdown';
27
27
  import Banner from '@khanacademy/wonder-blocks-banner';
28
+ import plusCircle from '@phosphor-icons/core/regular/plus-circle.svg';
28
29
  import { AccordionSection } from '@khanacademy/wonder-blocks-accordion';
29
30
  import IconButton from '@khanacademy/wonder-blocks-icon-button';
30
31
  import caretDoubleDownIcon from '@phosphor-icons/core/bold/caret-double-down-bold.svg';
@@ -34,7 +35,7 @@ import caretUpIcon from '@phosphor-icons/core/bold/caret-up-bold.svg';
34
35
  import trashIcon from '@phosphor-icons/core/bold/trash-bold.svg';
35
36
  import copyIcon from '@phosphor-icons/core/assets/regular/copy.svg';
36
37
  import autoPasteIcon from '@phosphor-icons/core/assets/regular/note-pencil.svg';
37
- import plusCircle from '@phosphor-icons/core/regular/plus-circle.svg';
38
+ import { vec } from 'mafs';
38
39
  import arrowFatDown from '@phosphor-icons/core/regular/arrow-fat-down.svg';
39
40
  import arrowFatLeft from '@phosphor-icons/core/regular/arrow-fat-left.svg';
40
41
  import arrowFatRight from '@phosphor-icons/core/regular/arrow-fat-right.svg';
@@ -44,7 +45,7 @@ import arrowCounterClockwise from '@phosphor-icons/core/bold/arrow-counter-clock
44
45
 
45
46
  // This file is processed by a Rollup plugin (replace) to inject the production
46
47
  const libName = "@khanacademy/perseus-editor";
47
- const libVersion = "14.3.0";
48
+ const libVersion = "14.5.0";
48
49
  addLibraryVersionToPerseusDebug(libName, libVersion);
49
50
 
50
51
  function _extends() {
@@ -229,7 +230,7 @@ class SectionControlButton extends React.Component {
229
230
  role: "button",
230
231
  hideDefaultFocusRing: true,
231
232
  "aria-label": title,
232
- style: styles$U.button
233
+ style: styles$T.button
233
234
  }, ({
234
235
  hovered,
235
236
  focused,
@@ -237,7 +238,7 @@ class SectionControlButton extends React.Component {
237
238
  }) => /*#__PURE__*/React.createElement(InlineIcon$b, icon));
238
239
  }
239
240
  }
240
- const styles$U = StyleSheet.create({
241
+ const styles$T = StyleSheet.create({
241
242
  button: {
242
243
  backgroundColor: color.gold,
243
244
  margin: 2,
@@ -20628,11 +20629,11 @@ class Spring extends React.Component {
20628
20629
  } = this.props;
20629
20630
  return React.createElement(View, {
20630
20631
  "aria-hidden": "true",
20631
- style: [styles$T.grow, style]
20632
+ style: [styles$S.grow, style]
20632
20633
  });
20633
20634
  }
20634
20635
  }
20635
- const styles$T = StyleSheet.create({
20636
+ const styles$S = StyleSheet.create({
20636
20637
  grow: {
20637
20638
  flexGrow: 1
20638
20639
  }
@@ -20698,6 +20699,7 @@ const _upgradeWidgetInfo = props => {
20698
20699
  // eslint-disable-next-line react/no-unsafe
20699
20700
  class WidgetEditor extends React.Component {
20700
20701
  constructor(props) {
20702
+ var _props$widgetIsOpen;
20701
20703
  super(props);
20702
20704
  this.widget = void 0;
20703
20705
  this._toggleWidget = e => {
@@ -20751,7 +20753,7 @@ class WidgetEditor extends React.Component {
20751
20753
  };
20752
20754
  };
20753
20755
  this.state = {
20754
- showWidget: false,
20756
+ showWidget: (_props$widgetIsOpen = props.widgetIsOpen) != null ? _props$widgetIsOpen : true,
20755
20757
  widgetInfo: _upgradeWidgetInfo(props)
20756
20758
  };
20757
20759
  this.widget = /*#__PURE__*/React.createRef();
@@ -20762,6 +20764,12 @@ class WidgetEditor extends React.Component {
20762
20764
  this.setState({
20763
20765
  widgetInfo: _upgradeWidgetInfo(nextProps)
20764
20766
  });
20767
+ // user can update internal state while the widget is handled globally
20768
+ if (nextProps.widgetIsOpen != null && nextProps.widgetIsOpen !== this.props.widgetIsOpen) {
20769
+ this.setState({
20770
+ showWidget: nextProps.widgetIsOpen
20771
+ });
20772
+ }
20765
20773
  }
20766
20774
  render() {
20767
20775
  const widgetInfo = this.state.widgetInfo;
@@ -20869,13 +20877,13 @@ class WidgetSelect extends React.Component {
20869
20877
  * that collapse a region.
20870
20878
  */
20871
20879
  function ToggleableCaret(props) {
20872
- const iconStyle = props.isExpanded ? styles$S.expanded : styles$S.collapsed;
20880
+ const iconStyle = props.isExpanded ? styles$R.expanded : styles$R.collapsed;
20873
20881
  return /*#__PURE__*/React.createElement(PhosphorIcon, {
20874
20882
  icon: caretRight,
20875
20883
  style: iconStyle
20876
20884
  });
20877
20885
  }
20878
- const styles$S = StyleSheet.create({
20886
+ const styles$R = StyleSheet.create({
20879
20887
  collapsed: {
20880
20888
  transition: ".15s"
20881
20889
  },
@@ -20908,16 +20916,16 @@ class TexErrorView extends React.Component {
20908
20916
  showErrors
20909
20917
  } = this.state;
20910
20918
  return /*#__PURE__*/React.createElement(View, {
20911
- style: styles$R.errorContainer
20919
+ style: styles$Q.errorContainer
20912
20920
  }, /*#__PURE__*/React.createElement(View, {
20913
- style: styles$R.title,
20921
+ style: styles$Q.title,
20914
20922
  onClick: this.handleToggleTexErrors
20915
20923
  }, /*#__PURE__*/React.createElement(ToggleableCaret, {
20916
20924
  isExpanded: showErrors
20917
20925
  }), "\xA0 TeX Errors (", errorList.length, ")"), showErrors && /*#__PURE__*/React.createElement(View, {
20918
- style: styles$R.errorExplanation
20926
+ style: styles$Q.errorExplanation
20919
20927
  }, "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, {
20920
- style: styles$R.error,
20928
+ style: styles$Q.error,
20921
20929
  key: index
20922
20930
  }, /*#__PURE__*/React.createElement(View, {
20923
20931
  style: {
@@ -20926,7 +20934,7 @@ class TexErrorView extends React.Component {
20926
20934
  }, e.math), /*#__PURE__*/React.createElement(View, null, e.message))));
20927
20935
  }
20928
20936
  }
20929
- const styles$R = StyleSheet.create({
20937
+ const styles$Q = StyleSheet.create({
20930
20938
  title: {
20931
20939
  backgroundColor: "#eee",
20932
20940
  fontSize: "1.25em",
@@ -21574,7 +21582,8 @@ class Editor extends React.Component {
21574
21582
  // eslint-disable-next-line react/jsx-no-bind
21575
21583
  ,
21576
21584
  onRemove: this._handleWidgetEditorRemove.bind(this, id),
21577
- apiOptions: this.props.apiOptions
21585
+ apiOptions: this.props.apiOptions,
21586
+ widgetIsOpen: this.props.widgetIsOpen
21578
21587
  }, this.props.widgets[id]));
21579
21588
  }
21580
21589
  render() {
@@ -21895,8 +21904,7 @@ class IframeContentRenderer extends React.Component {
21895
21904
  this._lastData = data;
21896
21905
 
21897
21906
  // We can't use JSON.stringify/parse for this because the apiOptions
21898
- // includes the functions GroupMetadataEditor, groupAnnotator,
21899
- // onFocusChange, and onInputError.
21907
+ // includes the functions GroupMetadataEditor, groupAnnotator, and onFocusChange.
21900
21908
  // @ts-expect-error - TS2339 - Property 'iframeDataStore' does not exist on type 'Window & typeof globalThis'.
21901
21909
  window.iframeDataStore[this.iframeID] = data;
21902
21910
  frame.contentWindow.postMessage(this.iframeID, "*");
@@ -24039,7 +24047,7 @@ ItemExtrasEditor.defaultProps = {
24039
24047
  zTable: false
24040
24048
  };
24041
24049
  const ItemExtraCheckbox = props => /*#__PURE__*/React.createElement(View, {
24042
- style: [styles$Q.checkbox, props.indent ? styles$Q.indented : undefined]
24050
+ style: [styles$P.checkbox, props.indent ? styles$P.indented : undefined]
24043
24051
  }, /*#__PURE__*/React.createElement(Checkbox$1, {
24044
24052
  label: /*#__PURE__*/React.createElement(View, {
24045
24053
  style: {
@@ -24049,7 +24057,7 @@ const ItemExtraCheckbox = props => /*#__PURE__*/React.createElement(View, {
24049
24057
  checked: props.checked,
24050
24058
  onChange: newCheckedState => props.onChange(newCheckedState)
24051
24059
  }));
24052
- const styles$Q = StyleSheet.create({
24060
+ const styles$P = StyleSheet.create({
24053
24061
  indented: {
24054
24062
  marginInlineStart: spacing.large_24
24055
24063
  }
@@ -24119,7 +24127,8 @@ class ItemEditor extends React.Component {
24119
24127
  imageUploader: this.props.imageUploader,
24120
24128
  onChange: this.handleEditorChange,
24121
24129
  apiOptions: this.props.apiOptions,
24122
- showWordCount: true
24130
+ showWordCount: true,
24131
+ widgetIsOpen: this.props.widgetIsOpen
24123
24132
  }, this.props.question))), /*#__PURE__*/React.createElement("div", {
24124
24133
  className: "perseus-editor-right-cell"
24125
24134
  }, /*#__PURE__*/React.createElement("div", {
@@ -24167,6 +24176,7 @@ const {
24167
24176
  } = components;
24168
24177
  class EditorPage extends React.Component {
24169
24178
  constructor(props) {
24179
+ var _this$props$widgetsAr;
24170
24180
  super(props);
24171
24181
  this._isMounted = void 0;
24172
24182
  this.renderer = void 0;
@@ -24199,7 +24209,8 @@ class EditorPage extends React.Component {
24199
24209
  json: _.pick(this.props, "question", "answerArea", "hints", "itemDataVersion"),
24200
24210
  gradeMessage: "",
24201
24211
  wasAnswered: false,
24202
- highlightLint: true
24212
+ highlightLint: true,
24213
+ widgetsAreOpen: (_this$props$widgetsAr = this.props.widgetsAreOpen) != null ? _this$props$widgetsAr : true
24203
24214
  };
24204
24215
  this._isMounted = false;
24205
24216
  }
@@ -24327,6 +24338,7 @@ class EditorPage extends React.Component {
24327
24338
  wasAnswered: this.state.wasAnswered,
24328
24339
  gradeMessage: this.state.gradeMessage,
24329
24340
  deviceType: this.props.previewDevice,
24341
+ widgetIsOpen: this.state.widgetsAreOpen,
24330
24342
  apiOptions: deviceBasedApiOptions,
24331
24343
  previewURL: this.props.previewURL
24332
24344
  }), (!this.props.developerMode || !this.props.jsonMode) && /*#__PURE__*/React.createElement(CombinedHintsEditor, {
@@ -24707,7 +24719,7 @@ class SimpleButton extends React.Component {
24707
24719
  } = _this$props,
24708
24720
  otherProps = _objectWithoutPropertiesLoose(_this$props, _excluded$3);
24709
24721
  return /*#__PURE__*/React.createElement("div", _extends({
24710
- className: css(styles$P.baseButton, color === "green" && styles$P.green, color === "orange" && styles$P.orange),
24722
+ className: css(styles$O.baseButton, color === "green" && styles$O.green, color === "orange" && styles$O.orange),
24711
24723
  onClick: onClick
24712
24724
  }, otherProps), children);
24713
24725
  }
@@ -24722,7 +24734,7 @@ SimpleButton.defaultProps = {
24722
24734
  color: "green",
24723
24735
  onClick: () => {}
24724
24736
  };
24725
- const styles$P = StyleSheet.create({
24737
+ const styles$O = StyleSheet.create({
24726
24738
  baseButton: {
24727
24739
  top: "0",
24728
24740
  fontSize: "11px",
@@ -24942,23 +24954,23 @@ const LeafContainer = ({
24942
24954
  }) => {
24943
24955
  const hasPreviewHeading = shape.type === "content" || shape.type === "hint";
24944
24956
  const previewHeading = hasPreviewHeading && /*#__PURE__*/React.createElement("div", {
24945
- className: css(styles$O.containerHeader)
24957
+ className: css(styles$N.containerHeader)
24946
24958
  }, /*#__PURE__*/React.createElement(Header, {
24947
24959
  depth: path.length,
24948
- className: css(styles$O.containerTitle)
24960
+ className: css(styles$N.containerTitle)
24949
24961
  }, capitalize(name)));
24950
24962
  return /*#__PURE__*/React.createElement("div", {
24951
- className: css(styles$O.container)
24963
+ className: css(styles$N.container)
24952
24964
  }, /*#__PURE__*/React.createElement("span", {
24953
- className: css(styles$O.row, styles$O.rowHeading)
24965
+ className: css(styles$N.row, styles$N.rowHeading)
24954
24966
  }, /*#__PURE__*/React.createElement("div", {
24955
- className: css(styles$O.columnLeft)
24967
+ className: css(styles$N.columnLeft)
24956
24968
  }, /*#__PURE__*/React.createElement("div", {
24957
- className: "pod-title " + css(styles$O.containerHeader)
24969
+ className: "pod-title " + css(styles$N.containerHeader)
24958
24970
  }, /*#__PURE__*/React.createElement("div", {
24959
- className: css(styles$O.containerTitle)
24971
+ className: css(styles$N.containerTitle)
24960
24972
  }, capitalize(name)), controls)), /*#__PURE__*/React.createElement("div", {
24961
- className: css(styles$O.columnRight)
24973
+ className: css(styles$N.columnRight)
24962
24974
  }, previewHeading)), children);
24963
24975
  };
24964
24976
  const ArrayContainer = props => {
@@ -24971,11 +24983,11 @@ const ArrayContainer = props => {
24971
24983
  actions
24972
24984
  } = props;
24973
24985
  return /*#__PURE__*/React.createElement("div", {
24974
- className: css(styles$O.container)
24986
+ className: css(styles$N.container)
24975
24987
  }, controls && /*#__PURE__*/React.createElement("div", {
24976
- className: css(styles$O.columnLeft, styles$O.containerHeader)
24988
+ className: css(styles$N.columnLeft, styles$N.containerHeader)
24977
24989
  }, controls), /*#__PURE__*/React.createElement("div", null, children), /*#__PURE__*/React.createElement("div", {
24978
- className: css(styles$O.columnLeft)
24990
+ className: css(styles$N.columnLeft)
24979
24991
  }, /*#__PURE__*/React.createElement("a", {
24980
24992
  href: "javascript:void 0",
24981
24993
  onClick: () => actions.addArrayElement(path, shape.elementShape)
@@ -24988,28 +25000,28 @@ const ObjectContainer = ({
24988
25000
  path
24989
25001
  }) => {
24990
25002
  const headingEditor = /*#__PURE__*/React.createElement("div", {
24991
- className: css(styles$O.containerHeader)
25003
+ className: css(styles$N.containerHeader)
24992
25004
  }, /*#__PURE__*/React.createElement(Header, {
24993
25005
  depth: path.length,
24994
- className: css(styles$O.containerTitle)
25006
+ className: css(styles$N.containerTitle)
24995
25007
  }, capitalize(name)), controls);
24996
25008
  const headingPreview = (name || controls) && /*#__PURE__*/React.createElement("div", {
24997
- className: css(styles$O.containerHeader, styles$O.previewCollectionHeader)
25009
+ className: css(styles$N.containerHeader, styles$N.previewCollectionHeader)
24998
25010
  }, /*#__PURE__*/React.createElement(Header, {
24999
25011
  depth: path.length,
25000
- className: css(styles$O.containerTitle)
25012
+ className: css(styles$N.containerTitle)
25001
25013
  }, capitalize(name)));
25002
25014
  const hasBothHeadings = headingEditor && headingPreview;
25003
25015
  return /*#__PURE__*/React.createElement("div", {
25004
- className: css(styles$O.container)
25016
+ className: css(styles$N.container)
25005
25017
  }, hasBothHeadings && /*#__PURE__*/React.createElement("span", {
25006
- className: css(styles$O.row)
25018
+ className: css(styles$N.row)
25007
25019
  }, /*#__PURE__*/React.createElement("div", {
25008
- className: css(styles$O.columnLeft)
25020
+ className: css(styles$N.columnLeft)
25009
25021
  }, headingEditor), /*#__PURE__*/React.createElement("div", {
25010
- className: css(styles$O.columnRight)
25022
+ className: css(styles$N.columnRight)
25011
25023
  }, headingPreview)), /*#__PURE__*/React.createElement("div", {
25012
- className: css(path.length > 0 && styles$O.contentIndent)
25024
+ className: css(path.length > 0 && styles$N.contentIndent)
25013
25025
  }, children));
25014
25026
  };
25015
25027
 
@@ -25132,11 +25144,11 @@ const ItemNodeContent = withStickiness(props => {
25132
25144
  className: "framework-perseus"
25133
25145
  }, lens(renderers).get(path));
25134
25146
  return /*#__PURE__*/React.createElement("span", null, /*#__PURE__*/React.createElement("div", {
25135
- className: css(styles$O.row)
25147
+ className: css(styles$N.row)
25136
25148
  }, /*#__PURE__*/React.createElement("div", {
25137
- className: css(styles$O.columnLeft)
25149
+ className: css(styles$N.columnLeft)
25138
25150
  }, /*#__PURE__*/React.createElement("div", {
25139
- className: css(sticky && styles$O.sticky)
25151
+ className: css(sticky && styles$N.sticky)
25140
25152
  },
25141
25153
  /*#__PURE__*/
25142
25154
  // @ts-expect-error - TS2769 - No overload matches this call.
@@ -25144,9 +25156,9 @@ const ItemNodeContent = withStickiness(props => {
25144
25156
  onChange: newVal => actions.mergeValueAtPath(path, newVal),
25145
25157
  apiOptions: apiOptions
25146
25158
  })))), /*#__PURE__*/React.createElement("div", {
25147
- className: css(styles$O.columnRight)
25159
+ className: css(styles$N.columnRight)
25148
25160
  }, /*#__PURE__*/React.createElement("div", {
25149
- className: css(sticky && styles$O.sticky)
25161
+ className: css(sticky && styles$N.sticky)
25150
25162
  }, preview))));
25151
25163
  });
25152
25164
  const HintNodeContent = withStickiness(props => {
@@ -25162,13 +25174,13 @@ const HintNodeContent = withStickiness(props => {
25162
25174
  className: "framework-perseus"
25163
25175
  }, lens(renderers).get(path));
25164
25176
  return /*#__PURE__*/React.createElement("div", {
25165
- className: css(styles$O.row)
25177
+ className: css(styles$N.row)
25166
25178
  }, /*#__PURE__*/React.createElement("div", {
25167
- className: css(styles$O.columnLeft)
25179
+ className: css(styles$N.columnLeft)
25168
25180
  }, /*#__PURE__*/React.createElement("div", {
25169
- className: css(sticky && styles$O.sticky)
25181
+ className: css(sticky && styles$N.sticky)
25170
25182
  }, /*#__PURE__*/React.createElement(HintEditor, _extends({}, data, {
25171
- className: css(styles$O.hintEditor),
25183
+ className: css(styles$N.hintEditor),
25172
25184
  onChange: newVal => actions.mergeValueAtPath(path, newVal),
25173
25185
  apiOptions: apiOptions,
25174
25186
  showTitle: false,
@@ -25183,9 +25195,9 @@ const HintNodeContent = withStickiness(props => {
25183
25195
  isFirst: true,
25184
25196
  isLast: true
25185
25197
  })))), /*#__PURE__*/React.createElement("div", {
25186
- className: css(styles$O.columnRight)
25198
+ className: css(styles$N.columnRight)
25187
25199
  }, /*#__PURE__*/React.createElement("div", {
25188
- className: css(sticky && styles$O.sticky)
25200
+ className: css(sticky && styles$N.sticky)
25189
25201
  }, preview)));
25190
25202
  });
25191
25203
  const TagsNodeContent = props => {
@@ -25202,9 +25214,9 @@ const TagsNodeContent = props => {
25202
25214
  return null;
25203
25215
  }
25204
25216
  return /*#__PURE__*/React.createElement("div", {
25205
- className: css(styles$O.columnLeft)
25217
+ className: css(styles$N.columnLeft)
25206
25218
  }, /*#__PURE__*/React.createElement("div", {
25207
- className: css(styles$O.tagsEditor)
25219
+ className: css(styles$N.tagsEditor)
25208
25220
  }, /*#__PURE__*/React.createElement(GroupMetadataEditor, {
25209
25221
  value: data,
25210
25222
  onChange: newVal => actions.setValueAtPath(path, newVal),
@@ -25235,23 +25247,23 @@ const ArrayNodeContent = props => {
25235
25247
  const subpath = path.concat(i);
25236
25248
  const controls = [i > 0 && /*#__PURE__*/React.createElement("div", {
25237
25249
  key: "moveArrayElementUp",
25238
- className: css(styles$O.control)
25250
+ className: css(styles$N.control)
25239
25251
  }, /*#__PURE__*/React.createElement(SimpleButton, {
25240
25252
  color: "orange",
25241
25253
  title: "Move up",
25242
25254
  onClick: () => actions.moveArrayElementUp(subpath)
25243
25255
  }, /*#__PURE__*/React.createElement("div", {
25244
- className: css(styles$O.verticalFlip)
25256
+ className: css(styles$N.verticalFlip)
25245
25257
  }, /*#__PURE__*/React.createElement(InlineIcon$6, iconChevronDown)))), i < data.length - 1 && /*#__PURE__*/React.createElement("div", {
25246
25258
  key: "moveArrayElementDown",
25247
- className: css(styles$O.control)
25259
+ className: css(styles$N.control)
25248
25260
  }, /*#__PURE__*/React.createElement(SimpleButton, {
25249
25261
  color: "orange",
25250
25262
  title: "Move down",
25251
25263
  onClick: () => actions.moveArrayElementDown(subpath)
25252
25264
  }, /*#__PURE__*/React.createElement(InlineIcon$6, iconChevronDown))), /*#__PURE__*/React.createElement("div", {
25253
25265
  key: "removeArrayElement",
25254
- className: css(styles$O.control)
25266
+ className: css(styles$N.control)
25255
25267
  }, /*#__PURE__*/React.createElement(SimpleButton, {
25256
25268
  color: "orange",
25257
25269
  title: "Delete",
@@ -25259,7 +25271,7 @@ const ArrayNodeContent = props => {
25259
25271
  }, /*#__PURE__*/React.createElement(InlineIcon$6, iconTrash$4)))];
25260
25272
  return /*#__PURE__*/React.createElement("div", {
25261
25273
  key: i,
25262
- className: css(styles$O.arrayElement, !elementIsLeaf && styles$O.arrayElementAndNotLeaf)
25274
+ className: css(styles$N.arrayElement, !elementIsLeaf && styles$N.arrayElementAndNotLeaf)
25263
25275
  }, /*#__PURE__*/React.createElement(NodeContainer, _extends({}, otherProps, {
25264
25276
  key: i,
25265
25277
  shape: shape.elementShape,
@@ -25285,7 +25297,7 @@ const ObjectNodeContent = props => {
25285
25297
  // made sense to the shape's author is the order in which we'll iterate :)
25286
25298
  const children = Object.keys(shape.shape).map(subkey => /*#__PURE__*/React.createElement("div", {
25287
25299
  key: subkey,
25288
- className: css(styles$O.objectElement)
25300
+ className: css(styles$N.objectElement)
25289
25301
  }, /*#__PURE__*/React.createElement(NodeContainer, _extends({}, otherProps, {
25290
25302
  shape: shape.shape[subkey],
25291
25303
  data: data[subkey],
@@ -25452,7 +25464,7 @@ class MultiRendererEditor extends React.Component {
25452
25464
  }, this._renderContent());
25453
25465
  }
25454
25466
  }
25455
- const styles$O = StyleSheet.create({
25467
+ const styles$N = StyleSheet.create({
25456
25468
  // This is used in a number of places throughout this file.
25457
25469
  container: {},
25458
25470
  // eslint-disable-next-line react-native/no-unused-styles
@@ -26398,7 +26410,7 @@ const SortableItem = createReactClass({
26398
26410
  }
26399
26411
  return /*#__PURE__*/React.createElement("li", {
26400
26412
  draggable: this.props.draggable,
26401
- className: [dragState, css(styles$N.sortableListItem)].join(" "),
26413
+ className: [dragState, css(styles$M.sortableListItem)].join(" "),
26402
26414
  onDragStart: this.handleDragStart,
26403
26415
  onDrop: this.handleDrop,
26404
26416
  onDragEnter: this.handleDragEnter,
@@ -26406,7 +26418,7 @@ const SortableItem = createReactClass({
26406
26418
  }, this.props.component);
26407
26419
  }
26408
26420
  });
26409
- const styles$N = StyleSheet.create({
26421
+ const styles$M = StyleSheet.create({
26410
26422
  sortableListItem: {
26411
26423
  margin: "5px 0"
26412
26424
  }
@@ -26654,13 +26666,13 @@ class ExpressionEditor extends React.Component {
26654
26666
  onChange: this.handleToggleDiv
26655
26667
  }));
26656
26668
  return /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement(HeadingSmall, null, "Global Options"), /*#__PURE__*/React.createElement("div", {
26657
- className: css(styles$M.paddedY)
26669
+ className: css(styles$L.paddedY)
26658
26670
  }, /*#__PURE__*/React.createElement(LabeledTextField, {
26659
26671
  label: "Visible label",
26660
26672
  value: this.props.visibleLabel || "",
26661
26673
  onChange: this.change("visibleLabel")
26662
26674
  }), /*#__PURE__*/React.createElement(InfoTip$i, null, /*#__PURE__*/React.createElement("p", null, "Optional visible text; strongly encouraged to help learners using dictation software, but can be omitted if the surrounding content provides enough context."))), /*#__PURE__*/React.createElement("div", {
26663
- className: css(styles$M.paddedY)
26675
+ className: css(styles$L.paddedY)
26664
26676
  }, /*#__PURE__*/React.createElement(LabeledTextField, {
26665
26677
  label: "Aria label",
26666
26678
  value: this.props.ariaLabel || "",
@@ -26669,13 +26681,13 @@ class ExpressionEditor extends React.Component {
26669
26681
  href: "https://www.w3.org/WAI/tips/designing/#ensure-that-form-elements-include-clearly-associated-labels",
26670
26682
  target: "_blank"
26671
26683
  }, "this article.")))), /*#__PURE__*/React.createElement("div", {
26672
- className: css(styles$M.paddedY)
26684
+ className: css(styles$L.paddedY)
26673
26685
  }, /*#__PURE__*/React.createElement(LabeledTextField, {
26674
26686
  label: "Function variables",
26675
26687
  value: this.state.functionsInternal,
26676
26688
  onChange: this.handleFunctions
26677
26689
  }), /*#__PURE__*/React.createElement(InfoTip$i, null, /*#__PURE__*/React.createElement("p", null, "Single-letter variables listed here will be interpreted as functions. This let us know that f(x) means \"f of x\" and not \"f times x\"."))), /*#__PURE__*/React.createElement("div", {
26678
- className: css(styles$M.paddedY)
26690
+ className: css(styles$L.paddedY)
26679
26691
  }, /*#__PURE__*/React.createElement(Checkbox$1, {
26680
26692
  label: "Use \xD7 instead of \u22C5 for multiplication",
26681
26693
  checked: this.props.times,
@@ -26685,7 +26697,7 @@ class ExpressionEditor extends React.Component {
26685
26697
  });
26686
26698
  }
26687
26699
  }), /*#__PURE__*/React.createElement(InfoTip$i, null, /*#__PURE__*/React.createElement("p", null, "For pre-algebra problems this option displays multiplication as \\times instead of \\cdot in both the rendered output and the acceptable formats examples."))), /*#__PURE__*/React.createElement("div", {
26688
- className: css(styles$M.paddedY)
26700
+ className: css(styles$L.paddedY)
26689
26701
  }, /*#__PURE__*/React.createElement(HeadingXSmall, null, "Button Sets"), buttonSetChoices), /*#__PURE__*/React.createElement(HeadingSmall, null, "Answers"), /*#__PURE__*/React.createElement("p", {
26690
26702
  style: {
26691
26703
  margin: "4px 0"
@@ -26756,18 +26768,18 @@ class AnswerOption extends React.Component {
26756
26768
  color: "destructive",
26757
26769
  light: true
26758
26770
  }, "Delete");
26759
- const answerStatusCss = css(styles$M.answerStatus, this.props.considered === "wrong" && styles$M.answerStatusWrong, this.props.considered === "correct" && styles$M.answerStatusCorrect, this.props.considered === "ungraded" && styles$M.answerStatusUngraded);
26771
+ const answerStatusCss = css(styles$L.answerStatus, this.props.considered === "wrong" && styles$L.answerStatusWrong, this.props.considered === "correct" && styles$L.answerStatusCorrect, this.props.considered === "ungraded" && styles$L.answerStatusUngraded);
26760
26772
  return /*#__PURE__*/React.createElement("div", {
26761
- className: css(styles$M.answerOption)
26773
+ className: css(styles$L.answerOption)
26762
26774
  }, /*#__PURE__*/React.createElement("div", {
26763
- className: css(styles$M.answerHandle)
26775
+ className: css(styles$L.answerHandle)
26764
26776
  }), /*#__PURE__*/React.createElement("div", {
26765
- className: css(styles$M.answerBody)
26777
+ className: css(styles$L.answerBody)
26766
26778
  }, /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("button", {
26767
26779
  onClick: this.toggleConsidered,
26768
26780
  className: answerStatusCss
26769
26781
  }, this.props.considered), /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement(Expression, this.props.expressionProps))), /*#__PURE__*/React.createElement("div", {
26770
- className: css(styles$M.paddedY, styles$M.paddedX)
26782
+ className: css(styles$L.paddedY, styles$L.paddedX)
26771
26783
  }, /*#__PURE__*/React.createElement(Checkbox$1, {
26772
26784
  label: "Answer expression must have the same form.",
26773
26785
  checked: this.props.form,
@@ -26777,7 +26789,7 @@ class AnswerOption extends React.Component {
26777
26789
  });
26778
26790
  }
26779
26791
  }), /*#__PURE__*/React.createElement(InfoTip$i, null, /*#__PURE__*/React.createElement("p", null, "The student's answer must be in the same form. Commutativity and excess negative signs are ignored."))), /*#__PURE__*/React.createElement("div", {
26780
- className: css(styles$M.paddedY, styles$M.paddedX)
26792
+ className: css(styles$L.paddedY, styles$L.paddedX)
26781
26793
  }, /*#__PURE__*/React.createElement(Checkbox$1, {
26782
26794
  label: "Answer expression must be fully expanded and simplified.",
26783
26795
  checked: this.props.simplify,
@@ -26787,11 +26799,11 @@ class AnswerOption extends React.Component {
26787
26799
  });
26788
26800
  }
26789
26801
  }), /*#__PURE__*/React.createElement(InfoTip$i, null, /*#__PURE__*/React.createElement("p", null, "The student's answer must be fully expanded and simplified. Answering this equation (x^2+2x+1) with this factored equation (x+1)^2 will render this response \"Your answer is not fully expanded and simplified.\""))), /*#__PURE__*/React.createElement("div", {
26790
- className: css(styles$M.buttonRow, styles$M.paddedY)
26802
+ className: css(styles$L.buttonRow, styles$L.paddedY)
26791
26803
  }, removeButton)));
26792
26804
  }
26793
26805
  }
26794
- const styles$M = StyleSheet.create({
26806
+ const styles$L = StyleSheet.create({
26795
26807
  paddedX: {
26796
26808
  paddingLeft: spacing.xSmall_8,
26797
26809
  paddingRight: spacing.xSmall_8
@@ -26886,10 +26898,10 @@ class GradedGroupEditor extends React.Component {
26886
26898
  }, /*#__PURE__*/React.createElement("div", {
26887
26899
  className: "perseus-widget-row"
26888
26900
  }, /*#__PURE__*/React.createElement("label", {
26889
- className: css(styles$L.title)
26901
+ className: css(styles$K.title)
26890
26902
  }, "Title:", " ", /*#__PURE__*/React.createElement(TextInput$5, {
26891
26903
  value: this.props.title,
26892
- className: css(styles$L.input)
26904
+ className: css(styles$K.input)
26893
26905
  // @ts-expect-error - TS2554 - Expected 3 arguments, but got 1.
26894
26906
  ,
26895
26907
  onChange: this.change("title")
@@ -26914,7 +26926,7 @@ class GradedGroupEditor extends React.Component {
26914
26926
  }, /*#__PURE__*/React.createElement(InlineIcon$4, iconPlus$1), " Add a hint"), this.props.hint && /*#__PURE__*/React.createElement("div", {
26915
26927
  className: "perseus-hint-editor"
26916
26928
  }, /*#__PURE__*/React.createElement("div", {
26917
- className: css(styles$L.hintsTitle)
26929
+ className: css(styles$K.hintsTitle)
26918
26930
  }, "Hint"), /*#__PURE__*/React.createElement(Editor, {
26919
26931
  ref: this.hintEditor,
26920
26932
  content: this.props.hint ? this.props.hint.content : "",
@@ -26951,7 +26963,7 @@ GradedGroupEditor.defaultProps = {
26951
26963
  images: {},
26952
26964
  hint: null
26953
26965
  };
26954
- const styles$L = StyleSheet.create({
26966
+ const styles$K = StyleSheet.create({
26955
26967
  title: {
26956
26968
  fontSize: 18,
26957
26969
  fontWeight: "bold"
@@ -28410,13 +28422,13 @@ function MathquillInput(props) {
28410
28422
  }
28411
28423
  });
28412
28424
  return /*#__PURE__*/React.createElement(View, {
28413
- style: styles$K.outerWrapper
28425
+ style: styles$J.outerWrapper
28414
28426
  }, /*#__PURE__*/React.createElement("span", {
28415
28427
  ref: mathFieldWrapperRef,
28416
28428
  className: "perseus-math-input mq-editable-field mq-math-mode"
28417
28429
  }));
28418
28430
  }
28419
- const styles$K = StyleSheet.create({
28431
+ const styles$J = StyleSheet.create({
28420
28432
  outerWrapper: {
28421
28433
  display: "inline-block",
28422
28434
  borderStyle: "solid",
@@ -29520,7 +29532,7 @@ const GraphPointsCountSelector = ({
29520
29532
  // Never uses placeholder, always has value
29521
29533
  ,
29522
29534
  placeholder: "",
29523
- style: styles$J.singleSelectShort
29535
+ style: styles$I.singleSelectShort
29524
29536
  }, [...[...Array(7).keys()].map(n => /*#__PURE__*/React.createElement(OptionItem, {
29525
29537
  key: n,
29526
29538
  value: `${n}`,
@@ -29531,7 +29543,7 @@ const GraphPointsCountSelector = ({
29531
29543
  label: "unlimited"
29532
29544
  })]);
29533
29545
  };
29534
- const styles$J = StyleSheet.create({
29546
+ const styles$I = StyleSheet.create({
29535
29547
  singleSelectShort: {
29536
29548
  // Non-standard spacing, but it's the smallest we can go
29537
29549
  // without running into styling issues with the dropdown.
@@ -29544,7 +29556,7 @@ const GraphTypeSelector = props => {
29544
29556
  selectedValue: props.graphType,
29545
29557
  onChange: props.onChange,
29546
29558
  placeholder: "Select an answer type",
29547
- style: styles$I.singleSelectShort
29559
+ style: styles$H.singleSelectShort
29548
29560
  }, props.showNoneOption && /*#__PURE__*/React.createElement(OptionItem, {
29549
29561
  value: "none",
29550
29562
  label: "None"
@@ -29580,7 +29592,7 @@ const GraphTypeSelector = props => {
29580
29592
  label: "Angle"
29581
29593
  }));
29582
29594
  };
29583
- const styles$I = StyleSheet.create({
29595
+ const styles$H = StyleSheet.create({
29584
29596
  singleSelectShort: {
29585
29597
  // Non-standard spacing, but it's the smallest we can go
29586
29598
  // without running into styling issues with the dropdown.
@@ -29595,11 +29607,11 @@ function Heading({
29595
29607
  onToggle
29596
29608
  }) {
29597
29609
  return /*#__PURE__*/React.createElement(Clickable, {
29598
- style: [styles$H.container, !isCollapsible && styles$H.notClickable],
29610
+ style: [styles$G.container, !isCollapsible && styles$G.notClickable],
29599
29611
  disabled: !isCollapsible,
29600
29612
  onClick: () => isCollapsible && (onToggle == null ? void 0 : onToggle(!isOpen))
29601
29613
  }, () => /*#__PURE__*/React.createElement(View, {
29602
- style: styles$H.heading
29614
+ style: styles$G.heading
29603
29615
  }, /*#__PURE__*/React.createElement(LabelLarge, {
29604
29616
  style: {
29605
29617
  fontSize: 14,
@@ -29609,7 +29621,7 @@ function Heading({
29609
29621
  isExpanded: isOpen
29610
29622
  })));
29611
29623
  }
29612
- const styles$H = StyleSheet.create({
29624
+ const styles$G = StyleSheet.create({
29613
29625
  container: {
29614
29626
  marginTop: spacing.small_12,
29615
29627
  // NOTE(jeremy): This is the inverse of the @editorPadding CSS
@@ -29669,15 +29681,18 @@ function InteractiveGraphDescription(props) {
29669
29681
  isOpen: isOpen,
29670
29682
  onToggle: setIsOpen
29671
29683
  }), isOpen && /*#__PURE__*/React.createElement(View, null, /*#__PURE__*/React.createElement(LabelXSmall, {
29672
- style: styles$G.caption
29684
+ style: styles$F.caption
29673
29685
  }, "Use these fields to describe the graph as a whole. These are used by screen readers to describe content to users who are visually impaired."), /*#__PURE__*/React.createElement(LabelLarge, {
29674
29686
  tag: "label"
29675
29687
  }, "Title", /*#__PURE__*/React.createElement(TextField, {
29676
29688
  value: ariaLabelValue,
29677
29689
  onChange: newValue => onChange({
29678
- fullGraphAriaLabel: newValue
29690
+ fullGraphAriaLabel:
29691
+ // Save as undefined if the new value
29692
+ // is an empty string.
29693
+ newValue || undefined
29679
29694
  }),
29680
- style: styles$G.spaceAbove
29695
+ style: styles$F.spaceAbove
29681
29696
  })), /*#__PURE__*/React.createElement(Strut, {
29682
29697
  size: spacing.small_12
29683
29698
  }), /*#__PURE__*/React.createElement(LabelLarge, {
@@ -29687,12 +29702,15 @@ function InteractiveGraphDescription(props) {
29687
29702
  resizeType: "vertical",
29688
29703
  value: ariaDescriptionValue,
29689
29704
  onChange: newValue => onChange({
29690
- fullGraphAriaDescription: newValue
29705
+ fullGraphAriaDescription:
29706
+ // Save as undefined if the new value
29707
+ // is an empty string.
29708
+ newValue || undefined
29691
29709
  }),
29692
- style: styles$G.spaceAbove
29710
+ style: styles$F.spaceAbove
29693
29711
  }))));
29694
29712
  }
29695
- const styles$G = StyleSheet.create({
29713
+ const styles$F = StyleSheet.create({
29696
29714
  caption: {
29697
29715
  color: color.offBlack64,
29698
29716
  paddingTop: spacing.xxSmall_6,
@@ -29712,16 +29730,16 @@ const LabeledRow = props => {
29712
29730
  style
29713
29731
  } = props;
29714
29732
  return /*#__PURE__*/React.createElement("label", {
29715
- className: css(styles$F.label)
29733
+ className: css(styles$E.label)
29716
29734
  }, /*#__PURE__*/React.createElement(View, {
29717
- style: [styles$F.row, style]
29735
+ style: [styles$E.row, style]
29718
29736
  }, labelSide === "start" || /*#__PURE__*/React.createElement(LabelSmall, {
29719
- style: styles$F.spaceEnd
29737
+ style: styles$E.spaceEnd
29720
29738
  }, label), children, labelSide === "end" && /*#__PURE__*/React.createElement(LabelSmall, {
29721
- style: styles$F.spaceStart
29739
+ style: styles$E.spaceStart
29722
29740
  }, label)));
29723
29741
  };
29724
- const styles$F = StyleSheet.create({
29742
+ const styles$E = StyleSheet.create({
29725
29743
  label: {
29726
29744
  width: "fit-content"
29727
29745
  },
@@ -30115,10 +30133,10 @@ class InteractiveGraphSettings extends React.Component {
30115
30133
  }
30116
30134
  }))), /*#__PURE__*/React.createElement(LabeledRow, {
30117
30135
  label: "Background image URL:",
30118
- style: styles$E.resetSpaceTop
30136
+ style: styles$D.resetSpaceTop
30119
30137
  }, /*#__PURE__*/React.createElement("input", {
30120
30138
  type: "text",
30121
- className: css(styles$E.backgroundUrlInput),
30139
+ className: css(styles$D.backgroundUrlInput),
30122
30140
  ref: this.bgUrlRef,
30123
30141
  value: this.state.backgroundImage.url || "",
30124
30142
  onChange: e => {
@@ -30132,9 +30150,9 @@ class InteractiveGraphSettings extends React.Component {
30132
30150
  onKeyPress: this.changeBackgroundUrl,
30133
30151
  onBlur: this.changeBackgroundUrl
30134
30152
  }), /*#__PURE__*/React.createElement(InfoTip$d, null, /*#__PURE__*/React.createElement("p", null, "Create an image in graphie, or use the \"Add image\" function to create a background."))), /*#__PURE__*/React.createElement(View, {
30135
- style: styles$E.protractorSection
30153
+ style: styles$D.protractorSection
30136
30154
  }, /*#__PURE__*/React.createElement(View, {
30137
- style: styles$E.checkboxRow
30155
+ style: styles$D.checkboxRow
30138
30156
  }, /*#__PURE__*/React.createElement(Checkbox$1, {
30139
30157
  label: "Show protractor",
30140
30158
  checked: this.props.showProtractor,
@@ -30143,7 +30161,7 @@ class InteractiveGraphSettings extends React.Component {
30143
30161
  showProtractor: value
30144
30162
  });
30145
30163
  },
30146
- style: styles$E.resetSpaceTop
30164
+ style: styles$D.resetSpaceTop
30147
30165
  })), this.props.showProtractor && /*#__PURE__*/React.createElement(Banner, {
30148
30166
  layout: "floating",
30149
30167
  text: "The protractor is not accessible. Please consider an alternate approach.",
@@ -30164,7 +30182,7 @@ InteractiveGraphSettings.defaultProps = {
30164
30182
  showProtractor: false,
30165
30183
  showTooltips: false
30166
30184
  };
30167
- const styles$E = StyleSheet.create({
30185
+ const styles$D = StyleSheet.create({
30168
30186
  resetSpaceTop: {
30169
30187
  marginTop: 0
30170
30188
  },
@@ -30201,13 +30219,13 @@ const SegmentCountSelector = ({
30201
30219
  const num = +newValue;
30202
30220
  onChange(num);
30203
30221
  },
30204
- style: styles$D.singleSelectShort
30222
+ style: styles$C.singleSelectShort
30205
30223
  }, _.range(1, 7).map(n => /*#__PURE__*/React.createElement(OptionItem, {
30206
30224
  key: n,
30207
30225
  value: `${n}`,
30208
30226
  label: `${n} segment${n > 1 ? "s" : ""}`
30209
30227
  })));
30210
- const styles$D = StyleSheet.create({
30228
+ const styles$C = StyleSheet.create({
30211
30229
  singleSelectShort: {
30212
30230
  // Non-standard spacing, but it's the smallest we can go
30213
30231
  // without running into styling issues with the dropdown.
@@ -30232,17 +30250,17 @@ const LockedFigureSelect = props => {
30232
30250
  figureTypes.push("label");
30233
30251
  }
30234
30252
  return /*#__PURE__*/React.createElement(View, {
30235
- style: styles$C.container
30253
+ style: styles$B.container
30236
30254
  }, /*#__PURE__*/React.createElement(ActionMenu, {
30237
30255
  menuText: "Add locked figure",
30238
- style: styles$C.addElementSelect
30256
+ style: styles$B.addElementSelect
30239
30257
  }, figureTypes.map(figureType => /*#__PURE__*/React.createElement(ActionItem, {
30240
30258
  key: `${id}-${figureType}`,
30241
30259
  label: figureType,
30242
30260
  onClick: () => onChange(figureType)
30243
30261
  }))));
30244
30262
  };
30245
- const styles$C = StyleSheet.create({
30263
+ const styles$B = StyleSheet.create({
30246
30264
  container: {
30247
30265
  marginTop: spacing.xSmall_8
30248
30266
  },
@@ -30359,18 +30377,18 @@ const AngleInput = props => {
30359
30377
  }
30360
30378
  return /*#__PURE__*/React.createElement(LabelMedium, {
30361
30379
  tag: "label",
30362
- style: styles$B.row
30380
+ style: styles$A.row
30363
30381
  }, "angle (degrees)", /*#__PURE__*/React.createElement(Strut, {
30364
30382
  size: spacing.xxSmall_6
30365
30383
  }), /*#__PURE__*/React.createElement(ScrolllessNumberTextField, {
30366
30384
  value: angleInput,
30367
30385
  onChange: handleAngleChange,
30368
- style: styles$B.textField
30386
+ style: styles$A.textField
30369
30387
  }), /*#__PURE__*/React.createElement(Strut, {
30370
30388
  size: spacing.xxSmall_6
30371
30389
  }));
30372
30390
  };
30373
- const styles$B = StyleSheet.create({
30391
+ const styles$A = StyleSheet.create({
30374
30392
  row: {
30375
30393
  display: "flex",
30376
30394
  flexDirection: "row",
@@ -30419,30 +30437,30 @@ const CoordinatePairInput = props => {
30419
30437
  onChange(newCoords);
30420
30438
  }
30421
30439
  return /*#__PURE__*/React.createElement(View, {
30422
- style: [styles$A.row, style]
30440
+ style: [styles$z.row, style]
30423
30441
  }, /*#__PURE__*/React.createElement(LabelMedium, {
30424
30442
  tag: "label",
30425
- style: styles$A.row
30443
+ style: styles$z.row
30426
30444
  }, labels ? labels[0] : "x coord", /*#__PURE__*/React.createElement(Strut, {
30427
30445
  size: spacing.xxSmall_6
30428
30446
  }), /*#__PURE__*/React.createElement(ScrolllessNumberTextField, {
30429
30447
  value: coordState[0],
30430
30448
  onChange: newValue => handleCoordChange(newValue, 0),
30431
- style: [styles$A.textField, error ? styles$A.errorField : undefined]
30449
+ style: [styles$z.textField, error ? styles$z.errorField : undefined]
30432
30450
  })), /*#__PURE__*/React.createElement(Strut, {
30433
30451
  size: spacing.medium_16
30434
30452
  }), /*#__PURE__*/React.createElement(LabelMedium, {
30435
30453
  tag: "label",
30436
- style: styles$A.row
30454
+ style: styles$z.row
30437
30455
  }, labels ? labels[1] : "y coord", /*#__PURE__*/React.createElement(Strut, {
30438
30456
  size: spacing.xxSmall_6
30439
30457
  }), /*#__PURE__*/React.createElement(ScrolllessNumberTextField, {
30440
30458
  value: coordState[1],
30441
30459
  onChange: newValue => handleCoordChange(newValue, 1),
30442
- style: [styles$A.textField, error ? styles$A.errorField : undefined]
30460
+ style: [styles$z.textField, error ? styles$z.errorField : undefined]
30443
30461
  })));
30444
30462
  };
30445
- const styles$A = StyleSheet.create({
30463
+ const styles$z = StyleSheet.create({
30446
30464
  row: {
30447
30465
  display: "flex",
30448
30466
  flexDirection: "row",
@@ -30475,14 +30493,14 @@ const PerseusEditorAccordion = props => {
30475
30493
  }, /*#__PURE__*/React.createElement(AccordionSection, {
30476
30494
  expanded: expanded,
30477
30495
  onToggle: onToggle,
30478
- style: [styles$z.container, containerStyle],
30479
- headerStyle: styles$z.accordionHeader,
30496
+ style: [styles$y.container, containerStyle],
30497
+ headerStyle: styles$y.accordionHeader,
30480
30498
  header: header
30481
30499
  }, /*#__PURE__*/React.createElement(View, {
30482
- style: [styles$z.accordionPanel, panelStyle]
30500
+ style: [styles$y.accordionPanel, panelStyle]
30483
30501
  }, children)));
30484
30502
  };
30485
- const styles$z = StyleSheet.create({
30503
+ const styles$y = StyleSheet.create({
30486
30504
  container: {
30487
30505
  backgroundColor: color.fadedBlue8,
30488
30506
  marginTop: spacing.xSmall_8
@@ -30513,13 +30531,13 @@ const ColorSwatch = props => {
30513
30531
  "aria-label":
30514
30532
  // Don't include an aria label for decorative circles.
30515
30533
  !decorative ? `${color$1}, ${filled ? "filled" : "open"}` : undefined,
30516
- style: [styles$y.colorSwatch, {
30534
+ style: [styles$x.colorSwatch, {
30517
30535
  border: `4px solid ${lockedFigureColors[color$1]}`,
30518
30536
  backgroundColor: filled ? lockedFigureColors[color$1] : color.white
30519
30537
  }]
30520
30538
  });
30521
30539
  };
30522
- const styles$y = StyleSheet.create({
30540
+ const styles$x = StyleSheet.create({
30523
30541
  colorSwatch: {
30524
30542
  // Add a white outline so that the color swatch is visible when
30525
30543
  // the dropdown option is highlighted with its blue background.
@@ -30538,10 +30556,10 @@ const ColorSelect = props => {
30538
30556
  onChange
30539
30557
  } = props;
30540
30558
  return /*#__PURE__*/React.createElement(View, {
30541
- style: [styles$x.row, style]
30559
+ style: [styles$w.row, style]
30542
30560
  }, /*#__PURE__*/React.createElement(LabelMedium, {
30543
30561
  tag: "label",
30544
- style: styles$x.row
30562
+ style: styles$w.row
30545
30563
  }, "color", /*#__PURE__*/React.createElement(Strut, {
30546
30564
  size: spacing.xxSmall_6
30547
30565
  }), /*#__PURE__*/React.createElement(SingleSelect, {
@@ -30560,7 +30578,7 @@ const ColorSelect = props => {
30560
30578
  })
30561
30579
  })))));
30562
30580
  };
30563
- const styles$x = StyleSheet.create({
30581
+ const styles$w = StyleSheet.create({
30564
30582
  row: {
30565
30583
  display: "flex",
30566
30584
  flexDirection: "row",
@@ -30579,17 +30597,17 @@ const EllipseSwatch = props => {
30579
30597
  } = props;
30580
30598
  return /*#__PURE__*/React.createElement(View, {
30581
30599
  "aria-label": `${color}, stroke ${strokeStyle}, fill ${fillStyle}`,
30582
- style: [styles$w.container, {
30600
+ style: [styles$v.container, {
30583
30601
  border: `4px ${strokeStyle} ${lockedFigureColors[color]}`
30584
30602
  }]
30585
30603
  }, /*#__PURE__*/React.createElement(View, {
30586
- style: [styles$w.innerCircle, {
30604
+ style: [styles$v.innerCircle, {
30587
30605
  backgroundColor: lockedFigureColors[color],
30588
30606
  opacity: fillStyle === "white" ? 0 : lockedFigureFillStyles[fillStyle]
30589
30607
  }]
30590
30608
  }));
30591
30609
  };
30592
- const styles$w = StyleSheet.create({
30610
+ const styles$v = StyleSheet.create({
30593
30611
  container: {
30594
30612
  // Add a white outline so that the color swatch is visible when
30595
30613
  // the dropdown option is highlighted with its blue background.
@@ -30615,7 +30633,7 @@ const LineStrokeSelect = props => {
30615
30633
  } = props;
30616
30634
  return /*#__PURE__*/React.createElement(LabelMedium, {
30617
30635
  tag: "label",
30618
- style: styles$v.lineStrokeSelect
30636
+ style: styles$u.lineStrokeSelect
30619
30637
  }, "stroke", /*#__PURE__*/React.createElement(Strut, {
30620
30638
  size: spacing.xxxSmall_4
30621
30639
  }), /*#__PURE__*/React.createElement(SingleSelect, {
@@ -30632,7 +30650,7 @@ const LineStrokeSelect = props => {
30632
30650
  label: "dashed"
30633
30651
  })));
30634
30652
  };
30635
- const styles$v = StyleSheet.create({
30653
+ const styles$u = StyleSheet.create({
30636
30654
  lineStrokeSelect: {
30637
30655
  display: "flex",
30638
30656
  flexDirection: "row",
@@ -30654,40 +30672,40 @@ const LockedFigureSettingsActions = props => {
30654
30672
  onRemove
30655
30673
  } = props;
30656
30674
  return /*#__PURE__*/React.createElement(View, {
30657
- style: styles$u.container
30675
+ style: styles$t.container
30658
30676
  }, /*#__PURE__*/React.createElement(Button, {
30659
30677
  startIcon: trashIcon,
30660
30678
  "aria-label": `Delete locked ${figureType}`,
30661
30679
  onClick: onRemove,
30662
30680
  kind: "tertiary",
30663
- style: styles$u.deleteButton
30681
+ style: styles$t.deleteButton
30664
30682
  }, "Delete"), onMove && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Spring, null), /*#__PURE__*/React.createElement(IconButton, {
30665
30683
  icon: caretDoubleUpIcon,
30666
30684
  size: "small",
30667
30685
  "aria-label": `Move locked ${figureType} to the back`,
30668
30686
  onClick: () => onMove("back"),
30669
- style: styles$u.iconButton
30687
+ style: styles$t.iconButton
30670
30688
  }), /*#__PURE__*/React.createElement(IconButton, {
30671
30689
  icon: caretUpIcon,
30672
30690
  size: "small",
30673
30691
  "aria-label": `Move locked ${figureType} backward`,
30674
30692
  onClick: () => onMove("backward"),
30675
- style: styles$u.iconButton
30693
+ style: styles$t.iconButton
30676
30694
  }), /*#__PURE__*/React.createElement(IconButton, {
30677
30695
  icon: caretDownIcon,
30678
30696
  size: "small",
30679
30697
  "aria-label": `Move locked ${figureType} forward`,
30680
30698
  onClick: () => onMove("forward"),
30681
- style: styles$u.iconButton
30699
+ style: styles$t.iconButton
30682
30700
  }), /*#__PURE__*/React.createElement(IconButton, {
30683
30701
  icon: caretDoubleDownIcon,
30684
30702
  size: "small",
30685
30703
  "aria-label": `Move locked ${figureType} to the front`,
30686
30704
  onClick: () => onMove("front"),
30687
- style: styles$u.iconButton
30705
+ style: styles$t.iconButton
30688
30706
  })));
30689
30707
  };
30690
- const styles$u = StyleSheet.create({
30708
+ const styles$t = StyleSheet.create({
30691
30709
  container: {
30692
30710
  width: "100%",
30693
30711
  flexDirection: "row",
@@ -30705,907 +30723,839 @@ const styles$u = StyleSheet.create({
30705
30723
  }
30706
30724
  });
30707
30725
 
30708
- const {
30709
- InfoTip: InfoTip$c
30710
- } = components;
30711
- const LockedEllipseSettings = props => {
30726
+ /**
30727
+ * LockedLabelSettings is a component that allows the user to edit the
30728
+ * settings of specifically a locked label on the graph within the
30729
+ * Interactive Graph widget.
30730
+ *
30731
+ * Used in the interactive graph editor's locked figures section.
30732
+ */
30733
+ function LockedLabelSettings(props) {
30712
30734
  const {
30713
- center,
30714
- radius,
30715
- angle,
30716
- color,
30717
- fillStyle,
30718
- strokeStyle,
30735
+ type,
30736
+ coord,
30737
+ color: color$1,
30738
+ size,
30739
+ text,
30719
30740
  expanded,
30720
- onToggle,
30721
30741
  onChangeProps,
30722
30742
  onMove,
30723
- onRemove
30743
+ onRemove,
30744
+ onToggle,
30745
+ containerStyle
30724
30746
  } = props;
30725
- function handleColorChange(newValue) {
30726
- onChangeProps({
30727
- color: newValue
30728
- });
30729
- }
30730
30747
  return /*#__PURE__*/React.createElement(PerseusEditorAccordion, {
30731
30748
  expanded: expanded,
30732
30749
  onToggle: onToggle,
30733
- header:
30734
- /*#__PURE__*/
30735
- // Summary: Ellipse, center, radius, color (opacity, dashed)
30736
- React.createElement(View, {
30737
- style: styles$t.row
30738
- }, /*#__PURE__*/React.createElement(LabelLarge, null, `Ellipse (${center[0]}, ${center[1]}), radius ${radius[0]}, ${radius[1]}`), /*#__PURE__*/React.createElement(Strut, {
30750
+ header: /*#__PURE__*/React.createElement(View, {
30751
+ style: [styles$s.row, styles$s.accordionHeaderContainer]
30752
+ }, /*#__PURE__*/React.createElement(LabelLarge, null, "Label (", coord[0], ", ", coord[1], ")"), /*#__PURE__*/React.createElement(Strut, {
30739
30753
  size: spacing.xSmall_8
30740
- }), /*#__PURE__*/React.createElement(EllipseSwatch, {
30741
- color: props.color,
30742
- fillStyle: fillStyle,
30743
- strokeStyle: strokeStyle
30744
- }))
30745
- }, /*#__PURE__*/React.createElement(View, {
30746
- style: styles$t.row
30754
+ }), text !== "" && /*#__PURE__*/React.createElement(LabelLarge, {
30755
+ style: [{
30756
+ backgroundColor: color.white,
30757
+ color: lockedFigureColors[color$1]
30758
+ }, styles$s.accordionHeader]
30759
+ }, text)),
30760
+ containerStyle: containerStyle
30747
30761
  }, /*#__PURE__*/React.createElement(CoordinatePairInput, {
30748
- coord: center,
30749
- style: styles$t.spaceUnder,
30750
- onChange: newCoords => onChangeProps({
30751
- center: newCoords
30752
- })
30753
- }), /*#__PURE__*/React.createElement(View, {
30754
- style: styles$t.spaceUnder
30755
- }, /*#__PURE__*/React.createElement(InfoTip$c, null, "The coordinates for the center of the ellipse."))), /*#__PURE__*/React.createElement(CoordinatePairInput, {
30756
- coord: radius,
30757
- labels: ["x radius", "y radius"],
30758
- style: styles$t.spaceUnder,
30759
- onChange: newCoords => onChangeProps({
30760
- radius: newCoords
30761
- })
30762
- }), /*#__PURE__*/React.createElement(AngleInput, {
30763
- angle: angle,
30764
- onChange: newAngle => onChangeProps({
30765
- angle: newAngle
30766
- })
30767
- }), /*#__PURE__*/React.createElement(Strut, {
30762
+ coord: coord,
30763
+ onChange: newCoords => {
30764
+ onChangeProps({
30765
+ coord: newCoords
30766
+ });
30767
+ },
30768
+ style: styles$s.spaceUnder
30769
+ }), /*#__PURE__*/React.createElement(LabelMedium, {
30770
+ tag: "label",
30771
+ style: [styles$s.row, styles$s.spaceUnder]
30772
+ }, "TeX", /*#__PURE__*/React.createElement(Strut, {
30768
30773
  size: spacing.xSmall_8
30769
- }), /*#__PURE__*/React.createElement(View, {
30770
- style: [styles$t.row, styles$t.spaceUnder]
30771
- }, /*#__PURE__*/React.createElement(ColorSelect, {
30772
- selectedValue: color,
30773
- onChange: handleColorChange
30774
+ }), /*#__PURE__*/React.createElement(TextField, {
30775
+ value: text,
30776
+ placeholder: "ex. x^2 or \\frac{1}{2}",
30777
+ onChange: newValue => onChangeProps({
30778
+ text: newValue
30779
+ })
30780
+ })), /*#__PURE__*/React.createElement(View, {
30781
+ style: styles$s.row
30782
+ }, onMove && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(ColorSelect, {
30783
+ selectedValue: color$1,
30784
+ onChange: newColor => {
30785
+ onChangeProps({
30786
+ color: newColor
30787
+ });
30788
+ },
30789
+ style: styles$s.spaceUnder
30774
30790
  }), /*#__PURE__*/React.createElement(Strut, {
30775
30791
  size: spacing.medium_16
30776
- }), /*#__PURE__*/React.createElement(LabelMedium, {
30792
+ })), /*#__PURE__*/React.createElement(LabelMedium, {
30777
30793
  tag: "label",
30778
- style: [styles$t.row, styles$t.truncatedWidth]
30779
- }, "fill", /*#__PURE__*/React.createElement(Strut, {
30780
- size: spacing.xxSmall_6
30794
+ style: styles$s.row
30795
+ }, "size", /*#__PURE__*/React.createElement(Strut, {
30796
+ size: spacing.xSmall_8
30781
30797
  }), /*#__PURE__*/React.createElement(SingleSelect, {
30782
- selectedValue: fillStyle,
30783
- onChange: value => onChangeProps({
30784
- fillStyle: value
30798
+ selectedValue: size,
30799
+ onChange: newValue => onChangeProps({
30800
+ size: newValue
30785
30801
  })
30786
- // Placeholder is required, but never gets used.
30802
+ // Placeholder is required, but never gets used since
30803
+ // we have a label for the select.
30787
30804
  ,
30788
30805
  placeholder: ""
30789
- }, Object.keys(lockedFigureFillStyles).map(option => /*#__PURE__*/React.createElement(OptionItem, {
30790
- key: option,
30791
- value: option,
30792
- label: option
30793
- }))))), /*#__PURE__*/React.createElement(LineStrokeSelect, {
30794
- selectedValue: strokeStyle,
30795
- onChange: value => onChangeProps({
30796
- strokeStyle: value
30797
- })
30798
- }), /*#__PURE__*/React.createElement(LockedFigureSettingsActions, {
30799
- figureType: props.type,
30806
+ }, /*#__PURE__*/React.createElement(OptionItem, {
30807
+ value: "small",
30808
+ label: "small"
30809
+ }), /*#__PURE__*/React.createElement(OptionItem, {
30810
+ value: "medium",
30811
+ label: "medium"
30812
+ }), /*#__PURE__*/React.createElement(OptionItem, {
30813
+ value: "large",
30814
+ label: "large"
30815
+ })))), /*#__PURE__*/React.createElement(LockedFigureSettingsActions, {
30816
+ figureType: type,
30800
30817
  onMove: onMove,
30801
30818
  onRemove: onRemove
30802
30819
  }));
30803
- };
30804
- const styles$t = StyleSheet.create({
30820
+ }
30821
+ const styles$s = StyleSheet.create({
30822
+ accordionHeaderContainer: {
30823
+ // Stop the label summary from wrapping.
30824
+ whiteSpace: "nowrap"
30825
+ },
30826
+ accordionHeader: {
30827
+ padding: spacing.xxxSmall_4,
30828
+ marginInlineEnd: spacing.xSmall_8,
30829
+ borderRadius: spacing.xxxSmall_4,
30830
+ textOverflow: "ellipsis",
30831
+ overflow: "hidden"
30832
+ },
30805
30833
  row: {
30806
30834
  display: "flex",
30807
30835
  flexDirection: "row",
30808
- alignItems: "center"
30809
- },
30810
- spaceUnder: {
30811
- marginBottom: spacing.xSmall_8
30812
- },
30813
- truncatedWidth: {
30836
+ alignItems: "center",
30814
30837
  // Allow truncation, stop bleeding over the edge.
30815
30838
  minWidth: 0
30816
- }
30817
- });
30818
-
30819
- const LineSwatch = props => {
30820
- const {
30821
- color,
30822
- lineStyle
30823
- } = props;
30824
- return /*#__PURE__*/React.createElement(View, {
30825
- style: styles$s.container
30826
- }, /*#__PURE__*/React.createElement(View, {
30827
- "aria-label": `${color}, ${lineStyle}`,
30828
- style: [styles$s.lineSwatch, {
30829
- border: `5px ${lineStyle} ${lockedFigureColors[color]}`
30830
- }]
30831
- }));
30832
- };
30833
- const styles$s = StyleSheet.create({
30834
- container: {
30835
- backgroundColor: color.white,
30836
- justifyContent: "center",
30837
- padding: spacing.xSmall_8,
30838
- borderRadius: spacing.xxxSmall_4
30839
30839
  },
30840
- lineSwatch: {
30841
- width: 40
30840
+ spaceUnder: {
30841
+ marginBottom: spacing.xSmall_8
30842
30842
  }
30843
30843
  });
30844
30844
 
30845
- const examples = {
30846
- linear: ["x + 5", "1/2x - 2"],
30847
- polynomial: ["1/2x^2 + 3x - 4", "(1/3)x^3 - 2x^2 + 3x - 4"],
30848
- trigonometric: ["sin(x) * 3", "arctan(2x) + 4"]
30849
- };
30850
-
30851
- /**
30852
- * LockedFunctionSettings is a component that allows the user to edit the
30853
- * settings of specifically a locked function on the graph.
30854
- *
30855
- * Used in the interactive graph editor's locked figures section.
30856
- */
30857
- const LockedFunctionSettings = props => {
30858
- const {
30859
- color: lineColor,
30845
+ const DEFAULT_COLOR = "grayH";
30846
+ function getDefaultFigureForType(type) {
30847
+ switch (type) {
30848
+ case "point":
30849
+ return {
30850
+ type: "point",
30851
+ coord: [0, 0],
30852
+ color: DEFAULT_COLOR,
30853
+ filled: true
30854
+ };
30855
+ case "line":
30856
+ return {
30857
+ type: "line",
30858
+ kind: "line",
30859
+ points: [getDefaultFigureForType("point"), _extends({}, getDefaultFigureForType("point"), {
30860
+ coord: [2, 2]
30861
+ })],
30862
+ color: DEFAULT_COLOR,
30863
+ lineStyle: "solid",
30864
+ showPoint1: false,
30865
+ showPoint2: false
30866
+ };
30867
+ case "vector":
30868
+ return {
30869
+ type: "vector",
30870
+ points: [[0, 0], [2, 2]],
30871
+ color: DEFAULT_COLOR
30872
+ };
30873
+ case "ellipse":
30874
+ return {
30875
+ type: "ellipse",
30876
+ center: [0, 0],
30877
+ radius: [1, 1],
30878
+ angle: 0,
30879
+ color: DEFAULT_COLOR,
30880
+ fillStyle: "none",
30881
+ strokeStyle: "solid"
30882
+ };
30883
+ case "polygon":
30884
+ return {
30885
+ type: "polygon",
30886
+ points: [[0, 2], [-1, 0], [1, 0]],
30887
+ color: DEFAULT_COLOR,
30888
+ showVertices: false,
30889
+ fillStyle: "none",
30890
+ strokeStyle: "solid"
30891
+ };
30892
+ case "function":
30893
+ return {
30894
+ type: "function",
30895
+ color: DEFAULT_COLOR,
30896
+ strokeStyle: "solid",
30897
+ equation: "x^2",
30898
+ directionalAxis: "x"
30899
+ };
30900
+ case "label":
30901
+ return {
30902
+ type: "label",
30903
+ coord: [0, 0],
30904
+ text: "",
30905
+ color: DEFAULT_COLOR,
30906
+ size: "medium"
30907
+ };
30908
+ default:
30909
+ throw new UnreachableCaseError(type);
30910
+ }
30911
+ }
30912
+
30913
+ const {
30914
+ InfoTip: InfoTip$c
30915
+ } = components;
30916
+ const LockedEllipseSettings = props => {
30917
+ var _flags$mafs;
30918
+ const {
30919
+ flags,
30920
+ center,
30921
+ radius,
30922
+ angle,
30923
+ color,
30924
+ labels,
30925
+ fillStyle,
30860
30926
  strokeStyle,
30861
- equation,
30862
- directionalAxis,
30863
- domain,
30927
+ expanded,
30928
+ onToggle,
30864
30929
  onChangeProps,
30865
30930
  onMove,
30866
30931
  onRemove
30867
30932
  } = props;
30868
- const equationPrefix = directionalAxis === "x" ? "y=" : "x=";
30869
- const domainRangeText = directionalAxis === "x" ? "domain" : "range";
30870
- const lineLabel = `Function (${equationPrefix}${equation})`;
30871
-
30872
- // Tracking the string value of domain/range constraints to handle interim state of
30873
- // entering a negative value, as well as representing Infinity as an empty string.
30874
- // This variable is used when specifying the values of the input fields.
30875
- const [domainEntries, setDomainEntries] = useState([domain && domain[0] !== -Infinity ? domain[0].toString() : "", domain && domain[1] !== Infinity ? domain[1].toString() : ""]);
30876
- const [exampleCategory, setExampleCategory] = useState("");
30877
- useEffect(() => {
30878
- // "useEffect" used to maintain parity between domain/range constraints and their string representation.
30879
- setDomainEntries([domain && domain[0] !== -Infinity ? domain[0].toString() : "", domain && domain[1] !== Infinity ? domain[1].toString() : ""]);
30880
- }, [domain]);
30933
+ function handleCenterChange(newCoord) {
30934
+ const xOffset = newCoord[0] - center[0];
30935
+ const yOffset = newCoord[1] - center[1];
30936
+ const newProps = {
30937
+ center: newCoord
30938
+ };
30881
30939
 
30882
- // Generic function for handling property changes (except for 'domain/range')
30883
- function handlePropChange(property, newValue) {
30884
- const updatedProps = {};
30885
- updatedProps[property] = newValue;
30886
- onChangeProps(updatedProps);
30940
+ // Update the coord by the same amount as the point for all labels
30941
+ newProps.labels = labels == null ? void 0 : labels.map(label => _extends({}, label, {
30942
+ coord: [label.coord[0] + xOffset, label.coord[1] + yOffset]
30943
+ }));
30944
+ onChangeProps(newProps);
30887
30945
  }
30946
+ function handleColorChange(newValue) {
30947
+ const newProps = {
30948
+ color: newValue
30949
+ };
30888
30950
 
30889
- /*
30890
- Reason for having a separate 'propChange' function for 'domain/range':
30891
- Domain/Range entries are optional. Their default value is +/- Infinity.
30892
- Since input fields that are empty evaluate to zero, there needs to be
30893
- dedicated code to convert empty to Infinity.
30894
- */
30895
- function handleDomainChange(limitIndex, newValueString) {
30896
- const newDomainEntries = [...domainEntries];
30897
- newDomainEntries[limitIndex] = newValueString;
30898
- setDomainEntries(newDomainEntries);
30899
- const newDomain = domain ? [...domain] : [-Infinity, Infinity];
30900
- let newValue = parseFloat(newValueString);
30901
- if (newValueString === "" && limitIndex === 0) {
30902
- newValue = -Infinity;
30903
- } else if (newValueString === "" && limitIndex === 1) {
30904
- newValue = Infinity;
30951
+ // Update the color of the all labels to match the point
30952
+ newProps.labels = labels == null ? void 0 : labels.map(label => _extends({}, label, {
30953
+ color: newValue
30954
+ }));
30955
+ onChangeProps(newProps);
30956
+ }
30957
+ function handleLabelChange(updatedLabel, labelIndex) {
30958
+ if (!labels) {
30959
+ return;
30905
30960
  }
30906
- newDomain[limitIndex] = newValue;
30961
+ const updatedLabels = [...labels];
30962
+ updatedLabels[labelIndex] = _extends({}, labels[labelIndex], updatedLabel);
30907
30963
  onChangeProps({
30908
- domain: newDomain
30964
+ labels: updatedLabels
30965
+ });
30966
+ }
30967
+ function handleLabelRemove(labelIndex) {
30968
+ if (!labels) {
30969
+ return;
30970
+ }
30971
+ const updatedLabels = labels.filter((_, index) => index !== labelIndex);
30972
+ onChangeProps({
30973
+ labels: updatedLabels
30909
30974
  });
30910
30975
  }
30911
- const exampleCategories = Object.keys(examples);
30912
- const exampleCategorySelected = exampleCategory !== "";
30913
- const exampleContent = exampleCategorySelected ? examples[exampleCategory] : ["Select category to see example equations"];
30914
30976
  return /*#__PURE__*/React.createElement(PerseusEditorAccordion, {
30915
- expanded: props.expanded,
30916
- onToggle: props.onToggle,
30917
- header: /*#__PURE__*/React.createElement(View, {
30977
+ expanded: expanded,
30978
+ onToggle: onToggle,
30979
+ header:
30980
+ /*#__PURE__*/
30981
+ // Summary: Ellipse, center, radius, color (opacity, dashed)
30982
+ React.createElement(View, {
30918
30983
  style: styles$r.row
30919
- }, /*#__PURE__*/React.createElement(LabelLarge, {
30920
- style: styles$r.accordionHeader
30921
- }, lineLabel), /*#__PURE__*/React.createElement(Strut, {
30984
+ }, /*#__PURE__*/React.createElement(LabelLarge, null, `Ellipse (${center[0]}, ${center[1]}), radius ${radius[0]}, ${radius[1]}`), /*#__PURE__*/React.createElement(Strut, {
30922
30985
  size: spacing.xSmall_8
30923
- }), /*#__PURE__*/React.createElement(LineSwatch, {
30924
- color: lineColor,
30925
- lineStyle: strokeStyle
30986
+ }), /*#__PURE__*/React.createElement(EllipseSwatch, {
30987
+ color: props.color,
30988
+ fillStyle: fillStyle,
30989
+ strokeStyle: strokeStyle
30926
30990
  }))
30927
30991
  }, /*#__PURE__*/React.createElement(View, {
30992
+ style: styles$r.row
30993
+ }, /*#__PURE__*/React.createElement(CoordinatePairInput, {
30994
+ coord: center,
30995
+ style: styles$r.spaceUnder,
30996
+ onChange: handleCenterChange
30997
+ }), /*#__PURE__*/React.createElement(View, {
30998
+ style: styles$r.spaceUnder
30999
+ }, /*#__PURE__*/React.createElement(InfoTip$c, null, "The coordinates for the center of the ellipse."))), /*#__PURE__*/React.createElement(CoordinatePairInput, {
31000
+ coord: radius,
31001
+ labels: ["x radius", "y radius"],
31002
+ style: styles$r.spaceUnder,
31003
+ onChange: newCoords => onChangeProps({
31004
+ radius: newCoords
31005
+ })
31006
+ }), /*#__PURE__*/React.createElement(AngleInput, {
31007
+ angle: angle,
31008
+ onChange: newAngle => onChangeProps({
31009
+ angle: newAngle
31010
+ })
31011
+ }), /*#__PURE__*/React.createElement(Strut, {
31012
+ size: spacing.xSmall_8
31013
+ }), /*#__PURE__*/React.createElement(View, {
30928
31014
  style: [styles$r.row, styles$r.spaceUnder]
30929
31015
  }, /*#__PURE__*/React.createElement(ColorSelect, {
30930
- selectedValue: lineColor,
30931
- onChange: newValue => {
30932
- handlePropChange("color", newValue);
30933
- }
31016
+ selectedValue: color,
31017
+ onChange: handleColorChange
30934
31018
  }), /*#__PURE__*/React.createElement(Strut, {
30935
- size: spacing.small_12
30936
- }), /*#__PURE__*/React.createElement(LineStrokeSelect, {
30937
- selectedValue: strokeStyle,
30938
- onChange: newValue => {
30939
- handlePropChange("strokeStyle", newValue);
30940
- }
30941
- })), /*#__PURE__*/React.createElement(View, {
30942
- style: [styles$r.row, styles$r.rowSpace]
30943
- }, /*#__PURE__*/React.createElement(SingleSelect, {
30944
- selectedValue: directionalAxis,
30945
- onChange: newValue => {
30946
- handlePropChange("directionalAxis", newValue);
30947
- },
30948
- "aria-label": "equation prefix",
30949
- style: [styles$r.dropdownLabel, styles$r.axisMenu]
30950
- // Placeholder is required, but never gets used.
30951
- ,
30952
- placeholder: ""
30953
- }, /*#__PURE__*/React.createElement(OptionItem, {
30954
- value: "x",
30955
- label: "y ="
30956
- }), /*#__PURE__*/React.createElement(OptionItem, {
30957
- value: "y",
30958
- label: "x ="
30959
- })), /*#__PURE__*/React.createElement(Strut, {
30960
- size: spacing.xSmall_8
30961
- }), /*#__PURE__*/React.createElement(TextField, {
30962
- type: "text",
30963
- "aria-label": "equation",
30964
- value: equation,
30965
- onChange: newValue => {
30966
- handlePropChange("equation", newValue);
30967
- },
30968
- style: [styles$r.textField]
30969
- })), /*#__PURE__*/React.createElement(View, {
30970
- style: [styles$r.row, styles$r.rowSpace]
30971
- }, /*#__PURE__*/React.createElement(LabelMedium, {
30972
- tag: "label",
30973
- style: [styles$r.dropdownLabel, styles$r.domainMin]
30974
- }, `${domainRangeText} min`, /*#__PURE__*/React.createElement(Strut, {
30975
- size: spacing.xxSmall_6
30976
- }), /*#__PURE__*/React.createElement(TextField, {
30977
- type: "number",
30978
- style: styles$r.domainMinField,
30979
- value: domainEntries[0],
30980
- onChange: newValue => {
30981
- handleDomainChange(0, newValue);
30982
- }
30983
- })), /*#__PURE__*/React.createElement(Strut, {
30984
31019
  size: spacing.medium_16
30985
31020
  }), /*#__PURE__*/React.createElement(LabelMedium, {
30986
31021
  tag: "label",
30987
- "aria-label": `${domainRangeText} max`,
30988
- style: [styles$r.dropdownLabel, styles$r.domainMax]
30989
- }, "max", /*#__PURE__*/React.createElement(Strut, {
30990
- size: spacing.xxSmall_6
30991
- }), /*#__PURE__*/React.createElement(TextField, {
30992
- type: "number",
30993
- style: styles$r.domainMaxField,
30994
- value: domainEntries[1],
30995
- onChange: newValue => {
30996
- handleDomainChange(1, newValue);
30997
- }
30998
- }))), /*#__PURE__*/React.createElement(PerseusEditorAccordion, {
30999
- header: /*#__PURE__*/React.createElement(LabelLarge, null, "Example Functions"),
31000
- expanded: false,
31001
- containerStyle: styles$r.exampleWorkspace,
31002
- panelStyle: styles$r.exampleAccordionPanel
31003
- }, /*#__PURE__*/React.createElement(LabelMedium, {
31004
- tag: "label",
31005
- style: styles$r.dropdownLabel
31006
- }, "Choose a category", /*#__PURE__*/React.createElement(Strut, {
31022
+ style: [styles$r.row, styles$r.truncatedWidth]
31023
+ }, "fill", /*#__PURE__*/React.createElement(Strut, {
31007
31024
  size: spacing.xxSmall_6
31008
31025
  }), /*#__PURE__*/React.createElement(SingleSelect, {
31009
- selectedValue: exampleCategory,
31010
- onChange: setExampleCategory,
31011
- placeholder: "examples"
31012
- }, exampleCategories.map(category => {
31013
- return /*#__PURE__*/React.createElement(OptionItem, {
31014
- key: category,
31015
- value: category,
31016
- label: category
31017
- });
31018
- }))), exampleCategorySelected && /*#__PURE__*/React.createElement("ul", {
31019
- className: css(styles$r.exampleContainer)
31020
- }, exampleContent.map((example, index) => /*#__PURE__*/React.createElement(ExampleItem, {
31021
- category: exampleCategory,
31022
- example: example,
31023
- index: index,
31024
- pasteEquationFn: handlePropChange
31025
- })))), /*#__PURE__*/React.createElement(LockedFigureSettingsActions, {
31026
+ selectedValue: fillStyle,
31027
+ onChange: value => onChangeProps({
31028
+ fillStyle: value
31029
+ })
31030
+ // Placeholder is required, but never gets used.
31031
+ ,
31032
+ placeholder: ""
31033
+ }, Object.keys(lockedFigureFillStyles).map(option => /*#__PURE__*/React.createElement(OptionItem, {
31034
+ key: option,
31035
+ value: option,
31036
+ label: option
31037
+ }))))), /*#__PURE__*/React.createElement(LineStrokeSelect, {
31038
+ selectedValue: strokeStyle,
31039
+ onChange: value => onChangeProps({
31040
+ strokeStyle: value
31041
+ })
31042
+ }), (flags == null || (_flags$mafs = flags["mafs"]) == null ? void 0 : _flags$mafs["locked-ellipse-labels"]) && /*#__PURE__*/React.createElement(React.Fragment, null, labels == null ? void 0 : labels.map((label, labelIndex) => /*#__PURE__*/React.createElement(LockedLabelSettings, _extends({}, label, {
31043
+ expanded: true,
31044
+ onChangeProps: newLabel => {
31045
+ handleLabelChange(newLabel, labelIndex);
31046
+ },
31047
+ onRemove: () => {
31048
+ handleLabelRemove(labelIndex);
31049
+ },
31050
+ containerStyle: styles$r.labelContainer
31051
+ }))), /*#__PURE__*/React.createElement(Button, {
31052
+ kind: "tertiary",
31053
+ startIcon: plusCircle,
31054
+ onClick: () => {
31055
+ var _labels$length;
31056
+ const newLabel = _extends({}, getDefaultFigureForType("label"), {
31057
+ coord: [center[0],
31058
+ // Additional vertical offset for each
31059
+ // label so they don't overlap.
31060
+ center[1] - ((_labels$length = labels == null ? void 0 : labels.length) != null ? _labels$length : 0)],
31061
+ // Default to the same color as the ellipse
31062
+ color: color
31063
+ });
31064
+ onChangeProps({
31065
+ labels: [...(labels != null ? labels : []), newLabel]
31066
+ });
31067
+ },
31068
+ style: styles$r.addButton
31069
+ }, "Add visible label")), /*#__PURE__*/React.createElement(LockedFigureSettingsActions, {
31026
31070
  figureType: props.type,
31027
31071
  onMove: onMove,
31028
31072
  onRemove: onRemove
31029
31073
  }));
31030
31074
  };
31031
- const ExampleItem = props => {
31032
- const {
31033
- category,
31034
- example,
31035
- index,
31036
- pasteEquationFn
31037
- } = props;
31038
- const exampleId = useId();
31039
- return /*#__PURE__*/React.createElement("li", {
31040
- key: `${category}-${index}`,
31041
- className: css(styles$r.exampleRow)
31042
- }, /*#__PURE__*/React.createElement(IconButton, {
31043
- icon: autoPasteIcon,
31044
- "aria-label": "paste example",
31045
- "aria-describedby": exampleId,
31046
- onClick: () => pasteEquationFn("equation", example),
31047
- size: "medium",
31048
- style: styles$r.copyPasteButton
31049
- }), /*#__PURE__*/React.createElement(IconButton, {
31050
- icon: copyIcon,
31051
- "aria-label": "copy example",
31052
- "aria-describedby": exampleId,
31053
- onClick: () => navigator.clipboard.writeText(example),
31054
- size: "medium",
31055
- style: styles$r.copyPasteButton
31056
- }), /*#__PURE__*/React.createElement(Strut, {
31057
- size: spacing.xxxSmall_4
31058
- }), /*#__PURE__*/React.createElement(View, {
31059
- style: styles$r.exampleContent,
31060
- id: exampleId
31061
- }, example));
31062
- };
31063
31075
  const styles$r = StyleSheet.create({
31064
- accordionHeader: {
31065
- textOverflow: "ellipsis",
31066
- // A maximum width needs to be specified in order for the ellipsis to work.
31067
- // The 64px in the calculation accounts for the line swatch (56px) and the preceding strut (8px).
31068
- // Margin and padding won't work here because they would create space between the header text and the swatch.
31069
- maxWidth: "calc(100% - 64px)",
31070
- overflow: "hidden",
31071
- whiteSpace: "nowrap"
31072
- },
31073
- axisMenu: {
31074
- minWidth: "auto"
31075
- },
31076
- copyPasteButton: {
31077
- flexShrink: "0",
31078
- margin: "0 2px"
31079
- },
31080
- domainMin: {
31081
- justifyContent: "space-between",
31082
- // The 'width' property is applied to the label, which wraps the text and the input field.
31083
- // The width of the input fields (min/max) should be the same (to have a consistent look),
31084
- // so the following calculation distributes the space accordingly.
31085
- // For the "domain/range min" block, the text is 82.7px, and the strut is 6px (88.7px total).
31086
- // The "domain/range max" block is 30.23px, and the strut is 6px (36.23px total).
31087
- // The calculation takes the remain space after the text & struts (141px total) are removed,
31088
- // and divides it between the two input fields equally.
31089
- // The calculation reads: "Take 1/2 of the non-text space, and add the required space for this label's text"
31090
- width: "calc(((100% - 141px) / 2) + 88.7px)",
31091
- // @ts-expect-error // TS2353: textWrap does not exist in type CSSProperties
31092
- textWrap: "nowrap"
31093
- },
31094
- domainMinField: {
31095
- width: "calc(100% - 88.7px)" // make room for the label
31096
- },
31097
-
31098
- domainMax: {
31099
- // See explanation for "domainMin" for the calculation below.
31100
- width: "calc(((100% - 141px) / 2) + 36.2px)"
31101
- },
31102
- domainMaxField: {
31103
- width: "calc(100% - 36.2px)" // make room for the label
31104
- },
31105
-
31106
- dropdownLabel: {
31107
- alignItems: "center",
31108
- display: "flex"
31109
- },
31110
- exampleAccordionPanel: {
31111
- alignItems: "start",
31112
- paddingBottom: "12px",
31113
- flexDirection: "row",
31114
- flexWrap: "wrap"
31115
- },
31116
- exampleContainer: {
31117
- background: "white",
31118
- border: `1px solid ${color.fadedOffBlack16}`,
31119
- borderRadius: "4px",
31120
- flexGrow: "1",
31121
- listStyleType: "none",
31122
- // Nothing special about the maxHeight value,
31123
- // just a good height to partially show a 3rd example in the list
31124
- // to hint at scrollable content.
31125
- maxHeight: "88px",
31126
- margin: "8px 0 0 0",
31127
- overflowY: "scroll",
31128
- padding: "4px 12px 4px 4px"
31129
- },
31130
- exampleContent: {
31131
- fontFamily: `"Lato", sans-serif`,
31132
- flexGrow: "1",
31133
- color: color.offBlack
31134
- },
31135
- exampleRow: {
31136
- alignItems: "center",
31076
+ row: {
31137
31077
  display: "flex",
31138
31078
  flexDirection: "row",
31139
- minHeight: "44px"
31079
+ alignItems: "center"
31140
31080
  },
31141
- exampleWorkspace: {
31142
- background: color.white50
31081
+ spaceUnder: {
31082
+ marginBottom: spacing.xSmall_8
31143
31083
  },
31144
- rowSpace: {
31145
- marginTop: spacing.xSmall_8
31084
+ truncatedWidth: {
31085
+ // Allow truncation, stop bleeding over the edge.
31086
+ minWidth: 0
31146
31087
  },
31147
- row: {
31148
- display: "flex",
31149
- flexDirection: "row",
31150
- alignItems: "center"
31088
+ addButton: {
31089
+ alignSelf: "start"
31151
31090
  },
31152
- textField: {
31153
- flexGrow: "1"
31091
+ labelContainer: {
31092
+ backgroundColor: color.white
31154
31093
  }
31155
31094
  });
31156
31095
 
31157
- /**
31158
- * LockedLabelSettings is a component that allows the user to edit the
31159
- * settings of specifically a locked label on the graph within the
31160
- * Interactive Graph widget.
31161
- *
31162
- * Used in the interactive graph editor's locked figures section.
31163
- */
31164
- function LockedLabelSettings(props) {
31096
+ const LineSwatch = props => {
31165
31097
  const {
31166
- type,
31167
- coord,
31168
- color: color$1,
31169
- size,
31170
- text,
31171
- expanded,
31098
+ color,
31099
+ lineStyle
31100
+ } = props;
31101
+ return /*#__PURE__*/React.createElement(View, {
31102
+ style: styles$q.container
31103
+ }, /*#__PURE__*/React.createElement(View, {
31104
+ "aria-label": `${color}, ${lineStyle}`,
31105
+ style: [styles$q.lineSwatch, {
31106
+ border: `5px ${lineStyle} ${lockedFigureColors[color]}`
31107
+ }]
31108
+ }));
31109
+ };
31110
+ const styles$q = StyleSheet.create({
31111
+ container: {
31112
+ backgroundColor: color.white,
31113
+ justifyContent: "center",
31114
+ padding: spacing.xSmall_8,
31115
+ borderRadius: spacing.xxxSmall_4
31116
+ },
31117
+ lineSwatch: {
31118
+ width: 40
31119
+ }
31120
+ });
31121
+
31122
+ const examples = {
31123
+ linear: ["x + 5", "1/2x - 2"],
31124
+ polynomial: ["1/2x^2 + 3x - 4", "(1/3)x^3 - 2x^2 + 3x - 4"],
31125
+ trigonometric: ["sin(x) * 3", "arctan(2x) + 4"]
31126
+ };
31127
+
31128
+ const LockedFunctionSettings = props => {
31129
+ var _flags$mafs;
31130
+ const {
31131
+ flags,
31132
+ color: lineColor,
31133
+ strokeStyle,
31134
+ equation,
31135
+ directionalAxis,
31136
+ domain,
31137
+ labels,
31172
31138
  onChangeProps,
31173
31139
  onMove,
31174
- onRemove,
31175
- onToggle
31140
+ onRemove
31176
31141
  } = props;
31177
- return /*#__PURE__*/React.createElement(PerseusEditorAccordion, {
31178
- expanded: expanded,
31179
- onToggle: onToggle,
31180
- header: /*#__PURE__*/React.createElement(View, {
31181
- style: [styles$q.row, styles$q.accordionHeaderContainer]
31182
- }, /*#__PURE__*/React.createElement(LabelLarge, null, "Label (", coord[0], ", ", coord[1], ")"), /*#__PURE__*/React.createElement(Strut, {
31183
- size: spacing.xSmall_8
31184
- }), text !== "" && /*#__PURE__*/React.createElement(LabelLarge, {
31185
- style: [{
31186
- backgroundColor: color.white,
31187
- color: lockedFigureColors[color$1]
31188
- }, styles$q.accordionHeader]
31189
- }, text)),
31190
- containerStyle: !onMove && styles$q.innerLabelAccordionPanel
31191
- }, /*#__PURE__*/React.createElement(CoordinatePairInput, {
31192
- coord: coord,
31193
- onChange: newCoords => {
31194
- onChangeProps({
31195
- coord: newCoords
31196
- });
31197
- },
31198
- style: styles$q.spaceUnder
31199
- }), /*#__PURE__*/React.createElement(LabelMedium, {
31200
- tag: "label",
31201
- style: [styles$q.row, styles$q.spaceUnder]
31202
- }, "TeX", /*#__PURE__*/React.createElement(Strut, {
31203
- size: spacing.xSmall_8
31204
- }), /*#__PURE__*/React.createElement(TextField, {
31205
- value: text,
31206
- placeholder: "ex. x^2 or \\frac{1}{2}",
31207
- onChange: newValue => onChangeProps({
31208
- text: newValue
31209
- })
31210
- })), /*#__PURE__*/React.createElement(View, {
31211
- style: styles$q.row
31212
- }, onMove && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(ColorSelect, {
31213
- selectedValue: color$1,
31214
- onChange: newColor => {
31215
- onChangeProps({
31216
- color: newColor
31217
- });
31218
- },
31219
- style: styles$q.spaceUnder
31220
- }), /*#__PURE__*/React.createElement(Strut, {
31221
- size: spacing.medium_16
31222
- })), /*#__PURE__*/React.createElement(LabelMedium, {
31223
- tag: "label",
31224
- style: styles$q.row
31225
- }, "size", /*#__PURE__*/React.createElement(Strut, {
31226
- size: spacing.xSmall_8
31227
- }), /*#__PURE__*/React.createElement(SingleSelect, {
31228
- selectedValue: size,
31229
- onChange: newValue => onChangeProps({
31230
- size: newValue
31231
- })
31232
- // Placeholder is required, but never gets used since
31233
- // we have a label for the select.
31234
- ,
31235
- placeholder: ""
31236
- }, /*#__PURE__*/React.createElement(OptionItem, {
31237
- value: "small",
31238
- label: "small"
31239
- }), /*#__PURE__*/React.createElement(OptionItem, {
31240
- value: "medium",
31241
- label: "medium"
31242
- }), /*#__PURE__*/React.createElement(OptionItem, {
31243
- value: "large",
31244
- label: "large"
31245
- })))), /*#__PURE__*/React.createElement(LockedFigureSettingsActions, {
31246
- figureType: type,
31247
- onMove: onMove,
31248
- onRemove: onRemove
31249
- }));
31250
- }
31251
- const styles$q = StyleSheet.create({
31252
- accordionHeaderContainer: {
31253
- // Stop the label summary from wrapping.
31254
- whiteSpace: "nowrap"
31255
- },
31256
- accordionHeader: {
31257
- padding: spacing.xxxSmall_4,
31258
- marginInlineEnd: spacing.xSmall_8,
31259
- borderRadius: spacing.xxxSmall_4,
31260
- textOverflow: "ellipsis",
31261
- overflow: "hidden"
31262
- },
31263
- row: {
31264
- display: "flex",
31265
- flexDirection: "row",
31266
- alignItems: "center",
31267
- // Allow truncation, stop bleeding over the edge.
31268
- minWidth: 0
31269
- },
31270
- spaceUnder: {
31271
- marginBottom: spacing.xSmall_8
31272
- },
31273
- innerLabelAccordionPanel: {
31274
- backgroundColor: color.white
31275
- }
31276
- });
31142
+ const equationPrefix = directionalAxis === "x" ? "y=" : "x=";
31143
+ const domainRangeText = directionalAxis === "x" ? "domain" : "range";
31144
+ const lineLabel = `Function (${equationPrefix}${equation})`;
31277
31145
 
31278
- const LabeledSwitch = props => {
31279
- const {
31280
- checked,
31281
- label,
31282
- style,
31283
- onChange
31284
- } = props;
31285
- const ids = useUniqueIdWithMock();
31286
- const switchId = ids.get("switch");
31287
- return /*#__PURE__*/React.createElement(View, {
31288
- style: [styles$p.row, style]
31289
- }, /*#__PURE__*/React.createElement(Switch, {
31290
- id: switchId,
31291
- checked: checked,
31292
- onChange: onChange
31293
- }), /*#__PURE__*/React.createElement(Strut, {
31294
- size: spacing.xSmall_8
31295
- }), /*#__PURE__*/React.createElement(LabelMedium, {
31296
- tag: "label",
31297
- htmlFor: switchId
31298
- }, label));
31299
- };
31300
- const styles$p = StyleSheet.create({
31301
- row: {
31302
- flexDirection: "row",
31303
- alignItems: "center"
31146
+ // Tracking the string value of domain/range constraints to handle interim state of
31147
+ // entering a negative value, as well as representing Infinity as an empty string.
31148
+ // This variable is used when specifying the values of the input fields.
31149
+ const [domainEntries, setDomainEntries] = useState([domain && domain[0] !== -Infinity ? domain[0].toString() : "", domain && domain[1] !== Infinity ? domain[1].toString() : ""]);
31150
+ const [exampleCategory, setExampleCategory] = useState("");
31151
+ useEffect(() => {
31152
+ // "useEffect" used to maintain parity between domain/range constraints and their string representation.
31153
+ setDomainEntries([domain && domain[0] !== -Infinity ? domain[0].toString() : "", domain && domain[1] !== Infinity ? domain[1].toString() : ""]);
31154
+ }, [domain]);
31155
+
31156
+ // Generic function for handling property changes (except for 'domain/range')
31157
+ function handlePropChange(property, newValue) {
31158
+ const updatedProps = {};
31159
+ updatedProps[property] = newValue;
31160
+ onChangeProps(updatedProps);
31304
31161
  }
31305
- });
31306
31162
 
31307
- /**
31308
- * DefiningPointSettings is a component that allows the user to edit the
31309
- * settings of a point that defines a locked figure (ex. line) on the graph.
31310
- *
31311
- * Used in the interactive graph editor's locked figures section.
31312
- */
31313
- const DefiningPointSettings = props => {
31314
- const {
31315
- coord,
31316
- color: pointColor,
31317
- filled = true,
31318
- label,
31319
- showPoint = "false",
31320
- error,
31321
- onChangeProps,
31322
- onTogglePoint,
31323
- expanded,
31324
- onToggle
31325
- } = props;
31326
- function handleColorChange(newValue) {
31163
+ /*
31164
+ Reason for having a separate 'propChange' function for 'domain/range':
31165
+ Domain/Range entries are optional. Their default value is +/- Infinity.
31166
+ Since input fields that are empty evaluate to zero, there needs to be
31167
+ dedicated code to convert empty to Infinity.
31168
+ */
31169
+ function handleDomainChange(limitIndex, newValueString) {
31170
+ const newDomainEntries = [...domainEntries];
31171
+ newDomainEntries[limitIndex] = newValueString;
31172
+ setDomainEntries(newDomainEntries);
31173
+ const newDomain = domain ? [...domain] : [-Infinity, Infinity];
31174
+ let newValue = parseFloat(newValueString);
31175
+ if (newValueString === "" && limitIndex === 0) {
31176
+ newValue = -Infinity;
31177
+ } else if (newValueString === "" && limitIndex === 1) {
31178
+ newValue = Infinity;
31179
+ }
31180
+ newDomain[limitIndex] = newValue;
31327
31181
  onChangeProps({
31182
+ domain: newDomain
31183
+ });
31184
+ }
31185
+ const exampleCategories = Object.keys(examples);
31186
+ const exampleCategorySelected = exampleCategory !== "";
31187
+ const exampleContent = exampleCategorySelected ? examples[exampleCategory] : ["Select category to see example equations"];
31188
+ function handleColorChange(newValue) {
31189
+ const newProps = {
31190
+ color: newValue
31191
+ };
31192
+
31193
+ // Update the color of the all labels to match the point
31194
+ newProps.labels = labels == null ? void 0 : labels.map(label => _extends({}, label, {
31328
31195
  color: newValue
31196
+ }));
31197
+ onChangeProps(newProps);
31198
+ }
31199
+ function handleLabelChange(updatedLabel, labelIndex) {
31200
+ if (!labels) {
31201
+ return;
31202
+ }
31203
+ const updatedLabels = [...labels];
31204
+ updatedLabels[labelIndex] = _extends({}, labels[labelIndex], updatedLabel);
31205
+ onChangeProps({
31206
+ labels: updatedLabels
31207
+ });
31208
+ }
31209
+ function handleLabelRemove(labelIndex) {
31210
+ if (!labels) {
31211
+ return;
31212
+ }
31213
+ const updatedLabels = labels.filter((_, index) => index !== labelIndex);
31214
+ onChangeProps({
31215
+ labels: updatedLabels
31329
31216
  });
31330
31217
  }
31331
31218
  return /*#__PURE__*/React.createElement(PerseusEditorAccordion, {
31332
- expanded: expanded,
31333
- onToggle: onToggle,
31334
- containerStyle: styles$o.container,
31335
- panelStyle: styles$o.accordionPanel,
31336
- header:
31337
- /*#__PURE__*/
31338
- // Summary: Point, coords, color (filled/open)
31339
- React.createElement(View, {
31340
- style: styles$o.row
31341
- }, /*#__PURE__*/React.createElement(LabelLarge, null, `${label || "Point"} (${coord[0]}, ${coord[1]})`), /*#__PURE__*/React.createElement(Strut, {
31219
+ expanded: props.expanded,
31220
+ onToggle: props.onToggle,
31221
+ header: /*#__PURE__*/React.createElement(View, {
31222
+ style: styles$p.row
31223
+ }, /*#__PURE__*/React.createElement(LabelLarge, {
31224
+ style: styles$p.accordionHeader
31225
+ }, lineLabel), /*#__PURE__*/React.createElement(Strut, {
31342
31226
  size: spacing.xSmall_8
31343
- }), showPoint && /*#__PURE__*/React.createElement(ColorSwatch, {
31344
- color: pointColor,
31345
- filled: filled
31227
+ }), /*#__PURE__*/React.createElement(LineSwatch, {
31228
+ color: lineColor,
31229
+ lineStyle: strokeStyle
31346
31230
  }))
31347
- }, /*#__PURE__*/React.createElement(CoordinatePairInput, {
31348
- coord: coord,
31349
- error: !!error,
31350
- style: styles$o.spaceUnder,
31351
- onChange: newCoords => {
31352
- onChangeProps({
31353
- coord: newCoords
31354
- });
31231
+ }, /*#__PURE__*/React.createElement(View, {
31232
+ style: [styles$p.row, styles$p.spaceUnder]
31233
+ }, /*#__PURE__*/React.createElement(ColorSelect, {
31234
+ selectedValue: lineColor,
31235
+ onChange: handleColorChange
31236
+ }), /*#__PURE__*/React.createElement(Strut, {
31237
+ size: spacing.small_12
31238
+ }), /*#__PURE__*/React.createElement(LineStrokeSelect, {
31239
+ selectedValue: strokeStyle,
31240
+ onChange: newValue => {
31241
+ handlePropChange("strokeStyle", newValue);
31355
31242
  }
31356
- }), onTogglePoint && /*#__PURE__*/React.createElement(LabeledSwitch, {
31357
- label: "show point on graph",
31358
- checked: !!showPoint,
31359
- style: showPoint && styles$o.spaceUnder,
31360
- onChange: onTogglePoint
31361
- }), showPoint && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(ColorSelect, {
31362
- selectedValue: pointColor,
31363
- onChange: handleColorChange,
31364
- style: styles$o.spaceUnder
31365
- }), /*#__PURE__*/React.createElement(LabeledSwitch, {
31366
- label: "open point",
31367
- checked: !filled,
31243
+ })), /*#__PURE__*/React.createElement(View, {
31244
+ style: [styles$p.row, styles$p.rowSpace]
31245
+ }, /*#__PURE__*/React.createElement(SingleSelect, {
31246
+ selectedValue: directionalAxis,
31368
31247
  onChange: newValue => {
31248
+ handlePropChange("directionalAxis", newValue);
31249
+ },
31250
+ "aria-label": "equation prefix",
31251
+ style: [styles$p.dropdownLabel, styles$p.axisMenu]
31252
+ // Placeholder is required, but never gets used.
31253
+ ,
31254
+ placeholder: ""
31255
+ }, /*#__PURE__*/React.createElement(OptionItem, {
31256
+ value: "x",
31257
+ label: "y ="
31258
+ }), /*#__PURE__*/React.createElement(OptionItem, {
31259
+ value: "y",
31260
+ label: "x ="
31261
+ })), /*#__PURE__*/React.createElement(Strut, {
31262
+ size: spacing.xSmall_8
31263
+ }), /*#__PURE__*/React.createElement(TextField, {
31264
+ type: "text",
31265
+ "aria-label": "equation",
31266
+ value: equation,
31267
+ onChange: newValue => {
31268
+ handlePropChange("equation", newValue);
31269
+ },
31270
+ style: [styles$p.textField]
31271
+ })), /*#__PURE__*/React.createElement(View, {
31272
+ style: [styles$p.row, styles$p.rowSpace]
31273
+ }, /*#__PURE__*/React.createElement(LabelMedium, {
31274
+ tag: "label",
31275
+ style: [styles$p.dropdownLabel, styles$p.domainMin]
31276
+ }, `${domainRangeText} min`, /*#__PURE__*/React.createElement(Strut, {
31277
+ size: spacing.xxSmall_6
31278
+ }), /*#__PURE__*/React.createElement(TextField, {
31279
+ type: "number",
31280
+ style: styles$p.domainMinField,
31281
+ value: domainEntries[0],
31282
+ onChange: newValue => {
31283
+ handleDomainChange(0, newValue);
31284
+ }
31285
+ })), /*#__PURE__*/React.createElement(Strut, {
31286
+ size: spacing.medium_16
31287
+ }), /*#__PURE__*/React.createElement(LabelMedium, {
31288
+ tag: "label",
31289
+ "aria-label": `${domainRangeText} max`,
31290
+ style: [styles$p.dropdownLabel, styles$p.domainMax]
31291
+ }, "max", /*#__PURE__*/React.createElement(Strut, {
31292
+ size: spacing.xxSmall_6
31293
+ }), /*#__PURE__*/React.createElement(TextField, {
31294
+ type: "number",
31295
+ style: styles$p.domainMaxField,
31296
+ value: domainEntries[1],
31297
+ onChange: newValue => {
31298
+ handleDomainChange(1, newValue);
31299
+ }
31300
+ }))), /*#__PURE__*/React.createElement(PerseusEditorAccordion, {
31301
+ header: /*#__PURE__*/React.createElement(LabelLarge, null, "Example Functions"),
31302
+ expanded: false,
31303
+ containerStyle: styles$p.exampleWorkspace,
31304
+ panelStyle: styles$p.exampleAccordionPanel
31305
+ }, /*#__PURE__*/React.createElement(LabelMedium, {
31306
+ tag: "label",
31307
+ style: styles$p.dropdownLabel
31308
+ }, "Choose a category", /*#__PURE__*/React.createElement(Strut, {
31309
+ size: spacing.xxSmall_6
31310
+ }), /*#__PURE__*/React.createElement(SingleSelect, {
31311
+ selectedValue: exampleCategory,
31312
+ onChange: setExampleCategory,
31313
+ placeholder: "examples"
31314
+ }, exampleCategories.map(category => {
31315
+ return /*#__PURE__*/React.createElement(OptionItem, {
31316
+ key: category,
31317
+ value: category,
31318
+ label: category
31319
+ });
31320
+ }))), exampleCategorySelected && /*#__PURE__*/React.createElement("ul", {
31321
+ className: css(styles$p.exampleContainer)
31322
+ }, exampleContent.map((example, index) => /*#__PURE__*/React.createElement(ExampleItem, {
31323
+ category: exampleCategory,
31324
+ example: example,
31325
+ index: index,
31326
+ pasteEquationFn: handlePropChange
31327
+ })))), (flags == null || (_flags$mafs = flags["mafs"]) == null ? void 0 : _flags$mafs["locked-function-labels"]) && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(View, {
31328
+ style: styles$p.horizontalRule
31329
+ }), labels == null ? void 0 : labels.map((label, labelIndex) => /*#__PURE__*/React.createElement(LockedLabelSettings, _extends({}, label, {
31330
+ expanded: true,
31331
+ onChangeProps: newLabel => {
31332
+ handleLabelChange(newLabel, labelIndex);
31333
+ },
31334
+ onRemove: () => {
31335
+ handleLabelRemove(labelIndex);
31336
+ },
31337
+ containerStyle: styles$p.labelContainer
31338
+ }))), /*#__PURE__*/React.createElement(Button, {
31339
+ kind: "tertiary",
31340
+ startIcon: plusCircle,
31341
+ onClick: () => {
31342
+ var _labels$length;
31343
+ const newLabel = _extends({}, getDefaultFigureForType("label"), {
31344
+ // Vertical offset for each label so they
31345
+ // don't overlap.
31346
+ coord: [0, -((_labels$length = labels == null ? void 0 : labels.length) != null ? _labels$length : 0)],
31347
+ // Default to the same color as the function
31348
+ color: lineColor
31349
+ });
31369
31350
  onChangeProps({
31370
- filled: !newValue
31351
+ labels: [...(labels != null ? labels : []), newLabel]
31371
31352
  });
31372
- }
31373
- })));
31353
+ },
31354
+ style: styles$p.addButton
31355
+ }, "Add visible label")), /*#__PURE__*/React.createElement(LockedFigureSettingsActions, {
31356
+ figureType: props.type,
31357
+ onMove: onMove,
31358
+ onRemove: onRemove
31359
+ }));
31374
31360
  };
31375
- const styles$o = StyleSheet.create({
31376
- container: {
31377
- marginTop: spacing.xSmall_8,
31378
- marginBottom: 0,
31379
- marginLeft: -spacing.xxxSmall_4,
31380
- marginRight: -spacing.xxxSmall_4,
31381
- backgroundColor: color.white
31361
+ const ExampleItem = props => {
31362
+ const {
31363
+ category,
31364
+ example,
31365
+ index,
31366
+ pasteEquationFn
31367
+ } = props;
31368
+ const exampleId = useId();
31369
+ return /*#__PURE__*/React.createElement("li", {
31370
+ key: `${category}-${index}`,
31371
+ className: css(styles$p.exampleRow)
31372
+ }, /*#__PURE__*/React.createElement(IconButton, {
31373
+ icon: autoPasteIcon,
31374
+ "aria-label": "paste example",
31375
+ "aria-describedby": exampleId,
31376
+ onClick: () => pasteEquationFn("equation", example),
31377
+ size: "medium",
31378
+ style: styles$p.copyPasteButton
31379
+ }), /*#__PURE__*/React.createElement(IconButton, {
31380
+ icon: copyIcon,
31381
+ "aria-label": "copy example",
31382
+ "aria-describedby": exampleId,
31383
+ onClick: () => navigator.clipboard.writeText(example),
31384
+ size: "medium",
31385
+ style: styles$p.copyPasteButton
31386
+ }), /*#__PURE__*/React.createElement(Strut, {
31387
+ size: spacing.xxxSmall_4
31388
+ }), /*#__PURE__*/React.createElement(View, {
31389
+ style: styles$p.exampleContent,
31390
+ id: exampleId
31391
+ }, example));
31392
+ };
31393
+ const styles$p = StyleSheet.create({
31394
+ accordionHeader: {
31395
+ textOverflow: "ellipsis",
31396
+ // A maximum width needs to be specified in order for the ellipsis to work.
31397
+ // The 64px in the calculation accounts for the line swatch (56px) and the preceding strut (8px).
31398
+ // Margin and padding won't work here because they would create space between the header text and the swatch.
31399
+ maxWidth: "calc(100% - 64px)",
31400
+ overflow: "hidden",
31401
+ whiteSpace: "nowrap"
31402
+ },
31403
+ axisMenu: {
31404
+ minWidth: "auto"
31405
+ },
31406
+ copyPasteButton: {
31407
+ flexShrink: "0",
31408
+ margin: "0 2px"
31409
+ },
31410
+ domainMin: {
31411
+ justifyContent: "space-between",
31412
+ // The 'width' property is applied to the label, which wraps the text and the input field.
31413
+ // The width of the input fields (min/max) should be the same (to have a consistent look),
31414
+ // so the following calculation distributes the space accordingly.
31415
+ // For the "domain/range min" block, the text is 82.7px, and the strut is 6px (88.7px total).
31416
+ // The "domain/range max" block is 30.23px, and the strut is 6px (36.23px total).
31417
+ // The calculation takes the remain space after the text & struts (141px total) are removed,
31418
+ // and divides it between the two input fields equally.
31419
+ // The calculation reads: "Take 1/2 of the non-text space, and add the required space for this label's text"
31420
+ width: "calc(((100% - 141px) / 2) + 88.7px)",
31421
+ // @ts-expect-error // TS2353: textWrap does not exist in type CSSProperties
31422
+ textWrap: "nowrap"
31423
+ },
31424
+ domainMinField: {
31425
+ width: "calc(100% - 88.7px)" // make room for the label
31426
+ },
31427
+
31428
+ domainMax: {
31429
+ // See explanation for "domainMin" for the calculation below.
31430
+ width: "calc(((100% - 141px) / 2) + 36.2px)"
31382
31431
  },
31383
- accordionPanel: {
31384
- // Need more space since we don't have the actions' margins.
31385
- paddingBottom: spacing.medium_16
31432
+ domainMaxField: {
31433
+ width: "calc(100% - 36.2px)" // make room for the label
31434
+ },
31435
+
31436
+ dropdownLabel: {
31437
+ alignItems: "center",
31438
+ display: "flex"
31439
+ },
31440
+ exampleAccordionPanel: {
31441
+ alignItems: "start",
31442
+ paddingBottom: "12px",
31443
+ flexDirection: "row",
31444
+ flexWrap: "wrap"
31445
+ },
31446
+ exampleContainer: {
31447
+ background: "white",
31448
+ border: `1px solid ${color.fadedOffBlack16}`,
31449
+ borderRadius: "4px",
31450
+ flexGrow: "1",
31451
+ listStyleType: "none",
31452
+ // Nothing special about the maxHeight value,
31453
+ // just a good height to partially show a 3rd example in the list
31454
+ // to hint at scrollable content.
31455
+ maxHeight: "88px",
31456
+ margin: "8px 0 0 0",
31457
+ overflowY: "scroll",
31458
+ padding: "4px 12px 4px 4px"
31459
+ },
31460
+ exampleContent: {
31461
+ fontFamily: `"Lato", sans-serif`,
31462
+ flexGrow: "1",
31463
+ color: color.offBlack
31464
+ },
31465
+ exampleRow: {
31466
+ alignItems: "center",
31467
+ display: "flex",
31468
+ flexDirection: "row",
31469
+ minHeight: "44px"
31470
+ },
31471
+ exampleWorkspace: {
31472
+ background: color.white50
31473
+ },
31474
+ rowSpace: {
31475
+ marginTop: spacing.xSmall_8
31386
31476
  },
31387
31477
  row: {
31478
+ display: "flex",
31388
31479
  flexDirection: "row",
31389
31480
  alignItems: "center"
31390
31481
  },
31391
- spaceUnder: {
31392
- marginBottom: spacing.xSmall_8
31482
+ textField: {
31483
+ flexGrow: "1"
31484
+ },
31485
+ // Label settings styles
31486
+ addButton: {
31487
+ alignSelf: "start"
31488
+ },
31489
+ horizontalRule: {
31490
+ marginTop: spacing.small_12,
31491
+ marginBottom: spacing.xxxSmall_4,
31492
+ height: 1,
31493
+ backgroundColor: color.offBlack16
31494
+ },
31495
+ labelContainer: {
31496
+ backgroundColor: color.white
31393
31497
  }
31394
31498
  });
31395
31499
 
31396
- const lengthZeroStr = "The line cannot have length 0.";
31397
- const LockedLineSettings = props => {
31500
+ const LabeledSwitch = props => {
31398
31501
  const {
31399
- kind,
31400
- points,
31401
- color: lineColor,
31402
- lineStyle = "solid",
31403
- showPoint1,
31404
- showPoint2,
31405
- onChangeProps,
31406
- onMove,
31407
- onRemove
31502
+ checked,
31503
+ label,
31504
+ style,
31505
+ onChange
31408
31506
  } = props;
31409
- const [point1, point2] = points;
31410
- const capitalizeKind = kind.charAt(0).toUpperCase() + kind.slice(1);
31411
- const lineLabel = `${capitalizeKind} (${point1.coord[0]},
31412
- ${point1.coord[1]}), (${point2.coord[0]}, ${point2.coord[1]})`;
31413
-
31414
- // Check if the line has length 0.
31415
- const isInvalid = vector.equal(point1.coord, point2.coord);
31416
- function handleChangePoint(newPointProps, index) {
31417
- const newPoints = [...points];
31418
- newPoints[index] = _extends({}, points[index], newPointProps);
31419
- onChangeProps({
31420
- points: newPoints
31421
- });
31422
- }
31423
- function handleColorChange(newColor) {
31424
- onChangeProps({
31425
- color: newColor,
31426
- // Keep the line's points' colors in sync with the line color.
31427
- points: [_extends({}, point1, {
31428
- color: newColor
31429
- }), _extends({}, point2, {
31430
- color: newColor
31431
- })]
31432
- });
31433
- }
31434
- return /*#__PURE__*/React.createElement(PerseusEditorAccordion, {
31435
- expanded: props.expanded,
31436
- onToggle: props.onToggle,
31437
- header: /*#__PURE__*/React.createElement(View, {
31438
- style: styles$n.row
31439
- }, /*#__PURE__*/React.createElement(LabelLarge, null, lineLabel), /*#__PURE__*/React.createElement(Strut, {
31440
- size: spacing.xSmall_8
31441
- }), /*#__PURE__*/React.createElement(LineSwatch, {
31442
- color: lineColor,
31443
- lineStyle: lineStyle
31444
- }))
31445
- }, /*#__PURE__*/React.createElement(LabelMedium, {
31446
- tag: "label",
31447
- style: [styles$n.row, styles$n.spaceUnder]
31448
- }, "kind", /*#__PURE__*/React.createElement(Strut, {
31449
- size: spacing.xxxSmall_4
31450
- }), /*#__PURE__*/React.createElement(SingleSelect, {
31451
- selectedValue: kind,
31452
- onChange: value => onChangeProps({
31453
- kind: value
31454
- })
31455
- // Placeholder is required, but never gets used.
31456
- ,
31457
- placeholder: ""
31458
- }, /*#__PURE__*/React.createElement(OptionItem, {
31459
- value: "line",
31460
- label: "line"
31461
- }), /*#__PURE__*/React.createElement(OptionItem, {
31462
- value: "ray",
31463
- label: "ray"
31464
- }), /*#__PURE__*/React.createElement(OptionItem, {
31465
- value: "segment",
31466
- label: "segment"
31467
- }))), /*#__PURE__*/React.createElement(View, {
31468
- style: styles$n.row
31469
- }, /*#__PURE__*/React.createElement(ColorSelect, {
31470
- selectedValue: lineColor,
31471
- onChange: handleColorChange
31507
+ const ids = useUniqueIdWithMock();
31508
+ const switchId = ids.get("switch");
31509
+ return /*#__PURE__*/React.createElement(View, {
31510
+ style: [styles$o.row, style]
31511
+ }, /*#__PURE__*/React.createElement(Switch, {
31512
+ id: switchId,
31513
+ checked: checked,
31514
+ onChange: onChange
31472
31515
  }), /*#__PURE__*/React.createElement(Strut, {
31473
- size: spacing.small_12
31474
- }), /*#__PURE__*/React.createElement(LineStrokeSelect, {
31475
- selectedValue: lineStyle,
31476
- onChange: value => onChangeProps({
31477
- lineStyle: value
31478
- })
31479
- })), isInvalid && /*#__PURE__*/React.createElement(LabelMedium, {
31480
- style: styles$n.errorText
31481
- }, lengthZeroStr), /*#__PURE__*/React.createElement(DefiningPointSettings, _extends({
31482
- label: "Point 1",
31483
- showPoint: showPoint1,
31484
- error: isInvalid ? lengthZeroStr : null
31485
- }, point1, {
31486
- onTogglePoint: newValue => onChangeProps({
31487
- showPoint1: newValue
31488
- }),
31489
- onChangeProps: newProps => handleChangePoint(newProps, 0)
31490
- })), /*#__PURE__*/React.createElement(DefiningPointSettings, _extends({
31491
- label: "Point 2",
31492
- showPoint: showPoint2,
31493
- error: isInvalid ? lengthZeroStr : null
31494
- }, point2, {
31495
- onTogglePoint: newValue => onChangeProps({
31496
- showPoint2: newValue
31497
- }),
31498
- onChangeProps: newProps => handleChangePoint(newProps, 1)
31499
- })), /*#__PURE__*/React.createElement(LockedFigureSettingsActions, {
31500
- figureType: props.type,
31501
- onMove: onMove,
31502
- onRemove: onRemove
31503
- }));
31516
+ size: spacing.xSmall_8
31517
+ }), /*#__PURE__*/React.createElement(LabelMedium, {
31518
+ tag: "label",
31519
+ htmlFor: switchId
31520
+ }, label));
31504
31521
  };
31505
- const styles$n = StyleSheet.create({
31522
+ const styles$o = StyleSheet.create({
31506
31523
  row: {
31507
- display: "flex",
31508
31524
  flexDirection: "row",
31509
31525
  alignItems: "center"
31510
- },
31511
- spaceUnder: {
31512
- marginBottom: spacing.xSmall_8
31513
- },
31514
- errorText: {
31515
- color: color.red
31516
31526
  }
31517
31527
  });
31518
31528
 
31519
- const DEFAULT_COLOR = "grayH";
31520
- function getDefaultFigureForType(type) {
31521
- switch (type) {
31522
- case "point":
31523
- return {
31524
- type: "point",
31525
- coord: [0, 0],
31526
- color: DEFAULT_COLOR,
31527
- filled: true,
31528
- labels: []
31529
- };
31530
- case "line":
31531
- return {
31532
- type: "line",
31533
- kind: "line",
31534
- points: [getDefaultFigureForType("point"), _extends({}, getDefaultFigureForType("point"), {
31535
- coord: [2, 2]
31536
- })],
31537
- color: DEFAULT_COLOR,
31538
- lineStyle: "solid",
31539
- showPoint1: false,
31540
- showPoint2: false
31541
- };
31542
- case "vector":
31543
- return {
31544
- type: "vector",
31545
- points: [[0, 0], [2, 2]],
31546
- color: DEFAULT_COLOR
31547
- };
31548
- case "ellipse":
31549
- return {
31550
- type: "ellipse",
31551
- center: [0, 0],
31552
- radius: [1, 1],
31553
- angle: 0,
31554
- color: DEFAULT_COLOR,
31555
- fillStyle: "none",
31556
- strokeStyle: "solid"
31557
- };
31558
- case "polygon":
31559
- return {
31560
- type: "polygon",
31561
- points: [[0, 2], [-1, 0], [1, 0]],
31562
- color: DEFAULT_COLOR,
31563
- showVertices: false,
31564
- fillStyle: "none",
31565
- strokeStyle: "solid"
31566
- };
31567
- case "function":
31568
- return {
31569
- type: "function",
31570
- color: DEFAULT_COLOR,
31571
- strokeStyle: "solid",
31572
- equation: "x^2",
31573
- directionalAxis: "x"
31574
- };
31575
- case "label":
31576
- return {
31577
- type: "label",
31578
- coord: [0, 0],
31579
- text: "",
31580
- color: DEFAULT_COLOR,
31581
- size: "medium"
31582
- };
31583
- default:
31584
- throw new UnreachableCaseError(type);
31585
- }
31586
- }
31587
-
31588
31529
  const LockedPointSettings = props => {
31589
- var _flags$mafs;
31530
+ var _flags$mafs, _flags$mafs2;
31590
31531
  const {
31591
31532
  flags,
31533
+ headerLabel,
31592
31534
  coord,
31593
31535
  color: pointColor,
31594
31536
  filled = true,
31595
31537
  labels,
31596
31538
  onChangeProps,
31597
31539
  onMove,
31598
- onRemove
31540
+ onRemove,
31541
+ // defining point props
31542
+ showPoint,
31543
+ expanded,
31544
+ onTogglePoint,
31545
+ onToggle
31599
31546
  } = props;
31547
+ const isDefiningPoint = !onMove && !onRemove;
31600
31548
  function handleColorChange(newValue) {
31601
31549
  const newProps = {
31602
31550
  color: newValue
31603
31551
  };
31604
31552
 
31605
31553
  // Update the color of the all labels to match the point
31606
- newProps.labels = labels.map(label => _extends({}, label, {
31607
- color: newValue
31608
- }));
31554
+ if (labels) {
31555
+ newProps.labels = labels.map(label => _extends({}, label, {
31556
+ color: newValue
31557
+ }));
31558
+ }
31609
31559
  onChangeProps(newProps);
31610
31560
  }
31611
31561
  function handleCoordChange(newCoord) {
@@ -31616,12 +31566,17 @@ const LockedPointSettings = props => {
31616
31566
  };
31617
31567
 
31618
31568
  // Update the coord by the same amount as the point for all labels
31619
- newProps.labels = labels.map(label => _extends({}, label, {
31620
- coord: [label.coord[0] + xOffset, label.coord[1] + yOffset]
31621
- }));
31569
+ if (labels) {
31570
+ newProps.labels = labels.map(label => _extends({}, label, {
31571
+ coord: [label.coord[0] + xOffset, label.coord[1] + yOffset]
31572
+ }));
31573
+ }
31622
31574
  onChangeProps(newProps);
31623
31575
  }
31624
31576
  function handleLabelChange(updatedLabel, labelIndex) {
31577
+ if (!labels) {
31578
+ return;
31579
+ }
31625
31580
  const updatedLabels = [...labels];
31626
31581
  updatedLabels[labelIndex] = _extends({}, labels[labelIndex], updatedLabel);
31627
31582
  onChangeProps({
@@ -31629,20 +31584,25 @@ const LockedPointSettings = props => {
31629
31584
  });
31630
31585
  }
31631
31586
  function handleLabelRemove(labelIndex) {
31587
+ if (!labels) {
31588
+ return;
31589
+ }
31632
31590
  const updatedLabels = labels.filter((_, index) => index !== labelIndex);
31633
31591
  onChangeProps({
31634
31592
  labels: updatedLabels
31635
31593
  });
31636
31594
  }
31637
31595
  return /*#__PURE__*/React.createElement(PerseusEditorAccordion, {
31638
- expanded: props.expanded,
31639
- onToggle: props.onToggle,
31596
+ expanded: expanded,
31597
+ onToggle: onToggle,
31598
+ containerStyle: isDefiningPoint ? styles$n.definingContainer : undefined,
31599
+ panelStyle: isDefiningPoint ? styles$n.definingPanel : undefined,
31640
31600
  header:
31641
31601
  /*#__PURE__*/
31642
31602
  // Summary: Point, coords, color (filled/open)
31643
31603
  React.createElement(View, {
31644
- style: styles$m.row
31645
- }, /*#__PURE__*/React.createElement(LabelLarge, null, `Point (${coord[0]}, ${coord[1]})`), /*#__PURE__*/React.createElement(Strut, {
31604
+ style: styles$n.row
31605
+ }, /*#__PURE__*/React.createElement(LabelLarge, null, `${headerLabel || "Point"} (${coord[0]}, ${coord[1]})`), /*#__PURE__*/React.createElement(Strut, {
31646
31606
  size: spacing.xSmall_8
31647
31607
  }), /*#__PURE__*/React.createElement(ColorSwatch, {
31648
31608
  color: pointColor,
@@ -31650,12 +31610,17 @@ const LockedPointSettings = props => {
31650
31610
  }))
31651
31611
  }, /*#__PURE__*/React.createElement(CoordinatePairInput, {
31652
31612
  coord: coord,
31653
- style: styles$m.spaceUnder,
31613
+ style: styles$n.spaceUnder,
31654
31614
  onChange: handleCoordChange
31655
- }), /*#__PURE__*/React.createElement(ColorSelect, {
31615
+ }), onTogglePoint && /*#__PURE__*/React.createElement(LabeledSwitch, {
31616
+ label: "show point on graph",
31617
+ checked: !!showPoint,
31618
+ style: showPoint && styles$n.spaceUnder,
31619
+ onChange: onTogglePoint
31620
+ }), (!isDefiningPoint || showPoint) && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(ColorSelect, {
31656
31621
  selectedValue: pointColor,
31657
31622
  onChange: handleColorChange,
31658
- style: styles$m.spaceUnder
31623
+ style: styles$n.spaceUnder
31659
31624
  }), /*#__PURE__*/React.createElement(LabeledSwitch, {
31660
31625
  label: "open point",
31661
31626
  checked: !filled,
@@ -31663,9 +31628,9 @@ const LockedPointSettings = props => {
31663
31628
  onChangeProps({
31664
31629
  filled: !newValue
31665
31630
  });
31666
- },
31667
- style: styles$m.spaceUnder
31668
- }), (flags == null || (_flags$mafs = flags["mafs"]) == null ? void 0 : _flags$mafs["locked-point-labels"]) && /*#__PURE__*/React.createElement(React.Fragment, null, labels.map((label, labelIndex) => /*#__PURE__*/React.createElement(LockedLabelSettings, _extends({}, label, {
31631
+ }
31632
+ })), (!isDefiningPoint && (flags == null || (_flags$mafs = flags["mafs"]) == null ? void 0 : _flags$mafs["locked-point-labels"]) || isDefiningPoint && (flags == null || (_flags$mafs2 = flags["mafs"]) == null ? void 0 : _flags$mafs2["locked-line-labels"])) && /*#__PURE__*/React.createElement(React.Fragment, null, labels == null ? void 0 : labels.map((label, labelIndex) => /*#__PURE__*/React.createElement(LockedLabelSettings, _extends({}, label, {
31633
+ containerStyle: !isDefiningPoint && styles$n.lockedPointLabelContainer,
31669
31634
  expanded: true,
31670
31635
  onChangeProps: newLabel => {
31671
31636
  handleLabelChange(newLabel, labelIndex);
@@ -31677,6 +31642,7 @@ const LockedPointSettings = props => {
31677
31642
  kind: "tertiary",
31678
31643
  startIcon: plusCircle,
31679
31644
  onClick: () => {
31645
+ var _labels$length;
31680
31646
  const newLabel = _extends({}, getDefaultFigureForType("label"), {
31681
31647
  // Place the label at the same position
31682
31648
  // as the point. Add a small offset to
@@ -31684,12 +31650,228 @@ const LockedPointSettings = props => {
31684
31650
  coord: [coord[0] + 0.5,
31685
31651
  // Additional offset for each label so
31686
31652
  // they don't overlap.
31687
- coord[1] - 1 * (labels == null ? void 0 : labels.length)],
31653
+ coord[1] - 1 * ((_labels$length = labels == null ? void 0 : labels.length) != null ? _labels$length : 0)],
31688
31654
  // Default to the same color as the point
31689
31655
  color: pointColor
31690
31656
  });
31691
31657
  onChangeProps({
31692
- labels: [...labels, newLabel]
31658
+ labels: [...(labels != null ? labels : []), newLabel]
31659
+ });
31660
+ },
31661
+ style: styles$n.addButton
31662
+ }, "Add visible label")), onRemove && /*#__PURE__*/React.createElement(LockedFigureSettingsActions, {
31663
+ figureType: props.type,
31664
+ onMove: onMove,
31665
+ onRemove: onRemove
31666
+ }));
31667
+ };
31668
+ const styles$n = StyleSheet.create({
31669
+ definingContainer: {
31670
+ marginTop: spacing.xSmall_8,
31671
+ marginBottom: 0,
31672
+ marginLeft: -spacing.xxxSmall_4,
31673
+ marginRight: -spacing.xxxSmall_4,
31674
+ backgroundColor: color.white
31675
+ },
31676
+ definingPanel: {
31677
+ // Need more space since we don't have the actions' margins.
31678
+ paddingBottom: spacing.xxSmall_6
31679
+ },
31680
+ // A regular point (NOT a defining point) has label
31681
+ // accordions with white backgrounds.
31682
+ lockedPointLabelContainer: {
31683
+ backgroundColor: color.white
31684
+ },
31685
+ row: {
31686
+ flexDirection: "row",
31687
+ alignItems: "center"
31688
+ },
31689
+ spaceUnder: {
31690
+ marginBottom: spacing.xSmall_8
31691
+ },
31692
+ addButton: {
31693
+ alignSelf: "start"
31694
+ }
31695
+ });
31696
+
31697
+ const lengthZeroStr = "The line cannot have length 0.";
31698
+ const LockedLineSettings = props => {
31699
+ var _flags$mafs;
31700
+ const {
31701
+ flags,
31702
+ kind,
31703
+ points,
31704
+ color: lineColor,
31705
+ lineStyle = "solid",
31706
+ showPoint1,
31707
+ showPoint2,
31708
+ labels,
31709
+ onChangeProps,
31710
+ onMove,
31711
+ onRemove
31712
+ } = props;
31713
+ const [point1, point2] = points;
31714
+ const capitalizeKind = kind.charAt(0).toUpperCase() + kind.slice(1);
31715
+ const lineLabel = `${capitalizeKind} (${point1.coord[0]},
31716
+ ${point1.coord[1]}), (${point2.coord[0]}, ${point2.coord[1]})`;
31717
+
31718
+ // Check if the line has length 0.
31719
+ const isInvalid = vector.equal(point1.coord, point2.coord);
31720
+ function handleChangePoint(newPointProps, index) {
31721
+ const newPoints = [...points];
31722
+ newPoints[index] = _extends({}, points[index], newPointProps);
31723
+
31724
+ // Update labels to be centered between the two points,
31725
+ // retaining existing offset.
31726
+ const oldMidpoint = vec.midpoint(points[0].coord, points[1].coord);
31727
+ const newMidpoint = vec.midpoint(newPoints[0].coord, newPoints[1].coord);
31728
+ const offset = [newMidpoint[0] - oldMidpoint[0], newMidpoint[1] - oldMidpoint[1]];
31729
+ const newLabels = labels == null ? void 0 : labels.map((label, labelIndex) => _extends({}, label, {
31730
+ coord: [label.coord[0] + offset[0], label.coord[1] + offset[1]]
31731
+ }));
31732
+ onChangeProps({
31733
+ points: newPoints,
31734
+ labels: newLabels
31735
+ });
31736
+ }
31737
+ function handleColorChange(newColor) {
31738
+ var _point1$labels, _point2$labels;
31739
+ const newLabels = labels == null ? void 0 : labels.map(label => _extends({}, label, {
31740
+ color: newColor
31741
+ }));
31742
+ onChangeProps({
31743
+ color: newColor,
31744
+ // Keep the line's points' colors in sync with the line color.
31745
+ points: [_extends({}, point1, {
31746
+ color: newColor,
31747
+ labels: (_point1$labels = point1.labels) == null ? void 0 : _point1$labels.map(label => _extends({}, label, {
31748
+ color: newColor
31749
+ }))
31750
+ }), _extends({}, point2, {
31751
+ color: newColor,
31752
+ labels: (_point2$labels = point2.labels) == null ? void 0 : _point2$labels.map(label => _extends({}, label, {
31753
+ color: newColor
31754
+ }))
31755
+ })],
31756
+ // Keep the line's labels' colors in sync with the line color.
31757
+ labels: newLabels
31758
+ });
31759
+ }
31760
+ function handleLabelChange(updatedLabel, labelIndex) {
31761
+ if (!labels) {
31762
+ return;
31763
+ }
31764
+ const updatedLabels = [...labels];
31765
+ updatedLabels[labelIndex] = _extends({}, labels[labelIndex], updatedLabel);
31766
+ onChangeProps({
31767
+ labels: updatedLabels
31768
+ });
31769
+ }
31770
+ function handleLabelRemove(labelIndex) {
31771
+ if (!labels) {
31772
+ return;
31773
+ }
31774
+ const updatedLabels = labels.filter((_, index) => index !== labelIndex);
31775
+ onChangeProps({
31776
+ labels: updatedLabels
31777
+ });
31778
+ }
31779
+ return /*#__PURE__*/React.createElement(PerseusEditorAccordion, {
31780
+ expanded: props.expanded,
31781
+ onToggle: props.onToggle,
31782
+ header: /*#__PURE__*/React.createElement(View, {
31783
+ style: styles$m.row
31784
+ }, /*#__PURE__*/React.createElement(LabelLarge, null, lineLabel), /*#__PURE__*/React.createElement(Strut, {
31785
+ size: spacing.xSmall_8
31786
+ }), /*#__PURE__*/React.createElement(LineSwatch, {
31787
+ color: lineColor,
31788
+ lineStyle: lineStyle
31789
+ }))
31790
+ }, /*#__PURE__*/React.createElement(LabelMedium, {
31791
+ tag: "label",
31792
+ style: [styles$m.row, styles$m.spaceUnder]
31793
+ }, "kind", /*#__PURE__*/React.createElement(Strut, {
31794
+ size: spacing.xxxSmall_4
31795
+ }), /*#__PURE__*/React.createElement(SingleSelect, {
31796
+ selectedValue: kind,
31797
+ onChange: value => onChangeProps({
31798
+ kind: value
31799
+ })
31800
+ // Placeholder is required, but never gets used.
31801
+ ,
31802
+ placeholder: ""
31803
+ }, /*#__PURE__*/React.createElement(OptionItem, {
31804
+ value: "line",
31805
+ label: "line"
31806
+ }), /*#__PURE__*/React.createElement(OptionItem, {
31807
+ value: "ray",
31808
+ label: "ray"
31809
+ }), /*#__PURE__*/React.createElement(OptionItem, {
31810
+ value: "segment",
31811
+ label: "segment"
31812
+ }))), /*#__PURE__*/React.createElement(View, {
31813
+ style: styles$m.row
31814
+ }, /*#__PURE__*/React.createElement(ColorSelect, {
31815
+ selectedValue: lineColor,
31816
+ onChange: handleColorChange
31817
+ }), /*#__PURE__*/React.createElement(Strut, {
31818
+ size: spacing.small_12
31819
+ }), /*#__PURE__*/React.createElement(LineStrokeSelect, {
31820
+ selectedValue: lineStyle,
31821
+ onChange: value => onChangeProps({
31822
+ lineStyle: value
31823
+ })
31824
+ })), isInvalid && /*#__PURE__*/React.createElement(LabelMedium, {
31825
+ style: styles$m.errorText
31826
+ }, lengthZeroStr), /*#__PURE__*/React.createElement(LockedPointSettings, _extends({
31827
+ flags: flags,
31828
+ headerLabel: "Point 1",
31829
+ expanded: true,
31830
+ showPoint: showPoint1,
31831
+ error: isInvalid ? lengthZeroStr : null
31832
+ }, point1, {
31833
+ onTogglePoint: newValue => onChangeProps({
31834
+ showPoint1: newValue
31835
+ }),
31836
+ onChangeProps: newProps => handleChangePoint(newProps, 0)
31837
+ })), /*#__PURE__*/React.createElement(LockedPointSettings, _extends({
31838
+ flags: flags,
31839
+ headerLabel: "Point 2",
31840
+ expanded: true,
31841
+ showPoint: showPoint2,
31842
+ error: isInvalid ? lengthZeroStr : null
31843
+ }, point2, {
31844
+ onTogglePoint: newValue => onChangeProps({
31845
+ showPoint2: newValue
31846
+ }),
31847
+ onChangeProps: newProps => handleChangePoint(newProps, 1)
31848
+ })), (flags == null || (_flags$mafs = flags["mafs"]) == null ? void 0 : _flags$mafs["locked-line-labels"]) && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(View, {
31849
+ style: styles$m.horizontalRule
31850
+ }), labels == null ? void 0 : labels.map((label, labelIndex) => /*#__PURE__*/React.createElement(LockedLabelSettings, _extends({}, label, {
31851
+ expanded: true,
31852
+ onChangeProps: newLabel => {
31853
+ handleLabelChange(newLabel, labelIndex);
31854
+ },
31855
+ onRemove: () => {
31856
+ handleLabelRemove(labelIndex);
31857
+ },
31858
+ containerStyle: styles$m.labelContainer
31859
+ }))), /*#__PURE__*/React.createElement(Button, {
31860
+ kind: "tertiary",
31861
+ startIcon: plusCircle,
31862
+ onClick: () => {
31863
+ var _labels$length;
31864
+ // Additional vertical offset for each label so
31865
+ // they don't overlap.
31866
+ const offsetPerLabel = [0, -1];
31867
+ const labelLocation = vec.add(vec.scale(offsetPerLabel, (_labels$length = labels == null ? void 0 : labels.length) != null ? _labels$length : 0), vec.midpoint(points[0].coord, points[1].coord));
31868
+ const newLabel = _extends({}, getDefaultFigureForType("label"), {
31869
+ coord: labelLocation,
31870
+ // Default to the same color as the line
31871
+ color: lineColor
31872
+ });
31873
+ onChangeProps({
31874
+ labels: [...(labels != null ? labels : []), newLabel]
31693
31875
  });
31694
31876
  },
31695
31877
  style: styles$m.addButton
@@ -31701,14 +31883,27 @@ const LockedPointSettings = props => {
31701
31883
  };
31702
31884
  const styles$m = StyleSheet.create({
31703
31885
  row: {
31886
+ display: "flex",
31704
31887
  flexDirection: "row",
31705
31888
  alignItems: "center"
31706
31889
  },
31707
31890
  spaceUnder: {
31708
31891
  marginBottom: spacing.xSmall_8
31709
31892
  },
31893
+ errorText: {
31894
+ color: color.red
31895
+ },
31710
31896
  addButton: {
31711
31897
  alignSelf: "start"
31898
+ },
31899
+ horizontalRule: {
31900
+ marginTop: spacing.small_12,
31901
+ marginBottom: spacing.xxxSmall_4,
31902
+ height: 1,
31903
+ backgroundColor: color.offBlack16
31904
+ },
31905
+ labelContainer: {
31906
+ backgroundColor: color.white
31712
31907
  }
31713
31908
  });
31714
31909
 
@@ -31959,17 +32154,14 @@ const styles$k = StyleSheet.create({
31959
32154
  }
31960
32155
  });
31961
32156
 
31962
- /**
31963
- * LockedVectorSettings is a component that allows the user to edit the
31964
- * settings of specifically a locked vector on the graph.
31965
- *
31966
- * Used in the interactive graph editor's locked figures section.
31967
- */
31968
32157
  const lengthErrorMessage = "The vector cannot have length 0.";
31969
32158
  const LockedVectorSettings = props => {
32159
+ var _flags$mafs;
31970
32160
  const {
32161
+ flags,
31971
32162
  points,
31972
32163
  color: lineColor,
32164
+ labels,
31973
32165
  onChangeProps,
31974
32166
  onMove,
31975
32167
  onRemove
@@ -31983,14 +32175,48 @@ const LockedVectorSettings = props => {
31983
32175
  if (typeof newCoord !== "undefined") {
31984
32176
  const newPoints = [...points];
31985
32177
  newPoints[index] = [...newCoord];
32178
+
32179
+ // Update labels to match the new points
32180
+ const oldMidpoint = vec.midpoint(tail, tip);
32181
+ const newMidpoint = vec.midpoint(newPoints[0], newPoints[1]);
32182
+ const offset = vec.sub(newMidpoint, oldMidpoint);
32183
+ const newLabels = labels == null ? void 0 : labels.map(label => _extends({}, label, {
32184
+ coord: vec.add(label.coord, offset)
32185
+ }));
31986
32186
  onChangeProps({
31987
- points: newPoints
32187
+ points: newPoints,
32188
+ labels: newLabels
31988
32189
  });
31989
32190
  }
31990
32191
  }
31991
32192
  function handleColorChange(newColor) {
31992
- onChangeProps({
32193
+ const newProps = {
32194
+ color: newColor
32195
+ };
32196
+
32197
+ // Update the color of the all labels to match the point
32198
+ newProps.labels = labels == null ? void 0 : labels.map(label => _extends({}, label, {
31993
32199
  color: newColor
32200
+ }));
32201
+ onChangeProps(newProps);
32202
+ }
32203
+ function handleLabelChange(updatedLabel, labelIndex) {
32204
+ if (!labels) {
32205
+ return;
32206
+ }
32207
+ const updatedLabels = [...labels];
32208
+ updatedLabels[labelIndex] = _extends({}, labels[labelIndex], updatedLabel);
32209
+ onChangeProps({
32210
+ labels: updatedLabels
32211
+ });
32212
+ }
32213
+ function handleLabelRemove(labelIndex) {
32214
+ if (!labels) {
32215
+ return;
32216
+ }
32217
+ const updatedLabels = labels.filter((_, index) => index !== labelIndex);
32218
+ onChangeProps({
32219
+ labels: updatedLabels
31994
32220
  });
31995
32221
  }
31996
32222
  return /*#__PURE__*/React.createElement(PerseusEditorAccordion, {
@@ -32039,7 +32265,37 @@ const LockedVectorSettings = props => {
32039
32265
  onChange: newProps => {
32040
32266
  handleChangePoint(newProps, 1);
32041
32267
  }
32042
- })), /*#__PURE__*/React.createElement(LockedFigureSettingsActions, {
32268
+ })), (flags == null || (_flags$mafs = flags["mafs"]) == null ? void 0 : _flags$mafs["locked-vector-labels"]) && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(View, {
32269
+ style: styles$j.horizontalRule
32270
+ }), labels == null ? void 0 : labels.map((label, labelIndex) => /*#__PURE__*/React.createElement(LockedLabelSettings, _extends({}, label, {
32271
+ expanded: true,
32272
+ onChangeProps: newLabel => {
32273
+ handleLabelChange(newLabel, labelIndex);
32274
+ },
32275
+ onRemove: () => {
32276
+ handleLabelRemove(labelIndex);
32277
+ },
32278
+ containerStyle: styles$j.labelContainer
32279
+ }))), /*#__PURE__*/React.createElement(Button, {
32280
+ kind: "tertiary",
32281
+ startIcon: plusCircle,
32282
+ onClick: () => {
32283
+ var _labels$length;
32284
+ // Additional vertical offset for each label so
32285
+ // they don't overlap.
32286
+ const offsetPerLabel = [0, -1];
32287
+ const labelLocation = vec.add(vec.scale(offsetPerLabel, (_labels$length = labels == null ? void 0 : labels.length) != null ? _labels$length : 0), vec.midpoint(tail, tip));
32288
+ const newLabel = _extends({}, getDefaultFigureForType("label"), {
32289
+ coord: labelLocation,
32290
+ // Default to the same color as the vector
32291
+ color: lineColor
32292
+ });
32293
+ onChangeProps({
32294
+ labels: [...(labels != null ? labels : []), newLabel]
32295
+ });
32296
+ },
32297
+ style: styles$j.addButton
32298
+ }, "Add visible label")), /*#__PURE__*/React.createElement(LockedFigureSettingsActions, {
32043
32299
  figureType: props.type,
32044
32300
  onMove: onMove,
32045
32301
  onRemove: onRemove
@@ -32063,6 +32319,18 @@ const styles$j = StyleSheet.create({
32063
32319
  row: {
32064
32320
  flexDirection: "row",
32065
32321
  alignItems: "center"
32322
+ },
32323
+ addButton: {
32324
+ alignSelf: "start"
32325
+ },
32326
+ horizontalRule: {
32327
+ marginTop: spacing.small_12,
32328
+ marginBottom: spacing.xxxSmall_4,
32329
+ height: 1,
32330
+ backgroundColor: color.offBlack16
32331
+ },
32332
+ labelContainer: {
32333
+ backgroundColor: color.white
32066
32334
  }
32067
32335
  });
32068
32336