@khanacademy/perseus 23.5.0 → 24.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/index.js CHANGED
@@ -634,8 +634,7 @@ class InnerMathInput extends React__namespace.Component {
634
634
  active: false
635
635
  }) : /*#__PURE__*/React__namespace.createElement(Clickable__default["default"], {
636
636
  "aria-label": this.state.keypadOpen ? this.context.strings.closeKeypad : this.context.strings.openKeypad,
637
- "aria-checked": this.state.keypadOpen,
638
- role: "switch",
637
+ role: "button",
639
638
  onClick: () => this.state.keypadOpen ? this.closeKeypad() : this.openKeypad()
640
639
  }, props => /*#__PURE__*/React__namespace.createElement(MathInputIcon, _extends({
641
640
  active: this.state.keypadOpen
@@ -21203,251 +21202,6 @@ const styles$B = {
21203
21202
  }
21204
21203
  };
21205
21204
 
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
21205
  /* eslint-disable @khanacademy/ts-no-error-suppressions */
21452
21206
  let lastId = 0;
21453
21207
  function uniqueIdForInput() {
@@ -21788,7 +21542,6 @@ const {
21788
21542
  class InputWithExamples extends React__namespace.Component {
21789
21543
  static contextType = PerseusI18nContext;
21790
21544
  static defaultProps = {
21791
- type: "text",
21792
21545
  shouldShowExamples: true,
21793
21546
  onFocus: function () {},
21794
21547
  onBlur: function () {},
@@ -21804,12 +21557,6 @@ class InputWithExamples extends React__namespace.Component {
21804
21557
  return `input-with-examples-${btoa(this.props.id).replace(/=/g, "")}`;
21805
21558
  };
21806
21559
  _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
21560
  // Otherwise, we need to add these INPUT and FOCUSED tags here.
21814
21561
  let className = ClassNames.INPUT + " " + ClassNames.INTERACTIVE;
21815
21562
  if (this.state.focused) {
@@ -21820,8 +21567,7 @@ class InputWithExamples extends React__namespace.Component {
21820
21567
  }
21821
21568
  return className;
21822
21569
  };
21823
- _getPropsForInputType = () => {
21824
- // Minimal set of props, used by each input type
21570
+ _renderInput = () => {
21825
21571
  const id = this._getUniqueId();
21826
21572
  const inputProps = {
21827
21573
  id: id,
@@ -21833,63 +21579,18 @@ class InputWithExamples extends React__namespace.Component {
21833
21579
  onFocus: this._handleFocus,
21834
21580
  onBlur: this._handleBlur,
21835
21581
  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, {
21582
+ style: this.props.style,
21844
21583
  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);
21584
+ onTouchStart: captureScratchpadTouchStart$2,
21585
+ autoCapitalize: "off",
21586
+ autoComplete: "off",
21587
+ autoCorrect: "off",
21588
+ spellCheck: "false"
21589
+ };
21590
+ return /*#__PURE__*/React__namespace.createElement(TextInput, inputProps);
21882
21591
  };
21883
21592
  render() {
21884
21593
  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
21594
  const examplesContent = ___default["default"].map(this.props.examples, example => {
21894
21595
  return "- " + example;
21895
21596
  }).join("\n");
@@ -22025,6 +21726,161 @@ SimpleKeypadInput.propTypes = {
22025
21726
  value: PropTypes__default["default"].oneOfType([PropTypes__default["default"].string, PropTypes__default["default"].number])
22026
21727
  };
22027
21728
 
21729
+ /*
21730
+ * In this file, an `expression` is some portion of valid TeX enclosed in
21731
+ * curly brackets.
21732
+ */
21733
+
21734
+ /*
21735
+ * Find the index at which an expression ends, i.e., has an unmatched
21736
+ * closing curly bracket. This method assumes that we start with a non-open
21737
+ * bracket character and end when we've seen more left than right brackets
21738
+ * (rather than assuming that we start with a bracket character and wait for
21739
+ * bracket equality).
21740
+ */
21741
+ function findEndpoint(tex, currentIndex) {
21742
+ let bracketDepth = 0;
21743
+ for (let i = currentIndex, len = tex.length; i < len; i++) {
21744
+ const c = tex[i];
21745
+ if (c === "{") {
21746
+ bracketDepth++;
21747
+ } else if (c === "}") {
21748
+ bracketDepth--;
21749
+ }
21750
+ if (bracketDepth < 0) {
21751
+ return i;
21752
+ }
21753
+ }
21754
+ // If we never see unbalanced curly brackets, default to the
21755
+ // entire string
21756
+ return tex.length;
21757
+ }
21758
+
21759
+ /*
21760
+ * Parses an individual set of curly brackets into TeX.
21761
+ */
21762
+ function parseNextExpression(tex, currentIndex, handler) {
21763
+ // Find the first '{' and grab subsequent TeX
21764
+ // Ex) tex: '{3}{7}', and we want the '3'
21765
+ const openBracketIndex = tex.indexOf("{", currentIndex);
21766
+ const nextExpIndex = openBracketIndex + 1;
21767
+
21768
+ // Truncate to only contain remaining TeX
21769
+ const endpoint = findEndpoint(tex, nextExpIndex);
21770
+ const expressionTeX = tex.substring(nextExpIndex, endpoint);
21771
+ const parsedExp = walkTex(expressionTeX, handler);
21772
+ return {
21773
+ endpoint: endpoint,
21774
+ expression: parsedExp
21775
+ };
21776
+ }
21777
+ function getNextFracIndex(tex, currentIndex) {
21778
+ const dfrac = "\\dfrac";
21779
+ const frac = "\\frac";
21780
+ const nextFrac = tex.indexOf(frac, currentIndex);
21781
+ const nextDFrac = tex.indexOf(dfrac, currentIndex);
21782
+ if (nextFrac > -1 && nextDFrac > -1) {
21783
+ return Math.min(nextFrac, nextDFrac);
21784
+ }
21785
+ if (nextFrac > -1) {
21786
+ return nextFrac;
21787
+ }
21788
+ if (nextDFrac > -1) {
21789
+ return nextDFrac;
21790
+ }
21791
+ return -1;
21792
+ }
21793
+ function walkTex(tex, handler) {
21794
+ if (!tex) {
21795
+ return "";
21796
+ }
21797
+
21798
+ // Ex) tex: '2 \dfrac {3}{7}'
21799
+ let parsedString = "";
21800
+ let currentIndex = 0;
21801
+ let nextFrac = getNextFracIndex(tex, currentIndex);
21802
+
21803
+ // For each \dfrac, find the two expressions (wrapped in {}) and recur
21804
+ while (nextFrac > -1) {
21805
+ // Gather first fragment, preceding \dfrac
21806
+ // Ex) parsedString: '2 '
21807
+ parsedString += tex.substring(currentIndex, nextFrac);
21808
+
21809
+ // Remove everything preceding \dfrac, which has been parsed
21810
+ currentIndex = nextFrac;
21811
+
21812
+ // Parse first expression and move index past it
21813
+ // Ex) firstParsedExpression.expression: '3'
21814
+ const firstParsedExpression = parseNextExpression(tex, currentIndex, handler);
21815
+ currentIndex = firstParsedExpression.endpoint + 1;
21816
+
21817
+ // Parse second expression
21818
+ // Ex) secondParsedExpression.expression: '7'
21819
+ const secondParsedExpression = parseNextExpression(tex, currentIndex, handler);
21820
+ currentIndex = secondParsedExpression.endpoint + 1;
21821
+
21822
+ // Add expressions to running total of parsed expressions
21823
+ if (parsedString.length) {
21824
+ parsedString += " ";
21825
+ }
21826
+
21827
+ // Apply a custom handler based on the parsed subexpressions
21828
+ parsedString += handler(firstParsedExpression.expression, secondParsedExpression.expression);
21829
+
21830
+ // Find next DFrac, relative to currentIndex
21831
+ nextFrac = getNextFracIndex(tex, currentIndex);
21832
+ }
21833
+
21834
+ // Add remaining TeX, which is \dfrac-free
21835
+ parsedString += tex.slice(currentIndex);
21836
+ return parsedString;
21837
+ }
21838
+
21839
+ /*
21840
+ * Modify a TeX expression, returning another TeX expression. The resulting
21841
+ * expression will have its innermost fractions stubbed out with \fracs
21842
+ * (as opposed to \dfracs). All other content will remain untouched.
21843
+ */
21844
+ function modifyTex(tex) {
21845
+ function isNestedFraction(tex) {
21846
+ return tex.indexOf("\\frac") > -1 || tex.indexOf("\\dfrac") > -1;
21847
+ }
21848
+ const handler = function (exp1, exp2) {
21849
+ let prefix;
21850
+ if (isNestedFraction(exp1) || isNestedFraction(exp2)) {
21851
+ prefix = "\\dfrac";
21852
+ } else {
21853
+ prefix = "\\frac";
21854
+ }
21855
+ return prefix + " {" + exp1 + "}{" + exp2 + "}";
21856
+ };
21857
+ return walkTex(tex, handler);
21858
+ }
21859
+
21860
+ /*
21861
+ * Parse a TeX expression into something interpretable by input-number.
21862
+ * The process is concerned with: (1) parsing fractions, i.e., \dfracs; and
21863
+ * (2) removing backslash-escaping from certain characters (right now, only
21864
+ * percent signs).
21865
+ *
21866
+ * The basic algorithm for handling \dfracs splits on \dfracs and then recurs
21867
+ * on the subsequent "expressions", i.e., the {} pairs that follow \dfrac. The
21868
+ * recursion is to allow for nested \dfrac elements.
21869
+ *
21870
+ * Backslash-escapes are removed with a simple search-and-replace.
21871
+ */
21872
+ function parseTex(tex) {
21873
+ const handler = function (exp1, exp2) {
21874
+ return exp1 + "/" + exp2;
21875
+ };
21876
+ const texWithoutFracs = walkTex(tex, handler);
21877
+ return texWithoutFracs.replace("\\%", "%");
21878
+ }
21879
+ var TexWrangler = {
21880
+ parseTex,
21881
+ modifyTex
21882
+ };
21883
+
22028
21884
  /* eslint-disable react/sort-comp */
22029
21885
  const ParseTex$1 = TexWrangler.parseTex;
22030
21886
  const answerTypes = {
@@ -22145,7 +22001,6 @@ class InputNumber extends React__namespace.Component {
22145
22001
  value: this.props.currentValue,
22146
22002
  onChange: this.handleChange,
22147
22003
  style: inputStyles,
22148
- type: "text",
22149
22004
  examples: this.examples(),
22150
22005
  shouldShowExamples: this.shouldShowExamples(),
22151
22006
  onFocus: this._handleFocus,
@@ -22612,7 +22467,6 @@ class NumericInput extends React__namespace.Component {
22612
22467
  value: this.props.currentValue,
22613
22468
  onChange: this.handleChange,
22614
22469
  labelText: labelText,
22615
- type: "text",
22616
22470
  examples: this.examples(),
22617
22471
  shouldShowExamples: this.shouldShowExamples(),
22618
22472
  onFocus: this._handleFocus,
@@ -29410,6 +29264,10 @@ function snap(snapStep, point) {
29410
29264
  return [Math.round(requestedX / snapX) * snapX, Math.round(requestedY / snapY) * snapY];
29411
29265
  }
29412
29266
 
29267
+ function useDraggable(params) {
29268
+ return mafs.useMovable(params);
29269
+ }
29270
+
29413
29271
  // The hitbox size of 48px by 48px is preserved from the legacy interactive
29414
29272
  // graph.
29415
29273
  const hitboxSizePx = 48;
@@ -29565,7 +29423,7 @@ function useControlPoint(point, color, onMovePoint) {
29565
29423
  } = useGraphConfig();
29566
29424
  const [focused, setFocused] = React.useState(false);
29567
29425
  const keyboardHandleRef = React.useRef(null);
29568
- mafs.useMovable({
29426
+ useDraggable({
29569
29427
  gestureTarget: keyboardHandleRef,
29570
29428
  point,
29571
29429
  onMove: onMovePoint,
@@ -29574,7 +29432,7 @@ function useControlPoint(point, color, onMovePoint) {
29574
29432
  const visiblePointRef = React.useRef(null);
29575
29433
  const {
29576
29434
  dragging
29577
- } = mafs.useMovable({
29435
+ } = useDraggable({
29578
29436
  gestureTarget: visiblePointRef,
29579
29437
  point,
29580
29438
  onMove: onMovePoint,
@@ -29628,7 +29486,7 @@ const Line$1 = props => {
29628
29486
  const line = React.useRef(null);
29629
29487
  const {
29630
29488
  dragging
29631
- } = mafs.useMovable({
29489
+ } = useDraggable({
29632
29490
  gestureTarget: line,
29633
29491
  point: start,
29634
29492
  onMove: newPoint => {
@@ -29976,7 +29834,7 @@ const StyledMovablePoint = props => {
29976
29834
  const snapToValue = snapTo ?? "grid";
29977
29835
  const {
29978
29836
  dragging
29979
- } = mafs.useMovable({
29837
+ } = useDraggable({
29980
29838
  gestureTarget: elementRef,
29981
29839
  point,
29982
29840
  onMove,
@@ -30018,7 +29876,7 @@ const PolygonGraph = props => {
30018
29876
  const snapToValue = snapTo ?? "grid";
30019
29877
  const {
30020
29878
  dragging
30021
- } = mafs.useMovable({
29879
+ } = useDraggable({
30022
29880
  gestureTarget: ref,
30023
29881
  point: dragReferencePoint,
30024
29882
  onMove: newPoint => {
@@ -30193,7 +30051,7 @@ function MovableCircle(props) {
30193
30051
  const draggableRef = React.useRef(null);
30194
30052
  const {
30195
30053
  dragging
30196
- } = mafs.useMovable({
30054
+ } = useDraggable({
30197
30055
  gestureTarget: draggableRef,
30198
30056
  point: center,
30199
30057
  onMove,
@@ -30324,10 +30182,24 @@ function SinusoidGraph(props) {
30324
30182
  coords
30325
30183
  } = graphState;
30326
30184
 
30327
- // Get the coefficients for calculating the quadratic equation
30185
+ // The coefficients are used to calculate the sinusoid equation, plot the graph, and to indicate
30186
+ // to content creators the currently selected "correct answer" in the Content Editor.
30187
+ // While we should technically never have invalid coordinates, we want to ensure that
30188
+ // we have a fallback so that the graph can still be plotted without crashing.
30189
+ const coeffRef = React__namespace.useRef({
30190
+ amplitude: 1,
30191
+ angularFrequency: 1,
30192
+ phase: 1,
30193
+ verticalOffset: 0
30194
+ });
30328
30195
  const coeffs = getSinusoidCoefficients(coords);
30196
+
30197
+ // If the coefficients are valid, update the reference
30198
+ if (coeffs !== undefined) {
30199
+ coeffRef.current = coeffs;
30200
+ }
30329
30201
  return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(mafs.Plot.OfX, {
30330
- y: x => computeSine(x, coeffs),
30202
+ y: x => computeSine(x, coeffRef.current),
30331
30203
  color: wonderBlocksTokens.color.blue
30332
30204
  }), coords.map((coord, i) => /*#__PURE__*/React__namespace.createElement(StyledMovablePoint, {
30333
30205
  key: "point-" + i,
@@ -30354,6 +30226,11 @@ const getSinusoidCoefficients = coords => {
30354
30226
  const p1 = coords[0];
30355
30227
  const p2 = coords[1];
30356
30228
 
30229
+ // If the x-coordinates are the same, we are unable to calculate the coefficients
30230
+ if (p2[0] === p1[0]) {
30231
+ return;
30232
+ }
30233
+
30357
30234
  // Resulting coefficients are canonical for this sine curve
30358
30235
  const amplitude = p2[1] - p1[1];
30359
30236
  const angularFrequency = Math.PI / (2 * (p2[0] - p1[0]));
@@ -31847,11 +31724,14 @@ function doMovePoint(state, action) {
31847
31724
  }
31848
31725
  case "sinusoid":
31849
31726
  {
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
31727
+ // First, we need to make sure to bound the new coordinates to the graph range
31852
31728
  const destination = action.destination;
31729
+ const boundDestination = boundAndSnapToGrid(destination, state);
31730
+
31731
+ // Then, we need to verify that the new coordinates are not on the same
31732
+ // vertical line. If they are, then we don't want to move the point
31853
31733
  const newCoords = [...state.coords];
31854
- newCoords[action.index] = action.destination;
31734
+ newCoords[action.index] = boundDestination;
31855
31735
  if (newCoords[0][0] === newCoords[1][0]) {
31856
31736
  return state;
31857
31737
  }
@@ -31861,7 +31741,7 @@ function doMovePoint(state, action) {
31861
31741
  coords: setAtIndex({
31862
31742
  array: state.coords,
31863
31743
  index: action.index,
31864
- newValue: boundAndSnapToGrid(destination, state)
31744
+ newValue: boundDestination
31865
31745
  })
31866
31746
  };
31867
31747
  }
@@ -31869,7 +31749,10 @@ function doMovePoint(state, action) {
31869
31749
  {
31870
31750
  // Set up the new coords and check if the quadratic coefficients are valid
31871
31751
  const newCoords = [...state.coords];
31872
- newCoords[action.index] = action.destination;
31752
+
31753
+ // Bind the new destination to the graph range/snapStep and then get the quadratic coefficients
31754
+ const boundDestination = boundAndSnapToGrid(action.destination, state);
31755
+ newCoords[action.index] = boundDestination;
31873
31756
  const QuadraticCoefficients = getQuadraticCoefficients(newCoords);
31874
31757
 
31875
31758
  // If the new destination results in an invalid quadratic equation, we don't want to move the point
@@ -31882,7 +31765,7 @@ function doMovePoint(state, action) {
31882
31765
  coords: setAtIndex({
31883
31766
  array: state.coords,
31884
31767
  index: action.index,
31885
- newValue: boundAndSnapToGrid(action.destination, state)
31768
+ newValue: boundDestination
31886
31769
  })
31887
31770
  };
31888
31771
  }
@@ -32325,6 +32208,10 @@ const StatefulMafsGraph = /*#__PURE__*/React__namespace.forwardRef((props, ref)
32325
32208
  dispatch(changeRange([[xMinRange, xMaxRange], [yMinRange, yMaxRange]]));
32326
32209
  }, [dispatch, xMinRange, xMaxRange, yMinRange, yMaxRange]);
32327
32210
  const numSegments = graph.type === "segment" ? graph.numSegments : null;
32211
+ const numSides = graph.type === "polygon" ? graph.numSides : null;
32212
+ const snapTo = graph.type === "polygon" ? graph.snapTo : null;
32213
+ const showAngles = graph.type === "polygon" ? graph.showAngles : null;
32214
+ const showSides = graph.type === "polygon" ? graph.showSides : null;
32328
32215
  const originalPropsRef = React.useRef(props);
32329
32216
  const latestPropsRef = wonderBlocksCore.useLatestRef(props);
32330
32217
  React.useEffect(() => {
@@ -32335,7 +32222,7 @@ const StatefulMafsGraph = /*#__PURE__*/React__namespace.forwardRef((props, ref)
32335
32222
  if (latestPropsRef.current !== originalPropsRef.current) {
32336
32223
  dispatch(reinitialize(latestPropsRef.current));
32337
32224
  }
32338
- }, [graph.type, numSegments, latestPropsRef]);
32225
+ }, [graph.type, numSegments, numSides, snapTo, showAngles, showSides, latestPropsRef]);
32339
32226
  return /*#__PURE__*/React__namespace.createElement(MafsGraph, _extends({}, props, {
32340
32227
  state: state,
32341
32228
  dispatch: dispatch
@@ -33698,6 +33585,12 @@ class InteractiveGraph extends React__namespace.Component {
33698
33585
  return InteractiveGraph.validate(this.getUserInput?.(), rubric, this);
33699
33586
  }
33700
33587
  render() {
33588
+ if (this.props.graph.type === "polygon" && this.props.graph.numSides === "unlimited") {
33589
+ return /*#__PURE__*/React__namespace.createElement(LegacyInteractiveGraph, _extends({
33590
+ ref: this.legacyGraphRef
33591
+ }, this.props));
33592
+ }
33593
+
33701
33594
  // Mafs shim
33702
33595
  if (this.props.apiOptions?.flags?.["mafs"]?.[this.props.graph.type]) {
33703
33596
  const box = getInteractiveBoxFromSizeClass(this.props.containerSizeClass);
@@ -44497,7 +44390,7 @@ const init = function (options) {
44497
44390
 
44498
44391
  // This file is processed by a Rollup plugin (replace) to inject the production
44499
44392
  const libName = "@khanacademy/perseus";
44500
- const libVersion = "23.5.0";
44393
+ const libVersion = "24.0.0";
44501
44394
  perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
44502
44395
 
44503
44396
  const apiVersion = {