@khanacademy/perseus-editor 14.4.0 → 14.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/es/index.js CHANGED
@@ -25,6 +25,7 @@ import { useMathInputI18n, createMathField } from '@khanacademy/math-input';
25
25
  import { vector, number } from '@khanacademy/kmath';
26
26
  import { SingleSelect, OptionItem, ActionMenu, ActionItem } from '@khanacademy/wonder-blocks-dropdown';
27
27
  import Banner from '@khanacademy/wonder-blocks-banner';
28
+ import plusCircle from '@phosphor-icons/core/regular/plus-circle.svg';
28
29
  import { AccordionSection } from '@khanacademy/wonder-blocks-accordion';
29
30
  import IconButton from '@khanacademy/wonder-blocks-icon-button';
30
31
  import caretDoubleDownIcon from '@phosphor-icons/core/bold/caret-double-down-bold.svg';
@@ -34,7 +35,6 @@ import caretUpIcon from '@phosphor-icons/core/bold/caret-up-bold.svg';
34
35
  import trashIcon from '@phosphor-icons/core/bold/trash-bold.svg';
35
36
  import copyIcon from '@phosphor-icons/core/assets/regular/copy.svg';
36
37
  import autoPasteIcon from '@phosphor-icons/core/assets/regular/note-pencil.svg';
37
- import plusCircle from '@phosphor-icons/core/regular/plus-circle.svg';
38
38
  import { vec } from 'mafs';
39
39
  import arrowFatDown from '@phosphor-icons/core/regular/arrow-fat-down.svg';
40
40
  import arrowFatLeft from '@phosphor-icons/core/regular/arrow-fat-left.svg';
@@ -45,7 +45,7 @@ import arrowCounterClockwise from '@phosphor-icons/core/bold/arrow-counter-clock
45
45
 
46
46
  // This file is processed by a Rollup plugin (replace) to inject the production
47
47
  const libName = "@khanacademy/perseus-editor";
48
- const libVersion = "14.4.0";
48
+ const libVersion = "14.5.0";
49
49
  addLibraryVersionToPerseusDebug(libName, libVersion);
50
50
 
51
51
  function _extends() {
@@ -21904,8 +21904,7 @@ class IframeContentRenderer extends React.Component {
21904
21904
  this._lastData = data;
21905
21905
 
21906
21906
  // We can't use JSON.stringify/parse for this because the apiOptions
21907
- // includes the functions GroupMetadataEditor, groupAnnotator,
21908
- // onFocusChange, and onInputError.
21907
+ // includes the functions GroupMetadataEditor, groupAnnotator, and onFocusChange.
21909
21908
  // @ts-expect-error - TS2339 - Property 'iframeDataStore' does not exist on type 'Window & typeof globalThis'.
21910
21909
  window.iframeDataStore[this.iframeID] = data;
21911
21910
  frame.contentWindow.postMessage(this.iframeID, "*");
@@ -29688,7 +29687,10 @@ function InteractiveGraphDescription(props) {
29688
29687
  }, "Title", /*#__PURE__*/React.createElement(TextField, {
29689
29688
  value: ariaLabelValue,
29690
29689
  onChange: newValue => onChange({
29691
- fullGraphAriaLabel: newValue
29690
+ fullGraphAriaLabel:
29691
+ // Save as undefined if the new value
29692
+ // is an empty string.
29693
+ newValue || undefined
29692
29694
  }),
29693
29695
  style: styles$F.spaceAbove
29694
29696
  })), /*#__PURE__*/React.createElement(Strut, {
@@ -29700,7 +29702,10 @@ function InteractiveGraphDescription(props) {
29700
29702
  resizeType: "vertical",
29701
29703
  value: ariaDescriptionValue,
29702
29704
  onChange: newValue => onChange({
29703
- fullGraphAriaDescription: newValue
29705
+ fullGraphAriaDescription:
29706
+ // Save as undefined if the new value
29707
+ // is an empty string.
29708
+ newValue || undefined
29704
29709
  }),
29705
29710
  style: styles$F.spaceAbove
29706
29711
  }))));
@@ -30718,15 +30723,205 @@ const styles$t = StyleSheet.create({
30718
30723
  }
30719
30724
  });
30720
30725
 
