@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/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,
@@ -28947,13 +28802,15 @@ const ItemExtras = [
28947
28802
 
28948
28803
  const PerseusExpressionAnswerFormConsidered = ["correct", "wrong", "ungraded"];
28949
28804
  const lockedFigureColors = {
28805
+ blue: "#3D7586",
28950
28806
  green: "#447A53",
28951
28807
  grayH: "#3B3D45",
28952
28808
  purple: "#594094",
28953
28809
  pink: "#B25071",
28954
- red: "#D92916"
28810
+ red: "#D92916",
28811
+ orange: "#946700"
28955
28812
  };
28956
- const lockedEllipseFillStyles = {
28813
+ const lockedFigureFillStyles = {
28957
28814
  none: 0,
28958
28815
  solid: 1,
28959
28816
  translucent: 0.4
@@ -28973,7 +28830,7 @@ const LockedEllipse = props => {
28973
28830
  center: center,
28974
28831
  radius: radius,
28975
28832
  angle: angle,
28976
- fillOpacity: lockedEllipseFillStyles[fillStyle],
28833
+ fillOpacity: lockedFigureFillStyles[fillStyle],
28977
28834
  strokeStyle: strokeStyle,
28978
28835
  color: lockedFigureColors[color]
28979
28836
  });
@@ -29232,6 +29089,29 @@ const LockedPoint = props => {
29232
29089
  });
29233
29090
  };
29234
29091
 
29092
+ const LockedPolygon = props => {
29093
+ const {
29094
+ points,
29095
+ color,
29096
+ showVertices,
29097
+ fillStyle,
29098
+ strokeStyle
29099
+ } = props;
29100
+ return /*#__PURE__*/React__namespace.createElement("g", {
29101
+ className: "locked-polygon"
29102
+ }, /*#__PURE__*/React__namespace.createElement(mafs.Polygon, {
29103
+ points: [...points],
29104
+ fillOpacity: lockedFigureFillStyles[fillStyle],
29105
+ strokeStyle: strokeStyle,
29106
+ color: lockedFigureColors[color]
29107
+ }), showVertices && points.map((point, index) => /*#__PURE__*/React__namespace.createElement(mafs.Point, {
29108
+ key: `locked-polygon-point-${index}`,
29109
+ x: point[0],
29110
+ y: point[1],
29111
+ color: lockedFigureColors[color]
29112
+ })));
29113
+ };
29114
+
29235
29115
  const LockedVector = props => {
29236
29116
  const {
29237
29117
  color,
@@ -29262,13 +29142,17 @@ const GraphLockedLayer = props => {
29262
29142
  key: `line-${index}`,
29263
29143
  range: props.range
29264
29144
  }, figure));
29145
+ case "vector":
29146
+ return /*#__PURE__*/React__namespace.createElement(LockedVector, _extends({
29147
+ key: `vector-${index}`
29148
+ }, figure));
29265
29149
  case "ellipse":
29266
29150
  return /*#__PURE__*/React__namespace.createElement(LockedEllipse, _extends({
29267
29151
  key: `ellipse-${index}`
29268
29152
  }, figure));
