@khanacademy/perseus 23.4.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.
Files changed (27) hide show
  1. package/dist/components/input-with-examples.d.ts +0 -4
  2. package/dist/es/index.css +0 -17
  3. package/dist/es/index.js +276 -338
  4. package/dist/es/index.js.map +1 -1
  5. package/dist/index.css +0 -17
  6. package/dist/index.d.ts +2 -2
  7. package/dist/index.js +274 -333
  8. package/dist/index.js.map +1 -1
  9. package/dist/perseus-api.d.ts +2 -1
  10. package/dist/perseus-types.d.ts +16 -8
  11. package/dist/server-item-renderer.d.ts +2 -1
  12. package/dist/types.d.ts +1 -1
  13. package/dist/widgets/expression.d.ts +12 -6
  14. package/dist/widgets/interactive-graphs/graphs/sinusoid.d.ts +1 -1
  15. package/dist/widgets/interactive-graphs/graphs/use-draggable.d.ts +13 -0
  16. package/dist/widgets/interactive-graphs/interactive-graph-question-builder.d.ts +9 -3
  17. package/dist/widgets/interactive-graphs/{locked-ellipse.d.ts → locked-figures/locked-ellipse.d.ts} +1 -1
  18. package/dist/widgets/interactive-graphs/{locked-line.d.ts → locked-figures/locked-line.d.ts} +1 -1
  19. package/dist/widgets/interactive-graphs/{locked-point.d.ts → locked-figures/locked-point.d.ts} +1 -1
  20. package/dist/widgets/interactive-graphs/locked-figures/locked-polygon.d.ts +4 -0
  21. package/dist/widgets/interactive-graphs/{locked-vector.d.ts → locked-figures/locked-vector.d.ts} +1 -1
  22. package/dist/widgets/interactive-graphs/mafs-graph.d.ts +2 -1
  23. package/dist/widgets/interactive-graphs/reducer/initialize-graph-state.d.ts +3 -2
  24. package/dist/widgets/interactive-graphs/reducer/interactive-graph-action.d.ts +8 -1
  25. package/dist/widgets/label-image.d.ts +4 -2
  26. package/package.json +1 -1
  27. package/dist/components/math-output.d.ts +0 -27
package/dist/es/index.js CHANGED
@@ -2,7 +2,7 @@ import * as KAS from '@khanacademy/kas';
2
2
  import { MathInputI18nContext, CursorContext, getKeyTranslator, createMathField, getCursorContext, DesktopKeypad, convertDotToTimesByLocale, mathQuillInstance, KeypadInput, KeypadType, KeyArray, keypadElementPropType } from '@khanacademy/math-input';
3
3
  import * as PerseusLinter from '@khanacademy/perseus-linter';
4
4
  import { linterContextDefault, Rule, linterContextProps } from '@khanacademy/perseus-linter';
5
- import { View, RenderStateRoot, UniqueIDProvider, useUniqueIdWithMock, Text as Text$2 } from '@khanacademy/wonder-blocks-core';
5
+ import { View, RenderStateRoot, UniqueIDProvider, useLatestRef, useUniqueIdWithMock, Text as Text$2 } from '@khanacademy/wonder-blocks-core';
6
6
  import Tooltip$1 from '@khanacademy/wonder-blocks-tooltip';
7
7
  import classNames from 'classnames';
8
8
  import * as React from 'react';
@@ -29,7 +29,7 @@ import { OptionItem, SingleSelect, MultiSelect } from '@khanacademy/wonder-block
29
29
  import caretDown from '@phosphor-icons/core/regular/caret-down.svg';
30
30
  import caretUp from '@phosphor-icons/core/regular/caret-up.svg';
31
31
  import debounce$2 from 'lodash.debounce';
32
- import { vec, Ellipse, Line as Line$4, Point as Point$2, useMovable, Text as Text$1, Polygon, Plot as Plot$3, Coordinates, Mafs } from 'mafs';
32
+ import { vec, Ellipse, Line as Line$4, Point as Point$2, Polygon, useMovable, Text as Text$1, Plot as Plot$3, Coordinates, Mafs } from 'mafs';
33
33
  import { PhosphorIcon } from '@khanacademy/wonder-blocks-icon';
34
34
  import { Body, LabelSmall, LabelMedium } from '@khanacademy/wonder-blocks-typography';
35
35
  import questionIcon from '@phosphor-icons/core/regular/question.svg';
@@ -20964,254 +20964,6 @@ const styles$B = {
20964
20964
  }
20965
20965
  };
20966
20966
 
