@khanacademy/perseus-editor 5.3.0 → 6.0.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
@@ -1,5 +1,5 @@
1
1
  import { addLibraryVersionToPerseusDebug } from '@khanacademy/perseus-core';
2
- import { constants, components, Widgets, icons, WIDGET_PROP_DENYLIST, Util, Log, PerseusError, Errors, PerseusMarkdown, preprocessTex, ApiOptions, buildMapper, itemToTree, buildEmptyItemTreeForShape, shapes, ItemExtras, itemDataVersion, ClassNames, MultiItems, traverse, Categorizer as Categorizer$1, Changeable, EditorJsonify, Dependencies, WidgetJsonifyDeprecated, PerseusExpressionAnswerFormConsidered, Expression, interactiveSizes, KhanMath, GrapherWidget, GrapherUtil, SizingUtils, KhanColors, ColorPicker, DashPicker, ArrowPicker, ElementContainer, lockedFigureColors, InteractiveGraphWidget, globalStyles, globalConstants, MatrixWidget, PlotterWidget, plotterPlotTypes, BaseRadio, TableWidget, displaySigFigs, widgets } from '@khanacademy/perseus';
2
+ import { constants, components, Widgets, icons, WIDGET_PROP_DENYLIST, Util, Log, PerseusError, Errors, PerseusMarkdown, preprocessTex, ApiOptions, buildMapper, itemToTree, buildEmptyItemTreeForShape, shapes, ItemExtras, itemDataVersion, ClassNames, MultiItems, traverse, Categorizer as Categorizer$1, Changeable, EditorJsonify, Dependencies, WidgetJsonifyDeprecated, PerseusExpressionAnswerFormConsidered, Expression, interactiveSizes, KhanMath, GrapherWidget, GrapherUtil, SizingUtils, PerseusI18nContext, KhanColors, ColorPicker, DashPicker, ArrowPicker, ElementContainer, lockedFigureColors, InteractiveGraphWidget, globalStyles, globalConstants, MatrixWidget, PlotterWidget, plotterPlotTypes, BaseRadio, TableWidget, displaySigFigs, widgets } from '@khanacademy/perseus';
3
3
  import * as React from 'react';
4
4
  import { useRef, useEffect } from 'react';
5
5
  import _ from 'underscore';
@@ -17,20 +17,19 @@ import ReactDOM from 'react-dom';
17
17
  import { point, number } from '@khanacademy/kmath';
18
18
  import * as KAS from '@khanacademy/kas';
19
19
  import { isTruthy, UnreachableCaseError } from '@khanacademy/wonder-stuff-core';
20
- import { createMathField } from '@khanacademy/math-input';
20
+ import { useMathInputI18n, createMathField } from '@khanacademy/math-input';
21
21
  import { SingleSelect, OptionItem, ActionMenu, ActionItem } from '@khanacademy/wonder-blocks-dropdown';
22
22
  import { TextField, Checkbox as Checkbox$1 } from '@khanacademy/wonder-blocks-form';
23
23
  import { LabelSmall, LabelMedium, LabelLarge, BodyMonospace } from '@khanacademy/wonder-blocks-typography';
24
24
  import { AccordionSection } from '@khanacademy/wonder-blocks-accordion';
25
25
  import IconButton from '@khanacademy/wonder-blocks-icon-button';
26
- import Switch from '@khanacademy/wonder-blocks-switch';
27
26
  import trashIcon from '@phosphor-icons/core/bold/trash-bold.svg';
27
+ import Switch from '@khanacademy/wonder-blocks-switch';
28
28
  import Button from '@khanacademy/wonder-blocks-button';
29
- import * as i18n$1 from '@khanacademy/wonder-blocks-i18n';
30
29
 
31
30
  // This file is processed by a Rollup plugin (replace) to inject the production
32
31
  const libName = "@khanacademy/perseus-editor";
33
- const libVersion = "5.3.0";
32
+ const libVersion = "6.0.0";
34
33
  addLibraryVersionToPerseusDebug(libName, libVersion);
35
34
 
36
35
  function _extends() {
@@ -220,7 +219,7 @@ class SectionControlButton extends React.Component {
220
219
  role: "button",
221
220
  hideDefaultFocusRing: true,
222
221
  "aria-label": title,
223
- style: styles$r.button
222
+ style: styles$s.button
224
223
  }, ({
225
224
  hovered,
226
225
  focused,
@@ -228,7 +227,7 @@ class SectionControlButton extends React.Component {
228
227
  }) => /*#__PURE__*/React.createElement(InlineIcon$b, icon));
229
228
  }
230
229
  }
