@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/index.js CHANGED
@@ -27,6 +27,7 @@ var mathInput = require('@khanacademy/math-input');
27
27
  var kmath = require('@khanacademy/kmath');
28
28
  var wonderBlocksDropdown = require('@khanacademy/wonder-blocks-dropdown');
29
29
  var Banner = require('@khanacademy/wonder-blocks-banner');
30
+ var plusCircle = require('@phosphor-icons/core/regular/plus-circle.svg');
30
31
  var wonderBlocksAccordion = require('@khanacademy/wonder-blocks-accordion');
31
32
  var IconButton = require('@khanacademy/wonder-blocks-icon-button');
32
33
  var caretDoubleDownIcon = require('@phosphor-icons/core/bold/caret-double-down-bold.svg');
@@ -36,7 +37,6 @@ var caretUpIcon = require('@phosphor-icons/core/bold/caret-up-bold.svg');
36
37
  var trashIcon = require('@phosphor-icons/core/bold/trash-bold.svg');
37
38
  var copyIcon = require('@phosphor-icons/core/assets/regular/copy.svg');
38
39
  var autoPasteIcon = require('@phosphor-icons/core/assets/regular/note-pencil.svg');
39
- var plusCircle = require('@phosphor-icons/core/regular/plus-circle.svg');
40
40
  var mafs = require('mafs');
41
41
  var arrowFatDown = require('@phosphor-icons/core/regular/arrow-fat-down.svg');
42
42
  var arrowFatLeft = require('@phosphor-icons/core/regular/arrow-fat-left.svg');
@@ -78,6 +78,7 @@ var ReactDOM__default = /*#__PURE__*/_interopDefaultLegacy(ReactDOM);
78
78
  var KAS__namespace = /*#__PURE__*/_interopNamespace(KAS);
79
79
  var Button__default = /*#__PURE__*/_interopDefaultLegacy(Button);
80
80
  var Banner__default = /*#__PURE__*/_interopDefaultLegacy(Banner);
81
+ var plusCircle__default = /*#__PURE__*/_interopDefaultLegacy(plusCircle);
81
82
  var IconButton__default = /*#__PURE__*/_interopDefaultLegacy(IconButton);
82
83
  var caretDoubleDownIcon__default = /*#__PURE__*/_interopDefaultLegacy(caretDoubleDownIcon);
83
84
  var caretDoubleUpIcon__default = /*#__PURE__*/_interopDefaultLegacy(caretDoubleUpIcon);
@@ -86,7 +87,6 @@ var caretUpIcon__default = /*#__PURE__*/_interopDefaultLegacy(caretUpIcon);
86
87
  var trashIcon__default = /*#__PURE__*/_interopDefaultLegacy(trashIcon);
87
88
  var copyIcon__default = /*#__PURE__*/_interopDefaultLegacy(copyIcon);
88
89
  var autoPasteIcon__default = /*#__PURE__*/_interopDefaultLegacy(autoPasteIcon);
89
- var plusCircle__default = /*#__PURE__*/_interopDefaultLegacy(plusCircle);
90
90
  var arrowFatDown__default = /*#__PURE__*/_interopDefaultLegacy(arrowFatDown);
91
91
  var arrowFatLeft__default = /*#__PURE__*/_interopDefaultLegacy(arrowFatLeft);
92
92
  var arrowFatRight__default = /*#__PURE__*/_interopDefaultLegacy(arrowFatRight);
@@ -96,7 +96,7 @@ var arrowCounterClockwise__default = /*#__PURE__*/_interopDefaultLegacy(arrowCou
96
96
 
97
97
  // This file is processed by a Rollup plugin (replace) to inject the production
98
98
  const libName = "@khanacademy/perseus-editor";
99
- const libVersion = "14.4.0";
99
+ const libVersion = "14.5.0";
100
100
  perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
101
101
 
102
102
  function _extends() {
@@ -21943,8 +21943,7 @@ class IframeContentRenderer extends React__namespace.Component {
21943
21943
  this._lastData = data;
21944
21944
 
21945
21945
  // We can't use JSON.stringify/parse for this because the apiOptions
21946
- // includes the functions GroupMetadataEditor, groupAnnotator,
21947
- // onFocusChange, and onInputError.
21946
+ // includes the functions GroupMetadataEditor, groupAnnotator, and onFocusChange.
21948
21947
  // @ts-expect-error - TS2339 - Property 'iframeDataStore' does not exist on type 'Window & typeof globalThis'.
21949
21948
  window.iframeDataStore[this.iframeID] = data;
21950
21949
  frame.contentWindow.postMessage(this.iframeID, "*");
@@ -29777,7 +29776,10 @@ function InteractiveGraphDescription(props) {
29777
29776
  }, "Title", /*#__PURE__*/React__namespace.createElement(wonderBlocksForm.TextField, {
29778
29777
  value: ariaLabelValue,
29779
29778
  onChange: newValue => onChange({
29780
- fullGraphAriaLabel: newValue
29779
+ fullGraphAriaLabel:
29780
+ // Save as undefined if the new value
29781
+ // is an empty string.
29782
+ newValue || undefined
29781
29783
  }),
29782
29784
  style: styles$F.spaceAbove
29783
29785
  })), /*#__PURE__*/React__namespace.createElement(Strut, {
@@ -29789,7 +29791,10 @@ function InteractiveGraphDescription(props) {
29789
29791
  resizeType: "vertical",
29790
29792
  value: ariaDescriptionValue,
29791
29793
  onChange: newValue => onChange({
29792
- fullGraphAriaDescription: newValue
29794
+ fullGraphAriaDescription:
29795
+ // Save as undefined if the new value
29796
+ // is an empty string.
29797
+ newValue || undefined
29793
29798
  }),
29794
29799
  style: styles$F.spaceAbove
29795
29800
  }))));
@@ -30824,15 +30829,205 @@ const styles$t = aphrodite.StyleSheet.create({
30824
30829
  }
30825
30830
  });
30826
30831
 