20967
- /*
20968
- * In this file, an `expression` is some portion of valid TeX enclosed in
20969
- * curly brackets.
20970
- */
20971
-
20972
- /*
20973
- * Find the index at which an expression ends, i.e., has an unmatched
20974
- * closing curly bracket. This method assumes that we start with a non-open
20975
- * bracket character and end when we've seen more left than right brackets
20976
- * (rather than assuming that we start with a bracket character and wait for
20977
- * bracket equality).
20978
- */
20979
- function findEndpoint(tex, currentIndex) {
20980
- let bracketDepth = 0;
20981
- for (let i = currentIndex, len = tex.length; i < len; i++) {
20982
- const c = tex[i];
20983
- if (c === "{") {
20984
- bracketDepth++;
20985
- } else if (c === "}") {
20986
- bracketDepth--;
20987
- }
20988
- if (bracketDepth < 0) {
20989
- return i;
20990
- }
20991
- }
20992
- // If we never see unbalanced curly brackets, default to the
20993
- // entire string
20994
- return tex.length;
20995
- }
20996
-
20997
- /*
20998
- * Parses an individual set of curly brackets into TeX.
20999
- */
21000
- function parseNextExpression(tex, currentIndex, handler) {
21001
- // Find the first '{' and grab subsequent TeX
21002
- // Ex) tex: '{3}{7}', and we want the '3'
21003
- const openBracketIndex = tex.indexOf("{", currentIndex);
21004
- const nextExpIndex = openBracketIndex + 1;
21005
-
21006
- // Truncate to only contain remaining TeX
21007
- const endpoint = findEndpoint(tex, nextExpIndex);
21008
- const expressionTeX = tex.substring(nextExpIndex, endpoint);
21009
- const parsedExp = walkTex(expressionTeX, handler);
21010
- return {
21011
- endpoint: endpoint,
21012
- expression: parsedExp
21013
- };
21014
- }
21015
- function getNextFracIndex(tex, currentIndex) {
21016
- const dfrac = "\\dfrac";
21017
- const frac = "\\frac";
21018
- const nextFrac = tex.indexOf(frac, currentIndex);
21019
- const nextDFrac = tex.indexOf(dfrac, currentIndex);
21020
- if (nextFrac > -1 && nextDFrac > -1) {
21021
- return Math.min(nextFrac, nextDFrac);
21022
- }
21023
- if (nextFrac > -1) {
21024
- return nextFrac;
21025
- }
21026
- if (nextDFrac > -1) {
21027
- return nextDFrac;
21028
- }
21029
- return -1;
21030
- }
21031
- function walkTex(tex, handler) {
21032
- if (!tex) {
21033
- return "";
21034
- }
21035
-
21036
- // Ex) tex: '2 \dfrac {3}{7}'
21037
- let parsedString = "";
21038
- let currentIndex = 0;
21039
- let nextFrac = getNextFracIndex(tex, currentIndex);
21040
-
21041
- // For each \dfrac, find the two expressions (wrapped in {}) and recur
21042
- while (nextFrac > -1) {
21043
- // Gather first fragment, preceding \dfrac
21044
- // Ex) parsedString: '2 '
21045
- parsedString += tex.substring(currentIndex, nextFrac);
21046
-
21047
- // Remove everything preceding \dfrac, which has been parsed
21048
- currentIndex = nextFrac;
21049
-
21050
- // Parse first expression and move index past it
21051
- // Ex) firstParsedExpression.expression: '3'
21052
- const firstParsedExpression = parseNextExpression(tex, currentIndex, handler);
21053
- currentIndex = firstParsedExpression.endpoint + 1;
21054
-
21055
- // Parse second expression
21056
- // Ex) secondParsedExpression.expression: '7'
21057
- const secondParsedExpression = parseNextExpression(tex, currentIndex, handler);
21058
- currentIndex = secondParsedExpression.endpoint + 1;
21059
-
21060
- // Add expressions to running total of parsed expressions
21061
- if (parsedString.length) {
21062
- parsedString += " ";
21063
- }
21064
-
21065
- // Apply a custom handler based on the parsed subexpressions
21066
- parsedString += handler(firstParsedExpression.expression, secondParsedExpression.expression);
21067
-
21068
- // Find next DFrac, relative to currentIndex
21069
- nextFrac = getNextFracIndex(tex, currentIndex);
21070
- }
21071
-
21072
- // Add remaining TeX, which is \dfrac-free
21073
- parsedString += tex.slice(currentIndex);
21074
- return parsedString;
21075
- }
21076
-
21077
- /*
21078
- * Modify a TeX expression, returning another TeX expression. The resulting
21079
- * expression will have its innermost fractions stubbed out with \fracs
21080
- * (as opposed to \dfracs). All other content will remain untouched.
21081
- */
21082
- function modifyTex(tex) {
21083
- function isNestedFraction(tex) {
21084
- return tex.indexOf("\\frac") > -1 || tex.indexOf("\\dfrac") > -1;
21085
- }
21086
- const handler = function handler(exp1, exp2) {
21087
- let prefix;
21088
- if (isNestedFraction(exp1) || isNestedFraction(exp2)) {
21089
- prefix = "\\dfrac";
21090
- } else {
21091
- prefix = "\\frac";
21092
- }
21093
- return prefix + " {" + exp1 + "}{" + exp2 + "}";
21094
- };
21095
- return walkTex(tex, handler);
21096
- }
21097
-
21098
- /*
21099
- * Parse a TeX expression into something interpretable by input-number.
21100
- * The process is concerned with: (1) parsing fractions, i.e., \dfracs; and
21101
- * (2) removing backslash-escaping from certain characters (right now, only
21102
- * percent signs).
21103
- *
21104
- * The basic algorithm for handling \dfracs splits on \dfracs and then recurs
21105
- * on the subsequent "expressions", i.e., the {} pairs that follow \dfrac. The
21106
- * recursion is to allow for nested \dfrac elements.
21107
- *
21108
- * Backslash-escapes are removed with a simple search-and-replace.
21109
- */
21110
- function parseTex(tex) {
21111
- const handler = function handler(exp1, exp2) {
21112
- return exp1 + "/" + exp2;
21113
- };
21114
- const texWithoutFracs = walkTex(tex, handler);
21115
- return texWithoutFracs.replace("\\%", "%");
21116
- }
21117
- var TexWrangler = {
21118
- parseTex,
21119
- modifyTex
21120
- };
21121
-
21122
- /* eslint-disable react/sort-comp */
21123
- const ModifyTex = TexWrangler.modifyTex;
21124
- class MathOutput extends React.Component {
21125
- constructor(...args) {
21126
- super(...args);
21127
- this.state = {
21128
- focused: false,
21129
- selectorNamespace: _.uniqueId("math-output")
21130
- };
21131
- this._getInputClassName = () => {
21132
- let className = "math-output " + ClassNames.INPUT + " " + ClassNames.INTERACTIVE;
21133
- if (this.state.focused) {
21134
- className += " " + ClassNames.FOCUSED;
21135
- }
21136
- if (this.props.className) {
21137
- className += " " + this.props.className;
21138
- }
21139
- return className;
21140
- };
21141
- this._getDisplayValue = value => {
21142
- // Cast from (potentially a) number to string
21143
- let displayText;
21144
- if (value != null) {
21145
- displayText = "" + value;
21146
- } else {
21147
- displayText = "";
21148
- }
21149
- return ModifyTex(displayText);
21150
- };
21151
- this.getValue = () => {
21152
- return this.props.value;
21153
- };
21154
- this.focus = () => {
21155
- if (!this.state.focused) {
21156
- this.props.onFocus();
21157
- this._bindBlurHandler();
21158
- this.setState({
21159
- focused: true
21160
- });
21161
- }
21162
- };
21163
- this.blur = () => {
21164
- if (this.state.focused) {
21165
- this.props.onBlur();
21166
- this._unbindBlurHandler();
21167
- this.setState({
21168
- focused: false
21169
- });
21170
- }
21171
- };
21172
- this._bindBlurHandler = () => {
21173
- $(document).bind("vclick." + this.state.selectorNamespace, e => {
21174
- // Detect whether the target has our React DOM node as a parent
21175
- const $closestWidget = $(e.target).closest(
21176
- // @ts-expect-error - TS2769 - No overload matches this call.
21177
- ReactDOM__default.findDOMNode(this));
21178
- if (!$closestWidget.length) {
21179
- this.blur();
21180
- }
21181
- });
21182
- };
21183
- this._unbindBlurHandler = () => {
21184
- $(document).unbind("." + this.state.selectorNamespace);
21185
- };
21186
- }
21187
- render() {
21188
- const divStyle = {
21189
- textAlign: "center"
21190
- };
21191
- const {
21192
- TeX
21193
- } = getDependencies();
21194
- return /*#__PURE__*/React.createElement("span", {
21195
- // eslint-disable-next-line react/no-string-refs
21196
- ref: "input",
21197
- className: this._getInputClassName(),
21198
- "aria-label": this.props.labelText,
21199
- onMouseDown: this.focus,
21200
- onTouchStart: this.focus
21201
- }, /*#__PURE__*/React.createElement("div", {
21202
- style: divStyle
21203
- }, /*#__PURE__*/React.createElement(TeX, null, this._getDisplayValue(this.props.value))));
21204
- }
21205
- componentWillUnmount() {
21206
- this._unbindBlurHandler();
21207
- }
21208
- }
21209
- MathOutput.defaultProps = {
21210
- value: "",
21211
- onFocus: function () {},
21212
- onBlur: function () {}
21213
- };
21214
-
21215
20967
  /* eslint-disable @khanacademy/ts-no-error-suppressions */
