@khanacademy/perseus 50.0.0 → 51.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.
Files changed (117) hide show
  1. package/dist/es/index.js +597 -2403
  2. package/dist/es/index.js.map +1 -1
  3. package/dist/es/strings.js +58 -11
  4. package/dist/es/strings.js.map +1 -1
  5. package/dist/index.d.ts +3 -8
  6. package/dist/index.js +537 -2320
  7. package/dist/index.js.map +1 -1
  8. package/dist/renderer-util.d.ts +1 -2
  9. package/dist/renderer.d.ts +2 -2
  10. package/dist/server-item-renderer.d.ts +1 -1
  11. package/dist/strings.d.ts +204 -13
  12. package/dist/strings.js +75 -10
  13. package/dist/strings.js.map +1 -1
  14. package/dist/types.d.ts +11 -14
  15. package/dist/util/scoring.d.ts +1 -2
  16. package/dist/util/test-utils.d.ts +1 -2
  17. package/dist/util.d.ts +0 -14
  18. package/dist/widget-ai-utils/categorizer/categorizer-ai-utils.d.ts +1 -1
  19. package/dist/widget-ai-utils/dropdown/dropdown-ai-utils.d.ts +1 -1
  20. package/dist/widget-ai-utils/expression/expression-ai-utils.d.ts +1 -1
  21. package/dist/widget-ai-utils/grapher/grapher-ai-utils.d.ts +1 -1
  22. package/dist/widget-ai-utils/input-number/input-number-ai-utils.d.ts +1 -1
  23. package/dist/widget-ai-utils/label-image/label-image-ai-utils.d.ts +1 -1
  24. package/dist/widget-ai-utils/matcher/matcher-ai-utils.d.ts +1 -1
  25. package/dist/widget-ai-utils/matrix/matrix-ai-utils.d.ts +1 -1
  26. package/dist/widget-ai-utils/mock-widget/prompt-utils.d.ts +1 -1
  27. package/dist/widget-ai-utils/number-line/number-line-ai-utils.d.ts +1 -1
  28. package/dist/widget-ai-utils/numeric-input/prompt-utils.d.ts +1 -1
  29. package/dist/widget-ai-utils/orderer/orderer-ai-utils.d.ts +1 -1
  30. package/dist/widget-ai-utils/radio/radio-ai-utils.d.ts +1 -1
  31. package/dist/widget-ai-utils/sorter/sorter-ai-utils.d.ts +1 -1
  32. package/dist/widgets/__shared__/score-noop.d.ts +1 -1
  33. package/dist/widgets/categorizer/categorizer.d.ts +4 -2
  34. package/dist/widgets/categorizer/categorizer.util.d.ts +17 -0
  35. package/dist/widgets/cs-program/cs-program.d.ts +1 -2
  36. package/dist/widgets/definition/definition.d.ts +1 -1
  37. package/dist/widgets/deprecated-standin/deprecated-standin.d.ts +1 -1
  38. package/dist/widgets/dropdown/dropdown.d.ts +2 -2
  39. package/dist/widgets/explanation/explanation.d.ts +1 -1
  40. package/dist/widgets/expression/expression.d.ts +5 -6
  41. package/dist/widgets/graded-group/graded-group.d.ts +1 -1
  42. package/dist/widgets/graded-group-set/graded-group-set.d.ts +1 -1
  43. package/dist/widgets/grapher/grapher.d.ts +1 -2
  44. package/dist/widgets/grapher/util.d.ts +5 -13
  45. package/dist/widgets/group/group.d.ts +2 -2
  46. package/dist/widgets/group/score-group.d.ts +1 -2
  47. package/dist/widgets/iframe/iframe.d.ts +1 -2
  48. package/dist/widgets/image/image.d.ts +1 -1
  49. package/dist/widgets/input-number/input-number.d.ts +1 -2
  50. package/dist/widgets/interaction/interaction.d.ts +1 -1
  51. package/dist/widgets/interactive-graph.d.ts +2 -3
  52. package/dist/widgets/interactive-graphs/graphs/linear-system.d.ts +2 -0
  53. package/dist/widgets/interactive-graphs/graphs/quadratic.d.ts +1 -3
  54. package/dist/widgets/interactive-graphs/graphs/ray.d.ts +2 -0
  55. package/dist/widgets/interactive-graphs/graphs/sinusoid.d.ts +4 -9
  56. package/dist/widgets/interactive-graphs/graphs/utils.d.ts +4 -0
  57. package/dist/widgets/interactive-graphs/math/index.d.ts +0 -1
  58. package/dist/widgets/interactive-graphs/stateful-mafs-graph.d.ts +1 -1
  59. package/dist/widgets/interactive-graphs/types.d.ts +2 -1
  60. package/dist/widgets/label-image/label-image.d.ts +2 -4
  61. package/dist/widgets/label-image/marker.d.ts +1 -2
  62. package/dist/widgets/matcher/matcher.d.ts +1 -2
  63. package/dist/widgets/matrix/matrix.d.ts +2 -4
  64. package/dist/widgets/measurer/measurer.d.ts +1 -1
  65. package/dist/widgets/mock-widgets/mock-widget.d.ts +1 -1
  66. package/dist/widgets/mock-widgets/score-mock-widget.d.ts +1 -2
  67. package/dist/widgets/molecule/molecule.d.ts +1 -1
  68. package/dist/widgets/number-line/number-line.d.ts +2 -3
  69. package/dist/widgets/numeric-input/numeric-input.d.ts +1 -2
  70. package/dist/widgets/orderer/orderer.d.ts +1 -2
  71. package/dist/widgets/passage/passage.d.ts +1 -1
  72. package/dist/widgets/passage-ref/passage-ref.d.ts +1 -1
  73. package/dist/widgets/passage-ref-target/passage-ref-target.d.ts +1 -1
  74. package/dist/widgets/plotter/plotter.d.ts +1 -2
  75. package/dist/widgets/radio/radio-component.d.ts +1 -1
  76. package/dist/widgets/radio/radio.d.ts +1 -1
  77. package/dist/widgets/sorter/sorter.d.ts +1 -2
  78. package/dist/widgets/table/table.d.ts +1 -2
  79. package/dist/widgets/video/video.d.ts +1 -1
  80. package/dist/widgets.d.ts +2 -1
  81. package/package.json +11 -11
  82. package/dist/tex-wrangler.d.ts +0 -7
  83. package/dist/util/geometry.d.ts +0 -28
  84. package/dist/validation.types.d.ts +0 -166
  85. package/dist/widgets/categorizer/score-categorizer.d.ts +0 -5
  86. package/dist/widgets/categorizer/validate-categorizer.d.ts +0 -13
  87. package/dist/widgets/cs-program/score-cs-program.d.ts +0 -4
  88. package/dist/widgets/dropdown/score-dropdown.d.ts +0 -4
  89. package/dist/widgets/dropdown/validate-dropdown.d.ts +0 -8
  90. package/dist/widgets/expression/get-decimal-separator.d.ts +0 -5
  91. package/dist/widgets/expression/score-expression.d.ts +0 -5
  92. package/dist/widgets/expression/validate-expression.d.ts +0 -12
  93. package/dist/widgets/grapher/grapher-types.d.ts +0 -58
  94. package/dist/widgets/grapher/score-grapher.d.ts +0 -4
  95. package/dist/widgets/iframe/score-iframe.d.ts +0 -3
  96. package/dist/widgets/input-number/score-input-number.d.ts +0 -39
  97. package/dist/widgets/interactive-graphs/math/angles.d.ts +0 -7
  98. package/dist/widgets/interactive-graphs/score-interactive-graph.d.ts +0 -4
  99. package/dist/widgets/label-image/score-label-image.d.ts +0 -10
  100. package/dist/widgets/label-image/types.d.ts +0 -11
  101. package/dist/widgets/matcher/score-matcher.d.ts +0 -4
  102. package/dist/widgets/matrix/score-matrix.d.ts +0 -5
  103. package/dist/widgets/matrix/validate-matrix.d.ts +0 -13
  104. package/dist/widgets/number-line/score-number-line.d.ts +0 -4
  105. package/dist/widgets/number-line/validate-number-line.d.ts +0 -12
  106. package/dist/widgets/numeric-input/score-numeric-input.d.ts +0 -6
  107. package/dist/widgets/orderer/score-orderer.d.ts +0 -3
  108. package/dist/widgets/orderer/validate-orderer.d.ts +0 -10
  109. package/dist/widgets/plotter/score-plotter.d.ts +0 -4
  110. package/dist/widgets/plotter/validate-plotter.d.ts +0 -10
  111. package/dist/widgets/radio/score-radio.d.ts +0 -5
  112. package/dist/widgets/radio/validate-radio.d.ts +0 -12
  113. package/dist/widgets/sorter/score-sorter.d.ts +0 -4
  114. package/dist/widgets/sorter/validate-sorter.d.ts +0 -11
  115. package/dist/widgets/table/score-table.d.ts +0 -5
  116. package/dist/widgets/table/utils.d.ts +0 -7
  117. package/dist/widgets/table/validate-table.d.ts +0 -4
package/dist/es/index.js CHANGED
@@ -1,7 +1,9 @@
1
1
  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
+ import { PerseusError, Errors, getDecimalSeparator, approximateDeepEqual, pluck, mapObject, GrapherUtil as GrapherUtil$1, lockedFigureColors, lockedFigureFillStyles, approximateEqual, getMatrixSize, addLibraryVersionToPerseusDebug, lockedFigureColorNames, plotterPlotTypes, ItemExtras } from '@khanacademy/perseus-core';
3
4
  import * as PerseusLinter from '@khanacademy/perseus-linter';
4
5
  import { linterContextDefault, Rule } from '@khanacademy/perseus-linter';
6
+ import { scoreExpression, KhanAnswerTypes, scoreInputNumber, inputNumberAnswerTypes, scoreNumericInput, scoreRadio, scoreCategorizer, scoreCSProgram, scoreDropdown, scoreGrapher, scoreIframe, scoreInteractiveGraph, scoreLabelImage, scoreLabelImageMarker, scoreMatcher, scoreMatrix, scoreNumberLine, scoreOrderer, scorePlotter, scoreSorter, scoreTable } from '@khanacademy/perseus-score';
5
7
  import { View, RenderStateRoot, Id, useOnMountEffect, useLatestRef, Text as Text$2 } from '@khanacademy/wonder-blocks-core';
6
8
  import Tooltip$1 from '@khanacademy/wonder-blocks-tooltip';
7
9
  import { HeadingMedium, LabelSmall, LabelLarge, Body, LabelMedium } from '@khanacademy/wonder-blocks-typography';
@@ -12,17 +14,15 @@ import React__default, { useContext, useState, useEffect, useRef, createContext,
12
14
  import * as ReactDOM from 'react-dom';
13
15
  import ReactDOM__default from 'react-dom';
14
16
  import _, { debounce as debounce$1 } from 'underscore';
15
- import { ErrorCodes, KhanAnswerTypes } from '@khanacademy/perseus-score';
16
17
  import Clickable from '@khanacademy/wonder-blocks-clickable';
17
18
  import { Popover, PopoverContentCore, PopoverContent } from '@khanacademy/wonder-blocks-popover';
18
19
  import { color, spacing, font } from '@khanacademy/wonder-blocks-tokens';
19
20
  import $ from 'jquery';
20
21
  import { v4 } from 'uuid';
21
- import { PerseusError, Errors, pluck, mapObject, lockedFigureColors, lockedFigureFillStyles, addLibraryVersionToPerseusDebug, lockedFigureColorNames, plotterPlotTypes, ItemExtras } from '@khanacademy/perseus-core';
22
22
  import PropTypes from 'prop-types';
23
23
  import { entries, UnreachableCaseError, keys } from '@khanacademy/wonder-stuff-core';
24
24
  import { CircularSpinner } from '@khanacademy/wonder-blocks-progress-spinner';
25
- import { point, KhanMath, number as number$1, vector as vector$1, sum as sum$1, line } from '@khanacademy/kmath';
25
+ import { point, KhanMath, number as number$1, vector as vector$3, line, geometry, angles, coefficients } from '@khanacademy/kmath';
26
26
  import SimpleMarkdown from '@khanacademy/simple-markdown';
27
27
  import { pureMarkdownRules } from '@khanacademy/pure-markdown';
28
28
  import { TextField } from '@khanacademy/wonder-blocks-form';
@@ -33,7 +33,7 @@ import { OptionItem, SingleSelect, MultiSelect } from '@khanacademy/wonder-block
33
33
  import caretDown from '@phosphor-icons/core/regular/caret-down.svg';
34
34
  import caretUp from '@phosphor-icons/core/regular/caret-up.svg';
35
35
  import debounce$2 from 'lodash.debounce';
36
- import { vec, Coordinates, Ellipse, Plot as Plot$3, Line as Line$4, Point as Point$2, Polygon, Text as Text$1, useTransformContext, Polyline, Mafs } from 'mafs';
36
+ import { vec, Coordinates, Ellipse, Plot as Plot$2, Line as Line$4, Point as Point$2, Polygon, Text as Text$1, useTransformContext, Polyline, Mafs } from 'mafs';
37
37
  import { PhosphorIcon } from '@khanacademy/wonder-blocks-icon';
38
38
  import questionIcon from '@phosphor-icons/core/regular/question.svg';
39
39
  import { useDrag } from '@use-gesture/react';
@@ -231,7 +231,15 @@ const strings = {
231
231
  srAngleSideAtCoordinates: "Point %(point)s, %(side)s at %(x)s comma %(y)s",
232
232
  srAngleVertexAtCoordinatesWithAngleMeasure: "Point 2, vertex at %(x)s comma %(y)s. Angle %(angleMeasure)s degrees",
233
233
  srAngleGraphAriaLabel: "An angle on a coordinate plane.",
234
- srAngleGraphAriaDescription: "The angle measure is %(angleMeasure)s degrees with a vertex at %(vertexX)s comma %(vertexY)s, a point on the starting side at %(startingSideX)s comma %(startingSideY)s and a point on the ending side at %(endingSideX)s comma %(endingSideY)s"
234
+ srAngleGraphAriaDescription: "The angle measure is %(angleMeasure)s degrees with a vertex at %(vertexX)s comma %(vertexY)s, a point on the starting side at %(startingSideX)s comma %(startingSideY)s and a point on the ending side at %(endingSideX)s comma %(endingSideY)s",
235
+ srLinearSystemGraph: "Two lines on a coordinate plane.",
236
+ srLinearSystemPoints: "Line %(lineNumber)s has two points, point 1 at %(point1X)s comma %(point1Y)s and point 2 at %(point2X)s comma %(point2Y)s.",
237
+ srLinearSystemPoint: "Point %(pointSequence)s on line %(lineNumber)s at %(x)s comma %(y)s.",
238
+ srRayGraph: "A ray on a coordinate plane.",
239
+ srRayPoints: "The endpoint is at %(point1X)s comma %(point1Y)s and the ray goes through point %(point2X)s comma %(point2Y)s.",
240
+ srRayGrabHandle: "Ray with endpoint %(point1X)s comma %(point1Y)s going through point %(point2X)s comma %(point2Y)s.",
241
+ srRayEndpoint: "Endpoint at %(point1X)s comma %(point1Y)s.",
242
+ srRayTerminalPoint: "Through point at %(point2X)s comma %(point2Y)s."
235
243
  // The above strings are used for interactive graph SR descriptions.
236
244
  };
237
245
 
@@ -500,19 +508,66 @@ const mockStrings = {
500
508
  startingSideY,
501
509
  endingSideX,
502
510
  endingSideY
503
- }) => `The angle measure is ${angleMeasure} degrees with a vertex at ${vertexX} comma ${vertexY}, a point on the starting side at ${startingSideX} comma ${startingSideY} and a point on the ending side at ${endingSideX} comma ${endingSideY}.`
511
+ }) => `The angle measure is ${angleMeasure} degrees with a vertex at ${vertexX} comma ${vertexY}, a point on the starting side at ${startingSideX} comma ${startingSideY} and a point on the ending side at ${endingSideX} comma ${endingSideY}.`,
512
+ srLinearSystemGraph: "Two lines on a coordinate plane.",
513
+ srLinearSystemPoints: ({
514
+ lineNumber,
515
+ point1X,
516
+ point1Y,
517
+ point2X,
518
+ point2Y
519
+ }) => `Line ${lineNumber} has two points, point 1 at ${point1X} comma ${point1Y} and point 2 at ${point2X} comma ${point2Y}.`,
520
+ srLinearSystemPoint: ({
521
+ lineNumber,
522
+ pointSequence,
523
+ x,
524
+ y
525
+ }) => `Point ${pointSequence} on line ${lineNumber} at ${x} comma ${y}.`,
526
+ srRayGraph: "A ray on a coordinate plane.",
527
+ srRayPoints: ({
528
+ point1X,
529
+ point1Y,
530
+ point2X,
531
+ point2Y
532
+ }) => `The endpoint is at ${point1X} comma ${point1Y} and the ray goes through point ${point2X} comma ${point2Y}.`,
533
+ srRayGrabHandle: ({
534
+ point1X,
535
+ point1Y,
536
+ point2X,
537
+ point2Y
538
+ }) => `Ray with endpoint ${point1X} comma ${point1Y} going through point ${point2X} comma ${point2Y}.`,
539
+ srRayEndpoint: ({
540
+ x,
541
+ y
542
+ }) => `Endpoint at ${x} comma ${y}.`,
543
+ srRayTerminalPoint: ({
544
+ x,
545
+ y
546
+ }) => `Through point at ${x} comma ${y}.`
504
547
  // The above strings are used for interactive graph SR descriptions.
505
548
  };
506
549
 
507
- ({
508
- [ErrorCodes.MISSING_PERCENT_ERROR]: strings.MISSING_PERCENT_ERROR,
509
- [ErrorCodes.NEEDS_TO_BE_SIMPLIFIED_ERROR]: strings.NEEDS_TO_BE_SIMPLFIED_ERROR,
510
- [ErrorCodes.APPROXIMATED_PI_ERROR]: strings.APPROXIMATED_PI_ERROR,
511
- [ErrorCodes.EXTRA_SYMBOLS_ERROR]: strings.EXTRA_SYMBOLS_ERROR,
512
- [ErrorCodes.WRONG_CASE_ERROR]: strings.WRONG_CASE_ERROR,
513
- [ErrorCodes.WRONG_LETTER_ERROR]: strings.WRONG_LETTER_ERROR,
514
- [ErrorCodes.MULTIPLICATION_SIGN_ERROR]: strings.MULTIPLICATION_SIGN_ERROR
515
- });
550
+ // This type helps us make sure all error codes are mapped to strings
551
+
552
+ const errorToString = {
553
+ MISSING_PERCENT_ERROR: strings.MISSING_PERCENT_ERROR,
554
+ NEEDS_TO_BE_SIMPLIFIED_ERROR: strings.NEEDS_TO_BE_SIMPLFIED_ERROR,
555
+ APPROXIMATED_PI_ERROR: strings.APPROXIMATED_PI_ERROR,
556
+ EXTRA_SYMBOLS_ERROR: strings.EXTRA_SYMBOLS_ERROR,
557
+ WRONG_CASE_ERROR: strings.WRONG_CASE_ERROR,
558
+ WRONG_LETTER_ERROR: strings.WRONG_LETTER_ERROR,
559
+ MULTIPLICATION_SIGN_ERROR: strings.MULTIPLICATION_SIGN_ERROR,
560
+ INVALID_SELECTION_ERROR: strings.invalidSelection,
561
+ CHOOSE_CORRECT_NUM_ERROR: strings.chooseCorrectNum,
562
+ NOT_NONE_ABOVE_ERROR: strings.notNoneOfTheAbove,
563
+ FILL_ALL_CELLS_ERROR: strings.fillAllCells
564
+ };
565
+ function mapErrorToString(err) {
566
+ if (!err) {
567
+ return err;
568
+ }
569
+ return errorToString[err] || err;
570
+ }
516
571
 
517
572
  /**
518
573
  * PerseusI18nContext provides a way to set the strings and locale that are
@@ -1449,205 +1504,6 @@ const getPromptJSON$t = (renderProps, userInput) => {
1449
1504
  };
1450
1505
  };
1451
1506
 
1452
- /**
1453
- * Get the character used for separating decimals.
1454
- */
1455
- const getDecimalSeparator = locale => {
1456
- var _match$;
1457
- switch (locale) {
1458
- // TODO(somewhatabstract): Remove this when Chrome supports the `ka`
1459
- // locale properly.
1460
- // https://github.com/formatjs/formatjs/issues/1526#issuecomment-559891201
1461
- //
1462
- // Supported locales in Chrome:
1463
- // https://source.chromium.org/chromium/chromium/src/+/master:third_party/icu/scripts/chrome_ui_languages.list
1464
- case "ka":
1465
- return ",";
1466
- default:
1467
- const numberWithDecimalSeparator = 1.1;
1468
- // TODO(FEI-3647): Update to use .formatToParts() once we no longer have to
1469
- // support Safari 12.
1470
- const match = new Intl.NumberFormat(locale).format(numberWithDecimalSeparator)
1471
- // 0x661 is ARABIC-INDIC DIGIT ONE
1472
- // 0x6F1 is EXTENDED ARABIC-INDIC DIGIT ONE
1473
- .match(/[^\d\u0661\u06F1]/);
1474
- return (_match$ = match == null ? void 0 : match[0]) != null ? _match$ : ".";
1475
- }
1476
- };
1477
-
1478
- // TODO(LP-11481): Change the primary API we use for logging in Perseus to use
1479
- // getDependencies() internally... simplify usage to just using a "singleton"
1480
- // which get's the injected logger.
1481
- /**
1482
- * Provides logging infrastructure for Perseus
1483
- */
1484
- const Log = {
1485
- // Logs a message
1486
- log: (message, extra) => {
1487
- getDependencies().Log.log(message, extra);
1488
- },
1489
- // Logs an error with a message
1490
- error: (message, kind, extra) => {
1491
- getDependencies().Log.error(message, kind, extra);
1492
- }
1493
- };
1494
-
1495
- /**
1496
- * Checks user input from the expression widget to see if it is scorable.
1497
- *
1498
- * Note: Most of the expression widget's validation requires the Rubric because
1499
- * of its use of KhanAnswerTypes as a core part of scoring.
1500
- *
1501
- * @see `scoreExpression()` for more details.
1502
- */
1503
- function validateExpression(userInput) {
1504
- if (userInput === "") {
1505
- return {
1506
- type: "invalid",
1507
- message: null
1508
- };
1509
- }
1510
- return null;
1511
- }
1512
-
1513
- /* Content creators input a list of answers which are matched from top to
1514
- * bottom. The intent is that they can include spcific solutions which should
1515
- * be graded as correct or incorrect (or ungraded!) first, then get more
1516
- * general.
1517
- *
1518
- * We iterate through each answer, trying to match it with the user's input
1519
- * using the following angorithm:
1520
- * - Try to parse the user's input. If it doesn't parse then return "not
1521
- * graded".
1522
- * - For each answer:
1523
- * ~ Try to validate the user's input against the answer. The answer is
1524
- * expected to parse.
1525
- * ~ If the user's input validates (the validator judges it "correct"), we've
1526
- * matched and can stop considering answers.
1527
- * - If there were no matches or the matching answer is considered "ungraded",
1528
- * show the user an error. TODO(joel) - what error?
1529
- * - Otherwise, pass through the resulting points and message.
1530
- */
1531
- function scoreExpression(userInput, rubric, strings, locale) {
1532
- const validationError = validateExpression(userInput);
1533
- if (validationError) {
1534
- return validationError;
1535
- }
1536
- const options = _.clone(rubric);
1537
- _.extend(options, {
1538
- decimal_separator: getDecimalSeparator(locale)
1539
- });
1540
- const createValidator = answer => {
1541
- // We give options to KAS.parse here because it is parsing the
1542
- // solution answer, not the student answer, and we don't want a
1543
- // solution to work if the student is using a different language
1544
- // (different from the content creation language, ie. English).
1545
- const expression = KAS.parse(answer.value, rubric);
1546
- // An answer may not be parsed if the expression was defined
1547
- // incorrectly. For example if the answer is using a symbol defined
1548
- // in the function variables list for the expression.
1549
- if (!expression.parsed) {
1550
- /* c8 ignore next */
1551
- Log.error("Unable to parse solution answer for expression", Errors.InvalidInput, {
1552
- loggedMetadata: {
1553
- rubric: JSON.stringify(rubric)
1554
- }
1555
- });
1556
- return null;
1557
- }
1558
- return KhanAnswerTypes.expression.createValidatorFunctional(expression.expr, _({}).extend(options, {
1559
- simplify: answer.simplify,
1560
- form: answer.form
1561
- }));
1562
- };
1563
-
1564
- // Find the first answer form that matches the user's input and that
1565
- // is considered correct. Also, track whether the input is
1566
- // considered "empty" for all answer forms, and keep the validation
1567
- // result for the first answer form for which the user's input was
1568
- // considered "ungraded".
1569
- // (Terminology reminder: the answer forms are provided by the
1570
- // assessment items; they are not the user's input. Each one might
1571
- // represent a correct answer, an incorrect one (if the exercise
1572
- // creator has predicted certain common wrong answers and wants to
1573
- // provide guidance via a message), or an ungraded one (same idea,
1574
- // but without giving the user an incorrect mark for the question).
1575
- let matchingAnswerForm;
1576
- let matchMessage;
1577
- let allEmpty = true;
1578
- let firstUngradedResult;
1579
- for (const answerForm of rubric.answerForms || []) {
1580
- const validator = createValidator(answerForm);
1581
- if (!validator) {
1582
- continue;
1583
- }
1584
- const result = validator(userInput);
1585
-
1586
- // Short-circuit as soon as the user's input matches some answer
1587
- // (independently of whether the answer is correct)
1588
- if (result.correct) {
1589
- matchingAnswerForm = answerForm;
1590
- matchMessage = result.message || "";
1591
- break;
1592
- }
1593
- allEmpty = allEmpty && result.empty;
1594
- // If this answer form is correct and the user's input is considered
1595
- // "ungraded" for it, we'll want to keep the evaluation result for
1596
- // later. If the user's input doesn't match any answer forms, we'll
1597
- // show the message from this validation.
1598
- if (answerForm.considered === "correct" && result.ungraded && !firstUngradedResult) {
1599
- firstUngradedResult = result;
1600
- }
1601
- }
1602
-
1603
- // Now check to see if we matched any answer form at all, and if
1604
- // we did, whether it's considered correct, incorrect, or ungraded
1605
- if (!matchingAnswerForm) {
1606
- if (firstUngradedResult) {
1607
- // While we didn't directly match with any answer form, we
1608
- // did at some point get an "ungraded" validation result,
1609
- // which might indicate e.g. a mismatch in variable casing.
1610
- // We'll return "invalid", which will let the user try again
1611
- // with no penalty, and the hopefully helpful validation
1612
- // message.
1613
- return {
1614
- type: "invalid",
1615
- message: firstUngradedResult.message,
1616
- suppressAlmostThere: firstUngradedResult.suppressAlmostThere
1617
- };
1618
- }
1619
- if (allEmpty) {
1620
- // If everything graded as empty, it's invalid.
1621
- return {
1622
- type: "invalid",
1623
- message: null
1624
- };
1625
- }
1626
- // We fell through all the possibilities and we're not empty,
1627
- // so the answer is considered incorrect.
1628
- return {
1629
- type: "points",
1630
- earned: 0,
1631
- total: 1
1632
- };
1633
- }
1634
- if (matchingAnswerForm.considered === "ungraded") {
1635
- return {
1636
- type: "invalid",
1637
- message: matchMessage
1638
- };
1639
- }
1640
- // We matched a graded answer form, so we can now tell the user
1641
- // whether their input was correct or incorrect, and hand out
1642
- // points accordingly
1643
- return {
1644
- type: "points",
1645
- earned: matchingAnswerForm.considered === "correct" ? 1 : 0,
1646
- total: 1,
1647
- message: matchMessage
1648
- };
1649
- }
1650
-
1651
1507
  // Map of international operator names to their English equivalents
