@khanacademy/perseus-editor 6.10.0 → 6.12.0

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