21216
20968
  let lastId = 0;
21217
20969
  function uniqueIdForInput(prefix = "input-") {
@@ -21566,12 +21318,6 @@ class InputWithExamples extends React.Component {
21566
21318
  return `input-with-examples-${btoa(this.props.id).replace(/=/g, "")}`;
21567
21319
  };
21568
21320
  this._getInputClassName = () => {
21569
- // <MathOutput> is a special component that manages its own class and
21570
- // state, as it's a <span> that wants to act like an <input>.
21571
- if (this.props.type === "tex") {
21572
- return this.props.className;
21573
- }
21574
-
21575
21321
  // Otherwise, we need to add these INPUT and FOCUSED tags here.
21576
21322
  let className = ClassNames.INPUT + " " + ClassNames.INTERACTIVE;
21577
21323
  if (this.state.focused) {
@@ -21582,8 +21328,7 @@ class InputWithExamples extends React.Component {
21582
21328
  }
21583
21329
  return className;
21584
21330
  };
21585
- this._getPropsForInputType = () => {
21586
- // Minimal set of props, used by each input type
21331
+ this._renderInput = () => {
21587
21332
  const id = this._getUniqueId();
21588
21333
  const inputProps = {
21589
21334
  id: id,
@@ -21595,52 +21340,15 @@ class InputWithExamples extends React.Component {
21595
21340
  onFocus: this._handleFocus,
21596
21341
  onBlur: this._handleBlur,
21597
21342
  disabled: this.props.disabled,
21598
- style: this.props.style
21599
- };
21600
- if (this.props.type === "tex") {
21601
- return inputProps;
21602
- }
21603
-
21604
- // Add useful props required for MATH and TEXT modes
21605
- _.extend(inputProps, {
21343
+ style: this.props.style,
21606
21344
  onChange: this.props.onChange,
21607
- onTouchStart: captureScratchpadTouchStart$2
21608
- });
21609
-
21610
- // And add final props that are MATH- and TEXT-specific
21611
- if (this.props.type === "math") {
21612
- return _.extend({
21613
- buttonSet: this.props.buttonSet,
21614
- buttonsVisible: this.props.buttonsVisible,
21615
- convertDotToTimes: this.props.convertDotToTimes
21616
- }, inputProps);
21617
- }
21618
- if (this.props.type === "text") {
21619
- return _.extend({
21620
- autoCapitalize: "off",
21621
- autoComplete: "off",
21622
- autoCorrect: "off",
21623
- spellCheck: "false"
21624
- }, inputProps);
21625
- }
21626
- };
21627
- this._getComponentForInputType = () => {
21628
- switch (this.props.type) {
21629
- case "tex":
21630
- return MathOutput;
21631
- case "math":
21632
- return MathInput;
21633
- case "text":
21634
- return TextInput;
21635
- default:
21636
- this.props.type;
21637
- return null;
21638
- }
21639
- };
21640
- this._renderInput = () => {
21641
- const inputProps = this._getPropsForInputType();
21642
- const InputComponent = this._getComponentForInputType();
21643
- return /*#__PURE__*/React.createElement(InputComponent, inputProps);
21345
+ onTouchStart: captureScratchpadTouchStart$2,
21346
+ autoCapitalize: "off",
21347
+ autoComplete: "off",
21348
+ autoCorrect: "off",
21349
+ spellCheck: "false"
21350
+ };
21351
+ return /*#__PURE__*/React.createElement(TextInput, inputProps);
21644
21352
  };
21645
21353
  this._handleFocus = () => {
21646
21354
  this.props.onFocus();
@@ -21682,14 +21390,6 @@ class InputWithExamples extends React.Component {
21682
21390
  }
21683
21391
  render() {
21684
21392
  const input = this._renderInput();
21685
-
21686
- // Static rendering, which doesn't include the 'tooltip' logic that the
21687
- // other types require, and is hence handled separately.
21688
- if (this.props.type === "tex") {
21689
- return input;
21690
- }
21691
-
21692
- // Else, we need to be able to show examples
21693
21393
  const examplesContent = _.map(this.props.examples, example => {
21694
21394
  return "- " + example;
21695
21395
  }).join("\n");
@@ -21716,7 +21416,6 @@ class InputWithExamples extends React.Component {
21716
21416
  }
21717
21417
  InputWithExamples.contextType = PerseusI18nContext;
21718
21418
  InputWithExamples.defaultProps = {
21719
- type: "text",
21720
21419
  shouldShowExamples: true,
21721
21420
  onFocus: function () {},
21722
21421
  onBlur: function () {},
@@ -21805,6 +21504,161 @@ SimpleKeypadInput.propTypes = {
21805
21504
  value: PropTypes.oneOfType([PropTypes.string, PropTypes.number])
21806
21505
  };
21807
21506
 
21507
+ /*
21508
+ * In this file, an `expression` is some portion of valid TeX enclosed in
21509
+ * curly brackets.
21510
+ */
21511
+
21512
+ /*
21513
+ * Find the index at which an expression ends, i.e., has an unmatched
21514
+ * closing curly bracket. This method assumes that we start with a non-open
21515
+ * bracket character and end when we've seen more left than right brackets
21516
+ * (rather than assuming that we start with a bracket character and wait for
21517
+ * bracket equality).
21518
+ */
21519
+ function findEndpoint(tex, currentIndex) {
21520
+ let bracketDepth = 0;
21521
+ for (let i = currentIndex, len = tex.length; i < len; i++) {
21522
+ const c = tex[i];
21523
+ if (c === "{") {
21524
+ bracketDepth++;
21525
+ } else if (c === "}") {
21526
+ bracketDepth--;
21527
+ }
21528
+ if (bracketDepth < 0) {
21529
+ return i;
21530
+ }
21531
+ }
21532
+ // If we never see unbalanced curly brackets, default to the
21533
+ // entire string
21534
+ return tex.length;
21535
+ }
21536
+
21537
+ /*
21538
+ * Parses an individual set of curly brackets into TeX.
21539
+ */
21540
+ function parseNextExpression(tex, currentIndex, handler) {
21541
+ // Find the first '{' and grab subsequent TeX
21542
+ // Ex) tex: '{3}{7}', and we want the '3'
21543
+ const openBracketIndex = tex.indexOf("{", currentIndex);
21544
+ const nextExpIndex = openBracketIndex + 1;
21545
+
21546
+ // Truncate to only contain remaining TeX
21547
+ const endpoint = findEndpoint(tex, nextExpIndex);
21548
+ const expressionTeX = tex.substring(nextExpIndex, endpoint);
21549
+ const parsedExp = walkTex(expressionTeX, handler);
21550
+ return {
21551
+ endpoint: endpoint,
21552
+ expression: parsedExp
21553
+ };
21554
+ }
21555
+ function getNextFracIndex(tex, currentIndex) {
21556
+ const dfrac = "\\dfrac";
21557
+ const frac = "\\frac";
21558
+ const nextFrac = tex.indexOf(frac, currentIndex);
21559
+ const nextDFrac = tex.indexOf(dfrac, currentIndex);
21560
+ if (nextFrac > -1 && nextDFrac > -1) {
21561
+ return Math.min(nextFrac, nextDFrac);
21562
+ }
21563
+ if (nextFrac > -1) {
21564
+ return nextFrac;
21565
+ }
21566
+ if (nextDFrac > -1) {
21567
+ return nextDFrac;
21568
+ }
21569
+ return -1;
21570
+ }
21571
+ function walkTex(tex, handler) {
21572
+ if (!tex) {
21573
+ return "";
21574
+ }
21575
+
21576
+ // Ex) tex: '2 \dfrac {3}{7}'
21577
+ let parsedString = "";
21578
+ let currentIndex = 0;
21579
+ let nextFrac = getNextFracIndex(tex, currentIndex);
21580
+
21581
+ // For each \dfrac, find the two expressions (wrapped in {}) and recur
21582
+ while (nextFrac > -1) {
21583
+ // Gather first fragment, preceding \dfrac
21584
+ // Ex) parsedString: '2 '
21585
+ parsedString += tex.substring(currentIndex, nextFrac);
21586
+
21587
+ // Remove everything preceding \dfrac, which has been parsed
21588
+ currentIndex = nextFrac;
21589
+
21590
+ // Parse first expression and move index past it
21591
+ // Ex) firstParsedExpression.expression: '3'
21592
+ const firstParsedExpression = parseNextExpression(tex, currentIndex, handler);
21593
+ currentIndex = firstParsedExpression.endpoint + 1;
21594
+
21595
+ // Parse second expression
21596
+ // Ex) secondParsedExpression.expression: '7'
21597
+ const secondParsedExpression = parseNextExpression(tex, currentIndex, handler);
21598
+ currentIndex = secondParsedExpression.endpoint + 1;
21599
+
21600
+ // Add expressions to running total of parsed expressions
21601
+ if (parsedString.length) {
21602
+ parsedString += " ";
21603
+ }
21604
+
21605
+ // Apply a custom handler based on the parsed subexpressions
21606
+ parsedString += handler(firstParsedExpression.expression, secondParsedExpression.expression);
21607
+
21608
+ // Find next DFrac, relative to currentIndex
21609
+ nextFrac = getNextFracIndex(tex, currentIndex);
21610
+ }
21611
+
21612
+ // Add remaining TeX, which is \dfrac-free
21613
+ parsedString += tex.slice(currentIndex);
21614
+ return parsedString;
21615
+ }
21616
+
21617
+ /*
21618
+ * Modify a TeX expression, returning another TeX expression. The resulting
21619
+ * expression will have its innermost fractions stubbed out with \fracs
21620
+ * (as opposed to \dfracs). All other content will remain untouched.
21621
+ */
21622
+ function modifyTex(tex) {
21623
+ function isNestedFraction(tex) {
21624
+ return tex.indexOf("\\frac") > -1 || tex.indexOf("\\dfrac") > -1;
21625
+ }
21626
+ const handler = function handler(exp1, exp2) {
21627
+ let prefix;
21628
+ if (isNestedFraction(exp1) || isNestedFraction(exp2)) {
21629
+ prefix = "\\dfrac";
21630
+ } else {
21631
+ prefix = "\\frac";
21632
+ }
21633
+ return prefix + " {" + exp1 + "}{" + exp2 + "}";
21634
+ };
21635
+ return walkTex(tex, handler);
21636
+ }
21637
+
21638
+ /*
21639
+ * Parse a TeX expression into something interpretable by input-number.
21640
+ * The process is concerned with: (1) parsing fractions, i.e., \dfracs; and
21641
+ * (2) removing backslash-escaping from certain characters (right now, only
21642
+ * percent signs).
21643
+ *
21644
+ * The basic algorithm for handling \dfracs splits on \dfracs and then recurs
21645
+ * on the subsequent "expressions", i.e., the {} pairs that follow \dfrac. The
21646
+ * recursion is to allow for nested \dfrac elements.
21647
+ *
21648
+ * Backslash-escapes are removed with a simple search-and-replace.
21649
+ */
21650
+ function parseTex(tex) {
21651
+ const handler = function handler(exp1, exp2) {
21652
+ return exp1 + "/" + exp2;
21653
+ };
21654
+ const texWithoutFracs = walkTex(tex, handler);
21655
+ return texWithoutFracs.replace("\\%", "%");
21656
+ }
21657
+ var TexWrangler = {
21658
+ parseTex,
21659
+ modifyTex
21660
+ };
21661
+
21808
21662
  /* eslint-disable react/sort-comp */
21809
21663
  const ParseTex$1 = TexWrangler.parseTex;
21810
21664
  const answerTypes = {
@@ -21983,7 +21837,6 @@ class InputNumber extends React.Component {
21983
21837
  value: this.props.currentValue,
21984
21838
  onChange: this.handleChange,
21985
21839
  style: inputStyles,
21986
- type: "text",
21987
21840
  examples: this.examples(),
21988
21841
  shouldShowExamples: this.shouldShowExamples(),
21989
21842
  onFocus: this._handleFocus,
@@ -22392,7 +22245,6 @@ class NumericInput extends React.Component {
22392
22245
  value: this.props.currentValue,
22393
22246
  onChange: this.handleChange,
22394
22247
  labelText: labelText,
22395
- type: "text",
22396
22248
  examples: this.examples(),
22397
22249
  shouldShowExamples: this.shouldShowExamples(),
22398
22250
  onFocus: this._handleFocus,
@@ -28656,13 +28508,15 @@ const ItemExtras = [
28656
28508
 
28657
28509
  const PerseusExpressionAnswerFormConsidered = ["correct", "wrong", "ungraded"];
28658
28510
  const lockedFigureColors = {
28511
+ blue: "#3D7586",
28659
28512
  green: "#447A53",
28660
28513
  grayH: "#3B3D45",
28661
28514
  purple: "#594094",
28662
28515
  pink: "#B25071",
28663
- red: "#D92916"
28516
+ red: "#D92916",
28517
+ orange: "#946700"
28664
28518
  };
28665
- const lockedEllipseFillStyles = {
28519
+ const lockedFigureFillStyles = {
28666
28520
  none: 0,
28667
28521
  solid: 1,
28668
28522
  translucent: 0.4
@@ -28682,7 +28536,7 @@ const LockedEllipse = props => {
28682
28536
  center: center,
28683
28537
  radius: radius,
28684
28538
  angle: angle,
28685
- fillOpacity: lockedEllipseFillStyles[fillStyle],
28539
+ fillOpacity: lockedFigureFillStyles[fillStyle],
28686
28540
  strokeStyle: strokeStyle,
28687
28541
  color: lockedFigureColors[color]
28688
28542
  });
@@ -28942,6 +28796,29 @@ const LockedPoint = props => {
28942
28796
  });
28943
28797
  };
28944
28798
 
28799
+ const LockedPolygon = props => {
28800
+ const {
28801
+ points,
28802
+ color,
28803
+ showVertices,
28804
+ fillStyle,
28805
+ strokeStyle
28806
+ } = props;
28807
+ return /*#__PURE__*/React.createElement("g", {
28808
+ className: "locked-polygon"
28809
+ }, /*#__PURE__*/React.createElement(Polygon, {
28810
+ points: [...points],
28811
+ fillOpacity: lockedFigureFillStyles[fillStyle],
28812
+ strokeStyle: strokeStyle,
28813
+ color: lockedFigureColors[color]
28814
+ }), showVertices && points.map((point, index) => /*#__PURE__*/React.createElement(Point$2, {
28815
+ key: `locked-polygon-point-${index}`,
28816
+ x: point[0],
28817
+ y: point[1],
28818
+ color: lockedFigureColors[color]
28819
+ })));
28820
+ };
28821
+
28945
28822
  const LockedVector = props => {
28946
28823
  const {
28947
28824
  color,
@@ -28972,13 +28849,17 @@ const GraphLockedLayer = props => {
28972
28849
  key: `line-${index}`,
28973
28850
  range: props.range
28974
28851
  }, figure));
28852
+ case "vector":
28853
+ return /*#__PURE__*/React.createElement(LockedVector, _extends({
28854
+ key: `vector-${index}`
28855
+ }, figure));
28975
28856
  case "ellipse":
28976
28857
  return /*#__PURE__*/React.createElement(LockedEllipse, _extends({
28977
28858
  key: `ellipse-${index}`
28978
28859
  }, figure));
28979
- case "vector":
28980
- return /*#__PURE__*/React.createElement(LockedVector, _extends({
28981
- key: `vector-${index}`
28860
+ case "polygon":
28861
+ return /*#__PURE__*/React.createElement(LockedPolygon, _extends({
28862
+ key: `polygon-${index}`
28982
28863
  }, figure));
28983
28864
  default:
28984
28865
  /**
@@ -28992,6 +28873,13 @@ const GraphLockedLayer = props => {
28992
28873
  }));
28993
28874
  };
28994
28875
 
28876
+ const REINITIALIZE = "reinitialize";
28877
+ function reinitialize(params) {
28878
+ return {
28879
+ type: REINITIALIZE,
28880
+ params
28881
+ };
28882
+ }
28995
28883
  const MOVE_CONTROL_POINT = "move-control-point";
28996
28884
  function moveControlPoint(pointIndex, destination, itemIndex) {
28997
28885
  return {
@@ -29084,6 +28972,10 @@ function snap(snapStep, point) {
29084
28972
  return [Math.round(requestedX / snapX) * snapX, Math.round(requestedY / snapY) * snapY];
29085
28973
  }
29086
28974
 
28975
+ function useDraggable(params) {
28976
+ return useMovable(params);
28977
+ }
28978
+
29087
28979
  // The hitbox size of 48px by 48px is preserved from the legacy interactive
29088
28980
  // graph.
29089
28981
  const hitboxSizePx = 48;
@@ -29237,7 +29129,7 @@ function useControlPoint(point, color, onMovePoint) {
29237
29129
  } = useGraphConfig();
29238
29130
  const [focused, setFocused] = useState(false);
29239
29131
  const keyboardHandleRef = useRef(null);
29240
- useMovable({
29132
+ useDraggable({
29241
29133
  gestureTarget: keyboardHandleRef,
29242
29134
  point,
29243
29135
  onMove: onMovePoint,
@@ -29246,7 +29138,7 @@ function useControlPoint(point, color, onMovePoint) {
29246
29138
  const visiblePointRef = useRef(null);
29247
29139
  const {
29248
29140
  dragging
29249
- } = useMovable({
29141
+ } = useDraggable({
29250
29142
  gestureTarget: visiblePointRef,
29251
29143
  point,
29252
29144
  onMove: onMovePoint,
@@ -29300,7 +29192,7 @@ const Line$1 = props => {
29300
29192
  const line = useRef(null);
29301
29193
  const {
29302
29194
  dragging
29303
- } = useMovable({
29195
+ } = useDraggable({
29304
29196
  gestureTarget: line,
29305
29197
  point: start,
29306
29198
  onMove: newPoint => {
@@ -29640,7 +29532,7 @@ const StyledMovablePoint = props => {
29640
29532
  const snapToValue = snapTo != null ? snapTo : "grid";
29641
29533
  const {
29642
29534
  dragging
29643
- } = useMovable({
29535
+ } = useDraggable({
29644
29536
  gestureTarget: elementRef,
29645
29537
  point,
29646
29538
  onMove,
@@ -29682,7 +29574,7 @@ const PolygonGraph = props => {
29682
29574
  const snapToValue = snapTo != null ? snapTo : "grid";
29683
29575
  const {
29684
29576
  dragging
29685
- } = useMovable({
29577
+ } = useDraggable({
29686
29578
  gestureTarget: ref,
29687
29579
  point: dragReferencePoint,
29688
29580
  onMove: newPoint => {
@@ -29845,7 +29737,7 @@ function MovableCircle(props) {
29845
29737
  const draggableRef = useRef(null);
29846
29738
  const {
29847
29739
  dragging
29848
- } = useMovable({
29740
+ } = useDraggable({
29849
29741
  gestureTarget: draggableRef,
29850
29742
  point: center,
29851
29743
  onMove,
@@ -29976,10 +29868,24 @@ function SinusoidGraph(props) {
29976
29868
  coords
29977
29869
  } = graphState;
29978
29870
 
29979
- // Get the coefficients for calculating the quadratic equation
29871
+ // The coefficients are used to calculate the sinusoid equation, plot the graph, and to indicate
29872
+ // to content creators the currently selected "correct answer" in the Content Editor.
29873
+ // While we should technically never have invalid coordinates, we want to ensure that
29874
+ // we have a fallback so that the graph can still be plotted without crashing.
29875
+ const coeffRef = React.useRef({
29876
+ amplitude: 1,
29877
+ angularFrequency: 1,
29878
+ phase: 1,
29879
+ verticalOffset: 0
29880
+ });
29980
29881
  const coeffs = getSinusoidCoefficients(coords);
29882
+
29883
+ // If the coefficients are valid, update the reference
29884
+ if (coeffs !== undefined) {
29885
+ coeffRef.current = coeffs;
29886
+ }
29981
29887
  return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Plot$3.OfX, {
29982
- y: x => computeSine(x, coeffs),
29888
+ y: x => computeSine(x, coeffRef.current),
29983
29889
  color: color.blue
29984
29890
  }), coords.map((coord, i) => /*#__PURE__*/React.createElement(StyledMovablePoint, {
29985
29891
  key: "point-" + i,
@@ -30006,6 +29912,11 @@ const getSinusoidCoefficients = coords => {
30006
29912
  const p1 = coords[0];
30007
29913
  const p2 = coords[1];
30008
29914
 
29915
+ // If the x-coordinates are the same, we are unable to calculate the coefficients
29916
+ if (p2[0] === p1[0]) {
29917
+ return;
29918
+ }
29919
+
30009
29920
  // Resulting coefficients are canonical for this sine curve
30010
29921
  const amplitude = p2[1] - p1[1];
30011
29922
  const angularFrequency = Math.PI / (2 * (p2[0] - p1[0]));
@@ -31284,6 +31195,8 @@ function getCircleCoords(graph) {
31284
31195
 
31285
31196
  function interactiveGraphReducer(state, action) {
31286
31197
  switch (action.type) {
31198
+ case REINITIALIZE:
31199
+ return initializeGraphState(action.params);
31287
31200
  case MOVE_CONTROL_POINT:
31288
31201
  return doMoveControlPoint(state, action);
31289
31202
  case MOVE_LINE:
@@ -31477,11 +31390,14 @@ function doMovePoint(state, action) {
31477
31390
  }
31478
31391
  case "sinusoid":
31479
31392
  {
31480
- // First, we need to verify that the new coordinates are not on the same vertical line
31481
- // If they are, we don't want to move the point
31393
+ // First, we need to make sure to bound the new coordinates to the graph range
31482
31394
  const destination = action.destination;
31395
+ const boundDestination = boundAndSnapToGrid(destination, state);
31396
+
31397
+ // Then, we need to verify that the new coordinates are not on the same
31398
+ // vertical line. If they are, then we don't want to move the point
31483
31399
  const _newCoords = [...state.coords];
31484
- _newCoords[action.index] = action.destination;
31400
+ _newCoords[action.index] = boundDestination;
31485
31401
  if (_newCoords[0][0] === _newCoords[1][0]) {
31486
31402
  return state;
31487
31403
  }
@@ -31490,7 +31406,7 @@ function doMovePoint(state, action) {
31490
31406
  coords: setAtIndex({
31491
31407
  array: state.coords,
31492
31408
  index: action.index,
31493
- newValue: boundAndSnapToGrid(destination, state)
31409
+ newValue: boundDestination
31494
31410
  })
31495
31411
  });
31496
31412
  }
@@ -31498,7 +31414,10 @@ function doMovePoint(state, action) {
31498
31414
  {
31499
31415
  // Set up the new coords and check if the quadratic coefficients are valid
31500
31416
  const _newCoords2 = [...state.coords];
31501
- _newCoords2[action.index] = action.destination;
31417
+
31418
+ // Bind the new destination to the graph range/snapStep and then get the quadratic coefficients
31419
+ const boundDestination = boundAndSnapToGrid(action.destination, state);
31420
+ _newCoords2[action.index] = boundDestination;
31502
31421
  const QuadraticCoefficients = getQuadraticCoefficients(_newCoords2);
31503
31422
 
31504
31423
  // If the new destination results in an invalid quadratic equation, we don't want to move the point
@@ -31510,7 +31429,7 @@ function doMovePoint(state, action) {
31510
31429
  coords: setAtIndex({
31511
31430
  array: state.coords,
31512
31431
  index: action.index,
31513
- newValue: boundAndSnapToGrid(action.destination, state)
31432
+ newValue: boundDestination
31514
31433
  })
31515
31434
  });
31516
31435
  }
@@ -31908,11 +31827,12 @@ function mafsStateToInteractiveGraph(state) {
31908
31827
  }
31909
31828
  const StatefulMafsGraph = /*#__PURE__*/React.forwardRef((props, ref) => {
31910
31829
  const {
31911
- onChange
31830
+ onChange,
31831
+ graph
31912
31832
  } = props;
31913
31833
  const [state, dispatch] = React.useReducer(interactiveGraphReducer, props, initializeGraphState);
31914
31834
  useImperativeHandle(ref, () => ({
31915
- getUserInput: () => getGradableGraph(state, props.graph)
31835
+ getUserInput: () => getGradableGraph(state, graph)
31916
31836
  }));
31917
31837
  const prevState = useRef(state);
31918
31838
  useEffect(() => {
@@ -31935,6 +31855,18 @@ const StatefulMafsGraph = /*#__PURE__*/React.forwardRef((props, ref) => {
31935
31855
  useEffect(() => {
31936
31856
  dispatch(changeRange([[xMinRange, xMaxRange], [yMinRange, yMaxRange]]));
31937
31857
  }, [dispatch, xMinRange, xMaxRange, yMinRange, yMaxRange]);
31858
+ const numSegments = graph.type === "segment" ? graph.numSegments : null;
31859
+ const originalPropsRef = useRef(props);
31860
+ const latestPropsRef = useLatestRef(props);
31861
+ useEffect(() => {
31862
+ // This conditional prevents the state from being "reinitialized" right
31863
+ // after the first render. This is an optimization, but also prevents
31864
+ // a bug where the graph would be marked "incorrect" during grading
31865
+ // even if the user never interacted with it.
31866
+ if (latestPropsRef.current !== originalPropsRef.current) {
31867
+ dispatch(reinitialize(latestPropsRef.current));
31868
+ }
31869
+ }, [graph.type, numSegments, latestPropsRef]);
31938
31870
  return /*#__PURE__*/React.createElement(MafsGraph, _extends({}, props, {
31939
31871
  state: state,
31940
31872
  dispatch: dispatch
@@ -33309,6 +33241,12 @@ class InteractiveGraph extends React.Component {
33309
33241
  }
33310
33242
  render() {
33311
33243
  var _this$props$apiOption;
33244
+ if (this.props.graph.type === "polygon" && this.props.graph.numSides === "unlimited") {
33245
+ return /*#__PURE__*/React.createElement(LegacyInteractiveGraph, _extends({
33246
+ ref: this.legacyGraphRef
33247
+ }, this.props));
33248
+ }
33249
+
33312
33250
  // Mafs shim
33313
33251
  if ((_this$props$apiOption = this.props.apiOptions) != null && (_this$props$apiOption = _this$props$apiOption.flags) != null && (_this$props$apiOption = _this$props$apiOption["mafs"]) != null && _this$props$apiOption[this.props.graph.type]) {
33314
33252
  const box = getInteractiveBoxFromSizeClass(this.props.containerSizeClass);
@@ -44101,7 +44039,7 @@ const init = function init(options) {
44101
44039
 
44102
44040
  // This file is processed by a Rollup plugin (replace) to inject the production
44103
44041
  const libName = "@khanacademy/perseus";
44104
- const libVersion = "23.4.0";
44042
+ const libVersion = "23.6.0";
44105
44043
  addLibraryVersionToPerseusDebug(libName, libVersion);
44106
44044
 
44107
44045
  const apiVersion = {
@@ -47102,5 +47040,5 @@ const EditorJsonify = {
47102
47040
  }
47103
47041
  };
47104
47042
 
47105
- export { ApiOptions, ArrowPicker, ArticleRenderer, BaseRadio, Categorizer$1 as Categorizer, changeable as Changeable, ClassNames, ColorPicker, DashPicker, dependencies as Dependencies, EditorJsonify, ElementContainer, Errors, Expression, util as GrapherUtil, Grapher$1 as GrapherWidget, HintRenderer, HintsRenderer, InputNumber$1 as InputNumber, InteractiveGraph$1 as InteractiveGraphWidget, ItemExtras, JiptParagraphs, KhanColors, KhanMath, context as LoadingContext, Log, Matrix$1 as MatrixWidget, MultiItems, NumericInput$1 as NumericInput, PerseusError, PerseusExpressionAnswerFormConsidered, PerseusI18nContext, PerseusI18nContextProvider, PerseusMarkdown, Plotter$1 as PlotterWidget, QUESTION_WIDGETS, Radio, Renderer, ref as ServerItemRenderer, sizingUtils as SizingUtils, Table$1 as TableWidget, Util, denylist as WIDGET_PROP_DENYLIST, WidgetJsonifyDeprecated, widgets$1 as Widgets, addWidget, apiVersion, buildEmptyItemTreeForShape, buildMapper, components, constants, contentHasWidgetType, convertWidgetNameToEnum, displaySigFigs, generateTestCategorizerWidget, generateTestExpressionWidget, generateTestInteractiveGraphWidget, generateTestNumericInputWidget, generateTestPerseusItem, generateTestRadioWidget, genericPerseusItemData, getAnswerFromUserInput, getAnswersFromWidgets, getCorrectAnswerForWidgetId, getValidWidgetIds, getWidgetFromWidgetMap, getWidgetIdsFromContent, getWidgetIdsFromContentByType, getWidgetTypeByWidgetId, getWidgetsFromWidgetMap, getWidgetsMapFromItemData, globalConstants, globalStyles, iconPaths as icons, init, injectWidgets, interactiveSizes$1 as interactiveSizes, isItemRenderableByVersion, isWidgetIdInContent, isWrongAnswerSupported, itemDataVersion, itemToTree, ItemVersion as itemVersion, libVersion, lockedEllipseFillStyles, lockedFigureColors, mediaQueries, parsePerseusItem, plotterPlotTypes, preprocessTex, registerAllWidgetsForTesting, shapes, shouldHaveIndividualAnswer, traverse, usePerseusI18n, violatingWidgets, allWidgets as widgets };
47043
+ export { ApiOptions, ArrowPicker, ArticleRenderer, BaseRadio, Categorizer$1 as Categorizer, changeable as Changeable, ClassNames, ColorPicker, DashPicker, dependencies as Dependencies, EditorJsonify, ElementContainer, Errors, Expression, util as GrapherUtil, Grapher$1 as GrapherWidget, HintRenderer, HintsRenderer, InputNumber$1 as InputNumber, InteractiveGraph$1 as InteractiveGraphWidget, ItemExtras, JiptParagraphs, KhanColors, KhanMath, context as LoadingContext, Log, Matrix$1 as MatrixWidget, MultiItems, NumericInput$1 as NumericInput, PerseusError, PerseusExpressionAnswerFormConsidered, PerseusI18nContext, PerseusI18nContextProvider, PerseusMarkdown, Plotter$1 as PlotterWidget, QUESTION_WIDGETS, Radio, Renderer, ref as ServerItemRenderer, sizingUtils as SizingUtils, Table$1 as TableWidget, Util, denylist as WIDGET_PROP_DENYLIST, WidgetJsonifyDeprecated, widgets$1 as Widgets, addWidget, apiVersion, buildEmptyItemTreeForShape, buildMapper, components, constants, contentHasWidgetType, convertWidgetNameToEnum, displaySigFigs, generateTestCategorizerWidget, generateTestExpressionWidget, generateTestInteractiveGraphWidget, generateTestNumericInputWidget, generateTestPerseusItem, generateTestRadioWidget, genericPerseusItemData, getAnswerFromUserInput, getAnswersFromWidgets, getCorrectAnswerForWidgetId, getValidWidgetIds, getWidgetFromWidgetMap, getWidgetIdsFromContent, getWidgetIdsFromContentByType, getWidgetTypeByWidgetId, getWidgetsFromWidgetMap, getWidgetsMapFromItemData, globalConstants, globalStyles, iconPaths as icons, init, injectWidgets, interactiveSizes$1 as interactiveSizes, isItemRenderableByVersion, isWidgetIdInContent, isWrongAnswerSupported, itemDataVersion, itemToTree, ItemVersion as itemVersion, libVersion, lockedFigureColors, lockedFigureFillStyles, mediaQueries, parsePerseusItem, plotterPlotTypes, preprocessTex, registerAllWidgetsForTesting, shapes, shouldHaveIndividualAnswer, traverse, usePerseusI18n, violatingWidgets, allWidgets as widgets };
47106
47044
  //# sourceMappingURL=index.js.map