30726
+ /**
30727
+ * LockedLabelSettings is a component that allows the user to edit the
30728
+ * settings of specifically a locked label on the graph within the
30729
+ * Interactive Graph widget.
30730
+ *
30731
+ * Used in the interactive graph editor's locked figures section.
30732
+ */
30733
+ function LockedLabelSettings(props) {
30734
+ const {
30735
+ type,
30736
+ coord,
30737
+ color: color$1,
30738
+ size,
30739
+ text,
30740
+ expanded,
30741
+ onChangeProps,
30742
+ onMove,
30743
+ onRemove,
30744
+ onToggle,
30745
+ containerStyle
30746
+ } = props;
30747
+ return /*#__PURE__*/React.createElement(PerseusEditorAccordion, {
30748
+ expanded: expanded,
30749
+ onToggle: onToggle,
30750
+ header: /*#__PURE__*/React.createElement(View, {
30751
+ style: [styles$s.row, styles$s.accordionHeaderContainer]
30752
+ }, /*#__PURE__*/React.createElement(LabelLarge, null, "Label (", coord[0], ", ", coord[1], ")"), /*#__PURE__*/React.createElement(Strut, {
30753
+ size: spacing.xSmall_8
30754
+ }), text !== "" && /*#__PURE__*/React.createElement(LabelLarge, {
30755
+ style: [{
30756
+ backgroundColor: color.white,
30757
+ color: lockedFigureColors[color$1]
30758
+ }, styles$s.accordionHeader]
30759
+ }, text)),
30760
+ containerStyle: containerStyle
30761
+ }, /*#__PURE__*/React.createElement(CoordinatePairInput, {
30762
+ coord: coord,
30763
+ onChange: newCoords => {
30764
+ onChangeProps({
30765
+ coord: newCoords
30766
+ });
30767
+ },
30768
+ style: styles$s.spaceUnder
30769
+ }), /*#__PURE__*/React.createElement(LabelMedium, {
30770
+ tag: "label",
30771
+ style: [styles$s.row, styles$s.spaceUnder]
30772
+ }, "TeX", /*#__PURE__*/React.createElement(Strut, {
30773
+ size: spacing.xSmall_8
30774
+ }), /*#__PURE__*/React.createElement(TextField, {
30775
+ value: text,
30776
+ placeholder: "ex. x^2 or \\frac{1}{2}",
30777
+ onChange: newValue => onChangeProps({
30778
+ text: newValue
30779
+ })
30780
+ })), /*#__PURE__*/React.createElement(View, {
30781
+ style: styles$s.row
30782
+ }, onMove && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(ColorSelect, {
30783
+ selectedValue: color$1,
30784
+ onChange: newColor => {
30785
+ onChangeProps({
30786
+ color: newColor
30787
+ });
30788
+ },
30789
+ style: styles$s.spaceUnder
30790
+ }), /*#__PURE__*/React.createElement(Strut, {
30791
+ size: spacing.medium_16
30792
+ })), /*#__PURE__*/React.createElement(LabelMedium, {
30793
+ tag: "label",
30794
+ style: styles$s.row
30795
+ }, "size", /*#__PURE__*/React.createElement(Strut, {
30796
+ size: spacing.xSmall_8
30797
+ }), /*#__PURE__*/React.createElement(SingleSelect, {
30798
+ selectedValue: size,
30799
+ onChange: newValue => onChangeProps({
30800
+ size: newValue
30801
+ })
30802
+ // Placeholder is required, but never gets used since
30803
+ // we have a label for the select.
30804
+ ,
30805
+ placeholder: ""
30806
+ }, /*#__PURE__*/React.createElement(OptionItem, {
30807
+ value: "small",
30808
+ label: "small"
30809
+ }), /*#__PURE__*/React.createElement(OptionItem, {
30810
+ value: "medium",
30811
+ label: "medium"
30812
+ }), /*#__PURE__*/React.createElement(OptionItem, {
30813
+ value: "large",
30814
+ label: "large"
30815
+ })))), /*#__PURE__*/React.createElement(LockedFigureSettingsActions, {
30816
+ figureType: type,
30817
+ onMove: onMove,
30818
+ onRemove: onRemove
30819
+ }));
30820
+ }
30821
+ const styles$s = StyleSheet.create({
30822
+ accordionHeaderContainer: {
30823
+ // Stop the label summary from wrapping.
30824
+ whiteSpace: "nowrap"
30825
+ },
30826
+ accordionHeader: {
30827
+ padding: spacing.xxxSmall_4,
30828
+ marginInlineEnd: spacing.xSmall_8,
30829
+ borderRadius: spacing.xxxSmall_4,
30830
+ textOverflow: "ellipsis",
30831
+ overflow: "hidden"
30832
+ },
30833
+ row: {
30834
+ display: "flex",
30835
+ flexDirection: "row",
30836
+ alignItems: "center",
30837
+ // Allow truncation, stop bleeding over the edge.
30838
+ minWidth: 0
30839
+ },
30840
+ spaceUnder: {
30841
+ marginBottom: spacing.xSmall_8
30842
+ }
30843
+ });
30844
+
30845
+ const DEFAULT_COLOR = "grayH";
30846
+ function getDefaultFigureForType(type) {
30847
+ switch (type) {
30848
+ case "point":
30849
+ return {
30850
+ type: "point",
30851
+ coord: [0, 0],
30852
+ color: DEFAULT_COLOR,
30853
+ filled: true
30854
+ };
30855
+ case "line":
30856
+ return {
30857
+ type: "line",
30858
+ kind: "line",
30859
+ points: [getDefaultFigureForType("point"), _extends({}, getDefaultFigureForType("point"), {
30860
+ coord: [2, 2]
30861
+ })],
30862
+ color: DEFAULT_COLOR,
30863
+ lineStyle: "solid",
30864
+ showPoint1: false,
30865
+ showPoint2: false
30866
+ };
30867
+ case "vector":
30868
+ return {
30869
+ type: "vector",
30870
+ points: [[0, 0], [2, 2]],
30871
+ color: DEFAULT_COLOR
30872
+ };
30873
+ case "ellipse":
30874
+ return {
30875
+ type: "ellipse",
30876
+ center: [0, 0],
30877
+ radius: [1, 1],
30878
+ angle: 0,
30879
+ color: DEFAULT_COLOR,
30880
+ fillStyle: "none",
30881
+ strokeStyle: "solid"
30882
+ };
30883
+ case "polygon":
30884
+ return {
30885
+ type: "polygon",
30886
+ points: [[0, 2], [-1, 0], [1, 0]],
30887
+ color: DEFAULT_COLOR,
30888
+ showVertices: false,
30889
+ fillStyle: "none",
30890
+ strokeStyle: "solid"
30891
+ };
30892
+ case "function":
30893
+ return {
30894
+ type: "function",
30895
+ color: DEFAULT_COLOR,
30896
+ strokeStyle: "solid",
30897
+ equation: "x^2",
30898
+ directionalAxis: "x"
30899
+ };
30900
+ case "label":
30901
+ return {
30902
+ type: "label",
30903
+ coord: [0, 0],
30904
+ text: "",
30905
+ color: DEFAULT_COLOR,
30906
+ size: "medium"
30907
+ };
30908
+ default:
30909
+ throw new UnreachableCaseError(type);
30910
+ }
30911
+ }
30912
+
30721
30913
  const {
30722
30914
  InfoTip: InfoTip$c
30723
30915
  } = components;