1652
1508
  const englishOperators = {
1653
1509
  arctg: "arctan",
@@ -2408,56 +2264,6 @@ function constrainedTickStepsFromTickSteps(tickSteps, ranges) {
2408
2264
  return [constrainTickStep(tickSteps[0], ranges[0]), constrainTickStep(tickSteps[1], ranges[1])];
2409
2265
  }
2410
2266
 
2411
- /**
2412
- * Approximate equality on numbers and primitives.
2413
- */
2414
- function eq$4(x, y) {
2415
- if (typeof x === "number" && typeof y === "number") {
2416
- return Math.abs(x - y) < 1e-9;
2417
- }
2418
- return x === y;
2419
- }
2420
-
2421
- /**
2422
- * Deep approximate equality on primitives, numbers, arrays, and objects.
2423
- * Recursive.
2424
- */
2425
- function deepEq$7(x, y) {
2426
- if (Array.isArray(x) && Array.isArray(y)) {
2427
- if (x.length !== y.length) {
2428
- return false;
2429
- }
2430
- for (let i = 0; i < x.length; i++) {
2431
- if (!deepEq$7(x[i], y[i])) {
2432
- return false;
2433
- }
2434
- }
2435
- return true;
2436
- }
2437
- if (Array.isArray(x) || Array.isArray(y)) {
2438
- return false;
2439
- }
2440
- if (typeof x === "function" && typeof y === "function") {
2441
- return eq$4(x, y);
2442
- }
2443
- if (typeof x === "function" || typeof y === "function") {
2444
- return false;
2445
- }
2446
- if (typeof x === "object" && typeof y === "object" && !!x && !!y) {
2447
- return x === y || _.all(x, function (v, k) {
2448
- // @ts-expect-error - TS2536 - Type 'CollectionKey<T>' cannot be used to index type 'T'.
2449
- return deepEq$7(y[k], v);
2450
- }) && _.all(y, function (v, k) {
2451
- // @ts-expect-error - TS2536 - Type 'CollectionKey<T>' cannot be used to index type 'T'.
2452
- return deepEq$7(x[k], v);
2453
- });
2454
- }
2455
- if (typeof x === "object" && !!x || typeof y === "object" && !!y) {
2456
- return false;
2457
- }
2458
- return eq$4(x, y);
2459
- }
2460
-
2461
2267
  /**
2462
2268
  * Query String Parser
2463
2269
  *
@@ -2674,15 +2480,6 @@ const textarea = {
2674
2480
  */
2675
2481
  const unescapeMathMode$1 = label => label.startsWith("$") && label.endsWith("$") ? label.slice(1, -1) : label;
2676
2482
  const random$1 = seededRNG$1(new Date().getTime() & 0xffffffff);
2677
-
2678
- // TODO(benchristel): in the future, we may want to make deepClone work for
2679
- // Record<string, Cloneable> as well. Currently, it only does arrays.
2680
- function deepClone(obj) {
2681
- if (Array.isArray(obj)) {
2682
- return obj.map(deepClone);
2683
- }
2684
- return obj;
2685
- }
2686
2483
  const Util = {
2687
2484
  inputPathsEqual,
2688
2485
  nestedMap: nestedMap$2,
@@ -2703,8 +2500,6 @@ const Util = {
2703
2500
  gridStepFromTickStep,
2704
2501
  tickStepFromNumTicks,
2705
2502
  constrainedTickStepsFromTickSteps,
2706
- eq: eq$4,
2707
- deepEq: deepEq$7,
2708
2503
  parseQueryString,
2709
2504
  updateQueryString: updateQueryString$2,
2710
2505
  strongEncodeURIComponent,
@@ -2722,8 +2517,24 @@ const Util = {
2722
2517
  getDataUrl: getDataUrl,
2723
2518
  textarea,
2724
2519
  unescapeMathMode: unescapeMathMode$1,
2725
- random: random$1,
2726
- deepClone
2520
+ random: random$1
2521
+ };
2522
+
2523
+ // TODO(LP-11481): Change the primary API we use for logging in Perseus to use
2524
+ // getDependencies() internally... simplify usage to just using a "singleton"
2525
+ // which get's the injected logger.
2526
+ /**
2527
+ * Provides logging infrastructure for Perseus
2528
+ */
2529
+ const Log = {
2530
+ // Logs a message
2531
+ log: (message, extra) => {
2532
+ getDependencies().Log.log(message, extra);
2533
+ },
2534
+ // Logs an error with a message
2535
+ error: (message, kind, extra) => {
2536
+ getDependencies().Log.error(message, kind, extra);
2537
+ }
2727
2538
  };
2728
2539
 
2729
2540
  // For offline exercises in the mobile app, we download the graphie data
@@ -8271,7 +8082,7 @@ var Tex$1 = {
8271
8082
  const {
8272
8083
  processMath: processMath$1
8273
8084
  } = Tex$1;
8274
- function polar$1(r, th) {
8085
+ function polar$2(r, th) {
8275
8086
  if (typeof r === "number") {
8276
8087
  r = [r, r];
8277
8088
  }
@@ -8430,7 +8241,7 @@ class Graphie$1 {
8430
8241
  // See: http://stackoverflow.com/questions/13932704/how-to-draw-sine-waves-with-svg-js
8431
8242
  const xCoords = [x0, x0 * 2 / 3 + x1 * 1 / 3, x0 * 1 / 3 + x1 * 2 / 3, x1];
8432
8243
  const yCoords = [computeSine(x0), computeSine(x0) + computeDerivative(x0) * (x1 - x0) / 3, computeSine(x1) - computeDerivative(x1) * (x1 - x0) / 3, computeSine(x1)];
8433
- const points = vector$1.zip(xCoords, yCoords);
8244
+ const points = vector$3.zip(xCoords, yCoords);
8434
8245
  return points.map(this.scalePoint);
8435
8246
  };
8436
8247
 
@@ -8763,8 +8574,8 @@ class Graphie$1 {
8763
8574
  endAngle = (endAngle % 360 + 360) % 360;
8764
8575
  const cent = this.scalePoint(center);
8765
8576
  const radii = this.scaleVector(radius);
8766
- const startVector = polar$1(radius, startAngle);
8767
- const endVector = polar$1(radius, endAngle);
8577
+ const startVector = polar$2(radius, startAngle);
8578
+ const endVector = polar$2(radius, endAngle);
8768
8579
 
8769
8580
  // We round the coordinates to make testing easier, because trig
8770
8581
  // operations return unround numbers.
@@ -8869,7 +8680,7 @@ class Graphie$1 {
8869
8680
  // Apply padding and offset to points to convert from
8870
8681
  // canvas coordinates to pixel coordinates relative to bounding box
8871
8682
  points = points.map(function (point) {
8872
- return vector$1.add(vector$1.subtract(point, topLeftOfBoundingBox), vector$1.scale(padding, 0.5));
8683
+ return vector$3.add(vector$3.subtract(point, topLeftOfBoundingBox), vector$3.scale(padding, 0.5));
8873
8684
  });
8874
8685
 
8875
8686
  // Calculate <div> dimensions
@@ -8933,8 +8744,8 @@ class Graphie$1 {
8933
8744
  const extraOffset = [Math.min(start[0], end[0]), Math.min(start[1], end[1])];
8934
8745
 
8935
8746
  // Apply padding and offset to start, end points
8936
- start = vector$1.add(vector$1.subtract(start, extraOffset), vector$1.scale(padding, 0.5));
8937
- end = vector$1.add(vector$1.subtract(end, extraOffset), vector$1.scale(padding, 0.5));
8747
+ start = vector$3.add(vector$3.subtract(start, extraOffset), vector$3.scale(padding, 0.5));
8748
+ end = vector$3.add(vector$3.subtract(end, extraOffset), vector$3.scale(padding, 0.5));
8938
8749
 
8939
8750
  // Calculate <div> dimensions
8940
8751
  const left = extraOffset[0] - padding[0] / 2;
@@ -9890,10 +9701,10 @@ const getClipPoint = function getClipPoint(graph, coord, angle) {
9890
9701
  // shoot a point off into the distance ...
9891
9702
  const distance = xExtent + yExtent;
9892
9703
  // we need to scale the point according to the scale of the axes
9893
- const angleVec = graph.unscaleVector(vector$1.cartFromPolarDeg(1, angle));
9894
- const distVec = vector$1.scale(vector$1.normalize(angleVec), distance);
9895
- const farCoord = vector$1.add(coord, distVec);
9896
- const scaledAngle = vector$1.polarDegFromCart(angleVec)[1];
9704
+ const angleVec = graph.unscaleVector(vector$3.cartFromPolarDeg(1, angle));
9705
+ const distVec = vector$3.scale(vector$3.normalize(angleVec), distance);
9706
+ const farCoord = vector$3.add(coord, distVec);
9707
+ const scaledAngle = vector$3.polarDegFromCart(angleVec)[1];
9897
9708
  // ... and then bring it back
9898
9709
  const clipPoint = graph.constrainToBoundsOnAngle(farCoord, 4, scaledAngle * Math.PI / 180);
9899
9710
  return clipPoint;
@@ -9944,7 +9755,7 @@ class WrappedDrawing {
9944
9755
  return this.visibleShape[0];
9945
9756
  }
9946
9757
  moveTo(point) {
9947
- const delta = vector$1.subtract(this.graphie.scalePoint(point), this.graphie.scalePoint(this.initialPoint));
9758
+ const delta = vector$3.subtract(this.graphie.scalePoint(point), this.graphie.scalePoint(this.initialPoint));
9948
9759
  const do3dTransform = InteractiveUtil.getCanUse3dTransform();
9949
9760
  const transformation = "translateX(" + delta[0] + "px) " + "translateY(" + delta[1] + "px)" + (do3dTransform ? " translateZ(0)" : "");
9950
9761
  this.transform(transformation);
@@ -9999,9 +9810,9 @@ class Arrowhead$1 extends WrappedPath {
9999
9810
  const center = [0.75, 0];
10000
9811
  const points = [[-3, 4], [-2.75, 2.5], [0, 0.25], center, [0, -0.25], [-2.75, -2.5], [-3, -4]].map(point => {
10001
9812
  // Scale points by `Arrowhead.scale` around `center`
10002
- const pv = vector$1.subtract(point, center);
10003
- const pvScaled = vector$1.scale(pv, Arrowhead$1.scale);
10004
- return vector$1.add(center, pvScaled);
9813
+ const pv = vector$3.subtract(point, center);
9814
+ const pvScaled = vector$3.scale(pv, Arrowhead$1.scale);
9815
+ return vector$3.add(center, pvScaled);
10005
9816
  });
10006
9817
  const unscaledPoints = points.map(graphie.unscalePoint);
10007
9818
  super(graphie, unscaledPoints, {
@@ -10072,7 +9883,7 @@ class WrappedEllipse extends WrappedDrawing {
10072
9883
  const wrapper = this.wrapper;
10073
9884
  wrapper.style.filter = filter;
10074
9885
  this.moveTo = function (point) {
10075
- const delta = vector$1.subtract(this.graphie.scalePoint(point), this.graphie.scalePoint(this.initialPoint));
9886
+ const delta = vector$3.subtract(this.graphie.scalePoint(point), this.graphie.scalePoint(this.initialPoint));
10076
9887
  const do3dTransform = InteractiveUtil.getCanUse3dTransform();
10077
9888
  const transform = "translateX(" + Math.round(delta[0]) + "px) " + "translateY(" + Math.round(delta[1]) + "px)" + (do3dTransform ? " translateZ(0)" : "");
10078
9889
  this.transform(transform);
@@ -10141,10 +9952,10 @@ class WrappedLine extends WrappedDrawing {
10141
9952
  const scaledEnd = this.graphie.scalePoint(end);
10142
9953
 
10143
9954
  // Compute transformation parameters
10144
- const polarDiff = vector$1.polarDegFromCart(vector$1.subtract(scaledEnd, scaledStart));
9955
+ const polarDiff = vector$3.polarDegFromCart(vector$3.subtract(scaledEnd, scaledStart));
10145
9956
  const lineLength = polarDiff[0];
10146
9957
  const angle = KhanMath.bound(polarDiff[1]);
10147
- const delta = vector$1.subtract(scaledStart, this.initialPoint);
9958
+ const delta = vector$3.subtract(scaledStart, this.initialPoint);
10148
9959
  const scale = KhanMath.bound(lineLength / this.initialLength);
10149
9960
 
10150
9961
  // Construct and apply transformation string
@@ -10154,260 +9965,11 @@ class WrappedLine extends WrappedDrawing {
10154
9965
  }
10155
9966
  }
10156
9967
 
10157
- /**
10158
- * A collection of geomtry-related utility functions
10159
- */
10160
- const {
10161
- eq: eq$3,
10162
- deepEq: deepEq$6
10163
- } = Util;
10164
-
10165
- // This should really be a readonly tuple of [number, number]
10166
-
10167
- // a, b, c
10168
-
10169
- // Given a number, return whether it is positive (1), negative (-1), or zero (0)
10170
- function sign(val) {
10171
- if (eq$3(val, 0)) {
10172
- return 0;
10173
- }
10174
- return val > 0 ? 1 : -1;
10175
- }
10176
-
10177
- // Determine whether three points are collinear (0), for a clockwise turn (negative),
10178
- // or counterclockwise turn (positive)
10179
- function ccw(a, b, c) {
10180
- return (b[0] - a[0]) * (c[1] - a[1]) - (c[0] - a[0]) * (b[1] - a[1]);
10181
- }
10182
- function collinear(a, b, c) {
10183
- return eq$3(ccw(a, b, c), 0);
10184
- }
10185
-
10186
- // Given rect bounding points A and B, whether point C is inside the rect
10187
- function pointInRect(a, b, c) {
10188
- return c[0] <= Math.max(a[0], b[0]) && c[0] >= Math.min(a[0], b[0]) && c[1] <= Math.max(a[1], b[1]) && c[1] >= Math.min(a[1], b[1]);
10189
- }
10190
-
10191
- // Whether line segment AB intersects line segment CD
10192
- // http://www.geeksforgeeks.org/check-if-two-given-line-segments-intersect/
10193
- function intersects(ab, cd) {
10194
- const triplets = [[ab[0], ab[1], cd[0]], [ab[0], ab[1], cd[1]], [cd[0], cd[1], ab[0]], [cd[0], cd[1], ab[1]]];
10195
- const orientations = _.map(triplets, function (triplet) {
10196
- return sign(ccw(...triplet));
10197
- });
10198
- if (orientations[0] !== orientations[1] && orientations[2] !== orientations[3]) {
10199
- return true;
10200
- }
10201
- for (let i = 0; i < 4; i++) {
10202
- if (orientations[i] === 0 && pointInRect(...triplets[i])) {
10203
- return true;
10204
- }
10205
- }
10206
- return false;
10207
- }
10208
-
10209
- // Whether any two sides of a polygon intersect each other
10210
- function polygonSidesIntersect(vertices) {
10211
- for (let i = 0; i < vertices.length; i++) {
10212
- for (let k = i + 1; k < vertices.length; k++) {
10213
- // If any two vertices are the same point, sides overlap
10214
- if (point.equal(vertices[i], vertices[k])) {
10215
- return true;
10216
- }
10217
-
10218
- // Find the other end of the sides starting at vertices i and k
10219
- const iNext = (i + 1) % vertices.length;
10220
- const kNext = (k + 1) % vertices.length;
10221
-
10222
- // Adjacent sides always intersect (at the vertex); skip those
10223
- if (iNext === k || kNext === i) {
10224
- continue;
10225
- }
10226
- const side1 = [vertices[i], vertices[iNext]];
10227
- const side2 = [vertices[k], vertices[kNext]];
10228
- if (intersects(side1, side2)) {
10229
- return true;
10230
- }
10231
- }
10232
- }
10233
- return false;
10234
- }
10235
- function vector(a, b) {
10236
- return _.map(_.zip(a, b), function (pair) {
10237
- return pair[0] - pair[1];
10238
- });
10239
- }
10240
- function reverseVector(vector) {
10241
- return [-vector[0], -vector[1]];
10242
- }
10243
-
10244
- // Returns whether connecting the given sequence of `points` forms a clockwise
10245
- // path (assuming a closed loop, where the last point connects back to the
10246
- // first).
10247
- function clockwise$1(points) {
10248
- const segments = _.zip(points, points.slice(1).concat(points.slice(0, 1)));
10249
- const areas = _.map(segments, function (segment) {
10250
- const p1 = segment[0];
10251
- const p2 = segment[1];
10252
- return (p2[0] - p1[0]) * (p2[1] + p1[1]);
10253
- });
10254
- return sum$1(areas) > 0;
10255
- }
10256
- function magnitude(v) {
10257
- return Math.sqrt(_.reduce(v, function (memo, el) {
10258
- // @ts-expect-error - TS2345 - Argument of type 'Coord' is not assignable to parameter of type 'number'.
10259
- return memo + Math.pow(el, 2);
10260
- }, 0));
10261
- }
10262
- function dotProduct(a, b) {
10263
- return _.reduce(_.zip(a, b), function (memo, pair) {
10264
- return memo + pair[0] * pair[1];
10265
- }, 0);
10266
- }
10267
- function sideLengths(coords) {
10268
- const segments = _.zip(coords, rotate(coords));
10269
- return segments.map(function (segment) {
10270
- // @ts-expect-error - TS2345 - Argument of type 'number[]' is not assignable to parameter of type 'readonly Coord[]'. | TS2556 - A spread argument must either have a tuple type or be passed to a rest parameter.
10271
- return magnitude(vector(...segment));
10272
- });
10273
- }
10274
-
10275
- // Based on http://math.stackexchange.com/a/151149
10276
- function angleMeasures(coords) {
10277
- const triplets = _.zip(rotate(coords, -1), coords, rotate(coords, 1));
10278
- const offsets = _.map(triplets, function (triplet) {
10279
- const p = vector(triplet[1], triplet[0]);
10280
- const q = vector(triplet[2], triplet[1]);
10281
- // @ts-expect-error - TS2345 - Argument of type 'number[]' is not assignable to parameter of type 'Coord'. | TS2345 - Argument of type 'number[]' is not assignable to parameter of type 'readonly Coord[]'. | TS2345 - Argument of type 'number[]' is not assignable to parameter of type 'readonly Coord[]'.
10282
- const raw = Math.acos(dotProduct(p, q) / (magnitude(p) * magnitude(q)));
10283
- // @ts-expect-error - TS2556 - A spread argument must either have a tuple type or be passed to a rest parameter.
10284
- return sign(ccw(...triplet)) > 0 ? raw : -raw;
10285
- });
10286
- const sum = _.reduce(offsets, function (memo, arg) {
10287
- return memo + arg;
10288
- }, 0);
10289
- return _.map(offsets, function (offset) {
10290
- return sum > 0 ? Math.PI - offset : Math.PI + offset;
10291
- });
10292
- }
10293
-
10294
- // Whether two polygons are similar (or if specified, congruent)
10295
- function similar(coords1, coords2, tolerance) {
10296
- if (coords1.length !== coords2.length) {
10297
- return false;
10298
- }
10299
- const n = coords1.length;
10300
- const angles1 = angleMeasures(coords1);
10301
- const angles2 = angleMeasures(coords2);
10302
- const sides1 = sideLengths(coords1);
10303
- const sides2 = sideLengths(coords2);
10304
- for (let i = 0; i < 2 * n; i++) {
10305
- let angles = angles2.slice();
10306
- let sides = sides2.slice();
10307
-
10308
- // Reverse angles and sides to allow matching reflected polygons
10309
- if (i >= n) {
10310
- angles.reverse();
10311
- sides.reverse();
10312
- // Since sides are calculated from two coordinates,
10313
- // simply reversing results in an off by one error
10314
- // @ts-expect-error - TS4104 - The type 'readonly number[]' is 'readonly' and cannot be assigned to the mutable type 'number[]'.
10315
- sides = rotate(sides, 1);
10316
- }
10317
-
10318
- // @ts-expect-error - TS4104 - The type 'readonly number[]' is 'readonly' and cannot be assigned to the mutable type 'number[]'.
10319
- angles = rotate(angles, i);
10320
- // @ts-expect-error - TS4104 - The type 'readonly number[]' is 'readonly' and cannot be assigned to the mutable type 'number[]'.
10321
- sides = rotate(sides, i);
10322
- if (deepEq$6(angles1, angles)) {
10323
- const sidePairs = _.zip(sides1, sides);
10324
- const factors = _.map(sidePairs, function (pair) {
10325
- return pair[0] / pair[1];
10326
- });
10327
- const same = _.all(factors, function (factor) {
10328
- return eq$3(factors[0], factor);
10329
- });
10330
- const congruentEnough = _.all(sidePairs, function (pair) {
10331
- return number$1.equal(pair[0], pair[1], tolerance);
10332
- });
10333
- if (same && congruentEnough) {
10334
- return true;
10335
- }
10336
- }
10337
- }
10338
- return false;
10339
- }
10340
-
10341
- // Given triangle with sides ABC return angle opposite side C in degrees
10342
- function lawOfCosines(a, b, c) {
10343
- return Math.acos((a * a + b * b - c * c) / (2 * a * b)) * 180 / Math.PI;
10344
- }
10345
- function canonicalSineCoefficients$1([amplitude, angularFrequency, phase, verticalOffset]) {
10346
- // For a curve of the form f(x) = a * Sin(b * x - c) + d,
10347
- // this function ensures that a, b > 0, and c is its
10348
- // smallest possible positive value.
10349
-
10350
- // Guarantee a > 0
10351
- if (amplitude < 0) {
10352
- amplitude *= -1;
10353
- angularFrequency *= -1;
10354
- phase *= -1;
10355
- }
10356
- const period = 2 * Math.PI;
10357
- // Guarantee b > 0
10358
- if (angularFrequency < 0) {
10359
- angularFrequency *= -1;
10360
- phase *= -1;
10361
- phase += period / 2;
10362
- }
10363
-
10364
- // Guarantee c is smallest possible positive value
10365
- while (phase > 0) {
10366
- phase -= period;
10367
- }
10368
- while (phase < 0) {
10369
- phase += period;
10370
- }
10371
- return [amplitude, angularFrequency, phase, verticalOffset];
10372
- }
10373
-
10374
- // e.g. rotate([1, 2, 3]) -> [2, 3, 1]
10375
- function rotate(array, n) {
10376
- n = typeof n === "undefined" ? 1 : n % array.length;
10377
- return array.slice(n).concat(array.slice(0, n));
10378
- }
10379
- function getLineEquation(first, second) {
10380
- if (eq$3(first[0], second[0])) {
10381
- return "x = " + first[0].toFixed(3);
10382
- }
10383
- const m = (second[1] - first[1]) / (second[0] - first[0]);
10384
- const b = first[1] - m * first[0];
10385
- return "y = " + m.toFixed(3) + "x + " + b.toFixed(3);
10386
- }
10387
-
10388
- // Stolen from the wikipedia article
10389
- // http://en.wikipedia.org/wiki/Line-line_intersection
10390
- function getLineIntersection(
10391
- // TODO(LP-10725): update these to be 2-tuples
10392
- firstPoints, secondPoints) {
10393
- const x1 = firstPoints[0][0];
10394
- const y1 = firstPoints[0][1];
10395
- const x2 = firstPoints[1][0];
10396
- const y2 = firstPoints[1][1];
10397
- const x3 = secondPoints[0][0];
10398
- const y3 = secondPoints[0][1];
10399
- const x4 = secondPoints[1][0];
10400
- const y4 = secondPoints[1][1];
10401
- const determinant = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
10402
- if (Math.abs(determinant) < 1e-9) {
10403
- return "Lines are parallel";
10404
- }
10405
- const x = ((x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4)) / determinant;
10406
- const y = ((x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4)) / determinant;
10407
- return "Intersection: (" + x.toFixed(3) + ", " + y.toFixed(3) + ")";
10408
- }
10409
-
10410
9968
  /* eslint-disable @babel/no-invalid-this */
9969
+ const {
9970
+ clockwise: clockwise$3,
9971
+ reverseVector: reverseVector$1
9972
+ } = geometry;
10411
9973
  function scaledDistanceFromAngle(angle) {
10412
9974
  const a = 3.51470560176242 * 20;
10413
9975
  const b = 0.5687298702748785 * 20;
@@ -10476,10 +10038,10 @@ _.extend(GraphUtils.Graphie.prototype, {
10476
10038
  let sRadius = sPadding + scaledDistanceFromAngle(angle);
10477
10039
  const temp = [];
10478
10040
  if (Math.abs(angle - 90) < 1e-9 && options.showRightAngleMarker) {
10479
- const v1 = vector$1.add(sVertex, scaledPolarDeg(sRadius, startAngle));
10480
- const v2 = vector$1.add(sVertex, scaledPolarDeg(sRadius, endAngle));
10041
+ const v1 = vector$3.add(sVertex, scaledPolarDeg(sRadius, startAngle));
10042
+ const v2 = vector$3.add(sVertex, scaledPolarDeg(sRadius, endAngle));
10481
10043
  sRadius *= Math.SQRT2;
10482
- const v3 = vector$1.add(sVertex, scaledPolarDeg(sRadius, halfAngle));
10044
+ const v3 = vector$3.add(sVertex, scaledPolarDeg(sRadius, halfAngle));
10483
10045
  _.each([v1, v2], function (v) {
10484
10046
  // @ts-expect-error - TS2345 - Argument of type 'any' is not assignable to parameter of type 'never'.
10485
10047
  temp.push(graphie.scaledPath([v, v3], options.style));
@@ -10500,7 +10062,7 @@ _.extend(GraphUtils.Graphie.prototype, {
10500
10062
  text = text.replace(match[0], KhanMath.toFixedApprox(angle, precision) + "^{\\circ}");
10501
10063
  }
10502
10064
  const sOffset = scaledPolarDeg(sRadius + 15, halfAngle);
10503
- const sPosition = vector$1.add(sVertex, sOffset);
10065
+ const sPosition = vector$3.add(sVertex, sOffset);
10504
10066
  const position = graphie.unscalePoint(sPosition);
10505
10067
 
10506
10068
  // Reuse label if possible
@@ -10551,7 +10113,7 @@ _.extend(GraphUtils.Graphie.prototype, {
10551
10113
  const sOffset = sSpacing * (i - (n - 1) / 2);
10552
10114
  const sOffsetVector = scaledPolarRad(sOffset, parallelAngle);
10553
10115
  const sHeightVector = scaledPolarRad(sHeight, perpendicularAngle);
10554
- const sPath = [vector$1.add(sMidpoint, sOffsetVector, sHeightVector), vector$1.add(sMidpoint, sOffsetVector, reverseVector(sHeightVector))];
10116
+ const sPath = [vector$3.add(sMidpoint, sOffsetVector, sHeightVector), vector$3.add(sMidpoint, sOffsetVector, reverseVector$1(sHeightVector))];
10555
10117
 
10556
10118
  // @ts-expect-error - TS2345 - Argument of type 'any' is not assignable to parameter of type 'never'.
10557
10119
  temp.push(graphie.scaledPath(sPath, style));
@@ -10576,9 +10138,9 @@ _.extend(GraphUtils.Graphie.prototype, {
10576
10138
  const sOffset = sCumulativeOffset + sSpacing * i;
10577
10139
  let sOffsetVector = scaledPolarRad(sOffset, parallelAngle);
10578
10140
  if (start !== p1) {
10579
- sOffsetVector = reverseVector(sOffsetVector);
10141
+ sOffsetVector = reverseVector$1(sOffsetVector);
10580
10142
  }
10581
- const sEnd = vector$1.add(sMidpoint, sOffsetVector);
10143
+ const sEnd = vector$3.add(sMidpoint, sOffsetVector);
10582
10144
 
10583
10145
  // @ts-expect-error - TS2345 - Argument of type 'any' is not assignable to parameter of type 'never'.
10584
10146
  temp.push(graphie.scaledPath([sStart, sEnd], style));
@@ -10594,7 +10156,7 @@ _.extend(GraphUtils.Graphie.prototype, {
10594
10156
  }
10595
10157
  const sOffset = 20;
10596
10158
  const sOffsetVector = scaledPolarRad(sOffset, perpendicularAngle);
10597
- const sPosition = vector$1.add(sMidpoint, sOffsetVector);
10159
+ const sPosition = vector$3.add(sMidpoint, sOffsetVector);
10598
10160
  const position = graphie.unscalePoint(sPosition);
10599
10161
 
10600
10162
  // Reuse label if possible
@@ -10652,7 +10214,7 @@ _.extend(GraphUtils.Graphie.prototype, {
10652
10214
  }
10653
10215
  const sRadius = 10 + scaledDistanceFromAngle(360 - angle);
10654
10216
  const sOffsetVector = scaledPolarDeg(sRadius, halfAngle);
10655
- const sPosition = vector$1.add(sVertex, sOffsetVector);
10217
+ const sPosition = vector$3.add(sVertex, sOffsetVector);
10656
10218
  const position = graphie.unscalePoint(sPosition);
10657
10219
 
10658
10220
  // Reuse label if possible
@@ -10928,7 +10490,7 @@ _.extend(GraphUtils.Graphie.prototype, {
10928
10490
 
10929
10491
  // Adjust the target coordinate by accounting for the gesture's
10930
10492
  // offset.
10931
- let coord = vector$1.add(graph.getMouseCoord(event), offset);
10493
+ let coord = vector$3.add(graph.getMouseCoord(event), offset);
10932
10494
  coord = applySnapAndConstraints(coord);
10933
10495
  let coordX = coord[0];
10934
10496
  let coordY = coord[1];
@@ -11039,7 +10601,7 @@ _.extend(GraphUtils.Graphie.prototype, {
11039
10601
  const startCoord = movablePoint.coord;
11040
10602
  const startMouseCoord = graph.getMouseCoord(event);
11041
10603
  const isMouse = !("ontouchstart" in window);
11042
- const touchOffset = isMouse ? [0, 0] : vector$1.subtract(startCoord, startMouseCoord);
10604
+ const touchOffset = isMouse ? [0, 0] : vector$3.subtract(startCoord, startMouseCoord);
11043
10605
  movablePoint.grab(touchOffset);
11044
10606
  }
11045
10607
  });
@@ -11259,17 +10821,17 @@ _.extend(GraphUtils.Graphie.prototype, {
11259
10821
  const getScaledAngle = function getScaledAngle(line) {
11260
10822
  const scaledA = line.graph.scalePoint(line.coordA);
11261
10823
  const scaledZ = line.graph.scalePoint(line.coordZ);
11262
- return vector$1.polarDegFromCart(vector$1.subtract(scaledZ, scaledA))[1];
10824
+ return vector$3.polarDegFromCart(vector$3.subtract(scaledZ, scaledA))[1];
11263
10825
  };
11264
10826
  const getClipPoint = function getClipPoint(graph, coord, angle) {
11265
10827
  graph = lineSegment.graph;
11266
10828
  const xExtent = graph.range[0][1] - graph.range[0][0];
11267
10829
  const yExtent = graph.range[1][1] - graph.range[1][0];
11268
10830
  const distance = xExtent + yExtent;
11269
- const angleVec = graph.unscaleVector(vector$1.cartFromPolarDeg(1, angle));
11270
- const distVec = vector$1.scale(vector$1.normalize(angleVec), distance);
11271
- const farCoord = vector$1.add(coord, distVec);
11272
- const scaledAngle = vector$1.polarDegFromCart(angleVec)[1];
10831
+ const angleVec = graph.unscaleVector(vector$3.cartFromPolarDeg(1, angle));
10832
+ const distVec = vector$3.scale(vector$3.normalize(angleVec), distance);
10833
+ const farCoord = vector$3.add(coord, distVec);
10834
+ const scaledAngle = vector$3.polarDegFromCart(angleVec)[1];
11273
10835
  const clipPoint = graph.constrainToBoundsOnAngle(farCoord, 4, scaledAngle * Math.PI / 180);
11274
10836
  return clipPoint;
11275
10837
  };
@@ -11643,7 +11205,7 @@ _.extend(GraphUtils.Graphie.prototype, {
11643
11205
  // Temporary objects
11644
11206
  _.invoke(polygon.temp, "remove");
11645
11207
  polygon.temp = [];
11646
- const isClockwise = clockwise$1(polygon.coords);
11208
+ const isClockwise = clockwise$3(polygon.coords);
11647
11209
 
11648
11210
  // Update angle labels
11649
11211
  if (polygon.angleLabels.length || polygon.showRightAngleMarkers.length) {
@@ -12171,8 +11733,8 @@ _.extend(GraphUtils.Graphie.prototype, {
12171
11733
  const drawRotateHandle = function drawRotateHandle(graphie, center, radius, halfWidth, lengthAngle, angle, interacting) {
12172
11734
  const getRotateHandlePoint = function getRotateHandlePoint(offset, distanceFromArrowMidline) {
12173
11735
  const distFromRotationCenter = radius + distanceFromArrowMidline;
12174
- const vec = vector$1.cartFromPolarDeg(distFromRotationCenter, angle + offset);
12175
- const absolute = vector$1.add(center, vec);
11736
+ const vec = vector$3.cartFromPolarDeg(distFromRotationCenter, angle + offset);
11737
+ const absolute = vector$3.add(center, vec);
12176
11738
  const pixels = graphie.scalePoint(absolute);
12177
11739
  return pixels[0] + "," + pixels[1];
12178
11740
  };
@@ -12212,7 +11774,7 @@ _.extend(GraphUtils.Graphie.prototype, {
12212
11774
  };
12213
11775
  }
12214
11776
  const rotateHandle = graph.addMovablePoint({
12215
- coord: point.addVector(rotatePoint.coord, vector$1.cartFromPolarDeg(radius, options.angleDeg || 0)),
11777
+ coord: point.addVector(rotatePoint.coord, vector$3.cartFromPolarDeg(radius, options.angleDeg || 0)),
12216
11778
  constraints: {
12217
11779
  fixedDistance: {
12218
11780
  dist: radius,
@@ -12237,8 +11799,8 @@ _.extend(GraphUtils.Graphie.prototype, {
12237
11799
  return; // Don't draw anything!
12238
11800
  }
12239
11801
 
12240
- const handleVec = vector$1.subtract(handleCoord, rotatePoint.coord);
12241
- const handlePolar = vector$1.polarDegFromCart(handleVec);
11802
+ const handleVec = vector$3.subtract(handleCoord, rotatePoint.coord);
11803
+ const handlePolar = vector$3.polarDegFromCart(handleVec);
12242
11804
  const angle = handlePolar[1];
12243
11805
  if (drawnRotateHandle) {
12244
11806
  drawnRotateHandle.remove();
@@ -12250,8 +11812,8 @@ _.extend(GraphUtils.Graphie.prototype, {
12250
11812
  // the rotationHandle as well, or it will end up out
12251
11813
  // of sync
12252
11814
  $(rotatePoint).on("move." + id, function () {
12253
- const delta = vector$1.subtract(rotatePoint.coord, rotatePointPrevCoord);
12254
- rotateHandle.setCoord(vector$1.add(rotateHandle.coord, delta));
11815
+ const delta = vector$3.subtract(rotatePoint.coord, rotatePointPrevCoord);
11816
+ rotateHandle.setCoord(vector$3.add(rotateHandle.coord, delta));
12255
11817
  redrawRotateHandle(rotateHandle.coord);
12256
11818
  rotatePointPrevCoord = rotatePoint.coord;
12257
11819
  rotateHandle.constraints.fixedDistance.point = rotatePoint;
@@ -12266,8 +11828,8 @@ _.extend(GraphUtils.Graphie.prototype, {
12266
11828
  }
12267
11829
  let coord = [x, y];
12268
11830
  if (options.onMove) {
12269
- const oldPolar = vector$1.polarDegFromCart(vector$1.subtract(rotateHandlePrevCoord, rotatePoint.coord));
12270
- const newPolar = vector$1.polarDegFromCart(vector$1.subtract(coord, rotatePoint.coord));
11831
+ const oldPolar = vector$3.polarDegFromCart(vector$3.subtract(rotateHandlePrevCoord, rotatePoint.coord));
11832
+ const newPolar = vector$3.polarDegFromCart(vector$3.subtract(coord, rotatePoint.coord));
12271
11833
  const oldAngle = oldPolar[1];
12272
11834
  const newAngle = newPolar[1];
12273
11835
  let result = options.onMove(newAngle, oldAngle);
@@ -12275,7 +11837,7 @@ _.extend(GraphUtils.Graphie.prototype, {
12275
11837
  if (result === false) {
12276
11838
  result = oldAngle;
12277
11839
  }
12278
- coord = vector$1.add(rotatePoint.coord, vector$1.cartFromPolarDeg(oldPolar[0], result));
11840
+ coord = vector$3.add(rotatePoint.coord, vector$3.cartFromPolarDeg(oldPolar[0], result));
12279
11841
  }
12280
11842
  }
12281
11843
  redrawRotateHandle(coord);
@@ -12286,8 +11848,8 @@ _.extend(GraphUtils.Graphie.prototype, {
12286
11848
  isRotating = false;
12287
11849
  redrawRotateHandle(rotateHandle.coord);
12288
11850
  if (options.onMoveEnd) {
12289
- const oldPolar = vector$1.polarDegFromCart(vector$1.subtract(rotateHandleStartCoord, rotatePoint.coord));
12290
- const newPolar = vector$1.polarDegFromCart(vector$1.subtract(rotateHandle.coord, rotatePoint.coord));
11851
+ const oldPolar = vector$3.polarDegFromCart(vector$3.subtract(rotateHandleStartCoord, rotatePoint.coord));
11852
+ const newPolar = vector$3.polarDegFromCart(vector$3.subtract(rotateHandle.coord, rotatePoint.coord));
12291
11853
  options.onMoveEnd(newPolar[1], oldPolar[1]);
12292
11854
  }
12293
11855
  };
@@ -12332,14 +11894,14 @@ _.extend(GraphUtils.Graphie.prototype, {
12332
11894
  if (point.equal(lineCoords[0], lineCoords[1])) {
12333
11895
  lineCoords = [lineCoords[0], point.addVector(lineCoords[0], [1, 1])];
12334
11896
  }
12335
- const lineDirection = vector$1.normalize(vector$1.subtract(lineCoords[1], lineCoords[0]));
12336
- const lineVec = vector$1.scale(lineDirection, size / 2);
12337
- const centerVec = vector$1.scale(lineDirection, distanceFromCenter);
12338
- const leftCenterVec = vector$1.rotateDeg(centerVec, 90);
12339
- const rightCenterVec = vector$1.rotateDeg(centerVec, -90);
12340
- const negLineVec = vector$1.negate(lineVec);
12341
- const leftVec = vector$1.rotateDeg(lineVec, 90);
12342
- const rightVec = vector$1.rotateDeg(lineVec, -90);
11897
+ const lineDirection = vector$3.normalize(vector$3.subtract(lineCoords[1], lineCoords[0]));
11898
+ const lineVec = vector$3.scale(lineDirection, size / 2);
11899
+ const centerVec = vector$3.scale(lineDirection, distanceFromCenter);
11900
+ const leftCenterVec = vector$3.rotateDeg(centerVec, 90);
11901
+ const rightCenterVec = vector$3.rotateDeg(centerVec, -90);
11902
+ const negLineVec = vector$3.negate(lineVec);
11903
+ const leftVec = vector$3.rotateDeg(lineVec, 90);
11904
+ const rightVec = vector$3.rotateDeg(lineVec, -90);
12343
11905
  const leftCenter = point.addVectors(buttonCoord, leftCenterVec);
12344
11906
  const rightCenter = point.addVectors(buttonCoord, rightCenterVec);
12345
11907
  const leftCoord1 = point.addVectors(buttonCoord, leftCenterVec, lineVec, leftVec);
@@ -12466,7 +12028,7 @@ _.extend(GraphUtils.Graphie.prototype, {
12466
12028
  // @ts-expect-error - TS2350 - Only a void function can be called with the 'new' keyword.
12467
12029
  return new Ruler(this, options || {});
12468
12030
  },
12469
- addPoints: vector$1.add
12031
+ addPoints: vector$3.add
12470
12032
  });
12471
12033
  function Protractor$1(graph, center) {
12472
12034
  // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
@@ -12740,10 +12302,10 @@ function Ruler(graphie, options) {
12740
12302
  });
12741
12303
  const width = options.units * options.pixelsPerUnit;
12742
12304
  const height = 50;
12743
- const leftBottom = graphie.unscalePoint(vector$1.subtract(graphie.scalePoint(options.center), vector$1.scale([width, -height], 0.5)));
12305
+ const leftBottom = graphie.unscalePoint(vector$3.subtract(graphie.scalePoint(options.center), vector$3.scale([width, -height], 0.5)));
12744
12306
  const graphieUnitsPerUnit = options.pixelsPerUnit / graphie.scale[0];
12745
12307
  const graphieUnitsHeight = height / graphie.scale[0];
12746
- const rightTop = vector$1.add(leftBottom, [options.units * graphieUnitsPerUnit, graphieUnitsHeight]);
12308
+ const rightTop = vector$3.add(leftBottom, [options.units * graphieUnitsPerUnit, graphieUnitsHeight]);
12747
12309
  const tickHeight = 1.0;
12748
12310
  let tickHeightMap;
12749
12311
  if (options.ticksPerUnit === 10) {
@@ -12890,11 +12452,11 @@ function Ruler(graphie, options) {
12890
12452
  const dy = rightBottomHandle.coord[1] - y;
12891
12453
  const dx = rightBottomHandle.coord[0] - x;
12892
12454
  const angle = Math.atan2(dy, dx) * 180 / Math.PI;
12893
- const center = vector$1.scale(vector$1.add([x, y], rightBottomHandle.coord), 0.5);
12455
+ const center = vector$3.scale(vector$3.add([x, y], rightBottomHandle.coord), 0.5);
12894
12456
  const scaledCenter = graphie.scalePoint(center);
12895
- const oldCenter = vector$1.scale(vector$1.add(leftBottomHandle.coord, rightBottomHandle.coord), 0.5);
12457
+ const oldCenter = vector$3.scale(vector$3.add(leftBottomHandle.coord, rightBottomHandle.coord), 0.5);
12896
12458
  const scaledOldCenter = graphie.scalePoint(oldCenter);
12897
- const diff = vector$1.subtract(scaledCenter, scaledOldCenter);
12459
+ const diff = vector$3.subtract(scaledCenter, scaledOldCenter);
12898
12460
  set.rotate(-angle, scaledOldCenter[0], scaledOldCenter[1]);
12899
12461
  set.translate(diff[0], diff[1]);
12900
12462
  }
@@ -12917,11 +12479,11 @@ function Ruler(graphie, options) {
12917
12479
  const dy = y - leftBottomHandle.coord[1];
12918
12480
  const dx = x - leftBottomHandle.coord[0];
12919
12481
  const angle = Math.atan2(dy, dx) * 180 / Math.PI;
12920
- const center = vector$1.scale(vector$1.add([x, y], leftBottomHandle.coord), 0.5);
12482
+ const center = vector$3.scale(vector$3.add([x, y], leftBottomHandle.coord), 0.5);
12921
12483
  const scaledCenter = graphie.scalePoint(center);
12922
- const oldCenter = vector$1.scale(vector$1.add(leftBottomHandle.coord, rightBottomHandle.coord), 0.5);
12484
+ const oldCenter = vector$3.scale(vector$3.add(leftBottomHandle.coord, rightBottomHandle.coord), 0.5);
12923
12485
  const scaledOldCenter = graphie.scalePoint(oldCenter);
12924
- const diff = vector$1.subtract(scaledCenter, scaledOldCenter);
12486
+ const diff = vector$3.subtract(scaledCenter, scaledOldCenter);
12925
12487
  set.rotate(-angle, scaledOldCenter[0], scaledOldCenter[1]);
12926
12488
  set.translate(diff[0], diff[1]);
12927
12489
  }
@@ -13064,12 +12626,12 @@ _.extend(MovableAngle.prototype, {
13064
12626
  points[1].onMove = function (x, y) {
13065
12627
  const oldVertex = points[1].coord;
13066
12628
  const newVertex = [x, y];
13067
- const delta = vector$1.add(newVertex, reverseVector(oldVertex));
12629
+ const delta = vector$3.add(newVertex, reverseVector$1(oldVertex));
13068
12630
  let valid = true;
13069
12631
  const newPoints = {};
13070
12632
  _.each([0, 2], function (i) {
13071
12633
  const oldPoint = points[i].coord;
13072
- let newPoint = vector$1.add(oldPoint, delta);
12634
+ let newPoint = vector$3.add(oldPoint, delta);
13073
12635
  let angle = GraphUtils.findAngleDeprecated(newVertex, newPoint);
13074
12636
  angle *= Math.PI / 180;
13075
12637
  newPoint = graphie.constrainToBoundsOnAngle(newPoint, 10, angle);
@@ -13106,7 +12668,7 @@ _.extend(MovableAngle.prototype, {
13106
12668
  let angle = GraphUtils.findAngleDeprecated(newPoint, vertex);
13107
12669
  angle = Math.round((angle - snapOffset) / snap) * snap + snapOffset;
13108
12670
  const distance = GraphUtils.getDistance(newPoint, vertex);
13109
- return vector$1.add(vertex, polar$1(distance, angle));
12671
+ return vector$3.add(vertex, polar$2(distance, angle));
13110
12672
  }
13111
12673
  return true;
13112
12674
  };
@@ -13257,7 +12819,6 @@ _.extend(MovableAngle.prototype, {
13257
12819
 
13258
12820
  /* eslint-disable @babel/no-invalid-this */
13259
12821
  const nestedMap$1 = Util.nestedMap;
13260
- const deepEq$5 = Util.deepEq;
13261
12822
 
13262
12823
  /**
13263
12824
  * A base class for all Graphie Movables
@@ -13354,7 +12915,7 @@ const createSimpleClass = function createSimpleClass(addFunction) {
13354
12915
  this._prevProps = this.props;
13355
12916
  },
13356
12917
  modify: function (graphie) {
13357
- if (!deepEq$5(this.props, this._prevProps)) {
12918
+ if (!approximateDeepEqual(this.props, this._prevProps)) {
13358
12919
  this.remove();
13359
12920
  this.add(graphie);
13360
12921
  this._prevProps = this.props;
@@ -13715,7 +13276,7 @@ function getEventName$1(key) {
13715
13276
  const getScaledAngle = function getScaledAngle(line) {
13716
13277
  const scaledA = line.graphie.scalePoint(line.coord(0));
13717
13278
  const scaledZ = line.graphie.scalePoint(line.coord(1));
13718
- return vector$1.polarDegFromCart(vector$1.subtract(scaledZ, scaledA))[1];
13279
+ return vector$3.polarDegFromCart(vector$3.subtract(scaledZ, scaledA))[1];
13719
13280
  };
13720
13281
 
13721
13282
  /**
@@ -13874,11 +13435,11 @@ const constraints$2 = {
13874
13435
  if (snap === null) {
13875
13436
  return true;
13876
13437
  }
13877
- let delta = vector$1.subtract(coord, prevCoord);
13438
+ let delta = vector$3.subtract(coord, prevCoord);
13878
13439
  // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
13879
13440
  snap = snap || this.graphie.snap;
13880
13441
  delta = point.roundTo(delta, snap);
13881
- return vector$1.add(prevCoord, delta);
13442
+ return vector$3.add(prevCoord, delta);
13882
13443
  };
13883
13444
  },
13884
13445
  bound: function (range, snap, paddingPx) {
@@ -13892,7 +13453,7 @@ const constraints$2 = {
13892
13453
  return function (coord, prevCoord) {
13893
13454
  // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
13894
13455
  const graphie = this.graphie;
13895
- const delta = vector$1.subtract(coord, prevCoord);
13456
+ const delta = vector$3.subtract(coord, prevCoord);
13896
13457
  range = range || graphie.range;
13897
13458
  // A null snap means no snap; an undefined snap means
13898
13459
  // default to graphie's
@@ -13912,8 +13473,8 @@ const constraints$2 = {
13912
13473
  const deltaBounds = _.map(
13913
13474
  // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
13914
13475
  this.coords(), function (coord, i) {
13915
- const max = vector$1.subtract(absoluteUpper, coord);
13916
- const min = vector$1.subtract(absoluteLower, coord);
13476
+ const max = vector$3.subtract(absoluteUpper, coord);
13477
+ const min = vector$3.subtract(absoluteLower, coord);
13917
13478
  return [min, max];
13918
13479
  });
13919
13480
 
@@ -13927,7 +13488,7 @@ const constraints$2 = {
13927
13488
  const deltaY = Math.max(lower[1], Math.min(upper[1], delta[1]));
13928
13489
  return [deltaX, deltaY];
13929
13490
  }, delta);
13930
- return vector$1.add(prevCoord, boundedDelta);
13491
+ return vector$3.add(prevCoord, boundedDelta);
13931
13492
  };
13932
13493
  }
13933
13494
  };
@@ -13936,10 +13497,10 @@ const constraints$2 = {
13936
13497
  constraints$2.standard = null;
13937
13498
  const onMove$1 = {
13938
13499
  updatePoints: function (coord, prevCoord) {
13939
- const actualDelta = vector$1.subtract(coord, prevCoord);
13500
+ const actualDelta = vector$3.subtract(coord, prevCoord);
13940
13501
  // @ts-expect-error - TS2339 - Property 'state' does not exist on type '{ readonly updatePoints: (coord: Coord, prevCoord: Coord) => void; }'.
13941
13502
  _.each(this.state.points, function (point) {
13942
- point.setCoord(vector$1.add(point.coord(), actualDelta));
13503
+ point.setCoord(vector$3.add(point.coord(), actualDelta));
13943
13504
  });
13944
13505
  }
13945
13506
  };
@@ -14101,9 +13662,9 @@ _.extend(MovableLine$4.prototype, {
14101
13662
  self._fireEvent(self.state.onMoveStart, self.coord(0), self.coord(0));
14102
13663
  },
14103
13664
  onMove: function (mouseCoord, prevMouseCoord) {
14104
- const delta = vector$1.subtract(mouseCoord, prevMouseCoord);
14105
- self._totalDelta = vector$1.add(self._totalDelta, delta);
14106
- let refCoord = vector$1.add(self._initialRefCoord, self._totalDelta);
13665
+ const delta = vector$3.subtract(mouseCoord, prevMouseCoord);
13666
+ self._totalDelta = vector$3.add(self._totalDelta, delta);
13667
+ let refCoord = vector$3.add(self._initialRefCoord, self._totalDelta);
14107
13668
  refCoord = self._applyConstraints(refCoord, self._prevRefCoord);
14108
13669
  if (refCoord === false) {
14109
13670
  return;
@@ -14615,7 +14176,7 @@ class MovablePoint$5 {
14615
14176
  // touch-based interaction.
14616
14177
  if (state.touchOffset == null) {
14617
14178
  const isMouse = !("ontouchstart" in window);
14618
- state.touchOffset = isMouse ? [0, 0] : vector$1.subtract(startCoord, startMouseCoord);
14179
+ state.touchOffset = isMouse ? [0, 0] : vector$3.subtract(startCoord, startMouseCoord);
14619
14180
  }
14620
14181
  const svgElem = state.visibleShape.wrapper;
14621
14182
  if (state.shadow) {
@@ -14642,7 +14203,7 @@ class MovablePoint$5 {
14642
14203
  self.draw();
14643
14204
  },
14644
14205
  onMove: (mouseCoord, prevMouseCoord) => {
14645
- const transformedCoord = vector$1.add(mouseCoord, state.touchOffset);
14206
+ const transformedCoord = vector$3.add(mouseCoord, state.touchOffset);
14646
14207
  self.moveTo(transformedCoord);
14647
14208
  if (state.showHairlines) {
14648
14209
  if (!this.state.outOfBounds) {
@@ -15035,7 +14596,7 @@ function sum(array) {
15035
14596
  return memo + arg;
15036
14597
  }, 0);
15037
14598
  }
15038
- function clockwise(points) {
14599
+ function clockwise$2(points) {
15039
14600
  const segments = _.zip(points, points.slice(1).concat(points.slice(0, 1)));
15040
14601
  const areas = _.map(segments, function (segment) {
15041
14602
  const p1 = segment[0];
@@ -15101,7 +14662,7 @@ const draw$1 = {
15101
14662
  const graphie = this.graphie;
15102
14663
  const self = this;
15103
14664
  const coords = _.invoke(state.points, "coord");
15104
- const isClockwise = clockwise(coords);
14665
+ const isClockwise = clockwise$2(coords);
15105
14666
  const n = coords.length;
15106
14667
 
15107
14668
  // graphie.labelAngle and similar methods attempt to re-use the label
@@ -15272,7 +14833,7 @@ const constraints = {
15272
14833
  return function (coord, prevCoord) {
15273
14834
  // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
15274
14835
  const graphie = this.graphie;
15275
- const delta = vector$1.subtract(coord, prevCoord);
14836
+ const delta = vector$3.subtract(coord, prevCoord);
15276
14837
  range = range || graphie.range;
15277
14838
  // A null snap means no snap; an undefined snap means
15278
14839
  // default to graphie's
@@ -15291,8 +14852,8 @@ const constraints = {
15291
14852
  // Calculate the bounds for the delta.
15292
14853
  // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
15293
14854
  const deltaBounds = _.map(this.coords(), function (coord, i) {
15294
- const max = vector$1.subtract(absoluteUpper, coord);
15295
- const min = vector$1.subtract(absoluteLower, coord);
14855
+ const max = vector$3.subtract(absoluteUpper, coord);
14856
+ const min = vector$3.subtract(absoluteLower, coord);
15296
14857
  return [min, max];
15297
14858
  });
15298
14859
 
@@ -15306,7 +14867,7 @@ const constraints = {
15306
14867
  const deltaY = Math.max(lower[1], Math.min(upper[1], delta[1]));
15307
14868
  return [deltaX, deltaY];
15308
14869
  }, delta);
15309
- return vector$1.add(prevCoord, boundedDelta);
14870
+ return vector$3.add(prevCoord, boundedDelta);
15310
14871
  };
15311
14872
  }
15312
14873
  };
@@ -15315,10 +14876,10 @@ const constraints = {
15315
14876
  constraints.standard = null;
15316
14877
  const onMove = {
15317
14878
  updatePoints: function (coord, prevCoord) {
15318
- const actualDelta = vector$1.subtract(coord, prevCoord);
14879
+ const actualDelta = vector$3.subtract(coord, prevCoord);
15319
14880
  // @ts-expect-error - TS2339 - Property 'state' does not exist on type '{ readonly updatePoints: (coord: Coord, prevCoord: Coord) => undefined; }'.
15320
14881
  _.each(this.state.points, function (point) {
15321
- point.setCoord(vector$1.add(point.coord(), actualDelta));
14882
+ point.setCoord(vector$3.add(point.coord(), actualDelta));
15322
14883
  });
15323
14884
  }
15324
14885
  };
@@ -15492,9 +15053,9 @@ _.extend(MovablePolygon.prototype, {
15492
15053
  self._fireEvent(self.state.onMoveStart, self.coord(0), self.coord(0));
15493
15054
  },
15494
15055
  onMove: function (mouseCoord, prevMouseCoord) {
15495
- const delta = vector$1.subtract(mouseCoord, prevMouseCoord);
15496
- self._totalDelta = vector$1.add(self._totalDelta, delta);
15497
- let refCoord = vector$1.add(self._initialRefCoord, self._totalDelta);
15056
+ const delta = vector$3.subtract(mouseCoord, prevMouseCoord);
15057
+ self._totalDelta = vector$3.add(self._totalDelta, delta);
15058
+ let refCoord = vector$3.add(self._initialRefCoord, self._totalDelta);
15498
15059
  refCoord = self._applyConstraints(refCoord, self._prevRefCoord);
15499
15060
  if (refCoord === false) {
15500
15061
  return;
@@ -15809,7 +15370,7 @@ const Parabola = GraphieClasses.createClass({
15809
15370
  this.parabola.toFront();
15810
15371
  }
15811
15372
  });
15812
- const Sinusoid$1 = GraphieClasses.createClass({
15373
+ const Sinusoid = GraphieClasses.createClass({
15813
15374
  displayName: "Sinusoid",
15814
15375
  movableProps: ["children"],
15815
15376
  add: function (graphie) {
@@ -15831,7 +15392,7 @@ const Sinusoid$1 = GraphieClasses.createClass({
15831
15392
  this.sinusoid.toFront();
15832
15393
  }
15833
15394
  });
15834
- const Plot$2 = GraphieClasses.createSimpleClass((graphie, props) => {
15395
+ const Plot$1 = GraphieClasses.createSimpleClass((graphie, props) => {
15835
15396
  return graphie.plot(props.fn, props.range, props.style);
15836
15397
  });
15837
15398
  const PlotParametric$1 = GraphieClasses.createSimpleClass((graphie, props) => {
@@ -15889,17 +15450,16 @@ var Movables = {
15889
15450
  MovablePoint: MovablePoint$4,
15890
15451
  Parabola: Parabola,
15891
15452
  Path: Path,
15892
- Plot: Plot$2,
15453
+ Plot: Plot$1,
15893
15454
  PlotParametric: PlotParametric$1,
15894
15455
  Point: Point$1,
15895
- Sinusoid: Sinusoid$1,
15456
+ Sinusoid: Sinusoid,
15896
15457
  Rect: Rect$1
15897
15458
  };
15898
15459
 
15899
15460
  const GraphieMovable = GraphieClasses.GraphieMovable;
15900
15461
  const createGraphie = GraphUtils.createGraphie;
15901
15462
  const {
15902
- deepEq: deepEq$4,
15903
15463
  nestedMap
15904
15464
  } = Util;
15905
15465
  const {
@@ -16141,7 +15701,7 @@ class Graphie extends React.Component {
16141
15701
  if (this.props.setup !== prevProps.setup) {
16142
15702
  Log.error("<Graphie> was given a new setup function. " + "This is a bad idea; please refactor your code to give " + "the same setup function reference to <Graphie> on " + "every render.", Errors.Internal);
16143
15703
  }
16144
- if (!deepEq$4(this.props.options, prevProps.options) || !deepEq$4(this.props.box, prevProps.box) || !deepEq$4(this.props.range, prevProps.range)) {
15704
+ if (!approximateDeepEqual(this.props.options, prevProps.options) || !approximateDeepEqual(this.props.box, prevProps.box) || !approximateDeepEqual(this.props.range, prevProps.range)) {
16145
15705
  this._setupGraphie();
16146
15706
  }
16147
15707
  this._updateMovables();
@@ -18248,6 +17808,10 @@ const getWidgetScorer = name => {
18248
17808
  var _widgets$name$scorer, _widgets$name4;
18249
17809
  return (_widgets$name$scorer = (_widgets$name4 = widgets[name]) == null ? void 0 : _widgets$name4.scorer) != null ? _widgets$name$scorer : null;
18250
17810
  };
17811
+ const getPublicWidgetOptionsFunction = name => {
17812
+ var _widgets$name$getPubl, _widgets$name5;
17813
+ return (_widgets$name$getPubl = (_widgets$name5 = widgets[name]) == null ? void 0 : _widgets$name5.getPublicWidgetOptions) != null ? _widgets$name$getPubl : i => i;
17814
+ };
18251
17815
  const getEditor = name => {
18252
17816
  return _.has(editors, name) ? editors[name] : null;
18253
17817
  };
@@ -18552,6 +18116,7 @@ var widgets$1 = /*#__PURE__*/Object.freeze({
18552
18116
  getWidget: getWidget,
18553
18117
  getWidgetExport: getWidgetExport,
18554
18118
  getWidgetScorer: getWidgetScorer,
18119
+ getPublicWidgetOptionsFunction: getPublicWidgetOptionsFunction,
18555
18120
  getEditor: getEditor,
18556
18121
  getTransform: getTransform,
18557
18122
  getVersion: getVersion,
@@ -20891,230 +20456,6 @@ const getPromptJSON$s = (renderProps, userInput) => {
20891
20456
  };
20892
20457
  };
20893
20458
 
20894
- /*
20895
- * In this file, an `expression` is some portion of valid TeX enclosed in
20896
- * curly brackets.
20897
- */
20898
-
20899
- /*
20900
- * Find the index at which an expression ends, i.e., has an unmatched
20901
- * closing curly bracket. This method assumes that we start with a non-open
20902
- * bracket character and end when we've seen more left than right brackets
20903
- * (rather than assuming that we start with a bracket character and wait for
20904
- * bracket equality).
20905
- */
20906
- function findEndpoint(tex, currentIndex) {
20907
- let bracketDepth = 0;
20908
- for (let i = currentIndex, len = tex.length; i < len; i++) {
20909
- const c = tex[i];
20910
- if (c === "{") {
20911
- bracketDepth++;
20912
- } else if (c === "}") {
20913
- bracketDepth--;
20914
- }
20915
- if (bracketDepth < 0) {
20916
- return i;
20917
- }
20918
- }
20919
- // If we never see unbalanced curly brackets, default to the
20920
- // entire string
20921
- return tex.length;
20922
- }
20923
-
20924
- /*
20925
- * Parses an individual set of curly brackets into TeX.
20926
- */
20927
- function parseNextExpression(tex, currentIndex, handler) {
20928
- // Find the first '{' and grab subsequent TeX
20929
- // Ex) tex: '{3}{7}', and we want the '3'
20930
- const openBracketIndex = tex.indexOf("{", currentIndex);
20931
- const nextExpIndex = openBracketIndex + 1;
20932
-
20933
- // Truncate to only contain remaining TeX
20934
- const endpoint = findEndpoint(tex, nextExpIndex);
20935
- const expressionTeX = tex.substring(nextExpIndex, endpoint);
20936
- const parsedExp = walkTex(expressionTeX, handler);
20937
- return {
20938
- endpoint: endpoint,
20939
- expression: parsedExp
20940
- };
20941
- }
20942
- function getNextFracIndex(tex, currentIndex) {
20943
- const dfrac = "\\dfrac";
20944
- const frac = "\\frac";
20945
- const nextFrac = tex.indexOf(frac, currentIndex);
20946
- const nextDFrac = tex.indexOf(dfrac, currentIndex);
20947
- if (nextFrac > -1 && nextDFrac > -1) {
20948
- return Math.min(nextFrac, nextDFrac);
20949
- }
20950
- if (nextFrac > -1) {
20951
- return nextFrac;
20952
- }
20953
- if (nextDFrac > -1) {
20954
- return nextDFrac;
20955
- }
20956
- return -1;
20957
- }
20958
- function walkTex(tex, handler) {
20959
- if (!tex) {
20960
- return "";
20961
- }
20962
-
20963
- // Ex) tex: '2 \dfrac {3}{7}'
20964
- let parsedString = "";
20965
- let currentIndex = 0;
20966
- let nextFrac = getNextFracIndex(tex, currentIndex);
20967
-
20968
- // For each \dfrac, find the two expressions (wrapped in {}) and recur
20969
- while (nextFrac > -1) {
20970
- // Gather first fragment, preceding \dfrac
20971
- // Ex) parsedString: '2 '
20972
- parsedString += tex.substring(currentIndex, nextFrac);
20973
-
20974
- // Remove everything preceding \dfrac, which has been parsed
20975
- currentIndex = nextFrac;
20976
-
20977
- // Parse first expression and move index past it
20978
- // Ex) firstParsedExpression.expression: '3'
20979
- const firstParsedExpression = parseNextExpression(tex, currentIndex, handler);
20980
- currentIndex = firstParsedExpression.endpoint + 1;
20981
-
20982
- // Parse second expression
20983
- // Ex) secondParsedExpression.expression: '7'
20984
- const secondParsedExpression = parseNextExpression(tex, currentIndex, handler);
20985
- currentIndex = secondParsedExpression.endpoint + 1;
20986
-
20987
- // Add expressions to running total of parsed expressions
20988
- if (parsedString.length) {
20989
- parsedString += " ";
20990
- }
20991
-
20992
- // Apply a custom handler based on the parsed subexpressions
20993
- parsedString += handler(firstParsedExpression.expression, secondParsedExpression.expression);
20994
-
20995
- // Find next DFrac, relative to currentIndex
20996
- nextFrac = getNextFracIndex(tex, currentIndex);
20997
- }
20998
-
20999
- // Add remaining TeX, which is \dfrac-free
21000
- parsedString += tex.slice(currentIndex);
21001
- return parsedString;
21002
- }
21003
-
21004
- /*
21005
- * Modify a TeX expression, returning another TeX expression. The resulting
21006
- * expression will have its innermost fractions stubbed out with \fracs
21007
- * (as opposed to \dfracs). All other content will remain untouched.
21008
- */
21009
- function modifyTex(tex) {
21010
- function isNestedFraction(tex) {
21011
- return tex.indexOf("\\frac") > -1 || tex.indexOf("\\dfrac") > -1;
21012
- }
21013
- const handler = function handler(exp1, exp2) {
21014
- let prefix;
21015
- if (isNestedFraction(exp1) || isNestedFraction(exp2)) {
21016
- prefix = "\\dfrac";
21017
- } else {
21018
- prefix = "\\frac";
21019
- }
21020
- return prefix + " {" + exp1 + "}{" + exp2 + "}";
21021
- };
21022
- return walkTex(tex, handler);
21023
- }
21024
-
21025
- /*
21026
- * Parse a TeX expression into something interpretable by input-number.
21027
- * The process is concerned with: (1) parsing fractions, i.e., \dfracs; and
21028
- * (2) removing backslash-escaping from certain characters (right now, only
21029
- * percent signs).
21030
- *
21031
- * The basic algorithm for handling \dfracs splits on \dfracs and then recurs
21032
- * on the subsequent "expressions", i.e., the {} pairs that follow \dfrac. The
21033
- * recursion is to allow for nested \dfrac elements.
21034
- *
21035
- * Backslash-escapes are removed with a simple search-and-replace.
21036
- */
21037
- function parseTex(tex) {
21038
- const handler = function handler(exp1, exp2) {
21039
- return exp1 + "/" + exp2;
21040
- };
21041
- const texWithoutFracs = walkTex(tex, handler);
21042
- return texWithoutFracs.replace("\\%", "%");
21043
- }
21044
- var TexWrangler = {
21045
- parseTex,
21046
- modifyTex
21047
- };
21048
-
21049
- const ParseTex$1 = TexWrangler.parseTex;
21050
- const answerTypes = {
21051
- number: {
21052
- name: "Numbers",
21053
- forms: "integer, decimal, proper, improper, mixed"
21054
- },
21055
- decimal: {
21056
- name: "Decimals",
21057
- forms: "decimal"
21058
- },
21059
- integer: {
21060
- name: "Integers",
21061
- forms: "integer"
21062
- },
21063
- rational: {
21064
- name: "Fractions and mixed numbers",
21065
- forms: "integer, proper, improper, mixed"
21066
- },
21067
- improper: {
21068
- name: "Improper numbers (no mixed)",
21069
- forms: "integer, proper, improper"
21070
- },
21071
- mixed: {
21072
- name: "Mixed numbers (no improper)",
21073
- forms: "integer, proper, mixed"
21074
- },
21075
- percent: {
21076
- name: "Numbers or percents",
21077
- forms: "integer, decimal, proper, improper, mixed, percent"
21078
- },
21079
- pi: {
21080
- name: "Numbers with pi",
21081
- forms: "pi"
21082
- }
21083
- };
21084
- function scoreInputNumber(userInput, rubric, strings) {
21085
- if (rubric.answerType == null) {
21086
- rubric.answerType = "number";
21087
- }
21088
-
21089
- // note(matthewc): this will get immediately parsed again by
21090
- // `KhanAnswerTypes.number.convertToPredicate`, but a string is
21091
- // expected here
21092
- const stringValue = `${rubric.value}`;
21093
- const val = KhanAnswerTypes.number.createValidatorFunctional(stringValue, {
21094
- simplify: rubric.simplify,
21095
- inexact: rubric.inexact || undefined,
21096
- maxError: rubric.maxError,
21097
- forms: answerTypes[rubric.answerType].forms
21098
- });
21099
-
21100
- // We may have received TeX; try to parse it before grading.
21101
- // If `currentValue` is not TeX, this should be a no-op.
21102
- const currentValue = ParseTex$1(userInput.currentValue);
21103
- const result = val(currentValue);
21104
- if (result.empty) {
21105
- return {
21106
- type: "invalid",
21107
- message: result.message
21108
- };
21109
- }
21110
- return {
21111
- type: "points",
21112
- earned: result.correct ? 1 : 0,
21113
- total: 1,
21114
- message: result.message
21115
- };
21116
- }
21117
-
21118
20459
  const formExamples$1 = {
21119
20460
  integer: function (options, strings) {
21120
20461
  return strings.integerExample;
@@ -21217,7 +20558,7 @@ class InputNumber extends React.Component {
21217
20558
  strings
21218
20559
  } = this.context;
21219
20560
  const type = this.props.answerType;
21220
- const forms = answerTypes[type].forms.split(/\s*,\s*/);
20561
+ const forms = inputNumberAnswerTypes[type].forms.split(/\s*,\s*/);
21221
20562
  const examples = _.map(forms, form => formExamples$1[form](this.props, strings));
21222
20563
  return [strings.yourAnswer].concat(examples);
21223
20564
  }
@@ -21347,140 +20688,6 @@ const getPromptJSON$r = (renderProps, userInput) => {
21347
20688
  };
21348
20689
  };
21349
20690
 
21350
- const ParseTex = TexWrangler.parseTex;
21351
- const answerFormButtons = [{
21352
- title: "Integers",
21353
- value: "integer",
21354
- content: "6"
21355
- }, {
21356
- title: "Decimals",
21357
- value: "decimal",
21358
- content: "0.75"
21359
- }, {
21360
- title: "Proper fractions",
21361
- value: "proper",
21362
- content: "\u2157"
21363
- }, {
21364
- title: "Improper fractions",
21365
- value: "improper",
21366
- content: "\u2077\u2044\u2084"
21367
- }, {
21368
- title: "Mixed numbers",
21369
- value: "mixed",
21370
- content: "1\u00BE"
21371
- }, {
21372
- title: "Numbers with \u03C0",
21373
- value: "pi",
21374
- content: "\u03C0"
21375
- }];
21376
-
21377
- // This function checks if the user inputted a percent value, parsing
21378
- // it as a number (and maybe scaling) so that it can be graded.
21379
- // NOTE(michaelpolyak): Unlike `KhanAnswerTypes.number.percent()` which
21380
- // can accept several input forms with or without "%", the decision
21381
- // to parse based on the presence of "%" in the input, is so that we
21382
- // don't accidently scale the user typed value before grading, CP-930.
21383
- function maybeParsePercentInput(inputValue, normalizedAnswerExpected) {
21384
- // If the input value is not a string ending with "%", then there's
21385
- // nothing more to do. The value will be graded as inputted by user.
21386
- if (!(typeof inputValue === "string" && inputValue.endsWith("%"))) {
21387
- return inputValue;
21388
- }
21389
- const value = parseFloat(inputValue.slice(0, -1));
21390
- // If the input value stripped of the "%" cannot be parsed as a
21391
- // number (the slice is not really necessary for parseFloat to work
21392
- // if the string starts with a number) then return the original
21393
- // input for grading.
21394
- if (isNaN(value)) {
21395
- return inputValue;
21396
- }
21397
-
21398
- // Next, if all correct answers are in the range of |0,1| then we
21399
- // scale the user typed value. We assume this is the correct thing
21400
- // to do since the input value ends with "%".
21401
- if (normalizedAnswerExpected) {
21402
- return value / 100;
21403
- }
21404
-
21405
- // Otherwise, we return input value (number) stripped of the "%".
21406
- return value;
21407
- }
21408
- function scoreNumericInput(userInput, rubric, strings) {
21409
- var _matchedAnswer$messag;
21410
- const defaultAnswerForms = answerFormButtons.map(e => e["value"])
21411
- // Don't default to validating the answer as a pi answer
21412
- // if answerForm isn't set on the answer
21413
- // https://khanacademy.atlassian.net/browse/LC-691
21414
- .filter(e => e !== "pi");
21415
- const createValidator = answer => {
21416
- var _answer$answerForms;
21417
- const stringAnswer = `${answer.value}`;
21418
-
21419
- // Always validate against the provided answer forms (pi, decimal, etc.)
21420
- const validatorForms = [...((_answer$answerForms = answer.answerForms) != null ? _answer$answerForms : [])];
21421
-
21422
- // When an answer is set to strict, we validate using ONLY
21423
- // the provided answerForms. If strict is false, or if there
21424
- // were no provided answer forms, we will include all
21425
- // of the default answer forms in our validator.
21426
- if (!answer.strict || validatorForms.length === 0) {
21427
- validatorForms.push(...defaultAnswerForms);
21428
- }
21429
- return KhanAnswerTypes.number.createValidatorFunctional(stringAnswer, {
21430
- message: answer.message,
21431
- simplify: answer.status === "correct" ? answer.simplify : "optional",
21432
- inexact: true,
21433
- // TODO(merlob) backfill / delete
21434
- maxError: answer.maxError,
21435
- forms: validatorForms
21436
- });
21437
- };
21438
-
21439
- // We may have received TeX; try to parse it before grading.
21440
- // If `currentValue` is not TeX, this should be a no-op.
21441
- const currentValue = ParseTex(userInput.currentValue);
21442
- const normalizedAnswerExpected = rubric.answers.filter(answer => answer.status === "correct").every(answer => answer.value != null && Math.abs(answer.value) <= 1);
21443
-
21444
- // The coefficient is an attribute of the widget
21445
- let localValue = currentValue;
21446
- if (rubric.coefficient) {
21447
- if (!localValue) {
21448
- localValue = 1;
21449
- } else if (localValue === "-") {
21450
- localValue = -1;
21451
- }
21452
- }
21453
- const matchedAnswer = rubric.answers.map(answer => {
21454
- const validateFn = createValidator(answer);
21455
- const score = validateFn(maybeParsePercentInput(localValue, normalizedAnswerExpected));
21456
- return _extends({}, answer, {
21457
- score
21458
- });
21459
- }).find(answer => {
21460
- // NOTE: "answer.score.correct" indicates a match via the validate function.
21461
- // It does NOT indicate that the answer itself is correct.
21462
- return answer.score.correct || answer.status === "correct" && answer.score.empty;
21463
- });
21464
- const result = (matchedAnswer == null ? void 0 : matchedAnswer.status) === "correct" ? matchedAnswer.score : {
21465
- empty: (matchedAnswer == null ? void 0 : matchedAnswer.status) === "ungraded",
21466
- correct: (matchedAnswer == null ? void 0 : matchedAnswer.status) === "correct",
21467
- message: (_matchedAnswer$messag = matchedAnswer == null ? void 0 : matchedAnswer.message) != null ? _matchedAnswer$messag : null,
21468
- guess: localValue
21469
- };
21470
- if (result.empty) {
21471
- return {
21472
- type: "invalid",
21473
- message: result.message
21474
- };
21475
- }
21476
- return {
21477
- type: "points",
21478
- earned: result.correct ? 1 : 0,
21479
- total: 1,
21480
- message: result.message
21481
- };
21482
- }
21483
-
21484
20691
  const formExamples = {
21485
20692
  integer: (form, strings) => strings.integerExample,
21486
20693
  proper: (form, strings) => form.simplify === "optional" ? strings.properExample : strings.simplifiedProperExample,
@@ -25468,72 +24675,6 @@ const styles$o = StyleSheet.create({
25468
24675
  }
25469
24676
  });
25470
24677
 
25471
- /**
25472
- * Checks if the user has selected at least one option. Additional validation
25473
- * is done in scoreRadio to check if the number of selected options is correct
25474
- * and if the user has selected both a correct option and the "none of the above"
25475
- * option.
25476
- * @param userInput
25477
- * @see `scoreRadio` for the additional validation logic and the scoring logic.
25478
- */
25479
- function validateRadio(userInput) {
25480
- const numSelected = userInput.choicesSelected.reduce((sum, selected) => {
25481
- return sum + (selected ? 1 : 0);
25482
- }, 0);
25483
- if (numSelected === 0) {
25484
- return {
25485
- type: "invalid",
25486
- message: null
25487
- };
25488
- }
25489
- return null;
25490
- }
25491
-
25492
- function scoreRadio(userInput, rubric, strings) {
25493
- const validationError = validateRadio(userInput);
25494
- if (validationError) {
25495
- return validationError;
25496
- }
25497
- const numSelected = userInput.choicesSelected.reduce((sum, selected) => {
25498
- return sum + (selected ? 1 : 0);
25499
- }, 0);
25500
- const numCorrect = rubric.choices.reduce((sum, currentChoice) => {
25501
- return currentChoice.correct ? sum + 1 : sum;
25502
- }, 0);
25503
- if (numCorrect > 1 && numSelected !== numCorrect) {
25504
- return {
25505
- type: "invalid",
25506
- message: strings.chooseCorrectNum
25507
- };
25508
- // If NOTA and some other answer are checked, ...
25509
- }
25510
-
25511
- const noneOfTheAboveSelected = rubric.choices.some((choice, index) => choice.isNoneOfTheAbove && userInput.choicesSelected[index]);
25512
- if (noneOfTheAboveSelected && numSelected > 1) {
25513
- return {
25514
- type: "invalid",
25515
- message: strings.notNoneOfTheAbove
25516
- };
25517
- }
25518
- const correct = userInput.choicesSelected.every((selected, i) => {
25519
- let isCorrect;
25520
- if (rubric.choices[i].isNoneOfTheAbove) {
25521
- isCorrect = rubric.choices.every((choice, j) => {
25522
- return i === j || !choice.correct;
25523
- });
25524
- } else {
25525
- isCorrect = !!rubric.choices[i].correct;
25526
- }
25527
- return isCorrect === selected;
25528
- });
25529
- return {
25530
- type: "points",
25531
- earned: correct ? 1 : 0,
25532
- total: 1,
25533
- message: null
25534
- };
25535
- }
25536
-
25537
24678
  // RenderProps is the return type for radio.jsx#transform
25538
24679
 
25539
24680
  class Radio$1 extends React.Component {
@@ -25639,7 +24780,7 @@ class Radio$1 extends React.Component {
25639
24780
  choiceStates
25640
24781
  } = this.props;
25641
24782
  if (choiceStates) {
25642
- const score = scoreRadio(this.getUserInput(), rubric, this.context.strings);
24783
+ const score = scoreRadio(this.getUserInput(), rubric);
25643
24784
  const widgetCorrect = score.type === "points" && score.total === score.earned;
25644
24785
  const newStates = choiceStates.map(state => _extends({}, state, {
25645
24786
  highlighted: state.selected,
@@ -26009,40 +25150,20 @@ const getPromptJSON$n = (renderProps, userInput) => {
26009
25150
  };
26010
25151
 
26011
25152
  /**
26012
- * Checks userInput from the categorizer widget to see if the user has selected
26013
- * a category for each item.
26014
- * @param userInput - The user's input corresponding to an array of indices that
26015
- * represent the selected category for each row/item.
26016
- * @param validationData - An array of strings corresponding to each row/item
26017
- * @param strings - Used to provide a validation message
25153
+ * For details on the individual options, see the
25154
+ * PerseusCategorizerWidgetOptions type
26018
25155
  */
26019
- function validateCategorizer(userInput, validationData, strings) {
26020
- const incomplete = validationData.items.some((_, i) => userInput.values[i] == null);
26021
- if (incomplete) {
26022
- return {
26023
- type: "invalid",
26024
- message: strings.invalidSelection
26025
- };
26026
- }
26027
- return null;
26028
- }
26029
25156
 
26030
- function scoreCategorizer(userInput, scoringData, strings) {
26031
- const validationError = validateCategorizer(userInput, scoringData, strings);
26032
- if (validationError) {
26033
- return validationError;
26034
- }
26035
- let allCorrect = true;
26036
- scoringData.values.forEach((value, i) => {
26037
- if (userInput.values[i] !== value) {
26038
- allCorrect = false;
26039
- }
26040
- });
25157
+ /**
25158
+ * Given a PerseusCategorizerWidgetOptions object, return a new object with only
25159
+ * the public options that should be exposed to the client.
25160
+ */
25161
+ function getCategorizerPublicWidgetOptions(options) {
26041
25162
  return {
26042
- type: "points",
26043
- earned: allCorrect ? 1 : 0,
26044
- total: 1,
26045
- message: null
25163
+ items: options.items,
25164
+ categories: options.categories,
25165
+ randomizeItems: options.randomizeItems,
25166
+ static: options.static
26046
25167
  };
26047
25168
  }
26048
25169
 
@@ -26224,7 +25345,8 @@ var Categorizer$1 = {
26224
25345
  isLintable: true,
26225
25346
  // TODO(LEMS-2656): remove TS suppression
26226
25347
  // @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusCSProgramUserInput'.
26227
- scorer: scoreCategorizer
25348
+ scorer: scoreCategorizer,
25349
+ getPublicWidgetOptions: getCategorizerPublicWidgetOptions
26228
25350
  };
26229
25351
 
26230
25352
  /**
@@ -26286,31 +25408,6 @@ const getPromptJSON$m = () => {
26286
25408
  return getUnsupportedPromptJSON("cs-program");
26287
25409
  };
26288
25410
 
26289
- function scoreCSProgram(state) {
26290
- // The iframe can tell us whether it's correct or incorrect,
26291
- // and pass an optional message
26292
- if (state.status === "correct") {
26293
- return {
26294
- type: "points",
26295
- earned: 1,
26296
- total: 1,
26297
- message: state.message || null
26298
- };
26299
- }
26300
- if (state.status === "incorrect") {
26301
- return {
26302
- type: "points",
26303
- earned: 0,
26304
- total: 1,
26305
- message: state.message || null
26306
- };
26307
- }
26308
- return {
26309
- type: "invalid",
26310
- message: "Keep going, you're not there yet!"
26311
- };
26312
- }
26313
-
26314
25411
  /**
26315
25412
  * This widget is for embedding Khan Academy CS programs.
26316
25413
  */
@@ -26583,34 +25680,6 @@ const getPromptJSON$k = (renderProps, userInput) => {
26583
25680
  };
26584
25681
  };
26585
25682
 
26586
- /**
26587
- * Checks if the user has selected an item from the dropdown before scoring.
26588
- * This is shown with a userInput value / index other than 0.
26589
- */
26590
- function validateDropdown(userInput) {
26591
- if (userInput.value === 0) {
26592
- return {
26593
- type: "invalid",
26594
- message: null
26595
- };
26596
- }
26597
- return null;
26598
- }
26599
-
26600
- function scoreDropdown(userInput, rubric) {
26601
- const validationError = validateDropdown(userInput);
26602
- if (validationError) {
26603
- return validationError;
26604
- }
26605
- const correct = rubric.choices[userInput.value - 1].correct;
26606
- return {
26607
- type: "points",
26608
- earned: correct ? 1 : 0,
26609
- total: 1,
26610
- message: null
26611
- };
26612
- }
26613
-
26614
25683
  class Dropdown extends React.Component {
26615
25684
  constructor(...args) {
26616
25685
  super(...args);
@@ -27063,7 +26132,7 @@ class GradedGroup extends React.Component {
27063
26132
  DEFAULT_INVALID_MESSAGE_2
27064
26133
  } = this.context.strings;
27065
26134
  const status = score.type === "points" ? score.total === score.earned ? GRADING_STATUSES.correct : GRADING_STATUSES.incorrect : GRADING_STATUSES.invalid;
27066
- const message = score.type === "points" ? score.message || "" : score.message ? `${INVALID_MESSAGE_PREFIX} ${score.message}` : `${INVALID_MESSAGE_PREFIX} ${DEFAULT_INVALID_MESSAGE_1}${DEFAULT_INVALID_MESSAGE_2}`;
26135
+ const message = score.type === "points" ? score.message || "" : score.message ? `${INVALID_MESSAGE_PREFIX} ${mapErrorToString(score.message)}` : `${INVALID_MESSAGE_PREFIX} ${DEFAULT_INVALID_MESSAGE_1}${DEFAULT_INVALID_MESSAGE_2}`;
27067
26136
  this.setState({
27068
26137
  status: status,
27069
26138
  message: message,
@@ -27673,408 +26742,21 @@ const getPromptJSON$g = (renderProps, userInput) => {
27673
26742
  };
27674
26743
  };
27675
26744
 
27676
- // @ts-expect-error - TS2339 - Property 'Plot' does not exist on type 'typeof Graphie'.
27677
- const Plot$1 = Graphie.Plot;
26745
+ const movableTypeToComponent = {
26746
+ // @ts-expect-error - TS2339 - Property 'Plot' does not exist on type 'typeof Graphie'.
26747
+ PLOT: Graphie.Plot,
26748
+ // @ts-expect-error - TS2339 - Property 'Parabola' does not exist on type 'typeof Graphie'.
26749
+ PARABOLA: Graphie.Parabola,
26750
+ // @ts-expect-error - TS2339 - Property 'Sinusoid' does not exist on type 'typeof Graphie'.
26751
+ SINUSOID: Graphie.Sinusoid
26752
+ };
27678
26753
  const DEFAULT_BACKGROUND_IMAGE = {
27679
26754
  url: null
27680
26755
  };
27681
-
27682
- // TODO(charlie): These really need to go into a utility file as they're being
27683
- // used by both interactive-graph and now grapher.
27684
- function canonicalSineCoefficients(coeffs) {
27685
- // For a curve of the form f(x) = a * Sin(b * x - c) + d,
27686
- // this function ensures that a, b > 0, and c is its
27687
- // smallest possible positive value.
27688
- let amplitude = coeffs[0];
27689
- let angularFrequency = coeffs[1];
27690
- let phase = coeffs[2];
27691
- const verticalOffset = coeffs[3];
27692
-
27693
- // Guarantee a > 0
27694
- if (amplitude < 0) {
27695
- amplitude *= -1;
27696
- angularFrequency *= -1;
27697
- phase *= -1;
27698
- }
27699
- const period = 2 * Math.PI;
27700
- // Guarantee b > 0
27701
- if (angularFrequency < 0) {
27702
- angularFrequency *= -1;
27703
- phase *= -1;
27704
- phase += period / 2;
27705
- }
27706
-
27707
- // Guarantee c is smallest possible positive value
27708
- while (phase > 0) {
27709
- phase -= period;
27710
- }
27711
- while (phase < 0) {
27712
- phase += period;
27713
- }
27714
- return [amplitude, angularFrequency, phase, verticalOffset];
27715
- }
27716
- function canonicalTangentCoefficients(coeffs) {
27717
- // For a curve of the form f(x) = a * Tan(b * x - c) + d,
27718
- // this function ensures that a, b > 0, and c is its
27719
- // smallest possible positive value.
27720
- let amplitude = coeffs[0];
27721
- let angularFrequency = coeffs[1];
27722
- let phase = coeffs[2];
27723
- const verticalOffset = coeffs[3];
27724
-
27725
- // Guarantee a > 0
27726
- if (amplitude < 0) {
27727
- amplitude *= -1;
27728
- angularFrequency *= -1;
27729
- phase *= -1;
27730
- }
27731
- const period = Math.PI;
27732
- // Guarantee b > 0
27733
- if (angularFrequency < 0) {
27734
- angularFrequency *= -1;
27735
- phase *= -1;
27736
- phase += period / 2;
27737
- }
27738
-
27739
- // Guarantee c is smallest possible positive value
27740
- while (phase > 0) {
27741
- phase -= period;
27742
- }
27743
- while (phase < 0) {
27744
- phase += period;
27745
- }
27746
- return [amplitude, angularFrequency, phase, verticalOffset];
27747
- }
27748
- const PlotDefaults = {
27749
- areEqual: function (coeffs1, coeffs2) {
27750
- return Util.deepEq(coeffs1, coeffs2);
27751
- },
27752
- Movable: Plot$1,
27753
- getPropsForCoeffs: function (coeffs) {
27754
- return {
27755
- // @ts-expect-error - TS2339 - Property 'getFunctionForCoeffs' does not exist on type '{ readonly areEqual: (coeffs1: any, coeffs2: any) => boolean; readonly Movable: any; readonly getPropsForCoeffs: (coeffs: any) => any; }'.
27756
- fn: _.partial(this.getFunctionForCoeffs, coeffs)
27757
- };
27758
- }
27759
- };
27760
- const Linear = _.extend({}, PlotDefaults, {
27761
- url: "https://ka-perseus-graphie.s3.amazonaws.com/67aaf581e6d9ef9038c10558a1f70ac21c11c9f8.png",
27762
- defaultCoords: [[0.25, 0.75], [0.75, 0.75]],
27763
- getCoefficients: function (coords) {
27764
- const p1 = coords[0];
27765
- const p2 = coords[1];
27766
- const denom = p2[0] - p1[0];
27767
- const num = p2[1] - p1[1];
27768
- if (denom === 0) {
27769
- return;
27770
- }
27771
- const m = num / denom;
27772
- const b = p2[1] - m * p2[0];
27773
- return [m, b];
27774
- },
27775
- getFunctionForCoeffs: function (coeffs, x) {
27776
- const m = coeffs[0];
27777
- const b = coeffs[1];
27778
- return m * x + b;
27779
- },
27780
- getEquationString: function (coords) {
27781
- const coeffs = this.getCoefficients(coords);
27782
- const m = coeffs[0];
27783
- const b = coeffs[1];
27784
- return "y = " + m.toFixed(3) + "x + " + b.toFixed(3);
27785
- }
27786
- });
27787
- const Quadratic = _.extend({}, PlotDefaults, {
27788
- url: "https://ka-perseus-graphie.s3.amazonaws.com/e23d36e6fc29ee37174e92c9daba2a66677128ab.png",
27789
- defaultCoords: [[0.5, 0.5], [0.75, 0.75]],
27790
- // @ts-expect-error - TS2339 - Property 'Parabola' does not exist on type 'typeof Graphie'.
27791
- Movable: Graphie.Parabola,
27792
- getCoefficients: function (coords) {
27793
- const p1 = coords[0];
27794
- const p2 = coords[1];
27795
-
27796
- // Parabola with vertex (h, k) has form: y = a * (h - k)^2 + k
27797
- const h = p1[0];
27798
- const k = p1[1];
27799
-
27800
- // Use these to calculate familiar a, b, c
27801
- const a = (p2[1] - k) / ((p2[0] - h) * (p2[0] - h));
27802
- const b = -2 * h * a;
27803
- const c = a * h * h + k;
27804
- return [a, b, c];
27805
- },
27806
- getFunctionForCoeffs: function (coeffs, x) {
27807
- const a = coeffs[0];
27808
- const b = coeffs[1];
27809
- const c = coeffs[2];
27810
- return (a * x + b) * x + c;
27811
- },
27812
- getPropsForCoeffs: function (coeffs) {
27813
- return {
27814
- a: coeffs[0],
27815
- b: coeffs[1],
27816
- c: coeffs[2]
27817
- };
27818
- },
27819
- getEquationString: function (coords) {
27820
- const coeffs = this.getCoefficients(coords);
27821
- const a = coeffs[0];
27822
- const b = coeffs[1];
27823
- const c = coeffs[2];
27824
- return "y = " + a.toFixed(3) + "x^2 + " + b.toFixed(3) + "x + " + c.toFixed(3);
27825
- }
27826
- });
27827
- const Sinusoid = _.extend({}, PlotDefaults, {
27828
- url: "https://ka-perseus-graphie.s3.amazonaws.com/3d68e7718498475f53b206c2ab285626baf8857e.png",
27829
- defaultCoords: [[0.5, 0.5], [0.6, 0.6]],
27830
- // @ts-expect-error - TS2339 - Property 'Sinusoid' does not exist on type 'typeof Graphie'.
27831
- Movable: Graphie.Sinusoid,
27832
- getCoefficients: function (coords) {
27833
- const p1 = coords[0];
27834
- const p2 = coords[1];
27835
- const a = p2[1] - p1[1];
27836
- const b = Math.PI / (2 * (p2[0] - p1[0]));
27837
- const c = p1[0] * b;
27838
- const d = p1[1];
27839
- return [a, b, c, d];
27840
- },
27841
- getFunctionForCoeffs: function (coeffs, x) {
27842
- const a = coeffs[0];
27843
- const b = coeffs[1];
27844
- const c = coeffs[2];
27845
- const d = coeffs[3];
27846
- return a * Math.sin(b * x - c) + d;
27847
- },
27848
- getPropsForCoeffs: function (coeffs) {
27849
- return {
27850
- a: coeffs[0],
27851
- b: coeffs[1],
27852
- c: coeffs[2],
27853
- d: coeffs[3]
27854
- };
27855
- },
27856
- getEquationString: function (coords) {
27857
- const coeffs = this.getCoefficients(coords);
27858
- const a = coeffs[0];
27859
- const b = coeffs[1];
27860
- const c = coeffs[2];
27861
- const d = coeffs[3];
27862
- return "y = " + a.toFixed(3) + " sin(" + b.toFixed(3) + "x - " + c.toFixed(3) + ") + " + d.toFixed(3);
27863
- },
27864
- areEqual: function (coeffs1, coeffs2) {
27865
- return Util.deepEq(canonicalSineCoefficients(coeffs1), canonicalSineCoefficients(coeffs2));
27866
- }
27867
- });
27868
- const Tangent = _.extend({}, PlotDefaults, {
27869
- url: "https://ka-perseus-graphie.s3.amazonaws.com/7db80d23c35214f98659fe1cf0765811c1bbfbba.png",
27870
- defaultCoords: [[0.5, 0.5], [0.75, 0.75]],
27871
- getCoefficients: function (coords) {
27872
- const p1 = coords[0];
27873
- const p2 = coords[1];
27874
- const a = p2[1] - p1[1];
27875
- const b = Math.PI / (4 * (p2[0] - p1[0]));
27876
- const c = p1[0] * b;
27877
- const d = p1[1];
27878
- return [a, b, c, d];
27879
- },
27880
- getFunctionForCoeffs: function (coeffs, x) {
27881
- const a = coeffs[0];
27882
- const b = coeffs[1];
27883
- const c = coeffs[2];
27884
- const d = coeffs[3];
27885
- return a * Math.tan(b * x - c) + d;
27886
- },
27887
- getEquationString: function (coords) {
27888
- const coeffs = this.getCoefficients(coords);
27889
- const a = coeffs[0];
27890
- const b = coeffs[1];
27891
- const c = coeffs[2];
27892
- const d = coeffs[3];
27893
- return "y = " + a.toFixed(3) + " sin(" + b.toFixed(3) + "x - " + c.toFixed(3) + ") + " + d.toFixed(3);
27894
- },
27895
- areEqual: function (coeffs1, coeffs2) {
27896
- return Util.deepEq(canonicalTangentCoefficients(coeffs1), canonicalTangentCoefficients(coeffs2));
27897
- }
27898
- });
27899
- const Exponential = _.extend({}, PlotDefaults, {
27900
- url: "https://ka-perseus-graphie.s3.amazonaws.com/9cbfad55525e3ce755a31a631b074670a5dad611.png",
27901
- defaultCoords: [[0.5, 0.55], [0.75, 0.75]],
27902
- defaultAsymptote: [[0, 0.5], [1.0, 0.5]],
27903
- /**
27904
- * Add extra constraints for movement of the points or asymptote (below):
27905
- * newCoord: [x, y]
27906
- * The end position of the point or asymptote endpoint
27907
- * oldCoord: [x, y]
27908
- * The old position of the point or asymptote endpoint
27909
- * coords:
27910
- * An array of coordinates representing the proposed end configuration
27911
- * of the plot coordinates.
27912
- * asymptote:
27913
- * An array of coordinates representing the proposed end configuration
27914
- * of the asymptote.
27915
- *
27916
- * Return: either a coordinate (to be used as the resulting coordinate of
27917
- * the move) or a boolean, where `true` uses newCoord as the resulting
27918
- * coordinate, and `false` uses oldCoord as the resulting coordinate.
27919
- */
27920
- extraCoordConstraint: function (newCoord, oldCoord, coords, asymptote, graph) {
27921
- const y = asymptote[0][1];
27922
- return _.all(coords, coord => coord[1] !== y);
27923
- },
27924
- extraAsymptoteConstraint: function (newCoord, oldCoord, coords, asymptote, graph) {
27925
- const y = newCoord[1];
27926
- const isValid = _.all(coords, coord => coord[1] > y) || _.all(coords, coord => coord[1] < y);
27927
- if (isValid) {
27928
- return [oldCoord[0], y];
27929
- }
27930
- // Snap the asymptote as close as possible, i.e., if the user moves
27931
- // the mouse really quickly into an invalid region
27932
- const oldY = oldCoord[1];
27933
- const wasBelow = _.all(coords, coord => coord[1] > oldY);
27934
- if (wasBelow) {
27935
- const bottomMost = _.min(_.map(coords, coord => coord[1]));
27936
- return [oldCoord[0], bottomMost - graph.snapStep[1]];
27937
- }
27938
- const topMost = _.max(_.map(coords, coord => coord[1]));
27939
- return [oldCoord[0], topMost + graph.snapStep[1]];
27940
- },
27941
- allowReflectOverAsymptote: true,
27942
- getCoefficients: function (coords, asymptote) {
27943
- const p1 = coords[0];
27944
- const p2 = coords[1];
27945
- const c = asymptote[0][1];
27946
- const b = Math.log((p1[1] - c) / (p2[1] - c)) / (p1[0] - p2[0]);
27947
- const a = (p1[1] - c) / Math.exp(b * p1[0]);
27948
- return [a, b, c];
27949
- },
27950
- getFunctionForCoeffs: function (coeffs, x) {
27951
- const a = coeffs[0];
27952
- const b = coeffs[1];
27953
- const c = coeffs[2];
27954
- return a * Math.exp(b * x) + c;
27955
- },
27956
- getEquationString: function (coords, asymptote) {
27957
- if (!asymptote) {
27958
- return null;
27959
- }
27960
- const coeffs = this.getCoefficients(coords, asymptote);
27961
- const a = coeffs[0];
27962
- const b = coeffs[1];
27963
- const c = coeffs[2];
27964
- return "y = " + a.toFixed(3) + "e^(" + b.toFixed(3) + "x) + " + c.toFixed(3);
27965
- }
27966
- });
27967
- const Logarithm = _.extend({}, PlotDefaults, {
27968
- url: "https://ka-perseus-graphie.s3.amazonaws.com/f6491e99d34af34d924bfe0231728ad912068dc3.png",
27969
- defaultCoords: [[0.55, 0.5], [0.75, 0.75]],
27970
- defaultAsymptote: [[0.5, 0], [0.5, 1.0]],
27971
- extraCoordConstraint: function (newCoord, oldCoord, coords, asymptote, graph) {
27972
- const x = asymptote[0][0];
27973
- return _.all(coords, coord => coord[0] !== x) && coords[0][1] !== coords[1][1];
27974
- },
27975
- extraAsymptoteConstraint: function (newCoord, oldCoord, coords, asymptote, graph) {
27976
- const x = newCoord[0];
27977
- const isValid = _.all(coords, coord => coord[0] > x) || _.all(coords, coord => coord[0] < x);
27978
- if (isValid) {
27979
- return [x, oldCoord[1]];
27980
- }
27981
- // Snap the asymptote as close as possible, i.e., if the user moves
27982
- // the mouse really quickly into an invalid region
27983
- const oldX = oldCoord[0];
27984
- const wasLeft = _.all(coords, coord => coord[0] > oldX);
27985
- if (wasLeft) {
27986
- const leftMost = _.min(_.map(coords, coord => coord[0]));
27987
- return [leftMost - graph.snapStep[0], oldCoord[1]];
27988
- }
27989
- const rightMost = _.max(_.map(coords, coord => coord[0]));
27990
- return [rightMost + graph.snapStep[0], oldCoord[1]];
27991
- },
27992
- allowReflectOverAsymptote: true,
27993
- getCoefficients: function (coords, asymptote) {
27994
- // It's easiest to calculate the logarithm's coefficients by thinking
27995
- // about it as the inverse of the exponential, so we flip x and y and
27996
- // perform some algebra on the coefficients. This also unifies the
27997
- // logic between the two 'models'.
27998
- const flip = coord => [coord[1], coord[0]];
27999
- const inverseCoeffs = Exponential.getCoefficients(_.map(coords, flip), _.map(asymptote, flip));
28000
- if (inverseCoeffs) {
28001
- const c = -inverseCoeffs[2] / inverseCoeffs[0];
28002
- const b = 1 / inverseCoeffs[0];
28003
- const a = 1 / inverseCoeffs[1];
28004
- return [a, b, c];
28005
- }
28006
- },
28007
- getFunctionForCoeffs: function (coeffs, x, asymptote) {
28008
- const a = coeffs[0];
28009
- const b = coeffs[1];
28010
- const c = coeffs[2];
28011
- return a * Math.log(b * x + c);
28012
- },
28013
- getEquationString: function (coords, asymptote) {
28014
- if (!asymptote) {
28015
- return null;
28016
- }
28017
- const coeffs = this.getCoefficients(coords, asymptote);
28018
- const a = coeffs[0];
28019
- const b = coeffs[1];
28020
- const c = coeffs[2];
28021
- return "y = ln(" + a.toFixed(3) + "x + " + b.toFixed(3) + ") + " + c.toFixed(3);
28022
- }
28023
- });
28024
- const AbsoluteValue = _.extend({}, PlotDefaults, {
28025
- url: "https://ka-perseus-graphie.s3.amazonaws.com/8256a630175a0cb1d11de223d6de0266daf98721.png",
28026
- defaultCoords: [[0.5, 0.5], [0.75, 0.75]],
28027
- getCoefficients: function (coords) {
28028
- const p1 = coords[0];
28029
- const p2 = coords[1];
28030
- const denom = p2[0] - p1[0];
28031
- const num = p2[1] - p1[1];
28032
- if (denom === 0) {
28033
- return;
28034
- }
28035
- let m = Math.abs(num / denom);
28036
- if (p2[1] < p1[1]) {
28037
- m *= -1;
28038
- }
28039
- const horizontalOffset = p1[0];
28040
- const verticalOffset = p1[1];
28041
- return [m, horizontalOffset, verticalOffset];
28042
- },
28043
- getFunctionForCoeffs: function (coeffs, x) {
28044
- const m = coeffs[0];
28045
- const horizontalOffset = coeffs[1];
28046
- const verticalOffset = coeffs[2];
28047
- return m * Math.abs(x - horizontalOffset) + verticalOffset;
28048
- },
28049
- getEquationString: function (coords) {
28050
- const coeffs = this.getCoefficients(coords);
28051
- const m = coeffs[0];
28052
- const horizontalOffset = coeffs[1];
28053
- const verticalOffset = coeffs[2];
28054
- return "y = " + m.toFixed(3) + "| x - " + horizontalOffset.toFixed(3) + "| + " + verticalOffset.toFixed(3);
28055
- }
28056
- });
28057
-
28058
- /* Utility functions for dealing with graphing interfaces. */
28059
- const functionTypeMapping = {
28060
- linear: Linear,
28061
- quadratic: Quadratic,
28062
- sinusoid: Sinusoid,
28063
- tangent: Tangent,
28064
- exponential: Exponential,
28065
- logarithm: Logarithm,
28066
- absolute_value: AbsoluteValue
28067
- };
28068
- const allTypes = _.keys(functionTypeMapping);
28069
- function functionForType(type) {
28070
- // @ts-expect-error: TypeScript doesn't know how to use deal with generics
28071
- // and conditional types in this way.
28072
- return functionTypeMapping[type];
28073
- }
28074
26756
  const getEquationString = props => {
28075
26757
  const plot = props.plot;
28076
26758
  if (plot.type && plot.coords) {
28077
- const handler = functionForType(plot.type);
26759
+ const handler = GrapherUtil$1.functionForType(plot.type);
28078
26760
  const result = handler.getEquationString(plot.coords, plot.asymptote);
28079
26761
  return result || "";
28080
26762
  }
@@ -28132,7 +26814,7 @@ const defaultPlotProps = (type, graph) => {
28132
26814
  // extra information. It would be valid to always submit the default
28133
26815
  // widget before even reading the question; you can't lose, but you
28134
26816
  // might get a free win.
28135
- const model = functionForType(type);
26817
+ const model = GrapherUtil$1.functionForType(type);
28136
26818
  const defaultAsymptote = "defaultAsymptote" in model ? model.defaultAsymptote : null;
28137
26819
  const gridStep = [1, 1];
28138
26820
  // @ts-expect-error - TS2345 - Argument of type 'number[]' is not assignable to parameter of type '[number, number]'.
@@ -28178,70 +26860,12 @@ const typeToButton = type => {
28178
26860
  value: type,
28179
26861
  title: capitalized,
28180
26862
  content: /*#__PURE__*/React.createElement("img", {
28181
- src: staticUrl(functionForType(type).url),
26863
+ src: staticUrl(GrapherUtil$1.functionForType(type).url),
28182
26864
  alt: capitalized
28183
26865
  })
28184
26866
  };
28185
26867
  };
28186
26868
 
28187
- function getCoefficientsByType(data) {
28188
- if (data.coords == null) {
28189
- return undefined;
28190
- }
28191
- if (data.type === "exponential" || data.type === "logarithm") {
28192
- const grader = functionForType(data.type);
28193
- return grader.getCoefficients(data.coords, data.asymptote);
28194
- } else if (data.type === "linear" || data.type === "quadratic" || data.type === "absolute_value" || data.type === "sinusoid" || data.type === "tangent") {
28195
- const grader = functionForType(data.type);
28196
- return grader.getCoefficients(data.coords);
28197
- } else {
28198
- throw new PerseusError("Invalid grapher type", Errors.InvalidInput);
28199
- }
28200
- }
28201
- function scoreGrapher(userInput, rubric) {
28202
- if (userInput.type !== rubric.correct.type) {
28203
- return {
28204
- type: "points",
28205
- earned: 0,
28206
- total: 1,
28207
- message: null
28208
- };
28209
- }
28210
-
28211
- // We haven't moved the coords
28212
- if (userInput.coords == null) {
28213
- return {
28214
- type: "invalid",
28215
- message: null
28216
- };
28217
- }
28218
-
28219
- // Get new function handler for grading
28220
- const grader = functionForType(userInput.type);
28221
- const guessCoeffs = getCoefficientsByType(userInput);
28222
- const correctCoeffs = getCoefficientsByType(rubric.correct);
28223
- if (guessCoeffs == null || correctCoeffs == null) {
28224
- return {
28225
- type: "invalid",
28226
- message: null
28227
- };
28228
- }
28229
- if (grader.areEqual(guessCoeffs, correctCoeffs)) {
28230
- return {
28231
- type: "points",
28232
- earned: 1,
28233
- total: 1,
28234
- message: null
28235
- };
28236
- }
28237
- return {
28238
- type: "points",
28239
- earned: 0,
28240
- total: 1,
28241
- message: null
28242
- };
28243
- }
28244
-
28245
26869
  // @ts-expect-error - TS2339 - Property 'MovablePoint' does not exist on type 'typeof Graphie'.
28246
26870
  const MovablePoint$3 = Graphie.MovablePoint;
28247
26871
  // @ts-expect-error - TS2339 - Property 'MovableLine' does not exist on type 'typeof Graphie'.
@@ -28289,7 +26913,8 @@ class FunctionGrapher extends React.Component {
28289
26913
  return;
28290
26914
  }
28291
26915
  const functionProps = model.getPropsForCoeffs(coeffs, xRange);
28292
- return /*#__PURE__*/React.createElement(model.Movable, _extends({}, functionProps, {
26916
+ const Movable = movableTypeToComponent[model.movable];
26917
+ return /*#__PURE__*/React.createElement(Movable, _extends({}, functionProps, {
28293
26918
  key: this.props.model.url,
28294
26919
  range: xRange,
28295
26920
  style: style
@@ -28305,8 +26930,8 @@ class FunctionGrapher extends React.Component {
28305
26930
  return asymptote && /*#__PURE__*/React.createElement(MovableLine$2, {
28306
26931
  onMove: (newCoord, oldCoord) => {
28307
26932
  // Calculate and apply displacement
28308
- const delta = vector$1.subtract(newCoord, oldCoord);
28309
- const newAsymptote = _.map(this._asymptote(), coord => vector$1.add(coord, delta));
26933
+ const delta = vector$3.subtract(newCoord, oldCoord);
26934
+ const newAsymptote = _.map(this._asymptote(), coord => vector$3.add(coord, delta));
28310
26935
  this.props.onChange({
28311
26936
  asymptote: newAsymptote
28312
26937
  });
@@ -28317,8 +26942,8 @@ class FunctionGrapher extends React.Component {
28317
26942
  // @ts-expect-error - TS2339 - Property 'constraints' does not exist on type '(graphie: any, movable: any, options: any) => void'.
28318
26943
  Interactive2.MovableLine.constraints.snap(), (newCoord, oldCoord) => {
28319
26944
  // Calculate and apply proposed displacement
28320
- const delta = vector$1.subtract(newCoord, oldCoord);
28321
- const proposedAsymptote = _.map(this._asymptote(), coord => vector$1.add(coord, delta));
26945
+ const delta = vector$3.subtract(newCoord, oldCoord);
26946
+ const proposedAsymptote = _.map(this._asymptote(), coord => vector$3.add(coord, delta));
28322
26947
  // Verify that resulting asymptote is valid for graph
28323
26948
  if (model.extraAsymptoteConstraint) {
28324
26949
  return model.extraAsymptoteConstraint(newCoord, oldCoord, this._coords(), proposedAsymptote, graph);
@@ -28590,7 +27215,7 @@ class Grapher extends React.Component {
28590
27215
  setup: this._setupGraphie
28591
27216
  },
28592
27217
  onChange: this.handlePlotChanges,
28593
- model: type && functionForType(type),
27218
+ model: type && GrapherUtil$1.functionForType(type),
28594
27219
  coords: coords,
28595
27220
  asymptote: asymptote,
28596
27221
  static: this.props.static,
@@ -28827,31 +27452,6 @@ const getPromptJSON$e = () => {
28827
27452
  return getUnsupportedPromptJSON("iframe");
28828
27453
  };
28829
27454
 
28830
- function scoreIframe(userInput) {
28831
- // The iframe can tell us whether it's correct or incorrect,
28832
- // and pass an optional message
28833
- if (userInput.status === "correct") {
28834
- return {
28835
- type: "points",
28836
- earned: 1,
28837
- total: 1,
28838
- message: userInput.message || null
28839
- };
28840
- }
28841
- if (userInput.status === "incorrect") {
28842
- return {
28843
- type: "points",
28844
- earned: 0,
28845
- total: 1,
28846
- message: userInput.message || null
28847
- };
28848
- }
28849
- return {
28850
- type: "invalid",
28851
- message: "Keep going, you're not there yet!"
28852
- };
28853
- }
28854
-
28855
27455
  /**
28856
27456
  * This is an iframe widget. It is used for rendering an iframe that
28857
27457
  * then communicates its state via window.postMessage
@@ -29285,7 +27885,7 @@ class Interaction extends React.Component {
29285
27885
  const xDiff = this._eval("(" + options.endX + ")-(" + options.startX + ")");
29286
27886
  // @ts-expect-error - TS2554 - Expected 2 arguments, but got 1.
29287
27887
  const yDiff = this._eval("(" + options.endY + ")-(" + options.startY + ")");
29288
- const endCoord = vector$1.add(startCoord, [xDiff, yDiff]);
27888
+ const endCoord = vector$3.add(startCoord, [xDiff, yDiff]);
29289
27889
  const variables = _.clone(this.state.variables);
29290
27890
  variables["x_" + options.startSubscript] = startCoord[0];
29291
27891
  variables["y_" + options.startSubscript] = startCoord[1];
@@ -29873,15 +28473,15 @@ class Graph extends React.Component {
29873
28473
  if (this.props.markings === "graph") {
29874
28474
  graphie.graphInit({
29875
28475
  range: range,
29876
- scale: vector$1.map(gridConfig, g => g.scale),
28476
+ scale: vector$3.map(gridConfig, g => g.scale),
29877
28477
  axisArrows: "<->",
29878
28478
  labelFormat: function (s) {
29879
28479
  return "\\small{" + s + "}";
29880
28480
  },
29881
28481
  gridStep: this.props.gridStep,
29882
- tickStep: vector$1.map(gridConfig, g => g.tickStep),
28482
+ tickStep: vector$3.map(gridConfig, g => g.tickStep),
29883
28483
  labelStep: 1,
29884
- unityLabels: vector$1.map(gridConfig, g => g.unityLabel),
28484
+ unityLabels: vector$3.map(gridConfig, g => g.unityLabel),
29885
28485
  isMobile: this.props.isMobile
29886
28486
  });
29887
28487
  graphie.label([0, range[1][1]], labels[1], this.props.isMobile ? "below right" : "above");
@@ -29889,7 +28489,7 @@ class Graph extends React.Component {
29889
28489
  } else if (this.props.markings === "grid") {
29890
28490
  graphie.graphInit({
29891
28491
  range: range,
29892
- scale: vector$1.map(gridConfig, g => g.scale),
28492
+ scale: vector$3.map(gridConfig, g => g.scale),
29893
28493
  gridStep: this.props.gridStep,
29894
28494
  axes: false,
29895
28495
  ticks: false,
@@ -29899,7 +28499,7 @@ class Graph extends React.Component {
29899
28499
  } else if (this.props.markings === "none") {
29900
28500
  graphie.init({
29901
28501
  range: range,
29902
- scale: vector$1.map(gridConfig, g => g.scale),
28502
+ scale: vector$3.map(gridConfig, g => g.scale),
29903
28503
  isMobile: this.props.isMobile
29904
28504
  });
29905
28505
  }
@@ -29972,7 +28572,7 @@ class Graph extends React.Component {
29972
28572
  }
29973
28573
  };
29974
28574
  this._getGridConfig = () => {
29975
- return vector$1.map(this.props.step, (step, i) => Util.gridDimensionConfig(step, this.props.range[i], this.props.box[i], this.props.gridStep[i]));
28575
+ return vector$3.map(this.props.step, (step, i) => Util.gridDimensionConfig(step, this.props.range[i], this.props.box[i], this.props.gridStep[i]));
29976
28576
  };
29977
28577
  this._updateProtractor = () => {
29978
28578
  if (this.protractor) {
@@ -30414,61 +29014,6 @@ function findIntersectionOfRays([[a, b], [c, d]], [[p, q], [r, s]]) {
30414
29014
  }
30415
29015
  }
30416
29016
 
30417
- // This file contains helper functions for working with angles.
30418
-
30419
- function convertDegreesToRadians(degrees) {
30420
- return degrees / 180 * Math.PI;
30421
- }
30422
-
30423
- // Returns a value between -180 and 180, inclusive. The angle is measured
30424
- // between the positive x-axis and the given vector.
30425
- function calculateAngleInDegrees([x, y]) {
30426
- return Math.atan2(y, x) * 180 / Math.PI;
30427
- }
30428
-
30429
- // Converts polar coordinates to cartesian. The th(eta) parameter is in degrees.
30430
- function polar(r, th) {
30431
- if (typeof r === "number") {
30432
- r = [r, r];
30433
- }
30434
- th = th * Math.PI / 180;
30435
- return [r[0] * Math.cos(th), r[1] * Math.sin(th)];
30436
- }
30437
- // This function calculates the angle between two points and an optional vertex.
30438
- // If the vertex is not provided, the angle is measured between the two points.
30439
- // This does not account for reflex angles or clockwise position.
30440
- const getAngleFromVertex = (point, vertex) => {
30441
- const x = point[0] - vertex[0];
30442
- const y = point[1] - vertex[1];
30443
- if (!x && !y) {
30444
- return 0;
30445
- }
30446
- return (180 + Math.atan2(-y, -x) * 180 / Math.PI + 360) % 360;
30447
- };
30448
-
30449
- // This function calculates the clockwise angle between three points,
30450
- // and is used to generate the labels and equation strings of the
30451
- // current angle for the interactive graph.
30452
- const getClockwiseAngle = (coords, allowReflexAngles = false) => {
30453
- const coordsCopy = [...coords];
30454
- // The coords are saved as [point1, vertex, point2] in the interactive graph
30455
- const areClockwise = clockwise$1([coordsCopy[0], coordsCopy[2], coordsCopy[1]]);
30456
-
30457
- // We may need to reverse the coordinates if we allow
30458
- // reflex angles and the points are not in clockwise order.
30459
- const shouldReverseCoords = !areClockwise || allowReflexAngles;
30460
-
30461
- // Reverse the coordinates accordingly to ensure the angle is calculated correctly
30462
- const clockwiseCoords = shouldReverseCoords ? coordsCopy.reverse() : coordsCopy;
30463
-
30464
- // Calculate the angles between the two points and get the difference
30465
- // between the two angles to get the clockwise angle.
30466
- const startAngle = getAngleFromVertex(clockwiseCoords[0], clockwiseCoords[1]);
30467
- const endAngle = getAngleFromVertex(clockwiseCoords[2], clockwiseCoords[1]);
30468
- const angle = (startAngle + 360 - endAngle) % 360;
30469
- return angle;
30470
- };
30471
-
30472
29017
  const defaultGraphConfig = {
30473
29018
  range: [[0, 1], [0, 1]],
30474
29019
  tickStep: [1, 1],
@@ -31591,7 +30136,8 @@ const LockedEllipse = props => {
31591
30136
  return /*#__PURE__*/React.createElement("g", {
31592
30137
  className: "locked-ellipse",
31593
30138
  "aria-label": hasAria ? ariaLabel : undefined,
31594
- "aria-hidden": !hasAria
30139
+ "aria-hidden": !hasAria,
30140
+ role: "img"
31595
30141
  }, /*#__PURE__*/React.createElement(Ellipse, {
31596
30142
  center: center,
31597
30143
  radius: radius,
@@ -31636,12 +30182,13 @@ const LockedFunction = props => {
31636
30182
  return /*#__PURE__*/React.createElement("g", {
31637
30183
  className: "locked-function",
31638
30184
  "aria-label": hasAria ? props.ariaLabel : undefined,
31639
- "aria-hidden": !hasAria
31640
- }, directionalAxis === "x" && /*#__PURE__*/React.createElement(Plot$3.OfX, _extends({
30185
+ "aria-hidden": !hasAria,
30186
+ role: "img"
30187
+ }, directionalAxis === "x" && /*#__PURE__*/React.createElement(Plot$2.OfX, _extends({
31641
30188
  y: x => equation.eval({
31642
30189
  x
31643
30190
  })
31644
- }, plotProps)), directionalAxis === "y" && /*#__PURE__*/React.createElement(Plot$3.OfY, _extends({
30191
+ }, plotProps)), directionalAxis === "y" && /*#__PURE__*/React.createElement(Plot$2.OfY, _extends({
31645
30192
  x: y => equation.eval({
31646
30193
  y
31647
30194
  })
@@ -31667,6 +30214,9 @@ function SVGLine(props) {
31667
30214
  });
31668
30215
  }
31669
30216
 
30217
+ const {
30218
+ calculateAngleInDegrees: calculateAngleInDegrees$3
30219
+ } = angles;
31670
30220
  function Vector(props) {
31671
30221
  const {
31672
30222
  tail,
@@ -31676,7 +30226,7 @@ function Vector(props) {
31676
30226
  } = props;
31677
30227
  const [tailPx, tipPx] = useTransformVectorsToPixels(tail, tip);
31678
30228
  const direction = vec.sub(tipPx, tailPx);
31679
- const angle = calculateAngleInDegrees(direction);
30229
+ const angle = calculateAngleInDegrees$3(direction);
31680
30230
  return /*#__PURE__*/React.createElement("g", {
31681
30231
  style: {
31682
30232
  stroke: color,
@@ -31693,6 +30243,14 @@ function Vector(props) {
31693
30243
  }));
31694
30244
  }
31695
30245
 
30246
+ function srFormatNumber(a, locale) {
30247
+ // adding zero here converts negative zero to positive zero.
30248
+ return (0 + a).toLocaleString(locale, {
30249
+ maximumFractionDigits: 3,
30250
+ useGrouping: false // no thousands separators
30251
+ });
30252
+ }
30253
+
31696
30254
  /**
31697
30255
  * Given a ray and a rectangular box, find the point where the ray intersects
31698
30256
  * the edge of the box. Assumes the `initialPoint` is inside the box.
@@ -31738,7 +30296,45 @@ function getArrayWithoutDuplicates(array) {
31738
30296
  }
31739
30297
  return returnArray;
31740
30298
  }
30299
+ function getSlopeStringForLine(line, strings) {
30300
+ const slope = (line[1][1] - line[0][1]) / (line[1][0] - line[0][0]);
30301
+ if (!Number.isFinite(slope)) {
30302
+ return strings.srLinearGraphSlopeVertical;
30303
+ }
30304
+ if (slope === 0) {
30305
+ return strings.srLinearGraphSlopeHorizontal;
30306
+ }
30307
+ return slope > 0 ? strings.srLinearGraphSlopeIncreasing : strings.srLinearGraphSlopeDecreasing;
30308
+ }
30309
+ function getInterceptStringForLine(line, strings, locale) {
30310
+ const slope = (line[1][1] - line[0][1]) / (line[1][0] - line[0][0]);
30311
+ const xIntercept = (0 - line[0][1]) / slope + line[0][0];
30312
+ const yIntercept = line[0][1] - slope * line[0][0];
31741
30313
 
30314
+ // Check if the line fully overlaps with an axis.
30315
+ const overlapsXAxis = line[0][1] === 0 && line[1][1] === 0;
30316
+ const overlapsYAxis = line[0][0] === 0 && line[1][0] === 0;
30317
+ const hasXIntercept = Number.isFinite(xIntercept) && !overlapsXAxis;
30318
+ const hasYIntercept = Number.isFinite(yIntercept) && !overlapsYAxis;
30319
+ if (hasXIntercept && hasYIntercept) {
30320
+ // Describe both intercepts in the same sentence.
30321
+ return xIntercept === 0 && yIntercept === 0 ? strings.srLinearGraphOriginIntercept : strings.srLinearGraphBothIntercepts({
30322
+ xIntercept: srFormatNumber(xIntercept, locale),
30323
+ yIntercept: srFormatNumber(yIntercept, locale)
30324
+ });
30325
+ }
30326
+
30327
+ // Describe only one intercept.
30328
+ return hasXIntercept ? strings.srLinearGraphXOnlyIntercept({
30329
+ xIntercept: srFormatNumber(xIntercept, locale)
30330
+ }) : strings.srLinearGraphYOnlyIntercept({
30331
+ yIntercept: srFormatNumber(yIntercept, locale)
30332
+ });
30333
+ }
30334
+
30335
+ const {
30336
+ calculateAngleInDegrees: calculateAngleInDegrees$2
30337
+ } = angles;
31742
30338
  const LockedLine = props => {
31743
30339
  const {
31744
30340
  color: color$1,
@@ -31770,7 +30366,7 @@ const LockedLine = props => {
31770
30366
  // eslint-disable-next-line react-hooks/rules-of-hooks
31771
30367
  const [tailPx, tipPx] = useTransformVectorsToPixels(point2.coord, point1.coord);
31772
30368
  const direction = vec.sub(tailPx, tipPx);
31773
- let angle = calculateAngleInDegrees(direction);
30369
+ let angle = calculateAngleInDegrees$2(direction);
31774
30370
  const startArrowHead = kind !== "segment" && /*#__PURE__*/React.createElement(Arrowhead, {
31775
30371
  angle: angle,
31776
30372
  tip: arrowTip,
@@ -31793,7 +30389,8 @@ const LockedLine = props => {
31793
30389
  return /*#__PURE__*/React.createElement("g", {
31794
30390
  className: kind === "ray" ? "locked-ray" : "locked-line",
31795
30391
  "aria-label": hasAria ? ariaLabel : undefined,
31796
- "aria-hidden": !hasAria
30392
+ "aria-hidden": !hasAria,
30393
+ role: "img"
31797
30394
  }, line, showPoint1 && /*#__PURE__*/React.createElement(Point$2, {
31798
30395
  x: point1.coord[X],
31799
30396
  y: point1.coord[Y],
@@ -31829,7 +30426,8 @@ const LockedPoint = props => {
31829
30426
  return /*#__PURE__*/React.createElement("g", {
31830
30427
  className: "locked-point",
31831
30428
  "aria-label": hasAria ? ariaLabel : undefined,
31832
- "aria-hidden": !hasAria
30429
+ "aria-hidden": !hasAria,
30430
+ role: "img"
31833
30431
  }, /*#__PURE__*/React.createElement(Point$2, {
31834
30432
  x: x,
31835
30433
  y: y,
@@ -31855,7 +30453,8 @@ const LockedPolygon = props => {
31855
30453
  return /*#__PURE__*/React.createElement("g", {
31856
30454
  className: "locked-polygon",
31857
30455
  "aria-label": hasAria ? props.ariaLabel : undefined,
31858
- "aria-hidden": !hasAria
30456
+ "aria-hidden": !hasAria,
30457
+ role: "img"
31859
30458
  }, /*#__PURE__*/React.createElement(Polygon, {
31860
30459
  points: [...points],
31861
30460
  fillOpacity: lockedFigureFillStyles[fillStyle],
@@ -31889,7 +30488,8 @@ const LockedVector = props => {
31889
30488
  return /*#__PURE__*/React.createElement("g", {
31890
30489
  className: "locked-vector",
31891
30490
  "aria-label": hasAria ? ariaLabel : undefined,
31892
- "aria-hidden": !hasAria
30491
+ "aria-hidden": !hasAria,
30492
+ role: "img"
31893
30493
  }, /*#__PURE__*/React.createElement(Vector, {
31894
30494
  tail: tail,
31895
30495
  tip: tip,
@@ -32176,6 +30776,12 @@ const SvgDefs = () => /*#__PURE__*/React__default.createElement("defs", null, /*
32176
30776
  in: "SourceGraphic"
32177
30777
  })));
32178
30778
 
30779
+ const {
30780
+ clockwise: clockwise$1
30781
+ } = geometry;
30782
+ const {
30783
+ getAngleFromVertex: getAngleFromVertex$1
30784
+ } = angles;
32179
30785
  const PolygonAngle = ({
32180
30786
  centerPoint,
32181
30787
  endPoints,
@@ -32264,8 +30870,8 @@ const Angle = ({
32264
30870
  const clockwiseCoords = shouldReverseCoords ? coords : coords.reverse();
32265
30871
 
32266
30872
  // Calculate the angles between the two points
32267
- const startAngle = getAngleFromVertex(clockwiseCoords[0], vertex);
32268
- const endAngle = getAngleFromVertex(clockwiseCoords[1], vertex);
30873
+ const startAngle = getAngleFromVertex$1(clockwiseCoords[0], vertex);
30874
+ const endAngle = getAngleFromVertex$1(clockwiseCoords[1], vertex);
32269
30875
  const angle = (startAngle + 360 - endAngle) % 360;
32270
30876
 
32271
30877
  // Check if the angle is reflexive
@@ -32621,14 +31227,6 @@ function range(min, max, step = 1) {
32621
31227
  return result;
32622
31228
  }
32623
31229
 
32624
- function srFormatNumber(a, locale) {
32625
- // adding zero here converts negative zero to positive zero.
32626
- return (0 + a).toLocaleString(locale, {
32627
- maximumFractionDigits: 3,
32628
- useGrouping: false // no thousands separators
32629
- });
32630
- }
32631
-
32632
31230
  // The hitbox size of 48px by 48px is preserved from the legacy interactive
32633
31231
  // graph.
32634
31232
  const hitboxSizePx = 48;
@@ -33016,6 +31614,11 @@ const MovablePoint$1 = /*#__PURE__*/React.forwardRef((props, pointRef) => {
33016
31614
  return /*#__PURE__*/React.createElement(React.Fragment, null, focusableHandle, visiblePoint);
33017
31615
  });
33018
31616
 
31617
+ const {
31618
+ calculateAngleInDegrees: calculateAngleInDegrees$1,
31619
+ getClockwiseAngle: getClockwiseAngle$2,
31620
+ polar: polar$1
31621
+ } = angles;
33019
31622
  function renderAngleGraph(state, dispatch) {
33020
31623
  return {
33021
31624
  graph: /*#__PURE__*/React.createElement(AngleGraph, {
@@ -33085,7 +31688,7 @@ function AngleGraph(props) {
33085
31688
  strings,
33086
31689
  locale
33087
31690
  } = usePerseusI18n();
33088
- const angleMeasure = srFormatNumber(getClockwiseAngle([endPoints[0], centerPoint, endPoints[1]], allowReflexAngles), locale);
31691
+ const angleMeasure = srFormatNumber(getClockwiseAngle$2([endPoints[0], centerPoint, endPoints[1]], allowReflexAngles), locale);
33089
31692
  const wholeAngleAriaLabel = strings.srAngleGraphAriaLabel;
33090
31693
  const wholeAngleDescription = strings.srAngleGraphAriaDescription({
33091
31694
  angleMeasure,
@@ -33149,9 +31752,7 @@ function AngleGraph(props) {
33149
31752
  ariaLabel: initialSideAriaLabel
33150
31753
  }), /*#__PURE__*/React.createElement("g", {
33151
31754
  id: "angle-description",
33152
- style: {
33153
- display: "hidden"
33154
- }
31755
+ style: a11y.srOnly
33155
31756
  }, wholeAngleDescription));
33156
31757
  }
33157
31758
  const positiveX = [1, 0];
@@ -33160,7 +31761,7 @@ const positiveY = [0, 1];
33160
31761
  const negativeY = [0, -1];
33161
31762
  function getAngleSideConstraint(sidePoint, vertex, snapDegrees) {
33162
31763
  var _findIntersectionOfRa, _findIntersectionOfRa2, _findIntersectionOfRa3, _findIntersectionOfRa4;
33163
- const currentAngle = calculateAngleInDegrees(vec.sub(sidePoint, vertex));
31764
+ const currentAngle = calculateAngleInDegrees$1(vec.sub(sidePoint, vertex));
33164
31765
 
33165
31766
  // Find the rays that start at the current point and point up, down, left
33166
31767
  // and right.
@@ -33177,8 +31778,8 @@ function getAngleSideConstraint(sidePoint, vertex, snapDegrees) {
33177
31778
 
33178
31779
  // find the rays that start from the vertex and point in the direction of
33179
31780
  // the angles we just computed.
33180
- const counterClockwiseRay = [vertex, vec.add(vertex, polar(1, oneStepCounterClockwise))];
33181
- const clockwiseRay = [vertex, vec.add(vertex, polar(1, oneStepClockwise))];
31781
+ const counterClockwiseRay = [vertex, vec.add(vertex, polar$1(1, oneStepCounterClockwise))];
31782
+ const clockwiseRay = [vertex, vec.add(vertex, polar$1(1, oneStepClockwise))];
33182
31783
 
33183
31784
  // find the intersections of those rays with the horizontal and vertical
33184
31785
  // rays extending from the sidePoint. These intersections are the points to
@@ -33195,6 +31796,9 @@ function getAngleSideConstraint(sidePoint, vertex, snapDegrees) {
33195
31796
  };
33196
31797
  }
33197
31798
 
31799
+ const {
31800
+ clockwise
31801
+ } = geometry;
33198
31802
  function getGradableGraph(state, initialGraph) {
33199
31803
  if (!state.hasBeenInteractedWith) {
33200
31804
  return _extends({}, initialGraph);
@@ -33263,7 +31867,7 @@ function getGradableGraph(state, initialGraph) {
33263
31867
  // by the order of the points in the coords array, and we want to maintain
33264
31868
  // the same angle scoring with the legacy graph (which had a bug).
33265
31869
  // (LEMS-2190): When we remove the legacy graph, move this logic to the scoring function.
33266
- const areClockwise = clockwise$1([state.coords[0], state.coords[2], state.coords[1]]);
31870
+ const areClockwise = clockwise([state.coords[0], state.coords[2], state.coords[1]]);
33267
31871
  const shouldReverseCoords = areClockwise && !state.allowReflexAngles;
33268
31872
  const coords = shouldReverseCoords ? state.coords.slice().reverse() : state.coords;
33269
31873
  return _extends({}, initialGraph, {
@@ -33375,14 +31979,10 @@ function CircleGraph(props) {
33375
31979
  }
33376
31980
  }), /*#__PURE__*/React.createElement("g", {
33377
31981
  id: radiusId,
33378
- style: {
33379
- display: "hidden"
33380
- }
31982
+ style: a11y.srOnly
33381
31983
  }, srCircleRadius), /*#__PURE__*/React.createElement("g", {
33382
31984
  id: outerPointsId,
33383
- style: {
33384
- display: "hidden"
33385
- }
31985
+ style: a11y.srOnly
33386
31986
  }, srCircleOuterPoints));
33387
31987
  }
33388
31988
  function MovableCircle(props) {
@@ -33567,49 +32167,16 @@ const LinearGraph = (props, key) => {
33567
32167
  point2X: srFormatNumber(line[1][0], locale),
33568
32168
  point2Y: srFormatNumber(line[1][1], locale)
33569
32169
  });
33570
-
33571
- // Slope description
33572
- const slope = (line[1][1] - line[0][1]) / (line[1][0] - line[0][0]);
33573
- let slopeString = "";
33574
- if (slope === Infinity || slope === -Infinity) {
33575
- slopeString = strings.srLinearGraphSlopeVertical;
33576
- } else if (slope === 0) {
33577
- slopeString = strings.srLinearGraphSlopeHorizontal;
33578
- } else {
33579
- slopeString = slope > 0 ? strings.srLinearGraphSlopeIncreasing : strings.srLinearGraphSlopeDecreasing;
33580
- }
33581
-
33582
- // Intersection description
33583
- const xIntercept = (0 - line[0][1]) / slope + line[0][0];
33584
- const yIntercept = line[0][1] - slope * line[0][0];
33585
- const hasXIntercept = xIntercept !== Infinity && xIntercept !== -Infinity;
33586
- const hasYIntercept = yIntercept !== Infinity && yIntercept !== -Infinity;
33587
- let interceptString;
33588
- if (hasXIntercept && hasYIntercept) {
33589
- // Describe both intercepts in the same sentence.
33590
- interceptString = xIntercept === 0 && yIntercept === 0 ? strings.srLinearGraphOriginIntercept : strings.srLinearGraphBothIntercepts({
33591
- xIntercept: srFormatNumber(xIntercept, locale),
33592
- yIntercept: srFormatNumber(yIntercept, locale)
33593
- });
33594
- } else {
33595
- // Describe only one intercept.
33596
- interceptString = hasXIntercept ? strings.srLinearGraphXOnlyIntercept({
33597
- xIntercept: srFormatNumber(xIntercept, locale)
33598
- }) : strings.srLinearGraphYOnlyIntercept({
33599
- yIntercept: srFormatNumber(yIntercept, locale)
33600
- });
33601
- }
32170
+ const slopeString = getSlopeStringForLine(line, strings);
32171
+ const interceptString = getInterceptStringForLine(line, strings, locale);
33602
32172
 
33603
32173
  // Linear graphs only have one line
33604
32174
  // (LEMS-2050): Update the reducer so that we have a separate action for moving one line
33605
32175
  // and another action for moving multiple lines
33606
32176
  return /*#__PURE__*/React.createElement("g", {
33607
- className: "linear-graph-container"
33608
32177
  // Outer line minimal description
33609
- ,
33610
32178
  "aria-label": strings.srLinearGraph,
33611
- "aria-describedby": `${pointsDescriptionId} ${interceptDescriptionId} ${slopeDescriptionId}`,
33612
- role: "figure"
32179
+ "aria-describedby": `${pointsDescriptionId} ${interceptDescriptionId} ${slopeDescriptionId}`
33613
32180
  }, /*#__PURE__*/React.createElement(MovableLine, {
33614
32181
  key: 0,
33615
32182
  ariaLabels: {
@@ -33628,19 +32195,13 @@ const LinearGraph = (props, key) => {
33628
32195
  color: "var(--movable-line-stroke-color)"
33629
32196
  }), /*#__PURE__*/React.createElement("g", {
33630
32197
  id: pointsDescriptionId,
33631
- style: {
33632
- display: "hidden"
33633
- }
32198
+ style: a11y.srOnly
33634
32199
  }, linearGraphPointsDescription), /*#__PURE__*/React.createElement("g", {
33635
32200
  id: interceptDescriptionId,
33636
- style: {
33637
- display: "hidden"
33638
- }
32201
+ style: a11y.srOnly
33639
32202
  }, interceptString), /*#__PURE__*/React.createElement("g", {
33640
32203
  id: slopeDescriptionId,
33641
- style: {
33642
- display: "hidden"
33643
- }
32204
+ style: a11y.srOnly
33644
32205
  }, slopeString));
33645
32206
  };
33646
32207
 
@@ -33650,7 +32211,9 @@ function renderLinearSystemGraph(state, dispatch) {
33650
32211
  graphState: state,
33651
32212
  dispatch: dispatch
33652
32213
  }),
33653
- interactiveElementsDescription: null
32214
+ interactiveElementsDescription: /*#__PURE__*/React.createElement(LinearSystemGraphDescription, {
32215
+ state: state
32216
+ })
33654
32217
  };
33655
32218
  }
33656
32219
  const LinearSystemGraph = props => {
@@ -33660,9 +32223,53 @@ const LinearSystemGraph = props => {
33660
32223
  const {
33661
32224
  coords: lines
33662
32225
  } = props.graphState;
33663
- return /*#__PURE__*/React.createElement(React.Fragment, null, lines == null ? void 0 : lines.map((line, i) => /*#__PURE__*/React.createElement(MovableLine, {
32226
+ const {
32227
+ strings,
32228
+ locale
32229
+ } = usePerseusI18n();
32230
+ const id = React.useId();
32231
+ const linesAriaInfo = lines.map((line, i) => {
32232
+ return {
32233
+ pointsDescriptionId: `${id}-line${i + 1}-points`,
32234
+ interceptDescriptionId: `${id}-line${i + 1}-intercept`,
32235
+ slopeDescriptionId: `${id}-line${i + 1}-slope`,
32236
+ pointsDescription: strings.srLinearSystemPoints({
32237
+ lineNumber: i + 1,
32238
+ point1X: srFormatNumber(line[0][0], locale),
32239
+ point1Y: srFormatNumber(line[0][1], locale),
32240
+ point2X: srFormatNumber(line[1][0], locale),
32241
+ point2Y: srFormatNumber(line[1][1], locale)
32242
+ }),
32243
+ interceptDescription: getInterceptStringForLine(line, strings, locale),
32244
+ slopeDescription: getSlopeStringForLine(line, strings)
32245
+ };
32246
+ });
32247
+ return /*#__PURE__*/React.createElement("g", {
32248
+ "aria-label": strings.srLinearSystemGraph,
32249
+ "aria-describedby": linesAriaInfo.map(({
32250
+ pointsDescriptionId,
32251
+ interceptDescriptionId,
32252
+ slopeDescriptionId
32253
+ }) => `${pointsDescriptionId} ${interceptDescriptionId} ${slopeDescriptionId}`).join(" ")
32254
+ }, lines == null ? void 0 : lines.map((line, i) => /*#__PURE__*/React.createElement(MovableLine, {
33664
32255
  key: i,
33665
32256
  points: line,
32257
+ ariaLabels: {
32258
+ point1AriaLabel: strings.srLinearSystemPoint({
32259
+ lineNumber: i + 1,
32260
+ pointSequence: 1,
32261
+ x: srFormatNumber(line[0][0], locale),
32262
+ y: srFormatNumber(line[0][1], locale)
32263
+ }),
32264
+ point2AriaLabel: strings.srLinearSystemPoint({
32265
+ lineNumber: i + 1,
32266
+ pointSequence: 2,
32267
+ x: srFormatNumber(line[1][0], locale),
32268
+ y: srFormatNumber(line[1][1], locale)
32269
+ }),
32270
+ grabHandleAriaLabel: `${linesAriaInfo[i].interceptDescription} ${linesAriaInfo[i].slopeDescription}`
32271
+ },
32272
+ ariaDescribedBy: `${linesAriaInfo[i].interceptDescriptionId} ${linesAriaInfo[i].slopeDescriptionId}`,
33666
32273
  onMoveLine: delta => {
33667
32274
  dispatch(actions.linearSystem.moveLine(i, delta));
33668
32275
  },
@@ -33672,8 +32279,63 @@ const LinearSystemGraph = props => {
33672
32279
  },
33673
32280
  onMovePoint: (endpointIndex, destination) => dispatch(actions.linearSystem.movePointInFigure(i, endpointIndex, destination)),
33674
32281
  color: "var(--movable-line-stroke-color)"
33675
- })), ";");
32282
+ })), linesAriaInfo.map(({
32283
+ pointsDescriptionId,
32284
+ interceptDescriptionId,
32285
+ slopeDescriptionId,
32286
+ pointsDescription,
32287
+ interceptDescription,
32288
+ slopeDescription
32289
+ }) => /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("g", {
32290
+ key: pointsDescriptionId,
32291
+ id: pointsDescriptionId,
32292
+ style: a11y.srOnly
32293
+ }, pointsDescription), /*#__PURE__*/React.createElement("g", {
32294
+ key: interceptDescriptionId,
32295
+ id: interceptDescriptionId,
32296
+ style: a11y.srOnly
32297
+ }, interceptDescription), /*#__PURE__*/React.createElement("g", {
32298
+ key: slopeDescriptionId,
32299
+ id: slopeDescriptionId,
32300
+ style: a11y.srOnly
32301
+ }, slopeDescription))));
33676
32302
  };
32303
+ function LinearSystemGraphDescription({
32304
+ state
32305
+ }) {
32306
+ // The reason that LinearSystemGraphDescription is a component (rather
32307
+ // than a function that returns a string) is because it needs to use a
32308
+ // hook: `usePerseusI18n`.
32309
+ const i18n = usePerseusI18n();
32310
+ return describeLinearSystemGraph(state, i18n);
32311
+ }
32312
+
32313
+ // Exported for testing
32314
+ function describeLinearSystemGraph(state, i18n) {
32315
+ const {
32316
+ strings,
32317
+ locale
32318
+ } = i18n;
32319
+ const {
32320
+ coords: lines
32321
+ } = state;
32322
+ const graphDescription = strings.srLinearSystemGraph;
32323
+ const lineDescriptions = lines.map((line, i) => {
32324
+ const point1 = line[0];
32325
+ const point2 = line[1];
32326
+ return strings.srLinearSystemPoints({
32327
+ lineNumber: i + 1,
32328
+ point1X: srFormatNumber(point1[0], locale),
32329
+ point1Y: srFormatNumber(point1[1], locale),
32330
+ point2X: srFormatNumber(point2[0], locale),
32331
+ point2Y: srFormatNumber(point2[1], locale)
32332
+ });
32333
+ });
32334
+ const allDescriptions = [graphDescription, ...lineDescriptions];
32335
+ return strings.srInteractiveElements({
32336
+ elements: allDescriptions.join(" ")
32337
+ });
32338
+ }
33677
32339
 
33678
32340
  function renderPointGraph(state, dispatch) {
33679
32341
  return {
@@ -34112,7 +32774,7 @@ function QuadraticGraph(props) {
34112
32774
  // to content creators the currently selected "correct answer". ex: y = 0.200x^2 + 0.000x + 0.000
34113
32775
  // While we should technically never have invalid coordinates, we want to ensure that we have a fallback
34114
32776
  const coeffRef = React.useRef([0, 0, 0]);
34115
- const coeffs = getQuadraticCoefficients$1(coords);
32777
+ const coeffs = getQuadraticCoefficients$2(coords);
34116
32778
  if (coeffs !== undefined) {
34117
32779
  coeffRef.current = coeffs;
34118
32780
  }
@@ -34122,7 +32784,7 @@ function QuadraticGraph(props) {
34122
32784
 
34123
32785
  // Calculate the y value based on the current x value
34124
32786
  const y = x => (a * x + b) * x + c;
34125
- return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Plot$3.OfX, {
32787
+ return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Plot$2.OfX, {
34126
32788
  y: y,
34127
32789
  color: color.blue
34128
32790
  }), coords.map((coord, i) => /*#__PURE__*/React.createElement(MovablePoint$1, {
@@ -34137,7 +32799,7 @@ function QuadraticGraph(props) {
34137
32799
  // These equations were originally set up in 2013 and may require some
34138
32800
  // additional comments to help clarify the quadratic formula manipulations
34139
32801
  // Origin: https://phabricator.khanacademy.org/D2413
34140
- const getQuadraticCoefficients$1 = coords => {
32802
+ const getQuadraticCoefficients$2 = coords => {
34141
32803
  const p1 = coords[0];
34142
32804
  const p2 = coords[1];
34143
32805
  const p3 = coords[2];
@@ -34160,7 +32822,9 @@ function renderRayGraph(state, dispatch) {
34160
32822
  graphState: state,
34161
32823
  dispatch: dispatch
34162
32824
  }),
34163
- interactiveElementsDescription: null
32825
+ interactiveElementsDescription: /*#__PURE__*/React.createElement(RayGraphDescription, {
32826
+ state: state
32827
+ })
34164
32828
  };
34165
32829
  }
34166
32830
  const RayGraph = props => {
@@ -34172,18 +32836,103 @@ const RayGraph = props => {
34172
32836
  } = props.graphState;
34173
32837
  const handleMoveLine = delta => dispatch(actions.ray.moveRay(delta));
34174
32838
  const handleMovePoint = (pointIndex, newPoint) => dispatch(actions.ray.movePoint(pointIndex, newPoint));
32839
+ const {
32840
+ strings,
32841
+ locale
32842
+ } = usePerseusI18n();
32843
+ const id = React.useId();
32844
+ const pointsDescriptionId = id + "-points";
32845
+
32846
+ // Aria label strings
32847
+ const {
32848
+ srRayGraph,
32849
+ srRayPoints,
32850
+ srRayEndpoint,
32851
+ srRayTerminalPoint,
32852
+ srRayGrabHandle
32853
+ } = describeRayGraph(props.graphState, {
32854
+ strings,
32855
+ locale
32856
+ });
34175
32857
 
34176
32858
  // Ray graphs only have one line
34177
- return /*#__PURE__*/React.createElement(MovableLine, {
32859
+ return /*#__PURE__*/React.createElement("g", {
32860
+ // Outer line minimal description
32861
+ "aria-label": srRayGraph,
32862
+ "aria-describedby": pointsDescriptionId
32863
+ }, /*#__PURE__*/React.createElement(MovableLine, {
34178
32864
  points: line,
32865
+ ariaLabels: {
32866
+ point1AriaLabel: srRayEndpoint,
32867
+ point2AriaLabel: srRayTerminalPoint,
32868
+ grabHandleAriaLabel: srRayGrabHandle
32869
+ },
34179
32870
  onMoveLine: handleMoveLine,
34180
32871
  onMovePoint: handleMovePoint,
34181
32872
  extend: {
34182
32873
  start: false,
34183
32874
  end: true
34184
32875
  }
34185
- });
32876
+ }), /*#__PURE__*/React.createElement("g", {
32877
+ id: pointsDescriptionId,
32878
+ style: a11y.srOnly
32879
+ }, srRayPoints));
34186
32880
  };
32881
+ function RayGraphDescription({
32882
+ state
32883
+ }) {
32884
+ // The reason that RayGraphDescription is a component (rather than a
32885
+ // function that returns a string) is because it needs to use a
32886
+ // hook: `usePerseusI18n`.
32887
+ const i18n = usePerseusI18n();
32888
+ const strings = describeRayGraph(state, i18n);
32889
+ return strings.srRayInteractiveElement;
32890
+ }
32891
+
32892
+ // Exported for testing
32893
+ function describeRayGraph(state, i18n) {
32894
+ const {
32895
+ coords: line
32896
+ } = state;
32897
+ const {
32898
+ strings,
32899
+ locale
32900
+ } = i18n;
32901
+
32902
+ // Aria label strings
32903
+ const srRayGraph = strings.srRayGraph;
32904
+ const srRayPoints = strings.srRayPoints({
32905
+ point1X: srFormatNumber(line[0][0], locale),
32906
+ point1Y: srFormatNumber(line[0][1], locale),
32907
+ point2X: srFormatNumber(line[1][0], locale),
32908
+ point2Y: srFormatNumber(line[1][1], locale)
32909
+ });
32910
+ const srRayEndpoint = strings.srRayEndpoint({
32911
+ x: srFormatNumber(line[0][0], locale),
32912
+ y: srFormatNumber(line[0][1], locale)
32913
+ });
32914
+ const srRayTerminalPoint = strings.srRayTerminalPoint({
32915
+ x: srFormatNumber(line[1][0], locale),
32916
+ y: srFormatNumber(line[1][1], locale)
32917
+ });
32918
+ const srRayGrabHandle = strings.srRayGrabHandle({
32919
+ point1X: srFormatNumber(line[0][0], locale),
32920
+ point1Y: srFormatNumber(line[0][1], locale),
32921
+ point2X: srFormatNumber(line[1][0], locale),
32922
+ point2Y: srFormatNumber(line[1][1], locale)
32923
+ });
32924
+ const srRayInteractiveElement = strings.srInteractiveElements({
32925
+ elements: [srRayGraph, srRayPoints].join(" ")
32926
+ });
32927
+ return {
32928
+ srRayGraph,
32929
+ srRayPoints,
32930
+ srRayEndpoint,
32931
+ srRayTerminalPoint,
32932
+ srRayGrabHandle,
32933
+ srRayInteractiveElement
32934
+ };
32935
+ }
34187
32936
 
34188
32937
  function renderSegmentGraph(state, dispatch) {
34189
32938
  return {
@@ -34251,7 +33000,7 @@ function SinusoidGraph(props) {
34251
33000
  if (coeffs !== undefined) {
34252
33001
  coeffRef.current = coeffs;
34253
33002
  }
34254
- return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Plot$3.OfX, {
33003
+ return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Plot$2.OfX, {
34255
33004
  y: x => computeSine(x, coeffRef.current),
34256
33005
  color: color.blue
34257
33006
  }), coords.map((coord, i) => /*#__PURE__*/React.createElement(MovablePoint$1, {
@@ -34298,6 +33047,10 @@ const getSinusoidCoefficients$1 = coords => {
34298
33047
  };
34299
33048
  };
34300
33049
 
33050
+ const {
33051
+ calculateAngleInDegrees,
33052
+ convertDegreesToRadians
33053
+ } = angles;
34301
33054
  const protractorImage = "https://ka-perseus-graphie.s3.amazonaws.com/e9d032f2ab8b95979f674fbfa67056442ba1ff6a.png";
34302
33055
 
34303
33056
  // The vector from the center of the protractor to the top left corner of the
@@ -34999,6 +33752,10 @@ function mafsStateToInteractiveGraph(state, originalGraph) {
34999
33752
  }
35000
33753
  }
35001
33754
 
33755
+ const {
33756
+ magnitude: magnitude$2,
33757
+ vector: vector$2
33758
+ } = geometry;
35002
33759
  function initializeGraphState(params) {
35003
33760
  var _graph$snapTo;
35004
33761
  const {
@@ -35278,7 +34035,7 @@ const getAngleCoords = params => {
35278
34035
  defaultCoords = normalizePoints(range, step, defaultCoords, true);
35279
34036
 
35280
34037
  // @ts-expect-error - TS2345 - Argument of type 'number[]' is not assignable to parameter of type 'readonly Coord[]'. | TS2556 - A spread argument must either have a tuple type or be passed to a rest parameter.
35281
- const radius = magnitude(vector(...defaultCoords));
34038
+ const radius = magnitude$2(vector$2(...defaultCoords));
35282
34039
 
35283
34040
  // We're adding a placeholder for the third point to appease ts and so that we
35284
34041
  // can calculate it after we've adjusted the first point by the angleOffsetDeg
@@ -35292,6 +34049,24 @@ const getAngleCoords = params => {
35292
34049
  return coords;
35293
34050
  };
35294
34051
 
34052
+ const {
34053
+ getAngleFromVertex,
34054
+ getClockwiseAngle: getClockwiseAngle$1,
34055
+ polar
34056
+ } = angles;
34057
+ const {
34058
+ angleMeasures: angleMeasures$1,
34059
+ ccw: ccw$1,
34060
+ lawOfCosines: lawOfCosines$1,
34061
+ magnitude: magnitude$1,
34062
+ polygonSidesIntersect,
34063
+ reverseVector,
34064
+ sign: sign$1,
34065
+ vector: vector$1
34066
+ } = geometry;
34067
+ const {
34068
+ getQuadraticCoefficients: getQuadraticCoefficients$1
34069
+ } = coefficients;
35295
34070
  const minDistanceBetweenAngleVertexAndSidePoint = 2;
35296
34071
  function interactiveGraphReducer(state, action) {
35297
34072
  switch (action.type) {
@@ -35810,11 +34585,9 @@ const getChange = (coords, delta, constraintOpts) => {
35810
34585
  const [dx, dy] = getDeltaVertex(maxMoves, minMoves, delta);
35811
34586
  return [dx, dy];
35812
34587
  };
35813
- const eq$2 = Util.eq;
35814
-
35815
34588
  // Less than or approximately equal
35816
34589
  function leq$1(a, b) {
35817
- return a < b || eq$2(a, b);
34590
+ return a < b || approximateEqual(a, b);
35818
34591
  }
35819
34592
  function boundAndSnapToGrid(point, {
35820
34593
  snapStep,
@@ -35958,7 +34731,7 @@ function boundAndSnapToPolygonAngle(destinationPoint, {
35958
34731
  });
35959
34732
 
35960
34733
  // Gets the radian angles between the coords and maps them to degrees
35961
- const angles = angleMeasures(coordsCopy).map(angle => angle * 180 / Math.PI);
34734
+ const angles = angleMeasures$1(coordsCopy).map(angle => angle * 180 / Math.PI);
35962
34735
 
35963
34736
  // Gets the relative index of a point
35964
34737
  const rel = j => {
@@ -35970,7 +34743,7 @@ function boundAndSnapToPolygonAngle(destinationPoint, {
35970
34743
  angles[rel(j)] = Math.round(angles[rel(j)]);
35971
34744
  });
35972
34745
  const getAngle = function getAngle(a, vertex, b) {
35973
- const angle = getClockwiseAngle([coordsCopy[rel(a)], coordsCopy[rel(vertex)], coordsCopy[rel(b)]]);
34746
+ const angle = getClockwiseAngle$1([coordsCopy[rel(a)], coordsCopy[rel(vertex)], coordsCopy[rel(b)]]);
35974
34747
  return angle;
35975
34748
  };
35976
34749
  const innerAngles = [angles[rel(-1)] - getAngle(-2, -1, 1), angles[rel(1)] - getAngle(-1, 1, 2)];
@@ -35982,9 +34755,9 @@ function boundAndSnapToPolygonAngle(destinationPoint, {
35982
34755
  })) {
35983
34756
  return startingPoint;
35984
34757
  }
35985
- const knownSide = magnitude(
34758
+ const knownSide = magnitude$1(
35986
34759
  // @ts-expect-error - TS2345 - Argument of type 'number[]' is not assignable to parameter of type 'readonly Coord[]'.
35987
- vector(coordsCopy[rel(-1)], coordsCopy[rel(1)]));
34760
+ vector$1(coordsCopy[rel(-1)], coordsCopy[rel(1)]));
35988
34761
 
35989
34762
  // Returns true if the points form a counter-clockwise turn;
35990
34763
  // a.k.a if the point is on the left or right of the polygon.
@@ -35992,7 +34765,7 @@ function boundAndSnapToPolygonAngle(destinationPoint, {
35992
34765
  // we want to add the inner angle to the outer angle, and if it's on the right,
35993
34766
  // we want to subtract the inner angle from the outer angle. The angle solved
35994
34767
  // for is then used in the polar function to determine the new point.
35995
- const onLeft = sign(ccw(coordsCopy[rel(-1)], coordsCopy[rel(1)], coordsCopy[index])) === 1;
34768
+ const onLeft = sign$1(ccw$1(coordsCopy[rel(-1)], coordsCopy[rel(1)], coordsCopy[index])) === 1;
35996
34769
 
35997
34770
  // Solve for side by using the law of sines
35998
34771
  const side = Math.sin(innerAngles[1] * Math.PI / 180) / Math.sin(innerAngles[2] * Math.PI / 180) * knownSide;
@@ -36004,7 +34777,7 @@ function boundAndSnapToPolygonAngle(destinationPoint, {
36004
34777
  // and the angle between the first and second sides of the
36005
34778
  // polygon (angular coordinate) to determine how to adjust the point
36006
34779
  const offset = polar(side, outerAngle + (onLeft ? 1 : -1) * innerAngles[0]);
36007
- return vector$1.add(coordsCopy[rel(-1)], offset);
34780
+ return vector$3.add(coordsCopy[rel(-1)], offset);
36008
34781
  }
36009
34782
  function boundAndSnapToSides(destinationPoint, {
36010
34783
  range,
@@ -36030,7 +34803,7 @@ function boundAndSnapToSides(destinationPoint, {
36030
34803
  };
36031
34804
  const sides = _.map([[coordsCopy[rel(-1)], coordsCopy[index]], [coordsCopy[index], coordsCopy[rel(1)]], [coordsCopy[rel(-1)], coordsCopy[rel(1)]]], function (coordsCopy) {
36032
34805
  // @ts-expect-error - TS2345 - Argument of type 'number[]' is not assignable to parameter of type 'readonly Coord[]'. | TS2556 - A spread argument must either have a tuple type or be passed to a rest parameter.
36033
- return magnitude(vector(...coordsCopy));
34806
+ return magnitude$1(vector$1(...coordsCopy));
36034
34807
  });
36035
34808
 
36036
34809
  // Round the sides to left and right of the current point
@@ -36045,7 +34818,7 @@ function boundAndSnapToSides(destinationPoint, {
36045
34818
 
36046
34819
  // Solve for angle by using the law of cosines
36047
34820
  // Angle at the first vertex of the polygon
36048
- const innerAngle = lawOfCosines(sides[0], sides[2], sides[1]);
34821
+ const innerAngle = lawOfCosines$1(sides[0], sides[2], sides[1]);
36049
34822
 
36050
34823
  // Angle at the second vertex of the polygon
36051
34824
  const outerAngle = getAngleFromVertex(coordsCopy[rel(1)], coordsCopy[rel(-1)]);
@@ -36056,13 +34829,13 @@ function boundAndSnapToSides(destinationPoint, {
36056
34829
  // we want to add the inner angle to the outer angle, and if it's on the right,
36057
34830
  // we want to subtract the inner angle from the outer angle. The angle solved
36058
34831
  // for is then used in the polar function to determine the new point.
36059
- const onLeft = sign(ccw(coordsCopy[rel(-1)], coordsCopy[rel(1)], coordsCopy[index])) === 1;
34832
+ const onLeft = sign$1(ccw$1(coordsCopy[rel(-1)], coordsCopy[rel(1)], coordsCopy[index])) === 1;
36060
34833
 
36061
34834
  // Uses the length of the first side of the polygon (radial coordinate)
36062
34835
  // and the angle between the first and second sides of the
36063
34836
  // polygon (angular coordinate) to determine how to adjust the point
36064
34837
  const offset = polar(sides[0], outerAngle + (onLeft ? 1 : -1) * innerAngle);
36065
- return vector$1.add(coordsCopy[rel(-1)], offset);
34838
+ return vector$3.add(coordsCopy[rel(-1)], offset);
36066
34839
  }
36067
34840
 
36068
34841
  // Returns the vector from the given point to the top-right corner of the graph when snapped to the grid
@@ -36092,7 +34865,7 @@ function minMove({
36092
34865
  });
36093
34866
  return vec.sub(bottomLeft, point);
36094
34867
  }
36095
- const coordsOverlap = coords => coords.some((coord, i) => coords.some((c, j) => i !== j && vector$1.equal(coord, c)));
34868
+ const coordsOverlap = coords => coords.some((coord, i) => coords.some((c, j) => i !== j && vector$3.equal(coord, c)));
36096
34869
  function updateAtIndex(args) {
36097
34870
  const {
36098
34871
  array,
@@ -36187,216 +34960,26 @@ const StatefulMafsGraph = /*#__PURE__*/React.forwardRef((props, ref) => {
36187
34960
  }));
36188
34961
  });
36189
34962
 
36190
- const eq$1 = Util.eq;
36191
- const deepEq$3 = Util.deepEq;
36192
- function scoreInteractiveGraph(userInput, rubric) {
36193
- // None-type graphs are not graded
36194
- if (userInput.type === "none" && rubric.correct.type === "none") {
36195
- return {
36196
- type: "points",
36197
- earned: 0,
36198
- total: 0,
36199
- message: null
36200
- };
36201
- }
36202
-
36203
- // When nothing has moved, there will neither be coords nor the
36204
- // circle's center/radius fields. When those fields are absent, skip
36205
- // all these checks; just go mark the answer as empty.
36206
- const hasValue = Boolean(
36207
- // @ts-expect-error - TS2339 - Property 'coords' does not exist on type 'PerseusGraphType'.
36208
- userInput.coords ||
36209
- // @ts-expect-error - TS2339 - Property 'center' does not exist on type 'PerseusGraphType'. | TS2339 - Property 'radius' does not exist on type 'PerseusGraphType'.
36210
- userInput.center && userInput.radius);
36211
- if (userInput.type === rubric.correct.type && hasValue) {
36212
- if (userInput.type === "linear" && rubric.correct.type === "linear" && userInput.coords != null) {
36213
- const guess = userInput.coords;
36214
- const correct = rubric.correct.coords;
36215
-
36216
- // If both of the guess points are on the correct line, it's
36217
- // correct.
36218
- if (collinear(correct[0], correct[1], guess[0]) && collinear(correct[0], correct[1], guess[1])) {
36219
- return {
36220
- type: "points",
36221
- earned: 1,
36222
- total: 1,
36223
- message: null
36224
- };
36225
- }
36226
- } else if (userInput.type === "linear-system" && rubric.correct.type === "linear-system" && userInput.coords != null) {
36227
- const guess = userInput.coords;
36228
- const correct = rubric.correct.coords;
36229
- if (collinear(correct[0][0], correct[0][1], guess[0][0]) && collinear(correct[0][0], correct[0][1], guess[0][1]) && collinear(correct[1][0], correct[1][1], guess[1][0]) && collinear(correct[1][0], correct[1][1], guess[1][1]) || collinear(correct[0][0], correct[0][1], guess[1][0]) && collinear(correct[0][0], correct[0][1], guess[1][1]) && collinear(correct[1][0], correct[1][1], guess[0][0]) && collinear(correct[1][0], correct[1][1], guess[0][1])) {
36230
- return {
36231
- type: "points",
36232
- earned: 1,
36233
- total: 1,
36234
- message: null
36235
- };
36236
- }
36237
- } else if (userInput.type === "quadratic" && rubric.correct.type === "quadratic" && userInput.coords != null) {
36238
- // If the parabola coefficients match, it's correct.
36239
- const guessCoeffs = getQuadraticCoefficients(userInput.coords);
36240
- const correctCoeffs = getQuadraticCoefficients(rubric.correct.coords);
36241
- if (deepEq$3(guessCoeffs, correctCoeffs)) {
36242
- return {
36243
- type: "points",
36244
- earned: 1,
36245
- total: 1,
36246
- message: null
36247
- };
36248
- }
36249
- } else if (userInput.type === "sinusoid" && rubric.correct.type === "sinusoid" && userInput.coords != null) {
36250
- const guessCoeffs = getSinusoidCoefficients(userInput.coords);
36251
- const correctCoeffs = getSinusoidCoefficients(rubric.correct.coords);
36252
- const canonicalGuessCoeffs = canonicalSineCoefficients$1(guessCoeffs);
36253
- const canonicalCorrectCoeffs = canonicalSineCoefficients$1(correctCoeffs);
36254
- // If the canonical coefficients match, it's correct.
36255
- if (deepEq$3(canonicalGuessCoeffs, canonicalCorrectCoeffs)) {
36256
- return {
36257
- type: "points",
36258
- earned: 1,
36259
- total: 1,
36260
- message: null
36261
- };
36262
- }
36263
- } else if (userInput.type === "circle" && rubric.correct.type === "circle") {
36264
- if (deepEq$3(userInput.center, rubric.correct.center) && eq$1(userInput.radius, rubric.correct.radius)) {
36265
- return {
36266
- type: "points",
36267
- earned: 1,
36268
- total: 1,
36269
- message: null
36270
- };
36271
- }
36272
- } else if (userInput.type === "point" && rubric.correct.type === "point" && userInput.coords != null) {
36273
- let correct = rubric.correct.coords;
36274
- if (correct == null) {
36275
- throw new Error("Point graph rubric has null coords");
36276
- }
36277
- const guess = userInput.coords.slice();
36278
- correct = correct.slice();
36279
- // Everything's already rounded so we shouldn't need to do an
36280
- // eq() comparison but _.isEqual(0, -0) is false, so we'll use
36281
- // eq() anyway. The sort should be fine because it'll stringify
36282
- // it and -0 converted to a string is "0"
36283
- guess == null || guess.sort();
36284
- // @ts-expect-error - TS2339 - Property 'sort' does not exist on type 'readonly Coord[]'.
36285
- correct.sort();
36286
- if (deepEq$3(guess, correct)) {
36287
- return {
36288
- type: "points",
36289
- earned: 1,
36290
- total: 1,
36291
- message: null
36292
- };
36293
- }
36294
- } else if (userInput.type === "polygon" && rubric.correct.type === "polygon" && userInput.coords != null) {
36295
- const guess = userInput.coords.slice();
36296
- const correct = rubric.correct.coords.slice();
36297
- let match;
36298
- if (rubric.correct.match === "similar") {
36299
- match = similar(guess, correct, Number.POSITIVE_INFINITY);
36300
- } else if (rubric.correct.match === "congruent") {
36301
- match = similar(guess, correct, number$1.DEFAULT_TOLERANCE);
36302
- } else if (rubric.correct.match === "approx") {
36303
- match = similar(guess, correct, 0.1);
36304
- } else {
36305
- /* exact */
36306
- guess.sort();
36307
- correct.sort();
36308
- match = deepEq$3(guess, correct);
36309
- }
36310
- if (match) {
36311
- return {
36312
- type: "points",
36313
- earned: 1,
36314
- total: 1,
36315
- message: null
36316
- };
36317
- }
36318
- } else if (userInput.type === "segment" && rubric.correct.type === "segment" && userInput.coords != null) {
36319
- let guess = Util.deepClone(userInput.coords);
36320
- let correct = Util.deepClone(rubric.correct.coords);
36321
- guess = _.invoke(guess, "sort").sort();
36322
- correct = _.invoke(correct, "sort").sort();
36323
- if (deepEq$3(guess, correct)) {
36324
- return {
36325
- type: "points",
36326
- earned: 1,
36327
- total: 1,
36328
- message: null
36329
- };
36330
- }
36331
- } else if (userInput.type === "ray" && rubric.correct.type === "ray" && userInput.coords != null) {
36332
- const guess = userInput.coords;
36333
- const correct = rubric.correct.coords;
36334
- if (deepEq$3(guess[0], correct[0]) && collinear(correct[0], correct[1], guess[1])) {
36335
- return {
36336
- type: "points",
36337
- earned: 1,
36338
- total: 1,
36339
- message: null
36340
- };
36341
- }
36342
- } else if (userInput.type === "angle" && rubric.correct.type === "angle") {
36343
- const guess = userInput.coords;
36344
- const correct = rubric.correct.coords;
36345
- const allowReflexAngles = rubric.correct.allowReflexAngles;
36346
- let match;
36347
- if (rubric.correct.match === "congruent") {
36348
- const angles = _.map([guess, correct], function (coords) {
36349
- if (!coords) {
36350
- return false;
36351
- }
36352
- const angle = getClockwiseAngle(coords, allowReflexAngles);
36353
- return angle;
36354
- });
36355
- // @ts-expect-error - TS2556 - A spread argument must either have a tuple type or be passed to a rest parameter.
36356
- match = eq$1(...angles);
36357
- } else {
36358
- /* exact */
36359
- match =
36360
- // @ts-expect-error - TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'.
36361
- deepEq$3(guess[1], correct[1]) &&
36362
- // @ts-expect-error - TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'.
36363
- collinear(correct[1], correct[0], guess[0]) &&
36364
- // @ts-expect-error - TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'.
36365
- collinear(correct[1], correct[2], guess[2]);
36366
- }
36367
- if (match) {
36368
- return {
36369
- type: "points",
36370
- earned: 1,
36371
- total: 1,
36372
- message: null
36373
- };
36374
- }
36375
- }
36376
- }
36377
-
36378
- // The input wasn't correct, so check if it's a blank input or if it's
36379
- // actually just wrong
36380
- if (!hasValue || _.isEqual(userInput, rubric.graph)) {
36381
- // We're where we started.
36382
- return {
36383
- type: "invalid",
36384
- message: null
36385
- };
36386
- }
36387
- return {
36388
- type: "points",
36389
- earned: 0,
36390
- total: 1,
36391
- message: null
36392
- };
36393
- }
36394
-
34963
+ const {
34964
+ getClockwiseAngle
34965
+ } = angles;
34966
+ const {
34967
+ angleMeasures,
34968
+ ccw,
34969
+ collinear,
34970
+ getLineEquation,
34971
+ getLineIntersection,
34972
+ intersects,
34973
+ lawOfCosines,
34974
+ magnitude,
34975
+ rotate,
34976
+ sign,
34977
+ vector
34978
+ } = geometry;
36395
34979
  const TRASH_ICON_URI = "https://ka-perseus-graphie.s3.amazonaws.com/b1452c0d79fd0f7ff4c3af9488474a0a0decb361.png";
36396
34980
  const defaultBackgroundImage = {
36397
34981
  url: null
36398
34982
  };
36399
- const eq = Util.eq;
36400
34983
  const UNLIMITED = "unlimited";
36401
34984
 
36402
34985
  // Sample background image:
@@ -36409,7 +34992,7 @@ function defaultVal(actual, defaultValue) {
36409
34992
 
36410
34993
  // Less than or approximately equal
36411
34994
  function leq(a, b) {
36412
- return a < b || eq(a, b);
34995
+ return a < b || approximateEqual(a, b);
36413
34996
  }
36414
34997
  function capitalize(str) {
36415
34998
  return str.replace(/(?:^|-)(.)/g, function (match, letter) {
@@ -37236,7 +35819,7 @@ class LegacyInteractiveGraph extends React.Component {
37236
35819
  // Solve for side by using the law of sines
37237
35820
  const side = Math.sin(innerAngles[1] * Math.PI / 180) / Math.sin(innerAngles[2] * Math.PI / 180) * knownSide;
37238
35821
  const outerAngle = GraphUtils.findAngleDeprecated(coords[rel(1)], coords[rel(-1)]);
37239
- const offset = polar$1(side, outerAngle + (onLeft ? 1 : -1) * innerAngles[0]);
35822
+ const offset = polar$2(side, outerAngle + (onLeft ? 1 : -1) * innerAngles[0]);
37240
35823
  return (_this$graphie5 = this.graphie) == null ? void 0 : _this$graphie5.addPoints(coords[rel(-1)], offset);
37241
35824
  }
37242
35825
  // @ts-expect-error - TS2339 - Property 'snapTo' does not exist on type 'PerseusGraphType'.
@@ -37261,7 +35844,7 @@ class LegacyInteractiveGraph extends React.Component {
37261
35844
  const innerAngle = lawOfCosines(sides[0], sides[2], sides[1]);
37262
35845
  const outerAngle = GraphUtils.findAngleDeprecated(coords[rel(1)], coords[rel(-1)]);
37263
35846
  const onLeft = sign(ccw(coords[rel(-1)], coords[rel(1)], coords[i])) === 1;
37264
- const offset = polar$1(sides[0], outerAngle + (onLeft ? 1 : -1) * innerAngle);
35847
+ const offset = polar$2(sides[0], outerAngle + (onLeft ? 1 : -1) * innerAngle);
37265
35848
  return (_this$graphie6 = this.graphie) == null ? void 0 : _this$graphie6.addPoints(coords[rel(-1)], offset);
37266
35849
  }
37267
35850
  // Snap to grid (already done)
@@ -37923,12 +36506,12 @@ class InteractiveGraph extends React.Component {
37923
36506
  }
37924
36507
  static getLinearEquationString(props) {
37925
36508
  const coords = InteractiveGraph.getLineCoords(props.graph, props);
37926
- if (eq(coords[0][0], coords[1][0])) {
36509
+ if (approximateEqual(coords[0][0], coords[1][0])) {
37927
36510
  return "x = " + coords[0][0].toFixed(3);
37928
36511
  }
37929
36512
  const m = (coords[1][1] - coords[0][1]) / (coords[1][0] - coords[0][0]);
37930
36513
  const b = coords[0][1] - m * coords[0][0];
37931
- if (eq(m, 0)) {
36514
+ if (approximateEqual(m, 0)) {
37932
36515
  return "y = " + b.toFixed(3);
37933
36516
  }
37934
36517
  return "y = " + m.toFixed(3) + "x + " + b.toFixed(3);
@@ -38454,59 +37037,6 @@ const styles$a = StyleSheet.create({
38454
37037
  }
38455
37038
  });
38456
37039
 
38457
- // Question state for marker as result of user selected answers.
38458
-
38459
- function scoreMarker(marker) {
38460
- const score = {
38461
- hasAnswers: false,
38462
- isCorrect: false
38463
- };
38464
- if (marker.selected && marker.selected.length > 0) {
38465
- score.hasAnswers = true;
38466
- }
38467
- if (marker.answers.length > 0) {
38468
- if (marker.selected && marker.selected.length === marker.answers.length) {
38469
- // All correct answers are selected by the user.
38470
- score.isCorrect = marker.selected.every(choice => marker.answers.includes(choice));
38471
- }
38472
- } else if (!marker.selected || marker.selected.length === 0) {
38473
- // Correct as no answers should be selected by the user.
38474
- score.isCorrect = true;
38475
- }
38476
- return score;
38477
- }
38478
-
38479
- // TODO(LEMS-2440): May need to pull answers out of PerseusLabelImageWidgetOptions[markers] for the rubric
38480
- function scoreLabelImage(userInput, rubric) {
38481
- let numAnswered = 0;
38482
- let numCorrect = 0;
38483
- for (const marker of userInput.markers) {
38484
- const score = scoreMarker(marker);
38485
- if (score.hasAnswers) {
38486
- numAnswered++;
38487
- }
38488
- if (score.isCorrect) {
38489
- numCorrect++;
38490
- }
38491
- }
38492
-
38493
- // We expect all question markers to be answered before grading.
38494
- if (numAnswered !== userInput.markers.length) {
38495
- return {
38496
- type: "invalid",
38497
- message: null
38498
- };
38499
- }
38500
- return {
38501
- type: "points",
38502
- // Markers with no expected answers are graded as correct if user
38503
- // makes no answer selection.
38504
- earned: numCorrect === userInput.markers.length ? 1 : 0,
38505
- total: 1,
38506
- message: null
38507
- };
38508
- }
38509
-
38510
37040
  /**
38511
37041
  * Represents a direction vector.
38512
37042
  * No diagonals allowed.
@@ -38728,7 +37258,7 @@ class LabelImage extends React.Component {
38728
37258
  onChange
38729
37259
  } = this.props;
38730
37260
  const updatedMarkers = markers.map(marker => {
38731
- const score = scoreMarker(marker);
37261
+ const score = scoreLabelImageMarker(marker);
38732
37262
  return _extends({}, marker, {
38733
37263
  // Reveal correctness state for markers with answers.
38734
37264
  showCorrectness: score.hasAnswers ? score.isCorrect ? "correct" : "incorrect" : undefined
@@ -38875,7 +37405,7 @@ class LabelImage extends React.Component {
38875
37405
  bottom: "top"
38876
37406
  }[markerPosition];
38877
37407
  }
38878
- const score = scoreMarker(marker);
37408
+ const score = scoreLabelImageMarker(marker);
38879
37409
  // Once the question is answered, show markers
38880
37410
  // with correct answers, otherwise passthrough
38881
37411
  // the correctness state.
@@ -39807,16 +38337,6 @@ const getPromptJSON$9 = (renderProps, userInput) => {
39807
38337
  };
39808
38338
  };
39809
38339
 
39810
- function scoreMatcher(state, rubric) {
39811
- const correct = _.isEqual(state.left, rubric.left) && _.isEqual(state.right, rubric.right);
39812
- return {
39813
- type: "points",
39814
- earned: correct ? 1 : 0,
39815
- total: 1,
39816
- message: null
39817
- };
39818
- }
39819
-
39820
38340
  const {
39821
38341
  shuffle: shuffle$1,
39822
38342
  seededRNG
@@ -40039,78 +38559,6 @@ const getPromptJSON$8 = (renderProps, userInput) => {
40039
38559
  };
40040
38560
  };
40041
38561
 
40042
- /**
40043
- * Checks user input from the matrix widget to see if it is scorable.
40044
- *
40045
- * Note: The matrix widget cannot do much validation without the Scoring
40046
- * Data because of its use of KhanAnswerTypes as a core part of scoring.
40047
- *
40048
- * @see `scoreMatrix()` for more details.
40049
- */
40050
- function validateMatrix(userInput, validationData, strings) {
40051
- const supplied = userInput.answers;
40052
- const suppliedSize = getMatrixSize(supplied);
40053
- for (let row = 0; row < suppliedSize[0]; row++) {
40054
- for (let col = 0; col < suppliedSize[1]; col++) {
40055
- if (supplied[row][col] == null || supplied[row][col].toString().length === 0) {
40056
- return {
40057
- type: "invalid",
40058
- message: strings.fillAllCells
40059
- };
40060
- }
40061
- }
40062
- }
40063
- return null;
40064
- }
40065
-
40066
- function scoreMatrix(userInput, rubric, strings) {
40067
- const validationResult = validateMatrix(userInput, rubric, strings);
40068
- if (validationResult != null) {
40069
- return validationResult;
40070
- }
40071
- const solution = rubric.answers;
40072
- const supplied = userInput.answers;
40073
- const solutionSize = getMatrixSize(solution);
40074
- const suppliedSize = getMatrixSize(supplied);
40075
- const incorrectSize = solutionSize[0] !== suppliedSize[0] || solutionSize[1] !== suppliedSize[1];
40076
- const createValidator = KhanAnswerTypes.number.createValidatorFunctional;
40077
- let message = null;
40078
- let incorrect = false;
40079
- _(suppliedSize[0]).times(row => {
40080
- _(suppliedSize[1]).times(col => {
40081
- if (!incorrectSize) {
40082
- const validator = createValidator(
40083
- // @ts-expect-error - TS2345 - Argument of type 'number' is not assignable to parameter of type 'string'.
40084
- solution[row][col], {
40085
- simplify: true
40086
- });
40087
- const result = validator(supplied[row][col]);
40088
- if (result.message) {
40089
- // @ts-expect-error - TS2322 - Type 'string' is not assignable to type 'null'.
40090
- message = result.message;
40091
- }
40092
- if (!result.correct) {
40093
- incorrect = true;
40094
- }
40095
- }
40096
- });
40097
- });
40098
- if (incorrectSize) {
40099
- return {
40100
- type: "points",
40101
- earned: 0,
40102
- total: 1,
40103
- message: null
40104
- };
40105
- }
40106
- return {
40107
- type: "points",
40108
- earned: incorrect ? 0 : 1,
40109
- total: 1,
40110
- message: message
40111
- };
40112
- }
40113
-
40114
38562
  const {
40115
38563
  assert: assert$2
40116
38564
  } = InteractiveUtil;
@@ -40159,29 +38607,6 @@ const getRefForPath$1 = function getRefForPath(path) {
40159
38607
  const column = getColumnFromPath$1(path);
40160
38608
  return "answer" + row + "," + column;
40161
38609
  };
40162
- function getMatrixSize(matrix) {
40163
- const matrixSize = [1, 1];
40164
-
40165
- // We need to find the widest row and tallest column to get the correct
40166
- // matrix size.
40167
- _(matrix).each((matrixRow, row) => {
40168
- let rowWidth = 0;
40169
- _(matrixRow).each((matrixCol, col) => {
40170
- if (matrixCol != null && matrixCol.toString().length) {
40171
- rowWidth = col + 1;
40172
- }
40173
- });
40174
-
40175
- // Matrix width:
40176
- matrixSize[1] = Math.max(matrixSize[1], rowWidth);
40177
-
40178
- // Matrix height:
40179
- if (rowWidth > 0) {
40180
- matrixSize[0] = Math.max(matrixSize[0], row + 1);
40181
- }
40182
- });
40183
- return matrixSize;
40184
- }
40185
38610
  class Matrix extends React.Component {
40186
38611
  constructor(...args) {
40187
38612
  super(...args);
@@ -41572,59 +39997,6 @@ const getPromptJSON$6 = (renderProps, userInput) => {
41572
39997
  };
41573
39998
  };
41574
39999
 
41575
- /**
41576
- * Checks user input is within the allowed range and not the same as the initial
41577
- * state.
41578
- * @param userInput
41579
- * @see 'scoreNumberLine' for the scoring logic.
41580
- */
41581
- function validateNumberLine(userInput) {
41582
- const divisionRange = userInput.divisionRange;
41583
- const outsideAllowedRange = userInput.numDivisions > divisionRange[1] || userInput.numDivisions < divisionRange[0];
41584
-
41585
- // TODO: I don't think isTickCrtl is a thing anymore
41586
- if (userInput.isTickCrtl && outsideAllowedRange) {
41587
- return {
41588
- type: "invalid",
41589
- message: "Number of divisions is outside the allowed range."
41590
- };
41591
- }
41592
- return null;
41593
- }
41594
-
41595
- function scoreNumberLine(userInput, scoringData) {
41596
- const validationError = validateNumberLine(userInput);
41597
- if (validationError) {
41598
- return validationError;
41599
- }
41600
- const range = scoringData.range;
41601
- const start = scoringData.initialX != null ? scoringData.initialX : range[0];
41602
- const startRel = scoringData.isInequality ? "ge" : "eq";
41603
- const correctRel = scoringData.correctRel || "eq";
41604
- const correctPos = number$1.equal(userInput.numLinePosition, scoringData.correctX || 0);
41605
- if (correctPos && correctRel === userInput.rel) {
41606
- return {
41607
- type: "points",
41608
- earned: 1,
41609
- total: 1,
41610
- message: null
41611
- };
41612
- }
41613
- if (userInput.numLinePosition === start && userInput.rel === startRel) {
41614
- // We're where we started.
41615
- return {
41616
- type: "invalid",
41617
- message: null
41618
- };
41619
- }
41620
- return {
41621
- type: "points",
41622
- earned: 0,
41623
- total: 1,
41624
- message: null
41625
- };
41626
- }
41627
-
41628
40000
  // @ts-expect-error - TS2339 - Property 'MovablePoint' does not exist on type 'typeof Graphie'.
41629
40001
  const MovablePoint = Graphie.MovablePoint;
41630
40002
  // @ts-expect-error - TS2339 - Property 'Line' does not exist on type 'typeof Graphie'.
@@ -42193,36 +40565,6 @@ const getPromptJSON$5 = (renderProps, userInput) => {
42193
40565
  };
42194
40566
  };
42195
40567
 
42196
- /**
42197
- * Checks user input from the orderer widget to see if the user has started
42198
- * ordering the options, making the widget scorable.
42199
- * @param userInput
42200
- * @see `scoreOrderer` for more details.
42201
- */
42202
- function validateOrderer(userInput) {
42203
- if (userInput.current.length === 0) {
42204
- return {
42205
- type: "invalid",
42206
- message: null
42207
- };
42208
- }
42209
- return null;
42210
- }
42211
-
42212
- function scoreOrderer(userInput, rubric) {
42213
- const validateError = validateOrderer(userInput);
42214
- if (validateError) {
42215
- return validateError;
42216
- }
42217
- const correct = _.isEqual(userInput.current, rubric.correctOptions.map(option => option.content));
42218
- return {
42219
- type: "points",
42220
- earned: correct ? 1 : 0,
42221
- total: 1,
42222
- message: null
42223
- };
42224
- }
42225
-
42226
40568
  class PlaceholderCard extends React.Component {
42227
40569
  render() {
42228
40570
  return /*#__PURE__*/React.createElement("div", {
@@ -43038,42 +41380,6 @@ const getPromptJSON$3 = () => {
43038
41380
  return getUnsupportedPromptJSON("plotter");
43039
41381
  };
43040
41382
 
43041
- const {
43042
- deepEq: deepEq$2
43043
- } = Util;
43044
-
43045
- /**
43046
- * Checks user input to confirm it is not the same as the starting values for the graph.
43047
- * This means the user has modified the graph, and the question can be scored.
43048
- *
43049
- * @see 'scorePlotter' for more details on scoring.
43050
- */
43051
- function validatePlotter(userInput, validationData) {
43052
- if (deepEq$2(userInput, validationData.starting)) {
43053
- return {
43054
- type: "invalid",
43055
- message: null
43056
- };
43057
- }
43058
- return null;
43059
- }
43060
-
43061
- const {
43062
- deepEq: deepEq$1
43063
- } = Util;
43064
- function scorePlotter(userInput, scoringData) {
43065
- const validationError = validatePlotter(userInput, scoringData);
43066
- if (validationError) {
43067
- return validationError;
43068
- }
43069
- return {
43070
- type: "points",
43071
- earned: deepEq$1(userInput, scoringData.correct) ? 1 : 0,
43072
- total: 1,
43073
- message: null
43074
- };
43075
- }
43076
-
43077
41383
  /* eslint-disable react/no-unsafe */
43078
41384
  class Plotter extends React.Component {
43079
41385
  constructor(..._args) {
@@ -43953,43 +42259,6 @@ const getPromptJSON$1 = userInput => {
43953
42259
  };
43954
42260
  };
43955
42261
 
43956
- /**
43957
- * Checks user input for the sorter widget to ensure that the user has made
43958
- * changes before attempting to score the widget.
43959
- * @param userInput
43960
- * @see 'scoreSorter' in 'packages/perseus/src/widgets/sorter/score-sorter.ts'
43961
- * for more details on how the sorter widget is scored.
43962
- */
43963
- function validateSorter(userInput) {
43964
- // If the sorter widget hasn't been changed yet, we treat it as "empty" which
43965
- // prevents the "Check" button from becoming active. We want the user
43966
- // to make a change before trying to move forward. This makes an
43967
- // assumption that the initial order isn't the correct order! However,
43968
- // this should be rare if it happens, and interacting with the list
43969
- // will enable the button, so they won't be locked out of progressing.
43970
- if (!userInput.changed) {
43971
- return {
43972
- type: "invalid",
43973
- message: null
43974
- };
43975
- }
43976
- return null;
43977
- }
43978
-
43979
- function scoreSorter(userInput, rubric) {
43980
- const validationError = validateSorter(userInput);
43981
- if (validationError) {
43982
- return validationError;
43983
- }
43984
- const correct = Util.deepEq(userInput.options, rubric.correct);
43985
- return {
43986
- type: "points",
43987
- earned: correct ? 1 : 0,
43988
- total: 1,
43989
- message: null
43990
- };
43991
- }
43992
-
43993
42262
  const {
43994
42263
  shuffle
43995
42264
  } = Util;
@@ -44082,81 +42351,6 @@ var Sorter$1 = {
44082
42351
  scorer: scoreSorter
44083
42352
  };
44084
42353
 
44085
- /**
44086
- * Filters the given table (modelled as a 2D array) to remove any rows that are
44087
- * completely empty.
44088
- *
44089
- * @returns A new table with only non-empty rows.
44090
- */
44091
- const filterNonEmpty = function filterNonEmpty(table) {
44092
- return table.filter(function (row) {
44093
- // Return only rows that are non-empty.
44094
- return row.some(cell => cell);
44095
- });
44096
- };
44097
-
44098
- function validateTable(userInput) {
44099
- const supplied = filterNonEmpty(userInput);
44100
- const hasEmptyCell = supplied.some(function (row) {
44101
- return row.some(function (cell) {
44102
- return cell === "";
44103
- });
44104
- });
44105
- if (hasEmptyCell || !supplied.length) {
44106
- return {
44107
- type: "invalid",
44108
- message: null
44109
- };
44110
- }
44111
- return null;
44112
- }
44113
-
44114
- function scoreTable(userInput, rubric, strings) {
44115
- const validationResult = validateTable(userInput);
44116
- if (validationResult != null) {
44117
- return validationResult;
44118
- }
44119
- const supplied = filterNonEmpty(userInput);
44120
- const solution = filterNonEmpty(rubric.answers);
44121
- if (supplied.length !== solution.length) {
44122
- return {
44123
- type: "points",
44124
- earned: 0,
44125
- total: 1,
44126
- message: null
44127
- };
44128
- }
44129
- const createValidator = KhanAnswerTypes.number.createValidatorFunctional;
44130
- let message = null;
44131
- const allCorrect = solution.every(function (rowSolution) {
44132
- for (let i = 0; i < supplied.length; i++) {
44133
- const rowSupplied = supplied[i];
44134
- const correct = rowSupplied.every(function (cellSupplied, i) {
44135
- const cellSolution = rowSolution[i];
44136
- const validator = createValidator(cellSolution, {
44137
- simplify: true
44138
- });
44139
- const result = validator(cellSupplied);
44140
- if (result.message) {
44141
- message = result.message;
44142
- }
44143
- return result.correct;
44144
- });
44145
- if (correct) {
44146
- supplied.splice(i, 1);
44147
- return true;
44148
- }
44149
- }
44150
- return false;
44151
- });
44152
- return {
44153
- type: "points",
44154
- earned: allCorrect ? 1 : 0,
44155
- total: 1,
44156
- message
44157
- };
44158
- }
44159
-
44160
42354
  const {
44161
42355
  assert
44162
42356
  } = InteractiveUtil;
@@ -44559,7 +42753,7 @@ const init = function init() {
44559
42753
 
44560
42754
  // This file is processed by a Rollup plugin (replace) to inject the production
44561
42755
  const libName = "@khanacademy/perseus";
44562
- const libVersion = "50.0.0";
42756
+ const libVersion = "51.0.0";
44563
42757
  addLibraryVersionToPerseusDebug(libName, libVersion);
44564
42758
 
44565
42759
  const apiVersion = {
@@ -46520,7 +44714,6 @@ const getValidWidgetIds = perseusItem => {
46520
44714
  return keys(widgets).filter(id => isWidgetIdInContent(perseusItem, id));
46521
44715
  };
46522
44716
 
46523
- const deepEq = Util.deepEq;
46524
44717
  function isRealJSONParse(jsonParse) {
46525
44718
  const randomPhrase = buildRandomPhrase();
46526
44719
  const randomHintPhrase = buildRandomPhrase();
@@ -46577,7 +44770,7 @@ function isRealJSONParse(jsonParse) {
46577
44770
  const testJSON = buildTestData(testingObject.replace(/"/g, '\\"'));
46578
44771
  const parsedTestJSON = jsonParse(testJSON);
46579
44772
  const parsedTestItemData = parsedTestJSON.data.assessmentItem.item.itemData;
46580
- return deepEq(parsedTestItemData, testingObject);
44773
+ return approximateDeepEqual(parsedTestItemData, testingObject);
46581
44774
  }
46582
44775
  function buildRandomString(capitalize = false) {
46583
44776
  let randomString = "";
@@ -46777,7 +44970,9 @@ function object(schema) {
46777
44970
  for (const [prop, propParser] of Object.entries(schema)) {
46778
44971
  const result = propParser(rawValue[prop], ctx.forSubtree(prop));
46779
44972
  if (isSuccess(result)) {
46780
- ret[prop] = result.value;
44973
+ if (result.value !== undefined || prop in rawValue) {
44974
+ ret[prop] = result.value;
44975
+ }
46781
44976
  } else {
46782
44977
  mismatches.push(...result.detail);
46783
44978
  }
@@ -48386,7 +46581,6 @@ const EditorJsonify = {
48386
46581
  * Main entry point
48387
46582
  */
48388
46583
  const GrapherUtil = {
48389
- allTypes,
48390
46584
  DEFAULT_GRAPHER_PROPS,
48391
46585
  chooseType,
48392
46586
  defaultPlotProps,
@@ -48401,5 +46595,5 @@ const ScoringUtil = {
48401
46595
  * Types
48402
46596
  */
48403
46597
 
48404
- export { ApiOptions, ArticleRenderer, BaseRadio, Categorizer$1 as Categorizer, changeable as Changeable, ClassNames, dependencies as Dependencies, EditorJsonify, Expression, GrapherUtil, Grapher$1 as GrapherWidget, HintRenderer, HintsRenderer, InputNumber$1 as InputNumber, InteractiveGraph$1 as InteractiveGraphWidget, JiptParagraphs, KhanColors, context as LoadingContext, Log, Matrix$1 as MatrixWidget, NumericInput$1 as NumericInput, PerseusI18nContext, PerseusI18nContextProvider, PerseusMarkdown, Plotter$1 as PlotterWidget, QUESTION_WIDGETS, Radio, Renderer, ScoringUtil, ref as ServerItemRenderer, Table$1 as TableWidget, Util, denylist as WIDGET_PROP_DENYLIST, widgets$1 as Widgets, addWidget, apiVersion, bodyXsmallBold, components, containerSizeClass, contentHasWidgetType, convertWidgetNameToEnum, displaySigFigs, generateTestCategorizerWidget, generateTestExpressionWidget, generateTestInteractiveGraphWidget, generateTestNumericInputWidget, generateTestPerseusItem, generateTestRadioWidget, genericPerseusItemData, getAngleCoords, getAnswerFromUserInput, getAnswersFromWidgets, getCircleCoords, getClockwiseAngle, getCorrectAnswerForWidgetId, getImagesWithoutAltData, getInteractiveBoxFromSizeClass, getLineCoords, getLinearSystemCoords, getPointCoords, getPolygonCoords, getQuadraticCoords, getSegmentCoords, getSinusoidCoords, getValidWidgetIds, getWidgetFromWidgetMap, getWidgetIdsFromContent, getWidgetIdsFromContentByType, getWidgetTypeByWidgetId, getWidgetsFromWidgetMap, getWidgetsMapFromItemData, iconChevronDown, iconTrash, init, injectWidgets, interactiveSizes$1 as interactiveSizes, isFailure, isItemRenderableByVersion, isSuccess, isWidgetIdInContent, isWrongAnswerSupported, itemDataVersion, ItemVersion as itemVersion, libVersion, makeSafeUrl, mathOnlyParser, mediaQueries, parseAndMigratePerseusArticle, parseAndMigratePerseusItem, parseDataFromJSONP, parsePerseusItem, preprocessTex, registerAllWidgetsForTesting, scorePerseusItem, shouldHaveIndividualAnswer, traverse, usePerseusI18n, violatingWidgets, allWidgets as widgets };
46598
+ export { ApiOptions, ArticleRenderer, BaseRadio, Categorizer$1 as Categorizer, changeable as Changeable, ClassNames, dependencies as Dependencies, EditorJsonify, Expression, GrapherUtil, Grapher$1 as GrapherWidget, HintRenderer, HintsRenderer, InputNumber$1 as InputNumber, InteractiveGraph$1 as InteractiveGraphWidget, JiptParagraphs, KhanColors, context as LoadingContext, Log, Matrix$1 as MatrixWidget, NumericInput$1 as NumericInput, PerseusI18nContext, PerseusI18nContextProvider, PerseusMarkdown, Plotter$1 as PlotterWidget, QUESTION_WIDGETS, Radio, Renderer, ScoringUtil, ref as ServerItemRenderer, Table$1 as TableWidget, Util, denylist as WIDGET_PROP_DENYLIST, widgets$1 as Widgets, addWidget, apiVersion, bodyXsmallBold, components, containerSizeClass, contentHasWidgetType, convertWidgetNameToEnum, displaySigFigs, generateTestCategorizerWidget, generateTestExpressionWidget, generateTestInteractiveGraphWidget, generateTestNumericInputWidget, generateTestPerseusItem, generateTestRadioWidget, genericPerseusItemData, getAngleCoords, getAnswerFromUserInput, getAnswersFromWidgets, getCircleCoords, getCorrectAnswerForWidgetId, getImagesWithoutAltData, getInteractiveBoxFromSizeClass, getLineCoords, getLinearSystemCoords, getPointCoords, getPolygonCoords, getQuadraticCoords, getSegmentCoords, getSinusoidCoords, getValidWidgetIds, getWidgetFromWidgetMap, getWidgetIdsFromContent, getWidgetIdsFromContentByType, getWidgetTypeByWidgetId, getWidgetsFromWidgetMap, getWidgetsMapFromItemData, iconChevronDown, iconTrash, init, injectWidgets, interactiveSizes$1 as interactiveSizes, isFailure, isItemRenderableByVersion, isSuccess, isWidgetIdInContent, isWrongAnswerSupported, itemDataVersion, ItemVersion as itemVersion, libVersion, makeSafeUrl, mathOnlyParser, mediaQueries, parseAndMigratePerseusArticle, parseAndMigratePerseusItem, parseDataFromJSONP, parsePerseusItem, preprocessTex, registerAllWidgetsForTesting, scorePerseusItem, shouldHaveIndividualAnswer, traverse, usePerseusI18n, violatingWidgets, allWidgets as widgets };
48405
46599
  //# sourceMappingURL=index.js.map