@khanacademy/perseus 23.5.0 → 23.6.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
@@ -21203,251 +21203,6 @@ const styles$B = {
21203
21203
  }
21204
21204
  };
21205
21205
 
21206
- /*
21207
- * In this file, an `expression` is some portion of valid TeX enclosed in
21208
- * curly brackets.
21209
- */
21210
-
21211
- /*
21212
- * Find the index at which an expression ends, i.e., has an unmatched
21213
- * closing curly bracket. This method assumes that we start with a non-open
21214
- * bracket character and end when we've seen more left than right brackets
21215
- * (rather than assuming that we start with a bracket character and wait for
21216
- * bracket equality).
21217
- */
21218
- function findEndpoint(tex, currentIndex) {
21219
- let bracketDepth = 0;
21220
- for (let i = currentIndex, len = tex.length; i < len; i++) {
21221
- const c = tex[i];
21222
- if (c === "{") {
21223
- bracketDepth++;
21224
- } else if (c === "}") {
21225
- bracketDepth--;
21226
- }
21227
- if (bracketDepth < 0) {
21228
- return i;
21229
- }
21230
- }
21231
- // If we never see unbalanced curly brackets, default to the
21232
- // entire string
21233
- return tex.length;
21234
- }
21235
-
21236
- /*
21237
- * Parses an individual set of curly brackets into TeX.
21238
- */
21239
- function parseNextExpression(tex, currentIndex, handler) {
21240
- // Find the first '{' and grab subsequent TeX
21241
- // Ex) tex: '{3}{7}', and we want the '3'
21242
- const openBracketIndex = tex.indexOf("{", currentIndex);
21243
- const nextExpIndex = openBracketIndex + 1;
21244
-
21245
- // Truncate to only contain remaining TeX
21246
- const endpoint = findEndpoint(tex, nextExpIndex);
21247
- const expressionTeX = tex.substring(nextExpIndex, endpoint);
21248
- const parsedExp = walkTex(expressionTeX, handler);
21249
- return {
21250
- endpoint: endpoint,
21251
- expression: parsedExp
21252
- };
21253
- }
21254
- function getNextFracIndex(tex, currentIndex) {
21255
- const dfrac = "\\dfrac";
21256
- const frac = "\\frac";
21257
- const nextFrac = tex.indexOf(frac, currentIndex);
21258
- const nextDFrac = tex.indexOf(dfrac, currentIndex);
21259
- if (nextFrac > -1 && nextDFrac > -1) {
21260
- return Math.min(nextFrac, nextDFrac);
21261
- }
21262
- if (nextFrac > -1) {
21263
- return nextFrac;
21264
- }
21265
- if (nextDFrac > -1) {
21266
- return nextDFrac;
21267
- }
21268
- return -1;
21269
- }
21270
- function walkTex(tex, handler) {
21271
- if (!tex) {
21272
- return "";
21273
- }
21274
-
21275
- // Ex) tex: '2 \dfrac {3}{7}'
21276
- let parsedString = "";
21277
- let currentIndex = 0;
21278
- let nextFrac = getNextFracIndex(tex, currentIndex);
21279
-
21280
- // For each \dfrac, find the two expressions (wrapped in {}) and recur
21281
- while (nextFrac > -1) {
21282
- // Gather first fragment, preceding \dfrac
21283
- // Ex) parsedString: '2 '
21284
- parsedString += tex.substring(currentIndex, nextFrac);
21285
-
21286
- // Remove everything preceding \dfrac, which has been parsed
21287
- currentIndex = nextFrac;
21288
-
21289
- // Parse first expression and move index past it
21290
- // Ex) firstParsedExpression.expression: '3'
21291
- const firstParsedExpression = parseNextExpression(tex, currentIndex, handler);
21292
- currentIndex = firstParsedExpression.endpoint + 1;
21293
-
21294
- // Parse second expression
21295
- // Ex) secondParsedExpression.expression: '7'
21296
- const secondParsedExpression = parseNextExpression(tex, currentIndex, handler);
21297
- currentIndex = secondParsedExpression.endpoint + 1;
21298
-
21299
- // Add expressions to running total of parsed expressions
21300
- if (parsedString.length) {
21301
- parsedString += " ";
21302
- }
21303
-
21304
- // Apply a custom handler based on the parsed subexpressions
21305
- parsedString += handler(firstParsedExpression.expression, secondParsedExpression.expression);
21306
-
21307
- // Find next DFrac, relative to currentIndex
21308
- nextFrac = getNextFracIndex(tex, currentIndex);
21309
- }
21310
-
21311
- // Add remaining TeX, which is \dfrac-free
21312
- parsedString += tex.slice(currentIndex);
21313
- return parsedString;
21314
- }
21315
-
21316
- /*
21317
- * Modify a TeX expression, returning another TeX expression. The resulting
21318
- * expression will have its innermost fractions stubbed out with \fracs
21319
- * (as opposed to \dfracs). All other content will remain untouched.
21320
- */
21321
- function modifyTex(tex) {
21322
- function isNestedFraction(tex) {
21323
- return tex.indexOf("\\frac") > -1 || tex.indexOf("\\dfrac") > -1;
21324
- }
21325
- const handler = function (exp1, exp2) {
21326
- let prefix;
21327
- if (isNestedFraction(exp1) || isNestedFraction(exp2)) {
21328
- prefix = "\\dfrac";
21329
- } else {
21330
- prefix = "\\frac";
21331
- }
21332
- return prefix + " {" + exp1 + "}{" + exp2 + "}";
21333
- };
21334
- return walkTex(tex, handler);
21335
- }
21336
-
21337
- /*
21338
- * Parse a TeX expression into something interpretable by input-number.
21339
- * The process is concerned with: (1) parsing fractions, i.e., \dfracs; and
21340
- * (2) removing backslash-escaping from certain characters (right now, only
21341
- * percent signs).
21342
- *
21343
- * The basic algorithm for handling \dfracs splits on \dfracs and then recurs
21344
- * on the subsequent "expressions", i.e., the {} pairs that follow \dfrac. The
21345
- * recursion is to allow for nested \dfrac elements.
21346
- *
21347
- * Backslash-escapes are removed with a simple search-and-replace.
21348
- */
21349
- function parseTex(tex) {
21350
- const handler = function (exp1, exp2) {
21351
- return exp1 + "/" + exp2;
21352
- };
21353
- const texWithoutFracs = walkTex(tex, handler);
21354
- return texWithoutFracs.replace("\\%", "%");
21355
- }
21356
- var TexWrangler = {
21357
- parseTex,
21358
- modifyTex
21359
- };
21360
-
21361
- /* eslint-disable react/sort-comp */
21362
- const ModifyTex = TexWrangler.modifyTex;
21363
- class MathOutput extends React__namespace.Component {
21364
- static defaultProps = {
21365
- value: "",
21366
- onFocus: function () {},
21367
- onBlur: function () {}
21368
- };
21369
- state = {
21370
- focused: false,
21371
- selectorNamespace: ___default["default"].uniqueId("math-output")
21372
- };
21373
- _getInputClassName = () => {
21374
- let className = "math-output " + ClassNames.INPUT + " " + ClassNames.INTERACTIVE;
21375
- if (this.state.focused) {
21376
- className += " " + ClassNames.FOCUSED;
21377
- }
21378
- if (this.props.className) {
21379
- className += " " + this.props.className;
21380
- }
21381
- return className;
21382
- };
21383
- _getDisplayValue = value => {
21384
- // Cast from (potentially a) number to string
21385
- let displayText;
21386
- if (value != null) {
21387
- displayText = "" + value;
21388
- } else {
21389
- displayText = "";
21390
- }
21391
- return ModifyTex(displayText);
21392
- };
21393
- render() {
21394
- const divStyle = {
21395
- textAlign: "center"
21396
- };
21397
- const {
21398
- TeX
21399
- } = getDependencies();
21400
- return /*#__PURE__*/React__namespace.createElement("span", {
21401
- // eslint-disable-next-line react/no-string-refs
21402
- ref: "input",
21403
- className: this._getInputClassName(),
21404
- "aria-label": this.props.labelText,
21405
- onMouseDown: this.focus,
21406
- onTouchStart: this.focus
21407
- }, /*#__PURE__*/React__namespace.createElement("div", {
21408
- style: divStyle
21409
- }, /*#__PURE__*/React__namespace.createElement(TeX, null, this._getDisplayValue(this.props.value))));
21410
- }
21411
- getValue = () => {
21412
- return this.props.value;
21413
- };
21414
- focus = () => {
21415
- if (!this.state.focused) {
21416
- this.props.onFocus();
21417
- this._bindBlurHandler();
21418
- this.setState({
21419
- focused: true
21420
- });
21421
- }
21422
- };
21423
- blur = () => {
21424
- if (this.state.focused) {
21425
- this.props.onBlur();
21426
- this._unbindBlurHandler();
21427
- this.setState({
21428
- focused: false
21429
- });
21430
- }
21431
- };
21432
- _bindBlurHandler = () => {
21433
- $__default["default"](document).bind("vclick." + this.state.selectorNamespace, e => {
21434
- // Detect whether the target has our React DOM node as a parent
21435
- const $closestWidget = $__default["default"](e.target).closest(
21436
- // @ts-expect-error - TS2769 - No overload matches this call.
21437
- ReactDOM__default["default"].findDOMNode(this));
21438
- if (!$closestWidget.length) {
21439
- this.blur();
21440
- }
21441
- });
21442
- };
21443
- _unbindBlurHandler = () => {
21444
- $__default["default"](document).unbind("." + this.state.selectorNamespace);
21445
- };
21446
- componentWillUnmount() {
21447
- this._unbindBlurHandler();
21448
- }
21449
- }
21450
-
21451
21206
  /* eslint-disable @khanacademy/ts-no-error-suppressions */