30724
30916
  const LockedEllipseSettings = props => {
30917
+ var _flags$mafs;
30725
30918
  const {
30919
+ flags,
30726
30920
  center,
30727
30921
  radius,
30728
30922
  angle,
30729
30923
  color,
30924
+ labels,
30730
30925
  fillStyle,
30731
30926
  strokeStyle,
30732
30927
  expanded,
@@ -30735,9 +30930,47 @@ const LockedEllipseSettings = props => {
30735
30930
  onMove,
30736
30931
  onRemove
30737
30932
  } = props;
30933
+ function handleCenterChange(newCoord) {
30934
+ const xOffset = newCoord[0] - center[0];
30935
+ const yOffset = newCoord[1] - center[1];
30936
+ const newProps = {
30937
+ center: newCoord
30938
+ };
30939
+
30940
+ // Update the coord by the same amount as the point for all labels
30941
+ newProps.labels = labels == null ? void 0 : labels.map(label => _extends({}, label, {
30942
+ coord: [label.coord[0] + xOffset, label.coord[1] + yOffset]
30943
+ }));
30944
+ onChangeProps(newProps);
30945
+ }
30738
30946
  function handleColorChange(newValue) {
30739
- onChangeProps({
30947
+ const newProps = {
30948
+ color: newValue
30949
+ };
30950
+
30951
+ // Update the color of the all labels to match the point
30952
+ newProps.labels = labels == null ? void 0 : labels.map(label => _extends({}, label, {
30740
30953
  color: newValue
30954
+ }));
30955
+ onChangeProps(newProps);
30956
+ }
30957
+ function handleLabelChange(updatedLabel, labelIndex) {
30958
+ if (!labels) {
30959
+ return;
30960
+ }
30961
+ const updatedLabels = [...labels];
30962
+ updatedLabels[labelIndex] = _extends({}, labels[labelIndex], updatedLabel);
30963
+ onChangeProps({
30964
+ labels: updatedLabels
30965
+ });
30966
+ }
30967
+ function handleLabelRemove(labelIndex) {
30968
+ if (!labels) {
30969
+ return;
30970
+ }
30971
+ const updatedLabels = labels.filter((_, index) => index !== labelIndex);
30972
+ onChangeProps({
30973
+ labels: updatedLabels
30741
30974
  });
30742
30975
  }
30743
30976
  return /*#__PURE__*/React.createElement(PerseusEditorAccordion, {
@@ -30747,7 +30980,7 @@ const LockedEllipseSettings = props => {
30747
30980
  /*#__PURE__*/
30748
30981
  // Summary: Ellipse, center, radius, color (opacity, dashed)
30749
30982
  React.createElement(View, {
30750
- style: styles$s.row
30983
+ style: styles$r.row
30751
30984
  }, /*#__PURE__*/React.createElement(LabelLarge, null, `Ellipse (${center[0]}, ${center[1]}), radius ${radius[0]}, ${radius[1]}`), /*#__PURE__*/React.createElement(Strut, {
30752
30985
  size: spacing.xSmall_8
30753
30986
  }), /*#__PURE__*/React.createElement(EllipseSwatch, {
@@ -30756,19 +30989,17 @@ const LockedEllipseSettings = props => {
30756
30989
  strokeStyle: strokeStyle
30757
30990
  }))
30758
30991
  }, /*#__PURE__*/React.createElement(View, {
30759
- style: styles$s.row
30992
+ style: styles$r.row
30760
30993
  }, /*#__PURE__*/React.createElement(CoordinatePairInput, {
30761
30994
  coord: center,
30762
- style: styles$s.spaceUnder,
30763
- onChange: newCoords => onChangeProps({
30764
- center: newCoords
30765
- })
30995
+ style: styles$r.spaceUnder,
30996
+ onChange: handleCenterChange
30766
30997
  }), /*#__PURE__*/React.createElement(View, {
30767
- style: styles$s.spaceUnder
30998
+ style: styles$r.spaceUnder
30768
30999
  }, /*#__PURE__*/React.createElement(InfoTip$c, null, "The coordinates for the center of the ellipse."))), /*#__PURE__*/React.createElement(CoordinatePairInput, {
30769
31000
  coord: radius,
30770
31001
  labels: ["x radius", "y radius"],
30771
- style: styles$s.spaceUnder,
31002
+ style: styles$r.spaceUnder,
30772
31003
  onChange: newCoords => onChangeProps({
30773
31004
  radius: newCoords
30774
31005
  })
@@ -30780,7 +31011,7 @@ const LockedEllipseSettings = props => {
30780
31011
  }), /*#__PURE__*/React.createElement(Strut, {
30781
31012
  size: spacing.xSmall_8
30782
31013
  }), /*#__PURE__*/React.createElement(View, {
30783
- style: [styles$s.row, styles$s.spaceUnder]
31014
+ style: [styles$r.row, styles$r.spaceUnder]
30784
31015
  }, /*#__PURE__*/React.createElement(ColorSelect, {
30785
31016
  selectedValue: color,
30786
31017
  onChange: handleColorChange
@@ -30788,7 +31019,7 @@ const LockedEllipseSettings = props => {
30788
31019
  size: spacing.medium_16
30789
31020
  }), /*#__PURE__*/React.createElement(LabelMedium, {
30790
31021
  tag: "label",
30791
- style: [styles$s.row, styles$s.truncatedWidth]
31022
+ style: [styles$r.row, styles$r.truncatedWidth]
30792
31023
  }, "fill", /*#__PURE__*/React.createElement(Strut, {
30793
31024
  size: spacing.xxSmall_6
30794
31025
  }), /*#__PURE__*/React.createElement(SingleSelect, {
@@ -30808,13 +31039,40 @@ const LockedEllipseSettings = props => {
30808
31039
  onChange: value => onChangeProps({
30809
31040
  strokeStyle: value
30810
31041
  })
30811
- }), /*#__PURE__*/React.createElement(LockedFigureSettingsActions, {
31042
+ }), (flags == null || (_flags$mafs = flags["mafs"]) == null ? void 0 : _flags$mafs["locked-ellipse-labels"]) && /*#__PURE__*/React.createElement(React.Fragment, null, labels == null ? void 0 : labels.map((label, labelIndex) => /*#__PURE__*/React.createElement(LockedLabelSettings, _extends({}, label, {
31043
+ expanded: true,
31044
+ onChangeProps: newLabel => {
31045
+ handleLabelChange(newLabel, labelIndex);
31046
+ },
31047
+ onRemove: () => {
31048
+ handleLabelRemove(labelIndex);
31049
+ },
31050
+ containerStyle: styles$r.labelContainer
31051
+ }))), /*#__PURE__*/React.createElement(Button, {
31052
+ kind: "tertiary",
31053
+ startIcon: plusCircle,
31054
+ onClick: () => {
31055
+ var _labels$length;
31056
+ const newLabel = _extends({}, getDefaultFigureForType("label"), {
31057
+ coord: [center[0],
31058
+ // Additional vertical offset for each
31059
+ // label so they don't overlap.
31060
+ center[1] - ((_labels$length = labels == null ? void 0 : labels.length) != null ? _labels$length : 0)],
31061
+ // Default to the same color as the ellipse
31062
+ color: color
31063
+ });
31064
+ onChangeProps({
31065
+ labels: [...(labels != null ? labels : []), newLabel]
31066
+ });
31067
+ },
31068
+ style: styles$r.addButton
31069
+ }, "Add visible label")), /*#__PURE__*/React.createElement(LockedFigureSettingsActions, {
30812
31070
  figureType: props.type,
30813
31071
  onMove: onMove,
30814
31072
  onRemove: onRemove
30815
31073
  }));
30816
31074
  };
30817
- const styles$s = StyleSheet.create({
31075
+ const styles$r = StyleSheet.create({
30818
31076
  row: {
30819
31077
  display: "flex",
30820
31078
  flexDirection: "row",
@@ -30826,6 +31084,12 @@ const styles$s = StyleSheet.create({
30826
31084
  truncatedWidth: {
30827
31085
  // Allow truncation, stop bleeding over the edge.
30828
31086
  minWidth: 0
31087
+ },
31088
+ addButton: {
31089
+ alignSelf: "start"
31090
+ },
31091
+ labelContainer: {
31092
+ backgroundColor: color.white
30829
31093
  }
30830
31094
  });
30831
31095
 
@@ -30835,15 +31099,15 @@ const LineSwatch = props => {
30835
31099
  lineStyle
30836
31100
  } = props;
30837
31101
  return /*#__PURE__*/React.createElement(View, {
30838
- style: styles$r.container
31102
+ style: styles$q.container
30839
31103
  }, /*#__PURE__*/React.createElement(View, {
30840
31104
  "aria-label": `${color}, ${lineStyle}`,
30841
- style: [styles$r.lineSwatch, {
31105
+ style: [styles$q.lineSwatch, {
30842
31106
  border: `5px ${lineStyle} ${lockedFigureColors[color]}`
30843
31107
  }]
30844
31108
  }));
30845
31109
  };
30846
- const styles$r = StyleSheet.create({
31110
+ const styles$q = StyleSheet.create({
30847
31111
  container: {
30848
31112
  backgroundColor: color.white,
30849
31113
  justifyContent: "center",
@@ -30861,19 +31125,16 @@ const examples = {
30861
31125
  trigonometric: ["sin(x) * 3", "arctan(2x) + 4"]
30862
31126
  };
30863
31127
 
30864
- /**
30865
- * LockedFunctionSettings is a component that allows the user to edit the
30866
- * settings of specifically a locked function on the graph.
30867
- *
30868
- * Used in the interactive graph editor's locked figures section.
30869
- */
30870
31128
  const LockedFunctionSettings = props => {
31129
+ var _flags$mafs;
30871
31130
  const {
31131
+ flags,
30872
31132
  color: lineColor,
30873
31133
  strokeStyle,
30874
31134
  equation,
30875
31135
  directionalAxis,
30876
31136
  domain,
31137
+ labels,
30877
31138
  onChangeProps,
30878
31139
  onMove,
30879
31140
  onRemove
@@ -30924,13 +31185,43 @@ const LockedFunctionSettings = props => {
30924
31185
  const exampleCategories = Object.keys(examples);
30925
31186
  const exampleCategorySelected = exampleCategory !== "";
30926
31187
  const exampleContent = exampleCategorySelected ? examples[exampleCategory] : ["Select category to see example equations"];
31188
+ function handleColorChange(newValue) {
31189
+ const newProps = {
31190
+ color: newValue
31191
+ };
31192
+
31193
+ // Update the color of the all labels to match the point
31194
+ newProps.labels = labels == null ? void 0 : labels.map(label => _extends({}, label, {
31195
+ color: newValue
31196
+ }));
31197
+ onChangeProps(newProps);
31198
+ }
31199
+ function handleLabelChange(updatedLabel, labelIndex) {
31200
+ if (!labels) {
31201
+ return;
31202
+ }
31203
+ const updatedLabels = [...labels];
31204
+ updatedLabels[labelIndex] = _extends({}, labels[labelIndex], updatedLabel);
31205
+ onChangeProps({
31206
+ labels: updatedLabels
31207
+ });
31208
+ }
31209
+ function handleLabelRemove(labelIndex) {
31210
+ if (!labels) {
31211
+ return;
31212
+ }
31213
+ const updatedLabels = labels.filter((_, index) => index !== labelIndex);
31214
+ onChangeProps({
31215
+ labels: updatedLabels
31216
+ });
31217
+ }
30927
31218
  return /*#__PURE__*/React.createElement(PerseusEditorAccordion, {
30928
31219
  expanded: props.expanded,
30929
31220
  onToggle: props.onToggle,
30930
31221
  header: /*#__PURE__*/React.createElement(View, {
30931
- style: styles$q.row
31222
+ style: styles$p.row
30932
31223
  }, /*#__PURE__*/React.createElement(LabelLarge, {
30933
- style: styles$q.accordionHeader
31224
+ style: styles$p.accordionHeader
30934
31225
  }, lineLabel), /*#__PURE__*/React.createElement(Strut, {
30935
31226
  size: spacing.xSmall_8
30936
31227
  }), /*#__PURE__*/React.createElement(LineSwatch, {
@@ -30938,12 +31229,10 @@ const LockedFunctionSettings = props => {
30938
31229
  lineStyle: strokeStyle
30939
31230
  }))
30940
31231
  }, /*#__PURE__*/React.createElement(View, {
30941
- style: [styles$q.row, styles$q.spaceUnder]
31232
+ style: [styles$p.row, styles$p.spaceUnder]
30942
31233
  }, /*#__PURE__*/React.createElement(ColorSelect, {
30943
31234
  selectedValue: lineColor,
30944
- onChange: newValue => {
30945
- handlePropChange("color", newValue);
30946
- }
31235
+ onChange: handleColorChange
30947
31236
  }), /*#__PURE__*/React.createElement(Strut, {
30948
31237
  size: spacing.small_12
30949
31238
  }), /*#__PURE__*/React.createElement(LineStrokeSelect, {
@@ -30952,14 +31241,14 @@ const LockedFunctionSettings = props => {
30952
31241
  handlePropChange("strokeStyle", newValue);
30953
31242
  }
30954
31243
  })), /*#__PURE__*/React.createElement(View, {
30955
- style: [styles$q.row, styles$q.rowSpace]
31244
+ style: [styles$p.row, styles$p.rowSpace]
30956
31245
  }, /*#__PURE__*/React.createElement(SingleSelect, {
30957
31246
  selectedValue: directionalAxis,
30958
31247
  onChange: newValue => {
30959
31248
  handlePropChange("directionalAxis", newValue);
30960
31249
  },
30961
31250
  "aria-label": "equation prefix",
30962
- style: [styles$q.dropdownLabel, styles$q.axisMenu]
31251
+ style: [styles$p.dropdownLabel, styles$p.axisMenu]
30963
31252
  // Placeholder is required, but never gets used.
30964
31253
  ,
30965
31254
  placeholder: ""
@@ -30978,17 +31267,17 @@ const LockedFunctionSettings = props => {
30978
31267
  onChange: newValue => {
30979
31268
  handlePropChange("equation", newValue);
30980
31269
  },
30981
- style: [styles$q.textField]
31270
+ style: [styles$p.textField]
30982
31271
  })), /*#__PURE__*/React.createElement(View, {
30983
- style: [styles$q.row, styles$q.rowSpace]
31272
+ style: [styles$p.row, styles$p.rowSpace]
30984
31273
  }, /*#__PURE__*/React.createElement(LabelMedium, {
30985
31274
  tag: "label",
30986
- style: [styles$q.dropdownLabel, styles$q.domainMin]
31275
+ style: [styles$p.dropdownLabel, styles$p.domainMin]
30987
31276
  }, `${domainRangeText} min`, /*#__PURE__*/React.createElement(Strut, {
30988
31277
  size: spacing.xxSmall_6
30989
31278
  }), /*#__PURE__*/React.createElement(TextField, {
30990
31279
  type: "number",
30991
- style: styles$q.domainMinField,
31280
+ style: styles$p.domainMinField,
30992
31281
  value: domainEntries[0],
30993
31282
  onChange: newValue => {
30994
31283
  handleDomainChange(0, newValue);
@@ -30998,12 +31287,12 @@ const LockedFunctionSettings = props => {
30998
31287
  }), /*#__PURE__*/React.createElement(LabelMedium, {
30999
31288
  tag: "label",
31000
31289
  "aria-label": `${domainRangeText} max`,
31001
- style: [styles$q.dropdownLabel, styles$q.domainMax]
31290
+ style: [styles$p.dropdownLabel, styles$p.domainMax]
31002
31291
  }, "max", /*#__PURE__*/React.createElement(Strut, {
31003
31292
  size: spacing.xxSmall_6
31004
31293
  }), /*#__PURE__*/React.createElement(TextField, {
31005
31294
  type: "number",
31006
- style: styles$q.domainMaxField,
31295
+ style: styles$p.domainMaxField,
31007
31296
  value: domainEntries[1],
31008
31297
  onChange: newValue => {
31009
31298
  handleDomainChange(1, newValue);
@@ -31011,11 +31300,11 @@ const LockedFunctionSettings = props => {
31011
31300
  }))), /*#__PURE__*/React.createElement(PerseusEditorAccordion, {
31012
31301
  header: /*#__PURE__*/React.createElement(LabelLarge, null, "Example Functions"),
31013
31302
  expanded: false,
31014
- containerStyle: styles$q.exampleWorkspace,
31015
- panelStyle: styles$q.exampleAccordionPanel
31303
+ containerStyle: styles$p.exampleWorkspace,
31304
+ panelStyle: styles$p.exampleAccordionPanel
31016
31305
  }, /*#__PURE__*/React.createElement(LabelMedium, {
31017
31306
  tag: "label",
31018
- style: styles$q.dropdownLabel
31307
+ style: styles$p.dropdownLabel
31019
31308
  }, "Choose a category", /*#__PURE__*/React.createElement(Strut, {
31020
31309
  size: spacing.xxSmall_6
31021
31310
  }), /*#__PURE__*/React.createElement(SingleSelect, {
@@ -31029,13 +31318,41 @@ const LockedFunctionSettings = props => {
31029
31318
  label: category
31030
31319
  });
31031
31320
  }))), exampleCategorySelected && /*#__PURE__*/React.createElement("ul", {
31032
- className: css(styles$q.exampleContainer)
31321
+ className: css(styles$p.exampleContainer)
31033
31322
  }, exampleContent.map((example, index) => /*#__PURE__*/React.createElement(ExampleItem, {
31034
31323
  category: exampleCategory,
31035
31324
  example: example,
31036
31325
  index: index,
31037
31326
  pasteEquationFn: handlePropChange
31038
- })))), /*#__PURE__*/React.createElement(LockedFigureSettingsActions, {
31327
+ })))), (flags == null || (_flags$mafs = flags["mafs"]) == null ? void 0 : _flags$mafs["locked-function-labels"]) && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(View, {
31328
+ style: styles$p.horizontalRule
31329
+ }), labels == null ? void 0 : labels.map((label, labelIndex) => /*#__PURE__*/React.createElement(LockedLabelSettings, _extends({}, label, {
31330
+ expanded: true,
31331
+ onChangeProps: newLabel => {
31332
+ handleLabelChange(newLabel, labelIndex);
31333
+ },
31334
+ onRemove: () => {
31335
+ handleLabelRemove(labelIndex);
31336
+ },
31337
+ containerStyle: styles$p.labelContainer
31338
+ }))), /*#__PURE__*/React.createElement(Button, {
31339
+ kind: "tertiary",
31340
+ startIcon: plusCircle,
31341
+ onClick: () => {
31342
+ var _labels$length;
31343
+ const newLabel = _extends({}, getDefaultFigureForType("label"), {
31344
+ // Vertical offset for each label so they
31345
+ // don't overlap.
31346
+ coord: [0, -((_labels$length = labels == null ? void 0 : labels.length) != null ? _labels$length : 0)],
31347
+ // Default to the same color as the function
31348
+ color: lineColor
31349
+ });
31350
+ onChangeProps({
31351
+ labels: [...(labels != null ? labels : []), newLabel]
31352
+ });
31353
+ },
31354
+ style: styles$p.addButton
31355
+ }, "Add visible label")), /*#__PURE__*/React.createElement(LockedFigureSettingsActions, {
31039
31356
  figureType: props.type,
31040
31357
  onMove: onMove,
31041
31358
  onRemove: onRemove
@@ -31051,29 +31368,29 @@ const ExampleItem = props => {
31051
31368
  const exampleId = useId();
31052
31369
  return /*#__PURE__*/React.createElement("li", {
31053
31370
  key: `${category}-${index}`,
31054
- className: css(styles$q.exampleRow)
31371
+ className: css(styles$p.exampleRow)
31055
31372
  }, /*#__PURE__*/React.createElement(IconButton, {
31056
31373
  icon: autoPasteIcon,
31057
31374
  "aria-label": "paste example",
31058
31375
  "aria-describedby": exampleId,
31059
31376
  onClick: () => pasteEquationFn("equation", example),
31060
31377
  size: "medium",
31061
- style: styles$q.copyPasteButton
31378
+ style: styles$p.copyPasteButton
31062
31379
  }), /*#__PURE__*/React.createElement(IconButton, {
31063
31380
  icon: copyIcon,
31064
31381
  "aria-label": "copy example",
31065
31382
  "aria-describedby": exampleId,
31066
31383
  onClick: () => navigator.clipboard.writeText(example),
31067
31384
  size: "medium",
31068
- style: styles$q.copyPasteButton
31385
+ style: styles$p.copyPasteButton
31069
31386
  }), /*#__PURE__*/React.createElement(Strut, {
31070
31387
  size: spacing.xxxSmall_4
31071
31388
  }), /*#__PURE__*/React.createElement(View, {
31072
- style: styles$q.exampleContent,
31389
+ style: styles$p.exampleContent,
31073
31390
  id: exampleId
31074
31391
  }, example));
31075
31392
  };
31076
- const styles$q = StyleSheet.create({
31393
+ const styles$p = StyleSheet.create({
31077
31394
  accordionHeader: {
31078
31395
  textOverflow: "ellipsis",
31079
31396
  // A maximum width needs to be specified in order for the ellipsis to work.
@@ -31164,125 +31481,19 @@ const styles$q = StyleSheet.create({
31164
31481
  },
31165
31482
  textField: {
31166
31483
  flexGrow: "1"
31167
- }
31168
- });
31169
-
31170
- /**
31171
- * LockedLabelSettings is a component that allows the user to edit the
31172
- * settings of specifically a locked label on the graph within the
31173
- * Interactive Graph widget.
31174
- *
31175
- * Used in the interactive graph editor's locked figures section.
31176
- */
31177
- function LockedLabelSettings(props) {
31178
- const {
31179
- type,
31180
- coord,
31181
- color: color$1,
31182
- size,
31183
- text,
31184
- expanded,
31185
- onChangeProps,
31186
- onMove,
31187
- onRemove,
31188
- onToggle,
31189
- containerStyle
31190
- } = props;
31191
- return /*#__PURE__*/React.createElement(PerseusEditorAccordion, {
31192
- expanded: expanded,
31193
- onToggle: onToggle,
31194
- header: /*#__PURE__*/React.createElement(View, {
31195
- style: [styles$p.row, styles$p.accordionHeaderContainer]
31196
- }, /*#__PURE__*/React.createElement(LabelLarge, null, "Label (", coord[0], ", ", coord[1], ")"), /*#__PURE__*/React.createElement(Strut, {
31197
- size: spacing.xSmall_8
31198
- }), text !== "" && /*#__PURE__*/React.createElement(LabelLarge, {
31199
- style: [{
31200
- backgroundColor: color.white,
31201
- color: lockedFigureColors[color$1]
31202
- }, styles$p.accordionHeader]
31203
- }, text)),
31204
- containerStyle: containerStyle
31205
- }, /*#__PURE__*/React.createElement(CoordinatePairInput, {
31206
- coord: coord,
31207
- onChange: newCoords => {
31208
- onChangeProps({
31209
- coord: newCoords
31210
- });
31211
- },
31212
- style: styles$p.spaceUnder
31213
- }), /*#__PURE__*/React.createElement(LabelMedium, {
31214
- tag: "label",
31215
- style: [styles$p.row, styles$p.spaceUnder]
31216
- }, "TeX", /*#__PURE__*/React.createElement(Strut, {
31217
- size: spacing.xSmall_8
31218
- }), /*#__PURE__*/React.createElement(TextField, {
31219
- value: text,
31220
- placeholder: "ex. x^2 or \\frac{1}{2}",
31221
- onChange: newValue => onChangeProps({
31222
- text: newValue
31223
- })
31224
- })), /*#__PURE__*/React.createElement(View, {
31225
- style: styles$p.row
31226
- }, onMove && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(ColorSelect, {
31227
- selectedValue: color$1,
31228
- onChange: newColor => {
31229
- onChangeProps({
31230
- color: newColor
31231
- });
31232
- },
31233
- style: styles$p.spaceUnder
31234
- }), /*#__PURE__*/React.createElement(Strut, {
31235
- size: spacing.medium_16
31236
- })), /*#__PURE__*/React.createElement(LabelMedium, {
31237
- tag: "label",
31238
- style: styles$p.row
31239
- }, "size", /*#__PURE__*/React.createElement(Strut, {
31240
- size: spacing.xSmall_8
31241
- }), /*#__PURE__*/React.createElement(SingleSelect, {
31242
- selectedValue: size,
31243
- onChange: newValue => onChangeProps({
31244
- size: newValue
31245
- })
31246
- // Placeholder is required, but never gets used since
31247
- // we have a label for the select.
31248
- ,
31249
- placeholder: ""
31250
- }, /*#__PURE__*/React.createElement(OptionItem, {
31251
- value: "small",
31252
- label: "small"
31253
- }), /*#__PURE__*/React.createElement(OptionItem, {
31254
- value: "medium",
31255
- label: "medium"
31256
- }), /*#__PURE__*/React.createElement(OptionItem, {
31257
- value: "large",
31258
- label: "large"
31259
- })))), /*#__PURE__*/React.createElement(LockedFigureSettingsActions, {
31260
- figureType: type,
31261
- onMove: onMove,
31262
- onRemove: onRemove
31263
- }));
31264
- }
31265
- const styles$p = StyleSheet.create({
31266
- accordionHeaderContainer: {
31267
- // Stop the label summary from wrapping.
31268
- whiteSpace: "nowrap"
31269
31484
  },
31270
- accordionHeader: {
31271
- padding: spacing.xxxSmall_4,
31272
- marginInlineEnd: spacing.xSmall_8,
31273
- borderRadius: spacing.xxxSmall_4,
31274
- textOverflow: "ellipsis",
31275
- overflow: "hidden"
31485
+ // Label settings styles
31486
+ addButton: {
31487
+ alignSelf: "start"
31276
31488
  },
31277
- row: {
31278
- display: "flex",
31279
- flexDirection: "row",
31280
- alignItems: "center",
31281
- // Allow truncation, stop bleeding over the edge.
31282
- minWidth: 0
31489
+ horizontalRule: {
31490
+ marginTop: spacing.small_12,
31491
+ marginBottom: spacing.xxxSmall_4,
31492
+ height: 1,
31493
+ backgroundColor: color.offBlack16
31283
31494
  },
31284
- spaceUnder: {
31285
- marginBottom: spacing.xSmall_8
31495
+ labelContainer: {
31496
+ backgroundColor: color.white
31286
31497
  }
31287
31498
  });
31288
31499
 
@@ -31315,76 +31526,6 @@ const styles$o = StyleSheet.create({
31315
31526
  }
31316
31527
  });
31317
31528
 
31318
- const DEFAULT_COLOR = "grayH";
31319
- function getDefaultFigureForType(type) {
31320
- switch (type) {
31321
- case "point":
31322
- return {
31323
- type: "point",
31324
- coord: [0, 0],
31325
- color: DEFAULT_COLOR,
31326
- filled: true,
31327
- labels: []
31328
- };
31329
- case "line":
31330
- return {
31331
- type: "line",
31332
- kind: "line",
31333
- points: [getDefaultFigureForType("point"), _extends({}, getDefaultFigureForType("point"), {
31334
- coord: [2, 2]
31335
- })],
31336
- color: DEFAULT_COLOR,
31337
- lineStyle: "solid",
31338
- showPoint1: false,
31339
- showPoint2: false,
31340
- labels: []
31341
- };
31342
- case "vector":
31343
- return {
31344
- type: "vector",
31345
- points: [[0, 0], [2, 2]],
31346
- color: DEFAULT_COLOR
31347
- };
31348
- case "ellipse":
31349
- return {
31350
- type: "ellipse",
31351
- center: [0, 0],
31352
- radius: [1, 1],
31353
- angle: 0,
31354
- color: DEFAULT_COLOR,
31355
- fillStyle: "none",
31356
- strokeStyle: "solid"
31357
- };
31358
- case "polygon":
31359
- return {
31360
- type: "polygon",
31361
- points: [[0, 2], [-1, 0], [1, 0]],
31362
- color: DEFAULT_COLOR,
31363
- showVertices: false,
31364
- fillStyle: "none",
31365
- strokeStyle: "solid"
31366
- };
31367
- case "function":
31368
- return {
31369
- type: "function",
31370
- color: DEFAULT_COLOR,
31371
- strokeStyle: "solid",
31372
- equation: "x^2",
31373
- directionalAxis: "x"
31374
- };
31375
- case "label":
31376
- return {
31377
- type: "label",
31378
- coord: [0, 0],
31379
- text: "",
31380
- color: DEFAULT_COLOR,
31381
- size: "medium"
31382
- };
31383
- default:
31384
- throw new UnreachableCaseError(type);
31385
- }
31386
- }
31387
-
31388
31529
  const LockedPointSettings = props => {
31389
31530
  var _flags$mafs, _flags$mafs2;
31390
31531
  const {
@@ -31410,9 +31551,11 @@ const LockedPointSettings = props => {
31410
31551
  };
31411
31552
 
31412
31553
  // Update the color of the all labels to match the point
31413
- newProps.labels = labels.map(label => _extends({}, label, {
31414
- color: newValue
31415
- }));
31554
+ if (labels) {
31555
+ newProps.labels = labels.map(label => _extends({}, label, {
31556
+ color: newValue
31557
+ }));
31558
+ }
31416
31559
  onChangeProps(newProps);
31417
31560
  }
31418
31561
  function handleCoordChange(newCoord) {
@@ -31423,12 +31566,17 @@ const LockedPointSettings = props => {
31423
31566
  };
31424
31567
 
31425
31568
  // Update the coord by the same amount as the point for all labels
31426
- newProps.labels = labels.map(label => _extends({}, label, {
31427
- coord: [label.coord[0] + xOffset, label.coord[1] + yOffset]
31428
- }));
31569
+ if (labels) {
31570
+ newProps.labels = labels.map(label => _extends({}, label, {
31571
+ coord: [label.coord[0] + xOffset, label.coord[1] + yOffset]
31572
+ }));
31573
+ }
31429
31574
  onChangeProps(newProps);
31430
31575
  }
31431
31576
  function handleLabelChange(updatedLabel, labelIndex) {
31577
+ if (!labels) {
31578
+ return;
31579
+ }
31432
31580
  const updatedLabels = [...labels];
31433
31581
  updatedLabels[labelIndex] = _extends({}, labels[labelIndex], updatedLabel);
31434
31582
  onChangeProps({
@@ -31436,6 +31584,9 @@ const LockedPointSettings = props => {
31436
31584
  });
31437
31585
  }
31438
31586
  function handleLabelRemove(labelIndex) {
31587
+ if (!labels) {
31588
+ return;
31589
+ }
31439
31590
  const updatedLabels = labels.filter((_, index) => index !== labelIndex);
31440
31591
  onChangeProps({
31441
31592
  labels: updatedLabels
@@ -31478,7 +31629,7 @@ const LockedPointSettings = props => {
31478
31629
  filled: !newValue
31479
31630
  });
31480
31631
  }
31481
- })), (!isDefiningPoint && (flags == null || (_flags$mafs = flags["mafs"]) == null ? void 0 : _flags$mafs["locked-point-labels"]) || isDefiningPoint && (flags == null || (_flags$mafs2 = flags["mafs"]) == null ? void 0 : _flags$mafs2["locked-line-labels"])) && /*#__PURE__*/React.createElement(React.Fragment, null, labels.map((label, labelIndex) => /*#__PURE__*/React.createElement(LockedLabelSettings, _extends({}, label, {
31632
+ })), (!isDefiningPoint && (flags == null || (_flags$mafs = flags["mafs"]) == null ? void 0 : _flags$mafs["locked-point-labels"]) || isDefiningPoint && (flags == null || (_flags$mafs2 = flags["mafs"]) == null ? void 0 : _flags$mafs2["locked-line-labels"])) && /*#__PURE__*/React.createElement(React.Fragment, null, labels == null ? void 0 : labels.map((label, labelIndex) => /*#__PURE__*/React.createElement(LockedLabelSettings, _extends({}, label, {
31482
31633
  containerStyle: !isDefiningPoint && styles$n.lockedPointLabelContainer,
31483
31634
  expanded: true,
31484
31635
  onChangeProps: newLabel => {
@@ -31491,6 +31642,7 @@ const LockedPointSettings = props => {
31491
31642
  kind: "tertiary",
31492
31643
  startIcon: plusCircle,
31493
31644
  onClick: () => {
31645
+ var _labels$length;
31494
31646
  const newLabel = _extends({}, getDefaultFigureForType("label"), {
31495
31647
  // Place the label at the same position
31496
31648
  // as the point. Add a small offset to
@@ -31498,12 +31650,12 @@ const LockedPointSettings = props => {
31498
31650
  coord: [coord[0] + 0.5,
31499
31651
  // Additional offset for each label so
31500
31652
  // they don't overlap.
31501
- coord[1] - 1 * (labels == null ? void 0 : labels.length)],
31653
+ coord[1] - 1 * ((_labels$length = labels == null ? void 0 : labels.length) != null ? _labels$length : 0)],
31502
31654
  // Default to the same color as the point
31503
31655
  color: pointColor
31504
31656
  });
31505
31657
  onChangeProps({
31506
- labels: [...labels, newLabel]
31658
+ labels: [...(labels != null ? labels : []), newLabel]
31507
31659
  });
31508
31660
  },
31509
31661
  style: styles$n.addButton
@@ -31574,7 +31726,7 @@ const LockedLineSettings = props => {
31574
31726
  const oldMidpoint = vec.midpoint(points[0].coord, points[1].coord);
31575
31727
  const newMidpoint = vec.midpoint(newPoints[0].coord, newPoints[1].coord);
31576
31728
  const offset = [newMidpoint[0] - oldMidpoint[0], newMidpoint[1] - oldMidpoint[1]];
31577
- const newLabels = labels.map((label, labelIndex) => _extends({}, label, {
31729
+ const newLabels = labels == null ? void 0 : labels.map((label, labelIndex) => _extends({}, label, {
31578
31730
  coord: [label.coord[0] + offset[0], label.coord[1] + offset[1]]
31579
31731
  }));
31580
31732
  onChangeProps({
@@ -31583,7 +31735,8 @@ const LockedLineSettings = props => {
31583
31735
  });
31584
31736
  }
31585
31737
  function handleColorChange(newColor) {
31586
- const newLabels = labels.map(label => _extends({}, label, {
31738
+ var _point1$labels, _point2$labels;
31739
+ const newLabels = labels == null ? void 0 : labels.map(label => _extends({}, label, {
31587
31740
  color: newColor
31588
31741
  }));
31589
31742
  onChangeProps({
@@ -31591,12 +31744,12 @@ const LockedLineSettings = props => {
31591
31744
  // Keep the line's points' colors in sync with the line color.
31592
31745
  points: [_extends({}, point1, {
31593
31746
  color: newColor,
31594
- labels: point1.labels.map(label => _extends({}, label, {
31747
+ labels: (_point1$labels = point1.labels) == null ? void 0 : _point1$labels.map(label => _extends({}, label, {
31595
31748
  color: newColor
31596
31749
  }))
31597
31750
  }), _extends({}, point2, {
31598
31751
  color: newColor,
31599
- labels: point2.labels.map(label => _extends({}, label, {
31752
+ labels: (_point2$labels = point2.labels) == null ? void 0 : _point2$labels.map(label => _extends({}, label, {
31600
31753
  color: newColor
31601
31754
  }))
31602
31755
  })],
@@ -31605,6 +31758,9 @@ const LockedLineSettings = props => {
31605
31758
  });
31606
31759
  }
31607
31760
  function handleLabelChange(updatedLabel, labelIndex) {
31761
+ if (!labels) {
31762
+ return;
31763
+ }
31608
31764
  const updatedLabels = [...labels];
31609
31765
  updatedLabels[labelIndex] = _extends({}, labels[labelIndex], updatedLabel);
31610
31766
  onChangeProps({
@@ -31612,6 +31768,9 @@ const LockedLineSettings = props => {
31612
31768
  });
31613
31769
  }
31614
31770
  function handleLabelRemove(labelIndex) {
31771
+ if (!labels) {
31772
+ return;
31773
+ }
31615
31774
  const updatedLabels = labels.filter((_, index) => index !== labelIndex);
31616
31775
  onChangeProps({
31617
31776
  labels: updatedLabels
@@ -31688,7 +31847,7 @@ const LockedLineSettings = props => {
31688
31847
  onChangeProps: newProps => handleChangePoint(newProps, 1)
31689
31848
  })), (flags == null || (_flags$mafs = flags["mafs"]) == null ? void 0 : _flags$mafs["locked-line-labels"]) && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(View, {
31690
31849
  style: styles$m.horizontalRule
31691
- }), labels.map((label, labelIndex) => /*#__PURE__*/React.createElement(LockedLabelSettings, _extends({}, label, {
31850
+ }), labels == null ? void 0 : labels.map((label, labelIndex) => /*#__PURE__*/React.createElement(LockedLabelSettings, _extends({}, label, {
31692
31851
  expanded: true,
31693
31852
  onChangeProps: newLabel => {
31694
31853
  handleLabelChange(newLabel, labelIndex);
@@ -31701,17 +31860,18 @@ const LockedLineSettings = props => {
31701
31860
  kind: "tertiary",
31702
31861
  startIcon: plusCircle,
31703
31862
  onClick: () => {
31863
+ var _labels$length;
31704
31864
  // Additional vertical offset for each label so
31705
31865
  // they don't overlap.
31706
31866
  const offsetPerLabel = [0, -1];
31707
- const labelLocation = vec.add(vec.scale(offsetPerLabel, labels.length), vec.midpoint(points[0].coord, points[1].coord));
31867
+ const labelLocation = vec.add(vec.scale(offsetPerLabel, (_labels$length = labels == null ? void 0 : labels.length) != null ? _labels$length : 0), vec.midpoint(points[0].coord, points[1].coord));
31708
31868
  const newLabel = _extends({}, getDefaultFigureForType("label"), {
31709
31869
  coord: labelLocation,
31710
- // Default to the same color as the point
31870
+ // Default to the same color as the line
31711
31871
  color: lineColor
31712
31872
  });
31713
31873
  onChangeProps({
31714
- labels: [...labels, newLabel]
31874
+ labels: [...(labels != null ? labels : []), newLabel]
31715
31875
  });
31716
31876
  },
31717
31877
  style: styles$m.addButton
@@ -31994,17 +32154,14 @@ const styles$k = StyleSheet.create({
31994
32154
  }
31995
32155
  });
31996
32156
 
31997
- /**
31998
- * LockedVectorSettings is a component that allows the user to edit the
31999
- * settings of specifically a locked vector on the graph.
32000
- *
32001
- * Used in the interactive graph editor's locked figures section.
32002
- */
32003
32157
  const lengthErrorMessage = "The vector cannot have length 0.";
32004
32158
  const LockedVectorSettings = props => {
32159
+ var _flags$mafs;
32005
32160
  const {
32161
+ flags,
32006
32162
  points,
32007
32163
  color: lineColor,
32164
+ labels,
32008
32165
  onChangeProps,
32009
32166
  onMove,
32010
32167
  onRemove
@@ -32018,14 +32175,48 @@ const LockedVectorSettings = props => {
32018
32175
  if (typeof newCoord !== "undefined") {
32019
32176
  const newPoints = [...points];
32020
32177
  newPoints[index] = [...newCoord];
32178
+
32179
+ // Update labels to match the new points
32180
+ const oldMidpoint = vec.midpoint(tail, tip);
32181
+ const newMidpoint = vec.midpoint(newPoints[0], newPoints[1]);
32182
+ const offset = vec.sub(newMidpoint, oldMidpoint);
32183
+ const newLabels = labels == null ? void 0 : labels.map(label => _extends({}, label, {
32184
+ coord: vec.add(label.coord, offset)
32185
+ }));
32021
32186
  onChangeProps({
32022
- points: newPoints
32187
+ points: newPoints,
32188
+ labels: newLabels
32023
32189
  });
32024
32190
  }
32025
32191
  }
32026
32192
  function handleColorChange(newColor) {
32027
- onChangeProps({
32193
+ const newProps = {
32194
+ color: newColor
32195
+ };
32196
+
32197
+ // Update the color of the all labels to match the point
32198
+ newProps.labels = labels == null ? void 0 : labels.map(label => _extends({}, label, {
32028
32199
  color: newColor
32200
+ }));
32201
+ onChangeProps(newProps);
32202
+ }
32203
+ function handleLabelChange(updatedLabel, labelIndex) {
32204
+ if (!labels) {
32205
+ return;
32206
+ }
32207
+ const updatedLabels = [...labels];
32208
+ updatedLabels[labelIndex] = _extends({}, labels[labelIndex], updatedLabel);
32209
+ onChangeProps({
32210
+ labels: updatedLabels
32211
+ });
32212
+ }
32213
+ function handleLabelRemove(labelIndex) {
32214
+ if (!labels) {
32215
+ return;
32216
+ }
32217
+ const updatedLabels = labels.filter((_, index) => index !== labelIndex);
32218
+ onChangeProps({
32219
+ labels: updatedLabels
32029
32220
  });
32030
32221
  }
32031
32222
  return /*#__PURE__*/React.createElement(PerseusEditorAccordion, {
@@ -32074,7 +32265,37 @@ const LockedVectorSettings = props => {
32074
32265
  onChange: newProps => {
32075
32266
  handleChangePoint(newProps, 1);
32076
32267
  }
32077
- })), /*#__PURE__*/React.createElement(LockedFigureSettingsActions, {
32268
+ })), (flags == null || (_flags$mafs = flags["mafs"]) == null ? void 0 : _flags$mafs["locked-vector-labels"]) && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(View, {
32269
+ style: styles$j.horizontalRule
32270
+ }), labels == null ? void 0 : labels.map((label, labelIndex) => /*#__PURE__*/React.createElement(LockedLabelSettings, _extends({}, label, {
32271
+ expanded: true,
32272
+ onChangeProps: newLabel => {
32273
+ handleLabelChange(newLabel, labelIndex);
32274
+ },
32275
+ onRemove: () => {
32276
+ handleLabelRemove(labelIndex);
32277
+ },
32278
+ containerStyle: styles$j.labelContainer
32279
+ }))), /*#__PURE__*/React.createElement(Button, {
32280
+ kind: "tertiary",
32281
+ startIcon: plusCircle,
32282
+ onClick: () => {
32283
+ var _labels$length;
32284
+ // Additional vertical offset for each label so
32285
+ // they don't overlap.
32286
+ const offsetPerLabel = [0, -1];
32287
+ const labelLocation = vec.add(vec.scale(offsetPerLabel, (_labels$length = labels == null ? void 0 : labels.length) != null ? _labels$length : 0), vec.midpoint(tail, tip));
32288
+ const newLabel = _extends({}, getDefaultFigureForType("label"), {
32289
+ coord: labelLocation,
32290
+ // Default to the same color as the vector
32291
+ color: lineColor
32292
+ });
32293
+ onChangeProps({
32294
+ labels: [...(labels != null ? labels : []), newLabel]
32295
+ });
32296
+ },
32297
+ style: styles$j.addButton
32298
+ }, "Add visible label")), /*#__PURE__*/React.createElement(LockedFigureSettingsActions, {
32078
32299
  figureType: props.type,
32079
32300
  onMove: onMove,
32080
32301
  onRemove: onRemove
@@ -32098,6 +32319,18 @@ const styles$j = StyleSheet.create({
32098
32319
  row: {
32099
32320
  flexDirection: "row",
32100
32321
  alignItems: "center"
32322
+ },
32323
+ addButton: {
32324
+ alignSelf: "start"
32325
+ },
32326
+ horizontalRule: {
32327
+ marginTop: spacing.small_12,
32328
+ marginBottom: spacing.xxxSmall_4,
32329
+ height: 1,
32330
+ backgroundColor: color.offBlack16
32331
+ },
32332
+ labelContainer: {
32333
+ backgroundColor: color.white
32101
32334
  }
32102
32335
  });
32103
32336