30832
+ /**
30833
+ * LockedLabelSettings is a component that allows the user to edit the
30834
+ * settings of specifically a locked label on the graph within the
30835
+ * Interactive Graph widget.
30836
+ *
30837
+ * Used in the interactive graph editor's locked figures section.
30838
+ */
30839
+ function LockedLabelSettings(props) {
30840
+ const {
30841
+ type,
30842
+ coord,
30843
+ color,
30844
+ size,
30845
+ text,
30846
+ expanded,
30847
+ onChangeProps,
30848
+ onMove,
30849
+ onRemove,
30850
+ onToggle,
30851
+ containerStyle
30852
+ } = props;
30853
+ return /*#__PURE__*/React__namespace.createElement(PerseusEditorAccordion, {
30854
+ expanded: expanded,
30855
+ onToggle: onToggle,
30856
+ header: /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
30857
+ style: [styles$s.row, styles$s.accordionHeaderContainer]
30858
+ }, /*#__PURE__*/React__namespace.createElement(wonderBlocksTypography.LabelLarge, null, "Label (", coord[0], ", ", coord[1], ")"), /*#__PURE__*/React__namespace.createElement(Strut, {
30859
+ size: wonderBlocksTokens.spacing.xSmall_8
30860
+ }), text !== "" && /*#__PURE__*/React__namespace.createElement(wonderBlocksTypography.LabelLarge, {
30861
+ style: [{
30862
+ backgroundColor: wonderBlocksTokens.color.white,
30863
+ color: perseus.lockedFigureColors[color]
30864
+ }, styles$s.accordionHeader]
30865
+ }, text)),
30866
+ containerStyle: containerStyle
30867
+ }, /*#__PURE__*/React__namespace.createElement(CoordinatePairInput, {
30868
+ coord: coord,
30869
+ onChange: newCoords => {
30870
+ onChangeProps({
30871
+ coord: newCoords
30872
+ });
30873
+ },
30874
+ style: styles$s.spaceUnder
30875
+ }), /*#__PURE__*/React__namespace.createElement(wonderBlocksTypography.LabelMedium, {
30876
+ tag: "label",
30877
+ style: [styles$s.row, styles$s.spaceUnder]
30878
+ }, "TeX", /*#__PURE__*/React__namespace.createElement(Strut, {
30879
+ size: wonderBlocksTokens.spacing.xSmall_8
30880
+ }), /*#__PURE__*/React__namespace.createElement(wonderBlocksForm.TextField, {
30881
+ value: text,
30882
+ placeholder: "ex. x^2 or \\frac{1}{2}",
30883
+ onChange: newValue => onChangeProps({
30884
+ text: newValue
30885
+ })
30886
+ })), /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
30887
+ style: styles$s.row
30888
+ }, onMove && /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(ColorSelect, {
30889
+ selectedValue: color,
30890
+ onChange: newColor => {
30891
+ onChangeProps({
30892
+ color: newColor
30893
+ });
30894
+ },
30895
+ style: styles$s.spaceUnder
30896
+ }), /*#__PURE__*/React__namespace.createElement(Strut, {
30897
+ size: wonderBlocksTokens.spacing.medium_16
30898
+ })), /*#__PURE__*/React__namespace.createElement(wonderBlocksTypography.LabelMedium, {
30899
+ tag: "label",
30900
+ style: styles$s.row
30901
+ }, "size", /*#__PURE__*/React__namespace.createElement(Strut, {
30902
+ size: wonderBlocksTokens.spacing.xSmall_8
30903
+ }), /*#__PURE__*/React__namespace.createElement(wonderBlocksDropdown.SingleSelect, {
30904
+ selectedValue: size,
30905
+ onChange: newValue => onChangeProps({
30906
+ size: newValue
30907
+ })
30908
+ // Placeholder is required, but never gets used since
30909
+ // we have a label for the select.
30910
+ ,
30911
+ placeholder: ""
30912
+ }, /*#__PURE__*/React__namespace.createElement(wonderBlocksDropdown.OptionItem, {
30913
+ value: "small",
30914
+ label: "small"
30915
+ }), /*#__PURE__*/React__namespace.createElement(wonderBlocksDropdown.OptionItem, {
30916
+ value: "medium",
30917
+ label: "medium"
30918
+ }), /*#__PURE__*/React__namespace.createElement(wonderBlocksDropdown.OptionItem, {
30919
+ value: "large",
30920
+ label: "large"
30921
+ })))), /*#__PURE__*/React__namespace.createElement(LockedFigureSettingsActions, {
30922
+ figureType: type,
30923
+ onMove: onMove,
30924
+ onRemove: onRemove
30925
+ }));
30926
+ }
30927
+ const styles$s = aphrodite.StyleSheet.create({
30928
+ accordionHeaderContainer: {
30929
+ // Stop the label summary from wrapping.
30930
+ whiteSpace: "nowrap"
30931
+ },
30932
+ accordionHeader: {
30933
+ padding: wonderBlocksTokens.spacing.xxxSmall_4,
30934
+ marginInlineEnd: wonderBlocksTokens.spacing.xSmall_8,
30935
+ borderRadius: wonderBlocksTokens.spacing.xxxSmall_4,
30936
+ textOverflow: "ellipsis",
30937
+ overflow: "hidden"
30938
+ },
30939
+ row: {
30940
+ display: "flex",
30941
+ flexDirection: "row",
30942
+ alignItems: "center",
30943
+ // Allow truncation, stop bleeding over the edge.
30944
+ minWidth: 0
30945
+ },
30946
+ spaceUnder: {
30947
+ marginBottom: wonderBlocksTokens.spacing.xSmall_8
30948
+ }
30949
+ });
30950
+
30951
+ const DEFAULT_COLOR = "grayH";
30952
+ function getDefaultFigureForType(type) {
30953
+ switch (type) {
30954
+ case "point":
30955
+ return {
30956
+ type: "point",
30957
+ coord: [0, 0],
30958
+ color: DEFAULT_COLOR,
30959
+ filled: true
30960
+ };
30961
+ case "line":
30962
+ return {
30963
+ type: "line",
30964
+ kind: "line",
30965
+ points: [getDefaultFigureForType("point"), {
30966
+ ...getDefaultFigureForType("point"),
30967
+ coord: [2, 2]
30968
+ }],
30969
+ color: DEFAULT_COLOR,
30970
+ lineStyle: "solid",
30971
+ showPoint1: false,
30972
+ showPoint2: false
30973
+ };
30974
+ case "vector":
30975
+ return {
30976
+ type: "vector",
30977
+ points: [[0, 0], [2, 2]],
30978
+ color: DEFAULT_COLOR
30979
+ };
30980
+ case "ellipse":
30981
+ return {
30982
+ type: "ellipse",
30983
+ center: [0, 0],
30984
+ radius: [1, 1],
30985
+ angle: 0,
30986
+ color: DEFAULT_COLOR,
30987
+ fillStyle: "none",
30988
+ strokeStyle: "solid"
30989
+ };
30990
+ case "polygon":
30991
+ return {
30992
+ type: "polygon",
30993
+ points: [[0, 2], [-1, 0], [1, 0]],
30994
+ color: DEFAULT_COLOR,
30995
+ showVertices: false,
30996
+ fillStyle: "none",
30997
+ strokeStyle: "solid"
30998
+ };
30999
+ case "function":
31000
+ return {
31001
+ type: "function",
31002
+ color: DEFAULT_COLOR,
31003
+ strokeStyle: "solid",
31004
+ equation: "x^2",
31005
+ directionalAxis: "x"
31006
+ };
31007
+ case "label":
31008
+ return {
31009
+ type: "label",
31010
+ coord: [0, 0],
31011
+ text: "",
31012
+ color: DEFAULT_COLOR,
31013
+ size: "medium"
31014
+ };
31015
+ default:
31016
+ throw new wonderStuffCore.UnreachableCaseError(type);
31017
+ }
31018
+ }
31019
+
30827
31020
  const {
30828
31021
  InfoTip: InfoTip$c
30829
31022
  } = perseus.components;