21452
21207
  let lastId = 0;
21453
21208
  function uniqueIdForInput() {
@@ -21788,7 +21543,6 @@ const {
21788
21543
  class InputWithExamples extends React__namespace.Component {
21789
21544
  static contextType = PerseusI18nContext;
21790
21545
  static defaultProps = {
21791
- type: "text",
21792
21546
  shouldShowExamples: true,
21793
21547
  onFocus: function () {},
21794
21548
  onBlur: function () {},
@@ -21804,12 +21558,6 @@ class InputWithExamples extends React__namespace.Component {
21804
21558
  return `input-with-examples-${btoa(this.props.id).replace(/=/g, "")}`;
21805
21559
  };
21806
21560
  _getInputClassName = () => {
21807
- // <MathOutput> is a special component that manages its own class and
21808
- // state, as it's a <span> that wants to act like an <input>.
21809
- if (this.props.type === "tex") {
21810
- return this.props.className;
21811
- }
21812
-
21813
21561
  // Otherwise, we need to add these INPUT and FOCUSED tags here.
21814
21562
  let className = ClassNames.INPUT + " " + ClassNames.INTERACTIVE;
21815
21563
  if (this.state.focused) {
@@ -21820,8 +21568,7 @@ class InputWithExamples extends React__namespace.Component {
21820
21568
  }
21821
21569
  return className;
21822
21570
  };
21823
- _getPropsForInputType = () => {
21824
- // Minimal set of props, used by each input type
21571
+ _renderInput = () => {
21825
21572
  const id = this._getUniqueId();
21826
21573
  const inputProps = {
21827
21574
  id: id,
@@ -21833,63 +21580,18 @@ class InputWithExamples extends React__namespace.Component {
21833
21580
  onFocus: this._handleFocus,
21834
21581
  onBlur: this._handleBlur,
21835
21582
  disabled: this.props.disabled,
21836
- style: this.props.style
21837
- };
21838
- if (this.props.type === "tex") {
21839
- return inputProps;
21840
- }
21841
-
21842
- // Add useful props required for MATH and TEXT modes
21843
- ___default["default"].extend(inputProps, {
21583
+ style: this.props.style,
21844
21584
  onChange: this.props.onChange,
21845
- onTouchStart: captureScratchpadTouchStart$2
21846
- });
21847
-
21848
- // And add final props that are MATH- and TEXT-specific
21849
- if (this.props.type === "math") {
21850
- return ___default["default"].extend({
21851
- buttonSet: this.props.buttonSet,
21852
- buttonsVisible: this.props.buttonsVisible,
21853
- convertDotToTimes: this.props.convertDotToTimes
21854
- }, inputProps);
21855
- }
21856
- if (this.props.type === "text") {
21857
- return ___default["default"].extend({
21858
- autoCapitalize: "off",
21859
- autoComplete: "off",
21860
- autoCorrect: "off",
21861
- spellCheck: "false"
21862
- }, inputProps);
21863
- }
21864
- };
21865
- _getComponentForInputType = () => {
21866
- switch (this.props.type) {
21867
- case "tex":
21868
- return MathOutput;
21869
- case "math":
21870
- return MathInput;
21871
- case "text":
21872
- return TextInput;
21873
- default:
21874
- this.props.type;
21875
- return null;
21876
- }
21877
- };
21878
- _renderInput = () => {
21879
- const inputProps = this._getPropsForInputType();
21880
- const InputComponent = this._getComponentForInputType();
21881
- return /*#__PURE__*/React__namespace.createElement(InputComponent, inputProps);
21585
+ onTouchStart: captureScratchpadTouchStart$2,
21586
+ autoCapitalize: "off",
21587
+ autoComplete: "off",
21588
+ autoCorrect: "off",
21589
+ spellCheck: "false"
21590
+ };
21591
+ return /*#__PURE__*/React__namespace.createElement(TextInput, inputProps);
21882
21592
  };
21883
21593
  render() {
21884
21594
  const input = this._renderInput();
21885
-
21886
- // Static rendering, which doesn't include the 'tooltip' logic that the
21887
- // other types require, and is hence handled separately.
21888
- if (this.props.type === "tex") {
21889
- return input;
21890
- }
21891
-
21892
- // Else, we need to be able to show examples
21893
21595
  const examplesContent = ___default["default"].map(this.props.examples, example => {
21894
21596
  return "- " + example;
21895
21597
  }).join("\n");
@@ -22025,6 +21727,161 @@ SimpleKeypadInput.propTypes = {
22025
21727
  value: PropTypes__default["default"].oneOfType([PropTypes__default["default"].string, PropTypes__default["default"].number])
22026
21728
  };
22027
21729
 
21730
+ /*
21731
+ * In this file, an `expression` is some portion of valid TeX enclosed in
21732
+ * curly brackets.
21733
+ */
21734
+
21735
+ /*
21736
+ * Find the index at which an expression ends, i.e., has an unmatched
21737
+ * closing curly bracket. This method assumes that we start with a non-open
21738
+ * bracket character and end when we've seen more left than right brackets
21739
+ * (rather than assuming that we start with a bracket character and wait for
21740
+ * bracket equality).
21741
+ */
21742
+ function findEndpoint(tex, currentIndex) {
21743
+ let bracketDepth = 0;
21744
+ for (let i = currentIndex, len = tex.length; i < len; i++) {
21745
+ const c = tex[i];
21746
+ if (c === "{") {
21747
+ bracketDepth++;
21748
+ } else if (c === "}") {
21749
+ bracketDepth--;
21750
+ }
21751
+ if (bracketDepth < 0) {
21752
+ return i;
21753
+ }
21754
+ }
21755
+ // If we never see unbalanced curly brackets, default to the
21756
+ // entire string
21757
+ return tex.length;
21758
+ }
21759
+
21760
+ /*
21761
+ * Parses an individual set of curly brackets into TeX.
21762
+ */
21763
+ function parseNextExpression(tex, currentIndex, handler) {
21764
+ // Find the first '{' and grab subsequent TeX
21765
+ // Ex) tex: '{3}{7}', and we want the '3'
21766
+ const openBracketIndex = tex.indexOf("{", currentIndex);
21767
+ const nextExpIndex = openBracketIndex + 1;
21768
+
21769
+ // Truncate to only contain remaining TeX
21770
+ const endpoint = findEndpoint(tex, nextExpIndex);
21771
+ const expressionTeX = tex.substring(nextExpIndex, endpoint);
21772
+ const parsedExp = walkTex(expressionTeX, handler);
21773
+ return {
21774
+ endpoint: endpoint,
21775
+ expression: parsedExp
21776
+ };
21777
+ }
21778
+ function getNextFracIndex(tex, currentIndex) {
21779
+ const dfrac = "\\dfrac";
21780
+ const frac = "\\frac";
21781
+ const nextFrac = tex.indexOf(frac, currentIndex);
21782
+ const nextDFrac = tex.indexOf(dfrac, currentIndex);
21783
+ if (nextFrac > -1 && nextDFrac > -1) {
21784
+ return Math.min(nextFrac, nextDFrac);
21785
+ }
21786
+ if (nextFrac > -1) {
21787
+ return nextFrac;
21788
+ }
21789
+ if (nextDFrac > -1) {
21790
+ return nextDFrac;
21791
+ }
21792
+ return -1;
21793
+ }
21794
+ function walkTex(tex, handler) {
21795
+ if (!tex) {
21796
+ return "";
21797
+ }
21798
+
21799
+ // Ex) tex: '2 \dfrac {3}{7}'
21800
+ let parsedString = "";
21801
+ let currentIndex = 0;
21802
+ let nextFrac = getNextFracIndex(tex, currentIndex);
21803
+
21804
+ // For each \dfrac, find the two expressions (wrapped in {}) and recur
21805
+ while (nextFrac > -1) {
21806
+ // Gather first fragment, preceding \dfrac
21807
+ // Ex) parsedString: '2 '
21808
+ parsedString += tex.substring(currentIndex, nextFrac);
21809
+
21810
+ // Remove everything preceding \dfrac, which has been parsed
21811
+ currentIndex = nextFrac;
21812
+
21813
+ // Parse first expression and move index past it
21814
+ // Ex) firstParsedExpression.expression: '3'
21815
+ const firstParsedExpression = parseNextExpression(tex, currentIndex, handler);
21816
+ currentIndex = firstParsedExpression.endpoint + 1;
21817
+
21818
+ // Parse second expression
21819
+ // Ex) secondParsedExpression.expression: '7'
21820
+ const secondParsedExpression = parseNextExpression(tex, currentIndex, handler);
21821
+ currentIndex = secondParsedExpression.endpoint + 1;
21822
+
21823
+ // Add expressions to running total of parsed expressions
21824
+ if (parsedString.length) {
21825
+ parsedString += " ";
21826
+ }
21827
+
21828
+ // Apply a custom handler based on the parsed subexpressions
21829
+ parsedString += handler(firstParsedExpression.expression, secondParsedExpression.expression);
21830
+
21831
+ // Find next DFrac, relative to currentIndex
21832
+ nextFrac = getNextFracIndex(tex, currentIndex);
21833
+ }
21834
+
21835
+ // Add remaining TeX, which is \dfrac-free
21836
+ parsedString += tex.slice(currentIndex);
21837
+ return parsedString;
21838
+ }
21839
+
21840
+ /*
21841
+ * Modify a TeX expression, returning another TeX expression. The resulting
21842
+ * expression will have its innermost fractions stubbed out with \fracs
21843
+ * (as opposed to \dfracs). All other content will remain untouched.
21844
+ */
21845
+ function modifyTex(tex) {
21846
+ function isNestedFraction(tex) {
21847
+ return tex.indexOf("\\frac") > -1 || tex.indexOf("\\dfrac") > -1;
21848
+ }
21849
+ const handler = function (exp1, exp2) {
21850
+ let prefix;
21851
+ if (isNestedFraction(exp1) || isNestedFraction(exp2)) {
21852
+ prefix = "\\dfrac";
21853
+ } else {
21854
+ prefix = "\\frac";
21855
+ }
21856
+ return prefix + " {" + exp1 + "}{" + exp2 + "}";
21857
+ };
21858
+ return walkTex(tex, handler);
21859
+ }
21860
+
21861
+ /*
21862
+ * Parse a TeX expression into something interpretable by input-number.
21863
+ * The process is concerned with: (1) parsing fractions, i.e., \dfracs; and
21864
+ * (2) removing backslash-escaping from certain characters (right now, only
21865
+ * percent signs).
21866
+ *
21867
+ * The basic algorithm for handling \dfracs splits on \dfracs and then recurs
21868
+ * on the subsequent "expressions", i.e., the {} pairs that follow \dfrac. The
21869
+ * recursion is to allow for nested \dfrac elements.
21870
+ *
21871
+ * Backslash-escapes are removed with a simple search-and-replace.
21872
+ */
21873
+ function parseTex(tex) {
21874
+ const handler = function (exp1, exp2) {
21875
+ return exp1 + "/" + exp2;
21876
+ };
21877
+ const texWithoutFracs = walkTex(tex, handler);
21878
+ return texWithoutFracs.replace("\\%", "%");
21879
+ }
21880
+ var TexWrangler = {
21881
+ parseTex,
21882
+ modifyTex
21883
+ };
21884
+
22028
21885
  /* eslint-disable react/sort-comp */
22029
21886
  const ParseTex$1 = TexWrangler.parseTex;
22030
21887
  const answerTypes = {
@@ -22145,7 +22002,6 @@ class InputNumber extends React__namespace.Component {
22145
22002
  value: this.props.currentValue,
22146
22003
  onChange: this.handleChange,
22147
22004
  style: inputStyles,
22148
- type: "text",
22149
22005
  examples: this.examples(),
22150
22006
  shouldShowExamples: this.shouldShowExamples(),
22151
22007
  onFocus: this._handleFocus,
@@ -22612,7 +22468,6 @@ class NumericInput extends React__namespace.Component {
22612
22468
  value: this.props.currentValue,
22613
22469
  onChange: this.handleChange,
22614
22470
  labelText: labelText,
22615
- type: "text",
22616
22471
  examples: this.examples(),
22617
22472
  shouldShowExamples: this.shouldShowExamples(),
22618
22473
  onFocus: this._handleFocus,
@@ -29410,6 +29265,10 @@ function snap(snapStep, point) {
29410
29265
  return [Math.round(requestedX / snapX) * snapX, Math.round(requestedY / snapY) * snapY];
29411
29266
  }
29412
29267
 
29268
+ function useDraggable(params) {
29269
+ return mafs.useMovable(params);
29270
+ }
29271
+
29413
29272
  // The hitbox size of 48px by 48px is preserved from the legacy interactive
29414
29273
  // graph.
29415
29274
  const hitboxSizePx = 48;
@@ -29565,7 +29424,7 @@ function useControlPoint(point, color, onMovePoint) {
29565
29424
  } = useGraphConfig();
29566
29425
  const [focused, setFocused] = React.useState(false);
29567
29426
  const keyboardHandleRef = React.useRef(null);
29568
- mafs.useMovable({
29427
+ useDraggable({
29569
29428
  gestureTarget: keyboardHandleRef,
29570
29429
  point,
29571
29430
  onMove: onMovePoint,
@@ -29574,7 +29433,7 @@ function useControlPoint(point, color, onMovePoint) {
29574
29433
  const visiblePointRef = React.useRef(null);
29575
29434
  const {
29576
29435
  dragging
29577
- } = mafs.useMovable({
29436
+ } = useDraggable({
29578
29437
  gestureTarget: visiblePointRef,
29579
29438
  point,
29580
29439
  onMove: onMovePoint,
@@ -29628,7 +29487,7 @@ const Line$1 = props => {
29628
29487
  const line = React.useRef(null);
29629
29488
  const {
29630
29489
  dragging
29631
- } = mafs.useMovable({
29490
+ } = useDraggable({
29632
29491
  gestureTarget: line,
29633
29492
  point: start,
29634
29493
  onMove: newPoint => {
@@ -29976,7 +29835,7 @@ const StyledMovablePoint = props => {
29976
29835
  const snapToValue = snapTo ?? "grid";
29977
29836
  const {
29978
29837
  dragging
29979
- } = mafs.useMovable({
29838
+ } = useDraggable({
29980
29839
  gestureTarget: elementRef,
29981
29840
  point,
29982
29841
  onMove,
@@ -30018,7 +29877,7 @@ const PolygonGraph = props => {
30018
29877
  const snapToValue = snapTo ?? "grid";
30019
29878
  const {
30020
29879
  dragging
30021
- } = mafs.useMovable({
29880
+ } = useDraggable({
30022
29881
  gestureTarget: ref,
30023
29882
  point: dragReferencePoint,
30024
29883
  onMove: newPoint => {
@@ -30193,7 +30052,7 @@ function MovableCircle(props) {
30193
30052
  const draggableRef = React.useRef(null);
30194
30053
  const {
30195
30054
  dragging
30196
- } = mafs.useMovable({
30055
+ } = useDraggable({
30197
30056
  gestureTarget: draggableRef,
30198
30057
  point: center,
30199
30058
  onMove,
@@ -30324,10 +30183,24 @@ function SinusoidGraph(props) {
30324
30183
  coords
30325
30184
  } = graphState;
30326
30185
 
30327
- // Get the coefficients for calculating the quadratic equation
30186
+ // The coefficients are used to calculate the sinusoid equation, plot the graph, and to indicate
30187
+ // to content creators the currently selected "correct answer" in the Content Editor.
30188
+ // While we should technically never have invalid coordinates, we want to ensure that
30189
+ // we have a fallback so that the graph can still be plotted without crashing.
30190
+ const coeffRef = React__namespace.useRef({
30191
+ amplitude: 1,
30192
+ angularFrequency: 1,
30193
+ phase: 1,
30194
+ verticalOffset: 0
30195
+ });
30328
30196
  const coeffs = getSinusoidCoefficients(coords);
30197
+
30198
+ // If the coefficients are valid, update the reference
30199
+ if (coeffs !== undefined) {
30200
+ coeffRef.current = coeffs;
30201
+ }
30329
30202
  return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(mafs.Plot.OfX, {
30330
- y: x => computeSine(x, coeffs),
30203
+ y: x => computeSine(x, coeffRef.current),
30331
30204
  color: wonderBlocksTokens.color.blue
30332
30205
  }), coords.map((coord, i) => /*#__PURE__*/React__namespace.createElement(StyledMovablePoint, {
30333
30206
  key: "point-" + i,
@@ -30354,6 +30227,11 @@ const getSinusoidCoefficients = coords => {
30354
30227
  const p1 = coords[0];
30355
30228
  const p2 = coords[1];
30356
30229
 
30230
+ // If the x-coordinates are the same, we are unable to calculate the coefficients
30231
+ if (p2[0] === p1[0]) {
30232
+ return;
30233
+ }
30234
+
30357
30235
  // Resulting coefficients are canonical for this sine curve
30358
30236
  const amplitude = p2[1] - p1[1];
30359
30237
  const angularFrequency = Math.PI / (2 * (p2[0] - p1[0]));
@@ -31847,11 +31725,14 @@ function doMovePoint(state, action) {
31847
31725
  }
31848
31726
  case "sinusoid":
31849
31727
  {
31850
- // First, we need to verify that the new coordinates are not on the same vertical line
31851
- // If they are, we don't want to move the point
31728
+ // First, we need to make sure to bound the new coordinates to the graph range
31852
31729
  const destination = action.destination;
31730
+ const boundDestination = boundAndSnapToGrid(destination, state);
31731
+
31732
+ // Then, we need to verify that the new coordinates are not on the same
31733
+ // vertical line. If they are, then we don't want to move the point
31853
31734
  const newCoords = [...state.coords];
31854
- newCoords[action.index] = action.destination;
31735
+ newCoords[action.index] = boundDestination;
31855
31736
  if (newCoords[0][0] === newCoords[1][0]) {
31856
31737
  return state;
31857
31738
  }
@@ -31861,7 +31742,7 @@ function doMovePoint(state, action) {
31861
31742
  coords: setAtIndex({
31862
31743
  array: state.coords,
31863
31744
  index: action.index,
31864
- newValue: boundAndSnapToGrid(destination, state)
31745
+ newValue: boundDestination
31865
31746
  })
31866
31747
  };
31867
31748
  }
@@ -31869,7 +31750,10 @@ function doMovePoint(state, action) {
31869
31750
  {
31870
31751
  // Set up the new coords and check if the quadratic coefficients are valid
31871
31752
  const newCoords = [...state.coords];
31872
- newCoords[action.index] = action.destination;
31753
+
31754
+ // Bind the new destination to the graph range/snapStep and then get the quadratic coefficients
31755
+ const boundDestination = boundAndSnapToGrid(action.destination, state);
31756
+ newCoords[action.index] = boundDestination;
31873
31757
  const QuadraticCoefficients = getQuadraticCoefficients(newCoords);
31874
31758
 
31875
31759
  // If the new destination results in an invalid quadratic equation, we don't want to move the point
@@ -31882,7 +31766,7 @@ function doMovePoint(state, action) {
31882
31766
  coords: setAtIndex({
31883
31767
  array: state.coords,
31884
31768
  index: action.index,
31885
- newValue: boundAndSnapToGrid(action.destination, state)
31769
+ newValue: boundDestination
31886
31770
  })
31887
31771
  };
31888
31772
  }
@@ -33698,6 +33582,12 @@ class InteractiveGraph extends React__namespace.Component {
33698
33582
  return InteractiveGraph.validate(this.getUserInput?.(), rubric, this);
33699
33583
  }
33700
33584
  render() {
33585
+ if (this.props.graph.type === "polygon" && this.props.graph.numSides === "unlimited") {
33586
+ return /*#__PURE__*/React__namespace.createElement(LegacyInteractiveGraph, _extends({
33587
+ ref: this.legacyGraphRef
33588
+ }, this.props));
33589
+ }
33590
+
33701
33591
  // Mafs shim
33702
33592
  if (this.props.apiOptions?.flags?.["mafs"]?.[this.props.graph.type]) {
33703
33593
  const box = getInteractiveBoxFromSizeClass(this.props.containerSizeClass);
@@ -44497,7 +44387,7 @@ const init = function (options) {
44497
44387
 
44498
44388
  // This file is processed by a Rollup plugin (replace) to inject the production
44499
44389
  const libName = "@khanacademy/perseus";
44500
- const libVersion = "23.5.0";
44390
+ const libVersion = "23.6.0";
44501
44391
  perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
44502
44392
 
44503
44393
  const apiVersion = {