29269
- case "vector":
29270
- return /*#__PURE__*/React__namespace.createElement(LockedVector, _extends({
29271
- key: `vector-${index}`
29153
+ case "polygon":
29154
+ return /*#__PURE__*/React__namespace.createElement(LockedPolygon, _extends({
29155
+ key: `polygon-${index}`
29272
29156
  }, figure));
29273
29157
  default:
29274
29158
  /**
@@ -29282,6 +29166,13 @@ const GraphLockedLayer = props => {
29282
29166
  }));
29283
29167
  };
29284
29168
 
29169
+ const REINITIALIZE = "reinitialize";
29170
+ function reinitialize(params) {
29171
+ return {
29172
+ type: REINITIALIZE,
29173
+ params
29174
+ };
29175
+ }
29285
29176
  const MOVE_CONTROL_POINT = "move-control-point";
29286
29177
  function moveControlPoint(pointIndex, destination, itemIndex) {
29287
29178
  return {
@@ -29374,6 +29265,10 @@ function snap(snapStep, point) {
29374
29265
  return [Math.round(requestedX / snapX) * snapX, Math.round(requestedY / snapY) * snapY];
29375
29266
  }
29376
29267
 
29268
+ function useDraggable(params) {
29269
+ return mafs.useMovable(params);
29270
+ }
29271
+
29377
29272
  // The hitbox size of 48px by 48px is preserved from the legacy interactive
29378
29273
  // graph.
29379
29274
  const hitboxSizePx = 48;
@@ -29529,7 +29424,7 @@ function useControlPoint(point, color, onMovePoint) {
29529
29424
  } = useGraphConfig();
29530
29425
  const [focused, setFocused] = React.useState(false);
29531
29426
  const keyboardHandleRef = React.useRef(null);
29532
- mafs.useMovable({
29427
+ useDraggable({
29533
29428
  gestureTarget: keyboardHandleRef,
29534
29429
  point,
29535
29430
  onMove: onMovePoint,
@@ -29538,7 +29433,7 @@ function useControlPoint(point, color, onMovePoint) {
29538
29433
  const visiblePointRef = React.useRef(null);
29539
29434
  const {
29540
29435
  dragging
29541
- } = mafs.useMovable({
29436
+ } = useDraggable({
29542
29437
  gestureTarget: visiblePointRef,
29543
29438
  point,
29544
29439
  onMove: onMovePoint,
@@ -29592,7 +29487,7 @@ const Line$1 = props => {
29592
29487
  const line = React.useRef(null);
29593
29488
  const {
29594
29489
  dragging
29595
- } = mafs.useMovable({
29490
+ } = useDraggable({
29596
29491
  gestureTarget: line,
29597
29492
  point: start,
29598
29493
  onMove: newPoint => {
@@ -29940,7 +29835,7 @@ const StyledMovablePoint = props => {
29940
29835
  const snapToValue = snapTo ?? "grid";
29941
29836
  const {
29942
29837
  dragging
29943
- } = mafs.useMovable({
29838
+ } = useDraggable({
29944
29839
  gestureTarget: elementRef,
29945
29840
  point,
29946
29841
  onMove,
@@ -29982,7 +29877,7 @@ const PolygonGraph = props => {
29982
29877
  const snapToValue = snapTo ?? "grid";
29983
29878
  const {
29984
29879
  dragging
29985
- } = mafs.useMovable({
29880
+ } = useDraggable({
29986
29881
  gestureTarget: ref,
29987
29882
  point: dragReferencePoint,
29988
29883
  onMove: newPoint => {
@@ -30157,7 +30052,7 @@ function MovableCircle(props) {
30157
30052
  const draggableRef = React.useRef(null);
30158
30053
  const {
30159
30054
  dragging
30160
- } = mafs.useMovable({
30055
+ } = useDraggable({
30161
30056
  gestureTarget: draggableRef,
30162
30057
  point: center,
30163
30058
  onMove,
@@ -30288,10 +30183,24 @@ function SinusoidGraph(props) {
30288
30183
  coords
30289
30184
  } = graphState;
30290
30185
 
30291
- // 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
+ });
30292
30196
  const coeffs = getSinusoidCoefficients(coords);
30197
+
30198
+ // If the coefficients are valid, update the reference
30199
+ if (coeffs !== undefined) {
30200
+ coeffRef.current = coeffs;
30201
+ }
30293
30202
  return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(mafs.Plot.OfX, {
30294
- y: x => computeSine(x, coeffs),
30203
+ y: x => computeSine(x, coeffRef.current),
30295
30204
  color: wonderBlocksTokens.color.blue
30296
30205
  }), coords.map((coord, i) => /*#__PURE__*/React__namespace.createElement(StyledMovablePoint, {
30297
30206
  key: "point-" + i,
@@ -30318,6 +30227,11 @@ const getSinusoidCoefficients = coords => {
30318
30227
  const p1 = coords[0];
30319
30228
  const p2 = coords[1];
30320
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
+
30321
30235
  // Resulting coefficients are canonical for this sine curve
30322
30236
  const amplitude = p2[1] - p1[1];
30323
30237
  const angularFrequency = Math.PI / (2 * (p2[0] - p1[0]));
@@ -31609,6 +31523,8 @@ function getCircleCoords(graph) {
31609
31523
 
31610
31524
  function interactiveGraphReducer(state, action) {
31611
31525
  switch (action.type) {
31526
+ case REINITIALIZE:
31527
+ return initializeGraphState(action.params);
31612
31528
  case MOVE_CONTROL_POINT:
31613
31529
  return doMoveControlPoint(state, action);
31614
31530
  case MOVE_LINE:
@@ -31809,11 +31725,14 @@ function doMovePoint(state, action) {
31809
31725
  }
31810
31726
  case "sinusoid":
31811
31727
  {
31812
- // First, we need to verify that the new coordinates are not on the same vertical line
31813
- // 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
31814
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
31815
31734
  const newCoords = [...state.coords];
31816
- newCoords[action.index] = action.destination;
31735
+ newCoords[action.index] = boundDestination;
31817
31736
  if (newCoords[0][0] === newCoords[1][0]) {
31818
31737
  return state;
31819
31738
  }
@@ -31823,7 +31742,7 @@ function doMovePoint(state, action) {
31823
31742
  coords: setAtIndex({
31824
31743
  array: state.coords,
31825
31744
  index: action.index,
31826
- newValue: boundAndSnapToGrid(destination, state)
31745
+ newValue: boundDestination
31827
31746
  })
31828
31747
  };
31829
31748
  }
@@ -31831,7 +31750,10 @@ function doMovePoint(state, action) {
31831
31750
  {
31832
31751
  // Set up the new coords and check if the quadratic coefficients are valid
31833
31752
  const newCoords = [...state.coords];
31834
- 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;
31835
31757
  const QuadraticCoefficients = getQuadraticCoefficients(newCoords);
31836
31758
 
31837
31759
  // If the new destination results in an invalid quadratic equation, we don't want to move the point
@@ -31844,7 +31766,7 @@ function doMovePoint(state, action) {
31844
31766
  coords: setAtIndex({
31845
31767
  array: state.coords,
31846
31768
  index: action.index,
31847
- newValue: boundAndSnapToGrid(action.destination, state)
31769
+ newValue: boundDestination
31848
31770
  })
31849
31771
  };
31850
31772
  }
@@ -32258,11 +32180,12 @@ function mafsStateToInteractiveGraph(state) {
32258
32180
  }
32259
32181
  const StatefulMafsGraph = /*#__PURE__*/React__namespace.forwardRef((props, ref) => {
32260
32182
  const {
32261
- onChange
32183
+ onChange,
32184
+ graph
32262
32185
  } = props;
32263
32186
  const [state, dispatch] = React__namespace.useReducer(interactiveGraphReducer, props, initializeGraphState);
32264
32187
  React.useImperativeHandle(ref, () => ({
32265
- getUserInput: () => getGradableGraph(state, props.graph)
32188
+ getUserInput: () => getGradableGraph(state, graph)
32266
32189
  }));
32267
32190
  const prevState = React.useRef(state);
32268
32191
  React.useEffect(() => {
@@ -32285,6 +32208,18 @@ const StatefulMafsGraph = /*#__PURE__*/React__namespace.forwardRef((props, ref)
32285
32208
  React.useEffect(() => {
32286
32209
  dispatch(changeRange([[xMinRange, xMaxRange], [yMinRange, yMaxRange]]));
32287
32210
  }, [dispatch, xMinRange, xMaxRange, yMinRange, yMaxRange]);
32211
+ const numSegments = graph.type === "segment" ? graph.numSegments : null;
32212
+ const originalPropsRef = React.useRef(props);
32213
+ const latestPropsRef = wonderBlocksCore.useLatestRef(props);
32214
+ React.useEffect(() => {
32215
+ // This conditional prevents the state from being "reinitialized" right
32216
+ // after the first render. This is an optimization, but also prevents
32217
+ // a bug where the graph would be marked "incorrect" during grading
32218
+ // even if the user never interacted with it.
32219
+ if (latestPropsRef.current !== originalPropsRef.current) {
32220
+ dispatch(reinitialize(latestPropsRef.current));
32221
+ }
32222
+ }, [graph.type, numSegments, latestPropsRef]);
32288
32223
  return /*#__PURE__*/React__namespace.createElement(MafsGraph, _extends({}, props, {
32289
32224
  state: state,
32290
32225
  dispatch: dispatch
@@ -33647,6 +33582,12 @@ class InteractiveGraph extends React__namespace.Component {
33647
33582
  return InteractiveGraph.validate(this.getUserInput?.(), rubric, this);
33648
33583
  }
33649
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
+
33650
33591
  // Mafs shim
33651
33592
  if (this.props.apiOptions?.flags?.["mafs"]?.[this.props.graph.type]) {
33652
33593
  const box = getInteractiveBoxFromSizeClass(this.props.containerSizeClass);
@@ -44446,7 +44387,7 @@ const init = function (options) {
44446
44387
 
44447
44388
  // This file is processed by a Rollup plugin (replace) to inject the production
44448
44389
  const libName = "@khanacademy/perseus";
44449
- const libVersion = "23.4.0";
44390
+ const libVersion = "23.6.0";
44450
44391
  perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
44451
44392
 
44452
44393
  const apiVersion = {
@@ -47546,8 +47487,8 @@ exports.itemDataVersion = itemDataVersion;
47546
47487
  exports.itemToTree = itemToTree;
47547
47488
  exports.itemVersion = ItemVersion;
47548
47489
  exports.libVersion = libVersion;
47549
- exports.lockedEllipseFillStyles = lockedEllipseFillStyles;
47550
47490
  exports.lockedFigureColors = lockedFigureColors;
47491
+ exports.lockedFigureFillStyles = lockedFigureFillStyles;
47551
47492
  exports.mediaQueries = mediaQueries;
47552
47493
  exports.parsePerseusItem = parsePerseusItem;
47553
47494
  exports.plotterPlotTypes = plotterPlotTypes;