231
- const styles$r = StyleSheet.create({
230
+ const styles$s = StyleSheet.create({
232
231
  button: {
233
232
  backgroundColor: color.gold,
234
233
  margin: 2,
@@ -20750,19 +20749,19 @@ class TexErrorView extends React.Component {
20750
20749
  const {
20751
20750
  showErrors
20752
20751
  } = this.state;
20753
- const iconStyle = showErrors ? styles$q.iconExpanded : styles$q.iconCollapsed;
20752
+ const iconStyle = showErrors ? styles$r.iconExpanded : styles$r.iconCollapsed;
20754
20753
  return /*#__PURE__*/React.createElement(View, {
20755
- style: styles$q.errorContainer
20754
+ style: styles$r.errorContainer
20756
20755
  }, /*#__PURE__*/React.createElement(View, {
20757
- style: styles$q.title,
20756
+ style: styles$r.title,
20758
20757
  onClick: this.handleToggleTexErrors
20759
20758
  }, /*#__PURE__*/React.createElement(PhosphorIcon, {
20760
20759
  icon: caretRight,
20761
20760
  style: iconStyle
20762
20761
  }), "\xA0 TeX Errors (", errorList.length, ")"), showErrors && /*#__PURE__*/React.createElement(View, {
20763
- style: styles$q.errorExplanation
20762
+ style: styles$r.errorExplanation
20764
20763
  }, "If your math doesn't display correctly, these errors might help you troubleshoot. Message #content-kitchen for help."), showErrors && errorList.map((e, index) => /*#__PURE__*/React.createElement(View, {
20765
- style: styles$q.error,
20764
+ style: styles$r.error,
20766
20765
  key: index
20767
20766
  }, /*#__PURE__*/React.createElement(View, {
20768
20767
  style: {
@@ -20771,7 +20770,7 @@ class TexErrorView extends React.Component {
20771
20770
  }, e.math), /*#__PURE__*/React.createElement(View, null, e.message))));
20772
20771
  }
20773
20772
  }
20774
- const styles$q = StyleSheet.create({
20773
+ const styles$r = StyleSheet.create({
20775
20774
  title: {
20776
20775
  backgroundColor: "#eee",
20777
20776
  fontSize: "1.25em",
@@ -24572,7 +24571,7 @@ class SimpleButton extends React.Component {
24572
24571
  } = _this$props,
24573
24572
  otherProps = _objectWithoutPropertiesLoose(_this$props, _excluded$3);
24574
24573
  return /*#__PURE__*/React.createElement("div", _extends({
24575
- className: css(styles$p.baseButton, color === "green" && styles$p.green, color === "orange" && styles$p.orange),
24574
+ className: css(styles$q.baseButton, color === "green" && styles$q.green, color === "orange" && styles$q.orange),
24576
24575
  onClick: onClick
24577
24576
  }, otherProps), children);
24578
24577
  }
@@ -24587,7 +24586,7 @@ SimpleButton.defaultProps = {
24587
24586
  color: "green",
24588
24587
  onClick: () => {}
24589
24588
  };
24590
- const styles$p = StyleSheet.create({
24589
+ const styles$q = StyleSheet.create({
24591
24590
  baseButton: {
24592
24591
  top: "0",
24593
24592
  fontSize: "11px",
@@ -24807,23 +24806,23 @@ const LeafContainer = ({
24807
24806
  }) => {
24808
24807
  const hasPreviewHeading = shape.type === "content" || shape.type === "hint";
24809
24808
  const previewHeading = hasPreviewHeading && /*#__PURE__*/React.createElement("div", {
24810
- className: css(styles$o.containerHeader)
24809
+ className: css(styles$p.containerHeader)
24811
24810
  }, /*#__PURE__*/React.createElement(Header, {
24812
24811
  depth: path.length,
24813
- className: css(styles$o.containerTitle)
24812
+ className: css(styles$p.containerTitle)
24814
24813
  }, capitalize(name)));
24815
24814
  return /*#__PURE__*/React.createElement("div", {
24816
- className: css(styles$o.container)
24815
+ className: css(styles$p.container)
24817
24816
  }, /*#__PURE__*/React.createElement("span", {
24818
- className: css(styles$o.row, styles$o.rowHeading)
24817
+ className: css(styles$p.row, styles$p.rowHeading)
24819
24818
  }, /*#__PURE__*/React.createElement("div", {
24820
- className: css(styles$o.columnLeft)
24819
+ className: css(styles$p.columnLeft)
24821
24820
  }, /*#__PURE__*/React.createElement("div", {
24822
- className: "pod-title " + css(styles$o.containerHeader)
24821
+ className: "pod-title " + css(styles$p.containerHeader)
24823
24822
  }, /*#__PURE__*/React.createElement("div", {
24824
- className: css(styles$o.containerTitle)
24823
+ className: css(styles$p.containerTitle)
24825
24824
  }, capitalize(name)), controls)), /*#__PURE__*/React.createElement("div", {
24826
- className: css(styles$o.columnRight)
24825
+ className: css(styles$p.columnRight)
24827
24826
  }, previewHeading)), children);
24828
24827
  };
24829
24828
  const ArrayContainer = props => {
@@ -24836,11 +24835,11 @@ const ArrayContainer = props => {
24836
24835
  actions
24837
24836
  } = props;
24838
24837
  return /*#__PURE__*/React.createElement("div", {
24839
- className: css(styles$o.container)
24838
+ className: css(styles$p.container)
24840
24839
  }, controls && /*#__PURE__*/React.createElement("div", {
24841
- className: css(styles$o.columnLeft, styles$o.containerHeader)
24840
+ className: css(styles$p.columnLeft, styles$p.containerHeader)
24842
24841
  }, controls), /*#__PURE__*/React.createElement("div", null, children), /*#__PURE__*/React.createElement("div", {
24843
- className: css(styles$o.columnLeft)
24842
+ className: css(styles$p.columnLeft)
24844
24843
  }, /*#__PURE__*/React.createElement("a", {
24845
24844
  href: "javascript:void 0",
24846
24845
  onClick: () => actions.addArrayElement(path, shape.elementShape)
@@ -24853,28 +24852,28 @@ const ObjectContainer = ({
24853
24852
  path
24854
24853
  }) => {
24855
24854
  const headingEditor = /*#__PURE__*/React.createElement("div", {
24856
- className: css(styles$o.containerHeader)
24855
+ className: css(styles$p.containerHeader)
24857
24856
  }, /*#__PURE__*/React.createElement(Header, {
24858
24857
  depth: path.length,
24859
- className: css(styles$o.containerTitle)
24858
+ className: css(styles$p.containerTitle)
24860
24859
  }, capitalize(name)), controls);
24861
24860
  const headingPreview = (name || controls) && /*#__PURE__*/React.createElement("div", {
24862
- className: css(styles$o.containerHeader, styles$o.previewCollectionHeader)
24861
+ className: css(styles$p.containerHeader, styles$p.previewCollectionHeader)
24863
24862
  }, /*#__PURE__*/React.createElement(Header, {
24864
24863
  depth: path.length,
24865
- className: css(styles$o.containerTitle)
24864
+ className: css(styles$p.containerTitle)
24866
24865
  }, capitalize(name)));
24867
24866
  const hasBothHeadings = headingEditor && headingPreview;
24868
24867
  return /*#__PURE__*/React.createElement("div", {
24869
- className: css(styles$o.container)
24868
+ className: css(styles$p.container)
24870
24869
  }, hasBothHeadings && /*#__PURE__*/React.createElement("span", {
24871
- className: css(styles$o.row)
24870
+ className: css(styles$p.row)
24872
24871
  }, /*#__PURE__*/React.createElement("div", {
24873
- className: css(styles$o.columnLeft)
24872
+ className: css(styles$p.columnLeft)
24874
24873
  }, headingEditor), /*#__PURE__*/React.createElement("div", {
24875
- className: css(styles$o.columnRight)
24874
+ className: css(styles$p.columnRight)
24876
24875
  }, headingPreview)), /*#__PURE__*/React.createElement("div", {
24877
- className: css(path.length > 0 && styles$o.contentIndent)
24876
+ className: css(path.length > 0 && styles$p.contentIndent)
24878
24877
  }, children));
24879
24878
  };
24880
24879
 
@@ -24997,11 +24996,11 @@ const ItemNodeContent = withStickiness(props => {
24997
24996
  className: "framework-perseus"
24998
24997
  }, lens(renderers).get(path));
24999
24998
  return /*#__PURE__*/React.createElement("span", null, /*#__PURE__*/React.createElement("div", {
25000
- className: css(styles$o.row)
24999
+ className: css(styles$p.row)
25001
25000
  }, /*#__PURE__*/React.createElement("div", {
25002
- className: css(styles$o.columnLeft)
25001
+ className: css(styles$p.columnLeft)
25003
25002
  }, /*#__PURE__*/React.createElement("div", {
25004
- className: css(sticky && styles$o.sticky)
25003
+ className: css(sticky && styles$p.sticky)
25005
25004
  },
25006
25005
  /*#__PURE__*/
25007
25006
  // @ts-expect-error - TS2769 - No overload matches this call.
@@ -25009,9 +25008,9 @@ const ItemNodeContent = withStickiness(props => {
25009
25008
  onChange: newVal => actions.mergeValueAtPath(path, newVal),
25010
25009
  apiOptions: apiOptions
25011
25010
  })))), /*#__PURE__*/React.createElement("div", {
25012
- className: css(styles$o.columnRight)
25011
+ className: css(styles$p.columnRight)
25013
25012
  }, /*#__PURE__*/React.createElement("div", {
25014
- className: css(sticky && styles$o.sticky)
25013
+ className: css(sticky && styles$p.sticky)
25015
25014
  }, preview))));
25016
25015
  });
25017
25016
  const HintNodeContent = withStickiness(props => {
@@ -25027,13 +25026,13 @@ const HintNodeContent = withStickiness(props => {
25027
25026
  className: "framework-perseus"
25028
25027
  }, lens(renderers).get(path));
25029
25028
  return /*#__PURE__*/React.createElement("div", {
25030
- className: css(styles$o.row)
25029
+ className: css(styles$p.row)
25031
25030
  }, /*#__PURE__*/React.createElement("div", {
25032
- className: css(styles$o.columnLeft)
25031
+ className: css(styles$p.columnLeft)
25033
25032
  }, /*#__PURE__*/React.createElement("div", {
25034
- className: css(sticky && styles$o.sticky)
25033
+ className: css(sticky && styles$p.sticky)
25035
25034
  }, /*#__PURE__*/React.createElement(HintEditor, _extends({}, data, {
25036
- className: css(styles$o.hintEditor),
25035
+ className: css(styles$p.hintEditor),
25037
25036
  onChange: newVal => actions.mergeValueAtPath(path, newVal),
25038
25037
  apiOptions: apiOptions,
25039
25038
  showTitle: false,
@@ -25048,9 +25047,9 @@ const HintNodeContent = withStickiness(props => {
25048
25047
  isFirst: true,
25049
25048
  isLast: true
25050
25049
  })))), /*#__PURE__*/React.createElement("div", {
25051
- className: css(styles$o.columnRight)
25050
+ className: css(styles$p.columnRight)
25052
25051
  }, /*#__PURE__*/React.createElement("div", {
25053
- className: css(sticky && styles$o.sticky)
25052
+ className: css(sticky && styles$p.sticky)
25054
25053
  }, preview)));
25055
25054
  });
25056
25055
  const TagsNodeContent = props => {
@@ -25067,9 +25066,9 @@ const TagsNodeContent = props => {
25067
25066
  return null;
25068
25067
  }
25069
25068
  return /*#__PURE__*/React.createElement("div", {
25070
- className: css(styles$o.columnLeft)
25069
+ className: css(styles$p.columnLeft)
25071
25070
  }, /*#__PURE__*/React.createElement("div", {
25072
- className: css(styles$o.tagsEditor)
25071
+ className: css(styles$p.tagsEditor)
25073
25072
  }, /*#__PURE__*/React.createElement(GroupMetadataEditor, {
25074
25073
  value: data,
25075
25074
  onChange: newVal => actions.setValueAtPath(path, newVal),
@@ -25100,23 +25099,23 @@ const ArrayNodeContent = props => {
25100
25099
  const subpath = path.concat(i);
25101
25100
  const controls = [i > 0 && /*#__PURE__*/React.createElement("div", {
25102
25101
  key: "moveArrayElementUp",
25103
- className: css(styles$o.control)
25102
+ className: css(styles$p.control)
25104
25103
  }, /*#__PURE__*/React.createElement(SimpleButton, {
25105
25104
  color: "orange",
25106
25105
  title: "Move up",
25107
25106
  onClick: () => actions.moveArrayElementUp(subpath)
25108
25107
  }, /*#__PURE__*/React.createElement("div", {
25109
- className: css(styles$o.verticalFlip)
25108
+ className: css(styles$p.verticalFlip)
25110
25109
  }, /*#__PURE__*/React.createElement(InlineIcon$6, iconChevronDown)))), i < data.length - 1 && /*#__PURE__*/React.createElement("div", {
25111
25110
  key: "moveArrayElementDown",
25112
- className: css(styles$o.control)
25111
+ className: css(styles$p.control)
25113
25112
  }, /*#__PURE__*/React.createElement(SimpleButton, {
25114
25113
  color: "orange",
25115
25114
  title: "Move down",
25116
25115
  onClick: () => actions.moveArrayElementDown(subpath)
25117
25116
  }, /*#__PURE__*/React.createElement(InlineIcon$6, iconChevronDown))), /*#__PURE__*/React.createElement("div", {
25118
25117
  key: "removeArrayElement",
25119
- className: css(styles$o.control)
25118
+ className: css(styles$p.control)
25120
25119
  }, /*#__PURE__*/React.createElement(SimpleButton, {
25121
25120
  color: "orange",
25122
25121
  title: "Delete",
@@ -25124,7 +25123,7 @@ const ArrayNodeContent = props => {
25124
25123
  }, /*#__PURE__*/React.createElement(InlineIcon$6, iconTrash$4)))];
25125
25124
  return /*#__PURE__*/React.createElement("div", {
25126
25125
  key: i,
25127
- className: css(styles$o.arrayElement, !elementIsLeaf && styles$o.arrayElementAndNotLeaf)
25126
+ className: css(styles$p.arrayElement, !elementIsLeaf && styles$p.arrayElementAndNotLeaf)
25128
25127
  }, /*#__PURE__*/React.createElement(NodeContainer, _extends({}, otherProps, {
25129
25128
  key: i,
25130
25129
  shape: shape.elementShape,
@@ -25150,7 +25149,7 @@ const ObjectNodeContent = props => {
25150
25149
  // made sense to the shape's author is the order in which we'll iterate :)
25151
25150
  const children = Object.keys(shape.shape).map(subkey => /*#__PURE__*/React.createElement("div", {
25152
25151
  key: subkey,
25153
- className: css(styles$o.objectElement)
25152
+ className: css(styles$p.objectElement)
25154
25153
  }, /*#__PURE__*/React.createElement(NodeContainer, _extends({}, otherProps, {
25155
25154
  shape: shape.shape[subkey],
25156
25155
  data: data[subkey],
@@ -25317,7 +25316,7 @@ class MultiRendererEditor extends React.Component {
25317
25316
  }, this._renderContent());
25318
25317
  }
25319
25318
  }
25320
- const styles$o = StyleSheet.create({
25319
+ const styles$p = StyleSheet.create({
25321
25320
  // This is used in a number of places throughout this file.
25322
25321
  container: {},
25323
25322
  // eslint-disable-next-line react-native/no-unused-styles
@@ -25469,47 +25468,47 @@ const StatefulArticleEditor = createReactClass({
25469
25468
  screen
25470
25469
  } = this.state;
25471
25470
  return /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("div", {
25472
- style: styles$n.controlBar
25471
+ style: styles$o.controlBar
25473
25472
  }, /*#__PURE__*/React.createElement("span", {
25474
- style: styles$n.controls
25473
+ style: styles$o.controls
25475
25474
  }, "Mode:", " ", /*#__PURE__*/React.createElement("span", {
25476
25475
  onClick: () => this.setState({
25477
25476
  mode: "edit"
25478
25477
  }),
25479
- style: mode === "edit" ? styles$n.controlSelected : styles$n.control
25478
+ style: mode === "edit" ? styles$o.controlSelected : styles$o.control
25480
25479
  }, "EDIT"), " | ", /*#__PURE__*/React.createElement("span", {
25481
25480
  onClick: () => this.setState({
25482
25481
  mode: "preview"
25483
25482
  }),
25484
- style: mode === "preview" ? styles$n.controlSelected : styles$n.control
25483
+ style: mode === "preview" ? styles$o.controlSelected : styles$o.control
25485
25484
  }, "PREVIEW"), " | ", /*#__PURE__*/React.createElement("span", {
25486
25485
  onClick: () => this.setState({
25487
25486
  mode: "json"
25488
25487
  }),
25489
- style: mode === "json" ? styles$n.controlSelected : styles$n.control
25488
+ style: mode === "json" ? styles$o.controlSelected : styles$o.control
25490
25489
  }, "JSON")), /*#__PURE__*/React.createElement("span", {
25491
- style: styles$n.controls
25490
+ style: styles$o.controls
25492
25491
  }, "Screen:", " ", /*#__PURE__*/React.createElement("span", {
25493
25492
  onClick: () => this.setState({
25494
25493
  screen: "phone"
25495
25494
  }),
25496
- style: screen === "phone" ? styles$n.controlSelected : styles$n.control
25495
+ style: screen === "phone" ? styles$o.controlSelected : styles$o.control
25497
25496
  }, "PHONE"), " | ", /*#__PURE__*/React.createElement("span", {
25498
25497
  onClick: () => this.setState({
25499
25498
  screen: "tablet"
25500
25499
  }),
25501
- style: screen === "tablet" ? styles$n.controlSelected : styles$n.control
25500
+ style: screen === "tablet" ? styles$o.controlSelected : styles$o.control
25502
25501
  }, "TABLET"), " | ", /*#__PURE__*/React.createElement("span", {
25503
25502
  onClick: () => this.setState({
25504
25503
  screen: "desktop"
25505
25504
  }),
25506
- style: screen === "desktop" ? styles$n.controlSelected : styles$n.control
25505
+ style: screen === "desktop" ? styles$o.controlSelected : styles$o.control
25507
25506
  }, "DESKTOP"))), /*#__PURE__*/React.createElement("div", {
25508
- style: styles$n.editor
25507
+ style: styles$o.editor
25509
25508
  }, /*#__PURE__*/React.createElement(ArticleEditor, this.state)));
25510
25509
  }
25511
25510
  });
25512
- const styles$n = {
25511
+ const styles$o = {
25513
25512
  controlBar: {
25514
25513
  marginBottom: 25,
25515
25514
  marginTop: 5
@@ -26999,10 +26998,10 @@ class GradedGroupEditor extends React.Component {
26999
26998
  }, /*#__PURE__*/React.createElement("div", {
27000
26999
  className: "perseus-widget-row"
27001
27000
  }, /*#__PURE__*/React.createElement("label", {
27002
- className: css(styles$m.title)
27001
+ className: css(styles$n.title)
27003
27002
  }, "Title:", " ", /*#__PURE__*/React.createElement(TextInput$6, {
27004
27003
  value: this.props.title,
27005
- className: css(styles$m.input)
27004
+ className: css(styles$n.input)
27006
27005
  // @ts-expect-error - TS2554 - Expected 3 arguments, but got 1.
27007
27006
  ,
27008
27007
  onChange: this.change("title")
@@ -27027,7 +27026,7 @@ class GradedGroupEditor extends React.Component {
27027
27026
  }, /*#__PURE__*/React.createElement(InlineIcon$4, iconPlus$1), " Add a hint"), this.props.hint && /*#__PURE__*/React.createElement("div", {
27028
27027
  className: "perseus-hint-editor"
27029
27028
  }, /*#__PURE__*/React.createElement("div", {
27030
- className: css(styles$m.hintsTitle)
27029
+ className: css(styles$n.hintsTitle)
27031
27030
  }, "Hint"), /*#__PURE__*/React.createElement(Editor, {
27032
27031
  ref: this.hintEditor,
27033
27032
  content: this.props.hint ? this.props.hint.content : "",
@@ -27064,7 +27063,7 @@ GradedGroupEditor.defaultProps = {
27064
27063
  images: {},
27065
27064
  hint: null
27066
27065
  };
27067
- const styles$m = StyleSheet.create({
27066
+ const styles$n = StyleSheet.create({
27068
27067
  title: {
27069
27068
  fontSize: 18,
27070
27069
  fontWeight: "bold"
@@ -28242,7 +28241,7 @@ class InputNumberEditor extends React.Component {
28242
28241
  super(...args);
28243
28242
  this.input = /*#__PURE__*/React.createRef();
28244
28243
  this.handleAnswerChange = str => {
28245
- const value = Util.firstNumericalParse(str) || 0;
28244
+ const value = Util.firstNumericalParse(str, this.context.strings) || 0;
28246
28245
  this.props.onChange({
28247
28246
  value: value
28248
28247
  });
@@ -28308,7 +28307,7 @@ class InputNumberEditor extends React.Component {
28308
28307
  defaultValue: this.props.maxError,
28309
28308
  "aria-label": "Max error",
28310
28309
  onBlur: e => {
28311
- const ans = "" + (Util.firstNumericalParse(e.target.value) || 0);
28310
+ const ans = "" + (Util.firstNumericalParse(e.target.value, this.context.strings) || 0);
28312
28311
  e.target.value = ans;
28313
28312
  this.props.onChange({
28314
28313
  maxError: ans
@@ -28346,6 +28345,7 @@ class InputNumberEditor extends React.Component {
28346
28345
  }), " ", "Right alignment")));
28347
28346
  }
28348
28347
  }
28348
+ InputNumberEditor.contextType = PerseusI18nContext;
28349
28349
  InputNumberEditor.widgetName = "input-number";
28350
28350
  InputNumberEditor.defaultProps = {
28351
28351
  value: 0,
@@ -28365,12 +28365,15 @@ InputNumberEditor.defaultProps = {
28365
28365
  function MathquillInput(props) {
28366
28366
  const mathFieldWrapperRef = useRef(null);
28367
28367
  const mathFieldInstance = useRef();
28368
+ const {
28369
+ strings
28370
+ } = useMathInputI18n();
28368
28371
  useEffect(() => {
28369
28372
  // If we have the mount for the input, but not the input
28370
28373
  // make a new Mathquill input
28371
28374
  if (mathFieldWrapperRef.current && !mathFieldInstance.current) {
28372
28375
  // Initialize MathQuill.MathField instance
28373
- mathFieldInstance.current = createMathField(mathFieldWrapperRef.current, baseConfig => _extends({}, baseConfig, {
28376
+ mathFieldInstance.current = createMathField(mathFieldWrapperRef.current, strings, baseConfig => _extends({}, baseConfig, {
28374
28377
  handlers: {
28375
28378
  edit: mathField => {
28376
28379
  // This handler is guaranteed to be called on change, but
@@ -28398,13 +28401,13 @@ function MathquillInput(props) {
28398
28401
  }
28399
28402
  });
28400
28403
  return /*#__PURE__*/React.createElement(View, {
28401
- style: styles$l.outerWrapper
28404
+ style: styles$m.outerWrapper
28402
28405
  }, /*#__PURE__*/React.createElement("span", {
28403
28406
  ref: mathFieldWrapperRef,
28404
28407
  className: "perseus-math-input mq-editable-field mq-math-mode"
28405
28408
  }));
28406
28409
  }
28407
- const styles$l = StyleSheet.create({
28410
+ const styles$m = StyleSheet.create({
28408
28411
  outerWrapper: {
28409
28412
  display: "inline-block",
28410
28413
  borderStyle: "solid",
@@ -29465,7 +29468,7 @@ const GraphPointsCountSelector = ({
29465
29468
  // Never uses placeholder, always has value
29466
29469
  ,
29467
29470
  placeholder: "",
29468
- style: styles$k.singleSelectShort
29471
+ style: styles$l.singleSelectShort
29469
29472
  }, [...[...Array(7).keys()].map(n => /*#__PURE__*/React.createElement(OptionItem, {
29470
29473
  key: n,
29471
29474
  value: `${n}`,
@@ -29476,7 +29479,7 @@ const GraphPointsCountSelector = ({
29476
29479
  label: "unlimited"
29477
29480
  })]);
29478
29481
  };
29479
- const styles$k = StyleSheet.create({
29482
+ const styles$l = StyleSheet.create({
29480
29483
  singleSelectShort: {
29481
29484
  // Non-standard spacing, but it's the smallest we can go
29482
29485
  // without running into styling issues with the dropdown.
@@ -29489,7 +29492,7 @@ const GraphTypeSelector = props => {
29489
29492
  selectedValue: props.graphType,
29490
29493
  onChange: props.onChange,
29491
29494
  placeholder: "Select a graph type",
29492
- style: styles$j.singleSelectShort
29495
+ style: styles$k.singleSelectShort
29493
29496
  }, /*#__PURE__*/React.createElement(OptionItem, {
29494
29497
  value: "linear",
29495
29498
  label: "Linear function"
@@ -29522,7 +29525,7 @@ const GraphTypeSelector = props => {
29522
29525
  label: "Angle"
29523
29526
  }));
29524
29527
  };
29525
- const styles$j = StyleSheet.create({
29528
+ const styles$k = StyleSheet.create({
29526
29529
  singleSelectShort: {
29527
29530
  // Non-standard spacing, but it's the smallest we can go
29528
29531
  // without running into styling issues with the dropdown.
@@ -29539,16 +29542,16 @@ const LabeledRow = props => {
29539
29542
  style
29540
29543
  } = props;
29541
29544
  return /*#__PURE__*/React.createElement("label", {
29542
- className: css(styles$i.label)
29545
+ className: css(styles$j.label)
29543
29546
  }, /*#__PURE__*/React.createElement(View, {
29544
- style: [styles$i.row, style]
29547
+ style: [styles$j.row, style]
29545
29548
  }, labelSide === "start" || /*#__PURE__*/React.createElement(LabelSmall, {
29546
- style: styles$i.spaceEnd
29549
+ style: styles$j.spaceEnd
29547
29550
  }, label), children, labelSide === "end" && /*#__PURE__*/React.createElement(LabelSmall, {
29548
- style: styles$i.spaceStart
29551
+ style: styles$j.spaceStart
29549
29552
  }, label)));
29550
29553
  };
29551
- const styles$i = StyleSheet.create({
29554
+ const styles$j = StyleSheet.create({
29552
29555
  label: {
29553
29556
  width: "fit-content"
29554
29557
  },
@@ -29942,10 +29945,10 @@ class InteractiveGraphSettings extends React.Component {
29942
29945
  onChange: this.change
29943
29946
  }))), /*#__PURE__*/React.createElement(LabeledRow, {
29944
29947
  label: "Background image URL:",
29945
- style: styles$h.resetSpaceTop
29948
+ style: styles$i.resetSpaceTop
29946
29949
  }, /*#__PURE__*/React.createElement("input", {
29947
29950
  type: "text",
29948
- className: css(styles$h.backgroundUrlInput),
29951
+ className: css(styles$i.backgroundUrlInput),
29949
29952
  ref: this.bgUrlRef,
29950
29953
  value: this.state.backgroundImage.url || "",
29951
29954
  onChange: e => {
@@ -29959,24 +29962,24 @@ class InteractiveGraphSettings extends React.Component {
29959
29962
  onKeyPress: this.changeBackgroundUrl,
29960
29963
  onBlur: this.changeBackgroundUrl
29961
29964
  }), /*#__PURE__*/React.createElement(InfoTip$c, null, /*#__PURE__*/React.createElement("p", null, "Create an image in graphie, or use the \"Add image\" function to create a background."))), /*#__PURE__*/React.createElement(View, {
29962
- style: styles$h.rulerSection
29965
+ style: styles$i.rulerSection
29963
29966
  }, /*#__PURE__*/React.createElement(View, {
29964
- style: styles$h.checkboxRow
29967
+ style: styles$i.checkboxRow
29965
29968
  }, /*#__PURE__*/React.createElement(PropCheckBox$7, {
29966
29969
  label: "Show ruler",
29967
29970
  showRuler: this.props.showRuler,
29968
29971
  onChange: this.change,
29969
- style: styles$h.resetSpaceTop
29972
+ style: styles$i.resetSpaceTop
29970
29973
  }), /*#__PURE__*/React.createElement(PropCheckBox$7, {
29971
29974
  label: "Show protractor",
29972
29975
  showProtractor: this.props.showProtractor,
29973
29976
  onChange: this.change,
29974
- style: styles$h.resetSpaceTop
29977
+ style: styles$i.resetSpaceTop
29975
29978
  })), this.props.showRuler && /*#__PURE__*/React.createElement(View, {
29976
- style: styles$h.spaceTop
29979
+ style: styles$i.spaceTop
29977
29980
  }, /*#__PURE__*/React.createElement(LabeledRow, {
29978
29981
  label: "Ruler label:",
29979
- style: styles$h.resetSpaceTop
29982
+ style: styles$i.resetSpaceTop
29980
29983
  }, /*#__PURE__*/React.createElement(SingleSelect, {
29981
29984
  id: "ruler-label-select",
29982
29985
  selectedValue: this.props.rulerLabel,
@@ -29986,7 +29989,7 @@ class InteractiveGraphSettings extends React.Component {
29986
29989
  });
29987
29990
  },
29988
29991
  placeholder: "None",
29989
- style: styles$h.singleSelectShort
29992
+ style: styles$i.singleSelectShort
29990
29993
  }, /*#__PURE__*/React.createElement(OptionItem, {
29991
29994
  value: "",
29992
29995
  label: "None",
@@ -30027,7 +30030,7 @@ class InteractiveGraphSettings extends React.Component {
30027
30030
  });
30028
30031
  },
30029
30032
  placeholder: "10",
30030
- style: styles$h.singleSelectShort
30033
+ style: styles$i.singleSelectShort
30031
30034
  }, [1, 2, 4, 8, 10, 16].map(value => /*#__PURE__*/React.createElement(OptionItem, {
30032
30035
  key: value,
30033
30036
  value: `${value}`,
@@ -30051,7 +30054,7 @@ InteractiveGraphSettings.defaultProps = {
30051
30054
  rulerLabel: "",
30052
30055
  rulerTicks: 10
30053
30056
  };
30054
- const styles$h = StyleSheet.create({
30057
+ const styles$i = StyleSheet.create({
30055
30058
  resetSpaceTop: {
30056
30059
  marginTop: 0
30057
30060
  },
@@ -30095,22 +30098,26 @@ const LockedFigureSelect = props => {
30095
30098
  onChange
30096
30099
  } = props;
30097
30100
  return /*#__PURE__*/React.createElement(View, {
30098
- style: styles$g.container
30101
+ style: styles$h.container
30099
30102
  }, /*#__PURE__*/React.createElement(ActionMenu, {
30100
30103
  menuText: "Add locked figure",
30101
- style: styles$g.addElementSelect
30104
+ style: styles$h.addElementSelect
30102
30105
  }, [/*#__PURE__*/React.createElement(ActionItem, {
30103
30106
  key: `${id}-point`,
30104
30107
  label: "Point",
30105
30108
  onClick: () => onChange("point")
30106
- }, "Point")]));
30109
+ }, "Point"), /*#__PURE__*/React.createElement(ActionItem, {
30110
+ key: `${id}-line`,
30111
+ label: "Line",
30112
+ onClick: () => onChange("line")
30113
+ }, "Line")]));
30107
30114
  };
30108
- const styles$g = StyleSheet.create({
30115
+ const styles$h = StyleSheet.create({
30109
30116
  container: {
30110
- marginTop: spacing.medium_16
30117
+ marginTop: spacing.xxxSmall_4
30111
30118
  },
30112
30119
  addElementSelect: {
30113
- backgroundColor: color.fadedBlue16,
30120
+ backgroundColor: color.fadedBlue8,
30114
30121
  borderRadius: spacing.xxxSmall_4
30115
30122
  }
30116
30123
  });
@@ -30195,7 +30202,7 @@ const strutStyle = size => {
30195
30202
  };
30196
30203
  };
30197
30204
 
30198
- const ColorCircle = props => {
30205
+ const ColorSwatch = props => {
30199
30206
  const {
30200
30207
  color: color$1,
30201
30208
  filled = true,
@@ -30205,15 +30212,15 @@ const ColorCircle = props => {
30205
30212
  "aria-label":
30206
30213
  // Don't include an aria label for decorative circles.
30207
30214
  !decorative ? `Color: ${color$1}, ${filled ? "filled" : "open"}` : undefined,
30208
- style: [styles$f.colorCircle, {
30209
- border: `4px solid ${color[color$1]}`,
30210
- backgroundColor: filled ? color[color$1] : color.white
30215
+ style: [styles$g.colorSwatch, {
30216
+ border: `4px solid ${lockedFigureColors[color$1]}`,
30217
+ backgroundColor: filled ? lockedFigureColors[color$1] : color.white
30211
30218
  }]
30212
30219
  });
30213
30220
  };
30214
- const styles$f = StyleSheet.create({
30215
- colorCircle: {
30216
- // Add a white outline so that the color circle is visible when
30221
+ const styles$g = StyleSheet.create({
30222
+ colorSwatch: {
30223
+ // Add a white outline so that the color swatch is visible when
30217
30224
  // the dropdown option is highlighted with its blue background.
30218
30225
  outline: `2px solid ${color.offWhite}`,
30219
30226
  borderRadius: "50%",
@@ -30222,6 +30229,7 @@ const styles$f = StyleSheet.create({
30222
30229
  }
30223
30230
  });
30224
30231
 
30232
+ const possibleColors = Object.keys(lockedFigureColors);
30225
30233
  const ColorSelect = props => {
30226
30234
  const {
30227
30235
  id,
@@ -30229,10 +30237,10 @@ const ColorSelect = props => {
30229
30237
  onChange
30230
30238
  } = props;
30231
30239
  return /*#__PURE__*/React.createElement(View, {
30232
- style: styles$e.row
30240
+ style: styles$f.row
30233
30241
  }, /*#__PURE__*/React.createElement(LabelMedium, {
30234
30242
  htmlFor: id,
30235
- style: styles$e.label,
30243
+ style: styles$f.label,
30236
30244
  tag: "label"
30237
30245
  }, "Color"), /*#__PURE__*/React.createElement(SingleSelect, {
30238
30246
  id: id,
@@ -30241,17 +30249,17 @@ const ColorSelect = props => {
30241
30249
  // Placeholder is required, but never gets used.
30242
30250
  ,
30243
30251
  placeholder: ""
30244
- }, lockedFigureColors.map(colorName => /*#__PURE__*/React.createElement(OptionItem, {
30252
+ }, possibleColors.map(colorName => /*#__PURE__*/React.createElement(OptionItem, {
30245
30253
  key: colorName,
30246
30254
  value: colorName,
30247
30255
  label: colorName,
30248
- leftAccessory: /*#__PURE__*/React.createElement(ColorCircle, {
30256
+ leftAccessory: /*#__PURE__*/React.createElement(ColorSwatch, {
30249
30257
  color: colorName,
30250
30258
  decorative: true
30251
30259
  })
30252
30260
  }, colorName))));
30253
30261
  };
30254
- const styles$e = StyleSheet.create({
30262
+ const styles$f = StyleSheet.create({
30255
30263
  row: {
30256
30264
  flexDirection: "row",
30257
30265
  alignItems: "center"
@@ -30302,7 +30310,7 @@ function focusWithChromeStickyFocusBugWorkaround(element) {
30302
30310
  });
30303
30311
  }
30304
30312
  function getValidNumberFromString(value) {
30305
- const parsed = parseInt(value);
30313
+ const parsed = parseFloat(value);
30306
30314
  // If the value is not a number, return 0.
30307
30315
  return isNaN(parsed) ? 0 : parsed;
30308
30316
  }
@@ -30392,28 +30400,28 @@ const LockedPointSettings = props => {
30392
30400
  , {
30393
30401
  className: "locked-figure-accordion"
30394
30402
  }, /*#__PURE__*/React.createElement(AccordionSection, {
30395
- style: [styles$d.container, style],
30396
- headerStyle: styles$d.accordionHeader,
30403
+ style: [styles$e.container, style],
30404
+ headerStyle: styles$e.accordionHeader,
30397
30405
  header:
30398
30406
  /*#__PURE__*/
30399
30407
  // Summary: Point, coords, color (filled/open)
30400
30408
  React.createElement(View, {
30401
- style: styles$d.row
30409
+ style: styles$e.row
30402
30410
  }, /*#__PURE__*/React.createElement(LabelLarge, null, `${label || "Point"} (${coord[0]}, ${coord[1]})`), /*#__PURE__*/React.createElement(Strut, {
30403
30411
  size: spacing.xSmall_8
30404
- }), toggled && /*#__PURE__*/React.createElement(ColorCircle, {
30412
+ }), toggled && /*#__PURE__*/React.createElement(ColorSwatch, {
30405
30413
  color: pointColor,
30406
30414
  filled: filled
30407
30415
  }))
30408
30416
  }, /*#__PURE__*/React.createElement(View, {
30409
- style: styles$d.accordionPanel
30417
+ style: styles$e.accordionPanel
30410
30418
  }, /*#__PURE__*/React.createElement(View, {
30411
- style: styles$d.row
30419
+ style: styles$e.row
30412
30420
  }, /*#__PURE__*/React.createElement(View, {
30413
- style: [styles$d.row, styles$d.spaceUnder]
30421
+ style: [styles$e.row, styles$e.spaceUnder]
30414
30422
  }, /*#__PURE__*/React.createElement(LabelMedium, {
30415
30423
  htmlFor: xCoordId,
30416
- style: styles$d.label,
30424
+ style: styles$e.label,
30417
30425
  tag: "label"
30418
30426
  }, "x Coord"), /*#__PURE__*/React.createElement(TextField, {
30419
30427
  id: xCoordId,
@@ -30421,14 +30429,14 @@ const LockedPointSettings = props => {
30421
30429
  value: coordState[0],
30422
30430
  onChange: newValue => handleCoordChange(newValue, 0),
30423
30431
  onBlur: handleBlur,
30424
- style: styles$d.textField
30432
+ style: styles$e.textField
30425
30433
  })), /*#__PURE__*/React.createElement(Strut, {
30426
30434
  size: spacing.medium_16
30427
30435
  }), /*#__PURE__*/React.createElement(View, {
30428
- style: [styles$d.row, styles$d.spaceUnder]
30436
+ style: [styles$e.row, styles$e.spaceUnder]
30429
30437
  }, /*#__PURE__*/React.createElement(LabelMedium, {
30430
30438
  htmlFor: yCoordId,
30431
- style: styles$d.label,
30439
+ style: styles$e.label,
30432
30440
  tag: "label"
30433
30441
  }, "y Coord"), /*#__PURE__*/React.createElement(TextField, {
30434
30442
  id: yCoordId,
@@ -30436,9 +30444,9 @@ const LockedPointSettings = props => {
30436
30444
  value: coordState[1],
30437
30445
  onChange: newValue => handleCoordChange(newValue, 1),
30438
30446
  onBlur: handleBlur,
30439
- style: styles$d.textField
30447
+ style: styles$e.textField
30440
30448
  }))), onToggle && /*#__PURE__*/React.createElement(View, {
30441
- style: [styles$d.row, styles$d.spaceUnder]
30449
+ style: [styles$e.row, styles$e.spaceUnder]
30442
30450
  }, /*#__PURE__*/React.createElement(Switch, {
30443
30451
  id: showPointToggleId,
30444
30452
  checked: !!toggled,
@@ -30462,15 +30470,15 @@ const LockedPointSettings = props => {
30462
30470
  filled: !newValue
30463
30471
  });
30464
30472
  },
30465
- style: styles$d.spaceUnder
30473
+ style: styles$e.spaceUnder
30466
30474
  })), onRemove && /*#__PURE__*/React.createElement(IconButton, {
30467
30475
  icon: trashIcon,
30468
30476
  "aria-label": `Delete locked point at ${coordState[0]}, ${coordState[1]}`,
30469
30477
  onClick: onRemove,
30470
- style: styles$d.deleteButton
30478
+ style: styles$e.deleteButton
30471
30479
  }))));
30472
30480
  };
30473
- const styles$d = StyleSheet.create({
30481
+ const styles$e = StyleSheet.create({
30474
30482
  container: {
30475
30483
  backgroundColor: color.fadedBlue8,
30476
30484
  marginBottom: spacing.small_12
@@ -30479,7 +30487,7 @@ const styles$d = StyleSheet.create({
30479
30487
  paddingTop: spacing.small_12,
30480
30488
  paddingBottom: spacing.small_12,
30481
30489
  paddingInlineStart: spacing.medium_16,
30482
- // Fixed height so the addition of the color circle doesn't
30490
+ // Fixed height so the addition of the color swatch doesn't
30483
30491
  // change the height of the header when toggling.
30484
30492
  height: spacing.xxLarge_48
30485
30493
  },
@@ -30507,6 +30515,189 @@ const styles$d = StyleSheet.create({
30507
30515
  }
30508
30516
  });
30509
30517
 
30518
+ const LockedLineSettings = props => {
30519
+ const {
30520
+ kind,
30521
+ points,
30522
+ color: lineColor = "blue",
30523
+ lineStyle = "solid",
30524
+ showArrows,
30525
+ showStartPoint,
30526
+ showEndPoint,
30527
+ onChangeProps,
30528
+ onRemove
30529
+ } = props;
30530
+ const [startPoint, endPoint] = points;
30531
+
30532
+ // Generate unique IDs so that the programmatic labels can be associated
30533
+ // with their respective text fields.
30534
+ const ids = useUniqueIdWithMock();
30535
+ const kindSelectId = ids.get("line-kind-select");
30536
+ const colorSelectId = ids.get("line-color-select");
30537
+ const styleSelectId = ids.get("line-style-select");
30538
+ const lineLabel = `Line (${startPoint.coord[0]}, ${startPoint.coord[1]}),
30539
+ (${endPoint.coord[0]}, ${endPoint.coord[1]})`;
30540
+ function handleChangePoint(newPointProps, index) {
30541
+ const newPoints = [...points];
30542
+ newPoints[index] = _extends({}, points[index], newPointProps);
30543
+ onChangeProps({
30544
+ points: newPoints
30545
+ });
30546
+ }
30547
+ function handleColorChange(newColor) {
30548
+ onChangeProps({
30549
+ color: newColor,
30550
+ // Keep the line's points' colors in sync with the line color.
30551
+ points: [_extends({}, startPoint, {
30552
+ color: newColor
30553
+ }), _extends({}, endPoint, {
30554
+ color: newColor
30555
+ })]
30556
+ });
30557
+ }
30558
+ return /*#__PURE__*/React.createElement(View
30559
+ // More specificity so that we can override the default
30560
+ // .heading > h2 > .header styles from the articles.less
30561
+ // file (which is imported in perseus-renderer.less).
30562
+ , {
30563
+ className: "locked-figure-accordion"
30564
+ }, /*#__PURE__*/React.createElement(AccordionSection, {
30565
+ style: styles$d.container,
30566
+ headerStyle: styles$d.accordionHeader,
30567
+ header: /*#__PURE__*/React.createElement(View, {
30568
+ style: styles$d.row
30569
+ }, /*#__PURE__*/React.createElement(LabelLarge, null, lineLabel), /*#__PURE__*/React.createElement(Strut, {
30570
+ size: spacing.xSmall_8
30571
+ }), /*#__PURE__*/React.createElement(ColorSwatch, {
30572
+ color: lineColor
30573
+ }))
30574
+ }, /*#__PURE__*/React.createElement(View, {
30575
+ style: styles$d.accordionPanel
30576
+ }, /*#__PURE__*/React.createElement(View, {
30577
+ style: [styles$d.row, styles$d.spaceUnder]
30578
+ }, /*#__PURE__*/React.createElement(LabelMedium, {
30579
+ htmlFor: kindSelectId,
30580
+ style: styles$d.label,
30581
+ tag: "label"
30582
+ }, "Kind"), /*#__PURE__*/React.createElement(SingleSelect, {
30583
+ id: kindSelectId,
30584
+ selectedValue: kind,
30585
+ onChange: value => onChangeProps({
30586
+ kind: value
30587
+ })
30588
+ // Placeholder is required, but never gets used.
30589
+ ,
30590
+ placeholder: ""
30591
+ }, /*#__PURE__*/React.createElement(OptionItem, {
30592
+ value: "line",
30593
+ label: "line"
30594
+ }), /*#__PURE__*/React.createElement(OptionItem, {
30595
+ value: "segment",
30596
+ label: "segment"
30597
+ }), /*#__PURE__*/React.createElement(OptionItem, {
30598
+ value: "ray",
30599
+ label: "ray"
30600
+ }))), /*#__PURE__*/React.createElement(View, {
30601
+ style: [styles$d.row, styles$d.spaceUnder]
30602
+ }, /*#__PURE__*/React.createElement(ColorSelect, {
30603
+ id: colorSelectId,
30604
+ selectedValue: lineColor || "blue",
30605
+ onChange: handleColorChange
30606
+ }), /*#__PURE__*/React.createElement(Strut, {
30607
+ size: spacing.medium_16
30608
+ }), /*#__PURE__*/React.createElement(View, {
30609
+ style: styles$d.row
30610
+ }, /*#__PURE__*/React.createElement(LabelMedium, {
30611
+ htmlFor: styleSelectId,
30612
+ style: styles$d.label,
30613
+ tag: "label"
30614
+ }, "Style"), /*#__PURE__*/React.createElement(SingleSelect, {
30615
+ id: styleSelectId,
30616
+ selectedValue: lineStyle,
30617
+ onChange: value => onChangeProps({
30618
+ lineStyle: value
30619
+ })
30620
+ // Placeholder is required, but never gets used.
30621
+ ,
30622
+ placeholder: ""
30623
+ }, /*#__PURE__*/React.createElement(OptionItem, {
30624
+ value: "solid",
30625
+ label: "solid"
30626
+ }), /*#__PURE__*/React.createElement(OptionItem, {
30627
+ value: "dashed",
30628
+ label: "dashed"
30629
+ })))), /*#__PURE__*/React.createElement(Checkbox$1, {
30630
+ label: "Show arrows",
30631
+ checked: showArrows,
30632
+ onChange: newValue => onChangeProps({
30633
+ showArrows: newValue
30634
+ }),
30635
+ style: styles$d.spaceUnder
30636
+ }), /*#__PURE__*/React.createElement(LockedPointSettings, _extends({
30637
+ label: "Start point",
30638
+ toggled: showStartPoint
30639
+ }, startPoint, {
30640
+ onToggle: newValue => onChangeProps({
30641
+ showStartPoint: newValue
30642
+ }),
30643
+ onChangeProps: newProps => handleChangePoint(newProps, 0),
30644
+ style: styles$d.lockedPointSettingsContainer
30645
+ })), /*#__PURE__*/React.createElement(LockedPointSettings, _extends({
30646
+ label: "End point",
30647
+ toggled: showEndPoint
30648
+ }, endPoint, {
30649
+ onToggle: newValue => onChangeProps({
30650
+ showEndPoint: newValue
30651
+ }),
30652
+ onChangeProps: newProps => handleChangePoint(newProps, 1),
30653
+ style: styles$d.lockedPointSettingsContainer
30654
+ })), /*#__PURE__*/React.createElement(IconButton, {
30655
+ icon: trashIcon,
30656
+ "aria-label": `Delete locked line defined by
30657
+ ${startPoint.coord[0]}, ${startPoint.coord[1]} and
30658
+ ${endPoint.coord[0]}, ${endPoint.coord[1]}.`,
30659
+ onClick: onRemove,
30660
+ style: styles$d.deleteButton
30661
+ }))));
30662
+ };
30663
+ const styles$d = StyleSheet.create({
30664
+ container: {
30665
+ backgroundColor: color.fadedBlue8,
30666
+ marginBottom: spacing.xSmall_8
30667
+ },
30668
+ accordionHeader: {
30669
+ paddingTop: spacing.small_12,
30670
+ paddingBottom: spacing.small_12,
30671
+ paddingInlineStart: spacing.medium_16
30672
+ },
30673
+ accordionPanel: {
30674
+ padding: spacing.medium_16,
30675
+ paddingBottom: spacing.xSmall_8
30676
+ },
30677
+ lockedPointSettingsContainer: {
30678
+ marginTop: spacing.xSmall_8,
30679
+ marginBottom: spacing.xSmall_8,
30680
+ marginLeft: -spacing.xxxSmall_4,
30681
+ marginRight: -spacing.xxxSmall_4,
30682
+ backgroundColor: color.white
30683
+ },
30684
+ row: {
30685
+ flexDirection: "row",
30686
+ alignItems: "center"
30687
+ },
30688
+ spaceUnder: {
30689
+ marginBottom: spacing.xSmall_8
30690
+ },
30691
+ label: {
30692
+ marginInlineEnd: spacing.xSmall_8
30693
+ },
30694
+ deleteButton: {
30695
+ position: "absolute",
30696
+ top: spacing.small_12,
30697
+ right: spacing.small_12
30698
+ }
30699
+ });
30700
+
30510
30701
  /**
30511
30702
  * LockedFigureSettings takes in a figure and renders the
30512
30703
  * respective settings for that figure type.
@@ -30520,6 +30711,8 @@ const LockedFigureSettings = props => {
30520
30711
  switch (props.type) {
30521
30712
  case "point":
30522
30713
  return /*#__PURE__*/React.createElement(LockedPointSettings, props);
30714
+ case "line":
30715
+ return /*#__PURE__*/React.createElement(LockedLineSettings, props);
30523
30716
  }
30524
30717
  return null;
30525
30718
  };
@@ -30555,19 +30748,12 @@ const LockedFiguresSection = props => {
30555
30748
  }
30556
30749
  return /*#__PURE__*/React.createElement(View, {
30557
30750
  style: styles$c.container
30558
- }, figures == null ? void 0 : figures.map((figure, index) => {
30559
- // TODO(LEMS-1740): Remove this check when
30560
- // locked lines are added to LockedFigureSettings.
30561
- if (figure.type === "line") {
30562
- return;
30563
- }
30564
- return /*#__PURE__*/React.createElement(LockedFigureSettings, _extends({
30565
- key: `${uniqueId}-locked-${figure}-${index}`
30566
- }, figure, {
30567
- onChangeProps: newProps => changeProps(index, newProps),
30568
- onRemove: () => removeLockedFigure(index)
30569
- }));
30570
- }), /*#__PURE__*/React.createElement(LockedFigureSelect, {
30751
+ }, figures == null ? void 0 : figures.map((figure, index) => /*#__PURE__*/React.createElement(LockedFigureSettings, _extends({
30752
+ key: `${uniqueId}-locked-${figure}-${index}`
30753
+ }, figure, {
30754
+ onChangeProps: newProps => changeProps(index, newProps),
30755
+ onRemove: () => removeLockedFigure(index)
30756
+ }))), /*#__PURE__*/React.createElement(LockedFigureSelect, {
30571
30757
  id: `${uniqueId}-select`,
30572
30758
  onChange: addLockedFigure
30573
30759
  }));
@@ -30907,8 +31093,8 @@ class InteractiveGraphEditor extends React.Component {
30907
31093
  borderWidth: 1
30908
31094
  }
30909
31095
  }, equationString), /*#__PURE__*/React.createElement(InfoTip$b, null, /*#__PURE__*/React.createElement("p", null, "Graph the correct answer in the graph below and ensure the equation or point coordinates displayed represent the correct answer."))), graph,
30910
- // Only show the "Add locked figure" dropdown if the graph is
30911
- // using Mafs.
31096
+ // Only show the "Add locked figure" dropdown if the graph
31097
+ // is using Mafs.
30912
31098
  this.props.graph && ((_this$props$apiOption = this.props.apiOptions) == null || (_this$props$apiOption = _this$props$apiOption.flags) == null || (_this$props$apiOption = _this$props$apiOption.mafs) == null ? void 0 : _this$props$apiOption["interactive-graph-locked-features-m1"]) && ((_this$props$apiOption2 = this.props.apiOptions) == null || (_this$props$apiOption2 = _this$props$apiOption2.flags) == null || (_this$props$apiOption2 = _this$props$apiOption2.mafs) == null ? void 0 : _this$props$apiOption2[this.props.graph.type]) && /*#__PURE__*/React.createElement(LockedFiguresSection, {
30913
31099
  figures: this.props.lockedFigures,
30914
31100
  onChange: this.props.onChange
@@ -32131,8 +32317,8 @@ class QuestionMarkers extends React.Component {
32131
32317
  // Open answer selection dropdown for each of the specified markers.
32132
32318
  indices.forEach(index => {
32133
32319
  if (this._markers[index]) {
32134
- // @ts-expect-error - TS2533 - Object is possibly 'null' or 'undefined'.
32135
- this._markers[index].openDropdown();
32320
+ var _this$_markers$index;
32321
+ (_this$_markers$index = this._markers[index]) == null || _this$_markers$index.openDropdown();
32136
32322
  }
32137
32323
  });
32138
32324
  }
@@ -33135,6 +33321,7 @@ NumberLineEditor.defaultProps = {
33135
33321
  showTooltips: false
33136
33322
  };
33137
33323
 
33324
+ /* eslint-disable jsx-a11y/anchor-is-valid, react/sort-comp */
33138
33325
  const {
33139
33326
  ButtonGroup,
33140
33327
  InfoTip: InfoTip$7,
@@ -33151,6 +33338,9 @@ const {
33151
33338
  const {
33152
33339
  firstNumericalParse
33153
33340
  } = Util;
33341
+
33342
+ // NOTE(john): Copied from perseus-types.d.ts in the Perseus package.
33343
+ // I'm unable to find a good way of importing these types into this project.
33154
33344
  const answerFormButtons = [{
33155
33345
  title: "Integers",
33156
33346
  value: "integer",
@@ -33187,28 +33377,15 @@ const initAnswer = status => {
33187
33377
  maxError: null
33188
33378
  };
33189
33379
  };
33190
- const NumericInputEditor = createReactClass({
33191
- displayName: "NumericInputEditor",
33192
- propTypes: _extends({}, Changeable.propTypes),
33193
- statics: {
33194
- widgetName: "numeric-input"
33195
- },
33196
- getDefaultProps: function () {
33197
- return {
33198
- answers: [initAnswer("correct")],
33199
- size: "normal",
33200
- coefficient: false,
33201
- labelText: "",
33202
- rightAlign: false
33203
- };
33204
- },
33205
- getInitialState: function () {
33206
- return {
33380
+ class NumericInputEditor extends React.Component {
33381
+ constructor(props) {
33382
+ super(props);
33383
+ this.state = {
33207
33384
  lastStatus: "wrong",
33208
33385
  showOptions: _.map(this.props.answers, () => false)
33209
33386
  };
33210
- },
33211
- render: function () {
33387
+ }
33388
+ render() {
33212
33389
  const answers = this.props.answers;
33213
33390
  const unsimplifiedAnswers = i => /*#__PURE__*/React.createElement("div", {
33214
33391
  className: "perseus-widget-row"
@@ -33225,14 +33402,14 @@ const NumericInputEditor = createReactClass({
33225
33402
  value: "enforced",
33226
33403
  content: "wrong"
33227
33404
  }],
33228
- onChange: this.updateAnswer(i, "simplify")
33405
+ onChange: this.updateAnswer(i, "simplify") || (() => {})
33229
33406
  }), /*#__PURE__*/React.createElement(InfoTip$7, null, /*#__PURE__*/React.createElement("p", null, "Normally select \"ungraded\". This will give the user a message saying the answer is correct but not simplified. The user will then have to simplify it and re-enter, but will not be penalized. (5th grade and after)"), /*#__PURE__*/React.createElement("p", null, "Select \"accepted\" only if the user is not expected to know how to simplify fractions yet. (Anything prior to 5th grade)"), /*#__PURE__*/React.createElement("p", null, "Select \"wrong\" ", /*#__PURE__*/React.createElement("em", null, "only"), " if we are specifically assessing the ability to simplify.")));
33230
33407
  const suggestedAnswerTypes = i => /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("div", {
33231
33408
  className: "perseus-widget-row"
33232
33409
  }, /*#__PURE__*/React.createElement("label", null, "Choose the suggested answer formats"), /*#__PURE__*/React.createElement(MultiButtonGroup, {
33233
33410
  buttons: answerFormButtons,
33234
33411
  values: answers[i]["answerForms"],
33235
- onChange: this.updateAnswer(i, "answerForms")
33412
+ onChange: this.updateAnswer(i, "answerForms") || (() => {})
33236
33413
  }), /*#__PURE__*/React.createElement(InfoTip$7, null, /*#__PURE__*/React.createElement("p", null, "Formats will be autoselected for you based on the given answer; to show no suggested formats and accept all types, simply have a decimal/integer be the answer. Values with \u03C0 will have format \"pi\", and values that are fractions will have some subset (mixed will be \"mixed\" and \"proper\"; improper/proper will both be \"improper\" and \"proper\"). If you would like to specify that it is only a proper fraction (or only a mixed/improper fraction), deselect the other format. Except for specific cases, you should not need to change the autoselected formats."), /*#__PURE__*/React.createElement("p", null, "To restrict the answer to ", /*#__PURE__*/React.createElement("em", null, "only"), " an improper fraction (i.e. 7/4), select the improper fraction and toggle \"strict\" to true. This ", /*#__PURE__*/React.createElement("b", null, "will not"), " ", "accept 1.75 as an answer.", " "), /*#__PURE__*/React.createElement("p", null, "Unless you are testing that specific skill, please do not restrict the answer format."))), /*#__PURE__*/React.createElement("div", {
33237
33414
  className: "perseus-widget-row"
33238
33415
  }, /*#__PURE__*/React.createElement(PropCheckBox$3, {
@@ -33318,7 +33495,7 @@ const NumericInputEditor = createReactClass({
33318
33495
  value: answer.value,
33319
33496
  className: "numeric-input-value",
33320
33497
  placeholder: "answer",
33321
- format: _.last(answer.answerForms),
33498
+ format: _.last(answer.answerForms || []),
33322
33499
  onFormatChange: (newValue, format) => {
33323
33500
  // NOTE(charlie): The mobile web expression
33324
33501
  // editor relies on this automatic answer
@@ -33342,13 +33519,13 @@ const NumericInputEditor = createReactClass({
33342
33519
  forms = ["proper", "improper"];
33343
33520
  }
33344
33521
  this.updateAnswer(i, {
33345
- value: firstNumericalParse(newValue),
33522
+ value: firstNumericalParse(newValue, this.context.strings),
33346
33523
  answerForms: forms
33347
33524
  });
33348
33525
  },
33349
33526
  onChange: newValue => {
33350
33527
  this.updateAnswer(i, {
33351
- value: firstNumericalParse(newValue)
33528
+ value: firstNumericalParse(newValue, this.context.strings)
33352
33529
  });
33353
33530
  }
33354
33531
  }), answer.strict && /*#__PURE__*/React.createElement("div", {
@@ -33364,7 +33541,7 @@ const NumericInputEditor = createReactClass({
33364
33541
  }, "\xB1"), /*#__PURE__*/React.createElement(NumberInput$5, {
33365
33542
  placeholder: 0,
33366
33543
  value: answers[i]["maxError"],
33367
- format: _.last(answer.answerForms),
33544
+ format: _.last(answer.answerForms || []),
33368
33545
  onChange: this.updateAnswer(i, "maxError")
33369
33546
  })) : null, /*#__PURE__*/React.createElement("div", {
33370
33547
  className: "value-divider"
@@ -33372,19 +33549,19 @@ const NumericInputEditor = createReactClass({
33372
33549
  href: "javascript:void(0)",
33373
33550
  className: "answer-status " + answer.status,
33374
33551
  onClick: () => this.onStatusChange(i),
33375
- onKeyDown: e => this.onSpace(e, this.onStatusChange, i)
33552
+ onKeyDown: e => this.onSpace(e, this.onStatusChange)
33376
33553
  }, answer.status), /*#__PURE__*/React.createElement("a", {
33377
33554
  href: "javascript:void(0)",
33378
33555
  className: "answer-trash",
33379
33556
  "aria-label": "Delete answer",
33380
33557
  onClick: () => this.onTrashAnswer(i),
33381
- onKeyDown: e => this.onSpace(e, this.onTrashAnswer, i)
33558
+ onKeyDown: e => this.onSpace(e, this.onTrashAnswer)
33382
33559
  }, /*#__PURE__*/React.createElement(InlineIcon$2, iconTrash$1)), /*#__PURE__*/React.createElement("a", {
33383
33560
  href: "javascript:void(0)",
33384
33561
  className: "options-toggle",
33385
33562
  "aria-label": "Toggle options",
33386
33563
  onClick: () => this.onToggleOptions(i),
33387
- onKeyDown: e => this.onSpace(e, this.onToggleOptions, i)
33564
+ onKeyDown: e => this.onSpace(e, this.onToggleOptions)
33388
33565
  }, /*#__PURE__*/React.createElement(InlineIcon$2, iconGear))), /*#__PURE__*/React.createElement("div", {
33389
33566
  className: "input-answer-editor-message"
33390
33567
  }, editor), this.state.showOptions[i] && /*#__PURE__*/React.createElement("div", {
@@ -33398,18 +33575,18 @@ const NumericInputEditor = createReactClass({
33398
33575
  }, "User input"), /*#__PURE__*/React.createElement("div", {
33399
33576
  className: "msg-title"
33400
33577
  }, "Message shown to user on attempt"), generateInputAnswerEditors(), addAnswerButton, inputSize, rightAlign, coefficientCheck, labelText);
33401
- },
33578
+ }
33402
33579
  change(...args) {
33403
33580
  return Changeable.change.apply(this, args);
33404
- },
33405
- onToggleOptions: function (choiceIndex) {
33581
+ }
33582
+ onToggleOptions(choiceIndex) {
33406
33583
  const showOptions = this.state.showOptions.slice();
33407
33584
  showOptions[choiceIndex] = !showOptions[choiceIndex];
33408
33585
  this.setState({
33409
33586
  showOptions: showOptions
33410
33587
  });
33411
- },
33412
- onTrashAnswer: function (choiceIndex) {
33588
+ }
33589
+ onTrashAnswer(choiceIndex) {
33413
33590
  if (choiceIndex >= 0 && choiceIndex < this.props.answers.length) {
33414
33591
  const answers = this.props.answers.slice(0);
33415
33592
  answers.splice(choiceIndex, 1);
@@ -33417,16 +33594,16 @@ const NumericInputEditor = createReactClass({
33417
33594
  answers: answers
33418
33595
  });
33419
33596
  }
33420
- },
33421
- onSpace: function (e, callback) {
33597
+ }
33598
+ onSpace(e, callback) {
33422
33599
  if (e.key === " ") {
33423
33600
  e.preventDefault(); // prevent page shifting
33424
33601
  // eslint-disable-next-line prefer-rest-params
33425
33602
  const args = _.toArray(arguments).slice(2);
33426
33603
  callback.apply(this, args);
33427
33604
  }
33428
- },
33429
- onStatusChange: function (choiceIndex) {
33605
+ }
33606
+ onStatusChange(choiceIndex) {
33430
33607
  const statuses = ["wrong", "ungraded", "correct"];
33431
33608
  const answers = this.props.answers;
33432
33609
  const i = _.indexOf(statuses, answers[choiceIndex].status);
@@ -33435,8 +33612,8 @@ const NumericInputEditor = createReactClass({
33435
33612
  status: newStatus,
33436
33613
  simplify: newStatus === "correct" ? "required" : "accepted"
33437
33614
  });
33438
- },
33439
- updateAnswer: function (choiceIndex, update) {
33615
+ }
33616
+ updateAnswer(choiceIndex, update) {
33440
33617
  if (!_.isObject(update)) {
33441
33618
  return _.partial((choiceIndex, key, value) => {
33442
33619
  const update = {};
@@ -33444,7 +33621,9 @@ const NumericInputEditor = createReactClass({
33444
33621
  this.updateAnswer(choiceIndex, update);
33445
33622
  }, choiceIndex, update);
33446
33623
  }
33447
- let answers = _.clone(this.props.answers);
33624
+ let answers = [
33625
+ // Have to do this to remove the `readonly` state from the prop
33626
+ ...this.props.answers];
33448
33627
 
33449
33628
  // Don't bother to make a new answer box unless we are editing the last
33450
33629
  // one.
@@ -33457,15 +33636,15 @@ const NumericInputEditor = createReactClass({
33457
33636
  this.props.onChange({
33458
33637
  answers: answers
33459
33638
  });
33460
- },
33461
- addAnswer: function () {
33639
+ }
33640
+ addAnswer() {
33462
33641
  const lastAnswer = initAnswer(this.state.lastStatus);
33463
33642
  const answers = this.props.answers.concat(lastAnswer);
33464
33643
  this.props.onChange({
33465
33644
  answers: answers
33466
33645
  });
33467
- },
33468
- getSaveWarnings: function () {
33646
+ }
33647
+ getSaveWarnings() {
33469
33648
  // Filter out all the empty answers
33470
33649
  const warnings = [];
33471
33650
  // TODO(emily): This doesn't actually work, because the value is either
@@ -33483,11 +33662,21 @@ const NumericInputEditor = createReactClass({
33483
33662
  }
33484
33663
  });
33485
33664
  return warnings;
33486
- },
33665
+ }
33487
33666
  serialize() {
33488
33667
  return EditorJsonify.serialize.call(this);
33489
33668
  }
33490
- });
33669
+ }
33670
+ NumericInputEditor.contextType = PerseusI18nContext;
33671
+ NumericInputEditor.widgetName = "numeric-input";
33672
+ NumericInputEditor.displayName = "NumericInputEditor";
33673
+ NumericInputEditor.defaultProps = {
33674
+ answers: [initAnswer("correct")],
33675
+ size: "normal",
33676
+ coefficient: false,
33677
+ labelText: "",
33678
+ rightAlign: false
33679
+ };
33491
33680
 
33492
33681
  /* eslint-disable @khanacademy/ts-no-error-suppressions */
33493
33682
  const {
@@ -34160,9 +34349,7 @@ class ChoiceEditor extends React.Component {
34160
34349
  apiOptions: this.props.apiOptions,
34161
34350
  content: this.props.choice.clue || "",
34162
34351
  widgetEnabled: false,
34163
- placeholder: i18n$1._("Why is this choice %(correctOrIncorrect)s?", {
34164
- correctOrIncorrect: checkedClass
34165
- }),
34352
+ placeholder: `Why is this choice ${checkedClass}?`,
34166
34353
  onChange: this.props.onClueChange
34167
34354
  });
34168
34355
  const deleteLink = /*#__PURE__*/React.createElement("a", {
@@ -34407,7 +34594,7 @@ RadioEditor.propTypes = _extends({}, Changeable.propTypes, {
34407
34594
  });
34408
34595
  RadioEditor.widgetName = "radio";
34409
34596
  RadioEditor.defaultProps = {
34410
- choices: [{}, {}],
34597
+ choices: [{}, {}, {}, {}],
34411
34598
  displayCount: null,
34412
34599
  randomize: false,
34413
34600
  hasNoneOfTheAbove: false,