30830
31023
  const LockedEllipseSettings = props => {
30831
31024
  const {
31025
+ flags,
30832
31026
  center,
30833
31027
  radius,
30834
31028
  angle,
30835
31029
  color,
31030
+ labels,
30836
31031
  fillStyle,
30837
31032
  strokeStyle,
30838
31033
  expanded,
@@ -30841,9 +31036,52 @@ const LockedEllipseSettings = props => {
30841
31036
  onMove,
30842
31037
  onRemove
30843
31038
  } = props;
31039
+ function handleCenterChange(newCoord) {
31040
+ const xOffset = newCoord[0] - center[0];
31041
+ const yOffset = newCoord[1] - center[1];
31042
+ const newProps = {
31043
+ center: newCoord
31044
+ };
31045
+
31046
+ // Update the coord by the same amount as the point for all labels
31047
+ newProps.labels = labels?.map(label => ({
31048
+ ...label,
31049
+ coord: [label.coord[0] + xOffset, label.coord[1] + yOffset]
31050
+ }));
31051
+ onChangeProps(newProps);
31052
+ }
30844
31053
  function handleColorChange(newValue) {
30845
- onChangeProps({
31054
+ const newProps = {
31055
+ color: newValue
31056
+ };
31057
+
31058
+ // Update the color of the all labels to match the point
31059
+ newProps.labels = labels?.map(label => ({
31060
+ ...label,
30846
31061
  color: newValue
31062
+ }));
31063
+ onChangeProps(newProps);
31064
+ }
31065
+ function handleLabelChange(updatedLabel, labelIndex) {
31066
+ if (!labels) {
31067
+ return;
31068
+ }
31069
+ const updatedLabels = [...labels];
31070
+ updatedLabels[labelIndex] = {
31071
+ ...labels[labelIndex],
31072
+ ...updatedLabel
31073
+ };
31074
+ onChangeProps({
31075
+ labels: updatedLabels
31076
+ });
31077
+ }
31078
+ function handleLabelRemove(labelIndex) {
31079
+ if (!labels) {
31080
+ return;
31081
+ }
31082
+ const updatedLabels = labels.filter((_, index) => index !== labelIndex);
31083
+ onChangeProps({
31084
+ labels: updatedLabels
30847
31085
  });
30848
31086
  }
30849
31087
  return /*#__PURE__*/React__namespace.createElement(PerseusEditorAccordion, {
@@ -30853,7 +31091,7 @@ const LockedEllipseSettings = props => {
30853
31091
  /*#__PURE__*/
30854
31092
  // Summary: Ellipse, center, radius, color (opacity, dashed)
30855
31093
  React__namespace.createElement(wonderBlocksCore.View, {
30856
- style: styles$s.row
31094
+ style: styles$r.row
30857
31095
  }, /*#__PURE__*/React__namespace.createElement(wonderBlocksTypography.LabelLarge, null, `Ellipse (${center[0]}, ${center[1]}), radius ${radius[0]}, ${radius[1]}`), /*#__PURE__*/React__namespace.createElement(Strut, {
30858
31096
  size: wonderBlocksTokens.spacing.xSmall_8
30859
31097
  }), /*#__PURE__*/React__namespace.createElement(EllipseSwatch, {
@@ -30862,19 +31100,17 @@ const LockedEllipseSettings = props => {
30862
31100
  strokeStyle: strokeStyle
30863
31101
  }))
30864
31102
  }, /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
30865
- style: styles$s.row
31103
+ style: styles$r.row
30866
31104
  }, /*#__PURE__*/React__namespace.createElement(CoordinatePairInput, {
30867
31105
  coord: center,
30868
- style: styles$s.spaceUnder,
30869
- onChange: newCoords => onChangeProps({
30870
- center: newCoords
30871
- })
31106
+ style: styles$r.spaceUnder,
31107
+ onChange: handleCenterChange
30872
31108
  }), /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
30873
- style: styles$s.spaceUnder
31109
+ style: styles$r.spaceUnder
30874
31110
  }, /*#__PURE__*/React__namespace.createElement(InfoTip$c, null, "The coordinates for the center of the ellipse."))), /*#__PURE__*/React__namespace.createElement(CoordinatePairInput, {
30875
31111
  coord: radius,
30876
31112
  labels: ["x radius", "y radius"],
30877
- style: styles$s.spaceUnder,
31113
+ style: styles$r.spaceUnder,
30878
31114
  onChange: newCoords => onChangeProps({
30879
31115
  radius: newCoords
30880
31116
  })
@@ -30886,7 +31122,7 @@ const LockedEllipseSettings = props => {
30886
31122
  }), /*#__PURE__*/React__namespace.createElement(Strut, {
30887
31123
  size: wonderBlocksTokens.spacing.xSmall_8
30888
31124
  }), /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
30889
- style: [styles$s.row, styles$s.spaceUnder]
31125
+ style: [styles$r.row, styles$r.spaceUnder]
30890
31126
  }, /*#__PURE__*/React__namespace.createElement(ColorSelect, {
30891
31127
  selectedValue: color,
30892
31128
  onChange: handleColorChange
@@ -30894,7 +31130,7 @@ const LockedEllipseSettings = props => {
30894
31130
  size: wonderBlocksTokens.spacing.medium_16
30895
31131
  }), /*#__PURE__*/React__namespace.createElement(wonderBlocksTypography.LabelMedium, {
30896
31132
  tag: "label",
30897
- style: [styles$s.row, styles$s.truncatedWidth]
31133
+ style: [styles$r.row, styles$r.truncatedWidth]
30898
31134
  }, "fill", /*#__PURE__*/React__namespace.createElement(Strut, {
30899
31135
  size: wonderBlocksTokens.spacing.xxSmall_6
30900
31136
  }), /*#__PURE__*/React__namespace.createElement(wonderBlocksDropdown.SingleSelect, {
@@ -30914,13 +31150,40 @@ const LockedEllipseSettings = props => {
30914
31150
  onChange: value => onChangeProps({
30915
31151
  strokeStyle: value
30916
31152
  })
30917
- }), /*#__PURE__*/React__namespace.createElement(LockedFigureSettingsActions, {
31153
+ }), flags?.["mafs"]?.["locked-ellipse-labels"] && /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, labels?.map((label, labelIndex) => /*#__PURE__*/React__namespace.createElement(LockedLabelSettings, _extends({}, label, {
31154
+ expanded: true,
31155
+ onChangeProps: newLabel => {
31156
+ handleLabelChange(newLabel, labelIndex);
31157
+ },
31158
+ onRemove: () => {
31159
+ handleLabelRemove(labelIndex);
31160
+ },
31161
+ containerStyle: styles$r.labelContainer
31162
+ }))), /*#__PURE__*/React__namespace.createElement(Button__default["default"], {
31163
+ kind: "tertiary",
31164
+ startIcon: plusCircle__default["default"],
31165
+ onClick: () => {
31166
+ const newLabel = {
31167
+ ...getDefaultFigureForType("label"),
31168
+ coord: [center[0],
31169
+ // Additional vertical offset for each
31170
+ // label so they don't overlap.
31171
+ center[1] - (labels?.length ?? 0)],
31172
+ // Default to the same color as the ellipse
31173
+ color: color
31174
+ };
31175
+ onChangeProps({
31176
+ labels: [...(labels ?? []), newLabel]
31177
+ });
31178
+ },
31179
+ style: styles$r.addButton
31180
+ }, "Add visible label")), /*#__PURE__*/React__namespace.createElement(LockedFigureSettingsActions, {
30918
31181
  figureType: props.type,
30919
31182
  onMove: onMove,
30920
31183
  onRemove: onRemove
30921
31184
  }));
30922
31185
  };
30923
- const styles$s = aphrodite.StyleSheet.create({
31186
+ const styles$r = aphrodite.StyleSheet.create({
30924
31187
  row: {
30925
31188
  display: "flex",
30926
31189
  flexDirection: "row",
@@ -30932,24 +31195,30 @@ const styles$s = aphrodite.StyleSheet.create({
30932
31195
  truncatedWidth: {
30933
31196
  // Allow truncation, stop bleeding over the edge.
30934
31197
  minWidth: 0
30935
- }
30936
- });
30937
-
30938
- const LineSwatch = props => {
31198
+ },
31199
+ addButton: {
31200
+ alignSelf: "start"
31201
+ },
31202
+ labelContainer: {
31203
+ backgroundColor: wonderBlocksTokens.color.white
31204
+ }
31205
+ });
31206
+
31207
+ const LineSwatch = props => {
30939
31208
  const {
30940
31209
  color,
30941
31210
  lineStyle
30942
31211
  } = props;
30943
31212
  return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
30944
- style: styles$r.container
31213
+ style: styles$q.container
30945
31214
  }, /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
30946
31215
  "aria-label": `${color}, ${lineStyle}`,
30947
- style: [styles$r.lineSwatch, {
31216
+ style: [styles$q.lineSwatch, {
30948
31217
  border: `5px ${lineStyle} ${perseus.lockedFigureColors[color]}`
30949
31218
  }]
30950
31219
  }));
30951
31220
  };
30952
- const styles$r = aphrodite.StyleSheet.create({
31221
+ const styles$q = aphrodite.StyleSheet.create({
30953
31222
  container: {
30954
31223
  backgroundColor: wonderBlocksTokens.color.white,
30955
31224
  justifyContent: "center",
@@ -30967,19 +31236,15 @@ const examples = {
30967
31236
  trigonometric: ["sin(x) * 3", "arctan(2x) + 4"]
30968
31237
  };
30969
31238
 
30970
- /**
30971
- * LockedFunctionSettings is a component that allows the user to edit the
30972
- * settings of specifically a locked function on the graph.
30973
- *
30974
- * Used in the interactive graph editor's locked figures section.
30975
- */
30976
31239
  const LockedFunctionSettings = props => {
30977
31240
  const {
31241
+ flags,
30978
31242
  color: lineColor,
30979
31243
  strokeStyle,
30980
31244
  equation,
30981
31245
  directionalAxis,
30982
31246
  domain,
31247
+ labels,
30983
31248
  onChangeProps,
30984
31249
  onMove,
30985
31250
  onRemove
@@ -31030,13 +31295,47 @@ const LockedFunctionSettings = props => {
31030
31295
  const exampleCategories = Object.keys(examples);
31031
31296
  const exampleCategorySelected = exampleCategory !== "";
31032
31297
  const exampleContent = exampleCategorySelected ? examples[exampleCategory] : ["Select category to see example equations"];
31298
+ function handleColorChange(newValue) {
31299
+ const newProps = {
31300
+ color: newValue
31301
+ };
31302
+
31303
+ // Update the color of the all labels to match the point
31304
+ newProps.labels = labels?.map(label => ({
31305
+ ...label,
31306
+ color: newValue
31307
+ }));
31308
+ onChangeProps(newProps);
31309
+ }
31310
+ function handleLabelChange(updatedLabel, labelIndex) {
31311
+ if (!labels) {
31312
+ return;
31313
+ }
31314
+ const updatedLabels = [...labels];
31315
+ updatedLabels[labelIndex] = {
31316
+ ...labels[labelIndex],
31317
+ ...updatedLabel
31318
+ };
31319
+ onChangeProps({
31320
+ labels: updatedLabels
31321
+ });
31322
+ }
31323
+ function handleLabelRemove(labelIndex) {
31324
+ if (!labels) {
31325
+ return;
31326
+ }
31327
+ const updatedLabels = labels.filter((_, index) => index !== labelIndex);
31328
+ onChangeProps({
31329
+ labels: updatedLabels
31330
+ });
31331
+ }
31033
31332
  return /*#__PURE__*/React__namespace.createElement(PerseusEditorAccordion, {
31034
31333
  expanded: props.expanded,
31035
31334
  onToggle: props.onToggle,
31036
31335
  header: /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
31037
- style: styles$q.row
31336
+ style: styles$p.row
31038
31337
  }, /*#__PURE__*/React__namespace.createElement(wonderBlocksTypography.LabelLarge, {
31039
- style: styles$q.accordionHeader
31338
+ style: styles$p.accordionHeader
31040
31339
  }, lineLabel), /*#__PURE__*/React__namespace.createElement(Strut, {
31041
31340
  size: wonderBlocksTokens.spacing.xSmall_8
31042
31341
  }), /*#__PURE__*/React__namespace.createElement(LineSwatch, {
@@ -31044,12 +31343,10 @@ const LockedFunctionSettings = props => {
31044
31343
  lineStyle: strokeStyle
31045
31344
  }))
31046
31345
  }, /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
31047
- style: [styles$q.row, styles$q.spaceUnder]
31346
+ style: [styles$p.row, styles$p.spaceUnder]
31048
31347
  }, /*#__PURE__*/React__namespace.createElement(ColorSelect, {
31049
31348
  selectedValue: lineColor,
31050
- onChange: newValue => {
31051
- handlePropChange("color", newValue);
31052
- }
31349
+ onChange: handleColorChange
31053
31350
  }), /*#__PURE__*/React__namespace.createElement(Strut, {
31054
31351
  size: wonderBlocksTokens.spacing.small_12
31055
31352
  }), /*#__PURE__*/React__namespace.createElement(LineStrokeSelect, {
@@ -31058,14 +31355,14 @@ const LockedFunctionSettings = props => {
31058
31355
  handlePropChange("strokeStyle", newValue);
31059
31356
  }
31060
31357
  })), /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
31061
- style: [styles$q.row, styles$q.rowSpace]
31358
+ style: [styles$p.row, styles$p.rowSpace]
31062
31359
  }, /*#__PURE__*/React__namespace.createElement(wonderBlocksDropdown.SingleSelect, {
31063
31360
  selectedValue: directionalAxis,
31064
31361
  onChange: newValue => {
31065
31362
  handlePropChange("directionalAxis", newValue);
31066
31363
  },
31067
31364
  "aria-label": "equation prefix",
31068
- style: [styles$q.dropdownLabel, styles$q.axisMenu]
31365
+ style: [styles$p.dropdownLabel, styles$p.axisMenu]
31069
31366
  // Placeholder is required, but never gets used.
31070
31367
  ,
31071
31368
  placeholder: ""
@@ -31084,17 +31381,17 @@ const LockedFunctionSettings = props => {
31084
31381
  onChange: newValue => {
31085
31382
  handlePropChange("equation", newValue);
31086
31383
  },
31087
- style: [styles$q.textField]
31384
+ style: [styles$p.textField]
31088
31385
  })), /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
31089
- style: [styles$q.row, styles$q.rowSpace]
31386
+ style: [styles$p.row, styles$p.rowSpace]
31090
31387
  }, /*#__PURE__*/React__namespace.createElement(wonderBlocksTypography.LabelMedium, {
31091
31388
  tag: "label",
31092
- style: [styles$q.dropdownLabel, styles$q.domainMin]
31389
+ style: [styles$p.dropdownLabel, styles$p.domainMin]
31093
31390
  }, `${domainRangeText} min`, /*#__PURE__*/React__namespace.createElement(Strut, {
31094
31391
  size: wonderBlocksTokens.spacing.xxSmall_6
31095
31392
  }), /*#__PURE__*/React__namespace.createElement(wonderBlocksForm.TextField, {
31096
31393
  type: "number",
31097
- style: styles$q.domainMinField,
31394
+ style: styles$p.domainMinField,
31098
31395
  value: domainEntries[0],
31099
31396
  onChange: newValue => {
31100
31397
  handleDomainChange(0, newValue);
@@ -31104,12 +31401,12 @@ const LockedFunctionSettings = props => {
31104
31401
  }), /*#__PURE__*/React__namespace.createElement(wonderBlocksTypography.LabelMedium, {
31105
31402
  tag: "label",
31106
31403
  "aria-label": `${domainRangeText} max`,
31107
- style: [styles$q.dropdownLabel, styles$q.domainMax]
31404
+ style: [styles$p.dropdownLabel, styles$p.domainMax]
31108
31405
  }, "max", /*#__PURE__*/React__namespace.createElement(Strut, {
31109
31406
  size: wonderBlocksTokens.spacing.xxSmall_6
31110
31407
  }), /*#__PURE__*/React__namespace.createElement(wonderBlocksForm.TextField, {
31111
31408
  type: "number",
31112
- style: styles$q.domainMaxField,
31409
+ style: styles$p.domainMaxField,
31113
31410
  value: domainEntries[1],
31114
31411
  onChange: newValue => {
31115
31412
  handleDomainChange(1, newValue);
@@ -31117,11 +31414,11 @@ const LockedFunctionSettings = props => {
31117
31414
  }))), /*#__PURE__*/React__namespace.createElement(PerseusEditorAccordion, {
31118
31415
  header: /*#__PURE__*/React__namespace.createElement(wonderBlocksTypography.LabelLarge, null, "Example Functions"),
31119
31416
  expanded: false,
31120
- containerStyle: styles$q.exampleWorkspace,
31121
- panelStyle: styles$q.exampleAccordionPanel
31417
+ containerStyle: styles$p.exampleWorkspace,
31418
+ panelStyle: styles$p.exampleAccordionPanel
31122
31419
  }, /*#__PURE__*/React__namespace.createElement(wonderBlocksTypography.LabelMedium, {
31123
31420
  tag: "label",
31124
- style: styles$q.dropdownLabel
31421
+ style: styles$p.dropdownLabel
31125
31422
  }, "Choose a category", /*#__PURE__*/React__namespace.createElement(Strut, {
31126
31423
  size: wonderBlocksTokens.spacing.xxSmall_6
31127
31424
  }), /*#__PURE__*/React__namespace.createElement(wonderBlocksDropdown.SingleSelect, {
@@ -31135,13 +31432,41 @@ const LockedFunctionSettings = props => {
31135
31432
  label: category
31136
31433
  });
31137
31434
  }))), exampleCategorySelected && /*#__PURE__*/React__namespace.createElement("ul", {
31138
- className: aphrodite.css(styles$q.exampleContainer)
31435
+ className: aphrodite.css(styles$p.exampleContainer)
31139
31436
  }, exampleContent.map((example, index) => /*#__PURE__*/React__namespace.createElement(ExampleItem, {
31140
31437
  category: exampleCategory,
31141
31438
  example: example,
31142
31439
  index: index,
31143
31440
  pasteEquationFn: handlePropChange
31144
- })))), /*#__PURE__*/React__namespace.createElement(LockedFigureSettingsActions, {
31441
+ })))), flags?.["mafs"]?.["locked-function-labels"] && /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
31442
+ style: styles$p.horizontalRule
31443
+ }), labels?.map((label, labelIndex) => /*#__PURE__*/React__namespace.createElement(LockedLabelSettings, _extends({}, label, {
31444
+ expanded: true,
31445
+ onChangeProps: newLabel => {
31446
+ handleLabelChange(newLabel, labelIndex);
31447
+ },
31448
+ onRemove: () => {
31449
+ handleLabelRemove(labelIndex);
31450
+ },
31451
+ containerStyle: styles$p.labelContainer
31452
+ }))), /*#__PURE__*/React__namespace.createElement(Button__default["default"], {
31453
+ kind: "tertiary",
31454
+ startIcon: plusCircle__default["default"],
31455
+ onClick: () => {
31456
+ const newLabel = {
31457
+ ...getDefaultFigureForType("label"),
31458
+ // Vertical offset for each label so they
31459
+ // don't overlap.
31460
+ coord: [0, -(labels?.length ?? 0)],
31461
+ // Default to the same color as the function
31462
+ color: lineColor
31463
+ };
31464
+ onChangeProps({
31465
+ labels: [...(labels ?? []), newLabel]
31466
+ });
31467
+ },
31468
+ style: styles$p.addButton
31469
+ }, "Add visible label")), /*#__PURE__*/React__namespace.createElement(LockedFigureSettingsActions, {
31145
31470
  figureType: props.type,
31146
31471
  onMove: onMove,
31147
31472
  onRemove: onRemove
@@ -31157,29 +31482,29 @@ const ExampleItem = props => {
31157
31482
  const exampleId = React.useId();
31158
31483
  return /*#__PURE__*/React__namespace.createElement("li", {
31159
31484
  key: `${category}-${index}`,
31160
- className: aphrodite.css(styles$q.exampleRow)
31485
+ className: aphrodite.css(styles$p.exampleRow)
31161
31486
  }, /*#__PURE__*/React__namespace.createElement(IconButton__default["default"], {
31162
31487
  icon: autoPasteIcon__default["default"],
31163
31488
  "aria-label": "paste example",
31164
31489
  "aria-describedby": exampleId,
31165
31490
  onClick: () => pasteEquationFn("equation", example),
31166
31491
  size: "medium",
31167
- style: styles$q.copyPasteButton
31492
+ style: styles$p.copyPasteButton
31168
31493
  }), /*#__PURE__*/React__namespace.createElement(IconButton__default["default"], {
31169
31494
  icon: copyIcon__default["default"],
31170
31495
  "aria-label": "copy example",
31171
31496
  "aria-describedby": exampleId,
31172
31497
  onClick: () => navigator.clipboard.writeText(example),
31173
31498
  size: "medium",
31174
- style: styles$q.copyPasteButton
31499
+ style: styles$p.copyPasteButton
31175
31500
  }), /*#__PURE__*/React__namespace.createElement(Strut, {
31176
31501
  size: wonderBlocksTokens.spacing.xxxSmall_4
31177
31502
  }), /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
31178
- style: styles$q.exampleContent,
31503
+ style: styles$p.exampleContent,
31179
31504
  id: exampleId
31180
31505
  }, example));
31181
31506
  };
31182
- const styles$q = aphrodite.StyleSheet.create({
31507
+ const styles$p = aphrodite.StyleSheet.create({
31183
31508
  accordionHeader: {
31184
31509
  textOverflow: "ellipsis",
31185
31510
  // A maximum width needs to be specified in order for the ellipsis to work.
@@ -31270,125 +31595,19 @@ const styles$q = aphrodite.StyleSheet.create({
31270
31595
  },
31271
31596
  textField: {
31272
31597
  flexGrow: "1"
31273
- }
31274
- });
31275
-
31276
- /**
31277
- * LockedLabelSettings is a component that allows the user to edit the
31278
- * settings of specifically a locked label on the graph within the
31279
- * Interactive Graph widget.
31280
- *
31281
- * Used in the interactive graph editor's locked figures section.
31282
- */
31283
- function LockedLabelSettings(props) {
31284
- const {
31285
- type,
31286
- coord,
31287
- color,
31288
- size,
31289
- text,
31290
- expanded,
31291
- onChangeProps,
31292
- onMove,
31293
- onRemove,
31294
- onToggle,
31295
- containerStyle
31296
- } = props;
31297
- return /*#__PURE__*/React__namespace.createElement(PerseusEditorAccordion, {
31298
- expanded: expanded,
31299
- onToggle: onToggle,
31300
- header: /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
31301
- style: [styles$p.row, styles$p.accordionHeaderContainer]
31302
- }, /*#__PURE__*/React__namespace.createElement(wonderBlocksTypography.LabelLarge, null, "Label (", coord[0], ", ", coord[1], ")"), /*#__PURE__*/React__namespace.createElement(Strut, {
31303
- size: wonderBlocksTokens.spacing.xSmall_8
31304
- }), text !== "" && /*#__PURE__*/React__namespace.createElement(wonderBlocksTypography.LabelLarge, {
31305
- style: [{
31306
- backgroundColor: wonderBlocksTokens.color.white,
31307
- color: perseus.lockedFigureColors[color]
31308
- }, styles$p.accordionHeader]
31309
- }, text)),
31310
- containerStyle: containerStyle
31311
- }, /*#__PURE__*/React__namespace.createElement(CoordinatePairInput, {
31312
- coord: coord,
31313
- onChange: newCoords => {
31314
- onChangeProps({
31315
- coord: newCoords
31316
- });
31317
- },
31318
- style: styles$p.spaceUnder
31319
- }), /*#__PURE__*/React__namespace.createElement(wonderBlocksTypography.LabelMedium, {
31320
- tag: "label",
31321
- style: [styles$p.row, styles$p.spaceUnder]
31322
- }, "TeX", /*#__PURE__*/React__namespace.createElement(Strut, {
31323
- size: wonderBlocksTokens.spacing.xSmall_8
31324
- }), /*#__PURE__*/React__namespace.createElement(wonderBlocksForm.TextField, {
31325
- value: text,
31326
- placeholder: "ex. x^2 or \\frac{1}{2}",
31327
- onChange: newValue => onChangeProps({
31328
- text: newValue
31329
- })
31330
- })), /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
31331
- style: styles$p.row
31332
- }, onMove && /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(ColorSelect, {
31333
- selectedValue: color,
31334
- onChange: newColor => {
31335
- onChangeProps({
31336
- color: newColor
31337
- });
31338
- },
31339
- style: styles$p.spaceUnder
31340
- }), /*#__PURE__*/React__namespace.createElement(Strut, {
31341
- size: wonderBlocksTokens.spacing.medium_16
31342
- })), /*#__PURE__*/React__namespace.createElement(wonderBlocksTypography.LabelMedium, {
31343
- tag: "label",
31344
- style: styles$p.row
31345
- }, "size", /*#__PURE__*/React__namespace.createElement(Strut, {
31346
- size: wonderBlocksTokens.spacing.xSmall_8
31347
- }), /*#__PURE__*/React__namespace.createElement(wonderBlocksDropdown.SingleSelect, {
31348
- selectedValue: size,
31349
- onChange: newValue => onChangeProps({
31350
- size: newValue
31351
- })
31352
- // Placeholder is required, but never gets used since
31353
- // we have a label for the select.
31354
- ,
31355
- placeholder: ""
31356
- }, /*#__PURE__*/React__namespace.createElement(wonderBlocksDropdown.OptionItem, {
31357
- value: "small",
31358
- label: "small"
31359
- }), /*#__PURE__*/React__namespace.createElement(wonderBlocksDropdown.OptionItem, {
31360
- value: "medium",
31361
- label: "medium"
31362
- }), /*#__PURE__*/React__namespace.createElement(wonderBlocksDropdown.OptionItem, {
31363
- value: "large",
31364
- label: "large"
31365
- })))), /*#__PURE__*/React__namespace.createElement(LockedFigureSettingsActions, {
31366
- figureType: type,
31367
- onMove: onMove,
31368
- onRemove: onRemove
31369
- }));
31370
- }
31371
- const styles$p = aphrodite.StyleSheet.create({
31372
- accordionHeaderContainer: {
31373
- // Stop the label summary from wrapping.
31374
- whiteSpace: "nowrap"
31375
31598
  },
31376
- accordionHeader: {
31377
- padding: wonderBlocksTokens.spacing.xxxSmall_4,
31378
- marginInlineEnd: wonderBlocksTokens.spacing.xSmall_8,
31379
- borderRadius: wonderBlocksTokens.spacing.xxxSmall_4,
31380
- textOverflow: "ellipsis",
31381
- overflow: "hidden"
31599
+ // Label settings styles
31600
+ addButton: {
31601
+ alignSelf: "start"
31382
31602
  },
31383
- row: {
31384
- display: "flex",
31385
- flexDirection: "row",
31386
- alignItems: "center",
31387
- // Allow truncation, stop bleeding over the edge.
31388
- minWidth: 0
31603
+ horizontalRule: {
31604
+ marginTop: wonderBlocksTokens.spacing.small_12,
31605
+ marginBottom: wonderBlocksTokens.spacing.xxxSmall_4,
31606
+ height: 1,
31607
+ backgroundColor: wonderBlocksTokens.color.offBlack16
31389
31608
  },
31390
- spaceUnder: {
31391
- marginBottom: wonderBlocksTokens.spacing.xSmall_8
31609
+ labelContainer: {
31610
+ backgroundColor: wonderBlocksTokens.color.white
31392
31611
  }
31393
31612
  });
31394
31613
 
@@ -31421,77 +31640,6 @@ const styles$o = aphrodite.StyleSheet.create({
31421
31640
  }
31422
31641
  });
31423
31642
 
31424
- const DEFAULT_COLOR = "grayH";
31425
- function getDefaultFigureForType(type) {
31426
- switch (type) {
31427
- case "point":
31428
- return {
31429
- type: "point",
31430
- coord: [0, 0],
31431
- color: DEFAULT_COLOR,
31432
- filled: true,
31433
- labels: []
31434
- };
31435
- case "line":
31436
- return {
31437
- type: "line",
31438
- kind: "line",
31439
- points: [getDefaultFigureForType("point"), {
31440
- ...getDefaultFigureForType("point"),
31441
- coord: [2, 2]
31442
- }],
31443
- color: DEFAULT_COLOR,
31444
- lineStyle: "solid",
31445
- showPoint1: false,
31446
- showPoint2: false,
31447
- labels: []
31448
- };
31449
- case "vector":
31450
- return {
31451
- type: "vector",
31452
- points: [[0, 0], [2, 2]],
31453
- color: DEFAULT_COLOR
31454
- };
31455
- case "ellipse":
31456
- return {
31457
- type: "ellipse",
31458
- center: [0, 0],
31459
- radius: [1, 1],
31460
- angle: 0,
31461
- color: DEFAULT_COLOR,
31462
- fillStyle: "none",
31463
- strokeStyle: "solid"
31464
- };
31465
- case "polygon":
31466
- return {
31467
- type: "polygon",
31468
- points: [[0, 2], [-1, 0], [1, 0]],
31469
- color: DEFAULT_COLOR,
31470
- showVertices: false,
31471
- fillStyle: "none",
31472
- strokeStyle: "solid"
31473
- };
31474
- case "function":
31475
- return {
31476
- type: "function",
31477
- color: DEFAULT_COLOR,
31478
- strokeStyle: "solid",
31479
- equation: "x^2",
31480
- directionalAxis: "x"
31481
- };
31482
- case "label":
31483
- return {
31484
- type: "label",
31485
- coord: [0, 0],
31486
- text: "",
31487
- color: DEFAULT_COLOR,
31488
- size: "medium"
31489
- };
31490
- default:
31491
- throw new wonderStuffCore.UnreachableCaseError(type);
31492
- }
31493
- }
31494
-
31495
31643
  const LockedPointSettings = props => {
31496
31644
  const {
31497
31645
  flags,
@@ -31516,10 +31664,12 @@ const LockedPointSettings = props => {
31516
31664
  };
31517
31665
 
31518
31666
  // Update the color of the all labels to match the point
31519
- newProps.labels = labels.map(label => ({
31520
- ...label,
31521
- color: newValue
31522
- }));
31667
+ if (labels) {
31668
+ newProps.labels = labels.map(label => ({
31669
+ ...label,
31670
+ color: newValue
31671
+ }));
31672
+ }
31523
31673
  onChangeProps(newProps);
31524
31674
  }
31525
31675
  function handleCoordChange(newCoord) {
@@ -31530,13 +31680,18 @@ const LockedPointSettings = props => {
31530
31680
  };
31531
31681
 
31532
31682
  // Update the coord by the same amount as the point for all labels
31533
- newProps.labels = labels.map(label => ({
31534
- ...label,
31535
- coord: [label.coord[0] + xOffset, label.coord[1] + yOffset]
31536
- }));
31683
+ if (labels) {
31684
+ newProps.labels = labels.map(label => ({
31685
+ ...label,
31686
+ coord: [label.coord[0] + xOffset, label.coord[1] + yOffset]
31687
+ }));
31688
+ }
31537
31689
  onChangeProps(newProps);
31538
31690
  }
31539
31691
  function handleLabelChange(updatedLabel, labelIndex) {
31692
+ if (!labels) {
31693
+ return;
31694
+ }
31540
31695
  const updatedLabels = [...labels];
31541
31696
  updatedLabels[labelIndex] = {
31542
31697
  ...labels[labelIndex],
@@ -31547,6 +31702,9 @@ const LockedPointSettings = props => {
31547
31702
  });
31548
31703
  }
31549
31704
  function handleLabelRemove(labelIndex) {
31705
+ if (!labels) {
31706
+ return;
31707
+ }
31550
31708
  const updatedLabels = labels.filter((_, index) => index !== labelIndex);
31551
31709
  onChangeProps({
31552
31710
  labels: updatedLabels
@@ -31589,7 +31747,7 @@ const LockedPointSettings = props => {
31589
31747
  filled: !newValue
31590
31748
  });
31591
31749
  }
31592
- })), (!isDefiningPoint && flags?.["mafs"]?.["locked-point-labels"] || isDefiningPoint && flags?.["mafs"]?.["locked-line-labels"]) && /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, labels.map((label, labelIndex) => /*#__PURE__*/React__namespace.createElement(LockedLabelSettings, _extends({}, label, {
31750
+ })), (!isDefiningPoint && flags?.["mafs"]?.["locked-point-labels"] || isDefiningPoint && flags?.["mafs"]?.["locked-line-labels"]) && /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, labels?.map((label, labelIndex) => /*#__PURE__*/React__namespace.createElement(LockedLabelSettings, _extends({}, label, {
31593
31751
  containerStyle: !isDefiningPoint && styles$n.lockedPointLabelContainer,
31594
31752
  expanded: true,
31595
31753
  onChangeProps: newLabel => {
@@ -31610,12 +31768,12 @@ const LockedPointSettings = props => {
31610
31768
  coord: [coord[0] + 0.5,
31611
31769
  // Additional offset for each label so
31612
31770
  // they don't overlap.
31613
- coord[1] - 1 * labels?.length],
31771
+ coord[1] - 1 * (labels?.length ?? 0)],
31614
31772
  // Default to the same color as the point
31615
31773
  color: pointColor
31616
31774
  };
31617
31775
  onChangeProps({
31618
- labels: [...labels, newLabel]
31776
+ labels: [...(labels ?? []), newLabel]
31619
31777
  });
31620
31778
  },
31621
31779
  style: styles$n.addButton
@@ -31688,7 +31846,7 @@ const LockedLineSettings = props => {
31688
31846
  const oldMidpoint = mafs.vec.midpoint(points[0].coord, points[1].coord);
31689
31847
  const newMidpoint = mafs.vec.midpoint(newPoints[0].coord, newPoints[1].coord);
31690
31848
  const offset = [newMidpoint[0] - oldMidpoint[0], newMidpoint[1] - oldMidpoint[1]];
31691
- const newLabels = labels.map((label, labelIndex) => ({
31849
+ const newLabels = labels?.map((label, labelIndex) => ({
31692
31850
  ...label,
31693
31851
  coord: [label.coord[0] + offset[0], label.coord[1] + offset[1]]
31694
31852
  }));
@@ -31698,7 +31856,7 @@ const LockedLineSettings = props => {
31698
31856
  });
31699
31857
  }
31700
31858
  function handleColorChange(newColor) {
31701
- const newLabels = labels.map(label => ({
31859
+ const newLabels = labels?.map(label => ({
31702
31860
  ...label,
31703
31861
  color: newColor
31704
31862
  }));
@@ -31708,14 +31866,14 @@ const LockedLineSettings = props => {
31708
31866
  points: [{
31709
31867
  ...point1,
31710
31868
  color: newColor,
31711
- labels: point1.labels.map(label => ({
31869
+ labels: point1.labels?.map(label => ({
31712
31870
  ...label,
31713
31871
  color: newColor
31714
31872
  }))
31715
31873
  }, {
31716
31874
  ...point2,
31717
31875
  color: newColor,
31718
- labels: point2.labels.map(label => ({
31876
+ labels: point2.labels?.map(label => ({
31719
31877
  ...label,
31720
31878
  color: newColor
31721
31879
  }))
@@ -31725,6 +31883,9 @@ const LockedLineSettings = props => {
31725
31883
  });
31726
31884
  }
31727
31885
  function handleLabelChange(updatedLabel, labelIndex) {
31886
+ if (!labels) {
31887
+ return;
31888
+ }
31728
31889
  const updatedLabels = [...labels];
31729
31890
  updatedLabels[labelIndex] = {
31730
31891
  ...labels[labelIndex],
@@ -31735,6 +31896,9 @@ const LockedLineSettings = props => {
31735
31896
  });
31736
31897
  }
31737
31898
  function handleLabelRemove(labelIndex) {
31899
+ if (!labels) {
31900
+ return;
31901
+ }
31738
31902
  const updatedLabels = labels.filter((_, index) => index !== labelIndex);
31739
31903
  onChangeProps({
31740
31904
  labels: updatedLabels
@@ -31811,7 +31975,7 @@ const LockedLineSettings = props => {
31811
31975
  onChangeProps: newProps => handleChangePoint(newProps, 1)
31812
31976
  })), flags?.["mafs"]?.["locked-line-labels"] && /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
31813
31977
  style: styles$m.horizontalRule
31814
- }), labels.map((label, labelIndex) => /*#__PURE__*/React__namespace.createElement(LockedLabelSettings, _extends({}, label, {
31978
+ }), labels?.map((label, labelIndex) => /*#__PURE__*/React__namespace.createElement(LockedLabelSettings, _extends({}, label, {
31815
31979
  expanded: true,
31816
31980
  onChangeProps: newLabel => {
31817
31981
  handleLabelChange(newLabel, labelIndex);
@@ -31827,15 +31991,15 @@ const LockedLineSettings = props => {
31827
31991
  // Additional vertical offset for each label so
31828
31992
  // they don't overlap.
31829
31993
  const offsetPerLabel = [0, -1];
31830
- const labelLocation = mafs.vec.add(mafs.vec.scale(offsetPerLabel, labels.length), mafs.vec.midpoint(points[0].coord, points[1].coord));
31994
+ const labelLocation = mafs.vec.add(mafs.vec.scale(offsetPerLabel, labels?.length ?? 0), mafs.vec.midpoint(points[0].coord, points[1].coord));
31831
31995
  const newLabel = {
31832
31996
  ...getDefaultFigureForType("label"),
31833
31997
  coord: labelLocation,
31834
- // Default to the same color as the point
31998
+ // Default to the same color as the line
31835
31999
  color: lineColor
31836
32000
  };
31837
32001
  onChangeProps({
31838
- labels: [...labels, newLabel]
32002
+ labels: [...(labels ?? []), newLabel]
31839
32003
  });
31840
32004
  },
31841
32005
  style: styles$m.addButton
@@ -32130,17 +32294,13 @@ const styles$k = aphrodite.StyleSheet.create({
32130
32294
  }
32131
32295
  });
32132
32296
 
32133
- /**
32134
- * LockedVectorSettings is a component that allows the user to edit the
32135
- * settings of specifically a locked vector on the graph.
32136
- *
32137
- * Used in the interactive graph editor's locked figures section.
32138
- */
32139
32297
  const lengthErrorMessage = "The vector cannot have length 0.";
32140
32298
  const LockedVectorSettings = props => {
32141
32299
  const {
32300
+ flags,
32142
32301
  points,
32143
32302
  color: lineColor,
32303
+ labels,
32144
32304
  onChangeProps,
32145
32305
  onMove,
32146
32306
  onRemove
@@ -32154,14 +32314,53 @@ const LockedVectorSettings = props => {
32154
32314
  if (typeof newCoord !== "undefined") {
32155
32315
  const newPoints = [...points];
32156
32316
  newPoints[index] = [...newCoord];
32317
+
32318
+ // Update labels to match the new points
32319
+ const oldMidpoint = mafs.vec.midpoint(tail, tip);
32320
+ const newMidpoint = mafs.vec.midpoint(newPoints[0], newPoints[1]);
32321
+ const offset = mafs.vec.sub(newMidpoint, oldMidpoint);
32322
+ const newLabels = labels?.map(label => ({
32323
+ ...label,
32324
+ coord: mafs.vec.add(label.coord, offset)
32325
+ }));
32157
32326
  onChangeProps({
32158
- points: newPoints
32327
+ points: newPoints,
32328
+ labels: newLabels
32159
32329
  });
32160
32330
  }
32161
32331
  }
32162
32332
  function handleColorChange(newColor) {
32163
- onChangeProps({
32333
+ const newProps = {
32164
32334
  color: newColor
32335
+ };
32336
+
32337
+ // Update the color of the all labels to match the point
32338
+ newProps.labels = labels?.map(label => ({
32339
+ ...label,
32340
+ color: newColor
32341
+ }));
32342
+ onChangeProps(newProps);
32343
+ }
32344
+ function handleLabelChange(updatedLabel, labelIndex) {
32345
+ if (!labels) {
32346
+ return;
32347
+ }
32348
+ const updatedLabels = [...labels];
32349
+ updatedLabels[labelIndex] = {
32350
+ ...labels[labelIndex],
32351
+ ...updatedLabel
32352
+ };
32353
+ onChangeProps({
32354
+ labels: updatedLabels
32355
+ });
32356
+ }
32357
+ function handleLabelRemove(labelIndex) {
32358
+ if (!labels) {
32359
+ return;
32360
+ }
32361
+ const updatedLabels = labels.filter((_, index) => index !== labelIndex);
32362
+ onChangeProps({
32363
+ labels: updatedLabels
32165
32364
  });
32166
32365
  }
32167
32366
  return /*#__PURE__*/React__namespace.createElement(PerseusEditorAccordion, {
@@ -32210,7 +32409,37 @@ const LockedVectorSettings = props => {
32210
32409
  onChange: newProps => {
32211
32410
  handleChangePoint(newProps, 1);
32212
32411
  }
32213
- })), /*#__PURE__*/React__namespace.createElement(LockedFigureSettingsActions, {
32412
+ })), flags?.["mafs"]?.["locked-vector-labels"] && /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
32413
+ style: styles$j.horizontalRule
32414
+ }), labels?.map((label, labelIndex) => /*#__PURE__*/React__namespace.createElement(LockedLabelSettings, _extends({}, label, {
32415
+ expanded: true,
32416
+ onChangeProps: newLabel => {
32417
+ handleLabelChange(newLabel, labelIndex);
32418
+ },
32419
+ onRemove: () => {
32420
+ handleLabelRemove(labelIndex);
32421
+ },
32422
+ containerStyle: styles$j.labelContainer
32423
+ }))), /*#__PURE__*/React__namespace.createElement(Button__default["default"], {
32424
+ kind: "tertiary",
32425
+ startIcon: plusCircle__default["default"],
32426
+ onClick: () => {
32427
+ // Additional vertical offset for each label so
32428
+ // they don't overlap.
32429
+ const offsetPerLabel = [0, -1];
32430
+ const labelLocation = mafs.vec.add(mafs.vec.scale(offsetPerLabel, labels?.length ?? 0), mafs.vec.midpoint(tail, tip));
32431
+ const newLabel = {
32432
+ ...getDefaultFigureForType("label"),
32433
+ coord: labelLocation,
32434
+ // Default to the same color as the vector
32435
+ color: lineColor
32436
+ };
32437
+ onChangeProps({
32438
+ labels: [...(labels ?? []), newLabel]
32439
+ });
32440
+ },
32441
+ style: styles$j.addButton
32442
+ }, "Add visible label")), /*#__PURE__*/React__namespace.createElement(LockedFigureSettingsActions, {
32214
32443
  figureType: props.type,
32215
32444
  onMove: onMove,
32216
32445
  onRemove: onRemove
@@ -32234,6 +32463,18 @@ const styles$j = aphrodite.StyleSheet.create({
32234
32463
  row: {
32235
32464
  flexDirection: "row",
32236
32465
  alignItems: "center"
32466
+ },
32467
+ addButton: {
32468
+ alignSelf: "start"
32469
+ },
32470
+ horizontalRule: {
32471
+ marginTop: wonderBlocksTokens.spacing.small_12,
32472
+ marginBottom: wonderBlocksTokens.spacing.xxxSmall_4,
32473
+ height: 1,
32474
+ backgroundColor: wonderBlocksTokens.color.offBlack16
32475
+ },
32476
+ labelContainer: {
32477
+ backgroundColor: wonderBlocksTokens.color.white
32237
32478
  }
32238
32479
  });
32239
32480